aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am93
-rw-r--r--src/codec/Makefile.am18
-rw-r--r--src/codec/ecu.c20
-rw-r--r--src/codec/ecu_fr.c387
-rw-r--r--src/codec/ecu_fr_old.c166
-rw-r--r--src/codec/gsm610.c160
-rw-r--r--src/codec/gsm620.c55
-rw-r--r--src/codec/gsm660.c166
-rw-r--r--src/codec/gsm690.c4
-rw-r--r--src/coding/Makefile.am21
-rw-r--r--src/coding/gsm0503_amr_dtx.c191
-rw-r--r--src/coding/gsm0503_coding.c1125
-rw-r--r--src/coding/gsm0503_interleaving.c56
-rw-r--r--src/coding/gsm0503_mapping.c4
-rw-r--r--src/coding/gsm0503_parity.c4
-rw-r--r--src/coding/gsm0503_tables.c4
-rw-r--r--src/coding/libosmocoding.map23
-rw-r--r--src/core/Makefile.am166
-rw-r--r--src/core/application.c (renamed from src/application.c)4
-rw-r--r--src/core/backtrace.c (renamed from src/backtrace.c)4
-rw-r--r--src/core/base64.c195
-rw-r--r--src/core/bitcomp.c (renamed from src/bitcomp.c)4
-rw-r--r--src/core/bits.c (renamed from src/bits.c)14
-rw-r--r--src/core/bitvec.c (renamed from src/bitvec.c)37
-rw-r--r--src/core/context.c (renamed from src/context.c)9
-rw-r--r--src/core/conv.c (renamed from src/conv.c)199
-rw-r--r--src/core/conv_acc.c (renamed from src/conv_acc.c)27
-rw-r--r--src/core/conv_acc_generic.c (renamed from src/conv_acc_generic.c)4
-rw-r--r--src/core/conv_acc_neon.c (renamed from src/conv_acc_neon.c)4
-rw-r--r--src/core/conv_acc_neon_impl.h (renamed from src/conv_acc_neon_impl.h)4
-rw-r--r--src/core/conv_acc_sse.c (renamed from src/conv_acc_sse.c)4
-rw-r--r--src/core/conv_acc_sse_avx.c (renamed from src/conv_acc_sse_avx.c)4
-rw-r--r--src/core/conv_acc_sse_impl.h (renamed from src/conv_acc_sse_impl.h)4
-rw-r--r--src/core/counter.c (renamed from src/counter.c)6
-rw-r--r--src/core/crc16.c (renamed from src/crc16.c)0
-rw-r--r--src/core/crcXXgen.c.tpl (renamed from src/crcXXgen.c.tpl)4
-rw-r--r--src/core/exec.c (renamed from src/exec.c)23
-rw-r--r--src/core/fsm.c (renamed from src/fsm.c)42
-rw-r--r--src/core/gsmtap_util.c (renamed from src/gsmtap_util.c)241
-rw-r--r--src/core/isdnhdlc.c (renamed from src/isdnhdlc.c)11
-rw-r--r--src/core/it_q.c269
-rw-r--r--src/core/libosmocore.map631
-rw-r--r--src/core/logging.c (renamed from src/logging.c)690
-rw-r--r--src/core/logging_gsmtap.c (renamed from src/logging_gsmtap.c)18
-rw-r--r--src/core/logging_syslog.c (renamed from src/logging_syslog.c)6
-rw-r--r--src/core/logging_systemd.c (renamed from src/logging_systemd.c)4
-rw-r--r--src/core/loggingrb.c (renamed from src/loggingrb.c)4
-rw-r--r--src/core/macaddr.c (renamed from src/macaddr.c)4
-rw-r--r--src/core/mnl.c111
-rw-r--r--src/core/msgb.c (renamed from src/msgb.c)62
-rw-r--r--src/core/msgfile.c (renamed from src/msgfile.c)4
-rw-r--r--src/core/netdev.c962
-rw-r--r--src/core/netns.c208
-rw-r--r--src/core/osmo_io.c1007
-rw-r--r--src/core/osmo_io_internal.h166
-rw-r--r--src/core/osmo_io_poll.c201
-rw-r--r--src/core/osmo_io_uring.c532
-rw-r--r--src/core/panic.c (renamed from src/panic.c)6
-rw-r--r--src/core/plugin.c (renamed from src/plugin.c)6
-rw-r--r--src/core/prbs.c (renamed from src/prbs.c)0
-rw-r--r--src/core/prim.c (renamed from src/prim.c)0
-rw-r--r--src/core/probes.d6
-rw-r--r--src/core/rate_ctr.c (renamed from src/rate_ctr.c)89
-rw-r--r--src/core/rbtree.c (renamed from src/rbtree.c)5
-rw-r--r--src/core/select.c (renamed from src/select.c)172
-rw-r--r--src/core/sercomm.c (renamed from src/sercomm.c)4
-rw-r--r--src/core/serial.c (renamed from src/serial.c)4
-rw-r--r--src/core/signal.c (renamed from src/signal.c)4
-rw-r--r--src/core/sockaddr_str.c (renamed from src/sockaddr_str.c)26
-rw-r--r--src/core/socket.c (renamed from src/socket.c)1323
-rw-r--r--src/core/soft_uart.c518
-rw-r--r--src/core/stat_item.c463
-rw-r--r--src/core/stat_item_internal.h35
-rw-r--r--src/core/stats.c (renamed from src/stats.c)92
-rw-r--r--src/core/stats_statsd.c (renamed from src/stats_statsd.c)65
-rw-r--r--src/core/stats_tcp.c327
-rw-r--r--src/core/strrb.c (renamed from src/strrb.c)8
-rw-r--r--src/core/tdef.c (renamed from src/tdef.c)35
-rw-r--r--src/core/thread.c56
-rw-r--r--src/core/time_cc.c228
-rw-r--r--src/core/timer.c (renamed from src/timer.c)15
-rw-r--r--src/core/timer_clockgettime.c (renamed from src/timer_clockgettime.c)4
-rw-r--r--src/core/timer_gettimeofday.c (renamed from src/timer_gettimeofday.c)4
-rw-r--r--src/core/tun.c577
-rw-r--r--src/core/use_count.c (renamed from src/use_count.c)6
-rw-r--r--src/core/utils.c (renamed from src/utils.c)218
-rw-r--r--src/core/write_queue.c (renamed from src/write_queue.c)28
-rw-r--r--src/ctrl/Makefile.am8
-rw-r--r--src/ctrl/control_cmd.c106
-rw-r--r--src/ctrl/control_if.c169
-rw-r--r--src/ctrl/control_vty.c17
-rw-r--r--src/ctrl/libosmoctrl.map3
-rw-r--r--src/gb/Makefile.am30
-rw-r--r--src/gb/bssgp_bvc_fsm.c857
-rw-r--r--src/gb/common_vty.c10
-rw-r--r--src/gb/common_vty.h2
-rw-r--r--src/gb/frame_relay.c1051
-rw-r--r--src/gb/gprs_bssgp.c248
-rw-r--r--src/gb/gprs_bssgp2.c486
-rw-r--r--src/gb/gprs_bssgp_bss.c40
-rw-r--r--src/gb/gprs_bssgp_internal.h2
-rw-r--r--src/gb/gprs_bssgp_rim.c1261
-rw-r--r--src/gb/gprs_bssgp_util.c345
-rw-r--r--src/gb/gprs_bssgp_vty.c2
-rw-r--r--src/gb/gprs_ns.c67
-rw-r--r--src/gb/gprs_ns2.c1059
-rw-r--r--src/gb/gprs_ns2_fr.c988
-rw-r--r--src/gb/gprs_ns2_frgre.c158
-rw-r--r--src/gb/gprs_ns2_internal.h311
-rw-r--r--src/gb/gprs_ns2_message.c395
-rw-r--r--src/gb/gprs_ns2_sns.c2688
-rw-r--r--src/gb/gprs_ns2_udp.c368
-rw-r--r--src/gb/gprs_ns2_vc_fsm.c618
-rw-r--r--src/gb/gprs_ns2_vty.c2519
-rw-r--r--src/gb/libosmogb.map93
-rw-r--r--src/gsm/Makefile.am32
-rw-r--r--src/gsm/a5.c4
-rw-r--r--src/gsm/abis_nm.c100
-rw-r--r--src/gsm/apn.c2
-rw-r--r--src/gsm/auth_comp128v1.c7
-rw-r--r--src/gsm/auth_comp128v23.c10
-rw-r--r--src/gsm/auth_core.c172
-rw-r--r--src/gsm/auth_milenage.c35
-rw-r--r--src/gsm/auth_tuak.c207
-rw-r--r--src/gsm/auth_xor.c28
-rw-r--r--src/gsm/auth_xor_2g.c81
-rw-r--r--src/gsm/bsslap.c6
-rw-r--r--src/gsm/bssmap_le.c85
-rw-r--r--src/gsm/bts_features.c57
-rw-r--r--src/gsm/cbsp.c309
-rw-r--r--src/gsm/comp128.c4
-rw-r--r--src/gsm/comp128v23.c4
-rw-r--r--src/gsm/gea.c4
-rw-r--r--src/gsm/gprs_cipher_core.c4
-rw-r--r--src/gsm/gprs_gea.c4
-rw-r--r--src/gsm/gprs_rlc.c3
-rw-r--r--src/gsm/gsm0341.c4
-rw-r--r--src/gsm/gsm0411_utils.c6
-rw-r--r--src/gsm/gsm0480.c4
-rw-r--r--src/gsm/gsm0502.c42
-rw-r--r--src/gsm/gsm0808.c984
-rw-r--r--src/gsm/gsm0808_utils.c890
-rw-r--r--src/gsm/gsm23003.c195
-rw-r--r--src/gsm/gsm23236.c26
-rw-r--r--src/gsm/gsm29118.c4
-rw-r--r--src/gsm/gsm29205.c4
-rw-r--r--src/gsm/gsm44021.c303
-rw-r--r--src/gsm/gsm44068.c121
-rw-r--r--src/gsm/gsm48.c260
-rw-r--r--src/gsm/gsm48_arfcn_range_encode.c4
-rw-r--r--src/gsm/gsm48_ie.c59
-rw-r--r--src/gsm/gsm48_rest_octets.c249
-rw-r--r--src/gsm/gsm_utils.c80
-rw-r--r--src/gsm/gsup.c125
-rw-r--r--src/gsm/ipa.c50
-rw-r--r--src/gsm/iuup.c1064
-rw-r--r--src/gsm/kasumi.c4
-rw-r--r--src/gsm/kdf.c163
-rw-r--r--src/gsm/kdf/common.h101
-rw-r--r--src/gsm/kdf/crypto.h470
-rw-r--r--src/gsm/kdf/sha1-internal.c307
-rw-r--r--src/gsm/kdf/sha1.c162
-rw-r--r--src/gsm/kdf/sha1.h33
-rw-r--r--src/gsm/kdf/sha1_i.h29
-rw-r--r--src/gsm/kdf/sha256-internal.c231
-rw-r--r--src/gsm/kdf/sha256.c156
-rw-r--r--src/gsm/kdf/sha256.h30
-rw-r--r--src/gsm/kdf/sha256_i.h31
-rw-r--r--src/gsm/lapdm.c315
-rw-r--r--src/gsm/libosmogsm.map133
-rw-r--r--src/gsm/milenage/milenage.c8
-rw-r--r--src/gsm/mncc.c2
-rw-r--r--src/gsm/rlp.c243
-rw-r--r--src/gsm/rsl.c85
-rw-r--r--src/gsm/rxlev_stat.c4
-rw-r--r--src/gsm/sysinfo.c2
-rw-r--r--src/gsm/tlv_parser.c170
-rw-r--r--src/gsm/tuak/KeccakP-1600-3gpp.c176
-rw-r--r--src/gsm/tuak/KeccakP-1600-3gpp.h25
-rw-r--r--src/gsm/tuak/tuak.c372
-rw-r--r--src/gsm/tuak/tuak.h33
-rw-r--r--src/isdn/Makefile.am26
-rw-r--r--src/isdn/i460_mux.c (renamed from src/gsm/i460_mux.c)36
-rw-r--r--src/isdn/lapd_core.c (renamed from src/gsm/lapd_core.c)293
-rw-r--r--src/isdn/libosmoisdn.map52
-rw-r--r--src/isdn/v110.c590
-rw-r--r--src/isdn/v110_ta.c881
-rw-r--r--src/pseudotalloc/Makefile.am3
-rw-r--r--src/pseudotalloc/pseudotalloc.c22
-rw-r--r--src/pseudotalloc/talloc.h3
-rw-r--r--src/sim/Makefile.am11
-rw-r--r--src/sim/card_fs_hpsim.c4
-rw-r--r--src/sim/card_fs_isim.c4
-rw-r--r--src/sim/card_fs_sim.c4
-rw-r--r--src/sim/card_fs_tetra.c4
-rw-r--r--src/sim/card_fs_uicc.c4
-rw-r--r--src/sim/card_fs_usim.c4
-rw-r--r--src/sim/class_tables.c94
-rw-r--r--src/sim/core.c4
-rw-r--r--src/sim/reader.c28
-rw-r--r--src/sim/reader_pcsc.c63
-rw-r--r--src/stat_item.c388
-rw-r--r--src/usb/Makefile.am11
-rw-r--r--src/usb/osmo_libusb.c26
-rw-r--r--src/vty/Makefile.am8
-rw-r--r--src/vty/command.c170
-rw-r--r--src/vty/cpu_sched_vty.c17
-rw-r--r--src/vty/fsm_vty.c52
-rw-r--r--src/vty/logging_vty.c125
-rw-r--r--src/vty/stats_vty.c213
-rw-r--r--src/vty/talloc_ctx_vty.c11
-rw-r--r--src/vty/tdef_vty.c9
-rw-r--r--src/vty/telnet_interface.c59
-rw-r--r--src/vty/utils.c114
-rw-r--r--src/vty/vector.c5
-rw-r--r--src/vty/vty.c55
216 files changed, 33840 insertions, 5081 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index b2c9204f..86066466 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1,80 +1,13 @@
-# 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 modifications: https://www.gnu.org/software/libtool/manual/html_node/Versioning.html
-LIBVERSION=16:0:0
-
-AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_builddir)/include
-AM_CFLAGS = -Wall $(TALLOC_CFLAGS) $(PTHREAD_CFLAGS) $(LIBSCTP_CFLAGS)
-
-if ENABLE_PSEUDOTALLOC
-AM_CPPFLAGS += -I$(top_srcdir)/src/pseudotalloc
-endif
-
-lib_LTLIBRARIES = libosmocore.la
-
-libosmocore_la_LIBADD = $(BACKTRACE_LIB) $(TALLOC_LIBS) $(LIBRARY_RT) $(PTHREAD_LIBS) $(LIBSCTP_LIBS)
-libosmocore_la_SOURCES = context.c timer.c timer_gettimeofday.c timer_clockgettime.c \
- select.c signal.c msgb.c bits.c \
- bitvec.c bitcomp.c counter.c fsm.c \
- write_queue.c utils.c socket.c \
- logging.c logging_syslog.c logging_gsmtap.c rate_ctr.c \
- gsmtap_util.c crc16.c panic.c backtrace.c \
- conv.c application.c rbtree.c strrb.c \
- loggingrb.c crc8gen.c crc16gen.c crc32gen.c crc64gen.c \
- macaddr.c stat_item.c stats.c stats_statsd.c prim.c \
- conv_acc.c conv_acc_generic.c sercomm.c prbs.c \
- isdnhdlc.c \
- tdef.c \
- sockaddr_str.c \
- use_count.c \
- exec.c \
- $(NULL)
-
-if HAVE_SSSE3
-libosmocore_la_SOURCES += conv_acc_sse.c
-if HAVE_SSE4_1
-conv_acc_sse.lo : AM_CFLAGS += -mssse3 -msse4.1
-else
-conv_acc_sse.lo : AM_CFLAGS += -mssse3
-endif
-
-if HAVE_AVX2
-libosmocore_la_SOURCES += conv_acc_sse_avx.c
-if HAVE_SSE4_1
-conv_acc_sse_avx.lo : AM_CFLAGS += -mssse3 -mavx2 -msse4.1
-else
-conv_acc_sse_avx.lo : AM_CFLAGS += -mssse3 -mavx2
-endif
-endif
-endif
-
-if HAVE_NEON
-libosmocore_la_SOURCES += conv_acc_neon.c
-# conv_acc_neon.lo : AM_CFLAGS += -mfpu=neon no, could as well be vfp with neon
-endif
-
-BUILT_SOURCES = crc8gen.c crc16gen.c crc32gen.c crc64gen.c
-EXTRA_DIST = conv_acc_sse_impl.h conv_acc_neon_impl.h crcXXgen.c.tpl
-
-libosmocore_la_LDFLAGS = -version-info $(LIBVERSION) -no-undefined
-
-if ENABLE_PLUGIN
-libosmocore_la_SOURCES += plugin.c
-libosmocore_la_LIBADD += $(LIBRARY_DLOPEN)
-endif
-
-if ENABLE_MSGFILE
-libosmocore_la_SOURCES += msgfile.c
-endif
-
-if ENABLE_SERIAL
-libosmocore_la_SOURCES += serial.c
-endif
-
-if ENABLE_SYSTEMD_LOGGING
-libosmocore_la_SOURCES += logging_systemd.c
-libosmocore_la_LIBADD += $(SYSTEMD_LIBS)
-endif
-
-crc%gen.c: crcXXgen.c.tpl
- $(AM_V_GEN)sed -e's/XX/$*/g' $< > $@
+SUBDIRS = \
+ core \
+ vty \
+ isdn \
+ codec \
+ gsm \
+ coding \
+ gb \
+ ctrl \
+ pseudotalloc \
+ sim \
+ usb \
+ $(NULL) \ No newline at end of file
diff --git a/src/codec/Makefile.am b/src/codec/Makefile.am
index 778eb2ad..bb01b9d3 100644
--- a/src/codec/Makefile.am
+++ b/src/codec/Makefile.am
@@ -1,10 +1,10 @@
# 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 modifications: https://www.gnu.org/software/libtool/manual/html_node/Versioning.html
-LIBVERSION=2:0:2
+LIBVERSION=4:0:0
-AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_builddir)/include $(TALLOC_CFLAGS)
-AM_CFLAGS = -Wall
+AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_builddir)/include -I$(top_builddir)
+AM_CFLAGS = -Wall $(TALLOC_CFLAGS)
if ENABLE_PSEUDOTALLOC
AM_CPPFLAGS += -I$(top_srcdir)/src/pseudotalloc
@@ -13,6 +13,14 @@ endif
lib_LTLIBRARIES = libosmocodec.la
-libosmocodec_la_SOURCES = gsm610.c gsm620.c gsm660.c gsm690.c ecu.c ecu_fr.c
+libosmocodec_la_SOURCES = \
+ gsm610.c \
+ gsm620.c \
+ gsm660.c \
+ gsm690.c \
+ ecu.c \
+ ecu_fr.c \
+ ecu_fr_old.c \
+ $(NULL)
libosmocodec_la_LDFLAGS = -version-info $(LIBVERSION) -no-undefined
-libosmocodec_la_LIBADD = $(top_builddir)/src/libosmocore.la
+libosmocodec_la_LIBADD = $(top_builddir)/src/core/libosmocore.la
diff --git a/src/codec/ecu.c b/src/codec/ecu.c
index db7148ce..cdb3e62e 100644
--- a/src/codec/ecu.c
+++ b/src/codec/ecu.c
@@ -14,10 +14,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
/* As the developer and copyright holder of the related code, I hereby
@@ -48,7 +44,7 @@ static const struct osmo_ecu_ops *g_ecu_ops[_NUM_OSMO_ECU_CODECS];
/*! initialize an ECU instance for given codec.
* \param[in] ctx talloc context from which to allocate
- * \parma[in] codec codec for which to initialize/create ECU */
+ * \param[in] codec codec for which to initialize/create ECU */
struct osmo_ecu_state *osmo_ecu_init(void *ctx, enum osmo_ecu_codec codec)
{
if (codec >= ARRAY_SIZE(g_ecu_ops))
@@ -100,6 +96,20 @@ int osmo_ecu_frame_out(struct osmo_ecu_state *st, uint8_t *frame_out)
return g_ecu_ops[st->codec]->frame_out(st, frame_out);
}
+/*! check if the current state of this ECU is a DTX pause.
+ * \param[in] st ECU state/instance on which to operate
+ * \return true if DTX pause, false otherwise */
+bool osmo_ecu_is_dtx_pause(struct osmo_ecu_state *st)
+{
+ if (st->codec >= ARRAY_SIZE(g_ecu_ops))
+ return false;
+ if (!g_ecu_ops[st->codec])
+ return false;
+ if (!g_ecu_ops[st->codec]->is_dtx_pause)
+ return false;
+ return g_ecu_ops[st->codec]->is_dtx_pause(st);
+}
+
/***********************************************************************
* low-level API for ECU implementations
***********************************************************************/
diff --git a/src/codec/ecu_fr.c b/src/codec/ecu_fr.c
index 4545172a..45ba0cc8 100644
--- a/src/codec/ecu_fr.c
+++ b/src/codec/ecu_fr.c
@@ -1,9 +1,15 @@
/*
* (C) 2017 by sysmocom - s.f.m.c. GmbH
* (C) 2017 by Philipp Maier <pmaier@sysmocom.de>
- *
* All Rights Reserved
*
+ * Significantly reworked in 2023 by Mother
+ * Mychaela N. Falconia <falcon@freecalypso.org> - however,
+ * Mother Mychaela's contributions are NOT subject to copyright.
+ * No rights reserved, all rights relinquished.
+ * Portions of this code are based on Themyscira libgsmfrp,
+ * a public domain library by the same author.
+ *
* 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
@@ -14,10 +20,46 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
+ * The present ECU implementation for GSM-FR is closely based on the
+ * TS 46.011 spec from 3GPP; more specifically, it is based on the
+ * Example solution presented in Chapter 6 of that spec, adapted for
+ * libosmocodec ECU architecture, and comes as close to fulfilling
+ * the spec's officially stated requirements (Chapter 5) as is
+ * possible within this Osmocom-imposed architecture. Please note
+ * the following areas where the present implementation fails to
+ * fulfill the original intent of GSM spec authors:
+ *
+ * - The "lost SID" criterion, defined in GSM 06.31, is based on the
+ * TAF bit from the Radio Subsystem. However, libosmocodec ECU API
+ * does not include this flag, thus spec requirements related to
+ * lost SID conditions cannot be implemented in a strictly compliant
+ * manner. The present implementation improvises its own "lost SID"
+ * detector (not strictly spec-compliant) by counting frame_out()
+ * calls in between good traffic frame inputs via frame_in().
+ *
+ * - In the architecture envisioned and assumed in the GSM specs,
+ * the ECU function of GSM 06.11 was never intended to be a fully
+ * modular component with its own bona fide I/O interfaces - this
+ * approach appears to be an Osmocom invention - instead this ECU
+ * function was intended to be subsumed in the Rx DTX handler
+ * component of GSM 06.31, also incorporating the comfort noise
+ * generator of GSM 06.12 - and unlike the narrower-scope ECU,
+ * this slightly-larger-scope Rx DTX handler is a modular component
+ * with well-defined I/O interfaces. In the case of BFI conditions
+ * following a SID, GSM 06.11 spec was written with the assumption
+ * that the ECU controls the comfort noise generator via internal
+ * signals, as opposed to emitting "corrected" SID frames on a
+ * modular interface going to a CN generator located somewhere else.
+ * Thus the "correct" behavior for a fully modularized ECU is unclear,
+ * and an argument can be made that the very existence of such a
+ * fully modularized ECU is incorrect in itself. The present
+ * implementation re-emits a "rejuvenated" form of the last saved
+ * SID frame during BFI conditions following a SID within the
+ * permitted window of 48 frames, then starts emitting muted SIDs
+ * with Xmaxc decreasing by 4 on each frame, and finally switches
+ * to emitting non-SID silence frames (Table 1 of TS 46.011)
+ * once Xmaxc reaches 0.
*/
#include <stdbool.h>
@@ -25,144 +67,216 @@
#include <stdint.h>
#include <errno.h>
-#include <osmocom/core/bitvec.h>
+#include <osmocom/core/prbs.h>
-#include <osmocom/codec/gsm610_bits.h>
#include <osmocom/codec/codec.h>
#include <osmocom/codec/ecu.h>
+#include <osmocom/core/linuxlist.h>
+
+/* See TS 46.011, Chapter 6 Example solution */
+#define GSM611_XMAXC_REDUCE 4
+
+/* The first 5 bytes of RTP encoding neatly contain the magic nibble
+ * and LARc parameters, which also happens to be the part of SID frames
+ * that needs to be passed through as-is. */
+#define SID_PREFIX_LEN 5
+
+enum ecu_principal_state {
+ STATE_NO_DATA,
+ STATE_SPEECH,
+ STATE_SP_MUTING,
+ STATE_SID,
+ STATE_SID_MUTING,
+};
-/* See also GSM 06.11, chapter 6 Example solution */
-#define GSM610_XMAXC_REDUCE 4
-#define GSM610_XMAXC_LEN 6
+struct fr_ecu_state {
+ struct osmo_ecu_state ecu_state;
+ enum ecu_principal_state pr_state;
+ uint8_t speech_frame[GSM_FR_BYTES];
+ uint8_t sid_prefix[SID_PREFIX_LEN];
+ uint8_t sid_xmaxc;
+ uint8_t sid_reemit_count;
+ struct osmo_prbs_state prng;
+ bool last_input_was_sid;
+};
-/**
- * Reduce the XMAXC field. When the XMAXC field reaches
- * zero the function will return true.
+/* This function is the frame input to the ECU - all inputs to this
+ * function have been received by the Radio Subsystem as good traffic
+ * frames in the GSM 06.31 definition.
*/
-static bool reduce_xmaxcr(struct bitvec *frame_bitvec,
- const unsigned int index)
+static void fr_ecu_input(struct fr_ecu_state *fr, const uint8_t *frame)
{
- unsigned int field_index;
- uint64_t field;
-
- field_index = index;
- field = bitvec_read_field(frame_bitvec, &field_index, GSM610_XMAXC_LEN);
- if (field > GSM610_XMAXC_REDUCE)
- field -= GSM610_XMAXC_REDUCE;
- else
- field = 0;
-
- field_index = index;
- bitvec_write_field(frame_bitvec, &field_index, field, GSM610_XMAXC_LEN);
-
- return field == 0;
+ enum osmo_gsm631_sid_class sidc;
+
+ sidc = osmo_fr_sid_classify(frame);
+ switch (sidc) {
+ case OSMO_GSM631_SID_CLASS_SPEECH:
+ memcpy(fr->speech_frame, frame, GSM_FR_BYTES);
+ fr->pr_state = STATE_SPEECH;
+ fr->last_input_was_sid = false;
+ return;
+ case OSMO_GSM631_SID_CLASS_INVALID:
+ /* GSM 06.31 section 6.1.2 says: "an invalid SID frame
+ * shall be substituted by the last valid SID frame
+ * and the procedure for valid SID frames be applied."
+ * However, libosmocodec ECU architecture prevents us
+ * from doing what the spec says: the frame_in() method
+ * gets a const frame that can't be modified, and
+ * frame_out() will never get called when BFI=0, even
+ * when the "good traffic frame" (in the BFI=0 sense)
+ * is an invalid SID by the bit-counting rule.
+ * Thus there is no place where we can re-emit a cached
+ * copy of the last valid SID upon receiving an invalid SID.
+ *
+ * In the standard GSM architecture this problem never
+ * arises because the ECU is not a separate component
+ * but is coupled with the CN generator, thus the output
+ * from the Rx DTX handler block will be a CN frame,
+ * for both valid-SID and invalid-SID inputs to the block.
+ * But what can we do within the constraints of libosmocodec
+ * ECU framework? We treat the invalid SID almost like a
+ * BFI, doing almost nothing in the frame_in() method,
+ * but we reset sid_reemit_count because by the rules of
+ * GSM 06.31 an invalid SID is still an accepted SID frame
+ * for the purpose of "lost SID" logic. */
+ fr->sid_reemit_count = 0;
+ fr->last_input_was_sid = true;
+ return;
+ case OSMO_GSM631_SID_CLASS_VALID:
+ /* save LARc part */
+ memcpy(fr->sid_prefix, frame, SID_PREFIX_LEN);
+ /* save Xmaxc from the last subframe */
+ fr->sid_xmaxc = ((frame[27] & 0x1F) << 1) | (frame[28] >> 7);
+ fr->pr_state = STATE_SID;
+ fr->sid_reemit_count = 0;
+ fr->last_input_was_sid = true;
+ return;
+ default:
+ /* There are only 3 possible SID classifications per GSM 06.31
+ * section 6.1.1, thus any other return value is a grave error
+ * in the code. */
+ OSMO_ASSERT(0);
+ }
}
-/**
- * Reduce all XMAXC fields in the frame. When all XMAXC fields
- * reach zero, then the function will return true.
+/* Reduce all 4 Xmaxc fields in the frame. When all 4 Xmaxc fields
+ * reach 0, the function will return true for "mute".
*/
-static bool reduce_xmaxcr_all(struct bitvec *frame_bitvec)
+static bool reduce_xmaxc(uint8_t *frame)
{
- bool silent = true;
-
- silent &= reduce_xmaxcr(frame_bitvec, GSM610_RTP_XMAXC00);
- silent &= reduce_xmaxcr(frame_bitvec, GSM610_RTP_XMAXC10);
- silent &= reduce_xmaxcr(frame_bitvec, GSM610_RTP_XMAXC20);
- silent &= reduce_xmaxcr(frame_bitvec, GSM610_RTP_XMAXC30);
-
- return silent;
+ bool mute_flag = true;
+ uint8_t sub, xmaxc;
+
+ for (sub = 0; sub < 4; sub++) {
+ xmaxc = ((frame[sub*7+6] & 0x1F) << 1) | (frame[sub*7+7] >> 7);
+ if (xmaxc > GSM611_XMAXC_REDUCE) {
+ xmaxc -= GSM611_XMAXC_REDUCE;
+ mute_flag = false;
+ } else
+ xmaxc = 0;
+ frame[sub*7+6] &= 0xE0;
+ frame[sub*7+6] |= xmaxc >> 1;
+ frame[sub*7+7] &= 0x7F;
+ frame[sub*7+7] |= (xmaxc & 1) << 7;
+ }
+ return mute_flag;
}
-/* Use certain modifications to conceal the errors in a full rate frame */
-static int conceal_frame(uint8_t *frame)
+/* TS 46.011 chapter 6, paragraph 4, last sentence: "The grid position
+ * parameters are chosen randomly between 0 and 3 during this time."
+ * (The "during this time" qualifier refers to the speech muting state.)
+ * This sentence in the spec must have been overlooked by previous ECU
+ * implementors, as this aspect of the muting logic was missing.
+ */
+static void random_grid_pos(struct fr_ecu_state *fr, uint8_t *frame)
{
- struct bitvec *frame_bitvec;
- unsigned int len;
- bool silent;
- int rc = 0;
-
- /* In case we already deal with a silent frame,
- * there is nothing to, we just abort immediately */
- if (osmo_fr_check_sid(frame, GSM_FR_BYTES))
- return 0;
-
- /* Attempt to allocate memory for bitvec */
- frame_bitvec = bitvec_alloc(GSM_FR_BYTES, NULL);
- if (!frame_bitvec)
- return -ENOMEM;
+ uint8_t sub;
- /* Convert a frame to bitvec */
- len = bitvec_unpack(frame_bitvec, frame);
- if (len != GSM_FR_BYTES) {
- rc = -EIO;
- goto leave;
- }
-
- /* Fudge frame parameters */
- silent = reduce_xmaxcr_all(frame_bitvec);
-
- /* If we reached silence level, mute the frame
- * completely, this also means that we can
- * save the bitvec_pack operation */
- if (silent) {
- memset(frame, 0x00, GSM_FR_BYTES);
- frame[0] = 0xd0;
- goto leave;
+ for (sub = 0; sub < 4; sub++) {
+ frame[sub*7+6] &= 0x9F;
+ frame[sub*7+6] |= osmo_prbs_get_ubit(&fr->prng) << 6;
+ frame[sub*7+6] |= osmo_prbs_get_ubit(&fr->prng) << 5;
}
+}
- /* Convert back to packed byte form */
- len = bitvec_pack(frame_bitvec, frame);
- if (len != GSM_FR_BYTES) {
- rc = -EIO;
- goto leave;
+/* Like reduce_xmaxc() above, but for comfort noise rather than speech. */
+static bool reduce_xmaxc_sid(struct fr_ecu_state *fr)
+{
+ if (fr->sid_xmaxc > GSM611_XMAXC_REDUCE) {
+ fr->sid_xmaxc -= GSM611_XMAXC_REDUCE;
+ return false;
}
-
-leave:
- bitvec_free(frame_bitvec);
- return rc;
+ fr->sid_xmaxc = 0;
+ return true;
}
-/*!
- * To be called when a good frame is received.
- * This function will then create a backup of the frame
- * and reset the internal state.
- * \param[in] state The state object for the ECU
- * \param[out] frame The valid frame (GSM_FR_BYTES bytes in RTP payload format)
+/* This function implements the part which is peculiar to the present
+ * "standalone" packaging of GSM-FR ECU, without a directly coupled
+ * comfort noise generator - it re-emits synthetic SID frames during
+ * DTX pauses, initially unchanged from the saved SID and later muted.
*/
-void osmo_ecu_fr_reset(struct osmo_ecu_fr_state *state, const uint8_t *frame)
+static void reemit_sid(struct fr_ecu_state *fr, uint8_t *frame)
{
- state->subsequent_lost_frame = false;
- memcpy(state->frame_backup, frame, GSM_FR_BYTES);
+ uint8_t *p, sub;
+
+ memcpy(frame, fr->sid_prefix, SID_PREFIX_LEN);
+ p = frame + SID_PREFIX_LEN;
+ for (sub = 0; sub < 4; sub++) {
+ *p++ = 0;
+ *p++ = fr->sid_xmaxc >> 1;
+ *p++ = (fr->sid_xmaxc & 1) << 7;
+ *p++ = 0;
+ *p++ = 0;
+ *p++ = 0;
+ *p++ = 0;
+ }
}
-/*!
- * To be called when a bad frame is received.
- * This function will then generate a replacement frame
- * that can be used to conceal the dropout.
- * \param[in] state The state object for the ECU
- * \param[out] frame The buffer to fill with GSM_FR_BYTES of replacement frame
- * \returns 0 if the frame was sucessfully filled
+/* This function is responsible for generating the ECU's output
+ * in the event that the Radio Subsystem does not have a good
+ * traffic frame - conditions corresponding to BFI=1 in the specs.
*/
-int osmo_ecu_fr_conceal(struct osmo_ecu_fr_state *state, uint8_t *frame)
+static void fr_ecu_output(struct fr_ecu_state *fr, uint8_t *frame)
{
- int rc;
-
- /* For subsequent frames we run the error concealment
- * functions on the backed up frame before we restore
- * the backup */
- if (state->subsequent_lost_frame) {
- rc = conceal_frame(state->frame_backup);
- if (rc)
- return rc;
+ bool mute;
+
+ switch (fr->pr_state) {
+ case STATE_NO_DATA:
+ memcpy(frame, osmo_gsm611_silence_frame, GSM_FR_BYTES);
+ return;
+ case STATE_SPEECH:
+ /* TS 46.011 chapter 6: "The first lost speech frame is
+ * replaced at the speech decoder input by the previous
+ * good speech frame." */
+ memcpy(frame, fr->speech_frame, GSM_FR_BYTES);
+ fr->pr_state = STATE_SP_MUTING;
+ return;
+ case STATE_SP_MUTING:
+ mute = reduce_xmaxc(fr->speech_frame);
+ memcpy(frame, fr->speech_frame, GSM_FR_BYTES);
+ random_grid_pos(fr, frame);
+ if (mute)
+ fr->pr_state = STATE_NO_DATA;
+ return;
+ case STATE_SID:
+ fr->sid_reemit_count++;
+ if (fr->sid_reemit_count >= 48) {
+ fr->pr_state = STATE_SID_MUTING;
+ reduce_xmaxc_sid(fr);
+ }
+ reemit_sid(fr, frame);
+ return;
+ case STATE_SID_MUTING:
+ if (reduce_xmaxc_sid(fr)) {
+ fr->pr_state = STATE_NO_DATA;
+ memcpy(frame, osmo_gsm611_silence_frame, GSM_FR_BYTES);
+ } else
+ reemit_sid(fr, frame);
+ return;
+ default:
+ /* a severe bug in the state machine! */
+ OSMO_ASSERT(0);
}
-
- /* Restore the backed up frame and set flag in case
- * we receive even more bad frames */
- memcpy(frame, state->frame_backup, GSM_FR_BYTES);
- state->subsequent_lost_frame = true;
-
- return 0;
}
/***********************************************************************
@@ -171,44 +285,57 @@ int osmo_ecu_fr_conceal(struct osmo_ecu_fr_state *state, uint8_t *frame)
static struct osmo_ecu_state *ecu_fr_init(void *ctx, enum osmo_ecu_codec codec)
{
- struct osmo_ecu_state *st;
- size_t size = sizeof(*st) + sizeof(struct osmo_ecu_fr_state);
+ struct fr_ecu_state *fr;
- st = talloc_named_const(ctx, size, "ecu_state_FR");
- if (!st)
- return NULL;
+ fr = talloc_zero(ctx, struct fr_ecu_state);
+ fr->ecu_state.codec = codec;
+ fr->pr_state = STATE_NO_DATA;
+ osmo_prbs_state_init(&fr->prng, &osmo_prbs15);
- memset(st, 0, size);
- st->codec = codec;
+ return (struct osmo_ecu_state *) fr;
+}
- return st;
+static inline struct fr_ecu_state *_osmo_ecu_state_get_fr(struct osmo_ecu_state *st)
+{
+ return (struct fr_ecu_state *)container_of(st, struct fr_ecu_state, ecu_state);
}
static int ecu_fr_frame_in(struct osmo_ecu_state *st, bool bfi, const uint8_t *frame,
unsigned int frame_bytes)
{
- struct osmo_ecu_fr_state *fr = (struct osmo_ecu_fr_state *) &st->data;
+ struct fr_ecu_state *fr = _osmo_ecu_state_get_fr(st);
+
if (bfi)
return 0;
+ if (frame_bytes != GSM_FR_BYTES)
+ return 0;
+ if ((frame[0] & 0xF0) != 0xD0)
+ return 0;
- osmo_ecu_fr_reset(fr, frame);
+ fr_ecu_input(fr, frame);
return 0;
}
static int ecu_fr_frame_out(struct osmo_ecu_state *st, uint8_t *frame_out)
{
- struct osmo_ecu_fr_state *fr = (struct osmo_ecu_fr_state *) &st->data;
+ struct fr_ecu_state *fr = _osmo_ecu_state_get_fr(st);
+
+ fr_ecu_output(fr, frame_out);
+ return GSM_FR_BYTES;
+}
+
+static bool ecu_fr_is_dtx_pause(struct osmo_ecu_state *st)
+{
+ struct fr_ecu_state *fr = _osmo_ecu_state_get_fr(st);
- if (osmo_ecu_fr_conceal(fr, frame_out) == 0)
- return GSM_FR_BYTES;
- else
- return -1;
+ return fr->last_input_was_sid;
}
static const struct osmo_ecu_ops osmo_ecu_ops_fr = {
.init = ecu_fr_init,
.frame_in = ecu_fr_frame_in,
.frame_out = ecu_fr_frame_out,
+ .is_dtx_pause = ecu_fr_is_dtx_pause,
};
static __attribute__((constructor)) void on_dso_load_ecu_fr(void)
diff --git a/src/codec/ecu_fr_old.c b/src/codec/ecu_fr_old.c
new file mode 100644
index 00000000..cfefce14
--- /dev/null
+++ b/src/codec/ecu_fr_old.c
@@ -0,0 +1,166 @@
+/*
+ * (C) 2017 by sysmocom - s.f.m.c. GmbH
+ * (C) 2017 by Philipp Maier <pmaier@sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * 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.
+ *
+ *
+ * This module implements legacy, deprecated osmo_ecu_fr_reset() and
+ * osmo_ecu_fr_conceal() functions only - see ecu_fr.c for the new
+ * GSM-FR ECU implementation.
+ */
+
+#include <stdbool.h>
+#include <string.h>
+#include <stdint.h>
+#include <errno.h>
+
+#include <osmocom/core/bitvec.h>
+
+#include <osmocom/codec/gsm610_bits.h>
+#include <osmocom/codec/codec.h>
+#include <osmocom/codec/ecu.h>
+
+/* See also GSM 06.11, chapter 6 Example solution */
+#define GSM610_XMAXC_REDUCE 4
+#define GSM610_XMAXC_LEN 6
+
+/**
+ * Reduce the XMAXC field. When the XMAXC field reaches
+ * zero the function will return true.
+ */
+static bool reduce_xmaxcr(struct bitvec *frame_bitvec,
+ const unsigned int index)
+{
+ unsigned int field_index;
+ uint64_t field;
+
+ field_index = index;
+ field = bitvec_read_field(frame_bitvec, &field_index, GSM610_XMAXC_LEN);
+ if (field > GSM610_XMAXC_REDUCE)
+ field -= GSM610_XMAXC_REDUCE;
+ else
+ field = 0;
+
+ field_index = index;
+ bitvec_write_field(frame_bitvec, &field_index, field, GSM610_XMAXC_LEN);
+
+ return field == 0;
+}
+
+/**
+ * Reduce all XMAXC fields in the frame. When all XMAXC fields
+ * reach zero, then the function will return true.
+ */
+static bool reduce_xmaxcr_all(struct bitvec *frame_bitvec)
+{
+ bool silent = true;
+
+ silent &= reduce_xmaxcr(frame_bitvec, GSM610_RTP_XMAXC00);
+ silent &= reduce_xmaxcr(frame_bitvec, GSM610_RTP_XMAXC10);
+ silent &= reduce_xmaxcr(frame_bitvec, GSM610_RTP_XMAXC20);
+ silent &= reduce_xmaxcr(frame_bitvec, GSM610_RTP_XMAXC30);
+
+ return silent;
+}
+
+/* Use certain modifications to conceal the errors in a full rate frame */
+static int conceal_frame(uint8_t *frame)
+{
+ struct bitvec *frame_bitvec;
+ unsigned int len;
+ bool silent;
+ int rc = 0;
+
+ /* In case we already deal with a silent frame,
+ * there is nothing to, we just abort immediately */
+ if (osmo_fr_check_sid(frame, GSM_FR_BYTES))
+ return 0;
+
+ /* Attempt to allocate memory for bitvec */
+ frame_bitvec = bitvec_alloc(GSM_FR_BYTES, NULL);
+ if (!frame_bitvec)
+ return -ENOMEM;
+
+ /* Convert a frame to bitvec */
+ len = bitvec_unpack(frame_bitvec, frame);
+ if (len != GSM_FR_BYTES) {
+ rc = -EIO;
+ goto leave;
+ }
+
+ /* Fudge frame parameters */
+ silent = reduce_xmaxcr_all(frame_bitvec);
+
+ /* If we reached silence level, mute the frame
+ * completely, this also means that we can
+ * save the bitvec_pack operation */
+ if (silent) {
+ memset(frame, 0x00, GSM_FR_BYTES);
+ frame[0] = 0xd0;
+ goto leave;
+ }
+
+ /* Convert back to packed byte form */
+ len = bitvec_pack(frame_bitvec, frame);
+ if (len != GSM_FR_BYTES) {
+ rc = -EIO;
+ goto leave;
+ }
+
+leave:
+ bitvec_free(frame_bitvec);
+ return rc;
+}
+
+/*!
+ * To be called when a good frame is received.
+ * This function will then create a backup of the frame
+ * and reset the internal state.
+ * \param[in] state The state object for the ECU
+ * \param[out] frame The valid frame (GSM_FR_BYTES bytes in RTP payload format)
+ */
+void osmo_ecu_fr_reset(struct osmo_ecu_fr_state *state, const uint8_t *frame)
+{
+ state->subsequent_lost_frame = false;
+ memcpy(state->frame_backup, frame, GSM_FR_BYTES);
+}
+
+/*!
+ * To be called when a bad frame is received.
+ * This function will then generate a replacement frame
+ * that can be used to conceal the dropout.
+ * \param[in] state The state object for the ECU
+ * \param[out] frame The buffer to fill with GSM_FR_BYTES of replacement frame
+ * \returns 0 if the frame was successfully filled
+ */
+int osmo_ecu_fr_conceal(struct osmo_ecu_fr_state *state, uint8_t *frame)
+{
+ int rc;
+
+ /* For subsequent frames we run the error concealment
+ * functions on the backed up frame before we restore
+ * the backup */
+ if (state->subsequent_lost_frame) {
+ rc = conceal_frame(state->frame_backup);
+ if (rc)
+ return rc;
+ }
+
+ /* Restore the backed up frame and set flag in case
+ * we receive even more bad frames */
+ memcpy(frame, state->frame_backup, GSM_FR_BYTES);
+ state->subsequent_lost_frame = true;
+
+ return 0;
+}
diff --git a/src/codec/gsm610.c b/src/codec/gsm610.c
index a05eabab..5cc4f148 100644
--- a/src/codec/gsm610.c
+++ b/src/codec/gsm610.c
@@ -17,10 +17,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <stdint.h>
@@ -300,6 +296,35 @@ const uint16_t gsm610_bitorder[260] = {
29, /* LARc5:0 */
};
+/*
+ * Table 1 in section 6 of 3GPP TS 46.011 (substitution and muting of lost
+ * frames) specifies a silence frame in the form of GSM 06.10 parameters;
+ * the following const datum is this GSM 06.11 silence frame in GSM-FR
+ * RTP encoding.
+ */
+const uint8_t osmo_gsm611_silence_frame[GSM_FR_BYTES] = {
+ 0xDA, 0xA7, 0xAA, 0xA5, 0x1A,
+ 0x50, 0x20, 0x38, 0xE4, 0x6D, 0xB9, 0x1B,
+ 0x50, 0x20, 0x38, 0xE4, 0x6D, 0xB9, 0x1B,
+ 0x50, 0x20, 0x38, 0xE4, 0x6D, 0xB9, 0x1B,
+ 0x50, 0x20, 0x38, 0xE4, 0x6D, 0xB9, 0x1B,
+};
+
+static const uint16_t sid_code_word_bits[95] = {
+ /* bit numbers are relative to the RTP frame beginning,
+ * with signature bits included in the count. */
+ 57, 58, 60, 61, 63, 64, 66, 67, 69, 70, 72, 73,
+ 75, 76, 78, 79, 81, 82, 84, 85, 87, 88, 90, 91,
+ 93, 94, 113, 114, 116, 117, 119, 120, 122, 123,
+ 125, 126, 128, 129, 131, 132, 134, 135, 137,
+ 138, 140, 141, 143, 144, 146, 147, 149, 150,
+ 169, 170, 172, 173, 175, 176, 178, 179, 181,
+ 182, 184, 185, 187, 188, 190, 191, 193, 194,
+ 196, 197, 199, 200, 202, 203, 205, 206, 225,
+ 226, 228, 229, 231, 232, 234, 235, 237, 240,
+ 243, 246, 249, 252, 255, 258, 261
+};
+
/*! Check whether RTP frame contains FR SID code word according to
* TS 101 318 §5.1.2
* \param[in] rtp_payload Buffer with RTP payload
@@ -309,16 +334,7 @@ const uint16_t gsm610_bitorder[260] = {
bool osmo_fr_check_sid(const uint8_t *rtp_payload, size_t payload_len)
{
struct bitvec bv;
- uint16_t i, z_bits[] = { 57, 58, 60, 61, 63, 64, 66, 67, 69, 70, 72, 73,
- 75, 76, 78, 79, 81, 82, 84, 85, 87, 88, 90, 91,
- 93, 94, 113, 114, 116, 117, 119, 120, 122, 123,
- 125, 126, 128, 129, 131, 132, 134, 135, 137,
- 138, 140, 141, 143, 144, 146, 147, 149, 150,
- 169, 170, 172, 173, 175, 176, 178, 179, 181,
- 182, 184, 185, 187, 188, 190, 191, 193, 194,
- 196, 197, 199, 200, 202, 203, 205, 206, 225,
- 226, 228, 229, 231, 232, 234, 235, 237, 240,
- 243, 246, 249, 252, 255, 258, 261 };
+ uint16_t i;
/* signature does not match Full Rate SID */
if ((rtp_payload[0] >> 4) != 0xD)
@@ -327,10 +343,120 @@ bool osmo_fr_check_sid(const uint8_t *rtp_payload, size_t payload_len)
bv.data = (uint8_t *) rtp_payload;
bv.data_len = payload_len;
- /* code word is all 0 at given bits, numbered from 1 */
- for (i = 0; i < ARRAY_SIZE(z_bits); i++)
- if (bitvec_get_bit_pos(&bv, z_bits[i]) != ZERO)
+ /* code word is all 0 at given bits */
+ for (i = 0; i < ARRAY_SIZE(sid_code_word_bits); i++) {
+ if (bitvec_get_bit_pos(&bv, sid_code_word_bits[i]) != ZERO)
return false;
+ }
return true;
}
+
+/*! Classify potentially-SID FR codec frame in RTP format according
+ * to the rules of GSM 06.31 §6.1.1
+ * \param[in] rtp_payload Buffer with RTP payload
+ * \returns enum osmo_gsm631_sid_class, with symbolic values
+ * OSMO_GSM631_SID_CLASS_SPEECH, OSMO_GSM631_SID_CLASS_INVALID or
+ * OSMO_GSM631_SID_CLASS_VALID corresponding to the 3 possible bit-counting
+ * classifications prescribed by the spec.
+ *
+ * Differences between the more familiar osmo_fr_check_sid() and the present
+ * function are:
+ *
+ * 1. osmo_fr_check_sid() returns true only if the SID frame is absolutely
+ * perfect, with all 95 bits of the SID code word zeroed. However, the
+ * rules of GSM 06.31 §6.1.1 allow up to one bit to be in error,
+ * and the frame is still accepted as valid SID.
+ *
+ * 2. The third possible state of invalid SID is not handled at all by the
+ * simpler osmo_fr_check_sid() function.
+ *
+ * 3. osmo_fr_check_sid() includes a check for 0xD RTP signature, and returns
+ * false if that signature nibble is wrong. That check is not included
+ * in the present version because there is no place for it in the
+ * ETSI-prescribed classification, it is neither speech nor SID. The
+ * assumption is that this function is used to classify the bit content
+ * of received codec frames, not their RTP encoding - the latter needs
+ * to be validated beforehand.
+ *
+ * Which function should one use? The answer depends on the specific
+ * circumstances, and needs to be addressed on a case-by-case basis.
+ */
+enum osmo_gsm631_sid_class osmo_fr_sid_classify(const uint8_t *rtp_payload)
+{
+ struct bitvec bv;
+ uint16_t i, n;
+
+ bv.data = (uint8_t *) rtp_payload;
+ bv.data_len = GSM_FR_BYTES;
+
+ /* count not-SID-matching bits per the spec */
+ n = 0;
+ for (i = 0; i < ARRAY_SIZE(sid_code_word_bits); i++) {
+ if (bitvec_get_bit_pos(&bv, sid_code_word_bits[i]) != ZERO)
+ n++;
+ if (n >= 16)
+ return OSMO_GSM631_SID_CLASS_SPEECH;
+ }
+ if (n >= 2)
+ return OSMO_GSM631_SID_CLASS_INVALID;
+ else
+ return OSMO_GSM631_SID_CLASS_VALID;
+}
+
+/*! Reset the SID field and the unused bits of a potentially corrupted,
+ * but still valid GSM-FR SID frame in RTP encoding to their pristine state.
+ * \param[in] rtp_payload Buffer with RTP payload - must be writable!
+ *
+ * Per GSM 06.12 section 5.2, a freshly minted SID frame carries 60 bits
+ * of comfort noise parameters (LARc and 4 times Xmaxc), while the remaining
+ * 200 bits are all zeros; the latter 200 all-0 bits further break down into
+ * 95 bits of SID field (checked by receivers to detect SID) and 105 unused
+ * bits which receivers are told to ignore. Network elements that receive
+ * SID frames from call leg A uplink and need to retransmit them on leg B
+ * downlink should "rejuvenate" received SID frames prior to retransmission;
+ * this function does the job.
+ */
+void osmo_fr_sid_reset(uint8_t *rtp_payload)
+{
+ uint8_t *p, sub;
+
+ p = rtp_payload + 5; /* skip magic+LARc */
+ for (sub = 0; sub < 4; sub++) {
+ *p++ = 0;
+ *p++ &= 0x1F; /* upper 5 bits of Xmaxc field */
+ *p++ &= 0x80; /* and the lsb spilling into the next byte */
+ *p++ = 0;
+ *p++ = 0;
+ *p++ = 0;
+ *p++ = 0;
+ }
+}
+
+/*! Preen potentially-SID FR codec frame in RTP format, ensuring that it is
+ * either a speech frame or a valid SID, and if the latter, making it a
+ * perfect, error-free SID frame.
+ * \param[in] rtp_payload Buffer with RTP payload - must be writable!
+ * \returns true if the frame is good, false otherwise
+ */
+bool osmo_fr_sid_preen(uint8_t *rtp_payload)
+{
+ enum osmo_gsm631_sid_class sidc;
+
+ sidc = osmo_fr_sid_classify(rtp_payload);
+ switch (sidc) {
+ case OSMO_GSM631_SID_CLASS_SPEECH:
+ return true;
+ case OSMO_GSM631_SID_CLASS_INVALID:
+ return false;
+ case OSMO_GSM631_SID_CLASS_VALID:
+ /* "Rejuvenate" this SID frame, correcting any errors */
+ osmo_fr_sid_reset(rtp_payload);
+ return true;
+ default:
+ /* There are only 3 possible SID classifications per GSM 06.31
+ * section 6.1.1, thus any other return value is a grave error
+ * in the code. */
+ OSMO_ASSERT(0);
+ }
+}
diff --git a/src/codec/gsm620.c b/src/codec/gsm620.c
index 282781fd..ef1d3b9b 100644
--- a/src/codec/gsm620.c
+++ b/src/codec/gsm620.c
@@ -17,14 +17,11 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <stdint.h>
#include <stdbool.h>
+#include <string.h>
#include <osmocom/core/bitvec.h>
#include <osmocom/core/utils.h>
@@ -268,12 +265,6 @@ const uint16_t gsm620_voiced_bitorder[112] = {
81, /* Code 3:7 */
};
-static inline uint16_t mask(const uint8_t msb)
-{
- const uint16_t m = (uint16_t)1 << (msb - 1);
- return (m - 1) ^ m;
-}
-
/*! Check whether RTP frame contains HR SID code word according to
* TS 101 318 §5.2.2
* \param[in] rtp_payload Buffer with RTP payload
@@ -282,16 +273,44 @@ static inline uint16_t mask(const uint8_t msb)
*/
bool osmo_hr_check_sid(const uint8_t *rtp_payload, size_t payload_len)
{
- uint8_t i, bits[] = { 1, 2, 8, 9, 5, 4, 9, 5, 4, 9, 5, 4, 9, 5 };
- struct bitvec bv;
- bv.data = (uint8_t *) rtp_payload;
- bv.data_len = payload_len;
- bv.cur_bit = 33;
+ struct bitvec bv = {
+ .data = (uint8_t *)rtp_payload,
+ .data_len = payload_len,
+ };
- /* code word is all 1 at given bits, numbered from 1, MODE is always 3 */
- for (i = 0; i < ARRAY_SIZE(bits); i++)
- if (bitvec_get_uint(&bv, bits[i]) != mask(bits[i]))
+ /* A SID frame is identified by a SID codeword consisting of 79 bits which are all 1,
+ * so we basically check if all bits in range r34..r112 (inclusive) are 1. */
+ for (bv.cur_bit = 33; bv.cur_bit < bv.data_len * 8; bv.cur_bit++)
+ if (bitvec_get_bit_pos(&bv, bv.cur_bit) != ONE)
return false;
return true;
}
+
+/*! Reset the SID field of a potentially corrupted, but still valid GSM-HR
+ * SID frame in TS 101 318 format to its pristine state (full SID codeword).
+ * \param[in] rtp_payload Buffer with RTP payload - must be writable!
+ *
+ * Per GSM 06.22 section 5.3, a freshly minted SID frame consists of 33 bits
+ * of comfort noise parameters and 79 bits of SID codeword (all 1s). Network
+ * elements that receive SID frames from call leg A uplink and need to
+ * retransmit them on leg B downlink should "rejuvenate" received SID frames
+ * prior to retransmission by resetting the SID field to its pristine state
+ * of all 1s; this function does the job.
+ *
+ * Important note: because of HR-specific quirks (lack of exact bit counting
+ * rules in GSM 06.41 spec compared to 06.31 & 06.81, plus the fact that such
+ * bit counting can only be done efficiently in the GSM 05.03 channel decoder
+ * prior to bit reordering based on voiced or unvoiced mode), a generic
+ * (usable from any network element) SID classification function similar to
+ * osmo_{fr,efr}_sid_classify() unfortunately cannot exist for HR. Therefore,
+ * the triggering condition for invoking this SID rejuvenation/reset function
+ * can only be an out-of-band SID indication, as in GSM 08.61 TRAU frames
+ * or RFC 5993 ToC octet.
+ */
+void osmo_hr_sid_reset(uint8_t *rtp_payload)
+{
+ /* set all 79 SID codeword bits to 1 */
+ rtp_payload[4] |= 0x7F;
+ memset(rtp_payload + 5, 0xFF, 9);
+}
diff --git a/src/codec/gsm660.c b/src/codec/gsm660.c
index 4f7bb099..b15bdf37 100644
--- a/src/codec/gsm660.c
+++ b/src/codec/gsm660.c
@@ -17,13 +17,13 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <stdint.h>
+#include <stdbool.h>
+
+#include <osmocom/core/bitvec.h>
+#include <osmocom/core/utils.h>
#include <osmocom/codec/codec.h>
/* GSM EFR - subjective importance bit ordering */
@@ -257,3 +257,161 @@ const uint16_t gsm660_bitorder[260] = {
243, /* 258 -> PULSE 4_9: b0 */
246, /* 259 -> PULSE 4_10: b0 */
};
+
+static const uint8_t sid_code_word_bits[95] = {
+ /* bit numbers are relative to "pure" EFR frame beginning,
+ * not counting the signature bits. */
+ 45, 46, 48, 49, 50, 51, 52, 53, 54, 55,
+ 56, 57, 58, 59, 60, 61, 62, 63, 64, 65,
+ 66, 67, 68, 94, 95, 96, 98, 99, 100, 101,
+ 102, 103, 104, 105, 106, 107, 108, 109, 110, 111,
+ 112, 113, 114, 115, 116, 117, 118, 148, 149, 150,
+ 151, 152, 153, 154, 155, 156, 157, 158, 159, 160,
+ 161, 162, 163, 164, 165, 166, 167, 168, 169, 170,
+ 171, 196, 197, 198, 199, 200, 201, 202, 203, 204,
+ 205, 206, 207, 208, 209, 212, 213, 214, 215, 216,
+ 217, 218, 219, 220, 221
+};
+
+/*! Check whether RTP frame contains EFR SID code word according to
+ * TS 101 318 §5.3.2
+ * \param[in] rtp_payload Buffer with RTP payload
+ * \param[in] payload_len Length of payload
+ * \returns true if code word is found, false otherwise
+ */
+bool osmo_efr_check_sid(const uint8_t *rtp_payload, size_t payload_len)
+{
+ struct bitvec bv;
+ uint16_t i;
+
+ /* signature does not match Enhanced Full Rate SID */
+ if ((rtp_payload[0] >> 4) != 0xC)
+ return false;
+
+ bv.data = (uint8_t *) rtp_payload;
+ bv.data_len = payload_len;
+
+ /* code word is all 1 at given bits */
+ for (i = 0; i < ARRAY_SIZE(sid_code_word_bits); i++) {
+ if (bitvec_get_bit_pos(&bv, sid_code_word_bits[i]+4) != ONE)
+ return false;
+ }
+
+ return true;
+}
+
+/*! Classify potentially-SID EFR codec frame in RTP format according
+ * to the rules of GSM 06.81 §6.1.1
+ * \param[in] rtp_payload Buffer with RTP payload
+ * \returns enum osmo_gsm631_sid_class, with symbolic values
+ * OSMO_GSM631_SID_CLASS_SPEECH, OSMO_GSM631_SID_CLASS_INVALID or
+ * OSMO_GSM631_SID_CLASS_VALID corresponding to the 3 possible bit-counting
+ * classifications prescribed by the spec.
+ *
+ * Differences between the more familiar osmo_efr_check_sid() and the present
+ * function are:
+ *
+ * 1. osmo_efr_check_sid() returns true only if the SID frame is absolutely
+ * perfect, with all 95 bits of the SID code word set. However, the
+ * rules of GSM 06.81 §6.1.1 allow up to one bit to be in error,
+ * and the frame is still accepted as valid SID.
+ *
+ * 2. The third possible state of invalid SID is not handled at all by the
+ * simpler osmo_efr_check_sid() function.
+ *
+ * 3. osmo_efr_check_sid() includes a check for 0xC RTP signature, and returns
+ * false if that signature nibble is wrong. That check is not included
+ * in the present version because there is no place for it in the
+ * ETSI-prescribed classification, it is neither speech nor SID. The
+ * assumption is that this function is used to classify the bit content
+ * of received codec frames, not their RTP encoding - the latter needs
+ * to be validated beforehand.
+ *
+ * Which function should one use? The answer depends on the specific
+ * circumstances, and needs to be addressed on a case-by-case basis.
+ */
+enum osmo_gsm631_sid_class osmo_efr_sid_classify(const uint8_t *rtp_payload)
+{
+ struct bitvec bv;
+ uint16_t i, n;
+
+ bv.data = (uint8_t *) rtp_payload;
+ bv.data_len = GSM_EFR_BYTES;
+
+ /* count not-SID-matching bits per the spec */
+ n = 0;
+ for (i = 0; i < ARRAY_SIZE(sid_code_word_bits); i++) {
+ if (bitvec_get_bit_pos(&bv, sid_code_word_bits[i]+4) != ONE)
+ n++;
+ if (n >= 16)
+ return OSMO_GSM631_SID_CLASS_SPEECH;
+ }
+ if (n >= 2)
+ return OSMO_GSM631_SID_CLASS_INVALID;
+ else
+ return OSMO_GSM631_SID_CLASS_VALID;
+}
+
+/*! Reset the SID field of a potentially corrupted, but still valid GSM-EFR
+ * SID frame in RTP encoding to its pristine state (full SID code word).
+ * \param[in] rtp_payload Buffer with RTP payload - must be writable!
+ *
+ * Per GSM 06.62 section 5.3, a freshly minted SID frame consists of 58 bits
+ * of comfort noise parameters (LSF and 4 times fixed codebook gain), 95 bits
+ * of SID code word (all 1s) and 91 unused bits (all 0s). Network elements
+ * that receive SID frames from call leg A uplink and need to retransmit them
+ * on leg B downlink should "rejuvenate" received SID frames prior to
+ * retransmission by resetting the SID field to its pristine state of all 1s;
+ * this function does the job.
+ *
+ * Potential TODO: it would be nice to also zero out the remaining 91 bits
+ * which the spec leaves as reserved, clearing out leg A radio bit errors -
+ * but do we really need to?
+ */
+void osmo_efr_sid_reset(uint8_t *rtp_payload)
+{
+ /* set all 95 SID code word bits to 1 */
+ rtp_payload[6] |= 0x6F;
+ rtp_payload[7] = 0xFF;
+ rtp_payload[8] = 0xFF;
+ rtp_payload[9] |= 0x80;
+ rtp_payload[12] |= 0x3B;
+ rtp_payload[13] = 0xFF;
+ rtp_payload[14] = 0xFF;
+ rtp_payload[15] |= 0xE0;
+ rtp_payload[19] = 0xFF;
+ rtp_payload[20] = 0xFF;
+ rtp_payload[21] = 0xFF;
+ rtp_payload[25] = 0xFF;
+ rtp_payload[26] |= 0xFC;
+ rtp_payload[27] = 0xFF;
+ rtp_payload[28] |= 0xC0;
+}
+
+/*! Preen potentially-SID EFR codec frame in RTP format, ensuring that it is
+ * either a speech frame or a valid SID, and if the latter, making it a
+ * perfect, error-free SID frame.
+ * \param[in] rtp_payload Buffer with RTP payload - must be writable!
+ * \returns true if the frame is good, false otherwise
+ */
+bool osmo_efr_sid_preen(uint8_t *rtp_payload)
+{
+ enum osmo_gsm631_sid_class sidc;
+
+ sidc = osmo_efr_sid_classify(rtp_payload);
+ switch (sidc) {
+ case OSMO_GSM631_SID_CLASS_SPEECH:
+ return true;
+ case OSMO_GSM631_SID_CLASS_INVALID:
+ return false;
+ case OSMO_GSM631_SID_CLASS_VALID:
+ /* "Rejuvenate" this SID frame, correcting any errors */
+ osmo_efr_sid_reset(rtp_payload);
+ return true;
+ default:
+ /* There are only 3 possible SID classifications per GSM 06.81
+ * section 6.1.1, thus any other return value is a grave error
+ * in the code. */
+ OSMO_ASSERT(0);
+ }
+}
diff --git a/src/codec/gsm690.c b/src/codec/gsm690.c
index cc6cdf0c..3b2e6694 100644
--- a/src/codec/gsm690.c
+++ b/src/codec/gsm690.c
@@ -18,10 +18,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <stdint.h>
diff --git a/src/coding/Makefile.am b/src/coding/Makefile.am
index b023668e..0cab66ff 100644
--- a/src/coding/Makefile.am
+++ b/src/coding/Makefile.am
@@ -1,13 +1,14 @@
# This is _NOT_ the library release version, it's an API version.
# Please read Chapter 6 "Library interface versions" of the libtool
# documentation before making any modification
-LIBVERSION=1:1:1
+LIBVERSION=3:0:3
AM_CPPFLAGS = \
-I"$(top_srcdir)/include" \
-I"$(top_builddir)/include" \
- $(TALLOC_CFLAGS)
-AM_CFLAGS = -Wall
+ -I"$(top_builddir)" \
+ $(NULL)
+AM_CFLAGS = -Wall $(TALLOC_CFLAGS)
if ENABLE_PSEUDOTALLOC
AM_CPPFLAGS += -I$(top_srcdir)/src/pseudotalloc
@@ -24,13 +25,15 @@ libosmocoding_la_SOURCES = \
gsm0503_amr_dtx.c
libosmocoding_la_LDFLAGS = \
$(LTLDFLAGS_OSMOCODING) \
- -version-info \
- $(LIBVERSION) \
+ -version-info $(LIBVERSION) \
-no-undefined \
- $(TALLOC_LIBS)
+ $(NULL)
+
libosmocoding_la_LIBADD = \
- ../libosmocore.la \
- ../gsm/libosmogsm.la \
- ../codec/libosmocodec.la
+ $(top_builddir)/src/core/libosmocore.la \
+ $(top_builddir)/src/gsm/libosmogsm.la \
+ $(top_builddir)/src/codec/libosmocodec.la \
+ $(NULL)
EXTRA_DIST = libosmocoding.map
+EXTRA_libosmocoding_la_DEPENDENCIES = libosmocoding.map
diff --git a/src/coding/gsm0503_amr_dtx.c b/src/coding/gsm0503_amr_dtx.c
index 7069b967..7de3b281 100644
--- a/src/coding/gsm0503_amr_dtx.c
+++ b/src/coding/gsm0503_amr_dtx.c
@@ -1,5 +1,5 @@
/*
- * (C) 2020 by sysmocom - s.f.m.c. GmbH, Author: Philipp Maier
+ * (C) 2020-2022 by sysmocom - s.f.m.c. GmbH, Author: Philipp Maier
* All Rights Reserved
*
* SPDX-License-Identifier: GPL-2.0+
@@ -32,6 +32,8 @@
#include <osmocom/coding/gsm0503_parity.h>
#include <osmocom/gsm/gsm0503.h>
+#define S2U(b) ((b) < 0)
+
/* See also: 3GPP TS 05.03, chapter 3.10.1.3, 3.10.5.2 Identification marker */
static const ubit_t id_marker_1[] = { 1, 0, 1, 1, 0, 0, 0, 0, 1 };
@@ -60,7 +62,7 @@ const struct value_string gsm0503_amr_dtx_frame_names[] = {
{ 0, NULL }
};
-static bool detect_afs_id_marker(int *n_errors, int *n_bits_total, const ubit_t * ubits, uint8_t offset, uint8_t count,
+static bool detect_afs_id_marker(int *n_errors, int *n_bits_total, const sbit_t *sbits, uint8_t offset, uint8_t count,
const ubit_t * id_marker, uint8_t id_marker_len)
{
unsigned int i, k;
@@ -69,20 +71,20 @@ static bool detect_afs_id_marker(int *n_errors, int *n_bits_total, const ubit_t
int bits = 0;
/* Override coded in-band data */
- ubits += offset;
+ sbits += offset;
/* Check for identification marker bits */
for (i = 0; i < count; i++) {
for (k = 0; k < 4; k++) {
- if (id_marker[id_bit_nr % id_marker_len] != *ubits)
+ if (*sbits == 0 || id_marker[id_bit_nr % id_marker_len] != S2U(*sbits))
errors++;
id_bit_nr++;
- ubits++;
+ sbits++;
bits++;
}
/* Jump to the next block of 4 bits */
- ubits += 4;
+ sbits += 4;
}
*n_errors = errors;
@@ -92,30 +94,30 @@ static bool detect_afs_id_marker(int *n_errors, int *n_bits_total, const ubit_t
return *n_errors < *n_bits_total / 8;
}
-static bool detect_ahs_id_marker(int *n_errors, int *n_bits_total, const ubit_t * ubits, const ubit_t * id_marker)
+static bool detect_ahs_id_marker(int *n_errors, int *n_bits_total, const sbit_t *sbits, const ubit_t *id_marker)
{
unsigned int i, k;
int errors = 0;
int bits = 0;
/* Override coded in-band data */
- ubits += 16;
+ sbits += 16;
/* Check first identification marker bits (23*9 bits) */
for (i = 0; i < 23; i++) {
for (k = 0; k < 9; k++) {
- if (id_marker[k] != *ubits)
+ if (*sbits == 0 || id_marker[k] != S2U(*sbits))
errors++;
- ubits++;
+ sbits++;
bits++;
}
}
/* Check remaining identification marker bits (5 bits) */
for (k = 0; k < 5; k++) {
- if (id_marker[k] != *ubits)
+ if (*sbits == 0 || id_marker[k] != S2U(*sbits))
errors++;
- ubits++;
+ sbits++;
bits++;
}
@@ -126,7 +128,7 @@ static bool detect_ahs_id_marker(int *n_errors, int *n_bits_total, const ubit_t
return *n_errors < *n_bits_total / 8;
}
-static bool detect_interleaved_ahs_id_marker(int *n_errors, int *n_bits_total, const ubit_t * ubits, uint8_t offset,
+static bool detect_interleaved_ahs_id_marker(int *n_errors, int *n_bits_total, const sbit_t *sbits, uint8_t offset,
uint8_t n_bits, const ubit_t * id_marker, uint8_t id_marker_len)
{
unsigned int i, k;
@@ -136,23 +138,23 @@ static bool detect_interleaved_ahs_id_marker(int *n_errors, int *n_bits_total, c
uint8_t remainder = n_bits % id_marker_len;
/* Override coded in-band data */
- ubits += offset;
+ sbits += offset;
/* Check first identification marker bits (23*9 bits) */
for (i = 0; i < full_rounds; i++) {
for (k = 0; k < id_marker_len; k++) {
- if (id_marker[k] != *ubits)
+ if (*sbits == 0 || id_marker[k] != S2U(*sbits))
errors++;
- ubits += 2;
+ sbits += 2;
bits++;
}
}
/* Check remaining identification marker bits (5 bits) */
for (k = 0; k < remainder; k++) {
- if (id_marker[k] != *ubits)
+ if (*sbits == 0 || id_marker[k] != S2U(*sbits))
errors++;
- ubits += 2;
+ sbits += 2;
bits++;
}
@@ -163,126 +165,128 @@ static bool detect_interleaved_ahs_id_marker(int *n_errors, int *n_bits_total, c
return *n_errors < *n_bits_total / 8;
}
-/* Detect a an FR AMR SID_FIRST frame by its identifcation marker */
-static bool detect_afs_sid_first(int *n_errors, int *n_bits_total, const ubit_t * ubits)
+/* Detect an FR AMR SID_FIRST frame by its identifcation marker */
+static bool detect_afs_sid_first(int *n_errors, int *n_bits_total, const sbit_t *sbits)
{
- return detect_afs_id_marker(n_errors, n_bits_total, ubits, 32, 53, id_marker_0, 9);
+ return detect_afs_id_marker(n_errors, n_bits_total, sbits, 32, 53, id_marker_0, 9);
}
-/* Detect an FR AMR SID_FIRST frame by its identification marker */
-static bool detect_afs_sid_update(int *n_errors, int *n_bits_total, const ubit_t * ubits)
+/* Detect an FR AMR SID_UPDATE frame by its identification marker */
+static bool detect_afs_sid_update(int *n_errors, int *n_bits_total, const sbit_t *sbits)
{
- return detect_afs_id_marker(n_errors, n_bits_total, ubits, 36, 53, id_marker_0, 9);
+ return detect_afs_id_marker(n_errors, n_bits_total, sbits, 36, 53, id_marker_0, 9);
}
-/* Detect an FR AMR SID_FIRST frame by its repeating coded inband data */
-static bool detect_afs_onset(int *n_errors, int *n_bits_total, const ubit_t * ubits)
+/* Detect an FR AMR ONSET frame by its repeating coded inband data */
+static int detect_afs_onset(int *n_errors, int *n_bits_total, const sbit_t *sbits)
{
bool rc;
- rc = detect_afs_id_marker(n_errors, n_bits_total, ubits, 4, 57, codec_mode_1_sid, 16);
+ rc = detect_afs_id_marker(n_errors, n_bits_total, sbits, 4, 57, codec_mode_1_sid, 16);
if (rc)
- return true;
+ return 0;
- rc = detect_afs_id_marker(n_errors, n_bits_total, ubits, 4, 57, codec_mode_2_sid, 16);
+ rc = detect_afs_id_marker(n_errors, n_bits_total, sbits, 4, 57, codec_mode_2_sid, 16);
if (rc)
- return true;
+ return 1;
- rc = detect_afs_id_marker(n_errors, n_bits_total, ubits, 4, 57, codec_mode_3_sid, 16);
+ rc = detect_afs_id_marker(n_errors, n_bits_total, sbits, 4, 57, codec_mode_3_sid, 16);
if (rc)
- return true;
+ return 2;
- rc = detect_afs_id_marker(n_errors, n_bits_total, ubits, 4, 57, codec_mode_4_sid, 16);
+ rc = detect_afs_id_marker(n_errors, n_bits_total, sbits, 4, 57, codec_mode_4_sid, 16);
if (rc)
- return true;
+ return 3;
- return false;
+ return -1;
}
/* Detect an HR AMR SID UPDATE frame by its identification marker */
-static bool detect_ahs_sid_update(int *n_errors, int *n_bits_total, const ubit_t * ubits)
+static bool detect_ahs_sid_update(int *n_errors, int *n_bits_total, const sbit_t *sbits)
{
- return detect_ahs_id_marker(n_errors, n_bits_total, ubits, id_marker_1);
+ return detect_ahs_id_marker(n_errors, n_bits_total, sbits, id_marker_1);
}
/* Detect an HR AMR SID FIRST (part 1) frame by its identification marker */
-static bool detect_ahs_sid_first_p1(int *n_errors, int *n_bits_total, const ubit_t * ubits)
+static bool detect_ahs_sid_first_p1(int *n_errors, int *n_bits_total, const sbit_t *sbits)
{
- return detect_ahs_id_marker(n_errors, n_bits_total, ubits, id_marker_0);
+ return detect_ahs_id_marker(n_errors, n_bits_total, sbits, id_marker_0);
}
/* Detect an HR AMR SID FIRST (part 2) frame by its repeating coded inband data */
-static bool detect_ahs_sid_first_p2(int *n_errors, int *n_bits_total, const ubit_t * ubits)
+static int detect_ahs_sid_first_p2(int *n_errors, int *n_bits_total, const sbit_t *sbits)
{
bool rc;
- rc = detect_interleaved_ahs_id_marker(n_errors, n_bits_total, ubits, 0, 114, codec_mode_1_sid, 16);
+ rc = detect_interleaved_ahs_id_marker(n_errors, n_bits_total, sbits, 0, 114, codec_mode_1_sid, 16);
if (rc)
- return true;
+ return 0;
- rc = detect_interleaved_ahs_id_marker(n_errors, n_bits_total, ubits, 0, 114, codec_mode_2_sid, 16);
+ rc = detect_interleaved_ahs_id_marker(n_errors, n_bits_total, sbits, 0, 114, codec_mode_2_sid, 16);
if (rc)
- return true;
+ return 1;
- rc = detect_interleaved_ahs_id_marker(n_errors, n_bits_total, ubits, 0, 114, codec_mode_3_sid, 16);
+ rc = detect_interleaved_ahs_id_marker(n_errors, n_bits_total, sbits, 0, 114, codec_mode_3_sid, 16);
if (rc)
- return true;
+ return 2;
- rc = detect_interleaved_ahs_id_marker(n_errors, n_bits_total, ubits, 0, 114, codec_mode_4_sid, 16);
+ rc = detect_interleaved_ahs_id_marker(n_errors, n_bits_total, sbits, 0, 114, codec_mode_4_sid, 16);
if (rc)
- return true;
+ return 3;
- return false;
+ return -1;
}
/* Detect an HR AMR ONSET frame by its repeating coded inband data */
-static bool detect_ahs_onset(int *n_errors, int *n_bits_total, const ubit_t * ubits)
+static int detect_ahs_onset(int *n_errors, int *n_bits_total, const sbit_t *sbits)
{
bool rc;
- rc = detect_interleaved_ahs_id_marker(n_errors, n_bits_total, ubits, 1, 114, codec_mode_1_sid, 16);
+ rc = detect_interleaved_ahs_id_marker(n_errors, n_bits_total, sbits, 1, 114, codec_mode_1_sid, 16);
if (rc)
- return true;
+ return 0;
- rc = detect_interleaved_ahs_id_marker(n_errors, n_bits_total, ubits, 1, 114, codec_mode_2_sid, 16);
+ rc = detect_interleaved_ahs_id_marker(n_errors, n_bits_total, sbits, 1, 114, codec_mode_2_sid, 16);
if (rc)
- return true;
+ return 1;
- rc = detect_interleaved_ahs_id_marker(n_errors, n_bits_total, ubits, 1, 114, codec_mode_3_sid, 16);
+ rc = detect_interleaved_ahs_id_marker(n_errors, n_bits_total, sbits, 1, 114, codec_mode_3_sid, 16);
if (rc)
- return true;
+ return 2;
- rc = detect_interleaved_ahs_id_marker(n_errors, n_bits_total, ubits, 1, 114, codec_mode_4_sid, 16);
+ rc = detect_interleaved_ahs_id_marker(n_errors, n_bits_total, sbits, 1, 114, codec_mode_4_sid, 16);
if (rc)
- return true;
+ return 3;
- return false;
+ return -1;
}
/* Detect an HR AMR SID FIRST INHIBIT frame by its identification marker */
-static bool detect_ahs_sid_first_inh(int *n_errors, int *n_bits_total, const ubit_t * ubits)
+static bool detect_ahs_sid_first_inh(int *n_errors, int *n_bits_total, const sbit_t *sbits)
{
- return detect_interleaved_ahs_id_marker(n_errors, n_bits_total, ubits, 33, 212, id_marker_1, 9);
+ return detect_interleaved_ahs_id_marker(n_errors, n_bits_total, sbits, 33, 212, id_marker_1, 9);
}
/* Detect an HR AMR SID UPDATE INHIBIT frame by its identification marker */
-static bool detect_ahs_sid_update_inh(int *n_errors, int *n_bits_total, const ubit_t * ubits)
+static bool detect_ahs_sid_update_inh(int *n_errors, int *n_bits_total, const sbit_t *sbits)
{
- return detect_interleaved_ahs_id_marker(n_errors, n_bits_total, ubits, 33, 212, id_marker_0, 9);
+ return detect_interleaved_ahs_id_marker(n_errors, n_bits_total, sbits, 33, 212, id_marker_0, 9);
}
/*! Detect FR AMR DTX frame in unmapped, deinterleaved frame bits.
- * \param[in] ubits input bits (456 bit).
* \param[out] n_errors number of errornous bits.
* \param[out] n_bits_total number of checked bits.
+ * \param[out] mode_id codec mode ID (0..3 or -1).
+ * \param[in] sbits input soft-bits (456 bit).
* \returns dtx frame type. */
-enum gsm0503_amr_dtx_frames gsm0503_detect_afs_dtx_frame(int *n_errors, int *n_bits_total, const ubit_t * ubits)
+enum gsm0503_amr_dtx_frames gsm0503_detect_afs_dtx_frame2(int *n_errors, int *n_bits_total,
+ int *mode_id, const sbit_t *sbits)
{
- if (detect_afs_sid_first(n_errors, n_bits_total, ubits))
+ if (detect_afs_sid_first(n_errors, n_bits_total, sbits))
return AFS_SID_FIRST;
- if (detect_afs_sid_update(n_errors, n_bits_total, ubits))
+ if (detect_afs_sid_update(n_errors, n_bits_total, sbits))
return AFS_SID_UPDATE;
- if (detect_afs_onset(n_errors, n_bits_total, ubits))
+ if ((*mode_id = detect_afs_onset(n_errors, n_bits_total, sbits)) != -1)
return AFS_ONSET;
*n_errors = 0;
@@ -290,27 +294,62 @@ enum gsm0503_amr_dtx_frames gsm0503_detect_afs_dtx_frame(int *n_errors, int *n_b
return AMR_OTHER;
}
-/*! Detect HR AMR DTX frame in unmapped, deinterleaved frame bits.
+/*! Detect FR AMR DTX frame in unmapped, deinterleaved frame bits.
+ * DEPRECATED: use gsm0503_detect_afs_dtx_frame2() instead.
+ * \param[out] n_errors number of errornous bits.
+ * \param[out] n_bits_total number of checked bits.
* \param[in] ubits input bits (456 bit).
+ * \returns dtx frame type. */
+enum gsm0503_amr_dtx_frames gsm0503_detect_afs_dtx_frame(int *n_errors, int *n_bits_total,
+ const ubit_t *ubits)
+{
+ int mode_id; /* unused */
+ sbit_t sbits[456];
+
+ osmo_ubit2sbit(&sbits[0], &ubits[0], sizeof(sbits));
+ return gsm0503_detect_afs_dtx_frame2(n_errors, n_bits_total, &mode_id, &sbits[0]);
+}
+
+/*! Detect HR AMR DTX frame in unmapped, deinterleaved frame bits.
* \param[out] n_errors number of errornous bits.
* \param[out] n_bits_total number of checked bits.
+ * \param[out] mode_id codec ID (CMI or CMC/CMR).
+ * \param[out] mode_id codec mode ID (0..3 or -1).
+ * \param[in] sbits input soft-bits (456 bit).
* \returns dtx frame type, */
-enum gsm0503_amr_dtx_frames gsm0503_detect_ahs_dtx_frame(int *n_errors, int *n_bits_total, const ubit_t * ubits)
+enum gsm0503_amr_dtx_frames gsm0503_detect_ahs_dtx_frame2(int *n_errors, int *n_bits_total,
+ int *mode_id, const sbit_t *sbits)
{
- if (detect_ahs_sid_update(n_errors, n_bits_total, ubits))
+ if (detect_ahs_sid_update(n_errors, n_bits_total, sbits))
return AHS_SID_UPDATE;
- if (detect_ahs_sid_first_inh(n_errors, n_bits_total, ubits))
+ if (detect_ahs_sid_first_inh(n_errors, n_bits_total, sbits))
return AHS_SID_FIRST_INH;
- if (detect_ahs_sid_update_inh(n_errors, n_bits_total, ubits))
+ if (detect_ahs_sid_update_inh(n_errors, n_bits_total, sbits))
return AHS_SID_UPDATE_INH;
- if (detect_ahs_sid_first_p1(n_errors, n_bits_total, ubits))
+ if (detect_ahs_sid_first_p1(n_errors, n_bits_total, sbits))
return AHS_SID_FIRST_P1;
- if (detect_ahs_sid_first_p2(n_errors, n_bits_total, ubits))
+ if ((*mode_id = detect_ahs_sid_first_p2(n_errors, n_bits_total, sbits)) != -1)
return AHS_SID_FIRST_P2;
- if (detect_ahs_onset(n_errors, n_bits_total, ubits))
+ if ((*mode_id = detect_ahs_onset(n_errors, n_bits_total, sbits)) != -1)
return AHS_ONSET;
*n_errors = 0;
*n_bits_total = 0;
return AMR_OTHER;
}
+
+/*! Detect HR AMR DTX frame in unmapped, deinterleaved frame bits.
+ * DEPRECATED: use gsm0503_detect_ahs_dtx_frame2() instead.
+ * \param[out] n_errors number of errornous bits.
+ * \param[out] n_bits_total number of checked bits.
+ * \param[in] ubits input bits (456 bit).
+ * \returns dtx frame type, */
+enum gsm0503_amr_dtx_frames gsm0503_detect_ahs_dtx_frame(int *n_errors, int *n_bits_total,
+ const ubit_t *ubits)
+{
+ int mode_id; /* unused */
+ sbit_t sbits[456];
+
+ osmo_ubit2sbit(&sbits[0], &ubits[0], sizeof(sbits));
+ return gsm0503_detect_ahs_dtx_frame2(n_errors, n_bits_total, &mode_id, &sbits[0]);
+}
diff --git a/src/coding/gsm0503_coding.c b/src/coding/gsm0503_coding.c
index 1bec56ea..022f4656 100644
--- a/src/coding/gsm0503_coding.c
+++ b/src/coding/gsm0503_coding.c
@@ -17,10 +17,6 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <stdio.h>
@@ -35,9 +31,7 @@
#include <osmocom/core/crcgen.h>
#include <osmocom/core/endian.h>
-#include <osmocom/gprs/protocol/gsm_04_60.h>
-#include <osmocom/gprs/gprs_rlc.h>
-
+#include <osmocom/gsm/protocol/gsm_44_060.h>
#include <osmocom/gsm/protocol/gsm_04_08.h>
#include <osmocom/gsm/gsm0503.h>
#include <osmocom/codec/codec.h>
@@ -542,24 +536,29 @@ static int osmo_conv_decode_ber_punctured(const struct osmo_conv_code *code,
int *n_errors, int *n_bits_total,
const uint8_t *data_punc)
{
- int res, i, coded_len;
+ int res, coded_len;
ubit_t recoded[EGPRS_DATA_C_MAX];
res = osmo_conv_decode(code, input, output);
- if (n_bits_total || n_errors) {
- coded_len = osmo_conv_encode(code, output, recoded);
- OSMO_ASSERT(sizeof(recoded) / sizeof(recoded[0]) >= coded_len);
- }
+ if (!n_bits_total && !n_errors)
+ return res;
+
+ coded_len = osmo_conv_encode(code, output, recoded);
+ OSMO_ASSERT(ARRAY_SIZE(recoded) >= coded_len);
/* Count bit errors */
if (n_errors) {
*n_errors = 0;
- for (i = 0; i < coded_len; i++) {
- if (((!data_punc) || (data_punc && !data_punc[i])) &&
- !((recoded[i] && input[i] < 0) ||
- (!recoded[i] && input[i] > 0)) )
- *n_errors += 1;
+ for (unsigned int i = 0; i < coded_len; i++) {
+ /* punctured bits do not count as bit errors */
+ if (data_punc != NULL && data_punc[i])
+ continue;
+ if (recoded[i] == 1 && input[i] < 0)
+ continue;
+ if (recoded[i] == 0 && input[i] > 0)
+ continue;
+ *n_errors += 1;
}
}
@@ -611,7 +610,7 @@ static int _xcch_decode_cB(uint8_t *l2_data, const sbit_t *cB,
/*! convenience wrapper for encoding to coded bits
* \param[out] cB caller-allocated buffer for 456 coded bits as per TS 05.03 4.1.3
- * \param[out] l2_data to-be-encoded L2 Frame
+ * \param[in] l2_data to-be-encoded L2 Frame
* \returns 0 */
static int _xcch_encode_cB(ubit_t *cB, const uint8_t *l2_data)
{
@@ -926,10 +925,10 @@ static int egprs_decode_data(uint8_t *l2_data, const sbit_t *c,
* \param[out] l2_data caller-allocated buffer for L2 Frame
* \param[in] bursts burst input data as soft unpacked bits
* \param[in] nbits number of bits in \a bursts
- * \param usf_p unused argument ?!?
+ * \param usf_p Uplink State Flag, FIXME: not implemented
* \param[out] n_errors number of detected bit-errors
* \param[out] n_bits_total total number of decoded bits
- * \returns 0 on success; negative on error */
+ * \returns number of bytes decoded; negative on error */
int gsm0503_pdtch_egprs_decode(uint8_t *l2_data, const sbit_t *bursts, uint16_t nbits,
uint8_t *usf_p, int *n_errors, int *n_bits_total)
{
@@ -1015,10 +1014,10 @@ int gsm0503_pdtch_egprs_decode(uint8_t *l2_data, const sbit_t *bursts, uint16_t
/*! Decode GPRS PDTCH
* \param[out] l2_data caller-allocated buffer for L2 Frame
* \param[in] bursts burst input data as soft unpacked bits
- * \param[out] usf_p uplink stealing flag
+ * \param[out] usf_p Uplink State Flag, only relevant for DL blocks
* \param[out] n_errors number of detected bit-errors
- * \param[out] n_bits_total total number of dcoded bits
- * \returns 0 on success; negative on error */
+ * \param[out] n_bits_total total number of decoded bits
+ * \returns number of bytes decoded; negative on error */
int gsm0503_pdtch_decode(uint8_t *l2_data, const sbit_t *bursts, uint8_t *usf_p,
int *n_errors, int *n_bits_total)
{
@@ -1047,6 +1046,10 @@ int gsm0503_pdtch_decode(uint8_t *l2_data, const sbit_t *bursts, uint8_t *usf_p,
osmo_conv_decode_ber(&gsm0503_xcch, cB,
conv, n_errors, n_bits_total);
+ /* the three USF bits d(0),d(1),d(2) are *not* precoded */
+ if (usf_p)
+ *usf_p = (conv[0] << 2) | (conv[1] << 1) | (conv[2] << 0);
+
rv = osmo_crc64gen_check_bits(&gsm0503_fire_crc40,
conv, 184, conv + 184);
if (rv)
@@ -1056,6 +1059,7 @@ int gsm0503_pdtch_decode(uint8_t *l2_data, const sbit_t *bursts, uint8_t *usf_p,
return 23;
case 2:
+ /* reorder, set punctured bits to 0 (unknown state) */
for (i = 587, j = 455; i >= 0; i--) {
if (!gsm0503_puncture_cs2[i])
cB[i] = cB[j--];
@@ -1063,9 +1067,15 @@ int gsm0503_pdtch_decode(uint8_t *l2_data, const sbit_t *bursts, uint8_t *usf_p,
cB[i] = 0;
}
- osmo_conv_decode_ber(&gsm0503_cs2_np, cB,
- conv, n_errors, n_bits_total);
+ /* decode as if puncturing was not employed (note '_np') */
+ osmo_conv_decode_ber_punctured(&gsm0503_cs2_np, cB, conv,
+ n_errors, NULL,
+ gsm0503_puncture_cs2);
+ /* indicate the actual amount of coded bits (excluding punctured ones) */
+ if (n_bits_total != NULL)
+ *n_bits_total = 456;
+ /* 5.1.2.2 a) the three USF bits d(0),d(1),d(2) are precoded into six bits */
for (i = 0; i < 8; i++) {
for (j = 0, k = 0; j < 6; j++)
k += abs(((int)gsm0503_usf2six[i][j]) - ((int)conv[j]));
@@ -1091,6 +1101,7 @@ int gsm0503_pdtch_decode(uint8_t *l2_data, const sbit_t *bursts, uint8_t *usf_p,
return 34;
case 3:
+ /* reorder, set punctured bits to 0 (unknown state) */
for (i = 675, j = 455; i >= 0; i--) {
if (!gsm0503_puncture_cs3[i])
cB[i] = cB[j--];
@@ -1098,9 +1109,15 @@ int gsm0503_pdtch_decode(uint8_t *l2_data, const sbit_t *bursts, uint8_t *usf_p,
cB[i] = 0;
}
- osmo_conv_decode_ber(&gsm0503_cs3_np, cB,
- conv, n_errors, n_bits_total);
+ /* decode as if puncturing was not employed (note '_np') */
+ osmo_conv_decode_ber_punctured(&gsm0503_cs3_np, cB, conv,
+ n_errors, NULL,
+ gsm0503_puncture_cs3);
+ /* indicate the actual amount of coded bits (excluding punctured ones) */
+ if (n_bits_total != NULL)
+ *n_bits_total = 456;
+ /* 5.1.3.2 a) the three USF bits d(0),d(1),d(2) are precoded into six bits */
for (i = 0; i < 8; i++) {
for (j = 0, k = 0; j < 6; j++)
k += abs(((int)gsm0503_usf2six[i][j]) - ((int)conv[j]));
@@ -1129,6 +1146,7 @@ int gsm0503_pdtch_decode(uint8_t *l2_data, const sbit_t *bursts, uint8_t *usf_p,
for (i = 12; i < 456; i++)
conv[i] = (cB[i] < 0) ? 1 : 0;
+ /* 5.1.4.2 a) the three USF bits d(0),d(1),d(2) are precoded into twelve bits */
for (i = 0; i < 8; i++) {
for (j = 0, k = 0; j < 12; j++)
k += abs(((int)gsm0503_usf2twelve_sbit[i][j]) - ((int)cB[j]));
@@ -1577,24 +1595,22 @@ static void tch_fr_disassemble(ubit_t *b_bits,
}
}
-/* assemble a HR codec frame in format as used inside RTP */
+/* assemble a HR codec frame in the canonical format of ETSI TS 101 318 */
static void tch_hr_reassemble(uint8_t *tch_data, const ubit_t *b_bits)
{
- int i, j;
-
- tch_data[0] = 0x00; /* F = 0, FT = 000 */
- memset(tch_data + 1, 0, 14);
+ int i;
- for (i = 0, j = 8; i < 112; i++, j++)
- tch_data[j >> 3] |= (b_bits[i] << (7 - (j & 7)));
+ memset(tch_data, 0, GSM_HR_BYTES);
+ for (i = 0; i < 112; i++)
+ tch_data[i >> 3] |= (b_bits[i] << (7 - (i & 7)));
}
static void tch_hr_disassemble(ubit_t *b_bits, const uint8_t *tch_data)
{
- int i, j;
+ int i;
- for (i = 0, j = 8; i < 112; i++, j++)
- b_bits[i] = (tch_data[j >> 3] >> (7 - (j & 7))) & 1;
+ for (i = 0; i < 112; i++)
+ b_bits[i] = (tch_data[i >> 3] >> (7 - (i & 7))) & 1;
}
/* assemble a EFR codec frame in format as used inside RTP */
@@ -1809,7 +1825,7 @@ static void tch_efr_unreorder(ubit_t *s, ubit_t *p, const ubit_t *w)
s[119] = (sum >= 2);
memcpy(s + 121, w + 125, 53);
sum = s[172] + w[178] + w[179];
- s[172] = (sum > 2);
+ s[172] = (sum >= 2);
memcpy(s + 174, w + 180, 50);
sum = s[222] + w[230] + w[231];
s[222] = (sum >= 2);
@@ -1865,7 +1881,7 @@ int gsm0503_tch_fr_decode(uint8_t *tch_data, const sbit_t *bursts,
return -1;
}
- return 23;
+ return GSM_MACBLOCK_LEN;
}
osmo_conv_decode_ber(&gsm0503_tch_fr, cB, conv, n_errors, n_bits_total);
@@ -1932,40 +1948,39 @@ int gsm0503_tch_fr_encode(ubit_t *bursts, const uint8_t *tch_data,
switch (len) {
case GSM_EFR_BYTES: /* TCH EFR */
-
tch_efr_disassemble(s, tch_data);
-
tch_efr_protected(s, b);
-
osmo_crc8gen_set_bits(&gsm0503_tch_efr_crc8, b, 65, p);
-
tch_efr_reorder(w, s, p);
-
tch_efr_w_to_d(d, w);
-
goto coding_efr_fr;
case GSM_FR_BYTES: /* TCH FR */
tch_fr_disassemble(w, tch_data, net_order);
-
tch_fr_b_to_d(d, w);
-
coding_efr_fr:
osmo_crc8gen_set_bits(&gsm0503_tch_fr_crc3, d, 50, p);
-
tch_fr_reorder(conv, d, p);
-
memcpy(cB + 378, d + 182, 78);
-
osmo_conv_encode(&gsm0503_tch_fr, conv, cB);
-
h = 0;
-
+ break;
+ case 0: /* no data, induce BFI in the receiver */
+ /* Do the same thing that sysmoBTS PHY does when fed a 0-length
+ * payload for DL: set all u(k) bits to 0, and do the same
+ * with all class 2 bits. This operation is NOT the same as
+ * an FR codec frame of all zero bits: with all-zeros d(k) input
+ * the CRC3 function will produce 111 output, whereas we
+ * transmit 000 in those parity bits too. The result will be
+ * an induced BFI (bad frame indication) condition in the
+ * receiver, for both TCH/FS and TCH/EFS decoders. */
+ memset(conv, 0, sizeof(conv));
+ memset(cB + 378, 0, 78);
+ osmo_conv_encode(&gsm0503_tch_fr, conv, cB);
+ h = 0;
break;
case GSM_MACBLOCK_LEN: /* FACCH */
_xcch_encode_cB(cB, tch_data);
-
h = 1;
-
break;
default:
return -1;
@@ -1982,13 +1997,13 @@ coding_efr_fr:
}
/*! Perform channel decoding of a HR(v1) channel according TS 05.03
- * \param[out] tch_data Codec frame in RTP payload format
- * \param[in] bursts buffer containing the symbols of 8 bursts
+ * \param[out] tch_data Codec frame in TS 101 318 canonical format
+ * \param[in] bursts buffer containing the symbols of 6 bursts
* \param[in] odd Odd (1) or even (0) frame number
* \param[out] n_errors Number of detected bit errors
* \param[out] n_bits_total Total number of bits
* \returns length of bytes used in \a tch_data output buffer; negative on error */
-int gsm0503_tch_hr_decode(uint8_t *tch_data, const sbit_t *bursts, int odd,
+int gsm0503_tch_hr_decode2(uint8_t *tch_data, const sbit_t *bursts, int odd,
int *n_errors, int *n_bits_total)
{
sbit_t iB[912], cB[456], h;
@@ -2002,7 +2017,7 @@ int gsm0503_tch_hr_decode(uint8_t *tch_data, const sbit_t *bursts, int odd,
steal -= h;
}
- for (i = 2; i < 5; i++) {
+ for (i = 2; i < 6; i++) {
gsm0503_tch_burst_unmap(NULL, &bursts[i * 116], &h, 1);
steal -= h;
}
@@ -2055,7 +2070,35 @@ int gsm0503_tch_hr_decode(uint8_t *tch_data, const sbit_t *bursts, int odd,
tch_hr_reassemble(tch_data, b);
- return 15;
+ return GSM_HR_BYTES;
+}
+
+/*! Perform channel decoding of a HR(v1) channel according TS 05.03,
+ * deprecated legacy API.
+ * \param[out] tch_data Codec frame in pseudo-RFC5993 format
+ * \param[in] bursts buffer containing the symbols of 6 bursts
+ * \param[in] odd Odd (1) or even (0) frame number
+ * \param[out] n_errors Number of detected bit errors
+ * \param[out] n_bits_total Total number of bits
+ * \returns length of bytes used in \a tch_data output buffer; negative on error
+ *
+ * The HR1 codec frame format returned by this function is pseudo-RFC5993,
+ * not true RFC 5993, as there is no SID classification being done
+ * and the FT bits in the ToC octet are always set to 0 - but this
+ * arguably-bogus format is the legacy public API.
+ */
+int gsm0503_tch_hr_decode(uint8_t *tch_data, const sbit_t *bursts, int odd,
+ int *n_errors, int *n_bits_total)
+{
+ int rc;
+
+ rc = gsm0503_tch_hr_decode2(tch_data, bursts, odd, n_errors,
+ n_bits_total);
+ if (rc != GSM_HR_BYTES)
+ return rc;
+ memmove(tch_data + 1, tch_data, GSM_HR_BYTES);
+ tch_data[0] = 0x00; /* FT=0, note absence of SID classification */
+ return GSM_HR_BYTES_RTP_RFC5993;
}
/*! Perform channel encoding on a TCH/HS channel according to TS 05.03
@@ -2070,46 +2113,41 @@ int gsm0503_tch_hr_encode(ubit_t *bursts, const uint8_t *tch_data, int len)
int i;
switch (len) {
- case 15: /* TCH HR */
+ case GSM_HR_BYTES_RTP_RFC5993: /* TCH HR with RFC 5993 prefix */
+ tch_data++;
+ /* fall-through */
+ case GSM_HR_BYTES: /* TCH HR in "pure" form */
tch_hr_disassemble(b, tch_data);
-
tch_hr_b_to_d(d, b);
-
osmo_crc8gen_set_bits(&gsm0503_tch_fr_crc3, d + 73, 22, p);
-
tch_hr_reorder(conv, d, p);
-
- osmo_conv_encode(&gsm0503_tch_hr, conv, cB);
-
memcpy(cB + 211, d + 95, 17);
-
+hr_conv_coding:
+ osmo_conv_encode(&gsm0503_tch_hr, conv, cB);
h = 0;
-
gsm0503_tch_hr_interleave(cB, iB);
-
for (i = 0; i < 4; i++) {
gsm0503_tch_burst_map(&iB[i * 114],
&bursts[i * 116], &h, i >> 1);
}
-
break;
+ case 0: /* no data, induce BFI in the receiver */
+ /* see comments in gsm0503_tch_fr_encode() - same deal here */
+ memset(conv, 0, sizeof(conv));
+ memset(cB + 211, 0, 17);
+ goto hr_conv_coding;
case GSM_MACBLOCK_LEN: /* FACCH */
_xcch_encode_cB(cB, tch_data);
-
h = 1;
-
gsm0503_tch_fr_interleave(cB, iB);
-
for (i = 0; i < 6; i++) {
gsm0503_tch_burst_map(&iB[i * 114],
&bursts[i * 116], &h, i >> 2);
}
-
for (i = 2; i < 4; i++) {
gsm0503_tch_burst_map(&iB[i * 114 + 456],
&bursts[i * 116], &h, 1);
}
-
break;
default:
return -1;
@@ -2118,6 +2156,26 @@ int gsm0503_tch_hr_encode(ubit_t *bursts, const uint8_t *tch_data, int len)
return 0;
}
+/* TCH/AFS: parse codec ID (CMI or CMC/CMR) from coded in-band data (16 bit) */
+static uint8_t gsm0503_tch_afs_decode_inband(const sbit_t *cB)
+{
+ unsigned int id = 0, best = 0;
+ unsigned int i, j, k;
+
+ for (i = 0; i < 4; i++) {
+ /* FIXME: why not using remaining (16 - 8) soft-bits here? */
+ for (j = 0, k = 0; j < 8; j++)
+ k += abs(((int)gsm0503_afs_ic_sbit[i][j]) - ((int)cB[j]));
+
+ if (i == 0 || k < best) {
+ best = k;
+ id = i;
+ }
+ }
+
+ return id;
+}
+
/*! Perform channel decoding of a TCH/AFS channel according TS 05.03
* \param[out] tch_data Codec frame in RTP payload format
* \param[in] bursts buffer containing the symbols of 8 bursts
@@ -2160,12 +2218,10 @@ int gsm0503_tch_afs_decode_dtx(uint8_t *tch_data, const sbit_t *bursts,
{
sbit_t iB[912], cB[456], h;
ubit_t d[244], p[6], conv[250];
- int i, j, k, best = 0, rv, len, steal = 0, id = 0;
- ubit_t cBd[456];
+ int i, rv, len, steal = 0, id = -1;
*n_errors = 0; *n_bits_total = 0;
static ubit_t sid_first_dummy[64] = { 0 };
sbit_t sid_update_enc[256];
- uint8_t dtx_prev;
for (i=0; i<8; i++) {
gsm0503_tch_burst_unmap(&iB[i * 114], &bursts[i * 116], &h, i >> 2);
@@ -2175,6 +2231,12 @@ int gsm0503_tch_afs_decode_dtx(uint8_t *tch_data, const sbit_t *bursts,
gsm0503_tch_fr_deinterleave(cB, iB);
if (steal > 0) {
+ /* If not NULL, dtx indicates type of previously decoded TCH/AFS frame.
+ * It's normally updated by gsm0503_detect_afs_dtx_frame2(), which is not
+ * reached in case of FACCH. Reset it here to avoid FACCH/F frames being
+ * misinterpreted as AMR's special DTX frames. */
+ if (dtx != NULL)
+ *dtx = AMR_OTHER;
rv = _xcch_decode_cB(tch_data, cB, n_errors, n_bits_total);
if (rv) {
/* Error decoding FACCH frame */
@@ -2186,16 +2248,20 @@ int gsm0503_tch_afs_decode_dtx(uint8_t *tch_data, const sbit_t *bursts,
/* Determine the DTX frame type (SID_UPDATE, ONSET etc...) */
if (dtx) {
- osmo_sbit2ubit(cBd, cB, 456);
- dtx_prev = *dtx;
- *dtx = gsm0503_detect_afs_dtx_frame(n_errors, n_bits_total, cBd);
+ const enum gsm0503_amr_dtx_frames dtx_prev = *dtx;
+
+ *dtx = gsm0503_detect_afs_dtx_frame2(n_errors, n_bits_total, &id, cB);
- if (dtx_prev == AFS_SID_UPDATE && *dtx == AMR_OTHER) {
+ switch (*dtx) {
+ case AMR_OTHER:
/* NOTE: The AFS_SID_UPDATE frame is splitted into
* two half rate frames. If the id marker frame
* (AFS_SID_UPDATE) is detected the following frame
* contains the actual comfort noised data part of
* (AFS_SID_UPDATE_CN). */
+ if (dtx_prev != AFS_SID_UPDATE)
+ break;
+ /* TODO: parse CMI _and_ CMC/CMR (16 + 16 bit) */
*dtx = AFS_SID_UPDATE_CN;
extract_afs_sid_update(sid_update_enc, cB);
@@ -2211,35 +2277,28 @@ int gsm0503_tch_afs_decode_dtx(uint8_t *tch_data, const sbit_t *bursts,
tch_amr_sid_update_append(conv, 1,
(codec_mode_req) ? codec[*ft]
- : codec[id]);
+ : codec[id > 0 ? id : 0]);
tch_amr_reassemble(tch_data, conv, 39);
len = 5;
goto out;
- } else if (*dtx == AFS_SID_FIRST) {
+ case AFS_SID_FIRST: /* TODO: parse CMI or CMC/CMR (16 bit) */
tch_amr_sid_update_append(sid_first_dummy, 0,
(codec_mode_req) ? codec[*ft]
- : codec[id]);
- tch_amr_reassemble(tch_data, conv, 39);
+ : codec[id > 0 ? id : 0]);
+ tch_amr_reassemble(tch_data, sid_first_dummy, 39);
len = 5;
goto out;
- } else if (*dtx == AFS_ONSET) {
+ case AFS_SID_UPDATE: /* TODO: parse CMI _and_ CMC/CMR (16 + 16 bit) */
+ case AFS_ONSET:
len = 0;
goto out;
+ default:
+ break;
}
}
- for (i = 0; i < 4; i++) {
- for (j = 0, k = 0; j < 8; j++)
- k += abs(((int)gsm0503_afs_ic_sbit[i][j]) - ((int)cB[j]));
-
- if (i == 0 || k < best) {
- best = k;
- id = i;
- }
- }
-
- /* Check if indicated codec fits into range of codecs */
- if (id >= codecs) {
+ /* Parse codec ID (CMI or CMC/CMR) and check if it fits into range of codecs */
+ if ((id = gsm0503_tch_afs_decode_inband(&cB[0])) >= codecs) {
/* Codec mode out of range, return id */
return id;
}
@@ -2390,10 +2449,12 @@ int gsm0503_tch_afs_decode_dtx(uint8_t *tch_data, const sbit_t *bursts,
out:
/* Change codec request / indication, if frame is valid */
- if (codec_mode_req)
- *cmr = id;
- else
- *ft = id;
+ if (id != -1) {
+ if (codec_mode_req)
+ *cmr = id;
+ else
+ *ft = id;
+ }
return len;
}
@@ -2401,7 +2462,7 @@ out:
/*! Perform channel encoding on a TCH/AFS channel according to TS 05.03
* \param[out] bursts caller-allocated output buffer for bursts bits
* \param[in] tch_data Codec input data in RTP payload format
- * \param[in] len Length of \a tch_data in bytes
+ * \param[in] len Length of \a tch_data in bytes or 0 to generate a bad frame
* \param[in] codec_mode_req Use CMR (1) or FT (0)
* \param[in] codec Array of codecs (active codec set)
* \param[in] codecs Number of entries in \a codec
@@ -2409,7 +2470,7 @@ out:
* \param[in] cmr Codec Mode Request (used in codec_mode_req = 1 only)
* \returns 0 in case of success; negative on error */
int gsm0503_tch_afs_encode(ubit_t *bursts, const uint8_t *tch_data, int len,
- int codec_mode_req, uint8_t *codec, int codecs, uint8_t ft,
+ int codec_mode_req, const uint8_t *codec, int codecs, uint8_t ft,
uint8_t cmr)
{
ubit_t iB[912], cB[456], h;
@@ -2427,28 +2488,27 @@ int gsm0503_tch_afs_encode(ubit_t *bursts, const uint8_t *tch_data, int len,
h = 0;
- if (codec_mode_req) {
- if (cmr >= codecs) {
- /* FIXME: CMR ID is not in codec list! */
- return -1;
- }
- id = cmr;
- } else {
- if (ft >= codecs) {
- /* FIXME: FT ID is not in codec list! */
- return -1;
- }
- id = ft;
- }
+ id = codec_mode_req ? cmr : ft;
+ if (OSMO_UNLIKELY(id >= ARRAY_SIZE(gsm0503_afs_ic_ubit)))
+ return -1;
+ if (OSMO_UNLIKELY(ft >= codecs))
+ return -1;
switch (codec[ft]) {
case 7: /* TCH/AFS12.2 */
- if (len != 31)
- goto invalid_length;
-
- tch_amr_disassemble(d, tch_data, 244);
-
- osmo_crc8gen_set_bits(&gsm0503_amr_crc6, d, 81, p);
+ if (!len) {
+ /* No data, induce BFI in the receiver by inverted CRC bits.
+ * The data bit are all 0, so the correct parity bits would be 111111. */
+ memset(d, 0, 244);
+ memset(p, 0, 6);
+ } else {
+ if (len != 31)
+ goto invalid_length;
+
+ tch_amr_disassemble(d, tch_data, 244);
+
+ osmo_crc8gen_set_bits(&gsm0503_amr_crc6, d, 81, p);
+ }
tch_amr_merge(conv, d, p, 244, 81);
@@ -2456,12 +2516,18 @@ int gsm0503_tch_afs_encode(ubit_t *bursts, const uint8_t *tch_data, int len,
break;
case 6: /* TCH/AFS10.2 */
- if (len != 26)
- goto invalid_length;
+ if (!len) {
+ /* See comment above. */
+ memset(d, 0, 204);
+ memset(p, 0, 6);
+ } else {
+ if (len != 26)
+ goto invalid_length;
- tch_amr_disassemble(d, tch_data, 204);
+ tch_amr_disassemble(d, tch_data, 204);
- osmo_crc8gen_set_bits(&gsm0503_amr_crc6, d, 65, p);
+ osmo_crc8gen_set_bits(&gsm0503_amr_crc6, d, 65, p);
+ }
tch_amr_merge(conv, d, p, 204, 65);
@@ -2469,12 +2535,18 @@ int gsm0503_tch_afs_encode(ubit_t *bursts, const uint8_t *tch_data, int len,
break;
case 5: /* TCH/AFS7.95 */
- if (len != 20)
- goto invalid_length;
+ if (!len) {
+ /* See comment above. */
+ memset(d, 0, 159);
+ memset(p, 0, 6);
+ } else {
+ if (len != 20)
+ goto invalid_length;
- tch_amr_disassemble(d, tch_data, 159);
+ tch_amr_disassemble(d, tch_data, 159);
- osmo_crc8gen_set_bits(&gsm0503_amr_crc6, d, 75, p);
+ osmo_crc8gen_set_bits(&gsm0503_amr_crc6, d, 75, p);
+ }
tch_amr_merge(conv, d, p, 159, 75);
@@ -2482,12 +2554,18 @@ int gsm0503_tch_afs_encode(ubit_t *bursts, const uint8_t *tch_data, int len,
break;
case 4: /* TCH/AFS7.4 */
- if (len != 19)
- goto invalid_length;
+ if (!len) {
+ /* See comment above. */
+ memset(d, 0, 148);
+ memset(p, 0, 6);
+ } else {
+ if (len != 19)
+ goto invalid_length;
- tch_amr_disassemble(d, tch_data, 148);
+ tch_amr_disassemble(d, tch_data, 148);
- osmo_crc8gen_set_bits(&gsm0503_amr_crc6, d, 61, p);
+ osmo_crc8gen_set_bits(&gsm0503_amr_crc6, d, 61, p);
+ }
tch_amr_merge(conv, d, p, 148, 61);
@@ -2495,12 +2573,18 @@ int gsm0503_tch_afs_encode(ubit_t *bursts, const uint8_t *tch_data, int len,
break;
case 3: /* TCH/AFS6.7 */
- if (len != 17)
- goto invalid_length;
+ if (!len) {
+ /* See comment above. */
+ memset(d, 0, 134);
+ memset(p, 0, 6);
+ } else {
+ if (len != 17)
+ goto invalid_length;
- tch_amr_disassemble(d, tch_data, 134);
+ tch_amr_disassemble(d, tch_data, 134);
- osmo_crc8gen_set_bits(&gsm0503_amr_crc6, d, 55, p);
+ osmo_crc8gen_set_bits(&gsm0503_amr_crc6, d, 55, p);
+ }
tch_amr_merge(conv, d, p, 134, 55);
@@ -2508,12 +2592,18 @@ int gsm0503_tch_afs_encode(ubit_t *bursts, const uint8_t *tch_data, int len,
break;
case 2: /* TCH/AFS5.9 */
- if (len != 15)
- goto invalid_length;
+ if (!len) {
+ /* See comment above. */
+ memset(d, 0, 118);
+ memset(p, 0, 6);
+ } else {
+ if (len != 15)
+ goto invalid_length;
- tch_amr_disassemble(d, tch_data, 118);
+ tch_amr_disassemble(d, tch_data, 118);
- osmo_crc8gen_set_bits(&gsm0503_amr_crc6, d, 55, p);
+ osmo_crc8gen_set_bits(&gsm0503_amr_crc6, d, 55, p);
+ }
tch_amr_merge(conv, d, p, 118, 55);
@@ -2521,12 +2611,18 @@ int gsm0503_tch_afs_encode(ubit_t *bursts, const uint8_t *tch_data, int len,
break;
case 1: /* TCH/AFS5.15 */
- if (len != 13)
- goto invalid_length;
+ if (!len) {
+ /* See comment above. */
+ memset(d, 0, 103);
+ memset(p, 0, 6);
+ } else {
+ if (len != 13)
+ goto invalid_length;
- tch_amr_disassemble(d, tch_data, 103);
+ tch_amr_disassemble(d, tch_data, 103);
- osmo_crc8gen_set_bits(&gsm0503_amr_crc6, d, 49, p);
+ osmo_crc8gen_set_bits(&gsm0503_amr_crc6, d, 49, p);
+ }
tch_amr_merge(conv, d, p, 103, 49);
@@ -2534,12 +2630,18 @@ int gsm0503_tch_afs_encode(ubit_t *bursts, const uint8_t *tch_data, int len,
break;
case 0: /* TCH/AFS4.75 */
- if (len != 12)
- goto invalid_length;
+ if (!len) {
+ /* See comment above. */
+ memset(d, 0, 95);
+ memset(p, 0, 6);
+ } else {
+ if (len != 12)
+ goto invalid_length;
- tch_amr_disassemble(d, tch_data, 95);
+ tch_amr_disassemble(d, tch_data, 95);
- osmo_crc8gen_set_bits(&gsm0503_amr_crc6, d, 39, p);
+ osmo_crc8gen_set_bits(&gsm0503_amr_crc6, d, 39, p);
+ }
tch_amr_merge(conv, d, p, 95, 39);
@@ -2568,9 +2670,29 @@ invalid_length:
return -1;
}
-/*! Perform channel decoding of a TCH/AFS channel according TS 05.03
+/* TCH/AHS: parse codec ID (CMI or CMC/CMR) from coded in-band data (16 bit) */
+static uint8_t gsm0503_tch_ahs_decode_inband(const sbit_t *cB)
+{
+ unsigned int id = 0, best = 0;
+ unsigned int i, j, k;
+
+ for (i = 0, k = 0; i < 4; i++) {
+ /* FIXME: why not using remaining (16 - 4) soft-bits here? */
+ for (j = 0, k = 0; j < 4; j++)
+ k += abs(((int)gsm0503_ahs_ic_sbit[i][j]) - ((int)cB[j]));
+
+ if (i == 0 || k < best) {
+ best = k;
+ id = i;
+ }
+ }
+
+ return id;
+}
+
+/*! Perform channel decoding of a TCH/AHS channel according TS 05.03
* \param[out] tch_data Codec frame in RTP payload format
- * \param[in] bursts buffer containing the symbols of 8 bursts
+ * \param[in] bursts buffer containing the symbols of 6 bursts
* \param[in] odd Is this an odd (1) or even (0) frame number?
* \param[in] codec_mode_req is this CMR (1) or CMC (0)
* \param[in] codec array of active codecs (active codec set)
@@ -2591,9 +2713,9 @@ int gsm0503_tch_ahs_decode(uint8_t *tch_data, const sbit_t *bursts, int odd,
n_bits_total, NULL);
}
-/*! Perform channel decoding of a TCH/AFS channel according TS 05.03
+/*! Perform channel decoding of a TCH/AHS channel according TS 05.03
* \param[out] tch_data Codec frame in RTP payload format
- * \param[in] bursts buffer containing the symbols of 8 bursts
+ * \param[in] bursts buffer containing the symbols of 6 bursts
* \param[in] odd Is this an odd (1) or even (0) frame number?
* \param[in] codec_mode_req is this CMR (1) or CMC (0)
* \param[in] codec array of active codecs (active codec set)
@@ -2612,10 +2734,8 @@ int gsm0503_tch_ahs_decode_dtx(uint8_t *tch_data, const sbit_t *bursts, int odd,
{
sbit_t iB[912], cB[456], h;
ubit_t d[244], p[6], conv[135];
- int i, j, k, best = 0, rv, len, steal = 0, id = 0;
- ubit_t cBd[456];
+ int i, rv, len, steal = 0, id = -1;
static ubit_t sid_first_dummy[64] = { 0 };
- uint8_t dtx_prev;
/* only unmap the stealing bits */
if (!odd) {
@@ -2631,6 +2751,13 @@ int gsm0503_tch_ahs_decode_dtx(uint8_t *tch_data, const sbit_t *bursts, int odd,
/* if we found a stole FACCH, but only at correct alignment */
if (steal > 0) {
+ /* If not NULL, dtx indicates type of previously decoded TCH/AHS frame.
+ * It's normally updated by gsm0503_detect_ahs_dtx_frame2(), which is not
+ * reached in case of FACCH. Reset it here to avoid FACCH/H frames being
+ * misinterpreted as AMR's special DTX frames. */
+ if (dtx != NULL)
+ *dtx = AMR_OTHER;
+
for (i = 0; i < 6; i++) {
gsm0503_tch_burst_unmap(&iB[i * 114],
&bursts[i * 116], NULL, i >> 2);
@@ -2661,21 +2788,37 @@ int gsm0503_tch_ahs_decode_dtx(uint8_t *tch_data, const sbit_t *bursts, int odd,
/* Determine the DTX frame type (SID_UPDATE, ONSET etc...) */
if (dtx) {
- osmo_sbit2ubit(cBd, cB, 456);
- dtx_prev = *dtx;
- *dtx = gsm0503_detect_ahs_dtx_frame(n_errors, n_bits_total, cBd);
-
- if (dtx_prev == AHS_SID_UPDATE && *dtx == AMR_OTHER) {
- /* NOTE: The AHS_SID_UPDATE frame is splitted into
- * two half rate frames. If the id marker frame
- * (AHS_SID_UPDATE) is detected the following frame
- * contains the actual comfort noised data part of
- * (AHS_SID_UPDATE_CN). */
+ int n_bits_total_sid;
+ int n_errors_sid;
+
+ *dtx = gsm0503_detect_ahs_dtx_frame2(n_errors, n_bits_total, &id, cB);
+ /* TODO: detect and handle AHS_SID_UPDATE + AHS_SID_UPDATE_INH */
+
+ switch (*dtx) {
+ case AHS_SID_UPDATE: /* TODO: parse CMI _and_ CMC/CMR (16 + 16 bit) */
+ /* cB[] contains 16 bits of coded in-band data and 212 bits containing
+ * the identification marker. We need to unmap/deinterleave 114 odd
+ * bits from the last two blocks, 114 even bits from the first two
+ * blocks and combine them together. */
+ gsm0503_tch_burst_unmap(&iB[0 * 114], &bursts[2 * 116], NULL, 0);
+ gsm0503_tch_burst_unmap(&iB[1 * 114], &bursts[3 * 116], NULL, 0);
+ gsm0503_tch_burst_unmap(&iB[2 * 114], &bursts[0 * 116], NULL, 1);
+ gsm0503_tch_burst_unmap(&iB[3 * 114], &bursts[1 * 116], NULL, 1);
+ gsm0503_tch_hr_deinterleave(cB, iB);
+
+ /* cB[] is expected to contain 16 bits of coded in-band data and
+ * 212 bits containing the coded data (53 bits coded at 1/4 rate). */
*dtx = AHS_SID_UPDATE_CN;
osmo_conv_decode_ber(&gsm0503_tch_axs_sid_update,
- cB + 16, conv, n_errors,
- n_bits_total);
+ cB + 16, conv, &n_errors_sid,
+ &n_bits_total_sid);
+ /* gsm0503_detect_ahs_dtx_frame2() calculates BER for the marker,
+ * osmo_conv_decode_ber() calculates BER for the coded data. */
+ if (n_errors != NULL)
+ *n_errors += n_errors_sid;
+ if (n_bits_total != NULL)
+ *n_bits_total += n_bits_total_sid;
rv = osmo_crc16gen_check_bits(&gsm0503_amr_crc14, conv,
35, conv + 35);
if (rv != 0) {
@@ -2685,38 +2828,30 @@ int gsm0503_tch_ahs_decode_dtx(uint8_t *tch_data, const sbit_t *bursts, int odd,
tch_amr_sid_update_append(conv, 1,
(codec_mode_req) ? codec[*ft]
- : codec[id]);
+ : codec[id > 0 ? id : 0]);
tch_amr_reassemble(tch_data, conv, 39);
len = 5;
goto out;
- } else if (*dtx == AHS_SID_FIRST_P2) {
+ case AHS_SID_FIRST_P2:
tch_amr_sid_update_append(sid_first_dummy, 0,
(codec_mode_req) ? codec[*ft]
- : codec[id]);
+ : codec[id > 0 ? id : 0]);
tch_amr_reassemble(tch_data, sid_first_dummy, 39);
len = 5;
goto out;
- } else if (*dtx == AHS_SID_UPDATE || *dtx == AHS_ONSET
- || *dtx == AHS_SID_FIRST_INH
- || *dtx == AHS_SID_UPDATE_INH
- || *dtx == AHS_SID_FIRST_P1) {
+ case AHS_ONSET:
+ case AHS_SID_FIRST_INH: /* TODO: parse CMI or CMC/CMR (16 bit) */
+ case AHS_SID_UPDATE_INH: /* TODO: parse CMI or CMC/CMR (16 bit) */
+ case AHS_SID_FIRST_P1: /* TODO: parse CMI or CMC/CMR (16 bit) */
len = 0;
goto out;
+ default:
+ break;
}
}
- for (i = 0; i < 4; i++) {
- for (j = 0, k = 0; j < 4; j++)
- k += abs(((int)gsm0503_ahs_ic_sbit[i][j]) - ((int)cB[j]));
-
- if (i == 0 || k < best) {
- best = k;
- id = i;
- }
- }
-
- /* Check if indicated codec fits into range of codecs */
- if (id >= codecs) {
+ /* Parse codec ID (CMI or CMC/CMR) and check if it fits into range of codecs */
+ if ((id = gsm0503_tch_ahs_decode_inband(&cB[0])) >= codecs) {
/* Codec mode out of range, return id */
return id;
}
@@ -2851,10 +2986,12 @@ int gsm0503_tch_ahs_decode_dtx(uint8_t *tch_data, const sbit_t *bursts, int odd,
out:
/* Change codec request / indication, if frame is valid */
- if (codec_mode_req)
- *cmr = id;
- else
- *ft = id;
+ if (id != -1) {
+ if (codec_mode_req)
+ *cmr = id;
+ else
+ *ft = id;
+ }
return len;
}
@@ -2862,7 +2999,7 @@ out:
/*! Perform channel encoding on a TCH/AHS channel according to TS 05.03
* \param[out] bursts caller-allocated output buffer for bursts bits
* \param[in] tch_data Codec input data in RTP payload format
- * \param[in] len Length of \a tch_data in bytes
+ * \param[in] len Length of \a tch_data in bytes or 0 to generate a bad frame
* \param[in] codec_mode_req Use CMR (1) or FT (0)
* \param[in] codec Array of codecs (active codec set)
* \param[in] codecs Number of entries in \a codec
@@ -2870,7 +3007,7 @@ out:
* \param[in] cmr Codec Mode Request (used in codec_mode_req = 1 only)
* \returns 0 in case of success; negative on error */
int gsm0503_tch_ahs_encode(ubit_t *bursts, const uint8_t *tch_data, int len,
- int codec_mode_req, uint8_t *codec, int codecs, uint8_t ft,
+ int codec_mode_req, const uint8_t *codec, int codecs, uint8_t ft,
uint8_t cmr)
{
ubit_t iB[912], cB[456], h;
@@ -2897,28 +3034,27 @@ int gsm0503_tch_ahs_encode(ubit_t *bursts, const uint8_t *tch_data, int len,
h = 0;
- if (codec_mode_req) {
- if (cmr >= codecs) {
- /* FIXME: CMR ID %d not in codec list */
- return -1;
- }
- id = cmr;
- } else {
- if (ft >= codecs) {
- /* FIXME: FT ID %d not in codec list */
- return -1;
- }
- id = ft;
- }
+ id = codec_mode_req ? cmr : ft;
+ if (OSMO_UNLIKELY(id >= ARRAY_SIZE(gsm0503_ahs_ic_ubit)))
+ return -1;
+ if (OSMO_UNLIKELY(ft >= codecs))
+ return -1;
switch (codec[ft]) {
case 5: /* TCH/AHS7.95 */
- if (len != 20)
- goto invalid_length;
-
- tch_amr_disassemble(d, tch_data, 159);
-
- osmo_crc8gen_set_bits(&gsm0503_amr_crc6, d, 67, p);
+ if (!len) {
+ /* No data, induce BFI in the receiver by inverted CRC bits.
+ * The data bit are all 0, so the correct parity bits would be 111111. */
+ memset(d, 0, 159);
+ memset(p, 0, 6);
+ } else {
+ if (len != 20)
+ goto invalid_length;
+
+ tch_amr_disassemble(d, tch_data, 159);
+
+ osmo_crc8gen_set_bits(&gsm0503_amr_crc6, d, 67, p);
+ }
tch_amr_merge(conv, d, p, 123, 67);
@@ -2928,12 +3064,18 @@ int gsm0503_tch_ahs_encode(ubit_t *bursts, const uint8_t *tch_data, int len,
break;
case 4: /* TCH/AHS7.4 */
- if (len != 19)
- goto invalid_length;
+ if (!len) {
+ /* See comment above. */
+ memset(d, 0, 148);
+ memset(p, 0, 6);
+ } else {
+ if (len != 19)
+ goto invalid_length;
- tch_amr_disassemble(d, tch_data, 148);
+ tch_amr_disassemble(d, tch_data, 148);
- osmo_crc8gen_set_bits(&gsm0503_amr_crc6, d, 61, p);
+ osmo_crc8gen_set_bits(&gsm0503_amr_crc6, d, 61, p);
+ }
tch_amr_merge(conv, d, p, 120, 61);
@@ -2943,12 +3085,18 @@ int gsm0503_tch_ahs_encode(ubit_t *bursts, const uint8_t *tch_data, int len,
break;
case 3: /* TCH/AHS6.7 */
- if (len != 17)
- goto invalid_length;
+ if (!len) {
+ /* See comment above. */
+ memset(d, 0, 134);
+ memset(p, 0, 6);
+ } else {
+ if (len != 17)
+ goto invalid_length;
- tch_amr_disassemble(d, tch_data, 134);
+ tch_amr_disassemble(d, tch_data, 134);
- osmo_crc8gen_set_bits(&gsm0503_amr_crc6, d, 55, p);
+ osmo_crc8gen_set_bits(&gsm0503_amr_crc6, d, 55, p);
+ }
tch_amr_merge(conv, d, p, 110, 55);
@@ -2958,12 +3106,18 @@ int gsm0503_tch_ahs_encode(ubit_t *bursts, const uint8_t *tch_data, int len,
break;
case 2: /* TCH/AHS5.9 */
- if (len != 15)
- goto invalid_length;
+ if (!len) {
+ /* See comment above. */
+ memset(d, 0, 118);
+ memset(p, 0, 6);
+ } else {
+ if (len != 15)
+ goto invalid_length;
- tch_amr_disassemble(d, tch_data, 118);
+ tch_amr_disassemble(d, tch_data, 118);
- osmo_crc8gen_set_bits(&gsm0503_amr_crc6, d, 55, p);
+ osmo_crc8gen_set_bits(&gsm0503_amr_crc6, d, 55, p);
+ }
tch_amr_merge(conv, d, p, 102, 55);
@@ -2973,12 +3127,18 @@ int gsm0503_tch_ahs_encode(ubit_t *bursts, const uint8_t *tch_data, int len,
break;
case 1: /* TCH/AHS5.15 */
- if (len != 13)
- goto invalid_length;
+ if (!len) {
+ /* See comment above. */
+ memset(d, 0, 103);
+ memset(p, 0, 6);
+ } else {
+ if (len != 13)
+ goto invalid_length;
- tch_amr_disassemble(d, tch_data, 103);
+ tch_amr_disassemble(d, tch_data, 103);
- osmo_crc8gen_set_bits(&gsm0503_amr_crc6, d, 49, p);
+ osmo_crc8gen_set_bits(&gsm0503_amr_crc6, d, 49, p);
+ }
tch_amr_merge(conv, d, p, 91, 49);
@@ -2988,12 +3148,18 @@ int gsm0503_tch_ahs_encode(ubit_t *bursts, const uint8_t *tch_data, int len,
break;
case 0: /* TCH/AHS4.75 */
- if (len != 12)
- goto invalid_length;
+ if (!len) {
+ /* See comment above. */
+ memset(d, 0, 95);
+ memset(p, 0, 6);
+ } else {
+ if (len != 12)
+ goto invalid_length;
- tch_amr_disassemble(d, tch_data, 95);
+ tch_amr_disassemble(d, tch_data, 95);
- osmo_crc8gen_set_bits(&gsm0503_amr_crc6, d, 39, p);
+ osmo_crc8gen_set_bits(&gsm0503_amr_crc6, d, 39, p);
+ }
tch_amr_merge(conv, d, p, 83, 39);
@@ -3007,7 +3173,7 @@ int gsm0503_tch_ahs_encode(ubit_t *bursts, const uint8_t *tch_data, int len,
return -1;
}
- memcpy(cB, gsm0503_afs_ic_ubit[id], 4);
+ memcpy(cB, gsm0503_ahs_ic_ubit[id], 4);
gsm0503_tch_hr_interleave(cB, iB);
@@ -3212,4 +3378,491 @@ int gsm0503_sch_encode(ubit_t *burst, const uint8_t *sb_info)
return 0;
}
+/*
+ * GSM CSD transcoding
+ */
+
+static inline void _tch_csd_burst_map(ubit_t *burst, const ubit_t *iB)
+{
+ unsigned int i;
+
+ /* hu(B): copy *even* numbered bits if not stolen by FACCH */
+ if (burst[58] == 0) {
+ for (i = 0; i < 57; i += 2)
+ burst[i] |= iB[i];
+ for (i = 58; i < 114; i += 2)
+ burst[i + 2] |= iB[i];
+ }
+
+ /* hl(B): copy *odd* numbered bits if not stolen by FACCH */
+ if (burst[57] == 0) {
+ for (i = 1; i < 57; i += 2)
+ burst[i] |= iB[i];
+ for (i = 57; i < 114; i += 2)
+ burst[i + 2] |= iB[i];
+ }
+}
+
+/*! Perform channel encoding of a TCH/F9.6 channel as per section 3.3.
+ * \param[out] bursts Caller-allocated buffer for symbols of 22 bursts,
+ * 22 * 2 * 58 == 2552 bits total.
+ * \param[in] data Data to be encoded (240 unpacked bits).
+ * \returns 0 in case of success; negative on error. */
+int gsm0503_tch_fr96_encode(ubit_t *bursts, const ubit_t *data)
+{
+ ubit_t iB[22 * 114], cB[4 * 114];
+ ubit_t conv[4 * 60 + 4];
+
+ /* 3.3.2 Block code: b1(60) + b2(60) + b3(60) + b4(60) + pad(4) */
+ memcpy(&conv[0], &data[0], 4 * 60);
+ /* pad(4) is set to 0 by osmo_conv_encode() below */
+
+ /* 3.3.3 Convolutional encoder */
+ osmo_conv_encode(&gsm0503_tch_f96, &conv[0], &cB[0]);
+
+ /* 3.3.4 Interleaving */
+ memset(&iB[0], 0, sizeof(iB));
+ gsm0503_tch_f96_interleave(&cB[0], &iB[0]);
+
+ /* 3.3.5 Mapping on a burst: as specified for TCH/FS in subclause 3.1.4 */
+ for (unsigned int i = 0; i < 22; i++)
+ _tch_csd_burst_map(&bursts[i * 116], &iB[i * 114]);
+
+ return 0;
+}
+
+/*! Perform channel decoding of a TCH/F9.6 channel as per section 3.3.
+ * \param[out] data Caller-allocated buffer for decoded data (240 unpacked bits).
+ * \param[in] bursts Buffer containing the symbols of 22 bursts,
+ * 22 * 2 * 58 == 2552 bits total.
+ * \param[out] n_errors Number of detected bit errors.
+ * \param[out] n_bits_total Total number of bits.
+ * \returns Number of unpacked bits used in the output buffer; negative on error. */
+int gsm0503_tch_fr96_decode(ubit_t *data, const sbit_t *bursts,
+ int *n_errors, int *n_bits_total)
+{
+ sbit_t iB[22 * 114], cB[4 * 114];
+ ubit_t conv[4 * 60 + 4];
+
+ /* 3.3.5 Mapping on a burst: as specified for TCH/FS in subclause 3.1.4 */
+ for (unsigned int i = 0; i < 22; i++) {
+ memcpy(&iB[i * 114], &bursts[i * 116], 57);
+ memcpy(&iB[i * 114 + 57], &bursts[i * 116 + 59], 57);
+ }
+
+ /* 3.3.4 Interleaving */
+ gsm0503_tch_f96_deinterleave(&cB[0], &iB[0]);
+
+ /* 3.3.3 Convolutional encoder */
+ osmo_conv_decode_ber(&gsm0503_tch_f96, &cB[0], &conv[0], n_errors, n_bits_total);
+
+ /* 3.3.2 Block code: b1(60) + b2(60) + b3(60) + b4(60) + pad(4) */
+ memcpy(&data[0], &conv[0], 4 * 60);
+
+ return 4 * 60;
+}
+
+/*! Perform channel encoding of a TCH/F4.8 channel as per section 3.4.
+ * \param[out] bursts Caller-allocated buffer for symbols of 22 bursts,
+ * 22 * 2 * 58 == 2552 bits total.
+ * \param[in] data Data to be encoded (120 unpacked bits).
+ * \returns 0 in case of success; negative on error */
+int gsm0503_tch_fr48_encode(ubit_t *bursts, const ubit_t *data)
+{
+ ubit_t iB[22 * 114], cB[4 * 114];
+ ubit_t conv[2 * 60 + 32];
+
+ /* 3.4.2 Block code:
+ *
+ * Sixteen bits equal to 0 are added to the 60 information bits, the result
+ * being a block of 76 bits, {u(0),u(1),...,u(75)}, with:
+ *
+ * u(19k+p) = d(15k+p) for k = 0,1,2,3 and p = 0,1,...,14;
+ * u(19k+p) = 0 for k = 0,1,2,3 and p = 15,16,17,18.
+ *
+ * Two such blocks forming a block of 152 bits: u1 + u2. */
+ for (unsigned int k = 0; k < 2 * 4; k++) {
+ memcpy(&conv[19 * k], &data[15 * k], 15);
+ memset(&conv[19 * k + 15], 0, 4);
+ }
+
+ /* 3.4.3 Convolutional encoder */
+ osmo_conv_encode(&gsm0503_tch_f48, &conv[0], &cB[0]);
+
+ /* 3.4.4 Interleaving: as specified for the TCH/F9.6 in subclause 3.3.4 */
+ memset(&iB[0], 0, sizeof(iB));
+ gsm0503_tch_f96_interleave(&cB[0], &iB[0]);
+
+ /* 3.4.5 Mapping on a burst: as specified for TCH/FS in subclause 3.1.4 */
+ for (unsigned int i = 0; i < 22; i++)
+ _tch_csd_burst_map(&bursts[i * 116], &iB[i * 114]);
+
+ return 0;
+}
+
+/*! Perform channel decoding of a TCH/F4.8 channel as per section 3.4.
+ * \param[out] data Caller-allocated buffer for decoded data (120 unpacked bits).
+ * \param[in] bursts Buffer containing the symbols of 22 bursts,
+ * 22 * 2 * 58 == 2552 bits total.
+ * \param[out] n_errors Number of detected bit errors.
+ * \param[out] n_bits_total Total number of bits.
+ * \returns Number of unpacked bits used in the output buffer; negative on error. */
+int gsm0503_tch_fr48_decode(ubit_t *data, const sbit_t *bursts,
+ int *n_errors, int *n_bits_total)
+{
+ sbit_t iB[22 * 114], cB[4 * 114];
+ ubit_t conv[2 * 60 + 32];
+
+ /* 3.4.5 Mapping on a burst: as specified for TCH/FS in subclause 3.1.4 */
+ for (unsigned int i = 0; i < 22; i++) {
+ memcpy(&iB[i * 114], &bursts[i * 116], 57);
+ memcpy(&iB[i * 114 + 57], &bursts[i * 116 + 59], 57);
+ }
+
+ /* 3.4.4 Interleaving: as specified for the TCH/F9.6 in subclause 3.3.4 */
+ gsm0503_tch_f96_deinterleave(&cB[0], &iB[0]);
+
+ /* 3.4.3 Convolutional encoder */
+ osmo_conv_decode_ber(&gsm0503_tch_f48, &cB[0], &conv[0], n_errors, n_bits_total);
+
+ /* 3.4.2 Block code:
+ *
+ * Sixteen bits equal to 0 are added to the 60 information bits, the result
+ * being a block of 76 bits, {u(0),u(1),...,u(75)}, with:
+ *
+ * u(19k+p) = d(15k+p) for k = 0,1,2,3 and p = 0,1,...,14;
+ * u(19k+p) = 0 for k = 0,1,2,3 and p = 15,16,17,18.
+ *
+ * Two such blocks forming a block of 152 bits: u1 + u2. */
+ for (unsigned int k = 0; k < 2 * 4; k++)
+ memcpy(&data[15 * k], &conv[19 * k], 15);
+
+ return 2 * 60;
+}
+
+/*! Perform channel encoding of a TCH/H4.8 channel as per section 3.5.
+ * The algorithm is identical to TCH/F9.6, so it's just a wrapper.
+ * \param[out] bursts Caller-allocated buffer for symbols of 22 bursts,
+ * 22 * 2 * 58 == 2552 bits total.
+ * \param[in] data Data to be encoded (240 unpacked bits).
+ * \returns 0 in case of success; negative on error */
+int gsm0503_tch_hr48_encode(ubit_t *bursts, const ubit_t *data)
+{
+ return gsm0503_tch_fr96_encode(bursts, data);
+}
+
+/*! Perform channel decoding of a TCH/H4.8 channel as per section 3.5.
+ * The algorithm is identical to TCH/F9.6, so it's just a wrapper.
+ * \param[out] data Caller-allocated buffer for decoded data (240 unpacked bits).
+ * \param[in] bursts Buffer containing the symbols of 22 bursts,
+ * 22 * 2 * 58 == 2552 bits total.
+ * \param[out] n_errors Number of detected bit errors.
+ * \param[out] n_bits_total Total number of bits.
+ * \returns Number of unpacked bits used in the output buffer; negative on error. */
+int gsm0503_tch_hr48_decode(ubit_t *data, const sbit_t *bursts,
+ int *n_errors, int *n_bits_total)
+{
+ return gsm0503_tch_fr96_decode(data, bursts, n_errors, n_bits_total);
+}
+
+/*! Perform channel encoding of a TCH/F2.4 channel as per section 3.6.
+ * \param[out] bursts Caller-allocated buffer for symbols of 8 bursts,
+ * 8 * 2 * 58 == 928 bits total.
+ * \param[in] data Data to be encoded (72 unpacked bits).
+ * \returns 0 in case of success; negative on error */
+int gsm0503_tch_fr24_encode(ubit_t *bursts, const ubit_t *data)
+{
+ ubit_t iB[8 * 114], cB[4 * 114];
+ const ubit_t h = 0;
+
+ /* 3.6.{1-3} Block code and Convolutional encoder */
+ osmo_conv_encode(&gsm0503_tch_f24, &data[0], &cB[0]);
+
+ /* 3.6.4 Interleaving: as specified for the TCH/FS in subclause 3.1.3 */
+ gsm0503_tch_fr_interleave(&cB[0], &iB[0]);
+
+ /* 3.6.5 Mapping on a burst: as specified for TCH/FS in subclause 3.1.4 */
+ for (unsigned int i = 0; i < 8; i++)
+ gsm0503_tch_burst_map(&iB[i * 114], &bursts[i * 116], &h, i >> 2);
+
+ return 0;
+}
+
+/*! Perform channel decoding of a TCH/F2.4 channel as per section 3.6.
+ * \param[out] data Caller-allocated buffer for decoded data (72 unpacked bits).
+ * \param[in] bursts Buffer containing the symbols of 8 bursts,
+ * 8 * 2 * 58 == 928 bits total.
+ * \param[out] n_errors Number of detected bit errors.
+ * \param[out] n_bits_total Total number of bits.
+ * \returns Number of unpacked bits used in the output buffer; negative on error. */
+int gsm0503_tch_fr24_decode(ubit_t *data, const sbit_t *bursts,
+ int *n_errors, int *n_bits_total)
+{
+ sbit_t iB[8 * 114], cB[4 * 114];
+
+ /* 3.6.5 Mapping on a burst: as specified for TCH/FS in subclause 3.1.4 */
+ for (unsigned int i = 0; i < 8; i++)
+ gsm0503_tch_burst_unmap(&iB[i * 114], &bursts[i * 116], NULL, i >> 2);
+
+ /* 3.6.4 Interleaving: as specified for the TCH/FS in subclause 3.1.3 */
+ gsm0503_tch_fr_deinterleave(&cB[0], &iB[0]);
+
+ /* 3.6.{1-3} Block code and Convolutional encoder */
+ osmo_conv_decode_ber(&gsm0503_tch_f24, &cB[0], &data[0], n_errors, n_bits_total);
+
+ return 72;
+}
+
+/*! Perform channel encoding of a TCH/H2.4 channel as per section 3.7.
+ * \param[out] bursts Caller-allocated buffer for symbols of 22 bursts,
+ * 22 * 2 * 58 == 2552 bits total.
+ * \param[in] data Data to be encoded (144 unpacked bits).
+ * \returns 0 in case of success; negative on error */
+int gsm0503_tch_hr24_encode(ubit_t *bursts, const ubit_t *data)
+{
+ ubit_t iB[22 * 114], cB[4 * 114];
+
+ /* 3.7.{1-3} Block code and Convolutional encoder */
+ osmo_conv_encode(&gsm0503_tch_h24, &data[ 0], &cB[ 0]);
+ osmo_conv_encode(&gsm0503_tch_h24, &data[72], &cB[228]);
+
+ /* 3.7.4 Interleaving: as specified for the TCH/F9.6 in subclause 3.3.4 */
+ memset(&iB[0], 0, sizeof(iB));
+ gsm0503_tch_f96_interleave(&cB[0], &iB[0]);
+
+ /* 3.7.5 Mapping on a burst: as specified for TCH/FS in subclause 3.1.4 */
+ for (unsigned int i = 0; i < 22; i++)
+ _tch_csd_burst_map(&bursts[i * 116], &iB[i * 114]);
+
+ return 0;
+}
+
+/*! Perform channel decoding of a TCH/H2.4 channel as per section 3.7.
+ * \param[out] data Caller-allocated buffer for decoded data (144 unpacked bits).
+ * \param[in] bursts Buffer containing the symbols of 22 bursts,
+ * 22 * 2 * 58 == 2552 bits total.
+ * \param[out] n_errors Number of detected bit errors.
+ * \param[out] n_bits_total Total number of bits.
+ * \returns Number of unpacked bits used in the output buffer; negative on error. */
+int gsm0503_tch_hr24_decode(ubit_t *data, const sbit_t *bursts,
+ int *n_errors, int *n_bits_total)
+{
+ int n_errors_l[2], n_bits_total_l[2];
+ sbit_t iB[22 * 114], cB[4 * 114];
+
+ /* 3.7.5 Mapping on a burst: as specified for TCH/FS in subclause 3.1.4 */
+ for (unsigned int i = 0; i < 22; i++) {
+ memcpy(&iB[i * 114], &bursts[i * 116], 57);
+ memcpy(&iB[i * 114 + 57], &bursts[i * 116 + 59], 57);
+ }
+
+ /* 3.7.4 Interleaving: as specified for the TCH/F9.6 in subclause 3.3.4 */
+ gsm0503_tch_f96_deinterleave(&cB[0], &iB[0]);
+
+ /* 3.7.{1-3} Block code and Convolutional encoder */
+ osmo_conv_decode_ber(&gsm0503_tch_h24, &cB[ 0], &data[ 0], &n_errors_l[0], &n_bits_total_l[0]);
+ osmo_conv_decode_ber(&gsm0503_tch_h24, &cB[228], &data[72], &n_errors_l[1], &n_bits_total_l[1]);
+
+ if (n_errors)
+ *n_errors = n_errors_l[0] + n_errors_l[1];
+
+ if (n_bits_total)
+ *n_bits_total = n_bits_total_l[0] + n_bits_total_l[1];
+
+ return 2 * 72;
+}
+
+/*! Perform channel encoding of a TCH/F14.4 channel as per section 3.8.
+ * \param[out] bursts Caller-allocated buffer for symbols of 22 bursts,
+ * 22 * 2 * 58 == 2552 bits total.
+ * \param[in] data Data to be encoded (290 unpacked bits).
+ * \returns 0 in case of success; negative on error */
+int gsm0503_tch_fr144_encode(ubit_t *bursts, const ubit_t *data)
+{
+ ubit_t iB[22 * 114], cB[4 * 114];
+ ubit_t conv[290 + 4];
+
+ /* 3.8.2 Block code: b(290) + pad(4) */
+ memcpy(&conv[0], &data[0], 290);
+ /* pad(4) is set to 0 by osmo_conv_encode() below */
+
+ /* 3.8.3 Convolutional encoder */
+ osmo_conv_encode(&gsm0503_tch_f144, &conv[0], &cB[0]);
+
+ /* 3.8.4 Interleaving */
+ memset(&iB[0], 0, sizeof(iB));
+ gsm0503_tch_f96_interleave(&cB[0], &iB[0]);
+
+ /* 3.8.5 Mapping on a burst: as specified for TCH/FS in subclause 3.1.4 */
+ for (unsigned int i = 0; i < 22; i++)
+ _tch_csd_burst_map(&bursts[i * 116], &iB[i * 114]);
+
+ return 0;
+}
+
+/*! Perform channel decoding of a TCH/14.4 channel as per section 3.8.
+ * \param[out] data Caller-allocated buffer for decoded data (290 unpacked bits).
+ * \param[in] bursts Buffer containing the symbols of 22 bursts,
+ * 22 * 2 * 58 == 2552 bits total.
+ * \param[out] n_errors Number of detected bit errors.
+ * \param[out] n_bits_total Total number of bits.
+ * \returns Number of unpacked bits used in the output buffer; negative on error. */
+int gsm0503_tch_fr144_decode(ubit_t *data, const sbit_t *bursts,
+ int *n_errors, int *n_bits_total)
+{
+ sbit_t iB[22 * 114], cB[4 * 114];
+ ubit_t conv[294];
+
+ /* 3.8.5 Mapping on a burst: as specified for TCH/FS in subclause 3.1.4 */
+ for (unsigned int i = 0; i < 22; i++) {
+ memcpy(&iB[i * 114], &bursts[i * 116], 57);
+ memcpy(&iB[i * 114 + 57], &bursts[i * 116 + 59], 57);
+ }
+
+ /* 3.8.4 Interleaving: as specified for the TCH/F9.6 in subclause 3.3.4 */
+ gsm0503_tch_f96_deinterleave(&cB[0], &iB[0]);
+
+ /* 3.8.3 Convolutional encoder */
+ osmo_conv_decode_ber(&gsm0503_tch_f144, &cB[0], &conv[0], n_errors, n_bits_total);
+
+ /* 3.8.2 Block code: b(290) + pad(4) */
+ memcpy(&data[0], &conv[0], 290);
+
+ return 290;
+}
+
+/*
+ * FACCH/[FH] transcoding
+ */
+
+/*! Perform channel encoding of a FACCH/F data as per section 4.2.
+ * \param[out] bursts Caller-allocated buffer for symbols of 8 bursts,
+ * 8 * 2 * 58 == 928 bits total.
+ * \param[in] data FACCH MAC block to be encoded (GSM_MACBLOCK_LEN).
+ * \returns 0 in case of success; negative on error */
+int gsm0503_tch_fr_facch_encode(ubit_t *bursts, const uint8_t *data)
+{
+ ubit_t iB[8 * 114], cB[4 * 114];
+ const ubit_t h = 1;
+
+ /* 4.2.1-3 as specified for the SACCH in 4.1.1-3 */
+ _xcch_encode_cB(&cB[0], &data[0]);
+
+ /* 4.2.4 Interleaving: as specified for the TCH/FS in subclause 3.1.3 */
+ gsm0503_tch_fr_interleave(&cB[0], &iB[0]);
+
+ /* 4.2.5 Mapping on a Burst:
+ * - hu(B)=1 the even numbered bits in the first 4 bursts and
+ * - hl(B)=1 the odd numbered bits of the last 4 bursts are stolen. */
+ for (unsigned int i = 0; i < 8; i++)
+ gsm0503_tch_burst_map(&iB[i * 114], &bursts[i * 116], &h, i >> 2);
+
+ return 0;
+}
+
+/*! Perform channel decoding of a FACCH/F data as per section 4.2.
+ * \param[out] data Caller-allocated buffer for decoded FACCH (GSM_MACBLOCK_LEN).
+ * \param[in] bursts Buffer containing the symbols of 8 bursts,
+ * 8 * 2 * 58 == 928 bits total.
+ * \param[out] n_errors Number of detected bit errors.
+ * \param[out] n_bits_total Total number of bits.
+ * \returns Number of bytes used in the output buffer; negative on error. */
+int gsm0503_tch_fr_facch_decode(uint8_t *data, const sbit_t *bursts,
+ int *n_errors, int *n_bits_total)
+{
+ sbit_t iB[8 * 114], cB[4 * 114];
+ int steal = 0;
+
+ /* FACCH decision: sum of 4 first hu(B) and 4 last hl(B) soft-bits */
+ for (unsigned int i = 0; i < 4; i++)
+ steal -= bursts[i * 116 + 58]; /* hu(B) */
+ for (unsigned int i = 4; i < 8; i++)
+ steal -= bursts[i * 116 + 57]; /* hl(B) */
+ if (steal <= 0)
+ return -1;
+
+ /* 4.2.5 Mapping on a Burst:
+ * - hu(B)=1 the even numbered bits in the first 4 bursts and
+ * - hl(B)=1 the odd numbered bits of the last 4 bursts are stolen. */
+ for (unsigned int i = 0; i < 8; i++)
+ gsm0503_tch_burst_unmap(&iB[i * 114], &bursts[i * 116], NULL, i >> 2);
+
+ /* 4.2.4 Interleaving: as specified for the TCH/FS in subclause 3.1.3 */
+ gsm0503_tch_fr_deinterleave(&cB[0], &iB[0]);
+
+ /* 4.2.1-3 as specified for the SACCH in 4.1.1-3 */
+ if (_xcch_decode_cB(&data[0], &cB[0], n_errors, n_bits_total) != 0)
+ return -1;
+
+ return GSM_MACBLOCK_LEN;
+}
+
+/*! Perform channel encoding of a FACCH/H data as per section 4.3.
+ * \param[out] bursts Caller-allocated buffer for symbols of 6 bursts,
+ * 6 * 2 * 58 == 696 bits total.
+ * \param[in] data FACCH MAC block to be encoded (GSM_MACBLOCK_LEN).
+ * \returns 0 in case of success; negative on error */
+int gsm0503_tch_hr_facch_encode(ubit_t *bursts, const uint8_t *data)
+{
+ ubit_t iB[8 * 114], cB[4 * 114];
+ const ubit_t h = 1;
+
+ /* 4.3.1-3 as specified for the SACCH in 4.1.1-3 */
+ _xcch_encode_cB(&cB[0], &data[0]);
+
+ /* 4.3.4 Interleaving */
+ gsm0503_tch_fr_interleave(&cB[0], &iB[0]);
+
+ /* 4.3.5 Mapping on a Burst:
+ * - hu(B)=1 the even numbered bits of the first 2 bursts,
+ * - hu(B)=1 & hl(B)=1 all bits of the middle 2 bursts and
+ * - hl(B)=1 the odd numbered bits of the last 2 bursts are stolen. */
+ for (unsigned int i = 0; i < 6; i++)
+ gsm0503_tch_burst_map(&iB[i * 114], &bursts[i * 116], &h, i >> 2);
+ for (unsigned int i = 2; i < 4; i++)
+ gsm0503_tch_burst_map(&iB[i * 114 + 456], &bursts[i * 116], &h, 1);
+
+ return 0;
+}
+
+/*! Perform channel decoding of a FACCH/H data as per section 4.3.
+ * \param[out] data Caller-allocated buffer for decoded FACCH (GSM_MACBLOCK_LEN).
+ * \param[in] bursts Buffer containing the symbols of 6 bursts,
+ * 6 * 2 * 58 == 696 bits total.
+ * \param[out] n_errors Number of detected bit errors.
+ * \param[out] n_bits_total Total number of bits.
+ * \returns Number of bytes used in the output buffer; negative on error. */
+int gsm0503_tch_hr_facch_decode(uint8_t *data, const sbit_t *bursts,
+ int *n_errors, int *n_bits_total)
+{
+ sbit_t iB[8 * 114], cB[4 * 114];
+ int steal = 0;
+
+ /* FACCH decision: sum of 4 first hu(B) and 4 last hl(B) soft-bits */
+ for (unsigned int i = 0; i < 4; i++)
+ steal -= bursts[i * 116 + 58]; /* hu(B) */
+ for (unsigned int i = 2; i < 6; i++)
+ steal -= bursts[i * 116 + 57]; /* hl(B) */
+ if (steal <= 0)
+ return -1;
+
+ /* 4.3.5 Mapping on a Burst:
+ * - hu(B)=1 the even numbered bits of the first 2 bursts,
+ * - hu(B)=1 & hl(B)=1 all bits of the middle 2 bursts and
+ * - hl(B)=1 the odd numbered bits of the last 2 bursts are stolen. */
+ for (unsigned int i = 0; i < 6; i++)
+ gsm0503_tch_burst_unmap(&iB[i * 114], &bursts[i * 116], NULL, i >> 2);
+ for (unsigned int i = 2; i < 4; i++)
+ gsm0503_tch_burst_unmap(&iB[i * 114 + 456], &bursts[i * 116], NULL, 1);
+
+ /* 4.3.4 Interleaving */
+ gsm0503_tch_fr_deinterleave(&cB[0], &iB[0]);
+
+ /* 4.3.1-3 as specified for the SACCH in 4.1.1-3 */
+ if (_xcch_decode_cB(&data[0], &cB[0], n_errors, n_bits_total) != 0)
+ return -1;
+
+ return GSM_MACBLOCK_LEN;
+}
+
/*! @} */
diff --git a/src/coding/gsm0503_interleaving.c b/src/coding/gsm0503_interleaving.c
index d5008d07..570d65aa 100644
--- a/src/coding/gsm0503_interleaving.c
+++ b/src/coding/gsm0503_interleaving.c
@@ -16,10 +16,6 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <stdint.h>
@@ -682,4 +678,56 @@ void gsm0503_tch_hr_interleave(const ubit_t *cB, ubit_t *iB)
}
}
+/* 3GPP TS 45.003 Section 3.3.4
+ * The coded bits are reordered and interleaved according to the following rule:
+ * i(B,j) = c(n,k) for k = 0,1,...,455
+ * n = 0,1,...,N,N + 1,...
+ * B = B0 +4n + (k mod 19) + (k div 114)
+ * j = (k mod 19) + 19 (k mod 6)
+ *
+ * The result of the interleaving is a distribution of the reordered 114
+ * bit of a given data block, n = N, over 19 blocks, 6 bits equally
+ * distributed in each block, in a diagonal way over consecutive blocks.
+ *
+ * Or in other words the interleaving is a distribution of the encoded,
+ * reordered 456 bits from four given input data blocks, which taken
+ * together give n = N, over 22 bursts, 6 bits equally distributed in
+ * the first and 22 nd bursts, 12 bits distributed in the second and 21
+ * st bursts, 18 bits distributed in the third and 20 th bursts and 24
+ * bits distributed in the other 16 bursts.
+ *
+ * The block of coded data is interleaved "diagonal", where a new block
+ * of coded data starts with every fourth burst and is distributed over
+ * 22 bursts.
+ *
+ * Also used for TCH/F4.8, TCH/H4.8, and TCH/H2.4 and TCH/F14.4 */
+void gsm0503_tch_f96_interleave(const ubit_t *cB, ubit_t *iB)
+{
+ int j, k, B;
+
+ for (k = 0; k < 456; k++) {
+ /* upper bound for B: 4*n + 18 + 4 = 4*n + 22 */
+ B = /* B0 + 4n + */ (k % 19) + (k / 114);
+ /* upper bound for j: 18 + 19*5 = 113 */
+ j = (k % 19) + 19 * (k % 6);
+ /* upper iB index: 4*n+23*114-1 */
+ iB[B * 114 + j] = cB[k];
+ }
+}
+
+void gsm0503_tch_f96_deinterleave(sbit_t *cB, const sbit_t *iB)
+{
+ int j, k, B;
+
+ for (k = 0; k < 456; k++) {
+ /* upper bound for B: 4*n + 18 + 4 = 4*n + 22 */
+ B = /* B0 + 4n + */ (k % 19) + (k / 114);
+ /* upper bound for j: 18 + 19*5 = 113 */
+ j = (k % 19) + 19 * (k % 6);
+ /* upper iB index: 4*n+23*114-1 */
+ cB[k] = iB[B * 114 + j];
+ }
+}
+
+
/*! @} */
diff --git a/src/coding/gsm0503_mapping.c b/src/coding/gsm0503_mapping.c
index f7532eb2..04acfd07 100644
--- a/src/coding/gsm0503_mapping.c
+++ b/src/coding/gsm0503_mapping.c
@@ -15,10 +15,6 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <stdint.h>
diff --git a/src/coding/gsm0503_parity.c b/src/coding/gsm0503_parity.c
index a8daacc7..ef71d351 100644
--- a/src/coding/gsm0503_parity.c
+++ b/src/coding/gsm0503_parity.c
@@ -15,10 +15,6 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <stdint.h>
diff --git a/src/coding/gsm0503_tables.c b/src/coding/gsm0503_tables.c
index df0abeed..25ea2fa3 100644
--- a/src/coding/gsm0503_tables.c
+++ b/src/coding/gsm0503_tables.c
@@ -15,10 +15,6 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <stdint.h>
diff --git a/src/coding/libosmocoding.map b/src/coding/libosmocoding.map
index 325b6d80..0444690e 100644
--- a/src/coding/libosmocoding.map
+++ b/src/coding/libosmocoding.map
@@ -76,6 +76,8 @@ gsm0503_xcch_deinterleave;
gsm0503_xcch_interleave;
gsm0503_tch_fr_deinterleave;
gsm0503_tch_fr_interleave;
+gsm0503_tch_f96_deinterleave;
+gsm0503_tch_f96_interleave;
gsm0503_tch_hr_deinterleave;
gsm0503_tch_hr_interleave;
gsm0503_mcs1_ul_deinterleave;
@@ -105,6 +107,7 @@ gsm0503_tch_fr_encode;
gsm0503_tch_fr_decode;
gsm0503_tch_hr_encode;
gsm0503_tch_hr_decode;
+gsm0503_tch_hr_decode2;
gsm0503_tch_afs_encode;
gsm0503_tch_afs_decode;
gsm0503_tch_afs_decode_dtx;
@@ -123,6 +126,26 @@ gsm0503_amr_dtx_frame_names;
gsm0503_amr_dtx_frame_name;
gsm0503_detect_afs_dtx_frame;
gsm0503_detect_ahs_dtx_frame;
+gsm0503_detect_afs_dtx_frame2;
+gsm0503_detect_ahs_dtx_frame2;
+
+gsm0503_tch_fr96_encode;
+gsm0503_tch_fr96_decode;
+gsm0503_tch_fr48_encode;
+gsm0503_tch_fr48_decode;
+gsm0503_tch_hr48_encode;
+gsm0503_tch_hr48_decode;
+gsm0503_tch_fr24_encode;
+gsm0503_tch_fr24_decode;
+gsm0503_tch_hr24_encode;
+gsm0503_tch_hr24_decode;
+gsm0503_tch_fr144_encode;
+gsm0503_tch_fr144_decode;
+
+gsm0503_tch_fr_facch_encode;
+gsm0503_tch_fr_facch_decode;
+gsm0503_tch_hr_facch_encode;
+gsm0503_tch_hr_facch_decode;
local: *;
};
diff --git a/src/core/Makefile.am b/src/core/Makefile.am
new file mode 100644
index 00000000..2efebd8d
--- /dev/null
+++ b/src/core/Makefile.am
@@ -0,0 +1,166 @@
+# 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 modifications: https://www.gnu.org/software/libtool/manual/html_node/Versioning.html
+LIBVERSION=21:0:0
+
+AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_builddir)/include -I$(top_builddir)
+AM_CFLAGS = -Wall $(TALLOC_CFLAGS) $(PTHREAD_CFLAGS) $(LIBSCTP_CFLAGS) $(LIBMNL_CFLAGS) $(URING_CFLAGS)
+
+if ENABLE_PSEUDOTALLOC
+AM_CPPFLAGS += -I$(top_srcdir)/src/pseudotalloc
+endif
+
+lib_LTLIBRARIES = libosmocore.la
+
+libosmocore_la_LIBADD = \
+ $(BACKTRACE_LIB) \
+ $(TALLOC_LIBS) \
+ $(LIBRARY_RT) \
+ $(PTHREAD_LIBS) \
+ $(LIBSCTP_LIBS) \
+ $(URING_LIBS) \
+ $(NULL)
+
+libosmocore_la_SOURCES = \
+ application.c \
+ backtrace.c \
+ base64.c \
+ bits.c \
+ bitvec.c \
+ bitcomp.c \
+ context.c \
+ conv.c \
+ conv_acc.c \
+ conv_acc_generic.c \
+ counter.c \
+ crc16.c \
+ crc8gen.c \
+ crc16gen.c \
+ crc32gen.c \
+ crc64gen.c \
+ exec.c \
+ fsm.c \
+ gsmtap_util.c \
+ isdnhdlc.c \
+ it_q.c \
+ logging.c \
+ logging_syslog.c \
+ logging_gsmtap.c \
+ loggingrb.c \
+ macaddr.c \
+ msgb.c \
+ netdev.c \
+ netns.c \
+ osmo_io.c \
+ osmo_io_poll.c \
+ panic.c \
+ prbs.c \
+ prim.c \
+ rate_ctr.c \
+ rbtree.c \
+ select.c \
+ sercomm.c \
+ signal.c \
+ sockaddr_str.c \
+ socket.c \
+ soft_uart.c \
+ stat_item.c \
+ stats.c \
+ stats_statsd.c \
+ stats_tcp.c \
+ strrb.c \
+ tdef.c \
+ thread.c \
+ time_cc.c \
+ timer.c \
+ timer_gettimeofday.c \
+ timer_clockgettime.c \
+ tun.c \
+ use_count.c \
+ utils.c \
+ write_queue.c \
+ probes.d \
+ $(NULL)
+
+if HAVE_SSSE3
+libosmocore_la_SOURCES += conv_acc_sse.c
+if HAVE_SSE4_1
+conv_acc_sse.lo : AM_CFLAGS += -mssse3 -msse4.1
+else
+conv_acc_sse.lo : AM_CFLAGS += -mssse3
+endif
+
+if HAVE_AVX2
+libosmocore_la_SOURCES += conv_acc_sse_avx.c
+if HAVE_SSE4_1
+conv_acc_sse_avx.lo : AM_CFLAGS += -mssse3 -mavx2 -msse4.1
+else
+conv_acc_sse_avx.lo : AM_CFLAGS += -mssse3 -mavx2
+endif
+endif
+endif
+
+if HAVE_NEON
+libosmocore_la_SOURCES += conv_acc_neon.c
+# conv_acc_neon.lo : AM_CFLAGS += -mfpu=neon no, could as well be vfp with neon
+endif
+
+BUILT_SOURCES = crc8gen.c crc16gen.c crc32gen.c crc64gen.c
+
+EXTRA_DIST = \
+ conv_acc_sse_impl.h \
+ conv_acc_neon_impl.h \
+ crcXXgen.c.tpl \
+ osmo_io_internal.h \
+ stat_item_internal.h \
+ libosmocore.map \
+ $(NULL)
+
+EXTRA_libosmocore_la_DEPENDENCIES = libosmocore.map
+
+libosmocore_la_LDFLAGS = \
+ $(LTLDFLAGS_OSMOCORE) \
+ -version-info \
+ $(LIBVERSION) \
+ -no-undefined
+
+if ENABLE_PLUGIN
+libosmocore_la_SOURCES += plugin.c
+libosmocore_la_LIBADD += $(LIBRARY_DLOPEN)
+endif
+
+if ENABLE_MSGFILE
+libosmocore_la_SOURCES += msgfile.c
+endif
+
+if ENABLE_SERIAL
+libosmocore_la_SOURCES += serial.c
+endif
+
+if ENABLE_SYSTEMD_LOGGING
+libosmocore_la_SOURCES += logging_systemd.c
+libosmocore_la_LIBADD += $(SYSTEMD_LIBS)
+endif
+
+if ENABLE_LIBMNL
+libosmocore_la_SOURCES += mnl.c
+libosmocore_la_LIBADD += $(LIBMNL_LIBS)
+endif
+
+if ENABLE_SYSTEMTAP
+probes.h: probes.d
+ $(DTRACE) -C -h -s $< -o $@
+
+probes.lo: probes.d
+ $(LIBTOOL) --mode=compile $(AM_V_lt) --tag=CC env CFLAGS="$(CFLAGS)" $(DTRACE) -C -G -s $< -o $@
+
+BUILT_SOURCES += probes.h probes.lo
+libosmocore_la_LIBADD += probes.lo
+endif
+
+if ENABLE_URING
+libosmocore_la_SOURCES += osmo_io_uring.c
+endif
+
+crc%gen.c: crcXXgen.c.tpl
+ $(AM_V_GEN)sed -e's/XX/$*/g' $< > $@
diff --git a/src/application.c b/src/core/application.c
index 7fd62808..f7e5816f 100644
--- a/src/application.c
+++ b/src/core/application.c
@@ -18,10 +18,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
/*! \mainpage libosmocore Documentation
diff --git a/src/backtrace.c b/src/core/backtrace.c
index a18bde02..60bd2381 100644
--- a/src/backtrace.c
+++ b/src/core/backtrace.c
@@ -18,10 +18,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <stdio.h>
diff --git a/src/core/base64.c b/src/core/base64.c
new file mode 100644
index 00000000..0c161ceb
--- /dev/null
+++ b/src/core/base64.c
@@ -0,0 +1,195 @@
+/*
+ * RFC 1521 base64 encoding/decoding
+ *
+ * Copyright (C) 2006-2015, ARM Limited, All Rights Reserved
+ *
+ * This file is part of mbed TLS (https://tls.mbed.org)
+ *
+ * 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 <osmocom/core/base64.h>
+
+#include <stdint.h>
+#include <stdio.h>
+#include <errno.h>
+
+static const unsigned char base64_enc_map[64] = {
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
+ 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
+ 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd',
+ 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
+ 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
+ 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', '+', '/'
+};
+
+static const unsigned char base64_dec_map[128] = {
+ 127, 127, 127, 127, 127, 127, 127, 127, 127, 127,
+ 127, 127, 127, 127, 127, 127, 127, 127, 127, 127,
+ 127, 127, 127, 127, 127, 127, 127, 127, 127, 127,
+ 127, 127, 127, 127, 127, 127, 127, 127, 127, 127,
+ 127, 127, 127, 62, 127, 127, 127, 63, 52, 53,
+ 54, 55, 56, 57, 58, 59, 60, 61, 127, 127,
+ 127, 64, 127, 127, 127, 0, 1, 2, 3, 4,
+ 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
+ 25, 127, 127, 127, 127, 127, 127, 26, 27, 28,
+ 29, 30, 31, 32, 33, 34, 35, 36, 37, 38,
+ 39, 40, 41, 42, 43, 44, 45, 46, 47, 48,
+ 49, 50, 51, 127, 127, 127, 127, 127
+};
+
+/*
+ * Encode a buffer into base64 format
+ */
+int osmo_base64_encode(unsigned char *dst, size_t dlen, size_t *olen,
+ const unsigned char *src, size_t slen)
+{
+ size_t i, n;
+ int C1, C2, C3;
+ unsigned char *p;
+
+ if (slen == 0) {
+ *olen = 0;
+ return 0;
+ }
+
+ n = (slen << 3) / 6;
+
+ switch ((slen << 3) - (n * 6)) {
+ case 2:
+ n += 3;
+ break;
+ case 4:
+ n += 2;
+ break;
+ default:
+ break;
+ }
+
+ if (dlen < n + 1) {
+ *olen = n + 1;
+ return -ENOBUFS;
+ }
+
+ n = (slen / 3) * 3;
+
+ for (i = 0, p = dst; i < n; i += 3) {
+ C1 = *src++;
+ C2 = *src++;
+ C3 = *src++;
+
+ *p++ = base64_enc_map[(C1 >> 2) & 0x3F];
+ *p++ = base64_enc_map[(((C1 & 3) << 4) + (C2 >> 4)) & 0x3F];
+ *p++ = base64_enc_map[(((C2 & 15) << 2) + (C3 >> 6)) & 0x3F];
+ *p++ = base64_enc_map[C3 & 0x3F];
+ }
+
+ if (i < slen) {
+ C1 = *src++;
+ C2 = ((i + 1) < slen) ? *src++ : 0;
+
+ *p++ = base64_enc_map[(C1 >> 2) & 0x3F];
+ *p++ = base64_enc_map[(((C1 & 3) << 4) + (C2 >> 4)) & 0x3F];
+
+ if ((i + 1) < slen)
+ *p++ = base64_enc_map[((C2 & 15) << 2) & 0x3F];
+ else
+ *p++ = '=';
+
+ *p++ = '=';
+ }
+
+ *olen = p - dst;
+ *p = 0;
+
+ return 0;
+}
+
+/*
+ * Decode a base64-formatted buffer
+ */
+int osmo_base64_decode(unsigned char *dst, size_t dlen, size_t *olen,
+ const unsigned char *src, size_t slen)
+{
+ size_t i, n;
+ uint32_t j, x;
+ unsigned char *p;
+
+ /* First pass: check for validity and get output length */
+ for (i = n = j = 0; i < slen; i++) {
+ /* Skip spaces before checking for EOL */
+ x = 0;
+ while (i < slen && src[i] == ' ') {
+ ++i;
+ ++x;
+ }
+
+ /* Spaces at end of buffer are OK */
+ if (i == slen)
+ break;
+
+ if ((slen - i) >= 2 && src[i] == '\r' && src[i + 1] == '\n')
+ continue;
+
+ if (src[i] == '\n')
+ continue;
+
+ /* Space inside a line is an error */
+ if (x != 0)
+ return -EINVAL;
+
+ if (src[i] == '=' && ++j > 2)
+ return -EINVAL;
+
+ if (src[i] > 127 || base64_dec_map[src[i]] == 127)
+ return -EINVAL;
+
+ if (base64_dec_map[src[i]] < 64 && j != 0)
+ return -EINVAL;
+
+ n++;
+ }
+
+ if (n == 0)
+ return 0;
+
+ n = ((n * 6) + 7) >> 3;
+ n -= j;
+
+ if (dst == NULL || dlen < n) {
+ *olen = n;
+ return -ENOBUFS;
+ }
+
+ for (j = 3, n = x = 0, p = dst; i > 0; i--, src++) {
+ if (*src == '\r' || *src == '\n' || *src == ' ')
+ continue;
+
+ j -= (base64_dec_map[*src] == 64);
+ x = (x << 6) | (base64_dec_map[*src] & 0x3F);
+
+ if (++n == 4) {
+ n = 0;
+ if (j > 0)
+ *p++ = (unsigned char)(x >> 16);
+ if (j > 1)
+ *p++ = (unsigned char)(x >> 8);
+ if (j > 2)
+ *p++ = (unsigned char)(x);
+ }
+ }
+
+ *olen = p - dst;
+
+ return 0;
+}
diff --git a/src/bitcomp.c b/src/core/bitcomp.c
index 6f2fb62b..5fb2cba2 100644
--- a/src/bitcomp.c
+++ b/src/core/bitcomp.c
@@ -18,10 +18,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
/*! \defgroup bitcomp Bit compression
diff --git a/src/bits.c b/src/core/bits.c
index aa117531..3da7d9b9 100644
--- a/src/bits.c
+++ b/src/core/bits.c
@@ -16,10 +16,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <stdint.h>
@@ -185,14 +181,14 @@ int osmo_pbit2ubit(ubit_t *out, const pbit_t *in, unsigned int num_bits)
* \param[in] in input buffer of unpacked bits
* \param[in] in_ofs offset into input buffer
* \param[in] num_bits number of bits
- * \param[in] lsb_mode Encode bits in LSB orde instead of MSB
+ * \param[in] lsb_mode Encode bits in LSB order instead of MSB
* \returns length in bytes (max written offset of output buffer + 1)
*/
int osmo_ubit2pbit_ext(pbit_t *out, unsigned int out_ofs,
const ubit_t *in, unsigned int in_ofs,
unsigned int num_bits, int lsb_mode)
{
- int i, op, bn;
+ unsigned int i, op, bn;
for (i=0; i<num_bits; i++) {
op = out_ofs + i;
bn = lsb_mode ? (op&7) : (7-(op&7));
@@ -210,14 +206,14 @@ int osmo_ubit2pbit_ext(pbit_t *out, unsigned int out_ofs,
* \param[in] in input buffer of packed bits
* \param[in] in_ofs offset into input buffer
* \param[in] num_bits number of bits
- * \param[in] lsb_mode Encode bits in LSB orde instead of MSB
+ * \param[in] lsb_mode Encode bits in LSB order instead of MSB
* \returns length in bytes (max written offset of output buffer + 1)
*/
int osmo_pbit2ubit_ext(ubit_t *out, unsigned int out_ofs,
const pbit_t *in, unsigned int in_ofs,
unsigned int num_bits, int lsb_mode)
{
- int i, ip, bn;
+ unsigned int i, ip, bn;
for (i=0; i<num_bits; i++) {
ip = in_ofs + i;
bn = lsb_mode ? (ip&7) : (7-(ip&7));
@@ -308,7 +304,7 @@ uint32_t osmo_revbytebits_8(uint8_t x)
*/
void osmo_revbytebits_buf(uint8_t *buf, int len)
{
- unsigned int i;
+ int i;
for (i = 0; i < len; i++)
buf[i] = flip_table[buf[i]];
diff --git a/src/bitvec.c b/src/core/bitvec.c
index 2b4e8c98..ac702b94 100644
--- a/src/bitvec.c
+++ b/src/core/bitvec.c
@@ -16,10 +16,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
/*! \addtogroup bitvec
@@ -201,7 +197,8 @@ int bitvec_get_bit_high(struct bitvec *bv)
* \return 0 on success; negative in case of error */
int bitvec_set_bits(struct bitvec *bv, const enum bit_value *bits, unsigned int count)
{
- int i, rc;
+ unsigned int i;
+ int rc;
for (i = 0; i < count; i++) {
rc = bitvec_set_bit(bv, bits[i]);
@@ -264,7 +261,7 @@ int16_t bitvec_get_int16_msb(const struct bitvec *bv, unsigned int num_bits)
* \return integer value retrieved from bit vector */
int bitvec_get_uint(struct bitvec *bv, unsigned int num_bits)
{
- int i;
+ unsigned int i;
unsigned int ui = 0;
for (i = 0; i < num_bits; i++) {
@@ -398,7 +395,7 @@ int bitvec_set_bytes(struct bitvec *bv, const uint8_t *bytes, unsigned int count
* \param[in] size Number of bytes in the vector
* \param[in] ctx Context from which to allocate
* \return pointer to allocated vector; NULL in case of error */
-struct bitvec *bitvec_alloc(unsigned int size, TALLOC_CTX *ctx)
+struct bitvec *bitvec_alloc(unsigned int size, void *ctx)
{
struct bitvec *bv = talloc(ctx, struct bitvec);
if (!bv)
@@ -472,19 +469,29 @@ int bitvec_unhex(struct bitvec *bv, const char *src)
* \param[in] bv The boolean vector to work on
* \param[in,out] read_index Where reading supposed to start in the vector
* \param[in] len How many bits to read from vector
- * \returns read bits or negative value on error
+ * \returns An integer made up of the bits read.
+ *
+ * In case of an error, errno is set to a non-zero value. Otherwise it holds 0.
*/
uint64_t bitvec_read_field(struct bitvec *bv, unsigned int *read_index, unsigned int len)
{
unsigned int i;
uint64_t ui = 0;
+
+ /* Prevent bitvec overrun due to incorrect index and/or length */
+ if (len && bytenum_from_bitnum(*read_index + len - 1) >= bv->data_len) {
+ errno = EOVERFLOW;
+ return 0;
+ }
+
bv->cur_bit = *read_index;
+ errno = 0;
for (i = 0; i < len; i++) {
- int bit = bitvec_get_bit_pos((const struct bitvec *)bv, bv->cur_bit);
- if (bit < 0)
- return bit;
- if (bit)
+ unsigned int bytenum = bytenum_from_bitnum(bv->cur_bit);
+ unsigned int bitnum = 7 - (bv->cur_bit % 8);
+
+ if (bv->data[bytenum] & (1 << bitnum))
ui |= ((uint64_t)1 << (len - i - 1));
bv->cur_bit++;
}
@@ -533,13 +540,11 @@ char bit_value_to_char(enum bit_value v)
*/
void bitvec_to_string_r(const struct bitvec *bv, char *str)
{
- unsigned i, pos = 0;
char *cur = str;
- for (i = 0; i < bv->cur_bit; i++) {
+ for (unsigned int i = 0; i < bv->cur_bit; i++) {
if (0 == i % 8)
*cur++ = ' ';
*cur++ = bit_value_to_char(bitvec_get_bit_pos(bv, i));
- pos++;
}
*cur = 0;
}
@@ -597,7 +602,7 @@ unsigned bitvec_rl(const struct bitvec *bv, bool b)
* \returns Number of consecutive bits of \p b in \p bv and cur_bit will
* \go to cur_bit + number of consecutive bit
*/
-unsigned bitvec_rl_curbit(struct bitvec *bv, bool b, int max_bits)
+unsigned bitvec_rl_curbit(struct bitvec *bv, bool b, unsigned int max_bits)
{
unsigned i = 0;
unsigned j = 8;
diff --git a/src/context.c b/src/core/context.c
index bad012bd..a0b3a555 100644
--- a/src/context.c
+++ b/src/core/context.c
@@ -2,7 +2,7 @@
* talloc context handling.
*
* (C) 2019 by Harald Welte <laforge@gnumonks.org>
- * All Rights Reserverd.
+ * All Rights Reserved.
*
* SPDX-License-Identifier: GPL-2.0+
*
@@ -15,11 +15,6 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
- * MA 02110-1301, USA.
*/
#include <string.h>
#include <errno.h>
@@ -44,7 +39,7 @@ int osmo_ctx_init(const char *id)
}
/* initialize osmo_ctx on main tread */
-static __attribute__((constructor)) void on_dso_load_ctx(void)
+static __attribute__((constructor(101))) void on_dso_load_ctx(void)
{
OSMO_ASSERT(osmo_ctx_init("main") == 0);
}
diff --git a/src/conv.c b/src/core/conv.c
index 06c4299b..8963018b 100644
--- a/src/conv.c
+++ b/src/core/conv.c
@@ -16,10 +16,6 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
/*! \addtogroup conv
@@ -67,7 +63,7 @@ osmo_conv_get_output_length(const struct osmo_conv_code *code, int len)
/* Count punctured bits */
if (code->puncture) {
- for (pbits=0; code->puncture[pbits] >= 0; pbits++);
+ for (pbits = 0; code->puncture[pbits] >= 0; pbits++) {}
out_len -= pbits;
}
@@ -85,7 +81,7 @@ osmo_conv_get_output_length(const struct osmo_conv_code *code, int len)
*/
void
osmo_conv_encode_init(struct osmo_conv_encoder *encoder,
- const struct osmo_conv_code *code)
+ const struct osmo_conv_code *code)
{
memset(encoder, 0x00, sizeof(struct osmo_conv_encoder));
OSMO_ASSERT(code != NULL);
@@ -94,12 +90,12 @@ osmo_conv_encode_init(struct osmo_conv_encoder *encoder,
void
osmo_conv_encode_load_state(struct osmo_conv_encoder *encoder,
- const ubit_t *input)
+ const ubit_t *input)
{
int i;
uint8_t state = 0;
- for (i=0; i<(encoder->code->K-1); i++)
+ for (i = 0; i < (encoder->code->K - 1); i++)
state = (state << 1) | input[i];
encoder->state = state;
@@ -107,15 +103,14 @@ osmo_conv_encode_load_state(struct osmo_conv_encoder *encoder,
static inline int
_conv_encode_do_output(struct osmo_conv_encoder *encoder,
- uint8_t out, ubit_t *output)
+ uint8_t out, ubit_t *output)
{
const struct osmo_conv_code *code = encoder->code;
int o_idx = 0;
int j;
if (code->puncture) {
- for (j=0; j<code->N; j++)
- {
+ for (j = 0; j < code->N; j++) {
int bit_no = code->N - j - 1;
int r_idx = encoder->i_idx * code->N + j;
@@ -125,8 +120,7 @@ _conv_encode_do_output(struct osmo_conv_encoder *encoder,
output[o_idx++] = (out >> bit_no) & 1;
}
} else {
- for (j=0; j<code->N; j++)
- {
+ for (j = 0; j < code->N; j++) {
int bit_no = code->N - j - 1;
output[o_idx++] = (out >> bit_no) & 1;
}
@@ -137,7 +131,7 @@ _conv_encode_do_output(struct osmo_conv_encoder *encoder,
int
osmo_conv_encode_raw(struct osmo_conv_encoder *encoder,
- const ubit_t *input, ubit_t *output, int n)
+ const ubit_t *input, ubit_t *output, int n)
{
const struct osmo_conv_code *code = encoder->code;
uint8_t state;
@@ -147,11 +141,11 @@ osmo_conv_encode_raw(struct osmo_conv_encoder *encoder,
o_idx = 0;
state = encoder->state;
- for (i=0; i<n; i++) {
+ for (i = 0; i < n; i++) {
int bit = input[i];
uint8_t out;
- out = code->next_output[state][bit];
+ out = code->next_output[state][bit];
state = code->next_state[state][bit];
o_idx += _conv_encode_do_output(encoder, out, &output[o_idx]);
@@ -165,8 +159,7 @@ osmo_conv_encode_raw(struct osmo_conv_encoder *encoder,
}
int
-osmo_conv_encode_flush(struct osmo_conv_encoder *encoder,
- ubit_t *output)
+osmo_conv_encode_flush(struct osmo_conv_encoder *encoder, ubit_t *output)
{
const struct osmo_conv_code *code = encoder->code;
uint8_t state;
@@ -179,14 +172,14 @@ osmo_conv_encode_flush(struct osmo_conv_encoder *encoder,
o_idx = 0;
state = encoder->state;
- for (i=0; i<n; i++) {
+ for (i = 0; i < n; i++) {
uint8_t out;
if (code->next_term_output) {
- out = code->next_term_output[state];
+ out = code->next_term_output[state];
state = code->next_term_state[state];
} else {
- out = code->next_output[state][0];
+ out = code->next_output[state][0];
state = code->next_state[state][0];
}
@@ -212,7 +205,7 @@ osmo_conv_encode_flush(struct osmo_conv_encoder *encoder,
*/
int
osmo_conv_encode(const struct osmo_conv_code *code,
- const ubit_t *input, ubit_t *output)
+ const ubit_t *input, ubit_t *output)
{
struct osmo_conv_encoder encoder;
int l;
@@ -242,17 +235,18 @@ osmo_conv_encode(const struct osmo_conv_code *code,
/* Forward declaration for accerlated decoding with certain codes */
int
osmo_conv_decode_acc(const struct osmo_conv_code *code,
- const sbit_t *input, ubit_t *output);
+ const sbit_t *input, ubit_t *output);
void
osmo_conv_decode_init(struct osmo_conv_decoder *decoder,
- const struct osmo_conv_code *code, int len, int start_state)
+ const struct osmo_conv_code *code, int len,
+ int start_state)
{
int n_states;
/* Init */
if (len <= 0)
- len = code->len;
+ len = code->len;
n_states = 1 << (code->K - 1);
@@ -263,7 +257,7 @@ osmo_conv_decode_init(struct osmo_conv_decoder *decoder,
decoder->len = len;
/* Allocate arrays */
- decoder->ae = malloc(sizeof(unsigned int) * n_states);
+ decoder->ae = malloc(sizeof(unsigned int) * n_states);
decoder->ae_next = malloc(sizeof(unsigned int) * n_states);
decoder->state_history = malloc(sizeof(uint8_t) * n_states * (len + decoder->code->K - 1));
@@ -287,7 +281,7 @@ osmo_conv_decode_reset(struct osmo_conv_decoder *decoder, int start_state)
memset(decoder->ae, 0x00, sizeof(unsigned int) * decoder->n_states);
} else {
/* Fixed start state */
- for (i=0; i<decoder->n_states; i++) {
+ for (i = 0; i < decoder->n_states; i++) {
decoder->ae[i] = (i == start_state) ? 0 : MAX_AE;
}
}
@@ -304,12 +298,12 @@ osmo_conv_decode_rewind(struct osmo_conv_decoder *decoder)
decoder->p_idx = 0;
/* Initial error normalize (remove constant) */
- for (i=0; i<decoder->n_states; i++) {
+ for (i = 0; i < decoder->n_states; i++) {
if (decoder->ae[i] < min_ae)
min_ae = decoder->ae[i];
}
- for (i=0; i<decoder->n_states; i++)
+ for (i = 0; i < decoder->n_states; i++)
decoder->ae[i] -= min_ae;
}
@@ -325,7 +319,7 @@ osmo_conv_decode_deinit(struct osmo_conv_decoder *decoder)
int
osmo_conv_decode_scan(struct osmo_conv_decoder *decoder,
- const sbit_t *input, int n)
+ const sbit_t *input, int n)
{
const struct osmo_conv_code *code = decoder->code;
@@ -342,27 +336,25 @@ osmo_conv_decode_scan(struct osmo_conv_decoder *decoder,
/* Prepare */
n_states = decoder->n_states;
- ae = decoder->ae;
+ ae = decoder->ae;
ae_next = decoder->ae_next;
state_history = &decoder->state_history[n_states * decoder->o_idx];
- in_sym = alloca(sizeof(sbit_t) * code->N);
+ in_sym = alloca(sizeof(sbit_t) * code->N);
i_idx = 0;
p_idx = decoder->p_idx;
/* Scan the treillis */
- for (i=0; i<n; i++)
- {
+ for (i = 0; i < n; i++) {
/* Reset next accumulated error */
- for (s=0; s<n_states; s++) {
+ for (s = 0; s < n_states; s++)
ae_next[s] = MAX_AE;
- }
/* Get input */
if (code->puncture) {
/* Hard way ... */
- for (j=0; j<code->N; j++) {
+ for (j = 0; j < code->N; j++) {
int idx = ((decoder->o_idx + i) * code->N) + j;
if (idx == code->puncture[p_idx]) {
in_sym[j] = 0; /* Undefined */
@@ -379,23 +371,21 @@ osmo_conv_decode_scan(struct osmo_conv_decoder *decoder,
}
/* Scan all state */
- for (s=0; s<n_states; s++)
- {
+ for (s = 0; s < n_states; s++) {
/* Scan possible input bits */
- for (b=0; b<2; b++)
- {
+ for (b = 0; b < 2; b++) {
int nae, ov, e;
uint8_t m;
/* Next output and state */
- uint8_t out = code->next_output[s][b];
+ uint8_t out = code->next_output[s][b];
uint8_t state = code->next_state[s][b];
/* New error for this path */
nae = ae[s]; /* start from last error */
m = 1 << (code->N - 1); /* mask for 'out' bit selection */
- for (j=0; j<code->N; j++) {
+ for (j = 0; j < code->N; j++) {
int is = (int)in_sym[j];
if (is) {
ov = (out & m) ? -127 : 127; /* sbit_t value for it */
@@ -425,8 +415,7 @@ osmo_conv_decode_scan(struct osmo_conv_decoder *decoder,
}
int
-osmo_conv_decode_flush(struct osmo_conv_decoder *decoder,
- const sbit_t *input)
+osmo_conv_decode_flush(struct osmo_conv_decoder *decoder, const sbit_t *input)
{
const struct osmo_conv_code *code = decoder->code;
@@ -443,27 +432,25 @@ osmo_conv_decode_flush(struct osmo_conv_decoder *decoder,
/* Prepare */
n_states = decoder->n_states;
- ae = decoder->ae;
+ ae = decoder->ae;
ae_next = decoder->ae_next;
state_history = &decoder->state_history[n_states * decoder->o_idx];
- in_sym = alloca(sizeof(sbit_t) * code->N);
+ in_sym = alloca(sizeof(sbit_t) * code->N);
i_idx = 0;
p_idx = decoder->p_idx;
/* Scan the treillis */
- for (i=0; i<code->K-1; i++)
- {
+ for (i = 0; i < code->K - 1; i++) {
/* Reset next accumulated error */
- for (s=0; s<n_states; s++) {
+ for (s = 0; s < n_states; s++)
ae_next[s] = MAX_AE;
- }
/* Get input */
if (code->puncture) {
/* Hard way ... */
- for (j=0; j<code->N; j++) {
+ for (j = 0; j < code->N; j++) {
int idx = ((decoder->o_idx + i) * code->N) + j;
if (idx == code->puncture[p_idx]) {
in_sym[j] = 0; /* Undefined */
@@ -480,8 +467,7 @@ osmo_conv_decode_flush(struct osmo_conv_decoder *decoder,
}
/* Scan all state */
- for (s=0; s<n_states; s++)
- {
+ for (s = 0; s < n_states; s++) {
int nae, ov, e;
uint8_t m;
@@ -490,10 +476,10 @@ osmo_conv_decode_flush(struct osmo_conv_decoder *decoder,
uint8_t state;
if (code->next_term_output) {
- out = code->next_term_output[s];
+ out = code->next_term_output[s];
state = code->next_term_state[s];
} else {
- out = code->next_output[s][0];
+ out = code->next_output[s][0];
state = code->next_state[s][0];
}
@@ -501,7 +487,7 @@ osmo_conv_decode_flush(struct osmo_conv_decoder *decoder,
nae = ae[s]; /* start from last error */
m = 1 << (code->N - 1); /* mask for 'out' bit selection */
- for (j=0; j<code->N; j++) {
+ for (j = 0; j < code->N; j++) {
int is = (int)in_sym[j];
if (is) {
ov = (out & m) ? -127 : 127; /* sbit_t value for it */
@@ -530,38 +516,86 @@ osmo_conv_decode_flush(struct osmo_conv_decoder *decoder,
}
int
-osmo_conv_decode_get_output(struct osmo_conv_decoder *decoder,
- ubit_t *output, int has_flush, int end_state)
+osmo_conv_decode_get_best_end_state(struct osmo_conv_decoder *decoder)
{
const struct osmo_conv_code *code = decoder->code;
- int min_ae;
- uint8_t min_state, cur_state;
- int i, s, n;
+ int min_ae, min_state;
+ int s;
- uint8_t *sh_ptr;
+ /* If flushed, we _know_ the end state */
+ if (code->term == CONV_TERM_FLUSH)
+ return 0;
- /* End state ? */
- if (end_state < 0) {
- /* Find state with least error */
- min_ae = MAX_AE;
- min_state = 0xff;
+ /* Search init */
+ min_state = -1;
+ min_ae = MAX_AE;
- for (s=0; s<decoder->n_states; s++)
- {
+ /* If tail biting, we search for the minimum path metric that
+ * gives a circular traceback (i.e. start_state == end_state */
+ if (code->term == CONV_TERM_TAIL_BITING) {
+ int t, n, i;
+ uint8_t *sh_ptr;
+
+ for (s = 0; s < decoder->n_states; s++) {
+ /* Check if that state traces back to itself */
+ n = decoder->o_idx;
+ sh_ptr = &decoder->state_history[decoder->n_states * (n-1)];
+ t = s;
+
+ for (i = n - 1; i >= 0; i--) {
+ t = sh_ptr[t];
+ sh_ptr -= decoder->n_states;
+ }
+
+ if (s != t)
+ continue;
+
+ /* If it does, consider it */
if (decoder->ae[s] < min_ae) {
min_ae = decoder->ae[s];
min_state = s;
}
}
- if (min_state == 0xff)
- return -1;
- } else {
- min_state = (uint8_t) end_state;
- min_ae = decoder->ae[end_state];
+ if (min_ae < MAX_AE)
+ return min_state;
+ }
+
+ /* Finally, just the lowest path metric */
+ for (s = 0; s < decoder->n_states; s++) {
+ /* Is it smaller ? */
+ if (decoder->ae[s] < min_ae) {
+ min_ae = decoder->ae[s];
+ min_state = s;
+ }
}
+ return min_state;
+}
+
+int
+osmo_conv_decode_get_output(struct osmo_conv_decoder *decoder,
+ ubit_t *output, int has_flush, int end_state)
+{
+ const struct osmo_conv_code *code = decoder->code;
+
+ int min_ae;
+ uint8_t min_state, cur_state;
+ int i, n;
+
+ uint8_t *sh_ptr;
+
+ /* End state ? */
+ if (end_state < 0)
+ end_state = osmo_conv_decode_get_best_end_state(decoder);
+
+ if (end_state < 0)
+ return -1;
+
+ min_state = (uint8_t) end_state;
+ min_ae = decoder->ae[end_state];
+
/* Traceback */
cur_state = min_state;
@@ -571,16 +605,15 @@ osmo_conv_decode_get_output(struct osmo_conv_decoder *decoder,
/* No output for the K-1 termination input bits */
if (has_flush) {
- for (i=0; i<code->K-1; i++) {
+ for (i = 0; i < code->K - 1; i++) {
cur_state = sh_ptr[cur_state];
sh_ptr -= decoder->n_states;
}
n -= code->K - 1;
}
- /* Generate output backward */
- for (i=n-1; i>=0; i--)
- {
+ /* Generate output backward */
+ for (i = n - 1; i >= 0; i--) {
min_state = cur_state;
cur_state = sh_ptr[cur_state];
@@ -602,12 +635,12 @@ osmo_conv_decode_get_output(struct osmo_conv_decoder *decoder,
*
* This is an all-in-one function, taking care of
* \ref osmo_conv_decode_init, \ref osmo_conv_decode_scan,
- * \ref osmo_conv_decode_flush, \ref osmo_conv_decode_get_output and
- * \ref osmo_conv_decode_deinit.
+ * \ref osmo_conv_decode_flush, \ref osmo_conv_decode_get_best_end_state,
+ * \ref osmo_conv_decode_get_output and \ref osmo_conv_decode_deinit.
*/
int
osmo_conv_decode(const struct osmo_conv_code *code,
- const sbit_t *input, ubit_t *output)
+ const sbit_t *input, ubit_t *output)
{
struct osmo_conv_decoder decoder;
int rv, l;
@@ -630,7 +663,7 @@ osmo_conv_decode(const struct osmo_conv_code *code,
rv = osmo_conv_decode_get_output(&decoder, output,
code->term == CONV_TERM_FLUSH, /* has_flush */
- code->term == CONV_TERM_FLUSH ? 0 : -1 /* end_state */
+ -1 /* end_state */
);
osmo_conv_decode_deinit(&decoder);
diff --git a/src/conv_acc.c b/src/core/conv_acc.c
index 0f6f7ca2..4bd3b076 100644
--- a/src/conv_acc.c
+++ b/src/core/conv_acc.c
@@ -16,10 +16,6 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <stdlib.h>
@@ -487,10 +483,27 @@ static void _traceback_rec(struct vdecoder *dec,
*/
static int traceback(struct vdecoder *dec, uint8_t *out, int term, int len)
{
- int i, sum, max = -1;
- unsigned path, state = 0;
+ int i, j, sum, max = -1;
+ unsigned path, state = 0, state_scan;
+
+ if (term == CONV_TERM_TAIL_BITING) {
+ for (i = 0; i < dec->trellis.num_states; i++) {
+ state_scan = i;
+ for (j = len - 1; j >= 0; j--) {
+ path = dec->paths[j][state_scan] + 1;
+ state_scan = vstate_lshift(state_scan, dec->k, path);
+ }
+ if (state_scan != i)
+ continue;
+ sum = dec->trellis.sums[i];
+ if (sum > max) {
+ max = sum;
+ state = i;
+ }
+ }
+ }
- if (term != CONV_TERM_FLUSH) {
+ if ((max < 0) && (term != CONV_TERM_FLUSH)) {
for (i = 0; i < dec->trellis.num_states; i++) {
sum = dec->trellis.sums[i];
if (sum > max) {
diff --git a/src/conv_acc_generic.c b/src/core/conv_acc_generic.c
index 28876738..2257e6a9 100644
--- a/src/conv_acc_generic.c
+++ b/src/core/conv_acc_generic.c
@@ -17,10 +17,6 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <stdlib.h>
diff --git a/src/conv_acc_neon.c b/src/core/conv_acc_neon.c
index 72449468..fb180e3d 100644
--- a/src/conv_acc_neon.c
+++ b/src/core/conv_acc_neon.c
@@ -18,10 +18,6 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <stdlib.h>
diff --git a/src/conv_acc_neon_impl.h b/src/core/conv_acc_neon_impl.h
index 4471127e..8a78c75b 100644
--- a/src/conv_acc_neon_impl.h
+++ b/src/core/conv_acc_neon_impl.h
@@ -18,10 +18,6 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
/* Some distributions (notably Alpine Linux) for some strange reason
diff --git a/src/conv_acc_sse.c b/src/core/conv_acc_sse.c
index 63d8722a..513ab052 100644
--- a/src/conv_acc_sse.c
+++ b/src/core/conv_acc_sse.c
@@ -17,10 +17,6 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <stdint.h>
diff --git a/src/conv_acc_sse_avx.c b/src/core/conv_acc_sse_avx.c
index 5ac3c163..82b4fa62 100644
--- a/src/conv_acc_sse_avx.c
+++ b/src/core/conv_acc_sse_avx.c
@@ -17,10 +17,6 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <stdint.h>
diff --git a/src/conv_acc_sse_impl.h b/src/core/conv_acc_sse_impl.h
index 9ebbfe9c..807dbe5e 100644
--- a/src/conv_acc_sse_impl.h
+++ b/src/core/conv_acc_sse_impl.h
@@ -18,10 +18,6 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
/* Some distributions (notably Alpine Linux) for some strange reason
diff --git a/src/counter.c b/src/core/counter.c
index 0fa31661..dace15f3 100644
--- a/src/counter.c
+++ b/src/core/counter.c
@@ -17,10 +17,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <string.h>
@@ -79,7 +75,7 @@ int osmo_counters_for_each(int (*handle_counter)(struct osmo_counter *, void *),
/*! Counts the registered counter
* \returns amount of counters */
-int osmo_counters_count()
+int osmo_counters_count(void)
{
return llist_count(&counters);
}
diff --git a/src/crc16.c b/src/core/crc16.c
index 29dace2e..29dace2e 100644
--- a/src/crc16.c
+++ b/src/core/crc16.c
diff --git a/src/crcXXgen.c.tpl b/src/core/crcXXgen.c.tpl
index 74e6d521..154291cc 100644
--- a/src/crcXXgen.c.tpl
+++ b/src/core/crcXXgen.c.tpl
@@ -16,10 +16,6 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
/*! \addtogroup crc
diff --git a/src/exec.c b/src/core/exec.c
index 2a03ba80..2e33788e 100644
--- a/src/exec.c
+++ b/src/core/exec.c
@@ -14,10 +14,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include "config.h"
@@ -78,7 +74,7 @@ static bool str_in_list(const char **list, const char *key)
*
* Constraints: Keys up to a maximum length of 255 characters are supported.
*
- * \oaram[out] out caller-allocated array of pointers for the generated output
+ * \param[out] out caller-allocated array of pointers for the generated output
* \param[in] out_len size of out (number of pointers)
* \param[in] in input environment (NULL-terminated list of pointers like **environ)
* \param[in] whitelist whitelist of permitted keys in environment (like **environ)
@@ -135,7 +131,7 @@ int osmo_environment_filter(char **out, size_t out_len, char **in, const char **
* Constraints: If the same key exists in 'out' and 'in', duplicate keys are
* generated. It is a simple append, without any duplicate checks.
*
- * \oaram[out] out caller-allocated array of pointers for the generated output
+ * \param[out] out caller-allocated array of pointers for the generated output
* \param[in] out_len size of out (number of pointers)
* \param[in] in input environment (NULL-terminated list of pointers like **environ)
* \returns number of entries filled in 'out'; negative on error */
@@ -217,10 +213,18 @@ int osmo_system_nowait2(const char *command, const char **env_whitelist, char **
int rc;
if (user) {
+ if (getpw_buflen == -1) /* Value was indeterminate */
+ getpw_buflen = 16384; /* Should be more than enough */
char buf[getpw_buflen];
- getpwnam_r(user, &_pw, buf, sizeof(buf), &pw);
- if (!pw)
+ rc = getpwnam_r(user, &_pw, buf, sizeof(buf), &pw);
+ if (rc < 0) {
+ LOGP(DLGLOBAL, LOGL_ERROR, "getpwnam_r(\"%s\") failed: %s\n", user, strerror(-rc));
+ return rc;
+ }
+ if (!pw) {
+ LOGP(DLGLOBAL, LOGL_ERROR, "getpwnam_r(\"%s\"): user not found!\n", user);
return -EINVAL;
+ }
}
rc = fork();
@@ -268,6 +272,9 @@ int osmo_system_nowait2(const char *command, const char **env_whitelist, char **
return -EIO;
} else {
/* we are in the parent */
+ if (rc == -1)
+ LOGP(DLGLOBAL, LOGL_ERROR, "fork() error executing command '%s': %s\n",
+ command, strerror(errno));
return rc;
}
}
diff --git a/src/fsm.c b/src/core/fsm.c
index 1e8909ec..9333cac5 100644
--- a/src/fsm.c
+++ b/src/core/fsm.c
@@ -14,11 +14,6 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
- * MA 02110-1301, USA.
*/
#include <errno.h>
@@ -450,8 +445,8 @@ struct osmo_fsm_inst *osmo_fsm_inst_alloc(struct osmo_fsm *fsm, void *ctx, void
osmo_timer_setup(&fi->timer, fsm_tmr_cb, fi);
if (osmo_fsm_inst_update_id(fi, id) < 0) {
- fsm_free_or_steal(fi);
- return NULL;
+ fsm_free_or_steal(fi);
+ return NULL;
}
INIT_LLIST_HEAD(&fi->proc.children);
@@ -581,7 +576,7 @@ void osmo_fsm_inst_free(struct osmo_fsm_inst *fi)
* \param[in] event Event integer value
* \returns string rendering of the event
*/
-const char *osmo_fsm_event_name(struct osmo_fsm *fsm, uint32_t event)
+const char *osmo_fsm_event_name(const struct osmo_fsm *fsm, uint32_t event)
{
static __thread char buf[32];
if (!fsm->event_names) {
@@ -595,7 +590,7 @@ const char *osmo_fsm_event_name(struct osmo_fsm *fsm, uint32_t event)
* \param[in] fi FSM instance
* \returns string rendering of the FSM identity
*/
-const char *osmo_fsm_inst_name(struct osmo_fsm_inst *fi)
+const char *osmo_fsm_inst_name(const struct osmo_fsm_inst *fi)
{
if (!fi)
return "NULL";
@@ -611,7 +606,7 @@ const char *osmo_fsm_inst_name(struct osmo_fsm_inst *fi)
* \param[in] state FSM state number
* \returns string rendering of the FSM state
*/
-const char *osmo_fsm_state_name(struct osmo_fsm *fsm, uint32_t state)
+const char *osmo_fsm_state_name(const struct osmo_fsm *fsm, uint32_t state)
{
static __thread char buf[32];
if (state >= fsm->num_states) {
@@ -690,8 +685,11 @@ static int state_chg(struct osmo_fsm_inst *fi, uint32_t new_state,
if (!keep_timer
|| (keep_timer && !osmo_timer_pending(&fi->timer))) {
fi->T = T;
- if (timeout_ms)
- osmo_timer_schedule(&fi->timer, timeout_ms / 1000, timeout_ms % 1000);
+ if (timeout_ms) {
+ osmo_timer_schedule(&fi->timer,
+ /* seconds */ (timeout_ms / 1000),
+ /* microseconds */ (timeout_ms % 1000) * 1000);
+ }
}
/* Call 'onenter' last, user might terminate FSM from there */
@@ -1016,6 +1014,26 @@ void _osmo_fsm_inst_term_children(struct osmo_fsm_inst *fi,
}
}
+/*! Broadcast an event to all the FSMs children.
+ *
+ * Iterate over all children and send them the specified event.
+ *
+ * \param[in] fi FSM instance of the parent
+ * \param[in] event Event to send to children of FSM instance
+ * \param[in] data Data to pass along with the event
+ * \param[in] file Calling source file (from osmo_fsm_inst_dispatch macro)
+ * \param[in] line Calling source line (from osmo_fsm_inst_dispatch macro)
+ */
+void _osmo_fsm_inst_broadcast_children(struct osmo_fsm_inst *fi,
+ uint32_t event, void *data,
+ const char *file, int line)
+{
+ struct osmo_fsm_inst *child, *tmp;
+ llist_for_each_entry_safe(child, tmp, &fi->proc.children, proc.child) {
+ _osmo_fsm_inst_dispatch(child, event, data, file, line);
+ }
+}
+
const struct value_string osmo_fsm_term_cause_names[] = {
OSMO_VALUE_STRING(OSMO_FSM_TERM_PARENT),
OSMO_VALUE_STRING(OSMO_FSM_TERM_REQUEST),
diff --git a/src/gsmtap_util.c b/src/core/gsmtap_util.c
index 9a0ac027..b64c7b05 100644
--- a/src/gsmtap_util.c
+++ b/src/core/gsmtap_util.c
@@ -17,13 +17,9 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
-#include "../config.h"
+#include "config.h"
#include <osmocom/core/gsmtap_util.h>
#include <osmocom/core/logging.h>
@@ -33,6 +29,7 @@
#include <osmocom/core/select.h>
#include <osmocom/core/socket.h>
#include <osmocom/core/byteswap.h>
+#include <osmocom/core/utils.h>
#include <osmocom/gsm/protocol/gsm_04_08.h>
#include <osmocom/gsm/rsl.h>
@@ -50,6 +47,43 @@
*
* \file gsmtap_util.c */
+/*! one gsmtap instance
+ * Until gsmtap_inst_fd() is removed from the API at some point in the future, we have to keep the first member as
+ * 'int' and the second as 'struct osmo_wqueue' (this effectively makes sure that the struct member wq.bfd.fd maintains
+ * the same memory offset from the start of the struct) to ensure that inlined static 'instances' of gsmtap_inst_fd() in
+ * old binaries keep working the way they used to even with gsmtap_inst objects obtained from newer versions of libosmocore */
+struct gsmtap_inst {
+ int osmo_io_mode; /*!< Indicates whether or not to use Osmo IO mode for message output (thus enabling use of tx queues).
+ * This field member may not be changed or moved (backwards compatibility) */
+ struct osmo_wqueue wq; /*!< the wait queue. This field member may not be changed or moved (backwards compatibility) */
+
+ struct osmo_io_fd *out; /*!< Used when osmo_io_mode is nonzero */
+ int sink_fd;
+};
+
+struct _gsmtap_inst_legacy {
+ int ofd_wq_mode;
+ struct osmo_wqueue wq;
+ struct osmo_fd sink_ofd;
+};
+osmo_static_assert(offsetof(struct gsmtap_inst, wq) == offsetof(struct _gsmtap_inst_legacy, wq),
+ gsmtap_inst_new_wq_offset_equals_legacy_wq_offset);
+
+/*! Deprecated, use gsmtap_inst_fd2() instead
+ * \param[in] gti GSMTAP instance
+ * \returns file descriptor of GSMTAP instance */
+int gsmtap_inst_fd(struct gsmtap_inst *gti)
+{
+ return gsmtap_inst_fd2(gti);
+}
+
+/*! obtain the file descriptor associated with a gsmtap instance
+ * \param[in] gti GSMTAP instance
+ * \returns file descriptor of GSMTAP instance */
+int gsmtap_inst_fd2(const struct gsmtap_inst *gti)
+{
+ return gti->wq.bfd.fd;
+}
/*! convert RSL channel number to GSMTAP channel type
* \param[in] rsl_chantype RSL channel type
@@ -63,12 +97,14 @@ uint8_t chantype_rsl2gsmtap2(uint8_t rsl_chantype, uint8_t link_id, bool user_pl
switch (rsl_chantype) {
case RSL_CHAN_Bm_ACCHs:
+ case RSL_CHAN_OSMO_VAMOS_Bm_ACCHs:
if (user_plane)
ret = GSMTAP_CHANNEL_VOICE_F;
else
ret = GSMTAP_CHANNEL_FACCH_F;
break;
case RSL_CHAN_Lm_ACCHs:
+ case RSL_CHAN_OSMO_VAMOS_Lm_ACCHs:
if (user_plane)
ret = GSMTAP_CHANNEL_VOICE_H;
else
@@ -176,7 +212,7 @@ void chantype_gsmtap2rsl(uint8_t gsmtap_chantype, uint8_t *rsl_chantype,
*/
struct msgb *gsmtap_makemsg_ex(uint8_t type, uint16_t arfcn, uint8_t ts, uint8_t chan_type,
uint8_t ss, uint32_t fn, int8_t signal_dbm,
- uint8_t snr, const uint8_t *data, unsigned int len)
+ int8_t snr, const uint8_t *data, unsigned int len)
{
struct msgb *msg;
struct gsmtap_hdr *gh;
@@ -223,7 +259,7 @@ struct msgb *gsmtap_makemsg_ex(uint8_t type, uint16_t arfcn, uint8_t ts, uint8_t
*/
struct msgb *gsmtap_makemsg(uint16_t arfcn, uint8_t ts, uint8_t chan_type,
uint8_t ss, uint32_t fn, int8_t signal_dbm,
- uint8_t snr, const uint8_t *data, unsigned int len)
+ int8_t snr, const uint8_t *data, unsigned int len)
{
return gsmtap_makemsg_ex(GSMTAP_TYPE_UM, arfcn, ts, chan_type,
ss, fn, signal_dbm, snr, data, len);
@@ -233,13 +269,14 @@ struct msgb *gsmtap_makemsg(uint16_t arfcn, uint8_t ts, uint8_t chan_type,
#include <sys/socket.h>
#include <netinet/in.h>
+#include <osmocom/core/osmo_io.h>
-/*! Create a new (sending) GSMTAP source socket
+/*! Create a new (sending) GSMTAP source socket
* \param[in] host host name or IP address in string format
* \param[in] port UDP port number in host byte order
* \return file descriptor of the new socket
*
- * Opens a GSMTAP source (sending) socket, conncet it to host/port and
+ * Opens a GSMTAP source (sending) socket, connect it to host/port and
* return resulting fd. If \a host is NULL, the destination address
* will be localhost. If \a port is 0, the default \ref
* GSMTAP_UDP_PORT will be used.
@@ -255,6 +292,30 @@ int gsmtap_source_init_fd(const char *host, uint16_t port)
OSMO_SOCK_F_CONNECT);
}
+/*! Create a new (sending) GSMTAP source socket
+ * \param[in] local_host local host name or IP address in string format
+ * \param[in] local_port local UDP port number in host byte order
+ * \param[in] rem_host remote host name or IP address in string format
+ * \param[in] rem_port remote UDP port number in host byte order
+ * \return file descriptor of the new socket
+ *
+ * Opens a GSMTAP source (sending) socket, connect it to remote host/port,
+ * bind to local host/port and return resulting fd.
+ * If \a local_host is NULL, the default address is used.
+ * If \a local_port is 0, than random unused port will be selected by OS.
+ * If \a rem_host is NULL, the destination address will be localhost.
+ * If \a rem_port is 0, the default \ref GSMTAP_UDP_PORT will be used.
+ */
+int gsmtap_source_init_fd2(const char *local_host, uint16_t local_port, const char *rem_host, uint16_t rem_port)
+{
+ if (!local_host)
+ return gsmtap_source_init_fd(rem_host, rem_port);
+
+ return osmo_sock_init2(AF_UNSPEC, SOCK_DGRAM, IPPROTO_UDP, local_host, local_port,
+ rem_host ? rem_host : "localhost", rem_port ? rem_port : GSMTAP_UDP_PORT,
+ OSMO_SOCK_F_BIND | OSMO_SOCK_F_CONNECT);
+}
+
/*! Add a local sink to an existing GSMTAP source and return fd
* \param[in] gsmtap_fd file descriptor of the gsmtap socket
* \returns file descriptor of locally bound receive socket
@@ -302,13 +363,13 @@ int gsmtap_sendmsg(struct gsmtap_inst *gti, struct msgb *msg)
if (!gti)
return -ENODEV;
- if (gti->ofd_wq_mode)
- return osmo_wqueue_enqueue(&gti->wq, msg);
+ if (gti->osmo_io_mode)
+ return osmo_iofd_write_msgb(gti->out, msg);
else {
/* try immediate send and return error if any */
int rc;
- rc = write(gsmtap_inst_fd(gti), msg->data, msg->len);
+ rc = write(gsmtap_inst_fd2(gti), msg->data, msg->len);
if (rc < 0) {
return rc;
} else if (rc >= msg->len) {
@@ -321,12 +382,26 @@ int gsmtap_sendmsg(struct gsmtap_inst *gti, struct msgb *msg)
}
}
+/*! Send a \ref msgb through a GSMTAP source; free the message even if tx queue full.
+ * \param[in] gti GSMTAP instance
+ * \param[in] msg message buffer; always freed, caller must not reference it later.
+ * \return 0 in case of success; negative in case of error
+ */
+int gsmtap_sendmsg_free(struct gsmtap_inst *gti, struct msgb *msg)
+{
+ int rc;
+ rc = gsmtap_sendmsg(gti, msg);
+ if (rc < 0)
+ msgb_free(msg);
+ return rc;
+}
+
/*! send an arbitrary type through GSMTAP.
* See \ref gsmtap_makemsg_ex for arguments
*/
int gsmtap_send_ex(struct gsmtap_inst *gti, uint8_t type, uint16_t arfcn, uint8_t ts,
uint8_t chan_type, uint8_t ss, uint32_t fn,
- int8_t signal_dbm, uint8_t snr, const uint8_t *data,
+ int8_t signal_dbm, int8_t snr, const uint8_t *data,
unsigned int len)
{
struct msgb *msg;
@@ -351,47 +426,13 @@ int gsmtap_send_ex(struct gsmtap_inst *gti, uint8_t type, uint16_t arfcn, uint8_
*/
int gsmtap_send(struct gsmtap_inst *gti, uint16_t arfcn, uint8_t ts,
uint8_t chan_type, uint8_t ss, uint32_t fn,
- int8_t signal_dbm, uint8_t snr, const uint8_t *data,
+ int8_t signal_dbm, int8_t snr, const uint8_t *data,
unsigned int len)
{
return gsmtap_send_ex(gti, GSMTAP_TYPE_UM, arfcn, ts, chan_type, ss, fn,
signal_dbm, snr, data, len);
}
-/* Callback from select layer if we can write to the socket */
-static int gsmtap_wq_w_cb(struct osmo_fd *ofd, struct msgb *msg)
-{
- int rc;
-
- rc = write(ofd->fd, msg->data, msg->len);
- if (rc < 0) {
- return rc;
- }
- if (rc != msg->len) {
- return -EIO;
- }
-
- return 0;
-}
-
-/* Callback from select layer if we can read from the sink socket */
-static int gsmtap_sink_fd_cb(struct osmo_fd *fd, unsigned int flags)
-{
- int rc;
- uint8_t buf[4096];
-
- if (!(flags & OSMO_FD_READ))
- return 0;
-
- rc = read(fd->fd, buf, sizeof(buf));
- if (rc < 0) {
- return rc;
- }
- /* simply discard any data arriving on the socket */
-
- return 0;
-}
-
/*! Add a local sink to an existing GSMTAP source and return fd
* \param[in] gti existing GSMTAP source
* \returns file descriptor of locally bound receive socket
@@ -409,30 +450,63 @@ static int gsmtap_sink_fd_cb(struct osmo_fd *fd, unsigned int flags)
*/
int gsmtap_source_add_sink(struct gsmtap_inst *gti)
{
- int fd, rc;
+ return gti->sink_fd = gsmtap_source_add_sink_fd(gsmtap_inst_fd2(gti));
+}
- fd = gsmtap_source_add_sink_fd(gsmtap_inst_fd(gti));
- if (fd < 0)
- return fd;
+/* Registered in Osmo IO as a no-op to set the write callback. */
+static void gsmtap_ops_noop_cb(struct osmo_io_fd *iofd, int res, struct msgb *msg)
+{
+}
- if (gti->ofd_wq_mode) {
- struct osmo_fd *sink_ofd;
+static struct osmo_io_ops gsmtap_ops = { .write_cb = gsmtap_ops_noop_cb };
- sink_ofd = &gti->sink_ofd;
- sink_ofd->fd = fd;
- sink_ofd->when = OSMO_FD_READ;
- sink_ofd->cb = gsmtap_sink_fd_cb;
+/*! Open GSMTAP source socket, connect and register osmo_fd
+ * \param[in] local_host IP address in string format
+ * \param[in] local_port UDP port number in host byte order
+ * \param[in] rem_host host name or IP address in string format
+ * \param[in] rem_port UDP port number in host byte order
+ * \param[in] ofd_wq_mode Register \ref osmo_wqueue (1) or not (0)
+ * \return callee-allocated \ref gsmtap_inst
+ *
+ * Open GSMTAP source (sending) socket, connect it to remote host/port,
+ * bind it local host/port,
+ * allocate 'struct gsmtap_inst' and optionally osmo_fd/osmo_wqueue
+ * registration.
+ */
+struct gsmtap_inst *gsmtap_source_init2(const char *local_host, uint16_t local_port,
+ const char *rem_host, uint16_t rem_port, int ofd_wq_mode)
+{
+ struct gsmtap_inst *gti;
+ int fd;
- rc = osmo_fd_register(sink_ofd);
- if (rc < 0) {
- close(fd);
- return rc;
- }
+ fd = gsmtap_source_init_fd2(local_host, local_port, rem_host, rem_port);
+ if (fd < 0)
+ return NULL;
+
+ gti = talloc_zero(NULL, struct gsmtap_inst);
+ gti->osmo_io_mode = ofd_wq_mode;
+ /* Still using the wq member for its 'fd' field only, since we are keeping it for now, anyways */
+ gti->wq.bfd.fd = fd;
+ gti->sink_fd = -1;
+
+ if (ofd_wq_mode) {
+ gti->out = osmo_iofd_setup(gti, gti->wq.bfd.fd, "gsmtap_inst.io_fd", OSMO_IO_FD_MODE_READ_WRITE, &gsmtap_ops, NULL);
+ if (gti->out == NULL)
+ goto err_cleanup;
+ if (osmo_iofd_register(gti->out, gti->wq.bfd.fd) < 0)
+ goto err_cleanup;
+
+ /* osmo write queue previously used was set up with value of 64 */
+ osmo_iofd_set_txqueue_max_length(gti->out, 64);
}
- return fd;
-}
+ return gti;
+err_cleanup:
+ talloc_free(gti);
+ close(fd);
+ return NULL;
+}
/*! Open GSMTAP source socket, connect and register osmo_fd
* \param[in] host host name or IP address in string format
@@ -447,31 +521,25 @@ int gsmtap_source_add_sink(struct gsmtap_inst *gti)
struct gsmtap_inst *gsmtap_source_init(const char *host, uint16_t port,
int ofd_wq_mode)
{
- struct gsmtap_inst *gti;
- int fd, rc;
-
- fd = gsmtap_source_init_fd(host, port);
- if (fd < 0)
- return NULL;
+ return gsmtap_source_init2(NULL, 0, host, port, ofd_wq_mode);
+}
- gti = talloc_zero(NULL, struct gsmtap_inst);
- gti->ofd_wq_mode = ofd_wq_mode;
- gti->wq.bfd.fd = fd;
- gti->sink_ofd.fd = -1;
+void gsmtap_source_free(struct gsmtap_inst *gti)
+{
+ if (!gti)
+ return;
- if (ofd_wq_mode) {
- osmo_wqueue_init(&gti->wq, 64);
- gti->wq.write_cb = &gsmtap_wq_w_cb;
+ if (gti->osmo_io_mode) {
+ osmo_iofd_free(gti->out);
- rc = osmo_fd_register(&gti->wq.bfd);
- if (rc < 0) {
- talloc_free(gti);
- close(fd);
- return NULL;
+ if (gti->sink_fd != -1) {
+ close(gti->sink_fd);
+ gti->sink_fd = -1;
}
+
}
- return gti;
+ talloc_free(gti);
}
#endif /* HAVE_SYS_SOCKET_H */
@@ -512,6 +580,8 @@ const struct value_string gsmtap_type_names[] = {
{ GSMTAP_TYPE_TETRA_I1, "TETRA V+D" },
{ GSMTAP_TYPE_TETRA_I1_BURST, "TETRA bursts" },
{ GSMTAP_TYPE_WMX_BURST, "WiMAX burst" },
+ { GSMTAP_TYPE_GB_LLC, "GPRS Gb LLC" },
+ { GSMTAP_TYPE_GB_SNDCP, "GPRS Gb SNDCP" },
{ GSMTAP_TYPE_GMR1_UM, "GMR-1 air interfeace (MES-MS<->GTS)"},
{ GSMTAP_TYPE_UMTS_RLC_MAC, "UMTS RLC/MAC" },
{ GSMTAP_TYPE_UMTS_RRC, "UMTS RRC" },
@@ -520,6 +590,9 @@ const struct value_string gsmtap_type_names[] = {
{ GSMTAP_TYPE_LTE_MAC_FRAMED, "LTE MAC with context hdr" },
{ GSMTAP_TYPE_OSMOCORE_LOG, "libosmocore logging" },
{ GSMTAP_TYPE_QC_DIAG, "Qualcomm DIAG" },
+ { GSMTAP_TYPE_LTE_NAS, "LTE Non-Access Stratum" },
+ { GSMTAP_TYPE_E1T1, "E1/T1 lines" },
+ { GSMTAP_TYPE_GSM_RLP, "GSM Radio Link Protocol" },
{ 0, NULL }
};
diff --git a/src/isdnhdlc.c b/src/core/isdnhdlc.c
index 58b4a661..4ced5afd 100644
--- a/src/isdnhdlc.c
+++ b/src/core/isdnhdlc.c
@@ -18,10 +18,6 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <string.h>
@@ -101,13 +97,12 @@ check_frame(struct osmo_isdnhdlc_vars *hdlc)
When a new flag is found, the complete frame has been received
and the CRC is checked.
If a valid frame is found, the function returns the frame length
- excluding the CRC with the bit HDLC_END_OF_FRAME set.
+ excluding the CRC.
If the beginning of a valid frame is found, the function returns
the length.
If a framing error is found (too many 1s and not a flag) the function
- returns the length with the bit OSMO_HDLC_FRAMING_ERROR set.
- If a CRC error is found the function returns the length with the
- bit OSMO_HDLC_CRC_ERROR set.
+ returns -OSMO_HDLC_FRAMING_ERROR.
+ If a CRC error is found the function returns -OSMO_HDLC_CRC_ERROR.
If the frame length exceeds the destination buffer size, the function
returns the length with the bit OSMO_HDLC_LENGTH_ERROR set.
diff --git a/src/core/it_q.c b/src/core/it_q.c
new file mode 100644
index 00000000..810dc903
--- /dev/null
+++ b/src/core/it_q.c
@@ -0,0 +1,269 @@
+/*! \file it_q.c
+ * Osmocom Inter-Thread queue implementation */
+/* (C) 2019 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved.
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+/*! \addtogroup it_q
+ * @{
+ * Inter-Thread Message Queue.
+ *
+ * This implements a general-purpose queue between threads. It uses
+ * user-provided data types (containing a llist_head as initial member)
+ * as elements in the queue and an eventfd-based notification mechanism.
+ * Hence, it can be used for pretty much anything, including but not
+ * limited to msgbs, including msgb-wrapped osmo_prim.
+ *
+ * The idea is that the sending thread simply calls osmo_it_q_enqueue().
+ * The receiving thread is woken up from its osmo_select_main() loop by eventfd,
+ * and a general osmo_fd callback function for the eventfd will dequeue each item
+ * and call a queue-specific callback function.
+ */
+
+#include "config.h"
+
+#ifdef HAVE_SYS_EVENTFD_H
+
+#include <pthread.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/eventfd.h>
+
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/it_q.h>
+
+/* "increment" the eventfd by specified 'inc' */
+static int eventfd_increment(int fd, uint64_t inc)
+{
+ int rc;
+
+ rc = write(fd, &inc, sizeof(inc));
+ if (rc != sizeof(inc))
+ return -1;
+
+ return 0;
+}
+
+/* global (for all threads) list of message queues in a program + associated lock */
+static LLIST_HEAD(it_queues);
+static pthread_rwlock_t it_queues_rwlock = PTHREAD_RWLOCK_INITIALIZER;
+
+/* resolve it-queue by its [globally unique] name; must be called with rwlock held */
+static struct osmo_it_q *_osmo_it_q_by_name(const char *name)
+{
+ struct osmo_it_q *q;
+ llist_for_each_entry(q, &it_queues, entry) {
+ if (!strcmp(q->name, name))
+ return q;
+ }
+ return NULL;
+}
+
+/*! resolve it-queue by its [globally unique] name */
+struct osmo_it_q *osmo_it_q_by_name(const char *name)
+{
+ struct osmo_it_q *q;
+ pthread_rwlock_rdlock(&it_queues_rwlock);
+ q = _osmo_it_q_by_name(name);
+ pthread_rwlock_unlock(&it_queues_rwlock);
+ return q;
+}
+
+/* osmo_fd call-back when eventfd is readable */
+static int osmo_it_q_fd_cb(struct osmo_fd *ofd, unsigned int what)
+{
+ struct osmo_it_q *q = (struct osmo_it_q *) ofd->data;
+ uint64_t val;
+ int i, rc;
+
+ if (!(what & OSMO_FD_READ))
+ return 0;
+
+ rc = read(ofd->fd, &val, sizeof(val));
+ if (rc < sizeof(val))
+ return rc;
+
+ for (i = 0; i < val; i++) {
+ struct llist_head *item = _osmo_it_q_dequeue(q);
+ /* in case the user might have called osmo_it_q_flush() we may
+ * end up in the eventfd-dispatch but without any messages left in the queue,
+ * otherwise I'd have loved to OSMO_ASSERT(msg) here. */
+ if (!item)
+ break;
+ q->read_cb(q, item);
+ }
+
+ return 0;
+}
+
+/*! Allocate a new inter-thread message queue.
+ * \param[in] ctx talloc context from which to allocate the queue
+ * \param[in] name human-readable string name of the queue; function creates a copy.
+ * \param[in] read_cb call-back function to be called for each de-queued message; may be
+ * NULL in case you don't want eventfd/osmo_select integration and
+ * will manually take care of noticing if and when to dequeue.
+ * \returns a newly-allocated inter-thread message queue; NULL in case of error */
+struct osmo_it_q *osmo_it_q_alloc(void *ctx, const char *name, unsigned int max_length,
+ void (*read_cb)(struct osmo_it_q *q, struct llist_head *item),
+ void *data)
+{
+ struct osmo_it_q *q;
+ int fd;
+
+ q = talloc_zero(ctx, struct osmo_it_q);
+ if (!q)
+ return NULL;
+ q->data = data;
+ q->name = talloc_strdup(q, name);
+ q->current_length = 0;
+ q->max_length = max_length;
+ q->read_cb = read_cb;
+ INIT_LLIST_HEAD(&q->list);
+ pthread_mutex_init(&q->mutex, NULL);
+ q->event_ofd.fd = -1;
+
+ if (q->read_cb) {
+ /* create eventfd *if* the user has provided a read_cb function */
+ fd = eventfd(0, 0);
+ if (fd < 0) {
+ talloc_free(q);
+ return NULL;
+ }
+
+ /* initialize BUT NOT REGISTER the osmo_fd. The receiving thread must
+ * take are to select/poll/read/... on it */
+ osmo_fd_setup(&q->event_ofd, fd, OSMO_FD_READ, osmo_it_q_fd_cb, q, 0);
+ }
+
+ /* add to global list of queues, checking for duplicate names */
+ pthread_rwlock_wrlock(&it_queues_rwlock);
+ if (_osmo_it_q_by_name(q->name)) {
+ pthread_rwlock_unlock(&it_queues_rwlock);
+ if (q->event_ofd.fd >= 0)
+ osmo_fd_close(&q->event_ofd);
+ talloc_free(q);
+ return NULL;
+ }
+ llist_add_tail(&q->entry, &it_queues);
+ pthread_rwlock_unlock(&it_queues_rwlock);
+
+ return q;
+}
+
+static void *item_dequeue(struct llist_head *queue)
+{
+ struct llist_head *lh;
+
+ if (llist_empty(queue))
+ return NULL;
+
+ lh = queue->next;
+ if (lh) {
+ llist_del(lh);
+ return lh;
+ } else
+ return NULL;
+}
+
+/*! Flush all messages currently present in queue */
+static void _osmo_it_q_flush(struct osmo_it_q *q)
+{
+ void *item;
+ while ((item = item_dequeue(&q->list))) {
+ talloc_free(item);
+ }
+ q->current_length = 0;
+}
+
+/*! Flush all messages currently present in queue */
+void osmo_it_q_flush(struct osmo_it_q *q)
+{
+ OSMO_ASSERT(q);
+
+ pthread_mutex_lock(&q->mutex);
+ _osmo_it_q_flush(q);
+ pthread_mutex_unlock(&q->mutex);
+}
+
+/*! Destroy a message queue */
+void osmo_it_q_destroy(struct osmo_it_q *q)
+{
+ OSMO_ASSERT(q);
+
+ /* first remove from global list of queues */
+ pthread_rwlock_wrlock(&it_queues_rwlock);
+ llist_del(&q->entry);
+ pthread_rwlock_unlock(&it_queues_rwlock);
+ /* next, close the eventfd */
+ if (q->event_ofd.fd >= 0)
+ osmo_fd_close(&q->event_ofd);
+ /* flush all messages still present */
+ osmo_it_q_flush(q);
+ pthread_mutex_destroy(&q->mutex);
+ /* and finally release memory */
+ talloc_free(q);
+}
+
+/*! Thread-safe en-queue to an inter-thread message queue.
+ * \param[in] queue Inter-thread queue on which to enqueue
+ * \param[in] item Item to enqueue. Must have llist_head as first member!
+ * \returns 0 on success; negative on error */
+int _osmo_it_q_enqueue(struct osmo_it_q *queue, struct llist_head *item)
+{
+ OSMO_ASSERT(queue);
+ OSMO_ASSERT(item);
+
+ pthread_mutex_lock(&queue->mutex);
+ if (queue->current_length+1 > queue->max_length) {
+ pthread_mutex_unlock(&queue->mutex);
+ return -ENOSPC;
+ }
+ llist_add_tail(item, &queue->list);
+ queue->current_length++;
+ pthread_mutex_unlock(&queue->mutex);
+ /* increment eventfd counter by one */
+ if (queue->event_ofd.fd >= 0)
+ eventfd_increment(queue->event_ofd.fd, 1);
+ return 0;
+}
+
+
+/*! Thread-safe de-queue from an inter-thread message queue.
+ * \param[in] queue Inter-thread queue from which to dequeue
+ * \returns llist_head of dequeued message; NULL if none available
+ */
+struct llist_head *_osmo_it_q_dequeue(struct osmo_it_q *queue)
+{
+ struct llist_head *l;
+ OSMO_ASSERT(queue);
+
+ pthread_mutex_lock(&queue->mutex);
+
+ l = item_dequeue(&queue->list);
+ if (l != NULL)
+ queue->current_length--;
+
+ pthread_mutex_unlock(&queue->mutex);
+
+ return l;
+}
+
+
+#endif /* HAVE_SYS_EVENTFD_H */
+
+/*! @} */
diff --git a/src/core/libosmocore.map b/src/core/libosmocore.map
new file mode 100644
index 00000000..c5ab6e37
--- /dev/null
+++ b/src/core/libosmocore.map
@@ -0,0 +1,631 @@
+LIBOSMOCORE_1.0 {
+global:
+
+assert_loginfo;
+bit_value_to_char;
+bitvec_add_array;
+bitvec_alloc;
+bitvec_fill;
+bitvec_find_bit_pos;
+bitvec_free;
+bitvec_get_bit_high;
+bitvec_get_bit_pos;
+bitvec_get_bit_pos_high;
+bitvec_get_bytes;
+bitvec_get_int16_msb;
+bitvec_get_nth_set_bit;
+bitvec_get_uint;
+bitvec_pack;
+bitvec_read_field;
+bitvec_rl;
+bitvec_rl_curbit;
+bitvec_set_bit;
+bitvec_set_bit_pos;
+bitvec_set_bits;
+bitvec_set_bytes;
+bitvec_set_u64;
+bitvec_set_uint;
+bitvec_shiftl;
+bitvec_spare_padding;
+bitvec_to_string_r;
+bitvec_unhex;
+bitvec_unpack;
+bitvec_write_field;
+bitvec_zero;
+chantype_gsmtap2rsl;
+chantype_rsl2gsmtap;
+chantype_rsl2gsmtap2;
+get_string_value;
+get_value_string;
+get_value_string_or_null;
+gsmtap_gsm_channel_names;
+gsmtap_inst_fd;
+gsmtap_inst_fd2;
+gsmtap_makemsg;
+gsmtap_makemsg_ex;
+gsmtap_send;
+gsmtap_send_ex;
+gsmtap_sendmsg;
+gsmtap_sendmsg_free;
+gsmtap_source_add_sink;
+gsmtap_source_add_sink_fd;
+gsmtap_source_free;
+gsmtap_source_init;
+gsmtap_source_init2;
+gsmtap_source_init_fd;
+gsmtap_source_init_fd2;
+gsmtap_type_names;
+log_add_target;
+log_category_name;
+log_check_level;
+log_cache_enable;
+log_cache_update;
+log_del_target;
+log_enable_multithread;
+log_fini;
+log_init;
+log_level_str;
+loglevel_strs;
+logp;
+logp2;
+log_parse_category;
+log_parse_category_mask;
+log_parse_level;
+logp_stub;
+log_reset_context;
+log_set_all_filter;
+log_set_category_filter;
+log_set_context;
+log_set_log_level;
+log_set_print_category;
+log_set_print_category_hex;
+log_set_print_extended_timestamp;
+log_set_print_filename;
+log_set_print_filename2;
+log_set_print_filename_pos;
+log_set_print_level;
+log_set_print_tid;
+log_set_print_timestamp;
+log_set_use_color;
+log_target_create;
+log_target_create_file;
+log_target_create_file_stream;
+log_target_create_gsmtap;
+log_target_create_rb;
+log_target_create_stderr;
+log_target_create_syslog;
+log_target_create_systemd;
+log_target_destroy;
+log_target_file_reopen;
+log_target_file_switch_to_stream;
+log_target_file_switch_to_wqueue;
+log_target_find;
+log_target_rb_avail_size;
+log_target_rb_get;
+log_target_rb_used_size;
+log_target_systemd_set_raw;
+log_targets_reopen;
+log_tgt_mutex_lock_impl;
+log_tgt_mutex_unlock_impl;
+msgb_alloc;
+msgb_alloc_c;
+msgb_copy;
+msgb_copy_c;
+msgb_copy_resize;
+msgb_copy_resize_c;
+msgb_data;
+msgb_dequeue;
+msgb_enqueue;
+_msgb_eq;
+msgb_free;
+msgb_hexdump;
+msgb_hexdump_buf;
+msgb_hexdump_c;
+msgb_length;
+msgb_printf;
+msgb_reset;
+msgb_resize_area;
+msgb_set_talloc_ctx;
+msgb_talloc_ctx_init;
+osmo_base64_decode;
+osmo_base64_encode;
+osmo_bcd2char;
+osmo_bcd2str;
+osmo_bit_reversal;
+osmo_char2bcd;
+osmo_clock_gettime;
+osmo_clock_override_add;
+osmo_clock_override_enable;
+osmo_clock_override_gettimespec;
+osmo_close_all_fds_above;
+osmo_config_list_parse;
+osmo_constant_time_cmp;
+osmo_conv_decode;
+osmo_conv_decode_acc;
+osmo_conv_decode_deinit;
+osmo_conv_decode_flush;
+osmo_conv_decode_get_best_end_state;
+osmo_conv_decode_get_output;
+osmo_conv_decode_init;
+osmo_conv_decode_reset;
+osmo_conv_decode_rewind;
+osmo_conv_decode_scan;
+osmo_conv_encode;
+osmo_conv_encode_flush;
+osmo_conv_encode_init;
+osmo_conv_encode_load_state;
+osmo_conv_encode_raw;
+osmo_conv_get_input_length;
+osmo_conv_get_output_length;
+osmo_counter_alloc;
+osmo_counter_difference;
+osmo_counter_free;
+osmo_counter_get_by_name;
+osmo_counters_count;
+osmo_counters_for_each;
+osmo_crc16;
+osmo_crc16_ccitt;
+osmo_crc16_ccitt_table;
+osmo_crc16_table;
+osmo_crc16gen_check_bits;
+osmo_crc16gen_compute_bits;
+osmo_crc16gen_set_bits;
+osmo_crc32gen_check_bits;
+osmo_crc32gen_compute_bits;
+osmo_crc32gen_set_bits;
+osmo_crc64gen_check_bits;
+osmo_crc64gen_compute_bits;
+osmo_crc64gen_set_bits;
+osmo_crc8gen_check_bits;
+osmo_crc8gen_compute_bits;
+osmo_crc8gen_set_bits;
+osmo_ctx;
+osmo_ctx_init;
+osmo_daemonize;
+osmo_decode_big_endian;
+osmo_encode_big_endian;
+osmo_environment_append;
+osmo_environment_filter;
+osmo_environment_whitelist;
+osmo_escape_cstr_buf;
+osmo_escape_cstr_c;
+osmo_escape_str;
+osmo_escape_str_buf;
+osmo_escape_str_buf2;
+osmo_escape_str_buf3;
+osmo_escape_str_c;
+osmo_event_for_prim;
+osmo_fd_close;
+osmo_fd_disp_fds;
+osmo_fd_fill_fds;
+osmo_fd_get_by_fd;
+osmo_fd_is_registered;
+osmo_fd_register;
+osmo_fd_setup;
+osmo_fd_unregister;
+osmo_fd_update_when;
+osmo_float_str_to_int;
+osmo_fsm_event_name;
+osmo_fsm_find_by_name;
+osmo_fsm_inst_alloc;
+osmo_fsm_inst_alloc_child;
+_osmo_fsm_inst_broadcast_children;
+osmo_fsm_inst_change_parent;
+_osmo_fsm_inst_dispatch;
+osmo_fsm_inst_find_by_id;
+osmo_fsm_inst_find_by_name;
+osmo_fsm_inst_free;
+osmo_fsm_inst_name;
+_osmo_fsm_inst_state_chg;
+_osmo_fsm_inst_state_chg_keep_or_start_timer;
+_osmo_fsm_inst_state_chg_keep_or_start_timer_ms;
+_osmo_fsm_inst_state_chg_keep_timer;
+_osmo_fsm_inst_state_chg_ms;
+_osmo_fsm_inst_term;
+_osmo_fsm_inst_term_children;
+osmo_fsm_inst_unlink_parent;
+osmo_fsm_inst_update_id;
+osmo_fsm_inst_update_id_f;
+osmo_fsm_inst_update_id_f_sanitize;
+osmo_fsm_log_addr;
+osmo_fsm_log_timeouts;
+osmo_fsm_register;
+osmo_fsm_set_dealloc_ctx;
+osmo_fsm_state_name;
+osmo_fsm_term_cause_names;
+osmo_fsm_term_safely;
+osmo_fsm_unregister;
+osmo_generate_backtrace;
+osmo_get_macaddr;
+osmo_gettid;
+osmo_gettimeofday;
+osmo_gettimeofday_override;
+osmo_gettimeofday_override_add;
+osmo_gettimeofday_override_time;
+osmo_g_fsms;
+osmo_hexdump;
+osmo_hexdump_buf;
+osmo_hexdump_c;
+osmo_hexdump_nospc;
+osmo_hexdump_nospc_c;
+osmo_hexparse;
+osmo_identifier_sanitize_buf;
+osmo_identifier_valid;
+osmo_init_ignore_signals;
+osmo_init_logging;
+osmo_init_logging2;
+osmo_int_to_float_str_buf;
+osmo_int_to_float_str_c;
+osmo_io_backend_names;
+osmo_iofd_close;
+osmo_iofd_free;
+osmo_iofd_get_data;
+osmo_iofd_get_ioops;
+osmo_iofd_get_fd;
+osmo_iofd_get_name;
+osmo_iofd_set_name;
+osmo_iofd_get_priv_nr;
+osmo_iofd_init;
+osmo_iofd_mode_names;
+osmo_iofd_ops;
+osmo_iofd_register;
+osmo_iofd_sendto_msgb;
+osmo_iofd_sctp_send_msgb;
+osmo_iofd_sendmsg_msgb;
+osmo_iofd_set_alloc_info;
+osmo_iofd_set_cmsg_size;
+osmo_iofd_set_data;
+osmo_iofd_set_ioops;
+osmo_iofd_set_priv_nr;
+osmo_iofd_set_txqueue_max_length;
+osmo_iofd_setup;
+osmo_iofd_txqueue_clear;
+osmo_iofd_txqueue_len;
+osmo_iofd_unregister;
+osmo_iofd_uring_init;
+osmo_iofd_notify_connected;
+osmo_iofd_write_msgb;
+osmo_ip_str_type;
+osmo_isdnhdlc_decode;
+osmo_isdnhdlc_encode;
+osmo_isdnhdlc_out_init;
+osmo_isdnhdlc_rcv_init;
+osmo_is_hexstr;
+osmo_isqrt32;
+osmo_it_q_alloc;
+osmo_it_q_by_name;
+_osmo_it_q_dequeue;
+osmo_it_q_destroy;
+_osmo_it_q_enqueue;
+osmo_it_q_flush;
+osmo_log_backtrace;
+osmo_log_info;
+osmo_log_target_list;
+osmo_luhn;
+osmo_macaddr_parse;
+osmo_mnl_destroy;
+osmo_mnl_init;
+osmo_multiaddr_ip_and_port_snprintf;
+osmo_netdev_add_addr;
+osmo_netdev_add_route;
+osmo_netdev_alloc;
+osmo_netdev_free;
+osmo_netdev_get_dev_name;
+osmo_netdev_get_ifindex;
+osmo_netdev_get_name;
+osmo_netdev_get_netns_name;
+osmo_netdev_get_priv_data;
+osmo_netdev_ifupdown;
+osmo_netdev_is_registered;
+osmo_netdev_register;
+osmo_netdev_set_dev_name_chg_cb;
+osmo_netdev_set_ifindex;
+osmo_netdev_set_ifupdown_ind_cb;
+osmo_netdev_set_mtu_chg_cb;
+osmo_netdev_set_netns_name;
+osmo_netdev_set_priv_data;
+osmo_netdev_unregister;
+osmo_netns_open_fd;
+osmo_netns_switch_enter;
+osmo_netns_switch_exit;
+osmo_nibble_shift_left_unal;
+osmo_nibble_shift_right;
+osmo_panic;
+osmo_pbit2ubit;
+osmo_pbit2ubit_ext;
+osmo_plugin_load_all;
+osmo_prbs11;
+osmo_prbs15;
+osmo_prbs7;
+osmo_prbs9;
+osmo_prbs_get_ubit;
+osmo_prbs_get_ubits;
+osmo_prbs_state_init;
+osmo_prim_op_names;
+osmo_print_n;
+osmo_quote_cstr_buf;
+osmo_quote_cstr_c;
+osmo_quote_str;
+osmo_quote_str_buf;
+osmo_quote_str_buf2;
+osmo_quote_str_buf3;
+osmo_quote_str_c;
+osmo_revbytebits_32;
+osmo_revbytebits_8;
+osmo_revbytebits_buf;
+osmo_sbit2ubit;
+osmo_select_init;
+osmo_select_main;
+osmo_select_main_ctx;
+osmo_select_shutdown_done;
+osmo_select_shutdown_request;
+osmo_select_shutdown_requested;
+osmo_separated_identifiers_valid;
+osmo_sercomm_change_speed;
+osmo_sercomm_drv_pull;
+osmo_sercomm_drv_rx_char;
+osmo_sercomm_init;
+osmo_sercomm_initialized;
+osmo_sercomm_register_rx_cb;
+osmo_sercomm_sendmsg;
+osmo_sercomm_tx_queue_depth;
+osmo_serial_clear_custom_baudrate;
+osmo_serial_init;
+osmo_serial_set_baudrate;
+osmo_serial_set_custom_baudrate;
+osmo_serial_speed_t;
+osmo_set_panic_handler;
+osmo_signal_dispatch;
+osmo_signalfd_setup;
+osmo_signal_register_handler;
+osmo_signal_talloc_ctx_init;
+osmo_signal_unregister_handler;
+osmo_sockaddr_cmp;
+osmo_sockaddr_from_octets;
+osmo_sockaddr_from_str_and_uint;
+osmo_sockaddr_in_to_str_and_uint;
+osmo_sockaddr_is_any;
+osmo_sockaddr_is_local;
+osmo_sockaddr_local_ip;
+osmo_sockaddr_netmask_to_prefixlen;
+osmo_sockaddr_ntop;
+osmo_sockaddr_port;
+osmo_sockaddr_set_port;
+osmo_sockaddr_str_cmp;
+osmo_sockaddr_str_from_32;
+osmo_sockaddr_str_from_32h;
+osmo_sockaddr_str_from_32n;
+osmo_sockaddr_str_from_in6_addr;
+osmo_sockaddr_str_from_in_addr;
+osmo_sockaddr_str_from_sockaddr;
+osmo_sockaddr_str_from_sockaddr_in;
+osmo_sockaddr_str_from_sockaddr_in6;
+osmo_sockaddr_str_from_osa;
+osmo_sockaddr_str_from_str;
+osmo_sockaddr_str_from_str2;
+osmo_sockaddr_str_is_nonzero;
+osmo_sockaddr_str_is_set;
+osmo_sockaddr_str_to_32;
+osmo_sockaddr_str_to_32h;
+osmo_sockaddr_str_to_32n;
+osmo_sockaddr_str_to_in6_addr;
+osmo_sockaddr_str_to_in_addr;
+osmo_sockaddr_str_to_sockaddr;
+osmo_sockaddr_str_to_sockaddr_in;
+osmo_sockaddr_str_to_sockaddr_in6;
+osmo_sockaddr_str_to_osa;
+osmo_sockaddr_to_octets;
+osmo_sockaddr_to_str;
+osmo_sockaddr_to_str_and_uint;
+osmo_sockaddr_to_str_buf;
+osmo_sockaddr_to_str_buf2;
+osmo_sockaddr_to_str_c;
+osmo_sock_get_ip_and_port;
+osmo_sock_get_local_ip;
+osmo_sock_get_local_ip_port;
+osmo_sock_get_name;
+osmo_sock_get_name2;
+osmo_sock_get_name2_c;
+osmo_sock_get_name_buf;
+osmo_sock_get_remote_ip;
+osmo_sock_get_remote_ip_port;
+osmo_sock_init;
+osmo_sock_init2;
+osmo_sock_init2_multiaddr;
+osmo_sock_init2_multiaddr2;
+osmo_sock_init2_ofd;
+osmo_sock_init_ofd;
+osmo_sock_init_osa;
+osmo_sock_init_osa_ofd;
+osmo_sock_init_sa;
+osmo_sock_local_ip;
+osmo_sock_mcast_all_set;
+osmo_sock_mcast_iface_set;
+osmo_sock_mcast_loop_set;
+osmo_sock_mcast_subscribe;
+osmo_sock_mcast_ttl_set;
+osmo_sock_multiaddr_add_local_addr;
+osmo_sock_multiaddr_del_local_addr;
+osmo_sock_multiaddr_get_ip_and_port;
+osmo_sock_multiaddr_get_name_buf;
+osmo_sock_sctp_get_peer_addr_info;
+osmo_sock_set_dscp;
+osmo_sock_set_priority;
+osmo_sock_unix_init;
+osmo_sock_unix_init_ofd;
+osmo_soft_uart_default_cfg;
+osmo_soft_uart_alloc;
+osmo_soft_uart_free;
+osmo_soft_uart_configure;
+osmo_soft_uart_get_name;
+osmo_soft_uart_set_name;
+osmo_soft_uart_set_rx;
+osmo_soft_uart_set_tx;
+osmo_soft_uart_rx_ubits;
+osmo_soft_uart_tx_ubits;
+osmo_soft_uart_get_status;
+osmo_soft_uart_set_status;
+osmo_soft_uart_set_status_line;
+osmo_soft_uart_flush_rx;
+osmo_stat_item_dec;
+osmo_stat_item_flush;
+osmo_stat_item_for_each_group;
+osmo_stat_item_for_each_item;
+osmo_stat_item_get_by_name;
+osmo_stat_item_get_desc;
+osmo_stat_item_get_group_by_name_idx;
+osmo_stat_item_get_group_by_name_idxname;
+osmo_stat_item_get_last;
+osmo_stat_item_group_alloc;
+osmo_stat_item_group_free;
+osmo_stat_item_group_get_item;
+osmo_stat_item_group_reset;
+osmo_stat_item_group_set_name;
+osmo_stat_item_inc;
+osmo_stat_item_init;
+osmo_stat_item_reset;
+osmo_stat_item_set;
+osmo_stats_config;
+osmo_stats_init;
+osmo_stats_report;
+osmo_stats_reporter_alloc;
+osmo_stats_reporter_create_log;
+osmo_stats_reporter_create_statsd;
+osmo_stats_reporter_disable;
+osmo_stats_reporter_enable;
+osmo_stats_reporter_find;
+osmo_stats_reporter_free;
+osmo_stats_reporter_list;
+osmo_stats_reporter_send;
+osmo_stats_reporter_send_buffer;
+osmo_stats_reporter_set_flush_period;
+osmo_stats_reporter_set_local_addr;
+osmo_stats_reporter_set_max_class;
+osmo_stats_reporter_set_mtu;
+osmo_stats_reporter_set_name_prefix;
+osmo_stats_reporter_set_remote_addr;
+osmo_stats_reporter_set_remote_port;
+osmo_stats_reporter_udp_close;
+osmo_stats_reporter_udp_open;
+osmo_stats_set_interval;
+osmo_stats_tcp_osmo_fd_register;
+osmo_stats_tcp_osmo_fd_unregister;
+osmo_stats_tcp_set_interval;
+osmo_stderr_target;
+osmo_str2bcd;
+osmo_str2lower;
+osmo_str2upper;
+osmo_strlcpy;
+osmo_strnchr;
+osmo_strrb_add;
+osmo_strrb_create;
+osmo_strrb_elements;
+osmo_strrb_get_nth;
+_osmo_strrb_is_bufindex_valid;
+osmo_strrb_is_empty;
+osmo_strbuf_drop_tail;
+osmo_strbuf_added_tail;
+osmo_str_startswith;
+osmo_str_to_int;
+osmo_str_to_int64;
+osmo_str_tolower;
+osmo_str_tolower_buf;
+osmo_str_tolower_c;
+osmo_str_toupper;
+osmo_str_toupper_buf;
+osmo_str_toupper_c;
+osmo_system_nowait;
+osmo_system_nowait2;
+osmo_t4_encode;
+osmo_talloc_replace_string_fmt;
+osmo_tcp_stats_config;
+_osmo_tdef_fsm_inst_state_chg;
+osmo_tdef_get;
+osmo_tdef_get_entry;
+osmo_tdef_get_state_timeout;
+osmo_tdef_range_str_buf;
+osmo_tdef_set;
+osmo_tdefs_reset;
+osmo_tdef_unit_names;
+osmo_tdef_val_in_range;
+osmo_time_cc_cleanup;
+osmo_time_cc_init;
+osmo_time_cc_set_flag;
+osmo_timer_add;
+osmo_timer_del;
+osmo_timerfd_disable;
+osmo_timerfd_schedule;
+osmo_timerfd_setup;
+osmo_timer_pending;
+osmo_timer_remaining;
+osmo_timers_check;
+osmo_timer_schedule;
+osmo_timer_setup;
+osmo_timers_nearest;
+osmo_timers_nearest_ms;
+osmo_timers_prepare;
+osmo_timers_update;
+osmo_tundev_alloc;
+osmo_tundev_close;
+osmo_tundev_free;
+osmo_tundev_get_dev_name;
+osmo_tundev_get_name;
+osmo_tundev_get_netdev;
+osmo_tundev_get_netns_name;
+osmo_tundev_get_priv_data;
+osmo_tundev_is_open;
+osmo_tundev_open;
+osmo_tundev_send;
+osmo_tundev_set_data_ind_cb;
+osmo_tundev_set_dev_name;
+osmo_tundev_set_netns_name;
+osmo_tundev_set_priv_data;
+osmo_ubit2pbit;
+osmo_ubit2pbit_ext;
+osmo_ubit2sbit;
+osmo_ubit_dump;
+osmo_ubit_dump_buf;
+osmo_use_count_by;
+osmo_use_count_find;
+osmo_use_count_free;
+_osmo_use_count_get_put;
+osmo_use_count_make_static_entries;
+osmo_use_count_name_buf;
+osmo_use_count_to_str_buf;
+osmo_use_count_to_str_c;
+osmo_use_count_total;
+osmo_vlogp;
+osmo_wqueue_bfd_cb;
+osmo_wqueue_clear;
+osmo_wqueue_enqueue;
+osmo_wqueue_enqueue_quiet;
+osmo_wqueue_set_maxlen;
+osmo_wqueue_init;
+rate_ctr_add;
+rate_ctr_difference;
+rate_ctr_for_each_counter;
+rate_ctr_for_each_group;
+rate_ctr_get_by_name;
+rate_ctr_get_group_by_name_idx;
+rate_ctr_group_alloc;
+rate_ctr_group_free;
+rate_ctr_group_get_ctr;
+rate_ctr_group_reset;
+rate_ctr_group_set_name;
+rate_ctr_init;
+rate_ctr_reset;
+rb_erase;
+rb_first;
+rb_insert_color;
+rb_last;
+rb_next;
+rb_prev;
+rb_replace_node;
+sercomm_drv_lock;
+sercomm_drv_unlock;
+tall_ctr_ctx; /* deprecated */
+tall_log_ctx;
+tall_msgb_ctx; /* deprecated */
+
+local: *;
+};
diff --git a/src/logging.c b/src/core/logging.c
index d60d6e4f..e1721241 100644
--- a/src/logging.c
+++ b/src/core/logging.c
@@ -17,10 +17,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
/*! \addtogroup logging
@@ -29,12 +25,13 @@
*
* \file logging.c */
-#include "../config.h"
+#include "config.h"
#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
+#include <unistd.h>
#ifdef HAVE_STRINGS_H
#include <strings.h>
@@ -44,8 +41,22 @@
#include <syslog.h>
#endif
+#ifdef HAVE_SYSTEMTAP
+/* include the generated probes header and put markers in code */
+#include "probes.h"
+#define TRACE(probe) probe
+#define TRACE_ENABLED(probe) probe ## _ENABLED()
+#else
+/* Wrap the probe to allow it to be removed when no systemtap available */
+#define TRACE(probe)
+#define TRACE_ENABLED(probe) (0)
+#endif /* HAVE_SYSTEMTAP */
+
#include <time.h>
#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
#include <errno.h>
#include <pthread.h>
@@ -53,12 +64,19 @@
#include <osmocom/core/utils.h>
#include <osmocom/core/logging.h>
#include <osmocom/core/timer.h>
+#include <osmocom/core/thread.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/write_queue.h>
+#include <osmocom/core/gsmtap_util.h>
#include <osmocom/vty/logging.h> /* for LOGGING_STR. */
/* maximum length of the log string of a single log event (typically line) */
#define MAX_LOG_SIZE 4096
+/* maximum number of log statements we queue in file/stderr target write queue */
+#define LOG_WQUEUE_LEN 156
+
osmo_static_assert(_LOG_CTX_COUNT <= ARRAY_SIZE(((struct log_context*)NULL)->ctx),
enum_logging_ctx_items_fit_in_struct_log_context);
osmo_static_assert(_LOG_FLT_COUNT <= ARRAY_SIZE(((struct log_target*)NULL)->filter_data),
@@ -72,7 +90,83 @@ static struct log_context log_context;
void *tall_log_ctx = NULL;
LLIST_HEAD(osmo_log_target_list);
+static __thread long int logging_tid;
+
#if (!EMBEDDED)
+/*! One global copy that contains the union of log levels for all targets
+* for all categories, used for quick lock free checks of log targets. */
+static volatile uint8_t *log_level_lookup_cache;
+
+/*! Updates cache for all targets for all categies, caller must hold osmo_log_tgt_mutex. */
+static void log_cache_update_all(void)
+{
+ struct log_target *tgt;
+ uint8_t tmp_en[osmo_log_info->num_cat];
+ uint8_t tmp_level[osmo_log_info->num_cat];
+
+ if (!log_level_lookup_cache)
+ return;
+
+ memset(tmp_en, 0, osmo_log_info->num_cat);
+ memset(tmp_level, UINT8_MAX, osmo_log_info->num_cat);
+
+ /* values can also decrease.. */
+ llist_for_each_entry(tgt, &osmo_log_target_list, entry) {
+ for (int i = 0; i < osmo_log_info->num_cat; i++) {
+ struct log_category *cat = &tgt->categories[i];
+ tmp_en[i] = OSMO_MAX(tmp_en[i], cat->enabled);
+ tmp_level[i] = OSMO_MIN(tmp_level[i], cat->loglevel);
+ tmp_level[i] = tgt->loglevel ? OSMO_MIN(tmp_level[i], tgt->loglevel) : tmp_level[i];
+ }
+ }
+
+ for (int i = 0; i < osmo_log_info->num_cat; i++)
+ log_level_lookup_cache[i] = tmp_en[i] ? tmp_level[i] : UINT8_MAX;
+}
+
+/*! Updates single cache entry, caller must hold osmo_log_tgt_mutex.
+ *
+ * \param[in] mapped_subsys plain category index (after mapping)
+ * \param[in] enabled log category enabled?
+ * \param[in] level log level
+ */
+void log_cache_update(int mapped_subsys, uint8_t enabled, uint8_t level)
+{
+ struct log_target *tgt;
+ struct log_category tmp = { UINT8_MAX, 0 };
+
+ if (!log_level_lookup_cache)
+ return;
+
+ /* values can also decrease.. */
+ llist_for_each_entry(tgt, &osmo_log_target_list, entry) {
+ struct log_category *cat = &tgt->categories[mapped_subsys];
+ tmp.enabled = OSMO_MAX(tmp.enabled, cat->enabled);
+ tmp.loglevel = OSMO_MIN(tmp.loglevel, cat->loglevel);
+ tmp.loglevel = tgt->loglevel ? OSMO_MIN(tmp.loglevel, tgt->loglevel) : tmp.loglevel;
+ }
+ tmp.enabled = OSMO_MAX(tmp.enabled, enabled);
+ tmp.loglevel = OSMO_MIN(tmp.loglevel, level);
+
+ log_level_lookup_cache[mapped_subsys] = tmp.enabled ? tmp.loglevel : UINT8_MAX;
+}
+
+/*! Queries log level cache.
+ *
+ * \param[in] mapped_subsys plain category index (after mapping)
+ * \param[in] level log level
+ * \returns true if logging should happen for at least one log target
+*/
+static bool log_cache_check(int mapped_subsys, int level)
+{
+ if (!log_level_lookup_cache) {
+ /* log-cache is not enabled, so we simply behave like we did before the cache */
+ return true;
+ }
+
+ return (level < log_level_lookup_cache[mapped_subsys]) ? false : true;
+}
+
/*! This mutex must be held while using osmo_log_target_list or any of its
log_targets in a multithread program. Prevents race conditions between threads
like producing unordered timestamps or VTY deleting a target while another
@@ -131,6 +225,7 @@ const struct value_string loglevel_strs[] = {
{ 0, NULL },
};
+/* 256 color palette see https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit */
#define INT2IDX(x) (-1*(x)-1)
static const struct log_info_cat internal_cat[OSMO_NUM_DLIB] = {
[INT2IDX(DLGLOBAL)] = { /* -1 becomes 0 */
@@ -144,99 +239,159 @@ static const struct log_info_cat internal_cat[OSMO_NUM_DLIB] = {
.description = "LAPD in libosmogsm",
.loglevel = LOGL_NOTICE,
.enabled = 1,
+ .color = "\033[38;5;12m",
},
[INT2IDX(DLINP)] = {
.name = "DLINP",
.description = "A-bis Intput Subsystem",
.loglevel = LOGL_NOTICE,
.enabled = 1,
+ .color = "\033[38;5;23m",
},
[INT2IDX(DLMUX)] = {
.name = "DLMUX",
.description = "A-bis B-Subchannel TRAU Frame Multiplex",
.loglevel = LOGL_NOTICE,
.enabled = 1,
+ .color = "\033[38;5;25m",
},
[INT2IDX(DLMI)] = {
.name = "DLMI",
.description = "A-bis Input Driver for Signalling",
.enabled = 0, .loglevel = LOGL_NOTICE,
+ .color = "\033[38;5;27m",
},
[INT2IDX(DLMIB)] = {
.name = "DLMIB",
.description = "A-bis Input Driver for B-Channels (voice)",
.enabled = 0, .loglevel = LOGL_NOTICE,
+ .color = "\033[38;5;29m",
},
[INT2IDX(DLSMS)] = {
.name = "DLSMS",
.description = "Layer3 Short Message Service (SMS)",
.enabled = 1, .loglevel = LOGL_NOTICE,
- .color = OSMO_LOGCOLOR_BRIGHTWHITE,
+ .color = "\033[38;5;31m",
},
[INT2IDX(DLCTRL)] = {
.name = "DLCTRL",
.description = "Control Interface",
.enabled = 1, .loglevel = LOGL_NOTICE,
+ .color = "\033[38;5;33m",
},
[INT2IDX(DLGTP)] = {
.name = "DLGTP",
.description = "GPRS GTP library",
.enabled = 1, .loglevel = LOGL_NOTICE,
+ .color = "\033[38;5;35m",
},
[INT2IDX(DLSTATS)] = {
.name = "DLSTATS",
.description = "Statistics messages and logging",
.enabled = 1, .loglevel = LOGL_NOTICE,
+ .color = "\033[38;5;37m",
},
[INT2IDX(DLGSUP)] = {
.name = "DLGSUP",
.description = "Generic Subscriber Update Protocol",
.enabled = 1, .loglevel = LOGL_NOTICE,
+ .color = "\033[38;5;39m",
},
[INT2IDX(DLOAP)] = {
.name = "DLOAP",
.description = "Osmocom Authentication Protocol",
.enabled = 1, .loglevel = LOGL_NOTICE,
+ .color = "\033[38;5;41m",
},
[INT2IDX(DLSS7)] = {
.name = "DLSS7",
.description = "libosmo-sigtran Signalling System 7",
.enabled = 1, .loglevel = LOGL_NOTICE,
+ .color = "\033[38;5;43m",
},
[INT2IDX(DLSCCP)] = {
.name = "DLSCCP",
.description = "libosmo-sigtran SCCP Implementation",
.enabled = 1, .loglevel = LOGL_NOTICE,
+ .color = "\033[38;5;45m",
},
[INT2IDX(DLSUA)] = {
.name = "DLSUA",
.description = "libosmo-sigtran SCCP User Adaptation",
.enabled = 1, .loglevel = LOGL_NOTICE,
+ .color = "\033[38;5;47m",
},
[INT2IDX(DLM3UA)] = {
.name = "DLM3UA",
.description = "libosmo-sigtran MTP3 User Adaptation",
.enabled = 1, .loglevel = LOGL_NOTICE,
+ .color = "\033[38;5;49m",
},
[INT2IDX(DLMGCP)] = {
.name = "DLMGCP",
.description = "libosmo-mgcp Media Gateway Control Protocol",
.enabled = 1, .loglevel = LOGL_NOTICE,
+ .color = "\033[38;5;51m",
},
[INT2IDX(DLJIBUF)] = {
.name = "DLJIBUF",
.description = "libosmo-netif Jitter Buffer",
.enabled = 1, .loglevel = LOGL_NOTICE,
+ .color = "\033[38;5;53m",
},
[INT2IDX(DLRSPRO)] = {
.name = "DLRSPRO",
.description = "Remote SIM protocol",
.enabled = 1, .loglevel = LOGL_NOTICE,
+ .color = "\033[38;5;55m",
},
[INT2IDX(DLNS)] = {
.name = "DLNS",
.description = "GPRS NS layer",
.enabled = 1, .loglevel = LOGL_NOTICE,
+ .color = "\033[38;5;57m",
+ },
+ [INT2IDX(DLBSSGP)] = {
+ .name = "DLBSSGP",
+ .description = "GPRS BSSGP layer",
+ .enabled = 1, .loglevel = LOGL_NOTICE,
+ .color = "\033[38;5;59m",
+ },
+ [INT2IDX(DLNSDATA)] = {
+ .name = "DLNSDATA",
+ .description = "GPRS NS layer data PDU",
+ .enabled = 1, .loglevel = LOGL_NOTICE,
+ .color = "\033[38;5;61m",
+ },
+ [INT2IDX(DLNSSIGNAL)] = {
+ .name = "DLNSSIGNAL",
+ .description = "GPRS NS layer signal PDU",
+ .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",
+ },
+ [INT2IDX(DLPFCP)] = {
+ .name = "DLPFCP",
+ .description = "libosmo-pfcp Packet Forwarding Control Protocol",
+ .enabled = 1, .loglevel = LOGL_NOTICE,
+ .color = "\033[38;5;51m",
+ },
+ [INT2IDX(DLCSN1)] = {
+ .name = "DLCSN1",
+ .description = "libosmo-csn1 Concrete Syntax Notation 1 codec",
+ .enabled = 1, .loglevel = LOGL_NOTICE,
+ .color = "\033[38;5;11m",
+ },
+ [INT2IDX(DLIO)] = {
+ .name = "DLIO",
+ .description = "libosmocore IO Subsystem",
+ .enabled = 1, .loglevel = LOGL_NOTICE,
+ .color = "\033[38;5;67m",
},
};
@@ -345,6 +500,10 @@ void log_parse_category_mask(struct log_target* target, const char *_mask)
}
} while ((category_token = strtok(NULL, ":")));
+#if !defined(EMBEDDED)
+ log_cache_update_all();
+#endif
+
free(mask);
}
@@ -389,23 +548,34 @@ static const char *const_basename(const char *path)
return bn + 1;
}
-static void _output(struct log_target *target, unsigned int subsys,
- unsigned int level, const char *file, int line, int cont,
- const char *format, va_list ap)
+/*! main output formatting function for log lines.
+ * \param[out] buf caller-allocated output buffer for the generated string
+ * \param[in] buf_len number of bytes available in buf
+ * \param[in] target log target for which the string is to be formatted
+ * \param[in] subsys Log sub-system number
+ * \param[in] level Log level
+ * \param[in] file name of source code file generating the log
+ * \param[in] line line source code line number within 'file' generating the log
+ * \param[in] cont is this a continuation (true) or not (false)
+ * \param[in] format format string
+ * \param[in] ap variable argument list for format
+ * \returns number of bytes written to out */
+static int _output_buf(char *buf, int buf_len, struct log_target *target, unsigned int subsys,
+ unsigned int level, const char *file, int line, int cont,
+ const char *format, va_list ap)
{
- char buf[MAX_LOG_SIZE];
- int ret, len = 0, offset = 0, rem = sizeof(buf);
+ int ret;
const char *c_subsys = NULL;
+ struct osmo_strbuf sb = { .buf = buf, .pos = buf, .len = buf_len };
+
+ /* safety net in case of encountering errors and returning nothing */
+ buf[0] = '\0';
/* are we using color */
if (target->use_color) {
c_subsys = color(subsys);
- if (c_subsys) {
- ret = snprintf(buf + offset, rem, "%s", c_subsys);
- if (ret < 0)
- goto err;
- OSMO_SNPRINTF_RET(ret, rem, offset, len);
- }
+ if (c_subsys)
+ OSMO_STRBUF_PRINTF(sb, "%s", c_subsys);
}
if (!cont) {
if (target->print_ext_timestamp) {
@@ -414,13 +584,10 @@ static void _output(struct log_target *target, unsigned int subsys,
struct timeval tv;
osmo_gettimeofday(&tv, NULL);
localtime_r(&tv.tv_sec, &tm);
- ret = snprintf(buf + offset, rem, "%04d%02d%02d%02d%02d%02d%03d ",
- tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
- tm.tm_hour, tm.tm_min, tm.tm_sec,
- (int)(tv.tv_usec / 1000));
- if (ret < 0)
- goto err;
- OSMO_SNPRINTF_RET(ret, rem, offset, len);
+ OSMO_STRBUF_PRINTF(sb, "%04d%02d%02d%02d%02d%02d%03d ",
+ tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
+ tm.tm_hour, tm.tm_min, tm.tm_sec,
+ (int)(tv.tv_usec / 1000));
#endif
} else if (target->print_timestamp) {
time_t tm;
@@ -428,100 +595,95 @@ static void _output(struct log_target *target, unsigned int subsys,
goto err;
/* Get human-readable representation of time.
man ctime: we need at least 26 bytes in buf */
- if (rem < 26 || !ctime_r(&tm, buf + offset))
+ if (OSMO_STRBUF_REMAIN(sb) < 26 || !ctime_r(&tm, sb.pos))
goto err;
- ret = strlen(buf + offset);
+ ret = strnlen(sb.pos, 26);
if (ret <= 0)
goto err;
+ OSMO_STRBUF_ADDED_TAIL(sb, ret);
/* Get rid of useless final '\n' added by ctime_r. We want a space instead. */
- buf[offset + ret - 1] = ' ';
- OSMO_SNPRINTF_RET(ret, rem, offset, len);
- }
- if (target->print_category) {
- ret = snprintf(buf + offset, rem, "%s%s%s%s ",
- target->use_color ? level_color(level) : "",
- log_category_name(subsys),
- target->use_color ? OSMO_LOGCOLOR_END : "",
- c_subsys ? c_subsys : "");
- if (ret < 0)
- goto err;
- OSMO_SNPRINTF_RET(ret, rem, offset, len);
+ OSMO_STRBUF_DROP_TAIL(sb, 1);
+ OSMO_STRBUF_PRINTF(sb, " ");
}
- if (target->print_level) {
- ret = snprintf(buf + offset, rem, "%s%s%s%s ",
- target->use_color ? level_color(level) : "",
- log_level_str(level),
- target->use_color ? OSMO_LOGCOLOR_END : "",
- c_subsys ? c_subsys : "");
- if (ret < 0)
- goto err;
- OSMO_SNPRINTF_RET(ret, rem, offset, len);
- }
- if (target->print_category_hex) {
- ret = snprintf(buf + offset, rem, "<%4.4x> ", subsys);
- if (ret < 0)
- goto err;
- OSMO_SNPRINTF_RET(ret, rem, offset, len);
+ if (target->print_tid) {
+ if (logging_tid == 0)
+ logging_tid = (long int)osmo_gettid();
+ OSMO_STRBUF_PRINTF(sb, "%ld ", logging_tid);
}
+ if (target->print_category)
+ OSMO_STRBUF_PRINTF(sb, "%s%s%s%s ",
+ target->use_color ? level_color(level) : "",
+ log_category_name(subsys),
+ target->use_color ? OSMO_LOGCOLOR_END : "",
+ c_subsys ? c_subsys : "");
+ if (target->print_level)
+ OSMO_STRBUF_PRINTF(sb, "%s%s%s%s ",
+ target->use_color ? level_color(level) : "",
+ log_level_str(level),
+ target->use_color ? OSMO_LOGCOLOR_END : "",
+ c_subsys ? c_subsys : "");
+ if (target->print_category_hex)
+ OSMO_STRBUF_PRINTF(sb, "<%4.4x> ", subsys);
if (target->print_filename_pos == LOG_FILENAME_POS_HEADER_END) {
switch (target->print_filename2) {
case LOG_FILENAME_NONE:
break;
case LOG_FILENAME_PATH:
- ret = snprintf(buf + offset, rem, "%s:%d ", file, line);
- if (ret < 0)
- goto err;
- OSMO_SNPRINTF_RET(ret, rem, offset, len);
+ OSMO_STRBUF_PRINTF(sb, "%s:%d ", file, line);
break;
case LOG_FILENAME_BASENAME:
- ret = snprintf(buf + offset, rem, "%s:%d ", const_basename(file), line);
- if (ret < 0)
- goto err;
- OSMO_SNPRINTF_RET(ret, rem, offset, len);
+ OSMO_STRBUF_PRINTF(sb, "%s:%d ", const_basename(file), line);
break;
}
}
}
- ret = vsnprintf(buf + offset, rem, format, ap);
- if (ret < 0)
- goto err;
- OSMO_SNPRINTF_RET(ret, rem, offset, len);
+ OSMO_STRBUF_APPEND(sb, vsnprintf, format, ap);
/* For LOG_FILENAME_POS_LAST, print the source file info only when the caller ended the log
* message in '\n'. If so, nip the last '\n' away, insert the source file info and re-append an
* '\n'. All this to allow LOGP("start..."); LOGPC("...end\n") constructs. */
if (target->print_filename_pos == LOG_FILENAME_POS_LINE_END
- && offset > 0 && buf[offset-1] == '\n') {
+ && sb.pos > sb.buf && *(sb.pos - 1) == '\n') {
switch (target->print_filename2) {
case LOG_FILENAME_NONE:
break;
case LOG_FILENAME_PATH:
- offset --;
- ret = snprintf(buf + offset, rem, " (%s:%d)\n", file, line);
- if (ret < 0)
- goto err;
- OSMO_SNPRINTF_RET(ret, rem, offset, len);
+ OSMO_STRBUF_DROP_TAIL(sb, 1);
+ OSMO_STRBUF_PRINTF(sb, " (%s:%d)\n", file, line);
break;
case LOG_FILENAME_BASENAME:
- offset --;
- ret = snprintf(buf + offset, rem, " (%s:%d)\n", const_basename(file), line);
- if (ret < 0)
- goto err;
- OSMO_SNPRINTF_RET(ret, rem, offset, len);
+ OSMO_STRBUF_DROP_TAIL(sb, 1);
+ OSMO_STRBUF_PRINTF(sb, " (%s:%d)\n", const_basename(file), line);
break;
}
}
if (target->use_color && c_subsys) {
- ret = snprintf(buf + offset, rem, OSMO_LOGCOLOR_END);
- if (ret < 0)
- goto err;
- OSMO_SNPRINTF_RET(ret, rem, offset, len);
+ /* Ensure the last color escape is sent before the newline
+ * (to not clobber journald, which works on single-lines only) */
+ if (sb.pos > sb.buf && *(sb.pos - 1) == '\n') {
+ OSMO_STRBUF_DROP_TAIL(sb, 1);
+ OSMO_STRBUF_PRINTF(sb, OSMO_LOGCOLOR_END "\n");
+ } else {
+ OSMO_STRBUF_PRINTF(sb, OSMO_LOGCOLOR_END);
+ }
}
err:
- buf[sizeof(buf)-1] = '\0';
- target->output(target, level, buf);
+ return OSMO_STRBUF_CHAR_COUNT(sb);
+}
+
+/* Format the log line for given target; use a stack buffer and call target->output */
+static void _output(struct log_target *target, unsigned int subsys,
+ unsigned int level, const char *file, int line, int cont,
+ const char *format, va_list ap)
+{
+ char buf[MAX_LOG_SIZE];
+ int rc;
+
+ rc = _output_buf(buf, sizeof(buf), target, subsys, level, file, line, cont, format, ap);
+ if (rc > 0)
+ target->output(target, level, buf);
}
/* Catch internal logging category indexes as well as out-of-bounds indexes.
@@ -596,6 +758,11 @@ void osmo_vlogp(int subsys, int level, const char *file, int line,
subsys = map_subsys(subsys);
+#if !defined(EMBEDDED)
+ if (!log_cache_check(subsys, level))
+ return;
+#endif
+
log_tgt_mutex_lock();
llist_for_each_entry(tar, &osmo_log_target_list, entry) {
@@ -645,9 +812,23 @@ void logp2(int subsys, unsigned int level, const char *file, int line, int cont,
{
va_list ap;
+ TRACE(LIBOSMOCORE_LOG_START());
va_start(ap, format);
osmo_vlogp(subsys, level, file, line, cont, format, ap);
va_end(ap);
+ TRACE(LIBOSMOCORE_LOG_DONE());
+}
+
+/* This logging function is used as a fallback when the logging framework is
+ * not is not properly initialized. */
+void logp_stub(const char *file, int line, int cont, const char *format, ...)
+{
+ va_list ap;
+ if (!cont)
+ fprintf(stderr, "%s:%d ", file, line);
+ va_start(ap, format);
+ vfprintf(stderr, format, ap);
+ va_end(ap);
}
/*! Register a new log target with the logging core
@@ -656,6 +837,9 @@ void logp2(int subsys, unsigned int level, const char *file, int line, int cont,
void log_add_target(struct log_target *target)
{
llist_add_tail(&target->entry, &osmo_log_target_list);
+#if (!EMBEDDED)
+ log_cache_update_all();
+#endif
}
/*! Unregister a log target from the logging core
@@ -664,6 +848,9 @@ void log_add_target(struct log_target *target)
void log_del_target(struct log_target *target)
{
llist_del(&target->entry);
+#if (!EMBEDDED)
+ log_cache_update_all();
+#endif
}
/*! Reset (clear) the logging context */
@@ -740,6 +927,15 @@ void log_set_print_extended_timestamp(struct log_target *target, int print_times
target->print_ext_timestamp = print_timestamp;
}
+/*! Enable or disable printing of timestamps while logging
+ * \param[in] target Log target to be affected
+ * \param[in] print_tid Enable (1) or disable (0) Thread ID logging
+ */
+void log_set_print_tid(struct log_target *target, int print_tid)
+{
+ target->print_tid = print_tid;
+}
+
/*! Use log_set_print_filename2() instead.
* Call log_set_print_filename2() with LOG_FILENAME_PATH or LOG_FILENAME_NONE, *as well as* call
* log_set_print_category_hex() with the argument passed to this function. This is to mirror legacy
@@ -815,6 +1011,9 @@ void log_set_print_level(struct log_target *target, int print_level)
void log_set_log_level(struct log_target *target, int log_level)
{
target->loglevel = log_level;
+#if !defined(EMBEDDED)
+ log_cache_update_all();
+#endif
}
/*! Set a category filter on a given log target
@@ -831,15 +1030,73 @@ void log_set_category_filter(struct log_target *target, int category,
category = map_subsys(category);
target->categories[category].enabled = !!enable;
target->categories[category].loglevel = level;
+
+#if !defined(EMBEDDED)
+ log_cache_update(category, !!enable, level);
+#endif
}
#if (!EMBEDDED)
-static void _file_output(struct log_target *target, unsigned int level,
+/* write-queue tells us we should write another msgb (log line) to the output fd */
+static int _file_wq_write_cb(struct osmo_fd *ofd, struct msgb *msg)
+{
+ int rc;
+
+ rc = write(ofd->fd, msgb_data(msg), msgb_length(msg));
+ if (rc < 0)
+ return rc;
+ if (rc != msgb_length(msg)) {
+ /* pull the number of bytes we have already written */
+ msgb_pull(msg, rc);
+ /* ask write_queue to re-insert the msgb at the head of the queue */
+ return -EAGAIN;
+ }
+ return 0;
+}
+
+/* output via buffered, blocking stdio streams */
+static void _file_output_stream(struct log_target *target, unsigned int level,
const char *log)
{
- fprintf(target->tgt_file.out, "%s", log);
+ OSMO_ASSERT(target->tgt_file.out);
+ fputs(log, target->tgt_file.out);
fflush(target->tgt_file.out);
}
+
+/* output via non-blocking write_queue, doing internal buffering */
+static void _file_raw_output(struct log_target *target, int subsys, unsigned int level, const char *file,
+ int line, int cont, const char *format, va_list ap)
+{
+ struct msgb *msg;
+ int rc;
+
+ OSMO_ASSERT(target->tgt_file.wqueue);
+ msg = msgb_alloc_c(target->tgt_file.wqueue, MAX_LOG_SIZE, "log_file_msg");
+ if (!msg)
+ return;
+
+ /* we simply enqueue the log message to a write queue here, to avoid any blocking
+ * writes on the output file. The write queue will tell us once the file is writable
+ * and call _file_wq_write_cb() */
+ rc = _output_buf((char *)msgb_data(msg), msgb_tailroom(msg), target, subsys, level, file, line, cont, format, ap);
+ msgb_put(msg, rc);
+
+ /* attempt a synchronous, non-blocking write, if the write queue is empty */
+ if (target->tgt_file.wqueue->current_length == 0) {
+ rc = _file_wq_write_cb(&target->tgt_file.wqueue->bfd, msg);
+ if (rc == 0) {
+ /* the write was complete, we can exit early */
+ msgb_free(msg);
+ return;
+ }
+ }
+ /* if we reach here, either we already had elements in the write_queue, or the synchronous write
+ * failed: enqueue the message to the write_queue (backlog) */
+ if (osmo_wqueue_enqueue_quiet(target->tgt_file.wqueue, msg) < 0) {
+ msgb_free(msg);
+ /* TODO: increment some counter so we can see that messages were dropped */
+ }
+}
#endif
/*! Create a new log target skeleton
@@ -879,11 +1136,21 @@ struct log_target *log_target_create(void)
/* global settings */
target->use_color = 1;
target->print_timestamp = 0;
+ target->print_tid = 0;
target->print_filename2 = LOG_FILENAME_PATH;
target->print_category_hex = true;
/* global log level */
target->loglevel = 0;
+
+#if !defined(EMBEDDED)
+ /* update cache */
+ for (i = 0; i < osmo_log_info->num_cat; i++) {
+ const struct log_info_cat *c = &osmo_log_info->cat[i];
+ log_cache_update(i, c->enabled, c->loglevel);
+ }
+#endif
+
return target;
}
@@ -901,7 +1168,7 @@ struct log_target *log_target_create_stderr(void)
target->type = LOG_TGT_TYPE_STDERR;
target->tgt_file.out = stderr;
- target->output = _file_output;
+ target->output = _file_output_stream;
return target;
#else
return NULL;
@@ -909,11 +1176,11 @@ struct log_target *log_target_create_stderr(void)
}
#if (!EMBEDDED)
-/*! Create a new file-based log target
+/*! Create a new file-based log target using buffered, blocking stream output
* \param[in] fname File name of the new log file
* \returns Log target in case of success, NULL otherwise
*/
-struct log_target *log_target_create_file(const char *fname)
+struct log_target *log_target_create_file_stream(const char *fname)
{
struct log_target *target;
@@ -927,9 +1194,165 @@ struct log_target *log_target_create_file(const char *fname)
log_target_destroy(target);
return NULL;
}
+ target->output = _file_output_stream;
+ target->tgt_file.fname = talloc_strdup(target, fname);
+
+ return target;
+}
+
+/*! switch from non-blocking/write-queue to blocking + buffered stream output
+ * \param[in] target log target which we should switch
+ * \return 0 on success; 1 if already switched before; negative on error
+ * Must be called with mutex osmo_log_tgt_mutex held, see log_tgt_mutex_lock.
+ */
+int log_target_file_switch_to_stream(struct log_target *target)
+{
+ struct osmo_wqueue *wq;
+
+ if (!target)
+ return -ENODEV;
+
+ if (target->tgt_file.out) {
+ /* target has already been switched over */
+ return 1;
+ }
+
+ wq = target->tgt_file.wqueue;
+ OSMO_ASSERT(wq);
+
+ /* re-open output as stream */
+ if (target->type == LOG_TGT_TYPE_STDERR)
+ target->tgt_file.out = stderr;
+ else
+ target->tgt_file.out = fopen(target->tgt_file.fname, "a");
+ if (!target->tgt_file.out) {
+ return -EIO;
+ }
+
+ /* synchronously write anything left in the queue */
+ while (!llist_empty(&wq->msg_queue)) {
+ struct msgb *msg = msgb_dequeue(&wq->msg_queue);
+ fwrite(msgb_data(msg), msgb_length(msg), 1, target->tgt_file.out);
+ msgb_free(msg);
+ }
+
+ /* now that everything succeeded, we can finally close the old output fd */
+ if (target->type == LOG_TGT_TYPE_FILE) {
+ osmo_fd_unregister(&wq->bfd);
+ close(wq->bfd.fd);
+ wq->bfd.fd = -1;
+ }
+
+ /* release the queue itself */
+ talloc_free(wq);
+ target->tgt_file.wqueue = NULL;
+ target->output = _file_output_stream;
+ target->raw_output = NULL;
+
+ return 0;
+}
+
+/*! switch from blocking + buffered file output to non-blocking write-queue based output.
+ * \param[in] target log target which we should switch
+ * \return 0 on success; 1 if already switched before; negative on error
+ * Must be called with mutex osmo_log_tgt_mutex held, see log_tgt_mutex_lock.
+ */
+int log_target_file_switch_to_wqueue(struct log_target *target)
+{
+ struct osmo_wqueue *wq;
+ int rc;
+
+ if (!target)
+ return -ENODEV;
+
+ if (!target->tgt_file.out) {
+ /* target has already been switched over */
+ return 1;
+ }
+
+ /* we create a ~640kB sized talloc pool within the write-queue to ensure individual
+ * log lines (stored as msgbs) will not put result in malloc() calls, and also to
+ * reduce the OOM probability within logging, as the pool is already allocated */
+ wq = talloc_pooled_object(target, struct osmo_wqueue, LOG_WQUEUE_LEN,
+ LOG_WQUEUE_LEN*(sizeof(struct msgb)+MAX_LOG_SIZE));
+ if (!wq)
+ return -ENOMEM;
+ osmo_wqueue_init(wq, LOG_WQUEUE_LEN);
+
+ fflush(target->tgt_file.out);
+ if (target->type == LOG_TGT_TYPE_FILE) {
+ rc = open(target->tgt_file.fname, O_WRONLY|O_APPEND|O_CREAT|O_NONBLOCK, 0660);
+ if (rc < 0) {
+ talloc_free(wq);
+ return -errno;
+ }
+ } else {
+ rc = STDERR_FILENO;
+ }
+ wq->bfd.fd = rc;
+ wq->bfd.when = OSMO_FD_WRITE;
+ wq->write_cb = _file_wq_write_cb;
+
+ rc = osmo_fd_register(&wq->bfd);
+ if (rc < 0) {
+ talloc_free(wq);
+ return -EIO;
+ }
+ target->tgt_file.wqueue = wq;
+ target->raw_output = _file_raw_output;
+ target->output = NULL;
+
+ /* now that everything succeeded, we can finally close the old output stream */
+ if (target->type == LOG_TGT_TYPE_FILE)
+ fclose(target->tgt_file.out);
+ target->tgt_file.out = NULL;
+
+ return 0;
+}
+
+/*! Create a new file-based log target using non-blocking write_queue
+ * \param[in] fname File name of the new log file
+ * \returns Log target in case of success, NULL otherwise
+ */
+struct log_target *log_target_create_file(const char *fname)
+{
+ struct log_target *target;
+ struct osmo_wqueue *wq;
+ int rc;
+
+ target = log_target_create();
+ if (!target)
+ return NULL;
+
+ target->type = LOG_TGT_TYPE_FILE;
+ /* we create a ~640kB sized talloc pool within the write-queue to ensure individual
+ * log lines (stored as msgbs) will not put result in malloc() calls, and also to
+ * reduce the OOM probability within logging, as the pool is already allocated */
+ wq = talloc_pooled_object(target, struct osmo_wqueue, LOG_WQUEUE_LEN,
+ LOG_WQUEUE_LEN*(sizeof(struct msgb)+MAX_LOG_SIZE));
+ if (!wq) {
+ log_target_destroy(target);
+ return NULL;
+ }
+ osmo_wqueue_init(wq, LOG_WQUEUE_LEN);
+ wq->bfd.fd = open(fname, O_WRONLY|O_APPEND|O_CREAT|O_NONBLOCK, 0660);
+ if (wq->bfd.fd < 0) {
+ talloc_free(wq);
+ log_target_destroy(target);
+ return NULL;
+ }
+ wq->bfd.when = OSMO_FD_WRITE;
+ wq->write_cb = _file_wq_write_cb;
- target->output = _file_output;
+ rc = osmo_fd_register(&wq->bfd);
+ if (rc < 0) {
+ talloc_free(wq);
+ log_target_destroy(target);
+ return NULL;
+ }
+ target->tgt_file.wqueue = wq;
+ target->raw_output = _file_raw_output;
target->tgt_file.fname = talloc_strdup(target, fname);
return target;
@@ -942,7 +1365,7 @@ struct log_target *log_target_create_file(const char *fname)
* \returns Log target (if found), NULL otherwise
* Must be called with mutex osmo_log_tgt_mutex held, see log_tgt_mutex_lock.
*/
-struct log_target *log_target_find(int type, const char *fname)
+struct log_target *log_target_find(enum log_target_type type, const char *fname)
{
struct log_target *tgt;
@@ -969,17 +1392,36 @@ struct log_target *log_target_find(int type, const char *fname)
* \param[in] target log target to unregister, close and delete */
void log_target_destroy(struct log_target *target)
{
-
/* just in case, to make sure we don't have any references */
log_del_target(target);
#if (!EMBEDDED)
+ struct osmo_wqueue *wq;
switch (target->type) {
case LOG_TGT_TYPE_FILE:
- if (target->tgt_file.out == NULL)
- break;
- fclose(target->tgt_file.out);
- target->tgt_file.out = NULL;
+ case LOG_TGT_TYPE_STDERR:
+ if (target->tgt_file.out) {
+ if (target->type == LOG_TGT_TYPE_FILE)
+ fclose(target->tgt_file.out);
+ target->tgt_file.out = NULL;
+ }
+ wq = target->tgt_file.wqueue;
+ if (wq) {
+ if (wq->bfd.fd >= 0) {
+ osmo_fd_unregister(&wq->bfd);
+ if (target->type == LOG_TGT_TYPE_FILE)
+ close(wq->bfd.fd);
+ wq->bfd.fd = -1;
+ }
+ osmo_wqueue_clear(wq);
+ talloc_free(wq);
+ target->tgt_file.wqueue = NULL;
+ }
+ talloc_free((void *)target->tgt_file.fname);
+ target->tgt_file.fname = NULL;
+ break;
+ case LOG_TGT_TYPE_GSMTAP:
+ gsmtap_source_free(target->tgt_gsmtap.gsmtap_inst);
break;
#ifdef HAVE_SYSLOG_H
case LOG_TGT_TYPE_SYSLOG:
@@ -1000,13 +1442,33 @@ void log_target_destroy(struct log_target *target)
* \returns 0 in case of success; negative otherwise */
int log_target_file_reopen(struct log_target *target)
{
- fclose(target->tgt_file.out);
+ struct osmo_wqueue *wq;
+ int rc;
- target->tgt_file.out = fopen(target->tgt_file.fname, "a");
- if (!target->tgt_file.out)
- return -errno;
+ OSMO_ASSERT(target->type == LOG_TGT_TYPE_FILE || target->type == LOG_TGT_TYPE_STDERR);
+ OSMO_ASSERT(target->tgt_file.out || target->tgt_file.wqueue);
- /* we assume target->output already to be set */
+ if (target->tgt_file.out) {
+ fclose(target->tgt_file.out);
+ target->tgt_file.out = fopen(target->tgt_file.fname, "a");
+ if (!target->tgt_file.out)
+ return -errno;
+ } else {
+ wq = target->tgt_file.wqueue;
+ if (wq->bfd.fd >= 0) {
+ osmo_fd_unregister(&wq->bfd);
+ close(wq->bfd.fd);
+ wq->bfd.fd = -1;
+ }
+
+ rc = open(target->tgt_file.fname, O_WRONLY|O_APPEND|O_CREAT|O_NONBLOCK, 0660);
+ if (rc < 0)
+ return -errno;
+ wq->bfd.fd = rc;
+ rc = osmo_fd_register(&wq->bfd);
+ if (rc < 0)
+ return rc;
+ }
return 0;
}
@@ -1036,6 +1498,31 @@ int log_targets_reopen(void)
return rc;
}
+/*! Enable the log level lookup cache to bypass string formatting and other code for log statements which are
+ * not actually enabled/needed by any existing log target.
+ * \retruns 0 in case of success, negative -errno in case of error. */
+int log_cache_enable(void)
+{
+#if !defined(EMBEDDED)
+ if (log_level_lookup_cache)
+ return -EEXIST;
+
+ log_level_lookup_cache = talloc_zero_array(osmo_log_info, uint8_t, osmo_log_info->num_cat);
+ if (!log_level_lookup_cache)
+ return -ENOMEM;
+
+ /* copy everything for level lookup cache */
+ log_tgt_mutex_lock();
+ log_cache_update_all();
+ log_tgt_mutex_unlock();
+
+ return 0;
+#else
+ return -ENOTSUP;
+#endif
+}
+
+
/*! Initialize the Osmocom logging core
* \param[in] inf Information regarding logging categories, could be NULL
* \param[in] ctx talloc context for logging allocations
@@ -1129,6 +1616,11 @@ int log_check_level(int subsys, unsigned int level)
subsys = map_subsys(subsys);
+#if !defined(EMBEDDED)
+ if (!log_cache_check(subsys, level))
+ return 0;
+#endif
+
/* TODO: The following could/should be cached (update on config) */
log_tgt_mutex_lock();
diff --git a/src/logging_gsmtap.c b/src/core/logging_gsmtap.c
index bd642715..7775c27d 100644
--- a/src/logging_gsmtap.c
+++ b/src/core/logging_gsmtap.c
@@ -21,23 +21,20 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
/*! \addtogroup logging
* @{
* \file logging_gsmtap.c */
-#include "../config.h"
+#include "config.h"
#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
+#include <unistd.h>
#ifdef HAVE_STRINGS_H
#include <strings.h>
@@ -50,9 +47,12 @@
#include <osmocom/core/logging.h>
#include <osmocom/core/timer.h>
#include <osmocom/core/byteswap.h>
+#include <osmocom/core/thread.h>
#define GSMTAP_LOG_MAX_SIZE 4096
+static __thread uint32_t logging_gsmtap_tid;
+
static void _gsmtap_raw_output(struct log_target *target, int subsys,
unsigned int level, const char *file,
int line, int cont, const char *format,
@@ -65,6 +65,7 @@ static void _gsmtap_raw_output(struct log_target *target, int subsys,
struct timeval tv;
int rc;
const char *file_basename;
+ unsigned int _level;
/* get timestamp ASAP */
osmo_gettimeofday(&tv, NULL);
@@ -82,6 +83,9 @@ static void _gsmtap_raw_output(struct log_target *target, int subsys,
/* Logging header */
golh = (struct gsmtap_osmocore_log_hdr *) msgb_put(msg, sizeof(*golh));
OSMO_STRLCPY_ARRAY(golh->proc_name, target->tgt_gsmtap.ident);
+ if (logging_gsmtap_tid == 0)
+ osmo_store32be((uint32_t)osmo_gettid(), &logging_gsmtap_tid);
+ golh->pid = logging_gsmtap_tid;
if (subsys_name)
OSMO_STRLCPY_ARRAY(golh->subsys, subsys_name + 1);
else
@@ -111,7 +115,11 @@ static void _gsmtap_raw_output(struct log_target *target, int subsys,
}
msgb_put(msg, rc);
+ /* Ensure that any error occurring when sending the log message doesn't cause infinite recursion */
+ _level = target->loglevel;
+ target->loglevel = UINT8_MAX;
rc = gsmtap_sendmsg(target->tgt_gsmtap.gsmtap_inst, msg);
+ target->loglevel = _level;
if (rc)
msgb_free(msg);
}
diff --git a/src/logging_syslog.c b/src/core/logging_syslog.c
index f980689d..1153bdf4 100644
--- a/src/logging_syslog.c
+++ b/src/core/logging_syslog.c
@@ -16,17 +16,13 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
/*! \addtogroup logging
* @{
* \file logging_syslog.c */
-#include "../config.h"
+#include "config.h"
#ifdef HAVE_SYSLOG_H
diff --git a/src/logging_systemd.c b/src/core/logging_systemd.c
index 7c966863..2e86feb6 100644
--- a/src/logging_systemd.c
+++ b/src/core/logging_systemd.c
@@ -14,10 +14,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
/*! \addtogroup logging
diff --git a/src/loggingrb.c b/src/core/loggingrb.c
index 4a80cc8a..2bf7b665 100644
--- a/src/loggingrb.c
+++ b/src/core/loggingrb.c
@@ -16,10 +16,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
/*! \addtogroup loggingrb
diff --git a/src/macaddr.c b/src/core/macaddr.c
index 56fdf86e..3b231fb8 100644
--- a/src/macaddr.c
+++ b/src/core/macaddr.c
@@ -18,10 +18,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
/*! \addtogroup utils
diff --git a/src/core/mnl.c b/src/core/mnl.c
new file mode 100644
index 00000000..d148e1b3
--- /dev/null
+++ b/src/core/mnl.c
@@ -0,0 +1,111 @@
+/*! \file mnl.c
+ *
+ * This code integrates libmnl (minimal netlink library) into the osmocom select
+ * loop abstraction. It allows other osmocom libraries or application code to
+ * create netlink sockets and subscribe to netlink events via libmnl. The completion
+ * handler / callbacks are dispatched via libosmocore select loop handling.
+ */
+
+/*
+ * (C) 2020 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved.
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <osmocom/core/select.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/mnl.h>
+
+#include <libmnl/libmnl.h>
+
+#include <errno.h>
+#include <string.h>
+
+/* osmo_fd call-back for when RTNL socket is readable */
+static int osmo_mnl_fd_cb(struct osmo_fd *ofd, unsigned int what)
+{
+ uint8_t buf[MNL_SOCKET_BUFFER_SIZE];
+ struct osmo_mnl *omnl = ofd->data;
+ int rc;
+
+ if (!(what & OSMO_FD_READ))
+ return 0;
+
+ rc = mnl_socket_recvfrom(omnl->mnls, buf, sizeof(buf));
+ if (rc <= 0) {
+ LOGP(DLGLOBAL, LOGL_ERROR, "Error in mnl_socket_recvfrom(): %s\n",
+ strerror(errno));
+ return -EIO;
+ }
+
+ return mnl_cb_run(buf, rc, 0, 0, omnl->mnl_cb, omnl);
+}
+
+/*! create an osmocom-wrapped limnl netlink socket.
+ * \param[in] ctx talloc context from which to allocate
+ * \param[in] bus netlink socket bus ID (see NETLINK_* constants)
+ * \param[in] groups groups of messages to bind/subscribe to
+ * \param[in] mnl_cb callback function called for each incoming message
+ * \param[in] priv opaque private user data
+ * \returns newly-allocated osmo_mnl or NULL in case of error. */
+struct osmo_mnl *osmo_mnl_init(void *ctx, int bus, unsigned int groups, mnl_cb_t mnl_cb, void *priv)
+{
+ struct osmo_mnl *olm = talloc_zero(ctx, struct osmo_mnl);
+
+ if (!olm)
+ return NULL;
+
+ olm->priv = priv;
+ olm->mnl_cb = mnl_cb;
+ olm->mnls = mnl_socket_open(bus);
+ if (!olm->mnls) {
+ LOGP(DLGLOBAL, LOGL_ERROR, "Error creating netlink socket for bus %d: %s\n",
+ bus, strerror(errno));
+ goto out_free;
+ }
+
+ if (mnl_socket_bind(olm->mnls, groups, MNL_SOCKET_AUTOPID) < 0) {
+ LOGP(DLGLOBAL, LOGL_ERROR, "Error binding netlink socket for bus %d to groups 0x%x: %s\n",
+ bus, groups, strerror(errno));
+ goto out_close;
+ }
+
+ osmo_fd_setup(&olm->ofd, mnl_socket_get_fd(olm->mnls), OSMO_FD_READ, osmo_mnl_fd_cb, olm, 0);
+
+ if (osmo_fd_register(&olm->ofd)) {
+ LOGP(DLGLOBAL, LOGL_ERROR, "Error registering netlinks socket\n");
+ goto out_close;
+ }
+
+ return olm;
+
+out_close:
+ mnl_socket_close(olm->mnls);
+out_free:
+ talloc_free(olm);
+ return NULL;
+}
+
+/*! destroy an existing osmocom-wrapped mnl netlink socket: Unregister + close + free.
+ * \param[in] omnl osmo_mnl socket previously returned by osmo_mnl_init() */
+void osmo_mnl_destroy(struct osmo_mnl *omnl)
+{
+ if (!omnl)
+ return;
+
+ osmo_fd_unregister(&omnl->ofd);
+ mnl_socket_close(omnl->mnls);
+ talloc_free(omnl);
+}
diff --git a/src/msgb.c b/src/core/msgb.c
index fbb157ae..713510c6 100644
--- a/src/msgb.c
+++ b/src/core/msgb.c
@@ -14,10 +14,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
/*! \addtogroup msgb
@@ -318,24 +314,29 @@ void *msgb_talloc_ctx_init(void *root_ctx, unsigned int pool_size)
return tall_msgb_ctx;
}
-/*! Copy an msgb.
+/*! Copy an msgb with memory reallocation.
*
- * This function allocates a new msgb, copies the data buffer of msg,
- * and adjusts the pointers (incl l1h-l4h) accordingly. The cb part
- * is not copied.
+ * This function allocates a new msgb with new_len size, copies the data buffer of msg,
+ * and adjusts the pointers (incl l1h-l4h) accordingly. The cb part is not copied.
+ * \param[in] ctx talloc context on which allocation happens
* \param[in] msg The old msgb object
- * \param[in] name Human-readable name to be associated with msgb
+ * \param[in] new_len The length of new msgb object
+ * \param[in] name Human-readable name to be associated with new msgb
*/
-struct msgb *msgb_copy_c(const void *ctx, const struct msgb *msg, const char *name)
+struct msgb *msgb_copy_resize_c(const void *ctx, const struct msgb *msg, uint16_t new_len, const char *name)
{
struct msgb *new_msg;
- new_msg = msgb_alloc_c(ctx, msg->data_len, name);
- if (!new_msg)
+ if (new_len < msgb_length(msg)) {
+ LOGP(DLGLOBAL, LOGL_ERROR,
+ "Data from old msgb (%u bytes) won't fit into new msgb (%u bytes) after reallocation\n",
+ msgb_length(msg), new_len);
return NULL;
+ }
- /* copy data */
- memcpy(new_msg->_data, msg->_data, new_msg->data_len);
+ new_msg = msgb_alloc_c(ctx, new_len, name);
+ if (!new_msg)
+ return NULL;
/* copy header */
new_msg->len = msg->len;
@@ -343,6 +344,9 @@ struct msgb *msgb_copy_c(const void *ctx, const struct msgb *msg, const char *na
new_msg->head += msg->head - msg->_data;
new_msg->tail += msg->tail - msg->_data;
+ /* copy data */
+ memcpy(new_msg->data, msg->data, msgb_length(msg));
+
if (msg->l1h)
new_msg->l1h = new_msg->_data + (msg->l1h - msg->_data);
if (msg->l2h)
@@ -355,6 +359,32 @@ struct msgb *msgb_copy_c(const void *ctx, const struct msgb *msg, const char *na
return new_msg;
}
+/*! Copy an msgb with memory reallocation.
+ *
+ * This function allocates a new msgb with new_len size, copies the data buffer of msg,
+ * and adjusts the pointers (incl l1h-l4h) accordingly. The cb part is not copied.
+ * \param[in] msg The old msgb object
+ * \param[in] name Human-readable name to be associated with new msgb
+ */
+struct msgb *msgb_copy_resize(const struct msgb *msg, uint16_t new_len, const char *name)
+{
+ return msgb_copy_resize_c(tall_msgb_ctx, msg, new_len, name);
+}
+
+/*! Copy an msgb.
+ *
+ * This function allocates a new msgb, copies the data buffer of msg,
+ * and adjusts the pointers (incl l1h-l4h) accordingly. The cb part
+ * is not copied.
+ * \param[in] ctx talloc context on which allocation happens
+ * \param[in] msg The old msgb object
+ * \param[in] name Human-readable name to be associated with msgb
+ */
+struct msgb *msgb_copy_c(const void *ctx, const struct msgb *msg, const char *name)
+{
+ return msgb_copy_resize_c(ctx, msg, msg->data_len, name);
+}
+
/*! Copy an msgb.
*
* This function allocates a new msgb, copies the data buffer of msg,
@@ -430,11 +460,11 @@ int msgb_resize_area(struct msgb *msg, uint8_t *area,
*/
char *msgb_hexdump_buf(char *buf, size_t buf_len, const struct msgb *msg)
{
- int buf_offs = 0;
+ unsigned int buf_offs = 0;
int nchars;
const unsigned char *start = msg->data;
const unsigned char *lxhs[4];
- int i;
+ unsigned int i;
lxhs[0] = msg->l1h;
lxhs[1] = msg->l2h;
diff --git a/src/msgfile.c b/src/core/msgfile.c
index 1f11aa60..abb4e7cf 100644
--- a/src/msgfile.c
+++ b/src/core/msgfile.c
@@ -17,10 +17,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#define _WITH_GETLINE
diff --git a/src/core/netdev.c b/src/core/netdev.c
new file mode 100644
index 00000000..318134af
--- /dev/null
+++ b/src/core/netdev.c
@@ -0,0 +1,962 @@
+
+/* network device (interface) functions.
+ * (C) 2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * Author: Pau Espin Pedrol <pespin@sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include "config.h"
+
+/*! \addtogroup netdev
+ * @{
+ * network device (interface) convenience functions
+ *
+ * \file netdev.c
+ *
+ * Example lifecycle use of the API:
+ *
+ * struct osmo_sockaddr_str osa_str = {};
+ * struct osmo_sockaddr osa = {};
+ *
+ * // Allocate object:
+ * struct osmo_netdev *netdev = osmo_netdev_alloc(parent_talloc_ctx, name);
+ * OSMO_ASSERT(netdev);
+ *
+ * // Configure object (before registration):
+ * rc = osmo_netdev_set_netns_name(netdev, "some_netns_name_or_null");
+ * rc = osmo_netdev_set_ifindex(netdev, if_nametoindex("eth0"));
+ *
+ * // Register object:
+ * rc = osmo_netdev_register(netdev);
+ * // The network interface is now being monitored and the network interface
+ * // can be operated (see below)
+ *
+ * // Add a local IPv4 address:
+ * rc = osmo_sockaddr_str_from_str2(&osa_str, "192.168.200.1");
+ * rc = osmo_sockaddr_str_to_sockaddr(&osa_str, &osa.u.sas);
+ * rc = osmo_netdev_add_addr(netdev, &osa, 24);
+ *
+ * // Bring network interface up:
+ * rc = osmo_netdev_ifupdown(netdev, true);
+ *
+ * // Add default route (0.0.0.0/0):
+ * rc = osmo_sockaddr_str_from_str2(&osa_str, "0.0.0.0");
+ * rc = osmo_sockaddr_str_to_sockaddr(&osa_str, &osa.u.sas);
+ * rc = osmo_netdev_add_route(netdev, &osa, 0, NULL);
+ *
+ * // Unregister (can be freed directly too):
+ * rc = osmo_netdev_unregister(netdev);
+ * // Free the object:
+ * osmo_netdev_free(netdev);
+ */
+
+#if (!EMBEDDED)
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+#include <ifaddrs.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <net/if.h>
+#include <net/route.h>
+
+#if defined(__linux__)
+#include <linux/if_link.h>
+#include <linux/rtnetlink.h>
+#else
+#error "Unknown platform!"
+#endif
+
+#include <osmocom/core/utils.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/core/netns.h>
+#include <osmocom/core/netdev.h>
+
+#if ENABLE_LIBMNL
+#include <osmocom/core/mnl.h>
+#endif
+
+#define IFINDEX_UNUSED 0
+
+#define LOGNETDEV(netdev, lvl, fmt, args ...) \
+ LOGP(DLGLOBAL, lvl, "NETDEV(%s,if=%s/%u,ns=%s): " fmt, \
+ (netdev)->name, osmo_netdev_get_dev_name(netdev) ? : "", \
+ (netdev)->ifindex, (netdev)->netns_name ? : "", ## args)
+
+static struct llist_head g_netdev_netns_ctx_list = LLIST_HEAD_INIT(g_netdev_netns_ctx_list);
+static struct llist_head g_netdev_list = LLIST_HEAD_INIT(g_netdev_list);
+
+/* One per netns, shared by all osmo_netdev in a given netns: */
+struct netdev_netns_ctx {
+ struct llist_head entry; /* entry in g_netdev_netns_ctx_list */
+ unsigned int refcount; /* Number of osmo_netdev currently registered on this netns */
+ const char *netns_name; /* default netns has empty string "" (never NULL!) */
+ int netns_fd; /* FD to the netns with name "netns_name" above */
+#if ENABLE_LIBMNL
+ struct osmo_mnl *omnl;
+#endif
+};
+
+static struct netdev_netns_ctx *netdev_netns_ctx_alloc(void *ctx, const char *netns_name)
+{
+ struct netdev_netns_ctx *netns_ctx;
+ OSMO_ASSERT(netns_name);
+
+ netns_ctx = talloc_zero(ctx, struct netdev_netns_ctx);
+ if (!netns_ctx)
+ return NULL;
+
+ netns_ctx->netns_name = talloc_strdup(netns_ctx, netns_name);
+ netns_ctx->netns_fd = -1;
+
+ llist_add_tail(&netns_ctx->entry, &g_netdev_netns_ctx_list);
+ return netns_ctx;
+
+}
+
+static void netdev_netns_ctx_free(struct netdev_netns_ctx *netns_ctx)
+{
+ if (!netns_ctx)
+ return;
+
+ llist_del(&netns_ctx->entry);
+
+#if ENABLE_LIBMNL
+ if (netns_ctx->omnl) {
+ osmo_mnl_destroy(netns_ctx->omnl);
+ netns_ctx->omnl = NULL;
+ }
+#endif
+
+ if (netns_ctx->netns_fd != -1) {
+ close(netns_ctx->netns_fd);
+ netns_ctx->netns_fd = -1;
+ }
+ talloc_free(netns_ctx);
+}
+
+#if ENABLE_LIBMNL
+static int netdev_netns_ctx_mnl_cb(const struct nlmsghdr *nlh, void *data);
+#endif
+
+static int netdev_netns_ctx_init(struct netdev_netns_ctx *netns_ctx)
+{
+ struct osmo_netns_switch_state switch_state;
+ int rc;
+
+ if (netns_ctx->netns_name[0] != '\0') {
+ LOGP(DLGLOBAL, LOGL_INFO, "Prepare netns: Switch to netns '%s'\n", netns_ctx->netns_name);
+ netns_ctx->netns_fd = osmo_netns_open_fd(netns_ctx->netns_name);
+ if (netns_ctx->netns_fd < 0) {
+ LOGP(DLGLOBAL, LOGL_ERROR, "Prepare netns: Cannot switch to netns '%s': %s (%d)\n",
+ netns_ctx->netns_name, strerror(errno), errno);
+ return netns_ctx->netns_fd;
+ }
+
+ /* temporarily switch to specified namespace to create netlink socket */
+ rc = osmo_netns_switch_enter(netns_ctx->netns_fd, &switch_state);
+ if (rc < 0) {
+ LOGP(DLGLOBAL, LOGL_ERROR, "Prepare netns: Cannot switch to netns '%s': %s (%d)\n",
+ netns_ctx->netns_name, strerror(errno), errno);
+ /* netns_ctx->netns_fd will be freed by future call to netdev_netns_ctx_free() */
+ return rc;
+ }
+ }
+
+#if ENABLE_LIBMNL
+ netns_ctx->omnl = osmo_mnl_init(NULL, NETLINK_ROUTE, RTMGRP_LINK, netdev_netns_ctx_mnl_cb, netns_ctx);
+ rc = (netns_ctx->omnl ? 0 : -EFAULT);
+#else
+ rc = 0;
+#endif
+
+ /* switch back to default namespace */
+ if (netns_ctx->netns_name[0] != '\0') {
+ int rc2 = osmo_netns_switch_exit(&switch_state);
+ if (rc2 < 0) {
+ LOGP(DLGLOBAL, LOGL_ERROR, "Prepare netns: Cannot switch back from netns '%s': %s\n",
+ netns_ctx->netns_name, strerror(errno));
+ return rc2;
+ }
+ LOGP(DLGLOBAL, LOGL_INFO, "Prepare netns: Back from netns '%s'\n",
+ netns_ctx->netns_name);
+ }
+ return rc;
+}
+
+static struct netdev_netns_ctx *netdev_netns_ctx_find_by_netns_name(const char *netns_name)
+{
+ struct netdev_netns_ctx *netns_ctx;
+
+ llist_for_each_entry(netns_ctx, &g_netdev_netns_ctx_list, entry) {
+ if (strcmp(netns_ctx->netns_name, netns_name))
+ continue;
+ return netns_ctx;
+ }
+
+ return NULL;
+}
+
+static struct netdev_netns_ctx *netdev_netns_ctx_get(const char *netns_name)
+{
+ struct netdev_netns_ctx *netns_ctx;
+ int rc;
+
+ OSMO_ASSERT(netns_name);
+ netns_ctx = netdev_netns_ctx_find_by_netns_name(netns_name);
+ if (!netns_ctx) {
+ netns_ctx = netdev_netns_ctx_alloc(NULL, netns_name);
+ if (!netns_ctx)
+ return NULL;
+ rc = netdev_netns_ctx_init(netns_ctx);
+ if (rc < 0) {
+ netdev_netns_ctx_free(netns_ctx);
+ return NULL;
+ }
+ }
+ netns_ctx->refcount++;
+ return netns_ctx;
+}
+
+static void netdev_netns_ctx_put(struct netdev_netns_ctx *netns_ctx)
+{
+ OSMO_ASSERT(netns_ctx);
+ netns_ctx->refcount--;
+
+ if (netns_ctx->refcount == 0)
+ netdev_netns_ctx_free(netns_ctx);
+}
+
+struct osmo_netdev {
+ /* entry in g_netdev_list */
+ struct llist_head entry;
+
+ /* Pointer to struct shared (refcounted) by all osmo_netdev in the same netns: */
+ struct netdev_netns_ctx *netns_ctx;
+
+ /* Name used to identify the osmo_netdev */
+ char *name;
+
+ /* ifindex of the network interface (address space is per netns) */
+ unsigned int ifindex;
+
+ /* Network interface name. Can change over lifetime of the interface. */
+ char *dev_name;
+
+ /* netns name where the netdev interface is created (NULL = default netns) */
+ char *netns_name;
+
+ /* API user private data */
+ void *priv_data;
+
+ /* Whether the netdev is in operation (managing the netdev interface) */
+ bool registered;
+
+ /* Called by netdev each time a new up/down state change is detected. Can be NULL. */
+ osmo_netdev_ifupdown_ind_cb_t ifupdown_ind_cb;
+
+ /* Called by netdev each time the registered network interface is renamed by the system. Can be NULL. */
+ osmo_netdev_dev_name_chg_cb_t dev_name_chg_cb;
+
+ /* Called by netdev each time the configured MTU changes in registered network interface. Can be NULL. */
+ osmo_netdev_mtu_chg_cb_t mtu_chg_cb;
+
+ /* Whether the netdev interface is UP */
+ bool if_up;
+ /* Whether we know the interface updown state (aka if if_up holds information)*/
+ bool if_up_known;
+
+ /* The netdev interface MTU size */
+ uint32_t if_mtu;
+ /* Whether we know the interface MTU size (aka if if_mtu holds information)*/
+ bool if_mtu_known;
+};
+
+#define NETDEV_NETNS_ENTER(netdev, switch_state, str_prefix) \
+ do { \
+ if ((netdev)->netns_name) { \
+ LOGNETDEV(netdev, LOGL_DEBUG, str_prefix ": Switch to netns '%s'\n", \
+ (netdev)->netns_name); \
+ int rc2 = osmo_netns_switch_enter((netdev)->netns_ctx->netns_fd, switch_state); \
+ if (rc2 < 0) { \
+ LOGNETDEV(netdev, LOGL_ERROR, str_prefix ": Cannot switch to netns '%s': %s (%d)\n", \
+ (netdev)->netns_name, strerror(errno), errno); \
+ return -EACCES; \
+ } \
+ } \
+ } while (0)
+
+#define NETDEV_NETNS_EXIT(netdev, switch_state, str_prefix) \
+ do { \
+ if ((netdev)->netns_name) { \
+ int rc2 = osmo_netns_switch_exit(switch_state); \
+ if (rc2 < 0) { \
+ LOGNETDEV(netdev, LOGL_ERROR, str_prefix ": Cannot switch back from netns '%s': %s\n", \
+ (netdev)->netns_name, strerror(errno)); \
+ return rc2; \
+ } \
+ LOGNETDEV(netdev, LOGL_DEBUG, str_prefix ": Back from netns '%s'\n", \
+ (netdev)->netns_name); \
+ } \
+ } while (0)
+
+#if ENABLE_LIBMNL
+/* validate the netlink attributes */
+static int netdev_mnl_data_attr_cb(const struct nlattr *attr, void *data)
+{
+ const struct nlattr **tb = data;
+ int type = mnl_attr_get_type(attr);
+
+ if (mnl_attr_type_valid(attr, IFLA_MAX) < 0)
+ return MNL_CB_OK;
+
+ switch (type) {
+ case IFLA_ADDRESS:
+ if (mnl_attr_validate(attr, MNL_TYPE_BINARY) < 0)
+ return MNL_CB_ERROR;
+ break;
+ case IFLA_MTU:
+ if (mnl_attr_validate(attr, MNL_TYPE_U32) < 0)
+ return MNL_CB_ERROR;
+ break;
+ case IFLA_IFNAME:
+ if (mnl_attr_validate(attr, MNL_TYPE_STRING) < 0)
+ return MNL_CB_ERROR;
+ break;
+ }
+ tb[type] = attr;
+ return MNL_CB_OK;
+}
+
+static void netdev_mnl_check_mtu_change(struct osmo_netdev *netdev, uint32_t mtu)
+{
+ if (netdev->if_mtu_known && netdev->if_mtu == mtu)
+ return;
+
+ LOGNETDEV(netdev, LOGL_NOTICE, "MTU changed: %u\n", mtu);
+ if (netdev->mtu_chg_cb)
+ netdev->mtu_chg_cb(netdev, mtu);
+
+ netdev->if_mtu_known = true;
+ netdev->if_mtu = mtu;
+}
+
+static void netdev_mnl_check_link_state_change(struct osmo_netdev *netdev, bool if_up)
+{
+ if (netdev->if_up_known && netdev->if_up == if_up)
+ return;
+
+ LOGNETDEV(netdev, LOGL_NOTICE, "Physical link state changed: %s\n",
+ if_up ? "UP" : "DOWN");
+ if (netdev->ifupdown_ind_cb)
+ netdev->ifupdown_ind_cb(netdev, if_up);
+
+ netdev->if_up_known = true;
+ netdev->if_up = if_up;
+}
+
+static int netdev_mnl_cb(struct osmo_netdev *netdev, struct ifinfomsg *ifm, struct nlattr **tb)
+{
+ char ifnamebuf[IF_NAMESIZE];
+ const char *ifname = NULL;
+ bool if_running;
+
+ if (tb[IFLA_IFNAME]) {
+ ifname = mnl_attr_get_str(tb[IFLA_IFNAME]);
+ LOGNETDEV(netdev, LOGL_DEBUG, "%s(): ifname=%s\n", __func__, ifname);
+ } else {
+ /* Try harder to obtain the ifname. This code path should in
+ * general not be triggered since usually IFLA_IFNAME is there */
+ struct osmo_netns_switch_state switch_state;
+ NETDEV_NETNS_ENTER(netdev, &switch_state, "if_indextoname");
+ ifname = if_indextoname(ifm->ifi_index, ifnamebuf);
+ NETDEV_NETNS_EXIT(netdev, &switch_state, "if_indextoname");
+ }
+ if (ifname) {
+ /* Update dev_name if it changed: */
+ if (strcmp(netdev->dev_name, ifname) != 0) {
+ if (netdev->dev_name_chg_cb)
+ netdev->dev_name_chg_cb(netdev, ifname);
+ osmo_talloc_replace_string(netdev, &netdev->dev_name, ifname);
+ }
+ }
+
+ if (tb[IFLA_MTU]) {
+ uint32_t mtu = mnl_attr_get_u32(tb[IFLA_MTU]);
+ LOGNETDEV(netdev, LOGL_DEBUG, "%s(): mtu=%u\n", __func__, mtu);
+ netdev_mnl_check_mtu_change(netdev, mtu);
+ }
+ if (tb[IFLA_ADDRESS]) {
+ uint8_t *hwaddr = mnl_attr_get_payload(tb[IFLA_ADDRESS]);
+ uint16_t hwaddr_len = mnl_attr_get_payload_len(tb[IFLA_ADDRESS]);
+ LOGNETDEV(netdev, LOGL_DEBUG, "%s(): hwaddress=%s\n",
+ __func__, osmo_hexdump(hwaddr, hwaddr_len));
+ }
+
+ if_running = !!(ifm->ifi_flags & IFF_RUNNING);
+ LOGNETDEV(netdev, LOGL_DEBUG, "%s(): up=%u running=%u\n",
+ __func__, !!(ifm->ifi_flags & IFF_UP), if_running);
+ netdev_mnl_check_link_state_change(netdev, if_running);
+
+ return MNL_CB_OK;
+}
+
+static int netdev_netns_ctx_mnl_cb(const struct nlmsghdr *nlh, void *data)
+{
+ struct osmo_mnl *omnl = data;
+ struct netdev_netns_ctx *netns_ctx = (struct netdev_netns_ctx *)omnl->priv;
+ struct nlattr *tb[IFLA_MAX+1] = {};
+ struct ifinfomsg *ifm = mnl_nlmsg_get_payload(nlh);
+ struct osmo_netdev *netdev;
+
+ OSMO_ASSERT(omnl);
+ OSMO_ASSERT(ifm);
+
+ mnl_attr_parse(nlh, sizeof(*ifm), netdev_mnl_data_attr_cb, tb);
+
+ LOGP(DLGLOBAL, LOGL_DEBUG,
+ "%s(): index=%d type=%d flags=0x%x family=%d\n", __func__,
+ ifm->ifi_index, ifm->ifi_type, ifm->ifi_flags, ifm->ifi_family);
+
+ if (ifm->ifi_index == IFINDEX_UNUSED)
+ return MNL_CB_ERROR;
+
+ /* Find the netdev (if any) using key <netns,ifindex>.
+ * Different users of the API may have its own osmo_netdev object
+ * tracking potentially same netif, hence we need to iterate the whole list
+ * and dispatch to all matches:
+ */
+ bool found_any = false;
+ llist_for_each_entry(netdev, &g_netdev_list, entry) {
+ if (!netdev->registered)
+ continue;
+ if (netdev->ifindex != ifm->ifi_index)
+ continue;
+ if (strcmp(netdev->netns_ctx->netns_name, netns_ctx->netns_name))
+ continue;
+ found_any = true;
+ netdev_mnl_cb(netdev, ifm, &tb[0]);
+ }
+
+ if (!found_any) {
+ LOGP(DLGLOBAL, LOGL_DEBUG, "%s(): device with ifindex %u on netns %s not registered\n", __func__,
+ ifm->ifi_index, netns_ctx->netns_name);
+ }
+ return MNL_CB_OK;
+}
+
+/* Trigger an initial dump of the iface to get link information */
+static int netdev_mnl_request_initial_dump(struct osmo_mnl *omnl, unsigned int if_index)
+{
+ char buf[MNL_SOCKET_BUFFER_SIZE];
+ struct nlmsghdr *nlh = mnl_nlmsg_put_header(buf);
+ struct ifinfomsg *ifm;
+
+ nlh->nlmsg_type = RTM_GETLINK;
+ nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
+ nlh->nlmsg_seq = time(NULL);
+
+ ifm = mnl_nlmsg_put_extra_header(nlh, sizeof(*ifm));
+ ifm->ifi_family = AF_UNSPEC;
+ ifm->ifi_index = if_index;
+
+ if (mnl_socket_sendto(omnl->mnls, nlh, nlh->nlmsg_len) < 0) {
+ LOGP(DLGLOBAL, LOGL_ERROR, "mnl_socket_sendto\n");
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static int netdev_mnl_set_ifupdown(struct osmo_mnl *omnl, unsigned int if_index,
+ const char *dev_name, bool up)
+{
+ char buf[MNL_SOCKET_BUFFER_SIZE];
+ struct nlmsghdr *nlh = mnl_nlmsg_put_header(buf);
+ struct ifinfomsg *ifm;
+ unsigned int change = 0;
+ unsigned int flags = 0;
+
+ if (up) {
+ change |= IFF_UP;
+ flags |= IFF_UP;
+ } else {
+ change |= IFF_UP;
+ flags &= ~IFF_UP;
+ }
+
+ nlh->nlmsg_type = RTM_NEWLINK;
+ nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
+ nlh->nlmsg_seq = time(NULL);
+
+ ifm = mnl_nlmsg_put_extra_header(nlh, sizeof(*ifm));
+ ifm->ifi_family = AF_UNSPEC;
+ ifm->ifi_change = change;
+ ifm->ifi_flags = flags;
+ ifm->ifi_index = if_index;
+
+ if (dev_name)
+ mnl_attr_put_str(nlh, IFLA_IFNAME, dev_name);
+
+ if (mnl_socket_sendto(omnl->mnls, nlh, nlh->nlmsg_len) < 0) {
+ LOGP(DLGLOBAL, LOGL_ERROR, "mnl_socket_sendto\n");
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static int netdev_mnl_add_addr(struct osmo_mnl *omnl, unsigned int if_index, const struct osmo_sockaddr *osa, uint8_t prefix)
+{
+ char buf[MNL_SOCKET_BUFFER_SIZE];
+ struct nlmsghdr *nlh = mnl_nlmsg_put_header(buf);
+ struct ifaddrmsg *ifm;
+
+ nlh->nlmsg_type = RTM_NEWADDR;
+ nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_REPLACE | NLM_F_ACK;
+ nlh->nlmsg_seq = time(NULL);
+
+ ifm = mnl_nlmsg_put_extra_header(nlh, sizeof(*ifm));
+ ifm->ifa_family = osa->u.sa.sa_family;
+ ifm->ifa_prefixlen = prefix;
+ ifm->ifa_flags = IFA_F_PERMANENT;
+ ifm->ifa_scope = RT_SCOPE_UNIVERSE;
+ ifm->ifa_index = if_index;
+
+ /*
+ * The exact meaning of IFA_LOCAL and IFA_ADDRESS depend
+ * on the address family being used and the device type.
+ * For broadcast devices (like the interfaces we use),
+ * for IPv4 we specify both and they are used interchangeably.
+ * For IPv6, only IFA_ADDRESS needs to be set.
+ */
+ switch (osa->u.sa.sa_family) {
+ case AF_INET:
+ mnl_attr_put_u32(nlh, IFA_LOCAL, osa->u.sin.sin_addr.s_addr);
+ mnl_attr_put_u32(nlh, IFA_ADDRESS, osa->u.sin.sin_addr.s_addr);
+ break;
+ case AF_INET6:
+ mnl_attr_put(nlh, IFA_ADDRESS, sizeof(struct in6_addr), &osa->u.sin6.sin6_addr);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (mnl_socket_sendto(omnl->mnls, nlh, nlh->nlmsg_len) < 0) {
+ LOGP(DLGLOBAL, LOGL_ERROR, "mnl_socket_sendto\n");
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static int netdev_mnl_add_route(struct osmo_mnl *omnl,
+ unsigned int if_index,
+ const struct osmo_sockaddr *dst_osa,
+ uint8_t dst_prefix,
+ const struct osmo_sockaddr *gw_osa)
+{
+ char buf[MNL_SOCKET_BUFFER_SIZE];
+ struct nlmsghdr *nlh = mnl_nlmsg_put_header(buf);
+ struct rtmsg *rtm;
+
+ nlh->nlmsg_type = RTM_NEWROUTE;
+ nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_ACK;
+ nlh->nlmsg_seq = time(NULL);
+
+ rtm = mnl_nlmsg_put_extra_header(nlh, sizeof(*rtm));
+ rtm->rtm_family = dst_osa->u.sa.sa_family;
+ rtm->rtm_dst_len = dst_prefix;
+ rtm->rtm_src_len = 0;
+ rtm->rtm_tos = 0;
+ rtm->rtm_protocol = RTPROT_STATIC;
+ rtm->rtm_table = RT_TABLE_MAIN;
+ rtm->rtm_type = RTN_UNICAST;
+ rtm->rtm_scope = gw_osa ? RT_SCOPE_UNIVERSE : RT_SCOPE_LINK;
+ rtm->rtm_flags = 0;
+
+ switch (dst_osa->u.sa.sa_family) {
+ case AF_INET:
+ mnl_attr_put_u32(nlh, RTA_DST, dst_osa->u.sin.sin_addr.s_addr);
+ break;
+ case AF_INET6:
+ mnl_attr_put(nlh, RTA_DST, sizeof(struct in6_addr), &dst_osa->u.sin6.sin6_addr);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ mnl_attr_put_u32(nlh, RTA_OIF, if_index);
+
+ if (gw_osa) {
+ switch (gw_osa->u.sa.sa_family) {
+ case AF_INET:
+ mnl_attr_put_u32(nlh, RTA_GATEWAY, gw_osa->u.sin.sin_addr.s_addr);
+ break;
+ case AF_INET6:
+ mnl_attr_put(nlh, RTA_GATEWAY, sizeof(struct in6_addr), &gw_osa->u.sin6.sin6_addr);
+ break;
+ default:
+ return -EINVAL;
+ }
+ }
+
+ if (mnl_socket_sendto(omnl->mnls, nlh, nlh->nlmsg_len) < 0) {
+ LOGP(DLGLOBAL, LOGL_ERROR, "mnl_socket_sendto\n");
+ return -EIO;
+ }
+
+ return 0;
+}
+#endif /* if ENABLE_LIBMNL */
+
+/*! Allocate a new netdev object.
+ * \param[in] ctx talloc context to use as a parent when allocating the netdev object
+ * \param[in] name A name providen to identify the netdev object
+ * \returns newly allocated netdev object on success; NULL on error
+ */
+struct osmo_netdev *osmo_netdev_alloc(void *ctx, const char *name)
+{
+ struct osmo_netdev *netdev;
+
+ netdev = talloc_zero(ctx, struct osmo_netdev);
+ if (!netdev)
+ return NULL;
+
+ netdev->name = talloc_strdup(netdev, name);
+
+ llist_add_tail(&netdev->entry, &g_netdev_list);
+ return netdev;
+}
+
+/*! Free an allocated netdev object.
+ * \param[in] netdev The netdev object to free
+ */
+void osmo_netdev_free(struct osmo_netdev *netdev)
+{
+ if (!netdev)
+ return;
+ if (osmo_netdev_is_registered(netdev))
+ osmo_netdev_unregister(netdev);
+ llist_del(&netdev->entry);
+ talloc_free(netdev);
+}
+
+/*! Start managing the network device referenced by the netdev object.
+ * \param[in] netdev The netdev object to open
+ * \returns 0 on success; negative on error
+ */
+int osmo_netdev_register(struct osmo_netdev *netdev)
+{
+ char ifnamebuf[IF_NAMESIZE];
+ struct osmo_netns_switch_state switch_state;
+ int rc = 0;
+
+ if (netdev->registered)
+ return -EALREADY;
+
+ netdev->netns_ctx = netdev_netns_ctx_get(netdev->netns_name ? : "");
+ if (!netdev->netns_ctx)
+ return -EFAULT;
+
+ NETDEV_NETNS_ENTER(netdev, &switch_state, "register");
+
+ if (!if_indextoname(netdev->ifindex, ifnamebuf)) {
+ rc = -ENODEV;
+ goto err_put_exit;
+ }
+ osmo_talloc_replace_string(netdev, &netdev->dev_name, ifnamebuf);
+
+#if ENABLE_LIBMNL
+ rc = netdev_mnl_request_initial_dump(netdev->netns_ctx->omnl, netdev->ifindex);
+#endif
+
+ NETDEV_NETNS_EXIT(netdev, &switch_state, "register");
+
+ netdev->registered = true;
+ return rc;
+
+err_put_exit:
+ NETDEV_NETNS_EXIT(netdev, &switch_state, "register");
+ netdev_netns_ctx_put(netdev->netns_ctx);
+ return rc;
+}
+
+/*! Unregister the netdev object (stop managing /moniutoring the interface)
+ * \param[in] netdev The netdev object to close
+ * \returns 0 on success; negative on error
+ */
+int osmo_netdev_unregister(struct osmo_netdev *netdev)
+{
+ if (!netdev->registered)
+ return -EALREADY;
+
+ netdev->if_up_known = false;
+ netdev->if_mtu_known = false;
+
+ netdev_netns_ctx_put(netdev->netns_ctx);
+ netdev->registered = false;
+ return 0;
+}
+
+/*! Retrieve whether the netdev object is in "registered" state.
+ * \param[in] netdev The netdev object to check
+ * \returns true if in state "registered"; false otherwise
+ */
+bool osmo_netdev_is_registered(struct osmo_netdev *netdev)
+{
+ return netdev->registered;
+}
+
+/*! Set private user data pointer on the netdev object.
+ * \param[in] netdev The netdev object where the field is set
+ */
+void osmo_netdev_set_priv_data(struct osmo_netdev *netdev, void *priv_data)
+{
+ netdev->priv_data = priv_data;
+}
+
+/*! Get private user data pointer from the netdev object.
+ * \param[in] netdev The netdev object from where to retrieve the field
+ * \returns The current value of the priv_data field.
+ */
+void *osmo_netdev_get_priv_data(struct osmo_netdev *netdev)
+{
+ return netdev->priv_data;
+}
+
+/*! Set data_ind_cb callback, called when a new packet is received on the network interface.
+ * \param[in] netdev The netdev object where the field is set
+ * \param[in] data_ind_cb the user provided function to be called when the link status (UP/DOWN) changes
+ */
+void osmo_netdev_set_ifupdown_ind_cb(struct osmo_netdev *netdev, osmo_netdev_ifupdown_ind_cb_t ifupdown_ind_cb)
+{
+ netdev->ifupdown_ind_cb = ifupdown_ind_cb;
+}
+
+/*! Set dev_name_chg_cb callback, called when a change in the network name is detected
+ * \param[in] netdev The netdev object where the field is set
+ * \param[in] dev_name_chg_cb the user provided function to be called when a the interface is renamed
+ */
+void osmo_netdev_set_dev_name_chg_cb(struct osmo_netdev *netdev, osmo_netdev_dev_name_chg_cb_t dev_name_chg_cb)
+{
+ netdev->dev_name_chg_cb = dev_name_chg_cb;
+}
+
+/*! Set mtu_chg_cb callback, called when a change in the network name is detected
+ * \param[in] netdev The netdev object where the field is set
+ * \param[in] mtu_chg_cb the user provided function to be called when the configured MTU at the interface changes
+ */
+void osmo_netdev_set_mtu_chg_cb(struct osmo_netdev *netdev, osmo_netdev_mtu_chg_cb_t mtu_chg_cb)
+{
+ netdev->mtu_chg_cb = mtu_chg_cb;
+}
+
+/*! Get name used to identify the netdev object.
+ * \param[in] netdev The netdev object from where to retrieve the field
+ * \returns The current value of the name used to identify the netdev object
+ */
+const char *osmo_netdev_get_name(const struct osmo_netdev *netdev)
+{
+ return netdev->name;
+}
+
+/*! Set (specify) interface index identifying the network interface to manage
+ * \param[in] netdev The netdev object where the field is set
+ * \param[in] ifindex The interface index identifying the interface
+ * \returns 0 on success; negative on error
+ *
+ * The ifindex, together with the netns_name (see
+ * osmo_netdev_netns_name_set()), form together the key identifiers of a
+ * network interface to manage.
+ * This field is used during osmo_netdev_register() time, and hence must be set
+ * before calling that API, and cannot be changed when the netdev object is in
+ * "registered" state.
+ */
+int osmo_netdev_set_ifindex(struct osmo_netdev *netdev, unsigned int ifindex)
+{
+ if (netdev->registered)
+ return -EALREADY;
+ netdev->ifindex = ifindex;
+ return 0;
+}
+
+/*! Get interface index identifying the interface managed by netdev
+ * \param[in] netdev The netdev object from where to retrieve the field
+ * \returns The current value of the configured netdev interface ifindex to use (0 = unset)
+ */
+unsigned int osmo_netdev_get_ifindex(const struct osmo_netdev *netdev)
+{
+ return netdev->ifindex;
+}
+
+/*! Set (specify) name of the network namespace where the network interface to manage is located
+ * \param[in] netdev The netdev object where the field is set
+ * \param[in] netns_name The network namespace where the network interface is located
+ * \returns 0 on success; negative on error
+ *
+ * The netns_name, together with the ifindex (see
+ * osmo_netdev_ifindex_set()), form together the key identifiers of a
+ * network interface to manage.
+ * This field is used during osmo_netdev_register() time, and hence must be set
+ * before calling that API, and cannot be changed when the netdev object is in
+ * "registered" state.
+ * If left as NULL (default), the management will be done in the current network namespace.
+ */
+int osmo_netdev_set_netns_name(struct osmo_netdev *netdev, const char *netns_name)
+{
+ if (netdev->registered)
+ return -EALREADY;
+ osmo_talloc_replace_string(netdev, &netdev->netns_name, netns_name);
+ return 0;
+}
+
+/*! Get name of network namespace used when opening the netdev interface
+ * \param[in] netdev The netdev object from where to retrieve the field
+ * \returns The current value of the configured network namespace
+ */
+const char *osmo_netdev_get_netns_name(const struct osmo_netdev *netdev)
+{
+ return netdev->netns_name;
+}
+
+/*! Get name used to name the network interface created by the netdev object
+ * \param[in] netdev The netdev object from where to retrieve the field
+ * \returns The interface name (or NULL if unknown)
+ *
+ * This information is retrieved internally once the netdev object enters the
+ * "registered" state. Hence, when not registered NULL can be returned.
+ */
+const char *osmo_netdev_get_dev_name(const struct osmo_netdev *netdev)
+{
+ return netdev->dev_name;
+}
+
+/*! Bring netdev interface UP or DOWN.
+ * \param[in] netdev The netdev object managing the netdev interface
+ * \param[in] ifupdown true to set the interface UP, false to set it DOWN
+ * \returns 0 on succes; negative on error.
+ */
+int osmo_netdev_ifupdown(struct osmo_netdev *netdev, bool ifupdown)
+{
+ struct osmo_netns_switch_state switch_state;
+ int rc;
+
+ if (!netdev->registered)
+ return -ENODEV;
+
+ LOGNETDEV(netdev, LOGL_NOTICE, "Bringing dev %s %s\n",
+ netdev->dev_name, ifupdown ? "UP" : "DOWN");
+
+ NETDEV_NETNS_ENTER(netdev, &switch_state, "ifupdown");
+
+#if ENABLE_LIBMNL
+ rc = netdev_mnl_set_ifupdown(netdev->netns_ctx->omnl, netdev->ifindex,
+ netdev->dev_name, ifupdown);
+#else
+ LOGNETDEV(netdev, LOGL_ERROR, "%s: NOT SUPPORTED. Build libosmocore with --enable-libmnl.\n", __func__);
+ rc = -ENOTSUP;
+#endif
+
+ NETDEV_NETNS_EXIT(netdev, &switch_state, "ifupdown");
+
+ return rc;
+}
+
+/*! Add IP address to netdev interface
+ * \param[in] netdev The netdev object managing the netdev interface
+ * \param[in] addr The local address to set on the interface
+ * \param[in] prefixlen The network prefix of addr
+ * \returns 0 on succes; negative on error.
+ */
+int osmo_netdev_add_addr(struct osmo_netdev *netdev, const struct osmo_sockaddr *addr, uint8_t prefixlen)
+{
+ struct osmo_netns_switch_state switch_state;
+ char buf[INET6_ADDRSTRLEN];
+ int rc;
+
+ if (!netdev->registered)
+ return -ENODEV;
+
+ LOGNETDEV(netdev, LOGL_NOTICE, "Adding address %s/%u to dev %s\n",
+ osmo_sockaddr_ntop(&addr->u.sa, buf), prefixlen, netdev->dev_name);
+
+ NETDEV_NETNS_ENTER(netdev, &switch_state, "Add address");
+
+#if ENABLE_LIBMNL
+ rc = netdev_mnl_add_addr(netdev->netns_ctx->omnl, netdev->ifindex, addr, prefixlen);
+#else
+ LOGNETDEV(netdev, LOGL_ERROR, "%s: NOT SUPPORTED. Build libosmocore with --enable-libmnl.\n", __func__);
+ rc = -ENOTSUP;
+#endif
+
+ NETDEV_NETNS_EXIT(netdev, &switch_state, "Add address");
+
+ return rc;
+}
+
+/*! Add IP route to netdev interface
+ * \param[in] netdev The netdev object managing the netdev interface
+ * \param[in] dst_addr The destination address of the route
+ * \param[in] dst_prefixlen The network prefix of dst_addr
+ * \param[in] gw_addr The gateway address. Optional, can be NULL.
+ * \returns 0 on succes; negative on error.
+ */
+int osmo_netdev_add_route(struct osmo_netdev *netdev, const struct osmo_sockaddr *dst_addr, uint8_t dst_prefixlen, const struct osmo_sockaddr *gw_addr)
+{
+ struct osmo_netns_switch_state switch_state;
+ char buf_dst[INET6_ADDRSTRLEN];
+ char buf_gw[INET6_ADDRSTRLEN];
+ int rc;
+
+ if (!netdev->registered)
+ return -ENODEV;
+
+ LOGNETDEV(netdev, LOGL_NOTICE, "Adding route %s/%u%s%s dev %s\n",
+ osmo_sockaddr_ntop(&dst_addr->u.sa, buf_dst), dst_prefixlen,
+ gw_addr ? " via " : "",
+ gw_addr ? osmo_sockaddr_ntop(&gw_addr->u.sa, buf_gw) : "",
+ netdev->dev_name);
+
+ NETDEV_NETNS_ENTER(netdev, &switch_state, "Add route");
+
+#if ENABLE_LIBMNL
+ rc = netdev_mnl_add_route(netdev->netns_ctx->omnl, netdev->ifindex, dst_addr, dst_prefixlen, gw_addr);
+#else
+ LOGNETDEV(netdev, LOGL_ERROR, "%s: NOT SUPPORTED. Build libosmocore with --enable-libmnl.\n", __func__);
+ rc = -ENOTSUP;
+#endif
+
+ NETDEV_NETNS_EXIT(netdev, &switch_state, "Add route");
+
+ return rc;
+}
+
+#endif /* (!EMBEDDED) */
+
+/*! @} */
diff --git a/src/core/netns.c b/src/core/netns.c
new file mode 100644
index 00000000..c1d75b1e
--- /dev/null
+++ b/src/core/netns.c
@@ -0,0 +1,208 @@
+
+/* Network namespace convenience functions
+ * (C) 2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include "config.h"
+
+/*! \addtogroup netns
+ * @{
+ * Network namespace convenience functions
+ *
+ * \file netns.c */
+
+#if defined(__linux__)
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sched.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/mount.h>
+#include <sys/param.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#include <osmocom/core/utils.h>
+#include <osmocom/core/netns.h>
+
+#define NETNS_PREFIX_PATH "/var/run/netns"
+#define NETNS_CURRENT_PATH "/proc/self/ns/net"
+
+/*! Open a file descriptor for the current network namespace.
+ * \returns fd of the current network namespace on success; negative in case of error
+ */
+static int netns_open_current_fd(void)
+{
+ int fd;
+ /* store the default namespace for later reference */
+ if ((fd = open(NETNS_CURRENT_PATH, O_RDONLY)) < 0)
+ return -errno;
+ return fd;
+}
+
+/*! switch to a (non-default) namespace, store existing signal mask in oldmask.
+ * \param[in] nsfd file descriptor representing the namespace to which we shall switch
+ * \param[out] state caller-provided memory location to which state of previous netns is stored
+ * \returns 0 on success; negative on error */
+int osmo_netns_switch_enter(int nsfd, struct osmo_netns_switch_state *state)
+{
+ sigset_t intmask;
+ int rc;
+
+ state->prev_nsfd = -1;
+
+ if (sigfillset(&intmask) < 0)
+ return -errno;
+ if ((rc = sigprocmask(SIG_BLOCK, &intmask, &state->prev_sigmask)) != 0)
+ return -rc;
+ state->prev_nsfd = netns_open_current_fd();
+
+ if (setns(nsfd, CLONE_NEWNET) < 0) {
+ /* restore old mask if we couldn't switch the netns */
+ sigprocmask(SIG_SETMASK, &state->prev_sigmask, NULL);
+ close(state->prev_nsfd);
+ state->prev_nsfd = -1;
+ return -errno;
+ }
+ return 0;
+}
+
+/*! switch back to the previous namespace, restoring signal mask.
+ * \param[in] state information about previous netns, filled by osmo_netns_switch_enter()
+ * \returns 0 on successs; negative on error */
+int osmo_netns_switch_exit(struct osmo_netns_switch_state *state)
+{
+ if (state->prev_nsfd < 0)
+ return -EINVAL;
+
+ int rc;
+ if (setns(state->prev_nsfd, CLONE_NEWNET) < 0)
+ return -errno;
+
+ close(state->prev_nsfd);
+ state->prev_nsfd = -1;
+
+ if ((rc = sigprocmask(SIG_SETMASK, &state->prev_sigmask, NULL)) != 0)
+ return -rc;
+ return 0;
+}
+
+static int create_netns(const char *name)
+{
+ char path[MAXPATHLEN];
+ sigset_t intmask, oldmask;
+ int fd, prev_nsfd;
+ int rc, rc2;
+
+ /* create /var/run/netns, if it doesn't exist already */
+ rc = mkdir(NETNS_PREFIX_PATH, S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH);
+ if (rc < 0 && errno != EEXIST)
+ return rc;
+
+ /* create /var/run/netns/[name], if it doesn't exist already */
+ rc = snprintf(path, sizeof(path), "%s/%s", NETNS_PREFIX_PATH, name);
+ if (rc >= sizeof(path))
+ return -ENAMETOOLONG;
+ fd = open(path, O_RDONLY|O_CREAT|O_EXCL, 0);
+ if (fd < 0)
+ return -errno;
+ if (close(fd) < 0)
+ return -errno;
+
+ /* mask off all signals, store old signal mask */
+ if (sigfillset(&intmask) < 0)
+ return -errno;
+ if ((rc = sigprocmask(SIG_BLOCK, &intmask, &oldmask)) != 0)
+ return -rc;
+
+ prev_nsfd = netns_open_current_fd();
+ if (prev_nsfd < 0)
+ return prev_nsfd;
+
+ /* create a new network namespace */
+ if (unshare(CLONE_NEWNET) < 0) {
+ rc = -errno;
+ goto restore_sigmask;
+ }
+ if (mount(NETNS_CURRENT_PATH, path, "none", MS_BIND, NULL) < 0) {
+ rc = -errno;
+ goto restore_sigmask;
+ }
+
+ /* switch back to previous namespace */
+ if (setns(prev_nsfd, CLONE_NEWNET) < 0) {
+ rc = -errno;
+ goto restore_sigmask;
+ }
+
+restore_sigmask:
+ close(prev_nsfd);
+
+ /* restore process mask */
+ if ((rc2 = sigprocmask(SIG_SETMASK, &oldmask, NULL)) != 0)
+ return -rc2;
+
+ /* might have been set above in case mount fails */
+ if (rc < 0)
+ return rc;
+
+ /* finally, open the created namespace file descriptor from previous ns */
+ if ((fd = open(path, O_RDONLY)) < 0)
+ return -errno;
+
+ return fd;
+}
+
+/*! Open a file descriptor for the network namespace with provided name.
+ * Creates /var/run/netns/ directory if it doesn't exist already.
+ * \param[in] name Name of the network namespace (in /var/run/netns/)
+ * \returns File descriptor of network namespace; negative in case of error
+ */
+int osmo_netns_open_fd(const char *name)
+{
+ int rc;
+ int fd;
+ char path[MAXPATHLEN];
+
+ /* path = /var/run/netns/[name] */
+ rc = snprintf(path, sizeof(path), "%s/%s", NETNS_PREFIX_PATH, name);
+ if (rc >= sizeof(path))
+ return -ENAMETOOLONG;
+
+ /* If netns already exists, simply open it: */
+ fd = open(path, O_RDONLY);
+ if (fd >= 0)
+ return fd;
+
+ /* The netns doesn't exist yet, let's create it: */
+ fd = create_netns(name);
+ return fd;
+}
+
+#endif /* defined(__linux__) */
+
+/*! @} */
diff --git a/src/core/osmo_io.c b/src/core/osmo_io.c
new file mode 100644
index 00000000..b589cb71
--- /dev/null
+++ b/src/core/osmo_io.c
@@ -0,0 +1,1007 @@
+/*
+ * New osmocom async I/O API.
+ *
+ * (C) 2022-2024 by Harald Welte <laforge@osmocom.org>
+ * (C) 2022-2024 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * Author: Daniel Willmann <dwillmann@sysmocom.de>
+ *
+ * All Rights Reserved.
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include "../config.h"
+#ifndef EMBEDDED
+
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <talloc.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdbool.h>
+#include <errno.h>
+
+#include <osmocom/core/osmo_io.h>
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+
+#include "osmo_io_internal.h"
+
+/*! \addtogroup osmo_io
+ * @{
+ *
+ * \file osmo_io.c */
+
+/*! This environment variable can be set to manually set the backend used in osmo_io */
+#define OSMO_IO_BACKEND_ENV "LIBOSMO_IO_BACKEND"
+
+const struct value_string osmo_io_backend_names[] = {
+ { OSMO_IO_BACKEND_POLL, "poll" },
+ { OSMO_IO_BACKEND_IO_URING, "io_uring" },
+ { 0, NULL }
+};
+
+const struct value_string osmo_iofd_mode_names[] = {
+ { OSMO_IO_FD_MODE_READ_WRITE, "read/write" },
+ { OSMO_IO_FD_MODE_RECVFROM_SENDTO, "recvfrom/sendto" },
+ { OSMO_IO_FD_MODE_RECVMSG_SENDMSG, "recvmsg/sendmsg" },
+ { 0, NULL }
+};
+
+static enum osmo_io_backend g_io_backend;
+
+/* Used by some tests, can't be static */
+struct iofd_backend_ops osmo_iofd_ops;
+
+#if defined(HAVE_URING)
+void osmo_iofd_uring_init(void);
+#endif
+
+/*! initialize osmo_io for the current thread */
+void osmo_iofd_init(void)
+{
+ switch (g_io_backend) {
+ case OSMO_IO_BACKEND_POLL:
+ break;
+#if defined(HAVE_URING)
+ case OSMO_IO_BACKEND_IO_URING:
+ osmo_iofd_uring_init();
+ break;
+#endif
+ default:
+ OSMO_ASSERT(0);
+ break;
+ }
+}
+
+/* ensure main thread always has pre-initialized osmo_io
+ * priority 103: run after on_dso_load_select */
+static __attribute__((constructor(103))) void on_dso_load_osmo_io(void)
+{
+ char *backend = getenv(OSMO_IO_BACKEND_ENV);
+ if (backend == NULL)
+ backend = OSMO_IO_BACKEND_DEFAULT;
+
+ if (!strcmp("POLL", backend)) {
+ g_io_backend = OSMO_IO_BACKEND_POLL;
+ osmo_iofd_ops = iofd_poll_ops;
+#if defined(HAVE_URING)
+ } else if (!strcmp("IO_URING", backend)) {
+ g_io_backend = OSMO_IO_BACKEND_IO_URING;
+ osmo_iofd_ops = iofd_uring_ops;
+#endif
+ } else {
+ fprintf(stderr, "Invalid osmo_io backend requested: \"%s\"\nCheck the environment variable %s\n", backend, OSMO_IO_BACKEND_ENV);
+ exit(1);
+ }
+
+ OSMO_ASSERT(osmo_iofd_ops.close);
+ OSMO_ASSERT(osmo_iofd_ops.register_fd);
+ OSMO_ASSERT(osmo_iofd_ops.unregister_fd);
+ OSMO_ASSERT(osmo_iofd_ops.write_enable);
+ OSMO_ASSERT(osmo_iofd_ops.write_disable);
+ OSMO_ASSERT(osmo_iofd_ops.read_enable);
+ OSMO_ASSERT(osmo_iofd_ops.read_disable);
+ OSMO_ASSERT(osmo_iofd_ops.notify_connected);
+
+ osmo_iofd_init();
+}
+
+/*! Allocate the msghdr.
+ * \param[in] iofd the osmo_io file structure
+ * \param[in] action the action this msg(hdr) is for (read, write, ..)
+ * \param[in] msg the msg buffer to use. Will allocate a new one if NULL
+ * \param[in] cmsg_size size (in bytes) of iofd_msghdr.cmsg buffer. Can be 0 if cmsg is not used.
+ * \returns the newly allocated msghdr or NULL in case of error */
+struct iofd_msghdr *iofd_msghdr_alloc(struct osmo_io_fd *iofd, enum iofd_msg_action action, struct msgb *msg,
+ size_t cmsg_size)
+{
+ bool free_msg = false;
+ struct iofd_msghdr *hdr;
+
+ if (!msg) {
+ msg = iofd_msgb_alloc(iofd);
+ if (!msg)
+ return NULL;
+ free_msg = true;
+ } else {
+ talloc_steal(iofd, msg);
+ }
+
+ hdr = talloc_zero_size(iofd, sizeof(struct iofd_msghdr) + cmsg_size);
+ if (!hdr) {
+ if (free_msg)
+ talloc_free(msg);
+ return NULL;
+ }
+
+ hdr->action = action;
+ hdr->iofd = iofd;
+ hdr->msg = msg;
+
+ return hdr;
+}
+
+/*! Free the msghdr.
+ * \param[in] msghdr the msghdr to free
+ */
+void iofd_msghdr_free(struct iofd_msghdr *msghdr)
+{
+ /* msghdr->msg is never owned by msghdr, it will either be freed in the send path or
+ * or passed on to the read callback which takes ownership. */
+ talloc_free(msghdr);
+}
+
+/*! convenience wrapper to call msgb_alloc with parameters from osmo_io_fd */
+struct msgb *iofd_msgb_alloc(struct osmo_io_fd *iofd)
+{
+ uint16_t headroom = iofd->msgb_alloc.headroom;
+
+ OSMO_ASSERT(iofd->msgb_alloc.size < 0xffff - headroom);
+ return msgb_alloc_headroom_c(iofd, iofd->msgb_alloc.size + headroom, headroom, "osmo_io_msgb");
+}
+
+/*! return the pending msgb in iofd or NULL if there is none*/
+struct msgb *iofd_msgb_pending(struct osmo_io_fd *iofd)
+{
+ struct msgb *msg = NULL;
+
+ msg = iofd->pending;
+ iofd->pending = NULL;
+
+ return msg;
+}
+
+/*! Return the pending msgb or allocate and return a new one */
+struct msgb *iofd_msgb_pending_or_alloc(struct osmo_io_fd *iofd)
+{
+ struct msgb *msg = NULL;
+
+ msg = iofd_msgb_pending(iofd);
+ if (!msg)
+ msg = iofd_msgb_alloc(iofd);
+
+ return msg;
+}
+
+/*! Enqueue a message to be sent.
+ *
+ * Enqueues the message at the back of the queue provided there is enough space.
+ * \param[in] iofd the file descriptor
+ * \param[in] msghdr the message to enqueue
+ * \returns 0 if the message was enqueued succcessfully,
+ * -ENOSPC if the queue already contains the maximum number of messages
+ */
+int iofd_txqueue_enqueue(struct osmo_io_fd *iofd, struct iofd_msghdr *msghdr)
+{
+ if (iofd->tx_queue.current_length >= iofd->tx_queue.max_length)
+ return -ENOSPC;
+
+ llist_add_tail(&msghdr->list, &iofd->tx_queue.msg_queue);
+ iofd->tx_queue.current_length++;
+
+ if (iofd->tx_queue.current_length == 1 && !IOFD_FLAG_ISSET(iofd, IOFD_FLAG_CLOSED))
+ osmo_iofd_ops.write_enable(iofd);
+
+ return 0;
+}
+
+/*! Enqueue a message at the front.
+ *
+ * Used to enqueue a msgb from a partial send again. This function will always
+ * enqueue the message, even if the maximum number of messages is reached.
+ * \param[in] iofd the file descriptor
+ * \param[in] msghdr the message to enqueue
+ */
+void iofd_txqueue_enqueue_front(struct osmo_io_fd *iofd, struct iofd_msghdr *msghdr)
+{
+ llist_add(&msghdr->list, &iofd->tx_queue.msg_queue);
+ iofd->tx_queue.current_length++;
+
+ if (iofd->tx_queue.current_length == 1 && !IOFD_FLAG_ISSET(iofd, IOFD_FLAG_CLOSED))
+ osmo_iofd_ops.write_enable(iofd);
+}
+
+/*! Dequeue a message from the front.
+ *
+ * \param[in] iofd the file descriptor
+ * \returns the msghdr from the front of the queue or NULL if the queue is empty
+ */
+struct iofd_msghdr *iofd_txqueue_dequeue(struct osmo_io_fd *iofd)
+{
+ struct llist_head *lh;
+
+ if (iofd->tx_queue.current_length == 0)
+ return NULL;
+
+ lh = iofd->tx_queue.msg_queue.next;
+
+ OSMO_ASSERT(lh);
+ iofd->tx_queue.current_length--;
+ llist_del(lh);
+
+ if (iofd->tx_queue.current_length == 0)
+ osmo_iofd_ops.write_disable(iofd);
+
+ return llist_entry(lh, struct iofd_msghdr, list);
+}
+
+/*! Handle segmentation of the msg. If this function returns *_HANDLE_ONE or MORE then the data in msg will contain
+ * one complete message.
+ * If there are bytes left over, *pending_out will point to a msgb with the remaining data.
+*/
+static enum iofd_seg_act iofd_handle_segmentation(struct osmo_io_fd *iofd, struct msgb *msg, struct msgb **pending_out)
+{
+ int extra_len, received_len;
+ struct msgb *msg_pending;
+
+ /* Save the start of message before segmentation_cb (which could change it) */
+ uint8_t *data = msg->data;
+
+ received_len = msgb_length(msg);
+
+ if (!iofd->io_ops.segmentation_cb) {
+ *pending_out = NULL;
+ return IOFD_SEG_ACT_HANDLE_ONE;
+ }
+
+ int expected_len = iofd->io_ops.segmentation_cb(msg);
+ if (expected_len == -EAGAIN) {
+ goto defer;
+ } else if (expected_len < 0) {
+ /* Something is wrong, skip this msgb */
+ LOGPIO(iofd, LOGL_ERROR, "segmentation_cb returned error (%d), skipping msg of size %d\n",
+ expected_len, received_len);
+ *pending_out = NULL;
+ msgb_free(msg);
+ return IOFD_SEG_ACT_DEFER;
+ }
+
+ extra_len = received_len - expected_len;
+ /* No segmentation needed, return the whole msgb */
+ if (extra_len == 0) {
+ *pending_out = NULL;
+ return IOFD_SEG_ACT_HANDLE_ONE;
+ /* segment is incomplete */
+ } else if (extra_len < 0) {
+ goto defer;
+ }
+
+ /* msgb contains more than one segment */
+ /* Copy the trailing data over */
+ msg_pending = iofd_msgb_alloc(iofd);
+ memcpy(msgb_data(msg_pending), data + expected_len, extra_len);
+ msgb_put(msg_pending, extra_len);
+ *pending_out = msg_pending;
+
+ /* Trim the original msgb to size. Don't use msgb_trim because we need to reference
+ * msg->data from before it might have been modified by the segmentation_cb(). */
+ msg->tail = data + expected_len;
+ msg->len = msg->tail - msg->data;
+ return IOFD_SEG_ACT_HANDLE_MORE;
+
+defer:
+ *pending_out = msg;
+ return IOFD_SEG_ACT_DEFER;
+}
+
+/*! Restore message boundaries on read() and pass individual messages to the read callback
+ */
+void iofd_handle_segmented_read(struct osmo_io_fd *iofd, struct msgb *msg, int rc)
+{
+ int res;
+ struct msgb *pending = NULL;
+
+ OSMO_ASSERT(iofd->mode == OSMO_IO_FD_MODE_READ_WRITE);
+
+ if (rc <= 0) {
+ iofd->io_ops.read_cb(iofd, rc, msg);
+ return;
+ }
+
+ do {
+ res = iofd_handle_segmentation(iofd, msg, &pending);
+ if (res != IOFD_SEG_ACT_DEFER || rc < 0)
+ iofd->io_ops.read_cb(iofd, rc, msg);
+ if (res == IOFD_SEG_ACT_HANDLE_MORE)
+ msg = pending;
+ } while (res == IOFD_SEG_ACT_HANDLE_MORE);
+
+ OSMO_ASSERT(iofd->pending == NULL);
+ iofd->pending = pending;
+}
+
+/*! completion handler: Internal function called by osmo_io_backend after a given I/O operation has completed
+ * \param[in] iofd I/O file-descriptor on which I/O has completed
+ * \param[in] msg message buffer containing data related to completed I/O
+ * \param[in] rc result code with read size or error (-errno)
+ * \param[in] hdr serialized msghdr containing state of completed I/O */
+void iofd_handle_recv(struct osmo_io_fd *iofd, struct msgb *msg, int rc, struct iofd_msghdr *hdr)
+{
+ talloc_steal(iofd->msgb_alloc.ctx, msg);
+ switch (iofd->mode) {
+ case OSMO_IO_FD_MODE_READ_WRITE:
+ iofd_handle_segmented_read(iofd, msg, rc);
+ break;
+ case OSMO_IO_FD_MODE_RECVFROM_SENDTO:
+ iofd->io_ops.recvfrom_cb(iofd, rc, msg, &hdr->osa);
+ break;
+ case OSMO_IO_FD_MODE_RECVMSG_SENDMSG:
+ iofd->io_ops.recvmsg_cb(iofd, rc, msg, &hdr->hdr);
+ break;
+ default:
+ OSMO_ASSERT(false);
+ break;
+ }
+}
+
+/*! completion handler: Internal function called by osmo_io_backend after a given I/O operation has completed
+ * \param[in] iofd I/O file-descriptor on which I/O has completed
+ * \param[in] rc return value of the I/O operation
+ * \param[in] msghdr serialized msghdr containing state of completed I/O
+ */
+void iofd_handle_send_completion(struct osmo_io_fd *iofd, int rc, struct iofd_msghdr *msghdr)
+{
+ struct msgb *msg = msghdr->msg;
+
+ /* Incomplete write */
+ if (rc > 0 && rc < msgb_length(msg)) {
+ /* Re-enqueue remaining data */
+ msgb_pull(msg, rc);
+ msghdr->iov[0].iov_len = msgb_length(msg);
+ iofd_txqueue_enqueue_front(iofd, msghdr);
+ return;
+ }
+
+ /* Reenqueue the complete msgb */
+ if (rc == -EAGAIN) {
+ iofd_txqueue_enqueue_front(iofd, msghdr);
+ return;
+ }
+
+ /* All other failure and success cases are handled here */
+ switch (msghdr->action) {
+ case IOFD_ACT_WRITE:
+ if (iofd->io_ops.write_cb)
+ iofd->io_ops.write_cb(iofd, rc, msg);
+ break;
+ case IOFD_ACT_SENDTO:
+ if (iofd->io_ops.sendto_cb)
+ iofd->io_ops.sendto_cb(iofd, rc, msg, &msghdr->osa);
+ break;
+ case IOFD_ACT_SENDMSG:
+ if (iofd->io_ops.sendmsg_cb)
+ iofd->io_ops.sendmsg_cb(iofd, rc, msg);
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+
+ msgb_free(msghdr->msg);
+ iofd_msghdr_free(msghdr);
+}
+
+/* Public functions */
+
+/*! Write a message to a file descriptor / connected socket.
+ * The osmo_io_fd must be using OSMO_IO_FD_MODE_READ_WRITE.
+ *
+ * Appends the message to the internal transmit queue for eventual non-blocking
+ * write to the underlying socket/file descriptor.
+ *
+ * If the function returns success (0) it will take ownership of the msgb and
+ * internally call msgb_free() after the write request completes.
+ * In case of an error, the msgb needs to be freed by the caller.
+ *
+ * \param[in] iofd osmo_io_fd file descriptor to write data to
+ * \param[in] msg message buffer containing the data to write
+ * \returns 0 in case of success; a negative value in case of error
+ */
+int osmo_iofd_write_msgb(struct osmo_io_fd *iofd, struct msgb *msg)
+{
+ int rc;
+
+ if (OSMO_UNLIKELY(msgb_length(msg) == 0)) {
+ LOGPIO(iofd, LOGL_ERROR, "Length is 0, rejecting msgb.\n");
+ return -EINVAL;
+ }
+
+ OSMO_ASSERT(iofd->mode == OSMO_IO_FD_MODE_READ_WRITE);
+
+ struct iofd_msghdr *msghdr = iofd_msghdr_alloc(iofd, IOFD_ACT_WRITE, msg, 0);
+ if (!msghdr)
+ return -ENOMEM;
+
+ msghdr->flags = MSG_NOSIGNAL;
+ msghdr->iov[0].iov_base = msgb_data(msghdr->msg);
+ msghdr->iov[0].iov_len = msgb_length(msghdr->msg);
+ msghdr->hdr.msg_iov = &msghdr->iov[0];
+ msghdr->hdr.msg_iovlen = 1;
+
+ rc = iofd_txqueue_enqueue(iofd, msghdr);
+ if (rc < 0) {
+ iofd_msghdr_free(msghdr);
+ LOGPIO(iofd, LOGL_ERROR, "enqueueing message failed (%d). Rejecting msgb\n", rc);
+ return rc;
+ }
+
+ return 0;
+}
+
+/*! Send a message through an unconnected socket.
+ * The osmo_io_fd must be using OSMO_IO_FD_MODE_RECVFROM_SENDTO.
+ *
+ * Appends the message to the internal transmit queue for eventual non-blocking
+ * sendto on the underlying socket/file descriptor.
+ *
+ * If the function returns success (0), it will take ownership of the msgb and
+ * internally call msgb_free() after the sendto request completes.
+ * In case of an error the msgb needs to be freed by the caller.
+ *
+ * \param[in] iofd file descriptor to write to
+ * \param[in] msg message buffer to send
+ * \param[in] sendto_flags Flags to pass to the send call
+ * \param[in] dest destination address to send the message to
+ * \returns 0 in case of success; a negative value in case of error
+ */
+int osmo_iofd_sendto_msgb(struct osmo_io_fd *iofd, struct msgb *msg, int sendto_flags, const struct osmo_sockaddr *dest)
+{
+ int rc;
+
+ if (OSMO_UNLIKELY(msgb_length(msg) == 0)) {
+ LOGPIO(iofd, LOGL_ERROR, "Length is 0, rejecting msgb.\n");
+ return -EINVAL;
+ }
+
+ OSMO_ASSERT(iofd->mode == OSMO_IO_FD_MODE_RECVFROM_SENDTO);
+
+ struct iofd_msghdr *msghdr = iofd_msghdr_alloc(iofd, IOFD_ACT_SENDTO, msg, 0);
+ if (!msghdr)
+ return -ENOMEM;
+
+ if (dest) {
+ msghdr->osa = *dest;
+ msghdr->hdr.msg_name = &msghdr->osa.u.sa;
+ msghdr->hdr.msg_namelen = osmo_sockaddr_size(&msghdr->osa);
+ }
+ msghdr->flags = sendto_flags;
+ msghdr->iov[0].iov_base = msgb_data(msghdr->msg);
+ msghdr->iov[0].iov_len = msgb_length(msghdr->msg);
+ msghdr->hdr.msg_iov = &msghdr->iov[0];
+ msghdr->hdr.msg_iovlen = 1;
+
+ rc = iofd_txqueue_enqueue(iofd, msghdr);
+ if (rc < 0) {
+ iofd_msghdr_free(msghdr);
+ LOGPIO(iofd, LOGL_ERROR, "enqueueing message failed (%d). Rejecting msgb\n", rc);
+ return rc;
+ }
+
+ return 0;
+}
+
+/*! osmo_io equivalent of the sendmsg(2) socket API call.
+ * The osmo_io_fd must be using OSMO_IO_FD_MODE_RECVMSG_SENDMSG.
+ *
+ * Appends the message to the internal transmit queue for eventual non-blocking
+ * sendmsg on the underlying socket/file descriptor.
+ *
+ * If the function returns success (0), it will take ownership of the msgb and
+ * internally call msgb_free() after the sendmsg request completes.
+ * In case of an error the msgb needs to be freed by the caller.
+ *
+ * \param[in] iofd file descriptor to write to
+ * \param[in] msg message buffer to send; is used to fill msgh->iov[]
+ * \param[in] sendmsg_flags Flags to pass to the send call
+ * \param[in] msgh 'struct msghdr' for name/control/flags. iov must be empty!
+ * \returns 0 in case of success; a negative value in case of error
+ */
+int osmo_iofd_sendmsg_msgb(struct osmo_io_fd *iofd, struct msgb *msg, int sendmsg_flags, const struct msghdr *msgh)
+{
+ int rc;
+ struct iofd_msghdr *msghdr;
+
+ if (OSMO_UNLIKELY(msgb_length(msg) == 0)) {
+ LOGPIO(iofd, LOGL_ERROR, "Length is 0, rejecting msgb.\n");
+ return -EINVAL;
+ }
+
+ OSMO_ASSERT(iofd->mode == OSMO_IO_FD_MODE_RECVMSG_SENDMSG);
+
+ if (OSMO_UNLIKELY(msgh->msg_namelen > sizeof(msghdr->osa))) {
+ LOGPIO(iofd, LOGL_ERROR, "osmo_iofd_sendmsg msg_namelen (%u) > supported %zu bytes\n",
+ msgh->msg_namelen, sizeof(msghdr->osa));
+ return -EINVAL;
+ }
+
+ if (OSMO_UNLIKELY(msgh->msg_iovlen)) {
+ LOGPIO(iofd, LOGL_ERROR, "osmo_iofd_sendmsg must have all in 'struct msgb', not in 'msg_iov'\n");
+ return -EINVAL;
+ }
+
+ msghdr = iofd_msghdr_alloc(iofd, IOFD_ACT_SENDMSG, msg, msgh->msg_controllen);
+ if (!msghdr)
+ return -ENOMEM;
+
+ /* copy over optional address */
+ if (msgh->msg_name) {
+ memcpy(&msghdr->osa, msgh->msg_name, msgh->msg_namelen);
+ msghdr->hdr.msg_name = &msghdr->osa.u.sa;
+ msghdr->hdr.msg_namelen = msgh->msg_namelen;
+ }
+
+ /* build iov from msgb */
+ msghdr->iov[0].iov_base = msgb_data(msghdr->msg);
+ msghdr->iov[0].iov_len = msgb_length(msghdr->msg);
+ msghdr->hdr.msg_iov = &msghdr->iov[0];
+ msghdr->hdr.msg_iovlen = 1;
+
+ /* copy over the cmsg from the msghdr */
+ if (msgh->msg_control && msgh->msg_controllen) {
+ msghdr->hdr.msg_control = msghdr->cmsg;
+ msghdr->hdr.msg_controllen = msgh->msg_controllen;
+ memcpy(msghdr->cmsg, msgh->msg_control, msgh->msg_controllen);
+ }
+
+ /* copy over msg_flags */
+ msghdr->hdr.msg_flags = sendmsg_flags;
+
+ rc = iofd_txqueue_enqueue(iofd, msghdr);
+ if (rc < 0) {
+ iofd_msghdr_free(msghdr);
+ LOGPIO(iofd, LOGL_ERROR, "enqueueing message failed (%d). Rejecting msgb\n", rc);
+ return rc;
+ }
+
+ return 0;
+}
+
+static int check_mode_callback_compat(enum osmo_io_fd_mode mode, const struct osmo_io_ops *ops)
+{
+ switch (mode) {
+ case OSMO_IO_FD_MODE_READ_WRITE:
+ if (ops->recvfrom_cb || ops->sendto_cb)
+ return false;
+ if (ops->recvmsg_cb || ops->sendmsg_cb)
+ return false;
+ break;
+ case OSMO_IO_FD_MODE_RECVFROM_SENDTO:
+ if (ops->read_cb || ops->write_cb)
+ return false;
+ if (ops->recvmsg_cb || ops->sendmsg_cb)
+ return false;
+ break;
+ case OSMO_IO_FD_MODE_RECVMSG_SENDMSG:
+ if (ops->recvfrom_cb || ops->sendto_cb)
+ return false;
+ if (ops->read_cb || ops->write_cb)
+ return false;
+ break;
+ default:
+ break;
+ }
+
+ return true;
+}
+
+/*! Allocate and setup a new iofd.
+ *
+ * Use this to create a new osmo_io_fd, specifying the osmo_io_fd_mode and osmo_io_ops, as well as optionally
+ * the file-descriptor number and a human-readable name. This is the first function you call for any
+ * osmo_io_fd.
+ *
+ * The created osmo_io_fd is not yet registered, and hence can not be used for any I/O until a subsequent
+ * call to osmo_iofd_register().
+ *
+ * The created osmo_io_fd is initialized with some default settings:
+ * * msgb allocations size: OSMO_IO_DEFAULT_MSGB_SIZE (1024)
+ * * msgb headroom: OSMO_IO_DEFAULT_MSGB_HEADROOM (128)
+ * * tx_queue depth: 32
+ *
+ * Those values may be adjusted from their defaults by using osmo_iofd_set_alloc_info() and
+ * osmo_iofd_set_txqueue_max_length() on the osmo_io_fd.
+ *
+ * \param[in] ctx the parent context from which to allocate
+ * \param[in] fd the underlying system file descriptor. May be -1 if not known yet; must then be specified
+ * at subsequent osmo_iofd_register() time.
+ * \param[in] name the optional human-readable name of the iofd; may be NULL
+ * \param[in] mode the osmo_io_fd_mode of the iofd, whether it should use read()/write(), sendto()/recvfrom()
+ * semantics.
+ * \param[in] ioops structure specifying the read/write/send/recv callbacks. Will be copied to the iofd, so
+ * the caller does not have to keep it around after issuing the osmo_iofd_setup call.
+ * \param[in] data opaque user data pointer accessible by the ioops callbacks
+ * \returns The newly allocated osmo_io_fd struct or NULL on failure
+ */
+struct osmo_io_fd *osmo_iofd_setup(const void *ctx, int fd, const char *name, enum osmo_io_fd_mode mode,
+ const struct osmo_io_ops *ioops, void *data)
+{
+ struct osmo_io_fd *iofd;
+
+ /* reject unsupported/unknown modes */
+ switch (mode) {
+ case OSMO_IO_FD_MODE_READ_WRITE:
+ case OSMO_IO_FD_MODE_RECVFROM_SENDTO:
+ case OSMO_IO_FD_MODE_RECVMSG_SENDMSG:
+ break;
+ default:
+ return NULL;
+ }
+
+ if (ioops && !check_mode_callback_compat(mode, ioops)) {
+ LOGP(DLIO, LOGL_ERROR, "iofd(%s): rejecting call-backs incompatible with mode %s\n",
+ name ? name : "unknown", osmo_iofd_mode_name(mode));
+ return NULL;
+ }
+
+ iofd = talloc_zero(ctx, struct osmo_io_fd);
+ if (!iofd)
+ return NULL;
+
+ iofd->fd = fd;
+ iofd->mode = mode;
+ IOFD_FLAG_SET(iofd, IOFD_FLAG_CLOSED);
+
+ if (name)
+ iofd->name = talloc_strdup(iofd, name);
+
+ if (ioops)
+ iofd->io_ops = *ioops;
+
+ iofd->pending = NULL;
+
+ iofd->data = data;
+
+ iofd->msgb_alloc.ctx = ctx;
+ iofd->msgb_alloc.size = OSMO_IO_DEFAULT_MSGB_SIZE;
+ iofd->msgb_alloc.headroom = OSMO_IO_DEFAULT_MSGB_HEADROOM;
+
+ iofd->tx_queue.max_length = 32;
+ INIT_LLIST_HEAD(&iofd->tx_queue.msg_queue);
+
+ return iofd;
+}
+
+/*! Set the size of the control message buffer allocated when submitting recvmsg.
+ *
+ * If your osmo_io_fd is in OSMO_IO_FD_MODE_RECVMSG_SENDMSG mode, this API function can be used to tell the
+ * osmo_io code how much memory should be allocated for the cmsg (control message) buffer when performing
+ * recvmsg(). */
+int osmo_iofd_set_cmsg_size(struct osmo_io_fd *iofd, size_t cmsg_size)
+{
+ if (iofd->mode != OSMO_IO_FD_MODE_RECVMSG_SENDMSG)
+ return -EINVAL;
+
+ iofd->cmsg_size = cmsg_size;
+ return 0;
+}
+
+/*! Register the osmo_io_fd for active I/O.
+ *
+ * Calling this function will register a previously initialized osmo_io_fd for performing I/O.
+ *
+ * If the osmo_iofd has a read_cb/recvfrom_cb_recvmsg_cb set in its osmo_io_ops, read/receive will be
+ * automatically enabled and the respective call-back is called at any time data becomes available.
+ *
+ * If there is to-be-transmitted data in the transmit queue, write will be automatically enabled, allowing
+ * the transmit queue to be drained as soon as the fd/socket becomes writable.
+ *
+ * \param[in] iofd the iofd file descriptor
+ * \param[in] fd the system fd number that will be registered. If you did not yet specify the file descriptor
+ * number during osmo_fd_setup(), or if it has changed since then, you can state the [new] file descriptor
+ * number as argument. If you wish to proceed with the previously specified file descriptor number, pass -1.
+ * \returns zero on success, a negative value on error
+*/
+int osmo_iofd_register(struct osmo_io_fd *iofd, int fd)
+{
+ int rc = 0;
+
+ if (fd >= 0)
+ iofd->fd = fd;
+ else if (iofd->fd < 0) {
+ /* this might happen if both osmo_iofd_setup() and osmo_iofd_register() are called with -1 */
+ LOGPIO(iofd, LOGL_ERROR, "Cannot register io_fd using invalid fd == %d\n", iofd->fd);
+ return -EBADF;
+ }
+
+ rc = osmo_iofd_ops.register_fd(iofd);
+ if (rc)
+ return rc;
+
+ IOFD_FLAG_UNSET(iofd, IOFD_FLAG_CLOSED);
+ if ((iofd->mode == OSMO_IO_FD_MODE_READ_WRITE && iofd->io_ops.read_cb) ||
+ (iofd->mode == OSMO_IO_FD_MODE_RECVFROM_SENDTO && iofd->io_ops.recvfrom_cb) ||
+ (iofd->mode == OSMO_IO_FD_MODE_RECVMSG_SENDMSG && iofd->io_ops.recvmsg_cb)) {
+ osmo_iofd_ops.read_enable(iofd);
+ }
+
+ if (iofd->tx_queue.current_length > 0)
+ osmo_iofd_ops.write_enable(iofd);
+
+ return rc;
+}
+
+/*! Unregister the given osmo_io_fd from osmo_io.
+ *
+ * After an osmo_io_fd has been successfully unregistered, it can no longer perform any I/O via osmo_io.
+ * However, it can be subsequently re-registered using osmo_iofd_register().
+ *
+ * \param[in] iofd the file descriptor
+ * \returns zero on success, a negative value on error
+ */
+int osmo_iofd_unregister(struct osmo_io_fd *iofd)
+{
+ return osmo_iofd_ops.unregister_fd(iofd);
+}
+
+/*! Retrieve the number of messages pending in the transmit queue.
+ *
+ * \param[in] iofd the file descriptor
+ */
+unsigned int osmo_iofd_txqueue_len(struct osmo_io_fd *iofd)
+{
+ return iofd->tx_queue.current_length;
+}
+
+/*! Clear the transmit queue of the given osmo_io_fd.
+ *
+ * This function frees all messages currently pending in the transmit queue
+ * \param[in] iofd the file descriptor
+ */
+void osmo_iofd_txqueue_clear(struct osmo_io_fd *iofd)
+{
+ struct iofd_msghdr *hdr;
+ while ((hdr = iofd_txqueue_dequeue(iofd))) {
+ msgb_free(hdr->msg);
+ iofd_msghdr_free(hdr);
+ }
+}
+
+/*! Free the given osmo_io_fd.
+ *
+ * The iofd will be automatically closed before via osmo_iofd_close() [which in turn will unregister
+ * it and clear any pending transmit queue items]. You must not reference the iofd
+ * after calling this function. However, it is safe to call this function from any of osmo_io
+ * call-backs; in this case, actual free will be internally delayed until that call-back completes.
+ *
+ * \param[in] iofd the file descriptor
+ */
+void osmo_iofd_free(struct osmo_io_fd *iofd)
+{
+ if (!iofd)
+ return;
+
+ osmo_iofd_close(iofd);
+
+ if (!IOFD_FLAG_ISSET(iofd, IOFD_FLAG_IN_CALLBACK)) {
+ talloc_free(iofd);
+ } else {
+ /* Prevent our parent context from freeing us prematurely */
+ talloc_steal(NULL, iofd);
+ IOFD_FLAG_SET(iofd, IOFD_FLAG_TO_FREE);
+ }
+}
+
+/*! Close the given osmo_io_fd.
+ *
+ * This function closes the underlying fd, unregisters it from osmo_io and clears any messages in the tx
+ * queue. The iofd itself is not freed and can be assigned a new file descriptor with osmo_iofd_register()
+ * \param[in] iofd the file descriptor
+ * \returns 0 on success, a negative value otherwise
+ */
+int osmo_iofd_close(struct osmo_io_fd *iofd)
+{
+ int rc = 0;
+
+ if (IOFD_FLAG_ISSET(iofd, IOFD_FLAG_CLOSED))
+ return rc;
+
+ IOFD_FLAG_SET(iofd, IOFD_FLAG_CLOSED);
+
+ /* Free pending msgs in tx queue */
+ osmo_iofd_txqueue_clear(iofd);
+ msgb_free(iofd->pending);
+
+ iofd->pending = NULL;
+
+ rc = osmo_iofd_ops.close(iofd);
+ iofd->fd = -1;
+ return rc;
+}
+
+/*! Set the size and headroom of the msgb allocated when receiving messages.
+ * \param[in] iofd the file descriptor
+ * \param[in] size the size of the msgb when receiving data
+ * \param[in] headroom the headroom of the msgb when receiving data
+ */
+void osmo_iofd_set_alloc_info(struct osmo_io_fd *iofd, unsigned int size, unsigned int headroom)
+{
+ iofd->msgb_alloc.headroom = headroom;
+ iofd->msgb_alloc.size = size;
+}
+
+/*! Set the maximum number of messages enqueued for sending.
+ * \param[in] iofd the file descriptor
+ * \param[in] size the maximum size of the transmit queue
+ */
+void osmo_iofd_set_txqueue_max_length(struct osmo_io_fd *iofd, unsigned int max_length)
+{
+ iofd->tx_queue.max_length = max_length;
+}
+
+/*! Retrieve the associated user-data from an osmo_io_fd.
+ *
+ * A call to this function will return the opaque user data pointer which was specified previously
+ * via osmo_iofd_setup() or via osmo_iofd_set_data().
+ *
+ * \param[in] iofd the file descriptor
+ * \returns the data that was previously set with \ref osmo_iofd_setup()
+ */
+void *osmo_iofd_get_data(const struct osmo_io_fd *iofd)
+{
+ return iofd->data;
+}
+
+/*! Set the associated user-data from an osmo_io_fd.
+ *
+ * Calling this function will set/overwrite the opaque user data pointer, which can later be retrieved using
+ * osmo_iofd_get_data().
+ *
+ * \param[in] iofd the file descriptor
+ * \param[in] data the data to set
+ */
+void osmo_iofd_set_data(struct osmo_io_fd *iofd, void *data)
+{
+ iofd->data = data;
+}
+
+/*! Retrieve the private number from an osmo_io_fd.
+ * Calling this function will retrieve the private user number previously set via osmo_iofd_set_priv_nr().
+ * \param[in] iofd the file descriptor
+ * \returns the private number that was previously set with \ref osmo_iofd_set_priv_nr()
+ */
+unsigned int osmo_iofd_get_priv_nr(const struct osmo_io_fd *iofd)
+{
+ return iofd->priv_nr;
+}
+
+/*! Set the private number of an osmo_io_fd.
+ * The priv_nr passed in via this call can later be retrieved via osmo_iofd_get_priv_nr(). It provides
+ * a way how additional context can be stored in the osmo_io_fd beyond the opaque 'data' pointer.
+ * \param[in] iofd the file descriptor
+ * \param[in] priv_nr the private number to set
+ */
+void osmo_iofd_set_priv_nr(struct osmo_io_fd *iofd, unsigned int priv_nr)
+{
+ iofd->priv_nr = priv_nr;
+}
+
+/*! Retrieve the underlying file descriptor from an osmo_io_fd.
+ * \param[in] iofd the file descriptor
+ * \returns the underlying file descriptor number */
+int osmo_iofd_get_fd(const struct osmo_io_fd *iofd)
+{
+ return iofd->fd;
+}
+
+/*! Retrieve the human-readable name of the given osmo_io_fd.
+ * \param[in] iofd the file descriptor
+ * \returns the name of the iofd as given in \ref osmo_iofd_setup() */
+const char *osmo_iofd_get_name(const struct osmo_io_fd *iofd)
+{
+ return iofd->name;
+}
+
+/*! Set the human-readable name of the file descriptor.
+ * The given name will be used as context by all related logging and future calls to osmo_iofd_get_name().
+ * \param[in] iofd the file descriptor
+ * \param[in] name the name to set on the file descriptor */
+void osmo_iofd_set_name(struct osmo_io_fd *iofd, const char *name)
+{
+ osmo_talloc_replace_string(iofd, &iofd->name, name);
+}
+
+/*! Set the osmo_io_ops calbacks for an osmo_io_fd.
+ * This function can be used to update/overwrite the call-back functions for the given osmo_io_fd; it
+ * replaces the currently-set call-back function pointers from a previous call to osmo_iofd_set_ioops()
+ * or the original osmo_iofd_setup().
+ * \param[in] iofd Target iofd file descriptor
+ * \param[in] ioops osmo_io_ops structure to be copied to the osmo_io_fd.
+ * \returns 0 on success, negative on error */
+int osmo_iofd_set_ioops(struct osmo_io_fd *iofd, const struct osmo_io_ops *ioops)
+{
+ if (!check_mode_callback_compat(iofd->mode, ioops)) {
+ LOGPIO(iofd, LOGL_ERROR, "rejecting call-backs incompatible with mode %s\n",
+ osmo_iofd_mode_name(iofd->mode));
+ return -EINVAL;
+ }
+
+ iofd->io_ops = *ioops;
+
+ switch (iofd->mode) {
+ case OSMO_IO_FD_MODE_READ_WRITE:
+ if (iofd->io_ops.read_cb)
+ osmo_iofd_ops.read_enable(iofd);
+ else
+ osmo_iofd_ops.read_disable(iofd);
+ break;
+ case OSMO_IO_FD_MODE_RECVFROM_SENDTO:
+ if (iofd->io_ops.recvfrom_cb)
+ osmo_iofd_ops.read_enable(iofd);
+ else
+ osmo_iofd_ops.read_disable(iofd);
+ break;
+ case OSMO_IO_FD_MODE_RECVMSG_SENDMSG:
+ if (iofd->io_ops.recvmsg_cb)
+ osmo_iofd_ops.read_enable(iofd);
+ else
+ osmo_iofd_ops.read_disable(iofd);
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+
+ return 0;
+}
+
+/*! Retrieve the osmo_io_ops for an iofd.
+ * \param[in] iofd Target iofd file descriptor
+ * \param[in] ioops caller-allocated osmo_io_ops structure to be filled */
+void osmo_iofd_get_ioops(struct osmo_io_fd *iofd, struct osmo_io_ops *ioops)
+{
+ *ioops = iofd->io_ops;
+}
+
+/*! Request notification of the user if/when a client socket is connected.
+ * Calling this function will request osmo_io to notify the user (via
+ * write call-back) once a non-blocking outbound connect() of the
+ * socket completes.
+ *
+ * This only works for connection oriented sockets in either
+ * OSMO_IO_FD_MODE_READ_WRITE or OSMO_IO_FD_MODE_RECVMSG_SENDMSG mode.
+ *
+ * \param[in] iofd the file descriptor */
+void osmo_iofd_notify_connected(struct osmo_io_fd *iofd)
+{
+ OSMO_ASSERT(iofd->mode == OSMO_IO_FD_MODE_READ_WRITE ||
+ iofd->mode == OSMO_IO_FD_MODE_RECVMSG_SENDMSG);
+ osmo_iofd_ops.notify_connected(iofd);
+}
+
+/*! @} */
+
+#endif /* ifndef(EMBEDDED) */
diff --git a/src/core/osmo_io_internal.h b/src/core/osmo_io_internal.h
new file mode 100644
index 00000000..a4b0749d
--- /dev/null
+++ b/src/core/osmo_io_internal.h
@@ -0,0 +1,166 @@
+/*! \file osmo_io_internal.h */
+
+#pragma once
+
+#include <unistd.h>
+#include <stdbool.h>
+#include <netinet/sctp.h>
+
+#include <osmocom/core/osmo_io.h>
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/socket.h>
+
+#include "../config.h"
+
+#define OSMO_IO_DEFAULT_MSGB_SIZE 1024
+#define OSMO_IO_DEFAULT_MSGB_HEADROOM 128
+
+extern const struct iofd_backend_ops iofd_poll_ops;
+#define OSMO_IO_BACKEND_DEFAULT "POLL"
+
+#if defined(HAVE_URING)
+extern const struct iofd_backend_ops iofd_uring_ops;
+#endif
+
+struct iofd_backend_ops {
+ int (*register_fd)(struct osmo_io_fd *iofd);
+ int (*unregister_fd)(struct osmo_io_fd *iofd);
+ int (*close)(struct osmo_io_fd *iofd);
+ void (*write_enable)(struct osmo_io_fd *iofd);
+ void (*write_disable)(struct osmo_io_fd *iofd);
+ void (*read_enable)(struct osmo_io_fd *iofd);
+ void (*read_disable)(struct osmo_io_fd *iofd);
+ void (*notify_connected)(struct osmo_io_fd *iofd);
+};
+
+#define IOFD_FLAG_CLOSED (1<<0)
+#define IOFD_FLAG_IN_CALLBACK (1<<1)
+#define IOFD_FLAG_TO_FREE (1<<2)
+#define IOFD_FLAG_NOTIFY_CONNECTED (1<<3)
+#define IOFD_FLAG_FD_REGISTERED (1<<4)
+
+#define IOFD_FLAG_SET(iofd, flag) \
+ (iofd)->flags |= (flag)
+
+#define IOFD_FLAG_UNSET(iofd, flag) \
+ (iofd)->flags &= ~(flag)
+
+#define IOFD_FLAG_ISSET(iofd, flag) ((iofd)->flags & (flag))
+
+struct osmo_io_fd {
+ /*! linked list for internal management */
+ struct llist_head list;
+ /*! actual operating-system level file decriptor */
+ int fd;
+ /*! type of read/write mode to use */
+ enum osmo_io_fd_mode mode;
+
+ /*! flags to guard closing/freeing of iofd */
+ uint32_t flags;
+
+ /*! human-readable name to associte with fd */
+ char *name;
+
+ /*! send/recv (msg) callback functions */
+ struct osmo_io_ops io_ops;
+ /*! Pending msgb to keep partial data during segmentation */
+ struct msgb *pending;
+
+ /*! data pointer passed through to call-back function */
+ void *data;
+ /*! private number, extending \a data */
+ unsigned int priv_nr;
+
+ /*! size of iofd_msghdr.cmsg[] when allocated in recvmsg path */
+ size_t cmsg_size;
+
+ struct {
+ /*! talloc context from which to allocate msgb when reading */
+ const void *ctx;
+ /*! size of msgb to allocate (excluding headroom) */
+ unsigned int size;
+ /*! headroom to allocate when allocating msgb's */
+ unsigned int headroom;
+ } msgb_alloc;
+
+ struct {
+ /*! maximum length of write queue */
+ unsigned int max_length;
+ /*! current length of write queue */
+ unsigned int current_length;
+ /*! actual linked list implementing the transmit queue */
+ struct llist_head msg_queue;
+ } tx_queue;
+
+ union {
+ struct {
+ struct osmo_fd ofd;
+ } poll;
+ struct {
+ bool read_enabled;
+ bool write_enabled;
+ void *read_msghdr;
+ void *write_msghdr;
+ /* TODO: index into array of registered fd's? */
+ /* osmo_fd for non-blocking connect handling */
+ struct osmo_fd connect_ofd;
+ } uring;
+ } u;
+};
+
+enum iofd_msg_action {
+ IOFD_ACT_READ,
+ IOFD_ACT_WRITE,
+ IOFD_ACT_RECVFROM,
+ IOFD_ACT_SENDTO,
+ IOFD_ACT_RECVMSG,
+ IOFD_ACT_SENDMSG,
+};
+
+
+/*! serialized version of 'struct msghdr' employed by sendmsg/recvmsg */
+struct iofd_msghdr {
+ /*! entry into osmo_io_fd.tx_queue.msg_queue */
+ struct llist_head list;
+ enum iofd_msg_action action;
+ /*! the 'struct msghdr' we are wrapping/ecapsulating here */
+ struct msghdr hdr;
+ /*! socket address of the remote peer */
+ struct osmo_sockaddr osa;
+ /*! io-vector we need to pass as argument to sendmsg/recvmsg; is set up
+ * to point into msg below */
+ struct iovec iov[1];
+ /*! flags we pass as argument to sendmsg / recvmsg */
+ int flags;
+
+ /*! message-buffer containing data for this I/O operation */
+ struct msgb *msg;
+ /*! I/O file descriptor on which we perform this I/O operation */
+ struct osmo_io_fd *iofd;
+
+ /*! control message buffer for passing sctp_sndrcvinfo along */
+ char cmsg[0]; /* size is determined by iofd->cmsg_size on recvmsg, and by mcghdr->msg_controllen on sendmsg */
+};
+
+enum iofd_seg_act {
+ IOFD_SEG_ACT_HANDLE_ONE,
+ IOFD_SEG_ACT_HANDLE_MORE,
+ IOFD_SEG_ACT_DEFER,
+};
+
+struct iofd_msghdr *iofd_msghdr_alloc(struct osmo_io_fd *iofd, enum iofd_msg_action action, struct msgb *msg, size_t cmsg_size);
+void iofd_msghdr_free(struct iofd_msghdr *msghdr);
+
+struct msgb *iofd_msgb_alloc(struct osmo_io_fd *iofd);
+struct msgb *iofd_msgb_pending(struct osmo_io_fd *iofd);
+struct msgb *iofd_msgb_pending_or_alloc(struct osmo_io_fd *iofd);
+
+void iofd_handle_recv(struct osmo_io_fd *iofd, struct msgb *msg, int rc, struct iofd_msghdr *msghdr);
+void iofd_handle_send_completion(struct osmo_io_fd *iofd, int rc, struct iofd_msghdr *msghdr);
+void iofd_handle_segmented_read(struct osmo_io_fd *iofd, struct msgb *msg, int rc);
+
+int iofd_txqueue_enqueue(struct osmo_io_fd *iofd, struct iofd_msghdr *msghdr);
+void iofd_txqueue_enqueue_front(struct osmo_io_fd *iofd, struct iofd_msghdr *msghdr);
+struct iofd_msghdr *iofd_txqueue_dequeue(struct osmo_io_fd *iofd);
diff --git a/src/core/osmo_io_poll.c b/src/core/osmo_io_poll.c
new file mode 100644
index 00000000..c4bb376d
--- /dev/null
+++ b/src/core/osmo_io_poll.c
@@ -0,0 +1,201 @@
+/*! \file osmo_io_poll.c
+ * New osmocom async I/O API.
+ *
+ * (C) 2022 by Harald Welte <laforge@osmocom.org>
+ * (C) 2022-2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * Author: Daniel Willmann <dwillmann@sysmocom.de>
+ *
+ * All Rights Reserved.
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include "../config.h"
+#ifndef EMBEDDED
+
+#include <errno.h>
+#include <stdio.h>
+#include <talloc.h>
+#include <unistd.h>
+#include <stdbool.h>
+#include <sys/socket.h>
+
+#include <osmocom/core/osmo_io.h>
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+
+#include "osmo_io_internal.h"
+
+static void iofd_poll_ofd_cb_recvmsg_sendmsg(struct osmo_fd *ofd, unsigned int what)
+{
+ struct osmo_io_fd *iofd = ofd->data;
+ struct msgb *msg;
+ int rc, flags = 0;
+
+ if (what & OSMO_FD_READ) {
+ struct iofd_msghdr hdr;
+
+ msg = iofd_msgb_pending_or_alloc(iofd);
+ if (!msg) {
+ LOGPIO(iofd, LOGL_ERROR, "Could not allocate msgb for reading\n");
+ OSMO_ASSERT(0);
+ }
+
+ hdr.msg = msg;
+ hdr.iov[0].iov_base = msg->tail;
+ hdr.iov[0].iov_len = msgb_tailroom(msg);
+ hdr.hdr = (struct msghdr) {
+ .msg_iov = &hdr.iov[0],
+ .msg_iovlen = 1,
+ .msg_name = &hdr.osa.u.sa,
+ .msg_namelen = sizeof(struct osmo_sockaddr),
+ };
+ if (iofd->mode == OSMO_IO_FD_MODE_RECVMSG_SENDMSG) {
+ hdr.hdr.msg_control = alloca(iofd->cmsg_size);
+ hdr.hdr.msg_controllen = iofd->cmsg_size;
+ }
+
+ rc = recvmsg(ofd->fd, &hdr.hdr, flags);
+ if (rc > 0)
+ msgb_put(msg, rc);
+
+ iofd_handle_recv(iofd, msg, (rc < 0 && errno > 0) ? -errno : rc, &hdr);
+ }
+
+ if (IOFD_FLAG_ISSET(iofd, IOFD_FLAG_CLOSED))
+ return;
+
+ if (what & OSMO_FD_WRITE) {
+ struct iofd_msghdr *msghdr = iofd_txqueue_dequeue(iofd);
+ if (msghdr) {
+ rc = sendmsg(ofd->fd, &msghdr->hdr, msghdr->flags);
+ iofd_handle_send_completion(iofd, (rc < 0 && errno > 0) ? -errno : rc, msghdr);
+ } else {
+ /* Socket is writable, but we have no data to send. A non-blocking/async
+ connect() is signalled this way. */
+ switch (iofd->mode) {
+ case OSMO_IO_FD_MODE_READ_WRITE:
+ iofd->io_ops.write_cb(iofd, 0, NULL);
+ break;
+ case OSMO_IO_FD_MODE_RECVFROM_SENDTO:
+ iofd->io_ops.sendto_cb(iofd, 0, NULL, NULL);
+ break;
+ case OSMO_IO_FD_MODE_RECVMSG_SENDMSG:
+ iofd->io_ops.sendmsg_cb(iofd, 0, NULL);
+ break;
+ default:
+ break;
+ }
+ if (osmo_iofd_txqueue_len(iofd) == 0)
+ iofd_poll_ops.write_disable(iofd);
+ }
+ }
+}
+
+static int iofd_poll_ofd_cb_dispatch(struct osmo_fd *ofd, unsigned int what)
+{
+ struct osmo_io_fd *iofd = ofd->data;
+
+ IOFD_FLAG_SET(iofd, IOFD_FLAG_IN_CALLBACK);
+ iofd_poll_ofd_cb_recvmsg_sendmsg(ofd, what);
+ IOFD_FLAG_UNSET(iofd, IOFD_FLAG_IN_CALLBACK);
+
+ if (IOFD_FLAG_ISSET(iofd, IOFD_FLAG_TO_FREE)) {
+ talloc_free(iofd);
+ return 0;
+ }
+
+ return 0;
+}
+
+static int iofd_poll_register(struct osmo_io_fd *iofd)
+{
+ struct osmo_fd *ofd = &iofd->u.poll.ofd;
+ int rc;
+
+ if (IOFD_FLAG_ISSET(iofd, IOFD_FLAG_FD_REGISTERED))
+ return 0;
+ osmo_fd_setup(ofd, iofd->fd, 0, &iofd_poll_ofd_cb_dispatch, iofd, 0);
+ rc = osmo_fd_register(ofd);
+ if (!rc)
+ IOFD_FLAG_SET(iofd, IOFD_FLAG_FD_REGISTERED);
+ return rc;
+}
+
+static int iofd_poll_unregister(struct osmo_io_fd *iofd)
+{
+ struct osmo_fd *ofd = &iofd->u.poll.ofd;
+
+ if (!IOFD_FLAG_ISSET(iofd, IOFD_FLAG_FD_REGISTERED))
+ return 0;
+ osmo_fd_unregister(ofd);
+ IOFD_FLAG_UNSET(iofd, IOFD_FLAG_FD_REGISTERED);
+
+ return 0;
+}
+
+static int iofd_poll_close(struct osmo_io_fd *iofd)
+{
+ iofd_poll_unregister(iofd);
+ osmo_fd_close(&iofd->u.poll.ofd);
+
+ return 0;
+}
+
+static void iofd_poll_read_enable(struct osmo_io_fd *iofd)
+{
+ osmo_fd_read_enable(&iofd->u.poll.ofd);
+}
+
+static void iofd_poll_read_disable(struct osmo_io_fd *iofd)
+{
+ osmo_fd_read_disable(&iofd->u.poll.ofd);
+}
+
+static void iofd_poll_write_enable(struct osmo_io_fd *iofd)
+{
+ osmo_fd_write_enable(&iofd->u.poll.ofd);
+}
+
+static void iofd_poll_write_disable(struct osmo_io_fd *iofd)
+{
+ osmo_fd_write_disable(&iofd->u.poll.ofd);
+}
+
+static void iofd_poll_notify_connected(struct osmo_io_fd *iofd)
+{
+ int rc;
+
+ rc = iofd_poll_register(iofd);
+ if (rc < 0)
+ return;
+ osmo_fd_write_enable(&iofd->u.poll.ofd);
+}
+
+const struct iofd_backend_ops iofd_poll_ops = {
+ .register_fd = iofd_poll_register,
+ .unregister_fd = iofd_poll_unregister,
+ .close = iofd_poll_close,
+ .write_enable = iofd_poll_write_enable,
+ .write_disable = iofd_poll_write_disable,
+ .read_enable = iofd_poll_read_enable,
+ .read_disable = iofd_poll_read_disable,
+ .notify_connected = iofd_poll_notify_connected,
+};
+
+#endif /* ifndef EMBEDDED */
diff --git a/src/core/osmo_io_uring.c b/src/core/osmo_io_uring.c
new file mode 100644
index 00000000..569f1505
--- /dev/null
+++ b/src/core/osmo_io_uring.c
@@ -0,0 +1,532 @@
+/*! \file osmo_io_uring.c
+ * io_uring backend for osmo_io.
+ *
+ * (C) 2022-2023 by sysmocom s.f.m.c.
+ * Author: Daniel Willmann <daniel@sysmocom.de>
+ * (C) 2023-2024 by Harald Welte <laforge@osmocom.org>
+ *
+ * All Rights Reserved.
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+/* TODO:
+ * Parameters:
+ * - number of simultaneous read/write in uring for given fd
+ *
+ */
+
+#include "../config.h"
+#if defined(__linux__)
+
+#include <stdio.h>
+#include <talloc.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdbool.h>
+#include <errno.h>
+
+#include <netinet/in.h>
+#include <netinet/sctp.h>
+#include <sys/eventfd.h>
+#include <liburing.h>
+
+#include <osmocom/core/osmo_io.h>
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/socket.h>
+
+#include "osmo_io_internal.h"
+
+#define IOFD_URING_ENTRIES 4096
+
+struct osmo_io_uring {
+ struct osmo_fd event_ofd;
+ struct io_uring ring;
+};
+
+static __thread struct osmo_io_uring g_ring;
+
+static void iofd_uring_cqe(struct io_uring *ring);
+
+/*! read call-back for eventfd notifying us if entries are in the completion queue */
+static int iofd_uring_poll_cb(struct osmo_fd *ofd, unsigned int what)
+{
+ struct io_uring *ring = ofd->data;
+ eventfd_t val;
+ int rc;
+
+ if (what & OSMO_FD_READ) {
+ rc = eventfd_read(ofd->fd, &val);
+ if (rc < 0) {
+ LOGP(DLIO, LOGL_ERROR, "eventfd_read() returned error\n");
+ return rc;
+ }
+
+ iofd_uring_cqe(ring);
+ }
+ if (what & OSMO_FD_WRITE)
+ OSMO_ASSERT(0);
+
+ return 0;
+}
+
+/*! initialize the uring and tie it into our event loop */
+void osmo_iofd_uring_init(void)
+{
+ int rc, evfd;
+
+ rc = io_uring_queue_init(IOFD_URING_ENTRIES, &g_ring.ring, 0);
+ if (rc < 0)
+ osmo_panic("failure during io_uring_queue_init(): %s\n", strerror(-rc));
+
+ rc = eventfd(0, 0);
+ if (rc < 0) {
+ io_uring_queue_exit(&g_ring.ring);
+ osmo_panic("failure creating eventfd(0, 0) for io_uring: %s\n", strerror(-rc));
+ }
+ evfd = rc;
+
+ osmo_fd_setup(&g_ring.event_ofd, evfd, OSMO_FD_READ, iofd_uring_poll_cb, &g_ring.ring, 0);
+ rc = osmo_fd_register(&g_ring.event_ofd);
+ if (rc < 0) {
+ close(evfd);
+ io_uring_queue_exit(&g_ring.ring);
+ osmo_panic("failure registering io_uring-eventfd as osmo_fd: %d\n", rc);
+ }
+ rc = io_uring_register_eventfd(&g_ring.ring, evfd);
+ if (rc < 0) {
+ osmo_fd_unregister(&g_ring.event_ofd);
+ close(evfd);
+ io_uring_queue_exit(&g_ring.ring);
+ osmo_panic("failure registering eventfd with io_uring: %s\n", strerror(-rc));
+ }
+}
+
+
+static void iofd_uring_submit_recv(struct osmo_io_fd *iofd, enum iofd_msg_action action)
+{
+ struct msgb *msg;
+ struct iofd_msghdr *msghdr;
+ struct io_uring_sqe *sqe;
+
+ msg = iofd_msgb_pending_or_alloc(iofd);
+ if (!msg) {
+ LOGPIO(iofd, LOGL_ERROR, "Could not allocate msgb for reading\n");
+ OSMO_ASSERT(0);
+ }
+
+ msghdr = iofd_msghdr_alloc(iofd, action, msg, iofd->cmsg_size);
+ if (!msghdr) {
+ LOGPIO(iofd, LOGL_ERROR, "Could not allocate msghdr for reading\n");
+ OSMO_ASSERT(0);
+ }
+
+ msghdr->iov[0].iov_base = msg->tail;
+ msghdr->iov[0].iov_len = msgb_tailroom(msg);
+
+ switch (action) {
+ case IOFD_ACT_READ:
+ break;
+ case IOFD_ACT_RECVMSG:
+ msghdr->hdr.msg_control = msghdr->cmsg;
+ msghdr->hdr.msg_controllen = iofd->cmsg_size;
+ /* fall-through */
+ case IOFD_ACT_RECVFROM:
+ msghdr->hdr.msg_iov = &msghdr->iov[0];
+ msghdr->hdr.msg_iovlen = 1;
+ msghdr->hdr.msg_name = &msghdr->osa.u.sa;
+ msghdr->hdr.msg_namelen = osmo_sockaddr_size(&msghdr->osa);
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+
+ sqe = io_uring_get_sqe(&g_ring.ring);
+ if (!sqe) {
+ LOGPIO(iofd, LOGL_ERROR, "Could not get io_uring_sqe\n");
+ OSMO_ASSERT(0);
+ }
+
+ switch (action) {
+ case IOFD_ACT_READ:
+ io_uring_prep_readv(sqe, iofd->fd, msghdr->iov, 1, 0);
+ break;
+ case IOFD_ACT_RECVMSG:
+ case IOFD_ACT_RECVFROM:
+ io_uring_prep_recvmsg(sqe, iofd->fd, &msghdr->hdr, msghdr->flags);
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+ io_uring_sqe_set_data(sqe, msghdr);
+
+ io_uring_submit(&g_ring.ring);
+ /* NOTE: This only works if we have one read per fd */
+ iofd->u.uring.read_msghdr = msghdr;
+}
+
+/*! completion call-back for READ/RECVFROM */
+static void iofd_uring_handle_recv(struct iofd_msghdr *msghdr, int rc)
+{
+ struct osmo_io_fd *iofd = msghdr->iofd;
+ struct msgb *msg = msghdr->msg;
+
+ if (rc > 0)
+ msgb_put(msg, rc);
+
+ if (!IOFD_FLAG_ISSET(iofd, IOFD_FLAG_CLOSED))
+ iofd_handle_recv(iofd, msg, rc, msghdr);
+
+ if (iofd->u.uring.read_enabled && !IOFD_FLAG_ISSET(iofd, IOFD_FLAG_CLOSED))
+ iofd_uring_submit_recv(iofd, msghdr->action);
+ else
+ iofd->u.uring.read_msghdr = NULL;
+
+
+ iofd_msghdr_free(msghdr);
+}
+
+static int iofd_uring_submit_tx(struct osmo_io_fd *iofd);
+
+/*! completion call-back for WRITE/SENDTO */
+static void iofd_uring_handle_tx(struct iofd_msghdr *msghdr, int rc)
+{
+ struct osmo_io_fd *iofd = msghdr->iofd;
+
+ /* Detach msghdr from iofd. It might get freed here or it is freed during iofd_handle_send_completion().
+ * If there is pending data to send, iofd_uring_submit_tx() will attach it again.
+ * iofd_handle_send_completion() will invoke a callback function to signal the possibility of write/send.
+ * This callback function might close iofd, leading to the potential freeing of iofd->u.uring.write_msghdr if
+ * still attached. Since iofd_handle_send_completion() frees msghdr at the end of the function, detaching
+ * msghdr here prevents a double-free bug. */
+ if (iofd->u.uring.write_msghdr == msghdr)
+ iofd->u.uring.write_msghdr = NULL;
+
+ if (OSMO_UNLIKELY(IOFD_FLAG_ISSET(iofd, IOFD_FLAG_CLOSED))) {
+ msgb_free(msghdr->msg);
+ iofd_msghdr_free(msghdr);
+ } else {
+ iofd_handle_send_completion(iofd, rc, msghdr);
+ }
+
+ /* submit the next to-be-transmitted message for this file descriptor */
+ if (iofd->u.uring.write_enabled && !IOFD_FLAG_ISSET(iofd, IOFD_FLAG_CLOSED))
+ iofd_uring_submit_tx(iofd);
+}
+
+/*! handle completion of a single I/O message */
+static void iofd_uring_handle_completion(struct iofd_msghdr *msghdr, int res)
+{
+ struct osmo_io_fd *iofd = msghdr->iofd;
+
+ IOFD_FLAG_SET(iofd, IOFD_FLAG_IN_CALLBACK);
+
+ switch (msghdr->action) {
+ case IOFD_ACT_READ:
+ case IOFD_ACT_RECVFROM:
+ case IOFD_ACT_RECVMSG:
+ iofd_uring_handle_recv(msghdr, res);
+ break;
+ case IOFD_ACT_WRITE:
+ case IOFD_ACT_SENDTO:
+ case IOFD_ACT_SENDMSG:
+ iofd_uring_handle_tx(msghdr, res);
+ break;
+ default:
+ OSMO_ASSERT(0)
+ }
+
+ IOFD_FLAG_UNSET(iofd, IOFD_FLAG_IN_CALLBACK);
+
+ if (IOFD_FLAG_ISSET(iofd, IOFD_FLAG_TO_FREE) && !iofd->u.uring.read_msghdr && !iofd->u.uring.write_msghdr)
+ talloc_free(iofd);
+}
+
+/*! process all pending completion queue entries in given io_uring */
+static void iofd_uring_cqe(struct io_uring *ring)
+{
+ int rc;
+ struct io_uring_cqe *cqe;
+ struct iofd_msghdr *msghdr;
+
+ while (io_uring_peek_cqe(ring, &cqe) == 0) {
+
+ msghdr = io_uring_cqe_get_data(cqe);
+ if (!msghdr) {
+ LOGP(DLIO, LOGL_DEBUG, "Cancellation returned\n");
+ io_uring_cqe_seen(ring, cqe);
+ continue;
+ }
+ if (!msghdr->iofd) {
+ io_uring_cqe_seen(ring, cqe);
+ iofd_msghdr_free(msghdr);
+ continue;
+ }
+
+ rc = cqe->res;
+ /* Hand the entry back to the kernel before */
+ io_uring_cqe_seen(ring, cqe);
+
+ iofd_uring_handle_completion(msghdr, rc);
+
+ }
+}
+
+/*! will submit the next to-be-transmitted message for given iofd */
+static int iofd_uring_submit_tx(struct osmo_io_fd *iofd)
+{
+ struct io_uring_sqe *sqe;
+ struct iofd_msghdr *msghdr;
+
+ msghdr = iofd_txqueue_dequeue(iofd);
+ if (!msghdr)
+ return -ENODATA;
+
+ sqe = io_uring_get_sqe(&g_ring.ring);
+ if (!sqe) {
+ LOGPIO(iofd, LOGL_ERROR, "Could not get io_uring_sqe\n");
+ OSMO_ASSERT(0);
+ }
+
+ io_uring_sqe_set_data(sqe, msghdr);
+
+ switch (msghdr->action) {
+ case IOFD_ACT_WRITE:
+ case IOFD_ACT_SENDTO:
+ case IOFD_ACT_SENDMSG:
+ io_uring_prep_sendmsg(sqe, msghdr->iofd->fd, &msghdr->hdr, msghdr->flags);
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+
+ io_uring_submit(&g_ring.ring);
+ iofd->u.uring.write_msghdr = msghdr;
+
+ return 0;
+}
+
+static void iofd_uring_write_enable(struct osmo_io_fd *iofd);
+static void iofd_uring_read_enable(struct osmo_io_fd *iofd);
+
+static int iofd_uring_register(struct osmo_io_fd *iofd)
+{
+ return 0;
+}
+
+static int iofd_uring_unregister(struct osmo_io_fd *iofd)
+{
+ struct io_uring_sqe *sqe;
+ struct iofd_msghdr *msghdr;
+
+ if (iofd->u.uring.read_msghdr) {
+ msghdr = iofd->u.uring.read_msghdr;
+ sqe = io_uring_get_sqe(&g_ring.ring);
+ OSMO_ASSERT(sqe != NULL);
+ io_uring_sqe_set_data(sqe, NULL);
+ LOGPIO(iofd, LOGL_DEBUG, "Cancelling read\n");
+ iofd->u.uring.read_msghdr = NULL;
+ talloc_steal(OTC_GLOBAL, msghdr);
+ msghdr->iofd = NULL;
+ io_uring_prep_cancel(sqe, msghdr, 0);
+ }
+
+ if (iofd->u.uring.write_msghdr) {
+ msghdr = iofd->u.uring.write_msghdr;
+ sqe = io_uring_get_sqe(&g_ring.ring);
+ OSMO_ASSERT(sqe != NULL);
+ io_uring_sqe_set_data(sqe, NULL);
+ LOGPIO(iofd, LOGL_DEBUG, "Cancelling write\n");
+ iofd->u.uring.write_msghdr = NULL;
+ talloc_steal(OTC_GLOBAL, msghdr);
+ msgb_free(msghdr->msg);
+ msghdr->iofd = NULL;
+ io_uring_prep_cancel(sqe, msghdr, 0);
+ }
+ io_uring_submit(&g_ring.ring);
+
+ if (IOFD_FLAG_ISSET(iofd, IOFD_FLAG_NOTIFY_CONNECTED)) {
+ osmo_fd_unregister(&iofd->u.uring.connect_ofd);
+ IOFD_FLAG_UNSET(iofd, IOFD_FLAG_NOTIFY_CONNECTED);
+ }
+
+ return 0;
+}
+
+static void iofd_uring_write_enable(struct osmo_io_fd *iofd)
+{
+ iofd->u.uring.write_enabled = true;
+
+ if (iofd->u.uring.write_msghdr)
+ return;
+
+ /* This function is called again, once the socket is connected. */
+ if (IOFD_FLAG_ISSET(iofd, IOFD_FLAG_NOTIFY_CONNECTED))
+ return;
+
+ if (osmo_iofd_txqueue_len(iofd) > 0)
+ iofd_uring_submit_tx(iofd);
+ else if (iofd->mode == OSMO_IO_FD_MODE_READ_WRITE) {
+ /* Empty write request to check when the socket is connected */
+ struct iofd_msghdr *msghdr;
+ struct io_uring_sqe *sqe;
+ struct msgb *msg = msgb_alloc_headroom(0, 0, "io_uring write dummy");
+ if (!msg) {
+ LOGPIO(iofd, LOGL_ERROR, "Could not allocate msgb for writing\n");
+ OSMO_ASSERT(0);
+ }
+ msghdr = iofd_msghdr_alloc(iofd, IOFD_ACT_WRITE, msg, 0);
+ if (!msghdr) {
+ LOGPIO(iofd, LOGL_ERROR, "Could not allocate msghdr for writing\n");
+ OSMO_ASSERT(0);
+ }
+
+ msghdr->iov[0].iov_base = msgb_data(msg);
+ msghdr->iov[0].iov_len = msgb_length(msg);
+
+ sqe = io_uring_get_sqe(&g_ring.ring);
+ if (!sqe) {
+ LOGPIO(iofd, LOGL_ERROR, "Could not get io_uring_sqe\n");
+ OSMO_ASSERT(0);
+ }
+ io_uring_prep_writev(sqe, iofd->fd, msghdr->iov, 1, 0);
+ io_uring_sqe_set_data(sqe, msghdr);
+
+ io_uring_submit(&g_ring.ring);
+ iofd->u.uring.write_msghdr = msghdr;
+ }
+}
+
+static void iofd_uring_write_disable(struct osmo_io_fd *iofd)
+{
+ iofd->u.uring.write_enabled = false;
+}
+
+static void iofd_uring_read_enable(struct osmo_io_fd *iofd)
+{
+ iofd->u.uring.read_enabled = true;
+
+ if (iofd->u.uring.read_msghdr)
+ return;
+
+ /* This function is called again, once the socket is connected. */
+ if (IOFD_FLAG_ISSET(iofd, IOFD_FLAG_NOTIFY_CONNECTED))
+ return;
+
+ switch (iofd->mode) {
+ case OSMO_IO_FD_MODE_READ_WRITE:
+ iofd_uring_submit_recv(iofd, IOFD_ACT_READ);
+ break;
+ case OSMO_IO_FD_MODE_RECVFROM_SENDTO:
+ iofd_uring_submit_recv(iofd, IOFD_ACT_RECVFROM);
+ break;
+ case OSMO_IO_FD_MODE_RECVMSG_SENDMSG:
+ iofd_uring_submit_recv(iofd, IOFD_ACT_RECVMSG);
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static void iofd_uring_read_disable(struct osmo_io_fd *iofd)
+{
+ iofd->u.uring.read_enabled = false;
+}
+
+static int iofd_uring_close(struct osmo_io_fd *iofd)
+{
+ iofd_uring_read_disable(iofd);
+ iofd_uring_write_disable(iofd);
+ iofd_uring_unregister(iofd);
+ return close(iofd->fd);
+}
+
+/* called via osmocom poll/select main handling once outbound non-blocking connect() completes */
+static int iofd_uring_connected_cb(struct osmo_fd *ofd, unsigned int what)
+{
+ struct osmo_io_fd *iofd = ofd->data;
+
+ LOGPIO(iofd, LOGL_DEBUG, "Socket connected or failed.\n");
+
+ if (!(what & OSMO_FD_WRITE))
+ return 0;
+
+ /* Unregister from poll/select handling. */
+ osmo_fd_unregister(ofd);
+ IOFD_FLAG_UNSET(iofd, IOFD_FLAG_NOTIFY_CONNECTED);
+
+ /* Notify the application about this via a zero-length write completion call-back. */
+ IOFD_FLAG_SET(iofd, IOFD_FLAG_IN_CALLBACK);
+ switch (iofd->mode) {
+ case OSMO_IO_FD_MODE_READ_WRITE:
+ iofd->io_ops.write_cb(iofd, 0, NULL);
+ break;
+ case OSMO_IO_FD_MODE_RECVFROM_SENDTO:
+ iofd->io_ops.sendto_cb(iofd, 0, NULL, NULL);
+ break;
+ case OSMO_IO_FD_MODE_RECVMSG_SENDMSG:
+ iofd->io_ops.sendmsg_cb(iofd, 0, NULL);
+ break;
+ }
+ IOFD_FLAG_UNSET(iofd, IOFD_FLAG_IN_CALLBACK);
+
+ /* If write/read notifications are pending, enable it now. */
+ if (iofd->u.uring.write_enabled && !IOFD_FLAG_ISSET(iofd, IOFD_FLAG_CLOSED))
+ iofd_uring_write_enable(iofd);
+ if (iofd->u.uring.read_enabled && !IOFD_FLAG_ISSET(iofd, IOFD_FLAG_CLOSED))
+ iofd_uring_read_enable(iofd);
+
+ if (IOFD_FLAG_ISSET(iofd, IOFD_FLAG_TO_FREE) && !iofd->u.uring.read_msghdr && !iofd->u.uring.write_msghdr)
+ talloc_free(iofd);
+ return 0;
+}
+
+static void iofd_uring_notify_connected(struct osmo_io_fd *iofd)
+{
+ if (iofd->mode == OSMO_IO_FD_MODE_RECVMSG_SENDMSG) {
+ /* Don't call this function after enabling read or write. */
+ OSMO_ASSERT(!iofd->u.uring.write_enabled && !iofd->u.uring.read_enabled);
+
+ /* Use a temporary osmo_fd which we can use to notify us once the connection is established
+ * or failed (indicated by FD becoming writable).
+ * This is needed as (at least for SCTP sockets) one cannot submit a zero-length writev/sendmsg
+ * in order to get notification when the socekt is writable.*/
+ if (!IOFD_FLAG_ISSET(iofd, IOFD_FLAG_NOTIFY_CONNECTED)) {
+ osmo_fd_setup(&iofd->u.uring.connect_ofd, iofd->fd, OSMO_FD_WRITE,
+ iofd_uring_connected_cb, iofd, 0);
+ if (osmo_fd_register(&iofd->u.uring.connect_ofd) < 0)
+ LOGPIO(iofd, LOGL_ERROR, "Failed to register FD for connect event.\n");
+ else
+ IOFD_FLAG_SET(iofd, IOFD_FLAG_NOTIFY_CONNECTED);
+ }
+ } else
+ iofd_uring_write_enable(iofd);
+}
+
+const struct iofd_backend_ops iofd_uring_ops = {
+ .register_fd = iofd_uring_register,
+ .unregister_fd = iofd_uring_unregister,
+ .close = iofd_uring_close,
+ .write_enable = iofd_uring_write_enable,
+ .write_disable = iofd_uring_write_disable,
+ .read_enable = iofd_uring_read_enable,
+ .read_disable = iofd_uring_read_disable,
+ .notify_connected = iofd_uring_notify_connected,
+};
+
+#endif /* defined(__linux__) */
diff --git a/src/panic.c b/src/core/panic.c
index 072f458b..bbf6d081 100644
--- a/src/panic.c
+++ b/src/core/panic.c
@@ -17,10 +17,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
/*! \addtogroup utils
@@ -31,7 +27,7 @@
#include <osmocom/core/panic.h>
#include <osmocom/core/backtrace.h>
-#include "../config.h"
+#include "config.h"
static osmo_panic_handler_t osmo_panic_handler = (void*)0;
diff --git a/src/plugin.c b/src/core/plugin.c
index 40de4f8c..687ad406 100644
--- a/src/plugin.c
+++ b/src/core/plugin.c
@@ -17,17 +17,13 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
/*! \addtogroup utils
* @{
* \file plugin.c */
-#include "../config.h"
+#include "config.h"
#if HAVE_DLFCN_H
diff --git a/src/prbs.c b/src/core/prbs.c
index 8fa04bb7..8fa04bb7 100644
--- a/src/prbs.c
+++ b/src/core/prbs.c
diff --git a/src/prim.c b/src/core/prim.c
index 3c8a7f12..3c8a7f12 100644
--- a/src/prim.c
+++ b/src/core/prim.c
diff --git a/src/core/probes.d b/src/core/probes.d
new file mode 100644
index 00000000..e4150f0c
--- /dev/null
+++ b/src/core/probes.d
@@ -0,0 +1,6 @@
+provider libosmocore {
+ probe log_start();
+ probe log_done();
+ probe stats_start();
+ probe stats_done();
+};
diff --git a/src/rate_ctr.c b/src/core/rate_ctr.c
index 9043a2c6..44e26585 100644
--- a/src/rate_ctr.c
+++ b/src/core/rate_ctr.c
@@ -14,10 +14,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
/*! \addtogroup rate_ctr
@@ -57,14 +53,17 @@
*
* \file rate_ctr.c */
+#include <errno.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
+#include <unistd.h>
+#include <inttypes.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/linuxlist.h>
#include <osmocom/core/talloc.h>
-#include <osmocom/core/timer.h>
+#include <osmocom/core/select.h>
#include <osmocom/core/rate_ctr.h>
#include <osmocom/core/logging.h>
@@ -263,6 +262,26 @@ void rate_ctr_group_free(struct rate_ctr_group *grp)
talloc_free(grp);
}
+/*! Get rate counter from group, identified by index idx
+ * \param[in] grp Rate counter group
+ * \param[in] idx Index of the counter to retrieve
+ * \returns rate counter requested
+ */
+struct rate_ctr *rate_ctr_group_get_ctr(struct rate_ctr_group *grp, unsigned int idx)
+{
+ return &grp->ctr[idx];
+}
+
+/*! Set a name for the group of counters be used instead of index value
+ at report time.
+ * \param[in] grp Rate counter group
+ * \param[in] name Name identifier to assign to the rate counter group
+ */
+void rate_ctr_group_set_name(struct rate_ctr_group *grp, const char *name)
+{
+ osmo_talloc_replace_string(grp, &grp->name, name);
+}
+
/*! Add a number to the counter */
void rate_ctr_add(struct rate_ctr *ctr, int inc)
{
@@ -287,14 +306,9 @@ static void interval_expired(struct rate_ctr *ctr, enum rate_ctr_intv intv)
ctr->intv[intv].rate = ctr->current - ctr->intv[intv].last;
/* save current counter for next interval */
ctr->intv[intv].last = ctr->current;
-
- /* update the rate of the next bigger interval. This will
- * be overwritten when that next larger interval expires */
- if (intv + 1 < ARRAY_SIZE(ctr->intv))
- ctr->intv[intv+1].rate += ctr->intv[intv].rate;
}
-static struct osmo_timer_list rate_ctr_timer;
+static struct osmo_fd rate_ctr_timer = { .fd = -1 };
static uint64_t timer_ticks;
/* The one-second interval has expired */
@@ -315,18 +329,35 @@ static void rate_ctr_group_intv(struct rate_ctr_group *grp)
}
}
-static void rate_ctr_timer_cb(void *data)
+static int rate_ctr_timer_cb(struct osmo_fd *ofd, unsigned int what)
{
struct rate_ctr_group *ctrg;
+ uint64_t expire_count;
+ int rc;
- /* Increment number of ticks before we calculate intervals,
- * as a counter value of 0 would already wrap all counters */
- timer_ticks++;
+ /* check that the timer has actually expired */
+ if (!(what & OSMO_FD_READ))
+ return 0;
- llist_for_each_entry(ctrg, &rate_ctr_groups, list)
- rate_ctr_group_intv(ctrg);
+ /* read from timerfd: number of expirations of periodic timer */
+ rc = read(ofd->fd, (void *) &expire_count, sizeof(expire_count));
+ if (rc < 0 && errno == EAGAIN)
+ return 0;
- osmo_timer_schedule(&rate_ctr_timer, 1, 0);
+ OSMO_ASSERT(rc == sizeof(expire_count));
+
+ if (expire_count > 1)
+ LOGP(DLGLOBAL, LOGL_NOTICE, "Stats timer expire_count=%" PRIu64 ": We missed %" PRIu64 " timers\n",
+ expire_count, expire_count - 1);
+
+ do { /* Increment number of ticks before we calculate intervals,
+ * as a counter value of 0 would already wrap all counters */
+ timer_ticks++;
+ llist_for_each_entry(ctrg, &rate_ctr_groups, list)
+ rate_ctr_group_intv(ctrg);
+ } while (--expire_count);
+
+ return 0;
}
/*! Initialize the counter module. Call this once from your application.
@@ -334,9 +365,27 @@ static void rate_ctr_timer_cb(void *data)
* \returns 0 on success; negative on error */
int rate_ctr_init(void *tall_ctx)
{
+ struct timespec ts_interval = { .tv_sec = 1, .tv_nsec = 0 };
+ int rc;
+
+ /* ignore repeated initialization */
+ if (osmo_fd_is_registered(&rate_ctr_timer))
+ return 0;
+
tall_rate_ctr_ctx = tall_ctx;
- osmo_timer_setup(&rate_ctr_timer, rate_ctr_timer_cb, NULL);
- osmo_timer_schedule(&rate_ctr_timer, 1, 0);
+
+ rc = osmo_timerfd_setup(&rate_ctr_timer, rate_ctr_timer_cb, NULL);
+ if (rc < 0) {
+ LOGP(DLGLOBAL, LOGL_ERROR, "Failed to setup the timer with error code %d (fd=%d)\n",
+ rc, rate_ctr_timer.fd);
+ return rc;
+ }
+
+ rc = osmo_timerfd_schedule(&rate_ctr_timer, NULL, &ts_interval);
+ if (rc < 0) {
+ LOGP(DLGLOBAL, LOGL_ERROR, "Failed to schedule the timer with error code %d (fd=%d)\n",
+ rc, rate_ctr_timer.fd);
+ }
return 0;
}
diff --git a/src/rbtree.c b/src/core/rbtree.c
index 211978f1..f4dc219b 100644
--- a/src/rbtree.c
+++ b/src/core/rbtree.c
@@ -15,11 +15,6 @@
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
- MA 02110-1301, USA
-
linux/lib/rbtree.c
*/
diff --git a/src/select.c b/src/core/select.c
index 71ee7f60..70047f09 100644
--- a/src/select.c
+++ b/src/core/select.c
@@ -5,7 +5,7 @@
* of the linux 2.4 netfilter subsystem. */
/*
* (C) 2000-2020 by Harald Welte <laforge@gnumonks.org>
- * All Rights Reserverd.
+ * All Rights Reserved.
*
* SPDX-License-Identifier: GPL-2.0+
*
@@ -18,11 +18,6 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
- * MA 02110-1301, USA.
*/
#include <fcntl.h>
@@ -38,8 +33,10 @@
#include <osmocom/core/logging.h>
#include <osmocom/core/talloc.h>
#include <osmocom/core/utils.h>
+#include <osmocom/core/stat_item.h>
+#include <osmocom/core/stats_tcp.h>
-#include "../config.h"
+#include "config.h"
#if defined(HAVE_SYS_SELECT_H) && defined(HAVE_POLL_H)
#include <sys/select.h>
@@ -57,6 +54,39 @@ static __thread int maxfd = 0;
static __thread struct llist_head osmo_fds; /* TLS cannot use LLIST_HEAD() */
static __thread int unregistered_count;
+/* Array of struct osmo_fd * (size "max_fd") ordered by "ofd->fd" */
+static __thread struct {
+ struct osmo_fd **table;
+ unsigned int size;
+} osmo_fd_lookup;
+
+static void osmo_fd_lookup_table_extend(unsigned int new_max_fd)
+{
+ unsigned int min_new_size;
+ unsigned int pw2;
+ unsigned int new_size;
+
+ /* First calculate the minimally required new size of the array in bytes: */
+ min_new_size = (new_max_fd + 1) * sizeof(struct osmo_fd *);
+ /* Now find the lower power of two of min_new_size (in bytes): */
+ pw2 = 32 - __builtin_clz(min_new_size);
+ /* get next (upper side) power of 2: */
+ pw2++;
+ OSMO_ASSERT(pw2 <= 31); /* Avoid shifting more than 31 bits */
+ new_size = 1 << (pw2 + 1);
+
+ /* Let's make it at least 1024B, to avoid reallocating quickly at startup */
+ if (new_size < 1024)
+ new_size = 1024;
+ if (new_size > osmo_fd_lookup.size) {
+ uint8_t *ptr = talloc_realloc_size(OTC_GLOBAL, osmo_fd_lookup.table, new_size);
+ OSMO_ASSERT(ptr);
+ memset(ptr + osmo_fd_lookup.size, 0, new_size - osmo_fd_lookup.size);
+ osmo_fd_lookup.table = (struct osmo_fd **)ptr;
+ osmo_fd_lookup.size = new_size;
+ }
+}
+
#ifndef FORCE_IO_SELECT
struct poll_state {
/* array of pollfd */
@@ -69,6 +99,11 @@ struct poll_state {
static __thread struct poll_state g_poll;
#endif /* FORCE_IO_SELECT */
+/*! See osmo_select_shutdown_request() */
+static int _osmo_select_shutdown_requested = 0;
+/*! See osmo_select_shutdown_request() */
+static bool _osmo_select_shutdown_done = false;
+
/*! Set up an osmo-fd. Will not register it.
* \param[inout] ofd Osmo FD to be set-up
* \param[in] fd OS-level file descriptor number
@@ -116,6 +151,9 @@ bool osmo_fd_is_registered(struct osmo_fd *fd)
/*! Register a new file descriptor with select loop abstraction
* \param[in] fd osmocom file descriptor to be registered
* \returns 0 on success; negative in case of error
+ *
+ * The API expects fd field of the struct osmo_fd to remain unchanged while
+ * registered, ie until osmo_fd_unregister() is called on it.
*/
int osmo_fd_register(struct osmo_fd *fd)
{
@@ -140,8 +178,10 @@ int osmo_fd_register(struct osmo_fd *fd)
return flags;
/* Register FD */
- if (fd->fd > maxfd)
+ if (fd->fd > maxfd) {
maxfd = fd->fd;
+ osmo_fd_lookup_table_extend(maxfd);
+ }
#ifdef OSMO_FD_CHECK
if (osmo_fd_is_registered(fd)) {
@@ -164,12 +204,17 @@ int osmo_fd_register(struct osmo_fd *fd)
#endif /* FORCE_IO_SELECT */
llist_add_tail(&fd->list, &osmo_fds);
+ osmo_fd_lookup.table[fd->fd] = fd;
return 0;
}
/*! Unregister a file descriptor from select loop abstraction
* \param[in] fd osmocom file descriptor to be unregistered
+ *
+ * Caller is responsible for ensuring the fd is really registered before calling this API.
+ * This function must be called before changing the value of the fd field in
+ * the struct osmo_fd.
*/
void osmo_fd_unregister(struct osmo_fd *fd)
{
@@ -181,6 +226,24 @@ void osmo_fd_unregister(struct osmo_fd *fd)
#ifndef FORCE_IO_SELECT
g_poll.num_registered--;
#endif /* FORCE_IO_SELECT */
+
+ if (OSMO_UNLIKELY(fd->fd < 0 || fd->fd > maxfd)) {
+ /* Some old users used to incorrectly set fd = -1 *before* calling osmo_unregister().
+ * Hence, in order to keep backward compatibility it's not possible to assert() here.
+ * Instead, print an error message since this is actually a bug in the API user. */
+#ifdef OSMO_FD_CHECK
+ osmo_panic("osmo_fd_unregister(fd=%u) out of expected range (0..%u), fix your code!!!\n",
+ fd->fd, maxfd);
+#else
+ fprintf(stderr, "osmo_fd_unregister(fd=%u) out of expected range (0..%u), fix your code!!!\n",
+ fd->fd, maxfd);
+ return;
+#endif
+ }
+
+ osmo_fd_lookup.table[fd->fd] = NULL;
+ /* If existent, free any statistical data */
+ osmo_stats_tcp_osmo_fd_unregister(fd);
}
/*! Close a file descriptor, mark it as closed + unregister from select loop abstraction
@@ -311,11 +374,12 @@ static unsigned int poll_fill_fds(void)
}
/* iterate over first n_fd entries of g_poll.poll + dispatch */
-static int poll_disp_fds(int n_fd)
+static int poll_disp_fds(unsigned int n_fd)
{
struct osmo_fd *ufd;
unsigned int i;
int work = 0;
+ int shutdown_pending_writes = 0;
for (i = 0; i < n_fd; i++) {
struct pollfd *p = &g_poll.poll[i];
@@ -340,6 +404,11 @@ static int poll_disp_fds(int n_fd)
/* make sure we never report more than the user requested */
flags &= ufd->when;
+ if (_osmo_select_shutdown_requested > 0) {
+ if (ufd->when & OSMO_FD_WRITE)
+ shutdown_pending_writes++;
+ }
+
if (flags) {
work = 1;
/* make sure to clear any log context before processing the next incoming message
@@ -351,6 +420,9 @@ static int poll_disp_fds(int n_fd)
}
}
+ if (_osmo_select_shutdown_requested > 0 && !shutdown_pending_writes)
+ _osmo_select_shutdown_done = true;
+
return work;
}
@@ -358,19 +430,26 @@ static int _osmo_select_main(int polling)
{
unsigned int n_poll;
int rc;
+ int timeout = 0;
/* prepare read and write fdsets */
n_poll = poll_fill_fds();
- if (!polling)
+ if (!polling) {
osmo_timers_prepare();
+ timeout = osmo_timers_nearest_ms();
+
+ if (_osmo_select_shutdown_requested && timeout == -1)
+ timeout = 0;
+ }
- rc = poll(g_poll.poll, n_poll, polling ? 0 : osmo_timers_nearest_ms());
+ rc = poll(g_poll.poll, n_poll, timeout);
if (rc < 0)
return 0;
/* fire timers */
- osmo_timers_update();
+ if (!_osmo_select_shutdown_requested)
+ osmo_timers_update();
OSMO_ASSERT(osmo_ctx->select);
@@ -443,23 +522,21 @@ int osmo_select_main_ctx(int polling)
* \returns \ref osmo_fd for \ref fd; NULL in case it doesn't exist */
struct osmo_fd *osmo_fd_get_by_fd(int fd)
{
- struct osmo_fd *ofd;
-
- llist_for_each_entry(ofd, &osmo_fds, list) {
- if (ofd->fd == fd)
- return ofd;
- }
- return NULL;
+ if (fd > maxfd || fd < 0)
+ return NULL;
+ return osmo_fd_lookup.table[fd];
}
/*! initialize the osmocom select abstraction for the current thread */
void osmo_select_init(void)
{
INIT_LLIST_HEAD(&osmo_fds);
+ osmo_fd_lookup_table_extend(0);
}
-/* ensure main thread always has pre-initialized osmo_fds */
-static __attribute__((constructor)) void on_dso_load_select(void)
+/* ensure main thread always has pre-initialized osmo_fds
+ * priority 102: must run after on_dso_load_ctx */
+static __attribute__((constructor(102))) void on_dso_load_select(void)
{
osmo_select_init();
}
@@ -596,6 +673,59 @@ osmo_signalfd_setup(void *ctx, sigset_t set, osmo_signalfd_cb *cb, void *data)
#endif /* HAVE_SYS_SIGNALFD_H */
+/*! Request osmo_select_* to only service pending OSMO_FD_WRITE requests. Once all writes are done,
+ * osmo_select_shutdown_done() returns true. This allows for example to send all outbound packets before terminating the
+ * process.
+ *
+ * Usage example:
+ *
+ * static void signal_handler(int signum)
+ * {
+ * fprintf(stdout, "signal %u received\n", signum);
+ *
+ * switch (signum) {
+ * case SIGINT:
+ * case SIGTERM:
+ * // If the user hits Ctrl-C the third time, just terminate immediately.
+ * if (osmo_select_shutdown_requested() >= 2)
+ * exit(-1);
+ * // Request write-only mode in osmo_select_main_ctx()
+ * osmo_select_shutdown_request();
+ * break;
+ * [...]
+ * }
+ *
+ * main()
+ * {
+ * signal(SIGINT, &signal_handler);
+ * signal(SIGTERM, &signal_handler);
+ *
+ * [...]
+ *
+ * // After the signal_handler issued osmo_select_shutdown_request(), osmo_select_shutdown_done() returns true
+ * // as soon as all write queues are empty.
+ * while (!osmo_select_shutdown_done()) {
+ * osmo_select_main_ctx(0);
+ * }
+ * }
+ */
+void osmo_select_shutdown_request(void)
+{
+ _osmo_select_shutdown_requested++;
+};
+
+/*! Return the number of times osmo_select_shutdown_request() was called before. */
+int osmo_select_shutdown_requested(void)
+{
+ return _osmo_select_shutdown_requested;
+};
+
+/*! Return true after osmo_select_shutdown_requested() was called, and after an osmo_select poll loop found no more
+ * pending OSMO_FD_WRITE on any registered socket. */
+bool osmo_select_shutdown_done(void) {
+ return _osmo_select_shutdown_done;
+};
+
/*! @} */
#endif /* _HAVE_SYS_SELECT_H */
diff --git a/src/sercomm.c b/src/core/sercomm.c
index 2639bf8a..1798acec 100644
--- a/src/sercomm.c
+++ b/src/core/sercomm.c
@@ -14,10 +14,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
/*! \addtogroup sercomm
diff --git a/src/serial.c b/src/core/serial.c
index c3bf5e89..117c049b 100644
--- a/src/serial.c
+++ b/src/core/serial.c
@@ -16,10 +16,6 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
/*! \addtogroup serial
diff --git a/src/signal.c b/src/core/signal.c
index be3b7778..ba1555ae 100644
--- a/src/signal.c
+++ b/src/core/signal.c
@@ -16,10 +16,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <osmocom/core/signal.h>
diff --git a/src/sockaddr_str.c b/src/core/sockaddr_str.c
index f5508a03..bea61849 100644
--- a/src/sockaddr_str.c
+++ b/src/core/sockaddr_str.c
@@ -20,10 +20,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include "config.h"
@@ -37,6 +33,7 @@
#include <osmocom/core/sockaddr_str.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/byteswap.h>
+#include <osmocom/core/socket.h>
/*! \addtogroup sockaddr_str
*
@@ -363,6 +360,17 @@ int osmo_sockaddr_str_from_sockaddr(struct osmo_sockaddr_str *sockaddr_str, cons
return -EINVAL;
}
+/*! Convert IPv4 or IPv6 address and port to osmo_sockaddr_str.
+ * \param[out] sockaddr_str The instance to copy to.
+ * \param[in] src IPv4 or IPv6 address and port data.
+ * \return 0 on success, negative if src does not indicate AF_INET nor AF_INET6 (or if the conversion fails, which
+ * should not be possible in practice).
+ */
+int osmo_sockaddr_str_from_osa(struct osmo_sockaddr_str *sockaddr_str, const struct osmo_sockaddr *src)
+{
+ return osmo_sockaddr_str_from_sockaddr(sockaddr_str, &src->u.sas);
+}
+
/*! Convert osmo_sockaddr_str address string to IPv4 address data.
* \param[in] sockaddr_str The instance to convert the IP of.
* \param[out] dst IPv4 address data to write to.
@@ -513,5 +521,15 @@ int osmo_sockaddr_str_to_sockaddr(const struct osmo_sockaddr_str *sockaddr_str,
}
}
+/*! Convert osmo_sockaddr_str address string and port to IPv4 or IPv6 address and port data.
+ * \param[in] sockaddr_str The instance to convert the IP and port of.
+ * \param[out] dst IPv4/IPv6 address and port data to write to.
+ * \return 0 on success, negative on error (e.g. invalid IP address string for the family indicated by sockaddr_str->af).
+ */
+int osmo_sockaddr_str_to_osa(const struct osmo_sockaddr_str *sockaddr_str, struct osmo_sockaddr *dst)
+{
+ return osmo_sockaddr_str_to_sockaddr(sockaddr_str, &dst->u.sas);
+}
+
/*! @} */
#endif // HAVE_NETINET_IN_H
diff --git a/src/socket.c b/src/core/socket.c
index 229f72e3..80a9d0ec 100644
--- a/src/socket.c
+++ b/src/core/socket.c
@@ -15,13 +15,9 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
-#include "../config.h"
+#include "config.h"
/*! \addtogroup socket
* @{
@@ -30,6 +26,7 @@
* \file socket.c */
#ifdef HAVE_SYS_SOCKET_H
+#define _GNU_SOURCE /* for struct ucred on glibc >= 2.8 */
#include <osmocom/core/logging.h>
#include <osmocom/core/select.h>
@@ -118,7 +115,7 @@ static struct addrinfo *addrinfo_helper(uint16_t family, uint16_t type, uint8_t
static int addrinfo_helper_multi(struct addrinfo **addrinfo, uint16_t family, uint16_t type, uint8_t proto,
const char **hosts, size_t host_cnt, uint16_t port, bool passive)
{
- int i, j;
+ unsigned int i, j;
for (i = 0; i < host_cnt; i++) {
addrinfo[i] = addrinfo_helper(family, type, proto, hosts[i], port, passive);
@@ -132,31 +129,64 @@ static int addrinfo_helper_multi(struct addrinfo **addrinfo, uint16_t family, ui
}
#endif /* HAVE_LIBSCTP*/
-static int socket_helper(const struct addrinfo *rp, unsigned int flags)
+static int socket_helper_tail(int sfd, unsigned int flags)
{
- int sfd, on = 1;
+ int rc, on = 1;
+ uint8_t dscp = GET_OSMO_SOCK_F_DSCP(flags);
+ uint8_t prio = GET_OSMO_SOCK_F_PRIO(flags);
- sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
- if (sfd == -1) {
- LOGP(DLGLOBAL, LOGL_ERROR,
- "unable to create socket: %s\n", strerror(errno));
- return sfd;
- }
if (flags & OSMO_SOCK_F_NONBLOCK) {
if (ioctl(sfd, FIONBIO, (unsigned char *)&on) < 0) {
LOGP(DLGLOBAL, LOGL_ERROR,
"cannot set this socket unblocking: %s\n",
strerror(errno));
close(sfd);
- sfd = -EINVAL;
+ return -EINVAL;
+ }
+ }
+
+ if (dscp) {
+ rc = osmo_sock_set_dscp(sfd, dscp);
+ if (rc) {
+ LOGP(DLGLOBAL, LOGL_ERROR, "cannot set IP DSCP of socket to %u: %s\n",
+ dscp, strerror(errno));
+ /* we consider this a non-fatal error */
+ }
+ }
+
+ if (prio) {
+ rc = osmo_sock_set_priority(sfd, prio);
+ if (rc) {
+ LOGP(DLGLOBAL, LOGL_ERROR, "cannot set priority of socket to %u: %s\n",
+ prio, strerror(errno));
+ /* we consider this a non-fatal error */
}
}
+
+ return 0;
+}
+
+static int socket_helper(const struct addrinfo *rp, unsigned int flags)
+{
+ int sfd, rc;
+
+ sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
+ if (sfd == -1) {
+ LOGP(DLGLOBAL, LOGL_ERROR,
+ "unable to create socket: %s\n", strerror(errno));
+ return sfd;
+ }
+
+ rc = socket_helper_tail(sfd, flags);
+ if (rc < 0)
+ return rc;
+
return sfd;
}
static int socket_helper_osa(const struct osmo_sockaddr *addr, uint16_t type, uint8_t proto, unsigned int flags)
{
- int sfd, on = 1;
+ int sfd, rc;
sfd = socket(addr->u.sa.sa_family, type, proto);
if (sfd == -1) {
@@ -164,15 +194,11 @@ static int socket_helper_osa(const struct osmo_sockaddr *addr, uint16_t type, ui
"unable to create socket: %s\n", strerror(errno));
return sfd;
}
- if (flags & OSMO_SOCK_F_NONBLOCK) {
- if (ioctl(sfd, FIONBIO, (unsigned char *)&on) < 0) {
- LOGP(DLGLOBAL, LOGL_ERROR,
- "cannot set this socket unblocking: %s\n",
- strerror(errno));
- close(sfd);
- sfd = -EINVAL;
- }
- }
+
+ rc = socket_helper_tail(sfd, flags);
+ if (rc < 0)
+ return rc;
+
return sfd;
}
@@ -185,7 +211,8 @@ static int socket_helper_osa(const struct osmo_sockaddr *addr, uint16_t type, ui
static int multiaddr_snprintf(char* buf, size_t buf_len, const char **hosts, size_t host_cnt)
{
int len = 0, offset = 0, rem = buf_len;
- int ret, i;
+ size_t i;
+ int ret;
char *after;
if (buf_len < 3)
@@ -223,7 +250,7 @@ static int osmo_sock_init_tail(int fd, uint16_t type, unsigned int flags)
if (rc < 0) {
LOGP(DLGLOBAL, LOGL_ERROR, "unable to listen on socket: %s\n",
strerror(errno));
- return rc;
+ return -errno;
}
break;
}
@@ -458,8 +485,8 @@ int osmo_sock_init2(uint16_t family, uint16_t type, uint8_t proto,
}
#define _SOCKADDR_TO_STR(dest, sockaddr) do { \
- if (osmo_sockaddr_str_from_sockaddr(&dest, &sockaddr->u.sas)) \
- osmo_strlcpy(dest.ip, "Invalid IP", 11); \
+ if (osmo_sockaddr_str_from_sockaddr(dest, &sockaddr->u.sas)) \
+ osmo_strlcpy((dest)->ip, "Invalid IP", 11); \
} while (0)
/*! Initialize a socket (including bind and/or connect)
@@ -494,7 +521,8 @@ int osmo_sock_init_osa(uint16_t type, uint8_t proto,
unsigned int flags)
{
int sfd = -1, rc, on = 1;
- struct osmo_sockaddr_str sastr = {};
+ struct osmo_sockaddr_str _sastr = {};
+ struct osmo_sockaddr_str *sastr = &_sastr;
if ((flags & (OSMO_SOCK_F_BIND | OSMO_SOCK_F_CONNECT)) == 0) {
LOGP(DLGLOBAL, LOGL_ERROR, "invalid: you have to specify either "
@@ -525,8 +553,8 @@ int osmo_sock_init_osa(uint16_t type, uint8_t proto,
sfd = socket_helper_osa(local, type, proto, flags);
if (sfd < 0) {
_SOCKADDR_TO_STR(sastr, local);
- LOGP(DLGLOBAL, LOGL_ERROR, "no suitable local addr found for: %s:%u\n",
- sastr.ip, sastr.port);
+ LOGP(DLGLOBAL, LOGL_ERROR, "no suitable local addr found for: " OSMO_SOCKADDR_STR_FMT "\n",
+ OSMO_SOCKADDR_STR_FMT_ARGS(sastr));
return -ENODEV;
}
@@ -534,21 +562,21 @@ int osmo_sock_init_osa(uint16_t type, uint8_t proto,
rc = setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR,
&on, sizeof(on));
if (rc < 0) {
+ int err = errno;
_SOCKADDR_TO_STR(sastr, local);
LOGP(DLGLOBAL, LOGL_ERROR,
- "cannot setsockopt socket:"
- " %s:%u: %s\n",
- sastr.ip, sastr.port,
- strerror(errno));
+ "cannot setsockopt socket: " OSMO_SOCKADDR_STR_FMT ": %s\n",
+ OSMO_SOCKADDR_STR_FMT_ARGS(sastr), strerror(err));
close(sfd);
return rc;
}
}
if (bind(sfd, &local->u.sa, sizeof(struct osmo_sockaddr)) == -1) {
+ int err = errno;
_SOCKADDR_TO_STR(sastr, local);
- LOGP(DLGLOBAL, LOGL_ERROR, "unable to bind socket: %s:%u: %s\n",
- sastr.ip, sastr.port, strerror(errno));
+ LOGP(DLGLOBAL, LOGL_ERROR, "unable to bind socket: " OSMO_SOCKADDR_STR_FMT ": %s\n",
+ OSMO_SOCKADDR_STR_FMT_ARGS(sastr), strerror(err));
close(sfd);
return -1;
}
@@ -569,9 +597,10 @@ int osmo_sock_init_osa(uint16_t type, uint8_t proto,
rc = connect(sfd, &remote->u.sa, sizeof(struct osmo_sockaddr));
if (rc != 0 && errno != EINPROGRESS) {
+ int err = errno;
_SOCKADDR_TO_STR(sastr, remote);
- LOGP(DLGLOBAL, LOGL_ERROR, "unable to connect socket: %s:%u: %s\n",
- sastr.ip, sastr.port, strerror(errno));
+ LOGP(DLGLOBAL, LOGL_ERROR, "unable to connect socket: " OSMO_SOCKADDR_STR_FMT ": %s\n",
+ OSMO_SOCKADDR_STR_FMT_ARGS(sastr), strerror(err));
close(sfd);
return rc;
}
@@ -588,18 +617,45 @@ int osmo_sock_init_osa(uint16_t type, uint8_t proto,
#ifdef HAVE_LIBSCTP
-/* Check whether there's an IPv6 Addr as first option of any addrinfo item in the addrinfo set */
+/* Check whether there's an addrinfo item in the addrinfo set with an IPv4 or IPv6 option */
static void addrinfo_has_v4v6addr(const struct addrinfo **result, size_t result_count, bool *has_v4, bool *has_v6)
{
size_t host_idx;
+ const struct addrinfo *rp;
*has_v4 = false;
*has_v6 = false;
for (host_idx = 0; host_idx < result_count; host_idx++) {
- if (result[host_idx]->ai_family == AF_INET)
- *has_v4 = true;
- else if (result[host_idx]->ai_family == AF_INET6)
- *has_v6 = true;
+ for (rp = result[host_idx]; rp != NULL; rp = rp->ai_next) {
+ if (result[host_idx]->ai_family == AF_INET)
+ *has_v4 = true;
+ else if (result[host_idx]->ai_family == AF_INET6)
+ *has_v6 = true;
+ }
+ }
+}
+
+/* Check whether there's an addrinfo item in the addrinfo set with only an IPv4 or IPv6 option */
+static void addrinfo_has_v4v6only_addr(const struct addrinfo **result, size_t result_count, bool *has_v4only, bool *has_v6only)
+{
+ size_t host_idx;
+ const struct addrinfo *rp;
+ *has_v4only = false;
+ *has_v6only = false;
+
+ for (host_idx = 0; host_idx < result_count; host_idx++) {
+ bool has_v4 = false;
+ bool has_v6 = false;
+ for (rp = result[host_idx]; rp != NULL; rp = rp->ai_next) {
+ if (rp->ai_family == AF_INET6)
+ has_v6 = true;
+ else
+ has_v4 = true;
+ }
+ if (has_v4 && !has_v6)
+ *has_v4only = true;
+ else if (has_v6 && !has_v4)
+ *has_v6only = true;
}
}
@@ -608,20 +664,23 @@ static bool addrinfo_has_in6addr_any(const struct addrinfo **result, size_t resu
{
size_t host_idx;
struct in6_addr in6addr_any = IN6ADDR_ANY_INIT;
+ const struct addrinfo *rp;
for (host_idx = 0; host_idx < result_count; host_idx++) {
- if (result[host_idx]->ai_family != AF_INET6)
- continue;
- if (memcmp(&((struct sockaddr_in6 *)result[host_idx]->ai_addr)->sin6_addr,
- &in6addr_any, sizeof(in6addr_any)) == 0)
- return true;
+ for (rp = result[host_idx]; rp != NULL; rp = rp->ai_next) {
+ if (rp->ai_family != AF_INET6)
+ continue;
+ if (memcmp(&((struct sockaddr_in6 *)rp->ai_addr)->sin6_addr,
+ &in6addr_any, sizeof(in6addr_any)) == 0)
+ return true;
+ }
}
return false;
}
static int socket_helper_multiaddr(uint16_t family, uint16_t type, uint8_t proto, unsigned int flags)
{
- int sfd, on = 1;
+ int sfd, rc;
sfd = socket(family, type, proto);
if (sfd == -1) {
@@ -629,22 +688,18 @@ static int socket_helper_multiaddr(uint16_t family, uint16_t type, uint8_t proto
"Unable to create socket: %s\n", strerror(errno));
return sfd;
}
- if (flags & OSMO_SOCK_F_NONBLOCK) {
- if (ioctl(sfd, FIONBIO, (unsigned char *)&on) < 0) {
- LOGP(DLGLOBAL, LOGL_ERROR,
- "Cannot set this socket unblocking: %s\n",
- strerror(errno));
- close(sfd);
- sfd = -EINVAL;
- }
- }
+
+ rc = socket_helper_tail(sfd, flags);
+ if (rc < 0)
+ return rc;
+
return sfd;
}
/* Build array of addresses taking first addrinfo result of the requested family
* for each host in addrs_buf. */
static int addrinfo_to_sockaddr(uint16_t family, const struct addrinfo **result,
- const char **hosts, int host_cont,
+ const char **hosts, unsigned int host_cont,
uint8_t *addrs_buf, size_t addrs_buf_len) {
size_t host_idx, offset = 0;
const struct addrinfo *rp;
@@ -652,26 +707,102 @@ static int addrinfo_to_sockaddr(uint16_t family, const struct addrinfo **result,
for (host_idx = 0; host_idx < host_cont; host_idx++) {
/* Addresses are ordered based on RFC 3484, see man getaddrinfo */
for (rp = result[host_idx]; rp != NULL; rp = rp->ai_next) {
- if (family != AF_UNSPEC && rp->ai_family != family)
- continue;
- if (offset + rp->ai_addrlen > addrs_buf_len) {
- LOGP(DLGLOBAL, LOGL_ERROR, "Output buffer to small: %zu\n",
- addrs_buf_len);
- return -ENOSPC;
+ if (family == AF_UNSPEC || rp->ai_family == family)
+ break;
+ }
+ if (!rp && family == AF_INET6) {
+ /* See if we can find an AF_INET addr for the AF_INET6 socket instead: */
+ for (rp = result[host_idx]; rp != NULL; rp = rp->ai_next) {
+ if (rp->ai_family == AF_INET)
+ break;
}
- memcpy(addrs_buf + offset, rp->ai_addr, rp->ai_addrlen);
- offset += rp->ai_addrlen;
- break;
}
if (!rp) { /* No addr could be bound for this host! */
LOGP(DLGLOBAL, LOGL_ERROR, "No suitable remote address found for host: %s\n",
hosts[host_idx]);
return -ENODEV;
}
+ if (offset + rp->ai_addrlen > addrs_buf_len) {
+ LOGP(DLGLOBAL, LOGL_ERROR, "Output buffer to small: %zu\n",
+ addrs_buf_len);
+ return -ENOSPC;
+ }
+ memcpy(addrs_buf + offset, rp->ai_addr, rp->ai_addrlen);
+ offset += rp->ai_addrlen;
}
return 0;
}
+static int setsockopt_sctp_auth_supported(int fd, uint32_t val)
+{
+#ifdef SCTP_AUTH_SUPPORTED
+ struct sctp_assoc_value assoc_val = {
+ .assoc_id = SCTP_FUTURE_ASSOC,
+ .assoc_value = val,
+ };
+ return setsockopt(fd, IPPROTO_SCTP, SCTP_AUTH_SUPPORTED, &assoc_val, sizeof(assoc_val));
+#else
+#pragma message "setsockopt(SCTP_AUTH_SUPPORTED) not supported! some SCTP features may not be available!"
+ LOGP(DLGLOBAL, LOGL_NOTICE, "Built without support for setsockopt(SCTP_AUTH_SUPPORTED), skipping\n");
+ return -ENOTSUP;
+#endif
+}
+
+static int setsockopt_sctp_asconf_supported(int fd, uint32_t val)
+{
+#ifdef SCTP_ASCONF_SUPPORTED
+ struct sctp_assoc_value assoc_val = {
+ .assoc_id = SCTP_FUTURE_ASSOC,
+ .assoc_value = val,
+ };
+ return setsockopt(fd, IPPROTO_SCTP, SCTP_ASCONF_SUPPORTED, &assoc_val, sizeof(assoc_val));
+#else
+#pragma message "setsockopt(SCTP_ASCONF_SUPPORTED) not supported! some SCTP features may not be available!"
+ LOGP(DLGLOBAL, LOGL_NOTICE, "Built without support for setsockopt(SCTP_ASCONF_SUPPORTED), skipping\n");
+ return -ENOTSUP;
+#endif
+}
+
+static int setsockopt_sctp_initmsg(int fd, const struct osmo_sock_init2_multiaddr_pars *pars)
+{
+ if (!pars->sctp.sockopt_initmsg.num_ostreams_present &&
+ !pars->sctp.sockopt_initmsg.max_instreams_present &&
+ !pars->sctp.sockopt_initmsg.max_attempts_present &&
+ !pars->sctp.sockopt_initmsg.max_init_timeo_present)
+ return 0; /* nothing to set/do */
+
+#ifdef SCTP_INITMSG
+ struct sctp_initmsg si = {0};
+ socklen_t si_len = sizeof(si);
+ int rc;
+
+ /* If at least one field not present, obtain current value from kernel: */
+ if (!pars->sctp.sockopt_initmsg.num_ostreams_present ||
+ !pars->sctp.sockopt_initmsg.max_instreams_present ||
+ !pars->sctp.sockopt_initmsg.max_attempts_present ||
+ !pars->sctp.sockopt_initmsg.max_init_timeo_present) {
+ rc = getsockopt(fd, IPPROTO_SCTP, SCTP_INITMSG, &si, &si_len);
+ if (rc < 0)
+ return rc;
+ }
+
+ if (pars->sctp.sockopt_initmsg.num_ostreams_present)
+ si.sinit_num_ostreams = pars->sctp.sockopt_initmsg.num_ostreams_value;
+ if (pars->sctp.sockopt_initmsg.max_instreams_present)
+ si.sinit_max_instreams = pars->sctp.sockopt_initmsg.max_instreams_value;
+ if (pars->sctp.sockopt_initmsg.max_attempts_present)
+ si.sinit_max_attempts = pars->sctp.sockopt_initmsg.max_attempts_value;
+ if (pars->sctp.sockopt_initmsg.max_init_timeo_present)
+ si.sinit_max_init_timeo = pars->sctp.sockopt_initmsg.max_init_timeo_value;
+
+ return setsockopt(fd, IPPROTO_SCTP, SCTP_INITMSG, &si, sizeof(si));
+#else
+#pragma message "setsockopt(SCTP_INITMSG) not supported! some SCTP features may not be available!"
+ LOGP(DLGLOBAL, LOGL_NOTICE, "Built without support for setsockopt(SCTP_INITMSG), skipping\n");
+ return -ENOTSUP
+#endif
+}
+
/*! Initialize a socket (including bind and/or connect) with multiple local or remote addresses.
* \param[in] family Address Family like AF_INET, AF_INET6, AF_UNSPEC
* \param[in] type Socket type like SOCK_DGRAM, SOCK_STREAM
@@ -696,25 +827,58 @@ int osmo_sock_init2_multiaddr(uint16_t family, uint16_t type, uint8_t proto,
const char **local_hosts, size_t local_hosts_cnt, uint16_t local_port,
const char **remote_hosts, size_t remote_hosts_cnt, uint16_t remote_port,
unsigned int flags)
+{
+ return osmo_sock_init2_multiaddr2(family, type, proto, local_hosts, local_hosts_cnt, local_port,
+ remote_hosts, remote_hosts_cnt, remote_port, flags, NULL);
+}
+
+/*! Initialize a socket (including bind and/or connect) with multiple local or remote addresses.
+ * \param[in] family Address Family like AF_INET, AF_INET6, AF_UNSPEC
+ * \param[in] type Socket type like SOCK_DGRAM, SOCK_STREAM
+ * \param[in] proto Protocol like IPPROTO_TCP, IPPROTO_UDP
+ * \param[in] local_hosts array of char pointers (strings), each containing local host name or IP address in string form
+ * \param[in] local_hosts_cnt length of local_hosts (in items)
+ * \param[in] local_port local port number in host byte order
+ * \param[in] remote_host array of char pointers (strings), each containing remote host name or IP address in string form
+ * \param[in] remote_hosts_cnt length of remote_hosts (in items)
+ * \param[in] remote_port remote port number in host byte order
+ * \param[in] flags flags like \ref OSMO_SOCK_F_CONNECT
+ * \param[in] pars Extra parameters for multi-address specific protocols, such as SCTP. Can be NULL.
+ * \returns socket file descriptor on success; negative on error
+ *
+ * This function is similar to \ref osmo_sock_init2(), but can be passed an
+ * array of local or remote addresses for protocols supporting multiple
+ * addresses per socket, like SCTP (currently only one supported). This function
+ * should not be used by protocols not supporting this kind of features, but
+ * rather \ref osmo_sock_init2() should be used instead.
+ * See \ref osmo_sock_init2() for more information on flags and general behavior.
+ *
+ * pars: If "pars" parameter is passed to the function, sctp.version shall be set to 0.
+ */
+int osmo_sock_init2_multiaddr2(uint16_t family, uint16_t type, uint8_t proto,
+ const char **local_hosts, size_t local_hosts_cnt, uint16_t local_port,
+ const char **remote_hosts, size_t remote_hosts_cnt, uint16_t remote_port,
+ unsigned int flags, struct osmo_sock_init2_multiaddr_pars *pars)
{
struct addrinfo *res_loc[OSMO_SOCK_MAX_ADDRS], *res_rem[OSMO_SOCK_MAX_ADDRS];
int sfd = -1, rc, on = 1;
- int i;
- bool loc_has_v4addr, rem_has_v4addr;
- bool loc_has_v6addr, rem_has_v6addr;
+ unsigned int i;
+ bool loc_has_v4addr = false, loc_has_v6addr = false;
+ bool rem_has_v4addr = false, rem_has_v6addr = false;
+ bool loc_has_v4only_addr, rem_has_v4only_addr;
+ bool loc_has_v6only_addr, rem_has_v6only_addr;
struct sockaddr_in6 addrs_buf[OSMO_SOCK_MAX_ADDRS];
char strbuf[512];
- /* updated later in case of AF_UNSPEC */
- loc_has_v4addr = rem_has_v4addr = (family == AF_INET);
- loc_has_v6addr = rem_has_v6addr = (family == AF_INET6);
-
/* TODO: So far this function is only aimed for SCTP, but could be
reused in the future for other protocols with multi-addr support */
if (proto != IPPROTO_SCTP)
return -ENOTSUP;
+ if (pars && pars->sctp.version != 0)
+ return -EINVAL;
+
if ((flags & (OSMO_SOCK_F_BIND | OSMO_SOCK_F_CONNECT)) == 0) {
LOGP(DLGLOBAL, LOGL_ERROR, "invalid: you have to specify either "
"BIND or CONNECT flags\n");
@@ -733,10 +897,17 @@ int osmo_sock_init2_multiaddr(uint16_t family, uint16_t type, uint8_t proto,
local_hosts_cnt, local_port, true);
if (rc < 0)
return -EINVAL;
- /* Figure out if there's any IPV4 or IPv6 addr in the set */
- if (family == AF_UNSPEC)
- addrinfo_has_v4v6addr((const struct addrinfo **)res_loc, local_hosts_cnt,
- &loc_has_v4addr, &loc_has_v6addr);
+ /* Figure out if there's any IPv4 or IPv6 entry in the result set */
+ addrinfo_has_v4v6addr((const struct addrinfo **)res_loc, local_hosts_cnt,
+ &loc_has_v4addr, &loc_has_v6addr);
+ /* Figure out if there's any IPv4-only or IPv6-only addr in the result set */
+ addrinfo_has_v4v6only_addr((const struct addrinfo **)res_loc, local_hosts_cnt,
+ &loc_has_v4only_addr, &loc_has_v6only_addr);
+ if (family == AF_INET && loc_has_v6only_addr) {
+ LOGP(DLGLOBAL, LOGL_ERROR, "Cannot bind an IPv6 address to an AF_INET socket\n");
+ rc = -EINVAL;
+ goto ret_freeaddrinfo_loc;
+ }
}
/* figure out remote side of socket */
if (flags & OSMO_SOCK_F_CONNECT) {
@@ -746,39 +917,109 @@ int osmo_sock_init2_multiaddr(uint16_t family, uint16_t type, uint8_t proto,
rc = -EINVAL;
goto ret_freeaddrinfo_loc;
}
- /* Figure out if there's any IPv4 or IPv6 addr in the set */
- if (family == AF_UNSPEC)
- addrinfo_has_v4v6addr((const struct addrinfo **)res_rem, remote_hosts_cnt,
- &rem_has_v4addr, &rem_has_v6addr);
+ /* Figure out if there's any IPv4 or IPv6 entry in the result set */
+ addrinfo_has_v4v6addr((const struct addrinfo **)res_rem, remote_hosts_cnt,
+ &rem_has_v4addr, &rem_has_v6addr);
+ /* Figure out if there's any IPv4-only or IPv6-only addr in the result set */
+ addrinfo_has_v4v6only_addr((const struct addrinfo **)res_rem, remote_hosts_cnt,
+ &rem_has_v4only_addr, &rem_has_v6only_addr);
+ if (family == AF_INET && rem_has_v6only_addr) {
+ LOGP(DLGLOBAL, LOGL_ERROR, "Cannot connect to an IPv6 address in an AF_INET socket\n");
+ rc = -EINVAL;
+ goto ret_freeaddrinfo;
+ }
+ }
+
+ /* Find out the socket family now if not established by caller:
+ * Both are checked here through "or" here to account for "bind flag set,
+ * connect flag not set" and viceversa. */
+ if (family == AF_UNSPEC) {
+ if (!loc_has_v6addr && !rem_has_v6addr)
+ family = AF_INET;
+ else
+ family = AF_INET6;
}
- if (((flags & OSMO_SOCK_F_BIND) && (flags & OSMO_SOCK_F_CONNECT)) &&
- !addrinfo_has_in6addr_any((const struct addrinfo **)res_loc, local_hosts_cnt) &&
+ /* if both sets are used, make sure there's at least 1 address of the
+ * same type on each set so that SCTP INIT/INIT-ACK can work. */
+ if (family == AF_INET6 && ((flags & OSMO_SOCK_F_BIND) && (flags & OSMO_SOCK_F_CONNECT)) &&
(loc_has_v4addr != rem_has_v4addr || loc_has_v6addr != rem_has_v6addr)) {
- LOGP(DLGLOBAL, LOGL_ERROR, "Invalid v4 vs v6 in local vs remote addresses\n");
- rc = -EINVAL;
- goto ret_freeaddrinfo;
+ if (!addrinfo_has_in6addr_any((const struct addrinfo **)res_loc, local_hosts_cnt)) {
+ LOGP(DLGLOBAL, LOGL_ERROR, "Invalid v4 vs v6 in local vs remote addresses: "
+ "local:%s%s remote:%s%s\n",
+ loc_has_v4addr ? " v4" : "", loc_has_v6addr ? " v6" : "",
+ rem_has_v4addr ? " v4" : "", rem_has_v6addr ? " v6" : "");
+ rc = -EINVAL;
+ goto ret_freeaddrinfo;
+ }
}
- sfd = socket_helper_multiaddr(loc_has_v6addr ? AF_INET6 : AF_INET,
- type, proto, flags);
+ sfd = socket_helper_multiaddr(family, type, proto, flags);
if (sfd < 0) {
rc = sfd;
goto ret_freeaddrinfo;
}
+ if (pars) {
+ if (pars->sctp.sockopt_auth_supported.set) {
+ /* RFC 5061 4.2.7: ASCONF also requires AUTH feature. */
+ rc = setsockopt_sctp_auth_supported(sfd, pars->sctp.sockopt_auth_supported.value);
+ if (rc < 0) {
+ int err = errno;
+ multiaddr_snprintf(strbuf, sizeof(strbuf), local_hosts, local_hosts_cnt);
+ LOGP(DLGLOBAL, LOGL_ERROR,
+ "cannot setsockopt(SCTP_AUTH_SUPPORTED) socket: %s:%u: %s\n",
+ strbuf, local_port, strerror(err));
+ if (pars->sctp.sockopt_auth_supported.abort_on_failure)
+ goto ret_close;
+ /* do not fail, some features such as Peer Primary Address won't be available
+ * unless configured system-wide through sysctl */
+ }
+ }
+
+ if (pars->sctp.sockopt_asconf_supported.set) {
+ rc = setsockopt_sctp_asconf_supported(sfd, pars->sctp.sockopt_asconf_supported.value);
+ if (rc < 0) {
+ int err = errno;
+ multiaddr_snprintf(strbuf, sizeof(strbuf), local_hosts, local_hosts_cnt);
+ LOGP(DLGLOBAL, LOGL_ERROR,
+ "cannot setsockopt(SCTP_ASCONF_SUPPORTED) socket: %s:%u: %s\n",
+ strbuf, local_port, strerror(err));
+ if (pars->sctp.sockopt_asconf_supported.abort_on_failure)
+ goto ret_close;
+ /* do not fail, some features such as Peer Primary Address won't be available
+ * unless configured system-wide through sysctl */
+ }
+ }
+
+ if (pars->sctp.sockopt_initmsg.set) {
+ rc = setsockopt_sctp_initmsg(sfd, pars);
+ if (rc < 0) {
+ int err = errno;
+ multiaddr_snprintf(strbuf, sizeof(strbuf), local_hosts, local_hosts_cnt);
+ LOGP(DLGLOBAL, LOGL_ERROR,
+ "cannot setsockopt(SCTP_INITMSG) socket: %s:%u: %s\n",
+ strbuf, local_port, strerror(err));
+ if (pars->sctp.sockopt_initmsg.abort_on_failure)
+ goto ret_close;
+ /* do not fail, some parameters will be left as the global default */
+ }
+ }
+ }
+
if (flags & OSMO_SOCK_F_BIND) {
/* Since so far we only allow IPPROTO_SCTP in this function,
no need to check below for "proto != IPPROTO_UDP || flags & OSMO_SOCK_F_UDP_REUSEADDR" */
rc = setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR,
&on, sizeof(on));
if (rc < 0) {
+ int err = errno;
multiaddr_snprintf(strbuf, sizeof(strbuf), local_hosts, local_hosts_cnt);
LOGP(DLGLOBAL, LOGL_ERROR,
"cannot setsockopt socket:"
" %s:%u: %s\n",
strbuf, local_port,
- strerror(errno));
+ strerror(err));
goto ret_close;
}
@@ -796,9 +1037,10 @@ int osmo_sock_init2_multiaddr(uint16_t family, uint16_t type, uint8_t proto,
rc = sctp_bindx(sfd, (struct sockaddr *)addrs_buf, local_hosts_cnt, SCTP_BINDX_ADD_ADDR);
if (rc == -1) {
+ int err = errno;
multiaddr_snprintf(strbuf, sizeof(strbuf), local_hosts, local_hosts_cnt);
LOGP(DLGLOBAL, LOGL_NOTICE, "unable to bind socket: %s:%u: %s\n",
- strbuf, local_port, strerror(errno));
+ strbuf, local_port, strerror(err));
rc = -ENODEV;
goto ret_close;
}
@@ -818,9 +1060,10 @@ int osmo_sock_init2_multiaddr(uint16_t family, uint16_t type, uint8_t proto,
rc = sctp_connectx(sfd, (struct sockaddr *)addrs_buf, remote_hosts_cnt, NULL);
if (rc != 0 && errno != EINPROGRESS) {
+ int err = errno;
multiaddr_snprintf(strbuf, sizeof(strbuf), remote_hosts, remote_hosts_cnt);
LOGP(DLGLOBAL, LOGL_ERROR, "unable to connect socket: %s:%u: %s\n",
- strbuf, remote_port, strerror(errno));
+ strbuf, remote_port, strerror(err));
rc = -ENODEV;
goto ret_close;
}
@@ -869,7 +1112,9 @@ int osmo_sock_init(uint16_t family, uint16_t type, uint8_t proto,
const char *host, uint16_t port, unsigned int flags)
{
struct addrinfo *result, *rp;
- int sfd, rc, on = 1;
+ int sfd = -1; /* initialize to avoid uninitialized false warnings on some gcc versions (11.1.0) */
+ int on = 1;
+ int rc;
if ((flags & (OSMO_SOCK_F_BIND | OSMO_SOCK_F_CONNECT)) ==
(OSMO_SOCK_F_BIND | OSMO_SOCK_F_CONNECT)) {
@@ -947,11 +1192,12 @@ int osmo_sock_init(uint16_t family, uint16_t type, uint8_t proto,
/*! fill \ref osmo_fd for a give sfd
* \param[out] ofd file descriptor (will be filled in)
* \param[in] sfd socket file descriptor
+ * \param[in] flags flags like \ref OSMO_SOCK_F_CONNECT
* \returns socket fd on success; negative on error
*
* This function fills the \a ofd structure.
*/
-static inline int osmo_fd_init_ofd(struct osmo_fd *ofd, int sfd)
+static inline int osmo_fd_init_ofd(struct osmo_fd *ofd, int sfd, unsigned int flags)
{
int rc;
@@ -961,6 +1207,14 @@ static inline int osmo_fd_init_ofd(struct osmo_fd *ofd, int sfd)
ofd->fd = sfd;
ofd->when = OSMO_FD_READ;
+ /* if we're doing a non-blocking connect, the completion will be signaled
+ * by marking the fd as WRITE-able. So in this exceptional case, we're
+ * also interested in when the socket becomes write-able */
+ if ((flags & (OSMO_SOCK_F_CONNECT|OSMO_SOCK_F_NONBLOCK)) ==
+ (OSMO_SOCK_F_CONNECT|OSMO_SOCK_F_NONBLOCK)) {
+ ofd->when |= OSMO_FD_WRITE;
+ }
+
rc = osmo_fd_register(ofd);
if (rc < 0) {
close(sfd);
@@ -986,7 +1240,7 @@ static inline int osmo_fd_init_ofd(struct osmo_fd *ofd, int sfd)
int osmo_sock_init_ofd(struct osmo_fd *ofd, int family, int type, int proto,
const char *host, uint16_t port, unsigned int flags)
{
- return osmo_fd_init_ofd(ofd, osmo_sock_init(family, type, proto, host, port, flags));
+ return osmo_fd_init_ofd(ofd, osmo_sock_init(family, type, proto, host, port, flags), flags);
}
/*! Initialize a socket and fill \ref osmo_fd
@@ -1009,14 +1263,14 @@ int osmo_sock_init2_ofd(struct osmo_fd *ofd, int family, int type, int proto,
const char *remote_host, uint16_t remote_port, unsigned int flags)
{
return osmo_fd_init_ofd(ofd, osmo_sock_init2(family, type, proto, local_host,
- local_port, remote_host, remote_port, flags));
+ local_port, remote_host, remote_port, flags), flags);
}
int osmo_sock_init_osa_ofd(struct osmo_fd *ofd, int type, int proto,
const struct osmo_sockaddr *local,
const struct osmo_sockaddr *remote, unsigned int flags)
{
- return osmo_fd_init_ofd(ofd, osmo_sock_init_osa(type, proto, local, remote, flags));
+ return osmo_fd_init_ofd(ofd, osmo_sock_init_osa(type, proto, local, remote, flags), flags);
}
/*! Initialize a socket and fill \ref sockaddr
@@ -1065,6 +1319,160 @@ int osmo_sock_init_sa(struct sockaddr *ss, uint16_t type,
return osmo_sock_init(ss->sa_family, type, proto, host, port, flags);
}
+#ifdef HAVE_LIBSCTP
+/*! Add addresses to the multi-address (SCTP) socket active binding set
+ * \param[in] sfd The multi-address (SCTP) socket
+ * \param[in] addrs array of char pointers (strings), each containing local host name or IP address in string form
+ * \param[in] addrs_cnt length of addrs_hosts (in items)
+ * \returns 0 on success; negative on error
+ *
+ * This function only supports SCTP sockets so far, and hence it should be
+ * called only on socket file descriptions referencing that kind of sockets.
+ */
+int osmo_sock_multiaddr_add_local_addr(int sfd, const char **addrs, size_t addrs_cnt)
+{
+ struct osmo_sockaddr osa;
+ socklen_t slen = sizeof(osa);
+ uint16_t sfd_family;
+ uint16_t type = SOCK_STREAM ;/* Fixme: we assume fd is SOCK_STREAM */
+ uint8_t proto = IPPROTO_SCTP; /* Fixme: we assume fd is IPPROTO_SCTP */
+ struct addrinfo *res[OSMO_SOCK_MAX_ADDRS];
+ uint16_t port;
+ struct sockaddr_in6 addrs_buf[OSMO_SOCK_MAX_ADDRS];
+ unsigned int i;
+ int rc;
+ bool res_has_v4addr = false, res_has_v6addr = false;
+
+ rc = getsockname(sfd, &osa.u.sa, &slen);
+ if (rc < 0)
+ return rc; /* TODO: log error? */
+ sfd_family = osa.u.sa.sa_family;
+ port = osmo_sockaddr_port(&osa.u.sa);
+
+ if (sfd_family != AF_INET && sfd_family != AF_INET6)
+ return -EINVAL;
+
+ rc = addrinfo_helper_multi(res, AF_UNSPEC, type, proto, addrs,
+ addrs_cnt, port, true);
+ if (rc < 0)
+ return -EINVAL;
+
+ addrinfo_has_v4v6addr((const struct addrinfo **)res, addrs_cnt,
+ &res_has_v4addr, &res_has_v6addr);
+ if (sfd_family == AF_INET && !res_has_v4addr) {
+ rc = -EINVAL;
+ goto ret_free;
+ }
+
+ uint16_t new_addr_family;
+ if (sfd_family == AF_INET)
+ new_addr_family = AF_INET;
+ else if (sfd_family == AF_INET6 && !res_has_v4addr)
+ new_addr_family = AF_INET6;
+ else
+ new_addr_family = AF_UNSPEC;
+ rc = addrinfo_to_sockaddr(new_addr_family, (const struct addrinfo **)res,
+ addrs, addrs_cnt,
+ (uint8_t *)addrs_buf, sizeof(addrs_buf));
+ if (rc < 0) {
+ rc = -ENODEV;
+ goto ret_free;
+ }
+
+ rc = sctp_bindx(sfd, (struct sockaddr *)addrs_buf, addrs_cnt, SCTP_BINDX_ADD_ADDR);
+ if (rc == -1) {
+ int err = errno;
+ char strbuf[512];
+ multiaddr_snprintf(strbuf, sizeof(strbuf), addrs, addrs_cnt);
+ LOGP(DLGLOBAL, LOGL_NOTICE, "Unable to bind socket to new addresses: %s:%u: %s\n",
+ strbuf, port, strerror(err));
+ rc = -ENODEV;
+ goto ret_free;
+ }
+
+ret_free:
+ for (i = 0; i < addrs_cnt; i++)
+ freeaddrinfo(res[i]);
+ return rc;
+}
+
+/*! Remove addresses from the multi-address (SCTP) socket active binding set
+ * \param[in] sfd The multi-address (SCTP) socket
+ * \param[in] addrs array of char pointers (strings), each containing local host name or IP address in string form
+ * \param[in] addrs_cnt length of addrs_hosts (in items)
+ * \returns 0 on success; negative on error
+ *
+ * This function only supports SCTP sockets so far, and hence it should be
+ * called only on socket file descriptions referencing that kind of sockets.
+ */
+int osmo_sock_multiaddr_del_local_addr(int sfd, const char **addrs, size_t addrs_cnt)
+{
+ struct osmo_sockaddr osa;
+ socklen_t slen = sizeof(osa);
+ uint16_t sfd_family;
+ uint16_t type = SOCK_STREAM ;/* Fixme: we assume fd is SOCK_STREAM */
+ uint8_t proto = IPPROTO_SCTP; /* Fixme: we assume fd is IPPROTO_SCTP */
+ struct addrinfo *res[OSMO_SOCK_MAX_ADDRS];
+ uint16_t port;
+ struct sockaddr_in6 addrs_buf[OSMO_SOCK_MAX_ADDRS];
+ unsigned int i;
+ int rc;
+ bool res_has_v4addr = false, res_has_v6addr = false;
+
+ rc = getsockname(sfd, &osa.u.sa, &slen);
+ if (rc < 0)
+ return rc; /* TODO: log error? */
+ sfd_family = osa.u.sa.sa_family;
+ port = osmo_sockaddr_port(&osa.u.sa);
+
+ if (sfd_family != AF_INET && sfd_family != AF_INET6)
+ return -EINVAL;
+
+ rc = addrinfo_helper_multi(res, AF_UNSPEC, type, proto, addrs,
+ addrs_cnt, port, true);
+ if (rc < 0)
+ return -EINVAL;
+
+ addrinfo_has_v4v6addr((const struct addrinfo **)res, addrs_cnt,
+ &res_has_v4addr, &res_has_v6addr);
+ if (sfd_family == AF_INET && !res_has_v4addr) {
+ rc = -EINVAL;
+ goto ret_free;
+ }
+
+ uint16_t del_addr_family;
+ if (sfd_family == AF_INET)
+ del_addr_family = AF_INET;
+ else if (sfd_family == AF_INET6 && !res_has_v4addr)
+ del_addr_family = AF_INET6;
+ else
+ del_addr_family = AF_UNSPEC;
+ rc = addrinfo_to_sockaddr(del_addr_family, (const struct addrinfo **)res,
+ addrs, addrs_cnt,
+ (uint8_t *)addrs_buf, sizeof(addrs_buf));
+ if (rc < 0) {
+ rc = -ENODEV;
+ goto ret_free;
+ }
+
+ rc = sctp_bindx(sfd, (struct sockaddr *)addrs_buf, addrs_cnt, SCTP_BINDX_REM_ADDR);
+ if (rc == -1) {
+ int err = errno;
+ char strbuf[512];
+ multiaddr_snprintf(strbuf, sizeof(strbuf), addrs, addrs_cnt);
+ LOGP(DLGLOBAL, LOGL_NOTICE, "Unable to unbind socket from addresses: %s:%u: %s\n",
+ strbuf, port, strerror(err));
+ rc = -ENODEV;
+ goto ret_free;
+ }
+
+ret_free:
+ for (i = 0; i < addrs_cnt; i++)
+ freeaddrinfo(res[i]);
+ return rc;
+}
+#endif /* HAVE_LIBSCTP */
+
static int sockaddr_equal(const struct sockaddr *a,
const struct sockaddr *b, unsigned int len)
{
@@ -1093,6 +1501,25 @@ static int sockaddr_equal(const struct sockaddr *a,
return 0;
}
+/* linux has a default route:
+local 127.0.0.0/8 dev lo proto kernel scope host src 127.0.0.1
+*/
+static int sockaddr_is_local_routed(const struct sockaddr *a)
+{
+#if __linux__
+ if (a->sa_family != AF_INET)
+ return 0;
+
+ uint32_t address = ((struct sockaddr_in *)a)->sin_addr.s_addr; /* already BE */
+ uint32_t eightmask = htonl(0xff000000); /* /8 mask */
+ uint32_t local_prefix_127 = htonl(0x7f000000); /* 127.0.0.0 */
+
+ if ((address & eightmask) == local_prefix_127)
+ return 1;
+#endif
+ return 0;
+}
+
/*! Determine if the given address is a local address
* \param[in] addr Socket Address
* \param[in] addrlen Length of socket address in bytes
@@ -1102,6 +1529,9 @@ int osmo_sockaddr_is_local(struct sockaddr *addr, unsigned int addrlen)
{
struct ifaddrs *ifaddr, *ifa;
+ if (sockaddr_is_local_routed(addr))
+ return 1;
+
if (getifaddrs(&ifaddr) == -1) {
LOGP(DLGLOBAL, LOGL_ERROR, "getifaddrs:"
" %s\n", strerror(errno));
@@ -1121,6 +1551,26 @@ int osmo_sockaddr_is_local(struct sockaddr *addr, unsigned int addrlen)
return 0;
}
+/*! Determine if the given address is an ANY address ("0.0.0.0", "::"). Port is not checked.
+ * \param[in] addr Socket Address
+ * \param[in] addrlen Length of socket address in bytes
+ * \returns 1 if address is ANY, 0 otherwise. -1 is address family not supported/detected.
+ */
+int osmo_sockaddr_is_any(const struct osmo_sockaddr *addr)
+{
+ switch (addr->u.sa.sa_family) {
+ case AF_INET6: {
+ struct in6_addr ip6_any = IN6ADDR_ANY_INIT;
+ return memcmp(&addr->u.sin6.sin6_addr,
+ &ip6_any, sizeof(ip6_any)) == 0;
+ }
+ case AF_INET:
+ return addr->u.sin.sin_addr.s_addr == INADDR_ANY;
+ default:
+ return -1;
+ }
+}
+
/*! Convert sockaddr_in to IP address as char string and port as uint16_t.
* \param[out] addr String buffer to write IP address to, or NULL.
* \param[out] addr_len Size of \a addr.
@@ -1200,6 +1650,101 @@ uint16_t osmo_sockaddr_port(const struct sockaddr *sa)
return 0;
}
+/*! Set sockaddr port content (to network byte order).
+ * \param[out] sa sockaddr to set the port of.
+ * \param[in] port port nr to set.
+ */
+void osmo_sockaddr_set_port(struct sockaddr *sa, uint16_t port)
+{
+ struct osmo_sockaddr *osa = (struct osmo_sockaddr *)sa;
+ switch (osa->u.sa.sa_family) {
+ case AF_INET6:
+ osa->u.sin6.sin6_port = htons(port);
+ return;
+ case AF_INET:
+ osa->u.sin.sin_port = htons(port);
+ return;
+ }
+}
+
+static unsigned int in6_addr_netmask_to_prefixlen(const struct in6_addr *netmask)
+{
+ #if defined(__linux__)
+ #define ADDRFIELD(i) s6_addr32[i]
+ #else
+ #define ADDRFIELD(i) __u6_addr.__u6_addr32[i]
+ #endif
+
+ unsigned int i, j, prefix = 0;
+
+ for (j = 0; j < 4; j++) {
+ uint32_t bits = netmask->ADDRFIELD(j);
+ uint8_t *b = (uint8_t *)&bits;
+ for (i = 0; i < 4; i++) {
+ while (b[i] & 0x80) {
+ prefix++;
+ b[i] = b[i] << 1;
+ }
+ }
+ }
+
+ #undef ADDRFIELD
+
+ return prefix;
+}
+
+static unsigned int in_addr_netmask_to_prefixlen(const struct in_addr *netmask)
+{
+ uint32_t bits = netmask->s_addr;
+ uint8_t *b = (uint8_t *)&bits;
+ unsigned int i, prefix = 0;
+
+ for (i = 0; i < 4; i++) {
+ while (b[i] & 0x80) {
+ prefix++;
+ b[i] = b[i] << 1;
+ }
+ }
+ return prefix;
+}
+
+/*! Convert netmask to prefix length representation
+ * \param[in] netmask sockaddr containing a netmask (consecutive list of 1-bit followed by consecutive list of 0-bit)
+ * \returns prefix length representation of the netmask (count of 1-bit from the start of the netmask), negative on error.
+ */
+int osmo_sockaddr_netmask_to_prefixlen(const struct osmo_sockaddr *netmask)
+{
+ switch (netmask->u.sa.sa_family) {
+ case AF_INET6:
+ return in6_addr_netmask_to_prefixlen(&netmask->u.sin6.sin6_addr);
+ case AF_INET:
+ return in_addr_netmask_to_prefixlen(&netmask->u.sin.sin_addr);
+ default:
+ return -ENOTSUP;
+ }
+}
+
+/*! Convert an IP address string (and port number) into a 'struct osmo_sockaddr'.
+ * \param[out] osa_out caller-allocated osmo_sockaddr storage
+ * \param[in] ipstr IP[v4,v6] address in string format
+ * \param[in] port port number (host byte order)
+ * \returns 0 on success; negative on error. */
+int osmo_sockaddr_from_str_and_uint(struct osmo_sockaddr *osa_out, const char *ipstr, uint16_t port)
+{
+ struct addrinfo *ai = addrinfo_helper(AF_UNSPEC, 0, 0, ipstr, port, true);
+
+ if (!ai)
+ return -EIO;
+
+ if (ai->ai_addrlen > sizeof(*osa_out))
+ return -ENOSPC;
+
+ memcpy(&osa_out->u.sa, ai->ai_addr, ai->ai_addrlen);
+ freeaddrinfo(ai);
+
+ return 0;
+}
+
/*! Initialize a unix domain socket (including bind/connect)
* \param[in] type Socket type like SOCK_DGRAM, SOCK_STREAM
* \param[in] proto Protocol like IPPROTO_TCP, IPPROTO_UDP
@@ -1218,7 +1763,7 @@ int osmo_sock_unix_init(uint16_t type, uint8_t proto,
const char *socket_path, unsigned int flags)
{
struct sockaddr_un local;
- int sfd, rc, on = 1;
+ int sfd, rc;
unsigned int namelen;
if ((flags & (OSMO_SOCK_F_BIND | OSMO_SOCK_F_CONNECT)) ==
@@ -1245,7 +1790,7 @@ int osmo_sock_unix_init(uint16_t type, uint8_t proto,
sfd = socket(AF_UNIX, type, proto);
if (sfd < 0)
- return -1;
+ return -errno;
if (flags & OSMO_SOCK_F_CONNECT) {
rc = connect(sfd, (struct sockaddr *)&local, namelen);
@@ -1258,26 +1803,20 @@ int osmo_sock_unix_init(uint16_t type, uint8_t proto,
goto err;
}
- if (flags & OSMO_SOCK_F_NONBLOCK) {
- if (ioctl(sfd, FIONBIO, (unsigned char *)&on) < 0) {
- LOGP(DLGLOBAL, LOGL_ERROR,
- "cannot set this socket unblocking: %s\n",
- strerror(errno));
- close(sfd);
- return -EINVAL;
- }
- }
+ rc = socket_helper_tail(sfd, flags);
+ if (rc < 0)
+ return rc;
rc = osmo_sock_init_tail(sfd, type, flags);
if (rc < 0) {
close(sfd);
- sfd = -1;
+ sfd = rc;
}
return sfd;
err:
close(sfd);
- return -1;
+ return -errno;
}
/*! Initialize a unix domain socket and fill \ref osmo_fd
@@ -1294,7 +1833,7 @@ err:
int osmo_sock_unix_init_ofd(struct osmo_fd *ofd, uint16_t type, uint8_t proto,
const char *socket_path, unsigned int flags)
{
- return osmo_fd_init_ofd(ofd, osmo_sock_unix_init(type, proto, socket_path, flags));
+ return osmo_fd_init_ofd(ofd, osmo_sock_unix_init(type, proto, socket_path, flags), flags);
}
/*! Get the IP and/or port number on socket in separate string buffers.
@@ -1330,6 +1869,97 @@ int osmo_sock_get_ip_and_port(int fd, char *ip, size_t ip_len, char *port, size_
return 0;
}
+#ifdef HAVE_LIBSCTP
+/*! Get multiple IP addresses and/or port number on socket in separate string buffers
+ * \param[in] fd file descriptor of socket.
+ * \param[out] ip_proto IPPROTO of the socket, eg: IPPROTO_SCTP.
+ * \param[out] ip Pointer to memory holding consecutive buffers of size ip_len.
+ * \param[out] ip_cnt length ip array pointer. on return it contains the number of addresses found.
+ * \param[in] ip_len length of each of the string buffer in the the ip array.
+ * \param[out] port number (will be filled in when not NULL).
+ * \param[in] port_len length of the port buffer.
+ * \param[in] local (true) or remote (false) name will get looked at.
+ * \returns 0 on success; negative otherwise.
+ *
+ * Upon return, ip_cnt can be set to a higher value than the one set by the
+ * caller. This can be used by the caller to find out the required array length
+ * and then obtaining by calling the function twice. Only up to ip_cnt addresses
+ * are filed in, as per the value provided by the caller.
+ *
+ * Usage example retrieving all (up to OSMO_SOCK_MAX_ADDRS, 32) bound IP addresses and bound port:
+ * char hostbuf[OSMO_SOCK_MAX_ADDRS][INET6_ADDRSTRLEN];
+ * size_t num_hostbuf = ARRAY_SIZE(hostbuf);
+ * char portbuf[6];
+ * rc = osmo_sock_multiaddr_get_ip_and_port(fd, IPPROTO_SCTP, &hostbuf[0][0], &num_hostbuf,
+ * sizeof(hostbuf[0]), portbuf, sizeof(portbuf), true);
+ * if (rc < 0)
+ * goto error;
+ * if (num_hostbuf > ARRAY_SIZE(hostbuf))
+ * goto not_enough_buffers;
+ */
+int osmo_sock_multiaddr_get_ip_and_port(int fd, int ip_proto, char *ip, size_t *ip_cnt, size_t ip_len,
+ char *port, size_t port_len, bool local)
+{
+ struct sockaddr *addrs = NULL;
+ unsigned int n_addrs, i;
+ void *addr_buf;
+ int rc;
+
+ switch (ip_proto) {
+ case IPPROTO_SCTP:
+ break; /* continue below */
+ default:
+ if (*ip_cnt == 0) {
+ *ip_cnt = 1;
+ return 0;
+ }
+ *ip_cnt = 1;
+ return osmo_sock_get_ip_and_port(fd, ip, ip_len, port, port_len, local);
+ }
+
+ rc = local ? sctp_getladdrs(fd, 0, &addrs) : sctp_getpaddrs(fd, 0, &addrs);
+ if (rc < 0)
+ return rc;
+ if (rc == 0)
+ return -ENOTCONN;
+
+ n_addrs = rc;
+ addr_buf = (void *)addrs;
+ for (i = 0; i < n_addrs; i++) {
+ struct sockaddr *sa_addr = (struct sockaddr *)addr_buf;
+ size_t addrlen;
+
+ if (i >= *ip_cnt)
+ break;
+
+ switch (sa_addr->sa_family) {
+ case AF_INET:
+ addrlen = sizeof(struct sockaddr_in);
+ break;
+ case AF_INET6:
+ addrlen = sizeof(struct sockaddr_in6);
+ break;
+ default:
+ rc = -EINVAL;
+ goto free_addrs_ret;
+ }
+
+ rc = getnameinfo(sa_addr, addrlen, &ip[i * ip_len], ip_len,
+ port, port_len,
+ NI_NUMERICHOST | NI_NUMERICSERV);
+ if (rc < 0)
+ goto free_addrs_ret;
+ addr_buf += addrlen;
+ }
+
+ *ip_cnt = n_addrs;
+ rc = 0;
+free_addrs_ret:
+ local ? sctp_freeladdrs(addrs) : sctp_freepaddrs(addrs);
+ return rc;
+}
+#endif
+
/*! Get local IP address on socket
* \param[in] fd file descriptor of socket
* \param[out] ip IP address (will be filled in)
@@ -1391,6 +2021,114 @@ char *osmo_sock_get_name(const void *ctx, int fd)
return talloc_asprintf(ctx, "(%s)", str);
}
+#ifdef HAVE_LIBSCTP
+/*! Format multiple IP addresses and/or port number into a combined string buffer
+ * \param[out] str Destination string buffer.
+ * \param[in] str_len sizeof(str), usually OSMO_SOCK_MULTIADDR_PEER_STR_MAXLEN.
+ * \param[out] ip Pointer to memory holding ip_cnt consecutive buffers of size ip_len.
+ * \param[out] ip_cnt length ip array pointer. on return it contains the number of addresses found.
+ * \param[in] ip_len length of each of the string buffer in the the ip array.
+ * \param[out] port number (will be printed in when not NULL).
+ * \return String length as returned by snprintf(), or negative on error.
+ *
+ * This API expects an ip array as the one filled in by
+ * osmo_sock_multiaddr_get_ip_and_port(), and hence it's a good companion for
+ * that API.
+ */
+int osmo_multiaddr_ip_and_port_snprintf(char *str, size_t str_len,
+ const char *ip, size_t ip_cnt, size_t ip_len,
+ const char *portbuf)
+{
+ struct osmo_strbuf sb = { .buf = str, .len = str_len };
+ bool is_v6 = false;
+ unsigned int i;
+
+ if (ip_cnt == 0) {
+ OSMO_STRBUF_PRINTF(sb, "NULL:%s", portbuf);
+ return sb.chars_needed;
+ }
+ if (ip_cnt > 1)
+ OSMO_STRBUF_PRINTF(sb, "(");
+ else if ((is_v6 = !!strchr(&ip[0], ':'))) /* IPv6, add [] to separate from port. */
+ OSMO_STRBUF_PRINTF(sb, "[");
+
+ for (i = 0; i < ip_cnt - 1; i++)
+ OSMO_STRBUF_PRINTF(sb, "%s|", &ip[i * ip_len]);
+ OSMO_STRBUF_PRINTF(sb, "%s", &ip[i * ip_len]);
+
+ if (ip_cnt > 1)
+ OSMO_STRBUF_PRINTF(sb, ")");
+ else if (is_v6)
+ OSMO_STRBUF_PRINTF(sb, "]");
+ if (portbuf)
+ OSMO_STRBUF_PRINTF(sb, ":%s", portbuf);
+
+ return sb.chars_needed;
+}
+
+/*! Get address/port information on socket in provided string buffer, like "r=1.2.3.4:5<->l=6.7.8.9:10".
+ * This does not include braces like osmo_sock_get_name().
+ * \param[out] str Destination string buffer.
+ * \param[in] str_len sizeof(str), usually OSMO_SOCK_MULTIADDR_NAME_MAXLEN.
+ * \param[in] fd File descriptor of socket.
+ * \param[in] fd IPPROTO of the socket, eg: IPPROTO_SCTP.
+ * \return String length as returned by snprintf(), or negative on error.
+ */
+int osmo_sock_multiaddr_get_name_buf(char *str, size_t str_len, int fd, int sk_proto)
+{
+ char hostbuf[OSMO_SOCK_MAX_ADDRS][INET6_ADDRSTRLEN];
+ size_t num_hostbuf = ARRAY_SIZE(hostbuf);
+ char portbuf[6];
+ struct osmo_strbuf sb = { .buf = str, .len = str_len };
+
+ if (fd < 0) {
+ osmo_strlcpy(str, "<error-bad-fd>", str_len);
+ return sb.chars_needed;
+ }
+
+ switch (sk_proto) {
+ case IPPROTO_SCTP:
+ break; /* continue below */
+ default:
+ return osmo_sock_get_name_buf(str, str_len, fd);
+ }
+
+ /* get remote */
+ OSMO_STRBUF_PRINTF(sb, "r=");
+ if (osmo_sock_multiaddr_get_ip_and_port(fd, sk_proto, &hostbuf[0][0], &num_hostbuf,
+ sizeof(hostbuf[0]), portbuf, sizeof(portbuf), false) != 0) {
+ OSMO_STRBUF_PRINTF(sb, "NULL");
+ } else {
+ const bool need_more_bufs = num_hostbuf > ARRAY_SIZE(hostbuf);
+ if (need_more_bufs)
+ num_hostbuf = ARRAY_SIZE(hostbuf);
+ OSMO_STRBUF_APPEND(sb, osmo_multiaddr_ip_and_port_snprintf,
+ &hostbuf[0][0], num_hostbuf, sizeof(hostbuf[0]), portbuf);
+ if (need_more_bufs)
+ OSMO_STRBUF_PRINTF(sb, "<need-more-bufs!>");
+ }
+
+ OSMO_STRBUF_PRINTF(sb, "<->l=");
+
+ /* get local */
+ num_hostbuf = ARRAY_SIZE(hostbuf);
+ if (osmo_sock_multiaddr_get_ip_and_port(fd, sk_proto, &hostbuf[0][0], &num_hostbuf,
+ sizeof(hostbuf[0]), portbuf, sizeof(portbuf), true) != 0) {
+ OSMO_STRBUF_PRINTF(sb, "NULL");
+ } else {
+ const bool need_more_bufs = num_hostbuf > ARRAY_SIZE(hostbuf);
+ if (need_more_bufs)
+ num_hostbuf = ARRAY_SIZE(hostbuf);
+ OSMO_STRBUF_APPEND(sb, osmo_multiaddr_ip_and_port_snprintf,
+ &hostbuf[0][0], num_hostbuf, sizeof(hostbuf[0]), portbuf);
+ if (need_more_bufs)
+ OSMO_STRBUF_PRINTF(sb, "<need-more-bufs!>");
+ }
+
+ return sb.chars_needed;
+}
+#endif
+
/*! Get address/port information on socket in provided string buffer, like "r=1.2.3.4:5<->l=6.7.8.9:10".
* This does not include braces like osmo_sock_get_name().
* \param[out] str Destination string buffer.
@@ -1400,21 +2138,89 @@ char *osmo_sock_get_name(const void *ctx, int fd)
*/
int osmo_sock_get_name_buf(char *str, size_t str_len, int fd)
{
- char hostbuf_l[INET6_ADDRSTRLEN], hostbuf_r[INET6_ADDRSTRLEN];
- char portbuf_l[6], portbuf_r[6];
+ struct osmo_strbuf sb = { .buf = str, .len = str_len };
+ struct osmo_sockaddr osa;
+ struct sockaddr_un *sun;
+ socklen_t len;
int rc;
- /* get local */
- if ((rc = osmo_sock_get_ip_and_port(fd, hostbuf_l, sizeof(hostbuf_l), portbuf_l, sizeof(portbuf_l), true))) {
+ if (fd < 0) {
+ osmo_strlcpy(str, "<error-bad-fd>", str_len);
+ return -EBADF;
+ }
+
+
+ len = sizeof(osa.u.sas);
+ rc = getsockname(fd, &osa.u.sa, &len);
+ if (rc < 0) {
osmo_strlcpy(str, "<error-in-getsockname>", str_len);
return rc;
}
- /* get remote */
- if (osmo_sock_get_ip_and_port(fd, hostbuf_r, sizeof(hostbuf_r), portbuf_r, sizeof(portbuf_r), false) != 0)
- return snprintf(str, str_len, "r=NULL<->l=%s:%s", hostbuf_l, portbuf_l);
-
- return snprintf(str, str_len, "r=%s:%s<->l=%s:%s", hostbuf_r, portbuf_r, hostbuf_l, portbuf_l);
+ switch (osa.u.sa.sa_family) {
+ case AF_INET:
+ case AF_INET6:
+ {
+ char hostbuf_l[INET6_ADDRSTRLEN], hostbuf_r[INET6_ADDRSTRLEN];
+ char portbuf_l[6], portbuf_r[6];
+
+ len = sizeof(osa.u.sas);
+ rc = getnameinfo(&osa.u.sa, len, hostbuf_l, sizeof(hostbuf_l),
+ portbuf_l, sizeof(portbuf_l),
+ NI_NUMERICHOST | NI_NUMERICSERV);
+ if (rc < 0) {
+ osmo_strlcpy(str, "<error-in-getnameinfo>", str_len);
+ return rc;
+ }
+ /* Now attempt to get remote: */
+ len = sizeof(osa.u.sas);
+ rc = getpeername(fd, &osa.u.sa, &len);
+ if (rc < 0) {
+ OSMO_STRBUF_PRINTF(sb, "r=NULL<->l=%s:%s", hostbuf_l, portbuf_l);
+ return sb.chars_needed;
+ }
+ len = sizeof(osa.u.sas);
+ rc = getnameinfo(&osa.u.sa, len, hostbuf_r, sizeof(hostbuf_r),
+ portbuf_r, sizeof(portbuf_r),
+ NI_NUMERICHOST | NI_NUMERICSERV);
+ if (rc < 0) {
+ OSMO_STRBUF_PRINTF(sb, "r=NULL<->l=%s:%s", hostbuf_l, portbuf_l);
+ return sb.chars_needed;
+ }
+ OSMO_STRBUF_PRINTF(sb, "r=%s:%s<->l=%s:%s", hostbuf_r, portbuf_r, hostbuf_l, portbuf_l);
+ return sb.chars_needed;
+ }
+ case AF_UNIX:
+ {
+ unsigned long long remote_pid;
+ bool have_remote_pid;
+#if defined(SO_PEERCRED)
+ struct ucred ucred;
+ len = sizeof(struct ucred);
+ if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &ucred, &len) == -1) {
+ have_remote_pid = false;
+ } else {
+ have_remote_pid = true;
+ remote_pid = (unsigned long long)ucred.pid;
+ }
+#else
+ #pragma message "SO_PEERCRED not available"
+ have_remote_pid = false;
+#endif
+ /* Make sure sun_path is NULL terminated: */
+ sun = (struct sockaddr_un *)&osa.u.sa;
+ sun->sun_path[sizeof(sun->sun_path) - 1] = '\0';
+ if (have_remote_pid)
+ OSMO_STRBUF_PRINTF(sb, "r=%llu<->", remote_pid);
+ else
+ OSMO_STRBUF_PRINTF(sb, "r=NULL<->");
+ OSMO_STRBUF_PRINTF(sb, "l=%s:%d", sun->sun_path, fd);
+ return sb.chars_needed;
+ }
+ default:
+ osmo_strlcpy(str, "<socket-family-no-supported>", str_len);
+ return -ENOTSUP;
+ }
}
/*! Get address/port information on socket in static string, like "r=1.2.3.4:5<->l=6.7.8.9:10".
@@ -1685,6 +2491,65 @@ int osmo_sockaddr_local_ip(struct osmo_sockaddr *local_ip, const struct osmo_soc
return rc;
}
+/*! Copy the addr part, the IP address octets in network byte order, to a buffer.
+ * Useful for encoding network protocols.
+ * \param[out] dst Write octets to this buffer.
+ * \param[in] dst_maxlen Space available in buffer.
+ * \param[in] os Sockaddr to copy IP of.
+ * \return nr of octets written on success, negative on error.
+ */
+int osmo_sockaddr_to_octets(uint8_t *dst, size_t dst_maxlen, const struct osmo_sockaddr *os)
+{
+ const void *addr;
+ size_t len;
+ switch (os->u.sa.sa_family) {
+ case AF_INET:
+ addr = &os->u.sin.sin_addr;
+ len = sizeof(os->u.sin.sin_addr);
+ break;
+ case AF_INET6:
+ addr = &os->u.sin6.sin6_addr;
+ len = sizeof(os->u.sin6.sin6_addr);
+ break;
+ default:
+ return -ENOTSUP;
+ }
+ if (dst_maxlen < len)
+ return -ENOSPC;
+ memcpy(dst, addr, len);
+ return len;
+}
+
+/*! Copy the addr part, the IP address octets in network byte order, from a buffer.
+ * Useful for decoding network protocols.
+ * \param[out] os Write IP address to this sockaddr.
+ * \param[in] src Source buffer to read IP address octets from.
+ * \param[in] src_len Number of octets to copy.
+ * \return number of octets read on success, negative on error.
+ */
+int osmo_sockaddr_from_octets(struct osmo_sockaddr *os, const void *src, size_t src_len)
+{
+ void *addr;
+ size_t len;
+ *os = (struct osmo_sockaddr){0};
+ switch (src_len) {
+ case sizeof(os->u.sin.sin_addr):
+ os->u.sa.sa_family = AF_INET;
+ addr = &os->u.sin.sin_addr;
+ len = sizeof(os->u.sin.sin_addr);
+ break;
+ case sizeof(os->u.sin6.sin6_addr):
+ os->u.sin6.sin6_family = AF_INET6;
+ addr = &os->u.sin6.sin6_addr;
+ len = sizeof(os->u.sin6.sin6_addr);
+ break;
+ default:
+ return -ENOTSUP;
+ }
+ memcpy(addr, src, len);
+ return len;
+}
+
/*! Compare two osmo_sockaddr.
* \param[in] a
* \param[in] b
@@ -1727,55 +2592,211 @@ const char *osmo_sockaddr_to_str(const struct osmo_sockaddr *sockaddr)
return osmo_sockaddr_to_str_buf(buf, sizeof(buf), sockaddr);
}
-/*! string-format a given osmo_sockaddr address into a user-supplied buffer
+/*! string-format a given osmo_sockaddr address into a user-supplied buffer.
+ * Same as osmo_sockaddr_to_str_buf() but returns a would-be length in snprintf() style.
* \param[in] buf user-supplied output buffer
* \param[in] buf_len size of the user-supplied output buffer in bytes
* \param[in] sockaddr the osmo_sockaddr to print
- * \return pointer to the string on success; NULL on error
+ * \return number of characters that would be written if the buffer is large enough, like snprintf().
*/
-char *osmo_sockaddr_to_str_buf(char *buf, size_t buf_len,
- const struct osmo_sockaddr *sockaddr)
+int osmo_sockaddr_to_str_buf2(char *buf, size_t buf_len, const struct osmo_sockaddr *sockaddr)
{
+ struct osmo_strbuf sb = { .buf = buf, .len = buf_len };
uint16_t port = 0;
- size_t written;
- if (buf_len < 5)
- return NULL;
- if (!sockaddr)
- return NULL;
+ if (!sockaddr) {
+ OSMO_STRBUF_PRINTF(sb, "NULL");
+ return sb.chars_needed;
+ }
switch (sockaddr->u.sa.sa_family) {
case AF_INET:
- written = osmo_sockaddr_to_str_and_uint(buf, buf_len, &port, &sockaddr->u.sa);
- if (written + 1 >= buf_len && port)
- return NULL;
+ OSMO_STRBUF_APPEND(sb, osmo_sockaddr_to_str_and_uint, &port, &sockaddr->u.sa);
if (port)
- snprintf(buf + written, buf_len - written, ":%u", port);
+ OSMO_STRBUF_PRINTF(sb, ":%u", port);
break;
case AF_INET6:
- buf[0] = '[';
- written = osmo_sockaddr_to_str_and_uint(buf + 1, buf_len - 1, &port, &sockaddr->u.sa);
- if (written + 2 >= buf_len)
- return NULL;
-
- if (written + 3 >= buf_len && port)
- return NULL;
-
+ OSMO_STRBUF_PRINTF(sb, "[");
+ OSMO_STRBUF_APPEND(sb, osmo_sockaddr_to_str_and_uint, &port, &sockaddr->u.sa);
+ OSMO_STRBUF_PRINTF(sb, "]");
if (port)
- snprintf(buf + 1 + written, buf_len - written - 1, "]:%u", port);
- else {
- buf[written + 1] = ']';
- buf[written + 2] = 0;
- }
+ OSMO_STRBUF_PRINTF(sb, ":%u", port);
break;
default:
- snprintf(buf, buf_len, "unsupported family %d", sockaddr->u.sa.sa_family);
- return buf;
+ OSMO_STRBUF_PRINTF(sb, "unsupported family %d", sockaddr->u.sa.sa_family);
+ break;
}
+ return sb.chars_needed;
+}
+
+/*! string-format a given osmo_sockaddr address into a talloc allocated buffer.
+ * Like osmo_sockaddr_to_str_buf2() but returns a talloc allocated string.
+ * \param[in] ctx talloc context to allocate from, e.g. OTC_SELECT.
+ * \param[in] sockaddr the osmo_sockaddr to print.
+ * \return human readable string.
+ */
+char *osmo_sockaddr_to_str_c(void *ctx, const struct osmo_sockaddr *sockaddr)
+{
+ OSMO_NAME_C_IMPL(ctx, 64, "ERROR", osmo_sockaddr_to_str_buf2, sockaddr)
+}
+
+/*! string-format a given osmo_sockaddr address into a user-supplied buffer.
+ * Like osmo_sockaddr_to_str_buf2() but returns buf, or NULL if too short.
+ * \param[in] buf user-supplied output buffer
+ * \param[in] buf_len size of the user-supplied output buffer in bytes
+ * \param[in] sockaddr the osmo_sockaddr to print
+ * \return pointer to the string on success; NULL on error
+ */
+char *osmo_sockaddr_to_str_buf(char *buf, size_t buf_len,
+ const struct osmo_sockaddr *sockaddr)
+{
+ int chars_needed = osmo_sockaddr_to_str_buf2(buf, buf_len, sockaddr);
+ if (chars_needed >= buf_len)
+ return NULL;
return buf;
}
+/*! Set the DSCP (differentiated services code point) of a socket.
+ * \param[in] dscp DSCP value in range 0..63
+ * \returns 0 on success; negative on error. */
+int osmo_sock_set_dscp(int fd, uint8_t dscp)
+{
+ struct sockaddr_storage local_addr;
+ socklen_t local_addr_len = sizeof(local_addr);
+ uint8_t tos;
+ socklen_t tos_len = sizeof(tos);
+ int tclass;
+ socklen_t tclass_len = sizeof(tclass);
+ int rc;
+
+ /* DSCP is a 6-bit value stored in the upper 6 bits of the 8-bit TOS */
+ if (dscp > 63)
+ return -EINVAL;
+
+ rc = getsockname(fd, (struct sockaddr *)&local_addr, &local_addr_len);
+ if (rc < 0)
+ return rc;
+
+ switch (local_addr.ss_family) {
+ case AF_INET:
+ /* read the original value */
+ rc = getsockopt(fd, IPPROTO_IP, IP_TOS, &tos, &tos_len);
+ if (rc < 0)
+ return rc;
+ /* mask-in the DSCP into the upper 6 bits */
+ tos &= 0x03;
+ tos |= dscp << 2;
+ /* and write it back to the kernel */
+ rc = setsockopt(fd, IPPROTO_IP, IP_TOS, &tos, sizeof(tos));
+ break;
+ case AF_INET6:
+ /* read the original value */
+ rc = getsockopt(fd, IPPROTO_IPV6, IPV6_TCLASS, &tclass, &tclass_len);
+ if (rc < 0)
+ return rc;
+ /* mask-in the DSCP into the upper 6 bits */
+ tclass &= 0x03;
+ tclass |= dscp << 2;
+ /* and write it back to the kernel */
+ rc = setsockopt(fd, IPPROTO_IPV6, IPV6_TCLASS, &tclass, sizeof(tclass));
+ break;
+ case AF_UNSPEC:
+ default:
+ LOGP(DLGLOBAL, LOGL_ERROR, "No DSCP support for socket family %u\n",
+ local_addr.ss_family);
+ rc = -1;
+ break;
+ }
+
+ return rc;
+}
+
+/*! Set the priority value of a socket.
+ * \param[in] prio priority value. Values outside 0..6 require CAP_NET_ADMIN.
+ * \returns 0 on success; negative on error. */
+int osmo_sock_set_priority(int fd, int prio)
+{
+ /* and write it back to the kernel */
+ return setsockopt(fd, SOL_SOCKET, SO_PRIORITY, &prio, sizeof(prio));
+}
+
+#ifdef HAVE_LIBSCTP
+/*! Fill in array of struct sctp_paddrinfo with each of the remote addresses of an SCTP socket
+ * \param[in] fd file descriptor of SCTP socket
+ * \param[out] pinfo Pointer to memory holding an array of struct sctp_paddrinfo (pinfo_cnt length).
+ * \param[in,out] pinfo_cnt length of pinfo array (in elements). On return it contains the number of addresses found.
+ * \returns 0 on success; negative otherwise
+ *
+ * Upon return, pinfo_cnt can be set to a higher value than the one set by the
+ * caller. This can be used by the caller to find out the required array length
+ * and then obtaining by calling the function twice. Only up to pinfo_cnt addresses
+ * are filled in, as per the value provided by the caller.
+ *
+ * Usage example retrieving struct sctp_paddrinfo for all (up to OSMO_SOCK_MAX_ADDRS, 32) remote IP addresses:
+ * struct sctp_paddrinfo pinfo[OSMO_SOCK_MAX_ADDRS];
+ * size_t pinfo_cnt = ARRAY_SIZE(pinfo);
+ * rc = osmo_sock_sctp_get_peer_addr_info(fd, &pinfo[0], &num_hostbuf, pinfo_cnt);
+ * if (rc < 0)
+ * goto error;
+ * if (pinfo_cnt > ARRAY_SIZE(hostbuf))
+ * goto not_enough_buffers;
+ */
+int osmo_sock_sctp_get_peer_addr_info(int fd, struct sctp_paddrinfo *pinfo, size_t *pinfo_cnt)
+{
+ struct sockaddr *addrs = NULL;
+ unsigned int n_addrs, i;
+ void *addr_buf;
+ int rc;
+ socklen_t optlen;
+
+ rc = sctp_getpaddrs(fd, 0, &addrs);
+
+ if (rc < 0)
+ return rc;
+ if (rc == 0)
+ return -ENOTCONN;
+
+ n_addrs = rc;
+ addr_buf = (void *)addrs;
+ for (i = 0; i < n_addrs; i++) {
+ struct sockaddr *sa_addr = (struct sockaddr *)addr_buf;
+ size_t addrlen;
+
+ switch (sa_addr->sa_family) {
+ case AF_INET:
+ addrlen = sizeof(struct sockaddr_in);
+ break;
+ case AF_INET6:
+ addrlen = sizeof(struct sockaddr_in6);
+ break;
+ default:
+ rc = -EINVAL;
+ goto free_addrs_ret;
+ }
+
+ if (i >= *pinfo_cnt) {
+ addr_buf += addrlen;
+ continue;
+ }
+
+ memset(&pinfo[i], 0, sizeof(pinfo[0]));
+ memcpy(&pinfo[i].spinfo_address, sa_addr, addrlen);
+ optlen = sizeof(pinfo[0]);
+ rc = getsockopt(fd, SOL_SCTP, SCTP_GET_PEER_ADDR_INFO, &pinfo[i], &optlen);
+ if (rc < 0)
+ goto free_addrs_ret;
+
+ addr_buf += addrlen;
+ }
+
+ *pinfo_cnt = n_addrs;
+ rc = 0;
+free_addrs_ret:
+ sctp_freepaddrs(addrs);
+ return rc;
+}
+#endif
#endif /* HAVE_SYS_SOCKET_H */
diff --git a/src/core/soft_uart.c b/src/core/soft_uart.c
new file mode 100644
index 00000000..f969ab70
--- /dev/null
+++ b/src/core/soft_uart.c
@@ -0,0 +1,518 @@
+/*! \file soft_uart.c
+ * Software UART implementation. */
+/*
+ * (C) 2022 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <errno.h>
+
+#include <osmocom/core/utils.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/core/soft_uart.h>
+
+/*! Rx/Tx flow state of a soft-UART */
+enum suart_flow_state {
+ SUART_FLOW_ST_IDLE, /*!< waiting for a start bit or Tx data */
+ SUART_FLOW_ST_DATA, /*!< receiving/transmitting data bits */
+ SUART_FLOW_ST_PARITY, /*!< receiving/transmitting parity bits */
+ SUART_FLOW_ST_STOP, /*!< receiving/transmitting stop bits */
+};
+
+/*! Internal state of a soft-UART */
+struct osmo_soft_uart {
+ struct osmo_soft_uart_cfg cfg;
+ const char *name;
+ /* modem status (bitmask of OSMO_SUART_STATUS_F_*) */
+ unsigned int status;
+ struct {
+ bool running;
+ uint8_t bit_count;
+ uint8_t shift_reg;
+ struct msgb *msg;
+ ubit_t parity_bit; /* 0 (even) / 1 (odd) */
+ unsigned int flags;
+ struct osmo_timer_list timer;
+ enum suart_flow_state flow_state;
+ } rx;
+ struct {
+ bool running;
+ uint8_t bit_count;
+ uint8_t shift_reg;
+ ubit_t parity_bit; /* 0 (even) / 1 (odd) */
+ enum suart_flow_state flow_state;
+ } tx;
+};
+
+/*! Default soft-UART configuration (8-N-1) */
+const struct osmo_soft_uart_cfg osmo_soft_uart_default_cfg = {
+ .num_data_bits = 8,
+ .num_stop_bits = 1,
+ .parity_mode = OSMO_SUART_PARITY_NONE,
+ .rx_buf_size = 1024,
+ .rx_timeout_ms = 100,
+ .flow_ctrl_mode = OSMO_SUART_FLOW_CTRL_NONE,
+};
+
+/*************************************************************************
+ * Receiver
+ *************************************************************************/
+
+/*! Flush the receive buffer, passing ownership of the msgb to the .rx_cb().
+ * \param[in] suart soft-UART instance holding the receive buffer. */
+void osmo_soft_uart_flush_rx(struct osmo_soft_uart *suart)
+{
+ if (suart->rx.msg && msgb_length(suart->rx.msg)) {
+ osmo_timer_del(&suart->rx.timer);
+ if (suart->cfg.rx_cb) {
+ suart->cfg.rx_cb(suart->cfg.priv, suart->rx.msg, suart->rx.flags);
+ /* call-back has taken ownership of msgb, no need to free() here */
+ suart->rx.msg = msgb_alloc_c(suart, suart->cfg.rx_buf_size, "soft_uart_rx");
+ } else {
+ msgb_reset(suart->rx.msg);
+ }
+ }
+}
+
+/* one character was received; add to receive buffer and notify user, if needed */
+static void suart_rx_ch(struct osmo_soft_uart *suart, uint8_t ch)
+{
+ unsigned int msg_len;
+
+ OSMO_ASSERT(suart->rx.msg);
+ msgb_put_u8(suart->rx.msg, ch);
+ msg_len = msgb_length(suart->rx.msg);
+
+ if (msg_len >= suart->cfg.rx_buf_size || suart->rx.flags) {
+ /* either the buffer is full, or we hit a parity and/or a framing error */
+ osmo_soft_uart_flush_rx(suart);
+ } else if (msg_len == 1) {
+ /* first character in new message: start timer */
+ osmo_timer_schedule(&suart->rx.timer, suart->cfg.rx_timeout_ms / 1000,
+ (suart->cfg.rx_timeout_ms % 1000) * 1000);
+ }
+}
+
+/* receive a single bit */
+static inline void suart_rx_bit(struct osmo_soft_uart *suart, const ubit_t bit)
+{
+ switch (suart->rx.flow_state) {
+ case SUART_FLOW_ST_IDLE:
+ if (bit == 0) { /* start bit condition */
+ suart->rx.flow_state = SUART_FLOW_ST_DATA;
+ suart->rx.flags = 0x00;
+ suart->rx.shift_reg = 0;
+ suart->rx.bit_count = 0;
+ suart->rx.parity_bit = 0;
+ }
+ break;
+ case SUART_FLOW_ST_DATA:
+ suart->rx.bit_count++;
+ suart->rx.shift_reg >>= 1;
+ if (bit != 0) {
+ suart->rx.parity_bit = !suart->rx.parity_bit; /* flip */
+ suart->rx.shift_reg |= 0x80;
+ }
+ if (suart->rx.bit_count >= suart->cfg.num_data_bits) {
+ /* we have accumulated enough data bits */
+ if (suart->cfg.parity_mode != OSMO_SUART_PARITY_NONE)
+ suart->rx.flow_state = SUART_FLOW_ST_PARITY;
+ else
+ suart->rx.flow_state = SUART_FLOW_ST_STOP;
+ /* align the register if needed */
+ if (suart->cfg.num_data_bits < 8)
+ suart->rx.shift_reg >>= (8 - suart->cfg.num_data_bits);
+ }
+ break;
+ case SUART_FLOW_ST_PARITY:
+ switch (suart->cfg.parity_mode) {
+ case OSMO_SUART_PARITY_EVEN:
+ /* number of 1-bits (in both data and parity) shall be even */
+ if (suart->rx.parity_bit != bit)
+ suart->rx.flags |= OSMO_SUART_F_PARITY_ERROR;
+ break;
+ case OSMO_SUART_PARITY_ODD:
+ /* number of 1-bits (in both data and parity) shall be odd */
+ if (suart->rx.parity_bit == bit)
+ suart->rx.flags |= OSMO_SUART_F_PARITY_ERROR;
+ break;
+ case OSMO_SUART_PARITY_MARK:
+ /* parity bit must always be 1 */
+ if (bit != 1)
+ suart->rx.flags |= OSMO_SUART_F_PARITY_ERROR;
+ break;
+ case OSMO_SUART_PARITY_SPACE:
+ /* parity bit must always be 0 */
+ if (bit != 0)
+ suart->rx.flags |= OSMO_SUART_F_PARITY_ERROR;
+ break;
+ case OSMO_SUART_PARITY_NONE: /* shall not happen */
+ default:
+ OSMO_ASSERT(0);
+ }
+
+ suart->rx.flow_state = SUART_FLOW_ST_STOP;
+ break;
+ case SUART_FLOW_ST_STOP:
+ suart->rx.bit_count++;
+ if (bit != 1)
+ suart->rx.flags |= OSMO_SUART_F_FRAMING_ERROR;
+
+ if (suart->rx.bit_count >= (suart->cfg.num_data_bits + suart->cfg.num_stop_bits)) {
+ /* we have accumulated enough stop bits */
+ suart_rx_ch(suart, suart->rx.shift_reg);
+ suart->rx.flow_state = SUART_FLOW_ST_IDLE;
+ }
+ break;
+ }
+}
+
+/* receive timer expiration: flush rx-buffer to user call-back */
+static void suart_rx_timer_cb(void *data)
+{
+ struct osmo_soft_uart *suart = data;
+ osmo_soft_uart_flush_rx(suart);
+}
+
+/*! Feed a number of unpacked bits into the soft-UART receiver.
+ * \param[in] suart soft-UART instance to feed bits into.
+ * \param[in] ubits pointer to the unpacked bits.
+ * \param[in] n_ubits number of unpacked bits to be fed.
+ * \returns 0 on success; negative on error.
+ * -EAGAIN indicates that the receiver is disabled. */
+int osmo_soft_uart_rx_ubits(struct osmo_soft_uart *suart, const ubit_t *ubits, size_t n_ubits)
+{
+ if (!suart->rx.running)
+ return -EAGAIN;
+ for (size_t i = 0; i < n_ubits; i++)
+ suart_rx_bit(suart, ubits[i]);
+ return 0;
+}
+
+/*************************************************************************
+ * Transmitter
+ *************************************************************************/
+
+/* pull a single bit out of the UART transmitter */
+static inline ubit_t suart_tx_bit(struct osmo_soft_uart *suart, struct msgb *msg)
+{
+ ubit_t tx_bit = 1;
+
+ switch (suart->tx.flow_state) {
+ case SUART_FLOW_ST_IDLE:
+ if (msg && msgb_length(msg) > 0) { /* if we have pending data */
+ suart->tx.shift_reg = msgb_pull_u8(msg);
+ suart->tx.flow_state = SUART_FLOW_ST_DATA;
+ suart->tx.bit_count = 0;
+ suart->tx.parity_bit = 0;
+ tx_bit = 0;
+ }
+ break;
+ case SUART_FLOW_ST_DATA:
+ tx_bit = suart->tx.shift_reg & 1;
+ suart->tx.parity_bit ^= tx_bit;
+ suart->tx.shift_reg >>= 1;
+ suart->tx.bit_count++;
+ if (suart->tx.bit_count >= suart->cfg.num_data_bits) {
+ /* we have transmitted all data bits */
+ if (suart->cfg.parity_mode != OSMO_SUART_PARITY_NONE)
+ suart->tx.flow_state = SUART_FLOW_ST_PARITY;
+ else
+ suart->tx.flow_state = SUART_FLOW_ST_STOP;
+ }
+ break;
+ case SUART_FLOW_ST_PARITY:
+ switch (suart->cfg.parity_mode) {
+ case OSMO_SUART_PARITY_EVEN:
+ /* number of 1-bits (in both data and parity) shall be even */
+ tx_bit = suart->tx.parity_bit;
+ break;
+ case OSMO_SUART_PARITY_ODD:
+ /* number of 1-bits (in both data and parity) shall be odd */
+ tx_bit = !suart->tx.parity_bit;
+ break;
+ case OSMO_SUART_PARITY_MARK:
+ /* parity bit must always be 1 */
+ tx_bit = 1;
+ break;
+ case OSMO_SUART_PARITY_SPACE:
+ /* parity bit must always be 0 */
+ tx_bit = 0;
+ break;
+ case OSMO_SUART_PARITY_NONE:
+ default: /* shall not happen */
+ OSMO_ASSERT(0);
+ }
+
+ suart->tx.flow_state = SUART_FLOW_ST_STOP;
+ break;
+ case SUART_FLOW_ST_STOP:
+ suart->tx.bit_count++;
+ if (suart->tx.bit_count >= (suart->cfg.num_data_bits + suart->cfg.num_stop_bits)) {
+ /* we have transmitted all stop bits, we're done */
+ suart->tx.flow_state = SUART_FLOW_ST_IDLE;
+ }
+ break;
+ }
+
+ return tx_bit;
+}
+
+/* pull pending bits out of the UART */
+static size_t suart_tx_pending(struct osmo_soft_uart *suart, ubit_t *ubits, size_t n_ubits)
+{
+ size_t i;
+
+ for (i = 0; i < n_ubits; i++) {
+ if (suart->tx.flow_state == SUART_FLOW_ST_IDLE)
+ break;
+ ubits[i] = suart_tx_bit(suart, NULL);
+ }
+
+ return i;
+}
+
+/*! Pull a number of unpacked bits out of the soft-UART transmitter.
+ * \param[in] suart soft-UART instance to pull the bits from.
+ * \param[out] ubits pointer to a buffer where to store pulled bits.
+ * \param[in] n_ubits number of unpacked bits to be pulled.
+ * \returns number of bits pulled (may be less than n_ubits); negative on error.
+ * -EAGAIN indicates that the transmitter is disabled. */
+int osmo_soft_uart_tx_ubits(struct osmo_soft_uart *suart, ubit_t *ubits, size_t n_ubits)
+{
+ const struct osmo_soft_uart_cfg *cfg = &suart->cfg;
+ size_t n_frame_bits, n_chars;
+ struct msgb *msg = NULL;
+
+ if (OSMO_UNLIKELY(n_ubits == 0))
+ return -EINVAL;
+
+ if (!suart->tx.running)
+ return -EAGAIN;
+
+ switch (suart->cfg.flow_ctrl_mode) {
+ case OSMO_SUART_FLOW_CTRL_DTR_DSR:
+ /* if DSR is de-asserted, Tx pending bits and suspend */
+ if (~suart->status & OSMO_SUART_STATUS_F_DSR)
+ return suart_tx_pending(suart, ubits, n_ubits);
+ /* else: keep transmitting as usual */
+ break;
+ case OSMO_SUART_FLOW_CTRL_RTS_CTS:
+ /* if CTS is de-asserted, Tx pending bits and suspend */
+ if (~suart->status & OSMO_SUART_STATUS_F_CTS)
+ return suart_tx_pending(suart, ubits, n_ubits);
+ /* else: keep transmitting as usual */
+ break;
+ case OSMO_SUART_FLOW_CTRL_NONE:
+ default:
+ break;
+ }
+
+ /* calculate UART frame size for the effective config */
+ n_frame_bits = 1 + cfg->num_data_bits + cfg->num_stop_bits;
+ if (cfg->parity_mode != OSMO_SUART_PARITY_NONE)
+ n_frame_bits += 1;
+
+ /* calculate the number of characters we can fit into n_ubits */
+ n_chars = n_ubits / n_frame_bits;
+ if (n_chars == 0) {
+ /* we can transmit at least one character */
+ if (suart->tx.flow_state == SUART_FLOW_ST_IDLE)
+ n_chars = 1;
+ }
+
+ if (n_chars > 0) {
+ /* allocate a Tx buffer msgb */
+ msg = msgb_alloc_c(suart, n_chars, "soft_uart_tx");
+ OSMO_ASSERT(msg != NULL);
+
+ /* call the .tx_cb() to populate the Tx buffer */
+ OSMO_ASSERT(cfg->tx_cb != NULL);
+ suart->cfg.tx_cb(cfg->priv, msg);
+ }
+
+ for (size_t i = 0; i < n_ubits; i++)
+ ubits[i] = suart_tx_bit(suart, msg);
+ msgb_free(msg);
+
+ return n_ubits;
+}
+
+/*! Get the modem status bitmask of the given soft-UART.
+ * \param[in] suart soft-UART instance to get the modem status.
+ * \returns bitmask of OSMO_SUART_STATUS_F_*. */
+unsigned int osmo_soft_uart_get_status(const struct osmo_soft_uart *suart)
+{
+ return suart->status;
+}
+
+/*! Set the modem status bitmask of the given soft-UART.
+ * \param[in] suart soft-UART instance to set the modem status.
+ * \param[in] status bitmask of OSMO_SUART_STATUS_F_*.
+ * \returns 0 on success; negative on error. */
+int osmo_soft_uart_set_status(struct osmo_soft_uart *suart, unsigned int status)
+{
+ const struct osmo_soft_uart_cfg *cfg = &suart->cfg;
+
+ if (cfg->status_change_cb != NULL) {
+ if (suart->status != status)
+ cfg->status_change_cb(cfg->priv, status);
+ }
+
+ suart->status = status;
+ return 0;
+}
+
+/*! Activate/deactivate a modem status line of the given soft-UART.
+ * \param[in] suart soft-UART instance to update the modem status.
+ * \param[in] line a modem status line, one of OSMO_SUART_STATUS_F_*.
+ * \param[in] active activate (true) or deactivate (false) the line. */
+void osmo_soft_uart_set_status_line(struct osmo_soft_uart *suart,
+ enum osmo_soft_uart_status line,
+ bool active)
+{
+ unsigned int status = suart->status;
+
+ if (active) /* assert the given line */
+ status |= line;
+ else /* de-assert the given line */
+ status &= ~line;
+
+ osmo_soft_uart_set_status(suart, status);
+}
+
+
+/*************************************************************************
+ * Management / Initialization
+ *************************************************************************/
+
+/*! Allocate a soft-UART instance.
+ * \param[in] ctx parent talloc context.
+ * \param[in] name name of the soft-UART instance.
+ * \param[in] cfg initial configuration of the soft-UART instance.
+ * \returns pointer to allocated soft-UART instance; NULL on error. */
+struct osmo_soft_uart *osmo_soft_uart_alloc(void *ctx, const char *name,
+ const struct osmo_soft_uart_cfg *cfg)
+{
+ struct osmo_soft_uart *suart = talloc_zero(ctx, struct osmo_soft_uart);
+ if (!suart)
+ return NULL;
+ suart->name = talloc_strdup(suart, name);
+
+ OSMO_ASSERT(cfg != NULL);
+ suart->cfg = *cfg;
+
+ return suart;
+}
+
+/*! Release memory taken by the given soft-UART.
+ * \param[in] suart soft-UART instance to be free()d. */
+void osmo_soft_uart_free(struct osmo_soft_uart *suart)
+{
+ if (suart == NULL)
+ return;
+
+ osmo_timer_del(&suart->rx.timer);
+ msgb_free(suart->rx.msg);
+
+ talloc_free((void *)suart->name);
+ talloc_free(suart);
+}
+
+/*! Change soft-UART configuration to the user-provided config.
+ * \param[in] suart soft-UART instance to be re-configured.
+ * \param[in] cfg the user-provided config to be applied.
+ * \returns 0 on success; negative on error. */
+int osmo_soft_uart_configure(struct osmo_soft_uart *suart, const struct osmo_soft_uart_cfg *cfg)
+{
+ /* consistency checks on the configuration */
+ if (cfg->num_data_bits > 8 || cfg->num_data_bits == 0)
+ return -EINVAL;
+ if (cfg->num_stop_bits == 0)
+ return -EINVAL;
+ if (cfg->parity_mode < 0 || cfg->parity_mode >= _OSMO_SUART_PARITY_NUM)
+ return -EINVAL;
+ if (cfg->rx_buf_size == 0)
+ return -EINVAL;
+
+ if (suart->cfg.rx_buf_size > cfg->rx_buf_size ||
+ suart->cfg.rx_timeout_ms > cfg->rx_timeout_ms) {
+ osmo_soft_uart_flush_rx(suart);
+ }
+
+ suart->cfg = *cfg;
+
+ osmo_timer_setup(&suart->rx.timer, suart_rx_timer_cb, suart);
+
+ return 0;
+}
+
+/*! Get a name for the given soft-UART instance.
+ * \param[in] suart soft-UART instance to get the name from.
+ * \returns name of the given soft-UART instance. */
+const char *osmo_soft_uart_get_name(const struct osmo_soft_uart *suart)
+{
+ return suart->name;
+}
+
+/*! Set a new name for the given soft-UART instance.
+ * \param[in] suart soft-UART instance to set the name for.
+ * \param[in] name the new name. */
+void osmo_soft_uart_set_name(struct osmo_soft_uart *suart, const char *name)
+{
+ osmo_talloc_replace_string(suart, (char **)&suart->name, name);
+}
+
+/*! Enable/disable receiver of the given soft-UART.
+ * \param[in] suart soft-UART instance to be re-configured.
+ * \param[in] enable enable/disable state of the receiver.
+ * \returns 0 on success; negative on error. */
+int osmo_soft_uart_set_rx(struct osmo_soft_uart *suart, bool enable)
+{
+ if (!enable && suart->rx.running) {
+ osmo_soft_uart_flush_rx(suart);
+ suart->rx.running = false;
+ suart->rx.flow_state = SUART_FLOW_ST_IDLE;
+ } else if (enable && !suart->rx.running) {
+ if (!suart->rx.msg)
+ suart->rx.msg = msgb_alloc_c(suart, suart->cfg.rx_buf_size, "soft_uart_rx");
+ suart->rx.running = true;
+ suart->rx.flow_state = SUART_FLOW_ST_IDLE;
+ }
+
+ return 0;
+}
+
+/*! Enable/disable transmitter of the given soft-UART.
+ * \param[in] suart soft-UART instance to be re-configured.
+ * \param[in] enable enable/disable state of the transmitter.
+ * \returns 0 on success; negative on error. */
+int osmo_soft_uart_set_tx(struct osmo_soft_uart *suart, bool enable)
+{
+ if (!enable && suart->tx.running) {
+ suart->tx.running = false;
+ suart->tx.flow_state = SUART_FLOW_ST_IDLE;
+ } else if (enable && !suart->tx.running) {
+ suart->tx.running = true;
+ suart->tx.flow_state = SUART_FLOW_ST_IDLE;
+ }
+
+ return 0;
+}
diff --git a/src/core/stat_item.c b/src/core/stat_item.c
new file mode 100644
index 00000000..804972bf
--- /dev/null
+++ b/src/core/stat_item.c
@@ -0,0 +1,463 @@
+/*! \file stat_item.c
+ * utility routines for keeping statistical values */
+/*
+ * (C) 2009-2010 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2015 by sysmocom - s.f.m.c. GmbH
+ *
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+/*! \addtogroup osmo_stat_item
+ * @{
+ *
+ * This osmo_stat_item module adds instrumentation capabilities to
+ * gather measurement and statistical values in a similar fashion to
+ * what we have as \ref osmo_counter_group.
+ *
+ * As opposed to counters, osmo_stat_item do not increment but consist
+ * of a configurable-sized FIFO, which can store not only the current
+ * (most recent) value, but also historic values.
+ *
+ * The only supported value type is an int32_t.
+ *
+ * Getting values from osmo_stat_item is usually done at a high level
+ * through the stats API (stats.c). It uses item->stats_next_id to
+ * store what has been sent to all enabled reporters. It is also
+ * possible to read from osmo_stat_item directly, without modifying
+ * its state, by storing next_id outside of osmo_stat_item.
+ *
+ * Each value stored in the FIFO of an osmo_stat_item has an associated
+ * value_id. The value_id is increased with each value, so (until the
+ * counter wraps) more recent values will have higher values.
+ *
+ * When a new value is set, the oldest value in the FIFO gets silently
+ * overwritten. Lost values are skipped when getting values from the
+ * item.
+ *
+ */
+
+/* Struct overview:
+ *
+ * Group and item descriptions:
+ * Each group description exists once as osmo_stat_item_group_desc,
+ * each such group description lists N osmo_stat_item_desc, i.e. describes N stat items.
+ *
+ * Actual stats:
+ * The global osmo_stat_item_groups llist contains all group instances, each points at a group description.
+ * This list mixes all types of groups in a single llist, where each instance points at its group desc and has an index.
+ * There are one or more instances of each group, each storing stats for a distinct object (for example, one description
+ * for a BTS group, and any number of BTS instances with independent stats). A group is identified by a group index nr
+ * and possibly also a given name for that particular index (e.g. in osmo-mgw, a group instance is named
+ * "virtual-trunk-0" and can be looked up by that name instead of its more or less arbitrary group index number).
+ *
+ * Each group instance contains one osmo_stat_item instance per global stat item description.
+ * Each osmo_stat_item keeps track of the values for the current reporting period (min, last, max, sum, n),
+ * and also stores the set of values reported at the end of the previous reporting period.
+ *
+ * const osmo_stat_item_group_desc foo
+ * +-- group_name_prefix = "foo"
+ * +-- item_desc[] (array of osmo_stat_item_desc)
+ * +-- osmo_stat_item_desc bar
+ * | +-- name = "bar"
+ * | +-- description
+ * | +-- unit
+ * | +-- default_value
+ * |
+ * +-- osmo_stat_item_desc: baz
+ * +-- ...
+ *
+ * const osmo_stat_item_group_desc moo
+ * +-- group_name_prefix = "moo"
+ * +-- item_desc[]
+ * +-- osmo_stat_item_desc goo
+ * | +-- name = "goo"
+ * | +-- description
+ * | +-- unit
+ * | +-- default_value
+ * |
+ * +-- osmo_stat_item_desc: loo
+ * +-- ...
+ *
+ * osmo_stat_item_groups (llist of osmo_stat_item_group)
+ * |
+ * +-- group: foo[0]
+ * | +-- desc --> osmo_stat_item_group_desc foo
+ * | +-- idx = 0
+ * | +-- name = NULL (no given name for this group instance)
+ * | +-- items[]
+ * | |
+ * | [0] --> osmo_stat_item instance for "bar"
+ * | | +-- desc --> osmo_stat_item_desc bar (see above)
+ * | | +-- value.{min, last, max, n, sum}
+ * | | +-- reported.{min, last, max, n, sum}
+ * | |
+ * | [1] --> osmo_stat_item instance for "baz"
+ * | | +-- desc --> osmo_stat_item_desc baz
+ * | | +-- value.{min, last, max, n, sum}
+ * | | +-- reported.{min, last, max, n, sum}
+ * | .
+ * | :
+ * |
+ * +-- group: foo[1]
+ * | +-- desc --> osmo_stat_item_group_desc foo
+ * | +-- idx = 1
+ * | +-- name = "special-foo" (instance can be looked up by this index-name)
+ * | +-- items[]
+ * | |
+ * | [0] --> osmo_stat_item instance for "bar"
+ * | | +-- desc --> osmo_stat_item_desc bar
+ * | | +-- value.{min, last, max, n, sum}
+ * | | +-- reported.{min, last, max, n, sum}
+ * | |
+ * | [1] --> osmo_stat_item instance for "baz"
+ * | | +-- desc --> osmo_stat_item_desc baz
+ * | | +-- value.{min, last, max, n, sum}
+ * | | +-- reported.{min, last, max, n, sum}
+ * | .
+ * | :
+ * |
+ * +-- group: moo[0]
+ * | +-- desc --> osmo_stat_item_group_desc moo
+ * | +-- idx = 0
+ * | +-- name = NULL
+ * | +-- items[]
+ * | |
+ * | [0] --> osmo_stat_item instance for "goo"
+ * | | +-- desc --> osmo_stat_item_desc goo
+ * | | +-- value.{min, last, max, n, sum}
+ * | | +-- reported.{min, last, max, n, sum}
+ * | |
+ * | [1] --> osmo_stat_item instance for "loo"
+ * | | +-- desc --> osmo_stat_item_desc loo
+ * | | +-- value.{min, last, max, n, sum}
+ * | | +-- reported.{min, last, max, n, sum}
+ * | .
+ * | :
+ * .
+ * :
+ *
+ */
+
+#include <stdint.h>
+#include <string.h>
+
+#include <osmocom/core/utils.h>
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/core/stat_item.h>
+
+#include <stat_item_internal.h>
+
+/*! global list of stat_item groups */
+static LLIST_HEAD(osmo_stat_item_groups);
+
+/*! talloc context from which we allocate */
+static void *tall_stat_item_ctx;
+
+/*! Allocate a new group of counters according to description.
+ * Allocate a group of stat items described in \a desc from talloc context \a ctx,
+ * giving the new group the index \a idx.
+ * \param[in] ctx \ref talloc context
+ * \param[in] desc Statistics item group description
+ * \param[in] idx Index of new stat item group
+ */
+struct osmo_stat_item_group *osmo_stat_item_group_alloc(void *ctx,
+ const struct osmo_stat_item_group_desc *group_desc,
+ unsigned int idx)
+{
+ unsigned int group_size;
+ unsigned int item_idx;
+ struct osmo_stat_item *items;
+
+ struct osmo_stat_item_group *group;
+
+ group_size = sizeof(struct osmo_stat_item_group) +
+ group_desc->num_items * sizeof(struct osmo_stat_item *);
+
+ if (!ctx)
+ ctx = tall_stat_item_ctx;
+
+ group = talloc_zero_size(ctx, group_size);
+ if (!group)
+ return NULL;
+
+ group->desc = group_desc;
+ group->idx = idx;
+
+ items = talloc_array(group, struct osmo_stat_item, group_desc->num_items);
+ OSMO_ASSERT(items);
+ for (item_idx = 0; item_idx < group_desc->num_items; item_idx++) {
+ struct osmo_stat_item *item = &items[item_idx];
+ const struct osmo_stat_item_desc *item_desc = &group_desc->item_desc[item_idx];
+ group->items[item_idx] = item;
+ *item = (struct osmo_stat_item){
+ .desc = item_desc,
+ .value = {
+ .n = 0,
+ .last = item_desc->default_value,
+ .min = item_desc->default_value,
+ .max = item_desc->default_value,
+ .sum = 0,
+ },
+ };
+ }
+
+ llist_add(&group->list, &osmo_stat_item_groups);
+ return group;
+}
+
+/*! Free the memory for the specified group of stat items */
+void osmo_stat_item_group_free(struct osmo_stat_item_group *grp)
+{
+ if (!grp)
+ return;
+
+ llist_del(&grp->list);
+ talloc_free(grp);
+}
+
+/*! Get statistics item from group, identified by index idx
+ * \param[in] grp Rate counter group
+ * \param[in] idx Index of the counter to retrieve
+ * \returns rate counter requested
+ */
+struct osmo_stat_item *osmo_stat_item_group_get_item(struct osmo_stat_item_group *grp, unsigned int idx)
+{
+ return grp->items[idx];
+}
+
+/*! Set a name for the statistics item group to be used instead of index value
+ at report time.
+ * \param[in] statg Statistics item group
+ * \param[in] name Name identifier to assign to the statistics item group
+ */
+void osmo_stat_item_group_set_name(struct osmo_stat_item_group *statg, const char *name)
+{
+ osmo_talloc_replace_string(statg, &statg->name, name);
+}
+
+/*! Increase the stat_item to the given value.
+ * This function adds a new value for the given stat_item at the end of
+ * the FIFO.
+ * \param[in] item The stat_item whose \a value we want to set
+ * \param[in] value The numeric value we want to store at end of FIFO
+ */
+void osmo_stat_item_inc(struct osmo_stat_item *item, int32_t value)
+{
+ osmo_stat_item_set(item, item->value.last + value);
+}
+
+/*! Descrease the stat_item to the given value.
+ * This function adds a new value for the given stat_item at the end of
+ * the FIFO.
+ * \param[in] item The stat_item whose \a value we want to set
+ * \param[in] value The numeric value we want to store at end of FIFO
+ */
+void osmo_stat_item_dec(struct osmo_stat_item *item, int32_t value)
+{
+ osmo_stat_item_set(item, item->value.last - value);
+}
+
+/*! Set the a given stat_item to the given value.
+ * This function adds a new value for the given stat_item at the end of
+ * the FIFO.
+ * \param[in] item The stat_item whose \a value we want to set
+ * \param[in] value The numeric value we want to store at end of FIFO
+ */
+void osmo_stat_item_set(struct osmo_stat_item *item, int32_t value)
+{
+ item->value.last = value;
+ if (item->value.n == 0) {
+ /* No values recorded yet, clamp min and max to this first value. */
+ item->value.min = item->value.max = value;
+ /* Overwrite any cruft remaining in value.sum */
+ item->value.sum = value;
+ item->value.n = 1;
+ } else {
+ item->value.min = OSMO_MIN(item->value.min, value);
+ item->value.max = OSMO_MAX(item->value.max, value);
+ item->value.sum += value;
+ item->value.n++;
+ }
+}
+
+/*! Indicate that a reporting period has elapsed, and prepare the stat item for a new period of collecting min/max/avg.
+ * \param item Stat item to flush.
+ */
+void osmo_stat_item_flush(struct osmo_stat_item *item)
+{
+ item->reported = item->value;
+
+ /* Indicate a new reporting period: no values have been received, but the previous value.last remains unchanged
+ * for the case that an entire period elapses without a new value appearing. */
+ item->value.n = 0;
+ item->value.sum = 0;
+
+ /* Also for the case that an entire period elapses without any osmo_stat_item_set(), put the min and max to the
+ * last value. As soon as one osmo_stat_item_set() occurs, these are both set to the new value (when n is still
+ * zero from above). */
+ item->value.min = item->value.max = item->value.last;
+}
+
+/*! Initialize the stat item module. Call this once from your program.
+ * \param[in] tall_ctx Talloc context from which this module allocates */
+int osmo_stat_item_init(void *tall_ctx)
+{
+ tall_stat_item_ctx = tall_ctx;
+
+ return 0;
+}
+
+/*! Search for item group based on group name and index
+ * \param[in] name Name of stats_item_group we want to find
+ * \param[in] idx Index of the group we want to find
+ * \returns pointer to group, if found; NULL otherwise */
+struct osmo_stat_item_group *osmo_stat_item_get_group_by_name_idx(
+ const char *name, const unsigned int idx)
+{
+ struct osmo_stat_item_group *statg;
+
+ llist_for_each_entry(statg, &osmo_stat_item_groups, list) {
+ if (!statg->desc)
+ continue;
+
+ if (!strcmp(statg->desc->group_name_prefix, name) &&
+ statg->idx == idx)
+ return statg;
+ }
+ return NULL;
+}
+
+/*! Search for item group based on group name and index's name.
+ * \param[in] name Name of stats_item_group we want to find.
+ * \param[in] idx_name Index of the group we want to find, by the index's name (osmo_stat_item_group->name).
+ * \returns pointer to group, if found; NULL otherwise. */
+struct osmo_stat_item_group *osmo_stat_item_get_group_by_name_idxname(const char *group_name, const char *idx_name)
+{
+ struct osmo_stat_item_group *statg;
+
+ llist_for_each_entry(statg, &osmo_stat_item_groups, list) {
+ if (!statg->desc || !statg->name)
+ continue;
+ if (strcmp(statg->desc->group_name_prefix, group_name))
+ continue;
+ if (strcmp(statg->name, idx_name))
+ continue;
+ return statg;
+ }
+ return NULL;
+}
+
+/*! Search for item based on group + item name
+ * \param[in] statg group in which to search for the item
+ * \param[in] name name of item to search within \a statg
+ * \returns pointer to item, if found; NULL otherwise */
+const struct osmo_stat_item *osmo_stat_item_get_by_name(
+ const struct osmo_stat_item_group *statg, const char *name)
+{
+ int i;
+ const struct osmo_stat_item_desc *item_desc;
+
+ if (!statg->desc)
+ return NULL;
+
+ for (i = 0; i < statg->desc->num_items; i++) {
+ item_desc = &statg->desc->item_desc[i];
+
+ if (!strcmp(item_desc->name, name)) {
+ return statg->items[i];
+ }
+ }
+ return NULL;
+}
+
+/*! Iterate over all items in group, call user-supplied function on each
+ * \param[in] statg stat_item group over whose items to iterate
+ * \param[in] handle_item Call-back function, aborts if rc < 0
+ * \param[in] data Private data handed through to \a handle_item
+ */
+int osmo_stat_item_for_each_item(struct osmo_stat_item_group *statg,
+ osmo_stat_item_handler_t handle_item, void *data)
+{
+ int rc = 0;
+ int i;
+
+ for (i = 0; i < statg->desc->num_items; i++) {
+ struct osmo_stat_item *item = statg->items[i];
+ rc = handle_item(statg, item, data);
+ if (rc < 0)
+ return rc;
+ }
+
+ return rc;
+}
+
+/*! Iterate over all stat_item groups in system, call user-supplied function on each
+ * \param[in] handle_group Call-back function, aborts if rc < 0
+ * \param[in] data Private data handed through to \a handle_group
+ */
+int osmo_stat_item_for_each_group(osmo_stat_item_group_handler_t handle_group, void *data)
+{
+ struct osmo_stat_item_group *statg;
+ int rc = 0;
+
+ llist_for_each_entry(statg, &osmo_stat_item_groups, list) {
+ rc = handle_group(statg, data);
+ if (rc < 0)
+ return rc;
+ }
+
+ return rc;
+}
+
+/*! Get the last (freshest) value. */
+int32_t osmo_stat_item_get_last(const struct osmo_stat_item *item)
+{
+ return item->value.last;
+}
+
+/*! Remove all values of a stat item
+ * \param[in] item stat item to reset
+ */
+void osmo_stat_item_reset(struct osmo_stat_item *item)
+{
+ item->value.sum = 0;
+ item->value.n = 0;
+ item->value.last = item->value.min = item->value.max = item->desc->default_value;
+}
+
+/*! Reset all osmo stat items in a group
+ * \param[in] statg stat item group to reset
+ */
+void osmo_stat_item_group_reset(struct osmo_stat_item_group *statg)
+{
+ int i;
+
+ for (i = 0; i < statg->desc->num_items; i++) {
+ struct osmo_stat_item *item = statg->items[i];
+ osmo_stat_item_reset(item);
+ }
+}
+
+/*! Return the description for an osmo_stat_item. */
+const struct osmo_stat_item_desc *osmo_stat_item_get_desc(struct osmo_stat_item *item)
+{
+ return item->desc;
+}
+
+/*! @} */
diff --git a/src/core/stat_item_internal.h b/src/core/stat_item_internal.h
new file mode 100644
index 00000000..9ede8c41
--- /dev/null
+++ b/src/core/stat_item_internal.h
@@ -0,0 +1,35 @@
+/*! \file stat_item_internal.h
+ * internal definitions for the osmo_stat_item API */
+#pragma once
+
+/*! \addtogroup osmo_stat_item
+ * @{
+ */
+
+struct osmo_stat_item_period {
+ /*! Number of osmo_stat_item_set() that occurred during the reporting period, zero if none. */
+ uint32_t n;
+ /*! Smallest value seen in a reporting period. */
+ int32_t min;
+ /*! Most recent value passed to osmo_stat_item_set(), or the item->desc->default_value if none. */
+ int32_t last;
+ /*! Largest value seen in a reporting period. */
+ int32_t max;
+ /*! Sum of all values passed to osmo_stat_item_set() in the reporting period. */
+ int64_t sum;
+};
+
+/*! data we keep for each actual item */
+struct osmo_stat_item {
+ /*! back-reference to the item description */
+ const struct osmo_stat_item_desc *desc;
+
+ /*! Current reporting period / current value. */
+ struct osmo_stat_item_period value;
+
+ /*! The results of the previous reporting period. According to these, the stats reporter decides whether to
+ * re-send values or omit an unchanged value from a report. */
+ struct osmo_stat_item_period reported;
+};
+
+/*! @} */
diff --git a/src/stats.c b/src/core/stats.c
index c91a9780..16e6f620 100644
--- a/src/stats.c
+++ b/src/core/stats.c
@@ -16,10 +16,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
/*! \addtogroup stats
@@ -89,14 +85,27 @@
#include <osmocom/core/select.h>
#include <osmocom/core/counter.h>
#include <osmocom/core/msgb.h>
+#include <osmocom/core/stats_tcp.h>
+
+#ifdef HAVE_SYSTEMTAP
+/* include the generated probes header and put markers in code */
+#include "probes.h"
+#define TRACE(probe) probe
+#define TRACE_ENABLED(probe) probe ## _ENABLED()
+#else
+/* Wrap the probe to allow it to be removed when no systemtap available */
+#define TRACE(probe)
+#define TRACE_ENABLED(probe) (0)
+#endif /* HAVE_SYSTEMTAP */
+
+#include <stat_item_internal.h>
#define STATS_DEFAULT_INTERVAL 5 /* secs */
#define STATS_DEFAULT_BUFLEN 256
-static LLIST_HEAD(osmo_stats_reporter_list);
+LLIST_HEAD(osmo_stats_reporter_list);
static void *osmo_stats_ctx = NULL;
static int is_initialised = 0;
-static int32_t current_stat_item_index = 0;
static struct osmo_stats_config s_stats_config = {
.interval = STATS_DEFAULT_INTERVAL,
@@ -166,7 +175,7 @@ static int osmo_stats_timer_cb(struct osmo_fd *ofd, unsigned int what)
return 0;
}
-static int start_timer()
+static int start_timer(void)
{
int rc;
int interval = osmo_stats_config->interval;
@@ -205,13 +214,15 @@ struct osmo_stats_reporter *osmo_stats_reporter_alloc(enum osmo_stats_reporter_t
{
struct osmo_stats_reporter *srep;
srep = talloc_zero(osmo_stats_ctx, struct osmo_stats_reporter);
- OSMO_ASSERT(srep);
+ if (!srep)
+ return NULL;
+
srep->type = type;
if (name)
srep->name = talloc_strdup(srep, name);
srep->fd = -1;
- llist_add(&srep->list, &osmo_stats_reporter_list);
+ llist_add_tail(&srep->list, &osmo_stats_reporter_list);
return srep;
}
@@ -225,15 +236,17 @@ void osmo_stats_reporter_free(struct osmo_stats_reporter *srep)
talloc_free(srep);
}
-/*! Initilize the stats reporting module; call this once in your program
+/*! Initialize the stats reporting module; call this once in your program.
* \param[in] ctx Talloc context from which stats related memory is allocated */
void osmo_stats_init(void *ctx)
{
osmo_stats_ctx = ctx;
- osmo_stat_item_discard_all(&current_stat_item_index);
-
is_initialised = 1;
start_timer();
+
+ /* Make sure that the tcp-stats interval timer also runs at its
+ * preconfigured rate. The vty might change this setting later. */
+ osmo_stats_tcp_set_interval(osmo_tcp_stats_config->interval);
}
/*! Find a stats_reporter of given \a type and \a name.
@@ -401,7 +414,7 @@ int osmo_stats_reporter_set_flush_period(struct osmo_stats_reporter *srep, unsig
/*! Set the name prefix of a given stats_reporter.
* \param[in] srep stats_reporter whose name prefix is to be set
- * \param[in] prefix NAme perfix to pre-pend for any reported value
+ * \param[in] prefix Name prefix to pre-pend for any reported value
* \returns 0 on success; negative on error */
int osmo_stats_reporter_set_name_prefix(struct osmo_stats_reporter *srep, const char *prefix)
{
@@ -475,6 +488,8 @@ int osmo_stats_reporter_udp_open(struct osmo_stats_reporter *srep)
}
srep->buffer = msgb_alloc(buffer_size, "stats buffer");
+ if (!srep->buffer)
+ goto failed;
return 0;
@@ -558,6 +573,8 @@ struct osmo_stats_reporter *osmo_stats_reporter_create_log(const char *name)
{
struct osmo_stats_reporter *srep;
srep = osmo_stats_reporter_alloc(OSMO_STATS_REPORTER_LOG, name);
+ if (!srep)
+ return NULL;
srep->have_net_config = 0;
@@ -683,36 +700,28 @@ static int osmo_stat_item_handler(
struct osmo_stat_item_group *statg, struct osmo_stat_item *item, void *sctx_)
{
struct osmo_stats_reporter *srep;
- int32_t idx = current_stat_item_index;
- int32_t value;
- int have_value;
-
- have_value = osmo_stat_item_get_next(item, &idx, &value) > 0;
- if (!have_value)
- /* Send the last value in case a flush is requested */
- value = osmo_stat_item_get_last(item);
-
- do {
- llist_for_each_entry(srep, &osmo_stats_reporter_list, list) {
- if (!srep->running)
- continue;
+ int32_t prev_reported_value = item->reported.max;
+ int32_t new_value = item->value.max;
- if (!have_value && !srep->force_single_flush)
- continue;
+ llist_for_each_entry(srep, &osmo_stats_reporter_list, list) {
+ if (!srep->running)
+ continue;
- if (!osmo_stats_reporter_check_config(srep,
- statg->idx, statg->desc->class_id))
- continue;
+ /* If the previously reported value is the same as the current value, skip resending the value.
+ * However, if the stats reporter is set to resend all values, do resend the current value regardless of
+ * repetitions.
+ */
+ if (new_value == prev_reported_value && !srep->force_single_flush)
+ continue;
- osmo_stats_reporter_send_item(srep, statg,
- item->desc, value);
- }
+ if (!osmo_stats_reporter_check_config(srep,
+ statg->idx, statg->desc->class_id))
+ continue;
- if (!have_value)
- break;
+ osmo_stats_reporter_send_item(srep, statg, item->desc, new_value);
+ }
- have_value = osmo_stat_item_get_next(item, &idx, &value) > 0;
- } while (have_value);
+ osmo_stat_item_flush(item);
return 0;
}
@@ -755,7 +764,7 @@ static int handle_counter(struct osmo_counter *counter, void *sctx_)
/*** main reporting function ***/
-static void flush_all_reporters()
+static void flush_all_reporters(void)
{
struct osmo_stats_reporter *srep;
@@ -778,16 +787,17 @@ static void flush_all_reporters()
}
}
-int osmo_stats_report()
+int osmo_stats_report(void)
{
/* per group actions */
+ TRACE(LIBOSMOCORE_STATS_START());
osmo_counters_for_each(handle_counter, NULL);
rate_ctr_for_each_group(rate_ctr_group_handler, NULL);
osmo_stat_item_for_each_group(osmo_stat_item_group_handler, NULL);
/* global actions */
- osmo_stat_item_discard_all(&current_stat_item_index);
flush_all_reporters();
+ TRACE(LIBOSMOCORE_STATS_DONE());
return 0;
}
diff --git a/src/stats_statsd.c b/src/core/stats_statsd.c
index d4496677..b27baff8 100644
--- a/src/stats_statsd.c
+++ b/src/core/stats_statsd.c
@@ -15,10 +15,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
/*! \addtogroup stats
@@ -32,6 +28,7 @@
#include <string.h>
#include <stdint.h>
+#include <inttypes.h>
#include <errno.h>
#include <osmocom/core/utils.h>
@@ -57,6 +54,8 @@ struct osmo_stats_reporter *osmo_stats_reporter_create_statsd(const char *name)
{
struct osmo_stats_reporter *srep;
srep = osmo_stats_reporter_alloc(OSMO_STATS_REPORTER_STATSD, name);
+ if (!srep)
+ return NULL;
srep->have_net_config = 1;
@@ -88,7 +87,7 @@ static void osmo_stats_reporter_sanitize_name(char *buf)
}
static int osmo_stats_reporter_statsd_send(struct osmo_stats_reporter *srep,
- const char *name1, unsigned int index1, const char *name2, int64_t value,
+ const char *name1, const char *index1, const char *name2, int64_t value,
const char *unit)
{
char *buf;
@@ -100,15 +99,15 @@ static int osmo_stats_reporter_statsd_send(struct osmo_stats_reporter *srep,
if (prefix) {
if (name1)
- fmt = "%1$s.%2$s.%6$u.%3$s:%4$d|%5$s";
+ fmt = "%1$s.%2$s.%6$s.%3$s:%4$" PRId64 "|%5$s";
else
- fmt = "%1$s.%2$0.0s%3$s:%4$d|%5$s";
+ fmt = "%1$s.%2$0.0s%3$s:%4$" PRId64 "|%5$s";
} else {
prefix = "";
if (name1)
- fmt = "%1$s%2$s.%6$u.%3$s:%4$d|%5$s";
+ fmt = "%1$s%2$s.%6$s.%3$s:%4$" PRId64 "|%5$s";
else
- fmt = "%1$s%2$0.0s%3$s:%4$d|%5$s";
+ fmt = "%1$s%2$0.0s%3$s:%4$" PRId64 "|%5$s";
}
if (srep->agg_enabled) {
@@ -161,32 +160,42 @@ static int osmo_stats_reporter_statsd_send_counter(struct osmo_stats_reporter *s
const struct rate_ctr_desc *desc,
int64_t value, int64_t delta)
{
- if (ctrg)
- return osmo_stats_reporter_statsd_send(srep,
- ctrg->desc->group_name_prefix,
- ctrg->idx,
- desc->name, delta, "c");
- else
- return osmo_stats_reporter_statsd_send(srep,
- NULL, 0,
- desc->name, delta, "c");
+ char buf_idx[64];
+ const char *idx_name = buf_idx;
+ const char *prefix;
+
+ if (ctrg) {
+ prefix = ctrg->desc->group_name_prefix;
+ if (ctrg->name)
+ idx_name = ctrg->name;
+ else
+ snprintf(buf_idx, sizeof(buf_idx), "%u", ctrg->idx);
+ } else {
+ prefix = NULL;
+ buf_idx[0] = '0';
+ buf_idx[1] = '\n';
+ }
+ return osmo_stats_reporter_statsd_send(srep, prefix, idx_name, desc->name, delta, "c");
}
static int osmo_stats_reporter_statsd_send_item(struct osmo_stats_reporter *srep,
const struct osmo_stat_item_group *statg,
const struct osmo_stat_item_desc *desc, int64_t value)
{
- if (value < 0) {
- return osmo_stats_reporter_statsd_send(srep,
- statg->desc->group_name_prefix,
- statg->idx,
- desc->name, 0, "g");
- } else {
- return osmo_stats_reporter_statsd_send(srep,
- statg->desc->group_name_prefix,
- statg->idx,
- desc->name, value, "g");
+ char buf_idx[64];
+ char *idx_name;
+ if (statg->name)
+ idx_name = statg->name;
+ else {
+ snprintf(buf_idx, sizeof(buf_idx), "%u", statg->idx);
+ idx_name = buf_idx;
}
+
+ if (value < 0)
+ value = 0;
+
+ return osmo_stats_reporter_statsd_send(srep, statg->desc->group_name_prefix,
+ idx_name, desc->name, value, "g");
}
#endif /* !EMBEDDED */
diff --git a/src/core/stats_tcp.c b/src/core/stats_tcp.c
new file mode 100644
index 00000000..c6459fe8
--- /dev/null
+++ b/src/core/stats_tcp.c
@@ -0,0 +1,327 @@
+/*
+ * (C) 2021 by sysmocom - s.f.m.c. GmbH
+ * Author: Philipp Maier <pmaier@sysmocom.de>
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+/*! \addtogroup stats
+ * @{
+ * \file stats_tcp.c */
+
+#include "config.h"
+#if !defined(EMBEDDED)
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <linux/tcp.h>
+#include <errno.h>
+#include <pthread.h>
+
+#include <osmocom/core/select.h>
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/core/stat_item.h>
+#include <osmocom/core/stats.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/core/stats_tcp.h>
+
+static struct osmo_tcp_stats_config s_tcp_stats_config = {
+ .interval = TCP_STATS_DEFAULT_INTERVAL,
+};
+
+struct osmo_tcp_stats_config *osmo_tcp_stats_config = &s_tcp_stats_config;
+
+static struct osmo_timer_list stats_tcp_poll_timer;
+
+static LLIST_HEAD(stats_tcp);
+static struct stats_tcp_entry *stats_tcp_entry_cur;
+pthread_mutex_t stats_tcp_lock;
+
+struct stats_tcp_entry {
+ struct llist_head entry;
+ const struct osmo_fd *fd;
+ struct osmo_stat_item_group *stats_tcp;
+ const char *name;
+};
+
+enum {
+ STATS_TCP_UNACKED,
+ STATS_TCP_LOST,
+ STATS_TCP_RETRANS,
+ STATS_TCP_RTT,
+ STATS_TCP_RCV_RTT,
+ STATS_TCP_NOTSENT_BYTES,
+ STATS_TCP_RWND_LIMITED,
+ STATS_TCP_SNDBUF_LIMITED,
+ STATS_TCP_REORD_SEEN,
+};
+
+static struct osmo_stat_item_desc stats_tcp_item_desc[] = {
+ [STATS_TCP_UNACKED] = { "tcp:unacked", "unacknowledged packets", "", 60, 0 },
+ [STATS_TCP_LOST] = { "tcp:lost", "lost packets", "", 60, 0 },
+ [STATS_TCP_RETRANS] = { "tcp:retrans", "retransmitted packets", "", 60, 0 },
+ [STATS_TCP_RTT] = { "tcp:rtt", "roundtrip-time", "", 60, 0 },
+ [STATS_TCP_RCV_RTT] = { "tcp:rcv_rtt", "roundtrip-time (receive)", "", 60, 0 },
+ [STATS_TCP_NOTSENT_BYTES] = { "tcp:notsent_bytes", "bytes not yet sent", "", 60, 0 },
+ [STATS_TCP_RWND_LIMITED] = { "tcp:rwnd_limited", "time (usec) limited by receive window", "", 60, 0 },
+ [STATS_TCP_SNDBUF_LIMITED] = { "tcp:sndbuf_limited", "Time (usec) limited by send buffer", "", 60, 0 },
+ [STATS_TCP_REORD_SEEN] = { "tcp:reord_seen", "reordering events seen", "", 60, 0 },
+};
+
+static struct osmo_stat_item_group_desc stats_tcp_desc = {
+ .group_name_prefix = "tcp",
+ .group_description = "stats tcp",
+ .class_id = OSMO_STATS_CLASS_GLOBAL,
+ .num_items = ARRAY_SIZE(stats_tcp_item_desc),
+ .item_desc = stats_tcp_item_desc,
+};
+
+static void fill_stats(struct stats_tcp_entry *stats_tcp_entry)
+{
+ int rc;
+ struct tcp_info tcp_info;
+ socklen_t tcp_info_len = sizeof(tcp_info);
+ char stat_name[256];
+
+ /* Do not fill in anything before the socket is connected to a remote end */
+ if (osmo_sock_get_ip_and_port(stats_tcp_entry->fd->fd, NULL, 0, NULL, 0, false) != 0)
+ return;
+
+ /* Gather TCP statistics and update the stats items */
+ rc = getsockopt(stats_tcp_entry->fd->fd, IPPROTO_TCP, TCP_INFO, &tcp_info, &tcp_info_len);
+ if (rc < 0)
+ return;
+
+ /* Create stats items if they do not exist yet */
+ if (!stats_tcp_entry->stats_tcp) {
+ stats_tcp_entry->stats_tcp =
+ osmo_stat_item_group_alloc(stats_tcp_entry, &stats_tcp_desc, stats_tcp_entry->fd->fd);
+ OSMO_ASSERT(stats_tcp_entry->stats_tcp);
+ }
+
+ /* Update statistics */
+ if (stats_tcp_entry->name)
+ snprintf(stat_name, sizeof(stat_name), "%s", stats_tcp_entry->name);
+ else
+ snprintf(stat_name, sizeof(stat_name), "%s", osmo_sock_get_name2(stats_tcp_entry->fd->fd));
+ osmo_stat_item_group_set_name(stats_tcp_entry->stats_tcp, stat_name);
+
+ osmo_stat_item_set(osmo_stat_item_group_get_item(stats_tcp_entry->stats_tcp, STATS_TCP_UNACKED),
+ tcp_info.tcpi_unacked);
+ osmo_stat_item_set(osmo_stat_item_group_get_item(stats_tcp_entry->stats_tcp, STATS_TCP_LOST),
+ tcp_info.tcpi_lost);
+ osmo_stat_item_set(osmo_stat_item_group_get_item(stats_tcp_entry->stats_tcp, STATS_TCP_RETRANS),
+ tcp_info.tcpi_retrans);
+ osmo_stat_item_set(osmo_stat_item_group_get_item(stats_tcp_entry->stats_tcp, STATS_TCP_RTT), tcp_info.tcpi_rtt);
+ osmo_stat_item_set(osmo_stat_item_group_get_item(stats_tcp_entry->stats_tcp, STATS_TCP_RCV_RTT),
+ tcp_info.tcpi_rcv_rtt);
+#if HAVE_TCP_INFO_TCPI_NOTSENT_BYTES == 1
+ osmo_stat_item_set(osmo_stat_item_group_get_item(stats_tcp_entry->stats_tcp, STATS_TCP_NOTSENT_BYTES),
+ tcp_info.tcpi_notsent_bytes);
+#else
+ osmo_stat_item_set(osmo_stat_item_group_get_item(stats_tcp_entry->stats_tcp, STATS_TCP_NOTSENT_BYTES), -1);
+#endif
+
+#if HAVE_TCP_INFO_TCPI_RWND_LIMITED == 1
+ osmo_stat_item_set(osmo_stat_item_group_get_item(stats_tcp_entry->stats_tcp, STATS_TCP_RWND_LIMITED),
+ tcp_info.tcpi_rwnd_limited);
+#else
+ osmo_stat_item_set(osmo_stat_item_group_get_item(stats_tcp_entry->stats_tcp, STATS_TCP_RWND_LIMITED), -1);
+#endif
+
+#if STATS_TCP_SNDBUF_LIMITED == 1
+ osmo_stat_item_set(osmo_stat_item_group_get_item(stats_tcp_entry->stats_tcp, STATS_TCP_REORD_SEEN),
+ tcp_info.tcpi_sndbuf_limited);
+#else
+ osmo_stat_item_set(osmo_stat_item_group_get_item(stats_tcp_entry->stats_tcp, STATS_TCP_REORD_SEEN), -1);
+#endif
+
+#if HAVE_TCP_INFO_TCPI_REORD_SEEN == 1
+ osmo_stat_item_set(osmo_stat_item_group_get_item(stats_tcp_entry->stats_tcp, STATS_TCP_REORD_SEEN),
+ tcp_info.tcpi_reord_seen);
+#else
+ osmo_stat_item_set(osmo_stat_item_group_get_item(stats_tcp_entry->stats_tcp, STATS_TCP_REORD_SEEN), -1);
+#endif
+
+}
+
+static bool is_tcp(const struct osmo_fd *fd)
+{
+ int rc;
+ struct stat fd_stat;
+ int so_protocol = 0;
+ socklen_t so_protocol_len = sizeof(so_protocol);
+
+ /* Is this a socket? */
+ rc = fstat(fd->fd, &fd_stat);
+ if (rc < 0)
+ return false;
+ if (!S_ISSOCK(fd_stat.st_mode))
+ return false;
+
+ /* Is it a TCP socket? */
+ rc = getsockopt(fd->fd, SOL_SOCKET, SO_PROTOCOL, &so_protocol, &so_protocol_len);
+ if (rc < 0)
+ return false;
+ if (so_protocol == IPPROTO_TCP)
+ return true;
+
+ return false;
+}
+
+/*! Register an osmo_fd for TCP stats monitoring.
+ * \param[in] fd osmocom file descriptor to be registered.
+ * \param[in] human readbla name that is used as prefix for the related stats item.
+ * \returns 0 on success; negative in case of error. */
+int osmo_stats_tcp_osmo_fd_register(const struct osmo_fd *fd, const char *name)
+{
+ struct stats_tcp_entry *stats_tcp_entry;
+
+ /* Only TCP sockets can be registered for monitoring, anything else will fall through. */
+ if (!is_tcp(fd))
+ return -EINVAL;
+
+ /* When the osmo_fd is registered and unregistered properly there shouldn't be any leftovers from already closed
+ * osmo_fds in the stats_tcp list. But lets proactively make sure that any leftovers are cleaned up. */
+ osmo_stats_tcp_osmo_fd_unregister(fd);
+
+ /* Make a new list object, attach the osmo_fd... */
+ stats_tcp_entry = talloc_zero(OTC_GLOBAL, struct stats_tcp_entry);
+ OSMO_ASSERT(stats_tcp_entry);
+ stats_tcp_entry->fd = fd;
+ stats_tcp_entry->name = talloc_strdup(stats_tcp_entry, name);
+
+ pthread_mutex_lock(&stats_tcp_lock);
+ llist_add_tail(&stats_tcp_entry->entry, &stats_tcp);
+ pthread_mutex_unlock(&stats_tcp_lock);
+
+ return 0;
+}
+
+static void next_stats_tcp_entry(void)
+{
+ struct stats_tcp_entry *last;
+
+ if (llist_empty(&stats_tcp)) {
+ stats_tcp_entry_cur = NULL;
+ return;
+ }
+
+ last = (struct stats_tcp_entry *)llist_last_entry(&stats_tcp, struct stats_tcp_entry, entry);
+
+ if (!stats_tcp_entry_cur || stats_tcp_entry_cur == last)
+ stats_tcp_entry_cur =
+ (struct stats_tcp_entry *)llist_first_entry(&stats_tcp, struct stats_tcp_entry, entry);
+ else
+ stats_tcp_entry_cur =
+ (struct stats_tcp_entry *)llist_entry(stats_tcp_entry_cur->entry.next, struct stats_tcp_entry,
+ entry);
+}
+
+/*! Register an osmo_fd for TCP stats monitoring.
+ * \param[in] fd osmocom file descriptor to be unregistered.
+ * \returns 0 on success; negative in case of error. */
+int osmo_stats_tcp_osmo_fd_unregister(const struct osmo_fd *fd)
+{
+ struct stats_tcp_entry *stats_tcp_entry;
+ int rc = -EINVAL;
+
+ pthread_mutex_lock(&stats_tcp_lock);
+ llist_for_each_entry(stats_tcp_entry, &stats_tcp, entry) {
+ if (fd->fd == stats_tcp_entry->fd->fd) {
+ /* In case we want to remove exactly that item which is also
+ * selected as the current item, we must designate either a
+ * different item or invalidate the current item.
+ */
+ if (stats_tcp_entry == stats_tcp_entry_cur) {
+ if (llist_count(&stats_tcp) > 2)
+ next_stats_tcp_entry();
+ else
+ stats_tcp_entry_cur = NULL;
+ }
+
+ /* Date item from list */
+ llist_del(&stats_tcp_entry->entry);
+ osmo_stat_item_group_free(stats_tcp_entry->stats_tcp);
+ talloc_free(stats_tcp_entry);
+ rc = 0;
+ break;
+ }
+ }
+ pthread_mutex_unlock(&stats_tcp_lock);
+
+ return rc;
+}
+
+static void stats_tcp_poll_timer_cb(void *data)
+{
+ int i;
+ int batch_size;
+ int llist_size;
+
+ pthread_mutex_lock(&stats_tcp_lock);
+
+ /* Make sure we do not run over the same sockets multiple times if the
+ * configured llist_size is larger then the actual list */
+ batch_size = osmo_tcp_stats_config->batch_size;
+ llist_size = llist_count(&stats_tcp);
+ if (llist_size < batch_size)
+ batch_size = llist_size;
+
+ /* Process a batch of sockets */
+ for (i = 0; i < batch_size; i++) {
+ next_stats_tcp_entry();
+ if (stats_tcp_entry_cur)
+ fill_stats(stats_tcp_entry_cur);
+ }
+
+ pthread_mutex_unlock(&stats_tcp_lock);
+
+ if (osmo_tcp_stats_config->interval > 0)
+ osmo_timer_schedule(&stats_tcp_poll_timer, osmo_tcp_stats_config->interval, 0);
+}
+
+/*! Set the polling interval (common for all sockets)
+ * \param[in] interval Poll interval in seconds
+ * \returns 0 on success; negative on error */
+int osmo_stats_tcp_set_interval(int interval)
+{
+ osmo_tcp_stats_config->interval = interval;
+ if (osmo_tcp_stats_config->interval > 0)
+ osmo_timer_schedule(&stats_tcp_poll_timer, osmo_tcp_stats_config->interval, 0);
+ return 0;
+}
+
+static __attribute__((constructor))
+void on_dso_load_stats_tcp(void)
+{
+ stats_tcp_entry_cur = NULL;
+ pthread_mutex_init(&stats_tcp_lock, NULL);
+
+ osmo_tcp_stats_config->interval = TCP_STATS_DEFAULT_INTERVAL;
+ osmo_tcp_stats_config->batch_size = TCP_STATS_DEFAULT_BATCH_SIZE;
+
+ osmo_timer_setup(&stats_tcp_poll_timer, stats_tcp_poll_timer_cb, NULL);
+}
+
+#endif /* !EMBEDDED */
+
+/* @} */
diff --git a/src/strrb.c b/src/core/strrb.c
index 461fdecc..c5a5ed6e 100644
--- a/src/strrb.c
+++ b/src/core/strrb.c
@@ -31,10 +31,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
/*! \addtogroup utils
@@ -56,12 +52,12 @@
* This function creates and initializes a ringbuffer.
* Note that the ringbuffer stores at most rb_size - 1 messages.
*/
-struct osmo_strrb *osmo_strrb_create(TALLOC_CTX * ctx, size_t rb_size)
+struct osmo_strrb *osmo_strrb_create(void *talloc_ctx, size_t rb_size)
{
struct osmo_strrb *rb = NULL;
unsigned int i;
- rb = talloc_zero(ctx, struct osmo_strrb);
+ rb = talloc_zero(talloc_ctx, struct osmo_strrb);
if (!rb)
goto alloc_error;
diff --git a/src/tdef.c b/src/core/tdef.c
index 897a92fd..f0c0f2e8 100644
--- a/src/tdef.c
+++ b/src/core/tdef.c
@@ -52,18 +52,18 @@
* By keeping separate osmo_tdef arrays, several groups of timers can be kept
* separately. The VTY tests in tests/tdef/ showcase different schemes:
*
- * - \ref tests/vty/tdef_vty_test_config_root.c:
+ * - \ref tests/tdef/tdef_vty_config_root_test.c:
* Keep several timer definitions in separately named groups: showcase the
* osmo_tdef_vty_groups*() API. Each timer group exists exactly once.
*
- * - \ref tests/vty/tdef_vty_test_config_subnode.c:
+ * - \ref tests/tdef/tdef_vty_config_subnode_test.c:
* Keep a single list of timers without separate grouping.
* Put this list on a specific subnode below the CONFIG_NODE.
* There could be several separate subnodes with timers like this, i.e.
* continuing from this example, sets of timers could be separated by placing
* timers in specific config subnodes instead of using the global group name.
*
- * - \ref tests/vty/tdef_vty_test_dynamic.c:
+ * - \ref tests/tdef/tdef_vty_dynamic_test.c:
* Dynamically allocate timer definitions per each new created object.
* Thus there can be an arbitrary number of independent timer definitions, one
* per allocated object.
@@ -200,8 +200,10 @@ void osmo_tdefs_reset(struct osmo_tdef *tdefs)
* \param[in] tdefs Array of timer definitions, last entry must be fully zero initialized.
* \param[in] T Timer number to get the value for.
* \param[in] as_unit Return timeout value in this unit.
- * \param[in] val_if_not_present Fallback value to return if no timeout is defined.
+ * \param[in] val_if_not_present Fallback value to return if no timeout is defined; if this is a negative number, a
+ * missing T timer definition aborts the program via OSMO_ASSERT().
* \return Timeout value in the unit given by as_unit, rounded up if necessary, or val_if_not_present.
+ * If val_if_not_present is negative and no T timer is defined, trigger OSMO_ASSERT() and do not return.
*/
unsigned long osmo_tdef_get(const struct osmo_tdef *tdefs, int T, enum osmo_tdef_unit as_unit, long val_if_not_present)
{
@@ -331,30 +333,41 @@ const struct osmo_tdef_state_timeout *osmo_tdef_get_state_timeout(uint32_t state
*/
int _osmo_tdef_fsm_inst_state_chg(struct osmo_fsm_inst *fi, uint32_t state,
const struct osmo_tdef_state_timeout *timeouts_array,
- const struct osmo_tdef *tdefs, unsigned long default_timeout,
+ const struct osmo_tdef *tdefs, long default_timeout,
const char *file, int line)
{
const struct osmo_tdef_state_timeout *t = osmo_tdef_get_state_timeout(state, timeouts_array);
- unsigned long val = 0;
+ unsigned long val_ms = 0;
/* No timeout defined for this state? */
if (!t)
return _osmo_fsm_inst_state_chg(fi, state, 0, 0, file, line);
- if (t->T)
- val = osmo_tdef_get(tdefs, t->T, OSMO_TDEF_S, default_timeout);
+ if (t->T) {
+ const struct osmo_tdef *tdef = osmo_tdef_get_entry((struct osmo_tdef *)tdefs, t->T);
+ if (tdef == NULL) {
+ /* emulate the old behavior: treat default_timeout as OSMO_TDEF_S */
+ OSMO_ASSERT(default_timeout >= 0);
+ val_ms = default_timeout * 1000;
+ } else {
+ val_ms = osmo_tdef_round(tdef->val, tdef->unit, OSMO_TDEF_MS);
+ /* emulate the old behavior: treat OSMO_TDEF_CUSTOM as OSMO_TDEF_S */
+ if (tdef->unit == OSMO_TDEF_CUSTOM)
+ val_ms *= 1000;
+ }
+ }
if (t->keep_timer) {
if (t->T)
- return _osmo_fsm_inst_state_chg_keep_or_start_timer(fi, state, val, t->T, file, line);
+ return _osmo_fsm_inst_state_chg_keep_or_start_timer_ms(fi, state, val_ms, t->T, file, line);
else
return _osmo_fsm_inst_state_chg_keep_timer(fi, state, file, line);
}
- /* val is always initialized here, because if t->keep_timer is false, t->T must be != 0.
+ /* val_ms is always initialized here, because if t->keep_timer is false, t->T must be != 0.
* Otherwise osmo_tdef_get_state_timeout() would have returned NULL. */
OSMO_ASSERT(t->T);
- return _osmo_fsm_inst_state_chg(fi, state, val, t->T, file, line);
+ return _osmo_fsm_inst_state_chg_ms(fi, state, val_ms, t->T, file, line);
}
const struct value_string osmo_tdef_unit_names[] = {
diff --git a/src/core/thread.c b/src/core/thread.c
new file mode 100644
index 00000000..d9a98422
--- /dev/null
+++ b/src/core/thread.c
@@ -0,0 +1,56 @@
+/*
+ * (C) 2021 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Pau Espin Pedrol <pespin@sysmocom.de>
+ *
+ * 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.
+ *
+ */
+
+/*! \addtogroup thread
+ * @{
+ * \file thread.c
+ */
+
+/*! \file thread.c
+ */
+
+#include "config.h"
+
+/* If HAVE_GETTID, then "_GNU_SOURCE" may need to be defined to use gettid() */
+#if HAVE_GETTID
+#define _GNU_SOURCE
+#endif
+#include <unistd.h>
+#include <sys/types.h>
+
+#include <osmocom/core/thread.h>
+
+/*! Wrapper around Linux's gettid() to make it easily accessible on different system versions.
+ * If the gettid() API cannot be found, it will use the syscall directly if
+ * available. If no syscall is found available, then getpid() is called as
+ * fallback. See 'man 2 gettid' for further and details information.
+ * \returns This call is always successful and returns returns the thread ID of
+ * the calling thread (or the process ID of the current process if
+ * gettid() or its syscall are unavailable in the system).
+ */
+pid_t osmo_gettid(void)
+{
+#if HAVE_GETTID
+ return gettid();
+#elif defined(LINUX) && defined(__NR_gettid)
+ return (pid_t) syscall(__NR_gettid);
+#else
+ #pragma message ("use pid as tid")
+ return getpid();
+#endif
+}
diff --git a/src/core/time_cc.c b/src/core/time_cc.c
new file mode 100644
index 00000000..0e6879e5
--- /dev/null
+++ b/src/core/time_cc.c
@@ -0,0 +1,228 @@
+/*! \file foo.c
+ * Report the cumulative counter of time for which a flag is true as rate counter.
+ */
+/* Copyright (C) 2021 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * Author: Neels Hofmeyr <nhofmeyr@sysmocom.de>
+ *
+ * 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/>.
+ *
+ */
+
+/*! \addtogroup time_cc
+ *
+ * Report the cumulative counter of time for which a flag is true as rate counter.
+ *
+ * Useful for reporting cumulative time counters as defined in 3GPP TS 52.402, for example allAvailableSDCCHAllocated,
+ * allAvailableTCHAllocated, availablePDCHAllocatedTime.
+ *
+ * For a usage example, see the description of struct osmo_time_cc.
+ *
+ * @{
+ * \file time_cc.c
+ */
+#include "config.h"
+#ifdef HAVE_CLOCK_GETTIME
+
+#include <limits.h>
+#include <time.h>
+
+#include <osmocom/core/tdef.h>
+#include <osmocom/core/rate_ctr.h>
+#include <osmocom/core/time_cc.h>
+
+#define GRAN_USEC(TIME_CC) ((TIME_CC)->cfg.gran_usec ? : 1000000)
+#define ROUND_THRESHOLD_USEC(TIME_CC) ((TIME_CC)->cfg.round_threshold_usec ? \
+ OSMO_MIN((TIME_CC)->cfg.round_threshold_usec, GRAN_USEC(TIME_CC)) \
+ : (GRAN_USEC(TIME_CC) / 2))
+
+static uint64_t time_now_usec(void)
+{
+ struct timespec tp;
+ if (osmo_clock_gettime(CLOCK_MONOTONIC, &tp))
+ return 0;
+ return (uint64_t)tp.tv_sec * 1000000 + tp.tv_nsec / 1000;
+}
+
+static void osmo_time_cc_forget_sum(struct osmo_time_cc *tc, uint64_t now);
+
+static void osmo_time_cc_update_from_tdef(struct osmo_time_cc *tc, uint64_t now)
+{
+ bool do_forget_sum = false;
+ if (!tc->cfg.T_defs)
+ return;
+ if (tc->cfg.T_gran) {
+ uint64_t was = GRAN_USEC(tc);
+ tc->cfg.gran_usec = osmo_tdef_get(tc->cfg.T_defs, tc->cfg.T_gran, OSMO_TDEF_US, -1);
+ if (was != GRAN_USEC(tc))
+ do_forget_sum = true;
+ }
+ if (tc->cfg.T_round_threshold)
+ tc->cfg.round_threshold_usec = osmo_tdef_get(tc->cfg.T_defs, tc->cfg.T_round_threshold,
+ OSMO_TDEF_US, -1);
+ if (tc->cfg.T_forget_sum) {
+ uint64_t was = tc->cfg.forget_sum_usec;
+ tc->cfg.forget_sum_usec = osmo_tdef_get(tc->cfg.T_defs, tc->cfg.T_forget_sum, OSMO_TDEF_US, -1);
+ if (tc->cfg.forget_sum_usec && was != tc->cfg.forget_sum_usec)
+ do_forget_sum = true;
+ }
+
+ if (do_forget_sum && tc->sum)
+ osmo_time_cc_forget_sum(tc, now);
+}
+
+static void osmo_time_cc_schedule_timer(struct osmo_time_cc *tc, uint64_t now);
+
+/*! Clear out osmo_timer and internal counting state of struct osmo_time_cc. The .cfg remains unaffected. After calling,
+ * the osmo_time_cc instance can be used again to accumulate state as if it had just been initialized. */
+void osmo_time_cc_cleanup(struct osmo_time_cc *tc)
+{
+ osmo_timer_del(&tc->timer);
+ *tc = (struct osmo_time_cc){
+ .cfg = tc->cfg,
+ };
+}
+
+static void osmo_time_cc_start(struct osmo_time_cc *tc, uint64_t now)
+{
+ osmo_time_cc_cleanup(tc);
+ tc->start_time = now;
+ tc->last_counted_time = now;
+ osmo_time_cc_update_from_tdef(tc, now);
+ osmo_time_cc_schedule_timer(tc, now);
+}
+
+static void osmo_time_cc_count_time(struct osmo_time_cc *tc, uint64_t now)
+{
+ uint64_t time_delta = now - tc->last_counted_time;
+ tc->last_counted_time = now;
+ if (!tc->flag_state)
+ return;
+ /* Flag is currently true, cumulate the elapsed time */
+ tc->total_sum += time_delta;
+ tc->sum += time_delta;
+}
+
+static void osmo_time_cc_report(struct osmo_time_cc *tc, uint64_t now)
+{
+ uint64_t delta;
+ uint64_t n;
+ /* We report a sum "rounded up", ahead of time. If the granularity period has not yet elapsed after the last
+ * reporting, do not report again yet. */
+ if (tc->reported_sum > tc->sum)
+ return;
+ delta = tc->sum - tc->reported_sum;
+ /* elapsed full periods */
+ n = delta / GRAN_USEC(tc);
+ /* If the delta has passed round_threshold (normally half of gran_usec), increment. */
+ delta -= n * GRAN_USEC(tc);
+ if (delta >= ROUND_THRESHOLD_USEC(tc))
+ n++;
+ if (!n)
+ return;
+
+ /* integer sanity, since rate_ctr_add() takes an int argument. */
+ if (n > INT_MAX)
+ n = INT_MAX;
+ if (tc->cfg.rate_ctr)
+ rate_ctr_add(tc->cfg.rate_ctr, n);
+ /* Store the increments of gran_usec that were counted. */
+ tc->reported_sum += n * GRAN_USEC(tc);
+}
+
+static void osmo_time_cc_forget_sum(struct osmo_time_cc *tc, uint64_t now)
+{
+ tc->reported_sum = 0;
+ tc->sum = 0;
+
+ if (tc->last_counted_time < now)
+ tc->last_counted_time = now;
+}
+
+/*! Initialize struct osmo_time_cc. Call this once before use, and before setting up the .cfg items. */
+void osmo_time_cc_init(struct osmo_time_cc *tc)
+{
+ *tc = (struct osmo_time_cc){0};
+}
+
+/*! Report state to be recorded by osmo_time_cc instance. Setting an unchanged state repeatedly has no effect. */
+void osmo_time_cc_set_flag(struct osmo_time_cc *tc, bool flag)
+{
+ uint64_t now = time_now_usec();
+ if (!tc->start_time)
+ osmo_time_cc_start(tc, now);
+ /* No flag change == no effect */
+ if (flag == tc->flag_state)
+ return;
+ /* Sum up elapsed time, report increments for that. */
+ osmo_time_cc_count_time(tc, now);
+ osmo_time_cc_report(tc, now);
+ tc->flag_state = flag;
+ osmo_time_cc_schedule_timer(tc, now);
+}
+
+static void osmo_time_cc_timer_cb(void *data)
+{
+ struct osmo_time_cc *tc = data;
+ uint64_t now = time_now_usec();
+
+ osmo_time_cc_update_from_tdef(tc, now);
+
+ if (tc->flag_state) {
+ osmo_time_cc_count_time(tc, now);
+ osmo_time_cc_report(tc, now);
+ } else if (tc->cfg.forget_sum_usec && tc->sum
+ && (now >= tc->last_counted_time + tc->cfg.forget_sum_usec)) {
+ osmo_time_cc_forget_sum(tc, now);
+ }
+ osmo_time_cc_schedule_timer(tc, now);
+}
+
+/*! Figure out the next time we should do anything, if the flag state remains unchanged. */
+static void osmo_time_cc_schedule_timer(struct osmo_time_cc *tc, uint64_t now)
+{
+ uint64_t next_event = UINT64_MAX;
+
+ osmo_time_cc_update_from_tdef(tc, now);
+
+ /* If it is required, when will the next forget_sum happen? */
+ if (tc->cfg.forget_sum_usec && !tc->flag_state && tc->sum > 0) {
+ uint64_t next_forget_time = tc->last_counted_time + tc->cfg.forget_sum_usec;
+ next_event = OSMO_MIN(next_event, next_forget_time);
+ }
+ /* Next rate_ctr increment? */
+ if (tc->flag_state) {
+ uint64_t next_inc = now + (tc->reported_sum - tc->sum) + ROUND_THRESHOLD_USEC(tc);
+ next_event = OSMO_MIN(next_event, next_inc);
+ }
+
+ /* No event coming up? */
+ if (next_event == UINT64_MAX)
+ return;
+
+ if (next_event <= now)
+ next_event = 0;
+ else
+ next_event -= now;
+
+ osmo_timer_setup(&tc->timer, osmo_time_cc_timer_cb, tc);
+ osmo_timer_del(&tc->timer);
+ osmo_timer_schedule(&tc->timer, next_event / 1000000, next_event % 1000000);
+}
+
+#endif /* HAVE_CLOCK_GETTIME */
+
+/*! @} */
diff --git a/src/timer.c b/src/core/timer.c
index bcd9f5ba..067bd875 100644
--- a/src/timer.c
+++ b/src/core/timer.c
@@ -19,10 +19,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
@@ -135,7 +131,7 @@ void osmo_timer_del(struct osmo_timer_list *timer)
* This function can be used to determine whether a given timer
* has alredy expired (returns 0) or is still pending (returns 1)
*/
-int osmo_timer_pending(struct osmo_timer_list *timer)
+int osmo_timer_pending(const struct osmo_timer_list *timer)
{
return timer->active;
}
@@ -195,7 +191,16 @@ int osmo_timers_nearest_ms(void)
return -1;
nearest_ms = nearest_p->tv_sec * 1000;
+#ifndef EMBEDDED
+ /* By adding 999 milliseconds, we ensure rounding up to the nearest
+ * whole millisecond. This approach prevents the return of 0 when the
+ * timer is still active, and it guarantees that the calling process
+ * does not wait for a duration shorter than the time remaining on the
+ * timer. */
+ nearest_ms += (nearest_p->tv_usec + 999) / 1000;
+#else
nearest_ms += nearest_p->tv_usec / 1000;
+#endif
return nearest_ms;
}
diff --git a/src/timer_clockgettime.c b/src/core/timer_clockgettime.c
index 7b17fd11..6112b8a5 100644
--- a/src/timer_clockgettime.c
+++ b/src/core/timer_clockgettime.c
@@ -14,10 +14,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
/*! \addtogroup timer
diff --git a/src/timer_gettimeofday.c b/src/core/timer_gettimeofday.c
index 34949465..e0212b5a 100644
--- a/src/timer_gettimeofday.c
+++ b/src/core/timer_gettimeofday.c
@@ -15,10 +15,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
/*! \addtogroup timer
diff --git a/src/core/tun.c b/src/core/tun.c
new file mode 100644
index 00000000..86128190
--- /dev/null
+++ b/src/core/tun.c
@@ -0,0 +1,577 @@
+
+/* TUN interface functions.
+ * (C) 2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * Author: Pau Espin Pedrol <pespin@sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include "config.h"
+
+/*! \addtogroup tun
+ * @{
+ * tun network device (interface) convenience functions
+ *
+ * \file tundev.c
+ *
+ * Example lifecycle use of the API:
+ *
+ * struct osmo_sockaddr_str osa_str = {};
+ * struct osmo_sockaddr osa = {};
+ *
+ * // Allocate object:
+ * struct osmo_tundev *tundev = osmo_tundev_alloc(parent_talloc_ctx, name);
+ * OSMO_ASSERT(tundev);
+ *
+ * // Configure object (before opening):
+ * osmo_tundev_set_data_ind_cb(tun, tun_data_ind_cb);
+ * rc = osmo_tundev_set_dev_name(tun, "mytunnel0");
+ * rc = osmo_tundev_set_netns_name(tun, "some_netns_name_or_null");
+ *
+ * // Open the tundev object:
+ * rc = osmo_tundev_open(tundev);
+ * // The tunnel device is now created and an associatd netdev object
+ * // is available to operate the device:
+ * struct osmo_netdev *netdev = osmo_tundev_get_netdev(tundev);
+ * OSMO_ASSERT(netdev);
+ *
+ * // Add a local IPv4 address:
+ * rc = osmo_sockaddr_str_from_str2(&osa_str, "192.168.200.1");
+ * rc = osmo_sockaddr_str_to_sockaddr(&osa_str, &osa.u.sas);
+ * rc = osmo_netdev_add_addr(netdev, &osa, 24);
+ *
+ * // Bring network interface up:
+ * rc = osmo_netdev_ifupdown(netdev, true);
+ *
+ * // Add default route (0.0.0.0/0):
+ * rc = osmo_sockaddr_str_from_str2(&osa_str, "0.0.0.0");
+ * rc = osmo_sockaddr_str_to_sockaddr(&osa_str, &osa.u.sas);
+ * rc = osmo_netdev_add_route(netdev, &osa, 0, NULL);
+ *
+ * // Close the tunnel (asssociated netdev object becomes unavailable)
+ * rc = osmo_tundev_close(tundev);
+ * // Free the object:
+ * osmo_tundev_free(tundev);
+ */
+
+#if (!EMBEDDED)
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+#include <ifaddrs.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <net/if.h>
+
+#if defined(__linux__)
+#include <linux/if_tun.h>
+#else
+#error "Unknown platform!"
+#endif
+
+#include <osmocom/core/utils.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/write_queue.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/core/netns.h>
+#include <osmocom/core/netdev.h>
+#include <osmocom/core/tun.h>
+
+#define TUN_DEV_PATH "/dev/net/tun"
+#define TUN_PACKET_MAX 8196
+
+#define LOGTUN(tun, lvl, fmt, args ...) \
+ LOGP(DLGLOBAL, lvl, "TUN(%s,if=%s/%u,ns=%s): " fmt, \
+ (tun)->name, (tun)->dev_name ? : "", \
+ (tun)->ifindex, (tun)->netns_name ? : "", ## args)
+
+struct osmo_tundev {
+ /* Name used to identify the osmo_tundev */
+ char *name;
+
+ /* netdev managing the tun interface: */
+ struct osmo_netdev *netdev;
+
+ /* ifindiex of the currently opened tunnel interface */
+ unsigned int ifindex;
+
+ /* Network interface name to use when setting up the tun device.
+ * NULL = let the system pick one. */
+ char *dev_name;
+ /* Whether dev_name is set by user or dynamically allocated by system */
+ bool dev_name_dynamic;
+
+ /* Write queue used since tun fd is set non-blocking */
+ struct osmo_wqueue wqueue;
+
+ /* netns name where the tun interface is created (NULL = default netns) */
+ char *netns_name;
+
+ /* API user private data */
+ void *priv_data;
+
+ /* Called by tundev each time a new packet is received on the tun interface. Can be NULL. */
+ osmo_tundev_data_ind_cb_t data_ind_cb;
+
+ /* Whether the tundev is in opened state (managing the tun interface) */
+ bool opened;
+};
+
+/* A new pkt arrived from the tun device, dispatch it to the API user */
+static int tundev_decaps(struct osmo_tundev *tundev)
+{
+ struct msgb *msg;
+ int rc;
+
+ msg = msgb_alloc(TUN_PACKET_MAX, "tundev_rx");
+
+ if ((rc = read(tundev->wqueue.bfd.fd, msgb_data(msg), TUN_PACKET_MAX)) <= 0) {
+ LOGTUN(tundev, LOGL_ERROR, "read() failed: %s (%d)\n", strerror(errno), errno);
+ msgb_free(msg);
+ return -1;
+ }
+ msgb_put(msg, rc);
+
+ if (tundev->data_ind_cb)
+ return tundev->data_ind_cb(tundev, msg);
+
+ msgb_free(msg);
+ return 0;
+}
+
+/* callback for tun device osmocom select loop integration */
+static int tundev_read_cb(struct osmo_fd *fd)
+{
+ struct osmo_tundev *tundev = fd->data;
+ return tundev_decaps(tundev);
+}
+
+/* callback for tun device osmocom select loop integration */
+static int tundev_write_cb(struct osmo_fd *fd, struct msgb *msg)
+{
+ struct osmo_tundev *tundev = fd->data;
+ size_t pkt_len = msgb_length(msg);
+
+ int rc;
+ rc = write(tundev->wqueue.bfd.fd, msgb_data(msg), pkt_len);
+ if (rc < 0)
+ LOGTUN(tundev, LOGL_ERROR, "write() failed: %s (%d)\n", strerror(errno), errno);
+ else if (rc < pkt_len)
+ LOGTUN(tundev, LOGL_ERROR, "short write() %d < %zu\n", rc, pkt_len);
+ return rc;
+}
+
+static int tundev_ifupdown_ind_cb(struct osmo_netdev *netdev, bool ifupdown)
+{
+ struct osmo_tundev *tundev = osmo_netdev_get_priv_data(netdev);
+ LOGTUN(tundev, LOGL_NOTICE, "Physical link state changed: %s\n",
+ ifupdown ? "UP" : "DOWN");
+
+ /* free any backlog, both on IFUP and IFDOWN. Keep the LMI, as it makes
+ * sense to get one out of the door ASAP. */
+ osmo_wqueue_clear(&tundev->wqueue);
+ return 0;
+}
+
+static int tundev_dev_name_chg_cb(struct osmo_netdev *netdev, const char *new_dev_name)
+{
+ struct osmo_tundev *tundev = osmo_netdev_get_priv_data(netdev);
+ LOGTUN(tundev, LOGL_NOTICE, "netdev changed name: %s -> %s\n",
+ osmo_netdev_get_dev_name(netdev), new_dev_name);
+
+ if (tundev->dev_name_dynamic) {
+ osmo_talloc_replace_string(tundev, &tundev->dev_name, new_dev_name);
+ } else {
+ /* TODO: in here we probably want to force the iface name back
+ * to tundev->dev_name one we have a osmo_netdev_set_ifname() API */
+ osmo_talloc_replace_string(tundev, &tundev->dev_name, new_dev_name);
+ }
+
+ return 0;
+}
+
+static int tundev_mtu_chg_cb(struct osmo_netdev *netdev, uint32_t new_mtu)
+{
+ struct osmo_tundev *tundev = osmo_netdev_get_priv_data(netdev);
+ LOGTUN(tundev, LOGL_NOTICE, "netdev changed MTU: %u\n", new_mtu);
+
+ return 0;
+}
+
+/*! Allocate a new tundev object.
+ * \param[in] ctx talloc context to use as a parent when allocating the tundev object
+ * \param[in] name A name providen to identify the tundev object
+ * \returns newly allocated tundev object on success; NULL on error
+ */
+struct osmo_tundev *osmo_tundev_alloc(void *ctx, const char *name)
+{
+ struct osmo_tundev *tundev;
+
+ tundev = talloc_zero(ctx, struct osmo_tundev);
+ if (!tundev)
+ return NULL;
+
+ tundev->netdev = osmo_netdev_alloc(tundev, name);
+ if (!tundev->netdev) {
+ talloc_free(tundev);
+ return NULL;
+ }
+ osmo_netdev_set_priv_data(tundev->netdev, tundev);
+ osmo_netdev_set_ifupdown_ind_cb(tundev->netdev, tundev_ifupdown_ind_cb);
+ osmo_netdev_set_dev_name_chg_cb(tundev->netdev, tundev_dev_name_chg_cb);
+ osmo_netdev_set_mtu_chg_cb(tundev->netdev, tundev_mtu_chg_cb);
+
+ tundev->name = talloc_strdup(tundev, name);
+ osmo_wqueue_init(&tundev->wqueue, 1000);
+ osmo_fd_setup(&tundev->wqueue.bfd, -1, OSMO_FD_READ, osmo_wqueue_bfd_cb, tundev, 0);
+ tundev->wqueue.read_cb = tundev_read_cb;
+ tundev->wqueue.write_cb = tundev_write_cb;
+
+ return tundev;
+}
+
+/*! Free an allocated tundev object.
+ * \param[in] tundev The tundev object to free
+ */
+void osmo_tundev_free(struct osmo_tundev *tundev)
+{
+ if (!tundev)
+ return;
+ osmo_tundev_close(tundev);
+ osmo_netdev_free(tundev->netdev);
+ talloc_free(tundev);
+}
+
+/*! Open and configure fd of the tunnel device.
+ * \param[in] tundev The tundev object whose tunnel interface to open
+ * \param[in] flags internal linux flags to pass when creating the device (not used yet)
+ * \returns 0 on success; negative on error
+ */
+static int tundev_open_fd(struct osmo_tundev *tundev, int flags)
+{
+ struct ifreq ifr;
+ int fd, rc;
+
+ fd = open(TUN_DEV_PATH, O_RDWR);
+ if (fd < 0) {
+ LOGTUN(tundev, LOGL_ERROR, "Cannot open " TUN_DEV_PATH ": %s\n", strerror(errno));
+ return fd;
+ }
+
+ memset(&ifr, 0, sizeof(ifr));
+ ifr.ifr_flags = IFF_TUN | IFF_NO_PI | flags;
+ if (tundev->dev_name) {
+ /* if a TUN interface name was specified, put it in the structure; otherwise,
+ the kernel will try to allocate the "next" device of the specified type */
+ osmo_strlcpy(ifr.ifr_name, tundev->dev_name, IFNAMSIZ);
+ }
+
+ /* try to create the device */
+ rc = ioctl(fd, TUNSETIFF, (void *) &ifr);
+ if (rc < 0)
+ goto close_ret;
+
+ /* Read name back from device */
+ if (!tundev->dev_name) {
+ ifr.ifr_name[IFNAMSIZ - 1] = '\0';
+ tundev->dev_name = talloc_strdup(tundev, ifr.ifr_name);
+ tundev->dev_name_dynamic = true;
+ }
+
+ /* Store interface index:
+ * (Note: there's a potential race condition here between creating the
+ * iface with a given name above and attempting to retrieve its ifindex based
+ * on that name. Someone (ie udev) could have the iface renamed in
+ * between here. It's a pity that TUNSETIFF doesn't copy back to us the
+ * newly allocated ifinidex as it does with ifname)
+ */
+ tundev->ifindex = if_nametoindex(tundev->dev_name);
+ if (tundev->ifindex == 0) {
+ LOGTUN(tundev, LOGL_ERROR, "Unable to find ifinidex for dev %s\n",
+ tundev->dev_name);
+ rc = -ENODEV;
+ goto close_ret;
+ }
+
+ LOGTUN(tundev, LOGL_INFO, "TUN device created\n");
+
+ /* set non-blocking: */
+ rc = fcntl(fd, F_GETFL);
+ if (rc < 0) {
+ LOGTUN(tundev, LOGL_ERROR, "fcntl(F_GETFL) failed: %s (%d)\n",
+ strerror(errno), errno);
+ goto close_ret;
+ }
+ rc = fcntl(fd, F_SETFL, rc | O_NONBLOCK);
+ if (rc < 0) {
+ LOGTUN(tundev, LOGL_ERROR, "fcntl(F_SETFL, O_NONBLOCK) failed: %s (%d)\n",
+ strerror(errno), errno);
+ goto close_ret;
+ }
+ return fd;
+
+close_ret:
+ close(fd);
+ return rc;
+}
+
+/*! Open the tunnel device owned by the tundev object.
+ * \param[in] tundev The tundev object to open
+ * \returns 0 on success; negative on error
+ */
+int osmo_tundev_open(struct osmo_tundev *tundev)
+{
+ struct osmo_netns_switch_state switch_state;
+ int rc;
+ int netns_fd = -1;
+
+ if (tundev->opened)
+ return -EALREADY;
+
+ /* temporarily switch to specified namespace to create tun device */
+ if (tundev->netns_name) {
+ LOGTUN(tundev, LOGL_INFO, "Open tun: Switch to netns '%s'\n",
+ tundev->netns_name);
+ netns_fd = osmo_netns_open_fd(tundev->netns_name);
+ if (netns_fd < 0) {
+ LOGP(DLGLOBAL, LOGL_ERROR, "Open tun: Cannot switch to netns '%s': %s (%d)\n",
+ tundev->netns_name, strerror(errno), errno);
+ return netns_fd;
+ }
+ rc = osmo_netns_switch_enter(netns_fd, &switch_state);
+ if (rc < 0) {
+ LOGTUN(tundev, LOGL_ERROR, "Open tun: Cannot switch to netns '%s': %s (%d)\n",
+ tundev->netns_name, strerror(errno), errno);
+ goto err_close_netns_fd;
+ }
+ }
+
+ tundev->wqueue.bfd.fd = tundev_open_fd(tundev, 0);
+ if (tundev->wqueue.bfd.fd < 0) {
+ LOGTUN(tundev, LOGL_ERROR, "Cannot open TUN device: %s\n", strerror(errno));
+ rc = -ENODEV;
+ goto err_restore_ns;
+ }
+
+ /* switch back to default namespace */
+ if (tundev->netns_name) {
+ rc = osmo_netns_switch_exit(&switch_state);
+ if (rc < 0) {
+ LOGTUN(tundev, LOGL_ERROR, "Open tun: Cannot switch back from netns '%s': %s\n",
+ tundev->netns_name, strerror(errno));
+ goto err_close_tun;
+ }
+ LOGTUN(tundev, LOGL_INFO, "Open tun: Back from netns '%s'\n",
+ tundev->netns_name);
+ }
+
+ rc = osmo_netdev_set_netns_name(tundev->netdev, tundev->netns_name);
+ if (rc < 0)
+ goto err_close_tun;
+ rc = osmo_netdev_set_ifindex(tundev->netdev, tundev->ifindex);
+ if (rc < 0)
+ goto err_close_tun;
+
+ rc = osmo_netdev_register(tundev->netdev);
+ if (rc < 0)
+ goto err_close_tun;
+
+ rc = osmo_fd_register(&tundev->wqueue.bfd);
+ if (rc < 0)
+ goto err_unregister_netdev;
+
+ tundev->opened = true;
+ return 0;
+
+err_unregister_netdev:
+ osmo_netdev_unregister(tundev->netdev);
+err_close_tun:
+ close(tundev->wqueue.bfd.fd);
+ tundev->wqueue.bfd.fd = -1;
+err_restore_ns:
+ if (tundev->netns_name)
+ osmo_netns_switch_exit(&switch_state);
+err_close_netns_fd:
+ if (netns_fd >= 0)
+ close(netns_fd);
+ return rc;
+}
+
+/*! Close the tunnel device owned by the tundev object.
+ * \param[in] tundev The tundev object to close
+ * \returns 0 on success; negative on error
+ */
+int osmo_tundev_close(struct osmo_tundev *tundev)
+{
+ if (!tundev->opened)
+ return -EALREADY;
+
+ osmo_wqueue_clear(&tundev->wqueue);
+ if (tundev->wqueue.bfd.fd != -1) {
+ osmo_fd_unregister(&tundev->wqueue.bfd);
+ close(tundev->wqueue.bfd.fd);
+ tundev->wqueue.bfd.fd = -1;
+ }
+
+ osmo_netdev_unregister(tundev->netdev);
+ if (tundev->dev_name_dynamic) {
+ TALLOC_FREE(tundev->dev_name);
+ tundev->dev_name_dynamic = false;
+ }
+ tundev->opened = false;
+ return 0;
+}
+
+/*! Retrieve whether the tundev object is in "opened" state.
+ * \param[in] tundev The tundev object to check
+ * \returns true if in state "opened"; false otherwise
+ */
+bool osmo_tundev_is_open(struct osmo_tundev *tundev)
+{
+ return tundev->opened;
+}
+
+/*! Set private user data pointer on the tundev object.
+ * \param[in] tundev The tundev object where the field is set
+ */
+void osmo_tundev_set_priv_data(struct osmo_tundev *tundev, void *priv_data)
+{
+ tundev->priv_data = priv_data;
+}
+
+/*! Get private user data pointer from the tundev object.
+ * \param[in] tundev The tundev object from where to retrieve the field
+ * \returns The current value of the priv_data field.
+ */
+void *osmo_tundev_get_priv_data(struct osmo_tundev *tundev)
+{
+ return tundev->priv_data;
+}
+
+/*! Set data_ind_cb callback, called when a new packet is received on the tun interface.
+ * \param[in] tundev The tundev object where the field is set
+ * \param[in] data_ind_cb the user provided function to be called when a new packet is received
+ */
+void osmo_tundev_set_data_ind_cb(struct osmo_tundev *tundev, osmo_tundev_data_ind_cb_t data_ind_cb)
+{
+ tundev->data_ind_cb = data_ind_cb;
+}
+
+/*! Get name used to identify the tundev object.
+ * \param[in] tundev The tundev object from where to retrieve the field
+ * \returns The current value of the name used to identify the tundev object
+ */
+const char *osmo_tundev_get_name(const struct osmo_tundev *tundev)
+{
+ return tundev->name;
+}
+
+/*! Set name used to name the tunnel interface created by the tundev object.
+ * \param[in] tundev The tundev object where the field is set
+ * \param[in] dev_name The tunnel interface name to use
+ * \returns 0 on success; negative on error
+ *
+ * This is used during osmo_tundev_open() time, and hence shouldn't be changed
+ * when the tundev object is in "opened" state.
+ * If left as NULL (default), the system will pick a suitable name during
+ * osmo_tundev_open(), and the field will be updated to the system-selected
+ * name, which can be retrieved later with osmo_tundev_get_dev_name().
+ */
+int osmo_tundev_set_dev_name(struct osmo_tundev *tundev, const char *dev_name)
+{
+ if (tundev->opened)
+ return -EALREADY;
+ osmo_talloc_replace_string(tundev, &tundev->dev_name, dev_name);
+ tundev->dev_name_dynamic = false;
+ return 0;
+}
+
+/*! Get name used to name the tunnel interface created by the tundev object
+ * \param[in] tundev The tundev object from where to retrieve the field
+ * \returns The current value of the configured tunnel interface name to use
+ */
+const char *osmo_tundev_get_dev_name(const struct osmo_tundev *tundev)
+{
+ return tundev->dev_name;
+}
+
+/*! Set name of the network namespace to use when opening the tunnel interface
+ * \param[in] tundev The tundev object where the field is set
+ * \param[in] netns_name The network namespace to use during tunnel interface creation
+ * \returns 0 on success; negative on error
+ *
+ * This is used during osmo_tundev_open() time, and hence shouldn't be changed
+ * when the tundev object is in "opened" state.
+ * If left as NULL (default), the system will stay in the current network namespace.
+ */
+int osmo_tundev_set_netns_name(struct osmo_tundev *tundev, const char *netns_name)
+{
+ if (tundev->opened)
+ return -EALREADY;
+ osmo_talloc_replace_string(tundev, &tundev->netns_name, netns_name);
+ return 0;
+}
+
+/*! Get name of network namespace used when opening the tunnel interface
+ * \param[in] tundev The tundev object from where to retrieve the field
+ * \returns The current value of the configured network namespace
+ */
+const char *osmo_tundev_get_netns_name(const struct osmo_tundev *tundev)
+{
+ return tundev->netns_name;
+}
+
+/*! Get netdev managing the tunnel interface of tundev
+ * \param[in] tundev The tundev object from where to retrieve the field
+ * \returns The netdev objet managing the tun interface
+ */
+struct osmo_netdev *osmo_tundev_get_netdev(struct osmo_tundev *tundev)
+{
+ return tundev->netdev;
+}
+
+/*! Submit a packet to the tunnel device managed by the tundev object
+ * \param[in] tundev The tundev object owning the tunnel device where to inject the packet
+ * \param[in] msg The msgb containg the packet to transfer
+ * \returns The current value of the configured network namespace
+ *
+ * This function takes the ownership of msg in all cases.
+ */
+int osmo_tundev_send(struct osmo_tundev *tundev, struct msgb *msg)
+{
+ int rc = osmo_wqueue_enqueue(&tundev->wqueue, msg);
+ if (rc < 0) {
+ LOGTUN(tundev, LOGL_ERROR, "Failed to enqueue the packet\n");
+ msgb_free(msg);
+ return rc;
+ }
+ return rc;
+}
+
+
+#endif /* (!EMBEDDED) */
+
+/*! @} */
diff --git a/src/use_count.c b/src/core/use_count.c
index d07e47fc..9714403d 100644
--- a/src/use_count.c
+++ b/src/core/use_count.c
@@ -19,10 +19,6 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <errno.h>
@@ -239,6 +235,8 @@ int _osmo_use_count_get_put(struct osmo_use_count *uc, const char *use, int32_t
{
struct osmo_use_count_entry *e;
int32_t old_use_count;
+ if (!uc)
+ return -EINVAL;
if (!change)
return 0;
diff --git a/src/utils.c b/src/core/utils.c
index f5896c41..1ec940d0 100644
--- a/src/utils.c
+++ b/src/core/utils.c
@@ -17,10 +17,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
@@ -151,13 +147,15 @@ uint8_t osmo_char2bcd(char c)
*/
int osmo_bcd2str(char *dst, size_t dst_size, const uint8_t *bcd, int start_nibble, int end_nibble, bool allow_hex)
{
- char *dst_end = dst + dst_size - 1;
+ char *dst_end;
int nibble_i;
int rc = 0;
if (!dst || dst_size < 1 || start_nibble < 0)
return -ENOMEM;
+ dst_end = dst + dst_size - 1;
+
for (nibble_i = start_nibble; nibble_i < end_nibble && dst < dst_end; nibble_i++, dst++) {
uint8_t nibble = bcd[nibble_i >> 1];
if ((nibble_i & 1))
@@ -206,7 +204,7 @@ int osmo_str2bcd(uint8_t *dst, size_t dst_size, const char *digits, int start_ni
if (end_nibble & 1)
end_nibble++;
}
- if ((end_nibble / 2) > dst_size)
+ if ((unsigned int) (end_nibble / 2) > dst_size)
return -ENOMEM;
for (nibble_i = start_nibble; nibble_i < end_nibble; nibble_i++) {
@@ -241,7 +239,7 @@ int osmo_str2bcd(uint8_t *dst, size_t dst_size, const char *digits, int start_ni
* \param[in] max_len maximum space in output buffer
* \returns number of parsed octets, or -1 on error
*/
-int osmo_hexparse(const char *str, uint8_t *b, int max_len)
+int osmo_hexparse(const char *str, uint8_t *b, unsigned int max_len)
{
char c;
@@ -316,7 +314,7 @@ const char *osmo_hexdump_buf(char *out_buf, size_t out_buf_size, const unsigned
for (i = 0; i < len; i++) {
const char *delimp = delim;
int len_remain = out_buf_size - (cur - out_buf) - 1;
- if (len_remain < (2 + delim_len)
+ if (len_remain < (int) (2 + delim_len)
&& !(!delim_after_last && i == (len - 1) && len_remain >= 2))
break;
@@ -344,7 +342,7 @@ const char *osmo_hexdump_buf(char *out_buf, size_t out_buf_size, const unsigned
*/
char *osmo_ubit_dump_buf(char *buf, size_t buf_len, const uint8_t *bits, unsigned int len)
{
- int i;
+ unsigned int i;
if (len > buf_len-1)
len = buf_len-1;
@@ -407,9 +405,6 @@ char *osmo_hexdump(const unsigned char *buf, int len)
*
* This function will print a sequence of bytes as hexadecimal numbers,
* adding one space character between each byte (e.g. "1a ef d9")
- *
- * The maximum size of the output buffer is 4096 bytes, i.e. the maximum
- * number of input bytes that can be printed in one call is 1365!
*/
char *osmo_hexdump_c(const void *ctx, const unsigned char *buf, int len)
{
@@ -446,9 +441,6 @@ char *osmo_hexdump_nospc(const unsigned char *buf, int len)
*
* This function will print a sequence of bytes as hexadecimal numbers,
* without any space character between each byte (e.g. "1aefd9")
- *
- * The maximum size of the output buffer is 4096 bytes, i.e. the maximum
- * number of input bytes that can be printed in one call is 2048!
*/
char *osmo_hexdump_nospc_c(const void *ctx, const unsigned char *buf, int len)
{
@@ -469,7 +461,7 @@ char *osmo_osmo_hexdump_nospc(const unsigned char *buf, int len)
__attribute__((weak, alias("osmo_hexdump_nospc")));
#endif
-#include "../config.h"
+#include "config.h"
#ifdef HAVE_CTYPE_H
#include <ctype.h>
/*! Convert an entire string to lower case
@@ -687,7 +679,7 @@ bool osmo_identifier_valid(const char *str)
* To guarantee passing osmo_separated_identifiers_valid(), replace_with must not itself be an illegal character. If in
* doubt, use '-'.
* \param[inout] str Identifier to sanitize, must be nul terminated and in a writable buffer.
- * \param[in] sep_chars Additional characters that are allowed besides osmo_identifier_illegal_chars.
+ * \param[in] sep_chars Additional characters that are to be replaced besides osmo_identifier_illegal_chars.
* \param[in] replace_with Replace any illegal characters with this character.
*/
void osmo_identifier_sanitize_buf(char *str, const char *sep_chars, char replace_with)
@@ -764,7 +756,7 @@ int osmo_print_n(char *buf, size_t bufsize, const char *str, size_t n)
* the non-legacy format also escapes those as "\xNN" sequences.
* \return Number of characters that would be written if bufsize were large enough excluding '\0' (like snprintf()).
*/
-static size_t _osmo_escape_str_buf(char *buf, size_t bufsize, const char *str, int in_len, bool legacy_format)
+static int _osmo_escape_str_buf(char *buf, size_t bufsize, const char *str, int in_len, bool legacy_format)
{
struct osmo_strbuf sb = { .buf = buf, .len = bufsize };
int in_pos = 0;
@@ -836,6 +828,18 @@ done:
* \param[in] bufsize sizeof(buf).
* \param[in] str A string that may contain any characters.
* \param[in] in_len Pass -1 to print until nul char, or >= 0 to force a length (also past nul chars).
+ * \return Number of characters that would be written if bufsize were large enough excluding '\0' (like snprintf()).
+ */
+int osmo_escape_str_buf3(char *buf, size_t bufsize, const char *str, int in_len)
+{
+ return _osmo_escape_str_buf(buf, bufsize, str, in_len, false);
+}
+
+/*! Return the string with all non-printable characters escaped.
+ * \param[out] buf string buffer to write escaped characters to.
+ * \param[in] bufsize sizeof(buf).
+ * \param[in] str A string that may contain any characters.
+ * \param[in] in_len Pass -1 to print until nul char, or >= 0 to force a length (also past nul chars).
* \return The output buffer (buf).
*/
char *osmo_escape_str_buf2(char *buf, size_t bufsize, const char *str, int in_len)
@@ -893,6 +897,20 @@ static size_t _osmo_quote_str_buf(char *buf, size_t bufsize, const char *str, in
return sb.chars_needed;
}
+/*! Like osmo_escape_str_buf3(), but returns double-quotes around a string, or "NULL" for a NULL string.
+ * This allows passing any char* value and get its C representation as string.
+ * The function signature is suitable for OSMO_STRBUF_APPEND_NOLEN().
+ * \param[out] buf string buffer to write escaped characters to.
+ * \param[in] bufsize sizeof(buf).
+ * \param[in] str A string that may contain any characters.
+ * \param[in] in_len Pass -1 to print until nul char, or >= 0 to force a length.
+ * \return Number of characters that would be written if bufsize were large enough excluding '\0' (like snprintf()).
+ */
+int osmo_quote_str_buf3(char *buf, size_t bufsize, const char *str, int in_len)
+{
+ return _osmo_quote_str_buf(buf, bufsize, str, in_len, false);
+}
+
/*! Like osmo_escape_str_buf2(), but returns double-quotes around a string, or "NULL" for a NULL string.
* This allows passing any char* value and get its C representation as string.
* The function signature is suitable for OSMO_STRBUF_APPEND_NOLEN().
@@ -1195,6 +1213,51 @@ char osmo_luhn(const char* in, int in_len)
return (sum * 9) % 10 + '0';
}
+/*! Remove up to N chars from the end of an osmo_strbuf.
+ * |--char-count---| - - chars_needed - - |
+ * |<---------drop----------|
+ */
+void osmo_strbuf_drop_tail(struct osmo_strbuf *sb, size_t n_chars)
+{
+ size_t drop_n;
+ if (sb->pos <= sb->buf)
+ return;
+ drop_n = OSMO_MIN(sb->chars_needed, n_chars);
+ sb->chars_needed -= drop_n;
+ /* chars_needed was reduced by n_chars, which may have been entirely behind the end of a full buffer, within the
+ * hypothetical chars_needed. Modify the buffer tail pos only if the buffer is not or longer full now. */
+ if (sb->chars_needed >= OSMO_STRBUF_CHAR_COUNT(*sb))
+ return;
+ sb->pos = sb->buf + sb->chars_needed;
+ *sb->pos = '\0';
+}
+
+/*! Let osmo_strbuf know that n_chars characters (excluding nul) were written to the end of the buffer.
+ * If sb is nonempty, the n_chars are assumed to have been written to sb->pos. If sb is still empty and pos == NULL, the
+ * n_chars are assumed to have been written to the start of the buffer.
+ * Advance sb->pos and sb->chars_needed by at most n_chars, or up to sb->len - 1.
+ * Ensure nul termination. */
+void osmo_strbuf_added_tail(struct osmo_strbuf *sb, size_t n_chars)
+{
+ /* On init of an osmo_strbuf, sb->pos == NULL, which is defined as semantically identical to pointing at the
+ * start of the buffer. A caller may just write to the buffer and call osmo_strbuf_added_tail(), in which case
+ * still pos == NULL. pos != NULL happens as soon as the first OSMO_STRBUF_*() API has acted on the strbuf. */
+ if (!sb->pos)
+ sb->pos = sb->buf;
+ sb->chars_needed += n_chars;
+ /* first get remaining space, not counting trailing nul; but safeguard against empty buffer */
+ size_t n_added = OSMO_STRBUF_REMAIN(*sb);
+ if (n_added)
+ n_added--;
+ /* do not add more than fit in sb->len, still ensuring nul termination */
+ n_added = OSMO_MIN(n_added, n_chars);
+ if (n_added)
+ sb->pos += n_added;
+ /* when a strbuf is full, sb->pos may point after the final nul, so nul terminate only when pos is valid. */
+ if (sb->pos < sb->buf + sb->len)
+ *sb->pos = '\0';
+}
+
/*! Compare start of a string.
* This is an optimisation of 'strstr(str, startswith_str) == str' because it doesn't search through the entire string.
* \param str (Longer) string to compare.
@@ -1232,7 +1295,7 @@ int osmo_float_str_to_int(int64_t *val, const char *str, unsigned int precision)
int64_t precision_factor;
int64_t integer_max;
int64_t decimal_max;
- int i;
+ unsigned int i;
OSMO_ASSERT(val);
*val = 0;
@@ -1256,7 +1319,7 @@ int osmo_float_str_to_int(int64_t *val, const char *str, unsigned int precision)
if (point)
return -EINVAL;
point = p;
- } else if (!isdigit(*p))
+ } else if (!isdigit((unsigned char)*p))
return -EINVAL;
}
@@ -1405,4 +1468,119 @@ char *osmo_int_to_float_str_c(void *ctx, int64_t val, unsigned int precision)
OSMO_NAME_C_IMPL(ctx, 16, "ERROR", osmo_int_to_float_str_buf, val, precision)
}
+/*! Convert a string of a number to int64_t, including all common strtoll() validity checks.
+ * It's not so trivial to call strtoll() and properly verify that the input string was indeed a valid number string.
+ * \param[out] result Buffer for the resulting integer number, or NULL if the caller is only interested in the
+ * validation result (returned rc).
+ * \param[in] str The string to convert.
+ * \param[in] base The integer base, i.e. 10 for decimal numbers or 16 for hexadecimal, as in strtoll().
+ * \param[in] min_val The smallest valid number expected in the string.
+ * \param[in] max_val The largest valid number expected in the string.
+ * \return 0 on success, -EOVERFLOW if the number in the string exceeds int64_t, -ENOTSUPP if the base is not supported,
+ * -ERANGE if the converted number exceeds the range [min_val..max_val] but is still within int64_t range, -E2BIG if
+ * surplus characters follow after the number, -EINVAL if the string does not contain a number. In case of -ERANGE and
+ * -E2BIG, the converted number is still accurately returned in result. In case of -EOVERFLOW, the returned value is
+ * clamped to INT64_MIN..INT64_MAX.
+ */
+int osmo_str_to_int64(int64_t *result, const char *str, int base, int64_t min_val, int64_t max_val)
+{
+ long long int val;
+ char *endptr;
+ if (result)
+ *result = 0;
+ if (!str || !*str)
+ return -EINVAL;
+ errno = 0;
+ val = strtoll(str, &endptr, base);
+ /* In case the number string exceeds long long int range, strtoll() clamps the returned value to LLONG_MIN or
+ * LLONG_MAX. Make sure of the same here with respect to int64_t. */
+ if (val < INT64_MIN) {
+ if (result)
+ *result = INT64_MIN;
+ return -ERANGE;
+ }
+ if (val > INT64_MAX) {
+ if (result)
+ *result = INT64_MAX;
+ return -ERANGE;
+ }
+ if (result)
+ *result = (int64_t)val;
+ switch (errno) {
+ case 0:
+ break;
+ case ERANGE:
+ return -EOVERFLOW;
+ default:
+ case EINVAL:
+ return -ENOTSUP;
+ }
+ if (!endptr || *endptr) {
+ /* No chars were converted */
+ if (endptr == str)
+ return -EINVAL;
+ /* Or there are surplus chars after the converted number */
+ return -E2BIG;
+ }
+ if (val < min_val || val > max_val)
+ return -ERANGE;
+ return 0;
+}
+
+/*! Convert a string of a number to int, including all common strtoll() validity checks.
+ * Same as osmo_str_to_int64() but using the plain int data type.
+ * \param[out] result Buffer for the resulting integer number, or NULL if the caller is only interested in the
+ * validation result (returned rc).
+ * \param[in] str The string to convert.
+ * \param[in] base The integer base, i.e. 10 for decimal numbers or 16 for hexadecimal, as in strtoll().
+ * \param[in] min_val The smallest valid number expected in the string.
+ * \param[in] max_val The largest valid number expected in the string.
+ * \return 0 on success, -EOVERFLOW if the number in the string exceeds int range, -ENOTSUPP if the base is not supported,
+ * -ERANGE if the converted number exceeds the range [min_val..max_val] but is still within int range, -E2BIG if
+ * surplus characters follow after the number, -EINVAL if the string does not contain a number. In case of -ERANGE and
+ * -E2BIG, the converted number is still accurately returned in result. In case of -EOVERFLOW, the returned value is
+ * clamped to INT_MIN..INT_MAX.
+ */
+int osmo_str_to_int(int *result, const char *str, int base, int min_val, int max_val)
+{
+ int64_t val;
+ int rc = osmo_str_to_int64(&val, str, base, min_val, max_val);
+ /* In case the number string exceeds long long int range, strtoll() clamps the returned value to LLONG_MIN or
+ * LLONG_MAX. Make sure of the same here with respect to int. */
+ if (val < INT_MIN) {
+ if (result)
+ *result = INT_MIN;
+ return -EOVERFLOW;
+ }
+ if (val > INT_MAX) {
+ if (result)
+ *result = INT_MAX;
+ return -EOVERFLOW;
+ }
+ if (result)
+ *result = (int)val;
+ return rc;
+}
+
+/*! Replace a string using talloc and release its prior content (if any).
+ * This is a format string capable equivalent of osmo_talloc_replace_string().
+ * \param[in] ctx Talloc context to use for allocation.
+ * \param[out] dst Pointer to string, will be updated with ptr to new string.
+ * \param[in] fmt Format string that will be copied to newly allocated string. */
+void osmo_talloc_replace_string_fmt(void *ctx, char **dst, const char *fmt, ...)
+{
+ char *name = NULL;
+
+ if (fmt != NULL) {
+ va_list ap;
+
+ va_start(ap, fmt);
+ name = talloc_vasprintf(ctx, fmt, ap);
+ va_end(ap);
+ }
+
+ talloc_free(*dst);
+ *dst = name;
+}
+
/*! @} */
diff --git a/src/write_queue.c b/src/core/write_queue.c
index b208b25e..8fb73a67 100644
--- a/src/write_queue.c
+++ b/src/core/write_queue.c
@@ -16,10 +16,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <errno.h>
@@ -106,7 +102,7 @@ void osmo_wqueue_init(struct osmo_wqueue *queue, int max_length)
/*! Enqueue a new \ref msgb into a write queue (without logging full queue events)
* \param[in] queue Write queue to be used
* \param[in] data to-be-enqueued message buffer
- * \returns 0 on success; negative on error
+ * \returns 0 on success; negative on error (MESSAGE NOT FREED IN CASE OF ERROR).
*/
int osmo_wqueue_enqueue_quiet(struct osmo_wqueue *queue, struct msgb *data)
{
@@ -122,7 +118,7 @@ int osmo_wqueue_enqueue_quiet(struct osmo_wqueue *queue, struct msgb *data)
/*! Enqueue a new \ref msgb into a write queue
* \param[in] queue Write queue to be used
* \param[in] data to-be-enqueued message buffer
- * \returns 0 on success; negative on error
+ * \returns 0 on success; negative on error (MESSAGE NOT FREED IN CASE OF ERROR).
*/
int osmo_wqueue_enqueue(struct osmo_wqueue *queue, struct msgb *data)
{
@@ -151,4 +147,24 @@ void osmo_wqueue_clear(struct osmo_wqueue *queue)
queue->bfd.when &= ~OSMO_FD_WRITE;
}
+/*! Update write queue length & drop excess messages.
+ * \param[in] queue linked list header of message queue
+ * \param[in] len new max. wqueue length
+ * \returns Number of messages dropped.
+ *
+ * Messages beyond the new maximum message queue size will be dropped.
+ */
+size_t osmo_wqueue_set_maxlen(struct osmo_wqueue *queue, unsigned int len)
+{
+ size_t dropped_msgs = 0;
+ struct msgb *msg;
+ queue->max_length = len;
+ while (queue->current_length > queue->max_length) {
+ msg = msgb_dequeue_count(&queue->msg_queue, &queue->current_length);
+ msgb_free(msg);
+ dropped_msgs++;
+ }
+ return dropped_msgs;
+}
+
/*! @} */
diff --git a/src/ctrl/Makefile.am b/src/ctrl/Makefile.am
index ca642869..f9e34333 100644
--- a/src/ctrl/Makefile.am
+++ b/src/ctrl/Makefile.am
@@ -1,9 +1,10 @@
# 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 modifications: https://www.gnu.org/software/libtool/manual/html_node/Versioning.html
-LIBVERSION=4:0:4
+LIBVERSION=8:1:8
-AM_CFLAGS = -Wall $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)/include $(TALLOC_CFLAGS)
+AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_builddir)/include -I$(top_builddir)
+AM_CFLAGS = -Wall $(TALLOC_CFLAGS)
if ENABLE_CTRL
lib_LTLIBRARIES = libosmoctrl.la
@@ -12,7 +13,7 @@ libosmoctrl_la_SOURCES = control_cmd.c control_if.c fsm_ctrl_commands.c
libosmoctrl_la_LDFLAGS = $(LTLDFLAGS_OSMOCTRL) -version-info $(LIBVERSION) -no-undefined
libosmoctrl_la_LIBADD = $(TALLOC_LIBS) \
- $(top_builddir)/src/libosmocore.la \
+ $(top_builddir)/src/core/libosmocore.la \
$(top_builddir)/src/gsm/libosmogsm.la \
$(top_builddir)/src/vty/libosmovty.la
@@ -21,5 +22,6 @@ libosmoctrl_la_SOURCES += control_vty.c
endif
EXTRA_DIST = libosmoctrl.map
+EXTRA_libosmoctrl_la_DEPENDENCIES = libosmoctrl.map
endif
diff --git a/src/ctrl/control_cmd.c b/src/ctrl/control_cmd.c
index 33496bd8..db205510 100644
--- a/src/ctrl/control_cmd.c
+++ b/src/ctrl/control_cmd.c
@@ -20,10 +20,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <ctype.h>
@@ -35,6 +31,7 @@
#include <unistd.h>
#include <osmocom/ctrl/control_cmd.h>
+#include <osmocom/ctrl/control_if.h>
#include <osmocom/core/msgb.h>
#include <osmocom/core/talloc.h>
@@ -207,13 +204,23 @@ failure:
}
/*! Install a given command definition at a given CTRL node.
- * \param[in] node CTRL node at whihc \a cmd is to be installed
+ * \param[in] node CTRL node at which \a cmd is to be installed
* \param[in] cmd command definition to be installed
* \returns 0 on success; negative on error */
int ctrl_cmd_install(enum ctrl_node_type node, struct ctrl_cmd_element *cmd)
{
vector cmds_vec;
+ /* If this assert triggers, it means the program forgot to initialize
+ * the CTRL interface first by calling ctrl_handle_alloc(2)() directly
+ * or indirectly through ctrl_interface_setup_dynip(2)()
+ */
+ if (!ctrl_node_vec) {
+ LOGP(DLCTRL, LOGL_ERROR,
+ "ctrl_handle must be initialized prior to installing cmds.\n");
+ return -ENODEV;
+ }
+
cmds_vec = vector_lookup_ensure(ctrl_node_vec, node);
if (!cmds_vec) {
@@ -516,92 +523,61 @@ err:
* \returns callee-allocated message buffer containing the encoded \a cmd; NULL on error */
struct msgb *ctrl_cmd_make(struct ctrl_cmd *cmd)
{
- struct msgb *msg;
+ struct msgb *msg = NULL;
+ char *strbuf;
+ size_t len;
const char *type;
- char *tmp;
if (!cmd->id)
return NULL;
- msg = msgb_alloc_headroom(4096, 128, "ctrl command make");
- if (!msg)
- return NULL;
-
type = get_value_string(ctrl_type_vals, cmd->type);
switch (cmd->type) {
case CTRL_TYPE_GET:
if (!cmd->variable)
- goto err;
-
- tmp = talloc_asprintf(cmd, "%s %s %s", type, cmd->id, cmd->variable);
- if (!tmp) {
- LOGP(DLCTRL, LOGL_ERROR, "Failed to allocate cmd.\n");
- goto err;
- }
-
- msg->l2h = msgb_put(msg, strlen(tmp));
- memcpy(msg->l2h, tmp, strlen(tmp));
- talloc_free(tmp);
+ return NULL;
+ strbuf = talloc_asprintf(cmd, "%s %s %s", type, cmd->id, cmd->variable);
break;
case CTRL_TYPE_SET:
if (!cmd->variable || !cmd->value)
- goto err;
-
- tmp = talloc_asprintf(cmd, "%s %s %s %s", type, cmd->id, cmd->variable,
- cmd->value);
- if (!tmp) {
- LOGP(DLCTRL, LOGL_ERROR, "Failed to allocate cmd.\n");
- goto err;
- }
-
- msg->l2h = msgb_put(msg, strlen(tmp));
- memcpy(msg->l2h, tmp, strlen(tmp));
- talloc_free(tmp);
+ return NULL;
+ strbuf = talloc_asprintf(cmd, "%s %s %s %s", type, cmd->id,
+ cmd->variable, cmd->value);
break;
case CTRL_TYPE_GET_REPLY:
case CTRL_TYPE_SET_REPLY:
case CTRL_TYPE_TRAP:
if (!cmd->variable || !cmd->reply)
- goto err;
-
- tmp = talloc_asprintf(cmd, "%s %s %s %s", type, cmd->id, cmd->variable,
- cmd->reply);
- if (!tmp) {
- LOGP(DLCTRL, LOGL_ERROR, "Failed to allocate cmd.\n");
- goto err;
- }
-
- msg->l2h = msgb_put(msg, strlen(tmp));
- memcpy(msg->l2h, tmp, strlen(tmp));
- talloc_free(tmp);
+ return NULL;
+ strbuf = talloc_asprintf(cmd, "%s %s %s %s", type, cmd->id,
+ cmd->variable, cmd->reply);
break;
case CTRL_TYPE_ERROR:
if (!cmd->reply)
- goto err;
-
- tmp = talloc_asprintf(cmd, "%s %s %s", type, cmd->id,
- cmd->reply);
- if (!tmp) {
- LOGP(DLCTRL, LOGL_ERROR, "Failed to allocate cmd.\n");
- goto err;
- }
-
- msg->l2h = msgb_put(msg, strlen(tmp));
- memcpy(msg->l2h, tmp, strlen(tmp));
- talloc_free(tmp);
+ return NULL;
+ strbuf = talloc_asprintf(cmd, "%s %s %s", type, cmd->id, cmd->reply);
break;
default:
LOGP(DLCTRL, LOGL_NOTICE, "Unknown command type %i\n", cmd->type);
- goto err;
- break;
+ return NULL;
}
- return msg;
+ if (!strbuf) {
+ LOGP(DLCTRL, LOGL_ERROR, "Failed to allocate cmd.\n");
+ goto ret;
+ }
+ len = strlen(strbuf);
-err:
- msgb_free(msg);
- return NULL;
+ msg = msgb_alloc_headroom(len + 128, 128, "ctrl ERROR command make");
+ if (!msg)
+ goto ret;
+ msg->l2h = msgb_put(msg, len);
+ memcpy(msg->l2h, strbuf, len);
+
+ret:
+ talloc_free(strbuf);
+ return msg;
}
/*! Build a deferred control command state and keep it the per-connection list of deferred commands.
@@ -672,7 +648,7 @@ int ctrl_cmd_def_send(struct ctrl_cmd_def *cd)
cmd->type = CTRL_TYPE_ERROR;
}
- rc = ctrl_cmd_send(&cmd->ccon->write_queue, cmd);
+ rc = ctrl_cmd_send2(cmd->ccon, cmd);
talloc_free(cmd);
llist_del(&cd->list);
diff --git a/src/ctrl/control_if.c b/src/ctrl/control_if.c
index ce2e3676..c265c3a9 100644
--- a/src/ctrl/control_if.c
+++ b/src/ctrl/control_if.c
@@ -20,10 +20,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include "config.h"
@@ -35,6 +31,7 @@
#include <string.h>
#include <time.h>
#include <unistd.h>
+#include <limits.h>
#include <arpa/inet.h>
@@ -50,9 +47,11 @@
#include <osmocom/ctrl/control_cmd.h>
#include <osmocom/ctrl/control_if.h>
+#include <osmocom/ctrl/control_vty.h>
#include <osmocom/core/msgb.h>
#include <osmocom/core/rate_ctr.h>
+#include <osmocom/core/stat_item.h>
#include <osmocom/core/select.h>
#include <osmocom/core/counter.h>
#include <osmocom/core/talloc.h>
@@ -82,26 +81,22 @@ static LLIST_HEAD(ctrl_lookup_helpers);
* \returns 1 on success; 0 in case of error */
int ctrl_parse_get_num(vector vline, int i, long *num)
{
- char *token, *tmp;
+ char *token;
+ int64_t val;
if (i >= vector_active(vline))
return 0;
token = vector_slot(vline, i);
- errno = 0;
- if (token[0] == '\0')
- return 0;
-
- *num = strtol(token, &tmp, 10);
- if (tmp[0] != '\0' || errno != 0)
+ if (osmo_str_to_int64(&val, token, 10, LONG_MIN, LONG_MAX))
return 0;
-
+ *num = (long)val;
return 1;
}
/*! Send a CTRL command to all connections.
* \param[in] ctrl global control handle
- * \param[in] cmd command to send to all connections in \ctrl
+ * \param[in] cmd command to send to all connections in ctrl
* \returns number of times the command has been sent */
int ctrl_cmd_send_to_all(struct ctrl_handle *ctrl, struct ctrl_cmd *cmd)
{
@@ -111,18 +106,28 @@ int ctrl_cmd_send_to_all(struct ctrl_handle *ctrl, struct ctrl_cmd *cmd)
llist_for_each_entry(ccon, &ctrl->ccon_list, list_entry) {
if (ccon == cmd->ccon)
continue;
- if (ctrl_cmd_send(&ccon->write_queue, cmd))
+ if (ctrl_cmd_send2(ccon, cmd))
ret++;
}
return ret;
}
-/*! Encode a CTRL command and append it to the given write queue
+/*! Encode a CTRL command and append it to the given ctrl_connection
* \param[inout] queue write queue to which encoded \a cmd shall be appended
* \param[in] cmd decoded command representation
* \returns 0 in case of success; negative on error */
int ctrl_cmd_send(struct osmo_wqueue *queue, struct ctrl_cmd *cmd)
{
+ struct ctrl_connection *ccon = container_of(queue, struct ctrl_connection, write_queue);
+ return ctrl_cmd_send2(ccon, cmd);
+}
+
+/*! Encode a CTRL command and append it to the given ctrl_connection
+ * \param[inout] queue write queue to which encoded \a cmd shall be appended
+ * \param[in] cmd decoded command representation
+ * \returns 0 in case of success; negative on error */
+int ctrl_cmd_send2(struct ctrl_connection *ccon, struct ctrl_cmd *cmd)
+{
int ret;
struct msgb *msg;
@@ -135,7 +140,7 @@ int ctrl_cmd_send(struct osmo_wqueue *queue, struct ctrl_cmd *cmd)
ipa_prepend_header_ext(msg, IPAC_PROTO_EXT_CTRL);
ipa_prepend_header(msg, IPAC_PROTO_OSMO);
- ret = osmo_wqueue_enqueue(queue, msg);
+ ret = osmo_wqueue_enqueue(&ccon->write_queue, msg);
if (ret != 0) {
LOGP(DLCTRL, LOGL_ERROR, "Failed to enqueue the command.\n");
msgb_free(msg);
@@ -221,6 +226,10 @@ int ctrl_cmd_handle(struct ctrl_handle *ctrl, struct ctrl_cmd *cmd,
if (cmd->type == CTRL_TYPE_SET_REPLY ||
cmd->type == CTRL_TYPE_GET_REPLY) {
+ if (ctrl->reply_cb) {
+ ctrl->reply_cb(ctrl, cmd, data);
+ return CTRL_CMD_HANDLED;
+ }
if (strncmp(cmd->reply, "OK", 2) == 0) {
LOGP(DLCTRL, LOGL_DEBUG, "%s <%s> for %s is OK\n",
get_value_string(ctrl_type_vals, cmd->type),
@@ -465,7 +474,7 @@ int ctrl_handle_msg(struct ctrl_handle *ctrl, struct ctrl_connection *ccon, stru
send_reply:
/* There is a reply or error that should be reported back to the sender. */
- ctrl_cmd_send(&ccon->write_queue, cmd);
+ ctrl_cmd_send2(ccon, cmd);
just_free:
talloc_free(cmd);
return 0;
@@ -485,8 +494,14 @@ static int control_write_cb(struct osmo_fd *bfd, struct msgb *msg)
control_close_conn(ccon);
return -EBADF;
}
- if (rc != msg->len)
+ if (rc < 0) {
LOGP(DLCTRL, LOGL_ERROR, "Failed to write message to the CTRL connection.\n");
+ return 0;
+ }
+ if (rc < msg->len) {
+ msgb_pull(msg, rc);
+ return -EAGAIN;
+ }
return 0;
}
@@ -510,6 +525,7 @@ struct ctrl_connection *osmo_ctrl_conn_alloc(void *ctx, void *data)
INIT_LLIST_HEAD(&ccon->def_cmds);
ccon->write_queue.bfd.data = data;
+ ccon->write_queue.bfd.fd = -1;
ccon->write_queue.write_cb = control_write_cb;
ccon->write_queue.read_cb = handle_control_read;
@@ -584,12 +600,12 @@ static uint64_t get_rate_ctr_value(const struct rate_ctr *ctr, int intv, const c
}
}
-static int get_rate_ctr_group_idx(const struct rate_ctr_group *ctrg, int intv, struct ctrl_cmd *cmd)
+static int get_rate_ctr_group_idx(struct rate_ctr_group *ctrg, int intv, struct ctrl_cmd *cmd)
{
unsigned int i;
for (i = 0; i < ctrg->desc->num_ctr; i++) {
ctrl_cmd_reply_printf(cmd, "%s %"PRIu64";", ctrg->desc->ctr_desc[i].name,
- get_rate_ctr_value(&ctrg->ctr[i], intv, ctrg->desc->group_name_prefix));
+ get_rate_ctr_value(rate_ctr_group_get_ctr(ctrg, i), intv, ctrg->desc->group_name_prefix));
if (!cmd->reply) {
cmd->reply = "OOM";
return CTRL_CMD_ERROR;
@@ -718,6 +734,100 @@ static int verify_rate_ctr(struct ctrl_cmd *cmd, const char *value, void *data)
return 0;
}
+/* Expose all stat_item groups on CTRL, as read-only variables of the form:
+ * stat_item.(last|...).group_name.N.item_name
+ * stat_item.(last|...).group_name.by_name.group_idx_name.item_name
+ */
+CTRL_CMD_DEFINE_RO(stat_item, "stat_item *");
+static int get_stat_item(struct ctrl_cmd *cmd, void *data)
+{
+ char *dup;
+ char *saveptr;
+ char *value_type;
+ char *group_name;
+ char *group_idx_str;
+ char *item_name;
+ char *tmp;
+ int32_t val;
+ struct osmo_stat_item_group *statg;
+ const struct osmo_stat_item *stat_item;
+
+ /* cmd will be freed in control_if.c after handling here, so no need to free the dup string. */
+ dup = talloc_strdup(cmd, cmd->variable);
+ if (!dup) {
+ cmd->reply = "OOM";
+ return CTRL_CMD_ERROR;
+ }
+
+ /* Split off the first "stat_item." part */
+ tmp = strtok_r(dup, ".", &saveptr);
+ if (!tmp || strcmp(tmp, "stat_item") != 0)
+ goto format_error;
+
+ /* Split off the "last." part (validated further below) */
+ value_type = strtok_r(NULL, ".", &saveptr);
+ if (!value_type)
+ goto format_error;
+
+ /* Split off the "group_name." part */
+ group_name = strtok_r(NULL, ".", &saveptr);
+ if (!group_name)
+ goto format_error;
+
+ /* Split off the "N." part */
+ group_idx_str = strtok_r(NULL, ".", &saveptr);
+ if (!group_idx_str)
+ goto format_error;
+ if (strcmp(group_idx_str, "by_name") == 0) {
+ /* The index is not given by "N" but by "by_name.foo". Get the "foo" idx-name */
+ group_idx_str = strtok_r(NULL, ".", &saveptr);
+ statg = osmo_stat_item_get_group_by_name_idxname(group_name, group_idx_str);
+ } else {
+ int idx;
+ if (osmo_str_to_int(&idx, group_idx_str, 10, 0, INT_MAX))
+ goto format_error;
+ statg = osmo_stat_item_get_group_by_name_idx(group_name, idx);
+ }
+ if (!statg) {
+ cmd->reply = "Stat group with given name and index not found";
+ return CTRL_CMD_ERROR;
+ }
+
+ /* Split off the "item_name" part */
+ item_name = strtok_r(NULL, ".", &saveptr);
+ if (!item_name)
+ goto format_error;
+ stat_item = osmo_stat_item_get_by_name(statg, item_name);
+ if (!stat_item) {
+ cmd->reply = "No such stat item found";
+ return CTRL_CMD_ERROR;
+ }
+
+ tmp = strtok_r(NULL, "", &saveptr);
+ if (tmp) {
+ cmd->reply = "Garbage after stat item name";
+ return CTRL_CMD_ERROR;
+ }
+
+ if (!strcmp(value_type, "last"))
+ val = osmo_stat_item_get_last(stat_item);
+ else
+ goto format_error;
+
+ cmd->reply = talloc_asprintf(cmd, "%"PRIu32, val);
+ if (!cmd->reply) {
+ cmd->reply = "OOM";
+ return CTRL_CMD_ERROR;
+ }
+
+ return CTRL_CMD_REPLY;
+
+format_error:
+ cmd->reply = "Stat item must be of form stat_item.type.group_name.N.item_name,"
+ " e.g. 'stat_item.last.bsc.0.msc_num:connected'";
+ return CTRL_CMD_ERROR;
+}
+
/* counter */
CTRL_CMD_DEFINE(counter, "counter *");
static int get_counter(struct ctrl_cmd *cmd, void *data)
@@ -779,7 +889,7 @@ static int verify_counter(struct ctrl_cmd *cmd, const char *value, void *data)
struct ctrl_handle *ctrl_interface_setup(void *data, uint16_t port,
ctrl_cmd_lookup lookup)
{
- return ctrl_interface_setup_dynip(data, "127.0.0.1", port, lookup);
+ return ctrl_interface_setup2(data, port, lookup, 0);
}
static int ctrl_initialized = 0;
@@ -805,6 +915,9 @@ static int ctrl_init(unsigned int node_count)
ret = ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_rate_ctr);
if (ret)
goto err_vec;
+ ret = ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_stat_item);
+ if (ret)
+ goto err_vec;
ret = ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_counter);
if (ret)
goto err_vec;
@@ -928,12 +1041,24 @@ struct ctrl_handle *ctrl_interface_setup_dynip(void *data,
return ctrl_interface_setup_dynip2(data, bind_addr, port, lookup, 0);
}
+/*! Initializes CTRL interface using the configured bind addr/port.
+ * \param[in] data Pointer which will be made available to each set_..() get_..() verify_..() control command function
+ * \param[in] default_port TCP port number to bind to if not explicitly configured
+ * \param[in] lookup Lookup function pointer, can be NULL
+ * \param[in] node_count Number of CTRL nodes to allocate, 0 for default.
+ */
+struct ctrl_handle *ctrl_interface_setup2(void *data, uint16_t default_port, ctrl_cmd_lookup lookup,
+ unsigned int node_count)
+{
+ return ctrl_interface_setup_dynip2(data, ctrl_vty_get_bind_addr(), ctrl_vty_get_bind_port(default_port), lookup,
+ node_count);
+}
/*! Install a lookup helper function for control nodes
* This function is used by e.g. library code to install lookup helpers
* for additional nodes in the control interface.
* \param[in] lookup The lookup helper function
- * \retuns - on success; negative on error.
+ * \returns - on success; negative on error.
*/
int ctrl_lookup_register(ctrl_cmd_lookup lookup)
{
diff --git a/src/ctrl/control_vty.c b/src/ctrl/control_vty.c
index e141a4c8..a7ebddc2 100644
--- a/src/ctrl/control_vty.c
+++ b/src/ctrl/control_vty.c
@@ -17,10 +17,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <stdlib.h>
@@ -30,16 +26,20 @@
static void *ctrl_vty_ctx = NULL;
static const char *ctrl_vty_bind_addr = NULL;
+/* Port the CTRL should bind to: -1 means not configured */
+static int ctrl_bind_port = -1;
DEFUN(cfg_ctrl_bind_addr,
cfg_ctrl_bind_addr_cmd,
- "bind A.B.C.D",
+ "bind A.B.C.D [<0-65535>]",
"Set bind address to listen for Control connections\n"
- "Local IP address (default 127.0.0.1)\n")
+ "Local IP address (default 127.0.0.1)\n"
+ "Local TCP port number\n")
{
talloc_free((char*)ctrl_vty_bind_addr);
ctrl_vty_bind_addr = NULL;
ctrl_vty_bind_addr = talloc_strdup(ctrl_vty_ctx, argv[0]);
+ ctrl_bind_port = argc > 1 ? atoi(argv[1]) : -1;
return CMD_SUCCESS;
}
@@ -50,6 +50,11 @@ const char *ctrl_vty_get_bind_addr(void)
return ctrl_vty_bind_addr;
}
+uint16_t ctrl_vty_get_bind_port(uint16_t default_port)
+{
+ return ctrl_bind_port >= 0 ? ctrl_bind_port : default_port;
+}
+
static struct cmd_node ctrl_node = {
L_CTRL_NODE,
"%s(config-ctrl)# ",
diff --git a/src/ctrl/libosmoctrl.map b/src/ctrl/libosmoctrl.map
index f995467b..3418e620 100644
--- a/src/ctrl/libosmoctrl.map
+++ b/src/ctrl/libosmoctrl.map
@@ -15,6 +15,7 @@ ctrl_cmd_parse;
ctrl_cmd_parse2;
ctrl_cmd_parse3;
ctrl_cmd_send;
+ctrl_cmd_send2;
ctrl_cmd_send_to_all;
ctrl_cmd_send_trap;
ctrl_cmd_trap;
@@ -22,12 +23,14 @@ ctrl_handle_alloc; /* could be removed? */
ctrl_handle_alloc2; /* could be removed? */
ctrl_handle_msg; /* only used in unit test */
ctrl_interface_setup;
+ctrl_interface_setup2;
ctrl_interface_setup_dynip;
ctrl_interface_setup_dynip2;
ctrl_lookup_register;
ctrl_parse_get_num;
ctrl_type_vals;
ctrl_vty_get_bind_addr;
+ctrl_vty_get_bind_port;
ctrl_vty_init;
osmo_ctrl_conn_alloc;
diff --git a/src/gb/Makefile.am b/src/gb/Makefile.am
index 65c35529..2f7ad5eb 100644
--- a/src/gb/Makefile.am
+++ b/src/gb/Makefile.am
@@ -1,10 +1,12 @@
# 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 modifications: https://www.gnu.org/software/libtool/manual/html_node/Versioning.html
-LIBVERSION=11:0:0
+LIBVERSION=16:0:2
-AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_builddir)/include
-AM_CFLAGS = -Wall ${GCC_FVISIBILITY_HIDDEN} -fno-strict-aliasing $(TALLOC_CFLAGS)
+AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_builddir)/include -I$(top_builddir)
+AM_CFLAGS = -Wall -fno-strict-aliasing \
+ $(TALLOC_CFLAGS) \
+ $(NULL)
# FIXME: this should eventually go into a milenage/Makefile.am
noinst_HEADERS = common_vty.h gb_internal.h gprs_bssgp_internal.h gprs_ns2_internal.h
@@ -12,18 +14,30 @@ noinst_HEADERS = common_vty.h gb_internal.h gprs_bssgp_internal.h gprs_ns2_inter
if ENABLE_GB
lib_LTLIBRARIES = libosmogb.la
-libosmogb_la_LDFLAGS = $(LTLDFLAGS_OSMOGB) -version-info $(LIBVERSION)
+libosmogb_la_LDFLAGS = \
+ $(LTLDFLAGS_OSMOGB) \
+ -version-info $(LIBVERSION) \
+ -no-undefined \
+ $(NULL)
libosmogb_la_LIBADD = $(TALLOC_LIBS) \
- $(top_builddir)/src/libosmocore.la \
+ $(top_builddir)/src/core/libosmocore.la \
$(top_builddir)/src/vty/libosmovty.la \
$(top_builddir)/src/gsm/libosmogsm.la
libosmogb_la_SOURCES = gprs_ns.c gprs_ns_frgre.c gprs_ns_vty.c gprs_ns_sns.c \
- gprs_bssgp.c gprs_bssgp_util.c gprs_bssgp_vty.c \
+ gprs_bssgp.c gprs_bssgp_util.c gprs_bssgp_vty.c gprs_bssgp_rim.c \
gprs_bssgp_bss.c \
- gprs_ns2.c gprs_ns2_udp.c gprs_ns2_frgre.c gprs_ns2_vc_fsm.c gprs_ns2_sns.c \
+ gprs_ns2.c gprs_ns2_udp.c gprs_ns2_frgre.c gprs_ns2_fr.c gprs_ns2_vc_fsm.c gprs_ns2_sns.c \
gprs_ns2_message.c gprs_ns2_vty.c \
- common_vty.c
+ gprs_bssgp2.c bssgp_bvc_fsm.c \
+ common_vty.c frame_relay.c
+
+# convenience library for testing with access to all non-static symbols
+noinst_LTLIBRARIES = libosmogb-test.la
+libosmogb_test_la_LIBADD = $(libosmogb_la_LIBADD)
+libosmogb_test_la_SOURCES= $(libosmogb_la_SOURCES)
+
endif
EXTRA_DIST = libosmogb.map
+EXTRA_libosmogb_la_DEPENDENCIES = libosmogb.map
diff --git a/src/gb/bssgp_bvc_fsm.c b/src/gb/bssgp_bvc_fsm.c
new file mode 100644
index 00000000..3a36c7dc
--- /dev/null
+++ b/src/gb/bssgp_bvc_fsm.c
@@ -0,0 +1,857 @@
+/* BSSGP per-BVC Finite State Machine */
+
+/* (C) 2020 Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <string.h>
+#include <stdio.h>
+
+#include <osmocom/core/fsm.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/tdef.h>
+
+#include <osmocom/gsm/gsm48.h>
+#include <osmocom/gsm/tlv.h>
+
+#include <osmocom/gprs/gprs_msgb.h>
+#include <osmocom/gprs/gprs_bssgp.h>
+#include <osmocom/gprs/gprs_bssgp2.h>
+#include <osmocom/gprs/bssgp_bvc_fsm.h>
+
+#include "common_vty.h"
+
+#define S(x) (1 << (x))
+
+/* TODO: Those are not made cofnigurable via a VTY yet */
+struct osmo_tdef bssgp_bvc_fsm_tdefs[] = {
+ {
+ .T = 1,
+ .default_val = 5,
+ .min_val = 1,
+ .max_val = 30,
+ .unit = OSMO_TDEF_S,
+ .desc = "Guards the BSSGP BVC (un)blocking procedure",
+ }, {
+ .T = 2,
+ .default_val = 10,
+ .min_val = 1,
+ .max_val = 120,
+ .unit = OSMO_TDEF_S,
+ .desc = "Guards the BSSGP BVC reset procedure",
+ }, {
+ .T = 3,
+ .default_val = 500,
+ .min_val = 100,
+ .max_val = 10000,
+ .unit = OSMO_TDEF_MS,
+ .desc = "Guards the BSSGP SUSPEND procedure",
+ }, {
+ .T = 4,
+ .default_val = 500,
+ .min_val = 100,
+ .max_val = 10000,
+ .unit = OSMO_TDEF_MS,
+ .desc = "Guards the BSSGP RESUME procedure",
+ }, {
+ .T = 5,
+ .default_val = 15,
+ .min_val = 1,
+ .max_val = 30,
+ .unit = OSMO_TDEF_S,
+ .desc = "Guards the BSSGP Radio Access Capability Update procedure",
+ },
+ {}
+};
+
+#define T1 1
+#define T2 2
+
+/* We cannot use osmo_tdef_fsm_* as it makes hard-coded assumptions that
+ * each new/target state will always use the same timer and timeout - or
+ * a timeout at all */
+#define T1_SECS osmo_tdef_get(bssgp_bvc_fsm_tdefs, 1, OSMO_TDEF_S, 5)
+#define T2_SECS osmo_tdef_get(bssgp_bvc_fsm_tdefs, 2, OSMO_TDEF_S, 10)
+
+/* forward declaration */
+static struct osmo_fsm bssgp_bvc_fsm;
+
+static const struct value_string ptp_bvc_event_names[] = {
+ { BSSGP_BVCFSM_E_RX_BLOCK, "RX-BVC-BLOCK" },
+ { BSSGP_BVCFSM_E_RX_BLOCK_ACK, "RX-BVC-BLOCK-ACK" },
+ { BSSGP_BVCFSM_E_RX_UNBLOCK, "RX-BVC-UNBLOCK" },
+ { BSSGP_BVCFSM_E_RX_UNBLOCK_ACK, "RX-BVC-UNBLOCK-ACK" },
+ { BSSGP_BVCFSM_E_RX_RESET, "RX-BVC-RESET" },
+ { BSSGP_BVCFSM_E_RX_RESET_ACK, "RX-BVC-RESET-ACK" },
+ { BSSGP_BVCFSM_E_RX_FC_BVC, "RX-FLOW-CONTROL-BVC" },
+ { BSSGP_BVCFSM_E_RX_FC_BVC_ACK, "RX-FLOW-CONTROL-BVC-ACK" },
+ { BSSGP_BVCFSM_E_REQ_BLOCK, "REQ-BLOCK" },
+ { BSSGP_BVCFSM_E_REQ_UNBLOCK, "REQ-UNBLOCK" },
+ { BSSGP_BVCFSM_E_REQ_RESET, "REQ-RESET" },
+ { BSSGP_BVCFSM_E_REQ_FC_BVC, "REQ-FLOW-CONTROL-BVC" },
+ { 0, NULL }
+};
+
+struct bvc_fsm_priv {
+ /* NS-instance; defining the scope for NSEI below */
+ struct gprs_ns2_inst *nsi;
+
+ /* NSEI of the underlying NS Entity */
+ uint16_t nsei;
+ /* Maximum size of the BSSGP PDU */
+ uint16_t max_pdu_len;
+
+ /* BVCI of this BVC */
+ uint16_t bvci;
+
+ /* are we the SGSN (true) or the BSS (false) */
+ bool role_sgsn;
+
+ /* BSS side: are we locally marked blocked? */
+ bool locally_blocked;
+ uint8_t block_cause;
+
+ /* cause value of the last outbound BVC-RESET (for re-transmissions) */
+ uint8_t last_reset_cause;
+
+ struct {
+ /* Bit 0..7: Features; Bit 8..15: Extended Features */
+ uint32_t advertised;
+ uint32_t received;
+ uint32_t negotiated;
+ /* only used if BSSGP_XFEAT_GBIT is negotiated */
+ enum bssgp_fc_granularity fc_granularity;
+ } features;
+
+ /* Cell Identification used by BSS when
+ * transmitting BVC-RESET / BVC-RESET-ACK, or those received
+ * from BSS in SGSN role */
+ struct gprs_ra_id ra_id;
+ uint16_t cell_id;
+
+ /* call-backs provided by the user */
+ const struct bssgp_bvc_fsm_ops *ops;
+ /* private data pointer passed to each call-back invocation */
+ void *ops_priv;
+};
+
+static int fi_tx_ptp(struct osmo_fsm_inst *fi, struct msgb *msg)
+{
+ struct bssgp_normal_hdr *bgph = (struct bssgp_normal_hdr *) msgb_bssgph(msg);
+ struct bvc_fsm_priv *bfp = fi->priv;
+
+ OSMO_ASSERT(fi->fsm == &bssgp_bvc_fsm);
+
+ LOGPFSM(fi, "Tx BSSGP %s\n", osmo_tlv_prot_msg_name(&osmo_pdef_bssgp, bgph->pdu_type));
+
+ return bssgp2_nsi_tx_ptp(bfp->nsi, bfp->nsei, bfp->bvci, msg, 0);
+}
+
+static int fi_tx_sig(struct osmo_fsm_inst *fi, struct msgb *msg)
+{
+ struct bssgp_normal_hdr *bgph = (struct bssgp_normal_hdr *) msgb_bssgph(msg);
+ struct bvc_fsm_priv *bfp = fi->priv;
+
+ OSMO_ASSERT(fi->fsm == &bssgp_bvc_fsm);
+
+ LOGPFSM(fi, "Tx BSSGP %s\n", osmo_tlv_prot_msg_name(&osmo_pdef_bssgp, bgph->pdu_type));
+
+ return bssgp2_nsi_tx_sig(bfp->nsi, bfp->nsei, msg, 0);
+}
+
+/* helper function to transmit BVC-RESET with right combination of conditional/optional IEs */
+static void _tx_bvc_reset(struct osmo_fsm_inst *fi, uint8_t cause)
+{
+ struct bvc_fsm_priv *bfp = fi->priv;
+ const uint8_t *features = NULL;
+ const uint8_t *features_ext = NULL;
+ uint8_t _features[2] = {
+ (bfp->features.advertised >> 0) & 0xff,
+ (bfp->features.advertised >> 8) & 0xff,
+ };
+ struct msgb *tx;
+
+ OSMO_ASSERT(fi->fsm == &bssgp_bvc_fsm);
+
+ /* transmit BVC-RESET to peer; RA-ID only present for PTP from BSS */
+ if (bfp->bvci == 0) {
+ features = &_features[0];
+ features_ext = &_features[1];
+ }
+ tx = bssgp2_enc_bvc_reset(bfp->bvci, cause,
+ bfp->bvci && !bfp->role_sgsn ? &bfp->ra_id : NULL,
+ bfp->cell_id, features, features_ext);
+ fi_tx_sig(fi, tx);
+}
+
+/* helper function to transmit BVC-RESET-ACK with right combination of conditional/optional IEs */
+static void _tx_bvc_reset_ack(struct osmo_fsm_inst *fi)
+{
+ struct bvc_fsm_priv *bfp = fi->priv;
+ const uint8_t *features = NULL;
+ const uint8_t *features_ext = NULL;
+ uint8_t _features[2] = {
+ (bfp->features.advertised >> 0) & 0xff,
+ (bfp->features.advertised >> 8) & 0xff,
+ };
+ struct msgb *tx;
+
+ OSMO_ASSERT(fi->fsm == &bssgp_bvc_fsm);
+
+ /* transmit BVC-RESET-ACK to peer; RA-ID only present for PTP from BSS -> SGSN */
+ if (bfp->bvci == 0) {
+ features = &_features[0];
+ features_ext = &_features[1];
+ }
+ tx = bssgp2_enc_bvc_reset_ack(bfp->bvci, bfp->bvci && !bfp->role_sgsn ? &bfp->ra_id : NULL,
+ bfp->cell_id, features, features_ext);
+ fi_tx_sig(fi, tx);
+}
+
+/* helper function to transmit BVC-STATUS with right combination of conditional/optional IEs */
+static void _tx_status(struct osmo_fsm_inst *fi, enum gprs_bssgp_cause cause, const struct msgb *rx)
+{
+ struct bvc_fsm_priv *bfp = fi->priv;
+ struct msgb *tx;
+ uint16_t *bvci = NULL;
+
+ /* GSM 08.18, 10.4.14.1: The BVCI must be included if (and only if) the
+ * cause is either "BVCI blocked" or "BVCI unknown" */
+ if (cause == BSSGP_CAUSE_UNKNOWN_BVCI || cause == BSSGP_CAUSE_BVCI_BLOCKED)
+ bvci = &bfp->bvci;
+
+ tx = bssgp2_enc_status(cause, bvci, rx, bfp->max_pdu_len);
+
+ if (msgb_bvci(rx) == 0)
+ fi_tx_sig(fi, tx);
+ else
+ fi_tx_ptp(fi, tx);
+}
+
+/* Update the features by bit-wise AND of advertised + received features */
+static void update_negotiated_features(struct osmo_fsm_inst *fi, const struct tlv_parsed *tp)
+{
+ struct bvc_fsm_priv *bfp = fi->priv;
+
+ OSMO_ASSERT(fi->fsm == &bssgp_bvc_fsm);
+
+ bfp->features.received = 0;
+
+ if (TLVP_PRES_LEN(tp, BSSGP_IE_FEATURE_BITMAP, 1))
+ bfp->features.received |= *TLVP_VAL(tp, BSSGP_IE_FEATURE_BITMAP);
+
+ if (TLVP_PRES_LEN(tp, BSSGP_IE_EXT_FEATURE_BITMAP, 1))
+ bfp->features.received |= (*TLVP_VAL(tp, BSSGP_IE_EXT_FEATURE_BITMAP) << 8);
+
+ bfp->features.negotiated = bfp->features.advertised & bfp->features.received;
+
+ LOGPFSML(fi, LOGL_NOTICE, "Updating features: Advertised 0x%04x, Received 0x%04x, Negotiated 0x%04x\n",
+ bfp->features.advertised, bfp->features.received, bfp->features.negotiated);
+}
+
+/* "tail" of each onenter() handler: Calling the state change notification call-back */
+static void _onenter_tail(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct bvc_fsm_priv *bfp = fi->priv;
+
+ if (prev_state == fi->state)
+ return;
+
+ if (bfp->ops && bfp->ops->state_chg_notification)
+ bfp->ops->state_chg_notification(bfp->nsei, bfp->bvci, prev_state, fi->state, bfp->ops_priv);
+}
+
+static void bssgp_bvc_fsm_null(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ /* we don't really expect anything in this state; all handled via allstate */
+ OSMO_ASSERT(0);
+}
+
+static void bssgp_bvc_fsm_blocked_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct bvc_fsm_priv *bfp = fi->priv;
+ /* signaling BVC can never be blocked */
+ OSMO_ASSERT(bfp->bvci != 0);
+ _onenter_tail(fi, prev_state);
+}
+
+static void bssgp_bvc_fsm_blocked(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct bvc_fsm_priv *bfp = fi->priv;
+ struct msgb *rx = NULL, *tx;
+ const struct tlv_parsed *tp = NULL;
+ uint8_t cause;
+
+ switch (event) {
+ case BSSGP_BVCFSM_E_RX_BLOCK_ACK:
+ rx = data;
+ tp = (const struct tlv_parsed *) msgb_bcid(rx);
+ /* If a BVC-BLOCK-ACK PDU is received by a BSS for the signalling BVC, the PDU is ignored. */
+ if (bfp->bvci == 0) {
+ LOGPFSML(fi, LOGL_ERROR, "Rx BVC-BLOCK-ACK on BVCI=0 is illegal\n");
+ if (!bfp->role_sgsn)
+ break;
+ _tx_status(fi, BSSGP_CAUSE_SEM_INCORR_PDU, rx);
+ break;
+ }
+ /* stop T1 timer */
+ osmo_fsm_inst_state_chg(fi, BSSGP_BVCFSM_S_BLOCKED, 0, 0);
+ break;
+ case BSSGP_BVCFSM_E_RX_BLOCK:
+ rx = data;
+ tp = (const struct tlv_parsed *) msgb_bcid(rx);
+ cause = *TLVP_VAL(tp, BSSGP_IE_CAUSE);
+ LOGPFSML(fi, LOGL_NOTICE, "Rx BVC-BLOCK (cause=%s)\n", bssgp_cause_str(cause));
+ /* If a BVC-BLOCK PDU is received by an SGSN for a blocked BVC, a BVC-BLOCK-ACK
+ * PDU shall be returned. */
+ if (bfp->role_sgsn) {
+ /* If a BVC-BLOCK PDU is received by an SGSN for
+ * the signalling BVC, the PDU is ignored */
+ if (bfp->bvci == 0)
+ break;
+ tx = bssgp2_enc_bvc_block_ack(bfp->bvci);
+ fi_tx_sig(fi, tx);
+ }
+ break;
+ case BSSGP_BVCFSM_E_RX_UNBLOCK:
+ rx = data;
+ tp = (const struct tlv_parsed *) msgb_bcid(rx);
+ LOGPFSML(fi, LOGL_NOTICE, "Rx BVC-UNBLOCK\n");
+ if (bfp->bvci == 0) {
+ LOGPFSML(fi, LOGL_ERROR, "Rx BVC-UNBLOCK on BVCI=0 is illegal\n");
+ /* If BVC-UNBLOCK PDU is received by an SGSN for the signalling BVC, the PDU is ignored.*/
+ if (bfp->role_sgsn)
+ break;
+ _tx_status(fi, BSSGP_CAUSE_SEM_INCORR_PDU, rx);
+ break;
+ }
+ if (!bfp->role_sgsn) {
+ LOGPFSML(fi, LOGL_ERROR, "Rx BVC-UNBLOCK on BSS is illegal\n");
+ _tx_status(fi, BSSGP_CAUSE_SEM_INCORR_PDU, rx);
+ break;
+ }
+ tx = bssgp2_enc_bvc_unblock_ack(bfp->bvci);
+ fi_tx_sig(fi, tx);
+ osmo_fsm_inst_state_chg(fi, BSSGP_BVCFSM_S_UNBLOCKED, T1_SECS, T1);
+ break;
+ case BSSGP_BVCFSM_E_REQ_UNBLOCK:
+ if (bfp->role_sgsn) {
+ LOGPFSML(fi, LOGL_ERROR, "SGSN side cannot initiate BVC unblock\n");
+ break;
+ }
+ if (bfp->bvci == 0) {
+ LOGPFSML(fi, LOGL_ERROR, "BVCI 0 cannot be unblocked\n");
+ break;
+ }
+ bfp->locally_blocked = false;
+ tx = bssgp2_enc_bvc_unblock(bfp->bvci);
+ fi_tx_sig(fi, tx);
+ osmo_fsm_inst_state_chg(fi, BSSGP_BVCFSM_S_UNBLOCKED, 0, 0);
+ break;
+ }
+}
+
+/* Waiting for RESET-ACK: Receive PDUs but don't transmit */
+static void bssgp_bvc_fsm_wait_reset_ack(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct bvc_fsm_priv *bfp = fi->priv;
+ const struct tlv_parsed *tp = NULL;
+ struct msgb *rx = NULL, *tx;
+ uint8_t cause;
+
+ switch (event) {
+ case BSSGP_BVCFSM_E_RX_RESET:
+ /* 48.018 Section 8.4.3: If the BSS (or SGSN) has sent a BVC-RESET PDU for a BVCI to
+ * the SGSN (or BSS) and is awaiting a BVC-RESET-ACK PDU in response, but instead
+ * receives a BVC-RESET PDU indicating the same BVCI, then this shall be interpreted
+ * as a BVC-RESET ACK PDU and the T2 timer shall be stopped. */
+ /* fall-through */
+ case BSSGP_BVCFSM_E_RX_RESET_ACK:
+ rx = data;
+ cause = bfp->last_reset_cause;
+ tp = (const struct tlv_parsed *) msgb_bcid(rx);
+ if (bfp->bvci == 0)
+ update_negotiated_features(fi, tp);
+ if (bfp->role_sgsn && bfp->bvci != 0)
+ bfp->cell_id = bssgp_parse_cell_id(&bfp->ra_id, TLVP_VAL(tp, BSSGP_IE_CELL_ID));
+ if (!bfp->role_sgsn && bfp->bvci != 0 && bfp->locally_blocked) {
+ /* initiate the blocking procedure */
+ /* transmit BVC-BLOCK, transition to BLOCKED state and start re-transmit timer */
+ tx = bssgp2_enc_bvc_block(bfp->bvci, bfp->block_cause);
+ fi_tx_sig(fi, tx);
+ osmo_fsm_inst_state_chg(fi, BSSGP_BVCFSM_S_BLOCKED, T1_SECS, T1);
+ } else
+ osmo_fsm_inst_state_chg(fi, BSSGP_BVCFSM_S_UNBLOCKED, 0, 0);
+ if (bfp->ops && bfp->ops->reset_ack_notification)
+ bfp->ops->reset_ack_notification(bfp->nsei, bfp->bvci, &bfp->ra_id, bfp->cell_id, cause, bfp->ops_priv);
+ break;
+ }
+}
+
+static void bssgp_bvc_fsm_unblocked(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct bssgp2_flow_ctrl rx_fc, *tx_fc;
+ struct bvc_fsm_priv *bfp = fi->priv;
+ const struct tlv_parsed *tp = NULL;
+ struct msgb *rx = NULL, *tx;
+ int rc;
+
+ switch (event) {
+ case BSSGP_BVCFSM_E_RX_UNBLOCK_ACK:
+ rx = data;
+ tp = (const struct tlv_parsed *) msgb_bcid(rx);
+ /* If BVC-UNBLOCK-ACK PDU is received by an BSS for the signalling BVC, the PDU is ignored. */
+ if (bfp->bvci == 0) {
+ LOGPFSML(fi, LOGL_ERROR, "Rx BVC-UNBLOCK-ACK on BVCI=0 is illegal\n");
+ if (!bfp->role_sgsn)
+ break;
+ _tx_status(fi, BSSGP_CAUSE_SEM_INCORR_PDU, rx);
+ break;
+ }
+ /* stop T1 timer */
+ osmo_fsm_inst_state_chg(fi, BSSGP_BVCFSM_S_UNBLOCKED, 0, 0);
+ break;
+ case BSSGP_BVCFSM_E_RX_UNBLOCK:
+ rx = data;
+ tp = (const struct tlv_parsed *) msgb_bcid(rx);
+ /* If a BVC-UNBLOCK PDU is received by an SGSN for a blocked BVC, a BVC-UNBLOCK-ACK
+ * PDU shall be returned. */
+ if (bfp->role_sgsn) {
+ /* If a BVC-UNBLOCK PDU is received by an SGSN for
+ * the signalling BVC, the PDU is ignored */
+ if (bfp->bvci == 0)
+ break;
+ bssgp_tx_simple_bvci(BSSGP_PDUT_BVC_UNBLOCK_ACK, bfp->nsei, bfp->bvci, 0);
+ }
+ break;
+ case BSSGP_BVCFSM_E_RX_BLOCK:
+ rx = data;
+ tp = (const struct tlv_parsed *) msgb_bcid(rx);
+ LOGPFSML(fi, LOGL_NOTICE, "Rx BVC-BLOCK (cause=%s)\n",
+ bssgp_cause_str(*TLVP_VAL(tp, BSSGP_IE_CAUSE)));
+ /* If a BVC-BLOCK PDU is received by an SGSN for the signalling BVC, the PDU is ignored */
+ if (bfp->bvci == 0) {
+ LOGPFSML(fi, LOGL_ERROR, "Rx BVC-BLOCK on BVCI=0 is illegal\n");
+ if (bfp->role_sgsn)
+ break;
+ _tx_status(fi, BSSGP_CAUSE_SEM_INCORR_PDU, rx);
+ break;
+ }
+ if (!bfp->role_sgsn) {
+ LOGPFSML(fi, LOGL_ERROR, "Rx BVC-BLOCK on BSS is illegal\n");
+ _tx_status(fi, BSSGP_CAUSE_SEM_INCORR_PDU, rx);
+ break;
+ }
+ /* transmit BVC-BLOCK-ACK, transition to BLOCKED state */
+ tx = bssgp2_enc_bvc_block_ack(bfp->bvci);
+ fi_tx_sig(fi, tx);
+ osmo_fsm_inst_state_chg(fi, BSSGP_BVCFSM_S_BLOCKED, 0, 0);
+ break;
+ case BSSGP_BVCFSM_E_REQ_BLOCK:
+ if (bfp->role_sgsn) {
+ LOGPFSML(fi, LOGL_ERROR, "SGSN may not initiate BVC-BLOCK\n");
+ break;
+ }
+ if (bfp->bvci == 0) {
+ LOGPFSML(fi, LOGL_ERROR, "BVCI 0 cannot be blocked\n");
+ break;
+ }
+ bfp->locally_blocked = true;
+ bfp->block_cause = *(uint8_t *)data;
+ /* transmit BVC-BLOCK, transition to BLOCKED state and start re-transmit timer */
+ tx = bssgp2_enc_bvc_block(bfp->bvci, bfp->block_cause);
+ fi_tx_sig(fi, tx);
+ osmo_fsm_inst_state_chg(fi, BSSGP_BVCFSM_S_BLOCKED, T1_SECS, T1);
+ break;
+ case BSSGP_BVCFSM_E_RX_FC_BVC:
+ rx = data;
+ tp = (const struct tlv_parsed *) msgb_bcid(rx);
+ /* we assume osmo_tlv_prot_* has been used before calling here to ensure this */
+ OSMO_ASSERT(bfp->role_sgsn);
+ rc = bssgp2_dec_fc_bvc(&rx_fc, tp);
+ if (rc < 0) {
+ _tx_status(fi, BSSGP_CAUSE_SEM_INCORR_PDU, rx);
+ break;
+ }
+ if (bfp->ops->rx_fc_bvc)
+ bfp->ops->rx_fc_bvc(bfp->nsei, bfp->bvci, &rx_fc, bfp->ops_priv);
+ tx = bssgp2_enc_fc_bvc_ack(rx_fc.tag);
+ fi_tx_ptp(fi, tx);
+ break;
+ case BSSGP_BVCFSM_E_RX_FC_BVC_ACK:
+ rx = data;
+ tp = (const struct tlv_parsed *) msgb_bcid(rx);
+ /* we assume osmo_tlv_prot_* has been used before calling here to ensure this */
+ OSMO_ASSERT(!bfp->role_sgsn);
+ break;
+ case BSSGP_BVCFSM_E_REQ_FC_BVC:
+ tx_fc = data;
+ tx = bssgp2_enc_fc_bvc(tx_fc, bfp->features.negotiated & (BSSGP_XFEAT_GBIT << 8) ?
+ &bfp->features.fc_granularity : NULL);
+ fi_tx_ptp(fi, tx);
+ break;
+ }
+}
+
+static void bssgp_bvc_fsm_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct bvc_fsm_priv *bfp = fi->priv;
+ uint8_t cause;
+ const struct tlv_parsed *tp = NULL;
+ struct msgb *rx = NULL;
+
+ switch (event) {
+ case BSSGP_BVCFSM_E_REQ_RESET:
+ bfp->locally_blocked = false;
+ cause = bfp->last_reset_cause = *(uint8_t *) data;
+ _tx_bvc_reset(fi, cause);
+ osmo_fsm_inst_state_chg(fi, BSSGP_BVCFSM_S_WAIT_RESET_ACK, T2_SECS, T2);
+#if 0 /* not sure if we really should notify the application if itself has requested the reset? */
+ if (bfp->ops && bfp->ops->reset_notification)
+ bfp->ops->reset_notification(bfp->nsei, bfp->bvci, NULL, 0, cause, bfp->ops_priv);
+#endif
+ break;
+ case BSSGP_BVCFSM_E_RX_RESET:
+ rx = data;
+ tp = (const struct tlv_parsed *) msgb_bcid(rx);
+ cause = *TLVP_VAL(tp, BSSGP_IE_CAUSE);
+ if (bfp->role_sgsn && bfp->bvci != 0)
+ bfp->cell_id = bssgp_parse_cell_id(&bfp->ra_id, TLVP_VAL(tp, BSSGP_IE_CELL_ID));
+ LOGPFSML(fi, LOGL_NOTICE, "Rx BVC-RESET (cause=%s)\n", bssgp_cause_str(cause));
+ if (bfp->bvci == 0)
+ update_negotiated_features(fi, tp);
+ _tx_bvc_reset_ack(fi);
+ osmo_fsm_inst_state_chg(fi, BSSGP_BVCFSM_S_UNBLOCKED, 0, 0);
+ if (bfp->ops && bfp->ops->reset_notification) {
+ bfp->ops->reset_notification(bfp->nsei, bfp->bvci, &bfp->ra_id, bfp->cell_id,
+ cause, bfp->ops_priv);
+ }
+ break;
+ }
+}
+
+static int bssgp_bvc_fsm_timer_cb(struct osmo_fsm_inst *fi)
+{
+ struct bvc_fsm_priv *bfp = fi->priv;
+ struct msgb *tx;
+
+ switch (fi->T) {
+ case T1:
+ switch (fi->state) {
+ case BSSGP_BVCFSM_S_BLOCKED:
+ /* re-transmit BVC-BLOCK */
+ tx = bssgp2_enc_bvc_block(bfp->bvci, bfp->block_cause);
+ fi_tx_sig(fi, tx);
+ osmo_fsm_inst_state_chg(fi, BSSGP_BVCFSM_S_BLOCKED, T1_SECS, T1);
+ break;
+ case BSSGP_BVCFSM_S_UNBLOCKED:
+ /* re-transmit BVC-UNBLOCK */
+ tx = bssgp2_enc_bvc_unblock(bfp->bvci);
+ fi_tx_sig(fi, tx);
+ osmo_fsm_inst_state_chg(fi, BSSGP_BVCFSM_S_UNBLOCKED, T1_SECS, T1);
+ break;
+ }
+ break;
+ case T2:
+ switch (fi->state) {
+ case BSSGP_BVCFSM_S_WAIT_RESET_ACK:
+ /* re-transmit BVC-RESET */
+ _tx_bvc_reset(fi, bfp->last_reset_cause);
+ osmo_fsm_inst_state_chg(fi, BSSGP_BVCFSM_S_WAIT_RESET_ACK, T2_SECS, T2);
+ break;
+ case BSSGP_BVCFSM_S_UNBLOCKED:
+ /* re-transmit BVC-RESET-ACK */
+ _tx_bvc_reset_ack(fi);
+ osmo_fsm_inst_state_chg(fi, BSSGP_BVCFSM_S_UNBLOCKED, T2_SECS, T2);
+ break;
+ }
+ break;
+ default:
+ OSMO_ASSERT(0);
+ break;
+ }
+ return 0;
+}
+
+
+
+static const struct osmo_fsm_state bssgp_bvc_fsm_states[] = {
+ [BSSGP_BVCFSM_S_NULL] = {
+ /* initial state from which we must do a RESET */
+ .name = "NULL",
+ .in_event_mask = 0,
+ .out_state_mask = S(BSSGP_BVCFSM_S_WAIT_RESET_ACK) |
+ S(BSSGP_BVCFSM_S_UNBLOCKED),
+ .action = bssgp_bvc_fsm_null,
+ },
+ [BSSGP_BVCFSM_S_BLOCKED] = {
+ .name = "BLOCKED",
+ .in_event_mask = S(BSSGP_BVCFSM_E_RX_UNBLOCK) |
+ S(BSSGP_BVCFSM_E_RX_BLOCK) |
+ S(BSSGP_BVCFSM_E_RX_BLOCK_ACK) |
+ S(BSSGP_BVCFSM_E_REQ_UNBLOCK),
+ .out_state_mask = S(BSSGP_BVCFSM_S_WAIT_RESET_ACK) |
+ S(BSSGP_BVCFSM_S_UNBLOCKED) |
+ S(BSSGP_BVCFSM_S_BLOCKED),
+ .action = bssgp_bvc_fsm_blocked,
+ .onenter = bssgp_bvc_fsm_blocked_onenter,
+ },
+ [BSSGP_BVCFSM_S_WAIT_RESET_ACK]= {
+ .name = "WAIT_RESET_ACK",
+ .in_event_mask = S(BSSGP_BVCFSM_E_RX_RESET_ACK) |
+ S(BSSGP_BVCFSM_E_RX_RESET),
+ .out_state_mask = S(BSSGP_BVCFSM_S_UNBLOCKED) |
+ S(BSSGP_BVCFSM_S_BLOCKED) |
+ S(BSSGP_BVCFSM_S_WAIT_RESET_ACK),
+ .action = bssgp_bvc_fsm_wait_reset_ack,
+ .onenter = _onenter_tail,
+ },
+
+ [BSSGP_BVCFSM_S_UNBLOCKED] = {
+ .name = "UNBLOCKED",
+ .in_event_mask = S(BSSGP_BVCFSM_E_RX_BLOCK) |
+ S(BSSGP_BVCFSM_E_RX_UNBLOCK) |
+ S(BSSGP_BVCFSM_E_RX_UNBLOCK_ACK) |
+ S(BSSGP_BVCFSM_E_REQ_BLOCK) |
+ S(BSSGP_BVCFSM_E_RX_FC_BVC) |
+ S(BSSGP_BVCFSM_E_RX_FC_BVC_ACK) |
+ S(BSSGP_BVCFSM_E_REQ_FC_BVC),
+ .out_state_mask = S(BSSGP_BVCFSM_S_BLOCKED) |
+ S(BSSGP_BVCFSM_S_WAIT_RESET_ACK) |
+ S(BSSGP_BVCFSM_S_UNBLOCKED),
+ .action = bssgp_bvc_fsm_unblocked,
+ .onenter = _onenter_tail,
+ },
+};
+
+static struct osmo_fsm bssgp_bvc_fsm = {
+ .name = "BSSGP-BVC",
+ .states = bssgp_bvc_fsm_states,
+ .num_states = ARRAY_SIZE(bssgp_bvc_fsm_states),
+ .allstate_event_mask = S(BSSGP_BVCFSM_E_REQ_RESET) |
+ S(BSSGP_BVCFSM_E_RX_RESET),
+ .allstate_action = bssgp_bvc_fsm_allstate,
+ .timer_cb = bssgp_bvc_fsm_timer_cb,
+ .log_subsys = DLBSSGP,
+ .event_names = ptp_bvc_event_names,
+};
+
+static struct osmo_fsm_inst *
+_bvc_fsm_alloc(void *ctx, struct gprs_ns2_inst *nsi, bool role_sgsn, uint16_t nsei, uint16_t bvci)
+{
+ struct osmo_fsm_inst *fi;
+ struct bvc_fsm_priv *bfp;
+ char idbuf[64];
+
+ /* TODO: encode our role in the id string? */
+ snprintf(idbuf, sizeof(idbuf), "NSE%05u-BVC%05u", nsei, bvci);
+
+ fi = osmo_fsm_inst_alloc(&bssgp_bvc_fsm, ctx, NULL, LOGL_INFO, idbuf);
+ if (!fi)
+ return NULL;
+
+ bfp = talloc_zero(fi, struct bvc_fsm_priv);
+ if (!bfp) {
+ osmo_fsm_inst_free(fi);
+ return NULL;
+ }
+ fi->priv = bfp;
+
+ bfp->nsi = nsi;
+ bfp->role_sgsn = role_sgsn;
+ bfp->nsei = nsei;
+ bfp->bvci = bvci;
+ bfp->max_pdu_len = UINT16_MAX;
+
+ return fi;
+}
+
+/*! Allocate a SIGNALING-BVC FSM for the BSS role (facing a remote SGSN).
+ * \param[in] ctx talloc context from which to allocate
+ * \param[in] nsi NS Instance on which this BVC operates
+ * \param[in] nsei NS Entity Identifier on which this BVC operates
+ * \param[in] features Feature [byte 0] and Extended Feature [byte 1] bitmap
+ * \returns newly-allocated FSM Instance; NULL in case of error */
+struct osmo_fsm_inst *
+bssgp_bvc_fsm_alloc_sig_bss(void *ctx, struct gprs_ns2_inst *nsi, uint16_t nsei, uint32_t features)
+{
+ struct osmo_fsm_inst *fi = _bvc_fsm_alloc(ctx, nsi, false, nsei, 0);
+ struct bvc_fsm_priv *bfp;
+
+ if (!fi)
+ return NULL;
+
+ bfp = fi->priv;
+ bfp->features.advertised = features;
+
+ return fi;
+}
+
+/*! Allocate a PTP-BVC FSM for the BSS role (facing a remote SGSN).
+ * \param[in] ctx talloc context from which to allocate
+ * \param[in] nsi NS Instance on which this BVC operates
+ * \param[in] nsei NS Entity Identifier on which this BVC operates
+ * \param[in] bvci BVCI of this FSM
+ * \param[in] ra_id Routing Area Identity of the cell (reported to SGSN)
+ * \param[in] cell_id Cell Identifier of the cell (reported to SGSN)
+ * \returns newly-allocated FSM Instance; NULL in case of error */
+struct osmo_fsm_inst *
+bssgp_bvc_fsm_alloc_ptp_bss(void *ctx, struct gprs_ns2_inst *nsi, uint16_t nsei,
+ uint16_t bvci, const struct gprs_ra_id *ra_id, uint16_t cell_id)
+{
+ struct osmo_fsm_inst *fi;
+ struct bvc_fsm_priv *bfp;
+
+ OSMO_ASSERT(bvci >= 2);
+ OSMO_ASSERT(ra_id);
+
+ fi = _bvc_fsm_alloc(ctx, nsi, false, nsei, bvci);
+ if (!fi)
+ return NULL;
+
+ bfp = fi->priv;
+ bfp->ra_id = *ra_id;
+ bfp->cell_id = cell_id;
+
+ return fi;
+}
+
+/*! Allocate a SIGNALING-BVC FSM for the SGSN role (facing a remote BSS).
+ * \param[in] ctx talloc context from which to allocate
+ * \param[in] nsi NS Instance on which this BVC operates
+ * \param[in] nsei NS Entity Identifier on which this BVC operates
+ * \param[in] features Feature [byte 0] and Extended Feature [byte 1] bitmap
+ * \returns newly-allocated FSM Instance; NULL in case of error */
+struct osmo_fsm_inst *
+bssgp_bvc_fsm_alloc_sig_sgsn(void *ctx, struct gprs_ns2_inst *nsi, uint16_t nsei, uint32_t features)
+{
+ struct osmo_fsm_inst *fi = _bvc_fsm_alloc(ctx, nsi, true, nsei, 0);
+ struct bvc_fsm_priv *bfp;
+
+ if (!fi)
+ return NULL;
+
+ bfp = fi->priv;
+ bfp->features.advertised = features;
+
+ return fi;
+}
+
+/*! Allocate a PTP-BVC FSM for the SGSN role (facing a remote BSS).
+ * \param[in] ctx talloc context from which to allocate
+ * \param[in] nsi NS Instance on which this BVC operates
+ * \param[in] nsei NS Entity Identifier on which this BVC operates
+ * \param[in] bvci BVCI of this FSM
+ * \returns newly-allocated FSM Instance; NULL in case of error */
+struct osmo_fsm_inst *
+bssgp_bvc_fsm_alloc_ptp_sgsn(void *ctx, struct gprs_ns2_inst *nsi, uint16_t nsei, uint16_t bvci)
+{
+ struct osmo_fsm_inst *fi;
+
+ OSMO_ASSERT(bvci >= 2);
+
+ fi = _bvc_fsm_alloc(ctx, nsi, true, nsei, bvci);
+ if (!fi)
+ return NULL;
+
+ return fi;
+}
+
+/*! Set the 'operations' callbacks + private data.
+ * \param[in] fi FSM instance for which the data shall be set
+ * \param[in] ops BSSGP BVC FSM operations (call-back functions) to register
+ * \param[in] ops_priv opaque/private data pointer passed through to call-backs */
+void bssgp_bvc_fsm_set_ops(struct osmo_fsm_inst *fi, const struct bssgp_bvc_fsm_ops *ops, void *ops_priv)
+{
+ struct bvc_fsm_priv *bfp = fi->priv;
+
+ OSMO_ASSERT(fi->fsm == &bssgp_bvc_fsm);
+
+ bfp->ops = ops;
+ bfp->ops_priv = ops_priv;
+}
+
+/*! Return if the given BVC FSM is in UNBLOCKED state. */
+bool bssgp_bvc_fsm_is_unblocked(struct osmo_fsm_inst *fi)
+{
+ return fi->state == BSSGP_BVCFSM_S_UNBLOCKED;
+}
+
+/*! Determine the cause value why given BVC FSM is blocked. */
+uint8_t bssgp_bvc_fsm_get_block_cause(struct osmo_fsm_inst *fi)
+{
+ struct bvc_fsm_priv *bfp = fi->priv;
+
+ OSMO_ASSERT(fi->fsm == &bssgp_bvc_fsm);
+ return bfp->block_cause;
+}
+
+/*! Return the advertised features / extended features. */
+uint32_t bssgp_bvc_fsm_get_features_advertised(struct osmo_fsm_inst *fi)
+{
+ struct bvc_fsm_priv *bfp = fi->priv;
+
+ OSMO_ASSERT(fi->fsm == &bssgp_bvc_fsm);
+ return bfp->features.advertised;
+}
+
+/*! Return the received features / extended features. */
+uint32_t bssgp_bvc_fsm_get_features_received(struct osmo_fsm_inst *fi)
+{
+ struct bvc_fsm_priv *bfp = fi->priv;
+
+ OSMO_ASSERT(fi->fsm == &bssgp_bvc_fsm);
+ return bfp->features.received;
+}
+
+/*! Return the negotiated features / extended features. */
+uint32_t bssgp_bvc_fsm_get_features_negotiated(struct osmo_fsm_inst *fi)
+{
+ struct bvc_fsm_priv *bfp = fi->priv;
+
+ OSMO_ASSERT(fi->fsm == &bssgp_bvc_fsm);
+ return bfp->features.negotiated;
+}
+
+/*! Set the maximum size of a BSSGP PDU.
+ *! On the NS layer this corresponds to the size of an NS SDU in NS-UNITDATA (3GPP TS 48.016 Ch. 9.2.10) */
+void bssgp_bvc_fsm_set_max_pdu_len(struct osmo_fsm_inst *fi, uint16_t max_pdu_len) {
+ struct bvc_fsm_priv *bfp = fi->priv;
+
+ OSMO_ASSERT(fi->fsm == &bssgp_bvc_fsm);
+ bfp->max_pdu_len = max_pdu_len;
+}
+
+/*! Return the maximum size of a BSSGP PDU
+ *! On the NS layer this corresponds to the size of an NS SDU in NS-UNITDATA (3GPP TS 48.016 Ch. 9.2.10) */
+uint16_t bssgp_bvc_fsm_get_max_pdu_len(const struct osmo_fsm_inst *fi)
+{
+ const struct bvc_fsm_priv *bfp = fi->priv;
+
+ OSMO_ASSERT(fi->fsm == &bssgp_bvc_fsm);
+ return bfp->max_pdu_len;
+}
+
+
+static __attribute__((constructor)) void on_dso_load_bvc_fsm(void)
+{
+ OSMO_ASSERT(osmo_fsm_register(&bssgp_bvc_fsm) == 0);
+}
diff --git a/src/gb/common_vty.c b/src/gb/common_vty.c
index eb665d52..ad3dea23 100644
--- a/src/gb/common_vty.c
+++ b/src/gb/common_vty.c
@@ -40,15 +40,21 @@
int gprs_log_filter_fn(const struct log_context *ctx,
struct log_target *tar)
{
+ const void *nse = ctx->ctx[LOG_CTX_GB_NSE];
const void *nsvc = ctx->ctx[LOG_CTX_GB_NSVC];
const void *bvc = ctx->ctx[LOG_CTX_GB_BVC];
+ /* Filter on the NS Entity */
+ if ((tar->filter_map & (1 << LOG_FLT_GB_NSE)) != 0
+ && nse && (nse == tar->filter_data[LOG_FLT_GB_NSE]))
+ return 1;
+
/* Filter on the NS Virtual Connection */
if ((tar->filter_map & (1 << LOG_FLT_GB_NSVC)) != 0
&& nsvc && (nsvc == tar->filter_data[LOG_FLT_GB_NSVC]))
return 1;
- /* Filter on the NS Virtual Connection */
+ /* Filter on the BSSGP Virtual Connection */
if ((tar->filter_map & (1 << LOG_FLT_GB_BVC)) != 0
&& bvc && (bvc == tar->filter_data[LOG_FLT_GB_BVC]))
return 1;
@@ -57,4 +63,4 @@ int gprs_log_filter_fn(const struct log_context *ctx,
}
-int DNS, DBSSGP;
+int DNS;
diff --git a/src/gb/common_vty.h b/src/gb/common_vty.h
index 801d2dab..8e883319 100644
--- a/src/gb/common_vty.h
+++ b/src/gb/common_vty.h
@@ -3,5 +3,5 @@
#include <osmocom/vty/command.h>
#include <osmocom/core/logging.h>
-extern int DNS, DBSSGP;
+extern int DNS;
diff --git a/src/gb/frame_relay.c b/src/gb/frame_relay.c
new file mode 100644
index 00000000..e973b915
--- /dev/null
+++ b/src/gb/frame_relay.c
@@ -0,0 +1,1051 @@
+/*! \file frame_relay.c
+ * Implement frame relay/PVC by Q.933
+ */
+/* (C) 2020 Harald Welte <laforge@gnumonks.org>
+ * (C) 2020 sysmocom - s.f.m.c. GmbH
+ * Author: Alexander Couzens <lynxis@fe80.eu>
+ *
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include <osmocom/gprs/frame_relay.h>
+#include <osmocom/core/endian.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/tdef.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/core/msgb.h>
+
+#include <osmocom/gsm/tlv.h>
+
+#define LOGPFRL(frl, lvl, fmt, args ...) \
+ LOGP(DFR, lvl, "%s: " fmt, (frl)->name, ## args)
+
+#define DFR DLNS
+
+/* Table 4-2/Q.931 */
+enum q931_msgtype {
+ /* Call establishment message */
+ Q931_MSGT_ALERTING = 0x01,
+ Q931_MSGT_CALL_PROCEEDING = 0x02,
+ Q931_MSGT_CONNECT = 0x07,
+ Q931_MSGT_CONNECT_ACK = 0x0f,
+ Q931_MSGT_PROGRESS = 0x03,
+ Q931_MSGT_SETUP = 0x05,
+ Q931_MSGT_SETUP_ACK = 0x0d,
+ /* Call information phase message */
+ Q931_MSGT_RESUME = 0x26,
+ Q931_MSGT_RESUME_ACK = 0x2e,
+ Q931_MSGT_RESUME_REJ = 0x22,
+ Q931_MSGT_SUSPEND = 0x25,
+ Q931_MSGT_SUSPEND_ACK = 0x2d,
+ Q931_MSGT_USER_INFO = 0x20,
+ /* Call clearing message */
+ Q931_MSGT_DISCONNECT = 0x45,
+ Q931_MSGT_RELEASE = 0x4d,
+ Q931_MSGT_RELEASE_COMPLETE = 0x5a,
+ Q931_MSGT_RESTART = 0x46,
+ Q931_MSGT_RESTART_ACK = 0x4e,
+ /* Miscellaneous messages */
+ Q931_MSGT_SEGMENT = 0x60,
+ Q931_MSGT_CONGESTION_CONTROL = 0x79,
+ Q931_MSGT_IFORMATION = 0x7b,
+ Q931_MSGT_NOTIFY = 0x6e,
+ Q931_MSGT_STATUS = 0x7d,
+ Q931_MSGT_STATUS_ENQUIRY = 0x75,
+};
+
+
+/* Figure A.1/Q.933 Report type information element */
+enum q933_type_of_report {
+ Q933_REPT_FULL_STATUS = 0x00,
+ Q933_REPT_LINK_INTEGRITY_VERIF = 0x01,
+ Q933_REPT_SINGLE_PVC_ASYNC_STS = 0x02,
+};
+
+/* Q.933 Section A.3 */
+enum q933_iei {
+ Q933_IEI_REPORT_TYPE = 0x51,
+ Q933_IEI_LINK_INT_VERIF = 0x53,
+ Q933_IEI_PVC_STATUS = 0x57,
+};
+
+/* Q.933 Section A.3.3 */
+enum q933_pvc_status {
+ Q933_PVC_STATUS_DLC_ACTIVE = 0x02,
+ Q933_PVC_STATUS_DLC_DELETE = 0x04,
+ Q933_PVC_STATUS_DLC_NEW = 0x08,
+};
+
+
+
+#define LAPF_UI 0x03 /* UI control word */
+#define Q931_PDISC_CC 0x08 /* protocol discriminator */
+#define LMI_Q933A_CALLREF 0x00 /* NULL call-ref */
+
+/* LMI DLCI values */
+#define LMI_Q933A_DLCI 0 /* Q.933A DLCI */
+#define LMI_CISCO_DLCI 1023 /* Cisco DLCI */
+
+/* maximum of supported */
+#define MAX_SUPPORTED_PVC 10
+
+/* TODO: add counters since good connection */
+
+/* Message header of the L3 payload of a Q.933 Annex A message */
+struct q933_a_hdr {
+ uint8_t prot_disc;
+ uint8_t call_ref;
+ uint8_t msg_type;
+} __attribute__((packed));
+
+/* Value part of the Q.933 Annex A.3.3 IE */
+struct q933_a_pvc_sts {
+#if OSMO_IS_LITTLE_ENDIAN
+ uint8_t dlci_msb:6,
+ spare:1,
+ ext0:1;
+ uint8_t space1:3,
+ dlci_lsb:4,
+ ext1:1;
+ uint8_t reserved:1,
+ active:1,
+ delete:1,
+ new:1,
+ spare2:3,
+ ext2:1;
+
+#elif OSMO_IS_BIG_ENDIAN
+/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianness.py) */
+ uint8_t ext0:1, spare:1, dlci_msb:6;
+ uint8_t ext1:1, dlci_lsb:4, space1:3;
+ uint8_t ext2:1, spare2:3, new:1, delete:1, active:1, reserved:1;
+#endif
+} __attribute__((packed));
+
+/* RX Message: 14 [ 00 01 03 08 00 75 95 01 01 00 03 02 01 00 ] */
+/* RX Message: 13 [ 00 01 03 08 00 75 51 01 00 53 02 01 00 ] */
+
+const struct value_string osmo_fr_role_names[] = {
+ { FR_ROLE_USER_EQUIPMENT, "USER" },
+ { FR_ROLE_NETWORK_EQUIPMENT, "NETWORK" },
+ { 0, NULL }
+};
+
+/* Table A.4/Q.933 */
+struct osmo_tdef fr_tdefs[] = {
+ {
+ .T=391,
+ .default_val = 10,
+ .min_val = 5,
+ .max_val = 30,
+ .desc = "Link integrity verification polling timer",
+ .unit = OSMO_TDEF_S,
+ }, {
+ .T=392,
+ .default_val = 15,
+ .min_val = 5,
+ .max_val = 30,
+ .desc = "Polling verification timer",
+ .unit = OSMO_TDEF_S,
+ },
+ {}
+};
+
+static const struct tlv_definition q933_att_tlvdef = {
+ .def = {
+ [Q933_IEI_REPORT_TYPE] = { TLV_TYPE_TLV },
+ [Q933_IEI_LINK_INT_VERIF] = { TLV_TYPE_TLV },
+ [Q933_IEI_PVC_STATUS] = { TLV_TYPE_TLV },
+ },
+};
+
+static void check_link_state(struct osmo_fr_link *link, bool valid);
+
+static inline uint16_t q922_to_dlci(const uint8_t *hdr)
+{
+ return ((hdr[0] & 0xFC) << 2) | ((hdr[1] & 0xF0) >> 4);
+}
+
+
+static inline void dlci_to_q922(uint8_t *hdr, uint16_t dlci)
+{
+ hdr[0] = (dlci >> 2) & 0xFC;
+ hdr[1] = ((dlci << 4) & 0xF0) | 0x01;
+}
+
+static void dlc_set_active(struct osmo_fr_dlc *dlc, bool active)
+{
+ if (active == dlc->active)
+ return;
+
+ dlc->active = active;
+
+ LOGPFRL(dlc->link, LOGL_NOTICE, "DLCI %u became %s\n", dlc->dlci, active ? "active" : "inactive");
+ if (dlc->status_cb)
+ dlc->status_cb(dlc, dlc->cb_data, active);
+}
+
+/* allocate a message buffer and put Q.933 Annex A headers (L2 + L3) */
+static struct msgb *q933_msgb_alloc(uint16_t dlci, uint8_t prot_disc, uint8_t msg_type)
+{
+ struct msgb *msg = msgb_alloc_headroom(1600+64, 64, "FR Q.933 Tx");
+ struct q933_a_hdr *qh;
+
+ if (!msg)
+ return NULL;
+
+ msg->l1h = msgb_put(msg, 2);
+ dlci_to_q922(msg->l1h, dlci);
+
+ /* LAPF UI control */
+ msg->l2h = msgb_put(msg, 1);
+ *msg->l2h = LAPF_UI;
+
+ msg->l3h = msgb_put(msg, sizeof(*qh));
+ qh = (struct q933_a_hdr *) msg->l3h;
+ qh->prot_disc = prot_disc;
+ qh->call_ref = LMI_Q933A_CALLREF;
+ qh->msg_type = msg_type;
+
+ return msg;
+}
+
+/* obtain the [next] transmit sequence number */
+static uint8_t link_get_tx_seq(struct osmo_fr_link *link)
+{
+ /* The {user equipment, network} increments the send sequence
+ * counter using modulo 256. The value zero is skipped. */
+ link->last_tx_seq++;
+ if (link->last_tx_seq == 0)
+ link->last_tx_seq++;
+
+ return link->last_tx_seq;
+}
+
+/* Append PVC Status IE according to Q.933 A.3.2 */
+static void msgb_put_link_int_verif(struct msgb *msg, struct osmo_fr_link *link)
+{
+ uint8_t link_int_tx[2];
+ link_int_tx[0] = link_get_tx_seq(link);
+ link_int_tx[1] = link->last_rx_seq;
+ msgb_tlv_put(msg, Q933_IEI_LINK_INT_VERIF, 2, link_int_tx);
+}
+
+static void dlc_destroy(struct osmo_fr_dlc *dlc)
+{
+ llist_del(&dlc->list);
+ talloc_free(dlc);
+}
+
+/* Append PVC Status IE according to Q.933 A.3.3 */
+static void msgb_put_pvc_status(struct msgb *msg, struct osmo_fr_dlc *dlc)
+{
+ uint8_t ie[3];
+
+ ie[0] = (dlc->dlci >> 4) & 0x3f;
+ /* extension bits */
+ ie[1] = 0x80 | ((dlc->dlci & 0xf) << 3);
+ /* extension bits */
+ ie[2] = 0x80;
+
+ /* FIXME: validate: this status should be added as long it's not yet acked by the remote */
+ if (dlc->active)
+ ie[2] |= Q933_PVC_STATUS_DLC_ACTIVE;
+
+ if (dlc->add) {
+ ie[2] |= Q933_PVC_STATUS_DLC_NEW;
+ /* we've reported it as new once, reset the status */
+ }
+
+ if (dlc->del) {
+ ie[2] |= Q933_PVC_STATUS_DLC_DELETE;
+ /* we've reported it as deleted once, destroy it */
+ dlc_destroy(dlc);
+ }
+
+ msgb_tlv_put(msg, Q933_IEI_PVC_STATUS, 3, ie);
+}
+
+/* Send a Q.933 STATUS ENQUIRY given type over given link */
+static int tx_lmi_q933_status_enq(struct osmo_fr_link *link, uint8_t rep_type)
+{
+ struct msgb *resp;
+
+ resp = q933_msgb_alloc(0, Q931_PDISC_CC, Q931_MSGT_STATUS_ENQUIRY);
+ if (!resp)
+ return -1;
+ resp->dst = link;
+ link->expected_rep = rep_type;
+
+ /* Table A.2/Q.933 */
+ msgb_tlv_put(resp, Q933_IEI_REPORT_TYPE, 1, &rep_type);
+ msgb_put_link_int_verif(resp, link);
+
+ return link->tx_cb(link->cb_data, resp);
+}
+
+/* Send a Q.933 STATUS of given type over given link */
+static int tx_lmi_q933_status(struct osmo_fr_link *link, uint8_t rep_type)
+{
+ struct osmo_fr_dlc *dlc;
+ struct msgb *resp;
+
+ resp = q933_msgb_alloc(0, Q931_PDISC_CC, Q931_MSGT_STATUS);
+ if (!resp)
+ return -1;
+
+ resp->dst = link;
+
+ /* Table A.1/Q.933 */
+ msgb_tlv_put(resp, Q933_IEI_REPORT_TYPE, 1, &rep_type);
+ switch (rep_type) {
+ case Q933_REPT_FULL_STATUS:
+ msgb_put_link_int_verif(resp, link);
+ llist_for_each_entry(dlc, &link->dlc_list, list) {
+ if (dlc->add || dlc->del)
+ dlc->state_send = true;
+
+ msgb_put_pvc_status(resp, dlc);
+ }
+ break;
+ case Q933_REPT_LINK_INTEGRITY_VERIF:
+ msgb_put_link_int_verif(resp, link);
+ llist_for_each_entry(dlc, &link->dlc_list, list) {
+ if (dlc->add || dlc->del) {
+ msgb_put_pvc_status(resp, dlc);
+ dlc->state_send = true;
+ }
+ }
+ break;
+ case Q933_REPT_SINGLE_PVC_ASYNC_STS:
+ llist_for_each_entry(dlc, &link->dlc_list, list)
+ msgb_put_pvc_status(resp, dlc);
+ break;
+ }
+
+ return link->tx_cb(link->cb_data, resp);
+}
+
+
+static void link_set_failed(struct osmo_fr_link *link)
+{
+ struct osmo_fr_dlc *dlc;
+
+ LOGPFRL(link, LOGL_NOTICE, "Link failed\n");
+ link->state = false;
+ if (link->status_cb)
+ link->status_cb(link, link->cb_data, link->state);
+
+ llist_for_each_entry(dlc, &link->dlc_list, list) {
+ dlc_set_active(dlc, false);
+ }
+}
+
+/* Q.933 */
+static int rx_lmi_q933_status_enq(struct msgb *msg, struct tlv_parsed *tp)
+{
+ struct osmo_fr_link *link = msg->dst;
+ struct osmo_fr_dlc *dlc;
+ const uint8_t *link_int_rx;
+ uint8_t rep_type;
+
+ OSMO_ASSERT(link);
+
+ if (link->role == FR_ROLE_USER_EQUIPMENT) {
+ LOGPFRL(link, LOGL_ERROR, "STATUS-ENQ aren't supported in role user\n");
+ return -1;
+ }
+
+ /* check for mandatory IEs */
+ if (!TLVP_PRES_LEN(tp, Q933_IEI_REPORT_TYPE, 1) ||
+ !TLVP_PRES_LEN(tp, Q933_IEI_LINK_INT_VERIF, 2))
+ return -1;
+
+ rep_type = *TLVP_VAL(tp, Q933_IEI_REPORT_TYPE);
+
+ link_int_rx = TLVP_VAL(tp, Q933_IEI_LINK_INT_VERIF);
+ link->last_rx_seq = link_int_rx[0];
+
+ /* this is a bit of a hack. Q.933 explicitly forbids either side from ever
+ * sending a sequence number of '0'. Values start from '1' and are modulo 256,
+ * but '0' is always skipped. So if the peer is sending us a "last received
+ * sequence number of '0' it means it has not yet received any packets from us,
+ * which in turn can only mean that it has just been restarted. Let's treat
+ * this as "service affecting condition" and notify upper layers. This helps
+ * particularly in recovering from rapidly re-starting peers, where the Q.933
+ * nor NS have time to actually detect the connection was lost. Se OS#4974 */
+ if (link_int_rx[1] == 0) {
+ link_set_failed(link);
+ /* the network checks the receive sequence number received from
+ * the user equipment against its send sequence counter */
+ } else if (link_int_rx[1] != link->last_tx_seq) {
+ check_link_state(link, false);
+ link->err_count++;
+ } else {
+ check_link_state(link, true);
+ /* confirm DLC state changes */
+ llist_for_each_entry(dlc, &link->dlc_list, list) {
+ if (!dlc->state_send)
+ continue;
+
+ if (dlc->add) {
+ dlc_set_active(dlc, link->state);
+ dlc->add = false;
+ }
+
+ if (dlc->del) {
+ dlc->del = false;
+ }
+
+ dlc->state_send = false;
+ }
+ }
+
+
+ /* The network responds to each STATUS ENQUIRY message with a
+ * STATUS message and resets the T392 timer */
+ osmo_timer_schedule(&link->t392, osmo_tdef_get(link->net->T_defs, 392, OSMO_TDEF_S, 15), 0);
+
+ return tx_lmi_q933_status(link, rep_type);
+}
+
+/* check if the link become active.
+ * The link becomes active when enough times a STATUS/STATUS ENQUIRY arrives without any loss.
+ * Look at the last N393 STATUS/STATUS ENQUIRY PDUs. The link is valid if at least N392
+ * got received.
+ * param[in] valid contains the status of the last packet */
+static void check_link_state(struct osmo_fr_link *link, bool valid)
+{
+ unsigned int last, i;
+ unsigned int carry = 0;
+ struct osmo_fr_dlc *dlc;
+
+ link->succeed <<= 1;
+ if (valid)
+ link->succeed |= 1;
+
+ /* count the bits */
+ last = link->succeed & ((1 << link->net->n393) - 1);
+ for (i = 0; i < link->net->n393; i++)
+ if (last & (1 << i))
+ carry++;
+
+ if (link->net->n393 - carry >= link->net->n392) {
+ /* failing link */
+ if (!link->state)
+ return;
+
+ link_set_failed(link);
+ } else {
+ /* good link */
+ if (link->state)
+ return;
+
+ LOGPFRL(link, LOGL_NOTICE, "Link recovered\n");
+ link->state = true;
+ if (link->status_cb)
+ link->status_cb(link, link->cb_data, link->state);
+
+ if (link->role == FR_ROLE_USER_EQUIPMENT) {
+ /* make sure the next STATUS ENQUIRY is for a full
+ * status report to get the configred DLCs ASAP */
+ link->polling_count = 0;
+ /* we must not proceed further below if we're in user role,
+ * as otherwise link recovery would set all DLCs as active */
+ return;
+ }
+
+ llist_for_each_entry(dlc, &link->dlc_list, list) {
+ if (!dlc->add && !dlc->del)
+ dlc_set_active(dlc, true);
+ }
+ }
+}
+
+static int validate_pvc_status(struct tlv_parsed *tp, size_t tp_len)
+{
+ size_t i;
+ uint16_t len = 0;
+
+ for (i = 0; i < tp_len; i++) {
+ if (!TLVP_PRESENT(&tp[i], Q933_IEI_PVC_STATUS))
+ continue;
+
+ /* PVC status can be 2 or 3 bytes. If the PVC is bigger
+ * ignore this to be compatible to future extensions. */
+ len = TLVP_LEN(&tp[i], Q933_IEI_PVC_STATUS);
+ if (len <= 1) {
+ return -EINVAL;
+ }
+ /* FIXME: validate correct flags: are some flags invalid at the same time? */
+ }
+
+ return 0;
+}
+
+static int parse_full_pvc_status(struct osmo_fr_link *link, struct tlv_parsed *tp, size_t tp_len)
+{
+ size_t i;
+ int err = 0;
+ struct osmo_fr_dlc *dlc, *tmp;
+ struct q933_a_pvc_sts *pvc;
+ uint16_t dlci = 0;
+ uint16_t *dlcis = talloc_zero_array(link, uint16_t, tp_len);
+ if (!dlcis)
+ return -ENOMEM;
+
+ /* first run validate all PVCs */
+ err = validate_pvc_status(tp, tp_len);
+ if (err < 0)
+ goto out;
+
+ for (i = 0; i < tp_len; i++) {
+ if (!TLVP_PRESENT(&tp[i], Q933_IEI_PVC_STATUS))
+ continue;
+
+ /* parse only 3 byte PVCs */
+ pvc = (struct q933_a_pvc_sts *) TLVP_VAL_MINLEN(
+ &tp[i],
+ Q933_IEI_PVC_STATUS,
+ sizeof(struct q933_a_pvc_sts));
+ if (!pvc)
+ continue;
+
+ dlci = ((pvc->dlci_msb & 0x3f) << 4) | (pvc->dlci_lsb & 0xf);
+ dlcis[i] = dlci;
+ dlc = osmo_fr_dlc_by_dlci(link, dlci);
+ if (!dlc) {
+ dlc = osmo_fr_dlc_alloc(link, dlci);
+ if (!dlc) {
+ LOGPFRL(link, LOGL_ERROR, "Could not create DLC %d\n", dlci);
+ continue;
+ }
+ }
+
+ /* Figure A.3/Q.933: The delete bit is only applicable for timely notification
+ * using the optional single PVC asynchronous status report.
+ * Ignoring the delete. */
+ dlc->add = pvc->new;
+ dlc_set_active(dlc, pvc->active);
+ dlc->del = 0;
+ }
+
+ /* check if all dlc are present in PVC Status */
+ llist_for_each_entry_safe(dlc, tmp, &link->dlc_list, list) {
+ bool found = false;
+ for (i = 0; i < tp_len; i++) {
+ if (dlcis[i] == dlc->dlci) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ dlc_set_active(dlc, false);
+ dlc->del = true;
+ }
+ }
+
+ return 0;
+out:
+ talloc_free(dlcis);
+ return err;
+}
+
+static int parse_link_pvc_status(struct osmo_fr_link *link, struct tlv_parsed *tp, size_t tp_len)
+{
+ int err;
+ size_t i;
+ struct q933_a_pvc_sts *pvc;
+ struct osmo_fr_dlc *dlc;
+ uint16_t dlci = 0;
+
+ err = validate_pvc_status(tp, tp_len);
+ if (err < 0)
+ return err;
+
+ for (i = 0; i < tp_len; i++) {
+ if (!TLVP_PRESENT(&tp[i], Q933_IEI_PVC_STATUS))
+ continue;
+
+ /* parse only 3 byte PVCs */
+ pvc = (struct q933_a_pvc_sts *) TLVP_VAL_MINLEN(
+ &tp[i],
+ Q933_IEI_PVC_STATUS,
+ sizeof(struct q933_a_pvc_sts));
+ if (!pvc)
+ continue;
+
+ dlci = ((pvc->dlci_msb & 0x3f) << 4) | (pvc->dlci_lsb & 0xf);
+ dlc = osmo_fr_dlc_by_dlci(link, dlci);
+ if (!dlc) {
+ /* don't create dlc's for the ones which are about to be deleted. */
+ if (pvc->delete)
+ continue;
+
+ dlc = osmo_fr_dlc_alloc(link, dlci);
+ if (!dlc) {
+ LOGPFRL(link, LOGL_ERROR, "Rx STATUS: Could not create DLC %d\n", dlci);
+ continue;
+ }
+ }
+
+ if (pvc->delete) {
+ dlc->del = 1;
+ } else {
+ dlc->add = pvc->new;
+ dlc_set_active(dlc, pvc->active);
+ dlc->del = 0;
+ }
+ }
+
+ return 0;
+}
+
+static size_t count_pvc_status(struct tlv_parsed *tp, size_t tp_len)
+{
+ size_t i, count = 0;
+ for (i = 0; i < tp_len; i++) {
+ if (!TLVP_PRESENT(&tp[i], Q933_IEI_PVC_STATUS))
+ continue;
+ count++;
+ }
+
+ return count;
+}
+
+static int rx_lmi_q933_status(struct msgb *msg, struct tlv_parsed *tp)
+{
+ struct osmo_fr_link *link = msg->dst;
+ const uint8_t *link_int_rx;
+ uint8_t rep_type;
+
+ OSMO_ASSERT(link);
+
+ if (link->role == FR_ROLE_NETWORK_EQUIPMENT) {
+ LOGPFRL(link, LOGL_ERROR, "Rx STATUS: STATUS aren't supported in role network\n");
+ return -1;
+ }
+
+ /* check for mandatory IEs */
+ if (!TLVP_PRES_LEN(tp, Q933_IEI_REPORT_TYPE, 1)) {
+ LOGPFRL(link, LOGL_NOTICE, "Rx STATUS: Missing TLV Q933 Report Type\n");
+ return -1;
+ }
+
+ rep_type = *TLVP_VAL(tp, Q933_IEI_REPORT_TYPE);
+
+ switch (rep_type) {
+ case Q933_REPT_FULL_STATUS:
+ case Q933_REPT_LINK_INTEGRITY_VERIF:
+ if (rep_type != link->expected_rep) {
+ LOGPFRL(link, LOGL_NOTICE, "Rx STATUS: Unexpected Q933 report type (got 0x%x != exp 0x%x)\n",
+ rep_type, link->expected_rep);
+ return -1;
+ }
+
+ if (!TLVP_PRES_LEN(tp, Q933_IEI_LINK_INT_VERIF, 2)) {
+ LOGPFRL(link, LOGL_NOTICE, "Rx STATUS: Missing TLV Q933 Link Integrety Verification\n");
+ return -1;
+ }
+ link_int_rx = TLVP_VAL(tp, Q933_IEI_LINK_INT_VERIF);
+ link->last_rx_seq = link_int_rx[0];
+ /* The received receive sequence number is not valid if
+ * it is not equal to the last transmitted send sequence
+ * number. Ignore messages containing this error. As a
+ * result, timer T391 expires and the user then
+ * increments the error count. */
+ if (link_int_rx[1] != link->last_tx_seq)
+ return 0;
+ break;
+ case Q933_REPT_SINGLE_PVC_ASYNC_STS:
+ default:
+ return -1;
+ }
+
+ check_link_state(link, true);
+ if (count_pvc_status(tp, MAX_SUPPORTED_PVC + 1) > MAX_SUPPORTED_PVC) {
+ LOGPFRL(link, LOGL_ERROR, "Rx STATUS: Too many PVC! Only %d are supported!\n", MAX_SUPPORTED_PVC);
+ }
+
+ switch (rep_type) {
+ case Q933_REPT_FULL_STATUS:
+ parse_full_pvc_status(link, tp, MAX_SUPPORTED_PVC);
+ break;
+ case Q933_REPT_LINK_INTEGRITY_VERIF:
+ parse_link_pvc_status(link, tp, MAX_SUPPORTED_PVC);
+ break;
+ default:
+ break;
+ }
+
+ /* The network responds to each STATUS ENQUIRY message with a
+ * STATUS message and resets the T392 timer */
+ osmo_timer_schedule(&link->t392, osmo_tdef_get(link->net->T_defs, 392, OSMO_TDEF_S, 15), 0);
+
+ return 0;
+}
+
+static int rx_lmi_q922(struct msgb *msg)
+{
+ struct osmo_fr_link *link = msg->dst;
+ struct q933_a_hdr *qh;
+ /* the + 1 is used to detect more than MAX_SUPPORTED_PVC */
+ struct tlv_parsed tp[MAX_SUPPORTED_PVC + 1];
+ uint8_t *lapf;
+ int rc;
+
+ OSMO_ASSERT(link);
+
+ if (msgb_l2len(msg) < 1)
+ return -1;
+ lapf = msgb_l2(msg);
+
+ /* we only support LAPF UI frames */
+ if (lapf[0] != LAPF_UI)
+ return -1;
+
+ msg->l3h = msg->l2h + 1;
+ if (msgb_l3len(msg) < 3)
+ return -1;
+
+ qh = (struct q933_a_hdr *) msgb_l3(msg);
+ if (qh->prot_disc != Q931_PDISC_CC) {
+ LOGPFRL(link, LOGL_NOTICE,
+ "Rx unsupported LMI protocol discriminator %u\n", qh->prot_disc);
+ return -1;
+ }
+
+ rc = tlv_parse2(tp, MAX_SUPPORTED_PVC + 1, &q933_att_tlvdef,
+ msgb_l3(msg) + sizeof(*qh),
+ msgb_l3len(msg) - sizeof(*qh), 0, 0);
+ if (rc < 0) {
+ LOGPFRL(link, LOGL_NOTICE,
+ "Failed to parse TLVs in LMI message type %u\n", qh->msg_type);
+ return rc;
+ }
+
+ switch (qh->msg_type) {
+ case Q931_MSGT_STATUS_ENQUIRY:
+ rc = rx_lmi_q933_status_enq(msg, tp);
+ break;
+ case Q931_MSGT_STATUS:
+ rc = rx_lmi_q933_status(msg, tp);
+ break;
+ default:
+ LOGPFRL(link, LOGL_NOTICE,
+ "Rx unsupported LMI message type %u\n", qh->msg_type);
+ rc = -1;
+ break;
+ }
+ msgb_free(msg);
+
+ return rc;
+}
+
+int osmo_fr_rx(struct msgb *msg)
+{
+ int rc = 0;
+ uint8_t *frh;
+ uint16_t dlci;
+ struct osmo_fr_dlc *dlc;
+ struct osmo_fr_link *link = msg->dst;
+
+ OSMO_ASSERT(link);
+
+ if (msgb_length(msg) < 2) {
+ LOGPFRL(link, LOGL_ERROR, "Rx short FR header: %u bytes\n", msgb_length(msg));
+ rc = -1;
+ goto out;
+ }
+
+ frh = msg->l1h = msgb_data(msg);
+ if (frh[0] & 0x01) {
+ LOGPFRL(link, LOGL_NOTICE, "Rx Unsupported single-byte FR address\n");
+ rc = -1;
+ goto out;
+ }
+ if ((frh[1] & 0x0f) != 0x01) {
+ LOGPFRL(link, LOGL_NOTICE, "Rx Unknown second FR octet 0x%02x\n", frh[1]);
+ rc = -1;
+ goto out;
+ }
+ dlci = q922_to_dlci(frh);
+ msg->l2h = frh + 2;
+
+ switch (dlci) {
+ case LMI_Q933A_DLCI:
+ return rx_lmi_q922(msg);
+ case LMI_CISCO_DLCI:
+ LOGPFRL(link, LOGL_ERROR, "Rx Unsupported FR DLCI %u\n", dlci);
+ goto out;
+ }
+
+ if (!link->state) {
+ LOGPFRL(link, LOGL_NOTICE, "Link is not reliable. Discarding Rx PDU on DLCI %d\n", dlci);
+ goto out;
+ }
+
+ dlc = osmo_fr_dlc_by_dlci(link, dlci);
+ if (dlc) {
+ if (dlc->active) {
+ /* dispatch to handler of respective DLC */
+ msg->dst = dlc;
+ return dlc->rx_cb(dlc->cb_data, msg);
+ } else {
+ LOGPFRL(link, LOGL_NOTICE, "DLCI %u not yet active. Discarding Rx PDU\n", dlci);
+ }
+ } else {
+ if (link->unknown_dlc_rx_cb)
+ return link->unknown_dlc_rx_cb(link->unknown_dlc_rx_cb_data, msg);
+ else
+ LOGPFRL(link, LOGL_NOTICE, "DLCI %u doesn't exist. Discarding Rx PDU\n", dlci);
+ }
+
+out:
+ msgb_free(msg);
+
+ return rc;
+}
+
+int osmo_fr_tx_dlc(struct msgb *msg)
+{
+ uint8_t *frh;
+ struct osmo_fr_dlc *dlc = msg->dst;
+ struct osmo_fr_link *link = dlc->link;
+
+ OSMO_ASSERT(dlc);
+ OSMO_ASSERT(link);
+
+ if (!link->state) {
+ LOGPFRL(link, LOGL_NOTICE, "Link is not reliable (yet), discarding Tx\n");
+ msgb_free(msg);
+ return -1;
+ }
+ if (!dlc->active) {
+ LOGPFRL(link, LOGL_NOTICE, "DLCI %u is not active (yet), discarding Tx\n", dlc->dlci);
+ msgb_free(msg);
+ return -1;
+ }
+ LOGPFRL(link, LOGL_DEBUG, "DLCI %u is active, sending message\n", dlc->dlci);
+
+ if (msgb_headroom(msg) < 2) {
+ msgb_free(msg);
+ return -ENOSPC;
+ }
+
+ frh = msgb_push(msg, 2);
+ dlci_to_q922(frh, dlc->dlci);
+
+ msg->dst = link;
+ return link->tx_cb(link->cb_data, msg);
+}
+
+/* Every T391 seconds, the user equipment sends a STATUS ENQUIRY
+ * message to the network and resets its polling timer (T391). */
+static void fr_t391_cb(void *data)
+{
+ struct osmo_fr_link *link = data;
+
+ OSMO_ASSERT(link);
+
+ if (link->polling_count % link->net->n391 == 0)
+ tx_lmi_q933_status_enq(link, Q933_REPT_FULL_STATUS);
+ else
+ tx_lmi_q933_status_enq(link, Q933_REPT_LINK_INTEGRITY_VERIF);
+ link->polling_count++;
+ osmo_timer_schedule(&link->t391, osmo_tdef_get(link->net->T_defs, 391, OSMO_TDEF_S, 10), 0);
+}
+
+static void fr_t392_cb(void *data)
+{
+ struct osmo_fr_link *link = data;
+
+ OSMO_ASSERT(link);
+
+ /* A.5 The network increments the error count .. Non-receipt of
+ * a STATUS ENQUIRY within T392, which results in restarting
+ * T392 */
+ link->err_count++;
+ check_link_state(link, false);
+ osmo_timer_schedule(&link->t392, osmo_tdef_get(link->net->T_defs, 392, OSMO_TDEF_S, 15), 0);
+}
+
+/* allocate a frame relay network */
+struct osmo_fr_network *osmo_fr_network_alloc(void *ctx)
+{
+ struct osmo_fr_network *net = talloc_zero(ctx, struct osmo_fr_network);
+ if (!net)
+ return NULL;
+
+ INIT_LLIST_HEAD(&net->links);
+ net->T_defs = fr_tdefs;
+ osmo_tdefs_reset(net->T_defs);
+ net->n391 = 6;
+ net->n392 = 3;
+ net->n393 = 4;
+
+ return net;
+}
+
+void osmo_fr_network_free(struct osmo_fr_network *net)
+{
+ struct osmo_fr_link *link, *tmp;
+
+ if (!net)
+ return;
+
+ llist_for_each_entry_safe(link, tmp, &net->links, list) {
+ osmo_fr_link_free(link);
+ }
+}
+
+/* allocate a frame relay link in a given network */
+struct osmo_fr_link *osmo_fr_link_alloc(struct osmo_fr_network *net, enum osmo_fr_role role, const char *name)
+{
+ struct osmo_fr_link *link = talloc_zero(net, struct osmo_fr_link);
+ if (!link)
+ return NULL;
+ link->role = role;
+ link->net = net;
+ link->name = talloc_strdup(link, name);
+ INIT_LLIST_HEAD(&link->dlc_list);
+ llist_add_tail(&link->list, &net->links);
+
+ osmo_timer_setup(&link->t391, fr_t391_cb, link);
+ osmo_timer_setup(&link->t392, fr_t392_cb, link);
+
+ switch (role) {
+ case FR_ROLE_USER_EQUIPMENT:
+ osmo_timer_schedule(&link->t391, osmo_tdef_get(link->net->T_defs, 391, OSMO_TDEF_S, 15), 0);
+ break;
+ case FR_ROLE_NETWORK_EQUIPMENT:
+ osmo_timer_schedule(&link->t392, osmo_tdef_get(link->net->T_defs, 392, OSMO_TDEF_S, 15), 0);
+ break;
+ }
+
+ LOGPFRL(link, LOGL_INFO, "Creating frame relay link with role %s\n", osmo_fr_role_str(role));
+
+ return link;
+}
+
+void osmo_fr_link_free(struct osmo_fr_link *link)
+{
+ struct osmo_fr_dlc *dlc, *tmp;
+
+ if (!link)
+ return;
+
+ osmo_timer_del(&link->t391);
+ osmo_timer_del(&link->t392);
+
+ llist_for_each_entry_safe(dlc, tmp, &link->dlc_list, list) {
+ osmo_fr_dlc_free(dlc);
+ }
+
+ llist_del(&link->list);
+ talloc_free(link);
+}
+
+/* allocate a data link connectoin on a given framerelay link */
+struct osmo_fr_dlc *osmo_fr_dlc_alloc(struct osmo_fr_link *link, uint16_t dlci)
+{
+ struct osmo_fr_dlc *dlc = talloc_zero(link, struct osmo_fr_dlc);
+ if (!dlc)
+ return NULL;
+
+ dlc->link = link;
+ dlc->dlci = dlci;
+ dlc->active = false;
+
+ llist_add_tail(&dlc->list, &link->dlc_list);
+
+ dlc->add = true;
+ tx_lmi_q933_status(link, Q933_REPT_SINGLE_PVC_ASYNC_STS);
+
+ return dlc;
+}
+
+void osmo_fr_dlc_free(struct osmo_fr_dlc *dlc)
+{
+ llist_del(&dlc->list);
+ talloc_free(dlc);
+}
+
+/* TODO: rework osmo_fr_dlc_alloc/free with handling it's own memory.
+ * For network role: The dlc have to created by the application (e.g. vty).
+ * The dlc shouldn't free'd directly. It should be communicated to the
+ * other side and wait until it's confirmed OR the link go off and free it afterwards.
+ * For user equpment role: The dlc can be created by the application or the dlc will be created
+ * by the frame relay because the network is configuring the dlc.
+ * The dlc shouldn't be free'd. Only the handler should be set to NULL.
+ */
+
+struct osmo_fr_dlc *osmo_fr_dlc_by_dlci(struct osmo_fr_link *link, uint16_t dlci)
+{
+ struct osmo_fr_dlc *dlc;
+
+ llist_for_each_entry(dlc, &link->dlc_list, list) {
+ if (dlc->dlci == dlci)
+ return dlc;
+ }
+ return NULL;
+}
+
+
+#include <osmocom/vty/vty.h>
+#include <osmocom/vty/tdef_vty.h>
+
+static void fr_dlc_dump_vty(struct vty *vty, const struct osmo_fr_dlc *dlc)
+{
+ vty_out(vty, " FR DLC %05u: %s%s%s%s", dlc->dlci,
+ dlc->active ? "ACTIVE" : "INACTIVE",
+ dlc->add ? " ADDED" : "", dlc->del ? " DELETED" : "", VTY_NEWLINE);
+}
+
+static void fr_link_dump_vty(struct vty *vty, const struct osmo_fr_link *link)
+{
+ const struct osmo_fr_dlc *dlc;
+
+ vty_out(vty, "FR Link '%s': Role %s, LastRxSeq %u, LastTxSeq %u%s",
+ link->name, link->role == FR_ROLE_USER_EQUIPMENT ? "USER" : "NETWORK",
+ link->last_rx_seq, link->last_tx_seq, VTY_NEWLINE);
+ llist_for_each_entry(dlc, &link->dlc_list, list) {
+ fr_dlc_dump_vty(vty, dlc);
+ }
+}
+
+void osmo_fr_network_dump_vty(struct vty *vty, const struct osmo_fr_network *net)
+{
+ struct osmo_fr_link *link;
+
+ vty_out(vty, "FR Network: N391 %u, N392 %u, N393 %u%s",
+ net->n391, net->n392, net->n393, VTY_NEWLINE);
+ osmo_tdef_vty_out_all(vty, net->T_defs, " ");
+ llist_for_each_entry(link, &net->links, list) {
+ fr_link_dump_vty(vty, link);
+ }
+}
diff --git a/src/gb/gprs_bssgp.c b/src/gb/gprs_bssgp.c
index ebbfab18..7abef804 100644
--- a/src/gb/gprs_bssgp.c
+++ b/src/gb/gprs_bssgp.c
@@ -40,8 +40,8 @@
#include <osmocom/gprs/gprs_bssgp_bss.h>
#include <osmocom/gprs/gprs_ns.h>
-#include "common_vty.h"
#include "osmocom/gsm/gsm48.h"
+#include "gprs_bssgp_internal.h"
void *bssgp_tall_ctx = NULL;
@@ -77,6 +77,7 @@ static int _bssgp_tx_dl_ud(struct bssgp_flow_control *fc, struct msgb *msg,
/* callback to be backward compatible with old users which do not set the bssgp_ns_send function */
static int _gprs_ns_sendmsg(void *ctx, struct msgb *msg)
{
+ OSMO_ASSERT(bssgp_nsi);
return gprs_ns_sendmsg(bssgp_nsi, msg);
}
@@ -93,28 +94,39 @@ struct bssgp_bvc_ctx *btsctx_by_raid_cid(const struct gprs_ra_id *raid, uint16_t
return NULL;
}
-/*! Transmit a BVC-RESET message with a given nsei and bvci (Chapter 10.4.12)
+/* Transmit a BVC-RESET or BVC-RESET-ACK with a given nsei and bvci (Chapter 10.4.12)
+ * \param[in] pdu Either BSSGP_PDUT_BVC_RESET or BSSGP_PDUT_BVC_RESET_ACK
* \param[in] nsei The NSEI to transmit over
* \param[in] bvci BVCI of the BVC to reset
- * \param[in] cause The cause of the reset
+ * \param[in] cause The cause of the reset only valid for BSSGP_PDUT_BVC_RESET.
* \param[in] ra_id Pointer to the ra_id to include. If NULL no cell information will be included
* \param[in] cell_id The cell_id to include (if ra_id is not NULL)
+ * returns >= 0 on success, on error < 0.
*/
-int bssgp_tx_bvc_reset_nsei_bvci(uint16_t nsei, uint16_t bvci, enum gprs_bssgp_cause cause, const struct gprs_ra_id *ra_id, uint16_t cell_id)
+static int tx_bvc_reset_nsei_bvci(enum bssgp_pdu_type pdu, uint16_t nsei, uint16_t bvci,
+ enum gprs_bssgp_cause cause, const struct gprs_ra_id *ra_id, uint16_t cell_id)
{
struct msgb *msg = bssgp_msgb_alloc();
struct bssgp_normal_hdr *bgph =
(struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
uint16_t _bvci = osmo_htons(bvci);
+ OSMO_ASSERT(pdu == BSSGP_PDUT_BVC_RESET || pdu == BSSGP_PDUT_BVC_RESET_ACK);
+
msgb_nsei(msg) = nsei;
- msgb_bvci(msg) = 0; /* Signalling */
- bgph->pdu_type = BSSGP_PDUT_BVC_RESET;
- LOGP(DBSSGP, LOGL_NOTICE, "BSSGP (BVCI=%u) Tx BVC-RESET "
- "CAUSE=%s\n", bvci, bssgp_cause_str(cause));
+ msgb_bvci(msg) = BVCI_SIGNALLING;
+ bgph->pdu_type = pdu;
msgb_tvlv_put(msg, BSSGP_IE_BVCI, 2, (uint8_t *) &_bvci);
- msgb_tvlv_put(msg, BSSGP_IE_CAUSE, 1, (uint8_t *) &cause);
+
+ if (pdu == BSSGP_PDUT_BVC_RESET) {
+ msgb_tvlv_put(msg, BSSGP_IE_CAUSE, 1, (uint8_t *) &cause);
+ LOGP(DLBSSGP, LOGL_NOTICE, "BSSGP (BVCI=%u) Tx BVC-RESET "
+ "CAUSE=%s\n", bvci, bssgp_cause_str(cause));
+ } else {
+ LOGP(DLBSSGP, LOGL_NOTICE, "BSSGP (BVCI=%u) Tx BVC-RESET-ACK\n", bvci);
+ }
+
if (ra_id) {
uint8_t bssgp_cid[8];
bssgp_create_cell_id(bssgp_cid, ra_id, cell_id);
@@ -126,6 +138,31 @@ int bssgp_tx_bvc_reset_nsei_bvci(uint16_t nsei, uint16_t bvci, enum gprs_bssgp_c
return bssgp_ns_send(bssgp_ns_send_data, msg);
}
+/*! Transmit a BVC-RESET message with a given nsei and bvci (Chapter 10.4.12)
+ * \param[in] nsei The NSEI to transmit over
+ * \param[in] bvci BVCI of the BVC to reset
+ * \param[in] cause The cause of the reset
+ * \param[in] ra_id Pointer to the ra_id to include. If NULL no cell information will be included
+ * \param[in] cell_id The cell_id to include (if ra_id is not NULL)
+ * returns >= 0 on success, on error < 0.
+ */
+int bssgp_tx_bvc_reset_nsei_bvci(uint16_t nsei, uint16_t bvci, enum gprs_bssgp_cause cause, const struct gprs_ra_id *ra_id, uint16_t cell_id)
+{
+ return tx_bvc_reset_nsei_bvci(BSSGP_PDUT_BVC_RESET, nsei, bvci, cause, ra_id, cell_id);
+}
+
+/*! Transmit a BVC-RESET-ACK message with a given nsei and bvci (Chapter 10.4.12)
+ * \param[in] nsei The NSEI to transmit over
+ * \param[in] bvci BVCI of the BVC to reset
+ * \param[in] ra_id Pointer to the ra_id to include. If NULL no cell information will be included
+ * \param[in] cell_id The cell_id to include (if ra_id is not NULL)
+ * returns >= 0 on success, on error < 0.
+ */
+int bssgp_tx_bvc_reset_ack_nsei_bvci(uint16_t nsei, uint16_t bvci, const struct gprs_ra_id *ra_id, uint16_t cell_id)
+{
+ return tx_bvc_reset_nsei_bvci(BSSGP_PDUT_BVC_RESET_ACK, nsei, bvci, 0, ra_id, cell_id);
+}
+
/*! Initiate reset procedure for all PTP BVC on a given NSEI.
*
* This function initiates reset procedure for all PTP BVC with a given cause.
@@ -140,7 +177,7 @@ int bssgp_tx_bvc_ptp_reset(uint16_t nsei, enum gprs_bssgp_cause cause)
llist_for_each_entry(bctx, &bssgp_bvc_ctxts, list) {
if (bctx->nsei == nsei && bctx->bvci != BVCI_SIGNALLING) {
- LOGP(DBSSGP, LOGL_DEBUG, "NSEI=%u/BVCI=%u RESET due to %s\n",
+ LOGP(DLBSSGP, LOGL_DEBUG, "NSEI=%u/BVCI=%u RESET due to %s\n",
nsei, bctx->bvci, bssgp_cause_str(cause));
rc = bssgp_tx_bvc_reset(bctx, bctx->bvci, cause);
if (rc < 0)
@@ -178,6 +215,7 @@ struct bssgp_bvc_ctx *btsctx_alloc(uint16_t bvci, uint16_t nsei)
return NULL;
ctx->bvci = bvci;
ctx->nsei = nsei;
+ ctx->is_sgsn = true;
/* FIXME: BVCI is not unique, only BVCI+NSEI ?!? */
ctx->ctrg = rate_ctr_group_alloc(ctx, &bssgp_ctrg_desc, bvci);
if (!ctx->ctrg)
@@ -337,7 +375,7 @@ static int bssgp_rx_bvc_reset(struct msgb *msg, struct tlv_parsed *tp,
uint16_t bvci;
bvci = tlvp_val16be(tp, BSSGP_IE_BVCI);
- DEBUGP(DBSSGP, "BSSGP BVCI=%u Rx RESET cause=%s\n", bvci,
+ DEBUGP(DLBSSGP, "BSSGP BVCI=%u Rx RESET cause=%s\n", bvci,
bssgp_cause_str(*TLVP_VAL(tp, BSSGP_IE_CAUSE)));
/* look-up or create the BTS context for this BVC */
@@ -350,19 +388,26 @@ static int bssgp_rx_bvc_reset(struct msgb *msg, struct tlv_parsed *tp,
/* When we receive a BVC-RESET PDU (at least of a PTP BVCI), the BSS
* informs us about its RAC + Cell ID, so we can create a mapping */
- if (bvci != 0 && bvci != 1) {
- if (!TLVP_PRESENT(tp, BSSGP_IE_CELL_ID)) {
- LOGP(DBSSGP, LOGL_ERROR, "BSSGP BVCI=%u Rx RESET "
+ if (bctx->is_sgsn && bvci != BVCI_SIGNALLING && bvci != BVCI_PTM) {
+ if (!TLVP_PRES_LEN(tp, BSSGP_IE_CELL_ID, 8)) {
+ LOGP(DLBSSGP, LOGL_ERROR, "BSSGP BVCI=%u Rx RESET "
"missing mandatory IE\n", bvci);
return -EINVAL;
}
/* actually extract RAC / CID */
bctx->cell_id = bssgp_parse_cell_id(&bctx->ra_id,
TLVP_VAL(tp, BSSGP_IE_CELL_ID));
- LOGP(DBSSGP, LOGL_NOTICE, "Cell %s CI %u on BVCI %u\n",
+ LOGP(DLBSSGP, LOGL_NOTICE, "Cell %s CI %u on BVCI %u\n",
osmo_rai_name(&bctx->ra_id), bctx->cell_id, bvci);
}
+ /* Acknowledge the RESET to the BTS */
+ if (bvci == BVCI_SIGNALLING || bvci == BVCI_PTM || bctx->is_sgsn)
+ bssgp_tx_simple_bvci(BSSGP_PDUT_BVC_RESET_ACK,
+ nsei, bvci, ns_bvci);
+ else
+ bssgp_tx_bvc_reset_ack_nsei_bvci(nsei, bvci, &bctx->ra_id, bctx->cell_id);
+
/* Send NM_BVC_RESET.ind to NM */
memset(&nmp, 0, sizeof(nmp));
nmp.nsei = nsei;
@@ -372,10 +417,6 @@ static int bssgp_rx_bvc_reset(struct msgb *msg, struct tlv_parsed *tp,
osmo_prim_init(&nmp.oph, SAP_BSSGP_NM, PRIM_NM_BVC_RESET,
PRIM_OP_INDICATION, msg);
bssgp_prim_cb(&nmp.oph, NULL);
-
- /* Acknowledge the RESET to the BTS */
- bssgp_tx_simple_bvci(BSSGP_PDUT_BVC_RESET_ACK,
- nsei, bvci, ns_bvci);
return 0;
}
@@ -388,20 +429,20 @@ static int bssgp_rx_bvc_block(struct msgb *msg, struct tlv_parsed *tp)
bvci = tlvp_val16be(tp, BSSGP_IE_BVCI);
if (bvci == BVCI_SIGNALLING) {
/* 8.3.2: Signalling BVC shall never be blocked */
- LOGP(DBSSGP, LOGL_ERROR, "NSEI=%u/BVCI=%u "
+ LOGP(DLBSSGP, LOGL_ERROR, "NSEI=%u/BVCI=%u "
"received block for signalling BVC!?!\n",
nsei, msgb_bvci(msg));
return 0;
}
- LOGP(DBSSGP, LOGL_INFO, "BSSGP Rx BVCI=%u BVC-BLOCK\n", bvci);
+ LOGP(DLBSSGP, LOGL_INFO, "BSSGP Rx BVCI=%u BVC-BLOCK\n", bvci);
ptp_ctx = btsctx_by_bvci_nsei(bvci, nsei);
if (!ptp_ctx)
return bssgp_tx_status(BSSGP_CAUSE_UNKNOWN_BVCI, &bvci, msg);
ptp_ctx->state |= BVC_S_BLOCKED;
- rate_ctr_inc(&ptp_ctx->ctrg->ctr[BSSGP_CTR_BLOCKED]);
+ rate_ctr_inc(rate_ctr_group_get_ctr(ptp_ctx->ctrg, BSSGP_CTR_BLOCKED));
/* Send NM_BVC_BLOCK.ind to NM */
memset(&nmp, 0, sizeof(nmp));
@@ -426,13 +467,13 @@ static int bssgp_rx_bvc_unblock(struct msgb *msg, struct tlv_parsed *tp)
bvci = tlvp_val16be(tp, BSSGP_IE_BVCI);
if (bvci == BVCI_SIGNALLING) {
/* 8.3.2: Signalling BVC shall never be blocked */
- LOGP(DBSSGP, LOGL_ERROR, "NSEI=%u/BVCI=%u "
+ LOGP(DLBSSGP, LOGL_ERROR, "NSEI=%u/BVCI=%u "
"received unblock for signalling BVC!?!\n",
nsei, msgb_bvci(msg));
return 0;
}
- DEBUGP(DBSSGP, "BSSGP BVCI=%u Rx BVC-UNBLOCK\n", bvci);
+ DEBUGP(DLBSSGP, "BSSGP BVCI=%u Rx BVC-UNBLOCK\n", bvci);
ptp_ctx = btsctx_by_bvci_nsei(bvci, nsei);
if (!ptp_ctx)
@@ -464,12 +505,12 @@ static int bssgp_rx_ul_ud(struct msgb *msg, struct tlv_parsed *tp,
/* extract TLLI and parse TLV IEs */
msgb_tlli(msg) = osmo_ntohl(budh->tlli);
- DEBUGP(DBSSGP, "BSSGP TLLI=0x%08x Rx UPLINK-UNITDATA\n", msgb_tlli(msg));
+ DEBUGP(DLBSSGP, "BSSGP TLLI=0x%08x Rx UPLINK-UNITDATA\n", msgb_tlli(msg));
/* Cell ID and LLC_PDU are the only mandatory IE */
- if (!TLVP_PRESENT(tp, BSSGP_IE_CELL_ID) ||
+ if (!TLVP_PRES_LEN(tp, BSSGP_IE_CELL_ID, 8) ||
!TLVP_PRESENT(tp, BSSGP_IE_LLC_PDU)) {
- LOGP(DBSSGP, LOGL_ERROR, "BSSGP TLLI=0x%08x Rx UL-UD "
+ LOGP(DLBSSGP, LOGL_ERROR, "BSSGP TLLI=0x%08x Rx UL-UD "
"missing mandatory IE\n", msgb_tlli(msg));
return bssgp_tx_status(BSSGP_CAUSE_MISSING_MAND_IE, NULL, msg);
}
@@ -497,16 +538,16 @@ static int bssgp_rx_suspend(struct msgb *msg, struct tlv_parsed *tp)
uint16_t ns_bvci = msgb_bvci(msg), nsei = msgb_nsei(msg);
int rc;
- if (!TLVP_PRESENT(tp, BSSGP_IE_TLLI) ||
- !TLVP_PRESENT(tp, BSSGP_IE_ROUTEING_AREA)) {
- LOGP(DBSSGP, LOGL_ERROR, "BSSGP BVCI=%u Rx SUSPEND "
+ if (!TLVP_PRES_LEN(tp, BSSGP_IE_TLLI, 4) ||
+ !TLVP_PRES_LEN(tp, BSSGP_IE_ROUTEING_AREA, 6)) {
+ LOGP(DLBSSGP, LOGL_ERROR, "BSSGP BVCI=%u Rx SUSPEND "
"missing mandatory IE\n", ns_bvci);
return bssgp_tx_status(BSSGP_CAUSE_MISSING_MAND_IE, NULL, msg);
}
tlli = tlvp_val32be(tp, BSSGP_IE_TLLI);
- DEBUGP(DBSSGP, "BSSGP BVCI=%u TLLI=0x%08x Rx SUSPEND\n",
+ DEBUGP(DLBSSGP, "BSSGP BVCI=%u TLLI=0x%08x Rx SUSPEND\n",
ns_bvci, tlli);
gsm48_parse_ra(&raid, TLVP_VAL(tp, BSSGP_IE_ROUTEING_AREA));
@@ -538,10 +579,10 @@ static int bssgp_rx_resume(struct msgb *msg, struct tlv_parsed *tp)
uint16_t ns_bvci = msgb_bvci(msg), nsei = msgb_nsei(msg);
int rc;
- if (!TLVP_PRESENT(tp, BSSGP_IE_TLLI) ||
- !TLVP_PRESENT(tp, BSSGP_IE_ROUTEING_AREA) ||
- !TLVP_PRESENT(tp, BSSGP_IE_SUSPEND_REF_NR)) {
- LOGP(DBSSGP, LOGL_ERROR, "BSSGP BVCI=%u Rx RESUME "
+ if (!TLVP_PRES_LEN(tp, BSSGP_IE_TLLI, 4 ) ||
+ !TLVP_PRES_LEN(tp, BSSGP_IE_ROUTEING_AREA, 6) ||
+ !TLVP_PRES_LEN(tp, BSSGP_IE_SUSPEND_REF_NR, 1)) {
+ LOGP(DLBSSGP, LOGL_ERROR, "BSSGP BVCI=%u Rx RESUME "
"missing mandatory IE\n", ns_bvci);
return bssgp_tx_status(BSSGP_CAUSE_MISSING_MAND_IE, NULL, msg);
}
@@ -549,7 +590,7 @@ static int bssgp_rx_resume(struct msgb *msg, struct tlv_parsed *tp)
tlli = tlvp_val32be(tp, BSSGP_IE_TLLI);
suspend_ref = *TLVP_VAL(tp, BSSGP_IE_SUSPEND_REF_NR);
- DEBUGP(DBSSGP, "BSSGP BVCI=%u TLLI=0x%08x Rx RESUME\n", ns_bvci, tlli);
+ DEBUGP(DLBSSGP, "BSSGP BVCI=%u TLLI=0x%08x Rx RESUME\n", ns_bvci, tlli);
gsm48_parse_ra(&raid, TLVP_VAL(tp, BSSGP_IE_ROUTEING_AREA));
@@ -580,21 +621,21 @@ static int bssgp_rx_llc_disc(struct msgb *msg, struct tlv_parsed *tp,
uint32_t tlli = 0;
uint16_t nsei = msgb_nsei(msg);
- if (!TLVP_PRESENT(tp, BSSGP_IE_TLLI) ||
- !TLVP_PRESENT(tp, BSSGP_IE_LLC_FRAMES_DISCARDED) ||
- !TLVP_PRESENT(tp, BSSGP_IE_BVCI) ||
- !TLVP_PRESENT(tp, BSSGP_IE_NUM_OCT_AFF)) {
- LOGP(DBSSGP, LOGL_ERROR, "BSSGP BVCI=%u Rx LLC DISCARDED "
+ if (!TLVP_PRES_LEN(tp, BSSGP_IE_TLLI, 4) ||
+ !TLVP_PRES_LEN(tp, BSSGP_IE_LLC_FRAMES_DISCARDED, 1) ||
+ !TLVP_PRES_LEN(tp, BSSGP_IE_BVCI, 2) ||
+ !TLVP_PRES_LEN(tp, BSSGP_IE_NUM_OCT_AFF, 3)) {
+ LOGP(DLBSSGP, LOGL_ERROR, "BSSGP BVCI=%u Rx LLC DISCARDED "
"missing mandatory IE\n", ctx->bvci);
+ return bssgp_tx_status(BSSGP_CAUSE_MISSING_MAND_IE, NULL, msg);
}
- if (TLVP_PRESENT(tp, BSSGP_IE_TLLI))
- tlli = tlvp_val32be(tp, BSSGP_IE_TLLI);
+ tlli = tlvp_val32be(tp, BSSGP_IE_TLLI);
- DEBUGP(DBSSGP, "BSSGP BVCI=%u TLLI=%08x Rx LLC DISCARDED\n",
+ DEBUGP(DLBSSGP, "BSSGP BVCI=%u TLLI=%08x Rx LLC DISCARDED\n",
ctx->bvci, tlli);
- rate_ctr_inc(&ctx->ctrg->ctr[BSSGP_CTR_DISCARDED]);
+ rate_ctr_inc(rate_ctr_group_get_ctr(ctx->ctrg, BSSGP_CTR_DISCARDED));
/* send NM_LLC_DISCARDED to NM */
memset(&nmp, 0, sizeof(nmp));
@@ -615,27 +656,27 @@ int bssgp_rx_status(struct msgb *msg, struct tlv_parsed *tp,
struct osmo_bssgp_prim nmp;
enum gprs_bssgp_cause cause;
- if (!TLVP_PRESENT(tp, BSSGP_IE_CAUSE)) {
- LOGP(DBSSGP, LOGL_ERROR, "BSSGP BVCI=%u Rx STATUS "
+ if (!TLVP_PRES_LEN(tp, BSSGP_IE_CAUSE, 1)) {
+ LOGP(DLBSSGP, LOGL_ERROR, "BSSGP BVCI=%u Rx STATUS "
"missing mandatory IE\n", bvci);
cause = BSSGP_CAUSE_PROTO_ERR_UNSPEC;
} else {
cause = *TLVP_VAL(tp, BSSGP_IE_CAUSE);
}
- LOGP(DBSSGP, LOGL_NOTICE, "BSSGP BVCI=%u Rx BVC STATUS, cause=%s\n",
+ LOGP(DLBSSGP, LOGL_NOTICE, "BSSGP BVCI=%u Rx BVC STATUS, cause=%s\n",
bvci, bssgp_cause_str(cause));
if (cause == BSSGP_CAUSE_BVCI_BLOCKED || cause == BSSGP_CAUSE_UNKNOWN_BVCI) {
- if (!TLVP_PRESENT(tp, BSSGP_IE_BVCI))
- LOGP(DBSSGP, LOGL_ERROR,
+ if (!TLVP_PRES_LEN(tp, BSSGP_IE_BVCI, 2))
+ LOGP(DLBSSGP, LOGL_ERROR,
"BSSGP BVCI=%u Rx STATUS cause=%s "
"missing conditional BVCI IE\n",
bvci, bssgp_cause_str(cause));
}
if (bctx)
- rate_ctr_inc(&bctx->ctrg->ctr[BSSGP_CTR_STATUS]);
+ rate_ctr_inc(rate_ctr_group_get_ctr(bctx->ctrg, BSSGP_CTR_STATUS));
/* send NM_STATUS to NM */
memset(&nmp, 0, sizeof(nmp));
@@ -648,7 +689,6 @@ int bssgp_rx_status(struct msgb *msg, struct tlv_parsed *tp,
return bssgp_prim_cb(&nmp.oph, NULL);
}
-
/* One element (msgb) in a BSSGP Flow Control queue */
struct bssgp_fc_queue_element {
/* linked list of queue elements */
@@ -680,7 +720,7 @@ static void fc_timer_cb(void *data)
list);
if (bssgp_fc_needs_queueing(fc, fcqe->llc_pdu_len)) {
- LOGP(DBSSGP, LOGL_NOTICE, "BSSGP-FC: fc_timer_cb() but still "
+ LOGP(DLBSSGP, LOGL_NOTICE, "BSSGP-FC: fc_timer_cb() but still "
"not able to send PDU of %u bytes\n", fcqe->llc_pdu_len);
/* make sure we re-start the timer */
fc_queue_timer_cfg(fc);
@@ -818,7 +858,7 @@ int bssgp_fc_in(struct bssgp_flow_control *fc, struct msgb *msg,
struct timeval time_now;
if (llc_pdu_len > fc->bucket_size_max) {
- LOGP(DBSSGP, LOGL_NOTICE, "Single PDU (size=%u) is larger "
+ LOGP(DLBSSGP, LOGL_NOTICE, "Single PDU (size=%u) is larger "
"than maximum bucket size (%u)!\n", llc_pdu_len,
fc->bucket_size_max);
msgb_free(msg);
@@ -879,15 +919,15 @@ static int bssgp_rx_fc_bvc(struct msgb *msg, struct tlv_parsed *tp,
uint32_t old_leak_rate = bctx->fc->bucket_leak_rate;
uint32_t old_r_def_ms = bctx->r_default_ms;
- DEBUGP(DBSSGP, "BSSGP BVCI=%u Rx Flow Control BVC\n",
+ DEBUGP(DLBSSGP, "BSSGP BVCI=%u Rx Flow Control BVC\n",
bctx->bvci);
- if (!TLVP_PRESENT(tp, BSSGP_IE_TAG) ||
- !TLVP_PRESENT(tp, BSSGP_IE_BVC_BUCKET_SIZE) ||
- !TLVP_PRESENT(tp, BSSGP_IE_BUCKET_LEAK_RATE) ||
- !TLVP_PRESENT(tp, BSSGP_IE_BMAX_DEFAULT_MS) ||
- !TLVP_PRESENT(tp, BSSGP_IE_R_DEFAULT_MS)) {
- LOGP(DBSSGP, LOGL_ERROR, "BSSGP BVCI=%u Rx FC BVC "
+ if (!TLVP_PRES_LEN(tp, BSSGP_IE_TAG, 1) ||
+ !TLVP_PRES_LEN(tp, BSSGP_IE_BVC_BUCKET_SIZE, 2) ||
+ !TLVP_PRES_LEN(tp, BSSGP_IE_BUCKET_LEAK_RATE, 2) ||
+ !TLVP_PRES_LEN(tp, BSSGP_IE_BMAX_DEFAULT_MS, 2) ||
+ !TLVP_PRES_LEN(tp, BSSGP_IE_R_DEFAULT_MS,2)) {
+ LOGP(DLBSSGP, LOGL_ERROR, "BSSGP BVCI=%u Rx FC BVC "
"missing mandatory IE\n", bctx->bvci);
return bssgp_tx_status(BSSGP_CAUSE_MISSING_MAND_IE, NULL, msg);
}
@@ -902,17 +942,17 @@ static int bssgp_rx_fc_bvc(struct msgb *msg, struct tlv_parsed *tp,
bctx->r_default_ms = 100 * tlvp_val16be(tp, BSSGP_IE_R_DEFAULT_MS) / 8;
if (old_leak_rate != 0 && bctx->fc->bucket_leak_rate == 0)
- LOGP(DBSSGP, LOGL_NOTICE, "BSS instructs us to bucket leak "
+ LOGP(DLBSSGP, LOGL_NOTICE, "BSS instructs us to bucket leak "
"rate of 0, stopping all DL GPRS!\n");
else if (old_leak_rate == 0 && bctx->fc->bucket_leak_rate != 0)
- LOGP(DBSSGP, LOGL_NOTICE, "BSS instructs us to bucket leak "
+ LOGP(DLBSSGP, LOGL_NOTICE, "BSS instructs us to bucket leak "
"rate of != 0, restarting all DL GPRS!\n");
if (old_r_def_ms != 0 && bctx->r_default_ms == 0)
- LOGP(DBSSGP, LOGL_NOTICE, "BSS instructs us to MS default "
+ LOGP(DLBSSGP, LOGL_NOTICE, "BSS instructs us to MS default "
"bucket leak rate of 0, stopping DL GPRS!\n");
else if (old_r_def_ms == 0 && bctx->r_default_ms != 0)
- LOGP(DBSSGP, LOGL_NOTICE, "BSS instructs us to MS default "
+ LOGP(DLBSSGP, LOGL_NOTICE, "BSS instructs us to MS default "
"bucket leak rate != 0, restarting DL GPRS!\n");
/* reconfigure the timer for flow control based on new values */
@@ -949,13 +989,13 @@ static int bssgp_rx_ptp(struct msgb *msg, struct tlv_parsed *tp,
break;
case BSSGP_PDUT_RA_CAPABILITY:
/* BSS requests RA capability or IMSI */
- DEBUGP(DBSSGP, "BSSGP BVCI=%u Rx RA CAPABILITY UPDATE\n",
+ DEBUGP(DLBSSGP, "BSSGP BVCI=%u Rx RA CAPABILITY UPDATE\n",
bctx->bvci);
/* FIXME: send GMM_RA_CAPABILITY_UPDATE.ind to GMM */
/* FIXME: send RA_CAPA_UPDATE_ACK */
break;
case BSSGP_PDUT_RADIO_STATUS:
- DEBUGP(DBSSGP, "BSSGP BVCI=%u Rx RADIO STATUS\n", bctx->bvci);
+ DEBUGP(DLBSSGP, "BSSGP BVCI=%u Rx RADIO STATUS\n", bctx->bvci);
/* BSS informs us of some exception */
/* FIXME: send GMM_RADIO_STATUS.ind to GMM */
break;
@@ -965,7 +1005,7 @@ static int bssgp_rx_ptp(struct msgb *msg, struct tlv_parsed *tp,
break;
case BSSGP_PDUT_FLOW_CONTROL_MS:
/* BSS informs us of available bandwidth to one MS */
- DEBUGP(DBSSGP, "BSSGP BVCI=%u Rx Flow Control MS\n",
+ DEBUGP(DLBSSGP, "BSSGP BVCI=%u Rx Flow Control MS\n",
bctx->bvci);
/* FIXME: actually implement flow control */
/* FIXME: Send FLOW_CONTROL_MS_ACK */
@@ -973,12 +1013,15 @@ static int bssgp_rx_ptp(struct msgb *msg, struct tlv_parsed *tp,
case BSSGP_PDUT_STATUS:
/* This is already handled in bssgp_rcvmsg() */
break;
+ case BSSGP_PDUT_BVC_RESET:
+ rc = bssgp_rx_bvc_reset(msg, tp, bctx->bvci);
+ break;
case BSSGP_PDUT_DOWNLOAD_BSS_PFC:
case BSSGP_PDUT_CREATE_BSS_PFC_ACK:
case BSSGP_PDUT_CREATE_BSS_PFC_NACK:
case BSSGP_PDUT_MODIFY_BSS_PFC:
case BSSGP_PDUT_DELETE_BSS_PFC_ACK:
- DEBUGP(DBSSGP, "BSSGP BVCI=%u Rx PDU type %s not [yet] "
+ DEBUGP(DLBSSGP, "BSSGP BVCI=%u Rx PDU type %s not [yet] "
"implemented\n", bctx->bvci, bssgp_pdu_str(pdu_type));
rc = bssgp_tx_status(BSSGP_CAUSE_PDU_INCOMP_FEAT, NULL, msg);
break;
@@ -989,13 +1032,13 @@ static int bssgp_rx_ptp(struct msgb *msg, struct tlv_parsed *tp,
case BSSGP_PDUT_RA_CAPA_UPDATE_ACK:
case BSSGP_PDUT_FLOW_CONTROL_BVC_ACK:
case BSSGP_PDUT_FLOW_CONTROL_MS_ACK:
- DEBUGP(DBSSGP, "BSSGP BVCI=%u PDU type %s only exists in DL\n",
+ DEBUGP(DLBSSGP, "BSSGP BVCI=%u PDU type %s only exists in DL\n",
bctx->bvci, bssgp_pdu_str(pdu_type));
bssgp_tx_status(BSSGP_CAUSE_PROTO_ERR_UNSPEC, NULL, msg);
rc = -EINVAL;
break;
default:
- DEBUGP(DBSSGP, "BSSGP BVCI=%u PDU type %s unknown\n",
+ DEBUGP(DLBSSGP, "BSSGP BVCI=%u PDU type %s unknown\n",
bctx->bvci, bssgp_pdu_str(pdu_type));
rc = bssgp_tx_status(BSSGP_CAUSE_PROTO_ERR_UNSPEC, NULL, msg);
break;
@@ -1026,13 +1069,13 @@ static int bssgp_rx_sign(struct msgb *msg, struct tlv_parsed *tp,
break;
case BSSGP_PDUT_FLUSH_LL_ACK:
/* BSS informs us it has performed LL FLUSH */
- DEBUGP(DBSSGP, "BSSGP Rx BVCI=%u FLUSH LL ACK\n", bvci);
+ DEBUGP(DLBSSGP, "BSSGP Rx BVCI=%u FLUSH LL ACK\n", bvci);
/* FIXME: send NM_FLUSH_LL.res to NM */
break;
case BSSGP_PDUT_LLC_DISCARD:
/* BSS informs that some LLC PDU's have been discarded */
if (!bctx) {
- LOGP(DBSSGP, LOGL_ERROR,
+ LOGP(DLBSSGP, LOGL_ERROR,
"BSSGP Rx LLC-DISCARD missing mandatory BVCI\n");
goto err_mand_ie;
}
@@ -1040,9 +1083,9 @@ static int bssgp_rx_sign(struct msgb *msg, struct tlv_parsed *tp,
break;
case BSSGP_PDUT_BVC_BLOCK:
/* BSS tells us that BVC shall be blocked */
- if (!TLVP_PRESENT(tp, BSSGP_IE_BVCI) ||
- !TLVP_PRESENT(tp, BSSGP_IE_CAUSE)) {
- LOGP(DBSSGP, LOGL_ERROR, "BSSGP Rx BVC-BLOCK "
+ if (!TLVP_PRES_LEN(tp, BSSGP_IE_BVCI, 2) ||
+ !TLVP_PRES_LEN(tp, BSSGP_IE_CAUSE, 1)) {
+ LOGP(DLBSSGP, LOGL_ERROR, "BSSGP Rx BVC-BLOCK "
"missing mandatory IE\n");
goto err_mand_ie;
}
@@ -1050,21 +1093,21 @@ static int bssgp_rx_sign(struct msgb *msg, struct tlv_parsed *tp,
break;
case BSSGP_PDUT_BVC_UNBLOCK:
/* BSS tells us that BVC shall be unblocked */
- if (!TLVP_PRESENT(tp, BSSGP_IE_BVCI)) {
- LOGP(DBSSGP, LOGL_ERROR, "BSSGP Rx BVC-UNBLOCK "
+ if (!TLVP_PRES_LEN(tp, BSSGP_IE_BVCI, 2)) {
+ LOGP(DLBSSGP, LOGL_ERROR, "BSSGP Rx BVC-UNBLOCK "
"missing mandatory IE\n");
goto err_mand_ie;
}
rc = bssgp_rx_bvc_unblock(msg, tp);
break;
case BSSGP_PDUT_BVC_RESET_ACK:
- LOGP(DBSSGP, LOGL_ERROR, "BSSGP BVCI=%u Rx BVC-RESET-ACK\n", bvci);
+ LOGP(DLBSSGP, LOGL_ERROR, "BSSGP BVCI=%u Rx BVC-RESET-ACK\n", bvci);
break;
case BSSGP_PDUT_BVC_RESET:
- /* BSS tells us that BVC init is required */
- if (!TLVP_PRESENT(tp, BSSGP_IE_BVCI) ||
- !TLVP_PRESENT(tp, BSSGP_IE_CAUSE)) {
- LOGP(DBSSGP, LOGL_ERROR, "BSSGP Rx BVC-RESET "
+ /* SGSN or BSS tells us that BVC init is required */
+ if (!TLVP_PRES_LEN(tp, BSSGP_IE_BVCI, 2) ||
+ !TLVP_PRES_LEN(tp, BSSGP_IE_CAUSE, 1)) {
+ LOGP(DLBSSGP, LOGL_ERROR, "BSSGP Rx BVC-RESET "
"missing mandatory IE\n");
goto err_mand_ie;
}
@@ -1073,6 +1116,15 @@ static int bssgp_rx_sign(struct msgb *msg, struct tlv_parsed *tp,
case BSSGP_PDUT_STATUS:
/* This is already handled in bssgp_rcvmsg() */
break;
+
+ case BSSGP_PDUT_RAN_INFO:
+ case BSSGP_PDUT_RAN_INFO_REQ:
+ case BSSGP_PDUT_RAN_INFO_ACK:
+ case BSSGP_PDUT_RAN_INFO_ERROR:
+ case BSSGP_PDUT_RAN_INFO_APP_ERROR:
+ rc = bssgp_rx_rim(msg, tp, bvci);
+ break;
+
/* those only exist in the SGSN -> BSS direction */
case BSSGP_PDUT_PAGING_PS:
case BSSGP_PDUT_PAGING_CS:
@@ -1084,13 +1136,13 @@ static int bssgp_rx_sign(struct msgb *msg, struct tlv_parsed *tp,
case BSSGP_PDUT_BVC_BLOCK_ACK:
case BSSGP_PDUT_BVC_UNBLOCK_ACK:
case BSSGP_PDUT_SGSN_INVOKE_TRACE:
- DEBUGP(DBSSGP, "BSSGP BVCI=%u Rx PDU type %s only exists in DL\n",
+ DEBUGP(DLBSSGP, "BSSGP BVCI=%u Rx PDU type %s only exists in DL\n",
bvci, bssgp_pdu_str(pdu_type));
bssgp_tx_status(BSSGP_CAUSE_PROTO_ERR_UNSPEC, NULL, msg);
rc = -EINVAL;
break;
default:
- DEBUGP(DBSSGP, "BSSGP BVCI=%u Rx PDU type %s unknown\n",
+ DEBUGP(DLBSSGP, "BSSGP BVCI=%u Rx PDU type %s unknown\n",
bvci, bssgp_pdu_str(pdu_type));
rc = bssgp_tx_status(BSSGP_CAUSE_PROTO_ERR_UNSPEC, NULL, msg);
break;
@@ -1128,14 +1180,14 @@ int bssgp_rcvmsg(struct msgb *msg)
rc = bssgp_tlv_parse(&tp, budh->data, data_len);
}
if (rc < 0) {
- LOGP(DBSSGP, LOGL_ERROR, "Failed to parse BSSGP %s message. Invalid message was: %s\n",
+ LOGP(DLBSSGP, LOGL_ERROR, "Failed to parse BSSGP %s message. Invalid message was: %s\n",
bssgp_pdu_str(pdu_type), msgb_hexdump(msg));
if (pdu_type != BSSGP_PDUT_STATUS)
return bssgp_tx_status(BSSGP_CAUSE_INV_MAND_INF, NULL, msg);
return rc;
}
- if (bvci == BVCI_SIGNALLING && TLVP_PRESENT(&tp, BSSGP_IE_BVCI))
+ if (bvci == BVCI_SIGNALLING && TLVP_PRES_LEN(&tp, BSSGP_IE_BVCI, 2))
bvci = tlvp_val16be(&tp, BSSGP_IE_BVCI);
/* look-up or create the BTS context for this BVC */
@@ -1143,8 +1195,8 @@ int bssgp_rcvmsg(struct msgb *msg)
if (bctx) {
log_set_context(LOG_CTX_GB_BVC, bctx);
- rate_ctr_inc(&bctx->ctrg->ctr[BSSGP_CTR_PKTS_IN]);
- rate_ctr_add(&bctx->ctrg->ctr[BSSGP_CTR_BYTES_IN],
+ rate_ctr_inc(rate_ctr_group_get_ctr(bctx->ctrg, BSSGP_CTR_PKTS_IN));
+ rate_ctr_add(rate_ctr_group_get_ctr(bctx->ctrg, BSSGP_CTR_BYTES_IN),
msgb_bssgp_len(msg));
}
@@ -1158,7 +1210,7 @@ int bssgp_rcvmsg(struct msgb *msg)
* registered if a BVCI is given. */
if (!bctx && bvci != BVCI_SIGNALLING &&
pdu_type != BSSGP_PDUT_BVC_RESET) {
- LOGP(DBSSGP, LOGL_NOTICE, "NSEI=%u/BVCI=%u Rejecting PDU type %s for unknown BVCI\n", nsei, bvci,
+ LOGP(DLBSSGP, LOGL_NOTICE, "NSEI=%u/BVCI=%u Rejecting PDU type %s for unknown BVCI\n", nsei, bvci,
bssgp_pdu_str(pdu_type));
return bssgp_tx_status(BSSGP_CAUSE_UNKNOWN_BVCI, &bvci, msg);
}
@@ -1170,7 +1222,7 @@ int bssgp_rcvmsg(struct msgb *msg)
else if (bctx)
rc = bssgp_rx_ptp(msg, &tp, bctx);
else
- LOGP(DBSSGP, LOGL_NOTICE,
+ LOGP(DLBSSGP, LOGL_NOTICE,
"NSEI=%u/BVCI=%u Cannot handle PDU type %s for unknown BVCI, NS BVCI %u\n", nsei, bvci,
bssgp_pdu_str(pdu_type), ns_bvci);
@@ -1194,7 +1246,7 @@ int bssgp_tx_dl_ud(struct msgb *msg, uint16_t pdu_lifetime,
/* Identifiers from UP: TLLI, BVCI, NSEI (all in msgb->cb) */
if (bvci <= BVCI_PTM ) {
- LOGP(DBSSGP, LOGL_ERROR, "Cannot send DL-UD to BVCI %u\n",
+ LOGP(DLBSSGP, LOGL_ERROR, "Cannot send DL-UD to BVCI %u\n",
bvci);
msgb_free(msg);
return -EINVAL;
@@ -1202,7 +1254,7 @@ int bssgp_tx_dl_ud(struct msgb *msg, uint16_t pdu_lifetime,
bctx = btsctx_by_bvci_nsei(bvci, nsei);
if (!bctx) {
- LOGP(DBSSGP, LOGL_ERROR, "Cannot send DL-UD to unknown BVCI %u\n",
+ LOGP(DLBSSGP, LOGL_ERROR, "Cannot send DL-UD to unknown BVCI %u\n",
bvci);
msgb_free(msg);
return -ENODEV;
@@ -1268,8 +1320,8 @@ int bssgp_tx_dl_ud(struct msgb *msg, uint16_t pdu_lifetime,
budh->tlli = osmo_htonl(msgb_tlli(msg));
budh->pdu_type = BSSGP_PDUT_DL_UNITDATA;
- rate_ctr_inc(&bctx->ctrg->ctr[BSSGP_CTR_PKTS_OUT]);
- rate_ctr_add(&bctx->ctrg->ctr[BSSGP_CTR_BYTES_OUT], msg->len);
+ rate_ctr_inc(rate_ctr_group_get_ctr(bctx->ctrg, BSSGP_CTR_PKTS_OUT));
+ rate_ctr_add(rate_ctr_group_get_ctr(bctx->ctrg, BSSGP_CTR_BYTES_OUT), msg->len);
/* Identifiers down: BVCI, NSEI (in msgb->cb) */
@@ -1353,7 +1405,9 @@ int bssgp_tx_paging(uint16_t nsei, uint16_t ns_bvci,
void bssgp_set_log_ss(int ss)
{
- DBSSGP = ss;
+ /* BSSGP has moved from DGPRS to DLGPRS, please update your code if it's
+ * still calling this function
+ */
}
/*!
@@ -1374,7 +1428,7 @@ void bssgp_fc_flush_queue(struct bssgp_flow_control *fc)
/*!
* \brief Flush the queues of all BSSGP contexts.
*/
-void bssgp_flush_all_queues()
+void bssgp_flush_all_queues(void)
{
struct bssgp_bvc_ctx *bctx;
diff --git a/src/gb/gprs_bssgp2.c b/src/gb/gprs_bssgp2.c
new file mode 100644
index 00000000..104fe08e
--- /dev/null
+++ b/src/gb/gprs_bssgp2.c
@@ -0,0 +1,486 @@
+/* BSSGP2 - second generation of BSSGP library */
+
+/* (C) 2020 Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <osmocom/core/utils.h>
+#include <osmocom/core/byteswap.h>
+#include <osmocom/core/msgb.h>
+
+#include <osmocom/gsm/gsm48.h>
+#include <osmocom/gsm/tlv.h>
+
+#include <osmocom/gprs/gprs_ns2.h>
+#include <osmocom/gprs/gprs_bssgp.h>
+#include <osmocom/gprs/gprs_bssgp2.h>
+
+
+/*! transmit BSSGP PDU over NS (PTP BVC)
+ * \param[in] nsi NS Instance through which to transmit
+ * \param[in] nsei NSEI of NSE through which to transmit
+ * \param[in] bvci BVCI through which to transmit
+ * \param[in] msg BSSGP PDU to transmit
+ * \returns 0 on success; negative on error */
+int bssgp2_nsi_tx_ptp(struct gprs_ns2_inst *nsi, uint16_t nsei, uint16_t bvci,
+ struct msgb *msg, uint32_t lsp)
+{
+ struct osmo_gprs_ns2_prim nsp = {};
+ int rc;
+
+ if (!msg)
+ return 0;
+
+ nsp.bvci = bvci;
+ nsp.nsei = nsei;
+ nsp.u.unitdata.link_selector = lsp;
+
+ osmo_prim_init(&nsp.oph, SAP_NS, GPRS_NS2_PRIM_UNIT_DATA, PRIM_OP_REQUEST, msg);
+ rc = gprs_ns2_recv_prim(nsi, &nsp.oph);
+
+ return rc;
+}
+
+/*! transmit BSSGP PDU over NS (SIGNALING BVC)
+ * \param[in] nsi NS Instance through which to transmit
+ * \param[in] nsei NSEI of NSE through which to transmit
+ * \param[in] msg BSSGP PDU to transmit
+ * \returns 0 on success; negative on error */
+int bssgp2_nsi_tx_sig(struct gprs_ns2_inst *nsi, uint16_t nsei, struct msgb *msg, uint32_t lsp)
+{
+ return bssgp2_nsi_tx_ptp(nsi, nsei, 0, msg, lsp);
+}
+
+/*! Encode BSSGP BVC-BLOCK PDU as per TS 48.018 Section 10.4.8. */
+struct msgb *bssgp2_enc_bvc_block(uint16_t bvci, enum gprs_bssgp_cause cause)
+{
+ struct msgb *msg = bssgp_msgb_alloc();
+ struct bssgp_normal_hdr *bgph;
+ uint16_t _bvci = osmo_htons(bvci);
+
+ if (!msg)
+ return NULL;
+
+ bgph = (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
+ bgph->pdu_type = BSSGP_PDUT_BVC_BLOCK;
+
+ msgb_tvlv_put(msg, BSSGP_IE_BVCI, 2, (uint8_t *) &_bvci);
+ msgb_tvlv_put(msg, BSSGP_IE_CAUSE, 1, (uint8_t *) &cause);
+
+ return msg;
+}
+
+/*! Encode BSSGP BVC-BLOCK-ACK PDU as per TS 48.018 Section 10.4.9. */
+struct msgb *bssgp2_enc_bvc_block_ack(uint16_t bvci)
+{
+ struct msgb *msg = bssgp_msgb_alloc();
+ struct bssgp_normal_hdr *bgph;
+ uint16_t _bvci = osmo_htons(bvci);
+
+ if (!msg)
+ return NULL;
+
+ bgph = (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
+ bgph->pdu_type = BSSGP_PDUT_BVC_BLOCK_ACK;
+
+ msgb_tvlv_put(msg, BSSGP_IE_BVCI, 2, (uint8_t *) &_bvci);
+
+ return msg;
+}
+
+/*! Encode BSSGP BVC-UNBLOCK PDU as per TS 48.018 Section 10.4.10. */
+struct msgb *bssgp2_enc_bvc_unblock(uint16_t bvci)
+{
+ struct msgb *msg = bssgp_msgb_alloc();
+ struct bssgp_normal_hdr *bgph;
+ uint16_t _bvci = osmo_htons(bvci);
+
+ if (!msg)
+ return NULL;
+
+ bgph = (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
+ bgph->pdu_type = BSSGP_PDUT_BVC_UNBLOCK;
+
+ msgb_tvlv_put(msg, BSSGP_IE_BVCI, 2, (uint8_t *) &_bvci);
+
+ return msg;
+}
+
+/*! Encode BSSGP BVC-UNBLOCK-ACK PDU as per TS 48.018 Section 10.4.11. */
+struct msgb *bssgp2_enc_bvc_unblock_ack(uint16_t bvci)
+{
+ struct msgb *msg = bssgp_msgb_alloc();
+ struct bssgp_normal_hdr *bgph;
+ uint16_t _bvci = osmo_htons(bvci);
+
+ if (!msg)
+ return NULL;
+
+ bgph = (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
+ bgph->pdu_type = BSSGP_PDUT_BVC_UNBLOCK_ACK;
+
+ msgb_tvlv_put(msg, BSSGP_IE_BVCI, 2, (uint8_t *) &_bvci);
+
+ return msg;
+}
+
+/*! Encode BSSGP BVC-RESET PDU as per TS 48.018 Section 10.4.12.
+ * \param[in] bvci PTP BVCI to encode into the BVCI IE
+ * \param[in] cause BSSGP Cause value (reason for reset)
+ * \param[in] ra_id Routing Area ID to be encoded to CELL_ID IE (optional)
+ * \param[in] cell_id Cell ID to be encoded to CELL_ID IE (only if ra_id is non-NULL)
+ * \param[in] feat_bm Feature Bitmap (optional)
+ * \param[in] ext_feat_bm Extended Feature Bitmap (optional) */
+struct msgb *bssgp2_enc_bvc_reset(uint16_t bvci, enum gprs_bssgp_cause cause,
+ const struct gprs_ra_id *ra_id, uint16_t cell_id,
+ const uint8_t *feat_bm, const uint8_t *ext_feat_bm)
+{
+ struct msgb *msg = bssgp_msgb_alloc();
+ struct bssgp_normal_hdr *bgph;
+ uint16_t _bvci = osmo_htons(bvci);
+
+ if (!msg)
+ return NULL;
+
+ bgph = (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
+ bgph->pdu_type = BSSGP_PDUT_BVC_RESET;
+
+ msgb_tvlv_put(msg, BSSGP_IE_BVCI, 2, (uint8_t *) &_bvci);
+ msgb_tvlv_put(msg, BSSGP_IE_CAUSE, 1, (uint8_t *) &cause);
+ if (ra_id) {
+ uint8_t bssgp_cid[8];
+ bssgp_create_cell_id(bssgp_cid, ra_id, cell_id);
+ msgb_tvlv_put(msg, BSSGP_IE_CELL_ID, sizeof(bssgp_cid), bssgp_cid);
+ }
+
+ if (feat_bm)
+ msgb_tvlv_put(msg, BSSGP_IE_FEATURE_BITMAP, 1, feat_bm);
+
+ if (ext_feat_bm)
+ msgb_tvlv_put(msg, BSSGP_IE_EXT_FEATURE_BITMAP, 1, feat_bm);
+
+ return msg;
+}
+
+/*! Encode BSSGP BVC-RESET-ACK PDU as per TS 48.018 Section 10.4.13.
+ * \param[in] bvci PTP BVCI to encode into the BVCI IE
+ * \param[in] ra_id Routing Area ID to be encoded to CELL_ID IE (optional)
+ * \param[in] cell_id Cell ID to be encoded to CELL_ID IE (only if ra_id is non-NULL)
+ * \param[in] feat_bm Feature Bitmap (optional)
+ * \param[in] ext_feat_bm Extended Feature Bitmap (optional) */
+struct msgb *bssgp2_enc_bvc_reset_ack(uint16_t bvci, const struct gprs_ra_id *ra_id, uint16_t cell_id,
+ const uint8_t *feat_bm, const uint8_t *ext_feat_bm)
+{
+ struct msgb *msg = bssgp_msgb_alloc();
+ struct bssgp_normal_hdr *bgph;
+ uint16_t _bvci = osmo_htons(bvci);
+
+ if (!msg)
+ return NULL;
+
+ bgph = (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
+ bgph->pdu_type = BSSGP_PDUT_BVC_RESET_ACK;
+
+ msgb_tvlv_put(msg, BSSGP_IE_BVCI, 2, (uint8_t *) &_bvci);
+ if (ra_id) {
+ uint8_t bssgp_cid[8];
+ bssgp_create_cell_id(bssgp_cid, ra_id, cell_id);
+ msgb_tvlv_put(msg, BSSGP_IE_CELL_ID, sizeof(bssgp_cid), bssgp_cid);
+ }
+
+ if (feat_bm)
+ msgb_tvlv_put(msg, BSSGP_IE_FEATURE_BITMAP, 1, feat_bm);
+
+ if (ext_feat_bm)
+ msgb_tvlv_put(msg, BSSGP_IE_EXT_FEATURE_BITMAP, 1, feat_bm);
+
+ return msg;
+}
+
+/*! Encode BSSGP STATUS PDU as per TS 48.018 Section 10.4.14.
+ * \param[in] cause BSSGP Cause value
+ * \param[in] bvci optional BVCI - only encoded if non-NULL
+ * \param[in] msg optional message buffer containing PDU in error - only encoded if non-NULL
+ * \param[in] max_pdu_len Maximum BSSGP PDU size the NS layer accepts */
+struct msgb *bssgp2_enc_status(uint8_t cause, const uint16_t *bvci, const struct msgb *orig_msg, uint16_t max_pdu_len)
+{
+ struct msgb *msg = bssgp_msgb_alloc();
+ struct bssgp_normal_hdr *bgph;
+
+ if (!msg)
+ return NULL;
+
+ bgph = (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
+ bgph->pdu_type = BSSGP_PDUT_STATUS;
+ msgb_tvlv_put(msg, BSSGP_IE_CAUSE, 1, &cause);
+ /* FIXME: Require/encode BVCI only if cause is BVCI unknown/blocked
+ * See 3GPP TS 48.018 Ch. 10.4.14 */
+ if (bvci) {
+ uint16_t _bvci = osmo_htons(*bvci);
+ msgb_tvlv_put(msg, BSSGP_IE_BVCI, 2, (uint8_t *) &_bvci);
+ }
+ if (orig_msg) {
+ uint32_t orig_len, max_orig_len;
+ /* Calculate how big the reply would be: the BSSGP msg so far + size of the PDU IN ERROR including tvl */
+ orig_len = msgb_bssgp_len(orig_msg);
+ max_orig_len = msgb_length(msg) + TVLV_GROSS_LEN(orig_len);
+ /* Truncate the difference between max_orig_len and mtu */
+ if (max_orig_len > max_pdu_len)
+ orig_len -= max_orig_len - max_pdu_len;
+ msgb_tvlv_put(msg, BSSGP_IE_PDU_IN_ERROR, orig_len, msgb_bssgph(orig_msg));
+ }
+
+ return msg;
+}
+
+static const unsigned int bssgp_fc_gran_tbl[] = {
+ [BSSGP_FC_GRAN_100] = 100,
+ [BSSGP_FC_GRAN_1000] = 1000,
+ [BSSGP_FC_GRAN_10000] = 10000,
+ [BSSGP_FC_GRAN_100000] = 100000,
+};
+
+/*! Decode a FLOW-CONTROL-BVC PDU as per TS 48.018 Section 10.4.4.
+ * \param[out] fc caller-allocated memory for parsed output
+ * \param[in] tp pre-parsed TLVs; caller must ensure mandatory IE presence/length
+ * \returns 0 on success; negative in case of error */
+int bssgp2_dec_fc_bvc(struct bssgp2_flow_ctrl *fc, const struct tlv_parsed *tp)
+{
+ unsigned int granularity = 100;
+
+ /* optional "Flow Control Granularity IE" (11.3.102); applies to
+ * bucket_size_max, bucket_leak_rate and PFC FC params IE */
+ if (TLVP_PRESENT(tp, BSSGP_IE_FLOW_CTRL_GRANULARITY)) {
+ uint8_t gran = *TLVP_VAL(tp, BSSGP_IE_FLOW_CTRL_GRANULARITY);
+ granularity = bssgp_fc_gran_tbl[gran & 3];
+ }
+
+ /* mandatory IEs */
+ fc->tag = *TLVP_VAL(tp, BSSGP_IE_TAG);
+ fc->bucket_size_max = granularity * tlvp_val16be(tp, BSSGP_IE_BVC_BUCKET_SIZE);
+ fc->bucket_leak_rate = (granularity * tlvp_val16be(tp, BSSGP_IE_BUCKET_LEAK_RATE)) / 8;
+ fc->u.bvc.bmax_default_ms = granularity * tlvp_val16be(tp, BSSGP_IE_BMAX_DEFAULT_MS);
+ fc->u.bvc.r_default_ms = (granularity * tlvp_val16be(tp, BSSGP_IE_R_DEFAULT_MS)) / 8;
+
+ /* optional / conditional */
+ if (TLVP_PRESENT(tp, BSSGP_IE_BUCKET_FULL_RATIO)) {
+ fc->bucket_full_ratio_present = true;
+ fc->bucket_full_ratio = *TLVP_VAL(tp, BSSGP_IE_BUCKET_FULL_RATIO);
+ } else {
+ fc->bucket_full_ratio_present = false;
+ }
+
+ if (TLVP_PRESENT(tp, BSSGP_IE_BVC_MEASUREMENT)) {
+ uint16_t val = tlvp_val16be(tp, BSSGP_IE_BVC_MEASUREMENT);
+ fc->u.bvc.measurement_present = true;
+ /* convert from centi-seconds to milli-seconds */
+ if (val == 0xffff)
+ fc->u.bvc.measurement = 0xffffffff;
+ else
+ fc->u.bvc.measurement = val * 10;
+ } else {
+ fc->u.bvc.measurement_present = false;
+ }
+
+ return 0;
+
+}
+
+/*! Encode a FLOW-CONTROL-BVC PDU as per TS 48.018 Section 10.4.4.
+ * \param[in] fc structure describing to-be-encoded FC parameters
+ * \param[in] gran if non-NULL: Encode using specified unit granularity
+ * \returns encoded PDU or NULL in case of error */
+struct msgb *bssgp2_enc_fc_bvc(const struct bssgp2_flow_ctrl *fc, enum bssgp_fc_granularity *gran)
+{
+ struct msgb *msg = bssgp_msgb_alloc();
+ struct bssgp_normal_hdr *bgph;
+ unsigned int granularity = 100;
+
+ if (gran)
+ granularity = bssgp_fc_gran_tbl[*gran & 3];
+
+ if (!msg)
+ return NULL;
+
+ bgph = (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
+ bgph->pdu_type = BSSGP_PDUT_FLOW_CONTROL_BVC;
+
+ msgb_tvlv_put(msg, BSSGP_IE_TAG, 1, &fc->tag);
+ msgb_tvlv_put_16be(msg, BSSGP_IE_BVC_BUCKET_SIZE, fc->bucket_size_max / granularity);
+ msgb_tvlv_put_16be(msg, BSSGP_IE_BUCKET_LEAK_RATE, fc->bucket_leak_rate * 8 / granularity);
+ msgb_tvlv_put_16be(msg, BSSGP_IE_BMAX_DEFAULT_MS, fc->u.bvc.bmax_default_ms / granularity);
+ msgb_tvlv_put_16be(msg, BSSGP_IE_R_DEFAULT_MS, fc->u.bvc.r_default_ms * 8 / granularity);
+
+ if (fc->bucket_full_ratio_present)
+ msgb_tvlv_put(msg, BSSGP_IE_BUCKET_FULL_RATIO, 1, &fc->bucket_full_ratio);
+
+ if (fc->u.bvc.measurement_present) {
+ uint16_t val;
+ /* convert from ms to cs */
+ if (fc->u.bvc.measurement == 0xffffffff)
+ val = 0xffff;
+ else
+ val = fc->u.bvc.measurement / 10;
+ msgb_tvlv_put_16be(msg, BSSGP_IE_BVC_MEASUREMENT, val);
+ }
+
+ if (gran) {
+ uint8_t val = *gran & 3;
+ msgb_tvlv_put(msg, BSSGP_IE_FLOW_CTRL_GRANULARITY, 1, &val);
+ }
+
+ return msg;
+}
+
+/*! Encode BSSGP FLUSH-LL PDU as per TS 48.018 Section 10.4.1.
+ * \param[in] tlli - the TLLI of the MS
+ * \param[in] old_bvci BVCI
+ * \param[in] new_bvci2 optional BVCI - only encoded if non-NULL
+ * \param[in] nsei optional - only encoded if non-NULL
+ * \returns encoded PDU or NULL in case of error */
+struct msgb *bssgp2_enc_flush_ll(uint32_t tlli, uint16_t old_bvci,
+ const uint16_t *new_bvci, const uint16_t *nsei)
+{
+ struct msgb *msg = bssgp_msgb_alloc();
+ struct bssgp_normal_hdr *bgph;
+
+ if (!msg)
+ return NULL;
+
+ bgph = (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
+ bgph->pdu_type = BSSGP_PDUT_FLUSH_LL;
+
+ msgb_tvlv_put_32be(msg, BSSGP_IE_TLLI, tlli);
+ msgb_tvlv_put_16be(msg, BSSGP_IE_BVCI, old_bvci);
+ if (new_bvci)
+ msgb_tvlv_put_16be(msg, BSSGP_IE_BVCI, *new_bvci);
+
+ if (nsei)
+ msgb_tvlv_put_16be(msg, BSSGP_IE_BVCI, *nsei);
+
+ return msg;
+}
+
+/*! Encode a FLOW-CONTROL-BVC-ACK PDU as per TS 48.018 Section 10.4.4.
+ * \param[in] tag the tag IE value to encode
+ * \returns encoded PDU or NULL in case of error */
+struct msgb *bssgp2_enc_fc_bvc_ack(uint8_t tag)
+{
+ struct msgb *msg = bssgp_msgb_alloc();
+ struct bssgp_normal_hdr *bgph;
+
+ if (!msg)
+ return NULL;
+
+ bgph = (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
+ bgph->pdu_type = BSSGP_PDUT_FLOW_CONTROL_BVC_ACK;
+
+ msgb_tvlv_put(msg, BSSGP_IE_TAG, 1, &tag);
+
+ return msg;
+}
+
+/*! Decode a FLOW-CONTROL-MS PDU as per TS 48.018 Section 10.4.6.
+ * \param[out] fc caller-allocated memory for parsed output
+ * \param[in] tp pre-parsed TLVs; caller must ensure mandatory IE presence/length
+ * \returns 0 on success; negative in case of error */
+int bssgp2_dec_fc_ms(struct bssgp2_flow_ctrl *fc, struct tlv_parsed *tp)
+{
+ unsigned int granularity = 100;
+
+ /* optional "Flow Control Granularity IE" (11.3.102); applies to
+ * bucket_size_max, bucket_leak_rate and PFC FC params IE */
+ if (TLVP_PRESENT(tp, BSSGP_IE_FLOW_CTRL_GRANULARITY)) {
+ uint8_t gran = *TLVP_VAL(tp, BSSGP_IE_FLOW_CTRL_GRANULARITY);
+ granularity = bssgp_fc_gran_tbl[gran & 3];
+ }
+
+ /* mandatory IEs */
+ fc->u.ms.tlli = tlvp_val32be(tp, BSSGP_IE_TLLI);
+ fc->tag = *TLVP_VAL(tp, BSSGP_IE_TAG);
+ fc->bucket_size_max = granularity * tlvp_val16be(tp, BSSGP_IE_MS_BUCKET_SIZE);
+ fc->bucket_leak_rate = (granularity * tlvp_val16be(tp, BSSGP_IE_BUCKET_LEAK_RATE)) / 8;
+
+ /* optional / conditional */
+ if (TLVP_PRESENT(tp, BSSGP_IE_BUCKET_FULL_RATIO)) {
+ fc->bucket_full_ratio_present = true;
+ fc->bucket_full_ratio = *TLVP_VAL(tp, BSSGP_IE_BUCKET_FULL_RATIO);
+ } else {
+ fc->bucket_full_ratio_present = false;
+ }
+
+ return 0;
+}
+
+/*! Encode a FLOW-CONTROL-MS PDU as per TS 48.018 Section 10.4.6.
+ * \param[in] fc structure describing to-be-encoded FC parameters
+ * \param[in] gran if non-NULL: Encode using specified unit granularity
+ * \returns encoded PDU or NULL in case of error */
+struct msgb *bssgp2_enc_fc_ms(const struct bssgp2_flow_ctrl *fc, enum bssgp_fc_granularity *gran)
+{
+ struct msgb *msg = bssgp_msgb_alloc();
+ struct bssgp_normal_hdr *bgph;
+ unsigned int granularity = 100;
+
+ if (gran)
+ granularity = bssgp_fc_gran_tbl[*gran & 3];
+
+ if (!msg)
+ return NULL;
+
+ bgph = (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
+ bgph->pdu_type = BSSGP_PDUT_FLOW_CONTROL_MS;
+
+ msgb_tvlv_put_32be(msg, BSSGP_IE_TLLI, fc->u.ms.tlli);
+ msgb_tvlv_put(msg, BSSGP_IE_TAG, 1, &fc->tag);
+ msgb_tvlv_put_16be(msg, BSSGP_IE_MS_BUCKET_SIZE, fc->bucket_size_max / granularity);
+ msgb_tvlv_put_16be(msg, BSSGP_IE_BUCKET_LEAK_RATE, fc->bucket_leak_rate * 8 / granularity);
+
+ if (fc->bucket_full_ratio_present)
+ msgb_tvlv_put(msg, BSSGP_IE_BUCKET_FULL_RATIO, 1, &fc->bucket_full_ratio);
+
+ if (gran) {
+ uint8_t val = *gran & 3;
+ msgb_tvlv_put(msg, BSSGP_IE_FLOW_CTRL_GRANULARITY, 1, &val);
+ }
+
+ return msg;
+}
+
+/*! Encode a FLOW-CONTROL-BVC-ACK PDU as per TS 48.018 Section 10.4.7.
+ * \param[in] tlli the TLLI IE value to encode
+ * \param[in] tag the tag IE value to encode
+ * \returns encoded PDU or NULL in case of error */
+struct msgb *bssgp2_enc_fc_ms_ack(uint32_t tlli, uint8_t tag)
+{
+ struct msgb *msg = bssgp_msgb_alloc();
+ struct bssgp_normal_hdr *bgph;
+
+ if (!msg)
+ return NULL;
+
+ bgph = (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
+ bgph->pdu_type = BSSGP_PDUT_FLOW_CONTROL_MS_ACK;
+
+ msgb_tvlv_put_32be(msg, BSSGP_IE_TLLI, tlli);
+ msgb_tvlv_put(msg, BSSGP_IE_TAG, 1, &tag);
+
+ return msg;
+}
diff --git a/src/gb/gprs_bssgp_bss.c b/src/gb/gprs_bssgp_bss.c
index 59b06f05..8230d871 100644
--- a/src/gb/gprs_bssgp_bss.c
+++ b/src/gb/gprs_bssgp_bss.c
@@ -35,7 +35,6 @@
#include <osmocom/gprs/gprs_ns.h>
#include "gprs_bssgp_internal.h"
-#include "common_vty.h"
#define GSM_IMSI_LENGTH 17
@@ -61,7 +60,7 @@ int bssgp_tx_suspend(uint16_t nsei, uint32_t tlli,
struct bssgp_normal_hdr *bgph =
(struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
- LOGP(DBSSGP, LOGL_NOTICE, "BSSGP (BVCI=0) Tx SUSPEND (TLLI=0x%04x)\n",
+ LOGP(DLBSSGP, LOGL_NOTICE, "BSSGP (BVCI=0) Tx SUSPEND (TLLI=0x%04x)\n",
tlli);
msgb_nsei(msg) = nsei;
msgb_bvci(msg) = 0; /* Signalling */
@@ -81,7 +80,7 @@ int bssgp_tx_resume(uint16_t nsei, uint32_t tlli,
struct bssgp_normal_hdr *bgph =
(struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
- LOGP(DBSSGP, LOGL_NOTICE, "BSSGP (BVCI=0) Tx RESUME (TLLI=0x%04x)\n",
+ LOGP(DLBSSGP, LOGL_NOTICE, "BSSGP (BVCI=0) Tx RESUME (TLLI=0x%04x)\n",
tlli);
msgb_nsei(msg) = nsei;
msgb_bvci(msg) = 0; /* Signalling */
@@ -102,7 +101,7 @@ int bssgp_tx_ra_capa_upd(struct bssgp_bvc_ctx *bctx, uint32_t tlli, uint8_t tag)
struct bssgp_normal_hdr *bgph =
(struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
- LOGP(DBSSGP, LOGL_NOTICE, "BSSGP (BVCI=%u) Tx RA-CAPA-UPD (TLLI=0x%04x)\n",
+ LOGP(DLBSSGP, LOGL_NOTICE, "BSSGP (BVCI=%u) Tx RA-CAPA-UPD (TLLI=0x%04x)\n",
bctx->bvci, tlli);
/* set NSEI and BVCI in msgb cb */
@@ -124,7 +123,7 @@ static struct msgb *common_tx_radio_status(struct bssgp_bvc_ctx *bctx)
struct bssgp_normal_hdr *bgph =
(struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
- LOGP(DBSSGP, LOGL_NOTICE, "BSSGP (BVCI=%u) Tx RADIO-STATUS ",
+ LOGP(DLBSSGP, LOGL_NOTICE, "BSSGP (BVCI=%u) Tx RADIO-STATUS ",
bctx->bvci);
/* set NSEI and BVCI in msgb cb */
@@ -140,7 +139,7 @@ static struct msgb *common_tx_radio_status(struct bssgp_bvc_ctx *bctx)
static int common_tx_radio_status2(struct msgb *msg, uint8_t cause)
{
msgb_tvlv_put(msg, BSSGP_IE_CAUSE, 1, &cause);
- LOGPC(DBSSGP, LOGL_NOTICE, "CAUSE=%s\n", bssgp_cause_str(cause));
+ LOGPC(DLBSSGP, LOGL_NOTICE, "CAUSE=%s\n", bssgp_cause_str(cause));
return bssgp_ns_send(bssgp_ns_send_data, msg);
}
@@ -154,7 +153,7 @@ int bssgp_tx_radio_status_tlli(struct bssgp_bvc_ctx *bctx, uint8_t cause,
if (!msg)
return -ENOMEM;
bssgp_msgb_tlli_put(msg, tlli);
- LOGPC(DBSSGP, LOGL_NOTICE, "TLLI=0x%08x ", tlli);
+ LOGPC(DLBSSGP, LOGL_NOTICE, "TLLI=0x%08x ", tlli);
return common_tx_radio_status2(msg, cause);
}
@@ -169,7 +168,7 @@ int bssgp_tx_radio_status_tmsi(struct bssgp_bvc_ctx *bctx, uint8_t cause,
if (!msg)
return -ENOMEM;
msgb_tvlv_put(msg, BSSGP_IE_TMSI, 4, (uint8_t *)&_tmsi);
- LOGPC(DBSSGP, LOGL_NOTICE, "TMSI=0x%08x ", tmsi);
+ LOGPC(DLBSSGP, LOGL_NOTICE, "TMSI=0x%08x ", tmsi);
return common_tx_radio_status2(msg, cause);
}
@@ -195,7 +194,7 @@ int bssgp_tx_radio_status_imsi(struct bssgp_bvc_ctx *bctx, uint8_t cause,
if (imsi_len > 2)
msgb_tvlv_put(msg, BSSGP_IE_IMSI, imsi_len-2, mi+2);
#pragma GCC diagnostic pop
- LOGPC(DBSSGP, LOGL_NOTICE, "IMSI=%s ", imsi);
+ LOGPC(DLBSSGP, LOGL_NOTICE, "IMSI=%s ", imsi);
return common_tx_radio_status2(msg, cause);
}
@@ -234,7 +233,7 @@ int bssgp_tx_llc_discarded(struct bssgp_bvc_ctx *bctx, uint32_t tlli,
uint16_t _bvci = osmo_htons(bctx->bvci);
uint32_t _oct_aff = osmo_htonl(num_octets & 0xFFFFFF);
- LOGP(DBSSGP, LOGL_NOTICE, "BSSGP (BVCI=%u) Tx LLC-DISCARDED "
+ LOGP(DLBSSGP, LOGL_NOTICE, "BSSGP (BVCI=%u) Tx LLC-DISCARDED "
"TLLI=0x%04x, FRAMES=%u, OCTETS=%u\n", bctx->bvci, tlli,
num_frames, num_octets);
msgb_nsei(msg) = bctx->nsei;
@@ -258,7 +257,7 @@ int bssgp_tx_bvc_block(struct bssgp_bvc_ctx *bctx, uint8_t cause)
(struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
uint16_t _bvci = osmo_htons(bctx->bvci);
- LOGP(DBSSGP, LOGL_NOTICE, "BSSGP (BVCI=%u) Tx BVC-BLOCK "
+ LOGP(DLBSSGP, LOGL_NOTICE, "BSSGP (BVCI=%u) Tx BVC-BLOCK "
"CAUSE=%s\n", bctx->bvci, bssgp_cause_str(cause));
msgb_nsei(msg) = bctx->nsei;
@@ -279,7 +278,7 @@ int bssgp_tx_bvc_unblock(struct bssgp_bvc_ctx *bctx)
(struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
uint16_t _bvci = osmo_htons(bctx->bvci);
- LOGP(DBSSGP, LOGL_NOTICE, "BSSGP (BVCI=%u) Tx BVC-UNBLOCK\n", bctx->bvci);
+ LOGP(DLBSSGP, LOGL_NOTICE, "BSSGP (BVCI=%u) Tx BVC-UNBLOCK\n", bctx->bvci);
msgb_nsei(msg) = bctx->nsei;
msgb_bvci(msg) = 0; /* Signalling */
@@ -467,8 +466,8 @@ int bssgp_tx_ul_ud(struct bssgp_bvc_ctx *bctx, uint32_t tlli,
msgb_nsei(msg) = bctx->nsei;
msgb_bvci(msg) = bctx->bvci;
- rate_ctr_inc(&bctx->ctrg->ctr[BSSGP_CTR_PKTS_OUT]);
- rate_ctr_add(&bctx->ctrg->ctr[BSSGP_CTR_BYTES_OUT], msg->len);
+ rate_ctr_inc(rate_ctr_group_get_ctr(bctx->ctrg, BSSGP_CTR_PKTS_OUT));
+ rate_ctr_add(rate_ctr_group_get_ctr(bctx->ctrg, BSSGP_CTR_BYTES_OUT), msg->len);
return bssgp_ns_send(bssgp_ns_send_data, msg);
}
@@ -511,24 +510,24 @@ int bssgp_rx_paging(struct bssgp_paging_info *pinfo,
TLVP_LEN(&tp, BSSGP_IE_IMSI));
/* DRX Parameters */
- if (!TLVP_PRESENT(&tp, BSSGP_IE_DRX_PARAMS))
+ if (!TLVP_PRES_LEN(&tp, BSSGP_IE_DRX_PARAMS, 2))
goto err_mand_ie;
pinfo->drx_params = tlvp_val16be(&tp, BSSGP_IE_DRX_PARAMS);
/* Scope */
- if (TLVP_PRESENT(&tp, BSSGP_IE_BSS_AREA_ID)) {
+ if (TLVP_PRES_LEN(&tp, BSSGP_IE_BSS_AREA_ID, 1)) {
pinfo->scope = BSSGP_PAGING_BSS_AREA;
- } else if (TLVP_PRESENT(&tp, BSSGP_IE_LOCATION_AREA)) {
+ } else if (TLVP_PRES_LEN(&tp, BSSGP_IE_LOCATION_AREA, 5)) {
pinfo->scope = BSSGP_PAGING_LOCATION_AREA;
memcpy(ra, TLVP_VAL(&tp, BSSGP_IE_LOCATION_AREA),
TLVP_LEN(&tp, BSSGP_IE_LOCATION_AREA));
gsm48_parse_ra(&pinfo->raid, ra);
- } else if (TLVP_PRESENT(&tp, BSSGP_IE_ROUTEING_AREA)) {
+ } else if (TLVP_PRES_LEN(&tp, BSSGP_IE_ROUTEING_AREA, 6)) {
pinfo->scope = BSSGP_PAGING_ROUTEING_AREA;
memcpy(ra, TLVP_VAL(&tp, BSSGP_IE_ROUTEING_AREA),
TLVP_LEN(&tp, BSSGP_IE_ROUTEING_AREA));
gsm48_parse_ra(&pinfo->raid, ra);
- } else if (TLVP_PRESENT(&tp, BSSGP_IE_BVCI)) {
+ } else if (TLVP_PRES_LEN(&tp, BSSGP_IE_BVCI, 2)) {
pinfo->scope = BSSGP_PAGING_BVCI;
pinfo->bvci = tlvp_val16be(&tp, BSSGP_IE_BVCI);
} else
@@ -546,8 +545,7 @@ int bssgp_rx_paging(struct bssgp_paging_info *pinfo,
}
/* Optional (P-)TMSI */
- if (TLVP_PRESENT(&tp, BSSGP_IE_TMSI) &&
- TLVP_LEN(&tp, BSSGP_IE_TMSI) >= 4) {
+ if (TLVP_PRES_LEN(&tp, BSSGP_IE_TMSI, 4)) {
if (!pinfo->ptmsi)
pinfo->ptmsi = talloc_zero_size(pinfo, sizeof(uint32_t));
*(pinfo->ptmsi) = osmo_load32be(TLVP_VAL(&tp, BSSGP_IE_TMSI));
diff --git a/src/gb/gprs_bssgp_internal.h b/src/gb/gprs_bssgp_internal.h
index 2ada027c..5022d32d 100644
--- a/src/gb/gprs_bssgp_internal.h
+++ b/src/gb/gprs_bssgp_internal.h
@@ -5,3 +5,5 @@
extern bssgp_bvc_send bssgp_ns_send;
extern void *bssgp_ns_send_data;
+
+int bssgp_rx_rim(struct msgb *msg, struct tlv_parsed *tp, uint16_t bvci);
diff --git a/src/gb/gprs_bssgp_rim.c b/src/gb/gprs_bssgp_rim.c
new file mode 100644
index 00000000..9c09e1ef
--- /dev/null
+++ b/src/gb/gprs_bssgp_rim.c
@@ -0,0 +1,1261 @@
+/*! \file gprs_bssgp.c
+ * GPRS BSSGP RIM protocol implementation as per 3GPP TS 48.018. */
+/*
+ * (C) 2020-2021 by sysmocom - s.f.m.c. GmbH
+ * Author: Philipp Maier <pmaier@sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <errno.h>
+#include <osmocom/gprs/gprs_bssgp.h>
+#include <osmocom/gprs/gprs_bssgp_rim.h>
+#include <osmocom/gsm/gsm0808_utils.h>
+#include "gprs_bssgp_internal.h"
+
+/* TVLV IEs use a variable length field. To be sure we will do all buffer
+ * length checks with the maximum possible header length, which is
+ * 1 octet tag + 2 octets length = 3 */
+#define TVLV_HDR_MAXLEN 3
+
+/* Usually RIM application containers and their surrounding RIM containers
+ * are not likely to exceed 128 octets, so the usual header length will be 2 */
+#define TVLV_HDR_LEN 2
+
+/* The reporting cell identifier is encoded as a cell identifier IE
+ * (3GPP TS 48.018, sub-clause 11.3.9) but without IE and length octets. */
+#define REP_CELL_ID_LEN 8
+
+const struct value_string bssgp_rim_routing_info_discr_strs[] = {
+ { BSSGP_RIM_ROUTING_INFO_GERAN, "GERAN-cell" },
+ { BSSGP_RIM_ROUTING_INFO_UTRAN, "UTRAN-RNC" },
+ { BSSGP_RIM_ROUTING_INFO_EUTRAN, "E-UTRAN-eNodeB/HeNB" },
+ { 0, NULL }
+};
+
+/*! Parse a RIM Routing address IE (3GPP TS 29.060, chapter 7.7.57 and 7.7.77).
+ * \param[out] ri user provided memory to store the parsed results.
+ * \param[in] buf input buffer of the value part of the RIM Routing address IE.
+ * \discr[in] discr value part (one byte) of the RIM Routing Address Discriminator IE.
+ * \returns length of parsed octets, -EINVAL on error. */
+int bssgp_parse_rim_ra(struct bssgp_rim_routing_info *ri, const uint8_t *buf,
+ unsigned int len, uint8_t discr)
+{
+ struct gprs_ra_id raid_temp;
+
+ memset(ri, 0, sizeof(*ri));
+ if (len < 2)
+ return -EINVAL;
+
+ ri->discr = discr;
+
+ switch (ri->discr) {
+ case BSSGP_RIM_ROUTING_INFO_GERAN:
+ if (len < 8)
+ return -EINVAL;
+ ri->geran.cid = bssgp_parse_cell_id(&ri->geran.raid, buf);
+ return 8;
+ case BSSGP_RIM_ROUTING_INFO_UTRAN:
+ if (len < 8)
+ return -EINVAL;
+ gsm48_parse_ra(&ri->utran.raid, buf);
+ ri->utran.rncid = osmo_load16be(buf + 6);
+ return 8;
+ case BSSGP_RIM_ROUTING_INFO_EUTRAN:
+ if (len < 6 || len > 13)
+ return -EINVAL;
+ /* Note: 3GPP TS 24.301 Figure 9.9.3.32.1 and 3GPP TS 24.008
+ * Figure 10.5.130 specify MCC/MNC encoding in the same way,
+ * so we can re-use gsm48_parse_ra() for that. */
+ gsm48_parse_ra(&raid_temp, buf);
+ ri->eutran.tai.mcc = raid_temp.mcc;
+ ri->eutran.tai.mnc = raid_temp.mnc;
+ ri->eutran.tai.mnc_3_digits = raid_temp.mnc_3_digits;
+ ri->eutran.tai.tac = osmo_load16be(buf + 3);
+ memcpy(ri->eutran.global_enb_id, buf + 5, len - 5);
+ ri->eutran.global_enb_id_len = len - 5;
+ return len;
+ default:
+ return -EINVAL;
+ }
+}
+
+/*! Parse a RIM Routing information IE (3GPP TS 48.018, chapter 11.3.70).
+ * \param[out] ri user provided memory to store the parsed results.
+ * \param[in] buf input buffer of the value part of the IE.
+ * \returns length of parsed octets, -EINVAL on error. */
+int bssgp_parse_rim_ri(struct bssgp_rim_routing_info *ri, const uint8_t *buf,
+ unsigned int len)
+{
+ uint8_t discr;
+ int rc;
+
+ if (len < 1)
+ return -EINVAL;
+
+ discr = buf[0] & 0x0f;
+
+ rc = bssgp_parse_rim_ra(ri, buf + 1, len - 1, discr);
+ if (rc < 0)
+ return rc;
+ return rc + 1;
+}
+
+/*! Encode a RIM Routing information IE (3GPP TS 48.018, chapter 11.3.70).
+ * \param[out] buf user provided memory (at least 14 byte) for the generated value part of the IE.
+ * \param[in] ri user provided input data struct.
+ * \returns length of encoded octets, -EINVAL on error. */
+int bssgp_create_rim_ri(uint8_t *buf, const struct bssgp_rim_routing_info *ri)
+{
+ int rc;
+ struct gprs_ra_id raid_temp;
+ int len;
+
+ buf[0] = ri->discr & 0x0f;
+ buf++;
+
+ switch (ri->discr) {
+ case BSSGP_RIM_ROUTING_INFO_GERAN:
+ rc = bssgp_create_cell_id(buf, &ri->geran.raid, ri->geran.cid);
+ if (rc < 0)
+ return -EINVAL;
+ len = rc + 1;
+ break;
+ case BSSGP_RIM_ROUTING_INFO_UTRAN:
+ gsm48_encode_ra((struct gsm48_ra_id *)buf, &ri->utran.raid);
+ osmo_store16be(ri->utran.rncid, buf + 6);
+ len = 9;
+ break;
+ case BSSGP_RIM_ROUTING_INFO_EUTRAN:
+ /* Note: 3GPP TS 24.301 Figure 9.9.3.32.1 and 3GPP TS 24.008
+ * Figure 10.5.130 specify MCC/MNC encoding in the same way,
+ * so we can re-use gsm48_encode_ra() for that. */
+ raid_temp = (struct gprs_ra_id) {
+ .mcc = ri->eutran.tai.mcc,
+ .mnc = ri->eutran.tai.mnc,
+ .mnc_3_digits = ri->eutran.tai.mnc_3_digits,
+ };
+
+ gsm48_encode_ra((struct gsm48_ra_id *)buf, &raid_temp);
+ osmo_store16be(ri->eutran.tai.tac, buf + 3);
+ OSMO_ASSERT(ri->eutran.global_enb_id_len <=
+ sizeof(ri->eutran.global_enb_id));
+ memcpy(buf + 5, ri->eutran.global_enb_id,
+ ri->eutran.global_enb_id_len);
+ len = ri->eutran.global_enb_id_len + 6;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ OSMO_ASSERT(len <= BSSGP_RIM_ROUTING_INFO_MAXLEN);
+ return len;
+}
+
+/*! Encode a RIM Routing information into a human readable string.
+ * \param[buf] user provided string buffer to store the resulting string.
+ * \param[buf_len] maximum length of string buffer.
+ * \param[in] ri user provided input data struct.
+ * \returns pointer to the beginning of the resulting string stored in string buffer. */
+char *bssgp_rim_ri_name_buf(char *buf, size_t buf_len, const struct bssgp_rim_routing_info *ri)
+{
+ char plmn_str[16];
+ char enb_id_str[16];
+ char g_id_ps_str[32];
+ struct osmo_plmn_id plmn;
+ struct osmo_cell_global_id_ps g_id_ps;
+
+ if (!ri)
+ return NULL;
+
+ switch (ri->discr) {
+ case BSSGP_RIM_ROUTING_INFO_GERAN:
+ g_id_ps.rai.rac = ri->geran.raid.rac;
+ g_id_ps.rai.lac.lac = ri->geran.raid.lac;
+ g_id_ps.rai.lac.plmn.mcc = ri->geran.raid.mcc;
+ g_id_ps.rai.lac.plmn.mnc_3_digits = ri->geran.raid.mnc_3_digits;
+ g_id_ps.rai.lac.plmn.mnc = ri->geran.raid.mnc;
+ g_id_ps.cell_identity = ri->geran.cid;
+ snprintf(buf, buf_len, "%s-%s", bssgp_rim_routing_info_discr_str(ri->discr),
+ osmo_cgi_ps_name_buf(g_id_ps_str, sizeof(g_id_ps_str), &g_id_ps));
+ break;
+ case BSSGP_RIM_ROUTING_INFO_UTRAN:
+ g_id_ps.rai.rac = ri->utran.raid.rac;
+ g_id_ps.rai.lac.lac = ri->utran.raid.lac;
+ g_id_ps.rai.lac.plmn.mcc = ri->utran.raid.mcc;
+ g_id_ps.rai.lac.plmn.mnc_3_digits = ri->utran.raid.mnc_3_digits;
+ g_id_ps.rai.lac.plmn.mnc = ri->utran.raid.mnc;
+ g_id_ps.cell_identity = ri->utran.rncid;
+ snprintf(buf, buf_len, "%s-%s", bssgp_rim_routing_info_discr_str(ri->discr),
+ osmo_cgi_ps_name_buf(g_id_ps_str, sizeof(g_id_ps_str), &g_id_ps));
+ break;
+ case BSSGP_RIM_ROUTING_INFO_EUTRAN:
+ plmn.mcc = ri->eutran.tai.mcc;
+ plmn.mnc = ri->eutran.tai.mnc;
+ plmn.mnc_3_digits = ri->eutran.tai.mnc_3_digits;
+ snprintf(buf, buf_len, "%s-%s-%u-%s", bssgp_rim_routing_info_discr_str(ri->discr),
+ osmo_plmn_name_buf(plmn_str, sizeof(plmn_str), &plmn), ri->eutran.tai.tac,
+ osmo_hexdump_buf(enb_id_str, sizeof(enb_id_str), ri->eutran.global_enb_id,
+ ri->eutran.global_enb_id_len, "", false));
+ break;
+ default:
+ snprintf(buf, buf_len, "invalid");
+ }
+
+ return buf;
+}
+
+/*! Encode a RIM Routing information into a human readable string.
+ * \param[in] ri user provided input data struct.
+ * \returns pointer to the resulting string. */
+const char *bssgp_rim_ri_name(const struct bssgp_rim_routing_info *ri)
+{
+ static __thread char rim_ri_buf[64];
+ return bssgp_rim_ri_name_buf(rim_ri_buf, sizeof(rim_ri_buf), ri);
+}
+
+/*! Decode a RAN Information Request Application Container for NACC (3GPP TS 48.018, section 11.3.63.1.1).
+ * \param[out] user provided memory for decoded data struct.
+ * \param[in] buf user provided memory with the encoded value data of the IE.
+ * \returns 0 on success, -EINVAL on error. */
+int bssgp_dec_ran_inf_req_app_cont_nacc(struct bssgp_ran_inf_req_app_cont_nacc *cont, const uint8_t *buf, size_t len)
+{
+ int rc;
+
+ if (len < REP_CELL_ID_LEN)
+ return -EINVAL;
+
+ rc = gsm0808_decode_cell_id_u((union gsm0808_cell_id_u*)&cont->reprt_cell,
+ CELL_IDENT_WHOLE_GLOBAL_PS, buf, len);
+ if (rc < 0)
+ return -EINVAL;
+
+ return 0;
+}
+
+/*! Encode a RAN Information Request Application Container for NACC (3GPP TS 48.018, section 11.3.63.1.1).
+ * \param[out] buf user provided memory for the generated value part of the IE.
+ * \param[in] cont user provided input data struct.
+ * \returns length of encoded octets, -EINVAL on error. */
+int bssgp_enc_ran_inf_req_app_cont_nacc(uint8_t *buf, size_t len, const struct bssgp_ran_inf_req_app_cont_nacc *cont)
+{
+ int rc;
+ struct gprs_ra_id *raid;
+
+ if (len < REP_CELL_ID_LEN)
+ return -EINVAL;
+
+ raid = (struct gprs_ra_id *)&cont->reprt_cell.rai;
+ rc = bssgp_create_cell_id(buf, raid, cont->reprt_cell.cell_identity);
+ if (rc < 0)
+ return -EINVAL;
+ return rc;
+}
+
+/*! Decode a RAN Information Application Container (3GPP TS 48.018, section 11.3.63.2.1).
+ * \param[out] user provided memory for decoded data struct.
+ * \param[in] buf user provided memory with the encoded value data of the IE.
+ * \returns 0 on success, -EINVAL on error. */
+int bssgp_dec_ran_inf_app_cont_nacc(struct bssgp_ran_inf_app_cont_nacc *cont, const uint8_t *buf, size_t len)
+{
+ unsigned int i;
+ int remaining_buf_len;
+ int rc;
+
+ /* The given buffer must at least contain a reporting cell identifer
+ * plus one octet that defines number/type of attached sysinfo messages. */
+ if (len < REP_CELL_ID_LEN + 1)
+ return -EINVAL;
+
+ rc = gsm0808_decode_cell_id_u((union gsm0808_cell_id_u*)&cont->reprt_cell,
+ CELL_IDENT_WHOLE_GLOBAL_PS, buf, len);
+ if (rc < 0)
+ return -EINVAL;
+
+ buf += REP_CELL_ID_LEN;
+
+ cont->type_psi = buf[0] & 1;
+ cont->num_si = buf[0] >> 1;
+ buf++;
+
+ /* The number of sysinfo messages may be zero */
+ if (cont->num_si == 0)
+ return 0;
+
+ /* Check if the prospected system information messages fit in the
+ * remaining buffer space */
+ remaining_buf_len = len - REP_CELL_ID_LEN - 1;
+ if (remaining_buf_len <= 0)
+ return -EINVAL;
+ if (cont->type_psi && remaining_buf_len / BSSGP_RIM_PSI_LEN < cont->num_si)
+ return -EINVAL;
+ else if (remaining_buf_len / BSSGP_RIM_SI_LEN < cont->num_si)
+ return -EINVAL;
+
+ for (i = 0; i < cont->num_si; i++) {
+ cont->si[i] = buf;
+ if (cont->type_psi)
+ buf += BSSGP_RIM_PSI_LEN;
+ else
+ buf += BSSGP_RIM_SI_LEN;
+ }
+
+ return 0;
+}
+
+/*! Encode a RAN Information Application Container (3GPP TS 48.018, section 11.3.63.2.1).
+ * \param[out] buf user provided memory for the generated value part of the IE.
+ * \param[in] cont user provided input data struct.
+ * \returns length of encoded octets, -EINVAL on error. */
+int bssgp_enc_ran_inf_app_cont_nacc(uint8_t *buf, size_t len, const struct bssgp_ran_inf_app_cont_nacc *cont)
+{
+ uint8_t *buf_ptr = buf;
+ int rc;
+ unsigned int silen;
+ unsigned int i;
+ struct gprs_ra_id *raid;
+
+ if (cont->type_psi)
+ silen = BSSGP_RIM_PSI_LEN;
+ else
+ silen = BSSGP_RIM_SI_LEN;
+
+ /* The buffer must accept the reporting cell id, plus 1 byte to define
+ * the type and number of sysinfo messages. */
+ if (len < REP_CELL_ID_LEN + 1 + silen * cont->num_si)
+ return -EINVAL;
+
+ raid = (struct gprs_ra_id *)&cont->reprt_cell.rai;
+ rc = bssgp_create_cell_id(buf_ptr, raid, cont->reprt_cell.cell_identity);
+ if (rc < 0)
+ return -EINVAL;
+ buf_ptr += rc;
+
+ buf_ptr[0] = 0x00;
+ if (cont->type_psi)
+ buf_ptr[0] |= 0x01;
+ buf_ptr[0] |= (cont->num_si << 1);
+ buf_ptr++;
+
+ for (i = 0; i < cont->num_si; i++) {
+ memcpy(buf_ptr, cont->si[i], silen);
+ buf_ptr += silen;
+ }
+
+ return (int)(buf_ptr - buf);
+}
+
+/* 3GPP TS 48.018, table 11.3.64.1.b, NACC Cause coding */
+const struct value_string bssgp_nacc_cause_strs[] = {
+ { BSSGP_NACC_CAUSE_UNSPEC, "unspecified error" },
+ { BSSGP_NACC_CAUSE_SYNTAX_ERR, "syntax error in app container" },
+ { BSSGP_NACC_CAUSE_RPRT_CELL_MISSMTCH, "reporting cell id mismatch" },
+ { BSSGP_NACC_CAUSE_SIPSI_TYPE_ERR, "SI/PSI type error" },
+ { BSSGP_NACC_CAUSE_SIPSI_LEN_ERR, "SI/PSI inconsistent length" },
+ { BSSGP_NACC_CAUSE_SIPSI_SET_ERR, "inconsistent set of msg" },
+ { 0, NULL }
+};
+
+/*! Decode a Application Error Container for NACC (3GPP TS 48.018, section 11.3.64.1).
+ * \param[out] user provided memory for decoded data struct.
+ * \param[in] buf user provided memory with the encoded value data of the IE.
+ * \returns 0 on success, -EINVAL on error. */
+int bssgp_dec_app_err_cont_nacc(struct bssgp_app_err_cont_nacc *cont, const uint8_t *buf, size_t len)
+{
+ /* The buffer must at least contain the NACC cause code, it should also
+ * contain the application container, but we won't error if it is missing. */
+ if (len < 1)
+ return -EINVAL;
+
+ cont->nacc_cause = buf[0];
+
+ if (len > 1) {
+ cont->err_app_cont = buf + 1;
+ cont->err_app_cont_len = len - 1;
+ } else {
+ cont->err_app_cont = NULL;
+ cont->err_app_cont_len = 0;
+ }
+
+ return 0;
+}
+
+/*! Encode Application Error Container for NACC (3GPP TS 48.018, section 11.3.64.1).
+ * \param[out] buf user provided memory for the generated value part of the IE.
+ * \param[in] cont user provided input data struct.
+ * \returns length of encoded octets, -EINVAL on error. */
+int bssgp_enc_app_err_cont_nacc(uint8_t *buf, size_t len, const struct bssgp_app_err_cont_nacc *cont)
+{
+ uint8_t *buf_ptr = buf;
+
+ /* The buffer must accept the length of the application container and the NACC
+ * cause code, which is one octet in length. */
+ if (len < cont->err_app_cont_len + 1)
+ return -EINVAL;
+
+ buf_ptr[0] = cont->nacc_cause;
+ buf_ptr++;
+
+ memcpy(buf_ptr, cont->err_app_cont, cont->err_app_cont_len);
+ buf_ptr += cont->err_app_cont_len;
+
+ return (int)(buf_ptr - buf);
+}
+
+/* The structs bssgp_ran_inf_req_rim_cont, bssgp_ran_inf_rim_cont and bssgp_ran_inf_app_err_rim_cont *cont
+ * share four common fields at the beginning, we use the following struct as parameter type for the common
+ * encoder/decoder functions. (See also 3GPP TS 48.018 table 11.3.62a.1.b, table 11.3.62a.2.b, and
+ * table 11.3.62a.5.b) */
+struct bssgp_ran_inf_x_cont {
+ enum bssgp_ran_inf_app_id app_id;
+ uint32_t seq_num;
+ struct bssgp_rim_pdu_ind pdu_ind;
+ uint8_t prot_ver;
+};
+
+static int dec_rim_cont_common(struct bssgp_ran_inf_x_cont *cont, struct tlv_parsed *tp)
+{
+ if (TLVP_PRES_LEN(tp, BSSGP_IE_RIM_APP_IDENTITY, sizeof(uint8_t)))
+ cont->app_id = TLVP_VAL(tp, BSSGP_IE_RIM_APP_IDENTITY)[0];
+ else
+ return -EINVAL;
+
+ if (TLVP_PRES_LEN(tp, BSSGP_IE_RIM_SEQ_NR, sizeof(cont->seq_num)))
+ cont->seq_num = tlvp_val32be(tp, BSSGP_IE_RIM_SEQ_NR);
+ else
+ return -EINVAL;
+
+ if (TLVP_PRES_LEN(tp, BSSGP_IE_RIM_PDU_INDICATIONS, sizeof(cont->pdu_ind)))
+ memcpy(&cont->pdu_ind, TLVP_VAL(tp, BSSGP_IE_RIM_PDU_INDICATIONS), sizeof(cont->pdu_ind));
+ else
+ return -EINVAL;
+
+ if (TLVP_PRES_LEN(tp, BSSGP_IE_RIM_PROTOCOL_VERSION, sizeof(cont->prot_ver)))
+ cont->prot_ver = TLVP_VAL(tp, BSSGP_IE_RIM_PROTOCOL_VERSION)[0];
+ else
+ cont->prot_ver = 1;
+
+ return 0;
+}
+
+static uint8_t *enc_rim_cont_common(uint8_t *buf, size_t len, const struct bssgp_ran_inf_x_cont *cont)
+{
+
+ uint32_t seq_num = osmo_htonl(cont->seq_num);
+ uint8_t app_id_temp;
+ uint8_t *buf_ptr = buf;
+
+ if (len <
+ TVLV_HDR_MAXLEN * 4 + sizeof(app_id_temp) + sizeof(seq_num) + sizeof(cont->pdu_ind) +
+ sizeof(cont->prot_ver))
+ return NULL;
+
+ app_id_temp = cont->app_id;
+ buf_ptr = tvlv_put(buf_ptr, BSSGP_IE_RIM_APP_IDENTITY, sizeof(app_id_temp), &app_id_temp);
+ buf_ptr = tvlv_put(buf_ptr, BSSGP_IE_RIM_SEQ_NR, sizeof(seq_num), (uint8_t *) & seq_num);
+ buf_ptr = tvlv_put(buf_ptr, BSSGP_IE_RIM_PDU_INDICATIONS, sizeof(cont->pdu_ind), (uint8_t *) & cont->pdu_ind);
+ if (cont->prot_ver > 0)
+ buf_ptr = tvlv_put(buf_ptr, BSSGP_IE_RIM_PROTOCOL_VERSION, sizeof(cont->prot_ver), &cont->prot_ver);
+
+ return buf_ptr;
+}
+
+/* 3GPP TS 48.018, table 11.3.61.b: RIM Application Identity coding */
+const struct value_string bssgp_ran_inf_app_id_strs[] = {
+ { BSSGP_RAN_INF_APP_ID_NACC, "Network Assisted Cell Change (NACC)" },
+ { BSSGP_RAN_INF_APP_ID_SI3, "System Information 3 (SI3)" },
+ { BSSGP_RAN_INF_APP_ID_MBMS, "MBMS data channel" },
+ { BSSGP_RAN_INF_APP_ID_SON, "SON Transfer" },
+ { BSSGP_RAN_INF_APP_ID_UTRA_SI, "UTRA System Information (UTRA SI)" },
+ { 0, NULL }
+};
+
+/*! Decode a RAN Information Request RIM Container (3GPP TS 48.018, table 11.3.62a.1.b).
+ * \param[out] user provided memory for decoded data struct.
+ * \param[in] buf user provided memory with the encoded value data of the IE.
+ * \returns 0 on success, -EINVAL on error. */
+int bssgp_dec_ran_inf_req_rim_cont(struct bssgp_ran_inf_req_rim_cont *cont, const uint8_t *buf, size_t len)
+{
+ int rc;
+ struct tlv_parsed tp;
+
+ memset(cont, 0, sizeof(*cont));
+
+ rc = tlv_parse(&tp, &tvlv_att_def, buf, len, 0, 0);
+ if (rc < 0)
+ return -EINVAL;
+
+ rc = dec_rim_cont_common((struct bssgp_ran_inf_x_cont *)cont, &tp);
+ if (rc < 0)
+ return -EINVAL;
+
+ if (TLVP_PRESENT(&tp, BSSGP_IE_RIM_REQ_APP_CONTAINER)) {
+ switch (cont->app_id) {
+ case BSSGP_RAN_INF_APP_ID_NACC:
+ rc = bssgp_dec_ran_inf_req_app_cont_nacc(&cont->u.app_cont_nacc,
+ TLVP_VAL(&tp, BSSGP_IE_RIM_REQ_APP_CONTAINER),
+ TLVP_LEN(&tp, BSSGP_IE_RIM_REQ_APP_CONTAINER));
+ break;
+ case BSSGP_RAN_INF_APP_ID_SI3:
+ case BSSGP_RAN_INF_APP_ID_MBMS:
+ case BSSGP_RAN_INF_APP_ID_SON:
+ case BSSGP_RAN_INF_APP_ID_UTRA_SI:
+ /* TODO: add parsers for Si3, MBMS, SON, UTRA-SI app containers */
+ return -EOPNOTSUPP;
+ default:
+ return -EINVAL;
+ }
+
+ if (rc < 0)
+ return rc;
+ }
+
+ if (TLVP_PRES_LEN(&tp, BSSGP_IE_SON_TRANSFER_APP_ID, 1)) {
+ cont->son_trans_app_id = TLVP_VAL(&tp, BSSGP_IE_SON_TRANSFER_APP_ID);
+ cont->son_trans_app_id_len = TLVP_LEN(&tp, BSSGP_IE_SON_TRANSFER_APP_ID);
+ }
+
+ return 0;
+}
+
+/* Dub a TLVP header into a given buffer. The value part of the IE must start
+ * at the 2nd octet. Should the length field make a 3 octet TLVP header
+ * necessary (unlikely, but possible) the value part is moved ahead by one
+ * octet. The function returns a pointer to the end of value part. */
+static uint8_t *dub_tlvp_header(uint8_t *buf, uint8_t iei, uint16_t len)
+{
+ uint8_t *buf_ptr = buf;
+
+ buf_ptr[0] = iei;
+ if (len <= TVLV_MAX_ONEBYTE) {
+ buf_ptr[1] = (uint8_t) len;
+ buf_ptr[1] |= 0x80;
+ buf_ptr += TVLV_HDR_LEN;
+ } else {
+ memmove(buf_ptr + 1, buf_ptr, len);
+ buf_ptr[1] = len >> 8;
+ buf_ptr[2] = len & 0xff;
+ buf_ptr += TVLV_HDR_MAXLEN;
+ }
+ buf_ptr += len;
+
+ return buf_ptr;
+}
+
+/*! Encode a RAN Information Request RIM Container (3GPP TS 48.018, table 11.3.62a.1.b).
+ * \param[out] buf user provided memory for the generated value part of the IE.
+ * \param[in] cont user provided input data struct.
+ * \returns length of encoded octets, -EINVAL on error. */
+int bssgp_enc_ran_inf_req_rim_cont(uint8_t *buf, size_t len, const struct bssgp_ran_inf_req_rim_cont *cont)
+{
+ uint8_t *buf_ptr = buf;
+ int app_cont_len = 0;
+ int remaining_buf_len;
+
+ buf_ptr = enc_rim_cont_common(buf_ptr, len, (struct bssgp_ran_inf_x_cont *)cont);
+ if (!buf_ptr)
+ return -EINVAL;
+
+ remaining_buf_len = len - (int)(buf_ptr - buf);
+ if (remaining_buf_len <= 0)
+ return -EINVAL;
+
+ switch (cont->app_id) {
+ case BSSGP_RAN_INF_APP_ID_NACC:
+ app_cont_len =
+ bssgp_enc_ran_inf_req_app_cont_nacc(buf_ptr + TVLV_HDR_LEN, remaining_buf_len - TVLV_HDR_MAXLEN,
+ &cont->u.app_cont_nacc);
+ break;
+ case BSSGP_RAN_INF_APP_ID_SI3:
+ case BSSGP_RAN_INF_APP_ID_MBMS:
+ case BSSGP_RAN_INF_APP_ID_SON:
+ case BSSGP_RAN_INF_APP_ID_UTRA_SI:
+ /* TODO: add encoders for Si3, MBMS, SON, UTRA-SI app containers */
+ return -EOPNOTSUPP;
+ default:
+ return -EINVAL;
+ }
+
+ if (app_cont_len < 0)
+ return -EINVAL;
+ buf_ptr = dub_tlvp_header(buf_ptr, BSSGP_IE_RIM_REQ_APP_CONTAINER, app_cont_len);
+
+ remaining_buf_len = len - (int)(buf_ptr - buf);
+ if (remaining_buf_len < 0)
+ return -EINVAL;
+
+ if (cont->son_trans_app_id && cont->son_trans_app_id_len > 0) {
+ if (remaining_buf_len < cont->son_trans_app_id_len + TVLV_HDR_MAXLEN)
+ return -EINVAL;
+ buf_ptr =
+ tvlv_put(buf_ptr, BSSGP_IE_SON_TRANSFER_APP_ID, cont->son_trans_app_id_len, cont->son_trans_app_id);
+ }
+ return (int)(buf_ptr - buf);
+}
+
+/*! Decode a RAN Information RIM Container (3GPP TS 48.018, table 11.3.62a.2.b).
+ * \param[out] user provided memory for decoded data struct.
+ * \param[in] buf user provided memory with the encoded value data of the IE.
+ * \returns 0 on success, -EINVAL on error. */
+int bssgp_dec_ran_inf_rim_cont(struct bssgp_ran_inf_rim_cont *cont, const uint8_t *buf, size_t len)
+{
+ int rc;
+ struct tlv_parsed tp;
+
+ memset(cont, 0, sizeof(*cont));
+
+ rc = tlv_parse(&tp, &tvlv_att_def, buf, len, 0, 0);
+ if (rc < 0)
+ return -EINVAL;
+
+ rc = dec_rim_cont_common((struct bssgp_ran_inf_x_cont *)cont, &tp);
+ if (rc < 0)
+ return -EINVAL;
+
+ if (TLVP_PRESENT(&tp, BSSGP_IE_RAN_INFO_APP_CONTAINER)) {
+ switch (cont->app_id) {
+ case BSSGP_RAN_INF_APP_ID_NACC:
+ rc = bssgp_dec_ran_inf_app_cont_nacc(&cont->u.app_cont_nacc,
+ TLVP_VAL(&tp, BSSGP_IE_RAN_INFO_APP_CONTAINER),
+ TLVP_LEN(&tp, BSSGP_IE_RAN_INFO_APP_CONTAINER));
+ break;
+ case BSSGP_RAN_INF_APP_ID_SI3:
+ case BSSGP_RAN_INF_APP_ID_MBMS:
+ case BSSGP_RAN_INF_APP_ID_SON:
+ case BSSGP_RAN_INF_APP_ID_UTRA_SI:
+ /* TODO: add parsers for Si3, MBMS, SON, UTRA-SI app containers */
+ return -EOPNOTSUPP;
+ default:
+ return -EINVAL;
+ }
+
+ if (rc < 0)
+ return rc;
+ } else if (TLVP_PRESENT(&tp, BSSGP_IE_APP_ERROR_CONTAINER)) {
+ switch (cont->app_id) {
+ case BSSGP_RAN_INF_APP_ID_NACC:
+ rc = bssgp_dec_app_err_cont_nacc(&cont->u.app_err_cont_nacc,
+ TLVP_VAL(&tp, BSSGP_IE_APP_ERROR_CONTAINER), TLVP_LEN(&tp,
+ BSSGP_IE_APP_ERROR_CONTAINER));
+ break;
+ case BSSGP_RAN_INF_APP_ID_SI3:
+ case BSSGP_RAN_INF_APP_ID_MBMS:
+ case BSSGP_RAN_INF_APP_ID_SON:
+ case BSSGP_RAN_INF_APP_ID_UTRA_SI:
+ /* TODO: add parsers for Si3, MBMS, SON, UTRA-SI app containers */
+ return -EOPNOTSUPP;
+ default:
+ return -EINVAL;
+ }
+ if (rc < 0)
+ return rc;
+ cont->app_err = true;
+ }
+
+ if (TLVP_PRES_LEN(&tp, BSSGP_IE_SON_TRANSFER_APP_ID, 1)) {
+ cont->son_trans_app_id = TLVP_VAL(&tp, BSSGP_IE_SON_TRANSFER_APP_ID);
+ cont->son_trans_app_id_len = TLVP_LEN(&tp, BSSGP_IE_SON_TRANSFER_APP_ID);
+ }
+
+ return 0;
+}
+
+/*! Encode a RAN Information RIM Container (3GPP TS 48.018, table 11.3.62a.2.b).
+ * \param[out] buf user provided memory for the generated value part of the IE.
+ * \param[in] cont user provided input data struct.
+ * \returns length of encoded octets, -EINVAL on error. */
+int bssgp_enc_ran_inf_rim_cont(uint8_t *buf, size_t len, const struct bssgp_ran_inf_rim_cont *cont)
+{
+ uint8_t *buf_ptr = buf;
+ int app_cont_len = 0;
+ int remaining_buf_len;
+
+ buf_ptr = enc_rim_cont_common(buf_ptr, len, (struct bssgp_ran_inf_x_cont *)cont);
+ if (!buf_ptr)
+ return -EINVAL;
+
+ remaining_buf_len = len - (int)(buf_ptr - buf);
+ if (remaining_buf_len <= 0)
+ return -EINVAL;
+
+ if (cont->app_err) {
+ switch (cont->app_id) {
+ case BSSGP_RAN_INF_APP_ID_NACC:
+ app_cont_len =
+ bssgp_enc_app_err_cont_nacc(buf_ptr + TVLV_HDR_LEN, remaining_buf_len - TVLV_HDR_MAXLEN,
+ &cont->u.app_err_cont_nacc);
+ break;
+ case BSSGP_RAN_INF_APP_ID_SI3:
+ case BSSGP_RAN_INF_APP_ID_MBMS:
+ case BSSGP_RAN_INF_APP_ID_SON:
+ case BSSGP_RAN_INF_APP_ID_UTRA_SI:
+ /* TODO: add encoders for Si3, MBMS, SON, UTRA-SI app containers */
+ return -EOPNOTSUPP;
+ default:
+ return -EINVAL;
+ }
+ if (app_cont_len < 0)
+ return -EINVAL;
+ buf_ptr = dub_tlvp_header(buf_ptr, BSSGP_IE_APP_ERROR_CONTAINER, app_cont_len);
+ } else {
+ switch (cont->app_id) {
+ case BSSGP_RAN_INF_APP_ID_NACC:
+ app_cont_len =
+ bssgp_enc_ran_inf_app_cont_nacc(buf_ptr + TVLV_HDR_LEN, remaining_buf_len - TVLV_HDR_MAXLEN,
+ &cont->u.app_cont_nacc);
+ break;
+ case BSSGP_RAN_INF_APP_ID_SI3:
+ case BSSGP_RAN_INF_APP_ID_MBMS:
+ case BSSGP_RAN_INF_APP_ID_SON:
+ case BSSGP_RAN_INF_APP_ID_UTRA_SI:
+ /* TODO: add encoders for Si3, MBMS, SON, UTRA-SI app containers */
+ return -EOPNOTSUPP;
+ default:
+ return -EINVAL;
+ }
+ if (app_cont_len < 0)
+ return -EINVAL;
+ buf_ptr = dub_tlvp_header(buf_ptr, BSSGP_IE_RAN_INFO_APP_CONTAINER, app_cont_len);
+ }
+
+ remaining_buf_len = len - (int)(buf_ptr - buf);
+ if (remaining_buf_len < 0)
+ return -EINVAL;
+
+ if (cont->son_trans_app_id && cont->son_trans_app_id_len > 0) {
+ if (remaining_buf_len < cont->son_trans_app_id_len + TVLV_HDR_MAXLEN)
+ return -EINVAL;
+ buf_ptr =
+ tvlv_put(buf_ptr, BSSGP_IE_SON_TRANSFER_APP_ID, cont->son_trans_app_id_len, cont->son_trans_app_id);
+ }
+ return (int)(buf_ptr - buf);
+}
+
+/*! Decode a RAN Information ACK RIM Container (3GPP TS 48.018, table 11.3.62a.3.b).
+ * \param[out] user provided memory for decoded data struct.
+ * \param[in] buf user provided memory with the encoded value data of the IE.
+ * \returns 0 on success, -EINVAL on error. */
+int bssgp_dec_ran_inf_ack_rim_cont(struct bssgp_ran_inf_ack_rim_cont *cont, const uint8_t *buf, size_t len)
+{
+ int rc;
+ struct tlv_parsed tp;
+
+ memset(cont, 0, sizeof(*cont));
+
+ rc = tlv_parse(&tp, &tvlv_att_def, buf, len, 0, 0);
+ if (rc < 0)
+ return -EINVAL;
+
+ if (TLVP_PRES_LEN(&tp, BSSGP_IE_RIM_APP_IDENTITY, sizeof(uint8_t)))
+ cont->app_id = TLVP_VAL(&tp, BSSGP_IE_RIM_APP_IDENTITY)[0];
+ else
+ return -EINVAL;
+
+ if (TLVP_PRES_LEN(&tp, BSSGP_IE_RIM_SEQ_NR, sizeof(cont->seq_num)))
+ cont->seq_num = tlvp_val32be(&tp, BSSGP_IE_RIM_SEQ_NR);
+ else
+ return -EINVAL;
+
+ if (TLVP_PRES_LEN(&tp, BSSGP_IE_RIM_PROTOCOL_VERSION, sizeof(cont->prot_ver)))
+ cont->prot_ver = TLVP_VAL(&tp, BSSGP_IE_RIM_PROTOCOL_VERSION)[0];
+ else
+ cont->prot_ver = 1;
+
+ if (TLVP_PRES_LEN(&tp, BSSGP_IE_SON_TRANSFER_APP_ID, 1)) {
+ cont->son_trans_app_id = TLVP_VAL(&tp, BSSGP_IE_SON_TRANSFER_APP_ID);
+ cont->son_trans_app_id_len = TLVP_LEN(&tp, BSSGP_IE_SON_TRANSFER_APP_ID);
+ }
+
+ return 0;
+}
+
+/*! Encode a RAN Information ACK RIM Container (3GPP TS 48.018, table 11.3.62a.3.b).
+ * \param[out] buf user provided memory for the generated value part of the IE.
+ * \param[in] cont user provided input data struct.
+ * \returns length of encoded octets, -EINVAL on error. */
+int bssgp_enc_ran_inf_ack_rim_cont(uint8_t *buf, size_t len, const struct bssgp_ran_inf_ack_rim_cont *cont)
+{
+ uint8_t *buf_ptr = buf;
+ uint32_t seq_num = osmo_htonl(cont->seq_num);
+ uint8_t app_id_temp;
+
+ if (len <
+ 4 * TVLV_HDR_MAXLEN + sizeof(app_id_temp) + sizeof(seq_num) + sizeof(cont->prot_ver) +
+ cont->son_trans_app_id_len)
+ return -EINVAL;
+
+ app_id_temp = cont->app_id;
+ buf_ptr = tvlv_put(buf_ptr, BSSGP_IE_RIM_APP_IDENTITY, sizeof(app_id_temp), &app_id_temp);
+ buf_ptr = tvlv_put(buf_ptr, BSSGP_IE_RIM_SEQ_NR, sizeof(seq_num), (uint8_t *) & seq_num);
+
+ if (cont->prot_ver > 0)
+ buf_ptr = tvlv_put(buf_ptr, BSSGP_IE_RIM_PROTOCOL_VERSION, sizeof(cont->prot_ver), &cont->prot_ver);
+
+ if (cont->son_trans_app_id && cont->son_trans_app_id_len > 0)
+ buf_ptr =
+ tvlv_put(buf_ptr, BSSGP_IE_SON_TRANSFER_APP_ID, cont->son_trans_app_id_len, cont->son_trans_app_id);
+
+ return (int)(buf_ptr - buf);
+}
+
+/*! Decode a RAN Information Error RIM Container (3GPP TS 48.018, table 11.3.62a.4.b).
+ * \param[out] user provided memory for decoded data struct.
+ * \param[in] buf user provided memory with the encoded value data of the IE.
+ * \returns 0 on success, -EINVAL on error. */
+int bssgp_dec_ran_inf_err_rim_cont(struct bssgp_ran_inf_err_rim_cont *cont, const uint8_t *buf, size_t len)
+{
+ int rc;
+ struct tlv_parsed tp;
+
+ memset(cont, 0, sizeof(*cont));
+
+ rc = tlv_parse(&tp, &tvlv_att_def, buf, len, 0, 0);
+ if (rc < 0)
+ return -EINVAL;
+
+ if (TLVP_PRES_LEN(&tp, BSSGP_IE_RIM_APP_IDENTITY, sizeof(uint8_t)))
+ cont->app_id = TLVP_VAL(&tp, BSSGP_IE_RIM_APP_IDENTITY)[0];
+ else
+ return -EINVAL;
+
+ if (TLVP_PRES_LEN(&tp, BSSGP_IE_CAUSE, sizeof(cont->cause)))
+ cont->cause = TLVP_VAL(&tp, BSSGP_IE_CAUSE)[0];
+ else
+ return -EINVAL;
+
+ if (TLVP_PRES_LEN(&tp, BSSGP_IE_RIM_PROTOCOL_VERSION, sizeof(cont->prot_ver)))
+ cont->prot_ver = TLVP_VAL(&tp, BSSGP_IE_RIM_PROTOCOL_VERSION)[0];
+ else
+ cont->prot_ver = 1;
+
+ if (TLVP_PRESENT(&tp, BSSGP_IE_PDU_IN_ERROR)) {
+ cont->err_pdu = TLVP_VAL(&tp, BSSGP_IE_PDU_IN_ERROR);
+ cont->err_pdu_len = TLVP_LEN(&tp, BSSGP_IE_PDU_IN_ERROR);
+ } else {
+ return -EINVAL;
+ }
+
+ if (TLVP_PRES_LEN(&tp, BSSGP_IE_SON_TRANSFER_APP_ID, 1)) {
+ cont->son_trans_app_id = TLVP_VAL(&tp, BSSGP_IE_SON_TRANSFER_APP_ID);
+ cont->son_trans_app_id_len = TLVP_LEN(&tp, BSSGP_IE_SON_TRANSFER_APP_ID);
+ }
+
+ return 0;
+}
+
+/*! Encode a RAN Information Error RIM Container (3GPP TS 48.018, table 11.3.62a.4.b).
+ * \param[out] buf user provided memory for the generated value part of the IE.
+ * \param[in] cont user provided input data struct.
+ * \returns length of encoded octets, -EINVAL on error. */
+int bssgp_enc_ran_inf_err_rim_cont(uint8_t *buf, size_t len, const struct bssgp_ran_inf_err_rim_cont *cont)
+{
+ uint8_t *buf_ptr = buf;
+ uint8_t app_id_temp;
+
+ if (len <
+ TVLV_HDR_MAXLEN * 5 + sizeof(app_id_temp) + sizeof(cont->cause) + sizeof(cont->prot_ver) +
+ cont->err_pdu_len + cont->son_trans_app_id_len)
+ return -EINVAL;
+
+ app_id_temp = cont->app_id;
+ buf_ptr = tvlv_put(buf_ptr, BSSGP_IE_RIM_APP_IDENTITY, sizeof(app_id_temp), &app_id_temp);
+ buf_ptr = tvlv_put(buf_ptr, BSSGP_IE_CAUSE, sizeof(cont->cause), &cont->cause);
+
+ if (cont->prot_ver > 0)
+ buf_ptr = tvlv_put(buf_ptr, BSSGP_IE_RIM_PROTOCOL_VERSION, sizeof(cont->prot_ver), &cont->prot_ver);
+
+ if (cont->err_pdu && cont->err_pdu_len > 0)
+ buf_ptr = tvlv_put(buf_ptr, BSSGP_IE_PDU_IN_ERROR, cont->err_pdu_len, cont->err_pdu);
+ else
+ return -EINVAL;
+
+ if (cont->son_trans_app_id && cont->son_trans_app_id_len > 0)
+ buf_ptr =
+ tvlv_put(buf_ptr, BSSGP_IE_SON_TRANSFER_APP_ID, cont->son_trans_app_id_len, cont->son_trans_app_id);
+
+ return (int)(buf_ptr - buf);
+}
+
+/*! Decode a RAN Information Application Error RIM Container (3GPP TS 48.018, table 11.3.62a.5.b).
+ * \param[out] user provided memory for decoded data struct.
+ * \param[in] buf user provided memory with the encoded value data of the IE.
+ * \returns 0 on success, -EINVAL on error. */
+int bssgp_dec_ran_inf_app_err_rim_cont(struct bssgp_ran_inf_app_err_rim_cont *cont, const uint8_t *buf, size_t len)
+{
+ int rc;
+ struct tlv_parsed tp;
+
+ memset(cont, 0, sizeof(*cont));
+
+ rc = tlv_parse(&tp, &tvlv_att_def, buf, len, 0, 0);
+ if (rc < 0)
+ return -EINVAL;
+
+ rc = dec_rim_cont_common((struct bssgp_ran_inf_x_cont *)cont, &tp);
+ if (rc < 0)
+ return -EINVAL;
+
+ switch (cont->app_id) {
+ case BSSGP_RAN_INF_APP_ID_NACC:
+ rc = bssgp_dec_app_err_cont_nacc(&cont->u.app_err_cont_nacc,
+ TLVP_VAL(&tp, BSSGP_IE_APP_ERROR_CONTAINER), TLVP_LEN(&tp,
+ BSSGP_IE_APP_ERROR_CONTAINER));
+ break;
+ case BSSGP_RAN_INF_APP_ID_SI3:
+ case BSSGP_RAN_INF_APP_ID_MBMS:
+ case BSSGP_RAN_INF_APP_ID_SON:
+ case BSSGP_RAN_INF_APP_ID_UTRA_SI:
+ /* TODO: add parsers for Si3, MBMS, SON, UTRA-SI app containers */
+ return -EOPNOTSUPP;
+ default:
+ return -EINVAL;
+ }
+ if (rc < 0)
+ return rc;
+
+ return 0;
+}
+
+/*! Encode a RAN Information Application Error RIM Container (3GPP TS 48.018, table 11.3.62a.5.b).
+ * \param[out] buf user provided memory for the generated value part of the IE.
+ * \param[in] cont user provided input data struct.
+ * \returns length of encoded octets, -EINVAL on error. */
+int bssgp_enc_ran_inf_app_err_rim_cont(uint8_t *buf, size_t len, const struct bssgp_ran_inf_app_err_rim_cont *cont)
+{
+ uint8_t *buf_ptr = buf;
+ int app_cont_len = 0;
+ int remaining_buf_len;
+
+ buf_ptr = enc_rim_cont_common(buf_ptr, len, (struct bssgp_ran_inf_x_cont *)cont);
+ if (!buf_ptr)
+ return -EINVAL;
+
+ remaining_buf_len = len - (int)(buf_ptr - buf);
+ if (remaining_buf_len <= 0)
+ return -EINVAL;
+
+ switch (cont->app_id) {
+ case BSSGP_RAN_INF_APP_ID_NACC:
+ app_cont_len =
+ bssgp_enc_app_err_cont_nacc(buf_ptr + TVLV_HDR_LEN, remaining_buf_len - TVLV_HDR_MAXLEN,
+ &cont->u.app_err_cont_nacc);
+ break;
+ case BSSGP_RAN_INF_APP_ID_SI3:
+ case BSSGP_RAN_INF_APP_ID_MBMS:
+ case BSSGP_RAN_INF_APP_ID_SON:
+ case BSSGP_RAN_INF_APP_ID_UTRA_SI:
+ /* TODO: add encoders for Si3, MBMS, SON, UTRA-SI app containers */
+ return -EOPNOTSUPP;
+ default:
+ return -EINVAL;
+ }
+ if (app_cont_len < 0)
+ return -EINVAL;
+ buf_ptr = dub_tlvp_header(buf_ptr, BSSGP_IE_APP_ERROR_CONTAINER, app_cont_len);
+
+ return (int)(buf_ptr - buf);
+}
+
+/*! Parse a given message buffer into a rim-pdu struct.
+ * \param[out] pdu user provided memory for the resulting RAN INFORMATION PDU.
+ * \param[in] msg BSSGP message buffer that contains the encoded RAN INFORMATION PDU.
+ * \returns 0 on sccess, -EINVAL on error. */
+int bssgp_parse_rim_pdu(struct bssgp_ran_information_pdu *pdu, const struct msgb *msg)
+{
+ struct tlv_parsed tp[2];
+ struct bssgp_normal_hdr *bgph = (struct bssgp_normal_hdr *)msgb_bssgph(msg);
+ int data_len;
+ int rc;
+ uint16_t nsei = msgb_nsei(msg);
+
+ memset(pdu, 0, sizeof(*pdu));
+
+ data_len = msgb_bssgp_len(msg) - sizeof(*bgph);
+ if (data_len < 0)
+ return -EINVAL;
+
+ rc = osmo_tlv_prot_parse(&osmo_pdef_bssgp, tp, ARRAY_SIZE(tp), bgph->pdu_type, bgph->data, data_len, 0, 0,
+ DLBSSGP, __func__);
+ if (rc < 0)
+ return -EINVAL;
+
+ if (TLVP_PRESENT(&tp[0], BSSGP_IE_RIM_ROUTING_INFO)) {
+ rc = bssgp_parse_rim_ri(&pdu->routing_info_dest, TLVP_VAL(&tp[0], BSSGP_IE_RIM_ROUTING_INFO),
+ TLVP_LEN(&tp[0], BSSGP_IE_RIM_ROUTING_INFO));
+ if (rc < 0) {
+ LOGP(DLBSSGP, LOGL_ERROR, "BSSGP RIM (NSEI=%u) invalid Destination Cell Identifier IE\n", nsei);
+ return -EINVAL;
+ }
+ } else {
+ LOGP(DLBSSGP, LOGL_ERROR, "BSSGP RIM (NSEI=%u) missing Destination Cell Identifier IE\n", nsei);
+ return -EINVAL;
+ }
+
+ if (TLVP_PRESENT(&tp[1], BSSGP_IE_RIM_ROUTING_INFO)) {
+ rc = bssgp_parse_rim_ri(&pdu->routing_info_src, TLVP_VAL(&tp[1], BSSGP_IE_RIM_ROUTING_INFO),
+ TLVP_LEN(&tp[1], BSSGP_IE_RIM_ROUTING_INFO));
+ if (rc < 0) {
+ LOGP(DLBSSGP, LOGL_ERROR, "BSSGP RIM (NSEI=%u) invalid Destination Cell Identifier IE\n", nsei);
+ return -EINVAL;
+ }
+ } else {
+ LOGP(DLBSSGP, LOGL_ERROR, "BSSGP RIM (NSEI=%u) missing Source Cell Identifier IE\n", nsei);
+ return -EINVAL;
+ }
+
+ if (TLVP_PRESENT(&tp[0], BSSGP_IE_RI_REQ_RIM_CONTAINER))
+ pdu->rim_cont_iei = BSSGP_IE_RI_REQ_RIM_CONTAINER;
+ else if (TLVP_PRESENT(&tp[0], BSSGP_IE_RI_RIM_CONTAINER))
+ pdu->rim_cont_iei = BSSGP_IE_RI_RIM_CONTAINER;
+ else if (TLVP_PRESENT(&tp[0], BSSGP_IE_RI_APP_ERROR_RIM_CONT))
+ pdu->rim_cont_iei = BSSGP_IE_RI_APP_ERROR_RIM_CONT;
+ else if (TLVP_PRESENT(&tp[0], BSSGP_IE_RI_ACK_RIM_CONTAINER))
+ pdu->rim_cont_iei = BSSGP_IE_RI_ACK_RIM_CONTAINER;
+ else if (TLVP_PRESENT(&tp[0], BSSGP_IE_RI_ERROR_RIM_COINTAINER))
+ pdu->rim_cont_iei = BSSGP_IE_RI_ERROR_RIM_COINTAINER;
+ else {
+ LOGP(DLBSSGP, LOGL_ERROR, "BSSGP RIM (NSEI=%u) missing or wrong RIM Container IE\n", nsei);
+ return -EINVAL;
+ }
+
+ pdu->rim_cont = TLVP_VAL(&tp[0], pdu->rim_cont_iei);
+ pdu->rim_cont_len = TLVP_LEN(&tp[0], pdu->rim_cont_iei);
+
+ /* Make sure the rim container field is not empty */
+ if (pdu->rim_cont_len < 1)
+ return -EINVAL;
+ if (!pdu->rim_cont)
+ return -EINVAL;
+
+ /* Note: It is not an error if we fail to parse the RIM container,
+ * since there are applications where parsing the RIM container
+ * is not necessary (routing). It is up to the API user to check
+ * the results. */
+ switch (pdu->rim_cont_iei) {
+ case BSSGP_IE_RI_REQ_RIM_CONTAINER:
+ rc = bssgp_dec_ran_inf_req_rim_cont(&pdu->decoded.req_rim_cont, pdu->rim_cont, pdu->rim_cont_len);
+ break;
+ case BSSGP_IE_RI_RIM_CONTAINER:
+ rc = bssgp_dec_ran_inf_rim_cont(&pdu->decoded.rim_cont, pdu->rim_cont, pdu->rim_cont_len);
+ break;
+ case BSSGP_IE_RI_APP_ERROR_RIM_CONT:
+ rc = bssgp_dec_ran_inf_app_err_rim_cont(&pdu->decoded.app_err_rim_cont, pdu->rim_cont,
+ pdu->rim_cont_len);
+ break;
+ case BSSGP_IE_RI_ACK_RIM_CONTAINER:
+ rc = bssgp_dec_ran_inf_ack_rim_cont(&pdu->decoded.ack_rim_cont, pdu->rim_cont, pdu->rim_cont_len);
+ break;
+ case BSSGP_IE_RI_ERROR_RIM_COINTAINER:
+ rc = bssgp_dec_ran_inf_err_rim_cont(&pdu->decoded.err_rim_cont, pdu->rim_cont, pdu->rim_cont_len);
+ break;
+ default:
+ LOGP(DLBSSGP, LOGL_DEBUG, "BSSGP RIM (NSEI=%u) cannot parse unknown RIM container.\n", nsei);
+ return 0;
+ }
+ if (rc < 0) {
+ LOGP(DLBSSGP, LOGL_DEBUG, "BSSGP RIM (NSEI=%u) unable to parse RIM container.\n", nsei);
+ return 0;
+ }
+ pdu->decoded_present = true;
+
+ return 0;
+}
+
+/*! Encode a given rim-pdu struct into a message buffer.
+ * \param[out] pdu user provided memory that contains the RAN INFORMATION PDU to encode.
+ * \returns BSSGP message buffer on sccess, NULL on error. */
+struct msgb *bssgp_encode_rim_pdu(const struct bssgp_ran_information_pdu *pdu)
+{
+ struct msgb *msg = bssgp_msgb_alloc();
+ struct bssgp_normal_hdr *bgph;
+ uint8_t rim_ri_buf[BSSGP_RIM_ROUTING_INFO_MAXLEN];
+ int rc;
+
+ if (!msg)
+ return NULL;
+ bgph = (struct bssgp_normal_hdr *)msgb_put(msg, sizeof(*bgph));
+
+ /* Set PDU type based on RIM container type */
+ switch (pdu->rim_cont_iei) {
+ case BSSGP_IE_RI_REQ_RIM_CONTAINER:
+ bgph->pdu_type = BSSGP_PDUT_RAN_INFO_REQ;
+ break;
+ case BSSGP_IE_RI_RIM_CONTAINER:
+ bgph->pdu_type = BSSGP_PDUT_RAN_INFO;
+ break;
+ case BSSGP_IE_RI_APP_ERROR_RIM_CONT:
+ bgph->pdu_type = BSSGP_PDUT_RAN_INFO_APP_ERROR;
+ break;
+ case BSSGP_IE_RI_ACK_RIM_CONTAINER:
+ bgph->pdu_type = BSSGP_PDUT_RAN_INFO_ACK;
+ break;
+ case BSSGP_IE_RI_ERROR_RIM_COINTAINER:
+ bgph->pdu_type = BSSGP_PDUT_RAN_INFO_ERROR;
+ break;
+ default:
+ /* The caller must correctly specify the container type! */
+ OSMO_ASSERT(false);
+ }
+
+ /* Put RIM routing information */
+ rc = bssgp_create_rim_ri(rim_ri_buf, &pdu->routing_info_dest);
+ if (rc < 0 || rc > BSSGP_RIM_ROUTING_INFO_MAXLEN)
+ goto error;
+ msgb_tvlv_put(msg, BSSGP_IE_RIM_ROUTING_INFO, rc, rim_ri_buf);
+ rc = bssgp_create_rim_ri(rim_ri_buf, &pdu->routing_info_src);
+ if (rc < 0 || rc > BSSGP_RIM_ROUTING_INFO_MAXLEN)
+ goto error;
+ msgb_tvlv_put(msg, BSSGP_IE_RIM_ROUTING_INFO, rc, rim_ri_buf);
+
+ /* Put RIM container */
+ if (pdu->decoded_present) {
+ uint8_t *rim_cont_buf = talloc_zero_size(msg, msg->data_len);
+ if (!rim_cont_buf)
+ goto error;
+
+ switch (pdu->rim_cont_iei) {
+ case BSSGP_IE_RI_REQ_RIM_CONTAINER:
+ rc = bssgp_enc_ran_inf_req_rim_cont(rim_cont_buf, msg->data_len, &pdu->decoded.req_rim_cont);
+ break;
+ case BSSGP_IE_RI_RIM_CONTAINER:
+ rc = bssgp_enc_ran_inf_rim_cont(rim_cont_buf, msg->data_len, &pdu->decoded.rim_cont);
+ break;
+ case BSSGP_IE_RI_APP_ERROR_RIM_CONT:
+ rc = bssgp_enc_ran_inf_app_err_rim_cont(rim_cont_buf, msg->data_len,
+ &pdu->decoded.app_err_rim_cont);
+ break;
+ case BSSGP_IE_RI_ACK_RIM_CONTAINER:
+ rc = bssgp_enc_ran_inf_ack_rim_cont(rim_cont_buf, msg->data_len, &pdu->decoded.ack_rim_cont);
+ break;
+ case BSSGP_IE_RI_ERROR_RIM_COINTAINER:
+ rc = bssgp_enc_ran_inf_err_rim_cont(rim_cont_buf, msg->data_len, &pdu->decoded.err_rim_cont);
+ break;
+ default:
+ /* The API user must set the iei properly! */
+ OSMO_ASSERT(false);
+ }
+ if (rc < 0) {
+ talloc_free(rim_cont_buf);
+ goto error;
+ }
+
+ msgb_tvlv_put(msg, pdu->rim_cont_iei, rc, rim_cont_buf);
+ talloc_free(rim_cont_buf);
+ } else {
+ /* Make sure the RIM container is actually present. */
+ OSMO_ASSERT(pdu->rim_cont_iei != 0 && pdu->rim_cont_len > 0 && pdu->rim_cont);
+ msgb_tvlv_put(msg, pdu->rim_cont_iei, pdu->rim_cont_len, pdu->rim_cont);
+ }
+
+ return msg;
+error:
+ msgb_free(msg);
+ return 0;
+}
+
+/*! Send RIM RAN INFORMATION REQUEST via BSSGP (3GPP TS 48.018, section 10.6.1).
+ * \param[in] pdu user provided memory for the RAN INFORMATION PDU to be sent.
+ * \param[in] nsei BSSGP network service entity identifier (NSEI).
+ * \returns 0 on sccess, -EINVAL on error. */
+int bssgp_tx_rim(const struct bssgp_ran_information_pdu *pdu, uint16_t nsei)
+{
+ struct msgb *msg;
+ struct bssgp_normal_hdr *bgph;
+ char ri_src_str[64];
+ char ri_dest_str[64];
+
+ /* Encode RIM PDU into mesage buffer */
+ msg = bssgp_encode_rim_pdu(pdu);
+ if (!msg) {
+ LOGP(DLBSSGP, LOGL_ERROR,
+ "BSSGP RIM (NSEI=%u) unable to encode BSSGP RIM PDU\n", nsei);
+ return -EINVAL;
+ }
+
+ msgb_nsei(msg) = nsei;
+ msgb_bvci(msg) = 0; /* Signalling */
+
+ bgph = (struct bssgp_normal_hdr *)msgb_bssgph(msg);
+ DEBUGP(DLBSSGP, "BSSGP BVCI=0 NSEI=%u Tx RIM-PDU:%s, src=%s, dest=%s\n",
+ nsei, bssgp_pdu_str(bgph->pdu_type),
+ bssgp_rim_ri_name_buf(ri_src_str, sizeof(ri_src_str), &pdu->routing_info_src),
+ bssgp_rim_ri_name_buf(ri_dest_str, sizeof(ri_dest_str), &pdu->routing_info_dest));
+
+ return bssgp_ns_send(bssgp_ns_send_data, msg);
+}
+
+/*! Send encoded RAN TRANSPARENT CONTAINER via BSSGP (3GPP TS 29.060, section 7.7.43).
+ * \param[in] msg user provided memory for the encoded RAN TRANSPARENT CONTAINER to be sent.
+ * (this function will take ownership of msg).
+ * \param[in] nsei BSSGP network service entity identifier (NSEI).
+ * \returns 0 on sccess, -EINVAL on error. */
+int bssgp_tx_rim_encoded(struct msgb *msg, uint16_t nsei)
+{
+ struct bssgp_normal_hdr *bgph;
+
+ msgb_nsei(msg) = nsei;
+ msgb_bvci(msg) = 0; /* Signalling */
+
+ bgph = (struct bssgp_normal_hdr *)msgb_bssgph(msg);
+ DEBUGP(DLBSSGP, "BSSGP BVCI=0 NSEI=%u Tx RIM-PDU:%s\n",
+ nsei, bssgp_pdu_str(bgph->pdu_type));
+
+ return bssgp_ns_send(bssgp_ns_send_data, msg);
+}
+
+/* For internal use only (called from gprs_bssgp.c) */
+int bssgp_rx_rim(struct msgb *msg, struct tlv_parsed *tp, uint16_t bvci)
+{
+ struct osmo_bssgp_prim nmp;
+ uint16_t nsei = msgb_nsei(msg);
+ struct bssgp_normal_hdr *bgph = (struct bssgp_normal_hdr *)msgb_bssgph(msg);
+ enum bssgp_prim prim;
+ char ri_src_str[64];
+ char ri_dest_str[64];
+
+ /* Specify PRIM type based on the RIM PDU */
+ switch (bgph->pdu_type) {
+ case BSSGP_PDUT_RAN_INFO:
+ case BSSGP_PDUT_RAN_INFO_REQ:
+ case BSSGP_PDUT_RAN_INFO_ACK:
+ case BSSGP_PDUT_RAN_INFO_ERROR:
+ case BSSGP_PDUT_RAN_INFO_APP_ERROR:
+ prim = PRIM_BSSGP_RIM_PDU_TRANSFER;
+ break;
+ default:
+ /* Caller already makes sure that this can't happen. */
+ OSMO_ASSERT(false);
+ }
+
+ /* Send BSSGP RIM indication to NM */
+ memset(&nmp, 0, sizeof(nmp));
+ nmp.nsei = nsei;
+ nmp.bvci = bvci;
+ nmp.tp = tp;
+ if (bssgp_parse_rim_pdu(&nmp.u.rim_pdu, msg) < 0)
+ return bssgp_tx_status(BSSGP_CAUSE_MISSING_MAND_IE, NULL, msg);
+ DEBUGP(DLBSSGP, "BSSGP BVCI=%u Rx RIM-PDU:%s, src=%s, dest=%s\n",
+ bvci, bssgp_pdu_str(bgph->pdu_type),
+ bssgp_rim_ri_name_buf(ri_src_str, sizeof(ri_src_str), &nmp.u.rim_pdu.routing_info_src),
+ bssgp_rim_ri_name_buf(ri_dest_str, sizeof(ri_dest_str), &nmp.u.rim_pdu.routing_info_dest));
+ osmo_prim_init(&nmp.oph, SAP_BSSGP_RIM, prim, PRIM_OP_INDICATION, msg);
+ bssgp_prim_cb(&nmp.oph, NULL);
+
+ return 0;
+}
diff --git a/src/gb/gprs_bssgp_util.c b/src/gb/gprs_bssgp_util.c
index 917f1f32..92896c1f 100644
--- a/src/gb/gprs_bssgp_util.c
+++ b/src/gb/gprs_bssgp_util.c
@@ -33,7 +33,6 @@
#include <osmocom/gprs/gprs_ns.h>
#include "gprs_bssgp_internal.h"
-#include "common_vty.h"
struct gprs_ns_inst *bssgp_nsi;
@@ -107,6 +106,8 @@ static const struct value_string bssgp_pdu_strings[] = {
{ BSSGP_PDUT_UL_UNITDATA, "UL-UNITDATA" },
{ BSSGP_PDUT_RA_CAPABILITY, "RA-CAPABILITY" },
{ BSSGP_PDUT_PTM_UNITDATA, "PTM-UNITDATA" },
+ { BSSGP_PDUT_DL_MMBS_UNITDATA, "DL-MBMS-UNITDATA" },
+ { BSSGP_PDUT_UL_MMBS_UNITDATA, "UL-MBMS-UNITDATA" },
{ BSSGP_PDUT_PAGING_PS, "PAGING-PS" },
{ BSSGP_PDUT_PAGING_CS, "PAGING-CS" },
{ BSSGP_PDUT_RA_CAPA_UDPATE, "RA-CAPABILITY-UPDATE" },
@@ -118,6 +119,10 @@ static const struct value_string bssgp_pdu_strings[] = {
{ BSSGP_PDUT_RESUME, "RESUME" },
{ BSSGP_PDUT_RESUME_ACK, "RESUME-ACK" },
{ BSSGP_PDUT_RESUME_NACK, "RESUME-NACK" },
+ { BSSGP_PDUT_DUMMY_PAGING_PS, "DUMMY-PAGING-PS" },
+ { BSSGP_PDUT_DUMMY_PAGING_PS_RESP, "DUMMY-PAGING-PS-RESP" },
+ { BSSGP_PDUT_MS_REGISTR_ENQ, "MS-REGISTRATION-ENQ" },
+ { BSSGP_PDUT_MS_REGISTR_ENQ_RESP, "MS-REGISTRATION-ENQ-RESP" },
{ BSSGP_PDUT_BVC_BLOCK, "BVC-BLOCK" },
{ BSSGP_PDUT_BVC_BLOCK_ACK, "BVC-BLOCK-ACK" },
{ BSSGP_PDUT_BVC_RESET, "BVC-RESET" },
@@ -131,8 +136,11 @@ static const struct value_string bssgp_pdu_strings[] = {
{ BSSGP_PDUT_FLUSH_LL, "FLUSH-LL" },
{ BSSGP_PDUT_FLUSH_LL_ACK, "FLUSH-LL-ACK" },
{ BSSGP_PDUT_LLC_DISCARD, "LLC DISCARDED" },
+ { BSSGP_PDUT_FLOW_CONTROL_PFC, "FLOW-CONTROL-PFC" },
+ { BSSGP_PDUT_FLOW_CONTROL_PFC_ACK, "FLOW-CONTROL-PFC-ACK" },
{ BSSGP_PDUT_SGSN_INVOKE_TRACE, "SGSN-INVOKE-TRACE" },
{ BSSGP_PDUT_STATUS, "STATUS" },
+ { BSSGP_PDUT_OVERLOAD, "OVERLOAD" },
{ BSSGP_PDUT_DOWNLOAD_BSS_PFC, "DOWNLOAD-BSS-PFC" },
{ BSSGP_PDUT_CREATE_BSS_PFC, "CREATE-BSS-PFC" },
{ BSSGP_PDUT_CREATE_BSS_PFC_ACK, "CREATE-BSS-PFC-ACK" },
@@ -141,9 +149,338 @@ static const struct value_string bssgp_pdu_strings[] = {
{ BSSGP_PDUT_MODIFY_BSS_PFC_ACK, "MODIFY-BSS-PFC-ACK" },
{ BSSGP_PDUT_DELETE_BSS_PFC, "DELETE-BSS-PFC" },
{ BSSGP_PDUT_DELETE_BSS_PFC_ACK, "DELETE-BSS-PFC-ACK" },
+ { BSSGP_PDUT_DELETE_BSS_PFC_REQ, "DELETE-BSS-PFC-REQ" },
+ { BSSGP_PDUT_PS_HO_REQUIRED, "PS-HO-REQUIRED" },
+ { BSSGP_PDUT_PS_HO_REQUIRED_ACK, "PS-HO-REQUIRED-ACK" },
+ { BSSGP_PDUT_PS_HO_REQUIRED_NACK, "PS-HO-REQUIRED-NACK" },
+ { BSSGP_PDUT_PS_HO_REQUEST, "PS-HO-REQUEST" },
+ { BSSGP_PDUT_PS_HO_REQUEST_ACK, "PS-HO-REQUEST-ACK" },
+ { BSSGP_PDUT_PS_HO_REQUEST_NACK, "PS-HO-REQUEST-NACK" },
+ { BSSGP_PDUT_PS_HO_COMPLETE, "PS-HO-COMPLETE" },
+ { BSSGP_PDUT_PS_HO_CANCEL, "PS-HO-CANCEL" },
+ { BSSGP_PDUT_PS_HO_COMPLETE_ACK, "PS-HO-COMPLETE-ACK" },
+ { BSSGP_PDUT_PERFORM_LOC_REQ, "PERFORM-LOC-REQ" },
+ { BSSGP_PDUT_PERFORM_LOC_RESP, "PERFORM-LOC-RESP" },
+ { BSSGP_PDUT_PERFORM_LOC_ABORT, "PERFORM-LOC-ABORT" },
+ { BSSGP_PDUT_POSITION_COMMAND, "POSITION-COMMAND" },
+ { BSSGP_PDUT_POSITION_RESPONSE, "POSITION-RESPONSE" },
+ { BSSGP_PDUT_RAN_INFO, "RAN-INFO" },
+ { BSSGP_PDUT_RAN_INFO_REQ, "RAN-INFO-REQ" },
+ { BSSGP_PDUT_RAN_INFO_ACK, "RAN-INFO-ACK" },
+ { BSSGP_PDUT_RAN_INFO_ERROR, "RAN-INFO-ERROR" },
+ { BSSGP_PDUT_RAN_INFO_APP_ERROR, "RAN-INFO-APP-ERROR" },
+ { BSSGP_PDUT_MBMS_START_REQ, "MBMS-START-REQ" },
+ { BSSGP_PDUT_MBMS_START_RESP, "MBMS-START-RESP" },
+ { BSSGP_PDUT_MBMS_STOP_REQ, "MBMS-STOP-REQ" },
+ { BSSGP_PDUT_MBMS_STOP_RESP, "MBMS-STOP-RESP" },
+ { BSSGP_PDUT_MBMS_UPDATE_REQ, "MBMS-UPDATE-REQ" },
+ { BSSGP_PDUT_MBMS_UPDATE_RESP, "MBMS-UPDATE-RESP" },
{ 0, NULL },
};
+static const uint8_t dl_ud_ies[] = { BSSGP_IE_PDU_LIFETIME };
+static const uint8_t ul_ud_ies[] = { BSSGP_IE_CELL_ID };
+static const uint8_t ra_cap_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_MS_RADIO_ACCESS_CAP };
+static const uint8_t dl_mb_ud_ies[] = { BSSGP_IE_PDU_LIFETIME, BSSGP_IE_TMGI, BSSGP_IE_LLC_PDU };
+static const uint8_t ul_mb_ud_ies[] = { BSSGP_IE_PDU_LIFETIME, BSSGP_IE_TMGI, BSSGP_IE_LLC_PDU };
+static const uint8_t pag_ps_ies[] = { BSSGP_IE_IMSI, BSSGP_IE_QOS_PROFILE };
+static const uint8_t pag_cs_ies[] = { BSSGP_IE_IMSI, BSSGP_IE_DRX_PARAMS };
+static const uint8_t ra_cap_upd_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_TAG };
+static const uint8_t ra_cap_upd_ack_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_TAG, BSSGP_IE_RA_CAP_UPD_CAUSE };
+static const uint8_t rad_sts_ies[] = { BSSGP_IE_RADIO_CAUSE };
+static const uint8_t suspend_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_ROUTEING_AREA };
+static const uint8_t suspend_ack_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_ROUTEING_AREA, BSSGP_IE_SUSPEND_REF_NR };
+static const uint8_t suspend_nack_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_ROUTEING_AREA };
+static const uint8_t resume_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_ROUTEING_AREA, BSSGP_IE_SUSPEND_REF_NR };
+static const uint8_t resume_ack_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_ROUTEING_AREA };
+static const uint8_t resume_nack_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_ROUTEING_AREA };
+static const uint8_t d_pag_ps_ies[] = { BSSGP_IE_IMSI };
+static const uint8_t d_pag_ps_resp_ies[] = { BSSGP_IE_IMSI, BSSGP_IE_T_UNTIL_NEXT_PAGING };
+static const uint8_t d_pag_ps_rej_ies[] = { BSSGP_IE_IMSI, BSSGP_IE_T_UNTIL_NEXT_PAGING };
+static const uint8_t ms_reg_enq_ies[] = { BSSGP_IE_IMSI };
+static const uint8_t ms_reg_enq_res_ies[] = { BSSGP_IE_IMSI };
+static const uint8_t flush_ll_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_BVCI };
+static const uint8_t flush_ll_ack_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_FLUSH_ACTION };
+static const uint8_t llc_disc_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_LLC_FRAMES_DISCARDED, BSSGP_IE_BVCI,
+ BSSGP_IE_NUM_OCT_AFF };
+static const uint8_t fc_bvc_ies[] = { BSSGP_IE_TAG, BSSGP_IE_BVC_BUCKET_SIZE, BSSGP_IE_BUCKET_LEAK_RATE,
+ BSSGP_IE_BMAX_DEFAULT_MS, BSSGP_IE_R_DEFAULT_MS };
+static const uint8_t fc_bvc_ack_ies[] = { BSSGP_IE_TAG };
+static const uint8_t fc_ms_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_TAG, BSSGP_IE_MS_BUCKET_SIZE,
+ BSSGP_IE_BUCKET_LEAK_RATE };
+static const uint8_t fc_ms_ack_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_TAG };
+static const uint8_t block_ies[] = { BSSGP_IE_BVCI, BSSGP_IE_CAUSE };
+static const uint8_t block_ack_ies[] = { BSSGP_IE_BVCI };
+static const uint8_t unblock_ies[] = { BSSGP_IE_BVCI };
+static const uint8_t unblock_ack_ies[] = { BSSGP_IE_BVCI };
+static const uint8_t reset_ies[] = { BSSGP_IE_BVCI, BSSGP_IE_CAUSE };
+static const uint8_t reset_ack_ies[] = { BSSGP_IE_BVCI };
+static const uint8_t status_ies[] = { BSSGP_IE_CAUSE };
+static const uint8_t inv_trc_ies[] = { BSSGP_IE_TRACE_TYPE, BSSGP_IE_TRACE_REFERENC };
+static const uint8_t dl_bss_pfc_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_PACKET_FLOW_ID };
+static const uint8_t crt_bss_pfc_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_PACKET_FLOW_ID,
+ BSSGP_IE_PACKET_FLOW_TIMER, BSSGP_IE_AGG_BSS_QOS_PROFILE };
+static const uint8_t crt_bss_pfc_ack_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_PACKET_FLOW_ID,
+ BSSGP_IE_AGG_BSS_QOS_PROFILE };
+static const uint8_t crt_bss_pfc_nack_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_PACKET_FLOW_ID, BSSGP_IE_CAUSE };
+static const uint8_t mod_bss_pfc_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_PACKET_FLOW_ID,
+ BSSGP_IE_AGG_BSS_QOS_PROFILE };
+static const uint8_t mod_bss_pfc_ack_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_PACKET_FLOW_ID,
+ BSSGP_IE_PACKET_FLOW_TIMER, BSSGP_IE_AGG_BSS_QOS_PROFILE };
+static const uint8_t del_bss_pfc_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_PACKET_FLOW_ID };
+static const uint8_t del_bss_pfc_ack_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_PACKET_FLOW_ID };
+static const uint8_t fc_pfc_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_TAG, BSSGP_IE_PFC_FLOW_CTRL_PARAMS };
+static const uint8_t fc_pfc_ack_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_TAG };
+static const uint8_t del_bss_pfc_req_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_PACKET_FLOW_ID, BSSGP_IE_CAUSE };
+static const uint8_t ps_ho_required_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_CAUSE, BSSGP_IE_CELL_ID,
+ BSSGP_IE_ACTIVE_PFC_LIST };
+static const uint8_t ps_ho_required_ack_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_LIST_OF_SETUP_PFC };
+static const uint8_t ps_ho_required_nack_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_CAUSE };
+static const uint8_t ps_ho_request_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_IMSI, BSSGP_IE_CAUSE,
+ BSSGP_IE_CELL_ID, BSSGP_IE_SBSS_TO_TBSS_TR_CONT,
+ BSSGP_IE_PFC_TO_BE_SETUP_LIST };
+static const uint8_t ps_ho_request_ack_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_LIST_OF_SETUP_PFC,
+ BSSGP_IE_TBSS_TO_SBSS_TR_CONT };
+static const uint8_t ps_ho_request_nack_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_CAUSE };
+static const uint8_t ps_ho_compl_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_IMSI };
+static const uint8_t ps_ho_cancel_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_CAUSE, BSSGP_IE_CELL_ID };
+static const uint8_t ps_ho_compl_ack_ies[] = { BSSGP_IE_TLLI };
+static const uint8_t overload_ies[] = { BSSGP_IE_PRIO_CLASS_IND };
+static const uint8_t rinfo_ies[] = { BSSGP_IE_RIM_ROUTING_INFO, BSSGP_IE_RI_RIM_CONTAINER };
+static const uint8_t rinfo_req_ies[] = { BSSGP_IE_RIM_ROUTING_INFO, BSSGP_IE_RI_REQ_RIM_CONTAINER };
+static const uint8_t rinfo_ack_ies[] = { BSSGP_IE_RIM_ROUTING_INFO, BSSGP_IE_RI_ACK_RIM_CONTAINER };
+static const uint8_t rinfo_err_ies[] = { BSSGP_IE_RIM_ROUTING_INFO, BSSGP_IE_RI_ERROR_RIM_COINTAINER };
+static const uint8_t rinfo_aerr_ies[] = { BSSGP_IE_RIM_ROUTING_INFO, BSSGP_IE_RI_APP_ERROR_RIM_CONT };
+
+#define DL BSSGP_PDUF_DL
+#define UL BSSGP_PDUF_UL
+#define SIG BSSGP_PDUF_SIG
+#define PTP BSSGP_PDUF_PTP
+#define PTM BSSGP_PDUF_PTM
+
+const struct osmo_tlv_prot_def osmo_pdef_bssgp = {
+ .name = "BSSGP",
+ .tlv_def = &tvlv_att_def,
+ .msg_def = {
+ [BSSGP_PDUT_DL_UNITDATA] = MSG_DEF("DL-UNITDATA", dl_ud_ies, DL|PTP),
+ [BSSGP_PDUT_UL_UNITDATA] = MSG_DEF("UL-UNITDATA", ul_ud_ies, UL|PTP),
+ [BSSGP_PDUT_RA_CAPABILITY] = MSG_DEF("RA-CAPABILITY", ra_cap_ies, DL|PTP),
+ [BSSGP_PDUT_DL_MMBS_UNITDATA] = MSG_DEF("DL-MBMS-UNITDATA", dl_mb_ud_ies, DL|PTM),
+ [BSSGP_PDUT_UL_MMBS_UNITDATA] = MSG_DEF("UL-MBMS-UNITDATA", ul_mb_ud_ies, UL|PTM),
+ [BSSGP_PDUT_PAGING_PS] = MSG_DEF("PAGING-PS", pag_ps_ies, DL|PTP|SIG),
+ [BSSGP_PDUT_PAGING_CS] = MSG_DEF("PAGING-CS", pag_cs_ies, DL|PTP|SIG),
+ [BSSGP_PDUT_RA_CAPA_UDPATE] = MSG_DEF("RA-CAPABILITY-UPDATE", ra_cap_upd_ies, UL|PTP),
+ [BSSGP_PDUT_RA_CAPA_UPDATE_ACK] = MSG_DEF("RA-CAPABILITY-UPDATE-ACK", ra_cap_upd_ack_ies, DL|PTP),
+ [BSSGP_PDUT_RADIO_STATUS] = MSG_DEF("RADIO-STATUS", rad_sts_ies, UL|PTP),
+ [BSSGP_PDUT_SUSPEND] = MSG_DEF("SUSPEND", suspend_ies, UL|SIG),
+ [BSSGP_PDUT_SUSPEND_ACK] = MSG_DEF("SUSPEND-ACK", suspend_ack_ies, DL|SIG),
+ [BSSGP_PDUT_SUSPEND_NACK] = MSG_DEF("SUSPEND-NACK", suspend_nack_ies, DL|SIG),
+ [BSSGP_PDUT_RESUME] = MSG_DEF("RESUME", resume_ies, UL|SIG),
+ [BSSGP_PDUT_RESUME_ACK] = MSG_DEF("RESUME-ACK", resume_ack_ies, DL|SIG),
+ [BSSGP_PDUT_RESUME_NACK] = MSG_DEF("RESUME-NACK", resume_nack_ies, DL|SIG),
+ [BSSGP_PDUT_DUMMY_PAGING_PS] = MSG_DEF("DUMMY-PAGING-PS", d_pag_ps_ies, DL|SIG|PTP),
+ [BSSGP_PDUT_DUMMY_PAGING_PS_RESP] = MSG_DEF("DUMMY-PAGING-PS-RESP", d_pag_ps_resp_ies, UL|SIG|PTP),
+ [BSSGP_PDUT_PAGING_PS_REJECT] = MSG_DEF("PAGING-PS-REJ", d_pag_ps_rej_ies, UL|SIG|PTP),
+ [BSSGP_PDUT_MS_REGISTR_ENQ] = MSG_DEF("MS-REGISRATION-ENQ", ms_reg_enq_ies, UL|SIG),
+ [BSSGP_PDUT_MS_REGISTR_ENQ_RESP] = MSG_DEF("MS-REGISRATION-ENQ-RESP", ms_reg_enq_res_ies, DL|SIG),
+ [BSSGP_PDUT_FLUSH_LL] = MSG_DEF("FLUSH-LL", flush_ll_ies, DL|SIG),
+ [BSSGP_PDUT_FLUSH_LL_ACK] = MSG_DEF("FLUSH-LL-ACK", flush_ll_ack_ies, UL|SIG),
+ [BSSGP_PDUT_LLC_DISCARD] = MSG_DEF("LLC-DISCARDED", llc_disc_ies, UL|SIG),
+ [BSSGP_PDUT_FLOW_CONTROL_BVC] = MSG_DEF("FC-BVC", fc_bvc_ies, UL|PTP),
+ [BSSGP_PDUT_FLOW_CONTROL_BVC_ACK] = MSG_DEF("FC-BVC-ACK", fc_bvc_ack_ies, DL|PTP),
+ [BSSGP_PDUT_FLOW_CONTROL_MS] = MSG_DEF("FC-MS", fc_ms_ies, UL|PTP),
+ [BSSGP_PDUT_FLOW_CONTROL_MS_ACK] = MSG_DEF("FC-MS-ACK", fc_ms_ack_ies, DL|PTP),
+ [BSSGP_PDUT_BVC_BLOCK] = MSG_DEF("BVC-BLOCK", block_ies, UL|SIG),
+ [BSSGP_PDUT_BVC_BLOCK_ACK] = MSG_DEF("BVC-BLOCK-ACK", block_ack_ies, DL|SIG),
+ [BSSGP_PDUT_BVC_UNBLOCK] = MSG_DEF("BVC-UNBLOCK", unblock_ies, UL|SIG),
+ [BSSGP_PDUT_BVC_UNBLOCK_ACK] = MSG_DEF("BVC-UNBLOCK-ACK", unblock_ack_ies, DL|SIG),
+ [BSSGP_PDUT_BVC_RESET] = MSG_DEF("BVC-RESET", reset_ies, UL|DL|SIG|PTP),
+ [BSSGP_PDUT_BVC_RESET_ACK] = MSG_DEF("BVC-RESET-ACK", reset_ack_ies, UL|DL|SIG|PTP),
+ [BSSGP_PDUT_STATUS] = MSG_DEF("STATUS", status_ies, UL|DL|PTP|SIG|PTM),
+ [BSSGP_PDUT_SGSN_INVOKE_TRACE] = MSG_DEF("SGSN-INVOKE-TRACE", inv_trc_ies, DL|SIG),
+ [BSSGP_PDUT_DOWNLOAD_BSS_PFC] = MSG_DEF("DOWNLOAD-BSS-PFC", dl_bss_pfc_ies, UL|PTP),
+ [BSSGP_PDUT_CREATE_BSS_PFC] = MSG_DEF("CREATE-BSS-PFC", crt_bss_pfc_ies, DL|PTP),
+ [BSSGP_PDUT_CREATE_BSS_PFC_ACK] = MSG_DEF("CREATE-BSS-PFC-ACK", crt_bss_pfc_ack_ies, UL|PTP),
+ [BSSGP_PDUT_CREATE_BSS_PFC_NACK] = MSG_DEF("CREATE-BSS-PFC-NACK", crt_bss_pfc_nack_ies, UL|PTP),
+ [BSSGP_PDUT_MODIFY_BSS_PFC] = MSG_DEF("MODIFY-BSS-PFC", mod_bss_pfc_ies, DL|PTP),
+ [BSSGP_PDUT_MODIFY_BSS_PFC_ACK] = MSG_DEF("MODIFY-BSS-PFC-ACK", mod_bss_pfc_ack_ies, UL|PTP),
+ [BSSGP_PDUT_DELETE_BSS_PFC] = MSG_DEF("DELETE-BSS-PFC", del_bss_pfc_ies, DL|PTP),
+ [BSSGP_PDUT_DELETE_BSS_PFC_ACK] = MSG_DEF("DELETE-BSS-PFC-ACK", del_bss_pfc_ack_ies, UL|PTP),
+ [BSSGP_PDUT_FLOW_CONTROL_PFC] = MSG_DEF("FC-PFC", fc_pfc_ies, UL|PTP),
+ [BSSGP_PDUT_FLOW_CONTROL_PFC_ACK] = MSG_DEF("FC-PFC-ACK", fc_pfc_ack_ies, DL|PTP),
+ [BSSGP_PDUT_DELETE_BSS_PFC_REQ] = MSG_DEF("DELETE-BSS-PFC-REQ", del_bss_pfc_req_ies, UL|PTP),
+ [BSSGP_PDUT_PS_HO_REQUIRED] = MSG_DEF("PS-HO-REQUIRED", ps_ho_required_ies, UL|PTP),
+ [BSSGP_PDUT_PS_HO_REQUIRED_ACK] = MSG_DEF("PS-HO-REQUIRED-ACK", ps_ho_required_ack_ies, DL|PTP),
+ [BSSGP_PDUT_PS_HO_REQUIRED_NACK] = MSG_DEF("PS-HO-REQUIRED-NACK", ps_ho_required_nack_ies, DL|PTP),
+ [BSSGP_PDUT_PS_HO_REQUEST] = MSG_DEF("PS-HO-REQUEST", ps_ho_request_ies, DL|PTP),
+ [BSSGP_PDUT_PS_HO_REQUEST_ACK] = MSG_DEF("PS-HO-REQUEST-ACK", ps_ho_request_ack_ies, UL|PTP),
+ [BSSGP_PDUT_PS_HO_REQUEST_NACK] = MSG_DEF("PS-HO-REQUEST-NACK", ps_ho_request_nack_ies, UL|PTP),
+ [BSSGP_PDUT_PS_HO_COMPLETE] = MSG_DEF("PS-HO-COMPLETE", ps_ho_compl_ies, UL|PTP),
+ [BSSGP_PDUT_PS_HO_CANCEL] = MSG_DEF("PS-HO-CANCEL", ps_ho_cancel_ies, UL|PTP),
+ [BSSGP_PDUT_PS_HO_COMPLETE_ACK] = MSG_DEF("PS-HO-COMPLETE-ACK", ps_ho_compl_ack_ies, DL|PTP),
+ [BSSGP_PDUT_OVERLOAD] = MSG_DEF("OVERLOAD", overload_ies, DL|SIG),
+ /* TODO: Messages on LCS SAP */
+ /* Messages on RIM SAP */
+ [BSSGP_PDUT_RAN_INFO] = MSG_DEF("RAN-INFORMATION", rinfo_ies, DL|UL|SIG),
+ [BSSGP_PDUT_RAN_INFO_REQ] = MSG_DEF("RAN-INFORMATION-REQUEST", rinfo_req_ies, DL|UL|SIG),
+ [BSSGP_PDUT_RAN_INFO_ACK] = MSG_DEF("RAN-INFORMATION-ACK", rinfo_ack_ies, DL|UL|SIG),
+ [BSSGP_PDUT_RAN_INFO_ERROR] = MSG_DEF("RAN-INFORMATION-ERROR", rinfo_err_ies, DL|UL|SIG),
+ [BSSGP_PDUT_RAN_INFO_APP_ERROR] = MSG_DEF("RAN-INFORMATION-APP-ERROR", rinfo_aerr_ies, DL|UL|SIG),
+ /* TODO: Messages on MBMS SAP */
+ },
+ .ie_def = {
+ [BSSGP_IE_ALIGNMENT] = { 0, "Alignment Octets" },
+ [BSSGP_IE_BMAX_DEFAULT_MS] = { 2, "Bmax default MS" },
+ [BSSGP_IE_BSS_AREA_ID] = { 1, "BSS Area Indication" },
+ [BSSGP_IE_BUCKET_LEAK_RATE] = { 2, "Bucket Leak Rate (R)" },
+ [BSSGP_IE_BVC_BUCKET_SIZE] = { 2, "BVC Bucket Size" },
+ [BSSGP_IE_BVCI] = { 2, "BVCI" },
+ [BSSGP_IE_BVC_MEASUREMENT] = {2, "BVC Measurement" },
+ [BSSGP_IE_CAUSE] = { 1, "Cause" },
+ [BSSGP_IE_CELL_ID] = { 8, "Cell Identifier" },
+ [BSSGP_IE_CHAN_NEEDED] = { 1, "Channel Needed" },
+ [BSSGP_IE_DRX_PARAMS] = { 2, "DRX Parameters" },
+ [BSSGP_IE_EMLPP_PRIO] = { 3, "eMLPP Priority" },
+ [BSSGP_IE_FLUSH_ACTION] = { 1, "Flush Action" },
+ [BSSGP_IE_IMSI] = { 1, "Mobile Identity" },
+ [BSSGP_IE_LLC_PDU] = { 0, "LLC-PDU" },
+ [BSSGP_IE_LLC_FRAMES_DISCARDED] = { 1, "LLC Frames Discarded" },
+ [BSSGP_IE_LOCATION_AREA] = { 5, "Location Area" },
+ [BSSGP_IE_LSA_ID_LIST] = { 3, "LSA Identifier List" },
+ [BSSGP_IE_LSA_INFORMATION] = { 5, "LSA Information" },
+ [BSSGP_IE_MOBILE_ID] = { 1, "Mobile Identity" },
+ [BSSGP_IE_MS_BUCKET_SIZE] = { 2, "MS Bucket Size" },
+ [BSSGP_IE_MS_RADIO_ACCESS_CAP] = { 1, "MS Radio Access Capability" },
+ [BSSGP_IE_OMC_ID] = { 1, "OMC Id" },
+ [BSSGP_IE_PDU_IN_ERROR] = { 0, "PDU In Error" },
+ [BSSGP_IE_PDU_LIFETIME] = { 2, "PDU Lifetime" },
+ [BSSGP_IE_PRIORITY] = { 1, "Priority" },
+ [BSSGP_IE_QOS_PROFILE] = { 3, "QoS Profile" },
+ [BSSGP_IE_RADIO_CAUSE] = { 1, "Radio Cause" },
+ [BSSGP_IE_RA_CAP_UPD_CAUSE] = { 1, "RA-Cap-UPD-Cause" },
+ [BSSGP_IE_ROUTEING_AREA] = { 6, "Routeing Area" },
+ [BSSGP_IE_R_DEFAULT_MS] = { 2, "R_default_MS" },
+ [BSSGP_IE_SUSPEND_REF_NR] = { 1, "Suspend Reference Number" },
+ [BSSGP_IE_TAG] = { 1, "Tag" },
+ [BSSGP_IE_TLLI] = { 4, "TLLI" },
+ [BSSGP_IE_TMSI] = { 4, "TMSI" },
+ [BSSGP_IE_TRACE_REFERENC] = { 2, "Trace Reference" },
+ [BSSGP_IE_TRACE_TYPE] = { 1, "Trace Type" },
+ [BSSGP_IE_TRANSACTION_ID] = { 2, "Transaction Id" },
+ [BSSGP_IE_TRIGGER_ID] = { 1, "Trigger Id" },
+ [BSSGP_IE_NUM_OCT_AFF] = { 3, "Number of octets affected" },
+ [BSSGP_IE_PACKET_FLOW_ID] = { 1, "Packet Flow Identifier (PFI)" },
+ [BSSGP_IE_AGG_BSS_QOS_PROFILE] = { 14, "Aggregate BSS QoS Profile" },
+ [BSSGP_IE_PACKET_FLOW_TIMER] = { 1, "GPRS Timer" },
+ [BSSGP_IE_FEATURE_BITMAP] = { 1, "Feature Bitmap" },
+ [BSSGP_IE_BUCKET_FULL_RATIO] = { 1, "Bucket Full Ratio" },
+ [BSSGP_IE_SERVICE_UTRAN_CCO] = { 1, "Service UTRAN COO" },
+ [BSSGP_IE_NSEI] = { 2, "NSEI" },
+ [BSSGP_IE_RRLP_APDU] = { 1, "RLLP APDU" },
+ [BSSGP_IE_LCS_QOS] = { 4, "LCS QoS" },
+ [BSSGP_IE_LCS_CLIENT_TYPE] = { 1, "LCS Client Type" },
+ [BSSGP_IE_REQUESTED_GPS_AST_DATA] = { 4, "Requested GPS Assistance Data" },
+ [BSSGP_IE_LOCATION_TYPE] = { 2, "Location Type" },
+ [BSSGP_IE_LOCATION_ESTIMATE] = { 1, "Location Estimate" },
+ [BSSGP_IE_POSITIONING_DATA] = { 1, "Positioning Data" },
+ [BSSGP_IE_DECIPHERING_KEYS] = { 15, "Deciphering Keys" },
+ [BSSGP_IE_LCS_PRIORITY] = { 1, "LCS Priority" },
+ [BSSGP_IE_LCS_CAUSE] = { 1, "LCS Cause" },
+ [BSSGP_IE_LCS_CAPABILITY] = { 1, "LCS Capability" },
+ [BSSGP_IE_RRLP_FLAGS] = { 1, "RRLP Flags" },
+ [BSSGP_IE_RIM_APP_IDENTITY] = { 1, "RIM Application Identity" },
+ [BSSGP_IE_RIM_SEQ_NR] = { 4, "RIM Sequence Number" },
+ [BSSGP_IE_RIM_REQ_APP_CONTAINER] = { 12, "RIM-REQUEST RIM Container" },
+ [BSSGP_IE_RAN_INFO_APP_CONTAINER] = { 12, "RAN-INFORMATION RIM Container" },
+ [BSSGP_IE_RI_ACK_RIM_CONTAINER] = { 9, "RAN-INFORMATION-ACK RIM Container" },
+ [BSSGP_IE_RI_ERROR_RIM_COINTAINER] = { 9, "RAN-INFOIRMATION-ERROR RIM Container" },
+ [BSSGP_IE_RI_APP_ERROR_RIM_CONT] = { 14, "RAN-INFORMATION-APP-ERROR RIM Container" },
+ [BSSGP_IE_RIM_PDU_INDICATIONS] = { 1, "RIM PDU Indications" },
+ [BSSGP_IE_RIM_PROTOCOL_VERSION] = { 1, "RIM Protocol Version Number" },
+ [BSSGP_IE_PFC_FLOW_CTRL_PARAMS] = { 7, "PFC FLow Control Parameters" },
+ [BSSGP_IE_GLOBAL_CN_ID] = { 5, "Global CN-Id" },
+ [BSSGP_IE_RIM_ROUTING_INFO] = { 1, "RIM Routing Information" },
+ [BSSGP_IE_MBMS_SESSION_ID] = { 0, "MBMS Session Identity" },
+ [BSSGP_IE_MBMS_SESSION_DURATION] = { 0, "MBMS Session Duration" },
+ [BSSGP_IE_MBMS_SA_ID_LIST] = { 3, "MBMS Service Area Identity List" },
+ [BSSGP_IE_MBMS_RESPONSE] = { 1, "MBMS Response" },
+ [BSSGP_IE_MBMS_RA_LIST] = { 9, "MBMS Routing Area List" },
+ [BSSGP_IE_MBMS_SESSION_INFO] = { 1, "MBMS Session Information" },
+ [BSSGP_IE_TMGI] = { 6, "TMGI" },
+ [BSSGP_IE_MBMS_STOP_CAUSE] = { 1, "MBM Stop Cause" },
+ [BSSGP_IE_SBSS_TO_TBSS_TR_CONT] = { 7, "Source BSS to Target BSS Transparent Container" },
+ [BSSGP_IE_TBSS_TO_SBSS_TR_CONT] = { 0, "Target BSS to Source BSS Transparent Container" },
+ [BSSGP_IE_NAS_CONT_FOR_PS_HO] = { 0, "NAS container for PS Handover" },
+ [BSSGP_IE_PFC_TO_BE_SETUP_LIST] = { 9, "PFCs to be set-up list" },
+ [BSSGP_IE_LIST_OF_SETUP_PFC] = { 1, "List of set-up PFCs" },
+ [BSSGP_IE_EXT_FEATURE_BITMAP] = { 1, "Extended Feature Bitmap" },
+ [BSSGP_IE_SRC_TO_TGT_TR_CONT] = { 0, "Source to Target Transparent Container" },
+ [BSSGP_IE_TGT_TO_SRC_TR_CONT] = { 0, "Target to Source Transparent Container" },
+ [BSSGP_IE_NC_ID] = { 8, "RNC Identifier" },
+ [BSSGP_IE_PAGE_MODE] = { 1, "Page Mode" },
+ [BSSGP_IE_CONTAINER_ID] = { 1, "Container ID" },
+ [BSSGP_IE_GLOBAL_TFI] = { 1, "Global TFI" },
+ [BSSGP_IE_IMEI] = { 1, "IMEI" },
+ [BSSGP_IE_TIME_TO_MBMS_DATA_XFR] = { 1, "Time to MBMS Data Transfer" },
+ [BSSGP_IE_MBMS_SESSION_REP_NR] = { 1, "MBMS Session Repetition Number" },
+ [BSSGP_IE_INTER_RAT_HO_INFO] = { 0, "Inter RAT Handover Info" },
+ [BSSGP_IE_PS_HO_COMMAND] = { 0, "PS Handover Command" },
+ [BSSGP_IE_PS_HO_INDICATIONS] = { 1, "PS Handover Indications" },
+ [BSSGP_IE_SI_PSI_CONTAINER] = { 1, "SI/PSI Container" },
+ [BSSGP_IE_ACTIVE_PFC_LIST] = { 2, "Active PFCs List" },
+ [BSSGP_IE_VELOCITY_DATA] = { 0, "Velocity Data" },
+ [BSSGP_IE_DTM_HO_COMMAND] = { 0, "DTM Handover Command" },
+ [BSSGP_IE_CS_INDICATION] = { 1, "CS Indication" },
+ [BSSGP_IE_RQD_GANNS_AST_DATA] = { 0, "Requested GANSS Assistance Data" },
+ [BSSGP_IE_GANSS_LOCATION_TYPE] = { 1, "GANSS Location Type" },
+ [BSSGP_IE_GANSS_POSITIONING_DATA] = { 0, "GANSS Positioning Data" },
+ [BSSGP_IE_FLOW_CTRL_GRANULARITY] = { 1, "Flow Control Granularity" },
+ [BSSGP_IE_ENB_ID] = { 6, "eNB Identifier" },
+ [BSSGP_IE_EUTRAN_IRAT_HO_INFO] = { 0, "E-UTRAN Inter RAT Handover Info" },
+ [BSSGP_IE_SUB_PID4RAT_FREQ_PRIO] = { 1, "Subscriber Profile ID for RAT/Frequency priority" },
+ [BSSGP_IE_REQ4IRAT_HO_INFO] = { 1, "Request for Inter-RAT Handover Info" },
+ [BSSGP_IE_RELIABLE_IRAT_HO_INFO] = { 1, "Reliable Inter-RAT Handover Info" },
+ [BSSGP_IE_SON_TRANSFER_APP_ID] = { 0, "SON Transfer Application Identity" },
+ [BSSGP_IE_CSG_ID] = { 5, "CSG Identifier" },
+ [BSSGP_IE_TAC] = { 3, "Tracking Area Code" },
+ [BSSGP_IE_REDIRECT_ATTEMPT_FLAG] = { 1, "Redirect Attempt Flag" },
+ [BSSGP_IE_REDIRECTION_INDICATION] = { 1, "Redirection Indication" },
+ [BSSGP_IE_REDIRECTION_COMPLETED] = { 1, "Redirection Completed" },
+ [BSSGP_IE_UNCONF_SEND_STATE_VAR] = { 2, "Unconfirmed send state variable" },
+ [BSSGP_IE_IRAT_MEASUREMENT_CONF] = { 10, "IRAT Measurement Configuration" },
+ [BSSGP_IE_SCI] = { 1, "SCI" },
+ [BSSGP_IE_GGSN_PGW_LOCATION] = { 1, "GGSN/P-GW Location" },
+ [BSSGP_IE_SELECTED_PLMN_ID] = { 3, "Selected PLMN ID" },
+ [BSSGP_IE_PRIO_CLASS_IND] = { 1, "Priority Class Indication" },
+ [BSSGP_IE_SOURCE_CELL_ID] = { 6, "Source Cell ID" },
+ [BSSGP_IE_IRAT_MEAS_CFG_E_EARFCN] = { 10, "IRAT Measurement Configuration (extended E-ARFCNs)" },
+ [BSSGP_IE_EDRX_PARAMETERS] = { 1, "eDRX Parameters" },
+ [BSSGP_IE_T_UNTIL_NEXT_PAGING] = { 2, "Time Until Next Paging Occasion" },
+ [BSSGP_IE_COVERAGE_CLASS] = { 1, "Coverage Class" },
+ [BSSGP_IE_PAGING_ATTEMPT_INFO] = { 1, "Paging Attempt Information" },
+ [BSSGP_IE_EXCEPTION_REPORT_FLAG] = { 1, "Exception Report Flag" },
+ [BSSGP_IE_OLD_RA_ID] = { 6, "Old Routing Area Identification" },
+ [BSSGP_IE_ATTACH_IND] = { 1, "Attach Indicator" },
+ [BSSGP_IE_PLMN_ID] = { 3, "PLMN Identity" },
+ [BSSGP_IE_MME_QUERY] = { 1, "MME Query" },
+ [BSSGP_IE_SGSN_GROUP_ID] = { 3, "SGSN Group Identity" },
+ [BSSGP_IE_ADDITIONAL_PTMSI] = { 4, "Additional P-TMSI" },
+ [BSSGP_IE_UE_USAGE_TYPE] = { 1, "UE Usage Type" },
+ [BSSGP_IE_MLAT_TIMER] = { 1, "Multilateration Timer" },
+ [BSSGP_IE_MLAT_TA] = { 2, "Multilateration Timing Advance" },
+ [BSSGP_IE_MS_SYNC_ACCURACY] = { 1, "MS Sync Accuracy" },
+ [BSSGP_IE_BTS_RX_ACCURACY_LVL] = { 1, "BTS Reception Accuracy Level" },
+ [BSSGP_IE_TA_REQ] = { 1, "Timing Advance Request (TAR)" },
+ },
+};
+
+#undef DL
+#undef UL
+#undef SIG
+#undef PTP
+#undef PTM
+
+
const char *bssgp_cause_str(enum gprs_bssgp_cause cause)
{
return get_value_string(bssgp_cause_strings, cause);
@@ -225,17 +562,17 @@ int bssgp_tx_status(uint8_t cause, uint16_t *bvci, struct msgb *orig_msg)
cause is either "BVCI blocked" or "BVCI unknown" */
if (cause == BSSGP_CAUSE_UNKNOWN_BVCI || cause == BSSGP_CAUSE_BVCI_BLOCKED) {
if (bvci == NULL)
- LOGP(DBSSGP, LOGL_ERROR, "BSSGP Tx STATUS, cause=%s: "
+ LOGP(DLBSSGP, LOGL_ERROR, "BSSGP Tx STATUS, cause=%s: "
"missing conditional BVCI\n",
bssgp_cause_str(cause));
} else {
if (bvci != NULL)
- LOGP(DBSSGP, LOGL_ERROR, "BSSGP Tx STATUS, cause=%s: "
+ LOGP(DLBSSGP, LOGL_ERROR, "BSSGP Tx STATUS, cause=%s: "
"unexpected conditional BVCI\n",
bssgp_cause_str(cause));
}
- LOGP(DBSSGP, LOGL_NOTICE, "BSSGP BVCI=%u Tx STATUS, cause=%s\n",
+ LOGP(DLBSSGP, LOGL_NOTICE, "BSSGP BVCI=%u Tx STATUS, cause=%s\n",
bvci ? *bvci : 0, bssgp_cause_str(cause));
msgb_nsei(msg) = msgb_nsei(orig_msg);
msgb_bvci(msg) = 0;
diff --git a/src/gb/gprs_bssgp_vty.c b/src/gb/gprs_bssgp_vty.c
index 3dee18eb..d04641d9 100644
--- a/src/gb/gprs_bssgp_vty.c
+++ b/src/gb/gprs_bssgp_vty.c
@@ -44,8 +44,6 @@
#include <osmocom/vty/telnet_interface.h>
#include <osmocom/vty/misc.h>
-#include "common_vty.h"
-
static void log_set_bvc_filter(struct log_target *target,
struct bssgp_bvc_ctx *bctx)
{
diff --git a/src/gb/gprs_ns.c b/src/gb/gprs_ns.c
index a6ef6d47..304fe7c1 100644
--- a/src/gb/gprs_ns.c
+++ b/src/gb/gprs_ns.c
@@ -29,7 +29,7 @@
* @{
*
* GPRS Networks Service (NS) messages on the Gb interface
- * 3GPP TS 08.16 version 8.0.1 Release 1999 / ETSI TS 101 299 V8.0.1 (2002-05)
+ * 3GPP TS 08.16 version 8.0.1 Release 1999 / ETSI TS 101 299 V8.0.1 (2002-05)
*
* Some introduction into NS: NS is used typically on top of frame relay,
* but in the ip.access world it is encapsulated in UDP packets. It serves
@@ -352,8 +352,7 @@ struct gprs_nsvc *gprs_nsvc_create2(struct gprs_ns_inst *nsi, uint16_t nsvci,
*/
void gprs_nsvc_delete(struct gprs_nsvc *nsvc)
{
- if (osmo_timer_pending(&nsvc->timer))
- osmo_timer_del(&nsvc->timer);
+ osmo_timer_del(&nsvc->timer);
llist_del(&nsvc->list);
rate_ctr_group_free(nsvc->ctrg);
osmo_stat_item_group_free(nsvc->statg);
@@ -486,8 +485,8 @@ static int gprs_ns_tx(struct gprs_nsvc *nsvc, struct msgb *msg)
}
/* Increment number of Uplink bytes */
- rate_ctr_inc(&nsvc->ctrg->ctr[NS_CTR_PKTS_OUT]);
- rate_ctr_add(&nsvc->ctrg->ctr[NS_CTR_BYTES_OUT], msgb_l2len(msg));
+ rate_ctr_inc(rate_ctr_group_get_ctr(nsvc->ctrg, NS_CTR_PKTS_OUT));
+ rate_ctr_add(rate_ctr_group_get_ctr(nsvc->ctrg, NS_CTR_BYTES_OUT), msgb_l2len(msg));
switch (nsvc->ll) {
case GPRS_NS_LL_UDP:
@@ -533,7 +532,7 @@ static int gprs_ns_tx_simple(struct gprs_nsvc *nsvc, uint8_t pdu_type)
/*! Transmit a NS-RESET on a given NSVC
* \param[in] nsvc NS-VC used for transmission
- * \paam[in] cause Numeric NS cause value
+ * \param[in] cause Numeric NS cause value
*/
int gprs_ns_tx_reset(struct gprs_nsvc *nsvc, uint8_t cause)
{
@@ -643,7 +642,7 @@ int gprs_ns_tx_block(struct gprs_nsvc *nsvc, uint8_t cause)
/* be conservative and mark it as blocked even now! */
ns_mark_blocked(nsvc);
- rate_ctr_inc(&nsvc->ctrg->ctr[NS_CTR_BLOCKED]);
+ rate_ctr_inc(rate_ctr_group_get_ctr(nsvc->ctrg, NS_CTR_BLOCKED));
msg->l2h = msgb_put(msg, sizeof(*nsh));
nsh = (struct gprs_ns_hdr *) msg->l2h;
@@ -750,8 +749,7 @@ static void nsvc_start_timer(struct gprs_nsvc *nsvc, enum nsvc_timer_mode mode)
nsvc->nsei, get_value_string(timer_mode_strs, mode),
seconds);
- if (osmo_timer_pending(&nsvc->timer))
- osmo_timer_del(&nsvc->timer);
+ osmo_timer_del(&nsvc->timer);
osmo_gettimeofday(&nsvc->timer_started, NULL);
nsvc->timer_mode = mode;
@@ -781,15 +779,15 @@ static void gprs_ns_timer_cb(void *data)
switch (nsvc->timer_mode) {
case NSVC_TIMER_TNS_ALIVE:
/* Tns-alive case: we expired without response ! */
- rate_ctr_inc(&nsvc->ctrg->ctr[NS_CTR_LOST_ALIVE]);
+ rate_ctr_inc(rate_ctr_group_get_ctr(nsvc->ctrg, NS_CTR_LOST_ALIVE));
nsvc->alive_retries++;
if (nsvc->alive_retries >
nsvc->nsi->timeout[NS_TOUT_TNS_ALIVE_RETRIES]) {
/* mark as dead (and blocked unless IP-SNS) */
- rate_ctr_inc(&nsvc->ctrg->ctr[NS_CTR_DEAD]);
+ rate_ctr_inc(rate_ctr_group_get_ctr(nsvc->ctrg, NS_CTR_DEAD));
if (!nsvc->nsi->bss_sns_fi && nsvc->nsi->nsip.use_reset_block_unblock) {
ns_set_state(nsvc, NSE_S_BLOCKED);
- rate_ctr_inc(&nsvc->ctrg->ctr[NS_CTR_BLOCKED]);
+ rate_ctr_inc(rate_ctr_group_get_ctr(nsvc->ctrg, NS_CTR_BLOCKED));
} else
ns_set_state(nsvc, 0);
LOGP(DNS, LOGL_NOTICE,
@@ -816,7 +814,7 @@ static void gprs_ns_timer_cb(void *data)
nsvc_start_timer(nsvc, NSVC_TIMER_TNS_ALIVE);
break;
case NSVC_TIMER_TNS_RESET:
- rate_ctr_inc(&nsvc->ctrg->ctr[NS_CTR_LOST_RESET]);
+ rate_ctr_inc(rate_ctr_group_get_ctr(nsvc->ctrg, NS_CTR_LOST_RESET));
if (!(nsvc->state & NSE_S_RESET))
LOGP(DNS, LOGL_NOTICE,
"NSEI=%u Reset timed out but RESET flag is not set\n",
@@ -1076,7 +1074,7 @@ int gprs_ns_tx_sns_size_ack(struct gprs_nsvc *nsvc, uint8_t *cause)
* if the NS-VC is ALIVE and not BLOCKED. After that, it adds a NS
* header for the NS-UNITDATA message type and sends it off.
*
- * Section 9.2.10: transmit side / NS-UNITDATA-REQUEST primitive
+ * Section 9.2.10: transmit side / NS-UNITDATA-REQUEST primitive
*/
int gprs_ns_sendmsg(struct gprs_ns_inst *nsi, struct msgb *msg)
{
@@ -1251,7 +1249,7 @@ static int gprs_ns_rx_reset(struct gprs_nsvc **nsvc, struct msgb *msg)
ns_osmo_signal_dispatch_mismatch(*nsvc, msg,
NS_PDUT_RESET,
NS_IE_VCI);
- rate_ctr_inc(&(*nsvc)->ctrg->ctr[NS_CTR_INV_VCI]);
+ rate_ctr_inc(rate_ctr_group_get_ctr((*nsvc)->ctrg, NS_CTR_INV_VCI));
gprs_ns_tx_reset_ack(*nsvc);
return 0;
}
@@ -1277,14 +1275,14 @@ static int gprs_ns_rx_reset(struct gprs_nsvc **nsvc, struct msgb *msg)
ns_osmo_signal_dispatch_mismatch(*nsvc, msg,
NS_PDUT_RESET,
NS_IE_NSEI);
- rate_ctr_inc(&(*nsvc)->ctrg->ctr[NS_CTR_INV_NSEI]);
+ rate_ctr_inc(rate_ctr_group_get_ctr((*nsvc)->ctrg, NS_CTR_INV_NSEI));
rc = gprs_ns_tx_reset_ack(*nsvc);
CHECK_TX_RC(rc, *nsvc);
return 0;
}
/* NSEI has changed */
- rate_ctr_inc(&(*nsvc)->ctrg->ctr[NS_CTR_NSEI_CHG]);
+ rate_ctr_inc(rate_ctr_group_get_ctr((*nsvc)->ctrg, NS_CTR_NSEI_CHG));
(*nsvc)->nsei = nsei;
}
@@ -1292,7 +1290,7 @@ static int gprs_ns_rx_reset(struct gprs_nsvc **nsvc, struct msgb *msg)
ns_set_state(*nsvc, NSE_S_BLOCKED | NSE_S_ALIVE);
if (orig_nsvc) {
- rate_ctr_inc(&(*nsvc)->ctrg->ctr[NS_CTR_REPLACED]);
+ rate_ctr_inc(rate_ctr_group_get_ctr((*nsvc)->ctrg, NS_CTR_REPLACED));
ns_osmo_signal_dispatch_replaced(*nsvc, orig_nsvc);
/* Update the ll info fields */
@@ -1390,7 +1388,7 @@ static int gprs_ns_rx_reset_ack(struct gprs_nsvc **nsvc, struct msgb *msg)
ns_osmo_signal_dispatch_mismatch(*nsvc, msg,
NS_PDUT_RESET_ACK,
NS_IE_VCI);
- rate_ctr_inc(&(*nsvc)->ctrg->ctr[NS_CTR_INV_VCI]);
+ rate_ctr_inc(rate_ctr_group_get_ctr((*nsvc)->ctrg, NS_CTR_INV_VCI));
LOGP(DNS, LOGL_ERROR,
"NS RESET ACK Unknown NS-VCI %d (%s NSEI=%d) "
"from %s\n",
@@ -1401,7 +1399,7 @@ static int gprs_ns_rx_reset_ack(struct gprs_nsvc **nsvc, struct msgb *msg)
}
/* Notify others */
- rate_ctr_inc(&(*nsvc)->ctrg->ctr[NS_CTR_REPLACED]);
+ rate_ctr_inc(rate_ctr_group_get_ctr((*nsvc)->ctrg, NS_CTR_REPLACED));
ns_osmo_signal_dispatch_replaced(*nsvc, orig_nsvc);
/* Update the ll info fields */
@@ -1415,7 +1413,7 @@ static int gprs_ns_rx_reset_ack(struct gprs_nsvc **nsvc, struct msgb *msg)
ns_osmo_signal_dispatch_mismatch(*nsvc, msg,
NS_PDUT_RESET_ACK,
NS_IE_NSEI);
- rate_ctr_inc(&(*nsvc)->ctrg->ctr[NS_CTR_INV_NSEI]);
+ rate_ctr_inc(rate_ctr_group_get_ctr((*nsvc)->ctrg, NS_CTR_INV_NSEI));
LOGP(DNS, LOGL_ERROR,
"NS RESET ACK Unknown NSEI %d (NS-VCI=%u) from %s\n",
nsei, nsvci, gprs_ns_ll_str(*nsvc));
@@ -1423,14 +1421,14 @@ static int gprs_ns_rx_reset_ack(struct gprs_nsvc **nsvc, struct msgb *msg)
}
/* NSEI has changed */
- rate_ctr_inc(&(*nsvc)->ctrg->ctr[NS_CTR_NSEI_CHG]);
+ rate_ctr_inc(rate_ctr_group_get_ctr((*nsvc)->ctrg, NS_CTR_NSEI_CHG));
(*nsvc)->nsei = nsei;
}
/* Mark NS-VC as blocked and alive */
ns_set_state(*nsvc, NSE_S_BLOCKED | NSE_S_ALIVE);
ns_set_remote_state(*nsvc, NSE_S_BLOCKED | NSE_S_ALIVE);
- rate_ctr_inc(&(*nsvc)->ctrg->ctr[NS_CTR_BLOCKED]);
+ rate_ctr_inc(rate_ctr_group_get_ctr((*nsvc)->ctrg, NS_CTR_BLOCKED));
if ((*nsvc)->persistent || (*nsvc)->remote_end_is_sgsn) {
/* stop RESET timer */
osmo_timer_del(&(*nsvc)->timer);
@@ -1471,7 +1469,7 @@ static int gprs_ns_rx_block(struct gprs_nsvc *nsvc, struct msgb *msg)
//nsvci = (uint16_t *) TLVP_VAL(&tp, NS_IE_VCI);
ns_osmo_signal_dispatch(nsvc, S_NS_BLOCK, *cause);
- rate_ctr_inc(&nsvc->ctrg->ctr[NS_CTR_BLOCKED]);
+ rate_ctr_inc(rate_ctr_group_get_ctr(nsvc->ctrg, NS_CTR_BLOCKED));
return gprs_ns_tx_block_ack(nsvc);
}
@@ -1705,7 +1703,7 @@ int gprs_ns_vc_create(struct gprs_ns_inst *nsi, struct msgb *msg,
existing_nsvc->nsei = nsei;
/* Do statistics */
- rate_ctr_inc(&existing_nsvc->ctrg->ctr[NS_CTR_NSEI_CHG]);
+ rate_ctr_inc(rate_ctr_group_get_ctr(existing_nsvc->ctrg, NS_CTR_NSEI_CHG));
}
*new_nsvc = existing_nsvc;
@@ -1734,8 +1732,8 @@ int gprs_ns_process_msg(struct gprs_ns_inst *nsi, struct msgb *msg,
log_set_context(LOG_CTX_GB_NSVC, *nsvc);
/* Increment number of Incoming bytes */
- rate_ctr_inc(&(*nsvc)->ctrg->ctr[NS_CTR_PKTS_IN]);
- rate_ctr_add(&(*nsvc)->ctrg->ctr[NS_CTR_BYTES_IN], msgb_l2len(msg));
+ rate_ctr_inc(rate_ctr_group_get_ctr((*nsvc)->ctrg, NS_CTR_PKTS_IN));
+ rate_ctr_add(rate_ctr_group_get_ctr((*nsvc)->ctrg, NS_CTR_BYTES_IN), msgb_l2len(msg));
if (nsvc_is_not_used(*nsvc) && !ns_is_sns(nsh->pdu_type) && nsh->pdu_type != NS_PDUT_STATUS) {
LOGP(DNS, LOGL_NOTICE, "NSEI=%u Rx %s on unused/pre-configured endpoint, discarding\n",
@@ -1762,7 +1760,7 @@ int gprs_ns_process_msg(struct gprs_ns_inst *nsi, struct msgb *msg,
case NS_PDUT_ALIVE_ACK:
ns_mark_alive(*nsvc);
if ((*nsvc)->timer_mode == NSVC_TIMER_TNS_ALIVE)
- osmo_stat_item_set((*nsvc)->statg->items[NS_STAT_ALIVE_DELAY],
+ osmo_stat_item_set(osmo_stat_item_group_get_item((*nsvc)->statg, NS_STAT_ALIVE_DELAY),
nsvc_timer_elapsed_ms(*nsvc));
/* stop Tns-alive and start Tns-test */
nsvc_start_timer(*nsvc, NSVC_TIMER_TNS_TEST);
@@ -2068,7 +2066,8 @@ int gprs_ns_nsip_listen(struct gprs_ns_inst *nsi)
osmo_sock_init2_ofd(&nsi->nsip.fd, AF_INET, SOCK_DGRAM,
IPPROTO_UDP, inet_ntoa(in),
nsi->nsip.local_port, remote_str,
- nsi->nsip.remote_port, OSMO_SOCK_F_BIND | OSMO_SOCK_F_CONNECT);
+ nsi->nsip.remote_port, OSMO_SOCK_F_BIND | OSMO_SOCK_F_CONNECT |
+ OSMO_SOCK_F_BIND | OSMO_SOCK_F_DSCP(nsi->nsip.dscp));
LOGP(DNS, LOGL_NOTICE,
"Listening for nsip packets from %s:%u on %s:%u\n",
@@ -2076,7 +2075,8 @@ int gprs_ns_nsip_listen(struct gprs_ns_inst *nsi)
} else {
/* Accept UDP packets from any source IP/Port */
ret = osmo_sock_init_ofd(&nsi->nsip.fd, AF_INET, SOCK_DGRAM,
- IPPROTO_UDP, inet_ntoa(in), nsi->nsip.local_port, OSMO_SOCK_F_BIND);
+ IPPROTO_UDP, inet_ntoa(in), nsi->nsip.local_port,
+ OSMO_SOCK_F_BIND | OSMO_SOCK_F_DSCP(nsi->nsip.dscp));
LOGP(DNS, LOGL_NOTICE, "Listening for nsip packets on %s:%u\n", inet_ntoa(in), nsi->nsip.local_port);
}
@@ -2087,13 +2087,6 @@ int gprs_ns_nsip_listen(struct gprs_ns_inst *nsi)
return ret;
}
- ret = setsockopt(nsi->nsip.fd.fd, IPPROTO_IP, IP_TOS,
- &nsi->nsip.dscp, sizeof(nsi->nsip.dscp));
- if (ret < 0)
- LOGP(DNS, LOGL_ERROR,
- "Failed to set the DSCP to %d with ret(%d) errno(%d)\n",
- nsi->nsip.dscp, ret, errno);
-
LOGP(DNS, LOGL_NOTICE, "NS UDP socket at %s:%d\n", inet_ntoa(in), nsi->nsip.local_port);
return ret;
diff --git a/src/gb/gprs_ns2.c b/src/gb/gprs_ns2.c
index cf04924e..4e496c1f 100644
--- a/src/gb/gprs_ns2.c
+++ b/src/gb/gprs_ns2.c
@@ -57,9 +57,7 @@
* Those mappings are administratively configured.
*
* This implementation has the following limitations:
- * - Only one NS-VC for each NSE: No load-sharing function
* - NSVCI 65535 and 65534 are reserved for internal use
- * - Only UDP is supported as of now, no frame relay support
* - There are no BLOCK and UNBLOCK timers (yet?)
*
* \file gprs_ns2.c */
@@ -74,6 +72,7 @@
#include <arpa/inet.h>
#include <osmocom/core/fsm.h>
+#include <osmocom/core/logging.h>
#include <osmocom/core/msgb.h>
#include <osmocom/core/rate_ctr.h>
#include <osmocom/core/socket.h>
@@ -160,32 +159,43 @@ const struct value_string gprs_ns2_cause_strs[] = {
{ 0, NULL }
};
-static const struct rate_ctr_desc nsvc_ctr_description[] = {
- { "packets:in", "Packets at NS Level ( In)" },
- { "packets:out","Packets at NS Level (Out)" },
- { "bytes:in", "Bytes at NS Level ( In)" },
- { "bytes:out", "Bytes at NS Level (Out)" },
- { "blocked", "NS-VC Block count " },
- { "dead", "NS-VC gone dead count " },
- { "replaced", "NS-VC replaced other count" },
- { "nsei-chg", "NS-VC changed NSEI count " },
- { "inv-nsvci", "NS-VCI was invalid count " },
- { "inv-nsei", "NSEI was invalid count " },
- { "lost:alive", "ALIVE ACK missing count " },
- { "lost:reset", "RESET ACK missing count " },
+static const struct rate_ctr_desc ns_ctr_description[] = {
+ [NS_CTR_PKTS_IN] = { "packets:in", "Packets at NS Level ( In)" },
+ [NS_CTR_PKTS_OUT] = { "packets:out", "Packets at NS Level (Out)" },
+ [NS_CTR_PKTS_OUT_DROP] = { "packets:out:drop", "Dropped Packets (Out)" },
+ [NS_CTR_BYTES_IN] = { "bytes:in", "Bytes at NS Level ( In)" },
+ [NS_CTR_BYTES_OUT] = { "bytes:out", "Bytes at NS Level (Out)" },
+ [NS_CTR_BYTES_OUT_DROP] = { "bytes:out:drop", "Dropped Bytes (Out)" },
+ [NS_CTR_BLOCKED] = { "blocked", "NS-VC Block count " },
+ [NS_CTR_UNBLOCKED] = { "unblocked", "NS-VC Unblock count " },
+ [NS_CTR_DEAD] = { "dead", "NS-VC gone dead count " },
+ [NS_CTR_REPLACED] = { "replaced", "NS-VC replaced other count" },
+ [NS_CTR_NSEI_CHG] = { "nsei-chg", "NS-VC changed NSEI count " },
+ [NS_CTR_INV_VCI] = { "inv-nsvci", "NS-VCI was invalid count " },
+ [NS_CTR_INV_NSEI] = { "inv-nsei", "NSEI was invalid count " },
+ [NS_CTR_LOST_ALIVE] = { "lost:alive", "ALIVE ACK missing count " },
+ [NS_CTR_LOST_RESET] = { "lost:reset", "RESET ACK missing count " },
+};
+
+static const struct rate_ctr_group_desc nse_ctrg_desc = {
+ .group_name_prefix = "ns:nse",
+ .group_description = "NSE Peer Statistics",
+ .num_ctr = ARRAY_SIZE(ns_ctr_description),
+ .ctr_desc = ns_ctr_description,
+ .class_id = OSMO_STATS_CLASS_PEER,
};
static const struct rate_ctr_group_desc nsvc_ctrg_desc = {
.group_name_prefix = "ns:nsvc",
.group_description = "NSVC Peer Statistics",
- .num_ctr = ARRAY_SIZE(nsvc_ctr_description),
- .ctr_desc = nsvc_ctr_description,
+ .num_ctr = ARRAY_SIZE(ns_ctr_description),
+ .ctr_desc = ns_ctr_description,
.class_id = OSMO_STATS_CLASS_PEER,
};
static const struct osmo_stat_item_desc nsvc_stat_description[] = {
- { "alive.delay", "ALIVE response time ", "ms", 16, 0 },
+ [NS_STAT_ALIVE_DELAY] = { "alive.delay", "ALIVE response time ", "ms", 16, 0 },
};
static const struct osmo_stat_item_group_desc nsvc_statg_desc = {
@@ -196,20 +206,41 @@ static const struct osmo_stat_item_group_desc nsvc_statg_desc = {
.class_id = OSMO_STATS_CLASS_PEER,
};
+const struct osmo_stat_item_desc nsbind_stat_description[] = {
+ [NS2_BIND_STAT_BACKLOG_LEN] = { "tx_backlog_length", "Transmit backlog length", "packets", 16, 0 },
+};
+
+static const struct osmo_stat_item_group_desc nsbind_statg_desc = {
+ .group_name_prefix = "ns.bind",
+ .group_description = "NS Bind Statistics",
+ .num_items = ARRAY_SIZE(nsbind_stat_description),
+ .item_desc = nsbind_stat_description,
+ .class_id = OSMO_STATS_CLASS_PEER,
+};
+
const struct value_string gprs_ns2_aff_cause_prim_strs[] = {
- { NS_AFF_CAUSE_VC_FAILURE, "NSVC failure" },
- { NS_AFF_CAUSE_VC_RECOVERY, "NSVC recovery" },
- { NS_AFF_CAUSE_FAILURE, "NSE failure" },
- { NS_AFF_CAUSE_RECOVERY, "NSE recovery" },
- { NS_AFF_CAUSE_SNS_CONFIGURED, "NSE SNS configured" },
- { NS_AFF_CAUSE_SNS_FAILURE, "NSE SNS failure" },
+ { GPRS_NS2_AFF_CAUSE_VC_FAILURE, "NSVC failure" },
+ { GPRS_NS2_AFF_CAUSE_VC_RECOVERY, "NSVC recovery" },
+ { GPRS_NS2_AFF_CAUSE_FAILURE, "NSE failure" },
+ { GPRS_NS2_AFF_CAUSE_RECOVERY, "NSE recovery" },
+ { GPRS_NS2_AFF_CAUSE_SNS_CONFIGURED, "NSE SNS configured" },
+ { GPRS_NS2_AFF_CAUSE_SNS_FAILURE, "NSE SNS failure" },
+ { GPRS_NS2_AFF_CAUSE_SNS_NO_ENDPOINTS, "NSE SNS no endpoints"},
+ { GPRS_NS2_AFF_CAUSE_MTU_CHANGE, "NSE MTU changed" },
{ 0, NULL }
};
const struct value_string gprs_ns2_prim_strs[] = {
- { PRIM_NS_UNIT_DATA, "UNIT DATA" },
- { PRIM_NS_CONGESTION, "CONGESTION" },
- { PRIM_NS_STATUS, "STATUS" },
+ { GPRS_NS2_PRIM_UNIT_DATA, "UNIT DATA" },
+ { GPRS_NS2_PRIM_CONGESTION, "CONGESTION" },
+ { GPRS_NS2_PRIM_STATUS, "STATUS" },
+ { 0, NULL }
+};
+
+const struct value_string gprs_ns2_lltype_strs[] = {
+ { GPRS_NS2_LL_UDP, "UDP" },
+ { GPRS_NS2_LL_FR_GRE, "FR_GRE" },
+ { GPRS_NS2_LL_FR, "FR" },
{ 0, NULL }
};
@@ -228,8 +259,8 @@ char *gprs_ns2_ll_str_buf(char *buf, size_t buf_len, struct gprs_ns2_vc *nsvc)
if (!buf_len)
return NULL;
- switch (nsvc->ll) {
- case GPRS_NS_LL_UDP:
+ switch (nsvc->nse->ll) {
+ case GPRS_NS2_LL_UDP:
if (!gprs_ns2_is_ip_bind(nsvc->bind)) {
buf[0] = '\0';
return buf;
@@ -252,14 +283,15 @@ char *gprs_ns2_ll_str_buf(char *buf, size_t buf_len, struct gprs_ns2_vc *nsvc)
local_str.ip, local_str.port,
remote_str.ip, remote_str.port);
break;
- case GPRS_NS_LL_FR_GRE:
+ case GPRS_NS2_LL_FR_GRE:
snprintf(buf, buf_len, "frgre)");
break;
- case GPRS_NS_LL_E1:
- snprintf(buf, buf_len, "e1)");
+ case GPRS_NS2_LL_FR:
+ snprintf(buf, buf_len, "fr)netif: %s dlci: %u", gprs_ns2_fr_bind_netif(nsvc->bind),
+ gprs_ns2_fr_nsvc_dlci(nsvc));
break;
default:
- buf[0] = '\0';
+ snprintf(buf, buf_len, "unknown)");
break;
}
@@ -300,61 +332,215 @@ const char *gprs_ns2_nsvc_state_name(struct gprs_ns2_vc *nsvc)
return osmo_fsm_inst_state_name(nsvc->fi);
}
+/* select a signalling NSVC and respect sig_counter
+ * param[out] reset_counter - all counter has to be resetted to their signal weight
+ * return the chosen nsvc or NULL
+ */
+static struct gprs_ns2_vc *ns2_load_sharing_signal(struct gprs_ns2_nse *nse)
+{
+ struct gprs_ns2_vc *nsvc = NULL, *last = NULL, *tmp;
+
+ llist_for_each_entry(tmp, &nse->nsvc, list) {
+ if (tmp->sig_weight == 0)
+ continue;
+ if (!ns2_vc_is_unblocked(tmp))
+ continue;
+ if (tmp->sig_counter == 0) {
+ last = tmp;
+ continue;
+ }
+
+ tmp->sig_counter--;
+ nsvc = tmp;
+ break;
+ }
+
+ /* all counter were zero, but there are valid nsvc */
+ if (!nsvc && last) {
+ llist_for_each_entry(tmp, &nse->nsvc, list) {
+ tmp->sig_counter = tmp->sig_weight;
+ }
+
+ last->sig_counter--;
+ return last;
+ } else {
+ return nsvc;
+ }
+}
+
+/* 4.4.1 Load Sharing function for the Frame Relay Sub-Network */
+static struct gprs_ns2_vc *ns2_load_sharing_modulo(
+ struct gprs_ns2_nse *nse,
+ uint16_t bvci,
+ uint32_t load_selector)
+{
+ struct gprs_ns2_vc *tmp;
+ uint32_t mod;
+ uint32_t i = 0;
+
+ if (nse->nsvc_count == 0)
+ return NULL;
+
+ mod = (bvci + load_selector) % nse->nsvc_count;
+ llist_for_each_entry(tmp, &nse->nsvc, list) {
+ if (!ns2_vc_is_unblocked(tmp))
+ continue;
+ if (i == mod)
+ return tmp;
+ i++;
+ }
+
+ return NULL;
+}
+
+/* 4.4.2 Load Sharing function for the IP Sub-Network
+ *
+ * Implement a simple approach for UDP load sharing of data weight based on the modulo of the lsp.
+ *
+ * E.g. 3 NSVC: 1st weight 5, 2nd weight 3, 3rd weight 1, lsp = 3.
+ * sum all weights = 9
+ * target_weight = lsp % sum = 3
+ *
+ * 1st NSVC will be the target for 0-4
+ * 2nd NSVC will be the target for 5-7
+ * 3rd NSVC will be the target for 8
+ *
+ * The 1st NSVC will be used.
+ * E.g. lsp = 7. The 2nd NSVC will used.
+ */
+static struct gprs_ns2_vc *ns2_load_sharing_weight_modulo(
+ struct gprs_ns2_nse *nse,
+ uint16_t bvci,
+ uint32_t load_selector)
+{
+ struct gprs_ns2_vc *tmp;
+ uint32_t mod;
+ uint32_t i = 0;
+
+ if (nse->nsvc_count == 0)
+ return NULL;
+
+ mod = (bvci + load_selector) % nse->sum_data_weight;
+ llist_for_each_entry(tmp, &nse->nsvc, list) {
+ if (tmp->data_weight == 0)
+ continue;
+ if (!ns2_vc_is_unblocked(tmp))
+ continue;
+ if (i == mod || mod < i + tmp->data_weight)
+ return tmp;
+ i += tmp->data_weight;
+ }
+
+ return NULL;
+}
+
+/* pick the first available data NSVC - no load sharing */
+struct gprs_ns2_vc *ns2_load_sharing_first(struct gprs_ns2_nse *nse)
+{
+ struct gprs_ns2_vc *nsvc = NULL, *tmp;
+
+ llist_for_each_entry(tmp, &nse->nsvc, list) {
+ if (!ns2_vc_is_unblocked(tmp))
+ continue;
+ if (tmp->data_weight == 0)
+ continue;
+
+ nsvc = tmp;
+ break;
+ }
+
+ return nsvc;
+}
+
+
+static struct gprs_ns2_vc *ns2_load_sharing(
+ struct gprs_ns2_nse *nse,
+ uint16_t bvci,
+ uint32_t link_selector)
+{
+ struct gprs_ns2_vc *nsvc = NULL;
+
+ switch (nse->ll) {
+ case GPRS_NS2_LL_FR:
+ nsvc = ns2_load_sharing_modulo(nse, bvci, link_selector);
+ break;
+ case GPRS_NS2_LL_UDP:
+ default:
+ if (bvci == 0) {
+ /* signalling */
+ nsvc = ns2_load_sharing_signal(nse);
+ } else {
+ /* data with load sharing parameter */
+ nsvc = ns2_load_sharing_weight_modulo(nse, bvci, link_selector);
+ }
+ break;
+ }
+
+ return nsvc;
+}
+
/*! Receive a primitive from the NS User (Gb).
* \param[in] nsi NS instance to which the primitive is issued
* \param[in] oph The primitive
* \return 0 on success; negative on error */
int gprs_ns2_recv_prim(struct gprs_ns2_inst *nsi, struct osmo_prim_hdr *oph)
{
- /* TODO: implement load distribution function */
/* TODO: implement resource distribution */
/* TODO: check for empty PDUs which can be sent to Request/Confirm
* the IP endpoint */
struct osmo_gprs_ns2_prim *nsp;
struct gprs_ns2_nse *nse = NULL;
- struct gprs_ns2_vc *nsvc = NULL, *tmp;
+ struct gprs_ns2_vc *nsvc = NULL;
uint16_t bvci, nsei;
uint8_t sducontrol = 0;
+ int rc = 0;
- if (oph->sap != SAP_NS)
- return -EINVAL;
+ if (oph->sap != SAP_NS) {
+ rc = -EINVAL;
+ goto out;
+ }
nsp = container_of(oph, struct osmo_gprs_ns2_prim, oph);
- if (oph->operation != PRIM_OP_REQUEST || oph->primitive != PRIM_NS_UNIT_DATA)
- return -EINVAL;
+ if (oph->operation != PRIM_OP_REQUEST || oph->primitive != GPRS_NS2_PRIM_UNIT_DATA) {
+ rc = -EINVAL;
+ goto out;
+ }
- if (!oph->msg)
- return -EINVAL;
+ if (!oph->msg) {
+ rc = -EINVAL;
+ goto out;
+ }
bvci = nsp->bvci;
nsei = nsp->nsei;
nse = gprs_ns2_nse_by_nsei(nsi, nsei);
- if (!nse)
- return -EINVAL;
-
- llist_for_each_entry(tmp, &nse->nsvc, list) {
- if (!gprs_ns2_vc_is_unblocked(tmp))
- continue;
- if (bvci == 0 && tmp->sig_weight == 0)
- continue;
- if (bvci != 0 && tmp->data_weight == 0)
- continue;
+ if (!nse) {
+ rc = -EINVAL;
+ goto out;
+ }
- nsvc = tmp;
+ if (!nse->alive) {
+ goto out;
}
+ nsvc = ns2_load_sharing(nse, bvci, nsp->u.unitdata.link_selector);
+
/* TODO: send a status primitive back */
if (!nsvc)
- return 0;
+ goto out;
- if (nsp->u.unitdata.change == NS_ENDPOINT_REQUEST_CHANGE)
+ if (nsp->u.unitdata.change == GPRS_NS2_ENDPOINT_REQUEST_CHANGE)
sducontrol = 1;
- else if (nsp->u.unitdata.change == NS_ENDPOINT_CONFIRM_CHANGE)
+ else if (nsp->u.unitdata.change == GPRS_NS2_ENDPOINT_CONFIRM_CHANGE)
sducontrol = 2;
return ns2_tx_unit_data(nsvc, bvci, sducontrol, oph->msg);
+
+out:
+ msgb_free(oph->msg);
+ return rc;
}
/*! Send a STATUS.ind primitive to the specified NS instance user.
@@ -372,14 +558,26 @@ void ns2_prim_status_ind(struct gprs_ns2_nse *nse,
nsp.nsei = nse->nsei;
nsp.bvci = bvci;
nsp.u.status.cause = cause;
- nsp.u.status.transfer = -1;
+ nsp.u.status.transfer = ns2_count_transfer_cap(nse, bvci);
nsp.u.status.first = nse->first;
nsp.u.status.persistent = nse->persistent;
- if (nsvc)
+ if (nse->mtu < 4)
+ nsp.u.status.mtu = 0;
+ else
+ nsp.u.status.mtu = nse->mtu - 4; /* 1 Byte NS PDU type, 1 Byte NS SDU control, 2 Byte BVCI */
+
+ if (nsvc) {
nsp.u.status.nsvc = gprs_ns2_ll_str_buf(nsvc_str, sizeof(nsvc_str), nsvc);
+ LOGNSVC(nsvc, LOGL_NOTICE, "NS-STATUS.ind(bvci=%05u): cause=%s, transfer=%d, first=%d, mtu=%d\n",
+ nsp.bvci, gprs_ns2_aff_cause_prim_str(nsp.u.status.cause),
+ nsp.u.status.transfer, nsp.u.status.first, nsp.u.status.mtu);
+ } else {
+ LOGNSE(nse, LOGL_NOTICE, "NS-STATUS.ind(bvci=%05u): cause=%s, transfer=%d, first=%d, mtu=%d\n",
+ nsp.bvci, gprs_ns2_aff_cause_prim_str(nsp.u.status.cause),
+ nsp.u.status.transfer, nsp.u.status.first, nsp.u.status.mtu);
+ }
- osmo_prim_init(&nsp.oph, SAP_NS, PRIM_NS_STATUS,
- PRIM_OP_INDICATION, NULL);
+ osmo_prim_init(&nsp.oph, SAP_NS, GPRS_NS2_PRIM_STATUS, PRIM_OP_INDICATION, NULL);
nse->nsi->cb(&nsp.oph, nse->nsi->cb_data);
}
@@ -387,9 +585,14 @@ void ns2_prim_status_ind(struct gprs_ns2_nse *nse,
* \param[in] bind The 'bind' on which we operate
* \param[in] nse The NS Entity on which we operate
* \param[in] initiater - if this is an incoming remote (!initiater) or a local outgoing connection (initater)
+ * \param[in] id - human-readable identifier
* \return newly allocated NS-VC on success; NULL on error */
-struct gprs_ns2_vc *ns2_vc_alloc(struct gprs_ns2_vc_bind *bind, struct gprs_ns2_nse *nse, bool initiater)
+struct gprs_ns2_vc *ns2_vc_alloc(struct gprs_ns2_vc_bind *bind, struct gprs_ns2_nse *nse, bool initiater,
+ enum gprs_ns2_vc_mode vc_mode, const char *id)
{
+ /* Sanity check */
+ OSMO_ASSERT(bind->ll == nse->ll);
+
struct gprs_ns2_vc *nsvc = talloc_zero(bind, struct gprs_ns2_vc);
if (!nsvc)
@@ -397,24 +600,29 @@ struct gprs_ns2_vc *ns2_vc_alloc(struct gprs_ns2_vc_bind *bind, struct gprs_ns2_
nsvc->bind = bind;
nsvc->nse = nse;
- nsvc->mode = bind->vc_mode;
+ nsvc->mode = vc_mode;
nsvc->sig_weight = 1;
nsvc->data_weight = 1;
- nsvc->ctrg = rate_ctr_group_alloc(nsvc, &nsvc_ctrg_desc, bind->nsi->rate_ctr_idx);
+ nsvc->ctrg = rate_ctr_group_alloc(nsvc, &nsvc_ctrg_desc, bind->nsi->nsvc_rate_ctr_idx);
if (!nsvc->ctrg) {
goto err;
}
- nsvc->statg = osmo_stat_item_group_alloc(nsvc, &nsvc_statg_desc, bind->nsi->rate_ctr_idx);
+ nsvc->statg = osmo_stat_item_group_alloc(nsvc, &nsvc_statg_desc, bind->nsi->nsvc_rate_ctr_idx);
if (!nsvc->statg)
goto err_group;
- if (!gprs_ns2_vc_fsm_alloc(nsvc, NULL, initiater))
+ if (!ns2_vc_fsm_alloc(nsvc, id, initiater))
goto err_statg;
- bind->nsi->rate_ctr_idx++;
+ bind->nsi->nsvc_rate_ctr_idx++;
- llist_add(&nsvc->list, &nse->nsvc);
- llist_add(&nsvc->blist, &bind->nsvc);
+ rate_ctr_group_set_name(nsvc->ctrg, id);
+ osmo_stat_item_group_set_name(nsvc->statg, id);
+
+ llist_add_tail(&nsvc->list, &nse->nsvc);
+ llist_add_tail(&nsvc->blist, &bind->nsvc);
+ osmo_clock_gettime(CLOCK_MONOTONIC, &nsvc->ts_alive_change);
+ ns2_nse_update_mtu(nse);
return nsvc;
@@ -432,10 +640,10 @@ err:
* \param[in] nsvc NS-VC to destroy */
void gprs_ns2_free_nsvc(struct gprs_ns2_vc *nsvc)
{
- if (!nsvc)
+ if (!nsvc || nsvc->freed)
return;
-
- ns2_prim_status_ind(nsvc->nse, nsvc, 0, NS_AFF_CAUSE_VC_FAILURE);
+ nsvc->freed = true;
+ ns2_prim_status_ind(nsvc->nse, nsvc, 0, GPRS_NS2_AFF_CAUSE_VC_FAILURE);
llist_del(&nsvc->list);
llist_del(&nsvc->blist);
@@ -444,7 +652,7 @@ void gprs_ns2_free_nsvc(struct gprs_ns2_vc *nsvc)
ns2_nse_notify_unblocked(nsvc, false);
/* check if sns is using this VC */
- ns2_sns_free_nsvc(nsvc);
+ ns2_sns_replace_nsvc(nsvc);
osmo_fsm_inst_term(nsvc->fi, OSMO_FSM_TERM_REQUEST, NULL);
/* let the driver/bind clean up it's internal state */
@@ -457,8 +665,34 @@ void gprs_ns2_free_nsvc(struct gprs_ns2_vc *nsvc)
talloc_free(nsvc);
}
+void ns2_free_nsvcs(struct gprs_ns2_nse *nse)
+{
+ struct gprs_ns2_vc *nsvc;
+
+ /* prevent recursive free() when the user reacts on a down event and free() a second time */
+ while (!llist_empty(&nse->nsvc)) {
+ nsvc = llist_first_entry(&nse->nsvc, struct gprs_ns2_vc, list);
+ gprs_ns2_free_nsvc(nsvc);
+ }
+}
+
+/*! Destroy/release all NS-VC of given NSE
+ * \param[in] nse NSE
+ */
+void gprs_ns2_free_nsvcs(struct gprs_ns2_nse *nse)
+{
+ if (!nse || nse->freed)
+ return;
+
+ if (nse->bss_sns_fi) {
+ osmo_fsm_inst_dispatch(nse->bss_sns_fi, NS2_SNS_EV_REQ_FREE_NSVCS, NULL);
+ } else {
+ ns2_free_nsvcs(nse);
+ }
+}
+
/*! Allocate a message buffer for use with the NS2 stack. */
-struct msgb *gprs_ns2_msgb_alloc(void)
+struct msgb *ns2_msgb_alloc(void)
{
struct msgb *msg = msgb_alloc_headroom(NS_ALLOC_SIZE, NS_ALLOC_HEADROOM,
"GPRS/NS");
@@ -477,7 +711,7 @@ struct msgb *gprs_ns2_msgb_alloc(void)
* \return 0 on success */
static int reject_status_msg(struct msgb *orig_msg, struct tlv_parsed *tp, struct msgb **reject, enum ns_cause cause)
{
- struct msgb *msg = gprs_ns2_msgb_alloc();
+ struct msgb *msg = ns2_msgb_alloc();
struct gprs_ns_hdr *nsh;
bool have_vci = false;
uint8_t _cause = cause;
@@ -486,7 +720,7 @@ static int reject_status_msg(struct msgb *orig_msg, struct tlv_parsed *tp, struc
if (!msg)
return -ENOMEM;
- if (TLVP_PRESENT(tp, NS_IE_NSEI)) {
+ if (TLVP_PRES_LEN(tp, NS_IE_NSEI, 2)) {
nsei = tlvp_val16be(tp, NS_IE_NSEI);
LOGP(DLNS, LOGL_NOTICE, "NSEI=%u Rejecting message without NSVCI. Tx NS STATUS (cause=%s)\n",
@@ -498,7 +732,7 @@ static int reject_status_msg(struct msgb *orig_msg, struct tlv_parsed *tp, struc
nsh->pdu_type = NS_PDUT_STATUS;
msgb_tvlv_put(msg, NS_IE_CAUSE, 1, &_cause);
- have_vci = TLVP_PRESENT(tp, NS_IE_VCI);
+ have_vci = TLVP_PRES_LEN(tp, NS_IE_VCI, 2);
/* Section 9.2.7.1: Static conditions for NS-VCI */
if (cause == NS_CAUSE_NSVC_BLOCKED ||
@@ -567,30 +801,94 @@ struct gprs_ns2_vc *gprs_ns2_nsvc_by_nsvci(struct gprs_ns2_inst *nsi, uint16_t n
/*! Create a NS Entity within given NS instance.
* \param[in] nsi NS instance in which to create NS Entity
* \param[in] nsei NS Entity Identifier of to-be-created NSE
+ * \param[in] ip_sns_role_sgsn Does local side implement SGSN role?
* \returns newly-allocated NS-E in successful case; NULL on error */
-struct gprs_ns2_nse *gprs_ns2_create_nse(struct gprs_ns2_inst *nsi, uint16_t nsei)
+struct gprs_ns2_nse *gprs_ns2_create_nse2(struct gprs_ns2_inst *nsi, uint16_t nsei,
+ enum gprs_ns2_ll linklayer, enum gprs_ns2_dialect dialect,
+ bool ip_sns_role_sgsn)
{
struct gprs_ns2_nse *nse;
nse = gprs_ns2_nse_by_nsei(nsi, nsei);
if (nse) {
- LOGP(DLNS, LOGL_ERROR, "NSEI:%u Can not create a NSE with already taken NSEI\n", nsei);
+ LOGNSE(nse, LOGL_ERROR, "Can not create a NSE with already taken NSEI\n");
return nse;
}
nse = talloc_zero(nsi, struct gprs_ns2_nse);
if (!nse)
return NULL;
+ nse->dialect = GPRS_NS2_DIALECT_UNDEF;
+ nse->ip_sns_role_sgsn = ip_sns_role_sgsn;
+
+ if (ns2_nse_set_dialect(nse, dialect) < 0) {
+ talloc_free(nse);
+ return NULL;
+ }
+
+ nse->ctrg = rate_ctr_group_alloc(nse, &nse_ctrg_desc, nsei);
+ if (!nse->ctrg) {
+ talloc_free(nse);
+ return NULL;
+ }
+ nse->ll = linklayer;
nse->nsei = nsei;
nse->nsi = nsi;
nse->first = true;
- llist_add(&nse->list, &nsi->nse);
+ nse->mtu = 0;
+ llist_add_tail(&nse->list, &nsi->nse);
INIT_LLIST_HEAD(&nse->nsvc);
+ osmo_clock_gettime(CLOCK_MONOTONIC, &nse->ts_alive_change);
return nse;
}
+int ns2_nse_set_dialect(struct gprs_ns2_nse *nse, enum gprs_ns2_dialect dialect)
+{
+ char sns[16];
+
+ if (nse->dialect == dialect)
+ return 0;
+
+ switch (nse->dialect) {
+ case GPRS_NS2_DIALECT_UNDEF:
+ if (dialect == GPRS_NS2_DIALECT_SNS) {
+ snprintf(sns, sizeof(sns), "NSE%05u-SNS", nse->nsei);
+ if (nse->ip_sns_role_sgsn)
+ nse->bss_sns_fi = ns2_sns_sgsn_fsm_alloc(nse, sns);
+ else
+ nse->bss_sns_fi = ns2_sns_bss_fsm_alloc(nse, sns);
+ if (!nse->bss_sns_fi)
+ return -1;
+ }
+ nse->dialect = dialect;
+ break;
+ default:
+ if (dialect == GPRS_NS2_DIALECT_UNDEF) {
+ if (nse->bss_sns_fi)
+ osmo_fsm_inst_term(nse->bss_sns_fi, OSMO_FSM_TERM_REQUEST, NULL);
+ nse->bss_sns_fi = NULL;
+ nse->dialect = GPRS_NS2_DIALECT_UNDEF;
+ } else {
+ /* we don't support arbitrary changes without going through UNDEF first */
+ return -EPERM;
+ }
+ }
+
+ return 0;
+}
+
+/*! Create a NS Entity within given NS instance.
+ * \param[in] nsi NS instance in which to create NS Entity
+ * \param[in] nsei NS Entity Identifier of to-be-created NSE
+ * \returns newly-allocated NS-E in successful case; NULL on error */
+struct gprs_ns2_nse *gprs_ns2_create_nse(struct gprs_ns2_inst *nsi, uint16_t nsei,
+ enum gprs_ns2_ll linklayer, enum gprs_ns2_dialect dialect)
+{
+ return gprs_ns2_create_nse2(nsi, nsei, linklayer, dialect, false);
+}
+
/*! Return the NSEI
* \param[in] nse NS Entity
* \return the nsei.
@@ -604,28 +902,32 @@ uint16_t gprs_ns2_nse_nsei(struct gprs_ns2_nse *nse)
* \param[in] nse NS Entity to destroy */
void gprs_ns2_free_nse(struct gprs_ns2_nse *nse)
{
- struct gprs_ns2_vc *nsvc, *tmp;
-
- if (!nse)
+ if (!nse || nse->freed)
return;
- llist_for_each_entry_safe(nsvc, tmp, &nse->nsvc, list) {
- gprs_ns2_free_nsvc(nsvc);
+ nse->freed = true;
+ nse->alive = false;
+ if (nse->bss_sns_fi) {
+ osmo_fsm_inst_term(nse->bss_sns_fi, OSMO_FSM_TERM_REQUEST, NULL);
+ nse->bss_sns_fi = NULL;
}
- ns2_prim_status_ind(nse, NULL, 0, NS_AFF_CAUSE_FAILURE);
+ gprs_ns2_free_nsvcs(nse);
+ ns2_prim_status_ind(nse, NULL, 0, GPRS_NS2_AFF_CAUSE_FAILURE);
+ rate_ctr_group_free(nse->ctrg);
+ ns2_free_nsvcs(nse);
llist_del(&nse->list);
- if (nse->bss_sns_fi)
- osmo_fsm_inst_term(nse->bss_sns_fi, OSMO_FSM_TERM_REQUEST, NULL);
talloc_free(nse);
}
void gprs_ns2_free_nses(struct gprs_ns2_inst *nsi)
{
- struct gprs_ns2_nse *nse, *ntmp;
+ struct gprs_ns2_nse *nse;
- llist_for_each_entry_safe(nse, ntmp, &nsi->nse, list) {
+ /* prevent recursive free() when the user reacts on a down event and free() a second time */
+ while (!llist_empty(&nsi->nse)) {
+ nse = llist_first_entry(&nsi->nse, struct gprs_ns2_nse, list);
gprs_ns2_free_nse(nse);
}
}
@@ -642,120 +944,229 @@ static inline int ns2_tlv_parse(struct tlv_parsed *dec,
return rc;
}
+static enum ns2_cs ns2_create_vc_sns(struct gprs_ns2_vc_bind *bind,
+ const struct osmo_sockaddr *remote,
+ struct gprs_ns2_vc **success, uint16_t nsei)
+{
+ struct gprs_ns2_vc *nsvc;
+ struct gprs_ns2_nse *nse;
+
+ nsvc = gprs_ns2_nsvc_by_sockaddr_bind(bind, remote);
+ /* ns2_create_vc() is only called if no NS-VC could be found */
+ OSMO_ASSERT(!nsvc);
+
+ nse = gprs_ns2_nse_by_nsei(bind->nsi, nsei);
+ if (!nse) {
+ if (!bind->accept_sns) {
+ struct osmo_sockaddr_str remote_str;
+ osmo_sockaddr_str_from_sockaddr(&remote_str, &remote->u.sas);
+ /* no dynamic creation of IP-SNS NSE permitted */
+ LOGP(DLNS, LOGL_ERROR, "[%s]:%u: Dynamic creation of NSE(%05u) via IP-SNS not "
+ "permitted. Check your config.\n", remote_str.ip, remote_str.port, nsei);
+ return NS2_CS_ERROR;
+ }
+ nse = gprs_ns2_create_nse2(bind->nsi, nsei, bind->ll, GPRS_NS2_DIALECT_SNS, true);
+ if (!nse) {
+ LOGP(DLNS, LOGL_ERROR, "Failed to create NSE(%05u)\n", nsei);
+ return NS2_CS_ERROR;
+ }
+ /* add configured list of default binds; if that fails, use only current bind */
+ if (!ns2_sns_add_sns_default_binds(nse))
+ gprs_ns2_sns_add_bind(nse, bind);
+ } else {
+ /* nsei already known */
+ if (nse->ll != bind->ll) {
+ LOGNSE(nse, LOGL_ERROR, "Received NS-RESET with wrong linklayer(%s)"
+ " for already known NSE(%s)\n", gprs_ns2_lltype_str(bind->ll),
+ gprs_ns2_lltype_str(nse->ll));
+ return NS2_CS_SKIPPED;
+ }
+ }
+
+ nsvc = ns2_ip_bind_connect(bind, nse, remote);
+ if (!nsvc)
+ return NS2_CS_SKIPPED;
+
+ nsvc->nsvci_is_valid = false;
+
+ *success = nsvc;
+
+ return NS2_CS_CREATED;
+}
/*! Create a new NS-VC based on a [received] message. Depending on the bind it might create a NSE.
* \param[in] bind the bind through which msg was received
* \param[in] msg the actual received message
+ * \param[in] remote address of remote peer sending message
* \param[in] logname A name to describe the VC. E.g. ip address pair
* \param[out] reject A message filled to be sent back. Only used in failure cases.
* \param[out] success A pointer which will be set to the new VC on success
* \return enum value indicating the status, e.g. GPRS_NS2_CS_CREATED */
-enum gprs_ns2_cs ns2_create_vc(struct gprs_ns2_vc_bind *bind,
- struct msgb *msg,
- const char *logname,
- struct msgb **reject,
- struct gprs_ns2_vc **success)
+enum ns2_cs ns2_create_vc(struct gprs_ns2_vc_bind *bind,
+ struct msgb *msg,
+ const struct osmo_sockaddr *remote,
+ const char *logname,
+ struct msgb **reject,
+ struct gprs_ns2_vc **success)
{
struct gprs_ns_hdr *nsh = (struct gprs_ns_hdr *)msg->l2h;
struct tlv_parsed tp;
struct gprs_ns2_vc *nsvc;
struct gprs_ns2_nse *nse;
+ enum gprs_ns2_dialect dialect;
+ enum gprs_ns2_vc_mode vc_mode;
uint16_t nsvci;
uint16_t nsei;
+ const struct osmo_sockaddr *local;
+ char idbuf[256], tmp[INET6_ADDRSTRLEN + 8];
- int rc;
+ int rc, tlv;
if (msg->len < sizeof(struct gprs_ns_hdr))
- return GPRS_NS2_CS_ERROR;
+ return NS2_CS_ERROR;
+
+ /* parse the tlv early to allow reject status msg to
+ * work with valid tp.
+ * Ignore the return code until the pdu type is parsed because
+ * an unknown pdu type should be ignored */
+ tlv = ns2_tlv_parse(&tp, nsh->data,
+ msgb_l2len(msg) - sizeof(*nsh), 0, 0);
+
+ if (bind->ll == GPRS_NS2_LL_UDP && nsh->pdu_type == SNS_PDUT_SIZE && tlv >= 0) {
+ uint16_t nsei;
- if (nsh->pdu_type == NS_PDUT_STATUS) {
+ if (!TLVP_PRES_LEN(&tp, NS_IE_NSEI, 2)) {
+ rc = reject_status_msg(msg, &tp, reject, NS_CAUSE_MISSING_ESSENT_IE);
+ if (rc < 0)
+ LOGP(DLNS, LOGL_ERROR, "Failed to generate reject message (%d)\n", rc);
+ return NS2_CS_REJECTED;
+ }
+ nsei = tlvp_val16be(&tp, NS_IE_NSEI);
+ /* Create NS-VC, and if required, even NSE dynamically */
+ return ns2_create_vc_sns(bind, remote, success, nsei);
+ }
+
+ switch (nsh->pdu_type) {
+ case NS_PDUT_STATUS:
/* Do not respond, see 3GPP TS 08.16, 7.5.1 */
LOGP(DLNS, LOGL_INFO, "Ignoring NS STATUS from %s "
"for non-existing NS-VC\n",
logname);
- return GPRS_NS2_CS_SKIPPED;
- }
-
- if (nsh->pdu_type == NS_PDUT_ALIVE_ACK) {
+ return NS2_CS_SKIPPED;
+ case NS_PDUT_ALIVE_ACK:
/* Ignore this, see 3GPP TS 08.16, 7.4.1 */
LOGP(DLNS, LOGL_INFO, "Ignoring NS ALIVE ACK from %s "
"for non-existing NS-VC\n",
logname);
- return GPRS_NS2_CS_SKIPPED;
- }
-
- if (nsh->pdu_type == NS_PDUT_RESET_ACK) {
+ return NS2_CS_SKIPPED;
+ case NS_PDUT_RESET_ACK:
/* Ignore this, see 3GPP TS 08.16, 7.3.1 */
LOGP(DLNS, LOGL_INFO, "Ignoring NS RESET ACK from %s "
"for non-existing NS-VC\n",
logname);
- return GPRS_NS2_CS_SKIPPED;
- }
-
- if (bind->vc_mode == NS2_VC_MODE_BLOCKRESET) {
- /* Only the RESET procedure creates a new NSVC */
- if (nsh->pdu_type != NS_PDUT_RESET) {
- rc = reject_status_msg(msg, &tp, reject, NS_CAUSE_PDU_INCOMP_PSTATE);
-
- if (rc < 0) {
- LOGP(DLNS, LOGL_ERROR, "Failed to generate reject message (%d)\n", rc);
- return rc;
- }
- return GPRS_NS2_CS_REJECTED;
+ return NS2_CS_SKIPPED;
+ case NS_PDUT_RESET:
+ /* accept PDU RESET when vc_mode matches */
+ if (bind->accept_ipaccess) {
+ dialect = GPRS_NS2_DIALECT_IPACCESS;
+ break;
}
- } else { /* NS2_VC_MODE_ALIVE */
- rc = reject_status_msg(msg, &tp, reject, NS_CAUSE_PDU_INCOMP_PSTATE);
- if (rc < 0) {
+ rc = reject_status_msg(msg, &tp, reject, NS_CAUSE_PDU_INCOMP_PSTATE);
+ if (rc < 0)
LOGP(DLNS, LOGL_ERROR, "Failed to generate reject message (%d)\n", rc);
- return rc;
- }
- return GPRS_NS2_CS_REJECTED;
+ return NS2_CS_REJECTED;
+ default:
+ rc = reject_status_msg(msg, &tp, reject, NS_CAUSE_PDU_INCOMP_PSTATE);
+ if (rc < 0)
+ LOGP(DLNS, LOGL_ERROR, "Failed to generate reject message (%d)\n", rc);
+ return NS2_CS_REJECTED;
}
- rc = ns2_tlv_parse(&tp, nsh->data,
- msgb_l2len(msg) - sizeof(*nsh), 0, 0);
- if (rc < 0) {
+ if (tlv < 0) {
+ /* TODO: correct behaviour would checking what's wrong.
+ * If it's an essential TLV for the PDU return NS_CAUSE_INVAL_ESSENT_IE.
+ * Otherwise ignore the non-essential TLV. */
LOGP(DLNS, LOGL_ERROR, "Rx NS RESET Error %d during "
- "TLV Parse\n", rc);
- /* TODO: send invalid message back */
- return GPRS_NS2_CS_REJECTED;
+ "TLV Parse\n", tlv);
+ rc = reject_status_msg(msg, &tp, reject, NS_CAUSE_PROTO_ERR_UNSPEC);
+ if (rc < 0)
+ LOGP(DLNS, LOGL_ERROR, "Failed to generate reject message (%d)\n", rc);
+ return NS2_CS_REJECTED;
}
- if (!TLVP_PRESENT(&tp, NS_IE_CAUSE) ||
- !TLVP_PRESENT(&tp, NS_IE_VCI) || !TLVP_PRESENT(&tp, NS_IE_NSEI)) {
+ if (!TLVP_PRES_LEN(&tp, NS_IE_CAUSE, 1) ||
+ !TLVP_PRES_LEN(&tp, NS_IE_VCI, 2) || !TLVP_PRES_LEN(&tp, NS_IE_NSEI, 2)) {
LOGP(DLNS, LOGL_ERROR, "NS RESET Missing mandatory IE\n");
rc = reject_status_msg(msg, &tp, reject, NS_CAUSE_MISSING_ESSENT_IE);
- return GPRS_NS2_CS_REJECTED;
+ if (rc < 0)
+ LOGP(DLNS, LOGL_ERROR, "Failed to generate reject message (%d)\n", rc);
+ return NS2_CS_REJECTED;
}
- /* find or create NSE */
nsei = tlvp_val16be(&tp, NS_IE_NSEI);
+ nsvci = tlvp_val16be(&tp, NS_IE_VCI);
+
+ /* find or create NSE */
nse = gprs_ns2_nse_by_nsei(bind->nsi, nsei);
if (!nse) {
- if (!bind->nsi->create_nse) {
- return GPRS_NS2_CS_SKIPPED;
- }
+ /* only create nse for udp & ipaccess */
+ if (bind->ll != GPRS_NS2_LL_UDP || dialect != GPRS_NS2_DIALECT_IPACCESS)
+ return NS2_CS_SKIPPED;
+
+ if (!bind->accept_ipaccess)
+ return NS2_CS_SKIPPED;
- nse = gprs_ns2_create_nse(bind->nsi, nsei);
+ nse = gprs_ns2_create_nse(bind->nsi, nsei, bind->ll, dialect);
if (!nse) {
- return GPRS_NS2_CS_ERROR;
+ LOGP(DLNS, LOGL_ERROR, "Failed to create NSE(%05u)\n", nsei);
+ return NS2_CS_ERROR;
+ }
+ } else {
+ /* nsei already known */
+ if (nse->ll != bind->ll) {
+ LOGNSE(nse, LOGL_ERROR, "Received NS-RESET NS-VCI(%05u) with wrong linklayer(%s)"
+ " for already known NSE(%s)\n", nsvci, gprs_ns2_lltype_str(bind->ll),
+ gprs_ns2_lltype_str(nse->ll));
+ return NS2_CS_SKIPPED;
}
}
- nsvc = ns2_vc_alloc(bind, nse, false);
- if (!nsvc)
- return GPRS_NS2_CS_SKIPPED;
+ nsvc = gprs_ns2_nsvc_by_nsvci(bind->nsi, nsvci);
+ if (nsvc) {
+ if (nsvc->persistent) {
+ LOGNSVC(nsvc, LOGL_ERROR, "Received NS-RESET for a persistent NSE over wrong connection.\n");
+ return NS2_CS_SKIPPED;
+ }
+ /* destroy old dynamic nsvc */
+ gprs_ns2_free_nsvc(nsvc);
+ }
- nsvc->ll = GPRS_NS_LL_UDP;
+ /* do nse persistent check late to be more precise on the error message */
+ if (nse->persistent) {
+ LOGNSE(nse, LOGL_ERROR, "Received NS-RESET for a persistent NSE but the unknown "
+ "NS-VCI(%05u)\n", nsvci);
+ return NS2_CS_SKIPPED;
+ }
nsvci = tlvp_val16be(&tp, NS_IE_VCI);
+ vc_mode = ns2_dialect_to_vc_mode(dialect);
+
+ local = gprs_ns2_ip_bind_sockaddr(bind);
+ osmo_sockaddr_to_str_buf(tmp, sizeof(tmp), local);
+ snprintf(idbuf, sizeof(idbuf), "%s-NSE%05u-NSVC%05u-%s-%s", gprs_ns2_lltype_str(nse->ll),
+ nse->nsei, nsvci, tmp, osmo_sockaddr_to_str(remote));
+ osmo_identifier_sanitize_buf(idbuf, NULL, '_');
+ nsvc = ns2_vc_alloc(bind, nse, false, vc_mode, idbuf);
+ if (!nsvc)
+ return NS2_CS_SKIPPED;
+
nsvc->nsvci = nsvci;
nsvc->nsvci_is_valid = true;
*success = nsvc;
- return GPRS_NS2_CS_CREATED;
+ return NS2_CS_CREATED;
}
/*! Create, and connect an inactive, new IP-based NS-VC
@@ -771,11 +1182,11 @@ struct gprs_ns2_vc *gprs_ns2_ip_connect_inactive(struct gprs_ns2_vc_bind *bind,
{
struct gprs_ns2_vc *nsvc;
- nsvc = gprs_ns2_ip_bind_connect(bind, nse, remote);
+ nsvc = ns2_ip_bind_connect(bind, nse, remote);
if (!nsvc)
return NULL;
- if (nsvc->mode == NS2_VC_MODE_BLOCKRESET) {
+ if (nsvc->mode == GPRS_NS2_VC_MODE_BLOCKRESET) {
nsvc->nsvci = nsvci;
nsvc->nsvci_is_valid = true;
}
@@ -799,7 +1210,7 @@ struct gprs_ns2_vc *gprs_ns2_ip_connect(struct gprs_ns2_vc_bind *bind,
if (!nsvc)
return NULL;
- gprs_ns2_vc_fsm_start(nsvc);
+ ns2_vc_fsm_start(nsvc);
return nsvc;
}
@@ -813,12 +1224,13 @@ struct gprs_ns2_vc *gprs_ns2_ip_connect(struct gprs_ns2_vc_bind *bind,
struct gprs_ns2_vc *gprs_ns2_ip_connect2(struct gprs_ns2_vc_bind *bind,
const struct osmo_sockaddr *remote,
uint16_t nsei,
- uint16_t nsvci)
+ uint16_t nsvci,
+ enum gprs_ns2_dialect dialect)
{
struct gprs_ns2_nse *nse = gprs_ns2_nse_by_nsei(bind->nsi, nsei);
if (!nse) {
- nse = gprs_ns2_create_nse(bind->nsi, nsei);
+ nse = gprs_ns2_create_nse(bind->nsi, nsei, GPRS_NS2_LL_UDP, dialect);
if (!nse)
return NULL;
}
@@ -826,37 +1238,6 @@ struct gprs_ns2_vc *gprs_ns2_ip_connect2(struct gprs_ns2_vc_bind *bind,
return gprs_ns2_ip_connect(bind, remote, nse, nsvci);
}
-/*! Create, connect and activate a new IP-SNS NSE.
- * \param[in] bind bind in which the new NS-VC is to be created
- * \param[in] remote remote address to which to connect
- * \param[in] nsei NSEI of the NS Entity in which the NS-VC is to be created
- * \return 0 on success; negative on error */
-int gprs_ns2_ip_connect_sns(struct gprs_ns2_vc_bind *bind,
- const struct osmo_sockaddr *remote,
- uint16_t nsei)
-{
- struct gprs_ns2_nse *nse = gprs_ns2_nse_by_nsei(bind->nsi, nsei);
- struct gprs_ns2_vc *nsvc;
-
- if (!nse) {
- nse = gprs_ns2_create_nse(bind->nsi, nsei);
- if (!nse)
- return -1;
- }
-
- nsvc = gprs_ns2_ip_bind_connect(bind, nse, remote);
- if (!nsvc)
- return -1;
-
- if (!nse->bss_sns_fi)
- nse->bss_sns_fi = ns2_sns_bss_fsm_alloc(nse, NULL);
-
- if (!nse->bss_sns_fi)
- return -1;
-
- return ns2_sns_bss_fsm_start(nse, nsvc, remote);
-}
-
/*! Find NS-VC for given socket address.
* \param[in] nse NS Entity in which to search
* \param[in] sockaddr socket address to search for
@@ -904,17 +1285,30 @@ int gprs_ns2_nse_foreach_nsvc(struct gprs_ns2_nse *nse, gprs_ns2_foreach_nsvc_cb
/*! Bottom-side entry-point for received NS PDU from the driver/bind
* \param[in] nsvc NS-VC for which the message was received
- * \param msg the received message. Ownership is trasnferred, caller must not free it!
+ * \param msg the received message. Ownership is transferred, caller must not free it!
* \return 0 on success; negative on error */
int ns2_recv_vc(struct gprs_ns2_vc *nsvc,
struct msgb *msg)
{
struct gprs_ns_hdr *nsh = (struct gprs_ns_hdr *) msg->l2h;
- struct tlv_parsed tp;
+ struct tlv_parsed tp = { };
int rc = 0;
- if (msg->len < sizeof(struct gprs_ns_hdr))
- return -EINVAL;
+ log_set_context(LOG_CTX_GB_NSE, nsvc->nse);
+ log_set_context(LOG_CTX_GB_NSVC, nsvc);
+
+ RATE_CTR_INC_NS(nsvc, NS_CTR_PKTS_IN);
+ RATE_CTR_ADD_NS(nsvc, NS_CTR_BYTES_IN, msg->len);
+
+ if (msg->len < sizeof(struct gprs_ns_hdr)) {
+ rc = -EINVAL;
+ goto freemsg;
+ }
+
+ if (nsh->pdu_type != NS_PDUT_UNITDATA)
+ LOG_NS_RX_SIGNAL(nsvc, nsh->pdu_type);
+ else
+ LOG_NS_DATA(nsvc, "Rx", nsh->pdu_type, LOGL_INFO, "\n");
switch (nsh->pdu_type) {
case SNS_PDUT_CONFIG:
@@ -923,11 +1317,10 @@ int ns2_recv_vc(struct gprs_ns2_vc *nsvc,
msgb_l2len(msg) - sizeof(*nsh)-1, 0, 0);
if (rc < 0) {
LOGP(DLNS, LOGL_NOTICE, "Error during TLV Parse in %s\n", msgb_hexdump(msg));
- return rc;
+ goto freemsg;
}
/* All sub-network service related message types */
- rc = gprs_ns2_sns_rx(nsvc, msg, &tp);
- break;
+ return ns2_sns_rx(nsvc, msg, &tp);
case SNS_PDUT_ACK:
case SNS_PDUT_ADD:
case SNS_PDUT_CHANGE_WEIGHT:
@@ -937,14 +1330,13 @@ int ns2_recv_vc(struct gprs_ns2_vc *nsvc,
msgb_l2len(msg) - sizeof(*nsh)-5, 0, 0);
if (rc < 0) {
LOGP(DLNS, LOGL_NOTICE, "Error during TLV Parse in %s\n", msgb_hexdump(msg));
- return rc;
+ goto freemsg;
}
tp.lv[NS_IE_NSEI].val = nsh->data+2;
tp.lv[NS_IE_NSEI].len = 2;
tp.lv[NS_IE_TRANS_ID].val = nsh->data+4;
tp.lv[NS_IE_TRANS_ID].len = 1;
- rc = gprs_ns2_sns_rx(nsvc, msg, &tp);
- break;
+ return ns2_sns_rx(nsvc, msg, &tp);
case SNS_PDUT_CONFIG_ACK:
case SNS_PDUT_SIZE:
case SNS_PDUT_SIZE_ACK:
@@ -952,64 +1344,81 @@ int ns2_recv_vc(struct gprs_ns2_vc *nsvc,
msgb_l2len(msg) - sizeof(*nsh), 0, 0);
if (rc < 0) {
LOGP(DLNS, LOGL_NOTICE, "Error during TLV Parse in %s\n", msgb_hexdump(msg));
- return rc;
+ goto freemsg;
}
/* All sub-network service related message types */
- rc = gprs_ns2_sns_rx(nsvc, msg, &tp);
- break;
-
+ return ns2_sns_rx(nsvc, msg, &tp);
case NS_PDUT_UNITDATA:
- rc = gprs_ns2_vc_rx(nsvc, msg, NULL);
- break;
+ return ns2_vc_rx(nsvc, msg, &tp);
default:
rc = ns2_tlv_parse(&tp, nsh->data,
msgb_l2len(msg) - sizeof(*nsh), 0, 0);
if (rc < 0) {
LOGP(DLNS, LOGL_NOTICE, "Error during TLV Parse\n");
if (nsh->pdu_type != NS_PDUT_STATUS)
- ns2_tx_status(nsvc, NS_CAUSE_PROTO_ERR_UNSPEC, 0, msg);
+ ns2_tx_status(nsvc, NS_CAUSE_PROTO_ERR_UNSPEC, 0, msg, NULL);
return rc;
}
- rc = gprs_ns2_vc_rx(nsvc, msg, &tp);
- break;
+ return ns2_vc_rx(nsvc, msg, &tp);
}
+freemsg:
+ msgb_free(msg);
return rc;
}
+/* summarize all active data nsvcs */
+void ns2_nse_data_sum(struct gprs_ns2_nse *nse)
+{
+ struct gprs_ns2_vc *nsvc;
+
+ nse->nsvc_count = 0;
+ nse->sum_data_weight = 0;
+ nse->sum_sig_weight = 0;
+
+ llist_for_each_entry(nsvc, &nse->nsvc, list) {
+ if (!ns2_vc_is_unblocked(nsvc))
+ continue;
+
+ nse->nsvc_count++;
+ nse->sum_data_weight += nsvc->data_weight;
+ nse->sum_sig_weight += nsvc->sig_weight;
+ }
+}
+
/*! Notify a nse about the change of a NS-VC.
* \param[in] nsvc NS-VC which has detected the change (and shall not be notified).
* \param[in] unblocked whether the NSE should be marked as unblocked (true) or blocked (false) */
void ns2_nse_notify_unblocked(struct gprs_ns2_vc *nsvc, bool unblocked)
{
struct gprs_ns2_nse *nse = nsvc->nse;
- struct gprs_ns2_vc *tmp;
+ struct gprs_ns2_inst *nsi = nse->nsi;
+ uint16_t nsei = nse->nsei;
- if (unblocked == nse->alive)
+ ns2_nse_data_sum(nse);
+ ns2_sns_notify_alive(nse, nsvc, unblocked);
+
+ /* NSE could have been freed, try to get it again */
+ nse = gprs_ns2_nse_by_nsei(nsi, nsei);
+
+ if (!nse || unblocked == nse->alive)
return;
- if (unblocked) {
- /* this is the first unblocked NSVC on an unavailable NSE */
+ /* wait until both data_weight and sig_weight are != 0 before declaring NSE as alive */
+ if (unblocked && nse->sum_data_weight && nse->sum_sig_weight) {
nse->alive = true;
- ns2_prim_status_ind(nse, NULL, 0, NS_AFF_CAUSE_RECOVERY);
+ osmo_clock_gettime(CLOCK_MONOTONIC, &nse->ts_alive_change);
+ ns2_prim_status_ind(nse, NULL, 0, GPRS_NS2_AFF_CAUSE_RECOVERY);
nse->first = false;
return;
}
- /* check if there are any remaining alive vcs */
- llist_for_each_entry(tmp, &nse->nsvc, list) {
- if (tmp == nsvc)
- continue;
-
- if (gprs_ns2_vc_is_unblocked(tmp)) {
- /* there is at least one remaining alive NSVC */
- return;
- }
+ if (nse->alive && (nse->sum_data_weight == 0 || nse->sum_sig_weight == 0)) {
+ /* nse became unavailable */
+ nse->alive = false;
+ osmo_clock_gettime(CLOCK_MONOTONIC, &nse->ts_alive_change);
+ ns2_prim_status_ind(nse, NULL, 0, GPRS_NS2_AFF_CAUSE_FAILURE);
}
-
- /* nse became unavailable */
- nse->alive = false;
- ns2_prim_status_ind(nse, NULL, 0, NS_AFF_CAUSE_FAILURE);
}
/*! Create a new GPRS NS instance
@@ -1038,6 +1447,11 @@ struct gprs_ns2_inst *gprs_ns2_instantiate(void *ctx, osmo_prim_cb cb, void *cb_
nsi->timeout[NS_TOUT_TNS_ALIVE] = 3;
nsi->timeout[NS_TOUT_TNS_ALIVE_RETRIES] = 10;
nsi->timeout[NS_TOUT_TSNS_PROV] = 3; /* 1..10 */
+ nsi->timeout[NS_TOUT_TSNS_SIZE_RETRIES] = 3;
+ nsi->timeout[NS_TOUT_TSNS_CONFIG_RETRIES] = 3;
+ nsi->timeout[NS_TOUT_TSNS_PROCEDURES_RETRIES] = 3;
+
+ nsi->txqueue_max_length = NS_DEFAULT_TXQUEUE_MAX_LENGTH;
return nsi;
}
@@ -1055,18 +1469,6 @@ void gprs_ns2_free(struct gprs_ns2_inst *nsi)
talloc_free(nsi);
}
-/*! Configure whether a NS Instance should dynamically create NSEs based on incoming traffic.
- * \param nsi the instance to modify
- * \param create_nse if NSE can be created on receiving package. SGSN set this.
- * \return 0 on success; negative on error
- */
-int gprs_ns2_dynamic_create_nse(struct gprs_ns2_inst *nsi, bool create_nse)
-{
- nsi->create_nse = create_nse;
-
- return 0;
-}
-
/*! Start the NS-ALIVE FSM in all NS-VCs of given NSE.
* \param[in] nse NS Entity in whihc to start NS-ALIVE FSMs */
void gprs_ns2_start_alive_all_nsvcs(struct gprs_ns2_nse *nse)
@@ -1075,30 +1477,35 @@ void gprs_ns2_start_alive_all_nsvcs(struct gprs_ns2_nse *nse)
OSMO_ASSERT(nse);
llist_for_each_entry(nsvc, &nse->nsvc, list) {
+ /* A pre-configured endpoint shall not be used for NSE data or signalling traffic
+ * (with the exception of Size and Configuration procedures) unless it is
+ * configured by the SGSN using the auto-configuration procedures */
if (nsvc->sns_only)
continue;
- gprs_ns2_vc_fsm_start(nsvc);
+ ns2_vc_fsm_start(nsvc);
}
}
-/*! Set the mode of given bind.
- * \param[in] bind the bind we want to set the mode of
- * \param[in] modde mode to set bind to */
-void gprs_ns2_bind_set_mode(struct gprs_ns2_vc_bind *bind, enum gprs_ns2_vc_mode mode)
-{
- bind->vc_mode = mode;
-}
-
/*! Destroy a given bind.
* \param[in] bind the bind we want to destroy */
void gprs_ns2_free_bind(struct gprs_ns2_vc_bind *bind)
{
- struct gprs_ns2_vc *nsvc, *tmp;
- if (!bind)
+ struct gprs_ns2_vc *nsvc;
+ struct gprs_ns2_nse *nse;
+ if (!bind || bind->freed)
return;
+ bind->freed = true;
+
+ if (gprs_ns2_is_ip_bind(bind)) {
+ llist_for_each_entry(nse, &bind->nsi->nse, list) {
+ gprs_ns2_sns_del_bind(nse, bind);
+ }
+ }
- llist_for_each_entry_safe(nsvc, tmp, &bind->nsvc, blist) {
+ /* prevent recursive free() when the user reacts on a down event and free() a second time */
+ while (!llist_empty(&bind->nsvc)) {
+ nsvc = llist_first_entry(&bind->nsvc, struct gprs_ns2_vc, blist);
gprs_ns2_free_nsvc(nsvc);
}
@@ -1106,15 +1513,183 @@ void gprs_ns2_free_bind(struct gprs_ns2_vc_bind *bind)
bind->driver->free_bind(bind);
llist_del(&bind->list);
+ osmo_stat_item_group_free(bind->statg);
+ talloc_free((char *)bind->name);
talloc_free(bind);
}
void gprs_ns2_free_binds(struct gprs_ns2_inst *nsi)
{
- struct gprs_ns2_vc_bind *bind, *tbind;
+ struct gprs_ns2_vc_bind *bind;
- llist_for_each_entry_safe(bind, tbind, &nsi->binding, list) {
+ /* prevent recursive free() when the user reacts on a down event and free() a second time */
+ while (!llist_empty(&nsi->binding)) {
+ bind = llist_first_entry(&nsi->binding, struct gprs_ns2_vc_bind, list);
gprs_ns2_free_bind(bind);
}
}
+
+/*! Search for a bind with a unique name
+ * \param[in] nsi NS instance on which we operate
+ * \param[in] name The unique bind name to search for
+ * \return the bind or NULL if not found
+ */
+struct gprs_ns2_vc_bind *gprs_ns2_bind_by_name(struct gprs_ns2_inst *nsi, const char *name)
+{
+ struct gprs_ns2_vc_bind *bind;
+
+ llist_for_each_entry(bind, &nsi->binding, list) {
+ if (!strcmp(bind->name, name))
+ return bind;
+ }
+
+ return NULL;
+}
+
+enum gprs_ns2_vc_mode ns2_dialect_to_vc_mode(enum gprs_ns2_dialect dialect)
+{
+ switch (dialect) {
+ case GPRS_NS2_DIALECT_SNS:
+ case GPRS_NS2_DIALECT_STATIC_ALIVE:
+ return GPRS_NS2_VC_MODE_ALIVE;
+ case GPRS_NS2_DIALECT_STATIC_RESETBLOCK:
+ case GPRS_NS2_DIALECT_IPACCESS:
+ return GPRS_NS2_VC_MODE_BLOCKRESET;
+ default:
+ return -1;
+ }
+}
+
+static void add_bind_array(struct gprs_ns2_vc_bind **array,
+ struct gprs_ns2_vc_bind *bind, int size)
+{
+ int i;
+ for (i=0; i < size; i++) {
+ if (array[i] == bind)
+ return;
+ if (!array[i])
+ break;
+ }
+
+ if (i == size)
+ return;
+
+ array[i] = bind;
+}
+
+void ns2_nse_update_mtu(struct gprs_ns2_nse *nse)
+{
+ struct gprs_ns2_vc *nsvc;
+ int mtu = 0;
+
+ if (llist_empty(&nse->nsvc)) {
+ nse->mtu = 0;
+ return;
+ }
+
+ llist_for_each_entry(nsvc, &nse->nsvc, list) {
+ if (mtu == 0)
+ mtu = nsvc->bind->mtu;
+ else if (mtu > nsvc->bind->mtu)
+ mtu = nsvc->bind->mtu;
+ }
+
+ if (nse->mtu == mtu)
+ return;
+
+ nse->mtu = mtu;
+ if (nse->alive)
+ ns2_prim_status_ind(nse, NULL, 0, GPRS_NS2_AFF_CAUSE_MTU_CHANGE);
+}
+
+/*! calculate the transfer capabilities for a nse
+ * \param nse the nse to count the transfer capability
+ * \param bvci a bvci - unused
+ * \return the transfer capability in mbit. On error < 0.
+ */
+int ns2_count_transfer_cap(struct gprs_ns2_nse *nse,
+ uint16_t bvci)
+{
+ struct gprs_ns2_vc *nsvc;
+ struct gprs_ns2_vc_bind **active_binds;
+ int i, active_nsvcs = 0, transfer_cap = 0;
+
+ /* calculate the transfer capabilities based on the binds.
+ * A bind has a transfer capability which is shared across all NSVCs.
+ * Take care the bind cap is not counted twice within a NSE.
+ * This should be accurate for FR and UDP but not for FR/GRE. */
+
+ if (!nse->alive)
+ return 0;
+
+ llist_for_each_entry(nsvc, &nse->nsvc, list) {
+ if (ns2_vc_is_unblocked(nsvc))
+ active_nsvcs++;
+ }
+
+ if (!active_nsvcs)
+ return 0;
+
+ active_binds = talloc_zero_array(nse, struct gprs_ns2_vc_bind*, active_nsvcs);
+ if (!active_binds)
+ return -ENOMEM;
+
+ llist_for_each_entry(nsvc, &nse->nsvc, list) {
+ if (!ns2_vc_is_unblocked(nsvc))
+ continue;
+ add_bind_array(active_binds, nsvc->bind, active_nsvcs);
+ }
+
+ /* TODO: change calcuation for FR/GRE */
+ for (i = 0; i < active_nsvcs; i++) {
+ if (active_binds[i])
+ transfer_cap += active_binds[i]->transfer_capability;
+ }
+
+ talloc_free(active_binds);
+ return transfer_cap;
+}
+
+/*! common allocation + low-level initialization of a bind. Called by vc-drivers */
+int ns2_bind_alloc(struct gprs_ns2_inst *nsi, const char *name,
+ struct gprs_ns2_vc_bind **result)
+{
+ struct gprs_ns2_vc_bind *bind;
+
+ if (!name)
+ return -EINVAL;
+
+ if (gprs_ns2_bind_by_name(nsi, name))
+ return -EALREADY;
+
+ bind = talloc_zero(nsi, struct gprs_ns2_vc_bind);
+ if (!bind)
+ return -ENOMEM;
+
+ bind->name = talloc_strdup(bind, name);
+ if (!bind->name) {
+ talloc_free(bind);
+ return -ENOMEM;
+ }
+
+ bind->statg = osmo_stat_item_group_alloc(bind, &nsbind_statg_desc, nsi->bind_rate_ctr_idx);
+ if (!bind->statg) {
+ talloc_free(bind);
+ return -ENOMEM;
+ }
+
+ bind->sns_sig_weight = 1;
+ bind->sns_data_weight = 1;
+ bind->nsi = nsi;
+ INIT_LLIST_HEAD(&bind->nsvc);
+ llist_add_tail(&bind->list, &nsi->binding);
+
+ nsi->bind_rate_ctr_idx++;
+
+ if (result)
+ *result = bind;
+
+ return 0;
+}
+
/*! @} */
diff --git a/src/gb/gprs_ns2_fr.c b/src/gb/gprs_ns2_fr.c
new file mode 100644
index 00000000..f6bee39c
--- /dev/null
+++ b/src/gb/gprs_ns2_fr.c
@@ -0,0 +1,988 @@
+/*! \file gprs_ns2_fr.c
+ * NS-over-FR-over-GRE implementation.
+ * GPRS Networks Service (NS) messages on the Gb interface.
+ * 3GPP TS 08.16 version 8.0.1 Release 1999 / ETSI TS 101 299 V8.0.1 (2002-05)
+ * as well as its successor 3GPP TS 48.016 */
+
+/* (C) 2009-2021 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2020 sysmocom - s.f.m.c. GmbH
+ * Author: Alexander Couzens <lynxis@fe80.eu>
+ *
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <netinet/ip6.h>
+#include <arpa/inet.h>
+#include <linux/if.h>
+
+#include <sys/ioctl.h>
+#include <netpacket/packet.h>
+#include <linux/if_ether.h>
+#include <linux/hdlc.h>
+#include <linux/hdlc/ioctl.h>
+#include <linux/sockios.h>
+
+#include <osmocom/gprs/frame_relay.h>
+#include <osmocom/core/byteswap.h>
+#include <osmocom/core/stat_item.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/gprs/gprs_ns2.h>
+#include <osmocom/core/netdev.h>
+#include <osmocom/gprs/protocol/gsm_08_16.h>
+#include <osmocom/gprs/protocol/gsm_08_18.h>
+
+#include "config.h"
+#include "common_vty.h"
+#include "gprs_ns2_internal.h"
+
+#define GRE_PTYPE_FR 0x6559
+#define GRE_PTYPE_IPv4 0x0800
+#define GRE_PTYPE_IPv6 0x86dd
+#define GRE_PTYPE_KAR 0x0000 /* keepalive response */
+
+#ifndef IPPROTO_GRE
+# define IPPROTO_GRE 47
+#endif
+
+#define E1_LINERATE 2048000
+#define E1_SLOTS_TOTAL 32
+#define E1_SLOTS_USED 31
+/* usable bitrate of the E1 superchannel with 31 of 32 timeslots */
+#define SUPERCHANNEL_LINERATE (E1_LINERATE*E1_SLOTS_USED)/E1_SLOTS_TOTAL
+/* nanoseconds per bit (504) */
+#define BIT_DURATION_NS (1000000000 / SUPERCHANNEL_LINERATE)
+
+static void free_bind(struct gprs_ns2_vc_bind *bind);
+static int fr_dlci_rx_cb(void *cb_data, struct msgb *msg);
+
+struct gprs_ns2_vc_driver vc_driver_fr = {
+ .name = "GB frame relay",
+ .free_bind = free_bind,
+};
+
+struct priv_bind {
+ struct osmo_netdev *netdev;
+ char netif[IFNAMSIZ];
+ struct osmo_fr_link *link;
+ int ifindex;
+ bool if_running;
+ /* backlog queue for AF_PACKET / ENOBUFS handling (see OS#4995) */
+ struct {
+ /* file-descriptor for AF_PACKET socket */
+ struct osmo_fd ofd;
+ /* LMI bucket (we only store the last LMI message, no need to queue */
+ struct msgb *lmi_msg;
+ /* list of NS msgb (backlog) */
+ struct llist_head list;
+ /* timer to trigger next attempt of AF_PACKET write */
+ struct osmo_timer_list timer;
+ /* re-try after that many micro-seconds */
+ uint32_t retry_us;
+ } backlog;
+};
+
+struct priv_vc {
+ struct osmo_sockaddr remote;
+ uint16_t dlci;
+ struct osmo_fr_dlc *dlc;
+};
+
+static void free_vc(struct gprs_ns2_vc *nsvc)
+{
+ if (!nsvc)
+ return;
+
+ if (!nsvc->priv)
+ return;
+
+ OSMO_ASSERT(gprs_ns2_is_fr_bind(nsvc->bind));
+ talloc_free(nsvc->priv);
+ nsvc->priv = NULL;
+}
+
+static void dump_vty(const struct gprs_ns2_vc_bind *bind, struct vty *vty, bool stats)
+{
+ struct priv_bind *priv;
+ struct gprs_ns2_vc *nsvc;
+ struct osmo_fr_link *fr_link;
+
+ if (!bind)
+ return;
+
+ priv = bind->priv;
+ fr_link = priv->link;
+
+ vty_out(vty, "FR bind: %s, role: %s, link: %s%s", priv->netif,
+ osmo_fr_role_str(fr_link->role), priv->if_running ? "UP" : "DOWN", VTY_NEWLINE);
+
+ llist_for_each_entry(nsvc, &bind->nsvc, blist) {
+ ns2_vty_dump_nsvc(vty, nsvc, stats);
+ }
+
+ priv = bind->priv;
+}
+
+/*! clean up all private driver state. Should be only called by gprs_ns2_free_bind() */
+static void free_bind(struct gprs_ns2_vc_bind *bind)
+{
+ struct priv_bind *priv;
+ struct msgb *msg, *msg2;
+
+ if (!bind)
+ return;
+
+ OSMO_ASSERT(gprs_ns2_is_fr_bind(bind));
+ priv = bind->priv;
+
+ OSMO_ASSERT(llist_empty(&bind->nsvc));
+
+ osmo_timer_del(&priv->backlog.timer);
+ llist_for_each_entry_safe(msg, msg2, &priv->backlog.list, list) {
+ msgb_free(msg);
+ }
+ msgb_free(priv->backlog.lmi_msg);
+
+ osmo_netdev_free(priv->netdev);
+ osmo_fr_link_free(priv->link);
+ osmo_fd_close(&priv->backlog.ofd);
+ talloc_free(priv);
+}
+
+static void fr_dlci_status_cb(struct osmo_fr_dlc *dlc, void *cb_data, bool active)
+{
+ struct gprs_ns2_vc *nsvc = cb_data;
+
+ if (active) {
+ ns2_vc_fsm_start(nsvc);
+ } else {
+ ns2_vc_force_unconfigured(nsvc);
+ }
+}
+
+static struct priv_vc *fr_alloc_vc(struct gprs_ns2_vc_bind *bind,
+ struct gprs_ns2_vc *nsvc,
+ uint16_t dlci)
+{
+ struct priv_bind *privb = bind->priv;
+ struct priv_vc *priv = talloc_zero(bind, struct priv_vc);
+ if (!priv)
+ return NULL;
+
+ OSMO_ASSERT(gprs_ns2_is_fr_bind(bind));
+ nsvc->priv = priv;
+ priv->dlci = dlci;
+ priv->dlc = osmo_fr_dlc_alloc(privb->link, dlci);
+ if (!priv->dlc) {
+ nsvc->priv = NULL;
+ talloc_free(priv);
+ return NULL;
+ }
+
+ priv->dlc->cb_data = nsvc;
+ priv->dlc->rx_cb = fr_dlci_rx_cb;
+ priv->dlc->status_cb = fr_dlci_status_cb;
+
+ return priv;
+}
+
+int gprs_ns2_find_vc_by_dlci(struct gprs_ns2_vc_bind *bind,
+ uint16_t dlci,
+ struct gprs_ns2_vc **result)
+{
+ struct gprs_ns2_vc *nsvc;
+ struct priv_vc *vcpriv;
+
+ OSMO_ASSERT(gprs_ns2_is_fr_bind(bind));
+ if (!result)
+ return -EINVAL;
+
+ llist_for_each_entry(nsvc, &bind->nsvc, blist) {
+ vcpriv = nsvc->priv;
+ if (vcpriv->dlci != dlci) {
+ *result = nsvc;
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+/* PDU from the network interface towards the fr layer (upwards) */
+static int fr_netif_ofd_cb(struct osmo_fd *bfd, uint32_t what)
+{
+ struct gprs_ns2_vc_bind *bind = bfd->data;
+ struct priv_bind *priv = bind->priv;
+ struct msgb *msg;
+ struct sockaddr_ll sll;
+ socklen_t sll_len = sizeof(sll);
+ int rc = 0;
+
+ /* we only handle read here. write to AF_PACKET sockets cannot be triggered
+ * by select or poll, see OS#4995 */
+ if (!(what & OSMO_FD_READ))
+ return 0;
+
+ msg = msgb_alloc(NS_ALLOC_SIZE, "Gb/NS/FR Rx");
+ if (!msg)
+ return -ENOMEM;
+
+ rc = recvfrom(bfd->fd, msg->data, NS_ALLOC_SIZE, 0, (struct sockaddr *)&sll, &sll_len);
+ if (rc < 0) {
+ LOGBIND(bind, LOGL_ERROR, "recv error %s during NS-FR recv\n", strerror(errno));
+ goto out_err;
+ } else if (rc == 0) {
+ goto out_err;
+ }
+
+ /* ignore any packets that we might have received for a different interface, between
+ * the socket() and the bind() call */
+ if (sll.sll_ifindex != priv->ifindex)
+ goto out_err;
+
+ msgb_put(msg, rc);
+ msg->dst = priv->link;
+ return osmo_fr_rx(msg);
+
+out_err:
+ msgb_free(msg);
+ return rc;
+}
+
+/* PDU from the frame relay towards the NS-VC (upwards) */
+static int fr_dlci_rx_cb(void *cb_data, struct msgb *msg)
+{
+ int rc;
+ struct gprs_ns2_vc *nsvc = cb_data;
+
+ rc = ns2_recv_vc(nsvc, msg);
+
+ return rc;
+}
+
+static int fr_netif_write_one(struct gprs_ns2_vc_bind *bind, struct msgb *msg)
+{
+ struct priv_bind *priv = bind->priv;
+ unsigned int len = msgb_length(msg);
+ int rc;
+
+ /* estimate the retry time based on the data rate it takes to transmit */
+ priv->backlog.retry_us = (BIT_DURATION_NS * 8 * len) / 1000;
+
+ rc = write(priv->backlog.ofd.fd, msgb_data(msg), len);
+ if (rc == len) {
+ msgb_free(msg);
+ return 0;
+ } else if (rc < 0) {
+ /* don't free, the caller might want to re-transmit */
+ switch (errno) {
+ case EAGAIN:
+ case ENOBUFS:
+ /* not a real error, but more a normal event on AF_PACKET */
+ /* don't free the message and let the caller re-enqueue */
+ return -errno;
+ default:
+ /* an actual error, like -ENETDOWN, -EMSGSIZE */
+ LOGBIND(bind, LOGL_ERROR, "error during write to AF_PACKET: %s\n", strerror(errno));
+ msgb_free(msg);
+ return 0;
+ }
+ } else {
+ /* short write */
+ LOGBIND(bind, LOGL_ERROR, "short write on AF_PACKET: %d < %d\n", rc, len);
+ msgb_free(msg);
+ return 0;
+ }
+}
+
+/*! determine if given bind is for FR-GRE encapsulation. */
+int gprs_ns2_is_fr_bind(struct gprs_ns2_vc_bind *bind)
+{
+ return (bind->driver == &vc_driver_fr);
+}
+
+/* PDU from the NS-VC towards the frame relay layer (downwards) */
+static int fr_vc_sendmsg(struct gprs_ns2_vc *nsvc, struct msgb *msg)
+{
+ struct priv_vc *vcpriv = nsvc->priv;
+ unsigned int vc_len = msgb_length(msg);
+ int rc;
+
+ msg->dst = vcpriv->dlc;
+ rc = osmo_fr_tx_dlc(msg);
+ if (OSMO_LIKELY(rc >= 0)) {
+ RATE_CTR_INC_NS(nsvc, NS_CTR_PKTS_OUT);
+ RATE_CTR_ADD_NS(nsvc, NS_CTR_BYTES_OUT, vc_len);
+ } else {
+ RATE_CTR_INC_NS(nsvc, NS_CTR_PKTS_OUT_DROP);
+ RATE_CTR_ADD_NS(nsvc, NS_CTR_BYTES_OUT_DROP, vc_len);
+ }
+ return rc;
+}
+
+static void enqueue_at_head(struct gprs_ns2_vc_bind *bind, struct msgb *msg)
+{
+ struct priv_bind *priv = bind->priv;
+ llist_add(&msg->list, &priv->backlog.list);
+ osmo_stat_item_inc(osmo_stat_item_group_get_item(bind->statg, NS2_BIND_STAT_BACKLOG_LEN), 1);
+ osmo_timer_schedule(&priv->backlog.timer, 0, priv->backlog.retry_us);
+}
+
+static void enqueue_at_tail(struct gprs_ns2_vc_bind *bind, struct msgb *msg)
+{
+ struct priv_bind *priv = bind->priv;
+ llist_add_tail(&msg->list, &priv->backlog.list);
+ osmo_stat_item_inc(osmo_stat_item_group_get_item(bind->statg, NS2_BIND_STAT_BACKLOG_LEN), 1);
+ osmo_timer_schedule(&priv->backlog.timer, 0, priv->backlog.retry_us);
+}
+
+#define LMI_Q933A_DLCI 0
+
+/* enqueue to backlog (LMI, signaling) or drop (userdata msg) */
+static int backlog_enqueue_or_free(struct gprs_ns2_vc_bind *bind, struct msgb *msg)
+{
+ struct priv_bind *priv = bind->priv;
+ uint8_t dlci = msg->data[0];
+ uint8_t ns_pdu_type;
+ uint16_t bvci;
+
+ if (msgb_length(msg) < 1)
+ goto out_free;
+
+ /* we want to enqueue only Q.933 LMI traffic or NS signaling; NOT user traffic */
+ switch (dlci) {
+ case LMI_Q933A_DLCI:
+ /* always store only the last LMI message in the lmi_msg bucket */
+ msgb_free(priv->backlog.lmi_msg);
+ priv->backlog.lmi_msg = msg;
+ return 0;
+ default:
+ /* there's no point in trying to enqueue messages if the interface is down */
+ if (!priv->if_running)
+ break;
+
+ if (msgb_length(msg) < 3)
+ break;
+ ns_pdu_type = msg->data[2];
+ switch (ns_pdu_type) {
+ case NS_PDUT_UNITDATA:
+ if (msgb_length(msg) < 6)
+ break;
+ bvci = osmo_load16be(msg->data + 4);
+ /* enqueue BVCI=0 traffic at tail of queue */
+ if (bvci == BVCI_SIGNALLING) {
+ enqueue_at_tail(bind, msg);
+ return 0;
+ }
+ break;
+ default:
+ /* enqueue NS signaling traffic at head of queue */
+ enqueue_at_head(bind, msg);
+ return 0;
+ }
+ break;
+ }
+
+out_free:
+ /* drop everything that is not LMI, NS-signaling or BVCI-0 */
+ msgb_free(msg);
+ return -1;
+}
+
+static void fr_backlog_timer_cb(void *data)
+{
+ struct gprs_ns2_vc_bind *bind = data;
+ struct priv_bind *priv = bind->priv;
+ int i, rc;
+
+ /* first try to get rid of the LMI message, if any */
+ if (priv->backlog.lmi_msg) {
+ rc = fr_netif_write_one(bind, priv->backlog.lmi_msg);
+ if (rc < 0)
+ goto restart_timer;
+ /* fr_netif_write_one() has just free'd it */
+ priv->backlog.lmi_msg = NULL;
+ }
+
+ /* attempt to send up to 10 messages in every timer */
+ for (i = 0; i < 10; i++) {
+ struct msgb *msg = msgb_dequeue(&priv->backlog.list);
+ if (!msg)
+ break;
+
+ rc = fr_netif_write_one(bind, msg);
+ if (rc < 0) {
+ /* re-add at head of list */
+ llist_add(&msg->list, &priv->backlog.list);
+ break;
+ }
+ osmo_stat_item_dec(osmo_stat_item_group_get_item(bind->statg, NS2_BIND_STAT_BACKLOG_LEN), 1);
+ }
+
+restart_timer:
+ /* re-start timer if we still have data in the queue */
+ if (!llist_empty(&priv->backlog.list))
+ osmo_timer_schedule(&priv->backlog.timer, 0, priv->backlog.retry_us);
+}
+
+/* PDU from the frame relay layer towards the network interface (downwards) */
+int fr_tx_cb(void *data, struct msgb *msg)
+{
+ struct gprs_ns2_vc_bind *bind = data;
+ struct priv_bind *priv = bind->priv;
+ int rc;
+
+ if (llist_empty(&priv->backlog.list)) {
+ /* attempt to transmit right now */
+ rc = fr_netif_write_one(bind, msg);
+ if (rc < 0) {
+ /* enqueue to backlog in case it fails */
+ return backlog_enqueue_or_free(bind, msg);
+ }
+ } else {
+ /* enqueue to backlog */
+ return backlog_enqueue_or_free(bind, msg);
+ }
+
+ return 0;
+}
+
+static int devname2ifindex(const char *ifname)
+{
+ struct ifreq ifr;
+ int sk, rc;
+
+ sk = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
+ if (sk < 0)
+ return sk;
+
+
+ memset(&ifr, 0, sizeof(ifr));
+ OSMO_STRLCPY_ARRAY(ifr.ifr_name, ifname);
+
+ rc = ioctl(sk, SIOCGIFINDEX, &ifr);
+ close(sk);
+ if (rc < 0)
+ return rc;
+
+ return ifr.ifr_ifindex;
+}
+
+static int open_socket(int ifindex, const struct gprs_ns2_vc_bind *nsbind)
+{
+ struct sockaddr_ll addr;
+ int fd, rc;
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sll_family = AF_PACKET;
+ addr.sll_protocol = htons(ETH_P_ALL);
+ addr.sll_ifindex = ifindex;
+
+ fd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_HDLC));
+ if (fd < 0) {
+ LOGBIND(nsbind, LOGL_ERROR, "Can not create AF_PACKET socket. Are you root or have CAP_NET_RAW?\n");
+ return fd;
+ }
+
+ /* there's a race condition between the above syscall and the bind() call below,
+ * causing other packets to be received in between */
+
+ rc = bind(fd, (struct sockaddr *)&addr, sizeof(addr));
+ if (rc < 0) {
+ LOGBIND(nsbind, LOGL_ERROR, "Can not bind AF_PACKET socket to ifindex %d\n", ifindex);
+ close(fd);
+ return rc;
+ }
+
+ return fd;
+}
+
+static int gprs_n2_fr_ifupdown_ind_cb(struct osmo_netdev *netdev, bool if_running)
+{
+ struct gprs_ns2_vc_bind *bind = osmo_netdev_get_priv_data(netdev);
+ struct priv_bind *bpriv = bind->priv;
+ struct msgb *msg, *msg2;
+
+ if (bpriv->if_running == if_running)
+ return 0;
+
+ LOGBIND(bind, LOGL_NOTICE, "FR net-device '%s': Physical link state changed: %s\n",
+ bpriv->netif, if_running ? "UP" : "DOWN");
+
+ /* free any backlog, both on IFUP and IFDOWN. Keep the LMI, as it makes
+ * sense to get one out of the door ASAP. */
+ llist_for_each_entry_safe(msg, msg2, &bpriv->backlog.list, list) {
+ msgb_free(msg);
+ }
+
+ if (if_running) {
+ /* interface just came up */
+ if (bpriv->backlog.lmi_msg)
+ osmo_timer_schedule(&bpriv->backlog.timer, 0, bpriv->backlog.retry_us);
+ } else {
+ /* interface just went down; no need to retransmit */
+ osmo_timer_del(&bpriv->backlog.timer);
+ }
+
+ bpriv->if_running = if_running;
+ return 0;
+}
+
+static int gprs_n2_fr_mtu_chg_cb(struct osmo_netdev *netdev, uint32_t new_mtu)
+{
+ struct gprs_ns2_vc_bind *bind = osmo_netdev_get_priv_data(netdev);
+ struct priv_bind *bpriv = bind->priv;
+ struct gprs_ns2_nse *nse;
+
+ /* 2 byte DLCI header */
+ if (new_mtu <= 2)
+ return 0;
+ new_mtu -= 2;
+
+ if (new_mtu == bind->mtu)
+ return 0;
+
+ LOGBIND(bind, LOGL_INFO, "MTU changed from %d to %d.\n",
+ bind->mtu + 2, new_mtu + 2);
+
+ bind->mtu = new_mtu;
+ if (!bpriv->if_running)
+ return 0;
+
+ llist_for_each_entry(nse, &bind->nsi->nse, list) {
+ ns2_nse_update_mtu(nse);
+ }
+ return 0;
+}
+
+static int set_ifupdown(const char *netif, bool up)
+{
+ int sock, rc;
+ struct ifreq req;
+
+ sock = socket(AF_INET, SOCK_DGRAM, 0);
+ if (sock < 0)
+ return sock;
+
+ memset(&req, 0, sizeof req);
+ OSMO_STRLCPY_ARRAY(req.ifr_name, netif);
+
+ rc = ioctl(sock, SIOCGIFFLAGS, &req);
+ if (rc < 0) {
+ close(sock);
+ return rc;
+ }
+
+ if ((req.ifr_flags & IFF_UP) == up) {
+ close(sock);
+ return 0;
+ }
+
+ if (up)
+ req.ifr_flags |= IFF_UP;
+
+ rc = ioctl(sock, SIOCSIFFLAGS, &req);
+ close(sock);
+ return rc;
+}
+
+static int setup_device(const char *netif, const struct gprs_ns2_vc_bind *bind)
+{
+ int sock, rc;
+ char buffer[128];
+ fr_proto *fr = (void*)buffer;
+ struct ifreq req;
+
+ sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP);
+ if (sock < 0) {
+ LOGBIND(bind, LOGL_ERROR, "%s: Unable to create socket: %s\n",
+ netif, strerror(errno));
+ return sock;
+ }
+
+ memset(&req, 0, sizeof(struct ifreq));
+ memset(&buffer, 0, sizeof(buffer));
+ OSMO_STRLCPY_ARRAY(req.ifr_name, netif);
+ req.ifr_settings.ifs_ifsu.sync = (void*)buffer;
+ req.ifr_settings.size = sizeof(buffer);
+ req.ifr_settings.type = IF_GET_PROTO;
+
+ /* EINVAL is returned when no protocol has been set */
+ rc = ioctl(sock, SIOCWANDEV, &req);
+ if (rc < 0 && errno != EINVAL) {
+ LOGBIND(bind, LOGL_ERROR, "%s: Unable to get FR protocol information: %s\n",
+ netif, strerror(errno));
+ goto err;
+ }
+
+ /* check if the device is good */
+ if (rc == 0 && req.ifr_settings.type == IF_PROTO_FR && fr->lmi == LMI_NONE) {
+ LOGBIND(bind, LOGL_NOTICE, "%s: has correct frame relay mode and lmi\n", netif);
+ goto ifup;
+ }
+
+ /* modify the device to match */
+ rc = set_ifupdown(netif, false);
+ if (rc) {
+ LOGBIND(bind, LOGL_ERROR, "Unable to bring down the device %s: %s\n",
+ netif, strerror(errno));
+ goto err;
+ }
+
+ memset(&req, 0, sizeof(struct ifreq));
+ memset(fr, 0, sizeof(fr_proto));
+ OSMO_STRLCPY_ARRAY(req.ifr_name, netif);
+ req.ifr_settings.type = IF_PROTO_FR;
+ req.ifr_settings.size = sizeof(fr_proto);
+ req.ifr_settings.ifs_ifsu.fr = fr;
+ fr->lmi = LMI_NONE;
+ /* even those settings aren't used, they must be in the range */
+ /* polling verification timer*/
+ fr->t391 = 10;
+ /* link integrity verification polling timer */
+ fr->t392 = 15;
+ /* full status polling counter*/
+ fr->n391 = 6;
+ /* error threshold */
+ fr->n392 = 3;
+ /* monitored events count */
+ fr->n393 = 4;
+
+ LOGBIND(bind, LOGL_INFO, "%s: Setting frame relay related parameters\n", netif);
+ rc = ioctl(sock, SIOCWANDEV, &req);
+ if (rc) {
+ LOGBIND(bind, LOGL_ERROR, "%s: Unable to set FR protocol on information: %s\n",
+ netif, strerror(errno));
+ goto err;
+ }
+
+ifup:
+ rc = set_ifupdown(netif, true);
+ if (rc)
+ LOGBIND(bind, LOGL_ERROR, "Unable to bring up the device %s: %s\n",
+ netif, strerror(errno));
+err:
+ close(sock);
+ return rc;
+}
+
+/*! Create a new bind for NS over FR.
+ * \param[in] nsi NS instance in which to create the bind
+ * \param[in] netif Network interface to bind to
+ * \param[in] fr_network
+ * \param[in] fr_role
+ * \param[out] result pointer to the created bind or if a bind with the name exists return the bind.
+ * \return 0 on success; negative on error. -EALREADY returned in case a bind with the name exists */
+int gprs_ns2_fr_bind(struct gprs_ns2_inst *nsi,
+ const char *name,
+ const char *netif,
+ struct osmo_fr_network *fr_network,
+ enum osmo_fr_role fr_role,
+ struct gprs_ns2_vc_bind **result)
+{
+ struct gprs_ns2_vc_bind *bind;
+ struct priv_bind *priv;
+ struct osmo_fr_link *fr_link;
+ int rc = 0;
+
+ if (strlen(netif) > IFNAMSIZ)
+ return -EINVAL;
+
+ bind = gprs_ns2_bind_by_name(nsi, name);
+ if (bind) {
+ if (result)
+ *result = bind;
+ return -EALREADY;
+ }
+
+ rc = ns2_bind_alloc(nsi, name, &bind);
+ if (rc < 0)
+ return rc;
+
+ bind->driver = &vc_driver_fr;
+ bind->ll = GPRS_NS2_LL_FR;
+ /* 2 mbit */
+ bind->transfer_capability = 2;
+ bind->send_vc = fr_vc_sendmsg;
+ bind->free_vc = free_vc;
+ bind->dump_vty = dump_vty;
+ bind->mtu = FRAME_RELAY_SDU;
+ priv = bind->priv = talloc_zero(bind, struct priv_bind);
+ if (!priv) {
+ rc = -ENOMEM;
+ goto err_bind;
+ }
+
+ INIT_LLIST_HEAD(&priv->backlog.list);
+ OSMO_STRLCPY_ARRAY(priv->netif, netif);
+
+ /* FIXME: move fd handling into socket.c */
+ fr_link = osmo_fr_link_alloc(fr_network, fr_role, netif);
+ if (!fr_link) {
+ rc = -EINVAL;
+ goto err_bind;
+ }
+
+ fr_link->tx_cb = fr_tx_cb;
+ fr_link->cb_data = bind;
+ priv->link = fr_link;
+
+ priv->ifindex = rc = devname2ifindex(netif);
+ if (rc < 0) {
+ LOGBIND(bind, LOGL_ERROR, "Can not get interface index for interface %s\n", netif);
+ goto err_fr;
+ }
+
+ priv->netdev = osmo_netdev_alloc(bind, name);
+ if (!priv->netdev) {
+ rc = -ENOENT;
+ goto err_fr;
+ }
+ osmo_netdev_set_priv_data(priv->netdev, bind);
+ osmo_netdev_set_ifupdown_ind_cb(priv->netdev, gprs_n2_fr_ifupdown_ind_cb);
+ osmo_netdev_set_mtu_chg_cb(priv->netdev, gprs_n2_fr_mtu_chg_cb);
+ rc = osmo_netdev_set_ifindex(priv->netdev, priv->ifindex);
+ if (rc < 0)
+ goto err_free_netdev;
+ rc = osmo_netdev_register(priv->netdev);
+ if (rc < 0)
+ goto err_free_netdev;
+
+ /* set protocol frame relay and lmi */
+ rc = setup_device(priv->netif, bind);
+ if(rc < 0) {
+ LOGBIND(bind, LOGL_ERROR, "Failed to setup the interface %s for frame relay and lmi\n", netif);
+ goto err_free_netdev;
+ }
+
+ rc = open_socket(priv->ifindex, bind);
+ if (rc < 0)
+ goto err_free_netdev;
+ priv->backlog.retry_us = 2500; /* start with some non-zero value; this corrsponds to 496 bytes */
+ osmo_timer_setup(&priv->backlog.timer, fr_backlog_timer_cb, bind);
+ osmo_fd_setup(&priv->backlog.ofd, rc, OSMO_FD_READ, fr_netif_ofd_cb, bind, 0);
+ rc = osmo_fd_register(&priv->backlog.ofd);
+ if (rc < 0)
+ goto err_fd;
+
+ if (result)
+ *result = bind;
+
+ return rc;
+
+err_fd:
+ close(priv->backlog.ofd.fd);
+err_free_netdev:
+ osmo_netdev_free(priv->netdev);
+ priv->netdev = NULL;
+err_fr:
+ osmo_fr_link_free(fr_link);
+ priv->link = NULL;
+err_bind:
+ gprs_ns2_free_bind(bind);
+
+ return rc;
+}
+
+/*! Return the frame relay role of a bind
+ * \param[in] bind The bind
+ * \return the frame relay role or -EINVAL if bind is not frame relay
+ */
+enum osmo_fr_role gprs_ns2_fr_bind_role(struct gprs_ns2_vc_bind *bind)
+{
+ struct priv_bind *priv;
+
+ if (bind->driver != &vc_driver_fr)
+ return -EINVAL;
+
+ priv = bind->priv;
+ return priv->link->role;
+}
+
+/*! Return the network interface of the bind
+ * \param[in] bind The bind
+ * \return the network interface
+ */
+const char *gprs_ns2_fr_bind_netif(struct gprs_ns2_vc_bind *bind)
+{
+ struct priv_bind *priv;
+
+ if (bind->driver != &vc_driver_fr)
+ return NULL;
+
+ priv = bind->priv;
+ return priv->netif;
+}
+
+/*! Find NS bind for a given network interface
+ * \param[in] nsi NS instance
+ * \param[in] netif the network interface to search for
+ * \return the bind or NULL if not found
+ */
+struct gprs_ns2_vc_bind *gprs_ns2_fr_bind_by_netif(
+ struct gprs_ns2_inst *nsi,
+ const char *netif)
+{
+ struct gprs_ns2_vc_bind *bind;
+ const char *_netif;
+
+ OSMO_ASSERT(nsi);
+ OSMO_ASSERT(netif);
+
+ llist_for_each_entry(bind, &nsi->binding, list) {
+ if (!gprs_ns2_is_fr_bind(bind))
+ continue;
+
+ _netif = gprs_ns2_fr_bind_netif(bind);
+ if (!strncmp(_netif, netif, IFNAMSIZ))
+ return bind;
+ }
+
+ return NULL;
+}
+
+/*! Create, connect and activate a new FR-based NS-VC
+ * \param[in] bind bind in which the new NS-VC is to be created
+ * \param[in] nsei NSEI of the NS Entity in which the NS-VC is to be created
+ * \param[in] dlci Data Link connection identifier
+ * \return pointer to newly-allocated, connected and activated NS-VC; NULL on error */
+struct gprs_ns2_vc *gprs_ns2_fr_connect(struct gprs_ns2_vc_bind *bind,
+ struct gprs_ns2_nse *nse,
+ uint16_t nsvci,
+ uint16_t dlci)
+{
+ struct gprs_ns2_vc *nsvc = NULL;
+ struct priv_vc *priv = NULL;
+ struct priv_bind *bpriv = bind->priv;
+ char idbuf[64];
+
+ OSMO_ASSERT(gprs_ns2_is_fr_bind(bind));
+ nsvc = gprs_ns2_fr_nsvc_by_dlci(bind, dlci);
+ if (nsvc) {
+ goto err;
+ }
+
+ snprintf(idbuf, sizeof(idbuf), "NSE%05u-NSVC%05u-%s-%s-DLCI%u", nse->nsei, nsvci,
+ gprs_ns2_lltype_str(nse->ll), bpriv->netif, dlci);
+ osmo_identifier_sanitize_buf(idbuf, NULL, '_');
+ nsvc = ns2_vc_alloc(bind, nse, true, GPRS_NS2_VC_MODE_BLOCKRESET, idbuf);
+ if (!nsvc)
+ goto err;
+
+ nsvc->priv = priv = fr_alloc_vc(bind, nsvc, dlci);
+ if (!priv)
+ goto err;
+
+ nsvc->nsvci = nsvci;
+ nsvc->nsvci_is_valid = true;
+
+ return nsvc;
+
+err:
+ gprs_ns2_free_nsvc(nsvc);
+ return NULL;
+}
+
+
+/*! Create, connect and activate a new FR-based NS-VC
+ * \param[in] bind bind in which the new NS-VC is to be created
+ * \param[in] nsei NSEI of the NS Entity in which the NS-VC is to be created
+ * \param[in] dlci Data Link connection identifier
+ * \return pointer to newly-allocated, connected and activated NS-VC; NULL on error */
+struct gprs_ns2_vc *gprs_ns2_fr_connect2(struct gprs_ns2_vc_bind *bind,
+ uint16_t nsei,
+ uint16_t nsvci,
+ uint16_t dlci)
+{
+ bool created_nse = false;
+ struct gprs_ns2_vc *nsvc = NULL;
+ struct gprs_ns2_nse *nse;
+
+ OSMO_ASSERT(gprs_ns2_is_fr_bind(bind));
+ nse = gprs_ns2_nse_by_nsei(bind->nsi, nsei);
+ if (!nse) {
+ nse = gprs_ns2_create_nse(bind->nsi, nsei, GPRS_NS2_LL_FR, GPRS_NS2_DIALECT_STATIC_RESETBLOCK);
+ if (!nse)
+ return NULL;
+ created_nse = true;
+ }
+
+ nsvc = gprs_ns2_fr_connect(bind, nse, nsvci, dlci);
+ if (!nsvc)
+ goto err_nse;
+
+ return nsvc;
+
+err_nse:
+ if (created_nse)
+ gprs_ns2_free_nse(nse);
+
+ return NULL;
+}
+
+/*! Return the nsvc by dlci.
+ * \param[in] bind
+ * \param[in] dlci Data Link connection identifier
+ * \return the nsvc or NULL if not found
+ */
+struct gprs_ns2_vc *gprs_ns2_fr_nsvc_by_dlci(struct gprs_ns2_vc_bind *bind,
+ uint16_t dlci)
+{
+ struct gprs_ns2_vc *nsvc;
+ struct priv_vc *vcpriv;
+
+ OSMO_ASSERT(gprs_ns2_is_fr_bind(bind));
+ llist_for_each_entry(nsvc, &bind->nsvc, blist) {
+ vcpriv = nsvc->priv;
+
+ if (dlci == vcpriv->dlci)
+ return nsvc;
+ }
+
+ return NULL;
+}
+
+/*! Return the dlci of the nsvc
+ * \param[in] nsvc
+ * \return the dlci or 0 on error. 0 is not a valid dlci.
+ */
+uint16_t gprs_ns2_fr_nsvc_dlci(const struct gprs_ns2_vc *nsvc)
+{
+ struct priv_vc *vcpriv;
+
+ if (!nsvc->bind)
+ return 0;
+
+ if (nsvc->bind->driver != &vc_driver_fr)
+ return 0;
+
+ vcpriv = nsvc->priv;
+ return vcpriv->dlci;
+}
diff --git a/src/gb/gprs_ns2_frgre.c b/src/gb/gprs_ns2_frgre.c
index cd478d6e..b99761eb 100644
--- a/src/gb/gprs_ns2_frgre.c
+++ b/src/gb/gprs_ns2_frgre.c
@@ -180,26 +180,25 @@ static int handle_rx_gre_ipv6(struct osmo_fd *bfd, struct msgb *msg,
inner_ip6h = (struct ip6_hdr *) ((uint8_t *)greh + sizeof(*greh));
if (gre_payload_len < sizeof(*ip6hdr) + sizeof(*inner_greh)) {
- LOGP(DLNS, LOGL_ERROR, "GRE keepalive too short\n");
+ LOGBIND(bind, LOGL_ERROR, "GRE keepalive too short\n");
return -EIO;
}
if (!memcmp(&inner_ip6h->ip6_src, &ip6hdr->ip6_src, sizeof(struct in6_addr)) ||
!memcmp(&inner_ip6h->ip6_dst, &ip6hdr->ip6_dst, sizeof(struct in6_addr))) {
- LOGP(DLNS, LOGL_ERROR,
- "GRE keepalive with wrong tunnel addresses\n");
+ LOGBIND(bind, LOGL_ERROR, "GRE keepalive with wrong tunnel addresses\n");
return -EIO;
}
/* Are IPv6 extensions header are allowed in the *inner*? In the outer they are */
if (inner_ip6h->ip6_ctlun.ip6_un1.ip6_un1_nxt != IPPROTO_GRE) {
- LOGP(DLNS, LOGL_ERROR, "GRE keepalive with wrong protocol\n");
+ LOGBIND(bind, LOGL_ERROR, "GRE keepalive with wrong protocol\n");
return -EIO;
}
inner_greh = (struct gre_hdr *) ((uint8_t *)inner_ip6h + sizeof(struct ip6_hdr));
if (inner_greh->ptype != osmo_htons(GRE_PTYPE_KAR)) {
- LOGP(DLNS, LOGL_ERROR, "GRE keepalive inner GRE type != 0\n");
+ LOGBIND(bind, LOGL_ERROR, "GRE keepalive inner GRE type != 0\n");
return -EIO;
}
@@ -212,7 +211,7 @@ static int handle_rx_gre_ipv6(struct osmo_fd *bfd, struct msgb *msg,
ia6 = ip6hdr->ip6_src;
char ip6str[INET6_ADDRSTRLEN] = {};
inet_ntop(AF_INET6, &ia6, ip6str, INET6_ADDRSTRLEN);
- LOGP(DLNS, LOGL_DEBUG, "GRE keepalive from %s, responding\n", ip6str);
+ LOGBIND(bind, LOGL_DEBUG, "GRE keepalive from %s, responding\n", ip6str);
/* why does it reduce the gre_payload_len by the ipv6 header?
* make it similiar to ipv4 even this seems to be wrong */
@@ -238,25 +237,24 @@ static int handle_rx_gre_ipv4(struct osmo_fd *bfd, struct msgb *msg,
inner_iph = (struct iphdr *) ((uint8_t *)greh + sizeof(*greh));
if (gre_payload_len < inner_iph->ihl*4 + sizeof(*inner_greh)) {
- LOGP(DLNS, LOGL_ERROR, "GRE keepalive too short\n");
+ LOGBIND(bind, LOGL_ERROR, "GRE keepalive too short\n");
return -EIO;
}
if (inner_iph->saddr != iph->daddr ||
inner_iph->daddr != iph->saddr) {
- LOGP(DLNS, LOGL_ERROR,
- "GRE keepalive with wrong tunnel addresses\n");
+ LOGBIND(bind, LOGL_ERROR, "GRE keepalive with wrong tunnel addresses\n");
return -EIO;
}
if (inner_iph->protocol != IPPROTO_GRE) {
- LOGP(DLNS, LOGL_ERROR, "GRE keepalive with wrong protocol\n");
+ LOGBIND(bind, LOGL_ERROR, "GRE keepalive with wrong protocol\n");
return -EIO;
}
inner_greh = (struct gre_hdr *) ((uint8_t *)inner_iph + iph->ihl*4);
if (inner_greh->ptype != osmo_htons(GRE_PTYPE_KAR)) {
- LOGP(DLNS, LOGL_ERROR, "GRE keepalive inner GRE type != 0\n");
+ LOGBIND(bind, LOGL_ERROR, "GRE keepalive inner GRE type != 0\n");
return -EIO;
}
@@ -267,8 +265,7 @@ static int handle_rx_gre_ipv4(struct osmo_fd *bfd, struct msgb *msg,
daddr.sin_port = IPPROTO_GRE;
ia.s_addr = iph->saddr;
- LOGP(DLNS, LOGL_DEBUG, "GRE keepalive from %s, responding\n",
- inet_ntoa(ia));
+ LOGBIND(bind, LOGL_DEBUG, "GRE keepalive from %s, responding\n", inet_ntoa(ia));
/* why does it reduce the gre_payload_len by the ipv4 header? */
return sendto(priv->fd.fd, inner_greh,
@@ -277,7 +274,8 @@ static int handle_rx_gre_ipv4(struct osmo_fd *bfd, struct msgb *msg,
}
static struct msgb *read_nsfrgre_msg(struct osmo_fd *bfd, int *error,
- struct osmo_sockaddr *saddr, uint16_t *dlci)
+ struct osmo_sockaddr *saddr, uint16_t *dlci,
+ const struct gprs_ns2_vc_bind *bind)
{
struct msgb *msg = msgb_alloc(NS_ALLOC_SIZE, "Gb/NS/FR/GRE Rx");
int ret = 0;
@@ -296,8 +294,7 @@ static struct msgb *read_nsfrgre_msg(struct osmo_fd *bfd, int *error,
ret = recvfrom(bfd->fd, msg->data, NS_ALLOC_SIZE, 0,
&saddr->u.sa, &saddr_len);
if (ret < 0) {
- LOGP(DLNS, LOGL_ERROR, "recv error %s during NS-FR-GRE recv\n",
- strerror(errno));
+ LOGBIND(bind, LOGL_ERROR, "recv error %s during NS-FR-GRE recv\n", strerror(errno));
*error = ret;
goto out_err;
} else if (ret == 0) {
@@ -314,6 +311,7 @@ static struct msgb *read_nsfrgre_msg(struct osmo_fd *bfd, int *error,
break;
case AF_INET6:
ip46hdr = sizeof(struct ip6_hdr);
+ break;
default:
*error = -EIO;
goto out_err;
@@ -322,7 +320,7 @@ static struct msgb *read_nsfrgre_msg(struct osmo_fd *bfd, int *error,
/* TODO: add support for the extension headers */
if (msg->len < ip46hdr + sizeof(*greh) + 2) {
- LOGP(DLNS, LOGL_ERROR, "Short IP packet: %u bytes\n", msg->len);
+ LOGBIND(bind, LOGL_ERROR, "Short IP packet: %u bytes\n", msg->len);
*error = -EIO;
goto out_err;
}
@@ -331,7 +329,7 @@ static struct msgb *read_nsfrgre_msg(struct osmo_fd *bfd, int *error,
case AF_INET:
iph = (struct iphdr *) msg->data;
if (msg->len < (iph->ihl*4 + sizeof(*greh) + 2)) {
- LOGP(DLNS, LOGL_ERROR, "Short IP packet: %u bytes\n", msg->len);
+ LOGBIND(bind, LOGL_ERROR, "Short IP packet: %u bytes\n", msg->len);
*error = -EIO;
goto out_err;
}
@@ -341,49 +339,56 @@ static struct msgb *read_nsfrgre_msg(struct osmo_fd *bfd, int *error,
break;
}
- greh = (struct gre_hdr *) (msg->data + iph->ihl*4);
+ if (iph)
+ greh = (struct gre_hdr *) (msg->data + iph->ihl*4);
+ else
+ greh = (struct gre_hdr *) (msg->data + sizeof(struct ip6_hdr));
+
if (greh->flags) {
- LOGP(DLNS, LOGL_NOTICE, "Unknown GRE flags 0x%04x\n",
- osmo_ntohs(greh->flags));
+ LOGBIND(bind, LOGL_NOTICE, "Unknown GRE flags 0x%04x\n", osmo_ntohs(greh->flags));
}
switch (osmo_ntohs(greh->ptype)) {
case GRE_PTYPE_IPv4:
/* IPv4 messages might be GRE keepalives */
- *error = handle_rx_gre_ipv4(bfd, msg, iph, greh);
+ if (iph)
+ *error = handle_rx_gre_ipv4(bfd, msg, iph, greh);
+ else
+ *error = -EIO;
goto out_err;
break;
case GRE_PTYPE_IPv6:
- *error = handle_rx_gre_ipv6(bfd, msg, ip6h, greh);
+ if (ip6h)
+ *error = handle_rx_gre_ipv6(bfd, msg, ip6h, greh);
+ else
+ *error = -EIO;
goto out_err;
break;
case GRE_PTYPE_FR:
/* continue as usual */
break;
default:
- LOGP(DLNS, LOGL_NOTICE, "Unknown GRE protocol 0x%04x != FR\n",
- osmo_ntohs(greh->ptype));
+ LOGBIND(bind, LOGL_NOTICE, "Unknown GRE protocol 0x%04x != FR\n", osmo_ntohs(greh->ptype));
*error = -EIO;
goto out_err;
break;
}
if (msg->len < sizeof(*greh) + 2) {
- LOGP(DLNS, LOGL_ERROR, "Short FR header: %u bytes\n", msg->len);
+ LOGBIND(bind, LOGL_ERROR, "Short FR header: %u bytes\n", msg->len);
*error = -EIO;
goto out_err;
}
frh = (uint8_t *)greh + sizeof(*greh);
if (frh[0] & 0x01) {
- LOGP(DLNS, LOGL_NOTICE, "Unsupported single-byte FR address\n");
+ LOGBIND(bind, LOGL_NOTICE, "Unsupported single-byte FR address\n");
*error = -EIO;
goto out_err;
}
*dlci = ((frh[0] & 0xfc) << 2);
if ((frh[1] & 0x0f) != 0x01) {
- LOGP(DLNS, LOGL_NOTICE, "Unknown second FR octet 0x%02x\n",
- frh[1]);
+ LOGBIND(bind, LOGL_NOTICE, "Unknown second FR octet 0x%02x\n", frh[1]);
*error = -EIO;
goto out_err;
}
@@ -398,9 +403,9 @@ out_err:
return NULL;
}
-static int gprs_ns2_find_vc_by_dlci(struct gprs_ns2_vc_bind *bind,
- uint16_t dlci,
- struct gprs_ns2_vc **result)
+static int ns2_find_vc_by_dlci(struct gprs_ns2_vc_bind *bind,
+ uint16_t dlci,
+ struct gprs_ns2_vc **result)
{
struct gprs_ns2_vc *nsvc;
struct priv_vc *vcpriv;
@@ -429,35 +434,34 @@ static int handle_nsfrgre_read(struct osmo_fd *bfd)
struct msgb *reject;
uint16_t dlci;
- msg = read_nsfrgre_msg(bfd, &rc, &saddr, &dlci);
+ msg = read_nsfrgre_msg(bfd, &rc, &saddr, &dlci, bind);
if (!msg)
return rc;
if (dlci == 0 || dlci == 1023) {
- LOGP(DLNS, LOGL_INFO, "Received FR on LMI DLCI %u - ignoring\n",
- dlci);
+ LOGBIND(bind, LOGL_INFO, "Received FR on LMI DLCI %u - ignoring\n", dlci);
rc = 0;
goto out;
}
- rc = gprs_ns2_find_vc_by_dlci(bind, dlci, &nsvc);
+ rc = ns2_find_vc_by_dlci(bind, dlci, &nsvc);
if (rc) {
/* VC not found */
- rc = ns2_create_vc(bind, msg, "newconnection", &reject, &nsvc);
+ rc = ns2_create_vc(bind, msg, &saddr, "newconnection", &reject, &nsvc);
switch (rc) {
- case GPRS_NS2_CS_FOUND:
+ case NS2_CS_FOUND:
break;
- case GPRS_NS2_CS_ERROR:
- case GPRS_NS2_CS_SKIPPED:
+ case NS2_CS_ERROR:
+ case NS2_CS_SKIPPED:
rc = 0;
goto out;
- case GPRS_NS2_CS_REJECTED:
+ case NS2_CS_REJECTED:
/* nsip_sendmsg will free reject */
rc = frgre_sendmsg(bind, reject, &saddr);
goto out;
- case GPRS_NS2_CS_CREATED:
+ case NS2_CS_CREATED:
frgre_alloc_vc(bind, nsvc, &saddr, dlci);
- gprs_ns2_vc_fsm_start(nsvc);
+ ns2_vc_fsm_start(nsvc);
break;
}
}
@@ -499,6 +503,8 @@ static int frgre_vc_sendmsg(struct gprs_ns2_vc *nsvc, struct msgb *msg)
uint16_t dlci = osmo_htons(bindpriv->dlci);
uint8_t *frh;
struct gre_hdr *greh;
+ unsigned int vc_len = msgb_length(msg);
+ int rc;
/* Prepend the FR header */
frh = msgb_push(msg, 2);
@@ -510,7 +516,15 @@ static int frgre_vc_sendmsg(struct gprs_ns2_vc *nsvc, struct msgb *msg)
greh->flags = 0;
greh->ptype = osmo_htons(GRE_PTYPE_FR);
- return frgre_sendmsg(bind, msg, &vcpriv->remote);
+ rc = frgre_sendmsg(bind, msg, &vcpriv->remote);
+ if (OSMO_LIKELY(rc >= 0)) {
+ RATE_CTR_INC_NS(nsvc, NS_CTR_PKTS_OUT);
+ RATE_CTR_ADD_NS(nsvc, NS_CTR_BYTES_OUT, rc);
+ } else {
+ RATE_CTR_INC_NS(nsvc, NS_CTR_PKTS_OUT_DROP);
+ RATE_CTR_ADD_NS(nsvc, NS_CTR_BYTES_OUT_DROP, vc_len);
+ }
+ return rc;
}
static int frgre_fd_cb(struct osmo_fd *bfd, unsigned int what)
@@ -535,64 +549,66 @@ int gprs_ns2_is_frgre_bind(struct gprs_ns2_vc_bind *bind)
* \param[in] nsi NS instance in which to create the bind
* \param[in] local local address on which to bind
* \param[in] dscp DSCP/TOS bits to use for transmitted data on this bind
- * \param[out] result pointer to created bind
- * \return 0 on success; negative on error */
+ * \param[out] result pointer to the created bind or if a bind with the name exists return the bind.
+ * \return 0 on success; negative on error. -EALREADY returned in case a bind with the name exists */
int gprs_ns2_frgre_bind(struct gprs_ns2_inst *nsi,
+ const char *name,
const struct osmo_sockaddr *local,
int dscp,
struct gprs_ns2_vc_bind **result)
{
- struct gprs_ns2_vc_bind *bind = talloc_zero(nsi, struct gprs_ns2_vc_bind);
+ struct gprs_ns2_vc_bind *bind;
struct priv_bind *priv;
int rc;
- if (!bind)
- return -ENOSPC;
+ if (local->u.sa.sa_family != AF_INET && local->u.sa.sa_family != AF_INET6)
+ return -EINVAL;
- if (local->u.sa.sa_family != AF_INET && local->u.sa.sa_family != AF_INET6) {
- talloc_free(bind);
+ if (dscp < 0 || dscp > 63)
return -EINVAL;
+
+ bind = gprs_ns2_bind_by_name(nsi, name);
+ if (bind) {
+ if (result)
+ *result = bind;
+ return -EALREADY;
}
+ rc = ns2_bind_alloc(nsi, name, &bind);
+ if (rc < 0)
+ return rc;
+
bind->driver = &vc_driver_frgre;
+ bind->ll = GPRS_NS2_LL_FR_GRE;
+ /* 2 mbit transfer capability. Counting should be done different for this. */
+ bind->transfer_capability = 2;
bind->send_vc = frgre_vc_sendmsg;
bind->free_vc = free_vc;
bind->nsi = nsi;
+ /* TODO: allow to set the MTU via vty. It can not be automatic detected because it goes over an
+ * ethernet device and the MTU here must match the FR side of the FR-to-GRE gateway.
+ */
+ bind->mtu = FRAME_RELAY_SDU;
priv = bind->priv = talloc_zero(bind, struct priv_bind);
if (!priv) {
- talloc_free(bind);
- return -ENOSPC;
+ gprs_ns2_free_bind(bind);
+ return -ENOMEM;
}
priv->fd.cb = frgre_fd_cb;
priv->fd.data = bind;
priv->addr = *local;
INIT_LLIST_HEAD(&bind->nsvc);
-
- llist_add(&bind->list, &nsi->binding);
+ priv->dscp = dscp;
rc = osmo_sock_init_osa_ofd(&priv->fd, SOCK_RAW, IPPROTO_GRE,
local, NULL,
- OSMO_SOCK_F_BIND);
+ OSMO_SOCK_F_BIND | OSMO_SOCK_F_DSCP(priv->dscp));
if (rc < 0) {
- talloc_free(priv);
- talloc_free(bind);
+ gprs_ns2_free_bind(bind);
return rc;
}
- if (dscp > 0) {
- priv->dscp = dscp;
-
- rc = setsockopt(priv->fd.fd, IPPROTO_IP, IP_TOS,
- &dscp, sizeof(dscp));
- if (rc < 0)
- LOGP(DLNS, LOGL_ERROR,
- "Failed to set the DSCP to %d with ret(%d) errno(%d)\n",
- dscp, rc, errno);
- }
-
- ns2_vty_bind_apply(bind);
-
if (result)
*result = bind;
diff --git a/src/gb/gprs_ns2_internal.h b/src/gb/gprs_ns2_internal.h
index 493b3918..2e7dac3f 100644
--- a/src/gb/gprs_ns2_internal.h
+++ b/src/gb/gprs_ns2_internal.h
@@ -5,9 +5,55 @@
#include <stdbool.h>
#include <stdint.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/rate_ctr.h>
#include <osmocom/gprs/protocol/gsm_08_16.h>
#include <osmocom/gprs/gprs_ns2.h>
+#define LOGNSE(nse, lvl, fmt, args ...) \
+ LOGP(DLNS, lvl, "NSE(%05u) " fmt, (nse)->nsei, ## args)
+
+#define LOGBIND(bind, lvl, fmt, args ...) \
+ LOGP(DLNS, lvl, "BIND(%s) " fmt, (bind)->name, ## args)
+
+#define LOGNSVC_SS(ss, nsvc, lvl, fmt, args ...) \
+ do { \
+ if ((nsvc)->nsvci_is_valid) { \
+ LOGP(ss, lvl, "NSE(%05u)-NSVC(%05u) " fmt, \
+ (nsvc)->nse->nsei, (nsvc)->nsvci, ## args); \
+ } else { \
+ LOGP(ss, lvl, "NSE(%05u)-NSVC(none) " fmt, \
+ (nsvc)->nse->nsei, ## args); \
+ } \
+ } while (0)
+
+#define LOGNSVC(nsvc, lvl, fmt, args ...) \
+ LOGNSVC_SS(DLNS, nsvc, lvl, fmt, ## args)
+
+#define LOG_NS_SIGNAL(nsvc, direction, pdu_type, lvl, fmt, args ...) \
+ LOGNSVC_SS(DLNSSIGNAL, nsvc, lvl, "%s %s" fmt, direction, get_value_string(gprs_ns_pdu_strings, pdu_type), ## args)
+
+#define LOG_NS_DATA(nsvc, direction, pdu_type, lvl, fmt, args ...) \
+ LOGNSVC_SS(DLNSDATA, nsvc, lvl, "%s %s" fmt, direction, get_value_string(gprs_ns_pdu_strings, pdu_type), ## args)
+
+#define LOG_NS_RX_SIGNAL(nsvc, pdu_type) LOG_NS_SIGNAL(nsvc, "Rx", pdu_type, LOGL_INFO, "\n")
+#define LOG_NS_TX_SIGNAL(nsvc, pdu_type) LOG_NS_SIGNAL(nsvc, "Tx", pdu_type, LOGL_INFO, "\n")
+
+#define RATE_CTR_INC_NS(nsvc, ctr) \
+ do { \
+ struct gprs_ns2_vc *_nsvc = (nsvc); \
+ rate_ctr_inc(rate_ctr_group_get_ctr(_nsvc->ctrg, ctr)); \
+ rate_ctr_inc(rate_ctr_group_get_ctr(_nsvc->nse->ctrg, ctr)); \
+ } while (0)
+
+#define RATE_CTR_ADD_NS(nsvc, ctr, val) \
+ do { \
+ struct gprs_ns2_vc *_nsvc = (nsvc); \
+ rate_ctr_add(rate_ctr_group_get_ctr(_nsvc->ctrg, ctr), val); \
+ rate_ctr_add(rate_ctr_group_get_ctr(_nsvc->nse->ctrg, ctr), val); \
+ } while (0)
+
+
struct osmo_fsm_inst;
struct tlv_parsed;
struct vty;
@@ -15,10 +61,23 @@ struct vty;
struct gprs_ns2_vc_driver;
struct gprs_ns2_vc_bind;
+#define NS_TIMERS_COUNT 11
+
+#define TNS_BLOCK_STR "tns-block"
+#define TNS_BLOCK_RETRIES_STR "tns-block-retries"
+#define TNS_RESET_STR "tns-reset"
+#define TNS_RESET_RETRIES_STR "tns-reset-retries"
+#define TNS_TEST_STR "tns-test"
+#define TNS_ALIVE_STR "tns-alive"
+#define TNS_ALIVE_RETRIES_STR "tns-alive-retries"
+#define TSNS_PROV_STR "tsns-prov"
+#define TSNS_SIZE_RETRIES_STR "tsns-size-retries"
+#define TSNS_CONFIG_RETRIES_STR "tsns-config-retries"
+#define TSNS_PROCEDURES_RETRIES_STR "tsns-procedures-retries"
+#define NS_TIMERS "(" TNS_BLOCK_STR "|" TNS_BLOCK_RETRIES_STR "|" TNS_RESET_STR "|" TNS_RESET_RETRIES_STR "|" TNS_TEST_STR "|"\
+ TNS_ALIVE_STR "|" TNS_ALIVE_RETRIES_STR "|" TSNS_PROV_STR "|" TSNS_SIZE_RETRIES_STR "|" TSNS_CONFIG_RETRIES_STR "|"\
+ TSNS_PROCEDURES_RETRIES_STR ")"
-
-#define NS_TIMERS_COUNT 8
-#define NS_TIMERS "(tns-block|tns-block-retries|tns-reset|tns-reset-retries|tns-test|tns-alive|tns-alive-retries|tsns-prov)"
#define NS_TIMERS_HELP \
"(un)blocking Timer (Tns-block) timeout\n" \
"(un)blocking Timer (Tns-block) number of retries\n" \
@@ -27,12 +86,17 @@ struct gprs_ns2_vc_bind;
"Test Timer (Tns-test) timeout\n" \
"Alive Timer (Tns-alive) timeout\n" \
"Alive Timer (Tns-alive) number of retries\n" \
- "SNS Provision Timer (Tsns-prov) timeout\n"
+ "SNS Provision Timer (Tsns-prov) timeout\n" \
+ "SNS Size number of retries\n" \
+ "SNS Config number of retries\n" \
+ "SNS Procedures number of retries\n" \
/* Educated guess - LLC user payload is 1500 bytes plus possible headers */
#define NS_ALLOC_SIZE 3072
#define NS_ALLOC_HEADROOM 20
+#define NS_DEFAULT_TXQUEUE_MAX_LENGTH 128
+
enum ns2_timeout {
NS_TOUT_TNS_BLOCK,
NS_TOUT_TNS_BLOCK_RETRIES,
@@ -42,6 +106,9 @@ enum ns2_timeout {
NS_TOUT_TNS_ALIVE,
NS_TOUT_TNS_ALIVE_RETRIES,
NS_TOUT_TSNS_PROV,
+ NS_TOUT_TSNS_SIZE_RETRIES,
+ NS_TOUT_TSNS_CONFIG_RETRIES,
+ NS_TOUT_TSNS_PROCEDURES_RETRIES,
};
enum nsvc_timer_mode {
@@ -52,26 +119,40 @@ enum nsvc_timer_mode {
_NSVC_TIMER_NR,
};
-enum ns_stat {
+enum ns2_vc_stat {
NS_STAT_ALIVE_DELAY,
};
-/*! Osmocom NS link layer types */
-enum gprs_ns_ll {
- GPRS_NS_LL_UDP, /*!< NS/UDP/IP */
- GPRS_NS_LL_E1, /*!< NS/E1 */
- GPRS_NS_LL_FR_GRE, /*!< NS/FR/GRE/IP */
+enum ns2_bind_stat {
+ NS2_BIND_STAT_BACKLOG_LEN,
};
/*! Osmocom NS2 VC create status */
-enum gprs_ns2_cs {
- GPRS_NS2_CS_CREATED, /*!< A NSVC object has been created */
- GPRS_NS2_CS_FOUND, /*!< A NSVC object has been found */
- GPRS_NS2_CS_REJECTED, /*!< Rejected and answered message */
- GPRS_NS2_CS_SKIPPED, /*!< Skipped message */
- GPRS_NS2_CS_ERROR, /*!< Failed to process message */
+enum ns2_cs {
+ NS2_CS_CREATED, /*!< A NSVC object has been created */
+ NS2_CS_FOUND, /*!< A NSVC object has been found */
+ NS2_CS_REJECTED, /*!< Rejected and answered message */
+ NS2_CS_SKIPPED, /*!< Skipped message */
+ NS2_CS_ERROR, /*!< Failed to process message */
};
+enum ns_ctr {
+ NS_CTR_PKTS_IN,
+ NS_CTR_PKTS_OUT,
+ NS_CTR_PKTS_OUT_DROP,
+ NS_CTR_BYTES_IN,
+ NS_CTR_BYTES_OUT,
+ NS_CTR_BYTES_OUT_DROP,
+ NS_CTR_BLOCKED,
+ NS_CTR_UNBLOCKED,
+ NS_CTR_DEAD,
+ NS_CTR_REPLACED,
+ NS_CTR_NSEI_CHG,
+ NS_CTR_INV_VCI,
+ NS_CTR_INV_NSEI,
+ NS_CTR_LOST_ALIVE,
+ NS_CTR_LOST_RESET,
+};
#define NSE_S_BLOCKED 0x0001
#define NSE_S_ALIVE 0x0002
@@ -95,15 +176,16 @@ struct gprs_ns2_inst {
/*! linked lists of all NSVC in this instance */
struct llist_head nse;
- /*! create dynamic NSE on receiving packages */
- bool create_nse;
-
uint16_t timeout[NS_TIMERS_COUNT];
/*! workaround for rate counter until rate counter accepts char str as index */
- uint32_t rate_ctr_idx;
+ uint32_t nsvc_rate_ctr_idx;
+ uint32_t bind_rate_ctr_idx;
+
+ uint32_t txqueue_max_length;
};
+
/*! Structure repesenting a NSE. The BSS/PCU will only have a single NSE, while SGSN has one for each BSS/PCU */
struct gprs_ns2_nse {
uint16_t nsei;
@@ -117,6 +199,9 @@ struct gprs_ns2_nse {
/*! llist head to hold all nsvc */
struct llist_head nsvc;
+ /*! count all active NSVCs */
+ int nsvc_count;
+
/*! true if this NSE was created by VTY or pcu socket) */
bool persistent;
@@ -127,7 +212,34 @@ struct gprs_ns2_nse {
/*! true if this NSE has at least one alive VC */
bool alive;
+ /*! which link-layer are we based on? */
+ enum gprs_ns2_ll ll;
+
+ /*! which dialect does this NSE speaks? */
+ enum gprs_ns2_dialect dialect;
+
struct osmo_fsm_inst *bss_sns_fi;
+
+ /*! sum of all the data weight of _alive_ NS-VCs */
+ uint32_t sum_data_weight;
+
+ /*! sum of all the signalling weight of _alive_ NS-VCs */
+ uint32_t sum_sig_weight;
+
+ /*! MTU of a NS PDU. This is the lowest MTU of all NSVCs */
+ uint16_t mtu;
+
+ /*! are we implementing the SGSN role? */
+ bool ip_sns_role_sgsn;
+
+ /*! NSE-wide statistics */
+ struct rate_ctr_group *ctrg;
+
+ /*! recursive anchor */
+ bool freed;
+
+ /*! when the NSE became alive or dead */
+ struct timespec ts_alive_change;
};
/*! Structure representing a single NS-VC */
@@ -153,27 +265,40 @@ struct gprs_ns2_vc {
/*! signalling weight. 0 = don't use for signalling (BVCI == 0)*/
uint8_t sig_weight;
- /*! signaling weight. 0 = don't use for user data (BVCI != 0) */
+ /*! signalling packet counter for the load sharing function */
+ uint8_t sig_counter;
+
+ /*! data weight. 0 = don't use for user data (BVCI != 0) */
uint8_t data_weight;
/*! can be used by the bind/driver of the virtual circuit. e.g. ipv4/ipv6/frgre/e1 */
void *priv;
bool nsvci_is_valid;
+ /*! should this NS-VC only be used for SNS-SIZE and SNS-CONFIG? */
bool sns_only;
struct rate_ctr_group *ctrg;
struct osmo_stat_item_group *statg;
- /*! which link-layer are we based on? */
- enum gprs_ns_ll ll;
enum gprs_ns2_vc_mode mode;
struct osmo_fsm_inst *fi;
+
+ /*! recursive anchor */
+ bool freed;
+
+ /*! if blocked by O&M/vty */
+ bool om_blocked;
+
+ /*! when the NSVC became alive or dead */
+ struct timespec ts_alive_change;
};
/*! Structure repesenting a bind instance. E.g. IPv4 listen port. */
struct gprs_ns2_vc_bind {
+ /*! unique name */
+ const char *name;
/*! list entry in nsi */
struct llist_head list;
/*! list of all VC */
@@ -184,8 +309,17 @@ struct gprs_ns2_vc_bind {
struct gprs_ns2_inst *nsi;
struct gprs_ns2_vc_driver *driver;
- /*! if VCs use reset/block/unblock method. IP shall not use this */
- enum gprs_ns2_vc_mode vc_mode;
+ bool accept_ipaccess;
+ bool accept_sns;
+
+ /*! transfer capability in mbit */
+ int transfer_capability;
+
+ /*! MTU of a NS PDU on this bind. */
+ uint16_t mtu;
+
+ /*! which link-layer are we based on? */
+ enum gprs_ns2_ll ll;
/*! send a msg over a VC */
int (*send_vc)(struct gprs_ns2_vc *nsvc, struct msgb *msg);
@@ -196,6 +330,16 @@ struct gprs_ns2_vc_bind {
/*! allow to show information for the vty */
void (*dump_vty)(const struct gprs_ns2_vc_bind *bind,
struct vty *vty, bool stats);
+
+ /*! the IP-SNS signalling weight when doing dynamic configuration */
+ uint8_t sns_sig_weight;
+ /*! the IP-SNS data weight when doing dynamic configuration */
+ uint8_t sns_data_weight;
+
+ struct osmo_stat_item_group *statg;
+
+ /*! recursive anchor */
+ bool freed;
};
struct gprs_ns2_vc_driver {
@@ -204,8 +348,28 @@ struct gprs_ns2_vc_driver {
void (*free_bind)(struct gprs_ns2_vc_bind *driver);
};
-enum gprs_ns2_cs ns2_create_vc(struct gprs_ns2_vc_bind *bind,
+enum ns2_sns_event {
+ NS2_SNS_EV_REQ_SELECT_ENDPOINT, /*!< Select a SNS endpoint from the list */
+ NS2_SNS_EV_RX_SIZE,
+ NS2_SNS_EV_RX_SIZE_ACK,
+ NS2_SNS_EV_RX_CONFIG,
+ NS2_SNS_EV_RX_CONFIG_END, /*!< SNS-CONFIG with end flag received */
+ NS2_SNS_EV_RX_CONFIG_ACK,
+ NS2_SNS_EV_RX_ADD,
+ NS2_SNS_EV_RX_DELETE,
+ NS2_SNS_EV_RX_CHANGE_WEIGHT,
+ NS2_SNS_EV_RX_ACK, /*!< Rx of SNS-ACK (response to ADD/DELETE/CHG_WEIGHT */
+ NS2_SNS_EV_REQ_NO_NSVC, /*!< no more NS-VC remaining (all dead) */
+ NS2_SNS_EV_REQ_FREE_NSVCS, /*!< free all NS-VCs */
+ NS2_SNS_EV_REQ_NSVC_ALIVE, /*!< a NS-VC became alive */
+ NS2_SNS_EV_REQ_ADD_BIND, /*!< add a new local bind to this NSE */
+ NS2_SNS_EV_REQ_DELETE_BIND, /*!< remove a local bind from this NSE */
+ NS2_SNS_EV_REQ_CHANGE_WEIGHT, /*!< a bind changed its weight */
+};
+
+enum ns2_cs ns2_create_vc(struct gprs_ns2_vc_bind *bind,
struct msgb *msg,
+ const struct osmo_sockaddr *remote,
const char *logname,
struct msgb **reject,
struct gprs_ns2_vc **success);
@@ -215,23 +379,32 @@ int ns2_recv_vc(struct gprs_ns2_vc *nsvc,
struct gprs_ns2_vc *ns2_vc_alloc(struct gprs_ns2_vc_bind *bind,
struct gprs_ns2_nse *nse,
- bool initiater);
+ bool initiater,
+ enum gprs_ns2_vc_mode vc_mode,
+ const char *id);
+
+void ns2_free_nsvcs(struct gprs_ns2_nse *nse);
+int ns2_bind_alloc(struct gprs_ns2_inst *nsi, const char *name,
+ struct gprs_ns2_vc_bind **result);
-struct msgb *gprs_ns2_msgb_alloc(void);
+struct msgb *ns2_msgb_alloc(void);
-void gprs_ns2_sns_dump_vty(struct vty *vty, const struct gprs_ns2_nse *nse, bool stats);
+void ns2_sns_write_vty(struct vty *vty, const struct gprs_ns2_nse *nse);
+void ns2_sns_dump_vty(struct vty *vty, const char *prefix, const struct gprs_ns2_nse *nse, bool stats);
void ns2_prim_status_ind(struct gprs_ns2_nse *nse,
struct gprs_ns2_vc *nsvc,
uint16_t bvci,
enum gprs_ns2_affecting_cause cause);
void ns2_nse_notify_alive(struct gprs_ns2_vc *nsvc, bool alive);
+void ns2_nse_update_mtu(struct gprs_ns2_nse *nse);
+int ns2_nse_set_dialect(struct gprs_ns2_nse *nse, enum gprs_ns2_dialect dialect);
/* message */
-int gprs_ns2_validate(struct gprs_ns2_vc *nsvc,
- uint8_t pdu_type,
- struct msgb *msg,
- struct tlv_parsed *tp,
- uint8_t *cause);
+int ns2_validate(struct gprs_ns2_vc *nsvc,
+ uint8_t pdu_type,
+ struct msgb *msg,
+ struct tlv_parsed *tp,
+ uint8_t *cause);
/* SNS messages */
int ns2_tx_sns_ack(struct gprs_ns2_vc *nsvc, uint8_t trans_id, uint8_t *cause,
@@ -249,9 +422,28 @@ int ns2_tx_sns_size(struct gprs_ns2_vc *nsvc, bool reset_flag, uint16_t max_nr_n
int ip4_ep_nr, int ip6_ep_nr);
int ns2_tx_sns_size_ack(struct gprs_ns2_vc *nsvc, uint8_t *cause);
+int ns2_tx_sns_add(struct gprs_ns2_vc *nsvc,
+ uint8_t trans_id,
+ const struct gprs_ns_ie_ip4_elem *ip4_elems,
+ unsigned int num_ip4_elems,
+ const struct gprs_ns_ie_ip6_elem *ip6_elems,
+ unsigned int num_ip6_elems);
+int ns2_tx_sns_change_weight(struct gprs_ns2_vc *nsvc,
+ uint8_t trans_id,
+ const struct gprs_ns_ie_ip4_elem *ip4_elems,
+ unsigned int num_ip4_elems,
+ const struct gprs_ns_ie_ip6_elem *ip6_elems,
+ unsigned int num_ip6_elems);
+int ns2_tx_sns_del(struct gprs_ns2_vc *nsvc,
+ uint8_t trans_id,
+ const struct gprs_ns_ie_ip4_elem *ip4_elems,
+ unsigned int num_ip4_elems,
+ const struct gprs_ns_ie_ip6_elem *ip6_elems,
+ unsigned int num_ip6_elems);
+
/* transmit message over a VC */
-int ns2_tx_block(struct gprs_ns2_vc *nsvc, uint8_t cause);
-int ns2_tx_block_ack(struct gprs_ns2_vc *nsvc);
+int ns2_tx_block(struct gprs_ns2_vc *nsvc, uint8_t cause, uint16_t *nsvci);
+int ns2_tx_block_ack(struct gprs_ns2_vc *nsvc, uint16_t *nsvci);
int ns2_tx_reset(struct gprs_ns2_vc *nsvc, uint8_t cause);
int ns2_tx_reset_ack(struct gprs_ns2_vc *nsvc);
@@ -267,32 +459,45 @@ int ns2_tx_unit_data(struct gprs_ns2_vc *nsvc,
struct msgb *msg);
int ns2_tx_status(struct gprs_ns2_vc *nsvc, uint8_t cause,
- uint16_t bvci, struct msgb *orig_msg);
+ uint16_t bvci, struct msgb *orig_msg, uint16_t *nsvci);
/* driver */
-struct gprs_ns2_vc *gprs_ns2_ip_bind_connect(struct gprs_ns2_vc_bind *bind,
- struct gprs_ns2_nse *nse,
- const struct osmo_sockaddr *remote);
+struct gprs_ns2_vc *ns2_ip_bind_connect(struct gprs_ns2_vc_bind *bind,
+ struct gprs_ns2_nse *nse,
+ const struct osmo_sockaddr *remote);
+int ns2_ip_count_bind(struct gprs_ns2_inst *nsi, struct osmo_sockaddr *remote);
+struct gprs_ns2_vc_bind *ns2_ip_get_bind_by_index(struct gprs_ns2_inst *nsi,
+ struct osmo_sockaddr *remote,
+ int index);
+void ns2_ip_set_txqueue_max_length(struct gprs_ns2_vc_bind *bind, unsigned int max_length);
/* sns */
-int gprs_ns2_sns_rx(struct gprs_ns2_vc *nsvc, struct msgb *msg, struct tlv_parsed *tp);
+int ns2_sns_rx(struct gprs_ns2_vc *nsvc, struct msgb *msg, struct tlv_parsed *tp);
struct osmo_fsm_inst *ns2_sns_bss_fsm_alloc(struct gprs_ns2_nse *nse,
const char *id);
-int ns2_sns_bss_fsm_start(struct gprs_ns2_nse *nse, struct gprs_ns2_vc *nsvc,
- const struct osmo_sockaddr *remote);
-void ns2_sns_free_nsvc(struct gprs_ns2_vc *nsvc);
+struct osmo_fsm_inst *ns2_sns_sgsn_fsm_alloc(struct gprs_ns2_nse *nse, const char *id);
+void ns2_sns_replace_nsvc(struct gprs_ns2_vc *nsvc);
+void ns2_sns_notify_alive(struct gprs_ns2_nse *nse, struct gprs_ns2_vc *nsvc, bool alive);
+void ns2_sns_update_weights(struct gprs_ns2_vc_bind *bind);
/* vc */
-struct osmo_fsm_inst *gprs_ns2_vc_fsm_alloc(struct gprs_ns2_vc *nsvc,
+struct osmo_fsm_inst *ns2_vc_fsm_alloc(struct gprs_ns2_vc *nsvc,
const char *id, bool initiate);
-int gprs_ns2_vc_fsm_start(struct gprs_ns2_vc *nsvc);
-int gprs_ns2_vc_force_unconfigured(struct gprs_ns2_vc *nsvc);
-int gprs_ns2_vc_rx(struct gprs_ns2_vc *nsvc, struct msgb *msg, struct tlv_parsed *tp);
-int gprs_ns2_vc_is_alive(struct gprs_ns2_vc *nsvc);
-int gprs_ns2_vc_is_unblocked(struct gprs_ns2_vc *nsvc);
-
-/* vty.c */
-void ns2_vty_bind_apply(struct gprs_ns2_vc_bind *bind);
+int ns2_vc_fsm_start(struct gprs_ns2_vc *nsvc);
+int ns2_vc_force_unconfigured(struct gprs_ns2_vc *nsvc);
+int ns2_vc_rx(struct gprs_ns2_vc *nsvc, struct msgb *msg, struct tlv_parsed *tp);
+int ns2_vc_is_alive(struct gprs_ns2_vc *nsvc);
+int ns2_vc_is_unblocked(struct gprs_ns2_vc *nsvc);
+int ns2_vc_block(struct gprs_ns2_vc *nsvc);
+int ns2_vc_reset(struct gprs_ns2_vc *nsvc);
+int ns2_vc_unblock(struct gprs_ns2_vc *nsvc);
+void ns2_vty_dump_nsvc(struct vty *vty, struct gprs_ns2_vc *nsvc, bool stats);
/* nse */
void ns2_nse_notify_unblocked(struct gprs_ns2_vc *nsvc, bool unblocked);
+enum gprs_ns2_vc_mode ns2_dialect_to_vc_mode(enum gprs_ns2_dialect dialect);
+int ns2_count_transfer_cap(struct gprs_ns2_nse *nse,
+ uint16_t bvci);
+
+/* vty */
+int ns2_sns_add_sns_default_binds(struct gprs_ns2_nse *nse);
diff --git a/src/gb/gprs_ns2_message.c b/src/gb/gprs_ns2_message.c
index fac6108c..de63b7aa 100644
--- a/src/gb/gprs_ns2_message.c
+++ b/src/gb/gprs_ns2_message.c
@@ -43,30 +43,13 @@
do { \
if (!nsvc->nse->bss_sns_fi) \
break; \
- LOGP(DLNS, LOGL_DEBUG, "NSEI=%u Rx invalid packet %s with SNS\n", \
- nsvc->nse->nsei, reason); \
+ LOGNSVC(nsvc, LOGL_DEBUG, "invalid packet %s with SNS\n", reason); \
} while (0)
-enum ns_ctr {
- NS_CTR_PKTS_IN,
- NS_CTR_PKTS_OUT,
- NS_CTR_BYTES_IN,
- NS_CTR_BYTES_OUT,
- NS_CTR_BLOCKED,
- NS_CTR_DEAD,
- NS_CTR_REPLACED,
- NS_CTR_NSEI_CHG,
- NS_CTR_INV_VCI,
- NS_CTR_INV_NSEI,
- NS_CTR_LOST_ALIVE,
- NS_CTR_LOST_RESET,
-};
-
-
-
-static int gprs_ns2_validate_reset(struct gprs_ns2_vc *nsvc, struct msgb *msg, struct tlv_parsed *tp, uint8_t *cause)
+static int ns2_validate_reset(struct gprs_ns2_vc *nsvc, struct msgb *msg, struct tlv_parsed *tp, uint8_t *cause)
{
- if (!TLVP_PRESENT(tp, NS_IE_CAUSE) || !TLVP_PRESENT(tp, NS_IE_VCI) || !TLVP_PRESENT(tp, NS_IE_NSEI)) {
+ if (!TLVP_PRES_LEN(tp, NS_IE_CAUSE, 1) ||
+ !TLVP_PRES_LEN(tp, NS_IE_VCI, 2) || !TLVP_PRES_LEN(tp, NS_IE_NSEI, 2)) {
*cause = NS_CAUSE_MISSING_ESSENT_IE;
return -1;
}
@@ -74,9 +57,9 @@ static int gprs_ns2_validate_reset(struct gprs_ns2_vc *nsvc, struct msgb *msg, s
return 0;
}
-static int gprs_ns2_validate_reset_ack(struct gprs_ns2_vc *nsvc, struct msgb *msg, struct tlv_parsed *tp, uint8_t *cause)
+static int ns2_validate_reset_ack(struct gprs_ns2_vc *nsvc, struct msgb *msg, struct tlv_parsed *tp, uint8_t *cause)
{
- if (!TLVP_PRESENT(tp, NS_IE_VCI) || !TLVP_PRESENT(tp, NS_IE_NSEI)) {
+ if (!TLVP_PRES_LEN(tp, NS_IE_VCI, 2) || !TLVP_PRES_LEN(tp, NS_IE_NSEI, 2)) {
*cause = NS_CAUSE_MISSING_ESSENT_IE;
return -1;
}
@@ -84,9 +67,9 @@ static int gprs_ns2_validate_reset_ack(struct gprs_ns2_vc *nsvc, struct msgb *ms
return 0;
}
-static int gprs_ns2_validate_block(struct gprs_ns2_vc *nsvc, struct msgb *msg, struct tlv_parsed *tp, uint8_t *cause)
+static int ns2_validate_block(struct gprs_ns2_vc *nsvc, struct msgb *msg, struct tlv_parsed *tp, uint8_t *cause)
{
- if (!TLVP_PRESENT(tp, NS_IE_VCI) || !TLVP_PRESENT(tp, NS_IE_CAUSE)) {
+ if (!TLVP_PRES_LEN(tp, NS_IE_VCI, 2) || !TLVP_PRES_LEN(tp, NS_IE_CAUSE, 1)) {
*cause = NS_CAUSE_MISSING_ESSENT_IE;
return -1;
}
@@ -94,9 +77,9 @@ static int gprs_ns2_validate_block(struct gprs_ns2_vc *nsvc, struct msgb *msg, s
return 0;
}
-static int gprs_ns2_validate_block_ack(struct gprs_ns2_vc *nsvc, struct msgb *msg, struct tlv_parsed *tp, uint8_t *cause)
+static int ns2_validate_block_ack(struct gprs_ns2_vc *nsvc, struct msgb *msg, struct tlv_parsed *tp, uint8_t *cause)
{
- if (!TLVP_PRESENT(tp, NS_IE_VCI)) {
+ if (!TLVP_PRES_LEN(tp, NS_IE_VCI, 2)) {
*cause = NS_CAUSE_MISSING_ESSENT_IE;
return -1;
}
@@ -104,42 +87,46 @@ static int gprs_ns2_validate_block_ack(struct gprs_ns2_vc *nsvc, struct msgb *ms
return 0;
}
-static int gprs_ns2_validate_status(struct gprs_ns2_vc *nsvc, struct msgb *msg, struct tlv_parsed *tp, uint8_t *cause)
+static int ns2_validate_status(struct gprs_ns2_vc *nsvc, struct msgb *msg, struct tlv_parsed *tp, uint8_t *cause)
{
- if (!TLVP_PRESENT(tp, NS_IE_CAUSE)) {
+ if (!TLVP_PRES_LEN(tp, NS_IE_CAUSE, 1)) {
*cause = NS_CAUSE_MISSING_ESSENT_IE;
return -1;
}
- uint8_t _cause = tlvp_val8(tp, NS_IE_VCI, 0);
-
+ uint8_t _cause = tlvp_val8(tp, NS_IE_CAUSE, 0);
switch (_cause) {
case NS_CAUSE_NSVC_BLOCKED:
case NS_CAUSE_NSVC_UNKNOWN:
- if (!TLVP_PRESENT(tp, NS_IE_CAUSE)) {
+ if (!TLVP_PRES_LEN(tp, NS_IE_VCI, 1)) {
*cause = NS_CAUSE_MISSING_ESSENT_IE;
return -1;
}
+
+ if (nsvc->mode != GPRS_NS2_VC_MODE_BLOCKRESET) {
+ *cause = NS_CAUSE_PDU_INCOMP_PSTATE;
+ return -1;
+ }
break;
case NS_CAUSE_SEM_INCORR_PDU:
case NS_CAUSE_PDU_INCOMP_PSTATE:
case NS_CAUSE_PROTO_ERR_UNSPEC:
case NS_CAUSE_INVAL_ESSENT_IE:
case NS_CAUSE_MISSING_ESSENT_IE:
- if (!TLVP_PRESENT(tp, NS_IE_CAUSE)) {
+ if (!TLVP_PRES_LEN(tp, NS_IE_PDU, 1)) {
*cause = NS_CAUSE_MISSING_ESSENT_IE;
return -1;
}
break;
case NS_CAUSE_BVCI_UNKNOWN:
- if (!TLVP_PRESENT(tp, NS_IE_BVCI)) {
+ if (!TLVP_PRES_LEN(tp, NS_IE_BVCI, 2)) {
*cause = NS_CAUSE_MISSING_ESSENT_IE;
return -1;
}
break;
case NS_CAUSE_UNKN_IP_TEST_FAILED:
- if (!TLVP_PRESENT (tp, NS_IE_IPv4_LIST) && !TLVP_PRESENT(tp, NS_IE_IPv6_LIST)) {
+ if (!TLVP_PRESENT(tp, NS_IE_IPv4_LIST) && !TLVP_PRESENT(tp, NS_IE_IPv6_LIST)) {
*cause = NS_CAUSE_MISSING_ESSENT_IE;
return -1;
}
@@ -149,23 +136,23 @@ static int gprs_ns2_validate_status(struct gprs_ns2_vc *nsvc, struct msgb *msg,
return 0;
}
-int gprs_ns2_validate(struct gprs_ns2_vc *nsvc,
- uint8_t pdu_type,
- struct msgb *msg,
- struct tlv_parsed *tp,
- uint8_t *cause)
+int ns2_validate(struct gprs_ns2_vc *nsvc,
+ uint8_t pdu_type,
+ struct msgb *msg,
+ struct tlv_parsed *tp,
+ uint8_t *cause)
{
switch (pdu_type) {
case NS_PDUT_RESET:
- return gprs_ns2_validate_reset(nsvc, msg, tp, cause);
+ return ns2_validate_reset(nsvc, msg, tp, cause);
case NS_PDUT_RESET_ACK:
- return gprs_ns2_validate_reset_ack(nsvc, msg, tp, cause);
+ return ns2_validate_reset_ack(nsvc, msg, tp, cause);
case NS_PDUT_BLOCK:
- return gprs_ns2_validate_block(nsvc, msg, tp, cause);
+ return ns2_validate_block(nsvc, msg, tp, cause);
case NS_PDUT_BLOCK_ACK:
- return gprs_ns2_validate_block_ack(nsvc, msg, tp, cause);
+ return ns2_validate_block_ack(nsvc, msg, tp, cause);
case NS_PDUT_STATUS:
- return gprs_ns2_validate_status(nsvc, msg, tp, cause);
+ return ns2_validate_status(nsvc, msg, tp, cause);
/* following PDUs doesn't have any payloads */
case NS_PDUT_ALIVE:
@@ -182,13 +169,18 @@ int gprs_ns2_validate(struct gprs_ns2_vc *nsvc,
return 0;
}
+static int ns_vc_tx(struct gprs_ns2_vc *nsvc, struct msgb *msg)
+{
+ return nsvc->bind->send_vc(nsvc, msg);
+}
/* transmit functions */
static int ns2_tx_simple(struct gprs_ns2_vc *nsvc, uint8_t pdu_type)
{
- struct msgb *msg = gprs_ns2_msgb_alloc();
+ struct msgb *msg = ns2_msgb_alloc();
struct gprs_ns_hdr *nsh;
+ log_set_context(LOG_CTX_GB_NSE, nsvc->nse);
log_set_context(LOG_CTX_GB_NSVC, nsvc);
if (!msg)
@@ -196,77 +188,87 @@ static int ns2_tx_simple(struct gprs_ns2_vc *nsvc, uint8_t pdu_type)
msg->l2h = msgb_put(msg, sizeof(*nsh));
nsh = (struct gprs_ns_hdr *) msg->l2h;
-
nsh->pdu_type = pdu_type;
- return nsvc->bind->send_vc(nsvc, msg);
+ LOG_NS_TX_SIGNAL(nsvc, nsh->pdu_type);
+ return ns_vc_tx(nsvc, msg);
}
/*! Transmit a NS-BLOCK on a given NS-VC.
* \param[in] vc NS-VC on which the NS-BLOCK is to be transmitted
* \param[in] cause Numeric NS Cause value
+ * \param[in] nsvci if given this NSVCI will be encoded. If NULL the nsvc->nsvci will be used.
* \returns 0 in case of success */
-int ns2_tx_block(struct gprs_ns2_vc *nsvc, uint8_t cause)
+int ns2_tx_block(struct gprs_ns2_vc *nsvc, uint8_t cause, uint16_t *nsvci)
{
struct msgb *msg;
struct gprs_ns_hdr *nsh;
- uint16_t nsvci = osmo_htons(nsvc->nsvci);
+ uint16_t encoded_nsvci;
+
+ if (nsvci)
+ encoded_nsvci = osmo_htons(*nsvci);
+ else
+ encoded_nsvci = osmo_htons(nsvc->nsvci);
+ log_set_context(LOG_CTX_GB_NSE, nsvc->nse);
log_set_context(LOG_CTX_GB_NSVC, nsvc);
ERR_IF_NSVC_USES_SNS(nsvc, "transmit NS BLOCK");
- msg = gprs_ns2_msgb_alloc();
+ msg = ns2_msgb_alloc();
if (!msg)
return -ENOMEM;
- LOGP(DLNS, LOGL_INFO, "NSEI=%u Tx NS BLOCK (NSVCI=%u, cause=%s)\n",
- nsvc->nse->nsei, nsvc->nsvci, gprs_ns2_cause_str(cause));
-
- rate_ctr_inc(&nsvc->ctrg->ctr[NS_CTR_BLOCKED]);
+ rate_ctr_inc(rate_ctr_group_get_ctr(nsvc->ctrg, NS_CTR_BLOCKED));
msg->l2h = msgb_put(msg, sizeof(*nsh));
nsh = (struct gprs_ns_hdr *) msg->l2h;
nsh->pdu_type = NS_PDUT_BLOCK;
msgb_tvlv_put(msg, NS_IE_CAUSE, 1, &cause);
- msgb_tvlv_put(msg, NS_IE_VCI, 2, (uint8_t *) &nsvci);
+ msgb_tvlv_put(msg, NS_IE_VCI, 2, (uint8_t *) &encoded_nsvci);
- return nsvc->bind->send_vc(nsvc, msg);
+ LOG_NS_SIGNAL(nsvc, "Tx", nsh->pdu_type, LOGL_INFO, " cause=%s\n", gprs_ns2_cause_str(cause));
+ return ns_vc_tx(nsvc, msg);
}
/*! Transmit a NS-BLOCK-ACK on a given NS-VC.
* \param[in] nsvc NS-VC on which the NS-BLOCK is to be transmitted
+ * \param[in] nsvci if given this NSVCI will be encoded. If NULL the nsvc->nsvci will be used.
* \returns 0 in case of success */
-int ns2_tx_block_ack(struct gprs_ns2_vc *nsvc)
+int ns2_tx_block_ack(struct gprs_ns2_vc *nsvc, uint16_t *nsvci)
{
struct msgb *msg;
struct gprs_ns_hdr *nsh;
- uint16_t nsvci = osmo_htons(nsvc->nsvci);
+ uint16_t encoded_nsvci;
+
+ if (nsvci)
+ encoded_nsvci = osmo_htons(*nsvci);
+ else
+ encoded_nsvci = osmo_htons(nsvc->nsvci);
+ log_set_context(LOG_CTX_GB_NSE, nsvc->nse);
log_set_context(LOG_CTX_GB_NSVC, nsvc);
ERR_IF_NSVC_USES_SNS(nsvc, "transmit NS BLOCK ACK");
- msg = gprs_ns2_msgb_alloc();
+ msg = ns2_msgb_alloc();
if (!msg)
return -ENOMEM;
- LOGP(DLNS, LOGL_INFO, "NSEI=%u Tx NS BLOCK ACK (NSVCI=%u)\n", nsvc->nse->nsei, nsvc->nsvci);
-
- /* be conservative and mark it as blocked even now! */
msg->l2h = msgb_put(msg, sizeof(*nsh));
nsh = (struct gprs_ns_hdr *) msg->l2h;
nsh->pdu_type = NS_PDUT_BLOCK_ACK;
- msgb_tvlv_put(msg, NS_IE_VCI, 2, (uint8_t *) &nsvci);
+ msgb_tvlv_put(msg, NS_IE_VCI, 2, (uint8_t *) &encoded_nsvci);
- return nsvc->bind->send_vc(nsvc, msg);
+ LOG_NS_TX_SIGNAL(nsvc, nsh->pdu_type);
+ return ns_vc_tx(nsvc, msg);
}
/*! Transmit a NS-RESET on a given NS-VC.
* \param[in] nsvc NS-VC used for transmission
- * \paam[in] cause Numeric NS cause value
+ * \param[in] cause Numeric NS cause value
* \returns 0 in case of success */
int ns2_tx_reset(struct gprs_ns2_vc *nsvc, uint8_t cause)
{
@@ -275,17 +277,15 @@ int ns2_tx_reset(struct gprs_ns2_vc *nsvc, uint8_t cause)
uint16_t nsvci = osmo_htons(nsvc->nsvci);
uint16_t nsei = osmo_htons(nsvc->nse->nsei);
+ log_set_context(LOG_CTX_GB_NSE, nsvc->nse);
log_set_context(LOG_CTX_GB_NSVC, nsvc);
ERR_IF_NSVC_USES_SNS(nsvc, "transmit NS RESET");
- msg = gprs_ns2_msgb_alloc();
+ msg = ns2_msgb_alloc();
if (!msg)
return -ENOMEM;
- LOGP(DLNS, LOGL_INFO, "NSEI=%u Tx NS RESET (NSVCI=%u, cause=%s)\n",
- nsvc->nse->nsei, nsvc->nsvci, gprs_ns2_cause_str(cause));
-
msg->l2h = msgb_put(msg, sizeof(*nsh));
nsh = (struct gprs_ns_hdr *) msg->l2h;
nsh->pdu_type = NS_PDUT_RESET;
@@ -294,7 +294,8 @@ int ns2_tx_reset(struct gprs_ns2_vc *nsvc, uint8_t cause)
msgb_tvlv_put(msg, NS_IE_VCI, 2, (uint8_t *) &nsvci);
msgb_tvlv_put(msg, NS_IE_NSEI, 2, (uint8_t *) &nsei);
- return nsvc->bind->send_vc(nsvc, msg);
+ LOG_NS_SIGNAL(nsvc, "Tx", nsh->pdu_type, LOGL_INFO, " cause=%s\n", gprs_ns2_cause_str(cause));
+ return ns_vc_tx(nsvc, msg);
}
/*! Transmit a NS-RESET-ACK on a given NS-VC.
@@ -307,11 +308,12 @@ int ns2_tx_reset_ack(struct gprs_ns2_vc *nsvc)
uint16_t nsvci, nsei;
/* Section 9.2.6 */
+ log_set_context(LOG_CTX_GB_NSE, nsvc->nse);
log_set_context(LOG_CTX_GB_NSVC, nsvc);
ERR_IF_NSVC_USES_SNS(nsvc, "transmit NS RESET ACK");
- msg = gprs_ns2_msgb_alloc();
+ msg = ns2_msgb_alloc();
if (!msg)
return -ENOMEM;
@@ -323,13 +325,11 @@ int ns2_tx_reset_ack(struct gprs_ns2_vc *nsvc)
nsh->pdu_type = NS_PDUT_RESET_ACK;
- LOGP(DLNS, LOGL_INFO, "NSEI=%u Tx NS RESET ACK (NSVCI=%u)\n",
- nsvc->nse->nsei, nsvc->nsvci);
-
msgb_tvlv_put(msg, NS_IE_VCI, 2, (uint8_t *)&nsvci);
msgb_tvlv_put(msg, NS_IE_NSEI, 2, (uint8_t *)&nsei);
- return nsvc->bind->send_vc(nsvc, msg);
+ LOG_NS_TX_SIGNAL(nsvc, nsh->pdu_type);
+ return ns_vc_tx(nsvc, msg);
}
/*! Transmit a NS-UNBLOCK on a given NS-VC.
@@ -337,13 +337,11 @@ int ns2_tx_reset_ack(struct gprs_ns2_vc *nsvc)
* \returns 0 in case of success */
int ns2_tx_unblock(struct gprs_ns2_vc *nsvc)
{
+ log_set_context(LOG_CTX_GB_NSE, nsvc->nse);
log_set_context(LOG_CTX_GB_NSVC, nsvc);
ERR_IF_NSVC_USES_SNS(nsvc, "transmit NS UNBLOCK");
- LOGP(DLNS, LOGL_INFO, "NSEI=%u Tx NS UNBLOCK (NSVCI=%u)\n",
- nsvc->nse->nsei, nsvc->nsvci);
-
return ns2_tx_simple(nsvc, NS_PDUT_UNBLOCK);
}
@@ -353,13 +351,11 @@ int ns2_tx_unblock(struct gprs_ns2_vc *nsvc)
* \returns 0 in case of success */
int ns2_tx_unblock_ack(struct gprs_ns2_vc *nsvc)
{
+ log_set_context(LOG_CTX_GB_NSE, nsvc->nse);
log_set_context(LOG_CTX_GB_NSVC, nsvc);
ERR_IF_NSVC_USES_SNS(nsvc, "transmit NS UNBLOCK ACK");
- LOGP(DLNS, LOGL_INFO, "NSEI=%u Tx NS UNBLOCK (NSVCI=%u)\n",
- nsvc->nse->nsei, nsvc->nsvci);
-
return ns2_tx_simple(nsvc, NS_PDUT_UNBLOCK_ACK);
}
@@ -368,9 +364,8 @@ int ns2_tx_unblock_ack(struct gprs_ns2_vc *nsvc)
* \returns 0 in case of success */
int ns2_tx_alive(struct gprs_ns2_vc *nsvc)
{
+ log_set_context(LOG_CTX_GB_NSE, nsvc->nse);
log_set_context(LOG_CTX_GB_NSVC, nsvc);
- LOGP(DLNS, LOGL_DEBUG, "NSEI=%u Tx NS ALIVE (NSVCI=%u)\n",
- nsvc->nse->nsei, nsvc->nsvci);
return ns2_tx_simple(nsvc, NS_PDUT_ALIVE);
}
@@ -380,9 +375,8 @@ int ns2_tx_alive(struct gprs_ns2_vc *nsvc)
* \returns 0 in case of success */
int ns2_tx_alive_ack(struct gprs_ns2_vc *nsvc)
{
+ log_set_context(LOG_CTX_GB_NSE, nsvc->nse);
log_set_context(LOG_CTX_GB_NSVC, nsvc);
- LOGP(DLNS, LOGL_DEBUG, "NSEI=%u Tx NS ALIVE_ACK (NSVCI=%u)\n",
- nsvc->nse->nsei, nsvc->nsvci);
return ns2_tx_simple(nsvc, NS_PDUT_ALIVE_ACK);
}
@@ -399,12 +393,13 @@ int ns2_tx_unit_data(struct gprs_ns2_vc *nsvc,
{
struct gprs_ns_hdr *nsh;
+ log_set_context(LOG_CTX_GB_NSE, nsvc->nse);
log_set_context(LOG_CTX_GB_NSVC, nsvc);
msg->l2h = msgb_push(msg, sizeof(*nsh) + 3);
nsh = (struct gprs_ns_hdr *) msg->l2h;
if (!nsh) {
- LOGP(DLNS, LOGL_ERROR, "Not enough headroom for NS header\n");
+ LOGNSVC(nsvc, LOGL_ERROR, "Not enough headroom for NS header\n");
msgb_free(msg);
return -EIO;
}
@@ -414,7 +409,8 @@ int ns2_tx_unit_data(struct gprs_ns2_vc *nsvc,
nsh->data[1] = bvci >> 8;
nsh->data[2] = bvci & 0xff;
- return nsvc->bind->send_vc(nsvc, msg);
+ LOG_NS_DATA(nsvc, "Tx", nsh->pdu_type, LOGL_INFO, "\n");
+ return ns_vc_tx(nsvc, msg);
}
/*! Transmit a NS-STATUS on a given NS-VC.
@@ -422,14 +418,17 @@ int ns2_tx_unit_data(struct gprs_ns2_vc *nsvc,
* \param[in] cause Numeric NS cause value
* \param[in] bvci BVCI to be reset within NSVC
* \param[in] orig_msg message causing the STATUS
+ * \param[in] nsvci if given this NSVCI will be encoded. If NULL the nsvc->nsvci will be used.
* \returns 0 in case of success */
int ns2_tx_status(struct gprs_ns2_vc *nsvc, uint8_t cause,
- uint16_t bvci, struct msgb *orig_msg)
+ uint16_t bvci, struct msgb *orig_msg, uint16_t *nsvci)
{
- struct msgb *msg = gprs_ns2_msgb_alloc();
+ struct msgb *msg = ns2_msgb_alloc();
struct gprs_ns_hdr *nsh;
- uint16_t nsvci = osmo_htons(nsvc->nsvci);
+ uint16_t encoded_nsvci;
+ unsigned int orig_len, max_orig_len;
+ log_set_context(LOG_CTX_GB_NSE, nsvc->nse);
log_set_context(LOG_CTX_GB_NSVC, nsvc);
bvci = osmo_htons(bvci);
@@ -437,39 +436,162 @@ int ns2_tx_status(struct gprs_ns2_vc *nsvc, uint8_t cause,
if (!msg)
return -ENOMEM;
- LOGP(DLNS, LOGL_NOTICE, "NSEI=%u Tx NS STATUS (NSVCI=%u, cause=%s)\n",
- nsvc->nse->nsei, nsvc->nsvci, gprs_ns2_cause_str(cause));
-
msg->l2h = msgb_put(msg, sizeof(*nsh));
nsh = (struct gprs_ns_hdr *) msg->l2h;
nsh->pdu_type = NS_PDUT_STATUS;
msgb_tvlv_put(msg, NS_IE_CAUSE, 1, &cause);
- /* Section 9.2.7.1: Static conditions for NS-VCI */
- if (cause == NS_CAUSE_NSVC_BLOCKED ||
- cause == NS_CAUSE_NSVC_UNKNOWN)
- msgb_tvlv_put(msg, NS_IE_VCI, 2, (uint8_t *)&nsvci);
-
- /* Section 9.2.7.2: Static conditions for NS PDU */
switch (cause) {
+ case NS_CAUSE_NSVC_BLOCKED:
+ case NS_CAUSE_NSVC_UNKNOWN:
+ /* Section 9.2.7.1: Static conditions for NS-VCI */
+ if (nsvci)
+ encoded_nsvci = osmo_htons(*nsvci);
+ else
+ encoded_nsvci = osmo_htons(nsvc->nsvci);
+ msgb_tvlv_put(msg, NS_IE_VCI, 2, (uint8_t *)&encoded_nsvci);
+ break;
case NS_CAUSE_SEM_INCORR_PDU:
case NS_CAUSE_PDU_INCOMP_PSTATE:
case NS_CAUSE_PROTO_ERR_UNSPEC:
case NS_CAUSE_INVAL_ESSENT_IE:
case NS_CAUSE_MISSING_ESSENT_IE:
- msgb_tvlv_put(msg, NS_IE_PDU, msgb_l2len(orig_msg),
- orig_msg->l2h);
+ /* Section 9.2.7.2: Static conditions for NS PDU */
+ /* ensure the PDU doesn't exceed the MTU */
+ orig_len = msgb_l2len(orig_msg);
+ max_orig_len = msgb_length(msg) + TVLV_GROSS_LEN(orig_len);
+ if (max_orig_len > nsvc->bind->mtu)
+ orig_len -= max_orig_len - nsvc->bind->mtu;
+ msgb_tvlv_put(msg, NS_IE_PDU, orig_len, orig_msg->l2h);
break;
+ case NS_CAUSE_BVCI_UNKNOWN:
+ /* Section 9.2.7.3: Static conditions for BVCI */
+ msgb_tvlv_put(msg, NS_IE_VCI, 2, (uint8_t *)&bvci);
+ break;
+
default:
break;
}
- /* Section 9.2.7.3: Static conditions for BVCI */
- if (cause == NS_CAUSE_BVCI_UNKNOWN)
- msgb_tvlv_put(msg, NS_IE_VCI, 2, (uint8_t *)&bvci);
+ LOG_NS_SIGNAL(nsvc, "Tx", nsh->pdu_type, LOGL_INFO, " cause=%s\n", gprs_ns2_cause_str(cause));
+ return ns_vc_tx(nsvc, msg);
+}
- return nsvc->bind->send_vc(nsvc, msg);
+/*! Encode + Transmit a SNS-ADD/SNS-CHANGE-WEIGHT as per Section 9.3.2/9.3.3.
+ * \param[in] nsvc NS-VC through which to transmit the SNS-CONFIG
+ * \param[in] pdu The PDU type to send out
+ * \param[in] trans_id The transaction id
+ * \param[in] ip4_elems Array of IPv4 Elements
+ * \param[in] num_ip4_elems number of ip4_elems
+ * \param[in] ip6_elems Array of IPv6 Elements
+ * \param[in] num_ip6_elems number of ip6_elems
+ * \returns 0 on success; negative in case of error */
+static int ns2_tx_sns_procedure(struct gprs_ns2_vc *nsvc,
+ enum ns_pdu_type pdu,
+ uint8_t trans_id,
+ const struct gprs_ns_ie_ip4_elem *ip4_elems,
+ unsigned int num_ip4_elems,
+ const struct gprs_ns_ie_ip6_elem *ip6_elems,
+ unsigned int num_ip6_elems)
+{
+ struct msgb *msg;
+ struct gprs_ns_hdr *nsh;
+ uint16_t nsei;
+
+ if (!nsvc)
+ return -EINVAL;
+
+ if (!ip4_elems && !ip6_elems)
+ return -EINVAL;
+
+ msg = ns2_msgb_alloc();
+
+ log_set_context(LOG_CTX_GB_NSE, nsvc->nse);
+ log_set_context(LOG_CTX_GB_NSVC, nsvc);
+ if (!msg)
+ return -ENOMEM;
+
+ if (!nsvc->nse->bss_sns_fi) {
+ LOGNSVC(nsvc, LOGL_ERROR, "Cannot transmit SNS on NSVC without SNS active\n");
+ msgb_free(msg);
+ return -EIO;
+ }
+
+ nsei = osmo_htons(nsvc->nse->nsei);
+
+ msg->l2h = msgb_put(msg, sizeof(*nsh));
+ nsh = (struct gprs_ns_hdr *) msg->l2h;
+ nsh->pdu_type = pdu;
+ msgb_tvlv_put(msg, NS_IE_NSEI, 2, (uint8_t *)&nsei);
+ msgb_v_put(msg, trans_id);
+
+ /* List of IP4 Elements 10.3.2c */
+ if (ip4_elems) {
+ msgb_tvlv_put(msg, NS_IE_IPv4_LIST, num_ip4_elems*sizeof(struct gprs_ns_ie_ip4_elem),
+ (const uint8_t *)ip4_elems);
+ } else if (ip6_elems) {
+ /* List of IP6 elements 10.3.2d */
+ msgb_tvlv_put(msg, NS_IE_IPv6_LIST, num_ip6_elems*sizeof(struct gprs_ns_ie_ip6_elem),
+ (const uint8_t *)ip6_elems);
+ }
+
+ return ns_vc_tx(nsvc, msg);
+}
+
+/*! Encode + Transmit a SNS-ADD as per Section 9.3.2.
+ * \param[in] nsvc NS-VC through which to transmit the SNS-CONFIG
+ * \param[in] trans_id The transaction id
+ * \param[in] ip4_elems Array of IPv4 Elements
+ * \param[in] num_ip4_elems number of ip4_elems
+ * \param[in] ip6_elems Array of IPv6 Elements
+ * \param[in] num_ip6_elems number of ip6_elems
+ * \returns 0 on success; negative in case of error */
+int ns2_tx_sns_add(struct gprs_ns2_vc *nsvc,
+ uint8_t trans_id,
+ const struct gprs_ns_ie_ip4_elem *ip4_elems,
+ unsigned int num_ip4_elems,
+ const struct gprs_ns_ie_ip6_elem *ip6_elems,
+ unsigned int num_ip6_elems)
+{
+ return ns2_tx_sns_procedure(nsvc, SNS_PDUT_ADD, trans_id, ip4_elems, num_ip4_elems, ip6_elems, num_ip6_elems);
+}
+
+/*! Encode + Transmit a SNS-CHANGE-WEIGHT as per Section 9.3.3.
+ * \param[in] nsvc NS-VC through which to transmit the SNS-CONFIG
+ * \param[in] trans_id The transaction id
+ * \param[in] ip4_elems Array of IPv4 Elements
+ * \param[in] num_ip4_elems number of ip4_elems
+ * \param[in] ip6_elems Array of IPv6 Elements
+ * \param[in] num_ip6_elems number of ip6_elems
+ * \returns 0 on success; negative in case of error */
+int ns2_tx_sns_change_weight(struct gprs_ns2_vc *nsvc,
+ uint8_t trans_id,
+ const struct gprs_ns_ie_ip4_elem *ip4_elems,
+ unsigned int num_ip4_elems,
+ const struct gprs_ns_ie_ip6_elem *ip6_elems,
+ unsigned int num_ip6_elems)
+{
+ return ns2_tx_sns_procedure(nsvc, SNS_PDUT_CHANGE_WEIGHT, trans_id, ip4_elems, num_ip4_elems, ip6_elems, num_ip6_elems);
+}
+
+/*! Encode + Transmit a SNS-DEL as per Section 9.3.6.
+ * \param[in] nsvc NS-VC through which to transmit the SNS-CONFIG
+ * \param[in] trans_id The transaction id
+ * \param[in] ip4_elems Array of IPv4 Elements
+ * \param[in] num_ip4_elems number of ip4_elems
+ * \param[in] ip6_elems Array of IPv6 Elements
+ * \param[in] num_ip6_elems number of ip6_elems
+ * \returns 0 on success; negative in case of error */
+int ns2_tx_sns_del(struct gprs_ns2_vc *nsvc,
+ uint8_t trans_id,
+ const struct gprs_ns_ie_ip4_elem *ip4_elems,
+ unsigned int num_ip4_elems,
+ const struct gprs_ns_ie_ip6_elem *ip6_elems,
+ unsigned int num_ip6_elems)
+{
+ /* TODO: IP Address field */
+ return ns2_tx_sns_procedure(nsvc, SNS_PDUT_DELETE, trans_id, ip4_elems, num_ip4_elems, ip6_elems, num_ip6_elems);
}
@@ -486,26 +608,27 @@ int ns2_tx_sns_ack(struct gprs_ns2_vc *nsvc, uint8_t trans_id, uint8_t *cause,
const struct gprs_ns_ie_ip6_elem *ip6_elems,
unsigned int num_ip6_elems)
{
- struct msgb *msg = gprs_ns2_msgb_alloc();
+ struct msgb *msg;
struct gprs_ns_hdr *nsh;
uint16_t nsei;
if (!nsvc)
return -1;
- msg = gprs_ns2_msgb_alloc();
+ msg = ns2_msgb_alloc();
+ log_set_context(LOG_CTX_GB_NSE, nsvc->nse);
log_set_context(LOG_CTX_GB_NSVC, nsvc);
if (!msg)
return -ENOMEM;
if (!nsvc->nse->bss_sns_fi) {
- LOGP(DLNS, LOGL_ERROR, "NSEI=%u Cannot transmit SNS on NSVC without SNS active\n",
- nsvc->nse->nsei);
+ LOGNSVC(nsvc, LOGL_ERROR, "Cannot transmit SNS on NSVC without SNS active\n");
msgb_free(msg);
return -EIO;
}
+
nsei = osmo_htons(nsvc->nse->nsei);
msg->l2h = msgb_put(msg, sizeof(*nsh));
@@ -529,7 +652,10 @@ int ns2_tx_sns_ack(struct gprs_ns2_vc *nsvc, uint8_t trans_id, uint8_t *cause,
(const uint8_t *)ip6_elems);
}
- return nsvc->bind->send_vc(nsvc, msg);
+ LOG_NS_SIGNAL(nsvc, "Tx", nsh->pdu_type, LOGL_INFO,
+ " (trans_id=%u, cause=%s, num_ip4=%u, num_ip6=%u)\n",
+ trans_id, cause ? gprs_ns2_cause_str(*cause) : "NULL", num_ip4_elems, num_ip6_elems);
+ return ns_vc_tx(nsvc, msg);
}
/*! Encode + Transmit a SNS-CONFIG as per Section 9.3.4.
@@ -551,15 +677,15 @@ int ns2_tx_sns_config(struct gprs_ns2_vc *nsvc, bool end_flag,
if (!nsvc)
return -1;
- msg = gprs_ns2_msgb_alloc();
+ msg = ns2_msgb_alloc();
+ log_set_context(LOG_CTX_GB_NSE, nsvc->nse);
log_set_context(LOG_CTX_GB_NSVC, nsvc);
if (!msg)
return -ENOMEM;
if (!nsvc->nse->bss_sns_fi) {
- LOGP(DLNS, LOGL_ERROR, "NSEI=%u Cannot transmit SNS on NSVC without SNS active\n",
- nsvc->nse->nsei);
+ LOGNSVC(nsvc, LOGL_ERROR, "Cannot transmit SNS on NSVC without SNS active\n");
msgb_free(msg);
return -EIO;
}
@@ -584,7 +710,10 @@ int ns2_tx_sns_config(struct gprs_ns2_vc *nsvc, bool end_flag,
(const uint8_t *)ip6_elems);
}
- return nsvc->bind->send_vc(nsvc, msg);
+ LOG_NS_SIGNAL(nsvc, "Tx", nsh->pdu_type, LOGL_INFO,
+ " (end_flag=%u, num_ip4=%u, num_ip6=%u)\n",
+ end_flag, num_ip4_elems, num_ip6_elems);
+ return ns_vc_tx(nsvc, msg);
}
/*! Encode + Transmit a SNS-CONFIG-ACK as per Section 9.3.5.
@@ -600,14 +729,14 @@ int ns2_tx_sns_config_ack(struct gprs_ns2_vc *nsvc, uint8_t *cause)
if (!nsvc)
return -1;
- msg = gprs_ns2_msgb_alloc();
+ msg = ns2_msgb_alloc();
+ log_set_context(LOG_CTX_GB_NSE, nsvc->nse);
log_set_context(LOG_CTX_GB_NSVC, nsvc);
if (!msg)
return -ENOMEM;
if (!nsvc->nse->bss_sns_fi) {
- LOGP(DLNS, LOGL_ERROR, "NSEI=%u Cannot transmit SNS on NSVC without SNS active\n",
- nsvc->nse->nsei);
+ LOGNSVC(nsvc, LOGL_ERROR, "Cannot transmit SNS on NSVC without SNS active\n");
msgb_free(msg);
return -EIO;
}
@@ -623,7 +752,10 @@ int ns2_tx_sns_config_ack(struct gprs_ns2_vc *nsvc, uint8_t *cause)
if (cause)
msgb_tvlv_put(msg, NS_IE_CAUSE, 1, cause);
- return nsvc->bind->send_vc(nsvc, msg);
+ LOGNSVC(nsvc, LOGL_INFO, "Tx SNS-CONFIG-ACK (cause=%s)\n",
+ cause ? gprs_ns2_cause_str(*cause) : "NULL");
+ LOG_NS_TX_SIGNAL(nsvc, nsh->pdu_type);
+ return ns_vc_tx(nsvc, msg);
}
@@ -637,22 +769,22 @@ int ns2_tx_sns_config_ack(struct gprs_ns2_vc *nsvc, uint8_t *cause)
int ns2_tx_sns_size(struct gprs_ns2_vc *nsvc, bool reset_flag, uint16_t max_nr_nsvc,
int ip4_ep_nr, int ip6_ep_nr)
{
- struct msgb *msg = gprs_ns2_msgb_alloc();
+ struct msgb *msg;
struct gprs_ns_hdr *nsh;
uint16_t nsei;
if (!nsvc)
return -1;
- msg = gprs_ns2_msgb_alloc();
+ msg = ns2_msgb_alloc();
+ log_set_context(LOG_CTX_GB_NSE, nsvc->nse);
log_set_context(LOG_CTX_GB_NSVC, nsvc);
if (!msg)
return -ENOMEM;
if (!nsvc->nse->bss_sns_fi) {
- LOGP(DLNS, LOGL_ERROR, "NSEI=%u Cannot transmit SNS on NSVC without SNS active\n",
- nsvc->nse->nsei);
+ LOGNSVC(nsvc, LOGL_ERROR, "Cannot transmit SNS on NSVC without SNS active\n");
msgb_free(msg);
return -EIO;
}
@@ -672,7 +804,10 @@ int ns2_tx_sns_size(struct gprs_ns2_vc *nsvc, bool reset_flag, uint16_t max_nr_n
if (ip6_ep_nr >= 0)
msgb_tv16_put(msg, NS_IE_IPv6_EP_NR, ip6_ep_nr);
- return nsvc->bind->send_vc(nsvc, msg);
+ LOG_NS_SIGNAL(nsvc, "Tx", nsh->pdu_type, LOGL_INFO,
+ " (reset=%u, max_nr_nsvc=%u, num_ip4=%d, num_ip6=%d)\n",
+ reset_flag, max_nr_nsvc, ip4_ep_nr, ip6_ep_nr);
+ return ns_vc_tx(nsvc, msg);
}
/*! Encode + Transmit a SNS-SIZE-ACK as per Section 9.3.8.
@@ -681,17 +816,17 @@ int ns2_tx_sns_size(struct gprs_ns2_vc *nsvc, bool reset_flag, uint16_t max_nr_n
* \returns 0 on success; negative in case of error */
int ns2_tx_sns_size_ack(struct gprs_ns2_vc *nsvc, uint8_t *cause)
{
- struct msgb *msg = gprs_ns2_msgb_alloc();
+ struct msgb *msg = ns2_msgb_alloc();
struct gprs_ns_hdr *nsh;
uint16_t nsei;
+ log_set_context(LOG_CTX_GB_NSE, nsvc->nse);
log_set_context(LOG_CTX_GB_NSVC, nsvc);
if (!msg)
return -ENOMEM;
if (!nsvc->nse->bss_sns_fi) {
- LOGP(DLNS, LOGL_ERROR, "NSEI=%u Cannot transmit SNS on NSVC without SNS active\n",
- nsvc->nse->nsei);
+ LOGNSVC(nsvc, LOGL_ERROR, "Cannot transmit SNS on NSVC without SNS active\n");
msgb_free(msg);
return -EIO;
}
@@ -707,7 +842,7 @@ int ns2_tx_sns_size_ack(struct gprs_ns2_vc *nsvc, uint8_t *cause)
if (cause)
msgb_tvlv_put(msg, NS_IE_CAUSE, 1, cause);
- return nsvc->bind->send_vc(nsvc, msg);
+ LOG_NS_SIGNAL(nsvc, "Tx", nsh->pdu_type, LOGL_INFO, " cause=%s\n",
+ cause ? gprs_ns2_cause_str(*cause) : "NULL");
+ return ns_vc_tx(nsvc, msg);
}
-
-
diff --git a/src/gb/gprs_ns2_sns.c b/src/gb/gprs_ns2_sns.c
index 31f8a5f5..0afc06ed 100644
--- a/src/gb/gprs_ns2_sns.c
+++ b/src/gb/gprs_ns2_sns.c
@@ -3,7 +3,7 @@
* 3GPP TS 08.16 version 8.0.1 Release 1999 / ETSI TS 101 299 V8.0.1 (2002-05)
* as well as its successor 3GPP TS 48.016 */
-/* (C) 2018 by Harald Welte <laforge@gnumonks.org>
+/* (C) 2018-2021 by Harald Welte <laforge@gnumonks.org>
* (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* Author: Alexander Couzens <lynxis@fe80.eu>
*
@@ -46,6 +46,7 @@
#include <osmocom/core/fsm.h>
#include <osmocom/core/msgb.h>
#include <osmocom/core/socket.h>
+#include <osmocom/core/sockaddr_str.h>
#include <osmocom/gsm/tlv.h>
#include <osmocom/gprs/gprs_msgb.h>
#include <osmocom/gprs/gprs_ns2.h>
@@ -55,64 +56,123 @@
#define S(x) (1 << (x))
-enum ns2_sns_type {
- IPv4,
- IPv6,
+enum ns2_sns_role {
+ GPRS_SNS_ROLE_BSS,
+ GPRS_SNS_ROLE_SGSN,
};
+/* BSS-side-only states _ST_BSS_; SGSN-side only states _ST_SGSN_; others shared */
enum gprs_sns_bss_state {
GPRS_SNS_ST_UNCONFIGURED,
- GPRS_SNS_ST_SIZE, /*!< SNS-SIZE procedure ongoing */
- GPRS_SNS_ST_CONFIG_BSS, /*!< SNS-CONFIG procedure (BSS->SGSN) ongoing */
- GPRS_SNS_ST_CONFIG_SGSN, /*!< SNS-CONFIG procedure (SGSN->BSS) ongoing */
+ GPRS_SNS_ST_BSS_SIZE, /*!< SNS-SIZE procedure ongoing */
+ GPRS_SNS_ST_BSS_CONFIG_BSS, /*!< SNS-CONFIG procedure (BSS->SGSN) ongoing */
+ GPRS_SNS_ST_BSS_CONFIG_SGSN, /*!< SNS-CONFIG procedure (SGSN->BSS) ongoing */
GPRS_SNS_ST_CONFIGURED,
-};
-
-enum gprs_sns_event {
- GPRS_SNS_EV_START,
- GPRS_SNS_EV_SIZE,
- GPRS_SNS_EV_SIZE_ACK,
- GPRS_SNS_EV_CONFIG,
- GPRS_SNS_EV_CONFIG_END, /*!< SNS-CONFIG with end flag received */
- GPRS_SNS_EV_CONFIG_ACK,
- GPRS_SNS_EV_ADD,
- GPRS_SNS_EV_DELETE,
- GPRS_SNS_EV_CHANGE_WEIGHT,
- GPRS_SNS_EV_NO_NSVC,
+ GPRS_SNS_ST_SGSN_WAIT_CONFIG, /* !< SGSN role: Wait for CONFIG from BSS */
+ GPRS_SNS_ST_SGSN_WAIT_CONFIG_ACK, /* !< SGSN role: Wait for CONFIG-ACK from BSS */
+ GPRS_SNS_ST_LOCAL_PROCEDURE, /*!< in process of a ADD/DEL/CHANGE procedure towards SGSN (BSS->SGSN) */
};
static const struct value_string gprs_sns_event_names[] = {
- { GPRS_SNS_EV_START, "START" },
- { GPRS_SNS_EV_SIZE, "SIZE" },
- { GPRS_SNS_EV_SIZE_ACK, "SIZE_ACK" },
- { GPRS_SNS_EV_CONFIG, "CONFIG" },
- { GPRS_SNS_EV_CONFIG_END, "CONFIG_END" },
- { GPRS_SNS_EV_CONFIG_ACK, "CONFIG_ACK" },
- { GPRS_SNS_EV_ADD, "ADD" },
- { GPRS_SNS_EV_DELETE, "DELETE" },
- { GPRS_SNS_EV_CHANGE_WEIGHT, "CHANGE_WEIGHT" },
- { GPRS_SNS_EV_NO_NSVC, "NO_NSVC" },
+ { NS2_SNS_EV_REQ_SELECT_ENDPOINT, "REQ_SELECT_ENDPOINT" },
+ { NS2_SNS_EV_RX_SIZE, "RX_SIZE" },
+ { NS2_SNS_EV_RX_SIZE_ACK, "RX_SIZE_ACK" },
+ { NS2_SNS_EV_RX_CONFIG, "RX_CONFIG" },
+ { NS2_SNS_EV_RX_CONFIG_END, "RX_CONFIG_END" },
+ { NS2_SNS_EV_RX_CONFIG_ACK, "RX_CONFIG_ACK" },
+ { NS2_SNS_EV_RX_ADD, "RX_ADD" },
+ { NS2_SNS_EV_RX_DELETE, "RX_DELETE" },
+ { NS2_SNS_EV_RX_ACK, "RX_ACK" },
+ { NS2_SNS_EV_RX_CHANGE_WEIGHT, "RX_CHANGE_WEIGHT" },
+ { NS2_SNS_EV_REQ_NO_NSVC, "REQ_NO_NSVC" },
+ { NS2_SNS_EV_REQ_FREE_NSVCS, "REQ_FREE_NSVCS" },
+ { NS2_SNS_EV_REQ_NSVC_ALIVE, "REQ_NSVC_ALIVE"},
+ { NS2_SNS_EV_REQ_ADD_BIND, "REQ_ADD_BIND"},
+ { NS2_SNS_EV_REQ_DELETE_BIND, "REQ_DELETE_BIND"},
+ { NS2_SNS_EV_REQ_CHANGE_WEIGHT, "REQ_CHANGE_WEIGHT"},
{ 0, NULL }
};
+#define GPRS_SNS_FLAG_KEEP_SELECT_ENDPOINT_ORDER (void *) 1
+
+enum sns_procedure {
+ SNS_PROC_NONE, /*!< used as invalid/idle value */
+ SNS_PROC_ADD,
+ SNS_PROC_DEL,
+ SNS_PROC_CHANGE_WEIGHT,
+};
+
+struct sns_endpoint {
+ struct llist_head list;
+ struct osmo_sockaddr saddr;
+};
+
+struct ns2_sns_bind {
+ struct llist_head list;
+ struct gprs_ns2_vc_bind *bind;
+ uint8_t change_weight_state;
+};
+
+struct ns2_sns_procedure {
+ struct llist_head list;
+ struct ns2_sns_bind *sbind;
+ uint16_t sig_weight;
+ uint16_t data_weight;
+ /* copy entry to protect against changes of gss->local */
+ struct gprs_ns_ie_ip4_elem ip4;
+ struct gprs_ns_ie_ip6_elem ip6;
+ enum sns_procedure procedure;
+ uint8_t trans_id;
+ /* is the procedure in process */
+ bool running;
+};
+
+struct ns2_sns_elems {
+ struct gprs_ns_ie_ip4_elem *ip4;
+ unsigned int num_ip4;
+ struct gprs_ns_ie_ip6_elem *ip6;
+ unsigned int num_ip6;
+};
+
struct ns2_sns_state {
struct gprs_ns2_nse *nse;
- enum ns2_sns_type ip;
-
- /* initial connection. the initial connection will be terminated
- * in configured state or moved into NSE if valid */
- struct osmo_sockaddr initial;
+ /* containing the address family AF_* */
+ int family;
+ enum ns2_sns_role role; /* local role: BSS or SGSN */
+
+ /* holds the list of initial SNS endpoints */
+ struct llist_head sns_endpoints;
+ /* list of used struct ns2_sns_bind */
+ struct llist_head binds;
+ /* pointer to the bind which was used to initiate the SNS connection */
+ struct ns2_sns_bind *initial_bind;
+ /* prevent recursive reselection */
+ bool reselection_running;
+
+ /* protection against recursive free() */
+ bool block_no_nsvc_events;
+
+ /* The current initial SNS endpoints.
+ * The initial connection will be moved into the NSE
+ * if configured via SNS. Otherwise it will be removed
+ * in configured state. */
+ struct sns_endpoint *initial;
/* all SNS PDU will be sent over this nsvc */
struct gprs_ns2_vc *sns_nsvc;
+ /* timer N */
+ int N;
+ /* true if at least one nsvc is alive */
+ bool alive;
/* local configuration to send to the remote end */
- struct gprs_ns_ie_ip4_elem *ip4_local;
- size_t num_ip4_local;
+ struct ns2_sns_elems local;
- /* local configuration to send to the remote end */
- struct gprs_ns_ie_ip6_elem *ip6_local;
- size_t num_ip6_local;
+ /* local configuration after all local procedures applied */
+ struct ns2_sns_elems local_procedure;
+
+ /* remote configuration as received */
+ struct ns2_sns_elems remote;
/* local configuration about our capabilities in terms of connections to
* remote (SGSN) side */
@@ -120,13 +180,9 @@ struct ns2_sns_state {
size_t num_max_ip4_remote;
size_t num_max_ip6_remote;
- /* remote configuration as received */
- struct gprs_ns_ie_ip4_elem *ip4_remote;
- unsigned int num_ip4_remote;
-
- /* remote configuration as received */
- struct gprs_ns_ie_ip6_elem *ip6_remote;
- unsigned int num_ip6_remote;
+ struct llist_head procedures;
+ struct ns2_sns_procedure *current_procedure;
+ uint8_t trans_id;
};
static inline struct gprs_ns2_nse *nse_inst_from_fi(struct osmo_fsm_inst *fi)
@@ -135,41 +191,68 @@ static inline struct gprs_ns2_nse *nse_inst_from_fi(struct osmo_fsm_inst *fi)
return gss->nse;
}
+/* The SNS has failed. Etither restart the SNS (BSS) or remove the SNS (SGSN) */
+#define sns_failed(fi, reason) \
+ _sns_failed(fi, reason, __FILE__, __LINE__)
+static void _sns_failed(struct osmo_fsm_inst *fi, const char *reason, const char *file, int line)
+{
+ struct ns2_sns_state *gss = fi->priv;
+
+ if (reason)
+ LOGPFSMLSRC(fi, LOGL_ERROR, file, line, "NSE %d: SNS failed: %s\n", gss->nse->nsei, reason);
+
+ gss->alive = false;
+ if (gss->role == GPRS_SNS_ROLE_SGSN) {
+ if (!gss->nse->persistent)
+ gprs_ns2_free_nse(gss->nse);
+ else
+ _osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_UNCONFIGURED, 0, 0, file, line);
+ } else {
+ _osmo_fsm_inst_dispatch(fi, NS2_SNS_EV_REQ_SELECT_ENDPOINT, NULL, file, line);
+ }
+}
+
/* helper function to compute the sum of all (data or signaling) weights */
-static int ip4_weight_sum(const struct gprs_ns_ie_ip4_elem *ip4, unsigned int num,
- bool data_weight)
+static int ip4_weight_sum(const struct ns2_sns_elems *elems, bool data_weight)
{
unsigned int i;
int weight_sum = 0;
- for (i = 0; i < num; i++) {
+ for (i = 0; i < elems->num_ip4; i++) {
if (data_weight)
- weight_sum += ip4[i].data_weight;
+ weight_sum += elems->ip4[i].data_weight;
else
- weight_sum += ip4[i].sig_weight;
+ weight_sum += elems->ip4[i].sig_weight;
}
return weight_sum;
}
-#define ip4_weight_sum_data(x,y) ip4_weight_sum(x, y, true)
-#define ip4_weight_sum_sig(x,y) ip4_weight_sum(x, y, false)
+#define ip4_weight_sum_data(elems) ip4_weight_sum(elems, true)
+#define ip4_weight_sum_sig(elems) ip4_weight_sum(elems, false)
/* helper function to compute the sum of all (data or signaling) weights */
-static int ip6_weight_sum(const struct gprs_ns_ie_ip6_elem *ip6, unsigned int num,
- bool data_weight)
+static int ip6_weight_sum(const struct ns2_sns_elems *elems, bool data_weight)
{
unsigned int i;
int weight_sum = 0;
- for (i = 0; i < num; i++) {
+ for (i = 0; i < elems->num_ip6; i++) {
if (data_weight)
- weight_sum += ip6[i].data_weight;
+ weight_sum += elems->ip6[i].data_weight;
else
- weight_sum += ip6[i].sig_weight;
+ weight_sum += elems->ip6[i].sig_weight;
}
return weight_sum;
}
-#define ip6_weight_sum_data(x,y) ip6_weight_sum(x, y, true)
-#define ip6_weight_sum_sig(x,y) ip6_weight_sum(x, y, false)
+#define ip6_weight_sum_data(elems) ip6_weight_sum(elems, true)
+#define ip6_weight_sum_sig(elems) ip6_weight_sum(elems, false)
+
+static int ip46_weight_sum(const struct ns2_sns_elems *elems, bool data_weight)
+{
+ return ip4_weight_sum(elems, data_weight) +
+ ip6_weight_sum(elems, data_weight);
+}
+#define ip46_weight_sum_data(elems) ip46_weight_sum(elems, true)
+#define ip46_weight_sum_sig(elems) ip46_weight_sum(elems, false)
static struct gprs_ns2_vc *nsvc_by_ip4_elem(struct gprs_ns2_nse *nse,
const struct gprs_ns_ie_ip4_elem *ip4)
@@ -207,16 +290,16 @@ const struct osmo_sockaddr *gprs_ns2_nse_sns_remote(struct gprs_ns2_nse *nse)
return NULL;
gss = (struct ns2_sns_state *) nse->bss_sns_fi->priv;
- return &gss->initial;
+ return &gss->initial->saddr;
}
-/*! called when a nsvc is beeing freed */
-void ns2_sns_free_nsvc(struct gprs_ns2_vc *nsvc)
+/*! called when a nsvc is beeing freed or the nsvc became dead */
+void ns2_sns_replace_nsvc(struct gprs_ns2_vc *nsvc)
{
- struct gprs_ns2_nse *nse;
+ struct gprs_ns2_nse *nse = nsvc->nse;
struct gprs_ns2_vc *tmp;
+ struct osmo_fsm_inst *fi = nse->bss_sns_fi;
struct ns2_sns_state *gss;
- struct osmo_fsm_inst *fi = nsvc->nse->bss_sns_fi;
if (!fi)
return;
@@ -225,109 +308,142 @@ void ns2_sns_free_nsvc(struct gprs_ns2_vc *nsvc)
if (nsvc != gss->sns_nsvc)
return;
- nse = nsvc->nse;
- if (nse->alive) {
- /* choose a different sns nsvc */
+ gss->sns_nsvc = NULL;
+ if (gss->alive) {
llist_for_each_entry(tmp, &nse->nsvc, list) {
- if (gprs_ns2_vc_is_unblocked(tmp))
+ if (ns2_vc_is_unblocked(tmp)) {
gss->sns_nsvc = tmp;
+ return;
+ }
}
} else {
- LOGPFSML(fi, LOGL_ERROR, "NSE %d: no remaining NSVC, resetting SNS FSM\n", nse->nsei);
- gss->sns_nsvc = NULL;
- osmo_fsm_inst_dispatch(fi, GPRS_SNS_EV_NO_NSVC, NULL);
+ /* the SNS is waiting for its first NS-VC to come up
+ * choose any other nsvc */
+ llist_for_each_entry(tmp, &nse->nsvc, list) {
+ if (nsvc != tmp) {
+ gss->sns_nsvc = tmp;
+ return;
+ }
+ }
}
+
+ if (gss->block_no_nsvc_events)
+ osmo_fsm_inst_dispatch(fi, NS2_SNS_EV_REQ_NO_NSVC, NULL);
}
-static void ns2_nsvc_create_ip4(struct osmo_fsm_inst *fi,
- struct gprs_ns2_nse *nse,
- const struct gprs_ns_ie_ip4_elem *ip4)
+static void ns2_clear_elems(struct ns2_sns_elems *elems)
+{
+ TALLOC_FREE(elems->ip4);
+ TALLOC_FREE(elems->ip6);
+
+ elems->num_ip4 = 0;
+ elems->num_ip6 = 0;
+}
+
+static void ns2_clear_procedures(struct ns2_sns_state *gss)
+{
+ struct ns2_sns_procedure *procedure, *tmp;
+ gss->current_procedure = NULL;
+ llist_for_each_entry_safe(procedure, tmp, &gss->procedures, list) {
+ llist_del(&procedure->list);
+ talloc_free(procedure);
+ }
+}
+
+static void ns2_vc_create_ip(struct osmo_fsm_inst *fi, struct gprs_ns2_nse *nse, const struct osmo_sockaddr *remote,
+ uint8_t sig_weight, uint8_t data_weight)
{
struct gprs_ns2_inst *nsi = nse->nsi;
struct gprs_ns2_vc *nsvc;
struct gprs_ns2_vc_bind *bind;
- struct osmo_sockaddr remote = { };
- /* copy over. Both data structures use network byte order */
- remote.u.sin.sin_family = AF_INET;
- remote.u.sin.sin_addr.s_addr = ip4->ip_addr;
- remote.u.sin.sin_port = ip4->udp_port;
/* for every bind, create a connection if bind type == IP */
llist_for_each_entry(bind, &nsi->binding, list) {
+ if (bind->ll != GPRS_NS2_LL_UDP)
+ continue;
/* ignore failed connection */
nsvc = gprs_ns2_ip_connect_inactive(bind,
- &remote,
+ remote,
nse, 0);
if (!nsvc) {
LOGPFSML(fi, LOGL_ERROR, "SNS-CONFIG: Failed to create NSVC\n");
continue;
}
- nsvc->sig_weight = ip4->sig_weight;
- nsvc->data_weight = ip4->data_weight;
+ nsvc->sig_weight = sig_weight;
+ nsvc->data_weight = data_weight;
}
}
+static void ns2_nsvc_create_ip4(struct osmo_fsm_inst *fi,
+ struct gprs_ns2_nse *nse,
+ const struct gprs_ns_ie_ip4_elem *ip4)
+{
+ struct osmo_sockaddr remote = { };
+ /* copy over. Both data structures use network byte order */
+ remote.u.sin.sin_family = AF_INET;
+ remote.u.sin.sin_addr.s_addr = ip4->ip_addr;
+ remote.u.sin.sin_port = ip4->udp_port;
+
+ ns2_vc_create_ip(fi, nse, &remote, ip4->sig_weight, ip4->data_weight);
+}
+
static void ns2_nsvc_create_ip6(struct osmo_fsm_inst *fi,
struct gprs_ns2_nse *nse,
const struct gprs_ns_ie_ip6_elem *ip6)
{
- struct gprs_ns2_inst *nsi = nse->nsi;
- struct gprs_ns2_vc *nsvc;
- struct gprs_ns2_vc_bind *bind;
struct osmo_sockaddr remote = {};
/* copy over. Both data structures use network byte order */
remote.u.sin6.sin6_family = AF_INET6;
remote.u.sin6.sin6_addr = ip6->ip_addr;
remote.u.sin6.sin6_port = ip6->udp_port;
- /* for every bind, create a connection if bind type == IP */
- llist_for_each_entry(bind, &nsi->binding, list) {
- /* ignore failed connection */
- nsvc = gprs_ns2_ip_connect_inactive(bind,
- &remote,
- nse, 0);
- if (!nsvc) {
- LOGPFSML(fi, LOGL_ERROR, "SNS-CONFIG: Failed to create NSVC\n");
+ ns2_vc_create_ip(fi, nse, &remote, ip6->sig_weight, ip6->data_weight);
+}
+
+static struct gprs_ns2_vc *nsvc_for_bind_and_remote(struct gprs_ns2_nse *nse,
+ struct gprs_ns2_vc_bind *bind,
+ const struct osmo_sockaddr *remote)
+{
+ struct gprs_ns2_vc *nsvc;
+
+ llist_for_each_entry(nsvc, &nse->nsvc, list) {
+ if (nsvc->bind != bind)
continue;
- }
- nsvc->sig_weight = ip6->sig_weight;
- nsvc->data_weight = ip6->data_weight;
+ if (!osmo_sockaddr_cmp(remote, gprs_ns2_ip_vc_remote(nsvc)))
+ return nsvc;
}
+ return NULL;
}
-
static int create_missing_nsvcs(struct osmo_fsm_inst *fi)
{
struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
struct gprs_ns2_nse *nse = nse_inst_from_fi(fi);
struct gprs_ns2_vc *nsvc;
- struct gprs_ns2_vc_bind *bind;
+ struct ns2_sns_bind *sbind;
struct osmo_sockaddr remote = { };
unsigned int i;
- for (i = 0; i < gss->num_ip4_remote; i++) {
- const struct gprs_ns_ie_ip4_elem *ip4 = &gss->ip4_remote[i];
+ /* iterate over all remote IPv4 endpoints */
+ for (i = 0; i < gss->remote.num_ip4; i++) {
+ const struct gprs_ns_ie_ip4_elem *ip4 = &gss->remote.ip4[i];
remote.u.sin.sin_family = AF_INET;
remote.u.sin.sin_addr.s_addr = ip4->ip_addr;
remote.u.sin.sin_port = ip4->udp_port;
- llist_for_each_entry(bind, &nse->nsi->binding, list) {
- bool found = false;
-
- llist_for_each_entry(nsvc, &nse->nsvc, list) {
- if (nsvc->bind != bind)
- continue;
+ /* iterate over all local binds within this SNS */
+ llist_for_each_entry(sbind, &gss->binds, list) {
+ struct gprs_ns2_vc_bind *bind = sbind->bind;
- if (!osmo_sockaddr_cmp(&remote, gprs_ns2_ip_vc_remote(nsvc))) {
- found = true;
- break;
- }
- }
+ /* we only care about UDP binds */
+ if (bind->ll != GPRS_NS2_LL_UDP)
+ continue;
- if (!found) {
+ nsvc = nsvc_for_bind_and_remote(nse, bind, &remote);
+ if (!nsvc) {
nsvc = gprs_ns2_ip_connect_inactive(bind, &remote, nse, 0);
if (!nsvc) {
/* TODO: add to a list to send back a NS-STATUS */
@@ -342,27 +458,24 @@ static int create_missing_nsvcs(struct osmo_fsm_inst *fi)
}
}
- for (i = 0; i < gss->num_ip6_remote; i++) {
- const struct gprs_ns_ie_ip6_elem *ip6 = &gss->ip6_remote[i];
+ /* iterate over all remote IPv4 endpoints */
+ for (i = 0; i < gss->remote.num_ip6; i++) {
+ const struct gprs_ns_ie_ip6_elem *ip6 = &gss->remote.ip6[i];
remote.u.sin6.sin6_family = AF_INET6;
remote.u.sin6.sin6_addr = ip6->ip_addr;
remote.u.sin6.sin6_port = ip6->udp_port;
- llist_for_each_entry(bind, &nse->nsi->binding, list) {
- bool found = false;
-
- llist_for_each_entry(nsvc, &nse->nsvc, list) {
- if (nsvc->bind != bind)
- continue;
+ /* iterate over all local binds within this SNS */
+ llist_for_each_entry(sbind, &gss->binds, list) {
+ struct gprs_ns2_vc_bind *bind = sbind->bind;
- if (!osmo_sockaddr_cmp(&remote, gprs_ns2_ip_vc_remote(nsvc))) {
- found = true;
- break;
- }
- }
+ if (bind->ll != GPRS_NS2_LL_UDP)
+ continue;
- if (!found) {
+ /* we only care about UDP binds */
+ nsvc = nsvc_for_bind_and_remote(nse, bind, &remote);
+ if (!nsvc) {
nsvc = gprs_ns2_ip_connect_inactive(bind, &remote, nse, 0);
if (!nsvc) {
/* TODO: add to a list to send back a NS-STATUS */
@@ -382,107 +495,137 @@ static int create_missing_nsvcs(struct osmo_fsm_inst *fi)
}
/* Add a given remote IPv4 element to gprs_sns_state */
-static int add_remote_ip4_elem(struct ns2_sns_state *gss, const struct gprs_ns_ie_ip4_elem *ip4)
+static int add_ip4_elem(struct ns2_sns_state *gss, struct ns2_sns_elems *elems,
+ const struct gprs_ns_ie_ip4_elem *ip4)
{
- unsigned int i;
-
- if (gss->num_ip4_remote >= gss->num_max_ip4_remote)
- return -NS_CAUSE_INVAL_NR_NS_VC;
-
/* check for duplicates */
- for (i = 0; i < gss->num_ip4_remote; i++) {
- if (memcmp(&gss->ip4_remote[i], ip4, sizeof(*ip4)))
+ for (unsigned int i = 0; i < elems->num_ip4; i++) {
+ if (memcmp(&elems->ip4[i], ip4, sizeof(*ip4)))
continue;
- /* TODO: log message duplicate */
- /* TODO: check if this is the correct cause code */
- return -NS_CAUSE_PROTO_ERR_UNSPEC;
+ return -1;
}
- gss->ip4_remote = talloc_realloc(gss, gss->ip4_remote, struct gprs_ns_ie_ip4_elem,
- gss->num_ip4_remote+1);
- gss->ip4_remote[gss->num_ip4_remote] = *ip4;
- gss->num_ip4_remote += 1;
+ elems->ip4 = talloc_realloc(gss, elems->ip4, struct gprs_ns_ie_ip4_elem,
+ elems->num_ip4+1);
+ elems->ip4[elems->num_ip4] = *ip4;
+ elems->num_ip4 += 1;
return 0;
}
/* Remove a given remote IPv4 element from gprs_sns_state */
-static int remove_remote_ip4_elem(struct ns2_sns_state *gss, const struct gprs_ns_ie_ip4_elem *ip4)
+static int remove_ip4_elem(struct ns2_sns_state *gss, struct ns2_sns_elems *elems,
+ const struct gprs_ns_ie_ip4_elem *ip4)
{
unsigned int i;
- for (i = 0; i < gss->num_ip4_remote; i++) {
- if (memcmp(&gss->ip4_remote[i], ip4, sizeof(*ip4)))
+ for (i = 0; i < elems->num_ip4; i++) {
+ if (memcmp(&elems->ip4[i], ip4, sizeof(*ip4)))
continue;
/* all array elements < i remain as they are; all > i are shifted left by one */
- memmove(&gss->ip4_remote[i], &gss->ip4_remote[i+1], gss->num_ip4_remote-i-1);
- gss->num_ip4_remote -= 1;
+ memmove(&elems->ip4[i], &elems->ip4[i+1], elems->num_ip4-i-1);
+ elems->num_ip4 -= 1;
return 0;
}
return -1;
}
/* update the weights for specified remote IPv4 */
-static int update_remote_ip4_elem(struct ns2_sns_state *gss, const struct gprs_ns_ie_ip4_elem *ip4)
+static int update_ip4_elem(struct ns2_sns_state *gss, struct ns2_sns_elems *elems,
+ const struct gprs_ns_ie_ip4_elem *ip4)
{
unsigned int i;
- for (i = 0; i < gss->num_ip4_remote; i++) {
- if (gss->ip4_remote[i].ip_addr != ip4->ip_addr ||
- gss->ip4_remote[i].udp_port != ip4->udp_port)
+ for (i = 0; i < elems->num_ip4; i++) {
+ if (elems->ip4[i].ip_addr != ip4->ip_addr ||
+ elems->ip4[i].udp_port != ip4->udp_port)
continue;
- gss->ip4_remote[i].sig_weight = ip4->sig_weight;
- gss->ip4_remote[i].data_weight = ip4->data_weight;
+ elems->ip4[i].sig_weight = ip4->sig_weight;
+ elems->ip4[i].data_weight = ip4->data_weight;
return 0;
}
return -1;
}
/* Add a given remote IPv6 element to gprs_sns_state */
-static int add_remote_ip6_elem(struct ns2_sns_state *gss, const struct gprs_ns_ie_ip6_elem *ip6)
+static int add_ip6_elem(struct ns2_sns_state *gss, struct ns2_sns_elems *elems,
+ const struct gprs_ns_ie_ip6_elem *ip6)
{
- if (gss->num_ip6_remote >= gss->num_max_ip6_remote)
- return -NS_CAUSE_INVAL_NR_NS_VC;
+ /* check for duplicates */
+ for (unsigned int i = 0; i < elems->num_ip6; i++) {
+ if (memcmp(&elems->ip6[i].ip_addr, &ip6->ip_addr, sizeof(ip6->ip_addr)) ||
+ elems->ip6[i].udp_port != ip6->udp_port)
+ continue;
+ return -1;
+ }
- gss->ip6_remote = talloc_realloc(gss, gss->ip6_remote, struct gprs_ns_ie_ip6_elem,
- gss->num_ip6_remote+1);
- gss->ip6_remote[gss->num_ip6_remote] = *ip6;
- gss->num_ip6_remote += 1;
+ elems->ip6 = talloc_realloc(gss, elems->ip6, struct gprs_ns_ie_ip6_elem,
+ elems->num_ip6+1);
+ elems->ip6[elems->num_ip6] = *ip6;
+ elems->num_ip6 += 1;
return 0;
}
/* Remove a given remote IPv6 element from gprs_sns_state */
-static int remove_remote_ip6_elem(struct ns2_sns_state *gss, const struct gprs_ns_ie_ip6_elem *ip6)
+static int remove_ip6_elem(struct ns2_sns_state *gss, struct ns2_sns_elems *elems,
+ const struct gprs_ns_ie_ip6_elem *ip6)
{
unsigned int i;
- for (i = 0; i < gss->num_ip6_remote; i++) {
- if (memcmp(&gss->ip6_remote[i], ip6, sizeof(*ip6)))
+ for (i = 0; i < elems->num_ip6; i++) {
+ if (memcmp(&elems->ip6[i], ip6, sizeof(*ip6)))
continue;
/* all array elements < i remain as they are; all > i are shifted left by one */
- memmove(&gss->ip6_remote[i], &gss->ip6_remote[i+1], gss->num_ip6_remote-i-1);
- gss->num_ip6_remote -= 1;
+ memmove(&elems->ip6[i], &elems->ip6[i+1], elems->num_ip6-i-1);
+ elems->num_ip6 -= 1;
return 0;
}
return -1;
}
/* update the weights for specified remote IPv6 */
-static int update_remote_ip6_elem(struct ns2_sns_state *gss, const struct gprs_ns_ie_ip6_elem *ip6)
+static int update_ip6_elem(struct ns2_sns_state *gss, struct ns2_sns_elems *elems,
+ const struct gprs_ns_ie_ip6_elem *ip6)
{
unsigned int i;
- for (i = 0; i < gss->num_ip6_remote; i++) {
- if (memcmp(&gss->ip6_remote[i].ip_addr, &ip6->ip_addr, sizeof(ip6->ip_addr)) ||
- gss->ip6_remote[i].udp_port != ip6->udp_port)
+ for (i = 0; i < elems->num_ip6; i++) {
+ if (memcmp(&elems->ip6[i].ip_addr, &ip6->ip_addr, sizeof(ip6->ip_addr)) ||
+ elems->ip6[i].udp_port != ip6->udp_port)
continue;
- gss->ip6_remote[i].sig_weight = ip6->sig_weight;
- gss->ip6_remote[i].data_weight = ip6->data_weight;
+ elems->ip6[i].sig_weight = ip6->sig_weight;
+ elems->ip6[i].data_weight = ip6->data_weight;
return 0;
}
return -1;
}
+static int remove_bind_elem(struct ns2_sns_state *gss, struct ns2_sns_elems *elems, struct ns2_sns_bind *sbind)
+{
+ struct gprs_ns_ie_ip4_elem ip4;
+ struct gprs_ns_ie_ip6_elem ip6;
+ const struct osmo_sockaddr *saddr = gprs_ns2_ip_bind_sockaddr(sbind->bind);
+
+ switch (saddr->u.sa.sa_family) {
+ case AF_INET:
+ ip4.ip_addr = saddr->u.sin.sin_addr.s_addr;
+ ip4.udp_port = saddr->u.sin.sin_port;
+ ip4.sig_weight = sbind->bind->sns_sig_weight;
+ ip4.data_weight = sbind->bind->sns_data_weight;
+ return remove_ip4_elem(gss, elems, &ip4);
+ case AF_INET6:
+ memcpy(&ip6.ip_addr, &saddr->u.sin6.sin6_addr, sizeof(struct in6_addr));
+ ip6.udp_port = saddr->u.sin.sin_port;
+ ip6.sig_weight = sbind->bind->sns_sig_weight;
+ ip6.data_weight = sbind->bind->sns_data_weight;
+ return remove_ip6_elem(gss, elems, &ip6);
+ default:
+ return -1;
+ }
+
+ return -1;
+}
+
static int do_sns_change_weight(struct osmo_fsm_inst *fi, const struct gprs_ns_ie_ip4_elem *ip4, const struct gprs_ns_ie_ip6_elem *ip6)
{
struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
@@ -500,7 +643,7 @@ static int do_sns_change_weight(struct osmo_fsm_inst *fi, const struct gprs_ns_i
* SNS-ACK PDU with a cause code of "Invalid weights". */
if (ip4) {
- if (update_remote_ip4_elem(gss, ip4))
+ if (update_ip4_elem(gss, &gss->remote, ip4))
return -NS_CAUSE_UNKN_IP_EP;
/* copy over. Both data structures use network byte order */
@@ -510,7 +653,7 @@ static int do_sns_change_weight(struct osmo_fsm_inst *fi, const struct gprs_ns_i
new_signal = ip4->sig_weight;
new_data = ip4->data_weight;
} else if (ip6) {
- if (update_remote_ip6_elem(gss, ip6))
+ if (update_ip6_elem(gss, &gss->remote, ip6))
return -NS_CAUSE_UNKN_IP_EP;
/* copy over. Both data structures use network byte order */
@@ -553,14 +696,14 @@ static int do_sns_delete(struct osmo_fsm_inst *fi,
struct osmo_sockaddr sa = {};
if (ip4) {
- if (remove_remote_ip4_elem(gss, ip4) < 0)
+ if (remove_ip4_elem(gss, &gss->remote, ip4) < 0)
return -NS_CAUSE_UNKN_IP_EP;
/* copy over. Both data structures use network byte order */
sa.u.sin.sin_addr.s_addr = ip4->ip_addr;
sa.u.sin.sin_port = ip4->udp_port;
sa.u.sin.sin_family = AF_INET;
} else if (ip6) {
- if (remove_remote_ip6_elem(gss, ip6))
+ if (remove_ip6_elem(gss, &gss->remote, ip6))
return -NS_CAUSE_UNKN_IP_EP;
/* copy over. Both data structures use network byte order */
@@ -597,12 +740,18 @@ static int do_sns_add(struct osmo_fsm_inst *fi,
/* Upon receiving an SNS-ADD PDU, if the consequent number of IPv4 endpoints
* exceeds the number of IPv4 endpoints supported by the NSE, the NSE shall send
* an SNS-ACK PDU with a cause code set to "Invalid number of IP4 Endpoints". */
- switch (gss->ip) {
- case IPv4:
- rc = add_remote_ip4_elem(gss, ip4);
+ switch (gss->family) {
+ case AF_INET:
+ if (gss->remote.num_ip4 >= gss->num_max_ip4_remote)
+ return -NS_CAUSE_INVAL_NR_NS_VC;
+ /* TODO: log message duplicate */
+ rc = add_ip4_elem(gss, &gss->remote, ip4);
break;
- case IPv6:
- rc = add_remote_ip6_elem(gss, ip6);
+ case AF_INET6:
+ if (gss->remote.num_ip6 >= gss->num_max_ip6_remote)
+ return -NS_CAUSE_INVAL_NR_NS_VC;
+ /* TODO: log message duplicate */
+ rc = add_ip6_elem(gss, &gss->remote, ip6);
break;
default:
/* the gss->ip is initialized with the bss */
@@ -610,13 +759,13 @@ static int do_sns_add(struct osmo_fsm_inst *fi,
}
if (rc)
- return rc;
+ return -NS_CAUSE_PROTO_ERR_UNSPEC;
/* Upon receiving an SNS-ADD PDU containing an already configured IP endpoint the
* NSE shall send an SNS-ACK PDU with the cause code "Protocol error -
* unspecified" */
- switch (gss->ip) {
- case IPv4:
+ switch (gss->family) {
+ case AF_INET:
nsvc = nsvc_by_ip4_elem(nse, ip4);
if (nsvc) {
/* the nsvc should be already in sync with the ip4 / ip6 elements */
@@ -626,7 +775,7 @@ static int do_sns_add(struct osmo_fsm_inst *fi,
/* TODO: failure case */
ns2_nsvc_create_ip4(fi, nse, ip4);
break;
- case IPv6:
+ case AF_INET6:
nsvc = nsvc_by_ip6_elem(nse, ip6);
if (nsvc) {
/* the nsvc should be already in sync with the ip4 / ip6 elements */
@@ -644,35 +793,31 @@ static int do_sns_add(struct osmo_fsm_inst *fi,
}
-static void ns2_sns_st_unconfigured(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+static void ns2_sns_st_bss_unconfigured(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
- struct gprs_ns2_nse *nse = nse_inst_from_fi(fi);
- struct gprs_ns2_inst *nsi = nse->nsi;
-
- switch (event) {
- case GPRS_SNS_EV_START:
- osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_SIZE, nsi->timeout[NS_TOUT_TSNS_PROV], 1);
- break;
- default:
- OSMO_ASSERT(0);
- }
+ struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
+ OSMO_ASSERT(gss->role == GPRS_SNS_ROLE_BSS);
+ /* empty state - SNS Select will start by ns2_sns_st_all_action() */
}
-static void ns2_sns_st_size(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+static void ns2_sns_st_bss_size(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
+ struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
struct gprs_ns2_nse *nse = nse_inst_from_fi(fi);
struct gprs_ns2_inst *nsi = nse->nsi;
struct tlv_parsed *tp = NULL;
+ OSMO_ASSERT(gss->role == GPRS_SNS_ROLE_BSS);
+
switch (event) {
- case GPRS_SNS_EV_SIZE_ACK:
+ case NS2_SNS_EV_RX_SIZE_ACK:
tp = data;
if (TLVP_VAL_MINLEN(tp, NS_IE_CAUSE, 1)) {
LOGPFSML(fi, LOGL_ERROR, "SNS-SIZE-ACK with cause %s\n",
gprs_ns2_cause_str(*TLVP_VAL(tp, NS_IE_CAUSE)));
/* TODO: What to do? */
} else {
- osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_CONFIG_BSS,
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_BSS_CONFIG_BSS,
nsi->timeout[NS_TOUT_TSNS_PROV], 2);
}
break;
@@ -681,33 +826,230 @@ static void ns2_sns_st_size(struct osmo_fsm_inst *fi, uint32_t event, void *data
}
}
-static void ns2_sns_st_size_onenter(struct osmo_fsm_inst *fi, uint32_t old_state)
+static int ns2_sns_count_num_local_ep(struct osmo_fsm_inst *fi, int ip_proto)
+{
+ struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
+ struct ns2_sns_bind *sbind;
+ int count = 0;
+
+ llist_for_each_entry(sbind, &gss->binds, list) {
+ const struct osmo_sockaddr *sa = gprs_ns2_ip_bind_sockaddr(sbind->bind);
+ if (!sa)
+ continue;
+
+ switch (ip_proto) {
+ case AF_INET:
+ if (sa->u.sas.ss_family == AF_INET)
+ count++;
+ break;
+ case AF_INET6:
+ if (sa->u.sas.ss_family == AF_INET6)
+ count++;
+ break;
+ }
+ }
+ return count;
+}
+
+static int ns2_sns_copy_local_endpoints(struct ns2_sns_state *gss)
+{
+ switch (gss->family) {
+ case AF_INET:
+ gss->local_procedure.ip4 = talloc_realloc(gss, gss->local_procedure.ip4, struct gprs_ns_ie_ip4_elem,
+ gss->local.num_ip4);
+ if (!gss->local_procedure.ip4)
+ return -ENOMEM;
+
+ gss->local_procedure.num_ip4 = gss->local.num_ip4;
+ memcpy(gss->local_procedure.ip4, gss->local.ip4,
+ sizeof(struct gprs_ns_ie_ip4_elem) * gss->local.num_ip4);
+ break;
+ case AF_INET6:
+ gss->local_procedure.ip6 = talloc_realloc(gss, gss->local_procedure.ip6, struct gprs_ns_ie_ip6_elem,
+ gss->local.num_ip6);
+ if (!gss->local_procedure.ip6)
+ return -ENOMEM;
+
+ gss->local_procedure.num_ip6 = gss->local.num_ip6;
+ memcpy(gss->local_procedure.ip6, gss->local.ip6,
+ sizeof(struct gprs_ns_ie_ip6_elem) * gss->local.num_ip6);
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+
+ return 0;
+}
+
+static void ns2_sns_compute_local_ep_from_binds(struct osmo_fsm_inst *fi)
+{
+ struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
+ struct gprs_ns_ie_ip4_elem *ip4_elems;
+ struct gprs_ns_ie_ip6_elem *ip6_elems;
+ struct gprs_ns2_vc_bind *bind;
+ struct ns2_sns_bind *sbind;
+ const struct osmo_sockaddr *remote;
+ const struct osmo_sockaddr *sa;
+ struct osmo_sockaddr local;
+ int count;
+
+ ns2_clear_elems(&gss->local);
+
+ /* no initial available */
+ if (gss->role == GPRS_SNS_ROLE_BSS) {
+ if (!gss->initial)
+ return;
+ remote = &gss->initial->saddr;
+ } else
+ remote = gprs_ns2_ip_vc_remote(gss->sns_nsvc);
+
+ /* count how many bindings are available (only UDP binds) */
+ count = llist_count(&gss->binds);
+ if (count == 0) {
+ LOGPFSML(fi, LOGL_ERROR, "No local binds for this NSE -> cannot determine IP endpoints\n");
+ return;
+ }
+
+ switch (gss->family) {
+ case AF_INET:
+ ip4_elems = talloc_realloc(fi, gss->local.ip4, struct gprs_ns_ie_ip4_elem, count);
+ if (!ip4_elems)
+ return;
+
+ gss->local.ip4 = ip4_elems;
+ llist_for_each_entry(sbind, &gss->binds, list) {
+ bind = sbind->bind;
+ sa = gprs_ns2_ip_bind_sockaddr(bind);
+ if (!sa)
+ continue;
+
+ if (sa->u.sas.ss_family != AF_INET)
+ continue;
+
+ /* check if this is an specific bind */
+ if (sa->u.sin.sin_addr.s_addr == 0) {
+ if (osmo_sockaddr_local_ip(&local, remote))
+ continue;
+
+ ip4_elems->ip_addr = local.u.sin.sin_addr.s_addr;
+ } else {
+ ip4_elems->ip_addr = sa->u.sin.sin_addr.s_addr;
+ }
+
+ ip4_elems->udp_port = sa->u.sin.sin_port;
+ ip4_elems->sig_weight = bind->sns_sig_weight;
+ ip4_elems->data_weight = bind->sns_data_weight;
+ ip4_elems++;
+ }
+
+ gss->local.num_ip4 = count;
+ gss->num_max_nsvcs = OSMO_MAX(gss->num_max_ip4_remote * gss->local.num_ip4, 8);
+ break;
+ case AF_INET6:
+ /* IPv6 */
+ ip6_elems = talloc_realloc(fi, gss->local.ip6, struct gprs_ns_ie_ip6_elem, count);
+ if (!ip6_elems)
+ return;
+
+ gss->local.ip6 = ip6_elems;
+
+ llist_for_each_entry(sbind, &gss->binds, list) {
+ bind = sbind->bind;
+ sa = gprs_ns2_ip_bind_sockaddr(bind);
+ if (!sa)
+ continue;
+
+ if (sa->u.sas.ss_family != AF_INET6)
+ continue;
+
+ /* check if this is an specific bind */
+ if (IN6_IS_ADDR_UNSPECIFIED(&sa->u.sin6.sin6_addr)) {
+ if (osmo_sockaddr_local_ip(&local, remote))
+ continue;
+
+ ip6_elems->ip_addr = local.u.sin6.sin6_addr;
+ } else {
+ ip6_elems->ip_addr = sa->u.sin6.sin6_addr;
+ }
+
+ ip6_elems->udp_port = sa->u.sin.sin_port;
+ ip6_elems->sig_weight = bind->sns_sig_weight;
+ ip6_elems->data_weight = bind->sns_data_weight;
+
+ ip6_elems++;
+ }
+ gss->local.num_ip6 = count;
+ gss->num_max_nsvcs = OSMO_MAX(gss->num_max_ip6_remote * gss->local.num_ip6, 8);
+ break;
+ }
+
+ ns2_sns_copy_local_endpoints(gss);
+}
+
+static void ns2_sns_choose_next_bind(struct ns2_sns_state *gss)
+{
+ /* take the first bind or take the next bind */
+ if (!gss->initial_bind || gss->initial_bind->list.next == &gss->binds)
+ gss->initial_bind = llist_first_entry_or_null(&gss->binds, struct ns2_sns_bind, list);
+ else
+ gss->initial_bind = llist_entry(gss->initial_bind->list.next, struct ns2_sns_bind, list);
+}
+
+/* setup all dynamic SNS settings, create a new nsvc and send the SIZE */
+static void ns2_sns_st_bss_size_onenter(struct osmo_fsm_inst *fi, uint32_t old_state)
{
struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
+ OSMO_ASSERT(gss->role == GPRS_SNS_ROLE_BSS);
+
+ /* on a generic failure, the timer callback will recover */
if (old_state != GPRS_SNS_ST_UNCONFIGURED)
- ns2_prim_status_ind(gss->nse, NULL, 0, NS_AFF_CAUSE_SNS_FAILURE);
+ ns2_prim_status_ind(gss->nse, NULL, 0, GPRS_NS2_AFF_CAUSE_SNS_FAILURE);
+ if (old_state != GPRS_SNS_ST_BSS_SIZE)
+ gss->N = 0;
+
+ ns2_clear_procedures(gss);
+ gss->alive = false;
+
+ ns2_sns_compute_local_ep_from_binds(fi);
+ ns2_sns_choose_next_bind(gss);
+
+ /* setup the NSVC */
+ if (!gss->sns_nsvc) {
+ struct gprs_ns2_vc_bind *bind = gss->initial_bind->bind;
+ struct osmo_sockaddr *remote = &gss->initial->saddr;
+ gss->sns_nsvc = ns2_ip_bind_connect(bind, gss->nse, remote);
+ if (!gss->sns_nsvc)
+ return;
+ /* A pre-configured endpoint shall not be used for NSE data or signalling traffic
+ * (with the exception of Size and Configuration procedures) unless it is configured
+ * by the SGSN using the auto-configuration procedures */
+ gss->sns_nsvc->sns_only = true;
+ }
if (gss->num_max_ip4_remote > 0)
- ns2_tx_sns_size(gss->sns_nsvc, true, gss->num_max_nsvcs, gss->num_max_ip4_remote, -1);
+ ns2_tx_sns_size(gss->sns_nsvc, true, gss->num_max_nsvcs, gss->local.num_ip4, -1);
else
- ns2_tx_sns_size(gss->sns_nsvc, true, gss->num_max_nsvcs, -1, gss->num_max_ip6_remote);
-
+ ns2_tx_sns_size(gss->sns_nsvc, true, gss->num_max_nsvcs, -1, gss->local.num_ip6);
}
-static void ns2_sns_st_config_bss(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+static void ns2_sns_st_bss_config_bss(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
+ struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
+ struct gprs_ns2_nse *nse = nse_inst_from_fi(fi);
struct tlv_parsed *tp = NULL;
+ OSMO_ASSERT(gss->role == GPRS_SNS_ROLE_BSS);
+
switch (event) {
- case GPRS_SNS_EV_CONFIG_ACK:
+ case NS2_SNS_EV_RX_CONFIG_ACK:
tp = (struct tlv_parsed *) data;
if (TLVP_VAL_MINLEN(tp, NS_IE_CAUSE, 1)) {
LOGPFSML(fi, LOGL_ERROR, "SNS-CONFIG-ACK with cause %s\n",
gprs_ns2_cause_str(*TLVP_VAL(tp, NS_IE_CAUSE)));
/* TODO: What to do? */
} else {
- osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_CONFIG_SGSN, 0, 0);
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_BSS_CONFIG_SGSN, nse->nsi->timeout[NS_TOUT_TSNS_PROV], 3);
}
break;
default:
@@ -715,152 +1057,139 @@ static void ns2_sns_st_config_bss(struct osmo_fsm_inst *fi, uint32_t event, void
}
}
-static void ns2_sns_st_config_bss_onenter(struct osmo_fsm_inst *fi, uint32_t old_state)
+static void ns2_sns_st_bss_config_bss_onenter(struct osmo_fsm_inst *fi, uint32_t old_state)
{
struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
+
+ OSMO_ASSERT(gss->role == GPRS_SNS_ROLE_BSS);
+
+ if (old_state != GPRS_SNS_ST_BSS_CONFIG_BSS)
+ gss->N = 0;
+
/* Transmit SNS-CONFIG */
- /* TODO: ipv6 */
- switch (gss->ip) {
- case IPv4:
+ switch (gss->family) {
+ case AF_INET:
ns2_tx_sns_config(gss->sns_nsvc, true,
- gss->ip4_local, gss->num_ip4_local,
- NULL, 0);
+ gss->local.ip4, gss->local.num_ip4,
+ NULL, 0);
break;
- case IPv6:
+ case AF_INET6:
ns2_tx_sns_config(gss->sns_nsvc, true,
- NULL, 0,
- gss->ip6_local, gss->num_ip6_local);
+ NULL, 0,
+ gss->local.ip6, gss->local.num_ip6);
break;
}
}
+/* calculate the timeout of the configured state. the configured
+ * state will fail if not at least one NS-VC is alive within X second.
+ */
+static inline int ns_sns_configured_timeout(struct osmo_fsm_inst *fi)
+{
+ int secs;
+ struct gprs_ns2_inst *nsi = nse_inst_from_fi(fi)->nsi;
+ secs = nsi->timeout[NS_TOUT_TNS_ALIVE] * nsi->timeout[NS_TOUT_TNS_ALIVE_RETRIES];
+ secs += nsi->timeout[NS_TOUT_TNS_TEST];
+
+ return secs;
+}
-static void ns_sns_st_config_sgsn_ip4(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+/* append the remote endpoints from the parsed TLV array to the ns2_sns_state */
+static int ns_sns_append_remote_eps(struct osmo_fsm_inst *fi, const struct tlv_parsed *tp)
{
struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
- struct gprs_ns2_nse *nse = nse_inst_from_fi(fi);
- const struct gprs_ns_ie_ip4_elem *v4_list;
- unsigned int num_v4;
- struct tlv_parsed *tp = NULL;
- uint8_t cause;
+ if (TLVP_PRESENT(tp, NS_IE_IPv4_LIST)) {
+ const struct gprs_ns_ie_ip4_elem *v4_list;
+ unsigned int num_v4;
+ v4_list = (const struct gprs_ns_ie_ip4_elem *) TLVP_VAL(tp, NS_IE_IPv4_LIST);
+ num_v4 = TLVP_LEN(tp, NS_IE_IPv4_LIST) / sizeof(*v4_list);
- tp = (struct tlv_parsed *) data;
+ if (num_v4 && gss->remote.ip6)
+ return -NS_CAUSE_INVAL_NR_IPv4_EP;
- if (!TLVP_PRESENT(tp, NS_IE_IPv4_LIST)) {
- cause = NS_CAUSE_INVAL_NR_IPv4_EP;
- ns2_tx_sns_config_ack(gss->sns_nsvc, &cause);
- osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_UNCONFIGURED, 0, 0);
- return;
+ /* realloc to the new size */
+ gss->remote.ip4 = talloc_realloc(gss, gss->remote.ip4,
+ struct gprs_ns_ie_ip4_elem,
+ gss->remote.num_ip4 + num_v4);
+ /* append the new entries to the end of the list */
+ memcpy(&gss->remote.ip4[gss->remote.num_ip4], v4_list, num_v4*sizeof(*v4_list));
+ gss->remote.num_ip4 += num_v4;
+
+ LOGPFSML(fi, LOGL_INFO, "Rx SNS-CONFIG: Remote IPv4 list now %u entries\n",
+ gss->remote.num_ip4);
}
- v4_list = (const struct gprs_ns_ie_ip4_elem *) TLVP_VAL(tp, NS_IE_IPv4_LIST);
- num_v4 = TLVP_LEN(tp, NS_IE_IPv4_LIST) / sizeof(*v4_list);
- /* realloc to the new size */
- gss->ip4_remote = talloc_realloc(gss, gss->ip4_remote,
- struct gprs_ns_ie_ip4_elem,
- gss->num_ip4_remote+num_v4);
- /* append the new entries to the end of the list */
- memcpy(&gss->ip4_remote[gss->num_ip4_remote], v4_list, num_v4*sizeof(*v4_list));
- gss->num_ip4_remote += num_v4;
-
- LOGPFSML(fi, LOGL_INFO, "Rx SNS-CONFIG: Remote IPv4 list now %u entries\n",
- gss->num_ip4_remote);
- if (event == GPRS_SNS_EV_CONFIG_END) {
- /* check if sum of data / sig weights == 0 */
- if (ip4_weight_sum_data(gss->ip4_remote, gss->num_ip4_remote) == 0 ||
- ip4_weight_sum_sig(gss->ip4_remote, gss->num_ip4_remote) == 0) {
- cause = NS_CAUSE_INVAL_WEIGH;
- ns2_tx_sns_config_ack(gss->sns_nsvc, &cause);
- osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_UNCONFIGURED, 0, 0);
- return;
- }
- create_missing_nsvcs(fi);
- ns2_tx_sns_config_ack(gss->sns_nsvc, NULL);
- /* start the test procedure on ALL NSVCs! */
- gprs_ns2_start_alive_all_nsvcs(nse);
- osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_CONFIGURED, 0, 0);
- } else {
- /* just send CONFIG-ACK */
- ns2_tx_sns_config_ack(gss->sns_nsvc, NULL);
+
+ if (TLVP_PRESENT(tp, NS_IE_IPv6_LIST)) {
+ const struct gprs_ns_ie_ip6_elem *v6_list;
+ unsigned int num_v6;
+ v6_list = (const struct gprs_ns_ie_ip6_elem *) TLVP_VAL(tp, NS_IE_IPv6_LIST);
+ num_v6 = TLVP_LEN(tp, NS_IE_IPv6_LIST) / sizeof(*v6_list);
+
+ if (num_v6 && gss->remote.ip4)
+ return -NS_CAUSE_INVAL_NR_IPv6_EP;
+
+ /* realloc to the new size */
+ gss->remote.ip6 = talloc_realloc(gss, gss->remote.ip6,
+ struct gprs_ns_ie_ip6_elem,
+ gss->remote.num_ip6 + num_v6);
+ /* append the new entries to the end of the list */
+ memcpy(&gss->remote.ip6[gss->remote.num_ip6], v6_list, num_v6*sizeof(*v6_list));
+ gss->remote.num_ip6 += num_v6;
+
+ LOGPFSML(fi, LOGL_INFO, "Rx SNS-CONFIG: Remote IPv6 list now %d entries\n",
+ gss->remote.num_ip6);
}
+
+ return 0;
}
-static void ns_sns_st_config_sgsn_ip6(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+static void ns2_sns_st_bss_config_sgsn_onenter(struct osmo_fsm_inst *fi, uint32_t old_state)
{
struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
- struct gprs_ns2_nse *nse = nse_inst_from_fi(fi);
- const struct gprs_ns_ie_ip6_elem *v6_list;
- unsigned int num_v6;
- struct tlv_parsed *tp = NULL;
-
- uint8_t cause;
- tp = (struct tlv_parsed *) data;
+ OSMO_ASSERT(gss->role == GPRS_SNS_ROLE_BSS);
- if (!TLVP_PRESENT(tp, NS_IE_IPv6_LIST)) {
- cause = NS_CAUSE_INVAL_NR_IPv6_EP;
- ns2_tx_sns_config_ack(gss->sns_nsvc, &cause);
- osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_UNCONFIGURED, 0, 0);
- return;
- }
- v6_list = (const struct gprs_ns_ie_ip6_elem *) TLVP_VAL(tp, NS_IE_IPv6_LIST);
- num_v6 = TLVP_LEN(tp, NS_IE_IPv6_LIST) / sizeof(*v6_list);
- /* realloc to the new size */
- gss->ip6_remote = talloc_realloc(gss, gss->ip6_remote,
- struct gprs_ns_ie_ip6_elem,
- gss->num_ip6_remote+num_v6);
- /* append the new entries to the end of the list */
- memcpy(&gss->ip6_remote[gss->num_ip6_remote], v6_list, num_v6*sizeof(*v6_list));
- gss->num_ip6_remote += num_v6;
-
- LOGPFSML(fi, LOGL_INFO, "Rx SNS-CONFIG: Remote IPv6 list now %u entries\n",
- gss->num_ip6_remote);
- if (event == GPRS_SNS_EV_CONFIG_END) {
- /* check if sum of data / sig weights == 0 */
- if (ip6_weight_sum_data(gss->ip6_remote, gss->num_ip6_remote) == 0 ||
- ip6_weight_sum_sig(gss->ip6_remote, gss->num_ip6_remote) == 0) {
- cause = NS_CAUSE_INVAL_WEIGH;
- ns2_tx_sns_config_ack(gss->sns_nsvc, &cause);
- osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_UNCONFIGURED, 0, 0);
- return;
- }
- create_missing_nsvcs(fi);
- ns2_tx_sns_config_ack(gss->sns_nsvc, NULL);
- /* start the test procedure on ALL NSVCs! */
- gprs_ns2_start_alive_all_nsvcs(nse);
- osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_CONFIGURED, 0, 0);
- } else {
- /* just send CONFIG-ACK */
- ns2_tx_sns_config_ack(gss->sns_nsvc, NULL);
- }
+ if (old_state != GPRS_SNS_ST_BSS_CONFIG_SGSN)
+ gss->N = 0;
}
-static void ns2_sns_st_config_sgsn(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+static void ns2_sns_st_bss_config_sgsn(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
+ struct gprs_ns2_nse *nse = nse_inst_from_fi(fi);
+ uint8_t cause;
+ int rc;
+
+ OSMO_ASSERT(gss->role == GPRS_SNS_ROLE_BSS);
switch (event) {
- case GPRS_SNS_EV_CONFIG_END:
- case GPRS_SNS_EV_CONFIG:
-
-#if 0 /* part of incoming SNS-SIZE (doesn't happen on BSS side */
- if (TLVP_PRESENT(tp, NS_IE_RESET_FLAG)) {
- /* reset all existing config */
- if (gss->ip4_remote)
- talloc_free(gss->ip4_remote);
- gss->num_ip4_remote = 0;
+ case NS2_SNS_EV_RX_CONFIG_END:
+ case NS2_SNS_EV_RX_CONFIG:
+ rc = ns_sns_append_remote_eps(fi, data);
+ if (rc < 0) {
+ cause = -rc;
+ ns2_tx_sns_config_ack(gss->sns_nsvc, &cause);
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_UNCONFIGURED, 0, 0);
+ return;
}
-#endif
- /* TODO: reject IPv6 elements on IPv4 mode and vice versa */
- switch (gss->ip) {
- case IPv4:
- ns_sns_st_config_sgsn_ip4(fi, event, data);
- break;
- case IPv6:
- ns_sns_st_config_sgsn_ip6(fi, event, data);
- break;
- default:
- OSMO_ASSERT(0);
+ if (event == NS2_SNS_EV_RX_CONFIG_END) {
+ /* check if sum of data / sig weights == 0 */
+ if (ip46_weight_sum_data(&gss->remote) == 0 || ip46_weight_sum_sig(&gss->remote) == 0) {
+ cause = NS_CAUSE_INVAL_WEIGH;
+ ns2_tx_sns_config_ack(gss->sns_nsvc, &cause);
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_UNCONFIGURED, 0, 0);
+ return;
+ }
+ create_missing_nsvcs(fi);
+ ns2_tx_sns_config_ack(gss->sns_nsvc, NULL);
+ /* start the test procedure on ALL NSVCs! */
+ gprs_ns2_start_alive_all_nsvcs(nse);
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_CONFIGURED, 0, 0);
+ } else {
+ /* just send CONFIG-ACK */
+ ns2_tx_sns_config_ack(gss->sns_nsvc, NULL);
+ osmo_timer_schedule(&fi->timer, nse->nsi->timeout[NS_TOUT_TSNS_PROV], 0);
}
break;
default:
@@ -868,7 +1197,7 @@ static void ns2_sns_st_config_sgsn(struct osmo_fsm_inst *fi, uint32_t event, voi
}
}
-/* called when receiving GPRS_SNS_EV_ADD in state configure */
+/* called when receiving NS2_SNS_EV_RX_ADD in state configure */
static void ns2_sns_st_configured_add(struct osmo_fsm_inst *fi,
struct ns2_sns_state *gss,
struct tlv_parsed *tp)
@@ -887,7 +1216,7 @@ static void ns2_sns_st_configured_add(struct osmo_fsm_inst *fi,
*/
trans_id = *TLVP_VAL(tp, NS_IE_TRANS_ID);
- if (gss->ip == IPv4) {
+ if (gss->family == AF_INET) {
if (!TLVP_PRESENT(tp, NS_IE_IPv4_LIST)) {
cause = NS_CAUSE_INVAL_NR_IPv4_EP;
ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0);
@@ -951,7 +1280,7 @@ static void ns2_sns_st_configured_delete(struct osmo_fsm_inst *fi,
* TODO: check if IPv4_LIST/IPv6_LIST and IP_ADDR is present at the same time
*/
trans_id = *TLVP_VAL(tp, NS_IE_TRANS_ID);
- if (gss->ip == IPv4) {
+ if (gss->family == AF_INET) {
if (TLVP_PRESENT(tp, NS_IE_IPv4_LIST)) {
v4_list = (const struct gprs_ns_ie_ip4_elem *) TLVP_VAL(tp, NS_IE_IPv4_LIST);
num_v4 = TLVP_LEN(tp, NS_IE_IPv4_LIST) / sizeof(*v4_list);
@@ -979,9 +1308,9 @@ static void ns2_sns_st_configured_delete(struct osmo_fsm_inst *fi,
return;
}
/* make a copy as do_sns_delete() will change the array underneath us */
- ip4_remote = talloc_memdup(fi, gss->ip4_remote,
- gss->num_ip4_remote * sizeof(*v4_list));
- for (i = 0; i < gss->num_ip4_remote; i++) {
+ ip4_remote = talloc_memdup(fi, gss->remote.ip4,
+ gss->remote.num_ip4 * sizeof(*v4_list));
+ for (i = 0; i < gss->remote.num_ip4; i++) {
if (ip4_remote[i].ip_addr == ip_addr) {
rc = do_sns_delete(fi, &ip4_remote[i], NULL);
if (rc < 0) {
@@ -1030,9 +1359,9 @@ static void ns2_sns_st_configured_delete(struct osmo_fsm_inst *fi,
}
memcpy(&ip6_addr, (ie+1), sizeof(struct in6_addr));
/* make a copy as do_sns_delete() will change the array underneath us */
- ip6_remote = talloc_memdup(fi, gss->ip6_remote,
- gss->num_ip6_remote * sizeof(*v4_list));
- for (i = 0; i < gss->num_ip6_remote; i++) {
+ ip6_remote = talloc_memdup(fi, gss->remote.ip6,
+ gss->remote.num_ip6 * sizeof(*v4_list));
+ for (i = 0; i < gss->remote.num_ip6; i++) {
if (!memcmp(&ip6_remote[i].ip_addr, &ip6_addr, sizeof(struct in6_addr))) {
rc = do_sns_delete(fi, NULL, &ip6_remote[i]);
if (rc < 0) {
@@ -1111,350 +1440,963 @@ static void ns2_sns_st_configured(struct osmo_fsm_inst *fi, uint32_t event, void
struct tlv_parsed *tp = data;
switch (event) {
- case GPRS_SNS_EV_ADD:
+ case NS2_SNS_EV_RX_ADD:
ns2_sns_st_configured_add(fi, gss, tp);
break;
- case GPRS_SNS_EV_DELETE:
+ case NS2_SNS_EV_RX_DELETE:
ns2_sns_st_configured_delete(fi, gss, tp);
break;
- case GPRS_SNS_EV_CHANGE_WEIGHT:
+ case NS2_SNS_EV_RX_CHANGE_WEIGHT:
ns2_sns_st_configured_change(fi, gss, tp);
break;
+ case NS2_SNS_EV_REQ_NSVC_ALIVE:
+ osmo_timer_del(&fi->timer);
+ break;
}
}
static void ns2_sns_st_configured_onenter(struct osmo_fsm_inst *fi, uint32_t old_state)
{
+ struct gprs_ns2_vc *nsvc;
+ struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
+ struct gprs_ns2_nse *nse = nse_inst_from_fi(fi);
+ /* NS-VC status updates are only parsed in ST_CONFIGURED.
+ * Do an initial check if there are any nsvc alive atm */
+ llist_for_each_entry(nsvc, &nse->nsvc, list) {
+ if (ns2_vc_is_unblocked(nsvc)) {
+ gss->alive = true;
+ osmo_timer_del(&fi->timer);
+ break;
+ }
+ }
+
+ /* remove the initial NSVC if the NSVC isn't part of the configuration */
+ if (gss->sns_nsvc->sns_only)
+ gprs_ns2_free_nsvc(gss->sns_nsvc);
+
+ if (old_state != GPRS_SNS_ST_LOCAL_PROCEDURE)
+ ns2_prim_status_ind(nse, NULL, 0, GPRS_NS2_AFF_CAUSE_SNS_CONFIGURED);
+
+ if (!llist_empty(&gss->procedures)) {
+ osmo_fsm_inst_state_chg(gss->nse->bss_sns_fi, GPRS_SNS_ST_LOCAL_PROCEDURE,
+ gss->nse->nsi->timeout[NS_TOUT_TSNS_PROV], 5);
+ }
+}
+
+static void ns2_sns_st_local_procedure_onenter(struct osmo_fsm_inst *fi, uint32_t old_state)
+{
+ struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
+
+ /* check if resend or not */
+ if (!gss->current_procedure) {
+ /* take next procedure */
+ gss->current_procedure = llist_first_entry_or_null(&gss->procedures,
+ struct ns2_sns_procedure, list);
+ if (!gss->current_procedure) {
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_CONFIGURED, 0, 0);
+ return;
+ }
+ gss->N = 0;
+ gss->current_procedure->running = true;
+ gss->current_procedure->trans_id = ++gss->trans_id;
+ if (gss->trans_id == 0)
+ gss->trans_id = gss->current_procedure->trans_id = 1;
+
+ }
+
+ /* also takes care of retransmitting */
+ switch (gss->current_procedure->procedure) {
+ case SNS_PROC_ADD:
+ if (gss->family == AF_INET)
+ ns2_tx_sns_add(gss->sns_nsvc, gss->current_procedure->trans_id, &gss->current_procedure->ip4, 1, NULL, 0);
+ else
+ ns2_tx_sns_add(gss->sns_nsvc, gss->current_procedure->trans_id, NULL, 0, &gss->current_procedure->ip6, 1);
+ break;
+ case SNS_PROC_CHANGE_WEIGHT:
+ if (gss->family == AF_INET)
+ ns2_tx_sns_change_weight(gss->sns_nsvc, gss->current_procedure->trans_id, &gss->current_procedure->ip4, 1, NULL, 0);
+ else
+ ns2_tx_sns_change_weight(gss->sns_nsvc, gss->current_procedure->trans_id, NULL, 0, &gss->current_procedure->ip6, 1);
+ break;
+ case SNS_PROC_DEL:
+ if (gss->family == AF_INET)
+ ns2_tx_sns_del(gss->sns_nsvc, gss->current_procedure->trans_id, &gss->current_procedure->ip4, 1, NULL, 0);
+ else
+ ns2_tx_sns_del(gss->sns_nsvc, gss->current_procedure->trans_id, NULL, 0, &gss->current_procedure->ip6, 1);
+ break;
+ default:
+ break;
+ }
+}
+
+static void create_nsvc_for_new_sbind(struct ns2_sns_state *gss, struct ns2_sns_bind *sbind)
+{
+ struct gprs_ns2_nse *nse = gss->nse;
+ struct gprs_ns2_vc_bind *bind = sbind->bind;
+ struct gprs_ns2_vc *nsvc;
+ struct osmo_sockaddr remote = { };
+ unsigned int i;
+
+ /* iterate over all remote IPv4 endpoints */
+ for (i = 0; i < gss->remote.num_ip4; i++) {
+ const struct gprs_ns_ie_ip4_elem *ip4 = &gss->remote.ip4[i];
+
+ remote.u.sin.sin_family = AF_INET;
+ remote.u.sin.sin_addr.s_addr = ip4->ip_addr;
+ remote.u.sin.sin_port = ip4->udp_port;
+ /* we only care about UDP binds */
+ if (bind->ll != GPRS_NS2_LL_UDP)
+ continue;
+
+ nsvc = nsvc_for_bind_and_remote(nse, bind, &remote);
+ if (!nsvc) {
+ nsvc = gprs_ns2_ip_connect_inactive(bind, &remote, nse, 0);
+ if (!nsvc) {
+ /* TODO: add to a list to send back a NS-STATUS */
+ continue;
+ }
+ }
+
+ /* update data / signalling weight */
+ nsvc->data_weight = ip4->data_weight;
+ nsvc->sig_weight = ip4->sig_weight;
+ nsvc->sns_only = false;
+ }
+
+ /* iterate over all remote IPv4 endpoints */
+ for (i = 0; i < gss->remote.num_ip6; i++) {
+ const struct gprs_ns_ie_ip6_elem *ip6 = &gss->remote.ip6[i];
+
+ remote.u.sin6.sin6_family = AF_INET6;
+ remote.u.sin6.sin6_addr = ip6->ip_addr;
+ remote.u.sin6.sin6_port = ip6->udp_port;
+
+ /* we only care about UDP binds */
+ nsvc = nsvc_for_bind_and_remote(nse, bind, &remote);
+ if (!nsvc) {
+ nsvc = gprs_ns2_ip_connect_inactive(bind, &remote, nse, 0);
+ if (!nsvc) {
+ /* TODO: add to a list to send back a NS-STATUS */
+ continue;
+ }
+ }
+
+ /* update data / signalling weight */
+ nsvc->data_weight = ip6->data_weight;
+ nsvc->sig_weight = ip6->sig_weight;
+ nsvc->sns_only = false;
+ }
+}
+
+static void ns2_sns_st_local_procedure(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
struct gprs_ns2_nse *nse = nse_inst_from_fi(fi);
- ns2_prim_status_ind(nse, NULL, 0, NS_AFF_CAUSE_SNS_CONFIGURED);
+ struct gprs_ns_ie_ip4_elem *ip4, *proc4;
+ struct gprs_ns_ie_ip6_elem *ip6, *proc6;
+ struct tlv_parsed *tp = data;
+ uint8_t trans_id;
+ uint8_t cause;
+
+ switch (event) {
+ case NS2_SNS_EV_RX_ADD:
+ ns2_sns_st_configured_add(fi, gss, tp);
+ break;
+ case NS2_SNS_EV_RX_DELETE:
+ ns2_sns_st_configured_delete(fi, gss, tp);
+ break;
+ case NS2_SNS_EV_RX_CHANGE_WEIGHT:
+ ns2_sns_st_configured_change(fi, gss, tp);
+ break;
+ case NS2_SNS_EV_RX_ACK:
+ /* presence of trans_id is already checked here */
+ trans_id = tlvp_val8(tp, NS_IE_TRANS_ID, 0);
+ if (trans_id != gss->current_procedure->trans_id) {
+ LOGPFSML(fi, LOGL_INFO, "NSEI=%u Rx SNS ACK with invalid transaction id %d. Valid %d\n",
+ nse->nsei, trans_id, gss->current_procedure->trans_id);
+ break;
+ }
+
+ if (TLVP_PRESENT(tp, NS_IE_CAUSE)) {
+ /* what happend on error cause? return to size? */
+ cause = tlvp_val8(tp, NS_IE_CAUSE, 0);
+ LOGPFSML(fi, LOGL_ERROR, "NSEI=%u Rx SNS ACK trans %d with cause code %d.\n",
+ nse->nsei, trans_id, cause);
+ sns_failed(fi, NULL);
+ break;
+ }
+
+ switch (gss->current_procedure->procedure) {
+ case SNS_PROC_ADD:
+ switch (gss->family) {
+ case AF_INET:
+ add_ip4_elem(gss, &gss->local, &gss->current_procedure->ip4);
+ break;
+ case AF_INET6:
+ add_ip6_elem(gss, &gss->local, &gss->current_procedure->ip6);
+ break;
+ }
+ /* the sbind can be NULL if the bind has been released by del_bind */
+ if (gss->current_procedure->sbind) {
+ create_nsvc_for_new_sbind(gss, gss->current_procedure->sbind);
+ gprs_ns2_start_alive_all_nsvcs(nse);
+ }
+ break;
+ case SNS_PROC_CHANGE_WEIGHT:
+ switch (gss->family) {
+ case AF_INET:
+ proc4 = &gss->current_procedure->ip4;
+ for (unsigned int i=0; i<gss->local.num_ip4; i++) {
+ ip4 = &gss->local.ip4[i];
+ if (ip4->ip_addr != proc4->ip_addr ||
+ ip4->udp_port != proc4->udp_port)
+ continue;
+ ip4->sig_weight = proc4->sig_weight;
+ ip4->data_weight = proc4->data_weight;
+ break;
+ }
+ break;
+ case AF_INET6:
+ proc6 = &gss->current_procedure->ip6;
+ for (unsigned int i=0; i<gss->local.num_ip6; i++) {
+ ip6 = &gss->local.ip6[i];
+ if (memcmp(&ip6->ip_addr, &proc6->ip_addr, sizeof(proc6->ip_addr)) ||
+ ip6->udp_port != proc6->udp_port) {
+ continue;
+ }
+ ip6->sig_weight = proc6->sig_weight;
+ ip6->data_weight = proc6->data_weight;
+ break;
+ }
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+ break;
+ case SNS_PROC_DEL:
+ switch (gss->family) {
+ case AF_INET:
+ remove_ip4_elem(gss, &gss->local, &gss->current_procedure->ip4);
+ break;
+ case AF_INET6:
+ remove_ip6_elem(gss, &gss->local, &gss->current_procedure->ip6);
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+
+ llist_del(&gss->current_procedure->list);
+ talloc_free(gss->current_procedure);
+ gss->current_procedure = NULL;
+
+ if (llist_empty(&gss->procedures))
+ osmo_fsm_inst_state_chg(gss->nse->bss_sns_fi, GPRS_SNS_ST_CONFIGURED,
+ 0, 0);
+ else
+ osmo_fsm_inst_state_chg(gss->nse->bss_sns_fi, GPRS_SNS_ST_LOCAL_PROCEDURE,
+ gss->nse->nsi->timeout[NS_TOUT_TSNS_PROV], 5);
+ break;
+ }
}
static const struct osmo_fsm_state ns2_sns_bss_states[] = {
[GPRS_SNS_ST_UNCONFIGURED] = {
- .in_event_mask = S(GPRS_SNS_EV_START),
- .out_state_mask = S(GPRS_SNS_ST_SIZE),
+ .in_event_mask = 0, /* handled by all_state_action */
+ .out_state_mask = S(GPRS_SNS_ST_UNCONFIGURED) |
+ S(GPRS_SNS_ST_BSS_SIZE),
.name = "UNCONFIGURED",
- .action = ns2_sns_st_unconfigured,
+ .action = ns2_sns_st_bss_unconfigured,
},
- [GPRS_SNS_ST_SIZE] = {
- .in_event_mask = S(GPRS_SNS_EV_SIZE_ACK),
+ [GPRS_SNS_ST_BSS_SIZE] = {
+ .in_event_mask = S(NS2_SNS_EV_RX_SIZE_ACK),
.out_state_mask = S(GPRS_SNS_ST_UNCONFIGURED) |
- S(GPRS_SNS_ST_SIZE) |
- S(GPRS_SNS_ST_CONFIG_BSS),
- .name = "SIZE",
- .action = ns2_sns_st_size,
- .onenter = ns2_sns_st_size_onenter,
+ S(GPRS_SNS_ST_BSS_SIZE) |
+ S(GPRS_SNS_ST_BSS_CONFIG_BSS),
+ .name = "BSS_SIZE",
+ .action = ns2_sns_st_bss_size,
+ .onenter = ns2_sns_st_bss_size_onenter,
},
- [GPRS_SNS_ST_CONFIG_BSS] = {
- .in_event_mask = S(GPRS_SNS_EV_CONFIG_ACK),
+ [GPRS_SNS_ST_BSS_CONFIG_BSS] = {
+ .in_event_mask = S(NS2_SNS_EV_RX_CONFIG_ACK),
.out_state_mask = S(GPRS_SNS_ST_UNCONFIGURED) |
- S(GPRS_SNS_ST_CONFIG_BSS) |
- S(GPRS_SNS_ST_CONFIG_SGSN) |
- S(GPRS_SNS_ST_SIZE),
- .name = "CONFIG_BSS",
- .action = ns2_sns_st_config_bss,
- .onenter = ns2_sns_st_config_bss_onenter,
+ S(GPRS_SNS_ST_BSS_CONFIG_BSS) |
+ S(GPRS_SNS_ST_BSS_CONFIG_SGSN) |
+ S(GPRS_SNS_ST_BSS_SIZE),
+ .name = "BSS_CONFIG_BSS",
+ .action = ns2_sns_st_bss_config_bss,
+ .onenter = ns2_sns_st_bss_config_bss_onenter,
},
- [GPRS_SNS_ST_CONFIG_SGSN] = {
- .in_event_mask = S(GPRS_SNS_EV_CONFIG) |
- S(GPRS_SNS_EV_CONFIG_END),
+ [GPRS_SNS_ST_BSS_CONFIG_SGSN] = {
+ .in_event_mask = S(NS2_SNS_EV_RX_CONFIG) |
+ S(NS2_SNS_EV_RX_CONFIG_END),
.out_state_mask = S(GPRS_SNS_ST_UNCONFIGURED) |
- S(GPRS_SNS_ST_CONFIG_SGSN) |
+ S(GPRS_SNS_ST_BSS_CONFIG_SGSN) |
S(GPRS_SNS_ST_CONFIGURED) |
- S(GPRS_SNS_ST_SIZE),
- .name = "CONFIG_SGSN",
- .action = ns2_sns_st_config_sgsn,
+ S(GPRS_SNS_ST_BSS_SIZE),
+ .name = "BSS_CONFIG_SGSN",
+ .action = ns2_sns_st_bss_config_sgsn,
+ .onenter = ns2_sns_st_bss_config_sgsn_onenter,
},
[GPRS_SNS_ST_CONFIGURED] = {
- .in_event_mask = S(GPRS_SNS_EV_ADD) |
- S(GPRS_SNS_EV_DELETE) |
- S(GPRS_SNS_EV_CHANGE_WEIGHT),
- .out_state_mask = S(GPRS_SNS_ST_UNCONFIGURED),
+ .in_event_mask = S(NS2_SNS_EV_RX_ADD) |
+ S(NS2_SNS_EV_RX_DELETE) |
+ S(NS2_SNS_EV_RX_CHANGE_WEIGHT) |
+ S(NS2_SNS_EV_REQ_NSVC_ALIVE),
+ .out_state_mask = S(GPRS_SNS_ST_UNCONFIGURED) |
+ S(GPRS_SNS_ST_BSS_SIZE) |
+ S(GPRS_SNS_ST_LOCAL_PROCEDURE),
.name = "CONFIGURED",
.action = ns2_sns_st_configured,
.onenter = ns2_sns_st_configured_onenter,
},
+ [GPRS_SNS_ST_LOCAL_PROCEDURE] = {
+ .in_event_mask = S(NS2_SNS_EV_RX_ADD) |
+ S(NS2_SNS_EV_RX_DELETE) |
+ S(NS2_SNS_EV_RX_CHANGE_WEIGHT) |
+ S(NS2_SNS_EV_RX_ACK) |
+ S(NS2_SNS_EV_REQ_NSVC_ALIVE),
+ .out_state_mask = S(GPRS_SNS_ST_UNCONFIGURED) |
+ S(GPRS_SNS_ST_BSS_SIZE) |
+ S(GPRS_SNS_ST_CONFIGURED) |
+ S(GPRS_SNS_ST_LOCAL_PROCEDURE),
+ .name = "LOCAL_PROCEDURE",
+ .action = ns2_sns_st_local_procedure,
+ .onenter = ns2_sns_st_local_procedure_onenter,
+ },
+
};
static int ns2_sns_fsm_bss_timer_cb(struct osmo_fsm_inst *fi)
{
+ struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
struct gprs_ns2_nse *nse = nse_inst_from_fi(fi);
struct gprs_ns2_inst *nsi = nse->nsi;
+ gss->N++;
switch (fi->T) {
case 1:
- osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_SIZE, nsi->timeout[NS_TOUT_TSNS_PROV], 1);
+ if (gss->N >= nsi->timeout[NS_TOUT_TSNS_SIZE_RETRIES]) {
+ sns_failed(fi, "Size retries failed. Selecting next IP-SNS endpoint.");
+ } else {
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_BSS_SIZE, nsi->timeout[NS_TOUT_TSNS_PROV], 1);
+ }
break;
case 2:
- osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_CONFIG_BSS, nsi->timeout[NS_TOUT_TSNS_PROV], 2);
+ if (gss->N >= nsi->timeout[NS_TOUT_TSNS_CONFIG_RETRIES]) {
+ sns_failed(fi, "BSS Config retries failed. Selecting next IP-SNS endpoint");
+ } else {
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_BSS_CONFIG_BSS, nsi->timeout[NS_TOUT_TSNS_PROV], 2);
+ }
+ break;
+ case 3:
+ if (gss->N >= nsi->timeout[NS_TOUT_TSNS_CONFIG_RETRIES]) {
+ sns_failed(fi, "SGSN Config retries failed. Selecting next IP-SNS endpoint.");
+ } else {
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_BSS_CONFIG_SGSN, nsi->timeout[NS_TOUT_TSNS_PROV], 3);
+ }
+ break;
+ case 4:
+ sns_failed(fi, "Config succeeded but no NS-VC came online. Selecting next IP-SNS endpoint.");
+ break;
+ case 5:
+ if (gss->N >= nsi->timeout[NS_TOUT_TSNS_CONFIG_RETRIES]) {
+ sns_failed(fi, "SNS Procedure retries failed.");
+ } else {
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_LOCAL_PROCEDURE, nsi->timeout[NS_TOUT_TSNS_PROV], 5);
+ }
break;
}
return 0;
}
-static void ns2_sns_st_all_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+static struct gprs_ns_ie_ip4_elem *ns2_get_sbind_ip4_entry(struct ns2_sns_state *gss,
+ struct ns2_sns_bind *sbind,
+ struct ns2_sns_elems *endpoints)
{
- struct gprs_ns2_nse *nse = nse_inst_from_fi(fi);
+ const struct osmo_sockaddr *addr;
+ struct gprs_ns_ie_ip4_elem *ip4;
- /* reset when receiving GPRS_SNS_EV_NO_NSVC */
- osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_SIZE, nse->nsi->timeout[NS_TOUT_TSNS_PROV], 3);
-}
+ if (gss->family != AF_INET)
+ return NULL;
-static struct osmo_fsm gprs_ns2_sns_bss_fsm = {
- .name = "GPRS-NS2-SNS-BSS",
- .states = ns2_sns_bss_states,
- .num_states = ARRAY_SIZE(ns2_sns_bss_states),
- .allstate_event_mask = GPRS_SNS_EV_NO_NSVC,
- .allstate_action = ns2_sns_st_all_action,
- .cleanup = NULL,
- .timer_cb = ns2_sns_fsm_bss_timer_cb,
- /* .log_subsys = DNS, "is not constant" */
- .event_names = gprs_sns_event_names,
- .pre_term = NULL,
- .log_subsys = DLNS,
-};
+ addr = gprs_ns2_ip_bind_sockaddr(sbind->bind);
+ if (addr->u.sa.sa_family != AF_INET)
+ return NULL;
-/*! Allocate an IP-SNS FSM for the BSS side.
- * \param[in] nse NS Entity in which the FSM runs
- * \param[in] id string identifier
- * \retruns FSM instance on success; NULL on error */
-struct osmo_fsm_inst *ns2_sns_bss_fsm_alloc(struct gprs_ns2_nse *nse,
- const char *id)
+ for (unsigned int i=0; i<endpoints->num_ip4; i++) {
+ ip4 = &endpoints->ip4[i];
+ if (ip4->ip_addr == addr->u.sin.sin_addr.s_addr &&
+ ip4->udp_port == addr->u.sin.sin_port)
+ return ip4;
+ }
+
+ return NULL;
+}
+
+static struct gprs_ns_ie_ip6_elem *ns2_get_sbind_ip6_entry(struct ns2_sns_state *gss,
+ struct ns2_sns_bind *sbind,
+ struct ns2_sns_elems *endpoints)
{
- struct osmo_fsm_inst *fi;
- struct ns2_sns_state *gss;
+ const struct osmo_sockaddr *addr;
+ struct gprs_ns_ie_ip6_elem *ip6;
- fi = osmo_fsm_inst_alloc(&gprs_ns2_sns_bss_fsm, nse, NULL, LOGL_DEBUG, id);
- if (!fi)
- return fi;
+ if (gss->family != AF_INET6)
+ return NULL;
- gss = talloc_zero(fi, struct ns2_sns_state);
- if (!gss)
- goto err;
+ addr = gprs_ns2_ip_bind_sockaddr(sbind->bind);
+ if (addr->u.sa.sa_family != AF_INET6)
+ return NULL;
- fi->priv = gss;
- gss->nse = nse;
+ for (unsigned int i=0; i<endpoints->num_ip6; i++) {
+ ip6 = &endpoints->ip6[i];
+ if (memcmp(&ip6->ip_addr, &addr->u.sin6.sin6_addr, sizeof(ip6->ip_addr)) ||
+ ip6->udp_port != addr->u.sin6.sin6_port)
+ return ip6;
+ }
- return fi;
-err:
- osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL);
return NULL;
}
-/*! Start an IP-SNS FSM.
- * \param[in] nse NS Entity whose IP-SNS FSM shall be started
- * \param[in] nsvc Initial NS-VC
- * \param[in] remote remote (SGSN) address
- * \returns 0 on success; negative on error */
-int ns2_sns_bss_fsm_start(struct gprs_ns2_nse *nse, struct gprs_ns2_vc *nsvc,
- const struct osmo_sockaddr *remote)
+/* return != 0 if the resulting weight is invalid. return 1 if sbind doesn't have an entry */
+static int ns2_update_weight_entry(struct ns2_sns_state *gss, struct ns2_sns_bind *sbind,
+ struct ns2_sns_elems *endpoints)
{
- struct osmo_fsm_inst *fi = nse->bss_sns_fi;
- struct ns2_sns_state *gss = (struct ns2_sns_state *) nse->bss_sns_fi->priv;
- struct gprs_ns_ie_ip4_elem *ip4_elems;
- struct gprs_ns_ie_ip6_elem *ip6_elems;
- struct gprs_ns2_vc_bind *bind;
- struct gprs_ns2_inst *nsi = nse->nsi;
- const struct osmo_sockaddr *sa;
- struct osmo_sockaddr local;
-
- gss->ip = remote->u.sa.sa_family == AF_INET ? IPv4 : IPv6;
+ struct gprs_ns_ie_ip4_elem *ip4;
+ struct gprs_ns_ie_ip6_elem *ip6;
+
+ switch (gss->family) {
+ case AF_INET:
+ ip4 = ns2_get_sbind_ip4_entry(gss, sbind, endpoints);
+ if (!ip4)
+ return 1;
+ ip4->sig_weight = sbind->bind->sns_sig_weight;
+ ip4->data_weight = sbind->bind->sns_data_weight;
+ return (ip4_weight_sum_sig(endpoints) != 0 && ip4_weight_sum_data(endpoints) != 0);
+ break;
+ case AF_INET6:
+ ip6 = ns2_get_sbind_ip6_entry(gss, sbind, endpoints);
+ if (!ip6)
+ return 1;
+ ip6->sig_weight = sbind->bind->sns_sig_weight;
+ ip6->data_weight = sbind->bind->sns_data_weight;
+ return (ip6_weight_sum_sig(endpoints) != 0 && ip6_weight_sum_data(endpoints) != 0);
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+static void ns2_add_procedure(struct ns2_sns_state *gss, struct ns2_sns_bind *sbind,
+ enum sns_procedure procedure_type)
+{
+ struct ns2_sns_procedure *procedure = NULL;
+ const struct osmo_sockaddr *saddr;
+ saddr = gprs_ns2_ip_bind_sockaddr(sbind->bind);
- gss->initial = *remote;
- gss->sns_nsvc = nsvc;
- nsvc->sns_only = true;
+ OSMO_ASSERT(saddr->u.sa.sa_family == gss->family);
- int count = 0;
- llist_for_each_entry(bind, &nsi->binding, list) {
- if (!gprs_ns2_is_ip_bind(bind))
- continue;
+ switch (procedure_type) {
+ case SNS_PROC_ADD:
+ break;
+ case SNS_PROC_DEL:
+ break;
+ case SNS_PROC_CHANGE_WEIGHT:
+ llist_for_each_entry(procedure, &gss->procedures, list) {
+ if (procedure->sbind == sbind && procedure->procedure == procedure_type &&
+ !procedure->running) {
+ switch(gss->family) {
+ case AF_INET:
+ /* merge it with a previous procedure */
+ procedure->ip4.ip_addr = sbind->bind->sns_sig_weight;
+ procedure->ip4.data_weight = sbind->bind->sns_data_weight;
+ break;
+ case AF_INET6:
+ /* merge it with a previous procedure */
+ procedure->ip6.sig_weight = sbind->bind->sns_sig_weight;
+ procedure->ip6.data_weight = sbind->bind->sns_data_weight;
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+ return;
+ }
+ }
+ break;
+ default:
+ return;
+ }
- sa = gprs_ns2_ip_bind_sockaddr(bind);
- if (!sa)
- continue;
+ procedure = talloc_zero(gss, struct ns2_sns_procedure);
+ if (!procedure)
+ return;
- if (sa->u.sa.sa_family == remote->u.sa.sa_family)
- count++;
+ switch (procedure_type) {
+ case SNS_PROC_ADD:
+ case SNS_PROC_CHANGE_WEIGHT:
+ procedure->sbind = sbind;
+ break;
+ default:
+ break;
}
- if (count == 0) {
- /* TODO: logging */
- goto err;
+ llist_add_tail(&procedure->list, &gss->procedures);
+ procedure->procedure = procedure_type;
+ procedure->sig_weight = sbind->bind->sns_sig_weight;
+ procedure->data_weight = sbind->bind->sns_data_weight;
+
+ switch(gss->family) {
+ case AF_INET:
+ procedure->ip4.ip_addr = saddr->u.sin.sin_addr.s_addr;
+ procedure->ip4.udp_port = saddr->u.sin.sin_port;
+ procedure->ip4.sig_weight = sbind->bind->sns_sig_weight;
+ procedure->ip4.data_weight = sbind->bind->sns_data_weight;
+ break;
+ case AF_INET6:
+ memcpy(&procedure->ip6.ip_addr, &saddr->u.sin6.sin6_addr, sizeof(struct in6_addr));
+ procedure->ip6.udp_port = saddr->u.sin.sin_port;
+ procedure->ip6.sig_weight = sbind->bind->sns_sig_weight;
+ procedure->ip6.data_weight = sbind->bind->sns_data_weight;
+ break;
+ default:
+ OSMO_ASSERT(0);
}
- switch (gss->ip) {
- case IPv4:
- ip4_elems = talloc_zero_size(fi, sizeof(struct gprs_ns_ie_ip4_elem) * count);
- if (!ip4_elems)
- goto err;
-
- gss->ip4_local = ip4_elems;
-
- llist_for_each_entry(bind, &nsi->binding, list) {
- if (!gprs_ns2_is_ip_bind(bind))
- continue;
+ if (gss->nse->bss_sns_fi->state == GPRS_SNS_ST_CONFIGURED) {
+ osmo_fsm_inst_state_chg(gss->nse->bss_sns_fi, GPRS_SNS_ST_LOCAL_PROCEDURE,
+ gss->nse->nsi->timeout[NS_TOUT_TSNS_PROV], 5);
+ }
+}
- sa = gprs_ns2_ip_bind_sockaddr(bind);
- if (!sa)
- continue;
+/* add an entrypoint to sns_endpoints */
+static int ns2_sns_add_elements(struct ns2_sns_state *gss, struct ns2_sns_bind *sbind,
+ struct ns2_sns_elems *elems)
+{
+ const struct osmo_sockaddr *saddr;
+ struct gprs_ns_ie_ip4_elem ip4;
+ struct gprs_ns_ie_ip6_elem ip6;
+ int rc = -1;
+
+ saddr = gprs_ns2_ip_bind_sockaddr(sbind->bind);
+ OSMO_ASSERT(saddr->u.sa.sa_family == gss->family);
+
+ switch (gss->family) {
+ case AF_INET:
+ ip4.ip_addr = saddr->u.sin.sin_addr.s_addr;
+ ip4.udp_port= saddr->u.sin.sin_port;
+ ip4.sig_weight = sbind->bind->sns_sig_weight;
+ ip4.data_weight = sbind->bind->sns_data_weight;
+ rc = add_ip4_elem(gss, elems, &ip4);
+ break;
+ case AF_INET6:
+ memcpy(&ip6.ip_addr, &saddr->u.sin6.sin6_addr, sizeof(struct in6_addr));
+ ip6.udp_port= saddr->u.sin.sin_port;
+ ip6.sig_weight = sbind->bind->sns_sig_weight;
+ ip6.data_weight = sbind->bind->sns_data_weight;
+ rc = add_ip6_elem(gss, elems, &ip6);
+ break;
+ }
- if (sa->u.sas.ss_family != AF_INET)
- continue;
+ return rc;
+}
- /* check if this is an specific bind */
- if (sa->u.sin.sin_addr.s_addr == 0) {
- if (osmo_sockaddr_local_ip(&local, remote))
- continue;
+/* common allstate-action for both roles */
+static void ns2_sns_st_all_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gprs_ns2_nse *nse = nse_inst_from_fi(fi);
+ struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
+ struct ns2_sns_bind *sbind;
+ struct gprs_ns2_vc *nsvc, *nsvc2;
+ struct ns2_sns_procedure *procedure;
- ip4_elems->ip_addr = local.u.sin.sin_addr.s_addr;
- } else {
- ip4_elems->ip_addr = sa->u.sin.sin_addr.s_addr;
+ switch (event) {
+ case NS2_SNS_EV_REQ_ADD_BIND:
+ sbind = data;
+ switch (fi->state) {
+ case GPRS_SNS_ST_UNCONFIGURED:
+ if (gss->role == GPRS_SNS_ROLE_BSS)
+ osmo_fsm_inst_dispatch(nse->bss_sns_fi, NS2_SNS_EV_REQ_SELECT_ENDPOINT, NULL);
+ break;
+ case GPRS_SNS_ST_BSS_SIZE:
+ switch (gss->family) {
+ case AF_INET:
+ if (gss->num_max_ip4_remote <= gss->local.num_ip4 ||
+ gss->num_max_ip4_remote * (gss->local.num_ip4 + 1) > gss->num_max_nsvcs) {
+ osmo_fsm_inst_dispatch(nse->bss_sns_fi, NS2_SNS_EV_REQ_SELECT_ENDPOINT, GPRS_SNS_FLAG_KEEP_SELECT_ENDPOINT_ORDER);
+ return;
+ }
+ break;
+ case AF_INET6:
+ if (gss->num_max_ip6_remote <= gss->local.num_ip6 ||
+ gss->num_max_ip6_remote * (gss->local.num_ip6 + 1) > gss->num_max_nsvcs) {
+ osmo_fsm_inst_dispatch(nse->bss_sns_fi, NS2_SNS_EV_REQ_SELECT_ENDPOINT, GPRS_SNS_FLAG_KEEP_SELECT_ENDPOINT_ORDER);
+ return;
+ }
+ break;
+ }
+ ns2_sns_add_elements(gss, sbind, &gss->local);
+ break;
+ case GPRS_SNS_ST_BSS_CONFIG_BSS:
+ case GPRS_SNS_ST_BSS_CONFIG_SGSN:
+ case GPRS_SNS_ST_CONFIGURED:
+ switch (gss->family) {
+ case AF_INET:
+ if (gss->num_max_ip4_remote <= gss->local.num_ip4) {
+ LOGPFSML(fi, LOGL_ERROR,
+ "NSE %d: ignoring bind %s because there are too many endpoints for the SNS.\n",
+ nse->nsei, sbind->bind->name);
+ return;
+ }
+ if (gss->remote.num_ip4 * (gss->local.num_ip4 + 1) > gss->num_max_nsvcs) {
+ LOGPFSML(fi, LOGL_ERROR,
+ "NSE %d: ignoring bind %s because there are too many endpoints for the SNS.\n",
+ nse->nsei, sbind->bind->name);
+ return;
+ }
+ break;
+ case AF_INET6:
+ if (gss->num_max_ip6_remote <= gss->local.num_ip6) {
+ LOGPFSML(fi, LOGL_ERROR,
+ "NSE %d: ignoring bind %s because there are too many endpoints for the SNS.\n",
+ nse->nsei, sbind->bind->name);
+ return;
+ }
+ if (gss->remote.num_ip6 * (gss->local.num_ip6 + 1) > gss->num_max_nsvcs) {
+ LOGPFSML(fi, LOGL_ERROR,
+ "NSE %d: ignoring bind %s because there are too many endpoints for the SNS.\n",
+ nse->nsei, sbind->bind->name);
+ return;
+ }
+ break;
+ }
+ ns2_sns_add_elements(gss, sbind, &gss->local_procedure);
+ ns2_add_procedure(gss, sbind, SNS_PROC_ADD);
+ break;
+ }
+ break;
+ case NS2_SNS_EV_REQ_DELETE_BIND:
+ sbind = data;
+ switch (fi->state) {
+ case GPRS_SNS_ST_UNCONFIGURED:
+ break;
+ case GPRS_SNS_ST_BSS_SIZE:
+ llist_for_each_entry_safe(nsvc, nsvc2, &nse->nsvc, list) {
+ if (nsvc->bind == sbind->bind) {
+ gprs_ns2_free_nsvc(nsvc);
+ }
+ }
+ osmo_fsm_inst_dispatch(fi, NS2_SNS_EV_REQ_SELECT_ENDPOINT, NULL);
+ break;
+ case GPRS_SNS_ST_BSS_CONFIG_BSS:
+ case GPRS_SNS_ST_BSS_CONFIG_SGSN:
+ case GPRS_SNS_ST_CONFIGURED:
+ case GPRS_SNS_ST_LOCAL_PROCEDURE:
+ remove_bind_elem(gss, &gss->local_procedure, sbind);
+ if (ip46_weight_sum(&gss->local_procedure, true) == 0 ||
+ ip46_weight_sum(&gss->local_procedure, false) == 0) {
+ LOGPFSML(fi, LOGL_ERROR, "NSE %d: weight has become invalid because of removing bind %s. Resetting the configuration\n",
+ nse->nsei, sbind->bind->name);
+ sns_failed(fi, NULL);
+ break;
+ }
+ gss->block_no_nsvc_events = true;
+ llist_for_each_entry_safe(nsvc, nsvc2, &nse->nsvc, list) {
+ if (nsvc->bind == sbind->bind) {
+ gprs_ns2_free_nsvc(nsvc);
+ }
+ }
+ gss->block_no_nsvc_events = false;
+ if (nse->sum_sig_weight == 0 || !nse->alive || !gss->alive) {
+ sns_failed(fi, "While deleting a bind the current state became invalid (no signalling weight)");
+ break;
}
- ip4_elems->udp_port = sa->u.sin.sin_port;
- ip4_elems->sig_weight = 2;
- ip4_elems->data_weight = 1;
- ip4_elems++;
+ /* ensure other procedures doesn't use the sbind */
+ llist_for_each_entry(procedure, &gss->procedures, list) {
+ if (procedure->sbind == sbind)
+ procedure->sbind = NULL;
+ }
+ ns2_add_procedure(gss, sbind, SNS_PROC_DEL);
+ break;
}
- gss->num_ip4_local = count;
- gss->num_max_ip4_remote = 4;
+ /* if this is the last bind, the free_nsvc() will trigger a reselection */
+ talloc_free(sbind);
break;
- case IPv6:
- /* IPv6 */
- ip6_elems = talloc_zero_size(fi, sizeof(struct gprs_ns_ie_ip6_elem) * count);
- if (!ip6_elems)
- goto err;
+ case NS2_SNS_EV_REQ_CHANGE_WEIGHT:
+ sbind = data;
+ switch (fi->state) {
+ case GPRS_SNS_ST_UNCONFIGURED:
+ /* select_endpoint will check if this is a valid configuration */
+ if (gss->role == GPRS_SNS_ROLE_BSS)
+ osmo_fsm_inst_dispatch(fi, NS2_SNS_EV_REQ_SELECT_ENDPOINT, NULL);
+ break;
+ case GPRS_SNS_ST_BSS_SIZE:
+ /* invalid weight? */
+ if (!ns2_update_weight_entry(gss, sbind, &gss->local))
+ sns_failed(fi, "updating weights results in an invalid configuration.");
+ break;
+ default:
+ if (!ns2_update_weight_entry(gss, sbind, &gss->local_procedure)) {
+ sns_failed(fi, "updating weights results in an invalid configuration.");
+ break;
+ }
+ ns2_add_procedure(gss, sbind, SNS_PROC_CHANGE_WEIGHT);
+ break;
+ }
+ }
+}
- gss->ip6_local = ip6_elems;
+/* validate the bss configuration (sns endpoint and binds)
+ * - no endpoints -> invalid
+ * - no binds -> invalid
+ * - only v4 sns endpoints, only v6 binds -> invalid
+ * - only v4 sns endpoints, but v4 sig weights == 0 -> invalid ...
+ */
+static int ns2_sns_bss_valid_configuration(struct ns2_sns_state *gss)
+{
+ struct ns2_sns_bind *sbind;
+ struct sns_endpoint *endpoint;
+ const struct osmo_sockaddr *addr;
+ int v4_sig = 0, v4_data = 0, v6_sig = 0, v6_data = 0;
+ bool v4_endpoints = false;
+ bool v6_endpoints = false;
+
+ if (llist_empty(&gss->sns_endpoints) || llist_empty(&gss->binds))
+ return 0;
- llist_for_each_entry(bind, &nsi->binding, list) {
- if (!gprs_ns2_is_ip_bind(bind))
- continue;
+ llist_for_each_entry(sbind, &gss->binds, list) {
+ addr = gprs_ns2_ip_bind_sockaddr(sbind->bind);
+ if (!addr)
+ continue;
+ switch (addr->u.sa.sa_family) {
+ case AF_INET:
+ v4_sig += sbind->bind->sns_sig_weight;
+ v4_data += sbind->bind->sns_data_weight;
+ break;
+ case AF_INET6:
+ v6_sig += sbind->bind->sns_sig_weight;
+ v6_data += sbind->bind->sns_data_weight;
+ break;
+ }
+ }
- sa = gprs_ns2_ip_bind_sockaddr(bind);
- if (!sa)
- continue;
+ llist_for_each_entry(endpoint, &gss->sns_endpoints, list) {
+ switch (endpoint->saddr.u.sa.sa_family) {
+ case AF_INET:
+ v4_endpoints = true;
+ break;
+ case AF_INET6:
+ v6_endpoints = true;
+ break;
+ }
+ }
- if (sa->u.sas.ss_family != AF_INET6)
- continue;
+ return (v4_endpoints && v4_sig && v4_data) || (v6_endpoints && v6_sig && v6_data);
+}
- /* check if this is an specific bind */
- if (IN6_IS_ADDR_UNSPECIFIED(&sa->u.sin6.sin6_addr)) {
- if (osmo_sockaddr_local_ip(&local, remote))
- continue;
+/* allstate-action for BSS role */
+static void ns2_sns_st_all_action_bss(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
+ struct gprs_ns2_nse *nse = nse_inst_from_fi(fi);
- ip6_elems->ip_addr = local.u.sin6.sin6_addr;
- } else {
- ip6_elems->ip_addr = sa->u.sin6.sin6_addr;
- }
+ /* reset when receiving NS2_SNS_EV_REQ_NO_NSVC */
+ switch (event) {
+ case NS2_SNS_EV_REQ_NO_NSVC:
+ /* ignore reselection running */
+ if (gss->reselection_running || gss->block_no_nsvc_events)
+ break;
- ip6_elems->udp_port = sa->u.sin.sin_port;
- ip6_elems->sig_weight = 2;
- ip6_elems->data_weight = 1;
+ sns_failed(fi, "no remaining NSVC, resetting SNS FSM");
+ break;
+ case NS2_SNS_EV_REQ_FREE_NSVCS:
+ case NS2_SNS_EV_REQ_SELECT_ENDPOINT:
+ /* TODO: keep the order of binds when data == GPRS_SNS_FLAG_KEEP_SELECT_ENDPOINT_ORDER */
+ /* tear down previous state
+ * gprs_ns2_free_nsvcs() will trigger NO_NSVC, prevent this from triggering a reselection */
+ if (gss->reselection_running || gss->block_no_nsvc_events)
+ break;
- ip6_elems++;
+ gss->reselection_running = true;
+ ns2_free_nsvcs(nse);
+ ns2_clear_elems(&gss->local);
+ ns2_clear_elems(&gss->remote);
+
+ /* Choose the next sns endpoint. */
+ if (!ns2_sns_bss_valid_configuration(gss)) {
+ gss->initial = NULL;
+ ns2_prim_status_ind(gss->nse, NULL, 0, GPRS_NS2_AFF_CAUSE_SNS_NO_ENDPOINTS);
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_UNCONFIGURED, 0, 3);
+ gss->reselection_running = false;
+ return;
+ } else if (!gss->initial) {
+ gss->initial = llist_first_entry(&gss->sns_endpoints, struct sns_endpoint, list);
+ } else if (gss->initial->list.next == &gss->sns_endpoints) {
+ /* last entry, continue with first */
+ gss->initial = llist_first_entry(&gss->sns_endpoints, struct sns_endpoint, list);
+ } else {
+ /* next element is an entry */
+ gss->initial = llist_entry(gss->initial->list.next, struct sns_endpoint, list);
}
- gss->num_ip6_local = count;
- gss->num_max_ip6_remote = 4;
+
+ gss->family = gss->initial->saddr.u.sa.sa_family;
+ gss->reselection_running = false;
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_BSS_SIZE, nse->nsi->timeout[NS_TOUT_TSNS_PROV], 1);
+ break;
+ default:
+ ns2_sns_st_all_action(fi, event, data);
break;
}
+}
- gss->num_max_nsvcs = 8;
+static struct osmo_fsm gprs_ns2_sns_bss_fsm = {
+ .name = "GPRS-NS2-SNS-BSS",
+ .states = ns2_sns_bss_states,
+ .num_states = ARRAY_SIZE(ns2_sns_bss_states),
+ .allstate_event_mask = S(NS2_SNS_EV_REQ_NO_NSVC) |
+ S(NS2_SNS_EV_REQ_FREE_NSVCS) |
+ S(NS2_SNS_EV_REQ_SELECT_ENDPOINT) |
+ S(NS2_SNS_EV_REQ_ADD_BIND) |
+ S(NS2_SNS_EV_REQ_CHANGE_WEIGHT) |
+ S(NS2_SNS_EV_REQ_DELETE_BIND),
+ .allstate_action = ns2_sns_st_all_action_bss,
+ .cleanup = NULL,
+ .timer_cb = ns2_sns_fsm_bss_timer_cb,
+ .event_names = gprs_sns_event_names,
+ .pre_term = NULL,
+ .log_subsys = DLNS,
+};
- return osmo_fsm_inst_dispatch(nse->bss_sns_fi, GPRS_SNS_EV_START, NULL);
+/*! Allocate an IP-SNS FSM for the BSS side.
+ * \param[in] nse NS Entity in which the FSM runs
+ * \param[in] id string identifier
+ * \returns FSM instance on success; NULL on error */
+struct osmo_fsm_inst *ns2_sns_bss_fsm_alloc(struct gprs_ns2_nse *nse,
+ const char *id)
+{
+ struct osmo_fsm_inst *fi;
+ struct ns2_sns_state *gss;
+ fi = osmo_fsm_inst_alloc(&gprs_ns2_sns_bss_fsm, nse, NULL, LOGL_DEBUG, id);
+ if (!fi)
+ return fi;
+
+ gss = talloc_zero(fi, struct ns2_sns_state);
+ if (!gss)
+ goto err;
+
+ fi->priv = gss;
+ gss->nse = nse;
+ gss->role = GPRS_SNS_ROLE_BSS;
+ /* The SGSN doesn't tell the BSS, so we assume there's always sufficient */
+ gss->num_max_ip4_remote = 8192;
+ gss->num_max_ip6_remote = 8192;
+ INIT_LLIST_HEAD(&gss->sns_endpoints);
+ INIT_LLIST_HEAD(&gss->binds);
+ INIT_LLIST_HEAD(&gss->procedures);
+
+ return fi;
err:
- return -1;
+ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL);
+ return NULL;
}
/*! main entry point for receiving SNS messages from the network.
* \param[in] nsvc NS-VC on which the message was received
* \param[in] msg message buffer of the IP-SNS message
* \param[in] tp parsed TLV structure of message
- * \retruns 0 on success; negative on error */
-int gprs_ns2_sns_rx(struct gprs_ns2_vc *nsvc, struct msgb *msg, struct tlv_parsed *tp)
+ * \returns 0 on success; negative on error */
+int ns2_sns_rx(struct gprs_ns2_vc *nsvc, struct msgb *msg, struct tlv_parsed *tp)
{
struct gprs_ns2_nse *nse = nsvc->nse;
struct gprs_ns_hdr *nsh = (struct gprs_ns_hdr *) msg->l2h;
uint16_t nsei = nsvc->nse->nsei;
+ struct ns2_sns_state *gss;
struct osmo_fsm_inst *fi;
+ int rc = 0;
if (!nse->bss_sns_fi) {
- LOGP(DLNS, LOGL_NOTICE, "NSEI=%u Rx %s for NS Instance that has no SNS!\n",
- nsvc->nse->nsei, get_value_string(gprs_ns_pdu_strings, nsh->pdu_type));
- return -EINVAL;
+ LOGNSVC(nsvc, LOGL_NOTICE, "Rx %s for NS Instance that has no SNS!\n",
+ get_value_string(gprs_ns_pdu_strings, nsh->pdu_type));
+ rc = -EINVAL;
+ goto out;
}
- LOGP(DLNS, LOGL_DEBUG, "NSEI=%u Rx SNS PDU type %s\n", nsei,
- get_value_string(gprs_ns_pdu_strings, nsh->pdu_type));
-
/* FIXME: how to resolve SNS FSM Instance by NSEI (SGSN)? */
fi = nse->bss_sns_fi;
+ gss = (struct ns2_sns_state *) fi->priv;
+ gss->sns_nsvc = nsvc;
+
+ LOGPFSML(fi, LOGL_DEBUG, "NSEI=%u Rx SNS PDU type %s\n", nsei,
+ get_value_string(gprs_ns_pdu_strings, nsh->pdu_type));
switch (nsh->pdu_type) {
case SNS_PDUT_SIZE:
- osmo_fsm_inst_dispatch(fi, GPRS_SNS_EV_SIZE, tp);
+ osmo_fsm_inst_dispatch(fi, NS2_SNS_EV_RX_SIZE, tp);
break;
case SNS_PDUT_SIZE_ACK:
- osmo_fsm_inst_dispatch(fi, GPRS_SNS_EV_SIZE_ACK, tp);
+ osmo_fsm_inst_dispatch(fi, NS2_SNS_EV_RX_SIZE_ACK, tp);
break;
case SNS_PDUT_CONFIG:
if (nsh->data[0] & 0x01)
- osmo_fsm_inst_dispatch(fi, GPRS_SNS_EV_CONFIG_END, tp);
+ osmo_fsm_inst_dispatch(fi, NS2_SNS_EV_RX_CONFIG_END, tp);
else
- osmo_fsm_inst_dispatch(fi, GPRS_SNS_EV_CONFIG, tp);
+ osmo_fsm_inst_dispatch(fi, NS2_SNS_EV_RX_CONFIG, tp);
break;
case SNS_PDUT_CONFIG_ACK:
- osmo_fsm_inst_dispatch(fi, GPRS_SNS_EV_CONFIG_ACK, tp);
+ osmo_fsm_inst_dispatch(fi, NS2_SNS_EV_RX_CONFIG_ACK, tp);
break;
case SNS_PDUT_ADD:
- osmo_fsm_inst_dispatch(fi, GPRS_SNS_EV_ADD, tp);
+ osmo_fsm_inst_dispatch(fi, NS2_SNS_EV_RX_ADD, tp);
break;
case SNS_PDUT_DELETE:
- osmo_fsm_inst_dispatch(fi, GPRS_SNS_EV_DELETE, tp);
+ osmo_fsm_inst_dispatch(fi, NS2_SNS_EV_RX_DELETE, tp);
break;
case SNS_PDUT_CHANGE_WEIGHT:
- osmo_fsm_inst_dispatch(fi, GPRS_SNS_EV_CHANGE_WEIGHT, tp);
+ osmo_fsm_inst_dispatch(fi, NS2_SNS_EV_RX_CHANGE_WEIGHT, tp);
break;
case SNS_PDUT_ACK:
- LOGP(DLNS, LOGL_NOTICE, "NSEI=%u Rx unsupported SNS PDU type %s\n", nsei,
- get_value_string(gprs_ns_pdu_strings, nsh->pdu_type));
+ osmo_fsm_inst_dispatch(fi, NS2_SNS_EV_RX_ACK, tp);
break;
default:
- LOGP(DLNS, LOGL_ERROR, "NSEI=%u Rx unknown SNS PDU type %s\n", nsei,
- get_value_string(gprs_ns_pdu_strings, nsh->pdu_type));
- return -EINVAL;
+ LOGPFSML(fi, LOGL_ERROR, "NSEI=%u Rx unknown SNS PDU type %s\n", nsei,
+ get_value_string(gprs_ns_pdu_strings, nsh->pdu_type));
+ rc = -EINVAL;
}
- return 0;
+out:
+ msgb_free(msg);
+
+ return rc;
}
#include <osmocom/vty/vty.h>
#include <osmocom/vty/misc.h>
-static void vty_dump_sns_ip4(struct vty *vty, const struct gprs_ns_ie_ip4_elem *ip4)
+static void vty_dump_sns_ip4(struct vty *vty, const char *prefix, const struct gprs_ns_ie_ip4_elem *ip4)
{
struct in_addr in = { .s_addr = ip4->ip_addr };
- vty_out(vty, " %s:%u, Signalling Weight: %u, Data Weight: %u%s",
+ vty_out(vty, "%s %s:%u, Signalling Weight: %u, Data Weight: %u%s", prefix,
inet_ntoa(in), ntohs(ip4->udp_port), ip4->sig_weight, ip4->data_weight, VTY_NEWLINE);
}
-static void vty_dump_sns_ip6(struct vty *vty, const struct gprs_ns_ie_ip6_elem *ip6)
+static void vty_dump_sns_ip6(struct vty *vty, const char *prefix, const struct gprs_ns_ie_ip6_elem *ip6)
{
char ip_addr[INET6_ADDRSTRLEN] = {};
if (!inet_ntop(AF_INET6, &ip6->ip_addr, ip_addr, (INET6_ADDRSTRLEN)))
strcpy(ip_addr, "Invalid IPv6");
- vty_out(vty, " %s:%u, Signalling Weight: %u, Data Weight: %u%s",
+ vty_out(vty, "%s %s:%u, Signalling Weight: %u, Data Weight: %u%s", prefix,
ip_addr, ntohs(ip6->udp_port), ip6->sig_weight, ip6->data_weight, VTY_NEWLINE);
}
/*! Dump the IP-SNS state to a vty.
* \param[in] vty VTY to which the state shall be printed
+ * \param[in] prefix prefix to print at start of each line (typically indenting)
* \param[in] nse NS Entity whose IP-SNS state shall be printed
* \param[in] stats Whether or not statistics shall also be printed */
-void gprs_ns2_sns_dump_vty(struct vty *vty, const struct gprs_ns2_nse *nse, bool stats)
+void ns2_sns_dump_vty(struct vty *vty, const char *prefix, const struct gprs_ns2_nse *nse, bool stats)
{
struct ns2_sns_state *gss;
unsigned int i;
@@ -1462,35 +2404,703 @@ void gprs_ns2_sns_dump_vty(struct vty *vty, const struct gprs_ns2_nse *nse, bool
if (!nse->bss_sns_fi)
return;
- vty_out_fsm_inst(vty, nse->bss_sns_fi);
+ vty_out_fsm_inst2(vty, prefix, nse->bss_sns_fi);
+ gss = (struct ns2_sns_state *) nse->bss_sns_fi->priv;
+
+ vty_out(vty, "%sMaximum number of remote NS-VCs: %zu, IPv4 Endpoints: %zu, IPv6 Endpoints: %zu%s",
+ prefix, gss->num_max_nsvcs, gss->num_max_ip4_remote, gss->num_max_ip6_remote, VTY_NEWLINE);
+
+ if (gss->local.num_ip4 && gss->remote.num_ip4) {
+ vty_out(vty, "%sLocal IPv4 Endpoints:%s", prefix, VTY_NEWLINE);
+ for (i = 0; i < gss->local.num_ip4; i++)
+ vty_dump_sns_ip4(vty, prefix, &gss->local.ip4[i]);
+
+ vty_out(vty, "%sRemote IPv4 Endpoints:%s", prefix, VTY_NEWLINE);
+ for (i = 0; i < gss->remote.num_ip4; i++)
+ vty_dump_sns_ip4(vty, prefix, &gss->remote.ip4[i]);
+ }
+
+ if (gss->local.num_ip6 && gss->remote.num_ip6) {
+ vty_out(vty, "%sLocal IPv6 Endpoints:%s", prefix, VTY_NEWLINE);
+ for (i = 0; i < gss->local.num_ip6; i++)
+ vty_dump_sns_ip6(vty, prefix, &gss->local.ip6[i]);
+
+ vty_out(vty, "%sRemote IPv6 Endpoints:%s", prefix, VTY_NEWLINE);
+ for (i = 0; i < gss->remote.num_ip6; i++)
+ vty_dump_sns_ip6(vty, prefix, &gss->remote.ip6[i]);
+ }
+}
+
+/*! write IP-SNS to a vty
+ * \param[in] vty VTY to which the state shall be printed
+ * \param[in] nse NS Entity whose IP-SNS state shall be printed */
+void ns2_sns_write_vty(struct vty *vty, const struct gprs_ns2_nse *nse)
+{
+ struct ns2_sns_state *gss;
+ struct osmo_sockaddr_str addr_str;
+ struct sns_endpoint *endpoint;
+
+ if (!nse->bss_sns_fi)
+ return;
+
gss = (struct ns2_sns_state *) nse->bss_sns_fi->priv;
+ llist_for_each_entry(endpoint, &gss->sns_endpoints, list) {
+ /* It's unlikely that an error happens, but let's better be safe. */
+ if (osmo_sockaddr_str_from_sockaddr(&addr_str, &endpoint->saddr.u.sas) != 0)
+ addr_str = (struct osmo_sockaddr_str) { .ip = "<INVALID>" };
+ vty_out(vty, " ip-sns-remote %s %u%s", addr_str.ip, addr_str.port, VTY_NEWLINE);
+ }
+}
+
+static struct sns_endpoint *ns2_get_sns_endpoint(struct ns2_sns_state *state,
+ const struct osmo_sockaddr *saddr)
+{
+ struct sns_endpoint *endpoint;
+
+ llist_for_each_entry(endpoint, &state->sns_endpoints, list) {
+ if (!osmo_sockaddr_cmp(saddr, &endpoint->saddr))
+ return endpoint;
+ }
+
+ return NULL;
+}
+
+/*! gprs_ns2_sns_add_endpoint
+ * \param[in] nse
+ * \param[in] sockaddr
+ * \return
+ */
+int gprs_ns2_sns_add_endpoint(struct gprs_ns2_nse *nse,
+ const struct osmo_sockaddr *saddr)
+{
+ struct ns2_sns_state *gss;
+ struct sns_endpoint *endpoint;
+ bool do_selection = false;
+
+ if (nse->ll != GPRS_NS2_LL_UDP) {
+ return -EINVAL;
+ }
+
+ if (nse->dialect != GPRS_NS2_DIALECT_SNS) {
+ return -EINVAL;
+ }
+
+ gss = nse->bss_sns_fi->priv;
+
+ if (ns2_get_sns_endpoint(gss, saddr))
+ return -EADDRINUSE;
+
+ endpoint = talloc_zero(nse->bss_sns_fi->priv, struct sns_endpoint);
+ if (!endpoint)
+ return -ENOMEM;
+
+ endpoint->saddr = *saddr;
+ if (llist_empty(&gss->sns_endpoints))
+ do_selection = true;
+
+ llist_add_tail(&endpoint->list, &gss->sns_endpoints);
+ if (do_selection)
+ osmo_fsm_inst_dispatch(nse->bss_sns_fi, NS2_SNS_EV_REQ_SELECT_ENDPOINT, NULL);
+
+ return 0;
+}
+
+/*! gprs_ns2_sns_del_endpoint
+ * \param[in] nse
+ * \param[in] sockaddr
+ * \return 0 on success, otherwise < 0
+ */
+int gprs_ns2_sns_del_endpoint(struct gprs_ns2_nse *nse,
+ const struct osmo_sockaddr *saddr)
+{
+ struct ns2_sns_state *gss;
+ struct sns_endpoint *endpoint;
+
+ if (nse->ll != GPRS_NS2_LL_UDP) {
+ return -EINVAL;
+ }
+
+ if (nse->dialect != GPRS_NS2_DIALECT_SNS) {
+ return -EINVAL;
+ }
+
+ gss = nse->bss_sns_fi->priv;
+ endpoint = ns2_get_sns_endpoint(gss, saddr);
+ if (!endpoint)
+ return -ENOENT;
+
+ /* if this is an unused SNS endpoint it's done */
+ if (gss->initial != endpoint) {
+ llist_del(&endpoint->list);
+ talloc_free(endpoint);
+ return 0;
+ }
+
+ /* gprs_ns2_free_nsvcs() will trigger NS2_SNS_EV_REQ_NO_NSVC on the last NS-VC
+ * and restart SNS SIZE procedure which selects a new initial */
+ LOGNSE(nse, LOGL_INFO, "Current in-use SNS endpoint is being removed."
+ "Closing all NS-VC and restart SNS-SIZE procedure"
+ "with a remaining SNS endpoint.\n");
+
+ /* Continue with the next endpoint in the list.
+ * Special case if the endpoint is at the start or end of the list */
+ if (endpoint->list.prev == &gss->sns_endpoints ||
+ endpoint->list.next == &gss->sns_endpoints)
+ gss->initial = NULL;
+ else
+ gss->initial = llist_entry(endpoint->list.next->prev,
+ struct sns_endpoint,
+ list);
+
+ llist_del(&endpoint->list);
+ gprs_ns2_free_nsvcs(nse);
+ talloc_free(endpoint);
+
+ return 0;
+}
- vty_out(vty, "Maximum number of remote NS-VCs: %zu, IPv4 Endpoints: %zu, IPv6 Endpoints: %zu%s",
- gss->num_max_nsvcs, gss->num_max_ip4_remote, gss->num_max_ip6_remote, VTY_NEWLINE);
+/*! gprs_ns2_sns_count
+ * \param[in] nse NS Entity whose IP-SNS endpoints shall be printed
+ * \return the count of endpoints or < 0 if NSE doesn't contain sns.
+ */
+int gprs_ns2_sns_count(struct gprs_ns2_nse *nse)
+{
+ struct ns2_sns_state *gss;
+ struct sns_endpoint *endpoint;
+ int count = 0;
+
+ if (nse->ll != GPRS_NS2_LL_UDP) {
+ return -EINVAL;
+ }
+
+ if (nse->dialect != GPRS_NS2_DIALECT_SNS) {
+ return -EINVAL;
+ }
+
+ gss = nse->bss_sns_fi->priv;
+ llist_for_each_entry(endpoint, &gss->sns_endpoints, list)
+ count++;
+
+ return count;
+}
+
+void ns2_sns_notify_alive(struct gprs_ns2_nse *nse, struct gprs_ns2_vc *nsvc, bool alive)
+{
+ struct ns2_sns_state *gss;
+ struct gprs_ns2_vc *tmp;
+
+ if (!nse->bss_sns_fi)
+ return;
+
+ gss = nse->bss_sns_fi->priv;
+ if (nse->bss_sns_fi->state != GPRS_SNS_ST_CONFIGURED && nse->bss_sns_fi->state != GPRS_SNS_ST_LOCAL_PROCEDURE)
+ return;
+
+ if (gss->block_no_nsvc_events)
+ return;
+
+ if (gss->alive && nse->sum_sig_weight == 0) {
+ sns_failed(nse->bss_sns_fi, "No signalling NSVC available");
+ return;
+ }
+
+ /* check if this is the current SNS NS-VC */
+ if (nsvc == gss->sns_nsvc && !alive) {
+ /* only replace the SNS NS-VC if there are other alive NS-VC.
+ * There aren't any other alive NS-VC when the SNS fsm just reached CONFIGURED
+ * and couldn't confirm yet if the NS-VC comes up */
+ llist_for_each_entry(tmp, &nse->nsvc, list) {
+ if (nsvc == tmp)
+ continue;
+ if (ns2_vc_is_unblocked(nsvc)) {
+ ns2_sns_replace_nsvc(nsvc);
+ break;
+ }
+ }
+ }
+
+ if (alive == gss->alive)
+ return;
+
+ if (alive) {
+ /* we need at least a signalling NSVC before become alive */
+ if (nse->sum_sig_weight == 0)
+ return;
+ gss->alive = true;
+ osmo_fsm_inst_dispatch(nse->bss_sns_fi, NS2_SNS_EV_REQ_NSVC_ALIVE, NULL);
+ } else {
+ /* is there at least another alive nsvc? */
+ llist_for_each_entry(tmp, &nse->nsvc, list) {
+ if (ns2_vc_is_unblocked(tmp))
+ return;
+ }
+
+ /* all NS-VC have failed */
+ gss->alive = false;
+ osmo_fsm_inst_dispatch(nse->bss_sns_fi, NS2_SNS_EV_REQ_NO_NSVC, NULL);
+ }
+}
+
+int gprs_ns2_sns_add_bind(struct gprs_ns2_nse *nse,
+ struct gprs_ns2_vc_bind *bind)
+{
+ struct ns2_sns_state *gss;
+ struct ns2_sns_bind *tmp;
+
+ OSMO_ASSERT(nse->bss_sns_fi);
+ gss = nse->bss_sns_fi->priv;
+
+ if (!gprs_ns2_is_ip_bind(bind)) {
+ return -EINVAL;
+ }
+
+ if (!llist_empty(&gss->binds)) {
+ llist_for_each_entry(tmp, &gss->binds, list) {
+ if (tmp->bind == bind)
+ return -EALREADY;
+ }
+ }
+
+ tmp = talloc_zero(gss, struct ns2_sns_bind);
+ if (!tmp)
+ return -ENOMEM;
+ tmp->bind = bind;
+ llist_add_tail(&tmp->list, &gss->binds);
+
+ osmo_fsm_inst_dispatch(nse->bss_sns_fi, NS2_SNS_EV_REQ_ADD_BIND, tmp);
+ return 0;
+}
+
+/* Remove a bind from the SNS. All assosiated NSVC must be removed. */
+int gprs_ns2_sns_del_bind(struct gprs_ns2_nse *nse,
+ struct gprs_ns2_vc_bind *bind)
+{
+ struct ns2_sns_state *gss;
+ struct ns2_sns_bind *tmp, *tmp2;
+ bool found = false;
+
+ if (!nse->bss_sns_fi)
+ return -EINVAL;
+
+ gss = nse->bss_sns_fi->priv;
+ if (gss->initial_bind && gss->initial_bind->bind == bind) {
+ if (gss->initial_bind->list.prev == &gss->binds)
+ gss->initial_bind = NULL;
+ else
+ gss->initial_bind = llist_entry(gss->initial_bind->list.prev, struct ns2_sns_bind, list);
+ }
+
+ llist_for_each_entry_safe(tmp, tmp2, &gss->binds, list) {
+ if (tmp->bind == bind) {
+ llist_del(&tmp->list);
+ found = true;
+ break;
+ }
+ }
+
+ if (!found)
+ return -ENOENT;
+
+ osmo_fsm_inst_dispatch(nse->bss_sns_fi, NS2_SNS_EV_REQ_DELETE_BIND, tmp);
+ return 0;
+}
+
+/* Update SNS weights for a bind (local endpoint).
+ * \param[in] bind the bind which has been updated
+ */
+void ns2_sns_update_weights(struct gprs_ns2_vc_bind *bind)
+{
+ struct ns2_sns_bind *sbind;
+ struct gprs_ns2_nse *nse;
+ struct ns2_sns_state *gss;
+ const struct osmo_sockaddr *addr = gprs_ns2_ip_bind_sockaddr(bind);
+
+ llist_for_each_entry(nse, &bind->nsi->nse, list) {
+ if (!nse->bss_sns_fi)
+ continue;
+
+ gss = nse->bss_sns_fi->priv;
+ if (addr->u.sa.sa_family != gss->family)
+ return;
+
+ llist_for_each_entry(sbind, &gss->binds, list) {
+ if (sbind->bind == bind) {
+ osmo_fsm_inst_dispatch(gss->nse->bss_sns_fi, NS2_SNS_EV_REQ_CHANGE_WEIGHT, sbind);
+ break;
+ }
+ }
+ }
+}
+
+
+
+
+/***********************************************************************
+ * SGSN role
+ ***********************************************************************/
+
+/* cleanup all state. If nsvc is given, don't remove this nsvc. (nsvc is given when a SIZE PDU received) */
+static void ns2_clear_sgsn(struct ns2_sns_state *gss, struct gprs_ns2_vc *size_nsvc)
+{
+ struct gprs_ns2_vc *nsvc, *nsvc2;
+
+ ns2_clear_procedures(gss);
+ ns2_clear_elems(&gss->local);
+ ns2_clear_elems(&gss->remote);
+ gss->block_no_nsvc_events = true;
+ llist_for_each_entry_safe(nsvc, nsvc2, &gss->nse->nsvc, list) {
+ /* Ignore the NSVC over which the SIZE PDU got received */
+ if (size_nsvc && size_nsvc == nsvc)
+ continue;
+
+ gprs_ns2_free_nsvc(nsvc);
+ }
+ gss->block_no_nsvc_events = false;
+}
+
+static void ns2_sns_st_sgsn_unconfigured_onenter(struct osmo_fsm_inst *fi, uint32_t old_state)
+{
+ struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
+
+ ns2_clear_sgsn(gss, NULL);
+}
+
+static void ns2_sns_st_sgsn_unconfigured(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
+ OSMO_ASSERT(gss->role == GPRS_SNS_ROLE_SGSN);
+ /* do nothing; Rx SNS-SIZE handled in ns2_sns_st_all_action_sgsn() */
+}
+
+/* We're waiting for inbound SNS-CONFIG from the BSS */
+static void ns2_sns_st_sgsn_wait_config(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
+ struct gprs_ns2_nse *nse = nse_inst_from_fi(fi);
+ struct gprs_ns2_inst *nsi = nse->nsi;
+ uint8_t cause;
+ int rc;
+
+ OSMO_ASSERT(gss->role == GPRS_SNS_ROLE_SGSN);
+
+ switch (event) {
+ case NS2_SNS_EV_RX_CONFIG:
+ case NS2_SNS_EV_RX_CONFIG_END:
+ rc = ns_sns_append_remote_eps(fi, data);
+ if (rc < 0) {
+ cause = -rc;
+ ns2_tx_sns_config_ack(gss->sns_nsvc, &cause);
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_UNCONFIGURED, 0, 0);
+ return;
+ }
+ /* only change state if last CONFIG was received */
+ if (event == NS2_SNS_EV_RX_CONFIG_END) {
+ /* ensure sum of data weight / sig weights is > 0 */
+ if (ip46_weight_sum_data(&gss->remote) == 0 || ip46_weight_sum_sig(&gss->remote) == 0) {
+ cause = NS_CAUSE_INVAL_WEIGH;
+ ns2_tx_sns_config_ack(gss->sns_nsvc, &cause);
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_UNCONFIGURED, 0, 0);
+ break;
+ }
+ ns2_tx_sns_config_ack(gss->sns_nsvc, NULL);
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_SGSN_WAIT_CONFIG_ACK, nsi->timeout[NS_TOUT_TSNS_PROV], 3);
+ } else {
+ /* just send CONFIG-ACK */
+ ns2_tx_sns_config_ack(gss->sns_nsvc, NULL);
+ osmo_timer_schedule(&fi->timer, nse->nsi->timeout[NS_TOUT_TSNS_PROV], 0);
+ }
+ break;
+ }
+}
+
+static void ns2_sns_st_sgsn_wait_config_ack_onenter(struct osmo_fsm_inst *fi, uint32_t old_state)
+{
+ struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
+ OSMO_ASSERT(gss->role == GPRS_SNS_ROLE_SGSN);
- if (gss->num_ip4_local && gss->num_ip4_remote) {
- vty_out(vty, "Local IPv4 Endpoints:%s", VTY_NEWLINE);
- for (i = 0; i < gss->num_ip4_local; i++)
- vty_dump_sns_ip4(vty, &gss->ip4_local[i]);
+ /* transmit SGSN-oriented SNS-CONFIG */
+ ns2_tx_sns_config(gss->sns_nsvc, true, gss->local.ip4, gss->local.num_ip4,
+ gss->local.ip6, gss->local.num_ip6);
+}
+
+/* We're waiting for SNS-CONFIG-ACK from the BSS (in response to our outbound SNS-CONFIG) */
+static void ns2_sns_st_sgsn_wait_config_ack(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
+ struct gprs_ns2_nse *nse = nse_inst_from_fi(fi);
+ struct tlv_parsed *tp = NULL;
+
+ OSMO_ASSERT(gss->role == GPRS_SNS_ROLE_SGSN);
+
+ switch (event) {
+ case NS2_SNS_EV_RX_CONFIG_ACK:
+ tp = data;
+ if (TLVP_VAL_MINLEN(tp, NS_IE_CAUSE, 1)) {
+ LOGPFSML(fi, LOGL_ERROR, "Rx SNS-CONFIG-ACK with cause %s\n",
+ gprs_ns2_cause_str(*TLVP_VAL(tp, NS_IE_CAUSE)));
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_UNCONFIGURED, 0, 0);
+ break;
+ }
+ /* we currently only send one SNS-CONFIG with END FLAG */
+ if (true) {
+ create_missing_nsvcs(fi);
+ /* start the test procedure on ALL NSVCs! */
+ gprs_ns2_start_alive_all_nsvcs(nse);
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_CONFIGURED, ns_sns_configured_timeout(fi), 4);
+ }
+ break;
+ }
+}
- vty_out(vty, "Remote IPv4 Endpoints:%s", VTY_NEWLINE);
- for (i = 0; i < gss->num_ip4_remote; i++)
- vty_dump_sns_ip4(vty, &gss->ip4_remote[i]);
+/* SGSN-side SNS state machine */
+static const struct osmo_fsm_state ns2_sns_sgsn_states[] = {
+ [GPRS_SNS_ST_UNCONFIGURED] = {
+ .in_event_mask = 0, /* handled by all_state_action */
+ .out_state_mask = S(GPRS_SNS_ST_UNCONFIGURED) |
+ S(GPRS_SNS_ST_SGSN_WAIT_CONFIG),
+ .name = "UNCONFIGURED",
+ .action = ns2_sns_st_sgsn_unconfigured,
+ .onenter = ns2_sns_st_sgsn_unconfigured_onenter,
+ },
+ [GPRS_SNS_ST_SGSN_WAIT_CONFIG] = {
+ .in_event_mask = S(NS2_SNS_EV_RX_CONFIG) |
+ S(NS2_SNS_EV_RX_CONFIG_END),
+ .out_state_mask = S(GPRS_SNS_ST_UNCONFIGURED) |
+ S(GPRS_SNS_ST_SGSN_WAIT_CONFIG) |
+ S(GPRS_SNS_ST_SGSN_WAIT_CONFIG_ACK),
+ .name = "SGSN_WAIT_CONFIG",
+ .action = ns2_sns_st_sgsn_wait_config,
+ },
+ [GPRS_SNS_ST_SGSN_WAIT_CONFIG_ACK] = {
+ .in_event_mask = S(NS2_SNS_EV_RX_CONFIG_ACK),
+ .out_state_mask = S(GPRS_SNS_ST_UNCONFIGURED) |
+ S(GPRS_SNS_ST_SGSN_WAIT_CONFIG) |
+ S(GPRS_SNS_ST_SGSN_WAIT_CONFIG_ACK) |
+ S(GPRS_SNS_ST_CONFIGURED),
+ .name = "SGSN_WAIT_CONFIG_ACK",
+ .action = ns2_sns_st_sgsn_wait_config_ack,
+ .onenter = ns2_sns_st_sgsn_wait_config_ack_onenter,
+ },
+ [GPRS_SNS_ST_CONFIGURED] = {
+ .in_event_mask = S(NS2_SNS_EV_RX_ADD) |
+ S(NS2_SNS_EV_RX_DELETE) |
+ S(NS2_SNS_EV_RX_CHANGE_WEIGHT) |
+ S(NS2_SNS_EV_REQ_NSVC_ALIVE),
+ .out_state_mask = S(GPRS_SNS_ST_UNCONFIGURED) |
+ S(GPRS_SNS_ST_SGSN_WAIT_CONFIG) |
+ S(GPRS_SNS_ST_LOCAL_PROCEDURE),
+ .name = "CONFIGURED",
+ /* shared with BSS side; once configured there's no difference */
+ .action = ns2_sns_st_configured,
+ .onenter = ns2_sns_st_configured_onenter,
+ },
+ [GPRS_SNS_ST_LOCAL_PROCEDURE] = {
+ .in_event_mask = S(NS2_SNS_EV_RX_ADD) |
+ S(NS2_SNS_EV_RX_DELETE) |
+ S(NS2_SNS_EV_RX_CHANGE_WEIGHT) |
+ S(NS2_SNS_EV_RX_ACK) |
+ S(NS2_SNS_EV_REQ_CHANGE_WEIGHT) |
+ S(NS2_SNS_EV_REQ_NSVC_ALIVE),
+ .out_state_mask = S(GPRS_SNS_ST_UNCONFIGURED) |
+ S(GPRS_SNS_ST_CONFIGURED) |
+ S(GPRS_SNS_ST_LOCAL_PROCEDURE),
+ .name = "LOCAL_PROCEDURE",
+ /* shared with BSS side; once configured there's no difference */
+ .action = ns2_sns_st_local_procedure,
+ .onenter = ns2_sns_st_local_procedure_onenter,
+ },
+};
+
+static int ns2_sns_fsm_sgsn_timer_cb(struct osmo_fsm_inst *fi)
+{
+ struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
+ struct gprs_ns2_nse *nse = nse_inst_from_fi(fi);
+ struct gprs_ns2_inst *nsi = nse->nsi;
+
+ gss->N++;
+ switch (fi->T) {
+ case 3:
+ if (gss->N >= nsi->timeout[NS_TOUT_TSNS_CONFIG_RETRIES]) {
+ LOGPFSML(fi, LOGL_ERROR, "NSE %d: SGSN Config retries failed. Giving up.\n", nse->nsei);
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_UNCONFIGURED, nsi->timeout[NS_TOUT_TSNS_PROV], 3);
+ } else {
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_SGSN_WAIT_CONFIG_ACK, nsi->timeout[NS_TOUT_TSNS_PROV], 3);
+ }
+ break;
+ case 4:
+ LOGPFSML(fi, LOGL_ERROR, "NSE %d: Config succeeded but no NS-VC came online.\n", nse->nsei);
+ break;
+ case 5:
+ if (gss->N >= nsi->timeout[NS_TOUT_TSNS_PROCEDURES_RETRIES]) {
+ sns_failed(fi, "SNS Procedure retries failed.");
+ } else {
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_LOCAL_PROCEDURE, nsi->timeout[NS_TOUT_TSNS_PROV],
+ fi->T);
+ }
+ break;
}
- if (gss->num_ip6_local && gss->num_ip6_remote) {
- vty_out(vty, "Local IPv6 Endpoints:%s", VTY_NEWLINE);
- for (i = 0; i < gss->num_ip6_local; i++)
- vty_dump_sns_ip6(vty, &gss->ip6_local[i]);
+ return 0;
+}
+
+/* allstate-action for SGSN role */
+static void ns2_sns_st_all_action_sgsn(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
+ struct tlv_parsed *tp = NULL;
+ size_t num_local_eps, num_remote_eps;
+ uint8_t flag;
+ uint8_t cause;
+
+ OSMO_ASSERT(gss->role == GPRS_SNS_ROLE_SGSN);
- vty_out(vty, "Remote IPv6 Endpoints:%s", VTY_NEWLINE);
- for (i = 0; i < gss->num_ip6_remote; i++)
- vty_dump_sns_ip6(vty, &gss->ip6_remote[i]);
+ switch (event) {
+ case NS2_SNS_EV_RX_SIZE:
+ tp = (struct tlv_parsed *) data;
+ /* check for mandatory / conditional IEs */
+ if (!TLVP_PRES_LEN(tp, NS_IE_RESET_FLAG, 1) ||
+ !TLVP_PRES_LEN(tp, NS_IE_MAX_NR_NSVC, 2)) {
+ cause = NS_CAUSE_MISSING_ESSENT_IE;
+ ns2_tx_sns_size_ack(gss->sns_nsvc, &cause);
+ if (fi->state == GPRS_SNS_ST_UNCONFIGURED)
+ sns_failed(fi, "Rx Size: Missing essential IE");
+ break;
+ }
+ if (!TLVP_PRES_LEN(tp, NS_IE_IPv4_EP_NR, 2) &&
+ !TLVP_PRES_LEN(tp, NS_IE_IPv6_EP_NR, 2)) {
+ cause = NS_CAUSE_MISSING_ESSENT_IE;
+ ns2_tx_sns_size_ack(gss->sns_nsvc, &cause);
+ if (fi->state == GPRS_SNS_ST_UNCONFIGURED)
+ sns_failed(fi, "Rx Size: Missing essential IE");
+ break;
+ }
+ if (TLVP_PRES_LEN(tp, NS_IE_IPv4_EP_NR, 2))
+ gss->num_max_ip4_remote = tlvp_val16be(tp, NS_IE_IPv4_EP_NR);
+ if (TLVP_PRES_LEN(tp, NS_IE_IPv6_EP_NR, 2))
+ gss->num_max_ip6_remote = tlvp_val16be(tp, NS_IE_IPv6_EP_NR);
+ /* decide if we go for IPv4 or IPv6 */
+ if (gss->num_max_ip6_remote && ns2_sns_count_num_local_ep(fi, AF_INET6)) {
+ gss->family = AF_INET6;
+ ns2_sns_compute_local_ep_from_binds(fi);
+ num_local_eps = gss->local.num_ip6;
+ num_remote_eps = gss->num_max_ip6_remote;
+ } else if (gss->num_max_ip4_remote && ns2_sns_count_num_local_ep(fi, AF_INET)) {
+ gss->family = AF_INET;
+ ns2_sns_compute_local_ep_from_binds(fi);
+ num_local_eps = gss->local.num_ip4;
+ num_remote_eps = gss->num_max_ip4_remote;
+ } else {
+ if (gss->local.num_ip4 && !gss->num_max_ip4_remote)
+ cause = NS_CAUSE_INVAL_NR_IPv4_EP;
+ else
+ cause = NS_CAUSE_INVAL_NR_IPv6_EP;
+ ns2_tx_sns_size_ack(gss->sns_nsvc, &cause);
+ if (fi->state == GPRS_SNS_ST_UNCONFIGURED)
+ sns_failed(fi, "Rx Size: Invalid Nr of IPv4/IPv6 EPs");
+ break;
+ }
+ /* ensure number of NS-VCs is sufficient for full mesh */
+ gss->num_max_nsvcs = tlvp_val16be(tp, NS_IE_MAX_NR_NSVC);
+ if (gss->num_max_nsvcs < num_remote_eps * num_local_eps) {
+ LOGPFSML(fi, LOGL_ERROR, "%zu local and %zu remote EPs, requires %zu NS-VC, "
+ "but BSS supports only %zu maximum NS-VCs\n", num_local_eps,
+ num_remote_eps, num_local_eps * num_remote_eps, gss->num_max_nsvcs);
+ cause = NS_CAUSE_INVAL_NR_NS_VC;
+ ns2_tx_sns_size_ack(gss->sns_nsvc, &cause);
+ if (fi->state == GPRS_SNS_ST_UNCONFIGURED)
+ sns_failed(fi, NULL);
+ break;
+ }
+ /* perform state reset, if requested */
+ flag = *TLVP_VAL(tp, NS_IE_RESET_FLAG);
+ if (flag & 1) {
+ /* clear all state */
+ /* TODO: ensure gss->sns_nsvc is always the NSVC on which we received the SIZE PDU */
+ gss->N = 0;
+ ns2_clear_sgsn(gss, gss->sns_nsvc);
+ /* keep the NSVC we need for SNS, but unconfigure it */
+ gss->sns_nsvc->sig_weight = 0;
+ gss->sns_nsvc->data_weight = 0;
+ gss->block_no_nsvc_events = true;
+ ns2_vc_force_unconfigured(gss->sns_nsvc);
+ gss->block_no_nsvc_events = false;
+ ns2_sns_compute_local_ep_from_binds(fi);
+ }
+
+ if (fi->state == GPRS_SNS_ST_UNCONFIGURED && !(flag & 1)) {
+ sns_failed(fi, "Rx Size without Reset flag, but NSE is unknown");
+ break;
+ }
+
+ /* send SIZE_ACK */
+ ns2_tx_sns_size_ack(gss->sns_nsvc, NULL);
+ /* only wait for SNS-CONFIG in case of Reset flag */
+ if (flag & 1)
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_SGSN_WAIT_CONFIG, 0, 0);
+ break;
+ case NS2_SNS_EV_REQ_FREE_NSVCS:
+ sns_failed(fi, "On user request to free all NSVCs");
+ break;
+ default:
+ ns2_sns_st_all_action(fi, event, data);
+ break;
}
}
+static struct osmo_fsm gprs_ns2_sns_sgsn_fsm = {
+ .name = "GPRS-NS2-SNS-SGSN",
+ .states = ns2_sns_sgsn_states,
+ .num_states = ARRAY_SIZE(ns2_sns_sgsn_states),
+ .allstate_event_mask = S(NS2_SNS_EV_RX_SIZE) |
+ S(NS2_SNS_EV_REQ_NO_NSVC) |
+ S(NS2_SNS_EV_REQ_FREE_NSVCS) |
+ S(NS2_SNS_EV_REQ_ADD_BIND) |
+ S(NS2_SNS_EV_REQ_CHANGE_WEIGHT) |
+ S(NS2_SNS_EV_REQ_DELETE_BIND),
+ .allstate_action = ns2_sns_st_all_action_sgsn,
+ .cleanup = NULL,
+ .timer_cb = ns2_sns_fsm_sgsn_timer_cb,
+ .event_names = gprs_sns_event_names,
+ .pre_term = NULL,
+ .log_subsys = DLNS,
+};
+
+/*! Allocate an IP-SNS FSM for the SGSN side.
+ * \param[in] nse NS Entity in which the FSM runs
+ * \param[in] id string identifier
+ * \returns FSM instance on success; NULL on error */
+struct osmo_fsm_inst *ns2_sns_sgsn_fsm_alloc(struct gprs_ns2_nse *nse, const char *id)
+{
+ struct osmo_fsm_inst *fi;
+ struct ns2_sns_state *gss;
+
+ fi = osmo_fsm_inst_alloc(&gprs_ns2_sns_sgsn_fsm, nse, NULL, LOGL_DEBUG, id);
+ if (!fi)
+ return fi;
+
+ gss = talloc_zero(fi, struct ns2_sns_state);
+ if (!gss)
+ goto err;
+
+ fi->priv = gss;
+ gss->nse = nse;
+ gss->role = GPRS_SNS_ROLE_SGSN;
+ INIT_LLIST_HEAD(&gss->sns_endpoints);
+ INIT_LLIST_HEAD(&gss->binds);
+ INIT_LLIST_HEAD(&gss->procedures);
+
+ return fi;
+err:
+ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL);
+ return NULL;
+}
+
+
+
+
/* initialize osmo_ctx on main tread */
static __attribute__((constructor)) void on_dso_load_ctx(void)
{
OSMO_ASSERT(osmo_fsm_register(&gprs_ns2_sns_bss_fsm) == 0);
+ OSMO_ASSERT(osmo_fsm_register(&gprs_ns2_sns_sgsn_fsm) == 0);
}
diff --git a/src/gb/gprs_ns2_udp.c b/src/gb/gprs_ns2_udp.c
index 26a04836..1dcc3030 100644
--- a/src/gb/gprs_ns2_udp.c
+++ b/src/gb/gprs_ns2_udp.c
@@ -28,6 +28,7 @@
#include <errno.h>
+#include <osmocom/core/osmo_io.h>
#include <osmocom/core/select.h>
#include <osmocom/core/sockaddr_str.h>
#include <osmocom/core/socket.h>
@@ -46,9 +47,10 @@ struct gprs_ns2_vc_driver vc_driver_ip = {
};
struct priv_bind {
- struct osmo_fd fd;
+ struct osmo_io_fd *iofd;
struct osmo_sockaddr addr;
int dscp;
+ uint8_t priority;
};
struct priv_vc {
@@ -63,23 +65,30 @@ static void free_bind(struct gprs_ns2_vc_bind *bind)
if (!bind)
return;
+ OSMO_ASSERT(gprs_ns2_is_ip_bind(bind));
+
priv = bind->priv;
- osmo_fd_close(&priv->fd);
+ osmo_iofd_free(priv->iofd);
+ priv->iofd = NULL;
talloc_free(priv);
}
static void free_vc(struct gprs_ns2_vc *nsvc)
{
+ if (!nsvc)
+ return;
+
if (!nsvc->priv)
return;
+ OSMO_ASSERT(gprs_ns2_is_ip_bind(nsvc->bind));
talloc_free(nsvc->priv);
nsvc->priv = NULL;
}
static void dump_vty(const struct gprs_ns2_vc_bind *bind,
- struct vty *vty, bool _stats)
+ struct vty *vty, bool stats)
{
struct priv_bind *priv;
struct gprs_ns2_vc *nsvc;
@@ -97,30 +106,35 @@ static void dump_vty(const struct gprs_ns2_vc_bind *bind,
nsvcs++;
}
- vty_out(vty, "UDP bind: %s:%d dcsp: %d%s", sockstr.ip, sockstr.port, priv->dscp, VTY_NEWLINE);
- vty_out(vty, " %lu NS-VC: %s", nsvcs, VTY_NEWLINE);
+ vty_out(vty, "UDP bind: %s:%d DSCP: %d Priority: %u%s", sockstr.ip, sockstr.port,
+ priv->dscp, priv->priority, VTY_NEWLINE);
+ vty_out(vty, " IP-SNS signalling weight: %u data weight: %u%s",
+ bind->sns_sig_weight, bind->sns_data_weight, VTY_NEWLINE);
+ vty_out(vty, " %lu NS-VC:%s", nsvcs, VTY_NEWLINE);
llist_for_each_entry(nsvc, &bind->nsvc, blist) {
- vty_out(vty, " %s%s", gprs_ns2_ll_str(nsvc), VTY_NEWLINE);
+ ns2_vty_dump_nsvc(vty, nsvc, stats);
}
}
/*! Find a NS-VC by its remote socket address.
* \param[in] bind in which to search
- * \param[in] saddr remote peer socket adddress to search
+ * \param[in] rem_addr remote peer socket address to search
* \returns NS-VC matching sockaddr; NULL if none found */
struct gprs_ns2_vc *gprs_ns2_nsvc_by_sockaddr_bind(struct gprs_ns2_vc_bind *bind,
- const struct osmo_sockaddr *saddr)
+ const struct osmo_sockaddr *rem_addr)
{
struct gprs_ns2_vc *nsvc;
struct priv_vc *vcpriv;
+ OSMO_ASSERT(gprs_ns2_is_ip_bind(bind));
+
llist_for_each_entry(nsvc, &bind->nsvc, blist) {
vcpriv = nsvc->priv;
- if (vcpriv->remote.u.sa.sa_family != saddr->u.sa.sa_family)
+ if (vcpriv->remote.u.sa.sa_family != rem_addr->u.sa.sa_family)
continue;
- if (osmo_sockaddr_cmp(&vcpriv->remote, saddr))
+ if (osmo_sockaddr_cmp(&vcpriv->remote, rem_addr))
continue;
return nsvc;
@@ -131,17 +145,11 @@ struct gprs_ns2_vc *gprs_ns2_nsvc_by_sockaddr_bind(struct gprs_ns2_vc_bind *bind
static inline int nsip_sendmsg(struct gprs_ns2_vc_bind *bind,
struct msgb *msg,
- struct osmo_sockaddr *dest)
+ const struct osmo_sockaddr *dest)
{
- int rc;
struct priv_bind *priv = bind->priv;
- rc = sendto(priv->fd.fd, msg->data, msg->len, 0,
- &dest->u.sa, sizeof(*dest));
-
- msgb_free(msg);
-
- return rc;
+ return osmo_iofd_sendto_msgb(priv->iofd, msg, 0, dest);
}
/*! send the msg and free it afterwards.
@@ -159,40 +167,7 @@ static int nsip_vc_sendmsg(struct gprs_ns2_vc *nsvc, struct msgb *msg)
return rc;
}
-/* Read a single NS-over-IP message */
-static struct msgb *read_nsip_msg(struct osmo_fd *bfd, int *error,
- struct osmo_sockaddr *saddr)
-{
- struct msgb *msg = gprs_ns2_msgb_alloc();
- int ret = 0;
- socklen_t saddr_len = sizeof(*saddr);
-
- if (!msg) {
- *error = -ENOMEM;
- return NULL;
- }
-
- ret = recvfrom(bfd->fd, msg->data, NS_ALLOC_SIZE - NS_ALLOC_HEADROOM, 0,
- &saddr->u.sa, &saddr_len);
- if (ret < 0) {
- LOGP(DLNS, LOGL_ERROR, "recv error %s during NSIP recvfrom %s\n",
- strerror(errno), osmo_sock_get_name2(bfd->fd));
- msgb_free(msg);
- *error = ret;
- return NULL;
- } else if (ret == 0) {
- msgb_free(msg);
- *error = ret;
- return NULL;
- }
-
- msg->l2h = msg->data;
- msgb_put(msg, ret);
-
- return msg;
-}
-
-static struct priv_vc *ns2_driver_alloc_vc(struct gprs_ns2_vc_bind *bind, struct gprs_ns2_vc *nsvc, struct osmo_sockaddr *remote)
+static struct priv_vc *ns2_driver_alloc_vc(struct gprs_ns2_vc_bind *bind, struct gprs_ns2_vc *nsvc, const struct osmo_sockaddr *remote)
{
struct priv_vc *priv = talloc_zero(bind, struct priv_vc);
if (!priv)
@@ -204,65 +179,67 @@ static struct priv_vc *ns2_driver_alloc_vc(struct gprs_ns2_vc_bind *bind, struct
return priv;
}
-static int handle_nsip_read(struct osmo_fd *bfd)
+static void handle_nsip_recvfrom(struct osmo_io_fd *iofd, int error, struct msgb *msg,
+ const struct osmo_sockaddr *saddr)
{
int rc = 0;
- int error = 0;
- struct gprs_ns2_vc_bind *bind = bfd->data;
- struct osmo_sockaddr saddr;
+ struct gprs_ns2_vc_bind *bind = osmo_iofd_get_data(iofd);
struct gprs_ns2_vc *nsvc;
- struct msgb *msg = read_nsip_msg(bfd, &error, &saddr);
+
struct msgb *reject;
- if (!msg)
- return -EINVAL;
+ msg->l2h = msgb_data(msg);
/* check if a vc is available */
- nsvc = gprs_ns2_nsvc_by_sockaddr_bind(bind, &saddr);
+ nsvc = gprs_ns2_nsvc_by_sockaddr_bind(bind, saddr);
if (!nsvc) {
/* VC not found */
- rc = ns2_create_vc(bind, msg, "newconnection", &reject, &nsvc);
+ rc = ns2_create_vc(bind, msg, saddr, "newconnection", &reject, &nsvc);
switch (rc) {
- case GPRS_NS2_CS_FOUND:
+ case NS2_CS_FOUND:
break;
- case GPRS_NS2_CS_ERROR:
- case GPRS_NS2_CS_SKIPPED:
+ case NS2_CS_ERROR:
+ case NS2_CS_SKIPPED:
rc = 0;
goto out;
- case GPRS_NS2_CS_REJECTED:
+ case NS2_CS_REJECTED:
/* nsip_sendmsg will free reject */
- rc = nsip_sendmsg(bind, reject, &saddr);
+ rc = nsip_sendmsg(bind, reject, saddr);
goto out;
- case GPRS_NS2_CS_CREATED:
- ns2_driver_alloc_vc(bind, nsvc, &saddr);
- gprs_ns2_vc_fsm_start(nsvc);
+ case NS2_CS_CREATED:
+ ns2_driver_alloc_vc(bind, nsvc, saddr);
+ /* only start the fsm for non SNS. SNS will take care of its own */
+ if (nsvc->nse->dialect != GPRS_NS2_DIALECT_SNS)
+ ns2_vc_fsm_start(nsvc);
break;
}
}
- return ns2_recv_vc(nsvc, msg);
+ ns2_recv_vc(nsvc, msg);
+ return;
out:
msgb_free(msg);
- return rc;
}
-static int handle_nsip_write(struct osmo_fd *bfd)
-{
- /* FIXME: actually send the data here instead of nsip_sendmsg() */
- return -EIO;
-}
-
-static int nsip_fd_cb(struct osmo_fd *bfd, unsigned int what)
+static void handle_nsip_sendto(struct osmo_io_fd *iofd, int res,
+ struct msgb *msg,
+ const struct osmo_sockaddr *daddr)
{
- int rc = 0;
+ struct gprs_ns2_vc_bind *bind = osmo_iofd_get_data(iofd);
+ struct gprs_ns2_vc *nsvc;
- if (what & OSMO_FD_READ)
- rc = handle_nsip_read(bfd);
- if (what & OSMO_FD_WRITE)
- rc = handle_nsip_write(bfd);
+ nsvc = gprs_ns2_nsvc_by_sockaddr_bind(bind, daddr);
+ if (!nsvc)
+ return;
- return rc;
+ if (OSMO_LIKELY(res >= 0)) {
+ RATE_CTR_INC_NS(nsvc, NS_CTR_PKTS_OUT);
+ RATE_CTR_ADD_NS(nsvc, NS_CTR_BYTES_OUT, res);
+ } else {
+ RATE_CTR_INC_NS(nsvc, NS_CTR_PKTS_OUT_DROP);
+ RATE_CTR_ADD_NS(nsvc, NS_CTR_BYTES_OUT_DROP, msgb_length(msg));
+ }
}
/*! Find NS bind for a given socket address
@@ -295,9 +272,10 @@ struct gprs_ns2_vc_bind *gprs_ns2_ip_bind_by_sockaddr(struct gprs_ns2_inst *nsi,
* \param[in] nsi NS Instance in which to create the NSVC
* \param[in] local the local address to bind to
* \param[in] dscp the DSCP/TOS bits used for transmitted data
- * \param[out] result if set, returns the bind object
- * \return 0 on success; negative in case of error */
+ * \param[out] result pointer to the created bind or if a bind with the name exists return the bind.
+ * \return 0 on success; negative on error. -EALREADY returned in case a bind with the name exists */
int gprs_ns2_ip_bind(struct gprs_ns2_inst *nsi,
+ const char *name,
const struct osmo_sockaddr *local,
int dscp,
struct gprs_ns2_vc_bind **result)
@@ -306,60 +284,64 @@ int gprs_ns2_ip_bind(struct gprs_ns2_inst *nsi,
struct priv_bind *priv;
int rc;
+ struct osmo_io_ops ioops = {
+ .sendto_cb = &handle_nsip_sendto,
+ .recvfrom_cb = &handle_nsip_recvfrom,
+ };
+
+ if (local->u.sa.sa_family != AF_INET && local->u.sa.sa_family != AF_INET6)
+ return -EINVAL;
+
+ if (dscp < 0 || dscp > 63)
+ return -EINVAL;
+
bind = gprs_ns2_ip_bind_by_sockaddr(nsi, local);
if (bind) {
- *result = bind;
+ if (result)
+ *result = bind;
return -EBUSY;
}
- bind = talloc_zero(nsi, struct gprs_ns2_vc_bind);
- if (!bind)
- return -ENOSPC;
-
- if (local->u.sa.sa_family != AF_INET && local->u.sa.sa_family != AF_INET6) {
- talloc_free(bind);
- return -EINVAL;
- }
+ rc = ns2_bind_alloc(nsi, name, &bind);
+ if (rc < 0)
+ return rc;
bind->driver = &vc_driver_ip;
+ bind->ll = GPRS_NS2_LL_UDP;
+ /* expect 100 mbit at least.
+ * TODO: ask the network layer about the speed. But would require
+ * notification on change. */
+ bind->transfer_capability = 100;
bind->send_vc = nsip_vc_sendmsg;
bind->free_vc = free_vc;
bind->dump_vty = dump_vty;
- bind->nsi = nsi;
priv = bind->priv = talloc_zero(bind, struct priv_bind);
if (!priv) {
- talloc_free(bind);
- return -ENOSPC;
+ gprs_ns2_free_bind(bind);
+ return -ENOMEM;
}
- priv->fd.cb = nsip_fd_cb;
- priv->fd.data = bind;
+
priv->addr = *local;
- INIT_LLIST_HEAD(&bind->nsvc);
+ priv->dscp = dscp;
- rc = osmo_sock_init_osa_ofd(&priv->fd, SOCK_DGRAM, IPPROTO_UDP,
+ rc = osmo_sock_init_osa(SOCK_DGRAM, IPPROTO_UDP,
local, NULL,
- OSMO_SOCK_F_BIND);
+ OSMO_SOCK_F_BIND | OSMO_SOCK_F_DSCP(priv->dscp));
if (rc < 0) {
- talloc_free(priv);
- talloc_free(bind);
+ gprs_ns2_free_bind(bind);
return rc;
}
- if (dscp > 0) {
- priv->dscp = dscp;
-
- rc = setsockopt(priv->fd.fd, IPPROTO_IP, IP_TOS,
- &dscp, sizeof(dscp));
- if (rc < 0)
- LOGP(DLNS, LOGL_ERROR,
- "Failed to set the DSCP to %d with ret(%d) errno(%d)\n",
- dscp, rc, errno);
- }
-
- llist_add(&bind->list, &nsi->binding);
- ns2_vty_bind_apply(bind);
+ priv->iofd = osmo_iofd_setup(bind, rc, "NS bind", OSMO_IO_FD_MODE_RECVFROM_SENDTO, &ioops, bind);
+ osmo_iofd_register(priv->iofd, rc);
+ osmo_iofd_set_alloc_info(priv->iofd, 4096, 128);
+ osmo_iofd_set_txqueue_max_length(priv->iofd, nsi->txqueue_max_length);
+ /* IPv4: max fragmented payload can be (13 bit) * 8 byte => 65535.
+ * IPv6: max payload can be 65535 (RFC 2460).
+ * UDP header = 8 byte */
+ bind->mtu = 65535 - 8;
if (result)
*result = bind;
@@ -371,14 +353,36 @@ int gprs_ns2_ip_bind(struct gprs_ns2_inst *nsi,
* \param[in] nse NS entity to be used for the new NS-VC
* \param[in] remote remote address to connect to
* \return pointer to newly-allocated and connected NS-VC; NULL on error */
-struct gprs_ns2_vc *gprs_ns2_ip_bind_connect(struct gprs_ns2_vc_bind *bind,
- struct gprs_ns2_nse *nse,
- const struct osmo_sockaddr *remote)
+struct gprs_ns2_vc *ns2_ip_bind_connect(struct gprs_ns2_vc_bind *bind,
+ struct gprs_ns2_nse *nse,
+ const struct osmo_sockaddr *remote)
{
struct gprs_ns2_vc *nsvc;
+ const struct osmo_sockaddr *local;
struct priv_vc *priv;
+ enum gprs_ns2_vc_mode vc_mode;
+ char idbuf[256], tmp[INET6_ADDRSTRLEN + 8];
+
+ OSMO_ASSERT(gprs_ns2_is_ip_bind(bind));
+
+ vc_mode = ns2_dialect_to_vc_mode(nse->dialect);
+ if ((int) vc_mode == -1) {
+ LOGNSE(nse, LOGL_ERROR, "Can not derive vc mode from dialect %d. Maybe libosmocore is too old.\n",
+ nse->dialect);
+ return NULL;
+ }
+
+ /* duplicate */
+ if (gprs_ns2_nsvc_by_sockaddr_bind(bind, remote))
+ return NULL;
+
+ local = gprs_ns2_ip_bind_sockaddr(bind);
+ osmo_sockaddr_to_str_buf(tmp, sizeof(tmp), local);
+ snprintf(idbuf, sizeof(idbuf), "NSE%05u-NSVC-%s-%s-%s", nse->nsei, gprs_ns2_lltype_str(nse->ll),
+ tmp, osmo_sockaddr_to_str(remote));
+ osmo_identifier_sanitize_buf(idbuf, NULL, '_');
- nsvc = ns2_vc_alloc(bind, nse, true);
+ nsvc = ns2_vc_alloc(bind, nse, true, vc_mode, idbuf);
if (!nsvc)
return NULL;
@@ -391,8 +395,6 @@ struct gprs_ns2_vc *gprs_ns2_ip_bind_connect(struct gprs_ns2_vc_bind *bind,
priv = nsvc->priv;
priv->remote = *remote;
- nsvc->ll = GPRS_NS_LL_UDP;
-
return nsvc;
}
@@ -403,9 +405,6 @@ const struct osmo_sockaddr *gprs_ns2_ip_vc_local(const struct gprs_ns2_vc *nsvc)
{
struct priv_bind *priv;
- if (nsvc->ll != GPRS_NS_LL_UDP)
- return NULL;
-
if (nsvc->bind->driver != &vc_driver_ip)
return NULL;
@@ -420,7 +419,7 @@ const struct osmo_sockaddr *gprs_ns2_ip_vc_remote(const struct gprs_ns2_vc *nsvc
{
struct priv_vc *priv;
- if (nsvc->ll != GPRS_NS_LL_UDP)
+ if (nsvc->bind->driver != &vc_driver_ip)
return NULL;
priv = nsvc->priv;
@@ -442,7 +441,7 @@ bool gprs_ns2_ip_vc_equal(const struct gprs_ns2_vc *nsvc,
struct priv_vc *vpriv;
struct priv_bind *bpriv;
- if (nsvc->ll != GPRS_NS_LL_UDP)
+ if (nsvc->bind->driver != &vc_driver_ip)
return false;
vpriv = nsvc->priv;
@@ -454,7 +453,7 @@ bool gprs_ns2_ip_vc_equal(const struct gprs_ns2_vc *nsvc,
if (osmo_sockaddr_cmp(remote, &vpriv->remote))
return false;
- if (nsvc->mode == NS2_VC_MODE_BLOCKRESET)
+ if (nsvc->mode == GPRS_NS2_VC_MODE_BLOCKRESET)
if (nsvc->nsvci != nsvci)
return false;
@@ -467,6 +466,7 @@ bool gprs_ns2_ip_vc_equal(const struct gprs_ns2_vc *nsvc,
const struct osmo_sockaddr *gprs_ns2_ip_bind_sockaddr(struct gprs_ns2_vc_bind *bind)
{
struct priv_bind *priv;
+ OSMO_ASSERT(gprs_ns2_is_ip_bind(bind));
priv = bind->priv;
return &priv->addr;
@@ -484,18 +484,114 @@ int gprs_ns2_ip_bind_set_dscp(struct gprs_ns2_vc_bind *bind, int dscp)
struct priv_bind *priv;
int rc = 0;
+ if (dscp < 0 || dscp > 63)
+ return -EINVAL;
+
+ OSMO_ASSERT(gprs_ns2_is_ip_bind(bind));
priv = bind->priv;
if (dscp != priv->dscp) {
priv->dscp = dscp;
- rc = setsockopt(priv->fd.fd, IPPROTO_IP, IP_TOS,
- &dscp, sizeof(dscp));
- if (rc < 0)
- LOGP(DLNS, LOGL_ERROR,
- "Failed to set the DSCP to %d with ret(%d) errno(%d)\n",
- dscp, rc, errno);
+ rc = osmo_sock_set_dscp(osmo_iofd_get_fd(priv->iofd), dscp);
+ if (rc < 0) {
+ LOGBIND(bind, LOGL_ERROR, "Failed to set the DSCP to %u with ret(%d) errno(%d)\n",
+ dscp, rc, errno);
+ }
+ }
+
+ return rc;
+}
+
+/*! Set the socket priority of the given bind. */
+int gprs_ns2_ip_bind_set_priority(struct gprs_ns2_vc_bind *bind, uint8_t priority)
+{
+ struct priv_bind *priv;
+ int rc = 0;
+
+ OSMO_ASSERT(gprs_ns2_is_ip_bind(bind));
+ priv = bind->priv;
+
+ if (priority != priv->priority) {
+ priv->priority = priority;
+
+ rc = osmo_sock_set_priority(osmo_iofd_get_fd(priv->iofd), priority);
+ if (rc < 0) {
+ LOGBIND(bind, LOGL_ERROR, "Failed to set the priority to %u with ret(%d) errno(%d)\n",
+ priority, rc, errno);
+ }
}
return rc;
}
+
+
+/*! Count UDP binds compatible with remote */
+int ns2_ip_count_bind(struct gprs_ns2_inst *nsi, struct osmo_sockaddr *remote)
+{
+ struct gprs_ns2_vc_bind *bind;
+ const struct osmo_sockaddr *sa;
+ int count = 0;
+
+ llist_for_each_entry(bind, &nsi->binding, list) {
+ if (!gprs_ns2_is_ip_bind(bind))
+ continue;
+
+ sa = gprs_ns2_ip_bind_sockaddr(bind);
+ if (!sa)
+ continue;
+
+ if (sa->u.sa.sa_family == remote->u.sa.sa_family)
+ count++;
+ }
+
+ return count;
+}
+
+/* return the matching bind by index */
+struct gprs_ns2_vc_bind *ns2_ip_get_bind_by_index(struct gprs_ns2_inst *nsi,
+ struct osmo_sockaddr *remote,
+ int index)
+{
+ struct gprs_ns2_vc_bind *bind;
+ const struct osmo_sockaddr *sa;
+ int i = 0;
+
+ llist_for_each_entry(bind, &nsi->binding, list) {
+ if (!gprs_ns2_is_ip_bind(bind))
+ continue;
+
+ sa = gprs_ns2_ip_bind_sockaddr(bind);
+ if (!sa)
+ continue;
+
+ if (sa->u.sa.sa_family == remote->u.sa.sa_family) {
+ if (index == i)
+ return bind;
+ i++;
+ }
+ }
+
+ return NULL;
+}
+
+void ns2_ip_set_txqueue_max_length(struct gprs_ns2_vc_bind *bind, unsigned int max_length)
+{
+ struct priv_bind *priv = bind->priv;
+ OSMO_ASSERT(gprs_ns2_is_ip_bind(bind));
+
+ osmo_iofd_set_txqueue_max_length(priv->iofd, max_length);
+}
+
+/*! set the signalling and data weight for this bind
+ * \param[in] bind
+ * \param[in] signalling the signalling weight
+ * \param[in] data the data weight
+ */
+void gprs_ns2_ip_bind_set_sns_weight(struct gprs_ns2_vc_bind *bind, uint8_t signalling, uint8_t data)
+{
+ OSMO_ASSERT(gprs_ns2_is_ip_bind(bind));
+ bind->sns_sig_weight = signalling;
+ bind->sns_data_weight = data;
+ ns2_sns_update_weights(bind);
+}
diff --git a/src/gb/gprs_ns2_vc_fsm.c b/src/gb/gprs_ns2_vc_fsm.c
index 08b6b2d0..9cd83c4f 100644
--- a/src/gb/gprs_ns2_vc_fsm.c
+++ b/src/gb/gprs_ns2_vc_fsm.c
@@ -49,23 +49,24 @@
#define S(x) (1 << (x))
-#define DNS 10
-
struct gprs_ns2_vc_priv {
struct gprs_ns2_vc *nsvc;
/* how often the timer was triggered */
int N;
/* The initiator is responsible to UNBLOCK the VC. The BSS is usually the initiator.
* It can change during runtime. The side which blocks an unblocked side.*/
+ bool initiator;
bool initiate_block;
bool initiate_reset;
+ /* if unitdata is forwarded to the user */
+ bool accept_unitdata;
/* the alive counter is present in all states */
struct {
struct osmo_timer_list timer;
enum ns2_timeout mode;
int N;
- struct timeval timer_started;
+ struct timespec timer_started;
} alive;
};
@@ -76,13 +77,13 @@ struct gprs_ns2_vc_priv {
* - UNCONFIGURED -> RESET -> BLOCK -> UNBLOCKED
*
* Without RESET/BLOCK, the state should follow:
- * - UNCONFIGURED -> ALIVE -> UNBLOCKED
+ * - UNCONFIGURED -> RECOVERY -> UNBLOCKED
*
* The UNBLOCKED and TEST states are used to send ALIVE PDU using the timeout Tns-test and Tns-alive.
* UNBLOCKED -> TEST: on expire of Tns-Test, send Alive PDU.
* TEST -> UNBLOCKED: on receive of Alive_Ack PDU, go into UNBLOCKED.
*
- * The ALIVE state is used as intermediate, because a VC is only valid if it received an Alive ACK when
+ * The RECOVERY state is used as intermediate, because a VC is only valid if it received an Alive ACK when
* not using RESET/BLOCK procedure.
*/
@@ -92,41 +93,49 @@ enum gprs_ns2_vc_state {
GPRS_NS2_ST_BLOCKED,
GPRS_NS2_ST_UNBLOCKED, /* allows sending NS_UNITDATA */
- GPRS_NS2_ST_ALIVE, /* only used when not using RESET/BLOCK procedure */
+ GPRS_NS2_ST_RECOVERING, /* only used when not using RESET/BLOCK procedure */
};
enum gprs_ns2_vc_event {
- GPRS_NS2_EV_START,
+ GPRS_NS2_EV_REQ_START,
/* received messages */
- GPRS_NS2_EV_RESET,
- GPRS_NS2_EV_RESET_ACK,
- GPRS_NS2_EV_UNBLOCK,
- GPRS_NS2_EV_UNBLOCK_ACK,
- GPRS_NS2_EV_BLOCK,
- GPRS_NS2_EV_BLOCK_ACK,
- GPRS_NS2_EV_ALIVE,
- GPRS_NS2_EV_ALIVE_ACK,
- GPRS_NS2_EV_STATUS,
-
- GPRS_NS2_EV_UNITDATA,
-
- GPRS_NS2_EV_FORCE_UNCONFIGURED,
+ GPRS_NS2_EV_RX_RESET,
+ GPRS_NS2_EV_RX_RESET_ACK,
+ GPRS_NS2_EV_RX_UNBLOCK,
+ GPRS_NS2_EV_RX_UNBLOCK_ACK,
+ GPRS_NS2_EV_RX_BLOCK,
+ GPRS_NS2_EV_RX_BLOCK_ACK,
+ GPRS_NS2_EV_RX_ALIVE,
+ GPRS_NS2_EV_RX_ALIVE_ACK,
+ GPRS_NS2_EV_RX_STATUS,
+
+ GPRS_NS2_EV_RX_UNITDATA,
+
+ GPRS_NS2_EV_REQ_FORCE_UNCONFIGURED, /* called via vty for tests */
+ GPRS_NS2_EV_REQ_OM_RESET, /* vty cmd: reset */
+ GPRS_NS2_EV_REQ_OM_BLOCK, /* vty cmd: block */
+ GPRS_NS2_EV_REQ_OM_UNBLOCK, /* vty cmd: unblock*/
+ GPRS_NS2_EV_RX_BLOCK_FOREIGN, /* received a BLOCK over another NSVC */
};
-static const struct value_string gprs_ns2_vc_event_names[] = {
- { GPRS_NS2_EV_START, "START" },
- { GPRS_NS2_EV_RESET, "RESET" },
- { GPRS_NS2_EV_RESET_ACK, "RESET_ACK" },
- { GPRS_NS2_EV_UNBLOCK, "UNBLOCK" },
- { GPRS_NS2_EV_UNBLOCK_ACK, "UNBLOCK_ACK" },
- { GPRS_NS2_EV_BLOCK, "BLOCK" },
- { GPRS_NS2_EV_BLOCK_ACK, "BLOCK_ACK" },
- { GPRS_NS2_EV_ALIVE, "ALIVE" },
- { GPRS_NS2_EV_ALIVE_ACK, "ALIVE_ACK" },
- { GPRS_NS2_EV_STATUS, "STATUS" },
- { GPRS_NS2_EV_UNITDATA, "UNITDATA" },
- {GPRS_NS2_EV_FORCE_UNCONFIGURED, "FORCE_UNCONFIGURED"},
+static const struct value_string ns2_vc_event_names[] = {
+ { GPRS_NS2_EV_REQ_START, "REQ-START" },
+ { GPRS_NS2_EV_RX_RESET, "RX-RESET" },
+ { GPRS_NS2_EV_RX_RESET_ACK, "RX-RESET_ACK" },
+ { GPRS_NS2_EV_RX_UNBLOCK, "RX-UNBLOCK" },
+ { GPRS_NS2_EV_RX_UNBLOCK_ACK, "RX-UNBLOCK_ACK" },
+ { GPRS_NS2_EV_RX_BLOCK, "RX-BLOCK" },
+ { GPRS_NS2_EV_RX_BLOCK_FOREIGN, "RX-BLOCK_FOREIGN" },
+ { GPRS_NS2_EV_RX_BLOCK_ACK, "RX-BLOCK_ACK" },
+ { GPRS_NS2_EV_RX_ALIVE, "RX-ALIVE" },
+ { GPRS_NS2_EV_RX_ALIVE_ACK, "RX-ALIVE_ACK" },
+ { GPRS_NS2_EV_RX_STATUS, "RX-STATUS" },
+ { GPRS_NS2_EV_RX_UNITDATA, "RX-UNITDATA" },
+ { GPRS_NS2_EV_REQ_FORCE_UNCONFIGURED, "REQ-FORCE_UNCONFIGURED" },
+ { GPRS_NS2_EV_REQ_OM_RESET, "REQ-O&M-RESET"},
+ { GPRS_NS2_EV_REQ_OM_BLOCK, "REQ-O&M-BLOCK"},
+ { GPRS_NS2_EV_REQ_OM_UNBLOCK, "REQ-O&M-UNBLOCK"},
{ 0, NULL }
};
@@ -136,35 +145,59 @@ static inline struct gprs_ns2_inst *ns_inst_from_fi(struct osmo_fsm_inst *fi)
return priv->nsvc->nse->nsi;
}
-static void start_test_procedure(struct gprs_ns2_vc_priv *priv)
+/* Start the NS-TEST procedure, either with transmitting a tx_alive,
+ * (start_tx_alive==true) or with starting tns-test */
+static void start_test_procedure(struct osmo_fsm_inst *fi, bool start_tx_alive)
{
+ struct gprs_ns2_vc_priv *priv = fi->priv;
struct gprs_ns2_inst *nsi = priv->nsvc->nse->nsi;
+ unsigned int tout_idx;
- if (osmo_timer_pending(&priv->alive.timer))
- return;
+ if (osmo_timer_pending(&priv->alive.timer)) {
+ if (start_tx_alive) {
+ if (priv->alive.mode == NS_TOUT_TNS_ALIVE)
+ return;
+ } else {
+ if (priv->alive.mode == NS_TOUT_TNS_TEST)
+ return;
+ }
+ }
- priv->alive.mode = NS_TOUT_TNS_ALIVE;
priv->alive.N = 0;
- osmo_gettimeofday(&priv->alive.timer_started, NULL);
- ns2_tx_alive(priv->nsvc);
- osmo_timer_schedule(&priv->alive.timer, nsi->timeout[NS_TOUT_TNS_ALIVE], 0);
+ if (start_tx_alive) {
+ priv->alive.mode = NS_TOUT_TNS_ALIVE;
+ osmo_clock_gettime(CLOCK_MONOTONIC, &priv->alive.timer_started);
+ ns2_tx_alive(priv->nsvc);
+ tout_idx = NS_TOUT_TNS_ALIVE;
+ } else {
+ priv->alive.mode = NS_TOUT_TNS_TEST;
+ tout_idx = NS_TOUT_TNS_TEST;
+ }
+ LOGPFSML(fi, LOGL_DEBUG, "Starting Tns-%s of %u seconds\n",
+ tout_idx == NS_TOUT_TNS_ALIVE ? "alive" : "test", nsi->timeout[tout_idx]);
+ osmo_timer_schedule(&priv->alive.timer, nsi->timeout[tout_idx], 0);
}
static void stop_test_procedure(struct gprs_ns2_vc_priv *priv)
{
+ osmo_stat_item_set(osmo_stat_item_group_get_item(priv->nsvc->statg, NS_STAT_ALIVE_DELAY), 0);
osmo_timer_del(&priv->alive.timer);
}
+/* how many milliseconds have expired since the last alive timer start? */
static int alive_timer_elapsed_ms(struct gprs_ns2_vc_priv *priv)
{
- struct timeval now, elapsed;
- osmo_gettimeofday(&now, NULL);
- timersub(&now, &priv->alive.timer_started, &elapsed);
+ struct timespec now, elapsed;
+
+ if (osmo_clock_gettime(CLOCK_MONOTONIC, &now) != 0)
+ return 0;
- return 1000 * elapsed.tv_sec + elapsed.tv_usec / 1000;
+ timespecsub(&now, &priv->alive.timer_started, &elapsed);
+ return elapsed.tv_sec * 1000 + (elapsed.tv_nsec / 1000000);
}
+/* we just received a NS-ALIVE-ACK; re-schedule after Tns-test */
static void recv_test_procedure(struct osmo_fsm_inst *fi)
{
struct gprs_ns2_vc_priv *priv = fi->priv;
@@ -177,7 +210,7 @@ static void recv_test_procedure(struct osmo_fsm_inst *fi)
priv->alive.mode = NS_TOUT_TNS_TEST;
osmo_timer_schedule(&priv->alive.timer, nsi->timeout[NS_TOUT_TNS_TEST], 0);
- osmo_stat_item_set(nsvc->statg->items[NS_STAT_ALIVE_DELAY],
+ osmo_stat_item_set(osmo_stat_item_group_get_item(nsvc->statg, NS_STAT_ALIVE_DELAY),
alive_timer_elapsed_ms(priv));
}
@@ -191,10 +224,13 @@ static void alive_timeout_handler(void *data)
switch (priv->alive.mode) {
case NS_TOUT_TNS_TEST:
priv->alive.mode = NS_TOUT_TNS_ALIVE;
+ priv->alive.N = 0;
+ osmo_clock_gettime(CLOCK_MONOTONIC, &priv->alive.timer_started);
ns2_tx_alive(priv->nsvc);
osmo_timer_schedule(&priv->alive.timer, nsi->timeout[NS_TOUT_TNS_ALIVE], 0);
break;
case NS_TOUT_TNS_ALIVE:
+ RATE_CTR_INC_NS(priv->nsvc, NS_CTR_LOST_ALIVE);
priv->alive.N++;
if (priv->alive.N <= nsi->timeout[NS_TOUT_TNS_ALIVE_RETRIES]) {
@@ -203,10 +239,10 @@ static void alive_timeout_handler(void *data)
osmo_timer_schedule(&priv->alive.timer, nsi->timeout[NS_TOUT_TNS_ALIVE], 0);
} else {
/* lost connection */
- if (priv->nsvc->mode == NS2_VC_MODE_BLOCKRESET) {
+ if (priv->nsvc->mode == GPRS_NS2_VC_MODE_BLOCKRESET) {
osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_RESET, nsi->timeout[NS_TOUT_TNS_RESET], 0);
} else {
- osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_ALIVE, nsi->timeout[NS_TOUT_TNS_ALIVE], 0);
+ osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_RECOVERING, nsi->timeout[NS_TOUT_TNS_ALIVE], 0);
}
}
break;
@@ -215,22 +251,39 @@ static void alive_timeout_handler(void *data)
}
}
-static void gprs_ns2_st_unconfigured(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+
+static void ns2_st_unconfigured_onenter(struct osmo_fsm_inst *fi, uint32_t old_state)
+{
+ struct gprs_ns2_vc_priv *priv = fi->priv;
+
+ stop_test_procedure(fi->priv);
+ ns2_nse_notify_unblocked(priv->nsvc, false);
+}
+
+static void ns2_st_unconfigured(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct gprs_ns2_vc_priv *priv = fi->priv;
struct gprs_ns2_inst *nsi = priv->nsvc->nse->nsi;
+ priv->initiate_reset = priv->initiate_block = priv->initiator;
+ priv->nsvc->om_blocked = false;
+
switch (event) {
- case GPRS_NS2_EV_START:
+ case GPRS_NS2_EV_REQ_START:
switch (priv->nsvc->mode) {
- case NS2_VC_MODE_ALIVE:
- osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_ALIVE, nsi->timeout[NS_TOUT_TNS_ALIVE], NS_TOUT_TNS_ALIVE);
+ case GPRS_NS2_VC_MODE_ALIVE:
+ if (priv->nsvc->nse->dialect == GPRS_NS2_DIALECT_SNS) {
+ /* In IP-SNS, the NS-VC are assumed initially alive, until the alive
+ * procedure should fail at some future point */
+ osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_UNBLOCKED, 0, 0);
+ } else {
+ osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_RECOVERING, nsi->timeout[NS_TOUT_TNS_ALIVE], NS_TOUT_TNS_ALIVE);
+ }
break;
- case NS2_VC_MODE_BLOCKRESET:
+ case GPRS_NS2_VC_MODE_BLOCKRESET:
osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_RESET, nsi->timeout[NS_TOUT_TNS_RESET], NS_TOUT_TNS_RESET);
break;
}
-
break;
default:
OSMO_ASSERT(0);
@@ -238,13 +291,14 @@ static void gprs_ns2_st_unconfigured(struct osmo_fsm_inst *fi, uint32_t event, v
}
-static void gprs_ns2_st_reset_onenter(struct osmo_fsm_inst *fi, uint32_t old_state)
+static void ns2_st_reset_onenter(struct osmo_fsm_inst *fi, uint32_t old_state)
{
struct gprs_ns2_vc_priv *priv = fi->priv;
if (old_state != GPRS_NS2_ST_RESET)
priv->N = 0;
+ priv->accept_unitdata = false;
if (priv->initiate_reset)
ns2_tx_reset(priv->nsvc, NS_CAUSE_OM_INTERVENTION);
@@ -252,14 +306,17 @@ static void gprs_ns2_st_reset_onenter(struct osmo_fsm_inst *fi, uint32_t old_sta
ns2_nse_notify_unblocked(priv->nsvc, false);
}
-static void gprs_ns2_st_reset(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+static void ns2_st_reset(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct gprs_ns2_inst *nsi = ns_inst_from_fi(fi);
struct gprs_ns2_vc_priv *priv = fi->priv;
if (priv->initiate_reset) {
switch (event) {
- case GPRS_NS2_EV_RESET_ACK:
+ case GPRS_NS2_EV_RX_RESET:
+ ns2_tx_reset_ack(priv->nsvc);
+ /* fall-through */
+ case GPRS_NS2_EV_RX_RESET_ACK:
osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_BLOCKED,
nsi->timeout[NS_TOUT_TNS_BLOCK], NS_TOUT_TNS_BLOCK);
break;
@@ -267,7 +324,7 @@ static void gprs_ns2_st_reset(struct osmo_fsm_inst *fi, uint32_t event, void *da
} else {
/* we are on the receiving end */
switch (event) {
- case GPRS_NS2_EV_RESET:
+ case GPRS_NS2_EV_RX_RESET:
ns2_tx_reset_ack(priv->nsvc);
osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_BLOCKED,
0, 0);
@@ -276,33 +333,69 @@ static void gprs_ns2_st_reset(struct osmo_fsm_inst *fi, uint32_t event, void *da
}
}
-static void gprs_ns2_st_blocked_onenter(struct osmo_fsm_inst *fi, uint32_t old_state)
+static void ns2_st_blocked_onenter(struct osmo_fsm_inst *fi, uint32_t old_state)
{
struct gprs_ns2_vc_priv *priv = fi->priv;
- if (old_state != GPRS_NS2_ST_BLOCKED)
+ if (old_state != GPRS_NS2_ST_BLOCKED) {
priv->N = 0;
+ RATE_CTR_INC_NS(priv->nsvc, NS_CTR_BLOCKED);
+ }
- if (priv->initiate_block)
+ ns2_nse_notify_unblocked(priv->nsvc, false);
+ if (priv->nsvc->om_blocked) {
+ /* we are already blocked after a RESET */
+ if (old_state == GPRS_NS2_ST_RESET) {
+ osmo_timer_del(&fi->timer);
+ } else {
+ ns2_tx_block(priv->nsvc, NS_CAUSE_OM_INTERVENTION, NULL);
+ }
+ } else if (priv->initiate_block) {
ns2_tx_unblock(priv->nsvc);
+ }
- start_test_procedure(priv);
+ start_test_procedure(fi, true);
}
-static void gprs_ns2_st_blocked(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+static void ns2_st_blocked(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct gprs_ns2_vc_priv *priv = fi->priv;
- if (priv->initiate_block) {
+ if (priv->nsvc->om_blocked) {
switch (event) {
- case GPRS_NS2_EV_BLOCK:
+ case GPRS_NS2_EV_RX_BLOCK_ACK:
+ priv->accept_unitdata = false;
+ osmo_timer_del(&fi->timer);
+ break;
+ case GPRS_NS2_EV_RX_BLOCK:
+ ns2_tx_block_ack(priv->nsvc, NULL);
+ /* fall through */
+ case GPRS_NS2_EV_RX_BLOCK_FOREIGN:
+ /* the BLOCK ACK for foreign BLOCK PDUs (rx over another nsvc) will be sent
+ * from the receiving nsvc */
+ priv->accept_unitdata = false;
+ osmo_timer_del(&fi->timer);
+ break;
+ case GPRS_NS2_EV_RX_UNBLOCK:
+ priv->accept_unitdata = false;
+ ns2_tx_block(priv->nsvc, NS_CAUSE_OM_INTERVENTION, NULL);
+ osmo_timer_add(&fi->timer);
+ break;
+ }
+ } else if (priv->initiate_block) {
+ switch (event) {
+ case GPRS_NS2_EV_RX_BLOCK_FOREIGN:
+ /* the block ack will be sent by the rx NSVC */
+ break;
+ case GPRS_NS2_EV_RX_BLOCK:
/* TODO: BLOCK is a UNBLOCK_NACK */
- ns2_tx_block_ack(priv->nsvc);
+ ns2_tx_block_ack(priv->nsvc, NULL);
break;
- case GPRS_NS2_EV_UNBLOCK:
+ case GPRS_NS2_EV_RX_UNBLOCK:
ns2_tx_unblock_ack(priv->nsvc);
/* fall through */
- case GPRS_NS2_EV_UNBLOCK_ACK:
+ case GPRS_NS2_EV_RX_UNBLOCK_ACK:
+ priv->accept_unitdata = true;
osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_UNBLOCKED,
0, NS_TOUT_TNS_TEST);
break;
@@ -310,7 +403,13 @@ static void gprs_ns2_st_blocked(struct osmo_fsm_inst *fi, uint32_t event, void *
} else {
/* we are on the receiving end. The initiator who sent RESET is responsible to UNBLOCK! */
switch (event) {
- case GPRS_NS2_EV_UNBLOCK:
+ case GPRS_NS2_EV_RX_BLOCK_FOREIGN:
+ /* the block ack will be sent by the rx NSVC */
+ break;
+ case GPRS_NS2_EV_RX_BLOCK:
+ ns2_tx_block_ack(priv->nsvc, NULL);
+ break;
+ case GPRS_NS2_EV_RX_UNBLOCK:
ns2_tx_unblock_ack(priv->nsvc);
osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_UNBLOCKED,
0, 0);
@@ -319,40 +418,60 @@ static void gprs_ns2_st_blocked(struct osmo_fsm_inst *fi, uint32_t event, void *
}
}
-static void gprs_ns2_st_unblocked_on_enter(struct osmo_fsm_inst *fi, uint32_t old_state)
+static void ns2_st_unblocked_on_enter(struct osmo_fsm_inst *fi, uint32_t old_state)
{
struct gprs_ns2_vc_priv *priv = fi->priv;
struct gprs_ns2_vc *nsvc = priv->nsvc;
struct gprs_ns2_nse *nse = nsvc->nse;
+ if (old_state != GPRS_NS2_ST_UNBLOCKED) {
+ RATE_CTR_INC_NS(nsvc, NS_CTR_UNBLOCKED);
+ osmo_clock_gettime(CLOCK_MONOTONIC, &nsvc->ts_alive_change);
+ }
+
+ priv->accept_unitdata = true;
ns2_nse_notify_unblocked(nsvc, true);
- ns2_prim_status_ind(nse, nsvc, 0, NS_AFF_CAUSE_VC_RECOVERY);
+ ns2_prim_status_ind(nse, nsvc, 0, GPRS_NS2_AFF_CAUSE_VC_RECOVERY);
+
+ /* the closest interpretation of the spec would start Tns-test here first,
+ * and only send a NS-ALIVE after Tns-test has expired (i.e. setting the
+ * second argument to 'false'. However, being quick in detecting unavailability
+ * of a NS-VC seems like a good idea */
+ start_test_procedure(fi, true);
}
-static void gprs_ns2_st_unblocked(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+static void ns2_st_unblocked(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct gprs_ns2_vc_priv *priv = fi->priv;
switch (event) {
- case GPRS_NS2_EV_BLOCK:
+ case GPRS_NS2_EV_RX_UNBLOCK:
+ ns2_tx_unblock_ack(priv->nsvc);
+ break;
+ case GPRS_NS2_EV_RX_BLOCK:
+ ns2_tx_block_ack(priv->nsvc, NULL);
+ /* fall through */
+ case GPRS_NS2_EV_RX_BLOCK_FOREIGN:
+ /* the BLOCK ACK for foreign BLOCK PDUs (rx over another nsvc) will be sent
+ * from the receiving nsvc */
priv->initiate_block = false;
- ns2_tx_block_ack(priv->nsvc);
+ priv->accept_unitdata = false;
osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_BLOCKED,
0, 2);
break;
}
}
-static void gprs_ns2_st_alive(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+static void ns2_st_alive(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
switch (event) {
- case GPRS_NS2_EV_ALIVE_ACK:
+ case GPRS_NS2_EV_RX_ALIVE_ACK:
osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_UNBLOCKED, 0, 0);
break;
}
}
-static void gprs_ns2_st_alive_onenter(struct osmo_fsm_inst *fi, uint32_t old_state)
+static void ns2_st_alive_onenter(struct osmo_fsm_inst *fi, uint32_t old_state)
{
struct gprs_ns2_vc_priv *priv = fi->priv;
struct gprs_ns2_inst *nsi = ns_inst_from_fi(fi);
@@ -360,77 +479,76 @@ static void gprs_ns2_st_alive_onenter(struct osmo_fsm_inst *fi, uint32_t old_sta
priv->alive.mode = NS_TOUT_TNS_TEST;
osmo_timer_schedule(&priv->alive.timer, nsi->timeout[NS_TOUT_TNS_TEST], 0);
- if (old_state != GPRS_NS2_ST_ALIVE)
+ if (old_state != GPRS_NS2_ST_RECOVERING)
priv->N = 0;
- ns2_tx_alive(priv->nsvc);
+ start_test_procedure(fi, true);
ns2_nse_notify_unblocked(priv->nsvc, false);
}
-static void gprs_ns2_st_alive_onleave(struct osmo_fsm_inst *fi, uint32_t next_state)
-{
- start_test_procedure(fi->priv);
-}
-
-static const struct osmo_fsm_state gprs_ns2_vc_states[] = {
+static const struct osmo_fsm_state ns2_vc_states[] = {
[GPRS_NS2_ST_UNCONFIGURED] = {
- .in_event_mask = S(GPRS_NS2_EV_START),
- .out_state_mask = S(GPRS_NS2_ST_RESET) | S(GPRS_NS2_ST_ALIVE),
+ .in_event_mask = S(GPRS_NS2_EV_REQ_START),
+ .out_state_mask = S(GPRS_NS2_ST_RESET) |
+ S(GPRS_NS2_ST_RECOVERING) |
+ S(GPRS_NS2_ST_UNBLOCKED),
.name = "UNCONFIGURED",
- .action = gprs_ns2_st_unconfigured,
+ .action = ns2_st_unconfigured,
+ .onenter = ns2_st_unconfigured_onenter,
},
[GPRS_NS2_ST_RESET] = {
- .in_event_mask = S(GPRS_NS2_EV_RESET_ACK) | S(GPRS_NS2_EV_RESET),
+ .in_event_mask = S(GPRS_NS2_EV_RX_RESET_ACK) | S(GPRS_NS2_EV_RX_RESET),
.out_state_mask = S(GPRS_NS2_ST_RESET) |
S(GPRS_NS2_ST_BLOCKED) |
S(GPRS_NS2_ST_UNCONFIGURED),
.name = "RESET",
- .action = gprs_ns2_st_reset,
- .onenter = gprs_ns2_st_reset_onenter,
+ .action = ns2_st_reset,
+ .onenter = ns2_st_reset_onenter,
},
[GPRS_NS2_ST_BLOCKED] = {
- .in_event_mask = S(GPRS_NS2_EV_BLOCK) | S(GPRS_NS2_EV_BLOCK_ACK) |
- S(GPRS_NS2_EV_UNBLOCK) | S(GPRS_NS2_EV_UNBLOCK_ACK),
+ .in_event_mask = S(GPRS_NS2_EV_RX_BLOCK) | S(GPRS_NS2_EV_RX_BLOCK_ACK) |
+ S(GPRS_NS2_EV_RX_UNBLOCK) | S(GPRS_NS2_EV_RX_UNBLOCK_ACK) |
+ S(GPRS_NS2_EV_RX_BLOCK_FOREIGN),
.out_state_mask = S(GPRS_NS2_ST_RESET) |
S(GPRS_NS2_ST_UNBLOCKED) |
S(GPRS_NS2_ST_BLOCKED) |
S(GPRS_NS2_ST_UNCONFIGURED),
.name = "BLOCKED",
- .action = gprs_ns2_st_blocked,
- .onenter = gprs_ns2_st_blocked_onenter,
+ .action = ns2_st_blocked,
+ .onenter = ns2_st_blocked_onenter,
},
[GPRS_NS2_ST_UNBLOCKED] = {
- .in_event_mask = S(GPRS_NS2_EV_BLOCK),
- .out_state_mask = S(GPRS_NS2_ST_RESET) | S(GPRS_NS2_ST_ALIVE) |
+ .in_event_mask = S(GPRS_NS2_EV_RX_BLOCK) | S(GPRS_NS2_EV_RX_UNBLOCK_ACK) |
+ S(GPRS_NS2_EV_RX_UNBLOCK) | S(GPRS_NS2_EV_RX_BLOCK_FOREIGN),
+ .out_state_mask = S(GPRS_NS2_ST_RESET) | S(GPRS_NS2_ST_RECOVERING) |
S(GPRS_NS2_ST_BLOCKED) |
S(GPRS_NS2_ST_UNCONFIGURED),
.name = "UNBLOCKED",
- .action = gprs_ns2_st_unblocked,
- .onenter = gprs_ns2_st_unblocked_on_enter,
+ .action = ns2_st_unblocked,
+ .onenter = ns2_st_unblocked_on_enter,
},
- /* ST_ALIVE is only used on VC without RESET/BLOCK */
- [GPRS_NS2_ST_ALIVE] = {
- .in_event_mask = S(GPRS_NS2_EV_ALIVE_ACK),
- .out_state_mask = S(GPRS_NS2_ST_RESET) |
+ /* ST_RECOVERING is only used on VC without RESET/BLOCK */
+ [GPRS_NS2_ST_RECOVERING] = {
+ .in_event_mask = S(GPRS_NS2_EV_RX_ALIVE_ACK),
+ .out_state_mask = S(GPRS_NS2_ST_RECOVERING) |
S(GPRS_NS2_ST_UNBLOCKED) |
S(GPRS_NS2_ST_UNCONFIGURED),
- .name = "ALIVE",
- .action = gprs_ns2_st_alive,
- .onenter = gprs_ns2_st_alive_onenter,
- .onleave = gprs_ns2_st_alive_onleave,
+ .name = "RECOVERING",
+ .action = ns2_st_alive,
+ .onenter = ns2_st_alive_onenter,
},
};
-static int gprs_ns2_vc_fsm_timer_cb(struct osmo_fsm_inst *fi)
+static int ns2_vc_fsm_timer_cb(struct osmo_fsm_inst *fi)
{
struct gprs_ns2_inst *nsi = ns_inst_from_fi(fi);
struct gprs_ns2_vc_priv *priv = fi->priv;
- /* PCU timeouts */
switch (fi->state) {
case GPRS_NS2_ST_RESET:
if (priv->initiate_reset) {
+ RATE_CTR_INC_NS(priv->nsvc, NS_CTR_LOST_RESET);
priv->N++;
if (priv->N <= nsi->timeout[NS_TOUT_TNS_RESET_RETRIES]) {
osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_RESET, nsi->timeout[NS_TOUT_TNS_RESET], 0);
@@ -443,21 +561,30 @@ static int gprs_ns2_vc_fsm_timer_cb(struct osmo_fsm_inst *fi)
case GPRS_NS2_ST_BLOCKED:
if (priv->initiate_block) {
priv->N++;
- if (priv->N <= nsi->timeout[NS_TOUT_TNS_BLOCK_RETRIES]) {
- osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_BLOCKED, nsi->timeout[NS_TOUT_TNS_BLOCK], 0);
+ if (priv->nsvc->om_blocked) {
+ if (priv->N <= nsi->timeout[NS_TOUT_TNS_BLOCK_RETRIES]) {
+ osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_BLOCKED, nsi->timeout[NS_TOUT_TNS_BLOCK], 0);
+ } else {
+ /* 7.2 stop accepting data when BLOCK PDU not responded */
+ priv->accept_unitdata = false;
+ }
} else {
- osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_RESET, nsi->timeout[NS_TOUT_TNS_RESET], 0);
+ if (priv->N <= nsi->timeout[NS_TOUT_TNS_BLOCK_RETRIES]) {
+ osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_BLOCKED, nsi->timeout[NS_TOUT_TNS_BLOCK], 0);
+ } else {
+ osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_RESET, nsi->timeout[NS_TOUT_TNS_RESET], 0);
+ }
}
}
break;
- case GPRS_NS2_ST_ALIVE:
+ case GPRS_NS2_ST_RECOVERING:
if (priv->initiate_reset) {
priv->N++;
if (priv->N <= nsi->timeout[NS_TOUT_TNS_ALIVE_RETRIES]) {
- osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_ALIVE, 0, 0);
+ osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_RECOVERING, 0, 0);
} else {
priv->N = 0;
- osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_ALIVE, 0, 0);
+ osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_RECOVERING, 0, 0);
}
break;
}
@@ -466,7 +593,7 @@ static int gprs_ns2_vc_fsm_timer_cb(struct osmo_fsm_inst *fi)
return 0;
}
-static void gprs_ns2_recv_unitdata(struct osmo_fsm_inst *fi,
+static void ns2_recv_unitdata(struct osmo_fsm_inst *fi,
struct msgb *msg)
{
struct gprs_ns2_vc_priv *priv = fi->priv;
@@ -493,24 +620,35 @@ static void gprs_ns2_recv_unitdata(struct osmo_fsm_inst *fi,
/* 10.3.9 NS SDU Control Bits */
if (nsh->data[0] & 0x1)
- nsp.u.unitdata.change = NS_ENDPOINT_REQUEST_CHANGE;
+ nsp.u.unitdata.change = GPRS_NS2_ENDPOINT_REQUEST_CHANGE;
- osmo_prim_init(&nsp.oph, SAP_NS, PRIM_NS_UNIT_DATA,
+ osmo_prim_init(&nsp.oph, SAP_NS, GPRS_NS2_PRIM_UNIT_DATA,
PRIM_OP_INDICATION, msg);
nsi->cb(&nsp.oph, nsi->cb_data);
}
-static void gprs_ns2_vc_fsm_allstate_action(struct osmo_fsm_inst *fi,
+static void ns2_vc_fsm_allstate_action(struct osmo_fsm_inst *fi,
uint32_t event,
void *data)
{
struct gprs_ns2_vc_priv *priv = fi->priv;
struct gprs_ns2_inst *nsi = ns_inst_from_fi(fi);
+ struct tlv_parsed *tp;
struct msgb *msg = data;
+ uint8_t cause;
switch (event) {
- case GPRS_NS2_EV_RESET:
- if (priv->nsvc->mode != NS2_VC_MODE_BLOCKRESET)
+ case GPRS_NS2_EV_REQ_OM_RESET:
+ if (priv->nsvc->mode != GPRS_NS2_VC_MODE_BLOCKRESET)
+ break;
+ /* move the FSM into reset */
+ if (fi->state != GPRS_NS2_ST_RESET) {
+ priv->initiate_reset = true;
+ osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_RESET, nsi->timeout[NS_TOUT_TNS_RESET], NS_TOUT_TNS_RESET);
+ }
+ break;
+ case GPRS_NS2_EV_RX_RESET:
+ if (priv->nsvc->mode != GPRS_NS2_VC_MODE_BLOCKRESET)
break;
/* move the FSM into reset */
@@ -519,9 +657,9 @@ static void gprs_ns2_vc_fsm_allstate_action(struct osmo_fsm_inst *fi,
osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_RESET, nsi->timeout[NS_TOUT_TNS_RESET], NS_TOUT_TNS_RESET);
}
/* pass the event down into FSM action */
- gprs_ns2_st_reset(fi, event, data);
+ ns2_st_reset(fi, event, data);
break;
- case GPRS_NS2_EV_ALIVE:
+ case GPRS_NS2_EV_RX_ALIVE:
switch (fi->state) {
case GPRS_NS2_ST_UNCONFIGURED:
case GPRS_NS2_ST_RESET:
@@ -531,50 +669,88 @@ static void gprs_ns2_vc_fsm_allstate_action(struct osmo_fsm_inst *fi,
ns2_tx_alive_ack(priv->nsvc);
}
break;
- case GPRS_NS2_EV_ALIVE_ACK:
+ case GPRS_NS2_EV_RX_ALIVE_ACK:
/* for VCs without RESET/BLOCK/UNBLOCK, the connections comes after ALIVE_ACK unblocked */
- if (fi->state == GPRS_NS2_ST_ALIVE)
- gprs_ns2_st_alive(fi, event, data);
+ if (fi->state == GPRS_NS2_ST_RECOVERING)
+ ns2_st_alive(fi, event, data);
else
recv_test_procedure(fi);
break;
- case GPRS_NS2_EV_UNITDATA:
+ case GPRS_NS2_EV_RX_UNITDATA:
/* UNITDATA has to handle the release of msg.
* If send upwards (gprs_ns2_recv_unitdata) it must NOT free
* the msg, the upper layer has to do it.
* Otherwise the msg must be freed.
*/
+
+ LOG_NS_DATA(priv->nsvc, "Rx", NS_PDUT_UNITDATA, LOGL_INFO, "\n");
switch (fi->state) {
case GPRS_NS2_ST_BLOCKED:
/* 7.2.1: the BLOCKED_ACK might be lost */
- if (priv->initiate_block) {
- gprs_ns2_recv_unitdata(fi, msg);
+ if (priv->accept_unitdata) {
+ ns2_recv_unitdata(fi, msg);
return;
}
ns2_tx_status(priv->nsvc,
NS_CAUSE_NSVC_BLOCKED,
- 0, msg);
+ 0, msg, NULL);
break;
/* ALIVE can receive UNITDATA if the ALIVE_ACK is lost */
- case GPRS_NS2_ST_ALIVE:
+ case GPRS_NS2_ST_RECOVERING:
case GPRS_NS2_ST_UNBLOCKED:
- gprs_ns2_recv_unitdata(fi, msg);
+ ns2_recv_unitdata(fi, msg);
return;
}
msgb_free(msg);
break;
- case GPRS_NS2_EV_FORCE_UNCONFIGURED:
- /* Force the NSVC back to its initial state */
- osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_UNCONFIGURED, 0, 0);
- osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_START, NULL);
- return;
+ case GPRS_NS2_EV_REQ_FORCE_UNCONFIGURED:
+ if (fi->state != GPRS_NS2_ST_UNCONFIGURED) {
+ /* Force the NSVC back to its initial state */
+ osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_UNCONFIGURED, 0, 0);
+ return;
+ }
+ break;
+ case GPRS_NS2_EV_REQ_OM_BLOCK:
+ /* vty cmd: block */
+ priv->initiate_block = true;
+ priv->nsvc->om_blocked = true;
+ osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_BLOCKED, nsi->timeout[NS_TOUT_TNS_BLOCK], 0);
+ break;
+ case GPRS_NS2_EV_REQ_OM_UNBLOCK:
+ /* vty cmd: unblock*/
+ if (!priv->nsvc->om_blocked)
+ return;
+ priv->nsvc->om_blocked = false;
+ if (fi->state == GPRS_NS2_ST_BLOCKED)
+ osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_BLOCKED, nsi->timeout[NS_TOUT_TNS_BLOCK], 0);
+ break;
+ case GPRS_NS2_EV_RX_STATUS:
+ tp = data;
+ cause = tlvp_val8(tp, NS_IE_CAUSE, 0);
+ switch (cause) {
+ case NS_CAUSE_NSVC_BLOCKED:
+ if (fi->state != GPRS_NS2_ST_BLOCKED) {
+ LOG_NS_SIGNAL(priv->nsvc, "Rx", NS_PDUT_STATUS, LOGL_ERROR, ": remote side reported blocked state.\n");
+ priv->initiate_block = false;
+ priv->accept_unitdata = false;
+ osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_BLOCKED, nsi->timeout[NS_TOUT_TNS_BLOCK], 0);
+ }
+ break;
+ case NS_CAUSE_NSVC_UNKNOWN:
+ if (fi->state != GPRS_NS2_ST_RESET && fi->state != GPRS_NS2_ST_UNCONFIGURED) {
+ LOG_NS_SIGNAL(priv->nsvc, "Rx", NS_PDUT_STATUS, LOGL_ERROR, ": remote side reported unknown nsvc.\n");
+ osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_RESET, nsi->timeout[NS_TOUT_TNS_RESET], 0);
+ }
+ break;
+ }
+
break;
}
}
-static void gprs_ns2_vc_fsm_clean(struct osmo_fsm_inst *fi,
+static void ns2_vc_fsm_clean(struct osmo_fsm_inst *fi,
enum osmo_fsm_term_cause cause)
{
struct gprs_ns2_vc_priv *priv = fi->priv;
@@ -582,20 +758,23 @@ static void gprs_ns2_vc_fsm_clean(struct osmo_fsm_inst *fi,
osmo_timer_del(&priv->alive.timer);
}
-static struct osmo_fsm gprs_ns2_vc_fsm = {
+static struct osmo_fsm ns2_vc_fsm = {
.name = "GPRS-NS2-VC",
- .states = gprs_ns2_vc_states,
- .num_states = ARRAY_SIZE(gprs_ns2_vc_states),
- .allstate_event_mask = S(GPRS_NS2_EV_UNITDATA) |
- S(GPRS_NS2_EV_RESET) |
- S(GPRS_NS2_EV_ALIVE) |
- S(GPRS_NS2_EV_ALIVE_ACK) |
- S(GPRS_NS2_EV_FORCE_UNCONFIGURED),
- .allstate_action = gprs_ns2_vc_fsm_allstate_action,
- .cleanup = gprs_ns2_vc_fsm_clean,
- .timer_cb = gprs_ns2_vc_fsm_timer_cb,
- /* .log_subsys = DNS, "is not constant" */
- .event_names = gprs_ns2_vc_event_names,
+ .states = ns2_vc_states,
+ .num_states = ARRAY_SIZE(ns2_vc_states),
+ .allstate_event_mask = S(GPRS_NS2_EV_RX_UNITDATA) |
+ S(GPRS_NS2_EV_RX_RESET) |
+ S(GPRS_NS2_EV_RX_ALIVE) |
+ S(GPRS_NS2_EV_RX_ALIVE_ACK) |
+ S(GPRS_NS2_EV_RX_STATUS) |
+ S(GPRS_NS2_EV_REQ_FORCE_UNCONFIGURED) |
+ S(GPRS_NS2_EV_REQ_OM_RESET) |
+ S(GPRS_NS2_EV_REQ_OM_BLOCK) |
+ S(GPRS_NS2_EV_REQ_OM_UNBLOCK),
+ .allstate_action = ns2_vc_fsm_allstate_action,
+ .cleanup = ns2_vc_fsm_clean,
+ .timer_cb = ns2_vc_fsm_timer_cb,
+ .event_names = ns2_vc_event_names,
.pre_term = NULL,
.log_subsys = DLNS,
};
@@ -608,21 +787,20 @@ static struct osmo_fsm gprs_ns2_vc_fsm = {
* \param initiator initiator is the site which starts the connection. Usually the BSS.
* \return NULL on error, otherwise the fsm
*/
-struct osmo_fsm_inst *gprs_ns2_vc_fsm_alloc(struct gprs_ns2_vc *nsvc,
+struct osmo_fsm_inst *ns2_vc_fsm_alloc(struct gprs_ns2_vc *nsvc,
const char *id, bool initiator)
{
struct osmo_fsm_inst *fi;
struct gprs_ns2_vc_priv *priv;
- fi = osmo_fsm_inst_alloc(&gprs_ns2_vc_fsm, nsvc, NULL, LOGL_DEBUG, id);
+ fi = osmo_fsm_inst_alloc(&ns2_vc_fsm, nsvc, NULL, LOGL_DEBUG, id);
if (!fi)
return fi;
nsvc->fi = fi;
priv = fi->priv = talloc_zero(fi, struct gprs_ns2_vc_priv);
priv->nsvc = nsvc;
- priv->initiate_reset = initiator;
- priv->initiate_block = initiator;
+ priv->initiator = initiator;
osmo_timer_setup(&priv->alive.timer, alive_timeout_handler, fi);
@@ -632,20 +810,52 @@ struct osmo_fsm_inst *gprs_ns2_vc_fsm_alloc(struct gprs_ns2_vc *nsvc,
/*! Start a NS-VC FSM.
* \param nsvc the virtual circuit
* \return 0 on success; negative on error */
-int gprs_ns2_vc_fsm_start(struct gprs_ns2_vc *nsvc)
+int ns2_vc_fsm_start(struct gprs_ns2_vc *nsvc)
{
/* allows to call this function even for started nsvc by gprs_ns2_start_alive_all_nsvcs */
if (nsvc->fi->state == GPRS_NS2_ST_UNCONFIGURED)
- return osmo_fsm_inst_dispatch(nsvc->fi, GPRS_NS2_EV_START, NULL);
+ return osmo_fsm_inst_dispatch(nsvc->fi, GPRS_NS2_EV_REQ_START, NULL);
return 0;
}
/*! Reset a NS-VC FSM.
* \param nsvc the virtual circuit
* \return 0 on success; negative on error */
-int gprs_ns2_vc_force_unconfigured(struct gprs_ns2_vc *nsvc)
+int ns2_vc_force_unconfigured(struct gprs_ns2_vc *nsvc)
+{
+ return osmo_fsm_inst_dispatch(nsvc->fi, GPRS_NS2_EV_REQ_FORCE_UNCONFIGURED, NULL);
+}
+
+/*! Block a NS-VC.
+ * \param nsvc the virtual circuit
+ * \return 0 on success; negative on error */
+int ns2_vc_block(struct gprs_ns2_vc *nsvc)
{
- return osmo_fsm_inst_dispatch(nsvc->fi, GPRS_NS2_EV_FORCE_UNCONFIGURED, NULL);
+ struct gprs_ns2_vc_priv *priv = nsvc->fi->priv;
+ if (priv->nsvc->om_blocked)
+ return -EALREADY;
+
+ return osmo_fsm_inst_dispatch(nsvc->fi, GPRS_NS2_EV_REQ_OM_BLOCK, NULL);
+}
+
+/*! Unblock a NS-VC.
+ * \param nsvc the virtual circuit
+ * \return 0 on success; negative on error */
+int ns2_vc_unblock(struct gprs_ns2_vc *nsvc)
+{
+ struct gprs_ns2_vc_priv *priv = nsvc->fi->priv;
+ if (!priv->nsvc->om_blocked)
+ return -EALREADY;
+
+ return osmo_fsm_inst_dispatch(nsvc->fi, GPRS_NS2_EV_REQ_OM_UNBLOCK, NULL);
+}
+
+/*! Reset a NS-VC.
+ * \param nsvc the virtual circuit
+ * \return 0 on success; negative on error */
+int ns2_vc_reset(struct gprs_ns2_vc *nsvc)
+{
+ return osmo_fsm_inst_dispatch(nsvc->fi, GPRS_NS2_EV_REQ_OM_RESET, NULL);
}
/*! entry point for messages from the driver/VL
@@ -653,58 +863,114 @@ int gprs_ns2_vc_force_unconfigured(struct gprs_ns2_vc *nsvc)
* \param msg message that was received
* \param tp parsed TLVs of the received message
* \return 0 on success; negative on error */
-int gprs_ns2_vc_rx(struct gprs_ns2_vc *nsvc, struct msgb *msg, struct tlv_parsed *tp)
+int ns2_vc_rx(struct gprs_ns2_vc *nsvc, struct msgb *msg, struct tlv_parsed *tp)
{
struct gprs_ns_hdr *nsh = (struct gprs_ns_hdr *) msg->l2h;
+ struct gprs_ns2_vc *target_nsvc = nsvc;
struct osmo_fsm_inst *fi = nsvc->fi;
int rc = 0;
uint8_t cause;
+ uint16_t nsei, nsvci;
/* TODO: 7.2: on UNBLOCK/BLOCK: check if NS-VCI is correct,
* if not answer STATUS with "NS-VC unknown" */
- /* TODO: handle RESET with different VCI */
/* TODO: handle BLOCK/UNBLOCK/ALIVE with different VCI */
- if (gprs_ns2_validate(nsvc, nsh->pdu_type, msg, tp, &cause)) {
+ if (ns2_validate(nsvc, nsh->pdu_type, msg, tp, &cause)) {
+ /* don't answer on a STATUS with a STATUS */
if (nsh->pdu_type != NS_PDUT_STATUS) {
- rc = ns2_tx_status(nsvc, cause, 0, msg);
+ rc = ns2_tx_status(nsvc, cause, 0, msg, NULL);
+ goto out;
+ }
+ }
+
+ if (TLVP_PRESENT(tp, NS_IE_NSEI)) {
+ nsei = tlvp_val16be(tp, NS_IE_NSEI);
+ if (nsei != nsvc->nse->nsei) {
+ /* 48.016 § 7.3.1 send, RESET_ACK to wrong NSVCI + ignore */
+ if (nsh->pdu_type == NS_PDUT_RESET)
+ ns2_tx_reset_ack(nsvc);
+
+ LOG_NS_SIGNAL(nsvc, "Rx", nsh->pdu_type, LOGL_ERROR, " with wrong NSEI (exp: %05u, got %05u). Ignoring PDU.\n", nsvc->nse->nsei, nsei);
goto out;
}
}
+ if (nsvc->nsvci_is_valid && TLVP_PRESENT(tp, NS_IE_VCI)) {
+ nsvci = tlvp_val16be(tp, NS_IE_VCI);
+ if (nsvci != nsvc->nsvci) {
+ /* 48.016 § 7.3.1 send RESET_ACK to wrong NSVCI + ignore */
+ if (nsh->pdu_type == NS_PDUT_RESET) {
+ ns2_tx_reset_ack(nsvc);
+ LOG_NS_SIGNAL(nsvc, "Rx", nsh->pdu_type, LOGL_ERROR, " with wrong NSVCI (exp: %05u, got %05u). Ignoring PDU.\n", nsvc->nsvci, nsvci);
+ goto out;
+ } else if (nsh->pdu_type == NS_PDUT_BLOCK || nsh->pdu_type == NS_PDUT_STATUS) {
+ /* this is a PDU received over a NSVC and reports a status/block for another NSVC */
+ target_nsvc = gprs_ns2_nsvc_by_nsvci(nsvc->nse->nsi, nsvci);
+ if (!target_nsvc) {
+ LOGPFSML(fi, LOGL_ERROR, "Received a %s PDU for unknown NSVC (NSVCI %d)\n",
+ get_value_string(gprs_ns_pdu_strings, nsh->pdu_type), nsvci);
+ if (nsh->pdu_type == NS_PDUT_BLOCK)
+ ns2_tx_status(nsvc, NS_CAUSE_NSVC_UNKNOWN, 0, msg, &nsvci);
+ goto out;
+ }
+
+ if (target_nsvc->nse != nsvc->nse) {
+ LOGPFSML(fi, LOGL_ERROR, "Received a %s PDU for a NSVC (NSVCI %d) but it belongs to a different NSE!\n",
+ get_value_string(gprs_ns_pdu_strings, nsh->pdu_type), nsvci);
+ goto out;
+ }
+
+ /* the status/block will be passed to the nsvc/target nsvc in the switch */
+ } else {
+ LOG_NS_SIGNAL(nsvc, "Rx", nsh->pdu_type, LOGL_ERROR, " with wrong NSVCI=%05u. Ignoring PDU.\n", nsvci);
+ goto out;
+ }
+ }
+ }
+
switch (nsh->pdu_type) {
case NS_PDUT_RESET:
- osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_RESET, tp);
+ osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_RX_RESET, tp);
break;
case NS_PDUT_RESET_ACK:
- osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_RESET_ACK, tp);
+ osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_RX_RESET_ACK, tp);
break;
case NS_PDUT_BLOCK:
- osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_BLOCK, tp);
+ if (target_nsvc != nsvc) {
+ osmo_fsm_inst_dispatch(target_nsvc->fi, GPRS_NS2_EV_RX_BLOCK_FOREIGN, tp);
+ ns2_tx_block_ack(nsvc, &nsvci);
+ } else {
+ osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_RX_BLOCK, tp);
+ }
break;
case NS_PDUT_BLOCK_ACK:
- osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_BLOCK_ACK, tp);
+ osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_RX_BLOCK_ACK, tp);
break;
case NS_PDUT_UNBLOCK:
- osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_UNBLOCK, tp);
+ osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_RX_UNBLOCK, tp);
break;
case NS_PDUT_UNBLOCK_ACK:
- osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_UNBLOCK_ACK, tp);
+ osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_RX_UNBLOCK_ACK, tp);
break;
case NS_PDUT_ALIVE:
- osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_ALIVE, tp);
+ osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_RX_ALIVE, tp);
break;
case NS_PDUT_ALIVE_ACK:
- osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_ALIVE_ACK, tp);
+ osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_RX_ALIVE_ACK, tp);
break;
case NS_PDUT_UNITDATA:
/* UNITDATA have to free msg because it might send the msg layer upwards */
- osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_UNITDATA, msg);
+ osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_RX_UNITDATA, msg);
return 0;
+ case NS_PDUT_STATUS:
+ osmo_fsm_inst_dispatch(target_nsvc->fi, GPRS_NS2_EV_RX_STATUS, tp);
+ break;
default:
- LOGP(DLNS, LOGL_ERROR, "NSEI=%u Rx unknown NS PDU type %s\n", nsvc->nse->nsei,
- get_value_string(gprs_ns_pdu_strings, nsh->pdu_type));
- return -EINVAL;
+ LOGPFSML(fi, LOGL_ERROR, "NSEI=%u Rx unknown NS PDU type %s\n", nsvc->nse->nsei,
+ get_value_string(gprs_ns_pdu_strings, nsh->pdu_type));
+ rc = -EINVAL;
+ break;
}
out:
@@ -714,7 +980,7 @@ out:
}
/*! is the given NS-VC unblocked? */
-int gprs_ns2_vc_is_unblocked(struct gprs_ns2_vc *nsvc)
+int ns2_vc_is_unblocked(struct gprs_ns2_vc *nsvc)
{
return (nsvc->fi->state == GPRS_NS2_ST_UNBLOCKED);
}
@@ -722,5 +988,5 @@ int gprs_ns2_vc_is_unblocked(struct gprs_ns2_vc *nsvc)
/* initialize osmo_ctx on main tread */
static __attribute__((constructor)) void on_dso_load_ctx(void)
{
- OSMO_ASSERT(osmo_fsm_register(&gprs_ns2_vc_fsm) == 0);
+ OSMO_ASSERT(osmo_fsm_register(&ns2_vc_fsm) == 0);
}
diff --git a/src/gb/gprs_ns2_vty.c b/src/gb/gprs_ns2_vty.c
index 63331b95..32de49d4 100644
--- a/src/gb/gprs_ns2_vty.c
+++ b/src/gb/gprs_ns2_vty.c
@@ -1,10 +1,9 @@
/*! \file gprs_ns2_vty.c
* VTY interface for our GPRS Networks Service (NS) implementation. */
-/* (C) 2009-2014 by Harald Welte <laforge@gnumonks.org>
- * (C) 2016-2017 by sysmocom - s.f.m.c. GmbH
- * (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+/* (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* Author: Alexander Couzens <lynxis@fe80.eu>
+ * (C) 2021 by Harald Welte <laforge@osmocom.org>
*
* All Rights Reserved
*
@@ -31,84 +30,219 @@
#include <stdint.h>
#include <arpa/inet.h>
+#include <net/if.h>
-#include <osmocom/core/msgb.h>
#include <osmocom/core/byteswap.h>
#include <osmocom/core/fsm.h>
-#include <osmocom/core/talloc.h>
-#include <osmocom/core/select.h>
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/osmo_io.h>
#include <osmocom/core/rate_ctr.h>
-#include <osmocom/core/socket.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/talloc.h>
#include <osmocom/core/sockaddr_str.h>
-#include <osmocom/core/linuxlist.h>
#include <osmocom/core/socket.h>
+#include <osmocom/gprs/frame_relay.h>
#include <osmocom/gprs/gprs_ns2.h>
#include <osmocom/gsm/tlv.h>
-#include <osmocom/vty/vty.h>
#include <osmocom/vty/command.h>
#include <osmocom/vty/logging.h>
-#include <osmocom/vty/telnet_interface.h>
#include <osmocom/vty/misc.h>
+#include <osmocom/vty/telnet_interface.h>
+#include <osmocom/vty/vty.h>
#include "gprs_ns2_internal.h"
-struct ns2_vty_priv {
- /* global listen */
- struct osmo_sockaddr_str udp;
- struct osmo_sockaddr_str frgreaddr;
+#define SHOW_NS_STR "Display information about the NS protocol\n"
+#define NSVCI_STR "NS Virtual Connection ID (NS-VCI)\n"
+#define DLCI_STR "Data Link connection identifier\n"
+
+static struct gprs_ns2_inst *vty_nsi = NULL;
+static struct osmo_fr_network *vty_fr_network = NULL;
+static struct llist_head binds;
+static struct llist_head nses;
+static struct llist_head ip_sns_default_binds;
+
+struct vty_bind {
+ struct llist_head list;
+ const char *name;
+ enum gprs_ns2_ll ll;
int dscp;
- enum gprs_ns2_vc_mode vc_mode;
- /* force vc mode if another configuration forces
- * the vc mode. E.g. SNS configuration */
- bool force_vc_mode;
- const char *force_vc_mode_reason;
- bool frgre;
-
- struct llist_head vtyvc;
+ uint8_t priority;
+ bool accept_ipaccess;
+ bool accept_sns;
+ uint8_t ip_sns_sig_weight;
+ uint8_t ip_sns_data_weight;
};
-struct ns2_vty_vc {
+struct vty_nse {
struct llist_head list;
+ uint16_t nsei;
+ /* list of binds which are valid for this nse. Only IP-SNS uses this
+ * to allow `no listen ..` in the bind context. So "half" created binds are valid for
+ * IP-SNS. This allows changing the bind ip without modifying all NSEs afterwards */
+ struct llist_head binds;
+};
- struct osmo_sockaddr_str remote;
- enum gprs_ns_ll ll;
+/* used by IP-SNS to connect multiple vty_nse_bind to a vty_nse */
+struct vty_nse_bind {
+ struct llist_head list;
+ struct vty_bind *vbind;
+};
- /* old vty code doesnt support multiple NSVCI per NSEI */
- uint16_t nsei;
- uint16_t nsvci;
- uint16_t frdlci;
+/* TODO: this should into osmo timer */
+const struct value_string gprs_ns_timer_strs[] = {
+ { NS_TOUT_TNS_BLOCK, TNS_BLOCK_STR },
+ { NS_TOUT_TNS_BLOCK_RETRIES, TNS_BLOCK_RETRIES_STR },
+ { NS_TOUT_TNS_RESET, TNS_RESET_STR },
+ { NS_TOUT_TNS_RESET_RETRIES, TNS_RESET_RETRIES_STR },
+ { NS_TOUT_TNS_TEST, TNS_TEST_STR },
+ { NS_TOUT_TNS_ALIVE, TNS_ALIVE_STR },
+ { NS_TOUT_TNS_ALIVE_RETRIES, TNS_ALIVE_RETRIES_STR },
+ { NS_TOUT_TSNS_PROV, TSNS_PROV_STR },
+ { NS_TOUT_TSNS_SIZE_RETRIES, TSNS_SIZE_RETRIES_STR },
+ { NS_TOUT_TSNS_CONFIG_RETRIES, TSNS_CONFIG_RETRIES_STR },
+ { NS_TOUT_TSNS_PROCEDURES_RETRIES, TSNS_PROCEDURES_RETRIES_STR },
+ { 0, NULL }
+};
- bool remote_end_is_sgsn;
- bool configured;
+const struct value_string vty_fr_role_names[] = {
+ { FR_ROLE_USER_EQUIPMENT, "fr" },
+ { FR_ROLE_NETWORK_EQUIPMENT, "frnet" },
+ { 0, NULL }
};
-static struct gprs_ns2_inst *vty_nsi = NULL;
-static struct ns2_vty_priv priv;
-
-/* FIXME: this should go to some common file as it is copied
- * in vty_interface.c of the BSC */
-static const struct value_string gprs_ns_timer_strs[] = {
- { 0, "tns-block" },
- { 1, "tns-block-retries" },
- { 2, "tns-reset" },
- { 3, "tns-reset-retries" },
- { 4, "tns-test" },
- { 5, "tns-alive" },
- { 6, "tns-alive-retries" },
- { 7, "tsns-prov" },
+const struct value_string vty_ll_names[] = {
+ { GPRS_NS2_LL_FR, "fr" },
+ { GPRS_NS2_LL_FR_GRE, "frgre" },
+ { GPRS_NS2_LL_UDP, "udp" },
{ 0, NULL }
};
-static void log_set_nsvc_filter(struct log_target *target,
- struct gprs_ns2_vc *nsvc)
+static struct vty_bind *vty_bind_by_name(const char *name)
{
- if (nsvc) {
- target->filter_map |= (1 << LOG_FLT_GB_NSVC);
- target->filter_data[LOG_FLT_GB_NSVC] = nsvc;
- } else if (target->filter_data[LOG_FLT_GB_NSVC]) {
- target->filter_map = ~(1 << LOG_FLT_GB_NSVC);
- target->filter_data[LOG_FLT_GB_NSVC] = NULL;
+ struct vty_bind *vbind;
+ llist_for_each_entry(vbind, &binds, list) {
+ if (!strcmp(vbind->name, name))
+ return vbind;
+ }
+ return NULL;
+}
+
+static struct vty_bind *vty_bind_alloc(const char *name)
+{
+ struct vty_bind *vbind = talloc_zero(vty_nsi, struct vty_bind);
+ if (!vbind)
+ return NULL;
+
+ vbind->name = talloc_strdup(vty_nsi, name);
+ if (!vbind->name) {
+ talloc_free(vbind);
+ return NULL;
+ }
+
+ vbind->ip_sns_sig_weight = 1;
+ vbind->ip_sns_data_weight = 1;
+ llist_add_tail(&vbind->list, &binds);
+ return vbind;
+}
+
+static void vty_bind_free(struct vty_bind *vbind)
+{
+ if (!vbind)
+ return;
+
+ llist_del(&vbind->list);
+ talloc_free(vbind);
+}
+
+static struct vty_nse *vty_nse_by_nsei(uint16_t nsei)
+{
+ struct vty_nse *vnse;
+ llist_for_each_entry(vnse, &nses, list) {
+ if (vnse->nsei == nsei)
+ return vnse;
+ }
+ return NULL;
+}
+
+static struct vty_nse *vty_nse_alloc(uint16_t nsei)
+{
+ struct vty_nse *vnse = talloc_zero(vty_nsi, struct vty_nse);
+ if (!vnse)
+ return NULL;
+
+ vnse->nsei = nsei;
+ INIT_LLIST_HEAD(&vnse->binds);
+ llist_add_tail(&vnse->list, &nses);
+ return vnse;
+}
+
+static void vty_nse_free(struct vty_nse *vnse)
+{
+ if (!vnse)
+ return;
+
+ llist_del(&vnse->list);
+ /* all vbind of the nse will be freed by talloc */
+ talloc_free(vnse);
+}
+
+static int vty_nse_add_vbind(struct vty_nse *vnse, struct vty_bind *vbind)
+{
+ struct vty_nse_bind *vnse_bind;
+
+ if (vbind->ll != GPRS_NS2_LL_UDP)
+ return -EINVAL;
+
+ llist_for_each_entry(vnse_bind, &vnse->binds, list) {
+ if (vnse_bind->vbind == vbind)
+ return -EALREADY;
+ }
+
+ vnse_bind = talloc(vnse, struct vty_nse_bind);
+ if (!vnse_bind)
+ return -ENOMEM;
+ vnse_bind->vbind = vbind;
+
+ llist_add_tail(&vnse_bind->list, &vnse->binds);
+ return 0;
+}
+
+static int vty_nse_remove_vbind(struct vty_nse *vnse, struct vty_bind *vbind)
+{
+ struct vty_nse_bind *vnse_bind, *tmp;
+ if (vbind->ll != GPRS_NS2_LL_UDP)
+ return -EINVAL;
+
+ llist_for_each_entry_safe(vnse_bind, tmp, &vnse->binds, list) {
+ if (vnse_bind->vbind == vbind) {
+ llist_del(&vnse_bind->list);
+ talloc_free(vnse_bind);
+ return 0;
+ }
+ }
+
+ return -ENOENT;
+}
+
+/* check if the NSE still has SNS configuration */
+static bool vty_nse_check_sns(struct gprs_ns2_nse *nse) {
+ struct vty_nse *vnse = vty_nse_by_nsei(nse->nsei);
+
+ int count = gprs_ns2_sns_count(nse);
+ if (count > 0) {
+ /* there are other sns endpoints */
+ return true;
}
+
+ if (!vnse)
+ return false;
+
+ if (llist_empty(&vnse->binds))
+ return false;
+
+ return true;
}
static struct cmd_node ns_node = {
@@ -117,557 +251,1986 @@ static struct cmd_node ns_node = {
1,
};
-static struct ns2_vty_vc *vtyvc_alloc(uint16_t nsei) {
- struct ns2_vty_vc *vtyvc = talloc_zero(vty_nsi, struct ns2_vty_vc);
- if (!vtyvc)
- return vtyvc;
-
- vtyvc->nsei = nsei;
+DEFUN(cfg_ns, cfg_ns_cmd,
+ "ns",
+ "Configure the GPRS Network Service")
+{
+ vty->node = L_NS_NODE;
+ return CMD_SUCCESS;
+}
- llist_add(&vtyvc->list, &priv.vtyvc);
+DEFUN(cfg_ns_timer, cfg_ns_timer_cmd,
+ "timer " NS_TIMERS " <0-65535>",
+ "Network Service Timer\n"
+ NS_TIMERS_HELP "Timer Value\n")
+{
+ int idx = get_string_value(gprs_ns_timer_strs, argv[0]);
+ int val = atoi(argv[1]);
- return vtyvc;
-}
+ if (idx < 0 || idx >= ARRAY_SIZE(vty_nsi->timeout))
+ return CMD_WARNING;
-static void ns2_vc_free(struct ns2_vty_vc *vtyvc) {
- if (!vtyvc)
- return;
+ vty_nsi->timeout[idx] = val;
- llist_del(&vtyvc->list);
- talloc_free(vtyvc);
+ return CMD_SUCCESS;
}
-static struct ns2_vty_vc *vtyvc_by_nsei(uint16_t nsei, bool alloc_missing) {
- struct ns2_vty_vc *vtyvc;
+DEFUN(cfg_ns_nsei, cfg_ns_nsei_cmd,
+ "nse <0-65535> [ip-sns-role-sgsn]",
+ "Persistent NS Entity\n"
+ "NS Entity ID (NSEI)\n"
+ "Create NSE in SGSN role (default: BSS)\n"
+ )
+{
+ struct gprs_ns2_nse *nse;
+ struct vty_nse *vnse;
+ uint16_t nsei = atoi(argv[0]);
+ bool sgsn_role = false;
+ bool free_vnse = false;
+ if (argc > 1 && !strcmp(argv[1], "ip-sns-role-sgsn"))
+ sgsn_role = true;
+
+ vnse = vty_nse_by_nsei(nsei);
+ if (!vnse) {
+ vnse = vty_nse_alloc(nsei);
+ if (!vnse) {
+ vty_out(vty, "Failed to create vty NSE!%s", VTY_NEWLINE);
+ return CMD_ERR_INCOMPLETE;
+ }
+ free_vnse = true;
+ }
- llist_for_each_entry(vtyvc, &priv.vtyvc, list) {
- if (vtyvc->nsei == nsei)
- return vtyvc;
+ nse = gprs_ns2_nse_by_nsei(vty_nsi, nsei);
+ if (!nse) {
+ nse = gprs_ns2_create_nse2(vty_nsi, nsei, GPRS_NS2_LL_UNDEF, GPRS_NS2_DIALECT_UNDEF,
+ sgsn_role);
+ if (!nse) {
+ vty_out(vty, "Failed to create NSE!%s", VTY_NEWLINE);
+ goto err;
+ }
+ nse->persistent = true;
}
- if (!alloc_missing)
- return NULL;
+ if (!nse->persistent) {
+ /* TODO: should the dynamic NSE removed? */
+ vty_out(vty, "A dynamic NSE with the specified NSEI already exists%s", VTY_NEWLINE);
+ goto err;
+ }
- vtyvc = vtyvc_alloc(nsei);
- if (!vtyvc)
- return vtyvc;
+ vty->node = L_NS_NSE_NODE;
+ vty->index = nse;
- vtyvc->nsei = nsei;
- return vtyvc;
+ return CMD_SUCCESS;
+
+err:
+ if (free_vnse)
+ talloc_free(vnse);
+
+ return CMD_ERR_INCOMPLETE;
}
-static int config_write_ns(struct vty *vty)
+DEFUN(cfg_no_ns_nsei, cfg_no_ns_nsei_cmd,
+ "no nse <0-65535>",
+ NO_STR
+ "Delete a Persistent NS Entity\n"
+ "NS Entity ID (NSEI)\n"
+ )
{
- struct ns2_vty_vc *vtyvc;
- unsigned int i;
- struct osmo_sockaddr_str sockstr;
+ struct gprs_ns2_nse *nse;
+ struct vty_nse *vnse;
+ uint16_t nsei = atoi(argv[0]);
- vty_out(vty, "ns%s", VTY_NEWLINE);
+ nse = gprs_ns2_nse_by_nsei(vty_nsi, nsei);
+ if (!nse) {
+ vty_out(vty, "Can not find NS Entity %s%s", argv[0], VTY_NEWLINE);
+ return CMD_ERR_NOTHING_TODO;
+ }
+
+ if (!nse->persistent) {
+ vty_out(vty, "Ignoring non-persistent NS Entity%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ vty_out(vty, "Deleting NS Entity %u%s", nse->nsei, VTY_NEWLINE);
+ gprs_ns2_free_nse(nse);
+
+ vnse = vty_nse_by_nsei(nsei);
+ vty_nse_free(vnse);
+
+ return CMD_SUCCESS;
+}
- /* global configuration must be written first, as some of it may be
- * relevant when creating the NSE/NSVC later below */
+/* TODO: add fr/gre */
+DEFUN(cfg_ns_bind, cfg_ns_bind_cmd,
+ "bind (fr|udp) ID",
+ "Configure local Bind\n"
+ "Frame Relay\n" "UDP/IP\n"
+ "Unique identifier for this bind (to reference from NS-VCs, NSEs, ...)\n"
+ )
+{
+ const char *nstype = argv[0];
+ const char *name = argv[1];
+ struct vty_bind *vbind;
+ enum gprs_ns2_ll ll;
+ int rc;
+
+ rc = get_string_value(vty_ll_names, nstype);
+ if (rc < 0)
+ return CMD_WARNING;
+ ll = (enum gprs_ns2_ll) rc;
- vty_out(vty, " encapsulation framerelay-gre enabled %u%s",
- priv.frgre ? 1 : 0, VTY_NEWLINE);
+ if (!osmo_identifier_valid(name)) {
+ vty_out(vty, "Invalid ID. The ID should be only alphanumeric.%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
- if (priv.frgre) {
- if (strlen(priv.frgreaddr.ip)) {
- vty_out(vty, " encapsulation framerelay-gre local-ip %s%s",
- sockstr.ip, VTY_NEWLINE);
+ vbind = vty_bind_by_name(name);
+ if (vbind) {
+ if (vbind->ll != ll) {
+ vty_out(vty, "A bind with the specified ID already exists with a different type (fr|frgre|udp)!%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
}
} else {
- if (strlen(priv.udp.ip)) {
- vty_out(vty, " encapsulation udp local-ip %s%s",
- priv.udp.ip, VTY_NEWLINE);
+ vbind = vty_bind_alloc(name);
+ if (!vbind) {
+ vty_out(vty, "Can not create bind - out of memory%s", VTY_NEWLINE);
+ return CMD_WARNING;
}
-
- if (priv.udp.port)
- vty_out(vty, " encapsulation udp local-port %u%s",
- priv.udp.port, VTY_NEWLINE);
+ vbind->ll = ll;
}
- if (priv.dscp)
- vty_out(vty, " encapsulation udp dscp %d%s",
- priv.dscp, VTY_NEWLINE);
+ vty->index = vbind;
+ vty->node = L_NS_BIND_NODE;
- vty_out(vty, " encapsulation udp use-reset-block-unblock %s%s",
- priv.vc_mode == NS2_VC_MODE_BLOCKRESET ? "enabled" : "disabled", VTY_NEWLINE);
+ return CMD_SUCCESS;
+}
- llist_for_each_entry(vtyvc, &priv.vtyvc, list) {
- vty_out(vty, " nse %u nsvci %u%s",
- vtyvc->nsei, vtyvc->nsvci, VTY_NEWLINE);
+DEFUN(cfg_no_ns_bind, cfg_no_ns_bind_cmd,
+ "no bind ID",
+ NO_STR
+ "Delete a bind\n"
+ "Unique identifier for this bind\n"
+ )
+{
+ struct vty_bind *vbind;
+ struct gprs_ns2_vc_bind *bind;
+ const char *name = argv[0];
- vty_out(vty, " nse %u remote-role %s%s",
- vtyvc->nsei, vtyvc->remote_end_is_sgsn ? "sgsn" : "bss",
- VTY_NEWLINE);
+ vbind = vty_bind_by_name(name);
+ if (!vbind) {
+ vty_out(vty, "bind %s does not exist!%s", name, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ vty_bind_free(vbind);
+ bind = gprs_ns2_bind_by_name(vty_nsi, name);
+ if (bind)
+ gprs_ns2_free_bind(bind);
+ return CMD_SUCCESS;
+}
- switch (vtyvc->ll) {
- case GPRS_NS_LL_UDP:
- vty_out(vty, " nse %u encapsulation udp%s", vtyvc->nsei, VTY_NEWLINE);
- vty_out(vty, " nse %u remote-ip %s%s",
- vtyvc->nsei,
- vtyvc->remote.ip,
- VTY_NEWLINE);
- vty_out(vty, " nse %u remote-port %u%s",
- vtyvc->nsei, vtyvc->remote.port,
- VTY_NEWLINE);
+
+static void config_write_vbind(struct vty *vty, struct vty_bind *vbind)
+{
+ struct gprs_ns2_vc_bind *bind;
+ const struct osmo_sockaddr *addr;
+ struct osmo_sockaddr_str addr_str;
+ const char *netif, *frrole_str, *llstr;
+ enum osmo_fr_role frrole;
+
+ llstr = get_value_string_or_null(vty_ll_names, vbind->ll);
+ if (!llstr)
+ return;
+ vty_out(vty, " bind %s %s%s", llstr, vbind->name, VTY_NEWLINE);
+
+ bind = gprs_ns2_bind_by_name(vty_nsi, vbind->name);
+ switch (vbind->ll) {
+ case GPRS_NS2_LL_FR:
+ if (bind) {
+ netif = gprs_ns2_fr_bind_netif(bind);
+ if (!netif)
+ return;
+ frrole = gprs_ns2_fr_bind_role(bind);
+ if ((int) frrole == -1)
+ return;
+ frrole_str = get_value_string_or_null(vty_fr_role_names, frrole);
+ if (netif && frrole_str)
+ vty_out(vty, " fr %s %s%s", netif, frrole_str, VTY_NEWLINE);
+ }
+ break;
+ case GPRS_NS2_LL_UDP:
+ if (bind) {
+ addr = gprs_ns2_ip_bind_sockaddr(bind);
+ if (!osmo_sockaddr_str_from_sockaddr(&addr_str, &addr->u.sas)) {
+ vty_out(vty, " listen %s %u%s", addr_str.ip, addr_str.port,
+ VTY_NEWLINE);
+ }
+ }
+ if (vbind->accept_ipaccess)
+ vty_out(vty, " accept-ipaccess%s", VTY_NEWLINE);
+ if (vbind->accept_sns)
+ vty_out(vty, " accept-dynamic-ip-sns%s", VTY_NEWLINE);
+ if (vbind->dscp)
+ vty_out(vty, " dscp %u%s", vbind->dscp, VTY_NEWLINE);
+ if (vbind->priority)
+ vty_out(vty, " socket-priority %u%s", vbind->priority, VTY_NEWLINE);
+ vty_out(vty, " ip-sns signalling-weight %u data-weight %u%s",
+ vbind->ip_sns_sig_weight, vbind->ip_sns_data_weight, VTY_NEWLINE);
+ break;
+ default:
+ return;
+ }
+}
+
+static void config_write_nsvc(struct vty *vty, const struct gprs_ns2_vc *nsvc)
+{
+ const char *netif;
+ uint16_t dlci;
+ const struct osmo_sockaddr *addr;
+ struct osmo_sockaddr_str addr_str;
+
+ switch (nsvc->nse->ll) {
+ case GPRS_NS2_LL_UNDEF:
+ break;
+ case GPRS_NS2_LL_UDP:
+ switch (nsvc->nse->dialect) {
+ case GPRS_NS2_DIALECT_IPACCESS:
+ addr = gprs_ns2_ip_vc_remote(nsvc);
+ if (!addr)
+ break;
+ if (osmo_sockaddr_str_from_sockaddr(&addr_str, &addr->u.sas))
+ break;
+ vty_out(vty, " nsvc ipa %s %s %u nsvci %u%s",
+ nsvc->bind->name, addr_str.ip, addr_str.port,
+ nsvc->nsvci, VTY_NEWLINE);
break;
- case GPRS_NS_LL_FR_GRE:
- vty_out(vty, " nse %u encapsulation framerelay-gre%s",
- vtyvc->nsei, VTY_NEWLINE);
- vty_out(vty, " nse %u remote-ip %s%s",
- vtyvc->nsei,
- vtyvc->remote.ip,
- VTY_NEWLINE);
- vty_out(vty, " nse %u fr-dlci %u%s",
- vtyvc->nsei, vtyvc->frdlci,
- VTY_NEWLINE);
+ case GPRS_NS2_DIALECT_STATIC_ALIVE:
+ addr = gprs_ns2_ip_vc_remote(nsvc);
+ if (!addr)
+ break;
+ if (osmo_sockaddr_str_from_sockaddr(&addr_str, &addr->u.sas))
+ break;
+ vty_out(vty, " nsvc udp %s %s %u%s",
+ nsvc->bind->name, addr_str.ip, addr_str.port, VTY_NEWLINE);
break;
default:
break;
}
+ break;
+ case GPRS_NS2_LL_FR:
+ netif = gprs_ns2_fr_bind_netif(nsvc->bind);
+ if (!netif)
+ break;
+ dlci = gprs_ns2_fr_nsvc_dlci(nsvc);
+ if (!dlci)
+ break;
+ OSMO_ASSERT(nsvc->nsvci_is_valid);
+ vty_out(vty, " nsvc fr %s dlci %u nsvci %u%s",
+ netif, dlci, nsvc->nsvci, VTY_NEWLINE);
+ break;
+ case GPRS_NS2_LL_FR_GRE:
+ break;
+ }
+}
+
+static void _config_write_ns_nse(struct vty *vty, struct gprs_ns2_nse *nse)
+{
+ struct gprs_ns2_vc *nsvc;
+ struct vty_nse *vnse = vty_nse_by_nsei(nse->nsei);
+ struct vty_nse_bind *vbind;
+
+ OSMO_ASSERT(vnse);
+
+ vty_out(vty, " nse %u%s%s", nse->nsei,
+ nse->ip_sns_role_sgsn ? " ip-sns-role-sgsn" : "", VTY_NEWLINE);
+ switch (nse->dialect) {
+ case GPRS_NS2_DIALECT_SNS:
+ ns2_sns_write_vty(vty, nse);
+ llist_for_each_entry(vbind, &vnse->binds, list) {
+ vty_out(vty, " ip-sns-bind %s%s", vbind->vbind->name, VTY_NEWLINE);
+ }
+ break;
+ default:
+ llist_for_each_entry(nsvc, &nse->nsvc, list) {
+ config_write_nsvc(vty, nsvc);
+ }
+ break;
+ }
+}
+
+static int config_write_ns_nse(struct vty *vty)
+{
+ struct gprs_ns2_nse *nse;
+
+ llist_for_each_entry(nse, &vty_nsi->nse, list) {
+ if (!nse->persistent)
+ continue;
+
+ _config_write_ns_nse(vty, nse);
+ }
+
+ return 0;
+}
+
+static int config_write_ns_bind(struct vty *vty)
+{
+ struct vty_bind *vbind;
+
+ llist_for_each_entry(vbind, &binds, list) {
+ config_write_vbind(vty, vbind);
}
+ return 0;
+}
+
+static int config_write_ns(struct vty *vty)
+{
+ struct vty_nse_bind *vbind;
+ unsigned int i;
+ int ret;
+
+ vty_out(vty, "ns%s", VTY_NEWLINE);
+
for (i = 0; i < ARRAY_SIZE(vty_nsi->timeout); i++)
vty_out(vty, " timer %s %u%s",
get_value_string(gprs_ns_timer_strs, i),
vty_nsi->timeout[i], VTY_NEWLINE);
- return CMD_SUCCESS;
+ if (vty_nsi->txqueue_max_length != NS_DEFAULT_TXQUEUE_MAX_LENGTH)
+ vty_out(vty, " txqueue-max-length %u%s", vty_nsi->txqueue_max_length, VTY_NEWLINE);
+
+ ret = config_write_ns_bind(vty);
+ if (ret)
+ return ret;
+
+ llist_for_each_entry(vbind, &ip_sns_default_binds, list) {
+ vty_out(vty, " ip-sns-default bind %s%s", vbind->vbind->name, VTY_NEWLINE);
+ }
+
+ ret = config_write_ns_nse(vty);
+ if (ret)
+ return ret;
+
+ return 0;
}
-DEFUN(cfg_ns, cfg_ns_cmd,
- "ns",
- "Configure the GPRS Network Service")
+
+static struct cmd_node ns_bind_node = {
+ L_NS_BIND_NODE,
+ "%s(config-ns-bind)# ",
+ 1,
+};
+
+DEFUN(cfg_ns_bind_listen, cfg_ns_bind_listen_cmd,
+ "listen " VTY_IPV46_CMD " <1-65535>",
+ "Configure local IP + Port of this bind\n"
+ "Local IPv4 Address\n" "Local IPv6 Address\n"
+ "Local UDP Port\n"
+ )
{
- vty->node = L_NS_NODE;
+ struct vty_bind *vbind = vty->index;
+ struct gprs_ns2_vc_bind *bind;
+ int rc;
+ const char *addr_str = argv[0];
+ unsigned int port = atoi(argv[1]);
+ struct osmo_sockaddr_str sockaddr_str;
+ struct osmo_sockaddr sockaddr;
+
+ if (vbind->ll != GPRS_NS2_LL_UDP) {
+ vty_out(vty, "listen can be only used with UDP bind%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (osmo_sockaddr_str_from_str(&sockaddr_str, addr_str, port)) {
+ vty_out(vty, "Can not parse the Address %s %s%s", argv[0], argv[1], VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ osmo_sockaddr_str_to_sockaddr(&sockaddr_str, &sockaddr.u.sas);
+ if (gprs_ns2_ip_bind_by_sockaddr(vty_nsi, &sockaddr)) {
+ vty_out(vty, "A bind with the specified address already exists!%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ rc = gprs_ns2_ip_bind(vty_nsi, vbind->name, &sockaddr, vbind->dscp, &bind);
+ if (rc != 0) {
+ vty_out(vty, "Failed to create the bind (rc %d)!%s", rc, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ bind->accept_ipaccess = vbind->accept_ipaccess;
+ bind->accept_sns = vbind->accept_sns;
+
return CMD_SUCCESS;
}
-static void dump_nsvc(struct vty *vty, struct gprs_ns2_vc *nsvc, bool stats)
+DEFUN(cfg_no_ns_bind_listen, cfg_no_ns_bind_listen_cmd,
+ "no listen",
+ NO_STR
+ "Delete a IP/Port assignment\n"
+ )
{
- vty_out(vty, " %s%s", gprs_ns2_ll_str(nsvc), VTY_NEWLINE);
+ struct vty_bind *vbind = vty->index;
+ struct gprs_ns2_vc_bind *bind;
- if (stats) {
- vty_out_rate_ctr_group(vty, " ", nsvc->ctrg);
- vty_out_stat_item_group(vty, " ", nsvc->statg);
+ if (vbind->ll != GPRS_NS2_LL_UDP) {
+ vty_out(vty, "no listen can be only used with UDP bind%s", VTY_NEWLINE);
+ return CMD_WARNING;
}
+
+ bind = gprs_ns2_bind_by_name(vty_nsi, vbind->name);
+ if (!bind)
+ return CMD_ERR_NOTHING_TODO;
+
+ OSMO_ASSERT(bind->ll == GPRS_NS2_LL_UDP);
+ gprs_ns2_free_bind(bind);
+ return CMD_SUCCESS;
}
-static void dump_nse(struct vty *vty, const struct gprs_ns2_nse *nse, bool stats, bool persistent_only)
+DEFUN(cfg_ns_bind_dscp, cfg_ns_bind_dscp_cmd,
+ "dscp <0-63>",
+ "Set DSCP/TOS on the UDP socket\n" "DSCP Value\n")
{
- struct gprs_ns2_vc *nsvc;
-
- vty_out(vty, "NSEI %5u%s",
- nse->nsei, VTY_NEWLINE);
+ struct vty_bind *vbind = vty->index;
+ struct gprs_ns2_vc_bind *bind;
+ uint16_t dscp = atoi(argv[0]);
- gprs_ns2_sns_dump_vty(vty, nse, stats);
- llist_for_each_entry(nsvc, &nse->nsvc, list) {
- if (persistent_only) {
- if (nsvc->persistent)
- dump_nsvc(vty, nsvc, stats);
- } else {
- dump_nsvc(vty, nsvc, stats);
- }
+ if (vbind->ll != GPRS_NS2_LL_UDP) {
+ vty_out(vty, "dscp can be only used with UDP bind%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
}
+
+ vbind->dscp = dscp;
+ bind = gprs_ns2_bind_by_name(vty_nsi, vbind->name);
+ if (bind)
+ gprs_ns2_ip_bind_set_dscp(bind, dscp);
+
+ return CMD_SUCCESS;
}
-static void dump_bind(struct vty *vty, const struct gprs_ns2_vc_bind *bind, bool stats)
+DEFUN(cfg_no_ns_bind_dscp, cfg_no_ns_bind_dscp_cmd,
+ "no dscp",
+ "Set DSCP/TOS on the UDP socket\n" "DSCP Value\n")
{
- if (bind->dump_vty)
- bind->dump_vty(bind, vty, stats);
+ struct vty_bind *vbind = vty->index;
+ struct gprs_ns2_vc_bind *bind;
+ uint16_t dscp = 0;
+
+ if (vbind->ll != GPRS_NS2_LL_UDP) {
+ vty_out(vty, "dscp can be only used with UDP bind%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ vbind->dscp = dscp;
+ bind = gprs_ns2_bind_by_name(vty_nsi, vbind->name);
+ if (bind)
+ gprs_ns2_ip_bind_set_dscp(bind, dscp);
+
+ return CMD_SUCCESS;
}
-static void dump_ns(struct vty *vty, const struct gprs_ns2_inst *nsi, bool stats, bool persistent_only)
+DEFUN(cfg_ns_bind_priority, cfg_ns_bind_priority_cmd,
+ "socket-priority <0-255>",
+ "Set socket priority on the UDP socket\n" "Priority Value (>6 requires CAP_NET_ADMIN)\n")
{
+ struct vty_bind *vbind = vty->index;
struct gprs_ns2_vc_bind *bind;
- struct gprs_ns2_nse *nse;
+ uint8_t prio = atoi(argv[0]);
- llist_for_each_entry(bind, &nsi->binding, list) {
- dump_bind(vty, bind, stats);
+ if (vbind->ll != GPRS_NS2_LL_UDP) {
+ vty_out(vty, "dscp can be only used with UDP bind%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
}
- llist_for_each_entry(nse, &nsi->nse, list) {
- dump_nse(vty, nse, stats, persistent_only);
- }
+ vbind->priority = prio;
+ bind = gprs_ns2_bind_by_name(vty_nsi, vbind->name);
+ if (bind)
+ gprs_ns2_ip_bind_set_priority(bind, prio);
+ return CMD_SUCCESS;
}
-DEFUN(show_ns, show_ns_cmd, "show ns",
- SHOW_STR "Display information about the NS protocol")
+DEFUN(cfg_ns_bind_ipaccess, cfg_ns_bind_ipaccess_cmd,
+ "accept-ipaccess",
+ "Allow to create dynamic NS Entity by NS Reset PDU on UDP (ip.access style)\n"
+ )
{
- dump_ns(vty, vty_nsi, false, false);
+ struct vty_bind *vbind = vty->index;
+ struct gprs_ns2_vc_bind *bind;
+
+ if (vbind->ll != GPRS_NS2_LL_UDP) {
+ vty_out(vty, "accept-ipaccess can be only used with UDP bind%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ vbind->accept_ipaccess = true;
+ bind = gprs_ns2_bind_by_name(vty_nsi, vbind->name);
+ if (bind)
+ bind->accept_ipaccess = true;
+
return CMD_SUCCESS;
}
-DEFUN(show_ns_stats, show_ns_stats_cmd, "show ns stats",
- SHOW_STR
- "Display information about the NS protocol\n"
- "Include statistics\n")
+DEFUN(cfg_no_ns_bind_ipaccess, cfg_no_ns_bind_ipaccess_cmd,
+ "no accept-ipaccess",
+ NO_STR
+ "Reject NS Reset PDU on UDP (ip.access style)\n"
+ )
{
- dump_ns(vty, vty_nsi, true, false);
+ struct vty_bind *vbind = vty->index;
+ struct gprs_ns2_vc_bind *bind;
+
+ if (vbind->ll != GPRS_NS2_LL_UDP) {
+ vty_out(vty, "no accept-ipaccess can be only used with UDP bind%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ vbind->accept_ipaccess = false;
+ bind = gprs_ns2_bind_by_name(vty_nsi, vbind->name);
+ if (bind)
+ bind->accept_ipaccess = false;
+
return CMD_SUCCESS;
}
-DEFUN(show_ns_pers, show_ns_pers_cmd, "show ns persistent",
- SHOW_STR
- "Display information about the NS protocol\n"
- "Show only persistent NS\n")
+DEFUN(cfg_ns_bind_accept_sns, cfg_ns_bind_accept_sns_cmd,
+ "accept-dynamic-ip-sns",
+ "Allow to create dynamic NS Entities by IP-SNS PDUs\n"
+ )
{
- dump_ns(vty, vty_nsi, true, true);
+ struct vty_bind *vbind = vty->index;
+ struct gprs_ns2_vc_bind *bind;
+
+ if (vbind->ll != GPRS_NS2_LL_UDP) {
+ vty_out(vty, "accept-dynamic-ip-sns can be only used with UDP bind%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ vbind->accept_sns = true;
+ bind = gprs_ns2_bind_by_name(vty_nsi, vbind->name);
+ if (bind)
+ bind->accept_sns = true;
+
return CMD_SUCCESS;
}
-DEFUN(show_nse, show_nse_cmd, "show ns (nsei|nsvc) <0-65535> [stats]",
- SHOW_STR "Display information about the NS protocol\n"
- "Select one NSE by its NSE Identifier\n"
- "Select one NSE by its NS-VC Identifier\n"
- "The Identifier of selected type\n"
- "Include Statistics\n")
+DEFUN(cfg_no_ns_bind_accept_sns, cfg_no_ns_bind_accept_sns_cmd,
+ "no accept-dynamic-ip-sns",
+ NO_STR
+ "Disable dynamic creation of NS Entities by IP-SNS PDUs\n"
+ )
{
- struct gprs_ns2_inst *nsi = vty_nsi;
- struct gprs_ns2_nse *nse;
- struct gprs_ns2_vc *nsvc;
- uint16_t id = atoi(argv[1]);
- bool show_stats = false;
+ struct vty_bind *vbind = vty->index;
+ struct gprs_ns2_vc_bind *bind;
- if (argc >= 3)
- show_stats = true;
+ if (vbind->ll != GPRS_NS2_LL_UDP) {
+ vty_out(vty, "no accept-dynamic-ip-sns can be only used with UDP bind%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
- if (!strcmp(argv[0], "nsei")) {
- nse = gprs_ns2_nse_by_nsei(nsi, id);
- if (!nse) {
- return CMD_WARNING;
- }
+ vbind->accept_sns = false;
+ bind = gprs_ns2_bind_by_name(vty_nsi, vbind->name);
+ if (bind)
+ bind->accept_sns = false;
- dump_nse(vty, nse, show_stats, false);
- } else {
- nsvc = gprs_ns2_nsvc_by_nsvci(nsi, id);
+ return CMD_SUCCESS;
+}
- if (!nsvc) {
- vty_out(vty, "No such NS Entity%s", VTY_NEWLINE);
- return CMD_WARNING;
- }
+DEFUN(cfg_ns_bind_ip_sns_weight, cfg_ns_bind_ip_sns_weight_cmd,
+ "ip-sns signalling-weight <0-254> data-weight <0-254>",
+ "IP SNS\n"
+ "signalling weight used by IP-SNS dynamic configuration\n"
+ "signalling weight used by IP-SNS dynamic configuration\n"
+ "data weight used by IP-SNS dynamic configuration\n"
+ "data weight used by IP-SNS dynamic configuration\n")
+{
+ struct vty_bind *vbind = vty->index;
+ struct gprs_ns2_vc_bind *bind;
+
+ int signalling = atoi(argv[0]);
+ int data = atoi(argv[1]);
- dump_nsvc(vty, nsvc, show_stats);
+ if (vbind->ll != GPRS_NS2_LL_UDP) {
+ vty_out(vty, "ip-sns signalling-weight <0-254> data-weight <0-254> can be only used with UDP bind%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
}
+ vbind->ip_sns_data_weight = data;
+ vbind->ip_sns_sig_weight = signalling;
+ bind = gprs_ns2_bind_by_name(vty_nsi, vbind->name);
+ if (bind)
+ gprs_ns2_ip_bind_set_sns_weight(bind, signalling, data);
+
return CMD_SUCCESS;
}
-static int nsvc_force_unconf_cb(struct gprs_ns2_vc *nsvc, void *ctx)
+DEFUN(cfg_ns_bind_fr, cfg_ns_bind_fr_cmd,
+ "fr NETIF (fr|frnet)",
+ "frame relay\n"
+ IFNAME_STR
+ "fr (user) is used by BSS or SGSN attached to UNI of a FR network\n"
+ "frnet (network) is used by SGSN if BSS is directly attached\n"
+ )
{
- gprs_ns2_vc_force_unconfigured(nsvc);
- return 0;
+ struct vty_bind *vbind = vty->index;
+ struct gprs_ns2_vc_bind *bind;
+ const char *netif = argv[0];
+ const char *role = argv[1];
+
+ int rc = 0;
+ enum osmo_fr_role frrole;
+
+ if (vbind->ll != GPRS_NS2_LL_FR) {
+ vty_out(vty, "fr can be only used with frame relay bind%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (!strcmp(role, "fr"))
+ frrole = FR_ROLE_USER_EQUIPMENT;
+ else if (!strcmp(role, "frnet"))
+ frrole = FR_ROLE_NETWORK_EQUIPMENT;
+ else
+ return CMD_WARNING;
+
+ bind = gprs_ns2_fr_bind_by_netif(vty_nsi, netif);
+ if (bind) {
+ vty_out(vty, "Interface %s already used.%s", netif, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ rc = gprs_ns2_fr_bind(vty_nsi, vbind->name, netif, vty_fr_network, frrole, &bind);
+ if (rc < 0) {
+ LOGP(DLNS, LOGL_ERROR, "Failed to bind interface %s on fr. Err: %d\n", netif, rc);
+ return CMD_WARNING;
+ }
+
+ return CMD_SUCCESS;
}
-DEFUN_HIDDEN(nsvc_force_unconf, nsvc_force_unconf_cmd,
- "nsvc nsei <0-65535> force-unconfigured",
- "NS Virtual Connection\n"
- "The NSEI\n"
- "Reset the NSVCs back to initial state\n"
- )
+DEFUN(cfg_no_ns_bind_fr, cfg_no_ns_bind_fr_cmd,
+ "no fr NETIF",
+ NO_STR
+ "Delete a frame relay link\n"
+ "Delete a frame relay link\n"
+ IFNAME_STR
+ )
{
- struct gprs_ns2_inst *nsi = vty_nsi;
- struct gprs_ns2_nse *nse;
+ struct vty_bind *vbind = vty->index;
+ struct gprs_ns2_vc_bind *bind;
+ const char *netif = argv[0];
- uint16_t id = atoi(argv[0]);
+ if (vbind->ll != GPRS_NS2_LL_FR) {
+ vty_out(vty, "fr can be only used with frame relay bind%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
- nse = gprs_ns2_nse_by_nsei(nsi, id);
- if (!nse) {
- vty_out(vty, "Could not find NSE for NSEI %u%s", id, VTY_NEWLINE);
+ bind = gprs_ns2_fr_bind_by_netif(vty_nsi, netif);
+ if (!bind) {
+ vty_out(vty, "Interface not found.%s", VTY_NEWLINE);
return CMD_WARNING;
}
- /* Perform the operation for all nsvc */
- gprs_ns2_nse_foreach_nsvc(nse, nsvc_force_unconf_cb, NULL);
+ if (strcmp(bind->name, vbind->name)) {
+ vty_out(vty, "The specified interface is not bound to this bind.%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ gprs_ns2_free_bind(bind);
return CMD_SUCCESS;
}
-#define NSE_CMD_STR "Persistent NS Entity\n" "NS Entity ID (NSEI)\n"
-DEFUN(cfg_nse_nsvc, cfg_nse_nsvci_cmd,
- "nse <0-65535> nsvci <0-65535>",
- NSE_CMD_STR
- "NS Virtual Connection\n"
- "NS Virtual Connection ID (NSVCI)\n"
- )
+static struct cmd_node ns_nse_node = {
+ L_NS_NSE_NODE,
+ "%s(config-ns-nse)# ",
+ 1,
+};
+
+DEFUN(cfg_ns_nse_nsvc_fr, cfg_ns_nse_nsvc_fr_cmd,
+ "nsvc fr NETIF dlci <16-1007> nsvci <0-65535>",
+ "NS Virtual Connection\n"
+ "frame relay\n"
+ "frame relay interface. Must be registered via fr vty\n"
+ NSVCI_STR
+ NSVCI_STR
+ DLCI_STR
+ DLCI_STR
+ )
{
- struct ns2_vty_vc *vtyvc;
+ struct gprs_ns2_vc_bind *bind;
+ struct gprs_ns2_vc *nsvc;
+ struct gprs_ns2_nse *nse = vty->index;
+ const char *netif = argv[0];
+ uint16_t dlci = atoi(argv[1]);
+ uint16_t nsvci = atoi(argv[2]);
+ bool dialect_modified = false;
+ bool ll_modified = false;
- uint16_t nsei = atoi(argv[0]);
- uint16_t nsvci = atoi(argv[1]);
+ if (nse->ll != GPRS_NS2_LL_FR && nse->ll != GPRS_NS2_LL_UNDEF) {
+ vty_out(vty, "Can not mix NS-VC with different link layer%s", VTY_NEWLINE);
+ goto err;
+ }
- vtyvc = vtyvc_by_nsei(nsei, true);
- if (!vtyvc) {
- vty_out(vty, "Can not allocate space %s", VTY_NEWLINE);
- return CMD_WARNING;
+ if (nse->dialect != GPRS_NS2_DIALECT_STATIC_RESETBLOCK && nse->dialect != GPRS_NS2_DIALECT_UNDEF) {
+ vty_out(vty, "Can not mix NS-VC with different dialects%s", VTY_NEWLINE);
+ goto err;
+ }
+
+ if (nse->ll == GPRS_NS2_LL_UNDEF) {
+ nse->ll = GPRS_NS2_LL_FR;
+ ll_modified = true;
}
- vtyvc->nsvci = nsvci;
+ if (nse->dialect == GPRS_NS2_DIALECT_UNDEF) {
+ ns2_nse_set_dialect(nse, GPRS_NS2_DIALECT_STATIC_RESETBLOCK);
+ dialect_modified = true;
+ }
+
+
+ bind = gprs_ns2_fr_bind_by_netif(vty_nsi, netif);
+ if (!bind) {
+ vty_out(vty, "Can not find fr interface \"%s\". Please configure it via fr vty.%s",
+ netif, VTY_NEWLINE);
+ goto err;
+ }
+
+ if (gprs_ns2_fr_nsvc_by_dlci(bind, dlci)) {
+ vty_out(vty, "A NS-VC with the specified DLCI already exist!%s", VTY_NEWLINE);
+ goto err;
+ }
+
+ if (gprs_ns2_nsvc_by_nsvci(vty_nsi, nsvci)) {
+ vty_out(vty, "A NS-VC with the specified NS-VCI already exist!%s", VTY_NEWLINE);
+ goto err;
+ }
+ nsvc = gprs_ns2_fr_connect(bind, nse, nsvci, dlci);
+ if (!nsvc) {
+ /* Could not create NS-VC, connect failed */
+ vty_out(vty, "Failed to create the NS-VC%s", VTY_NEWLINE);
+ goto err;
+ }
+ nsvc->persistent = true;
return CMD_SUCCESS;
+
+err:
+ if (ll_modified)
+ nse->ll = GPRS_NS2_LL_UNDEF;
+ if (dialect_modified)
+ ns2_nse_set_dialect(nse, GPRS_NS2_DIALECT_UNDEF);
+
+ return CMD_WARNING;
}
-DEFUN(cfg_nse_remoteip, cfg_nse_remoteip_cmd,
- "nse <0-65535> remote-ip " VTY_IPV46_CMD,
- NSE_CMD_STR
- "Remote IP Address\n"
- "Remote IPv4 Address\n"
- "Remote IPv6 Address\n")
+DEFUN(cfg_no_ns_nse_nsvc_fr_dlci, cfg_no_ns_nse_nsvc_fr_dlci_cmd,
+ "no nsvc fr NETIF dlci <16-1007>",
+ NO_STR
+ "Delete frame relay NS-VC\n"
+ "frame relay\n"
+ "frame relay interface. Must be registered via fr vty\n"
+ DLCI_STR
+ DLCI_STR
+ )
{
- uint16_t nsei = atoi(argv[0]);
- struct ns2_vty_vc *vtyvc;
+ struct gprs_ns2_vc_bind *bind;
+ struct gprs_ns2_vc *nsvc;
+ struct gprs_ns2_nse *nse = vty->index;
+ const char *netif = argv[0];
+ uint16_t dlci = atoi(argv[1]);
+
+ if (nse->ll != GPRS_NS2_LL_FR) {
+ vty_out(vty, "This NSE doesn't support frame relay.%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ bind = gprs_ns2_fr_bind_by_netif(vty_nsi, netif);
+ if (!bind) {
+ vty_out(vty, "Can not find fr interface \"%s\"%s",
+ netif, VTY_NEWLINE);
+ return CMD_ERR_NOTHING_TODO;
+ }
- vtyvc = vtyvc_by_nsei(nsei, true);
- if (!vtyvc) {
- vty_out(vty, "Can not allocate space %s", VTY_NEWLINE);
+ nsvc = gprs_ns2_fr_nsvc_by_dlci(bind, dlci);
+ if (!nsvc) {
+ vty_out(vty, "Can not find a NS-VC on fr interface %s with dlci %u%s",
+ netif, dlci, VTY_NEWLINE);
return CMD_WARNING;
}
- osmo_sockaddr_str_from_str2(&vtyvc->remote, argv[1]);
+ if (nse != nsvc->nse) {
+ vty_out(vty, "The specified NS-VC is not a part of the NSE %u!%s"
+ "To remove this NS-VC go to the vty node 'nse %u'%s",
+ nse->nsei, VTY_NEWLINE,
+ nsvc->nse->nsei, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ gprs_ns2_free_nsvc(nsvc);
+ if (llist_empty(&nse->nsvc)) {
+ nse->ll = GPRS_NS2_LL_UNDEF;
+ ns2_nse_set_dialect(nse, GPRS_NS2_DIALECT_UNDEF);
+ }
return CMD_SUCCESS;
}
-DEFUN(cfg_nse_remoteport, cfg_nse_remoteport_cmd,
- "nse <0-65535> remote-port <0-65535>",
- NSE_CMD_STR
- "Remote UDP Port\n"
- "Remote UDP Port Number\n")
+DEFUN(cfg_no_ns_nse_nsvci, cfg_no_ns_nse_nsvci_cmd,
+ "no nsvc nsvci <0-65535>",
+ NO_STR
+ "Delete NSVC\n"
+ NSVCI_STR
+ NSVCI_STR
+ )
{
- uint16_t nsei = atoi(argv[0]);
- uint16_t port = atoi(argv[1]);
- struct ns2_vty_vc *vtyvc;
+ struct gprs_ns2_vc *nsvc;
+ struct gprs_ns2_nse *nse = vty->index;
+ uint16_t nsvci = atoi(argv[0]);
+
+ switch (nse->dialect) {
+ case GPRS_NS2_DIALECT_SNS:
+ case GPRS_NS2_DIALECT_STATIC_ALIVE:
+ vty_out(vty, "NSE doesn't support NSVCI.%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ case GPRS_NS2_DIALECT_UNDEF:
+ vty_out(vty, "No NSVCs configured%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ case GPRS_NS2_DIALECT_IPACCESS:
+ case GPRS_NS2_DIALECT_STATIC_RESETBLOCK:
+ break;
+ }
+
+ nsvc = gprs_ns2_nsvc_by_nsvci(vty_nsi, nsvci);
+ if (!nsvc) {
+ vty_out(vty, "Can not find NS-VC with NS-VCI %u%s", nsvci, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
- vtyvc = vtyvc_by_nsei(nsei, true);
- if (!vtyvc) {
- vty_out(vty, "Can not allocate space %s", VTY_NEWLINE);
+ if (nse != nsvc->nse) {
+ vty_out(vty, "NS-VC with NS-VCI %u is not part of this NSE!%s",
+ nsvci, VTY_NEWLINE);
return CMD_WARNING;
}
- vtyvc->remote.port = port;
+ gprs_ns2_free_nsvc(nsvc);
+ if (llist_empty(&nse->nsvc)) {
+ nse->ll = GPRS_NS2_LL_UNDEF;
+ ns2_nse_set_dialect(nse, GPRS_NS2_DIALECT_UNDEF);
+ }
return CMD_SUCCESS;
}
-DEFUN(cfg_nse_fr_dlci, cfg_nse_fr_dlci_cmd,
- "nse <0-65535> fr-dlci <16-1007>",
- NSE_CMD_STR
- "Frame Relay DLCI\n"
- "Frame Relay DLCI Number\n")
+static int ns_nse_nsvc_udp_cmds(struct vty *vty, const char *bind_name, const char *remote_char, uint16_t port,
+ uint16_t sig_weight, uint16_t data_weight)
{
- uint16_t nsei = atoi(argv[0]);
- uint16_t dlci = atoi(argv[1]);
- struct ns2_vty_vc *vtyvc;
+ struct gprs_ns2_vc_bind *bind;
+ struct gprs_ns2_vc *nsvc;
+ struct gprs_ns2_nse *nse = vty->index;
+ bool dialect_modified = false;
+ bool ll_modified = false;
- vtyvc = vtyvc_by_nsei(nsei, true);
- if (!vtyvc) {
- vty_out(vty, "Can not allocate space %s", VTY_NEWLINE);
- return CMD_WARNING;
+ struct osmo_sockaddr_str remote_str;
+ struct osmo_sockaddr remote;
+
+ if (nse->ll == GPRS_NS2_LL_UNDEF) {
+ nse->ll = GPRS_NS2_LL_UDP;
+ ll_modified = true;
}
- if (vtyvc->ll != GPRS_NS_LL_FR_GRE) {
- vty_out(vty, "Warning: seting FR DLCI on non-FR NSE%s",
- VTY_NEWLINE);
+ if (nse->dialect == GPRS_NS2_DIALECT_UNDEF) {
+ ns2_nse_set_dialect(nse, GPRS_NS2_DIALECT_STATIC_ALIVE);
+ dialect_modified = true;
+ }
+
+ if (nse->ll != GPRS_NS2_LL_UDP) {
+ vty_out(vty, "Can not mix NS-VC with different link layer%s", VTY_NEWLINE);
+ goto err;
+ }
+
+ if (nse->dialect != GPRS_NS2_DIALECT_STATIC_ALIVE) {
+ vty_out(vty, "Can not mix NS-VC with different dialects%s", VTY_NEWLINE);
+ goto err;
}
- vtyvc->frdlci = dlci;
+ if (osmo_sockaddr_str_from_str(&remote_str, remote_char, port)) {
+ vty_out(vty, "Can not parse IPv4/IPv6 or port.%s", VTY_NEWLINE);
+ goto err;
+ }
+
+ if (osmo_sockaddr_str_to_sockaddr(&remote_str, &remote.u.sas)) {
+ vty_out(vty, "Can not parse IPv4/IPv6 or port.%s", VTY_NEWLINE);
+ goto err;
+ }
+
+ bind = gprs_ns2_bind_by_name(vty_nsi, bind_name);
+ if (!bind) {
+ vty_out(vty, "Can not find bind with name %s%s",
+ bind_name, VTY_NEWLINE);
+ goto err;
+ }
+
+ if (bind->ll != GPRS_NS2_LL_UDP) {
+ vty_out(vty, "Bind %s is not an UDP bind.%s",
+ bind_name, VTY_NEWLINE);
+ goto err;
+ }
+
+ nsvc = gprs_ns2_nsvc_by_sockaddr_bind(bind, &remote);
+ if (nsvc) {
+ if (nsvc->nse == nse)
+ vty_out(vty, "Specified NSVC is already present in this NSE.%s", VTY_NEWLINE);
+ else
+ vty_out(vty, "Specified NSVC is already present in another NSE%05u.%s", nsvc->nse->nsei, VTY_NEWLINE);
+ goto err;
+ }
+
+ nsvc = gprs_ns2_ip_connect(bind, &remote, nse, 0);
+ if (!nsvc) {
+ vty_out(vty, "Can not create NS-VC.%s", VTY_NEWLINE);
+ goto err;
+ }
+ nsvc->sig_weight = sig_weight;
+ nsvc->data_weight = data_weight;
+ nsvc->persistent = true;
return CMD_SUCCESS;
+
+err:
+ if (ll_modified)
+ nse->ll = GPRS_NS2_LL_UNDEF;
+ if (dialect_modified)
+ ns2_nse_set_dialect(nse, GPRS_NS2_DIALECT_UNDEF);
+ return CMD_WARNING;
}
-DEFUN(cfg_nse_encaps, cfg_nse_encaps_cmd,
- "nse <0-65535> encapsulation (udp|framerelay-gre)",
- NSE_CMD_STR
- "Encapsulation for NS\n"
- "UDP/IP Encapsulation\n" "Frame-Relay/GRE/IP Encapsulation\n")
+DEFUN(cfg_ns_nse_nsvc_udp, cfg_ns_nse_nsvc_udp_cmd,
+ "nsvc udp BIND " VTY_IPV46_CMD " <1-65535>",
+ "NS Virtual Connection\n"
+ "NS over UDP\n"
+ "A unique bind identifier created by ns bind\n"
+ "Remote IPv4 Address\n" "Remote IPv6 Address\n"
+ "Remote UDP Port\n")
{
- uint16_t nsei = atoi(argv[0]);
- struct ns2_vty_vc *vtyvc;
+ const char *bind_name = argv[0];
+ const char *remote = argv[1];
+ uint16_t port = atoi(argv[2]);
+ uint16_t sig_weight = 1;
+ uint16_t data_weight = 1;
+
+ return ns_nse_nsvc_udp_cmds(vty, bind_name, remote, port, sig_weight, data_weight);
+}
+
+DEFUN(cfg_ns_nse_nsvc_udp_weights, cfg_ns_nse_nsvc_udp_weights_cmd,
+ "nsvc udp BIND " VTY_IPV46_CMD " <1-65535> signalling-weight <0-254> data-weight <0-254>",
+ "NS Virtual Connection\n"
+ "NS over UDP\n"
+ "A unique bind identifier created by ns bind\n"
+ "Remote IPv4 Address\n" "Remote IPv6 Address\n"
+ "Remote UDP Port\n"
+ "Signalling weight of the NSVC (default = 1)\n"
+ "Signalling weight of the NSVC (default = 1)\n"
+ "Data weight of the NSVC (default = 1)\n"
+ "Data weight of the NSVC (default = 1)\n"
+ )
+{
+ const char *bind_name = argv[0];
+ const char *remote = argv[1];
+ uint16_t port = atoi(argv[2]);
+ uint16_t sig_weight = atoi(argv[3]);
+ uint16_t data_weight = atoi(argv[4]);
+
+ return ns_nse_nsvc_udp_cmds(vty, bind_name, remote, port, sig_weight, data_weight);
+}
- vtyvc = vtyvc_by_nsei(nsei, true);
- if (!vtyvc) {
- vty_out(vty, "Can not allocate space %s", VTY_NEWLINE);
+DEFUN(cfg_no_ns_nse_nsvc_udp, cfg_no_ns_nse_nsvc_udp_cmd,
+ "no nsvc udp BIND " VTY_IPV46_CMD " <1-65535>",
+ NO_STR
+ "Delete a NS Virtual Connection\n"
+ "NS over UDP\n"
+ "A unique bind identifier created by ns bind\n"
+ "Remote IPv4 Address\n" "Remote IPv6 Address\n"
+ "Remote UDP Port\n"
+ )
+{
+ struct gprs_ns2_vc_bind *bind;
+ struct gprs_ns2_vc *nsvc;
+ struct gprs_ns2_nse *nse = vty->index;
+ const char *bind_name = argv[0];
+ struct osmo_sockaddr_str remote_str;
+ struct osmo_sockaddr remote;
+ uint16_t port = atoi(argv[2]);
+
+ if (nse->ll != GPRS_NS2_LL_UDP) {
+ vty_out(vty, "This NSE doesn't support UDP.%s", VTY_NEWLINE);
return CMD_WARNING;
}
- if (!strcmp(argv[1], "udp"))
- vtyvc->ll = GPRS_NS_LL_UDP;
- else
- vtyvc->ll = GPRS_NS_LL_FR_GRE;
+ if (nse->dialect != GPRS_NS2_DIALECT_STATIC_ALIVE) {
+ vty_out(vty, "This NSE doesn't support UDP with dialect static alive.%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ bind = gprs_ns2_bind_by_name(vty_nsi, bind_name);
+ if (!bind) {
+ vty_out(vty, "Can not find bind with name %s%s",
+ bind_name, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (bind->ll != GPRS_NS2_LL_UDP) {
+ vty_out(vty, "Bind %s is not an UDP bind.%s",
+ bind_name, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (osmo_sockaddr_str_from_str(&remote_str, argv[1], port)) {
+ vty_out(vty, "Can not parse IPv4/IPv6 or port.%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (osmo_sockaddr_str_to_sockaddr(&remote_str, &remote.u.sas)) {
+ vty_out(vty, "Can not parse IPv4/IPv6 or port.%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ nsvc = gprs_ns2_nsvc_by_sockaddr_bind(bind, &remote);
+ if (!nsvc) {
+ vty_out(vty, "Can not find NS-VC with remote %s:%u%s",
+ remote_str.ip, remote_str.port, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (!nsvc->persistent) {
+ vty_out(vty, "NS-VC with remote %s:%u is a dynamic NS-VC. Not configured by vty.%s",
+ remote_str.ip, remote_str.port, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (nsvc->nse != nse) {
+ vty_out(vty, "NS-VC is not part of this NSE!%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ gprs_ns2_free_nsvc(nsvc);
+ if (llist_empty(&nse->nsvc)) {
+ nse->ll = GPRS_NS2_LL_UNDEF;
+ ns2_nse_set_dialect(nse, GPRS_NS2_DIALECT_UNDEF);
+ }
return CMD_SUCCESS;
}
-DEFUN(cfg_nse_remoterole, cfg_nse_remoterole_cmd,
- "nse <0-65535> remote-role (sgsn|bss)",
- NSE_CMD_STR
- "Remote NSE Role\n"
- "Remote Peer is SGSN\n"
- "Remote Peer is BSS\n")
+DEFUN(cfg_ns_nse_nsvc_ipa, cfg_ns_nse_nsvc_ipa_cmd,
+ "nsvc ipa BIND " VTY_IPV46_CMD " <1-65535> nsvci <0-65535>" ,
+ "NS Virtual Connection\n"
+ "NS over UDP ip.access style (uses RESET/BLOCK)\n"
+ "A unique bind identifier created by ns bind\n"
+ "Remote IPv4 Address\n" "Remote IPv6 Address\n"
+ "Remote UDP Port\n"
+ NSVCI_STR
+ NSVCI_STR
+ )
{
- uint16_t nsei = atoi(argv[0]);
- struct ns2_vty_vc *vtyvc;
+ struct gprs_ns2_vc_bind *bind;
+ struct gprs_ns2_vc *nsvc;
+ struct gprs_ns2_nse *nse = vty->index;
+ bool dialect_modified = false;
+ bool ll_modified = false;
+
+ const char *bind_name = argv[0];
+ struct osmo_sockaddr_str remote_str;
+ struct osmo_sockaddr remote;
+ uint16_t port = atoi(argv[2]);
+ uint16_t nsvci = atoi(argv[3]);
+
+ if (nse->ll == GPRS_NS2_LL_UNDEF) {
+ nse->ll = GPRS_NS2_LL_UDP;
+ ll_modified = true;
+ }
- vtyvc = vtyvc_by_nsei(nsei, true);
- if (!vtyvc) {
- vty_out(vty, "Can not allocate space %s", VTY_NEWLINE);
- return CMD_WARNING;
+ if (nse->dialect == GPRS_NS2_DIALECT_UNDEF) {
+ ns2_nse_set_dialect(nse, GPRS_NS2_DIALECT_IPACCESS);
+ dialect_modified = true;
}
- if (!strcmp(argv[1], "sgsn"))
- vtyvc->remote_end_is_sgsn = 1;
- else
- vtyvc->remote_end_is_sgsn = 0;
+ if (nse->ll != GPRS_NS2_LL_UDP) {
+ vty_out(vty, "Can not mix NS-VC with different link layer%s", VTY_NEWLINE);
+ goto err;
+ }
+
+ if (nse->dialect != GPRS_NS2_DIALECT_IPACCESS) {
+ vty_out(vty, "Can not mix NS-VC with different dialects%s", VTY_NEWLINE);
+ goto err;
+ }
+
+ if (osmo_sockaddr_str_from_str(&remote_str, argv[1], port)) {
+ vty_out(vty, "Can not parse IPv4/IPv6 or port.%s", VTY_NEWLINE);
+ goto err;
+ }
+
+ if (osmo_sockaddr_str_to_sockaddr(&remote_str, &remote.u.sas)) {
+ vty_out(vty, "Can not parse IPv4/IPv6 or port.%s", VTY_NEWLINE);
+ goto err;
+ }
+
+ bind = gprs_ns2_bind_by_name(vty_nsi, bind_name);
+ if (!bind) {
+ vty_out(vty, "Can not find bind with name %s%s",
+ bind_name, VTY_NEWLINE);
+ goto err;
+ }
+
+ if (bind->ll != GPRS_NS2_LL_UDP) {
+ vty_out(vty, "Bind %s is not an UDP bind.%s",
+ bind_name, VTY_NEWLINE);
+ goto err;
+ }
+
+ nsvc = gprs_ns2_ip_connect(bind, &remote, nse, nsvci);
+ if (!nsvc) {
+ vty_out(vty, "Can not create NS-VC.%s", VTY_NEWLINE);
+ goto err;
+ }
+ nsvc->persistent = true;
return CMD_SUCCESS;
+
+err:
+ if (ll_modified)
+ nse->ll = GPRS_NS2_LL_UNDEF;
+ if (dialect_modified)
+ ns2_nse_set_dialect(nse, GPRS_NS2_DIALECT_UNDEF);
+ return CMD_WARNING;
}
-DEFUN(cfg_no_nse, cfg_no_nse_cmd,
- "no nse <0-65535>",
- "Delete Persistent NS Entity\n"
- "Delete " NSE_CMD_STR)
+DEFUN(cfg_no_ns_nse_nsvc_ipa, cfg_no_ns_nse_nsvc_ipa_cmd,
+ "no nsvc ipa BIND " VTY_IPV46_CMD " <1-65535> nsvci <0-65535>",
+ NO_STR
+ "Delete a NS Virtual Connection\n"
+ "NS over UDP\n"
+ "A unique bind identifier created by ns bind\n"
+ "Remote IPv4 Address\n" "Remote IPv6 Address\n"
+ "Remote UDP Port\n"
+ NSVCI_STR
+ NSVCI_STR
+ )
{
- uint16_t nsei = atoi(argv[0]);
- struct ns2_vty_vc *vtyvc;
+ struct gprs_ns2_vc_bind *bind;
+ struct gprs_ns2_vc *nsvc;
+ struct gprs_ns2_nse *nse = vty->index;
+ const char *bind_name = argv[0];
+ struct osmo_sockaddr_str remote_str;
+ struct osmo_sockaddr remote;
+ uint16_t port = atoi(argv[2]);
+ uint16_t nsvci = atoi(argv[3]);
+
+ if (nse->ll != GPRS_NS2_LL_UDP) {
+ vty_out(vty, "This NSE doesn't support UDP.%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (nse->dialect != GPRS_NS2_DIALECT_IPACCESS) {
+ vty_out(vty, "This NSE doesn't support UDP with dialect ipaccess.%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ bind = gprs_ns2_bind_by_name(vty_nsi, bind_name);
+ if (!bind) {
+ vty_out(vty, "Can not find bind with name %s%s",
+ bind_name, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (bind->ll != GPRS_NS2_LL_UDP) {
+ vty_out(vty, "Bind %s is not an UDP bind.%s",
+ bind_name, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (osmo_sockaddr_str_from_str(&remote_str, argv[1], port)) {
+ vty_out(vty, "Can not parse IPv4/IPv6 or port.%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (osmo_sockaddr_str_to_sockaddr(&remote_str, &remote.u.sas)) {
+ vty_out(vty, "Can not parse IPv4/IPv6 or port.%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ nsvc = gprs_ns2_nsvc_by_sockaddr_bind(bind, &remote);
+ if (!nsvc) {
+ vty_out(vty, "Can not find NS-VC with remote %s:%u%s",
+ remote_str.ip, remote_str.port, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (!nsvc->persistent) {
+ vty_out(vty, "NS-VC with remote %s:%u is a dynamic NS-VC. Not configured by vty.%s",
+ remote_str.ip, remote_str.port, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (nsvc->nse != nse) {
+ vty_out(vty, "NS-VC is not part of this NSE!%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (!nsvc->nsvci_is_valid) {
+ vty_out(vty, "NS-VC doesn't have a nsvci!%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
- vtyvc = vtyvc_by_nsei(nsei, false);
- if (!vtyvc) {
- vty_out(vty, "The NSE %d does not exists.%s", nsei, VTY_NEWLINE);
+ if (nsvc->nsvci != nsvci) {
+ vty_out(vty, "NS-VC has a different nsvci (%u)!%s",
+ nsvc->nsvci, VTY_NEWLINE);
return CMD_WARNING;
}
- ns2_vc_free(vtyvc);
+ gprs_ns2_free_nsvc(nsvc);
+ if (llist_empty(&nse->nsvc)) {
+ nse->ll = GPRS_NS2_LL_UNDEF;
+ ns2_nse_set_dialect(nse, GPRS_NS2_DIALECT_UNDEF);
+ }
return CMD_SUCCESS;
}
-DEFUN(cfg_ns_timer, cfg_ns_timer_cmd,
- "timer " NS_TIMERS " <0-65535>",
- "Network Service Timer\n"
- NS_TIMERS_HELP "Timer Value\n")
+DEFUN(cfg_ns_nse_ip_sns_remote, cfg_ns_nse_ip_sns_remote_cmd,
+ "ip-sns-remote " VTY_IPV46_CMD " <1-65535>",
+ "SNS Initial Endpoint\n"
+ "SGSN IPv4 Address\n" "SGSN IPv6 Address\n"
+ "SGSN UDP Port\n"
+ )
{
- int idx = get_string_value(gprs_ns_timer_strs, argv[0]);
- int val = atoi(argv[1]);
+ struct gprs_ns2_nse *nse = vty->index;
+ bool dialect_modified = false;
+ bool ll_modified = false;
+ int rc;
+
+ /* argv[0] */
+ struct osmo_sockaddr_str remote_str;
+ struct osmo_sockaddr remote;
+ uint16_t port = atoi(argv[1]);
- if (idx < 0 || idx >= ARRAY_SIZE(vty_nsi->timeout))
+ if (nse->ll == GPRS_NS2_LL_UNDEF) {
+ nse->ll = GPRS_NS2_LL_UDP;
+ ll_modified = true;
+ }
+
+ if (nse->dialect == GPRS_NS2_DIALECT_UNDEF) {
+ if (ns2_nse_set_dialect(nse, GPRS_NS2_DIALECT_SNS) < 0)
+ goto err;
+ dialect_modified = true;
+ }
+
+ if (nse->ll != GPRS_NS2_LL_UDP) {
+ vty_out(vty, "Can not mix NS-VC with different link layer%s", VTY_NEWLINE);
+ goto err;
+ }
+
+ if (nse->dialect != GPRS_NS2_DIALECT_SNS) {
+ vty_out(vty, "Can not mix NS-VC with different dialects%s", VTY_NEWLINE);
+ goto err;
+ }
+
+ if (osmo_sockaddr_str_from_str(&remote_str, argv[0], port)) {
+ vty_out(vty, "Can not parse IPv4/IPv6 or port.%s", VTY_NEWLINE);
+ goto err;
+ }
+
+ if (osmo_sockaddr_str_to_sockaddr(&remote_str, &remote.u.sas)) {
+ vty_out(vty, "Can not parse IPv4/IPv6 or port.%s", VTY_NEWLINE);
+ goto err;
+ }
+
+ rc = gprs_ns2_sns_add_endpoint(nse, &remote);
+ switch (rc) {
+ case 0:
+ return CMD_SUCCESS;
+ case -EADDRINUSE:
+ vty_out(vty, "Specified SNS endpoint already part of the NSE.%s", VTY_NEWLINE);
return CMD_WARNING;
+ default:
+ vty_out(vty, "Can not add specified SNS endpoint.%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
- vty_nsi->timeout[idx] = val;
+err:
+ if (ll_modified)
+ nse->ll = GPRS_NS2_LL_UNDEF;
+ if (dialect_modified)
+ ns2_nse_set_dialect(nse, GPRS_NS2_DIALECT_UNDEF);
+ return CMD_WARNING;
+}
+
+DEFUN(cfg_no_ns_nse_ip_sns_remote, cfg_no_ns_nse_ip_sns_remote_cmd,
+ "no ip-sns-remote " VTY_IPV46_CMD " <1-65535>",
+ NO_STR
+ "Delete a SNS Initial Endpoint\n"
+ "SGSN IPv4 Address\n" "SGSN IPv6 Address\n"
+ "SGSN UDP Port\n"
+ )
+{
+ struct gprs_ns2_nse *nse = vty->index;
+ struct osmo_sockaddr_str remote_str; /* argv[0] */
+ struct osmo_sockaddr remote;
+ uint16_t port = atoi(argv[1]);
+
+ if (nse->ll != GPRS_NS2_LL_UDP) {
+ vty_out(vty, "This NSE doesn't support UDP.%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (nse->dialect != GPRS_NS2_DIALECT_SNS) {
+ vty_out(vty, "This NSE doesn't support UDP with dialect ip-sns.%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (osmo_sockaddr_str_from_str(&remote_str, argv[0], port)) {
+ vty_out(vty, "Can not parse IPv4/IPv6 or port.%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (osmo_sockaddr_str_to_sockaddr(&remote_str, &remote.u.sas)) {
+ vty_out(vty, "Can not parse IPv4/IPv6 or port.%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (gprs_ns2_sns_del_endpoint(nse, &remote)) {
+ vty_out(vty, "Can not remove specified SNS endpoint.%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (vty_nse_check_sns(nse)) {
+ /* there is still sns configuration valid */
+ return CMD_SUCCESS;
+ } else {
+ /* clean up nse to allow other nsvc commands */
+ ns2_nse_set_dialect(nse, GPRS_NS2_DIALECT_UNDEF);
+ nse->ll = GPRS_NS2_LL_UNDEF;
+ }
return CMD_SUCCESS;
}
-#define ENCAPS_STR "NS encapsulation options\n"
+/* add all IP-SNS default binds to the given NSE */
+int ns2_sns_add_sns_default_binds(struct gprs_ns2_nse *nse)
+{
+ struct vty_nse_bind *vnse_bind;
+ int count = 0;
+
+ OSMO_ASSERT(nse->ll == GPRS_NS2_LL_UDP);
+ OSMO_ASSERT(nse->dialect == GPRS_NS2_DIALECT_SNS);
-DEFUN(cfg_nsip_local_ip, cfg_nsip_local_ip_cmd,
- "encapsulation udp local-ip " VTY_IPV46_CMD,
- ENCAPS_STR "NS over UDP Encapsulation\n"
- "Set the IP address on which we listen for NS/UDP\n"
- "IPv4 Address\n"
- "IPv6 Address\n")
+ llist_for_each_entry(vnse_bind, &ip_sns_default_binds, list) {
+ struct gprs_ns2_vc_bind *bind = gprs_ns2_bind_by_name(vty_nsi, vnse_bind->vbind->name);
+ /* the bind might not yet created because "listen" is missing. */
+ if (!bind)
+ continue;
+ gprs_ns2_sns_add_bind(nse, bind);
+ count++;
+ }
+ return count;
+}
+
+DEFUN(cfg_ns_ip_sns_default_bind, cfg_ns_ip_sns_default_bind_cmd,
+ "ip-sns-default bind ID",
+ "Defaults for dynamically created NSEs created by IP-SNS in SGSN role\n"
+ "IP SNS binds\n"
+ "Name of NS udp bind whose IP endpoint will be used as IP-SNS local endpoint. Can be given multiple times.\n")
{
- osmo_sockaddr_str_from_str2(&priv.udp, argv[0]);
+ struct vty_bind *vbind;
+ struct vty_nse_bind *vnse_bind;
+ const char *name = argv[0];
+
+ vbind = vty_bind_by_name(name);
+ if (!vbind) {
+ vty_out(vty, "Can not find the given bind '%s'%s", name, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (vbind->ll != GPRS_NS2_LL_UDP) {
+ vty_out(vty, "ip-sns-default bind can only be used with UDP bind%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ llist_for_each_entry(vnse_bind, &ip_sns_default_binds, list) {
+ if (vnse_bind->vbind == vbind)
+ return CMD_SUCCESS;
+ }
+
+ vnse_bind = talloc(vty_nsi, struct vty_nse_bind);
+ if (!vnse_bind)
+ return CMD_WARNING;
+ vnse_bind->vbind = vbind;
+
+ llist_add_tail(&vnse_bind->list, &ip_sns_default_binds);
return CMD_SUCCESS;
}
-DEFUN(cfg_nsip_local_port, cfg_nsip_local_port_cmd,
- "encapsulation udp local-port <0-65535>",
- ENCAPS_STR "NS over UDP Encapsulation\n"
- "Set the UDP port on which we listen for NS/UDP\n"
- "UDP port number\n")
+DEFUN(cfg_no_ns_ip_sns_default_bind, cfg_no_ns_ip_sns_default_bind_cmd,
+ "no ip-sns-default bind ID",
+ NO_STR "Defaults for dynamically created NSEs created by IP-SNS in SGSN role\n"
+ "IP SNS binds\n"
+ "Name of NS udp bind whose IP endpoint will be removed as IP-SNS local endpoint.\n")
{
- unsigned int port = atoi(argv[0]);
+ struct vty_bind *vbind;
+ struct vty_nse_bind *vnse_bind;
+ const char *name = argv[0];
- priv.udp.port = port;
+ vbind = vty_bind_by_name(name);
+ if (!vbind) {
+ vty_out(vty, "Can not find the given bind '%s'%s", name, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
- return CMD_SUCCESS;
+ if (vbind->ll != GPRS_NS2_LL_UDP) {
+ vty_out(vty, "ip-sns-default bind can only be used with UDP bind%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ llist_for_each_entry(vnse_bind, &ip_sns_default_binds, list) {
+ if (vnse_bind->vbind == vbind) {
+ llist_del(&vnse_bind->list);
+ talloc_free(vnse_bind);
+ return CMD_SUCCESS;
+ }
+ }
+
+ vty_out(vty, "Bind '%s' was not an ip-sns-default bind%s", name, VTY_NEWLINE);
+ return CMD_WARNING;
}
-DEFUN(cfg_nsip_dscp, cfg_nsip_dscp_cmd,
- "encapsulation udp dscp <0-255>",
- ENCAPS_STR "NS over UDP Encapsulation\n"
- "Set DSCP/TOS on the UDP socket\n" "DSCP Value\n")
+DEFUN(cfg_ns_txqueue_max_length, cfg_ns_txqueue_max_length_cmd,
+ "txqueue-max-length <1-4096>",
+ "Set the maximum length of the txqueue (for UDP)\n"
+ "Maximum length of the txqueue\n")
{
- int dscp = atoi(argv[0]);
struct gprs_ns2_vc_bind *bind;
+ uint32_t max_length = atoi(argv[0]);
+ vty_nsi->txqueue_max_length = max_length;
- priv.dscp = dscp;
llist_for_each_entry(bind, &vty_nsi->binding, list) {
- if (gprs_ns2_is_ip_bind(bind))
- gprs_ns2_ip_bind_set_dscp(bind, dscp);
+ if (!gprs_ns2_is_ip_bind(bind))
+ continue;
+
+ ns2_ip_set_txqueue_max_length(bind, max_length);
}
+
return CMD_SUCCESS;
}
-DEFUN(cfg_nsip_res_block_unblock, cfg_nsip_res_block_unblock_cmd,
- "encapsulation udp use-reset-block-unblock (enabled|disabled)",
- ENCAPS_STR "NS over UDP Encapsulation\n"
- "Use NS-{RESET,BLOCK,UNBLOCK} procedures in violation of 3GPP TS 48.016\n"
- "Enable NS-{RESET,BLOCK,UNBLOCK}\n"
- "Disable NS-{RESET,BLOCK,UNBLOCK}\n")
+DEFUN(cfg_ns_nse_ip_sns_bind, cfg_ns_nse_ip_sns_bind_cmd,
+ "ip-sns-bind BINDID",
+ "IP SNS binds\n"
+ "Name of NS udp bind whose IP endpoint will be used as IP-SNS local endpoint. Can be given multiple times.\n")
{
- enum gprs_ns2_vc_mode vc_mode;
+ struct gprs_ns2_nse *nse = vty->index;
struct gprs_ns2_vc_bind *bind;
+ struct vty_bind *vbind;
+ struct vty_nse *vnse;
+ const char *name = argv[0];
+ bool ll_modified = false;
+ bool dialect_modified = false;
+ int rc;
+
+ if (nse->ll == GPRS_NS2_LL_UNDEF) {
+ nse->ll = GPRS_NS2_LL_UDP;
+ ll_modified = true;
+ }
+
+ if (nse->dialect == GPRS_NS2_DIALECT_UNDEF) {
+ if (ns2_nse_set_dialect(nse, GPRS_NS2_DIALECT_SNS) < 0)
+ goto err;
+ dialect_modified = true;
+ }
- if (!strcmp(argv[0], "enabled"))
- vc_mode = NS2_VC_MODE_BLOCKRESET;
+ if (nse->ll != GPRS_NS2_LL_UDP) {
+ vty_out(vty, "Can not mix NS-VC with different link layer%s", VTY_NEWLINE);
+ goto err;
+ }
+
+ if (nse->dialect != GPRS_NS2_DIALECT_SNS) {
+ vty_out(vty, "Can not mix NS-VC with different dialects%s", VTY_NEWLINE);
+ goto err;
+ }
+
+ vbind = vty_bind_by_name(name);
+ if (!vbind) {
+ vty_out(vty, "Can not find the given bind '%s'%s", name, VTY_NEWLINE);
+ goto err;
+ }
+
+ if (vbind->ll != GPRS_NS2_LL_UDP) {
+ vty_out(vty, "ip-sns-bind can only be used with UDP bind%s",
+ VTY_NEWLINE);
+ goto err;
+ }
+
+ /* the vnse has been created together when creating the nse node. The parent node should check this already! */
+ vnse = vty_nse_by_nsei(nse->nsei);
+ OSMO_ASSERT(vnse);
+
+ rc = vty_nse_add_vbind(vnse, vbind);
+ switch (rc) {
+ case 0:
+ break;
+ case -EALREADY:
+ vty_out(vty, "Failed to add ip-sns-bind %s already present%s", name, VTY_NEWLINE);
+ goto err;
+ case -ENOMEM:
+ vty_out(vty, "Failed to add ip-sns-bind %s out of memory%s", name, VTY_NEWLINE);
+ goto err;
+ default:
+ vty_out(vty, "Failed to add ip-sns-bind %s! %d%s", name, rc, VTY_NEWLINE);
+ goto err;
+ }
+
+ /* the bind might not yet created because "listen" is missing. */
+ bind = gprs_ns2_bind_by_name(vty_nsi, name);
+ if (!bind)
+ return CMD_SUCCESS;
+
+ rc = gprs_ns2_sns_add_bind(nse, bind);
+ switch (rc) {
+ case 0:
+ break;
+ case -EALREADY:
+ vty_out(vty, "Failed to add ip-sns-bind %s already present%s", name, VTY_NEWLINE);
+ goto err;
+ case -ENOMEM:
+ vty_out(vty, "Failed to add ip-sns-bind %s out of memory%s", name, VTY_NEWLINE);
+ goto err;
+ default:
+ vty_out(vty, "Failed to add ip-sns-bind %s! %d%s", name, rc, VTY_NEWLINE);
+ goto err;
+ }
+
+ return CMD_SUCCESS;
+err:
+ if (ll_modified)
+ nse->ll = GPRS_NS2_LL_UNDEF;
+ if (dialect_modified)
+ ns2_nse_set_dialect(nse, GPRS_NS2_DIALECT_UNDEF);
+
+ return CMD_WARNING;
+}
+
+DEFUN(cfg_no_ns_nse_ip_sns_bind, cfg_no_ns_nse_ip_sns_bind_cmd,
+ "no ip-sns-bind BINDID",
+ NO_STR
+ "IP SNS binds\n"
+ "Name of NS udp bind whose IP endpoint will not be used as IP-SNS local endpoint\n")
+{
+ struct gprs_ns2_nse *nse = vty->index;
+ struct gprs_ns2_vc_bind *bind;
+ struct vty_bind *vbind;
+ struct vty_nse *vnse;
+ const char *name = argv[0];
+ int rc;
+
+ if (nse->ll != GPRS_NS2_LL_UDP) {
+ vty_out(vty, "This NSE doesn't support UDP.%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (nse->dialect != GPRS_NS2_DIALECT_SNS) {
+ vty_out(vty, "This NSE doesn't support UDP with dialect ip-sns.%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ vbind = vty_bind_by_name(name);
+ if (!vbind) {
+ vty_out(vty, "Can not find the given bind '%s'%s", name, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (vbind->ll != GPRS_NS2_LL_UDP) {
+ vty_out(vty, "no ip-sns-bind can only be used with UDP bind%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ /* the vnse has been created together when creating the nse node. The parent node should check this already! */
+ vnse = vty_nse_by_nsei(nse->nsei);
+ OSMO_ASSERT(vnse);
+
+ rc = vty_nse_remove_vbind(vnse, vbind);
+ switch(rc) {
+ case 0:
+ break;
+ case -ENOENT:
+ vty_out(vty, "Bind %s is not part of this NSE%s", name, VTY_NEWLINE);
+ return CMD_WARNING;
+ case -EINVAL:
+ vty_out(vty, "no ip-sns-bind can only be used with UDP bind%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ default:
+ return CMD_WARNING;
+ }
+
+ /* the bind might not exists yet */
+ bind = gprs_ns2_bind_by_name(vty_nsi, name);
+ if (bind)
+ gprs_ns2_sns_del_bind(nse, bind);
+
+ if (!vty_nse_check_sns(nse)) {
+ /* clean up nse to allow other nsvc commands */
+ ns2_nse_set_dialect(nse, GPRS_NS2_DIALECT_UNDEF);
+ nse->ll = GPRS_NS2_LL_UNDEF;
+ }
+
+ return CMD_SUCCESS;
+}
+
+/* non-config commands */
+void ns2_vty_dump_nsvc(struct vty *vty, struct gprs_ns2_vc *nsvc, bool stats)
+{
+ if (nsvc->nsvci_is_valid)
+ vty_out(vty, " NSVCI %05u: %s %s %s %s %ssince ", nsvc->nsvci,
+ osmo_fsm_inst_state_name(nsvc->fi),
+ nsvc->persistent ? "PERSIST" : "DYNAMIC",
+ gprs_ns2_ll_str(nsvc),
+ ns2_vc_is_unblocked(nsvc) ? "ALIVE" : "DEAD",
+ nsvc->om_blocked ? "(blocked by O&M/vty) " :
+ !ns2_vc_is_unblocked(nsvc) ? "(cause: remote) " : "");
else
- vc_mode = NS2_VC_MODE_ALIVE;
+ vty_out(vty, " %s %s sig_weight=%u data_weight=%u %s %s %ssince ",
+ osmo_fsm_inst_state_name(nsvc->fi),
+ nsvc->persistent ? "PERSIST" : "DYNAMIC",
+ nsvc->sig_weight, nsvc->data_weight,
+ gprs_ns2_ll_str(nsvc),
+ ns2_vc_is_unblocked(nsvc) ? "ALIVE" : "DEAD",
+ !ns2_vc_is_unblocked(nsvc) ? "(cause: remote) " : "");
+
+ vty_out_uptime(vty, &nsvc->ts_alive_change);
+ vty_out_newline(vty);
+
+ if (stats) {
+ vty_out_rate_ctr_group(vty, " ", nsvc->ctrg);
+ vty_out_stat_item_group(vty, " ", nsvc->statg);
+ }
+}
+
+static void dump_nse(struct vty *vty, const struct gprs_ns2_nse *nse, bool stats, bool persistent_only)
+{
+ struct gprs_ns2_vc *nsvc;
+ unsigned int nsvcs = 0;
- if (priv.force_vc_mode) {
- if (priv.vc_mode != vc_mode)
- {
- vty_out(vty, "Ignoring use-reset-block because it's already set by %s.%s",
- priv.force_vc_mode_reason, VTY_NEWLINE);
+ if (persistent_only && !nse->persistent)
+ return;
+
+ vty_out(vty, "NSEI %05u: %s, %s since ", nse->nsei, gprs_ns2_lltype_str(nse->ll),
+ nse->alive ? "ALIVE" : "DEAD");
+ vty_out_uptime(vty, &nse->ts_alive_change);
+ vty_out_newline(vty);
+
+ ns2_sns_dump_vty(vty, " ", nse, stats);
+ llist_for_each_entry(nsvc, &nse->nsvc, list) {
+ nsvcs++;
+ }
+ vty_out(vty, " %u NS-VC:%s", nsvcs, VTY_NEWLINE);
+ llist_for_each_entry(nsvc, &nse->nsvc, list)
+ ns2_vty_dump_nsvc(vty, nsvc, stats);
+}
+
+static void dump_bind(struct vty *vty, const struct gprs_ns2_vc_bind *bind, bool stats)
+{
+ if (bind->dump_vty)
+ bind->dump_vty(bind, vty, stats);
+
+ if (stats) {
+ vty_out_stat_item_group(vty, " ", bind->statg);
+ }
+}
+
+static void dump_ns_bind(struct vty *vty, const struct gprs_ns2_inst *nsi, bool stats)
+{
+ struct gprs_ns2_vc_bind *bind;
+
+ llist_for_each_entry(bind, &nsi->binding, list) {
+ dump_bind(vty, bind, stats);
+ }
+}
+
+
+static void dump_ns_entities(struct vty *vty, const struct gprs_ns2_inst *nsi, bool stats, bool persistent_only)
+{
+ struct gprs_ns2_nse *nse;
+
+ llist_for_each_entry(nse, &nsi->nse, list) {
+ dump_nse(vty, nse, stats, persistent_only);
+ }
+}
+
+/* Backwards compatibility, among other things for the TestVTYGbproxy which expects
+ * 'show ns' to output something about binds */
+DEFUN_HIDDEN(show_ns, show_ns_cmd, "show ns",
+ SHOW_STR SHOW_NS_STR)
+{
+ dump_ns_entities(vty, vty_nsi, false, false);
+ dump_ns_bind(vty, vty_nsi, false);
+ if (vty_fr_network && llist_count(&vty_fr_network->links))
+ osmo_fr_network_dump_vty(vty, vty_fr_network);
+ return CMD_SUCCESS;
+}
+
+
+DEFUN(show_ns_binds, show_ns_binds_cmd, "show ns binds [stats]",
+ SHOW_STR SHOW_NS_STR
+ "Display information about the NS protocol binds\n"
+ "Include statistic\n")
+{
+ bool stats = false;
+ if (argc > 0)
+ stats = true;
+
+ dump_ns_bind(vty, vty_nsi, stats);
+ return CMD_SUCCESS;
+}
+
+DEFUN(show_ns_entities, show_ns_entities_cmd, "show ns entities [stats]",
+ SHOW_STR SHOW_NS_STR
+ "Display information about the NS protocol entities (NSEs)\n"
+ "Include statistics\n")
+{
+ bool stats = false;
+ if (argc > 0)
+ stats = true;
+
+ dump_ns_entities(vty, vty_nsi, stats, false);
+ return CMD_SUCCESS;
+}
+
+DEFUN(show_ns_pers, show_ns_pers_cmd, "show ns persistent",
+ SHOW_STR SHOW_NS_STR
+ "Show only persistent NS\n")
+{
+ dump_ns_entities(vty, vty_nsi, true, true);
+ return CMD_SUCCESS;
+}
+
+DEFUN(show_nse, show_nse_cmd, "show ns (nsei|nsvc) <0-65535> [stats]",
+ SHOW_STR SHOW_NS_STR
+ "Select one NSE by its NSE Identifier\n"
+ "Select one NSE by its NS-VC Identifier\n"
+ "The Identifier of selected type\n"
+ "Include Statistics\n")
+{
+ struct gprs_ns2_inst *nsi = vty_nsi;
+ struct gprs_ns2_nse *nse;
+ struct gprs_ns2_vc *nsvc;
+ uint16_t id = atoi(argv[1]);
+ bool show_stats = false;
+
+ if (argc >= 3)
+ show_stats = true;
+
+ if (!strcmp(argv[0], "nsei")) {
+ nse = gprs_ns2_nse_by_nsei(nsi, id);
+ if (!nse) {
return CMD_WARNING;
}
- return CMD_SUCCESS;
+ dump_nse(vty, nse, show_stats, false);
+ } else {
+ nsvc = gprs_ns2_nsvc_by_nsvci(nsi, id);
+
+ if (!nsvc) {
+ vty_out(vty, "No such NS Entity%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ ns2_vty_dump_nsvc(vty, nsvc, show_stats);
}
- priv.vc_mode = vc_mode;
+ return CMD_SUCCESS;
+}
+
+static int nsvc_force_unconf_cb(struct gprs_ns2_vc *nsvc, void *ctx)
+{
+ ns2_vc_force_unconfigured(nsvc);
+ ns2_vc_fsm_start(nsvc);
+ return 0;
+}
- llist_for_each_entry(bind, &vty_nsi->binding, list) {
- gprs_ns2_bind_set_mode(bind, priv.vc_mode);
+DEFUN_HIDDEN(nsvc_force_unconf, nsvc_force_unconf_cmd,
+ "nsvc nsei <0-65535> force-unconfigured",
+ "NS Virtual Connection\n"
+ "The NSEI\n"
+ "Reset the NSVCs back to initial state\n"
+ )
+{
+ struct gprs_ns2_inst *nsi = vty_nsi;
+ struct gprs_ns2_nse *nse;
+
+ uint16_t id = atoi(argv[0]);
+
+ nse = gprs_ns2_nse_by_nsei(nsi, id);
+ if (!nse) {
+ vty_out(vty, "Could not find NSE for NSEI %u%s", id, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (!nse->persistent) {
+ gprs_ns2_free_nse(nse);
+ } else if (nse->dialect == GPRS_NS2_DIALECT_SNS) {
+ gprs_ns2_free_nsvcs(nse);
+ } else {
+ /* Perform the operation for all nsvc */
+ gprs_ns2_nse_foreach_nsvc(nse, nsvc_force_unconf_cb, NULL);
}
return CMD_SUCCESS;
}
-DEFUN(cfg_frgre_local_ip, cfg_frgre_local_ip_cmd,
- "encapsulation framerelay-gre local-ip " VTY_IPV46_CMD,
- ENCAPS_STR "NS over Frame Relay over GRE Encapsulation\n"
- "Set the IP address on which we listen for NS/FR/GRE\n"
- "IPv4 Address\n"
- "IPv6 Address\n")
+DEFUN(nse_restart_sns, nse_restart_sns_cmd,
+ "nse <0-65535> restart-sns",
+ "NSE specific commands\n"
+ "NS Entity ID (NSEI)\n"
+ "Restart SNS procedure\n")
{
- osmo_sockaddr_str_from_str2(&priv.frgreaddr, argv[0]);
+ struct gprs_ns2_inst *nsi = vty_nsi;
+ struct gprs_ns2_nse *nse;
+ uint16_t id = atoi(argv[0]);
+ nse = gprs_ns2_nse_by_nsei(nsi, id);
+ if (!nse) {
+ vty_out(vty, "Could not find NSE for NSEI %u%s", id, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (nse->dialect != GPRS_NS2_DIALECT_SNS) {
+ vty_out(vty, "Given NSEI %u doesn't use IP-SNS%s", id, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ gprs_ns2_free_nsvcs(nse);
return CMD_SUCCESS;
}
-DEFUN(cfg_frgre_enable, cfg_frgre_enable_cmd,
- "encapsulation framerelay-gre enabled (1|0)",
- ENCAPS_STR "NS over Frame Relay over GRE Encapsulation\n"
- "Enable or disable Frame Relay over GRE\n"
- "Enable\n" "Disable\n")
+DEFUN(nsvc_block, nsvc_block_cmd,
+ "nsvc <0-65535> (block|unblock|reset)",
+ "NS Virtual Connection\n"
+ NSVCI_STR
+ "Block a NSVC. As cause code O&M intervention will be used.\n"
+ "Unblock a NSVC. As cause code O&M intervention will be used.\n"
+ "Reset a NSVC. As cause code O&M intervention will be used.\n")
{
- int enabled = atoi(argv[0]);
+ struct gprs_ns2_inst *nsi = vty_nsi;
+ struct gprs_ns2_vc *nsvc;
+ int rc;
+
+ uint16_t id = atoi(argv[0]);
- priv.frgre = enabled;
+ nsvc = gprs_ns2_nsvc_by_nsvci(nsi, id);
+ if (!nsvc) {
+ vty_out(vty, "Could not find NSVCI %05u%s", id, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (!strcmp(argv[1], "block")) {
+ rc = ns2_vc_block(nsvc);
+ switch (rc) {
+ case 0:
+ vty_out(vty, "The NS-VC %05u will be blocked.%s", id, VTY_NEWLINE);
+ return CMD_SUCCESS;
+ case -EALREADY:
+ vty_out(vty, "The NS-VC %05u is already blocked.%s", id, VTY_NEWLINE);
+ return CMD_ERR_NOTHING_TODO;
+ default:
+ vty_out(vty, "An unknown error %d happend on NS-VC %05u.%s", rc, id, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ } else if (!strcmp(argv[1], "unblock")) {
+ rc = ns2_vc_unblock(nsvc);
+ switch (rc) {
+ case 0:
+ vty_out(vty, "The NS-VC %05u will be unblocked.%s", id, VTY_NEWLINE);
+ return CMD_SUCCESS;
+ case -EALREADY:
+ vty_out(vty, "The NS-VC %05u is already unblocked.%s", id, VTY_NEWLINE);
+ return CMD_ERR_NOTHING_TODO;
+ default:
+ vty_out(vty, "An unknown error %d happend on NS-VC %05u.%s", rc, id, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ } else {
+ ns2_vc_reset(nsvc);
+ vty_out(vty, "The NS-VC %05u has been resetted.%s", id, VTY_NEWLINE);
+ }
return CMD_SUCCESS;
}
-/* TODO: allow vty to reset/block/unblock nsvc/nsei */
+static void log_set_nse_filter(struct log_target *target,
+ struct gprs_ns2_nse *nse)
+{
+ if (nse) {
+ target->filter_map |= (1 << LOG_FLT_GB_NSE);
+ target->filter_data[LOG_FLT_GB_NSE] = nse;
+ } else if (target->filter_data[LOG_FLT_GB_NSE]) {
+ target->filter_map = ~(1 << LOG_FLT_GB_NSE);
+ target->filter_data[LOG_FLT_GB_NSE] = NULL;
+ }
+}
+
+static void log_set_nsvc_filter(struct log_target *target,
+ struct gprs_ns2_vc *nsvc)
+{
+ if (nsvc) {
+ target->filter_map |= (1 << LOG_FLT_GB_NSVC);
+ target->filter_data[LOG_FLT_GB_NSVC] = nsvc;
+ } else if (target->filter_data[LOG_FLT_GB_NSVC]) {
+ target->filter_map = ~(1 << LOG_FLT_GB_NSVC);
+ target->filter_data[LOG_FLT_GB_NSVC] = NULL;
+ }
+}
+
+DEFUN(logging_fltr_nse,
+ logging_fltr_nse_cmd,
+ "logging filter nse nsei <0-65535>",
+ LOGGING_STR FILTER_STR
+ "Filter based on NS Entity\n"
+ "Identify NSE by NSEI\n"
+ "Numeric identifier\n")
+{
+ struct log_target *tgt;
+ struct gprs_ns2_nse *nse;
+ uint16_t id = atoi(argv[0]);
+
+ log_tgt_mutex_lock();
+ tgt = osmo_log_vty2tgt(vty);
+ if (!tgt) {
+ log_tgt_mutex_unlock();
+ return CMD_WARNING;
+ }
+
+ nse = gprs_ns2_nse_by_nsei(vty_nsi, id);
+ if (!nse) {
+ vty_out(vty, "No NSE by that identifier%s", VTY_NEWLINE);
+ log_tgt_mutex_unlock();
+ return CMD_WARNING;
+ }
+
+ log_set_nse_filter(tgt, nse);
+ log_tgt_mutex_unlock();
+ return CMD_SUCCESS;
+}
-/* TODO: add filter for NSEI as ns1 code does */
/* TODO: add filter for single connection by description */
DEFUN(logging_fltr_nsvc,
logging_fltr_nsvc_cmd,
@@ -679,7 +2242,7 @@ DEFUN(logging_fltr_nsvc,
{
struct log_target *tgt;
struct gprs_ns2_vc *nsvc;
- uint16_t id = atoi(argv[1]);
+ uint16_t id = atoi(argv[0]);
log_tgt_mutex_lock();
tgt = osmo_log_vty2tgt(vty);
@@ -700,155 +2263,89 @@ DEFUN(logging_fltr_nsvc,
return CMD_SUCCESS;
}
-/**
- * gprs_ns2_vty_init initialize the vty
- * \param[inout] nsi
- * \param[in] default_bind set the default address to bind to. Can be NULL.
- * \return 0 on success
+/*! initialized a reduced vty interface which excludes the configuration nodes besides timeouts.
+ * This can be used by the PCU which can be only configured by the BTS/BSC and not by the vty.
+ * \param[in] nsi NS instance on which we operate
+ * \return 0 on success.
*/
-int gprs_ns2_vty_init(struct gprs_ns2_inst *nsi,
- const struct osmo_sockaddr_str *default_bind)
+int gprs_ns2_vty_init_reduced(struct gprs_ns2_inst *nsi)
{
- static bool vty_elements_installed = false;
-
vty_nsi = nsi;
- memset(&priv, 0, sizeof(struct ns2_vty_priv));
- INIT_LLIST_HEAD(&priv.vtyvc);
- priv.vc_mode = NS2_VC_MODE_BLOCKRESET;
- if (default_bind)
- memcpy(&priv.udp, default_bind, sizeof(*default_bind));
-
- /* Regression test code may call this function repeatedly, so make sure
- * that VTY elements are not duplicated, which would assert. */
- if (vty_elements_installed)
- return 0;
- vty_elements_installed = true;
+ INIT_LLIST_HEAD(&binds);
+ INIT_LLIST_HEAD(&nses);
+ INIT_LLIST_HEAD(&ip_sns_default_binds);
+
+ vty_fr_network = osmo_fr_network_alloc(nsi);
+ if (!vty_fr_network)
+ return -ENOMEM;
install_lib_element_ve(&show_ns_cmd);
- install_lib_element_ve(&show_ns_stats_cmd);
+ install_lib_element_ve(&show_ns_binds_cmd);
+ install_lib_element_ve(&show_ns_entities_cmd);
install_lib_element_ve(&show_ns_pers_cmd);
install_lib_element_ve(&show_nse_cmd);
+ install_lib_element_ve(&logging_fltr_nse_cmd);
install_lib_element_ve(&logging_fltr_nsvc_cmd);
install_lib_element(ENABLE_NODE, &nsvc_force_unconf_cmd);
+ install_lib_element(ENABLE_NODE, &nsvc_block_cmd);
+ install_lib_element(ENABLE_NODE, &nse_restart_sns_cmd);
+ install_lib_element(CFG_LOG_NODE, &logging_fltr_nse_cmd);
install_lib_element(CFG_LOG_NODE, &logging_fltr_nsvc_cmd);
install_lib_element(CONFIG_NODE, &cfg_ns_cmd);
+
install_node(&ns_node, config_write_ns);
- install_lib_element(L_NS_NODE, &cfg_nse_nsvci_cmd);
- install_lib_element(L_NS_NODE, &cfg_nse_remoteip_cmd);
- install_lib_element(L_NS_NODE, &cfg_nse_remoteport_cmd);
- install_lib_element(L_NS_NODE, &cfg_nse_fr_dlci_cmd);
- install_lib_element(L_NS_NODE, &cfg_nse_encaps_cmd);
- install_lib_element(L_NS_NODE, &cfg_nse_remoterole_cmd);
- install_lib_element(L_NS_NODE, &cfg_no_nse_cmd);
+ /* TODO: convert into osmo timer */
install_lib_element(L_NS_NODE, &cfg_ns_timer_cmd);
- install_lib_element(L_NS_NODE, &cfg_nsip_local_ip_cmd);
- install_lib_element(L_NS_NODE, &cfg_nsip_local_port_cmd);
- install_lib_element(L_NS_NODE, &cfg_nsip_dscp_cmd);
- install_lib_element(L_NS_NODE, &cfg_nsip_res_block_unblock_cmd);
- install_lib_element(L_NS_NODE, &cfg_frgre_enable_cmd);
- install_lib_element(L_NS_NODE, &cfg_frgre_local_ip_cmd);
-
- /* TODO: nsvc/nsei command to reset states or reset/block/unblock nsei/nsvcs */
return 0;
}
-/*!
- * \brief gprs_ns2_vty_create parse the vty tree into ns nodes
- * It has to be in different steps to ensure the bind is created before creating VCs.
- * \return 0 on success
- */
-int gprs_ns2_vty_create() {
- struct ns2_vty_vc *vtyvc;
- struct gprs_ns2_vc_bind *bind;
- struct gprs_ns2_nse *nse;
- struct gprs_ns2_vc *nsvc;
- struct osmo_sockaddr sockaddr;
-
- if (!vty_nsi)
- return -1;
-
- /* create binds, only support a single bind. either FR or UDP */
- if (priv.frgre) {
- /* TODO not yet supported !*/
- return -1;
- } else {
- /* UDP */
- osmo_sockaddr_str_to_sockaddr(&priv.udp, &sockaddr.u.sas);
- if (gprs_ns2_ip_bind(vty_nsi, &sockaddr, priv.dscp, &bind)) {
- /* TODO: could not bind on the specific address */
- return -1;
- }
- gprs_ns2_bind_set_mode(bind, priv.vc_mode);
- }
-
- /* create vcs */
- llist_for_each_entry(vtyvc, &priv.vtyvc, list) {
- if (strlen(vtyvc->remote.ip) == 0) {
- /* Invalid IP for VC */
- continue;
- }
-
- if (!vtyvc->remote.port) {
- /* Invalid port for VC */
- continue;
- }
-
- if (osmo_sockaddr_str_to_sockaddr(&vtyvc->remote, &sockaddr.u.sas)) {
- /* Invalid sockaddr for VC */
- continue;
- }
-
- nse = gprs_ns2_nse_by_nsei(vty_nsi, vtyvc->nsei);
- if (!nse) {
- nse = gprs_ns2_create_nse(vty_nsi, vtyvc->nsei);
- if (!nse) {
- /* Could not create NSE for VTY */
- continue;
- }
- }
- nse->persistent = true;
-
- nsvc = gprs_ns2_ip_connect(bind,
- &sockaddr,
- nse,
- vtyvc->nsvci);
- if (!nsvc) {
- /* Could not create NSVC, connect failed */
- continue;
- }
- nsvc->persistent = true;
- }
-
-
- return 0;
-}
-
-/*!
- * \brief ns2_vty_bind_apply will be called when a new bind is created to apply vty settings
- * \param bind
- * \return
- */
-void ns2_vty_bind_apply(struct gprs_ns2_vc_bind *bind)
+int gprs_ns2_vty_init(struct gprs_ns2_inst *nsi)
{
- gprs_ns2_bind_set_mode(bind, priv.vc_mode);
-}
-
-/*!
- * \brief ns2_vty_force_vc_mode force a mode and prevents the vty from overwriting it.
- * \param force if true mode and reason will be set. false to allow modification via vty.
- * \param mode
- * \param reason A description shown to the user when a vty command wants to change the mode.
- */
-void gprs_ns2_vty_force_vc_mode(bool force, enum gprs_ns2_vc_mode mode, const char *reason)
-{
- priv.force_vc_mode = force;
+ int rc = gprs_ns2_vty_init_reduced(nsi);
+ if (rc)
+ return rc;
+
+ install_lib_element(L_NS_NODE, &cfg_ns_nsei_cmd);
+ install_lib_element(L_NS_NODE, &cfg_no_ns_nsei_cmd);
+ install_lib_element(L_NS_NODE, &cfg_ns_bind_cmd);
+ install_lib_element(L_NS_NODE, &cfg_no_ns_bind_cmd);
+
+ install_lib_element(L_NS_NODE, &cfg_ns_ip_sns_default_bind_cmd);
+ install_lib_element(L_NS_NODE, &cfg_no_ns_ip_sns_default_bind_cmd);
+
+ install_lib_element(L_NS_NODE, &cfg_ns_txqueue_max_length_cmd);
+
+ install_node(&ns_bind_node, NULL);
+ install_lib_element(L_NS_BIND_NODE, &cfg_ns_bind_listen_cmd);
+ install_lib_element(L_NS_BIND_NODE, &cfg_no_ns_bind_listen_cmd);
+ install_lib_element(L_NS_BIND_NODE, &cfg_ns_bind_dscp_cmd);
+ install_lib_element(L_NS_BIND_NODE, &cfg_no_ns_bind_dscp_cmd);
+ install_lib_element(L_NS_BIND_NODE, &cfg_ns_bind_priority_cmd);
+ install_lib_element(L_NS_BIND_NODE, &cfg_ns_bind_ip_sns_weight_cmd);
+ install_lib_element(L_NS_BIND_NODE, &cfg_ns_bind_ipaccess_cmd);
+ install_lib_element(L_NS_BIND_NODE, &cfg_no_ns_bind_ipaccess_cmd);
+ install_lib_element(L_NS_BIND_NODE, &cfg_ns_bind_fr_cmd);
+ install_lib_element(L_NS_BIND_NODE, &cfg_no_ns_bind_fr_cmd);
+ install_lib_element(L_NS_BIND_NODE, &cfg_ns_bind_accept_sns_cmd);
+ install_lib_element(L_NS_BIND_NODE, &cfg_no_ns_bind_accept_sns_cmd);
+
+ install_node(&ns_nse_node, NULL);
+ install_lib_element(L_NS_NSE_NODE, &cfg_ns_nse_nsvc_fr_cmd);
+ install_lib_element(L_NS_NSE_NODE, &cfg_no_ns_nse_nsvci_cmd);
+ install_lib_element(L_NS_NSE_NODE, &cfg_no_ns_nse_nsvc_fr_dlci_cmd);
+ install_lib_element(L_NS_NSE_NODE, &cfg_ns_nse_nsvc_udp_cmd);
+ install_lib_element(L_NS_NSE_NODE, &cfg_ns_nse_nsvc_udp_weights_cmd);
+ install_lib_element(L_NS_NSE_NODE, &cfg_no_ns_nse_nsvc_udp_cmd);
+ install_lib_element(L_NS_NSE_NODE, &cfg_ns_nse_nsvc_ipa_cmd);
+ install_lib_element(L_NS_NSE_NODE, &cfg_no_ns_nse_nsvc_ipa_cmd);
+ install_lib_element(L_NS_NSE_NODE, &cfg_ns_nse_ip_sns_remote_cmd);
+ install_lib_element(L_NS_NSE_NODE, &cfg_no_ns_nse_ip_sns_remote_cmd);
+ install_lib_element(L_NS_NSE_NODE, &cfg_ns_nse_ip_sns_bind_cmd);
+ install_lib_element(L_NS_NSE_NODE, &cfg_no_ns_nse_ip_sns_bind_cmd);
- if (force) {
- priv.vc_mode = mode;
- priv.force_vc_mode_reason = reason;
- }
+ return 0;
}
diff --git a/src/gb/libosmogb.map b/src/gb/libosmogb.map
index 72437abb..e5a5c8fd 100644
--- a/src/gb/libosmogb.map
+++ b/src/gb/libosmogb.map
@@ -2,7 +2,25 @@ LIBOSMOGB_1.0 {
global:
bssgp_cause_str;
bssgp_create_cell_id;
+bssgp_create_rim_ri;
+bssgp_dec_app_err_cont_nacc;
+bssgp_dec_ran_inf_ack_rim_cont;
+bssgp_dec_ran_inf_err_rim_cont;
+bssgp_dec_ran_inf_req_app_cont_nacc;
+bssgp_dec_ran_inf_req_rim_cont;
+bssgp_dec_ran_inf_app_cont_nacc;
+bssgp_dec_ran_inf_app_err_rim_cont;
+bssgp_dec_ran_inf_rim_cont;
bssgp_pdu_str;
+bssgp_enc_app_err_cont_nacc;
+bssgp_enc_ran_inf_ack_rim_cont;
+bssgp_enc_ran_inf_err_rim_cont;
+bssgp_enc_ran_inf_req_app_cont_nacc;
+bssgp_enc_ran_inf_req_rim_cont;
+bssgp_enc_ran_inf_app_cont_nacc;
+bssgp_enc_ran_inf_app_err_rim_cont;
+bssgp_enc_ran_inf_rim_cont;
+bssgp_encode_rim_pdu;
bssgp_fc_in;
bssgp_fc_init;
bssgp_fc_ms_init;
@@ -12,7 +30,15 @@ bssgp_msgb_alloc;
bssgp_msgb_copy;
bssgp_msgb_tlli_put;
bssgp_msgb_ra_put;
+bssgp_nacc_cause_strs;
bssgp_parse_cell_id;
+bssgp_parse_rim_pdu;
+bssgp_parse_rim_ri;
+bssgp_parse_rim_ra;
+bssgp_ran_inf_app_id_strs;
+bssgp_rim_routing_info_discr_strs;
+bssgp_rim_ri_name_buf;
+bssgp_rim_ri_name;
bssgp_set_bssgp_callback;
bssgp_tx_bvc_block;
bssgp_tx_bvc_reset;
@@ -30,6 +56,8 @@ bssgp_tx_radio_status_tmsi;
bssgp_tx_resume;
bssgp_tx_resume_ack;
bssgp_tx_resume_nack;
+bssgp_tx_rim;
+bssgp_tx_rim_encoded;
bssgp_tx_simple_bvci;
bssgp_tx_status;
bssgp_tx_suspend;
@@ -45,6 +73,45 @@ bssgp_tx_paging;
bssgp_vty_init;
bssgp_nsi;
+bssgp2_nsi_tx_ptp;
+bssgp2_nsi_tx_sig;
+bssgp2_dec_fc_bvc;
+bssgp2_dec_fc_ms;
+bssgp2_enc_bvc_block;
+bssgp2_enc_bvc_block_ack;
+bssgp2_enc_bvc_unblock;
+bssgp2_enc_bvc_unblock_ack;
+bssgp2_enc_bvc_reset;
+bssgp2_enc_bvc_reset_ack;
+bssgp2_enc_fc_bvc;
+bssgp2_enc_fc_bvc_ack;
+bssgp2_enc_fc_ms;
+bssgp2_enc_fc_ms_ack;
+bssgp2_enc_flush_ll;
+bssgp2_enc_status;
+
+bssgp_bvc_fsm_alloc_sig_bss;
+bssgp_bvc_fsm_alloc_ptp_bss;
+bssgp_bvc_fsm_alloc_sig_sgsn;
+bssgp_bvc_fsm_alloc_ptp_sgsn;
+bssgp_bvc_fsm_set_ops;
+bssgp_bvc_fsm_is_unblocked;
+bssgp_bvc_fsm_get_block_cause;
+bssgp_bvc_fsm_get_features_advertised;
+bssgp_bvc_fsm_get_features_received;
+bssgp_bvc_fsm_get_features_negotiated;
+bssgp_bvc_fsm_set_max_pdu_len;
+bssgp_bvc_fsm_get_max_pdu_len;
+
+osmo_fr_network_alloc;
+osmo_fr_network_free;
+osmo_fr_link_alloc;
+osmo_fr_link_free;
+osmo_fr_dlc_alloc;
+osmo_fr_rx;
+osmo_fr_tx_dlc;
+osmo_fr_role_names;
+
gprs_ns_signal_ns_names;
gprs_ns_pdu_strings;
gprs_ns_cause_str;
@@ -74,10 +141,10 @@ gprs_ns_ll_clear;
gprs_ns_msgb_alloc;
gprs_ns2_aff_cause_prim_strs;
-gprs_ns2_bind_set_mode;
+gprs_ns2_bind_by_name;
gprs_ns2_cause_strs;
gprs_ns2_create_nse;
-gprs_ns2_dynamic_create_nse;
+gprs_ns2_create_nse2;
gprs_ns2_find_vc_by_sockaddr;
gprs_ns2_free;
gprs_ns2_free_bind;
@@ -85,16 +152,26 @@ gprs_ns2_free_binds;
gprs_ns2_free_nse;
gprs_ns2_free_nses;
gprs_ns2_free_nsvc;
+gprs_ns2_free_nsvcs;
gprs_ns2_frgre_bind;
+gprs_ns2_fr_bind;
+gprs_ns2_fr_bind_netif;
+gprs_ns2_fr_bind_by_netif;
+gprs_ns2_fr_connect;
+gprs_ns2_fr_nsvc_by_dlci;
+gprs_ns2_fr_nsvc_dlci;
+gprs_ns2_is_fr_bind;
+gprs_ns2_find_vc_by_dlci;
gprs_ns2_instantiate;
gprs_ns2_ip_bind;
gprs_ns2_ip_bind_by_sockaddr;
gprs_ns2_ip_bind_set_dscp;
+gprs_ns2_ip_bind_set_priority;
+gprs_ns2_ip_bind_set_sns_weight;
gprs_ns2_ip_bind_sockaddr;
gprs_ns2_ip_connect;
gprs_ns2_ip_connect2;
gprs_ns2_ip_connect_inactive;
-gprs_ns2_ip_connect_sns;
gprs_ns2_ip_vc_local;
gprs_ns2_ip_vc_remote;
gprs_ns2_ip_vc_equal;
@@ -103,6 +180,7 @@ gprs_ns2_is_ip_bind;
gprs_ns2_ll_str;
gprs_ns2_ll_str_buf;
gprs_ns2_ll_str_c;
+gprs_ns2_lltype_strs;
gprs_ns2_nse_by_nsei;
gprs_ns2_nse_foreach_nsvc;
gprs_ns2_nse_nsei;
@@ -114,9 +192,12 @@ gprs_ns2_prim_strs;
gprs_ns2_recv_prim;
gprs_ns2_reset_persistent_nsvcs;
gprs_ns2_start_alive_all_nsvcs;
-gprs_ns2_vty_create;
-gprs_ns2_vty_force_vc_mode;
+gprs_ns2_sns_add_bind;
+gprs_ns2_sns_add_endpoint;
+gprs_ns2_sns_del_bind;
+gprs_ns2_sns_del_endpoint;
gprs_ns2_vty_init;
+gprs_ns2_vty_init_reduced;
gprs_nsvc_create2;
gprs_nsvc_delete;
@@ -132,5 +213,7 @@ bssgp_bvc_ctx_free;
btsctx_by_bvci_nsei;
btsctx_by_raid_cid;
+osmo_pdef_bssgp;
+
local: *;
};
diff --git a/src/gsm/Makefile.am b/src/gsm/Makefile.am
index ccb2456f..e6b687d2 100644
--- a/src/gsm/Makefile.am
+++ b/src/gsm/Makefile.am
@@ -1,10 +1,10 @@
# 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 modifications: https://www.gnu.org/software/libtool/manual/html_node/Versioning.html
-LIBVERSION=15:0:0
+LIBVERSION=20:0:0
-AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_builddir)/include $(TALLOC_CFLAGS)
-AM_CFLAGS = -Wall ${GCC_FVISIBILITY_HIDDEN}
+AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_builddir)/include
+AM_CFLAGS = -Wall $(TALLOC_CFLAGS)
if ENABLE_PSEUDOTALLOC
AM_CPPFLAGS += -I$(top_srcdir)/src/pseudotalloc
@@ -15,6 +15,8 @@ noinst_HEADERS = milenage/aes.h milenage/aes_i.h milenage/aes_wrap.h \
milenage/common.h milenage/crypto.h milenage/includes.h \
milenage/milenage.h
+noinst_HEADERS += tuak/KeccakP-1600-3gpp.h tuak/tuak.h
+
noinst_LTLIBRARIES = libgsmint.la
lib_LTLIBRARIES = libosmogsm.la
@@ -25,28 +27,38 @@ libgsmint_la_SOURCES = a5.c rxlev_stat.c tlv_parser.c comp128.c comp128v23.c \
gsm48_ie.c gsm0808.c sysinfo.c \
gprs_cipher_core.c gprs_rlc.c gsm0480.c abis_nm.c gsm0502.c \
gsm0411_utils.c gsm0411_smc.c gsm0411_smr.c gsm0414.c \
- lapd_core.c lapdm.c kasumi.c gsm29205.c gsm_04_08_gprs.c \
- auth_core.c auth_comp128v1.c auth_comp128v23.c auth_xor.c \
+ lapdm.c kasumi.c gsm29205.c gsm_04_08_gprs.c \
+ auth_core.c auth_comp128v1.c auth_comp128v23.c auth_xor.c auth_xor_2g.c \
auth_milenage.c milenage/aes-encblock.c gea.c \
milenage/aes-internal.c milenage/aes-internal-enc.c \
milenage/milenage.c gan.c ipa.c gsm0341.c apn.c \
- gsup.c gsup_sms.c gprs_gea.c gsm0503_conv.c oap.c gsm0808_utils.c \
+ tuak/KeccakP-1600-3gpp.c tuak/tuak.c auth_tuak.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
+ gsm29118.c gsm48_rest_octets.c cbsp.c gsm48049.c \
+ gad.c bsslap.c bssmap_le.c kdf.c iuup.c gsm44021.c gsm44068.c rlp.c
+if !EMBEDDED
+libgsmint_la_SOURCES += gsup.c gsup_sms.c
+endif # !EMBEDDED
+
libgsmint_la_LDFLAGS = -no-undefined
-libgsmint_la_LIBADD = $(top_builddir)/src/libosmocore.la
+libgsmint_la_LIBADD = $(top_builddir)/src/core/libosmocore.la $(top_builddir)/src/isdn/libosmoisdn.la
libosmogsm_la_SOURCES =
libosmogsm_la_LDFLAGS = $(LTLDFLAGS_OSMOGSM) -version-info $(LIBVERSION) -no-undefined
libosmogsm_la_LIBADD = libgsmint.la $(TALLOC_LIBS)
if ENABLE_GNUTLS
-AM_CPPFLAGS += $(LIBGNUTLS_CFLAGS)
+AM_CFLAGS += $(LIBGNUTLS_CFLAGS)
libosmogsm_la_LIBADD += $(LIBGNUTLS_LIBS)
+else
+noinst_HEADERS += kdf/sha1.h kdf/sha256.h kdf/common.h kdf/sha1_i.h kdf/sha256_i.h
+libgsmint_la_SOURCES += kdf/sha256.c kdf/sha256-internal.c \
+ kdf/sha1.c kdf/sha1-internal.c
endif
EXTRA_DIST = libosmogsm.map
+EXTRA_libosmogsm_la_DEPENDENCIES = libosmogsm.map
# Convolutional codes generation
gsm0503_conv.c: $(top_srcdir)/utils/conv_gen.py $(top_srcdir)/utils/conv_codes_gsm.py
diff --git a/src/gsm/a5.c b/src/gsm/a5.c
index 223d3ad8..d223cb76 100644
--- a/src/gsm/a5.c
+++ b/src/gsm/a5.c
@@ -14,10 +14,6 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
/*! \addtogroup a5
diff --git a/src/gsm/abis_nm.c b/src/gsm/abis_nm.c
index 13837d2b..c4060e01 100644
--- a/src/gsm/abis_nm.c
+++ b/src/gsm/abis_nm.c
@@ -610,6 +610,10 @@ const struct value_string abis_nm_obj_class_names[] = {
{ NM_OC_RADIO_CARRIER, "RADIO-CARRIER" },
{ NM_OC_BASEB_TRANSC, "BASEBAND-TRANSCEIVER" },
{ NM_OC_CHANNEL, "CHANNEL" },
+ { NM_OC_IPAC_E1_TRUNK, "IPAC-E1-TRUNK" },
+ { NM_OC_IPAC_E1_PORT, "IPAC-E1-PORT" },
+ { NM_OC_IPAC_E1_CHAN, "IPAC-E1-CHAN" },
+ { NM_OC_IPAC_CLK_MODULE,"IPAC-CLK-MODULE" },
{ NM_OC_BS11_ADJC, "ADJC" },
{ NM_OC_BS11_HANDOVER, "HANDOVER" },
{ NM_OC_BS11_PWR_CTRL, "POWER-CONTROL" },
@@ -699,10 +703,104 @@ static const enum abis_nm_chan_comb chcomb4pchan[] = {
[GSM_PCHAN_UNKNOWN] = 0xff,
[GSM_PCHAN_CCCH_SDCCH4_CBCH] = NM_CHANC_BCCH_CBCH,
[GSM_PCHAN_SDCCH8_SACCH8C_CBCH] = NM_CHANC_SDCCH_CBCH,
- [GSM_PCHAN_TCH_F_TCH_H_PDCH] = NM_CHANC_OSMO_TCHFull_TCHHalf_PDCH,
+ [GSM_PCHAN_OSMO_DYN] = NM_CHANC_OSMO_DYN,
/* FIXME: bounds check */
};
+const struct value_string abis_nm_ipacc_freq_band_desc[] = {
+ { NM_IPAC_F_FREQ_BAND_PGSM, "P-GSM" },
+ { NM_IPAC_F_FREQ_BAND_EGSM, "E-GSM" },
+ { NM_IPAC_F_FREQ_BAND_RGSM, "R-GSM" },
+ { NM_IPAC_F_FREQ_BAND_DCS, "DCS-1800" },
+ { NM_IPAC_F_FREQ_BAND_PCS, "PCS-1900" },
+ { NM_IPAC_F_FREQ_BAND_850, "850" },
+ { NM_IPAC_F_FREQ_BAND_480, "480" },
+ { NM_IPAC_F_FREQ_BAND_450, "450" },
+ { 0, NULL }
+};
+
+const struct value_string abis_nm_ipacc_ciph_algo_desc[] = {
+ { NM_IPAC_F_CIPH_ALGO_A51, "A5/1" },
+ { NM_IPAC_F_CIPH_ALGO_A52, "A5/2" },
+ { NM_IPAC_F_CIPH_ALGO_A53, "A5/3" },
+ { NM_IPAC_F_CIPH_ALGO_A54, "A5/4" },
+ { NM_IPAC_F_CIPH_ALGO_A55, "A5/5" },
+ { NM_IPAC_F_CIPH_ALGO_A56, "A5/6" },
+ { NM_IPAC_F_CIPH_ALGO_A57, "A5/7" },
+ { NM_IPAC_F_CIPH_ALGO_A58, "A5/8" },
+ { 0, NULL }
+};
+
+const struct value_string abis_nm_ipacc_chant_desc[] = {
+ { NM_IPAC_F_CHANT_TCHF, "TCH/F" },
+ { NM_IPAC_F_CHANT_TCHH, "TCH/H" },
+ { NM_IPAC_F_CHANT_SDCCH8, "SDCCH/8" },
+ { NM_IPAC_F_CHANT_BCCH, "BCCH" },
+ { NM_IPAC_F_CHANT_BCCH_SDCCH4, "BCCH+SDCCH/4" },
+ { NM_IPAC_F_CHANT_BCH, "BCH" },
+ { NM_IPAC_F_CHANT_BCCH_SDCCH4_CBCH, "BCCH+SDCCH/4+CBCH" },
+ { NM_IPAC_F_CHANT_SDCCH8_CBCH, "SDCCH/8+CBCH" },
+ { NM_IPAC_F_CHANT_PDCHF, "PDCH/F" },
+ { NM_IPAC_F_CHANT_TCHF_PDCHF, "TCH/F or PDCH/F" },
+ { NM_IPAC_F_CHANT_TCHH_PDCHH, "TCH/H+PDCH/H" },
+ { NM_IPAC_F_CHANT_TCHF_TCHH, "TCH/F or TCH/H" },
+ { 0, NULL }
+};
+
+const struct value_string abis_nm_ipacc_chanm_desc[] = {
+ { NM_IPAC_F_CHANM_SPEECH_FS, "FR1/FS" },
+ { NM_IPAC_F_CHANM_SPEECH_EFS, "FR2/EFS" },
+ { NM_IPAC_F_CHANM_SPEECH_AFS, "FR3/AFS" },
+ { NM_IPAC_F_CHANM_SPEECH_HS, "HR1/HS" },
+ { NM_IPAC_F_CHANM_SPEECH_AHS, "HR3/AHS" },
+ { NM_IPAC_F_CHANM_CSD_NT_4k8, "CSD NT 4.8 kbit/s" },
+ { NM_IPAC_F_CHANM_CSD_NT_9k6, "CSD NT 9.6 kbit/s" },
+ { NM_IPAC_F_CHANM_CSD_NT_14k4, "CSD NT 14.4 kbit/s" },
+ { NM_IPAC_F_CHANM_CSD_T_1200_75, "CSD T 1200/75 kbit/s" },
+ { NM_IPAC_F_CHANM_CSD_T_600, "CSD T 600 kbit/s" },
+ { NM_IPAC_F_CHANM_CSD_T_1k2, "CSD T 1k2 kbit/s" },
+ { NM_IPAC_F_CHANM_CSD_T_2k4, "CSD T 2.4 kbit/s" },
+ { NM_IPAC_F_CHANM_CSD_T_4k8, "CSD T 4.8 kbit/s" },
+ { NM_IPAC_F_CHANM_CSD_T_9k6, "CSD T 9.6 kbit/s" },
+ { NM_IPAC_F_CHANM_CSD_T_14k4, "CSD T 14.4 kbit/s" },
+ { 0, NULL }
+};
+
+const struct value_string abis_nm_ipacc_gprs_coding_desc[] = {
+ { NM_IPAC_F_GPRS_CODING_CS1, "CS1" },
+ { NM_IPAC_F_GPRS_CODING_CS2, "CS2" },
+ { NM_IPAC_F_GPRS_CODING_CS3, "CS3" },
+ { NM_IPAC_F_GPRS_CODING_CS4, "CS4" },
+ { NM_IPAC_F_GPRS_CODING_MCS1, "MCS1" },
+ { NM_IPAC_F_GPRS_CODING_MCS2, "MCS2" },
+ { NM_IPAC_F_GPRS_CODING_MCS3, "MCS3" },
+ { NM_IPAC_F_GPRS_CODING_MCS4, "MCS4" },
+ { NM_IPAC_F_GPRS_CODING_MCS5, "MCS5" },
+ { NM_IPAC_F_GPRS_CODING_MCS6, "MCS6" },
+ { NM_IPAC_F_GPRS_CODING_MCS7, "MCS7" },
+ { NM_IPAC_F_GPRS_CODING_MCS8, "MCS8" },
+ { NM_IPAC_F_GPRS_CODING_MCS9, "MCS9" },
+ { 0, NULL }
+};
+
+const struct value_string abis_nm_ipacc_rtp_feat_desc[] = {
+ { NM_IPAC_F_RTP_FEAT_COMPR_CONTROL, "Compression Control" },
+ { NM_IPAC_F_RTP_FEAT_IR_8k, "Intermediate Rate 8 kbit/s" },
+ { NM_IPAC_F_RTP_FEAT_IR_16k, "Intermediate Rate 16 kbit/s" },
+ { NM_IPAC_F_RTP_FEAT_IR_32k, "Intermediate Rate 32 kbit/s" },
+ { NM_IPAC_F_RTP_FEAT_IR_64k, "Intermediate Rate 64 kbit/s" },
+ { NM_IPAC_F_RTP_FEAT_MULTIPLEX_RTP, "RTP Multiplex" },
+ { NM_IPAC_F_RTP_FEAT_MULTIPLEX_SRTP, "SRTP Multiplex" },
+ { 0, NULL }
+};
+
+const struct value_string abis_nm_ipacc_rsl_feat_desc[] = {
+ { NM_IPAC_F_RSL_FEAT_PHYSICAL_CONTEXT, "Physical Context" },
+ { NM_IPAC_F_RSL_FEAT_DYN_PDCH_ACT, "Dynamic PDCH Activation" },
+ { NM_IPAC_F_RSL_FEAT_RTP_PT2, "RTP Payload Type 2" },
+ { 0, NULL }
+};
+
/*! Pack 3GPP TS 12.21 § 8.8.2 Failure Event Report into msgb */
struct msgb *abis_nm_fail_evt_rep(enum abis_nm_event_type t,
enum abis_nm_severity s,
diff --git a/src/gsm/apn.c b/src/gsm/apn.c
index a7074eab..4800702c 100644
--- a/src/gsm/apn.c
+++ b/src/gsm/apn.c
@@ -24,8 +24,8 @@
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
-#include <talloc.h>
+#include <osmocom/core/talloc.h>
#include <osmocom/gsm/apn.h>
#define APN_OI_GPRS_FMT "mnc%03u.mcc%03u.gprs"
diff --git a/src/gsm/auth_comp128v1.c b/src/gsm/auth_comp128v1.c
index 493ebfd0..ded3f8a6 100644
--- a/src/gsm/auth_comp128v1.c
+++ b/src/gsm/auth_comp128v1.c
@@ -17,10 +17,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <osmocom/crypt/auth.h>
@@ -31,9 +27,10 @@
*/
static int c128v1_gen_vec(struct osmo_auth_vector *vec,
- struct osmo_sub_auth_data *aud,
+ struct osmo_sub_auth_data2 *aud,
const uint8_t *_rand)
{
+ OSMO_ASSERT(aud->algo == OSMO_AUTH_ALG_COMP128v1);
comp128v1(aud->u.gsm.ki, _rand, vec->sres, vec->kc);
vec->auth_types = OSMO_AUTH_TYPE_GSM;
diff --git a/src/gsm/auth_comp128v23.c b/src/gsm/auth_comp128v23.c
index 279d2b74..f942bc02 100644
--- a/src/gsm/auth_comp128v23.c
+++ b/src/gsm/auth_comp128v23.c
@@ -19,10 +19,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <osmocom/crypt/auth.h>
@@ -33,9 +29,10 @@
*/
static int c128v2_gen_vec(struct osmo_auth_vector *vec,
- struct osmo_sub_auth_data *aud,
+ struct osmo_sub_auth_data2 *aud,
const uint8_t *_rand)
{
+ OSMO_ASSERT(aud->algo == OSMO_AUTH_ALG_COMP128v2);
comp128v2(aud->u.gsm.ki, _rand, vec->sres, vec->kc);
vec->auth_types = OSMO_AUTH_TYPE_GSM;
@@ -50,9 +47,10 @@ static struct osmo_auth_impl c128v2_alg = {
};
static int c128v3_gen_vec(struct osmo_auth_vector *vec,
- struct osmo_sub_auth_data *aud,
+ struct osmo_sub_auth_data2 *aud,
const uint8_t *_rand)
{
+ OSMO_ASSERT(aud->algo == OSMO_AUTH_ALG_COMP128v3);
comp128v3(aud->u.gsm.ki, _rand, vec->sres, vec->kc);
vec->auth_types = OSMO_AUTH_TYPE_GSM;
diff --git a/src/gsm/auth_core.c b/src/gsm/auth_core.c
index 9e750a01..6972cb77 100644
--- a/src/gsm/auth_core.c
+++ b/src/gsm/auth_core.c
@@ -1,4 +1,4 @@
-/* (C) 2010-2012 by Harald Welte <laforge@gnumonks.org>
+/* (C) 2010-2023 by Harald Welte <laforge@gnumonks.org>
*
* All Rights Reserved
*
@@ -14,10 +14,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include "config.h"
@@ -40,6 +36,35 @@
static LLIST_HEAD(osmo_auths);
+/* generate auth_data2 from auth_data (for legacy API/ABI compatibility */
+static int auth_data2auth_data2(struct osmo_sub_auth_data2 *out, const struct osmo_sub_auth_data *in)
+{
+ out->type = in->type;
+ out->algo = in->algo;
+ switch (in->type) {
+ case OSMO_AUTH_TYPE_NONE:
+ return 0;
+ case OSMO_AUTH_TYPE_GSM:
+ memcpy(out->u.gsm.ki, in->u.gsm.ki, sizeof(out->u.gsm.ki));
+ break;
+ case OSMO_AUTH_TYPE_UMTS:
+ memcpy(out->u.umts.opc, in->u.umts.opc, sizeof(in->u.umts.opc));
+ out->u.umts.opc_len = sizeof(in->u.umts.opc);
+ memcpy(out->u.umts.k, in->u.umts.k, sizeof(in->u.umts.k));
+ out->u.umts.k_len = sizeof(in->u.umts.k);
+ memcpy(out->u.umts.amf, in->u.umts.amf, sizeof(out->u.umts.amf));
+ out->u.umts.sqn = in->u.umts.sqn;
+ out->u.umts.opc_is_op = in->u.umts.opc_is_op;
+ out->u.umts.ind_bitlen = in->u.umts.ind_bitlen;
+ out->u.umts.ind = in->u.umts.ind;
+ out->u.umts.sqn_ms = in->u.umts.sqn_ms;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
static struct osmo_auth_impl *selected_auths[_OSMO_AUTH_ALG_NUM];
/*! Register an authentication algorithm implementation with the core
@@ -142,6 +167,42 @@ int osmo_auth_3g_from_2g(struct osmo_auth_vector *vec)
}
/*! Generate authentication vector
+ * \param[out] vec Generated authentication vector. See below!
+ * \param[in] aud Subscriber-specific key material
+ * \param[in] _rand Random challenge to be used
+ * \returns 0 on success, negative error on failure
+ *
+ * This function performs the core cryptographic function of the AUC,
+ * computing authentication triples/quintuples based on the permanent
+ * subscriber data and a random value. The result is what is forwarded
+ * by the AUC via HLR and VLR to the MSC which will then be able to
+ * invoke authentication with the MS.
+ *
+ * Contrary to the older osmo_auth_gen_vec(), the caller must specify
+ * the desired RES length in the vec->res_len field prior to calling
+ * this function. The requested length must match the capabilities of
+ * the chosen algorithm (e.g. 4/8 for MILENAGE).
+ */
+int osmo_auth_gen_vec2(struct osmo_auth_vector *vec,
+ struct osmo_sub_auth_data2 *aud,
+ const uint8_t *_rand)
+{
+ struct osmo_auth_impl *impl = selected_auths[aud->algo];
+ int rc;
+
+ if (!impl)
+ return -ENOENT;
+
+ rc = impl->gen_vec(vec, aud, _rand);
+ if (rc < 0)
+ return rc;
+
+ memcpy(vec->rand, _rand, sizeof(vec->rand));
+
+ return 0;
+}
+
+/*! Generate authentication vector
* \param[out] vec Generated authentication vector
* \param[in] aud Subscriber-specific key material
* \param[in] _rand Random challenge to be used
@@ -157,13 +218,58 @@ int osmo_auth_gen_vec(struct osmo_auth_vector *vec,
struct osmo_sub_auth_data *aud,
const uint8_t *_rand)
{
+ struct osmo_sub_auth_data2 aud2;
+ int rc;
+
+ if (aud->type == OSMO_AUTH_TYPE_UMTS) {
+ /* old API callers are not expected to initialize this struct field,
+ * and always expect an 8-byte RES value */
+ vec->res_len = 8;
+ }
+
+ rc = auth_data2auth_data2(&aud2, aud);
+ if (rc < 0)
+ return rc;
+
+ rc = osmo_auth_gen_vec2(vec, &aud2, _rand);
+ if (aud->type == OSMO_AUTH_TYPE_UMTS)
+ aud->u.umts.sqn = aud2.u.umts.sqn;
+
+ return rc;
+}
+
+/*! Generate authentication vector and re-sync sequence
+ * \param[out] vec Generated authentication vector. See below!
+ * \param[in] aud Subscriber-specific key material
+ * \param[in] auts AUTS value sent by the SIM/MS
+ * \param[in] rand_auts RAND value sent by the SIM/MS
+ * \param[in] _rand Random challenge to be used to generate vector
+ * \returns 0 on success, negative error on failure
+ *
+ * This function performs a special variant of the core cryptographic
+ * function of the AUC: computing authentication triples/quintuples
+ * based on the permanent subscriber data, a random value as well as the
+ * AUTS and RAND values returned by the SIM/MS. This special variant is
+ * needed if the sequence numbers between MS and AUC have for some
+ * reason become different.
+ *
+ * Contrary to the older osmo_auth_gen_vec_auts(), the caller must specify
+ * the desired RES length in the vec->res_len field prior to calling
+ * this function. The requested length must match the capabilities of
+ * the chosen algorithm (e.g. 4/8 for MILENAGE).
+ */
+int osmo_auth_gen_vec_auts2(struct osmo_auth_vector *vec,
+ struct osmo_sub_auth_data2 *aud,
+ const uint8_t *auts, const uint8_t *rand_auts,
+ const uint8_t *_rand)
+{
struct osmo_auth_impl *impl = selected_auths[aud->algo];
int rc;
- if (!impl)
+ if (!impl || !impl->gen_vec_auts)
return -ENOENT;
- rc = impl->gen_vec(vec, aud, _rand);
+ rc = impl->gen_vec_auts(vec, aud, auts, rand_auts, _rand);
if (rc < 0)
return rc;
@@ -192,19 +298,26 @@ int osmo_auth_gen_vec_auts(struct osmo_auth_vector *vec,
const uint8_t *auts, const uint8_t *rand_auts,
const uint8_t *_rand)
{
- struct osmo_auth_impl *impl = selected_auths[aud->algo];
+ struct osmo_sub_auth_data2 aud2;
int rc;
- if (!impl || !impl->gen_vec_auts)
- return -ENOENT;
+ if (aud->type == OSMO_AUTH_TYPE_UMTS) {
+ /* old API callers are not expected to initialize this struct field,
+ * and always expect an 8-byte RES value */
+ vec->res_len = 8;
+ }
- rc = impl->gen_vec_auts(vec, aud, auts, rand_auts, _rand);
+ rc = auth_data2auth_data2(&aud2, aud);
if (rc < 0)
return rc;
- memcpy(vec->rand, _rand, sizeof(vec->rand));
+ rc = osmo_auth_gen_vec_auts2(vec, &aud2, auts, rand_auts, _rand);
+ if (aud->type == OSMO_AUTH_TYPE_UMTS) {
+ aud->u.umts.sqn = aud2.u.umts.sqn;
+ aud->u.umts.sqn_ms = aud2.u.umts.sqn_ms;
+ }
- return 0;
+ return rc;
}
static const struct value_string auth_alg_vals[] = {
@@ -212,8 +325,10 @@ static const struct value_string auth_alg_vals[] = {
{ OSMO_AUTH_ALG_COMP128v1, "COMP128v1" },
{ OSMO_AUTH_ALG_COMP128v2, "COMP128v2" },
{ OSMO_AUTH_ALG_COMP128v3, "COMP128v3" },
- { OSMO_AUTH_ALG_XOR, "XOR" },
+ { OSMO_AUTH_ALG_XOR_3G, "XOR-3G" },
{ OSMO_AUTH_ALG_MILENAGE, "MILENAGE" },
+ { OSMO_AUTH_ALG_XOR_2G, "XOR-2G" },
+ { OSMO_AUTH_ALG_TUAK, "TUAK" },
{ 0, NULL }
};
@@ -249,4 +364,33 @@ void osmo_auth_c3(uint8_t kc[], const uint8_t ck[], const uint8_t ik[])
kc[i] = ck[i] ^ ck[i + 8] ^ ik[i] ^ ik[i + 8];
}
+/*! Derive GSM SRES from UMTS [X]RES (auth function c2 from 3GPP TS 33.103 Section 6.8.1.2
+ * \param[out] sres GSM SRES value, 4 byte target buffer
+ * \param[in] res UMTS XRES, 4..16 bytes input buffer
+ * \param[in] res_len length of res parameter (in bytes)
+ * \param[in] sres_deriv_func SRES derivation function (1 or 2, see 3GPP TS 55.205 Section 4
+ */
+void osmo_auth_c2(uint8_t sres[4], const uint8_t *res, size_t res_len, uint8_t sres_deriv_func)
+{
+ uint8_t xres[16];
+
+ OSMO_ASSERT(sres_deriv_func == 1 || sres_deriv_func == 2);
+ OSMO_ASSERT(res_len <= sizeof(xres));
+
+ memcpy(xres, res, res_len);
+
+ /* zero-pad the end, if XRES is < 16 bytes */
+ if (res_len < sizeof(xres))
+ memset(xres+res_len, 0, sizeof(xres)-res_len);
+
+ if (sres_deriv_func == 1) {
+ /* SRES derivation function #1 */
+ for (unsigned int i = 0; i < 4; i++)
+ sres[i] = xres[i] ^ xres[4+i] ^ xres[8+i] ^ xres[12+i];
+ } else {
+ /* SRES derivation function #2 */
+ memcpy(sres, xres, 4);
+ }
+}
+
/*! @} */
diff --git a/src/gsm/auth_milenage.c b/src/gsm/auth_milenage.c
index 95891000..2bd05a6d 100644
--- a/src/gsm/auth_milenage.c
+++ b/src/gsm/auth_milenage.c
@@ -17,12 +17,9 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
+#include <errno.h>
#include <osmocom/crypt/auth.h>
#include <osmocom/core/bits.h>
#include "milenage/common.h"
@@ -32,7 +29,7 @@
* @{
*/
-static const uint8_t *gen_opc_if_needed(const struct osmo_sub_auth_data *aud, uint8_t *gen_opc)
+static const uint8_t *gen_opc_if_needed(const struct osmo_sub_auth_data2 *aud, uint8_t *gen_opc)
{
int rc;
@@ -47,7 +44,7 @@ static const uint8_t *gen_opc_if_needed(const struct osmo_sub_auth_data *aud, ui
}
static int milenage_gen_vec(struct osmo_auth_vector *vec,
- struct osmo_sub_auth_data *aud,
+ struct osmo_sub_auth_data2 *aud,
const uint8_t *_rand)
{
size_t res_len = sizeof(vec->res);
@@ -57,7 +54,15 @@ static int milenage_gen_vec(struct osmo_auth_vector *vec,
uint8_t sqn[6];
uint64_t ind_mask;
uint64_t seq_1;
- int rc;
+
+ OSMO_ASSERT(aud->algo == OSMO_AUTH_ALG_MILENAGE);
+
+ if (aud->u.umts.k_len != 16)
+ return -EINVAL;
+ if (aud->u.umts.opc_len != 16)
+ return -EINVAL;
+ if (vec->res_len != 4 && vec->res_len != 8)
+ return -EINVAL;
opc = gen_opc_if_needed(aud, gen_opc);
if (!opc)
@@ -131,10 +136,9 @@ static int milenage_gen_vec(struct osmo_auth_vector *vec,
milenage_generate(opc, aud->u.umts.amf, aud->u.umts.k,
sqn, _rand,
vec->autn, vec->ik, vec->ck, vec->res, &res_len);
- vec->res_len = res_len;
- rc = gsm_milenage(opc, aud->u.umts.k, _rand, vec->sres, vec->kc);
- if (rc < 0)
- return rc;
+
+ osmo_auth_c3(vec->kc, vec->ck, vec->ik);
+ osmo_auth_c2(vec->sres, vec->res, vec->res_len, 1);
vec->auth_types = OSMO_AUTH_TYPE_UMTS | OSMO_AUTH_TYPE_GSM;
@@ -145,7 +149,7 @@ static int milenage_gen_vec(struct osmo_auth_vector *vec,
}
static int milenage_gen_vec_auts(struct osmo_auth_vector *vec,
- struct osmo_sub_auth_data *aud,
+ struct osmo_sub_auth_data2 *aud,
const uint8_t *auts, const uint8_t *rand_auts,
const uint8_t *_rand)
{
@@ -154,6 +158,13 @@ static int milenage_gen_vec_auts(struct osmo_auth_vector *vec,
const uint8_t *opc;
int rc;
+ OSMO_ASSERT(aud->algo == OSMO_AUTH_ALG_MILENAGE);
+
+ if (aud->u.umts.k_len != 16)
+ return -EINVAL;
+ if (aud->u.umts.opc_len != 16)
+ return -EINVAL;
+
opc = gen_opc_if_needed(aud, gen_opc);
rc = milenage_auts(opc, aud->u.umts.k, rand_auts, auts, sqn_out);
diff --git a/src/gsm/auth_tuak.c b/src/gsm/auth_tuak.c
new file mode 100644
index 00000000..05fbf691
--- /dev/null
+++ b/src/gsm/auth_tuak.c
@@ -0,0 +1,207 @@
+/*! \file auth_tuak.c
+ * GSM/GPRS/3G authentication core infrastructure */
+/*
+ * (C) 2023 by Harald Welte <laforge@osmocom.org>
+ *
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+/* NOTE: TUAK offers a lot of size variability in terms of size of length of MAC_A, MAC_S,
+ * but this is not used within 3GPP. The different sizes of Kc and RES are handled via
+ * osmo_sub_auth_data2. */
+
+#include <errno.h>
+#include <osmocom/crypt/auth.h>
+#include <osmocom/core/bits.h>
+#include "tuak/tuak.h"
+
+/*! \addtogroup auth
+ * @{
+ */
+
+static const uint8_t *gen_opc_if_needed(const struct osmo_sub_auth_data2 *aud, uint8_t *gen_opc)
+{
+ int rc;
+
+ /* Check if we only know OP and compute OPC if required */
+ if (aud->type == OSMO_AUTH_TYPE_UMTS && aud->u.umts.opc_is_op) {
+ rc = tuak_opc_gen(gen_opc, aud->u.umts.k, aud->u.umts.k_len, aud->u.umts.opc);
+ if (rc < 0)
+ return NULL;
+ return gen_opc;
+ }
+
+ return aud->u.umts.opc;
+}
+
+static int tuak_gen_vec(struct osmo_auth_vector *vec,
+ struct osmo_sub_auth_data2 *aud,
+ const uint8_t *_rand)
+{
+ size_t res_len = vec->res_len;
+ uint64_t next_sqn;
+ uint8_t gen_opc[32];
+ const uint8_t *opc;
+ uint8_t sqn[6];
+ uint64_t ind_mask;
+ uint64_t seq_1;
+
+ switch (vec->res_len) {
+ case 4:
+ case 8:
+ case 16:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ OSMO_ASSERT(aud->algo == OSMO_AUTH_ALG_TUAK);
+
+ if (aud->u.umts.k_len != 16 && aud->u.umts.k_len != 32)
+ return -EINVAL;
+ if (aud->u.umts.opc_len != 32)
+ return -EINVAL;
+
+ opc = gen_opc_if_needed(aud, gen_opc);
+ if (!opc)
+ return -1;
+
+ /* Determine next SQN, according to 3GPP TS 33.102:
+ * SQN consists of SEQ and a lower significant part of IND bits:
+ *
+ * |----------SEQ------------|
+ * |------------------------SQN-----------|
+ * |-----IND----|
+ *
+ * The IND part is used as "slots": e.g. a given HLR client will always
+ * get the same IND part, called ind here, with incrementing SEQ. In
+ * the USIM, each IND slot enforces that its SEQ are used in ascending
+ * order -- as long as that constraint is satisfied, the SQN may jump
+ * forwards and backwards. For example, for ind_bitlen == 5, asking the
+ * USIM for SQN = 32, 64, 33 is allowed, because 32 and 64 are
+ * SEQ || (ind == 0), and though 33 is below 64, it is ind == 1 and
+ * allowed. Not allowed would be 32, 96, 64, because 64 would go
+ * backwards after 96, both being ind == 0.
+ *
+ * From the last used SQN, we want to increment SEQ + 1, and then pick
+ * the matching IND part.
+ *
+ * IND size is suggested in TS 33.102 as 5 bits. SQN is 48 bits long.
+ * If ind_bitlen is passed too large here, the algorithms will break
+ * down. But at which point should we return an error? A sane limit
+ * seems to be ind_bitlen == 10, but to protect against failure,
+ * limiting ind_bitlen to 28 is enough, 28 being the number of bits
+ * suggested for the delta in 33.102, which is discussed to still
+ * require 2^15 > 32000 authentications to wrap the SQN back to the
+ * start.
+ *
+ * Note that if a caller with ind == 1 generates N vectors, the SQN
+ * stored after this will reflect SEQ + N. If then another caller with
+ * ind == 2 generates another N vectors, this will then use SEQ + N
+ * onwards and end up with SEQ + N + N. In other words, most of each
+ * SEQ's IND slots will remain unused. When looking at SQN being 48
+ * bits wide, after dropping ind_bitlen (say 5) from it, we will still
+ * have a sequence range of 2^43 = 8.8e12, eight trillion sequences,
+ * which is large enough to not bother further. With the maximum
+ * ind_bitlen of 28 enforced below, we still get more than 1 million
+ * sequences, which is also sufficiently large.
+ *
+ * An ind_bitlen of zero may be passed from legacy callers that are not
+ * aware of the IND extension. For these, below algorithm works out as
+ * before, simply incrementing SQN by 1.
+ *
+ * This is also a mechanism for tools like the osmo-auc-gen to directly
+ * request a given SQN to be used. With ind_bitlen == 0 the caller can
+ * be sure that this code will increment SQN by exactly one before
+ * generating a tuple, thus a caller would simply pass
+ * { .ind_bitlen = 0, .ind = 0, .sqn = (desired_sqn - 1) }
+ */
+
+ if (aud->u.umts.ind_bitlen > OSMO_MILENAGE_IND_BITLEN_MAX)
+ return -2;
+
+ seq_1 = 1LL << aud->u.umts.ind_bitlen;
+ ind_mask = ~(seq_1 - 1);
+
+ /* the ind index must not affect the SEQ part */
+ if (aud->u.umts.ind >= seq_1)
+ return -3;
+
+ /* keep the incremented SQN local until gsm_milenage() succeeded. */
+ next_sqn = ((aud->u.umts.sqn + seq_1) & ind_mask) + aud->u.umts.ind;
+
+ osmo_store64be_ext(next_sqn, sqn, 6);
+
+ tuak_generate(opc, aud->u.umts.amf, aud->u.umts.k, aud->u.umts.k_len,
+ sqn, _rand, vec->autn, vec->ik, vec->ck, vec->res, &res_len);
+
+ /* generate the GSM Kc + SRES values using C2 + C3 functions */
+ osmo_auth_c3(vec->kc, vec->ck, vec->ik);
+ osmo_auth_c2(vec->sres, vec->res, vec->res_len, 1);
+
+ vec->auth_types = OSMO_AUTH_TYPE_UMTS | OSMO_AUTH_TYPE_GSM;
+
+ /* for storage in the caller's AUC database */
+ aud->u.umts.sqn = next_sqn;
+
+ return 0;
+}
+
+static int tuak_gen_vec_auts(struct osmo_auth_vector *vec,
+ struct osmo_sub_auth_data2 *aud,
+ const uint8_t *auts, const uint8_t *rand_auts,
+ const uint8_t *_rand)
+{
+ uint8_t sqn_out[6];
+ uint8_t gen_opc[32];
+ const uint8_t *opc;
+ int rc;
+
+ OSMO_ASSERT(aud->algo == OSMO_AUTH_ALG_TUAK);
+
+ if (aud->u.umts.k_len != 16 && aud->u.umts.k_len != 32)
+ return -EINVAL;
+ if (aud->u.umts.opc_len != 32)
+ return -EINVAL;
+
+ opc = gen_opc_if_needed(aud, gen_opc);
+
+ rc = tuak_auts(opc, aud->u.umts.k, sizeof(aud->u.umts.k), rand_auts, auts, sqn_out);
+ if (rc < 0)
+ return rc;
+
+ aud->u.umts.sqn_ms = osmo_load64be_ext(sqn_out, 6) >> 16;
+ /* Update our "largest used SQN" from the USIM -- milenage_gen_vec()
+ * below will increment SQN. */
+ aud->u.umts.sqn = aud->u.umts.sqn_ms;
+
+ return tuak_gen_vec(vec, aud, _rand);
+}
+
+static struct osmo_auth_impl tuak_alg = {
+ .algo = OSMO_AUTH_ALG_TUAK,
+ .name = "TUAK (libosmogsm built-in)",
+ .priority = 1000,
+ .gen_vec = &tuak_gen_vec,
+ .gen_vec_auts = &tuak_gen_vec_auts,
+};
+
+static __attribute__((constructor)) void on_dso_load_tuak(void)
+{
+ osmo_auth_register(&tuak_alg);
+}
+
+/*! @} */
diff --git a/src/gsm/auth_xor.c b/src/gsm/auth_xor.c
index 36e006e6..a506a03d 100644
--- a/src/gsm/auth_xor.c
+++ b/src/gsm/auth_xor.c
@@ -20,10 +20,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <string.h>
@@ -47,19 +43,23 @@ static void xor(uint8_t *out, const uint8_t *a, const uint8_t *b, size_t len)
/* 3GPP TS 34.108, section 8.1.2.1 */
static int xor_gen_vec(struct osmo_auth_vector *vec,
- struct osmo_sub_auth_data *aud,
+ struct osmo_sub_auth_data2 *aud,
const uint8_t *_rand)
{
uint8_t xdout[16], cdout[8];
uint8_t ak[6], xmac[8];
int i;
+ OSMO_ASSERT(aud->algo == OSMO_AUTH_ALG_XOR_3G);
+
/* Step 1: xdout = (ki or k) ^ rand */
if (aud->type == OSMO_AUTH_TYPE_GSM)
xor(xdout, aud->u.gsm.ki, _rand, sizeof(xdout));
- else if (aud->type == OSMO_AUTH_TYPE_UMTS)
+ else if (aud->type == OSMO_AUTH_TYPE_UMTS) {
+ if (aud->u.umts.k_len != 16)
+ return -EINVAL;
xor(xdout, aud->u.umts.k, _rand, sizeof(xdout));
- else
+ } else
return -ENOTSUP;
/**
@@ -129,7 +129,7 @@ static int xor_gen_vec(struct osmo_auth_vector *vec,
/* 3GPP TS 34.108, section 8.1.2.2 */
static int xor_gen_vec_auts(struct osmo_auth_vector *vec,
- struct osmo_sub_auth_data *aud,
+ struct osmo_sub_auth_data2 *aud,
const uint8_t *auts,
const uint8_t *rand_auts,
const uint8_t *_rand)
@@ -138,12 +138,16 @@ static int xor_gen_vec_auts(struct osmo_auth_vector *vec,
uint8_t ak[6], xmac[8];
uint8_t sqnms[6];
+ OSMO_ASSERT(aud->algo == OSMO_AUTH_ALG_XOR_3G);
+
/* Step 1: xdout = (ki or k) ^ rand */
if (aud->type == OSMO_AUTH_TYPE_GSM)
xor(xdout, aud->u.gsm.ki, _rand, sizeof(xdout));
- else if (aud->type == OSMO_AUTH_TYPE_UMTS)
+ else if (aud->type == OSMO_AUTH_TYPE_UMTS) {
+ if (aud->u.umts.k_len != 16)
+ return -EINVAL;
xor(xdout, aud->u.umts.k, _rand, sizeof(xdout));
- else
+ } else
return -ENOTSUP;
/* Step 2: ak = xdout[2-8] */
@@ -172,8 +176,8 @@ static int xor_gen_vec_auts(struct osmo_auth_vector *vec,
}
static struct osmo_auth_impl xor_alg = {
- .algo = OSMO_AUTH_ALG_XOR,
- .name = "XOR (libosmogsm built-in)",
+ .algo = OSMO_AUTH_ALG_XOR_3G,
+ .name = "XOR-3G (libosmogsm built-in)",
.priority = 1000,
.gen_vec = &xor_gen_vec,
.gen_vec_auts = &xor_gen_vec_auts,
diff --git a/src/gsm/auth_xor_2g.c b/src/gsm/auth_xor_2g.c
new file mode 100644
index 00000000..367c79d4
--- /dev/null
+++ b/src/gsm/auth_xor_2g.c
@@ -0,0 +1,81 @@
+/*! \file auth_xor.c
+ * GSM XOR-2G algorithm as specified in Annex 4 (A.4.1.2) of 3GPP TS 51.010-1.
+ * This is implemented by typical GSM MS tester */
+
+/*
+ * (C) 2023 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * Author: Daniel Willmann <dwillmann@sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * 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 <string.h>
+#include <stdint.h>
+#include <errno.h>
+
+#include <osmocom/crypt/auth.h>
+
+/*! \addtogroup auth
+ * @{
+ */
+
+static void xor(uint8_t *out, const uint8_t *a, const uint8_t *b, size_t len)
+{
+ size_t i;
+
+ for (i = 0; i < len; i++)
+ out[i] = a[i] ^ b[i];
+}
+
+/* GSM XOR-2G algorithm as specified in Annex 4 (A.4.1.2) of 3GPP TS 51.010-1. */
+static int xor2g_gen_vec(struct osmo_auth_vector *vec,
+ struct osmo_sub_auth_data2 *aud,
+ const uint8_t *_rand)
+{
+ uint8_t res1[16];
+
+ OSMO_ASSERT(aud->algo == OSMO_AUTH_ALG_XOR_2G);
+
+ if (aud->type != OSMO_AUTH_TYPE_GSM)
+ return -ENOTSUP;
+
+ /* Step 1: XOR to the challenge RAND, a predefined number Ki, having the same bit length (128 bits) as
+ * RAND. */
+ xor(res1, aud->u.gsm.ki, _rand, sizeof(res1));
+
+ /* Step 2: The most significant 32 bits of RES1 form SRES. */
+ memcpy(vec->sres, res1, 4);
+ /* The next 64 bits of RES1 form Kc */
+ memcpy(vec->kc, res1+4, 8);
+
+ vec->auth_types = OSMO_AUTH_TYPE_GSM;
+ return 0;
+}
+
+static struct osmo_auth_impl xor2g_alg = {
+ .algo = OSMO_AUTH_ALG_XOR_2G,
+ .name = "XOR-2G (libosmogsm built-in)",
+ .priority = 1000,
+ .gen_vec = &xor2g_gen_vec,
+};
+
+static __attribute__((constructor)) void on_dso_load_xor(void)
+{
+ osmo_auth_register(&xor2g_alg);
+}
+
+/*! @} */
diff --git a/src/gsm/bsslap.c b/src/gsm/bsslap.c
index 7886da68..70ded13f 100644
--- a/src/gsm/bsslap.c
+++ b/src/gsm/bsslap.c
@@ -17,10 +17,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <errno.h>
@@ -221,7 +217,7 @@ int osmo_bsslap_dec(struct bsslap_pdu *pdu,
int ies_len;
struct tlv_parsed tp;
- *pdu = (struct bsslap_pdu){};
+ memset(pdu, 0x00, sizeof(*pdu));
if (err)
*err = NULL;
diff --git a/src/gsm/bssmap_le.c b/src/gsm/bssmap_le.c
index 79546532..1ee45517 100644
--- a/src/gsm/bssmap_le.c
+++ b/src/gsm/bssmap_le.c
@@ -17,10 +17,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <string.h>
@@ -184,7 +180,7 @@ int osmo_bssmap_le_ie_dec_location_type(struct bssmap_le_location_type *lt,
struct osmo_bssmap_le_err **err, void *err_ctx,
const uint8_t *elem, uint8_t len)
{
- *lt = (struct bssmap_le_location_type){};
+ memset(lt, 0x00, sizeof(*lt));
if (!elem || len < 1)
DEC_ERR(-EINVAL, msgt, iei, LCS_CAUSE_UNSPECIFIED, "zero length");
@@ -265,6 +261,57 @@ static int osmo_bssmap_le_ie_dec_lcs_client_type(enum bssmap_le_lcs_client_type
}
}
+/*! Encode full BSSMAP-LE LCS Priority IE, including IEI tag and length.
+ * \param[inout] msg Message buffer to append to.
+ * \param[in] priority Value to enconde.
+ * \returns length of bytes written to the msgb.
+ */
+static uint8_t osmo_bssmap_le_ie_enc_lcs_priority(struct msgb *msg, uint8_t priority)
+{
+ OSMO_ASSERT(msg);
+ msgb_put_u8(msg, BSSMAP_LE_IEI_LCS_PRIORITY);
+ /* length */
+ msgb_put_u8(msg, 1);
+ msgb_put_u8(msg, priority);
+ return 3;
+}
+
+static int osmo_bssmap_le_ie_dec_lcs_priority(uint8_t *priority,
+ enum bssmap_le_msgt msgt, enum bssmap_le_iei iei,
+ struct osmo_bssmap_le_err **err, void *err_ctx,
+ const uint8_t *elem, uint8_t len)
+{
+ if (!elem || len != 1)
+ DEC_ERR(-EINVAL, msgt, iei, LCS_CAUSE_UNSPECIFIED, "unexpected length");
+
+ *priority = elem[0];
+ return 0;
+}
+
+/*! Encode full BSSMAP-LE LCS QoS IE, including IEI tag and length.
+ * \param[inout] msg Message buffer to append to.
+ * \param[in] priority Value to enconde.
+ * \returns length of bytes written to the msgb.
+ */
+static uint8_t osmo_bssmap_le_ie_enc_lcs_qos(struct msgb *msg, const struct osmo_bssmap_le_lcs_qos *qos)
+{
+ OSMO_ASSERT(msg);
+ msgb_tlv_put(msg, BSSMAP_LE_IEI_LCS_QoS, sizeof(*qos), (const uint8_t *)qos);
+ return 2 + sizeof(*qos);
+}
+
+static int osmo_bssmap_le_ie_dec_lcs_qos(struct osmo_bssmap_le_lcs_qos *qos,
+ enum bssmap_le_msgt msgt, enum bssmap_le_iei iei,
+ struct osmo_bssmap_le_err **err, void *err_ctx,
+ const uint8_t *elem, uint8_t len)
+{
+ if (!elem || len != sizeof(*qos))
+ DEC_ERR(-EINVAL, msgt, iei, LCS_CAUSE_UNSPECIFIED, "unexpected length");
+
+ memcpy(qos, elem, len);
+ return 0;
+}
+
/*! Encode the value part of 3GPP TS 49.031 10.13 LCS Cause, without IEI and len.
* Identically used in 3GPP TS 48.008 3.2.2.66. Usage example:
*
@@ -301,7 +348,7 @@ int osmo_lcs_cause_dec(struct lcs_cause_ie *lcs_cause,
struct osmo_bssmap_le_err **err, void *err_ctx,
const uint8_t *data, uint8_t len)
{
- *lcs_cause = (struct lcs_cause_ie){};
+ memset(lcs_cause, 0x00, sizeof(*lcs_cause));
if (!data || len < 1)
DEC_ERR(-EINVAL, msgt, iei, LCS_CAUSE_UNSPECIFIED, "zero length");
@@ -476,6 +523,12 @@ static int osmo_bssmap_le_enc_perform_loc_req(struct msgb *msg, const struct bss
if (params->lcs_client_type_present)
osmo_bssmap_le_ie_enc_lcs_client_type(msg, params->lcs_client_type);
+ if (params->more_items && params->lcs_priority_present)
+ osmo_bssmap_le_ie_enc_lcs_priority(msg, params->lcs_priority);
+
+ if (params->more_items && params->lcs_qos_present)
+ osmo_bssmap_le_ie_enc_lcs_qos(msg, &params->lcs_qos);
+
if (params->apdu_present) {
int rc = osmo_bssmap_le_ie_enc_apdu(msg, &params->apdu);
if (rc < 0)
@@ -505,7 +558,7 @@ static int osmo_bssmap_le_dec_perform_loc_req(struct bssmap_le_perform_loc_req *
struct osmo_bssmap_le_err **err, void *err_ctx,
const struct tlv_parsed *tp)
{
- *params = (struct bssmap_le_perform_loc_req){};
+ memset(params, 0x00, sizeof(*params));
DEC_IE_MANDATORY(msgt, BSSMAP_LE_IEI_LOCATION_TYPE, osmo_bssmap_le_ie_dec_location_type,
&params->location_type);
@@ -513,11 +566,18 @@ static int osmo_bssmap_le_dec_perform_loc_req(struct bssmap_le_perform_loc_req *
&params->cell_id);
DEC_IE_OPTIONAL_FLAG(msgt, BSSMAP_LE_IEI_LCS_CLIENT_TYPE, osmo_bssmap_le_ie_dec_lcs_client_type,
&params->lcs_client_type, params->lcs_client_type_present);
+ DEC_IE_OPTIONAL_FLAG(msgt, BSSMAP_LE_IEI_LCS_PRIORITY, osmo_bssmap_le_ie_dec_lcs_priority,
+ &params->lcs_priority, params->lcs_priority_present);
+ DEC_IE_OPTIONAL_FLAG(msgt, BSSMAP_LE_IEI_LCS_QoS, osmo_bssmap_le_ie_dec_lcs_qos,
+ &params->lcs_qos, params->lcs_qos_present);
DEC_IE_OPTIONAL_FLAG(msgt, BSSMAP_LE_IEI_APDU, osmo_bssmap_le_ie_dec_apdu, &params->apdu,
params->apdu_present);
DEC_IE_OPTIONAL(msgt, BSSMAP_LE_IEI_IMSI, osmo_bssmap_le_ie_dec_imsi, &params->imsi);
DEC_IE_OPTIONAL(msgt, BSSMAP_LE_IEI_IMEI, osmo_bssmap_le_ie_dec_imei, &params->imei);
+ if (params->lcs_priority_present || params->lcs_qos_present)
+ params->more_items = true;
+
return 0;
}
@@ -546,7 +606,7 @@ static int osmo_bssmap_le_dec_perform_loc_resp(struct bssmap_le_perform_loc_resp
struct osmo_bssmap_le_err **err, void *err_ctx,
const struct tlv_parsed *tp)
{
- *params = (struct bssmap_le_perform_loc_resp){};
+ memset(params, 0x00, sizeof(*params));
DEC_IE_OPTIONAL_FLAG(msgt, BSSMAP_LE_IEI_GEO_LOCATION, osmo_bssmap_le_ie_dec_gad, &params->location_estimate,
params->location_estimate_present);
@@ -570,7 +630,7 @@ static int osmo_bssmap_le_dec_perform_loc_abort(struct lcs_cause_ie *params,
struct osmo_bssmap_le_err **err, void *err_ctx,
const struct tlv_parsed *tp)
{
- *params = (struct lcs_cause_ie){};
+ memset(params, 0x00, sizeof(*params));
DEC_IE_MANDATORY(msgt, BSSMAP_LE_IEI_LCS_CAUSE, osmo_lcs_cause_dec, params);
return 0;
@@ -587,7 +647,8 @@ static int osmo_bssmap_le_dec_conn_oriented_info(struct bssmap_le_conn_oriented_
struct osmo_bssmap_le_err **err, void *err_ctx,
const struct tlv_parsed *tp)
{
- *params = (struct bssmap_le_conn_oriented_info){};
+ memset(params, 0x00, sizeof(*params));
+
DEC_IE_MANDATORY(msgt, BSSMAP_LE_IEI_APDU, osmo_bssmap_le_ie_dec_apdu, &params->apdu);
return 0;
}
@@ -651,7 +712,7 @@ static int osmo_bssmap_le_dec(struct bssmap_le_pdu *pdu,
int ies_len;
struct tlv_parsed tp;
- *pdu = (struct bssmap_le_pdu){};
+ memset(pdu, 0x00, sizeof(*pdu));
if (len < 1)
DEC_ERR(-EINVAL, -1, -1, LCS_CAUSE_UNSPECIFIED, "zero length");
@@ -738,7 +799,7 @@ int osmo_bssap_le_dec(struct bssap_le_pdu *pdu, struct osmo_bssap_le_err **err,
return RC; \
} while(0)
- *pdu = (struct bssap_le_pdu){};
+ memset(pdu, 0x00, sizeof(*pdu));
h = msgb_l2(msg);
if (!h)
diff --git a/src/gsm/bts_features.c b/src/gsm/bts_features.c
index 93703aaa..b6cd82ec 100644
--- a/src/gsm/bts_features.c
+++ b/src/gsm/bts_features.c
@@ -14,13 +14,9 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
- * MA 02110-1301, USA.
*/
+#include <osmocom/core/utils.h>
#include <osmocom/gsm/bts_features.h>
const struct value_string osmo_bts_features_descs[] = {
@@ -41,12 +37,59 @@ const struct value_string osmo_bts_features_descs[] = {
{ BTS_FEAT_ETWS_PN, "ETWS Primary Notification via PCH" },
{ BTS_FEAT_PAGING_COORDINATION, "BSS Paging Coordination" },
{ BTS_FEAT_IPV6_NSVC, "NSVC IPv6" },
- { BTS_FEAT_ACCH_REP, "FACCH/SACCH Repeation" },
+ { BTS_FEAT_ACCH_REP, "FACCH/SACCH Repetition" },
+ { BTS_FEAT_CCN, "Cell Change Notification (CCN)" },
+ { BTS_FEAT_VAMOS, "VAMOS (Voice services over Adaptive Multi-user channels on One Slot)" },
+ { BTS_FEAT_ABIS_OSMO_PCU, "OsmoPCU over OML link IPA multiplex" },
+ { BTS_FEAT_BCCH_POWER_RED, "BCCH carrier power reduction mode" },
+ { BTS_FEAT_DYN_TS_SDCCH8, "Dynamic Timeslot configuration as SDCCH8" },
+ { BTS_FEAT_ACCH_TEMP_OVP, "FACCH/SACCH Temporary overpower" },
+ { BTS_FEAT_OSMUX, "Osmux (Osmocom RTP multiplexing)" },
+ { BTS_FEAT_VBS, "Voice Broadcast Service" },
+ { BTS_FEAT_VGCS, "Voice Group Call Service" },
{ 0, NULL }
};
-/*! return string representation of a BTS feature */
+/* Ensure that all BTS_FEAT_* entries are present in osmo_bts_features_descs[] */
+osmo_static_assert(ARRAY_SIZE(osmo_bts_features_descs) == _NUM_BTS_FEAT + 1, _bts_features_descs);
+
+/*! return description string of a BTS feature (osmo_bts_features_descs).
+ * To get the plain feature name, use osmo_bts_features_name() instead. */
const char *osmo_bts_feature_name(enum osmo_bts_features feature)
{
return get_value_string(osmo_bts_features_descs, feature);
}
+
+const struct value_string osmo_bts_features_names[] = {
+ { BTS_FEAT_HSCSD, "HSCSD" },
+ { BTS_FEAT_GPRS, "GPRS" },
+ { BTS_FEAT_EGPRS, "EGPRS" },
+ { BTS_FEAT_ECSD, "ECSD" },
+ { BTS_FEAT_HOPPING, "HOPPING" },
+ { BTS_FEAT_MULTI_TSC, "MULTI_TSC" },
+ { BTS_FEAT_OML_ALERTS, "OML_ALERTS" },
+ { BTS_FEAT_AGCH_PCH_PROP, "AGCH_PCH_PROP" },
+ { BTS_FEAT_CBCH, "CBCH" },
+ { BTS_FEAT_SPEECH_F_V1, "SPEECH_F_V1" },
+ { BTS_FEAT_SPEECH_H_V1, "SPEECH_H_V1" },
+ { BTS_FEAT_SPEECH_F_EFR, "SPEECH_F_EFR" },
+ { BTS_FEAT_SPEECH_F_AMR, "SPEECH_F_AMR" },
+ { BTS_FEAT_SPEECH_H_AMR, "SPEECH_H_AMR" },
+ { BTS_FEAT_ETWS_PN, "ETWS_PN" },
+ { BTS_FEAT_PAGING_COORDINATION, "PAGING_COORDINATION" },
+ { BTS_FEAT_IPV6_NSVC, "IPV6_NSVC" },
+ { BTS_FEAT_ACCH_REP, "ACCH_REP" },
+ { BTS_FEAT_CCN, "CCN" },
+ { BTS_FEAT_VAMOS, "VAMOS" },
+ { BTS_FEAT_ABIS_OSMO_PCU, "ABIS_OSMO_PCU" },
+ { BTS_FEAT_BCCH_POWER_RED, "BCCH_PWR_RED" },
+ { BTS_FEAT_DYN_TS_SDCCH8, "DYN_TS_SDCCH8" },
+ { BTS_FEAT_ACCH_TEMP_OVP, "ACCH_TEMP_OVP" },
+ { BTS_FEAT_OSMUX, "OSMUX" },
+ { BTS_FEAT_VBS, "VBS" },
+ { BTS_FEAT_VGCS, "VGCS" },
+ {}
+};
+
+/* Ensure that all BTS_FEAT_* entries are present in osmo_bts_features_names[] */
+osmo_static_assert(ARRAY_SIZE(osmo_bts_features_names) == _NUM_BTS_FEAT + 1, _bts_features_names);
diff --git a/src/gsm/cbsp.c b/src/gsm/cbsp.c
index b89358d4..a5e58f4c 100644
--- a/src/gsm/cbsp.c
+++ b/src/gsm/cbsp.c
@@ -15,10 +15,6 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "config.h"
@@ -34,7 +30,7 @@
#include <osmocom/gsm/cbsp.h>
#include <osmocom/gsm/gsm0808_utils.h>
-const __thread char *osmo_cbsp_errstr;
+__thread const char *osmo_cbsp_errstr;
struct msgb *osmo_cbsp_msgb_alloc(void *ctx, const char *name)
{
@@ -130,13 +126,13 @@ static int encode_wperiod(uint32_t secs)
if (secs <= 10)
return secs;
if (secs <= 30)
- return (secs-10)/2;
+ return 10 + (secs-10)/2;
if (secs <= 120)
- return (secs-30)/5;
+ return 30 + (secs-30)/5;
if (secs <= 600)
- return (secs-120)/10;
+ return 120 + (secs-120)/10;
if (secs <= 60*60)
- return (secs-600)/30;
+ return 600 + (secs-600)/30;
osmo_cbsp_errstr = "warning period out of range";
return -1;
}
@@ -177,12 +173,15 @@ static int cbsp_enc_write_repl(struct msgb *msg, const struct osmo_cbsp_write_re
}
} else {
int wperiod = encode_wperiod(in->u.emergency.warning_period);
+ uint8_t *cur;
if (wperiod < 0)
return -EINVAL;
msgb_tv_put(msg, CBSP_IEI_EMERG_IND, in->u.emergency.indicator);
msgb_tv16_put(msg, CBSP_IEI_WARN_TYPE, in->u.emergency.warning_type);
- msgb_tlv_put(msg, CBSP_IEI_WARN_SEC_INFO, sizeof(in->u.emergency.warning_sec_info),
- in->u.emergency.warning_sec_info);
+ /* Tag + fixed length value! */
+ msgb_put_u8(msg, CBSP_IEI_WARN_SEC_INFO);
+ cur = msgb_put(msg, sizeof(in->u.emergency.warning_sec_info));
+ memcpy(cur, in->u.emergency.warning_sec_info, sizeof(in->u.emergency.warning_sec_info));
msgb_tv_put(msg, CBSP_IEI_WARNING_PERIOD, wperiod);
}
return 0;
@@ -349,7 +348,12 @@ static int cbsp_enc_reset_fail(struct msgb *msg, const struct osmo_cbsp_reset_fa
/* 8.1.3.18a KEEP ALIVE */
static int cbsp_enc_keep_alive(struct msgb *msg, const struct osmo_cbsp_keep_alive *in)
{
- msgb_tv_put(msg, CBSP_IEI_KEEP_ALIVE_REP_PERIOD, in->repetition_period);
+ int rperiod = encode_wperiod(in->repetition_period);
+ if (in->repetition_period > 120)
+ return -EINVAL;
+ if (rperiod < 0)
+ return -EINVAL;
+ msgb_tv_put(msg, CBSP_IEI_KEEP_ALIVE_REP_PERIOD, rperiod);
return 0;
}
@@ -533,8 +537,8 @@ static int cbsp_decode_fail_list(struct llist_head *fl, void *ctx,
struct osmo_cbsp_fail_ent *ent = talloc_zero(ctx, struct osmo_cbsp_fail_ent);
unsigned int len_remain = len - (cur - buf);
OSMO_ASSERT(ent);
- ent->id_discr = cur[0];
- rc = gsm0808_decode_cell_id_u(&ent->cell_id, ent->id_discr, cur+1, len_remain-1);
+ ent->id_discr = *cur++;
+ rc = gsm0808_decode_cell_id_u(&ent->cell_id, ent->id_discr, cur, len_remain-1);
if (rc < 0) {
osmo_cbsp_errstr = "fail list: error decoding cell_id_union";
return rc;
@@ -635,6 +639,7 @@ static int cbsp_dec_write_repl(struct osmo_cbsp_write_replace *out, const struct
struct msgb *in, void *ctx)
{
unsigned int i;
+ int rc;
/* check for mandatory IEs */
if (!TLVP_PRESENT(tp, CBSP_IEI_MSG_ID) ||
@@ -652,8 +657,10 @@ static int cbsp_dec_write_repl(struct osmo_cbsp_write_replace *out, const struct
}
INIT_LLIST_HEAD(&out->cell_list.list);
- cbsp_decode_cell_list(&out->cell_list, ctx, TLVP_VAL(tp, CBSP_IEI_CELL_LIST),
- TLVP_LEN(tp, CBSP_IEI_CELL_LIST));
+ rc = cbsp_decode_cell_list(&out->cell_list, ctx, TLVP_VAL(tp, CBSP_IEI_CELL_LIST),
+ TLVP_LEN(tp, CBSP_IEI_CELL_LIST));
+ if (rc < 0)
+ return rc;
if (TLVP_PRESENT(tp, CBSP_IEI_CHANNEL_IND)) {
uint8_t num_of_pages;
@@ -675,6 +682,7 @@ static int cbsp_dec_write_repl(struct osmo_cbsp_write_replace *out, const struct
out->u.cbs.category = *TLVP_VAL(tp, CBSP_IEI_CATEGORY);
out->u.cbs.rep_period = tlvp_val16be(tp, CBSP_IEI_REP_PERIOD);
out->u.cbs.num_bcast_req = tlvp_val16be(tp, CBSP_IEI_NUM_BCAST_REQ);
+ out->u.cbs.dcs = *TLVP_VAL(tp, CBSP_IEI_DCS);
num_of_pages = *TLVP_VAL(tp, CBSP_IEI_NUM_OF_PAGES);
if (num_of_pages < 1)
return -EINVAL;
@@ -713,6 +721,8 @@ static int cbsp_dec_write_repl(struct osmo_cbsp_write_replace *out, const struct
static int cbsp_dec_write_repl_compl(struct osmo_cbsp_write_replace_complete *out,
const struct tlv_parsed *tp, struct msgb *in, void *ctx)
{
+ int rc;
+
if (!TLVP_PRES_LEN(tp, CBSP_IEI_MSG_ID, 2) ||
!TLVP_PRES_LEN(tp, CBSP_IEI_NEW_SERIAL_NR, 2)) {
osmo_cbsp_errstr = "missing/short mandatory IE";
@@ -728,14 +738,18 @@ static int cbsp_dec_write_repl_compl(struct osmo_cbsp_write_replace_complete *ou
INIT_LLIST_HEAD(&out->num_compl_list.list);
if (TLVP_PRES_LEN(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST, 7)) {
- cbsp_decode_num_compl_list(&out->num_compl_list, ctx,
- TLVP_VAL(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST),
- TLVP_LEN(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST));
+ rc = cbsp_decode_num_compl_list(&out->num_compl_list, ctx,
+ TLVP_VAL(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST),
+ TLVP_LEN(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST));
+ if (rc < 0)
+ return rc;
}
INIT_LLIST_HEAD(&out->cell_list.list);
- cbsp_decode_cell_list(&out->cell_list, ctx, TLVP_VAL(tp, CBSP_IEI_CELL_LIST),
- TLVP_LEN(tp, CBSP_IEI_CELL_LIST));
+ rc = cbsp_decode_cell_list(&out->cell_list, ctx, TLVP_VAL(tp, CBSP_IEI_CELL_LIST),
+ TLVP_LEN(tp, CBSP_IEI_CELL_LIST));
+ if (rc < 0)
+ return rc;
if (TLVP_PRES_LEN(tp, CBSP_IEI_CHANNEL_IND, 1)) {
out->channel_ind = talloc(ctx, enum cbsp_channel_ind);
@@ -748,6 +762,8 @@ static int cbsp_dec_write_repl_compl(struct osmo_cbsp_write_replace_complete *ou
static int cbsp_dec_write_repl_fail(struct osmo_cbsp_write_replace_failure *out,
const struct tlv_parsed *tp, struct msgb *in, void *ctx)
{
+ int rc;
+
if (!TLVP_PRES_LEN(tp, CBSP_IEI_MSG_ID, 2) ||
!TLVP_PRES_LEN(tp, CBSP_IEI_NEW_SERIAL_NR, 2) ||
!TLVP_PRES_LEN(tp, CBSP_IEI_FAILURE_LIST, 5)) {
@@ -763,21 +779,27 @@ static int cbsp_dec_write_repl_fail(struct osmo_cbsp_write_replace_failure *out,
}
INIT_LLIST_HEAD(&out->fail_list);
- cbsp_decode_fail_list(&out->fail_list, ctx,
- TLVP_VAL(tp, CBSP_IEI_FAILURE_LIST),
- TLVP_LEN(tp, CBSP_IEI_FAILURE_LIST));
+ rc = cbsp_decode_fail_list(&out->fail_list, ctx,
+ TLVP_VAL(tp, CBSP_IEI_FAILURE_LIST),
+ TLVP_LEN(tp, CBSP_IEI_FAILURE_LIST));
+ if (rc < 0)
+ return rc;
INIT_LLIST_HEAD(&out->num_compl_list.list);
if (TLVP_PRES_LEN(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST, 7)) {
- cbsp_decode_num_compl_list(&out->num_compl_list, ctx,
- TLVP_VAL(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST),
- TLVP_LEN(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST));
+ rc = cbsp_decode_num_compl_list(&out->num_compl_list, ctx,
+ TLVP_VAL(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST),
+ TLVP_LEN(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST));
+ if (rc < 0)
+ return rc;
}
INIT_LLIST_HEAD(&out->cell_list.list);
if (TLVP_PRES_LEN(tp, CBSP_IEI_CELL_LIST, 1)) {
- cbsp_decode_cell_list(&out->cell_list, ctx, TLVP_VAL(tp, CBSP_IEI_CELL_LIST),
- TLVP_LEN(tp, CBSP_IEI_CELL_LIST));
+ rc = cbsp_decode_cell_list(&out->cell_list, ctx, TLVP_VAL(tp, CBSP_IEI_CELL_LIST),
+ TLVP_LEN(tp, CBSP_IEI_CELL_LIST));
+ if (rc < 0)
+ return rc;
}
if (TLVP_PRES_LEN(tp, CBSP_IEI_CHANNEL_IND, 1)) {
@@ -791,6 +813,8 @@ static int cbsp_dec_write_repl_fail(struct osmo_cbsp_write_replace_failure *out,
static int cbsp_dec_kill(struct osmo_cbsp_kill *out, const struct tlv_parsed *tp,
struct msgb *in, void *ctx)
{
+ int rc;
+
if (!TLVP_PRES_LEN(tp, CBSP_IEI_MSG_ID, 2) ||
!TLVP_PRES_LEN(tp, CBSP_IEI_OLD_SERIAL_NR, 2) ||
!TLVP_PRES_LEN(tp, CBSP_IEI_CELL_LIST, 1)) {
@@ -802,8 +826,10 @@ static int cbsp_dec_kill(struct osmo_cbsp_kill *out, const struct tlv_parsed *tp
out->old_serial_nr = tlvp_val16be(tp, CBSP_IEI_OLD_SERIAL_NR);
INIT_LLIST_HEAD(&out->cell_list.list);
- cbsp_decode_cell_list(&out->cell_list, ctx, TLVP_VAL(tp, CBSP_IEI_CELL_LIST),
- TLVP_LEN(tp, CBSP_IEI_CELL_LIST));
+ rc = cbsp_decode_cell_list(&out->cell_list, ctx, TLVP_VAL(tp, CBSP_IEI_CELL_LIST),
+ TLVP_LEN(tp, CBSP_IEI_CELL_LIST));
+ if (rc < 0)
+ return rc;
if (TLVP_PRES_LEN(tp, CBSP_IEI_CHANNEL_IND, 1)) {
out->channel_ind = talloc(ctx, enum cbsp_channel_ind);
@@ -816,6 +842,8 @@ static int cbsp_dec_kill(struct osmo_cbsp_kill *out, const struct tlv_parsed *tp
static int cbsp_dec_kill_compl(struct osmo_cbsp_kill_complete *out, const struct tlv_parsed *tp,
struct msgb *in, void *ctx)
{
+ int rc;
+
if (!TLVP_PRES_LEN(tp, CBSP_IEI_MSG_ID, 2) ||
!TLVP_PRES_LEN(tp, CBSP_IEI_OLD_SERIAL_NR, 2) ||
!TLVP_PRES_LEN(tp, CBSP_IEI_CELL_LIST, 1)) {
@@ -828,14 +856,18 @@ static int cbsp_dec_kill_compl(struct osmo_cbsp_kill_complete *out, const struct
INIT_LLIST_HEAD(&out->num_compl_list.list);
if (TLVP_PRES_LEN(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST, 7)) {
- cbsp_decode_num_compl_list(&out->num_compl_list, ctx,
- TLVP_VAL(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST),
- TLVP_LEN(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST));
+ rc = cbsp_decode_num_compl_list(&out->num_compl_list, ctx,
+ TLVP_VAL(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST),
+ TLVP_LEN(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST));
+ if (rc < 0)
+ return rc;
}
INIT_LLIST_HEAD(&out->cell_list.list);
- cbsp_decode_cell_list(&out->cell_list, ctx, TLVP_VAL(tp, CBSP_IEI_CELL_LIST),
- TLVP_LEN(tp, CBSP_IEI_CELL_LIST));
+ rc = cbsp_decode_cell_list(&out->cell_list, ctx, TLVP_VAL(tp, CBSP_IEI_CELL_LIST),
+ TLVP_LEN(tp, CBSP_IEI_CELL_LIST));
+ if (rc < 0)
+ return rc;
if (TLVP_PRES_LEN(tp, CBSP_IEI_CHANNEL_IND, 1)) {
out->channel_ind = talloc(ctx, enum cbsp_channel_ind);
@@ -848,6 +880,8 @@ static int cbsp_dec_kill_compl(struct osmo_cbsp_kill_complete *out, const struct
static int cbsp_dec_kill_fail(struct osmo_cbsp_kill_failure *out, const struct tlv_parsed *tp,
struct msgb *in, void *ctx)
{
+ int rc;
+
if (!TLVP_PRES_LEN(tp, CBSP_IEI_MSG_ID, 2) ||
!TLVP_PRES_LEN(tp, CBSP_IEI_OLD_SERIAL_NR, 2) ||
!TLVP_PRES_LEN(tp, CBSP_IEI_FAILURE_LIST, 5)) {
@@ -859,21 +893,27 @@ static int cbsp_dec_kill_fail(struct osmo_cbsp_kill_failure *out, const struct t
out->old_serial_nr = tlvp_val16be(tp, CBSP_IEI_OLD_SERIAL_NR);
INIT_LLIST_HEAD(&out->fail_list);
- cbsp_decode_fail_list(&out->fail_list, ctx,
- TLVP_VAL(tp, CBSP_IEI_FAILURE_LIST),
- TLVP_LEN(tp, CBSP_IEI_FAILURE_LIST));
+ rc = cbsp_decode_fail_list(&out->fail_list, ctx,
+ TLVP_VAL(tp, CBSP_IEI_FAILURE_LIST),
+ TLVP_LEN(tp, CBSP_IEI_FAILURE_LIST));
+ if (rc < 0)
+ return rc;
INIT_LLIST_HEAD(&out->num_compl_list.list);
if (TLVP_PRES_LEN(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST, 7)) {
- cbsp_decode_num_compl_list(&out->num_compl_list, ctx,
- TLVP_VAL(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST),
- TLVP_LEN(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST));
+ rc = cbsp_decode_num_compl_list(&out->num_compl_list, ctx,
+ TLVP_VAL(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST),
+ TLVP_LEN(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST));
+ if (rc < 0)
+ return rc;
}
INIT_LLIST_HEAD(&out->cell_list.list);
if (TLVP_PRES_LEN(tp, CBSP_IEI_CELL_LIST, 1)) {
- cbsp_decode_cell_list(&out->cell_list, ctx, TLVP_VAL(tp, CBSP_IEI_CELL_LIST),
- TLVP_LEN(tp, CBSP_IEI_CELL_LIST));
+ rc = cbsp_decode_cell_list(&out->cell_list, ctx, TLVP_VAL(tp, CBSP_IEI_CELL_LIST),
+ TLVP_LEN(tp, CBSP_IEI_CELL_LIST));
+ if (rc < 0)
+ return rc;
}
if (TLVP_PRES_LEN(tp, CBSP_IEI_CHANNEL_IND, 1)) {
@@ -887,6 +927,8 @@ static int cbsp_dec_kill_fail(struct osmo_cbsp_kill_failure *out, const struct t
static int cbsp_dec_load_query(struct osmo_cbsp_load_query *out, const struct tlv_parsed *tp,
struct msgb *in, void *ctx)
{
+ int rc;
+
if (!TLVP_PRES_LEN(tp, CBSP_IEI_CELL_LIST, 1) ||
!TLVP_PRES_LEN(tp, CBSP_IEI_CHANNEL_IND, 1)) {
osmo_cbsp_errstr = "missing/short mandatory IE";
@@ -894,8 +936,10 @@ static int cbsp_dec_load_query(struct osmo_cbsp_load_query *out, const struct tl
}
INIT_LLIST_HEAD(&out->cell_list.list);
- cbsp_decode_cell_list(&out->cell_list, ctx, TLVP_VAL(tp, CBSP_IEI_CELL_LIST),
- TLVP_LEN(tp, CBSP_IEI_CELL_LIST));
+ rc = cbsp_decode_cell_list(&out->cell_list, ctx, TLVP_VAL(tp, CBSP_IEI_CELL_LIST),
+ TLVP_LEN(tp, CBSP_IEI_CELL_LIST));
+ if (rc < 0)
+ return rc;
out->channel_ind = *TLVP_VAL(tp, CBSP_IEI_CHANNEL_IND);
return 0;
@@ -905,6 +949,8 @@ static int cbsp_dec_load_query(struct osmo_cbsp_load_query *out, const struct tl
static int cbsp_dec_load_query_compl(struct osmo_cbsp_load_query_complete *out,
const struct tlv_parsed *tp, struct msgb *in, void *ctx)
{
+ int rc;
+
if (!TLVP_PRES_LEN(tp, CBSP_IEI_RR_LOADING_LIST, 6) ||
!TLVP_PRES_LEN(tp, CBSP_IEI_CHANNEL_IND, 1)) {
osmo_cbsp_errstr = "missing/short mandatory IE";
@@ -912,9 +958,11 @@ static int cbsp_dec_load_query_compl(struct osmo_cbsp_load_query_complete *out,
}
INIT_LLIST_HEAD(&out->loading_list.list);
- cbsp_decode_loading_list(&out->loading_list, ctx,
- TLVP_VAL(tp, CBSP_IEI_RR_LOADING_LIST),
- TLVP_LEN(tp, CBSP_IEI_RR_LOADING_LIST));
+ rc = cbsp_decode_loading_list(&out->loading_list, ctx,
+ TLVP_VAL(tp, CBSP_IEI_RR_LOADING_LIST),
+ TLVP_LEN(tp, CBSP_IEI_RR_LOADING_LIST));
+ if (rc < 0)
+ return rc;
out->channel_ind = *TLVP_VAL(tp, CBSP_IEI_CHANNEL_IND);
return 0;
@@ -924,6 +972,8 @@ static int cbsp_dec_load_query_compl(struct osmo_cbsp_load_query_complete *out,
static int cbsp_dec_load_query_fail(struct osmo_cbsp_load_query_failure *out,
const struct tlv_parsed *tp, struct msgb *in, void *ctx)
{
+ int rc;
+
if (!TLVP_PRES_LEN(tp, CBSP_IEI_FAILURE_LIST, 5) ||
!TLVP_PRES_LEN(tp, CBSP_IEI_CHANNEL_IND, 1)) {
osmo_cbsp_errstr = "missing/short mandatory IE";
@@ -931,25 +981,29 @@ static int cbsp_dec_load_query_fail(struct osmo_cbsp_load_query_failure *out,
}
INIT_LLIST_HEAD(&out->fail_list);
- cbsp_decode_fail_list(&out->fail_list, ctx,
- TLVP_VAL(tp, CBSP_IEI_FAILURE_LIST),
- TLVP_LEN(tp, CBSP_IEI_FAILURE_LIST));
+ rc = cbsp_decode_fail_list(&out->fail_list, ctx,
+ TLVP_VAL(tp, CBSP_IEI_FAILURE_LIST),
+ TLVP_LEN(tp, CBSP_IEI_FAILURE_LIST));
+ if (rc < 0)
+ return rc;
out->channel_ind = *TLVP_VAL(tp, CBSP_IEI_CHANNEL_IND);
INIT_LLIST_HEAD(&out->loading_list.list);
if (TLVP_PRES_LEN(tp, CBSP_IEI_RR_LOADING_LIST, 6)) {
- cbsp_decode_loading_list(&out->loading_list, ctx,
- TLVP_VAL(tp, CBSP_IEI_RR_LOADING_LIST),
- TLVP_LEN(tp, CBSP_IEI_RR_LOADING_LIST));
+ rc = cbsp_decode_loading_list(&out->loading_list, ctx,
+ TLVP_VAL(tp, CBSP_IEI_RR_LOADING_LIST),
+ TLVP_LEN(tp, CBSP_IEI_RR_LOADING_LIST));
}
- return 0;
+ return rc;
}
/* 8.1.3.10 STATUS QUERY */
static int cbsp_dec_msg_status_query(struct osmo_cbsp_msg_status_query *out,
const struct tlv_parsed *tp, struct msgb *in, void *ctx)
{
+ int rc;
+
if (!TLVP_PRES_LEN(tp, CBSP_IEI_MSG_ID, 2) ||
!TLVP_PRES_LEN(tp, CBSP_IEI_OLD_SERIAL_NR, 2) ||
!TLVP_PRES_LEN(tp, CBSP_IEI_CELL_LIST, 1) ||
@@ -962,8 +1016,10 @@ static int cbsp_dec_msg_status_query(struct osmo_cbsp_msg_status_query *out,
out->old_serial_nr = tlvp_val16be(tp, CBSP_IEI_OLD_SERIAL_NR);
INIT_LLIST_HEAD(&out->cell_list.list);
- cbsp_decode_cell_list(&out->cell_list, ctx, TLVP_VAL(tp, CBSP_IEI_CELL_LIST),
- TLVP_LEN(tp, CBSP_IEI_CELL_LIST));
+ rc = cbsp_decode_cell_list(&out->cell_list, ctx, TLVP_VAL(tp, CBSP_IEI_CELL_LIST),
+ TLVP_LEN(tp, CBSP_IEI_CELL_LIST));
+ if (rc < 0)
+ return rc;
out->channel_ind = *TLVP_VAL(tp, CBSP_IEI_CHANNEL_IND);
return 0;
@@ -973,6 +1029,8 @@ static int cbsp_dec_msg_status_query(struct osmo_cbsp_msg_status_query *out,
static int cbsp_dec_msg_status_query_compl(struct osmo_cbsp_msg_status_query_complete *out,
const struct tlv_parsed *tp, struct msgb *in, void *ctx)
{
+ int rc;
+
if (!TLVP_PRES_LEN(tp, CBSP_IEI_MSG_ID, 2) ||
!TLVP_PRES_LEN(tp, CBSP_IEI_OLD_SERIAL_NR, 2) ||
!TLVP_PRES_LEN(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST, 7) ||
@@ -985,9 +1043,11 @@ static int cbsp_dec_msg_status_query_compl(struct osmo_cbsp_msg_status_query_com
out->old_serial_nr = tlvp_val16be(tp, CBSP_IEI_OLD_SERIAL_NR);
INIT_LLIST_HEAD(&out->num_compl_list.list);
- cbsp_decode_num_compl_list(&out->num_compl_list, ctx,
- TLVP_VAL(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST),
- TLVP_LEN(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST));
+ rc = cbsp_decode_num_compl_list(&out->num_compl_list, ctx,
+ TLVP_VAL(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST),
+ TLVP_LEN(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST));
+ if (rc < 0)
+ return rc;
out->channel_ind = *TLVP_VAL(tp, CBSP_IEI_CHANNEL_IND);
return 0;
}
@@ -996,6 +1056,8 @@ static int cbsp_dec_msg_status_query_compl(struct osmo_cbsp_msg_status_query_com
static int cbsp_dec_msg_status_query_fail(struct osmo_cbsp_msg_status_query_failure *out,
const struct tlv_parsed *tp, struct msgb *in, void *ctx)
{
+ int rc;
+
if (!TLVP_PRES_LEN(tp, CBSP_IEI_MSG_ID, 2) ||
!TLVP_PRES_LEN(tp, CBSP_IEI_OLD_SERIAL_NR, 2) ||
!TLVP_PRES_LEN(tp, CBSP_IEI_FAILURE_LIST, 5) ||
@@ -1008,17 +1070,21 @@ static int cbsp_dec_msg_status_query_fail(struct osmo_cbsp_msg_status_query_fail
out->old_serial_nr = tlvp_val16be(tp, CBSP_IEI_OLD_SERIAL_NR);
INIT_LLIST_HEAD(&out->fail_list);
- cbsp_decode_fail_list(&out->fail_list, ctx,
- TLVP_VAL(tp, CBSP_IEI_FAILURE_LIST),
- TLVP_LEN(tp, CBSP_IEI_FAILURE_LIST));
+ rc = cbsp_decode_fail_list(&out->fail_list, ctx,
+ TLVP_VAL(tp, CBSP_IEI_FAILURE_LIST),
+ TLVP_LEN(tp, CBSP_IEI_FAILURE_LIST));
+ if (rc < 0)
+ return rc;
out->channel_ind = *TLVP_VAL(tp, CBSP_IEI_CHANNEL_IND);
INIT_LLIST_HEAD(&out->num_compl_list.list);
if (TLVP_PRES_LEN(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST, 7)) {
- cbsp_decode_num_compl_list(&out->num_compl_list, ctx,
- TLVP_VAL(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST),
- TLVP_LEN(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST));
+ rc = cbsp_decode_num_compl_list(&out->num_compl_list, ctx,
+ TLVP_VAL(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST),
+ TLVP_LEN(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST));
+ if (rc < 0)
+ return rc;
}
return 0;
}
@@ -1027,14 +1093,19 @@ static int cbsp_dec_msg_status_query_fail(struct osmo_cbsp_msg_status_query_fail
static int cbsp_dec_reset(struct osmo_cbsp_reset *out, const struct tlv_parsed *tp,
struct msgb *in, void *ctx)
{
+ int rc;
+
if (!TLVP_PRES_LEN(tp, CBSP_IEI_CELL_LIST, 1)) {
osmo_cbsp_errstr = "missing/short mandatory IE";
return -EINVAL;
}
INIT_LLIST_HEAD(&out->cell_list.list);
- cbsp_decode_cell_list(&out->cell_list, ctx, TLVP_VAL(tp, CBSP_IEI_CELL_LIST),
- TLVP_LEN(tp, CBSP_IEI_CELL_LIST));
+ rc = cbsp_decode_cell_list(&out->cell_list, ctx, TLVP_VAL(tp, CBSP_IEI_CELL_LIST),
+ TLVP_LEN(tp, CBSP_IEI_CELL_LIST));
+ if (rc < 0)
+ return rc;
+
return 0;
}
@@ -1042,14 +1113,19 @@ static int cbsp_dec_reset(struct osmo_cbsp_reset *out, const struct tlv_parsed *
static int cbsp_dec_reset_compl(struct osmo_cbsp_reset_complete *out, const struct tlv_parsed *tp,
struct msgb *in, void *ctx)
{
+ int rc;
+
if (!TLVP_PRES_LEN(tp, CBSP_IEI_CELL_LIST, 1)) {
osmo_cbsp_errstr = "missing/short mandatory IE";
return -EINVAL;
}
INIT_LLIST_HEAD(&out->cell_list.list);
- cbsp_decode_cell_list(&out->cell_list, ctx, TLVP_VAL(tp, CBSP_IEI_CELL_LIST),
- TLVP_LEN(tp, CBSP_IEI_CELL_LIST));
+ rc = cbsp_decode_cell_list(&out->cell_list, ctx, TLVP_VAL(tp, CBSP_IEI_CELL_LIST),
+ TLVP_LEN(tp, CBSP_IEI_CELL_LIST));
+ if (rc < 0)
+ return rc;
+
return 0;
}
@@ -1057,20 +1133,26 @@ static int cbsp_dec_reset_compl(struct osmo_cbsp_reset_complete *out, const stru
static int cbsp_dec_reset_fail(struct osmo_cbsp_reset_failure *out, const struct tlv_parsed *tp,
struct msgb *in, void *ctx)
{
+ int rc;
+
if (!TLVP_PRES_LEN(tp, CBSP_IEI_FAILURE_LIST, 5)) {
osmo_cbsp_errstr = "missing/short mandatory IE";
return -EINVAL;
}
INIT_LLIST_HEAD(&out->fail_list);
- cbsp_decode_fail_list(&out->fail_list, ctx,
- TLVP_VAL(tp, CBSP_IEI_FAILURE_LIST),
- TLVP_LEN(tp, CBSP_IEI_FAILURE_LIST));
+ rc = cbsp_decode_fail_list(&out->fail_list, ctx,
+ TLVP_VAL(tp, CBSP_IEI_FAILURE_LIST),
+ TLVP_LEN(tp, CBSP_IEI_FAILURE_LIST));
+ if (rc < 0)
+ return rc;
INIT_LLIST_HEAD(&out->cell_list.list);
if (TLVP_PRES_LEN(tp, CBSP_IEI_CELL_LIST, 1)) {
- cbsp_decode_cell_list(&out->cell_list, ctx, TLVP_VAL(tp, CBSP_IEI_CELL_LIST),
- TLVP_LEN(tp, CBSP_IEI_CELL_LIST));
+ rc = cbsp_decode_cell_list(&out->cell_list, ctx, TLVP_VAL(tp, CBSP_IEI_CELL_LIST),
+ TLVP_LEN(tp, CBSP_IEI_CELL_LIST));
+ if (rc < 0)
+ return rc;
}
return 0;
}
@@ -1079,12 +1161,14 @@ static int cbsp_dec_reset_fail(struct osmo_cbsp_reset_failure *out, const struct
static int cbsp_dec_keep_alive(struct osmo_cbsp_keep_alive *out, const struct tlv_parsed *tp,
struct msgb *in, void *ctx)
{
+ uint8_t rperiod;
if (!TLVP_PRES_LEN(tp, CBSP_IEI_KEEP_ALIVE_REP_PERIOD, 1)) {
osmo_cbsp_errstr = "missing/short mandatory IE";
return -EINVAL;
}
- out->repetition_period = *TLVP_VAL(tp, CBSP_IEI_KEEP_ALIVE_REP_PERIOD);
+ rperiod = *TLVP_VAL(tp, CBSP_IEI_KEEP_ALIVE_REP_PERIOD);
+ out->repetition_period = decode_wperiod(rperiod);
return 0;
}
@@ -1099,6 +1183,8 @@ static int cbsp_dec_keep_alive_compl(struct osmo_cbsp_keep_alive_complete *out,
static int cbsp_dec_restart(struct osmo_cbsp_restart *out, const struct tlv_parsed *tp,
struct msgb *in, void *ctx)
{
+ int rc;
+
if (!TLVP_PRES_LEN(tp, CBSP_IEI_CELL_LIST, 1) ||
!TLVP_PRES_LEN(tp, CBSP_IEI_BCAST_MSG_TYPE, 1) ||
!TLVP_PRES_LEN(tp, CBSP_IEI_RECOVERY_IND, 1)) {
@@ -1107,8 +1193,10 @@ static int cbsp_dec_restart(struct osmo_cbsp_restart *out, const struct tlv_pars
}
INIT_LLIST_HEAD(&out->cell_list.list);
- cbsp_decode_cell_list(&out->cell_list, ctx, TLVP_VAL(tp, CBSP_IEI_CELL_LIST),
- TLVP_LEN(tp, CBSP_IEI_CELL_LIST));
+ rc = cbsp_decode_cell_list(&out->cell_list, ctx, TLVP_VAL(tp, CBSP_IEI_CELL_LIST),
+ TLVP_LEN(tp, CBSP_IEI_CELL_LIST));
+ if (rc < 0)
+ return rc;
out->bcast_msg_type = *TLVP_VAL(tp, CBSP_IEI_BCAST_MSG_TYPE);
out->recovery_ind = *TLVP_VAL(tp, CBSP_IEI_RECOVERY_IND);
@@ -1119,6 +1207,8 @@ static int cbsp_dec_restart(struct osmo_cbsp_restart *out, const struct tlv_pars
static int cbsp_dec_failure(struct osmo_cbsp_failure *out, const struct tlv_parsed *tp,
struct msgb *in, void *ctx)
{
+ int rc;
+
if (!TLVP_PRES_LEN(tp, CBSP_IEI_FAILURE_LIST, 5) ||
!TLVP_PRES_LEN(tp, CBSP_IEI_BCAST_MSG_TYPE, 1)) {
osmo_cbsp_errstr = "missing/short mandatory IE";
@@ -1126,9 +1216,11 @@ static int cbsp_dec_failure(struct osmo_cbsp_failure *out, const struct tlv_pars
}
INIT_LLIST_HEAD(&out->fail_list);
- cbsp_decode_fail_list(&out->fail_list, ctx,
- TLVP_VAL(tp, CBSP_IEI_FAILURE_LIST),
- TLVP_LEN(tp, CBSP_IEI_FAILURE_LIST));
+ rc = cbsp_decode_fail_list(&out->fail_list, ctx,
+ TLVP_VAL(tp, CBSP_IEI_FAILURE_LIST),
+ TLVP_LEN(tp, CBSP_IEI_FAILURE_LIST));
+ if (rc < 0)
+ return rc;
out->bcast_msg_type = *TLVP_VAL(tp, CBSP_IEI_BCAST_MSG_TYPE);
return 0;
@@ -1169,6 +1261,7 @@ static int cbsp_dec_error_ind(struct osmo_cbsp_error_ind *out, const struct tlv_
* \returns callee-allocated decoded representation of CBSP message; NULL on error */
struct osmo_cbsp_decoded *osmo_cbsp_decode(void *ctx, struct msgb *in)
{
+ OSMO_ASSERT(in->l1h != NULL && in->l2h != NULL);
struct osmo_cbsp_decoded *out = talloc_zero(ctx, struct osmo_cbsp_decoded);
const struct cbsp_header *h = msgb_l1(in);
struct tlv_parsed tp[16]; /* max. number of pages in a given CBS message */
@@ -1434,6 +1527,10 @@ int osmo_cbsp_recv_buffered(void *ctx, int fd, struct msgb **rmsg, struct msgb *
needed = len - msgb_l2len(msg);
if (needed > 0) {
+ if (needed > msgb_tailroom(msg)) {
+ rc = -ENOMEM;
+ goto discard_msg;
+ }
rc = recv(fd, msg->tail, needed, 0);
if (rc == 0)
goto discard_msg;
@@ -1457,12 +1554,7 @@ int osmo_cbsp_recv_buffered(void *ctx, int fd, struct msgb **rmsg, struct msgb *
}
}
/* else: complete message received */
- rc = msgb_l2len(msg);
- if (rc == 0) {
- /* drop empty message */
- rc = -EAGAIN;
- goto discard_msg;
- }
+ rc = msgb_length(msg);
if (tmp_msg)
*tmp_msg = NULL;
*rmsg = msg;
@@ -1475,4 +1567,45 @@ discard_msg:
return rc;
}
+/*! call-back function to segment the data at message boundaries.
+ * Returns the size of the next message. If it returns -EAGAIN or a value larger than msgb_length() (message
+ * is incomplete), the caller (e.g. osmo_io) has to wait for more data to be read. */
+int osmo_cbsp_segmentation_cb(struct msgb *msg)
+{
+ const struct cbsp_header *h;
+ int len;
+
+ if (msgb_length(msg) < sizeof(*h))
+ return -EAGAIN;
+
+ h = (const struct cbsp_header *) msg->data;
+ msg->l1h = msg->data;
+ msg->l2h = msg->data + sizeof(*h);
+ /* then read the length as specified in the header */
+ len = h->len[0] << 16 | h->len[1] << 8 | h->len[2];
+
+ return sizeof(*h) + len;
+}
+
+/*! value_string[] for enum osmo_cbsp_cause. */
+const struct value_string osmo_cbsp_cause_names[] = {
+ { OSMO_CBSP_CAUSE_PARAM_NOT_RECOGNISED, "Parameter-not-recognised" },
+ { OSMO_CBSP_CAUSE_PARAM_VALUE_INVALID, "Parameter-value-invalid" },
+ { OSMO_CBSP_CAUSE_MSG_REF_NOT_IDENTIFIED, "Message-reference-not-identified" },
+ { OSMO_CBSP_CAUSE_CELL_ID_NOT_VALID, "Cell-identity-not-valid" },
+ { OSMO_CBSP_CAUSE_UNRECOGNISED_MESSAGE, "Unrecognised-message" },
+ { OSMO_CBSP_CAUSE_MISSING_MANDATORY_ELEMENT, "Missing-mandatory-element" },
+ { OSMO_CBSP_CAUSE_BSC_CAPACITY_EXCEEDED, "BSC-capacity-exceeded" },
+ { OSMO_CBSP_CAUSE_CELL_MEMORY_EXCEEDED, "Cell-memory-exceeded" },
+ { OSMO_CBSP_CAUSE_BSC_MEMORY_EXCEEDED, "BSC-memory-exceeded" },
+ { OSMO_CBSP_CAUSE_CELL_BROADCAST_NOT_SUPPORTED, "Cell-broadcast-not-supported" },
+ { OSMO_CBSP_CAUSE_CELL_BROADCAST_NOT_OPERATIONAL, "Cell-broadcast-not-operational" },
+ { OSMO_CBSP_CAUSE_INCOMPATIBLE_DRX_PARAM, "Incompatible-DRX-parameter:"},
+ { OSMO_CBSP_CAUSE_EXT_CHAN_NOT_SUPPORTED, "Extended-channel-not-supported"},
+ { OSMO_CBSP_CAUSE_MSG_REF_ALREADY_USED, "Message-reference-already-used"},
+ { OSMO_CBSP_CAUSE_UNSPECIFIED_ERROR, "Unspecified-error"},
+ { OSMO_CBSP_CAUSE_LAI_OR_LAC_NOT_VALID, "LAI-or-LAC-not-valid"},
+ { 0, NULL }
+};
+
#endif /* HAVE_SYS_SOCKET_H */
diff --git a/src/gsm/comp128.c b/src/gsm/comp128.c
index b28a8437..e252b04c 100644
--- a/src/gsm/comp128.c
+++ b/src/gsm/comp128.c
@@ -58,10 +58,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <string.h>
diff --git a/src/gsm/comp128v23.c b/src/gsm/comp128v23.c
index 550f6a49..0947017c 100644
--- a/src/gsm/comp128v23.c
+++ b/src/gsm/comp128v23.c
@@ -23,10 +23,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <stdint.h>
diff --git a/src/gsm/gea.c b/src/gsm/gea.c
index 5756bb08..aedc898e 100644
--- a/src/gsm/gea.c
+++ b/src/gsm/gea.c
@@ -12,10 +12,6 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <osmocom/core/bits.h>
diff --git a/src/gsm/gprs_cipher_core.c b/src/gsm/gprs_cipher_core.c
index 7f2b1a50..97e581db 100644
--- a/src/gsm/gprs_cipher_core.c
+++ b/src/gsm/gprs_cipher_core.c
@@ -17,10 +17,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include "config.h"
diff --git a/src/gsm/gprs_gea.c b/src/gsm/gprs_gea.c
index 73147886..eae1cf12 100644
--- a/src/gsm/gprs_gea.c
+++ b/src/gsm/gprs_gea.c
@@ -16,10 +16,6 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <osmocom/crypt/gprs_cipher.h>
diff --git a/src/gsm/gprs_rlc.c b/src/gsm/gprs_rlc.c
index bdfc8eac..4f02a7a9 100644
--- a/src/gsm/gprs_rlc.c
+++ b/src/gsm/gprs_rlc.c
@@ -13,9 +13,8 @@
#include <string.h>
#include <osmocom/core/utils.h>
-#include <osmocom/gprs/gprs_rlc.h>
#include <osmocom/coding/gsm0503_coding.h>
-#include <osmocom/gprs/protocol/gsm_04_60.h>
+#include <osmocom/gsm/protocol/gsm_44_060.h>
#define EGPRS_CPS_TYPE1_TBL_SZ 29
#define EGPRS_CPS_TYPE2_TBL_SZ 8
diff --git a/src/gsm/gsm0341.c b/src/gsm/gsm0341.c
index 89f5de3f..a6238b36 100644
--- a/src/gsm/gsm0341.c
+++ b/src/gsm/gsm0341.c
@@ -14,10 +14,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
diff --git a/src/gsm/gsm0411_utils.c b/src/gsm/gsm0411_utils.c
index ccefe546..470b017f 100644
--- a/src/gsm/gsm0411_utils.c
+++ b/src/gsm/gsm0411_utils.c
@@ -27,7 +27,7 @@
*
*/
-#include "../../config.h"
+#include "config.h"
#include <time.h>
#include <string.h>
@@ -293,7 +293,7 @@ enum sms_alphabet gsm338_get_sms_alphabet(uint8_t dcs)
* \param[in] type GSM340_TYPE_*
* \param[in] plan Numbering Plan
* \param[in] number string containing number
- * \reurns number of bytes of \a oa that have been used */
+ * \returns number of bytes of \a oa that have been used */
int gsm340_gen_oa(uint8_t *oa, unsigned int oa_len, uint8_t type,
uint8_t plan, const char *number)
{
@@ -348,7 +348,7 @@ int gsm411_push_rp_header(struct msgb *msg, uint8_t rp_msg_type,
* \param[in] proto Protocol
* \param[in] trans Transaction
* \param[in] msg_type Message Type
- * \retrns 0 */
+ * \returns 0 */
int gsm411_push_cp_header(struct msgb *msg, uint8_t proto, uint8_t trans,
uint8_t msg_type)
{
diff --git a/src/gsm/gsm0480.c b/src/gsm/gsm0480.c
index 1d5cde95..7a7f71f0 100644
--- a/src/gsm/gsm0480.c
+++ b/src/gsm/gsm0480.c
@@ -19,10 +19,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <osmocom/gsm/gsm48.h>
diff --git a/src/gsm/gsm0502.c b/src/gsm/gsm0502.c
index e34d3f57..e4a761da 100644
--- a/src/gsm/gsm0502.c
+++ b/src/gsm/gsm0502.c
@@ -34,7 +34,7 @@
#include <inttypes.h>
unsigned int
-gsm0502_calc_paging_group(struct gsm48_control_channel_descr *chan_desc, uint64_t imsi)
+gsm0502_calc_paging_group(const struct gsm48_control_channel_descr *chan_desc, uint64_t imsi)
{
int ccch_conf;
int bs_cc_chans;
@@ -179,7 +179,6 @@ uint32_t gsm0502_fn_remap(uint32_t fn, enum gsm0502_fn_remap_channel channel)
uint8_t fn_cycle;
uint8_t i;
int sub = -1;
- uint32_t fn_map;
struct fn_remap_table *table;
OSMO_ASSERT(channel < ARRAY_SIZE(fn_remap_table_ptr));
@@ -199,9 +198,7 @@ uint32_t gsm0502_fn_remap(uint32_t fn, enum gsm0502_fn_remap_channel channel)
return fn;
}
- fn_map = (fn + GSM_MAX_FN - sub) % GSM_MAX_FN;
-
- return fn_map;
+ return GSM_TDMA_FN_SUB(fn, sub);
}
/* Magic numbers (RNTABLE) for pseudo-random hopping sequence generation. */
@@ -263,3 +260,38 @@ uint16_t gsm0502_hop_seq_gen(const struct gsm_time *t,
return ma ? ma[mai] : mai;
}
+
+#define CB_FCCH -1
+#define CB_SCH -2
+#define CB_BCCH -3
+#define CB_IDLE -4
+
+/* Clause 7 Table 3 and Figure 8a */
+static const int ccch_block_table[51] = {
+ CB_FCCH, CB_SCH, /* 0..1 */
+ CB_BCCH, CB_BCCH, CB_BCCH, CB_BCCH, /* 2..5: BCCH */
+ 0, 0, 0, 0, /* 6..9: B0 */
+ CB_FCCH, CB_SCH, /* 10..11 */
+ 1, 1, 1, 1, /* 12..15: B1 */
+ 2, 2, 2, 2, /* 16..19: B2 */
+ CB_FCCH, CB_SCH, /* 20..21 */
+ 3, 3, 3, 3, /* 22..25: B3 */
+ 4, 4, 4, 4, /* 26..29: B4 */
+ CB_FCCH, CB_SCH, /* 30..31 */
+ 5, 5, 5, 5, /* 32..35: B5 */
+ 6, 6, 6, 6, /* 36..39: B6 */
+ CB_FCCH, CB_SCH, /* 40..41 */
+ 7, 7, 7, 7, /* 42..45: B7 */
+ 8, 8, 8, 8, /* 46..49: B8 */
+ CB_IDLE /* 50: Idle */
+};
+
+/*! Calculate CCCH block number from the given TDMA frame number.
+ * \param[in] fn TDMA frame number (of first or last burst).
+ * \returns CCCH block number 0..8 or a negative value,
+ * if the given frame number cannot carry CCCH.
+ */
+int gsm0502_fn2ccch_block(uint32_t fn)
+{
+ return ccch_block_table[fn % ARRAY_SIZE(ccch_block_table)];
+}
diff --git a/src/gsm/gsm0808.c b/src/gsm/gsm0808.c
index 971d9625..529dbdfe 100644
--- a/src/gsm/gsm0808.c
+++ b/src/gsm/gsm0808.c
@@ -15,12 +15,10 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
+#include "config.h"
+
#include <string.h>
#include <osmocom/core/byteswap.h>
@@ -104,13 +102,19 @@ struct msgb *gsm0808_create_layer3_2(const struct msgb *msg_l3, const struct osm
msgb_l3len(msg_l3), msg_l3->l3h);
/* AoIP: add Codec List (BSS Supported) 3.2.2.103 */
- if (scl)
- gsm0808_enc_speech_codec_list(msg, scl);
+ if (scl) {
+ if (gsm0808_enc_speech_codec_list2(msg, scl) < 0)
+ goto exit_free;
+ }
/* push the bssmap header */
msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg));
return msg;
+
+exit_free:
+ msgb_free(msg);
+ return NULL;
}
/*! Create "Complete L3 Info" for A, legacy implementation.
@@ -228,22 +232,32 @@ struct msgb *gsm0808_create_clear_command2(uint8_t cause, bool csfb_ind)
return msg;
}
-/*! Create BSSMAP Cipher Mode Command message
+/*! Superseded by gsm0808_create_cipher2() to include Kc128.
+ * Create BSSMAP Cipher Mode Command message (without Kc128).
* \param[in] ei Mandatory Encryption Information
+ * \param[in] kc128 optional kc128 key for A5/4
* \param[in] cipher_response_mode optional 1-byte Cipher Response Mode
* \returns callee-allocated msgb with BSSMAP Cipher Mode Command message */
struct msgb *gsm0808_create_cipher(const struct gsm0808_encrypt_info *ei,
const uint8_t *cipher_response_mode)
{
+ struct gsm0808_cipher_mode_command cmc = {
+ .ei = *ei,
+ .cipher_response_mode_present = (cipher_response_mode != NULL),
+ .cipher_response_mode = (cipher_response_mode ? *cipher_response_mode : 0),
+ };
+ return gsm0808_create_cipher2(&cmc);
+}
+
+/*! Create BSSMAP Cipher Mode Command message.
+ * \param[in] cmc Information to encode.
+ * \returns callee-allocated msgb with BSSMAP Cipher Mode Command message */
+struct msgb *gsm0808_create_cipher2(const struct gsm0808_cipher_mode_command *cmc)
+{
/* See also: 3GPP TS 48.008 3.2.1.30 CIPHER MODE COMMAND */
struct msgb *msg;
- /* Mandatory emelent! */
- OSMO_ASSERT(ei);
-
- msg =
- msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM,
- "cipher-mode-command");
+ msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM, "cipher-mode-command");
if (!msg)
return NULL;
@@ -251,12 +265,16 @@ struct msgb *gsm0808_create_cipher(const struct gsm0808_encrypt_info *ei,
msgb_v_put(msg, BSS_MAP_MSG_CIPHER_MODE_CMD);
/* Encryption Information 3.2.2.10 */
- gsm0808_enc_encrypt_info(msg, ei);
+ gsm0808_enc_encrypt_info(msg, &cmc->ei);
/* Cipher Response Mode 3.2.2.34 */
- if (cipher_response_mode)
+ if (cmc->cipher_response_mode_present)
msgb_tv_put(msg, GSM0808_IE_CIPHER_RESPONSE_MODE,
- *cipher_response_mode);
+ cmc->cipher_response_mode);
+
+ /* Kc128 3.2.2.109 */
+ if (cmc->kc128_present)
+ gsm0808_enc_kc128(msg, cmc->kc128);
/* pre-pend the header */
msg->l3h =
@@ -363,7 +381,7 @@ struct msgb *gsm0808_create_lcls_conn_ctrl(enum gsm0808_lcls_config config,
if (config != GSM0808_LCLS_CFG_NA)
msgb_tv_put(msg, GSM0808_IE_LCLS_CONFIG, config);
if (control != GSM0808_LCLS_CSC_NA)
- msgb_tv_put(msg, GSM0808_IE_LCLS_CONFIG, control);
+ msgb_tv_put(msg, GSM0808_IE_LCLS_CONN_STATUS_CTRL, control);
msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg));
return msg;
@@ -408,7 +426,7 @@ struct msgb *gsm0808_create_lcls_notification(enum gsm0808_lcls_status status, b
/*! Create BSSMAP Classmark Request message
* \returns callee-allocated msgb with BSSMAP Classmark Request message */
-struct msgb *gsm0808_create_classmark_request()
+struct msgb *gsm0808_create_classmark_request(void)
{
struct msgb *msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM,
"classmark-request");
@@ -494,8 +512,6 @@ struct msgb *gsm0808_create_ass2(const struct gsm0808_channel_type *ct,
{
/* See also: 3GPP TS 48.008 3.2.1.1 ASSIGNMENT REQUEST */
struct msgb *msg;
- uint16_t cic_sw;
- uint32_t ci_sw;
/* Mandatory emelent! */
OSMO_ASSERT(ct);
@@ -513,11 +529,8 @@ struct msgb *gsm0808_create_ass2(const struct gsm0808_channel_type *ct,
gsm0808_enc_channel_type(msg, ct);
/* Circuit Identity Code 3.2.2.2 */
- if (cic) {
- cic_sw = osmo_htons(*cic);
- msgb_tv_fixed_put(msg, GSM0808_IE_CIRCUIT_IDENTITY_CODE,
- sizeof(cic_sw), (uint8_t *) & cic_sw);
- }
+ if (cic)
+ msgb_tv16_put(msg, GSM0808_IE_CIRCUIT_IDENTITY_CODE, *cic);
/* AoIP: AoIP Transport Layer Address (MGW) 3.2.2.102 */
if (ss) {
@@ -525,23 +538,17 @@ struct msgb *gsm0808_create_ass2(const struct gsm0808_channel_type *ct,
}
/* AoIP: Codec List (MSC Preferred) 3.2.2.103 */
- if (scl)
- gsm0808_enc_speech_codec_list(msg, scl);
+ if (scl) {
+ if (gsm0808_enc_speech_codec_list2(msg, scl) < 0)
+ goto exit_free;
+ }
/* AoIP: Call Identifier 3.2.2.105 */
if (ci) {
/* NOTE: 3GPP TS 48.008, section 3.2.2.105 specifies that
- the least significant byte should be transmitted first.
- On x86, this would mean that the endieness is already
- correct, however a platform independed implementation
- is required: */
-#ifndef OSMO_IS_LITTLE_ENDIAN
- ci_sw = osmo_swab32(*ci);
-#else
- ci_sw = *ci;
-#endif
- msgb_tv_fixed_put(msg, GSM0808_IE_CALL_ID, sizeof(ci_sw),
- (uint8_t *) & ci_sw);
+ * the least significant byte shall be transmitted first. */
+ msgb_v_put(msg, GSM0808_IE_CALL_ID);
+ osmo_store32le(*ci, msgb_put(msg, sizeof(*ci)));
}
if (kc)
@@ -555,6 +562,10 @@ struct msgb *gsm0808_create_ass2(const struct gsm0808_channel_type *ct,
msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg));
return msg;
+
+exit_free:
+ msgb_free(msg);
+ return NULL;
}
/*! Create BSSMAP Assignment Request message, 3GPP TS 48.008 §3.2.1.1.
@@ -619,12 +630,16 @@ struct msgb *gsm0808_create_ass_compl2(uint8_t rr_cause, uint8_t chosen_channel,
gsm0808_enc_aoip_trasp_addr(msg, ss);
/* AoIP: Speech Codec (Chosen) 3.2.2.104 */
- if (sc)
- gsm0808_enc_speech_codec(msg, sc);
+ if (sc) {
+ if (gsm0808_enc_speech_codec2(msg, sc) < 0)
+ goto exit_free;
+ }
/* AoIP: add Codec List (BSS Supported) 3.2.2.103 */
- if (scl)
- gsm0808_enc_speech_codec_list(msg, scl);
+ if (scl) {
+ if (gsm0808_enc_speech_codec_list2(msg, scl) < 0)
+ goto exit_free;
+ }
/* FIXME: write LSA identifier 3.2.2.15 - see 3GPP TS 43.073 */
@@ -635,6 +650,10 @@ struct msgb *gsm0808_create_ass_compl2(uint8_t rr_cause, uint8_t chosen_channel,
msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg));
return msg;
+
+exit_free:
+ msgb_free(msg);
+ return NULL;
}
/*! Create BSSMAP Assignment Completed message
@@ -696,13 +715,19 @@ struct msgb *gsm0808_create_ass_fail(uint8_t cause, const uint8_t *rr_cause,
/* Circuit pool list 3.2.2.46 */
/* AoIP: add Codec List (BSS Supported) 3.2.2.103 */
- if (scl)
- gsm0808_enc_speech_codec_list(msg, scl);
+ if (scl) {
+ if (gsm0808_enc_speech_codec_list2(msg, scl) < 0)
+ goto exit_free;
+ }
/* update the size */
msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg));
return msg;
+
+exit_free:
+ msgb_free(msg);
+ return NULL;
}
/*! Create BSSMAP Assignment Failure message
@@ -804,7 +829,7 @@ struct msgb *gsm0808_create_paging(const char *imsi, const uint32_t *tmsi,
{
struct gsm0808_cell_id_list2 cil2 = {};
- /* Mandatory emelents! */
+ /* Mandatory elements! */
OSMO_ASSERT(cil);
if (cil->id_list_len > GSM0808_CELL_ID_LIST2_MAXLEN)
@@ -846,6 +871,11 @@ static uint8_t put_old_bss_to_new_bss_information(struct msgb *msg,
msgb_tlv_put(msg, GSM0808_FE_IE_CURRENT_CHANNEL_TYPE_2, 2, val);
}
+ if (i->last_eutran_plmn_id_present) {
+ msgb_put_u8(msg, GSM0808_FE_IE_LAST_USED_EUTRAN_PLMN_ID);
+ osmo_plmn_to_bcd(msgb_put(msg, 3), &i->last_eutran_plmn_id);
+ }
+
*tlv_len = (uint8_t) (msg->tail - old_tail);
return *tlv_len + 2;
}
@@ -990,8 +1020,10 @@ struct msgb *gsm0808_create_handover_request(const struct gsm0808_handover_reque
if (params->aoip_transport_layer)
gsm0808_enc_aoip_trasp_addr(msg, params->aoip_transport_layer);
- if (params->codec_list_msc_preferred)
- gsm0808_enc_speech_codec_list(msg, params->codec_list_msc_preferred);
+ if (params->codec_list_msc_preferred) {
+ if (gsm0808_enc_speech_codec_list2(msg, params->codec_list_msc_preferred) < 0)
+ goto exit_free;
+ }
if (params->call_id_present) {
uint8_t val[4];
@@ -999,6 +1031,9 @@ struct msgb *gsm0808_create_handover_request(const struct gsm0808_handover_reque
msgb_tv_fixed_put(msg, GSM0808_IE_CALL_ID, 4, val);
}
+ if (params->more_items && params->kc128_present)
+ gsm0808_enc_kc128(msg, params->kc128);
+
if (params->global_call_reference && params->global_call_reference_len) {
msgb_tlv_put(msg, GSM0808_IE_GLOBAL_CALL_REF,
params->global_call_reference_len, params->global_call_reference);
@@ -1008,6 +1043,10 @@ struct msgb *gsm0808_create_handover_request(const struct gsm0808_handover_reque
msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg));
return msg;
+
+exit_free:
+ msgb_free(msg);
+ return NULL;
}
/*! Create BSSMAP HANDOVER REQUEST ACKNOWLEDGE message, 3GPP TS 48.008 3.2.1.10.
@@ -1040,14 +1079,28 @@ struct msgb *gsm0808_create_handover_request_ack2(const struct gsm0808_handover_
if (params->aoip_transport_layer)
gsm0808_enc_aoip_trasp_addr(msg, params->aoip_transport_layer);
+ /* AoIP: add Codec List (BSS Supported) 3.2.2.103.
+ * (codec_list_bss_supported was added to struct gsm0808_handover_request_ack later than speech_codec_chosen
+ * below, but it needs to come before it in the message coding). */
+ if (params->more_items && params->codec_list_bss_supported.len) {
+ if (gsm0808_enc_speech_codec_list2(msg, &params->codec_list_bss_supported) < 0)
+ goto exit_free;
+ }
+
/* AoIP: Speech Codec (Chosen) 3.2.2.104 */
- if (params->speech_codec_chosen_present)
- gsm0808_enc_speech_codec(msg, &params->speech_codec_chosen);
+ if (params->speech_codec_chosen_present) {
+ if (gsm0808_enc_speech_codec2(msg, &params->speech_codec_chosen) < 0)
+ goto exit_free;
+ }
/* prepend header with final length */
msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg));
return msg;
+
+exit_free:
+ msgb_free(msg);
+ return NULL;
}
/*! Same as gsm0808_create_handover_request_ack2() but with less parameters.
@@ -1099,7 +1152,7 @@ struct msgb *gsm0808_create_handover_command(const struct gsm0808_handover_comma
/*! Create BSSMAP HANDOVER DETECT message, 3GPP TS 48.008 3.2.1.40.
* Sent from the MT BSC back to the MSC when the MS has sent a handover RACH request and the MT BSC has
* received the Handover Detect message. */
-struct msgb *gsm0808_create_handover_detect()
+struct msgb *gsm0808_create_handover_detect(void)
{
struct msgb *msg;
@@ -1118,7 +1171,7 @@ struct msgb *gsm0808_create_handover_detect()
/*! Create BSSMAP HANDOVER SUCCEEDED message, 3GPP TS 48.008 3.2.1.13.
* Sent from the MSC back to the old BSS to notify that the MS has successfully accessed the new BSS. */
-struct msgb *gsm0808_create_handover_succeeded()
+struct msgb *gsm0808_create_handover_succeeded(void)
{
struct msgb *msg;
@@ -1153,12 +1206,16 @@ struct msgb *gsm0808_create_handover_complete(const struct gsm0808_handover_comp
msgb_tlv_put(msg, GSM0808_IE_RR_CAUSE, 1, &params->rr_cause);
/* AoIP: Speech Codec (Chosen) 3.2.2.104 */
- if (params->speech_codec_chosen_present)
- gsm0808_enc_speech_codec(msg, &params->speech_codec_chosen);
+ if (params->speech_codec_chosen_present) {
+ if (gsm0808_enc_speech_codec2(msg, &params->speech_codec_chosen) < 0)
+ goto exit_free;
+ }
/* AoIP: add Codec List (BSS Supported) 3.2.2.103 */
- if (params->codec_list_bss_supported.len)
- gsm0808_enc_speech_codec_list(msg, &params->codec_list_bss_supported);
+ if (params->codec_list_bss_supported.len) {
+ if (gsm0808_enc_speech_codec_list2(msg, &params->codec_list_bss_supported) < 0)
+ goto exit_free;
+ }
/* Chosen Encryption Algorithm 3.2.2.44 */
if (params->chosen_encr_alg_present && params->chosen_encr_alg > 0)
@@ -1172,6 +1229,10 @@ struct msgb *gsm0808_create_handover_complete(const struct gsm0808_handover_comp
msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg));
return msg;
+
+exit_free:
+ msgb_free(msg);
+ return NULL;
}
/*! Create BSSMAP HANDOVER FAILURE message, 3GPP TS 48.008 3.2.1.16.
@@ -1195,13 +1256,19 @@ struct msgb *gsm0808_create_handover_failure(const struct gsm0808_handover_failu
msgb_tlv_put(msg, GSM0808_IE_RR_CAUSE, 1, &params->rr_cause);
/* AoIP: add Codec List (BSS Supported) 3.2.2.103 */
- if (params->codec_list_bss_supported.len)
- gsm0808_enc_speech_codec_list(msg, &params->codec_list_bss_supported);
+ if (params->codec_list_bss_supported.len) {
+ if (gsm0808_enc_speech_codec_list2(msg, &params->codec_list_bss_supported) < 0)
+ goto exit_free;
+ }
/* prepend header with final length */
msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg));
return msg;
+
+exit_free:
+ msgb_free(msg);
+ return NULL;
}
/*! Create BSSMAP HANDOVER PERFORMED message, 3GPP TS 48.008 3.2.1.25.
@@ -1237,8 +1304,10 @@ struct msgb *gsm0808_create_handover_performed(const struct gsm0808_handover_per
msgb_tv_put(msg, GSM0808_IE_SPEECH_VERSION, params->speech_version_chosen);
/* AoIP: Speech Codec (chosen) 3.2.2.104 */
- if (params->speech_codec_chosen_present)
- gsm0808_enc_speech_codec(msg, &params->speech_codec_chosen);
+ if (params->speech_codec_chosen_present) {
+ if (gsm0808_enc_speech_codec2(msg, &params->speech_codec_chosen) < 0)
+ goto exit_free;
+ }
/* LCLS-BSS-Status 3.2.2.119 */
if (params->lcls_bss_status_present)
@@ -1248,6 +1317,10 @@ struct msgb *gsm0808_create_handover_performed(const struct gsm0808_handover_per
msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg));
return msg;
+
+exit_free:
+ msgb_free(msg);
+ return NULL;
}
/*! Create BSSMAP COMMON ID message, 3GPP TS 48.008 3.2.1.68.
@@ -1443,6 +1516,663 @@ struct msgb *gsm0808_create_perform_location_abort(const struct lcs_cause_ie *lc
return msg;
}
+/*! Create BSSMAP VGCS/VBS SETUP message, 3GPP TS 48.008 3.2.1.50.
+ * Sent from the MSC to the BSC to request VGCS/VBS call. */
+struct msgb *gsm0808_create_vgcs_vbs_setup(const struct gsm0808_vgcs_vbs_setup *params)
+{
+ struct msgb *msg;
+
+ msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM, "BSSMAP-VGCS/VBS-SETUP");
+ if (!msg)
+ return NULL;
+
+ /* Message Type, 3.2.2.1 */
+ msgb_v_put(msg, BSS_MAP_MSG_VGCS_VBS_SETUP);
+
+ /* Group Call Reference, 3.2.2.55 */
+ gsm0808_enc_group_callref(msg, &params->callref);
+
+ /* Priority, 3.2.2.18 */
+ if (params->priority_present)
+ gsm0808_enc_priority(msg, &params->priority);
+
+ /* VGCS Feature Flags, 3.2.2.88 */
+ if (params->vgcs_feature_flags_present)
+ gsm0808_enc_vgcs_feature_flags(msg, &params->flags);
+
+ /* prepend header with final length */
+ msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg));
+
+ return msg;
+}
+
+/*! Create BSSMAP VGCS/VBS SETUP ACK message, 3GPP TS 48.008 3.2.1.51.
+ * Sent from the BSC to the MSC to confirm VGCS/VBS call. */
+struct msgb *gsm0808_create_vgcs_vbs_setup_ack(const struct gsm0808_vgcs_vbs_setup_ack *params)
+{
+ struct msgb *msg;
+
+ msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM, "BSSMAP-VGCS/VBS-SETUP-ACK");
+ if (!msg)
+ return NULL;
+
+ /* Message Type, 3.2.2.1 */
+ msgb_v_put(msg, BSS_MAP_MSG_VGCS_VBS_SETUP_ACK);
+
+ /* VGCS Feature Flags, 3.2.2.88 */
+ if (params->vgcs_feature_flags_present)
+ gsm0808_enc_vgcs_feature_flags(msg, &params->flags);
+
+ /* prepend header with final length */
+ msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg));
+
+ return msg;
+}
+
+/*! Create BSSMAP VGCS/VBS SETUP REFUSE message, 3GPP TS 48.008 3.2.1.52.
+ * Sent from the BSC to the MSC to reject VGCS/VBS call. */
+struct msgb *gsm0808_create_vgcs_vbs_setup_refuse(enum gsm0808_cause cause)
+{
+ struct msgb *msg;
+
+ msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM, "BSSMAP-VGCS/VBS-SETUP-REFUSE");
+ if (!msg)
+ return NULL;
+
+ /* Message Type, 3.2.2.1 */
+ msgb_v_put(msg, BSS_MAP_MSG_VGCS_VBS_SETUP_REFUSE);
+
+ /* Cause, 3.2.2.5 */
+ gsm0808_enc_cause(msg, cause);
+
+ /* prepend header with final length */
+ msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg));
+
+ return msg;
+}
+
+/*! Create BSSMAP VGCS/VBS ASSIGNMENT REQUEST message, 3GPP TS 48.008 3.2.1.53.
+ * Sent from the MSC to the BSC to assign radio resources for a VGCS/VBS. */
+struct msgb *gsm0808_create_vgcs_vbs_assign_req(const struct gsm0808_vgcs_vbs_assign_req *params)
+{
+ struct msgb *msg;
+
+ msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM, "BSSMAP-VGCS/VBS-ASSIGNMENT-REQUEST");
+ if (!msg)
+ return NULL;
+
+ /* Message Type, 3.2.2.1 */
+ msgb_v_put(msg, BSS_MAP_MSG_VGCS_VBS_ASSIGNMENT_RQST);
+
+ /* Channel Type, 3.2.2.11 */
+ gsm0808_enc_channel_type(msg, &params->channel_type);
+
+ /* Assignment Requrirement, 3.2.2.52 */
+ gsm0808_enc_assign_req(msg, params->ass_req);
+
+ /* Cell Identifier, 3.2.2.17 */
+ gsm0808_enc_cell_id(msg, &params->cell_identifier);
+
+ /* Group Call Reference, 3.2.2.55 */
+ gsm0808_enc_group_callref(msg, &params->callref);
+
+ /* Priority, 3.2.2.18 */
+ if (params->priority_present)
+ gsm0808_enc_priority(msg, &params->priority);
+
+ /* Circuit Identity Code, 3.2.2.2 */
+ if (params->cic_present)
+ msgb_tv16_put(msg, GSM0808_IE_CIRCUIT_IDENTITY_CODE, params->cic);
+
+ /* Downlink DTX Flag, 3.2.2.26 */
+ if (params->downlink_dtx_flag_present)
+ msgb_tv_put(msg, GSM0808_IE_DOWNLINK_DTX_FLAG, params->downlink_dtx_flag);
+
+ /* Encryption Information, 3.2.2.10 */
+ if (params->encryption_information_present)
+ gsm0808_enc_encrypt_info(msg, &params->encryption_information);
+
+ /* VSTK_RAND Imformation, 3.2.2.83 */
+ if (params->vstk_rand_present)
+ msgb_tlv_put(msg, GSM0808_IE_VSTK_RAND_INFO, sizeof(params->vstk_rand), params->vstk_rand);
+
+ /* VSTK Information, 3.2.2.84 */
+ if (params->vstk_present)
+ msgb_tlv_put(msg, GSM0808_IE_VSTK_INFO, sizeof(params->vstk), params->vstk);
+
+ /* Cell Identifier List Segment, 3.2.2.27a */
+ if (params->cils_present)
+ gsm0808_enc_cell_id_list_segment(msg, GSM0808_IE_CELL_ID_LIST_SEGMENT, &params->cils);
+
+ /* AoIP Transport Layer Address (MGW), 3.2.2.102 */
+ if (params->aoip_transport_layer_present)
+ gsm0808_enc_aoip_trasp_addr(msg, &params->aoip_transport_layer);
+
+ /* Call Identifier, 3.2.2.105 */
+ if (params->call_id_present) {
+ /* NOTE: 3GPP TS 48.008, section 3.2.2.105 specifies that
+ * the least significant byte shall be transmitted first. */
+ msgb_v_put(msg, GSM0808_IE_CALL_ID);
+ osmo_store32le(params->call_id, msgb_put(msg, sizeof(uint32_t)));
+ }
+
+ /* Codec List (MSC Preferred) 3.2.2.103 */
+ if (params->codec_list_present) {
+ if (gsm0808_enc_speech_codec_list2(msg, &params->codec_list_msc_preferred) < 0)
+ goto exit_free;
+ }
+
+ /* prepend header with final length */
+ msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg));
+
+ return msg;
+
+exit_free:
+ msgb_free(msg);
+ return NULL;
+}
+
+/*! Create BSSMAP VGCS/VBS ASSIGNMENT RESULT message, 3GPP TS 48.008 3.2.1.54.
+ * Sent from the BSC to the MSC to indicate assignment/deassingment of radio resources for a VGCS/VBS. */
+struct msgb *gsm0808_create_vgcs_vbs_assign_res(const struct gsm0808_vgcs_vbs_assign_res *params)
+{
+ struct msgb *msg;
+
+ msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM, "BSSMAP-VGCS/VBS-ASSIGNMENT-RESULT");
+ if (!msg)
+ return NULL;
+
+ /* Message Type, 3.2.2.1 */
+ msgb_v_put(msg, BSS_MAP_MSG_VGCS_VBS_ASSIGNMENT_RESULT);
+
+ /* Channel Type, 3.2.2.11 */
+ gsm0808_enc_channel_type(msg, &params->channel_type);
+
+ /* Cell Identifier, 3.2.2.17 */
+ gsm0808_enc_cell_id(msg, &params->cell_identifier);
+
+ /* Chosen Channel, 3.2.2.33 */
+ if (params->chosen_channel_present)
+ msgb_tv_put(msg, GSM0808_IE_CHOSEN_CHANNEL, params->chosen_channel);
+
+ /* Circuit Identity Code, 3.2.2.2 */
+ if (params->cic_present)
+ msgb_tv16_put(msg, GSM0808_IE_CIRCUIT_IDENTITY_CODE, params->cic);
+
+ /* Circuit Pool, 3.2.2.45 */
+ if (params->circuit_pool_present)
+ msgb_tv_put(msg, GSM0808_IE_CIRCUIT_POOL, params->circuit_pool);
+
+ /* AoIP Transport Layer Address (BSS), 3.2.2.102 */
+ if (params->aoip_transport_layer_present)
+ gsm0808_enc_aoip_trasp_addr(msg, &params->aoip_transport_layer);
+
+ /* Codec (MSC Chosen) 3.2.2.103 */
+ if (params->codec_present) {
+ if (gsm0808_enc_speech_codec2(msg, &params->codec_msc_chosen) < 0)
+ goto exit_free;
+ }
+
+ /* Call Identifier, 3.2.2.105 */
+ if (params->call_id_present) {
+ /* NOTE: 3GPP TS 48.008, section 3.2.2.105 specifies that
+ * the least significant byte shall be transmitted first. */
+ msgb_v_put(msg, GSM0808_IE_CALL_ID);
+ osmo_store32le(params->call_id, msgb_put(msg, sizeof(uint32_t)));
+ }
+
+ /* prepend header with final length */
+ msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg));
+
+ return msg;
+
+exit_free:
+ msgb_free(msg);
+ return NULL;
+}
+
+/*! Create BSSMAP VGCS/VBS ASSIGNMENT FAILURE message, 3GPP TS 48.008 3.2.1.55.
+ * Sent from the BSC to the MSC to indicate assignment failure for a VGCS/VBS. */
+struct msgb *gsm0808_create_vgcs_vbs_assign_fail(const struct gsm0808_vgcs_vbs_assign_fail *params)
+{
+ struct msgb *msg;
+
+ msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM, "BSSMAP-VGCS/VBS-ASSIGNMENT-RESULT");
+ if (!msg)
+ return NULL;
+
+ /* Message Type, 3.2.2.1 */
+ msgb_v_put(msg, BSS_MAP_MSG_VGCS_VBS_ASSIGNMENT_FAILURE);
+
+ /* Cause, 3.2.2.5 */
+ gsm0808_enc_cause(msg, params->cause);
+
+ /* Circuit Pool, 3.2.2.45 */
+ if (params->circuit_pool_present)
+ msgb_tv_put(msg, GSM0808_IE_CIRCUIT_POOL, params->circuit_pool);
+
+ /* Circuit Pool List, 3.2.2.46 */
+ if (params->circuit_pool_present)
+ msgb_tlv_put(msg, GSM0808_IE_CIRCUIT_POOL_LIST, params->cpl.list_len, params->cpl.pool);
+
+ /* Codec List (BSS Supported) 3.2.2.103 */
+ if (params->codec_list_present) {
+ if (gsm0808_enc_speech_codec_list2(msg, &params->codec_list_bss_supported) < 0)
+ goto exit_free;
+ }
+
+ /* prepend header with final length */
+ msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg));
+
+ return msg;
+
+exit_free:
+ msgb_free(msg);
+ return NULL;
+}
+
+/*! Create BSSMAP VGCS/VBS QUEUING INDICATION message, 3GPP TS 48.008 3.2.1.56.
+ * Sent from the BSC to the MSC to indicate delay in assignment for a VGCS/VBS. */
+struct msgb *gsm0808_create_vgcs_queuing_ind(void)
+{
+ struct msgb *msg;
+ uint8_t val = BSS_MAP_MSG_VGCS_VBS_QUEUING_INDICATION;
+
+ msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM, "BSSMAP-VGCS/VBS-QUEUING-INDICATION");
+ if (!msg)
+ return NULL;
+
+ msg->l3h = msg->data;
+ msgb_tlv_put(msg, BSSAP_MSG_BSS_MANAGEMENT, 1, &val);
+
+ return msg;
+}
+
+/*! Create BSSMAP (VGCS) UPLINK REQUEST message, 3GPP TS 48.008 3.2.1.57.
+ * Sent from the BSC to the MSC to indicate that a mobile requested access to uplink. */
+struct msgb *gsm0808_create_uplink_request(const struct gsm0808_uplink_request *params)
+{
+ struct msgb *msg;
+ int rc;
+
+ msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM, "BSSMAP-UPLINK-REQUEST");
+ if (!msg)
+ return NULL;
+
+ /* Message Type, 3.2.2.1 */
+ msgb_v_put(msg, BSS_MAP_MSG_UPLINK_RQST);
+
+ /* Talker Priority, 3.2.2.89 */
+ if (params->talker_priority_present)
+ msgb_tv_put(msg, GSM0808_IE_TALKER_PRIORITY, params->talker_priority);
+
+ /* Cell Identifier, 3.2.2.17 */
+ if (params->cell_identifier_present)
+ gsm0808_enc_cell_id(msg, &params->cell_identifier);
+
+ /* Layer 3 Information, 3.2.2.24 */
+ if (params->l3_present)
+ msgb_tlv_put(msg, GSM0808_IE_LAYER_3_INFORMATION, params->l3.l3_len, params->l3.l3);
+
+ /* Mobile Identity, 3.2.2.41 */
+ if (params->mi_present) {
+ rc = osmo_mobile_identity_encode_msgb(msg, &params->mi, false);
+ if (rc < 0) {
+ msgb_free(msg);
+ return NULL;
+ }
+ }
+
+ /* prepend header with final length */
+ msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg));
+
+ return msg;
+}
+
+/*! Create BSSMAP (VGCS) UPLINK REQUEST ACKNOWLEDGE message, 3GPP TS 48.008 3.2.1.58.
+ * Sent from the MSC to the BSC to indicate that access to uplink was granted. */
+struct msgb *gsm0808_create_uplink_request_ack(const struct gsm0808_uplink_request_ack *params)
+{
+ struct msgb *msg;
+
+ msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM, "BSSMAP-UPLINK-REQUEST-ACKNOWLEDGE");
+ if (!msg)
+ return NULL;
+
+ /* Message Type, 3.2.2.1 */
+ msgb_v_put(msg, BSS_MAP_MSG_UPLINK_RQST_ACKNOWLEDGE);
+
+ /* Talker Priority, 3.2.2.89 */
+ if (params->talker_priority_present)
+ msgb_tv_put(msg, GSM0808_IE_TALKER_PRIORITY, params->talker_priority);
+
+ /* Emergency set indication, 3.2.2.90 */
+ if (params->emerg_set_ind_present)
+ msgb_v_put(msg, GSM0808_IE_EMERGENCY_SET_INDICATION);
+
+ /* Talker Identity, 3.2.2.91 */
+ if (params->talker_identity_present)
+ gsm0808_enc_talker_identity(msg, &params->talker_identity);
+
+ /* prepend header with final length */
+ msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg));
+
+ return msg;
+}
+
+/*! Create BSSMAP (VGCS) UPLINK CONFIRM message, 3GPP TS 48.008 3.2.1.59.
+ * Sent from the BSC to the MSC to indicate that access to uplink was has been successfully established. */
+struct msgb *gsm0808_create_uplink_request_cnf(const struct gsm0808_uplink_request_cnf *params)
+{
+ struct msgb *msg;
+
+ msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM, "BSSMAP-UPLINK-REQUEST-CONFIRM");
+ if (!msg)
+ return NULL;
+
+ /* Message Type, 3.2.2.1 */
+ msgb_v_put(msg, BSS_MAP_MSG_UPLINK_RQST_CONFIRMATION);
+
+ /* Cell Identifier, 3.2.2.17 */
+ gsm0808_enc_cell_id(msg, &params->cell_identifier);
+
+ /* Talker Identity, 3.2.2.91 */
+ if (params->talker_identity_present)
+ gsm0808_enc_talker_identity(msg, &params->talker_identity);
+
+ /* Layer 3 Information, 3.2.2.24 */
+ msgb_tlv_put(msg, GSM0808_IE_LAYER_3_INFORMATION, params->l3.l3_len, params->l3.l3);
+
+ /* prepend header with final length */
+ msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg));
+
+ return msg;
+}
+
+/*! Create BSSMAP (VGCS) UPLINK APPLICATION DATA message, 3GPP TS 48.008 3.2.1.59a.
+ * Sent from the BSC to the MSC to pass L3 info from the talker. */
+struct msgb *gsm0808_create_uplink_app_data(const struct gsm0808_uplink_app_data *params)
+{
+ struct msgb *msg;
+ uint8_t val;
+
+ msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM, "BSSMAP-UPLINK-APPLICATION-DATA");
+ if (!msg)
+ return NULL;
+
+ /* Message Type, 3.2.2.1 */
+ msgb_v_put(msg, BSS_MAP_MSG_UPLINK_APP_DATA);
+
+ /* Cell Identifier, 3.2.2.17 */
+ gsm0808_enc_cell_id(msg, &params->cell_identifier);
+
+ /* Layer 3 Information, 3.2.2.24 */
+ msgb_tlv_put(msg, GSM0808_IE_LAYER_3_INFORMATION, params->l3.l3_len, params->l3.l3);
+
+ /* Application Data Information, 3.2.2.100 */
+ val = params->bt_ind;
+ msgb_tlv_put(msg, GSM0808_IE_APP_DATA_INFO, 1, &val);
+
+ /* prepend header with final length */
+ msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg));
+
+ return msg;
+}
+
+/*! Create BSSMAP (VGCS) UPLINK RELEASE INDICATION message, 3GPP TS 48.008 3.2.1.60.
+ * Sent from the BSC to the MSC to indicate that the uplink has been released. */
+struct msgb *gsm0808_create_uplink_release_ind(const struct gsm0808_uplink_release_ind *params)
+{
+ struct msgb *msg;
+
+ msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM, "BSSMAP-UPLINK-RELEASE-INDICATION");
+ if (!msg)
+ return NULL;
+
+ /* Message Type, 3.2.2.1 */
+ msgb_v_put(msg, BSS_MAP_MSG_UPLINK_RELEASE_INDICATION);
+
+ /* Cause, 3.2.2.5 */
+ gsm0808_enc_cause(msg, params->cause);
+
+ /* Talker Priority, 3.2.2.89 */
+ if (params->talker_priority_present)
+ msgb_tv_put(msg, GSM0808_IE_TALKER_PRIORITY, params->talker_priority);
+
+ /* prepend header with final length */
+ msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg));
+
+ return msg;
+}
+
+/*! Create BSSMAP (VGCS) UPLINK REJECT COMMAND message, 3GPP TS 48.008 3.2.1.61.
+ * Sent from the MSC to the BSC to indicate that the uplink is not available for allocation. */
+struct msgb *gsm0808_create_uplink_reject_cmd(const struct gsm0808_uplink_reject_cmd *params)
+{
+ struct msgb *msg;
+
+ msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM, "BSSMAP-UPLINK-REJECT-COMMAND");
+ if (!msg)
+ return NULL;
+
+ /* Message Type, 3.2.2.1 */
+ msgb_v_put(msg, BSS_MAP_MSG_UPLINK_REJECT_CMD);
+
+ /* Cause, 3.2.2.5 */
+ gsm0808_enc_cause(msg, params->cause);
+
+ /* Talker Priority, 3.2.2.89 */
+ if (params->current_talker_priority_present)
+ msgb_tv_put(msg, GSM0808_IE_TALKER_PRIORITY, params->current_talker_priority);
+
+ /* Talker Priority, 3.2.2.89 */
+ if (params->rejected_talker_priority_present)
+ msgb_tv_put(msg, GSM0808_IE_TALKER_PRIORITY, params->rejected_talker_priority);
+
+ /* Talker Identity, 3.2.2.91 */
+ if (params->talker_identity_present)
+ gsm0808_enc_talker_identity(msg, &params->talker_identity);
+
+ /* prepend header with final length */
+ msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg));
+
+ return msg;
+}
+
+/*! Create BSSMAP (VGCS) UPLINK RELEASE COMMAND message, 3GPP TS 48.008 3.2.1.62.
+ * Sent from the MSC to the BSC to indicate that the uplink is available for allocation. */
+struct msgb *gsm0808_create_uplink_release_cmd(const enum gsm0808_cause cause)
+{
+ struct msgb *msg;
+
+ msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM, "BSSMAP-UPLINK-RELEASE-COMMAND");
+ if (!msg)
+ return NULL;
+
+ /* Message Type, 3.2.2.1 */
+ msgb_v_put(msg, BSS_MAP_MSG_UPLINK_RELEASE_CMD);
+
+ /* Cause, 3.2.2.5 */
+ gsm0808_enc_cause(msg, cause);
+
+ /* prepend header with final length */
+ msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg));
+
+ return msg;
+}
+
+/*! Create BSSMAP (VGCS) UPLINK SEIZED COMMAND message, 3GPP TS 48.008 3.2.1.62.
+ * Sent from the MSC to the BSC to indicate that the uplink is no longer available for allocation. */
+struct msgb *gsm0808_create_uplink_seized_cmd(const struct gsm0808_uplink_seized_cmd *params)
+{
+ struct msgb *msg;
+
+ msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM, "BSSMAP-UPLINK-SEIZED-COMMAND");
+ if (!msg)
+ return NULL;
+
+ /* Message Type, 3.2.2.1 */
+ msgb_v_put(msg, BSS_MAP_MSG_UPLINK_SEIZED_CMD);
+
+ /* Cause, 3.2.2.5 */
+ gsm0808_enc_cause(msg, params->cause);
+
+ /* Talker Priority, 3.2.2.89 */
+ if (params->talker_priority_present)
+ msgb_tv_put(msg, GSM0808_IE_TALKER_PRIORITY, params->talker_priority);
+
+ /* Emergency set indication, 3.2.2.90 */
+ if (params->emerg_set_ind_present)
+ msgb_v_put(msg, GSM0808_IE_EMERGENCY_SET_INDICATION);
+
+ /* Talker Identity, 3.2.2.91 */
+ if (params->talker_identity_present)
+ gsm0808_enc_talker_identity(msg, &params->talker_identity);
+
+ /* prepend header with final length */
+ msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg));
+
+ return msg;
+}
+
+/*! Create BSSMAP VGCS ADDITIONAL INFORMATION message, 3GPP TS 48.008 3.2.1.78.
+ * Sent from the MSC to the BSC to transfer talker identity. */
+struct msgb *gsm0808_create_vgcs_additional_info(const struct gsm0808_talker_identity *ti)
+{
+ struct msgb *msg;
+
+ msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM, "BSSMAP-VGCS-ADDITIONAL-INFO");
+ if (!msg)
+ return NULL;
+
+ /* Message Type, 3.2.2.1 */
+ msgb_v_put(msg, BSS_MAP_MSG_VGCS_ADDL_INFO);
+
+ /* Talker Identity, 3.2.2.91 */
+ gsm0808_enc_talker_identity(msg, ti);
+
+ /* prepend header with final length */
+ msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg));
+
+ return msg;
+}
+
+/*! Create BSSMAP VGCS/VBS AREA CELL INFO message, 3GPP TS 48.008 3.2.1.79.
+ * Sent from the BSC to the MSC to transfer additional infos about cells. */
+struct msgb *gsm0808_create_vgcs_vbs_area_cell_info(const struct gsm0808_vgcs_vbs_area_cell_info *params)
+{
+ struct msgb *msg;
+
+ msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM, "BSSMAP-VGCS/VBS-AREA-CELL-INFO");
+ if (!msg)
+ return NULL;
+
+ /* Message Type, 3.2.2.1 */
+ msgb_v_put(msg, BSS_MAP_MSG_VGCS_VBS_ASSIGNMENT_RQST);
+
+ /* Cell Identifier List Segment, 3.2.2.27a */
+ gsm0808_enc_cell_id_list_segment(msg, GSM0808_IE_CELL_ID_LIST_SEGMENT, &params->cils);
+
+ /* Assignment Requrirement, 3.2.2.52 */
+ if (params->ass_req_present)
+ gsm0808_enc_assign_req(msg, params->ass_req);
+
+ /* prepend header with final length */
+ msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg));
+
+ return msg;
+}
+
+/*! Create BSSMAP VGCS/VBS ASSIGNMENT STATUS message, 3GPP TS 48.008 3.2.1.80.
+ * Sent from the BSC to the MSC to indicate assignment status for each cell. */
+struct msgb *gsm0808_create_vgcs_vbs_assign_stat(const struct gsm0808_vgcs_vbs_assign_stat *params)
+{
+ struct msgb *msg;
+ uint8_t val;
+
+ msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM, "BSSMAP-VGCS/VBS-ASSIGNMENT-STATUS");
+ if (!msg)
+ return NULL;
+
+ /* Message Type, 3.2.2.1 */
+ msgb_v_put(msg, BSS_MAP_MSG_VGCS_VBS_ASSIGNMENT_STATUS);
+
+ /* Cell Identifier List Segment, 3.2.2.27b */
+ if (params->cils_est_present)
+ gsm0808_enc_cell_id_list_segment(msg, GSM0808_IE_CELL_ID_LIST_SEG_EST_CELLS, &params->cils_est);
+ /* Cell Identifier List Segment, 3.2.2.27c */
+ if (params->cils_tbe_present)
+ gsm0808_enc_cell_id_list_segment(msg, GSM0808_IE_CELL_ID_LIST_SEG_CELLS_TBE, &params->cils_tbe);
+ /* Cell Identifier List Segment, 3.2.2.27e */
+ if (params->cils_rel_present)
+ gsm0808_enc_cell_id_list_segment(msg, GSM0808_IE_CELL_ID_LIST_SEG_REL_CELLS, &params->cils_rel);
+ /* Cell Identifier List Segment, 3.2.2.27f */
+ if (params->cils_ne_present)
+ gsm0808_enc_cell_id_list_segment(msg, GSM0808_IE_CELL_ID_LIST_SEG_NE_CELLS, &params->cils_ne);
+
+ /* VGCS/VBS Cell Status, 3.2.2.94 */
+ if (params->cell_status_present) {
+ val = params->cell_status;
+ msgb_tlv_put(msg, GSM0808_IE_VGCS_VBS_CELL_STATUS, 1, &val);
+ }
+
+ /* prepend header with final length */
+ msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg));
+
+ return msg;
+}
+
+/*! Create BSSMAP VGCS SMS message, 3GPP TS 48.008 3.2.1.81.
+ * Sent from the MSC to the BSC to send an SMS to VGCS. */
+struct msgb *gsm0808_create_vgcs_sms(const struct gsm0808_sms_to_vgcs *sms)
+{
+ struct msgb *msg;
+
+ msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM, "BSSMAP-VGCS-SMS");
+ if (!msg)
+ return NULL;
+
+ /* SMS to VGCS, 3.2.2.92 */
+ msgb_tlv_put(msg, GSM0808_IE_VGCS_VBS_CELL_STATUS, sms->sms_len, sms->sms);
+
+ /* prepend header with final length */
+ msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg));
+
+ return msg;
+}
+
+/*! Create BSSMAP (VGCS/VBS) NOTIFICATION DATA message, 3GPP TS 48.008 3.2.1.82.
+ * Sent from the MSC to the BSC to send application specific data. */
+struct msgb *gsm0808_create_notification_data(const struct gsm0808_notification_data *params)
+{
+ struct msgb *msg;
+
+ msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM, "BSSMAP-VGCS-SMS");
+ if (!msg)
+ return NULL;
+
+ /* Message Type, 3.2.2.1 */
+ msgb_v_put(msg, BSS_MAP_MSG_NOTIFICATION_DATA);
+
+ /* Application Data, 3.2.2.98 */
+ msgb_tlv_put(msg, GSM0808_IE_APP_DATA, params->app_data.data_len, params->app_data.data);
+
+ /* Data Identity, 3.2.2.99 */
+ gsm0808_enc_data_identity(msg, &params->data_ident);
+
+ /* MSISDN, 3.2.2.101 */
+ if (params->msisdn_present)
+ gsm0808_enc_msisdn(msg, params->msisdn);
+
+ /* prepend header with final length */
+ msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg));
+
+ return msg;
+}
+
+/* Note that EMERGENCY RESET INDICATION and EMERGENCY RESET COMMAND cannot be implemented, due to lack of
+ * message type information in the specifications. */
+
/* As per 3GPP TS 48.008 version 11.7.0 Release 11 */
static const struct tlv_definition bss_att_tlvdef = {
.def = {
@@ -1514,6 +2244,7 @@ static const struct tlv_definition bss_att_tlvdef = {
[GSM0808_IE_LOCATION_ESTIMATE] = { TLV_TYPE_TLV },
[GSM0808_IE_POSITIONING_DATA] = { TLV_TYPE_TLV },
[GSM0808_IE_LCS_CAUSE] = { TLV_TYPE_TLV },
+ [GSM0808_IE_LCS_CLIENT_TYPE] = { TLV_TYPE_TLV },
[GSM0808_IE_APDU] = { TLV_TYPE_TLV },
[GSM0808_IE_NETWORK_ELEMENT_IDENTITY] = { TLV_TYPE_TLV },
[GSM0808_IE_GPS_ASSISTANCE_DATA] = { TLV_TYPE_TLV },
@@ -1530,6 +2261,7 @@ static const struct tlv_definition bss_att_tlvdef = {
[GSM0800_IE_INTER_SYSTEM_INFO] = { TLV_TYPE_TLV },
[GSM0808_IE_SNA_ACCESS_INFO] = { TLV_TYPE_TLV },
[GSM0808_IE_VSTK_RAND_INFO] = { TLV_TYPE_TLV },
+ [GSM0808_IE_VSTK_INFO] = { TLV_TYPE_TLV },
[GSM0808_IE_PAGING_INFO] = { TLV_TYPE_TV },
[GSM0808_IE_IMEI] = { TLV_TYPE_TLV },
[GSM0808_IE_VELOCITY_ESTIMATE] = { TLV_TYPE_TLV },
@@ -1572,6 +2304,11 @@ static const struct tlv_definition bss_att_tlvdef = {
[GSM0808_IE_CN_TO_MS_TRANSP_INFO] = { TLV_TYPE_TLV },
[GSM0808_IE_SELECTED_PLMN_ID] = { TLV_TYPE_FIXED, 3 },
[GSM0808_IE_LAST_USED_EUTRAN_PLMN_ID] = { TLV_TYPE_FIXED, 3 },
+ [GSM0808_IE_OLD_LAI] = { TLV_TYPE_FIXED, 5 },
+ [GSM0808_IE_ATTACH_INDICATOR] = { TLV_TYPE_T },
+ [GSM0808_IE_SELECTED_OPERATOR] = { TLV_TYPE_FIXED, 3 },
+ [GSM0808_IE_PS_REGISTERED_OPERATOR] = { TLV_TYPE_FIXED, 3 },
+ [GSM0808_IE_CS_REGISTERED_OPERATOR] = { TLV_TYPE_FIXED, 3 },
/* Osmocom extensions */
[GSM0808_IE_OSMO_OSMUX_SUPPORT] = { TLV_TYPE_T },
@@ -1584,6 +2321,32 @@ const struct tlv_definition *gsm0808_att_tlvdef(void)
return &bss_att_tlvdef;
}
+/* As per 3GPP TS 48.008 version 16.0.0 Release 16 § 3.2.2.58 Old BSS to New BSS Information */
+const struct tlv_definition gsm0808_old_bss_to_new_bss_info_att_tlvdef = {
+ .def = {
+ [GSM0808_FE_IE_EXTRA_INFORMATION] = { TLV_TYPE_TLV },
+ [GSM0808_FE_IE_CURRENT_CHANNEL_TYPE_2] = { TLV_TYPE_TLV },
+ [GSM0808_FE_IE_TARGET_CELL_RADIO_INFORMATION] = { TLV_TYPE_TLV },
+ [GSM0808_FE_IE_GPRS_SUSPEND_INFORMATION] = { TLV_TYPE_TLV },
+ [GSM0808_FE_IE_MULTIRATE_CONFIGURATION_INFORMATION] = { TLV_TYPE_TLV },
+ [GSM0808_FE_IE_DUAL_TRANSFER_MODE_INFORMATION] = { TLV_TYPE_TLV },
+ [GSM0808_FE_IE_INTER_RAT_HANDOVER_INFO] = { TLV_TYPE_TLV },
+ [GSM0808_FE_IE_CDMA2000_CAPABILITY_INFORMATION] = { TLV_TYPE_TLV },
+ [GSM0808_FE_IE_DOWNLINK_CELL_LOAD_INFORMATION] = { TLV_TYPE_TLV },
+ [GSM0808_FE_IE_UPLINK_CELL_LOAD_INFORMATION] = { TLV_TYPE_TLV },
+ [GSM0808_FE_IE_CELL_LOAD_INFORMATION_GROUP] = { TLV_TYPE_TLV },
+ [GSM0808_FE_IE_CELL_LOAD_INFORMATION] = { TLV_TYPE_TLV },
+ [GSM0808_FE_IE_PS_INDICATION] = { TLV_TYPE_TLV },
+ [GSM0808_FE_IE_DTM_HANDOVER_COMMAND_INDICATION] = { TLV_TYPE_TLV },
+ [GSM0808_FE_IE_D_RNTI] = { TLV_TYPE_TLV },
+ [GSM0808_FE_IE_IRAT_MEASUREMENT_CONFIGURATION] = { TLV_TYPE_TLV },
+ [GSM0808_FE_IE_SOURCE_CELL_ID] = { TLV_TYPE_TLV },
+ [GSM0808_FE_IE_IRAT_MEASUREMENT_CONFIGURATION_EXTENDED_E_ARFCNS] = { TLV_TYPE_TLV },
+ [GSM0808_FE_IE_VGCS_TALKER_MODE] = { TLV_TYPE_TLV },
+ [GSM0808_FE_IE_LAST_USED_EUTRAN_PLMN_ID] = { TLV_TYPE_FIXED, 3 },
+ },
+};
+
const struct value_string gsm0406_dlci_sapi_names[] = {
{ DLCI_SAPI_RR_MM_CC, "RR/MM/CC" },
{ DLCI_SAPI_SMS, "SMS" },
@@ -1671,7 +2434,9 @@ static const struct value_string gsm0808_msgt_names[] = {
{ BSS_MAP_MSG_VGCS_VBS_ASSIGNMENT_RQST, "VGCS/VBS ASSIGN REQ" },
{ BSS_MAP_MSG_VGCS_VBS_ASSIGNMENT_RESULT, "VGCS/VBS ASSIGN RES" },
{ BSS_MAP_MSG_VGCS_VBS_ASSIGNMENT_FAILURE, "VGCS/VBS ASSIGN FAIL" },
+ { BSS_MAP_MSG_VGCS_VBS_ASSIGNMENT_STATUS, "VGCS/VBS ASSIGN STATUS" },
{ BSS_MAP_MSG_VGCS_VBS_QUEUING_INDICATION, "VGCS/VBS QUEUING IND" },
+ { BSS_MAP_MSG_VGCS_VBS_AREA_CELL_INFO, "VGCS/VBS AREA CELL INFO" },
{ BSS_MAP_MSG_UPLINK_RQST, "UPLINK REQ" },
{ BSS_MAP_MSG_UPLINK_RQST_ACKNOWLEDGE, "UPLINK REQ ACK" },
{ BSS_MAP_MSG_UPLINK_RQST_CONFIRMATION, "UPLINK REQ CONF" },
@@ -1680,11 +2445,12 @@ static const struct value_string gsm0808_msgt_names[] = {
{ BSS_MAP_MSG_UPLINK_RELEASE_CMD, "UPLINK REL CMD" },
{ BSS_MAP_MSG_UPLINK_SEIZED_CMD, "UPLINK SEIZED CMD" },
{ BSS_MAP_MSG_VGCS_ADDL_INFO, "VGCS ADDL INFO" },
+ { BSS_MAP_MSG_VGCS_SMS, "VGCS SMS" },
{ BSS_MAP_MSG_NOTIFICATION_DATA, "NOTIF DATA" },
{ BSS_MAP_MSG_UPLINK_APP_DATA, "UPLINK APP DATA" },
{ BSS_MAP_MSG_LCLS_CONNECT_CTRL, "LCLS-CONNECT-CONTROL" },
- { BSS_MAP_MSG_LCLS_CONNECT_CTRL_ACK, "CLS-CONNECT-CONTROL-ACK" },
+ { BSS_MAP_MSG_LCLS_CONNECT_CTRL_ACK, "LCLS-CONNECT-CONTROL-ACK" },
{ BSS_MAP_MSG_LCLS_NOTIFICATION, "LCLS-NOTIFICATION" },
{ 0, NULL }
@@ -1718,6 +2484,7 @@ const struct value_string gsm0808_speech_codec_type_names[] = {
{ GSM0808_SCT_HR3, "HR3" },
{ GSM0808_SCT_HR4, "HR4" },
{ GSM0808_SCT_HR6, "HR6" },
+ { GSM0808_SCT_EXT, "Codec Extension" },
{ GSM0808_SCT_CSD, "CSD" },
{ 0, NULL }
};
@@ -1924,4 +2691,101 @@ const struct value_string gsm0808_lcls_status_names[] = {
{ 0, NULL }
};
+/* Convert one S0-S15 bit to its set of AMR modes, for HR AMR and FR AMR.
+ * This is 3GPP TS 28.062 Table 7.11.3.1.3-2: "Preferred Configurations", with some configurations removed as specified
+ * in 3GPP TS 48.008 3.2.2.103:
+ *
+ * FR_AMR is coded ‘0011’.
+ * S11, S13 and S15 are reserved and coded with zeroes.
+ *
+ * HR_AMR is coded ‘0100’.
+ * S6 - S7 and S11 – S15 are reserved and coded with zeroes.
+ *
+ * Meaning: for FR, exclude all Optimisation Mode configurations.
+ * For HR, exclude all that are not supported by HR AMR -- drop all that include at least one of
+ * 10.2 or 12.2.
+ *
+ * Also, for HR, drop 12.2k from S1.
+ *
+ * The first array dimension is 0 for half rate and 1 for full rate.
+ * The second array dimension is the configuration number (0..15) aka Sn.
+ * The values are bitmask combinations of (1 << GSM0808_AMR_MODE_nnnn).
+ *
+ * For example, accumulate all modes that are possible in a given my_s15_s0:
+ *
+ * uint8_t modes = 0;
+ * for (s_bit = 0; s_bit < 15; s_bit++)
+ * if (my_s15_s0 & (1 << s_bit))
+ * modes |= gsm0808_amr_modes_from_cfg[full_rate ? 1 : 0][s_bit];
+ * for (i = 0; i < 8; i++)
+ * if (modes & (1 << i))
+ * printf(" %s", gsm0808_amr_mode_name(i));
+ */
+const uint8_t gsm0808_amr_modes_from_cfg[2][16] = {
+#define B(X) (1 << (GSM0808_AMR_MODE_##X))
+ /* HR */
+ {
+ /* Sn = modes */
+ [0] = B(4_75),
+ [1] = B(4_75) | B(5_90) | B(7_40),
+ [2] = B(5_90),
+ [3] = B(6_70),
+ [4] = B(7_40),
+ [5] = B(7_95),
+ [6] = 0,
+ [7] = 0,
+
+ [8] = B(4_75) | B(5_90),
+ [9] = B(4_75) | B(5_90) | B(6_70),
+ [10] = B(4_75) | B(5_90) | B(6_70) | B(7_40),
+ [11] = 0,
+ [12] = 0,
+ [13] = 0,
+ [14] = 0,
+ [15] = 0,
+ },
+ /* FR */
+ {
+ /* Sn = modes */
+ [0] = B(4_75),
+ [1] = B(4_75) | B(5_90) | B(7_40) | B(12_2),
+ [2] = B(5_90),
+ [3] = B(6_70),
+ [4] = B(7_40),
+ [5] = B(7_95),
+ [6] = B(10_2),
+ [7] = B(12_2),
+
+ [8] = B(4_75) | B(5_90),
+ [9] = B(4_75) | B(5_90) | B(6_70),
+ [10] = B(4_75) | B(5_90) | B(6_70) | B(7_40),
+ [11] = 0,
+ [12] = B(4_75) | B(5_90) | B(6_70) | B(10_2),
+ [13] = 0,
+ [14] = B(4_75) | B(5_90) | B(7_95) | B(12_2),
+ [15] = 0,
+ }
+};
+
+/* AMR mode names from GSM0808_AMR_MODE_*, for use with gsm0808_amr_modes_from_cfg.
+ *
+ * For example:
+ * printf("S9: ");
+ * uint8_t s9_modes = gsm0808_amr_modes_from_cfg[full_rate ? 1 : 0][9];
+ * for (bit = 0; bit < 8; bit++)
+ * if (s9_modes & (1 << bit))
+ * printf("%s,", gsm0808_amr_mode_name(bit));
+ */
+const struct value_string gsm0808_amr_mode_names[] = {
+ { GSM0808_AMR_MODE_4_75, "4.75" },
+ { GSM0808_AMR_MODE_5_15, "5.15" },
+ { GSM0808_AMR_MODE_5_90, "5.90" },
+ { GSM0808_AMR_MODE_6_70, "6.70" },
+ { GSM0808_AMR_MODE_7_40, "7.40" },
+ { GSM0808_AMR_MODE_7_95, "7.95" },
+ { GSM0808_AMR_MODE_10_2, "10.2" },
+ { GSM0808_AMR_MODE_12_2, "12.2" },
+ {}
+};
+
/*! @} */
diff --git a/src/gsm/gsm0808_utils.c b/src/gsm/gsm0808_utils.c
index 6bf771f8..c9f26d35 100644
--- a/src/gsm/gsm0808_utils.c
+++ b/src/gsm/gsm0808_utils.c
@@ -78,7 +78,7 @@ uint8_t gsm0808_enc_cause(struct msgb *msg, uint16_t cause)
/*! Encode TS 08.08 AoIP transport address IE
* \param[out] msg Message Buffer to which to append IE
* \param[in] ss Socket Address to be used in IE
- * \returns number of bytes added to \a msg */
+ * \returns number of bytes added to \a msg; 0 if msg is too small */
uint8_t gsm0808_enc_aoip_trasp_addr(struct msgb *msg,
const struct sockaddr_storage *ss)
{
@@ -87,16 +87,25 @@ uint8_t gsm0808_enc_aoip_trasp_addr(struct msgb *msg,
struct sockaddr_in6 *sin6;
uint16_t port = 0;
uint8_t *ptr;
- uint8_t *old_tail;
- uint8_t *tlv_len;
+ const uint8_t len_tl = 2;
+ uint8_t len_v = sizeof(port);
- OSMO_ASSERT(msg);
- OSMO_ASSERT(ss);
OSMO_ASSERT(ss->ss_family == AF_INET || ss->ss_family == AF_INET6);
+ switch (ss->ss_family) {
+ case AF_INET:
+ len_v += IP_V4_ADDR_LEN;
+ break;
+ case AF_INET6:
+ len_v += IP_V6_ADDR_LEN;
+ break;
+ }
+
+ if (msgb_tailroom(msg) < len_tl + len_v)
+ return 0;
+
msgb_put_u8(msg, GSM0808_IE_AOIP_TRASP_ADDR);
- tlv_len = msgb_put(msg,1);
- old_tail = msg->tail;
+ msgb_put_u8(msg, len_v);
switch (ss->ss_family) {
case AF_INET:
@@ -114,9 +123,7 @@ uint8_t gsm0808_enc_aoip_trasp_addr(struct msgb *msg,
}
msgb_put_u16(msg, port);
-
- *tlv_len = (uint8_t) (msg->tail - old_tail);
- return *tlv_len + 2;
+ return len_tl + len_v;
}
/*! Decode TS 08.08 AoIP transport address IE
@@ -132,7 +139,6 @@ int gsm0808_dec_aoip_trasp_addr(struct sockaddr_storage *ss,
struct sockaddr_in6 sin6;
const uint8_t *old_elem = elem;
- OSMO_ASSERT(ss);
if (!elem)
return -EINVAL;
if (len == 0)
@@ -180,7 +186,6 @@ int gsm0808_dec_aoip_trasp_addr(struct sockaddr_storage *ss,
* \returns number of bytes parsed */
int gsm0808_dec_osmux_cid(uint8_t *cid, const uint8_t *elem, uint8_t len)
{
- OSMO_ASSERT(cid);
if (!elem)
return -EINVAL;
if (len != 1)
@@ -204,21 +209,21 @@ static void decode_lai(const uint8_t *data, struct osmo_location_area_id *decode
gsm48_decode_lai2(&lai, decoded);
}
-/* Helper function for gsm0808_enc_speech_codec()
- * and gsm0808_enc_speech_codec_list() */
-static uint8_t enc_speech_codec(struct msgb *msg,
- const struct gsm0808_speech_codec *sc)
+/* Helper function for gsm0808_enc_speech_codec[_list]().
+ * Returns number of bytes appended; negative on error. */
+static int enc_speech_codec(struct msgb *msg,
+ const struct gsm0808_speech_codec *sc)
{
/* See also 3GPP TS 48.008 3.2.2.103 Speech Codec List */
uint8_t header = 0;
uint8_t *old_tail;
- bool type_extended = false;
+ bool type_extended;
/* Note: Extended codec types are codec types that require 8 instead
* of 4 bit to fully specify the selected codec. In the following,
* we check if we work with an extended type or not. We also check
* if the codec type is valid at all. */
- switch(sc->type) {
+ switch (sc->type) {
case GSM0808_SCT_FR1:
case GSM0808_SCT_FR2:
case GSM0808_SCT_FR3:
@@ -235,8 +240,7 @@ static uint8_t enc_speech_codec(struct msgb *msg,
break;
default:
/* Invalid codec type specified */
- OSMO_ASSERT(false);
- break;
+ return -EINVAL;
}
old_tail = msg->tail;
@@ -251,11 +255,10 @@ static uint8_t enc_speech_codec(struct msgb *msg,
header |= (1 << 4);
if (type_extended) {
- header |= 0x0f;
+ header |= GSM0808_SCT_EXT;
msgb_put_u8(msg, header);
msgb_put_u8(msg, sc->type);
} else {
- OSMO_ASSERT(sc->type < 0x0f);
header |= sc->type;
msgb_put_u8(msg, header);
}
@@ -273,11 +276,13 @@ static uint8_t enc_speech_codec(struct msgb *msg,
case GSM0808_SCT_FR5:
case GSM0808_SCT_HR4:
case GSM0808_SCT_CSD:
- OSMO_ASSERT((sc->cfg & 0xff00) == 0);
+ if (sc->cfg >> 8)
+ return -EINVAL;
msgb_put_u8(msg, (uint8_t) sc->cfg & 0xff);
break;
default:
- OSMO_ASSERT(sc->cfg == 0);
+ if (sc->cfg != 0)
+ return -EINVAL;
break;
}
@@ -287,27 +292,38 @@ static uint8_t enc_speech_codec(struct msgb *msg,
/*! Encode TS 08.08 Speech Codec IE
* \param[out] msg Message Buffer to which IE will be appended
* \param[in] sc Speech Codec to be encoded into IE
- * \returns number of bytes appended to \a msg */
-uint8_t gsm0808_enc_speech_codec(struct msgb *msg,
- const struct gsm0808_speech_codec *sc)
+ * \returns number of bytes appended to \a msg; negative on error */
+int gsm0808_enc_speech_codec2(struct msgb *msg,
+ const struct gsm0808_speech_codec *sc)
{
/*! See also 3GPP TS 48.008 3.2.2.103 Speech Codec List */
- uint8_t *old_tail;
uint8_t *tlv_len;
-
- OSMO_ASSERT(msg);
- OSMO_ASSERT(sc);
+ int rc;
msgb_put_u8(msg, GSM0808_IE_SPEECH_CODEC);
tlv_len = msgb_put(msg, 1);
- old_tail = msg->tail;
- enc_speech_codec(msg, sc);
+ rc = enc_speech_codec(msg, sc);
+ if (rc < 0)
+ return rc;
- *tlv_len = (uint8_t) (msg->tail - old_tail);
+ *tlv_len = rc;
return *tlv_len + 2;
}
+/*! Deprecated: gsm0808_enc_speech_codec2() wrapper for backwards compatibility.
+ * Mimics the old behavior: crash on missing and/or invalid input. */
+uint8_t gsm0808_enc_speech_codec(struct msgb *msg,
+ const struct gsm0808_speech_codec *sc)
+{
+ int rc;
+
+ rc = gsm0808_enc_speech_codec2(msg, sc);
+ OSMO_ASSERT(rc > 0);
+
+ return rc;
+}
+
/*! Decode TS 08.08 Speech Codec IE
* \param[out] sc Caller-allocated memory for Speech Codec
* \param[in] elem IE value to be decoded
@@ -320,7 +336,6 @@ int gsm0808_dec_speech_codec(struct gsm0808_speech_codec *sc,
uint8_t header;
const uint8_t *old_elem = elem;
- OSMO_ASSERT(sc);
if (!elem)
return -EINVAL;
if (len == 0)
@@ -332,7 +347,7 @@ int gsm0808_dec_speech_codec(struct gsm0808_speech_codec *sc,
/* An extended codec type needs at least two fields,
* bail if the input data length is not sufficient. */
- if ((header & 0x0F) == 0x0F && len < 2)
+ if ((header & 0x0F) == GSM0808_SCT_EXT && len < 2)
return -EINVAL;
elem++;
@@ -347,7 +362,7 @@ int gsm0808_dec_speech_codec(struct gsm0808_speech_codec *sc,
if (header & (1 << 4))
sc->tf = true;
- if ((header & 0x0F) != 0x0F) {
+ if ((header & 0x0F) != GSM0808_SCT_EXT) {
sc->type = (header & 0x0F);
} else {
sc->type = *elem;
@@ -392,35 +407,46 @@ int gsm0808_dec_speech_codec(struct gsm0808_speech_codec *sc,
/*! Encode TS 08.08 Speech Codec list
* \param[out] msg Message Buffer to which IE is to be appended
* \param[in] scl Speech Codec List to be encoded into IE
- * \returns number of bytes added to \a msg */
-uint8_t gsm0808_enc_speech_codec_list(struct msgb *msg,
- const struct gsm0808_speech_codec_list *scl)
+ * \returns number of bytes added to \a msg; negative on error */
+int gsm0808_enc_speech_codec_list2(struct msgb *msg,
+ const struct gsm0808_speech_codec_list *scl)
{
/*! See also 3GPP TS 48.008 3.2.2.103 Speech Codec List */
uint8_t *old_tail;
uint8_t *tlv_len;
unsigned int i;
- uint8_t rc;
unsigned int bytes_used = 0;
- OSMO_ASSERT(msg);
- OSMO_ASSERT(scl);
-
msgb_put_u8(msg, GSM0808_IE_SPEECH_CODEC_LIST);
tlv_len = msgb_put(msg, 1);
old_tail = msg->tail;
for (i = 0; i < scl->len; i++) {
- rc = enc_speech_codec(msg, &scl->codec[i]);
- OSMO_ASSERT(rc >= 1);
+ int rc = enc_speech_codec(msg, &scl->codec[i]);
+ if (rc < 1)
+ return rc;
bytes_used += rc;
- OSMO_ASSERT(bytes_used <= 255);
+ if (bytes_used > 0xff)
+ return -EOVERFLOW;
}
*tlv_len = (uint8_t) (msg->tail - old_tail);
return *tlv_len + 2;
}
+/*! Deprecated: gsm0808_enc_speech_codec_list2() wrapper for backwards compatibility.
+ * Mimics the old behavior: crash on missing and/or invalid input. */
+uint8_t gsm0808_enc_speech_codec_list(struct msgb *msg,
+ const struct gsm0808_speech_codec_list *scl)
+{
+ int rc;
+
+ rc = gsm0808_enc_speech_codec_list2(msg, scl);
+ OSMO_ASSERT(rc > 0);
+
+ return rc;
+}
+
/*! Decode TS 08.08 Speech Codec list IE
* \param[out] scl Caller-provided memory to store codec list
* \param[in] elem IE value to be decoded
@@ -435,7 +461,6 @@ int gsm0808_dec_speech_codec_list(struct gsm0808_speech_codec_list *scl,
int rc;
uint8_t decoded = 0;
- OSMO_ASSERT(scl);
if (!elem)
return -EINVAL;
@@ -472,16 +497,8 @@ uint8_t gsm0808_enc_channel_type(struct msgb *msg,
uint8_t *old_tail;
uint8_t *tlv_len;
- OSMO_ASSERT(msg);
- OSMO_ASSERT(ct);
OSMO_ASSERT(ct->perm_spch_len <= CHANNEL_TYPE_ELEMENT_MAXLEN - 2);
- /* FIXME: Implement encoding support for Data
- * and Speech + CTM Text Telephony */
- if ((ct->ch_indctr & 0x0f) != GSM0808_CHAN_SPEECH
- && (ct->ch_indctr & 0x0f) != GSM0808_CHAN_SIGN)
- OSMO_ASSERT(false);
-
msgb_put_u8(msg, GSM0808_IE_CHANNEL_TYPE);
tlv_len = msgb_put(msg, 1);
old_tail = msg->tail;
@@ -489,12 +506,45 @@ uint8_t gsm0808_enc_channel_type(struct msgb *msg,
msgb_put_u8(msg, ct->ch_indctr & 0x0f);
msgb_put_u8(msg, ct->ch_rate_type);
- for (i = 0; i < ct->perm_spch_len; i++) {
- byte = ct->perm_spch[i];
+ switch (ct->ch_indctr) {
+ case GSM0808_CHAN_DATA:
+ byte = ct->data_rate;
+
+ if (!ct->data_transparent)
+ byte |= 0x40; /* Set T/NT */
+
+ if (ct->data_rate_allowed_is_set) {
+ OSMO_ASSERT(!ct->data_transparent);
+ byte |= 0x80; /* Set ext */
+ msgb_put_u8(msg, byte);
- if (i < ct->perm_spch_len - 1)
- byte |= 0x80;
+ byte = ct->data_rate_allowed;
+ if (ct->data_asym_pref_is_set) {
+ byte |= 0x80; /* Set ext */
+ msgb_put_u8(msg, byte);
+
+ /* Set asymmetry indication, rest is spare */
+ byte = ct->data_asym_pref << 5;
+ }
+ }
msgb_put_u8(msg, byte);
+ break;
+ case GSM0808_CHAN_SPEECH:
+ case GSM0808_CHAN_SPEECH_CTM_TEXT_TELEPHONY:
+ for (i = 0; i < ct->perm_spch_len; i++) {
+ byte = ct->perm_spch[i];
+
+ if (i < ct->perm_spch_len - 1)
+ byte |= 0x80;
+ msgb_put_u8(msg, byte);
+ }
+ break;
+ case GSM0808_CHAN_SIGN:
+ /* Octet 5 is spare */
+ msgb_put_u8(msg, 0);
+ break;
+ default:
+ OSMO_ASSERT(false);
}
*tlv_len = (uint8_t) (msg->tail - old_tail);
@@ -514,7 +564,6 @@ int gsm0808_dec_channel_type(struct gsm0808_channel_type *ct,
uint8_t byte;
const uint8_t *old_elem = elem;
- OSMO_ASSERT(ct);
if (!elem)
return -EINVAL;
if (len < 3 || len > 11)
@@ -524,17 +573,60 @@ int gsm0808_dec_channel_type(struct gsm0808_channel_type *ct,
ct->ch_indctr = (*elem) & 0x0f;
elem++;
- ct->ch_rate_type = (*elem) & 0x0f;
+ ct->ch_rate_type = *elem;
elem++;
- for (i = 0; i < ARRAY_SIZE(ct->perm_spch); i++) {
+ switch (ct->ch_indctr) {
+ case GSM0808_CHAN_DATA:
byte = *elem;
elem++;
- ct->perm_spch[i] = byte & 0x7f;
- if ((byte & 0x80) == 0x00)
- break;
+ ct->data_transparent = !(byte & 0x40); /* T/NT */
+ ct->data_rate = byte & 0x3f;
+
+ /* Optional extension for non-transparent service */
+ if (byte & 0x80) {
+ if (ct->data_transparent)
+ return -EINVAL;
+ if (elem - old_elem >= len)
+ return -EOVERFLOW;
+ byte = *elem;
+ elem++;
+
+ ct->data_rate_allowed_is_set = true;
+ ct->data_rate_allowed = byte & 0x7f;
+
+ /* Optional extension */
+ if (byte & 0x80) {
+ if (elem - old_elem >= len)
+ return -EOVERFLOW;
+ byte = *elem;
+ elem++;
+
+ ct->data_asym_pref_is_set = true;
+ ct->data_asym_pref = byte & 0x60 >> 5;
+ }
+ }
+ break;
+ case GSM0808_CHAN_SPEECH:
+ case GSM0808_CHAN_SPEECH_CTM_TEXT_TELEPHONY:
+ for (i = 0; i < ARRAY_SIZE(ct->perm_spch); i++) {
+ if (elem - old_elem >= len)
+ return -EOVERFLOW;
+
+ byte = *elem;
+ elem++;
+ ct->perm_spch[i] = byte & 0x7f;
+ if ((byte & 0x80) == 0x00)
+ break;
+ }
+ ct->perm_spch_len = i + 1;
+ break;
+ case GSM0808_CHAN_SIGN:
+ /* Octet 5 is spare */
+ break;
+ default:
+ return -ENOTSUP;
}
- ct->perm_spch_len = i + 1;
return (int)(elem - old_elem);
}
@@ -703,8 +795,6 @@ uint8_t gsm0808_enc_encrypt_info(struct msgb *msg,
uint8_t *old_tail;
uint8_t *tlv_len;
- OSMO_ASSERT(msg);
- OSMO_ASSERT(ei);
OSMO_ASSERT(ei->key_len <= ARRAY_SIZE(ei->key));
OSMO_ASSERT(ei->perm_algo_len <= ENCRY_INFO_PERM_ALGO_MAXLEN);
@@ -721,6 +811,8 @@ uint8_t gsm0808_enc_encrypt_info(struct msgb *msg,
}
msgb_put_u8(msg, perm_algo);
+ /* FIXME: 48.008 3.2.2.10 Encryption Information says:
+ * "When present, the key shall be 8 octets long." */
ptr = msgb_put(msg, ei->key_len);
memcpy(ptr, ei->key, ei->key_len);
@@ -741,7 +833,6 @@ int gsm0808_dec_encrypt_info(struct gsm0808_encrypt_info *ei,
unsigned int perm_algo_len = 0;
const uint8_t *old_elem = elem;
- OSMO_ASSERT(ei);
if (!elem)
return -EINVAL;
if (len == 0)
@@ -760,6 +851,8 @@ int gsm0808_dec_encrypt_info(struct gsm0808_encrypt_info *ei,
}
ei->perm_algo_len = perm_algo_len;
+ /* FIXME: 48.008 3.2.2.10 Encryption Information says:
+ * "When present, the key shall be 8 octets long." */
ei->key_len = len - 1;
memcpy(ei->key, elem, ei->key_len);
elem+=ei->key_len;
@@ -767,6 +860,80 @@ int gsm0808_dec_encrypt_info(struct gsm0808_encrypt_info *ei,
return (int)(elem - old_elem);
}
+/*! Encode TS 48.008 Kc128 IE.
+ * \param[out] msg Message Buffer to which IE is to be appended.
+ * \param[in] kc128 Pointer to 16 bytes of Kc128 key data.
+ * \returns number of bytes appended to msg */
+int gsm0808_enc_kc128(struct msgb *msg, const uint8_t *kc128)
+{
+ uint8_t *start = msg->tail;
+ msgb_tv_fixed_put(msg, GSM0808_IE_KC_128, 16, kc128);
+ return msg->tail - start;
+}
+
+/*! Decode TS 48.008 Kc128 IE.
+ * \param[out] kc128 Target buffer for received Kc128 key, 16 bytes long.
+ * \param[in] elem IE value to be decoded (without IE discriminator).
+ * \param[in] len Length of elem in bytes.
+ * \returns number of bytes parsed; negative on error */
+int gsm0808_dec_kc128(uint8_t *kc128, const uint8_t *elem, uint8_t len)
+{
+ if (len != 16)
+ return -EINVAL;
+ memcpy(kc128, elem, 16);
+ return len;
+}
+
+/* Store individual Cell Identifier information in a CGI, without clearing the remaining ones.
+ * This is useful to supplement one CGI with information from more than one Cell Identifier,
+ * which in turn is useful to match Cell Identifiers of differing kinds to each other.
+ * Before first invocation, clear the *dst struct externally, this function does only write those members
+ * that are present in parameter u.
+ */
+static void cell_id_to_cgi(struct osmo_cell_global_id *dst,
+ enum CELL_IDENT discr, const union gsm0808_cell_id_u *u)
+{
+ switch (discr) {
+ case CELL_IDENT_WHOLE_GLOBAL:
+ *dst = u->global;
+ return;
+
+ case CELL_IDENT_WHOLE_GLOBAL_PS:
+ dst->lai = u->global_ps.rai.lac;
+ dst->cell_identity = u->global_ps.cell_identity;
+ return;
+
+ case CELL_IDENT_LAC_AND_CI:
+ dst->lai.lac = u->lac_and_ci.lac;
+ dst->cell_identity = u->lac_and_ci.ci;
+ return;
+
+ case CELL_IDENT_CI:
+ dst->cell_identity = u->ci;
+ return;
+
+ case CELL_IDENT_LAI_AND_LAC:
+ dst->lai = u->lai_and_lac;
+ return;
+
+ case CELL_IDENT_LAC:
+ dst->lai.lac = u->lac;
+ return;
+
+ case CELL_IDENT_SAI:
+ dst->lai = u->sai.lai;
+ return;
+
+ case CELL_IDENT_NO_CELL:
+ case CELL_IDENT_BSS:
+ case CELL_IDENT_UTRAN_PLMN_LAC_RNC:
+ case CELL_IDENT_UTRAN_RNC:
+ case CELL_IDENT_UTRAN_LAC_RNC:
+ /* No values to set. */
+ return;
+ }
+}
+
/* Return the size of the value part of a cell identifier of given type */
int gsm0808_cell_id_size(enum CELL_IDENT discr)
{
@@ -784,6 +951,10 @@ int gsm0808_cell_id_size(enum CELL_IDENT discr)
case CELL_IDENT_BSS:
case CELL_IDENT_NO_CELL:
return 0;
+ case CELL_IDENT_SAI:
+ return 7;
+ case CELL_IDENT_WHOLE_GLOBAL_PS:
+ return 8;
default:
return -EINVAL;
}
@@ -828,6 +999,20 @@ int gsm0808_decode_cell_id_u(union gsm0808_cell_id_u *out, enum CELL_IDENT discr
case CELL_IDENT_NO_CELL:
/* Does not have any list items */
break;
+ case CELL_IDENT_SAI:
+ if (len < 7)
+ return -EINVAL;
+ decode_lai(buf, &out->sai.lai);
+ out->sai.sac = osmo_load16be(buf + sizeof(struct gsm48_loc_area_id));
+ break;
+ case CELL_IDENT_WHOLE_GLOBAL_PS:
+ /* 3GPP TS 48.018 sec 11.3.9 "Cell Identifier" */
+ if (len < 8)
+ return -EINVAL;
+ decode_lai(buf, (struct osmo_location_area_id *)&out->global_ps.rai); /* rai contains lai + non-decoded rac */
+ out->global_ps.rai.rac = *(buf + sizeof(struct gsm48_loc_area_id));
+ out->global_ps.cell_identity = osmo_load16be(buf + sizeof(struct gsm48_loc_area_id) + 1);
+ break;
default:
/* Remaining cell identification types are not implemented. */
return -EINVAL;
@@ -869,6 +1054,26 @@ void gsm0808_msgb_put_cell_id_u(struct msgb *msg, enum CELL_IDENT id_discr, cons
case CELL_IDENT_NO_CELL:
/* Does not have any list items */
break;
+
+ case CELL_IDENT_SAI: {
+ const struct osmo_service_area_id *id = &u->sai;
+ struct gsm48_loc_area_id lai;
+ gsm48_generate_lai2(&lai, &id->lai);
+ memcpy(msgb_put(msg, sizeof(lai)), &lai, sizeof(lai));
+ msgb_put_u16(msg, id->sac);
+ break;
+ }
+
+ case CELL_IDENT_WHOLE_GLOBAL_PS: {
+ /* 3GPP TS 48.018 sec 11.3.9 "Cell Identifier" */
+ const struct osmo_cell_global_id_ps *id = &u->global_ps;
+ struct gsm48_loc_area_id lai;
+ gsm48_generate_lai2(&lai, &id->rai.lac);
+ memcpy(msgb_put(msg, sizeof(lai)), &lai, sizeof(lai));
+ memcpy(msgb_put(msg, 1), &id->rai.rac, 1);
+ msgb_put_u16(msg, id->cell_identity);
+ break;
+ }
default:
/* Support for other identifier list types is not implemented. */
OSMO_ASSERT(false);
@@ -885,19 +1090,31 @@ uint8_t gsm0808_enc_cell_id_list2(struct msgb *msg,
uint8_t *old_tail;
uint8_t *tlv_len;
unsigned int i;
-
- OSMO_ASSERT(msg);
- OSMO_ASSERT(cil);
+ uint8_t id_discr;
msgb_put_u8(msg, GSM0808_IE_CELL_IDENTIFIER_LIST);
tlv_len = msgb_put(msg, 1);
old_tail = msg->tail;
- msgb_put_u8(msg, cil->id_discr & 0x0f);
+ /* CGI-PS is an osmocom-specific type. In here we don't care about the
+ * PS part, only the CS one. */
+ if (cil->id_discr == CELL_IDENT_WHOLE_GLOBAL_PS)
+ id_discr = CELL_IDENT_WHOLE_GLOBAL;
+ else
+ id_discr = cil->id_discr & 0x0f;
+
+ msgb_put_u8(msg, id_discr);
OSMO_ASSERT(cil->id_list_len <= GSM0808_CELL_ID_LIST2_MAXLEN);
- for (i = 0; i < cil->id_list_len; i++)
- gsm0808_msgb_put_cell_id_u(msg, cil->id_discr, &cil->id_list[i]);
+ for (i = 0; i < cil->id_list_len; i++) {
+ if (cil->id_discr == CELL_IDENT_WHOLE_GLOBAL_PS) {
+ union gsm0808_cell_id_u u;
+ cell_id_to_cgi(&u.global, cil->id_discr, &cil->id_list[i]);
+ gsm0808_msgb_put_cell_id_u(msg, CELL_IDENT_WHOLE_GLOBAL, &u);
+ } else {
+ gsm0808_msgb_put_cell_id_u(msg, cil->id_discr, &cil->id_list[i]);
+ }
+ }
*tlv_len = (uint8_t) (msg->tail - old_tail);
return *tlv_len + 2;
@@ -916,9 +1133,6 @@ uint8_t gsm0808_enc_cell_id_list(struct msgb *msg,
uint8_t *tlv_len;
unsigned int i;
- OSMO_ASSERT(msg);
- OSMO_ASSERT(cil);
-
msgb_put_u8(msg, GSM0808_IE_CELL_IDENTIFIER_LIST);
tlv_len = msgb_put(msg, 1);
old_tail = msg->tail;
@@ -1057,6 +1271,31 @@ static int parse_cell_id_lac_list(struct gsm0808_cell_id_list2 *cil, const uint8
return i;
}
+static int parse_cell_id_sai_list(struct gsm0808_cell_id_list2 *cil, const uint8_t *data, size_t remain, size_t *consumed)
+{
+ struct osmo_service_area_id *id;
+ uint16_t *sac_be;
+ size_t lai_offset;
+ int i = 0;
+ const size_t elemlen = sizeof(struct gsm48_loc_area_id) + sizeof(*sac_be);
+
+ *consumed = 0;
+ while (remain >= elemlen) {
+ if (i >= GSM0808_CELL_ID_LIST2_MAXLEN)
+ return -ENOSPC;
+ id = &cil->id_list[i].sai;
+ lai_offset = i * elemlen;
+ decode_lai(&data[lai_offset], &id->lai);
+ sac_be = (uint16_t *)(&data[lai_offset + sizeof(struct gsm48_loc_area_id)]);
+ id->sac = osmo_load16be(sac_be);
+ *consumed += elemlen;
+ remain -= elemlen;
+ i++;
+ }
+
+ return i;
+}
+
/*! Decode Cell Identifier List IE
* \param[out] cil Caller-provided memory to store Cell ID list
* \param[in] elem IE value to be decoded
@@ -1069,7 +1308,6 @@ int gsm0808_dec_cell_id_list2(struct gsm0808_cell_id_list2 *cil,
size_t bytes_elem = 0;
int list_len = 0;
- OSMO_ASSERT(cil);
if (!elem)
return -EINVAL;
if (len == 0)
@@ -1101,6 +1339,12 @@ int gsm0808_dec_cell_id_list2(struct gsm0808_cell_id_list2 *cil,
case CELL_IDENT_NO_CELL:
/* Does not have any list items */
break;
+ case CELL_IDENT_SAI:
+ list_len = parse_cell_id_sai_list(cil, elem, len, &bytes_elem);
+ break;
+ case CELL_IDENT_UTRAN_PLMN_LAC_RNC:
+ case CELL_IDENT_UTRAN_RNC:
+ case CELL_IDENT_UTRAN_LAC_RNC:
default:
/* Remaining cell identification types are not implemented. */
return -EINVAL;
@@ -1131,7 +1375,6 @@ int gsm0808_dec_cell_id_list(struct gsm0808_cell_id_list *cil,
const uint8_t *old_elem = elem;
unsigned int item_count = 0;
- OSMO_ASSERT(cil);
if (!elem)
return -EINVAL;
if (len == 0)
@@ -1289,9 +1532,6 @@ uint8_t gsm0808_enc_cell_id(struct msgb *msg, const struct gsm0808_cell_id *ci)
.id_list_len = 1,
};
- OSMO_ASSERT(msg);
- OSMO_ASSERT(ci);
-
ie_tag = msg->tail;
rc = gsm0808_enc_cell_id_list2(msg, &cil);
@@ -1498,7 +1738,7 @@ int gsm48_mr_cfg_from_gsm0808_sc_cfg(struct gsm48_multi_rate_conf *cfg,
/* Rate 5,15k can never be selected (see table) */
cfg->m5_15 = 0;
- if (s15_s0 & GSM0808_SC_CFG_AMR_4_75_5_90_7_40_12_20 & 0xff) {
+ if (s15_s0 & GSM0808_SC_CFG_AMR_4_75_5_90_7_40_12_20) {
/* Table Table 7.11.3.1.3-2 lists one mode that selects 4
* rates at once (Config-NB-Code = 1). The rates selected
* are known to be compatible between GERAN and UTRAN, since
@@ -1592,6 +1832,10 @@ int gsm0808_cell_id_u_name(char *buf, size_t buflen,
return snprintf(buf, buflen, "%s", osmo_lai_name(&u->lai_and_lac));
case CELL_IDENT_WHOLE_GLOBAL:
return snprintf(buf, buflen, "%s", osmo_cgi_name(&u->global));
+ case CELL_IDENT_WHOLE_GLOBAL_PS:
+ return snprintf(buf, buflen, "%s", osmo_cgi_ps_name(&u->global_ps));
+ case CELL_IDENT_SAI:
+ return snprintf(buf, buflen, "%s", osmo_sai_name(&u->sai));
default:
/* For CELL_IDENT_BSS and CELL_IDENT_NO_CELL, just print the discriminator.
* Same for kinds we have no string representation of yet. */
@@ -1599,47 +1843,6 @@ int gsm0808_cell_id_u_name(char *buf, size_t buflen,
}
}
-/* Store individual Cell Identifier information in a CGI, without clearing the remaining ones.
- * This is useful to supplement one CGI with information from more than one Cell Identifier,
- * which in turn is useful to match Cell Identifiers of differing kinds to each other.
- * Before first invocation, clear the *dst struct externally, this function does only write those members
- * that are present in parameter u.
- */
-static void cell_id_to_cgi(struct osmo_cell_global_id *dst,
- enum CELL_IDENT discr, const union gsm0808_cell_id_u *u)
-{
- switch (discr) {
- case CELL_IDENT_WHOLE_GLOBAL:
- *dst = u->global;
- return;
-
- case CELL_IDENT_LAC_AND_CI:
- dst->lai.lac = u->lac_and_ci.lac;
- dst->cell_identity = u->lac_and_ci.ci;
- return;
-
- case CELL_IDENT_CI:
- dst->cell_identity = u->ci;
- return;
-
- case CELL_IDENT_LAI_AND_LAC:
- dst->lai = u->lai_and_lac;
- return;
-
- case CELL_IDENT_LAC:
- dst->lai.lac = u->lac;
- return;
-
- case CELL_IDENT_NO_CELL:
- case CELL_IDENT_BSS:
- case CELL_IDENT_UTRAN_PLMN_LAC_RNC:
- case CELL_IDENT_UTRAN_RNC:
- case CELL_IDENT_UTRAN_LAC_RNC:
- /* No values to set. */
- return;
- }
-}
-
/*! Return true if the common information between the two Cell Identifiers match.
* For example, if a LAC+CI is compared to LAC, return true if the LAC are the same.
* Note that CELL_IDENT_NO_CELL will always return false.
@@ -1762,6 +1965,14 @@ void gsm0808_cell_id_from_cgi(struct gsm0808_cell_id *cid, enum CELL_IDENT id_di
cid->id.global = *cgi;
return;
+ case CELL_IDENT_WHOLE_GLOBAL_PS:
+ cid->id.global_ps = (struct osmo_cell_global_id_ps){
+ .rai = {
+ .lac = cgi->lai,
+ },
+ .cell_identity = cgi->cell_identity,
+ };
+ return;
case CELL_IDENT_LAC_AND_CI:
cid->id.lac_and_ci = (struct osmo_lac_and_ci_id){
.lac = cgi->lai.lac,
@@ -1781,6 +1992,12 @@ void gsm0808_cell_id_from_cgi(struct gsm0808_cell_id *cid, enum CELL_IDENT id_di
cid->id.lac = cgi->lai.lac;
return;
+ case CELL_IDENT_SAI:
+ cid->id.sai = (struct osmo_service_area_id){
+ .lai = cgi->lai,
+ };
+ return;
+
case CELL_IDENT_NO_CELL:
case CELL_IDENT_BSS:
case CELL_IDENT_UTRAN_PLMN_LAC_RNC:
@@ -1805,6 +2022,11 @@ int gsm0808_cell_id_to_cgi(struct osmo_cell_global_id *cgi, const struct gsm0808
*cgi = cid->id.global;
return OSMO_CGI_PART_PLMN | OSMO_CGI_PART_LAC | OSMO_CGI_PART_CI;
+ case CELL_IDENT_WHOLE_GLOBAL_PS:
+ cgi->lai = cid->id.global_ps.rai.lac;
+ cgi->cell_identity = cid->id.global_ps.cell_identity;
+ return OSMO_CGI_PART_PLMN | OSMO_CGI_PART_LAC | OSMO_CGI_PART_CI;
+
case CELL_IDENT_LAC_AND_CI:
cgi->lai.lac = cid->id.lac_and_ci.lac;
cgi->cell_identity = cid->id.lac_and_ci.ci;
@@ -1822,6 +2044,10 @@ int gsm0808_cell_id_to_cgi(struct osmo_cell_global_id *cgi, const struct gsm0808
cgi->lai.lac = cid->id.lac;
return OSMO_CGI_PART_LAC;
+ case CELL_IDENT_SAI:
+ cgi->lai = cid->id.sai.lai;
+ return OSMO_CGI_PART_PLMN | OSMO_CGI_PART_LAC;
+
case CELL_IDENT_NO_CELL:
case CELL_IDENT_BSS:
case CELL_IDENT_UTRAN_PLMN_LAC_RNC:
@@ -1844,28 +2070,17 @@ const struct value_string gsm0808_cell_id_discr_names[] = {
{ CELL_IDENT_UTRAN_PLMN_LAC_RNC, "UTRAN-PLMN-LAC-RNC" },
{ CELL_IDENT_UTRAN_RNC, "UTRAN-RNC" },
{ CELL_IDENT_UTRAN_LAC_RNC, "UTRAN-LAC-RNC" },
+ { CELL_IDENT_SAI, "SAI" },
+ { CELL_IDENT_WHOLE_GLOBAL_PS, "CGI-PS"},
{ 0, NULL }
};
-#define APPEND_THING(func, args...) do { \
- int remain = buflen - (pos - buf); \
- int l = func(pos, remain, ##args); \
- if (l < 0 || l > remain) \
- pos = buf + buflen; \
- else \
- pos += l; \
- if (l > 0) \
- total_len += l; \
- } while(0)
-#define APPEND_STR(fmt, args...) APPEND_THING(snprintf, fmt, ##args)
-#define APPEND_CELL_ID_U(DISCR, U) APPEND_THING(gsm0808_cell_id_u_name, DISCR, U)
-
char *gsm0808_cell_id_name_buf(char *buf, size_t buflen, const struct gsm0808_cell_id *cid)
{
- char *pos = buf;
- int total_len = 0;
- APPEND_STR("%s:", gsm0808_cell_id_discr_name(cid->id_discr));
- APPEND_CELL_ID_U(cid->id_discr, &cid->id);
+ struct osmo_strbuf sb = { .buf = buf, .len = buflen };
+
+ OSMO_STRBUF_PRINTF(sb, "%s:", gsm0808_cell_id_discr_name(cid->id_discr));
+ OSMO_STRBUF_APPEND(sb, gsm0808_cell_id_u_name, cid->id_discr, &cid->id);
return buf;
}
@@ -1912,30 +2127,31 @@ char *gsm0808_cell_id_name_c(const void *ctx, const struct gsm0808_cell_id *cid)
*/
int gsm0808_cell_id_list_name_buf(char *buf, size_t buflen, const struct gsm0808_cell_id_list2 *cil)
{
- char *pos = buf;
- int total_len = 0;
- int i;
+ struct osmo_strbuf sb = { .buf = buf, .len = buflen };
- APPEND_STR("%s[%u]", gsm0808_cell_id_discr_name(cil->id_discr), cil->id_list_len);
+ OSMO_STRBUF_PRINTF(sb, "%s[%u]",
+ gsm0808_cell_id_discr_name(cil->id_discr),
+ cil->id_list_len);
switch (cil->id_discr) {
case CELL_IDENT_BSS:
case CELL_IDENT_NO_CELL:
- return total_len;
+ return sb.chars_needed;
default:
break;
}
- APPEND_STR(":{");
+ OSMO_STRBUF_PRINTF(sb, ":{");
- for (i = 0; i < cil->id_list_len; i++) {
+ for (unsigned int i = 0; i < cil->id_list_len; i++) {
if (i)
- APPEND_STR(", ");
- APPEND_CELL_ID_U(cil->id_discr, &cil->id_list[i]);
+ OSMO_STRBUF_PRINTF(sb, ", ");
+ OSMO_STRBUF_APPEND(sb, gsm0808_cell_id_u_name,
+ cil->id_discr, &cil->id_list[i]);
}
- APPEND_STR("}");
- return total_len;
+ OSMO_STRBUF_PRINTF(sb, "}");
+ return sb.chars_needed;
}
/*! Return a human-readable representation of \a cil in a static buffer.
@@ -1957,9 +2173,6 @@ char *gsm0808_cell_id_list_name_c(const void *ctx, const struct gsm0808_cell_id_
return buf;
}
-#undef APPEND_STR
-#undef APPEND_CELL_ID_U
-
char *gsm0808_channel_type_name_buf(char *buf, size_t buf_len, const struct gsm0808_channel_type *ct)
{
snprintf(buf, buf_len, "ch_indctr=0x%x ch_rate_type=0x%x perm_spch=%s",
@@ -1982,4 +2195,367 @@ char *gsm0808_channel_type_name_c(const void *ctx, const struct gsm0808_channel_
return gsm0808_channel_type_name_buf(buf, 128, ct);
}
+/*! Encode Group Call Reference IE (3GPP TS 48.008 3.2.2.55).
+ * \param[out] msg Message Buffer to which IE is to be appended
+ * \param[in] gc Group Call Reference to be encoded
+ * \returns number of bytes appended to \a msg */
+uint8_t gsm0808_enc_group_callref(struct msgb *msg, const struct gsm0808_group_callref *gc)
+{
+ uint8_t *old_tail;
+ uint8_t *tlv_len;
+
+ OSMO_ASSERT(msg);
+ OSMO_ASSERT(gc);
+
+ msgb_put_u8(msg, GSM0808_IE_GROUP_CALL_REFERENCE);
+ tlv_len = msgb_put(msg, 1);
+ old_tail = msg->tail;
+
+ memcpy(msgb_put(msg, sizeof(*gc)), gc, sizeof(*gc));
+
+ *tlv_len = (uint8_t) (msg->tail - old_tail);
+ return *tlv_len + 2;
+}
+
+/*! Decode Group Call Reference IE (3GPP TS 48.008 3.2.2.55).
+ * \param[out] gc Group Call Reference structure to store data
+ * \param[in] elem IE value to be decoded.
+ * \param[in] len Length of \a elem in bytes.
+ * \returns number of bytes parsed; negative on error */
+int gsm0808_dec_group_callref(struct gsm0808_group_callref *gc, const uint8_t *elem, uint8_t len)
+{
+ OSMO_ASSERT(gc);
+ OSMO_ASSERT(elem);
+
+ if (len != sizeof(*gc))
+ return -EINVAL;
+
+ memcpy(gc, elem, sizeof(*gc));
+
+ return len;
+}
+
+/*! Encode Priority IE (3GPP TS 48.008 3.2.2.18).
+ * \param[out] msg Message Buffer to which IE is to be appended
+ * \param[in] pri Priority to be encoded
+ * \returns number of bytes appended to \a msg */
+uint8_t gsm0808_enc_priority(struct msgb *msg, const struct gsm0808_priority *pri)
+{
+ uint8_t *old_tail;
+ uint8_t *tlv_len;
+
+ OSMO_ASSERT(msg);
+ OSMO_ASSERT(pri);
+
+ msgb_put_u8(msg, GSM0808_IE_PRIORITY);
+ tlv_len = msgb_put(msg, 1);
+ old_tail = msg->tail;
+
+ memcpy(msgb_put(msg, sizeof(*pri)), pri, sizeof(*pri));
+
+ *tlv_len = (uint8_t) (msg->tail - old_tail);
+ return *tlv_len + 2;
+}
+
+/*! Decode Priority IE (3GPP TS 48.008 3.2.2.18).
+ * \param[out] pri Priority structure to store data
+ * \param[in] elem IE value to be decoded.
+ * \param[in] len Length of \a elem in bytes.
+ * \returns number of bytes parsed; negative on error */
+int gsm0808_dec_priority(struct gsm0808_priority *pri, const uint8_t *elem, uint8_t len)
+{
+ OSMO_ASSERT(pri);
+ OSMO_ASSERT(elem);
+
+ if (len != sizeof(*pri))
+ return -EINVAL;
+
+ memcpy(pri, elem, sizeof(*pri));
+
+ return len;
+}
+
+/*! Encode VGCS Feature Flags IE (3GPP TS 48.008 3.2.2.88).
+ * \param[out] msg Message Buffer to which IE is to be appended
+ * \param[in] ff VGCS Feature Flags to be encoded
+ * \returns number of bytes appended to \a msg */
+uint8_t gsm0808_enc_vgcs_feature_flags(struct msgb *msg, const struct gsm0808_vgcs_feature_flags *ff)
+{
+ uint8_t *old_tail;
+ uint8_t *tlv_len;
+
+ OSMO_ASSERT(msg);
+ OSMO_ASSERT(ff);
+
+ msgb_put_u8(msg, GSM0808_IE_VGCS_FEATURE_FLAGS);
+ tlv_len = msgb_put(msg, 1);
+ old_tail = msg->tail;
+
+ memcpy(msgb_put(msg, sizeof(*ff)), ff, sizeof(*ff));
+
+ *tlv_len = (uint8_t) (msg->tail - old_tail);
+ return *tlv_len + 2;
+}
+
+/*! Decode VGCS Feature Flags IE (3GPP TS 48.008 3.2.2.88).
+ * \param[out] ff VGCS Feature Flags structure to store data
+ * \param[in] elem IE value to be decoded.
+ * \param[in] len Length of \a elem in bytes.
+ * \returns number of bytes parsed; negative on error */
+int gsm0808_dec_vgcs_feature_flags(struct gsm0808_vgcs_feature_flags *ff, const uint8_t *elem, uint8_t len)
+{
+ OSMO_ASSERT(ff);
+ OSMO_ASSERT(elem);
+
+ if (len != sizeof(*ff))
+ return -EINVAL;
+
+ memcpy(ff, elem, sizeof(*ff));
+
+ return len;
+}
+
+/*! Encode Data Identity IE (3GPP TS 48.008 3.2.2.99).
+ * \param[out] msg Message Buffer to which IE is to be appended
+ * \param[in] di Data Identity to be encoded
+ * \returns number of bytes appended to \a msg */
+uint8_t gsm0808_enc_data_identity(struct msgb *msg, const struct gsm0808_data_identity *ti)
+{
+ uint8_t *old_tail;
+ uint8_t *tlv_len;
+
+ OSMO_ASSERT(msg);
+ OSMO_ASSERT(ti);
+
+ msgb_put_u8(msg, GSM0808_IE_DATA_IDENTITY);
+ tlv_len = msgb_put(msg, 1);
+ old_tail = msg->tail;
+
+ memcpy(msgb_put(msg, sizeof(*ti)), ti, sizeof(*ti));
+
+ *tlv_len = (uint8_t) (msg->tail - old_tail);
+ return *tlv_len + 2;
+}
+
+/*! Decode Data Identity IE (3GPP TS 48.008 3.2.2.99).
+ * \param[out] di Data Identity structure to store data
+ * \param[in] elem IE value to be decoded.
+ * \param[in] len Length of \a elem in bytes.
+ * \returns number of bytes parsed; negative on error */
+int gsm0808_dec_data_identity(struct gsm0808_data_identity *ti, const uint8_t *elem, uint8_t len)
+{
+ OSMO_ASSERT(ti);
+ OSMO_ASSERT(elem);
+
+ if (len < sizeof(*ti))
+ return -EINVAL;
+
+ memcpy(ti, elem, sizeof(*ti));
+
+ return len;
+}
+
+/*! Encode MSISDN IE (3GPP TS 48.008 3.2.2.101).
+ * \param[out] msg Message Buffer to which IE is to be appended
+ * \param[in] msisdn MSISDN to be encoded
+ * \returns number of bytes appended to \a msg */
+uint8_t gsm0808_enc_msisdn(struct msgb *msg, const char *msisdn)
+{
+ uint8_t *tlv_len;
+ uint8_t bcd_lv[11];
+ int rc;
+
+ OSMO_ASSERT(msg);
+ OSMO_ASSERT(msisdn);
+
+ rc = gsm48_encode_bcd_number(bcd_lv, sizeof(bcd_lv), 0, msisdn);
+ if (rc < 1)
+ return 0;
+
+ msgb_put_u8(msg, GSM0808_IE_MSISDN);
+ tlv_len = msgb_put(msg, rc);
+
+ memcpy(tlv_len, bcd_lv, rc);
+
+ *tlv_len = rc - 1;
+ return *tlv_len + 2;
+}
+
+/*! Decode MSISDN IE (3GPP TS 48.008 3.2.2.101).
+ * \param[out] msisdn MSISDN structure to store data
+ * \param[in] elem IE value to be decoded.
+ * \param[in] len Length of \a elem in bytes.
+ * \returns number of bytes parsed; negative on error */
+int gsm0808_dec_msisdn(char *msisdn, const char *elem, uint8_t len)
+{
+ OSMO_ASSERT(msisdn);
+ OSMO_ASSERT(elem);
+
+ if (len < 1)
+ return -EINVAL;
+
+ gsm48_decode_bcd_number(msisdn, MSISDN_MAXLEN + 1, (uint8_t *)(elem - 1), 0);
+
+ return len;
+}
+
+/*! Encode Talker Identity IE (3GPP TS 48.008 3.2.2.91).
+ * \param[out] msg Message Buffer to which IE is to be appended
+ * \param[in] ti Talker Identity to be encoded
+ * \returns number of bytes appended to \a msg */
+uint8_t gsm0808_enc_talker_identity(struct msgb *msg, const struct gsm0808_talker_identity *ti)
+{
+ uint8_t *old_tail;
+ uint8_t *tlv_len;
+ uint8_t *ptr;
+ unsigned int bytes;
+
+ OSMO_ASSERT(msg);
+ OSMO_ASSERT(ti);
+
+ msgb_put_u8(msg, GSM0808_IE_TALKER_IDENTITY);
+ tlv_len = msgb_put(msg, 1);
+ old_tail = msg->tail;
+
+ bytes = (ti->id_bits + 7) >> 3;
+ ptr = msgb_put(msg, 1 + bytes);
+ ptr[0] = -ti->id_bits & 0x7;
+ memcpy(ptr + 1, ti->talker_id, bytes);
+
+ *tlv_len = (uint8_t) (msg->tail - old_tail);
+ return *tlv_len + 2;
+}
+
+/*! Decode Talker Identity IE (3GPP TS 48.008 3.2.2.91).
+ * \param[out] ti Talker Identity structure to store data
+ * \param[in] elem IE value to be decoded.
+ * \param[in] len Length of \a elem in bytes.
+ * \returns number of bytes parsed; negative on error */
+int gsm0808_dec_talker_identity(struct gsm0808_talker_identity *ti, const uint8_t *elem, uint8_t len)
+{
+ OSMO_ASSERT(ti);
+ OSMO_ASSERT(elem);
+
+ if (len < 2)
+ return -EINVAL;
+ unsigned int bytes;
+
+ bytes = len - 1;
+ if (bytes > TALKER_IDENTITY_MAXLEN)
+ return -EINVAL;
+ ti->id_bits = (bytes << 3) - (elem[0] & 0x7);
+ memcpy(ti->talker_id, elem + 1, bytes);
+
+ return len;
+}
+
+/*! Encode Assignment Requirements IE (3GPP TS 48.008 3.2.2.52).
+ * \param[out] msg Message Buffer to which IE is to be appended
+ * \param[in] ar Assignment Requirement to be encoded
+ * \returns number of bytes appended to \a msg */
+uint8_t gsm0808_enc_assign_req(struct msgb *msg, const enum gsm0808_assignment_requirement ar)
+{
+ uint8_t *ptr;
+
+ OSMO_ASSERT(msg);
+
+ msgb_put_u8(msg, GSM0808_IE_ASSIGNMENT_REQUIREMENT);
+
+ ptr = msgb_put(msg, 1);
+ ptr[0] = ar;
+
+ return 2;
+}
+
+/*! Decode Assignment Requirements IE (3GPP TS 48.008 3.2.2.52).
+ * \param[out] ar Assignment Requirements enum to store data
+ * \param[in] elem IE value to be decoded.
+ * \param[in] len Length of \a elem in bytes.
+ * \returns number of bytes parsed; negative on error */
+int gsm0808_dec_assign_req(enum gsm0808_assignment_requirement *ar, const uint8_t *elem, uint8_t len)
+{
+ OSMO_ASSERT(ar);
+ OSMO_ASSERT(elem);
+
+ if (len != 1)
+ return -EINVAL;
+
+ *ar = elem[0];
+
+ return 1;
+}
+
+/*! Encode Cell Identifier List Segment IE (3GPP TS 48.008 3.2.2.27a).
+ * \param[out] msg Message Buffer to which IE is to be appended
+ * \param[in] ie_type Type of IE to use (5 different lists are specified.)
+ * \param[in] ci Cell Identifier List Segment to be encoded
+ * \returns number of bytes appended to \a msg */
+uint8_t gsm0808_enc_cell_id_list_segment(struct msgb *msg, uint8_t ie_type,
+ const struct gsm0808_cell_id_list_segment *ci)
+{
+ uint8_t *old_tail;
+ uint8_t *tlv_len;
+ int rc;
+
+ OSMO_ASSERT(msg);
+ OSMO_ASSERT(ci);
+
+ msgb_put_u8(msg, ie_type);
+ tlv_len = msgb_put(msg, 1);
+ old_tail = msg->tail;
+
+ msgb_put_u8(msg, (ci->seq_last << 4) | ci->seq_number);
+
+ rc = gsm0808_enc_cell_id_list2(msg, &ci->cil);
+ if (rc <= 0)
+ return rc;
+
+ *tlv_len = (uint8_t) (msg->tail - old_tail);
+ return *tlv_len + 2;
+}
+
+/*! Decode Cell Identifier List Segment IE (3GPP TS 48.008 3.2.2.27a).
+ * \param[out] ci Cell Identifier List Segment structure to store data
+ * \param[in] elem IE value to be decoded.
+ * \param[in] len Length of \a elem in bytes.
+ * \returns number of bytes parsed; negative on error */
+int gsm0808_dec_cell_id_list_segment(struct gsm0808_cell_id_list_segment *ci, const uint8_t *elem, uint8_t len)
+{
+ int rc;
+
+ OSMO_ASSERT(ci);
+ OSMO_ASSERT(elem);
+
+ if (len < 1)
+ return -EINVAL;
+
+ ci->seq_last = elem[0] >> 4;
+ ci->seq_number = elem[0] & 0x0f;
+
+ rc = gsm0808_dec_cell_id_list2(&ci->cil, elem + 1, len - 1);
+ if (rc < 0)
+ return rc;
+
+ return len;
+}
+
+/*! Decode Call Identifier IE (3GPP TS 48.008 3.2.2.105).
+ * \param[out] ci Call Identifier structure to store data
+ * \param[in] elem IE value to be decoded.
+ * \param[in] len Length of \a elem in bytes.
+ * \returns number of bytes parsed; negative on error */
+int gsm0808_dec_call_id(uint32_t *ci, const uint8_t *elem, uint8_t len)
+{
+ OSMO_ASSERT(ci);
+ OSMO_ASSERT(elem);
+
+ if (len != 4)
+ return -EINVAL;
+
+ /* The Call Identifier is stored as little endian. */
+ *ci = osmo_load32le(elem);
+
+ return 4;
+}
+
/*! @} */
diff --git a/src/gsm/gsm23003.c b/src/gsm/gsm23003.c
index e20afcbc..1eed41fe 100644
--- a/src/gsm/gsm23003.c
+++ b/src/gsm/gsm23003.c
@@ -17,10 +17,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <ctype.h>
@@ -247,6 +243,43 @@ char *osmo_lai_name_c(const void *ctx, const struct osmo_location_area_id *lai)
return osmo_lai_name_buf(buf, 32, lai);
}
+/*! Return MCC-MNC-LAC-RAC as string, in caller-provided output buffer.
+ * \param[out] buf caller-allocated output buffer
+ * \param[in] buf_len size of buf in bytes
+ * \param[in] rai RAI to encode, the rac member is ignored.
+ * \returns buf
+ */
+char *osmo_rai_name2_buf(char *buf, size_t buf_len, const struct osmo_routing_area_id *rai)
+{
+ char plmn[16];
+ snprintf(buf, buf_len, "%s-%u-%u", osmo_plmn_name_buf(plmn, sizeof(plmn),
+ &rai->lac.plmn), rai->lac.lac, rai->rac);
+ return buf;
+}
+
+/*! Return MCC-MNC-LAC-RAC as string, in a static buffer.
+ * \param[in] rai RAI to encode, the rac member is ignored.
+ * \returns Static string buffer.
+ */
+const char *osmo_rai_name2(const struct osmo_routing_area_id *rai)
+{
+ static __thread char buf[32];
+ return osmo_rai_name2_buf(buf, sizeof(buf), rai);
+}
+
+/*! Return MCC-MNC-LAC-RAC as string, in a talloc-allocated output buffer.
+ * \param[in] ctx talloc context from which to allocate output buffer
+ * \param[in] rai RAI to encode, the rac member is ignored.
+ * \returns string representation of lai in dynamically allocated buffer.
+ */
+char *osmo_rai_name2_c(const void *ctx, const struct osmo_routing_area_id *rai)
+{
+ char *buf = talloc_size(ctx, 32);
+ if (!buf)
+ return NULL;
+ return osmo_rai_name2_buf(buf, 32, rai);
+}
+
/*! Return MCC-MNC-LAC-CI as string, in caller-provided output buffer.
* \param[out] buf caller-allocated output buffer
* \param[in] buf_len size of buf in bytes
@@ -291,6 +324,95 @@ char *osmo_cgi_name_c(const void *ctx, const struct osmo_cell_global_id *cgi)
return osmo_cgi_name_buf(buf, 32, cgi);
}
+/*! Return MCC-MNC-LAC-RAC-CI as string, in caller-provided output buffer.
+ * \param[out] buf caller-allocated output buffer
+ * \param[in] buf_len size of buf in bytes
+ * \param[in] cgi_ps CGI-PS to encode.
+ * \returns buf
+ */
+char *osmo_cgi_ps_name_buf(char *buf, size_t buf_len, const struct osmo_cell_global_id_ps *cgi_ps)
+{
+ snprintf(buf, buf_len, "%s-%u", osmo_rai_name2(&cgi_ps->rai), cgi_ps->cell_identity);
+ return buf;
+}
+
+/*! Return MCC-MNC-LAC-RAC-CI as string, in a static buffer.
+ * \param[in] cgi_ps CGI-PS to encode.
+ * \returns Static string buffer.
+ */
+const char *osmo_cgi_ps_name(const struct osmo_cell_global_id_ps *cgi_ps)
+{
+ static __thread char buf[32];
+ return osmo_cgi_ps_name_buf(buf, sizeof(buf), cgi_ps);
+}
+
+/*! Same as osmo_cgi_ps_name(), but uses a different static buffer.
+ * Useful for printing two distinct CGI-PSs in the same printf format.
+ * \param[in] cgi CGI-PS to encode.
+ * \returns Static string buffer.
+ */
+const char *osmo_cgi_ps_name2(const struct osmo_cell_global_id_ps *cgi_ps)
+{
+ static __thread char buf[32];
+ return osmo_cgi_ps_name_buf(buf, sizeof(buf), cgi_ps);
+}
+
+/*! Return MCC-MNC-LAC-RAC-CI as string, in a talloc-allocated output buffer.
+ * \param[in] ctx talloc context from which to allocate output buffer
+ * \param[in] cgi_ps CGI-PS to encode.
+ * \returns string representation of CGI in dynamically-allocated buffer.
+ */
+char *osmo_cgi_ps_name_c(const void *ctx, const struct osmo_cell_global_id_ps *cgi_ps)
+{
+ char *buf = talloc_size(ctx, 32);
+ return osmo_cgi_ps_name_buf(buf, 32, cgi_ps);
+}
+
+/*! Return MCC-MNC-LAC-SAC as string, in caller-provided output buffer.
+ * \param[out] buf caller-allocated output buffer
+ * \param[in] buf_len size of buf in bytes
+ * \param[in] sai SAI to encode.
+ * \returns buf
+ */
+char *osmo_sai_name_buf(char *buf, size_t buf_len, const struct osmo_service_area_id *sai)
+{
+ snprintf(buf, buf_len, "%s-%u", osmo_lai_name(&sai->lai), sai->sac);
+ return buf;
+}
+
+/*! Return MCC-MNC-LAC-SAC as string, in a static buffer.
+ * \param[in] sai SAI to encode.
+ * \returns Static string buffer.
+ */
+const char *osmo_sai_name(const struct osmo_service_area_id *sai)
+{
+ static __thread char buf[32];
+ return osmo_sai_name_buf(buf, sizeof(buf), sai);
+}
+
+/*! Same as osmo_cgi_name(), but uses a different static buffer.
+ * Useful for printing two distinct CGIs in the same printf format.
+ * \param[in] sai SAI to encode.
+ * \returns Static string buffer.
+ */
+const char *osmo_sai_name2(const struct osmo_service_area_id *sai)
+{
+ static __thread char buf[32];
+ return osmo_sai_name_buf(buf, sizeof(buf), sai);
+}
+
+/*! Return MCC-MNC-LAC-SAC as string, in a talloc-allocated output buffer.
+ * \param[in] ctx talloc context from which to allocate output buffer
+ * \param[in] sai SAI to encode.
+ * \returns string representation of CGI in dynamically-allocated buffer.
+ */
+char *osmo_sai_name_c(const void *ctx, const struct osmo_service_area_id *sai)
+{
+ char *buf = talloc_size(ctx, 32);
+ return osmo_sai_name_buf(buf, 32, sai);
+}
+
+
static void to_bcd(uint8_t *bcd, uint16_t val)
{
bcd[2] = val % 10;
@@ -368,14 +490,12 @@ void osmo_plmn_to_bcd(uint8_t *bcd_dst, const struct osmo_plmn_id *plmn)
}
}
-/* Convert given 3-byte BCD buffer to integers and write results to *mcc and
- * *mnc. The first three BCD digits result in the MCC and the remaining ones in
- * the MNC. Return mnc_3_digits as false if the MNC's most significant digit is encoded as 0xF, true
- * otherwise; i.e. true if MNC > 99 or if it is represented with leading zeros instead of 0xF.
+/* Convert given 3-byte BCD buffer to integers and write results to plmn->mcc and plmn->mnc. The first three BCD digits
+ * result in the MCC and the remaining ones in the MNC. Set plmn->mnc_3_digits as false if the MNC's most significant
+ * digit is encoded as 0xF, true otherwise; i.e. true if MNC > 99 or if it is represented with leading zeros instead of
+ * 0xF.
* \param[in] bcd_src 3-byte BCD buffer containing MCC+MNC representations.
- * \param[out] mcc MCC result buffer, or NULL.
- * \param[out] mnc MNC result buffer, or NULL.
- * \param[out] mnc_3_digits Result buffer for 3-digit flag, or NULL.
+ * \param[out] plmn user provided memory to store the result.
*/
void osmo_plmn_from_bcd(const uint8_t *bcd_src, struct osmo_plmn_id *plmn)
{
@@ -406,22 +526,23 @@ void osmo_plmn_from_bcd(const uint8_t *bcd_src, struct osmo_plmn_id *plmn)
*/
int osmo_mnc_from_str(const char *mnc_str, uint16_t *mnc, bool *mnc_3_digits)
{
- long int _mnc = 0;
+ int _mnc = 0;
bool _mnc_3_digits = false;
- char *endptr;
int rc = 0;
if (!mnc_str || !isdigit((unsigned char)mnc_str[0]) || strlen(mnc_str) > 3)
return -EINVAL;
- errno = 0;
- _mnc = strtol(mnc_str, &endptr, 10);
- if (errno)
- rc = -errno;
- else if (*endptr)
+ rc = osmo_str_to_int(&_mnc, mnc_str, 10, 0, 999);
+ /* Heed the API definition to return -EINVAL in case of surplus chars */
+ if (rc == -E2BIG)
return -EINVAL;
- if (_mnc < 0 || _mnc > 999)
- return -ERANGE;
+ /* Heed the API definition to always return negative errno */
+ if (rc > 0)
+ return -rc;
+ if (rc < 0)
+ return rc;
+
_mnc_3_digits = strlen(mnc_str) > 2;
if (mnc)
@@ -484,6 +605,23 @@ int osmo_lai_cmp(const struct osmo_location_area_id *a, const struct osmo_locati
return 0;
}
+/* Compare two RAI.
+ * The order of comparison is MCC, MNC, LAC, RAC. See also osmo_lai_cmp().
+ * \param a[in] "Left" side RAI.
+ * \param b[in] "Right" side RAI.
+ * \returns 0 if the RAI are equal, -1 if a < b, 1 if a > b. */
+int osmo_rai_cmp(const struct osmo_routing_area_id *a, const struct osmo_routing_area_id *b)
+{
+ int rc = osmo_lai_cmp(&a->lac, &b->lac);
+ if (rc)
+ return rc;
+ if (a->rac < b->rac)
+ return -1;
+ if (a->rac > b->rac)
+ return 1;
+ return 0;
+}
+
/* Compare two CGI.
* The order of comparison is MCC, MNC, LAC, CI. See also osmo_lai_cmp().
* \param a[in] "Left" side CGI.
@@ -501,6 +639,23 @@ int osmo_cgi_cmp(const struct osmo_cell_global_id *a, const struct osmo_cell_glo
return 0;
}
+/* Compare two CGI-PS.
+ * The order of comparison is MCC, MNC, LAC, RAC, CI. See also osmo_rai_cmp().
+ * \param a[in] "Left" side CGI-PS.
+ * \param b[in] "Right" side CGI-PS.
+ * \returns 0 if the CGI are equal, -1 if a < b, 1 if a > b. */
+int osmo_cgi_ps_cmp(const struct osmo_cell_global_id_ps *a, const struct osmo_cell_global_id_ps *b)
+{
+ int rc = osmo_rai_cmp(&a->rai, &b->rai);
+ if (rc)
+ return rc;
+ if (a->cell_identity < b->cell_identity)
+ return -1;
+ if (a->cell_identity > b->cell_identity)
+ return 1;
+ return 0;
+}
+
/*! Generate TS 23.003 Section 19.2 Home Network Realm/Domain (text form)
* \param out[out] caller-provided output buffer, at least 33 bytes long
* \param plmn[in] Osmocom representation of PLMN ID (MCC + MNC)
diff --git a/src/gsm/gsm23236.c b/src/gsm/gsm23236.c
index 01d0eb36..4a83ec82 100644
--- a/src/gsm/gsm23236.c
+++ b/src/gsm/gsm23236.c
@@ -17,10 +17,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <errno.h>
@@ -436,27 +432,13 @@ char *osmo_nri_ranges_to_str_c(void *ctx, const struct osmo_nri_ranges *nri_rang
*/
static int osmo_nri_parse(int16_t *dst, const char *str)
{
- char *endp;
- int64_t val;
+ int val;
int base = 10;
-
- if (osmo_str_startswith(str, "0x")) {
- str += 2;
+ if (osmo_str_startswith(str, "0x"))
base = 16;
- }
-
- if (!str || !str[0])
+ if (osmo_str_to_int(&val, str, base, 0, INT16_MAX))
return -1;
-
- errno = 0;
- val = strtoull(str, &endp, base);
- if (errno || *endp != '\0')
- return -1;
-
- if (val < 0 || val > INT16_MAX)
- return -1;
-
- *dst = val;
+ *dst = (int16_t)val;
return 0;
}
diff --git a/src/gsm/gsm29118.c b/src/gsm/gsm29118.c
index 2c02b2fd..0ca5b52e 100644
--- a/src/gsm/gsm29118.c
+++ b/src/gsm/gsm29118.c
@@ -14,10 +14,6 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <osmocom/core/utils.h>
diff --git a/src/gsm/gsm29205.c b/src/gsm/gsm29205.c
index 6ceb8ee4..8fed0206 100644
--- a/src/gsm/gsm29205.c
+++ b/src/gsm/gsm29205.c
@@ -14,10 +14,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include "config.h"
diff --git a/src/gsm/gsm44021.c b/src/gsm/gsm44021.c
new file mode 100644
index 00000000..5e9d5ab8
--- /dev/null
+++ b/src/gsm/gsm44021.c
@@ -0,0 +1,303 @@
+/*************************************************************************
+ * GSM CSD modified V.110 frame decoding/encoding (ubits <-> struct with D/S/X/E bits)
+ *************************************************************************/
+
+/* (C) 2022 by Harald Welte <laforge@osmocom.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 <osmocom/core/bits.h>
+#include <osmocom/isdn/v110.h>
+
+/*! Decode a 60-bit GSM 12kbit/s CSD frame present as 60 ubits into a struct osmo_v110_decoded_frame.
+ * \param[out] caller-allocated output data structure, filled by this function
+ * \param[in] ra_bits One V.110 frame as 60 unpacked bits. */
+int osmo_csd_12k_6k_decode_frame(struct osmo_v110_decoded_frame *fr, const ubit_t *ra_bits, size_t n_bits)
+{
+ /* 3GPP TS 44.021 Section 8.1.2 / 8.1.3
+ D1 D2 D3 D4 D5 D6 S1
+ D7 D8 D9 D10 D11 D12 X
+ D13 D14 D15 D16 D17 D18 S3
+ D19 D20 D21 D22 D23 D24 S4
+ E4 E5 E6 E7 D25 D26 D27
+ D28 D29 D30 S6 D31 D32 D33
+ D34 D35 D36 X D37 D38 D39
+ D40 D41 D42 S8 D43 D44 D45
+ D46 D47 D48 S9 */
+
+ if (n_bits < 60)
+ return -EINVAL;
+
+ /* X1 .. X2 */
+ fr->x_bits[0] = ra_bits[1 * 7 + 6];
+ fr->x_bits[1] = ra_bits[6 * 7 + 3];
+
+ /* S1, S3, S4, S6, S8, S9 */
+ fr->s_bits[0] = ra_bits[0 * 7 + 6];
+ fr->s_bits[2] = ra_bits[2 * 7 + 6];
+ fr->s_bits[3] = ra_bits[3 * 7 + 6];
+ fr->s_bits[5] = ra_bits[5 * 7 + 3];
+ fr->s_bits[7] = ra_bits[7 * 7 + 3];
+ fr->s_bits[8] = ra_bits[8 * 7 + 3];
+
+ /* E1 .. E3 must be set by out-of-band knowledge! */
+
+ /* E4 .. E7 */
+ memcpy(fr->e_bits+3, ra_bits + 4 * 7 + 0, 4);
+
+ /* D-bits */
+ memcpy(fr->d_bits + 0 * 6 + 0, ra_bits + 0 * 7 + 0, 6);
+ memcpy(fr->d_bits + 1 * 6 + 0, ra_bits + 1 * 7 + 0, 6);
+ memcpy(fr->d_bits + 2 * 6 + 0, ra_bits + 2 * 7 + 0, 6);
+ memcpy(fr->d_bits + 3 * 6 + 0, ra_bits + 3 * 7 + 0, 6);
+ memcpy(fr->d_bits + 4 * 6 + 0, ra_bits + 4 * 7 + 4, 3);
+ memcpy(fr->d_bits + 4 * 6 + 3, ra_bits + 5 * 7 + 0, 3);
+ memcpy(fr->d_bits + 5 * 6 + 0, ra_bits + 5 * 7 + 4, 3);
+ memcpy(fr->d_bits + 5 * 6 + 3, ra_bits + 6 * 7 + 0, 3);
+ memcpy(fr->d_bits + 6 * 6 + 0, ra_bits + 6 * 7 + 4, 3);
+ memcpy(fr->d_bits + 6 * 6 + 3, ra_bits + 7 * 7 + 0, 3);
+ memcpy(fr->d_bits + 7 * 6 + 0, ra_bits + 7 * 7 + 4, 3);
+ memcpy(fr->d_bits + 7 * 6 + 3, ra_bits + 8 * 7 + 0, 3);
+
+ return 0;
+}
+
+int osmo_csd_12k_6k_encode_frame(ubit_t *ra_bits, size_t ra_bits_size, const struct osmo_v110_decoded_frame *fr)
+{
+ if (ra_bits_size < 60)
+ return -EINVAL;
+
+ /* X1 .. X2 */
+ ra_bits[1 * 7 + 6] = fr->x_bits[0];
+ ra_bits[6 * 7 + 3] = fr->x_bits[1];
+
+ /* S1, S3, S4, S6, S8, S9 */
+ ra_bits[0 * 7 + 6] = fr->s_bits[0];
+ ra_bits[2 * 7 + 6] = fr->s_bits[2];
+ ra_bits[3 * 7 + 6] = fr->s_bits[3];
+ ra_bits[5 * 7 + 3] = fr->s_bits[5];
+ ra_bits[7 * 7 + 3] = fr->s_bits[7];
+ ra_bits[8 * 7 + 3] = fr->s_bits[8];
+
+ /* E1 .. E3 are dropped */
+
+ /* E4 .. E7 */
+ memcpy(ra_bits + 4 * 7 + 0, fr->e_bits+3, 4);
+
+ /* D-bits */
+ memcpy(ra_bits + 0 * 7 + 0, fr->d_bits + 0 * 6 + 0, 6);
+ memcpy(ra_bits + 1 * 7 + 0, fr->d_bits + 1 * 6 + 0, 6);
+ memcpy(ra_bits + 2 * 7 + 0, fr->d_bits + 2 * 6 + 0, 6);
+ memcpy(ra_bits + 3 * 7 + 0, fr->d_bits + 3 * 6 + 0, 6);
+ memcpy(ra_bits + 4 * 7 + 4, fr->d_bits + 4 * 6 + 0, 3);
+ memcpy(ra_bits + 5 * 7 + 0, fr->d_bits + 4 * 6 + 3, 3);
+ memcpy(ra_bits + 5 * 7 + 4, fr->d_bits + 5 * 6 + 0, 3);
+ memcpy(ra_bits + 6 * 7 + 0, fr->d_bits + 5 * 6 + 3, 3);
+ memcpy(ra_bits + 6 * 7 + 4, fr->d_bits + 6 * 6 + 0, 3);
+ memcpy(ra_bits + 7 * 7 + 0, fr->d_bits + 6 * 6 + 3, 3);
+ memcpy(ra_bits + 7 * 7 + 4, fr->d_bits + 7 * 6 + 0, 3);
+ memcpy(ra_bits + 8 * 7 + 0, fr->d_bits + 7 * 6 + 3, 3);
+
+ return 60;
+}
+
+/*! Decode a 36-bit GSM 3k6kbit/s CSD frame present as 36 ubits into a struct osmo_v110_decoded_frame.
+ * \param[out] caller-allocated output data structure, filled by this function
+ * \param[in] ra_bits One V.110 frame as 36 unpacked bits. */
+int osmo_csd_3k6_decode_frame(struct osmo_v110_decoded_frame *fr, const ubit_t *ra_bits, size_t n_bits)
+{
+
+ /* 3GPP TS 44.021 Section 8.1.4
+ D1 D2 D3 S1 D4 D5 D6 X
+ D7 D8 D9 S3 D10 D11 D12 S4
+ E4 E5 E6 E7 D13 D14 D15 S6
+ D16 D17 D18 X D19 D20 D21 S8
+ D22 D23 D24 S9
+ */
+
+ if (n_bits < 36)
+ return -EINVAL;
+
+ /* X1 .. X2 */
+ fr->x_bits[0] = ra_bits[0 * 8 + 7];
+ fr->x_bits[1] = ra_bits[3 * 8 + 3];
+
+ /* S1, S3, S4, S6, S8, S9 */
+ fr->s_bits[0] = ra_bits[0 * 8 + 3];
+ fr->s_bits[2] = ra_bits[1 * 8 + 3];
+ fr->s_bits[3] = ra_bits[1 * 8 + 7];
+ fr->s_bits[5] = ra_bits[2 * 8 + 7];
+ fr->s_bits[7] = ra_bits[3 * 8 + 7];
+ fr->s_bits[8] = ra_bits[4 * 8 + 3];
+
+ /* E1 .. E3 must be set by out-of-band knowledge! */
+
+ /* E4 .. E7 */
+ memcpy(fr->e_bits+3, ra_bits + 2 * 8 + 0, 4);
+
+ /* D-bits */
+ unsigned int d_idx = 0;
+ fr->d_bits[d_idx++] = ra_bits[0 * 8 + 0]; /* D1 */
+ fr->d_bits[d_idx++] = ra_bits[0 * 8 + 0]; /* D1 */
+ fr->d_bits[d_idx++] = ra_bits[0 * 8 + 1]; /* D2 */
+ fr->d_bits[d_idx++] = ra_bits[0 * 8 + 1]; /* D2 */
+ fr->d_bits[d_idx++] = ra_bits[0 * 8 + 2]; /* D3 */
+ fr->d_bits[d_idx++] = ra_bits[0 * 8 + 2]; /* D3 */
+ fr->d_bits[d_idx++] = ra_bits[0 * 8 + 4]; /* D4 */
+ fr->d_bits[d_idx++] = ra_bits[0 * 8 + 4]; /* D4 */
+ fr->d_bits[d_idx++] = ra_bits[0 * 8 + 5]; /* D5 */
+ fr->d_bits[d_idx++] = ra_bits[0 * 8 + 5]; /* D5 */
+ fr->d_bits[d_idx++] = ra_bits[0 * 8 + 6]; /* D6 */
+ fr->d_bits[d_idx++] = ra_bits[0 * 8 + 6]; /* D6 */
+
+ fr->d_bits[d_idx++] = ra_bits[1 * 8 + 0]; /* D7 */
+ fr->d_bits[d_idx++] = ra_bits[1 * 8 + 0]; /* D7 */
+ fr->d_bits[d_idx++] = ra_bits[1 * 8 + 1]; /* D8 */
+ fr->d_bits[d_idx++] = ra_bits[1 * 8 + 1]; /* D8 */
+ fr->d_bits[d_idx++] = ra_bits[1 * 8 + 2]; /* D9 */
+ fr->d_bits[d_idx++] = ra_bits[1 * 8 + 2]; /* D9 */
+ fr->d_bits[d_idx++] = ra_bits[1 * 8 + 4]; /* D10 */
+ fr->d_bits[d_idx++] = ra_bits[1 * 8 + 4]; /* D10 */
+ fr->d_bits[d_idx++] = ra_bits[1 * 8 + 5]; /* D11 */
+ fr->d_bits[d_idx++] = ra_bits[1 * 8 + 5]; /* D11 */
+ fr->d_bits[d_idx++] = ra_bits[1 * 8 + 6]; /* D12 */
+ fr->d_bits[d_idx++] = ra_bits[1 * 8 + 6]; /* D12 */
+
+ fr->d_bits[d_idx++] = ra_bits[2 * 8 + 4]; /* D13 */
+ fr->d_bits[d_idx++] = ra_bits[2 * 8 + 4]; /* D13 */
+ fr->d_bits[d_idx++] = ra_bits[2 * 8 + 5]; /* D14 */
+ fr->d_bits[d_idx++] = ra_bits[2 * 8 + 5]; /* D14 */
+ fr->d_bits[d_idx++] = ra_bits[2 * 8 + 6]; /* D15 */
+ fr->d_bits[d_idx++] = ra_bits[2 * 8 + 6]; /* D15 */
+
+ fr->d_bits[d_idx++] = ra_bits[3 * 8 + 0]; /* D16 */
+ fr->d_bits[d_idx++] = ra_bits[3 * 8 + 0]; /* D16 */
+ fr->d_bits[d_idx++] = ra_bits[3 * 8 + 1]; /* D17 */
+ fr->d_bits[d_idx++] = ra_bits[3 * 8 + 1]; /* D17 */
+ fr->d_bits[d_idx++] = ra_bits[3 * 8 + 2]; /* D18 */
+ fr->d_bits[d_idx++] = ra_bits[3 * 8 + 2]; /* D18 */
+ fr->d_bits[d_idx++] = ra_bits[3 * 8 + 4]; /* D19 */
+ fr->d_bits[d_idx++] = ra_bits[3 * 8 + 4]; /* D19 */
+ fr->d_bits[d_idx++] = ra_bits[3 * 8 + 5]; /* D20 */
+ fr->d_bits[d_idx++] = ra_bits[3 * 8 + 5]; /* D20 */
+ fr->d_bits[d_idx++] = ra_bits[3 * 8 + 6]; /* D21 */
+ fr->d_bits[d_idx++] = ra_bits[3 * 8 + 6]; /* D21 */
+
+ fr->d_bits[d_idx++] = ra_bits[4 * 8 + 0]; /* D22 */
+ fr->d_bits[d_idx++] = ra_bits[4 * 8 + 0]; /* D22 */
+ fr->d_bits[d_idx++] = ra_bits[4 * 8 + 1]; /* D23 */
+ fr->d_bits[d_idx++] = ra_bits[4 * 8 + 1]; /* D23 */
+ fr->d_bits[d_idx++] = ra_bits[4 * 8 + 2]; /* D24 */
+ fr->d_bits[d_idx++] = ra_bits[4 * 8 + 2]; /* D24 */
+
+ OSMO_ASSERT(d_idx == 48);
+
+ return 0;
+}
+
+int osmo_csd_3k6_encode_frame(ubit_t *ra_bits, size_t ra_bits_size, const struct osmo_v110_decoded_frame *fr)
+{
+ if (ra_bits_size < 36)
+ return -EINVAL;
+
+ /* X1 .. X2 */
+ ra_bits[0 * 8 + 7] = fr->x_bits[0];
+ ra_bits[3 * 8 + 3] = fr->x_bits[1];
+
+ /* S1, S3, S4, S6, S8, S9 */
+ ra_bits[0 * 8 + 3] = fr->s_bits[0];
+ ra_bits[1 * 8 + 3] = fr->s_bits[2];
+ ra_bits[1 * 8 + 7] = fr->s_bits[3];
+ ra_bits[2 * 8 + 7] = fr->s_bits[5];
+ ra_bits[3 * 8 + 7] = fr->s_bits[7];
+ ra_bits[4 * 8 + 3] = fr->s_bits[8];
+
+ /* E1 .. E3 are ignored */
+
+ /* E4 .. E7 */
+ memcpy(ra_bits + 2 * 8 + 0, fr->e_bits+3, 4);
+
+ /* D-bits */
+ unsigned int d_idx = 0;
+ ra_bits[0 * 8 + 0] = fr->d_bits[d_idx]; d_idx += 2;
+ ra_bits[0 * 8 + 1] = fr->d_bits[d_idx]; d_idx += 2;
+ ra_bits[0 * 8 + 2] = fr->d_bits[d_idx]; d_idx += 2;
+ ra_bits[0 * 8 + 4] = fr->d_bits[d_idx]; d_idx += 2;
+ ra_bits[0 * 8 + 5] = fr->d_bits[d_idx]; d_idx += 2;
+ ra_bits[0 * 8 + 6] = fr->d_bits[d_idx]; d_idx += 2;
+
+ ra_bits[1 * 8 + 0] = fr->d_bits[d_idx]; d_idx += 2;
+ ra_bits[1 * 8 + 1] = fr->d_bits[d_idx]; d_idx += 2;
+ ra_bits[1 * 8 + 2] = fr->d_bits[d_idx]; d_idx += 2;
+ ra_bits[1 * 8 + 4] = fr->d_bits[d_idx]; d_idx += 2;
+ ra_bits[1 * 8 + 5] = fr->d_bits[d_idx]; d_idx += 2;
+ ra_bits[1 * 8 + 6] = fr->d_bits[d_idx]; d_idx += 2;
+
+ ra_bits[2 * 8 + 4] = fr->d_bits[d_idx]; d_idx += 2;
+ ra_bits[2 * 8 + 5] = fr->d_bits[d_idx]; d_idx += 2;
+ ra_bits[2 * 8 + 6] = fr->d_bits[d_idx]; d_idx += 2;
+
+ ra_bits[3 * 8 + 0] = fr->d_bits[d_idx]; d_idx += 2;
+ ra_bits[3 * 8 + 1] = fr->d_bits[d_idx]; d_idx += 2;
+ ra_bits[3 * 8 + 2] = fr->d_bits[d_idx]; d_idx += 2;
+ ra_bits[3 * 8 + 4] = fr->d_bits[d_idx]; d_idx += 2;
+ ra_bits[3 * 8 + 5] = fr->d_bits[d_idx]; d_idx += 2;
+ ra_bits[3 * 8 + 6] = fr->d_bits[d_idx]; d_idx += 2;
+
+ ra_bits[4 * 8 + 0] = fr->d_bits[d_idx]; d_idx += 2;
+ ra_bits[4 * 8 + 1] = fr->d_bits[d_idx]; d_idx += 2;
+ ra_bits[4 * 8 + 2] = fr->d_bits[d_idx]; d_idx += 2;
+
+ OSMO_ASSERT(d_idx == 48);
+
+ return 36;
+}
+
+/*! Print a encoded "CSD modififed V.110" frame in the same table-like structure as the spec.
+ * \param outf output FILE stream to which to dump
+ * \param[in] fr unpacked bits to dump
+ * \param[in] in_len length of unpacked bits available at fr. */
+void osmo_csd_ubit_dump(FILE *outf, const ubit_t *fr, size_t in_len)
+{
+ switch (in_len) {
+ case 60:
+ for (unsigned int septet = 0; septet < 9; septet++) {
+ if (septet < 8) {
+ fprintf(outf, "%d\t%d\t%d\t%d\t%d\t%d\t%d\n", fr[septet * 7 + 0],
+ fr[septet * 7 + 1], fr[septet * 7 + 2], fr[septet * 7 + 3],
+ fr[septet * 7 + 4], fr[septet * 7 + 5], fr[septet*7 + 6]);
+ } else {
+ fprintf(outf, "%d\t%d\t%d\t%d\n", fr[septet * 7 + 0],
+ fr[septet * 7 + 1], fr[septet * 7 + 2], fr[septet * 7 + 3]);
+ }
+ }
+ break;
+ case 36:
+ for (unsigned int octet = 0; octet < 5; octet++) {
+ if (octet < 4) {
+ fprintf(outf, "%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\n",
+ fr[octet * 8 + 0], fr[octet * 8 + 1], fr[octet * 8 + 2],
+ fr[octet * 8 + 3], fr[octet * 8 + 4], fr[octet * 8 + 5],
+ fr[octet * 8 + 6], fr[octet * 8 + 7]);
+ } else {
+ fprintf(outf, "%d\t%d\t%d\t%d\n", fr[octet * 8 + 0],
+ fr[octet * 8 + 1], fr[octet * 8 + 2], fr[octet * 8 + 3]);
+ }
+ }
+ break;
+ default:
+ fprintf(outf, "invalid input data length: %zu\n", in_len);
+ }
+}
diff --git a/src/gsm/gsm44068.c b/src/gsm/gsm44068.c
new file mode 100644
index 00000000..2556b128
--- /dev/null
+++ b/src/gsm/gsm44068.c
@@ -0,0 +1,121 @@
+/* Group Call Control (GCC) is an ETSI/3GPP standard protocol used between
+ * MS (Mobile Station) and MSC (Mobile Switchting Center) in 2G/GSM-R network.
+ * It is specified in 3GPP TS 44.068.
+ *
+ * (C) 2023 by Sysmocom s.f.m.c. GmbH
+ * All Rights Reserved
+ *
+ * Author: Andreas Eversberg
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+#include <stddef.h>
+#include <osmocom/gsm/tlv.h>
+#include <osmocom/gsm/protocol/gsm_44_068.h>
+
+/***********************************************************************
+ * Protocol Definitions
+ ***********************************************************************/
+
+const struct value_string osmo_gsm44068_msg_type_names[] = {
+ { OSMO_GSM44068_MSGT_IMMEDIATE_SETUP, "IMMEDIATE SETUP" },
+ { OSMO_GSM44068_MSGT_SETUP, "SETUP" },
+ { OSMO_GSM44068_MSGT_CONNECT, "CONNECT" },
+ { OSMO_GSM44068_MSGT_TERMINATION, "TERMINATION" },
+ { OSMO_GSM44068_MSGT_TERMINATION_REQUEST, "TERMINATION REQUEST" },
+ { OSMO_GSM44068_MSGT_TERMINATION_REJECT, "TERMINATION REJECT" },
+ { OSMO_GSM44068_MSGT_STATUS, "STATUS" },
+ { OSMO_GSM44068_MSGT_GET_STATUS, "GET STATUS" },
+ { OSMO_GSM44068_MSGT_SET_PARAMETER, "SET PARAMETER" },
+ { OSMO_GSM44068_MSGT_IMMEDIATE_SETUP_2, "IMMEDIATE SETUP 2" },
+ { 0, NULL }
+};
+
+const struct value_string osmo_gsm44068_priority_level_names[] = {
+ { OSMO_GSM44068_PRIO_LEVEL_4, "priority level 4" },
+ { OSMO_GSM44068_PRIO_LEVEL_3, "priority level 3" },
+ { OSMO_GSM44068_PRIO_LEVEL_2, "priority level 2" },
+ { OSMO_GSM44068_PRIO_LEVEL_1, "priority level 1" },
+ { OSMO_GSM44068_PRIO_LEVEL_0, "priority level 0" },
+ { OSMO_GSM44068_PRIO_LEVEL_B, "priority level B" },
+ { OSMO_GSM44068_PRIO_LEVEL_A, "priority level A" },
+ { 0, NULL }
+};
+
+const struct value_string osmo_gsm44068_cause_names[] = {
+ { OSMO_GSM44068_CAUSE_ILLEGAL_MS, "Illegal MS" },
+ { OSMO_GSM44068_CAUSE_IMEI_NOT_ACCEPTED, "IMEI not accepted" },
+ { OSMO_GSM44068_CAUSE_ILLEGAL_ME, "Illegal ME" },
+ { OSMO_GSM44068_CAUSE_SERVICE_NOT_AUTHORIZED, "Service not authorized" },
+ { OSMO_GSM44068_CAUSE_APP_NOT_SUPPORTED_ON_PROTO, "Application not supported on the protocol" },
+ { OSMO_GSM44068_CAUSE_RR_CONNECTION_ABORTED, "RR connection aborted" },
+ { OSMO_GSM44068_CAUSE_NORMAL_CALL_CLEARING, "Normal call clearing" },
+ { OSMO_GSM44068_CAUSE_NETWORK_FAILURE, "Network failure" },
+ { OSMO_GSM44068_CAUSE_BUSY, "Busy" },
+ { OSMO_GSM44068_CAUSE_CONGESTION, "Congestion" },
+ { OSMO_GSM44068_CAUSE_USER_NOT_ORIGINATOR, "User not originator of call" },
+ { OSMO_GSM44068_CAUSE_NET_WANTS_TO_MAINTAIN_CALL, "Network wants to maintain call" },
+ { OSMO_GSM44068_CAUSE_RESPONSE_TO_GET_STATUS, "Response to GET STATUS" },
+ { OSMO_GSM44068_CAUSE_SERVICE_OPTION_NOT_SUBSCR, "Service option not supported" },
+ { OSMO_GSM44068_CAUSE_REQUESTED_SERVICE_NOT_SUB, "Requested service option not subscribed" },
+ { OSMO_GSM44068_CAUSE_SERVICE_OPTION_OOO, "Service option temporarily out of order" },
+ { OSMO_GSM44068_CAUSE_CALL_CANNOT_BE_IDENTIFIED, "Call cannot be identified" },
+ { OSMO_GSM44068_CAUSE_RETRY_UPON_ENTRY_NEW_CALL, "retry upon entry into a new cell" },
+ { OSMO_GSM44068_CAUSE_INVALID_TRANSACTION_ID, "Invalid transaction identifier value" },
+ { OSMO_GSM44068_CAUSE_SEMANTICALLY_INCORRECT_MSG, "Semantically incorrect message" },
+ { OSMO_GSM44068_CAUSE_INVALID_MANDATORY_INFO, "Invalid mandatory information" },
+ { OSMO_GSM44068_CAUSE_MESSAGE_TYPE_NON_EXISTENT, "Message type non-existent or not implemented" },
+ { OSMO_GSM44068_CAUSE_MESSAGE_TYPE_NOT_COMPAT, "Message type not compatible with the protocol state" },
+ { OSMO_GSM44068_CAUSE_IE_NON_EXISTENT, "Information element non-existent or not implemented" },
+ { OSMO_GSM44068_CAUSE_IE_NOT_COMPAT, "Message type not compatible with the protocol state" },
+ { OSMO_GSM44068_CAUSE_PROTOCOL_ERROR, "Protocol error, unspecified" },
+ { 0, NULL }
+};
+
+const struct value_string osmo_gsm44068_call_state_names[] = {
+ { OSMO_GSM44068_CSTATE_U0, "U0" },
+ { OSMO_GSM44068_CSTATE_U1, "U1" },
+ { OSMO_GSM44068_CSTATE_U2sl_U2, "U2sl/U2" },
+ { OSMO_GSM44068_CSTATE_U3, "U3" },
+ { OSMO_GSM44068_CSTATE_U4, "U4" },
+ { OSMO_GSM44068_CSTATE_U5, "U5" },
+ { OSMO_GSM44068_CSTATE_U0p, "U0.p" },
+ { OSMO_GSM44068_CSTATE_U2wr_U6, "U2wr/U6" },
+ { OSMO_GSM44068_CSTATE_U2r, "U2r" },
+ { OSMO_GSM44068_CSTATE_U2ws, "U2ws" },
+ { OSMO_GSM44068_CSTATE_U2sr, "U2sr" },
+ { OSMO_GSM44068_CSTATE_U2nc, "U2nc" },
+ { 0, NULL }
+};
+
+const struct value_string osmo_gsm44068_talker_priority_names[] = {
+ { OSMO_GSM44068_PRIO_NORMAL, "Normal" },
+ { OSMO_GSM44068_PRIO_PRIVILEGED, "Privileged" },
+ { OSMO_GSM44068_PRIO_EMERGENCY, "Emergency" },
+ { 0, NULL }
+};
+
+const struct tlv_definition osmo_gsm44068_att_tlvdef = {
+ .def = {
+ [OSMO_GSM44068_IEI_MOBILE_IDENTITY] = { TLV_TYPE_TLV },
+ [OSMO_GSM44068_IEI_USER_USER] = { TLV_TYPE_TLV },
+ [OSMO_GSM44068_IEI_CALL_STATE] = { TLV_TYPE_SINGLE_TV },
+ [OSMO_GSM44068_IEI_STATE_ATTRIBUTES] = { TLV_TYPE_SINGLE_TV },
+ [OSMO_GSM44068_IEI_TALKER_PRIORITY] = { TLV_TYPE_SINGLE_TV },
+ [OSMO_GSM44068_IEI_SMS_INDICATIONS] = { TLV_TYPE_SINGLE_TV },
+ },
+};
diff --git a/src/gsm/gsm48.c b/src/gsm/gsm48.c
index c497c745..64b17658 100644
--- a/src/gsm/gsm48.c
+++ b/src/gsm/gsm48.c
@@ -19,10 +19,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <stdint.h>
@@ -46,6 +42,7 @@
#include <osmocom/gsm/protocol/gsm_08_58.h>
#include <osmocom/gsm/protocol/gsm_04_08_gprs.h>
#include <osmocom/gsm/protocol/gsm_23_003.h>
+#include <osmocom/gsm/protocol/gsm_44_068.h>
/*! \addtogroup gsm0408
* @{
@@ -124,6 +121,7 @@ const struct tlv_definition gsm48_rr_att_tlvdef = {
[GSM48_IE_CHDES_2_AFTER] = { TLV_TYPE_FIXED, 3 },
[GSM48_IE_MODE_SEC_CH] = { TLV_TYPE_TV },
[GSM48_IE_F_CH_SEQ_AFTER] = { TLV_TYPE_FIXED, 9 },
+ [GSM48_IE_EXTENDED_TSC_SET] = { TLV_TYPE_TV },
[GSM48_IE_MA_AFTER] = { TLV_TYPE_TLV },
[GSM48_IE_BA_RANGE] = { TLV_TYPE_TLV },
[GSM48_IE_GROUP_CHDES] = { TLV_TYPE_TLV },
@@ -132,10 +130,10 @@ const struct tlv_definition gsm48_rr_att_tlvdef = {
[GSM48_IE_REALTIME_DIFF] = { TLV_TYPE_TLV },
[GSM48_IE_START_TIME] = { TLV_TYPE_FIXED, 2 },
[GSM48_IE_TIMING_ADVANCE] = { TLV_TYPE_TV },
- [GSM48_IE_GROUP_CIP_SEQ] = { TLV_TYPE_SINGLE_TV },
- [GSM48_IE_CIP_MODE_SET] = { TLV_TYPE_SINGLE_TV },
- [GSM48_IE_GPRS_RESUMPT] = { TLV_TYPE_SINGLE_TV },
- [GSM48_IE_SYNC_IND] = { TLV_TYPE_SINGLE_TV },
+ [GSM48_IE_GROUP_CIP_SEQ_HO] = { TLV_TYPE_SINGLE_TV },
+ [GSM48_IE_CIP_MODE_SET_HO] = { TLV_TYPE_SINGLE_TV },
+ [GSM48_IE_GPRS_RESUMPT_HO] = { TLV_TYPE_SINGLE_TV },
+ [GSM48_IE_SYNC_IND_HO] = { TLV_TYPE_SINGLE_TV },
},
};
@@ -151,7 +149,7 @@ const struct tlv_definition gsm48_mm_att_tlvdef = {
[GSM48_IE_NET_DST] = { TLV_TYPE_TLV },
[GSM48_IE_LOCATION_AREA] = { TLV_TYPE_FIXED, 5 },
- [GSM48_IE_PRIORITY_LEV] = { TLV_TYPE_SINGLE_TV },
+ [GSM48_IE_PRIORITY_LEV_HO] = { TLV_TYPE_SINGLE_TV },
[GSM48_IE_FOLLOW_ON_PROC] = { TLV_TYPE_T },
[GSM48_IE_CTS_PERMISSION] = { TLV_TYPE_T },
},
@@ -422,19 +420,102 @@ const char *gsm48_rr_msg_name(uint8_t msgtype)
return get_value_string(rr_msg_names, msgtype);
}
+/* 3GPP TS 44.018 Table 10.4.2 */
+static const struct value_string rr_msg_type_short_names[] = {
+ { GSM48_MT_RR_SH_SI10, "System Information Type 10" },
+ { GSM48_MT_RR_SH_FACCH, "Notification/FACCH" },
+ { GSM48_MT_RR_SH_UL_FREE, "Uplink Free" },
+ { GSM48_MT_RR_SH_MEAS_REP, "Enhanced Measurement Report (uplink)" },
+ { GSM48_MT_RR_SH_MEAS_INFO, "Measurement Information (downlink)" },
+ { GSM48_MT_RR_SH_VGCS_RECON, "VBS/VGCS Reconfigure" },
+ { GSM48_MT_RR_SH_VGCS_RECON2, "VBS/VGCS Reconfigure2" },
+ { GSM48_MT_RR_SH_VGCS_INFO, "VGCS Additional Information" },
+ { GSM48_MT_RR_SH_VGCS_SMS, "VGCS SMS Information" },
+ { GSM48_MT_RR_SH_SI10bis, "System Information Type 10bis" },
+ { GSM48_MT_RR_SH_SI10ter, "System Information Type 10ter" },
+ { GSM48_MT_RR_SH_VGCS_NEIGH, "VGCS Neighbour Cell Information" },
+ { GSM48_MT_RR_SH_APP_DATA, "Notify Application Data" },
+ { 0, NULL }
+};
+
+/*! return string representation of RR Message Type using the RR short protocol discriminator */
+const char *gsm48_rr_short_pd_msg_name(uint8_t msgtype)
+{
+ return get_value_string(rr_msg_type_short_names, msgtype);
+}
const struct value_string gsm48_chan_mode_names[] = {
{ GSM48_CMODE_SIGN, "SIGNALLING" },
{ GSM48_CMODE_SPEECH_V1, "SPEECH_V1" },
{ GSM48_CMODE_SPEECH_EFR, "SPEECH_EFR" },
{ GSM48_CMODE_SPEECH_AMR, "SPEECH_AMR" },
+ { GSM48_CMODE_SPEECH_V4, "SPEECH_V4" },
+ { GSM48_CMODE_SPEECH_V5, "SPEECH_V5" },
+ { GSM48_CMODE_SPEECH_V6, "SPEECH_V6" },
+
+ { GSM48_CMODE_DATA_43k5_14k5, "DATA_43k5_14k5" },
+ { GSM48_CMODE_DATA_29k0_14k5, "DATA_29k0_14k5" },
+ { GSM48_CMODE_DATA_43k5_29k0, "DATA_43k5_29k0" },
+ { GSM48_CMODE_DATA_14k5_43k5, "DATA_14k5_43k5" },
+ { GSM48_CMODE_DATA_14k5_29k0, "DATA_14k5_29k0" },
+ { GSM48_CMODE_DATA_29k0_43k5, "DATA_29k0_43k5" },
+
+ { GSM48_CMODE_DATA_43k5, "DATA_43k5" },
+ { GSM48_CMODE_DATA_32k0, "DATA_32k0" },
+ { GSM48_CMODE_DATA_29k0, "DATA_29k0" },
{ GSM48_CMODE_DATA_14k5, "DATA_14k5" },
{ GSM48_CMODE_DATA_12k0, "DATA_12k0" },
{ GSM48_CMODE_DATA_6k0, "DATA_6k0" },
{ GSM48_CMODE_DATA_3k6, "DATA_3k6" },
+
+ { GSM48_CMODE_SPEECH_V1_VAMOS, "SPEECH_V1_VAMOS" },
+ { GSM48_CMODE_SPEECH_V2_VAMOS, "SPEECH_V2_VAMOS" },
+ { GSM48_CMODE_SPEECH_V3_VAMOS, "SPEECH_V3_VAMOS" },
+ { GSM48_CMODE_SPEECH_V5_VAMOS, "SPEECH_V5_VAMOS" },
{ 0, NULL },
};
+/*! Translate GSM48_CMODE_SPEECH_* to its corresponding GSM48_CMODE_SPEECH_*_VAMOS mode.
+ * If the mode has no equivalent VAMOS mode, return a negative value.
+ */
+enum gsm48_chan_mode gsm48_chan_mode_to_vamos(enum gsm48_chan_mode mode)
+{
+ switch (mode) {
+ case GSM48_CMODE_SPEECH_V1:
+ case GSM48_CMODE_SPEECH_V1_VAMOS:
+ return GSM48_CMODE_SPEECH_V1_VAMOS;
+ case GSM48_CMODE_SPEECH_EFR:
+ case GSM48_CMODE_SPEECH_V2_VAMOS:
+ return GSM48_CMODE_SPEECH_V2_VAMOS;
+ case GSM48_CMODE_SPEECH_AMR:
+ case GSM48_CMODE_SPEECH_V3_VAMOS:
+ return GSM48_CMODE_SPEECH_V3_VAMOS;
+ case GSM48_CMODE_SPEECH_V5_VAMOS:
+ return GSM48_CMODE_SPEECH_V5_VAMOS;
+ default:
+ return -1;
+ }
+}
+
+/*! Translate GSM48_CMODE_SPEECH_*_VAMOS to its corresponding GSM48_CMODE_SPEECH_* non-vamos mode.
+ * If the mode is not a VAMOS mode, return the unchanged mode.
+ */
+enum gsm48_chan_mode gsm48_chan_mode_to_non_vamos(enum gsm48_chan_mode mode)
+{
+ switch (mode) {
+ case GSM48_CMODE_SPEECH_V1_VAMOS:
+ return GSM48_CMODE_SPEECH_V1;
+ case GSM48_CMODE_SPEECH_V2_VAMOS:
+ return GSM48_CMODE_SPEECH_EFR;
+ case GSM48_CMODE_SPEECH_V3_VAMOS:
+ return GSM48_CMODE_SPEECH_AMR;
+ case GSM48_CMODE_SPEECH_V5_VAMOS:
+ return GSM48_CMODE_SPEECH_V5;
+ default:
+ return mode;
+ }
+}
+
const struct value_string gsm_chan_t_names[] = {
{ GSM_LCHAN_NONE, "NONE" },
{ GSM_LCHAN_SDCCH, "SDCCH" },
@@ -557,11 +638,13 @@ int osmo_mobile_identity_decode(struct osmo_mobile_identity *mi, const uint8_t *
{
int rc;
int nibbles_len;
- char *str;
- size_t str_size;
+ char *str = NULL; /* initialize to avoid uninitialized false warnings on some gcc versions (11.1.0) */
+ size_t str_size = 0; /* initialize to avoid uninitialized false warnings on some gcc versions (11.1.0) */
- if (!mi_data || mi_len < 1)
- return -EBADMSG;
+ if (!mi_data || mi_len < 1) {
+ rc = -EBADMSG;
+ goto return_error;
+ }
nibbles_len = (mi_len - 1) * 2 + ((mi_data[0] & GSM_MI_ODD) ? 1 : 0);
@@ -633,8 +716,12 @@ int osmo_mobile_identity_decode(struct osmo_mobile_identity *mi, const uint8_t *
goto return_error;
}
rc = osmo_bcd2str(str, str_size, mi_data, 1, 1 + nibbles_len, allow_hex);
- /* rc checked below */
- break;
+ /* check mi->str printing rc */
+ if (rc < 1 || rc >= str_size) {
+ rc = -EBADMSG;
+ goto return_error;
+ }
+ return 0;
default:
/* Already handled above, but as future bug paranoia: */
@@ -642,13 +729,6 @@ int osmo_mobile_identity_decode(struct osmo_mobile_identity *mi, const uint8_t *
goto return_error;
}
- /* check mi->str printing rc */
- if (rc < 1 || rc >= str_size) {
- rc = -EBADMSG;
- goto return_error;
- }
- return 0;
-
return_error:
*mi = (struct osmo_mobile_identity){
.type = GSM_MI_TYPE_NONE,
@@ -786,11 +866,13 @@ int osmo_mobile_identity_encode_msgb(struct msgb *msg, const struct osmo_mobile_
* osmo_mobile_identity.
*
* \param[out] mi Return buffer for decoded Mobile Identity.
- * \param[in] msg The Complete Layer 3 message to extract from (LU, CM Service Req or Paging Resp).
+ * \param[in] l3_data The Complete Layer 3 message to extract from (LU, CM Service Req or Paging Resp).
+ * \param[in] l3_len Length of l3_data in bytes.
* \returns 0 on success, negative on error: return codes as defined in osmo_mobile_identity_decode(), or
* -ENOTSUP = not a Complete Layer 3 message,
*/
-int osmo_mobile_identity_decode_from_l3(struct osmo_mobile_identity *mi, struct msgb *msg, bool allow_hex)
+int osmo_mobile_identity_decode_from_l3_buf(struct osmo_mobile_identity *mi, const uint8_t *l3_data, size_t l3_len,
+ bool allow_hex)
{
const struct gsm48_hdr *gh;
int8_t pdisc = 0;
@@ -809,10 +891,10 @@ int osmo_mobile_identity_decode_from_l3(struct osmo_mobile_identity *mi, struct
.tmsi = GSM_RESERVED_TMSI,
};
- if (msgb_l3len(msg) < sizeof(*gh))
+ if (l3_len < sizeof(*gh))
return -EBADMSG;
- gh = msgb_l3(msg);
+ gh = (void *)l3_data;
pdisc = gsm48_hdr_pdisc(gh);
mtype = gsm48_hdr_msg_type(gh);
@@ -822,12 +904,12 @@ int osmo_mobile_identity_decode_from_l3(struct osmo_mobile_identity *mi, struct
switch (mtype) {
case GSM48_MT_MM_LOC_UPD_REQUEST:
/* First make sure that lu-> can be dereferenced */
- if (msgb_l3len(msg) < sizeof(*gh) + sizeof(*lu))
+ if (l3_len < sizeof(*gh) + sizeof(*lu))
return -EBADMSG;
/* Now we know there is enough msgb data to read a lu->mi_len, so also check that */
lu = (struct gsm48_loc_upd_req*)gh->data;
- if (msgb_l3len(msg) < sizeof(*gh) + sizeof(*lu) + lu->mi_len)
+ if (l3_len < sizeof(*gh) + sizeof(*lu) + lu->mi_len)
return -EBADMSG;
mi_data = lu->mi;
mi_len = lu->mi_len;
@@ -837,7 +919,7 @@ int osmo_mobile_identity_decode_from_l3(struct osmo_mobile_identity *mi, struct
case GSM48_MT_MM_CM_REEST_REQ:
/* Unfortunately in Phase1 the Classmark2 length is variable, so we cannot
* just use gsm48_service_request struct, and need to parse it manually. */
- if (msgb_l3len(msg) < sizeof(*gh) + 2)
+ if (l3_len < sizeof(*gh) + 2)
return -EBADMSG;
cm2_len = gh->data[1];
@@ -845,7 +927,7 @@ int osmo_mobile_identity_decode_from_l3(struct osmo_mobile_identity *mi, struct
goto got_cm2;
case GSM48_MT_MM_IMSI_DETACH_IND:
- if (msgb_l3len(msg) < sizeof(*gh) + sizeof(*idi))
+ if (l3_len < sizeof(*gh) + sizeof(*idi))
return -EBADMSG;
idi = (struct gsm48_imsi_detach_ind*) gh->data;
mi_data = idi->mi;
@@ -853,7 +935,7 @@ int osmo_mobile_identity_decode_from_l3(struct osmo_mobile_identity *mi, struct
goto got_mi;
case GSM48_MT_MM_ID_RESP:
- if (msgb_l3len(msg) < sizeof(*gh) + 2)
+ if (l3_len < sizeof(*gh) + 2)
return -EBADMSG;
mi_data = gh->data+1;
mi_len = gh->data[0];
@@ -868,13 +950,24 @@ int osmo_mobile_identity_decode_from_l3(struct osmo_mobile_identity *mi, struct
switch (mtype) {
case GSM48_MT_RR_PAG_RESP:
- if (msgb_l3len(msg) < sizeof(*gh) + sizeof(*paging_response))
+ if (l3_len < sizeof(*gh) + sizeof(*paging_response))
return -EBADMSG;
paging_response = (struct gsm48_pag_resp*)gh->data;
cm2_len = paging_response->cm2_len;
cm2_buf = (uint8_t*)&paging_response->cm2;
goto got_cm2;
+ case GSM48_MT_RR_TALKER_IND:
+ /* Check minimum size: Header + CM2 LV + minimum MI LV */
+ if (l3_len < sizeof(*gh) + 4 + 2)
+ return -EBADMSG;
+ /* CM2 shall be always 3 bytes in length */
+ if (gh->data[0] != 3)
+ return -EBADMSG;
+ cm2_len = gh->data[0];
+ cm2_buf = gh->data + 1;
+ goto got_cm2;
+
default:
break;
}
@@ -887,7 +980,7 @@ got_cm2:
/* MI (Mobile Identity) LV follows the Classmark2 */
/* There must be at least a mi_len byte after the CM2 */
- if (cm2_buf + cm2_len + 1 > msg->tail)
+ if (cm2_buf + cm2_len + 1 > l3_data + l3_len)
return -EBADMSG;
mi_start = cm2_buf + cm2_len;
@@ -896,12 +989,27 @@ got_cm2:
got_mi:
/* mi_data points at the start of the Mobile Identity coding of mi_len bytes */
- if (mi_data + mi_len > msg->tail)
+ if (mi_data + mi_len > l3_data + l3_len)
return -EBADMSG;
return osmo_mobile_identity_decode(mi, mi_data, mi_len, allow_hex);
}
+/*! Extract Mobile Identity from a Complete Layer 3 message.
+ *
+ * Determine the Mobile Identity data and call osmo_mobile_identity_decode() to return a decoded struct
+ * osmo_mobile_identity.
+ *
+ * \param[out] mi Return buffer for decoded Mobile Identity.
+ * \param[in] msg The Complete Layer 3 message to extract from (LU, CM Service Req or Paging Resp).
+ * \returns 0 on success, negative on error: return codes as defined in osmo_mobile_identity_decode(), or
+ * -ENOTSUP = not a Complete Layer 3 message,
+ */
+int osmo_mobile_identity_decode_from_l3(struct osmo_mobile_identity *mi, struct msgb *msg, bool allow_hex)
+{
+ return osmo_mobile_identity_decode_from_l3_buf(mi, msgb_l3(msg), msgb_l3len(msg), allow_hex);
+}
+
/*! Return a human readable representation of a struct osmo_mobile_identity.
* Write a string like "IMSI-1234567", "TMSI-0x1234ABCD" or "NONE", "NULL".
* \param[out] buf String buffer to write to.
@@ -1240,7 +1348,64 @@ int gsm48_mi_to_string(char *string, int str_len, const uint8_t *mi, int mi_len)
return 1;
}
-/*! Parse TS 04.08 Routing Area Identifier
+/*! Decode to struct osmo_routing_area_id from a 3GPP TS 24.008 § 10.5.5.15 Routing area identification.
+ * \param[out] dst Store the decoded result here.
+ * \param[in] ra_data The start of a Routing Area ID in encoded form, to be decoded.
+ * \param[in] ra_data_len Buffer size available to read from at *ra_data.
+ * \return the number of decoded bytes on success, or negative on error (if the input buffer is too small).
+ */
+int osmo_routing_area_id_decode(struct osmo_routing_area_id *dst, const uint8_t *ra_data, size_t ra_data_len)
+{
+ const struct gsm48_ra_id *ra_id;
+ if (ra_data_len < sizeof(*ra_id))
+ return -ENOSPC;
+
+ gsm48_decode_lai2((void *)ra_data, &dst->lac);
+
+ ra_id = (void *)ra_data;
+ dst->rac = ra_id->rac;
+
+ return sizeof(*ra_id);
+}
+
+/*! Encode struct osmo_routing_area_id to a 3GPP TS 24.008 § 10.5.5.15 Routing area identification: write to a buffer.
+ * \param[out] buf Return buffer for encoded Mobile Identity.
+ * \param[in] buflen sizeof(buf).
+ * \param[in] src RA to encode.
+ * \return Amount of bytes written to buf, or negative on error.
+ */
+int osmo_routing_area_id_encode_buf(uint8_t *buf, size_t buflen, const struct osmo_routing_area_id *src)
+{
+ struct gsm48_ra_id *ra_id;
+ if (buflen < sizeof(*ra_id))
+ return -ENOSPC;
+
+ gsm48_generate_lai2((void *)buf, &src->lac);
+
+ ra_id = (void *)buf;
+ ra_id->rac = src->rac;
+
+ return sizeof(*ra_id);
+}
+
+/*! Encode struct osmo_routing_area_id to a 3GPP TS 24.008 § 10.5.5.15 Routing area identification: append to msgb.
+ * To succeed, the msgb must have tailroom >= sizeof(struct gsm48_ra_id).
+ * \param[out] msg Append to this msgb.
+ * \param[in] src Encode this Routing Area ID.
+ * \return Number of bytes appended to msgb, or negative on error.
+ */
+int osmo_routing_area_id_encode_msgb(struct msgb *msg, const struct osmo_routing_area_id *src)
+{
+ int rc = osmo_routing_area_id_encode_buf(msg->tail, msgb_tailroom(msg), src);
+ if (rc <= 0)
+ return rc;
+ msgb_put(msg, rc);
+ return rc;
+}
+
+/*! Parse TS 04.08 Routing Area Identifier.
+ * Preferably use osmo_routing_area_id_decode() instead: struct osmo_routing_area_id is better integrated with other API
+ * like osmo_plmn_cmp().
* \param[out] Caller-provided memory for decoded RA ID
* \param[in] buf Input buffer pointing to RAI IE value */
void gsm48_parse_ra(struct gprs_ra_id *raid, const uint8_t *buf)
@@ -1298,6 +1463,25 @@ int gsm48_construct_ra(uint8_t *buf, const struct gprs_ra_id *raid)
return 6;
}
+/*! Compare a TS 04.08 Routing Area Identifier
+ * \param[in] raid1 first Routing Area ID to compare.
+ * \param[in] raid2 second Routing Area ID to compare.
+ * \returns true if raid1 and raid2 match, false otherwise. */
+bool gsm48_ra_equal(const struct gprs_ra_id *raid1, const struct gprs_ra_id *raid2)
+{
+ if (raid1->mcc != raid2->mcc)
+ return false;
+ if (raid1->mnc != raid2->mnc)
+ return false;
+ if (raid1->mnc_3_digits != raid2->mnc_3_digits)
+ return false;
+ if (raid1->lac != raid2->lac)
+ return false;
+ if (raid1->rac != raid2->rac)
+ return false;
+ return true;
+}
+
/*! Determine number of paging sub-channels
* \param[in] chan_desc Control Channel Description
* \returns number of paging sub-channels
@@ -1305,7 +1489,7 @@ int gsm48_construct_ra(uint8_t *buf, const struct gprs_ra_id *raid)
* Uses From Table 10.5.33 of GSM 04.08 to determine the number of
* paging sub-channels in the given control channel configuration
*/
-int gsm48_number_of_paging_subchannels(struct gsm48_control_channel_descr *chan_desc)
+int gsm48_number_of_paging_subchannels(const struct gsm48_control_channel_descr *chan_desc)
{
unsigned int n_pag_blocks = gsm0502_get_n_pag_blocks(chan_desc);
@@ -1585,6 +1769,10 @@ char *gsm48_pdisc_msgtype_name_buf(char *buf, size_t buf_len, uint8_t pdisc, uin
case GSM48_PDISC_CC:
msgt_names = gsm48_cc_msgtype_names;
break;
+ case GSM48_PDISC_GROUP_CC:
+ case GSM48_PDISC_BCAST_CC:
+ msgt_names = osmo_gsm44068_msg_type_names;
+ break;
case GSM48_PDISC_NC_SS:
msgt_names = gsm48_nc_ss_msgtype_names;
break;
diff --git a/src/gsm/gsm48_arfcn_range_encode.c b/src/gsm/gsm48_arfcn_range_encode.c
index 6423a9a8..afe552d8 100644
--- a/src/gsm/gsm48_arfcn_range_encode.c
+++ b/src/gsm/gsm48_arfcn_range_encode.c
@@ -166,6 +166,10 @@ int osmo_gsm48_range_enc_determine_range(const int *arfcns, const int size, int
{
int max = 0;
+ /* don't dereference arfcns[] array if size is 0 */
+ if (size == 0)
+ return OSMO_GSM48_ARFCN_RANGE_128;
+
/*
* Go for the easiest. And pick arfcns[0] == f0.
*/
diff --git a/src/gsm/gsm48_ie.c b/src/gsm/gsm48_ie.c
index 6070e5cc..b95609fe 100644
--- a/src/gsm/gsm48_ie.c
+++ b/src/gsm/gsm48_ie.c
@@ -19,10 +19,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
@@ -48,10 +44,11 @@ static const char bcd_num_digits[] = {
};
/*! Like gsm48_decode_bcd_number2() but with less airtight bounds checking.
- * \param[out] Caller-provided output buffer
+ * \param[out] output Caller-provided output buffer
+ * \param[in] output_len sizeof(output)
* \param[in] bcd_lv Length-Value portion of to-be-decoded IE
* \param[in] h_len Length of an optional heder between L and V portion
- * \returns - in case of success; negative on error */
+ * \returns 0 in case of success; negative on error */
int gsm48_decode_bcd_number(char *output, int output_len,
const uint8_t *bcd_lv, int h_len)
{
@@ -143,7 +140,7 @@ static int asc_to_bcd(const char asc)
* \param[in] max_len Maximum Length of \a bcd_lv
* \param[in] h_len Length of an optional heder between L and V portion
* \param[in] input phone number as 0-terminated ASCII
- * \returns number of bytes used in \a bcd_lv
+ * \returns number of bytes used in \a bcd_lv; negative on error
*
* Depending on a context (e.g. called or calling party BCD number), the
* optional header between L and V parts can contain TON (Type Of Number),
@@ -183,8 +180,8 @@ int gsm48_encode_bcd_number(uint8_t *bcd_lv, uint8_t max_len,
}
/*! Decode TS 04.08 Bearer Capability IE (10.5.4.5)
- * \param[out] Caller-provided memory for decoded output
- * \[aram[in] LV portion of TS 04.08 Bearer Capability
+ * \param[out] bcap Caller-provided memory for decoded output
+ * \param[in] lv LV portion of TS 04.08 Bearer Capability
* \returns 0 on success; negative on error */
int gsm48_decode_bearer_cap(struct gsm_mncc_bearer_cap *bcap,
const uint8_t *lv)
@@ -207,7 +204,24 @@ int gsm48_decode_bearer_cap(struct gsm_mncc_bearer_cap *bcap,
case GSM_MNCC_BCAP_SPEECH:
i = 1;
s = 0;
- while(!(lv[i] & 0x80)) {
+ if ((lv[1] & 0x80) != 0) { /* octet 3a is absent */
+ switch (bcap->radio) {
+ case GSM48_BCAP_RRQ_FR_ONLY:
+ bcap->speech_ver[s++] = GSM48_BCAP_SV_FR;
+ break;
+ case GSM48_BCAP_RRQ_DUAL_HR:
+ bcap->speech_ver[s++] = GSM48_BCAP_SV_HR;
+ bcap->speech_ver[s++] = GSM48_BCAP_SV_FR;
+ break;
+ case GSM48_BCAP_RRQ_DUAL_FR:
+ bcap->speech_ver[s++] = GSM48_BCAP_SV_FR;
+ bcap->speech_ver[s++] = GSM48_BCAP_SV_HR;
+ break;
+ }
+ bcap->speech_ver[s] = -1; /* end of list */
+ return 0;
+ }
+ while (!(lv[i] & 0x80)) {
i++; /* octet 3a etc */
if (in_len < i)
return 0;
@@ -220,9 +234,10 @@ int gsm48_decode_bearer_cap(struct gsm_mncc_bearer_cap *bcap,
}
break;
case GSM_MNCC_BCAP_UNR_DIG:
+ case GSM_MNCC_BCAP_AUDIO:
case GSM_MNCC_BCAP_FAX_G3:
i = 1;
- while(!(lv[i] & 0x80)) {
+ while (!(lv[i] & 0x80)) {
i++; /* octet 3a etc */
if (in_len < i)
return 0;
@@ -236,7 +251,7 @@ int gsm48_decode_bearer_cap(struct gsm_mncc_bearer_cap *bcap,
return 0;
bcap->data.rate_adaption = (lv[i] >> 3) & 3;
bcap->data.sig_access = lv[i] & 7;
- while(!(lv[i] & 0x80)) {
+ while (!(lv[i] & 0x80)) {
i++; /* octet 5a etc */
if (in_len < i)
return 0;
@@ -321,10 +336,11 @@ int gsm48_encode_bearer_cap(struct msgb *msg, int lv_only,
lv[i] |= 0x80; /* last IE of octet 3 etc */
break;
case GSM48_BCAP_ITCAP_UNR_DIG_INF:
+ case GSM48_BCAP_ITCAP_3k1_AUDIO:
case GSM48_BCAP_ITCAP_FAX_G3:
lv[i++] |= 0x80; /* last IE of octet 3 etc */
/* octet 4 */
- lv[i++] = 0xb8;
+ lv[i++] = 0x88;
/* octet 5 */
lv[i++] = 0x80 | ((bcap->data.rate_adaption & 3) << 3)
| (bcap->data.sig_access & 7);
@@ -338,7 +354,9 @@ int gsm48_encode_bearer_cap(struct msgb *msg, int lv_only,
lv[i++] = (bcap->data.parity & 7) |
((bcap->data.interm_rate & 3) << 5);
/* octet 6c */
- lv[i] = 0x80 | (bcap->data.modem_type & 0x1f);
+ lv[i] = 0x80 |
+ ((bcap->data.transp & 3) << 5) |
+ (bcap->data.modem_type & 0x1f);
break;
default:
return -EINVAL;
@@ -354,9 +372,9 @@ int gsm48_encode_bearer_cap(struct msgb *msg, int lv_only,
}
/*! Decode TS 04.08 Call Control Capabilities IE (10.5.4.5a)
- * \param[out] Caller-provided memory for decoded CC capabilities
+ * \param[out] ccap Caller-provided memory for decoded CC capabilities
* \param[in] lv Length-Value of IE
- * \retursns 0 on success; negative on error */
+ * \returns 0 on success; negative on error */
int gsm48_decode_cccap(struct gsm_mncc_cccap *ccap, const uint8_t *lv)
{
uint8_t in_len = lv[0];
@@ -440,7 +458,7 @@ int gsm48_encode_called(struct msgb *msg,
}
/*! Decode TS 04.08 Caller ID
- * \param[out] called Caller-provided memory for decoded number
+ * \param[out] callerid Caller-provided memory for decoded number
* \param[in] lv Length-Value portion of IE
* \returns 0 on success; negative on error */
int gsm48_decode_callerid(struct gsm_mncc_number *callerid,
@@ -812,7 +830,7 @@ int gsm48_decode_ssversion(struct gsm_mncc_ssversion *ssv,
{
uint8_t in_len = lv[0];
- if (in_len < 1 || in_len < sizeof(ssv->info))
+ if (in_len < 1 || in_len > sizeof(ssv->info))
return -EINVAL;
memcpy(ssv->info, lv + 1, in_len);
@@ -867,8 +885,9 @@ static int32_t smod(int32_t n, int32_t m)
* \param[in] cd Cell Channel Description IE
* \param[in] len Length of \a cd in bytes
* \returns 0 on success; negative on error */
-int gsm48_decode_freq_list(struct gsm_sysinfo_freq *f, uint8_t *cd,
- uint8_t len, uint8_t mask, uint8_t frqt)
+int gsm48_decode_freq_list(struct gsm_sysinfo_freq *f,
+ const uint8_t *cd, uint8_t len,
+ uint8_t mask, uint8_t frqt)
{
int i;
diff --git a/src/gsm/gsm48_rest_octets.c b/src/gsm/gsm48_rest_octets.c
index a5275985..57c2c99a 100644
--- a/src/gsm/gsm48_rest_octets.c
+++ b/src/gsm/gsm48_rest_octets.c
@@ -59,11 +59,88 @@ int osmo_gsm48_rest_octets_si1_encode(uint8_t *data, uint8_t *nch_pos, int is180
return bv.data_len;
}
+struct nch_pos {
+ uint8_t num_blocks;
+ uint8_t first_block;
+};
+
+/* 3GPP TS 44.010 Table 10.5.2.32.1b */
+static const struct nch_pos si1ro_nch_positions[] = {
+ [0x00] = {1, 0},
+ [0x01] = {1, 1},
+ [0x02] = {1, 2},
+ [0x03] = {1, 3},
+ [0x04] = {1, 4},
+ [0x05] = {1, 5},
+ [0x06] = {1, 6},
+
+ [0x07] = {2, 0},
+ [0x08] = {2, 1},
+ [0x09] = {2, 2},
+ [0x0a] = {2, 3},
+ [0x0b] = {2, 4},
+ [0x0c] = {2, 5},
+
+ [0x0d] = {3, 0},
+ [0x0e] = {3, 1},
+ [0x0f] = {3, 2},
+ [0x10] = {3, 3},
+ [0x11] = {3, 4},
+
+ [0x12] = {4, 0},
+ [0x13] = {4, 1},
+ [0x14] = {4, 2},
+ [0x15] = {4, 3},
+
+ [0x16] = {5, 0},
+ [0x17] = {5, 1},
+ [0x18] = {5, 2},
+
+ [0x19] = {6, 0},
+ [0x1a] = {6, 1},
+
+ [0x1b] = {7, 0},
+};
+
+/*! Decode the 5-bit 'NCH position' field within SI1 Rest Octets.
+ * \param[in] value 5-bit value from SI1 rest octets
+ * \param[out] num_blocks Number of CCCH used for NCH
+ * \param[out] first_block First CCCH block used for NCH
+ * \returns 0 on success; negative in case of error */
+int osmo_gsm48_si1ro_nch_pos_decode(uint8_t value, uint8_t *num_blocks, uint8_t *first_block)
+{
+ if (value >= ARRAY_SIZE(si1ro_nch_positions))
+ return -EINVAL;
+
+ *num_blocks = si1ro_nch_positions[value].num_blocks;
+ *first_block = si1ro_nch_positions[value].first_block;
+
+ return 0;
+}
+
+/*! Encode the 5-bit 'NCH position' field within SI1 Rest Octets.
+ * \param[in] num_blocks Number of CCCH used for NCH
+ * \param[in] first_block First CCCH block used for NCH
+ * \returns 5-bit value for SI1 rest octets on success; negative in case of error */
+int osmo_gsm48_si1ro_nch_pos_encode(uint8_t num_blocks, uint8_t first_block)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(si1ro_nch_positions); i++) {
+ if (si1ro_nch_positions[i].num_blocks == num_blocks &&
+ si1ro_nch_positions[i].first_block == first_block) {
+ return i;
+ }
+ }
+ return -EINVAL;
+}
+
/* Append Repeated E-UTRAN Neighbour Cell to bitvec: see 3GPP TS 44.018 Table 10.5.2.33b.1 */
static inline bool append_eutran_neib_cell(struct bitvec *bv, const struct osmo_earfcn_si2q *e, size_t *e_offset,
uint8_t budget)
{
unsigned i, skip = 0;
+ size_t offset = *e_offset;
int16_t rem = budget - 6; /* account for mandatory stop bit and THRESH_E-UTRAN_high */
uint8_t earfcn_budget;
@@ -94,7 +171,7 @@ static inline bool append_eutran_neib_cell(struct bitvec *bv, const struct osmo_
/* now we can proceed with actually adding EARFCNs within adjusted budget limit */
for (i = 0; i < e->length; i++) {
if (e->arfcn[i] != OSMO_EARFCN_INVALID) {
- if (skip < *e_offset) {
+ if (skip < offset) {
skip++; /* ignore EARFCNs added on previous calls */
} else {
earfcn_budget = 17; /* compute budget per-EARFCN */
@@ -165,7 +242,7 @@ static inline void append_earfcn(struct bitvec *bv, const struct osmo_earfcn_si2
{
bool appended;
unsigned int old = bv->cur_bit; /* save current position to make rollback possible */
- int rem = budget - 25;
+ int rem = ((int)budget) - 40;
if (rem <= 0)
return;
@@ -193,8 +270,29 @@ static inline void append_earfcn(struct bitvec *bv, const struct osmo_earfcn_si2
/* Priority and E-UTRAN Parameters Description */
bitvec_set_bit(bv, 1);
- /* No Serving Cell Priority Parameters Descr. */
- bitvec_set_bit(bv, 0);
+ /* budget: 10 bits used above */
+
+ /* Serving Cell Priority Parameters Descr. is Present,
+ * see also: 3GPP TS 44.018, Table 10.5.2.33b.1 */
+ bitvec_set_bit(bv, 1);
+
+ /* GERAN_PRIORITY */
+ bitvec_set_uint(bv, 0, 3);
+
+ /* THRESH_Priority_Search */
+ bitvec_set_uint(bv, 0, 4);
+
+ /* THRESH_GSM_low */
+ bitvec_set_uint(bv, 0, 4);
+
+ /* H_PRIO */
+ bitvec_set_uint(bv, 0, 2);
+
+ /* T_Reselection */
+ bitvec_set_uint(bv, 0, 2);
+
+ /* budget: 26 bits used above */
+
/* No 3G Priority Parameters Description */
bitvec_set_bit(bv, 0);
/* E-UTRAN Parameters Description */
@@ -218,12 +316,16 @@ static inline void append_earfcn(struct bitvec *bv, const struct osmo_earfcn_si2
/* Repeated E-UTRAN Neighbour Cells */
bitvec_set_bit(bv, 1);
+ /* budget: 34 bits used above */
+
appended = append_eutran_neib_cell(bv, e, e_offset, rem);
if (!appended) { /* appending is impossible within current budget: rollback */
bv->cur_bit = old;
return;
}
+ /* budget: further 6 bits used below, totalling 40 bits */
+
/* stop bit - end of Repeated E-UTRAN Neighbour Cells sequence: */
bitvec_set_bit(bv, 0);
@@ -585,9 +687,9 @@ int osmo_gsm48_rest_octets_si3_encode(uint8_t *data, const struct osmo_gsm48_si_
/* 3G Early Classmark Sending Restriction. If H, then controlled by
* early_cm_ctrl above */
if (si3->early_cm_restrict_3g)
- bitvec_set_bit(&bv, H);
- else
bitvec_set_bit(&bv, L);
+ else
+ bitvec_set_bit(&bv, H);
if (si3->si2quater_indicator) {
bitvec_set_bit(&bv, H); /* indicator struct present */
@@ -723,6 +825,119 @@ int osmo_gsm48_rest_octets_si6_encode(uint8_t *data, const struct osmo_gsm48_si6
return bv.data_len;
}
+
+static unsigned int decode_t3192(unsigned int t3192)
+{
+ /* See also 3GPP TS 44.060
+ Table 12.24.2: GPRS Cell Options information element details */
+ static const unsigned int decode_t3192_tbl[8] = {500, 1000, 1500, 0, 80, 120, 160, 200};
+ OSMO_ASSERT(t3192 <= 7);
+ return decode_t3192_tbl[t3192];
+}
+
+static unsigned int decode_drx_timer(unsigned int drx)
+{
+ static const unsigned int decode_drx_timer_tbl[8] = {0, 1, 2, 4, 8, 16, 32, 64};
+ OSMO_ASSERT(drx <= 7);
+ return decode_drx_timer_tbl[drx];
+}
+
+static int decode_gprs_cell_opt(struct osmo_gprs_cell_options *gco, struct bitvec *bv)
+{
+ gco->nmo = bitvec_get_uint(bv, 2);
+ gco->t3168 = (bitvec_get_uint(bv, 3) + 1) * 500;
+ gco->t3192 = decode_t3192(bitvec_get_uint(bv, 3));
+ gco->drx_timer_max = decode_drx_timer(bitvec_get_uint(bv, 3));
+
+ /* ACCESS_BURST_TYPE: */
+ bitvec_get_uint(bv, 1);
+ /* CONTROL_ACK_TYPE: */
+ gco->ctrl_ack_type_use_block = bitvec_get_uint(bv, 1);
+ gco->bs_cv_max = bitvec_get_uint(bv, 4);
+
+ if (bitvec_get_uint(bv, 1)) {
+ bitvec_get_uint(bv, 3); /* DEC */
+ bitvec_get_uint(bv, 3); /* INC */
+ bitvec_get_uint(bv, 3); /* MAX */
+ }
+
+ if (bitvec_get_uint(bv, 1)) {
+ int ext_len = bitvec_get_uint(bv, 6);
+ if (ext_len < 0)
+ return ext_len;
+ unsigned int cur_bit = bv->cur_bit;
+ /* Extension Information */
+ /* R99 extension: */
+ gco->ext_info.egprs_supported = bitvec_get_uint(bv, 1);
+ if (gco->ext_info.egprs_supported) {
+ gco->ext_info.use_egprs_p_ch_req = !bitvec_get_uint(bv, 1);
+ gco->ext_info.bep_period = bitvec_get_uint(bv, 4);
+ }
+ gco->ext_info.pfc_supported = bitvec_get_uint(bv, 1);
+ gco->ext_info.dtm_supported = bitvec_get_uint(bv, 1);
+ gco->ext_info.bss_paging_coordination = bitvec_get_uint(bv, 1);
+ /* REL-4 extension: */
+ gco->ext_info.ccn_active = bitvec_get_uint(bv, 1);
+ bitvec_get_uint(bv, 1); /* NW_EXT_UTBF */
+ bv->cur_bit = cur_bit + ext_len + 1;
+ }
+ return 0;
+}
+
+static void decode_gprs_pwr_ctrl_pars(struct osmo_gprs_power_ctrl_pars *pcp, struct bitvec *bv)
+{
+ pcp->alpha = bitvec_get_uint(bv, 4);
+ pcp->t_avg_w = bitvec_get_uint(bv,5);
+ pcp->t_avg_t = bitvec_get_uint(bv, 5);
+ pcp->pc_meas_chan = bitvec_get_uint(bv, 1);
+ pcp->n_avg_i = bitvec_get_uint(bv, 4);
+}
+
+/*! Decode SI13 Rest Octests (04.08 Chapter 10.5.2.37b).
+ * \param[out] si13 decoded SI13 rest octets
+ * \param[in] encoded SI13 rest octets
+ * \returns parsed bits on success, negative on error */
+int osmo_gsm48_rest_octets_si13_decode(struct osmo_gsm48_si13_info *si13, const uint8_t *data)
+{
+ struct osmo_gprs_cell_options *co = &si13->cell_opts;
+ struct osmo_gprs_power_ctrl_pars *pcp = &si13->pwr_ctrl_pars;
+ struct bitvec bv;
+ int rc;
+
+ memset(&bv, 0, sizeof(bv));
+ bv.data = (uint8_t *) data;
+ bv.data_len = 20;
+
+ memset(si13, 0, sizeof(*si13));
+
+
+ if (bitvec_get_bit_high(&bv) == H) {
+ si13->bcch_change_mark = bitvec_get_uint(&bv, 3);
+ si13->si_change_field = bitvec_get_uint(&bv, 4);
+ if (bitvec_get_uint(&bv, 1)) {
+ si13->bcch_change_mark = bitvec_get_uint(&bv, 2);
+ /* FIXME: implement parsing GPRS Mobile Allocation IE */
+ return -ENOTSUP;
+ }
+ if (bitvec_get_uint(&bv, 1)) {
+ /* PBCCH present in cell */
+ /* FIXME: parse not implemented */
+ return -ENOTSUP;
+ } else {
+ /* PBCCH not present in cell */
+ si13->rac = bitvec_get_uint(&bv, 8);
+ si13->spgc_ccch_sup = bitvec_get_uint(&bv, 1);
+ si13->prio_acc_thr = bitvec_get_uint(&bv, 3);
+ si13->net_ctrl_ord = bitvec_get_uint(&bv, 2);
+ if ((rc = decode_gprs_cell_opt(co, &bv)) < 0)
+ return rc;
+
+ decode_gprs_pwr_ctrl_pars(pcp, &bv);
+ }
+ }
+ return bv.cur_bit;
+}
+
/* GPRS Mobile Allocation as per TS 04.60 Chapter 12.10a:
< GPRS Mobile Allocation IE > ::=
< HSN : bit (6) >
@@ -881,24 +1096,20 @@ static int append_gprs_cell_opt(struct bitvec *bv,
} else {
/* extension information */
bitvec_set_bit(bv, 1);
+ /* R99 extension: */
if (!gco->ext_info.egprs_supported) {
/* 6bit length of extension */
- bitvec_set_uint(bv, (1 + 3)-1, 6);
+ bitvec_set_uint(bv, (1 + 5)-1, 6);
/* EGPRS supported in the cell */
bitvec_set_bit(bv, 0);
} else {
/* 6bit length of extension */
- bitvec_set_uint(bv, (1 + 5 + 3)-1, 6);
+ bitvec_set_uint(bv, (1 + 5 + 5)-1, 6);
/* EGPRS supported in the cell */
bitvec_set_bit(bv, 1);
- /* 1bit EGPRS PACKET CHANNEL REQUEST */
- if (gco->supports_egprs_11bit_rach == 0) {
- bitvec_set_bit(bv,
- gco->ext_info.use_egprs_p_ch_req);
- } else {
- bitvec_set_bit(bv, 0);
- }
+ /* 1bit EGPRS PACKET CHANNEL REQUEST (inverted logic) */
+ bitvec_set_bit(bv, !gco->ext_info.use_egprs_p_ch_req);
/* 4bit BEP PERIOD */
bitvec_set_uint(bv, gco->ext_info.bep_period, 4);
@@ -906,6 +1117,10 @@ static int append_gprs_cell_opt(struct bitvec *bv,
bitvec_set_bit(bv, gco->ext_info.pfc_supported);
bitvec_set_bit(bv, gco->ext_info.dtm_supported);
bitvec_set_bit(bv, gco->ext_info.bss_paging_coordination);
+
+ /* REL-4 extension: */
+ bitvec_set_bit(bv, gco->ext_info.ccn_active);
+ bitvec_set_bit(bv, 0); /* NW_EXT_UTBF disabled */
}
return 0;
@@ -1036,9 +1251,9 @@ void osmo_gsm48_rest_octets_si3_decode(struct osmo_gsm48_si_ro_info *si3, const
/* 3G Early Classmark Sending Restriction. If H, then controlled by
* early_cm_ctrl above */
if (bitvec_get_bit_high(&bv) == H)
- si3->early_cm_restrict_3g = 1;
- else
si3->early_cm_restrict_3g = 0;
+ else
+ si3->early_cm_restrict_3g = 1;
if (bitvec_get_bit_high(&bv) == H)
si3->si2quater_indicator = 1;
diff --git a/src/gsm/gsm_utils.c b/src/gsm/gsm_utils.c
index ae77a9dc..bb403392 100644
--- a/src/gsm/gsm_utils.c
+++ b/src/gsm/gsm_utils.c
@@ -19,10 +19,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
/*! \mainpage libosmogsm Documentation
@@ -84,6 +80,7 @@
#include <osmocom/gsm/gsm_utils.h>
#include <osmocom/gsm/meas_rep.h>
#include <osmocom/gsm/protocol/gsm_04_08.h>
+#include <osmocom/gsm/gsm0502.h>
#include <stdlib.h>
#include <stdint.h>
@@ -96,7 +93,7 @@
#include <time.h>
#include <unistd.h>
-#include "../../config.h"
+#include "config.h"
#if (!EMBEDDED)
/* FIXME: this can be removed once we bump glibc requirements to 2.25: */
@@ -324,17 +321,18 @@ int gsm_septet_encode(uint8_t *result, const char *data)
* \param[in] septet_len Length of \a rdata
* \param[in] padding padding bits at start
* \returns number of bytes used in \a result */
-int gsm_septets2octets(uint8_t *result, const uint8_t *rdata, uint8_t septet_len, uint8_t padding)
+int gsm_septet_pack(uint8_t *result, const uint8_t *rdata, size_t septet_len, uint8_t padding)
{
int i = 0, z = 0;
uint8_t cb, nb;
int shift = 0;
- uint8_t *data = calloc(septet_len + 1, sizeof(uint8_t));
+ uint8_t *data = malloc(septet_len + 1);
if (padding) {
shift = 7 - padding;
/* the first zero is needed for padding */
memcpy(data + 1, rdata, septet_len);
+ data[0] = 0x00;
septet_len++;
} else
memcpy(data, rdata, septet_len);
@@ -369,6 +367,12 @@ int gsm_septets2octets(uint8_t *result, const uint8_t *rdata, uint8_t septet_len
return z;
}
+/*! Backwards compatibility wrapper for gsm_septets_pack(), deprecated. */
+int gsm_septets2octets(uint8_t *result, const uint8_t *rdata, uint8_t septet_len, uint8_t padding)
+{
+ return gsm_septet_pack(result, rdata, septet_len, padding);
+}
+
/*! GSM 7-bit alphabet TS 03.38 6.2.1 Character packing
* \param[out] result Caller-provided output buffer
* \param[in] n Maximum length of \a result in bytes
@@ -382,7 +386,7 @@ int gsm_7bit_encode_n(uint8_t *result, size_t n, const char *data, int *octets)
size_t max_septets = n * 8 / 7;
/* prepare for the worst case, every character expanding to two bytes */
- uint8_t *rdata = calloc(strlen(data) * 2, sizeof(uint8_t));
+ uint8_t *rdata = malloc(strlen(data) * 2);
y = gsm_septet_encode(rdata, data);
if (y > max_septets) {
@@ -393,7 +397,7 @@ int gsm_7bit_encode_n(uint8_t *result, size_t n, const char *data, int *octets)
y = max_septets;
}
- o = gsm_septets2octets(result, rdata, y, 0);
+ o = gsm_septet_pack(result, rdata, y, 0);
if (octets)
*octets = o;
@@ -487,7 +491,7 @@ int osmo_get_rand_id(uint8_t *out, size_t len)
* \param[out] buf Pre-allocated bufer for storing IE
* \returns Number of bytes filled in buf
*/
-size_t gsm0858_rsl_ul_meas_enc(struct gsm_meas_rep_unidir *mru, bool dtxd_used,
+size_t gsm0858_rsl_ul_meas_enc(const struct gsm_meas_rep_unidir *mru, bool dtxd_used,
uint8_t *buf)
{
buf[0] = dtxd_used ? (1 << 6) : 0;
@@ -882,10 +886,21 @@ char *gsm_fn_as_gsmtime_str(uint32_t fn)
/*! Encode decoded \ref gsm_time to Frame Number
* \param[in] time GSM Time in decoded structure
* \returns GSM Frame Number */
-uint32_t gsm_gsmtime2fn(struct gsm_time *time)
+uint32_t gsm_gsmtime2fn(const struct gsm_time *time)
{
- /* TS 05.02 Chapter 4.3.3 TDMA frame number */
- return (51 * ((time->t3 - time->t2 + 26) % 26) + time->t3 + (26 * 51 * time->t1));
+ uint32_t fn;
+
+ /* See also:
+ * 3GPP TS 44.018, section 10.5.2.38, 3GPP TS 45.002 section 4.3.3, and
+ * 3GPP TS 48.058, section 9.3.8 */
+ fn = 51 * OSMO_MOD_FLR((time->t3-time->t2), 26) + time->t3 + 51 * 26 * time->t1;
+
+ /* Note: Corrupted input values may cause a resulting frame number
+ * larger then the maximum permitted value of GSM_MAX_FN. Even though
+ * the caller is expected to check the input values beforehand we must
+ * make sure that the result cannot exceed the value range of a valid
+ * GSM frame number. */
+ return fn % GSM_MAX_FN;
}
char *osmo_dump_gsmtime_buf(char *buf, size_t buf_len, const struct gsm_time *tm)
@@ -910,6 +925,45 @@ char *osmo_dump_gsmtime_c(const void *ctx, const struct gsm_time *tm)
return osmo_dump_gsmtime_buf(buf, 64, tm);
}
+#define GSM_RFN_THRESHOLD (GSM_RFN_MODULUS / 2)
+uint32_t gsm_rfn2fn(uint16_t rfn, uint32_t curr_fn)
+{
+ uint32_t curr_rfn;
+ uint32_t fn_rounded;
+ const uint32_t rfn32 = rfn; /* used as 32bit for calculations */
+
+ /* Ensure that all following calculations are performed with the
+ * relative frame number */
+ OSMO_ASSERT(rfn32 < GSM_RFN_MODULUS);
+
+ /* Compute an internal relative frame number from the full internal
+ frame number */
+ curr_rfn = gsm_fn2rfn(curr_fn);
+
+ /* Compute a "rounded" version of the internal frame number, which
+ * exactly fits in the RFN_MODULUS raster */
+ fn_rounded = GSM_TDMA_FN_SUB(curr_fn, curr_rfn);
+
+ /* If the delta between the internal and the external relative frame
+ * number exceeds a certain limit, we need to assume that the incoming
+ * request belongs to a the previous rfn period. To correct this,
+ * we roll back the rounded frame number by one RFN_MODULUS */
+ if (GSM_TDMA_FN_DIFF(rfn32, curr_rfn) > GSM_RFN_THRESHOLD) {
+ /* Race condition between rfn and curr_fn detected: rfn belongs
+ to the previous RFN_MODULUS cycle, wrapping... */
+ if (fn_rounded < GSM_RFN_MODULUS) {
+ /* Corner case detected: wrapping crosses GSM_MAX_FN border */
+ fn_rounded = GSM_TDMA_FN_SUB(GSM_MAX_FN, (GSM_TDMA_FN_SUB(GSM_RFN_MODULUS, fn_rounded)));
+ } else {
+ fn_rounded = GSM_TDMA_FN_SUB(fn_rounded, GSM_RFN_MODULUS);
+ }
+ }
+
+ /* The real frame number is the sum of the rounded frame number and the
+ * relative framenumber computed via RACH */
+ return GSM_TDMA_FN_SUM(fn_rounded, rfn32);
+}
+
/*! append range1024 encoded data to bit vector
* \param[out] bv Caller-provided output bit-vector
* \param[in] r Input Range1024 sructure */
diff --git a/src/gsm/gsup.c b/src/gsm/gsup.c
index ef33ed08..4f0a1b5f 100644
--- a/src/gsm/gsup.c
+++ b/src/gsm/gsup.c
@@ -103,6 +103,10 @@ const struct value_string osmo_gsup_message_type_names[] = {
OSMO_VALUE_STRING(OSMO_GSUP_MSGT_ROUTING_ERROR),
+ OSMO_VALUE_STRING(OSMO_GSUP_MSGT_EPDG_TUNNEL_REQUEST),
+ OSMO_VALUE_STRING(OSMO_GSUP_MSGT_EPDG_TUNNEL_RESULT),
+ OSMO_VALUE_STRING(OSMO_GSUP_MSGT_EPDG_TUNNEL_ERROR),
+
{ 0, NULL }
};
@@ -122,6 +126,62 @@ int osmo_gsup_get_err_msg_type(enum osmo_gsup_message_type type_in)
return OSMO_GSUP_TO_MSGT_ERROR(type_in);
}
+static int decode_pdp_address(const uint8_t *data, size_t data_len, struct osmo_gsup_pdp_info *pdp_info)
+{
+ const struct gsm48_pdp_address *pdp_addr = (const struct gsm48_pdp_address *)data;
+ /* Explicitly pre-nitialize them to AF_UNSPEC to signal they are empty: */
+ pdp_info->pdp_address[0].u.sa.sa_family = AF_UNSPEC;
+ pdp_info->pdp_address[1].u.sa.sa_family = AF_UNSPEC;
+
+ if (data_len < 2)
+ return -GMM_CAUSE_PROTO_ERR_UNSPEC;
+
+ pdp_info->pdp_type_org = pdp_addr->organization;
+ pdp_info->pdp_type_nr = pdp_addr->type;
+
+ if (pdp_info->pdp_type_org != PDP_TYPE_ORG_IETF)
+ return -GMM_CAUSE_PROTO_ERR_UNSPEC;
+
+ /* Skip type-org + type-nr for easy calculations below: */
+ data_len -= 2;
+
+ switch (pdp_info->pdp_type_nr) {
+ case PDP_TYPE_N_IETF_IPv4:
+ if (data_len == 0)
+ return 0; /* empty, marked as AF_UNSET. */
+ if (data_len != sizeof(pdp_addr->ietf.v4))
+ return -GMM_CAUSE_PROTO_ERR_UNSPEC;
+ pdp_info->pdp_address[0].u.sa.sa_family = AF_INET;
+ pdp_info->pdp_address[0].u.sin.sin_addr.s_addr = pdp_addr->ietf.v4;
+ return 0;
+ case PDP_TYPE_N_IETF_IPv6:
+ if (data_len == 0)
+ return 0; /* empty, marked as AF_UNSET. */
+ if (data_len != sizeof(pdp_addr->ietf.v6))
+ return -GMM_CAUSE_PROTO_ERR_UNSPEC;
+ pdp_info->pdp_address[0].u.sa.sa_family = AF_INET6;
+ memcpy(&pdp_info->pdp_address[0].u.sin6.sin6_addr,
+ pdp_addr->ietf.v6,
+ sizeof(pdp_addr->ietf.v6));
+ return 0;
+ case PDP_TYPE_N_IETF_IPv4v6:
+ if (data_len == 0)
+ return 0; /* empty, marked as AF_UNSET. */
+ if (data_len != sizeof(pdp_addr->ietf.v4v6))
+ return -GMM_CAUSE_PROTO_ERR_UNSPEC;
+ pdp_info->pdp_address[0].u.sa.sa_family = AF_INET;
+ pdp_info->pdp_address[0].u.sin.sin_addr.s_addr = pdp_addr->ietf.v4v6.v4;
+ pdp_info->pdp_address[1].u.sa.sa_family = AF_INET6;
+ memcpy(&pdp_info->pdp_address[1].u.sin6.sin6_addr,
+ pdp_addr->ietf.v4v6.v6,
+ sizeof(pdp_addr->ietf.v4v6.v6));
+ return 0;
+ default:
+ /* reserved, both pdp_info->pdp_address are preinitialied to AF_UNSET. */
+ return 0;
+ }
+}
+
static int decode_pdp_info(uint8_t *data, size_t data_len,
struct osmo_gsup_pdp_info *pdp_info)
{
@@ -145,9 +205,9 @@ static int decode_pdp_info(uint8_t *data, size_t data_len,
pdp_info->context_id = osmo_decode_big_endian(value, value_len);
break;
- case OSMO_GSUP_PDP_TYPE_IE:
- pdp_info->pdp_type =
- osmo_decode_big_endian(value, value_len) & 0x0fff;
+ case OSMO_GSUP_PDP_ADDRESS_IE:
+ if ((rc = decode_pdp_address(value, value_len, pdp_info)) < 0)
+ return rc;
break;
case OSMO_GSUP_ACCESS_POINT_NAME_IE:
@@ -262,7 +322,7 @@ static int decode_auth_info(uint8_t *data, size_t data_len,
parse_error:
LOGP(DLGSUP, LOGL_ERROR,
- "GSUP IE type %d, length %zu invalid in PDP info\n", iei, value_len);
+ "GSUP IE type %d, length %zu invalid in auth info\n", iei, value_len);
return -1;
}
@@ -353,7 +413,7 @@ int osmo_gsup_decode(const uint8_t *const_data, size_t data_len,
switch (iei) {
case OSMO_GSUP_IMSI_IE:
- case OSMO_GSUP_PDP_TYPE_IE:
+ case OSMO_GSUP_PDP_ADDRESS_IE:
case OSMO_GSUP_ACCESS_POINT_NAME_IE:
case OSMO_GSUP_SRES_IE:
case OSMO_GSUP_KC_IE:
@@ -446,6 +506,11 @@ int osmo_gsup_decode(const uint8_t *const_data, size_t data_len,
gsup_msg->rand = value;
break;
+ case OSMO_GSUP_PCO_IE:
+ gsup_msg->pco = value;
+ gsup_msg->pco_len = value_len;
+ break;
+
case OSMO_GSUP_MSISDN_IE:
gsup_msg->msisdn_enc = value;
gsup_msg->msisdn_enc_len = value_len;
@@ -597,11 +662,45 @@ static void encode_pdp_info(struct msgb *msg, enum osmo_gsup_iei iei,
u8 = pdp_info->context_id;
msgb_tlv_put(msg, OSMO_GSUP_PDP_CONTEXT_ID_IE, sizeof(u8), &u8);
- if (pdp_info->pdp_type) {
- msgb_tlv_put(msg, OSMO_GSUP_PDP_TYPE_IE,
- OSMO_GSUP_PDP_TYPE_SIZE,
- osmo_encode_big_endian(pdp_info->pdp_type | 0xf000,
- OSMO_GSUP_PDP_TYPE_SIZE));
+ if (pdp_info->pdp_type_org == PDP_TYPE_ORG_IETF) {
+ struct gsm48_pdp_address pdp_addr;
+ uint8_t pdp_addr_len = 2;
+ pdp_addr.spare = 0x0f;
+ pdp_addr.organization = pdp_info->pdp_type_org;
+ pdp_addr.type = pdp_info->pdp_type_nr;
+
+ switch (pdp_info->pdp_type_nr) {
+ case PDP_TYPE_N_IETF_IPv4:
+ if (pdp_info->pdp_address[0].u.sa.sa_family == AF_INET) {
+ pdp_addr.ietf.v4 = pdp_info->pdp_address[0].u.sin.sin_addr.s_addr;
+ pdp_addr_len += sizeof(pdp_addr.ietf.v4);
+ }
+ break;
+ case PDP_TYPE_N_IETF_IPv6:
+ if (pdp_info->pdp_address[0].u.sa.sa_family == AF_INET6) {
+ memcpy(pdp_addr.ietf.v6,
+ &pdp_info->pdp_address[0].u.sin6.sin6_addr,
+ sizeof(pdp_addr.ietf.v6));
+ pdp_addr_len += sizeof(pdp_addr.ietf.v6);
+ }
+ break;
+ case PDP_TYPE_N_IETF_IPv4v6:
+ if (pdp_info->pdp_address[0].u.sa.sa_family == AF_INET) {
+ pdp_addr.ietf.v4v6.v4 = pdp_info->pdp_address[0].u.sin.sin_addr.s_addr;
+ pdp_addr_len += sizeof(pdp_addr.ietf.v4v6.v4);
+ }
+ if (pdp_info->pdp_address[0].u.sa.sa_family == AF_INET6) {
+ memcpy(pdp_addr.ietf.v4v6.v6,
+ &pdp_info->pdp_address[1].u.sin6.sin6_addr,
+ sizeof(pdp_addr.ietf.v4v6.v6));
+ pdp_addr_len += sizeof(pdp_addr.ietf.v4v6.v6);
+ }
+ break;
+ }
+
+ msgb_tlv_put(msg, OSMO_GSUP_PDP_ADDRESS_IE,
+ pdp_addr_len,
+ (const uint8_t *)&pdp_addr);
}
if (pdp_info->apn_enc) {
@@ -778,6 +877,11 @@ int osmo_gsup_encode(struct msgb *msg, const struct osmo_gsup_message *gsup_msg)
if (gsup_msg->rand)
msgb_tlv_put(msg, OSMO_GSUP_RAND_IE, 16, gsup_msg->rand);
+ if (gsup_msg->pco && gsup_msg->pco_len > 0) {
+ if (gsup_msg->pco_len > OSMO_GSUP_MAX_PCO_LEN)
+ return -EINVAL;
+ msgb_tlv_put(msg, OSMO_GSUP_PCO_IE, gsup_msg->pco_len, gsup_msg->pco);
+ }
if (gsup_msg->cn_domain) {
uint8_t dn = gsup_msg->cn_domain;
msgb_tlv_put(msg, OSMO_GSUP_CN_DOMAIN_IE, 1, &dn);
@@ -911,6 +1015,7 @@ const struct value_string osmo_gsup_message_class_names[] = {
{ OSMO_GSUP_MESSAGE_CLASS_SMS, "SMS" },
{ OSMO_GSUP_MESSAGE_CLASS_USSD, "USSD" },
{ OSMO_GSUP_MESSAGE_CLASS_INTER_MSC, "Inter-MSC" },
+ { OSMO_GSUP_MESSAGE_CLASS_IPSEC_EPDG, "IPsec-ePDG" },
{}
};
diff --git a/src/gsm/ipa.c b/src/gsm/ipa.c
index 7a26ba45..6e41fd98 100644
--- a/src/gsm/ipa.c
+++ b/src/gsm/ipa.c
@@ -267,34 +267,43 @@ int ipa_parse_unitid(const char *str, struct ipaccess_unit *unit_data)
return 0;
}
+/*! Fill ud struct from tp structure.
+ * \param[in,out] ud ipaccess_unit to fill
+ * \param[in] tp the decoded TLV structure from eg. ID_RESP message
+ * \returns zero on success, negative on error
+ *
+ * This function expects parameter ud's fields to be initialized to zero if not yet set.
+ * Existing incoming string pointer fields are expected to be allocated using
+ * talloc and will be deallocated as such if replaced with the content of tp.
+ **/
int ipa_ccm_tlv_to_unitdata(struct ipaccess_unit *ud,
const struct tlv_parsed *tp)
{
int rc = 0;
if (TLVP_PRES_LEN(tp, IPAC_IDTAG_SERNR, 1))
- ud->serno = talloc_strdup(ud, (char *)
- TLVP_VAL(tp, IPAC_IDTAG_SERNR));
+ osmo_talloc_replace_string(ud, &ud->serno,
+ (char *)TLVP_VAL(tp, IPAC_IDTAG_SERNR));
if (TLVP_PRES_LEN(tp, IPAC_IDTAG_UNITNAME, 1))
- ud->unit_name = talloc_strdup(ud, (char *)
- TLVP_VAL(tp, IPAC_IDTAG_UNITNAME));
+ osmo_talloc_replace_string(ud, &ud->unit_name,
+ (char *)TLVP_VAL(tp, IPAC_IDTAG_UNITNAME));
if (TLVP_PRES_LEN(tp, IPAC_IDTAG_LOCATION1, 1))
- ud->location1 = talloc_strdup(ud, (char *)
- TLVP_VAL(tp, IPAC_IDTAG_LOCATION1));
+ osmo_talloc_replace_string(ud, &ud->location1,
+ (char *)TLVP_VAL(tp, IPAC_IDTAG_LOCATION1));
if (TLVP_PRES_LEN(tp, IPAC_IDTAG_LOCATION2, 1))
- ud->location2 = talloc_strdup(ud, (char *)
- TLVP_VAL(tp, IPAC_IDTAG_LOCATION2));
+ osmo_talloc_replace_string(ud, &ud->location2,
+ (char *)TLVP_VAL(tp, IPAC_IDTAG_LOCATION2));
if (TLVP_PRES_LEN(tp, IPAC_IDTAG_EQUIPVERS, 1))
- ud->equipvers = talloc_strdup(ud, (char *)
- TLVP_VAL(tp, IPAC_IDTAG_EQUIPVERS));
+ osmo_talloc_replace_string(ud, &ud->equipvers,
+ (char *)TLVP_VAL(tp, IPAC_IDTAG_EQUIPVERS));
if (TLVP_PRES_LEN(tp, IPAC_IDTAG_SWVERSION, 1))
- ud->swversion = talloc_strdup(ud, (char *)
- TLVP_VAL(tp, IPAC_IDTAG_SWVERSION));
+ osmo_talloc_replace_string(ud, &ud->swversion,
+ (char *)TLVP_VAL(tp, IPAC_IDTAG_SWVERSION));
if (TLVP_PRES_LEN(tp, IPAC_IDTAG_MACADDR, 17)) {
rc = osmo_macaddr_parse(ud->mac_addr, (char *)
@@ -388,7 +397,7 @@ struct msgb *ipa_ccm_make_id_resp(const struct ipaccess_unit *dev,
tag = msgb_put(msg, 3 + strlen(str) + 1);
tag[0] = 0x00;
tag[1] = 1 + strlen(str) + 1;
- tag[2] = ies_req[1];
+ tag[2] = ies_req[i];
memcpy(tag + 3, str, strlen(str) + 1);
}
ipa_prepend_header(msg, IPAC_PROTO_IPACCESS);
@@ -412,10 +421,14 @@ struct msgb *ipa_ccm_make_id_resp_from_req(const struct ipaccess_unit *dev,
/* build a array of the IEIs */
while (len >= 2) {
uint8_t t_len, t_tag;
- len -= 2;
+ len -= 2; /* subtract the length of the two bytes read below */
t_len = *cur++;
t_tag = *cur++;
+ /* as the 'tag' is included in the length of t_len, this cannot happen */
+ if (t_len == 0)
+ break;
+
if (t_len > len + 1) {
LOGP(DLINP, LOGL_ERROR, "IPA CCM tag 0x%02x does not fit\n", t_tag);
break;
@@ -423,13 +436,14 @@ struct msgb *ipa_ccm_make_id_resp_from_req(const struct ipaccess_unit *dev,
ies[num_ies++] = t_tag;
- cur += t_len;
+ /* we need to subtract one from t_len to account for the tag */
+ cur += t_len - 1;
/* prevent any unsigned integer underflow due to somebody sending us
* messages with wrong length values */
if (len <= t_len)
- len -= t_len;
- else
len = 0;
+ else
+ len -= t_len - 1;
}
return ipa_ccm_make_id_resp(dev, ies, num_ies);
}
@@ -473,7 +487,7 @@ int ipa_ccm_rcvmsg_base(struct msgb *msg, struct osmo_fd *bfd)
case IPAC_MSGT_PING:
ret = ipa_ccm_send_pong(bfd->fd);
if (ret < 0) {
- LOGP(DLINP, LOGL_ERROR, "Cannot send PING "
+ LOGP(DLINP, LOGL_ERROR, "Cannot send PONG "
"message. Reason: %s\n", strerror(errno));
break;
}
diff --git a/src/gsm/iuup.c b/src/gsm/iuup.c
new file mode 100644
index 00000000..16a6f5e0
--- /dev/null
+++ b/src/gsm/iuup.c
@@ -0,0 +1,1064 @@
+/*! \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_ASSERT(size > IUUP_MSGB_HEADROOM_MIN_REQUIRED);
+ 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;
+ uint16_t payload_crc;
+ uint8_t rfci_cnt;
+ struct msgb *msg;
+
+ 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 = iui->config.num_subflows;
+ ihdr->ti = iui->config.IPTIs_present ? 1 : 0;
+ ihdr->spare = 0;
+
+ /* RFCI + subflow size part: */
+ rfci_cnt = 0;
+ for (i = 0; i < ARRAY_SIZE(iui->config.rfci); i++) {
+ bool last;
+ uint8_t len_size;
+ struct osmo_iuup_rfci *rfci = &iui->config.rfci[i];
+ if (!rfci->used)
+ continue;
+ rfci_cnt++;
+ last = (rfci_cnt == iui->config.num_rfci);
+
+ len_size = 1;
+ for (j = 0; j < iui->config.num_subflows; j++) {
+ if (rfci->subflow_sizes[j] > UINT8_MAX)
+ len_size = 2;
+ }
+
+ ihdr_rfci = (struct iuup_ctrl_init_rfci_hdr *)msgb_put(msg, sizeof(*ihdr_rfci) + len_size * iui->config.num_subflows);
+ ihdr_rfci->rfci = rfci->id;
+ 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 < iui->config.num_subflows; j++)
+ osmo_store16be(rfci->subflow_sizes[j], buf++);
+ } else {
+ for (j = 0; j < iui->config.num_subflows; j++)
+ ihdr_rfci->subflow_length[j] = rfci->subflow_sizes[j];
+ }
+ /* early loop termination: */
+ if (last)
+ break;
+ }
+ /* Sanity check: */
+ if (rfci_cnt != iui->config.num_rfci) {
+ LOGP(DLIUUP, LOGL_ERROR, "rfci_cnt %u != num_rfci %u\n",
+ rfci_cnt, iui->config.num_rfci);
+ msgb_free(msg);
+ return NULL;
+ }
+
+ if (iui->config.IPTIs_present) {
+ uint8_t num_bytes = (iui->config.num_rfci + 1) / 2;
+ uint8_t *buf = msgb_put(msg, num_bytes);
+ rfci_cnt = 0;
+ for (i = 0; i < ARRAY_SIZE(iui->config.rfci); i++) {
+ struct osmo_iuup_rfci *rfci = &iui->config.rfci[i];
+ if (!rfci->used)
+ continue;
+ if (!(rfci_cnt & 0x01)) /* is even: */
+ buf[rfci_cnt / 2] = (((uint8_t)rfci->IPTI) << 4);
+ else
+ buf[rfci_cnt / 2] |= (rfci->IPTI & 0x0F);
+ rfci_cnt++;
+ /* early loop termination: */
+ if (rfci_cnt == iui->config.num_rfci)
+ break;
+ }
+ }
+
+ 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;
+}
+
+static struct osmo_iuup_rnl_prim *irp_init_ind_alloc(struct osmo_iuup_instance *iui)
+{
+ struct osmo_iuup_rnl_prim *irp;
+
+ irp = osmo_iuup_rnl_prim_alloc(iui, OSMO_IUUP_RNL_STATUS, PRIM_OP_INDICATION, IUUP_MSGB_SIZE);
+ irp->u.status.procedure = IUUP_PROC_INIT;
+ irp->u.status.u.initialization.mode_version = iui->mode_version;
+ irp->u.status.u.initialization.data_pdu_type = iui->config.data_pdu_type;
+ irp->u.status.u.initialization.num_subflows = iui->config.num_subflows;
+ irp->u.status.u.initialization.num_rfci = iui->config.num_rfci;
+ irp->u.status.u.initialization.IPTIs_present = iui->config.IPTIs_present;
+ memcpy(irp->u.status.u.initialization.rfci, iui->config.rfci, sizeof(iui->config.rfci));
+ return irp;
+}
+
+/* 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;
+ default:
+ OSMO_ASSERT(0);
+ }
+
+ /* 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;
+ int i;
+ bool is_last;
+ uint16_t remote_mask, match_mask;
+ struct osmo_iuup_rnl_prim *irp;
+ 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 {
+ struct osmo_iuup_rfci *rfci = &iui->config.rfci[num_rfci];
+ uint8_t l_size_bytes = ihdr_rfci->li + 1;
+ is_last = ihdr_rfci->lri;
+ if (num_rfci >= IUUP_MAX_RFCIS) {
+ LOGPFSML(iui->fi, LOGL_NOTICE, "Initialization: Too many RFCIs received (%u)\n",
+ num_rfci);
+ err_cause = IUUP_ERR_CAUSE_UNEXPECTED_RFCI;
+ goto send_nack;
+ }
+ rfci->used = 1;
+ rfci->id = ihdr_rfci->rfci;
+ 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++) {
+ rfci->subflow_sizes[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++) {
+ rfci->subflow_sizes[i] = *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.rfci[i*2].IPTI = *buf >> 4;
+ iui->config.rfci[i*2 + 1].IPTI = *buf & 0x0f;
+ buf++;
+ }
+ iui->config.rfci[i*2].IPTI = *buf >> 4;
+ if (!(num_rfci & 0x01)) /* is even: */
+ iui->config.rfci[i*2 + 1].IPTI = *buf & 0x0f;
+ buf++;
+ itail = (struct iuup_ctrl_init_tail *)buf;
+ } else {
+ iui->config.IPTIs_present = false;
+ 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;
+
+ irp = irp_init_ind_alloc(iui);
+ iui->user_prim_cb(&irp->oph, iui->user_prim_priv);
+
+ 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);
+ }
+}
+
+/* 3GPP TS 25.415 B.2.3 "Support Mode Data Transfer Ready State" */
+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_INIT:
+ /* "In case of handover or relocation, Initialisation procedures
+ * may have to be performed and Iu UP instance may have to enter
+ * the initialisation state." */
+ itp = data;
+ if (!iuup_rx_initialization(iui, itp))
+ osmo_fsm_inst_state_chg(fi, IUUP_FSM_ST_INIT, 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;
+ }
+}
+
+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_CONFIG_REQ) |
+ S(IUUP_FSM_EVT_INIT) |
+ 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, "Header 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)
+ goto payload_crc_err;
+ 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)
+ goto payload_crc_err;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+
+payload_crc_err:
+ LOGP(DLIUUP, LOGL_NOTICE, "Payload Checksum error (pdu type %u): rx 0x%02x vs exp 0x%02x\n",
+ pdu_type, payload_crc, payload_crc_computed);
+ return -EIO;
+}
+
+/* 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/kasumi.c b/src/gsm/kasumi.c
index f93c002b..78885bdd 100644
--- a/src/gsm/kasumi.c
+++ b/src/gsm/kasumi.c
@@ -16,10 +16,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <stdint.h>
diff --git a/src/gsm/kdf.c b/src/gsm/kdf.c
new file mode 100644
index 00000000..4113aada
--- /dev/null
+++ b/src/gsm/kdf.c
@@ -0,0 +1,163 @@
+/*
+ * (C) 2021 by sysmocom s.f.m.c. GmbH
+ *
+ * Author: Eric Wild <ewild@sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <stdint.h>
+#include <string.h>
+
+#include "config.h"
+#if (USE_GNUTLS)
+#include <gnutls/gnutls.h>
+#include <gnutls/crypto.h>
+#define HMAC_FUNC(k,lk,s,sl,out) gnutls_hmac_fast(GNUTLS_MAC_SHA256,k,lk,s,sl,out)
+#else
+#include <osmocom/crypt/kdf.h>
+#define HMAC_FUNC(k,lk,s,sl,out) hmac_sha256(k,lk,s,sl,out)
+#endif
+
+#include <osmocom/core/bit32gen.h>
+#include <osmocom/crypt/kdf.h>
+
+#include "kdf/common.h"
+#include "kdf/sha256.h"
+
+
+#if (USE_GNUTLS)
+/* gnutls < 3.3.0 requires global init.
+ * gnutls >= 3.3.0 does it automatic.
+ * It doesn't hurt calling it twice,
+ * as long it's not done at the same time (threads).
+ */
+__attribute__((constructor))
+static void on_dso_load_gnutls(void)
+{
+ if (!gnutls_check_version("3.3.0"))
+ gnutls_global_init();
+}
+
+__attribute__((destructor))
+static void on_dso_unload_gnutls(void)
+{
+ if (!gnutls_check_version("3.3.0"))
+ gnutls_global_deinit();
+}
+#endif
+
+/*
+ * This file uses the generic key derivation function defined in 3GPP TS 33.220 Annex B
+ *
+ * The S parameter always consists of concatenated values FC | P0 | L0 | Pi | Li | ...
+ * with Pi = Parameter number i and Li = Length of Pi (two octets)
+ *
+ * FC is either a single octet or two octets 0xff | FC
+ * FC values ranges depend on the specification parts that use the KDF,
+ * they are defined in 3GPP TS 33.220 Annex B.2.2
+ *
+ */
+
+/*! \addtogroup kdf
+ * @{
+ * key derivation functions
+ *
+ * \file kdf.c */
+
+/* 3GPP TS 33.102 B.5 */
+void osmo_kdf_kc128(const uint8_t* ck, const uint8_t* ik, uint8_t* kc128) {
+ uint8_t k[16*2];
+ uint8_t s[1];
+ uint8_t out_tmp256[32];
+ memcpy (&k[0], ck, 16);
+ memcpy (&k[16], ik, 16);
+
+ s[0] = 0x32; // yeah, really just one FC byte..
+
+ HMAC_FUNC(k, 32, s, 1, out_tmp256);
+ memcpy(kc128, out_tmp256, 16);
+}
+
+/* 3GPP TS 33.401 A.2 */
+void osmo_kdf_kasme(const uint8_t *ck, const uint8_t *ik, const uint8_t* plmn_id,
+ const uint8_t *sqn, const uint8_t *ak, uint8_t *kasme)
+{
+ uint8_t s[14];
+ uint8_t k[16*2];
+ int i;
+
+ memcpy(&k[0], ck, 16);
+ memcpy(&k[16], ik, 16);
+
+ s[0] = 0x10;
+ memcpy(&s[1], plmn_id, 3);
+ s[4] = 0x00;
+ s[5] = 0x03;
+
+ for (i = 0; i < 6; i++)
+ s[6+i] = sqn[i] ^ ak[i];
+ s[12] = 0x00;
+ s[13] = 0x06;
+
+ HMAC_FUNC(k, 32, s, 14, kasme);
+}
+
+/* 3GPP TS 33.401 A.3 */
+void osmo_kdf_enb(const uint8_t *kasme, uint32_t ul_count, uint8_t *kenb)
+{
+ uint8_t s[7];
+
+ s[0] = 0x11;
+ osmo_store32be(ul_count, &s[1]);
+ s[5] = 0x00;
+ s[6] = 0x04;
+
+ HMAC_FUNC(kasme, 32, s, 7, kenb);
+}
+
+/* 3GPP TS 33.401 A.4 */
+void osmo_kdf_nh(const uint8_t *kasme, const uint8_t *sync_input, uint8_t *nh)
+{
+ uint8_t s[35];
+
+ s[0] = 0x12;
+ memcpy(s+1, sync_input, 32);
+ s[33] = 0x00;
+ s[34] = 0x20;
+
+ HMAC_FUNC(kasme, 32, s, 35, nh);
+}
+
+/* 3GPP TS 33.401 A.7 */
+void osmo_kdf_nas(uint8_t algo_type, uint8_t algo_id, const uint8_t *kasme, uint8_t *knas)
+{
+ uint8_t s[7];
+ uint8_t out[32];
+
+ s[0] = 0x15;
+ s[1] = algo_type;
+ s[2] = 0x00;
+ s[3] = 0x01;
+ s[4] = algo_id;
+ s[5] = 0x00;
+ s[6] = 0x01;
+
+ HMAC_FUNC(kasme, 32, s, 7, out);
+ memcpy(knas, out+16, 16);
+}
+
+/*! @} */
diff --git a/src/gsm/kdf/common.h b/src/gsm/kdf/common.h
new file mode 100644
index 00000000..c1ef17ef
--- /dev/null
+++ b/src/gsm/kdf/common.h
@@ -0,0 +1,101 @@
+#pragma once
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define CONFIG_CRYPTO_INTERNAL
+#define TEST_FAIL() 0
+
+#define MSG_DEBUG
+#define wpa_hexdump(x, args...)
+#define wpa_hexdump_key(x, args...)
+#define wpa_printf(x, args...)
+
+#define os_memcpy(x, y, z) memcpy(x, y, z)
+#define os_memcmp(x, y, z) memcmp(x, y, z)
+#define os_memset(x, y, z) memset(x, y, z)
+#define os_malloc(x) malloc(x)
+#define os_free(x) free(x)
+#define os_strlen(x) strlen(x)
+
+#define forced_memzero(ptr, len) memset(ptr, 0, len);
+
+typedef uint64_t u64;
+typedef uint32_t u32;
+typedef uint16_t u16;
+typedef uint8_t u8;
+typedef int64_t s64;
+typedef int32_t s32;
+typedef int16_t s16;
+typedef int8_t s8;
+
+
+/* Macros for handling unaligned memory accesses */
+
+#define WPA_GET_BE16(a) ((u16) (((a)[0] << 8) | (a)[1]))
+#define WPA_PUT_BE16(a, val) \
+ do { \
+ (a)[0] = ((u16) (val)) >> 8; \
+ (a)[1] = ((u16) (val)) & 0xff; \
+ } while (0)
+
+#define WPA_GET_LE16(a) ((u16) (((a)[1] << 8) | (a)[0]))
+#define WPA_PUT_LE16(a, val) \
+ do { \
+ (a)[1] = ((u16) (val)) >> 8; \
+ (a)[0] = ((u16) (val)) & 0xff; \
+ } while (0)
+
+#define WPA_GET_BE24(a) ((((u32) (a)[0]) << 16) | (((u32) (a)[1]) << 8) | \
+ ((u32) (a)[2]))
+#define WPA_PUT_BE24(a, val) \
+ do { \
+ (a)[0] = (u8) ((((u32) (val)) >> 16) & 0xff); \
+ (a)[1] = (u8) ((((u32) (val)) >> 8) & 0xff); \
+ (a)[2] = (u8) (((u32) (val)) & 0xff); \
+ } while (0)
+
+#define WPA_GET_BE32(a) ((((u32) (a)[0]) << 24) | (((u32) (a)[1]) << 16) | \
+ (((u32) (a)[2]) << 8) | ((u32) (a)[3]))
+#define WPA_PUT_BE32(a, val) \
+ do { \
+ (a)[0] = (u8) ((((u32) (val)) >> 24) & 0xff); \
+ (a)[1] = (u8) ((((u32) (val)) >> 16) & 0xff); \
+ (a)[2] = (u8) ((((u32) (val)) >> 8) & 0xff); \
+ (a)[3] = (u8) (((u32) (val)) & 0xff); \
+ } while (0)
+
+#define WPA_GET_LE32(a) ((((u32) (a)[3]) << 24) | (((u32) (a)[2]) << 16) | \
+ (((u32) (a)[1]) << 8) | ((u32) (a)[0]))
+#define WPA_PUT_LE32(a, val) \
+ do { \
+ (a)[3] = (u8) ((((u32) (val)) >> 24) & 0xff); \
+ (a)[2] = (u8) ((((u32) (val)) >> 16) & 0xff); \
+ (a)[1] = (u8) ((((u32) (val)) >> 8) & 0xff); \
+ (a)[0] = (u8) (((u32) (val)) & 0xff); \
+ } while (0)
+
+#define WPA_GET_BE64(a) ((((u64) (a)[0]) << 56) | (((u64) (a)[1]) << 48) | \
+ (((u64) (a)[2]) << 40) | (((u64) (a)[3]) << 32) | \
+ (((u64) (a)[4]) << 24) | (((u64) (a)[5]) << 16) | \
+ (((u64) (a)[6]) << 8) | ((u64) (a)[7]))
+#define WPA_PUT_BE64(a, val) \
+ do { \
+ (a)[0] = (u8) (((u64) (val)) >> 56); \
+ (a)[1] = (u8) (((u64) (val)) >> 48); \
+ (a)[2] = (u8) (((u64) (val)) >> 40); \
+ (a)[3] = (u8) (((u64) (val)) >> 32); \
+ (a)[4] = (u8) (((u64) (val)) >> 24); \
+ (a)[5] = (u8) (((u64) (val)) >> 16); \
+ (a)[6] = (u8) (((u64) (val)) >> 8); \
+ (a)[7] = (u8) (((u64) (val)) & 0xff); \
+ } while (0)
+
+#define WPA_GET_LE64(a) ((((u64) (a)[7]) << 56) | (((u64) (a)[6]) << 48) | \
+ (((u64) (a)[5]) << 40) | (((u64) (a)[4]) << 32) | \
+ (((u64) (a)[3]) << 24) | (((u64) (a)[2]) << 16) | \
+ (((u64) (a)[1]) << 8) | ((u64) (a)[0]))
+
+
+#define __must_check
diff --git a/src/gsm/kdf/crypto.h b/src/gsm/kdf/crypto.h
new file mode 100644
index 00000000..6dca191c
--- /dev/null
+++ b/src/gsm/kdf/crypto.h
@@ -0,0 +1,470 @@
+/*
+ * WPA Supplicant / wrapper functions for crypto libraries
+ * Copyright (c) 2004-2009, Jouni Malinen <j@w1.fi>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Alternatively, this software may be distributed under the terms of BSD
+ * license.
+ *
+ * See README and COPYING for more details.
+ *
+ * This file defines the cryptographic functions that need to be implemented
+ * for wpa_supplicant and hostapd. When TLS is not used, internal
+ * implementation of MD5, SHA1, and AES is used and no external libraries are
+ * required. When TLS is enabled (e.g., by enabling EAP-TLS or EAP-PEAP), the
+ * crypto library used by the TLS implementation is expected to be used for
+ * non-TLS needs, too, in order to save space by not implementing these
+ * functions twice.
+ *
+ * Wrapper code for using each crypto library is in its own file (crypto*.c)
+ * and one of these files is build and linked in to provide the functions
+ * defined here.
+ */
+
+#ifndef CRYPTO_H
+#define CRYPTO_H
+
+/**
+ * md4_vector - MD4 hash for data vector
+ * @num_elem: Number of elements in the data vector
+ * @addr: Pointers to the data areas
+ * @len: Lengths of the data blocks
+ * @mac: Buffer for the hash
+ * Returns: 0 on success, -1 on failure
+ */
+int md4_vector(size_t num_elem, const u8 *addr[], const size_t *len, u8 *mac);
+
+/**
+ * md5_vector - MD5 hash for data vector
+ * @num_elem: Number of elements in the data vector
+ * @addr: Pointers to the data areas
+ * @len: Lengths of the data blocks
+ * @mac: Buffer for the hash
+ * Returns: 0 on success, -1 on failure
+ */
+int md5_vector(size_t num_elem, const u8 *addr[], const size_t *len, u8 *mac);
+
+#ifdef CONFIG_FIPS
+/**
+ * md5_vector_non_fips_allow - MD5 hash for data vector (non-FIPS use allowed)
+ * @num_elem: Number of elements in the data vector
+ * @addr: Pointers to the data areas
+ * @len: Lengths of the data blocks
+ * @mac: Buffer for the hash
+ * Returns: 0 on success, -1 on failure
+ */
+int md5_vector_non_fips_allow(size_t num_elem, const u8 *addr[],
+ const size_t *len, u8 *mac);
+#else /* CONFIG_FIPS */
+#define md5_vector_non_fips_allow md5_vector
+#endif /* CONFIG_FIPS */
+
+
+/**
+ * sha1_vector - SHA-1 hash for data vector
+ * @num_elem: Number of elements in the data vector
+ * @addr: Pointers to the data areas
+ * @len: Lengths of the data blocks
+ * @mac: Buffer for the hash
+ * Returns: 0 on success, -1 on failure
+ */
+int sha1_vector(size_t num_elem, const u8 *addr[], const size_t *len,
+ u8 *mac);
+
+/**
+ * fips186_2-prf - NIST FIPS Publication 186-2 change notice 1 PRF
+ * @seed: Seed/key for the PRF
+ * @seed_len: Seed length in bytes
+ * @x: Buffer for PRF output
+ * @xlen: Output length in bytes
+ * Returns: 0 on success, -1 on failure
+ *
+ * This function implements random number generation specified in NIST FIPS
+ * Publication 186-2 for EAP-SIM. This PRF uses a function that is similar to
+ * SHA-1, but has different message padding.
+ */
+int __must_check fips186_2_prf(const u8 *seed, size_t seed_len, u8 *x,
+ size_t xlen);
+
+/**
+ * sha256_vector - SHA256 hash for data vector
+ * @num_elem: Number of elements in the data vector
+ * @addr: Pointers to the data areas
+ * @len: Lengths of the data blocks
+ * @mac: Buffer for the hash
+ * Returns: 0 on success, -1 on failure
+ */
+int sha256_vector(size_t num_elem, const u8 *addr[], const size_t *len,
+ u8 *mac);
+
+/**
+ * des_encrypt - Encrypt one block with DES
+ * @clear: 8 octets (in)
+ * @key: 7 octets (in) (no parity bits included)
+ * @cypher: 8 octets (out)
+ */
+void des_encrypt(const u8 *clear, const u8 *key, u8 *cypher);
+
+/**
+ * aes_encrypt_init - Initialize AES for encryption
+ * @key: Encryption key
+ * @len: Key length in bytes (usually 16, i.e., 128 bits)
+ * Returns: Pointer to context data or %NULL on failure
+ */
+void * aes_encrypt_init(const u8 *key, size_t len);
+
+/**
+ * aes_encrypt - Encrypt one AES block
+ * @ctx: Context pointer from aes_encrypt_init()
+ * @plain: Plaintext data to be encrypted (16 bytes)
+ * @crypt: Buffer for the encrypted data (16 bytes)
+ */
+void aes_encrypt(void *ctx, const u8 *plain, u8 *crypt);
+
+/**
+ * aes_encrypt_deinit - Deinitialize AES encryption
+ * @ctx: Context pointer from aes_encrypt_init()
+ */
+void aes_encrypt_deinit(void *ctx);
+
+/**
+ * aes_decrypt_init - Initialize AES for decryption
+ * @key: Decryption key
+ * @len: Key length in bytes (usually 16, i.e., 128 bits)
+ * Returns: Pointer to context data or %NULL on failure
+ */
+void * aes_decrypt_init(const u8 *key, size_t len);
+
+/**
+ * aes_decrypt - Decrypt one AES block
+ * @ctx: Context pointer from aes_encrypt_init()
+ * @crypt: Encrypted data (16 bytes)
+ * @plain: Buffer for the decrypted data (16 bytes)
+ */
+void aes_decrypt(void *ctx, const u8 *crypt, u8 *plain);
+
+/**
+ * aes_decrypt_deinit - Deinitialize AES decryption
+ * @ctx: Context pointer from aes_encrypt_init()
+ */
+void aes_decrypt_deinit(void *ctx);
+
+
+enum crypto_hash_alg {
+ CRYPTO_HASH_ALG_MD5, CRYPTO_HASH_ALG_SHA1,
+ CRYPTO_HASH_ALG_HMAC_MD5, CRYPTO_HASH_ALG_HMAC_SHA1,
+ CRYPTO_HASH_ALG_SHA256, CRYPTO_HASH_ALG_HMAC_SHA256
+};
+
+struct crypto_hash;
+
+/**
+ * crypto_hash_init - Initialize hash/HMAC function
+ * @alg: Hash algorithm
+ * @key: Key for keyed hash (e.g., HMAC) or %NULL if not needed
+ * @key_len: Length of the key in bytes
+ * Returns: Pointer to hash context to use with other hash functions or %NULL
+ * on failure
+ *
+ * This function is only used with internal TLSv1 implementation
+ * (CONFIG_TLS=internal). If that is not used, the crypto wrapper does not need
+ * to implement this.
+ */
+struct crypto_hash * crypto_hash_init(enum crypto_hash_alg alg, const u8 *key,
+ size_t key_len);
+
+/**
+ * crypto_hash_update - Add data to hash calculation
+ * @ctx: Context pointer from crypto_hash_init()
+ * @data: Data buffer to add
+ * @len: Length of the buffer
+ *
+ * This function is only used with internal TLSv1 implementation
+ * (CONFIG_TLS=internal). If that is not used, the crypto wrapper does not need
+ * to implement this.
+ */
+void crypto_hash_update(struct crypto_hash *ctx, const u8 *data, size_t len);
+
+/**
+ * crypto_hash_finish - Complete hash calculation
+ * @ctx: Context pointer from crypto_hash_init()
+ * @hash: Buffer for hash value or %NULL if caller is just freeing the hash
+ * context
+ * @len: Pointer to length of the buffer or %NULL if caller is just freeing the
+ * hash context; on return, this is set to the actual length of the hash value
+ * Returns: 0 on success, -1 if buffer is too small (len set to needed length),
+ * or -2 on other failures (including failed crypto_hash_update() operations)
+ *
+ * This function calculates the hash value and frees the context buffer that
+ * was used for hash calculation.
+ *
+ * This function is only used with internal TLSv1 implementation
+ * (CONFIG_TLS=internal). If that is not used, the crypto wrapper does not need
+ * to implement this.
+ */
+int crypto_hash_finish(struct crypto_hash *ctx, u8 *hash, size_t *len);
+
+
+enum crypto_cipher_alg {
+ CRYPTO_CIPHER_NULL = 0, CRYPTO_CIPHER_ALG_AES, CRYPTO_CIPHER_ALG_3DES,
+ CRYPTO_CIPHER_ALG_DES, CRYPTO_CIPHER_ALG_RC2, CRYPTO_CIPHER_ALG_RC4
+};
+
+struct crypto_cipher;
+
+/**
+ * crypto_cipher_init - Initialize block/stream cipher function
+ * @alg: Cipher algorithm
+ * @iv: Initialization vector for block ciphers or %NULL for stream ciphers
+ * @key: Cipher key
+ * @key_len: Length of key in bytes
+ * Returns: Pointer to cipher context to use with other cipher functions or
+ * %NULL on failure
+ *
+ * This function is only used with internal TLSv1 implementation
+ * (CONFIG_TLS=internal). If that is not used, the crypto wrapper does not need
+ * to implement this.
+ */
+struct crypto_cipher * crypto_cipher_init(enum crypto_cipher_alg alg,
+ const u8 *iv, const u8 *key,
+ size_t key_len);
+
+/**
+ * crypto_cipher_encrypt - Cipher encrypt
+ * @ctx: Context pointer from crypto_cipher_init()
+ * @plain: Plaintext to cipher
+ * @crypt: Resulting ciphertext
+ * @len: Length of the plaintext
+ * Returns: 0 on success, -1 on failure
+ *
+ * This function is only used with internal TLSv1 implementation
+ * (CONFIG_TLS=internal). If that is not used, the crypto wrapper does not need
+ * to implement this.
+ */
+int __must_check crypto_cipher_encrypt(struct crypto_cipher *ctx,
+ const u8 *plain, u8 *crypt, size_t len);
+
+/**
+ * crypto_cipher_decrypt - Cipher decrypt
+ * @ctx: Context pointer from crypto_cipher_init()
+ * @crypt: Ciphertext to decrypt
+ * @plain: Resulting plaintext
+ * @len: Length of the cipher text
+ * Returns: 0 on success, -1 on failure
+ *
+ * This function is only used with internal TLSv1 implementation
+ * (CONFIG_TLS=internal). If that is not used, the crypto wrapper does not need
+ * to implement this.
+ */
+int __must_check crypto_cipher_decrypt(struct crypto_cipher *ctx,
+ const u8 *crypt, u8 *plain, size_t len);
+
+/**
+ * crypto_cipher_decrypt - Free cipher context
+ * @ctx: Context pointer from crypto_cipher_init()
+ *
+ * This function is only used with internal TLSv1 implementation
+ * (CONFIG_TLS=internal). If that is not used, the crypto wrapper does not need
+ * to implement this.
+ */
+void crypto_cipher_deinit(struct crypto_cipher *ctx);
+
+
+struct crypto_public_key;
+struct crypto_private_key;
+
+/**
+ * crypto_public_key_import - Import an RSA public key
+ * @key: Key buffer (DER encoded RSA public key)
+ * @len: Key buffer length in bytes
+ * Returns: Pointer to the public key or %NULL on failure
+ *
+ * This function can just return %NULL if the crypto library supports X.509
+ * parsing. In that case, crypto_public_key_from_cert() is used to import the
+ * public key from a certificate.
+ *
+ * This function is only used with internal TLSv1 implementation
+ * (CONFIG_TLS=internal). If that is not used, the crypto wrapper does not need
+ * to implement this.
+ */
+struct crypto_public_key * crypto_public_key_import(const u8 *key, size_t len);
+
+/**
+ * crypto_private_key_import - Import an RSA private key
+ * @key: Key buffer (DER encoded RSA private key)
+ * @len: Key buffer length in bytes
+ * @passwd: Key encryption password or %NULL if key is not encrypted
+ * Returns: Pointer to the private key or %NULL on failure
+ *
+ * This function is only used with internal TLSv1 implementation
+ * (CONFIG_TLS=internal). If that is not used, the crypto wrapper does not need
+ * to implement this.
+ */
+struct crypto_private_key * crypto_private_key_import(const u8 *key,
+ size_t len,
+ const char *passwd);
+
+/**
+ * crypto_public_key_from_cert - Import an RSA public key from a certificate
+ * @buf: DER encoded X.509 certificate
+ * @len: Certificate buffer length in bytes
+ * Returns: Pointer to public key or %NULL on failure
+ *
+ * This function can just return %NULL if the crypto library does not support
+ * X.509 parsing. In that case, internal code will be used to parse the
+ * certificate and public key is imported using crypto_public_key_import().
+ *
+ * This function is only used with internal TLSv1 implementation
+ * (CONFIG_TLS=internal). If that is not used, the crypto wrapper does not need
+ * to implement this.
+ */
+struct crypto_public_key * crypto_public_key_from_cert(const u8 *buf,
+ size_t len);
+
+/**
+ * crypto_public_key_encrypt_pkcs1_v15 - Public key encryption (PKCS #1 v1.5)
+ * @key: Public key
+ * @in: Plaintext buffer
+ * @inlen: Length of plaintext buffer in bytes
+ * @out: Output buffer for encrypted data
+ * @outlen: Length of output buffer in bytes; set to used length on success
+ * Returns: 0 on success, -1 on failure
+ *
+ * This function is only used with internal TLSv1 implementation
+ * (CONFIG_TLS=internal). If that is not used, the crypto wrapper does not need
+ * to implement this.
+ */
+int __must_check crypto_public_key_encrypt_pkcs1_v15(
+ struct crypto_public_key *key, const u8 *in, size_t inlen,
+ u8 *out, size_t *outlen);
+
+/**
+ * crypto_private_key_decrypt_pkcs1_v15 - Private key decryption (PKCS #1 v1.5)
+ * @key: Private key
+ * @in: Encrypted buffer
+ * @inlen: Length of encrypted buffer in bytes
+ * @out: Output buffer for encrypted data
+ * @outlen: Length of output buffer in bytes; set to used length on success
+ * Returns: 0 on success, -1 on failure
+ *
+ * This function is only used with internal TLSv1 implementation
+ * (CONFIG_TLS=internal). If that is not used, the crypto wrapper does not need
+ * to implement this.
+ */
+int __must_check crypto_private_key_decrypt_pkcs1_v15(
+ struct crypto_private_key *key, const u8 *in, size_t inlen,
+ u8 *out, size_t *outlen);
+
+/**
+ * crypto_private_key_sign_pkcs1 - Sign with private key (PKCS #1)
+ * @key: Private key from crypto_private_key_import()
+ * @in: Plaintext buffer
+ * @inlen: Length of plaintext buffer in bytes
+ * @out: Output buffer for encrypted (signed) data
+ * @outlen: Length of output buffer in bytes; set to used length on success
+ * Returns: 0 on success, -1 on failure
+ *
+ * This function is only used with internal TLSv1 implementation
+ * (CONFIG_TLS=internal). If that is not used, the crypto wrapper does not need
+ * to implement this.
+ */
+int __must_check crypto_private_key_sign_pkcs1(struct crypto_private_key *key,
+ const u8 *in, size_t inlen,
+ u8 *out, size_t *outlen);
+
+/**
+ * crypto_public_key_free - Free public key
+ * @key: Public key
+ *
+ * This function is only used with internal TLSv1 implementation
+ * (CONFIG_TLS=internal). If that is not used, the crypto wrapper does not need
+ * to implement this.
+ */
+void crypto_public_key_free(struct crypto_public_key *key);
+
+/**
+ * crypto_private_key_free - Free private key
+ * @key: Private key from crypto_private_key_import()
+ *
+ * This function is only used with internal TLSv1 implementation
+ * (CONFIG_TLS=internal). If that is not used, the crypto wrapper does not need
+ * to implement this.
+ */
+void crypto_private_key_free(struct crypto_private_key *key);
+
+/**
+ * crypto_public_key_decrypt_pkcs1 - Decrypt PKCS #1 signature
+ * @key: Public key
+ * @crypt: Encrypted signature data (using the private key)
+ * @crypt_len: Encrypted signature data length
+ * @plain: Buffer for plaintext (at least crypt_len bytes)
+ * @plain_len: Plaintext length (max buffer size on input, real len on output);
+ * Returns: 0 on success, -1 on failure
+ */
+int __must_check crypto_public_key_decrypt_pkcs1(
+ struct crypto_public_key *key, const u8 *crypt, size_t crypt_len,
+ u8 *plain, size_t *plain_len);
+
+/**
+ * crypto_global_init - Initialize crypto wrapper
+ *
+ * This function is only used with internal TLSv1 implementation
+ * (CONFIG_TLS=internal). If that is not used, the crypto wrapper does not need
+ * to implement this.
+ */
+int __must_check crypto_global_init(void);
+
+/**
+ * crypto_global_deinit - Deinitialize crypto wrapper
+ *
+ * This function is only used with internal TLSv1 implementation
+ * (CONFIG_TLS=internal). If that is not used, the crypto wrapper does not need
+ * to implement this.
+ */
+void crypto_global_deinit(void);
+
+/**
+ * crypto_mod_exp - Modular exponentiation of large integers
+ * @base: Base integer (big endian byte array)
+ * @base_len: Length of base integer in bytes
+ * @power: Power integer (big endian byte array)
+ * @power_len: Length of power integer in bytes
+ * @modulus: Modulus integer (big endian byte array)
+ * @modulus_len: Length of modulus integer in bytes
+ * @result: Buffer for the result
+ * @result_len: Result length (max buffer size on input, real len on output)
+ * Returns: 0 on success, -1 on failure
+ *
+ * This function calculates result = base ^ power mod modulus. modules_len is
+ * used as the maximum size of modulus buffer. It is set to the used size on
+ * success.
+ *
+ * This function is only used with internal TLSv1 implementation
+ * (CONFIG_TLS=internal). If that is not used, the crypto wrapper does not need
+ * to implement this.
+ */
+int __must_check crypto_mod_exp(const u8 *base, size_t base_len,
+ const u8 *power, size_t power_len,
+ const u8 *modulus, size_t modulus_len,
+ u8 *result, size_t *result_len);
+
+/**
+ * rc4_skip - XOR RC4 stream to given data with skip-stream-start
+ * @key: RC4 key
+ * @keylen: RC4 key length
+ * @skip: number of bytes to skip from the beginning of the RC4 stream
+ * @data: data to be XOR'ed with RC4 stream
+ * @data_len: buf length
+ * Returns: 0 on success, -1 on failure
+ *
+ * Generate RC4 pseudo random stream for the given key, skip beginning of the
+ * stream, and XOR the end result with the data buffer to perform RC4
+ * encryption/decryption.
+ */
+int rc4_skip(const u8 *key, size_t keylen, size_t skip,
+ u8 *data, size_t data_len);
+
+#endif /* CRYPTO_H */
diff --git a/src/gsm/kdf/sha1-internal.c b/src/gsm/kdf/sha1-internal.c
new file mode 100644
index 00000000..2216b439
--- /dev/null
+++ b/src/gsm/kdf/sha1-internal.c
@@ -0,0 +1,307 @@
+/*
+ * SHA1 hash implementation and interface functions
+ * Copyright (c) 2003-2005, Jouni Malinen <j@w1.fi>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Alternatively, this software may be distributed under the terms of BSD
+ * license.
+ *
+ * See README and COPYING for more details.
+ */
+
+
+#include "common.h"
+#include "sha1.h"
+#include "sha1_i.h"
+//#include "md5.h"
+#include "crypto.h"
+
+typedef struct SHA1Context SHA1_CTX;
+
+void SHA1Transform(u32 state[5], const unsigned char buffer[64]);
+
+
+/**
+ * sha1_vector - SHA-1 hash for data vector
+ * @num_elem: Number of elements in the data vector
+ * @addr: Pointers to the data areas
+ * @len: Lengths of the data blocks
+ * @mac: Buffer for the hash
+ * Returns: 0 on success, -1 of failure
+ */
+int sha1_vector(size_t num_elem, const u8 *addr[], const size_t *len, u8 *mac)
+{
+ SHA1_CTX ctx;
+ size_t i;
+
+ SHA1Init(&ctx);
+ for (i = 0; i < num_elem; i++)
+ SHA1Update(&ctx, addr[i], len[i]);
+ SHA1Final(mac, &ctx);
+ return 0;
+}
+
+
+/* ===== start - public domain SHA1 implementation ===== */
+
+/*
+SHA-1 in C
+By Steve Reid <sreid@sea-to-sky.net>
+100% Public Domain
+
+-----------------
+Modified 7/98
+By James H. Brown <jbrown@burgoyne.com>
+Still 100% Public Domain
+
+Corrected a problem which generated improper hash values on 16 bit machines
+Routine SHA1Update changed from
+ void SHA1Update(SHA1_CTX* context, unsigned char* data, unsigned int
+len)
+to
+ void SHA1Update(SHA1_CTX* context, unsigned char* data, unsigned
+long len)
+
+The 'len' parameter was declared an int which works fine on 32 bit machines.
+However, on 16 bit machines an int is too small for the shifts being done
+against
+it. This caused the hash function to generate incorrect values if len was
+greater than 8191 (8K - 1) due to the 'len << 3' on line 3 of SHA1Update().
+
+Since the file IO in main() reads 16K at a time, any file 8K or larger would
+be guaranteed to generate the wrong hash (e.g. Test Vector #3, a million
+"a"s).
+
+I also changed the declaration of variables i & j in SHA1Update to
+unsigned long from unsigned int for the same reason.
+
+These changes should make no difference to any 32 bit implementations since
+an
+int and a long are the same size in those environments.
+
+--
+I also corrected a few compiler warnings generated by Borland C.
+1. Added #include <process.h> for exit() prototype
+2. Removed unused variable 'j' in SHA1Final
+3. Changed exit(0) to return(0) at end of main.
+
+ALL changes I made can be located by searching for comments containing 'JHB'
+-----------------
+Modified 8/98
+By Steve Reid <sreid@sea-to-sky.net>
+Still 100% public domain
+
+1- Removed #include <process.h> and used return() instead of exit()
+2- Fixed overwriting of finalcount in SHA1Final() (discovered by Chris Hall)
+3- Changed email address from steve@edmweb.com to sreid@sea-to-sky.net
+
+-----------------
+Modified 4/01
+By Saul Kravitz <Saul.Kravitz@celera.com>
+Still 100% PD
+Modified to run on Compaq Alpha hardware.
+
+-----------------
+Modified 4/01
+By Jouni Malinen <j@w1.fi>
+Minor changes to match the coding style used in Dynamics.
+
+Modified September 24, 2004
+By Jouni Malinen <j@w1.fi>
+Fixed alignment issue in SHA1Transform when SHA1HANDSOFF is defined.
+
+*/
+
+/*
+Test Vectors (from FIPS PUB 180-1)
+"abc"
+ A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D
+"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"
+ 84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1
+A million repetitions of "a"
+ 34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F
+*/
+
+#define SHA1HANDSOFF
+
+#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits))))
+
+/* blk0() and blk() perform the initial expand. */
+/* I got the idea of expanding during the round function from SSLeay */
+#ifndef WORDS_BIGENDIAN
+#define blk0(i) (block->l[i] = (rol(block->l[i], 24) & 0xFF00FF00) | \
+ (rol(block->l[i], 8) & 0x00FF00FF))
+#else
+#define blk0(i) block->l[i]
+#endif
+#define blk(i) (block->l[i & 15] = rol(block->l[(i + 13) & 15] ^ \
+ block->l[(i + 8) & 15] ^ block->l[(i + 2) & 15] ^ block->l[i & 15], 1))
+
+/* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */
+#define R0(v,w,x,y,z,i) \
+ z += ((w & (x ^ y)) ^ y) + blk0(i) + 0x5A827999 + rol(v, 5); \
+ w = rol(w, 30);
+#define R1(v,w,x,y,z,i) \
+ z += ((w & (x ^ y)) ^ y) + blk(i) + 0x5A827999 + rol(v, 5); \
+ w = rol(w, 30);
+#define R2(v,w,x,y,z,i) \
+ z += (w ^ x ^ y) + blk(i) + 0x6ED9EBA1 + rol(v, 5); w = rol(w, 30);
+#define R3(v,w,x,y,z,i) \
+ z += (((w | x) & y) | (w & x)) + blk(i) + 0x8F1BBCDC + rol(v, 5); \
+ w = rol(w, 30);
+#define R4(v,w,x,y,z,i) \
+ z += (w ^ x ^ y) + blk(i) + 0xCA62C1D6 + rol(v, 5); \
+ w=rol(w, 30);
+
+
+#ifdef VERBOSE /* SAK */
+void SHAPrintContext(SHA1_CTX *context, char *msg)
+{
+ printf("%s (%d,%d) %x %x %x %x %x\n",
+ msg,
+ context->count[0], context->count[1],
+ context->state[0],
+ context->state[1],
+ context->state[2],
+ context->state[3],
+ context->state[4]);
+}
+#endif
+
+/* Hash a single 512-bit block. This is the core of the algorithm. */
+
+void SHA1Transform(u32 state[5], const unsigned char buffer[64])
+{
+ u32 a, b, c, d, e;
+ typedef union {
+ unsigned char c[64];
+ u32 l[16];
+ } CHAR64LONG16;
+ CHAR64LONG16* block;
+#ifdef SHA1HANDSOFF
+ CHAR64LONG16 workspace;
+ block = &workspace;
+ os_memcpy(block, buffer, 64);
+#else
+ block = (CHAR64LONG16 *) buffer;
+#endif
+ /* Copy context->state[] to working vars */
+ a = state[0];
+ b = state[1];
+ c = state[2];
+ d = state[3];
+ e = state[4];
+ /* 4 rounds of 20 operations each. Loop unrolled. */
+ R0(a,b,c,d,e, 0); R0(e,a,b,c,d, 1); R0(d,e,a,b,c, 2); R0(c,d,e,a,b, 3);
+ R0(b,c,d,e,a, 4); R0(a,b,c,d,e, 5); R0(e,a,b,c,d, 6); R0(d,e,a,b,c, 7);
+ R0(c,d,e,a,b, 8); R0(b,c,d,e,a, 9); R0(a,b,c,d,e,10); R0(e,a,b,c,d,11);
+ R0(d,e,a,b,c,12); R0(c,d,e,a,b,13); R0(b,c,d,e,a,14); R0(a,b,c,d,e,15);
+ R1(e,a,b,c,d,16); R1(d,e,a,b,c,17); R1(c,d,e,a,b,18); R1(b,c,d,e,a,19);
+ R2(a,b,c,d,e,20); R2(e,a,b,c,d,21); R2(d,e,a,b,c,22); R2(c,d,e,a,b,23);
+ R2(b,c,d,e,a,24); R2(a,b,c,d,e,25); R2(e,a,b,c,d,26); R2(d,e,a,b,c,27);
+ R2(c,d,e,a,b,28); R2(b,c,d,e,a,29); R2(a,b,c,d,e,30); R2(e,a,b,c,d,31);
+ R2(d,e,a,b,c,32); R2(c,d,e,a,b,33); R2(b,c,d,e,a,34); R2(a,b,c,d,e,35);
+ R2(e,a,b,c,d,36); R2(d,e,a,b,c,37); R2(c,d,e,a,b,38); R2(b,c,d,e,a,39);
+ R3(a,b,c,d,e,40); R3(e,a,b,c,d,41); R3(d,e,a,b,c,42); R3(c,d,e,a,b,43);
+ R3(b,c,d,e,a,44); R3(a,b,c,d,e,45); R3(e,a,b,c,d,46); R3(d,e,a,b,c,47);
+ R3(c,d,e,a,b,48); R3(b,c,d,e,a,49); R3(a,b,c,d,e,50); R3(e,a,b,c,d,51);
+ R3(d,e,a,b,c,52); R3(c,d,e,a,b,53); R3(b,c,d,e,a,54); R3(a,b,c,d,e,55);
+ R3(e,a,b,c,d,56); R3(d,e,a,b,c,57); R3(c,d,e,a,b,58); R3(b,c,d,e,a,59);
+ R4(a,b,c,d,e,60); R4(e,a,b,c,d,61); R4(d,e,a,b,c,62); R4(c,d,e,a,b,63);
+ R4(b,c,d,e,a,64); R4(a,b,c,d,e,65); R4(e,a,b,c,d,66); R4(d,e,a,b,c,67);
+ R4(c,d,e,a,b,68); R4(b,c,d,e,a,69); R4(a,b,c,d,e,70); R4(e,a,b,c,d,71);
+ R4(d,e,a,b,c,72); R4(c,d,e,a,b,73); R4(b,c,d,e,a,74); R4(a,b,c,d,e,75);
+ R4(e,a,b,c,d,76); R4(d,e,a,b,c,77); R4(c,d,e,a,b,78); R4(b,c,d,e,a,79);
+ /* Add the working vars back into context.state[] */
+ state[0] += a;
+ state[1] += b;
+ state[2] += c;
+ state[3] += d;
+ state[4] += e;
+ /* Wipe variables */
+ a = b = c = d = e = 0;
+#ifdef SHA1HANDSOFF
+ os_memset(block, 0, 64);
+#endif
+}
+
+
+/* SHA1Init - Initialize new context */
+
+void SHA1Init(SHA1_CTX* context)
+{
+ /* SHA1 initialization constants */
+ context->state[0] = 0x67452301;
+ context->state[1] = 0xEFCDAB89;
+ context->state[2] = 0x98BADCFE;
+ context->state[3] = 0x10325476;
+ context->state[4] = 0xC3D2E1F0;
+ context->count[0] = context->count[1] = 0;
+}
+
+
+/* Run your data through this. */
+
+void SHA1Update(SHA1_CTX* context, const void *_data, u32 len)
+{
+ u32 i, j;
+ const unsigned char *data = _data;
+
+#ifdef VERBOSE
+ SHAPrintContext(context, "before");
+#endif
+ j = (context->count[0] >> 3) & 63;
+ if ((context->count[0] += len << 3) < (len << 3))
+ context->count[1]++;
+ context->count[1] += (len >> 29);
+ if ((j + len) > 63) {
+ os_memcpy(&context->buffer[j], data, (i = 64-j));
+ SHA1Transform(context->state, context->buffer);
+ for ( ; i + 63 < len; i += 64) {
+ SHA1Transform(context->state, &data[i]);
+ }
+ j = 0;
+ }
+ else i = 0;
+ os_memcpy(&context->buffer[j], &data[i], len - i);
+#ifdef VERBOSE
+ SHAPrintContext(context, "after ");
+#endif
+}
+
+
+/* Add padding and return the message digest. */
+
+void SHA1Final(unsigned char digest[20], SHA1_CTX* context)
+{
+ u32 i;
+ unsigned char finalcount[8];
+
+ for (i = 0; i < 8; i++) {
+ finalcount[i] = (unsigned char)
+ ((context->count[(i >= 4 ? 0 : 1)] >>
+ ((3-(i & 3)) * 8) ) & 255); /* Endian independent */
+ }
+ SHA1Update(context, (unsigned char *) "\200", 1);
+ while ((context->count[0] & 504) != 448) {
+ SHA1Update(context, (unsigned char *) "\0", 1);
+ }
+ SHA1Update(context, finalcount, 8); /* Should cause a SHA1Transform()
+ */
+ for (i = 0; i < 20; i++) {
+ digest[i] = (unsigned char)
+ ((context->state[i >> 2] >> ((3 - (i & 3)) * 8)) &
+ 255);
+ }
+ /* Wipe variables */
+ i = 0;
+ os_memset(context->buffer, 0, 64);
+ os_memset(context->state, 0, 20);
+ os_memset(context->count, 0, 8);
+ os_memset(finalcount, 0, 8);
+}
+
+/* ===== end - public domain SHA1 implementation ===== */
diff --git a/src/gsm/kdf/sha1.c b/src/gsm/kdf/sha1.c
new file mode 100644
index 00000000..0d39f77b
--- /dev/null
+++ b/src/gsm/kdf/sha1.c
@@ -0,0 +1,162 @@
+/*
+ * SHA1 hash implementation and interface functions
+ * Copyright (c) 2003-2005, Jouni Malinen <j@w1.fi>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Alternatively, this software may be distributed under the terms of BSD
+ * license.
+ *
+ * See README and COPYING for more details.
+ */
+
+
+#include "common.h"
+#include "sha1.h"
+#include "crypto.h"
+
+
+/**
+ * hmac_sha1_vector - HMAC-SHA1 over data vector (RFC 2104)
+ * @key: Key for HMAC operations
+ * @key_len: Length of the key in bytes
+ * @num_elem: Number of elements in the data vector
+ * @addr: Pointers to the data areas
+ * @len: Lengths of the data blocks
+ * @mac: Buffer for the hash (20 bytes)
+ * Returns: 0 on success, -1 on failure
+ */
+int hmac_sha1_vector(const u8 *key, size_t key_len, size_t num_elem,
+ const u8 *addr[], const size_t *len, u8 *mac)
+{
+ unsigned char k_pad[64]; /* padding - key XORd with ipad/opad */
+ unsigned char tk[20];
+ const u8 *_addr[6];
+ size_t _len[6], i;
+
+ if (num_elem > 5) {
+ /*
+ * Fixed limit on the number of fragments to avoid having to
+ * allocate memory (which could fail).
+ */
+ return -1;
+ }
+
+ /* if key is longer than 64 bytes reset it to key = SHA1(key) */
+ if (key_len > 64) {
+ if (sha1_vector(1, &key, &key_len, tk))
+ return -1;
+ key = tk;
+ key_len = 20;
+ }
+
+ /* the HMAC_SHA1 transform looks like:
+ *
+ * SHA1(K XOR opad, SHA1(K XOR ipad, text))
+ *
+ * where K is an n byte key
+ * ipad is the byte 0x36 repeated 64 times
+ * opad is the byte 0x5c repeated 64 times
+ * and text is the data being protected */
+
+ /* start out by storing key in ipad */
+ os_memset(k_pad, 0, sizeof(k_pad));
+ os_memcpy(k_pad, key, key_len);
+ /* XOR key with ipad values */
+ for (i = 0; i < 64; i++)
+ k_pad[i] ^= 0x36;
+
+ /* perform inner SHA1 */
+ _addr[0] = k_pad;
+ _len[0] = 64;
+ for (i = 0; i < num_elem; i++) {
+ _addr[i + 1] = addr[i];
+ _len[i + 1] = len[i];
+ }
+ if (sha1_vector(1 + num_elem, _addr, _len, mac))
+ return -1;
+
+ os_memset(k_pad, 0, sizeof(k_pad));
+ os_memcpy(k_pad, key, key_len);
+ /* XOR key with opad values */
+ for (i = 0; i < 64; i++)
+ k_pad[i] ^= 0x5c;
+
+ /* perform outer SHA1 */
+ _addr[0] = k_pad;
+ _len[0] = 64;
+ _addr[1] = mac;
+ _len[1] = SHA1_MAC_LEN;
+ return sha1_vector(2, _addr, _len, mac);
+}
+
+
+/**
+ * hmac_sha1 - HMAC-SHA1 over data buffer (RFC 2104)
+ * @key: Key for HMAC operations
+ * @key_len: Length of the key in bytes
+ * @data: Pointers to the data area
+ * @data_len: Length of the data area
+ * @mac: Buffer for the hash (20 bytes)
+ * Returns: 0 on success, -1 of failure
+ */
+int hmac_sha1(const u8 *key, size_t key_len, const u8 *data, size_t data_len,
+ u8 *mac)
+{
+ return hmac_sha1_vector(key, key_len, 1, &data, &data_len, mac);
+}
+
+
+/**
+ * sha1_prf - SHA1-based Pseudo-Random Function (PRF) (IEEE 802.11i, 8.5.1.1)
+ * @key: Key for PRF
+ * @key_len: Length of the key in bytes
+ * @label: A unique label for each purpose of the PRF
+ * @data: Extra data to bind into the key
+ * @data_len: Length of the data
+ * @buf: Buffer for the generated pseudo-random key
+ * @buf_len: Number of bytes of key to generate
+ * Returns: 0 on success, -1 of failure
+ *
+ * This function is used to derive new, cryptographically separate keys from a
+ * given key (e.g., PMK in IEEE 802.11i).
+ */
+int sha1_prf(const u8 *key, size_t key_len, const char *label,
+ const u8 *data, size_t data_len, u8 *buf, size_t buf_len)
+{
+ u8 counter = 0;
+ size_t pos, plen;
+ u8 hash[SHA1_MAC_LEN];
+ size_t label_len = os_strlen(label) + 1;
+ const unsigned char *addr[3];
+ size_t len[3];
+
+ addr[0] = (u8 *) label;
+ len[0] = label_len;
+ addr[1] = data;
+ len[1] = data_len;
+ addr[2] = &counter;
+ len[2] = 1;
+
+ pos = 0;
+ while (pos < buf_len) {
+ plen = buf_len - pos;
+ if (plen >= SHA1_MAC_LEN) {
+ if (hmac_sha1_vector(key, key_len, 3, addr, len,
+ &buf[pos]))
+ return -1;
+ pos += SHA1_MAC_LEN;
+ } else {
+ if (hmac_sha1_vector(key, key_len, 3, addr, len,
+ hash))
+ return -1;
+ os_memcpy(&buf[pos], hash, plen);
+ break;
+ }
+ counter++;
+ }
+
+ return 0;
+}
diff --git a/src/gsm/kdf/sha1.h b/src/gsm/kdf/sha1.h
new file mode 100644
index 00000000..f0c1a5f9
--- /dev/null
+++ b/src/gsm/kdf/sha1.h
@@ -0,0 +1,33 @@
+/*
+ * SHA1 hash implementation and interface functions
+ * Copyright (c) 2003-2009, Jouni Malinen <j@w1.fi>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Alternatively, this software may be distributed under the terms of BSD
+ * license.
+ *
+ * See README and COPYING for more details.
+ */
+
+#ifndef SHA1_H
+#define SHA1_H
+
+#define SHA1_MAC_LEN 20
+
+int hmac_sha1_vector(const u8 *key, size_t key_len, size_t num_elem,
+ const u8 *addr[], const size_t *len, u8 *mac);
+int hmac_sha1(const u8 *key, size_t key_len, const u8 *data, size_t data_len,
+ u8 *mac);
+int sha1_prf(const u8 *key, size_t key_len, const char *label,
+ const u8 *data, size_t data_len, u8 *buf, size_t buf_len);
+int sha1_t_prf(const u8 *key, size_t key_len, const char *label,
+ const u8 *seed, size_t seed_len, u8 *buf, size_t buf_len);
+int __must_check tls_prf_sha1_md5(const u8 *secret, size_t secret_len,
+ const char *label, const u8 *seed,
+ size_t seed_len, u8 *out, size_t outlen);
+int pbkdf2_sha1(const char *passphrase, const char *ssid, size_t ssid_len,
+ int iterations, u8 *buf, size_t buflen);
+#endif /* SHA1_H */
diff --git a/src/gsm/kdf/sha1_i.h b/src/gsm/kdf/sha1_i.h
new file mode 100644
index 00000000..ec2f82f7
--- /dev/null
+++ b/src/gsm/kdf/sha1_i.h
@@ -0,0 +1,29 @@
+/*
+ * SHA1 internal definitions
+ * Copyright (c) 2003-2005, Jouni Malinen <j@w1.fi>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Alternatively, this software may be distributed under the terms of BSD
+ * license.
+ *
+ * See README and COPYING for more details.
+ */
+
+#ifndef SHA1_I_H
+#define SHA1_I_H
+
+struct SHA1Context {
+ u32 state[5];
+ u32 count[2];
+ unsigned char buffer[64];
+};
+
+void SHA1Init(struct SHA1Context *context);
+void SHA1Update(struct SHA1Context *context, const void *data, u32 len);
+void SHA1Final(unsigned char digest[20], struct SHA1Context *context);
+void SHA1Transform(u32 state[5], const unsigned char buffer[64]);
+
+#endif /* SHA1_I_H */
diff --git a/src/gsm/kdf/sha256-internal.c b/src/gsm/kdf/sha256-internal.c
new file mode 100644
index 00000000..18cc8f80
--- /dev/null
+++ b/src/gsm/kdf/sha256-internal.c
@@ -0,0 +1,231 @@
+/*
+ * SHA-256 hash implementation and interface functions
+ * Copyright (c) 2003-2011, Jouni Malinen <j@w1.fi>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Alternatively, this software may be distributed under the terms of BSD
+ * license.
+ *
+ * See README and COPYING for more details.
+ */
+
+
+#include "common.h"
+#include "sha256.h"
+#include "sha256_i.h"
+#include "crypto.h"
+
+
+/**
+ * sha256_vector - SHA256 hash for data vector
+ * @num_elem: Number of elements in the data vector
+ * @addr: Pointers to the data areas
+ * @len: Lengths of the data blocks
+ * @mac: Buffer for the hash
+ * Returns: 0 on success, -1 of failure
+ */
+int sha256_vector(size_t num_elem, const u8 *addr[], const size_t *len,
+ u8 *mac)
+{
+ struct sha256_state ctx;
+ size_t i;
+
+ sha256_init(&ctx);
+ for (i = 0; i < num_elem; i++)
+ if (sha256_process(&ctx, addr[i], len[i]))
+ return -1;
+ if (sha256_done(&ctx, mac))
+ return -1;
+ return 0;
+}
+
+
+/* ===== start - public domain SHA256 implementation ===== */
+
+/* This is based on SHA256 implementation in LibTomCrypt that was released into
+ * public domain by Tom St Denis. */
+
+/* the K array */
+static const unsigned long K[64] = {
+ 0x428a2f98UL, 0x71374491UL, 0xb5c0fbcfUL, 0xe9b5dba5UL, 0x3956c25bUL,
+ 0x59f111f1UL, 0x923f82a4UL, 0xab1c5ed5UL, 0xd807aa98UL, 0x12835b01UL,
+ 0x243185beUL, 0x550c7dc3UL, 0x72be5d74UL, 0x80deb1feUL, 0x9bdc06a7UL,
+ 0xc19bf174UL, 0xe49b69c1UL, 0xefbe4786UL, 0x0fc19dc6UL, 0x240ca1ccUL,
+ 0x2de92c6fUL, 0x4a7484aaUL, 0x5cb0a9dcUL, 0x76f988daUL, 0x983e5152UL,
+ 0xa831c66dUL, 0xb00327c8UL, 0xbf597fc7UL, 0xc6e00bf3UL, 0xd5a79147UL,
+ 0x06ca6351UL, 0x14292967UL, 0x27b70a85UL, 0x2e1b2138UL, 0x4d2c6dfcUL,
+ 0x53380d13UL, 0x650a7354UL, 0x766a0abbUL, 0x81c2c92eUL, 0x92722c85UL,
+ 0xa2bfe8a1UL, 0xa81a664bUL, 0xc24b8b70UL, 0xc76c51a3UL, 0xd192e819UL,
+ 0xd6990624UL, 0xf40e3585UL, 0x106aa070UL, 0x19a4c116UL, 0x1e376c08UL,
+ 0x2748774cUL, 0x34b0bcb5UL, 0x391c0cb3UL, 0x4ed8aa4aUL, 0x5b9cca4fUL,
+ 0x682e6ff3UL, 0x748f82eeUL, 0x78a5636fUL, 0x84c87814UL, 0x8cc70208UL,
+ 0x90befffaUL, 0xa4506cebUL, 0xbef9a3f7UL, 0xc67178f2UL
+};
+
+
+/* Various logical functions */
+#define RORc(x, y) \
+( ((((unsigned long) (x) & 0xFFFFFFFFUL) >> (unsigned long) ((y) & 31)) | \
+ ((unsigned long) (x) << (unsigned long) (32 - ((y) & 31)))) & 0xFFFFFFFFUL)
+#define Ch(x,y,z) (z ^ (x & (y ^ z)))
+#define Maj(x,y,z) (((x | y) & z) | (x & y))
+#define S(x, n) RORc((x), (n))
+#define R(x, n) (((x)&0xFFFFFFFFUL)>>(n))
+#define Sigma0(x) (S(x, 2) ^ S(x, 13) ^ S(x, 22))
+#define Sigma1(x) (S(x, 6) ^ S(x, 11) ^ S(x, 25))
+#define Gamma0(x) (S(x, 7) ^ S(x, 18) ^ R(x, 3))
+#define Gamma1(x) (S(x, 17) ^ S(x, 19) ^ R(x, 10))
+#ifndef MIN
+#define MIN(x, y) (((x) < (y)) ? (x) : (y))
+#endif
+
+/* compress 512-bits */
+static int sha256_compress(struct sha256_state *md, unsigned char *buf)
+{
+ u32 S[8], W[64], t0, t1;
+ u32 t;
+ int i;
+
+ /* copy state into S */
+ for (i = 0; i < 8; i++) {
+ S[i] = md->state[i];
+ }
+
+ /* copy the state into 512-bits into W[0..15] */
+ for (i = 0; i < 16; i++)
+ W[i] = WPA_GET_BE32(buf + (4 * i));
+
+ /* fill W[16..63] */
+ for (i = 16; i < 64; i++) {
+ W[i] = Gamma1(W[i - 2]) + W[i - 7] + Gamma0(W[i - 15]) +
+ W[i - 16];
+ }
+
+ /* Compress */
+#define RND(a,b,c,d,e,f,g,h,i) \
+ t0 = h + Sigma1(e) + Ch(e, f, g) + K[i] + W[i]; \
+ t1 = Sigma0(a) + Maj(a, b, c); \
+ d += t0; \
+ h = t0 + t1;
+
+ for (i = 0; i < 64; ++i) {
+ RND(S[0], S[1], S[2], S[3], S[4], S[5], S[6], S[7], i);
+ t = S[7]; S[7] = S[6]; S[6] = S[5]; S[5] = S[4];
+ S[4] = S[3]; S[3] = S[2]; S[2] = S[1]; S[1] = S[0]; S[0] = t;
+ }
+
+ /* feedback */
+ for (i = 0; i < 8; i++) {
+ md->state[i] = md->state[i] + S[i];
+ }
+ return 0;
+}
+
+
+/* Initialize the hash state */
+void sha256_init(struct sha256_state *md)
+{
+ md->curlen = 0;
+ md->length = 0;
+ md->state[0] = 0x6A09E667UL;
+ md->state[1] = 0xBB67AE85UL;
+ md->state[2] = 0x3C6EF372UL;
+ md->state[3] = 0xA54FF53AUL;
+ md->state[4] = 0x510E527FUL;
+ md->state[5] = 0x9B05688CUL;
+ md->state[6] = 0x1F83D9ABUL;
+ md->state[7] = 0x5BE0CD19UL;
+}
+
+/**
+ Process a block of memory though the hash
+ @param md The hash state
+ @param in The data to hash
+ @param inlen The length of the data (octets)
+ @return CRYPT_OK if successful
+*/
+int sha256_process(struct sha256_state *md, const unsigned char *in,
+ unsigned long inlen)
+{
+ unsigned long n;
+
+ if (md->curlen >= sizeof(md->buf))
+ return -1;
+
+ while (inlen > 0) {
+ if (md->curlen == 0 && inlen >= SHA256_BLOCK_SIZE) {
+ if (sha256_compress(md, (unsigned char *) in) < 0)
+ return -1;
+ md->length += SHA256_BLOCK_SIZE * 8;
+ in += SHA256_BLOCK_SIZE;
+ inlen -= SHA256_BLOCK_SIZE;
+ } else {
+ n = MIN(inlen, (SHA256_BLOCK_SIZE - md->curlen));
+ os_memcpy(md->buf + md->curlen, in, n);
+ md->curlen += n;
+ in += n;
+ inlen -= n;
+ if (md->curlen == SHA256_BLOCK_SIZE) {
+ if (sha256_compress(md, md->buf) < 0)
+ return -1;
+ md->length += 8 * SHA256_BLOCK_SIZE;
+ md->curlen = 0;
+ }
+ }
+ }
+
+ return 0;
+}
+
+
+/**
+ Terminate the hash to get the digest
+ @param md The hash state
+ @param out [out] The destination of the hash (32 bytes)
+ @return CRYPT_OK if successful
+*/
+int sha256_done(struct sha256_state *md, unsigned char *out)
+{
+ int i;
+
+ if (md->curlen >= sizeof(md->buf))
+ return -1;
+
+ /* increase the length of the message */
+ md->length += md->curlen * 8;
+
+ /* append the '1' bit */
+ md->buf[md->curlen++] = (unsigned char) 0x80;
+
+ /* if the length is currently above 56 bytes we append zeros
+ * then compress. Then we can fall back to padding zeros and length
+ * encoding like normal.
+ */
+ if (md->curlen > 56) {
+ while (md->curlen < SHA256_BLOCK_SIZE) {
+ md->buf[md->curlen++] = (unsigned char) 0;
+ }
+ sha256_compress(md, md->buf);
+ md->curlen = 0;
+ }
+
+ /* pad up to 56 bytes of zeroes */
+ while (md->curlen < 56) {
+ md->buf[md->curlen++] = (unsigned char) 0;
+ }
+
+ /* store length */
+ WPA_PUT_BE64(md->buf + 56, md->length);
+ sha256_compress(md, md->buf);
+
+ /* copy output */
+ for (i = 0; i < 8; i++)
+ WPA_PUT_BE32(out + (4 * i), md->state[i]);
+
+ return 0;
+}
+
+/* ===== end - public domain SHA256 implementation ===== */
diff --git a/src/gsm/kdf/sha256.c b/src/gsm/kdf/sha256.c
new file mode 100644
index 00000000..4f8b6cc5
--- /dev/null
+++ b/src/gsm/kdf/sha256.c
@@ -0,0 +1,156 @@
+/*
+ * SHA-256 hash implementation and interface functions
+ * Copyright (c) 2003-2007, Jouni Malinen <j@w1.fi>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Alternatively, this software may be distributed under the terms of BSD
+ * license.
+ *
+ * See README and COPYING for more details.
+ */
+
+
+#include "common.h"
+#include "sha256.h"
+#include "crypto.h"
+
+
+/**
+ * hmac_sha256_vector - HMAC-SHA256 over data vector (RFC 2104)
+ * @key: Key for HMAC operations
+ * @key_len: Length of the key in bytes
+ * @num_elem: Number of elements in the data vector
+ * @addr: Pointers to the data areas
+ * @len: Lengths of the data blocks
+ * @mac: Buffer for the hash (32 bytes)
+ */
+void hmac_sha256_vector(const u8 *key, size_t key_len, size_t num_elem,
+ const u8 *addr[], const size_t *len, u8 *mac)
+{
+ unsigned char k_pad[64]; /* padding - key XORd with ipad/opad */
+ unsigned char tk[32];
+ const u8 *_addr[6];
+ size_t _len[6], i;
+
+ if (num_elem > 5) {
+ /*
+ * Fixed limit on the number of fragments to avoid having to
+ * allocate memory (which could fail).
+ */
+ return;
+ }
+
+ /* if key is longer than 64 bytes reset it to key = SHA256(key) */
+ if (key_len > 64) {
+ sha256_vector(1, &key, &key_len, tk);
+ key = tk;
+ key_len = 32;
+ }
+
+ /* the HMAC_SHA256 transform looks like:
+ *
+ * SHA256(K XOR opad, SHA256(K XOR ipad, text))
+ *
+ * where K is an n byte key
+ * ipad is the byte 0x36 repeated 64 times
+ * opad is the byte 0x5c repeated 64 times
+ * and text is the data being protected */
+
+ /* start out by storing key in ipad */
+ os_memset(k_pad, 0, sizeof(k_pad));
+ os_memcpy(k_pad, key, key_len);
+ /* XOR key with ipad values */
+ for (i = 0; i < 64; i++)
+ k_pad[i] ^= 0x36;
+
+ /* perform inner SHA256 */
+ _addr[0] = k_pad;
+ _len[0] = 64;
+ for (i = 0; i < num_elem; i++) {
+ _addr[i + 1] = addr[i];
+ _len[i + 1] = len[i];
+ }
+ sha256_vector(1 + num_elem, _addr, _len, mac);
+
+ os_memset(k_pad, 0, sizeof(k_pad));
+ os_memcpy(k_pad, key, key_len);
+ /* XOR key with opad values */
+ for (i = 0; i < 64; i++)
+ k_pad[i] ^= 0x5c;
+
+ /* perform outer SHA256 */
+ _addr[0] = k_pad;
+ _len[0] = 64;
+ _addr[1] = mac;
+ _len[1] = SHA256_MAC_LEN;
+ sha256_vector(2, _addr, _len, mac);
+}
+
+
+/**
+ * hmac_sha256 - HMAC-SHA256 over data buffer (RFC 2104)
+ * @key: Key for HMAC operations
+ * @key_len: Length of the key in bytes
+ * @data: Pointers to the data area
+ * @data_len: Length of the data area
+ * @mac: Buffer for the hash (20 bytes)
+ */
+void hmac_sha256(const u8 *key, size_t key_len, const u8 *data,
+ size_t data_len, u8 *mac)
+{
+ hmac_sha256_vector(key, key_len, 1, &data, &data_len, mac);
+}
+
+
+/**
+ * sha256_prf - SHA256-based Pseudo-Random Function (IEEE 802.11r, 8.5.1.5.2)
+ * @key: Key for PRF
+ * @key_len: Length of the key in bytes
+ * @label: A unique label for each purpose of the PRF
+ * @data: Extra data to bind into the key
+ * @data_len: Length of the data
+ * @buf: Buffer for the generated pseudo-random key
+ * @buf_len: Number of bytes of key to generate
+ *
+ * This function is used to derive new, cryptographically separate keys from a
+ * given key.
+ */
+void sha256_prf(const u8 *key, size_t key_len, const char *label,
+ const u8 *data, size_t data_len, u8 *buf, size_t buf_len)
+{
+ u16 counter = 1;
+ size_t pos, plen;
+ u8 hash[SHA256_MAC_LEN];
+ const u8 *addr[4];
+ size_t len[4];
+ u8 counter_le[2], length_le[2];
+
+ addr[0] = counter_le;
+ len[0] = 2;
+ addr[1] = (u8 *) label;
+ len[1] = os_strlen(label);
+ addr[2] = data;
+ len[2] = data_len;
+ addr[3] = length_le;
+ len[3] = sizeof(length_le);
+
+ WPA_PUT_LE16(length_le, buf_len * 8);
+ pos = 0;
+ while (pos < buf_len) {
+ plen = buf_len - pos;
+ WPA_PUT_LE16(counter_le, counter);
+ if (plen >= SHA256_MAC_LEN) {
+ hmac_sha256_vector(key, key_len, 4, addr, len,
+ &buf[pos]);
+ pos += SHA256_MAC_LEN;
+ } else {
+ hmac_sha256_vector(key, key_len, 4, addr, len, hash);
+ os_memcpy(&buf[pos], hash, plen);
+ break;
+ }
+ counter++;
+ }
+}
diff --git a/src/gsm/kdf/sha256.h b/src/gsm/kdf/sha256.h
new file mode 100644
index 00000000..b1ce6afe
--- /dev/null
+++ b/src/gsm/kdf/sha256.h
@@ -0,0 +1,30 @@
+/*
+ * SHA256 hash implementation and interface functions
+ * Copyright (c) 2003-2011, Jouni Malinen <j@w1.fi>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Alternatively, this software may be distributed under the terms of BSD
+ * license.
+ *
+ * See README and COPYING for more details.
+ */
+
+#ifndef SHA256_H
+#define SHA256_H
+
+#define SHA256_MAC_LEN 32
+
+void hmac_sha256_vector(const u8 *key, size_t key_len, size_t num_elem,
+ const u8 *addr[], const size_t *len, u8 *mac);
+void hmac_sha256(const u8 *key, size_t key_len, const u8 *data,
+ size_t data_len, u8 *mac);
+void sha256_prf(const u8 *key, size_t key_len, const char *label,
+ const u8 *data, size_t data_len, u8 *buf, size_t buf_len);
+void tls_prf_sha256(const u8 *secret, size_t secret_len,
+ const char *label, const u8 *seed, size_t seed_len,
+ u8 *out, size_t outlen);
+
+#endif /* SHA256_H */
diff --git a/src/gsm/kdf/sha256_i.h b/src/gsm/kdf/sha256_i.h
new file mode 100644
index 00000000..20ae4889
--- /dev/null
+++ b/src/gsm/kdf/sha256_i.h
@@ -0,0 +1,31 @@
+/*
+ * SHA-256 internal definitions
+ * Copyright (c) 2003-2011, Jouni Malinen <j@w1.fi>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Alternatively, this software may be distributed under the terms of BSD
+ * license.
+ *
+ * See README and COPYING for more details.
+ */
+
+#ifndef SHA256_I_H
+#define SHA256_I_H
+
+#define SHA256_BLOCK_SIZE 64
+
+struct sha256_state {
+ u64 length;
+ u32 state[8], curlen;
+ u8 buf[SHA256_BLOCK_SIZE];
+};
+
+void sha256_init(struct sha256_state *md);
+int sha256_process(struct sha256_state *md, const unsigned char *in,
+ unsigned long inlen);
+int sha256_done(struct sha256_state *md, unsigned char *out);
+
+#endif /* SHA256_I_H */
diff --git a/src/gsm/lapdm.c b/src/gsm/lapdm.c
index 8620cabe..1fc986d6 100644
--- a/src/gsm/lapdm.c
+++ b/src/gsm/lapdm.c
@@ -19,10 +19,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
/*! \addtogroup lapdm
@@ -31,6 +27,7 @@
#include <stdio.h>
#include <stdint.h>
+#include <inttypes.h>
#include <string.h>
#include <errno.h>
@@ -61,6 +58,7 @@
#define LAPDm_ADDR_SAPI(addr) (((addr) >> 2) & 0x7)
#define LAPDm_ADDR_CR(addr) (((addr) >> 1) & 0x1)
#define LAPDm_ADDR_EA(addr) ((addr) & 0x1)
+#define LAPDm_ADDR_SHORT_L2(addr) ((addr) & 0x3)
/* TS 04.06 Table 3 / Section 3.4.3 */
#define LAPDm_CTRL_I(nr, ns, p) ((((nr) & 0x7) << 5) | (((p) & 0x1) << 4) | (((ns) & 0x7) << 1))
@@ -199,9 +197,10 @@ void lapdm_entity_init3(struct lapdm_entity *le, enum lapdm_mode mode,
char name[256];
if (name_pfx) {
snprintf(name, sizeof(name), "%s[%s]", name_pfx, i == 0 ? "0" : "3");
- lapdm_dl_init(&le->datalink[i], le, t200_ms[i], n200, name);
+ lapdm_dl_init(&le->datalink[i], le, (t200_ms) ? t200_ms[i] : 0, n200, name);
} else
- lapdm_dl_init(&le->datalink[i], le, t200_ms[i], n200, NULL);
+ lapdm_dl_init(&le->datalink[i], le, (t200_ms) ? t200_ms[i] : 0, n200, NULL);
+ INIT_LLIST_HEAD(&le->datalink[i].tx_ui_queue);
}
lapdm_entity_set_mode(le, mode);
@@ -257,7 +256,7 @@ int lapdm_channel_init2(struct lapdm_channel *lc, enum lapdm_mode mode,
* \param[in] t200_ms_dcch per-SAPI array of T200 in milli-seconds for DCCH
* \param[in] t200_ms_acch per-SAPI array of T200 in milli-seconds for SACCH
* \param[in] chan_t GSM channel type (to correctly set N200)
- * \parma[in] name_pfx human-readable name (copied by function + extended with ACCH/DCCH)
+ * \param[in] name_pfx human-readable name (copied by function + extended with ACCH/DCCH)
*/
int lapdm_channel_init3(struct lapdm_channel *lc, enum lapdm_mode mode,
const int *t200_ms_dcch, const int *t200_ms_acch, enum gsm_chan_t chan_t,
@@ -298,6 +297,7 @@ void lapdm_entity_exit(struct lapdm_entity *le)
for (i = 0; i < ARRAY_SIZE(le->datalink); i++) {
dl = &le->datalink[i];
lapd_dl_exit(&dl->dl);
+ msgb_queue_free(&dl->tx_ui_queue);
}
}
@@ -361,11 +361,50 @@ static int tx_ph_data_enqueue(struct lapdm_datalink *dl, struct msgb *msg,
/* if there is a pending message, queue it */
if (le->tx_pending || le->flags & LAPDM_ENT_F_POLLING_ONLY) {
+ struct msgb *old_msg;
+
+ /* In 'RTS' mode there can be only one message. */
+ if (le->flags & LAPDM_ENT_F_RTS) {
+ /* Overwrite existing message by removing it first. */
+ if ((old_msg = msgb_dequeue(&dl->dl.tx_queue))) {
+ msgb_free(old_msg);
+ /* Reset V(S) to V(A), because there is no outstanding message now. */
+ dl->dl.v_send = dl->dl.v_ack;
+ }
+ }
+
*msgb_push(msg, 1) = pad;
*msgb_push(msg, 1) = link_id;
*msgb_push(msg, 1) = chan_nr;
msgb_enqueue(&dl->dl.tx_queue, msg);
- return -EBUSY;
+ return 0;
+ }
+
+ osmo_prim_init(&pp.oph, SAP_GSM_PH, PRIM_PH_DATA,
+ PRIM_OP_REQUEST, msg);
+ pp.u.data.chan_nr = chan_nr;
+ pp.u.data.link_id = link_id;
+
+ /* send the frame now */
+ le->tx_pending = 0; /* disabled flow control */
+ lapdm_pad_msgb(msg, pad);
+
+ return le->l1_prim_cb(&pp.oph, le->l1_ctx);
+}
+
+static int tx_ph_data_enqueue_ui(struct lapdm_datalink *dl, struct msgb *msg,
+ uint8_t chan_nr, uint8_t link_id, uint8_t pad)
+{
+ struct lapdm_entity *le = dl->entity;
+ struct osmo_phsap_prim pp;
+
+ /* if there is a pending message, queue it */
+ if (le->tx_pending || le->flags & LAPDM_ENT_F_POLLING_ONLY) {
+ *msgb_push(msg, 1) = pad;
+ *msgb_push(msg, 1) = link_id;
+ *msgb_push(msg, 1) = chan_nr;
+ msgb_enqueue(&dl->tx_ui_queue, msg);
+ return 0;
}
osmo_prim_init(&pp.oph, SAP_GSM_PH, PRIM_PH_DATA,
@@ -380,21 +419,60 @@ static int tx_ph_data_enqueue(struct lapdm_datalink *dl, struct msgb *msg,
return le->l1_prim_cb(&pp.oph, le->l1_ctx);
}
+/* Get transmit frame from queue, if any. In polling mode, indicate RTS to LAPD and start T200, if pending. */
+static struct msgb *tx_dequeue_msgb(struct lapdm_datalink *dl, uint32_t fn)
+{
+ struct msgb *msg;
+
+ /* Call RTS function of LAPD, to queue next frame. */
+ if (dl->entity->flags & LAPDM_ENT_F_RTS) {
+ struct lapd_msg_ctx lctx;
+ int rc;
+
+ /* Poll next frame. */
+ lctx.dl = &dl->dl;
+ rc = lapd_ph_rts_ind(&lctx);
+
+ /* If T200 has been started, calculate timeout FN. */
+ if (rc == 1) {
+ /* Set T200 in advance. */
+ dl->t200_timeout = fn;
+ ADD_MODULO(dl->t200_timeout, dl->t200_fn, GSM_MAX_FN);
+
+ LOGDL(&dl->dl, LOGL_INFO,
+ "T200 running from FN %"PRIu32" to FN %"PRIu32" (%"PRIu32" frames).\n",
+ fn, dl->t200_timeout, dl->t200_fn);
+ }
+ }
+
+ /* If there is no frame from LAPD, send UI frame, if any. */
+ msg = msgb_dequeue(&dl->dl.tx_queue);
+ if (msg)
+ LOGDL(&dl->dl, LOGL_INFO, "Sending frame from TX queue. (FN %"PRIu32")\n", fn);
+ else {
+ msg = msgb_dequeue(&dl->tx_ui_queue);
+ if (msg)
+ LOGDL(&dl->dl, LOGL_INFO, "Sending UI frame from TX queue. (FN %"PRIu32")\n", fn);
+ }
+ return msg;
+}
+
/* Dequeue a Downlink message for DCCH (dedicated channel) */
-static struct msgb *tx_dequeue_dcch_msgb(struct lapdm_entity *le)
+static struct msgb *tx_dequeue_dcch_msgb(struct lapdm_entity *le, uint32_t fn)
{
struct msgb *msg;
/* SAPI=0 always has higher priority than SAPI=3 */
- msg = msgb_dequeue(&le->datalink[DL_SAPI0].dl.tx_queue);
- if (msg == NULL) /* no SAPI=0 messages, dequeue SAPI=3 (if any) */
- msg = msgb_dequeue(&le->datalink[DL_SAPI3].dl.tx_queue);
+ msg = tx_dequeue_msgb(&le->datalink[DL_SAPI0], fn);
+ if (msg == NULL) { /* no SAPI=0 messages, dequeue SAPI=3 (if any) */
+ msg = tx_dequeue_msgb(&le->datalink[DL_SAPI3], fn);
+ }
return msg;
}
/* Dequeue a Downlink message for ACCH (associated channel) */
-static struct msgb *tx_dequeue_acch_msgb(struct lapdm_entity *le)
+static struct msgb *tx_dequeue_acch_msgb(struct lapdm_entity *le, uint32_t fn)
{
struct lapdm_datalink *dl;
int last = le->last_tx_dequeue;
@@ -406,7 +484,7 @@ static struct msgb *tx_dequeue_acch_msgb(struct lapdm_entity *le)
/* next */
i = (i + 1) % n;
dl = &le->datalink[i];
- if ((msg = msgb_dequeue(&dl->dl.tx_queue)))
+ if ((msg = tx_dequeue_msgb(dl, fn)))
break;
} while (i != last);
@@ -420,7 +498,7 @@ static struct msgb *tx_dequeue_acch_msgb(struct lapdm_entity *le)
/*! dequeue a msg that's pending transmission via L1 and wrap it into
* a osmo_phsap_prim */
-int lapdm_phsap_dequeue_prim(struct lapdm_entity *le, struct osmo_phsap_prim *pp)
+int lapdm_phsap_dequeue_prim_fn(struct lapdm_entity *le, struct osmo_phsap_prim *pp, uint32_t fn)
{
struct msgb *msg;
uint8_t pad;
@@ -428,9 +506,9 @@ int lapdm_phsap_dequeue_prim(struct lapdm_entity *le, struct osmo_phsap_prim *pp
/* Dequeue depending on channel type: DCCH or ACCH.
* See 3GPP TS 44.005, section 4.2.2 "Priority". */
if (le == &le->lapdm_ch->lapdm_dcch)
- msg = tx_dequeue_dcch_msgb(le);
+ msg = tx_dequeue_dcch_msgb(le, fn);
else
- msg = tx_dequeue_acch_msgb(le);
+ msg = tx_dequeue_acch_msgb(le, fn);
if (!msg)
return -ENODEV;
@@ -452,6 +530,57 @@ int lapdm_phsap_dequeue_prim(struct lapdm_entity *le, struct osmo_phsap_prim *pp
return 0;
}
+static void lapdm_t200_fn_dl(struct lapdm_datalink *dl, uint32_t fn)
+{
+ uint32_t diff;
+
+ OSMO_ASSERT((dl->dl.lapd_flags & LAPD_F_RTS));
+
+ /* If T200 is running, check if it has fired. */
+ if (dl->dl.t200_rts != LAPD_T200_RTS_RUNNING)
+ return;
+
+ /* Calculate how many frames fn is behind t200_timeout.
+ * If it is negative (>= GSM_MAX_FN / 2), we have not reached t200_timeout yet.
+ * If it is 0 or positive, we reached it or we are a bit too late, which is not a problem.
+ */
+ diff = fn;
+ ADD_MODULO(diff, GSM_MAX_FN - dl->t200_timeout, GSM_MAX_FN);
+ if (diff >= GSM_MAX_FN / 2)
+ return;
+
+ LOGDL(&dl->dl, LOGL_INFO, "T200 timeout at FN %"PRIu32", detected at FN %"PRIu32".\n", dl->t200_timeout, fn);
+
+ lapd_t200_timeout(&dl->dl);
+}
+
+/*! Get receive frame number from L1. It is used to check the T200 timeout.
+ * This function is used if LAPD is in RTS mode only. (Applies if the LAPDM_ENT_F_POLLING_ONLY flag is set.)
+ * This function must be called for every valid or invalid data frame received.
+ * The frame number fn must be the frame number of the first burst of a data frame.
+ * This function must be called after the frame is delivered to layer 2.
+ * In case of TCH, this this function must be called for every speech frame received, meaning that there was no valid
+ * data frame. */
+void lapdm_t200_fn(struct lapdm_entity *le, uint32_t fn)
+{
+ unsigned int i;
+
+ if (!(le->flags & LAPDM_ENT_F_POLLING_ONLY)) {
+ LOGP(DLLAPD, LOGL_ERROR, "Function call not allowed on timer based T200.\n");
+ return;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(le->datalink); i++)
+ lapdm_t200_fn_dl(&le->datalink[i], fn);
+}
+
+/*! dequeue a msg that's pending transmission via L1 and wrap it into
+ * a osmo_phsap_prim */
+int lapdm_phsap_dequeue_prim(struct lapdm_entity *le, struct osmo_phsap_prim *pp)
+{
+ return lapdm_phsap_dequeue_prim_fn(le, pp, 0);
+}
+
/* get next frame from the tx queue. because the ms has multiple datalinks,
* each datalink's queue is read round-robin.
*/
@@ -661,7 +790,8 @@ static int update_pending_frames(struct lapd_msg_ctx *lctx)
LAPDm_CTRL_PF_BIT(msg->l2h[1]));
rc = 0;
} else if (LAPDm_CTRL_is_S(msg->l2h[1])) {
- LOGDL(dl, LOGL_ERROR, "Supervisory frame in queue, this shouldn't happen\n");
+ msg->l2h[1] = LAPDm_CTRL_S(dl->v_recv, LAPDm_CTRL_S_BITS(msg->l2h[1]),
+ LAPDm_CTRL_PF_BIT(msg->l2h[1]));
}
}
@@ -703,7 +833,7 @@ static int lapdm_rx_not_permitted(const struct lapdm_entity *le,
/* input into layer2 (from layer 1) */
static int l2_ph_data_ind(struct msgb *msg, struct lapdm_entity *le,
- uint8_t chan_nr, uint8_t link_id)
+ uint8_t chan_nr, uint8_t link_id, uint32_t fn)
{
uint8_t cbits = chan_nr >> 3;
uint8_t sapi; /* we cannot take SAPI from link_id, as L1 has no clue */
@@ -718,6 +848,7 @@ static int l2_ph_data_ind(struct msgb *msg, struct lapdm_entity *le,
memset(&mctx, 0, sizeof(mctx));
mctx.chan_nr = chan_nr;
mctx.link_id = link_id;
+ mctx.fn = fn;
/* check for L1 chan_nr/link_id and determine LAPDm hdr format */
if (cbits == 0x10 || cbits == 0x12) {
@@ -729,15 +860,24 @@ static int l2_ph_data_ind(struct msgb *msg, struct lapdm_entity *le,
if (mctx.link_id & 0x40) {
/* It was received from network on SACCH */
- /* If UI on SACCH sent by BTS, lapdm_fmt must be B4 */
- if (le->mode == LAPDM_MODE_MS
- && LAPDm_CTRL_is_U(msg->l2h[3])
- && LAPDm_CTRL_U_BITS(msg->l2h[3]) == 0) {
+ /* A Short L3 header has both bits == 0. */
+ if (LAPDm_ADDR_SHORT_L2(msg->l2h[2]) == 0) {
+ mctx.lapdm_fmt = LAPDm_FMT_Bter;
+ n201 = N201_Bter_SACCH;
+ sapi = 0;
+ } else if (le->mode == LAPDM_MODE_MS
+ && LAPDm_CTRL_is_U(msg->l2h[3])
+ && LAPDm_CTRL_U_BITS(msg->l2h[3]) == 0) {
+ /* If UI on SACCH sent by BTS, lapdm_fmt must be B4 */
mctx.lapdm_fmt = LAPDm_FMT_B4;
n201 = N201_B4;
+ /* sapi is found after two-btyte L1 header */
+ sapi = (msg->l2h[2] >> 2) & 7;
} else {
mctx.lapdm_fmt = LAPDm_FMT_B;
n201 = N201_AB_SACCH;
+ /* sapi is found after two-btyte L1 header */
+ sapi = (msg->l2h[2] >> 2) & 7;
}
/* SACCH frames have a two-byte L1 header that
* OsmocomBB L1 doesn't strip */
@@ -745,11 +885,17 @@ static int l2_ph_data_ind(struct msgb *msg, struct lapdm_entity *le,
mctx.ta_ind = msg->l2h[1];
msgb_pull(msg, 2);
msg->l2h += 2;
- sapi = (msg->l2h[0] >> 2) & 7;
} else {
- mctx.lapdm_fmt = LAPDm_FMT_B;
- n201 = N201_AB_SDCCH;
- sapi = (msg->l2h[0] >> 2) & 7;
+ /* A Short L3 header has both bits == 0. */
+ if (LAPDm_ADDR_SHORT_L2(msg->l2h[0]) == 0) {
+ mctx.lapdm_fmt = LAPDm_FMT_Bter;
+ n201 = N201_Bter_SDCCH;
+ sapi = 0;
+ } else {
+ mctx.lapdm_fmt = LAPDm_FMT_B;
+ n201 = N201_AB_SDCCH;
+ sapi = (msg->l2h[0] >> 2) & 7;
+ }
}
}
@@ -839,13 +985,14 @@ static int l2_ph_data_ind(struct msgb *msg, struct lapdm_entity *le,
return -EINVAL;
}
/* send to LAPD */
+ LOGDL(lctx.dl, LOGL_DEBUG, "Frame received at FN %"PRIu32".\n", fn);
rc = lapd_ph_data_ind(msg, &lctx);
break;
case LAPDm_FMT_Bter:
- /* FIXME */
- msgb_free(msg);
- break;
+ /* fall-through */
case LAPDm_FMT_Bbis:
+ /* Update context so that users can read fields like fn: */
+ memcpy(&mctx.dl->mctx, &mctx, sizeof(mctx.dl->mctx));
/* directly pass up to layer3 */
msg->l3h = msg->l2h;
msgb_pull_to_l3(msg);
@@ -899,56 +1046,33 @@ int lapdm_phsap_up(struct osmo_prim_hdr *oph, struct lapdm_entity *le)
if (oph->sap != SAP_GSM_PH) {
LOGP(DLLAPD, LOGL_ERROR, "primitive for unknown SAP %u\n",
oph->sap);
- rc = -ENODEV;
- goto free;
+ msgb_free(oph->msg);
+ return -ENODEV;
}
- switch (oph->primitive) {
- case PRIM_PH_DATA:
- if (oph->operation != PRIM_OP_INDICATION) {
- LOGP(DLLAPD, LOGL_ERROR, "PH_DATA is not INDICATION %u\n",
- oph->operation);
- rc = -ENODEV;
- goto free;
- }
+ switch (OSMO_PRIM_HDR(oph)) {
+ case OSMO_PRIM(PRIM_PH_DATA, PRIM_OP_INDICATION):
rc = l2_ph_data_ind(oph->msg, le, pp->u.data.chan_nr,
- pp->u.data.link_id);
+ pp->u.data.link_id, pp->u.data.fn);
break;
- case PRIM_PH_RTS:
- if (oph->operation != PRIM_OP_INDICATION) {
- LOGP(DLLAPD, LOGL_ERROR, "PH_RTS is not INDICATION %u\n",
- oph->operation);
- rc = -ENODEV;
- goto free;
- }
+ case OSMO_PRIM(PRIM_PH_RTS, PRIM_OP_INDICATION):
rc = l2_ph_data_conf(oph->msg, le);
break;
- case PRIM_PH_RACH:
- switch (oph->operation) {
- case PRIM_OP_INDICATION:
- rc = l2_ph_rach_ind(le, pp->u.rach_ind.ra, pp->u.rach_ind.fn,
- pp->u.rach_ind.acc_delay);
- break;
- case PRIM_OP_CONFIRM:
- rc = l2_ph_chan_conf(oph->msg, le, pp->u.rach_ind.fn);
- break;
- default:
- rc = -EIO;
- goto free;
- }
+ case OSMO_PRIM(PRIM_PH_RACH, PRIM_OP_INDICATION):
+ rc = l2_ph_rach_ind(le, pp->u.rach_ind.ra, pp->u.rach_ind.fn,
+ pp->u.rach_ind.acc_delay);
+ break;
+ case OSMO_PRIM(PRIM_PH_RACH, PRIM_OP_CONFIRM):
+ rc = l2_ph_chan_conf(oph->msg, le, pp->u.rach_ind.fn);
break;
default:
LOGP(DLLAPD, LOGL_ERROR, "Unknown primitive %u\n",
oph->primitive);
- rc = -EINVAL;
- goto free;
+ msgb_free(oph->msg);
+ return -EINVAL;
}
return rc;
-
-free:
- msgb_free(oph->msg);
- return rc;
}
@@ -1034,6 +1158,7 @@ static int rslms_rx_rll_udata_req(struct msgb *msg, struct lapdm_datalink *dl)
uint8_t sapi = link_id & 7;
struct tlv_parsed tv;
int length, ui_bts;
+ bool use_b_ter;
if (!le) {
LOGDL(&dl->dl, LOGL_ERROR, "lapdm_datalink without entity error\n");
@@ -1059,8 +1184,10 @@ static int rslms_rx_rll_udata_req(struct msgb *msg, struct lapdm_datalink *dl)
}
msg->l3h = (uint8_t *) TLVP_VAL(&tv, RSL_IE_L3_INFO);
length = TLVP_LEN(&tv, RSL_IE_L3_INFO);
+ /* check for Bter frame */
+ use_b_ter = (length == ((link_id & 0x40) ? 21 : 23) && sapi == 0);
/* check if the layer3 message length exceeds N201 */
- if (length + ((link_id & 0x40) ? 4 : 2) + !ui_bts > 23) {
+ if (length + ((link_id & 0x40) ? 4 : 2) + !ui_bts > 23 && !use_b_ter) {
LOGDL(&dl->dl, LOGL_ERROR, "frame too large: %d > N201(%d) "
"(discarding)\n", length,
((link_id & 0x40) ? 18 : 20) + ui_bts);
@@ -1075,11 +1202,14 @@ static int rslms_rx_rll_udata_req(struct msgb *msg, struct lapdm_datalink *dl)
msgb_trim(msg, length);
/* Push L1 + LAPDm header on msgb */
- msg->l2h = msgb_push(msg, 2 + !ui_bts);
- msg->l2h[0] = LAPDm_ADDR(LAPDm_LPD_NORMAL, sapi, dl->dl.cr.loc2rem.cmd);
- msg->l2h[1] = LAPDm_CTRL_U(LAPDm_U_UI, 0);
- if (!ui_bts)
- msg->l2h[2] = LAPDm_LEN(length);
+ if (!use_b_ter) {
+ msg->l2h = msgb_push(msg, 2 + !ui_bts);
+ msg->l2h[0] = LAPDm_ADDR(LAPDm_LPD_NORMAL, sapi, dl->dl.cr.loc2rem.cmd);
+ msg->l2h[1] = LAPDm_CTRL_U(LAPDm_U_UI, 0);
+ if (!ui_bts)
+ msg->l2h[2] = LAPDm_LEN(length);
+ } else
+ msg->l2h = msg->data;
if (link_id & 0x40) {
msg->l2h = msgb_push(msg, 2);
msg->l2h[0] = le->tx_power;
@@ -1087,7 +1217,7 @@ static int rslms_rx_rll_udata_req(struct msgb *msg, struct lapdm_datalink *dl)
}
/* Tramsmit */
- return tx_ph_data_enqueue(dl, msg, chan_nr, link_id, 23);
+ return tx_ph_data_enqueue_ui(dl, msg, chan_nr, link_id, 23);
}
/* L3 requests transfer of acknowledged information */
@@ -1484,6 +1614,7 @@ void lapdm_entity_reset(struct lapdm_entity *le)
for (i = 0; i < ARRAY_SIZE(le->datalink); i++) {
dl = &le->datalink[i];
lapd_dl_reset(&dl->dl);
+ msgb_queue_free(&dl->tx_ui_queue);
}
}
@@ -1497,7 +1628,22 @@ void lapdm_channel_reset(struct lapdm_channel *lc)
/*! Set the flags of a LAPDm entity */
void lapdm_entity_set_flags(struct lapdm_entity *le, unsigned int flags)
{
+ unsigned int dl_flags = 0;
+ struct lapdm_datalink *dl;
+ int i;
+
le->flags = flags;
+
+ /* Set flags at LAPD. */
+ if (le->flags & LAPDM_ENT_F_RTS)
+ dl_flags |= LAPD_F_RTS;
+ if (le->flags & LAPDM_ENT_F_DROP_2ND_REJ)
+ dl_flags |= LAPD_F_DROP_2ND_REJ;
+
+ for (i = 0; i < ARRAY_SIZE(le->datalink); i++) {
+ dl = &le->datalink[i];
+ lapd_dl_set_flags(&dl->dl, dl_flags);
+ }
}
/*! Set the flags of all LAPDm entities in a LAPDm channel */
@@ -1507,4 +1653,25 @@ void lapdm_channel_set_flags(struct lapdm_channel *lc, unsigned int flags)
lapdm_entity_set_flags(&lc->lapdm_acch, flags);
}
+/*! Set the T200 FN timer of a LAPDm entity
+ * \param[in] \ref lapdm_entity
+ * \param[in] t200_fn Array of T200 timeout in frame numbers for all SAPIs (0, 3) */
+void lapdm_entity_set_t200_fn(struct lapdm_entity *le, const uint32_t *t200_fn)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(le->datalink); i++)
+ le->datalink[i].t200_fn = t200_fn[i];
+}
+
+/*! Set the T200 FN timer of all LAPDm entities in a LAPDm channel
+ * \param[in] \ref lapdm_channel
+ * \param[in] t200_fn_dcch Array of T200 timeout in frame numbers for all SAPIs (0, 3) on SDCCH/FACCH
+ * \param[in] t200_fn_acch Array of T200 timeout in frame numbers for all SAPIs (0, 3) on SACCH */
+void lapdm_channel_set_t200_fn(struct lapdm_channel *lc, const uint32_t *t200_fn_dcch, const uint32_t *t200_fn_acch)
+{
+ lapdm_entity_set_t200_fn(&lc->lapdm_dcch, t200_fn_dcch);
+ lapdm_entity_set_t200_fn(&lc->lapdm_acch, t200_fn_acch);
+}
+
/*! @} */
diff --git a/src/gsm/libosmogsm.map b/src/gsm/libosmogsm.map
index f339120c..2c4c621c 100644
--- a/src/gsm/libosmogsm.map
+++ b/src/gsm/libosmogsm.map
@@ -40,6 +40,14 @@ abis_nm_put_sw_file;
abis_nm_get_sw_conf;
abis_nm_get_sw_desc_len;
+abis_nm_ipacc_freq_band_desc;
+abis_nm_ipacc_ciph_algo_desc;
+abis_nm_ipacc_chant_desc;
+abis_nm_ipacc_chanm_desc;
+abis_nm_ipacc_gprs_coding_desc;
+abis_nm_ipacc_rtp_feat_desc;
+abis_nm_ipacc_rsl_feat_desc;
+
osmo_sitype_strs;
osmo_c4;
osmo_get_rand_id;
@@ -111,6 +119,7 @@ gsm0480_gen_reject;
gsm0502_calc_paging_group;
gsm0502_fn_remap;
gsm0502_hop_seq_gen;
+gsm0502_fn2ccch_block;
gsm0503_xcch;
gsm0503_rach;
@@ -121,6 +130,11 @@ gsm0503_cs3;
gsm0503_cs2_np;
gsm0503_cs3_np;
gsm0503_tch_fr;
+gsm0503_tch_f24;
+gsm0503_tch_h24;
+gsm0503_tch_f48;
+gsm0503_tch_f96;
+gsm0503_tch_f144;
gsm0503_tch_hr;
gsm0503_tch_afs_12_2;
gsm0503_tch_afs_10_2;
@@ -154,6 +168,7 @@ gsm0503_mcs8;
gsm0503_mcs9;
gsm0808_att_tlvdef;
+gsm0808_old_bss_to_new_bss_info_att_tlvdef;
gsm0808_bssap_name;
gsm0808_bssmap_name;
gsm0808_cause_name;
@@ -169,6 +184,7 @@ gsm0808_create_ass_compl2;
gsm0808_create_assignment_failure;
gsm0808_create_ass_fail;
gsm0808_create_cipher;
+gsm0808_create_cipher2;
gsm0808_create_cipher_complete;
gsm0808_create_cipher_reject;
gsm0808_create_cipher_reject_ext;
@@ -203,6 +219,25 @@ gsm0808_create_handover_succeeded;
gsm0808_create_handover_complete;
gsm0808_create_handover_failure;
gsm0808_create_handover_performed;
+gsm0808_create_vgcs_vbs_setup;
+gsm0808_create_vgcs_vbs_setup_ack;
+gsm0808_create_vgcs_vbs_setup_refuse;
+gsm0808_create_vgcs_vbs_assign_req;
+gsm0808_create_vgcs_vbs_assign_res;
+gsm0808_create_vgcs_vbs_assign_fail;
+gsm0808_create_uplink_request;
+gsm0808_create_uplink_request_ack;
+gsm0808_create_uplink_request_cnf;
+gsm0808_create_uplink_app_data;
+gsm0808_create_uplink_release_ind;
+gsm0808_create_uplink_reject_cmd;
+gsm0808_create_uplink_release_cmd;
+gsm0808_create_uplink_seized_cmd;
+gsm0808_create_vgcs_additional_info;
+gsm0808_create_vgcs_vbs_area_cell_info;
+gsm0808_create_vgcs_vbs_assign_stat;
+gsm0808_create_vgcs_sms;
+gsm0808_create_notification_data;
gsm0808_create_common_id;
gsm0808_prepend_dtap_header;
gsm0808_enc_cause;
@@ -210,17 +245,38 @@ gsm0808_enc_aoip_trasp_addr;
gsm0808_dec_aoip_trasp_addr;
gsm0808_dec_osmux_cid;
gsm0808_enc_speech_codec;
+gsm0808_enc_speech_codec2;
gsm0808_dec_speech_codec;
gsm0808_enc_speech_codec_list;
+gsm0808_enc_speech_codec_list2;
gsm0808_dec_speech_codec_list;
gsm0808_enc_channel_type;
gsm0808_dec_channel_type;
gsm0808_enc_encrypt_info;
gsm0808_dec_encrypt_info;
+gsm0808_enc_kc128;
+gsm0808_dec_kc128;
gsm0808_enc_cell_id_list;
gsm0808_enc_cell_id_list2;
gsm0808_dec_cell_id_list;
gsm0808_dec_cell_id_list2;
+gsm0808_enc_group_callref;
+gsm0808_dec_group_callref;
+gsm0808_enc_priority;
+gsm0808_dec_priority;
+gsm0808_enc_vgcs_feature_flags;
+gsm0808_dec_vgcs_feature_flags;
+gsm0808_enc_data_identity;
+gsm0808_dec_data_identity;
+gsm0808_enc_msisdn;
+gsm0808_dec_msisdn;
+gsm0808_enc_talker_identity;
+gsm0808_dec_talker_identity;
+gsm0808_enc_assign_req;
+gsm0808_dec_assign_req;
+gsm0808_enc_cell_id_list_segment;
+gsm0808_dec_cell_id_list_segment;
+gsm0808_dec_call_id;
gsm0808_cell_id_list_add;
gsm0808_cell_id_to_list;
gsm0808_cell_id_to_cgi;
@@ -242,6 +298,8 @@ gsm0808_chan_type_to_speech_codec;
gsm0808_speech_codec_from_chan_type;
gsm0808_sc_cfg_from_gsm48_mr_cfg;
gsm48_mr_cfg_from_gsm0808_sc_cfg;
+gsm0808_amr_modes_from_cfg;
+gsm0808_amr_mode_names;
gsm0808_speech_codec_type_names;
gsm0808_permitted_speech_names;
gsm0808_chosen_enc_alg_names;
@@ -320,12 +378,17 @@ osmo_gsm48_rest_octets_si2quater_encode;
osmo_gsm48_rest_octets_si6_encode;
osmo_gsm48_rest_octets_si3_encode;
osmo_gsm48_rest_octets_si4_encode;
+osmo_gsm48_rest_octets_si13_decode;
osmo_gsm48_rest_octets_si13_encode;
osmo_gsm48_rest_octets_si3_decode;
osmo_gsm48_rest_octets_si4_decode;
+osmo_gsm48_si1ro_nch_pos_encode;
+osmo_gsm48_si1ro_nch_pos_decode;
+gsm48_rr_short_pd_msg_name;
gsm48_rr_msg_name;
gsm48_cc_state_name;
gsm48_construct_ra;
+gsm48_ra_equal;
gsm48_encode_ra;
gsm48_hdr_gmm_cipherable;
gsm48_decode_bcd_number;
@@ -370,14 +433,20 @@ gsm48_generate_mid;
gsm48_generate_mid_from_imsi;
gsm48_generate_mid_from_tmsi;
gsm48_mi_to_string;
+gsm48_chan_mode_to_vamos;
+gsm48_chan_mode_to_non_vamos;
osmo_mobile_identity_to_str_buf;
osmo_mobile_identity_to_str_c;
osmo_mobile_identity_cmp;
osmo_mobile_identity_decode;
+osmo_mobile_identity_decode_from_l3_buf;
osmo_mobile_identity_decode_from_l3;
osmo_mobile_identity_encoded_len;
osmo_mobile_identity_encode_buf;
osmo_mobile_identity_encode_msgb;
+osmo_routing_area_id_decode;
+osmo_routing_area_id_encode_buf;
+osmo_routing_area_id_encode_msgb;
gsm48_mm_att_tlvdef;
gsm48_number_of_paging_subchannels;
gsm48_parse_ra;
@@ -394,6 +463,7 @@ gsm48_generate_lai2;
gsm48_decode_lai2;
osmo_bts_features_descs;
osmo_bts_feature_name;
+osmo_bts_features_names;
osmo_plmn_to_bcd;
osmo_plmn_from_bcd;
osmo_mcc_name;
@@ -406,16 +476,27 @@ osmo_plmn_name;
osmo_plmn_name_buf;
osmo_plmn_name_c;
osmo_plmn_name2;
+osmo_lai_cmp;
osmo_lai_name;
osmo_lai_name_buf;
osmo_lai_name_c;
+osmo_rai_cmp;
osmo_rai_name;
osmo_rai_name_buf;
osmo_rai_name_c;
+osmo_rai_name2;
+osmo_rai_name2_buf;
+osmo_rai_name2_c;
+osmo_cgi_cmp;
osmo_cgi_name;
osmo_cgi_name_buf;
osmo_cgi_name_c;
osmo_cgi_name2;
+osmo_cgi_ps_cmp;
+osmo_cgi_ps_name;
+osmo_cgi_ps_name_buf;
+osmo_cgi_ps_name_c;
+osmo_cgi_ps_name2;
osmo_gummei_name;
osmo_gummei_name_buf;
osmo_gummei_name_c;
@@ -439,6 +520,13 @@ gsm48_pdisc_msgtype_name_buf;
gsm48_pdisc_msgtype_name_c;
gsm48_reject_value_names;
+osmo_gsm44068_msg_type_names;
+osmo_gsm44068_priority_level_names;
+osmo_gsm44068_cause_names;
+osmo_gsm44068_call_state_names;
+osmo_gsm44068_talker_priority_names;
+osmo_gsm44068_att_tlvdef;
+
gsm_7bit_decode;
gsm_7bit_decode_ussd;
gsm_7bit_encode;
@@ -464,9 +552,11 @@ gsm_gsmtime2fn;
osmo_dump_gsmtime;
osmo_dump_gsmtime_buf;
osmo_dump_gsmtime_c;
+gsm_rfn2fn;
gsm_milenage;
gsm_septet_encode;
+gsm_septet_pack;
gsm_septets2octets;
lapd_dl_exit;
@@ -497,7 +587,11 @@ lapdm_entity_init3;
lapdm_entity_reset;
lapdm_entity_set_flags;
lapdm_entity_set_mode;
+lapdm_entity_set_t200_fn;
+lapdm_channel_set_t200_fn;
lapdm_phsap_dequeue_prim;
+lapdm_phsap_dequeue_prim_fn;
+lapdm_t200_fn;
lapdm_phsap_up;
lapdm_rslms_recvmsg;
@@ -521,14 +615,23 @@ osmo_a5_2;
osmo_auth_alg_name;
osmo_auth_alg_parse;
osmo_auth_gen_vec;
+osmo_auth_gen_vec2;
osmo_auth_gen_vec_auts;
+osmo_auth_gen_vec_auts2;
osmo_auth_3g_from_2g;
osmo_auth_load;
osmo_auth_register;
osmo_auth_supported;
+osmo_auth_c2;
osmo_auth_c3;
osmo_sub_auth_type_names;
+osmo_kdf_kc128;
+osmo_kdf_kasme;
+osmo_kdf_enb;
+osmo_kdf_nh;
+osmo_kdf_nas;
+
osmo_rsl2sitype;
osmo_sitype2rsl;
@@ -580,6 +683,11 @@ osmo_shift_tlv;
osmo_match_shift_tlv;
osmo_shift_lv;
+osmo_tlv_prot_msg_name;
+osmo_tlv_prot_ie_name;
+osmo_tlv_prot_validate_tp;
+osmo_tlv_prot_parse;
+
gan_msgt_vals;
gan_pdisc_vals;
@@ -685,6 +793,7 @@ osmo_cbsp_encode;
osmo_cbsp_decode;
osmo_cbsp_recv_buffered;
osmo_cbsp_errstr;
+osmo_cbsp_segmentation_cb;
osmo_i460_demux_in;
osmo_i460_mux_enqueue;
@@ -739,5 +848,29 @@ 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;
+
+osmo_csd_12k_6k_decode_frame;
+osmo_csd_12k_6k_encode_frame;
+osmo_csd_3k6_decode_frame;
+osmo_csd_3k6_encode_frame;
+osmo_csd_ubit_dump;
+
+osmo_rlp_decode;
+osmo_rlp_encode;
+osmo_rlp_fcs_compute;
+osmo_rlp_ftype_s_vals;
+osmo_rlp_ftype_u_vals;
+osmo_rlp_ftype_vals;
+
local: *;
};
diff --git a/src/gsm/milenage/milenage.c b/src/gsm/milenage/milenage.c
index 3c14ab96..ba9ab33b 100644
--- a/src/gsm/milenage/milenage.c
+++ b/src/gsm/milenage/milenage.c
@@ -244,19 +244,13 @@ int milenage_auts(const u8 *opc, const u8 *k, const u8 *_rand, const u8 *auts,
int gsm_milenage(const u8 *opc, const u8 *k, const u8 *_rand, u8 *sres, u8 *kc)
{
u8 res[8], ck[16], ik[16];
- int i;
if (milenage_f2345(opc, k, _rand, res, ck, ik, NULL, NULL))
return -1;
osmo_auth_c3(kc, ck, ik);
+ osmo_auth_c2(sres, res, sizeof(res), 1);
-#ifdef GSM_MILENAGE_ALT_SRES
- os_memcpy(sres, res, 4);
-#else /* GSM_MILENAGE_ALT_SRES */
- for (i = 0; i < 4; i++)
- sres[i] = res[i] ^ res[i + 4];
-#endif /* GSM_MILENAGE_ALT_SRES */
return 0;
}
diff --git a/src/gsm/mncc.c b/src/gsm/mncc.c
index 938cf9a6..8a7dc4d6 100644
--- a/src/gsm/mncc.c
+++ b/src/gsm/mncc.c
@@ -20,7 +20,7 @@
*
*/
-#include "../config.h"
+#include "config.h"
#ifdef HAVE_SYS_SOCKET_H
diff --git a/src/gsm/rlp.c b/src/gsm/rlp.c
new file mode 100644
index 00000000..1e90a689
--- /dev/null
+++ b/src/gsm/rlp.c
@@ -0,0 +1,243 @@
+/*
+ * Copyright (C) 2022-2023 Harald Welte <laforge@osmocom.org>
+ *
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+/*! \addtogroup rlp
+ * @{
+ * RLP (Radio Link Protocol) as per 3GPP TS 24.022
+ *
+ */
+
+#include <stdint.h>
+#include <stddef.h>
+#include <string.h>
+#include <errno.h>
+
+#include <osmocom/gsm/rlp.h>
+
+const struct value_string osmo_rlp_ftype_vals[] = {
+ { OSMO_RLP_FT_U, "U" },
+ { OSMO_RLP_FT_S, "S" },
+ { OSMO_RLP_FT_IS, "IS" },
+ { 0, NULL }
+};
+
+const struct value_string osmo_rlp_ftype_u_vals[] = {
+ { OSMO_RLP_U_FT_SABM, "SABM" },
+ { OSMO_RLP_U_FT_UA, "UA" },
+ { OSMO_RLP_U_FT_DISC, "DISC" },
+ { OSMO_RLP_U_FT_DM, "DM" },
+ { OSMO_RLP_U_FT_NULL, "NULL" },
+ { OSMO_RLP_U_FT_UI, "UI" },
+ { OSMO_RLP_U_FT_XID, "XID" },
+ { OSMO_RLP_U_FT_TEST, "TEST" },
+ { OSMO_RLP_U_FT_REMAP, "REMAP" },
+ { 0, NULL }
+};
+
+const struct value_string osmo_rlp_ftype_s_vals[] = {
+ { OSMO_RLP_S_FT_RR, "RR" },
+ { OSMO_RLP_S_FT_REJ, "REJ" },
+ { OSMO_RLP_S_FT_RNR, "RNR" },
+ { OSMO_RLP_S_FT_SREJ, "SREJ" },
+ { 0, NULL }
+};
+
+/* number of bytes used up by FCS */
+#define FCS_SIZE_BYTES 3
+
+/*! decode a RLP frame into its abstract representation. Doesn't check FCS correctness.
+ * \param[out] out caller-allocated memory for output of decoded frame
+ * \param[in] version RLP version number to use when decoding
+ * \param[in] data raw RLP frame input data
+ * \param[in] data_len length of data (in octets); must be 30 (240bit) or 72 (576bit)
+ * \returns 0 in case of success; negative on error */
+int osmo_rlp_decode(struct osmo_rlp_frame_decoded *out, uint8_t version, const uint8_t *data, size_t data_len)
+{
+ const uint8_t hdr_len = 2; /* will become a variable when we introduce v2 support */
+ uint8_t n_s, n_r;
+
+ if (data_len != 240/8 && data_len != 576/8)
+ return -EINVAL;
+
+ /* we only support version 0+1 so far */
+ if (version >= 2)
+ return -ENOTSUP;
+
+ memset(out, 0, sizeof(*out));
+ out->version = version;
+
+ out->c_r = data[0] & 1;
+ n_s = (data[0] >> 3) | (data[1] & 1) << 5;
+ n_r = (data[1] >> 2);
+ out->fcs = (data[data_len-1] << 16) | (data[data_len-2]) << 8 | (data[data_len-3] << 0);
+ out->p_f = (data[1] >> 1) & 1;
+
+ switch (n_s) {
+ case 0x3f:
+ out->ftype = OSMO_RLP_FT_U;
+ out->u_ftype = n_r & 0x1f;
+ if (out->u_ftype == OSMO_RLP_U_FT_XID) {
+ memcpy(out->info, data + hdr_len, data_len - (hdr_len + FCS_SIZE_BYTES));
+ out->info_len = data_len - (hdr_len + FCS_SIZE_BYTES);
+ }
+ break;
+ case 0x3e:
+ out->ftype = OSMO_RLP_FT_S;
+ out->s_ftype = (data[0] >> 1) & 3;
+ out->n_r = n_r;
+ break;
+ default:
+ out->ftype = OSMO_RLP_FT_IS;
+ out->s_ftype = (data[0] >> 1) & 3;
+ out->n_s = n_s;
+ out->n_r = n_r;
+ memcpy(out->info, data + hdr_len, data_len - (hdr_len + FCS_SIZE_BYTES));
+ out->info_len = data_len - (2 + 3);
+ break;
+ }
+
+ return 0;
+}
+
+/*! encode a RLP frame from its abstract representation. Generates FCS.
+ * \param[out] out caller-allocated output buffer
+ * \param[in] out_size size of output buffer (in octets); must be 30 (240bit) or 72 (576bit)
+ * \param[in] in decoded RLP frame which is to be encoded
+ * \returns number of output bytes used; negative on error */
+int osmo_rlp_encode(uint8_t *out, size_t out_size, const struct osmo_rlp_frame_decoded *in)
+{
+ const uint8_t hdr_len = 2; /* will become a variable when we introduce v2 support */
+ uint8_t n_s, n_r, s_bits;
+ uint32_t fcs;
+
+ /* we only support version 0+1 so far */
+ if (in->version >= 2)
+ return -ENOTSUP;
+
+ if (out_size != 240/8 && out_size != 576/8)
+ return -EINVAL;
+
+ memset(out, 0, out_size);
+
+ if (in->c_r)
+ out[0] |= 0x01;
+ if (in->p_f)
+ out[1] |= 0x02;
+
+ switch (in->ftype) {
+ case OSMO_RLP_FT_U:
+ n_s = 0x3f;
+ n_r = in->u_ftype;
+ s_bits = 0;
+ if (in->u_ftype == OSMO_RLP_U_FT_XID) {
+ if (in->info_len > out_size - (hdr_len + FCS_SIZE_BYTES))
+ return -EINVAL;
+ memcpy(out+hdr_len, in->info, in->info_len);
+ }
+ break;
+ case OSMO_RLP_FT_S:
+ n_s = 0x3e;
+ n_r = in->n_r;
+ s_bits = in->s_ftype;
+ break;
+ case OSMO_RLP_FT_IS:
+ /* we only support 240 bit so far */
+ if (in->info_len > out_size - (hdr_len + FCS_SIZE_BYTES))
+ return -EINVAL;
+ n_s = in->n_s;
+ n_r = in->n_r;
+ s_bits = in->s_ftype;
+ memcpy(out+hdr_len, in->info, in->info_len);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* patch N(S) into output data */
+ out[0] |= (n_s & 0x1F) << 3;
+ out[1] |= (n_s & 0x20) >> 5;
+
+ /* patch N(R) / M-bits into output data */
+ out[1] |= (n_r & 0x3f) << 2;
+
+ /* patch S-bits into output data */
+ out[0] |= (s_bits & 3) << 1;
+
+ /* compute FCS + add it to end of frame */
+ fcs = osmo_rlp_fcs_compute(out, out_size - FCS_SIZE_BYTES);
+ out[out_size - 3] = (fcs >> 0) & 0xff;
+ out[out_size - 2] = (fcs >> 8) & 0xff;
+ out[out_size - 1] = (fcs >> 16) & 0xff;
+
+ return out_size;
+}
+
+
+static const uint32_t rlp_fcs_table[256] = {
+ 0x00B29D2D, 0x00643A5B, 0x0044D87A, 0x00927F0C, 0x00051C38, 0x00D3BB4E, 0x00F3596F, 0x0025FE19,
+ 0x008694BC, 0x005033CA, 0x0070D1EB, 0x00A6769D, 0x003115A9, 0x00E7B2DF, 0x00C750FE, 0x0011F788,
+ 0x00DA8E0F, 0x000C2979, 0x002CCB58, 0x00FA6C2E, 0x006D0F1A, 0x00BBA86C, 0x009B4A4D, 0x004DED3B,
+ 0x00EE879E, 0x003820E8, 0x0018C2C9, 0x00CE65BF, 0x0059068B, 0x008FA1FD, 0x00AF43DC, 0x0079E4AA,
+ 0x0062BB69, 0x00B41C1F, 0x0094FE3E, 0x00425948, 0x00D53A7C, 0x00039D0A, 0x00237F2B, 0x00F5D85D,
+ 0x0056B2F8, 0x0080158E, 0x00A0F7AF, 0x007650D9, 0x00E133ED, 0x0037949B, 0x001776BA, 0x00C1D1CC,
+ 0x000AA84B, 0x00DC0F3D, 0x00FCED1C, 0x002A4A6A, 0x00BD295E, 0x006B8E28, 0x004B6C09, 0x009DCB7F,
+ 0x003EA1DA, 0x00E806AC, 0x00C8E48D, 0x001E43FB, 0x008920CF, 0x005F87B9, 0x007F6598, 0x00A9C2EE,
+ 0x0049DA1E, 0x009F7D68, 0x00BF9F49, 0x0069383F, 0x00FE5B0B, 0x0028FC7D, 0x00081E5C, 0x00DEB92A,
+ 0x007DD38F, 0x00AB74F9, 0x008B96D8, 0x005D31AE, 0x00CA529A, 0x001CF5EC, 0x003C17CD, 0x00EAB0BB,
+ 0x0021C93C, 0x00F76E4A, 0x00D78C6B, 0x00012B1D, 0x00964829, 0x0040EF5F, 0x00600D7E, 0x00B6AA08,
+ 0x0015C0AD, 0x00C367DB, 0x00E385FA, 0x0035228C, 0x00A241B8, 0x0074E6CE, 0x005404EF, 0x0082A399,
+ 0x0099FC5A, 0x004F5B2C, 0x006FB90D, 0x00B91E7B, 0x002E7D4F, 0x00F8DA39, 0x00D83818, 0x000E9F6E,
+ 0x00ADF5CB, 0x007B52BD, 0x005BB09C, 0x008D17EA, 0x001A74DE, 0x00CCD3A8, 0x00EC3189, 0x003A96FF,
+ 0x00F1EF78, 0x0027480E, 0x0007AA2F, 0x00D10D59, 0x00466E6D, 0x0090C91B, 0x00B02B3A, 0x00668C4C,
+ 0x00C5E6E9, 0x0013419F, 0x0033A3BE, 0x00E504C8, 0x007267FC, 0x00A4C08A, 0x008422AB, 0x005285DD,
+ 0x001F18F0, 0x00C9BF86, 0x00E95DA7, 0x003FFAD1, 0x00A899E5, 0x007E3E93, 0x005EDCB2, 0x00887BC4,
+ 0x002B1161, 0x00FDB617, 0x00DD5436, 0x000BF340, 0x009C9074, 0x004A3702, 0x006AD523, 0x00BC7255,
+ 0x00770BD2, 0x00A1ACA4, 0x00814E85, 0x0057E9F3, 0x00C08AC7, 0x00162DB1, 0x0036CF90, 0x00E068E6,
+ 0x00430243, 0x0095A535, 0x00B54714, 0x0063E062, 0x00F48356, 0x00222420, 0x0002C601, 0x00D46177,
+ 0x00CF3EB4, 0x001999C2, 0x00397BE3, 0x00EFDC95, 0x0078BFA1, 0x00AE18D7, 0x008EFAF6, 0x00585D80,
+ 0x00FB3725, 0x002D9053, 0x000D7272, 0x00DBD504, 0x004CB630, 0x009A1146, 0x00BAF367, 0x006C5411,
+ 0x00A72D96, 0x00718AE0, 0x005168C1, 0x0087CFB7, 0x0010AC83, 0x00C60BF5, 0x00E6E9D4, 0x00304EA2,
+ 0x00932407, 0x00458371, 0x00656150, 0x00B3C626, 0x0024A512, 0x00F20264, 0x00D2E045, 0x00044733,
+ 0x00E45FC3, 0x0032F8B5, 0x00121A94, 0x00C4BDE2, 0x0053DED6, 0x008579A0, 0x00A59B81, 0x00733CF7,
+ 0x00D05652, 0x0006F124, 0x00261305, 0x00F0B473, 0x0067D747, 0x00B17031, 0x00919210, 0x00473566,
+ 0x008C4CE1, 0x005AEB97, 0x007A09B6, 0x00ACAEC0, 0x003BCDF4, 0x00ED6A82, 0x00CD88A3, 0x001B2FD5,
+ 0x00B84570, 0x006EE206, 0x004E0027, 0x0098A751, 0x000FC465, 0x00D96313, 0x00F98132, 0x002F2644,
+ 0x00347987, 0x00E2DEF1, 0x00C23CD0, 0x00149BA6, 0x0083F892, 0x00555FE4, 0x0075BDC5, 0x00A31AB3,
+ 0x00007016, 0x00D6D760, 0x00F63541, 0x00209237, 0x00B7F103, 0x00615675, 0x0041B454, 0x00971322,
+ 0x005C6AA5, 0x008ACDD3, 0x00AA2FF2, 0x007C8884, 0x00EBEBB0, 0x003D4CC6, 0x001DAEE7, 0x00CB0991,
+ 0x00686334, 0x00BEC442, 0x009E2663, 0x00488115, 0x00DFE221, 0x00094557, 0x0029A776, 0x00FF0000
+};
+
+/*! compute RLP FCS according to 3GPP TS 24.022 Section 4.4.
+ * \param[in] in input data over which to compute FCS
+ * \param[in] in_len length of input data (in octets)
+ * \returns computed frame check sequence (FCS). */
+uint32_t osmo_rlp_fcs_compute(const uint8_t *in, size_t in_len)
+{
+ uint32_t divider = 0;
+ size_t i;
+
+ for (i = 0; i < in_len; i++) {
+ uint8_t input = in[i] ^ (divider & 0xff);
+ divider = (divider >> 8) ^ rlp_fcs_table[input];
+ }
+
+ return divider;
+}
+
+/*! @} */
diff --git a/src/gsm/rsl.c b/src/gsm/rsl.c
index 2ab49c23..fbba982a 100644
--- a/src/gsm/rsl.c
+++ b/src/gsm/rsl.c
@@ -17,10 +17,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <stdint.h>
@@ -127,6 +123,10 @@ const struct tlv_definition rsl_att_tlvdef = {
[RSL_IE_LLP_APDU] = { TLV_TYPE_TLV },
[RSL_IE_SIEMENS_MRPCI] = { TLV_TYPE_TV },
[RSL_IE_OSMO_REP_ACCH_CAP] = { TLV_TYPE_TLV },
+ [RSL_IE_OSMO_TRAINING_SEQUENCE] = { TLV_TYPE_TLV },
+ [RSL_IE_OSMO_TEMP_OVP_ACCH_CAP] = { TLV_TYPE_TLV },
+ [RSL_IE_OSMO_OSMUX_CID] = { TLV_TYPE_TLV },
+ [RSL_IE_IPAC_SRTP_CONFIG] = { TLV_TYPE_TLV },
[RSL_IE_IPAC_PROXY_UDP] = { TLV_TYPE_FIXED, 2 },
[RSL_IE_IPAC_BSCMPL_TOUT] = { TLV_TYPE_TV },
[RSL_IE_IPAC_REMOTE_IP] = { TLV_TYPE_FIXED, 4 },
@@ -135,6 +135,8 @@ const struct tlv_definition rsl_att_tlvdef = {
[RSL_IE_IPAC_LOCAL_PORT] = { TLV_TYPE_FIXED, 2 },
[RSL_IE_IPAC_SPEECH_MODE] = { TLV_TYPE_TV },
[RSL_IE_IPAC_LOCAL_IP] = { TLV_TYPE_FIXED, 4 },
+ [RSL_IE_IPAC_CONN_STAT] = { TLV_TYPE_TLV, 28 },
+ [RSL_IE_IPAC_HO_C_PARMS] = { TLV_TYPE_TLV },
[RSL_IE_IPAC_CONN_ID] = { TLV_TYPE_FIXED, 2 },
[RSL_IE_IPAC_RTP_CSD_FMT] = { TLV_TYPE_TV },
[RSL_IE_IPAC_RTP_JIT_BUF] = { TLV_TYPE_FIXED, 2 },
@@ -158,6 +160,7 @@ uint8_t rsl_enc_chan_nr(uint8_t type, uint8_t subch, uint8_t timeslot)
switch (type) {
case RSL_CHAN_Lm_ACCHs:
+ case RSL_CHAN_OSMO_VAMOS_Lm_ACCHs:
subch &= 0x01;
break;
case RSL_CHAN_SDCCH4_ACCH:
@@ -186,38 +189,34 @@ int rsl_dec_chan_nr(uint8_t chan_nr, uint8_t *type, uint8_t *subch, uint8_t *tim
{
*timeslot = chan_nr & 0x7;
- if ((chan_nr & 0xf8) == RSL_CHAN_Bm_ACCHs) {
- *type = RSL_CHAN_Bm_ACCHs;
- *subch = 0;
- } else if ((chan_nr & 0xf0) == RSL_CHAN_Lm_ACCHs) {
- *type = RSL_CHAN_Lm_ACCHs;
- *subch = (chan_nr >> 3) & 0x1;
- } else if ((chan_nr & 0xe0) == RSL_CHAN_SDCCH4_ACCH) {
- *type = RSL_CHAN_SDCCH4_ACCH;
- *subch = (chan_nr >> 3) & 0x3;
- } else if ((chan_nr & 0xc0) == RSL_CHAN_SDCCH8_ACCH) {
- *type = RSL_CHAN_SDCCH8_ACCH;
- *subch = (chan_nr >> 3) & 0x7;
- } else if ((chan_nr & 0xf8) == RSL_CHAN_BCCH) {
- *type = RSL_CHAN_BCCH;
- *subch = 0;
- } else if ((chan_nr & 0xf8) == RSL_CHAN_RACH) {
- *type = RSL_CHAN_RACH;
- *subch = 0;
- } else if ((chan_nr & 0xf8) == RSL_CHAN_PCH_AGCH) {
- *type = RSL_CHAN_PCH_AGCH;
- *subch = 0;
- } else if ((chan_nr & 0xf8) == RSL_CHAN_OSMO_PDCH) {
- *type = RSL_CHAN_OSMO_PDCH;
+ switch (chan_nr & RSL_CHAN_NR_MASK) {
+ case RSL_CHAN_Bm_ACCHs:
+ case RSL_CHAN_BCCH:
+ case RSL_CHAN_RACH:
+ case RSL_CHAN_PCH_AGCH:
+ case RSL_CHAN_OSMO_PDCH:
+ case RSL_CHAN_OSMO_CBCH4:
+ case RSL_CHAN_OSMO_CBCH8:
+ case RSL_CHAN_OSMO_VAMOS_Bm_ACCHs:
+ *type = chan_nr & RSL_CHAN_NR_MASK;
*subch = 0;
- } else if ((chan_nr & 0xf8) == RSL_CHAN_OSMO_CBCH4) {
- *type = RSL_CHAN_OSMO_CBCH4;
- *subch = 0;
- } else if ((chan_nr & 0xf8) == RSL_CHAN_OSMO_CBCH8) {
- *type = RSL_CHAN_OSMO_CBCH8;
- *subch = 0;
- } else
- return -EINVAL;
+ break;
+ default:
+ if ((chan_nr & 0xf0) == RSL_CHAN_Lm_ACCHs) {
+ *type = RSL_CHAN_Lm_ACCHs;
+ *subch = (chan_nr >> 3) & 0x1;
+ } else if ((chan_nr & 0xe0) == RSL_CHAN_SDCCH4_ACCH) {
+ *type = RSL_CHAN_SDCCH4_ACCH;
+ *subch = (chan_nr >> 3) & 0x3;
+ } else if ((chan_nr & 0xc0) == RSL_CHAN_SDCCH8_ACCH) {
+ *type = RSL_CHAN_SDCCH8_ACCH;
+ *subch = (chan_nr >> 3) & 0x7;
+ } else if ((chan_nr & 0xf0) == RSL_CHAN_OSMO_VAMOS_Lm_ACCHs) {
+ *type = RSL_CHAN_OSMO_VAMOS_Lm_ACCHs;
+ *subch = (chan_nr >> 3) & 0x1;
+ } else
+ return -EINVAL;
+ }
return 0;
}
@@ -253,6 +252,10 @@ char *rsl_chan_nr_str_buf(char *buf, size_t buf_len, uint8_t chan_nr)
snprintf(buf, buf_len, "CBCH(SDCCH/4) on TS%d", ts);
else if (cbits == ABIS_RSL_CHAN_NR_CBITS_OSMO_CBCH8)
snprintf(buf, buf_len, "CBCH(SDCCH/8) on TS%d", ts);
+ else if (cbits == ABIS_RSL_CHAN_NR_CBITS_OSMO_VAMOS_Bm_ACCHs)
+ snprintf(buf, buf_len, "VAMOS TCH/F on TS%d", ts);
+ else if ((cbits & 0x1e) == ABIS_RSL_CHAN_NR_CBITS_OSMO_VAMOS_Lm_ACCHs(0))
+ snprintf(buf, buf_len, "VAMOS TCH/H(%u) on TS%d", cbits & 0x01, ts);
else
snprintf(buf, buf_len, "UNKNOWN on TS%d", ts);
@@ -265,7 +268,7 @@ char *rsl_chan_nr_str_buf(char *buf, size_t buf_len, uint8_t chan_nr)
*/
const char *rsl_chan_nr_str(uint8_t chan_nr)
{
- static __thread char str[20];
+ static __thread char str[32];
return rsl_chan_nr_str_buf(str, sizeof(str), chan_nr);
}
@@ -276,10 +279,10 @@ const char *rsl_chan_nr_str(uint8_t chan_nr)
*/
char *rsl_chan_nr_str_c(const void *ctx, uint8_t chan_nr)
{
- char *str = talloc_size(ctx, 20);
+ char *str = talloc_size(ctx, 32);
if (!str)
return NULL;
- return rsl_chan_nr_str_buf(str, 20, chan_nr);
+ return rsl_chan_nr_str_buf(str, 32, chan_nr);
}
static const struct value_string rsl_err_vals[] = {
@@ -550,7 +553,7 @@ void rsl_rll_push_l3(struct msgb *msg, uint8_t msg_type, uint8_t chan_nr,
/* construct a RSLms RLL message (DATA INDICATION, UNIT DATA
* INDICATION) and send it off via RSLms */
- /* Push the L3 IE tag and lengh */
+ /* Push the L3 IE tag and length */
msgb_tv16_push(msg, RSL_IE_L3_INFO, l3_len);
/* Then push the RSL header */
@@ -618,6 +621,10 @@ const struct tlv_definition rsl_ipac_eie_tlvdef = {
[RSL_IPAC_EIE_3G_NCELL_LIST] = { TLV_TYPE_TLV },
[RSL_IPAC_EIE_SDCCH_CTL_PARAM] = { TLV_TYPE_TV },
[RSL_IPAC_EIE_AMR_CONV_THRESH] = { TLV_TYPE_FIXED, 9 },
+ /* Osmocom extensions: */
+ [RSL_IPAC_EIE_OSMO_MEAS_AVG_CFG]= { TLV_TYPE_TLV },
+ [RSL_IPAC_EIE_OSMO_MS_PWR_CTL] = { TLV_TYPE_TLV },
+ [RSL_IPAC_EIE_OSMO_PC_THRESH_COMP]= { TLV_TYPE_TLV },
},
};
diff --git a/src/gsm/rxlev_stat.c b/src/gsm/rxlev_stat.c
index 9c650cc1..f1b77f4c 100644
--- a/src/gsm/rxlev_stat.c
+++ b/src/gsm/rxlev_stat.c
@@ -17,10 +17,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <unistd.h>
diff --git a/src/gsm/sysinfo.c b/src/gsm/sysinfo.c
index b615871f..40e26524 100644
--- a/src/gsm/sysinfo.c
+++ b/src/gsm/sysinfo.c
@@ -46,7 +46,7 @@ osmo_static_assert(sizeof(struct gsm48_system_information_type_4) == 13, _si4_si
/* bs11 forgot the l2 len, 0-6 rest octets */
osmo_static_assert(sizeof(struct gsm48_system_information_type_5) == 18, _si5_size);
osmo_static_assert(sizeof(struct gsm48_system_information_type_6) == 11, _si6_size);
-
+osmo_static_assert(sizeof(struct gsm48_system_information_type_10) == 1, _si10_size);
osmo_static_assert(sizeof(struct gsm48_system_information_type_13) == 3, _si13_size);
static const uint8_t sitype2rsl[_MAX_SYSINFO_TYPE] = {
diff --git a/src/gsm/tlv_parser.c b/src/gsm/tlv_parser.c
index 159b42bd..8dd460db 100644
--- a/src/gsm/tlv_parser.c
+++ b/src/gsm/tlv_parser.c
@@ -1,4 +1,4 @@
-/* (C) 2008-2017 by Harald Welte <laforge@gnumonks.org>
+/* (C) 2008-2020 by Harald Welte <laforge@gnumonks.org>
* (C) 2016-2017 by sysmocom - s.f.m.c. GmbH
*
* All Rights Reserved
@@ -24,6 +24,7 @@
#include <stdint.h>
#include <errno.h>
#include <osmocom/core/utils.h>
+#include <osmocom/core/logging.h>
#include <osmocom/gsm/tlv.h>
/*! \addtogroup tlv
@@ -125,7 +126,7 @@ int osmo_tlvp_merge(struct tlv_parsed *dst, const struct tlv_parsed *src)
* \param[inout] msg Caller-allocated message buffer with sufficient tailroom
* \param[in] type TLV type/format to use during encode
* \param[in] tag Tag of TLV to be encoded
- * \parma[in] len Length of TLV to be encoded
+ * \param[in] len Length of TLV to be encoded
* \param[in] val Value part of TLV to be encoded
* \returns 0 on success; negative in case of error */
int tlv_encode_one(struct msgb *msg, enum tlv_type type, uint8_t tag,
@@ -224,20 +225,33 @@ int tlv_encode_ordered(struct msgb *msg, const struct tlv_definition *def, const
* \param[in] def structure defining the valid TLV tags / configurations
* \param[in] buf the input data buffer to be parsed
* \param[in] buf_len length of the input data buffer
- * \returns number of bytes consumed by the TLV entry / IE parsed; negative in case of error
+ * \returns number of bytes consumed by the TLV entry / IE parsed; negative in case of error.
+ *
+ * In IEs of type TLV_TYPE_SINGLE_TV, the data pointer \ref o_val will point to the
+ * byte shared by both the Tag and te Value, hence the tag is to be trimmed
+ * by the caller.
*/
int tlv_parse_one(uint8_t *o_tag, uint16_t *o_len, const uint8_t **o_val,
const struct tlv_definition *def,
const uint8_t *buf, int buf_len)
{
uint8_t tag;
- int len;
+ int len; /* number of bytes consumed by TLV entry */
+
+ if (buf_len < 1)
+ return OSMO_TLVP_ERR_OFS_BEYOND_BUFFER;
tag = *buf;
*o_tag = tag;
/* single octet TV IE */
- if (def->def[tag & 0xf0].type == TLV_TYPE_SINGLE_TV) {
+ if (def->def[tag >> 4].type == TLV_TYPE_SINGLE_TV) {
+ *o_tag = tag >> 4;
+ *o_val = buf;
+ *o_len = 1;
+ return 1;
+ } else if ((tag > 0x0f) && (def->def[tag & 0xf0].type == TLV_TYPE_SINGLE_TV)) {
+ /* backward compat for old IEs with half-octet tag defined as 0xN0: */
*o_tag = tag & 0xf0;
*o_val = buf;
*o_len = 1;
@@ -264,56 +278,54 @@ int tlv_parse_one(uint8_t *o_tag, uint16_t *o_len, const uint8_t **o_val,
break;
case TLV_TYPE_TLV:
tlv: /* GSM TS 04.07 11.2.4: Type 4 TLV */
- if (buf + 1 > buf + buf_len)
- return -1;
+ if (buf_len < 2)
+ return OSMO_TLVP_ERR_OFS_BEYOND_BUFFER;
*o_val = buf+2;
*o_len = *(buf+1);
len = *o_len + 2;
- if (len > buf_len)
- return -2;
break;
case TLV_TYPE_vTvLV_GAN: /* 44.318 / 11.1.4 */
/* FIXME: variable-length TAG! */
+ if (buf_len < 2)
+ return OSMO_TLVP_ERR_OFS_BEYOND_BUFFER;
if (*(buf+1) & 0x80) {
+ if (buf_len < 3)
+ return OSMO_TLVP_ERR_OFS_BEYOND_BUFFER;
/* like TL16Vbut without highest bit of len */
- if (2 > buf_len)
- return -1;
*o_val = buf+3;
*o_len = (*(buf+1) & 0x7F) << 8 | *(buf+2);
len = *o_len + 3;
- if (len > buf_len)
- return -2;
} else {
/* like TLV */
goto tlv;
}
break;
case TLV_TYPE_TvLV:
+ if (buf_len < 2)
+ return OSMO_TLVP_ERR_OFS_BEYOND_BUFFER;
if (*(buf+1) & 0x80) {
/* like TLV, but without highest bit of len */
- if (buf + 1 > buf + buf_len)
- return -1;
*o_val = buf+2;
*o_len = *(buf+1) & 0x7f;
len = *o_len + 2;
- if (len > buf_len)
- return -2;
break;
}
/* like TL16V, fallthrough */
case TLV_TYPE_TL16V:
- if (2 > buf_len)
- return -1;
+ if (buf_len < 3)
+ return OSMO_TLVP_ERR_OFS_BEYOND_BUFFER;
*o_val = buf+3;
*o_len = *(buf+1) << 8 | *(buf+2);
len = *o_len + 3;
- if (len > buf_len)
- return -2;
break;
default:
- return -3;
+ return OSMO_TLVP_ERR_UNKNOWN_TLV_TYPE;
}
+ if (buf_len < len) {
+ *o_val = NULL;
+ return OSMO_TLVP_ERR_OFS_LEN_BEYOND_BUFFER;
+ }
return len;
}
@@ -369,12 +381,12 @@ int tlv_parse2(struct tlv_parsed *dec, int dec_multiples,
const uint8_t *val;
uint16_t parsed_len;
if (ofs > buf_len)
- return -1;
+ return OSMO_TLVP_ERR_OFS_BEYOND_BUFFER;
val = &buf[ofs+1];
len = buf[ofs];
parsed_len = len + 1;
if (ofs + parsed_len > buf_len)
- return -2;
+ return OSMO_TLVP_ERR_OFS_LEN_BEYOND_BUFFER;
num_parsed++;
ofs += parsed_len;
/* store the resulting val and len */
@@ -390,12 +402,12 @@ int tlv_parse2(struct tlv_parsed *dec, int dec_multiples,
const uint8_t *val;
uint16_t parsed_len;
if (ofs > buf_len)
- return -1;
+ return OSMO_TLVP_ERR_OFS_BEYOND_BUFFER;
val = &buf[ofs+1];
len = buf[ofs];
parsed_len = len + 1;
if (ofs + parsed_len > buf_len)
- return -2;
+ return OSMO_TLVP_ERR_OFS_LEN_BEYOND_BUFFER;
num_parsed++;
ofs += parsed_len;
/* store the resulting val and len */
@@ -431,7 +443,7 @@ int tlv_parse2(struct tlv_parsed *dec, int dec_multiples,
return num_parsed;
}
-/*! take a master (src) tlvdev and fill up all empty slots in 'dst'
+/*! take a master (src) tlv_definition and fill up all empty slots in 'dst'
* \param dst TLV parser definition that is to be patched
* \param[in] src TLV parser definition whose content is patched into \a dst */
void tlv_def_patch(struct tlv_definition *dst, const struct tlv_definition *src)
@@ -627,4 +639,108 @@ fail:
return -1;
}
+static __thread char ienamebuf[32];
+static __thread char msgnamebuf[32];
+
+/*! get the message name for given msg_type in protocol pdef */
+const char *osmo_tlv_prot_msg_name(const struct osmo_tlv_prot_def *pdef, uint8_t msg_type)
+{
+ if (pdef->msg_def[msg_type].name) {
+ return pdef->msg_def[msg_type].name;
+ } else if (pdef->msgt_names) {
+ return get_value_string(pdef->msgt_names, msg_type);
+ } else {
+ snprintf(msgnamebuf, sizeof(msgnamebuf), "Unknown msg_type 0x%02x", msg_type);
+ return msgnamebuf;
+ }
+}
+
+/*! get the IE name for given IEI in protocol pdef */
+const char *osmo_tlv_prot_ie_name(const struct osmo_tlv_prot_def *pdef, uint8_t iei)
+{
+ if (pdef->ie_def[iei].name) {
+ return pdef->ie_def[iei].name;
+ } else {
+ snprintf(ienamebuf, sizeof(ienamebuf), "Unknown IEI 0x%02x", iei);
+ return ienamebuf;
+ }
+}
+
+/*! Validate an already TLV-decoded message against the protocol definition.
+ * \param[in] pdef protocol definition of given protocol
+ * \param[in] msg_type message type of the parsed message
+ * \param[in] tp TLV parser result
+ * \param[in] log_subsys logging sub-system for log messages
+ * \param[in] log_pfx prefix for log messages
+ * \returns 0 in case of success; negative osmo_tlv_parser_error in case of error
+ */
+int osmo_tlv_prot_validate_tp(const struct osmo_tlv_prot_def *pdef, uint8_t msg_type,
+ const struct tlv_parsed *tp, int log_subsys, const char *log_pfx)
+{
+ const struct osmo_tlv_prot_msg_def *msg_def= &pdef->msg_def[msg_type];
+ unsigned int err = 0;
+ unsigned int i;
+
+ if (msg_def->mand_ies) {
+ for (i = 0; i < msg_def->mand_count; i++) {
+ uint8_t iei = msg_def->mand_ies[i];
+ if (!TLVP_PRESENT(tp, iei)) {
+ LOGP(log_subsys, LOGL_ERROR, "%s %s %s: Missing Mandatory IE: %s\n",
+ log_pfx, pdef->name, osmo_tlv_prot_msg_name(pdef, msg_type),
+ osmo_tlv_prot_ie_name(pdef, iei));
+ if (!err)
+ err = OSMO_TLVP_ERR_MAND_IE_MISSING;
+ }
+ }
+ }
+
+ for (i = 0; i < ARRAY_SIZE(tp->lv); i++) {
+ uint16_t min_len;
+
+ if (!TLVP_PRESENT(tp, i))
+ continue;
+
+ min_len = pdef->ie_def[i].min_len;
+ if (TLVP_LEN(tp, i) < min_len) {
+ LOGP(log_subsys, LOGL_ERROR, "%s %s %s: Short IE %s: %u < %u\n", log_pfx,
+ pdef->name, osmo_tlv_prot_msg_name(pdef, msg_type),
+ osmo_tlv_prot_ie_name(pdef, i), TLVP_LEN(tp, i), min_len);
+ if (!err)
+ err = OSMO_TLVP_ERR_IE_TOO_SHORT;
+ }
+ }
+
+ return err;
+}
+
+/*! Parse + Validate a TLV-encoded message against the protocol definition.
+ * \param[in] pdef protocol definition of given protocol
+ * \param[out] dec caller-allocated pointer to \ref tlv_parsed
+ * \param[in] dec_multiples length of the tlv_parsed[] in \a dec.
+ * \param[in] msg_type message type of the parsed message
+ * \param[in] buf the input data buffer to be parsed
+ * \param[in] buf_len length of the input data buffer
+ * \param[in] lv_tag an initial LV tag at the start of the buffer
+ * \param[in] lv_tag2 a second initial LV tag following the \a lv_tag
+ * \param[in] log_subsys logging sub-system for log messages
+ * \param[in] log_pfx prefix for log messages
+ * \returns 0 in case of success; negative osmo_tlv_parser_error in case of error
+ */
+int osmo_tlv_prot_parse(const struct osmo_tlv_prot_def *pdef,
+ struct tlv_parsed *dec, unsigned int dec_multiples, uint8_t msg_type,
+ const uint8_t *buf, unsigned int buf_len, uint8_t lv_tag, uint8_t lv_tag2,
+ int log_subsys, const char *log_pfx)
+{
+ int rc;
+
+ rc = tlv_parse2(dec, dec_multiples, pdef->tlv_def, buf, buf_len, lv_tag, lv_tag2);
+ if (rc < 0) {
+ LOGP(log_subsys, LOGL_ERROR, "%s %s %s: TLV parser error %d\n", log_pfx,
+ pdef->name, osmo_tlv_prot_msg_name(pdef, msg_type), rc);
+ return rc;
+ }
+
+ return osmo_tlv_prot_validate_tp(pdef, msg_type, dec, log_subsys, log_pfx);
+}
+
/*! @} */
diff --git a/src/gsm/tuak/KeccakP-1600-3gpp.c b/src/gsm/tuak/KeccakP-1600-3gpp.c
new file mode 100644
index 00000000..3f5e2ad4
--- /dev/null
+++ b/src/gsm/tuak/KeccakP-1600-3gpp.c
@@ -0,0 +1,176 @@
+/* -----------------------------------------------------------------------
+ * code extracted from 3GPP TS 35.231, annex E for Keccak core functions
+ * https://portal.3gpp.org/desktopmodules/Specifications/SpecificationDetails.aspx?specificationId=2402
+ *-----------------------------------------------------------------------*/
+
+/* This code may be freely used or adapted.
+*/
+
+#include "KeccakP-1600-3gpp.h"
+
+
+const uint8_t Rho[25] = {0,1,62,28,27,36,44,6,55,20,3,10,43,25,39,41,45,
+ 15,21,8,18,2,61,56,14};
+
+const uint8_t Pi[25] = {0,6,12,18,24,3,9,10,16,22,1,7,13,19,20,4,5,11,17,
+ 23,2,8,14,15,21};
+
+const uint8_t Iota[24] = {1,146,218,112,155,33,241,89,138,136,57,42,187,203,
+ 217,83,82,192,26,106,241,208,33,120};
+
+#define ROTATE64(value, n) \
+((((uint64_t)(value))<<(n)) | (((uint64_t)(value))>>(64-(n))))
+
+/* ---------------------------------------------------------------------
+ 64-bit version of Keccak_f(1600)
+ ---------------------------------------------------------------------
+*/
+void Keccak_f_64(uint64_t s[25])
+{ uint64_t t[5];
+ uint8_t i, j, round;
+
+ for(round=0; round<24; ++round)
+ { /* Theta function */
+ for(i=0; i<5; ++i)
+ t[i] = s[i] ^ s[5+i] ^ s[10+i] ^ s[15+i] ^ s[20+i];
+ for(i=0; i<5; ++i, s+=5)
+ { s[0] ^= t[4] ^ ROTATE64(t[1], 1);
+ s[1] ^= t[0] ^ ROTATE64(t[2], 1);
+ s[2] ^= t[1] ^ ROTATE64(t[3], 1);
+ s[3] ^= t[2] ^ ROTATE64(t[4], 1);
+ s[4] ^= t[3] ^ ROTATE64(t[0], 1);
+ }
+ s -= 25;
+
+ /* Rho function */
+ for(i=1; i<25; ++i)
+ s[i] = ROTATE64(s[i], Rho[i]);
+
+ /* Pi function */
+ for(t[1] = s[i=1]; (j=Pi[i]) > 1; s[i]=s[j], i=j);
+ s[i] = t[1];
+
+ /* Chi function */
+ for(i=0; i<5; ++i, s += 5)
+ { t[0] = (~s[1]) & s[2];
+ t[1] = (~s[2]) & s[3];
+ t[2] = (~s[3]) & s[4];
+ t[3] = (~s[4]) & s[0];
+ t[4] = (~s[0]) & s[1];
+ for(j=0; j<5; ++j) s[j] ^= t[j];
+ }
+ s -= 25;
+
+ /* Iota function */
+ t[0] = Iota[round];
+ *s ^= (t[0] | (t[0]<<11) | (t[0]<<26) | (t[0]<<57))
+ & 0x800000008000808BULL; /* set & mask bits 0,1,3,7,15,31,63 */
+ }
+}
+
+
+/* ---------------------------------------------------------------------
+ 8-bit version of Keccak_f(1600)
+ ---------------------------------------------------------------------
+*/
+void Keccak_f_8(uint8_t s[200])
+{ uint8_t t[40], i, j, k, round;
+
+ for(round=0; round<24; ++round)
+ { /* Theta function */
+ for(i=0; i<40; ++i)
+ t[i]=s[i]^s[40+i]^s[80+i]^s[120+i]^s[160+i];
+ for(i=0; i<200; i+=8)
+ for(j = (i+32)%40, k=0; k<8; ++k)
+ s[i+k] ^= t[j+k];
+ for(i=0; i<40; t[i] = (t[i]<<1)|j, i+=8)
+ for(j = t[i+7]>>7, k=7; k; --k)
+ t[i+k] = (t[i+k]<<1)|(t[i+k-1]>>7);
+ for(i=0; i<200; i+=8)
+ for(j = (i+8)%40, k=0; k<8; ++k)
+ s[i+k] ^= t[j+k];
+
+ /* Rho function */
+ for(i=8; i<200; i+=8)
+ { for(j = Rho[i>>3]>>3, k=0; k<8; ++k) /* j:=bytes to shift, s->t */
+ t[(k+j)&7] = s[i+k];
+ for(j = Rho[i>>3]&7, k=7; k; --k) /* j:=bits to shift, t->s */
+ s[i+k] = (t[k]<<j) | (t[k-1]>>(8-j));
+ s[i] = (t[0]<<j) | (t[7]>>(8-j));
+ }
+
+ /* Pi function */
+ for(k=8; k<16; ++k) t[k] = s[k]; /* =memcpy(t+8, s+8, 8) */
+ for(i=1; (j=Pi[i])>1; i=j)
+ for(k=0; k<8; ++k) /* =memcpy(s+(i<<3), s+(j<<3), 8) */
+ s[(i<<3)|k] = s[(j<<3)|k];
+ for(k=0; k<8; ++k) /* =memcpy(s+(i<<3), t+8, 8) */
+ s[(i<<3)|k] = t[k+8];
+
+ /* Chi function */
+ for(i=0; i<200; i+=40)
+ { for(j=0; j<40; ++j)
+ t[j]=(~s[i+(j+8)%40]) & s[i+(j+16)%40];
+ for(j=0; j<40; ++j) s[i+j]^=t[j];
+ }
+
+ /* Iota function */
+ k = Iota[round];
+ s[0] ^= k & 0x8B; /* bits 0, 1, 3, 7 */
+ s[1] ^= (k<<3)&0x80; /* bit 15 */
+ s[3] ^= (k<<2)&0x80; /* bit 31 */
+ s[7] ^= (k<<1)&0x80; /* bit 63 */
+
+ }
+}
+
+/* ---------------------------------------------------------------------
+ 32-bit version of Keccak_f(1600)
+ ---------------------------------------------------------------------
+*/
+void Keccak_f_32(uint32_t s[50])
+{ uint32_t t[10];
+ uint8_t i, j, round, k;
+
+ for(round=0; round<24; ++round)
+ { /* Theta function */
+ for(i=0; i<10; ++i)
+ t[i] = s[i] ^ s[10+i] ^ s[20+i] ^ s[30+i] ^ s[40+i];
+ for(i=0; i<5; ++i)
+ for(j=8, k=2; ; j%=10, k=(k+2)%10)
+ { *s++ ^= t[j++] ^ ((t[k]<<1)|(t[k+1]>>31));
+ *s++ ^= t[j++] ^ ((t[k+1]<<1)|(t[k]>>31));
+ if(j==8) break;
+ }
+ s -= 50;
+
+ /* Rho function */
+ for(i=2; i<50; i+=2)
+ { k = Rho[i>>1] & 0x1f;
+ t[0] = (s[i+1] << k) | (s[i] >> (32-k));
+ t[1] = (s[i] << k) | (s[i+1] >> (32-k));
+ k = Rho[i>>1] >> 5;
+ s[i] = t[1-k], s[i+1] = t[k];
+ }
+
+ /* Pi function */
+ for(i=2, t[0]=s[2], t[1]=s[3]; (j=(Pi[i>>1]<<1))>2; i=j)
+ s[i]=s[j], s[i+1]=s[j+1];
+ s[i]=t[0], s[i+1]=t[1];
+
+ /* Chi function */
+ for(i=0; i<5; ++i, s+=10)
+ { for(j=0; j<10; ++j)
+ t[j] = (~s[(j+2)%10]) & s[(j+4)%10];
+ for(j=0; j<10; ++j)
+ s[j] ^= t[j];
+ }
+ s -= 50;
+
+ /* Iota function */
+ t[0] = Iota[round];
+ s[0] ^= (t[0] | (t[0]<<11) | (t[0]<<26)) & 0x8000808B;
+ s[1] ^= (t[0]<<25) & 0x80000000;
+ }
+}
+
diff --git a/src/gsm/tuak/KeccakP-1600-3gpp.h b/src/gsm/tuak/KeccakP-1600-3gpp.h
new file mode 100644
index 00000000..a23cc460
--- /dev/null
+++ b/src/gsm/tuak/KeccakP-1600-3gpp.h
@@ -0,0 +1,25 @@
+/* -----------------------------------------------------------------------
+ * code extracted from 3GPP TS 35.231, annex E for Keccak core functions
+ * https://portal.3gpp.org/desktopmodules/Specifications/SpecificationDetails.aspx?specificationId=2402
+ *-----------------------------------------------------------------------*/
+
+/* this is the trick to make the code cross-platform
+ * at least, Win32 / Linux */
+
+#if defined(_WIN32) || defined(__WIN32__)
+# include <windows.h>
+# define EXPORTIT __declspec(dllexport)
+#else
+# define EXPORTIT
+#endif
+
+#include <stdint.h>
+
+/*------------------------------------------------------------------------
+ * KeccakP-1600-3gpp.h
+ *------------------------------------------------------------------------*/
+
+EXPORTIT void Keccak_f_8 (uint8_t s[200]);
+EXPORTIT void Keccak_f_32(uint32_t s[50]);
+EXPORTIT void Keccak_f_64(uint64_t s[25]);
+
diff --git a/src/gsm/tuak/tuak.c b/src/gsm/tuak/tuak.c
new file mode 100644
index 00000000..c044a377
--- /dev/null
+++ b/src/gsm/tuak/tuak.c
@@ -0,0 +1,372 @@
+/* (C) 2023 by Harald Welte <laforge@osmocom.org>
+ *
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <stdint.h>
+#include <string.h>
+#include <string.h>
+#include <errno.h>
+
+#include <osmocom/core/utils.h>
+
+#include "KeccakP-1600-3gpp.h"
+
+/* TUAK authentication algorithm
+ * as proposed by 3GPP as an alternative to Milenage
+ * algorithm based on SHA-3 (more exactly its KeccakP-1600 permutation)
+ * see 3GPP TS 35.231, 232 and 233 */
+
+static unsigned int g_keccak_iterations = 1;
+static const char algoname[] = "TUAK1.0";
+const uint8_t zero16[16] = { 0, };
+
+void tuak_set_keccak_iterations(unsigned int i)
+{
+ g_keccak_iterations = i;
+}
+
+/* append data from 'input' to 'buf' at 'idx', reversing byte order */
+#define PUSH_DATA(buf, idx, input, nbytes) \
+ for (int i = nbytes-1; i >= 0; i--) { \
+ buf[idx++] = input[i]; \
+ }
+
+/* like memcpy(), but reversing they order of bytes */
+void memcpy_reverse(uint8_t *dst, const uint8_t *src, size_t len)
+{
+ for (size_t i = 0; i < len; i++)
+ dst[i] = src[len-i-1];
+}
+
+static void tuak_core(uint8_t buf[200], const uint8_t *opc, uint8_t instance, const uint8_t *_rand,
+ const uint8_t *amf, const uint8_t *sqn, const uint8_t *k, uint8_t k_len_bytes,
+ unsigned int keccac_iterations)
+{
+ unsigned int idx = 0;
+
+ PUSH_DATA(buf, idx, opc, 32);
+ buf[idx++] = instance;
+ PUSH_DATA(buf, idx, algoname, strlen(algoname)); /* without trailing NUL */
+ PUSH_DATA(buf, idx, _rand, 16);
+ PUSH_DATA(buf, idx, amf, 2);
+ PUSH_DATA(buf, idx, sqn, 6);
+ PUSH_DATA(buf, idx, k, k_len_bytes);
+ memset(buf+idx, 0, 32-k_len_bytes); idx += 32-k_len_bytes;
+ buf[idx++] = 0x1f;
+ memset(buf+idx, 0, 38); idx += 38;
+ buf[idx++] = 0x80;
+ memset(buf+idx, 0, 64); idx += 64;
+ OSMO_ASSERT(idx == 200);
+
+ for (unsigned int i = 0; i < keccac_iterations; i++)
+ Keccak_f_64((uint64_t *) buf);
+}
+
+/**
+ * tuak_f1 - TUAK f1 algorithm
+ * @opc: OPc = 256-bit value derived from OP and K
+ * @k: K = 128-bit or 256-bit subscriber key
+ * @_rand: RAND = 128-bit random challenge
+ * @sqn: SQN = 48-bit sequence number
+ * @amf: AMF = 16-bit authentication management field
+ * @mac_a: Buffer for MAC-A = 64/128/256-bit network authentication code
+ * Returns: 0 on success, -1 on failure
+ */
+int tuak_f1(const uint8_t *opc, const uint8_t *k, uint8_t k_len_bytes, const uint8_t *_rand,
+ const uint8_t *sqn, const uint8_t *amf, uint8_t *mac_a, uint8_t mac_a_len_bytes,
+ unsigned int keccac_iterations)
+{
+ uint8_t buf[200];
+ uint8_t instance = 0x00;
+
+ switch (mac_a_len_bytes) {
+ case 8:
+ instance |= 0x08;
+ break;
+ case 16:
+ instance |= 0x10;
+ break;
+ case 32:
+ instance |= 0x20;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (k_len_bytes) {
+ case 16:
+ break;
+ case 32:
+ instance |= 0x01;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ tuak_core(buf, opc, instance, _rand, amf, sqn, k, k_len_bytes, keccac_iterations);
+
+ memcpy_reverse(mac_a, buf, mac_a_len_bytes);
+
+ return 0;
+}
+
+/**
+ * tuak_f1star - TUAK f1* algorithm
+ * @opc: OPc = 256-bit value derived from OP and K
+ * @k: K = 128-bit or 256-bit subscriber key
+ * @_rand: RAND = 128-bit random challenge
+ * @sqn: SQN = 48-bit sequence number
+ * @amf: AMF = 16-bit authentication management field
+ * @mac_s: Buffer for MAC-S = 64/128/256-bit resync authentication code
+ * Returns: 0 on success, -1 on failure
+ */
+int tuak_f1star(const uint8_t *opc, const uint8_t *k, uint8_t k_len_bytes, const uint8_t *_rand,
+ const uint8_t *sqn, const uint8_t *amf, uint8_t *mac_s, uint8_t mac_s_len_bytes,
+ unsigned int keccac_iterations)
+{
+ uint8_t buf[200];
+ uint8_t instance = 0x80;
+
+ switch (mac_s_len_bytes) {
+ case 8:
+ instance |= 0x08;
+ break;
+ case 16:
+ instance |= 0x10;
+ break;
+ case 32:
+ instance |= 0x20;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (k_len_bytes) {
+ case 16:
+ break;
+ case 32:
+ instance |= 0x01;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ tuak_core(buf, opc, instance, _rand, amf, sqn, k, k_len_bytes, keccac_iterations);
+
+ memcpy_reverse(mac_s, buf, mac_s_len_bytes);
+
+ return 0;
+}
+
+/**
+ * tuak_f2345 - TUAK f2, f3, f4, f5, algorithms
+ * @opc: OPc = 256-bit value derived from OP and K
+ * @k: K = 128/256-bit subscriber key
+ * @_rand: RAND = 128-bit random challenge
+ * @res: Buffer for RES = 32/64/128/256-bit signed response (f2), or %NULL
+ * @ck: Buffer for CK = 128/256-bit confidentiality key (f3), or %NULL
+ * @ik: Buffer for IK = 128/256-bit integrity key (f4), or %NULL
+ * @ak: Buffer for AK = 48-bit anonymity key (f5), or %NULL
+ * Returns: 0 on success, -1 on failure
+ */
+int tuak_f2345(const uint8_t *opc, const uint8_t *k, uint8_t k_len_bytes,
+ const uint8_t *_rand, uint8_t *res, uint8_t res_len_bytes,
+ uint8_t *ck, uint8_t ck_len_bytes,
+ uint8_t *ik, uint8_t ik_len_bytes, uint8_t *ak, unsigned int keccac_iterations)
+{
+ uint8_t buf[200];
+ uint8_t instance = 0x40;
+
+ switch (res_len_bytes) {
+ case 4:
+ break;
+ case 8:
+ instance |= 0x08;
+ break;
+ case 16:
+ instance |= 0x10;
+ break;
+ case 32:
+ instance |= 0x20;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (ck_len_bytes) {
+ case 16:
+ break;
+ case 32:
+ instance |= 0x04;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (ik_len_bytes) {
+ case 16:
+ break;
+ case 32:
+ instance |= 0x02;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (k_len_bytes) {
+ case 16:
+ break;
+ case 32:
+ instance |= 0x01;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ tuak_core(buf, opc, instance, _rand, zero16, zero16, k, k_len_bytes, keccac_iterations);
+
+ if (res)
+ memcpy_reverse(res, buf, res_len_bytes);
+
+ if (ck)
+ memcpy_reverse(ck, buf + 32, ck_len_bytes);
+
+ if (ik)
+ memcpy_reverse(ik, buf + 64, ik_len_bytes);
+
+ if (ak)
+ memcpy_reverse(ak, buf + 96, 6);
+
+ return 0;
+}
+
+/**
+ * tuak_f5star - TUAK f5* algorithm
+ * @opc: OPc = 256-bit value derived from OP and K
+ * @k: K = 128/256-bit subscriber key
+ * @_rand: RAND = 128-bit random challenge
+ * @ak: Buffer for AK = 48-bit anonymity key (f5)
+ * Returns: 0 on success, -1 on failure
+ */
+int tuak_f5star(const uint8_t *opc, const uint8_t *k, uint8_t k_len_bytes,
+ const uint8_t *_rand, uint8_t *ak, unsigned int keccac_iterations)
+{
+ uint8_t buf[200];
+ uint8_t instance = 0xc0;
+
+ switch (k_len_bytes) {
+ case 16:
+ break;
+ case 32:
+ instance += 1;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ tuak_core(buf, opc, instance, _rand, zero16, zero16, k, k_len_bytes, keccac_iterations);
+
+ memcpy_reverse(ak, buf + 96, 6);
+
+ return 0;
+}
+
+/**
+ * tuak_generate - Generate AKA AUTN,IK,CK,RES
+ * @opc: OPc = 256-bit operator variant algorithm configuration field (encr.)
+ * @amf: AMF = 16-bit authentication management field
+ * @k: K = 128/256-bit subscriber key
+ * @sqn: SQN = 48-bit sequence number
+ * @_rand: RAND = 128-bit random challenge
+ * @autn: Buffer for AUTN = 128-bit authentication token
+ * @ik: Buffer for IK = 128/256-bit integrity key (f4), or %NULL
+ * @ck: Buffer for CK = 128/256-bit confidentiality key (f3), or %NULL
+ * @res: Buffer for RES = 32/64/128-bit signed response (f2), or %NULL
+ * @res_len: Max length for res; set to used length or 0 on failure
+ */
+void tuak_generate(const uint8_t *opc, const uint8_t *amf, const uint8_t *k, uint8_t k_len_bytes,
+ const uint8_t *sqn, const uint8_t *_rand, uint8_t *autn, uint8_t *ik,
+ uint8_t *ck, uint8_t *res, size_t *res_len)
+{
+ int i;
+ uint8_t mac_a[8], ak[6];
+
+ if (*res_len < 4) {
+ *res_len = 0;
+ return;
+ }
+ if (tuak_f1(opc, k, k_len_bytes, _rand, sqn, amf, mac_a, sizeof(mac_a), g_keccak_iterations) ||
+ tuak_f2345(opc, k, k_len_bytes, _rand, res, *res_len, ck, 16, ik, 16, ak, g_keccak_iterations)) {
+ *res_len = 0;
+ return;
+ }
+
+ /* AUTN = (SQN ^ AK) || AMF || MAC */
+ for (i = 0; i < 6; i++)
+ autn[i] = sqn[i] ^ ak[i];
+ memcpy(autn + 6, amf, 2);
+ memcpy(autn + 8, mac_a, 8);
+}
+
+
+/**
+ * tuak_auts - Milenage AUTS validation
+ * @opc: OPc = 256-bit operator variant algorithm configuration field (encr.)
+ * @k: K = 128/256-bit subscriber key
+ * @_rand: RAND = 128-bit random challenge
+ * @auts: AUTS = 112-bit authentication token from client
+ * @sqn: Buffer for SQN = 48-bit sequence number
+ * Returns: 0 = success (sqn filled), -1 on failure
+ */
+int tuak_auts(const uint8_t *opc, const uint8_t *k, uint8_t k_len_bytes,
+ const uint8_t *_rand, const uint8_t *auts, uint8_t *sqn)
+{
+ uint8_t amf[2] = { 0x00, 0x00 }; /* TS 33.102 v7.0.0, 6.3.3 */
+ uint8_t ak[6], mac_s[8];
+ int i;
+
+ if (tuak_f5star(opc, k, k_len_bytes, _rand, ak, g_keccak_iterations))
+ return -1;
+ for (i = 0; i < 6; i++)
+ sqn[i] = auts[i] ^ ak[i];
+ if (tuak_f1star(opc, k, k_len_bytes, _rand, sqn, amf, mac_s, 8, g_keccak_iterations) ||
+ memcmp(mac_s, auts + 6, 8) != 0)
+ return -1;
+ return 0;
+}
+
+int tuak_opc_gen(uint8_t *opc, const uint8_t *k, uint8_t k_len_bytes, const uint8_t *op)
+{
+ uint8_t buf[200];
+ uint8_t instance;
+
+ switch (k_len_bytes) {
+ case 16:
+ instance = 0x00;
+ break;
+ case 32:
+ instance = 0x01;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ tuak_core(buf, op, instance, zero16, zero16, zero16, k, k_len_bytes, g_keccak_iterations);
+
+ memcpy_reverse(opc, buf, 32);
+
+ return 0;
+}
diff --git a/src/gsm/tuak/tuak.h b/src/gsm/tuak/tuak.h
new file mode 100644
index 00000000..1a808224
--- /dev/null
+++ b/src/gsm/tuak/tuak.h
@@ -0,0 +1,33 @@
+#pragma once
+#include <stdint.h>
+
+/* low-level functions */
+
+int tuak_f1(const uint8_t *opc, const uint8_t *k, uint8_t k_len_bytes, const uint8_t *_rand,
+ const uint8_t *sqn, const uint8_t *amf, uint8_t *mac_a, uint8_t mac_a_len_bytes,
+ unsigned int keccac_iterations);
+
+int tuak_f1star(const uint8_t *opc, const uint8_t *k, uint8_t k_len_bytes, const uint8_t *_rand,
+ const uint8_t *sqn, const uint8_t *amf, uint8_t *mac_s, uint8_t mac_s_len_bytes,
+ unsigned int keccac_iterations);
+
+int tuak_f2345(const uint8_t *opc, const uint8_t *k, uint8_t k_len_bytes,
+ const uint8_t *_rand, uint8_t *res, uint8_t res_len_bytes,
+ uint8_t *ck, uint8_t ck_len_bytes,
+ uint8_t *ik, uint8_t ik_len_bytes, uint8_t *ak, unsigned int keccac_iterations);
+
+int tuak_f5star(const uint8_t *opc, const uint8_t *k, uint8_t k_len_bytes,
+ const uint8_t *_rand, uint8_t *ak, unsigned int keccac_iterations);
+
+/* high-level API */
+
+void tuak_set_keccak_iterations(unsigned int i);
+
+void tuak_generate(const uint8_t *opc, const uint8_t *amf, const uint8_t *k, uint8_t k_len_bytes,
+ const uint8_t *sqn, const uint8_t *_rand, uint8_t *autn, uint8_t *ik,
+ uint8_t *ck, uint8_t *res, size_t *res_len);
+
+int tuak_auts(const uint8_t *opc, const uint8_t *k, uint8_t k_len_bytes,
+ const uint8_t *_rand, const uint8_t *auts, uint8_t *sqn);
+
+int tuak_opc_gen(uint8_t *opc, const uint8_t *k, uint8_t k_len_bytes, const uint8_t *op);
diff --git a/src/isdn/Makefile.am b/src/isdn/Makefile.am
new file mode 100644
index 00000000..3e7f86eb
--- /dev/null
+++ b/src/isdn/Makefile.am
@@ -0,0 +1,26 @@
+# 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 modifications: https://www.gnu.org/software/libtool/manual/html_node/Versioning.html
+LIBVERSION=1:0:1
+
+AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_builddir)/include
+AM_CFLAGS = -Wall $(TALLOC_CFLAGS)
+
+if ENABLE_PSEUDOTALLOC
+AM_CPPFLAGS += -I$(top_srcdir)/src/pseudotalloc
+endif
+
+noinst_LTLIBRARIES = libisdnint.la
+lib_LTLIBRARIES = libosmoisdn.la
+
+libisdnint_la_SOURCES = i460_mux.c lapd_core.c v110.c v110_ta.c
+
+libisdnint_la_LDFLAGS = -no-undefined
+libisdnint_la_LIBADD = $(top_builddir)/src/core/libosmocore.la
+
+libosmoisdn_la_SOURCES =
+libosmoisdn_la_LDFLAGS = $(LTLDFLAGS_OSMOISDN) -version-info $(LIBVERSION) -no-undefined
+libosmoisdn_la_LIBADD = libisdnint.la $(TALLOC_LIBS)
+
+EXTRA_DIST = libosmoisdn.map
+EXTRA_libosmoisdn_la_DEPENDENCIES = libosmoisdn.map
diff --git a/src/gsm/i460_mux.c b/src/isdn/i460_mux.c
index 91ab2a19..b070bbdc 100644
--- a/src/gsm/i460_mux.c
+++ b/src/isdn/i460_mux.c
@@ -14,11 +14,6 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
- * MA 02110-1301, USA.
*/
#include <errno.h>
@@ -26,10 +21,12 @@
#include <osmocom/core/bits.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/msgb.h>
-#include <osmocom/gsm/i460_mux.h>
+#include <osmocom/isdn/i460_mux.h>
-/* count the number of sub-channels in this I460 slot */
-static int osmo_i460_subchan_count(struct osmo_i460_timeslot *ts)
+/*! count the number of sub-channels in this I.460 slot.
+ * \param[in] ts timeslot that holds the I.460 subchannels.
+ * \return number of subchannels. */
+int osmo_i460_subchan_count(struct osmo_i460_timeslot *ts)
{
int i, num_used = 0;
@@ -124,10 +121,10 @@ static void demux_subchan_extract_bits(struct osmo_i460_subchan *schan, const ui
}
}
-/*! Data from E1 timeslot into de-multiplexer
- * \param[in] ts timeslot state
- * \param[in] data input data bytes as received from E1/T1
- * \param[in] data_len length of data in bytes */
+/*! Feed multiplexed data (from an E1 timeslot) into de-multiplexer.
+ * \param[in] ts timeslot state.
+ * \param[in] data input data bytes as received from E1/T1.
+ * \param[in] data_len length of data in bytes. */
void osmo_i460_demux_in(struct osmo_i460_timeslot *ts, const uint8_t *data, size_t data_len)
{
struct osmo_i460_subchan *schan;
@@ -200,7 +197,7 @@ static ubit_t mux_schan_provide_bit(struct osmo_i460_subchan *schan)
/*! provide one byte with the subchan-specific bits of given sub-channel.
* \param[in] schan sub-channel that is to provide bits
- * \parma[out] mask bitmask of those bits filled in
+ * \param[out] mask bitmask of those bits filled in
* \returns bits of given sub-channel */
static uint8_t mux_subchan_provide_bits(struct osmo_i460_subchan *schan, uint8_t *mask)
{
@@ -248,16 +245,14 @@ static uint8_t mux_subchan_provide_bits(struct osmo_i460_subchan *schan, uint8_t
/* provide one byte of multiplexed I.460 bits */
static uint8_t mux_timeslot_provide_bits(struct osmo_i460_timeslot *ts)
{
- int i, count = 0;
uint8_t ret = 0xff; /* unused bits must be '1' as per I.460 */
- for (i = 0; i < ARRAY_SIZE(ts->schan); i++) {
+ for (int i = 0; i < ARRAY_SIZE(ts->schan); i++) {
struct osmo_i460_subchan *schan = &ts->schan[i];
uint8_t bits, mask;
if (schan->rate == OSMO_I460_RATE_NONE)
continue;
- count++;
bits = mux_subchan_provide_bits(schan, &mask);
ret &= ~mask;
ret |= bits;
@@ -267,11 +262,10 @@ static uint8_t mux_timeslot_provide_bits(struct osmo_i460_timeslot *ts)
}
-/*! Data from E1 timeslot into de-multiplexer
- * \param[in] ts timeslot state
- * \param[out] out caller-provided buffer where to store generated output bytes
- * \param[in] out_len number of bytes to be stored at out
- */
+/*! Get multiplexed data from de-multiplexer (for feeding it into an E1 timeslot).
+ * \param[in] ts timeslot state.
+ * \param[out] out caller-provided buffer where to store generated output bytes.
+ * \param[in] out_len number of bytes to be stored at out. */
int osmo_i460_mux_out(struct osmo_i460_timeslot *ts, uint8_t *out, size_t out_len)
{
int i;
diff --git a/src/gsm/lapd_core.c b/src/isdn/lapd_core.c
index ed0b3209..b32ed263 100644
--- a/src/gsm/lapd_core.c
+++ b/src/isdn/lapd_core.c
@@ -18,10 +18,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
/*! \addtogroup lapd
@@ -73,6 +69,7 @@
//#define TEST_CONTENT_RESOLUTION_NETWORK
#include <stdio.h>
+#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include <errno.h>
@@ -82,7 +79,7 @@
#include <osmocom/core/msgb.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/talloc.h>
-#include <osmocom/gsm/lapd_core.h>
+#include <osmocom/isdn/lapd_core.h>
#include <osmocom/gsm/rsl.h>
/* TS 04.06 Table 4 / Section 3.8.1 */
@@ -111,7 +108,7 @@
static void lapd_t200_cb(void *data);
static void lapd_t203_cb(void *data);
-static int lapd_send_i(struct lapd_msg_ctx *lctx, int line);
+static int lapd_send_i(struct lapd_datalink *dl, int line, bool rts);
static int lapd_est_req(struct osmo_dlsap_prim *dp, struct lapd_msg_ctx *lctx);
/* UTILITY FUNCTIONS */
@@ -172,12 +169,17 @@ static void lapd_dl_flush_hist(struct lapd_datalink *dl)
}
}
-static void lapd_dl_flush_tx(struct lapd_datalink *dl)
+static void lapd_dl_flush_tx_queue(struct lapd_datalink *dl)
{
struct msgb *msg;
while ((msg = msgb_dequeue(&dl->tx_queue)))
msgb_free(msg);
+}
+
+static void lapd_dl_flush_tx(struct lapd_datalink *dl)
+{
+ lapd_dl_flush_tx_queue(dl);
lapd_dl_flush_hist(dl);
}
@@ -202,11 +204,35 @@ static inline const char *lapd_state_name(enum lapd_state state)
static void lapd_start_t200(struct lapd_datalink *dl)
{
- if (osmo_timer_pending(&dl->t200))
- return;
- LOGDL(dl, LOGL_INFO, "start T200 (timeout=%d.%06ds)\n",
- dl->t200_sec, dl->t200_usec);
- osmo_timer_schedule(&dl->t200, dl->t200_sec, dl->t200_usec);
+ if ((dl->lapd_flags & LAPD_F_RTS)) {
+ if (dl->t200_rts != LAPD_T200_RTS_OFF)
+ return;
+ LOGDL(dl, LOGL_INFO, "Start T200. (pending until triggered by RTS)\n");
+ dl->t200_rts = LAPD_T200_RTS_PENDING;
+ } else {
+ if (osmo_timer_pending(&dl->t200))
+ return;
+ LOGDL(dl, LOGL_INFO, "Start T200 (timeout=%d.%06ds).\n", dl->t200_sec, dl->t200_usec);
+ osmo_timer_schedule(&dl->t200, dl->t200_sec, dl->t200_usec);
+ }
+}
+
+/*! Handle timeout condition of T200 in RTS mode.
+ * The caller (LAPDm code) implements the T200 timer and must detect timeout condition.
+ * The function gets called by LAPDm code when it detects a timeout of T200.
+ * \param[in] dl caller-allocated datalink structure */
+int lapd_t200_timeout(struct lapd_datalink *dl)
+{
+ OSMO_ASSERT((dl->lapd_flags & LAPD_F_RTS));
+
+ if (dl->t200_rts != LAPD_T200_RTS_RUNNING)
+ return -EINVAL;
+
+ dl->t200_rts = LAPD_T200_RTS_OFF;
+
+ lapd_t200_cb(dl);
+
+ return 0;
}
static void lapd_start_t203(struct lapd_datalink *dl)
@@ -219,10 +245,24 @@ static void lapd_start_t203(struct lapd_datalink *dl)
static void lapd_stop_t200(struct lapd_datalink *dl)
{
- if (!osmo_timer_pending(&dl->t200))
- return;
+ if ((dl->lapd_flags & LAPD_F_RTS)) {
+ if (dl->t200_rts == LAPD_T200_RTS_OFF)
+ return;
+ dl->t200_rts = LAPD_T200_RTS_OFF;
+ } else {
+ if (!osmo_timer_pending(&dl->t200))
+ return;
+ osmo_timer_del(&dl->t200);
+ }
LOGDL(dl, LOGL_INFO, "stop T200\n");
- osmo_timer_del(&dl->t200);
+}
+
+static bool lapd_is_t200_started(struct lapd_datalink *dl)
+{
+ if ((dl->lapd_flags & LAPD_F_RTS))
+ return (dl->t200_rts != LAPD_T200_RTS_OFF);
+ else
+ return osmo_timer_pending(&dl->t200);
}
static void lapd_stop_t203(struct lapd_datalink *dl)
@@ -357,6 +397,21 @@ void lapd_dl_reset(struct lapd_datalink *dl)
lapd_dl_newstate(dl, LAPD_STATE_IDLE);
}
+/*! Set lapd_flags to change behaviour
+ * \param[in] dl \ref lapd_datalink instance
+ * \param[in] flags \ref lapd_flags */
+int lapd_dl_set_flags(struct lapd_datalink *dl, unsigned int flags)
+{
+ if (lapd_is_t200_started(dl) && (flags & LAPD_F_RTS) != (dl->lapd_flags & LAPD_F_RTS)) {
+ LOGDL(dl, LOGL_ERROR, "Changing RTS flag not allowed while T200 is running.\n");
+ return -EINVAL;
+ }
+
+ dl->lapd_flags = flags;
+
+ return 0;
+}
+
/* reset and de-allocate history buffer */
void lapd_dl_exit(struct lapd_datalink *dl)
{
@@ -596,6 +651,8 @@ static void lapd_t200_cb(void *data)
switch (dl->state) {
case LAPD_STATE_SABM_SENT:
/* 5.4.1.3 */
+ /* increment re-transmission counter */
+ dl->retrans_ctr++;
if (dl->retrans_ctr >= dl->n200_est_rel + 1) {
/* flush tx and send buffers */
lapd_dl_flush_tx(dl);
@@ -614,18 +671,16 @@ static void lapd_t200_cb(void *data)
}
/* retransmit SABM command */
lapd_send_resend(dl);
- /* increment re-transmission counter */
- dl->retrans_ctr++;
/* restart T200 (PH-READY-TO-SEND) */
lapd_start_t200(dl);
break;
case LAPD_STATE_DISC_SENT:
/* 5.4.4.3 */
+ /* increment re-transmission counter */
+ dl->retrans_ctr++;
if (dl->retrans_ctr >= dl->n200_est_rel + 1) {
/* send MDL ERROR INIDCATION to L3 */
mdl_error(MDL_CAUSE_T200_EXPIRED, &dl->lctx);
- /* send RELEASE INDICATION to L3 */
- send_dl_simple(PRIM_DL_REL, PRIM_OP_CONFIRM, &dl->lctx);
/* flush tx and send buffers */
lapd_dl_flush_tx(dl);
lapd_dl_flush_send(dl);
@@ -634,12 +689,12 @@ static void lapd_t200_cb(void *data)
/* NOTE: we must not change any other states or buffers
* and queues, since we may reconnect after handover
* failure. the buffered messages is replaced there */
+ /* send RELEASE INDICATION to L3 */
+ send_dl_simple(PRIM_DL_REL, PRIM_OP_CONFIRM, &dl->lctx);
break;
}
/* retransmit DISC command */
lapd_send_resend(dl);
- /* increment re-transmission counter */
- dl->retrans_ctr++;
/* restart T200 (PH-READY-TO-SEND) */
lapd_start_t200(dl);
break;
@@ -741,12 +796,14 @@ static void lapd_t203_cb(void *data)
lapd_start_t200(dl);
}
-/* 5.5.3.1: Common function to acknowlege frames up to the given N(R) value */
-static void lapd_acknowledge(struct lapd_msg_ctx *lctx)
+/* 5.5.3.1: Common function to acknowlege frames up to the given N(R) value
+ * In case of a sequence error, the cause is returned with negative sign. */
+static int lapd_acknowledge(struct lapd_msg_ctx *lctx)
{
struct lapd_datalink *dl = lctx->dl;
uint8_t nr = lctx->n_recv;
- int s = 0, rej = 0, t200_reset = 0;
+ int s = 0, rej = 0;
+ bool t200_reset = false;
int i, h;
/* supervisory frame ? */
@@ -771,9 +828,8 @@ static void lapd_acknowledge(struct lapd_msg_ctx *lctx)
* link layer entity shall reset the timer T200 on
* receipt of a valid I frame with N(R) higher than V(A),
* or an REJ with an N(R) equal to V(A). */
- if ((!rej && nr != dl->v_ack)
- || (rej && nr == dl->v_ack)) {
- t200_reset = 1;
+ if ((!rej && nr != dl->v_ack) || (rej && nr == dl->v_ack)) {
+ t200_reset = true;
lapd_stop_t200(dl);
/* 5.5.3.1 Note 1 + 2 imply timer recovery cond. */
}
@@ -781,10 +837,9 @@ static void lapd_acknowledge(struct lapd_msg_ctx *lctx)
* N(R) is called valid, if and only if
* (N(R)-V(A)) mod 8 <= (V(S)-V(A)) mod 8.
*/
- if (sub_mod(nr, dl->v_ack, dl->v_range)
- > sub_mod(dl->v_send, dl->v_ack, dl->v_range)) {
+ if (sub_mod(nr, dl->v_ack, dl->v_range) > sub_mod(dl->v_send, dl->v_ack, dl->v_range)) {
LOGDL(dl, LOGL_NOTICE, "N(R) sequence error\n");
- mdl_error(MDL_CAUSE_SEQ_ERR, lctx);
+ return -MDL_CAUSE_SEQ_ERR;
}
}
@@ -805,11 +860,10 @@ static void lapd_acknowledge(struct lapd_msg_ctx *lctx)
/* Stop T203, if running */
lapd_stop_t203(dl);
/* Start T203, if T200 is not running in MF EST state, if enabled */
- if (!osmo_timer_pending(&dl->t200)
- && (dl->t203_sec || dl->t203_usec)
- && (dl->state == LAPD_STATE_MF_EST)) {
+ if (!lapd_is_t200_started(dl) && (dl->t203_sec || dl->t203_usec) && (dl->state == LAPD_STATE_MF_EST))
lapd_start_t203(dl);
- }
+
+ return 0;
}
/* L1 -> L2 */
@@ -946,7 +1000,7 @@ static int lapd_rx_u_sabm(struct msgb *msg, struct lapd_msg_ctx *lctx)
dl->cont_res = lapd_msgb_alloc(length, "CONT RES");
memcpy(msgb_put(dl->cont_res, length), msg->l3h,
length);
- LOGDL(dl, LOGL_NOTICE, "Store content res.\n");
+ LOGDL(dl, LOGL_INFO, "Store content res.\n");
}
/* send notification to L3 */
if (length == 0) {
@@ -1230,13 +1284,12 @@ static int lapd_rx_u_ua(struct msgb *msg, struct lapd_msg_ctx *lctx)
|| !!memcmp(dl->tx_hist[0].msg->data, msg->l3h,
length)) {
LOGDL(dl, LOGL_INFO, "**** UA response mismatches ****\n");
- rc = send_dl_simple(PRIM_DL_REL,
- PRIM_OP_INDICATION, lctx);
- msgb_free(msg);
/* go to idle state */
lapd_dl_flush_tx(dl);
lapd_dl_flush_send(dl);
lapd_dl_newstate(dl, LAPD_STATE_IDLE);
+ rc = send_dl_simple(PRIM_DL_REL, PRIM_OP_INDICATION, lctx);
+ msgb_free(msg);
return 0;
}
}
@@ -1247,7 +1300,7 @@ static int lapd_rx_u_ua(struct msgb *msg, struct lapd_msg_ctx *lctx)
/* enter multiple-frame-established state */
lapd_dl_newstate(dl, LAPD_STATE_MF_EST);
/* send outstanding frames, if any (resume / reconnect) */
- lapd_send_i(lctx, __LINE__);
+ lapd_send_i(dl, __LINE__, false);
/* send notification to L3 */
rc = send_dl_simple(PRIM_DL_EST, PRIM_OP_CONFIRM, lctx);
msgb_free(msg);
@@ -1370,7 +1423,7 @@ static int lapd_rx_s(struct msgb *msg, struct lapd_msg_ctx *lctx)
lapd_dl_newstate(dl, LAPD_STATE_MF_EST);
}
/* Send message, if possible due to acknowledged data */
- lapd_send_i(lctx, __LINE__);
+ lapd_send_i(dl, __LINE__, false);
break;
case LAPD_S_RNR:
@@ -1381,6 +1434,10 @@ static int lapd_rx_s(struct msgb *msg, struct lapd_msg_ctx *lctx)
/* 5.5.5 */
/* Set peer receiver busy condition */
dl->peer_busy = 1;
+ /* Flush pending messages in TX queue. */
+ lapd_dl_flush_tx_queue(dl);
+ /* stop Timer T200 */
+ lapd_stop_t200(dl);
if (lctx->p_f) {
if (lctx->cr == dl->cr.rem2loc.cmd) {
@@ -1407,7 +1464,7 @@ static int lapd_rx_s(struct msgb *msg, struct lapd_msg_ctx *lctx)
LOGDL(dl, LOGL_INFO, "RNR not polling/final state received\n");
/* Send message, if possible due to acknowledged data */
- lapd_send_i(lctx, __LINE__);
+ lapd_send_i(dl, __LINE__, false);
break;
case LAPD_S_REJ:
@@ -1421,6 +1478,8 @@ static int lapd_rx_s(struct msgb *msg, struct lapd_msg_ctx *lctx)
dl->peer_busy = 0;
/* V(S) and V(A) to the N(R) in the REJ frame */
dl->v_send = dl->v_ack = lctx->n_recv;
+ /* Flush pending messages in TX queue. */
+ lapd_dl_flush_tx_queue(dl);
/* stop Timer T200 */
lapd_stop_t200(dl);
/* 5.5.3.2 */
@@ -1457,6 +1516,8 @@ static int lapd_rx_s(struct msgb *msg, struct lapd_msg_ctx *lctx)
dl->peer_busy = 0;
/* V(S) and V(A) to the N(R) in the REJ frame */
dl->v_send = dl->v_ack = lctx->n_recv;
+ /* Flush pending messages in TX queue. */
+ lapd_dl_flush_tx_queue(dl);
/* stop Timer T200 */
lapd_stop_t200(dl);
/* 5.5.7 Clear timer recovery condition */
@@ -1466,6 +1527,8 @@ static int lapd_rx_s(struct msgb *msg, struct lapd_msg_ctx *lctx)
dl->peer_busy = 0;
/* V(S) and V(A) to the N(R) in the REJ frame */
dl->v_send = dl->v_ack = lctx->n_recv;
+ /* Flush pending messages in TX queue. */
+ lapd_dl_flush_tx_queue(dl);
/* 5.5.3.2 */
if (lctx->cr == dl->cr.rem2loc.cmd && lctx->p_f) {
if (!dl->own_busy && !dl->seq_err_cond) {
@@ -1492,8 +1555,8 @@ static int lapd_rx_s(struct msgb *msg, struct lapd_msg_ctx *lctx)
/* FIXME: 5.5.4.2 2) */
- /* Send message, if possible due to acknowledged data */
- lapd_send_i(lctx, __LINE__);
+ /* Send message, if possible due to acknowledged data and new V(S) and V(A). */
+ lapd_send_i(dl, __LINE__, false);
break;
default:
@@ -1515,6 +1578,8 @@ static int lapd_rx_i(struct msgb *msg, struct lapd_msg_ctx *lctx)
uint8_t ns = lctx->n_send;
int length = lctx->length;
int rc;
+ bool i_frame_in_queue = false;
+ int mdl_cause = 0;
LOGDL(dl, LOGL_INFO, "I received in state %s on SAPI(%u)\n",
lapd_state_name(dl->state), lctx->sapi);
@@ -1572,9 +1637,8 @@ static int lapd_rx_i(struct msgb *msg, struct lapd_msg_ctx *lctx)
ns, dl->v_recv, lapd_state_name(dl->state));
/* discard data */
msgb_free(msg);
- if (dl->seq_err_cond != 1) {
- /* FIXME: help me understand what exactly todo here
- */
+ /* Send reject, but suppress second reject if LAPD_F_DROP_2ND_REJ flag is set. */
+ if (dl->seq_err_cond != 1 || !(dl->lapd_flags & LAPD_F_DROP_2ND_REJ)) {
dl->seq_err_cond = 1;
lapd_send_rej(lctx, lctx->p_f);
} else {
@@ -1592,10 +1656,12 @@ static int lapd_rx_i(struct msgb *msg, struct lapd_msg_ctx *lctx)
}
/* Even if N(s) sequence error, acknowledge to N(R)-1 */
/* 5.5.3.1: Acknowlege all transmitted frames up the N(R)-1 */
- lapd_acknowledge(lctx); /* V(A) is also set here */
+ mdl_cause = lapd_acknowledge(lctx); /* V(A) is also set here */
+ if (mdl_cause < 0)
+ mdl_error(-mdl_cause, lctx);
/* Send message, if possible due to acknowledged data */
- lapd_send_i(lctx, __LINE__);
+ lapd_send_i(dl, __LINE__, false);
return 0;
}
@@ -1605,8 +1671,15 @@ static int lapd_rx_i(struct msgb *msg, struct lapd_msg_ctx *lctx)
dl->v_recv = inc_mod(dl->v_recv, dl->v_range);
LOGDL(dl, LOGL_INFO, "incrementing V(R) to %u\n", dl->v_recv);
+ /* Update all pending frames in the queue to the new V(R) state. */
+ if (dl->update_pending_frames) {
+ rc = dl->update_pending_frames(lctx);
+ if (!rc)
+ i_frame_in_queue = true;
+ }
+
/* 5.5.3.1: Acknowlege all transmitted frames up the the N(R)-1 */
- lapd_acknowledge(lctx); /* V(A) is also set here */
+ mdl_cause = lapd_acknowledge(lctx); /* V(A) is also set here */
/* Only if we are not in own receiver busy condition */
if (!dl->own_busy) {
@@ -1651,6 +1724,10 @@ static int lapd_rx_i(struct msgb *msg, struct lapd_msg_ctx *lctx)
} else
LOGDL(dl, LOGL_INFO, "I frame ignored during own receiver busy condition\n");
+ /* Indicate sequence error, if exists. */
+ if (mdl_cause < 0)
+ mdl_error(-mdl_cause, lctx);
+
/* Check for P bit */
if (lctx->p_f) {
/* 5.5.2.1 */
@@ -1669,14 +1746,8 @@ static int lapd_rx_i(struct msgb *msg, struct lapd_msg_ctx *lctx)
/* check if we are not in own receiver busy */
if (!dl->own_busy) {
/* NOTE: V(R) is already set above */
- rc = lapd_send_i(lctx, __LINE__);
-
- /* if update_pending_iframe returns 0 it updated
- * the lapd header of an iframe in the tx queue */
- if (rc && dl->update_pending_frames)
- rc = dl->update_pending_frames(lctx);
-
- if (rc) {
+ rc = lapd_send_i(dl, __LINE__, false);
+ if (rc && !i_frame_in_queue) {
LOGDL(dl, LOGL_INFO, "we are not busy and have no pending data, "
"send RR\n");
/* Send RR with F=0 */
@@ -1692,7 +1763,7 @@ static int lapd_rx_i(struct msgb *msg, struct lapd_msg_ctx *lctx)
}
/* Send message, if possible due to acknowledged data */
- lapd_send_i(lctx, __LINE__);
+ lapd_send_i(dl, __LINE__, false);
return rc;
}
@@ -1713,13 +1784,38 @@ int lapd_ph_data_ind(struct msgb *msg, struct lapd_msg_ctx *lctx)
rc = lapd_rx_i(msg, lctx);
break;
default:
- LOGDL(lctx->dl, LOGL_NOTICE, "unknown LAPD format\n");
+ LOGDL(lctx->dl, LOGL_NOTICE, "unknown LAPD format 0x%02x\n", lctx->format);
msgb_free(msg);
rc = -EINVAL;
}
return rc;
}
+/*! Enqueue next LAPD frame and run pending T200. (Must be called when frame is ready to send.)
+ * The caller (LAPDm code) calls this function before it sends the next frame.
+ * If there is no frame in the TX queue, LAPD will enqueue next I-frame, if possible.
+ * If the T200 is pending, it is changed to running state.
+ * \param[in] lctx LAPD context
+ * \param[out] rc set to 1, if timer T200 state changed to running, set to 0, if not. */
+int lapd_ph_rts_ind(struct lapd_msg_ctx *lctx)
+{
+ struct lapd_datalink *dl = lctx->dl;
+
+ /* If there is no pending frame, try to enqueue next I frame. */
+ if (llist_empty(&dl->tx_queue) && (dl->state == LAPD_STATE_MF_EST || dl->state == LAPD_STATE_TIMER_RECOV)) {
+ /* Send an I frame, if there are pending outgoing messages. */
+ lapd_send_i(dl, __LINE__, true);
+ }
+
+ /* Run T200 at RTS, if pending. Tell caller that is has been started. (rc = 1) */
+ if (dl->t200_rts == LAPD_T200_RTS_PENDING) {
+ dl->t200_rts = LAPD_T200_RTS_RUNNING;
+ return 1;
+ }
+
+ return 0;
+}
+
/* L3 -> L2 */
/* send unit data */
@@ -1743,6 +1839,20 @@ static int lapd_udata_req(struct osmo_dlsap_prim *dp, struct lapd_msg_ctx *lctx)
return dl->send_ph_data_req(&nctx, msg);
}
+static void msg_to_tx_hist(struct lapd_history *tx_hist, const struct msgb *msg, int length, int more)
+{
+ tx_hist->msg = lapd_msgb_alloc(msg->len, "HIST");
+ tx_hist->more = more;
+ msgb_put(tx_hist->msg, msg->len);
+ if (length)
+ memcpy(tx_hist->msg->data, msg->l3h, msg->len);
+}
+
+static void msg_to_tx_hist0(struct lapd_datalink *dl, const struct msgb *msg)
+{
+ return msg_to_tx_hist(&dl->tx_hist[0], msg, msg->len, 0);
+}
+
/* request link establishment */
static int lapd_est_req(struct osmo_dlsap_prim *dp, struct lapd_msg_ctx *lctx)
{
@@ -1781,11 +1891,8 @@ static int lapd_est_req(struct osmo_dlsap_prim *dp, struct lapd_msg_ctx *lctx)
nctx.more = 0;
/* Transmit-buffer carries exactly one segment */
- dl->tx_hist[0].msg = lapd_msgb_alloc(msg->len, "HIST");
- msgb_put(dl->tx_hist[0].msg, msg->len);
- if (msg->len)
- memcpy(dl->tx_hist[0].msg->data, msg->l3h, msg->len);
- dl->tx_hist[0].more = 0;
+ msg_to_tx_hist0(dl, msg);
+
/* set Vs to 0, because it is used as index when resending SABM */
dl->v_send = 0;
@@ -1819,15 +1926,15 @@ static int lapd_data_req(struct osmo_dlsap_prim *dp, struct lapd_msg_ctx *lctx)
msgb_enqueue(&dl->send_queue, msg);
/* Send message, if possible */
- lapd_send_i(&dl->lctx, __LINE__);
+ lapd_send_i(dl, __LINE__, false);
return 0;
}
/* Send next I frame from queued/buffered data */
-static int lapd_send_i(struct lapd_msg_ctx *lctx, int line)
+static int lapd_send_i(struct lapd_datalink *dl, int line, bool rts)
{
- struct lapd_datalink *dl = lctx->dl;
+ struct lapd_msg_ctx *lctx = &dl->lctx;
uint8_t k = dl->k;
uint8_t h;
struct msgb *msg;
@@ -1835,18 +1942,26 @@ static int lapd_send_i(struct lapd_msg_ctx *lctx, int line)
int rc = - 1; /* we sent nothing */
struct lapd_msg_ctx nctx;
+ if (!rts)
+ LOGDL(dl, LOGL_INFO, "%s() called from line %d\n", __func__, line);
- LOGDL(dl, LOGL_INFO, "%s() called from line %d\n", __func__, line);
+ if ((dl->lapd_flags & LAPD_F_RTS) && !llist_empty(&dl->tx_queue)) {
+ if (!rts)
+ LOGDL(dl, LOGL_INFO, "There is a frame in the TX queue, not checking for sending I frame.\n");
+ return rc;
+ }
next_frame:
if (dl->peer_busy) {
- LOGDL(dl, LOGL_INFO, "peer busy, not sending\n");
+ if (!rts)
+ LOGDL(dl, LOGL_INFO, "Peer busy, not sending.\n");
return rc;
}
if (dl->state == LAPD_STATE_TIMER_RECOV) {
- LOGDL(dl, LOGL_INFO, "timer recovery, not sending\n");
+ if (!rts)
+ LOGDL(dl, LOGL_INFO, "Timer recovery, not sending.\n");
return rc;
}
@@ -1857,8 +1972,9 @@ static int lapd_send_i(struct lapd_msg_ctx *lctx, int line)
* of the error recovery procedures as described in subclauses 5.5.4 and
* 5.5.7. */
if (dl->v_send == add_mod(dl->v_ack, k, dl->v_range)) {
- LOGDL(dl, LOGL_INFO, "k frames outstanding, not sending more "
- "(k=%u V(S)=%u V(A)=%u)\n", k, dl->v_send, dl->v_ack);
+ if (!rts)
+ LOGDL(dl, LOGL_INFO, "k frames outstanding, not sending more. (k=%u V(S)=%u V(A)=%u)\n",
+ k, dl->v_send, dl->v_ack);
return rc;
}
@@ -1900,7 +2016,7 @@ static int lapd_send_i(struct lapd_msg_ctx *lctx, int line)
msg = lapd_msgb_alloc(length, "LAPD I");
msg->l3h = msgb_put(msg, length);
/* assemble message */
- memcpy(&nctx, &dl->lctx, sizeof(nctx));
+ memcpy(&nctx, lctx, sizeof(nctx));
/* keep nctx.ldp */
/* keep nctx.sapi */
/* keep nctx.tei */
@@ -1918,11 +2034,8 @@ static int lapd_send_i(struct lapd_msg_ctx *lctx, int line)
memcpy(msg->l3h, dl->send_buffer->l3h + dl->send_out,
length);
/* store in tx_hist */
- dl->tx_hist[h].msg = lapd_msgb_alloc(msg->len, "HIST");
- msgb_put(dl->tx_hist[h].msg, msg->len);
- if (length)
- memcpy(dl->tx_hist[h].msg->data, msg->l3h, msg->len);
- dl->tx_hist[h].more = nctx.more;
+ msg_to_tx_hist(&dl->tx_hist[h], msg, length, nctx.more);
+
/* Add length to track how much is already in the tx buffer */
dl->send_out += length;
} else {
@@ -1933,7 +2046,7 @@ static int lapd_send_i(struct lapd_msg_ctx *lctx, int line)
msg = lapd_msgb_alloc(length, "LAPD I resend");
msg->l3h = msgb_put(msg, length);
/* assemble message */
- memcpy(&nctx, &dl->lctx, sizeof(nctx));
+ memcpy(&nctx, lctx, sizeof(nctx));
/* keep nctx.ldp */
/* keep nctx.sapi */
/* keep nctx.tei */
@@ -1955,7 +2068,7 @@ static int lapd_send_i(struct lapd_msg_ctx *lctx, int line)
/* If timer T200 is not running at the time right before transmitting a
* frame, when the PH-READY-TO-SEND primitive is received from the
* physical layer., it shall be set. */
- if (!osmo_timer_pending(&dl->t200)) {
+ if (!lapd_is_t200_started(dl)) {
/* stop Timer T203, if running */
lapd_stop_t203(dl);
/* start Timer T200 */
@@ -1964,7 +2077,11 @@ static int lapd_send_i(struct lapd_msg_ctx *lctx, int line)
dl->send_ph_data_req(&nctx, msg);
- rc = 0; /* we sent something */
+ /* When using RTS, we send only one frame. */
+ if ((dl->lapd_flags & LAPD_F_RTS))
+ return 0;
+
+ rc = 0; /* We sent an I frame, so sending RR frame is not required. */
goto next_frame;
}
@@ -2043,11 +2160,8 @@ static int lapd_res_req(struct osmo_dlsap_prim *dp, struct lapd_msg_ctx *lctx)
nctx.length = 0;
nctx.more = 0;
- dl->tx_hist[0].msg = lapd_msgb_alloc(msg->len, "HIST");
- msgb_put(dl->tx_hist[0].msg, msg->len);
- if (msg->len)
- memcpy(dl->tx_hist[0].msg->data, msg->l3h, msg->len);
- dl->tx_hist[0].more = 0;
+ msg_to_tx_hist0(dl, msg);
+
/* set Vs to 0, because it is used as index when resending SABM */
dl->v_send = 0;
@@ -2107,11 +2221,8 @@ static int lapd_rel_req(struct osmo_dlsap_prim *dp, struct lapd_msg_ctx *lctx)
nctx.length = 0;
nctx.more = 0;
- dl->tx_hist[0].msg = lapd_msgb_alloc(msg->len, "HIST");
- msgb_put(dl->tx_hist[0].msg, msg->len);
- if (msg->len)
- memcpy(dl->tx_hist[0].msg->data, msg->l3h, msg->len);
- dl->tx_hist[0].more = 0;
+ msg_to_tx_hist0(dl, msg);
+
/* set Vs to 0, because it is used as index when resending DISC */
dl->v_send = 0;
diff --git a/src/isdn/libosmoisdn.map b/src/isdn/libosmoisdn.map
new file mode 100644
index 00000000..8f34f0d8
--- /dev/null
+++ b/src/isdn/libosmoisdn.map
@@ -0,0 +1,52 @@
+LIBOSMOISDN_1.0 {
+global:
+
+tall_lapd_ctx;
+lapd_dl_exit;
+lapd_dl_init;
+lapd_dl_init2;
+lapd_dl_set_name;
+lapd_dl_reset;
+lapd_dl_set_flags;
+lapd_msgb_alloc;
+lapd_ph_data_ind;
+lapd_ph_rts_ind;
+lapd_recv_dlsap;
+lapd_set_mode;
+lapd_state_names;
+lapd_t200_timeout;
+
+osmo_i460_demux_in;
+osmo_i460_mux_enqueue;
+osmo_i460_mux_out;
+osmo_i460_subchan_add;
+osmo_i460_subchan_del;
+osmo_i460_subchan_count;
+osmo_i460_ts_init;
+
+osmo_v110_decode_frame;
+osmo_v110_encode_frame;
+osmo_v110_ubit_dump;
+osmo_v110_e1e2e3;
+osmo_v110_sync_ra1_get_user_data_chunk_bitlen;
+osmo_v110_sync_ra1_get_user_data_rate;
+osmo_v110_sync_ra1_get_intermediate_rate;
+osmo_v110_sync_ra1_user_to_ir;
+osmo_v110_sync_ra1_ir_to_user;
+
+osmo_v110_ta_alloc;
+osmo_v110_ta_free;
+osmo_v110_ta_set_timer_val_ms;
+osmo_v110_ta_frame_in;
+osmo_v110_ta_frame_out;
+osmo_v110_ta_sync_ind;
+osmo_v110_ta_desync_ind;
+osmo_v110_ta_get_status;
+osmo_v110_ta_get_circuit;
+osmo_v110_ta_set_circuit;
+
+osmo_v110_ta_circuit_names;
+osmo_v110_ta_circuit_descs;
+
+local: *;
+};
diff --git a/src/isdn/v110.c b/src/isdn/v110.c
new file mode 100644
index 00000000..b4bc4e30
--- /dev/null
+++ b/src/isdn/v110.c
@@ -0,0 +1,590 @@
+/* V.110 frames according to ITU-T V.110
+ *
+ * This code implements the following functionality:
+ * - parsing/encoding of osmo_v110_decoded_frame from/to actual 80-bit V.110 frame
+ * - synchronous rate adapting of user bit rate to V.110 D-bits as per Table 6
+ *
+ * It is (at least initially) a very "naive" implementation, as it first and foremost
+ * aims to be functional and correct, rather than efficient in any way. Hence it
+ * operates on unpacked bits (ubit_t, 1 bit per byte), and has various intermediate
+ * representations and indirect function calls. If needed, a more optimized variant
+ * can always be developed later on.
+ */
+
+/* (C) 2022 by Harald Welte <laforge@osmocom.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 <stdint.h>
+#include <errno.h>
+
+#include <osmocom/core/bits.h>
+
+#include <osmocom/isdn/v110.h>
+
+/*************************************************************************
+ * V.110 frame decoding/encoding (ubits <-> struct with D/S/X/E bits)
+ *************************************************************************/
+
+/*! Decode a 80-bit V.110 frame present as 80 ubits into a struct osmo_v110_decoded_frame.
+ * \param[out] fr caller-allocated output data structure, filled by this function
+ * \param[in] ra_bits One V.110 frame as 80 unpacked bits.
+ * \param[in] n_bits number of unpacked bits provided in ra_bits
+ * \returns 0 in case of success; negative on error. */
+int osmo_v110_decode_frame(struct osmo_v110_decoded_frame *fr, const ubit_t *ra_bits, size_t n_bits)
+{
+ if (n_bits < 80)
+ return -EINVAL;
+
+ /* X1 .. X2 */
+ fr->x_bits[0] = ra_bits[2 * 8 + 7];
+ fr->x_bits[1] = ra_bits[7 * 8 + 7];
+
+ /* S1, S3, S4, S6, S8, S9 */
+ fr->s_bits[0] = ra_bits[1 * 8 + 7];
+ fr->s_bits[2] = ra_bits[3 * 8 + 7];
+ fr->s_bits[3] = ra_bits[4 * 8 + 7];
+ fr->s_bits[5] = ra_bits[6 * 8 + 7];
+ fr->s_bits[7] = ra_bits[8 * 8 + 7];
+ fr->s_bits[8] = ra_bits[9 * 8 + 7];
+
+ /* E1 .. E7 */
+ memcpy(fr->e_bits, ra_bits + 5 * 8 + 1, 7);
+
+ /* D-bits */
+ memcpy(fr->d_bits + 0 * 6, ra_bits + 1 * 8 + 1, 6);
+ memcpy(fr->d_bits + 1 * 6, ra_bits + 2 * 8 + 1, 6);
+ memcpy(fr->d_bits + 2 * 6, ra_bits + 3 * 8 + 1, 6);
+ memcpy(fr->d_bits + 3 * 6, ra_bits + 4 * 8 + 1, 6);
+
+ memcpy(fr->d_bits + 4 * 6, ra_bits + 6 * 8 + 1, 6);
+ memcpy(fr->d_bits + 5 * 6, ra_bits + 7 * 8 + 1, 6);
+ memcpy(fr->d_bits + 6 * 6, ra_bits + 8 * 8 + 1, 6);
+ memcpy(fr->d_bits + 7 * 6, ra_bits + 9 * 8 + 1, 6);
+
+ return 0;
+}
+
+/*! Encode a struct osmo_v110_decoded_frame into an 80-bit V.110 frame as ubits.
+ * \param[out] ra_bits caller-provided output buffer at leat 80 ubits large
+ * \param[in] n_bits length of ra_bits. Must be at least 80.
+ * \param[in] input data structure
+ * \returns number of bits written to ra_bits */
+int osmo_v110_encode_frame(ubit_t *ra_bits, size_t n_bits, const struct osmo_v110_decoded_frame *fr)
+{
+ if (n_bits < 80)
+ return -ENOSPC;
+
+ /* alignment pattern */
+ memset(ra_bits+0, 0, 8);
+ for (int i = 1; i < 10; i++)
+ ra_bits[i*8] = 1;
+
+ /* X1 .. X2 */
+ ra_bits[2 * 8 + 7] = fr->x_bits[0];
+ ra_bits[7 * 8 + 7] = fr->x_bits[1];
+
+ /* S1, S3, S4, S6, S8, S9 */
+ ra_bits[1 * 8 + 7] = fr->s_bits[0];
+ ra_bits[3 * 8 + 7] = fr->s_bits[2];
+ ra_bits[4 * 8 + 7] = fr->s_bits[3];
+ ra_bits[6 * 8 + 7] = fr->s_bits[5];
+ ra_bits[8 * 8 + 7] = fr->s_bits[7];
+ ra_bits[9 * 8 + 7] = fr->s_bits[8];
+
+ /* E1 .. E7 */
+ memcpy(ra_bits + 5 * 8 + 1, fr->e_bits, 7);
+
+ /* D-bits */
+ memcpy(ra_bits + 1 * 8 + 1, fr->d_bits + 0 * 6, 6);
+ memcpy(ra_bits + 2 * 8 + 1, fr->d_bits + 1 * 6, 6);
+ memcpy(ra_bits + 3 * 8 + 1, fr->d_bits + 2 * 6, 6);
+ memcpy(ra_bits + 4 * 8 + 1, fr->d_bits + 3 * 6, 6);
+
+ memcpy(ra_bits + 6 * 8 + 1, fr->d_bits + 4 * 6, 6);
+ memcpy(ra_bits + 7 * 8 + 1, fr->d_bits + 5 * 6, 6);
+ memcpy(ra_bits + 8 * 8 + 1, fr->d_bits + 6 * 6, 6);
+ memcpy(ra_bits + 9 * 8 + 1, fr->d_bits + 7 * 6, 6);
+
+ return 10 * 8;
+}
+
+/*! Print a encoded V.110 frame in the same table-like structure as the spec.
+ * \param outf output FILE stream to which to dump
+ * \param[in] fr unpacked bits to dump
+ * \param[in] in_len length of unpacked bits available at fr. */
+void osmo_v110_ubit_dump(FILE *outf, const ubit_t *fr, size_t in_len)
+{
+ if (in_len < 80)
+ fprintf(outf, "short input data\n");
+
+ for (unsigned int octet = 0; octet < 10; octet++) {
+ fprintf(outf, "%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\n",
+ fr[octet * 8 + 0], fr[octet * 8 + 1], fr[octet * 8 + 2], fr[octet * 8 + 3],
+ fr[octet * 8 + 4], fr[octet * 8 + 5], fr[octet * 8 + 6], fr[octet * 8 + 7]);
+ }
+}
+
+/*************************************************************************
+ * RA1 synchronous rate adaptation
+ *************************************************************************/
+
+/* I actually couldn't find any reference as to the value of F(ill) bits */
+#define F 1
+
+/*! E1/E2/E3 bit values as per Table 5/V.110 */
+const ubit_t osmo_v110_e1e2e3[_NUM_OSMO_V110_SYNC_RA1][3] = {
+ [OSMO_V110_SYNC_RA1_600] = { 1, 0, 0 },
+ [OSMO_V110_SYNC_RA1_1200] = { 0, 1, 0 },
+ [OSMO_V110_SYNC_RA1_2400] = { 1, 1, 0 },
+ [OSMO_V110_SYNC_RA1_4800] = { 0, 1, 1 },
+ [OSMO_V110_SYNC_RA1_7200] = { 1, 0, 1 },
+ [OSMO_V110_SYNC_RA1_9600] = { 0, 1, 1 },
+ [OSMO_V110_SYNC_RA1_12000] = { 0, 0, 1 },
+ [OSMO_V110_SYNC_RA1_14400] = { 1, 0, 1 },
+ [OSMO_V110_SYNC_RA1_19200] = { 0, 1, 1 },
+ [OSMO_V110_SYNC_RA1_24000] = { 0, 0, 1 },
+ [OSMO_V110_SYNC_RA1_28800] = { 1, 0, 1 },
+ [OSMO_V110_SYNC_RA1_38400] = { 0, 1, 1 },
+};
+
+/*! Adapt from 6 synchronous 600bit/s input bits to a decoded V.110 frame.
+ * \param[out] fr caller-allocated output frame to which E+D bits are stored
+ * \param[in] d_in input user bits
+ * \param[in] in_len number of bits in d_in. Must be 6.
+ * \returns 0 on success; negative in case of error. */
+static int v110_adapt_600_to_IR8000(struct osmo_v110_decoded_frame *fr, const ubit_t *d_in, size_t in_len)
+{
+ if (in_len != 6)
+ return -EINVAL;
+
+ /* Table 6a / V.110 */
+ osmo_v110_e1e2e3_set(fr->e_bits, OSMO_V110_SYNC_RA1_600);
+ for (int i = 0; i < 6; i++)
+ memset(fr->d_bits + i*8, d_in[i], 8);
+
+ return 0;
+}
+
+static int v110_adapt_IR8000_to_600(ubit_t *d_out, size_t out_len, const struct osmo_v110_decoded_frame *fr)
+{
+ if (out_len < 6)
+ return -ENOSPC;
+
+ if (osmo_v110_e1e2e3_cmp(fr->e_bits, OSMO_V110_SYNC_RA1_600))
+ return -EINVAL;
+
+ for (int i = 0; i < 6; i++) {
+ /* we only use one of the bits, not some kind of consistency check or majority vote */
+ d_out[i] = fr->d_bits[i*8];
+ }
+
+ return 6;
+}
+
+/*! Adapt from 12 synchronous 1200bit/s input bits to a decoded V.110 frame.
+ * \param[out] fr caller-allocated output frame to which E+D bits are stored
+ * \param[in] d_in input user bits
+ * \param[in] in_len number of bits in d_in. Must be 12.
+ * \returns 0 on success; negative in case of error. */
+static int v110_adapt_1200_to_IR8000(struct osmo_v110_decoded_frame *fr, const ubit_t *d_in, size_t in_len)
+{
+ if (in_len != 12)
+ return -EINVAL;
+
+ /* Table 6b / V.110 */
+ osmo_v110_e1e2e3_set(fr->e_bits, OSMO_V110_SYNC_RA1_1200);
+ for (int i = 0; i < 12; i++)
+ memset(fr->d_bits + i*4, d_in[i], 4);
+
+ return 0;
+}
+
+static int v110_adapt_IR8000_to_1200(ubit_t *d_out, size_t out_len, const struct osmo_v110_decoded_frame *fr)
+{
+ if (out_len < 12)
+ return -ENOSPC;
+
+ if (osmo_v110_e1e2e3_cmp(fr->e_bits, OSMO_V110_SYNC_RA1_1200))
+ return -EINVAL;
+
+ for (int i = 0; i < 12; i++) {
+ /* we only use one of the bits, not some kind of consistency check or majority vote */
+ d_out[i] = fr->d_bits[i*4];
+ }
+
+ return 12;
+}
+
+/*! Adapt from 24 synchronous 2400bit/s input bits to a decoded V.110 frame.
+ * \param[out] fr caller-allocated output frame to which E+D bits are stored
+ * \param[in] d_in input user bits
+ * \param[in] in_len number of bits in d_in. Must be 24.
+ * \returns 0 on success; negative in case of error. */
+static int v110_adapt_2400_to_IR8000(struct osmo_v110_decoded_frame *fr, const ubit_t *d_in, size_t in_len)
+{
+ if (in_len != 24)
+ return -EINVAL;
+
+ /* Table 6c / V.110 */
+ osmo_v110_e1e2e3_set(fr->e_bits, OSMO_V110_SYNC_RA1_2400);
+ for (int i = 0; i < 24; i++) {
+ fr->d_bits[i*2 + 0] = d_in[i];
+ fr->d_bits[i*2 + 1] = d_in[i];
+ }
+
+ return 0;
+}
+
+static int v110_adapt_IR8000_to_2400(ubit_t *d_out, size_t out_len, const struct osmo_v110_decoded_frame *fr)
+{
+ if (out_len < 24)
+ return -ENOSPC;
+
+ /* Table 6c / V.110 */
+ if (osmo_v110_e1e2e3_cmp(fr->e_bits, OSMO_V110_SYNC_RA1_2400))
+ return -EINVAL;
+
+ for (int i = 0; i < 24; i++) {
+ /* we only use one of the bits, not some kind of consistency check or majority vote */
+ d_out[i] = fr->d_bits[i*2];
+ }
+
+ return 24;
+}
+
+/*! Adapt from 36 synchronous N x 3600bit/s input bits to a decoded V.110 frame.
+ * \param[out] fr caller-allocated output frame to which E+D bits are stored
+ * \param[in] d_in input user bits
+ * \param[in] in_len number of bits in d_in. Must be 36.
+ * \returns 0 on success; negative in case of error. */
+static int v110_adapt_Nx3600_to_IR(struct osmo_v110_decoded_frame *fr, const ubit_t *d_in, size_t in_len)
+{
+ int d_idx = 0;
+
+ if (in_len != 36)
+ return -EINVAL;
+
+ /* Table 6d / V.110 (7200 is one of Nx3600) */
+ osmo_v110_e1e2e3_set(fr->e_bits, OSMO_V110_SYNC_RA1_7200);
+
+ memcpy(fr->d_bits + d_idx, d_in + 0, 10); d_idx += 10; /* D1..D10 */
+ memset(fr->d_bits + d_idx, F, 2); d_idx += 2;
+ memcpy(fr->d_bits + d_idx, d_in + 10, 2); d_idx += 2; /* D11..D12 */
+ memset(fr->d_bits + d_idx, F, 2); d_idx += 2;
+ memcpy(fr->d_bits + d_idx, d_in + 12, 2); d_idx += 2; /* D13..D14 */
+ memset(fr->d_bits + d_idx, F, 2); d_idx += 2;
+ memcpy(fr->d_bits + d_idx, d_in + 14, 14); d_idx += 14; /* D15..D28 */
+ memset(fr->d_bits + d_idx, F, 2); d_idx += 2;
+ memcpy(fr->d_bits + d_idx, d_in + 28, 2); d_idx += 2; /* D29..D30 */
+ memset(fr->d_bits + d_idx, F, 2); d_idx += 2;
+ memcpy(fr->d_bits + d_idx, d_in + 30, 2); d_idx += 2; /* D31..D32 */
+ memset(fr->d_bits + d_idx, F, 2); d_idx += 2;
+ memcpy(fr->d_bits + d_idx, d_in + 32, 4); d_idx += 4; /* D33..D36 */
+
+ OSMO_ASSERT(d_idx == 48);
+
+ return 0;
+}
+
+static int v110_adapt_IR_to_Nx3600(ubit_t *d_out, size_t out_len, const struct osmo_v110_decoded_frame *fr)
+{
+ int d_idx = 0;
+
+ if (out_len < 36)
+ return -ENOSPC;
+
+ /* Table 6d / V.110 (7200 is one of Nx3600) */
+ if (osmo_v110_e1e2e3_cmp(fr->e_bits, OSMO_V110_SYNC_RA1_7200))
+ return -EINVAL;
+
+ memcpy(d_out + 0, fr->d_bits + d_idx, 10); d_idx += 10; /* D1..D10 */
+ d_idx += 2;
+ memcpy(d_out + 10, fr->d_bits + d_idx, 2); d_idx += 2; /* D11..D12 */
+ d_idx += 2;
+ memcpy(d_out + 12, fr->d_bits + d_idx, 2); d_idx += 2; /* D13..D14 */
+ d_idx += 2;
+ memcpy(d_out + 14, fr->d_bits + d_idx, 14); d_idx += 14;/* D15..D28 */
+ d_idx += 2;
+ memcpy(d_out + 28, fr->d_bits + d_idx, 2); d_idx += 2; /* D29..D30 */
+ d_idx += 2;
+ memcpy(d_out + 30, fr->d_bits + d_idx, 2); d_idx += 2; /* D31..D32 */
+ d_idx += 2;
+ memcpy(d_out + 32, fr->d_bits + d_idx, 4); d_idx += 4; /* D33..D36 */
+
+ OSMO_ASSERT(d_idx == 48);
+
+ return 36;
+}
+
+
+/*! Adapt from 48 synchronous N x 4800bit/s input bits to a decoded V.110 frame.
+ * \param[out] fr caller-allocated output frame to which E+D bits are stored
+ * \param[in] d_in input user bits
+ * \param[in] in_len number of bits in d_in. Must be 48.
+ * \returns 0 on success; negative in case of error. */
+static int v110_adapt_Nx4800_to_IR(struct osmo_v110_decoded_frame *fr, const ubit_t *d_in, size_t in_len)
+{
+ if (in_len != 48)
+ return -EINVAL;
+
+ /* Table 6e / V.110 (4800 is one of Nx4800) */
+ osmo_v110_e1e2e3_set(fr->e_bits, OSMO_V110_SYNC_RA1_4800);
+
+ memcpy(fr->d_bits, d_in, 48);
+
+ return 0;
+}
+
+static int v110_adapt_IR_to_Nx4800(ubit_t *d_out, size_t out_len, const struct osmo_v110_decoded_frame *fr)
+{
+ if (out_len < 48)
+ return -ENOSPC;
+
+ /* Table 6e / V.110 (4800 is one of Nx4800) */
+ if (osmo_v110_e1e2e3_cmp(fr->e_bits, OSMO_V110_SYNC_RA1_4800))
+ return -EINVAL;
+
+ memcpy(d_out, fr->d_bits, 48);
+
+ return 48;
+}
+
+/*! Adapt from 30 synchronous N x 12000bit/s input bits to a decoded V.110 frame.
+ * \param[out] fr caller-allocated output frame to which E+D bits are stored
+ * \param[in] d_in input user bits
+ * \param[in] in_len number of bits in d_in. Must be 30.
+ * \returns 0 on success; negative in case of error. */
+static int v110_adapt_Nx12000_to_IR(struct osmo_v110_decoded_frame *fr, const ubit_t *d_in, size_t in_len)
+{
+ int d_idx = 0;
+
+ if (in_len != 30)
+ return -EINVAL;
+
+ /* Table 6f / V.110 (12000 is one of Nx12000) */
+ osmo_v110_e1e2e3_set(fr->e_bits, OSMO_V110_SYNC_RA1_12000);
+
+ memcpy(fr->d_bits + d_idx, d_in + 0, 10); d_idx += 10; /* D1..D10 */
+ memset(fr->d_bits + d_idx, F, 2); d_idx += 2;
+ memcpy(fr->d_bits + d_idx, d_in + 10, 2); d_idx += 2; /* D11..D12 */
+ memset(fr->d_bits + d_idx, F, 2); d_idx += 2;
+ memcpy(fr->d_bits + d_idx, d_in + 12, 2); d_idx += 2; /* D13..D14 */
+ memset(fr->d_bits + d_idx, F, 2); d_idx += 2;
+ fr->d_bits[d_idx++] = d_in[14]; /* D15 */
+ memset(fr->d_bits + d_idx, F, 3); d_idx += 3;
+ memcpy(fr->d_bits + d_idx, d_in + 15, 10); d_idx += 10; /* D16..D25 */
+ memset(fr->d_bits + d_idx, F, 2); d_idx += 2;
+ memcpy(fr->d_bits + d_idx, d_in + 25, 2); d_idx += 2; /* D26..D27 */
+ memset(fr->d_bits + d_idx, F, 2); d_idx += 2;
+ memcpy(fr->d_bits + d_idx, d_in + 27, 2); d_idx += 2; /* D28..D29 */
+ memset(fr->d_bits + d_idx, F, 2); d_idx += 2;
+ fr->d_bits[d_idx++] = d_in[29]; /* D30 */
+ memset(fr->d_bits + d_idx, F, 3); d_idx += 3;
+
+ OSMO_ASSERT(d_idx == 48);
+
+ return 0;
+}
+
+static int v110_adapt_IR_to_Nx12000(ubit_t *d_out, size_t out_len, const struct osmo_v110_decoded_frame *fr)
+{
+ int d_idx = 0;
+
+ if (out_len < 30)
+ return -ENOSPC;
+
+ /* Table 6f / V.110 (12000 is one of Nx12000) */
+ if (osmo_v110_e1e2e3_cmp(fr->e_bits, OSMO_V110_SYNC_RA1_12000))
+ return -EINVAL;
+
+ memcpy(d_out + 0, fr->d_bits + d_idx, 10); d_idx += 10; /* D1..D10 */
+ d_idx += 2;
+ memcpy(d_out + 10, fr->d_bits + d_idx, 2); d_idx += 2; /* D11..D12 */
+ d_idx += 2;
+ memcpy(d_out + 12, fr->d_bits + d_idx, 2); d_idx += 2; /* D13..D14 */
+ d_idx += 2;
+ d_out[14] = fr->d_bits[d_idx++]; /* D15 */
+ d_idx += 3;
+ memcpy(d_out + 15, fr->d_bits + d_idx, 10); d_idx += 10;/* D16..D25 */
+ d_idx += 2;
+ memcpy(d_out + 25, fr->d_bits + d_idx, 2); d_idx += 2; /* D26..D27 */
+ d_idx += 2;
+ memcpy(d_out + 27, fr->d_bits + d_idx, 2); d_idx += 2; /* D28..D29 */
+ d_idx += 2;
+ d_out[29] = fr->d_bits[d_idx++]; /* D30 */
+ d_idx += 3;
+
+ OSMO_ASSERT(d_idx == 48);
+
+ return 30;
+}
+
+/* definition of a synchronous V.110 RA1 rate adaptation. There is one for each supported tuple
+ * of user data rate and intermediate rate (IR). */
+struct osmo_v110_sync_ra1 {
+ unsigned int data_rate;
+ unsigned int intermediate_rate;
+ unsigned int user_data_chunk_bits;
+ /*! RA1 function in user bitrate -> intermediate rate direction */
+ int (*adapt_user_to_ir)(struct osmo_v110_decoded_frame *fr, const ubit_t *d_in, size_t in_len);
+ /*! RA1 function in intermediate rate -> user bitrate direction */
+ int (*adapt_ir_to_user)(ubit_t *d_out, size_t out_len, const struct osmo_v110_decoded_frame *fr);
+};
+
+/* all of the synchronous data signalling rates; see Table 1/V.110 */
+static const struct osmo_v110_sync_ra1 osmo_v110_sync_ra1_def[_NUM_OSMO_V110_SYNC_RA1] = {
+ [OSMO_V110_SYNC_RA1_600] = {
+ .data_rate = 600,
+ .intermediate_rate = 8000,
+ .user_data_chunk_bits = 6,
+ .adapt_user_to_ir = v110_adapt_600_to_IR8000,
+ .adapt_ir_to_user = v110_adapt_IR8000_to_600,
+ },
+ [OSMO_V110_SYNC_RA1_1200] = {
+ .data_rate = 1200,
+ .intermediate_rate = 8000,
+ .user_data_chunk_bits = 12,
+ .adapt_user_to_ir = v110_adapt_1200_to_IR8000,
+ .adapt_ir_to_user = v110_adapt_IR8000_to_1200,
+ },
+ [OSMO_V110_SYNC_RA1_2400] = {
+ .data_rate = 2400,
+ .intermediate_rate = 8000,
+ .user_data_chunk_bits = 24,
+ .adapt_user_to_ir = v110_adapt_2400_to_IR8000,
+ .adapt_ir_to_user = v110_adapt_IR8000_to_2400,
+ },
+ [OSMO_V110_SYNC_RA1_4800] = {
+ .data_rate = 4800,
+ .intermediate_rate = 8000,
+ .user_data_chunk_bits = 48,
+ .adapt_user_to_ir = v110_adapt_Nx4800_to_IR,
+ .adapt_ir_to_user = v110_adapt_IR_to_Nx4800,
+ },
+ [OSMO_V110_SYNC_RA1_7200] = {
+ .data_rate = 7200,
+ .intermediate_rate = 16000,
+ .user_data_chunk_bits = 36,
+ .adapt_user_to_ir = v110_adapt_Nx3600_to_IR,
+ .adapt_ir_to_user = v110_adapt_IR_to_Nx3600,
+ },
+ [OSMO_V110_SYNC_RA1_9600] = {
+ .data_rate = 9600,
+ .intermediate_rate = 16000,
+ .user_data_chunk_bits = 48,
+ .adapt_user_to_ir = v110_adapt_Nx4800_to_IR,
+ .adapt_ir_to_user = v110_adapt_IR_to_Nx4800,
+ },
+ [OSMO_V110_SYNC_RA1_12000] = {
+ .data_rate = 12000,
+ .intermediate_rate = 32000,
+ .user_data_chunk_bits = 30,
+ .adapt_user_to_ir = v110_adapt_Nx12000_to_IR,
+ .adapt_ir_to_user = v110_adapt_IR_to_Nx12000,
+ },
+ [OSMO_V110_SYNC_RA1_14400] = {
+ .data_rate = 14400,
+ .intermediate_rate = 32000,
+ .user_data_chunk_bits = 36,
+ .adapt_user_to_ir = v110_adapt_Nx3600_to_IR,
+ .adapt_ir_to_user = v110_adapt_IR_to_Nx3600,
+ },
+ [OSMO_V110_SYNC_RA1_19200] = {
+ .data_rate = 19200,
+ .intermediate_rate = 32000,
+ .user_data_chunk_bits = 48,
+ .adapt_user_to_ir = v110_adapt_Nx4800_to_IR,
+ .adapt_ir_to_user = v110_adapt_IR_to_Nx4800,
+ },
+ [OSMO_V110_SYNC_RA1_24000] = {
+ .data_rate = 24000,
+ .intermediate_rate = 64000,
+ .user_data_chunk_bits = 30,
+ .adapt_user_to_ir = v110_adapt_Nx12000_to_IR,
+ .adapt_ir_to_user = v110_adapt_IR_to_Nx12000,
+ },
+ [OSMO_V110_SYNC_RA1_28800] = {
+ .data_rate = 28800,
+ .intermediate_rate = 64000,
+ .user_data_chunk_bits = 36,
+ .adapt_user_to_ir = v110_adapt_Nx3600_to_IR,
+ .adapt_ir_to_user = v110_adapt_IR_to_Nx3600,
+ },
+ [OSMO_V110_SYNC_RA1_38400] = {
+ .data_rate = 38400,
+ .intermediate_rate = 64000,
+ .user_data_chunk_bits = 48,
+ .adapt_user_to_ir = v110_adapt_Nx4800_to_IR,
+ .adapt_ir_to_user = v110_adapt_IR_to_Nx4800,
+ },
+};
+
+/*! obtain the size (in number of bits) of the user data bits in one V.110
+ * frame for specified RA1 rate */
+int osmo_v110_sync_ra1_get_user_data_chunk_bitlen(enum osmo_v100_sync_ra1_rate rate)
+{
+ if (rate < 0 || rate >= _NUM_OSMO_V110_SYNC_RA1)
+ return -EINVAL;
+
+ return osmo_v110_sync_ra1_def[rate].user_data_chunk_bits;
+}
+
+/*! obtain the user data rate (in bits/s) for specified RA1 rate */
+int osmo_v110_sync_ra1_get_user_data_rate(enum osmo_v100_sync_ra1_rate rate)
+{
+ if (rate < 0 || rate >= _NUM_OSMO_V110_SYNC_RA1)
+ return -EINVAL;
+
+ return osmo_v110_sync_ra1_def[rate].data_rate;
+}
+
+/*! obtain the intermediate rate (in bits/s) for specified RA1 rate */
+int osmo_v110_sync_ra1_get_intermediate_rate(enum osmo_v100_sync_ra1_rate rate)
+{
+ if (rate < 0 || rate >= _NUM_OSMO_V110_SYNC_RA1)
+ return -EINVAL;
+
+ return osmo_v110_sync_ra1_def[rate].intermediate_rate;
+}
+
+/*! perform V.110 RA1 function in user rate -> intermediate rate direction.
+ * \param[in] rate specification of the user bitrate
+ * \param[out] fr caller-allocated output buffer for the [decoded] V.110 frame generated
+ * \param[in] d_in input user data (unpacked bits)
+ * \param[in] in_len length of user input data (in number of bits)
+ * \returns 0 on success; negative in case of error */
+int osmo_v110_sync_ra1_user_to_ir(enum osmo_v100_sync_ra1_rate rate, struct osmo_v110_decoded_frame *fr,
+ const ubit_t *d_in, size_t in_len)
+{
+ if (rate < 0 || rate >= _NUM_OSMO_V110_SYNC_RA1)
+ return -EINVAL;
+
+ return osmo_v110_sync_ra1_def[rate].adapt_user_to_ir(fr, d_in, in_len);
+}
+
+/*! perform V.110 RA1 function in intermediate rate -> user rate direction.
+ * \param[in] rate specification of the user bitrate
+ * \param[out] d_out caller-allocated output user data (unpacked bits)
+ * \param[out] out_len length of d_out output buffer
+ * \param[in] fr [decoded] V.110 frame used as input
+ * \returns number of unpacked bits written to d_out on success; negative in case of error */
+int osmo_v110_sync_ra1_ir_to_user(enum osmo_v100_sync_ra1_rate rate, ubit_t *d_out, size_t out_len,
+ const struct osmo_v110_decoded_frame *fr)
+{
+ if (rate < 0 || rate >= _NUM_OSMO_V110_SYNC_RA1)
+ return -EINVAL;
+
+ return osmo_v110_sync_ra1_def[rate].adapt_ir_to_user(d_out, out_len, fr);
+}
diff --git a/src/isdn/v110_ta.c b/src/isdn/v110_ta.c
new file mode 100644
index 00000000..6730b20c
--- /dev/null
+++ b/src/isdn/v110_ta.c
@@ -0,0 +1,881 @@
+/*! \file v110_ta.c
+ * TA (Terminal Adapter) implementation as per ITU-T V.110. */
+/*
+ * (C) 2022 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ *
+ * Initial (Work-in-Progress) implementation by Harald Welte,
+ * completed and co-authored by Vadim Yanitskiy.
+ *
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <errno.h>
+
+#include <osmocom/core/logging.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/bits.h>
+#include <osmocom/core/tdef.h>
+#include <osmocom/core/fsm.h>
+
+#include <osmocom/isdn/v110.h>
+#include <osmocom/isdn/v110_ta.h>
+
+#define S(x) (1 << (x))
+
+#define V24_FLAGMASK_IS_ON(flags, circuit) \
+ (((flags) & S(circuit)) != 0)
+
+#define V24_FLAGMASK_IS_OFF(flags, circuit) \
+ (((flags) & S(circuit)) == 0)
+
+#define V24_FLAGMASK_SET_ON(flags, circuit) \
+ (flags) |= S(circuit)
+
+#define V24_FLAGMASK_SET_OFF(flags, circuit) \
+ (flags) &= ~S(circuit)
+
+/* inverse logic: ON = binary 0; OFF = binary 1 */
+#define V110_SX_BIT_ON 0
+#define V110_SX_BIT_OFF 1
+
+const struct value_string osmo_v110_ta_circuit_names[] = {
+ { OSMO_V110_TA_C_105, "105/RTS" },
+ { OSMO_V110_TA_C_106, "106/CTS" },
+ { OSMO_V110_TA_C_107, "107/DSR" },
+ { OSMO_V110_TA_C_108, "108/DTR" },
+ { OSMO_V110_TA_C_109, "109/DCD" },
+ { OSMO_V110_TA_C_133, "133" },
+ { 0, NULL }
+};
+
+const struct value_string osmo_v110_ta_circuit_descs[] = {
+ { OSMO_V110_TA_C_105, "Request to Send" },
+ { OSMO_V110_TA_C_106, "Clear to Send" },
+ { OSMO_V110_TA_C_107, "Data Set Ready" },
+ { OSMO_V110_TA_C_108, "Data Terminal Ready" },
+ { OSMO_V110_TA_C_109, "Data Carrier Detect" },
+ { OSMO_V110_TA_C_133, "Ready for receiving" },
+ { 0, NULL }
+};
+
+static const struct osmo_tdef v110_ta_tdef[] = {
+ { .T = OSMO_V110_TA_TIMER_X1,
+ .unit = OSMO_TDEF_MS, .default_val = 3000, /* suggested in 7.1.5 e) */
+ .desc = "ITU-T V.110 7.1.5 Loss of frame synchronization: sync recovery timer" },
+ { .T = OSMO_V110_TA_TIMER_T1,
+ .unit = OSMO_TDEF_MS, .default_val = 10000, /* suggested in 7.1.2.2 */
+ .desc = "ITU-T V.110 7.1.2 Connect TA to line: sync establishment timer" },
+ { .T = OSMO_V110_TA_TIMER_T2,
+ .unit = OSMO_TDEF_MS, .default_val = 5000, /* suggested in 7.1.4.1 */
+ .desc = "ITU-T V.110 7.1.4 Disconnect mode: disconnect confirmation timer" },
+ { /* end of list */ }
+};
+
+/*********************************************************************************
+ * V.110 TERMINAL ADAPTER FSM
+ *********************************************************************************/
+
+enum v110_ta_fsm_state {
+ V110_TA_ST_IDLE_READY, /* 7.1.1 Idle (or ready) state */
+ V110_TA_ST_CON_TA_TO_LINE, /* 7.1.2 Connect TA to line state */
+ V110_TA_ST_DATA_TRANSFER, /* 7.1.3 Data transfer state */
+ V110_TA_ST_DISCONNECTING, /* 7.1.4 Disconnect mode */
+ V110_TA_ST_RESYNCING, /* 7.1.5 Re-synchronizing state */
+};
+
+enum v110_ta_fsm_event {
+ V110_TA_EV_RX_FRAME_IND, /* a V.110 frame was received by the lower layer */
+ V110_TA_EV_TX_FRAME_RTS, /* a V.110 frame is to be sent by the lower layer */
+ V110_TA_EV_V24_STATUS_CHG, /* V.24 flag-mask has been updated by TE */
+ V110_TA_EV_SYNC_IND, /* the lower layer has synchronized to the frame clock */
+ V110_TA_EV_DESYNC_IND, /* the lower layer has lost frame clock synchronization */
+ V110_TA_EV_TIMEOUT, /* generic event for handling a timeout condition */
+};
+
+static const struct value_string v110_ta_fsm_event_names[] = {
+ { V110_TA_EV_RX_FRAME_IND, "RX_FRAME_IND" },
+ { V110_TA_EV_TX_FRAME_RTS, "TX_FRAME_RTS" },
+ { V110_TA_EV_V24_STATUS_CHG, "V24_STATUS_CHG" },
+ { V110_TA_EV_SYNC_IND, "SYNC_IND" },
+ { V110_TA_EV_DESYNC_IND, "DESYNC_IND" },
+ { V110_TA_EV_TIMEOUT, "TIMEOUT" },
+ { 0, NULL }
+};
+
+enum v110_ta_d_bit_mode {
+ V110_TA_DBIT_M_ALL_ZERO = 0, /* set all bits to binary '0' */
+ V110_TA_DBIT_M_ALL_ONE = 1, /* set all bits to binary '1' */
+ V110_TA_DBIT_M_FORWARD, /* forward D-bits to/from DTE */
+};
+
+struct v110_ta_state {
+ /*! V.24 status flags shared between DTE (user) and DCE (TA, us) */
+ unsigned int v24_flags;
+ struct {
+ /* what kind of D-bits to transmit in V.110 frames */
+ enum v110_ta_d_bit_mode d_bit_mode;
+ /* what to put in S-bits of transmitted V.110 frames */
+ ubit_t s_bits;
+ /* what to put in X-bits of transmitted V.110 frames */
+ ubit_t x_bits;
+ } tx;
+ struct {
+ enum v110_ta_d_bit_mode d_bit_mode;
+ } rx;
+};
+
+struct osmo_v110_ta {
+ const char *name;
+ struct osmo_tdef *Tdefs;
+ struct osmo_fsm_inst *fi;
+ struct osmo_v110_ta_cfg *cfg;
+ struct v110_ta_state state;
+};
+
+static inline bool v110_df_x_bits_are(const struct osmo_v110_decoded_frame *df, ubit_t cmp)
+{
+ return (df->x_bits[0] == cmp) && (df->x_bits[1] == cmp);
+}
+
+static inline bool v110_df_s_bits_are(const struct osmo_v110_decoded_frame *df, ubit_t cmp)
+{
+ /* ITU-T Table 2/V.110 (see also 5.1.2.3) defines the following S-bits:
+ * S1, S3, S4, S6, S8, S9 (6 bits total). However, fr->s_bits[] contains
+ * 9 (MAX_S_BITS) bits, including the undefined bits S2, S5, S7.
+ * Hence we must skip those undefined bits. */
+ static const uint8_t sbit_map[] = { 0, 2, 3, 5, 7, 8 };
+
+ for (unsigned int i = 0; i < ARRAY_SIZE(sbit_map); i++) {
+ uint8_t idx = sbit_map[i];
+ if (df->s_bits[idx] != cmp)
+ return false;
+ }
+
+ return true;
+}
+
+static inline bool v110_df_d_bits_are(const struct osmo_v110_decoded_frame *df, ubit_t cmp)
+{
+ for (unsigned int i = 0; i < MAX_D_BITS; i++) {
+ if (df->d_bits[i] != cmp)
+ return false;
+ }
+
+ return true;
+}
+
+/* handle one V.110 frame and forward user bits to the application */
+static void v110_ta_handle_frame(const struct osmo_v110_ta *ta,
+ const struct osmo_v110_decoded_frame *df)
+{
+ const struct osmo_v110_ta_cfg *cfg = ta->cfg;
+ const struct v110_ta_state *ts = &ta->state;
+ ubit_t user_bits[MAX_D_BITS];
+ int num_user_bits;
+ int rc;
+
+ switch (ts->rx.d_bit_mode) {
+ case V110_TA_DBIT_M_ALL_ZERO:
+ case V110_TA_DBIT_M_ALL_ONE:
+ /* generate as many user bits as needed for the configured rate */
+ num_user_bits = osmo_v110_sync_ra1_get_user_data_chunk_bitlen(cfg->rate);
+ OSMO_ASSERT(num_user_bits > 0);
+ /* set them all to binary '0' or binary '1' */
+ memset(&user_bits[0], (int)ts->rx.d_bit_mode, num_user_bits);
+ cfg->rx_cb(cfg->priv, &user_bits[0], num_user_bits);
+ break;
+ case V110_TA_DBIT_M_FORWARD:
+ rc = osmo_v110_sync_ra1_ir_to_user(cfg->rate, &user_bits[0], sizeof(user_bits), df);
+ if (rc > 0)
+ cfg->rx_cb(cfg->priv, &user_bits[0], rc);
+ /* XXX else: indicate an error somehow? */
+ break;
+ }
+}
+
+/* build one V.110 frame to transmit */
+static void v110_ta_build_frame(const struct osmo_v110_ta *ta,
+ struct osmo_v110_decoded_frame *df)
+{
+ const struct osmo_v110_ta_cfg *cfg = ta->cfg;
+ const struct v110_ta_state *ts = &ta->state;
+ ubit_t user_bits[MAX_D_BITS];
+ int num_user_bits;
+ int rc;
+
+ /* E-bits (E1/E2/E3 may be overwritten below) */
+ memset(df->e_bits, 1, sizeof(df->e_bits));
+ /* S-bits */
+ memset(df->s_bits, ts->tx.s_bits, sizeof(df->s_bits));
+ /* X-bits */
+ memset(df->x_bits, ts->tx.x_bits, sizeof(df->x_bits));
+
+ /* D-bits */
+ switch (ts->tx.d_bit_mode) {
+ case V110_TA_DBIT_M_ALL_ZERO:
+ case V110_TA_DBIT_M_ALL_ONE:
+ /* set them all to binary '0' or binary '1' */
+ memset(df->d_bits, (int)ts->tx.d_bit_mode, sizeof(df->d_bits));
+ break;
+ case V110_TA_DBIT_M_FORWARD:
+ /* how many user bits to retrieve */
+ num_user_bits = osmo_v110_sync_ra1_get_user_data_chunk_bitlen(cfg->rate);
+ OSMO_ASSERT(num_user_bits > 0);
+ /* retrieve user bits from the application */
+ cfg->tx_cb(cfg->priv, &user_bits[0], num_user_bits);
+ /* convert user bits to intermediate rate (store to df) */
+ rc = osmo_v110_sync_ra1_user_to_ir(cfg->rate, df, &user_bits[0], num_user_bits);
+ OSMO_ASSERT(rc == 0);
+ break;
+ }
+}
+
+static void v110_ta_flags_update(struct osmo_v110_ta *ta, unsigned int v24_flags)
+{
+ struct osmo_v110_ta_cfg *cfg = ta->cfg;
+
+ if (ta->state.v24_flags == v24_flags)
+ return;
+ if (cfg->status_update_cb != NULL)
+ cfg->status_update_cb(cfg->priv, v24_flags);
+ ta->state.v24_flags = v24_flags;
+}
+
+static const struct osmo_tdef_state_timeout v110_ta_fsm_timeouts[32] = {
+ [V110_TA_ST_RESYNCING] = { .T = OSMO_V110_TA_TIMER_X1 },
+ [V110_TA_ST_CON_TA_TO_LINE] = { .T = OSMO_V110_TA_TIMER_T1 },
+ [V110_TA_ST_DISCONNECTING] = { .T = OSMO_V110_TA_TIMER_T2 },
+};
+
+#define v110_ta_fsm_state_chg(state) \
+ osmo_tdef_fsm_inst_state_chg(fi, state, \
+ v110_ta_fsm_timeouts, \
+ ((struct osmo_v110_ta *)(fi->priv))->Tdefs, \
+ 0)
+
+/* ITU-T V.110 Section 7.1.1 */
+static void v110_ta_fsm_idle_ready_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct osmo_v110_ta *ta = (struct osmo_v110_ta *)fi->priv;
+ struct v110_ta_state *ts = &ta->state;
+ unsigned int v24_flags = ta->state.v24_flags;
+
+ /* 7.1.1.2 During the idle (or ready) state the TA will transmit continuous binary 1s into the B-channel */
+ ts->tx.d_bit_mode = V110_TA_DBIT_M_ALL_ONE; /* circuit 103: continuous binary '1' */
+ ts->tx.s_bits = V110_SX_BIT_OFF; /* OFF is binary '1' */
+ ts->tx.x_bits = V110_SX_BIT_OFF; /* OFF is binary '1' */
+
+ /* 7.1.1.3 During the idle (or ready) state the TA (DCE) will transmit the following toward the DTE: */
+ /* - circuit 104: continuous binary '1' */
+ ts->rx.d_bit_mode = V110_TA_DBIT_M_ALL_ONE;
+ /* - circuits 107, 106, 109 = OFF */
+ V24_FLAGMASK_SET_OFF(v24_flags, OSMO_V110_TA_C_106);
+ V24_FLAGMASK_SET_OFF(v24_flags, OSMO_V110_TA_C_107);
+ V24_FLAGMASK_SET_OFF(v24_flags, OSMO_V110_TA_C_109);
+ v110_ta_flags_update(ta, v24_flags);
+}
+
+/* ITU-T V.110 Section 7.1.1 */
+static void v110_ta_fsm_idle_ready(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct osmo_v110_ta *ta = (struct osmo_v110_ta *)fi->priv;
+ struct v110_ta_state *ts = &ta->state;
+
+ switch (event) {
+ case V110_TA_EV_V24_STATUS_CHG:
+ /* When the TA is to be switched to the data mode, circuit 108 must be ON */
+ if (V24_FLAGMASK_IS_ON(ts->v24_flags, OSMO_V110_TA_C_108)) {
+ /* 7.12.2: Start timer T1 when switching to CON_TA_LINE */
+ v110_ta_fsm_state_chg(V110_TA_ST_CON_TA_TO_LINE);
+ }
+ break;
+ case V110_TA_EV_RX_FRAME_IND:
+ v110_ta_handle_frame(ta, (const struct osmo_v110_decoded_frame *)data);
+ break;
+ case V110_TA_EV_TX_FRAME_RTS:
+ v110_ta_build_frame(ta, (struct osmo_v110_decoded_frame *)data);
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+/* ITU-T V.110 Section 7.1.2 */
+static void v110_ta_fsm_connect_ta_to_line_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct osmo_v110_ta *ta = (struct osmo_v110_ta *)fi->priv;
+ struct v110_ta_state *ts = &ta->state;
+
+ /* 7.1.2.1 Switching to the data mode causes the TA to transmit the following towards the ISDN: */
+ /* a) frame synchronization pattern as described in 5.1.3.1 and 5.2.1 (done by the API user) */
+ /* b) circuit 103: continuous binary '1' */
+ ts->tx.d_bit_mode = V110_TA_DBIT_M_ALL_ONE;
+ /* c) status bits S = OFF and X = OFF */
+ ts->tx.s_bits = V110_SX_BIT_OFF; /* OFF is binary '1' */
+ ts->tx.x_bits = V110_SX_BIT_OFF; /* OFF is binary '1' */
+
+ /* 7.1.2.2 ... the receiver in the TA will begin to search for the frame synchronization
+ * pattern in the received bit stream (see 5.1.3.1 and 5.2.1) and start timer T1. */
+ OSMO_ASSERT(fi->T == OSMO_V110_TA_TIMER_T1);
+}
+
+/* ITU-T V.110 Section 7.1.2 */
+static void v110_ta_fsm_connect_ta_to_line(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct osmo_v110_ta *ta = (struct osmo_v110_ta *)fi->priv;
+ struct v110_ta_state *ts = &ta->state;
+
+ switch (event) {
+ case V110_TA_EV_V24_STATUS_CHG:
+ /* If circuit 108 is OFF, we go back to IDLE/READY */
+ if (V24_FLAGMASK_IS_OFF(ts->v24_flags, OSMO_V110_TA_C_108))
+ v110_ta_fsm_state_chg(V110_TA_ST_IDLE_READY);
+ break;
+ case V110_TA_EV_SYNC_IND:
+ /* 7.1.2.3 When the receiver recognizes the frame synchronization pattern, it causes the S-
+ * and X-bits in the transmitted frames to be turned ON (provided that circuit 108 is ON). */
+ OSMO_ASSERT(V24_FLAGMASK_IS_ON(ts->v24_flags, OSMO_V110_TA_C_108));
+ ts->tx.s_bits = V110_SX_BIT_ON;
+ ts->tx.x_bits = V110_SX_BIT_ON;
+ break;
+ case V110_TA_EV_RX_FRAME_IND:
+ {
+ const struct osmo_v110_decoded_frame *df = data;
+ unsigned int v24_flags = ta->state.v24_flags;
+
+ /* 7.1.2.4 When the receiver recognizes that the status of bits S and X are ON */
+ if (v110_df_s_bits_are(df, V110_SX_BIT_ON) &&
+ v110_df_x_bits_are(df, V110_SX_BIT_ON)) {
+ /* ... it will perform the following functions: */
+ /* a) Turn ON circuit 107 toward the DTE and stop timer T1 */
+ V24_FLAGMASK_SET_ON(v24_flags, OSMO_V110_TA_C_107);
+ osmo_timer_del(&fi->timer);
+ /* b) Then, circuit 103 may be connected to the data bits in the frame; however, the
+ * DTE must maintain a binary 1 condition on circuit 103 until circuit 106 is turned
+ * ON in the next portion of the sequence. */
+ /* c) Turn ON circuit 109 and connect the data bits to circuit 104. */
+ V24_FLAGMASK_SET_ON(v24_flags, OSMO_V110_TA_C_109);
+ ts->rx.d_bit_mode = V110_TA_DBIT_M_FORWARD;
+ /* d) After an interval of N bits (see 6.3), it will turn ON circuit 106. */
+ V24_FLAGMASK_SET_ON(v24_flags, OSMO_V110_TA_C_106);
+ ts->tx.d_bit_mode = V110_TA_DBIT_M_FORWARD;
+ v110_ta_flags_update(ta, v24_flags);
+ /* Circuit 106 transitioning from OFF to ON will cause the transmitted data to
+ * transition from binary 1 to the data mode. */
+ v110_ta_fsm_state_chg(V110_TA_ST_DATA_TRANSFER);
+ }
+
+ v110_ta_handle_frame(ta, df);
+ break;
+ }
+ case V110_TA_EV_TX_FRAME_RTS:
+ v110_ta_build_frame(ta, (struct osmo_v110_decoded_frame *)data);
+ break;
+ case V110_TA_EV_TIMEOUT:
+ v110_ta_fsm_state_chg(V110_TA_ST_IDLE_READY);
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+/* ITU-T V.110 Section 7.1.3 */
+static void v110_ta_fsm_data_transfer_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct osmo_v110_ta *ta = (struct osmo_v110_ta *)fi->priv;
+ struct v110_ta_state *ts = &ta->state;
+ unsigned int v24_flags = ta->state.v24_flags;
+
+ /* 7.1.3.1 While in the data transfer state, the following circuit conditions exist:
+ * a): 105, 107, 108, and 109 are in the ON condition */
+ /* XXX: OSMO_ASSERT(V24_FLAGMASK_IS_ON(ts->v24_flags, OSMO_V110_TA_C_105)); */
+ V24_FLAGMASK_SET_ON(v24_flags, OSMO_V110_TA_C_107);
+ /* XXX: OSMO_ASSERT(V24_FLAGMASK_IS_ON(ts->v24_flags, OSMO_V110_TA_C_108)); */
+ V24_FLAGMASK_SET_ON(v24_flags, OSMO_V110_TA_C_109);
+ /* b) data is being transmitted on circuit 103 and received on circuit 104 */
+ ts->rx.d_bit_mode = V110_TA_DBIT_M_FORWARD;
+ ts->tx.d_bit_mode = V110_TA_DBIT_M_FORWARD;
+ /* c) circuits 133 (when implemented) and 106 are in the ON condition unless local out-of-band
+ * flow control is being used, either or both circuits may be in the ON or the OFF condition. */
+ if (!ta->cfg->flow_ctrl.end_to_end) {
+ /* XXX: OSMO_ASSERT(V24_FLAGMASK_IS_ON(ts->v24_flags, OSMO_V110_TA_C_133)); */
+ V24_FLAGMASK_SET_ON(v24_flags, OSMO_V110_TA_C_106);
+ }
+ v110_ta_flags_update(ta, v24_flags);
+
+ /* 7.1.3.2 While in the data transfer state, the following status bit conditions exist: */
+ /* a) status bits S in both directions are in the ON condition; */
+ ts->tx.s_bits = V110_SX_BIT_ON;
+ /* b) status bits X in both directions are in the ON condition unless end-to-end flow control
+ * is being used, in which case status bit X in either or both directions may be in the
+ * ON or the OFF condition. */
+ if (!ta->cfg->flow_ctrl.end_to_end)
+ ts->tx.x_bits = V110_SX_BIT_ON;
+}
+
+/* ITU-T V.110 Section 7.1.3 */
+static void v110_ta_fsm_data_transfer(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct osmo_v110_ta *ta = (struct osmo_v110_ta *)fi->priv;
+ struct v110_ta_state *ts = &ta->state;
+
+ /* 7.1.3.3 While in the data transfer state: */
+ /* a) the S status bits shall *not* be mapped to/from the interchange circuits */
+ /* b) the X status bits shall *not* be mapped according to Table 3,
+ * unless end-to-end flow control is implemented */
+ /* TODO: if (ta->cfg->flow_ctrl.end_to_end) { ... } */
+
+ switch (event) {
+ case V110_TA_EV_V24_STATUS_CHG:
+ /* 7.1.4.1 At the completion of the data transfer phase, the local DTE will indicate a
+ * disconnect request by turning OFF circuit 108 */
+ if (V24_FLAGMASK_IS_ON(ts->v24_flags, OSMO_V110_TA_C_108))
+ break;
+ v110_ta_fsm_state_chg(V110_TA_ST_DISCONNECTING);
+ break;
+ case V110_TA_EV_DESYNC_IND:
+ v110_ta_fsm_state_chg(V110_TA_ST_RESYNCING);
+ break;
+ case V110_TA_EV_TX_FRAME_RTS:
+ v110_ta_build_frame(ta, (struct osmo_v110_decoded_frame *)data);
+ break;
+ case V110_TA_EV_RX_FRAME_IND:
+ {
+ const struct osmo_v110_decoded_frame *df = data;
+ unsigned int v24_flags = ta->state.v24_flags;
+
+ /* 7.1.4.2 ... this TA will recognize the transition of the status bits S from
+ * ON to OFF and the data bits from data to binary 0 as a disconnect request */
+ if (v110_df_s_bits_are(df, V110_SX_BIT_OFF) && v110_df_d_bits_are(df, 0)) {
+ /* ... and it will turn OFF circuits 107 and 109. */
+ V24_FLAGMASK_SET_OFF(v24_flags, OSMO_V110_TA_C_107);
+ V24_FLAGMASK_SET_OFF(v24_flags, OSMO_V110_TA_C_109);
+ v110_ta_flags_update(ta, v24_flags);
+ /* DTE should respond by turning OFF circuit 108 */
+ break; /* XXX: shall we forward D-bits to DTE anyway? */
+ }
+
+ v110_ta_handle_frame(ta, df);
+ break;
+ }
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+/* ITU-T V.110 Section 7.1.4 */
+static void v110_ta_fsm_disconnect_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct osmo_v110_ta *ta = (struct osmo_v110_ta *)fi->priv;
+ struct v110_ta_state *ts = &ta->state;
+ unsigned int v24_flags = ta->state.v24_flags;
+
+ /* 7.1.4.1 At the completion of the data transfer phase, the local DTE will indicate a
+ * disconnect request by turning OFF circuit 108. This will cause the following to occur: */
+ /* a) the status bits S in the frame toward ISDN will turn OFF, status bits X are kept ON */
+ ts->tx.s_bits = V110_SX_BIT_OFF;
+ /* b) circuit 106 will be turned OFF */
+ V24_FLAGMASK_SET_OFF(v24_flags, OSMO_V110_TA_C_106);
+ v110_ta_flags_update(ta, v24_flags);
+ /* c) the data bits in the frame will be set to binary 0. */
+ ts->tx.d_bit_mode = V110_TA_DBIT_M_ALL_ZERO;
+
+ /* To guard against the failure of the remote TA to respond to the disconnect request,
+ * the local TA may start a timer T2 (suggested value 5 s) which is stopped by the
+ * reception or transmission of any D-channel clearing message (DISCONNECT, RELEASE,
+ * RELEASE COMPLETE). */
+ OSMO_ASSERT(fi->T == OSMO_V110_TA_TIMER_T2);
+}
+
+/* ITU-T V.110 Section 7.1.4 */
+static void v110_ta_fsm_disconnect(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct osmo_v110_ta *ta = (struct osmo_v110_ta *)fi->priv;
+
+ switch (event) {
+ case V110_TA_EV_V24_STATUS_CHG:
+ break; /* nothing to do */
+ case V110_TA_EV_TX_FRAME_RTS:
+ v110_ta_build_frame(ta, (struct osmo_v110_decoded_frame *)data);
+ break;
+ case V110_TA_EV_RX_FRAME_IND:
+ {
+ const struct osmo_v110_decoded_frame *df = data;
+
+ /* 7.1.4.3 The TA at the station that originated the disconnect request will
+ * recognize reception of S = OFF or the loss of framing signals as a disconnect
+ * acknowledgement and turn OFF circuits 107 and 109. */
+ if (v110_df_s_bits_are(df, V110_SX_BIT_OFF)) {
+ /* circuits 107 and 109 set to off in .onenter() */
+ v110_ta_fsm_state_chg(V110_TA_ST_IDLE_READY);
+ }
+
+ v110_ta_handle_frame(ta, df);
+ break;
+ }
+ case V110_TA_EV_DESYNC_IND:
+ case V110_TA_EV_TIMEOUT:
+ /* circuits 107 and 109 set to off in .onenter() */
+ v110_ta_fsm_state_chg(V110_TA_ST_IDLE_READY);
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+/* ITU-T V.110 Section 7.1.5 */
+static void v110_ta_fsm_resyncing_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct osmo_v110_ta *ta = (struct osmo_v110_ta *)fi->priv;
+ struct v110_ta_state *ts = &ta->state;
+
+ /* 7.1.5 In the event of loss of frame synchronization, the (local) TA should
+ * attempt to resynchronize as follows: */
+ /* a) Place circuit 104 in binary 1 condition (passes from the data mode) */
+ ts->rx.d_bit_mode = V110_TA_DBIT_M_ALL_ONE;
+ /* b) Turn OFF status bit X in the transmitted frame */
+ ts->tx.x_bits = V110_SX_BIT_OFF;
+
+ /* guard timeout, see 7.1.5 e) */
+ OSMO_ASSERT(fi->T == OSMO_V110_TA_TIMER_X1);
+}
+
+/* ITU-T V.110 Section 7.1.5 */
+static void v110_ta_fsm_resyncing(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct osmo_v110_ta *ta = (struct osmo_v110_ta *)fi->priv;
+ struct v110_ta_state *ts = &ta->state;
+ unsigned int v24_flags = ta->state.v24_flags;
+
+ switch (event) {
+ case V110_TA_EV_V24_STATUS_CHG:
+ break; /* TODO: handle circuit 108 being set to OFF? */
+ case V110_TA_EV_TX_FRAME_RTS:
+ v110_ta_build_frame(ta, (struct osmo_v110_decoded_frame *)data);
+ break;
+ case V110_TA_EV_SYNC_IND:
+ /* f) If resynchronization is achieved, the local TA should turn ON status bit X */
+ ts->tx.x_bits = V110_SX_BIT_ON;
+ v110_ta_fsm_state_chg(V110_TA_ST_DATA_TRANSFER);
+ break;
+ case V110_TA_EV_TIMEOUT:
+ /* e) If after an interval of X1 seconds the local TA cannot attain synchronization,
+ * it should send a disconnect request by turning OFF all of the status bits for several
+ * (at least three) frames with data bits set to binary 0 and then disconnect by turning
+ * OFF circuit 107 and transferring to the disconnected mode as discussed in 7.1.4.2. */
+ ts->tx.s_bits = V110_SX_BIT_OFF;
+ ts->tx.x_bits = V110_SX_BIT_OFF;
+ ts->tx.d_bit_mode = V110_TA_DBIT_M_ALL_ZERO;
+ /* TODO: actually Tx those frames (delay state transition) */
+ V24_FLAGMASK_SET_OFF(v24_flags, OSMO_V110_TA_C_107);
+ v110_ta_flags_update(ta, v24_flags);
+ v110_ta_fsm_state_chg(V110_TA_ST_DISCONNECTING);
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static int v110_ta_timer_cb(struct osmo_fsm_inst *fi)
+{
+ osmo_fsm_inst_dispatch(fi, V110_TA_EV_TIMEOUT, NULL);
+ return 0;
+}
+
+static const struct osmo_fsm_state v110_ta_states[] = {
+ [V110_TA_ST_IDLE_READY] = {
+ .name = "IDLE_READY",
+ .in_event_mask = S(V110_TA_EV_V24_STATUS_CHG)
+ | S(V110_TA_EV_TX_FRAME_RTS)
+ | S(V110_TA_EV_RX_FRAME_IND),
+ .out_state_mask = S(V110_TA_ST_IDLE_READY)
+ | S(V110_TA_ST_CON_TA_TO_LINE),
+ .action = &v110_ta_fsm_idle_ready,
+ .onenter = &v110_ta_fsm_idle_ready_onenter,
+ },
+ [V110_TA_ST_CON_TA_TO_LINE] = {
+ .name = "CONNECT_TA_TO_LINE",
+ .in_event_mask = S(V110_TA_EV_V24_STATUS_CHG)
+ | S(V110_TA_EV_TIMEOUT)
+ | S(V110_TA_EV_SYNC_IND)
+ | S(V110_TA_EV_TX_FRAME_RTS)
+ | S(V110_TA_EV_RX_FRAME_IND),
+ .out_state_mask = S(V110_TA_ST_DATA_TRANSFER)
+ | S(V110_TA_ST_IDLE_READY),
+ .action = &v110_ta_fsm_connect_ta_to_line,
+ .onenter = &v110_ta_fsm_connect_ta_to_line_onenter,
+ },
+ [V110_TA_ST_DATA_TRANSFER] = {
+ .name = "DATA_TRANSFER",
+ .in_event_mask = S(V110_TA_EV_V24_STATUS_CHG)
+ | S(V110_TA_EV_DESYNC_IND)
+ | S(V110_TA_EV_TX_FRAME_RTS)
+ | S(V110_TA_EV_RX_FRAME_IND),
+ .out_state_mask = S(V110_TA_ST_RESYNCING)
+ | S(V110_TA_ST_DISCONNECTING),
+ .action = &v110_ta_fsm_data_transfer,
+ .onenter = &v110_ta_fsm_data_transfer_onenter,
+ },
+ [V110_TA_ST_DISCONNECTING] = {
+ .name = "DISCONNECTING",
+ .in_event_mask = S(V110_TA_EV_V24_STATUS_CHG)
+ | S(V110_TA_EV_TIMEOUT)
+ | S(V110_TA_EV_TX_FRAME_RTS)
+ | S(V110_TA_EV_RX_FRAME_IND)
+ | S(V110_TA_EV_DESYNC_IND),
+ .out_state_mask = S(V110_TA_ST_IDLE_READY),
+ .action = &v110_ta_fsm_disconnect,
+ .onenter = &v110_ta_fsm_disconnect_onenter,
+ },
+ [V110_TA_ST_RESYNCING] = {
+ .name = "RESYNCING",
+ .in_event_mask = S(V110_TA_EV_V24_STATUS_CHG)
+ | S(V110_TA_EV_TIMEOUT)
+ | S(V110_TA_EV_TX_FRAME_RTS)
+ | S(V110_TA_EV_SYNC_IND),
+ .out_state_mask = S(V110_TA_ST_IDLE_READY)
+ | S(V110_TA_ST_DATA_TRANSFER),
+ .action = &v110_ta_fsm_resyncing,
+ .onenter = &v110_ta_fsm_resyncing_onenter,
+ },
+};
+
+static struct osmo_fsm osmo_v110_ta_fsm = {
+ .name = "V110-TA",
+ .states = v110_ta_states,
+ .num_states = ARRAY_SIZE(v110_ta_states),
+ .timer_cb = v110_ta_timer_cb,
+ .log_subsys = DLGLOBAL,
+ .event_names = v110_ta_fsm_event_names,
+};
+
+static __attribute__((constructor)) void on_dso_load(void)
+{
+ OSMO_ASSERT(osmo_fsm_register(&osmo_v110_ta_fsm) == 0);
+}
+
+/*! Allocate a V.110 TA (Terminal Adapter) instance.
+ * \param[in] ctx parent talloc context.
+ * \param[in] name name of the TA instance.
+ * \param[in] cfg initial configuration of the TA instance.
+ * \returns pointer to allocated TA instance; NULL on error. */
+struct osmo_v110_ta *osmo_v110_ta_alloc(void *ctx, const char *name,
+ const struct osmo_v110_ta_cfg *cfg)
+{
+ struct osmo_v110_ta *ta;
+
+ OSMO_ASSERT(cfg != NULL);
+ OSMO_ASSERT(cfg->rx_cb != NULL);
+ OSMO_ASSERT(cfg->tx_cb != NULL);
+
+ /* local (TE-TA) flow control is not implemented */
+ if (cfg->flow_ctrl.local != OSMO_V110_LOCAL_FLOW_CTRL_NONE) {
+ LOGP(DLGLOBAL, LOGL_ERROR, "Local (TE-TA) flow control is not implemented\n");
+ return NULL;
+ }
+
+ ta = talloc_zero(ctx, struct osmo_v110_ta);
+ if (ta == NULL)
+ return NULL;
+
+ ta->name = talloc_strdup(ta, name);
+ ta->cfg = talloc_memdup(ta, cfg, sizeof(*cfg));
+ if (ta->name == NULL || ta->cfg == NULL)
+ goto exit_free;
+
+ ta->Tdefs = talloc_memdup(ta, v110_ta_tdef, sizeof(v110_ta_tdef));
+ if (ta->Tdefs == NULL)
+ goto exit_free;
+ osmo_tdefs_reset(ta->Tdefs); /* apply default values */
+
+ ta->fi = osmo_fsm_inst_alloc(&osmo_v110_ta_fsm, ta, ta, LOGL_DEBUG, name);
+ if (ta->fi == NULL)
+ goto exit_free;
+
+ /* perform a loop transition to init the internal state */
+ osmo_fsm_inst_state_chg(ta->fi, V110_TA_ST_IDLE_READY, 0, 0);
+
+ return ta;
+
+exit_free:
+ if (ta->fi != NULL)
+ osmo_fsm_inst_free(ta->fi);
+ talloc_free(ta);
+ return NULL;
+}
+
+/*! Release memory taken by the given V.110 TA instance.
+ * \param[in] ta TA instance to be free()d. */
+void osmo_v110_ta_free(struct osmo_v110_ta *ta)
+{
+ if (ta == NULL)
+ return;
+ if (ta->fi != NULL)
+ osmo_fsm_inst_free(ta->fi);
+ talloc_free(ta); /* also free()s name and cfg */
+}
+
+/*! Configure a timer of the given V.110 TA instance.
+ * \param[in] ta TA instance to be configured.
+ * \param[in] timer a timer to be configured.
+ * \param[in] val_ms the new timeout value to set (in milliseconds).
+ * \returns 0 in case of success; negative on error. */
+int osmo_v110_ta_set_timer_val_ms(struct osmo_v110_ta *ta,
+ enum osmo_v110_ta_timer timer,
+ unsigned long val_ms)
+{
+ return osmo_tdef_set(ta->Tdefs, (int)timer, val_ms, OSMO_TDEF_MS);
+}
+
+/*! Feed a [decoded] V.110 frame into the given TA instance.
+ *
+ * This function, like its _out counterpart, is intended to be used by the lower layers
+ * receiving V.110 frames over some medium. The caller of this function is responsible
+ * for finding the synchronization pattern (if needed), aligning to the frame boundaries,
+ * and decoding frames using osmo_v110_decode_frame() or osmo_csd_*_decode_frame().
+ *
+ * Bits E1/E2/E3 are expected to be set by the caller (if not being transmitted
+ * over the medium) in accordance with the configured synchronous user rate.
+ *
+ * Bits D1..D48 are passed to the bit rate adaption function RA1. The resulting output
+ * is then passed to the upper layer (application) via the configured .rx_cb(). Though,
+ * in certain states of the TA's FSM, bits D1..D48 are ignored and the upper layer
+ * gets a sequence of binary '0' or '1'.
+ *
+ * \param[in] ta TA instance to feed the given frame into.
+ * \param[in] in pointer to a [decoded] V.110 frame.
+ * \returns 0 in case of success; negative on error. */
+int osmo_v110_ta_frame_in(struct osmo_v110_ta *ta, const struct osmo_v110_decoded_frame *in)
+{
+ return osmo_fsm_inst_dispatch(ta->fi, V110_TA_EV_RX_FRAME_IND, (void *)in);
+}
+
+/*! Pull a [decoded] V.110 frame out of the given TA instance.
+ *
+ * This function, like its _in counterpart, is intended to be used by the lower layers
+ * transmitting V.110 frames over some medium. The caller of this function is responsible
+ * for encoding the output frame using osmo_v110_encode_frame() or osmo_csd_*_encode_frame().
+ *
+ * Bits E1/E2/E3 are set in accordance with the configured synchronous user rate.
+ * Bits E4/E5/E6/E7 are unconditionally set to binary '1'.
+ *
+ * Bits D1..D48 are set depending on the state of TA's FSM:
+ *
+ * - In data transfer mode, the user bits are obtained from the upper layer (application)
+ * via the configured .tx_cb(), and then passed to the bit rate adaption function RA1,
+ * which generates bits D1..D48.
+ * - In other modes, bits D1..D48 are all set to binary '0' or '1'.
+ *
+ * \param[in] ta TA instance to pull a frame from.
+ * \param[out] out where to store a [decoded] V.110 frame.
+ * \returns 0 in case of success; negative on error. */
+int osmo_v110_ta_frame_out(struct osmo_v110_ta *ta, struct osmo_v110_decoded_frame *out)
+{
+ return osmo_fsm_inst_dispatch(ta->fi, V110_TA_EV_TX_FRAME_RTS, (void *)out);
+}
+
+/*! Indicate a synchronization establishment event.
+ *
+ * This function is intended to be called when the lower layer
+ * achieves synchronization to the frame clock.
+ *
+ * \param[in] ta TA instance to indicate the event to.
+ * \returns 0 in case of success; negative on error. */
+int osmo_v110_ta_sync_ind(struct osmo_v110_ta *ta)
+{
+ return osmo_fsm_inst_dispatch(ta->fi, V110_TA_EV_SYNC_IND, NULL);
+}
+
+/*! Indicate a synchronization loss event.
+ *
+ * This function is intended to be called when the lower layer
+ * experiences a loss of synchronization with the frame clock.
+ *
+ * \param[in] ta TA instance to indicate the event to.
+ * \returns 0 in case of success; negative on error. */
+int osmo_v110_ta_desync_ind(struct osmo_v110_ta *ta)
+{
+ return osmo_fsm_inst_dispatch(ta->fi, V110_TA_EV_DESYNC_IND, NULL);
+}
+
+/*! Get the V.24 status bit-mask of the given TA instance.
+ * \param[in] ta TA instance to get the circuit bit-mask.
+ * \returns bitmask of OSMO_V110_TA_C_*. */
+unsigned int osmo_v110_ta_get_status(const struct osmo_v110_ta *ta)
+{
+ return ta->state.v24_flags;
+}
+
+/*! Set the V.24 status bit-mask of the given TA instance.
+ * \param[in] ta TA instance to update the circuit state.
+ * \param[in] status bit-mask of OSMO_V110_TA_C_*.
+ * \returns 0 on success; negative on error. */
+static int v110_ta_set_status(struct osmo_v110_ta *ta, unsigned int status)
+{
+ const unsigned int old_status = ta->state.v24_flags;
+ int rc = 0;
+
+ ta->state.v24_flags = status;
+ if (status != old_status)
+ rc = osmo_fsm_inst_dispatch(ta->fi, V110_TA_EV_V24_STATUS_CHG, NULL);
+
+ return rc;
+}
+
+/*! Get state of a V.24 circuit of the given TA instance.
+ * \param[in] ta TA instance to get the circuit state.
+ * \param[in] circuit a V.24 circuit, one of OSMO_V110_TA_C_*.
+ * \returns circuit state: active (true) or inactive (false). */
+bool osmo_v110_ta_get_circuit(const struct osmo_v110_ta *ta,
+ enum osmo_v110_ta_circuit circuit)
+{
+ return V24_FLAGMASK_IS_ON(ta->state.v24_flags, circuit);
+}
+
+/*! Activate/deactivate a V.24 circuit of the given TA instance.
+ * \param[in] ta TA instance to update the circuit state.
+ * \param[in] circuit a V.24 circuit, one of OSMO_V110_TA_C_* (DTE->DCE).
+ * \param[in] active activate (true) or deactivate (false) the circuit.
+ * \returns 0 on success; negative on error. */
+int osmo_v110_ta_set_circuit(struct osmo_v110_ta *ta,
+ enum osmo_v110_ta_circuit circuit, bool active)
+{
+ unsigned int status = ta->state.v24_flags;
+
+ /* permit setting only DTE->DCE circuits */
+ switch (circuit) {
+ case OSMO_V110_TA_C_105:
+ case OSMO_V110_TA_C_108:
+ case OSMO_V110_TA_C_133:
+ break;
+ default:
+ LOGPFSML(ta->fi, LOGL_ERROR,
+ "Setting circuit %s is not permitted (wrong direction?)\n",
+ osmo_v110_ta_circuit_name(circuit));
+ return -EACCES;
+ }
+
+ if (active)
+ V24_FLAGMASK_SET_ON(status, circuit);
+ else
+ V24_FLAGMASK_SET_OFF(status, circuit);
+
+ return v110_ta_set_status(ta, status);
+}
diff --git a/src/pseudotalloc/Makefile.am b/src/pseudotalloc/Makefile.am
index 3c78bba8..030b281b 100644
--- a/src/pseudotalloc/Makefile.am
+++ b/src/pseudotalloc/Makefile.am
@@ -1,4 +1,5 @@
-AM_CFLAGS = -Wall -I. $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)/include
+AM_CPPFLAGS = -I. -I$(top_srcdir)/include -I$(top_builddir)/include -I$(top_builddir)
+AM_CFLAGS = -Wall
if ENABLE_PSEUDOTALLOC
diff --git a/src/pseudotalloc/pseudotalloc.c b/src/pseudotalloc/pseudotalloc.c
index 25425e57..895c6dd0 100644
--- a/src/pseudotalloc/pseudotalloc.c
+++ b/src/pseudotalloc/pseudotalloc.c
@@ -55,21 +55,21 @@ void talloc_set_name_const(const void *ptr, const char *name)
{
}
-char *talloc_strdup(const void *context, const char *p)
+void *talloc_memdup(const void *ctx, const void *p, size_t size)
{
- char *ptr;
- size_t len;
+ void *ptr;
- if (!p)
- return NULL;
- len = strlen(p);
+ ptr = talloc_size(ctx, size);
+ if (ptr && p)
+ memcpy(ptr, p, size);
+ return ptr;
+}
- ptr = talloc_size(context, len+1);
- if (!ptr)
+char *talloc_strdup(const void *ctx, const char *p)
+{
+ if (!p)
return NULL;
- memcpy(ptr, p, len+1);
-
- return ptr;
+ return talloc_memdup(ctx, p, strlen(p) + 1);
}
void *talloc_pool(const void *context, size_t size)
diff --git a/src/pseudotalloc/talloc.h b/src/pseudotalloc/talloc.h
index d257a981..38935991 100644
--- a/src/pseudotalloc/talloc.h
+++ b/src/pseudotalloc/talloc.h
@@ -50,7 +50,8 @@ int _talloc_free(void *ptr, const char *location);
void *talloc_named_const(const void *context, size_t size, const char *name);
void *talloc_named(const void *context, size_t size, const char *fmt, ...);
void talloc_set_name_const(const void *ptr, const char *name);
-char *talloc_strdup(const void *t, const char *p);
+void *talloc_memdup(const void *ctx, const void *p, size_t size);
+char *talloc_strdup(const void *ctx, const char *p);
void *talloc_pool(const void *context, size_t size);
#define talloc_array(ctx, type, count) (type *)_talloc_array(ctx, sizeof(type), count, #type)
void *_talloc_array(const void *ctx, size_t el_size, unsigned count, const char *name);
diff --git a/src/sim/Makefile.am b/src/sim/Makefile.am
index 4e2348bd..0f6be576 100644
--- a/src/sim/Makefile.am
+++ b/src/sim/Makefile.am
@@ -1,9 +1,9 @@
# 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 modifications: https://www.gnu.org/software/libtool/manual/html_node/Versioning.html
-LIBVERSION=2:0:0
+LIBVERSION=3:2:1
-AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)/include
+AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_builddir)/include -I$(top_builddir)
AM_CFLAGS = -fPIC -Wall $(TALLOC_CFLAGS)
AM_LDFLAGS = $(COVERAGE_LDFLAGS)
@@ -15,9 +15,12 @@ lib_LTLIBRARIES = libosmosim.la
libosmosim_la_SOURCES = core.c reader.c class_tables.c \
card_fs_sim.c card_fs_usim.c card_fs_uicc.c \
card_fs_isim.c card_fs_hpsim.c card_fs_tetra.c
-libosmosim_la_LDFLAGS = -version-info $(LIBVERSION)
+libosmosim_la_LDFLAGS = \
+ -version-info $(LIBVERSION) \
+ -no-undefined \
+ $(NULL)
libosmosim_la_LIBADD = \
- $(top_builddir)/src/libosmocore.la \
+ $(top_builddir)/src/core/libosmocore.la \
$(top_builddir)/src/gsm/libosmogsm.la \
$(TALLOC_LIBS)
if ENABLE_PCSC
diff --git a/src/sim/card_fs_hpsim.c b/src/sim/card_fs_hpsim.c
index 4a5f7d9a..2c115b75 100644
--- a/src/sim/card_fs_hpsim.c
+++ b/src/sim/card_fs_hpsim.c
@@ -17,10 +17,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
diff --git a/src/sim/card_fs_isim.c b/src/sim/card_fs_isim.c
index f11c0294..1a38da2f 100644
--- a/src/sim/card_fs_isim.c
+++ b/src/sim/card_fs_isim.c
@@ -17,10 +17,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
diff --git a/src/sim/card_fs_sim.c b/src/sim/card_fs_sim.c
index 55ce9af7..f07a2370 100644
--- a/src/sim/card_fs_sim.c
+++ b/src/sim/card_fs_sim.c
@@ -17,10 +17,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <errno.h>
diff --git a/src/sim/card_fs_tetra.c b/src/sim/card_fs_tetra.c
index 12853a52..597e38b7 100644
--- a/src/sim/card_fs_tetra.c
+++ b/src/sim/card_fs_tetra.c
@@ -17,10 +17,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <errno.h>
diff --git a/src/sim/card_fs_uicc.c b/src/sim/card_fs_uicc.c
index 5dcaaa12..87def0e4 100644
--- a/src/sim/card_fs_uicc.c
+++ b/src/sim/card_fs_uicc.c
@@ -17,10 +17,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
diff --git a/src/sim/card_fs_usim.c b/src/sim/card_fs_usim.c
index 8f880e75..8cff3fc3 100644
--- a/src/sim/card_fs_usim.c
+++ b/src/sim/card_fs_usim.c
@@ -15,10 +15,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
diff --git a/src/sim/class_tables.c b/src/sim/class_tables.c
index 6f541ee2..29c1e40e 100644
--- a/src/sim/class_tables.c
+++ b/src/sim/class_tables.c
@@ -13,17 +13,13 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <stdint.h>
#include <osmocom/core/utils.h>
#include <osmocom/sim/class_tables.h>
-static const uint8_t iso7816_ins_tbl[] = {
+static const uint8_t iso7816_ins_tbl[256] = {
[0xB0] = 2, /* READ BIN */
[0xD0] = 3, /* WRITE BIN */
[0xD6] = 3, /* UPDATE BIN */
@@ -117,7 +113,7 @@ static const uint8_t uicc_ins_tbl_046[256] = {
[0xD6] = 3, /* UPDATE BINARY */
[0xB2] = 2, /* READ RECORD */
[0xDC] = 3, /* UPDATE RECORD */
- [0xA2] = 4, /* SEEK */
+ [0xA2] = 4, /* SEARCH RECORD */
[0x20] = 3, /* VERIFY PIN */
[0x24] = 3, /* CHANGE PIN */
[0x26] = 3, /* DISABLE PIN */
@@ -177,6 +173,24 @@ static int uicc046_cla_ins_helper(const struct osim_cla_ins_case *cic,
return 0;
}
+static int gp_cla_ins_helper(const struct osim_cla_ins_case *cic,
+ const uint8_t *hdr)
+{
+ uint8_t ins = hdr[1];
+ uint8_t p1 = hdr[2];
+
+ switch (ins) {
+ case 0xE2: /* STORE DATA */
+ switch (p1 & 0x01) {
+ case 1:
+ return 4;
+ default:
+ return 3;
+ }
+ }
+ return 0;
+}
+
/* ETSI TS 102 221, Table 10.5, CLA = 0x8x, 0xCx or 0xEx */
static const uint8_t uicc_ins_tbl_8ce[256] = {
[0xF2] = 2, /* STATUS */
@@ -184,6 +198,7 @@ static const uint8_t uicc_ins_tbl_8ce[256] = {
[0xCB] = 4, /* RETRIEVE DATA */
[0xDB] = 3, /* SET DATA */
[0xAA] = 3, /* TERMINAL CAPABILITY */
+ [0x78] = 4, /* GET IDENTITY */
};
/* ETSI TS 102 221, Table 10.5, CLA = 0x80 */
@@ -192,6 +207,22 @@ static const uint8_t uicc_ins_tbl_80[256] = {
[0xC2] = 4, /* ENVELOPE */
[0x12] = 2, /* FETCH */
[0x14] = 3, /* TERMINAL RESPONSE */
+ [0x76] = 4, /* SUSPEND UICC */
+ [0x7A] = 4, /* EXCHANGE CAPABILITIES */
+};
+
+/* Card Specification v2.3.1*/
+static const uint8_t gp_ins_tbl_8ce[256] = {
+ [0xE4] = 4, /* DELETE */
+ [0xE2] = 0x80, /* STORE DATA */
+ [0xCA] = 4, /* GET DATA */
+ [0xCB] = 4, /* GET DATA */
+ [0xF2] = 4, /* GET STATUS */
+ [0xE6] = 4, /* INSTALL */
+ [0xE8] = 4, /* LOAD */
+ [0xD8] = 4, /* PUT KEY */
+ [0xF0] = 3, /* SET STATUS */
+ [0xC0] = 2, /* GET RESPONSE */
};
static const struct osim_cla_ins_case uicc_ins_case[] = {
@@ -226,6 +257,21 @@ static const struct osim_cla_ins_case uicc_ins_case[] = {
.cla = 0xE0,
.cla_mask = 0xF0,
.ins_tbl = uicc_ins_tbl_8ce,
+ }, {
+ .cla = 0x80,
+ .cla_mask = 0xF0,
+ .helper = gp_cla_ins_helper,
+ .ins_tbl = gp_ins_tbl_8ce,
+ }, {
+ .cla = 0xC0,
+ .cla_mask = 0xF0,
+ .helper = gp_cla_ins_helper,
+ .ins_tbl = gp_ins_tbl_8ce,
+ }, {
+ .cla = 0xE0,
+ .cla_mask = 0xF0,
+ .helper = gp_cla_ins_helper,
+ .ins_tbl = gp_ins_tbl_8ce,
},
};
@@ -273,7 +319,23 @@ static const struct osim_cla_ins_case uicc_sim_ins_case[] = {
.cla = 0xE0,
.cla_mask = 0xF0,
.ins_tbl = uicc_ins_tbl_8ce,
+ }, {
+ .cla = 0x80,
+ .cla_mask = 0xF0,
+ .helper = gp_cla_ins_helper,
+ .ins_tbl = gp_ins_tbl_8ce,
+ }, {
+ .cla = 0xC0,
+ .cla_mask = 0xF0,
+ .helper = gp_cla_ins_helper,
+ .ins_tbl = gp_ins_tbl_8ce,
+ }, {
+ .cla = 0xE0,
+ .cla_mask = 0xF0,
+ .helper = gp_cla_ins_helper,
+ .ins_tbl = gp_ins_tbl_8ce,
},
+
};
const struct osim_cla_ins_card_profile osim_uicc_sim_cic_profile = {
@@ -288,6 +350,13 @@ const uint8_t usim_ins_case[256] = {
[0x88] = 4, /* AUTHENTICATE */
};
+/* https://learn.microsoft.com/en-us/windows-hardware/drivers/smartcard/discovery-process */
+static const uint8_t microsoft_discovery_ins_tbl[256] = {
+ [0xA4] = 4, /* SELECT FILE */
+ [0xCA] = 2, /* GET DATA */
+ [0xC0] = 2, /* GET RESPONSE */
+};
+
int osim_determine_apdu_case(const struct osim_cla_ins_card_profile *prof,
const uint8_t *hdr)
{
@@ -305,12 +374,23 @@ int osim_determine_apdu_case(const struct osim_cla_ins_card_profile *prof,
case 0x80:
return cic->helper(cic, hdr);
case 0x00:
- /* continue with fruther cic, rather than abort
+ /* continue with further cic, rather than abort
* now */
continue;
default:
return rc;
}
}
+ /* special handling for Microsoft who insists to use INS=0xCA in CLA=0x00 which is not
+ * really part of GSM SIM, ETSI UICC or 3GPP USIM specifications, but only ISO7816. Rather than adding
+ * it to each and every card profile, let's add the instructions listed at
+ * https://learn.microsoft.com/en-us/windows-hardware/drivers/smartcard/discovery-process explicitly
+ * here. They will only be used in case no more specific match was found in the actual profile above. */
+ if (cla == 0x00) {
+ rc = microsoft_discovery_ins_tbl[ins];
+ if (rc)
+ return rc;
+ }
+
return 0;
}
diff --git a/src/sim/core.c b/src/sim/core.c
index d11c2d83..fa17e12d 100644
--- a/src/sim/core.c
+++ b/src/sim/core.c
@@ -17,10 +17,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
diff --git a/src/sim/reader.c b/src/sim/reader.c
index d5292baa..982b2eef 100644
--- a/src/sim/reader.c
+++ b/src/sim/reader.c
@@ -17,10 +17,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
@@ -44,7 +40,7 @@ static int get_sw(struct msgb *resp)
{
int ret;
- if (!msgb_apdu_de(resp) || msgb_apdu_le(resp) < 2)
+ if (!resp->l2h || msgb_apdu_le(resp) < 2)
return -EIO;
ret = msgb_get_u16(resp);
@@ -123,7 +119,6 @@ transceive_again:
/* save SW */
sw = msgb_apdu_sw(tmsg);
- printf("sw = 0x%04x\n", sw);
msgb_apdu_sw(amsg) = sw;
switch (msgb_apdu_case(amsg)) {
@@ -278,3 +273,24 @@ struct osim_card_hdl *osim_card_open(struct osim_reader_hdl *rh, enum osim_proto
return ch;
}
+
+int osim_card_reset(struct osim_card_hdl *card, bool cold_reset)
+{
+ struct osim_reader_hdl *rh = card->reader;
+
+ return rh->ops->card_reset(card, cold_reset);
+}
+
+int osim_card_close(struct osim_card_hdl *card)
+{
+ struct osim_reader_hdl *rh = card->reader;
+ int rc;
+
+ rc = rh->ops->card_close(card);
+
+ card->reader = NULL;
+ talloc_free(card);
+ rh->card = NULL;
+
+ return rc;
+}
diff --git a/src/sim/reader_pcsc.c b/src/sim/reader_pcsc.c
index c37380a3..c37072ee 100644
--- a/src/sim/reader_pcsc.c
+++ b/src/sim/reader_pcsc.c
@@ -17,10 +17,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
@@ -41,12 +37,9 @@
if (rv != SCARD_S_SUCCESS) { \
fprintf(stderr, text ": %s (0x%lX)\n", pcsc_stringify_error(rv), rv); \
goto end; \
-} else { \
- printf(text ": OK\n\n"); \
}
-
struct pcsc_reader_state {
SCARDCONTEXT hContext;
SCARDHANDLE hCard;
@@ -56,6 +49,27 @@ struct pcsc_reader_state {
char *name;
};
+static int pcsc_get_atr(struct osim_card_hdl *card)
+{
+ struct osim_reader_hdl *rh = card->reader;
+ struct pcsc_reader_state *st = rh->priv;
+ char pbReader[MAX_READERNAME];
+ DWORD dwReaderLen = sizeof(pbReader);
+ DWORD dwAtrLen = sizeof(card->atr);
+ DWORD dwState, dwProt;
+ long rc;
+
+ rc = SCardStatus(st->hCard, pbReader, &dwReaderLen, &dwState, &dwProt,
+ card->atr, &dwAtrLen);
+ PCSC_ERROR(rc, "SCardStatus");
+ card->atr_len = dwAtrLen;
+
+ return 0;
+
+end:
+ return -EIO;
+}
+
static struct osim_reader_hdl *pcsc_reader_open(int num, const char *id, void *ctx)
{
struct osim_reader_hdl *rh;
@@ -130,12 +144,42 @@ static struct osim_card_hdl *pcsc_card_open(struct osim_reader_hdl *rh,
chan->card = card;
llist_add(&chan->list, &card->channels);
+ pcsc_get_atr(card);
+
return card;
end:
return NULL;
}
+static int pcsc_card_reset(struct osim_card_hdl *card, bool cold_reset)
+{
+ struct pcsc_reader_state *st = card->reader->priv;
+ LONG rc;
+
+ rc = SCardReconnect(st->hCard, SCARD_SHARE_SHARED, SCARD_PROTOCOL_T0,
+ cold_reset ? SCARD_UNPOWER_CARD : SCARD_RESET_CARD,
+ &st->dwActiveProtocol);
+ PCSC_ERROR(rc, "SCardReconnect");
+
+ return 0;
+end:
+ return -EIO;
+}
+
+static int pcsc_card_close(struct osim_card_hdl *card)
+{
+ struct pcsc_reader_state *st = card->reader->priv;
+ LONG rc;
+
+ rc = SCardDisconnect(st->hCard, SCARD_UNPOWER_CARD);
+ PCSC_ERROR(rc, "SCardDisconnect");
+
+ return 0;
+end:
+ return -EIO;
+}
+
static int pcsc_transceive(struct osim_reader_hdl *rh, struct msgb *msg)
{
@@ -143,13 +187,10 @@ static int pcsc_transceive(struct osim_reader_hdl *rh, struct msgb *msg)
DWORD rlen = msgb_tailroom(msg);
LONG rc;
- printf("TX: %s\n", osmo_hexdump(msg->data, msg->len));
-
rc = SCardTransmit(st->hCard, st->pioSendPci, msg->data, msgb_length(msg),
&st->pioRecvPci, msg->tail, &rlen);
PCSC_ERROR(rc, "SCardEndTransaction");
- printf("RX: %s\n", osmo_hexdump(msg->tail, rlen));
msgb_put(msg, rlen);
msgb_apdu_le(msg) = rlen;
@@ -162,6 +203,8 @@ const struct osim_reader_ops pcsc_reader_ops = {
.name = "PC/SC",
.reader_open = pcsc_reader_open,
.card_open = pcsc_card_open,
+ .card_reset = pcsc_card_reset,
+ .card_close = pcsc_card_close,
.transceive = pcsc_transceive,
};
diff --git a/src/stat_item.c b/src/stat_item.c
deleted file mode 100644
index ba364640..00000000
--- a/src/stat_item.c
+++ /dev/null
@@ -1,388 +0,0 @@
-/*! \file stat_item.c
- * utility routines for keeping statistical values */
-/*
- * (C) 2009-2010 by Harald Welte <laforge@gnumonks.org>
- * (C) 2015 by sysmocom - s.f.m.c. GmbH
- *
- * All Rights Reserved
- *
- * SPDX-License-Identifier: GPL-2.0+
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- */
-
-/*! \addtogroup osmo_stat_item
- * @{
- *
- * This osmo_stat_item module adds instrumentation capabilities to
- * gather measurement and statistical values in a similar fashion to
- * what we have as \ref osmo_counter_group.
- *
- * As opposed to counters, osmo_stat_item do not increment but consist
- * of a configurable-sized FIFO, which can store not only the current
- * (most recent) value, but also historic values.
- *
- * The only supported value type is an int32_t.
- *
- * Getting values from the osmo_stat_item does not modify its state to
- * allow for multiple independent back-ends retrieving values (e.g. VTY
- * and statd).
- *
- * Each value stored in the FIFO of an osmo_stat_item has an associated
- * value_id. The value_id is derived from an application-wide globally
- * incrementing counter, so (until the counter wraps) more recent
- * values will have higher values.
- *
- * When a new value is set, the oldest value in the FIFO gets silently
- * overwritten. Lost values are skipped when getting values from the
- * item.
- *
- */
-
-#include <stdint.h>
-#include <string.h>
-
-#include <osmocom/core/utils.h>
-#include <osmocom/core/linuxlist.h>
-#include <osmocom/core/talloc.h>
-#include <osmocom/core/timer.h>
-#include <osmocom/core/stat_item.h>
-
-/*! global list of stat_item groups */
-static LLIST_HEAD(osmo_stat_item_groups);
-/*! counter for assigning globally unique value identifiers */
-static int32_t global_value_id = 0;
-
-/*! talloc context from which we allocate */
-static void *tall_stat_item_ctx;
-
-/*! Allocate a new group of counters according to description.
- * Allocate a group of stat items described in \a desc from talloc context \a ctx,
- * giving the new group the index \a idx.
- * \param[in] ctx \ref talloc context
- * \param[in] desc Statistics item group description
- * \param[in] idx Index of new stat item group
- */
-struct osmo_stat_item_group *osmo_stat_item_group_alloc(void *ctx,
- const struct osmo_stat_item_group_desc *desc,
- unsigned int idx)
-{
- unsigned int group_size;
- unsigned long items_size = 0;
- unsigned int item_idx;
- void *items;
-
- struct osmo_stat_item_group *group;
-
- group_size = sizeof(struct osmo_stat_item_group) +
- desc->num_items * sizeof(struct osmo_stat_item *);
-
- if (!ctx)
- ctx = tall_stat_item_ctx;
-
- group = talloc_zero_size(ctx, group_size);
- if (!group)
- return NULL;
-
- group->desc = desc;
- group->idx = idx;
-
- /* Get combined size of all items */
- for (item_idx = 0; item_idx < desc->num_items; item_idx++) {
- unsigned int size;
- size = sizeof(struct osmo_stat_item) +
- sizeof(struct osmo_stat_item_value) *
- desc->item_desc[item_idx].num_values;
- /* Align to pointer size */
- size = (size + sizeof(void *) - 1) & ~(sizeof(void *) - 1);
-
- /* Store offsets into the item array */
- group->items[item_idx] = (void *)items_size;
-
- items_size += size;
- }
-
- items = talloc_zero_size(group, items_size);
- if (!items) {
- talloc_free(group);
- return NULL;
- }
-
- /* Update item pointers */
- for (item_idx = 0; item_idx < desc->num_items; item_idx++) {
- struct osmo_stat_item *item = (struct osmo_stat_item *)
- ((uint8_t *)items + (unsigned long)group->items[item_idx]);
- unsigned int i;
-
- group->items[item_idx] = item;
- item->last_offs = desc->item_desc[item_idx].num_values - 1;
- item->last_value_index = -1;
- item->desc = &desc->item_desc[item_idx];
-
- for (i = 0; i <= item->last_offs; i++) {
- item->values[i].value = desc->item_desc[item_idx].default_value;
- item->values[i].id = OSMO_STAT_ITEM_NOVALUE_ID;
- }
- }
-
- llist_add(&group->list, &osmo_stat_item_groups);
-
- return group;
-}
-
-/*! Free the memory for the specified group of stat items */
-void osmo_stat_item_group_free(struct osmo_stat_item_group *grp)
-{
- llist_del(&grp->list);
- talloc_free(grp);
-}
-
-/*! Increase the stat_item to the given value.
- * This function adds a new value for the given stat_item at the end of
- * the FIFO.
- * \param[in] item The stat_item whose \a value we want to set
- * \param[in] value The numeric value we want to store at end of FIFO
- */
-void osmo_stat_item_inc(struct osmo_stat_item *item, int32_t value)
-{
- int32_t oldvalue = item->values[item->last_offs].value;
- osmo_stat_item_set(item, oldvalue + value);
-}
-
-/*! Descrease the stat_item to the given value.
- * This function adds a new value for the given stat_item at the end of
- * the FIFO.
- * \param[in] item The stat_item whose \a value we want to set
- * \param[in] value The numeric value we want to store at end of FIFO
- */
-void osmo_stat_item_dec(struct osmo_stat_item *item, int32_t value)
-{
- int32_t oldvalue = item->values[item->last_offs].value;
- osmo_stat_item_set(item, oldvalue - value);
-}
-
-/*! Set the a given stat_item to the given value.
- * This function adds a new value for the given stat_item at the end of
- * the FIFO.
- * \param[in] item The stat_item whose \a value we want to set
- * \param[in] value The numeric value we want to store at end of FIFO
- */
-void osmo_stat_item_set(struct osmo_stat_item *item, int32_t value)
-{
- item->last_offs += 1;
- if (item->last_offs >= item->desc->num_values)
- item->last_offs = 0;
-
- global_value_id += 1;
- if (global_value_id == OSMO_STAT_ITEM_NOVALUE_ID)
- global_value_id += 1;
-
- item->values[item->last_offs].value = value;
- item->values[item->last_offs].id = global_value_id;
-}
-
-/*! Retrieve the next value from the osmo_stat_item object.
- * If a new value has been set, it is returned. The idx is used to decide
- * which value to return.
- * On success, *idx is updated to refer to the next unread value. If
- * values have been missed due to FIFO overflow, *idx is incremented by
- * (1 + num_lost).
- * This way, the osmo_stat_item object can be kept stateless from the reader's
- * perspective and therefore be used by several backends simultaneously.
- *
- * \param val the osmo_stat_item object
- * \param idx identifies the next value to be read
- * \param value a pointer to store the value
- * \returns the increment of the index (0: no value has been read,
- * 1: one value has been taken,
- * (1+n): n values have been skipped, one has been taken)
- */
-int osmo_stat_item_get_next(const struct osmo_stat_item *item, int32_t *next_idx,
- int32_t *value)
-{
- const struct osmo_stat_item_value *next_value;
- const struct osmo_stat_item_value *item_value = NULL;
- int idx_delta;
- int next_offs;
-
- next_offs = item->last_offs;
- next_value = &item->values[next_offs];
-
- while (next_value->id - *next_idx >= 0 &&
- next_value->id != OSMO_STAT_ITEM_NOVALUE_ID)
- {
- item_value = next_value;
-
- next_offs -= 1;
- if (next_offs < 0)
- next_offs = item->desc->num_values - 1;
- if (next_offs == item->last_offs)
- break;
- next_value = &item->values[next_offs];
- }
-
- if (!item_value)
- /* All items have been read */
- return 0;
-
- *value = item_value->value;
-
- idx_delta = item_value->id + 1 - *next_idx;
-
- *next_idx = item_value->id + 1;
-
- return idx_delta;
-}
-
-/*! Skip/discard all values of this item and update \a idx accordingly */
-int osmo_stat_item_discard(const struct osmo_stat_item *item, int32_t *idx)
-{
- int discarded = item->values[item->last_offs].id + 1 - *idx;
- *idx = item->values[item->last_offs].id + 1;
-
- return discarded;
-}
-
-/*! Skip all values of all items and update \a idx accordingly */
-int osmo_stat_item_discard_all(int32_t *idx)
-{
- int discarded = global_value_id + 1 - *idx;
- *idx = global_value_id + 1;
-
- return discarded;
-}
-
-/*! Initialize the stat item module. Call this once from your program.
- * \param[in] tall_ctx Talloc context from which this module allocates */
-int osmo_stat_item_init(void *tall_ctx)
-{
- tall_stat_item_ctx = tall_ctx;
-
- return 0;
-}
-
-/*! Search for item group based on group name and index
- * \param[in] name Name of stats_item_group we want to find
- * \param[in] idx Index of the group we want to find
- * \returns pointer to group, if found; NULL otherwise */
-struct osmo_stat_item_group *osmo_stat_item_get_group_by_name_idx(
- const char *name, const unsigned int idx)
-{
- struct osmo_stat_item_group *statg;
-
- llist_for_each_entry(statg, &osmo_stat_item_groups, list) {
- if (!statg->desc)
- continue;
-
- if (!strcmp(statg->desc->group_name_prefix, name) &&
- statg->idx == idx)
- return statg;
- }
- return NULL;
-}
-
-/*! Search for item based on group + item name
- * \param[in] statg group in which to search for the item
- * \param[in] name name of item to search within \a statg
- * \returns pointer to item, if found; NULL otherwise */
-const struct osmo_stat_item *osmo_stat_item_get_by_name(
- const struct osmo_stat_item_group *statg, const char *name)
-{
- int i;
- const struct osmo_stat_item_desc *item_desc;
-
- if (!statg->desc)
- return NULL;
-
- for (i = 0; i < statg->desc->num_items; i++) {
- item_desc = &statg->desc->item_desc[i];
-
- if (!strcmp(item_desc->name, name)) {
- return statg->items[i];
- }
- }
- return NULL;
-}
-
-/*! Iterate over all items in group, call user-supplied function on each
- * \param[in] statg stat_item group over whose items to iterate
- * \param[in] handle_item Call-back function, aborts if rc < 0
- * \param[in] data Private data handed through to \a handle_item
- */
-int osmo_stat_item_for_each_item(struct osmo_stat_item_group *statg,
- osmo_stat_item_handler_t handle_item, void *data)
-{
- int rc = 0;
- int i;
-
- for (i = 0; i < statg->desc->num_items; i++) {
- struct osmo_stat_item *item = statg->items[i];
- rc = handle_item(statg, item, data);
- if (rc < 0)
- return rc;
- }
-
- return rc;
-}
-
-/*! Iterate over all stat_item groups in system, call user-supplied function on each
- * \param[in] handle_group Call-back function, aborts if rc < 0
- * \param[in] data Private data handed through to \a handle_group
- */
-int osmo_stat_item_for_each_group(osmo_stat_item_group_handler_t handle_group, void *data)
-{
- struct osmo_stat_item_group *statg;
- int rc = 0;
-
- llist_for_each_entry(statg, &osmo_stat_item_groups, list) {
- rc = handle_group(statg, data);
- if (rc < 0)
- return rc;
- }
-
- return rc;
-}
-
-
-/*! Remove all values of a stat item
- * \param[in] item stat item to reset
- */
-void osmo_stat_item_reset(struct osmo_stat_item *item)
-{
- unsigned int i;
-
- item->last_offs = item->desc->num_values - 1;
- item->last_value_index = -1;
-
- for (i = 0; i <= item->last_offs; i++) {
- item->values[i].value = item->desc->default_value;
- item->values[i].id = OSMO_STAT_ITEM_NOVALUE_ID;
- }
-}
-
-/*! Reset all osmo stat items in a group
- * \param[in] statg stat item group to reset
- */
-void osmo_stat_item_group_reset(struct osmo_stat_item_group *statg)
-{
- int i;
-
- for (i = 0; i < statg->desc->num_items; i++) {
- struct osmo_stat_item *item = statg->items[i];
- osmo_stat_item_reset(item);
- }
-}
-/*! @} */
diff --git a/src/usb/Makefile.am b/src/usb/Makefile.am
index 2dee434b..c7d7a2a2 100644
--- a/src/usb/Makefile.am
+++ b/src/usb/Makefile.am
@@ -1,9 +1,9 @@
# 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 modifications: https://www.gnu.org/software/libtool/manual/html_node/Versioning.html
-LIBVERSION=0:0:0
+LIBVERSION=0:1:0
-AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)/include
+AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_builddir)/include -I$(top_builddir)
AM_CFLAGS = -fPIC -Wall $(LIBUSB_CFLAGS) $(TALLOC_CFLAGS)
AM_LDFLAGS = $(COVERAGE_LDFLAGS)
@@ -12,9 +12,12 @@ if ENABLE_LIBUSB
lib_LTLIBRARIES = libosmousb.la
libosmousb_la_SOURCES = osmo_libusb.c
-libosmousb_la_LDFLAGS = -version-info $(LIBVERSION)
+libosmousb_la_LDFLAGS = \
+ -version-info $(LIBVERSION) \
+ -no-undefined \
+ $(NULL)
libosmousb_la_LIBADD = \
- $(top_builddir)/src/libosmocore.la \
+ $(top_builddir)/src/core/libosmocore.la \
$(TALLOC_LIBS) \
$(LIBUSB_LIBS)
diff --git a/src/usb/osmo_libusb.c b/src/usb/osmo_libusb.c
index bb862067..a249d100 100644
--- a/src/usb/osmo_libusb.c
+++ b/src/usb/osmo_libusb.c
@@ -14,10 +14,6 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <errno.h>
#include <unistd.h>
@@ -103,7 +99,7 @@ static void osmo_usb_added_cb(int fd, short events, void *user_data)
static void osmo_usb_removed_cb(int fd, void *user_data)
{
struct osmo_fd *ofd = osmo_fd_get_by_fd(fd);
- if (!fd)
+ if (!ofd)
return;
osmo_fd_unregister(ofd);
talloc_free(ofd);
@@ -541,7 +537,8 @@ libusb_device_handle *osmo_libusb_open_claim_interface(void *ctx, libusb_context
addr = libusb_get_device_address(*dev);
path = osmo_libusb_dev_get_path_buf(pathbuf, sizeof(pathbuf), *dev);
if ((ifm->addr && addr == ifm->addr) ||
- (strlen(ifm->path) && !strcmp(path, ifm->path))) {
+ (strlen(ifm->path) && !strcmp(path, ifm->path)) ||
+ (!ifm->addr && !strlen(ifm->path) && !list[1] /* only one device */)) {
rc = libusb_open(*dev, &usb_devh);
if (rc < 0) {
fprintf(stderr, "Cannot open device: %s\n", libusb_error_name(rc));
@@ -739,11 +736,15 @@ int osmo_libusb_get_ep_addrs(libusb_device_handle *devh, unsigned int if_num,
int osmo_libusb_init(libusb_context **pluctx)
{
libusb_context *luctx = NULL;
+ const struct libusb_pollfd **pfds;
+
int rc;
rc = libusb_init(pluctx);
- if (rc != 0)
+ if (rc != 0) {
+ LOGP(DLUSB, LOGL_ERROR, "Error initializing libusb: %s\n", libusb_strerror(rc));
return rc;
+ }
if (pluctx)
luctx = *pluctx;
@@ -754,6 +755,17 @@ int osmo_libusb_init(libusb_context **pluctx)
libusb_set_pollfd_notifiers(luctx, osmo_usb_added_cb, osmo_usb_removed_cb, luctx);
+ /* get the initial file descriptors which were created even before during libusb_init() */
+ pfds = libusb_get_pollfds(luctx);
+ if (pfds) {
+ const struct libusb_pollfd **pfds2 = pfds;
+ const struct libusb_pollfd *pfd;
+ /* synthesize 'add' call-backs. not sure why libusb doesn't do that by itself? */
+ for (pfd = *pfds2; pfd; pfd = *++pfds2)
+ osmo_usb_added_cb(pfd->fd, pfd->events, luctx);
+ libusb_free_pollfds(pfds);
+ }
+
return 0;
}
diff --git a/src/vty/Makefile.am b/src/vty/Makefile.am
index 81ff1045..c314a141 100644
--- a/src/vty/Makefile.am
+++ b/src/vty/Makefile.am
@@ -1,10 +1,10 @@
# 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 modifications: https://www.gnu.org/software/libtool/manual/html_node/Versioning.html
-LIBVERSION=8:1:4
+LIBVERSION=13:0:0
-AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_builddir)/include
-AM_CFLAGS = -Wall $(TALLOC_CFLAGS)
+AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_builddir)/include -I$(top_builddir)
+AM_CFLAGS = -Wall $(TALLOC_CFLAGS) $(PTHREAD_CFLAGS)
if ENABLE_VTY
lib_LTLIBRARIES = libosmovty.la
@@ -14,5 +14,5 @@ libosmovty_la_SOURCES = buffer.c command.c vty.c vector.c utils.c \
fsm_vty.c talloc_ctx_vty.c \
cpu_sched_vty.c tdef_vty.c
libosmovty_la_LDFLAGS = -version-info $(LIBVERSION) -no-undefined
-libosmovty_la_LIBADD = $(top_builddir)/src/libosmocore.la $(TALLOC_LIBS)
+libosmovty_la_LIBADD = $(top_builddir)/src/core/libosmocore.la $(TALLOC_LIBS) $(PTHREAD_LIBS)
endif
diff --git a/src/vty/command.c b/src/vty/command.c
index b38be34e..a3e0e36b 100644
--- a/src/vty/command.c
+++ b/src/vty/command.c
@@ -38,14 +38,18 @@ Boston, MA 02110-1301, USA. */
#include <ctype.h>
#include <time.h>
#include <limits.h>
+#include <signal.h>
#include <sys/time.h>
#include <sys/stat.h>
+#include <sys/types.h>
#include <osmocom/vty/vector.h>
#include <osmocom/vty/vty.h>
#include <osmocom/vty/command.h>
+#include <osmocom/core/logging.h>
#include <osmocom/core/talloc.h>
+#include <osmocom/core/timer.h>
#include <osmocom/core/utils.h>
#ifndef MAXPATHLEN
@@ -63,6 +67,9 @@ Boston, MA 02110-1301, USA. */
void *tall_vty_cmd_ctx;
+/* Set by on_dso_load_starttime() for "show uptime". */
+static struct timespec starttime;
+
/* Command vector which includes some level of command lists. Normally
each daemon maintains each own cmdvec. */
vector cmdvec;
@@ -70,6 +77,21 @@ vector cmdvec;
/* Host information structure. */
struct host host;
+struct vty_parent_node {
+ struct llist_head entry;
+
+ /*! private data, specified by creator */
+ void *priv;
+ void *index;
+
+ /*! Node status of this vty */
+ int node;
+
+ /*! When reading from a config file, these are the indenting characters expected for children of
+ * this VTY node. */
+ char *indent;
+};
+
/* Standard command node structures. */
struct cmd_node auth_node = {
AUTH_NODE,
@@ -727,7 +749,7 @@ static int vty_dump_element(const struct cmd_element *cmd, print_func_t print_fu
char flag;
/* Skip attribute if *not* set */
- if (~cmd->usrattr & (1 << i))
+ if (~cmd->usrattr & ((unsigned)1 << i))
continue;
xml_att_desc = xml_escape(desc[i]);
@@ -1085,6 +1107,7 @@ static int config_write_host(struct vty *vty)
static vector cmd_node_vector(vector v, enum node_type ntype)
{
struct cmd_node *cnode = vector_slot(v, ntype);
+ OSMO_ASSERT(cnode != NULL);
return cnode->cmd_vector;
}
@@ -1342,7 +1365,6 @@ static enum match_type cmd_ipv6_prefix_match(const char *str)
int colons = 0, nums = 0, double_colon = 0;
int mask;
const char *sp = NULL;
- char *endptr = NULL;
if (str == NULL)
return PARTLY_MATCH;
@@ -1440,11 +1462,7 @@ static enum match_type cmd_ipv6_prefix_match(const char *str)
if (state < STATE_MASK)
return PARTLY_MATCH;
- mask = strtol(str, &endptr, 10);
- if (*endptr != '\0')
- return NO_MATCH;
-
- if (mask < 0 || mask > 128)
+ if (osmo_str_to_int(&mask, str, 10, 0, 128))
return NO_MATCH;
/* I don't know why mask < 13 makes command match partly.
@@ -1477,19 +1495,52 @@ static enum match_type cmd_ipv6_prefix_match(const char *str)
#error "LONG_MAX not defined!"
#endif
-static int cmd_range_match(const char *range, const char *str)
+/* This function is aimed at quickly guessing & filtering the numeric base a
+ * string can contain, by no means validates the entire value.
+ * Returns 16 if string follows pattern "({+,-}0x[digits])"
+ * Returns 10 if string follows pattern "({+,-}[digits])"
+ * Returns a negative value if something other is detected (error)
+*/
+static int check_base(const char *str)
+{
+ const char *ptr = str;
+ /* Skip any space */
+ while (isspace(*ptr)) ptr++;
+
+ /* Skip optional sign: */
+ if (*ptr == '+' || *ptr == '-')
+ ptr++;
+ if (*ptr == '0') {
+ ptr++;
+ if (*ptr == '\0' || isdigit(*ptr))
+ return 10;
+ if (!(*ptr == 'x' || *ptr == 'X'))
+ return -1;
+ ptr++;
+ if (isxdigit(*ptr))
+ return 16;
+ return -1;
+ }
+ return 10;
+}
+
+int vty_cmd_range_match(const char *range, const char *str)
{
char *p;
char buf[DECIMAL_STRLEN_MAX_UNSIGNED + 1];
char *endptr = NULL;
+ int min_base, max_base, val_base;
if (str == NULL)
return 1;
+ if ((val_base = check_base(str)) < 0)
+ return 0;
+
if (range[1] == '-') {
signed long min = 0, max = 0, val;
- val = strtol(str, &endptr, 10);
+ val = strtol(str, &endptr, val_base);
if (*endptr != '\0')
return 0;
@@ -1501,7 +1552,9 @@ static int cmd_range_match(const char *range, const char *str)
return 0;
strncpy(buf, range, p - range);
buf[p - range] = '\0';
- min = -strtol(buf, &endptr, 10);
+ if ((min_base = check_base(buf)) < 0)
+ return 0;
+ min = -strtol(buf, &endptr, min_base);
if (*endptr != '\0')
return 0;
@@ -1513,7 +1566,9 @@ static int cmd_range_match(const char *range, const char *str)
return 0;
strncpy(buf, range, p - range);
buf[p - range] = '\0';
- max = strtol(buf, &endptr, 10);
+ if ((max_base = check_base(buf)) < 0)
+ return 0;
+ max = strtol(buf, &endptr, max_base);
if (*endptr != '\0')
return 0;
@@ -1525,7 +1580,7 @@ static int cmd_range_match(const char *range, const char *str)
if (str[0] == '-')
return 0;
- val = strtoul(str, &endptr, 10);
+ val = strtoul(str, &endptr, val_base);
if (*endptr != '\0')
return 0;
@@ -1537,7 +1592,9 @@ static int cmd_range_match(const char *range, const char *str)
return 0;
strncpy(buf, range, p - range);
buf[p - range] = '\0';
- min = strtoul(buf, &endptr, 10);
+ if ((min_base = check_base(buf)) < 0)
+ return 0;
+ min = strtoul(buf, &endptr, min_base);
if (*endptr != '\0')
return 0;
@@ -1549,7 +1606,9 @@ static int cmd_range_match(const char *range, const char *str)
return 0;
strncpy(buf, range, p - range);
buf[p - range] = '\0';
- max = strtoul(buf, &endptr, 10);
+ if ((max_base = check_base(buf)) < 0)
+ return 0;
+ max = strtoul(buf, &endptr, max_base);
if (*endptr != '\0')
return 0;
@@ -1557,6 +1616,14 @@ static int cmd_range_match(const char *range, const char *str)
return 0;
}
+ /* Don't allow ranges specified by min and max with different bases */
+ if (min_base != max_base)
+ return 0;
+ /* arg value passed must match the base of the range */
+ if (min_base != val_base)
+ return 0;
+
+ /* Everything's fine */
return 1;
}
@@ -1600,7 +1667,7 @@ cmd_match(const char *str, const char *command,
return VARARG_MATCH;
else if (CMD_RANGE(str))
{
- if (cmd_range_match(str, command))
+ if (vty_cmd_range_match(str, command))
return RANGE_MATCH;
}
#ifdef HAVE_IPV6
@@ -1797,7 +1864,7 @@ is_cmd_ambiguous(char *command, vector v, int index, enum match_type type)
}
break;
case RANGE_MATCH:
- if (cmd_range_match
+ if (vty_cmd_range_match
(str, command)) {
if (matched
&& strcmp(matched,
@@ -1890,7 +1957,7 @@ static const char *cmd_entry_function_desc(const char *src, const char *dst)
return dst;
if (CMD_RANGE(dst)) {
- if (cmd_range_match(dst, src))
+ if (vty_cmd_range_match(dst, src))
return dst;
else
return NULL;
@@ -2386,6 +2453,7 @@ static bool vty_pop_parent(struct vty *vty)
llist_del(&parent->entry);
vty->node = parent->node;
vty->priv = parent->priv;
+ vty->index = parent->index;
if (vty->indent)
talloc_free(vty->indent);
vty->indent = parent->indent;
@@ -2596,6 +2664,7 @@ cmd_execute_command_real(vector vline, struct vty *vty,
struct vty_parent_node this_node = {
.node = vty->node,
.priv = vty->priv,
+ .index = vty->index,
.indent = vty->indent,
};
struct vty_parent_node *parent = vty_parent(vty);
@@ -2871,6 +2940,7 @@ int config_from_file(struct vty *vty, FILE * fp)
this_node = (struct vty_parent_node){
.node = vty->node,
.priv = vty->priv,
+ .index = vty->index,
.indent = vty->indent,
};
@@ -2927,7 +2997,7 @@ return_invalid_indent:
/* Configration from terminal */
DEFUN(config_terminal,
config_terminal_cmd,
- "configure terminal",
+ "configure [terminal]",
"Configuration from vty interface\n" "Configuration terminal\n")
{
if (vty_config_lock(vty))
@@ -3018,6 +3088,18 @@ gDEFUN(config_exit,
return CMD_SUCCESS;
}
+DEFUN(shutdown,
+ shutdown_cmd, "shutdown", "Request a shutdown of the program\n")
+{
+ LOGP(DLGLOBAL, LOGL_INFO, "Shutdown requested from telnet\n");
+ vty_out(vty, "%s is shutting down. Bye!%s", host.app_info->name, VTY_NEWLINE);
+
+ /* Same exit path as if it was killed by the service manager */
+ kill(getpid(), SIGTERM);
+
+ return CMD_SUCCESS;
+}
+
/* Show version. */
DEFUN(show_version,
show_version_cmd, "show version", SHOW_STR "Displays program version\n")
@@ -3037,6 +3119,23 @@ DEFUN(show_online_help,
return CMD_SUCCESS;
}
+DEFUN(show_pid,
+ show_pid_cmd, "show pid", SHOW_STR "Displays the process ID\n")
+{
+ vty_out(vty, "%d%s", getpid(), VTY_NEWLINE);
+ return CMD_SUCCESS;
+}
+
+DEFUN(show_uptime,
+ show_uptime_cmd, "show uptime", SHOW_STR "Displays how long the program has been running\n")
+{
+ vty_out(vty, "%s has been running for ", host.app_info->name);
+ vty_out_uptime(vty, &starttime);
+ vty_out_newline(vty);
+
+ return CMD_SUCCESS;
+}
+
/* Help display function for all node. */
gDEFUN(config_help,
config_help_cmd, "help", "Description of the interactive help system\n")
@@ -3758,16 +3857,7 @@ DEFUN(config_terminal_length, config_terminal_length_cmd,
"Set number of lines on a screen\n"
"Number of lines on screen (0 for no pausing)\n")
{
- int lines;
- char *endptr = NULL;
-
- lines = strtol(argv[0], &endptr, 10);
- if (lines < 0 || lines > 512 || *endptr != '\0') {
- vty_out(vty, "length is malformed%s", VTY_NEWLINE);
- return CMD_WARNING;
- }
- vty->lines = lines;
-
+ vty->lines = atoi(argv[0]);
return CMD_SUCCESS;
}
@@ -3786,16 +3876,7 @@ DEFUN(service_terminal_length, service_terminal_length_cmd,
"System wide terminal length configuration\n"
"Number of lines of VTY (0 means no line control)\n")
{
- int lines;
- char *endptr = NULL;
-
- lines = strtol(argv[0], &endptr, 10);
- if (lines < 0 || lines > 512 || *endptr != '\0') {
- vty_out(vty, "length is malformed%s", VTY_NEWLINE);
- return CMD_WARNING;
- }
- host.lines = lines;
-
+ host.lines = atoi(argv[0]);
return CMD_SUCCESS;
}
@@ -4219,6 +4300,11 @@ void host_config_set(const char *filename)
host.config = talloc_strdup(tall_vty_cmd_ctx, filename);
}
+const char *host_config_file(void)
+{
+ return host.config;
+}
+
/*! Deprecated, now happens implicitly when calling install_node().
* Users of the API may still attempt to call this function, hence
* leave it here as a no-op. */
@@ -4341,6 +4427,8 @@ void cmd_init(int terminal)
install_node(&config_node, config_write_host);
/* Each node's basic commands. */
+ install_lib_element(VIEW_NODE, &show_pid_cmd);
+ install_lib_element(VIEW_NODE, &show_uptime_cmd);
install_lib_element(VIEW_NODE, &show_version_cmd);
install_lib_element(VIEW_NODE, &show_online_help_cmd);
if (terminal) {
@@ -4359,6 +4447,7 @@ void cmd_init(int terminal)
install_lib_element(ENABLE_NODE, &config_disable_cmd);
install_lib_element(ENABLE_NODE, &config_terminal_cmd);
install_lib_element(ENABLE_NODE, &copy_runningconfig_startupconfig_cmd);
+ install_lib_element(ENABLE_NODE, &shutdown_cmd);
}
install_lib_element(ENABLE_NODE, &show_startup_config_cmd);
install_lib_element(ENABLE_NODE, &show_version_cmd);
@@ -4394,6 +4483,11 @@ void cmd_init(int terminal)
srand(time(NULL));
}
+static __attribute__((constructor)) void on_dso_load_starttime(void)
+{
+ osmo_clock_gettime(CLOCK_MONOTONIC, &starttime);
+}
+
/* FIXME: execute this section in the unit test instead */
static __attribute__((constructor)) void on_dso_load(void)
{
diff --git a/src/vty/cpu_sched_vty.c b/src/vty/cpu_sched_vty.c
index 4ccc6274..7198f747 100644
--- a/src/vty/cpu_sched_vty.c
+++ b/src/vty/cpu_sched_vty.c
@@ -25,6 +25,8 @@
#define _GNU_SOURCE
+#include "config.h"
+
#include <string.h>
#include <stdlib.h>
#include <errno.h>
@@ -87,7 +89,7 @@ static struct cmd_node sched_node = {
};
/* returns number of configured CPUs in the system, or negative otherwise */
-static int get_num_cpus() {
+static int get_num_cpus(void) {
static unsigned int num_cpus = 0;
long ln;
@@ -276,7 +278,6 @@ static bool proc_name_exists(const char *name, pid_t *res_pid)
static enum sched_vty_thread_id procname2pid(pid_t *res_pid, const char *str, bool applynow)
{
size_t i, len;
- char *end;
bool is_pid = true;
if (strcmp(str, "all") == 0) {
@@ -297,12 +298,12 @@ static enum sched_vty_thread_id procname2pid(pid_t *res_pid, const char *str, bo
}
}
if (is_pid) {
- errno = 0;
- *res_pid = strtoul(str, &end, 0);
- if ((errno == ERANGE && *res_pid == ULONG_MAX) || (errno && !*res_pid) ||
- str == end) {
+ int64_t val;
+ if (osmo_str_to_int64(&val, str, 0, 0, INT64_MAX))
+ return SCHED_VTY_THREAD_UNKNOWN;
+ *res_pid = (pid_t)val;
+ if (*res_pid != val)
return SCHED_VTY_THREAD_UNKNOWN;
- }
if (!applynow || proc_tid_exists(*res_pid))
return SCHED_VTY_THREAD_ID;
else
@@ -638,8 +639,10 @@ int osmo_cpu_sched_vty_apply_localthread(void)
return 0;
}
+#ifdef HAVE_PTHREAD_GETNAME_NP
if (pthread_getname_np(pthread_self(), name, sizeof(name)) == 0)
has_name = true;
+#endif
/* Get latest matching mask for the thread */
pthread_mutex_lock(&sched_vty_opts->cpu_affinity_li_mutex);
diff --git a/src/vty/fsm_vty.c b/src/vty/fsm_vty.c
index 3169ee77..da6038fa 100644
--- a/src/vty/fsm_vty.c
+++ b/src/vty/fsm_vty.c
@@ -14,16 +14,12 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <stdlib.h>
#include <string.h>
-#include "../../config.h"
+#include "config.h"
#include <osmocom/vty/command.h>
#include <osmocom/vty/buffer.h>
@@ -51,62 +47,82 @@ extern struct llist_head osmo_g_fsms;
/*! Print information about a FSM [class] to the given VTY
* \param vty The VTY to which to print
+ * \param[in] prefix prefix to print at start of each line (typically indenting)
* \param[in] fsm The FSM class to print
*/
-void vty_out_fsm(struct vty *vty, struct osmo_fsm *fsm)
+void vty_out_fsm2(struct vty *vty, const char *prefix, struct osmo_fsm *fsm)
{
unsigned int i;
const struct value_string *evt_name;
- vty_out(vty, "FSM Name: '%s', Log Subsys: '%s'%s", fsm->name,
+ vty_out(vty, "%sFSM Name: '%s', Log Subsys: '%s'%s", prefix, fsm->name,
log_category_name(fsm->log_subsys), VTY_NEWLINE);
/* list the events */
if (fsm->event_names) {
for (evt_name = fsm->event_names; evt_name->str != NULL; evt_name++) {
- vty_out(vty, " Event %02u (0x%08x): '%s'%s", evt_name->value,
- (1 << evt_name->value), evt_name->str, VTY_NEWLINE);
+ vty_out(vty, "%s Event %02u (0x%08x): '%s'%s", prefix, evt_name->value,
+ (1U << evt_name->value), evt_name->str, VTY_NEWLINE);
}
} else
- vty_out(vty, " No event names are defined for this FSM! Please fix!%s", VTY_NEWLINE);
+ vty_out(vty, "%s No event names are defined for this FSM! Please fix!%s", prefix, VTY_NEWLINE);
/* list the states */
- vty_out(vty, " Number of States: %u%s", fsm->num_states, VTY_NEWLINE);
+ vty_out(vty, "%s Number of States: %u%s", prefix, fsm->num_states, VTY_NEWLINE);
for (i = 0; i < fsm->num_states; i++) {
const struct osmo_fsm_state *state = &fsm->states[i];
- vty_out(vty, " State %-20s InEvtMask: 0x%08x, OutStateMask: 0x%08x%s",
+ vty_out(vty, "%s State %-20s InEvtMask: 0x%08x, OutStateMask: 0x%08x%s", prefix,
state->name, state->in_event_mask, state->out_state_mask,
VTY_NEWLINE);
}
}
+/*! Print information about a FSM [class] to the given VTY
+ * \param vty The VTY to which to print
+ * \param[in] fsm The FSM class to print
+ */
+void vty_out_fsm(struct vty *vty, struct osmo_fsm *fsm)
+{
+ vty_out_fsm2(vty, "", fsm);
+}
+
/*! Print a FSM instance to the given VTY
* \param vty The VTY to which to print
+ * \param[in] prefix prefix to print at start of each line (typically indenting)
* \param[in] fsmi The FSM instance to print
*/
-void vty_out_fsm_inst(struct vty *vty, struct osmo_fsm_inst *fsmi)
+void vty_out_fsm_inst2(struct vty *vty, const char *prefix, struct osmo_fsm_inst *fsmi)
{
struct osmo_fsm_inst *child;
- vty_out(vty, "FSM Instance Name: '%s', ID: '%s'%s",
+ vty_out(vty, "%sFSM Instance Name: '%s', ID: '%s'%s", prefix,
fsmi->name, fsmi->id, VTY_NEWLINE);
- vty_out(vty, " Log-Level: '%s', State: '%s'%s",
+ vty_out(vty, "%s Log-Level: '%s', State: '%s'%s", prefix,
log_level_str(fsmi->log_level),
osmo_fsm_state_name(fsmi->fsm, fsmi->state),
VTY_NEWLINE);
if (fsmi->T)
- vty_out(vty, " Timer: %u%s", fsmi->T, VTY_NEWLINE);
+ vty_out(vty, "%s Timer: %u%s", prefix, fsmi->T, VTY_NEWLINE);
if (fsmi->proc.parent) {
- vty_out(vty, " Parent: '%s', Term-Event: '%s'%s",
+ vty_out(vty, "%s Parent: '%s', Term-Event: '%s'%s", prefix,
fsmi->proc.parent->name,
osmo_fsm_event_name(fsmi->proc.parent->fsm,
fsmi->proc.parent_term_event),
VTY_NEWLINE);
}
llist_for_each_entry(child, &fsmi->proc.children, proc.child) {
- vty_out(vty, " Child: '%s'%s", child->name, VTY_NEWLINE);
+ vty_out(vty, "%s Child: '%s'%s", prefix, child->name, VTY_NEWLINE);
}
}
+/*! Print a FSM instance to the given VTY
+ * \param vty The VTY to which to print
+ * \param[in] fsmi The FSM instance to print
+ */
+void vty_out_fsm_inst(struct vty *vty, struct osmo_fsm_inst *fsmi)
+{
+ vty_out_fsm_inst2(vty, "", fsmi);
+}
+
#define SH_FSM_STR SHOW_STR "Show information about finite state machines\n"
#define SH_FSMI_STR SHOW_STR "Show information about finite state machine instances\n"
diff --git a/src/vty/logging_vty.c b/src/vty/logging_vty.c
index 02823504..678ae686 100644
--- a/src/vty/logging_vty.c
+++ b/src/vty/logging_vty.c
@@ -15,16 +15,12 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <stdlib.h>
#include <string.h>
-#include "../../config.h"
+#include "config.h"
#include <osmocom/core/talloc.h>
#include <osmocom/core/logging.h>
@@ -131,7 +127,7 @@ DEFUN(enable_logging,
conn = (struct telnet_connection *) vty->priv;
if (conn->dbg) {
- vty_out(vty, "Logging already enabled.%s", VTY_NEWLINE);
+ vty_out(vty, "%% Logging already enabled.%s", VTY_NEWLINE);
return CMD_WARNING;
}
@@ -158,7 +154,7 @@ struct log_target *osmo_log_vty2tgt(struct vty *vty)
conn = (struct telnet_connection *) vty->priv;
if (!conn->dbg)
- vty_out(vty, "Logging was not enabled.%s", VTY_NEWLINE);
+ vty_out(vty, "%% Logging was not enabled.%s", VTY_NEWLINE);
return conn->dbg;
}
@@ -225,6 +221,22 @@ DEFUN(logging_prnt_ext_timestamp,
RET_WITH_UNLOCK(CMD_SUCCESS);
}
+DEFUN(logging_prnt_tid,
+ logging_prnt_tid_cmd,
+ "logging print thread-id (0|1)",
+ LOGGING_STR "Log output settings\n"
+ "Configure log message logging Thread ID\n"
+ "Don't prefix each log message\n"
+ "Prefix each log message with current Thread ID\n")
+{
+ struct log_target *tgt;
+
+ ACQUIRE_VTY_LOG_TGT_WITH_LOCK(vty, tgt);
+
+ log_set_print_tid(tgt, atoi(argv[0]));
+ RET_WITH_UNLOCK(CMD_SUCCESS);
+}
+
DEFUN(logging_prnt_cat,
logging_prnt_cat_cmd,
"logging print category (0|1)",
@@ -356,12 +368,12 @@ DEFUN(logging_level,
int level = log_parse_level(argv[1]);
if (level < 0) {
- vty_out(vty, "Invalid level `%s'%s", argv[1], VTY_NEWLINE);
+ vty_out(vty, "%% Invalid level '%s'%s", argv[1], VTY_NEWLINE);
return CMD_WARNING;
}
if (category < 0) {
- vty_out(vty, "Invalid category `%s'%s", argv[0], VTY_NEWLINE);
+ vty_out(vty, "%% Invalid category '%s'%s", argv[0], VTY_NEWLINE);
return CMD_WARNING;
}
@@ -370,6 +382,10 @@ DEFUN(logging_level,
tgt->categories[category].enabled = 1;
tgt->categories[category].loglevel = level;
+#if !defined(EMBEDDED)
+ log_cache_update(category, 1, level);
+#endif
+
RET_WITH_UNLOCK(CMD_SUCCESS);
}
@@ -394,6 +410,9 @@ DEFUN(logging_level_set_all, logging_level_set_all_cmd,
cat->enabled = 1;
cat->loglevel = level;
+#if !defined(EMBEDDED)
+ log_cache_update(i, 1, level);
+#endif
}
RET_WITH_UNLOCK(CMD_SUCCESS);
}
@@ -579,7 +598,7 @@ gDEFUN(cfg_description, cfg_description_cmd,
char **dptr = vty->index_sub;
if (!dptr) {
- vty_out(vty, "vty->index_sub == NULL%s", VTY_NEWLINE);
+ vty_out(vty, "%% vty->index_sub == NULL%s", VTY_NEWLINE);
return CMD_WARNING;
}
@@ -600,7 +619,7 @@ gDEFUN(cfg_no_description, cfg_no_description_cmd,
char **dptr = vty->index_sub;
if (!dptr) {
- vty_out(vty, "vty->index_sub == NULL%s", VTY_NEWLINE);
+ vty_out(vty, "%% vty->index_sub == NULL%s", VTY_NEWLINE);
return CMD_WARNING;
}
@@ -818,9 +837,31 @@ DEFUN(cfg_log_gsmtap, cfg_log_gsmtap_cmd,
RET_WITH_UNLOCK(CMD_SUCCESS);
}
+DEFUN(cfg_no_log_gsmtap, cfg_no_log_gsmtap_cmd,
+ "no log gsmtap [HOSTNAME]",
+ NO_STR LOG_STR "Logging via GSMTAP\n"
+ "Host name to send the GSMTAP logging to (UDP port 4729)\n")
+{
+ const char *hostname = argc ? argv[0] : "127.0.0.1";
+ struct log_target *tgt;
+
+ log_tgt_mutex_lock();
+ tgt = log_target_find(LOG_TGT_TYPE_GSMTAP, hostname);
+ if (tgt == NULL) {
+ vty_out(vty, "%% Unable to find GSMTAP log target for %s%s",
+ hostname, VTY_NEWLINE);
+ RET_WITH_UNLOCK(CMD_WARNING);
+ }
+
+ log_target_destroy(tgt);
+
+ RET_WITH_UNLOCK(CMD_SUCCESS);
+}
+
DEFUN(cfg_log_stderr, cfg_log_stderr_cmd,
- "log stderr",
- LOG_STR "Logging via STDERR of the process\n")
+ "log stderr [blocking-io]",
+ LOG_STR "Logging via STDERR of the process\n"
+ "Use blocking, synchronous I/O\n")
{
struct log_target *tgt;
@@ -836,6 +877,11 @@ DEFUN(cfg_log_stderr, cfg_log_stderr_cmd,
log_add_target(tgt);
}
+ if (argc > 0 && !strcmp(argv[0], "blocking-io"))
+ log_target_file_switch_to_stream(tgt);
+ else
+ log_target_file_switch_to_wqueue(tgt);
+
vty->index = tgt;
vty->node = CFG_LOG_NODE;
@@ -862,8 +908,9 @@ DEFUN(cfg_no_log_stderr, cfg_no_log_stderr_cmd,
}
DEFUN(cfg_log_file, cfg_log_file_cmd,
- "log file .FILENAME",
- LOG_STR "Logging to text file\n" "Filename\n")
+ "log file FILENAME [blocking-io]",
+ LOG_STR "Logging to text file\n" "Filename\n"
+ "Use blocking, synchronous I/O\n")
{
const char *fname = argv[0];
struct log_target *tgt;
@@ -873,13 +920,18 @@ DEFUN(cfg_log_file, cfg_log_file_cmd,
if (!tgt) {
tgt = log_target_create_file(fname);
if (!tgt) {
- vty_out(vty, "%% Unable to create file `%s'%s",
+ vty_out(vty, "%% Unable to create file '%s'%s",
fname, VTY_NEWLINE);
RET_WITH_UNLOCK(CMD_WARNING);
}
log_add_target(tgt);
}
+ if (argc > 1 && !strcmp(argv[1], "blocking-io"))
+ log_target_file_switch_to_stream(tgt);
+ else
+ log_target_file_switch_to_wqueue(tgt);
+
vty->index = tgt;
vty->node = CFG_LOG_NODE;
@@ -888,7 +940,7 @@ DEFUN(cfg_log_file, cfg_log_file_cmd,
DEFUN(cfg_no_log_file, cfg_no_log_file_cmd,
- "no log file .FILENAME",
+ "no log file FILENAME",
NO_STR LOG_STR "Logging to text file\n" "Filename\n")
{
const char *fname = argv[0];
@@ -897,7 +949,7 @@ DEFUN(cfg_no_log_file, cfg_no_log_file_cmd,
log_tgt_mutex_lock();
tgt = log_target_find(LOG_TGT_TYPE_FILE, fname);
if (!tgt) {
- vty_out(vty, "%% No such log file `%s'%s",
+ vty_out(vty, "%% No such log file '%s'%s",
fname, VTY_NEWLINE);
RET_WITH_UNLOCK(CMD_WARNING);
}
@@ -963,7 +1015,10 @@ static int config_write_log_single(struct vty *vty, struct log_target *tgt)
return 1;
break;
case LOG_TGT_TYPE_STDERR:
- vty_out(vty, "log stderr%s", VTY_NEWLINE);
+ if (tgt->tgt_file.wqueue)
+ vty_out(vty, "log stderr%s", VTY_NEWLINE);
+ else
+ vty_out(vty, "log stderr blocking-io%s", VTY_NEWLINE);
break;
case LOG_TGT_TYPE_SYSLOG:
#ifdef HAVE_SYSLOG_H
@@ -974,7 +1029,10 @@ static int config_write_log_single(struct vty *vty, struct log_target *tgt)
#endif
break;
case LOG_TGT_TYPE_FILE:
- vty_out(vty, "log file %s%s", tgt->tgt_file.fname, VTY_NEWLINE);
+ if (tgt->tgt_file.wqueue)
+ vty_out(vty, "log file %s%s", tgt->tgt_file.fname, VTY_NEWLINE);
+ else
+ vty_out(vty, "log file %s blocking-io%s", tgt->tgt_file.fname, VTY_NEWLINE);
break;
case LOG_TGT_TYPE_STRRB:
vty_out(vty, "log alarms %zu%s",
@@ -1003,6 +1061,8 @@ static int config_write_log_single(struct vty *vty, struct log_target *tgt)
tgt->print_category_hex ? 1 : 0, VTY_NEWLINE);
vty_out(vty, " logging print category %d%s",
tgt->print_category ? 1 : 0, VTY_NEWLINE);
+ vty_out(vty, " logging print thread-id %d%s",
+ tgt->print_tid ? 1 : 0, VTY_NEWLINE);
if (tgt->print_ext_timestamp)
vty_out(vty, " logging print extended-timestamp 1%s", VTY_NEWLINE);
else
@@ -1010,8 +1070,9 @@ static int config_write_log_single(struct vty *vty, struct log_target *tgt)
tgt->print_timestamp ? 1 : 0, VTY_NEWLINE);
if (tgt->print_level)
vty_out(vty, " logging print level 1%s", VTY_NEWLINE);
- vty_out(vty, " logging print file %s%s",
+ vty_out(vty, " logging print file %s%s%s",
get_value_string(logging_print_file_args, tgt->print_filename2),
+ tgt->print_filename_pos == LOG_FILENAME_POS_LINE_END ? " last" : "",
VTY_NEWLINE);
if (tgt->loglevel) {
@@ -1091,6 +1152,23 @@ DEFUN(vty_logp,
int category = log_parse_category(argv[0]);
int level = log_parse_level(argv[1]);
char *str = argv_concat(argv, argc, 2);
+
+ if (level < 0) {
+ vty_out(vty, "%% Invalid level '%s'%s", argv[1], VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (category < 0) {
+ vty_out(vty, "%% Invalid category '%s'%s", argv[0], VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ /* Properly handle library specific sub-systems */
+ if ((unsigned int) category >= osmo_log_info->num_cat_user) {
+ category -= osmo_log_info->num_cat_user - 1;
+ category *= -1;
+ }
+
LOGP(category, level, "%s\n", str);
return CMD_SUCCESS;
}
@@ -1125,7 +1203,7 @@ static void gen_vty_logp_cmd_strs(struct cmd_element *cmd)
/*! Register logging related commands to the VTY. Call this once from
* your application if you want to support those commands. */
-void logging_vty_add_cmds()
+void logging_vty_add_cmds(void)
{
install_lib_element_ve(&enable_logging_cmd);
install_lib_element_ve(&disable_logging_cmd);
@@ -1133,6 +1211,7 @@ void logging_vty_add_cmds()
install_lib_element_ve(&logging_use_clr_cmd);
install_lib_element_ve(&logging_prnt_timestamp_cmd);
install_lib_element_ve(&logging_prnt_ext_timestamp_cmd);
+ install_lib_element_ve(&logging_prnt_tid_cmd);
install_lib_element_ve(&logging_prnt_cat_cmd);
install_lib_element_ve(&logging_prnt_cat_hex_cmd);
install_lib_element_ve(&logging_prnt_level_cmd);
@@ -1167,6 +1246,7 @@ void logging_vty_add_cmds()
install_lib_element(CFG_LOG_NODE, &logging_use_clr_cmd);
install_lib_element(CFG_LOG_NODE, &logging_prnt_timestamp_cmd);
install_lib_element(CFG_LOG_NODE, &logging_prnt_ext_timestamp_cmd);
+ install_lib_element(CFG_LOG_NODE, &logging_prnt_tid_cmd);
install_lib_element(CFG_LOG_NODE, &logging_prnt_cat_cmd);
install_lib_element(CFG_LOG_NODE, &logging_prnt_cat_hex_cmd);
install_lib_element(CFG_LOG_NODE, &logging_prnt_level_cmd);
@@ -1193,4 +1273,5 @@ void logging_vty_add_cmds()
install_lib_element(CONFIG_NODE, &cfg_log_systemd_journal_cmd);
install_lib_element(CONFIG_NODE, &cfg_no_log_systemd_journal_cmd);
install_lib_element(CONFIG_NODE, &cfg_log_gsmtap_cmd);
+ install_lib_element(CONFIG_NODE, &cfg_no_log_gsmtap_cmd);
}
diff --git a/src/vty/stats_vty.c b/src/vty/stats_vty.c
index 630ee325..f940018f 100644
--- a/src/vty/stats_vty.c
+++ b/src/vty/stats_vty.c
@@ -1,5 +1,5 @@
/*
- * (C) 2009-2010 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2009-2022 by Harald Welte <laforge@gnumonks.org>
* (C) 2009-2014 by Holger Hans Peter Freyther
* (C) 2015 by sysmocom - s.f.m.c. GmbH
* All Rights Reserved
@@ -16,16 +16,12 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <stdlib.h>
#include <string.h>
-#include "../../config.h"
+#include "config.h"
#include <osmocom/vty/command.h>
#include <osmocom/vty/buffer.h>
@@ -37,11 +33,13 @@
#include <osmocom/core/stats.h>
#include <osmocom/core/counter.h>
#include <osmocom/core/rate_ctr.h>
+#include <osmocom/core/stats_tcp.h>
#define CFG_STATS_STR "Configure stats sub-system\n"
#define CFG_REPORTER_STR "Configure a stats reporter\n"
#define SHOW_STATS_STR "Show statistical values\n"
+#define SKIP_ZERO_STR "Skip items with total count zero\n"
#define STATS_STR "Stats related commands\n"
@@ -269,14 +267,20 @@ DEFUN(cfg_stats_reporter_flush_period, cfg_stats_reporter_flush_period_cmd,
}
DEFUN(cfg_stats_reporter_statsd, cfg_stats_reporter_statsd_cmd,
- "stats reporter statsd",
- CFG_STATS_STR CFG_REPORTER_STR "Report to a STATSD server\n")
+ "stats reporter statsd [NAME]",
+ CFG_STATS_STR CFG_REPORTER_STR
+ "Report to a STATSD server\n"
+ "Name of the reporter\n")
{
struct osmo_stats_reporter *srep;
+ const char *name = NULL;
+
+ if (argc > 0)
+ name = argv[0];
- srep = osmo_stats_reporter_find(OSMO_STATS_REPORTER_STATSD, NULL);
+ srep = osmo_stats_reporter_find(OSMO_STATS_REPORTER_STATSD, name);
if (!srep) {
- srep = osmo_stats_reporter_create_statsd(NULL);
+ srep = osmo_stats_reporter_create_statsd(name);
if (!srep) {
vty_out(vty, "%% Unable to create statsd reporter%s",
VTY_NEWLINE);
@@ -293,15 +297,21 @@ DEFUN(cfg_stats_reporter_statsd, cfg_stats_reporter_statsd_cmd,
}
DEFUN(cfg_no_stats_reporter_statsd, cfg_no_stats_reporter_statsd_cmd,
- "no stats reporter statsd",
- NO_STR CFG_STATS_STR CFG_REPORTER_STR "Report to a STATSD server\n")
+ "no stats reporter statsd [NAME]",
+ NO_STR CFG_STATS_STR CFG_REPORTER_STR
+ "Report to a STATSD server\n"
+ "Name of the reporter\n")
{
struct osmo_stats_reporter *srep;
+ const char *name = NULL;
- srep = osmo_stats_reporter_find(OSMO_STATS_REPORTER_STATSD, NULL);
+ if (argc > 0)
+ name = argv[0];
+
+ srep = osmo_stats_reporter_find(OSMO_STATS_REPORTER_STATSD, name);
if (!srep) {
- vty_out(vty, "%% No statsd logging active%s",
- VTY_NEWLINE);
+ vty_out(vty, "%% There is no such statsd reporter with name '%s'%s",
+ name ? name : "", VTY_NEWLINE);
return CMD_WARNING;
}
@@ -311,14 +321,20 @@ DEFUN(cfg_no_stats_reporter_statsd, cfg_no_stats_reporter_statsd_cmd,
}
DEFUN(cfg_stats_reporter_log, cfg_stats_reporter_log_cmd,
- "stats reporter log",
- CFG_STATS_STR CFG_REPORTER_STR "Report to the logger\n")
+ "stats reporter log [NAME]",
+ CFG_STATS_STR CFG_REPORTER_STR
+ "Report to the logger\n"
+ "Name of the reporter\n")
{
struct osmo_stats_reporter *srep;
+ const char *name = NULL;
- srep = osmo_stats_reporter_find(OSMO_STATS_REPORTER_LOG, NULL);
+ if (argc > 0)
+ name = argv[0];
+
+ srep = osmo_stats_reporter_find(OSMO_STATS_REPORTER_LOG, name);
if (!srep) {
- srep = osmo_stats_reporter_create_log(NULL);
+ srep = osmo_stats_reporter_create_log(name);
if (!srep) {
vty_out(vty, "%% Unable to create log reporter%s",
VTY_NEWLINE);
@@ -335,15 +351,21 @@ DEFUN(cfg_stats_reporter_log, cfg_stats_reporter_log_cmd,
}
DEFUN(cfg_no_stats_reporter_log, cfg_no_stats_reporter_log_cmd,
- "no stats reporter log",
- NO_STR CFG_STATS_STR CFG_REPORTER_STR "Report to the logger\n")
+ "no stats reporter log [NAME]",
+ NO_STR CFG_STATS_STR CFG_REPORTER_STR
+ "Report to the logger\n"
+ "Name of the reporter\n")
{
struct osmo_stats_reporter *srep;
+ const char *name = NULL;
+
+ if (argc > 0)
+ name = argv[0];
- srep = osmo_stats_reporter_find(OSMO_STATS_REPORTER_LOG, NULL);
+ srep = osmo_stats_reporter_find(OSMO_STATS_REPORTER_LOG, name);
if (!srep) {
- vty_out(vty, "%% No log reporting active%s",
- VTY_NEWLINE);
+ vty_out(vty, "%% There is no such log reporter with name '%s'%s",
+ name ? name : "", VTY_NEWLINE);
return CMD_WARNING;
}
@@ -369,27 +391,60 @@ DEFUN(cfg_stats_interval, cfg_stats_interval_cmd,
return CMD_SUCCESS;
}
+DEFUN(cfg_tcp_stats_interval, cfg_tcp_stats_interval_cmd,
+ "stats-tcp interval <0-65535>",
+ CFG_STATS_STR "Set the tcp socket stats polling interval\n"
+ "Interval in seconds (0 disables the polling interval)\n")
+{
+ int rc;
+ int interval = atoi(argv[0]);
+ rc = osmo_stats_tcp_set_interval(interval);
+ if (rc < 0) {
+ vty_out(vty, "%% Unable to set interval: %s%s",
+ strerror(-rc), VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_tcp_stats_batch_size, cfg_tcp_stats_batch_size_cmd,
+ "stats-tcp batch-size <1-65535>",
+ CFG_STATS_STR "Set the number of tcp sockets that are processed per stats polling interval\n"
+ "Number of sockets per interval\n")
+{
+ osmo_tcp_stats_config->batch_size = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
DEFUN(show_stats,
show_stats_cmd,
- "show stats",
- SHOW_STR SHOW_STATS_STR)
+ "show stats [skip-zero]",
+ SHOW_STR SHOW_STATS_STR SKIP_ZERO_STR)
{
- vty_out_statistics_full(vty, "");
+ bool skip_zero = false;
+ if (argc > 0)
+ skip_zero = true;
+
+ vty_out_statistics_full2(vty, "", skip_zero);
return CMD_SUCCESS;
}
DEFUN(show_stats_level,
show_stats_level_cmd,
- "show stats level (global|peer|subscriber)",
+ "show stats level (global|peer|subscriber) [skip-zero]",
SHOW_STR SHOW_STATS_STR
"Set the maximum group level\n"
"Show global groups only\n"
"Show global and network peer related groups\n"
- "Show global, peer, and subscriber groups\n")
+ "Show global, peer, and subscriber groups\n" SKIP_ZERO_STR)
{
int level = get_string_value(stats_class_strs, argv[0]);
- vty_out_statistics_partial(vty, "", level);
+ bool skip_zero = false;
+ if (argc > 1)
+ skip_zero = true;
+ vty_out_statistics_partial2(vty, "", level, skip_zero);
return CMD_SUCCESS;
}
@@ -415,13 +470,6 @@ static int asciidoc_handle_counter(struct osmo_counter *counter, void *sctx_)
static void asciidoc_counter_generate(struct vty *vty)
{
- if (osmo_counters_count() == 0)
- {
- vty_out(vty, "// there are no ungrouped osmo_counters%s",
- VTY_NEWLINE);
- return;
- }
-
vty_out(vty, "// ungrouped osmo_counters%s", VTY_NEWLINE);
vty_out(vty, ".ungrouped osmo counters%s", VTY_NEWLINE);
vty_out(vty, "[options=\"header\"]%s", VTY_NEWLINE);
@@ -482,10 +530,11 @@ static int asciidoc_osmo_stat_item_handler(
{
struct vty *vty = sctx_;
- char *name = osmo_asciidoc_escape(item->desc->name);
- char *description = osmo_asciidoc_escape(item->desc->description);
+ const struct osmo_stat_item_desc *desc = osmo_stat_item_get_desc(item);
+ char *name = osmo_asciidoc_escape(desc->name);
+ char *description = osmo_asciidoc_escape(desc->description);
char *group_name_prefix = osmo_asciidoc_escape(statg->desc->group_name_prefix);
- char *unit = osmo_asciidoc_escape(item->desc->unit);
+ char *unit = osmo_asciidoc_escape(desc->unit);
/* | Name | Reference | Description | Unit | */
vty_out(vty, "| %s | <<%s_%s>> | %s | %s%s",
@@ -542,30 +591,48 @@ DEFUN(show_stats_asciidoc_table,
vty_out(vty, "// generating tables for rate_ctr_group%s", VTY_NEWLINE);
rate_ctr_for_each_group(asciidoc_rate_ctr_group_handler, vty);
- vty_out(vty, "== Osmo Stat Items%s%s", VTY_NEWLINE, VTY_NEWLINE);
+ vty_out(vty, "=== Osmo Stat Items%s%s", VTY_NEWLINE, VTY_NEWLINE);
vty_out(vty, "// generating tables for osmo_stat_items%s", VTY_NEWLINE);
osmo_stat_item_for_each_group(asciidoc_osmo_stat_item_group_handler, vty);
- vty_out(vty, "== Osmo Counters%s%s", VTY_NEWLINE, VTY_NEWLINE);
- vty_out(vty, "// generating tables for osmo_counters%s", VTY_NEWLINE);
- asciidoc_counter_generate(vty);
+ if (osmo_counters_count() == 0)
+ {
+ vty_out(vty, "// there are no ungrouped osmo_counters%s",
+ VTY_NEWLINE);
+ } else {
+ vty_out(vty, "=== Osmo Counters%s%s", VTY_NEWLINE, VTY_NEWLINE);
+ vty_out(vty, "// generating tables for osmo_counters%s", VTY_NEWLINE);
+ asciidoc_counter_generate(vty);
+ }
return CMD_SUCCESS;
}
+struct rctr_vty_ctx {
+ struct vty *vty;
+ bool skip_zero;
+};
+
static int rate_ctr_group_handler(struct rate_ctr_group *ctrg, void *sctx_)
{
- struct vty *vty = sctx_;
- vty_out(vty, "%s %u:%s", ctrg->desc->group_description, ctrg->idx, VTY_NEWLINE);
- vty_out_rate_ctr_group_fmt(vty, "%25n: %10c (%S/s %M/m %H/h %D/d) %d", ctrg);
+ struct rctr_vty_ctx *sctx = sctx_;
+ struct vty *vty = sctx->vty;
+ vty_out(vty, "%s %u", ctrg->desc->group_description, ctrg->idx);
+ if (ctrg->name != NULL)
+ vty_out(vty, " (%s)", ctrg->name);
+ vty_out(vty, ":%s", VTY_NEWLINE);
+ vty_out_rate_ctr_group_fmt2(vty, "%25n: %10c (%S/s %M/m %H/h %D/d) %d", ctrg, sctx->skip_zero);
return 0;
}
DEFUN(show_rate_counters,
show_rate_counters_cmd,
- "show rate-counters",
- SHOW_STR "Show all rate counters\n")
+ "show rate-counters [skip-zero]",
+ SHOW_STR "Show all rate counters\n" SKIP_ZERO_STR)
{
- rate_ctr_for_each_group(rate_ctr_group_handler, vty);
+ struct rctr_vty_ctx rctx = { .vty = vty, .skip_zero = false };
+ if (argc > 0)
+ rctx.skip_zero = true;
+ rate_ctr_for_each_group(rate_ctr_group_handler, &rctx);
return CMD_SUCCESS;
}
@@ -584,37 +651,37 @@ static int reset_rate_ctr_group_handler(struct rate_ctr_group *ctrg, void *sctx_
return 0;
}
-static int reset_osmo_stat_item_group_handler(struct osmo_stat_item_group *statg, void *sctx_)
-{
- osmo_stat_item_group_reset(statg);
- return 0;
-}
-
DEFUN(stats_reset,
stats_reset_cmd,
"stats reset",
- STATS_STR "Reset all stats\n")
+ STATS_STR "Reset all rate counter stats\n")
{
rate_ctr_for_each_group(reset_rate_ctr_group_handler, NULL);
- osmo_stat_item_for_each_group(reset_osmo_stat_item_group_handler, NULL);
return CMD_SUCCESS;
}
static int config_write_stats_reporter(struct vty *vty, struct osmo_stats_reporter *srep)
{
- if (srep == NULL)
- return 0;
+ const char *type = NULL;
switch (srep->type) {
case OSMO_STATS_REPORTER_STATSD:
- vty_out(vty, "stats reporter statsd%s", VTY_NEWLINE);
+ type = "statsd";
break;
case OSMO_STATS_REPORTER_LOG:
- vty_out(vty, "stats reporter log%s", VTY_NEWLINE);
+ type = "log";
break;
+ default:
+ /* don't try to save unknown stats reporters to the VTY. Imagine some
+ * application registering a new application specific stats reporter that
+ * this VTY code knows nothing about! */
+ return 0;
}
- vty_out(vty, " disable%s", VTY_NEWLINE);
+ vty_out(vty, "stats reporter %s", type);
+ if (srep->name != NULL)
+ vty_out(vty, " %s", srep->name);
+ vty_out(vty, "%s", VTY_NEWLINE);
if (srep->have_net_config) {
if (srep->dest_addr_str)
@@ -648,6 +715,8 @@ static int config_write_stats_reporter(struct vty *vty, struct osmo_stats_report
if (srep->enabled)
vty_out(vty, " enable%s", VTY_NEWLINE);
+ else
+ vty_out(vty, " disable%s", VTY_NEWLINE);
return 1;
}
@@ -656,13 +725,15 @@ static int config_write_stats(struct vty *vty)
{
struct osmo_stats_reporter *srep;
- /* TODO: loop through all reporters */
- srep = osmo_stats_reporter_find(OSMO_STATS_REPORTER_STATSD, NULL);
- config_write_stats_reporter(vty, srep);
- srep = osmo_stats_reporter_find(OSMO_STATS_REPORTER_LOG, NULL);
- config_write_stats_reporter(vty, srep);
-
vty_out(vty, "stats interval %d%s", osmo_stats_config->interval, VTY_NEWLINE);
+ if (osmo_tcp_stats_config->interval != TCP_STATS_DEFAULT_INTERVAL)
+ vty_out(vty, "stats-tcp interval %d%s", osmo_tcp_stats_config->interval, VTY_NEWLINE);
+ if (osmo_tcp_stats_config->batch_size != TCP_STATS_DEFAULT_BATCH_SIZE)
+ vty_out(vty, "stats-tcp batch-size %d%s", osmo_tcp_stats_config->batch_size, VTY_NEWLINE);
+
+ /* Loop through all reporters */
+ llist_for_each_entry(srep, &osmo_stats_reporter_list, list)
+ config_write_stats_reporter(vty, srep);
return 1;
}
@@ -671,7 +742,7 @@ static int config_write_stats(struct vty *vty)
* Call this once during your application initialization if you would
* like to have stats VTY commands enabled.
*/
-void osmo_stats_vty_add_cmds()
+void osmo_stats_vty_add_cmds(void)
{
install_lib_element_ve(&show_stats_cmd);
install_lib_element_ve(&show_stats_level_cmd);
@@ -681,6 +752,8 @@ void osmo_stats_vty_add_cmds()
install_lib_element(CONFIG_NODE, &cfg_stats_reporter_log_cmd);
install_lib_element(CONFIG_NODE, &cfg_no_stats_reporter_log_cmd);
install_lib_element(CONFIG_NODE, &cfg_stats_interval_cmd);
+ install_lib_element(CONFIG_NODE, &cfg_tcp_stats_interval_cmd);
+ install_lib_element(CONFIG_NODE, &cfg_tcp_stats_batch_size_cmd);
install_node(&cfg_stats_node, config_write_stats);
diff --git a/src/vty/talloc_ctx_vty.c b/src/vty/talloc_ctx_vty.c
index 39664787..ea8ebe70 100644
--- a/src/vty/talloc_ctx_vty.c
+++ b/src/vty/talloc_ctx_vty.c
@@ -17,17 +17,13 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <stdio.h>
#include <regex.h>
#include <string.h>
-#include <talloc.h>
+#include <osmocom/core/talloc.h>
#include <osmocom/vty/command.h>
#include <osmocom/vty/vty.h>
@@ -151,6 +147,8 @@ static void talloc_ctx_walk(const char *ctx, const char *depth,
/* Determine a context for report */
if (!strncmp(ctx, "app", 3))
talloc_ctx = host.app_info->tall_ctx;
+ else if (!strcmp(ctx, "global"))
+ talloc_ctx = OTC_GLOBAL;
else if (!strncmp(ctx, "all", 3))
talloc_ctx = NULL;
@@ -167,11 +165,12 @@ static void talloc_ctx_walk(const char *ctx, const char *depth,
}
#define BASE_CMD_STR \
- "show talloc-context (application|all) (full|brief|DEPTH)"
+ "show talloc-context (application|global|all) (full|brief|DEPTH)"
#define BASE_CMD_DESCR \
SHOW_STR "Show talloc memory hierarchy\n" \
"Application's context\n" \
+ "Global context (OTC_GLOBAL)\n" \
"All contexts, if NULL-context tracking is enabled\n" \
"Display a full talloc memory hierarchy\n" \
"Display a brief talloc memory hierarchy\n" \
diff --git a/src/vty/tdef_vty.c b/src/vty/tdef_vty.c
index 0556d8c9..bd209ae0 100644
--- a/src/vty/tdef_vty.c
+++ b/src/vty/tdef_vty.c
@@ -50,10 +50,9 @@
*/
struct osmo_tdef *osmo_tdef_vty_parse_T_arg(struct vty *vty, struct osmo_tdef *tdefs, const char *T_str)
{
- long l;
+ int l;
int T;
struct osmo_tdef *t;
- char *endptr;
const char *T_nr_str;
int sign = 1;
@@ -77,9 +76,7 @@ struct osmo_tdef *osmo_tdef_vty_parse_T_arg(struct vty *vty, struct osmo_tdef *t
return NULL;
}
- errno = 0;
- l = strtol(T_nr_str, &endptr, 10);
- if (errno || *endptr || l > INT_MAX || l < 0) {
+ if (osmo_str_to_int(&l, T_nr_str, 10, 0, INT_MAX)) {
vty_out(vty, "%% Invalid T timer argument (should be 'T1234' or 'X1234'): '%s'%s", T_str, VTY_NEWLINE);
return NULL;
}
@@ -245,7 +242,7 @@ void osmo_tdef_vty_out_all(struct vty *vty, struct osmo_tdef *tdefs, const char
/*! Write current timer configuration arguments to the vty. Skip all entries that reflect their default value.
* The passed prefix string must contain both necessary indent and the VTY command the specific implementation is using.
- * See tdef_vty_test_config_subnode.c and tdef_vty_test_dynamic.c for examples.
+ * See tdef_vty_config_subnode_test.c and tdef_vty_dynamic_test.c for examples.
* \param[in] vty VTY context.
* \param[in] tdefs Array of timers to print, ended with a fully zero-initialized entry.
* \param[in] prefix_fmt Arbitrary string to start each line with, with variable printf like arguments.
diff --git a/src/vty/telnet_interface.c b/src/vty/telnet_interface.c
index cd32e68f..8fa5dbff 100644
--- a/src/vty/telnet_interface.c
+++ b/src/vty/telnet_interface.c
@@ -14,10 +14,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <sys/socket.h>
@@ -46,7 +42,7 @@
* process in order to enable interactive command-line introspection,
* interaction and configuration.
*
- * You typically call \ref telnet_init or \ref telnet_init_dynif once
+ * You typically call telnet_init_default once
* from your application code to enable this.
*/
@@ -64,26 +60,14 @@ static struct osmo_fd server_socket = {
.priv_nr = 0,
};
-/*! Initialize telnet based VTY interface listening to 127.0.0.1
- * \param[in] tall_ctx \ref talloc context
- * \param[in] priv private data to be passed to callback
- * \param[in] port TCP port number to bind to
- */
-int telnet_init(void *tall_ctx, void *priv, int port)
-{
- return telnet_init_dynif(tall_ctx, priv, "127.0.0.1", port);
-}
-
-/*! Initialize telnet based VTY interface
- * \param[in] tall_ctx \ref talloc context
- * \param[in] priv private data to be passed to callback
- * \param[in] ip IP to listen to ('::1' for localhost, '::0' for all, ...)
- * \param[in] port TCP port number to bind to
- */
-int telnet_init_dynif(void *tall_ctx, void *priv, const char *ip, int port)
+/* Helper for deprecating telnet_init_dynif(), which previously held this code */
+static int _telnet_init_dynif(void *tall_ctx, void *priv, const char *ip, int port)
{
int rc;
+ if (port < 0)
+ return -EINVAL;
+
tall_telnet_ctx = talloc_named_const(tall_ctx, 1,
"telnet_connection");
@@ -98,22 +82,45 @@ int telnet_init_dynif(void *tall_ctx, void *priv, const char *ip, int port)
if (rc < 0) {
LOGP(DLGLOBAL, LOGL_ERROR, "Cannot bind telnet at %s %d\n",
ip, port);
- return -1;
+ return rc;
}
LOGP(DLGLOBAL, LOGL_NOTICE, "Available via telnet %s %d\n", ip, port);
return 0;
}
+/*! Initialize telnet based VTY interface listening to 127.0.0.1
+ * \param[in] tall_ctx \ref talloc context
+ * \param[in] priv private data to be passed to callback
+ * \param[in] port TCP port number to bind to
+ * \deprecated use telnet_init_default() instead
+ */
+int telnet_init(void *tall_ctx, void *priv, int port)
+{
+ return _telnet_init_dynif(tall_ctx, priv, "127.0.0.1", port);
+}
+
+/*! Initialize telnet based VTY interface
+ * \param[in] tall_ctx \ref talloc context
+ * \param[in] priv private data to be passed to callback
+ * \param[in] ip IP to listen to ('::1' for localhost, '::0' for all, ...)
+ * \param[in] port TCP port number to bind to
+ * \deprecated use telnet_init_default() instead
+ */
+int telnet_init_dynif(void *tall_ctx, void *priv, const char *ip, int port)
+{
+ return _telnet_init_dynif(tall_ctx, priv, ip, port);
+}
+
/*! Initializes telnet based VTY interface using the configured bind addr/port.
* \param[in] tall_ctx \ref talloc context
* \param[in] priv private data to be passed to callback
- * \param[in] default_port TCP port number to bind to if not explicitely configured
+ * \param[in] default_port TCP port number to bind to if not explicitly configured
*/
int telnet_init_default(void *tall_ctx, void *priv, int default_port)
{
- return telnet_init_dynif(tall_ctx, priv, vty_get_bind_addr(),
- vty_get_bind_port(default_port));
+ return _telnet_init_dynif(tall_ctx, priv, vty_get_bind_addr(),
+ vty_get_bind_port(default_port));
}
diff --git a/src/vty/utils.c b/src/vty/utils.c
index 0358d9bd..0f5a34e4 100644
--- a/src/vty/utils.c
+++ b/src/vty/utils.c
@@ -18,13 +18,10 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <stdint.h>
+#include <stdbool.h>
#include <inttypes.h>
#include <string.h>
#include <ctype.h>
@@ -48,6 +45,7 @@ struct vty_out_context {
struct vty *vty;
const char *prefix;
int max_level;
+ bool skip_zero;
};
static int rate_ctr_handler(
@@ -57,6 +55,9 @@ static int rate_ctr_handler(
struct vty_out_context *vctx = vctx_;
struct vty *vty = vctx->vty;
+ if (vctx->skip_zero && ctr->current == 0)
+ return 0;
+
vty_out(vty, " %s%s: %8" PRIu64 " "
"(%" PRIu64 "/s %" PRIu64 "/m %" PRIu64 "/h %" PRIu64 "/d)%s",
vctx->prefix, desc->description, ctr->current,
@@ -73,17 +74,24 @@ static int rate_ctr_handler(
* \param[in] vty The VTY to which it should be printed
* \param[in] prefix Any additional log prefix ahead of each line
* \param[in] ctrg Rate counter group to be printed
+ * \param[in] skip_zero Skip all zero-valued counters
*/
-void vty_out_rate_ctr_group(struct vty *vty, const char *prefix,
- struct rate_ctr_group *ctrg)
+void vty_out_rate_ctr_group2(struct vty *vty, const char *prefix,
+ struct rate_ctr_group *ctrg, bool skip_zero)
{
- struct vty_out_context vctx = {vty, prefix};
+ struct vty_out_context vctx = {vty, prefix, 0, skip_zero};
vty_out(vty, "%s%s:%s", prefix, ctrg->desc->group_description, VTY_NEWLINE);
rate_ctr_for_each_counter(ctrg, rate_ctr_handler, &vctx);
}
+void vty_out_rate_ctr_group(struct vty *vty, const char *prefix,
+ struct rate_ctr_group *ctrg)
+{
+ vty_out_rate_ctr_group2(vty, prefix, ctrg, false);
+}
+
static char *
pad_append_str(char *s, const char *a, int minwidth)
{
@@ -107,7 +115,12 @@ static int rate_ctr_handler_fmt(
struct vty_out_context *vctx = vctx_;
struct vty *vty = vctx->vty;
const char *fmt = vctx->prefix;
- char *s = talloc_strdup(vty, "");
+ char *s;
+
+ if (vctx->skip_zero && ctr->current == 0)
+ return 0;
+
+ s = talloc_strdup(vty, "");
OSMO_ASSERT(s);
while (*fmt) {
@@ -209,14 +222,20 @@ static int rate_ctr_handler_fmt(
* \param[in] vty The VTY to which it should be printed
* \param[in] ctrg Rate counter group to be printed
* \param[in] fmt A format which may contain the above directives.
+ * \param[in] skip_zero Skip all zero-valued counters
*/
-void vty_out_rate_ctr_group_fmt(struct vty *vty, const char *fmt,
- struct rate_ctr_group *ctrg)
+void vty_out_rate_ctr_group_fmt2(struct vty *vty, const char *fmt,
+ struct rate_ctr_group *ctrg, bool skip_zero)
{
- struct vty_out_context vctx = {vty, fmt};
+ struct vty_out_context vctx = {vty, fmt, 0, skip_zero};
rate_ctr_for_each_counter(ctrg, rate_ctr_handler_fmt, &vctx);
}
+void vty_out_rate_ctr_group_fmt(struct vty *vty, const char *fmt,
+ struct rate_ctr_group *ctrg)
+{
+ vty_out_rate_ctr_group_fmt2(vty, fmt, ctrg, false);
+}
static int rate_ctr_group_handler(struct rate_ctr_group *ctrg, void *vctx_)
{
struct vty_out_context *vctx = vctx_;
@@ -225,12 +244,10 @@ static int rate_ctr_group_handler(struct rate_ctr_group *ctrg, void *vctx_)
if (ctrg->desc->class_id > vctx->max_level)
return 0;
- if (ctrg->idx)
- vty_out(vty, "%s%s (%d):%s", vctx->prefix,
- ctrg->desc->group_description, ctrg->idx, VTY_NEWLINE);
- else
- vty_out(vty, "%s%s:%s", vctx->prefix,
- ctrg->desc->group_description, VTY_NEWLINE);
+ vty_out(vty, "%s%s (%d)", vctx->prefix, ctrg->desc->group_description, ctrg->idx);
+ if (ctrg->name)
+ vty_out(vty, "('%s')", ctrg->name);
+ vty_out(vty, ":%s", VTY_NEWLINE);
rate_ctr_for_each_counter(ctrg, rate_ctr_handler, vctx);
@@ -249,14 +266,15 @@ static int osmo_stat_item_handler(
{
struct vty_out_context *vctx = vctx_;
struct vty *vty = vctx->vty;
- const char *unit =
- item->desc->unit != OSMO_STAT_ITEM_NO_UNIT ?
- item->desc->unit : "";
+ const struct osmo_stat_item_desc *desc = osmo_stat_item_get_desc(item);
+ int32_t value = osmo_stat_item_get_last(item);
+ const char *unit = (desc->unit != OSMO_STAT_ITEM_NO_UNIT) ? desc->unit : "";
+
+ if (vctx->skip_zero && value == 0)
+ return 0;
vty_out(vty, " %s%s: %8" PRIi32 " %s%s",
- vctx->prefix, item->desc->description,
- osmo_stat_item_get_last(item),
- unit, VTY_NEWLINE);
+ vctx->prefix, desc->description, value, unit, VTY_NEWLINE);
return 0;
}
@@ -265,17 +283,24 @@ static int osmo_stat_item_handler(
* \param[in] vty The VTY to which it should be printed
* \param[in] prefix Any additional log prefix ahead of each line
* \param[in] statg Stat item group to be printed
+ * \param[in] skip_zero Skip all zero-valued counters
*/
-void vty_out_stat_item_group(struct vty *vty, const char *prefix,
- struct osmo_stat_item_group *statg)
+void vty_out_stat_item_group2(struct vty *vty, const char *prefix,
+ struct osmo_stat_item_group *statg, bool skip_zero)
{
- struct vty_out_context vctx = {vty, prefix};
+ struct vty_out_context vctx = {vty, prefix, 0, skip_zero};
vty_out(vty, "%s%s:%s", prefix, statg->desc->group_description,
VTY_NEWLINE);
osmo_stat_item_for_each_item(statg, osmo_stat_item_handler, &vctx);
}
+void vty_out_stat_item_group(struct vty *vty, const char *prefix,
+ struct osmo_stat_item_group *statg)
+{
+ return vty_out_stat_item_group2(vty, prefix, statg, false);
+}
+
static int osmo_stat_item_group_handler(struct osmo_stat_item_group *statg, void *vctx_)
{
struct vty_out_context *vctx = vctx_;
@@ -284,13 +309,10 @@ static int osmo_stat_item_group_handler(struct osmo_stat_item_group *statg, void
if (statg->desc->class_id > vctx->max_level)
return 0;
- if (statg->idx)
- vty_out(vty, "%s%s (%d):%s", vctx->prefix,
- statg->desc->group_description, statg->idx,
- VTY_NEWLINE);
- else
- vty_out(vty, "%s%s:%s", vctx->prefix,
- statg->desc->group_description, VTY_NEWLINE);
+ vty_out(vty, "%s%s (%d)", vctx->prefix, statg->desc->group_description, statg->idx);
+ if (statg->name)
+ vty_out(vty, "('%s')", statg->name);
+ vty_out(vty, ":%s", VTY_NEWLINE);
osmo_stat_item_for_each_item(statg, osmo_stat_item_handler, vctx);
@@ -308,21 +330,22 @@ static int handle_counter(struct osmo_counter *counter, void *vctx_)
struct vty_out_context *vctx = vctx_;
struct vty *vty = vctx->vty;
const char *description = counter->description;
+ unsigned long value = osmo_counter_get(counter);
+
+ if (vctx->skip_zero && value == 0)
+ return 0;
if (!counter->description)
description = counter->name;
- vty_out(vty, " %s%s: %8lu%s",
- vctx->prefix, description,
- osmo_counter_get(counter), VTY_NEWLINE);
+ vty_out(vty, " %s%s: %8lu%s", vctx->prefix, description, value, VTY_NEWLINE);
return 0;
}
-void vty_out_statistics_partial(struct vty *vty, const char *prefix,
- int max_level)
+void vty_out_statistics_partial2(struct vty *vty, const char *prefix, int max_level, bool skip_zero)
{
- struct vty_out_context vctx = {vty, prefix, max_level};
+ struct vty_out_context vctx = {vty, prefix, max_level, skip_zero};
vty_out(vty, "%sUngrouped counters:%s", prefix, VTY_NEWLINE);
osmo_counters_for_each(handle_counter, &vctx);
@@ -330,9 +353,19 @@ void vty_out_statistics_partial(struct vty *vty, const char *prefix,
osmo_stat_item_for_each_group(osmo_stat_item_group_handler, &vctx);
}
+void vty_out_statistics_partial(struct vty *vty, const char *prefix, int max_level)
+{
+ return vty_out_statistics_partial2(vty, prefix, max_level, false);
+}
+
+void vty_out_statistics_full2(struct vty *vty, const char *prefix, bool skip_zero)
+{
+ vty_out_statistics_partial2(vty, prefix, INT_MAX, skip_zero);
+}
+
void vty_out_statistics_full(struct vty *vty, const char *prefix)
{
- vty_out_statistics_partial(vty, prefix, INT_MAX);
+ vty_out_statistics_full2(vty, prefix, false);
}
/*! Generate a VTY command string from value_string */
@@ -382,6 +415,7 @@ char *vty_cmd_string_from_valstr(void *ctx, const struct value_string *vals,
if (ret < 0)
goto err;
OSMO_SNPRINTF_RET(ret, rem, offset, len);
+ (void)len; /* suppress warnings about len being set but not used */
err:
str[size-1] = '\0';
return str;
diff --git a/src/vty/vector.c b/src/vty/vector.c
index f9e5ec3e..34b161d8 100644
--- a/src/vty/vector.c
+++ b/src/vty/vector.c
@@ -16,11 +16,6 @@
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with GNU Zebra; see the file COPYING. If not, write to the Free
- * Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
*/
#include <stdlib.h>
diff --git a/src/vty/vty.c b/src/vty/vty.c
index f4e8e80f..3a549b43 100644
--- a/src/vty/vty.c
+++ b/src/vty/vty.c
@@ -66,6 +66,7 @@
#include <osmocom/vty/command.h>
#include <osmocom/vty/buffer.h>
#include <osmocom/core/talloc.h>
+#include <osmocom/core/timer.h>
#include <osmocom/core/utils.h>
#ifndef MAXPATHLEN
@@ -207,6 +208,12 @@ static void vty_auth(struct vty *vty)
}
}
+void vty_flush(struct vty *vty)
+{
+ if (vty->obuf)
+ buffer_flush_all(vty->obuf, vty->fd);
+}
+
/*! Close a given vty interface. */
void vty_close(struct vty *vty)
{
@@ -332,6 +339,25 @@ int vty_out_newline(struct vty *vty)
return 0;
}
+/*! calculates the time difference of a give timespec to the current time
+ * and prints in a human readable format (days, hours, minutes, seconds).
+ */
+int vty_out_uptime(struct vty *vty, const struct timespec *starttime)
+{
+ struct timespec now;
+ struct timespec uptime;
+
+ osmo_clock_gettime(CLOCK_MONOTONIC, &now);
+ timespecsub(&now, starttime, &uptime);
+
+ int d = uptime.tv_sec / (3600 * 24);
+ int h = uptime.tv_sec / 3600 % 24;
+ int m = uptime.tv_sec / 60 % 60;
+ int s = uptime.tv_sec % 60;
+
+ return vty_out(vty, "%dd %dh %dm %ds", d, h, m, s);
+}
+
/*! return the current index of a given VTY */
void *vty_current_index(struct vty *vty)
{
@@ -459,6 +485,7 @@ static int vty_command(struct vty *vty)
static const char telnet_backward_char = 0x08;
static const char telnet_space_char = ' ';
+static const char telnet_escape_char = 0x1B;
/* Basic function to write buffer to vty. */
static void vty_write(struct vty *vty, const char *buf, size_t nbytes)
@@ -858,6 +885,19 @@ static void vty_down_level(struct vty *vty)
vty->cp = 0;
}
+/* When '^L' is typed, clear all lines above the current one. */
+static void vty_clear_screen(struct vty *vty)
+{
+ vty_out(vty, "%c%s%c%s",
+ telnet_escape_char,
+ "[2J", /* Erase Screen */
+ telnet_escape_char,
+ "[H" /* Cursor Home */
+ );
+ vty_prompt(vty);
+ vty_redraw_line(vty);
+}
+
/* When '^Z' is received from vty, move down to the enable mode. */
static void vty_end_config(struct vty *vty)
{
@@ -1402,6 +1442,9 @@ int vty_read(struct vty *vty)
case CONTROL('K'):
vty_kill_line(vty);
break;
+ case CONTROL('L'):
+ vty_clear_screen(vty);
+ break;
case CONTROL('N'):
vty_next_line(vty);
break;
@@ -1462,9 +1505,13 @@ int vty_read(struct vty *vty)
return 0;
}
-/* Read up configuration file */
-static int
-vty_read_file(FILE *confp, void *priv)
+/* Read up configuration from a file stream */
+/*! Read up VTY configuration from a file stream
+ * \param[in] confp file pointer of the stream for the configuration file
+ * \param[in] priv private data to be passed to \ref vty_read_file
+ * \returns Zero on success, non-zero on error
+ */
+int vty_read_config_filep(FILE *confp, void *priv)
{
int ret;
struct vty *vty;
@@ -1870,7 +1917,7 @@ int vty_read_config_file(const char *file_name, void *priv)
if (!cfile)
return -ENOENT;
- rc = vty_read_file(cfile, priv);
+ rc = vty_read_config_filep(cfile, priv);
fclose(cfile);
host_config_set(file_name);