aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore6
-rw-r--r--Makefile.am21
-rw-r--r--README.md3
-rw-r--r--TODO-RELEASE4
-rw-r--r--configure.ac100
-rwxr-xr-xcontrib/jenkins_amd64.sh2
-rwxr-xr-xcontrib/jenkins_arm.sh2
-rw-r--r--contrib/jenkins_common.sh2
-rw-r--r--contrib/libosmocore.spec.in454
-rwxr-xr-xcontrib/struct_endianess.py4
-rw-r--r--debian/changelog338
-rw-r--r--debian/control48
-rw-r--r--debian/libosmocore-dev.install1
-rw-r--r--debian/libosmocore16.install (renamed from debian/libosmocore12.install)0
-rw-r--r--debian/libosmogb11.install (renamed from debian/libosmogb9.install)0
-rw-r--r--debian/libosmogsm15.install (renamed from debian/libosmogsm13.install)0
-rw-r--r--debian/libosmosim2.install (renamed from debian/libosmosim0.install)0
-rw-r--r--debian/libosmousb0.install1
-rw-r--r--debian/patches/series0
-rwxr-xr-xdebian/rules2
-rw-r--r--include/Makefile.am20
-rw-r--r--include/osmocom/codec/codec.h6
-rw-r--r--include/osmocom/codec/ecu.h64
-rw-r--r--include/osmocom/coding/gsm0503_amr_dtx.h40
-rw-r--r--include/osmocom/coding/gsm0503_coding.h6
-rw-r--r--include/osmocom/coding/gsm0503_parity.h19
-rw-r--r--include/osmocom/core/bitXXgen.h.tpl27
-rw-r--r--include/osmocom/core/defs.h2
-rw-r--r--include/osmocom/core/exec.h29
-rw-r--r--include/osmocom/core/fsm.h2
-rw-r--r--include/osmocom/core/gsmtap.h40
-rw-r--r--include/osmocom/core/gsmtap_util.h6
-rw-r--r--include/osmocom/core/logging.h48
-rw-r--r--include/osmocom/core/logging_internal.h3
-rw-r--r--include/osmocom/core/msgb.h19
-rw-r--r--include/osmocom/core/rate_ctr.h3
-rw-r--r--include/osmocom/core/select.h40
-rw-r--r--include/osmocom/core/sockaddr_str.h21
-rw-r--r--include/osmocom/core/socket.h43
-rw-r--r--include/osmocom/core/stat_item.h4
-rw-r--r--include/osmocom/core/stats.h5
-rw-r--r--include/osmocom/core/talloc.h28
-rw-r--r--include/osmocom/core/tdef.h7
-rw-r--r--include/osmocom/core/timer.h1
-rw-r--r--include/osmocom/core/use_count.h5
-rw-r--r--include/osmocom/core/utils.h73
-rw-r--r--include/osmocom/core/write_queue.h1
-rw-r--r--include/osmocom/ctrl/ports.h3
-rw-r--r--include/osmocom/gprs/gprs_bssgp.h5
-rw-r--r--include/osmocom/gprs/gprs_bssgp_bss.h1
-rw-r--r--include/osmocom/gprs/gprs_ns.h8
-rw-r--r--include/osmocom/gprs/gprs_ns2.h188
-rw-r--r--include/osmocom/gprs/gprs_ns_frgre.h3
-rw-r--r--include/osmocom/gprs/protocol/gsm_04_60.h153
-rw-r--r--include/osmocom/gprs/protocol/gsm_08_16.h10
-rw-r--r--include/osmocom/gsm/bsslap.h57
-rw-r--r--include/osmocom/gsm/bssmap_le.h81
-rw-r--r--include/osmocom/gsm/bts_features.h11
-rw-r--r--include/osmocom/gsm/cbsp.h4
-rw-r--r--include/osmocom/gsm/gad.h190
-rw-r--r--include/osmocom/gsm/gsm0502.h40
-rw-r--r--include/osmocom/gsm/gsm0808.h14
-rw-r--r--include/osmocom/gsm/gsm0808_lcs.h52
-rw-r--r--include/osmocom/gsm/gsm0808_utils.h6
-rw-r--r--include/osmocom/gsm/gsm23236.h60
-rw-r--r--include/osmocom/gsm/gsm48.h57
-rw-r--r--include/osmocom/gsm/gsm48_ie.h3
-rw-r--r--include/osmocom/gsm/gsm48_rest_octets.h3
-rw-r--r--include/osmocom/gsm/gsm_utils.h4
-rw-r--r--include/osmocom/gsm/gsup.h15
-rw-r--r--include/osmocom/gsm/i460_mux.h121
-rw-r--r--include/osmocom/gsm/l1sap.h15
-rw-r--r--include/osmocom/gsm/lapd_core.h10
-rw-r--r--include/osmocom/gsm/lapdm.h12
-rw-r--r--include/osmocom/gsm/mncc.h8
-rw-r--r--include/osmocom/gsm/prim.h2
-rw-r--r--include/osmocom/gsm/protocol/gsm_04_08.h281
-rw-r--r--include/osmocom/gsm/protocol/gsm_08_08.h40
-rw-r--r--include/osmocom/gsm/protocol/gsm_08_58.h5
-rw-r--r--include/osmocom/gsm/protocol/gsm_12_21.h8
-rw-r--r--include/osmocom/gsm/protocol/gsm_23_032.h172
-rw-r--r--include/osmocom/gsm/protocol/gsm_23_041.h8
-rw-r--r--include/osmocom/gsm/protocol/gsm_29_118.h2
-rw-r--r--include/osmocom/gsm/protocol/gsm_48_071.h122
-rw-r--r--include/osmocom/gsm/protocol/gsm_49_031.h212
-rw-r--r--include/osmocom/gsm/protocol/ipaccess.h37
-rw-r--r--include/osmocom/gsm/tlv.h45
-rw-r--r--include/osmocom/sim/sim.h66
-rw-r--r--include/osmocom/usb/libusb.h119
-rw-r--r--include/osmocom/vty/command.h78
-rw-r--r--include/osmocom/vty/cpu_sched_vty.h37
-rw-r--r--include/osmocom/vty/ports.h3
-rw-r--r--include/osmocom/vty/tdef_vty.h5
-rw-r--r--include/osmocom/vty/vty.h24
-rw-r--r--libosmocodec.pc.in4
-rw-r--r--libosmocoding.pc.in4
-rw-r--r--libosmocore.pc.in4
-rw-r--r--libosmousb.pc.in11
-rw-r--r--m4/ax_pthread.m4486
-rw-r--r--m4/osmo_ac_code_coverage.m451
-rw-r--r--m4/osmo_ax_code_coverage.m4267
-rwxr-xr-xosmo-release.sh15
-rw-r--r--src/Makefile.am21
-rw-r--r--src/bits.c58
-rw-r--r--src/bitvec.c29
-rw-r--r--src/codec/Makefile.am6
-rw-r--r--src/codec/ecu.c118
-rw-r--r--src/codec/ecu_fr.c51
-rw-r--r--src/codec/gsm690.c113
-rw-r--r--src/coding/Makefile.am3
-rw-r--r--src/coding/gsm0503_amr_dtx.c316
-rw-r--r--src/coding/gsm0503_coding.c193
-rw-r--r--src/coding/gsm0503_parity.c11
-rw-r--r--src/coding/gsm0503_tables.c6
-rw-r--r--src/coding/libosmocoding.map7
-rw-r--r--src/context.c52
-rw-r--r--src/conv.c2
-rw-r--r--src/conv_acc.c28
-rw-r--r--src/conv_acc_neon.c110
-rw-r--r--src/conv_acc_neon_impl.h354
-rw-r--r--src/ctrl/control_vty.c4
-rw-r--r--src/exec.c294
-rw-r--r--src/fsm.c71
-rw-r--r--src/gb/Makefile.am9
-rw-r--r--src/gb/common_vty.c4
-rw-r--r--src/gb/gprs_bssgp.c63
-rw-r--r--src/gb/gprs_bssgp_bss.c43
-rw-r--r--src/gb/gprs_bssgp_internal.h7
-rw-r--r--src/gb/gprs_bssgp_util.c7
-rw-r--r--src/gb/gprs_bssgp_vty.c24
-rw-r--r--src/gb/gprs_ns.c49
-rw-r--r--src/gb/gprs_ns2.c1097
-rw-r--r--src/gb/gprs_ns2_frgre.c600
-rw-r--r--src/gb/gprs_ns2_internal.h296
-rw-r--r--src/gb/gprs_ns2_message.c713
-rw-r--r--src/gb/gprs_ns2_sns.c1495
-rw-r--r--src/gb/gprs_ns2_udp.c498
-rw-r--r--src/gb/gprs_ns2_vc_fsm.c668
-rw-r--r--src/gb/gprs_ns2_vty.c853
-rw-r--r--src/gb/gprs_ns_vty.c123
-rw-r--r--src/gb/libosmogb.map47
-rw-r--r--src/gsm/Makefile.am11
-rw-r--r--src/gsm/abis_nm.c1
-rw-r--r--src/gsm/auth_xor.c187
-rw-r--r--src/gsm/bsslap.c329
-rw-r--r--src/gsm/bssmap_le.c876
-rw-r--r--src/gsm/bts_features.c3
-rw-r--r--src/gsm/cbsp.c14
-rw-r--r--src/gsm/gad.c491
-rw-r--r--src/gsm/gsm0411_smc.c3
-rw-r--r--src/gsm/gsm0411_smr.c3
-rw-r--r--src/gsm/gsm0480.c1
-rw-r--r--src/gsm/gsm0502.c219
-rw-r--r--src/gsm/gsm0808.c262
-rw-r--r--src/gsm/gsm0808_utils.c34
-rw-r--r--src/gsm/gsm23236.c560
-rw-r--r--src/gsm/gsm48.c502
-rw-r--r--src/gsm/gsm48049.c2
-rw-r--r--src/gsm/gsm48_rest_octets.c116
-rw-r--r--src/gsm/gsm_utils.c4
-rw-r--r--src/gsm/gsup.c56
-rw-r--r--src/gsm/i460_mux.c393
-rw-r--r--src/gsm/ipa.c14
-rw-r--r--src/gsm/lapd_core.c1191
-rw-r--r--src/gsm/lapdm.c144
-rw-r--r--src/gsm/libosmogsm.map80
-rw-r--r--src/gsm/rsl.c24
-rw-r--r--src/gsmtap_util.c45
-rw-r--r--src/logging.c161
-rw-r--r--src/logging_systemd.c121
-rw-r--r--src/macaddr.c7
-rw-r--r--src/msgb.c2
-rw-r--r--src/rate_ctr.c21
-rw-r--r--src/select.c271
-rw-r--r--src/serial.c2
-rw-r--r--src/sim/Makefile.am22
-rw-r--r--src/sim/card_fs_hpsim.c76
-rw-r--r--src/sim/card_fs_isim.c49
-rw-r--r--src/sim/card_fs_sim.c2
-rw-r--r--src/sim/card_fs_uicc.c59
-rw-r--r--src/sim/card_fs_usim.c105
-rw-r--r--src/sim/class_tables.c2
-rw-r--r--src/sim/core.c165
-rw-r--r--src/sim/reader.c3
-rw-r--r--src/sim/reader_pcsc.c11
-rw-r--r--src/sim/sim_int.h14
-rw-r--r--src/sockaddr_str.c168
-rw-r--r--src/socket.c843
-rw-r--r--src/stat_item.c29
-rw-r--r--src/stats.c87
-rw-r--r--src/stats_statsd.c20
-rw-r--r--src/tdef.c74
-rw-r--r--src/timer.c22
-rw-r--r--src/usb/Makefile.am21
-rw-r--r--src/usb/osmo_libusb.c764
-rw-r--r--src/use_count.c31
-rw-r--r--src/utils.c444
-rw-r--r--src/vty/Makefile.am4
-rw-r--r--src/vty/command.c791
-rw-r--r--src/vty/cpu_sched_vty.c677
-rw-r--r--src/vty/fsm_vty.c8
-rw-r--r--src/vty/logging_vty.c376
-rw-r--r--src/vty/stats_vty.c139
-rw-r--r--src/vty/talloc_ctx_vty.c6
-rw-r--r--src/vty/tdef_vty.c43
-rw-r--r--src/vty/telnet_interface.c2
-rw-r--r--src/vty/vty.c107
-rw-r--r--src/write_queue.c39
-rw-r--r--tests/Makefile.am76
-rw-r--r--tests/bitgen/bitgen_test.c85
-rw-r--r--tests/bitgen/bitgen_test.ok260
-rw-r--r--tests/bitvec/bitvec_test.c42
-rw-r--r--tests/bitvec/bitvec_test.ok28
-rw-r--r--tests/bsslap/bsslap_test.c103
-rw-r--r--tests/bsslap/bsslap_test.ok7
-rw-r--r--tests/bssmap_le/bssmap_le_test.c177
-rw-r--r--tests/bssmap_le/bssmap_le_test.ok11
-rw-r--r--tests/codec/codec_ecu_fr_test.c88
-rw-r--r--tests/codec/codec_ecu_fr_test.ok219
-rw-r--r--tests/codec/codec_test.c39
-rw-r--r--tests/codec/codec_test.ok13
-rw-r--r--tests/coding/coding_test.c238
-rw-r--r--tests/coding/coding_test.ok34616
-rw-r--r--tests/context/context_test.c84
-rw-r--r--tests/context/context_test.ok4
-rw-r--r--tests/conv/conv_gsm0503_test.ok8
-rw-r--r--tests/dtx/dtx_gsm0503_test.c130
-rw-r--r--tests/dtx/dtx_gsm0503_test.ok11
-rw-r--r--tests/exec/exec_test.c155
-rw-r--r--tests/exec/exec_test.err1
-rw-r--r--tests/exec/exec_test.ok46
-rw-r--r--tests/fsm/fsm_dealloc_test.c71
-rw-r--r--tests/fsm/fsm_dealloc_test.err3694
-rw-r--r--tests/fsm/fsm_test.c3
-rw-r--r--tests/fsm/fsm_test.err88
-rw-r--r--tests/gad/gad_test.c143
-rw-r--r--tests/gad/gad_test.ok8
-rw-r--r--tests/gsm0408/gsm0408_test.c409
-rw-r--r--tests/gsm0408/gsm0408_test.ok51
-rw-r--r--tests/gsm0502/gsm0502_test.c159
-rw-r--r--tests/gsm0502/gsm0502_test.ok271
-rw-r--r--tests/gsm0808/gsm0808_test.c63
-rw-r--r--tests/gsm0808/gsm0808_test.ok6
-rw-r--r--tests/gsm23236/gsm23236_test.c620
-rw-r--r--tests/gsm23236/gsm23236_test.ok514
-rw-r--r--tests/gsm29205/gsm29205_test.c8
-rw-r--r--tests/gsup/gsup_test.c10
-rw-r--r--tests/gsup/gsup_test.err19
-rw-r--r--tests/gsup/gsup_test.ok2
-rw-r--r--tests/i460_mux/i460_mux_test.c398
-rw-r--r--tests/i460_mux/i460_mux_test.ok115
-rw-r--r--tests/lapd/lapd_test.c99
-rw-r--r--tests/lapd/lapd_test.ok46
-rw-r--r--tests/logging/logging_test.c6
-rw-r--r--tests/logging/logging_vty_test.c1
-rw-r--r--tests/logging/logging_vty_test.vty5
-rw-r--r--tests/loggingrb/loggingrb_test.c6
-rw-r--r--tests/sockaddr_str/sockaddr_str_test.c43
-rw-r--r--tests/sockaddr_str/sockaddr_str_test.ok739
-rw-r--r--tests/socket/socket_sctp_test.c233
-rw-r--r--tests/socket/socket_sctp_test.err8
-rw-r--r--tests/socket/socket_sctp_test.ok24
-rw-r--r--tests/socket/socket_test.c264
-rw-r--r--tests/socket/socket_test.err6
-rw-r--r--tests/socket/socket_test.ok26
-rw-r--r--tests/tdef/tdef_test.c30
-rw-r--r--tests/tdef/tdef_test.ok13
-rw-r--r--tests/tdef/tdef_vty_test_config_root.c3
-rw-r--r--tests/tdef/tdef_vty_test_config_root.vty50
-rw-r--r--tests/testsuite.at73
-rw-r--r--tests/use_count/use_count_test.c2
-rw-r--r--tests/utils/utils_test.c853
-rw-r--r--tests/utils/utils_test.ok529
-rw-r--r--tests/vty/fail_cmd_ret_warning.cfg3
-rw-r--r--tests/vty/ok_deprecated_logging.cfg3
-rw-r--r--tests/vty/vty_test.c95
-rw-r--r--tests/vty/vty_test.err67
-rw-r--r--tests/vty/vty_test.ok15
-rw-r--r--tests/vty/vty_transcript_test.c148
-rw-r--r--tests/vty/vty_transcript_test.vty93
-rw-r--r--utils/Makefile.am4
-rw-r--r--utils/conv_codes_gsm.py21
-rw-r--r--utils/conv_gen.py2
-rwxr-xr-x[-rw-r--r--]utils/gsmtap_logread.py0
-rw-r--r--utils/osmo-arfcn.c7
-rw-r--r--utils/osmo-auc-gen.c5
-rw-r--r--utils/osmo-sim-test.c466
287 files changed, 33586 insertions, 38083 deletions
diff --git a/.gitignore b/.gitignore
index 51c443f3..a9dd3039 100644
--- a/.gitignore
+++ b/.gitignore
@@ -95,3 +95,9 @@ tests/conv/gsm0503_test_vectors.c
# vi files
*.sw?
/tests/libsercomstub.a
+
+# code coverage reports
+libosmocore-*-coverage*
+coverage-cobertura.xml
+
+contrib/libosmocore.spec
diff --git a/Makefile.am b/Makefile.am
index 2e73f72e..f2a05a96 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1,13 +1,19 @@
ACLOCAL_AMFLAGS = -I m4
AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include
-SUBDIRS = include src src/vty src/codec src/gsm src/coding src/gb src/ctrl src/sim src/pseudotalloc utils tests
+SUBDIRS = include src src/vty src/codec src/gsm src/coding src/gb src/ctrl src/sim src/pseudotalloc src/usb utils tests
pkgconfigdir = $(libdir)/pkgconfig
pkgconfig_DATA = libosmocore.pc libosmocodec.pc libosmovty.pc libosmogsm.pc \
- libosmogb.pc libosmoctrl.pc libosmocoding.pc libosmosim.pc
+ libosmogb.pc libosmoctrl.pc libosmocoding.pc libosmosim.pc \
+ libosmousb.pc
+
+aclocaldir = $(datadir)/aclocal
+dist_aclocal_DATA = m4/osmo_ac_code_coverage.m4 \
+ m4/osmo_ax_code_coverage.m4
@RELMAKE@
+@CODE_COVERAGE_RULES@
relengdir = $(includedir)
releng_DATA = osmo-release.mk
@@ -22,7 +28,15 @@ $(top_srcdir)/.version:
dist-hook:
echo $(VERSION) > $(distdir)/.tarball-version
-EXTRA_DIST = git-version-gen .version README.md osmo-release.mk osmo-release.sh
+EXTRA_DIST = \
+ .version \
+ README.md \
+ contrib/libosmocore.spec.in \
+ debian \
+ git-version-gen \
+ osmo-release.mk \
+ osmo-release.sh \
+ $(NULL)
HTML = \
$(top_builddir)/doc/core/html/index.html \
@@ -62,6 +76,7 @@ apidoc: $(HTML)
$(top_builddir)/doc/libosmocore.tag.prep: $(top_builddir)/Doxyfile.core \
$(top_srcdir)/include/osmocom/core/*.h \
$(top_srcdir)/src/*.[hc] \
+ $(top_srcdir)/src/crcXXgen.c.tpl \
$(top_srcdir)/src/pseudotalloc/*.[hc]
rm -rf $(top_builddir)/doc/core; mkdir -p $(top_builddir)/doc/core
rm -rf $(top_builddir)/doc/libosmocore.map
diff --git a/README.md b/README.md
index 623804b1..07c1a2af 100644
--- a/README.md
+++ b/README.md
@@ -14,7 +14,7 @@ communications.
There is no clear scope of it. We simply move all shared code between
the various Osmocom projects in this library to avoid code duplication.
-The libosmcoore.git repository build multiple libraries:
+The libosmocore.git repository build multiple libraries:
* **libosmocore** contains some general-purpose functions like select-loop
abstraction, message buffers, timers, linked lists
@@ -28,7 +28,6 @@ The libosmcoore.git repository build multiple libraries:
* **libosmocodec** contains an implementation of GSM voice codecs
* **libosmocoding** contains an implementation of GSM channel coding
* **libosmosim** contains infrastructure to interface SIM/UICC/USIM cards
-* **libosmotrau** contains encoding/decoding functions for A-bis TRAU frames
Homepage
diff --git a/TODO-RELEASE b/TODO-RELEASE
index 665ecf79..7699aff3 100644
--- a/TODO-RELEASE
+++ b/TODO-RELEASE
@@ -7,4 +7,6 @@
# If any interfaces have been added since the last public release: c:r:a + 1.
# If any interfaces have been removed or changed since the last public release: c:r:0.
#library what description / commit summary line
-core osmo_tdef_get() change val_if_not_present arg from unsigned long to long to allow passing -1
+libosmogsm new API gsm0808_create_sapi_reject_cause() with cause argument
+libosmovty ABI change struct cmd_element: add a field for program specific attributes
+libosmovty ABI change struct vty_app_info: optional program specific attributes description
diff --git a/configure.ac b/configure.ac
index 7ad5908d..7de495bc 100644
--- a/configure.ac
+++ b/configure.ac
@@ -24,6 +24,11 @@ LT_INIT([pic-only disable-static])
AC_CONFIG_MACRO_DIR([m4])
+dnl patching ${archive_cmds} to affect generation of file "libtool" to fix linking with clang
+AS_CASE(["$LD"],[*clang*],
+ [AS_CASE(["${host_os}"],
+ [*linux*],[archive_cmds='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib'])])
+
dnl check for pkg-config
dnl * If pkg-config is missing, we get a "syntax error" for PKG_CHECK_MODULES.
dnl Instead, we want to say that pkg-config and pkg.m4 are missing.
@@ -57,7 +62,7 @@ AC_SUBST(LTLDFLAGS_OSMOCTRL)
dnl checks for header files
AC_HEADER_STDC
-AC_CHECK_HEADERS(execinfo.h sys/select.h sys/socket.h sys/timerfd.h syslog.h ctype.h netinet/tcp.h netinet/in.h)
+AC_CHECK_HEADERS(execinfo.h poll.h sys/select.h sys/socket.h sys/signalfd.h sys/timerfd.h syslog.h ctype.h netinet/tcp.h netinet/in.h)
# for src/conv.c
AC_FUNC_ALLOCA
AC_SEARCH_LIBS([dlopen], [dl dld], [LIBRARY_DLOPEN="$LIBS";LIBS=""])
@@ -68,8 +73,13 @@ AC_SUBST(LIBRARY_DLSYM)
AC_CHECK_LIB(execinfo, backtrace, BACKTRACE_LIB=-lexecinfo, BACKTRACE_LIB=)
AC_SUBST(BACKTRACE_LIB)
+# check for pthread (PTHREAD_CFLAGS, PTHREAD_LIBS)
+AX_PTHREAD
+
# check for old glibc < 2.17 to get clock_gettime
-AC_SEARCH_LIBS([clock_gettime], [rt posix4], [LIBRARY_RT="$LIBS";LIBS=""])
+AC_SEARCH_LIBS([clock_gettime], [rt posix4],
+ [AC_DEFINE(HAVE_CLOCK_GETTIME, 1, [Define if clock_gettime is available])
+ LIBRARY_RT="$LIBS";LIBS="";])
AC_SUBST(LIBRARY_RT)
AC_ARG_ENABLE(doxygen,
@@ -95,7 +105,7 @@ AC_COMPILE_IFELSE([AC_LANG_SOURCE([char foo;])],
CFLAGS="$saved_CFLAGS"
AC_SUBST(SYMBOL_VISIBILITY)
-AC_CHECK_FUNCS(clock_gettime localtime_r)
+AC_CHECK_FUNCS(localtime_r)
AC_DEFUN([CHECK_TM_INCLUDES_TM_GMTOFF], [
AC_CACHE_CHECK(
@@ -139,10 +149,24 @@ AC_ARG_ENABLE([pcsc], [AS_HELP_STRING([--disable-pcsc], [Build without PC/SC sup
])
AS_IF([test "x$ENABLE_PCSC" = "xyes"], [
PKG_CHECK_MODULES(PCSC, libpcsclite)
+ AC_DEFINE([HAVE_PCSC],[1],[Build with PC/SC support])
])
AM_CONDITIONAL(ENABLE_PCSC, test "x$ENABLE_PCSC" = "xyes")
AC_SUBST(ENABLE_PCSC)
+AC_ARG_ENABLE([libusb], [AS_HELP_STRING([--disable-libusb], [Build without libusb support])],
+ [
+ ENABLE_LIBUSB=$enableval
+ ],
+ [
+ ENABLE_LIBUSB="yes"
+ ])
+AS_IF([test "x$ENABLE_LIBUSB" = "xyes"], [
+ PKG_CHECK_MODULES(LIBUSB, libusb-1.0)
+])
+AM_CONDITIONAL(ENABLE_LIBUSB, test "x$ENABLE_LIBUSB" = "xyes")
+AC_SUBST(ENABLE_LIBUSB)
+
AC_ARG_ENABLE([gnutls], [AS_HELP_STRING([--disable-gnutls], [Do not use GnuTLS fallback for missing getrandom()])],
[ENABLE_GNUTLS=$enableval], [ENABLE_GNUTLS="yes"])
AM_CONDITIONAL(ENABLE_GNUTLS, test x"$ENABLE_GNUTLS" = x"yes")
@@ -157,6 +181,39 @@ then
AC_DEFINE([USE_GNUTLS], [1], [Use GnuTLS as a fallback for missing getrandom()])
fi
+AC_ARG_ENABLE([systemd_logging],
+ [AS_HELP_STRING(
+ [--enable-systemd-logging],
+ [Build with systemd-journal logging support]
+ )],
+ [systemd_logging=$enableval], [systemd_logging="no"])
+AS_IF([test "x$systemd_logging" = "xyes"], [
+ PKG_CHECK_MODULES(SYSTEMD, libsystemd)
+ AC_DEFINE([ENABLE_SYSTEMD_LOGGING], [1], [Enable systemd-journal logging target])
+])
+AM_CONDITIONAL(ENABLE_SYSTEMD_LOGGING, test "x$systemd_logging" = "xyes")
+AC_SUBST(ENABLE_SYSTEMD_LOGGING)
+
+AC_ARG_ENABLE([libsctp], [AS_HELP_STRING([--disable-libsctp], [Do not enable socket multiaddr APIs requiring libsctp])],
+ [ENABLE_LIBSCTP=$enableval], [ENABLE_LIBSCTP="yes"])
+AM_CONDITIONAL(ENABLE_LIBSCTP, test x"$ENABLE_LIBSCTP" = x"yes")
+AS_IF([test "x$ENABLE_LIBSCTP" = "xyes"], [
+ old_LIBS=$LIBS
+ AC_SEARCH_LIBS([sctp_bindx], [sctp], [
+ AC_DEFINE(HAVE_LIBSCTP, 1, [Define 1 to enable SCTP support])
+ AC_SUBST(HAVE_LIBSCTP, [1])
+ if test -n "$ac_lib"; then
+ AC_SUBST(LIBSCTP_LIBS, [-l$ac_lib])
+ fi
+ ], [
+ AC_MSG_ERROR([sctp_bindx not found in searched libs])])
+ LIBS=$old_LIBS
+])
+
+AC_ARG_ENABLE([sctp-tests], [AS_HELP_STRING([--disable-sctp-tests], [Do not run socket tests requiring system SCTP support])],
+ [ENABLE_SCTP_TESTS=$enableval], [ENABLE_SCTP_TESTS="yes"])
+AM_CONDITIONAL(ENABLE_SCTP_TESTS, test x"$ENABLE_SCTP_TESTS" = x"yes")
+
AC_ARG_ENABLE(plugin,
[AS_HELP_STRING(
[--disable-plugin],
@@ -187,14 +244,24 @@ fi
AC_ARG_ENABLE(bsc_fd_check,
[AS_HELP_STRING(
[--enable-bsc-fd-check],
- [Instrument bsc_register_fd to check that the fd is registered]
+ [Instrument osmo_fd_register to check that the fd is registered]
)],
[fd_check=$enableval], [fd_check="no"])
if test x"$fd_check" = x"no"
then
- AC_DEFINE([BSC_FD_CHECK],[1],[Instrument the bsc_register_fd])
+ AC_DEFINE([OSMO_FD_CHECK],[1],[Instrument the osmo_fd_register])
fi
+AC_ARG_ENABLE([force_io_select],
+ [AS_HELP_STRING(
+ [--enable-force-io-select],
+ [Build with old select I/O instead of poll]
+ )],
+ [force_io_select=$enableval], [force_io_select="no"])
+AS_IF([test "x$force_io_select" = "xyes"], [
+ AC_DEFINE([FORCE_IO_SELECT], [1], [Force the use of select() instaed of poll()])
+])
+
AC_ARG_ENABLE(msgfile,
[AS_HELP_STRING(
[--disable-msgfile],
@@ -250,7 +317,7 @@ AC_ARG_ENABLE(embedded,
)],
[embedded=$enableval], [embedded="no"])
-AM_CONDITIONAL(ENABLE_STATS_TEST, true)
+AM_CONDITIONAL(EMBEDDED, false)
AM_CONDITIONAL(ENABLE_SERCOM_STUB, false)
if test x"$embedded" = x"yes"
@@ -265,10 +332,11 @@ then
AM_CONDITIONAL(ENABLE_UTILITIES, false)
AM_CONDITIONAL(ENABLE_GB, false)
AM_CONDITIONAL(ENABLE_GNUTLS, false)
+ AM_CONDITIONAL(ENABLE_LIBSCTP, false)
AM_CONDITIONAL(ENABLE_PCSC, false)
AM_CONDITIONAL(ENABLE_PSEUDOTALLOC, true)
AM_CONDITIONAL(ENABLE_SERCOM_STUB, true)
- AM_CONDITIONAL(ENABLE_STATS_TEST, false)
+ AM_CONDITIONAL(EMBEDDED, true)
AC_DEFINE([USE_GNUTLS], [0])
AC_DEFINE([PANIC_INFLOOP],[1],[Use infinite loop on panic rather than fprintf/abort])
fi
@@ -337,6 +405,19 @@ else
AM_CONDITIONAL(HAVE_SSE4_1, false)
fi
+AC_ARG_ENABLE(neon,
+ [AS_HELP_STRING(
+ [--enable-neon],
+ [Enable ARM NEON instructions support [default=no]]
+ )],
+ [neon=$enableval], [neon="no"])
+AC_MSG_CHECKING([whether to enable ARM NEON instructions support])
+AC_MSG_RESULT([$neon])
+AM_CONDITIONAL(HAVE_NEON, [test "x$neon" != "xno"])
+
+
+OSMO_AC_CODE_COVERAGE
+
dnl Check if the compiler supports specified GCC's built-in function
AC_DEFUN([CHECK_BUILTIN_SUPPORT], [
AC_CACHE_CHECK(
@@ -376,12 +457,14 @@ AC_OUTPUT(
libosmogb.pc
libosmoctrl.pc
libosmosim.pc
+ libosmousb.pc
include/Makefile
src/Makefile
src/vty/Makefile
src/codec/Makefile
src/coding/Makefile
src/sim/Makefile
+ src/usb/Makefile
src/gsm/Makefile
src/gb/Makefile
src/ctrl/Makefile
@@ -396,4 +479,5 @@ AC_OUTPUT(
Doxyfile.coding
Doxyfile.gb
Doxyfile.ctrl
- Makefile)
+ Makefile
+ contrib/libosmocore.spec)
diff --git a/contrib/jenkins_amd64.sh b/contrib/jenkins_amd64.sh
index c79e26ab..3fee57cf 100755
--- a/contrib/jenkins_amd64.sh
+++ b/contrib/jenkins_amd64.sh
@@ -29,7 +29,7 @@ build .
# do distcheck only once, which is fine from built source tree, since distcheck
# is well separated from the source tree state.
DISTCHECK_CONFIGURE_FLAGS=--enable-external-tests \
- $MAKE distcheck \
+ $MAKE $PARALLEL_MAKE distcheck \
|| cat-testlogs.sh
$MAKE maintainer-clean
diff --git a/contrib/jenkins_arm.sh b/contrib/jenkins_arm.sh
index e3a6cd14..c9cd922e 100755
--- a/contrib/jenkins_arm.sh
+++ b/contrib/jenkins_arm.sh
@@ -18,6 +18,8 @@ build() {
--enable-embedded \
--disable-doxygen \
--disable-shared \
+ --disable-libsctp \
+ --disable-libusb \
--enable-external-tests \
CFLAGS="-Os -ffunction-sections -fdata-sections -nostartfiles -nodefaultlibs $WERROR_FLAGS"
diff --git a/contrib/jenkins_common.sh b/contrib/jenkins_common.sh
index fa1d544b..b86a479a 100644
--- a/contrib/jenkins_common.sh
+++ b/contrib/jenkins_common.sh
@@ -10,7 +10,7 @@ fi
osmo-clean-workspace.sh
-verify_value_string_arrays_are_terminated.py $(find . -name "*.[hc]")
+verify_value_string_arrays_are_terminated.py
prep_build() {
_src_dir="$1"
diff --git a/contrib/libosmocore.spec.in b/contrib/libosmocore.spec.in
new file mode 100644
index 00000000..fb45516e
--- /dev/null
+++ b/contrib/libosmocore.spec.in
@@ -0,0 +1,454 @@
+#
+# spec file for package libosmocore
+#
+# Copyright (c) 2018 SUSE LINUX GmbH, Nuernberg, Germany.
+#
+# All modifications and additions to the file contributed by third parties
+# remain the property of their copyright owners, unless otherwise agreed
+# upon. The license for this file, and modifications and additions to the
+# file, is the same license as for the pristine package itself (unless the
+# license for the pristine package is not an Open Source License, in which
+# case the license is the MIT License). An "Open Source License" is a
+# license that conforms to the Open Source Definition (Version 1.9)
+# published by the Open Source Initiative.
+
+Name: libosmocore
+Version: @VERSION@
+Release: 0
+Summary: The Open Source Mobile Communications Core Library
+License: GPL-2.0-only AND GPL-2.0-or-later AND LGPL-2.1-or-later AND AGPL-3.0-or-later
+Group: Productivity/Telephony/Utilities
+Url: https://osmocom.org/projects/libosmocore/wiki/Libosmocore
+Source: %name-%version.tar.xz
+BuildRequires: automake >= 1.6
+BuildRequires: libtool >= 2
+BuildRequires: lksctp-tools-devel
+BuildRequires: pkg-config >= 0.20
+BuildRequires: python3
+BuildRequires: xz
+BuildRequires: pkgconfig(gnutls) >= 2.12.0
+BuildRequires: pkgconfig(libpcsclite)
+BuildRequires: pkgconfig(libusb-1.0)
+BuildRequires: pkgconfig(talloc) >= 2.0.1
+
+%description
+libosmocore is a package with various utility functions that were
+originally developed as part of the OpenBSC project, but which are of
+a more generic nature and thus useful to (at least) other programs
+that Osmocom develops w.r.t. mobile communications.
+
+There is no clear scope of it. It simply houses all code shared
+between OsmocomBB and OpenBSC to avoid code duplication.
+
+%package tools
+Summary: GSM utilities from the osmocore project
+License: GPL-2.0-only AND GPL-2.0-or-later AND LGPL-3.0-or-later AND AGPL-3.0-or-later
+Group: Productivity/Telephony/Utilities
+Provides: %name-utils = %version-%release
+
+%description tools
+libosmocore is a package with various utility functions that were
+originally developed as part of the OpenBSC project.
+
+This package contains a program for frequency calculation for GSM
+called "osmo-arfcn", and a program called "osmo-auc-gen" that is used
+for testing GSM authentication.
+
+%package -n libosmocodec0
+Summary: GSM 06.10, 06.20, 06.60, 06.90 codec library
+License: GPL-2.0-or-later
+Group: System/Libraries
+
+%description -n libosmocodec0
+The libosmocodec library contains an implementation of multiple
+GSM codecs:
+
+* GSM 06.10 Full Rate (FR) codec
+* GSM 06.20 Half Rate (HR) codec
+* GSM 06.60 Enhanced Full Range (EFR) codec
+* GSM 06.90 Adaptive Multi-Rate (AMR) codec
+
+%package -n libosmocodec-devel
+Summary: Development files for the Osmocom GSM codec library
+License: GPL-2.0-or-later
+Group: Development/Libraries/C and C++
+Requires: libosmocodec0 = %version
+
+%description -n libosmocodec-devel
+The libosmocodec library contains an implementation of multiple
+GSM codecs.
+
+This subpackage contains libraries and header files for developing
+applications that want to make use of libosmocodec.
+
+%package -n libosmocoding0
+Summary: GSM/GPRS/EDGE transcoding routines library
+License: GPL-2.0-or-later
+Group: System/Libraries
+
+%description -n libosmocoding0
+libosmocoding is a library which provides GSM, GPRS and EDGE
+transcoding routines.
+
+The following data types are currently supported: xCCH, PDTCH (CS 1-4
+and MCS 1-9), TCH/FR, TCH/HR, TCH/AFS, RCH/AHS, RACH and SCH.
+
+%package -n libosmocoding-devel
+Summary: Development files for the Osmocom transcoding library
+License: GPL-2.0-or-later
+Group: Development/Libraries/C and C++
+Requires: libosmocodec-devel = %version
+Requires: libosmocoding0 = %version
+Requires: libosmocore-devel = %version
+Requires: libosmogsm-devel = %version
+
+%description -n libosmocoding-devel
+libosmocoding is a library which provides GSM, GPRS and EDGE
+transcoding routines.
+
+This subpackage contains libraries and header files for developing
+applications that want to make use of libosmocoding.
+
+%package -n libosmocore16
+Summary: Osmocom core library
+# crc16.c has GPL2-only clauses, the rest (*.c) is GPL-2.0+
+License: GPL-2.0-only AND GPL-2.0-or-later
+Group: System/Libraries
+
+%description -n libosmocore16
+libosmocore is a library with various utility functions shared
+between OpenBSC and OsmocomBB.
+
+%package -n libosmocore-devel
+Summary: Development files for the Osmocom core library
+# crc16.h has GPL2-only clauses, the rest (*.h) is GPL-2.0+
+License: GPL-2.0-only AND GPL-2.0-or-later
+Group: Development/Libraries/C and C++
+Requires: libosmocore16 = %version
+Requires: libtalloc-devel
+
+%description -n libosmocore-devel
+libosmocore is a library with various utility functions shared
+between OpenBSC and OsmocomBB.
+
+This subpackage contains libraries and header files for developing
+applications that want to make use of libosmocore.
+
+%package -n libosmoctrl0
+Summary: Osmocom SNMP-like control interface library
+License: GPL-2.0-or-later
+Group: System/Libraries
+
+%description -n libosmoctrl0
+libosmocore is a package with various utility functions that were
+originally developed as part of the OpenBSC project.
+
+libosmoctrl is an SNMP-like control interface. In contrast to the VTY
+interface, the control interface is meant to be used by programs.
+
+%package -n libosmoctrl-devel
+Summary: Osmocom control interface library
+License: GPL-2.0-or-later
+Group: Development/Libraries/C and C++
+Requires: libosmocore-devel = %version
+Requires: libosmoctrl0 = %version
+Requires: libosmogsm-devel = %version
+
+%description -n libosmoctrl-devel
+libosmoctrl is an SNMP-like control interface. In contrast to the VTY
+interface, the control interface is meant to be used by programs.
+
+This subpackage contains libraries and header files for developing
+applications that want to make use of libosmoctrl.
+
+%package -n libosmogb11
+Summary: Osmocom GPRS Gb Interface (NS/BSSGP) library
+License: AGPL-3.0-or-later
+Group: System/Libraries
+
+%description -n libosmogb11
+libosmocore is a package with various utility functions that were
+originally developed as part of the OpenBSC project.
+
+The libosmogb library contains a GPRS BSSGP protocol implementation.
+
+%package -n libosmogb-devel
+Summary: Development files for the Osmocom GPRS Gb interface library
+License: AGPL-3.0-or-later
+Group: Development/Libraries/C and C++
+Requires: libosmocore-devel = %version
+Requires: libosmogb11 = %version
+Requires: libosmovty-devel = %version
+
+%description -n libosmogb-devel
+The libosmogb library contains a GPRS BSSGP protocol implementation.
+
+This subpackage contains libraries and header files for developing
+applications that want to make use of libosmogb.
+
+%package -n libosmogsm15
+Summary: Osmocom GSM utility library
+License: GPL-2.0-or-later AND AGPL-3.0-or-later
+Group: System/Libraries
+
+%description -n libosmogsm15
+libosmocore is a package with various utility functions that were
+originally developed as part of the OpenBSC project.
+
+The libosmogsm library in particular is a collection of common code
+used in various GSM related sub-projects inside the Osmocom family of
+projects. It includes A5/1 and A5/2 ciphers, COMP128v1, a LAPDm
+implementation, a GSM TLV parser, SMS utility routines as well as
+protocol definitions for a series of protocols.
+
+%package -n libosmogsm-devel
+Summary: Development files for the Osmocom GSM utility library
+License: GPL-2.0-or-later AND AGPL-3.0-or-later
+Group: Development/Libraries/C and C++
+Requires: libosmocore-devel = %version
+Requires: libosmogsm15 = %version
+
+%description -n libosmogsm-devel
+The libosmogsm library in particular is a collection of common code
+used in various GSM related sub-projects inside the Osmocom family of
+projects. It includes A5/1 and A5/2 ciphers, COMP128v1, a LAPDm
+implementation, a GSM TLV parser, SMS utility routines as well as
+protocol definitions for a series of protocols.
+
+This subpackage contains libraries and header files for developing
+applications that want to make use of libosmogsm.
+
+%package -n libosmosim2
+Summary: Osmocom SIM card related utility library
+License: GPL-2.0-or-later
+Group: System/Libraries
+
+%description -n libosmosim2
+libosmocore is a package with various utility functions that were
+originally developed as part of the OpenBSC project.
+
+The libosmosim library in particular contains routines for SIM card
+access.
+
+%package -n libosmosim-devel
+Summary: Development files for the Osmocom SIM card utility library
+License: GPL-2.0-or-later
+Group: Development/Libraries/C and C++
+Requires: libosmocore-devel = %version
+Requires: libosmosim2 = %version
+
+%description -n libosmosim-devel
+The libosmosim library in particular contains routines for SIM card
+access.
+
+This subpackage contains libraries and header files for developing
+applications that want to make use of libosmosim.
+
+%package -n libosmovty4
+Summary: Osmocom VTY interface library
+License: GPL-2.0-or-later
+Group: System/Libraries
+
+%description -n libosmovty4
+libosmocore is a package with various utility functions that were
+originally developed as part of the OpenBSC project.
+
+The libosmovty library implements the interactive command-line on the
+VTY (Virtual TTY), as well as configuration file parsing.
+
+%package -n libosmovty-devel
+Summary: Development files for the Osmocom VTY interface library
+License: GPL-2.0-or-later
+Group: Development/Libraries/C and C++
+Requires: libosmocore-devel = %version
+Requires: libosmovty4 = %version
+
+%description -n libosmovty-devel
+The libosmovty library implements the interactive command-line on the
+VTY (Virtual TTY), as well as configuration file parsing.
+
+This subpackage contains libraries and header files for developing
+applications that want to make use of libosmovty.
+
+%package -n libosmousb0
+Summary: Osmocom USB library
+License: GPL-2.0-or-later
+Group: System/Libraries
+
+%description -n libosmousb0
+libosmocore is a package with various utility functions that were
+originally developed as part of the OpenBSC project.
+
+The libosmosub library in particular contains routines for USB device
+access via libusb-1.0, integrated into the libosmocore select event loop.
+
+%package -n libosmousb-devel
+Summary: Development files for the Osmocom USB library
+License: GPL-2.0-or-later
+Group: Development/Libraries/C and C++
+Requires: libosmocore-devel = %version
+Requires: libosmousb0 = %version
+Requires: pkgconfig(libusb-1.0)
+
+%description -n libosmousb-devel
+The libosmosub library in particular contains routines for USB device
+access via libusb-1.0, integrated into the libosmocore select event loop.
+
+This subpackage contains libraries and header files for developing
+applications that want to make use of libosmousb.
+
+
+%prep
+%setup -q
+
+%build
+echo "%version" >.tarball-version
+autoreconf -fiv
+%configure --enable-shared --disable-static \
+ --includedir="%_includedir/%name"
+make %{?_smp_mflags} V=1
+
+%install
+b="%buildroot"
+make %{?_smp_mflags} install DESTDIR="$b"
+find "$b/%_libdir" -type f -name "*.la" -delete
+
+%check
+make %{?_smp_mflags} check || (find . -name testsuite.log -exec cat {} +)
+
+%post -n libosmocodec0 -p /sbin/ldconfig
+%postun -n libosmocodec0 -p /sbin/ldconfig
+%post -n libosmocoding0 -p /sbin/ldconfig
+%postun -n libosmocoding0 -p /sbin/ldconfig
+%post -n libosmocore16 -p /sbin/ldconfig
+%postun -n libosmocore16 -p /sbin/ldconfig
+%post -n libosmoctrl0 -p /sbin/ldconfig
+%postun -n libosmoctrl0 -p /sbin/ldconfig
+%post -n libosmogb11 -p /sbin/ldconfig
+%postun -n libosmogb11 -p /sbin/ldconfig
+%post -n libosmogsm15 -p /sbin/ldconfig
+%postun -n libosmogsm15 -p /sbin/ldconfig
+%post -n libosmosim2 -p /sbin/ldconfig
+%postun -n libosmosim2 -p /sbin/ldconfig
+%post -n libosmovty4 -p /sbin/ldconfig
+%postun -n libosmovty4 -p /sbin/ldconfig
+%post -n libosmousb0 -p /sbin/ldconfig
+%postun -n libosmousb0 -p /sbin/ldconfig
+
+%files tools
+%defattr(-,root,root)
+%_bindir/osmo-*
+
+%files -n libosmocodec0
+%defattr(-,root,root)
+%_libdir/libosmocodec.so.0*
+
+%files -n libosmocodec-devel
+%defattr(-,root,root)
+%dir %_includedir/%name
+%dir %_includedir/%name/osmocom
+%_includedir/%name/osmocom/codec/
+%_libdir/libosmocodec.so
+%_libdir/pkgconfig/libosmocodec.pc
+
+%files -n libosmocoding0
+%defattr(-,root,root)
+%_libdir/libosmocoding.so.0*
+
+%files -n libosmocoding-devel
+%defattr(-,root,root)
+%dir %_includedir/%name
+%dir %_includedir/%name/osmocom
+%_includedir/%name/osmocom/coding/
+%_libdir/libosmocoding.so
+%_libdir/pkgconfig/libosmocoding.pc
+
+%files -n libosmocore16
+%defattr(-,root,root)
+%_libdir/libosmocore.so.16*
+
+%files -n libosmocore-devel
+%defattr(-,root,root)
+%dir %_includedir/%name
+%dir %_includedir/%name/osmocom
+%_includedir/%name/osmocom/core/
+%_libdir/libosmocore.so
+%_libdir/pkgconfig/libosmocore.pc
+%_datadir/aclocal/osmo_ax_code_coverage.m4
+%_datadir/aclocal/osmo_ac_code_coverage.m4
+
+%files -n libosmoctrl0
+%defattr(-,root,root)
+%_libdir/libosmoctrl.so.0*
+
+%files -n libosmoctrl-devel
+%defattr(-,root,root)
+%dir %_includedir/%name
+%dir %_includedir/%name/osmocom
+%_includedir/%name/osmocom/ctrl/
+%_libdir/libosmoctrl.so
+%_libdir/pkgconfig/libosmoctrl.pc
+
+%files -n libosmogb11
+%defattr(-,root,root)
+%_libdir/libosmogb.so.11*
+
+%files -n libosmogb-devel
+%defattr(-,root,root)
+%dir %_includedir/%name
+%dir %_includedir/%name/osmocom
+%_includedir/%name/osmocom/gprs/
+%_libdir/libosmogb.so
+%_libdir/pkgconfig/libosmogb.pc
+
+%files -n libosmogsm15
+%defattr(-,root,root)
+%_libdir/libosmogsm.so.15*
+
+%files -n libosmogsm-devel
+%defattr(-,root,root)
+%dir %_includedir/%name
+%dir %_includedir/%name/osmocom
+%_includedir/%name/osmocom/gsm/
+%_includedir/%name/osmocom/crypt/
+%_libdir/libosmogsm.so
+%_libdir/pkgconfig/libosmogsm.pc
+
+%files -n libosmosim2
+%defattr(-,root,root)
+%_libdir/libosmosim.so.2*
+
+%files -n libosmosim-devel
+%defattr(-,root,root)
+%dir %_includedir/%name
+%dir %_includedir/%name/osmocom/
+%_includedir/%name/osmocom/sim/
+%_libdir/libosmosim.so
+%_libdir/pkgconfig/libosmosim.pc
+
+%files -n libosmovty4
+%defattr(-,root,root)
+%_libdir/libosmovty.so.4*
+
+%files -n libosmovty-devel
+%defattr(-,root,root)
+%dir %_includedir/%name
+%dir %_includedir/%name/osmocom
+%_includedir/%name/osmocom/vty/
+%_includedir/%name/osmo-release.mk
+%_libdir/libosmovty.so
+%_libdir/pkgconfig/libosmovty.pc
+
+%files -n libosmousb0
+%defattr(-,root,root)
+%_libdir/libosmousb.so.0*
+
+%files -n libosmousb-devel
+%defattr(-,root,root)
+%dir %_includedir/%name
+%dir %_includedir/%name/osmocom
+%_includedir/%name/osmocom/usb/
+%_libdir/libosmousb.so
+%_libdir/pkgconfig/libosmousb.pc
+
+%changelog
diff --git a/contrib/struct_endianess.py b/contrib/struct_endianess.py
index be73fbe2..6ce75fcb 100755
--- a/contrib/struct_endianess.py
+++ b/contrib/struct_endianess.py
@@ -17,6 +17,7 @@ re_struct_end = re.compile(r'^}[^;]*;\s*$')
re_substruct_start = re.compile(r'^\s+struct\s*{\s*$')
re_substruct_end = re.compile(r'^\s+}\s*([^;]*\s)[a-zA-Z_][a-zA-Z_0-9]*\s*;\s*$')
+re_unnamed_substruct_end = re.compile(r'^\s+}\s*;\s*$')
re_int_def = re.compile(r'(^\s*((const|unsigned|signed|char|int|long|int[0-9]+_t|uint[0-9]_t)\s+)+\s*)([^;]*;)',
re.DOTALL | re.MULTILINE)
@@ -73,7 +74,8 @@ def section_struct_body(struct_body_lines):
line = struct_body_lines[j]
if (re_substruct_start.fullmatch(line)
- or re_substruct_end.fullmatch(line)):
+ or re_substruct_end.fullmatch(line)
+ or re_unnamed_substruct_end.fullmatch(line)):
end_def()
arbitrary_part.append(line)
j += 1
diff --git a/debian/changelog b/debian/changelog
index 0e280896..317fefe2 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,341 @@
+libosmocore (1.4.0) unstable; urgency=medium
+
+ [ Pau Espin Pedrol ]
+ * bitvec: Fix return value in doc for bitvec_write_field()
+ * gb: Fix typo in bssgp_cause_strings description
+ * logging_vty.c: Avoid acquiring log tgt lock in logging level cmd when not needed
+ * value_string: Switch val from unsigned to int
+ * gsm0503_coding: Fix misleading comment UL vs DL
+ * gsm0503_coding: Fix USF encoding in MCS1-4
+ * gsm0503_tables: Document USF encoding tables
+ * tests/coding: Test decoding of DL EGPRS data packet
+ * tests/coding_test: Fix test_pdtch() results
+ * gsm_04_06.h: Remove repeated egprs header struct definitions
+ * configure.ac: Fix HAVE_CLOCK_GETTIME undef when func in -lrt
+ * Drop old BSC references in fd check configure option
+ * Revert "gsmtap_makemsg_ex: NULL for unknown chan_type"
+ * use_count.h: Fix API doc example
+ * tests/fsm_test.c: Disable use color in logging output
+ * tests: vty: Extend test to do some numeric range validations
+ * vty: Allow 64 bit values in numeric ranges if system supports it
+ * vty: Don't match negative values on purely positive ranges
+ * vty: Introduce support to set cpu-affinity and scheduler policy
+
+ [ Harald Welte ]
+ * usb: Use OSMO_STRLCPY where appropriate
+ * usb: Add osmo_libusb_find_matching_dev_{path,serial}
+ * osmo_libusb: Check return of libusb_get_string_descriptor_ascii()
+ * osmo-sim-test: Recurse through subdirectories
+ * osmo-sim-test: Fall-back to classic SIM
+ * chantype_rsl2gsmtap(): Add entries for CBCH
+ * gsmtap.h: Add definitions for voice inside GSMTAP
+ * Add GSMTAP_CHANNEL_VOICE to gsmtap_gsm_channel_names[] value_string
+ * Add CTRL port number for osmo-mgw
+ * gsmtap: Solve TCH / FACCH confusion once and for all
+ * fixup depreciation warning
+ * socket: Add osmo_sock_mcast_iface_set() to bind multicast to device
+ * libosmosim: Build irrespective of PC/SC support
+ * sim: card_fs_usim.c: Fix FID of EF.EXT4
+ * card_fs_sim: Avoid '/' in file names
+ * osmo-sim-test: Use stderr for error messages
+ * osmo-sim-test: don't print SW in successful case of dump_file()
+ * osmo-sim-test: Optionally dump card files to host filesystem
+ * sim: add osim_file_desc_find_aid()
+ * sim: re-structure how we support cards + applications
+ * osmo-sim-test: Also [attempt to] dump DF.GSM on USIM cards
+ * card_fs_{usim,isim}: Update to 15.7.0 / Release 15
+ * sim: Add HPSIM application support
+ * osmo-sim-test: Avoid double-close
+ * ports.h: Add 4268 for UECUPS VTY
+ * exec: Introduce osmo_system_nowait2() to allow specify a user
+ * select.c: Introduce support for signalfd
+ * timerfd: call osmo_fd_unregister() when closing on read error
+ * gsm0503_parity: Fix compilation with gcc-10
+ * gsm_29_118.h: Fix compilation with gcc-10
+ * sim: When decoding SW, take application specific SW into account
+ * README.md: fix typo (coore -> core)
+ * README.md: We don't build libosmotrau. The latter is in libosmo-abis.git
+ * usb: Add osmo_libusb_find_open_claim() all-in-one API
+ * codec: Add functions for AMR s->d bits and d->s bits
+ * libosmogsm: add Doxygen docs for gsm0502_hop_seq_gen()
+ * Implement ITU-T I.460 multiplex / demultiplex
+ * NS: Optionally disable NS-{RESET,BLOCK,UNBLOCK} when using UDP/IP
+ * NS: replace use of gprs_nsvc_create() with gprs_nsvc_crate2()
+ * lapd/lapdm: print user-defined string name instead of (dl=%p)
+ * lapd_core: Fix log line being about LAPD and not LAPDm
+ * Revert "add osmo_mobile_identity API"
+ * bts_features.h: Introduce BTS_FEAT_PAGING_COORDINATION
+ * gsm0808: Add gsm0808_create_common_id()
+ * gprs_bssgp: Add bssgp_tx_bvc_reset2()
+ * gprs_ns: Set sockaddr_in.sin_family for persistent NSVCs
+ * vty/ports.h: Add VTY port for osmo-e1d
+ * lapd_core: Ensure we always have some tailroom
+ * lapd_core: After calling into L3, check if the state has changed
+ * vty: Avoid ultra-long multi-line strings cluttering talloc reports
+ * gsm0411_{smc,smr}.c: Work around newlib bug
+ * bits.c: Use faster look-up-table approach for osmo_revbytebits_{buf,u8}
+ * i460: Add back-pointer from sub-channel to timeslot
+ * i460: pass more context to call-back functions
+ * i460: Fix bit- and subslots ordering of I.460 mux + demux
+
+ [ Neels Hofmeyr ]
+ * add crcXXgen.c.tpl to EXTRA_DIST
+ * jenkins.sh: simpler invocation of verify_value_string_...
+ * gsm_04_08.h: fix big endian structs
+ * add missing endian.h in gsm_23_041.h
+ * struct_endianess.py: also recognise unnamed substructs
+ * cosmetic: apply changes to match struct_endianess.py output
+ * enable vty xml dumping to stdout
+ * api doc: clarify 'returns' of gsm48_mi_to_string()
+ * api doc: clarify OSMO_NAME_C_IMPL() required FUNC_BUF signature
+ * fix osmo_mi_name_c() to always return talloced strings, via osmo_mi_name_buf()
+ * add gsm23236: MSC pooling: TMSI and NRI utility functions
+ * tlv.h: add msgb_tvl_put() to add a TvLV without the value part
+ * osmo_bcd2str: also validate start_nibble parameter
+ * add osmo_mobile_identity API
+ * add osmo_mobile_identity API
+ * gsm0408_test: allow deprecated API
+ * api comment: fix example of osmo_mobile_identity_encode_msgb
+ * fixup for gsm0808_create_common_id(): add API doc, use new MI API
+
+ [ Philipp Maier ]
+ * l1sap: add measurement related struct members
+ * osmo-sim-test: check tlv_parsed struct tp before access
+ * parity: add amr crc14 definition
+ * conv: add convolutional coder for AMR SID UPDATE frames
+ * gsm690: Fix amr speech bit length table
+ * dtx: add decoding for AMR-DTX frames
+ * exec: osmo_system_nowait2: initalize *pw pointer with NULL
+ * logging: use LOGL_NOTICE when no loglevel is set
+ * logging: do not allow multiple calls of log_init()
+ * gsm0505_amr_dtx: add missing value strings
+ * gsm0808: fix endieness of call identifier
+ * i460_mux: correctly reset subchannels
+ * gsm_08_58: add missing RSL error cause codes
+ * i460_mux: add callback to notify empty tx queue
+
+ [ Vadim Yanitskiy ]
+ * usb/Makefile.am: fix copy-pasted library name: s/libosmosim/libosmousb/
+ * gsm/gsm48049.c: fix use of GNU 'missing =' extension in designator
+ * tdef_vty: do not enforce enum 'node_type' in osmo_tdef_vty_groups_init()
+ * conv: prevent theoretical NULL pointer dereference in osmo_conv_encode()
+ * osmo_libusb: check return value of osmo_fd_register()
+ * exec: prevent uninitialized memory access in osmo_system_nowait()
+ * exec: propogate errors from osmo_environment_[filter|append]
+ * bitvec: make bitvec_free() safe against NULL
+ * tests/bitvec: add a unit test for bitvec_read_field()
+ * bitvec: fix bitvec_unhex(): do not return 1 on success
+ * bitvec: fix misleading description of bitvec_spare_padding()
+ * bitvec: cosmetic: init i only once in bitvec_[un]pack()
+ * bitvec: avoid redundant zero-initialization in bitvec_alloc()
+ * tests/coding: check return value of encoding / decoding functions
+ * tests/coding: reduce verbosity of 8-bit / 11-bit RACH coding tests
+ * tests/coding: cosmetic: use ARRAY_SIZE() macro from utils.h
+ * coding: fix documentation of PDTCH encoding functions
+ * tests/coding: add 11-bit Access Burst samples from a real phone
+ * coding: fix bit ordering in 11-bit RACH coding functions
+ * rest_octets: fix encoding of 3G Early Classmark Sending Restriction
+ * libosmogsm: cosmetic: add spaces before and after PRIu32
+ * bts_features: fix: properly check the result of bitvec_get_bit_pos()
+ * bts_features: introduce osmo_bts_unset_feature()
+ * gsm0502: add TDMA frame number constants and modular arithmetic
+ * utils/gsmtap_logread.py: make it executable
+ * src/Makefile.am: add conv_acc_neon_impl.h to EXTRA_DIST
+ * configure.ac: clarify description of --enable-neon
+ * configure.ac: fix: do not define HAVE_NEON unconditionally
+ * configure.ac: print ARM NEON instructions support status
+
+ [ Eric Wild ]
+ * pcsc: don't leak memory
+
+ [ Alexander Chemeris ]
+ * gb: Fix typos in gprs_ns.c comments
+ * gb: Print signalling and data weights on NS-VC creation.
+ * select: Fix typo in a comment Osmcoom->Osmocom
+ * stats: Move cfg_stats_interval_cmd() function.
+ * stats: Fix documentation for osmo_stats_set_interval()
+ * stats: Support regular stats flush
+ * stats: Change timer to timerfd to make it a true interval timer.
+ * gsm0808: Fix encoding of the SAPI_N_REJECT BSSMAP message.
+ * gsm0808: Make a function to extract Cause IE publicly available.
+ * gsm0808_utils: Fix gsm0808_cause_class() function
+ * gsm0808_utils: Add gsm0808_get_cipher_reject_cause() back with a deprecation notice.
+ * gsm0808: Implement helper functions for CONFUSION BSSMAP message decoding.
+
+ [ Maksim Aristov ]
+ * debian: Change python3 dependency to native arch
+
+ [ Eric ]
+ * configure.ac: fix libtool issue with clang and sanitizer
+ * timer.c: make timers thread safe
+ * pkgconfig/osmocodec/osmocoding: link to talloc
+ * libomsocoding: NEON viterbi acceleration
+
+ [ Kirill Zakharenko ]
+ * statsd: fix rendering for groups with idx==0
+
+ [ Sylvain Munaut ]
+ * libosmogsm: import hopping sequence generation code
+
+ [ Oliver Smith ]
+ * contrib: import RPM spec
+ * gsmtap_makemsg_ex: NULL for unknown chan_type
+ * contrib: integrate RPM spec
+ * Makefile.am: EXTRA_DIST: debian, contrib/*.spec.in
+
+ [ Alexander Couzens ]
+ * gprs_ns.h: add missing prototype gprs_ns_rcvmsg()
+ * gprs_ns: fix typo in comment
+ * gprs_ns_frgre.h: add missing declaration of structs
+ * bts_features: add feature BTS_FEAT_IPV6_NSVC
+ * socket: osmo_sock_local_ip: correct doxygen comment
+ * vty: add a define VTY_IPV46_CMD to require a IPv4/6 address
+ * Gb/BSSGP: replace hardcoded Tx into NS library by a callback
+ * gsm 12.21: add osmocom specific NM_ATT_OSMO_NS_LINK_CFG
+ * osmo_sock_init2: improve support for AF_UNSPEC
+ * socket.h: introduce osmo_sockaddr to hold v4 and v6 endpoints
+
+ [ Daniel Willmann ]
+ * rate_ctr: Add functions to reset rate counter (groups)
+ * stat_item: Add function to reset stat items and groups
+ * stats: Add stats commands related to testing
+
+ -- Harald Welte <laforge@osmocom.org> Thu, 13 Aug 2020 11:06:35 +0200
+
+libosmocore (1.3.0) unstable; urgency=medium
+
+ [ Pau Espin Pedrol ]
+ * osmo-release.sh: Add DRY_RUN mode
+ * osmo-release.sh: Verify debian/rules dh_strip lines match LIBVERSION
+ * osmo-release.sh: Verify consistency of dependency versions in configure.ac and debian/control
+ * osmo-release.sh: Check patches under debian/patches apply
+ * osmo-release.sh: Support releasing openbsc.git
+ * vty: Register logp cmd next to logging commands
+ * tdef: Introduce API osmo_tdef_set()
+ * tdef_vty.h: Add missing header dependencies
+ * logging_internal.h: Fix osmo_log_info definition
+ * osmo-release.sh: update TODO-RELEASE for non-lib projects too
+ * logging: Move extern declaration of osmo_log_target_list from logging.h to logging_internal.h
+ * msgb: Allow size==headroom in msgb_alloc_headroom*()
+ * tdef: Introduce min_val and max_val fields
+ * tdef_test: verify case where osmo_tdef_set returns -EEXIST
+ * vty: Optionally Set/replace cfg file during cmd 'write file'
+ * logging: Introduce mutex API to manage log_target in multi-thread envs
+ * socket.c: Move glibc workarounds to same place in addrinfo_helper()
+ * vty: Fix go_parent_cb not called for indented nodes at end of cfg file
+ * tdef: Return correct snprintf value for osmo_tdef_range_str_buf()
+ * socket: Introduce API osmo_sock_init2_multiaddr()
+ * socket: Remove unneeded condition check in osmo_sock_init2_multiaddr()
+ * libosmocore.pc.in: Append -lsctp to Libs.private
+ * socket.c: build multiaddr socket API helpers only if used by public APIs
+ * configure: Introduce --disable-libsctp and error by default if libsctp not found
+ * vty: Return error if cmd returns CMD_WARNING while reading cfg file
+ * cosmetic: gsm_04_08.h: Fix trailing whitespace
+ * gsm_04_08.h: Introduce API osmo_gsm48_rfpowercap2powerclass()
+ * gsm: Fix compilation error under some compilers
+ * gsm: gsm_utils: Fix return type of API ms_class_gmsk_dbm() and add unit tests
+ * gsm: gsm_04_08.h: Allow accessing classmark2 as struct instead of uint32_t
+ * Introduce fields related to DTAP DLCI
+ * osmo-release.sh: Use set -e before applying changes to prepare release
+ * osmo-release.sh: Improve of PKG_CHECK_MODULES from configure.ac
+ * Drop empty file debian/patches/series
+
+ [ Harald Welte ]
+ * codec/ecu_fr: Mark input TCH frame as 'const' as we only read it
+ * context: Add support for [per-thread] global talloc contexts
+ * cbsp: Fix endless loop iteration when decoding cell list IEs
+ * cbsp: Remove printf() statement from early development/debugging
+ * cbsp: Fix decoding of WRITE-REPLACE payload
+ * codec/ecu: Introduce new generic Error Concealment Unit abstraction
+ * gsm_08_58: Add vendor-specific Message Type for ETWS Primary Warning
+ * Introduce BTS_FEAT_ETWS_PN for communicating ETWS PN capability
+ * sim/class_tables: Fix typo in comment
+ * cosmetic: clarify c_iflag in osmo_serial_init()
+ * select: Make file descriptor lists per-thread
+ * 04.80: Deprecate gsm0480_create_ussd_resp()
+ * Check for osmo_fsm_register() error return value
+ * gprs_ns_instantiate(): propagate errors from gprs_sns_init() to caller
+ * osmo-arfcn: Fix '-h' option
+ * utils: exit(2) on unsupported positional arguments on command line
+ * gsup: Introduce OSMO_GSUP_NUM_VECTORS_REQ_IE
+ * gprs_bssgp: Work around gcc-9 claiming "error=stringop-overflow"
+ * libosmocore libusb integration
+ * usb: Import a variety of libusb utility functions from simtrace
+ * debian/control: Add missing libusb-1.0-0-dev dependency
+ * Introduce helper functions for safe fork+exec of processes
+
+ [ Neels Hofmeyr ]
+ * add vty logp command to echo on all log targets
+ * osmo_tdef_get(): allow passing -1 as default timeout
+ * fix: vty crash by logging during VTY_CLOSED event handling
+ * OSMO_SOCKADDR_STR_FMT_ARGS: remove useless condition
+ * OSMO_SOCKADDR_STR_FMT_ARGS: guard against NULL pointer
+ * tdef: fixup osmo_tdef_set()
+ * gsup: add OSMO_GSUP_SUPPORTED_RAT_TYPES_IE and OSMO_GSUP_CURRENT_RAT_TYPE_IE
+ * API doc tweaks (mncc.h, gsm_08_08.h)
+ * add osmo_fsm_set_dealloc_ctx(), to help with use-after-free
+ * fsm: refuse state chg and events after term
+ * add osmo_sockaddr_str_is_nonzero()
+ * test: add OSMO_SOCKADDR_STR_FMT to sockaddr_str_test.c
+ * fix OSMO_SOCKADDR_STR_FMT for IPv6
+ * add osmo_sockaddr_str_cmp()
+ * utils.c: fix various inaccurate API doc about return values
+ * logging.h: define ansi color constants
+ * fix DLSMS logging category color: '[1:38m' isn't actually defined
+ * cosmetic: logging.h: fix comment s/levels/subsystems
+ * osmo_sockaddr_str: API doc: fix 32bit addr mixup of host/network byte order
+ * utils.h: add OSMO_NAME_C_IMPL() macro
+ * fix osmo_escape_str_c() and osmo_quote_str_c()
+ * GSUP: rename E_ROUTING_ERROR to ROUTING_ERROR
+ * fsm.h: add missing include of logging.h
+ * msgb_put: more elaborate logging of head/tailroom failure
+ * utils_test: add osmo_print_n_test()
+ * utils: add osmo_strnchr()
+ * osmo_sockaddr_str: deprecate osmo_sockaddr_str_*_32n()
+ * vty: track parent nodes also for telnet sessions
+ * vty_app_info.is_config_node: add OSMO_DEPRECATED
+ * add osmo_escape_cstr and osmo_quote_cstr
+ * add all missing OSMO_GSUP_TO_MSGT_*() macros
+
+ [ Oliver Smith ]
+ * Cosmetic: l1sap.h: change /* !< to /*!<
+ * logging.h: add L1 SAPI related context and filter
+ * gprs_ns_vty: return success for disabled FR/GRE
+ * debian, utils: switch to python 3
+
+ [ Ruben Undheim ]
+ * MAXPATHLEN set if not defined
+ * No fail if no /proc/cpuinfo
+
+ [ Philipp Maier ]
+ * cosmetic: Move comment to the right place
+ * cosmetic: Add comment on GSM-FR ECU struct
+ * ecu_fr: increase test coverage for FR ECU implementation
+ * gsm0508: add functions to calculate beginning of a block
+
+ [ Vadim Yanitskiy ]
+ * gsm29205_test: fix error: missing braces around initializer
+ * GPRS/BSSGP: introduce bssgp_bvc_ctx_free()
+ * logging/vty: do not print deprecated logging commands to stdout
+ * logging/vty: use LOG_LEVEL_ARGS in logging_vty_add_deprecated_subsys()
+ * logging/vty: fix: actually ignore deprecated logging commands
+ * logging/vty: fix vty_read_file(): do not write warnings to stdin
+ * logging/vty: fix: do not close stderr in vty_close()
+ * libosmovty: properly initialize vty->fd in vty_new()
+ * libosmovty: simplify condition checking vty->fd in vty_close()
+ * core/defs.h: introduce and use OSMO_DEPRECATED_OUTSIDE
+
+ [ Daniel Willmann ]
+ * libosmogsm: add support for XOR authentication
+
+ [ Vasil Velichkov ]
+ * Add code coverage support
+
+ [ Eric Wild ]
+ * sim: allow opening reader# > 0
+
+ -- Pau Espin Pedrol <pespin@sysmocom.de> Thu, 02 Jan 2020 18:42:29 +0100
+
libosmocore (1.2.0) unstable; urgency=medium
[ Harald Welte ]
diff --git a/debian/control b/debian/control
index 07163da2..381e2914 100644
--- a/debian/control
+++ b/debian/control
@@ -1,5 +1,5 @@
Source: libosmocore
-Maintainer: Harald Welte <laforge@gnumonks.org>
+Maintainer: Osmocom team <openbsc@lists.osmocom.org>
Section: libs
Priority: optional
Build-Depends: debhelper (>= 9),
@@ -15,7 +15,9 @@ Build-Depends: debhelper (>= 9),
libpcsclite-dev,
pkg-config,
libtalloc-dev,
- python (>= 2.7.6)
+ libsctp-dev,
+ libusb-1.0-0-dev,
+ python3:native
Standards-Version: 3.9.8
Vcs-Git: git://git.osmocom.org/libosmocore.git
Vcs-Browser: http://git.osmocom.org/libosmocore/
@@ -27,12 +29,13 @@ Architecture: any
Multi-Arch: foreign
Depends: libosmocodec0 (= ${binary:Version}),
libosmocoding0 (= ${binary:Version}),
- libosmocore12 (= ${binary:Version}),
- libosmogb9 (= ${binary:Version}),
- libosmogsm13 (= ${binary:Version}),
+ libosmocore16 (= ${binary:Version}),
+ libosmogb11 (= ${binary:Version}),
+ libosmogsm15 (= ${binary:Version}),
libosmovty4 (= ${binary:Version}),
libosmoctrl0 (= ${binary:Version}),
- libosmosim0 (= ${binary:Version}),
+ libosmosim2 (= ${binary:Version}),
+ libosmousb0 (= ${binary:Version}),
${misc:Depends}
Description: Open Source MObile COMmunications CORE library (metapackage)
The libraries provided by this package contain various utility functions.
@@ -110,7 +113,7 @@ Description: Documentation for the osmo coding library
.
This package contains the documentation for the libosmocoding library.
-Package: libosmocore12
+Package: libosmocore16
Section: libs
Architecture: any
Multi-Arch: same
@@ -124,14 +127,14 @@ Description: Osmo Core library
(at least) other programs that are developed in the sphere of Free Software /
Open Source mobile communication.
.
- The libosmocore12 library in particular is a collection of common code used in
+ The libosmocore16 library in particular is a collection of common code used in
various sub-projects inside the Osmocom family of projects.
Package: libosmocore-doc
Architecture: all
Section: doc
Depends: ${misc:Depends},
- libosmocore12,
+ libosmocore16,
libjs-jquery,
libosmocodec-doc,
libosmocoding-doc,
@@ -146,7 +149,7 @@ Description: Documentation for the Osmo Core library
.
This package contains the documentation for the libosmocore library.
-Package: libosmogb9
+Package: libosmogb11
Section: libs
Architecture: any
Multi-Arch: same
@@ -167,7 +170,7 @@ Package: libosmogb-doc
Architecture: all
Section: doc
Depends: ${misc:Depends},
- libosmogb9,
+ libosmogb11,
libjs-jquery
Description: Documentation for the Osmo GPRS Gb library
This is part of the libosmocore "meta"-library. The libosmocore library
@@ -178,7 +181,7 @@ Description: Documentation for the Osmo GPRS Gb library
.
This package contains the documentation for the libosmogb library.
-Package: libosmogsm13
+Package: libosmogsm15
Section: libs
Architecture: any
Multi-Arch: same
@@ -202,7 +205,7 @@ Package: libosmogsm-doc
Architecture: all
Section: doc
Depends: ${misc:Depends},
- libosmogsm13,
+ libosmogsm15,
libjs-jquery
Description: Documentation for the Osmo GSM utility library
This is part of the libosmocore "meta"-library. The libosmocore library
@@ -276,7 +279,7 @@ Description: Documentation for the Osmocom CTRL library
.
This package contains the documentation for the libosmoctrl library.
-Package: libosmosim0
+Package: libosmosim2
Section: libs
Architecture: any
Multi-Arch: same
@@ -292,6 +295,23 @@ Description: Osmo SIM library
.
The libosmosim library in particular contains routines for SIM card access.
+Package: libosmousb0
+Section: libs
+Architecture: any
+Multi-Arch: same
+Depends: ${shlibs:Depends},
+ ${misc:Depends}
+Pre-Depends: ${misc:Pre-Depends}
+Description: Osmo USB library
+ This is part of the libosmocore "meta"-library. The libosmocore library
+ contains various utility functions that were originally developed as part of
+ the OpenBSC project, but which are of a more generic nature and thus useful to
+ (at least) other programs that are developed in the sphere of Free Software /
+ Open Source mobile communication.
+ .
+ The libosmosub library in particular contains routines for USB device access
+ via libusb-1.0, integrated into the libosmocore select event loop.
+
Package: libosmocore-dev
Architecture: any
Multi-Arch: same
diff --git a/debian/libosmocore-dev.install b/debian/libosmocore-dev.install
index 944a7bf1..4a7e0afc 100644
--- a/debian/libosmocore-dev.install
+++ b/debian/libosmocore-dev.install
@@ -4,3 +4,4 @@ usr/lib/*/lib*.so
usr/lib/*/lib*.la
usr/lib/*/pkgconfig/*
usr/bin/osmo-release.sh
+usr/share/aclocal/osmo*.m4
diff --git a/debian/libosmocore12.install b/debian/libosmocore16.install
index b73331b9..b73331b9 100644
--- a/debian/libosmocore12.install
+++ b/debian/libosmocore16.install
diff --git a/debian/libosmogb9.install b/debian/libosmogb11.install
index 4c474255..4c474255 100644
--- a/debian/libosmogb9.install
+++ b/debian/libosmogb11.install
diff --git a/debian/libosmogsm13.install b/debian/libosmogsm15.install
index 5e617298..5e617298 100644
--- a/debian/libosmogsm13.install
+++ b/debian/libosmogsm15.install
diff --git a/debian/libosmosim0.install b/debian/libosmosim2.install
index 0a780abc..0a780abc 100644
--- a/debian/libosmosim0.install
+++ b/debian/libosmosim2.install
diff --git a/debian/libosmousb0.install b/debian/libosmousb0.install
new file mode 100644
index 00000000..4031b9f4
--- /dev/null
+++ b/debian/libosmousb0.install
@@ -0,0 +1 @@
+usr/lib/*/libosmousb*.so.*
diff --git a/debian/patches/series b/debian/patches/series
deleted file mode 100644
index e69de29b..00000000
--- a/debian/patches/series
+++ /dev/null
diff --git a/debian/rules b/debian/rules
index a9d961c7..afff17fa 100755
--- a/debian/rules
+++ b/debian/rules
@@ -26,7 +26,7 @@ override_dh_auto_test:
dh_auto_test || (find . -name testsuite.log -exec cat {} \; ; false)
override_dh_auto_configure:
- dh_auto_configure -- --enable-static
+ dh_auto_configure -- --enable-static --disable-sctp-tests
override_dh_clean:
dh_clean
diff --git a/include/Makefile.am b/include/Makefile.am
index a82d6ac4..44ff3789 100644
--- a/include/Makefile.am
+++ b/include/Makefile.am
@@ -23,6 +23,7 @@ nobase_include_HEADERS = \
osmocom/core/crcgen.h \
osmocom/core/endian.h \
osmocom/core/defs.h \
+ osmocom/core/exec.h \
osmocom/core/fsm.h \
osmocom/core/gsmtap.h \
osmocom/core/gsmtap_util.h \
@@ -64,6 +65,7 @@ nobase_include_HEADERS = \
osmocom/gprs/gprs_msgb.h \
osmocom/gprs/gprs_ns.h \
osmocom/gprs/gprs_ns_frgre.h \
+ osmocom/gprs/gprs_ns2.h \
osmocom/gprs/gprs_rlc.h \
osmocom/gprs/protocol/gsm_04_60.h \
osmocom/gprs/protocol/gsm_08_16.h \
@@ -89,10 +91,16 @@ nobase_include_HEADERS = \
osmocom/coding/gsm0503_mapping.h \
osmocom/coding/gsm0503_interleaving.h \
osmocom/coding/gsm0503_coding.h \
+ osmocom/coding/gsm0503_amr_dtx.h \
+ osmocom/gsm/bsslap.h \
+ osmocom/gsm/bssmap_le.h \
+ osmocom/gsm/gad.h \
osmocom/gsm/gsm0808.h \
+ osmocom/gsm/gsm0808_lcs.h \
osmocom/gsm/gsm29205.h \
osmocom/gsm/gsm0808_utils.h \
osmocom/gsm/gsm23003.h \
+ osmocom/gsm/gsm23236.h \
osmocom/gsm/gsm29118.h \
osmocom/gsm/gsm48.h \
osmocom/gsm/gsm48_arfcn_range_encode.h \
@@ -101,6 +109,7 @@ nobase_include_HEADERS = \
osmocom/gsm/gsm_utils.h \
osmocom/gsm/gsup.h \
osmocom/gsm/gsup_sms.h \
+ osmocom/gsm/i460_mux.h \
osmocom/gsm/ipa.h \
osmocom/gsm/lapd_core.h \
osmocom/gsm/lapdm.h \
@@ -110,6 +119,7 @@ nobase_include_HEADERS = \
osmocom/gsm/l1sap.h \
osmocom/gsm/oap.h \
osmocom/gsm/oap_client.h \
+ osmocom/gsm/protocol/gsm_23_032.h \
osmocom/gsm/protocol/gsm_03_40.h \
osmocom/gsm/protocol/gsm_03_41.h \
osmocom/gsm/protocol/gsm_04_08.h \
@@ -127,6 +137,8 @@ nobase_include_HEADERS = \
osmocom/gsm/protocol/gsm_29_118.h \
osmocom/gsm/protocol/gsm_44_318.h \
osmocom/gsm/protocol/gsm_48_049.h \
+ osmocom/gsm/protocol/gsm_48_071.h \
+ osmocom/gsm/protocol/gsm_49_031.h \
osmocom/gsm/protocol/ipaccess.h \
osmocom/gsm/protocol/smpp34_osmocom.h \
osmocom/gsm/rsl.h \
@@ -160,10 +172,16 @@ nobase_include_HEADERS += \
osmocom/vty/vector.h \
osmocom/vty/vty.h \
osmocom/vty/ports.h \
+ osmocom/vty/cpu_sched_vty.h \
osmocom/vty/tdef_vty.h \
osmocom/ctrl/control_vty.h
endif
+if ENABLE_LIBUSB
+nobase_include_HEADERS += \
+ osmocom/usb/libusb.h
+endif
+
noinst_HEADERS = \
osmocom/gsm/kasumi.h \
osmocom/gsm/gea.h \
@@ -179,7 +197,7 @@ osmocom/core/crc%gen.h: osmocom/core/crcXXgen.h.tpl
$(AM_V_GEN)sed -e's/XX/$*/g' $< > $@
osmocom/gsm/gsm0503.h: $(top_srcdir)/utils/conv_gen.py $(top_srcdir)/utils/conv_codes_gsm.py
- $(AM_V_GEN)python $(top_srcdir)/utils/conv_gen.py gen_header gsm \
+ $(AM_V_GEN)python3 $(top_srcdir)/utils/conv_gen.py gen_header gsm \
--target-path $(builddir)/osmocom/gsm
CLEANFILES = osmocom/gsm/gsm0503.h
diff --git a/include/osmocom/codec/codec.h b/include/osmocom/codec/codec.h
index 6a1bf9fb..cbdad75e 100644
--- a/include/osmocom/codec/codec.h
+++ b/include/osmocom/codec/codec.h
@@ -6,6 +6,7 @@
#include <stdbool.h>
#include <osmocom/core/utils.h>
+#include <osmocom/core/bits.h>
/* TS 101318 Chapter 5.1: 260 bits + 4bit sig */
#define GSM_FR_BYTES 33
@@ -51,6 +52,11 @@ enum osmo_amr_quality {
AMR_GOOD = 1
};
+extern const uint8_t gsm690_bitlength[AMR_NO_DATA+1];
+
+int osmo_amr_s_to_d(ubit_t *out, const ubit_t *in, uint16_t n_bits, enum osmo_amr_type amr_mode);
+int osmo_amr_d_to_s(ubit_t *out, const ubit_t *in, uint16_t n_bits, enum osmo_amr_type amr_mode);
+
/*! Check if given AMR Frame Type is a speech frame
* \param[in] ft AMR Frame Type
* \returns true if AMR with given Frame Type contains voice, false otherwise
diff --git a/include/osmocom/codec/ecu.h b/include/osmocom/codec/ecu.h
index ec0a2f8d..668df367 100644
--- a/include/osmocom/codec/ecu.h
+++ b/include/osmocom/codec/ecu.h
@@ -3,13 +3,71 @@
#include <stdint.h>
#include <stdbool.h>
+#include <osmocom/core/defs.h>
#include <osmocom/codec/codec.h>
-/* Codec independent ECU state */
+/* ECU state for GSM-FR */
struct osmo_ecu_fr_state {
bool subsequent_lost_frame;
uint8_t frame_backup[GSM_FR_BYTES];
};
-void osmo_ecu_fr_reset(struct osmo_ecu_fr_state *state, const uint8_t *frame);
-int osmo_ecu_fr_conceal(struct osmo_ecu_fr_state *state, uint8_t *frame);
+void osmo_ecu_fr_reset(struct osmo_ecu_fr_state *state, const uint8_t *frame)
+ OSMO_DEPRECATED_OUTSIDE("Use generic ECU abstraction layer instead");
+int osmo_ecu_fr_conceal(struct osmo_ecu_fr_state *state, uint8_t *frame)
+ OSMO_DEPRECATED_OUTSIDE("Use generic ECU abstraction layer instead");
+
+enum osmo_ecu_codec {
+ OSMO_ECU_CODEC_HR,
+ OSMO_ECU_CODEC_FR,
+ OSMO_ECU_CODEC_EFR,
+ OSMO_ECU_CODEC_AMR,
+ _NUM_OSMO_ECU_CODECS
+};
+
+/***********************************************************************
+ * Generic ECU abstraction layer below
+ ***********************************************************************/
+
+/* As the developer and copyright holder of the related code, I hereby
+ * state that any ECU implementation using 'struct osmo_ecu_ops' and
+ * registering with the 'osmo_ecu_register()' function shall not be
+ * considered as a derivative work under any applicable copyright law;
+ * the copyleft terms of GPLv2 shall hence not apply to any such ECU
+ * implementation.
+ *
+ * The intent of the above exception is to allow anyone to combine third
+ * party Error Concealment Unit implementations with libosmocodec.
+ * including but not limited to such published by ETSI.
+ *
+ * -- Harald Welte <laforge@gnumonks.org> on August 1, 2019.
+ */
+
+/* Codec independent ECU state */
+struct osmo_ecu_state {
+ enum osmo_ecu_codec codec;
+ uint8_t data[0];
+};
+
+/* initialize an ECU instance */
+struct osmo_ecu_state *osmo_ecu_init(void *ctx, enum osmo_ecu_codec codec);
+
+/* destroy an ECU instance */
+void osmo_ecu_destroy(struct osmo_ecu_state *st);
+
+/* process a received frame a substitute/erroneous frame */
+int osmo_ecu_frame_in(struct osmo_ecu_state *st, bool bfi,
+ const uint8_t *frame, unsigned int frame_bytes);
+
+/* generate output data for a substitute/erroneous frame */
+int osmo_ecu_frame_out(struct osmo_ecu_state *st, uint8_t *frame_out);
+
+struct osmo_ecu_ops {
+ struct osmo_ecu_state * (*init)(void *ctx, enum osmo_ecu_codec codec);
+ void (*destroy)(struct osmo_ecu_state *);
+ int (*frame_in)(struct osmo_ecu_state *st, bool bfi,
+ const uint8_t *frame, unsigned int frame_bytes);
+ int (*frame_out)(struct osmo_ecu_state *st, uint8_t *frame_out);
+};
+
+int osmo_ecu_register(const struct osmo_ecu_ops *ops, enum osmo_ecu_codec codec);
diff --git a/include/osmocom/coding/gsm0503_amr_dtx.h b/include/osmocom/coding/gsm0503_amr_dtx.h
new file mode 100644
index 00000000..f048a6e3
--- /dev/null
+++ b/include/osmocom/coding/gsm0503_amr_dtx.h
@@ -0,0 +1,40 @@
+/*! \file gsm0503_amr_dtx.h
+ * GSM TS 05.03 coding
+ */
+
+#pragma once
+
+#include <stdint.h>
+
+#include <osmocom/core/defs.h>
+#include <osmocom/core/bits.h>
+
+/*! \addtogroup coding
+ * @{
+ * \file gsm0503_amr_dtx.h */
+
+enum gsm0503_amr_dtx_frames {
+ AMR_OTHER,
+ AFS_SID_FIRST,
+ AFS_SID_UPDATE,
+ AFS_SID_UPDATE_CN,
+ AFS_ONSET,
+ AHS_SID_UPDATE,
+ AHS_SID_UPDATE_CN,
+ AHS_SID_FIRST_P1,
+ AHS_SID_FIRST_P2,
+ AHS_ONSET,
+ AHS_SID_FIRST_INH,
+ AHS_SID_UPDATE_INH,
+};
+
+extern const struct value_string gsm0503_amr_dtx_frame_names[];
+static inline const char *gsm0503_amr_dtx_frame_name(enum gsm0503_amr_dtx_frames frame)
+{
+ return get_value_string(gsm0503_amr_dtx_frame_names, frame);
+}
+
+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_ahs_dtx_frame(int *n_errors, int *n_bits_total, const ubit_t *ubits);
+
+/*! @} */
diff --git a/include/osmocom/coding/gsm0503_coding.h b/include/osmocom/coding/gsm0503_coding.h
index 98038f8f..2afa049b 100644
--- a/include/osmocom/coding/gsm0503_coding.h
+++ b/include/osmocom/coding/gsm0503_coding.h
@@ -58,12 +58,18 @@ int gsm0503_tch_afs_encode(ubit_t *bursts, const uint8_t *tch_data, int len,
int gsm0503_tch_afs_decode(uint8_t *tch_data, const sbit_t *bursts,
int codec_mode_req, uint8_t *codec, int codecs, uint8_t *ft,
uint8_t *cmr, int *n_errors, int *n_bits_total);
+int gsm0503_tch_afs_decode_dtx(uint8_t *tch_data, const sbit_t *bursts,
+ int codec_mode_req, uint8_t *codec, int codecs, uint8_t *ft,
+ uint8_t *cmr, int *n_errors, int *n_bits_total, uint8_t *dtx);
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, uint8_t cmr);
int gsm0503_tch_ahs_decode(uint8_t *tch_data, const sbit_t *bursts, int odd,
int codec_mode_req, uint8_t *codec, int codecs, uint8_t *ft,
uint8_t *cmr, int *n_errors, int *n_bits_total);
+int gsm0503_tch_ahs_decode_dtx(uint8_t *tch_data, const sbit_t *bursts, int odd,
+ int codec_mode_req, uint8_t *codec, int codecs, uint8_t *ft,
+ uint8_t *cmr, int *n_errors, int *n_bits_total, uint8_t *dtx);
int gsm0503_rach_ext_encode(ubit_t *burst, uint16_t ra, uint8_t bsic, bool is_11bit);
int gsm0503_rach_encode(ubit_t *burst, const uint8_t *ra, uint8_t bsic) OSMO_DEPRECATED("Use gsm0503_rach_ext_encode() instead");
diff --git a/include/osmocom/coding/gsm0503_parity.h b/include/osmocom/coding/gsm0503_parity.h
index 28a54443..bda5f99e 100644
--- a/include/osmocom/coding/gsm0503_parity.h
+++ b/include/osmocom/coding/gsm0503_parity.h
@@ -10,14 +10,15 @@
* @{
* \file gsm0503_parity.h */
-const struct osmo_crc64gen_code gsm0503_fire_crc40;
-const struct osmo_crc16gen_code gsm0503_cs234_crc16;
-const struct osmo_crc8gen_code gsm0503_mcs_crc8_hdr;
-const struct osmo_crc16gen_code gsm0503_mcs_crc12;
-const struct osmo_crc8gen_code gsm0503_rach_crc6;
-const struct osmo_crc16gen_code gsm0503_sch_crc10;
-const struct osmo_crc8gen_code gsm0503_tch_fr_crc3;
-const struct osmo_crc8gen_code gsm0503_tch_efr_crc8;
-const struct osmo_crc8gen_code gsm0503_amr_crc6;
+extern const struct osmo_crc64gen_code gsm0503_fire_crc40;
+extern const struct osmo_crc16gen_code gsm0503_cs234_crc16;
+extern const struct osmo_crc8gen_code gsm0503_mcs_crc8_hdr;
+extern const struct osmo_crc16gen_code gsm0503_mcs_crc12;
+extern const struct osmo_crc8gen_code gsm0503_rach_crc6;
+extern const struct osmo_crc16gen_code gsm0503_sch_crc10;
+extern const struct osmo_crc8gen_code gsm0503_tch_fr_crc3;
+extern const struct osmo_crc8gen_code gsm0503_tch_efr_crc8;
+extern const struct osmo_crc8gen_code gsm0503_amr_crc6;
+extern const struct osmo_crc16gen_code gsm0503_amr_crc14;
/*! @} */
diff --git a/include/osmocom/core/bitXXgen.h.tpl b/include/osmocom/core/bitXXgen.h.tpl
index 6881d87d..258fccb6 100644
--- a/include/osmocom/core/bitXXgen.h.tpl
+++ b/include/osmocom/core/bitXXgen.h.tpl
@@ -22,7 +22,9 @@
#pragma once
-/*! load unaligned n-byte integer (little-endian encoding) into uintXX_t
+#include <osmocom/core/utils.h>
+
+/*! load unaligned n-byte integer (little-endian encoding) into uintXX_t, into the least significant octets.
* \param[in] p Buffer where integer is stored
* \param[in] n Number of bytes stored in p
* \returns XX bit unsigned integer
@@ -32,11 +34,14 @@ static inline uintXX_t osmo_loadXXle_ext(const void *p, uint8_t n)
uint8_t i;
uintXX_t r = 0;
const uint8_t *q = (uint8_t *)p;
+ OSMO_ASSERT(n <= sizeof(r));
for(i = 0; i < n; r |= ((uintXX_t)q[i] << (8 * i)), i++);
return r;
}
-/*! load unaligned n-byte integer (big-endian encoding) into uintXX_t
+/*! load unaligned n-byte integer (big-endian encoding) into uintXX_t, into the MOST significant octets.
+ * WARNING: for n < sizeof(uintXX_t), the result is not returned in the least significant octets, as one might expect.
+ * To always return the same value as fed to osmo_storeXXbe_ext() before, use osmo_loadXXbe_ext_2().
* \param[in] p Buffer where integer is stored
* \param[in] n Number of bytes stored in p
* \returns XX bit unsigned integer
@@ -46,10 +51,26 @@ static inline uintXX_t osmo_loadXXbe_ext(const void *p, uint8_t n)
uint8_t i;
uintXX_t r = 0;
const uint8_t *q = (uint8_t *)p;
+ OSMO_ASSERT(n <= sizeof(r));
for(i = 0; i < n; r |= ((uintXX_t)q[i] << (XX - 8* (1 + i))), i++);
return r;
}
+/*! load unaligned n-byte integer (big-endian encoding) into uintXX_t, into the least significant octets.
+ * \param[in] p Buffer where integer is stored
+ * \param[in] n Number of bytes stored in p
+ * \returns XX bit unsigned integer
+ */
+static inline uintXX_t osmo_loadXXbe_ext_2(const void *p, uint8_t n)
+{
+ uint8_t i;
+ uintXX_t r = 0;
+ const uint8_t *q = (uint8_t *)p;
+ OSMO_ASSERT(n <= sizeof(r));
+ for(i = 0; i < n; r |= ((uintXX_t)q[i] << (XX - 8* (1 + i + (sizeof(r) - n)))), i++);
+ return r;
+}
+
/*! store unaligned n-byte integer (little-endian encoding) from uintXX_t
* \param[in] x unsigned XX bit integer
@@ -60,6 +81,7 @@ static inline void osmo_storeXXle_ext(uintXX_t x, void *p, uint8_t n)
{
uint8_t i;
uint8_t *q = (uint8_t *)p;
+ OSMO_ASSERT(n <= sizeof(x));
for(i = 0; i < n; q[i] = (x >> i * 8) & 0xFF, i++);
}
@@ -72,6 +94,7 @@ static inline void osmo_storeXXbe_ext(uintXX_t x, void *p, uint8_t n)
{
uint8_t i;
uint8_t *q = (uint8_t *)p;
+ OSMO_ASSERT(n <= sizeof(x));
for(i = 0; i < n; q[i] = (x >> ((n - 1 - i) * 8)) & 0xFF, i++);
}
diff --git a/include/osmocom/core/defs.h b/include/osmocom/core/defs.h
index 5e5aa90f..33ffb7bb 100644
--- a/include/osmocom/core/defs.h
+++ b/include/osmocom/core/defs.h
@@ -43,8 +43,10 @@
#if BUILDING_LIBOSMOCORE
# define OSMO_DEPRECATED_OUTSIDE_LIBOSMOCORE
+# define OSMO_DEPRECATED_OUTSIDE(text)
#else
# define OSMO_DEPRECATED_OUTSIDE_LIBOSMOCORE OSMO_DEPRECATED("For internal use inside libosmocore only.")
+# define OSMO_DEPRECATED_OUTSIDE(text) OSMO_DEPRECATED(text)
#endif
#undef _OSMO_HAS_ATTRIBUTE_DEPRECATED_WITH_MESSAGE
diff --git a/include/osmocom/core/exec.h b/include/osmocom/core/exec.h
new file mode 100644
index 00000000..e63ec114
--- /dev/null
+++ b/include/osmocom/core/exec.h
@@ -0,0 +1,29 @@
+#pragma once
+/* (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.
+ *
+ * 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.
+ *
+ */
+extern const char *osmo_environment_whitelist[];
+
+int osmo_environment_filter(char **out, size_t out_len, char **in, const char **whitelist);
+int osmo_environment_append(char **out, size_t out_len, char **in);
+int osmo_close_all_fds_above(int last_fd_to_keep);
+int osmo_system_nowait2(const char *command, const char **env_whitelist, char **addl_env, const char *user);
+int osmo_system_nowait(const char *command, const char **env_whitelist, char **addl_env);
diff --git a/include/osmocom/core/fsm.h b/include/osmocom/core/fsm.h
index 1701c45e..7b262c71 100644
--- a/include/osmocom/core/fsm.h
+++ b/include/osmocom/core/fsm.h
@@ -10,6 +10,7 @@
#include <osmocom/core/linuxlist.h>
#include <osmocom/core/timer.h>
#include <osmocom/core/utils.h>
+#include <osmocom/core/logging.h>
/*! \defgroup fsm Finite State Machine abstraction
* @{
@@ -122,6 +123,7 @@ struct osmo_fsm_inst {
void osmo_fsm_log_addr(bool log_addr);
void osmo_fsm_log_timeouts(bool log_timeouts);
void osmo_fsm_term_safely(bool term_safely);
+void osmo_fsm_set_dealloc_ctx(void *ctx);
/*! Log using FSM instance's context, on explicit logging subsystem and level.
* \param fi An osmo_fsm_inst.
diff --git a/include/osmocom/core/gsmtap.h b/include/osmocom/core/gsmtap.h
index 35ba71e5..11b2d137 100644
--- a/include/osmocom/core/gsmtap.h
+++ b/include/osmocom/core/gsmtap.h
@@ -48,6 +48,7 @@
#define GSMTAP_TYPE_OSMOCORE_LOG 0x10 /* libosmocore logging */
#define GSMTAP_TYPE_QC_DIAG 0x11 /* Qualcomm DIAG frame */
#define GSMTAP_TYPE_LTE_NAS 0x12 /* LTE Non-Access Stratum */
+#define GSMTAP_TYPE_E1T1 0x13 /* E1/T1 Lines */
/* ====== DO NOT MAKE UNAPPROVED MODIFICATIONS HERE ===== */
@@ -82,8 +83,8 @@
#define GSMTAP_CHANNEL_SDCCH 0x06
#define GSMTAP_CHANNEL_SDCCH4 0x07
#define GSMTAP_CHANNEL_SDCCH8 0x08
-#define GSMTAP_CHANNEL_TCH_F 0x09
-#define GSMTAP_CHANNEL_TCH_H 0x0a
+#define GSMTAP_CHANNEL_FACCH_F 0x09 /* Actually, it's FACCH/F (signaling) */
+#define GSMTAP_CHANNEL_FACCH_H 0x0a /* Actually, it's FACCH/H (signaling) */
#define GSMTAP_CHANNEL_PACCH 0x0b
#define GSMTAP_CHANNEL_CBCH52 0x0c
#define GSMTAP_CHANNEL_PDTCH 0x0d
@@ -91,6 +92,10 @@
#define GSMTAP_CHANNEL_PDCH GSMTAP_CHANNEL_PDTCH
#define GSMTAP_CHANNEL_PTCCH 0x0e
#define GSMTAP_CHANNEL_CBCH51 0x0f
+#define GSMTAP_CHANNEL_VOICE_F 0x10 /* voice codec payload (FR/EFR/AMR) */
+#define GSMTAP_CHANNEL_VOICE_H 0x11 /* voice codec payload (HR/AMR) */
+#define GSMTAP_CHANNEL_TCH_F GSMTAP_CHANNEL_FACCH_F /* We used the wrong naming in 2008 when we were young */
+#define GSMTAP_CHANNEL_TCH_H GSMTAP_CHANNEL_FACCH_H /* We used the wrong naming in 2008 when we were young */
/* GPRS Coding Scheme CS1..4 */
#define GSMTAP_GPRS_CS_BASE 0x20
@@ -167,6 +172,14 @@
#define GSMTAP_LTE_CH_DTCH 0x06
#define GSMTAP_LTE_CH_MTCH 0x07
+/* ====== DO NOT MAKE UNAPPROVED MODIFICATIONS HERE ===== */
+/* sub-types for TYPE_E1T1 */
+#define GSMTAP_E1T1_LAPD 0x01 /* Q.921 LAPD */
+#define GSMTAP_E1T1_FR 0x02 /* Frame Relay */
+#define GSMTAP_E1T1_RAW 0x03 /* raw/transparent B-channel */
+#define GSMTAP_E1T1_TRAU16 0x04 /* 16k TRAU frames; sub-slot 0-3 */
+#define GSMTAP_E1T1_TRAU8 0x05 /* 8k TRAU frames; sub-slot 0-7 */
+
/* flags for the ARFCN */
#define GSMTAP_ARFCN_F_PCS 0x8000
#define GSMTAP_ARFCN_F_UPLINK 0x4000
@@ -318,3 +331,26 @@ struct gsmtap_osmocore_log_hdr {
uint32_t line_nr;/*!< line number */
} src_file;
} __attribute__((packed));
+
+/*! First byte of type==GSMTAP_TYPE_UM sub_type==GSMTAP_CHANNEL_VOICE payload */
+enum gsmtap_um_voice_type {
+ /*! 1 byte TOC + 112 bits (14 octets) = 15 octets payload;
+ * Reference is RFC5993 Section 5.2.1 + 3GPP TS 46.030 Annex B */
+ GSMTAP_UM_VOICE_HR,
+ /*! 33 payload bytes; Reference is RFC3551 Section 4.5.8.1 */
+ GSMTAP_UM_VOICE_FR,
+ /*! 31 payload bytes; Reference is RFC3551 Section 4.5.9 + ETSI TS 101 318 */
+ GSMTAP_UM_VOICE_EFR,
+ /*! 1 byte TOC + 5..31 bytes = 6..32 bytes payload; RFC4867 octet-aligned */
+ GSMTAP_UM_VOICE_AMR,
+ /* TODO: Revisit the types below; their usage; ... */
+ GSMTAP_UM_VOICE_AMR_SID_BAD,
+ GSMTAP_UM_VOICE_AMR_ONSET,
+ GSMTAP_UM_VOICE_AMR_RATSCCH,
+ GSMTAP_UM_VOICE_AMR_SID_UPDATE_INH,
+ GSMTAP_UM_VOICE_AMR_SID_FIRST_P1,
+ GSMTAP_UM_VOICE_AMR_SID_FIRST_P2,
+ GSMTAP_UM_VOICE_AMR_SID_FIRST_INH,
+ GSMTAP_UM_VOICE_AMR_RATSCCH_MARKER,
+ GSMTAP_UM_VOICE_AMR_RATSCCH_DATA,
+};
diff --git a/include/osmocom/core/gsmtap_util.h b/include/osmocom/core/gsmtap_util.h
index f8a12a60..9b215be3 100644
--- a/include/osmocom/core/gsmtap_util.h
+++ b/include/osmocom/core/gsmtap_util.h
@@ -8,7 +8,11 @@
* @{
* \file gsmtap_util.h */
-uint8_t chantype_rsl2gsmtap(uint8_t rsl_chantype, uint8_t rsl_link_id);
+uint8_t chantype_rsl2gsmtap2(uint8_t rsl_chantype, uint8_t rsl_link_id, bool user_plane);
+
+uint8_t chantype_rsl2gsmtap(uint8_t rsl_chantype, uint8_t rsl_link_id)
+ OSMO_DEPRECATED("Use chantype_rsl2gsmtap2() instead");
+
void chantype_gsmtap2rsl(uint8_t gsmtap_chantype, uint8_t *rsl_chantype, uint8_t *link_id);
struct msgb *gsmtap_makemsg_ex(uint8_t type, uint16_t arfcn, uint8_t ts, uint8_t chan_type,
diff --git a/include/osmocom/core/logging.h b/include/osmocom/core/logging.h
index 803b4a94..6d0d5a3a 100644
--- a/include/osmocom/core/logging.h
+++ b/include/osmocom/core/logging.h
@@ -105,7 +105,7 @@ void logp(int subsys, const char *file, int line, int cont, const char *format,
#define LOGL_ERROR 7 /*!< error condition, requires user action */
#define LOGL_FATAL 8 /*!< fatal, program aborted */
-/* logging levels defined by the library itself */
+/* logging subsystems defined by the library itself */
#define DLGLOBAL -1 /*!< global logging */
#define DLLAPD -2 /*!< LAPD implementation */
#define DLINP -3 /*!< (A-bis) Input sub-system */
@@ -125,7 +125,27 @@ void logp(int subsys, const char *file, int line, int cont, const char *format,
#define DLMGCP -17 /*!< Osmocom MGCP */
#define DLJIBUF -18 /*!< Osmocom Jitter Buffer */
#define DLRSPRO -19 /*!< Osmocom Remote SIM Protocol */
-#define OSMO_NUM_DLIB 19 /*!< Number of logging sub-systems in libraries */
+#define DLNS -20 /*!< Osmocom NS layer */
+#define OSMO_NUM_DLIB 20 /*!< Number of logging sub-systems in libraries */
+
+/* Colors that can be used in log_info_cat.color */
+#define OSMO_LOGCOLOR_NORMAL NULL
+#define OSMO_LOGCOLOR_RED "\033[1;31m"
+#define OSMO_LOGCOLOR_GREEN "\033[1;32m"
+#define OSMO_LOGCOLOR_YELLOW "\033[1;33m"
+#define OSMO_LOGCOLOR_BLUE "\033[1;34m"
+#define OSMO_LOGCOLOR_PURPLE "\033[1;35m"
+#define OSMO_LOGCOLOR_CYAN "\033[1;36m"
+#define OSMO_LOGCOLOR_DARKRED "\033[31m"
+#define OSMO_LOGCOLOR_DARKGREEN "\033[32m"
+#define OSMO_LOGCOLOR_DARKYELLOW "\033[33m"
+#define OSMO_LOGCOLOR_DARKBLUE "\033[34m"
+#define OSMO_LOGCOLOR_DARKPURPLE "\033[35m"
+#define OSMO_LOGCOLOR_DARKCYAN "\033[36m"
+#define OSMO_LOGCOLOR_DARKGREY "\033[1;30m"
+#define OSMO_LOGCOLOR_GREY "\033[37m"
+#define OSMO_LOGCOLOR_BRIGHTWHITE "\033[1;37m"
+#define OSMO_LOGCOLOR_END "\033[0;m"
/*! Configuration of single log category / sub-system */
struct log_category {
@@ -154,6 +174,7 @@ enum log_ctx_index {
LOG_CTX_GB_BVC,
LOG_CTX_BSC_SUBSCR,
LOG_CTX_VLR_SUBSCR,
+ LOG_CTX_L1_SAPI,
_LOG_CTX_COUNT
};
@@ -166,6 +187,7 @@ enum log_filter_index {
LOG_FLT_GB_BVC,
LOG_FLT_BSC_SUBSCR,
LOG_FLT_VLR_SUBSCR,
+ LOG_FLT_L1_SAPI,
_LOG_FLT_COUNT
};
@@ -222,6 +244,7 @@ enum log_target_type {
LOG_TGT_TYPE_STDERR, /*!< stderr logging */
LOG_TGT_TYPE_STRRB, /*!< osmo_strrb-backed logging */
LOG_TGT_TYPE_GSMTAP, /*!< GSMTAP network logging */
+ LOG_TGT_TYPE_SYSTEMD, /*!< systemd journal logging */
};
/*! Whether/how to log the source filename (and line number). */
@@ -289,6 +312,10 @@ struct log_target {
const char *ident;
const char *hostname;
} tgt_gsmtap;
+
+ struct {
+ bool raw;
+ } sd_journal;
};
/*! call-back function to be called when the logging framework
@@ -370,6 +397,8 @@ struct log_target *log_target_create_gsmtap(const char *host, uint16_t port,
const char *ident,
bool ofd_wq_mode,
bool add_sink);
+struct log_target *log_target_create_systemd(bool raw);
+void log_target_systemd_set_raw(struct log_target *target, bool raw);
int log_target_file_reopen(struct log_target *tgt);
int log_targets_reopen(void);
@@ -377,6 +406,19 @@ void log_add_target(struct log_target *target);
void log_del_target(struct log_target *target);
struct log_target *log_target_find(int type, const char *fname);
-extern struct llist_head osmo_log_target_list;
+
+void log_enable_multithread(void);
+
+void log_tgt_mutex_lock_impl(void);
+void log_tgt_mutex_unlock_impl(void);
+#define LOG_MTX_DEBUG 0
+#if LOG_MTX_DEBUG
+ #include <pthread.h>
+ #define log_tgt_mutex_lock() do { fprintf(stderr, "[%lu] %s:%d [%s] lock\n", pthread_self(), __FILE__, __LINE__, __func__); log_tgt_mutex_lock_impl(); } while (0)
+ #define log_tgt_mutex_unlock() do { fprintf(stderr, "[%lu] %s:%d [%s] unlock\n", pthread_self(), __FILE__, __LINE__, __func__); log_tgt_mutex_unlock_impl(); } while (0)
+#else
+ #define log_tgt_mutex_lock() log_tgt_mutex_lock_impl()
+ #define log_tgt_mutex_unlock() log_tgt_mutex_unlock_impl()
+#endif
/*! @} */
diff --git a/include/osmocom/core/logging_internal.h b/include/osmocom/core/logging_internal.h
index a510f83e..2e656603 100644
--- a/include/osmocom/core/logging_internal.h
+++ b/include/osmocom/core/logging_internal.h
@@ -7,8 +7,9 @@
#include <osmocom/core/utils.h>
extern void *tall_log_ctx;
-extern const struct log_info *osmo_log_info;
+extern struct log_info *osmo_log_info;
extern const struct value_string loglevel_strs[];
+extern struct llist_head osmo_log_target_list;
void assert_loginfo(const char *src);
diff --git a/include/osmocom/core/msgb.h b/include/osmocom/core/msgb.h
index e05d37f0..cc76e3ad 100644
--- a/include/osmocom/core/msgb.h
+++ b/include/osmocom/core/msgb.h
@@ -239,7 +239,11 @@ static inline unsigned char *msgb_put(struct msgb *msgb, unsigned int len)
{
unsigned char *tmp = msgb->tail;
if (msgb_tailroom(msgb) < (int) len)
- MSGB_ABORT(msgb, "Not enough tailroom msgb_put (%u < %u)\n",
+ MSGB_ABORT(msgb, "Not enough tailroom msgb_put"
+ " (allocated %u, head at %u, len %u, tailroom %u < want tailroom %u)\n",
+ msgb->data_len - sizeof(struct msgb),
+ msgb->head - msgb->_data,
+ msgb->len,
msgb_tailroom(msgb), len);
msgb->tail += len;
msgb->len += len;
@@ -335,8 +339,13 @@ static inline uint32_t msgb_get_u32(struct msgb *msgb)
static inline unsigned char *msgb_push(struct msgb *msgb, unsigned int len)
{
if (msgb_headroom(msgb) < (int) len)
- MSGB_ABORT(msgb, "Not enough headroom msgb_push (%u < %u)\n",
- msgb_headroom(msgb), len);
+ MSGB_ABORT(msgb, "Not enough headroom msgb_push"
+ " (allocated %u, head at %u < want headroom %u, len %u, tailroom %u)\n",
+ msgb->data_len - sizeof(struct msgb),
+ msgb->head - msgb->_data,
+ len,
+ msgb->len,
+ msgb_tailroom(msgb));
msgb->data -= len;
msgb->len += len;
return msgb->data;
@@ -518,7 +527,7 @@ static inline int msgb_l3trim(struct msgb *msg, int l3len)
static inline struct msgb *msgb_alloc_headroom_c(const void *ctx, int size, int headroom,
const char *name)
{
- osmo_static_assert(size > headroom, headroom_bigger);
+ osmo_static_assert(size >= headroom, headroom_bigger);
struct msgb *msg = msgb_alloc_c(ctx, size, name);
if (msg)
@@ -540,7 +549,7 @@ static inline struct msgb *msgb_alloc_headroom_c(const void *ctx, int size, int
static inline struct msgb *msgb_alloc_headroom(int size, int headroom,
const char *name)
{
- osmo_static_assert(size > headroom, headroom_bigger);
+ osmo_static_assert(size >= headroom, headroom_bigger);
struct msgb *msg = msgb_alloc(size, name);
if (msg)
diff --git a/include/osmocom/core/rate_ctr.h b/include/osmocom/core/rate_ctr.h
index f7e6e225..1669ce49 100644
--- a/include/osmocom/core/rate_ctr.h
+++ b/include/osmocom/core/rate_ctr.h
@@ -116,4 +116,7 @@ int rate_ctr_for_each_counter(struct rate_ctr_group *ctrg,
int rate_ctr_for_each_group(rate_ctr_group_handler_t handle_group, void *data);
+void rate_ctr_reset(struct rate_ctr *ctr);
+void rate_ctr_group_reset(struct rate_ctr_group *ctrg);
+
/*! @} */
diff --git a/include/osmocom/core/select.h b/include/osmocom/core/select.h
index e4787b09..b4101998 100644
--- a/include/osmocom/core/select.h
+++ b/include/osmocom/core/select.h
@@ -7,6 +7,7 @@
#include <osmocom/core/linuxlist.h>
#include <stdbool.h>
#include <time.h>
+#include <signal.h>
/*! \defgroup select Select loop abstraction
* @{
@@ -18,6 +19,8 @@
#define OSMO_FD_WRITE 0x0002
/*! Indicate interest in exceptions from the file descriptor */
#define OSMO_FD_EXCEPT 0x0004
+/*! Used as when_mask in osmo_fd_update_when() */
+#define OSMO_FD_MASK 0xFFFF
/* legacy naming dating back to early OpenBSC / bsc_hack of 2008 */
#define BSC_FD_READ OSMO_FD_READ
@@ -46,11 +49,31 @@ void osmo_fd_setup(struct osmo_fd *ofd, int fd, unsigned int when,
int (*cb)(struct osmo_fd *fd, unsigned int what),
void *data, unsigned int priv_nr);
+void osmo_fd_update_when(struct osmo_fd *ofd, unsigned int when_mask, unsigned int when);
+
+static inline void osmo_fd_read_enable(struct osmo_fd *ofd) {
+ osmo_fd_update_when(ofd, OSMO_FD_MASK, OSMO_FD_READ);
+}
+
+static inline void osmo_fd_read_disable(struct osmo_fd *ofd) {
+ osmo_fd_update_when(ofd, ~OSMO_FD_READ, 0);
+}
+
+static inline void osmo_fd_write_enable(struct osmo_fd *ofd) {
+ osmo_fd_update_when(ofd, OSMO_FD_MASK, OSMO_FD_WRITE);
+}
+
+static inline void osmo_fd_write_disable(struct osmo_fd *ofd) {
+ osmo_fd_update_when(ofd, ~OSMO_FD_WRITE, 0);
+}
+
bool osmo_fd_is_registered(struct osmo_fd *fd);
int osmo_fd_register(struct osmo_fd *fd);
void osmo_fd_unregister(struct osmo_fd *fd);
void osmo_fd_close(struct osmo_fd *fd);
int osmo_select_main(int polling);
+int osmo_select_main_ctx(int polling);
+void osmo_select_init(void);
struct osmo_fd *osmo_fd_get_by_fd(int fd);
@@ -66,4 +89,21 @@ int osmo_timerfd_schedule(struct osmo_fd *ofd, const struct timespec *first,
const struct timespec *interval);
int osmo_timerfd_setup(struct osmo_fd *ofd, int (*cb)(struct osmo_fd *, unsigned int), void *data);
+/* signalfd integration */
+struct osmo_signalfd;
+struct signalfd_siginfo;
+
+typedef void osmo_signalfd_cb(struct osmo_signalfd *osfd, const struct signalfd_siginfo *fdsi);
+
+struct osmo_signalfd {
+ struct osmo_fd ofd;
+ sigset_t sigset;
+ osmo_signalfd_cb *cb;
+ void *data;
+};
+
+struct osmo_signalfd *
+osmo_signalfd_setup(void *ctx, sigset_t set, osmo_signalfd_cb *cb, void *data);
+
+
/*! @} */
diff --git a/include/osmocom/core/sockaddr_str.h b/include/osmocom/core/sockaddr_str.h
index 253b755f..d96b7434 100644
--- a/include/osmocom/core/sockaddr_str.h
+++ b/include/osmocom/core/sockaddr_str.h
@@ -31,6 +31,7 @@
#include <stdint.h>
#include <stdbool.h>
#include <arpa/inet.h>
+#include <osmocom/core/defs.h>
struct in_addr;
struct in6_addr;
@@ -61,17 +62,24 @@ struct osmo_sockaddr_str {
* struct osmo_sockaddr_str *my_sockaddr_str = ...;
* printf("got " OSMO_SOCKADDR_STR_FMT "\n", OSMO_SOCKADDR_STR_FMT_ARGS(my_sockaddr_str));
*/
-#define OSMO_SOCKADDR_STR_FMT "%s:%u"
-#define OSMO_SOCKADDR_STR_FMT_ARGS(R) ((R)->ip ? : ""), (R)->port
+#define OSMO_SOCKADDR_STR_FMT "%s%s%s:%u"
+#define OSMO_SOCKADDR_STR_FMT_ARGS(R) \
+ ((R) && (R)->af == AF_INET6)? "[" : "", \
+ (R)? (R)->ip : "NULL", \
+ ((R) && (R)->af == AF_INET6)? "]" : "", \
+ (R)? (R)->port : 0
bool osmo_sockaddr_str_is_set(const struct osmo_sockaddr_str *sockaddr_str);
+bool osmo_sockaddr_str_is_nonzero(const struct osmo_sockaddr_str *sockaddr_str);
+int osmo_sockaddr_str_cmp(const struct osmo_sockaddr_str *a, const struct osmo_sockaddr_str *b);
int osmo_sockaddr_str_from_str(struct osmo_sockaddr_str *sockaddr_str, const char *ip, uint16_t port);
+int osmo_sockaddr_str_from_str2(struct osmo_sockaddr_str *sockaddr_str, const char *ip);
int osmo_sockaddr_str_from_in_addr(struct osmo_sockaddr_str *sockaddr_str, const struct in_addr *addr, uint16_t port);
int osmo_sockaddr_str_from_in6_addr(struct osmo_sockaddr_str *sockaddr_str, const struct in6_addr *addr, uint16_t port);
int osmo_sockaddr_str_from_32(struct osmo_sockaddr_str *sockaddr_str, uint32_t ip, uint16_t port);
-int osmo_sockaddr_str_from_32n(struct osmo_sockaddr_str *sockaddr_str, uint32_t ip, uint16_t port);
+int osmo_sockaddr_str_from_32h(struct osmo_sockaddr_str *sockaddr_str, uint32_t ip, uint16_t port);
int osmo_sockaddr_str_from_sockaddr_in(struct osmo_sockaddr_str *sockaddr_str, const struct sockaddr_in *src);
int osmo_sockaddr_str_from_sockaddr_in6(struct osmo_sockaddr_str *sockaddr_str, const struct sockaddr_in6 *src);
int osmo_sockaddr_str_from_sockaddr(struct osmo_sockaddr_str *sockaddr_str, const struct sockaddr_storage *src);
@@ -79,9 +87,14 @@ int osmo_sockaddr_str_from_sockaddr(struct osmo_sockaddr_str *sockaddr_str, cons
int osmo_sockaddr_str_to_in_addr(const struct osmo_sockaddr_str *sockaddr_str, struct in_addr *dst);
int osmo_sockaddr_str_to_in6_addr(const struct osmo_sockaddr_str *sockaddr_str, struct in6_addr *dst);
int osmo_sockaddr_str_to_32(const struct osmo_sockaddr_str *sockaddr_str, uint32_t *ip);
-int osmo_sockaddr_str_to_32n(const struct osmo_sockaddr_str *sockaddr_str, uint32_t *ip);
+int osmo_sockaddr_str_to_32h(const struct osmo_sockaddr_str *sockaddr_str, uint32_t *ip);
int osmo_sockaddr_str_to_sockaddr_in(const struct osmo_sockaddr_str *sockaddr_str, struct sockaddr_in *dst);
int osmo_sockaddr_str_to_sockaddr_in6(const struct osmo_sockaddr_str *sockaddr_str, struct sockaddr_in6 *dst);
int osmo_sockaddr_str_to_sockaddr(const struct osmo_sockaddr_str *sockaddr_str, struct sockaddr_storage *dst);
+int osmo_sockaddr_str_from_32n(struct osmo_sockaddr_str *sockaddr_str, uint32_t ip, uint16_t port)
+ OSMO_DEPRECATED("osmo_sockaddr_str_from_32n() actually uses *host* byte order. Use osmo_sockaddr_str_from_32h() instead");
+int osmo_sockaddr_str_to_32n(const struct osmo_sockaddr_str *sockaddr_str, uint32_t *ip)
+ OSMO_DEPRECATED("osmo_sockaddr_str_to_32n() actually uses *host* byte order. Use osmo_sockaddr_str_to_32h() instead");
+
/*! @} */
diff --git a/include/osmocom/core/socket.h b/include/osmocom/core/socket.h
index 37b1eaef..0443c06e 100644
--- a/include/osmocom/core/socket.h
+++ b/include/osmocom/core/socket.h
@@ -2,6 +2,7 @@
* Osmocom socket convenience functions. */
#pragma once
+#if (!EMBEDDED)
/*! \defgroup socket Socket convenience functions
* @{
@@ -11,17 +12,24 @@
#include <stdbool.h>
#include <stddef.h>
-#if (!EMBEDDED)
#include <arpa/inet.h>
/*! maximum length of a socket name ("r=1.2.3.4:123<->l=5.6.7.8:987") */
#define OSMO_SOCK_NAME_MAXLEN (2 + INET6_ADDRSTRLEN + 1 + 5 + 3 + 2 + INET6_ADDRSTRLEN + 1 + 5 + 1)
-#endif
struct sockaddr_in;
struct sockaddr;
struct osmo_fd;
+struct osmo_sockaddr {
+ union {
+ struct sockaddr sa;
+ struct sockaddr_storage sas;
+ struct sockaddr_in sin;
+ struct sockaddr_in6 sin6;
+ } u;
+};
+
/* flags for osmo_sock_init. */
/*! connect the socket to a remote peer */
#define OSMO_SOCK_F_CONNECT (1 << 0)
@@ -36,6 +44,9 @@ struct osmo_fd;
/*! use SO_REUSEADDR on UDP ports (required for multicast) */
#define OSMO_SOCK_F_UDP_REUSEADDR (1 << 5)
+/*! maximum number of local or remote addresses supported by an osmo_sock instance */
+#define OSMO_SOCK_MAX_ADDRS 32
+
int osmo_sock_init(uint16_t family, uint16_t type, uint8_t proto,
const char *host, uint16_t port, unsigned int flags);
@@ -43,6 +54,15 @@ int osmo_sock_init2(uint16_t family, uint16_t type, uint8_t proto,
const char *local_host, uint16_t local_port,
const char *remote_host, uint16_t remote_port, unsigned int flags);
+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);
+
+int osmo_sock_init_osa(uint16_t type, uint8_t proto,
+ const struct osmo_sockaddr *local,
+ const struct osmo_sockaddr *remote,
+ unsigned int flags);
+
int osmo_sock_init_ofd(struct osmo_fd *ofd, int family, int type, int proto,
const char *host, uint16_t port, unsigned int flags);
@@ -50,6 +70,11 @@ int osmo_sock_init2_ofd(struct osmo_fd *ofd, int family, int type, int proto,
const char *local_host, uint16_t local_port,
const char *remote_host, uint16_t remote_port, unsigned int 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);
+
int osmo_sock_init_sa(struct sockaddr *ss, uint16_t type,
uint8_t proto, unsigned int flags);
@@ -60,6 +85,9 @@ unsigned int osmo_sockaddr_to_str_and_uint(char *addr, unsigned int addr_len, ui
size_t osmo_sockaddr_in_to_str_and_uint(char *addr, unsigned int addr_len, uint16_t *port,
const struct sockaddr_in *sin);
+const char *osmo_sockaddr_ntop(const struct sockaddr *sa, char *dst);
+uint16_t osmo_sockaddr_port(const struct sockaddr *sa);
+
int osmo_sock_unix_init(uint16_t type, uint8_t proto,
const char *socket_path, unsigned int flags);
@@ -80,8 +108,19 @@ int osmo_sock_get_remote_ip_port(int fd, char *port, size_t len);
int osmo_sock_mcast_loop_set(int fd, bool enable);
int osmo_sock_mcast_ttl_set(int fd, uint8_t ttl);
int osmo_sock_mcast_all_set(int fd, bool enable);
+int osmo_sock_mcast_iface_set(int fd, const char *ifname);
int osmo_sock_mcast_subscribe(int fd, const char *grp_addr);
int osmo_sock_local_ip(char *local_ip, const char *remote_ip);
+int osmo_sockaddr_local_ip(struct osmo_sockaddr *local_ip,
+ const struct osmo_sockaddr *remote_ip);
+int osmo_sockaddr_cmp(const struct osmo_sockaddr *a,
+ const struct osmo_sockaddr *b);
+
+const char *osmo_sockaddr_to_str(const struct osmo_sockaddr *sockaddr);
+char *osmo_sockaddr_to_str_buf(char *buf, size_t buf_len,
+ const struct osmo_sockaddr *sockaddr);
+
+#endif /* (!EMBEDDED) */
/*! @} */
diff --git a/include/osmocom/core/stat_item.h b/include/osmocom/core/stat_item.h
index 806173ab..4710dba7 100644
--- a/include/osmocom/core/stat_item.h
+++ b/include/osmocom/core/stat_item.h
@@ -114,4 +114,8 @@ static inline int32_t osmo_stat_item_get_last(const struct osmo_stat_item *item)
{
return item->values[item->last_offs].value;
}
+
+void osmo_stat_item_reset(struct osmo_stat_item *item);
+void osmo_stat_item_group_reset(struct osmo_stat_item_group *statg);
+
/*! @} */
diff --git a/include/osmocom/core/stats.h b/include/osmocom/core/stats.h
index e01016d4..b9edac2a 100644
--- a/include/osmocom/core/stats.h
+++ b/include/osmocom/core/stats.h
@@ -73,6 +73,7 @@ struct osmo_stats_reporter {
char *bind_addr_str; /*!< local bind IP address */
int dest_port; /*!< destination (UDP) port */
int mtu; /*!< Maximum Transmission Unit */
+ unsigned int flush_period; /*!< period between regular flushes */
/*! Maximum class/index to report. FIXME: More details! */
enum osmo_stats_class max_class;
@@ -87,7 +88,8 @@ struct osmo_stats_reporter {
int fd; /*!< file descriptor of socket */
struct msgb *buffer; /*!< message buffer for log output */
int agg_enabled; /*!< is aggregation enabled? */
- int force_single_flush;
+ int force_single_flush; /*!< set to 1 to force a flush (send even unchanged stats values) */
+ unsigned int flush_period_counter; /*!< count sends between forced flushes */
struct llist_head list;
int (*open)(struct osmo_stats_reporter *srep);
@@ -129,6 +131,7 @@ int osmo_stats_reporter_set_max_class(struct osmo_stats_reporter *srep,
int osmo_stats_reporter_set_name_prefix(struct osmo_stats_reporter *srep, const char *prefix);
int osmo_stats_reporter_enable(struct osmo_stats_reporter *srep);
int osmo_stats_reporter_disable(struct osmo_stats_reporter *srep);
+int osmo_stats_reporter_set_flush_period(struct osmo_stats_reporter *srep, unsigned int period);
/* reporter creation */
struct osmo_stats_reporter *osmo_stats_reporter_create_log(const char *name);
diff --git a/include/osmocom/core/talloc.h b/include/osmocom/core/talloc.h
index 191a463f..c68a56cf 100644
--- a/include/osmocom/core/talloc.h
+++ b/include/osmocom/core/talloc.h
@@ -1,5 +1,27 @@
-/*! \file talloc.h
- * Convenience wrapper. libosmocore used to ship its own internal copy of
- * talloc, before libtalloc became a standard component on most systems */
+/*! \file talloc.h */
#pragma once
#include <talloc.h>
+
+/*! per-thread talloc contexts. This works around the problem that talloc is not
+ * thread-safe. However, one can simply have a different set of talloc contexts for each
+ * thread, and ensure that allocations made on one thread are always only free'd on that
+ * very same thread.
+ * WARNING: Users must make sure they free() on the same thread as they allocate!! */
+struct osmo_talloc_contexts {
+ /*! global per-thread talloc context. */
+ void *global;
+ /*! volatile select-dispatch context. This context is completely free'd and
+ * re-created every time the main select loop in osmo_select_main() returns from
+ * select(2) and calls per-fd callback functions. This allows users of this
+ * facility to allocate temporary objects like string buffers, message buffers
+ * and the like which are automatically free'd when going into the next select()
+ * system call */
+ void *select;
+};
+
+extern __thread struct osmo_talloc_contexts *osmo_ctx;
+
+/* short-hand #defines for the osmo talloc contexts (OTC) that can be used to pass
+ * to the various _c functions like msgb_alloc_c() */
+#define OTC_GLOBAL (osmo_ctx->global)
+#define OTC_SELECT (osmo_ctx->select)
diff --git a/include/osmocom/core/tdef.h b/include/osmocom/core/tdef.h
index 566f5dd3..54819d95 100644
--- a/include/osmocom/core/tdef.h
+++ b/include/osmocom/core/tdef.h
@@ -77,6 +77,10 @@ struct osmo_tdef {
/*! Currently active timeout value, e.g. set by user config. This is the only mutable member: a user may
* configure the timeout value, but neither unit nor any other field. */
unsigned long val;
+ /*! Minimum timer value (in this tdef unit), checked if set (not zero). */
+ unsigned long min_val;
+ /*! Maximum timer value (in this tdef unit), checked if set (not zero). */
+ unsigned long max_val;
};
/*! Iterate an array of struct osmo_tdef, the last item should be fully zero, i.e. "{}".
@@ -97,6 +101,9 @@ void osmo_tdefs_reset(struct osmo_tdef *tdefs);
unsigned long osmo_tdef_get(const struct osmo_tdef *tdefs, int T, enum osmo_tdef_unit as_unit,
long val_if_not_present);
struct osmo_tdef *osmo_tdef_get_entry(struct osmo_tdef *tdefs, int T);
+int osmo_tdef_set(struct osmo_tdef *tdefs, int T, unsigned long val, enum osmo_tdef_unit val_unit);
+bool osmo_tdef_val_in_range(struct osmo_tdef *tdef, unsigned long new_val);
+int osmo_tdef_range_str_buf(char *buf, size_t buf_len, struct osmo_tdef *t);
/*! Using osmo_tdef for osmo_fsm_inst: array entry for a mapping of state numbers to timeout definitions.
* For a usage example, see osmo_tdef_get_state_timeout() and test_tdef_state_timeout() in tdef_test.c. */
diff --git a/include/osmocom/core/timer.h b/include/osmocom/core/timer.h
index 19797662..6ffc3b17 100644
--- a/include/osmocom/core/timer.h
+++ b/include/osmocom/core/timer.h
@@ -84,6 +84,7 @@ int osmo_timer_remaining(const struct osmo_timer_list *timer,
* internal timer list management
*/
struct timeval *osmo_timers_nearest(void);
+int osmo_timers_nearest_ms(void);
void osmo_timers_prepare(void);
int osmo_timers_update(void);
int osmo_timers_check(void);
diff --git a/include/osmocom/core/use_count.h b/include/osmocom/core/use_count.h
index 6a4bf1f3..cc5493c7 100644
--- a/include/osmocom/core/use_count.h
+++ b/include/osmocom/core/use_count.h
@@ -130,8 +130,9 @@ typedef int (* osmo_use_count_cb_t )(struct osmo_use_count_entry *use_count_entr
* int foo_use_cb(struct osmo_use_count_entry *use_count_entry, int32_t old_use_count, const char *file, int line)
* {
* struct foo *foo = use_count_entry->use_count->talloc_object;
- * if (osmo_use_count_total(&use_count_entry->use_count) == 0)
+ * if (osmo_use_count_total(use_count_entry->use_count) == 0)
* talloc_free(foo);
+ * return 0;
* }
*
* // The function name is a convenient use token:
@@ -215,6 +216,8 @@ int _osmo_use_count_get_put(struct osmo_use_count *uc, const char *use, int32_t
const char *file, int line);
const char *osmo_use_count_name_buf(char *buf, size_t buf_len, const struct osmo_use_count *uc);
+int osmo_use_count_to_str_buf(char *buf, size_t buf_len, const struct osmo_use_count *uc);
+char *osmo_use_count_to_str_c(void *ctx, const struct osmo_use_count *uc);
int32_t osmo_use_count_total(const struct osmo_use_count *uc);
int32_t osmo_use_count_by(const struct osmo_use_count *uc, const char *use);
diff --git a/include/osmocom/core/utils.h b/include/osmocom/core/utils.h
index 601bb565..c9d5560d 100644
--- a/include/osmocom/core/utils.h
+++ b/include/osmocom/core/utils.h
@@ -3,6 +3,7 @@
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
+#include <string.h>
#include <osmocom/core/backtrace.h>
#include <osmocom/core/talloc.h>
@@ -21,6 +22,8 @@
#define OSMO_MAX(a, b) ((a) >= (b) ? (a) : (b))
/*! Return the minimum of two specified values */
#define OSMO_MIN(a, b) ((a) >= (b) ? (b) : (a))
+/*! Return a typical cmp result for comparable entities a and b. */
+#define OSMO_CMP(a, b) ((a) < (b)? -1 : ((a) > (b)? 1 : 0))
/*! Stringify the name of a macro x, e.g. an FSM event name.
* Note: if nested within another preprocessor macro, this will
* stringify the value of x instead of its name. */
@@ -37,7 +40,7 @@
/*! A mapping between human-readable string and numeric value */
struct value_string {
- unsigned int value; /*!< numeric value */
+ int value; /*!< numeric value */
const char *str; /*!< human-readable string */
};
@@ -52,6 +55,7 @@ char osmo_bcd2char(uint8_t bcd);
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);
+int osmo_str2bcd(uint8_t *dst, size_t dst_size, const char *digits, int start_nibble, int end_nibble, bool allow_hex);
int osmo_hexparse(const char *str, uint8_t *b, int max_len);
@@ -136,6 +140,7 @@ uint64_t osmo_decode_big_endian(const uint8_t *data, size_t data_len);
uint8_t *osmo_encode_big_endian(uint64_t value, size_t data_len);
size_t osmo_strlcpy(char *dst, const char *src, size_t siz);
+const char *osmo_strnchr(const char *str, size_t str_size, char c);
bool osmo_is_hexstr(const char *str, int min_digits, int max_digits,
bool require_even);
@@ -144,6 +149,11 @@ bool osmo_identifier_valid(const char *str);
bool osmo_separated_identifiers_valid(const char *str, const char *sep_chars);
void osmo_identifier_sanitize_buf(char *str, const char *sep_chars, char replace_with);
+size_t osmo_escape_cstr_buf(char *buf, size_t bufsize, const char *str, int in_len);
+char *osmo_escape_cstr_c(void *ctx, const char *str, int in_len);
+size_t osmo_quote_cstr_buf(char *buf, size_t bufsize, const char *str, int in_len);
+char *osmo_quote_cstr_c(void *ctx, const char *str, int in_len);
+
const char *osmo_escape_str(const char *str, int len);
char *osmo_escape_str_buf2(char *buf, size_t bufsize, const char *str, int in_len);
const char *osmo_escape_str_buf(const char *str, int in_len, char *buf, size_t bufsize);
@@ -269,4 +279,65 @@ struct osmo_strbuf {
bool osmo_str_startswith(const char *str, const char *startswith_str);
+int osmo_float_str_to_int(int64_t *val, const char *str, unsigned int precision);
+int osmo_int_to_float_str_buf(char *buf, size_t buflen, int64_t val, unsigned int precision);
+char *osmo_int_to_float_str_c(void *ctx, int64_t val, unsigned int precision);
+
+/*! Translate a buffer function to a talloc context function.
+ * This is the full function body of a char *foo_name_c(void *ctx, val...) function, implemented by an
+ * int foo_name_buf(buf, buflen, val...) function:
+ *
+ * char *foo_name_c(void *ctx, example_t arg)
+ * {
+ * OSMO_NAME_C_IMPL(ctx, 64, "ERROR", foo_name_buf, arg)
+ * }
+ *
+ * Return a talloc'd string containing the result of the given foo_name_buf() function, or ON_ERROR on error in the called
+ * foo_name_buf() function.
+ *
+ * If ON_ERROR is NULL, the function returns NULL on error rc from FUNC_BUF. Take care: returning NULL in printf() like
+ * formats (LOGP()) makes the program crash. If ON_ERROR is non-NULL, it must be a string constant, which is not
+ * returned directly, but written to an allocated string buffer first.
+ *
+ * \param[in] INITIAL_BUFSIZE Which size to first talloc from ctx -- a larger size makes a reallocation less likely, a
+ * smaller size allocates less unused bytes, zero allocates once but still runs the string composition twice.
+ * \param[in] ON_ERROR String constant to copy on error rc returned by FUNC_BUF, or NULL to return NULL.
+ * \param[in] FUNC_BUF Name of a function with signature int foo_buf(char *buf, size_t buflen, ...).
+ * The function must return the strlen() that it would write to a sufficiently large buffer or
+ * negative on error, like snprintf().
+ * \param[in] FUNC_BUF_ARGS Additional arguments to pass to FUNC_BUF after the buf and buflen.
+ */
+#define OSMO_NAME_C_IMPL(CTX, INITIAL_BUFSIZE, ON_ERROR, FUNC_BUF, FUNC_BUF_ARGS...) \
+ size_t _len = INITIAL_BUFSIZE; \
+ int _needed; \
+ char *_str = NULL; \
+ if ((INITIAL_BUFSIZE) > 0) { \
+ _str = (char*)talloc_named_const(CTX, _len, __func__); \
+ OSMO_ASSERT(_str); \
+ } \
+ _needed = FUNC_BUF(_str, _len, ## FUNC_BUF_ARGS); \
+ if (_needed < 0) \
+ goto OSMO_NAME_C_on_error; \
+ if (_needed < _len) \
+ return _str; \
+ _len = _needed + 1; \
+ if (_str) \
+ talloc_free(_str); \
+ _str = (char*)talloc_named_const(CTX, _len, __func__); \
+ OSMO_ASSERT(_str); \
+ _needed = FUNC_BUF(_str, _len, ## FUNC_BUF_ARGS); \
+ if (_needed < 0) \
+ goto OSMO_NAME_C_on_error; \
+ return _str; \
+OSMO_NAME_C_on_error: \
+ /* Re-using and re-sizing above allocated buf ends up in very complex code. Just free and strdup. */ \
+ if (_str) \
+ talloc_free(_str); \
+ if (!(ON_ERROR)) \
+ return NULL; \
+ _str = talloc_strdup(CTX, ON_ERROR); \
+ OSMO_ASSERT(_str); \
+ talloc_set_name_const(_str, __func__); \
+ return _str;
+
/*! @} */
diff --git a/include/osmocom/core/write_queue.h b/include/osmocom/core/write_queue.h
index 071621d1..75d5d8fb 100644
--- a/include/osmocom/core/write_queue.h
+++ b/include/osmocom/core/write_queue.h
@@ -53,6 +53,7 @@ struct osmo_wqueue {
void osmo_wqueue_init(struct osmo_wqueue *queue, int max_length);
void osmo_wqueue_clear(struct osmo_wqueue *queue);
int osmo_wqueue_enqueue(struct osmo_wqueue *queue, struct msgb *data);
+int osmo_wqueue_enqueue_quiet(struct osmo_wqueue *queue, struct msgb *data);
int osmo_wqueue_bfd_cb(struct osmo_fd *fd, unsigned int what);
/*! @} */
diff --git a/include/osmocom/ctrl/ports.h b/include/osmocom/ctrl/ports.h
index 25d2491b..9759dc1e 100644
--- a/include/osmocom/ctrl/ports.h
+++ b/include/osmocom/ctrl/ports.h
@@ -24,4 +24,7 @@
#define OSMO_CTRL_PORT_GBPROXY 4263
/* 4264 used by VTY interface */
#define OSMO_CTRL_PORT_CBC 4265
+/* 4266 used by D-GSM mDNS */
+#define OSMO_CTRL_PORT_MGW 4267
+#define OSMO_CTRL_PORT_SMLC 4272
/* When adding/changing port numbers, keep docs and wiki in sync. See above. */
diff --git a/include/osmocom/gprs/gprs_bssgp.h b/include/osmocom/gprs/gprs_bssgp.h
index 400c3e00..b9d251cc 100644
--- a/include/osmocom/gprs/gprs_bssgp.h
+++ b/include/osmocom/gprs/gprs_bssgp.h
@@ -12,7 +12,10 @@
#include <osmocom/gprs/protocol/gsm_08_18.h>
/* gprs_bssgp_util.c */
+typedef int (*bssgp_bvc_send)(void *ctx, struct msgb *msg);
+
extern struct gprs_ns_inst *bssgp_nsi;
+void bssgp_set_bssgp_callback(bssgp_bvc_send ns_send, void *data);
struct msgb *bssgp_msgb_alloc(void);
struct msgb *bssgp_msgb_copy(const struct msgb *msg, const char *name);
const char *bssgp_cause_str(enum gprs_bssgp_cause cause);
@@ -112,6 +115,8 @@ extern struct llist_head bssgp_bvc_ctxts;
struct bssgp_bvc_ctx *btsctx_by_raid_cid(const struct gprs_ra_id *raid, uint16_t cid);
/* Find a BTS context based on BVCI+NSEI tuple */
struct bssgp_bvc_ctx *btsctx_by_bvci_nsei(uint16_t bvci, uint16_t nsei);
+/* Free a given BTS context */
+void bssgp_bvc_ctx_free(struct bssgp_bvc_ctx *ctx);
#define BVC_F_BLOCKED 0x0001
diff --git a/include/osmocom/gprs/gprs_bssgp_bss.h b/include/osmocom/gprs/gprs_bssgp_bss.h
index f07ab526..ab62b669 100644
--- a/include/osmocom/gprs/gprs_bssgp_bss.h
+++ b/include/osmocom/gprs/gprs_bssgp_bss.h
@@ -57,6 +57,7 @@ int bssgp_tx_bvc_block(struct bssgp_bvc_ctx *bctx, uint8_t cause);
int bssgp_tx_bvc_unblock(struct bssgp_bvc_ctx *bctx);
int bssgp_tx_bvc_reset(struct bssgp_bvc_ctx *bctx, uint16_t bvci, uint8_t cause);
+int bssgp_tx_bvc_reset2(struct bssgp_bvc_ctx *bctx, uint16_t bvci, uint8_t cause, bool add_cell_id);
int bssgp_tx_ul_ud(struct bssgp_bvc_ctx *bctx, uint32_t tlli,
const uint8_t *qos_profile, struct msgb *llc_pdu);
diff --git a/include/osmocom/gprs/gprs_ns.h b/include/osmocom/gprs/gprs_ns.h
index 02faa506..af25825e 100644
--- a/include/osmocom/gprs/gprs_ns.h
+++ b/include/osmocom/gprs/gprs_ns.h
@@ -97,6 +97,8 @@ struct gprs_ns_inst {
uint32_t remote_ip;
uint16_t remote_port;
int dscp;
+ /*! IPA compatibility: NS-RESET/BLOCK/UNBLOCK even on IP-SNS */
+ bool use_reset_block_unblock;
} nsip;
/*! NS-over-FR-over-GRE-over-IP specific bits */
struct {
@@ -186,6 +188,11 @@ struct sockaddr_in;
/* main function for higher layers (BSSGP) to send NS messages */
int gprs_ns_sendmsg(struct gprs_ns_inst *nsi, struct msgb *msg);
+/* Receive incoming NS message from underlying transport layer */
+int gprs_ns_rcvmsg(struct gprs_ns_inst *nsi, struct msgb *msg,
+ struct sockaddr_in *saddr, enum gprs_ns_ll ll);
+
+
int gprs_ns_tx_alive(struct gprs_nsvc *nsvc);
int gprs_ns_tx_alive_ack(struct gprs_nsvc *nsvc);
int gprs_ns_tx_reset(struct gprs_nsvc *nsvc, uint8_t cause);
@@ -195,7 +202,6 @@ int gprs_ns_tx_unblock(struct gprs_nsvc *nsvc);
/* Listen for incoming GPRS packets via NS/FR/GRE */
int gprs_ns_frgre_listen(struct gprs_ns_inst *nsi);
-struct gprs_nsvc *gprs_nsvc_create(struct gprs_ns_inst *nsi, uint16_t nsvci);
struct gprs_nsvc *gprs_nsvc_create2(struct gprs_ns_inst *nsi, uint16_t nsvci,
uint8_t sig_weight, uint8_t data_weight);
void gprs_nsvc_delete(struct gprs_nsvc *nsvc);
diff --git a/include/osmocom/gprs/gprs_ns2.h b/include/osmocom/gprs/gprs_ns2.h
new file mode 100644
index 00000000..fddc896a
--- /dev/null
+++ b/include/osmocom/gprs/gprs_ns2.h
@@ -0,0 +1,188 @@
+/*! \file gprs_ns2.h */
+
+
+#pragma once
+
+#include <stdint.h>
+#include <netinet/in.h>
+
+#include <osmocom/core/prim.h>
+
+struct osmo_sockaddr;
+struct osmo_sockaddr_str;
+
+struct gprs_ns2_inst;
+struct gprs_ns2_nse;
+struct gprs_ns2_vc;
+struct gprs_ns2_vc_bind;
+struct gprs_ns2_vc_driver;
+struct gprs_ns_ie_ip4_elem;
+struct gprs_ns_ie_ip6_elem;
+
+enum gprs_ns2_vc_mode {
+ /*! The VC will use RESET/BLOCK/UNBLOCK to start the connection and do ALIVE/ACK.
+ * This is what is needed for Frame Relay transport, and if you use a R97/R99 Gb
+ * interface over an IP transport (never standardized by 3GPP) */
+ NS2_VC_MODE_BLOCKRESET,
+ /*! The VC will only use ALIVE/ACK (no RESET/BLOCK/UNBLOCK), which is for Gb-IP
+ * interface compliant to 3GPP Rel=4 or later. */
+ NS2_VC_MODE_ALIVE,
+};
+
+/*! Osmocom NS primitives according to 48.016 5.2 Service primitves */
+enum gprs_ns2_prim {
+ PRIM_NS_UNIT_DATA,
+ PRIM_NS_CONGESTION,
+ PRIM_NS_STATUS,
+};
+
+/*! Osmocom NS primitives according to 48.016 5.2.2.4 Service primitves */
+enum gprs_ns2_congestion_cause {
+ NS_CONG_CAUSE_BACKWARD_BEGIN,
+ NS_CONG_CAUSE_BACKWARD_END,
+ NS_CONG_CAUSE_FORWARD_BEGIN,
+ NS_CONG_CAUSE_FORWARD_END,
+};
+
+/*! Osmocom NS primitives according to 48.016 5.2.2.6 Service primitves */
+enum gprs_ns2_affecting_cause {
+ NS_AFF_CAUSE_VC_FAILURE,
+ NS_AFF_CAUSE_VC_RECOVERY,
+ NS_AFF_CAUSE_FAILURE,
+ NS_AFF_CAUSE_RECOVERY,
+ /* osmocom own causes */
+ NS_AFF_CAUSE_SNS_CONFIGURED,
+ NS_AFF_CAUSE_SNS_FAILURE,
+};
+
+/*! Osmocom NS primitives according to 48.016 5.2.2.7 Service primitves */
+enum gprs_ns2_change_ip_endpoint {
+ NS_ENDPOINT_NO_CHANGE,
+ NS_ENDPOINT_REQUEST_CHANGE,
+ NS_ENDPOINT_CONFIRM_CHANGE,
+};
+
+struct osmo_gprs_ns2_prim {
+ struct osmo_prim_hdr oph;
+
+ uint16_t nsei;
+ uint16_t bvci;
+
+ union {
+ struct {
+ enum gprs_ns2_change_ip_endpoint change;
+ /* TODO: implement resource distribution
+ * add place holder for the link selector */
+ long long _resource_distribution_placeholder1;
+ long long _resource_distribution_placeholder2;
+ long long _resource_distribution_placeholder3;
+ } unitdata;
+ struct {
+ enum gprs_ns2_congestion_cause cause;
+ } congestion;
+ struct {
+ enum gprs_ns2_affecting_cause cause;
+ /* 48.016 5.2.2.6 transfer capability */
+ int transfer;
+ /* osmocom specific */
+ /* Persistent NSE/NSVC are configured by vty */
+ bool persistent;
+ /* Only true on the first time it's available.
+ * Allow the BSSGP layer to reset persistent NSE */
+ bool first;
+ } status;
+ } u;
+};
+
+/* instance */
+struct gprs_ns2_inst *gprs_ns2_instantiate(void *ctx, osmo_prim_cb cb, void *cb_data);
+void gprs_ns2_free(struct gprs_ns2_inst *inst);
+int gprs_ns2_dynamic_create_nse(struct gprs_ns2_inst *nsi, bool create_nse);
+
+/* Entrypoint for primitives from the NS USER */
+int gprs_ns2_recv_prim(struct gprs_ns2_inst *nsi, struct osmo_prim_hdr *oph);
+
+/*! a callback to iterate over all NSVC */
+typedef int (*gprs_ns2_foreach_nsvc_cb)(struct gprs_ns2_vc *nsvc, void *ctx);
+
+int gprs_ns2_nse_foreach_nsvc(struct gprs_ns2_nse *nse,
+ gprs_ns2_foreach_nsvc_cb cb, void *cb_data);
+struct gprs_ns2_nse *gprs_ns2_nse_by_nsei(struct gprs_ns2_inst *nsi, uint16_t nsei);
+struct gprs_ns2_nse *gprs_ns2_create_nse(struct gprs_ns2_inst *nsi, uint16_t nsei);
+uint16_t gprs_ns2_nse_nsei(struct gprs_ns2_nse *nse);
+void gprs_ns2_free_nse(struct gprs_ns2_nse *nse);
+void gprs_ns2_free_nses(struct gprs_ns2_inst *nsi);
+
+/* create vc */
+void gprs_ns2_free_nsvc(struct gprs_ns2_vc *nsvc);
+struct gprs_ns2_vc *gprs_ns2_nsvc_by_nsvci(struct gprs_ns2_inst *nsi, uint16_t nsvci);
+
+/* IP VL driver */
+int gprs_ns2_ip_bind(struct gprs_ns2_inst *nsi,
+ const struct osmo_sockaddr *local,
+ int dscp,
+ struct gprs_ns2_vc_bind **result);
+struct gprs_ns2_vc_bind *gprs_ns2_ip_bind_by_sockaddr(struct gprs_ns2_inst *nsi,
+ const struct osmo_sockaddr *sockaddr);
+void gprs_ns2_bind_set_mode(struct gprs_ns2_vc_bind *bind, enum gprs_ns2_vc_mode mode);
+
+/* create a VC connection */
+struct gprs_ns2_vc *gprs_ns2_ip_connect(struct gprs_ns2_vc_bind *bind,
+ const struct osmo_sockaddr *remote,
+ struct gprs_ns2_nse *nse,
+ uint16_t nsvci);
+
+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);
+struct gprs_ns2_vc *gprs_ns2_ip_connect_inactive(struct gprs_ns2_vc_bind *bind,
+ const struct osmo_sockaddr *remote,
+ struct gprs_ns2_nse *nse,
+ uint16_t nsvci);
+
+void gprs_ns2_free_bind(struct gprs_ns2_vc_bind *bind);
+void gprs_ns2_free_binds(struct gprs_ns2_inst *nsi);
+
+/* create a VC SNS connection */
+int gprs_ns2_ip_connect_sns(struct gprs_ns2_vc_bind *bind,
+ const struct osmo_sockaddr *remote,
+ uint16_t nsei);
+const struct osmo_sockaddr *gprs_ns2_nse_sns_remote(struct gprs_ns2_nse *nse);
+
+const struct osmo_sockaddr *gprs_ns2_ip_vc_remote(const struct gprs_ns2_vc *nsvc);
+const struct osmo_sockaddr *gprs_ns2_ip_vc_local(const struct gprs_ns2_vc *nsvc);
+bool gprs_ns2_ip_vc_equal(const struct gprs_ns2_vc *nsvc,
+ const struct osmo_sockaddr *local,
+ const struct osmo_sockaddr *remote,
+ uint16_t nsvci);
+const struct osmo_sockaddr *gprs_ns2_ip_bind_sockaddr(struct gprs_ns2_vc_bind *bind);
+int gprs_ns2_is_ip_bind(struct gprs_ns2_vc_bind *bind);
+int gprs_ns2_ip_bind_set_dscp(struct gprs_ns2_vc_bind *bind, int dscp);
+struct gprs_ns2_vc *gprs_ns2_nsvc_by_sockaddr_bind(
+ struct gprs_ns2_vc_bind *bind,
+ const struct osmo_sockaddr *saddr);
+
+int gprs_ns2_frgre_bind(struct gprs_ns2_inst *nsi,
+ const struct osmo_sockaddr *local,
+ int dscp,
+ struct gprs_ns2_vc_bind **result);
+int gprs_ns2_is_frgre_bind(struct gprs_ns2_vc_bind *bind);
+
+struct gprs_ns2_vc *gprs_ns2_nsvc_by_sockaddr_nse(
+ struct gprs_ns2_nse *nse,
+ const struct osmo_sockaddr *sockaddr);
+void gprs_ns2_start_alive_all_nsvcs(struct gprs_ns2_nse *nse);
+const char *gprs_ns2_cause_str(int cause);
+const char *gprs_ns2_ll_str(struct gprs_ns2_vc *nsvc);
+char *gprs_ns2_ll_str_buf(char *buf, size_t buf_len, struct gprs_ns2_vc *nsvc);
+char *gprs_ns2_ll_str_c(const void *ctx, struct gprs_ns2_vc *nsvc);
+
+/* vty */
+int gprs_ns2_vty_init(struct gprs_ns2_inst *nsi,
+ const struct osmo_sockaddr_str *default_bind);
+int gprs_ns2_vty_create();
+void gprs_ns2_vty_force_vc_mode(bool force, enum gprs_ns2_vc_mode mode, const char *reason);
+
+
+/*! @} */
diff --git a/include/osmocom/gprs/gprs_ns_frgre.h b/include/osmocom/gprs/gprs_ns_frgre.h
index d48ce086..8cf54c76 100644
--- a/include/osmocom/gprs/gprs_ns_frgre.h
+++ b/include/osmocom/gprs/gprs_ns_frgre.h
@@ -2,4 +2,7 @@
#pragma once
+struct gprs_nsvc;
+struct msgb;
+
int gprs_ns_frgre_sendmsg(struct gprs_nsvc *nsvc, struct msgb *msg);
diff --git a/include/osmocom/gprs/protocol/gsm_04_60.h b/include/osmocom/gprs/protocol/gsm_04_60.h
index f592b14f..05728f49 100644
--- a/include/osmocom/gprs/protocol/gsm_04_60.h
+++ b/include/osmocom/gprs/protocol/gsm_04_60.h
@@ -9,7 +9,6 @@
#include <stdint.h>
#include <osmocom/core/endian.h>
-#if OSMO_IS_LITTLE_ENDIAN == 1
/* TS 04.60 10.3a.4.1.1 */
struct gprs_rlc_ul_header_egprs_1 {
#if OSMO_IS_LITTLE_ENDIAN
@@ -160,158 +159,6 @@ struct gprs_rlc_dl_header_egprs_3 {
uint8_t dummy:1, spb:2, cps:4, bsn1_lo:1;
#endif
} __attribute__ ((packed));
-#else
-/* TS 04.60 10.3a.4.1.1 */
-struct gprs_rlc_ul_header_egprs_1 {
-#if OSMO_IS_LITTLE_ENDIAN
- uint8_t tfi_hi:2,
- cv:4,
- si:1,
- r:1;
- uint8_t bsn1_hi:5,
- tfi_lo:3;
- uint8_t bsn2_hi:2,
- bsn1_lo:6;
- uint8_t bsn2_lo:8;
- uint8_t spare_hi:1,
- pi:1,
- rsb:1,
- cps:5;
- uint8_t dummy:2,
- spare_lo:6;
-#elif OSMO_IS_BIG_ENDIAN
-/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianess.py) */
- uint8_t r:1, si:1, cv:4, tfi_hi:2;
- uint8_t tfi_lo:3, bsn1_hi:5;
- uint8_t bsn1_lo:6, bsn2_hi:2;
- uint8_t bsn2_lo:8;
- uint8_t cps:5, rsb:1, pi:1, spare_hi:1;
- uint8_t spare_lo:6, dummy:2;
-#endif
-} __attribute__ ((packed));
-
-/* TS 04.60 10.3a.4.2.1 */
-struct gprs_rlc_ul_header_egprs_2 {
-#if OSMO_IS_LITTLE_ENDIAN
- uint8_t tfi_hi:2,
- cv:4,
- si:1,
- r:1;
- uint8_t bsn1_hi:5,
- tfi_lo:3;
- uint8_t cps_hi:2,
- bsn1_lo:6;
- uint8_t spare_hi:5,
- pi:1,
- rsb:1,
- cps_lo:1;
- uint8_t dummy:3,
- spare_lo:5;
-#elif OSMO_IS_BIG_ENDIAN
-/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianess.py) */
- uint8_t r:1, si:1, cv:4, tfi_hi:2;
- uint8_t tfi_lo:3, bsn1_hi:5;
- uint8_t bsn1_lo:6, cps_hi:2;
- uint8_t cps_lo:1, rsb:1, pi:1, spare_hi:5;
- uint8_t spare_lo:5, dummy:3;
-#endif
-} __attribute__ ((packed));
-
-/* TS 04.60 10.3a.4.3.1 */
-struct gprs_rlc_ul_header_egprs_3 {
-#if OSMO_IS_LITTLE_ENDIAN
- uint8_t tfi_hi:2,
- cv:4,
- si:1,
- r:1;
- uint8_t bsn1_hi:5,
- tfi_lo:3;
- uint8_t cps_hi:2,
- bsn1_lo:6;
- uint8_t dummy:1,
- spare:1,
- pi:1,
- rsb:1,
- spb:2,
- cps_lo:2;
-#elif OSMO_IS_BIG_ENDIAN
-/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianess.py) */
- uint8_t r:1, si:1, cv:4, tfi_hi:2;
- uint8_t tfi_lo:3, bsn1_hi:5;
- uint8_t bsn1_lo:6, cps_hi:2;
- uint8_t cps_lo:2, spb:2, rsb:1, pi:1, spare:1, dummy:1;
-#endif
-} __attribute__ ((packed));
-
-struct gprs_rlc_dl_header_egprs_1 {
-#if OSMO_IS_LITTLE_ENDIAN
- uint8_t tfi_hi:1,
- rrbp:2,
- es_p:2,
- usf:3;
- uint8_t bsn1_hi:2,
- pr:2,
- tfi_lo:4;
- uint8_t bsn1_mid:8;
- uint8_t bsn2_hi:7,
- bsn1_lo:1;
- uint8_t cps:5,
- bsn2_lo:3;
-#elif OSMO_IS_BIG_ENDIAN
-/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianess.py) */
- uint8_t usf:3, es_p:2, rrbp:2, tfi_hi:1;
- uint8_t tfi_lo:4, pr:2, bsn1_hi:2;
- uint8_t bsn1_mid:8;
- uint8_t bsn1_lo:1, bsn2_hi:7;
- uint8_t bsn2_lo:3, cps:5;
-#endif
-} __attribute__ ((packed));
-
-struct gprs_rlc_dl_header_egprs_2 {
-#if OSMO_IS_LITTLE_ENDIAN
- uint8_t tfi_hi:1,
- rrbp:2,
- es_p:2,
- usf:3;
- uint8_t bsn1_hi:2,
- pr:2,
- tfi_lo:4;
- uint8_t bsn1_mid:8;
- uint8_t dummy:4,
- cps:3,
- bsn1_lo:1;
-#elif OSMO_IS_BIG_ENDIAN
-/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianess.py) */
- uint8_t usf:3, es_p:2, rrbp:2, tfi_hi:1;
- uint8_t tfi_lo:4, pr:2, bsn1_hi:2;
- uint8_t bsn1_mid:8;
- uint8_t bsn1_lo:1, cps:3, dummy:4;
-#endif
-} __attribute__ ((packed));
-
-struct gprs_rlc_dl_header_egprs_3 {
-#if OSMO_IS_LITTLE_ENDIAN
- uint8_t tfi_hi:1,
- rrbp:2,
- es_p:2,
- usf:3;
- uint8_t bsn1_hi:2,
- pr:2,
- tfi_lo:4;
- uint8_t bsn1_mid:8;
- uint8_t dummy:1,
- spb:2,
- cps:4,
- bsn1_lo:1;
-#elif OSMO_IS_BIG_ENDIAN
-/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianess.py) */
- uint8_t usf:3, es_p:2, rrbp:2, tfi_hi:1;
- uint8_t tfi_lo:4, pr:2, bsn1_hi:2;
- uint8_t bsn1_mid:8;
- uint8_t bsn1_lo:1, cps:4, spb:2, dummy:1;
-#endif
-} __attribute__ ((packed));
-#endif
/* TS 03.60 Chapter 6.3.3.1: Network Mode of Operation */
enum osmo_gprs_nmo {
diff --git a/include/osmocom/gprs/protocol/gsm_08_16.h b/include/osmocom/gprs/protocol/gsm_08_16.h
index 95efcb6d..f98f68dd 100644
--- a/include/osmocom/gprs/protocol/gsm_08_16.h
+++ b/include/osmocom/gprs/protocol/gsm_08_16.h
@@ -6,6 +6,8 @@
#pragma once
#include <stdint.h>
+#include <arpa/inet.h>
+#include <osmocom/core/utils.h>
/*! \addtogroup libgb
* @{
@@ -26,6 +28,14 @@ struct gprs_ns_ie_ip4_elem {
uint8_t data_weight;
} __attribute__ ((packed));
+/*! Section 10.3.2d List of IP6 Elements */
+struct gprs_ns_ie_ip6_elem {
+ struct in6_addr ip_addr;
+ uint16_t udp_port;
+ uint8_t sig_weight;
+ uint8_t data_weight;
+} __attribute__ ((packed));
+
extern const struct value_string gprs_ns_pdu_strings[];
/*! NS PDU Type (TS 08.16, Section 10.3.7, Table 14) */
diff --git a/include/osmocom/gsm/bsslap.h b/include/osmocom/gsm/bsslap.h
new file mode 100644
index 00000000..b2174b3a
--- /dev/null
+++ b/include/osmocom/gsm/bsslap.h
@@ -0,0 +1,57 @@
+/*! \addtogroup bsslap
+ * @{
+ * \file bsslap.h
+ * Message encoding and decoding for 3GPP TS 48.071 BSS LCS Assistance Protocol (BSSLAP).
+ */
+/*
+ * (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Neels Hofmeyr <neels@hofmeyr.de>
+ *
+ * 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.
+ *
+ */
+#pragma once
+
+#include <osmocom/gsm/protocol/gsm_48_071.h>
+#include <osmocom/gsm/protocol/gsm_49_031.h>
+
+struct msgb;
+
+struct osmo_bsslap_err {
+ int rc;
+ enum bsslap_msgt msg_type;
+ enum bsslap_iei iei;
+ enum lcs_cause cause;
+ char *logmsg;
+};
+
+extern const struct value_string osmo_bsslap_msgt_names[];
+static inline const char *osmo_bsslap_msgt_name(enum bsslap_msgt val)
+{ return get_value_string(osmo_bsslap_msgt_names, val); }
+
+extern const struct value_string osmo_bsslap_iei_names[];
+static inline const char *osmo_bsslap_iei_name(enum bsslap_iei val)
+{ return get_value_string(osmo_bsslap_iei_names, val); }
+
+int osmo_bsslap_enc(struct msgb *msg, const struct bsslap_pdu *pdu);
+int osmo_bsslap_dec(struct bsslap_pdu *pdu,
+ struct osmo_bsslap_err **err, void *err_ctx,
+ const uint8_t *data, size_t len);
+
+/*! @} */
diff --git a/include/osmocom/gsm/bssmap_le.h b/include/osmocom/gsm/bssmap_le.h
new file mode 100644
index 00000000..1c750c8a
--- /dev/null
+++ b/include/osmocom/gsm/bssmap_le.h
@@ -0,0 +1,81 @@
+/*! \addtogroup bssmap_le
+ * @{
+ * \file bssmap_le.h
+ * Message encoding and decoding for 3GPP TS 49.031 BSSMAP-LE.
+ */
+/*
+ * (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Neels Hofmeyr <neels@hofmeyr.de>
+ *
+ * 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.
+ *
+ */
+#pragma once
+
+#include <osmocom/gsm/protocol/gsm_49_031.h>
+
+struct osmo_bsslap_err;
+struct osmo_gad_err;
+
+struct osmo_bssmap_le_err {
+ int rc;
+ enum bssmap_le_msgt msg_type;
+ enum bssmap_le_iei iei;
+ enum lcs_cause cause;
+ struct osmo_bsslap_err *bsslap_err;
+ struct osmo_gad_err *gad_err;
+ char *logmsg;
+};
+
+struct osmo_bssap_le_err {
+ int rc;
+ struct osmo_bssmap_le_err *bssmap_le_err;
+ void *dtap_err;
+ char *logmsg;
+};
+
+enum bssmap_le_msgt osmo_bssmap_le_msgt(const uint8_t *data, uint8_t len);
+
+extern const struct value_string osmo_bssmap_le_msgt_names[];
+static inline const char *osmo_bssmap_le_msgt_name(enum bssmap_le_msgt val)
+{ return get_value_string(osmo_bssmap_le_msgt_names, val); }
+
+extern const struct value_string osmo_bssmap_le_iei_names[];
+static inline const char *osmo_bssmap_le_iei_name(enum bssmap_le_iei val)
+{ return get_value_string(osmo_bssmap_le_iei_names, val); }
+
+int osmo_lcs_cause_enc(struct msgb *msg, const struct lcs_cause_ie *lcs_cause);
+int osmo_lcs_cause_dec(struct lcs_cause_ie *lcs_cause,
+ enum bssmap_le_msgt msgt, enum bssmap_le_iei iei,
+ struct osmo_bssmap_le_err **err, void *err_ctx,
+ const uint8_t *data, uint8_t len);
+
+int osmo_bssap_le_pdu_to_str_buf(char *buf, size_t buflen, const struct bssap_le_pdu *bssap_le);
+char *osmo_bssap_le_pdu_to_str_c(void *ctx, const struct bssap_le_pdu *bssap_le);
+
+struct msgb *osmo_bssap_le_enc(const struct bssap_le_pdu *pdu);
+int osmo_bssap_le_dec(struct bssap_le_pdu *pdu, struct osmo_bssap_le_err **err, void *err_ctx, struct msgb *msg);
+
+uint8_t osmo_bssmap_le_ie_enc_location_type(struct msgb *msg, const struct bssmap_le_location_type *location_type);
+int osmo_bssmap_le_ie_dec_location_type(struct bssmap_le_location_type *lt,
+ 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);
+
+/*! @} */
diff --git a/include/osmocom/gsm/bts_features.h b/include/osmocom/gsm/bts_features.h
index e84f95b6..341ba405 100644
--- a/include/osmocom/gsm/bts_features.h
+++ b/include/osmocom/gsm/bts_features.h
@@ -23,6 +23,9 @@ enum osmo_bts_features {
BTS_FEAT_SPEECH_F_EFR,
BTS_FEAT_SPEECH_F_AMR,
BTS_FEAT_SPEECH_H_AMR,
+ BTS_FEAT_ETWS_PN,
+ BTS_FEAT_PAGING_COORDINATION, /* BTS hands CS paging to PCU/PACCH */
+ BTS_FEAT_IPV6_NSVC,
_NUM_BTS_FEAT
};
@@ -36,8 +39,14 @@ static inline int osmo_bts_set_feature(struct bitvec *features, enum osmo_bts_fe
return bitvec_set_bit_pos(features, feature, 1);
}
+static inline int osmo_bts_unset_feature(struct bitvec *features, enum osmo_bts_features feature)
+{
+ OSMO_ASSERT(_NUM_BTS_FEAT < MAX_BTS_FEATURES);
+ return bitvec_set_bit_pos(features, feature, 0);
+}
+
static inline bool osmo_bts_has_feature(const struct bitvec *features, enum osmo_bts_features feature)
{
OSMO_ASSERT(_NUM_BTS_FEAT < MAX_BTS_FEATURES);
- return bitvec_get_bit_pos(features, feature);
+ return bitvec_get_bit_pos(features, feature) == ONE;
}
diff --git a/include/osmocom/gsm/cbsp.h b/include/osmocom/gsm/cbsp.h
index 90516cb7..d456ce2f 100644
--- a/include/osmocom/gsm/cbsp.h
+++ b/include/osmocom/gsm/cbsp.h
@@ -6,8 +6,8 @@
#include <osmocom/gsm/gsm0808_utils.h>
/* Definitions for parsed / abstract representation of messages in the
- * CBSP (Cell Broadcast Service Protocol). Data here is *not* formatted
- * like the * on-the-wire format. Any similarities are coincidetial ;) */
+ * CBSP (Cell Broadcast Service Protocol, 3GPP TS 48.049). Data here is *not* formatted
+ * like the on-the-wire format. Any similarities are coincidential ;) */
/* Copyright (C) 2019 Harald Welte <laforge@gnumonks.org>
*
diff --git a/include/osmocom/gsm/gad.h b/include/osmocom/gsm/gad.h
new file mode 100644
index 00000000..57d0e37a
--- /dev/null
+++ b/include/osmocom/gsm/gad.h
@@ -0,0 +1,190 @@
+/*! \addtogroup gad
+ * @{
+ * \file gad.h
+ * Message encoding and decoding for 3GPP TS 23.032 GAD: Universal Geographical Area Description.
+ */
+/*
+ * (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * Author: Neels Hofmeyr <neels@hofmeyr.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/>.
+ *
+ */
+
+#pragma once
+
+#include <osmocom/gsm/protocol/gsm_23_032.h>
+#include <osmocom/core/utils.h>
+
+struct msgb;
+
+struct osmo_gad_ell_point {
+ /*! Latitude in micro degrees (degrees * 1e6), -90'000'000 (S) .. 90'000'000 (N). */
+ int32_t lat;
+ /*! Longitude in micro degrees (degrees * 1e6), -180'000'000 (W) .. 180'000'000 (E). */
+ int32_t lon;
+};
+
+struct osmo_gad_ell_point_unc_circle {
+ /*! Latitude in micro degrees (degrees * 1e6), -90'000'000 (S) .. 90'000'000 (N). */
+ int32_t lat;
+ /*! Longitude in micro degrees (degrees * 1e6), -180'000'000 (W) .. 180'000'000 (E). */
+ int32_t lon;
+ /*! Uncertainty circle radius in millimeters (m * 1e3), 0 .. 18'000'000. */
+ uint32_t unc;
+};
+
+struct osmo_gad_ell_point_unc_ellipse {
+ /*! Latitude in micro degrees (degrees * 1e6), -90'000'000 (S) .. 90'000'000 (N). */
+ int32_t lat;
+ /*! Longitude in micro degrees (degrees * 1e6), -180'000'000 (W) .. 180'000'000 (E). */
+ int32_t lon;
+ /*! Uncertainty ellipsoid radius of major axis in millimeters, 0 .. 18'000'000.
+ * Coding of uncertainty is non-linear, use osmo_gad_dec_unc(osmo_gad_enc_unc(val)) to clamp. */
+ uint32_t unc_semi_major;
+ /*! Uncertainty ellipsoid radius of minor axis in millimeters, 0 .. 18'000'000.
+ * Coding of uncertainty is non-linear, use osmo_gad_dec_unc(osmo_gad_enc_unc(val)) to clamp. */
+ uint32_t unc_semi_minor;
+ /*! Major axis orientation in degrees (DEG), 0 (N) .. 90 (E) .. 179 (SSE). */
+ uint8_t major_ori;
+ /*! Confidence in percent, 0 = no information, 1..100%, 101..128 = no information. */
+ uint8_t confidence;
+};
+
+struct osmo_gad_polygon {
+ uint8_t num_points;
+ struct osmo_gad_ell_point point[15];
+};
+
+struct osmo_gad_ell_point_alt {
+ /*! latitude in micro degrees (degrees * 1e6), -90'000'000 (S) .. 90'000'000 (N). */
+ int32_t lat;
+ /*! longitude in micro degrees (degrees * 1e6), -180'000'000 (W) .. 180'000'000 (E). */
+ int32_t lon;
+ /*! Altitude in meters, -32767 (depth) .. 32767 (height) */
+ int16_t alt;
+};
+
+struct osmo_gad_ell_point_alt_unc_ell {
+ /*! latitude in micro degrees (degrees * 1e6), -90'000'000 (S) .. 90'000'000 (N). */
+ int32_t lat;
+ /*! longitude in micro degrees (degrees * 1e6), -180'000'000 (W) .. 180'000'000 (E). */
+ int32_t lon;
+ /*! Altitude in meters, -32767 (depth) .. 32767 (height) */
+ int16_t alt;
+ /*! Uncertainty ellipsoid radius of major axis in millimeters, 0 .. 18'000'000.
+ * Coding of uncertainty is non-linear, use osmo_gad_dec_unc(osmo_gad_enc_unc(val)) to clamp. */
+ uint32_t unc_semi_major;
+ /*! Uncertainty ellipsoid radius of minor axis in millimeters, 0 .. 18'000'000.
+ * Coding of uncertainty is non-linear, use osmo_gad_dec_unc(osmo_gad_enc_unc(val)) to clamp. */
+ uint32_t unc_semi_minor;
+ /*! Major axis orientation in degrees (DEG), 0 (N) .. 90 (E) .. 179 (SSE). */
+ uint8_t major_ori;
+ /*! Uncertainty altitude in millimeters, 0 .. 990'000.
+ * Coding of uncertainty altitude is non-linear, and distinct from the non-altitude uncertainty coding. Use
+ * osmo_gad_dec_unc_alt(osmo_gad_enc_unc_alt(val)) to clamp. */
+ int32_t unc_alt;
+ /*! Confidence in percent, 0 = no information, 1..100%, 101..128 = no information. */
+ uint8_t confidence;
+};
+
+struct osmo_gad_ell_arc {
+ /*! latitude in micro degrees (degrees * 1e6), -90'000'000 (S) .. 90'000'000 (N). */
+ int32_t lat;
+ /*! longitude in micro degrees (degrees * 1e6), -180'000'000 (W) .. 180'000'000 (E). */
+ int32_t lon;
+ /*! inner circle radius in mm (m * 1e3) */
+ uint32_t inner_r;
+ /*! Uncertainty circle radius in millimeters, 0 .. 18'000'000.
+ * Coding of uncertainty is non-linear, use osmo_gad_dec_unc(osmo_gad_enc_unc(val)) to clamp. */
+ uint32_t unc_r;
+ /*! Offset angle of first arc edge in degrees from North clockwise (eastwards), 0..359.
+ * Angle is coded in increments of 2 degrees. */
+ uint16_t ofs_angle;
+ /*! Included angle defining the angular width of the arc, in degrees clockwise, 1..360.
+ * Angle is coded in increments of 2 degrees. */
+ uint16_t incl_angle;
+ /*! Confidence in percent, 0 = no information, 1..100%, 101..128 = no information. */
+ uint8_t confidence;
+};
+
+struct osmo_gad_ha_ell_point_alt_unc_ell {
+ /*! latitude in micro degrees (degrees * 1e6), -90'000'000 (S) .. 90'000'000 (N). */
+ int32_t lat;
+ /*! longitude in micro degrees (degrees * 1e6), -180'000'000 (W) .. 180'000'000 (E). */
+ int32_t lon;
+ /*! Altitude in millimeters, -500'000 (depth) .. 10'000'000 (height) */
+ int32_t alt;
+ /*! Uncertainty ellipsoid radius of major axis in millimeters, 0 .. 46'491.
+ * Coding of high-accuracy uncertainty is non-linear, use osmo_gad_dec_ha_unc(osmo_gad_enc_ha_unc(val)) to
+ * clamp. */
+ uint32_t unc_semi_major;
+ /*! Uncertainty ellipsoid radius of minor axis in millimeters, 0 .. 46'491.
+ * Coding of high-accuracy uncertainty is non-linear, use osmo_gad_dec_ha_unc(osmo_gad_enc_ha_unc(val)) to
+ * clamp. */
+ uint32_t unc_semi_minor;
+ /*! Major axis orientation in degrees (DEG), 0 (N) .. 90 (E) .. 179 (SSE). */
+ uint8_t major_ori;
+ /*! Horizontal confidence in percent, 0 = no information, 1..100%, 101..128 = no information. */
+ uint8_t h_confidence;
+ /*! High-Accuracy uncertainty altitude */
+ int32_t unc_alt;
+ /*! Vertical confidence in percent, 0 = no information, 1..100%, 101..128 = no information. */
+ uint8_t v_confidence;
+};
+
+struct osmo_gad {
+ enum gad_type type;
+ union {
+ struct osmo_gad_ell_point ell_point;
+ struct osmo_gad_ell_point_unc_circle ell_point_unc_circle;
+ struct osmo_gad_ell_point_unc_ellipse ell_point_unc_ellipse;
+ struct osmo_gad_polygon polygon;
+ struct osmo_gad_ell_point_alt ell_point_alt;
+ struct osmo_gad_ell_point_alt_unc_ell ell_point_alt_unc_ell;
+ struct osmo_gad_ell_arc ell_arc;
+ struct osmo_gad_ell_point_unc_ellipse ha_ell_point_unc_ellipse;
+ struct osmo_gad_ha_ell_point_alt_unc_ell ha_ell_point_alt_unc_ell;
+ };
+};
+
+struct osmo_gad_err {
+ int rc;
+ enum gad_type type;
+ char *logmsg;
+};
+
+extern const struct value_string osmo_gad_type_names[];
+static inline const char *osmo_gad_type_name(enum gad_type val)
+{ return get_value_string(osmo_gad_type_names, val); }
+
+int osmo_gad_raw_write(struct msgb *msg, const union gad_raw *gad_raw);
+int osmo_gad_raw_read(union gad_raw *gad_raw, struct osmo_gad_err **err, void *err_ctx, const uint8_t *data, uint8_t len);
+
+int osmo_gad_enc(union gad_raw *gad_raw, const struct osmo_gad *gad);
+int osmo_gad_dec(struct osmo_gad *gad, struct osmo_gad_err **err, void *err_ctx, const union gad_raw *gad_raw);
+
+int osmo_gad_to_str_buf(char *buf, size_t buflen, const struct osmo_gad *gad);
+char *osmo_gad_to_str_c(void *ctx, const struct osmo_gad *gad);
+
+uint32_t osmo_gad_enc_lat(int32_t deg_1e6);
+int32_t osmo_gad_dec_lat(uint32_t lat);
+uint32_t osmo_gad_enc_lon(int32_t deg_1e6);
+int32_t osmo_gad_dec_lon(uint32_t lon);
+uint8_t osmo_gad_enc_unc(uint32_t mm);
+uint32_t osmo_gad_dec_unc(uint8_t unc);
+/*! @} */
diff --git a/include/osmocom/gsm/gsm0502.h b/include/osmocom/gsm/gsm0502.h
index fe5cf7e1..1be2cc39 100644
--- a/include/osmocom/gsm/gsm0502.h
+++ b/include/osmocom/gsm/gsm0502.h
@@ -7,6 +7,30 @@
#include <osmocom/gsm/protocol/gsm_04_08.h>
#include <osmocom/gsm/protocol/gsm_08_58.h>
+/* 4.3.3 TDMA frame number : constants and modular arithmetic */
+#define GSM_TDMA_FN_DURATION_nS 4615384 /* in 1e−9 seconds (approx) */
+#define GSM_TDMA_FN_DURATION_uS 4615 /* in 1e-6 seconds (approx) */
+
+#define GSM_TDMA_SUPERFRAME (26 * 51)
+#define GSM_TDMA_HYPERFRAME (2048 * GSM_TDMA_SUPERFRAME)
+
+/*! Return the sum of two specified TDMA frame numbers (summation) */
+#define GSM_TDMA_FN_SUM(a, b) \
+ ((a + b) % GSM_TDMA_HYPERFRAME)
+/*! Return the difference of two specified TDMA frame numbers (subtraction) */
+#define GSM_TDMA_FN_SUB(a, b) \
+ ((a + GSM_TDMA_HYPERFRAME - b) % GSM_TDMA_HYPERFRAME)
+/*! Return the *minimum* difference of two specified TDMA frame numbers (distance) */
+#define GSM_TDMA_FN_DIFF(a, b) \
+ OSMO_MIN(GSM_TDMA_FN_SUB(a, b), GSM_TDMA_FN_SUB(b, a))
+
+/*! Increment the given TDMA frame number by 1 and return the result (like ++fn) */
+#define GSM_TDMA_FN_INC(fn) \
+ ((fn) = GSM_TDMA_FN_SUM((fn), 1))
+/*! Decrement the given TDMA frame number by 1 and return the result (like --fn) */
+#define GSM_TDMA_FN_DEC(fn) \
+ ((fn) = GSM_TDMA_FN_SUB((fn), 1))
+
/* Table 5 Clause 7 TS 05.02 */
static inline unsigned int
gsm0502_get_n_pag_blocks(struct gsm48_control_channel_descr *chan_desc)
@@ -35,3 +59,19 @@ gsm0502_get_paging_group(uint64_t imsi, unsigned int bs_cc_chans,
unsigned int
gsm0502_calc_paging_group(struct gsm48_control_channel_descr *chan_desc, uint64_t imsi);
+
+enum gsm0502_fn_remap_channel {
+ FN_REMAP_TCH_F,
+ FN_REMAP_TCH_H0,
+ FN_REMAP_TCH_H1,
+ FN_REMAP_FACCH_F,
+ FN_REMAP_FACCH_H0,
+ FN_REMAP_FACCH_H1,
+ FN_REMAP_MAX,
+};
+
+uint32_t gsm0502_fn_remap(uint32_t fn, enum gsm0502_fn_remap_channel channel);
+
+uint16_t gsm0502_hop_seq_gen(const struct gsm_time *t,
+ uint8_t hsn, uint8_t maio,
+ size_t n, const uint16_t *ma);
diff --git a/include/osmocom/gsm/gsm0808.h b/include/osmocom/gsm/gsm0808.h
index 373b4341..20137daf 100644
--- a/include/osmocom/gsm/gsm0808.h
+++ b/include/osmocom/gsm/gsm0808.h
@@ -60,7 +60,9 @@ struct msgb *gsm0808_create_cipher_reject_ext(enum gsm0808_cause_class class, ui
struct msgb *gsm0808_create_classmark_request();
struct msgb *gsm0808_create_classmark_update(const uint8_t *cm2, uint8_t cm2_len,
const uint8_t *cm3, uint8_t cm3_len);
-struct msgb *gsm0808_create_sapi_reject(uint8_t link_id);
+struct msgb *gsm0808_create_sapi_reject_cause(uint8_t link_id, uint16_t cause);
+struct msgb *gsm0808_create_sapi_reject(uint8_t link_id)
+ OSMO_DEPRECATED("Use gsm0808_create_sapi_reject_cause() instead");
struct msgb *gsm0808_create_ass(const struct gsm0808_channel_type *ct,
const uint16_t *cic,
const struct sockaddr_storage *ss,
@@ -104,6 +106,9 @@ struct msgb *gsm0808_create_lcls_conn_ctrl(enum gsm0808_lcls_config config,
enum gsm0808_lcls_control control);
struct msgb *gsm0808_create_lcls_conn_ctrl_ack(enum gsm0808_lcls_status status);
struct msgb *gsm0808_create_lcls_notification(enum gsm0808_lcls_status status, bool break_req);
+struct msgb *gsm0808_create_common_id(const char *imsi,
+ const struct osmo_plmn_id *selected_plmn_id,
+ const struct osmo_plmn_id *last_used_eutran_plnm_id);
/*! 3GPP TS 48.008 §3.2.2.5.8 Old BSS to New BSS information */
@@ -315,6 +320,13 @@ const char *gsm0808_bssap_name(uint8_t msg_type);
const char *gsm0808_cause_name(enum gsm0808_cause cause);
const char *gsm0808_cause_class_name(enum gsm0808_cause_class class);
+/*! Parse Cause TLV 3GPP TS 08.08 §3.2.2.5
+ * \returns Cause value */
+enum gsm0808_cause gsm0808_get_cause(const struct tlv_parsed *tp);
+
+const char *gsm0808_diagnostics_octet_location_str(uint8_t pointer);
+const char *gsm0808_diagnostics_bit_location_str(uint8_t bit_pointer);
+
extern const struct value_string gsm0808_lcls_config_names[];
extern const struct value_string gsm0808_lcls_control_names[];
extern const struct value_string gsm0808_lcls_status_names[];
diff --git a/include/osmocom/gsm/gsm0808_lcs.h b/include/osmocom/gsm/gsm0808_lcs.h
new file mode 100644
index 00000000..8fcbe380
--- /dev/null
+++ b/include/osmocom/gsm/gsm0808_lcs.h
@@ -0,0 +1,52 @@
+/*! \addtogroup gsm0808
+ * @{
+ * \file gsm0808_lcs.h
+ *
+ * Declarations that depend on both gsm0808.h and bssmap_le.h: LCS related message coding.
+ * (This file prevents circular dependency between struct definitions for BSSMAP messages, since BSSMAP references
+ * struct lcs_cause and struct bssmap_le_location_type, and BSSMAP-LE references gsm0808_cause.
+ */
+/*
+ * (C) 2020 by sysmocom - s.f.m.c. GmbH <info@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.
+ *
+ * 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.
+ *
+ */
+#pragma once
+
+#include <osmocom/gsm/gsm0808.h>
+#include <osmocom/gsm/bssmap_le.h>
+
+struct gsm0808_perform_location_request {
+ struct bssmap_le_location_type location_type;
+ struct osmo_mobile_identity imsi;
+
+ bool more_items; /*!< always set this to false */
+};
+struct msgb *gsm0808_create_perform_location_request(const struct gsm0808_perform_location_request *params);
+
+struct gsm0808_perform_location_response {
+ bool location_estimate_present;
+ union gad_raw location_estimate;
+
+ struct lcs_cause_ie lcs_cause;
+};
+struct msgb *gsm0808_create_perform_location_response(const struct gsm0808_perform_location_response *params);
+
+int gsm0808_enc_lcs_cause(struct msgb *msg, const struct lcs_cause_ie *lcs_cause);
+struct msgb *gsm0808_create_perform_location_abort(const struct lcs_cause_ie *lcs_cause);
+
+/*! @} */
diff --git a/include/osmocom/gsm/gsm0808_utils.h b/include/osmocom/gsm/gsm0808_utils.h
index 76db2b6d..59db6edc 100644
--- a/include/osmocom/gsm/gsm0808_utils.h
+++ b/include/osmocom/gsm/gsm0808_utils.h
@@ -95,6 +95,7 @@ void gsm0808_cell_id_from_cgi(struct gsm0808_cell_id *cid, enum CELL_IDENT id_di
int gsm0808_cell_id_to_cgi(struct osmo_cell_global_id *cgi, const struct gsm0808_cell_id *cid);
void gsm0808_msgb_put_cell_id_u(struct msgb *msg, enum CELL_IDENT id_discr, const union gsm0808_cell_id_u *u);
int gsm0808_decode_cell_id_u(union gsm0808_cell_id_u *out, enum CELL_IDENT discr, const uint8_t *buf, unsigned int len);
+int gsm0808_cell_id_size(enum CELL_IDENT discr);
uint8_t gsm0808_enc_cause(struct msgb *msg, uint16_t cause);
uint8_t gsm0808_enc_aoip_trasp_addr(struct msgb *msg,
@@ -144,7 +145,7 @@ int gsm48_mr_cfg_from_gsm0808_sc_cfg(struct gsm48_multi_rate_conf *cfg, uint16_t
/*! \returns 3GPP TS 08.08 §3.2.2.5 Class of a given Cause */
static inline enum gsm0808_cause_class gsm0808_cause_class(enum gsm0808_cause cause)
{
- return (cause << 1) >> 4;
+ return (cause >> 4) & 0x7;
}
/*! \returns true if 3GPP TS 08.08 §3.2.2.5 Class has extended bit set */
@@ -154,7 +155,8 @@ static inline bool gsm0808_cause_ext(enum gsm0808_cause cause)
return (cause & 0x80) && !(cause & 0x0F);
}
-int gsm0808_get_cipher_reject_cause(const struct tlv_parsed *tp);
+int gsm0808_get_cipher_reject_cause(const struct tlv_parsed *tp)
+OSMO_DEPRECATED("Use gsm0808_get_cause() instead");
/*! \returns 3GPP TS 48.008 3.2.2.49 Current Channel Type 1 from enum gsm_chan_t. */
static inline uint8_t gsm0808_current_channel_type_1(enum gsm_chan_t type)
diff --git a/include/osmocom/gsm/gsm23236.h b/include/osmocom/gsm/gsm23236.h
new file mode 100644
index 00000000..23b003a1
--- /dev/null
+++ b/include/osmocom/gsm/gsm23236.h
@@ -0,0 +1,60 @@
+/*! \file gsm23236.h
+ * API to handle Network Resource Indicator (NRI) values and ranges for MSC pooling, as in 3GPP TS 23.236.
+ */
+
+#pragma once
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#include <osmocom/core/linuxlist.h>
+
+#define OSMO_NRI_BITLEN_MIN 1
+#define OSMO_NRI_BITLEN_MAX 15
+#define OSMO_NRI_BITLEN_DEFAULT 10
+
+/*! One range of NRI values. An NRI is a Network Resource Indicator, a number of a configured bit length (typically 10
+ * bit). In an MSC pool for load balancing, it is used to indicate which MSC has issued a TMSI: the NRI is located with
+ * its most significant bit located on the TMSI's second octet's most significant bit. See 3GPP TS 23.236. */
+struct osmo_nri_range {
+ struct llist_head entry;
+
+ /*! First value of the NRI range, i.e. inclusive. */
+ int16_t first;
+ /*! Last value of the NRI range, i.e. inclusive. */
+ int16_t last;
+};
+
+/*! A list of struct osmo_nri_range. Use osmo_nri_ranges_alloc() to create, and osmo_nri_ranges_free() (or talloc_free()
+ * on the parent context) to destroy. Always use osmo_nri_ranges_add() to insert entries, to ensure that the list
+ * remains sorted by 'first' values, which some of the osmo_nri_ranges API assumes to always be true.
+ *
+ * This struct serves as talloc context for the osmo_nri_range entries in the list, simplifying function signatures. It
+ * also makes the API future proof, to easily accomodate possible future additions.
+ */
+struct osmo_nri_ranges {
+ /* List of struct osmo_nri_range entries, talloc allocated from the parent struct osmo_nri_ranges. */
+ struct llist_head entries;
+};
+
+int osmo_nri_v_validate(int16_t nri_v, uint8_t nri_bitlen);
+bool osmo_nri_v_matches_ranges(int16_t nri_v, const struct osmo_nri_ranges *nri_ranges);
+int osmo_nri_v_limit_by_ranges(int16_t *nri_v, const struct osmo_nri_ranges *nri_ranges, uint32_t nri_bitlen);
+
+int osmo_tmsi_nri_v_get(int16_t *nri_v, uint32_t tmsi, uint8_t nri_bitlen);
+int osmo_tmsi_nri_v_set(uint32_t *tmsi, int16_t nri_v, uint8_t nri_bitlen);
+int osmo_tmsi_nri_v_limit_by_ranges(uint32_t *tmsi, const struct osmo_nri_ranges *nri_ranges, uint8_t nri_bitlen);
+
+int osmo_nri_range_validate(const struct osmo_nri_range *range, uint8_t nri_bitlen);
+bool osmo_nri_range_overlaps_ranges(const struct osmo_nri_range *range, const struct osmo_nri_ranges *nri_ranges);
+
+struct osmo_nri_ranges *osmo_nri_ranges_alloc(void *ctx);
+void osmo_nri_ranges_free(struct osmo_nri_ranges *nri_ranges);
+int osmo_nri_ranges_add(struct osmo_nri_ranges *nri_ranges, const struct osmo_nri_range *add);
+int osmo_nri_ranges_del(struct osmo_nri_ranges *nri_ranges, const struct osmo_nri_range *del);
+int osmo_nri_ranges_vty_add(const char **message, struct osmo_nri_range *added_range,
+ struct osmo_nri_ranges *nri_ranges, int argc, const char **argv, uint8_t nri_bitlen);
+int osmo_nri_ranges_vty_del(const char **message, struct osmo_nri_range *removed_range,
+ struct osmo_nri_ranges *nri_ranges, int argc, const char **argv);
+int osmo_nri_ranges_to_str_buf(char *buf, size_t buflen, const struct osmo_nri_ranges *nri_ranges);
+char *osmo_nri_ranges_to_str_c(void *ctx, const struct osmo_nri_ranges *nri_ranges);
diff --git a/include/osmocom/gsm/gsm48.h b/include/osmocom/gsm/gsm48.h
index 7c68b1d2..f772f4a1 100644
--- a/include/osmocom/gsm/gsm48.h
+++ b/include/osmocom/gsm/gsm48.h
@@ -4,10 +4,12 @@
#include <stdbool.h>
+#include <osmocom/core/defs.h>
#include <osmocom/core/msgb.h>
#include <osmocom/gsm/tlv.h>
#include <osmocom/gsm/protocol/gsm_04_08.h>
+#include <osmocom/gsm/protocol/gsm_23_003.h>
#include <osmocom/gsm/gsm48_ie.h>
#include <osmocom/gsm/gsm23003.h>
@@ -48,16 +50,55 @@ void gsm48_generate_lai(struct gsm48_loc_area_id *lai48, uint16_t mcc,
void gsm48_generate_lai2(struct gsm48_loc_area_id *lai48, const struct osmo_location_area_id *lai);
#define GSM48_MID_MAX_SIZE 11
-int gsm48_generate_mid_from_tmsi(uint8_t *buf, uint32_t tmsi);
-int gsm48_generate_mid_from_imsi(uint8_t *buf, const char *imsi);
-uint8_t gsm48_generate_mid(uint8_t *buf, const char *id, uint8_t mi_type);
+int gsm48_generate_mid_from_tmsi(uint8_t *buf, uint32_t tmsi)
+ OSMO_DEPRECATED_OUTSIDE("Instead use: l = msgb_tl_put(msg, GSM48_IE_MOBILE_ID);"
+ " *l = osmo_mobile_identity_encode_msgb(...)");
+int gsm48_generate_mid_from_imsi(uint8_t *buf, const char *imsi)
+ OSMO_DEPRECATED_OUTSIDE("Instead use: l = msgb_tl_put(msg, GSM48_IE_MOBILE_ID);"
+ " *l = osmo_mobile_identity_encode_msgb(...)");
+uint8_t gsm48_generate_mid(uint8_t *buf, const char *id, uint8_t mi_type)
+ OSMO_DEPRECATED_OUTSIDE("Instead use: l = msgb_tl_put(msg, GSM48_IE_MOBILE_ID);"
+ " *l = osmo_mobile_identity_encode_msgb(...)");
-/* Convert Mobile Identity (10.5.1.4) to string */
-int gsm48_mi_to_string(char *string, int str_len, const uint8_t *mi, int mi_len);
const char *gsm48_mi_type_name(uint8_t mi);
-const char *osmo_mi_name(const uint8_t *mi, uint8_t mi_len);
-char *osmo_mi_name_buf(char *buf, size_t buf_len, const uint8_t *mi, uint8_t mi_len);
-char *osmo_mi_name_c(const void *ctx, const uint8_t *mi, uint8_t mi_len);
+/* Convert encoded Mobile Identity (10.5.1.4) to string */
+int gsm48_mi_to_string(char *string, int str_len, const uint8_t *mi, int mi_len)
+ OSMO_DEPRECATED_OUTSIDE("Instead use osmo_mobile_identity_decode()");
+const char *osmo_mi_name(const uint8_t *mi, uint8_t mi_len)
+ OSMO_DEPRECATED_OUTSIDE("Instead use osmo_mobile_identity_to_str_c()");
+char *osmo_mi_name_buf(char *buf, size_t buf_len, const uint8_t *mi, uint8_t mi_len)
+ OSMO_DEPRECATED_OUTSIDE("Instead use osmo_mobile_identity_to_str_buf()");
+char *osmo_mi_name_c(const void *ctx, const uint8_t *mi, uint8_t mi_len)
+ OSMO_DEPRECATED_OUTSIDE("Instead use osmo_mobile_identity_to_str_c()");
+
+/*! Decoded representation of a Mobile Identity (3GPP TS 24.008 10.5.1.4).
+ * See osmo_mobile_identity_decode() and osmo_mobile_identity_from_l3(). */
+struct osmo_mobile_identity {
+ /*! A GSM_MI_TYPE_* constant (like GSM_MI_TYPE_IMSI). */
+ uint8_t type;
+ /*! Decoded Mobile Identity digits or TMSI value. IMSI, IMEI and IMEISV as digits like
+ * "12345678", and TMSI is represented as raw uint32_t. */
+ union {
+ /*! type == GSM_MI_TYPE_IMSI. */
+ char imsi[GSM23003_IMSI_MAX_DIGITS + 1];
+ /*! type == GSM_MI_TYPE_IMEI. */
+ char imei[GSM23003_IMEI_NUM_DIGITS + 1];
+ /*! type == GSM_MI_TYPE_IMEISV. */
+ char imeisv[GSM23003_IMEISV_NUM_DIGITS + 1];
+ /*! TMSI / P-TMSI / M-TMSI integer value if type == GSM_MI_TYPE_TMSI. */
+ uint32_t tmsi;
+ };
+};
+
+int osmo_mobile_identity_to_str_buf(char *buf, size_t buflen, const struct osmo_mobile_identity *mi);
+char *osmo_mobile_identity_to_str_c(void *ctx, const struct osmo_mobile_identity *mi);
+int osmo_mobile_identity_cmp(const struct osmo_mobile_identity *a, const struct osmo_mobile_identity *b);
+int osmo_mobile_identity_decode(struct osmo_mobile_identity *mi, const uint8_t *mi_data, uint8_t mi_len,
+ bool allow_hex);
+int osmo_mobile_identity_decode_from_l3(struct osmo_mobile_identity *mi, struct msgb *msg, bool allow_hex);
+int osmo_mobile_identity_encoded_len(const struct osmo_mobile_identity *mi, int *mi_digits);
+int osmo_mobile_identity_encode_buf(uint8_t *buf, size_t buflen, const struct osmo_mobile_identity *mi, bool allow_hex);
+int osmo_mobile_identity_encode_msgb(struct msgb *msg, const struct osmo_mobile_identity *mi, bool allow_hex);
/* Parse Routeing Area Identifier */
void gsm48_parse_ra(struct gprs_ra_id *raid, const uint8_t *buf);
diff --git a/include/osmocom/gsm/gsm48_ie.h b/include/osmocom/gsm/gsm48_ie.h
index 71050df5..339aa136 100644
--- a/include/osmocom/gsm/gsm48_ie.h
+++ b/include/osmocom/gsm/gsm48_ie.h
@@ -7,6 +7,7 @@
#include <errno.h>
#include <osmocom/core/msgb.h>
+#include <osmocom/core/defs.h>
#include <osmocom/gsm/tlv.h>
#include <osmocom/gsm/mncc.h>
#include <osmocom/gsm/protocol/gsm_04_08.h>
@@ -14,7 +15,7 @@
/* decode a 'called/calling/connect party BCD number' as in 10.5.4.7 */
int gsm48_decode_bcd_number(char *output, int output_len,
const uint8_t *bcd_lv, int h_len)
- OSMO_DEPRECATED("Use gsm48_decode_bcd_number2() for improved bounds checking");
+ OSMO_DEPRECATED_OUTSIDE("Use gsm48_decode_bcd_number2() for improved bounds checking");
int gsm48_decode_bcd_number2(char *output, size_t output_len,
const uint8_t *bcd_lv, size_t input_len,
size_t h_len);
diff --git a/include/osmocom/gsm/gsm48_rest_octets.h b/include/osmocom/gsm/gsm48_rest_octets.h
index d3bb878e..8f143be7 100644
--- a/include/osmocom/gsm/gsm48_rest_octets.h
+++ b/include/osmocom/gsm/gsm48_rest_octets.h
@@ -125,3 +125,6 @@ int osmo_gsm48_rest_octets_si13_encode(uint8_t *data, const struct osmo_gsm48_si
/* Parse SI3 Rest Octets */
void osmo_gsm48_rest_octets_si3_decode(struct osmo_gsm48_si_ro_info *si3, const uint8_t *data);
+
+/* Parse SI4 Rest Octets */
+void osmo_gsm48_rest_octets_si4_decode(struct osmo_gsm48_si_ro_info *si4, const uint8_t *data, int len);
diff --git a/include/osmocom/gsm/gsm_utils.h b/include/osmocom/gsm/gsm_utils.h
index 7a5da9a6..de634348 100644
--- a/include/osmocom/gsm/gsm_utils.h
+++ b/include/osmocom/gsm/gsm_utils.h
@@ -30,7 +30,6 @@
#include <osmocom/core/defs.h>
#include <osmocom/core/utils.h>
-#include <osmocom/gsm/protocol/gsm_04_08.h>
#define ADD_MODULO(sum, delta, modulo) do { \
if ((sum += delta) >= modulo) \
@@ -116,8 +115,7 @@ int gsm_septet_encode(uint8_t *result, const char *data);
uint8_t gsm_get_octet_len(const uint8_t sept_len);
int gsm_7bit_decode_n_hdr(char *decoded, size_t n, const uint8_t *user_data, uint8_t length, uint8_t ud_hdr_ind);
-unsigned int ms_class_gmsk_dbm(enum gsm_band band, int ms_class);
-
+int ms_class_gmsk_dbm(enum gsm_band band, int ms_class);
int ms_pwr_ctl_lvl(enum gsm_band band, unsigned int dbm);
int ms_pwr_dbm(enum gsm_band band, uint8_t lvl);
diff --git a/include/osmocom/gsm/gsup.h b/include/osmocom/gsm/gsup.h
index be856620..56d7a309 100644
--- a/include/osmocom/gsm/gsup.h
+++ b/include/osmocom/gsm/gsup.h
@@ -45,6 +45,7 @@
#include <osmocom/gsm/protocol/gsm_03_40.h>
#include <osmocom/gsm/protocol/gsm_04_08_gprs.h>
#include <osmocom/gsm/protocol/gsm_08_08.h>
+#include <osmocom/gsm/gsm_utils.h>
#include <osmocom/crypt/auth.h>
#define OSMO_GSUP_PORT 4222
@@ -86,6 +87,8 @@ enum osmo_gsup_iei {
OSMO_GSUP_AUTS_IE = 0x26,
OSMO_GSUP_RES_IE = 0x27,
OSMO_GSUP_CN_DOMAIN_IE = 0x28,
+ OSMO_GSUP_SUPPORTED_RAT_TYPES_IE = 0x29, /* supported RAT types */
+ OSMO_GSUP_CURRENT_RAT_TYPE_IE = 0x2a, /* currently used RAT type */
OSMO_GSUP_SESSION_ID_IE = 0x30,
OSMO_GSUP_SESSION_STATE_IE = 0x31,
@@ -104,6 +107,7 @@ enum osmo_gsup_iei {
OSMO_GSUP_IMEI_IE = 0x50,
OSMO_GSUP_IMEI_RESULT_IE = 0x51,
+ OSMO_GSUP_NUM_VECTORS_REQ_IE = 0x52,
/* Inter-MSC handover related */
OSMO_GSUP_SOURCE_NAME_IE = 0x60,
@@ -193,12 +197,17 @@ enum osmo_gsup_message_type {
OSMO_GSUP_MSGT_E_CLOSE = 0b01000111,
OSMO_GSUP_MSGT_E_ABORT = 0b01001011,
- OSMO_GSUP_MSGT_E_ROUTING_ERROR = 0b01001110,
+ OSMO_GSUP_MSGT_ROUTING_ERROR = 0b01001110,
};
+#define OSMO_GSUP_MSGT_E_ROUTING_ERROR OSMO_GSUP_MSGT_ROUTING_ERROR
+
#define OSMO_GSUP_IS_MSGT_REQUEST(msgt) (((msgt) & 0b00000011) == 0b00)
#define OSMO_GSUP_IS_MSGT_ERROR(msgt) (((msgt) & 0b00000011) == 0b01)
+#define OSMO_GSUP_IS_MSGT_RESULT(msgt) (((msgt) & 0b00000011) == 0b10)
+#define OSMO_GSUP_TO_MSGT_REQUEST(msgt) (((msgt) & 0b11111100))
#define OSMO_GSUP_TO_MSGT_ERROR(msgt) (((msgt) & 0b11111100) | 0b01)
+#define OSMO_GSUP_TO_MSGT_RESULT(msgt) (((msgt) & 0b11111100) | 0b10)
extern const struct value_string osmo_gsup_message_type_names[];
static inline const char *
@@ -373,6 +382,10 @@ struct osmo_gsup_message {
/*! Session Management cause as of 3GPP TS 24.008 10.5.6.6 / Table 10.5.157. */
enum gsm48_gsm_cause cause_sm;
+
+ enum osmo_rat_type current_rat_type;
+ enum osmo_rat_type supported_rat_types[8]; /*!< arbitrary choice */
+ size_t supported_rat_types_len;
};
int osmo_gsup_decode(const uint8_t *data, size_t data_len,
diff --git a/include/osmocom/gsm/i460_mux.h b/include/osmocom/gsm/i460_mux.h
new file mode 100644
index 00000000..8e392434
--- /dev/null
+++ b/include/osmocom/gsm/i460_mux.h
@@ -0,0 +1,121 @@
+/*! \file i460_mux.h
+ * ITU-T I.460 sub-channel multiplexer + demultiplexer */
+/*
+ * (C) 2020 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.
+ *
+ * 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.
+ */
+
+#pragma once
+#include <stdint.h>
+#include <osmocom/core/bits.h>
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/msgb.h>
+
+/* I.460 sub-slot rate */
+enum osmo_i460_rate {
+ OSMO_I460_RATE_NONE, /* disabled */
+ OSMO_I460_RATE_64k,
+ OSMO_I460_RATE_32k,
+ OSMO_I460_RATE_16k,
+ OSMO_I460_RATE_8k,
+};
+
+struct osmo_i460_subchan;
+
+typedef void (*out_cb_bits_t)(struct osmo_i460_subchan *schan, void *user_data,
+ const ubit_t *bits, unsigned int num_bits);
+typedef void (*out_cb_bytes_t)(struct osmo_i460_subchan *schan, void *user_data,
+ const uint8_t *bytes, unsigned int num_bytes);
+
+struct osmo_i460_subchan_demux {
+ /*! bit-buffer for output bits */
+ uint8_t *out_bitbuf;
+ /*! size of out_bitbuf in bytes */
+ unsigned int out_bitbuf_size;
+ /*! offset of next bit to be written in out_bitbuf */
+ unsigned int out_idx;
+ /*! callback to be called once we have received out_bitbuf_size bits */
+ out_cb_bits_t out_cb_bits;
+ out_cb_bytes_t out_cb_bytes;
+ void *user_data;
+};
+
+typedef void (*in_cb_queue_empty_t)(struct osmo_i460_subchan *schan, void *user_data);
+
+struct osmo_i460_subchan_mux {
+ /*! list of to-be-transmitted message buffers */
+ struct llist_head tx_queue;
+ in_cb_queue_empty_t in_cb_queue_empty;
+ void *user_data;
+};
+
+struct osmo_i460_subchan {
+ struct osmo_i460_timeslot *ts; /* back-pointer */
+ enum osmo_i460_rate rate; /* 8/16/32/64k */
+ uint8_t bit_offset; /* bit offset inside each byte of the B-channel */
+ struct osmo_i460_subchan_demux demux;
+ struct osmo_i460_subchan_mux mux;
+};
+
+struct osmo_i460_timeslot {
+ struct osmo_i460_subchan schan[8];
+};
+
+/*! description of a sub-channel; passed by caller */
+struct osmo_i460_schan_desc {
+ enum osmo_i460_rate rate;
+ uint8_t bit_offset;
+ struct {
+ /* size (in bits) of the internal buffer; determines granularity */
+ size_t num_bits;
+ /*! call-back function called whenever we received num_bits */
+ out_cb_bits_t out_cb_bits;
+ /*! out_cb_bytes call-back function called whenever we received num_bits.
+ * The user is usually expected to provide either out_cb_bits or out_cb_bytes. If only
+ * out_cb_bits is provided, output data will always be provided as unpacked bits; if only
+ * out_cb_bytes is provided, output data will always be provided as packet bits (bytes). If
+ * both are provided, it is up to the I.460 multiplex to decide if it calls either of the two,
+ * depending on what can be provided without extra conversion. */
+ out_cb_bytes_t out_cb_bytes;
+ /* opaque user data pointer to pass to out_cb */
+ void *user_data;
+ } demux;
+
+ struct {
+ /* call-back function whenever the muxer requires more input data from the sub-channels,
+ * but has nothing enqueued yet. A typical function would then call osmo_i460_mux_enqueue() */
+ in_cb_queue_empty_t in_cb_queue_empty;
+ /* opaque user data pointer to pass to in_cb */
+ void *user_data;
+ } mux;
+};
+
+void osmo_i460_demux_in(struct osmo_i460_timeslot *ts, const uint8_t *data, size_t data_len);
+
+void osmo_i460_mux_enqueue(struct osmo_i460_subchan *schan, struct msgb *msg);
+int osmo_i460_mux_out(struct osmo_i460_timeslot *ts, uint8_t *out, size_t out_len);
+
+void osmo_i460_ts_init(struct osmo_i460_timeslot *ts);
+
+struct osmo_i460_subchan *
+osmo_i460_subchan_add(void *ctx, struct osmo_i460_timeslot *ts, const struct osmo_i460_schan_desc *chd);
+
+void osmo_i460_subchan_del(struct osmo_i460_subchan *schan);
+
+/*! @} */
diff --git a/include/osmocom/gsm/l1sap.h b/include/osmocom/gsm/l1sap.h
index 19cc87a3..d1396908 100644
--- a/include/osmocom/gsm/l1sap.h
+++ b/include/osmocom/gsm/l1sap.h
@@ -67,8 +67,8 @@ struct ph_rach_ind_param {
/* elements added on 2018-02-26 */
int8_t rssi; /*!< RSSI of RACH indication */
uint16_t ber10k; /*!< BER in units of 0.01% */
- int16_t acc_delay_256bits;/* !< Burst TA Offset in 1/256th bits */
- int16_t lqual_cb; /* !< Link quality in centiBel */
+ int16_t acc_delay_256bits;/*!< Burst TA Offset in 1/256th bits */
+ int16_t lqual_cb; /*!< Link quality in centiBel */
};
/*! for PH-[UNIT]DATA.{req,ind} | PH-RTS.ind */
@@ -79,11 +79,12 @@ struct ph_data_param {
int8_t rssi; /*!< RSSI of receivedindication */
uint16_t ber10k; /*!< BER in units of 0.01% */
union {
- int16_t ta_offs_qbits; /* !< Burst TA Offset in quarter bits */
+ int16_t ta_offs_qbits; /*!< Burst TA Offset in quarter bits */
int16_t ta_offs_256bits;/*!< timing advance offset (in 1/256th bits) */
};
- int16_t lqual_cb; /* !< Link quality in centiBel */
+ int16_t lqual_cb; /*!< Link quality in centiBel */
enum osmo_ph_pres_info_type pdch_presence_info; /*!< Info regarding presence/validity of header and data parts */
+ uint8_t is_sub:1; /*!< flags */
};
/*! for TCH.{req,ind} | TCH-RTS.ind */
@@ -93,7 +94,9 @@ struct ph_tch_param {
int8_t rssi; /*!< RSSI of received indication */
uint8_t marker; /*!< RTP Marker bit (speech onset indicator) */
uint16_t ber10k; /*!< BER in units of 0.01% */
- int16_t lqual_cb; /* !< Link quality in centiBel */
+ int16_t lqual_cb; /*!< Link quality in centiBel */
+ int16_t ta_offs_256bits;/*!< timing advance offset (in 1/256th bits) */
+ uint8_t is_sub:1; /*!< flags */
};
/*! for PH-CONN.ind */
@@ -123,7 +126,7 @@ struct info_meas_ind_param {
/*! for {ACTIVATE,DEACTIVATE,MODIFY} MPH-INFO.req */
struct info_act_req_param {
uint8_t chan_nr; /*!< Channel Number (Like RSL) */
- uint8_t sacch_only; /*!< \breif Only deactivate SACCH */
+ uint8_t sacch_only; /*!< \brief Only deactivate SACCH */
};
/*! for {ACTIVATE,DEACTIVATE} MPH-INFO.cnf */
diff --git a/include/osmocom/gsm/lapd_core.h b/include/osmocom/gsm/lapd_core.h
index cfc357a7..69e10875 100644
--- a/include/osmocom/gsm/lapd_core.h
+++ b/include/osmocom/gsm/lapd_core.h
@@ -14,6 +14,9 @@
* \file lapd_core.h
*/
+#define LOGDL(dl, level, fmt, args...) \
+ LOGP(DLLAPD, level, "(%s) " fmt, (dl)->name, ## args)
+
/*! LAPD related primitives (L2<->L3 SAP)*/
enum osmo_dl_prim {
PRIM_DL_UNIT_DATA, /*!< DL-UNIT-DATA */
@@ -158,10 +161,13 @@ struct lapd_datalink {
uint8_t range_hist; /*!< range of history buffer 2..2^n */
struct msgb *rcv_buffer; /*!< buffer to assemble the received message */
struct msgb *cont_res; /*!< buffer to store content resolution data on network side, to detect multiple phones on same channel */
+ char *name; /*!< user-provided name */
};
-void lapd_dl_init(struct lapd_datalink *dl, uint8_t k, uint8_t v_range,
- int maxf);
+void lapd_dl_init(struct lapd_datalink *dl, uint8_t k, uint8_t v_range, int maxf)
+ OSMO_DEPRECATED("Use lapd_dl_init2() instead");
+void lapd_dl_init2(struct lapd_datalink *dl, uint8_t k, uint8_t v_range, int maxf, const char *name);
+void lapd_dl_set_name(struct lapd_datalink *dl, const char *name);
void lapd_dl_exit(struct lapd_datalink *dl);
void lapd_dl_reset(struct lapd_datalink *dl);
int lapd_set_mode(struct lapd_datalink *dl, enum lapd_mode mode);
diff --git a/include/osmocom/gsm/lapdm.h b/include/osmocom/gsm/lapdm.h
index 931de80a..633df1ad 100644
--- a/include/osmocom/gsm/lapdm.h
+++ b/include/osmocom/gsm/lapdm.h
@@ -83,13 +83,19 @@ struct lapdm_datalink *lapdm_datalink_for_sapi(struct lapdm_entity *le, uint8_t
/* initialize a LAPDm entity */
void lapdm_entity_init(struct lapdm_entity *le, enum lapdm_mode mode, int t200)
- OSMO_DEPRECATED("Use lapdm_entity_init2() instead");
+ OSMO_DEPRECATED("Use lapdm_entity_init3() instead");
void lapdm_entity_init2(struct lapdm_entity *le, enum lapdm_mode mode,
- const int *t200_ms, int n200);
+ const int *t200_ms, int n200)
+ OSMO_DEPRECATED("Use lapdm_entity_init3() instead");
+void lapdm_entity_init3(struct lapdm_entity *le, enum lapdm_mode mode,
+ const int *t200_ms, int n200, const char *name_pfx);
void lapdm_channel_init(struct lapdm_channel *lc, enum lapdm_mode mode)
- OSMO_DEPRECATED("Use lapdm_channel_init2() instead");
+ OSMO_DEPRECATED("Use lapdm_channel_init3() instead");
int lapdm_channel_init2(struct lapdm_channel *lc, enum lapdm_mode mode,
const int *t200_ms_dcch, const int *t200_ms_acch, enum gsm_chan_t chan_t);
+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,
+ const char *name_pfx);
/* deinitialize a LAPDm entity */
void lapdm_entity_exit(struct lapdm_entity *le);
void lapdm_channel_exit(struct lapdm_channel *lc);
diff --git a/include/osmocom/gsm/mncc.h b/include/osmocom/gsm/mncc.h
index e5e96074..db70235c 100644
--- a/include/osmocom/gsm/mncc.h
+++ b/include/osmocom/gsm/mncc.h
@@ -10,10 +10,10 @@
/* Expanded fields from GSM TS 04.08, Table 10.5.102 */
struct gsm_mncc_bearer_cap {
- int transfer; /* Information Transfer Capability */
- int mode; /* Transfer Mode */
- int coding; /* Coding Standard */
- int radio; /* Radio Channel Requirement */
+ int transfer; /* Information Transfer Capability, see enum gsm48_bcap_itcap. */
+ int mode; /* Transfer Mode, see enum gsm48_bcap_tmod. */
+ int coding; /* Coding Standard, see enum gsm48_bcap_coding.*/
+ int radio; /* Radio Channel Requirement, see enum gsm48_bcap_rrq. */
int speech_ctm; /* CTM text telephony indication */
int speech_ver[8]; /* Speech version indication, see enum gsm48_bcap_speech_ver; -1 marks end */
struct {
diff --git a/include/osmocom/gsm/prim.h b/include/osmocom/gsm/prim.h
index 386b7d89..e7a60e30 100644
--- a/include/osmocom/gsm/prim.h
+++ b/include/osmocom/gsm/prim.h
@@ -14,4 +14,6 @@ enum osmo_gsm_sap {
SAP_BSSGP_LL,
SAP_BSSGP_NM,
SAP_BSSGP_PFM,
+
+ SAP_NS,
};
diff --git a/include/osmocom/gsm/protocol/gsm_04_08.h b/include/osmocom/gsm/protocol/gsm_04_08.h
index 2be6ed34..f8f2eabd 100644
--- a/include/osmocom/gsm/protocol/gsm_04_08.h
+++ b/include/osmocom/gsm/protocol/gsm_04_08.h
@@ -9,6 +9,8 @@
#include <osmocom/core/utils.h>
#include <osmocom/core/endian.h>
+#include <osmocom/gsm/gsm_utils.h>
+
struct gsm_lchan;
/* Chapter 10.5.1.5 */
@@ -73,9 +75,8 @@ const char *osmo_gsm48_classmark_a5_name(const struct osmo_gsm48_classmark *cm);
char *osmo_gsm48_classmark_a5_name_buf(char *buf, size_t buf_len, const struct osmo_gsm48_classmark *cm);
char *osmo_gsm48_classmark_a5_name_c(const void *ctx, const struct osmo_gsm48_classmark *cm);
void osmo_gsm48_classmark_update(struct osmo_gsm48_classmark *dst, const struct osmo_gsm48_classmark *src);
-
+int8_t osmo_gsm48_rfpowercap2powerclass(enum gsm_band band, uint8_t rf_power_cap);
/* Chapter 10.5.2.1b.3 */
-#if OSMO_IS_LITTLE_ENDIAN == 1
struct gsm48_range_1024 {
#if OSMO_IS_LITTLE_ENDIAN
uint8_t w1_hi:2,
@@ -129,64 +130,8 @@ struct gsm48_range_1024 {
uint8_t w15_lo:2, w16:6;
#endif
} __attribute__ ((packed));
-#else
-struct gsm48_range_1024 {
-#if OSMO_IS_LITTLE_ENDIAN
- uint8_t form_id:5,
- f0:1,
- w1_hi:2;
- uint8_t w1_lo;
- uint8_t w2_hi;
- uint8_t w2_lo:1,
- w3_hi:7;
- uint8_t w3_lo:2,
- w4_hi:6;
- uint8_t w4_lo:2,
- w5_hi:6;
- uint8_t w5_lo:2,
- w6_hi:6;
- uint8_t w6_lo:2,
- w7_hi:6;
- uint8_t w7_lo:2,
- w8_hi:6;
- uint8_t w8_lo:1,
- w9:7;
- uint8_t w10:7,
- w11_hi:1;
- uint8_t w11_lo:6,
- w12_hi:2;
- uint8_t w12_lo:5,
- w13_hi:3;
- uint8_t w13_lo:4,
- w14_hi:4;
- uint8_t w14_lo:3,
- w15_hi:5;
- uint8_t w15_lo:2,
- w16:6;
-#elif OSMO_IS_BIG_ENDIAN
-/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianess.py) */
- uint8_t w1_hi:2, f0:1, form_id:5;
- uint8_t w1_lo;
- uint8_t w2_hi;
- uint8_t w3_hi:7, w2_lo:1;
- uint8_t w4_hi:6, w3_lo:2;
- uint8_t w5_hi:6, w4_lo:2;
- uint8_t w6_hi:6, w5_lo:2;
- uint8_t w7_hi:6, w6_lo:2;
- uint8_t w8_hi:6, w7_lo:2;
- uint8_t w9:7, w8_lo:1;
- uint8_t w11_hi:1, w10:7;
- uint8_t w12_hi:2, w11_lo:6;
- uint8_t w13_hi:3, w12_lo:5;
- uint8_t w14_hi:4, w13_lo:4;
- uint8_t w15_hi:5, w14_lo:3;
- uint8_t w16:6, w15_lo:2;
-#endif
-} __attribute__ ((packed));
-#endif
/* Chapter 10.5.2.1b.4 */
-#if OSMO_IS_LITTLE_ENDIAN == 1
struct gsm48_range_512 {
#if OSMO_IS_LITTLE_ENDIAN
uint8_t orig_arfcn_hi:1,
@@ -240,64 +185,8 @@ struct gsm48_range_512 {
uint8_t w16_lo:3, w17:5;
#endif
} __attribute__ ((packed));
-#else
-struct gsm48_range_512 {
-#if OSMO_IS_LITTLE_ENDIAN
- uint8_t form_id:7,
- orig_arfcn_hi:1;
- uint8_t orig_arfcn_mid;
- uint8_t orig_arfcn_lo:1,
- w1_hi:7;
- uint8_t w1_lo:2,
- w2_hi:6;
- uint8_t w2_lo:2,
- w3_hi:6;
- uint8_t w3_lo:2,
- w4_hi:6;
- uint8_t w4_lo:1,
- w5:7;
- uint8_t w6:7,
- w7_hi:1;
- uint8_t w7_lo:6,
- w8_hi:2;
- uint8_t w8_lo:4,
- w9_hi:4;
- uint8_t w9_lo:2,
- w10:6;
- uint8_t w11:6,
- w12_hi:2;
- uint8_t w12_lo:4,
- w13_hi:4;
- uint8_t w13_lo:2,
- w14:6;
- uint8_t w15:6,
- w16_hi:2;
- uint8_t w16_lo:3,
- w17:5;
-#elif OSMO_IS_BIG_ENDIAN
-/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianess.py) */
- uint8_t orig_arfcn_hi:1, form_id:7;
- uint8_t orig_arfcn_mid;
- uint8_t w1_hi:7, orig_arfcn_lo:1;
- uint8_t w2_hi:6, w1_lo:2;
- uint8_t w3_hi:6, w2_lo:2;
- uint8_t w4_hi:6, w3_lo:2;
- uint8_t w5:7, w4_lo:1;
- uint8_t w7_hi:1, w6:7;
- uint8_t w8_hi:2, w7_lo:6;
- uint8_t w9_hi:4, w8_lo:4;
- uint8_t w10:6, w9_lo:2;
- uint8_t w12_hi:2, w11:6;
- uint8_t w13_hi:4, w12_lo:4;
- uint8_t w14:6, w13_lo:2;
- uint8_t w16_hi:2, w15:6;
- uint8_t w17:5, w16_lo:3;
-#endif
-} __attribute__ ((packed));
-#endif
/* Chapter 10.5.2.1b.5 */
-#if OSMO_IS_LITTLE_ENDIAN == 1
struct gsm48_range_256 {
#if OSMO_IS_LITTLE_ENDIAN
uint8_t orig_arfcn_hi:1,
@@ -357,70 +246,8 @@ struct gsm48_range_256 {
uint8_t w20_lo:3, w21:4, spare:1;
#endif
} __attribute__ ((packed));
-#else
-struct gsm48_range_256 {
-#if OSMO_IS_LITTLE_ENDIAN
- uint8_t form_id:7,
- orig_arfcn_hi:1;
- uint8_t orig_arfcn_mid;
- uint8_t orig_arfcn_lo:1,
- w1_hi:7;
- uint8_t w1_lo:1,
- w2:7;
- uint8_t w3:7,
- w4_hi:1;
- uint8_t w4_lo:5,
- w5_hi:3;
- uint8_t w5_lo:3,
- w6_hi:5;
- uint8_t w6_lo:1,
- w7:6,
- w8_hi:1;
- uint8_t w8_lo:4,
- w9_hi:4;
- uint8_t w9_lo:1,
- w10:5,
- w11_hi:2;
- uint8_t w11_lo:3,
- w12:5;
- uint8_t w13:5,
- w14_hi:3;
- uint8_t w14_lo:2,
- w15:5,
- w16_hi:1;
- uint8_t w16_lo:3,
- w17:4,
- w18_hi:1;
- uint8_t w18_lo:3,
- w19:4,
- w20_hi:1;
- uint8_t w20_lo:3,
- w21:4,
- spare:1;
-#elif OSMO_IS_BIG_ENDIAN
-/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianess.py) */
- uint8_t orig_arfcn_hi:1, form_id:7;
- uint8_t orig_arfcn_mid;
- uint8_t w1_hi:7, orig_arfcn_lo:1;
- uint8_t w2:7, w1_lo:1;
- uint8_t w4_hi:1, w3:7;
- uint8_t w5_hi:3, w4_lo:5;
- uint8_t w6_hi:5, w5_lo:3;
- uint8_t w8_hi:1, w7:6, w6_lo:1;
- uint8_t w9_hi:4, w8_lo:4;
- uint8_t w11_hi:2, w10:5, w9_lo:1;
- uint8_t w12:5, w11_lo:3;
- uint8_t w14_hi:3, w13:5;
- uint8_t w16_hi:1, w15:5, w14_lo:2;
- uint8_t w18_hi:1, w17:4, w16_lo:3;
- uint8_t w20_hi:1, w19:4, w18_lo:3;
- uint8_t spare:1, w21:4, w20_lo:3;
-#endif
-} __attribute__ ((packed));
-#endif
/* Chapter 10.5.2.1b.6 */
-#if OSMO_IS_LITTLE_ENDIAN == 1
struct gsm48_range_128 {
#if OSMO_IS_LITTLE_ENDIAN
uint8_t orig_arfcn_hi:1,
@@ -482,69 +309,6 @@ struct gsm48_range_128 {
uint8_t w26_lo:1, w27:3, w28:3, spare:1;
#endif
} __attribute__ ((packed));
-#else
-struct gsm48_range_128 {
-#if OSMO_IS_LITTLE_ENDIAN
- uint8_t form_id:7,
- orig_arfcn_hi:1;
- uint8_t orig_arfcn_mid;
- uint8_t orig_arfcn_lo:1,
- w1:7;
- uint8_t w2:6,
- w3_hi:2;
- uint8_t w3_lo:4,
- w4_hi:4;
- uint8_t w4_lo:1,
- w5:5,
- w6_hi:2;
- uint8_t w6_lo:3,
- w7:5;
- uint8_t w8:4,
- w9:4;
- uint8_t w10:4,
- w11:4;
- uint8_t w12:4,
- w13:4;
- uint8_t w14:4,
- w15:4;
- uint8_t w16:3,
- w17:3,
- w18_hi:2;
- uint8_t w18_lo:1,
- w19:3,
- w20:3,
- w21_hi:1;
- uint8_t w21_lo:2,
- w22:3,
- w23:3;
- uint8_t w24:3,
- w25:3,
- w26_hi:2;
- uint8_t w26_lo:1,
- w27:3,
- w28:3,
- spare:1;
-#elif OSMO_IS_BIG_ENDIAN
-/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianess.py) */
- uint8_t orig_arfcn_hi:1, form_id:7;
- uint8_t orig_arfcn_mid;
- uint8_t w1:7, orig_arfcn_lo:1;
- uint8_t w3_hi:2, w2:6;
- uint8_t w4_hi:4, w3_lo:4;
- uint8_t w6_hi:2, w5:5, w4_lo:1;
- uint8_t w7:5, w6_lo:3;
- uint8_t w9:4, w8:4;
- uint8_t w11:4, w10:4;
- uint8_t w13:4, w12:4;
- uint8_t w15:4, w14:4;
- uint8_t w18_hi:2, w17:3, w16:3;
- uint8_t w21_hi:1, w20:3, w19:3, w18_lo:1;
- uint8_t w23:3, w22:3, w21_lo:2;
- uint8_t w26_hi:2, w25:3, w24:3;
- uint8_t spare:1, w28:3, w27:3, w26_lo:1;
-#endif
-} __attribute__ ((packed));
-#endif
/* Chapter 10.5.2.1b.7 */
struct gsm48_var_bit {
@@ -887,13 +651,25 @@ struct gsm48_pag_resp {
#if OSMO_IS_LITTLE_ENDIAN
uint8_t spare:4,
key_seq:4;
- uint32_t classmark2;
+ union {
+ uint32_t classmark2; /* Backward compatibility */
+ struct {
+ uint8_t cm2_len;
+ struct gsm48_classmark2 cm2;
+ };
+ };
uint8_t mi_len;
uint8_t mi[0];
#elif OSMO_IS_BIG_ENDIAN
/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianess.py) */
uint8_t key_seq:4, spare:4;
- uint32_t classmark2;
+ union {
+ uint32_t classmark2;
+ struct {
+ uint8_t cm2_len;
+ struct gsm48_classmark2 cm2;
+ };
+ };
uint8_t mi_len;
uint8_t mi[0];
#endif
@@ -954,7 +730,7 @@ struct gsm48_system_information_type_header {
#if OSMO_IS_LITTLE_ENDIAN
uint8_t l2_plen;
uint8_t rr_protocol_discriminator :4,
- skip_indicator:4;
+ skip_indicator:4;
uint8_t system_information;
#elif OSMO_IS_BIG_ENDIAN
/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianess.py) */
@@ -1025,15 +801,26 @@ struct gsm48_service_request {
#if OSMO_IS_LITTLE_ENDIAN
uint8_t cm_service_type : 4,
cipher_key_seq : 4;
- /* length + 3 bytes */
- uint32_t classmark;
+ union {
+ uint32_t classmark; /* Backward compatibility */
+ struct {
+ uint8_t cm2_len;
+ struct gsm48_classmark2 classmark2;
+ };
+ };
uint8_t mi_len;
uint8_t mi[0];
/* optional priority level */
#elif OSMO_IS_BIG_ENDIAN
/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianess.py) */
uint8_t cipher_key_seq:4, cm_service_type:4;
- uint32_t classmark;
+ union {
+ uint32_t classmark;
+ struct {
+ uint8_t cm2_len;
+ struct gsm48_classmark2 classmark2;
+ };
+ };
uint8_t mi_len;
uint8_t mi[0];
#endif
@@ -1104,7 +891,7 @@ struct gsm48_system_information_type_4 {
struct gsm48_system_information_type_5 {
#if OSMO_IS_LITTLE_ENDIAN
uint8_t rr_protocol_discriminator :4,
- skip_indicator:4;
+ skip_indicator:4;
uint8_t system_information;
uint8_t bcch_frequency_list[16];
#elif OSMO_IS_BIG_ENDIAN
@@ -1149,7 +936,7 @@ struct gsm48_system_information_type_5ter {
struct gsm48_system_information_type_6 {
#if OSMO_IS_LITTLE_ENDIAN
uint8_t rr_protocol_discriminator :4,
- skip_indicator:4;
+ skip_indicator:4;
uint8_t system_information;
uint16_t cell_identity;
struct gsm48_loc_area_id lai;
diff --git a/include/osmocom/gsm/protocol/gsm_08_08.h b/include/osmocom/gsm/protocol/gsm_08_08.h
index 9806e083..1390f0e8 100644
--- a/include/osmocom/gsm/protocol/gsm_08_08.h
+++ b/include/osmocom/gsm/protocol/gsm_08_08.h
@@ -7,6 +7,7 @@
#include <stdint.h>
#include <osmocom/core/linuxlist.h>
#include <osmocom/core/utils.h>
+#include <osmocom/core/endian.h>
/*
* this is from GSM 03.03 CGI but is copied in GSM 08.08
@@ -41,10 +42,30 @@ struct bssmap_header {
struct dtap_header {
uint8_t type;
- uint8_t link_id;
+ union {
+ uint8_t link_id; /* Backward compatibility */
+ struct {
+#if OSMO_IS_LITTLE_ENDIAN
+ uint8_t dlci_cc:2,
+ dlci_spare:3,
+ dlci_sapi:3; /* enum gsm0406_dlc_sapi */
+#elif OSMO_IS_BIG_ENDIAN
+/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianess.py) */
+ uint8_t dlci_sapi:3, dlci_spare:3, dlci_cc:2;
+#endif
+ };
+ };
uint8_t length;
} __attribute__((packed));
+/* Data Link Control SAPI, GSM 08.06 § 6.3.2, GSM 04.06 § 3.3.3 */
+enum gsm0406_dlci_sapi {
+ DLCI_SAPI_RR_MM_CC = 0x0,
+ DLCI_SAPI_SMS = 0x3,
+};
+extern const struct value_string gsm0406_dlci_sapi_names[];
+static inline const char *gsm0406_dlci_sapi_name(enum gsm0406_dlci_sapi val)
+{ return get_value_string(gsm0406_dlci_sapi_names, val); }
enum BSS_MAP_MSG_TYPE {
BSS_MAP_MSG_RESERVED_0 = 0,
@@ -512,13 +533,15 @@ enum gsm0808_paging_info {
GSM0808_PAGINF_FOR_USSD = 0x02,
};
-/* 3GPP TS 48.008 3.2.2.104 Speech Codec */
+/*! 3GPP TS 48.008 3.2.2.104 Speech Codec */
struct gsm0808_speech_codec {
bool fi;
bool pi;
bool pt;
bool tf;
+ /*! See enum gsm0808_speech_codec_type. */
uint8_t type;
+ /*! For examples, see enum gsm0808_speech_codec_defaults. */
uint16_t cfg;
};
@@ -640,3 +663,16 @@ enum gsm0808_lcls_status {
GSM0808_LCLS_STS_LOCALLY_SWITCHED = 0x04,
GSM0808_LCLS_STS_NA = 0xFF
};
+
+/* 3GPP TS 48.008 3.2.2.32 Diagnostics */
+struct gsm0808_diagnostics {
+ uint8_t error_pointer_octet;
+#if OSMO_IS_LITTLE_ENDIAN
+ uint8_t error_pointer_bit_spare:4,
+ error_pointer_bit:4;
+#elif OSMO_IS_BIG_ENDIAN
+/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianess.py) */
+ uint8_t error_pointer_bit:4, error_pointer_bit_spare:4;
+#endif
+ uint8_t msg[0]; /*! received message which provoked the error */
+} __attribute__((packed));
diff --git a/include/osmocom/gsm/protocol/gsm_08_58.h b/include/osmocom/gsm/protocol/gsm_08_58.h
index 1d4530d1..cd13a7dc 100644
--- a/include/osmocom/gsm/protocol/gsm_08_58.h
+++ b/include/osmocom/gsm/protocol/gsm_08_58.h
@@ -236,6 +236,8 @@ enum abis_rsl_msgtype {
RSL_MT_IPAC_DLCX = 0x77,
RSL_MT_IPAC_DLCX_ACK,
RSL_MT_IPAC_DLCX_NACK,
+
+ RSL_MT_OSMO_ETWS_CMD = 0x7f,
};
/*! Siemens vendor-specific RSL message types */
@@ -540,6 +542,9 @@ struct rsl_ie_chan_ident {
#define RSL_ERR_CCCH_OVERLOAD 0x23
#define RSL_ERR_ACCH_OVERLOAD 0x24
#define RSL_ERR_PROCESSOR_OVERLOAD 0x25
+#define RSL_ERR_BTS_NOT_EQUIPPED 0x27
+#define RSL_ERR_REMOTE_TRANSC_FAIL 0x28
+#define RSL_ERR_NOTIFICATION_OVERFL 0x29
#define RSL_ERR_RES_UNAVAIL 0x2f
/* service or option not available */
#define RSL_ERR_TRANSC_UNAVAIL 0x30
diff --git a/include/osmocom/gsm/protocol/gsm_12_21.h b/include/osmocom/gsm/protocol/gsm_12_21.h
index 86d12ea7..34622b33 100644
--- a/include/osmocom/gsm/protocol/gsm_12_21.h
+++ b/include/osmocom/gsm/protocol/gsm_12_21.h
@@ -511,6 +511,7 @@ enum abis_nm_attr {
/* osmocom (osmo-bts) specific attributes, used in combination
* with the "org.osmocom" manufacturer identification */
+ NM_ATT_OSMO_NS_LINK_CFG = 0xfd, /* osmocom version supports IPv4 & IPv6 in difference to IPACC */
NM_ATT_OSMO_REDUCEPOWER = 0xfe, /* TLV_TYPE_TV */
};
#define NM_ATT_BS11_FILE_DATA NM_ATT_EVENT_TYPE
@@ -788,6 +789,13 @@ enum ipac_bcch_info_type {
IPAC_BINF_CELL_ALLOC = (1 << 2),
};
+/*! Osmocom NSVC address type for NM_ATT_OSMO_NS_LINK_CFG */
+enum osmo_oml_nsvc_address_type {
+ OSMO_NSVC_ADDR_UNSPEC = 0x00,
+ OSMO_NSVC_ADDR_IPV4 = 0x04,
+ OSMO_NSVC_ADDR_IPV6 = 0x29,
+};
+
/*! 3GPP TS 52.021 §9.4.62 SW Description */
struct abis_nm_sw_desc {
uint8_t file_id[UINT8_MAX];
diff --git a/include/osmocom/gsm/protocol/gsm_23_032.h b/include/osmocom/gsm/protocol/gsm_23_032.h
new file mode 100644
index 00000000..5be98a29
--- /dev/null
+++ b/include/osmocom/gsm/protocol/gsm_23_032.h
@@ -0,0 +1,172 @@
+/*! \defgroup gad 3GPP TS 23.032 GAD: Universal Geographical Area Description.
+ * @{
+ * \file gsm_23_032.h
+ */
+/*
+ * (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * Author: Neels Hofmeyr <neels@hofmeyr.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/>.
+ *
+ */
+
+#pragma once
+
+#include <stdint.h>
+#include <osmocom/core/endian.h>
+
+enum gad_type {
+ /*! Ellipsoid point */
+ GAD_TYPE_ELL_POINT = 0,
+ /*! Ellipsoid point with uncertainty circle. */
+ GAD_TYPE_ELL_POINT_UNC_CIRCLE = 1,
+ /*! Ellipsoid point with uncertainty ellipse. */
+ GAD_TYPE_ELL_POINT_UNC_ELLIPSE = 3,
+ GAD_TYPE_POLYGON = 5,
+ /*! Ellipsoid point with altitude. */
+ GAD_TYPE_ELL_POINT_ALT = 8,
+ /*! Ellipsoid point with altitude and uncertainty ellipsoid. */
+ GAD_TYPE_ELL_POINT_ALT_UNC_ELL = 9,
+ /*! Ellipsoid arc */
+ GAD_TYPE_ELL_ARC = 10,
+ /*! High accuracy ellipsoid point with uncertainty ellipse. */
+ GAD_TYPE_HA_ELL_POINT_UNC_ELLIPSE = 11,
+ /*! High accuracy ellipsoid point with altitude and uncertainty ellipsoid. */
+ GAD_TYPE_HA_ELL_POINT_ALT_UNC_ELL = 12,
+};
+
+struct gad_raw_head {
+ uint8_t spare:4,
+ type:4;
+} __attribute__ ((packed));
+
+struct gad_raw_ell_point {
+ struct gad_raw_head h; /*!< type = GAD_TYPE_ELL_POINT */
+ uint8_t lat[3];
+ uint8_t lon[3];
+} __attribute__ ((packed));
+
+struct gad_raw_ell_point_unc_circle {
+ struct gad_raw_head h; /*!< type = GAD_TYPE_ELL_POINT_UNC_CIRCLE */
+ uint8_t lat[3];
+ uint8_t lon[3];
+ uint8_t unc:7,
+ spare2:1;
+} __attribute__ ((packed));
+
+struct gad_raw_ell_point_unc_ellipse {
+ struct gad_raw_head h; /*!< type = GAD_TYPE_ELL_POINT_UNC_ELLIPSE */
+ uint8_t lat[3];
+ uint8_t lon[3];
+ uint8_t unc_semi_major:7,
+ spare1:1;
+ uint8_t unc_semi_minor:7,
+ spare2:1;
+ uint8_t major_ori;
+ uint8_t confidence:7,
+ spare3:1;
+} __attribute__ ((packed));
+
+struct gad_raw_polygon {
+ struct {
+ uint8_t num_points:4;
+ uint8_t type:4; /*!< type = GAD_TYPE_POLYGON */
+ } h;
+ struct {
+ uint8_t lat[3];
+ uint8_t lon[3];
+ } point[15];
+} __attribute__ ((packed));
+
+struct gad_raw_ell_point_alt {
+ struct gad_raw_head h; /*!< type = GAD_TYPE_ELL_POINT_ALT */
+ uint8_t lat[3];
+ uint8_t lon[3];
+ uint8_t alt[2];
+} __attribute__ ((packed));
+
+struct gad_raw_ell_point_alt_unc_ell {
+ struct gad_raw_head h; /*!< type = GAD_TYPE_ELL_POINT_ALT_UNC_ELL */
+ uint8_t lat[3];
+ uint8_t lon[3];
+ uint8_t alt[2];
+ uint8_t unc_semi_major:7,
+ spare1:1;
+ uint8_t unc_semi_minor:7,
+ spare2:1;
+ uint8_t major_ori;
+ uint8_t unc_alt:7,
+ spare3:1;
+ uint8_t confidence:7,
+ spare4:1;
+} __attribute__ ((packed));
+
+struct gad_raw_ell_arc {
+ struct gad_raw_head h; /*!< type = GAD_TYPE_ELL_ARC */
+ uint8_t lat[3];
+ uint8_t lon[3];
+ uint8_t inner_r[2];
+ uint8_t unc_r:7,
+ spare1:1;
+ uint8_t ofs_angle;
+ uint8_t incl_angle;
+ uint8_t confidence:7,
+ spare2:1;
+} __attribute__ ((packed));
+
+struct gad_raw_ha_ell_point_unc_ell {
+ struct gad_raw_head h; /*!< type = GAD_TYPE_HA_ELL_POINT_UNC_ELLIPSE */
+ uint8_t lat[4];
+ uint8_t lon[4];
+ uint8_t alt[3];
+ uint8_t unc_semi_major;
+ uint8_t unc_semi_minor;
+ uint8_t major_ori;
+ uint8_t confidence:7,
+ spare1:1;
+} __attribute__ ((packed));
+
+struct gad_raw_ha_ell_point_alt_unc_ell {
+ struct gad_raw_head h; /*!< type = GAD_TYPE_HA_ELL_POINT_ALT_UNC_ELL */
+ uint8_t lat[4];
+ uint8_t lon[4];
+ uint8_t alt[3];
+ uint8_t unc_semi_major;
+ uint8_t unc_semi_minor;
+ uint8_t major_ori;
+ uint8_t h_confidence:7,
+ spare1:1;
+ uint8_t unc_alt;
+ uint8_t v_confidence:7,
+ spare2:1;
+} __attribute__ ((packed));
+
+/*! GAD PDU in network-byte-order according to 3GPP TS 23.032 GAD: Universal Geographical Area Description. */
+union gad_raw {
+ struct gad_raw_head h;
+ struct gad_raw_ell_point ell_point;
+ struct gad_raw_ell_point_unc_circle ell_point_unc_circle;
+ struct gad_raw_ell_point_unc_ellipse ell_point_unc_ellipse;
+ struct gad_raw_polygon polygon;
+ struct gad_raw_ell_point_alt ell_point_alt;
+ struct gad_raw_ell_point_alt_unc_ell ell_point_alt_unc_ell;
+ struct gad_raw_ell_arc ell_arc;
+ struct gad_raw_ha_ell_point_unc_ell ha_ell_point_unc_ell;
+ struct gad_raw_ha_ell_point_alt_unc_ell ha_ell_point_alt_unc_ell;
+} __attribute__ ((packed));
+
+/*! @} */
diff --git a/include/osmocom/gsm/protocol/gsm_23_041.h b/include/osmocom/gsm/protocol/gsm_23_041.h
index c75c0883..e726cff2 100644
--- a/include/osmocom/gsm/protocol/gsm_23_041.h
+++ b/include/osmocom/gsm/protocol/gsm_23_041.h
@@ -1,5 +1,7 @@
#pragma once
+#include <osmocom/core/endian.h>
+
/* Section 9.4.1.2: GSM Message Format */
struct gsm23041_msg_param_gsm {
uint16_t serial_nr;
@@ -9,9 +11,9 @@ struct gsm23041_msg_param_gsm {
#if OSMO_IS_LITTLE_ENDIAN
uint8_t num_pages:4,
page_nr:4;
-#else
- uint8_t page_nr:4,
- num_pages:4;
+#elif OSMO_IS_BIG_ENDIAN
+/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianess.py) */
+ uint8_t page_nr:4, num_pages:4;
#endif
} page_param;
uint8_t content[0];
diff --git a/include/osmocom/gsm/protocol/gsm_29_118.h b/include/osmocom/gsm/protocol/gsm_29_118.h
index 9adb90f5..15835888 100644
--- a/include/osmocom/gsm/protocol/gsm_29_118.h
+++ b/include/osmocom/gsm/protocol/gsm_29_118.h
@@ -181,4 +181,4 @@ static inline const char *sgsap_ue_emm_mode_name(enum sgsap_ue_emm_mode mode) {
* See also: RFC1123 Section 2.1 Host Names and Numbers */
#define SGS_VLR_NAME_MAXLEN 255
-const struct tlv_definition sgsap_ie_tlvdef;
+extern const struct tlv_definition sgsap_ie_tlvdef;
diff --git a/include/osmocom/gsm/protocol/gsm_48_071.h b/include/osmocom/gsm/protocol/gsm_48_071.h
new file mode 100644
index 00000000..fb9653ab
--- /dev/null
+++ b/include/osmocom/gsm/protocol/gsm_48_071.h
@@ -0,0 +1,122 @@
+/*! \defgroup bsslap 3GPP TS 48.071 BSS LCS Assistance Protocol (BSSLAP).
+ * @{
+ * \file gsm_48_071.h
+ */
+/*
+ * (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Neels Hofmeyr <neels@hofmeyr.de>
+ *
+ * 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.
+ *
+ */
+#pragma once
+
+#include <osmocom/gsm/protocol/gsm_04_08.h>
+
+enum bsslap_msgt {
+ BSSLAP_MSGT_TA_REQUEST = 0x1,
+ BSSLAP_MSGT_TA_RESPONSE = 0x2,
+ BSSLAP_MSGT_REJECT = 0xa,
+ BSSLAP_MSGT_RESET = 0xb,
+ BSSLAP_MSGT_ABORT = 0xc,
+ BSSLAP_MSGT_TA_LAYER3 = 0xd,
+ BSSLAP_MSGT_MS_POS_CMD = 0xf,
+ BSSLAP_MSGT_MS_POS_RESP = 0x10,
+ BSSLAP_MSGT_UTDOA_REQ = 0x11,
+ BSSLAP_MSGT_UTDOA_RESP = 0x12,
+};
+
+enum bsslap_cause {
+ BSSLAP_CAUSE_CONGESTION = 0x0,
+ BSSLAP_CAUSE_CHAN_MODE_NOT_SUPP = 0x1,
+ BSSLAP_CAUSE_POS_PROC_NOT_SUPP = 0x2,
+ BSSLAP_CAUSE_OTHER_RADIO_EVT_FAIL = 0x3,
+ BSSLAP_CAUSE_INTRA_BSS_HO = 0x4,
+ BSSLAP_CAUSE_SUPERV_TIMER_EXPIRED = 0x5,
+ BSSLAP_CAUSE_INTER_BSS_HO = 0x6,
+ BSSLAP_CAUSE_LOSS_SIG_CONN_MS = 0x7,
+ BSSLAP_CAUSE_INCORR_SERV_CELL_ID = 0x8,
+ BSSLAP_CAUSE_BSSAP_LE_SEGMENT_ERR = 0x9,
+ BSSLAP_CAUSE_CONCUR_POS_PROC_NOT_EN = 0xa,
+};
+
+enum bsslap_iei {
+ BSSLAP_IEI_TA = 0x1,
+ BSSLAP_IEI_CELL_ID = 0x9,
+ BSSLAP_IEI_CHAN_DESC = 0x10,
+ BSSLAP_IEI_MEAS_REP = 0x14,
+ BSSLAP_IEI_CAUSE = 0x18,
+ BSSLAP_IEI_RRLP_FLAG = 0x19,
+ BSSLAP_IEI_RRLP = 0x1b,
+ BSSLAP_IEI_CELL_ID_LIST = 0x1c,
+ BSSLAP_IEI_ENH_MEAS_REP = 0x1d,
+ BSSLAP_IEI_LAC = 0x1e,
+ BSSLAP_IEI_FREQ_LIST = 0x21,
+ BSSLAP_IEI_MS_POWER = 0x22,
+ BSSLAP_IEI_DELTA_TIMER = 0x23,
+ BSSLAP_IEI_SERVING_CELL_ID = 0x24,
+ BSSLAP_IEI_ENCR_KEY = 0x25,
+ BSSLAP_IEI_CIPH_MODE_SET = 0x26,
+ BSSLAP_IEI_CHAN_MODE = 0x27,
+ BSSLAP_IEI_MR_CONFIG = 0x28,
+ BSSLAP_IEI_POLLING_REPETITION = 0x29,
+ BSSLAP_IEI_PACKET_CHAN_DESC = 0x2a,
+ BSSLAP_IEI_TLLI = 0x2b,
+ BSSLAP_IEI_TFI = 0x2c,
+ BSSLAP_IEI_TBF_START_TIME = 0x2d,
+ BSSLAP_IEI_PWRUP_START_TIME = 0x2e,
+ BSSLAP_IEI_LONG_ENCR_KEY = 0x2f,
+ BSSLAP_IEI_CONCUR_POS_PROC_F = 0x30,
+};
+
+struct bsslap_ta_response {
+ uint16_t cell_id;
+ uint8_t ta;
+
+ bool more_items; /*!< always set this to false */
+};
+
+struct bsslap_ta_layer3 {
+ uint8_t ta;
+
+ bool more_items; /*!< always set this to false */
+};
+
+struct bsslap_reset {
+ uint16_t cell_id;
+ uint8_t ta;
+ struct gsm48_chan_desc chan_desc;
+ enum bsslap_cause cause;
+
+ bool more_items; /*!< always set this to false */
+};
+
+struct bsslap_pdu {
+ enum bsslap_msgt msg_type;
+ union {
+ /* ta_request: a TA Request message consists only of the message type. */
+ struct bsslap_ta_response ta_response;
+ enum bsslap_cause reject;
+ struct bsslap_reset reset;
+ enum bsslap_cause abort;
+ struct bsslap_ta_layer3 ta_layer3;
+ };
+};
+
+/*! @} */
diff --git a/include/osmocom/gsm/protocol/gsm_49_031.h b/include/osmocom/gsm/protocol/gsm_49_031.h
new file mode 100644
index 00000000..c6152e17
--- /dev/null
+++ b/include/osmocom/gsm/protocol/gsm_49_031.h
@@ -0,0 +1,212 @@
+/*! \defgroup bssmap_le 3GPP TS 49.031 BSSMAP-LE.
+ * @{
+ * \file gsm_49_031.h
+ */
+/*
+ * (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Neels Hofmeyr <neels@hofmeyr.de>
+ *
+ * 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.
+ *
+ */
+#pragma once
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <osmocom/gsm/protocol/gsm_48_071.h>
+#include <osmocom/gsm/protocol/gsm_23_032.h>
+#include <osmocom/gsm/gsm0808_utils.h>
+#include <osmocom/gsm/gsm48.h>
+
+/*! 3GPP TS 49.031 10.13 LCS Cause, also in 3GPP TS 48.008 3.2.2.66, which simply refers to the former. */
+enum lcs_cause {
+ LCS_CAUSE_UNSPECIFIED = 0,
+ LCS_CAUSE_SYSTEM_FAILURE = 1,
+ LCS_CAUSE_PROTOCOL_ERROR = 2,
+ LCS_CAUSE_DATA_MISSING_IN_REQ = 3,
+ LCS_CAUSE_UNEXP_DATA_IN_REQ = 4,
+ LCS_CAUSE_POS_METH_FAILURE = 5,
+ LCS_CAUSE_TGT_MS_UNREACHABLE = 6,
+ LCS_CAUSE_REQUEST_ABORTED = 7,
+ LCS_CAUSE_FACILITY_NOTSUPP = 8,
+ LCS_CAUSE_INTER_BSC_HO = 9,
+ LCS_CAUSE_INTRA_BSC_HO = 10,
+ LCS_CAUSE_CONGESTION = 11,
+ LCS_CAUSE_INTER_NSE_CHG = 12,
+ LCS_CAUSE_RA_UPDAT = 13,
+ LCS_CAUSE_PTMSI_REALLOC = 14,
+ LCS_CAUSE_GPRS_SUSPENSION = 15,
+};
+
+/*! 3GPP TS 49.031 10.13 LCS Cause, also in 3GPP TS 48.008 3.2.2.66, which simply refers to the former. */
+struct lcs_cause_ie {
+ bool present;
+ enum lcs_cause cause_val;
+ bool diag_val_present;
+ uint8_t diag_val;
+};
+
+enum bssap_le_msg_discr {
+ BSSAP_LE_MSG_DISCR_BSSMAP_LE = 0,
+};
+
+enum bssmap_le_msgt {
+ BSSMAP_LE_MSGT_PERFORM_LOC_REQ = 0x2b,
+ BSSMAP_LE_MSGT_PERFORM_LOC_RESP = 0x2d,
+ BSSMAP_LE_MSGT_PERFORM_LOC_ABORT = 0x2e,
+ BSSMAP_LE_MSGT_PERFORM_LOC_INFO = 0x2f,
+ BSSMAP_LE_MSGT_ASSIST_INFO_REQ = 0x20,
+ BSSMAP_LE_MSGT_ASSIST_INFO_RESP = 0x21,
+ BSSMAP_LE_MSGT_CONN_ORIENTED_INFO = 0x2a,
+ BSSMAP_LE_MSGT_CONN_LESS_INFO = 0x3a,
+ BSSMAP_LE_MSGT_RESET = 0x30,
+ BSSMAP_LE_MSGT_RESET_ACK = 0x31,
+};
+
+enum bssmap_le_iei {
+ BSSMAP_LE_IEI_LCS_QoS = 0x3e,
+ BSSMAP_LE_IEI_LCS_PRIORITY = 0x43,
+ BSSMAP_LE_IEI_LOCATION_TYPE = 0x44,
+ BSSMAP_LE_IEI_GANSS_LOCATION_TYPE = 0x82,
+ BSSMAP_LE_IEI_GEO_LOCATION = 0x45,
+ BSSMAP_LE_IEI_POSITIONING_DATA = 0x46,
+ BSSMAP_LE_IEI_GANSS_POS_DATA = 0x83,
+ BSSMAP_LE_IEI_VELOCITY_DATA = 0x55,
+ BSSMAP_LE_IEI_LCS_CAUSE = 0x47,
+ BSSMAP_LE_IEI_LCS_CLIENT_TYPE = 0x48,
+ BSSMAP_LE_IEI_APDU = 0x49,
+ BSSMAP_LE_IEI_NET_ELEM_ID = 0x4a,
+ BSSMAP_LE_IEI_REQ_GPS_ASS_D = 0x4b,
+ BSSMAP_LE_IEI_REQ_GANSS_ASS_D = 0x41,
+ BSSMAP_LE_IEI_DECIPH_KEYS = 0x4c,
+ BSSMAP_LE_IEI_RET_ERR_REQ = 0x4d,
+ BSSMAP_LE_IEI_RET_ERR_CAUSE = 0x4e,
+ BSSMAP_LE_IEI_SEGMENTATION = 0x4f,
+ BSSMAP_LE_IEI_CLASSMARK3_INFO = 0x13,
+ BSSMAP_LE_IEI_CAUSE = 0x4,
+ BSSMAP_LE_IEI_CELL_ID = 0x5,
+ BSSMAP_LE_IEI_CHOSEN_CHAN = 0x21,
+ BSSMAP_LE_IEI_IMSI = 0x0,
+ BSSMAP_LE_IEI_LCS_CAPABILITY = 0x50,
+ BSSMAP_LE_IEI_PKT_MEAS_REP = 0x51,
+ BSSMAP_LE_IEI_CELL_ID_LIST = 0x52,
+ BSSMAP_LE_IEI_IMEI = 0x80,
+ BSSMAP_LE_IEI_BSS_MLAT_CAP = 0x84,
+ BSSMAP_LE_IEI_CELL_INFO_LIST = 0x85,
+ BSSMAP_LE_IEI_BTS_RX_ACC_LVL = 0x86,
+ BSSMAP_LE_IEI_MLAT_METHOD = 0x87,
+ BSSMAP_LE_IEI_MLAT_TA = 0x88,
+ BSSMAP_LE_IEI_MS_SYNC_ACC = 0x89,
+ BSSMAP_LE_IEI_SHORT_ID_SET = 0x8a,
+ BSSMAP_LE_IEI_RANDOM_ID_SET = 0x8b,
+ BSSMAP_LE_IEI_SHORT_BSS_ID = 0x8c,
+ BSSMAP_LE_IEI_RANDOM_ID = 0x8d,
+ BSSMAP_LE_IEI_SHORT_ID = 0x8e,
+ BSSMAP_LE_IEI_COVERAGE_CLASS = 0x8f,
+ BSSMAP_LE_IEI_MTA_ACC_SEC_RQD = 0x90,
+};
+
+enum bssmap_le_apdu_proto {
+ BSSMAP_LE_APDU_PROT_RESERVED = 0,
+ BSSMAP_LE_APDU_PROT_BSSLAP = 1,
+ BSSMAP_LE_APDU_PROT_LLP = 2,
+ BSSMAP_LE_APDU_PROT_SMLCPP = 3,
+};
+
+enum bssmap_le_location_information {
+ BSSMAP_LE_LOC_INFO_CURRENT_GEOGRAPHIC = 0x0,
+ BSSMAP_LE_LOC_INFO_ASSIST_TARGET_MS = 0x1,
+ BSSMAP_LE_LOC_INFO_BC_DECIPHER_KEYS = 0x2,
+};
+
+enum bssmap_le_positioning_method {
+ BSSMAP_LE_POS_METHOD_OMITTED = 0x0,
+ BSSMAP_LE_POS_METHOD_MOBILE_ASSISTED_E_OTD = 0x1,
+ BSSMAP_LE_POS_METHOD_MOBILE_BASED_E_OTD = 0x2,
+ BSSMAP_LE_POS_METHOD_ASSISTED_GPS = 0x3,
+};
+
+struct bssmap_le_location_type {
+ enum bssmap_le_location_information location_information;
+ enum bssmap_le_positioning_method positioning_method;
+};
+
+enum bssmap_le_lcs_client_type {
+ BSSMAP_LE_LCS_CTYPE_VALUE_ADDED_UNSPECIFIED = 0x0,
+ BSSMAP_LE_LCS_CTYPE_PLMN_OPER_UNSPECIFIED = 0x20,
+ BSSMAP_LE_LCS_CTYPE_PLMN_OPER_BCAST_SERVICE = 0x21,
+ BSSMAP_LE_LCS_CTYPE_PLMN_OPER_OAM = 0x22,
+ BSSMAP_LE_LCS_CTYPE_PLMN_OPER_ANON_STATS = 0x23,
+ BSSMAP_LE_LCS_CTYPE_PLMN_OPER_TGT_MS_SVC = 0x24,
+ BSSMAP_LE_LCS_CTYPE_EMERG_SVC_UNSPECIFIED = 0x30,
+ BSSMAP_LE_LCS_CTYPE_LI_UNSPECIFIED = 0x40,
+};
+
+struct bssmap_le_perform_loc_req {
+ struct bssmap_le_location_type location_type;
+ struct gsm0808_cell_id cell_id;
+
+ bool lcs_client_type_present;
+ enum bssmap_le_lcs_client_type lcs_client_type;
+
+ struct osmo_mobile_identity imsi;
+ struct osmo_mobile_identity imei;
+
+ bool apdu_present;
+ struct bsslap_pdu apdu;
+
+ bool more_items; /*!< always set this to false */
+};
+
+struct bssmap_le_perform_loc_resp {
+ bool location_estimate_present;
+ union gad_raw location_estimate;
+
+ struct lcs_cause_ie lcs_cause;
+
+ bool more_items; /*!< always set this to false */
+};
+
+struct bssmap_le_conn_oriented_info {
+ struct bsslap_pdu apdu;
+
+ bool more_items; /*!< always set this to false */
+};
+
+struct bssmap_le_pdu {
+ enum bssmap_le_msgt msg_type;
+ union {
+ enum gsm0808_cause reset;
+ /* reset_ack consists only of the message type */
+ struct bssmap_le_perform_loc_req perform_loc_req;
+ struct bssmap_le_perform_loc_resp perform_loc_resp;
+ struct lcs_cause_ie perform_loc_abort;
+ struct bssmap_le_conn_oriented_info conn_oriented_info;
+ };
+};
+
+struct bssap_le_pdu {
+ enum bssap_le_msg_discr discr;
+ union {
+ struct bssmap_le_pdu bssmap_le;
+ /* future: add DTAP PDU, currently not implemented */
+ };
+};
+
+/*! @} */
diff --git a/include/osmocom/gsm/protocol/ipaccess.h b/include/osmocom/gsm/protocol/ipaccess.h
index 80413d10..4f1d0b16 100644
--- a/include/osmocom/gsm/protocol/ipaccess.h
+++ b/include/osmocom/gsm/protocol/ipaccess.h
@@ -42,26 +42,35 @@ enum ipaccess_proto_ext {
};
enum ipaccess_msgtype {
- IPAC_MSGT_PING = 0x00,
- IPAC_MSGT_PONG = 0x01,
- IPAC_MSGT_ID_GET = 0x04,
- IPAC_MSGT_ID_RESP = 0x05,
- IPAC_MSGT_ID_ACK = 0x06,
+ IPAC_MSGT_PING = 0x00, /* Heartbeet */
+ IPAC_MSGT_PONG = 0x01, /* Heartbeat Ack */
+ IPAC_MSGT_ID_GET = 0x04, /* Identity Request */
+ IPAC_MSGT_ID_RESP = 0x05, /* Identity */
+ IPAC_MSGT_ID_ACK = 0x06, /* Identity Ack */
+ IPAC_MSGT_ID_NACK = 0x07, /* Identity Nack */
+ IPAC_MSGT_PROXY = 0x08, /* Proxy */
+ IPAC_MSGT_PROXY_ACK = 0x09, /* Proxy Ack */
+ IPAC_MSGT_PROXY_NACK = 0x0a, /* Proxy Nack */
+ IPAC_MSGT_SSL_INFO = 0x0b, /* SSL Info */
/* OpenBSC extension */
IPAC_MSGT_SCCP_OLD = 0xff,
};
enum ipaccess_id_tags {
- IPAC_IDTAG_SERNR = 0x00,
- IPAC_IDTAG_UNITNAME = 0x01,
- IPAC_IDTAG_LOCATION1 = 0x02,
- IPAC_IDTAG_LOCATION2 = 0x03,
- IPAC_IDTAG_EQUIPVERS = 0x04,
- IPAC_IDTAG_SWVERSION = 0x05,
- IPAC_IDTAG_IPADDR = 0x06,
- IPAC_IDTAG_MACADDR = 0x07,
- IPAC_IDTAG_UNIT = 0x08,
+ IPAC_IDTAG_SERNR = 0x00, /* Unit Serial Number */
+ IPAC_IDTAG_UNITNAME = 0x01, /* Unit Name */
+ IPAC_IDTAG_LOCATION1 = 0x02, /* Unit Location */
+ IPAC_IDTAG_LOCATION2 = 0x03, /* Unit Type */
+ IPAC_IDTAG_EQUIPVERS = 0x04, /* Hardware Version */
+ IPAC_IDTAG_SWVERSION = 0x05, /* Software Version */
+ IPAC_IDTAG_IPADDR = 0x06, /* IP Address */
+ IPAC_IDTAG_MACADDR = 0x07, /* Ethernet Address */
+ IPAC_IDTAG_UNIT = 0x08, /* Unit ID */
+ IPAC_IDTAG_USERNAME = 0x09, /* User Name */
+ IPAC_IDTAG_PASSWORD = 0x0a, /* Password */
+ IPAC_IDTAG_ACCESS_CLASS = 0x0b, /* Access Class */
+ IPAC_IDTG_APP_PROTO_VER = 0x0c, /* Application Protocol Version */
};
/*
diff --git a/include/osmocom/gsm/tlv.h b/include/osmocom/gsm/tlv.h
index bb0e8fc9..254c21bc 100644
--- a/include/osmocom/gsm/tlv.h
+++ b/include/osmocom/gsm/tlv.h
@@ -111,6 +111,14 @@ static inline uint8_t *tlv_put(uint8_t *buf, uint8_t tag, uint8_t len,
return buf + len;
}
+/*! put (append) a TL field (a TLV field but omitting the value part). */
+static inline uint8_t *tl_put(uint8_t *buf, uint8_t tag, uint8_t len)
+{
+ *buf++ = tag;
+ *buf++ = len;
+ return buf;
+}
+
/*! put (append) a TLV16 field */
static inline uint8_t *tlv16_put(uint8_t *buf, uint8_t tag, uint8_t len,
const uint16_t *val)
@@ -132,6 +140,15 @@ static inline uint8_t *tl16v_put(uint8_t *buf, uint8_t tag, uint16_t len,
return buf + len*2;
}
+/*! put (append) a TL16 field. */
+static inline uint8_t *tl16_put(uint8_t *buf, uint8_t tag, uint16_t len)
+{
+ *buf++ = tag;
+ *buf++ = len >> 8;
+ *buf++ = len & 0xff;
+ return buf;
+}
+
/*! put (append) a TL16V field */
static inline uint8_t *t16lv_put(uint8_t *buf, uint16_t tag, uint8_t len,
const uint8_t *val)
@@ -158,6 +175,23 @@ static inline uint8_t *tvlv_put(uint8_t *buf, uint8_t tag, uint16_t len,
return ret;
}
+/*! put (append) a TvL field (a TvLV with variable-size length, where the value part's length is already known, but will
+ * be put() later).
+ * \returns pointer to the value's start position.
+ */
+static inline uint8_t *tvl_put(uint8_t *buf, uint8_t tag, uint16_t len)
+{
+ uint8_t *ret;
+
+ if (len <= TVLV_MAX_ONEBYTE) {
+ ret = tl_put(buf, tag, len);
+ buf[1] |= 0x80;
+ } else
+ ret = tl16_put(buf, tag, len);
+
+ return ret;
+}
+
/*! put (append) a variable-length tag or variable-length length * */
static inline uint8_t *vt_gan_put(uint8_t *buf, uint16_t tag)
{
@@ -215,6 +249,17 @@ static inline uint8_t *msgb_t16lv_put(struct msgb *msg, uint16_t tag, uint8_t le
return t16lv_put(buf, tag, len, val);
}
+/*! put (append) a TvL field to \ref msgb, i.e. a TvLV with variable-size length, where the value's length is already
+ * known, but will be put() later. The value section is not yet reserved, only tag and variable-length are put in the
+ * msgb.
+ * \returns pointer to the value's start position and end of the msgb.
+ */
+static inline uint8_t *msgb_tvl_put(struct msgb *msg, uint8_t tag, uint16_t len)
+{
+ uint8_t *buf = msgb_put(msg, TVLV_GROSS_LEN(len));
+ return tvl_put(buf, tag, len);
+}
+
/*! put (append) a TvLV field to \ref msgb */
static inline uint8_t *msgb_tvlv_put(struct msgb *msg, uint8_t tag, uint16_t len,
const uint8_t *val)
diff --git a/include/osmocom/sim/sim.h b/include/osmocom/sim/sim.h
index 2bc47153..bfd1ac94 100644
--- a/include/osmocom/sim/sim.h
+++ b/include/osmocom/sim/sim.h
@@ -9,6 +9,7 @@
#include <osmocom/core/linuxlist.h>
#define APDU_HDR_LEN 5
+#define MAX_AID_LEN 16 /* Table 13.2 of TS 102 221 */
/*! command-response pairs cases
*
@@ -66,6 +67,8 @@ struct osim_msgb_cb {
#define msgb_apdu_dc(__x) ((__x)->l2h + sizeof(struct osim_apdu_cmd_hdr))
#define msgb_apdu_de(__x) ((__x)->l2h + sizeof(struct osim_apdu_cmd_hdr) + msgb_apdu_lc(__x))
+int osim_init(void *ctx);
+
/* FILES */
struct osim_file;
@@ -135,6 +138,7 @@ enum osim_file_type {
TYPE_ADF, /*!< Application Dedicated File */
TYPE_EF, /*!< Entry File */
TYPE_EF_INT, /*!< Internal Entry File */
+ TYPE_MF, /*!< Master File */
};
enum osim_ef_type {
@@ -242,6 +246,9 @@ struct osim_file_desc *
osim_file_desc_find_name(struct osim_file_desc *parent, const char *name);
struct osim_file_desc *
+osim_file_desc_find_aid(struct osim_file_desc *parent, const uint8_t *aid, uint8_t aid_len);
+
+struct osim_file_desc *
osim_file_desc_find_fid(struct osim_file_desc *parent, uint16_t fid);
struct osim_file_desc *
@@ -281,6 +288,29 @@ struct osim_card_sw {
.class = SW_CLS_NONE, .u.str = NULL \
}
+/*! A card application (e.g. USIM, ISIM, HPSIM) */
+struct osim_card_app_profile {
+ /*! entry in the global list of card application profiles */
+ struct llist_head list;
+ /*! human-readable name */
+ const char *name;
+ /*! AID of this application, as used in EF.DIR */
+ uint8_t aid[MAX_AID_LEN];
+ uint8_t aid_len;
+ /*! file system description */
+ struct osim_file_desc *adf;
+ /*! Status words defined by application */
+ const struct osim_card_sw *sw;
+};
+
+const struct osim_card_app_profile *
+osim_app_profile_find_by_name(const char *name);
+
+const struct osim_card_app_profile *
+osim_app_profile_find_by_aid(const uint8_t *aid, uint8_t aid_len);
+
+const struct osim_card_sw *osim_app_profile_find_sw(const struct osim_card_app_profile *ap, uint16_t sw_in);
+
/*! A card profile (e.g. SIM card */
struct osim_card_profile {
const char *name;
@@ -290,15 +320,13 @@ struct osim_card_profile {
const struct osim_card_sw **sws;
};
-const struct osim_card_sw *osim_find_sw(const struct osim_card_profile *cp,
- uint16_t sw);
-enum osim_card_sw_class osim_sw_class(const struct osim_card_profile *cp,
- uint16_t sw_in);
+const struct osim_card_sw *osim_cprof_find_sw(const struct osim_card_profile *cp, uint16_t sw_in);
-struct osim_card_hdl;
-char *osim_print_sw_buf(char *buf, size_t buf_len, const struct osim_card_hdl *ch, uint16_t sw_in);
-char *osim_print_sw(const struct osim_card_hdl *ch, uint16_t sw_in);
-char *osim_print_sw_c(const void *ctx, const struct osim_card_hdl *ch, uint16_t sw_in);
+struct osim_chan_hdl;
+enum osim_card_sw_class osim_sw_class(const struct osim_chan_hdl *ch, uint16_t sw_in);
+char *osim_print_sw_buf(char *buf, size_t buf_len, const struct osim_chan_hdl *ch, uint16_t sw_in);
+char *osim_print_sw(const struct osim_chan_hdl *ch, uint16_t sw_in);
+char *osim_print_sw_c(const void *ctx, const struct osim_chan_hdl *ch, uint16_t sw_in);
extern const struct tlv_definition ts102221_fcp_tlv_def;
extern const struct value_string ts102221_fcp_vals[14];
@@ -353,6 +381,19 @@ struct osim_reader_hdl {
struct osim_card_hdl *card;
};
+/*! descriptor for a given application present on a card */
+struct osim_card_app_hdl {
+ /*! member in card list of applications */
+ struct llist_head list;
+ /*! AID of the application */
+ uint8_t aid[MAX_AID_LEN];
+ uint8_t aid_len;
+ /*! application label from EF_DIR */
+ char *label;
+ /*! application profile (if any known) */
+ const struct osim_card_app_profile *prof;
+};
+
struct osim_card_hdl {
/*! member in global list of cards */
struct llist_head list;
@@ -365,6 +406,9 @@ struct osim_card_hdl {
/*! list of channels for this card */
struct llist_head channels;
+
+ /*! list of applications found on card */
+ struct llist_head apps;
};
struct osim_chan_hdl {
@@ -372,9 +416,15 @@ struct osim_chan_hdl {
struct llist_head list;
/*! card to which this channel belongs */
struct osim_card_hdl *card;
+ /*! current working directory */
const struct osim_file_desc *cwd;
+ /*! currently selected application (if any) */
+ struct osim_card_app_hdl *cur_app;
};
+int osim_card_hdl_add_app(struct osim_card_hdl *ch, const uint8_t *aid, uint8_t aid_len,
+ const char *label);
+
/* reader.c */
int osim_transceive_apdu(struct osim_chan_hdl *st, struct msgb *amsg);
struct osim_reader_hdl *osim_reader_open(enum osim_reader_driver drv, int idx,
diff --git a/include/osmocom/usb/libusb.h b/include/osmocom/usb/libusb.h
new file mode 100644
index 00000000..9ad3f71a
--- /dev/null
+++ b/include/osmocom/usb/libusb.h
@@ -0,0 +1,119 @@
+#pragma once
+/* libusb utilities
+ *
+ * (C) 2010-2019 by Harald Welte <hwelte@hmw-consulting.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.
+ *
+ * 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 <libusb.h>
+
+#define USB_MAX_PATH_LEN 20
+
+struct dev_id {
+ uint16_t vendor_id;
+ uint16_t product_id;
+};
+
+/* structure describing a single matching interface found */
+struct usb_interface_match {
+ /* libusb device E*/
+ libusb_device *usb_dev;
+ /* Vendor ID of the device running matching interface */
+ uint16_t vendor;
+ /* Product ID of the device running matching interface */
+ uint16_t product;
+ /* USB Bus Address */
+ uint8_t addr;
+ /* physical path */
+ char path[USB_MAX_PATH_LEN];
+ /* configuration of matching interface */
+ uint8_t configuration;
+ /* interface number of matching interface */
+ uint8_t interface;
+ /* altsetting of matching interface */
+ uint8_t altsetting;
+ /* bInterfaceClass of matching interface */
+ uint8_t class;
+ /* bInterfaceSubClass of matching interface */
+ uint8_t sub_class;
+ /* bInterfaceProtocol of matching interface */
+ uint8_t protocol;
+ /* index of string descriptor of matching interface */
+ uint8_t string_idx;
+};
+
+/*! Description of the USB device+interface we're looking for */
+struct osmo_usb_matchspec {
+ /*! specify the USB device */
+ struct {
+ int vendor_id; /*!< typically -1 for compile time defaults */
+ int product_id; /*!< typically -1 for compile time defaults */
+ char *path; /*!< used for disambiguation when multiple matches; can be NULL */
+ } dev;
+
+ /*! specify the USB configuration */
+ int config_id; /*!< typically -1 unless user selects specific configuration */
+
+ /*! specify the USB interface */
+ struct {
+ /* typically those three are set to application defaults */
+ int class; /*!< -1 or a user-specified class */
+ int subclass; /*!< -1 or a user-specified subclass */
+ int proto; /*!< -1 or a user-specified protocol */
+
+ /* typically those two are -1; but user can override them */
+ int num;
+ int altsetting;
+ } intf;
+};
+
+
+char *osmo_libusb_dev_get_path_buf(char *buf, size_t bufsize, libusb_device *dev);
+char *osmo_libusb_dev_get_path_c(void *ctx, libusb_device *dev);
+
+libusb_device **osmo_libusb_find_matching_usb_devs(void *ctx, struct libusb_context *luctx,
+ const struct dev_id *dev_ids);
+
+libusb_device *osmo_libusb_find_matching_dev_path(struct libusb_context *luctx,
+ const struct dev_id *dev_ids,
+ const char *path);
+
+libusb_device *osmo_libusb_find_matching_dev_serial(struct libusb_context *luctx,
+ const struct dev_id *dev_ids,
+ const char *serial);
+
+int osmo_libusb_dev_find_matching_interfaces(libusb_device *dev, int class, int sub_class,
+ int protocol, struct usb_interface_match *out,
+ unsigned int out_len);
+
+int osmo_libusb_find_matching_interfaces(libusb_context *luctx, const struct dev_id *dev_ids,
+ int class, int sub_class, int protocol,
+ struct usb_interface_match *out, unsigned int out_len);
+
+libusb_device_handle *osmo_libusb_open_claim_interface(void *ctx, libusb_context *luctx,
+ const struct usb_interface_match *ifm);
+
+void osmo_libusb_match_init(struct osmo_usb_matchspec *cfg, int if_class, int if_subclass, int if_proto);
+
+libusb_device_handle *osmo_libusb_find_open_claim(const struct osmo_usb_matchspec *cfg,
+ const struct dev_id *default_dev_ids);
+
+int osmo_libusb_get_ep_addrs(libusb_device_handle *devh, unsigned int if_num,
+ uint8_t *out, uint8_t *in, uint8_t *irq);
+
+
+int osmo_libusb_init(libusb_context **luctx);
+void osmo_libusb_exit(libusb_context *luctx);
diff --git a/include/osmocom/vty/command.h b/include/osmocom/vty/command.h
index d63dbdef..eb7ee35e 100644
--- a/include/osmocom/vty/command.h
+++ b/include/osmocom/vty/command.h
@@ -24,10 +24,12 @@
#pragma once
#include <stdio.h>
+#include <stdbool.h>
#include <sys/types.h>
#include "vector.h"
#include <osmocom/core/defs.h>
+#include <osmocom/core/utils.h>
/*! \defgroup command VTY Command
* @{
@@ -65,6 +67,9 @@ struct host {
/*! VTY application information */
const struct vty_app_info *app_info;
+
+ /*! Whether the expert mode is enabled. */
+ bool expert_mode;
};
/*! There are some command levels which called from command node. */
@@ -97,6 +102,7 @@ enum node_type {
L_CS7_SCCPADDR_NODE, /*!< SS7 SCCP Address */
L_CS7_SCCPADDR_GT_NODE, /*!< SS7 SCCP Global Title */
+ L_CPU_SCHED_NODE, /*!< CPU Sched related options node */
/*
* When adding new nodes to the libosmocore project, these nodes can be
* used to avoid ABI changes for unrelated projects.
@@ -136,6 +142,27 @@ struct cmd_node {
enum {
CMD_ATTR_DEPRECATED = (1 << 0),
CMD_ATTR_HIDDEN = (1 << 1),
+ CMD_ATTR_IMMEDIATE = (1 << 2),
+ CMD_ATTR_NODE_EXIT = (1 << 3),
+ CMD_ATTR_LIB_COMMAND = (1 << 4),
+};
+
+/*! Attributes shared between libraries (up to 32 entries). */
+enum {
+ /* The entries of this enum shall conform the following requirements:
+ * 1. Naming format: 'OSMO_' + <LIBNAME> + '_LIB_ATTR_' + <ATTRNAME>,
+ * where LIBNAME is a short name of the library, e.g. 'ABIS', 'MGCP',
+ * and ATTRNAME is a brief name of the attribute, e.g. RTP_CONN_EST;
+ * for example: 'OSMO_ABIS_LIB_ATTR_RSL_LINK_UP'.
+ * 2. Brevity: shortenings and abbreviations are welcome!
+ * 3. Values are not flags but indexes, unlike CMD_ATTR_*.
+ * 4. Ordering: new entries added before _OSMO_CORE_LIB_ATTR_COUNT. */
+ OSMO_SCCP_LIB_ATTR_RSTRT_ASP,
+ OSMO_ABIS_LIB_ATTR_IPA_NEW_LNK,
+ OSMO_ABIS_LIB_ATTR_LINE_UPD,
+
+ /* Keep this floating entry last, it's needed for count check. */
+ _OSMO_CORE_LIB_ATTR_COUNT
};
/*! Structure of a command element */
@@ -148,7 +175,8 @@ struct cmd_element {
unsigned int cmdsize; /*!< Command index count. */
char *config; /*!< Configuration string */
vector subconfig; /*!< Sub configuration string */
- unsigned char attr; /*!< Command attributes */
+ unsigned char attr; /*!< Command attributes (global) */
+ unsigned int usrattr; /*!< Command attributes (program specific) */
};
/*! Command description structure. */
@@ -199,6 +227,16 @@ struct desc {
.daemon = dnum, \
};
+#define DEFUN_CMD_ELEMENT_ATTR_USRATTR(funcname, cmdname, cmdstr, helpstr, attrs, usrattrs) \
+ static struct cmd_element cmdname = \
+ { \
+ .string = cmdstr, \
+ .func = funcname, \
+ .doc = helpstr, \
+ .attr = attrs, \
+ .usrattr = usrattrs, \
+ };
+
#define DEFUN_CMD_FUNC_DECL(funcname) \
static int funcname (struct cmd_element *, struct vty *, int, const char *[]); \
@@ -237,7 +275,23 @@ struct desc {
DEFUN_ATTR (funcname, cmdname, cmdstr, helpstr, CMD_ATTR_HIDDEN)
#define DEFUN_DEPRECATED(funcname, cmdname, cmdstr, helpstr) \
- DEFUN_ATTR (funcname, cmdname, cmdstr, helpstr, CMD_ATTR_DEPRECATED) \
+ DEFUN_ATTR (funcname, cmdname, cmdstr, helpstr, CMD_ATTR_DEPRECATED)
+
+/*! Macro for defining a VTY node and function with global & program specific attributes.
+ * \param[in] funcname Name of the function implementing the node.
+ * \param[in] cmdname Name of the command node.
+ * \param[in] attr Global attributes (see CMD_ATTR_*).
+ * \param[in] usrattr Program specific attributes.
+ * \param[in] cmdstr String with syntax of node.
+ * \param[in] helpstr String with help message of node.
+ */
+#define DEFUN_ATTR_USRATTR(funcname, cmdname, attr, usrattr, cmdstr, helpstr) \
+ DEFUN_CMD_FUNC_DECL(funcname) \
+ DEFUN_CMD_ELEMENT_ATTR_USRATTR(funcname, cmdname, cmdstr, helpstr, attr, usrattr) \
+ DEFUN_CMD_FUNC_TEXT(funcname)
+
+#define DEFUN_USRATTR(funcname, cmdname, usrattr, cmdstr, helpstr) \
+ DEFUN_ATTR_USRATTR(funcname, cmdname, 0, usrattr, cmdstr, helpstr)
/* DEFUN_NOSH for commands that vtysh should ignore */
#define DEFUN_NOSH(funcname, cmdname, cmdstr, helpstr) \
@@ -304,6 +358,10 @@ struct desc {
#define CMD_IPV6(S) ((strcmp ((S), "X:X::X:X") == 0))
#define CMD_IPV6_PREFIX(S) ((strcmp ((S), "X:X::X:X/M") == 0))
+#define VTY_IPV4_CMD "A.B.C.D"
+#define VTY_IPV6_CMD "X:X::X:X"
+#define VTY_IPV46_CMD "(" VTY_IPV4_CMD "|" VTY_IPV6_CMD ")"
+
/* Common descriptions. */
#define SHOW_STR "Show running system information\n"
#define IP_STR "IP information\n"
@@ -363,7 +421,9 @@ struct desc {
void install_node(struct cmd_node *, int (*)(struct vty *));
void install_default(int node_type) OSMO_DEPRECATED("Now happens implicitly with install_node()");
void install_element(int node_type, struct cmd_element *);
+void install_lib_element(int node_type, struct cmd_element *);
void install_element_ve(struct cmd_element *cmd);
+void install_lib_element_ve(struct cmd_element *cmd);
void sort_node(void);
void vty_install_default(int node_type) OSMO_DEPRECATED("Now happens implicitly with install_node()");
@@ -401,4 +461,18 @@ void print_version(int print_copyright);
extern void *tall_vty_cmd_ctx;
+/*! VTY reference generation mode. */
+enum vty_ref_gen_mode {
+ /*! Default mode: all commands except deprecated and hidden. */
+ VTY_REF_GEN_MODE_DEFAULT = 0,
+ /*! Expert mode: all commands including hidden, excluding deprecated. */
+ VTY_REF_GEN_MODE_EXPERT,
+};
+
+extern const struct value_string vty_ref_gen_mode_names[];
+extern const struct value_string vty_ref_gen_mode_desc[];
+
+int vty_dump_xml_ref_mode(FILE *stream, enum vty_ref_gen_mode mode);
+int vty_dump_xml_ref(FILE *stream) OSMO_DEPRECATED("Use vty_dump_xml_ref_mode() instead");
+
/*! @} */
diff --git a/include/osmocom/vty/cpu_sched_vty.h b/include/osmocom/vty/cpu_sched_vty.h
new file mode 100644
index 00000000..171f1687
--- /dev/null
+++ b/include/osmocom/vty/cpu_sched_vty.h
@@ -0,0 +1,37 @@
+/*! \file cpu_sched_vty.h
+ * API to CPU / Threading / Scheduler properties from VTY configuration.
+ */
+/* (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ *
+ * Author: Pau Espin Pedrol <pespin@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.
+ *
+ * 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+
+ */
+#pragma once
+
+#include <osmocom/vty/command.h>
+
+/*! \defgroup cpu_sched_VTY Configuration
+ * @{
+ * \file cpu_sched_vty.h
+ */
+
+void osmo_cpu_sched_vty_init(void *tall_ctx);
+int osmo_cpu_sched_vty_apply_localthread(void);
+
+/*! @} */
diff --git a/include/osmocom/vty/ports.h b/include/osmocom/vty/ports.h
index 201e1157..b3550d15 100644
--- a/include/osmocom/vty/ports.h
+++ b/include/osmocom/vty/ports.h
@@ -34,4 +34,7 @@
#define OSMO_VTY_PORT_HNBGW 4261
/* 4262-4263 used by control interface */
#define OSMO_VTY_PORT_CBC 4264
+#define OSMO_VTY_PORT_UECUPS 4268
+#define OSMO_VTY_PORT_E1D 4269
+#define OSMO_VTY_PORT_SMLC 4271
/* When adding/changing port numbers, keep docs and wiki in sync. See above. */
diff --git a/include/osmocom/vty/tdef_vty.h b/include/osmocom/vty/tdef_vty.h
index 3027913d..800af7d7 100644
--- a/include/osmocom/vty/tdef_vty.h
+++ b/include/osmocom/vty/tdef_vty.h
@@ -25,6 +25,9 @@
#pragma once
#include <stdint.h>
+#include <stdarg.h>
+
+#include <osmocom/vty/command.h>
struct vty;
@@ -65,7 +68,7 @@ void osmo_tdef_vty_out_all_va(struct vty *vty, struct osmo_tdef *tdefs, const ch
struct osmo_tdef *osmo_tdef_vty_parse_T_arg(struct vty *vty, struct osmo_tdef *tdefs, const char *osmo_tdef_str);
unsigned long osmo_tdef_vty_parse_val_arg(const char *val_arg, unsigned long default_val);
-void osmo_tdef_vty_groups_init(enum node_type parent_node, struct osmo_tdef_group *groups);
+void osmo_tdef_vty_groups_init(unsigned int parent_cfg_node, struct osmo_tdef_group *groups);
void osmo_tdef_vty_groups_write(struct vty *vty, const char *indent);
/*! @} */
diff --git a/include/osmocom/vty/vty.h b/include/osmocom/vty/vty.h
index 03a29248..6a82d7e3 100644
--- a/include/osmocom/vty/vty.h
+++ b/include/osmocom/vty/vty.h
@@ -5,6 +5,7 @@
#include <stdbool.h>
#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/defs.h>
/*! \defgroup vty VTY (Virtual TTY) interface
* @{
@@ -27,6 +28,12 @@
#define VTY_BUFSIZ 512
#define VTY_MAXHIST 20
+/* Number of application / library specific VTY attributes */
+#define VTY_CMD_USR_ATTR_NUM 32
+/* Flag characters reserved for global VTY attributes */
+#define VTY_CMD_ATTR_FLAGS_RESERVED \
+ { '.', '!', '@', '^' }
+
/*! VTY events */
enum event {
VTY_SERV,
@@ -178,12 +185,23 @@ struct vty_app_info {
const char *copyright;
/*! \ref talloc context */
void *tall_ctx;
- /*! call-back for returning to parent n ode */
+ /*! Call-back for taking actions upon exiting a node.
+ * The return value is ignored, and changes to vty->node and vty->index made in this callback are ignored.
+ * Implicit parent node tracking always sets the correct parent node and vty->index after this callback exits,
+ * so this callback can handle only those nodes that should take specific actions upon node exit, or can be left
+ * NULL entirely. */
int (*go_parent_cb)(struct vty *vty);
- /*! call-back to determine if node is config node */
- int (*is_config_node)(struct vty *vty, int node);
+ /*! OBSOLETED: Implicit parent node tracking has replaced the use of this callback. This callback is no longer
+ * called, ever, and can be left NULL. */
+ int (*is_config_node)(struct vty *vty, int node)
+ OSMO_DEPRECATED("Implicit parent node tracking has replaced the use of this callback. This callback is"
+ " no longer called, ever, and can be left NULL.");
/*! Check if the config is consistent before write */
int (*config_is_consistent)(struct vty *vty);
+ /*! Description of the application specific VTY attributes (optional). */
+ const char * usr_attr_desc[VTY_CMD_USR_ATTR_NUM];
+ /*! Flag letters of the application specific VTY attributes (optional). */
+ char usr_attr_letters[VTY_CMD_USR_ATTR_NUM];
};
/* Prototypes. */
diff --git a/libosmocodec.pc.in b/libosmocodec.pc.in
index 3030230b..9e058ef7 100644
--- a/libosmocodec.pc.in
+++ b/libosmocodec.pc.in
@@ -6,6 +6,6 @@ includedir=@includedir@
Name: Osmocom Codec related utilities Library
Description: C Utility Library
Version: @VERSION@
-Libs: -L${libdir} -losmocodec
-Cflags: -I${includedir}/
+Libs: -L${libdir} @TALLOC_LIBS@ -losmocodec
+Cflags: -I${includedir}/ @TALLOC_CFLAGS@
diff --git a/libosmocoding.pc.in b/libosmocoding.pc.in
index 580b1702..d1d03c42 100644
--- a/libosmocoding.pc.in
+++ b/libosmocoding.pc.in
@@ -6,6 +6,6 @@ includedir=@includedir@
Name: Osmocom L1 transcoding Library
Description: C Utility Library
Version: @VERSION@
-Libs: -L${libdir} -losmocoding -losmocodec -losmogsm -losmocore
-Cflags: -I${includedir}/
+Libs: -L${libdir} @TALLOC_LIBS@ -losmocoding -losmocodec -losmogsm -losmocore
+Cflags: -I${includedir}/ @TALLOC_CFLAGS@
diff --git a/libosmocore.pc.in b/libosmocore.pc.in
index d355659b..80f17c8b 100644
--- a/libosmocore.pc.in
+++ b/libosmocore.pc.in
@@ -7,5 +7,5 @@ Name: Osmocom Core Library
Description: C Utility Library
Version: @VERSION@
Libs: -L${libdir} @TALLOC_LIBS@ -losmocore
-Cflags: -I${includedir}/ @TALLOC_CFLAGS@
-
+Libs.private: @PTHREAD_LIBS@ @LIBSCTP_LIBS@
+Cflags: -I${includedir}/ @TALLOC_CFLAGS@ @PTHREAD_CFLAGS@
diff --git a/libosmousb.pc.in b/libosmousb.pc.in
new file mode 100644
index 00000000..ce6d2715
--- /dev/null
+++ b/libosmousb.pc.in
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: Osmocom libusb (USB) integration
+Description: C Utility Library
+Version: @VERSION@
+Libs: -L${libdir} @TALLOC_LIBS@ -losmousb -losmocore
+Cflags: -I${includedir}/
+
diff --git a/m4/ax_pthread.m4 b/m4/ax_pthread.m4
new file mode 100644
index 00000000..4920e073
--- /dev/null
+++ b/m4/ax_pthread.m4
@@ -0,0 +1,486 @@
+# ===========================================================================
+# https://www.gnu.org/software/autoconf-archive/ax_pthread.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+# AX_PTHREAD([ACTION-IF-FOUND[, ACTION-IF-NOT-FOUND]])
+#
+# DESCRIPTION
+#
+# This macro figures out how to build C programs using POSIX threads. It
+# sets the PTHREAD_LIBS output variable to the threads library and linker
+# flags, and the PTHREAD_CFLAGS output variable to any special C compiler
+# flags that are needed. (The user can also force certain compiler
+# flags/libs to be tested by setting these environment variables.)
+#
+# Also sets PTHREAD_CC to any special C compiler that is needed for
+# multi-threaded programs (defaults to the value of CC otherwise). (This
+# is necessary on AIX to use the special cc_r compiler alias.)
+#
+# NOTE: You are assumed to not only compile your program with these flags,
+# but also to link with them as well. For example, you might link with
+# $PTHREAD_CC $CFLAGS $PTHREAD_CFLAGS $LDFLAGS ... $PTHREAD_LIBS $LIBS
+#
+# If you are only building threaded programs, you may wish to use these
+# variables in your default LIBS, CFLAGS, and CC:
+#
+# LIBS="$PTHREAD_LIBS $LIBS"
+# CFLAGS="$CFLAGS $PTHREAD_CFLAGS"
+# CC="$PTHREAD_CC"
+#
+# In addition, if the PTHREAD_CREATE_JOINABLE thread-attribute constant
+# has a nonstandard name, this macro defines PTHREAD_CREATE_JOINABLE to
+# that name (e.g. PTHREAD_CREATE_UNDETACHED on AIX).
+#
+# Also HAVE_PTHREAD_PRIO_INHERIT is defined if pthread is found and the
+# PTHREAD_PRIO_INHERIT symbol is defined when compiling with
+# PTHREAD_CFLAGS.
+#
+# ACTION-IF-FOUND is a list of shell commands to run if a threads library
+# is found, and ACTION-IF-NOT-FOUND is a list of commands to run it if it
+# is not found. If ACTION-IF-FOUND is not specified, the default action
+# will define HAVE_PTHREAD.
+#
+# Please let the authors know if this macro fails on any platform, or if
+# you have any other suggestions or comments. This macro was based on work
+# by SGJ on autoconf scripts for FFTW (http://www.fftw.org/) (with help
+# from M. Frigo), as well as ac_pthread and hb_pthread macros posted by
+# Alejandro Forero Cuervo to the autoconf macro repository. We are also
+# grateful for the helpful feedback of numerous users.
+#
+# Updated for Autoconf 2.68 by Daniel Richard G.
+#
+# LICENSE
+#
+# Copyright (c) 2008 Steven G. Johnson <stevenj@alum.mit.edu>
+# Copyright (c) 2011 Daniel Richard G. <skunk@iSKUNK.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 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 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 <https://www.gnu.org/licenses/>.
+#
+# As a special exception, the respective Autoconf Macro's copyright owner
+# gives unlimited permission to copy, distribute and modify the configure
+# scripts that are the output of Autoconf when processing the Macro. You
+# need not follow the terms of the GNU General Public License when using
+# or distributing such scripts, even though portions of the text of the
+# Macro appear in them. The GNU General Public License (GPL) does govern
+# all other use of the material that constitutes the Autoconf Macro.
+#
+# This special exception to the GPL applies to versions of the Autoconf
+# Macro released by the Autoconf Archive. When you make and distribute a
+# modified version of the Autoconf Macro, you may extend this special
+# exception to the GPL to apply to your modified version as well.
+
+#serial 25
+
+AU_ALIAS([ACX_PTHREAD], [AX_PTHREAD])
+AC_DEFUN([AX_PTHREAD], [
+AC_REQUIRE([AC_CANONICAL_HOST])
+AC_REQUIRE([AC_PROG_CC])
+AC_REQUIRE([AC_PROG_SED])
+AC_LANG_PUSH([C])
+ax_pthread_ok=no
+
+# We used to check for pthread.h first, but this fails if pthread.h
+# requires special compiler flags (e.g. on Tru64 or Sequent).
+# It gets checked for in the link test anyway.
+
+# First of all, check if the user has set any of the PTHREAD_LIBS,
+# etcetera environment variables, and if threads linking works using
+# them:
+if test "x$PTHREAD_CFLAGS$PTHREAD_LIBS" != "x"; then
+ ax_pthread_save_CC="$CC"
+ ax_pthread_save_CFLAGS="$CFLAGS"
+ ax_pthread_save_LIBS="$LIBS"
+ AS_IF([test "x$PTHREAD_CC" != "x"], [CC="$PTHREAD_CC"])
+ CFLAGS="$CFLAGS $PTHREAD_CFLAGS"
+ LIBS="$PTHREAD_LIBS $LIBS"
+ AC_MSG_CHECKING([for pthread_join using $CC $PTHREAD_CFLAGS $PTHREAD_LIBS])
+ AC_LINK_IFELSE([AC_LANG_CALL([], [pthread_join])], [ax_pthread_ok=yes])
+ AC_MSG_RESULT([$ax_pthread_ok])
+ if test "x$ax_pthread_ok" = "xno"; then
+ PTHREAD_LIBS=""
+ PTHREAD_CFLAGS=""
+ fi
+ CC="$ax_pthread_save_CC"
+ CFLAGS="$ax_pthread_save_CFLAGS"
+ LIBS="$ax_pthread_save_LIBS"
+fi
+
+# We must check for the threads library under a number of different
+# names; the ordering is very important because some systems
+# (e.g. DEC) have both -lpthread and -lpthreads, where one of the
+# libraries is broken (non-POSIX).
+
+# Create a list of thread flags to try. Items starting with a "-" are
+# C compiler flags, and other items are library names, except for "none"
+# which indicates that we try without any flags at all, and "pthread-config"
+# which is a program returning the flags for the Pth emulation library.
+
+ax_pthread_flags="pthreads none -Kthread -pthread -pthreads -mthreads pthread --thread-safe -mt pthread-config"
+
+# The ordering *is* (sometimes) important. Some notes on the
+# individual items follow:
+
+# pthreads: AIX (must check this before -lpthread)
+# none: in case threads are in libc; should be tried before -Kthread and
+# other compiler flags to prevent continual compiler warnings
+# -Kthread: Sequent (threads in libc, but -Kthread needed for pthread.h)
+# -pthread: Linux/gcc (kernel threads), BSD/gcc (userland threads), Tru64
+# (Note: HP C rejects this with "bad form for `-t' option")
+# -pthreads: Solaris/gcc (Note: HP C also rejects)
+# -mt: Sun Workshop C (may only link SunOS threads [-lthread], but it
+# doesn't hurt to check since this sometimes defines pthreads and
+# -D_REENTRANT too), HP C (must be checked before -lpthread, which
+# is present but should not be used directly; and before -mthreads,
+# because the compiler interprets this as "-mt" + "-hreads")
+# -mthreads: Mingw32/gcc, Lynx/gcc
+# pthread: Linux, etcetera
+# --thread-safe: KAI C++
+# pthread-config: use pthread-config program (for GNU Pth library)
+
+case $host_os in
+
+ freebsd*)
+
+ # -kthread: FreeBSD kernel threads (preferred to -pthread since SMP-able)
+ # lthread: LinuxThreads port on FreeBSD (also preferred to -pthread)
+
+ ax_pthread_flags="-kthread lthread $ax_pthread_flags"
+ ;;
+
+ hpux*)
+
+ # From the cc(1) man page: "[-mt] Sets various -D flags to enable
+ # multi-threading and also sets -lpthread."
+
+ ax_pthread_flags="-mt -pthread pthread $ax_pthread_flags"
+ ;;
+
+ openedition*)
+
+ # IBM z/OS requires a feature-test macro to be defined in order to
+ # enable POSIX threads at all, so give the user a hint if this is
+ # not set. (We don't define these ourselves, as they can affect
+ # other portions of the system API in unpredictable ways.)
+
+ AC_EGREP_CPP([AX_PTHREAD_ZOS_MISSING],
+ [
+# if !defined(_OPEN_THREADS) && !defined(_UNIX03_THREADS)
+ AX_PTHREAD_ZOS_MISSING
+# endif
+ ],
+ [AC_MSG_WARN([IBM z/OS requires -D_OPEN_THREADS or -D_UNIX03_THREADS to enable pthreads support.])])
+ ;;
+
+ solaris*)
+
+ # On Solaris (at least, for some versions), libc contains stubbed
+ # (non-functional) versions of the pthreads routines, so link-based
+ # tests will erroneously succeed. (N.B.: The stubs are missing
+ # pthread_cleanup_push, or rather a function called by this macro,
+ # so we could check for that, but who knows whether they'll stub
+ # that too in a future libc.) So we'll check first for the
+ # standard Solaris way of linking pthreads (-mt -lpthread).
+
+ ax_pthread_flags="-mt,pthread pthread $ax_pthread_flags"
+ ;;
+esac
+
+# GCC generally uses -pthread, or -pthreads on some platforms (e.g. SPARC)
+
+AS_IF([test "x$GCC" = "xyes"],
+ [ax_pthread_flags="-pthread -pthreads $ax_pthread_flags"])
+
+# The presence of a feature test macro requesting re-entrant function
+# definitions is, on some systems, a strong hint that pthreads support is
+# correctly enabled
+
+case $host_os in
+ darwin* | hpux* | linux* | osf* | solaris*)
+ ax_pthread_check_macro="_REENTRANT"
+ ;;
+
+ aix*)
+ ax_pthread_check_macro="_THREAD_SAFE"
+ ;;
+
+ *)
+ ax_pthread_check_macro="--"
+ ;;
+esac
+AS_IF([test "x$ax_pthread_check_macro" = "x--"],
+ [ax_pthread_check_cond=0],
+ [ax_pthread_check_cond="!defined($ax_pthread_check_macro)"])
+
+# Are we compiling with Clang?
+
+AC_CACHE_CHECK([whether $CC is Clang],
+ [ax_cv_PTHREAD_CLANG],
+ [ax_cv_PTHREAD_CLANG=no
+ # Note that Autoconf sets GCC=yes for Clang as well as GCC
+ if test "x$GCC" = "xyes"; then
+ AC_EGREP_CPP([AX_PTHREAD_CC_IS_CLANG],
+ [/* Note: Clang 2.7 lacks __clang_[a-z]+__ */
+# if defined(__clang__) && defined(__llvm__)
+ AX_PTHREAD_CC_IS_CLANG
+# endif
+ ],
+ [ax_cv_PTHREAD_CLANG=yes])
+ fi
+ ])
+ax_pthread_clang="$ax_cv_PTHREAD_CLANG"
+
+ax_pthread_clang_warning=no
+
+# Clang needs special handling, because older versions handle the -pthread
+# option in a rather... idiosyncratic way
+
+if test "x$ax_pthread_clang" = "xyes"; then
+
+ # Clang takes -pthread; it has never supported any other flag
+
+ # (Note 1: This will need to be revisited if a system that Clang
+ # supports has POSIX threads in a separate library. This tends not
+ # to be the way of modern systems, but it's conceivable.)
+
+ # (Note 2: On some systems, notably Darwin, -pthread is not needed
+ # to get POSIX threads support; the API is always present and
+ # active. We could reasonably leave PTHREAD_CFLAGS empty. But
+ # -pthread does define _REENTRANT, and while the Darwin headers
+ # ignore this macro, third-party headers might not.)
+
+ PTHREAD_CFLAGS="-pthread"
+ PTHREAD_LIBS=
+
+ ax_pthread_ok=yes
+
+ # However, older versions of Clang make a point of warning the user
+ # that, in an invocation where only linking and no compilation is
+ # taking place, the -pthread option has no effect ("argument unused
+ # during compilation"). They expect -pthread to be passed in only
+ # when source code is being compiled.
+ #
+ # Problem is, this is at odds with the way Automake and most other
+ # C build frameworks function, which is that the same flags used in
+ # compilation (CFLAGS) are also used in linking. Many systems
+ # supported by AX_PTHREAD require exactly this for POSIX threads
+ # support, and in fact it is often not straightforward to specify a
+ # flag that is used only in the compilation phase and not in
+ # linking. Such a scenario is extremely rare in practice.
+ #
+ # Even though use of the -pthread flag in linking would only print
+ # a warning, this can be a nuisance for well-run software projects
+ # that build with -Werror. So if the active version of Clang has
+ # this misfeature, we search for an option to squash it.
+
+ AC_CACHE_CHECK([whether Clang needs flag to prevent "argument unused" warning when linking with -pthread],
+ [ax_cv_PTHREAD_CLANG_NO_WARN_FLAG],
+ [ax_cv_PTHREAD_CLANG_NO_WARN_FLAG=unknown
+ # Create an alternate version of $ac_link that compiles and
+ # links in two steps (.c -> .o, .o -> exe) instead of one
+ # (.c -> exe), because the warning occurs only in the second
+ # step
+ ax_pthread_save_ac_link="$ac_link"
+ ax_pthread_sed='s/conftest\.\$ac_ext/conftest.$ac_objext/g'
+ ax_pthread_link_step=`$as_echo "$ac_link" | sed "$ax_pthread_sed"`
+ ax_pthread_2step_ac_link="($ac_compile) && (echo ==== >&5) && ($ax_pthread_link_step)"
+ ax_pthread_save_CFLAGS="$CFLAGS"
+ for ax_pthread_try in '' -Qunused-arguments -Wno-unused-command-line-argument unknown; do
+ AS_IF([test "x$ax_pthread_try" = "xunknown"], [break])
+ CFLAGS="-Werror -Wunknown-warning-option $ax_pthread_try -pthread $ax_pthread_save_CFLAGS"
+ ac_link="$ax_pthread_save_ac_link"
+ AC_LINK_IFELSE([AC_LANG_SOURCE([[int main(void){return 0;}]])],
+ [ac_link="$ax_pthread_2step_ac_link"
+ AC_LINK_IFELSE([AC_LANG_SOURCE([[int main(void){return 0;}]])],
+ [break])
+ ])
+ done
+ ac_link="$ax_pthread_save_ac_link"
+ CFLAGS="$ax_pthread_save_CFLAGS"
+ AS_IF([test "x$ax_pthread_try" = "x"], [ax_pthread_try=no])
+ ax_cv_PTHREAD_CLANG_NO_WARN_FLAG="$ax_pthread_try"
+ ])
+
+ case "$ax_cv_PTHREAD_CLANG_NO_WARN_FLAG" in
+ no | unknown) ;;
+ *) PTHREAD_CFLAGS="$ax_cv_PTHREAD_CLANG_NO_WARN_FLAG $PTHREAD_CFLAGS" ;;
+ esac
+
+fi # $ax_pthread_clang = yes
+
+if test "x$ax_pthread_ok" = "xno"; then
+for ax_pthread_try_flag in $ax_pthread_flags; do
+
+ case $ax_pthread_try_flag in
+ none)
+ AC_MSG_CHECKING([whether pthreads work without any flags])
+ ;;
+
+ -mt,pthread)
+ AC_MSG_CHECKING([whether pthreads work with -mt -lpthread])
+ PTHREAD_CFLAGS="-mt"
+ PTHREAD_LIBS="-lpthread"
+ ;;
+
+ -*)
+ AC_MSG_CHECKING([whether pthreads work with $ax_pthread_try_flag])
+ PTHREAD_CFLAGS="$ax_pthread_try_flag"
+ ;;
+
+ pthread-config)
+ AC_CHECK_PROG([ax_pthread_config], [pthread-config], [yes], [no])
+ AS_IF([test "x$ax_pthread_config" = "xno"], [continue])
+ PTHREAD_CFLAGS="`pthread-config --cflags`"
+ PTHREAD_LIBS="`pthread-config --ldflags` `pthread-config --libs`"
+ ;;
+
+ *)
+ AC_MSG_CHECKING([for the pthreads library -l$ax_pthread_try_flag])
+ PTHREAD_LIBS="-l$ax_pthread_try_flag"
+ ;;
+ esac
+
+ ax_pthread_save_CFLAGS="$CFLAGS"
+ ax_pthread_save_LIBS="$LIBS"
+ CFLAGS="$CFLAGS $PTHREAD_CFLAGS"
+ LIBS="$PTHREAD_LIBS $LIBS"
+
+ # Check for various functions. We must include pthread.h,
+ # since some functions may be macros. (On the Sequent, we
+ # need a special flag -Kthread to make this header compile.)
+ # We check for pthread_join because it is in -lpthread on IRIX
+ # while pthread_create is in libc. We check for pthread_attr_init
+ # due to DEC craziness with -lpthreads. We check for
+ # pthread_cleanup_push because it is one of the few pthread
+ # functions on Solaris that doesn't have a non-functional libc stub.
+ # We try pthread_create on general principles.
+
+ AC_LINK_IFELSE([AC_LANG_PROGRAM([#include <pthread.h>
+# if $ax_pthread_check_cond
+# error "$ax_pthread_check_macro must be defined"
+# endif
+ static void routine(void *a) { a = 0; }
+ static void *start_routine(void *a) { return a; }],
+ [pthread_t th; pthread_attr_t attr;
+ pthread_create(&th, 0, start_routine, 0);
+ pthread_join(th, 0);
+ pthread_attr_init(&attr);
+ pthread_cleanup_push(routine, 0);
+ pthread_cleanup_pop(0) /* ; */])],
+ [ax_pthread_ok=yes],
+ [])
+
+ CFLAGS="$ax_pthread_save_CFLAGS"
+ LIBS="$ax_pthread_save_LIBS"
+
+ AC_MSG_RESULT([$ax_pthread_ok])
+ AS_IF([test "x$ax_pthread_ok" = "xyes"], [break])
+
+ PTHREAD_LIBS=""
+ PTHREAD_CFLAGS=""
+done
+fi
+
+# Various other checks:
+if test "x$ax_pthread_ok" = "xyes"; then
+ ax_pthread_save_CFLAGS="$CFLAGS"
+ ax_pthread_save_LIBS="$LIBS"
+ CFLAGS="$CFLAGS $PTHREAD_CFLAGS"
+ LIBS="$PTHREAD_LIBS $LIBS"
+
+ # Detect AIX lossage: JOINABLE attribute is called UNDETACHED.
+ AC_CACHE_CHECK([for joinable pthread attribute],
+ [ax_cv_PTHREAD_JOINABLE_ATTR],
+ [ax_cv_PTHREAD_JOINABLE_ATTR=unknown
+ for ax_pthread_attr in PTHREAD_CREATE_JOINABLE PTHREAD_CREATE_UNDETACHED; do
+ AC_LINK_IFELSE([AC_LANG_PROGRAM([#include <pthread.h>],
+ [int attr = $ax_pthread_attr; return attr /* ; */])],
+ [ax_cv_PTHREAD_JOINABLE_ATTR=$ax_pthread_attr; break],
+ [])
+ done
+ ])
+ AS_IF([test "x$ax_cv_PTHREAD_JOINABLE_ATTR" != "xunknown" && \
+ test "x$ax_cv_PTHREAD_JOINABLE_ATTR" != "xPTHREAD_CREATE_JOINABLE" && \
+ test "x$ax_pthread_joinable_attr_defined" != "xyes"],
+ [AC_DEFINE_UNQUOTED([PTHREAD_CREATE_JOINABLE],
+ [$ax_cv_PTHREAD_JOINABLE_ATTR],
+ [Define to necessary symbol if this constant
+ uses a non-standard name on your system.])
+ ax_pthread_joinable_attr_defined=yes
+ ])
+
+ AC_CACHE_CHECK([whether more special flags are required for pthreads],
+ [ax_cv_PTHREAD_SPECIAL_FLAGS],
+ [ax_cv_PTHREAD_SPECIAL_FLAGS=no
+ case $host_os in
+ solaris*)
+ ax_cv_PTHREAD_SPECIAL_FLAGS="-D_POSIX_PTHREAD_SEMANTICS"
+ ;;
+ esac
+ ])
+ AS_IF([test "x$ax_cv_PTHREAD_SPECIAL_FLAGS" != "xno" && \
+ test "x$ax_pthread_special_flags_added" != "xyes"],
+ [PTHREAD_CFLAGS="$ax_cv_PTHREAD_SPECIAL_FLAGS $PTHREAD_CFLAGS"
+ ax_pthread_special_flags_added=yes])
+
+ AC_CACHE_CHECK([for PTHREAD_PRIO_INHERIT],
+ [ax_cv_PTHREAD_PRIO_INHERIT],
+ [AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include <pthread.h>]],
+ [[int i = PTHREAD_PRIO_INHERIT;
+ return i;]])],
+ [ax_cv_PTHREAD_PRIO_INHERIT=yes],
+ [ax_cv_PTHREAD_PRIO_INHERIT=no])
+ ])
+ AS_IF([test "x$ax_cv_PTHREAD_PRIO_INHERIT" = "xyes" && \
+ test "x$ax_pthread_prio_inherit_defined" != "xyes"],
+ [AC_DEFINE([HAVE_PTHREAD_PRIO_INHERIT], [1], [Have PTHREAD_PRIO_INHERIT.])
+ ax_pthread_prio_inherit_defined=yes
+ ])
+
+ CFLAGS="$ax_pthread_save_CFLAGS"
+ LIBS="$ax_pthread_save_LIBS"
+
+ # More AIX lossage: compile with *_r variant
+ if test "x$GCC" != "xyes"; then
+ case $host_os in
+ aix*)
+ AS_CASE(["x/$CC"],
+ [x*/c89|x*/c89_128|x*/c99|x*/c99_128|x*/cc|x*/cc128|x*/xlc|x*/xlc_v6|x*/xlc128|x*/xlc128_v6],
+ [#handle absolute path differently from PATH based program lookup
+ AS_CASE(["x$CC"],
+ [x/*],
+ [AS_IF([AS_EXECUTABLE_P([${CC}_r])],[PTHREAD_CC="${CC}_r"])],
+ [AC_CHECK_PROGS([PTHREAD_CC],[${CC}_r],[$CC])])])
+ ;;
+ esac
+ fi
+fi
+
+test -n "$PTHREAD_CC" || PTHREAD_CC="$CC"
+
+AC_SUBST([PTHREAD_LIBS])
+AC_SUBST([PTHREAD_CFLAGS])
+AC_SUBST([PTHREAD_CC])
+
+# Finally, execute ACTION-IF-FOUND/ACTION-IF-NOT-FOUND:
+if test "x$ax_pthread_ok" = "xyes"; then
+ ifelse([$1],,[AC_DEFINE([HAVE_PTHREAD],[1],[Define if you have POSIX threads libraries and header files.])],[$1])
+ :
+else
+ ax_pthread_ok=no
+ $2
+fi
+AC_LANG_POP
+])dnl AX_PTHREAD
diff --git a/m4/osmo_ac_code_coverage.m4 b/m4/osmo_ac_code_coverage.m4
new file mode 100644
index 00000000..7921db59
--- /dev/null
+++ b/m4/osmo_ac_code_coverage.m4
@@ -0,0 +1,51 @@
+AC_DEFUN([OSMO_AC_CODE_COVERAGE],[
+ dnl Check for --enable-code-coverage
+ AC_REQUIRE([OSMO_AX_CODE_COVERAGE])
+ AC_REQUIRE([AX_CHECK_COMPILE_FLAG])
+
+ AS_IF([ test "x$enable_code_coverage" = "xyes" ], [
+ # Check whether --coverage flags is supported and add it to CFLAGS
+ # When it is not supported add CODE_COVERAGE_CFLAGS to CFLAGS instead
+ AX_CHECK_COMPILE_FLAG([--coverage],
+ [CFLAGS="$CFLAGS -O0 -g --coverage"],
+ [CFLAGS="$CFLAGS $CODE_COVERAGE_CFLAGS"])
+
+ # Add both the absolute source and build directories to the coverage directories.
+ CODE_COVERAGE_DIRECTORY='$(abspath $(abs_top_srcdir)) $(abspath $(abs_top_builddir))'
+ AC_SUBST(CODE_COVERAGE_DIRECTORY)
+
+ # Enable branch coverage by default
+ CODE_COVERAGE_BRANCH_COVERAGE='1'
+ AC_SUBST(CODE_COVERAGE_BRANCH_COVERAGE)
+
+ # Exclude external files by default
+ CODE_COVERAGE_LCOV_OPTIONS='$(CODE_COVERAGE_LCOV_OPTIONS_DEFAULT) --no-external'
+ AC_SUBST(CODE_COVERAGE_LCOV_OPTIONS)
+
+ # Exclude tests sources from the coverage report
+ CODE_COVERAGE_IGNORE_PATTERN='"$(abspath $(abs_top_srcdir))/tests/*"'
+ AC_SUBST(CODE_COVERAGE_IGNORE_PATTERN)
+
+ # lcov_cobertura is needed only when you want to export the coverage report in
+ # the Cobertura's XML format supported by Jenkin's Cobertura plugin
+ AC_CHECK_PROG([LCOV_COBERTURA], [lcov_cobertura], [lcov_cobertura])
+ AS_IF([test "x$LCOV_COBERTURA" != "xno"], [m4_pattern_allow([AM_V_GEN]) CODE_COVERAGE_RULES+='
+coverage-cobertura.xml: $(CODE_COVERAGE_OUTPUT_FILE)
+ $(AM_V_GEN)$(LCOV_COBERTURA) -b $(top_srcdir) -o $$@@ $(CODE_COVERAGE_OUTPUT_FILE)
+
+.PHONY: code-coverage-cobertura
+code-coverage-cobertura: code-coverage-capture coverage-cobertura.xml
+'
+ ], [CODE_COVERAGE_RULES+='
+.PHONY: code-coverage-cobertura
+code-coverage-cobertura:
+ @echo "Need to install lcov_cobertura"
+'
+ ])
+ ], [CODE_COVERAGE_RULES+='
+.PHONY: code-coverage-cobertura
+code-coverage-cobertura:
+ @echo "Need to and reconfigure with --enable-code-coverage"
+'
+ ])
+])
diff --git a/m4/osmo_ax_code_coverage.m4 b/m4/osmo_ax_code_coverage.m4
new file mode 100644
index 00000000..23cebb04
--- /dev/null
+++ b/m4/osmo_ax_code_coverage.m4
@@ -0,0 +1,267 @@
+#
+# Renamed version of AX_CODE_COVERAGE macro from autoconf-archive v2018.03.13
+#
+# ===========================================================================
+# https://www.gnu.org/software/autoconf-archive/ax_code_coverage.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+# OSMO_AX_CODE_COVERAGE()
+#
+# DESCRIPTION
+#
+# Defines CODE_COVERAGE_CPPFLAGS, CODE_COVERAGE_CFLAGS,
+# CODE_COVERAGE_CXXFLAGS and CODE_COVERAGE_LIBS which should be included
+# in the CPPFLAGS, CFLAGS CXXFLAGS and LIBS/LIBADD variables of every
+# build target (program or library) which should be built with code
+# coverage support. Also defines CODE_COVERAGE_RULES which should be
+# substituted in your Makefile; and $enable_code_coverage which can be
+# used in subsequent configure output. CODE_COVERAGE_ENABLED is defined
+# and substituted, and corresponds to the value of the
+# --enable-code-coverage option, which defaults to being disabled.
+#
+# Test also for gcov program and create GCOV variable that could be
+# substituted.
+#
+# Note that all optimization flags in CFLAGS must be disabled when code
+# coverage is enabled.
+#
+# Usage example:
+#
+# configure.ac:
+#
+# OSMO_AX_CODE_COVERAGE
+#
+# Makefile.am:
+#
+# @CODE_COVERAGE_RULES@
+# my_program_LIBS = ... $(CODE_COVERAGE_LIBS) ...
+# my_program_CPPFLAGS = ... $(CODE_COVERAGE_CPPFLAGS) ...
+# my_program_CFLAGS = ... $(CODE_COVERAGE_CFLAGS) ...
+# my_program_CXXFLAGS = ... $(CODE_COVERAGE_CXXFLAGS) ...
+#
+# This results in a "check-code-coverage" rule being added to any
+# Makefile.am which includes "@CODE_COVERAGE_RULES@" (assuming the module
+# has been configured with --enable-code-coverage). Running `make
+# check-code-coverage` in that directory will run the module's test suite
+# (`make check`) and build a code coverage report detailing the code which
+# was touched, then print the URI for the report.
+#
+# In earlier versions of this macro, CODE_COVERAGE_LDFLAGS was defined
+# instead of CODE_COVERAGE_LIBS. They are both still defined, but use of
+# CODE_COVERAGE_LIBS is preferred for clarity; CODE_COVERAGE_LDFLAGS is
+# deprecated. They have the same value.
+#
+# This code was derived from Makefile.decl in GLib, originally licenced
+# under LGPLv2.1+.
+#
+# LICENSE
+#
+# Copyright (c) 2012, 2016 Philip Withnall
+# Copyright (c) 2012 Xan Lopez
+# Copyright (c) 2012 Christian Persch
+# Copyright (c) 2012 Paolo Borelli
+# Copyright (c) 2012 Dan Winship
+# Copyright (c) 2015 Bastien ROUCARIES
+#
+# This library is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or (at
+# your option) any later version.
+#
+# This library 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 Lesser
+# General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+#serial 25
+
+AC_DEFUN([OSMO_AX_CODE_COVERAGE],[
+ dnl Check for --enable-code-coverage
+ AC_REQUIRE([AC_PROG_SED])
+
+ # allow to override gcov location
+ AC_ARG_WITH([gcov],
+ [AS_HELP_STRING([--with-gcov[=GCOV]], [use given GCOV for coverage (GCOV=gcov).])],
+ [_AX_CODE_COVERAGE_GCOV_PROG_WITH=$with_gcov],
+ [_AX_CODE_COVERAGE_GCOV_PROG_WITH=gcov])
+
+ AC_MSG_CHECKING([whether to build with code coverage support])
+ AC_ARG_ENABLE([code-coverage],
+ AS_HELP_STRING([--enable-code-coverage],
+ [Whether to enable code coverage support]),,
+ enable_code_coverage=no)
+
+ AM_CONDITIONAL([CODE_COVERAGE_ENABLED], [test x$enable_code_coverage = xyes])
+ AC_SUBST([CODE_COVERAGE_ENABLED], [$enable_code_coverage])
+ AC_MSG_RESULT($enable_code_coverage)
+
+ AS_IF([ test "$enable_code_coverage" = "yes" ], [
+ # check for gcov
+ AC_CHECK_TOOL([GCOV],
+ [$_AX_CODE_COVERAGE_GCOV_PROG_WITH],
+ [:])
+ AS_IF([test "X$GCOV" = "X:"],
+ [AC_MSG_ERROR([gcov is needed to do coverage])])
+ AC_SUBST([GCOV])
+
+ dnl Check if gcc is being used
+ AS_IF([ test "$GCC" = "no" ], [
+ AC_MSG_ERROR([not compiling with gcc, which is required for gcov code coverage])
+ ])
+
+ AC_CHECK_PROG([LCOV], [lcov], [lcov])
+ AC_CHECK_PROG([GENHTML], [genhtml], [genhtml])
+
+ AS_IF([ test -z "$LCOV" ], [
+ AC_MSG_ERROR([To enable code coverage reporting you must have lcov installed])
+ ])
+
+ AS_IF([ test -z "$GENHTML" ], [
+ AC_MSG_ERROR([Could not find genhtml from the lcov package])
+ ])
+
+ dnl Build the code coverage flags
+ dnl Define CODE_COVERAGE_LDFLAGS for backwards compatibility
+ CODE_COVERAGE_CPPFLAGS="-DNDEBUG"
+ CODE_COVERAGE_CFLAGS="-O0 -g -fprofile-arcs -ftest-coverage"
+ CODE_COVERAGE_CXXFLAGS="-O0 -g -fprofile-arcs -ftest-coverage"
+ CODE_COVERAGE_LIBS="-lgcov"
+ CODE_COVERAGE_LDFLAGS="$CODE_COVERAGE_LIBS"
+
+ AC_SUBST([CODE_COVERAGE_CPPFLAGS])
+ AC_SUBST([CODE_COVERAGE_CFLAGS])
+ AC_SUBST([CODE_COVERAGE_CXXFLAGS])
+ AC_SUBST([CODE_COVERAGE_LIBS])
+ AC_SUBST([CODE_COVERAGE_LDFLAGS])
+
+ [CODE_COVERAGE_RULES_CHECK='
+ -$(A''M_V_at)$(MAKE) $(AM_MAKEFLAGS) -k check
+ $(A''M_V_at)$(MAKE) $(AM_MAKEFLAGS) code-coverage-capture
+']
+ [CODE_COVERAGE_RULES_CAPTURE='
+ $(code_coverage_v_lcov_cap)$(LCOV) $(code_coverage_quiet) $(addprefix --directory ,$(CODE_COVERAGE_DIRECTORY)) --capture --output-file "$(CODE_COVERAGE_OUTPUT_FILE).tmp" --test-name "$(call code_coverage_sanitize,$(PACKAGE_NAME)-$(PACKAGE_VERSION))" --no-checksum --compat-libtool $(CODE_COVERAGE_LCOV_SHOPTS) $(CODE_COVERAGE_LCOV_OPTIONS)
+ $(code_coverage_v_lcov_ign)$(LCOV) $(code_coverage_quiet) $(addprefix --directory ,$(CODE_COVERAGE_DIRECTORY)) --remove "$(CODE_COVERAGE_OUTPUT_FILE).tmp" "/tmp/*" $(CODE_COVERAGE_IGNORE_PATTERN) --output-file "$(CODE_COVERAGE_OUTPUT_FILE)" $(CODE_COVERAGE_LCOV_SHOPTS) $(CODE_COVERAGE_LCOV_RMOPTS)
+ -@rm -f $(CODE_COVERAGE_OUTPUT_FILE).tmp
+ $(code_coverage_v_genhtml)LANG=C $(GENHTML) $(code_coverage_quiet) $(addprefix --prefix ,$(CODE_COVERAGE_DIRECTORY)) --output-directory "$(CODE_COVERAGE_OUTPUT_DIRECTORY)" --title "$(PACKAGE_NAME)-$(PACKAGE_VERSION) Code Coverage" --legend --show-details "$(CODE_COVERAGE_OUTPUT_FILE)" $(CODE_COVERAGE_GENHTML_OPTIONS)
+ @echo "file://$(abs_builddir)/$(CODE_COVERAGE_OUTPUT_DIRECTORY)/index.html"
+']
+ [CODE_COVERAGE_RULES_CLEAN='
+clean: code-coverage-clean
+distclean: code-coverage-clean
+code-coverage-clean:
+ -$(LCOV) --directory $(top_builddir) -z
+ -rm -rf $(CODE_COVERAGE_OUTPUT_FILE) $(CODE_COVERAGE_OUTPUT_FILE).tmp $(CODE_COVERAGE_OUTPUT_DIRECTORY)
+ -find . \( -name "*.gcda" -o -name "*.gcno" -o -name "*.gcov" \) -delete
+']
+ ], [
+ [CODE_COVERAGE_RULES_CHECK='
+ @echo "Need to reconfigure with --enable-code-coverage"
+']
+ CODE_COVERAGE_RULES_CAPTURE="$CODE_COVERAGE_RULES_CHECK"
+ CODE_COVERAGE_RULES_CLEAN=''
+ ])
+
+[CODE_COVERAGE_RULES='
+# Code coverage
+#
+# Optional:
+# - CODE_COVERAGE_DIRECTORY: Top-level directory for code coverage reporting.
+# Multiple directories may be specified, separated by whitespace.
+# (Default: $(top_builddir))
+# - CODE_COVERAGE_OUTPUT_FILE: Filename and path for the .info file generated
+# by lcov for code coverage. (Default:
+# $(PACKAGE_NAME)-$(PACKAGE_VERSION)-coverage.info)
+# - CODE_COVERAGE_OUTPUT_DIRECTORY: Directory for generated code coverage
+# reports to be created. (Default:
+# $(PACKAGE_NAME)-$(PACKAGE_VERSION)-coverage)
+# - CODE_COVERAGE_BRANCH_COVERAGE: Set to 1 to enforce branch coverage,
+# set to 0 to disable it and leave empty to stay with the default.
+# (Default: empty)
+# - CODE_COVERAGE_LCOV_SHOPTS_DEFAULT: Extra options shared between both lcov
+# instances. (Default: based on $CODE_COVERAGE_BRANCH_COVERAGE)
+# - CODE_COVERAGE_LCOV_SHOPTS: Extra options to shared between both lcov
+# instances. (Default: $CODE_COVERAGE_LCOV_SHOPTS_DEFAULT)
+# - CODE_COVERAGE_LCOV_OPTIONS_GCOVPATH: --gcov-tool pathtogcov
+# - CODE_COVERAGE_LCOV_OPTIONS_DEFAULT: Extra options to pass to the
+# collecting lcov instance. (Default: $CODE_COVERAGE_LCOV_OPTIONS_GCOVPATH)
+# - CODE_COVERAGE_LCOV_OPTIONS: Extra options to pass to the collecting lcov
+# instance. (Default: $CODE_COVERAGE_LCOV_OPTIONS_DEFAULT)
+# - CODE_COVERAGE_LCOV_RMOPTS_DEFAULT: Extra options to pass to the filtering
+# lcov instance. (Default: empty)
+# - CODE_COVERAGE_LCOV_RMOPTS: Extra options to pass to the filtering lcov
+# instance. (Default: $CODE_COVERAGE_LCOV_RMOPTS_DEFAULT)
+# - CODE_COVERAGE_GENHTML_OPTIONS_DEFAULT: Extra options to pass to the
+# genhtml instance. (Default: based on $CODE_COVERAGE_BRANCH_COVERAGE)
+# - CODE_COVERAGE_GENHTML_OPTIONS: Extra options to pass to the genhtml
+# instance. (Default: $CODE_COVERAGE_GENHTML_OPTIONS_DEFAULT)
+# - CODE_COVERAGE_IGNORE_PATTERN: Extra glob pattern of files to ignore
+#
+# The generated report will be titled using the $(PACKAGE_NAME) and
+# $(PACKAGE_VERSION). In order to add the current git hash to the title,
+# use the git-version-gen script, available online.
+
+# Optional variables
+CODE_COVERAGE_DIRECTORY ?= $(top_builddir)
+CODE_COVERAGE_OUTPUT_FILE ?= $(PACKAGE_NAME)-$(PACKAGE_VERSION)-coverage.info
+CODE_COVERAGE_OUTPUT_DIRECTORY ?= $(PACKAGE_NAME)-$(PACKAGE_VERSION)-coverage
+CODE_COVERAGE_BRANCH_COVERAGE ?=
+CODE_COVERAGE_LCOV_SHOPTS_DEFAULT ?= $(if $(CODE_COVERAGE_BRANCH_COVERAGE),\
+--rc lcov_branch_coverage=$(CODE_COVERAGE_BRANCH_COVERAGE))
+CODE_COVERAGE_LCOV_SHOPTS ?= $(CODE_COVERAGE_LCOV_SHOPTS_DEFAULT)
+CODE_COVERAGE_LCOV_OPTIONS_GCOVPATH ?= --gcov-tool "$(GCOV)"
+CODE_COVERAGE_LCOV_OPTIONS_DEFAULT ?= $(CODE_COVERAGE_LCOV_OPTIONS_GCOVPATH)
+CODE_COVERAGE_LCOV_OPTIONS ?= $(CODE_COVERAGE_LCOV_OPTIONS_DEFAULT)
+CODE_COVERAGE_LCOV_RMOPTS_DEFAULT ?=
+CODE_COVERAGE_LCOV_RMOPTS ?= $(CODE_COVERAGE_LCOV_RMOPTS_DEFAULT)
+CODE_COVERAGE_GENHTML_OPTIONS_DEFAULT ?=\
+$(if $(CODE_COVERAGE_BRANCH_COVERAGE),\
+--rc genhtml_branch_coverage=$(CODE_COVERAGE_BRANCH_COVERAGE))
+CODE_COVERAGE_GENHTML_OPTIONS ?= $(CODE_COVERAGE_GENHTML_OPTIONS_DEFAULT)
+CODE_COVERAGE_IGNORE_PATTERN ?=
+
+GITIGNOREFILES ?=
+GITIGNOREFILES += $(CODE_COVERAGE_OUTPUT_FILE) $(CODE_COVERAGE_OUTPUT_DIRECTORY)
+
+code_coverage_v_lcov_cap = $(code_coverage_v_lcov_cap_$(V))
+code_coverage_v_lcov_cap_ = $(code_coverage_v_lcov_cap_$(AM_DEFAULT_VERBOSITY))
+code_coverage_v_lcov_cap_0 = @echo " LCOV --capture"\
+ $(CODE_COVERAGE_OUTPUT_FILE);
+code_coverage_v_lcov_ign = $(code_coverage_v_lcov_ign_$(V))
+code_coverage_v_lcov_ign_ = $(code_coverage_v_lcov_ign_$(AM_DEFAULT_VERBOSITY))
+code_coverage_v_lcov_ign_0 = @echo " LCOV --remove /tmp/*"\
+ $(CODE_COVERAGE_IGNORE_PATTERN);
+code_coverage_v_genhtml = $(code_coverage_v_genhtml_$(V))
+code_coverage_v_genhtml_ = $(code_coverage_v_genhtml_$(AM_DEFAULT_VERBOSITY))
+code_coverage_v_genhtml_0 = @echo " GEN " $(CODE_COVERAGE_OUTPUT_DIRECTORY);
+code_coverage_quiet = $(code_coverage_quiet_$(V))
+code_coverage_quiet_ = $(code_coverage_quiet_$(AM_DEFAULT_VERBOSITY))
+code_coverage_quiet_0 = --quiet
+
+# sanitizes the test-name: replaces with underscores: dashes and dots
+code_coverage_sanitize = $(subst -,_,$(subst .,_,$(1)))
+
+# Use recursive makes in order to ignore errors during check
+check-code-coverage:'"$CODE_COVERAGE_RULES_CHECK"'
+
+# Capture code coverage data
+code-coverage-capture: code-coverage-capture-hook'"$CODE_COVERAGE_RULES_CAPTURE"'
+
+# Hook rule executed before code-coverage-capture, overridable by the user
+code-coverage-capture-hook:
+
+'"$CODE_COVERAGE_RULES_CLEAN"'
+
+A''M_DISTCHECK_CONFIGURE_FLAGS ?=
+A''M_DISTCHECK_CONFIGURE_FLAGS += --disable-code-coverage
+
+.PHONY: check-code-coverage code-coverage-capture code-coverage-capture-hook code-coverage-clean
+']
+
+ AC_SUBST([CODE_COVERAGE_RULES])
+ m4_ifdef([_AM_SUBST_NOTMAKE], [_AM_SUBST_NOTMAKE([CODE_COVERAGE_RULES])])
+])
diff --git a/osmo-release.sh b/osmo-release.sh
index 1effe7e2..31cd1a03 100755
--- a/osmo-release.sh
+++ b/osmo-release.sh
@@ -29,7 +29,7 @@ check_configureac_debctrl_deps_match() {
else
configureac_file="configure.ac"
fi
- configureac_list=$(grep -e "PKG_CHECK_MODULES" "${GIT_TOPDIR}/${configureac_file}" | cut -d "," -f 2 | tr -d ")" | tr -d " " | sed "s/>=/ /g")
+ configureac_list=$(grep -e "PKG_CHECK_MODULES(" "${GIT_TOPDIR}/${configureac_file}" | cut -d "," -f 2 | tr -d ")" | tr -d "[" | tr -d "]" | tr -d " " | sed "s/>=/ /g")
echo "$configureac_list" | \
{ return_error=0
while read -r dep ver; do
@@ -156,16 +156,19 @@ if [ "z$LIBVERS" != "z" ]; then
if [ "z$DRY_RUN" != "z0" ]; then
exit 0
fi
- if [ -f "TODO-RELEASE" ]; then
- grep '#' TODO-RELEASE > TODO-RELEASE.clean
- mv TODO-RELEASE.clean TODO-RELEASE
- git add TODO-RELEASE
- fi
fi
if [ "z$DRY_RUN" != "z0" ]; then
exit 0
fi
+
+set -e
+if [ -f "TODO-RELEASE" ]; then
+ grep '#' TODO-RELEASE > TODO-RELEASE.clean
+ mv TODO-RELEASE.clean TODO-RELEASE
+ git add TODO-RELEASE
+fi
+
gbp dch --debian-tag='%(version)s' --auto --meta --git-author --multimaint-merge --ignore-branch --new-version="$NEW_VER"
dch -r -m --distribution "unstable" ""
git add ${GIT_TOPDIR}/debian/changelog
diff --git a/src/Makefile.am b/src/Makefile.am
index f9378100..b2c9204f 100644
--- a/src/Makefile.am
+++ b/src/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=14:0:2
+LIBVERSION=16:0:0
AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_builddir)/include
-AM_CFLAGS = -Wall $(TALLOC_CFLAGS)
+AM_CFLAGS = -Wall $(TALLOC_CFLAGS) $(PTHREAD_CFLAGS) $(LIBSCTP_CFLAGS)
if ENABLE_PSEUDOTALLOC
AM_CPPFLAGS += -I$(top_srcdir)/src/pseudotalloc
@@ -12,8 +12,8 @@ endif
lib_LTLIBRARIES = libosmocore.la
-libosmocore_la_LIBADD = $(BACKTRACE_LIB) $(TALLOC_LIBS) $(LIBRARY_RT)
-libosmocore_la_SOURCES = timer.c timer_gettimeofday.c timer_clockgettime.c \
+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 \
@@ -27,6 +27,7 @@ libosmocore_la_SOURCES = timer.c timer_gettimeofday.c timer_clockgettime.c \
tdef.c \
sockaddr_str.c \
use_count.c \
+ exec.c \
$(NULL)
if HAVE_SSSE3
@@ -47,8 +48,13 @@ 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
+EXTRA_DIST = conv_acc_sse_impl.h conv_acc_neon_impl.h crcXXgen.c.tpl
libosmocore_la_LDFLAGS = -version-info $(LIBVERSION) -no-undefined
@@ -65,5 +71,10 @@ 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' $< > $@
diff --git a/src/bits.c b/src/bits.c
index 8837c1fb..aa117531 100644
--- a/src/bits.c
+++ b/src/bits.c
@@ -226,6 +226,35 @@ int osmo_pbit2ubit_ext(ubit_t *out, unsigned int out_ofs,
return out_ofs + num_bits;
}
+/* look-up table for bit-reversal within a byte. Generated using:
+ int i,k;
+ for (i = 0 ; i < 256 ; i++) {
+ uint8_t sample = 0 ;
+ for (k = 0; k<8; k++) {
+ if ( i & 1 << k ) sample |= 0x80 >> k;
+ }
+ flip_table[i] = sample;
+ }
+ */
+static const uint8_t flip_table[256] = {
+ 0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0, 0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0,
+ 0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8, 0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8,
+ 0x04, 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4, 0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4,
+ 0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec, 0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc,
+ 0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2, 0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2,
+ 0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea, 0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa,
+ 0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6, 0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6,
+ 0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee, 0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe,
+ 0x01, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1, 0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1,
+ 0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9, 0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9,
+ 0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5, 0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5,
+ 0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed, 0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd,
+ 0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3, 0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3,
+ 0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb, 0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb,
+ 0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7, 0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7,
+ 0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef, 0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff,
+};
+
/*! generalized bit reversal function
* \param[in] x the 32bit value to be reversed
* \param[in] k the type of reversal requested
@@ -265,16 +294,10 @@ uint32_t osmo_revbytebits_32(uint32_t x)
/*! reverse the bit order in a byte
* \param[in] x 8bit input value
* \returns 8bit value where bits order has been reversed
- *
- * See Chapter 7 "Hackers Delight"
*/
uint32_t osmo_revbytebits_8(uint8_t x)
{
- x = (x & 0x55) << 1 | (x & 0xAA) >> 1;
- x = (x & 0x33) << 2 | (x & 0xCC) >> 2;
- x = (x & 0x0F) << 4 | (x & 0xF0) >> 4;
-
- return x;
+ return flip_table[x];
}
/*! reverse bit-order of each byte in a buffer
@@ -286,26 +309,9 @@ uint32_t osmo_revbytebits_8(uint8_t x)
void osmo_revbytebits_buf(uint8_t *buf, int len)
{
unsigned int i;
- unsigned int unaligned_cnt;
- int len_remain = len;
- unaligned_cnt = ((unsigned long)buf & 3);
- for (i = 0; i < unaligned_cnt; i++) {
- buf[i] = osmo_revbytebits_8(buf[i]);
- len_remain--;
- if (len_remain <= 0)
- return;
- }
-
- for (i = unaligned_cnt; i + 3 < len; i += 4) {
- osmo_store32be(osmo_revbytebits_32(osmo_load32be(buf + i)), buf + i);
- len_remain -= 4;
- }
-
- for (i = len - len_remain; i < len; i++) {
- buf[i] = osmo_revbytebits_8(buf[i]);
- len_remain--;
- }
+ for (i = 0; i < len; i++)
+ buf[i] = flip_table[buf[i]];
}
/*! @} */
diff --git a/src/bitvec.c b/src/bitvec.c
index 0c263ad6..d7f32fbd 100644
--- a/src/bitvec.c
+++ b/src/bitvec.c
@@ -45,6 +45,7 @@
#include <osmocom/core/bits.h>
#include <osmocom/core/bitvec.h>
#include <osmocom/core/panic.h>
+#include <osmocom/core/utils.h>
#define BITNUM_FROM_COMP(byte, bit) ((byte*8)+bit)
@@ -291,7 +292,7 @@ int bitvec_fill(struct bitvec *bv, unsigned int num_bits, enum bit_value fill)
return 0;
}
-/*! pad all remaining bits up to num_bits
+/*! pad all remaining bits up to a given bit number
* \return 0 on success; negative otherwise */
int bitvec_spare_padding(struct bitvec *bv, unsigned int up_to_bit)
{
@@ -399,7 +400,7 @@ int bitvec_set_bytes(struct bitvec *bv, const uint8_t *bytes, unsigned int count
* \return pointer to allocated vector; NULL in case of error */
struct bitvec *bitvec_alloc(unsigned int size, TALLOC_CTX *ctx)
{
- struct bitvec *bv = talloc_zero(ctx, struct bitvec);
+ struct bitvec *bv = talloc(ctx, struct bitvec);
if (!bv)
return NULL;
@@ -418,6 +419,8 @@ struct bitvec *bitvec_alloc(unsigned int size, TALLOC_CTX *ctx)
* \param[in] bit vector to free */
void bitvec_free(struct bitvec *bv)
{
+ if (bv == NULL)
+ return;
talloc_free(bv->data);
talloc_free(bv);
}
@@ -428,7 +431,7 @@ void bitvec_free(struct bitvec *bv)
* \return number of bytes (= bits) copied */
unsigned int bitvec_pack(const struct bitvec *bv, uint8_t *buffer)
{
- unsigned int i = 0;
+ unsigned int i;
for (i = 0; i < bv->data_len; i++)
buffer[i] = bv->data[i];
@@ -441,7 +444,7 @@ unsigned int bitvec_pack(const struct bitvec *bv, uint8_t *buffer)
* \return number of bytes (= bits) copied */
unsigned int bitvec_unpack(struct bitvec *bv, const uint8_t *buffer)
{
- unsigned int i = 0;
+ unsigned int i;
for (i = 0; i < bv->data_len; i++)
bv->data[i] = buffer[i];
@@ -455,17 +458,13 @@ unsigned int bitvec_unpack(struct bitvec *bv, const uint8_t *buffer)
*/
int bitvec_unhex(struct bitvec *bv, const char *src)
{
- unsigned i;
- unsigned val;
- unsigned write_index = 0;
- unsigned digits = bv->data_len * 2;
+ int rc;
- for (i = 0; i < digits; i++) {
- if (sscanf(src + i, "%1x", &val) < 1) {
- return 1;
- }
- bitvec_write_field(bv, &write_index, val, 4);
- }
+ rc = osmo_hexparse(src, bv->data, bv->data_len);
+ if (rc < 0) /* turn -1 into 1 in case of error */
+ return 1;
+
+ bv->cur_bit = rc * 8;
return 0;
}
@@ -497,7 +496,7 @@ uint64_t bitvec_read_field(struct bitvec *bv, unsigned int *read_index, unsigned
* \param[in] bv The boolean vector to work on
* \param[in,out] write_index Where writing supposed to start in the vector
* \param[in] len How many bits to write
- * \returns next write index or negative value on error
+ * \returns 0 on success, negative value on error
*/
int bitvec_write_field(struct bitvec *bv, unsigned int *write_index, uint64_t val, unsigned int len)
{
diff --git a/src/codec/Makefile.am b/src/codec/Makefile.am
index b522d43a..778eb2ad 100644
--- a/src/codec/Makefile.am
+++ b/src/codec/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=1:1:1
+LIBVERSION=2:0:2
-AM_CPPFLAGS = -I$(top_srcdir)/include $(TALLOC_CFLAGS)
+AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_builddir)/include $(TALLOC_CFLAGS)
AM_CFLAGS = -Wall
if ENABLE_PSEUDOTALLOC
@@ -13,6 +13,6 @@ endif
lib_LTLIBRARIES = libosmocodec.la
-libosmocodec_la_SOURCES = gsm610.c gsm620.c gsm660.c gsm690.c ecu_fr.c
+libosmocodec_la_SOURCES = gsm610.c gsm620.c gsm660.c gsm690.c ecu.c ecu_fr.c
libosmocodec_la_LDFLAGS = -version-info $(LIBVERSION) -no-undefined
libosmocodec_la_LIBADD = $(top_builddir)/src/libosmocore.la
diff --git a/src/codec/ecu.c b/src/codec/ecu.c
new file mode 100644
index 00000000..db7148ce
--- /dev/null
+++ b/src/codec/ecu.c
@@ -0,0 +1,118 @@
+/* Core infrastructure for ECU implementations */
+
+/* (C) 2019 by Harald Welte <laforge@gnumonks.org>
+ *
+ * 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.
+ *
+ * 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
+ * state that any ECU implementation using 'struct osmo_ecu_ops' and
+ * registering with the 'osmo_ecu_register()' function shall not be
+ * considered as a derivative work under any applicable copyright law;
+ * the copyleft terms of GPLv2 shall hence not apply to any such ECU
+ * implementation.
+ *
+ * The intent of the above exception is to allow anyone to combine third
+ * party Error Concealment Unit implementations with libosmocodec.
+ * including but not limited to such published by ETSI.
+ *
+ * -- Harald Welte <laforge@gnumonks.org> on August 1, 2019.
+ */
+
+#include <string.h>
+#include <errno.h>
+
+#include <osmocom/codec/ecu.h>
+#include <osmocom/core/talloc.h>
+
+static const struct osmo_ecu_ops *g_ecu_ops[_NUM_OSMO_ECU_CODECS];
+
+/***********************************************************************
+ * high-level API for users
+ ***********************************************************************/
+
+/*! 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 */
+struct osmo_ecu_state *osmo_ecu_init(void *ctx, enum osmo_ecu_codec codec)
+{
+ if (codec >= ARRAY_SIZE(g_ecu_ops))
+ return NULL;
+ if (!g_ecu_ops[codec] || !g_ecu_ops[codec]->init)
+ return NULL;
+ return g_ecu_ops[codec]->init(ctx, codec);
+}
+
+/*! destroy an ECU instance */
+void osmo_ecu_destroy(struct osmo_ecu_state *st)
+{
+ if (st->codec >= ARRAY_SIZE(g_ecu_ops))
+ return;
+ if (!g_ecu_ops[st->codec])
+ return;
+
+ if (!g_ecu_ops[st->codec]->destroy)
+ talloc_free(st);
+ else
+ g_ecu_ops[st->codec]->destroy(st);
+}
+
+/*! process a received frame a substitute/erroneous frame.
+ * \param[in] st ECU state/instance on which to operate
+ * \param[in] bfi Bad Frame Indication
+ * \param[in] frame received codec frame to be processed
+ * \param[in] frame_bytes number of bytes available in frame */
+int osmo_ecu_frame_in(struct osmo_ecu_state *st, bool bfi,
+ const uint8_t *frame, unsigned int frame_bytes)
+{
+ if (st->codec >= ARRAY_SIZE(g_ecu_ops))
+ return -EINVAL;
+ if (!g_ecu_ops[st->codec])
+ return -EBUSY;
+ return g_ecu_ops[st->codec]->frame_in(st, bfi, frame, frame_bytes);
+}
+
+/*! generate output data for a substitute/erroneous frame.
+ * \param[in] st ECU state/instance on which to operate
+ * \param[out] frame_out buffer for generated output frame
+ * \return number of bytes written to frame_out; negative on error */
+int osmo_ecu_frame_out(struct osmo_ecu_state *st, uint8_t *frame_out)
+{
+ if (st->codec >= ARRAY_SIZE(g_ecu_ops))
+ return -EINVAL;
+ if (!g_ecu_ops[st->codec])
+ return -EBUSY;
+ return g_ecu_ops[st->codec]->frame_out(st, frame_out);
+}
+
+/***********************************************************************
+ * low-level API for ECU implementations
+ ***********************************************************************/
+
+/*! register an ECU implementation for a given codec */
+int osmo_ecu_register(const struct osmo_ecu_ops *ops, enum osmo_ecu_codec codec)
+{
+ if (codec >= ARRAY_SIZE(g_ecu_ops))
+ return -EINVAL;
+ if (g_ecu_ops[codec])
+ return -EBUSY;
+
+ g_ecu_ops[codec] = ops;
+
+ return 0;
+}
diff --git a/src/codec/ecu_fr.c b/src/codec/ecu_fr.c
index ef42ea9f..4545172a 100644
--- a/src/codec/ecu_fr.c
+++ b/src/codec/ecu_fr.c
@@ -164,3 +164,54 @@ int osmo_ecu_fr_conceal(struct osmo_ecu_fr_state *state, uint8_t *frame)
return 0;
}
+
+/***********************************************************************
+ * Integration with ECU core
+ ***********************************************************************/
+
+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);
+
+ st = talloc_named_const(ctx, size, "ecu_state_FR");
+ if (!st)
+ return NULL;
+
+ memset(st, 0, size);
+ st->codec = codec;
+
+ return st;
+}
+
+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;
+ if (bfi)
+ return 0;
+
+ osmo_ecu_fr_reset(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;
+
+ if (osmo_ecu_fr_conceal(fr, frame_out) == 0)
+ return GSM_FR_BYTES;
+ else
+ return -1;
+}
+
+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,
+};
+
+static __attribute__((constructor)) void on_dso_load_ecu_fr(void)
+{
+ osmo_ecu_register(&osmo_ecu_ops_fr, OSMO_ECU_CODEC_FR);
+}
diff --git a/src/codec/gsm690.c b/src/codec/gsm690.c
index 19557164..cc6cdf0c 100644
--- a/src/codec/gsm690.c
+++ b/src/codec/gsm690.c
@@ -2,6 +2,7 @@
* GSM 06.90 - GSM AMR Codec. */
/*
* (C) 2010 Sylvain Munaut <tnt@246tNt.com>
+ * (C) 2020 Harald Welte <laforge@gnumonks.org>
*
* All Rights Reserved
*
@@ -29,6 +30,7 @@
#include <stdlib.h>
#include <osmocom/core/utils.h>
+#include <osmocom/core/bits.h>
#include <osmocom/codec/codec.h>
/*
* These table map between the raw encoder parameter output and
@@ -216,8 +218,117 @@ const uint16_t gsm690_4_75_bitorder[95] = {
92, 31, 52, 65, 86,
};
+/*! These constants refer to the length of one "AMR core frame" as per
+ * TS 26.101 Section 4.2.2 / Table 2. */
+const uint8_t gsm690_bitlength[AMR_NO_DATA+1] = {
+ [AMR_4_75] = 95,
+ [AMR_5_15] = 103,
+ [AMR_5_90] = 118,
+ [AMR_6_70] = 134,
+ [AMR_7_40] = 148,
+ [AMR_7_95] = 159,
+ [AMR_10_2] = 204,
+ [AMR_12_2] = 244,
+ [AMR_SID] = 39,
+};
+
+struct ts26101_reorder_table {
+ /*! Table as per TS 26.101 Annex B to compute d-bits from s-bits */
+ const uint16_t *s_to_d;
+ /*! size of table */
+ uint8_t len;
+};
+
+static const struct ts26101_reorder_table ts26101_reorder_tables[8] = {
+ [AMR_4_75] = {
+ .s_to_d = gsm690_4_75_bitorder,
+ .len = ARRAY_SIZE(gsm690_4_75_bitorder),
+ },
+ [AMR_5_15] = {
+ .s_to_d = gsm690_5_15_bitorder,
+ .len = ARRAY_SIZE(gsm690_5_15_bitorder),
+ },
+ [AMR_5_90] = {
+ .s_to_d = gsm690_5_9_bitorder,
+ .len = ARRAY_SIZE(gsm690_5_9_bitorder),
+ },
+ [AMR_6_70] = {
+ .s_to_d = gsm690_6_7_bitorder,
+ .len = ARRAY_SIZE(gsm690_6_7_bitorder),
+ },
+ [AMR_7_40] = {
+ .s_to_d = gsm690_7_4_bitorder,
+ .len = ARRAY_SIZE(gsm690_7_4_bitorder),
+ },
+ [AMR_7_95] = {
+ .s_to_d = gsm690_7_95_bitorder,
+ .len = ARRAY_SIZE(gsm690_7_95_bitorder),
+ },
+ [AMR_10_2] = {
+ .s_to_d = gsm690_10_2_bitorder,
+ .len = ARRAY_SIZE(gsm690_10_2_bitorder),
+ },
+ [AMR_12_2] = {
+ .s_to_d = gsm690_12_2_bitorder,
+ .len = ARRAY_SIZE(gsm690_12_2_bitorder),
+ },
+};
+
+/*! Convert from S-bits (codec output) to d-bits.
+ * \param[out] out user-provided output buffer for generated unpacked d-bits
+ * \param[in] in input buffer for unpacked s-bits
+ * \param[in] n_bits number of bits (in both in and out)
+ * \param[in] AMR mode (0..7) */
+int osmo_amr_s_to_d(ubit_t *out, const ubit_t *in, uint16_t n_bits, enum osmo_amr_type amr_mode)
+{
+ const struct ts26101_reorder_table *tbl;
+ int i;
+
+ if (amr_mode >= ARRAY_SIZE(ts26101_reorder_tables))
+ return -ENODEV;
+
+ tbl = &ts26101_reorder_tables[amr_mode];
+
+ if (n_bits > tbl->len)
+ return -EINVAL;
+
+ for (i = 0; i < n_bits; i++) {
+ uint16_t n = tbl->s_to_d[i];
+ out[i] = in[n];
+ }
+
+ return n_bits;
+}
+
+/*! Convert from d-bits to s-bits (codec input).
+ * \param[out] out user-provided output buffer for generated unpacked s-bits
+ * \param[in] in input buffer for unpacked d-bits
+ * \param[in] n_bits number of bits (in both in and out)
+ * \param[in] AMR mode (0..7) */
+int osmo_amr_d_to_s(ubit_t *out, const ubit_t *in, uint16_t n_bits, enum osmo_amr_type amr_mode)
+{
+ const struct ts26101_reorder_table *tbl;
+ int i;
+
+ if (amr_mode >= ARRAY_SIZE(ts26101_reorder_tables))
+ return -ENODEV;
+
+ tbl = &ts26101_reorder_tables[amr_mode];
+
+ if (n_bits > tbl->len)
+ return -EINVAL;
+
+ for (i = 0; i < n_bits; i++) {
+ uint16_t n = tbl->s_to_d[i];
+ out[n] = in[i];
+ }
+
+ return n_bits;
+}
+
+/* See also RFC 4867 §3.6, Table 1, Column "Total speech bits" */
static const uint8_t amr_len_by_ft[16] = {
- 12, 13, 15, 17, 19, 20, 26, 31, 7, 0, 0, 0, 0, 0, 0, 0
+ 12, 13, 15, 17, 19, 20, 26, 31, 5, 0, 0, 0, 0, 0, 0, 0
};
const struct value_string osmo_amr_type_names[] = {
diff --git a/src/coding/Makefile.am b/src/coding/Makefile.am
index f47fe457..b023668e 100644
--- a/src/coding/Makefile.am
+++ b/src/coding/Makefile.am
@@ -20,7 +20,8 @@ libosmocoding_la_SOURCES = \
gsm0503_mapping.c \
gsm0503_tables.c \
gsm0503_parity.c \
- gsm0503_coding.c
+ gsm0503_coding.c \
+ gsm0503_amr_dtx.c
libosmocoding_la_LDFLAGS = \
$(LTLDFLAGS_OSMOCODING) \
-version-info \
diff --git a/src/coding/gsm0503_amr_dtx.c b/src/coding/gsm0503_amr_dtx.c
new file mode 100644
index 00000000..7069b967
--- /dev/null
+++ b/src/coding/gsm0503_amr_dtx.c
@@ -0,0 +1,316 @@
+/*
+ * (C) 2020 by sysmocom - s.f.m.c. GmbH, Author: Philipp Maier
+ * 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 <stdio.h>
+#include <stdint.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#include <osmocom/core/bits.h>
+#include <osmocom/core/conv.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/coding/gsm0503_amr_dtx.h>
+#include <osmocom/coding/gsm0503_parity.h>
+#include <osmocom/gsm/gsm0503.h>
+
+/* 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 };
+
+/* See also: 3GPP TS 05.03, chapter 3.9.1.3, 3.10.2.2, 3.10.2.2 Identification marker */
+static const ubit_t id_marker_0[] = { 0, 1, 0, 0, 1, 1, 1, 1, 0 };
+
+/* See also: 3GPP TS 05.03, chapter 3.9 Adaptive multi rate speech channel at full rate (TCH/AFS) */
+static const ubit_t codec_mode_1_sid[] = { 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0 };
+static const ubit_t codec_mode_2_sid[] = { 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0 };
+static const ubit_t codec_mode_3_sid[] = { 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1 };
+static const ubit_t codec_mode_4_sid[] = { 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1 };
+
+const struct value_string gsm0503_amr_dtx_frame_names[] = {
+ { AMR_OTHER, "AMR_OTHER (audio)" },
+ { AFS_SID_FIRST, "AFS_SID_FIRST" },
+ { AFS_SID_UPDATE, "AFS_SID_UPDATE (marker)" },
+ { AFS_SID_UPDATE_CN, "AFS_SID_UPDATE_CN (audio)" },
+ { AFS_ONSET, "AFS_ONSET" },
+ { AHS_SID_UPDATE, "AHS_SID_UPDATE (marker)" },
+ { AHS_SID_UPDATE_CN, "AHS_SID_UPDATE_CN (audio)" },
+ { AHS_SID_FIRST_P1, "AHS_SID_FIRST_P1" },
+ { AHS_SID_FIRST_P2, "AHS_SID_FIRST_P2" },
+ { AHS_ONSET, "AHS_ONSET" },
+ { AHS_SID_FIRST_INH, "AHS_SID_FIRST_INH" },
+ { AHS_SID_UPDATE_INH, "AHS_SID_UPDATE_INH" },
+ { 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,
+ const ubit_t * id_marker, uint8_t id_marker_len)
+{
+ unsigned int i, k;
+ unsigned int id_bit_nr = 0;
+ int errors = 0;
+ int bits = 0;
+
+ /* Override coded in-band data */
+ ubits += 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)
+ errors++;
+ id_bit_nr++;
+ ubits++;
+ bits++;
+ }
+
+ /* Jump to the next block of 4 bits */
+ ubits += 4;
+ }
+
+ *n_errors = errors;
+ *n_bits_total = bits;
+
+ /* Tolerate up to 1/8 errornous bits */
+ 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)
+{
+ unsigned int i, k;
+ int errors = 0;
+ int bits = 0;
+
+ /* Override coded in-band data */
+ ubits += 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)
+ errors++;
+ ubits++;
+ bits++;
+ }
+ }
+
+ /* Check remaining identification marker bits (5 bits) */
+ for (k = 0; k < 5; k++) {
+ if (id_marker[k] != *ubits)
+ errors++;
+ ubits++;
+ bits++;
+ }
+
+ *n_errors = errors;
+ *n_bits_total = bits;
+
+ /* Tolerate up to 1/8 errornous bits */
+ 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,
+ uint8_t n_bits, const ubit_t * id_marker, uint8_t id_marker_len)
+{
+ unsigned int i, k;
+ int errors = 0;
+ int bits = 0;
+ uint8_t full_rounds = n_bits / id_marker_len;
+ uint8_t remainder = n_bits % id_marker_len;
+
+ /* Override coded in-band data */
+ ubits += 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)
+ errors++;
+ ubits += 2;
+ bits++;
+ }
+ }
+
+ /* Check remaining identification marker bits (5 bits) */
+ for (k = 0; k < remainder; k++) {
+ if (id_marker[k] != *ubits)
+ errors++;
+ ubits += 2;
+ bits++;
+ }
+
+ *n_errors = errors;
+ *n_bits_total = bits;
+
+ /* Tolerate up to 1/8 errornous bits */
+ 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)
+{
+ return detect_afs_id_marker(n_errors, n_bits_total, ubits, 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)
+{
+ return detect_afs_id_marker(n_errors, n_bits_total, ubits, 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)
+{
+ bool rc;
+
+ rc = detect_afs_id_marker(n_errors, n_bits_total, ubits, 4, 57, codec_mode_1_sid, 16);
+ if (rc)
+ return true;
+
+ rc = detect_afs_id_marker(n_errors, n_bits_total, ubits, 4, 57, codec_mode_2_sid, 16);
+ if (rc)
+ return true;
+
+ rc = detect_afs_id_marker(n_errors, n_bits_total, ubits, 4, 57, codec_mode_3_sid, 16);
+ if (rc)
+ return true;
+
+ rc = detect_afs_id_marker(n_errors, n_bits_total, ubits, 4, 57, codec_mode_4_sid, 16);
+ if (rc)
+ return true;
+
+ return false;
+}
+
+/* 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)
+{
+ return detect_ahs_id_marker(n_errors, n_bits_total, ubits, 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)
+{
+ return detect_ahs_id_marker(n_errors, n_bits_total, ubits, 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)
+{
+ bool rc;
+
+ rc = detect_interleaved_ahs_id_marker(n_errors, n_bits_total, ubits, 0, 114, codec_mode_1_sid, 16);
+ if (rc)
+ return true;
+
+ rc = detect_interleaved_ahs_id_marker(n_errors, n_bits_total, ubits, 0, 114, codec_mode_2_sid, 16);
+ if (rc)
+ return true;
+
+ rc = detect_interleaved_ahs_id_marker(n_errors, n_bits_total, ubits, 0, 114, codec_mode_3_sid, 16);
+ if (rc)
+ return true;
+
+ rc = detect_interleaved_ahs_id_marker(n_errors, n_bits_total, ubits, 0, 114, codec_mode_4_sid, 16);
+ if (rc)
+ return true;
+
+ return false;
+}
+
+/* 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)
+{
+ bool rc;
+
+ rc = detect_interleaved_ahs_id_marker(n_errors, n_bits_total, ubits, 1, 114, codec_mode_1_sid, 16);
+ if (rc)
+ return true;
+
+ rc = detect_interleaved_ahs_id_marker(n_errors, n_bits_total, ubits, 1, 114, codec_mode_2_sid, 16);
+ if (rc)
+ return true;
+
+ rc = detect_interleaved_ahs_id_marker(n_errors, n_bits_total, ubits, 1, 114, codec_mode_3_sid, 16);
+ if (rc)
+ return true;
+
+ rc = detect_interleaved_ahs_id_marker(n_errors, n_bits_total, ubits, 1, 114, codec_mode_4_sid, 16);
+ if (rc)
+ return true;
+
+ return false;
+}
+
+/* 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)
+{
+ return detect_interleaved_ahs_id_marker(n_errors, n_bits_total, ubits, 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)
+{
+ return detect_interleaved_ahs_id_marker(n_errors, n_bits_total, ubits, 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.
+ * \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)
+{
+ if (detect_afs_sid_first(n_errors, n_bits_total, ubits))
+ return AFS_SID_FIRST;
+ if (detect_afs_sid_update(n_errors, n_bits_total, ubits))
+ return AFS_SID_UPDATE;
+ if (detect_afs_onset(n_errors, n_bits_total, ubits))
+ return AFS_ONSET;
+
+ *n_errors = 0;
+ *n_bits_total = 0;
+ return AMR_OTHER;
+}
+
+/*! Detect HR 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.
+ * \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)
+{
+ if (detect_ahs_sid_update(n_errors, n_bits_total, ubits))
+ return AHS_SID_UPDATE;
+ if (detect_ahs_sid_first_inh(n_errors, n_bits_total, ubits))
+ return AHS_SID_FIRST_INH;
+ if (detect_ahs_sid_update_inh(n_errors, n_bits_total, ubits))
+ return AHS_SID_UPDATE_INH;
+ if (detect_ahs_sid_first_p1(n_errors, n_bits_total, ubits))
+ return AHS_SID_FIRST_P1;
+ if (detect_ahs_sid_first_p2(n_errors, n_bits_total, ubits))
+ return AHS_SID_FIRST_P2;
+ if (detect_ahs_onset(n_errors, n_bits_total, ubits))
+ return AHS_ONSET;
+
+ *n_errors = 0;
+ *n_bits_total = 0;
+ return AMR_OTHER;
+}
diff --git a/src/coding/gsm0503_coding.c b/src/coding/gsm0503_coding.c
index 7385d233..1bec56ea 100644
--- a/src/coding/gsm0503_coding.c
+++ b/src/coding/gsm0503_coding.c
@@ -47,6 +47,7 @@
#include <osmocom/coding/gsm0503_tables.h>
#include <osmocom/coding/gsm0503_coding.h>
#include <osmocom/coding/gsm0503_parity.h>
+#include <osmocom/coding/gsm0503_amr_dtx.h>
/*! \mainpage libosmocoding Documentation
*
@@ -1168,7 +1169,7 @@ int gsm0503_pdtch_decode(uint8_t *l2_data, const sbit_t *bursts, uint8_t *usf_p,
}
/*
- * EGPRS PDTCH UL block encoding
+ * EGPRS PDTCH DL block encoding
*/
static int egprs_type3_map(ubit_t *bursts, const ubit_t *hc, const ubit_t *dc, int usf)
{
@@ -1176,7 +1177,7 @@ static int egprs_type3_map(ubit_t *bursts, const ubit_t *hc, const ubit_t *dc, i
ubit_t iB[456];
const ubit_t *hl_hn = gsm0503_pdtch_hl_hn_ubit[3];
- gsm0503_mcs1_dl_interleave(gsm0503_usf2six[usf], hc, dc, iB);
+ gsm0503_mcs1_dl_interleave(gsm0503_usf2twelve_ubit[usf], hc, dc, iB);
for (i = 0; i < 4; i++) {
gsm0503_xcch_burst_map(&iB[i * 114], &bursts[i * 116],
@@ -1332,7 +1333,7 @@ static int egprs_parse_dl_cps(struct egprs_cps *cps,
* \param[out] bursts caller-allocated buffer for unpacked burst bits
* \param[in] l2_data L2 (MAC) block to be encoded
* \param[in] l2_len length of l2_data in bytes, used to determine MCS
- * \returns 0 on success; negative on error */
+ * \returns number of bits encoded; negative on error */
int gsm0503_pdtch_egprs_encode(ubit_t *bursts,
const uint8_t *l2_data, uint8_t l2_len)
{
@@ -1427,7 +1428,7 @@ bad_header:
* \param[out] bursts caller-allocated buffer for unpacked burst bits
* \param[in] l2_data L2 (MAC) block to be encoded
* \param[in] l2_len length of l2_data in bytes, used to determine CS
- * \returns 0 on success; negative on error */
+ * \returns number of bits encoded; negative on error */
int gsm0503_pdtch_encode(ubit_t *bursts, const uint8_t *l2_data, uint8_t l2_len)
{
ubit_t iB[456], cB[676];
@@ -1635,6 +1636,39 @@ static void tch_amr_disassemble(ubit_t *d_bits, const uint8_t *tch_data, int len
d_bits[i] = (tch_data[j >> 3] >> (7 - (j & 7))) & 1;
}
+/* Append STI and MI bits to the SID_UPDATE frame, see also
+ * 3GPP TS 26.101, chapter 4.2.3 AMR Core Frame with comfort noise bits */
+static void tch_amr_sid_update_append(ubit_t *sid_update, uint8_t sti, uint8_t mi)
+{
+ /* Zero out the space that had been used by the CRC14 */
+ memset(sid_update + 35, 0, 14);
+
+ /* Append STI and MI parameters */
+ sid_update[35] = sti & 1;
+ sid_update[36] = mi & 1;
+ sid_update[37] = mi >> 1 & 1;
+ sid_update[38] = mi >> 2 & 1;
+}
+
+/* Extract a SID UPDATE fram the sbits of an FR AMR frame */
+static void extract_afs_sid_update(sbit_t *sid_update, const sbit_t *sbits)
+{
+
+ unsigned int i;
+
+ sbits += 32;
+
+ for (i = 0; i < 53; i++) {
+ sid_update[0] = sbits[0];
+ sid_update[1] = sbits[1];
+ sid_update[2] = sbits[2];
+ sid_update[3] = sbits[3];
+ sid_update += 4;
+ sbits += 8;
+ }
+
+}
+
/* re-arrange according to TS 05.03 Table 2 (receiver) */
static void tch_fr_d_to_b(ubit_t *b_bits, const ubit_t *d_bits)
{
@@ -2101,10 +2135,37 @@ int gsm0503_tch_afs_decode(uint8_t *tch_data, const sbit_t *bursts,
int codec_mode_req, uint8_t *codec, int codecs, uint8_t *ft,
uint8_t *cmr, int *n_errors, int *n_bits_total)
{
+ return gsm0503_tch_afs_decode_dtx(tch_data, bursts, codec_mode_req,
+ codec, codecs, ft, cmr, n_errors,
+ n_bits_total, NULL);
+}
+
+/*! 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
+ * \param[in] codec_mode_req is this CMR (1) or CMC (0)
+ * \param[in] codec array of active codecs (active codec set)
+ * \param[in] codecs number of codecs in \a codec
+ * \param ft Frame Type; Input if \a codec_mode_req = 1, Output * otherwise
+ * \param[out] cmr Output in \a codec_mode_req = 1
+ * \param[out] n_errors Number of detected bit errors
+ * \param[out] n_bits_total Total number of bits
+ * \param[inout] dtx DTX frame type output, previous DTX frame type input
+ * \returns (>=4) length of bytes used in \a tch_data output buffer; ([0,3])
+ * codec out of range; negative on error
+ */
+int gsm0503_tch_afs_decode_dtx(uint8_t *tch_data, const sbit_t *bursts,
+ int codec_mode_req, uint8_t *codec, int codecs, uint8_t *ft,
+ uint8_t *cmr, int *n_errors, int *n_bits_total, uint8_t *dtx)
+{
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];
*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);
@@ -2123,6 +2184,50 @@ int gsm0503_tch_afs_decode(uint8_t *tch_data, const sbit_t *bursts,
return GSM_MACBLOCK_LEN;
}
+ /* 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);
+
+ if (dtx_prev == AFS_SID_UPDATE && *dtx == 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). */
+ *dtx = AFS_SID_UPDATE_CN;
+
+ extract_afs_sid_update(sid_update_enc, cB);
+ osmo_conv_decode_ber(&gsm0503_tch_axs_sid_update,
+ sid_update_enc, conv, n_errors,
+ n_bits_total);
+ rv = osmo_crc16gen_check_bits(&gsm0503_amr_crc14, conv,
+ 35, conv + 35);
+ if (rv != 0) {
+ /* Error checking CRC14 for an AMR SID_UPDATE frame */
+ return -1;
+ }
+
+ tch_amr_sid_update_append(conv, 1,
+ (codec_mode_req) ? codec[*ft]
+ : codec[id]);
+ tch_amr_reassemble(tch_data, conv, 39);
+ len = 5;
+ goto out;
+ } else if (*dtx == AFS_SID_FIRST) {
+ tch_amr_sid_update_append(sid_first_dummy, 0,
+ (codec_mode_req) ? codec[*ft]
+ : codec[id]);
+ tch_amr_reassemble(tch_data, conv, 39);
+ len = 5;
+ goto out;
+ } else if (*dtx == AFS_ONSET) {
+ len = 0;
+ goto out;
+ }
+ }
+
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]));
@@ -2283,6 +2388,7 @@ int gsm0503_tch_afs_decode(uint8_t *tch_data, const sbit_t *bursts,
return -1;
}
+out:
/* Change codec request / indication, if frame is valid */
if (codec_mode_req)
*cmr = id;
@@ -2480,9 +2586,36 @@ int gsm0503_tch_ahs_decode(uint8_t *tch_data, const sbit_t *bursts, int odd,
int codec_mode_req, uint8_t *codec, int codecs, uint8_t *ft,
uint8_t *cmr, int *n_errors, int *n_bits_total)
{
+ return gsm0503_tch_ahs_decode_dtx(tch_data, bursts, odd, codec_mode_req,
+ codec, codecs, ft, cmr, n_errors,
+ n_bits_total, NULL);
+}
+
+/*! 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
+ * \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)
+ * \param[in] codecs number of codecs in \a codec
+ * \param ft Frame Type; Input if \a codec_mode_req = 1, Output * otherwise
+ * \param[out] cmr Output in \a codec_mode_req = 1
+ * \param[out] n_errors Number of detected bit errors
+ * \param[out] n_bits_total Total number of bits
+ * \param[inout] dtx DTX frame type output, previous DTX frame type input
+ * \returns (>=4) length of bytes used in \a tch_data output buffer; ([0,3])
+ * codec out of range; negative on error
+ */
+int gsm0503_tch_ahs_decode_dtx(uint8_t *tch_data, const sbit_t *bursts, int odd,
+ int codec_mode_req, uint8_t *codec, int codecs, uint8_t *ft,
+ uint8_t *cmr, int *n_errors, int *n_bits_total, uint8_t *dtx)
+{
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];
+ static ubit_t sid_first_dummy[64] = { 0 };
+ uint8_t dtx_prev;
/* only unmap the stealing bits */
if (!odd) {
@@ -2526,6 +2659,52 @@ int gsm0503_tch_ahs_decode(uint8_t *tch_data, const sbit_t *bursts, int odd,
gsm0503_tch_hr_deinterleave(cB, iB);
+ /* 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). */
+ *dtx = AHS_SID_UPDATE_CN;
+
+ osmo_conv_decode_ber(&gsm0503_tch_axs_sid_update,
+ cB + 16, conv, n_errors,
+ n_bits_total);
+ rv = osmo_crc16gen_check_bits(&gsm0503_amr_crc14, conv,
+ 35, conv + 35);
+ if (rv != 0) {
+ /* Error checking CRC14 for an AMR SID_UPDATE frame */
+ return -1;
+ }
+
+ tch_amr_sid_update_append(conv, 1,
+ (codec_mode_req) ? codec[*ft]
+ : codec[id]);
+ tch_amr_reassemble(tch_data, conv, 39);
+ len = 5;
+ goto out;
+ } else if (*dtx == AHS_SID_FIRST_P2) {
+ tch_amr_sid_update_append(sid_first_dummy, 0,
+ (codec_mode_req) ? codec[*ft]
+ : codec[id]);
+ 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) {
+ len = 0;
+ goto out;
+ }
+ }
+
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]));
@@ -2670,6 +2849,7 @@ int gsm0503_tch_ahs_decode(uint8_t *tch_data, const sbit_t *bursts, int odd,
return -1;
}
+out:
/* Change codec request / indication, if frame is valid */
if (codec_mode_req)
*cmr = id;
@@ -2879,7 +3059,7 @@ static inline int16_t rach_decode_ber(const sbit_t *burst, uint8_t bsic, bool is
osmo_ubit2pbit_ext(ra, 0, conv, 0, nbits, 1);
- return is_11bit ? osmo_load16le(ra) : ra[0];
+ return is_11bit ? ((ra[0] << 3) | (ra[1] & 0x07)) : ra[0];
}
/*! Decode the Extended (11-bit) RACH according to 3GPP TS 45.003
@@ -2974,7 +3154,8 @@ int gsm0503_rach_ext_encode(ubit_t *burst, uint16_t ra11, uint8_t bsic, bool is_
uint8_t ra[2] = { 0 }, nbits = 8;
if (is_11bit) {
- osmo_store16le(ra11, ra);
+ ra[0] = (uint8_t) (ra11 >> 3);
+ ra[1] = (uint8_t) (ra11 & 0x07);
nbits = 11;
} else
ra[0] = (uint8_t)ra11;
diff --git a/src/coding/gsm0503_parity.c b/src/coding/gsm0503_parity.c
index 874114ff..a8daacc7 100644
--- a/src/coding/gsm0503_parity.c
+++ b/src/coding/gsm0503_parity.c
@@ -134,4 +134,15 @@ const struct osmo_crc8gen_code gsm0503_amr_crc6 = {
.remainder = 0x3f,
};
+/*! GSM AMR parity (SID_UPDATE)
+ *
+ * g(x) = x^14 + x^13 + x^5 + x^3 + x^2 + 1
+ */
+const struct osmo_crc16gen_code gsm0503_amr_crc14 = {
+ .bits = 14,
+ .poly = 0x202d,
+ .init = 0x0000,
+ .remainder = 0x3fff,
+};
+
/*! @} */
diff --git a/src/coding/gsm0503_tables.c b/src/coding/gsm0503_tables.c
index 5fe634bf..df0abeed 100644
--- a/src/coding/gsm0503_tables.c
+++ b/src/coding/gsm0503_tables.c
@@ -63,6 +63,9 @@ const sbit_t gsm0503_pdtch_edge_hl_hn_sbit[3][8] = {
{ -127,-127, -127, 127, 127,-127, -127,-127 },
};
+/*
+ * 3GPP TS 05.03 sec 5.1.2.2 "Block code". Rows re-ordered to be indxed by USF in host bit order.
+ */
const ubit_t gsm0503_usf2six[8][6] = {
{ 0,0,0, 0,0,0 },
{ 1,0,0, 1,0,1 },
@@ -74,6 +77,9 @@ const ubit_t gsm0503_usf2six[8][6] = {
{ 1,1,1, 0,0,0 },
};
+/*
+ * 3GPP TS 05.03 sec 5.1.4.2 "Block code". Rows re-ordered to be indxed by USF in host bit order.
+ */
const ubit_t gsm0503_usf2twelve_ubit[8][12] = {
{ 0,0,0, 0,0,0, 0,0,0, 0,0,0 },
{ 1,1,0, 1,0,0, 0,0,1, 0,1,1 },
diff --git a/src/coding/libosmocoding.map b/src/coding/libosmocoding.map
index 87b38864..325b6d80 100644
--- a/src/coding/libosmocoding.map
+++ b/src/coding/libosmocoding.map
@@ -56,6 +56,7 @@ gsm0503_sch_crc10;
gsm0503_tch_fr_crc3;
gsm0503_tch_efr_crc8;
gsm0503_amr_crc6;
+gsm0503_amr_crc14;
gsm0503_xcch_burst_unmap;
gsm0503_xcch_burst_map;
@@ -106,8 +107,10 @@ gsm0503_tch_hr_encode;
gsm0503_tch_hr_decode;
gsm0503_tch_afs_encode;
gsm0503_tch_afs_decode;
+gsm0503_tch_afs_decode_dtx;
gsm0503_tch_ahs_encode;
gsm0503_tch_ahs_decode;
+gsm0503_tch_ahs_decode_dtx;
gsm0503_rach_ext_encode;
gsm0503_rach_ext_decode;
gsm0503_rach_ext_decode_ber;
@@ -116,6 +119,10 @@ gsm0503_rach_decode;
gsm0503_rach_decode_ber;
gsm0503_sch_encode;
gsm0503_sch_decode;
+gsm0503_amr_dtx_frame_names;
+gsm0503_amr_dtx_frame_name;
+gsm0503_detect_afs_dtx_frame;
+gsm0503_detect_ahs_dtx_frame;
local: *;
};
diff --git a/src/context.c b/src/context.c
new file mode 100644
index 00000000..bad012bd
--- /dev/null
+++ b/src/context.c
@@ -0,0 +1,52 @@
+/*! \file context.c
+ * talloc context handling.
+ *
+ * (C) 2019 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserverd.
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301, USA.
+ */
+#include <string.h>
+#include <errno.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+
+__thread struct osmo_talloc_contexts *osmo_ctx;
+
+int osmo_ctx_init(const char *id)
+{
+ osmo_ctx = talloc_named(NULL, sizeof(*osmo_ctx), "global-%s", id);
+ if (!osmo_ctx)
+ return -ENOMEM;
+ memset(osmo_ctx, 0, sizeof(*osmo_ctx));
+ osmo_ctx->global = osmo_ctx;
+ osmo_ctx->select = talloc_named_const(osmo_ctx->global, 0, "select");
+ if (!osmo_ctx->select) {
+ talloc_free(osmo_ctx);
+ return -ENOMEM;
+ }
+ return 0;
+}
+
+/* initialize osmo_ctx on main tread */
+static __attribute__((constructor)) void on_dso_load_ctx(void)
+{
+ OSMO_ASSERT(osmo_ctx_init("main") == 0);
+}
+
+/*! @} */
diff --git a/src/conv.c b/src/conv.c
index a2c13def..06c4299b 100644
--- a/src/conv.c
+++ b/src/conv.c
@@ -36,6 +36,7 @@
#include <stdlib.h>
#include <string.h>
+#include <osmocom/core/utils.h>
#include <osmocom/core/bits.h>
#include <osmocom/core/conv.h>
@@ -87,6 +88,7 @@ osmo_conv_encode_init(struct osmo_conv_encoder *encoder,
const struct osmo_conv_code *code)
{
memset(encoder, 0x00, sizeof(struct osmo_conv_encoder));
+ OSMO_ASSERT(code != NULL);
encoder->code = code;
}
diff --git a/src/conv_acc.c b/src/conv_acc.c
index c16e4364..0f6f7ca2 100644
--- a/src/conv_acc.c
+++ b/src/conv_acc.c
@@ -85,6 +85,11 @@ int16_t *osmo_conv_sse_avx_vdec_malloc(size_t n);
void osmo_conv_sse_avx_vdec_free(int16_t *ptr);
#endif
+#ifdef HAVE_NEON
+int16_t *osmo_conv_neon_vdec_malloc(size_t n);
+void osmo_conv_neon_vdec_free(int16_t *ptr);
+#endif
+
/* Forward Metric Units */
void osmo_conv_gen_metrics_k5_n2(const int8_t *seq, const int16_t *out,
int16_t *sums, int16_t *paths, int norm);
@@ -129,6 +134,21 @@ void osmo_conv_sse_avx_metrics_k7_n4(const int8_t *seq, const int16_t *out,
int16_t *sums, int16_t *paths, int norm);
#endif
+#if defined(HAVE_NEON)
+void osmo_conv_neon_metrics_k5_n2(const int8_t *seq, const int16_t *out,
+ int16_t *sums, int16_t *paths, int norm);
+void osmo_conv_neon_metrics_k5_n3(const int8_t *seq, const int16_t *out,
+ int16_t *sums, int16_t *paths, int norm);
+void osmo_conv_neon_metrics_k5_n4(const int8_t *seq, const int16_t *out,
+ int16_t *sums, int16_t *paths, int norm);
+void osmo_conv_neon_metrics_k7_n2(const int8_t *seq, const int16_t *out,
+ int16_t *sums, int16_t *paths, int norm);
+void osmo_conv_neon_metrics_k7_n3(const int8_t *seq, const int16_t *out,
+ int16_t *sums, int16_t *paths, int norm);
+void osmo_conv_neon_metrics_k7_n4(const int8_t *seq, const int16_t *out,
+ int16_t *sums, int16_t *paths, int norm);
+#endif
+
/* Trellis State
* state - Internal lshift register value
* prev - Register values of previous 0 and 1 states
@@ -528,6 +548,12 @@ static int vdec_init(struct vdecoder *dec, const struct osmo_conv_code *code)
if (dec->k == 5) {
switch (dec->n) {
case 2:
+/* rach len 14 is too short for neon */
+#ifdef HAVE_NEON
+ if (code->len < 100)
+ dec->metric_func = osmo_conv_gen_metrics_k5_n2;
+ else
+#endif
dec->metric_func = osmo_conv_metrics_k5_n2;
break;
case 3:
@@ -681,6 +707,8 @@ static void osmo_conv_init(void)
} else {
INIT_POINTERS(gen);
}
+#elif defined(HAVE_NEON)
+ INIT_POINTERS(neon);
#else
INIT_POINTERS(gen);
#endif
diff --git a/src/conv_acc_neon.c b/src/conv_acc_neon.c
new file mode 100644
index 00000000..72449468
--- /dev/null
+++ b/src/conv_acc_neon.c
@@ -0,0 +1,110 @@
+/*! \file conv_acc_neon.c
+ * Accelerated Viterbi decoder implementation
+ * for architectures with only NEON available. */
+/*
+ * (C) 2020 by sysmocom - s.f.m.c. GmbH
+ * Author: Eric Wild
+ *
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <malloc.h>
+#include "config.h"
+
+#if defined(HAVE_NEON)
+#include <arm_neon.h>
+#endif
+
+/* align req is 16 on android because google was confused, 8 on sane platforms */
+#define NEON_ALIGN 8
+
+#include <conv_acc_neon_impl.h>
+
+/* Aligned Memory Allocator
+ * NEON requires 8-byte memory alignment. We store relevant trellis values
+ * (accumulated sums, outputs, and path decisions) as 16 bit signed integers
+ * so the allocated memory is casted as such.
+ */
+__attribute__ ((visibility("hidden")))
+int16_t *osmo_conv_neon_vdec_malloc(size_t n)
+{
+ return (int16_t *) memalign(NEON_ALIGN, sizeof(int16_t) * n);
+}
+
+__attribute__ ((visibility("hidden")))
+void osmo_conv_neon_vdec_free(int16_t *ptr)
+{
+ free(ptr);
+}
+
+__attribute__ ((visibility("hidden")))
+void osmo_conv_neon_metrics_k5_n2(const int8_t *val, const int16_t *out,
+ int16_t *sums, int16_t *paths, int norm)
+{
+ const int16_t _val[4] = { val[0], val[1], val[0], val[1] };
+
+ _neon_metrics_k5_n2(_val, out, sums, paths, norm);
+}
+
+__attribute__ ((visibility("hidden")))
+void osmo_conv_neon_metrics_k5_n3(const int8_t *val, const int16_t *out,
+ int16_t *sums, int16_t *paths, int norm)
+{
+ const int16_t _val[4] = { val[0], val[1], val[2], 0 };
+
+ _neon_metrics_k5_n4(_val, out, sums, paths, norm);
+}
+
+__attribute__ ((visibility("hidden")))
+void osmo_conv_neon_metrics_k5_n4(const int8_t *val, const int16_t *out,
+ int16_t *sums, int16_t *paths, int norm)
+{
+ const int16_t _val[4] = { val[0], val[1], val[2], val[3] };
+
+ _neon_metrics_k5_n4(_val, out, sums, paths, norm);
+}
+
+__attribute__ ((visibility("hidden")))
+void osmo_conv_neon_metrics_k7_n2(const int8_t *val, const int16_t *out,
+ int16_t *sums, int16_t *paths, int norm)
+{
+ const int16_t _val[4] = { val[0], val[1], val[0], val[1] };
+
+ _neon_metrics_k7_n2(_val, out, sums, paths, norm);
+}
+
+__attribute__ ((visibility("hidden")))
+void osmo_conv_neon_metrics_k7_n3(const int8_t *val, const int16_t *out,
+ int16_t *sums, int16_t *paths, int norm)
+{
+ const int16_t _val[4] = { val[0], val[1], val[2], 0 };
+
+ _neon_metrics_k7_n4(_val, out, sums, paths, norm);
+}
+
+__attribute__ ((visibility("hidden")))
+void osmo_conv_neon_metrics_k7_n4(const int8_t *val, const int16_t *out,
+ int16_t *sums, int16_t *paths, int norm)
+{
+ const int16_t _val[4] = { val[0], val[1], val[2], val[3] };
+
+ _neon_metrics_k7_n4(_val, out, sums, paths, norm);
+}
diff --git a/src/conv_acc_neon_impl.h b/src/conv_acc_neon_impl.h
new file mode 100644
index 00000000..4471127e
--- /dev/null
+++ b/src/conv_acc_neon_impl.h
@@ -0,0 +1,354 @@
+/*! \file conv_acc_neon_impl.h
+ * Accelerated Viterbi decoder implementation:
+ * straight port of SSE to NEON based on Tom Tsous work */
+/*
+ * (C) 2020 by sysmocom - s.f.m.c. GmbH
+ * Author: Eric Wild
+ *
+ * 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.
+ */
+
+/* Some distributions (notably Alpine Linux) for some strange reason
+ * don't have this #define */
+#ifndef __always_inline
+#define __always_inline inline __attribute__((always_inline))
+#endif
+
+#define NEON_BUTTERFLY(M0,M1,M2,M3,M4) \
+{ \
+ M3 = vqaddq_s16(M0, M2); \
+ M4 = vqsubq_s16(M1, M2); \
+ M0 = vqsubq_s16(M0, M2); \
+ M1 = vqaddq_s16(M1, M2); \
+ M2 = vmaxq_s16(M3, M4); \
+ M3 = vreinterpretq_s16_u16(vcgtq_s16(M3, M4)); \
+ M4 = vmaxq_s16(M0, M1); \
+ M1 = vreinterpretq_s16_u16(vcgtq_s16(M0, M1)); \
+}
+
+#define NEON_DEINTERLEAVE_K5(M0,M1,M2,M3) \
+{ \
+ int16x8x2_t tmp; \
+ tmp = vuzpq_s16(M0, M1); \
+ M2 = tmp.val[0]; \
+ M3 = tmp.val[1]; \
+}
+
+#define NEON_DEINTERLEAVE_K7(M0,M1,M2,M3,M4,M5,M6,M7,M8,M9,M10,M11,M12,M13,M14,M15) \
+{ \
+ int16x8x2_t tmp; \
+ tmp = vuzpq_s16(M0, M1); \
+ M8 = tmp.val[0]; M9 = tmp.val[1]; \
+ tmp = vuzpq_s16(M2, M3); \
+ M10 = tmp.val[0]; M11 = tmp.val[1]; \
+ tmp = vuzpq_s16(M4, M5); \
+ M12 = tmp.val[0]; M13 = tmp.val[1]; \
+ tmp = vuzpq_s16(M6, M7); \
+ M14 = tmp.val[0]; M15 = tmp.val[1]; \
+}
+
+#define NEON_BRANCH_METRIC_N2(M0,M1,M2,M3,M4,M6,M7) \
+{ \
+ M0 = vmulq_s16(M4, M0); \
+ M1 = vmulq_s16(M4, M1); \
+ M2 = vmulq_s16(M4, M2); \
+ M3 = vmulq_s16(M4, M3); \
+ M6 = vcombine_s16(vpadd_s16(vget_low_s16(M0), vget_high_s16(M0)), vpadd_s16(vget_low_s16(M1), vget_high_s16(M1))); \
+ M7 = vcombine_s16(vpadd_s16(vget_low_s16(M2), vget_high_s16(M2)), vpadd_s16(vget_low_s16(M3), vget_high_s16(M3))); \
+}
+
+#define NEON_BRANCH_METRIC_N4(M0,M1,M2,M3,M4,M5) \
+{ \
+ M0 = vmulq_s16(M4, M0); \
+ M1 = vmulq_s16(M4, M1); \
+ M2 = vmulq_s16(M4, M2); \
+ M3 = vmulq_s16(M4, M3); \
+ int16x4_t t1 = vpadd_s16(vpadd_s16(vget_low_s16(M0), vget_high_s16(M0)), vpadd_s16(vget_low_s16(M1), vget_high_s16(M1))); \
+ int16x4_t t2 = vpadd_s16(vpadd_s16(vget_low_s16(M2), vget_high_s16(M2)), vpadd_s16(vget_low_s16(M3), vget_high_s16(M3))); \
+ M5 = vcombine_s16(t1, t2); \
+}
+
+#define NEON_NORMALIZE_K5(M0,M1,M2,M3) \
+{ \
+ M2 = vminq_s16(M0, M1); \
+ int16x4_t t = vpmin_s16(vget_low_s16(M2), vget_high_s16(M2)); \
+ t = vpmin_s16(t, t); \
+ t = vpmin_s16(t, t); \
+ M2 = vdupq_lane_s16(t, 0); \
+ M0 = vqsubq_s16(M0, M2); \
+ M1 = vqsubq_s16(M1, M2); \
+}
+
+#define NEON_NORMALIZE_K7(M0,M1,M2,M3,M4,M5,M6,M7,M8,M9,M10,M11) \
+{ \
+ M8 = vminq_s16(M0, M1); \
+ M9 = vminq_s16(M2, M3); \
+ M10 = vminq_s16(M4, M5); \
+ M11 = vminq_s16(M6, M7); \
+ M8 = vminq_s16(M8, M9); \
+ M10 = vminq_s16(M10, M11); \
+ M8 = vminq_s16(M8, M10); \
+ int16x4_t t = vpmin_s16(vget_low_s16(M8), vget_high_s16(M8)); \
+ t = vpmin_s16(t, t); \
+ t = vpmin_s16(t, t); \
+ M8 = vdupq_lane_s16(t, 0); \
+ M0 = vqsubq_s16(M0, M8); \
+ M1 = vqsubq_s16(M1, M8); \
+ M2 = vqsubq_s16(M2, M8); \
+ M3 = vqsubq_s16(M3, M8); \
+ M4 = vqsubq_s16(M4, M8); \
+ M5 = vqsubq_s16(M5, M8); \
+ M6 = vqsubq_s16(M6, M8); \
+ M7 = vqsubq_s16(M7, M8); \
+}
+
+__always_inline void _neon_metrics_k5_n2(const int16_t *val, const int16_t *outa, int16_t *sumsa, int16_t *paths,
+ int norm)
+{
+ int16_t *__restrict out = __builtin_assume_aligned(outa, 8);
+ int16_t *__restrict sums = __builtin_assume_aligned(sumsa, 8);
+ int16x8_t m0, m1, m2, m3, m4, m5, m6;
+ int16x4_t input;
+
+ /* (BMU) Load and expand 8-bit input out to 16-bits */
+ input = vld1_s16(val);
+ m2 = vcombine_s16(input, input);
+
+ /* (BMU) Load and compute branch metrics */
+ m0 = vld1q_s16(&out[0]);
+ m1 = vld1q_s16(&out[8]);
+
+ m0 = vmulq_s16(m2, m0);
+ m1 = vmulq_s16(m2, m1);
+ m2 = vcombine_s16(vpadd_s16(vget_low_s16(m0), vget_high_s16(m0)),
+ vpadd_s16(vget_low_s16(m1), vget_high_s16(m1)));
+
+ /* (PMU) Load accumulated path matrics */
+ m0 = vld1q_s16(&sums[0]);
+ m1 = vld1q_s16(&sums[8]);
+
+ NEON_DEINTERLEAVE_K5(m0, m1, m3, m4)
+
+ /* (PMU) Butterflies: 0-7 */
+ NEON_BUTTERFLY(m3, m4, m2, m5, m6)
+
+ if (norm)
+ NEON_NORMALIZE_K5(m2, m6, m0, m1)
+
+ vst1q_s16(&sums[0], m2);
+ vst1q_s16(&sums[8], m6);
+ vst1q_s16(&paths[0], m5);
+ vst1q_s16(&paths[8], m4);
+}
+
+__always_inline void _neon_metrics_k5_n4(const int16_t *val, const int16_t *outa, int16_t *sumsa, int16_t *paths,
+ int norm)
+{
+ int16_t *__restrict out = __builtin_assume_aligned(outa, 8);
+ int16_t *__restrict sums = __builtin_assume_aligned(sumsa, 8);
+ int16x8_t m0, m1, m2, m3, m4, m5, m6;
+ int16x4_t input;
+
+ /* (BMU) Load and expand 8-bit input out to 16-bits */
+ input = vld1_s16(val);
+ m4 = vcombine_s16(input, input);
+
+ /* (BMU) Load and compute branch metrics */
+ m0 = vld1q_s16(&out[0]);
+ m1 = vld1q_s16(&out[8]);
+ m2 = vld1q_s16(&out[16]);
+ m3 = vld1q_s16(&out[24]);
+
+ NEON_BRANCH_METRIC_N4(m0, m1, m2, m3, m4, m2)
+
+ /* (PMU) Load accumulated path matrics */
+ m0 = vld1q_s16(&sums[0]);
+ m1 = vld1q_s16(&sums[8]);
+
+ NEON_DEINTERLEAVE_K5(m0, m1, m3, m4)
+
+ /* (PMU) Butterflies: 0-7 */
+ NEON_BUTTERFLY(m3, m4, m2, m5, m6)
+
+ if (norm)
+ NEON_NORMALIZE_K5(m2, m6, m0, m1)
+
+ vst1q_s16(&sums[0], m2);
+ vst1q_s16(&sums[8], m6);
+ vst1q_s16(&paths[0], m5);
+ vst1q_s16(&paths[8], m4);
+}
+
+__always_inline static void _neon_metrics_k7_n2(const int16_t *val, const int16_t *outa, int16_t *sumsa, int16_t *paths,
+ int norm)
+{
+ int16_t *__restrict out = __builtin_assume_aligned(outa, 8);
+ int16_t *__restrict sums = __builtin_assume_aligned(sumsa, 8);
+ int16x8_t m0, m1, m2, m3, m4, m5, m6, m7;
+ int16x8_t m8, m9, m10, m11, m12, m13, m14, m15;
+ int16x4_t input;
+
+ /* (PMU) Load accumulated path matrics */
+ m0 = vld1q_s16(&sums[0]);
+ m1 = vld1q_s16(&sums[8]);
+ m2 = vld1q_s16(&sums[16]);
+ m3 = vld1q_s16(&sums[24]);
+ m4 = vld1q_s16(&sums[32]);
+ m5 = vld1q_s16(&sums[40]);
+ m6 = vld1q_s16(&sums[48]);
+ m7 = vld1q_s16(&sums[56]);
+
+ /* (PMU) Deinterleave into even and odd packed registers */
+ NEON_DEINTERLEAVE_K7(m0, m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11, m12, m13, m14, m15)
+
+ /* (BMU) Load and expand 8-bit input out to 16-bits */
+ input = vld1_s16(val);
+ m7 = vcombine_s16(input, input);
+
+ /* (BMU) Load and compute branch metrics */
+ m0 = vld1q_s16(&out[0]);
+ m1 = vld1q_s16(&out[8]);
+ m2 = vld1q_s16(&out[16]);
+ m3 = vld1q_s16(&out[24]);
+
+ NEON_BRANCH_METRIC_N2(m0, m1, m2, m3, m7, m4, m5)
+
+ m0 = vld1q_s16(&out[32]);
+ m1 = vld1q_s16(&out[40]);
+ m2 = vld1q_s16(&out[48]);
+ m3 = vld1q_s16(&out[56]);
+
+ NEON_BRANCH_METRIC_N2(m0, m1, m2, m3, m7, m6, m7)
+
+ /* (PMU) Butterflies: 0-15 */
+ NEON_BUTTERFLY(m8, m9, m4, m0, m1)
+ NEON_BUTTERFLY(m10, m11, m5, m2, m3)
+
+ vst1q_s16(&paths[0], m0);
+ vst1q_s16(&paths[8], m2);
+ vst1q_s16(&paths[32], m9);
+ vst1q_s16(&paths[40], m11);
+
+ /* (PMU) Butterflies: 17-31 */
+ NEON_BUTTERFLY(m12, m13, m6, m0, m2)
+ NEON_BUTTERFLY(m14, m15, m7, m9, m11)
+
+ vst1q_s16(&paths[16], m0);
+ vst1q_s16(&paths[24], m9);
+ vst1q_s16(&paths[48], m13);
+ vst1q_s16(&paths[56], m15);
+
+ if (norm)
+ NEON_NORMALIZE_K7(m4, m1, m5, m3, m6, m2, m7, m11, m0, m8, m9, m10)
+
+ vst1q_s16(&sums[0], m4);
+ vst1q_s16(&sums[8], m5);
+ vst1q_s16(&sums[16], m6);
+ vst1q_s16(&sums[24], m7);
+ vst1q_s16(&sums[32], m1);
+ vst1q_s16(&sums[40], m3);
+ vst1q_s16(&sums[48], m2);
+ vst1q_s16(&sums[56], m11);
+}
+
+__always_inline static void _neon_metrics_k7_n4(const int16_t *val, const int16_t *outa, int16_t *sumsa, int16_t *paths,
+ int norm)
+{
+ int16_t *__restrict out = __builtin_assume_aligned(outa, 8);
+ int16_t *__restrict sums = __builtin_assume_aligned(sumsa, 8);
+ int16x8_t m0, m1, m2, m3, m4, m5, m6, m7;
+ int16x8_t m8, m9, m10, m11, m12, m13, m14, m15;
+ int16x4_t input;
+
+ /* (PMU) Load accumulated path matrics */
+ m0 = vld1q_s16(&sums[0]);
+ m1 = vld1q_s16(&sums[8]);
+ m2 = vld1q_s16(&sums[16]);
+ m3 = vld1q_s16(&sums[24]);
+ m4 = vld1q_s16(&sums[32]);
+ m5 = vld1q_s16(&sums[40]);
+ m6 = vld1q_s16(&sums[48]);
+ m7 = vld1q_s16(&sums[56]);
+
+ /* (PMU) Deinterleave into even and odd packed registers */
+ NEON_DEINTERLEAVE_K7(m0, m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11, m12, m13, m14, m15)
+
+ /* (BMU) Load and expand 8-bit input out to 16-bits */
+ input = vld1_s16(val);
+ m7 = vcombine_s16(input, input);
+
+ /* (BMU) Load and compute branch metrics */
+ m0 = vld1q_s16(&out[0]);
+ m1 = vld1q_s16(&out[8]);
+ m2 = vld1q_s16(&out[16]);
+ m3 = vld1q_s16(&out[24]);
+
+ NEON_BRANCH_METRIC_N4(m0, m1, m2, m3, m7, m4)
+
+ m0 = vld1q_s16(&out[32]);
+ m1 = vld1q_s16(&out[40]);
+ m2 = vld1q_s16(&out[48]);
+ m3 = vld1q_s16(&out[56]);
+
+ NEON_BRANCH_METRIC_N4(m0, m1, m2, m3, m7, m5)
+
+ m0 = vld1q_s16(&out[64]);
+ m1 = vld1q_s16(&out[72]);
+ m2 = vld1q_s16(&out[80]);
+ m3 = vld1q_s16(&out[88]);
+
+ NEON_BRANCH_METRIC_N4(m0, m1, m2, m3, m7, m6)
+
+ m0 = vld1q_s16(&out[96]);
+ m1 = vld1q_s16(&out[104]);
+ m2 = vld1q_s16(&out[112]);
+ m3 = vld1q_s16(&out[120]);
+
+ NEON_BRANCH_METRIC_N4(m0, m1, m2, m3, m7, m7)
+
+ /* (PMU) Butterflies: 0-15 */
+ NEON_BUTTERFLY(m8, m9, m4, m0, m1)
+ NEON_BUTTERFLY(m10, m11, m5, m2, m3)
+
+ vst1q_s16(&paths[0], m0);
+ vst1q_s16(&paths[8], m2);
+ vst1q_s16(&paths[32], m9);
+ vst1q_s16(&paths[40], m11);
+
+ /* (PMU) Butterflies: 17-31 */
+ NEON_BUTTERFLY(m12, m13, m6, m0, m2)
+ NEON_BUTTERFLY(m14, m15, m7, m9, m11)
+
+ vst1q_s16(&paths[16], m0);
+ vst1q_s16(&paths[24], m9);
+ vst1q_s16(&paths[48], m13);
+ vst1q_s16(&paths[56], m15);
+
+ if (norm)
+ NEON_NORMALIZE_K7(m4, m1, m5, m3, m6, m2, m7, m11, m0, m8, m9, m10)
+
+ vst1q_s16(&sums[0], m4);
+ vst1q_s16(&sums[8], m5);
+ vst1q_s16(&sums[16], m6);
+ vst1q_s16(&sums[24], m7);
+ vst1q_s16(&sums[32], m1);
+ vst1q_s16(&sums[40], m3);
+ vst1q_s16(&sums[48], m2);
+ vst1q_s16(&sums[56], m11);
+}
diff --git a/src/ctrl/control_vty.c b/src/ctrl/control_vty.c
index ef988892..e141a4c8 100644
--- a/src/ctrl/control_vty.c
+++ b/src/ctrl/control_vty.c
@@ -82,10 +82,10 @@ static int config_write_ctrl(struct vty *vty)
int ctrl_vty_init(void *ctx)
{
ctrl_vty_ctx = ctx;
- install_element(CONFIG_NODE, &cfg_ctrl_cmd);
+ install_lib_element(CONFIG_NODE, &cfg_ctrl_cmd);
install_node(&ctrl_node, config_write_ctrl);
- install_element(L_CTRL_NODE, &cfg_ctrl_bind_addr_cmd);
+ install_lib_element(L_CTRL_NODE, &cfg_ctrl_bind_addr_cmd);
return 0;
}
diff --git a/src/exec.c b/src/exec.c
new file mode 100644
index 00000000..2a03ba80
--- /dev/null
+++ b/src/exec.c
@@ -0,0 +1,294 @@
+/* (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.
+ *
+ * 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"
+#ifndef EMBEDDED
+
+#define _GNU_SOURCE
+#include <unistd.h>
+
+#include <errno.h>
+#include <string.h>
+
+#include <stdio.h>
+#include <dirent.h>
+#include <sys/types.h>
+#include <pwd.h>
+
+#include <osmocom/core/logging.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/exec.h>
+
+/*! suggested list of environment variables to pass (if they exist) to a sub-process/script */
+const char *osmo_environment_whitelist[] = {
+ "USER", "LOGNAME", "HOME",
+ "LANG", "LC_ALL", "LC_COLLATE", "LC_CTYPE", "LC_MESSAGES", "LC_MONETARY", "LC_NUMERIC", "LC_TIME",
+ "PATH",
+ "PWD",
+ "SHELL",
+ "TERM",
+ "TMPDIR",
+ "LD_LIBRARY_PATH",
+ "LD_PRELOAD",
+ "POSIXLY_CORRECT",
+ "HOSTALIASES",
+ "TZ", "TZDIR",
+ "TERMCAP",
+ "COLUMNS", "LINES",
+ NULL
+};
+
+static bool str_in_list(const char **list, const char *key)
+{
+ const char **ent;
+
+ for (ent = list; *ent; ent++) {
+ if (!strcmp(*ent, key))
+ return true;
+ }
+ return false;
+}
+
+/*! filtered a process environment by whitelist; only copying pointers, no actual strings.
+ *
+ * This function is useful if you'd like to generate an environment to pass exec*e()
+ * functions. It will create a new environment containing only those entries whose
+ * keys (as per environment convention KEY=VALUE) are contained in the whitelist. The
+ * function will not copy the actual strings, but just create a new pointer array, pointing
+ * to the same memory as the input strings.
+ *
+ * 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[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)
+ * \returns number of entries filled in 'out'; negtive on error */
+int osmo_environment_filter(char **out, size_t out_len, char **in, const char **whitelist)
+{
+ char tmp[256];
+ char **ent;
+ size_t out_used = 0;
+
+ /* invalid calls */
+ if (!out || out_len == 0 || !whitelist)
+ return -EINVAL;
+
+ /* legal, but unusual: no input to filter should generate empty, terminated out */
+ if (!in) {
+ out[0] = NULL;
+ return 1;
+ }
+
+ /* iterate over input entries */
+ for (ent = in; *ent; ent++) {
+ char *eq = strchr(*ent, '=');
+ unsigned long eq_pos;
+ if (!eq) {
+ /* no '=' in string, skip it */
+ continue;
+ }
+ eq_pos = eq - *ent;
+ if (eq_pos >= ARRAY_SIZE(tmp))
+ continue;
+ strncpy(tmp, *ent, eq_pos);
+ tmp[eq_pos] = '\0';
+ if (str_in_list(whitelist, tmp)) {
+ if (out_used == out_len-1)
+ break;
+ /* append to output */
+ out[out_used++] = *ent;
+ }
+ }
+ OSMO_ASSERT(out_used < out_len);
+ out[out_used++] = NULL;
+ return out_used;
+}
+
+/*! append one environment to another; only copying pointers, not actual strings.
+ *
+ * This function is useful if you'd like to append soem entries to an environment
+ * befoer passing it to exec*e() functions.
+ *
+ * It will append all entries from 'in' to the environment in 'out', as long as
+ * 'out' has space (determined by 'out_len').
+ *
+ * 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[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 */
+int osmo_environment_append(char **out, size_t out_len, char **in)
+{
+ size_t out_used = 0;
+
+ if (!out || out_len == 0)
+ return -EINVAL;
+
+ /* seek to end of existing output */
+ for (out_used = 0; out[out_used]; out_used++) {}
+
+ if (!in) {
+ if (out_used == 0)
+ out[out_used++] = NULL;
+ return out_used;
+ }
+
+ for (; *in && out_used < out_len-1; in++)
+ out[out_used++] = *in;
+
+ OSMO_ASSERT(out_used < out_len);
+ out[out_used++] = NULL;
+
+ return out_used;
+}
+
+/* Iterate over files in /proc/self/fd and close all above lst_fd_to_keep */
+int osmo_close_all_fds_above(int last_fd_to_keep)
+{
+ struct dirent *ent;
+ DIR *dir;
+ int rc;
+
+ dir = opendir("/proc/self/fd");
+ if (!dir) {
+ LOGP(DLGLOBAL, LOGL_ERROR, "Cannot open /proc/self/fd: %s\n", strerror(errno));
+ return -ENODEV;
+ }
+
+ while ((ent = readdir(dir))) {
+ int fd = atoi(ent->d_name);
+ if (fd <= last_fd_to_keep)
+ continue;
+ if (fd == dirfd(dir))
+ continue;
+ rc = close(fd);
+ if (rc)
+ LOGP(DLGLOBAL, LOGL_ERROR, "Error closing fd=%d: %s\n", fd, strerror(errno));
+ }
+ closedir(dir);
+ return 0;
+}
+
+/* Seems like POSIX has no header file for this, and even glibc + __USE_GNU doesn't help */
+extern char **environ;
+
+/*! call an external shell command as 'user' without waiting for it.
+ *
+ * This mimics the behavior of system(3), with the following differences:
+ * - it doesn't wait for completion of the child process
+ * - it closes all non-stdio file descriptors by iterating /proc/self/fd
+ * - it constructs a reduced environment where only whitelisted keys survive
+ * - it (optionally) appends additional variables to the environment
+ * - it (optionally) changes the user ID to that of 'user' (requires execution as root)
+ *
+ * \param[in] command the shell command to be executed, see system(3)
+ * \param[in] env_whitelist A white-list of keys for environment variables
+ * \param[in] addl_env any additional environment variables to be appended
+ * \param[in] user name of the user to which we should switch before executing the command
+ * \returns PID of generated child process; negative on error
+ */
+int osmo_system_nowait2(const char *command, const char **env_whitelist, char **addl_env, const char *user)
+{
+ struct passwd _pw;
+ struct passwd *pw = NULL;
+ int getpw_buflen = sysconf(_SC_GETPW_R_SIZE_MAX);
+ int rc;
+
+ if (user) {
+ char buf[getpw_buflen];
+ getpwnam_r(user, &_pw, buf, sizeof(buf), &pw);
+ if (!pw)
+ return -EINVAL;
+ }
+
+ rc = fork();
+ if (rc == 0) {
+ /* we are in the child */
+ char *new_env[1024];
+
+ /* close all file descriptors above stdio */
+ osmo_close_all_fds_above(2);
+
+ /* man execle: "an array of pointers *must* be terminated by a null pointer" */
+ new_env[0] = NULL;
+
+ /* build the new environment */
+ if (env_whitelist) {
+ rc = osmo_environment_filter(new_env, ARRAY_SIZE(new_env), environ, env_whitelist);
+ if (rc < 0)
+ return rc;
+ }
+ if (addl_env) {
+ rc = osmo_environment_append(new_env, ARRAY_SIZE(new_env), addl_env);
+ if (rc < 0)
+ return rc;
+ }
+
+ /* drop privileges */
+ if (pw) {
+ if (setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) < 0) {
+ perror("setresgid() during privilege drop");
+ exit(1);
+ }
+
+ if (setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) < 0) {
+ perror("setresuid() during privilege drop");
+ exit(1);
+ }
+
+ }
+
+ /* if we want to behave like system(3), we must go via the shell */
+ execle("/bin/sh", "sh", "-c", command, (char *) NULL, new_env);
+ /* only reached in case of error */
+ LOGP(DLGLOBAL, LOGL_ERROR, "Error executing command '%s' after fork: %s\n",
+ command, strerror(errno));
+ return -EIO;
+ } else {
+ /* we are in the parent */
+ return rc;
+ }
+}
+
+/*! call an external shell command without waiting for it.
+ *
+ * This mimics the behavior of system(3), with the following differences:
+ * - it doesn't wait for completion of the child process
+ * - it closes all non-stdio file descriptors by iterating /proc/self/fd
+ * - it constructs a reduced environment where only whitelisted keys survive
+ * - it (optionally) appends additional variables to the environment
+ *
+ * \param[in] command the shell command to be executed, see system(3)
+ * \param[in] env_whitelist A white-list of keys for environment variables
+ * \param[in] addl_env any additional environment variables to be appended
+ * \returns PID of generated child process; negative on error
+ */
+int osmo_system_nowait(const char *command, const char **env_whitelist, char **addl_env)
+{
+ return osmo_system_nowait2(command, env_whitelist, addl_env, NULL);
+}
+
+
+#endif /* EMBEDDED */
diff --git a/src/fsm.c b/src/fsm.c
index c8863513..1e8909ec 100644
--- a/src/fsm.c
+++ b/src/fsm.c
@@ -102,8 +102,20 @@ static __thread struct {
unsigned int depth;
/*! Talloc context to collect all deferred deallocations (FSM instances, and talloc objects if any). */
void *collect_ctx;
+ /*! See osmo_fsm_set_dealloc_ctx() */
+ void *fsm_dealloc_ctx;
} fsm_term_safely;
+/*! Internal call to free an FSM instance, which redirects to the context set by osmo_fsm_set_dealloc_ctx() if any.
+ */
+static void fsm_free_or_steal(void *talloc_object)
+{
+ if (fsm_term_safely.fsm_dealloc_ctx)
+ talloc_steal(fsm_term_safely.fsm_dealloc_ctx, talloc_object);
+ else
+ talloc_free(talloc_object);
+}
+
/*! specify if FSM instance addresses should be logged or not
*
* By default, the FSM name includes the pointer address of the \ref
@@ -139,11 +151,9 @@ void osmo_fsm_log_timeouts(bool log_timeouts)
/*! Enable safer way to deallocate cascades of terminating FSM instances.
*
- * For legacy compatibility, this is disabled by default. In newer programs / releases, it is recommended to enable this
- * feature during main() startup, since it greatly simplifies deallocating child, parent and other FSM instances without
- * running into double-free or use-after-free scenarios. When enabled, this feature changes the order of logging, which
- * may break legacy unit test expectations, and changes the order of deallocation to after the parent term event is
- * dispatched.
+ * Note, using osmo_fsm_set_dealloc_ctx() is a more general solution to this same problem.
+ * Particularly, in a program using osmo_select_main_ctx(), the simplest solution to avoid most use-after-free problems
+ * from FSM instance deallocation is using osmo_fsm_set_dealloc_ctx(OTC_SELECT).
*
* When enabled, an FSM instance termination detects whether another FSM instance is already terminating, and instead of
* deallocating immediately, collects all terminating FSM instances in a talloc context, to be bulk deallocated once all
@@ -155,6 +165,9 @@ void osmo_fsm_log_timeouts(bool log_timeouts)
*
* For illustration, see fsm_dealloc_test.c.
*
+ * When enabled, this feature changes the order of logging, which may break legacy unit test expectations, and changes
+ * the order of deallocation to after the parent term event is dispatched.
+ *
* \param[in] term_safely Pass true to switch to safer FSM instance termination behavior.
*/
void osmo_fsm_term_safely(bool term_safely)
@@ -162,6 +175,31 @@ void osmo_fsm_term_safely(bool term_safely)
fsm_term_safely_enabled = term_safely;
}
+/*! Instead of deallocating FSM instances, move them to the given talloc context.
+ *
+ * It is the caller's responsibility to clear this context to actually free the memory of terminated FSM instances.
+ * Make sure to not talloc_free(ctx) itself before setting a different osmo_fsm_set_dealloc_ctx(). To clear a ctx
+ * without the need to call osmo_fsm_set_dealloc_ctx() again, rather use talloc_free_children(ctx).
+ *
+ * For example, to defer deallocation to the next osmo_select_main_ctx() iteration, set this to OTC_SELECT.
+ *
+ * Deferring deallocation is the simplest solution to avoid most use-after-free problems from FSM instance deallocation.
+ * This is a simpler and more general solution than osmo_fsm_term_safely().
+ *
+ * To disable the feature again, pass NULL as ctx.
+ *
+ * Both osmo_fsm_term_safely() and osmo_fsm_set_dealloc_ctx() can be enabled at the same time, which will result in
+ * first collecting deallocated FSM instances in fsm_term_safely.collect_ctx, and finally reparenting that to the ctx
+ * passed here. However, in practice, it does not really make sense to enable both at the same time.
+ *
+ * \param ctx[in] Instead of talloc_free()int, talloc_steal() all future deallocated osmo_fsm_inst instances to this
+ * ctx. If NULL, go back to talloc_free() as usual.
+ */
+void osmo_fsm_set_dealloc_ctx(void *ctx)
+{
+ fsm_term_safely.fsm_dealloc_ctx = ctx;
+}
+
/*! talloc_free() the given object immediately, or once ongoing FSM terminations are done.
*
* If an FSM deallocation cascade is ongoing, talloc_steal() the given talloc_object into the talloc context that is
@@ -185,7 +223,7 @@ void osmo_fsm_term_safely(bool term_safely)
static void osmo_fsm_defer_free(void *talloc_object)
{
if (!fsm_term_safely.depth) {
- talloc_free(talloc_object);
+ fsm_free_or_steal(talloc_object);
return;
}
@@ -412,7 +450,7 @@ 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) {
- talloc_free(fi);
+ fsm_free_or_steal(fi);
return NULL;
}
@@ -529,11 +567,11 @@ void osmo_fsm_inst_free(struct osmo_fsm_inst *fi)
* deallocate separately to avoid use-after-free errors, put it in there and deallocate all at once. */
LOGPFSM(fi, "Deallocated, including all deferred deallocations\n");
osmo_fsm_defer_free(fi);
- talloc_free(fsm_term_safely.collect_ctx);
+ fsm_free_or_steal(fsm_term_safely.collect_ctx);
fsm_term_safely.collect_ctx = NULL;
} else {
LOGPFSM(fi, "Deallocated\n");
- talloc_free(fi);
+ fsm_free_or_steal(fi);
}
fsm_term_safely.root_fi = NULL;
}
@@ -592,6 +630,13 @@ static int state_chg(struct osmo_fsm_inst *fi, uint32_t new_state,
const struct osmo_fsm_state *st = &fsm->states[fi->state];
struct timeval remaining;
+ if (fi->proc.terminating) {
+ LOGPFSMSRC(fi, file, line,
+ "FSM instance already terminating, not changing state to %s\n",
+ osmo_fsm_state_name(fsm, new_state));
+ return -EINVAL;
+ }
+
/* validate if new_state is a valid state */
if (!(st->out_state_mask & (1 << new_state))) {
LOGPFSMLSRC(fi, LOGL_ERROR, file, line,
@@ -802,6 +847,14 @@ int _osmo_fsm_inst_dispatch(struct osmo_fsm_inst *fi, uint32_t event, void *data
}
fsm = fi->fsm;
+
+ if (fi->proc.terminating) {
+ LOGPFSMSRC(fi, file, line,
+ "FSM instance already terminating, not dispatching event %s\n",
+ osmo_fsm_event_name(fsm, event));
+ return -EINVAL;
+ }
+
OSMO_ASSERT(fi->state < fsm->num_states);
fs = &fi->fsm->states[fi->state];
diff --git a/src/gb/Makefile.am b/src/gb/Makefile.am
index e14c11c1..65c35529 100644
--- a/src/gb/Makefile.am
+++ b/src/gb/Makefile.am
@@ -1,13 +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=9:1:0
+LIBVERSION=11:0:0
AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_builddir)/include
AM_CFLAGS = -Wall ${GCC_FVISIBILITY_HIDDEN} -fno-strict-aliasing $(TALLOC_CFLAGS)
# FIXME: this should eventually go into a milenage/Makefile.am
-noinst_HEADERS = common_vty.h gb_internal.h
+noinst_HEADERS = common_vty.h gb_internal.h gprs_bssgp_internal.h gprs_ns2_internal.h
if ENABLE_GB
lib_LTLIBRARIES = libosmogb.la
@@ -20,7 +20,10 @@ libosmogb_la_LIBADD = $(TALLOC_LIBS) \
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_bss.c common_vty.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_message.c gprs_ns2_vty.c \
+ common_vty.c
endif
EXTRA_DIST = libosmogb.map
diff --git a/src/gb/common_vty.c b/src/gb/common_vty.c
index a47294b5..eb665d52 100644
--- a/src/gb/common_vty.c
+++ b/src/gb/common_vty.c
@@ -40,8 +40,8 @@
int gprs_log_filter_fn(const struct log_context *ctx,
struct log_target *tar)
{
- const struct gprs_nsvc *nsvc = ctx->ctx[LOG_CTX_GB_NSVC];
- const struct gprs_bvc *bvc = ctx->ctx[LOG_CTX_GB_BVC];
+ const void *nsvc = ctx->ctx[LOG_CTX_GB_NSVC];
+ const void *bvc = ctx->ctx[LOG_CTX_GB_BVC];
/* Filter on the NS Virtual Connection */
if ((tar->filter_map & (1 << LOG_FLT_GB_NSVC)) != 0
diff --git a/src/gb/gprs_bssgp.c b/src/gb/gprs_bssgp.c
index b695c284..8b8d534e 100644
--- a/src/gb/gprs_bssgp.c
+++ b/src/gb/gprs_bssgp.c
@@ -44,6 +44,11 @@
void *bssgp_tall_ctx = NULL;
+static int _gprs_ns_sendmsg(void *ctx, struct msgb *msg);
+
+bssgp_bvc_send bssgp_ns_send = _gprs_ns_sendmsg;
+void *bssgp_ns_send_data = NULL;
+
static const struct rate_ctr_desc bssgp_ctr_description[] = {
{ "packets:in", "Packets at BSSGP Level ( In)" },
{ "packets:out","Packets at BSSGP Level (Out)" },
@@ -67,6 +72,13 @@ LLIST_HEAD(bssgp_bvc_ctxts);
static int _bssgp_tx_dl_ud(struct bssgp_flow_control *fc, struct msgb *msg,
uint32_t llc_pdu_len, void *priv);
+
+/* 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)
+{
+ return gprs_ns_sendmsg(bssgp_nsi, msg);
+}
+
/* Find a BTS Context based on parsed RA ID and Cell ID */
struct bssgp_bvc_ctx *btsctx_by_raid_cid(const struct gprs_ra_id *raid, uint16_t cid)
{
@@ -117,6 +129,12 @@ struct bssgp_bvc_ctx *btsctx_by_bvci_nsei(uint16_t bvci, uint16_t nsei)
return NULL;
}
+void bssgp_set_bssgp_callback(bssgp_bvc_send ns_send, void *data)
+{
+ bssgp_ns_send = ns_send;
+ bssgp_ns_send_data = data;
+}
+
struct bssgp_bvc_ctx *btsctx_alloc(uint16_t bvci, uint16_t nsei)
{
struct bssgp_bvc_ctx *ctx;
@@ -128,17 +146,36 @@ struct bssgp_bvc_ctx *btsctx_alloc(uint16_t bvci, uint16_t nsei)
ctx->nsei = nsei;
/* FIXME: BVCI is not unique, only BVCI+NSEI ?!? */
ctx->ctrg = rate_ctr_group_alloc(ctx, &bssgp_ctrg_desc, bvci);
- if (!ctx->ctrg) {
- talloc_free(ctx);
- return NULL;
- }
+ if (!ctx->ctrg)
+ goto err_ctrg;
+
ctx->fc = talloc_zero(ctx, struct bssgp_flow_control);
+ if (!ctx->fc)
+ goto err_fc;
+
/* cofigure for 2Mbit, 30 packets in queue */
bssgp_fc_init(ctx->fc, 100000, 2*1024*1024/8, 30, &_bssgp_tx_dl_ud);
llist_add(&ctx->list, &bssgp_bvc_ctxts);
return ctx;
+
+err_fc:
+ rate_ctr_group_free(ctx->ctrg);
+err_ctrg:
+ talloc_free(ctx);
+ return NULL;
+}
+
+void bssgp_bvc_ctx_free(struct bssgp_bvc_ctx *ctx)
+{
+ if (!ctx)
+ return;
+
+ osmo_timer_del(&ctx->fc->timer);
+ rate_ctr_group_free(ctx->ctrg);
+ llist_del(&ctx->list);
+ talloc_free(ctx);
}
/* Chapter 10.4.5: Flow Control BVC ACK */
@@ -154,7 +191,7 @@ static int bssgp_tx_fc_bvc_ack(uint16_t nsei, uint8_t tag, uint16_t ns_bvci)
bgph->pdu_type = BSSGP_PDUT_FLOW_CONTROL_BVC_ACK;
msgb_tvlv_put(msg, BSSGP_IE_TAG, 1, &tag);
- return gprs_ns_sendmsg(bssgp_nsi, msg);
+ return bssgp_ns_send(bssgp_ns_send_data, msg);
}
/* 10.3.7 SUSPEND-ACK PDU */
@@ -173,7 +210,7 @@ int bssgp_tx_suspend_ack(uint16_t nsei, uint32_t tlli,
bssgp_msgb_ra_put(msg, ra_id);
msgb_tvlv_put(msg, BSSGP_IE_SUSPEND_REF_NR, 1, &suspend_ref);
- return gprs_ns_sendmsg(bssgp_nsi, msg);
+ return bssgp_ns_send(bssgp_ns_send_data, msg);
}
/* 10.3.8 SUSPEND-NACK PDU */
@@ -195,7 +232,7 @@ int bssgp_tx_suspend_nack(uint16_t nsei, uint32_t tlli,
if (cause)
msgb_tvlv_put(msg, BSSGP_IE_CAUSE, 1, cause);
- return gprs_ns_sendmsg(bssgp_nsi, msg);
+ return bssgp_ns_send(bssgp_ns_send_data, msg);
}
/* 10.3.10 RESUME-ACK PDU */
@@ -213,7 +250,7 @@ int bssgp_tx_resume_ack(uint16_t nsei, uint32_t tlli,
bssgp_msgb_tlli_put(msg, tlli);
bssgp_msgb_ra_put(msg, ra_id);
- return gprs_ns_sendmsg(bssgp_nsi, msg);
+ return bssgp_ns_send(bssgp_ns_send_data, msg);
}
/* 10.3.11 RESUME-NACK PDU */
@@ -234,7 +271,7 @@ int bssgp_tx_resume_nack(uint16_t nsei, uint32_t tlli,
if (cause)
msgb_tvlv_put(msg, BSSGP_IE_CAUSE, 1, cause);
- return gprs_ns_sendmsg(bssgp_nsi, msg);
+ return bssgp_ns_send(bssgp_ns_send_data, msg);
}
uint16_t bssgp_parse_cell_id(struct gprs_ra_id *raid, const uint8_t *buf)
@@ -257,7 +294,7 @@ int bssgp_create_cell_id(uint8_t *buf, const struct gprs_ra_id *raid,
}
/* Chapter 8.4 BVC-Reset Procedure */
-static int bssgp_rx_bvc_reset(struct msgb *msg, struct tlv_parsed *tp,
+static int bssgp_rx_bvc_reset(struct msgb *msg, struct tlv_parsed *tp,
uint16_t ns_bvci)
{
struct osmo_bssgp_prim nmp;
@@ -735,7 +772,7 @@ static int bssgp_fc_needs_queueing(struct bssgp_flow_control *fc, uint32_t pdu_l
static int _bssgp_tx_dl_ud(struct bssgp_flow_control *fc, struct msgb *msg,
uint32_t llc_pdu_len, void *priv)
{
- return gprs_ns_sendmsg(bssgp_nsi, msg);
+ return bssgp_ns_send(bssgp_ns_send_data, msg);
}
/* input function of the flow control implementation, called first
@@ -1169,6 +1206,7 @@ int bssgp_tx_dl_ud(struct msgb *msg, uint16_t pdu_lifetime,
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Warray-bounds"
int imsi_len = gsm48_generate_mid_from_imsi(mi, dup->imsi);
+ OSMO_ASSERT(imsi_len <= GSM48_MID_MAX_SIZE);
if (imsi_len > 2)
msgb_tvlv_push(msg, BSSGP_IE_IMSI,
imsi_len-2, mi+2);
@@ -1238,6 +1276,7 @@ int bssgp_tx_paging(uint16_t nsei, uint16_t ns_bvci,
* mi[131], which is wrong */
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Warray-bounds"
+ OSMO_ASSERT(imsi_len <= GSM48_MID_MAX_SIZE);
msgb_tvlv_put(msg, BSSGP_IE_IMSI, imsi_len-2, mi+2);
#pragma GCC diagnostic pop
/* DRX Parameters */
@@ -1275,7 +1314,7 @@ int bssgp_tx_paging(uint16_t nsei, uint16_t ns_bvci,
msgb_tvlv_put(msg, BSSGP_IE_TMSI, 4, (uint8_t *) &ptmsi);
}
- return gprs_ns_sendmsg(bssgp_nsi, msg);
+ return bssgp_ns_send(bssgp_ns_send_data, msg);
}
void bssgp_set_log_ss(int ss)
diff --git a/src/gb/gprs_bssgp_bss.c b/src/gb/gprs_bssgp_bss.c
index f06c403f..d1734ee5 100644
--- a/src/gb/gprs_bssgp_bss.c
+++ b/src/gb/gprs_bssgp_bss.c
@@ -34,6 +34,7 @@
#include <osmocom/gprs/gprs_bssgp_bss.h>
#include <osmocom/gprs/gprs_ns.h>
+#include "gprs_bssgp_internal.h"
#include "common_vty.h"
#define GSM_IMSI_LENGTH 17
@@ -69,7 +70,7 @@ int bssgp_tx_suspend(uint16_t nsei, uint32_t tlli,
bssgp_msgb_tlli_put(msg, tlli);
bssgp_msgb_ra_put(msg, ra_id);
- return gprs_ns_sendmsg(bssgp_nsi, msg);
+ return bssgp_ns_send(bssgp_ns_send_data, msg);
}
/*! GMM-RESUME.req (Chapter 10.3.9) */
@@ -91,7 +92,7 @@ int bssgp_tx_resume(uint16_t nsei, uint32_t tlli,
msgb_tvlv_put(msg, BSSGP_IE_SUSPEND_REF_NR, 1, &suspend_ref);
- return gprs_ns_sendmsg(bssgp_nsi, msg);
+ return bssgp_ns_send(bssgp_ns_send_data, msg);
}
/*! Transmit RA-CAPABILITY-UPDATE (10.3.3) */
@@ -113,7 +114,7 @@ int bssgp_tx_ra_capa_upd(struct bssgp_bvc_ctx *bctx, uint32_t tlli, uint8_t tag)
msgb_tvlv_put(msg, BSSGP_IE_TAG, 1, &tag);
- return gprs_ns_sendmsg(bssgp_nsi, msg);
+ return bssgp_ns_send(bssgp_ns_send_data, msg);
}
/* first common part of RADIO-STATUS */
@@ -141,7 +142,7 @@ 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));
- return gprs_ns_sendmsg(bssgp_nsi, msg);
+ return bssgp_ns_send(bssgp_ns_send_data, msg);
}
/*! Transmit RADIO-STATUS for TLLI (10.3.5) */
@@ -189,6 +190,7 @@ int bssgp_tx_radio_status_imsi(struct bssgp_bvc_ctx *bctx, uint8_t cause,
* mi[131], which is wrong */
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Warray-bounds"
+ OSMO_ASSERT(imsi_len <= GSM48_MID_MAX_SIZE);
/* strip the MI type and length values (2 bytes) */
if (imsi_len > 2)
msgb_tvlv_put(msg, BSSGP_IE_IMSI, imsi_len-2, mi+2);
@@ -219,7 +221,7 @@ int bssgp_tx_flush_ll_ack(struct bssgp_bvc_ctx *bctx, uint32_t tlli,
msgb_tvlv_put(msg, BSSGP_IE_BVCI, 2, (uint8_t *) &_bvci_new);
msgb_tvlv_put(msg, BSSGP_IE_NUM_OCT_AFF, 3, (uint8_t *) &_oct_aff);
- return gprs_ns_sendmsg(bssgp_nsi, msg);
+ return bssgp_ns_send(bssgp_ns_send_data, msg);
}
/*! Transmit LLC-DISCARDED (Chapter 10.4.3) */
@@ -245,7 +247,7 @@ int bssgp_tx_llc_discarded(struct bssgp_bvc_ctx *bctx, uint32_t tlli,
msgb_tvlv_put(msg, BSSGP_IE_BVCI, 2, (uint8_t *) &_bvci);
msgb_tvlv_put(msg, BSSGP_IE_NUM_OCT_AFF, 3, ((uint8_t *) &_oct_aff) + 1);
- return gprs_ns_sendmsg(bssgp_nsi, msg);
+ return bssgp_ns_send(bssgp_ns_send_data, msg);
}
/*! Transmit a BVC-BLOCK message (Chapter 10.4.8) */
@@ -266,7 +268,7 @@ int bssgp_tx_bvc_block(struct bssgp_bvc_ctx *bctx, uint8_t cause)
msgb_tvlv_put(msg, BSSGP_IE_BVCI, 2, (uint8_t *) &_bvci);
msgb_tvlv_put(msg, BSSGP_IE_CAUSE, 1, &cause);
- return gprs_ns_sendmsg(bssgp_nsi, msg);
+ return bssgp_ns_send(bssgp_ns_send_data, msg);
}
/*! Transmit a BVC-UNBLOCK message (Chapter 10.4.10) */
@@ -285,11 +287,11 @@ int bssgp_tx_bvc_unblock(struct bssgp_bvc_ctx *bctx)
msgb_tvlv_put(msg, BSSGP_IE_BVCI, 2, (uint8_t *) &_bvci);
- return gprs_ns_sendmsg(bssgp_nsi, msg);
+ return bssgp_ns_send(bssgp_ns_send_data, msg);
}
/*! Transmit a BVC-RESET message (Chapter 10.4.12) */
-int bssgp_tx_bvc_reset(struct bssgp_bvc_ctx *bctx, uint16_t bvci, uint8_t cause)
+int bssgp_tx_bvc_reset2(struct bssgp_bvc_ctx *bctx, uint16_t bvci, uint8_t cause, bool add_cell_id)
{
struct msgb *msg = bssgp_msgb_alloc();
struct bssgp_normal_hdr *bgph =
@@ -305,14 +307,27 @@ int bssgp_tx_bvc_reset(struct bssgp_bvc_ctx *bctx, uint16_t bvci, uint8_t cause)
msgb_tvlv_put(msg, BSSGP_IE_BVCI, 2, (uint8_t *) &_bvci);
msgb_tvlv_put(msg, BSSGP_IE_CAUSE, 1, &cause);
- if (bvci != BVCI_PTM) {
+ if (add_cell_id) {
uint8_t bssgp_cid[8];
bssgp_create_cell_id(bssgp_cid, &bctx->ra_id, bctx->cell_id);
msgb_tvlv_put(msg, BSSGP_IE_CELL_ID, sizeof(bssgp_cid), bssgp_cid);
}
/* Optional: Feature Bitmap */
- return gprs_ns_sendmsg(bssgp_nsi, msg);
+ return bssgp_ns_send(bssgp_ns_send_data, msg);
+}
+int bssgp_tx_bvc_reset(struct bssgp_bvc_ctx *bctx, uint16_t bvci, uint8_t cause)
+{
+ /* The Cell Identifier IE is mandatory in the BVC-RESET PDU sent from BSS to SGSN in order to reset a
+ * BVC corresponding to a PTP functional entity. The Cell Identifier IE shall not be used in any other
+ * BVC-RESET PDU. */
+ switch (bvci) {
+ case BVCI_SIGNALLING:
+ case BVCI_PTM:
+ return bssgp_tx_bvc_reset2(bctx, bvci, cause, false);
+ default:
+ return bssgp_tx_bvc_reset2(bctx, bvci, cause, true);
+ }
}
/*! Transmit a FLOW_CONTROL-BVC (Chapter 10.4.4)
@@ -384,7 +399,7 @@ int bssgp_tx_fc_bvc(struct bssgp_bvc_ctx *bctx, uint8_t tag,
sizeof(e_queue_delay),
(uint8_t *) &e_queue_delay);
- return gprs_ns_sendmsg(bssgp_nsi, msg);
+ return bssgp_ns_send(bssgp_ns_send_data, msg);
}
/*! Transmit a FLOW_CONTROL-MS (Chapter 10.4.6)
@@ -427,7 +442,7 @@ int bssgp_tx_fc_ms(struct bssgp_bvc_ctx *bctx, uint32_t tlli, uint8_t tag,
msgb_tvlv_put(msg, BSSGP_IE_BUCKET_FULL_RATIO,
1, bucket_full_ratio);
- return gprs_ns_sendmsg(bssgp_nsi, msg);
+ return bssgp_ns_send(bssgp_ns_send_data, msg);
}
/*! RL-UL-UNITDATA.req (Chapter 10.2.2)
@@ -473,7 +488,7 @@ int bssgp_tx_ul_ud(struct bssgp_bvc_ctx *bctx, uint32_t tlli,
rate_ctr_inc(&bctx->ctrg->ctr[BSSGP_CTR_PKTS_OUT]);
rate_ctr_add(&bctx->ctrg->ctr[BSSGP_CTR_BYTES_OUT], msg->len);
- return gprs_ns_sendmsg(bssgp_nsi, msg);
+ return bssgp_ns_send(bssgp_ns_send_data, msg);
}
/* Parse a single GMM-PAGING.req to a given NSEI/NS-BVCI */
diff --git a/src/gb/gprs_bssgp_internal.h b/src/gb/gprs_bssgp_internal.h
new file mode 100644
index 00000000..2ada027c
--- /dev/null
+++ b/src/gb/gprs_bssgp_internal.h
@@ -0,0 +1,7 @@
+
+#pragma once
+
+#include <osmocom/gprs/gprs_bssgp.h>
+
+extern bssgp_bvc_send bssgp_ns_send;
+extern void *bssgp_ns_send_data;
diff --git a/src/gb/gprs_bssgp_util.c b/src/gb/gprs_bssgp_util.c
index 669dfb86..917f1f32 100644
--- a/src/gb/gprs_bssgp_util.c
+++ b/src/gb/gprs_bssgp_util.c
@@ -32,6 +32,7 @@
#include <osmocom/gprs/gprs_bssgp.h>
#include <osmocom/gprs/gprs_ns.h>
+#include "gprs_bssgp_internal.h"
#include "common_vty.h"
struct gprs_ns_inst *bssgp_nsi;
@@ -43,7 +44,7 @@ struct gprs_ns_inst *bssgp_nsi;
static const struct value_string bssgp_cause_strings[] = {
{ BSSGP_CAUSE_PROC_OVERLOAD, "Processor overload" },
{ BSSGP_CAUSE_EQUIP_FAIL, "Equipment Failure" },
- { BSSGP_CAUSE_TRASIT_NET_FAIL, "Transit netowkr service failure" },
+ { BSSGP_CAUSE_TRASIT_NET_FAIL, "Transit network service failure" },
{ BSSGP_CAUSE_CAPA_GREATER_0KPBS, "Transmission capacity modified" },
{ BSSGP_CAUSE_UNKNOWN_MS, "Unknown MS" },
{ BSSGP_CAUSE_UNKNOWN_BVCI, "Unknown BVCI" },
@@ -210,7 +211,7 @@ int bssgp_tx_simple_bvci(uint8_t pdu_type, uint16_t nsei,
_bvci = osmo_htons(bvci);
msgb_tvlv_put(msg, BSSGP_IE_BVCI, 2, (uint8_t *) &_bvci);
- return gprs_ns_sendmsg(bssgp_nsi, msg);
+ return bssgp_ns_send(bssgp_ns_send_data, msg);
}
/* Chapter 10.4.14: Status */
@@ -248,5 +249,5 @@ int bssgp_tx_status(uint8_t cause, uint16_t *bvci, struct msgb *orig_msg)
msgb_tvlv_put(msg, BSSGP_IE_PDU_IN_ERROR,
msgb_bssgp_len(orig_msg), msgb_bssgph(orig_msg));
- return gprs_ns_sendmsg(bssgp_nsi, msg);
+ return bssgp_ns_send(bssgp_ns_send_data, msg);
}
diff --git a/src/gb/gprs_bssgp_vty.c b/src/gb/gprs_bssgp_vty.c
index 3af6517f..3dee18eb 100644
--- a/src/gb/gprs_bssgp_vty.c
+++ b/src/gb/gprs_bssgp_vty.c
@@ -181,35 +181,41 @@ DEFUN(logging_fltr_bvc,
"BVCI of the BVC to be filtered\n"
"BSSGP Virtual Connection Identifier (BVCI)\n")
{
- struct log_target *tgt = osmo_log_vty2tgt(vty);
+ struct log_target *tgt;
struct bssgp_bvc_ctx *bvc;
uint16_t nsei = atoi(argv[0]);
uint16_t bvci = atoi(argv[1]);
- if (!tgt)
+ log_tgt_mutex_lock();
+ tgt = osmo_log_vty2tgt(vty);
+ if (!tgt) {
+ log_tgt_mutex_unlock();
return CMD_WARNING;
+ }
bvc = btsctx_by_bvci_nsei(bvci, nsei);
if (!bvc) {
vty_out(vty, "No BVC by that identifier%s", VTY_NEWLINE);
+ log_tgt_mutex_unlock();
return CMD_WARNING;
}
log_set_bvc_filter(tgt, bvc);
+ log_tgt_mutex_unlock();
return CMD_SUCCESS;
}
int bssgp_vty_init(void)
{
- install_element_ve(&show_bssgp_cmd);
- install_element_ve(&show_bssgp_stats_cmd);
- install_element_ve(&show_bvc_cmd);
- install_element_ve(&logging_fltr_bvc_cmd);
- install_element_ve(&bvc_reset_cmd);
+ install_lib_element_ve(&show_bssgp_cmd);
+ install_lib_element_ve(&show_bssgp_stats_cmd);
+ install_lib_element_ve(&show_bvc_cmd);
+ install_lib_element_ve(&logging_fltr_bvc_cmd);
+ install_lib_element_ve(&bvc_reset_cmd);
- install_element(CFG_LOG_NODE, &logging_fltr_bvc_cmd);
+ install_lib_element(CFG_LOG_NODE, &logging_fltr_bvc_cmd);
- install_element(CONFIG_NODE, &cfg_bssgp_cmd);
+ install_lib_element(CONFIG_NODE, &cfg_bssgp_cmd);
install_node(&bssgp_node, config_write_bssgp);
return 0;
diff --git a/src/gb/gprs_ns.c b/src/gb/gprs_ns.c
index c77ebb37..a6ef6d47 100644
--- a/src/gb/gprs_ns.c
+++ b/src/gb/gprs_ns.c
@@ -51,7 +51,7 @@
*
* There can be multiple BSSGP virtual connections over one (group of) NSVC's. BSSGP will
* therefore identify the BSSGP virtual connection by a BVCI passed down to NS.
- * NS then has to firgure out which NSVC's are responsible for this BVCI.
+ * NS then has to figure out which NSVC's are responsible for this BVCI.
* Those mappings are administratively configured.
*
* This implementation has the following limitations:
@@ -318,7 +318,8 @@ struct gprs_nsvc *gprs_nsvc_create2(struct gprs_ns_inst *nsi, uint16_t nsvci,
return NULL;
}
- LOGP(DNS, LOGL_INFO, "NSVCI=%u Creating NS-VC\n", nsvci);
+ LOGP(DNS, LOGL_INFO, "NSVCI=%u Creating NS-VC with Signal weight %u, Data weight %u\n",
+ nsvci, sig_weight, data_weight);
nsvc = talloc_zero(nsi, struct gprs_nsvc);
if (!nsvc)
@@ -326,7 +327,7 @@ struct gprs_nsvc *gprs_nsvc_create2(struct gprs_ns_inst *nsi, uint16_t nsvci,
nsvc->nsvci = nsvci;
nsvc->nsvci_is_valid = 1;
/* before RESET procedure: BLOCKED and DEAD */
- if (nsi->bss_sns_fi)
+ if (nsi->bss_sns_fi || !nsi->nsip.use_reset_block_unblock)
ns_set_state(nsvc, 0);
else
ns_set_state(nsvc, NSE_S_BLOCKED);
@@ -346,12 +347,6 @@ struct gprs_nsvc *gprs_nsvc_create2(struct gprs_ns_inst *nsi, uint16_t nsvci,
return nsvc;
}
-/*! Old API for creating a NS-VC. Uses gprs_nsvc_create2 with fixed weights. */
-struct gprs_nsvc *gprs_nsvc_create(struct gprs_ns_inst *nsi, uint16_t nsvci)
-{
- return gprs_nsvc_create2(nsi, nsvci, 1, 1);
-}
-
/*! Delete given NS-VC
* \param[in] nsvc gprs_nsvc to be deleted
*/
@@ -624,7 +619,7 @@ int gprs_ns_tx_status(struct gprs_nsvc *nsvc, uint8_t cause,
return gprs_ns_tx(nsvc, msg);
}
-/*! Transmit a NS-BLOCK on a tiven NS-VC
+/*! Transmit a NS-BLOCK on a given NS-VC
* \param[in] nsvc NS-VC on which the NS-BLOCK is to be transmitted
* \param[in] cause Numeric NS Cause value
* \returns 0 in case of success
@@ -792,7 +787,7 @@ static void gprs_ns_timer_cb(void *data)
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]);
- if (!nsvc->nsi->bss_sns_fi) {
+ 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]);
} else
@@ -803,7 +798,7 @@ static void gprs_ns_timer_cb(void *data)
nsvc->nsi->timeout[NS_TOUT_TNS_ALIVE_RETRIES]);
ns_osmo_signal_dispatch(nsvc, S_NS_ALIVE_EXP, 0);
/* FIXME: should we send this signal in case of SNS? */
- if (!nsvc->nsi->bss_sns_fi)
+ if (!nsvc->nsi->bss_sns_fi && nsvc->nsi->nsip.use_reset_block_unblock)
ns_osmo_signal_dispatch(nsvc, S_NS_BLOCK, NS_CAUSE_NSVC_BLOCKED);
return;
}
@@ -1078,7 +1073,7 @@ int gprs_ns_tx_sns_size_ack(struct gprs_nsvc *nsvc, uint8_t *cause)
* \param[in] msg struct msgb to be trasnmitted
*
* This function obtains the NS-VC by the msgb_nsei(msg) and then checks
- * if the NS-VC is ALIVEV and not BLOCKED. After that, it adds a NS
+ * 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
@@ -1268,7 +1263,7 @@ static int gprs_ns_rx_reset(struct gprs_nsvc **nsvc, struct msgb *msg)
nsvci, (*nsvc)->nsvci,
gprs_ns_ll_str(*nsvc));
orig_nsvc = *nsvc;
- *nsvc = gprs_nsvc_create((*nsvc)->nsi, nsvci);
+ *nsvc = gprs_nsvc_create2((*nsvc)->nsi, nsvci, 1, 1);
(*nsvc)->nsei = nsei;
}
}
@@ -1690,7 +1685,7 @@ int gprs_ns_vc_create(struct gprs_ns_inst *nsi, struct msgb *msg,
* simply have changed addresses, or it is a SGSN */
existing_nsvc = gprs_nsvc_by_nsvci(nsi, nsvci);
if (!existing_nsvc) {
- *new_nsvc = gprs_nsvc_create(nsi, 0xffff);
+ *new_nsvc = gprs_nsvc_create2(nsi, 0xffff, 1, 1);
(*new_nsvc)->nsvci_is_valid = 0;
log_set_context(LOG_CTX_GB_NSVC, *new_nsvc);
gprs_ns_ll_copy(*new_nsvc, fallback_nsvc);
@@ -1757,8 +1752,12 @@ int gprs_ns_process_msg(struct gprs_ns_inst *nsi, struct msgb *msg,
* fine. */
if ((*nsvc)->state == NSE_S_BLOCKED)
rc = gprs_nsvc_reset((*nsvc), NS_CAUSE_PDU_INCOMP_PSTATE);
- else if (!((*nsvc)->state & NSE_S_RESET))
+ else if (!((*nsvc)->state & NSE_S_RESET)) {
+ /* if we're not alive, we cannot transmit the ACK; set ALIVE */
+ if (!((*nsvc)->state & NSE_S_ALIVE))
+ ns_mark_alive(*nsvc);
rc = gprs_ns_tx_alive_ack(*nsvc);
+ }
break;
case NS_PDUT_ALIVE_ACK:
ns_mark_alive(*nsvc);
@@ -1884,13 +1883,18 @@ static bool gprs_sns_fsm_registered = false;
*/
struct gprs_ns_inst *gprs_ns_instantiate(gprs_ns_cb_t *cb, void *ctx)
{
- struct gprs_ns_inst *nsi = talloc_zero(ctx, struct gprs_ns_inst);
+ struct gprs_ns_inst *nsi;
if (!gprs_sns_fsm_registered) {
- gprs_sns_init();
+ int rc = gprs_sns_init();
+ if (rc < 0)
+ return NULL;
gprs_sns_fsm_registered = true;
}
+ nsi = talloc_zero(ctx, struct gprs_ns_inst);
+ if (!nsi)
+ return NULL;
nsi->cb = cb;
INIT_LLIST_HEAD(&nsi->gprs_nsvcs);
nsi->timeout[NS_TOUT_TNS_BLOCK] = 3;
@@ -1904,11 +1908,16 @@ struct gprs_ns_inst *gprs_ns_instantiate(gprs_ns_cb_t *cb, void *ctx)
/* Create the dummy NSVC that we use for sending
* messages to non-existant/unknown NS-VC's */
- nsi->unknown_nsvc = gprs_nsvc_create(nsi, 0xfffe);
+ nsi->unknown_nsvc = gprs_nsvc_create2(nsi, 0xfffe, 1, 1);
nsi->unknown_nsvc->nsvci_is_valid = 0;
llist_del(&nsi->unknown_nsvc->list);
INIT_LLIST_HEAD(&nsi->unknown_nsvc->list);
+ /* By default we are in IPA compatible mode, that is we use NS-RESET, NS-BLOCK
+ * and NS-UNBLOCK procedures even for an IP/UDP based Gb interface, in violation
+ * of 3GPP TS 48.016. */
+ nsi->nsip.use_reset_block_unblock = true;
+
return nsi;
}
@@ -2140,7 +2149,7 @@ struct gprs_nsvc *gprs_ns_nsip_connect(struct gprs_ns_inst *nsi,
nsvc = gprs_nsvc_by_rem_addr(nsi, dest);
if (!nsvc)
- nsvc = gprs_nsvc_create(nsi, nsvci);
+ nsvc = gprs_nsvc_create2(nsi, nsvci, 1, 1);
nsvc->ip.bts_addr = *dest;
nsvc->nsei = nsei;
nsvc->remote_end_is_sgsn = 1;
diff --git a/src/gb/gprs_ns2.c b/src/gb/gprs_ns2.c
new file mode 100644
index 00000000..f65bea7a
--- /dev/null
+++ b/src/gb/gprs_ns2.c
@@ -0,0 +1,1097 @@
+/*! \file gprs_ns2.c
+ * 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-2018 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2016-2017,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/>.
+ *
+ */
+
+/*! \addtogroup libgb
+ * @{
+ *
+ * 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)
+ *
+ * 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
+ * as an intermediate shim betwen BSSGP and the underlying medium. It doesn't
+ * do much, apart from providing congestion notification and status indication.
+ *
+ * Terms:
+ *
+ * NS Network Service
+ * NSVC NS Virtual Connection
+ * NSEI NS Entity Identifier
+ * NSVL NS Virtual Link
+ * NSVLI NS Virtual Link Identifier
+ * BVC BSSGP Virtual Connection
+ * BVCI BSSGP Virtual Connection Identifier
+ * NSVCG NS Virtual Connection Goup
+ * Blocked NS-VC cannot be used for user traffic
+ * Alive Ability of a NS-VC to provide communication
+ *
+ * There can be multiple BSSGP virtual connections over one (group of) NSVC's. BSSGP will
+ * therefore identify the BSSGP virtual connection by a BVCI passed down to NS.
+ * NS then has to figure out which NSVC's are responsible for this BVCI.
+ * 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 */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdint.h>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+
+#include <osmocom/core/fsm.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/rate_ctr.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/core/sockaddr_str.h>
+#include <osmocom/core/stats.h>
+#include <osmocom/core/stat_item.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/gprs/gprs_msgb.h>
+#include <osmocom/gsm/prim.h>
+#include <osmocom/gsm/tlv.h>
+
+#include "gprs_ns2_internal.h"
+
+#define ns_set_state(ns_, st_) ns_set_state_with_log(ns_, st_, false, __FILE__, __LINE__)
+#define ns_set_remote_state(ns_, st_) ns_set_state_with_log(ns_, st_, true, __FILE__, __LINE__)
+#define ns_mark_blocked(ns_) ns_set_state(ns_, (ns_)->state | NSE_S_BLOCKED)
+#define ns_mark_unblocked(ns_) ns_set_state(ns_, (ns_)->state & (~NSE_S_BLOCKED));
+#define ns_mark_alive(ns_) ns_set_state(ns_, (ns_)->state | NSE_S_ALIVE)
+#define ns_mark_dead(ns_) ns_set_state(ns_, (ns_)->state & (~NSE_S_ALIVE));
+
+/* HACK: The NS_IE_IP_ADDR does not follow any known TLV rules.
+ * Since it's a hard ABI break to implement 16 bit tag with fixed length entries to workaround it,
+ * the parser will be called with ns_att_tlvdef1 and if it's failed with ns_att_tlvdef2.
+ * The TLV parser depends on 8bit tag in many places.
+ * The NS_IE_IP_ADDR is only valid for SNS_ACK SNS_ADD and SNS_DELETE.
+ */
+static const struct tlv_definition ns_att_tlvdef1 = {
+ .def = {
+ [NS_IE_CAUSE] = { TLV_TYPE_TvLV, 0 },
+ [NS_IE_VCI] = { TLV_TYPE_TvLV, 0 },
+ [NS_IE_PDU] = { TLV_TYPE_TvLV, 0 },
+ [NS_IE_BVCI] = { TLV_TYPE_TvLV, 0 },
+ [NS_IE_NSEI] = { TLV_TYPE_TvLV, 0 },
+ [NS_IE_IPv4_LIST] = { TLV_TYPE_TvLV, 0 },
+ [NS_IE_IPv6_LIST] = { TLV_TYPE_TvLV, 0 },
+ [NS_IE_MAX_NR_NSVC] = { TLV_TYPE_FIXED, 2 },
+ [NS_IE_IPv4_EP_NR] = { TLV_TYPE_FIXED, 2 },
+ [NS_IE_IPv6_EP_NR] = { TLV_TYPE_FIXED, 2 },
+ [NS_IE_RESET_FLAG] = { TLV_TYPE_TV, 0 },
+ /* NS_IE_IP_ADDR in the IPv4 version */
+ [NS_IE_IP_ADDR] = { TLV_TYPE_FIXED, 5 },
+ },
+};
+
+static const struct tlv_definition ns_att_tlvdef2 = {
+ .def = {
+ [NS_IE_CAUSE] = { TLV_TYPE_TvLV, 0 },
+ [NS_IE_VCI] = { TLV_TYPE_TvLV, 0 },
+ [NS_IE_PDU] = { TLV_TYPE_TvLV, 0 },
+ [NS_IE_BVCI] = { TLV_TYPE_TvLV, 0 },
+ [NS_IE_NSEI] = { TLV_TYPE_TvLV, 0 },
+ [NS_IE_IPv4_LIST] = { TLV_TYPE_TvLV, 0 },
+ [NS_IE_IPv6_LIST] = { TLV_TYPE_TvLV, 0 },
+ [NS_IE_MAX_NR_NSVC] = { TLV_TYPE_FIXED, 2 },
+ [NS_IE_IPv4_EP_NR] = { TLV_TYPE_FIXED, 2 },
+ [NS_IE_IPv6_EP_NR] = { TLV_TYPE_FIXED, 2 },
+ [NS_IE_RESET_FLAG] = { TLV_TYPE_TV, 0 },
+ /* NS_IE_IP_ADDR in the IPv6 version */
+ [NS_IE_IP_ADDR] = { TLV_TYPE_FIXED, 17 },
+ },
+};
+
+
+/* Section 10.3.2, Table 13 */
+static const struct value_string ns2_cause_str[] = {
+ { NS_CAUSE_TRANSIT_FAIL, "Transit network failure" },
+ { NS_CAUSE_OM_INTERVENTION, "O&M intervention" },
+ { NS_CAUSE_EQUIP_FAIL, "Equipment failure" },
+ { NS_CAUSE_NSVC_BLOCKED, "NS-VC blocked" },
+ { NS_CAUSE_NSVC_UNKNOWN, "NS-VC unknown" },
+ { NS_CAUSE_BVCI_UNKNOWN, "BVCI unknown" },
+ { NS_CAUSE_SEM_INCORR_PDU, "Semantically incorrect PDU" },
+ { NS_CAUSE_PDU_INCOMP_PSTATE, "PDU not compatible with protocol state" },
+ { NS_CAUSE_PROTO_ERR_UNSPEC, "Protocol error, unspecified" },
+ { NS_CAUSE_INVAL_ESSENT_IE, "Invalid essential IE" },
+ { NS_CAUSE_MISSING_ESSENT_IE, "Missing essential IE" },
+ { NS_CAUSE_INVAL_NR_IPv4_EP, "Invalid Number of IPv4 Endpoints" },
+ { NS_CAUSE_INVAL_NR_IPv6_EP, "Invalid Number of IPv6 Endpoints" },
+ { NS_CAUSE_INVAL_NR_NS_VC, "Invalid Number of NS-VCs" },
+ { NS_CAUSE_INVAL_WEIGH, "Invalid Weights" },
+ { NS_CAUSE_UNKN_IP_EP, "Unknown IP Endpoint" },
+ { NS_CAUSE_UNKN_IP_ADDR, "Unknown IP Address" },
+ { NS_CAUSE_UNKN_IP_TEST_FAILED, "IP Test Failed" },
+ { 0, NULL }
+};
+
+/*! Obtain a human-readable string for NS cause value */
+const char *gprs_ns2_cause_str(int cause)
+{
+ enum ns_cause _cause = cause;
+ return get_value_string(ns2_cause_str, _cause);
+}
+
+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_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,
+ .class_id = OSMO_STATS_CLASS_PEER,
+};
+
+
+static const struct osmo_stat_item_desc nsvc_stat_description[] = {
+ { "alive.delay", "ALIVE response time ", "ms", 16, 0 },
+};
+
+static const struct osmo_stat_item_group_desc nsvc_statg_desc = {
+ .group_name_prefix = "ns.nsvc",
+ .group_description = "NSVC Peer Statistics",
+ .num_items = ARRAY_SIZE(nsvc_stat_description),
+ .item_desc = nsvc_stat_description,
+ .class_id = OSMO_STATS_CLASS_PEER,
+};
+
+/*! string-format a given NS-VC into a user-supplied buffer.
+ * \param[in] buf user-allocated output buffer
+ * \param[in] buf_len size of user-allocated output buffer in bytes
+ * \param[in] nsvc NS-VC to be string-formatted
+ * \return pointer to buf on success; NULL on error */
+char *gprs_ns2_ll_str_buf(char *buf, size_t buf_len, struct gprs_ns2_vc *nsvc)
+{
+ const struct osmo_sockaddr *local;
+ const struct osmo_sockaddr *remote;
+ struct osmo_sockaddr_str local_str;
+ struct osmo_sockaddr_str remote_str;
+
+ if (!buf_len)
+ return NULL;
+
+ switch (nsvc->ll) {
+ case GPRS_NS_LL_UDP:
+ if (!gprs_ns2_is_ip_bind(nsvc->bind)) {
+ buf[0] = '\0';
+ return buf;
+ }
+
+ local = gprs_ns2_ip_bind_sockaddr(nsvc->bind);
+ remote = gprs_ns2_ip_vc_remote(nsvc);
+ if (osmo_sockaddr_str_from_sockaddr(&local_str, &local->u.sas))
+ strcpy(local_str.ip, "invalid");
+ if (osmo_sockaddr_str_from_sockaddr(&remote_str, &remote->u.sas))
+ strcpy(remote_str.ip, "invalid");
+
+ if (nsvc->nsvci_is_valid)
+ snprintf(buf, buf_len, "udp)[%s]:%u<%u>[%s]:%u",
+ local_str.ip, local_str.port,
+ nsvc->nsvci,
+ remote_str.ip, remote_str.port);
+ else
+ snprintf(buf, buf_len, "udp)[%s]:%u<>[%s]:%u",
+ local_str.ip, local_str.port,
+ remote_str.ip, remote_str.port);
+ break;
+ case GPRS_NS_LL_FR_GRE:
+ snprintf(buf, buf_len, "frgre)");
+ break;
+ case GPRS_NS_LL_E1:
+ snprintf(buf, buf_len, "e1)");
+ break;
+ default:
+ buf[0] = '\0';
+ break;
+ }
+
+ buf[buf_len - 1] = '\0';
+
+ return buf;
+}
+
+/* udp is the longest: udp)[IP6]:65536<65536>[IP6]:65536 */
+#define NS2_LL_MAX_STR 4+2*(INET6_ADDRSTRLEN+9)+8
+
+/*! string-format a given NS-VC to a thread-local static buffer.
+ * \param[in] nsvc NS-VC to be string-formatted
+ * \return pointer to the string on success; NULL on error */
+const char *gprs_ns2_ll_str(struct gprs_ns2_vc *nsvc)
+{
+ static __thread char buf[NS2_LL_MAX_STR];
+ return gprs_ns2_ll_str_buf(buf, sizeof(buf), nsvc);
+}
+
+/*! string-format a given NS-VC to a dynamically allocated string.
+ * \param[in] ctx talloc context from which to allocate
+ * \param[in] nsvc NS-VC to be string-formatted
+ * \return pointer to the string on success; NULL on error */
+char *gprs_ns2_ll_str_c(const void *ctx, struct gprs_ns2_vc *nsvc)
+{
+ char *buf = talloc_size(ctx, NS2_LL_MAX_STR);
+ if (!buf)
+ return buf;
+ return gprs_ns2_ll_str_buf(buf, NS2_LL_MAX_STR, 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;
+ uint16_t bvci, nsei;
+ uint8_t sducontrol = 0;
+
+ if (oph->sap != SAP_NS)
+ return -EINVAL;
+
+ 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->msg)
+ return -EINVAL;
+
+ 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;
+
+ nsvc = tmp;
+ }
+
+ /* TODO: send a status primitive back */
+ if (!nsvc)
+ return 0;
+
+ if (nsp->u.unitdata.change == NS_ENDPOINT_REQUEST_CHANGE)
+ sducontrol = 1;
+ else if (nsp->u.unitdata.change == NS_ENDPOINT_CONFIRM_CHANGE)
+ sducontrol = 2;
+
+ return ns2_tx_unit_data(nsvc, bvci, sducontrol, oph->msg);
+}
+
+/*! Send a STATUS.ind primitive to the specified NS instance user.
+ * \param[in] nsi NS instance on which we operate
+ * \param[in] nsei NSEI to which the statue relates
+ * \param[in] bvci BVCI to which the status relates
+ * \param[in] cause The cause of the status */
+void ns2_prim_status_ind(struct gprs_ns2_nse *nse,
+ uint16_t bvci,
+ enum gprs_ns2_affecting_cause cause)
+{
+ struct osmo_gprs_ns2_prim nsp = {};
+ nsp.nsei = nse->nsei;
+ nsp.bvci = bvci;
+ nsp.u.status.cause = cause;
+ nsp.u.status.transfer = -1;
+ nsp.u.status.first = nse->first;
+ nsp.u.status.persistent = nse->persistent;
+ osmo_prim_init(&nsp.oph, SAP_NS, PRIM_NS_STATUS,
+ PRIM_OP_INDICATION, NULL);
+ nse->nsi->cb(&nsp.oph, nse->nsi->cb_data);
+}
+
+/*! Allocate a NS-VC within the given bind + 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)
+ * \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 *nsvc = talloc_zero(bind, struct gprs_ns2_vc);
+
+ if (!nsvc)
+ return NULL;
+
+ nsvc->bind = bind;
+ nsvc->nse = nse;
+ nsvc->mode = bind->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);
+ if (!nsvc->ctrg) {
+ goto err;
+ }
+ nsvc->statg = osmo_stat_item_group_alloc(nsvc, &nsvc_statg_desc, bind->nsi->rate_ctr_idx);
+ if (!nsvc->statg)
+ goto err_group;
+ if (!gprs_ns2_vc_fsm_alloc(nsvc, NULL, initiater))
+ goto err_statg;
+
+ bind->nsi->rate_ctr_idx++;
+
+ llist_add(&nsvc->list, &nse->nsvc);
+ llist_add(&nsvc->blist, &bind->nsvc);
+
+ return nsvc;
+
+err_statg:
+ osmo_stat_item_group_free(nsvc->statg);
+err_group:
+ rate_ctr_group_free(nsvc->ctrg);
+err:
+ talloc_free(nsvc);
+
+ return NULL;
+}
+
+/*! Destroy/release given NS-VC.
+ * \param[in] nsvc NS-VC to destroy */
+void gprs_ns2_free_nsvc(struct gprs_ns2_vc *nsvc)
+{
+ if (!nsvc)
+ return;
+
+ ns2_prim_status_ind(nsvc->nse, 0, NS_AFF_CAUSE_VC_FAILURE);
+
+ llist_del(&nsvc->list);
+ llist_del(&nsvc->blist);
+
+ /* notify nse this nsvc is unavailable */
+ ns2_nse_notify_unblocked(nsvc, false);
+
+ /* check if sns is using this VC */
+ ns2_sns_free_nsvc(nsvc);
+ osmo_fsm_inst_term(nsvc->fi, OSMO_FSM_TERM_REQUEST, NULL);
+
+ /* let the driver/bind clean up it's internal state */
+ if (nsvc->priv && nsvc->bind->free_vc)
+ nsvc->bind->free_vc(nsvc);
+
+ osmo_stat_item_group_free(nsvc->statg);
+ rate_ctr_group_free(nsvc->ctrg);
+
+ talloc_free(nsvc);
+}
+
+/*! Allocate a message buffer for use with the NS2 stack. */
+struct msgb *gprs_ns2_msgb_alloc(void)
+{
+ struct msgb *msg = msgb_alloc_headroom(NS_ALLOC_SIZE, NS_ALLOC_HEADROOM,
+ "GPRS/NS");
+ if (!msg) {
+ LOGP(DLNS, LOGL_ERROR, "Failed to allocate NS message of size %d\n",
+ NS_ALLOC_SIZE);
+ }
+ return msg;
+}
+
+/*! Create a status message to be sent over a new connection.
+ * \param[in] orig_msg the original message
+ * \param[in] tp TLVP parsed of the original message
+ * \param[out] reject callee-allocated message buffer of the generated NS-STATUS
+ * \param[in] cause Cause for the rejection
+ * \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 gprs_ns_hdr *nsh;
+ bool have_vci = false;
+ uint8_t _cause = cause;
+ uint16_t nsei = 0;
+
+ if (!msg)
+ return -ENOMEM;
+
+ if (TLVP_PRESENT(tp, NS_IE_NSEI)) {
+ nsei = tlvp_val16be(tp, NS_IE_NSEI);
+
+ LOGP(DLNS, LOGL_NOTICE, "NSEI=%u Rejecting message without NSVCI. Tx NS STATUS (cause=%s)\n",
+ nsei, 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);
+ have_vci = TLVP_PRESENT(tp, NS_IE_VCI);
+
+ /* Section 9.2.7.1: Static conditions for NS-VCI */
+ if (cause == NS_CAUSE_NSVC_BLOCKED ||
+ cause == NS_CAUSE_NSVC_UNKNOWN) {
+ if (!have_vci) {
+ msgb_free(msg);
+ return -EINVAL;
+ }
+
+ msgb_tvlv_put(msg, NS_IE_VCI, 2, TLVP_VAL(tp, NS_IE_VCI));
+ }
+
+ /* Section 9.2.7.2: Static conditions for NS PDU */
+ switch (cause) {
+ 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);
+ break;
+ default:
+ break;
+ }
+
+ *reject = msg;
+ return 0;
+}
+
+/*! Resolve a NS Entity based on its NSEI.
+ * \param[in] nsi NS Instance in which we do the look-up
+ * \param[in] nsei NSEI to look up
+ * \return NS Entity in successful case; NULL if none found */
+struct gprs_ns2_nse *gprs_ns2_nse_by_nsei(struct gprs_ns2_inst *nsi, uint16_t nsei)
+{
+ struct gprs_ns2_nse *nse;
+
+ llist_for_each_entry(nse, &nsi->nse, list) {
+ if (nse->nsei == nsei)
+ return nse;
+ }
+
+ return NULL;
+}
+
+/*! Resolve a NS-VC Entity based on its NS-VCI.
+ * \param[in] nsi NS Instance in which we do the look-up
+ * \param[in] nsvci NS-VCI to look up
+ * \return NS-VC Entity in successful case; NULL if none found */
+struct gprs_ns2_vc *gprs_ns2_nsvc_by_nsvci(struct gprs_ns2_inst *nsi, uint16_t nsvci)
+{
+ struct gprs_ns2_nse *nse;
+ struct gprs_ns2_vc *nsvc;
+
+ llist_for_each_entry(nse, &nsi->nse, list) {
+ llist_for_each_entry(nsvc, &nse->nsvc, list) {
+ if (nsvc->nsvci_is_valid && nsvc->nsvci == nsvci)
+ return nsvc;
+ }
+ }
+
+ return NULL;
+}
+
+/*! 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)
+{
+ 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);
+ return nse;
+ }
+
+ nse = talloc_zero(nsi, struct gprs_ns2_nse);
+ if (!nse)
+ return NULL;
+
+ nse->nsei = nsei;
+ nse->nsi = nsi;
+ nse->first = true;
+ llist_add(&nse->list, &nsi->nse);
+ INIT_LLIST_HEAD(&nse->nsvc);
+
+ return nse;
+}
+
+/*! Return the NSEI
+ * \param[in] nse NS Entity
+ * \return the nsei.
+ */
+uint16_t gprs_ns2_nse_nsei(struct gprs_ns2_nse *nse)
+{
+ return nse->nsei;
+}
+
+/*! Destroy given NS Entity.
+ * \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)
+ return;
+
+ llist_for_each_entry_safe(nsvc, tmp, &nse->nsvc, list) {
+ gprs_ns2_free_nsvc(nsvc);
+ }
+
+ ns2_prim_status_ind(nse, 0, NS_AFF_CAUSE_FAILURE);
+
+ 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;
+
+ llist_for_each_entry_safe(nse, ntmp, &nsi->nse, list) {
+ gprs_ns2_free_nse(nse);
+ }
+}
+
+static inline int ns2_tlv_parse(struct tlv_parsed *dec,
+ const uint8_t *buf, int buf_len, uint8_t lv_tag,
+ uint8_t lv_tag2)
+{
+ /* workaround for NS_IE_IP_ADDR not following any known TLV rules.
+ * See comment of ns_att_tlvdef1. */
+ int rc = tlv_parse(dec, &ns_att_tlvdef1, buf, buf_len, lv_tag, lv_tag2);
+ if (rc < 0)
+ return tlv_parse(dec, &ns_att_tlvdef2, buf, buf_len, lv_tag, lv_tag2);
+ return rc;
+}
+
+
+/*! 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] 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)
+{
+ 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;
+ uint16_t nsvci;
+ uint16_t nsei;
+
+ int rc;
+
+ if (msg->len < sizeof(struct gprs_ns_hdr))
+ return GPRS_NS2_CS_ERROR;
+
+ if (nsh->pdu_type == 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) {
+ /* 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) {
+ /* 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;
+ }
+ } else { /* NS2_VC_MODE_ALIVE */
+ 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;
+ }
+
+ rc = ns2_tlv_parse(&tp, nsh->data,
+ msgb_l2len(msg) - sizeof(*nsh), 0, 0);
+ if (rc < 0) {
+ LOGP(DLNS, LOGL_ERROR, "Rx NS RESET Error %d during "
+ "TLV Parse\n", rc);
+ /* TODO: send invalid message back */
+ return GPRS_NS2_CS_REJECTED;
+ }
+
+ if (!TLVP_PRESENT(&tp, NS_IE_CAUSE) ||
+ !TLVP_PRESENT(&tp, NS_IE_VCI) || !TLVP_PRESENT(&tp, NS_IE_NSEI)) {
+ 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;
+ }
+
+ /* find or create NSE */
+ nsei = tlvp_val16be(&tp, NS_IE_NSEI);
+ nse = gprs_ns2_nse_by_nsei(bind->nsi, nsei);
+ if (!nse) {
+ if (!bind->nsi->create_nse) {
+ return GPRS_NS2_CS_SKIPPED;
+ }
+
+ nse = gprs_ns2_create_nse(bind->nsi, nsei);
+ if (!nse) {
+ return GPRS_NS2_CS_ERROR;
+ }
+ }
+
+ nsvc = ns2_vc_alloc(bind, nse, false);
+ if (!nsvc)
+ return GPRS_NS2_CS_SKIPPED;
+
+ nsvc->ll = GPRS_NS_LL_UDP;
+
+ nsvci = tlvp_val16be(&tp, NS_IE_VCI);
+ nsvc->nsvci = nsvci;
+ nsvc->nsvci_is_valid = true;
+
+ *success = nsvc;
+
+ return GPRS_NS2_CS_CREATED;
+}
+
+/*! Create, and connect an inactive, new IP-based NS-VC
+ * \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] nse NS Entity in which the NS-VC is to be created
+ * \param[in] nsvci is only required when bind->vc_mode == NS2_VC_MODE_BLOCKRESET
+ * \return pointer to newly-allocated, connected and inactive NS-VC; NULL on error */
+struct gprs_ns2_vc *gprs_ns2_ip_connect_inactive(struct gprs_ns2_vc_bind *bind,
+ const struct osmo_sockaddr *remote,
+ struct gprs_ns2_nse *nse,
+ uint16_t nsvci)
+{
+ struct gprs_ns2_vc *nsvc;
+
+ nsvc = gprs_ns2_ip_bind_connect(bind, nse, remote);
+ if (!nsvc)
+ return NULL;
+
+ if (nsvc->mode == NS2_VC_MODE_BLOCKRESET) {
+ nsvc->nsvci = nsvci;
+ nsvc->nsvci_is_valid = true;
+ }
+
+ return nsvc;
+}
+
+/*! Create, connect and activate a new IP-based NS-VC
+ * \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] nse NS Entity in which the NS-VC is to be created
+ * \param[in] nsvci is only required when bind->vc_mode == NS2_VC_MODE_BLOCKRESET
+ * \return pointer to newly-allocated, connected and activated NS-VC; NULL on error */
+struct gprs_ns2_vc *gprs_ns2_ip_connect(struct gprs_ns2_vc_bind *bind,
+ const struct osmo_sockaddr *remote,
+ struct gprs_ns2_nse *nse,
+ uint16_t nsvci)
+{
+ struct gprs_ns2_vc *nsvc;
+ nsvc = gprs_ns2_ip_connect_inactive(bind, remote, nse, nsvci);
+ if (!nsvc)
+ return NULL;
+
+ gprs_ns2_vc_fsm_start(nsvc);
+
+ return nsvc;
+}
+
+/*! Create, connect and activate a new IP-based NS-VC
+ * \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
+ * \param[in] nsvci is only required when bind->vc_mode == NS2_VC_MODE_BLOCKRESET
+ * \return pointer to newly-allocated, connected and activated NS-VC; NULL on error */
+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)
+{
+ struct gprs_ns2_nse *nse = gprs_ns2_nse_by_nsei(bind->nsi, nsei);
+
+ if (!nse) {
+ nse = gprs_ns2_create_nse(bind->nsi, nsei);
+ if (!nse)
+ return NULL;
+ }
+
+ 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
+ * \return NS-VC matching sockaddr; NULL if none found */
+struct gprs_ns2_vc *gprs_ns2_nsvc_by_sockaddr_nse(struct gprs_ns2_nse *nse,
+ const struct osmo_sockaddr *sockaddr)
+{
+ struct gprs_ns2_vc *nsvc;
+ const struct osmo_sockaddr *remote;
+
+ OSMO_ASSERT(nse);
+ OSMO_ASSERT(sockaddr);
+
+ llist_for_each_entry(nsvc, &nse->nsvc, list) {
+ remote = gprs_ns2_ip_vc_remote(nsvc);
+ if (!osmo_sockaddr_cmp(sockaddr, remote))
+ return nsvc;
+ }
+
+ return NULL;
+}
+
+/*!
+ * Iterate over all nsvc of a NS Entity and call the callback.
+ * If the callback returns < 0 it aborts the loop and returns the callback return code.
+ * \param[in] nse NS Entity to iterate over all nsvcs
+ * \param[in] cb the callback to call
+ * \param[inout] cb_data the private data of the callback
+ * \return 0 if the loop completes. If a callback returns < 0 it will returns this value.
+ */
+int gprs_ns2_nse_foreach_nsvc(struct gprs_ns2_nse *nse, gprs_ns2_foreach_nsvc_cb cb, void *cb_data)
+{
+ struct gprs_ns2_vc *nsvc, *tmp;
+ int rc = 0;
+ llist_for_each_entry_safe(nsvc, tmp, &nse->nsvc, list) {
+ rc = cb(nsvc, cb_data);
+ if (rc < 0)
+ return rc;
+ }
+
+ return 0;
+}
+
+
+
+/*! 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!
+ * \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;
+ int rc = 0;
+
+ if (msg->len < sizeof(struct gprs_ns_hdr))
+ return -EINVAL;
+
+ switch (nsh->pdu_type) {
+ case SNS_PDUT_CONFIG:
+ /* one additional byte ('end flag') before the TLV part starts */
+ rc = ns2_tlv_parse(&tp, nsh->data+1,
+ 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;
+ }
+ /* All sub-network service related message types */
+ rc = gprs_ns2_sns_rx(nsvc, msg, &tp);
+ break;
+ case SNS_PDUT_ACK:
+ case SNS_PDUT_ADD:
+ case SNS_PDUT_CHANGE_WEIGHT:
+ case SNS_PDUT_DELETE:
+ /* weird layout: NSEI TLV, then value-only transaction IE, then TLV again */
+ rc = ns2_tlv_parse(&tp, nsh->data+5,
+ 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;
+ }
+ 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;
+ case SNS_PDUT_CONFIG_ACK:
+ case SNS_PDUT_SIZE:
+ case SNS_PDUT_SIZE_ACK:
+ 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 in %s\n", msgb_hexdump(msg));
+ return rc;
+ }
+ /* All sub-network service related message types */
+ rc = gprs_ns2_sns_rx(nsvc, msg, &tp);
+ break;
+
+ case NS_PDUT_UNITDATA:
+ rc = gprs_ns2_vc_rx(nsvc, msg, NULL);
+ break;
+ 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);
+ return rc;
+ }
+ rc = gprs_ns2_vc_rx(nsvc, msg, &tp);
+ break;
+ }
+
+ return rc;
+}
+
+/*! 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;
+
+ if (unblocked == nse->alive)
+ return;
+
+ if (unblocked) {
+ /* this is the first unblocked NSVC on an unavailable NSE */
+ nse->alive = true;
+ ns2_prim_status_ind(nse, 0, NS_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;
+ }
+ }
+
+ /* nse became unavailable */
+ nse->alive = false;
+ ns2_prim_status_ind(nse, 0, NS_AFF_CAUSE_FAILURE);
+}
+
+/*! Create a new GPRS NS instance
+ * \param[in] ctx a talloc context to allocate NS instance from
+ * \param[in] cb Call-back function for dispatching primitives to the user
+ * \param[in] cb_data transparent user data passed to Call-back
+ * \returns dynamically allocated gprs_ns_inst; NULL on error */
+struct gprs_ns2_inst *gprs_ns2_instantiate(void *ctx, osmo_prim_cb cb, void *cb_data)
+{
+ struct gprs_ns2_inst *nsi;
+
+ nsi = talloc_zero(ctx, struct gprs_ns2_inst);
+ if (!nsi)
+ return NULL;
+
+ nsi->cb = cb;
+ nsi->cb_data = cb_data;
+ INIT_LLIST_HEAD(&nsi->binding);
+ INIT_LLIST_HEAD(&nsi->nse);
+
+ nsi->timeout[NS_TOUT_TNS_BLOCK] = 3;
+ nsi->timeout[NS_TOUT_TNS_BLOCK_RETRIES] = 3;
+ nsi->timeout[NS_TOUT_TNS_RESET] = 3;
+ nsi->timeout[NS_TOUT_TNS_RESET_RETRIES] = 3;
+ nsi->timeout[NS_TOUT_TNS_TEST] = 30;
+ nsi->timeout[NS_TOUT_TNS_ALIVE] = 3;
+ nsi->timeout[NS_TOUT_TNS_ALIVE_RETRIES] = 10;
+ nsi->timeout[NS_TOUT_TSNS_PROV] = 3; /* 1..10 */
+
+ return nsi;
+}
+
+/*! Destroy a NS Instance (including all its NSEs, binds, ...).
+ * \param[in] nsi NS instance to destroy */
+void gprs_ns2_free(struct gprs_ns2_inst *nsi)
+{
+ if (!nsi)
+ return;
+
+ gprs_ns2_free_nses(nsi);
+ gprs_ns2_free_binds(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)
+{
+ struct gprs_ns2_vc *nsvc;
+ OSMO_ASSERT(nse);
+
+ llist_for_each_entry(nsvc, &nse->nsvc, list) {
+ if (nsvc->sns_only)
+ continue;
+
+ gprs_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)
+ return;
+
+ llist_for_each_entry_safe(nsvc, tmp, &bind->nsvc, blist) {
+ gprs_ns2_free_nsvc(nsvc);
+ }
+
+ if (bind->driver->free_bind)
+ bind->driver->free_bind(bind);
+
+ llist_del(&bind->list);
+ talloc_free(bind);
+}
+
+void gprs_ns2_free_binds(struct gprs_ns2_inst *nsi)
+{
+ struct gprs_ns2_vc_bind *bind, *tbind;
+
+ llist_for_each_entry_safe(bind, tbind, &nsi->binding, list) {
+ gprs_ns2_free_bind(bind);
+ }
+}
+/*! @} */
diff --git a/src/gb/gprs_ns2_frgre.c b/src/gb/gprs_ns2_frgre.c
new file mode 100644
index 00000000..cd478d6e
--- /dev/null
+++ b/src/gb/gprs_ns2_frgre.c
@@ -0,0 +1,600 @@
+/*! \file gprs_ns2_frgre.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-2010,2014,2017 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 <osmocom/core/byteswap.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/gprs/gprs_ns2.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
+
+struct gre_hdr {
+ uint16_t flags;
+ uint16_t ptype;
+} __attribute__ ((packed));
+
+#if defined(__FreeBSD__) || defined(__APPLE__) || defined(__CYGWIN__)
+/**
+ * On BSD the IPv4 struct is called struct ip and instead of iXX
+ * the members are called ip_XX. One could change this code to use
+ * struct ip but that would require to define _BSD_SOURCE and that
+ * might have other complications. Instead make sure struct iphdr
+ * is present on FreeBSD. The below is taken from GLIBC.
+ *
+ * The GNU C Library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ */
+struct iphdr
+ {
+#if BYTE_ORDER == LITTLE_ENDIAN
+ unsigned int ihl:4;
+ unsigned int version:4;
+#elif BYTE_ORDER == BIG_ENDIAN
+ unsigned int version:4;
+ unsigned int ihl:4;
+#endif
+ u_int8_t tos;
+ u_int16_t tot_len;
+ u_int16_t id;
+ u_int16_t frag_off;
+ u_int8_t ttl;
+ u_int8_t protocol;
+ u_int16_t check;
+ u_int32_t saddr;
+ u_int32_t daddr;
+ /*The options start here. */
+ };
+#endif
+
+
+static void free_bind(struct gprs_ns2_vc_bind *bind);
+static inline int frgre_sendmsg(struct gprs_ns2_vc_bind *bind,
+ struct msgb *msg,
+ struct osmo_sockaddr *dest);
+
+struct gprs_ns2_vc_driver vc_driver_frgre = {
+ .name = "GB frame relay over GRE",
+ .free_bind = free_bind,
+};
+
+struct priv_bind {
+ struct osmo_fd fd;
+ struct osmo_sockaddr addr;
+ uint16_t dlci;
+ int dscp;
+};
+
+struct priv_vc {
+ struct osmo_sockaddr remote;
+ uint16_t dlci;
+};
+
+static void free_vc(struct gprs_ns2_vc *nsvc)
+{
+ OSMO_ASSERT(nsvc);
+
+ if (!nsvc->priv)
+ return;
+
+ talloc_free(nsvc->priv);
+ nsvc->priv = NULL;
+}
+
+
+/*! 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;
+
+ if (!bind)
+ return;
+
+ priv = bind->priv;
+
+ OSMO_ASSERT(llist_empty(&bind->nsvc));
+
+ osmo_fd_close(&priv->fd);
+ talloc_free(priv);
+}
+
+static struct priv_vc *frgre_alloc_vc(struct gprs_ns2_vc_bind *bind,
+ struct gprs_ns2_vc *nsvc,
+ struct osmo_sockaddr *remote,
+ uint16_t dlci)
+{
+ struct priv_vc *priv = talloc_zero(bind, struct priv_vc);
+ if (!priv)
+ return NULL;
+
+ nsvc->priv = priv;
+ priv->remote = *remote;
+ priv->dlci = dlci;
+
+ return priv;
+}
+
+static int handle_rx_gre_ipv6(struct osmo_fd *bfd, struct msgb *msg,
+ struct ip6_hdr *ip6hdr, struct gre_hdr *greh)
+{
+ /* RFC 7676 IPv6 Support for Generic Routing Encapsulation (GRE) */
+ struct gprs_ns2_vc_bind *bind = bfd->data;
+ struct priv_bind *priv = bind->priv;
+ int gre_payload_len;
+ struct ip6_hdr *inner_ip6h;
+ struct gre_hdr *inner_greh;
+ struct sockaddr_in6 daddr;
+ struct in6_addr ia6;
+
+ gre_payload_len = msg->len - (sizeof(*ip6hdr) + sizeof(*greh));
+
+ 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");
+ 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");
+ 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");
+ 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");
+ return -EIO;
+ }
+
+ /* Actually send the response back */
+
+ daddr.sin6_family = AF_INET6;
+ daddr.sin6_addr = inner_ip6h->ip6_dst;
+ daddr.sin6_port = IPPROTO_GRE;
+
+ 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);
+
+ /* why does it reduce the gre_payload_len by the ipv6 header?
+ * make it similiar to ipv4 even this seems to be wrong */
+ return sendto(priv->fd.fd, inner_greh,
+ gre_payload_len - sizeof(*inner_ip6h), 0,
+ (struct sockaddr *)&daddr, sizeof(daddr));
+}
+
+/* IPv4 messages inside the GRE tunnel might be GRE keepalives */
+static int handle_rx_gre_ipv4(struct osmo_fd *bfd, struct msgb *msg,
+ struct iphdr *iph, struct gre_hdr *greh)
+{
+ struct gprs_ns2_vc_bind *bind = bfd->data;
+ struct priv_bind *priv = bind->priv;
+ int gre_payload_len;
+ struct iphdr *inner_iph;
+ struct gre_hdr *inner_greh;
+ struct sockaddr_in daddr;
+ struct in_addr ia;
+
+ gre_payload_len = msg->len - (iph->ihl*4 + sizeof(*greh));
+
+ 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");
+ return -EIO;
+ }
+
+ if (inner_iph->saddr != iph->daddr ||
+ inner_iph->daddr != iph->saddr) {
+ LOGP(DLNS, 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");
+ 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");
+ return -EIO;
+ }
+
+ /* Actually send the response back */
+
+ daddr.sin_family = AF_INET;
+ daddr.sin_addr.s_addr = inner_iph->daddr;
+ daddr.sin_port = IPPROTO_GRE;
+
+ ia.s_addr = iph->saddr;
+ LOGP(DLNS, 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,
+ gre_payload_len - inner_iph->ihl*4, 0,
+ (struct sockaddr *)&daddr, sizeof(daddr));
+}
+
+static struct msgb *read_nsfrgre_msg(struct osmo_fd *bfd, int *error,
+ struct osmo_sockaddr *saddr, uint16_t *dlci)
+{
+ struct msgb *msg = msgb_alloc(NS_ALLOC_SIZE, "Gb/NS/FR/GRE Rx");
+ int ret = 0;
+ socklen_t saddr_len = sizeof(*saddr);
+ struct iphdr *iph = NULL;
+ struct ip6_hdr *ip6h = NULL;
+ size_t ip46hdr;
+ struct gre_hdr *greh;
+ uint8_t *frh;
+
+ if (!msg) {
+ *error = -ENOMEM;
+ return NULL;
+ }
+
+ 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));
+ *error = ret;
+ goto out_err;
+ } else if (ret == 0) {
+ *error = ret;
+ goto out_err;
+ }
+
+ msgb_put(msg, ret);
+
+ /* we've received a raw packet including the IPv4 or IPv6 header */
+ switch (saddr->u.sa.sa_family) {
+ case AF_INET:
+ ip46hdr = sizeof(struct iphdr);
+ break;
+ case AF_INET6:
+ ip46hdr = sizeof(struct ip6_hdr);
+ default:
+ *error = -EIO;
+ goto out_err;
+ break;
+ }
+
+ /* 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);
+ *error = -EIO;
+ goto out_err;
+ }
+
+ switch (saddr->u.sa.sa_family) {
+ 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);
+ *error = -EIO;
+ goto out_err;
+ }
+ break;
+ case AF_INET6:
+ ip6h = (struct ip6_hdr *) msg->data;
+ break;
+ }
+
+ greh = (struct gre_hdr *) (msg->data + iph->ihl*4);
+ if (greh->flags) {
+ LOGP(DLNS, 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);
+ goto out_err;
+ break;
+ case GRE_PTYPE_IPv6:
+ *error = handle_rx_gre_ipv6(bfd, msg, ip6h, greh);
+ 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));
+ *error = -EIO;
+ goto out_err;
+ break;
+ }
+
+ if (msg->len < sizeof(*greh) + 2) {
+ LOGP(DLNS, 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");
+ *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]);
+ *error = -EIO;
+ goto out_err;
+ }
+ *dlci |= (frh[1] >> 4);
+
+ msg->l2h = frh+2;
+
+ return msg;
+
+out_err:
+ msgb_free(msg);
+ return NULL;
+}
+
+static 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;
+
+ 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;
+}
+
+static int handle_nsfrgre_read(struct osmo_fd *bfd)
+{
+ int rc;
+ struct osmo_sockaddr saddr;
+ struct gprs_ns2_vc *nsvc;
+ struct gprs_ns2_vc_bind *bind = bfd->data;
+ struct msgb *msg;
+ struct msgb *reject;
+ uint16_t dlci;
+
+ msg = read_nsfrgre_msg(bfd, &rc, &saddr, &dlci);
+ if (!msg)
+ return rc;
+
+ if (dlci == 0 || dlci == 1023) {
+ LOGP(DLNS, 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);
+ if (rc) {
+ /* VC not found */
+ rc = ns2_create_vc(bind, msg, "newconnection", &reject, &nsvc);
+ switch (rc) {
+ case GPRS_NS2_CS_FOUND:
+ break;
+ case GPRS_NS2_CS_ERROR:
+ case GPRS_NS2_CS_SKIPPED:
+ rc = 0;
+ goto out;
+ case GPRS_NS2_CS_REJECTED:
+ /* nsip_sendmsg will free reject */
+ rc = frgre_sendmsg(bind, reject, &saddr);
+ goto out;
+ case GPRS_NS2_CS_CREATED:
+ frgre_alloc_vc(bind, nsvc, &saddr, dlci);
+ gprs_ns2_vc_fsm_start(nsvc);
+ break;
+ }
+ }
+
+ rc = ns2_recv_vc(nsvc, msg);
+out:
+ msgb_free(msg);
+
+ return rc;
+}
+
+static int handle_nsfrgre_write(struct osmo_fd *bfd)
+{
+ /* FIXME: actually send the data here instead of nsip_sendmsg() */
+ return -EIO;
+}
+
+static inline int frgre_sendmsg(struct gprs_ns2_vc_bind *bind,
+ struct msgb *msg,
+ 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;
+}
+
+static int frgre_vc_sendmsg(struct gprs_ns2_vc *nsvc, struct msgb *msg)
+{
+ struct gprs_ns2_vc_bind *bind = nsvc->bind;
+ struct priv_vc *vcpriv = nsvc->priv;
+ struct priv_bind *bindpriv = bind->priv;
+
+ uint16_t dlci = osmo_htons(bindpriv->dlci);
+ uint8_t *frh;
+ struct gre_hdr *greh;
+
+ /* Prepend the FR header */
+ frh = msgb_push(msg, 2);
+ frh[0] = (dlci >> 2) & 0xfc;
+ frh[1] = ((dlci & 0xf)<<4) | 0x01;
+
+ /* Prepend the GRE header */
+ greh = (struct gre_hdr *) msgb_push(msg, sizeof(*greh));
+ greh->flags = 0;
+ greh->ptype = osmo_htons(GRE_PTYPE_FR);
+
+ return frgre_sendmsg(bind, msg, &vcpriv->remote);
+}
+
+static int frgre_fd_cb(struct osmo_fd *bfd, unsigned int what)
+{
+ int rc = 0;
+
+ if (what & OSMO_FD_READ)
+ rc = handle_nsfrgre_read(bfd);
+ if (what & OSMO_FD_WRITE)
+ rc = handle_nsfrgre_write(bfd);
+
+ return rc;
+}
+
+/*! determine if given bind is for FR-GRE encapsulation. */
+int gprs_ns2_is_frgre_bind(struct gprs_ns2_vc_bind *bind)
+{
+ return (bind->driver == &vc_driver_frgre);
+}
+
+/*! Create a new bind for NS over FR-GRE.
+ * \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 */
+int gprs_ns2_frgre_bind(struct gprs_ns2_inst *nsi,
+ 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 priv_bind *priv;
+ int rc;
+
+ if (!bind)
+ return -ENOSPC;
+
+ if (local->u.sa.sa_family != AF_INET && local->u.sa.sa_family != AF_INET6) {
+ talloc_free(bind);
+ return -EINVAL;
+ }
+
+ bind->driver = &vc_driver_frgre;
+ bind->send_vc = frgre_vc_sendmsg;
+ bind->free_vc = free_vc;
+ bind->nsi = nsi;
+
+ priv = bind->priv = talloc_zero(bind, struct priv_bind);
+ if (!priv) {
+ talloc_free(bind);
+ return -ENOSPC;
+ }
+ priv->fd.cb = frgre_fd_cb;
+ priv->fd.data = bind;
+ priv->addr = *local;
+ INIT_LLIST_HEAD(&bind->nsvc);
+
+ llist_add(&bind->list, &nsi->binding);
+
+ rc = osmo_sock_init_osa_ofd(&priv->fd, SOCK_RAW, IPPROTO_GRE,
+ local, NULL,
+ OSMO_SOCK_F_BIND);
+ if (rc < 0) {
+ talloc_free(priv);
+ talloc_free(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;
+
+ return rc;
+}
diff --git a/src/gb/gprs_ns2_internal.h b/src/gb/gprs_ns2_internal.h
new file mode 100644
index 00000000..b480391c
--- /dev/null
+++ b/src/gb/gprs_ns2_internal.h
@@ -0,0 +1,296 @@
+/*! \file gprs_ns2_internal.h */
+
+#pragma once
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include <osmocom/gprs/protocol/gsm_08_16.h>
+#include <osmocom/gprs/gprs_ns2.h>
+
+struct osmo_fsm_inst;
+struct tlv_parsed;
+struct vty;
+
+struct gprs_ns2_vc_driver;
+struct gprs_ns2_vc_bind;
+
+
+
+#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" \
+ "Reset Timer (Tns-reset) timeout\n" \
+ "Reset Timer (Tns-reset) number of retries\n" \
+ "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"
+
+/* Educated guess - LLC user payload is 1500 bytes plus possible headers */
+#define NS_ALLOC_SIZE 3072
+#define NS_ALLOC_HEADROOM 20
+
+enum ns2_timeout {
+ NS_TOUT_TNS_BLOCK,
+ NS_TOUT_TNS_BLOCK_RETRIES,
+ NS_TOUT_TNS_RESET,
+ NS_TOUT_TNS_RESET_RETRIES,
+ NS_TOUT_TNS_TEST,
+ NS_TOUT_TNS_ALIVE,
+ NS_TOUT_TNS_ALIVE_RETRIES,
+ NS_TOUT_TSNS_PROV,
+};
+
+enum nsvc_timer_mode {
+ /* standard timers */
+ NSVC_TIMER_TNS_TEST,
+ NSVC_TIMER_TNS_ALIVE,
+ NSVC_TIMER_TNS_RESET,
+ _NSVC_TIMER_NR,
+};
+
+enum ns_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 */
+};
+
+/*! 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 */
+};
+
+
+#define NSE_S_BLOCKED 0x0001
+#define NSE_S_ALIVE 0x0002
+#define NSE_S_RESET 0x0004
+
+#define NS_DESC_B(st) ((st) & NSE_S_BLOCKED ? "BLOCKED" : "UNBLOCKED")
+#define NS_DESC_A(st) ((st) & NSE_S_ALIVE ? "ALIVE" : "DEAD")
+#define NS_DESC_R(st) ((st) & NSE_S_RESET ? "RESET" : "UNRESET")
+
+/*! An instance of the NS protocol stack */
+struct gprs_ns2_inst {
+ /*! callback to the user for incoming UNIT DATA IND */
+ osmo_prim_cb cb;
+
+ /*! callback data */
+ void *cb_data;
+
+ /*! linked lists of all NSVC binds (e.g. IPv4 bind, but could be also E1 */
+ struct llist_head binding;
+
+ /*! 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;
+};
+
+/*! 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;
+
+ /*! entry back to ns2_inst */
+ struct gprs_ns2_inst *nsi;
+
+ /*! llist entry for gprs_ns2_inst */
+ struct llist_head list;
+
+ /*! llist head to hold all nsvc */
+ struct llist_head nsvc;
+
+ /*! true if this NSE was created by VTY or pcu socket) */
+ bool persistent;
+
+ /*! true if this NSE wasn't yet alive at all.
+ * Will be true after the first status ind with NS_AFF_CAUSE_RECOVERY */
+ bool first;
+
+ /*! true if this NSE has at least one alive VC */
+ bool alive;
+
+ struct osmo_fsm_inst *bss_sns_fi;
+};
+
+/*! Structure representing a single NS-VC */
+struct gprs_ns2_vc {
+ /*! list of NS-VCs within NSE */
+ struct llist_head list;
+
+ /*! list of NS-VCs within bind, bind is the owner! */
+ struct llist_head blist;
+
+ /*! pointer to NS Instance */
+ struct gprs_ns2_nse *nse;
+
+ /*! pointer to NS VL bind. bind own the memory of this instance */
+ struct gprs_ns2_vc_bind *bind;
+
+ /*! true if this NS was created by VTY or pcu socket) */
+ bool persistent;
+
+ /*! uniquely identifies NS-VC if VC contains nsvci */
+ uint16_t nsvci;
+
+ /*! 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) */
+ 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;
+ 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;
+};
+
+/*! Structure repesenting a bind instance. E.g. IPv4 listen port. */
+struct gprs_ns2_vc_bind {
+ /*! list entry in nsi */
+ struct llist_head list;
+ /*! list of all VC */
+ struct llist_head nsvc;
+ /*! driver private structure */
+ void *priv;
+ /*! a pointer back to the nsi */
+ 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;
+
+ /*! send a msg over a VC */
+ int (*send_vc)(struct gprs_ns2_vc *nsvc, struct msgb *msg);
+
+ /*! free the vc priv data */
+ void (*free_vc)(struct gprs_ns2_vc *nsvc);
+
+ /*! allow to show information for the vty */
+ void (*dump_vty)(const struct gprs_ns2_vc_bind *bind,
+ struct vty *vty, bool stats);
+};
+
+struct gprs_ns2_vc_driver {
+ const char *name;
+ void *priv;
+ void (*free_bind)(struct gprs_ns2_vc_bind *driver);
+};
+
+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);
+
+int ns2_recv_vc(struct gprs_ns2_vc *nsvc,
+ struct msgb *msg);
+
+struct gprs_ns2_vc *ns2_vc_alloc(struct gprs_ns2_vc_bind *bind,
+ struct gprs_ns2_nse *nse,
+ bool initiater);
+
+struct msgb *gprs_ns2_msgb_alloc(void);
+
+void gprs_ns2_sns_dump_vty(struct vty *vty, const struct gprs_ns2_nse *nse, bool stats);
+void ns2_prim_status_ind(struct gprs_ns2_nse *nse,
+ uint16_t bvci,
+ enum gprs_ns2_affecting_cause cause);
+void ns2_nse_notify_alive(struct gprs_ns2_vc *nsvc, bool alive);
+
+/* message */
+int gprs_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,
+ 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_config(struct gprs_ns2_vc *nsvc, bool end_flag,
+ 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_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);
+int ns2_tx_sns_size_ack(struct gprs_ns2_vc *nsvc, uint8_t *cause);
+
+/* 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_reset(struct gprs_ns2_vc *nsvc, uint8_t cause);
+int ns2_tx_reset_ack(struct gprs_ns2_vc *nsvc);
+
+int ns2_tx_unblock(struct gprs_ns2_vc *nsvc);
+int ns2_tx_unblock_ack(struct gprs_ns2_vc *nsvc);
+
+int ns2_tx_alive(struct gprs_ns2_vc *nsvc);
+int ns2_tx_alive_ack(struct gprs_ns2_vc *nsvc);
+
+int ns2_tx_unit_data(struct gprs_ns2_vc *nsvc,
+ uint16_t bvci, uint8_t sducontrol,
+ struct msgb *msg);
+
+int ns2_tx_status(struct gprs_ns2_vc *nsvc, uint8_t cause,
+ uint16_t bvci, struct msgb *orig_msg);
+
+/* 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);
+
+/* sns */
+int gprs_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);
+
+/* vc */
+struct osmo_fsm_inst *gprs_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_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);
+
+/* nse */
+void ns2_nse_notify_unblocked(struct gprs_ns2_vc *nsvc, bool unblocked);
diff --git a/src/gb/gprs_ns2_message.c b/src/gb/gprs_ns2_message.c
new file mode 100644
index 00000000..fac6108c
--- /dev/null
+++ b/src/gb/gprs_ns2_message.c
@@ -0,0 +1,713 @@
+/*! \file gprs_ns2_message.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) 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 <osmocom/core/byteswap.h>
+#include <osmocom/core/rate_ctr.h>
+#include <osmocom/core/stat_item.h>
+#include <osmocom/core/stats.h>
+#include <osmocom/gsm/tlv.h>
+#include <osmocom/gprs/gprs_msgb.h>
+#include <osmocom/gprs/gprs_ns2.h>
+#include <osmocom/gprs/protocol/gsm_08_16.h>
+
+#include "gprs_ns2_internal.h"
+
+#define ERR_IF_NSVC_USES_SNS(nsvc, reason) \
+ 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); \
+ } 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)
+{
+ if (!TLVP_PRESENT(tp, NS_IE_CAUSE) || !TLVP_PRESENT(tp, NS_IE_VCI) || !TLVP_PRESENT(tp, NS_IE_NSEI)) {
+ *cause = NS_CAUSE_MISSING_ESSENT_IE;
+ return -1;
+ }
+
+ return 0;
+}
+
+static int gprs_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)) {
+ *cause = NS_CAUSE_MISSING_ESSENT_IE;
+ return -1;
+ }
+
+ return 0;
+}
+
+static int gprs_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)) {
+ *cause = NS_CAUSE_MISSING_ESSENT_IE;
+ return -1;
+ }
+
+ return 0;
+}
+
+static int gprs_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)) {
+ *cause = NS_CAUSE_MISSING_ESSENT_IE;
+ return -1;
+ }
+
+ return 0;
+}
+
+static int gprs_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)) {
+ *cause = NS_CAUSE_MISSING_ESSENT_IE;
+ return -1;
+ }
+
+ uint8_t _cause = tlvp_val8(tp, NS_IE_VCI, 0);
+
+ switch (_cause) {
+ case NS_CAUSE_NSVC_BLOCKED:
+ case NS_CAUSE_NSVC_UNKNOWN:
+ if (!TLVP_PRESENT(tp, NS_IE_CAUSE)) {
+ *cause = NS_CAUSE_MISSING_ESSENT_IE;
+ 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)) {
+ *cause = NS_CAUSE_MISSING_ESSENT_IE;
+ return -1;
+ }
+ break;
+ case NS_CAUSE_BVCI_UNKNOWN:
+ if (!TLVP_PRESENT(tp, NS_IE_BVCI)) {
+ *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)) {
+ *cause = NS_CAUSE_MISSING_ESSENT_IE;
+ return -1;
+ }
+ break;
+ }
+
+ 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)
+{
+ switch (pdu_type) {
+ case NS_PDUT_RESET:
+ return gprs_ns2_validate_reset(nsvc, msg, tp, cause);
+ case NS_PDUT_RESET_ACK:
+ return gprs_ns2_validate_reset_ack(nsvc, msg, tp, cause);
+ case NS_PDUT_BLOCK:
+ return gprs_ns2_validate_block(nsvc, msg, tp, cause);
+ case NS_PDUT_BLOCK_ACK:
+ return gprs_ns2_validate_block_ack(nsvc, msg, tp, cause);
+ case NS_PDUT_STATUS:
+ return gprs_ns2_validate_status(nsvc, msg, tp, cause);
+
+ /* following PDUs doesn't have any payloads */
+ case NS_PDUT_ALIVE:
+ case NS_PDUT_ALIVE_ACK:
+ case NS_PDUT_UNBLOCK:
+ case NS_PDUT_UNBLOCK_ACK:
+ if (msgb_l2len(msg) != sizeof(struct gprs_ns_hdr)) {
+ *cause = NS_CAUSE_PROTO_ERR_UNSPEC;
+ return -1;
+ }
+ break;
+ }
+
+ return 0;
+}
+
+
+/* transmit functions */
+static int ns2_tx_simple(struct gprs_ns2_vc *nsvc, uint8_t pdu_type)
+{
+ struct msgb *msg = gprs_ns2_msgb_alloc();
+ struct gprs_ns_hdr *nsh;
+
+ log_set_context(LOG_CTX_GB_NSVC, nsvc);
+
+ if (!msg)
+ return -ENOMEM;
+
+ 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);
+}
+
+/*! 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
+ * \returns 0 in case of success */
+int ns2_tx_block(struct gprs_ns2_vc *nsvc, uint8_t cause)
+{
+ struct msgb *msg;
+ struct gprs_ns_hdr *nsh;
+ uint16_t nsvci = osmo_htons(nsvc->nsvci);
+
+ log_set_context(LOG_CTX_GB_NSVC, nsvc);
+
+ ERR_IF_NSVC_USES_SNS(nsvc, "transmit NS BLOCK");
+
+ msg = gprs_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]);
+
+ 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);
+
+ return nsvc->bind->send_vc(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
+ * \returns 0 in case of success */
+int ns2_tx_block_ack(struct gprs_ns2_vc *nsvc)
+{
+ struct msgb *msg;
+ struct gprs_ns_hdr *nsh;
+ uint16_t nsvci = osmo_htons(nsvc->nsvci);
+
+ log_set_context(LOG_CTX_GB_NSVC, nsvc);
+
+ ERR_IF_NSVC_USES_SNS(nsvc, "transmit NS BLOCK ACK");
+
+ msg = gprs_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);
+
+ return nsvc->bind->send_vc(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
+ * \returns 0 in case of success */
+int ns2_tx_reset(struct gprs_ns2_vc *nsvc, uint8_t cause)
+{
+ struct msgb *msg;
+ struct gprs_ns_hdr *nsh;
+ uint16_t nsvci = osmo_htons(nsvc->nsvci);
+ uint16_t nsei = osmo_htons(nsvc->nse->nsei);
+
+ log_set_context(LOG_CTX_GB_NSVC, nsvc);
+
+ ERR_IF_NSVC_USES_SNS(nsvc, "transmit NS RESET");
+
+ msg = gprs_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;
+
+ 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_NSEI, 2, (uint8_t *) &nsei);
+
+ return nsvc->bind->send_vc(nsvc, msg);
+}
+
+/*! Transmit a NS-RESET-ACK on a given NS-VC.
+ * \param[in] nsvc NS-VC used for transmission
+ * \returns 0 in case of success */
+int ns2_tx_reset_ack(struct gprs_ns2_vc *nsvc)
+{
+ struct msgb *msg;
+ struct gprs_ns_hdr *nsh;
+ uint16_t nsvci, nsei;
+
+ /* Section 9.2.6 */
+ log_set_context(LOG_CTX_GB_NSVC, nsvc);
+
+ ERR_IF_NSVC_USES_SNS(nsvc, "transmit NS RESET ACK");
+
+ msg = gprs_ns2_msgb_alloc();
+ if (!msg)
+ return -ENOMEM;
+
+ nsvci = osmo_htons(nsvc->nsvci);
+ nsei = osmo_htons(nsvc->nse->nsei);
+
+ msg->l2h = msgb_put(msg, sizeof(*nsh));
+ nsh = (struct gprs_ns_hdr *) msg->l2h;
+
+ 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);
+}
+
+/*! Transmit a NS-UNBLOCK on a given NS-VC.
+ * \param[in] nsvc NS-VC on which the NS-UNBLOCK is to be transmitted
+ * \returns 0 in case of success */
+int ns2_tx_unblock(struct gprs_ns2_vc *nsvc)
+{
+ 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);
+}
+
+
+/*! Transmit a NS-UNBLOCK-ACK on a given NS-VC.
+ * \param[in] nsvc NS-VC on which the NS-UNBLOCK-ACK is to be transmitted
+ * \returns 0 in case of success */
+int ns2_tx_unblock_ack(struct gprs_ns2_vc *nsvc)
+{
+ 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);
+}
+
+/*! Transmit a NS-ALIVE on a given NS-VC.
+ * \param[in] nsvc NS-VC on which the NS-ALIVE is to be transmitted
+ * \returns 0 in case of success */
+int ns2_tx_alive(struct gprs_ns2_vc *nsvc)
+{
+ 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);
+}
+
+/*! Transmit a NS-ALIVE-ACK on a given NS-VC.
+ * \param[in] nsvc NS-VC on which the NS-ALIVE-ACK is to be transmitted
+ * \returns 0 in case of success */
+int ns2_tx_alive_ack(struct gprs_ns2_vc *nsvc)
+{
+ 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);
+}
+
+/*! Transmit NS-UNITDATA on a given NS-VC.
+ * \param[in] nsvc NS-VC on which the NS-UNITDATA is to be transmitted
+ * \param[in] bvci BVCI to encode in NS-UNITDATA header
+ * \param[in] sducontrol SDU control octet of NS header
+ * \param[in] msg message buffer containing payload
+ * \returns 0 in case of success */
+int ns2_tx_unit_data(struct gprs_ns2_vc *nsvc,
+ uint16_t bvci, uint8_t sducontrol,
+ struct msgb *msg)
+{
+ struct gprs_ns_hdr *nsh;
+
+ 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");
+ msgb_free(msg);
+ return -EIO;
+ }
+
+ nsh->pdu_type = NS_PDUT_UNITDATA;
+ nsh->data[0] = sducontrol;
+ nsh->data[1] = bvci >> 8;
+ nsh->data[2] = bvci & 0xff;
+
+ return nsvc->bind->send_vc(nsvc, msg);
+}
+
+/*! Transmit a NS-STATUS on a given NS-VC.
+ * \param[in] nsvc NS-VC to be used for transmission
+ * \param[in] cause Numeric NS cause value
+ * \param[in] bvci BVCI to be reset within NSVC
+ * \param[in] orig_msg message causing the STATUS
+ * \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)
+{
+ struct msgb *msg = gprs_ns2_msgb_alloc();
+ struct gprs_ns_hdr *nsh;
+ uint16_t nsvci = osmo_htons(nsvc->nsvci);
+
+ log_set_context(LOG_CTX_GB_NSVC, nsvc);
+
+ bvci = osmo_htons(bvci);
+
+ 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_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);
+ 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);
+
+ return nsvc->bind->send_vc(nsvc, msg);
+}
+
+
+/*! Encode + Transmit a SNS-ACK as per Section 9.3.1.
+ * \param[in] nsvc NS-VC through which to transmit the ACK
+ * \param[in] trans_id Transaction ID which to acknowledge
+ * \param[in] cause Pointer to cause value (NULL if no cause to be sent)
+ * \param[in] ip4_elems Array of IPv4 Elements
+ * \param[in] num_ip4_elems number of ip4_elems
+ * \returns 0 on success; negative in case of error */
+int ns2_tx_sns_ack(struct gprs_ns2_vc *nsvc, uint8_t trans_id, uint8_t *cause,
+ 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 = gprs_ns2_msgb_alloc();
+ struct gprs_ns_hdr *nsh;
+ uint16_t nsei;
+
+ if (!nsvc)
+ return -1;
+
+ msg = gprs_ns2_msgb_alloc();
+
+ 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);
+ 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 = SNS_PDUT_ACK;
+ msgb_tvlv_put(msg, NS_IE_NSEI, 2, (uint8_t *)&nsei);
+ msgb_v_put(msg, trans_id);
+ if (cause)
+ msgb_tvlv_put(msg, NS_IE_CAUSE, 1, cause);
+ if (ip4_elems) {
+ /* List of IP4 Elements 10.3.2c */
+ msgb_tvlv_put(msg, NS_IE_IPv4_LIST,
+ num_ip4_elems*sizeof(struct gprs_ns_ie_ip4_elem),
+ (const uint8_t *)ip4_elems);
+ }
+ 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 nsvc->bind->send_vc(nsvc, msg);
+}
+
+/*! Encode + Transmit a SNS-CONFIG as per Section 9.3.4.
+ * \param[in] nsvc NS-VC through which to transmit the SNS-CONFIG
+ * \param[in] end_flag Whether or not this is the last SNS-CONFIG
+ * \param[in] ip4_elems Array of IPv4 Elements
+ * \param[in] num_ip4_elems number of ip4_elems
+ * \returns 0 on success; negative in case of error */
+int ns2_tx_sns_config(struct gprs_ns2_vc *nsvc, bool end_flag,
+ 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 -1;
+
+ msg = gprs_ns2_msgb_alloc();
+
+ 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);
+ 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 = SNS_PDUT_CONFIG;
+
+ msgb_v_put(msg, end_flag ? 0x01 : 0x00);
+ msgb_tvlv_put(msg, NS_IE_NSEI, 2, (uint8_t *)&nsei);
+
+ /* 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 nsvc->bind->send_vc(nsvc, msg);
+}
+
+/*! Encode + Transmit a SNS-CONFIG-ACK as per Section 9.3.5.
+ * \param[in] nsvc NS-VC through which to transmit the SNS-CONFIG-ACK
+ * \param[in] cause Pointer to cause value (NULL if no cause to be sent)
+ * \returns 0 on success; negative in case of error */
+int ns2_tx_sns_config_ack(struct gprs_ns2_vc *nsvc, uint8_t *cause)
+{
+ struct msgb *msg;
+ struct gprs_ns_hdr *nsh;
+ uint16_t nsei;
+
+ if (!nsvc)
+ return -1;
+
+ msg = gprs_ns2_msgb_alloc();
+ 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);
+ 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 = SNS_PDUT_CONFIG_ACK;
+
+ msgb_tvlv_put(msg, NS_IE_NSEI, 2, (uint8_t *)&nsei);
+ if (cause)
+ msgb_tvlv_put(msg, NS_IE_CAUSE, 1, cause);
+
+ return nsvc->bind->send_vc(nsvc, msg);
+}
+
+
+/*! Encode + transmit a SNS-SIZE as per Section 9.3.7.
+ * \param[in] nsvc NS-VC through which to transmit the SNS-SIZE
+ * \param[in] reset_flag Whether or not to add a RESET flag
+ * \param[in] max_nr_nsvc Maximum number of NS-VCs
+ * \param[in] ip4_ep_nr Number of IPv4 endpoints (< 0 will omit the TLV)
+ * \param[in] ip6_ep_nr Number of IPv6 endpoints (< 0 will omit the TLV)
+ * \returns 0 on success; negative in case of error */
+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 gprs_ns_hdr *nsh;
+ uint16_t nsei;
+
+ if (!nsvc)
+ return -1;
+
+ msg = gprs_ns2_msgb_alloc();
+
+ 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);
+ 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 = SNS_PDUT_SIZE;
+
+ msgb_tvlv_put(msg, NS_IE_NSEI, 2, (uint8_t *)&nsei);
+ msgb_tv_put(msg, NS_IE_RESET_FLAG, reset_flag ? 0x01 : 0x00);
+ msgb_tv16_put(msg, NS_IE_MAX_NR_NSVC, max_nr_nsvc);
+ if (ip4_ep_nr >= 0)
+ msgb_tv16_put(msg, NS_IE_IPv4_EP_NR, ip4_ep_nr);
+ if (ip6_ep_nr >= 0)
+ msgb_tv16_put(msg, NS_IE_IPv6_EP_NR, ip6_ep_nr);
+
+ return nsvc->bind->send_vc(nsvc, msg);
+}
+
+/*! Encode + Transmit a SNS-SIZE-ACK as per Section 9.3.8.
+ * \param[in] nsvc NS-VC through which to transmit the SNS-SIZE-ACK
+ * \param[in] cause Pointer to cause value (NULL if no cause to be sent)
+ * \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 gprs_ns_hdr *nsh;
+ uint16_t nsei;
+
+ 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);
+ 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 = SNS_PDUT_SIZE_ACK;
+
+ msgb_tvlv_put(msg, NS_IE_NSEI, 2, (uint8_t *)&nsei);
+ if (cause)
+ msgb_tvlv_put(msg, NS_IE_CAUSE, 1, cause);
+
+ return nsvc->bind->send_vc(nsvc, msg);
+}
+
+
diff --git a/src/gb/gprs_ns2_sns.c b/src/gb/gprs_ns2_sns.c
new file mode 100644
index 00000000..1afd4b7c
--- /dev/null
+++ b/src/gb/gprs_ns2_sns.c
@@ -0,0 +1,1495 @@
+/*! \file gprs_ns2_sns.c
+ * NS Sub-Network Service Protocol implementation
+ * 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) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * 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/>.
+ *
+ */
+
+/* The BSS NSE only has one SGSN IP address configured, and it will use the SNS procedures
+ * to communicated its local IPs/ports as well as all the SGSN side IPs/ports and
+ * associated weights. The BSS then uses this to establish a full mesh
+ * of NSVCs between all BSS-side IPs/ports and SGSN-side IPs/ports.
+ *
+ * Known limitation/expectation/bugs:
+ * - No concurrent dual stack. It supports either IPv4 or IPv6, but not both at the same time.
+ * - SNS Add/Change/Delete: Doesn't answer on the same NSVC as received SNS ADD/CHANGE/DELETE PDUs.
+ * - SNS Add/Change/Delete: Doesn't communicated the failed IPv4/IPv6 entries on the SNS_ACK.
+ */
+
+#include <errno.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <stdint.h>
+
+#include <osmocom/core/fsm.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/gsm/tlv.h>
+#include <osmocom/gprs/gprs_msgb.h>
+#include <osmocom/gprs/gprs_ns2.h>
+#include <osmocom/gprs/protocol/gsm_08_16.h>
+
+#include "gprs_ns2_internal.h"
+
+#define S(x) (1 << (x))
+
+enum ns2_sns_type {
+ IPv4,
+ IPv6,
+};
+
+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_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,
+};
+
+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" },
+ { 0, NULL }
+};
+
+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;
+ /* all SNS PDU will be sent over this nsvc */
+ struct gprs_ns2_vc *sns_nsvc;
+
+ /* local configuration to send to the remote end */
+ struct gprs_ns_ie_ip4_elem *ip4_local;
+ size_t num_ip4_local;
+
+ /* local configuration to send to the remote end */
+ struct gprs_ns_ie_ip6_elem *ip6_local;
+ size_t num_ip6_local;
+
+ /* local configuration about our capabilities in terms of connections to
+ * remote (SGSN) side */
+ size_t num_max_nsvcs;
+ 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;
+};
+
+static inline struct gprs_ns2_nse *nse_inst_from_fi(struct osmo_fsm_inst *fi)
+{
+ struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
+ return gss->nse;
+}
+
+/* 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)
+{
+ unsigned int i;
+ int weight_sum = 0;
+
+ for (i = 0; i < num; i++) {
+ if (data_weight)
+ weight_sum += ip4[i].data_weight;
+ else
+ weight_sum += 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)
+
+/* 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)
+{
+ unsigned int i;
+ int weight_sum = 0;
+
+ for (i = 0; i < num; i++) {
+ if (data_weight)
+ weight_sum += ip6[i].data_weight;
+ else
+ weight_sum += 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)
+
+static struct gprs_ns2_vc *nsvc_by_ip4_elem(struct gprs_ns2_nse *nse,
+ const struct gprs_ns_ie_ip4_elem *ip4)
+{
+ struct osmo_sockaddr sa;
+ /* 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;
+
+ return gprs_ns2_nsvc_by_sockaddr_nse(nse, &sa);
+}
+
+static struct gprs_ns2_vc *nsvc_by_ip6_elem(struct gprs_ns2_nse *nse,
+ const struct gprs_ns_ie_ip6_elem *ip6)
+{
+ struct osmo_sockaddr sa;
+ /* copy over. Both data structures use network byte order */
+ sa.u.sin6.sin6_addr = ip6->ip_addr;
+ sa.u.sin6.sin6_port = ip6->udp_port;
+ sa.u.sin6.sin6_family = AF_INET;
+
+ return gprs_ns2_nsvc_by_sockaddr_nse(nse, &sa);
+}
+
+/*! Return the initial SNS remote socket address
+ * \param nse NS Entity
+ * \return address of the initial SNS connection; NULL in case of error
+ */
+const struct osmo_sockaddr *gprs_ns2_nse_sns_remote(struct gprs_ns2_nse *nse)
+{
+ struct ns2_sns_state *gss;
+
+ if (!nse->bss_sns_fi)
+ return NULL;
+
+ gss = (struct ns2_sns_state *) nse->bss_sns_fi->priv;
+ return &gss->initial;
+}
+
+/*! called when a nsvc is beeing freed */
+void ns2_sns_free_nsvc(struct gprs_ns2_vc *nsvc)
+{
+ struct gprs_ns2_nse *nse;
+ struct gprs_ns2_vc *tmp;
+ struct ns2_sns_state *gss;
+ struct osmo_fsm_inst *fi = nsvc->nse->bss_sns_fi;
+
+ if (!fi)
+ return;
+
+ gss = (struct ns2_sns_state *) fi->priv;
+ if (nsvc != gss->sns_nsvc)
+ return;
+
+ nse = nsvc->nse;
+ if (nse->alive) {
+ /* choose a different sns nsvc */
+ llist_for_each_entry(tmp, &nse->nsvc, list) {
+ if (gprs_ns2_vc_is_unblocked(tmp))
+ gss->sns_nsvc = tmp;
+ }
+ } else {
+ LOGPFSML(fi, LOGL_ERROR, "NSE %d: no remaining NSVC. Reseting SNS FSM.", nse->nsei);
+ gss->sns_nsvc = NULL;
+ osmo_fsm_inst_dispatch(fi, GPRS_SNS_EV_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)
+{
+ 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) {
+ /* 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");
+ continue;
+ }
+
+ nsvc->sig_weight = ip4->sig_weight;
+ nsvc->data_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");
+ continue;
+ }
+
+ nsvc->sig_weight = ip6->sig_weight;
+ nsvc->data_weight = ip6->data_weight;
+ }
+}
+
+
+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 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];
+
+ 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;
+
+ if (!osmo_sockaddr_cmp(&remote, gprs_ns2_ip_vc_remote(nsvc))) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ 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;
+ }
+ }
+
+ for (i = 0; i < gss->num_ip6_remote; i++) {
+ const struct gprs_ns_ie_ip6_elem *ip6 = &gss->ip6_remote[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;
+
+ if (!osmo_sockaddr_cmp(&remote, gprs_ns2_ip_vc_remote(nsvc))) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ 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;
+ }
+ }
+
+
+ return 0;
+}
+
+/* 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)
+{
+ 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)))
+ continue;
+ /* TODO: log message duplicate */
+ /* TODO: check if this is the correct cause code */
+ return -NS_CAUSE_PROTO_ERR_UNSPEC;
+ }
+
+ 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;
+ 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)
+{
+ unsigned int i;
+
+ for (i = 0; i < gss->num_ip4_remote; i++) {
+ if (memcmp(&gss->ip4_remote[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;
+ 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)
+{
+ 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)
+ continue;
+
+ gss->ip4_remote[i].sig_weight = ip4->sig_weight;
+ gss->ip4_remote[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)
+{
+ if (gss->num_ip6_remote >= gss->num_max_ip6_remote)
+ return -NS_CAUSE_INVAL_NR_NS_VC;
+
+ 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;
+ 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)
+{
+ unsigned int i;
+
+ for (i = 0; i < gss->num_ip6_remote; i++) {
+ if (memcmp(&gss->ip6_remote[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;
+ 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)
+{
+ 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)
+ continue;
+ gss->ip6_remote[i].sig_weight = ip6->sig_weight;
+ gss->ip6_remote[i].data_weight = ip6->data_weight;
+ return 0;
+ }
+ 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;
+ struct gprs_ns2_nse *nse = nse_inst_from_fi(fi);
+ struct gprs_ns2_vc *nsvc;
+ struct osmo_sockaddr sa = {};
+ const struct osmo_sockaddr *remote;
+ uint8_t new_signal;
+ uint8_t new_data;
+
+ /* TODO: Upon receiving an SNS-CHANGEWEIGHT PDU, if the resulting sum of the
+ * signalling weights of all the peer IP endpoints configured for this NSE is
+ * equal to zero or if the resulting sum of the data weights of all the peer IP
+ * endpoints configured for this NSE is equal to zero, the BSS/SGSN shall send an
+ * SNS-ACK PDU with a cause code of "Invalid weights". */
+
+ if (ip4) {
+ if (update_remote_ip4_elem(gss, ip4))
+ 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;
+ new_signal = ip4->sig_weight;
+ new_data = ip4->data_weight;
+ } else if (ip6) {
+ if (update_remote_ip6_elem(gss, ip6))
+ return -NS_CAUSE_UNKN_IP_EP;
+
+ /* copy over. Both data structures use network byte order */
+ sa.u.sin6.sin6_addr = ip6->ip_addr;
+ sa.u.sin6.sin6_port = ip6->udp_port;
+ sa.u.sin6.sin6_family = AF_INET6;
+ new_signal = ip6->sig_weight;
+ new_data = ip6->data_weight;
+ } else {
+ OSMO_ASSERT(false);
+ }
+
+ llist_for_each_entry(nsvc, &nse->nsvc, list) {
+ remote = gprs_ns2_ip_vc_remote(nsvc);
+ /* all nsvc in NSE should be IP/UDP nsvc */
+ OSMO_ASSERT(remote);
+
+ if (osmo_sockaddr_cmp(&sa, remote))
+ continue;
+
+ LOGPFSML(fi, LOGL_INFO, "CHANGE-WEIGHT NS-VC %s data_weight %u->%u, sig_weight %u->%u\n",
+ gprs_ns2_ll_str(nsvc), nsvc->data_weight, new_data,
+ nsvc->sig_weight, new_signal);
+
+ nsvc->data_weight = new_data;
+ nsvc->sig_weight = new_signal;
+ }
+
+ return 0;
+}
+
+static int do_sns_delete(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;
+ struct gprs_ns2_nse *nse = nse_inst_from_fi(fi);
+ struct gprs_ns2_vc *nsvc, *tmp;
+ const struct osmo_sockaddr *remote;
+ struct osmo_sockaddr sa = {};
+
+ if (ip4) {
+ if (remove_remote_ip4_elem(gss, 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))
+ return -NS_CAUSE_UNKN_IP_EP;
+
+ /* copy over. Both data structures use network byte order */
+ sa.u.sin6.sin6_addr = ip6->ip_addr;
+ sa.u.sin6.sin6_port = ip6->udp_port;
+ sa.u.sin6.sin6_family = AF_INET6;
+ } else {
+ OSMO_ASSERT(false);
+ }
+
+ llist_for_each_entry_safe(nsvc, tmp, &nse->nsvc, list) {
+ remote = gprs_ns2_ip_vc_remote(nsvc);
+ /* all nsvc in NSE should be IP/UDP nsvc */
+ OSMO_ASSERT(remote);
+ if (osmo_sockaddr_cmp(&sa, remote))
+ continue;
+
+ LOGPFSML(fi, LOGL_INFO, "DELETE NS-VC %s\n", gprs_ns2_ll_str(nsvc));
+ gprs_ns2_free_nsvc(nsvc);
+ }
+
+ return 0;
+}
+
+static int do_sns_add(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;
+ struct gprs_ns2_nse *nse = nse_inst_from_fi(fi);
+ struct gprs_ns2_vc *nsvc;
+ int rc = 0;
+
+ /* 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);
+ break;
+ case IPv6:
+ rc = add_remote_ip6_elem(gss, ip6);
+ break;
+ default:
+ /* the gss->ip is initialized with the bss */
+ OSMO_ASSERT(false);
+ }
+
+ if (rc)
+ return rc;
+
+ /* 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:
+ nsvc = nsvc_by_ip4_elem(nse, ip4);
+ if (nsvc) {
+ /* the nsvc should be already in sync with the ip4 / ip6 elements */
+ return -NS_CAUSE_PROTO_ERR_UNSPEC;
+ }
+
+ /* TODO: failure case */
+ ns2_nsvc_create_ip4(fi, nse, ip4);
+ break;
+ case IPv6:
+ nsvc = nsvc_by_ip6_elem(nse, ip6);
+ if (nsvc) {
+ /* the nsvc should be already in sync with the ip4 / ip6 elements */
+ return -NS_CAUSE_PROTO_ERR_UNSPEC;
+ }
+
+ /* TODO: failure case */
+ ns2_nsvc_create_ip6(fi, nse, ip6);
+ break;
+ }
+
+ gprs_ns2_start_alive_all_nsvcs(nse);
+
+ return 0;
+}
+
+
+static void ns2_sns_st_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);
+ }
+}
+
+static void ns2_sns_st_size(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;
+ struct tlv_parsed *tp = NULL;
+
+ switch (event) {
+ case GPRS_SNS_EV_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,
+ nsi->timeout[NS_TOUT_TSNS_PROV], 2);
+ }
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static void ns2_sns_st_size_onenter(struct osmo_fsm_inst *fi, uint32_t old_state)
+{
+ struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
+
+ if (old_state != GPRS_SNS_ST_UNCONFIGURED)
+ ns2_prim_status_ind(gss->nse, 0, NS_AFF_CAUSE_SNS_FAILURE);
+
+ 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);
+ else
+ ns2_tx_sns_size(gss->sns_nsvc, true, gss->num_max_nsvcs, -1, gss->num_max_ip6_remote);
+
+}
+
+static void ns2_sns_st_config_bss(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct tlv_parsed *tp = NULL;
+
+ switch (event) {
+ case GPRS_SNS_EV_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);
+ }
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static void ns2_sns_st_config_bss_onenter(struct osmo_fsm_inst *fi, uint32_t old_state)
+{
+ struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
+ /* Transmit SNS-CONFIG */
+ /* TODO: ipv6 */
+ switch (gss->ip) {
+ case IPv4:
+ ns2_tx_sns_config(gss->sns_nsvc, true,
+ gss->ip4_local, gss->num_ip4_local,
+ NULL, 0);
+ break;
+ case IPv6:
+ ns2_tx_sns_config(gss->sns_nsvc, true,
+ NULL, 0,
+ gss->ip6_local, gss->num_ip6_local);
+ break;
+ }
+}
+
+
+static void ns_sns_st_config_sgsn_ip4(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);
+ const struct gprs_ns_ie_ip4_elem *v4_list;
+ unsigned int num_v4;
+ struct tlv_parsed *tp = NULL;
+
+ uint8_t cause;
+
+ tp = (struct tlv_parsed *) data;
+
+ 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;
+ }
+ 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);
+ }
+}
+
+static void ns_sns_st_config_sgsn_ip6(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);
+ 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;
+
+ 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);
+ }
+}
+
+static void ns2_sns_st_config_sgsn(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
+
+ 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;
+ }
+#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);
+ }
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+/* called when receiving GPRS_SNS_EV_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)
+{
+ const struct gprs_ns_ie_ip4_elem *v4_list = NULL;
+ const struct gprs_ns_ie_ip6_elem *v6_list = NULL;
+ int num_v4 = 0, num_v6 = 0;
+ uint8_t trans_id, cause = 0xff;
+ unsigned int i;
+ int rc = 0;
+
+ /* TODO: refactor EV_ADD/CHANGE/REMOVE by
+ * check uniqueness within the lists (no doublicate entries)
+ * check not-known-by-us and sent back a list of unknown/known values
+ * (abnormal behaviour according to 48.016)
+ */
+
+ trans_id = *TLVP_VAL(tp, NS_IE_TRANS_ID);
+ if (gss->ip == IPv4) {
+ 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);
+ return;
+ }
+
+ 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);
+ for (i = 0; i < num_v4; i++) {
+ unsigned int j;
+ rc = do_sns_add(fi, &v4_list[i], NULL);
+ if (rc < 0) {
+ /* rollback/undo to restore previous state */
+ for (j = 0; j < i; j++)
+ do_sns_delete(fi, &v4_list[j], NULL);
+ cause = -rc;
+ ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0);
+ break;
+ }
+ }
+ } else { /* IPv6 */
+ if (!TLVP_PRESENT(tp, NS_IE_IPv6_LIST)) {
+ cause = NS_CAUSE_INVAL_NR_IPv6_EP;
+ ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 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);
+ for (i = 0; i < num_v6; i++) {
+ unsigned int j;
+ rc = do_sns_add(fi, NULL, &v6_list[i]);
+ if (rc < 0) {
+ /* rollback/undo to restore previous state */
+ for (j = 0; j < i; j++)
+ do_sns_delete(fi, NULL, &v6_list[j]);
+ cause = -rc;
+ ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0);
+ break;
+ }
+ }
+ }
+
+ /* TODO: correct behaviour is to answer to the *same* NSVC from which the SNS_ADD was received */
+ ns2_tx_sns_ack(gss->sns_nsvc, trans_id, NULL, v4_list, num_v4, v6_list, num_v6);
+}
+
+static void ns2_sns_st_configured_delete(struct osmo_fsm_inst *fi,
+ struct ns2_sns_state *gss,
+ struct tlv_parsed *tp)
+{
+ const struct gprs_ns_ie_ip4_elem *v4_list = NULL;
+ const struct gprs_ns_ie_ip6_elem *v6_list = NULL;
+ int num_v4 = 0, num_v6 = 0;
+ uint8_t trans_id, cause = 0xff;
+ unsigned int i;
+ int rc = 0;
+
+ /* TODO: split up delete into v4 + v6
+ * TODO: check if IPv4_LIST or IP_ADDR(v4) is present on IPv6 and vice versa
+ * 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 (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);
+ for ( i = 0; i < num_v4; i++) {
+ rc = do_sns_delete(fi, &v4_list[i], NULL);
+ if (rc < 0) {
+ cause = -rc;
+ /* continue to delete others */
+ }
+ }
+ if (cause != 0xff) {
+ /* TODO: create list of not-deleted and return it */
+ ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0);
+ return;
+ }
+
+ } else if (TLVP_PRESENT(tp, NS_IE_IP_ADDR) && TLVP_LEN(tp, NS_IE_IP_ADDR) == 5) {
+ /* delete all NS-VCs for given IPv4 address */
+ const uint8_t *ie = TLVP_VAL(tp, NS_IE_IP_ADDR);
+ struct gprs_ns_ie_ip4_elem *ip4_remote;
+ uint32_t ip_addr = *(uint32_t *)(ie+1);
+ if (ie[0] != 0x01) { /* Address Type != IPv4 */
+ cause = NS_CAUSE_UNKN_IP_ADDR;
+ ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0);
+ 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++) {
+ if (ip4_remote[i].ip_addr == ip_addr) {
+ rc = do_sns_delete(fi, &ip4_remote[i], NULL);
+ if (rc < 0) {
+ cause = -rc;
+ /* continue to delete others */
+ }
+ }
+ }
+ talloc_free(ip4_remote);
+ if (cause != 0xff) {
+ /* TODO: create list of not-deleted and return it */
+ ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0);
+ return;
+ }
+ } else {
+ cause = NS_CAUSE_INVAL_NR_IPv4_EP;
+ ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0);
+ return;
+ }
+ } else { /* IPv6 */
+ if (TLVP_PRESENT(tp, NS_IE_IPv6_LIST)) {
+ 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);
+ for (i = 0; i < num_v6; i++) {
+ rc = do_sns_delete(fi, NULL, &v6_list[i]);
+ if (rc < 0) {
+ cause = -rc;
+ /* continue to delete others */
+ }
+ }
+ if (cause != 0xff) {
+ /* TODO: create list of not-deleted and return it */
+ ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0);
+ return;
+ }
+ } else if (TLVP_PRES_LEN(tp, NS_IE_IP_ADDR, 17)) {
+ /* delete all NS-VCs for given IPv4 address */
+ const uint8_t *ie = TLVP_VAL(tp, NS_IE_IP_ADDR);
+ struct gprs_ns_ie_ip6_elem *ip6_remote;
+ struct in6_addr ip6_addr;
+ unsigned int i;
+ if (ie[0] != 0x02) { /* Address Type != IPv6 */
+ cause = NS_CAUSE_UNKN_IP_ADDR;
+ ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0);
+ return;
+ }
+ 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++) {
+ 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) {
+ cause = -rc;
+ /* continue to delete others */
+ }
+ }
+ }
+
+ talloc_free(ip6_remote);
+ if (cause != 0xff) {
+ /* TODO: create list of not-deleted and return it */
+ ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0);
+ return;
+ }
+ } else {
+ cause = NS_CAUSE_INVAL_NR_IPv6_EP;
+ ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0);
+ return;
+ }
+ }
+ ns2_tx_sns_ack(gss->sns_nsvc, trans_id, NULL, v4_list, num_v4, v6_list, num_v6);
+}
+
+static void ns2_sns_st_configured_change(struct osmo_fsm_inst *fi,
+ struct ns2_sns_state *gss,
+ struct tlv_parsed *tp)
+{
+ const struct gprs_ns_ie_ip4_elem *v4_list = NULL;
+ const struct gprs_ns_ie_ip6_elem *v6_list = NULL;
+ int num_v4 = 0, num_v6 = 0;
+ uint8_t trans_id, cause = 0xff;
+ int rc = 0;
+ unsigned int i;
+
+ trans_id = *TLVP_VAL(tp, NS_IE_TRANS_ID);
+ 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);
+ for (i = 0; i < num_v4; i++) {
+ rc = do_sns_change_weight(fi, &v4_list[i], NULL);
+ if (rc < 0) {
+ cause = -rc;
+ /* continue to others */
+ }
+ }
+ if (cause != 0xff) {
+ ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0);
+ return;
+ }
+ } else if (TLVP_PRESENT(tp, NS_IE_IPv6_LIST)) {
+ 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);
+ for (i = 0; i < num_v6; i++) {
+ rc = do_sns_change_weight(fi, NULL, &v6_list[i]);
+ if (rc < 0) {
+ cause = -rc;
+ /* continue to others */
+ }
+ }
+ if (cause != 0xff) {
+ ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0);
+ return;
+ }
+ } else {
+ cause = NS_CAUSE_INVAL_NR_IPv4_EP;
+ ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0);
+ return;
+ }
+ ns2_tx_sns_ack(gss->sns_nsvc, trans_id, NULL, v4_list, num_v4, v6_list, num_v6);
+}
+
+static void ns2_sns_st_configured(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 = data;
+
+ switch (event) {
+ case GPRS_SNS_EV_ADD:
+ ns2_sns_st_configured_add(fi, gss, tp);
+ break;
+ case GPRS_SNS_EV_DELETE:
+ ns2_sns_st_configured_delete(fi, gss, tp);
+ break;
+ case GPRS_SNS_EV_CHANGE_WEIGHT:
+ ns2_sns_st_configured_change(fi, gss, tp);
+ break;
+ }
+}
+
+static void ns2_sns_st_configured_onenter(struct osmo_fsm_inst *fi, uint32_t old_state)
+{
+ struct gprs_ns2_nse *nse = nse_inst_from_fi(fi);
+ ns2_prim_status_ind(nse, 0, NS_AFF_CAUSE_SNS_CONFIGURED);
+}
+
+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),
+ .name = "UNCONFIGURED",
+ .action = ns2_sns_st_unconfigured,
+ },
+ [GPRS_SNS_ST_SIZE] = {
+ .in_event_mask = S(GPRS_SNS_EV_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,
+ },
+ [GPRS_SNS_ST_CONFIG_BSS] = {
+ .in_event_mask = S(GPRS_SNS_EV_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,
+ },
+ [GPRS_SNS_ST_CONFIG_SGSN] = {
+ .in_event_mask = S(GPRS_SNS_EV_CONFIG) |
+ S(GPRS_SNS_EV_CONFIG_END),
+ .out_state_mask = S(GPRS_SNS_ST_UNCONFIGURED) |
+ S(GPRS_SNS_ST_CONFIG_SGSN) |
+ S(GPRS_SNS_ST_CONFIGURED) |
+ S(GPRS_SNS_ST_SIZE),
+ .name = "CONFIG_SGSN",
+ .action = ns2_sns_st_config_sgsn,
+ },
+ [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),
+ .name = "CONFIGURED",
+ .action = ns2_sns_st_configured,
+ .onenter = ns2_sns_st_configured_onenter,
+ },
+};
+
+static int ns2_sns_fsm_bss_timer_cb(struct osmo_fsm_inst *fi)
+{
+ struct gprs_ns2_nse *nse = nse_inst_from_fi(fi);
+ struct gprs_ns2_inst *nsi = nse->nsi;
+
+ switch (fi->T) {
+ case 1:
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_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);
+ break;
+ }
+ return 0;
+}
+
+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);
+
+ /* 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);
+}
+
+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,
+};
+
+/*! 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)
+{
+ 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;
+
+ 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)
+{
+ 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;
+
+ gss->initial = *remote;
+ gss->sns_nsvc = nsvc;
+ nsvc->sns_only = true;
+
+ 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++;
+ }
+
+ if (count == 0) {
+ /* TODO: logging */
+ goto err;
+ }
+
+ 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;
+
+ 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 = 2;
+ ip4_elems->data_weight = 1;
+ ip4_elems++;
+ }
+
+ gss->num_ip4_local = count;
+ gss->num_max_ip4_remote = 4;
+ break;
+ case IPv6:
+ /* IPv6 */
+ ip6_elems = talloc_zero_size(fi, sizeof(struct gprs_ns_ie_ip6_elem) * count);
+ if (!ip6_elems)
+ goto err;
+
+ gss->ip6_local = ip6_elems;
+
+ 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.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 = 2;
+ ip6_elems->data_weight = 1;
+
+ ip6_elems++;
+ }
+ gss->num_ip6_local = count;
+ gss->num_max_ip6_remote = 4;
+ break;
+ }
+
+ gss->num_max_nsvcs = 8;
+
+ return osmo_fsm_inst_dispatch(nse->bss_sns_fi, GPRS_SNS_EV_START, NULL);
+
+err:
+ return -1;
+}
+
+/*! 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)
+{
+ 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 osmo_fsm_inst *fi;
+
+ 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;
+ }
+
+ 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;
+
+ switch (nsh->pdu_type) {
+ case SNS_PDUT_SIZE:
+ osmo_fsm_inst_dispatch(fi, GPRS_SNS_EV_SIZE, tp);
+ break;
+ case SNS_PDUT_SIZE_ACK:
+ osmo_fsm_inst_dispatch(fi, GPRS_SNS_EV_SIZE_ACK, tp);
+ break;
+ case SNS_PDUT_CONFIG:
+ if (nsh->data[0] & 0x01)
+ osmo_fsm_inst_dispatch(fi, GPRS_SNS_EV_CONFIG_END, tp);
+ else
+ osmo_fsm_inst_dispatch(fi, GPRS_SNS_EV_CONFIG, tp);
+ break;
+ case SNS_PDUT_CONFIG_ACK:
+ osmo_fsm_inst_dispatch(fi, GPRS_SNS_EV_CONFIG_ACK, tp);
+ break;
+ case SNS_PDUT_ADD:
+ osmo_fsm_inst_dispatch(fi, GPRS_SNS_EV_ADD, tp);
+ break;
+ case SNS_PDUT_DELETE:
+ osmo_fsm_inst_dispatch(fi, GPRS_SNS_EV_DELETE, tp);
+ break;
+ case SNS_PDUT_CHANGE_WEIGHT:
+ osmo_fsm_inst_dispatch(fi, GPRS_SNS_EV_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));
+ 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;
+ }
+
+ return 0;
+}
+
+#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)
+{
+ struct in_addr in = { .s_addr = ip4->ip_addr };
+ vty_out(vty, " %s:%u, Signalling Weight: %u, Data Weight: %u%s",
+ 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)
+{
+ 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",
+ 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] 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)
+{
+ struct ns2_sns_state *gss;
+ unsigned int i;
+
+ if (!nse->bss_sns_fi)
+ return;
+
+ vty_out_fsm_inst(vty, nse->bss_sns_fi);
+ gss = (struct ns2_sns_state *) nse->bss_sns_fi->priv;
+
+ 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);
+
+ 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]);
+
+ 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]);
+ }
+
+ 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]);
+
+ 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]);
+ }
+}
+
+/* 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);
+}
diff --git a/src/gb/gprs_ns2_udp.c b/src/gb/gprs_ns2_udp.c
new file mode 100644
index 00000000..2cc10064
--- /dev/null
+++ b/src/gb/gprs_ns2_udp.c
@@ -0,0 +1,498 @@
+/*! \file gprs_ns2_udp.c
+ * NS-over-UDP 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) 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 <osmocom/core/select.h>
+#include <osmocom/core/sockaddr_str.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/gprs/gprs_ns2.h>
+
+#include "common_vty.h"
+#include "gprs_ns2_internal.h"
+
+
+static void free_bind(struct gprs_ns2_vc_bind *bind);
+
+
+struct gprs_ns2_vc_driver vc_driver_ip = {
+ .name = "GB UDP IPv4/IPv6",
+ .free_bind = free_bind,
+};
+
+struct priv_bind {
+ struct osmo_fd fd;
+ struct osmo_sockaddr addr;
+ int dscp;
+};
+
+struct priv_vc {
+ struct osmo_sockaddr remote;
+};
+
+/*! 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;
+
+ if (!bind)
+ return;
+
+ priv = bind->priv;
+
+ osmo_fd_close(&priv->fd);
+ talloc_free(priv);
+}
+
+static void free_vc(struct gprs_ns2_vc *nsvc)
+{
+ if (!nsvc->priv)
+ return;
+
+ 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_sockaddr_str sockstr = {};
+ unsigned long nsvcs = 0;
+
+ if (!bind)
+ return;
+
+ priv = bind->priv;
+ if (osmo_sockaddr_str_from_sockaddr(&sockstr, &priv->addr.u.sas))
+ strcpy(sockstr.ip, "invalid");
+
+ llist_for_each_entry(nsvc, &bind->nsvc, blist) {
+ 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);
+
+ llist_for_each_entry(nsvc, &bind->nsvc, blist) {
+ vty_out(vty, " %s%s", gprs_ns2_ll_str(nsvc), VTY_NEWLINE);
+ }
+}
+
+
+/*! 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
+ * \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)
+{
+ struct gprs_ns2_vc *nsvc;
+ struct priv_vc *vcpriv;
+
+ llist_for_each_entry(nsvc, &bind->nsvc, blist) {
+ vcpriv = nsvc->priv;
+ if (vcpriv->remote.u.sa.sa_family != saddr->u.sa.sa_family)
+ continue;
+ if (osmo_sockaddr_cmp(&vcpriv->remote, saddr))
+ continue;
+
+ return nsvc;
+ }
+
+ return NULL;
+}
+
+static inline int nsip_sendmsg(struct gprs_ns2_vc_bind *bind,
+ struct msgb *msg,
+ 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;
+}
+
+/*! send the msg and free it afterwards.
+ * \param nsvc NS-VC on which the message shall be sent
+ * \param msg message to be sent
+ * \return number of bytes transmitted; negative on error */
+static int nsip_vc_sendmsg(struct gprs_ns2_vc *nsvc, struct msgb *msg)
+{
+ int rc;
+ struct gprs_ns2_vc_bind *bind = nsvc->bind;
+ struct priv_vc *priv = nsvc->priv;
+
+ rc = nsip_sendmsg(bind, msg, &priv->remote);
+
+ 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)
+{
+ struct priv_vc *priv = talloc_zero(bind, struct priv_vc);
+ if (!priv)
+ return NULL;
+
+ nsvc->priv = priv;
+ priv->remote = *remote;
+
+ return priv;
+}
+
+static int handle_nsip_read(struct osmo_fd *bfd)
+{
+ int rc;
+ int error = 0;
+ struct gprs_ns2_vc_bind *bind = bfd->data;
+ struct osmo_sockaddr saddr;
+ struct gprs_ns2_vc *nsvc;
+ struct msgb *msg = read_nsip_msg(bfd, &error, &saddr);
+ struct msgb *reject;
+
+ if (!msg)
+ return -EINVAL;
+
+ /* check if a vc is available */
+ nsvc = gprs_ns2_nsvc_by_sockaddr_bind(bind, &saddr);
+ if (!nsvc) {
+ /* VC not found */
+ rc = ns2_create_vc(bind, msg, "newconnection", &reject, &nsvc);
+ switch (rc) {
+ case GPRS_NS2_CS_FOUND:
+ break;
+ case GPRS_NS2_CS_ERROR:
+ case GPRS_NS2_CS_SKIPPED:
+ rc = 0;
+ goto out;
+ case GPRS_NS2_CS_REJECTED:
+ /* nsip_sendmsg will free reject */
+ 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);
+ break;
+ }
+ }
+
+ rc = ns2_recv_vc(nsvc, msg);
+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)
+{
+ int rc = 0;
+
+ if (what & OSMO_FD_READ)
+ rc = handle_nsip_read(bfd);
+ if (what & OSMO_FD_WRITE)
+ rc = handle_nsip_write(bfd);
+
+ return rc;
+}
+
+/*! Find NS bind for a given socket address
+ * \param[in] nsi NS instance
+ * \param[in] sockaddr socket address to search for
+ * \return
+ */
+struct gprs_ns2_vc_bind *gprs_ns2_ip_bind_by_sockaddr(struct gprs_ns2_inst *nsi,
+ const struct osmo_sockaddr *sockaddr)
+{
+ struct gprs_ns2_vc_bind *bind;
+ const struct osmo_sockaddr *local;
+
+ OSMO_ASSERT(nsi);
+ OSMO_ASSERT(sockaddr);
+
+ llist_for_each_entry(bind, &nsi->binding, list) {
+ if (!gprs_ns2_is_ip_bind(bind))
+ continue;
+
+ local = gprs_ns2_ip_bind_sockaddr(bind);
+ if (!osmo_sockaddr_cmp(sockaddr, local))
+ return bind;
+ }
+
+ return NULL;
+}
+
+/*! Bind to an IPv4/IPv6 address
+ * \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 */
+int gprs_ns2_ip_bind(struct gprs_ns2_inst *nsi,
+ const struct osmo_sockaddr *local,
+ int dscp,
+ struct gprs_ns2_vc_bind **result)
+{
+ struct gprs_ns2_vc_bind *bind;
+ struct priv_bind *priv;
+ int rc;
+
+ bind = gprs_ns2_ip_bind_by_sockaddr(nsi, local);
+ if (bind) {
+ *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;
+ }
+
+ bind->driver = &vc_driver_ip;
+ 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;
+ }
+ priv->fd.cb = nsip_fd_cb;
+ priv->fd.data = bind;
+ priv->addr = *local;
+ INIT_LLIST_HEAD(&bind->nsvc);
+
+ rc = osmo_sock_init_osa_ofd(&priv->fd, SOCK_DGRAM, IPPROTO_UDP,
+ local, NULL,
+ OSMO_SOCK_F_BIND);
+ if (rc < 0) {
+ talloc_free(priv);
+ talloc_free(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);
+
+ if (result)
+ *result = bind;
+
+ return 0;
+}
+
+/*! Create new NS-VC to a given remote address
+ * \param[in] bind the bind we want to connect
+ * \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 *nsvc;
+ struct priv_vc *priv;
+
+ nsvc = ns2_vc_alloc(bind, nse, true);
+ nsvc->priv = talloc_zero(bind, struct priv_vc);
+ if (!nsvc->priv) {
+ gprs_ns2_free_nsvc(nsvc);
+ return NULL;
+ }
+
+ priv = nsvc->priv;
+ priv->remote = *remote;
+
+ nsvc->ll = GPRS_NS_LL_UDP;
+
+ return nsvc;
+}
+
+/*! Return the socket address of the local peer of a NS-VC.
+ * \param[in] nsvc NS-VC whose local peer we want to know
+ * \return address of the local peer; NULL in case of error */
+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;
+
+ priv = nsvc->bind->priv;
+ return &priv->addr;
+}
+
+/*! Return the socket address of the remote peer of a NS-VC.
+ * \param[in] nsvc NS-VC whose remote peer we want to know
+ * \return address of the remote peer; NULL in case of error */
+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)
+ return NULL;
+
+ priv = nsvc->priv;
+ return &priv->remote;
+}
+
+/*! Compare the NS-VC with the given parameter
+ * \param[in] nsvc NS-VC to compare with
+ * \param[in] local The local address
+ * \param[in] remote The remote address
+ * \param[in] nsvci NS-VCI will only be used if the NS-VC in BLOCKRESET mode otherwise NS-VCI isn't applicable.
+ * \return true if the NS-VC has the same properties as given
+ */
+bool gprs_ns2_ip_vc_equal(const struct gprs_ns2_vc *nsvc,
+ const struct osmo_sockaddr *local,
+ const struct osmo_sockaddr *remote,
+ uint16_t nsvci)
+{
+ struct priv_vc *vpriv;
+ struct priv_bind *bpriv;
+
+ if (nsvc->ll != GPRS_NS_LL_UDP)
+ return false;
+
+ vpriv = nsvc->priv;
+ bpriv = nsvc->bind->priv;
+
+ if (osmo_sockaddr_cmp(local, &bpriv->addr))
+ return false;
+
+ if (osmo_sockaddr_cmp(remote, &vpriv->remote))
+ return false;
+
+ if (nsvc->mode == NS2_VC_MODE_BLOCKRESET)
+ if (nsvc->nsvci != nsvci)
+ return false;
+
+ return true;
+}
+
+/*! Return the locally bound socket address of the bind.
+ * \param[in] bind The bind whose local address we want to know
+ * \return address of the local bind */
+const struct osmo_sockaddr *gprs_ns2_ip_bind_sockaddr(struct gprs_ns2_vc_bind *bind)
+{
+ struct priv_bind *priv;
+
+ priv = bind->priv;
+ return &priv->addr;
+}
+
+/*! Is the given bind an IP bind? */
+int gprs_ns2_is_ip_bind(struct gprs_ns2_vc_bind *bind)
+{
+ return (bind->driver == &vc_driver_ip);
+}
+
+/*! Set the DSCP (TOS) bit value of the given bind. */
+int gprs_ns2_ip_bind_set_dscp(struct gprs_ns2_vc_bind *bind, int dscp)
+{
+ struct priv_bind *priv;
+ int rc = 0;
+
+ 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);
+ }
+
+ return rc;
+}
diff --git a/src/gb/gprs_ns2_vc_fsm.c b/src/gb/gprs_ns2_vc_fsm.c
new file mode 100644
index 00000000..d13f1ce6
--- /dev/null
+++ b/src/gb/gprs_ns2_vc_fsm.c
@@ -0,0 +1,668 @@
+/*! \file gprs_ns2_vc_fsm.c
+ * NS virtual circuit FSM implementation
+ * 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) 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/>.
+ *
+ */
+
+/* The BSS NSE only has one SGSN IP address configured, and it will use the SNS procedures
+ * to communicated its local IPs/ports as well as all the SGSN side IPs/ports and
+ * associated weights. In theory, the BSS then uses this to establish a full mesh
+ * of NSVCs between all BSS-side IPs/ports and SGSN-side IPs/ports */
+
+#include <errno.h>
+
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include <osmocom/core/fsm.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/rate_ctr.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/core/stat_item.h>
+#include <osmocom/gsm/prim.h>
+#include <osmocom/gsm/tlv.h>
+#include <osmocom/gprs/gprs_msgb.h>
+#include <osmocom/gprs/protocol/gsm_08_16.h>
+
+#include "gprs_ns2_internal.h"
+
+#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 initiater is responsible to UNBLOCK the VC. The BSS is usually the initiater.
+ * It can change while runtime. The side which blocks an unblocked side.*/
+ bool initiater;
+
+ /* the alive counter is present in all states */
+ struct {
+ struct osmo_timer_list timer;
+ enum ns2_timeout mode;
+ int N;
+ struct timeval timer_started;
+ } alive;
+};
+
+
+/* The FSM covers both the VC with RESET/BLOCK and without RESET/BLOCK procedure..
+ *
+ * With RESET/BLOCK, the state should follow:
+ * - UNCONFIGURED -> RESET -> BLOCK -> UNBLOCKED
+ *
+ * Without RESET/BLOCK, the state should follow:
+ * - UNCONFIGURED -> ALIVE -> 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
+ * not using RESET/BLOCK procedure.
+ */
+
+enum gprs_ns2_vc_state {
+ GPRS_NS2_ST_UNCONFIGURED,
+ GPRS_NS2_ST_RESET,
+ GPRS_NS2_ST_BLOCKED,
+ GPRS_NS2_ST_UNBLOCKED, /* allows sending NS_UNITDATA */
+
+ GPRS_NS2_ST_ALIVE, /* only used when not using RESET/BLOCK procedure */
+};
+
+enum gprs_ns2_vc_event {
+ GPRS_NS2_EV_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,
+};
+
+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" },
+ { 0, NULL }
+};
+
+static inline struct gprs_ns2_inst *ns_inst_from_fi(struct osmo_fsm_inst *fi)
+{
+ struct gprs_ns2_vc_priv *priv = fi->priv;
+ return priv->nsvc->nse->nsi;
+}
+
+static void start_test_procedure(struct gprs_ns2_vc_priv *priv)
+{
+ struct gprs_ns2_inst *nsi = priv->nsvc->nse->nsi;
+
+ if (osmo_timer_pending(&priv->alive.timer))
+ 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);
+}
+
+static void stop_test_procedure(struct gprs_ns2_vc_priv *priv)
+{
+ osmo_timer_del(&priv->alive.timer);
+}
+
+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);
+
+ return 1000 * elapsed.tv_sec + elapsed.tv_usec / 1000;
+}
+
+static void recv_test_procedure(struct osmo_fsm_inst *fi)
+{
+ struct gprs_ns2_vc_priv *priv = fi->priv;
+ struct gprs_ns2_inst *nsi = ns_inst_from_fi(fi);
+ struct gprs_ns2_vc *nsvc = priv->nsvc;
+
+ /* ignoring ACKs without sending an ALIVE */
+ if (priv->alive.mode != NS_TOUT_TNS_ALIVE)
+ return;
+
+ 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],
+ alive_timer_elapsed_ms(priv));
+}
+
+
+static void alive_timeout_handler(void *data)
+{
+ struct osmo_fsm_inst *fi = data;
+ struct gprs_ns2_inst *nsi = ns_inst_from_fi(fi);
+ struct gprs_ns2_vc_priv *priv = fi->priv;
+
+ switch (priv->alive.mode) {
+ case NS_TOUT_TNS_TEST:
+ priv->alive.mode = NS_TOUT_TNS_ALIVE;
+ ns2_tx_alive(priv->nsvc);
+ osmo_timer_schedule(&priv->alive.timer, nsi->timeout[NS_TOUT_TNS_ALIVE], 0);
+ break;
+ case NS_TOUT_TNS_ALIVE:
+ priv->alive.N++;
+
+ if (priv->alive.N <= nsi->timeout[NS_TOUT_TNS_ALIVE_RETRIES]) {
+ /* retransmission */
+ ns2_tx_alive(priv->nsvc);
+ osmo_timer_schedule(&priv->alive.timer, nsi->timeout[NS_TOUT_TNS_ALIVE], 0);
+ } else {
+ /* lost connection */
+ if (priv->nsvc->mode == 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);
+ }
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+static void gprs_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;
+
+ switch (event) {
+ case GPRS_NS2_EV_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);
+ break;
+ case 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);
+ }
+}
+
+
+static void gprs_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;
+
+ if (priv->initiater)
+ ns2_tx_reset(priv->nsvc, NS_CAUSE_OM_INTERVENTION);
+
+ stop_test_procedure(priv);
+ ns2_nse_notify_unblocked(priv->nsvc, false);
+}
+
+static void gprs_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->initiater) {
+ switch (event) {
+ case GPRS_NS2_EV_RESET_ACK:
+ osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_BLOCKED,
+ nsi->timeout[NS_TOUT_TNS_BLOCK], NS_TOUT_TNS_BLOCK);
+ break;
+ }
+ } else {
+ /* we are on the receiving end */
+ switch (event) {
+ case GPRS_NS2_EV_RESET:
+ ns2_tx_reset_ack(priv->nsvc);
+ osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_BLOCKED,
+ 0, 0);
+ break;
+ }
+ }
+}
+
+static void gprs_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)
+ priv->N = 0;
+
+ if (priv->initiater)
+ ns2_tx_unblock(priv->nsvc);
+
+ start_test_procedure(priv);
+}
+
+static void gprs_ns2_st_blocked(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gprs_ns2_vc_priv *priv = fi->priv;
+
+ if (priv->initiater) {
+ switch (event) {
+ case GPRS_NS2_EV_BLOCK:
+ /* TODO: BLOCK is a UNBLOCK_NACK */
+ ns2_tx_block_ack(priv->nsvc);
+ break;
+ case GPRS_NS2_EV_UNBLOCK:
+ ns2_tx_unblock_ack(priv->nsvc);
+ /* fall through */
+ case GPRS_NS2_EV_UNBLOCK_ACK:
+ osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_UNBLOCKED,
+ 0, NS_TOUT_TNS_TEST);
+ break;
+ }
+ } else {
+ /* we are on the receiving end. The initiator who sent RESET is responsible to UNBLOCK! */
+ switch (event) {
+ case GPRS_NS2_EV_UNBLOCK:
+ ns2_tx_unblock_ack(priv->nsvc);
+ osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_UNBLOCKED,
+ 0, 0);
+ break;
+ }
+ }
+}
+
+static void gprs_ns2_st_unblocked_on_enter(struct osmo_fsm_inst *fi, uint32_t old_state)
+{
+ struct gprs_ns2_vc_priv *priv = fi->priv;
+
+ ns2_nse_notify_unblocked(priv->nsvc, true);
+}
+
+static void gprs_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:
+ priv->initiater = false;
+ ns2_tx_block_ack(priv->nsvc);
+ 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)
+{
+ switch (event) {
+ case GPRS_NS2_EV_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)
+{
+ struct gprs_ns2_vc_priv *priv = fi->priv;
+ struct gprs_ns2_inst *nsi = ns_inst_from_fi(fi);
+
+ 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)
+ priv->N = 0;
+
+ ns2_tx_alive(priv->nsvc);
+ 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[] = {
+ [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),
+ .name = "UNCONFIGURED",
+ .action = gprs_ns2_st_unconfigured,
+ },
+ [GPRS_NS2_ST_RESET] = {
+ .in_event_mask = S(GPRS_NS2_EV_RESET_ACK) | S(GPRS_NS2_EV_RESET),
+ .out_state_mask = S(GPRS_NS2_ST_RESET) |
+ S(GPRS_NS2_ST_BLOCKED),
+ .name = "RESET",
+ .action = gprs_ns2_st_reset,
+ .onenter = gprs_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),
+ .out_state_mask = S(GPRS_NS2_ST_RESET) |
+ S(GPRS_NS2_ST_UNBLOCKED) |
+ S(GPRS_NS2_ST_BLOCKED),
+ .name = "BLOCKED",
+ .action = gprs_ns2_st_blocked,
+ .onenter = gprs_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) |
+ S(GPRS_NS2_ST_BLOCKED),
+ .name = "UNBLOCKED",
+ .action = gprs_ns2_st_unblocked,
+ .onenter = gprs_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) |
+ S(GPRS_NS2_ST_UNBLOCKED),
+ .name = "ALIVE",
+ .action = gprs_ns2_st_alive,
+ .onenter = gprs_ns2_st_alive_onenter,
+ .onleave = gprs_ns2_st_alive_onleave,
+ },
+};
+
+static int gprs_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;
+
+ if (priv->initiater) {
+ /* PCU timeouts */
+ switch (fi->state) {
+ case GPRS_NS2_ST_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);
+ } else {
+ priv->N = 0;
+ osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_RESET, nsi->timeout[NS_TOUT_TNS_RESET], 0);
+ }
+ break;
+ case GPRS_NS2_ST_BLOCKED:
+ 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);
+ } else {
+ osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_RESET, nsi->timeout[NS_TOUT_TNS_RESET], 0);
+ }
+ break;
+ case GPRS_NS2_ST_ALIVE:
+ priv->N++;
+ if (priv->N <= nsi->timeout[NS_TOUT_TNS_ALIVE_RETRIES]) {
+ osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_ALIVE, 0, 0);
+ } else {
+ priv->N = 0;
+ osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_ALIVE, 0, 0);
+ }
+ break;
+ }
+ }
+ return 0;
+}
+
+static void gprs_ns2_recv_unitdata(struct osmo_fsm_inst *fi,
+ struct msgb *msg)
+{
+ struct gprs_ns2_vc_priv *priv = fi->priv;
+ struct gprs_ns2_inst *nsi = ns_inst_from_fi(fi);
+ struct gprs_ns_hdr *nsh = (struct gprs_ns_hdr *) msg->l2h;
+ struct osmo_gprs_ns2_prim nsp = {};
+ uint16_t bvci;
+
+ if (msgb_l2len(msg) < sizeof(*nsh) + 3)
+ return;
+
+ /* TODO: 7.1: For an IP sub-network, an NS-UNITDATA PDU
+ * for a PTP BVC may indicate a request to change the IP endpoint
+ * and/or a response to a change in the IP endpoint. */
+
+ /* TODO: nsh->data[0] -> C/R only valid in IP SNS */
+ bvci = nsh->data[1] << 8 | nsh->data[2];
+
+ msg->l3h = &nsh->data[3];
+ nsp.bvci = bvci;
+ nsp.nsei = priv->nsvc->nse->nsei;
+
+ /* 10.3.9 NS SDU Control Bits */
+ if (nsh->data[0] & 0x1)
+ nsp.u.unitdata.change = NS_ENDPOINT_REQUEST_CHANGE;
+
+ osmo_prim_init(&nsp.oph, SAP_NS, PRIM_NS_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,
+ uint32_t event,
+ void *data)
+{
+ struct gprs_ns2_vc_priv *priv = fi->priv;
+ struct gprs_ns2_inst *nsi = ns_inst_from_fi(fi);
+
+ switch (event) {
+ case GPRS_NS2_EV_RESET:
+ if (priv->nsvc->mode != NS2_VC_MODE_BLOCKRESET)
+ break;
+
+ /* move the FSM into reset */
+ if (fi->state != GPRS_NS2_ST_RESET) {
+ priv->initiater = false;
+ 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);
+ break;
+ case GPRS_NS2_EV_ALIVE:
+ switch (fi->state) {
+ case GPRS_NS2_ST_UNCONFIGURED:
+ case GPRS_NS2_ST_RESET:
+ /* ignore ALIVE */
+ break;
+ default:
+ ns2_tx_alive_ack(priv->nsvc);
+ }
+ break;
+ case GPRS_NS2_EV_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);
+ else
+ recv_test_procedure(fi);
+ break;
+ case GPRS_NS2_EV_UNITDATA:
+ switch (fi->state) {
+ case GPRS_NS2_ST_BLOCKED:
+ /* 7.2.1: the BLOCKED_ACK might be lost */
+ if (priv->initiater)
+ gprs_ns2_recv_unitdata(fi, data);
+ else
+ ns2_tx_status(priv->nsvc,
+ NS_CAUSE_NSVC_BLOCKED,
+ 0, data);
+ break;
+ /* ALIVE can receive UNITDATA if the ALIVE_ACK is lost */
+ case GPRS_NS2_ST_ALIVE:
+ case GPRS_NS2_ST_UNBLOCKED:
+ gprs_ns2_recv_unitdata(fi, data);
+ break;
+ }
+ break;
+ }
+}
+
+static struct osmo_fsm gprs_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),
+ .allstate_action = gprs_ns2_vc_fsm_allstate_action,
+ .cleanup = NULL,
+ .timer_cb = gprs_ns2_vc_fsm_timer_cb,
+ /* .log_subsys = DNS, "is not constant" */
+ .event_names = gprs_ns2_vc_event_names,
+ .pre_term = NULL,
+ .log_subsys = DLNS,
+};
+
+/*!
+ * \brief gprs_ns2_vc_fsm_alloc
+ * \param ctx
+ * \param vc
+ * \param id a char representation of the virtual curcuit
+ * \param initiater initiater 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,
+ const char *id, bool initiater)
+{
+ 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);
+ if (!fi)
+ return fi;
+
+ nsvc->fi = fi;
+ priv = fi->priv = talloc_zero(fi, struct gprs_ns2_vc_priv);
+ priv->nsvc = nsvc;
+ priv->initiater = initiater;
+
+ osmo_timer_setup(&priv->alive.timer, alive_timeout_handler, fi);
+
+ return fi;
+}
+
+/*! 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)
+{
+ /* 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 0;
+}
+
+/*! entry point for messages from the driver/VL
+ * \param nsvc virtual circuit on which the message was received
+ * \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)
+{
+ struct gprs_ns_hdr *nsh = (struct gprs_ns_hdr *) msg->l2h;
+ struct osmo_fsm_inst *fi = nsvc->fi;
+ uint8_t cause;
+
+ /* 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 (nsh->pdu_type != NS_PDUT_STATUS) {
+ return ns2_tx_status(nsvc, cause, 0, msg);
+ }
+ }
+
+ switch (nsh->pdu_type) {
+ case NS_PDUT_RESET:
+ osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_RESET, tp);
+ break;
+ case NS_PDUT_RESET_ACK:
+ osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_RESET_ACK, tp);
+ break;
+ case NS_PDUT_BLOCK:
+ osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_BLOCK, tp);
+ break;
+ case NS_PDUT_BLOCK_ACK:
+ osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_BLOCK_ACK, tp);
+ break;
+ case NS_PDUT_UNBLOCK:
+ osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_UNBLOCK, tp);
+ break;
+ case NS_PDUT_UNBLOCK_ACK:
+ osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_UNBLOCK_ACK, tp);
+ break;
+ case NS_PDUT_ALIVE:
+ osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_ALIVE, tp);
+ break;
+ case NS_PDUT_ALIVE_ACK:
+ osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_ALIVE_ACK, tp);
+ break;
+ case NS_PDUT_UNITDATA:
+ osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_UNITDATA, msg);
+ 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;
+ }
+
+ return 0;
+}
+
+/*! is the given NS-VC unblocked? */
+int gprs_ns2_vc_is_unblocked(struct gprs_ns2_vc *nsvc)
+{
+ return (nsvc->fi->state == GPRS_NS2_ST_UNBLOCKED);
+}
+
+/* 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);
+}
diff --git a/src/gb/gprs_ns2_vty.c b/src/gb/gprs_ns2_vty.c
new file mode 100644
index 00000000..a457361a
--- /dev/null
+++ b/src/gb/gprs_ns2_vty.c
@@ -0,0 +1,853 @@
+/*! \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>
+ * 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 <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdint.h>
+
+#include <arpa/inet.h>
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/byteswap.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/rate_ctr.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/core/sockaddr_str.h>
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/socket.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 "gprs_ns2_internal.h"
+
+struct ns2_vty_priv {
+ /* global listen */
+ struct osmo_sockaddr_str udp;
+ struct osmo_sockaddr_str frgreaddr;
+ 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;
+};
+
+struct ns2_vty_vc {
+ struct llist_head list;
+
+ struct osmo_sockaddr_str remote;
+ enum gprs_ns_ll ll;
+
+ /* old vty code doesnt support multiple NSVCI per NSEI */
+ uint16_t nsei;
+ uint16_t nsvci;
+ uint16_t frdlci;
+
+ bool remote_end_is_sgsn;
+ bool configured;
+};
+
+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" },
+ { 0, 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;
+ }
+}
+
+static struct cmd_node ns_node = {
+ L_NS_NODE,
+ "%s(config-ns)# ",
+ 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;
+
+ llist_add(&vtyvc->list, &priv.vtyvc);
+
+ return vtyvc;
+}
+
+static void ns2_vc_free(struct ns2_vty_vc *vtyvc) {
+ if (!vtyvc)
+ return;
+
+ llist_del(&vtyvc->list);
+ talloc_free(vtyvc);
+}
+
+static struct ns2_vty_vc *vtyvc_by_nsei(uint16_t nsei, bool alloc_missing) {
+ struct ns2_vty_vc *vtyvc;
+
+ llist_for_each_entry(vtyvc, &priv.vtyvc, list) {
+ if (vtyvc->nsei == nsei)
+ return vtyvc;
+ }
+
+ if (!alloc_missing)
+ return NULL;
+
+ vtyvc = vtyvc_alloc(nsei);
+ if (!vtyvc)
+ return vtyvc;
+
+ vtyvc->nsei = nsei;
+ return vtyvc;
+}
+
+static int config_write_ns(struct vty *vty)
+{
+ struct ns2_vty_vc *vtyvc;
+ unsigned int i;
+ struct osmo_sockaddr_str sockstr;
+
+ vty_out(vty, "ns%s", VTY_NEWLINE);
+
+ /* global configuration must be written first, as some of it may be
+ * relevant when creating the NSE/NSVC later below */
+
+ vty_out(vty, " encapsulation framerelay-gre enabled %u%s",
+ priv.frgre ? 1 : 0, VTY_NEWLINE);
+
+ if (priv.frgre) {
+ if (strlen(priv.frgreaddr.ip)) {
+ vty_out(vty, " encapsulation framerelay-gre local-ip %s%s",
+ sockstr.ip, VTY_NEWLINE);
+ }
+ } else {
+ if (strlen(priv.udp.ip)) {
+ vty_out(vty, " encapsulation udp local-ip %s%s",
+ priv.udp.ip, VTY_NEWLINE);
+ }
+
+ if (priv.udp.port)
+ vty_out(vty, " encapsulation udp local-port %u%s",
+ priv.udp.port, VTY_NEWLINE);
+ }
+
+ if (priv.dscp)
+ vty_out(vty, " encapsulation udp dscp %d%s",
+ priv.dscp, VTY_NEWLINE);
+
+ vty_out(vty, " encapsulation udp use-reset-block-unblock %s%s",
+ priv.vc_mode == NS2_VC_MODE_BLOCKRESET ? "enabled" : "disabled", VTY_NEWLINE);
+
+ llist_for_each_entry(vtyvc, &priv.vtyvc, list) {
+ vty_out(vty, " nse %u nsvci %u%s",
+ vtyvc->nsei, vtyvc->nsvci, VTY_NEWLINE);
+
+ vty_out(vty, " nse %u remote-role %s%s",
+ vtyvc->nsei, vtyvc->remote_end_is_sgsn ? "sgsn" : "bss",
+ VTY_NEWLINE);
+
+ 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);
+ 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);
+ break;
+ default:
+ break;
+ }
+ }
+
+ 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;
+}
+
+DEFUN(cfg_ns, cfg_ns_cmd,
+ "ns",
+ "Configure the GPRS Network Service")
+{
+ vty->node = L_NS_NODE;
+ return CMD_SUCCESS;
+}
+
+static void dump_nsvc(struct vty *vty, struct gprs_ns2_vc *nsvc, bool stats)
+{
+ struct osmo_sockaddr_str remote;
+ struct osmo_sockaddr_str local;
+ const struct osmo_sockaddr *sockaddr;
+
+ switch (nsvc->ll) {
+ case GPRS_NS_LL_UDP: {
+ sockaddr = gprs_ns2_ip_vc_remote(nsvc);
+ if (!sockaddr) {
+ vty_out(vty, "unknown");
+ break;
+ }
+
+ if (osmo_sockaddr_str_from_sockaddr(
+ &remote,
+ &sockaddr->u.sas)) {
+ vty_out(vty, "unknown");
+ break;
+ }
+
+ vty_out(vty, "%s:%u <> %s:%u", local.ip, local.port, remote.ip, remote.port);
+ break;
+ }
+ case GPRS_NS_LL_FR_GRE:
+ /* TODO: implement dump_nse for FR GRE */
+ case GPRS_NS_LL_E1:
+ /* TODO: implement dump_nse for E1 */
+ break;
+ }
+
+ vty_out(vty, "Remote: %s ",
+ gprs_ns2_ll_str(nsvc));
+
+ vty_out(vty, "%s%s", nsvc->ll == GPRS_NS_LL_UDP ? "UDP" : "FR-GRE", VTY_NEWLINE);
+
+ 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;
+
+ vty_out(vty, "NSEI %5u%s",
+ nse->nsei, VTY_NEWLINE);
+
+ 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);
+ }
+ }
+}
+
+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);
+}
+
+static void dump_ns(struct vty *vty, const struct gprs_ns2_inst *nsi, bool stats, bool persistent_only)
+{
+ struct gprs_ns2_vc_bind *bind;
+ struct gprs_ns2_nse *nse;
+
+ llist_for_each_entry(bind, &nsi->binding, list) {
+ dump_bind(vty, bind, stats);
+ }
+
+ llist_for_each_entry(nse, &nsi->nse, list) {
+ dump_nse(vty, nse, stats, persistent_only);
+ }
+
+}
+
+DEFUN(show_ns, show_ns_cmd, "show ns",
+ SHOW_STR "Display information about the NS protocol")
+{
+ dump_ns(vty, vty_nsi, false, false);
+ 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")
+{
+ dump_ns(vty, vty_nsi, true, 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")
+{
+ dump_ns(vty, vty_nsi, true, 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")
+{
+ 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;
+ }
+
+ 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;
+ }
+
+ dump_nsvc(vty, nsvc, show_stats);
+ }
+
+ 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"
+ )
+{
+ struct ns2_vty_vc *vtyvc;
+
+ uint16_t nsei = atoi(argv[0]);
+ uint16_t nsvci = atoi(argv[1]);
+
+ vtyvc = vtyvc_by_nsei(nsei, true);
+ if (!vtyvc) {
+ vty_out(vty, "Can not allocate space %s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ vtyvc->nsvci = nsvci;
+
+ return CMD_SUCCESS;
+}
+
+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")
+{
+ uint16_t nsei = atoi(argv[0]);
+ struct ns2_vty_vc *vtyvc;
+
+ vtyvc = vtyvc_by_nsei(nsei, true);
+ if (!vtyvc) {
+ vty_out(vty, "Can not allocate space %s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ osmo_sockaddr_str_from_str2(&vtyvc->remote, argv[1]);
+
+ 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")
+{
+ uint16_t nsei = atoi(argv[0]);
+ uint16_t port = atoi(argv[1]);
+ struct ns2_vty_vc *vtyvc;
+
+ vtyvc = vtyvc_by_nsei(nsei, true);
+ if (!vtyvc) {
+ vty_out(vty, "Can not allocate space %s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ vtyvc->remote.port = port;
+
+ 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")
+{
+ uint16_t nsei = atoi(argv[0]);
+ uint16_t dlci = atoi(argv[1]);
+ struct ns2_vty_vc *vtyvc;
+
+ vtyvc = vtyvc_by_nsei(nsei, true);
+ if (!vtyvc) {
+ vty_out(vty, "Can not allocate space %s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (vtyvc->ll != GPRS_NS_LL_FR_GRE) {
+ vty_out(vty, "Warning: seting FR DLCI on non-FR NSE%s",
+ VTY_NEWLINE);
+ }
+
+ vtyvc->frdlci = dlci;
+
+ return CMD_SUCCESS;
+}
+
+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")
+{
+ uint16_t nsei = atoi(argv[0]);
+ struct ns2_vty_vc *vtyvc;
+
+ vtyvc = vtyvc_by_nsei(nsei, true);
+ if (!vtyvc) {
+ vty_out(vty, "Can not allocate space %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;
+
+ 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")
+{
+ uint16_t nsei = atoi(argv[0]);
+ struct ns2_vty_vc *vtyvc;
+
+ vtyvc = vtyvc_by_nsei(nsei, true);
+ if (!vtyvc) {
+ vty_out(vty, "Can not allocate space %s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (!strcmp(argv[1], "sgsn"))
+ vtyvc->remote_end_is_sgsn = 1;
+ else
+ vtyvc->remote_end_is_sgsn = 0;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_no_nse, cfg_no_nse_cmd,
+ "no nse <0-65535>",
+ "Delete Persistent NS Entity\n"
+ "Delete " NSE_CMD_STR)
+{
+ uint16_t nsei = atoi(argv[0]);
+ struct ns2_vty_vc *vtyvc;
+
+ vtyvc = vtyvc_by_nsei(nsei, false);
+ if (!vtyvc) {
+ vty_out(vty, "The NSE %d does not exists.%s", nsei, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ ns2_vc_free(vtyvc);
+
+ 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")
+{
+ int idx = get_string_value(gprs_ns_timer_strs, argv[0]);
+ int val = atoi(argv[1]);
+
+ if (idx < 0 || idx >= ARRAY_SIZE(vty_nsi->timeout))
+ return CMD_WARNING;
+
+ vty_nsi->timeout[idx] = val;
+
+ return CMD_SUCCESS;
+}
+
+#define ENCAPS_STR "NS encapsulation options\n"
+
+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")
+{
+ osmo_sockaddr_str_from_str2(&priv.udp, argv[0]);
+
+ 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")
+{
+ unsigned int port = atoi(argv[0]);
+
+ priv.udp.port = port;
+
+ return CMD_SUCCESS;
+}
+
+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")
+{
+ int dscp = atoi(argv[0]);
+ struct gprs_ns2_vc_bind *bind;
+
+ 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);
+ }
+
+ 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")
+{
+ enum gprs_ns2_vc_mode vc_mode;
+ struct gprs_ns2_vc_bind *bind;
+
+ if (!strcmp(argv[0], "enabled"))
+ vc_mode = NS2_VC_MODE_BLOCKRESET;
+ else
+ vc_mode = NS2_VC_MODE_ALIVE;
+
+ 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);
+ return CMD_WARNING;
+ }
+
+ return CMD_SUCCESS;
+ }
+
+ priv.vc_mode = vc_mode;
+
+ llist_for_each_entry(bind, &vty_nsi->binding, list) {
+ gprs_ns2_bind_set_mode(bind, priv.vc_mode);
+ }
+
+ 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")
+{
+ osmo_sockaddr_str_from_str2(&priv.frgreaddr, argv[0]);
+
+ 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")
+{
+ int enabled = atoi(argv[0]);
+
+ priv.frgre = enabled;
+
+ return CMD_SUCCESS;
+}
+
+/* TODO: allow vty to reset/block/unblock nsvc/nsei */
+
+/* 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,
+ "logging filter nsvc nsvci <0-65535>",
+ LOGGING_STR FILTER_STR
+ "Filter based on NS Virtual Connection\n"
+ "Identify NS-VC by NSVCI\n"
+ "Numeric identifier\n")
+{
+ struct log_target *tgt;
+ struct gprs_ns2_vc *nsvc;
+ uint16_t id = atoi(argv[1]);
+
+ log_tgt_mutex_lock();
+ tgt = osmo_log_vty2tgt(vty);
+ if (!tgt) {
+ log_tgt_mutex_unlock();
+ return CMD_WARNING;
+ }
+
+ nsvc = gprs_ns2_nsvc_by_nsvci(vty_nsi, id);
+ if (!nsvc) {
+ vty_out(vty, "No NS-VC by that identifier%s", VTY_NEWLINE);
+ log_tgt_mutex_unlock();
+ return CMD_WARNING;
+ }
+
+ log_set_nsvc_filter(tgt, nsvc);
+ log_tgt_mutex_unlock();
+ 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
+ */
+int gprs_ns2_vty_init(struct gprs_ns2_inst *nsi,
+ const struct osmo_sockaddr_str *default_bind)
+{
+ 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;
+
+ install_lib_element_ve(&show_ns_cmd);
+ install_lib_element_ve(&show_ns_stats_cmd);
+ install_lib_element_ve(&show_ns_pers_cmd);
+ install_lib_element_ve(&show_nse_cmd);
+ install_lib_element_ve(&logging_fltr_nsvc_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);
+ 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)
+{
+ 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;
+
+ if (force) {
+ priv.vc_mode = mode;
+ priv.force_vc_mode_reason = reason;
+ }
+}
diff --git a/src/gb/gprs_ns_vty.c b/src/gb/gprs_ns_vty.c
index 53c71a9a..f27bf6a8 100644
--- a/src/gb/gprs_ns_vty.c
+++ b