diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/Makefile.am | 112 | ||||
-rw-r--r-- | src/codec/Makefile.am | 18 | ||||
-rw-r--r-- | src/codec/ecu.c | 20 | ||||
-rw-r--r-- | src/codec/ecu_fr.c | 387 | ||||
-rw-r--r-- | src/codec/ecu_fr_old.c | 166 | ||||
-rw-r--r-- | src/codec/gsm610.c | 160 | ||||
-rw-r--r-- | src/codec/gsm620.c | 55 | ||||
-rw-r--r-- | src/codec/gsm660.c | 166 | ||||
-rw-r--r-- | src/codec/gsm690.c | 4 | ||||
-rw-r--r-- | src/coding/Makefile.am | 21 | ||||
-rw-r--r-- | src/coding/gsm0503_amr_dtx.c | 191 | ||||
-rw-r--r-- | src/coding/gsm0503_coding.c | 1123 | ||||
-rw-r--r-- | src/coding/gsm0503_interleaving.c | 56 | ||||
-rw-r--r-- | src/coding/gsm0503_mapping.c | 4 | ||||
-rw-r--r-- | src/coding/gsm0503_parity.c | 4 | ||||
-rw-r--r-- | src/coding/gsm0503_tables.c | 4 | ||||
-rw-r--r-- | src/coding/libosmocoding.map | 23 | ||||
-rw-r--r-- | src/core/Makefile.am | 166 | ||||
-rw-r--r-- | src/core/application.c (renamed from src/application.c) | 4 | ||||
-rw-r--r-- | src/core/backtrace.c (renamed from src/backtrace.c) | 4 | ||||
-rw-r--r-- | src/core/base64.c | 195 | ||||
-rw-r--r-- | src/core/bitcomp.c (renamed from src/bitcomp.c) | 4 | ||||
-rw-r--r-- | src/core/bits.c (renamed from src/bits.c) | 14 | ||||
-rw-r--r-- | src/core/bitvec.c (renamed from src/bitvec.c) | 37 | ||||
-rw-r--r-- | src/core/context.c (renamed from src/context.c) | 9 | ||||
-rw-r--r-- | src/core/conv.c (renamed from src/conv.c) | 199 | ||||
-rw-r--r-- | src/core/conv_acc.c (renamed from src/conv_acc.c) | 27 | ||||
-rw-r--r-- | src/core/conv_acc_generic.c (renamed from src/conv_acc_generic.c) | 4 | ||||
-rw-r--r-- | src/core/conv_acc_neon.c (renamed from src/conv_acc_neon.c) | 4 | ||||
-rw-r--r-- | src/core/conv_acc_neon_impl.h (renamed from src/conv_acc_neon_impl.h) | 4 | ||||
-rw-r--r-- | src/core/conv_acc_sse.c (renamed from src/conv_acc_sse.c) | 4 | ||||
-rw-r--r-- | src/core/conv_acc_sse_avx.c (renamed from src/conv_acc_sse_avx.c) | 4 | ||||
-rw-r--r-- | src/core/conv_acc_sse_impl.h (renamed from src/conv_acc_sse_impl.h) | 4 | ||||
-rw-r--r-- | src/core/counter.c (renamed from src/counter.c) | 6 | ||||
-rw-r--r-- | src/core/crc16.c (renamed from src/crc16.c) | 0 | ||||
-rw-r--r-- | src/core/crcXXgen.c.tpl (renamed from src/crcXXgen.c.tpl) | 4 | ||||
-rw-r--r-- | src/core/exec.c (renamed from src/exec.c) | 23 | ||||
-rw-r--r-- | src/core/fsm.c (renamed from src/fsm.c) | 18 | ||||
-rw-r--r-- | src/core/gsmtap_util.c (renamed from src/gsmtap_util.c) | 231 | ||||
-rw-r--r-- | src/core/isdnhdlc.c (renamed from src/isdnhdlc.c) | 11 | ||||
-rw-r--r-- | src/core/it_q.c (renamed from src/it_q.c) | 18 | ||||
-rw-r--r-- | src/core/libosmocore.map | 631 | ||||
-rw-r--r-- | src/core/logging.c (renamed from src/logging.c) | 629 | ||||
-rw-r--r-- | src/core/logging_gsmtap.c (renamed from src/logging_gsmtap.c) | 11 | ||||
-rw-r--r-- | src/core/logging_syslog.c (renamed from src/logging_syslog.c) | 6 | ||||
-rw-r--r-- | src/core/logging_systemd.c (renamed from src/logging_systemd.c) | 4 | ||||
-rw-r--r-- | src/core/loggingrb.c (renamed from src/loggingrb.c) | 4 | ||||
-rw-r--r-- | src/core/macaddr.c (renamed from src/macaddr.c) | 4 | ||||
-rw-r--r-- | src/core/mnl.c (renamed from src/mnl.c) | 9 | ||||
-rw-r--r-- | src/core/msgb.c (renamed from src/msgb.c) | 62 | ||||
-rw-r--r-- | src/core/msgfile.c (renamed from src/msgfile.c) | 4 | ||||
-rw-r--r-- | src/core/netdev.c | 962 | ||||
-rw-r--r-- | src/core/netns.c | 208 | ||||
-rw-r--r-- | src/core/osmo_io.c | 1007 | ||||
-rw-r--r-- | src/core/osmo_io_internal.h | 166 | ||||
-rw-r--r-- | src/core/osmo_io_poll.c | 201 | ||||
-rw-r--r-- | src/core/osmo_io_uring.c | 532 | ||||
-rw-r--r-- | src/core/panic.c (renamed from src/panic.c) | 6 | ||||
-rw-r--r-- | src/core/plugin.c (renamed from src/plugin.c) | 6 | ||||
-rw-r--r-- | src/core/prbs.c (renamed from src/prbs.c) | 0 | ||||
-rw-r--r-- | src/core/prim.c (renamed from src/prim.c) | 0 | ||||
-rw-r--r-- | src/core/probes.d (renamed from src/probes.d) | 0 | ||||
-rw-r--r-- | src/core/rate_ctr.c (renamed from src/rate_ctr.c) | 69 | ||||
-rw-r--r-- | src/core/rbtree.c (renamed from src/rbtree.c) | 5 | ||||
-rw-r--r-- | src/core/select.c (renamed from src/select.c) | 108 | ||||
-rw-r--r-- | src/core/sercomm.c (renamed from src/sercomm.c) | 4 | ||||
-rw-r--r-- | src/core/serial.c (renamed from src/serial.c) | 4 | ||||
-rw-r--r-- | src/core/signal.c (renamed from src/signal.c) | 4 | ||||
-rw-r--r-- | src/core/sockaddr_str.c (renamed from src/sockaddr_str.c) | 26 | ||||
-rw-r--r-- | src/core/socket.c (renamed from src/socket.c) | 1140 | ||||
-rw-r--r-- | src/core/soft_uart.c | 518 | ||||
-rw-r--r-- | src/core/stat_item.c (renamed from src/stat_item.c) | 304 | ||||
-rw-r--r-- | src/core/stat_item_internal.h | 35 | ||||
-rw-r--r-- | src/core/stats.c (renamed from src/stats.c) | 85 | ||||
-rw-r--r-- | src/core/stats_statsd.c (renamed from src/stats_statsd.c) | 6 | ||||
-rw-r--r-- | src/core/stats_tcp.c | 327 | ||||
-rw-r--r-- | src/core/strrb.c (renamed from src/strrb.c) | 8 | ||||
-rw-r--r-- | src/core/tdef.c (renamed from src/tdef.c) | 33 | ||||
-rw-r--r-- | src/core/thread.c (renamed from src/thread.c) | 4 | ||||
-rw-r--r-- | src/core/time_cc.c | 228 | ||||
-rw-r--r-- | src/core/timer.c (renamed from src/timer.c) | 13 | ||||
-rw-r--r-- | src/core/timer_clockgettime.c (renamed from src/timer_clockgettime.c) | 4 | ||||
-rw-r--r-- | src/core/timer_gettimeofday.c (renamed from src/timer_gettimeofday.c) | 4 | ||||
-rw-r--r-- | src/core/tun.c | 577 | ||||
-rw-r--r-- | src/core/use_count.c (renamed from src/use_count.c) | 6 | ||||
-rw-r--r-- | src/core/utils.c (renamed from src/utils.c) | 122 | ||||
-rw-r--r-- | src/core/write_queue.c (renamed from src/write_queue.c) | 28 | ||||
-rw-r--r-- | src/ctrl/Makefile.am | 8 | ||||
-rw-r--r-- | src/ctrl/control_cmd.c | 17 | ||||
-rw-r--r-- | src/ctrl/control_if.c | 53 | ||||
-rw-r--r-- | src/ctrl/control_vty.c | 17 | ||||
-rw-r--r-- | src/ctrl/libosmoctrl.map | 3 | ||||
-rw-r--r-- | src/gb/Makefile.am | 16 | ||||
-rw-r--r-- | src/gb/bssgp_bvc_fsm.c | 6 | ||||
-rw-r--r-- | src/gb/frame_relay.c | 4 | ||||
-rw-r--r-- | src/gb/gprs_bssgp.c | 2 | ||||
-rw-r--r-- | src/gb/gprs_bssgp2.c | 29 | ||||
-rw-r--r-- | src/gb/gprs_bssgp_rim.c | 78 | ||||
-rw-r--r-- | src/gb/gprs_ns.c | 8 | ||||
-rw-r--r-- | src/gb/gprs_ns2.c | 15 | ||||
-rw-r--r-- | src/gb/gprs_ns2_fr.c | 195 | ||||
-rw-r--r-- | src/gb/gprs_ns2_frgre.c | 12 | ||||
-rw-r--r-- | src/gb/gprs_ns2_internal.h | 39 | ||||
-rw-r--r-- | src/gb/gprs_ns2_message.c | 58 | ||||
-rw-r--r-- | src/gb/gprs_ns2_udp.c | 138 | ||||
-rw-r--r-- | src/gb/gprs_ns2_vc_fsm.c | 101 | ||||
-rw-r--r-- | src/gb/gprs_ns2_vty.c | 99 | ||||
-rw-r--r-- | src/gb/libosmogb.map | 3 | ||||
-rw-r--r-- | src/gsm/Makefile.am | 27 | ||||
-rw-r--r-- | src/gsm/a5.c | 4 | ||||
-rw-r--r-- | src/gsm/abis_nm.c | 94 | ||||
-rw-r--r-- | src/gsm/apn.c | 2 | ||||
-rw-r--r-- | src/gsm/auth_comp128v1.c | 7 | ||||
-rw-r--r-- | src/gsm/auth_comp128v23.c | 10 | ||||
-rw-r--r-- | src/gsm/auth_core.c | 172 | ||||
-rw-r--r-- | src/gsm/auth_milenage.c | 35 | ||||
-rw-r--r-- | src/gsm/auth_tuak.c | 207 | ||||
-rw-r--r-- | src/gsm/auth_xor.c | 28 | ||||
-rw-r--r-- | src/gsm/auth_xor_2g.c | 81 | ||||
-rw-r--r-- | src/gsm/bsslap.c | 6 | ||||
-rw-r--r-- | src/gsm/bssmap_le.c | 85 | ||||
-rw-r--r-- | src/gsm/bts_features.c | 19 | ||||
-rw-r--r-- | src/gsm/cbsp.c | 275 | ||||
-rw-r--r-- | src/gsm/comp128.c | 4 | ||||
-rw-r--r-- | src/gsm/comp128v23.c | 4 | ||||
-rw-r--r-- | src/gsm/gea.c | 4 | ||||
-rw-r--r-- | src/gsm/gprs_cipher_core.c | 4 | ||||
-rw-r--r-- | src/gsm/gprs_gea.c | 4 | ||||
-rw-r--r-- | src/gsm/gprs_rlc.c | 3 | ||||
-rw-r--r-- | src/gsm/gsm0341.c | 4 | ||||
-rw-r--r-- | src/gsm/gsm0411_utils.c | 6 | ||||
-rw-r--r-- | src/gsm/gsm0480.c | 4 | ||||
-rw-r--r-- | src/gsm/gsm0502.c | 42 | ||||
-rw-r--r-- | src/gsm/gsm0808.c | 891 | ||||
-rw-r--r-- | src/gsm/gsm0808_utils.c | 716 | ||||
-rw-r--r-- | src/gsm/gsm23003.c | 80 | ||||
-rw-r--r-- | src/gsm/gsm23236.c | 26 | ||||
-rw-r--r-- | src/gsm/gsm29118.c | 4 | ||||
-rw-r--r-- | src/gsm/gsm29205.c | 4 | ||||
-rw-r--r-- | src/gsm/gsm44021.c | 303 | ||||
-rw-r--r-- | src/gsm/gsm44068.c | 121 | ||||
-rw-r--r-- | src/gsm/gsm48.c | 178 | ||||
-rw-r--r-- | src/gsm/gsm48_ie.c | 59 | ||||
-rw-r--r-- | src/gsm/gsm48_rest_octets.c | 76 | ||||
-rw-r--r-- | src/gsm/gsm_utils.c | 70 | ||||
-rw-r--r-- | src/gsm/gsup.c | 125 | ||||
-rw-r--r-- | src/gsm/ipa.c | 35 | ||||
-rw-r--r-- | src/gsm/iuup.c | 1064 | ||||
-rw-r--r-- | src/gsm/kasumi.c | 4 | ||||
-rw-r--r-- | src/gsm/kdf.c | 6 | ||||
-rw-r--r-- | src/gsm/lapdm.c | 315 | ||||
-rw-r--r-- | src/gsm/libosmogsm.map | 101 | ||||
-rw-r--r-- | src/gsm/milenage/milenage.c | 8 | ||||
-rw-r--r-- | src/gsm/mncc.c | 2 | ||||
-rw-r--r-- | src/gsm/rlp.c | 243 | ||||
-rw-r--r-- | src/gsm/rsl.c | 16 | ||||
-rw-r--r-- | src/gsm/rxlev_stat.c | 4 | ||||
-rw-r--r-- | src/gsm/sysinfo.c | 2 | ||||
-rw-r--r-- | src/gsm/tlv_parser.c | 16 | ||||
-rw-r--r-- | src/gsm/tuak/KeccakP-1600-3gpp.c | 176 | ||||
-rw-r--r-- | src/gsm/tuak/KeccakP-1600-3gpp.h | 25 | ||||
-rw-r--r-- | src/gsm/tuak/tuak.c | 372 | ||||
-rw-r--r-- | src/gsm/tuak/tuak.h | 33 | ||||
-rw-r--r-- | src/isdn/Makefile.am | 26 | ||||
-rw-r--r-- | src/isdn/i460_mux.c (renamed from src/gsm/i460_mux.c) | 36 | ||||
-rw-r--r-- | src/isdn/lapd_core.c (renamed from src/gsm/lapd_core.c) | 284 | ||||
-rw-r--r-- | src/isdn/libosmoisdn.map | 52 | ||||
-rw-r--r-- | src/isdn/v110.c | 590 | ||||
-rw-r--r-- | src/isdn/v110_ta.c | 881 | ||||
-rw-r--r-- | src/pseudotalloc/Makefile.am | 3 | ||||
-rw-r--r-- | src/pseudotalloc/pseudotalloc.c | 22 | ||||
-rw-r--r-- | src/pseudotalloc/talloc.h | 3 | ||||
-rw-r--r-- | src/sim/Makefile.am | 11 | ||||
-rw-r--r-- | src/sim/card_fs_hpsim.c | 4 | ||||
-rw-r--r-- | src/sim/card_fs_isim.c | 4 | ||||
-rw-r--r-- | src/sim/card_fs_sim.c | 4 | ||||
-rw-r--r-- | src/sim/card_fs_tetra.c | 4 | ||||
-rw-r--r-- | src/sim/card_fs_uicc.c | 4 | ||||
-rw-r--r-- | src/sim/card_fs_usim.c | 4 | ||||
-rw-r--r-- | src/sim/class_tables.c | 94 | ||||
-rw-r--r-- | src/sim/core.c | 4 | ||||
-rw-r--r-- | src/sim/reader.c | 6 | ||||
-rw-r--r-- | src/sim/reader_pcsc.c | 4 | ||||
-rw-r--r-- | src/usb/Makefile.am | 11 | ||||
-rw-r--r-- | src/usb/osmo_libusb.c | 26 | ||||
-rw-r--r-- | src/vty/Makefile.am | 8 | ||||
-rw-r--r-- | src/vty/command.c | 126 | ||||
-rw-r--r-- | src/vty/cpu_sched_vty.c | 17 | ||||
-rw-r--r-- | src/vty/fsm_vty.c | 8 | ||||
-rw-r--r-- | src/vty/logging_vty.c | 69 | ||||
-rw-r--r-- | src/vty/stats_vty.c | 178 | ||||
-rw-r--r-- | src/vty/talloc_ctx_vty.c | 11 | ||||
-rw-r--r-- | src/vty/tdef_vty.c | 9 | ||||
-rw-r--r-- | src/vty/telnet_interface.c | 59 | ||||
-rw-r--r-- | src/vty/utils.c | 93 | ||||
-rw-r--r-- | src/vty/vector.c | 5 | ||||
-rw-r--r-- | src/vty/vty.c | 20 |
197 files changed, 18589 insertions, 2820 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index 2f18d092..86066466 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,99 +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=17:0:0 - -AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_builddir)/include -AM_CFLAGS = -Wall $(TALLOC_CFLAGS) $(PTHREAD_CFLAGS) $(LIBSCTP_CFLAGS) $(LIBMNL_CFLAGS) - -if ENABLE_PSEUDOTALLOC -AM_CPPFLAGS += -I$(top_srcdir)/src/pseudotalloc -endif - -lib_LTLIBRARIES = libosmocore.la - -libosmocore_la_LIBADD = $(BACKTRACE_LIB) $(TALLOC_LIBS) $(LIBRARY_RT) $(PTHREAD_LIBS) $(LIBSCTP_LIBS) -libosmocore_la_SOURCES = context.c timer.c timer_gettimeofday.c timer_clockgettime.c \ - select.c signal.c msgb.c bits.c \ - bitvec.c bitcomp.c counter.c fsm.c \ - write_queue.c utils.c socket.c \ - logging.c logging_syslog.c logging_gsmtap.c rate_ctr.c \ - gsmtap_util.c crc16.c panic.c backtrace.c \ - conv.c application.c rbtree.c strrb.c \ - loggingrb.c crc8gen.c crc16gen.c crc32gen.c crc64gen.c \ - macaddr.c stat_item.c stats.c stats_statsd.c prim.c \ - conv_acc.c conv_acc_generic.c sercomm.c prbs.c \ - isdnhdlc.c \ - tdef.c \ - thread.c \ - sockaddr_str.c \ - use_count.c \ - exec.c \ - it_q.c \ - probes.d \ - $(NULL) - -if HAVE_SSSE3 -libosmocore_la_SOURCES += conv_acc_sse.c -if HAVE_SSE4_1 -conv_acc_sse.lo : AM_CFLAGS += -mssse3 -msse4.1 -else -conv_acc_sse.lo : AM_CFLAGS += -mssse3 -endif - -if HAVE_AVX2 -libosmocore_la_SOURCES += conv_acc_sse_avx.c -if HAVE_SSE4_1 -conv_acc_sse_avx.lo : AM_CFLAGS += -mssse3 -mavx2 -msse4.1 -else -conv_acc_sse_avx.lo : AM_CFLAGS += -mssse3 -mavx2 -endif -endif -endif - -if HAVE_NEON -libosmocore_la_SOURCES += conv_acc_neon.c -# conv_acc_neon.lo : AM_CFLAGS += -mfpu=neon no, could as well be vfp with neon -endif - -BUILT_SOURCES = crc8gen.c crc16gen.c crc32gen.c crc64gen.c -EXTRA_DIST = conv_acc_sse_impl.h conv_acc_neon_impl.h crcXXgen.c.tpl - -libosmocore_la_LDFLAGS = -version-info $(LIBVERSION) -no-undefined - -if ENABLE_PLUGIN -libosmocore_la_SOURCES += plugin.c -libosmocore_la_LIBADD += $(LIBRARY_DLOPEN) -endif - -if ENABLE_MSGFILE -libosmocore_la_SOURCES += msgfile.c -endif - -if ENABLE_SERIAL -libosmocore_la_SOURCES += serial.c -endif - -if ENABLE_SYSTEMD_LOGGING -libosmocore_la_SOURCES += logging_systemd.c -libosmocore_la_LIBADD += $(SYSTEMD_LIBS) -endif - -if ENABLE_LIBMNL -libosmocore_la_SOURCES += mnl.c -libosmocore_la_LIBADD += $(LIBMNL_LIBS) -endif - -if ENABLE_SYSTEMTAP -probes.h: probes.d - $(DTRACE) -C -h -s $< -o $@ - -probes.lo: probes.d - $(LIBTOOL) --mode=compile $(AM_V_lt) --tag=CC env CFLAGS="$(CFLAGS)" $(DTRACE) -C -G -s $< -o $@ - -BUILT_SOURCES += probes.h probes.lo -libosmocore_la_LIBADD += probes.lo -endif - -crc%gen.c: crcXXgen.c.tpl - $(AM_V_GEN)sed -e's/XX/$*/g' $< > $@ +SUBDIRS = \ + core \ + vty \ + isdn \ + codec \ + gsm \ + coding \ + gb \ + ctrl \ + pseudotalloc \ + sim \ + usb \ + $(NULL)
\ No newline at end of file diff --git a/src/codec/Makefile.am b/src/codec/Makefile.am index 778eb2ad..bb01b9d3 100644 --- a/src/codec/Makefile.am +++ b/src/codec/Makefile.am @@ -1,10 +1,10 @@ # This is _NOT_ the library release version, it's an API version. # Please read chapter "Library interface versions" of the libtool documentation # before making any modifications: https://www.gnu.org/software/libtool/manual/html_node/Versioning.html -LIBVERSION=2:0:2 +LIBVERSION=4:0:0 -AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_builddir)/include $(TALLOC_CFLAGS) -AM_CFLAGS = -Wall +AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_builddir)/include -I$(top_builddir) +AM_CFLAGS = -Wall $(TALLOC_CFLAGS) if ENABLE_PSEUDOTALLOC AM_CPPFLAGS += -I$(top_srcdir)/src/pseudotalloc @@ -13,6 +13,14 @@ endif lib_LTLIBRARIES = libosmocodec.la -libosmocodec_la_SOURCES = gsm610.c gsm620.c gsm660.c gsm690.c ecu.c ecu_fr.c +libosmocodec_la_SOURCES = \ + gsm610.c \ + gsm620.c \ + gsm660.c \ + gsm690.c \ + ecu.c \ + ecu_fr.c \ + ecu_fr_old.c \ + $(NULL) libosmocodec_la_LDFLAGS = -version-info $(LIBVERSION) -no-undefined -libosmocodec_la_LIBADD = $(top_builddir)/src/libosmocore.la +libosmocodec_la_LIBADD = $(top_builddir)/src/core/libosmocore.la diff --git a/src/codec/ecu.c b/src/codec/ecu.c index db7148ce..cdb3e62e 100644 --- a/src/codec/ecu.c +++ b/src/codec/ecu.c @@ -14,10 +14,6 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ /* As the developer and copyright holder of the related code, I hereby @@ -48,7 +44,7 @@ static const struct osmo_ecu_ops *g_ecu_ops[_NUM_OSMO_ECU_CODECS]; /*! initialize an ECU instance for given codec. * \param[in] ctx talloc context from which to allocate - * \parma[in] codec codec for which to initialize/create ECU */ + * \param[in] codec codec for which to initialize/create ECU */ struct osmo_ecu_state *osmo_ecu_init(void *ctx, enum osmo_ecu_codec codec) { if (codec >= ARRAY_SIZE(g_ecu_ops)) @@ -100,6 +96,20 @@ int osmo_ecu_frame_out(struct osmo_ecu_state *st, uint8_t *frame_out) return g_ecu_ops[st->codec]->frame_out(st, frame_out); } +/*! check if the current state of this ECU is a DTX pause. + * \param[in] st ECU state/instance on which to operate + * \return true if DTX pause, false otherwise */ +bool osmo_ecu_is_dtx_pause(struct osmo_ecu_state *st) +{ + if (st->codec >= ARRAY_SIZE(g_ecu_ops)) + return false; + if (!g_ecu_ops[st->codec]) + return false; + if (!g_ecu_ops[st->codec]->is_dtx_pause) + return false; + return g_ecu_ops[st->codec]->is_dtx_pause(st); +} + /*********************************************************************** * low-level API for ECU implementations ***********************************************************************/ diff --git a/src/codec/ecu_fr.c b/src/codec/ecu_fr.c index 4545172a..45ba0cc8 100644 --- a/src/codec/ecu_fr.c +++ b/src/codec/ecu_fr.c @@ -1,9 +1,15 @@ /* * (C) 2017 by sysmocom - s.f.m.c. GmbH * (C) 2017 by Philipp Maier <pmaier@sysmocom.de> - * * All Rights Reserved * + * Significantly reworked in 2023 by Mother + * Mychaela N. Falconia <falcon@freecalypso.org> - however, + * Mother Mychaela's contributions are NOT subject to copyright. + * No rights reserved, all rights relinquished. + * Portions of this code are based on Themyscira libgsmfrp, + * a public domain library by the same author. + * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or @@ -14,10 +20,46 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * + * The present ECU implementation for GSM-FR is closely based on the + * TS 46.011 spec from 3GPP; more specifically, it is based on the + * Example solution presented in Chapter 6 of that spec, adapted for + * libosmocodec ECU architecture, and comes as close to fulfilling + * the spec's officially stated requirements (Chapter 5) as is + * possible within this Osmocom-imposed architecture. Please note + * the following areas where the present implementation fails to + * fulfill the original intent of GSM spec authors: + * + * - The "lost SID" criterion, defined in GSM 06.31, is based on the + * TAF bit from the Radio Subsystem. However, libosmocodec ECU API + * does not include this flag, thus spec requirements related to + * lost SID conditions cannot be implemented in a strictly compliant + * manner. The present implementation improvises its own "lost SID" + * detector (not strictly spec-compliant) by counting frame_out() + * calls in between good traffic frame inputs via frame_in(). + * + * - In the architecture envisioned and assumed in the GSM specs, + * the ECU function of GSM 06.11 was never intended to be a fully + * modular component with its own bona fide I/O interfaces - this + * approach appears to be an Osmocom invention - instead this ECU + * function was intended to be subsumed in the Rx DTX handler + * component of GSM 06.31, also incorporating the comfort noise + * generator of GSM 06.12 - and unlike the narrower-scope ECU, + * this slightly-larger-scope Rx DTX handler is a modular component + * with well-defined I/O interfaces. In the case of BFI conditions + * following a SID, GSM 06.11 spec was written with the assumption + * that the ECU controls the comfort noise generator via internal + * signals, as opposed to emitting "corrected" SID frames on a + * modular interface going to a CN generator located somewhere else. + * Thus the "correct" behavior for a fully modularized ECU is unclear, + * and an argument can be made that the very existence of such a + * fully modularized ECU is incorrect in itself. The present + * implementation re-emits a "rejuvenated" form of the last saved + * SID frame during BFI conditions following a SID within the + * permitted window of 48 frames, then starts emitting muted SIDs + * with Xmaxc decreasing by 4 on each frame, and finally switches + * to emitting non-SID silence frames (Table 1 of TS 46.011) + * once Xmaxc reaches 0. */ #include <stdbool.h> @@ -25,144 +67,216 @@ #include <stdint.h> #include <errno.h> -#include <osmocom/core/bitvec.h> +#include <osmocom/core/prbs.h> -#include <osmocom/codec/gsm610_bits.h> #include <osmocom/codec/codec.h> #include <osmocom/codec/ecu.h> +#include <osmocom/core/linuxlist.h> + +/* See TS 46.011, Chapter 6 Example solution */ +#define GSM611_XMAXC_REDUCE 4 + +/* The first 5 bytes of RTP encoding neatly contain the magic nibble + * and LARc parameters, which also happens to be the part of SID frames + * that needs to be passed through as-is. */ +#define SID_PREFIX_LEN 5 + +enum ecu_principal_state { + STATE_NO_DATA, + STATE_SPEECH, + STATE_SP_MUTING, + STATE_SID, + STATE_SID_MUTING, +}; -/* See also GSM 06.11, chapter 6 Example solution */ -#define GSM610_XMAXC_REDUCE 4 -#define GSM610_XMAXC_LEN 6 +struct fr_ecu_state { + struct osmo_ecu_state ecu_state; + enum ecu_principal_state pr_state; + uint8_t speech_frame[GSM_FR_BYTES]; + uint8_t sid_prefix[SID_PREFIX_LEN]; + uint8_t sid_xmaxc; + uint8_t sid_reemit_count; + struct osmo_prbs_state prng; + bool last_input_was_sid; +}; -/** - * Reduce the XMAXC field. When the XMAXC field reaches - * zero the function will return true. +/* This function is the frame input to the ECU - all inputs to this + * function have been received by the Radio Subsystem as good traffic + * frames in the GSM 06.31 definition. */ -static bool reduce_xmaxcr(struct bitvec *frame_bitvec, - const unsigned int index) +static void fr_ecu_input(struct fr_ecu_state *fr, const uint8_t *frame) { - unsigned int field_index; - uint64_t field; - - field_index = index; - field = bitvec_read_field(frame_bitvec, &field_index, GSM610_XMAXC_LEN); - if (field > GSM610_XMAXC_REDUCE) - field -= GSM610_XMAXC_REDUCE; - else - field = 0; - - field_index = index; - bitvec_write_field(frame_bitvec, &field_index, field, GSM610_XMAXC_LEN); - - return field == 0; + enum osmo_gsm631_sid_class sidc; + + sidc = osmo_fr_sid_classify(frame); + switch (sidc) { + case OSMO_GSM631_SID_CLASS_SPEECH: + memcpy(fr->speech_frame, frame, GSM_FR_BYTES); + fr->pr_state = STATE_SPEECH; + fr->last_input_was_sid = false; + return; + case OSMO_GSM631_SID_CLASS_INVALID: + /* GSM 06.31 section 6.1.2 says: "an invalid SID frame + * shall be substituted by the last valid SID frame + * and the procedure for valid SID frames be applied." + * However, libosmocodec ECU architecture prevents us + * from doing what the spec says: the frame_in() method + * gets a const frame that can't be modified, and + * frame_out() will never get called when BFI=0, even + * when the "good traffic frame" (in the BFI=0 sense) + * is an invalid SID by the bit-counting rule. + * Thus there is no place where we can re-emit a cached + * copy of the last valid SID upon receiving an invalid SID. + * + * In the standard GSM architecture this problem never + * arises because the ECU is not a separate component + * but is coupled with the CN generator, thus the output + * from the Rx DTX handler block will be a CN frame, + * for both valid-SID and invalid-SID inputs to the block. + * But what can we do within the constraints of libosmocodec + * ECU framework? We treat the invalid SID almost like a + * BFI, doing almost nothing in the frame_in() method, + * but we reset sid_reemit_count because by the rules of + * GSM 06.31 an invalid SID is still an accepted SID frame + * for the purpose of "lost SID" logic. */ + fr->sid_reemit_count = 0; + fr->last_input_was_sid = true; + return; + case OSMO_GSM631_SID_CLASS_VALID: + /* save LARc part */ + memcpy(fr->sid_prefix, frame, SID_PREFIX_LEN); + /* save Xmaxc from the last subframe */ + fr->sid_xmaxc = ((frame[27] & 0x1F) << 1) | (frame[28] >> 7); + fr->pr_state = STATE_SID; + fr->sid_reemit_count = 0; + fr->last_input_was_sid = true; + return; + default: + /* There are only 3 possible SID classifications per GSM 06.31 + * section 6.1.1, thus any other return value is a grave error + * in the code. */ + OSMO_ASSERT(0); + } } -/** - * Reduce all XMAXC fields in the frame. When all XMAXC fields - * reach zero, then the function will return true. +/* Reduce all 4 Xmaxc fields in the frame. When all 4 Xmaxc fields + * reach 0, the function will return true for "mute". */ -static bool reduce_xmaxcr_all(struct bitvec *frame_bitvec) +static bool reduce_xmaxc(uint8_t *frame) { - bool silent = true; - - silent &= reduce_xmaxcr(frame_bitvec, GSM610_RTP_XMAXC00); - silent &= reduce_xmaxcr(frame_bitvec, GSM610_RTP_XMAXC10); - silent &= reduce_xmaxcr(frame_bitvec, GSM610_RTP_XMAXC20); - silent &= reduce_xmaxcr(frame_bitvec, GSM610_RTP_XMAXC30); - - return silent; + bool mute_flag = true; + uint8_t sub, xmaxc; + + for (sub = 0; sub < 4; sub++) { + xmaxc = ((frame[sub*7+6] & 0x1F) << 1) | (frame[sub*7+7] >> 7); + if (xmaxc > GSM611_XMAXC_REDUCE) { + xmaxc -= GSM611_XMAXC_REDUCE; + mute_flag = false; + } else + xmaxc = 0; + frame[sub*7+6] &= 0xE0; + frame[sub*7+6] |= xmaxc >> 1; + frame[sub*7+7] &= 0x7F; + frame[sub*7+7] |= (xmaxc & 1) << 7; + } + return mute_flag; } -/* Use certain modifications to conceal the errors in a full rate frame */ -static int conceal_frame(uint8_t *frame) +/* TS 46.011 chapter 6, paragraph 4, last sentence: "The grid position + * parameters are chosen randomly between 0 and 3 during this time." + * (The "during this time" qualifier refers to the speech muting state.) + * This sentence in the spec must have been overlooked by previous ECU + * implementors, as this aspect of the muting logic was missing. + */ +static void random_grid_pos(struct fr_ecu_state *fr, uint8_t *frame) { - struct bitvec *frame_bitvec; - unsigned int len; - bool silent; - int rc = 0; - - /* In case we already deal with a silent frame, - * there is nothing to, we just abort immediately */ - if (osmo_fr_check_sid(frame, GSM_FR_BYTES)) - return 0; - - /* Attempt to allocate memory for bitvec */ - frame_bitvec = bitvec_alloc(GSM_FR_BYTES, NULL); - if (!frame_bitvec) - return -ENOMEM; + uint8_t sub; - /* Convert a frame to bitvec */ - len = bitvec_unpack(frame_bitvec, frame); - if (len != GSM_FR_BYTES) { - rc = -EIO; - goto leave; - } - - /* Fudge frame parameters */ - silent = reduce_xmaxcr_all(frame_bitvec); - - /* If we reached silence level, mute the frame - * completely, this also means that we can - * save the bitvec_pack operation */ - if (silent) { - memset(frame, 0x00, GSM_FR_BYTES); - frame[0] = 0xd0; - goto leave; + for (sub = 0; sub < 4; sub++) { + frame[sub*7+6] &= 0x9F; + frame[sub*7+6] |= osmo_prbs_get_ubit(&fr->prng) << 6; + frame[sub*7+6] |= osmo_prbs_get_ubit(&fr->prng) << 5; } +} - /* Convert back to packed byte form */ - len = bitvec_pack(frame_bitvec, frame); - if (len != GSM_FR_BYTES) { - rc = -EIO; - goto leave; +/* Like reduce_xmaxc() above, but for comfort noise rather than speech. */ +static bool reduce_xmaxc_sid(struct fr_ecu_state *fr) +{ + if (fr->sid_xmaxc > GSM611_XMAXC_REDUCE) { + fr->sid_xmaxc -= GSM611_XMAXC_REDUCE; + return false; } - -leave: - bitvec_free(frame_bitvec); - return rc; + fr->sid_xmaxc = 0; + return true; } -/*! - * To be called when a good frame is received. - * This function will then create a backup of the frame - * and reset the internal state. - * \param[in] state The state object for the ECU - * \param[out] frame The valid frame (GSM_FR_BYTES bytes in RTP payload format) +/* This function implements the part which is peculiar to the present + * "standalone" packaging of GSM-FR ECU, without a directly coupled + * comfort noise generator - it re-emits synthetic SID frames during + * DTX pauses, initially unchanged from the saved SID and later muted. */ -void osmo_ecu_fr_reset(struct osmo_ecu_fr_state *state, const uint8_t *frame) +static void reemit_sid(struct fr_ecu_state *fr, uint8_t *frame) { - state->subsequent_lost_frame = false; - memcpy(state->frame_backup, frame, GSM_FR_BYTES); + uint8_t *p, sub; + + memcpy(frame, fr->sid_prefix, SID_PREFIX_LEN); + p = frame + SID_PREFIX_LEN; + for (sub = 0; sub < 4; sub++) { + *p++ = 0; + *p++ = fr->sid_xmaxc >> 1; + *p++ = (fr->sid_xmaxc & 1) << 7; + *p++ = 0; + *p++ = 0; + *p++ = 0; + *p++ = 0; + } } -/*! - * To be called when a bad frame is received. - * This function will then generate a replacement frame - * that can be used to conceal the dropout. - * \param[in] state The state object for the ECU - * \param[out] frame The buffer to fill with GSM_FR_BYTES of replacement frame - * \returns 0 if the frame was sucessfully filled +/* This function is responsible for generating the ECU's output + * in the event that the Radio Subsystem does not have a good + * traffic frame - conditions corresponding to BFI=1 in the specs. */ -int osmo_ecu_fr_conceal(struct osmo_ecu_fr_state *state, uint8_t *frame) +static void fr_ecu_output(struct fr_ecu_state *fr, uint8_t *frame) { - int rc; - - /* For subsequent frames we run the error concealment - * functions on the backed up frame before we restore - * the backup */ - if (state->subsequent_lost_frame) { - rc = conceal_frame(state->frame_backup); - if (rc) - return rc; + bool mute; + + switch (fr->pr_state) { + case STATE_NO_DATA: + memcpy(frame, osmo_gsm611_silence_frame, GSM_FR_BYTES); + return; + case STATE_SPEECH: + /* TS 46.011 chapter 6: "The first lost speech frame is + * replaced at the speech decoder input by the previous + * good speech frame." */ + memcpy(frame, fr->speech_frame, GSM_FR_BYTES); + fr->pr_state = STATE_SP_MUTING; + return; + case STATE_SP_MUTING: + mute = reduce_xmaxc(fr->speech_frame); + memcpy(frame, fr->speech_frame, GSM_FR_BYTES); + random_grid_pos(fr, frame); + if (mute) + fr->pr_state = STATE_NO_DATA; + return; + case STATE_SID: + fr->sid_reemit_count++; + if (fr->sid_reemit_count >= 48) { + fr->pr_state = STATE_SID_MUTING; + reduce_xmaxc_sid(fr); + } + reemit_sid(fr, frame); + return; + case STATE_SID_MUTING: + if (reduce_xmaxc_sid(fr)) { + fr->pr_state = STATE_NO_DATA; + memcpy(frame, osmo_gsm611_silence_frame, GSM_FR_BYTES); + } else + reemit_sid(fr, frame); + return; + default: + /* a severe bug in the state machine! */ + OSMO_ASSERT(0); } - - /* Restore the backed up frame and set flag in case - * we receive even more bad frames */ - memcpy(frame, state->frame_backup, GSM_FR_BYTES); - state->subsequent_lost_frame = true; - - return 0; } /*********************************************************************** @@ -171,44 +285,57 @@ int osmo_ecu_fr_conceal(struct osmo_ecu_fr_state *state, uint8_t *frame) static struct osmo_ecu_state *ecu_fr_init(void *ctx, enum osmo_ecu_codec codec) { - struct osmo_ecu_state *st; - size_t size = sizeof(*st) + sizeof(struct osmo_ecu_fr_state); + struct fr_ecu_state *fr; - st = talloc_named_const(ctx, size, "ecu_state_FR"); - if (!st) - return NULL; + fr = talloc_zero(ctx, struct fr_ecu_state); + fr->ecu_state.codec = codec; + fr->pr_state = STATE_NO_DATA; + osmo_prbs_state_init(&fr->prng, &osmo_prbs15); - memset(st, 0, size); - st->codec = codec; + return (struct osmo_ecu_state *) fr; +} - return st; +static inline struct fr_ecu_state *_osmo_ecu_state_get_fr(struct osmo_ecu_state *st) +{ + return (struct fr_ecu_state *)container_of(st, struct fr_ecu_state, ecu_state); } static int ecu_fr_frame_in(struct osmo_ecu_state *st, bool bfi, const uint8_t *frame, unsigned int frame_bytes) { - struct osmo_ecu_fr_state *fr = (struct osmo_ecu_fr_state *) &st->data; + struct fr_ecu_state *fr = _osmo_ecu_state_get_fr(st); + if (bfi) return 0; + if (frame_bytes != GSM_FR_BYTES) + return 0; + if ((frame[0] & 0xF0) != 0xD0) + return 0; - osmo_ecu_fr_reset(fr, frame); + fr_ecu_input(fr, frame); return 0; } static int ecu_fr_frame_out(struct osmo_ecu_state *st, uint8_t *frame_out) { - struct osmo_ecu_fr_state *fr = (struct osmo_ecu_fr_state *) &st->data; + struct fr_ecu_state *fr = _osmo_ecu_state_get_fr(st); + + fr_ecu_output(fr, frame_out); + return GSM_FR_BYTES; +} + +static bool ecu_fr_is_dtx_pause(struct osmo_ecu_state *st) +{ + struct fr_ecu_state *fr = _osmo_ecu_state_get_fr(st); - if (osmo_ecu_fr_conceal(fr, frame_out) == 0) - return GSM_FR_BYTES; - else - return -1; + return fr->last_input_was_sid; } static const struct osmo_ecu_ops osmo_ecu_ops_fr = { .init = ecu_fr_init, .frame_in = ecu_fr_frame_in, .frame_out = ecu_fr_frame_out, + .is_dtx_pause = ecu_fr_is_dtx_pause, }; static __attribute__((constructor)) void on_dso_load_ecu_fr(void) diff --git a/src/codec/ecu_fr_old.c b/src/codec/ecu_fr_old.c new file mode 100644 index 00000000..cfefce14 --- /dev/null +++ b/src/codec/ecu_fr_old.c @@ -0,0 +1,166 @@ +/* + * (C) 2017 by sysmocom - s.f.m.c. GmbH + * (C) 2017 by Philipp Maier <pmaier@sysmocom.de> + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * + * This module implements legacy, deprecated osmo_ecu_fr_reset() and + * osmo_ecu_fr_conceal() functions only - see ecu_fr.c for the new + * GSM-FR ECU implementation. + */ + +#include <stdbool.h> +#include <string.h> +#include <stdint.h> +#include <errno.h> + +#include <osmocom/core/bitvec.h> + +#include <osmocom/codec/gsm610_bits.h> +#include <osmocom/codec/codec.h> +#include <osmocom/codec/ecu.h> + +/* See also GSM 06.11, chapter 6 Example solution */ +#define GSM610_XMAXC_REDUCE 4 +#define GSM610_XMAXC_LEN 6 + +/** + * Reduce the XMAXC field. When the XMAXC field reaches + * zero the function will return true. + */ +static bool reduce_xmaxcr(struct bitvec *frame_bitvec, + const unsigned int index) +{ + unsigned int field_index; + uint64_t field; + + field_index = index; + field = bitvec_read_field(frame_bitvec, &field_index, GSM610_XMAXC_LEN); + if (field > GSM610_XMAXC_REDUCE) + field -= GSM610_XMAXC_REDUCE; + else + field = 0; + + field_index = index; + bitvec_write_field(frame_bitvec, &field_index, field, GSM610_XMAXC_LEN); + + return field == 0; +} + +/** + * Reduce all XMAXC fields in the frame. When all XMAXC fields + * reach zero, then the function will return true. + */ +static bool reduce_xmaxcr_all(struct bitvec *frame_bitvec) +{ + bool silent = true; + + silent &= reduce_xmaxcr(frame_bitvec, GSM610_RTP_XMAXC00); + silent &= reduce_xmaxcr(frame_bitvec, GSM610_RTP_XMAXC10); + silent &= reduce_xmaxcr(frame_bitvec, GSM610_RTP_XMAXC20); + silent &= reduce_xmaxcr(frame_bitvec, GSM610_RTP_XMAXC30); + + return silent; +} + +/* Use certain modifications to conceal the errors in a full rate frame */ +static int conceal_frame(uint8_t *frame) +{ + struct bitvec *frame_bitvec; + unsigned int len; + bool silent; + int rc = 0; + + /* In case we already deal with a silent frame, + * there is nothing to, we just abort immediately */ + if (osmo_fr_check_sid(frame, GSM_FR_BYTES)) + return 0; + + /* Attempt to allocate memory for bitvec */ + frame_bitvec = bitvec_alloc(GSM_FR_BYTES, NULL); + if (!frame_bitvec) + return -ENOMEM; + + /* Convert a frame to bitvec */ + len = bitvec_unpack(frame_bitvec, frame); + if (len != GSM_FR_BYTES) { + rc = -EIO; + goto leave; + } + + /* Fudge frame parameters */ + silent = reduce_xmaxcr_all(frame_bitvec); + + /* If we reached silence level, mute the frame + * completely, this also means that we can + * save the bitvec_pack operation */ + if (silent) { + memset(frame, 0x00, GSM_FR_BYTES); + frame[0] = 0xd0; + goto leave; + } + + /* Convert back to packed byte form */ + len = bitvec_pack(frame_bitvec, frame); + if (len != GSM_FR_BYTES) { + rc = -EIO; + goto leave; + } + +leave: + bitvec_free(frame_bitvec); + return rc; +} + +/*! + * To be called when a good frame is received. + * This function will then create a backup of the frame + * and reset the internal state. + * \param[in] state The state object for the ECU + * \param[out] frame The valid frame (GSM_FR_BYTES bytes in RTP payload format) + */ +void osmo_ecu_fr_reset(struct osmo_ecu_fr_state *state, const uint8_t *frame) +{ + state->subsequent_lost_frame = false; + memcpy(state->frame_backup, frame, GSM_FR_BYTES); +} + +/*! + * To be called when a bad frame is received. + * This function will then generate a replacement frame + * that can be used to conceal the dropout. + * \param[in] state The state object for the ECU + * \param[out] frame The buffer to fill with GSM_FR_BYTES of replacement frame + * \returns 0 if the frame was successfully filled + */ +int osmo_ecu_fr_conceal(struct osmo_ecu_fr_state *state, uint8_t *frame) +{ + int rc; + + /* For subsequent frames we run the error concealment + * functions on the backed up frame before we restore + * the backup */ + if (state->subsequent_lost_frame) { + rc = conceal_frame(state->frame_backup); + if (rc) + return rc; + } + + /* Restore the backed up frame and set flag in case + * we receive even more bad frames */ + memcpy(frame, state->frame_backup, GSM_FR_BYTES); + state->subsequent_lost_frame = true; + + return 0; +} diff --git a/src/codec/gsm610.c b/src/codec/gsm610.c index a05eabab..5cc4f148 100644 --- a/src/codec/gsm610.c +++ b/src/codec/gsm610.c @@ -17,10 +17,6 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ #include <stdint.h> @@ -300,6 +296,35 @@ const uint16_t gsm610_bitorder[260] = { 29, /* LARc5:0 */ }; +/* + * Table 1 in section 6 of 3GPP TS 46.011 (substitution and muting of lost + * frames) specifies a silence frame in the form of GSM 06.10 parameters; + * the following const datum is this GSM 06.11 silence frame in GSM-FR + * RTP encoding. + */ +const uint8_t osmo_gsm611_silence_frame[GSM_FR_BYTES] = { + 0xDA, 0xA7, 0xAA, 0xA5, 0x1A, + 0x50, 0x20, 0x38, 0xE4, 0x6D, 0xB9, 0x1B, + 0x50, 0x20, 0x38, 0xE4, 0x6D, 0xB9, 0x1B, + 0x50, 0x20, 0x38, 0xE4, 0x6D, 0xB9, 0x1B, + 0x50, 0x20, 0x38, 0xE4, 0x6D, 0xB9, 0x1B, +}; + +static const uint16_t sid_code_word_bits[95] = { + /* bit numbers are relative to the RTP frame beginning, + * with signature bits included in the count. */ + 57, 58, 60, 61, 63, 64, 66, 67, 69, 70, 72, 73, + 75, 76, 78, 79, 81, 82, 84, 85, 87, 88, 90, 91, + 93, 94, 113, 114, 116, 117, 119, 120, 122, 123, + 125, 126, 128, 129, 131, 132, 134, 135, 137, + 138, 140, 141, 143, 144, 146, 147, 149, 150, + 169, 170, 172, 173, 175, 176, 178, 179, 181, + 182, 184, 185, 187, 188, 190, 191, 193, 194, + 196, 197, 199, 200, 202, 203, 205, 206, 225, + 226, 228, 229, 231, 232, 234, 235, 237, 240, + 243, 246, 249, 252, 255, 258, 261 +}; + /*! Check whether RTP frame contains FR SID code word according to * TS 101 318 §5.1.2 * \param[in] rtp_payload Buffer with RTP payload @@ -309,16 +334,7 @@ const uint16_t gsm610_bitorder[260] = { bool osmo_fr_check_sid(const uint8_t *rtp_payload, size_t payload_len) { struct bitvec bv; - uint16_t i, z_bits[] = { 57, 58, 60, 61, 63, 64, 66, 67, 69, 70, 72, 73, - 75, 76, 78, 79, 81, 82, 84, 85, 87, 88, 90, 91, - 93, 94, 113, 114, 116, 117, 119, 120, 122, 123, - 125, 126, 128, 129, 131, 132, 134, 135, 137, - 138, 140, 141, 143, 144, 146, 147, 149, 150, - 169, 170, 172, 173, 175, 176, 178, 179, 181, - 182, 184, 185, 187, 188, 190, 191, 193, 194, - 196, 197, 199, 200, 202, 203, 205, 206, 225, - 226, 228, 229, 231, 232, 234, 235, 237, 240, - 243, 246, 249, 252, 255, 258, 261 }; + uint16_t i; /* signature does not match Full Rate SID */ if ((rtp_payload[0] >> 4) != 0xD) @@ -327,10 +343,120 @@ bool osmo_fr_check_sid(const uint8_t *rtp_payload, size_t payload_len) bv.data = (uint8_t *) rtp_payload; bv.data_len = payload_len; - /* code word is all 0 at given bits, numbered from 1 */ - for (i = 0; i < ARRAY_SIZE(z_bits); i++) - if (bitvec_get_bit_pos(&bv, z_bits[i]) != ZERO) + /* code word is all 0 at given bits */ + for (i = 0; i < ARRAY_SIZE(sid_code_word_bits); i++) { + if (bitvec_get_bit_pos(&bv, sid_code_word_bits[i]) != ZERO) return false; + } return true; } + +/*! Classify potentially-SID FR codec frame in RTP format according + * to the rules of GSM 06.31 §6.1.1 + * \param[in] rtp_payload Buffer with RTP payload + * \returns enum osmo_gsm631_sid_class, with symbolic values + * OSMO_GSM631_SID_CLASS_SPEECH, OSMO_GSM631_SID_CLASS_INVALID or + * OSMO_GSM631_SID_CLASS_VALID corresponding to the 3 possible bit-counting + * classifications prescribed by the spec. + * + * Differences between the more familiar osmo_fr_check_sid() and the present + * function are: + * + * 1. osmo_fr_check_sid() returns true only if the SID frame is absolutely + * perfect, with all 95 bits of the SID code word zeroed. However, the + * rules of GSM 06.31 §6.1.1 allow up to one bit to be in error, + * and the frame is still accepted as valid SID. + * + * 2. The third possible state of invalid SID is not handled at all by the + * simpler osmo_fr_check_sid() function. + * + * 3. osmo_fr_check_sid() includes a check for 0xD RTP signature, and returns + * false if that signature nibble is wrong. That check is not included + * in the present version because there is no place for it in the + * ETSI-prescribed classification, it is neither speech nor SID. The + * assumption is that this function is used to classify the bit content + * of received codec frames, not their RTP encoding - the latter needs + * to be validated beforehand. + * + * Which function should one use? The answer depends on the specific + * circumstances, and needs to be addressed on a case-by-case basis. + */ +enum osmo_gsm631_sid_class osmo_fr_sid_classify(const uint8_t *rtp_payload) +{ + struct bitvec bv; + uint16_t i, n; + + bv.data = (uint8_t *) rtp_payload; + bv.data_len = GSM_FR_BYTES; + + /* count not-SID-matching bits per the spec */ + n = 0; + for (i = 0; i < ARRAY_SIZE(sid_code_word_bits); i++) { + if (bitvec_get_bit_pos(&bv, sid_code_word_bits[i]) != ZERO) + n++; + if (n >= 16) + return OSMO_GSM631_SID_CLASS_SPEECH; + } + if (n >= 2) + return OSMO_GSM631_SID_CLASS_INVALID; + else + return OSMO_GSM631_SID_CLASS_VALID; +} + +/*! Reset the SID field and the unused bits of a potentially corrupted, + * but still valid GSM-FR SID frame in RTP encoding to their pristine state. + * \param[in] rtp_payload Buffer with RTP payload - must be writable! + * + * Per GSM 06.12 section 5.2, a freshly minted SID frame carries 60 bits + * of comfort noise parameters (LARc and 4 times Xmaxc), while the remaining + * 200 bits are all zeros; the latter 200 all-0 bits further break down into + * 95 bits of SID field (checked by receivers to detect SID) and 105 unused + * bits which receivers are told to ignore. Network elements that receive + * SID frames from call leg A uplink and need to retransmit them on leg B + * downlink should "rejuvenate" received SID frames prior to retransmission; + * this function does the job. + */ +void osmo_fr_sid_reset(uint8_t *rtp_payload) +{ + uint8_t *p, sub; + + p = rtp_payload + 5; /* skip magic+LARc */ + for (sub = 0; sub < 4; sub++) { + *p++ = 0; + *p++ &= 0x1F; /* upper 5 bits of Xmaxc field */ + *p++ &= 0x80; /* and the lsb spilling into the next byte */ + *p++ = 0; + *p++ = 0; + *p++ = 0; + *p++ = 0; + } +} + +/*! Preen potentially-SID FR codec frame in RTP format, ensuring that it is + * either a speech frame or a valid SID, and if the latter, making it a + * perfect, error-free SID frame. + * \param[in] rtp_payload Buffer with RTP payload - must be writable! + * \returns true if the frame is good, false otherwise + */ +bool osmo_fr_sid_preen(uint8_t *rtp_payload) +{ + enum osmo_gsm631_sid_class sidc; + + sidc = osmo_fr_sid_classify(rtp_payload); + switch (sidc) { + case OSMO_GSM631_SID_CLASS_SPEECH: + return true; + case OSMO_GSM631_SID_CLASS_INVALID: + return false; + case OSMO_GSM631_SID_CLASS_VALID: + /* "Rejuvenate" this SID frame, correcting any errors */ + osmo_fr_sid_reset(rtp_payload); + return true; + default: + /* There are only 3 possible SID classifications per GSM 06.31 + * section 6.1.1, thus any other return value is a grave error + * in the code. */ + OSMO_ASSERT(0); + } +} diff --git a/src/codec/gsm620.c b/src/codec/gsm620.c index 282781fd..ef1d3b9b 100644 --- a/src/codec/gsm620.c +++ b/src/codec/gsm620.c @@ -17,14 +17,11 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ #include <stdint.h> #include <stdbool.h> +#include <string.h> #include <osmocom/core/bitvec.h> #include <osmocom/core/utils.h> @@ -268,12 +265,6 @@ const uint16_t gsm620_voiced_bitorder[112] = { 81, /* Code 3:7 */ }; -static inline uint16_t mask(const uint8_t msb) -{ - const uint16_t m = (uint16_t)1 << (msb - 1); - return (m - 1) ^ m; -} - /*! Check whether RTP frame contains HR SID code word according to * TS 101 318 §5.2.2 * \param[in] rtp_payload Buffer with RTP payload @@ -282,16 +273,44 @@ static inline uint16_t mask(const uint8_t msb) */ bool osmo_hr_check_sid(const uint8_t *rtp_payload, size_t payload_len) { - uint8_t i, bits[] = { 1, 2, 8, 9, 5, 4, 9, 5, 4, 9, 5, 4, 9, 5 }; - struct bitvec bv; - bv.data = (uint8_t *) rtp_payload; - bv.data_len = payload_len; - bv.cur_bit = 33; + struct bitvec bv = { + .data = (uint8_t *)rtp_payload, + .data_len = payload_len, + }; - /* code word is all 1 at given bits, numbered from 1, MODE is always 3 */ - for (i = 0; i < ARRAY_SIZE(bits); i++) - if (bitvec_get_uint(&bv, bits[i]) != mask(bits[i])) + /* A SID frame is identified by a SID codeword consisting of 79 bits which are all 1, + * so we basically check if all bits in range r34..r112 (inclusive) are 1. */ + for (bv.cur_bit = 33; bv.cur_bit < bv.data_len * 8; bv.cur_bit++) + if (bitvec_get_bit_pos(&bv, bv.cur_bit) != ONE) return false; return true; } + +/*! Reset the SID field of a potentially corrupted, but still valid GSM-HR + * SID frame in TS 101 318 format to its pristine state (full SID codeword). + * \param[in] rtp_payload Buffer with RTP payload - must be writable! + * + * Per GSM 06.22 section 5.3, a freshly minted SID frame consists of 33 bits + * of comfort noise parameters and 79 bits of SID codeword (all 1s). Network + * elements that receive SID frames from call leg A uplink and need to + * retransmit them on leg B downlink should "rejuvenate" received SID frames + * prior to retransmission by resetting the SID field to its pristine state + * of all 1s; this function does the job. + * + * Important note: because of HR-specific quirks (lack of exact bit counting + * rules in GSM 06.41 spec compared to 06.31 & 06.81, plus the fact that such + * bit counting can only be done efficiently in the GSM 05.03 channel decoder + * prior to bit reordering based on voiced or unvoiced mode), a generic + * (usable from any network element) SID classification function similar to + * osmo_{fr,efr}_sid_classify() unfortunately cannot exist for HR. Therefore, + * the triggering condition for invoking this SID rejuvenation/reset function + * can only be an out-of-band SID indication, as in GSM 08.61 TRAU frames + * or RFC 5993 ToC octet. + */ +void osmo_hr_sid_reset(uint8_t *rtp_payload) +{ + /* set all 79 SID codeword bits to 1 */ + rtp_payload[4] |= 0x7F; + memset(rtp_payload + 5, 0xFF, 9); +} diff --git a/src/codec/gsm660.c b/src/codec/gsm660.c index 4f7bb099..b15bdf37 100644 --- a/src/codec/gsm660.c +++ b/src/codec/gsm660.c @@ -17,13 +17,13 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ #include <stdint.h> +#include <stdbool.h> + +#include <osmocom/core/bitvec.h> +#include <osmocom/core/utils.h> #include <osmocom/codec/codec.h> /* GSM EFR - subjective importance bit ordering */ @@ -257,3 +257,161 @@ const uint16_t gsm660_bitorder[260] = { 243, /* 258 -> PULSE 4_9: b0 */ 246, /* 259 -> PULSE 4_10: b0 */ }; + +static const uint8_t sid_code_word_bits[95] = { + /* bit numbers are relative to "pure" EFR frame beginning, + * not counting the signature bits. */ + 45, 46, 48, 49, 50, 51, 52, 53, 54, 55, + 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, + 66, 67, 68, 94, 95, 96, 98, 99, 100, 101, + 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, + 112, 113, 114, 115, 116, 117, 118, 148, 149, 150, + 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, + 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, + 171, 196, 197, 198, 199, 200, 201, 202, 203, 204, + 205, 206, 207, 208, 209, 212, 213, 214, 215, 216, + 217, 218, 219, 220, 221 +}; + +/*! Check whether RTP frame contains EFR SID code word according to + * TS 101 318 §5.3.2 + * \param[in] rtp_payload Buffer with RTP payload + * \param[in] payload_len Length of payload + * \returns true if code word is found, false otherwise + */ +bool osmo_efr_check_sid(const uint8_t *rtp_payload, size_t payload_len) +{ + struct bitvec bv; + uint16_t i; + + /* signature does not match Enhanced Full Rate SID */ + if ((rtp_payload[0] >> 4) != 0xC) + return false; + + bv.data = (uint8_t *) rtp_payload; + bv.data_len = payload_len; + + /* code word is all 1 at given bits */ + for (i = 0; i < ARRAY_SIZE(sid_code_word_bits); i++) { + if (bitvec_get_bit_pos(&bv, sid_code_word_bits[i]+4) != ONE) + return false; + } + + return true; +} + +/*! Classify potentially-SID EFR codec frame in RTP format according + * to the rules of GSM 06.81 §6.1.1 + * \param[in] rtp_payload Buffer with RTP payload + * \returns enum osmo_gsm631_sid_class, with symbolic values + * OSMO_GSM631_SID_CLASS_SPEECH, OSMO_GSM631_SID_CLASS_INVALID or + * OSMO_GSM631_SID_CLASS_VALID corresponding to the 3 possible bit-counting + * classifications prescribed by the spec. + * + * Differences between the more familiar osmo_efr_check_sid() and the present + * function are: + * + * 1. osmo_efr_check_sid() returns true only if the SID frame is absolutely + * perfect, with all 95 bits of the SID code word set. However, the + * rules of GSM 06.81 §6.1.1 allow up to one bit to be in error, + * and the frame is still accepted as valid SID. + * + * 2. The third possible state of invalid SID is not handled at all by the + * simpler osmo_efr_check_sid() function. + * + * 3. osmo_efr_check_sid() includes a check for 0xC RTP signature, and returns + * false if that signature nibble is wrong. That check is not included + * in the present version because there is no place for it in the + * ETSI-prescribed classification, it is neither speech nor SID. The + * assumption is that this function is used to classify the bit content + * of received codec frames, not their RTP encoding - the latter needs + * to be validated beforehand. + * + * Which function should one use? The answer depends on the specific + * circumstances, and needs to be addressed on a case-by-case basis. + */ +enum osmo_gsm631_sid_class osmo_efr_sid_classify(const uint8_t *rtp_payload) +{ + struct bitvec bv; + uint16_t i, n; + + bv.data = (uint8_t *) rtp_payload; + bv.data_len = GSM_EFR_BYTES; + + /* count not-SID-matching bits per the spec */ + n = 0; + for (i = 0; i < ARRAY_SIZE(sid_code_word_bits); i++) { + if (bitvec_get_bit_pos(&bv, sid_code_word_bits[i]+4) != ONE) + n++; + if (n >= 16) + return OSMO_GSM631_SID_CLASS_SPEECH; + } + if (n >= 2) + return OSMO_GSM631_SID_CLASS_INVALID; + else + return OSMO_GSM631_SID_CLASS_VALID; +} + +/*! Reset the SID field of a potentially corrupted, but still valid GSM-EFR + * SID frame in RTP encoding to its pristine state (full SID code word). + * \param[in] rtp_payload Buffer with RTP payload - must be writable! + * + * Per GSM 06.62 section 5.3, a freshly minted SID frame consists of 58 bits + * of comfort noise parameters (LSF and 4 times fixed codebook gain), 95 bits + * of SID code word (all 1s) and 91 unused bits (all 0s). Network elements + * that receive SID frames from call leg A uplink and need to retransmit them + * on leg B downlink should "rejuvenate" received SID frames prior to + * retransmission by resetting the SID field to its pristine state of all 1s; + * this function does the job. + * + * Potential TODO: it would be nice to also zero out the remaining 91 bits + * which the spec leaves as reserved, clearing out leg A radio bit errors - + * but do we really need to? + */ +void osmo_efr_sid_reset(uint8_t *rtp_payload) +{ + /* set all 95 SID code word bits to 1 */ + rtp_payload[6] |= 0x6F; + rtp_payload[7] = 0xFF; + rtp_payload[8] = 0xFF; + rtp_payload[9] |= 0x80; + rtp_payload[12] |= 0x3B; + rtp_payload[13] = 0xFF; + rtp_payload[14] = 0xFF; + rtp_payload[15] |= 0xE0; + rtp_payload[19] = 0xFF; + rtp_payload[20] = 0xFF; + rtp_payload[21] = 0xFF; + rtp_payload[25] = 0xFF; + rtp_payload[26] |= 0xFC; + rtp_payload[27] = 0xFF; + rtp_payload[28] |= 0xC0; +} + +/*! Preen potentially-SID EFR codec frame in RTP format, ensuring that it is + * either a speech frame or a valid SID, and if the latter, making it a + * perfect, error-free SID frame. + * \param[in] rtp_payload Buffer with RTP payload - must be writable! + * \returns true if the frame is good, false otherwise + */ +bool osmo_efr_sid_preen(uint8_t *rtp_payload) +{ + enum osmo_gsm631_sid_class sidc; + + sidc = osmo_efr_sid_classify(rtp_payload); + switch (sidc) { + case OSMO_GSM631_SID_CLASS_SPEECH: + return true; + case OSMO_GSM631_SID_CLASS_INVALID: + return false; + case OSMO_GSM631_SID_CLASS_VALID: + /* "Rejuvenate" this SID frame, correcting any errors */ + osmo_efr_sid_reset(rtp_payload); + return true; + default: + /* There are only 3 possible SID classifications per GSM 06.81 + * section 6.1.1, thus any other return value is a grave error + * in the code. */ + OSMO_ASSERT(0); + } +} diff --git a/src/codec/gsm690.c b/src/codec/gsm690.c index cc6cdf0c..3b2e6694 100644 --- a/src/codec/gsm690.c +++ b/src/codec/gsm690.c @@ -18,10 +18,6 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ #include <stdint.h> diff --git a/src/coding/Makefile.am b/src/coding/Makefile.am index b023668e..0cab66ff 100644 --- a/src/coding/Makefile.am +++ b/src/coding/Makefile.am @@ -1,13 +1,14 @@ # This is _NOT_ the library release version, it's an API version. # Please read Chapter 6 "Library interface versions" of the libtool # documentation before making any modification -LIBVERSION=1:1:1 +LIBVERSION=3:0:3 AM_CPPFLAGS = \ -I"$(top_srcdir)/include" \ -I"$(top_builddir)/include" \ - $(TALLOC_CFLAGS) -AM_CFLAGS = -Wall + -I"$(top_builddir)" \ + $(NULL) +AM_CFLAGS = -Wall $(TALLOC_CFLAGS) if ENABLE_PSEUDOTALLOC AM_CPPFLAGS += -I$(top_srcdir)/src/pseudotalloc @@ -24,13 +25,15 @@ libosmocoding_la_SOURCES = \ gsm0503_amr_dtx.c libosmocoding_la_LDFLAGS = \ $(LTLDFLAGS_OSMOCODING) \ - -version-info \ - $(LIBVERSION) \ + -version-info $(LIBVERSION) \ -no-undefined \ - $(TALLOC_LIBS) + $(NULL) + libosmocoding_la_LIBADD = \ - ../libosmocore.la \ - ../gsm/libosmogsm.la \ - ../codec/libosmocodec.la + $(top_builddir)/src/core/libosmocore.la \ + $(top_builddir)/src/gsm/libosmogsm.la \ + $(top_builddir)/src/codec/libosmocodec.la \ + $(NULL) EXTRA_DIST = libosmocoding.map +EXTRA_libosmocoding_la_DEPENDENCIES = libosmocoding.map diff --git a/src/coding/gsm0503_amr_dtx.c b/src/coding/gsm0503_amr_dtx.c index 7069b967..7de3b281 100644 --- a/src/coding/gsm0503_amr_dtx.c +++ b/src/coding/gsm0503_amr_dtx.c @@ -1,5 +1,5 @@ /* - * (C) 2020 by sysmocom - s.f.m.c. GmbH, Author: Philipp Maier + * (C) 2020-2022 by sysmocom - s.f.m.c. GmbH, Author: Philipp Maier * All Rights Reserved * * SPDX-License-Identifier: GPL-2.0+ @@ -32,6 +32,8 @@ #include <osmocom/coding/gsm0503_parity.h> #include <osmocom/gsm/gsm0503.h> +#define S2U(b) ((b) < 0) + /* See also: 3GPP TS 05.03, chapter 3.10.1.3, 3.10.5.2 Identification marker */ static const ubit_t id_marker_1[] = { 1, 0, 1, 1, 0, 0, 0, 0, 1 }; @@ -60,7 +62,7 @@ const struct value_string gsm0503_amr_dtx_frame_names[] = { { 0, NULL } }; -static bool detect_afs_id_marker(int *n_errors, int *n_bits_total, const ubit_t * ubits, uint8_t offset, uint8_t count, +static bool detect_afs_id_marker(int *n_errors, int *n_bits_total, const sbit_t *sbits, uint8_t offset, uint8_t count, const ubit_t * id_marker, uint8_t id_marker_len) { unsigned int i, k; @@ -69,20 +71,20 @@ static bool detect_afs_id_marker(int *n_errors, int *n_bits_total, const ubit_t int bits = 0; /* Override coded in-band data */ - ubits += offset; + sbits += offset; /* Check for identification marker bits */ for (i = 0; i < count; i++) { for (k = 0; k < 4; k++) { - if (id_marker[id_bit_nr % id_marker_len] != *ubits) + if (*sbits == 0 || id_marker[id_bit_nr % id_marker_len] != S2U(*sbits)) errors++; id_bit_nr++; - ubits++; + sbits++; bits++; } /* Jump to the next block of 4 bits */ - ubits += 4; + sbits += 4; } *n_errors = errors; @@ -92,30 +94,30 @@ static bool detect_afs_id_marker(int *n_errors, int *n_bits_total, const ubit_t return *n_errors < *n_bits_total / 8; } -static bool detect_ahs_id_marker(int *n_errors, int *n_bits_total, const ubit_t * ubits, const ubit_t * id_marker) +static bool detect_ahs_id_marker(int *n_errors, int *n_bits_total, const sbit_t *sbits, const ubit_t *id_marker) { unsigned int i, k; int errors = 0; int bits = 0; /* Override coded in-band data */ - ubits += 16; + sbits += 16; /* Check first identification marker bits (23*9 bits) */ for (i = 0; i < 23; i++) { for (k = 0; k < 9; k++) { - if (id_marker[k] != *ubits) + if (*sbits == 0 || id_marker[k] != S2U(*sbits)) errors++; - ubits++; + sbits++; bits++; } } /* Check remaining identification marker bits (5 bits) */ for (k = 0; k < 5; k++) { - if (id_marker[k] != *ubits) + if (*sbits == 0 || id_marker[k] != S2U(*sbits)) errors++; - ubits++; + sbits++; bits++; } @@ -126,7 +128,7 @@ static bool detect_ahs_id_marker(int *n_errors, int *n_bits_total, const ubit_t return *n_errors < *n_bits_total / 8; } -static bool detect_interleaved_ahs_id_marker(int *n_errors, int *n_bits_total, const ubit_t * ubits, uint8_t offset, +static bool detect_interleaved_ahs_id_marker(int *n_errors, int *n_bits_total, const sbit_t *sbits, uint8_t offset, uint8_t n_bits, const ubit_t * id_marker, uint8_t id_marker_len) { unsigned int i, k; @@ -136,23 +138,23 @@ static bool detect_interleaved_ahs_id_marker(int *n_errors, int *n_bits_total, c uint8_t remainder = n_bits % id_marker_len; /* Override coded in-band data */ - ubits += offset; + sbits += offset; /* Check first identification marker bits (23*9 bits) */ for (i = 0; i < full_rounds; i++) { for (k = 0; k < id_marker_len; k++) { - if (id_marker[k] != *ubits) + if (*sbits == 0 || id_marker[k] != S2U(*sbits)) errors++; - ubits += 2; + sbits += 2; bits++; } } /* Check remaining identification marker bits (5 bits) */ for (k = 0; k < remainder; k++) { - if (id_marker[k] != *ubits) + if (*sbits == 0 || id_marker[k] != S2U(*sbits)) errors++; - ubits += 2; + sbits += 2; bits++; } @@ -163,126 +165,128 @@ static bool detect_interleaved_ahs_id_marker(int *n_errors, int *n_bits_total, c return *n_errors < *n_bits_total / 8; } -/* Detect a an FR AMR SID_FIRST frame by its identifcation marker */ -static bool detect_afs_sid_first(int *n_errors, int *n_bits_total, const ubit_t * ubits) +/* Detect an FR AMR SID_FIRST frame by its identifcation marker */ +static bool detect_afs_sid_first(int *n_errors, int *n_bits_total, const sbit_t *sbits) { - return detect_afs_id_marker(n_errors, n_bits_total, ubits, 32, 53, id_marker_0, 9); + return detect_afs_id_marker(n_errors, n_bits_total, sbits, 32, 53, id_marker_0, 9); } -/* Detect an FR AMR SID_FIRST frame by its identification marker */ -static bool detect_afs_sid_update(int *n_errors, int *n_bits_total, const ubit_t * ubits) +/* Detect an FR AMR SID_UPDATE frame by its identification marker */ +static bool detect_afs_sid_update(int *n_errors, int *n_bits_total, const sbit_t *sbits) { - return detect_afs_id_marker(n_errors, n_bits_total, ubits, 36, 53, id_marker_0, 9); + return detect_afs_id_marker(n_errors, n_bits_total, sbits, 36, 53, id_marker_0, 9); } -/* Detect an FR AMR SID_FIRST frame by its repeating coded inband data */ -static bool detect_afs_onset(int *n_errors, int *n_bits_total, const ubit_t * ubits) +/* Detect an FR AMR ONSET frame by its repeating coded inband data */ +static int detect_afs_onset(int *n_errors, int *n_bits_total, const sbit_t *sbits) { bool rc; - rc = detect_afs_id_marker(n_errors, n_bits_total, ubits, 4, 57, codec_mode_1_sid, 16); + rc = detect_afs_id_marker(n_errors, n_bits_total, sbits, 4, 57, codec_mode_1_sid, 16); if (rc) - return true; + return 0; - rc = detect_afs_id_marker(n_errors, n_bits_total, ubits, 4, 57, codec_mode_2_sid, 16); + rc = detect_afs_id_marker(n_errors, n_bits_total, sbits, 4, 57, codec_mode_2_sid, 16); if (rc) - return true; + return 1; - rc = detect_afs_id_marker(n_errors, n_bits_total, ubits, 4, 57, codec_mode_3_sid, 16); + rc = detect_afs_id_marker(n_errors, n_bits_total, sbits, 4, 57, codec_mode_3_sid, 16); if (rc) - return true; + return 2; - rc = detect_afs_id_marker(n_errors, n_bits_total, ubits, 4, 57, codec_mode_4_sid, 16); + rc = detect_afs_id_marker(n_errors, n_bits_total, sbits, 4, 57, codec_mode_4_sid, 16); if (rc) - return true; + return 3; - return false; + return -1; } /* Detect an HR AMR SID UPDATE frame by its identification marker */ -static bool detect_ahs_sid_update(int *n_errors, int *n_bits_total, const ubit_t * ubits) +static bool detect_ahs_sid_update(int *n_errors, int *n_bits_total, const sbit_t *sbits) { - return detect_ahs_id_marker(n_errors, n_bits_total, ubits, id_marker_1); + return detect_ahs_id_marker(n_errors, n_bits_total, sbits, id_marker_1); } /* Detect an HR AMR SID FIRST (part 1) frame by its identification marker */ -static bool detect_ahs_sid_first_p1(int *n_errors, int *n_bits_total, const ubit_t * ubits) +static bool detect_ahs_sid_first_p1(int *n_errors, int *n_bits_total, const sbit_t *sbits) { - return detect_ahs_id_marker(n_errors, n_bits_total, ubits, id_marker_0); + return detect_ahs_id_marker(n_errors, n_bits_total, sbits, id_marker_0); } /* Detect an HR AMR SID FIRST (part 2) frame by its repeating coded inband data */ -static bool detect_ahs_sid_first_p2(int *n_errors, int *n_bits_total, const ubit_t * ubits) +static int detect_ahs_sid_first_p2(int *n_errors, int *n_bits_total, const sbit_t *sbits) { bool rc; - rc = detect_interleaved_ahs_id_marker(n_errors, n_bits_total, ubits, 0, 114, codec_mode_1_sid, 16); + rc = detect_interleaved_ahs_id_marker(n_errors, n_bits_total, sbits, 0, 114, codec_mode_1_sid, 16); if (rc) - return true; + return 0; - rc = detect_interleaved_ahs_id_marker(n_errors, n_bits_total, ubits, 0, 114, codec_mode_2_sid, 16); + rc = detect_interleaved_ahs_id_marker(n_errors, n_bits_total, sbits, 0, 114, codec_mode_2_sid, 16); if (rc) - return true; + return 1; - rc = detect_interleaved_ahs_id_marker(n_errors, n_bits_total, ubits, 0, 114, codec_mode_3_sid, 16); + rc = detect_interleaved_ahs_id_marker(n_errors, n_bits_total, sbits, 0, 114, codec_mode_3_sid, 16); if (rc) - return true; + return 2; - rc = detect_interleaved_ahs_id_marker(n_errors, n_bits_total, ubits, 0, 114, codec_mode_4_sid, 16); + rc = detect_interleaved_ahs_id_marker(n_errors, n_bits_total, sbits, 0, 114, codec_mode_4_sid, 16); if (rc) - return true; + return 3; - return false; + return -1; } /* Detect an HR AMR ONSET frame by its repeating coded inband data */ -static bool detect_ahs_onset(int *n_errors, int *n_bits_total, const ubit_t * ubits) +static int detect_ahs_onset(int *n_errors, int *n_bits_total, const sbit_t *sbits) { bool rc; - rc = detect_interleaved_ahs_id_marker(n_errors, n_bits_total, ubits, 1, 114, codec_mode_1_sid, 16); + rc = detect_interleaved_ahs_id_marker(n_errors, n_bits_total, sbits, 1, 114, codec_mode_1_sid, 16); if (rc) - return true; + return 0; - rc = detect_interleaved_ahs_id_marker(n_errors, n_bits_total, ubits, 1, 114, codec_mode_2_sid, 16); + rc = detect_interleaved_ahs_id_marker(n_errors, n_bits_total, sbits, 1, 114, codec_mode_2_sid, 16); if (rc) - return true; + return 1; - rc = detect_interleaved_ahs_id_marker(n_errors, n_bits_total, ubits, 1, 114, codec_mode_3_sid, 16); + rc = detect_interleaved_ahs_id_marker(n_errors, n_bits_total, sbits, 1, 114, codec_mode_3_sid, 16); if (rc) - return true; + return 2; - rc = detect_interleaved_ahs_id_marker(n_errors, n_bits_total, ubits, 1, 114, codec_mode_4_sid, 16); + rc = detect_interleaved_ahs_id_marker(n_errors, n_bits_total, sbits, 1, 114, codec_mode_4_sid, 16); if (rc) - return true; + return 3; - return false; + return -1; } /* Detect an HR AMR SID FIRST INHIBIT frame by its identification marker */ -static bool detect_ahs_sid_first_inh(int *n_errors, int *n_bits_total, const ubit_t * ubits) +static bool detect_ahs_sid_first_inh(int *n_errors, int *n_bits_total, const sbit_t *sbits) { - return detect_interleaved_ahs_id_marker(n_errors, n_bits_total, ubits, 33, 212, id_marker_1, 9); + return detect_interleaved_ahs_id_marker(n_errors, n_bits_total, sbits, 33, 212, id_marker_1, 9); } /* Detect an HR AMR SID UPDATE INHIBIT frame by its identification marker */ -static bool detect_ahs_sid_update_inh(int *n_errors, int *n_bits_total, const ubit_t * ubits) +static bool detect_ahs_sid_update_inh(int *n_errors, int *n_bits_total, const sbit_t *sbits) { - return detect_interleaved_ahs_id_marker(n_errors, n_bits_total, ubits, 33, 212, id_marker_0, 9); + return detect_interleaved_ahs_id_marker(n_errors, n_bits_total, sbits, 33, 212, id_marker_0, 9); } /*! Detect FR AMR DTX frame in unmapped, deinterleaved frame bits. - * \param[in] ubits input bits (456 bit). * \param[out] n_errors number of errornous bits. * \param[out] n_bits_total number of checked bits. + * \param[out] mode_id codec mode ID (0..3 or -1). + * \param[in] sbits input soft-bits (456 bit). * \returns dtx frame type. */ -enum gsm0503_amr_dtx_frames gsm0503_detect_afs_dtx_frame(int *n_errors, int *n_bits_total, const ubit_t * ubits) +enum gsm0503_amr_dtx_frames gsm0503_detect_afs_dtx_frame2(int *n_errors, int *n_bits_total, + int *mode_id, const sbit_t *sbits) { - if (detect_afs_sid_first(n_errors, n_bits_total, ubits)) + if (detect_afs_sid_first(n_errors, n_bits_total, sbits)) return AFS_SID_FIRST; - if (detect_afs_sid_update(n_errors, n_bits_total, ubits)) + if (detect_afs_sid_update(n_errors, n_bits_total, sbits)) return AFS_SID_UPDATE; - if (detect_afs_onset(n_errors, n_bits_total, ubits)) + if ((*mode_id = detect_afs_onset(n_errors, n_bits_total, sbits)) != -1) return AFS_ONSET; *n_errors = 0; @@ -290,27 +294,62 @@ enum gsm0503_amr_dtx_frames gsm0503_detect_afs_dtx_frame(int *n_errors, int *n_b return AMR_OTHER; } -/*! Detect HR AMR DTX frame in unmapped, deinterleaved frame bits. +/*! Detect FR AMR DTX frame in unmapped, deinterleaved frame bits. + * DEPRECATED: use gsm0503_detect_afs_dtx_frame2() instead. + * \param[out] n_errors number of errornous bits. + * \param[out] n_bits_total number of checked bits. * \param[in] ubits input bits (456 bit). + * \returns dtx frame type. */ +enum gsm0503_amr_dtx_frames gsm0503_detect_afs_dtx_frame(int *n_errors, int *n_bits_total, + const ubit_t *ubits) +{ + int mode_id; /* unused */ + sbit_t sbits[456]; + + osmo_ubit2sbit(&sbits[0], &ubits[0], sizeof(sbits)); + return gsm0503_detect_afs_dtx_frame2(n_errors, n_bits_total, &mode_id, &sbits[0]); +} + +/*! Detect HR AMR DTX frame in unmapped, deinterleaved frame bits. * \param[out] n_errors number of errornous bits. * \param[out] n_bits_total number of checked bits. + * \param[out] mode_id codec ID (CMI or CMC/CMR). + * \param[out] mode_id codec mode ID (0..3 or -1). + * \param[in] sbits input soft-bits (456 bit). * \returns dtx frame type, */ -enum gsm0503_amr_dtx_frames gsm0503_detect_ahs_dtx_frame(int *n_errors, int *n_bits_total, const ubit_t * ubits) +enum gsm0503_amr_dtx_frames gsm0503_detect_ahs_dtx_frame2(int *n_errors, int *n_bits_total, + int *mode_id, const sbit_t *sbits) { - if (detect_ahs_sid_update(n_errors, n_bits_total, ubits)) + if (detect_ahs_sid_update(n_errors, n_bits_total, sbits)) return AHS_SID_UPDATE; - if (detect_ahs_sid_first_inh(n_errors, n_bits_total, ubits)) + if (detect_ahs_sid_first_inh(n_errors, n_bits_total, sbits)) return AHS_SID_FIRST_INH; - if (detect_ahs_sid_update_inh(n_errors, n_bits_total, ubits)) + if (detect_ahs_sid_update_inh(n_errors, n_bits_total, sbits)) return AHS_SID_UPDATE_INH; - if (detect_ahs_sid_first_p1(n_errors, n_bits_total, ubits)) + if (detect_ahs_sid_first_p1(n_errors, n_bits_total, sbits)) return AHS_SID_FIRST_P1; - if (detect_ahs_sid_first_p2(n_errors, n_bits_total, ubits)) + if ((*mode_id = detect_ahs_sid_first_p2(n_errors, n_bits_total, sbits)) != -1) return AHS_SID_FIRST_P2; - if (detect_ahs_onset(n_errors, n_bits_total, ubits)) + if ((*mode_id = detect_ahs_onset(n_errors, n_bits_total, sbits)) != -1) return AHS_ONSET; *n_errors = 0; *n_bits_total = 0; return AMR_OTHER; } + +/*! Detect HR AMR DTX frame in unmapped, deinterleaved frame bits. + * DEPRECATED: use gsm0503_detect_ahs_dtx_frame2() instead. + * \param[out] n_errors number of errornous bits. + * \param[out] n_bits_total number of checked bits. + * \param[in] ubits input bits (456 bit). + * \returns dtx frame type, */ +enum gsm0503_amr_dtx_frames gsm0503_detect_ahs_dtx_frame(int *n_errors, int *n_bits_total, + const ubit_t *ubits) +{ + int mode_id; /* unused */ + sbit_t sbits[456]; + + osmo_ubit2sbit(&sbits[0], &ubits[0], sizeof(sbits)); + return gsm0503_detect_ahs_dtx_frame2(n_errors, n_bits_total, &mode_id, &sbits[0]); +} diff --git a/src/coding/gsm0503_coding.c b/src/coding/gsm0503_coding.c index 9c189cdd..022f4656 100644 --- a/src/coding/gsm0503_coding.c +++ b/src/coding/gsm0503_coding.c @@ -17,10 +17,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include <stdio.h> @@ -35,9 +31,7 @@ #include <osmocom/core/crcgen.h> #include <osmocom/core/endian.h> -#include <osmocom/gprs/protocol/gsm_04_60.h> -#include <osmocom/gprs/gprs_rlc.h> - +#include <osmocom/gsm/protocol/gsm_44_060.h> #include <osmocom/gsm/protocol/gsm_04_08.h> #include <osmocom/gsm/gsm0503.h> #include <osmocom/codec/codec.h> @@ -542,24 +536,29 @@ static int osmo_conv_decode_ber_punctured(const struct osmo_conv_code *code, int *n_errors, int *n_bits_total, const uint8_t *data_punc) { - int res, i, coded_len; + int res, coded_len; ubit_t recoded[EGPRS_DATA_C_MAX]; res = osmo_conv_decode(code, input, output); - if (n_bits_total || n_errors) { - coded_len = osmo_conv_encode(code, output, recoded); - OSMO_ASSERT(sizeof(recoded) / sizeof(recoded[0]) >= coded_len); - } + if (!n_bits_total && !n_errors) + return res; + + coded_len = osmo_conv_encode(code, output, recoded); + OSMO_ASSERT(ARRAY_SIZE(recoded) >= coded_len); /* Count bit errors */ if (n_errors) { *n_errors = 0; - for (i = 0; i < coded_len; i++) { - if (((!data_punc) || (data_punc && !data_punc[i])) && - !((recoded[i] && input[i] < 0) || - (!recoded[i] && input[i] > 0)) ) - *n_errors += 1; + for (unsigned int i = 0; i < coded_len; i++) { + /* punctured bits do not count as bit errors */ + if (data_punc != NULL && data_punc[i]) + continue; + if (recoded[i] == 1 && input[i] < 0) + continue; + if (recoded[i] == 0 && input[i] > 0) + continue; + *n_errors += 1; } } @@ -611,7 +610,7 @@ static int _xcch_decode_cB(uint8_t *l2_data, const sbit_t *cB, /*! convenience wrapper for encoding to coded bits * \param[out] cB caller-allocated buffer for 456 coded bits as per TS 05.03 4.1.3 - * \param[out] l2_data to-be-encoded L2 Frame + * \param[in] l2_data to-be-encoded L2 Frame * \returns 0 */ static int _xcch_encode_cB(ubit_t *cB, const uint8_t *l2_data) { @@ -926,10 +925,10 @@ static int egprs_decode_data(uint8_t *l2_data, const sbit_t *c, * \param[out] l2_data caller-allocated buffer for L2 Frame * \param[in] bursts burst input data as soft unpacked bits * \param[in] nbits number of bits in \a bursts - * \param usf_p unused argument ?!? + * \param usf_p Uplink State Flag, FIXME: not implemented * \param[out] n_errors number of detected bit-errors * \param[out] n_bits_total total number of decoded bits - * \returns 0 on success; negative on error */ + * \returns number of bytes decoded; negative on error */ int gsm0503_pdtch_egprs_decode(uint8_t *l2_data, const sbit_t *bursts, uint16_t nbits, uint8_t *usf_p, int *n_errors, int *n_bits_total) { @@ -1015,10 +1014,10 @@ int gsm0503_pdtch_egprs_decode(uint8_t *l2_data, const sbit_t *bursts, uint16_t /*! Decode GPRS PDTCH * \param[out] l2_data caller-allocated buffer for L2 Frame * \param[in] bursts burst input data as soft unpacked bits - * \param[out] usf_p uplink stealing flag + * \param[out] usf_p Uplink State Flag, only relevant for DL blocks * \param[out] n_errors number of detected bit-errors - * \param[out] n_bits_total total number of dcoded bits - * \returns 0 on success; negative on error */ + * \param[out] n_bits_total total number of decoded bits + * \returns number of bytes decoded; negative on error */ int gsm0503_pdtch_decode(uint8_t *l2_data, const sbit_t *bursts, uint8_t *usf_p, int *n_errors, int *n_bits_total) { @@ -1047,6 +1046,10 @@ int gsm0503_pdtch_decode(uint8_t *l2_data, const sbit_t *bursts, uint8_t *usf_p, osmo_conv_decode_ber(&gsm0503_xcch, cB, conv, n_errors, n_bits_total); + /* the three USF bits d(0),d(1),d(2) are *not* precoded */ + if (usf_p) + *usf_p = (conv[0] << 2) | (conv[1] << 1) | (conv[2] << 0); + rv = osmo_crc64gen_check_bits(&gsm0503_fire_crc40, conv, 184, conv + 184); if (rv) @@ -1056,6 +1059,7 @@ int gsm0503_pdtch_decode(uint8_t *l2_data, const sbit_t *bursts, uint8_t *usf_p, return 23; case 2: + /* reorder, set punctured bits to 0 (unknown state) */ for (i = 587, j = 455; i >= 0; i--) { if (!gsm0503_puncture_cs2[i]) cB[i] = cB[j--]; @@ -1063,9 +1067,15 @@ int gsm0503_pdtch_decode(uint8_t *l2_data, const sbit_t *bursts, uint8_t *usf_p, cB[i] = 0; } - osmo_conv_decode_ber(&gsm0503_cs2_np, cB, - conv, n_errors, n_bits_total); + /* decode as if puncturing was not employed (note '_np') */ + osmo_conv_decode_ber_punctured(&gsm0503_cs2_np, cB, conv, + n_errors, NULL, + gsm0503_puncture_cs2); + /* indicate the actual amount of coded bits (excluding punctured ones) */ + if (n_bits_total != NULL) + *n_bits_total = 456; + /* 5.1.2.2 a) the three USF bits d(0),d(1),d(2) are precoded into six bits */ for (i = 0; i < 8; i++) { for (j = 0, k = 0; j < 6; j++) k += abs(((int)gsm0503_usf2six[i][j]) - ((int)conv[j])); @@ -1091,6 +1101,7 @@ int gsm0503_pdtch_decode(uint8_t *l2_data, const sbit_t *bursts, uint8_t *usf_p, return 34; case 3: + /* reorder, set punctured bits to 0 (unknown state) */ for (i = 675, j = 455; i >= 0; i--) { if (!gsm0503_puncture_cs3[i]) cB[i] = cB[j--]; @@ -1098,9 +1109,15 @@ int gsm0503_pdtch_decode(uint8_t *l2_data, const sbit_t *bursts, uint8_t *usf_p, cB[i] = 0; } - osmo_conv_decode_ber(&gsm0503_cs3_np, cB, - conv, n_errors, n_bits_total); + /* decode as if puncturing was not employed (note '_np') */ + osmo_conv_decode_ber_punctured(&gsm0503_cs3_np, cB, conv, + n_errors, NULL, + gsm0503_puncture_cs3); + /* indicate the actual amount of coded bits (excluding punctured ones) */ + if (n_bits_total != NULL) + *n_bits_total = 456; + /* 5.1.3.2 a) the three USF bits d(0),d(1),d(2) are precoded into six bits */ for (i = 0; i < 8; i++) { for (j = 0, k = 0; j < 6; j++) k += abs(((int)gsm0503_usf2six[i][j]) - ((int)conv[j])); @@ -1129,6 +1146,7 @@ int gsm0503_pdtch_decode(uint8_t *l2_data, const sbit_t *bursts, uint8_t *usf_p, for (i = 12; i < 456; i++) conv[i] = (cB[i] < 0) ? 1 : 0; + /* 5.1.4.2 a) the three USF bits d(0),d(1),d(2) are precoded into twelve bits */ for (i = 0; i < 8; i++) { for (j = 0, k = 0; j < 12; j++) k += abs(((int)gsm0503_usf2twelve_sbit[i][j]) - ((int)cB[j])); @@ -1577,24 +1595,22 @@ static void tch_fr_disassemble(ubit_t *b_bits, } } -/* assemble a HR codec frame in format as used inside RTP */ +/* assemble a HR codec frame in the canonical format of ETSI TS 101 318 */ static void tch_hr_reassemble(uint8_t *tch_data, const ubit_t *b_bits) { - int i, j; - - tch_data[0] = 0x00; /* F = 0, FT = 000 */ - memset(tch_data + 1, 0, 14); + int i; - for (i = 0, j = 8; i < 112; i++, j++) - tch_data[j >> 3] |= (b_bits[i] << (7 - (j & 7))); + memset(tch_data, 0, GSM_HR_BYTES); + for (i = 0; i < 112; i++) + tch_data[i >> 3] |= (b_bits[i] << (7 - (i & 7))); } static void tch_hr_disassemble(ubit_t *b_bits, const uint8_t *tch_data) { - int i, j; + int i; - for (i = 0, j = 8; i < 112; i++, j++) - b_bits[i] = (tch_data[j >> 3] >> (7 - (j & 7))) & 1; + for (i = 0; i < 112; i++) + b_bits[i] = (tch_data[i >> 3] >> (7 - (i & 7))) & 1; } /* assemble a EFR codec frame in format as used inside RTP */ @@ -1809,7 +1825,7 @@ static void tch_efr_unreorder(ubit_t *s, ubit_t *p, const ubit_t *w) s[119] = (sum >= 2); memcpy(s + 121, w + 125, 53); sum = s[172] + w[178] + w[179]; - s[172] = (sum > 2); + s[172] = (sum >= 2); memcpy(s + 174, w + 180, 50); sum = s[222] + w[230] + w[231]; s[222] = (sum >= 2); @@ -1865,7 +1881,7 @@ int gsm0503_tch_fr_decode(uint8_t *tch_data, const sbit_t *bursts, return -1; } - return 23; + return GSM_MACBLOCK_LEN; } osmo_conv_decode_ber(&gsm0503_tch_fr, cB, conv, n_errors, n_bits_total); @@ -1932,40 +1948,39 @@ int gsm0503_tch_fr_encode(ubit_t *bursts, const uint8_t *tch_data, switch (len) { case GSM_EFR_BYTES: /* TCH EFR */ - tch_efr_disassemble(s, tch_data); - tch_efr_protected(s, b); - osmo_crc8gen_set_bits(&gsm0503_tch_efr_crc8, b, 65, p); - tch_efr_reorder(w, s, p); - tch_efr_w_to_d(d, w); - goto coding_efr_fr; case GSM_FR_BYTES: /* TCH FR */ tch_fr_disassemble(w, tch_data, net_order); - tch_fr_b_to_d(d, w); - coding_efr_fr: osmo_crc8gen_set_bits(&gsm0503_tch_fr_crc3, d, 50, p); - tch_fr_reorder(conv, d, p); - memcpy(cB + 378, d + 182, 78); - osmo_conv_encode(&gsm0503_tch_fr, conv, cB); - h = 0; - + break; + case 0: /* no data, induce BFI in the receiver */ + /* Do the same thing that sysmoBTS PHY does when fed a 0-length + * payload for DL: set all u(k) bits to 0, and do the same + * with all class 2 bits. This operation is NOT the same as + * an FR codec frame of all zero bits: with all-zeros d(k) input + * the CRC3 function will produce 111 output, whereas we + * transmit 000 in those parity bits too. The result will be + * an induced BFI (bad frame indication) condition in the + * receiver, for both TCH/FS and TCH/EFS decoders. */ + memset(conv, 0, sizeof(conv)); + memset(cB + 378, 0, 78); + osmo_conv_encode(&gsm0503_tch_fr, conv, cB); + h = 0; break; case GSM_MACBLOCK_LEN: /* FACCH */ _xcch_encode_cB(cB, tch_data); - h = 1; - break; default: return -1; @@ -1982,13 +1997,13 @@ coding_efr_fr: } /*! Perform channel decoding of a HR(v1) channel according TS 05.03 - * \param[out] tch_data Codec frame in RTP payload format - * \param[in] bursts buffer containing the symbols of 8 bursts + * \param[out] tch_data Codec frame in TS 101 318 canonical format + * \param[in] bursts buffer containing the symbols of 6 bursts * \param[in] odd Odd (1) or even (0) frame number * \param[out] n_errors Number of detected bit errors * \param[out] n_bits_total Total number of bits * \returns length of bytes used in \a tch_data output buffer; negative on error */ -int gsm0503_tch_hr_decode(uint8_t *tch_data, const sbit_t *bursts, int odd, +int gsm0503_tch_hr_decode2(uint8_t *tch_data, const sbit_t *bursts, int odd, int *n_errors, int *n_bits_total) { sbit_t iB[912], cB[456], h; @@ -2002,7 +2017,7 @@ int gsm0503_tch_hr_decode(uint8_t *tch_data, const sbit_t *bursts, int odd, steal -= h; } - for (i = 2; i < 5; i++) { + for (i = 2; i < 6; i++) { gsm0503_tch_burst_unmap(NULL, &bursts[i * 116], &h, 1); steal -= h; } @@ -2055,7 +2070,35 @@ int gsm0503_tch_hr_decode(uint8_t *tch_data, const sbit_t *bursts, int odd, tch_hr_reassemble(tch_data, b); - return 15; + return GSM_HR_BYTES; +} + +/*! Perform channel decoding of a HR(v1) channel according TS 05.03, + * deprecated legacy API. + * \param[out] tch_data Codec frame in pseudo-RFC5993 format + * \param[in] bursts buffer containing the symbols of 6 bursts + * \param[in] odd Odd (1) or even (0) frame number + * \param[out] n_errors Number of detected bit errors + * \param[out] n_bits_total Total number of bits + * \returns length of bytes used in \a tch_data output buffer; negative on error + * + * The HR1 codec frame format returned by this function is pseudo-RFC5993, + * not true RFC 5993, as there is no SID classification being done + * and the FT bits in the ToC octet are always set to 0 - but this + * arguably-bogus format is the legacy public API. + */ +int gsm0503_tch_hr_decode(uint8_t *tch_data, const sbit_t *bursts, int odd, + int *n_errors, int *n_bits_total) +{ + int rc; + + rc = gsm0503_tch_hr_decode2(tch_data, bursts, odd, n_errors, + n_bits_total); + if (rc != GSM_HR_BYTES) + return rc; + memmove(tch_data + 1, tch_data, GSM_HR_BYTES); + tch_data[0] = 0x00; /* FT=0, note absence of SID classification */ + return GSM_HR_BYTES_RTP_RFC5993; } /*! Perform channel encoding on a TCH/HS channel according to TS 05.03 @@ -2070,46 +2113,41 @@ int gsm0503_tch_hr_encode(ubit_t *bursts, const uint8_t *tch_data, int len) int i; switch (len) { - case 15: /* TCH HR */ + case GSM_HR_BYTES_RTP_RFC5993: /* TCH HR with RFC 5993 prefix */ + tch_data++; + /* fall-through */ + case GSM_HR_BYTES: /* TCH HR in "pure" form */ tch_hr_disassemble(b, tch_data); - tch_hr_b_to_d(d, b); - osmo_crc8gen_set_bits(&gsm0503_tch_fr_crc3, d + 73, 22, p); - tch_hr_reorder(conv, d, p); - - osmo_conv_encode(&gsm0503_tch_hr, conv, cB); - memcpy(cB + 211, d + 95, 17); - +hr_conv_coding: + osmo_conv_encode(&gsm0503_tch_hr, conv, cB); h = 0; - gsm0503_tch_hr_interleave(cB, iB); - for (i = 0; i < 4; i++) { gsm0503_tch_burst_map(&iB[i * 114], &bursts[i * 116], &h, i >> 1); } - break; + case 0: /* no data, induce BFI in the receiver */ + /* see comments in gsm0503_tch_fr_encode() - same deal here */ + memset(conv, 0, sizeof(conv)); + memset(cB + 211, 0, 17); + goto hr_conv_coding; case GSM_MACBLOCK_LEN: /* FACCH */ _xcch_encode_cB(cB, tch_data); - h = 1; - gsm0503_tch_fr_interleave(cB, iB); - for (i = 0; i < 6; i++) { gsm0503_tch_burst_map(&iB[i * 114], &bursts[i * 116], &h, i >> 2); } - for (i = 2; i < 4; i++) { gsm0503_tch_burst_map(&iB[i * 114 + 456], &bursts[i * 116], &h, 1); } - break; default: return -1; @@ -2118,6 +2156,26 @@ int gsm0503_tch_hr_encode(ubit_t *bursts, const uint8_t *tch_data, int len) return 0; } +/* TCH/AFS: parse codec ID (CMI or CMC/CMR) from coded in-band data (16 bit) */ +static uint8_t gsm0503_tch_afs_decode_inband(const sbit_t *cB) +{ + unsigned int id = 0, best = 0; + unsigned int i, j, k; + + for (i = 0; i < 4; i++) { + /* FIXME: why not using remaining (16 - 8) soft-bits here? */ + for (j = 0, k = 0; j < 8; j++) + k += abs(((int)gsm0503_afs_ic_sbit[i][j]) - ((int)cB[j])); + + if (i == 0 || k < best) { + best = k; + id = i; + } + } + + return id; +} + /*! Perform channel decoding of a TCH/AFS channel according TS 05.03 * \param[out] tch_data Codec frame in RTP payload format * \param[in] bursts buffer containing the symbols of 8 bursts @@ -2160,12 +2218,10 @@ int gsm0503_tch_afs_decode_dtx(uint8_t *tch_data, const sbit_t *bursts, { sbit_t iB[912], cB[456], h; ubit_t d[244], p[6], conv[250]; - int i, j, k, best = 0, rv, len, steal = 0, id = 0; - ubit_t cBd[456]; + int i, rv, len, steal = 0, id = -1; *n_errors = 0; *n_bits_total = 0; static ubit_t sid_first_dummy[64] = { 0 }; sbit_t sid_update_enc[256]; - uint8_t dtx_prev; for (i=0; i<8; i++) { gsm0503_tch_burst_unmap(&iB[i * 114], &bursts[i * 116], &h, i >> 2); @@ -2175,6 +2231,12 @@ int gsm0503_tch_afs_decode_dtx(uint8_t *tch_data, const sbit_t *bursts, gsm0503_tch_fr_deinterleave(cB, iB); if (steal > 0) { + /* If not NULL, dtx indicates type of previously decoded TCH/AFS frame. + * It's normally updated by gsm0503_detect_afs_dtx_frame2(), which is not + * reached in case of FACCH. Reset it here to avoid FACCH/F frames being + * misinterpreted as AMR's special DTX frames. */ + if (dtx != NULL) + *dtx = AMR_OTHER; rv = _xcch_decode_cB(tch_data, cB, n_errors, n_bits_total); if (rv) { /* Error decoding FACCH frame */ @@ -2186,16 +2248,20 @@ int gsm0503_tch_afs_decode_dtx(uint8_t *tch_data, const sbit_t *bursts, /* Determine the DTX frame type (SID_UPDATE, ONSET etc...) */ if (dtx) { - osmo_sbit2ubit(cBd, cB, 456); - dtx_prev = *dtx; - *dtx = gsm0503_detect_afs_dtx_frame(n_errors, n_bits_total, cBd); + const enum gsm0503_amr_dtx_frames dtx_prev = *dtx; + + *dtx = gsm0503_detect_afs_dtx_frame2(n_errors, n_bits_total, &id, cB); - if (dtx_prev == AFS_SID_UPDATE && *dtx == AMR_OTHER) { + switch (*dtx) { + case AMR_OTHER: /* NOTE: The AFS_SID_UPDATE frame is splitted into * two half rate frames. If the id marker frame * (AFS_SID_UPDATE) is detected the following frame * contains the actual comfort noised data part of * (AFS_SID_UPDATE_CN). */ + if (dtx_prev != AFS_SID_UPDATE) + break; + /* TODO: parse CMI _and_ CMC/CMR (16 + 16 bit) */ *dtx = AFS_SID_UPDATE_CN; extract_afs_sid_update(sid_update_enc, cB); @@ -2211,35 +2277,28 @@ int gsm0503_tch_afs_decode_dtx(uint8_t *tch_data, const sbit_t *bursts, tch_amr_sid_update_append(conv, 1, (codec_mode_req) ? codec[*ft] - : codec[id]); + : codec[id > 0 ? id : 0]); tch_amr_reassemble(tch_data, conv, 39); len = 5; goto out; - } else if (*dtx == AFS_SID_FIRST) { + case AFS_SID_FIRST: /* TODO: parse CMI or CMC/CMR (16 bit) */ tch_amr_sid_update_append(sid_first_dummy, 0, (codec_mode_req) ? codec[*ft] - : codec[id]); - tch_amr_reassemble(tch_data, conv, 39); + : codec[id > 0 ? id : 0]); + tch_amr_reassemble(tch_data, sid_first_dummy, 39); len = 5; goto out; - } else if (*dtx == AFS_ONSET) { + case AFS_SID_UPDATE: /* TODO: parse CMI _and_ CMC/CMR (16 + 16 bit) */ + case AFS_ONSET: len = 0; goto out; + default: + break; } } - for (i = 0; i < 4; i++) { - for (j = 0, k = 0; j < 8; j++) - k += abs(((int)gsm0503_afs_ic_sbit[i][j]) - ((int)cB[j])); - - if (i == 0 || k < best) { - best = k; - id = i; - } - } - - /* Check if indicated codec fits into range of codecs */ - if (id >= codecs) { + /* Parse codec ID (CMI or CMC/CMR) and check if it fits into range of codecs */ + if ((id = gsm0503_tch_afs_decode_inband(&cB[0])) >= codecs) { /* Codec mode out of range, return id */ return id; } @@ -2390,10 +2449,12 @@ int gsm0503_tch_afs_decode_dtx(uint8_t *tch_data, const sbit_t *bursts, out: /* Change codec request / indication, if frame is valid */ - if (codec_mode_req) - *cmr = id; - else - *ft = id; + if (id != -1) { + if (codec_mode_req) + *cmr = id; + else + *ft = id; + } return len; } @@ -2401,7 +2462,7 @@ out: /*! Perform channel encoding on a TCH/AFS channel according to TS 05.03 * \param[out] bursts caller-allocated output buffer for bursts bits * \param[in] tch_data Codec input data in RTP payload format - * \param[in] len Length of \a tch_data in bytes + * \param[in] len Length of \a tch_data in bytes or 0 to generate a bad frame * \param[in] codec_mode_req Use CMR (1) or FT (0) * \param[in] codec Array of codecs (active codec set) * \param[in] codecs Number of entries in \a codec @@ -2409,7 +2470,7 @@ out: * \param[in] cmr Codec Mode Request (used in codec_mode_req = 1 only) * \returns 0 in case of success; negative on error */ int gsm0503_tch_afs_encode(ubit_t *bursts, const uint8_t *tch_data, int len, - int codec_mode_req, uint8_t *codec, int codecs, uint8_t ft, + int codec_mode_req, const uint8_t *codec, int codecs, uint8_t ft, uint8_t cmr) { ubit_t iB[912], cB[456], h; @@ -2427,28 +2488,27 @@ int gsm0503_tch_afs_encode(ubit_t *bursts, const uint8_t *tch_data, int len, h = 0; - if (codec_mode_req) { - if (cmr >= codecs) { - /* FIXME: CMR ID is not in codec list! */ - return -1; - } - id = cmr; - } else { - if (ft >= codecs) { - /* FIXME: FT ID is not in codec list! */ - return -1; - } - id = ft; - } + id = codec_mode_req ? cmr : ft; + if (OSMO_UNLIKELY(id >= ARRAY_SIZE(gsm0503_afs_ic_ubit))) + return -1; + if (OSMO_UNLIKELY(ft >= codecs)) + return -1; switch (codec[ft]) { case 7: /* TCH/AFS12.2 */ - if (len != 31) - goto invalid_length; - - tch_amr_disassemble(d, tch_data, 244); - - osmo_crc8gen_set_bits(&gsm0503_amr_crc6, d, 81, p); + if (!len) { + /* No data, induce BFI in the receiver by inverted CRC bits. + * The data bit are all 0, so the correct parity bits would be 111111. */ + memset(d, 0, 244); + memset(p, 0, 6); + } else { + if (len != 31) + goto invalid_length; + + tch_amr_disassemble(d, tch_data, 244); + + osmo_crc8gen_set_bits(&gsm0503_amr_crc6, d, 81, p); + } tch_amr_merge(conv, d, p, 244, 81); @@ -2456,12 +2516,18 @@ int gsm0503_tch_afs_encode(ubit_t *bursts, const uint8_t *tch_data, int len, break; case 6: /* TCH/AFS10.2 */ - if (len != 26) - goto invalid_length; + if (!len) { + /* See comment above. */ + memset(d, 0, 204); + memset(p, 0, 6); + } else { + if (len != 26) + goto invalid_length; - tch_amr_disassemble(d, tch_data, 204); + tch_amr_disassemble(d, tch_data, 204); - osmo_crc8gen_set_bits(&gsm0503_amr_crc6, d, 65, p); + osmo_crc8gen_set_bits(&gsm0503_amr_crc6, d, 65, p); + } tch_amr_merge(conv, d, p, 204, 65); @@ -2469,12 +2535,18 @@ int gsm0503_tch_afs_encode(ubit_t *bursts, const uint8_t *tch_data, int len, break; case 5: /* TCH/AFS7.95 */ - if (len != 20) - goto invalid_length; + if (!len) { + /* See comment above. */ + memset(d, 0, 159); + memset(p, 0, 6); + } else { + if (len != 20) + goto invalid_length; - tch_amr_disassemble(d, tch_data, 159); + tch_amr_disassemble(d, tch_data, 159); - osmo_crc8gen_set_bits(&gsm0503_amr_crc6, d, 75, p); + osmo_crc8gen_set_bits(&gsm0503_amr_crc6, d, 75, p); + } tch_amr_merge(conv, d, p, 159, 75); @@ -2482,12 +2554,18 @@ int gsm0503_tch_afs_encode(ubit_t *bursts, const uint8_t *tch_data, int len, break; case 4: /* TCH/AFS7.4 */ - if (len != 19) - goto invalid_length; + if (!len) { + /* See comment above. */ + memset(d, 0, 148); + memset(p, 0, 6); + } else { + if (len != 19) + goto invalid_length; - tch_amr_disassemble(d, tch_data, 148); + tch_amr_disassemble(d, tch_data, 148); - osmo_crc8gen_set_bits(&gsm0503_amr_crc6, d, 61, p); + osmo_crc8gen_set_bits(&gsm0503_amr_crc6, d, 61, p); + } tch_amr_merge(conv, d, p, 148, 61); @@ -2495,12 +2573,18 @@ int gsm0503_tch_afs_encode(ubit_t *bursts, const uint8_t *tch_data, int len, break; case 3: /* TCH/AFS6.7 */ - if (len != 17) - goto invalid_length; + if (!len) { + /* See comment above. */ + memset(d, 0, 134); + memset(p, 0, 6); + } else { + if (len != 17) + goto invalid_length; - tch_amr_disassemble(d, tch_data, 134); + tch_amr_disassemble(d, tch_data, 134); - osmo_crc8gen_set_bits(&gsm0503_amr_crc6, d, 55, p); + osmo_crc8gen_set_bits(&gsm0503_amr_crc6, d, 55, p); + } tch_amr_merge(conv, d, p, 134, 55); @@ -2508,12 +2592,18 @@ int gsm0503_tch_afs_encode(ubit_t *bursts, const uint8_t *tch_data, int len, break; case 2: /* TCH/AFS5.9 */ - if (len != 15) - goto invalid_length; + if (!len) { + /* See comment above. */ + memset(d, 0, 118); + memset(p, 0, 6); + } else { + if (len != 15) + goto invalid_length; - tch_amr_disassemble(d, tch_data, 118); + tch_amr_disassemble(d, tch_data, 118); - osmo_crc8gen_set_bits(&gsm0503_amr_crc6, d, 55, p); + osmo_crc8gen_set_bits(&gsm0503_amr_crc6, d, 55, p); + } tch_amr_merge(conv, d, p, 118, 55); @@ -2521,12 +2611,18 @@ int gsm0503_tch_afs_encode(ubit_t *bursts, const uint8_t *tch_data, int len, break; case 1: /* TCH/AFS5.15 */ - if (len != 13) - goto invalid_length; + if (!len) { + /* See comment above. */ + memset(d, 0, 103); + memset(p, 0, 6); + } else { + if (len != 13) + goto invalid_length; - tch_amr_disassemble(d, tch_data, 103); + tch_amr_disassemble(d, tch_data, 103); - osmo_crc8gen_set_bits(&gsm0503_amr_crc6, d, 49, p); + osmo_crc8gen_set_bits(&gsm0503_amr_crc6, d, 49, p); + } tch_amr_merge(conv, d, p, 103, 49); @@ -2534,12 +2630,18 @@ int gsm0503_tch_afs_encode(ubit_t *bursts, const uint8_t *tch_data, int len, break; case 0: /* TCH/AFS4.75 */ - if (len != 12) - goto invalid_length; + if (!len) { + /* See comment above. */ + memset(d, 0, 95); + memset(p, 0, 6); + } else { + if (len != 12) + goto invalid_length; - tch_amr_disassemble(d, tch_data, 95); + tch_amr_disassemble(d, tch_data, 95); - osmo_crc8gen_set_bits(&gsm0503_amr_crc6, d, 39, p); + osmo_crc8gen_set_bits(&gsm0503_amr_crc6, d, 39, p); + } tch_amr_merge(conv, d, p, 95, 39); @@ -2568,9 +2670,29 @@ invalid_length: return -1; } -/*! Perform channel decoding of a TCH/AFS channel according TS 05.03 +/* TCH/AHS: parse codec ID (CMI or CMC/CMR) from coded in-band data (16 bit) */ +static uint8_t gsm0503_tch_ahs_decode_inband(const sbit_t *cB) +{ + unsigned int id = 0, best = 0; + unsigned int i, j, k; + + for (i = 0, k = 0; i < 4; i++) { + /* FIXME: why not using remaining (16 - 4) soft-bits here? */ + for (j = 0, k = 0; j < 4; j++) + k += abs(((int)gsm0503_ahs_ic_sbit[i][j]) - ((int)cB[j])); + + if (i == 0 || k < best) { + best = k; + id = i; + } + } + + return id; +} + +/*! Perform channel decoding of a TCH/AHS channel according TS 05.03 * \param[out] tch_data Codec frame in RTP payload format - * \param[in] bursts buffer containing the symbols of 8 bursts + * \param[in] bursts buffer containing the symbols of 6 bursts * \param[in] odd Is this an odd (1) or even (0) frame number? * \param[in] codec_mode_req is this CMR (1) or CMC (0) * \param[in] codec array of active codecs (active codec set) @@ -2591,9 +2713,9 @@ int gsm0503_tch_ahs_decode(uint8_t *tch_data, const sbit_t *bursts, int odd, n_bits_total, NULL); } -/*! Perform channel decoding of a TCH/AFS channel according TS 05.03 +/*! Perform channel decoding of a TCH/AHS channel according TS 05.03 * \param[out] tch_data Codec frame in RTP payload format - * \param[in] bursts buffer containing the symbols of 8 bursts + * \param[in] bursts buffer containing the symbols of 6 bursts * \param[in] odd Is this an odd (1) or even (0) frame number? * \param[in] codec_mode_req is this CMR (1) or CMC (0) * \param[in] codec array of active codecs (active codec set) @@ -2612,10 +2734,8 @@ int gsm0503_tch_ahs_decode_dtx(uint8_t *tch_data, const sbit_t *bursts, int odd, { sbit_t iB[912], cB[456], h; ubit_t d[244], p[6], conv[135]; - int i, j, k, best = 0, rv, len, steal = 0, id = 0; - ubit_t cBd[456]; + int i, rv, len, steal = 0, id = -1; static ubit_t sid_first_dummy[64] = { 0 }; - uint8_t dtx_prev; /* only unmap the stealing bits */ if (!odd) { @@ -2631,6 +2751,13 @@ int gsm0503_tch_ahs_decode_dtx(uint8_t *tch_data, const sbit_t *bursts, int odd, /* if we found a stole FACCH, but only at correct alignment */ if (steal > 0) { + /* If not NULL, dtx indicates type of previously decoded TCH/AHS frame. + * It's normally updated by gsm0503_detect_ahs_dtx_frame2(), which is not + * reached in case of FACCH. Reset it here to avoid FACCH/H frames being + * misinterpreted as AMR's special DTX frames. */ + if (dtx != NULL) + *dtx = AMR_OTHER; + for (i = 0; i < 6; i++) { gsm0503_tch_burst_unmap(&iB[i * 114], &bursts[i * 116], NULL, i >> 2); @@ -2661,21 +2788,37 @@ int gsm0503_tch_ahs_decode_dtx(uint8_t *tch_data, const sbit_t *bursts, int odd, /* Determine the DTX frame type (SID_UPDATE, ONSET etc...) */ if (dtx) { - osmo_sbit2ubit(cBd, cB, 456); - dtx_prev = *dtx; - *dtx = gsm0503_detect_ahs_dtx_frame(n_errors, n_bits_total, cBd); - - if (dtx_prev == AHS_SID_UPDATE && *dtx == AMR_OTHER) { - /* NOTE: The AHS_SID_UPDATE frame is splitted into - * two half rate frames. If the id marker frame - * (AHS_SID_UPDATE) is detected the following frame - * contains the actual comfort noised data part of - * (AHS_SID_UPDATE_CN). */ + int n_bits_total_sid; + int n_errors_sid; + + *dtx = gsm0503_detect_ahs_dtx_frame2(n_errors, n_bits_total, &id, cB); + /* TODO: detect and handle AHS_SID_UPDATE + AHS_SID_UPDATE_INH */ + + switch (*dtx) { + case AHS_SID_UPDATE: /* TODO: parse CMI _and_ CMC/CMR (16 + 16 bit) */ + /* cB[] contains 16 bits of coded in-band data and 212 bits containing + * the identification marker. We need to unmap/deinterleave 114 odd + * bits from the last two blocks, 114 even bits from the first two + * blocks and combine them together. */ + gsm0503_tch_burst_unmap(&iB[0 * 114], &bursts[2 * 116], NULL, 0); + gsm0503_tch_burst_unmap(&iB[1 * 114], &bursts[3 * 116], NULL, 0); + gsm0503_tch_burst_unmap(&iB[2 * 114], &bursts[0 * 116], NULL, 1); + gsm0503_tch_burst_unmap(&iB[3 * 114], &bursts[1 * 116], NULL, 1); + gsm0503_tch_hr_deinterleave(cB, iB); + + /* cB[] is expected to contain 16 bits of coded in-band data and + * 212 bits containing the coded data (53 bits coded at 1/4 rate). */ *dtx = AHS_SID_UPDATE_CN; osmo_conv_decode_ber(&gsm0503_tch_axs_sid_update, - cB + 16, conv, n_errors, - n_bits_total); + cB + 16, conv, &n_errors_sid, + &n_bits_total_sid); + /* gsm0503_detect_ahs_dtx_frame2() calculates BER for the marker, + * osmo_conv_decode_ber() calculates BER for the coded data. */ + if (n_errors != NULL) + *n_errors += n_errors_sid; + if (n_bits_total != NULL) + *n_bits_total += n_bits_total_sid; rv = osmo_crc16gen_check_bits(&gsm0503_amr_crc14, conv, 35, conv + 35); if (rv != 0) { @@ -2685,38 +2828,30 @@ int gsm0503_tch_ahs_decode_dtx(uint8_t *tch_data, const sbit_t *bursts, int odd, tch_amr_sid_update_append(conv, 1, (codec_mode_req) ? codec[*ft] - : codec[id]); + : codec[id > 0 ? id : 0]); tch_amr_reassemble(tch_data, conv, 39); len = 5; goto out; - } else if (*dtx == AHS_SID_FIRST_P2) { + case AHS_SID_FIRST_P2: tch_amr_sid_update_append(sid_first_dummy, 0, (codec_mode_req) ? codec[*ft] - : codec[id]); + : codec[id > 0 ? id : 0]); tch_amr_reassemble(tch_data, sid_first_dummy, 39); len = 5; goto out; - } else if (*dtx == AHS_SID_UPDATE || *dtx == AHS_ONSET - || *dtx == AHS_SID_FIRST_INH - || *dtx == AHS_SID_UPDATE_INH - || *dtx == AHS_SID_FIRST_P1) { + case AHS_ONSET: + case AHS_SID_FIRST_INH: /* TODO: parse CMI or CMC/CMR (16 bit) */ + case AHS_SID_UPDATE_INH: /* TODO: parse CMI or CMC/CMR (16 bit) */ + case AHS_SID_FIRST_P1: /* TODO: parse CMI or CMC/CMR (16 bit) */ len = 0; goto out; + default: + break; } } - for (i = 0; i < 4; i++) { - for (j = 0, k = 0; j < 4; j++) - k += abs(((int)gsm0503_ahs_ic_sbit[i][j]) - ((int)cB[j])); - - if (i == 0 || k < best) { - best = k; - id = i; - } - } - - /* Check if indicated codec fits into range of codecs */ - if (id >= codecs) { + /* Parse codec ID (CMI or CMC/CMR) and check if it fits into range of codecs */ + if ((id = gsm0503_tch_ahs_decode_inband(&cB[0])) >= codecs) { /* Codec mode out of range, return id */ return id; } @@ -2851,10 +2986,12 @@ int gsm0503_tch_ahs_decode_dtx(uint8_t *tch_data, const sbit_t *bursts, int odd, out: /* Change codec request / indication, if frame is valid */ - if (codec_mode_req) - *cmr = id; - else - *ft = id; + if (id != -1) { + if (codec_mode_req) + *cmr = id; + else + *ft = id; + } return len; } @@ -2862,7 +2999,7 @@ out: /*! Perform channel encoding on a TCH/AHS channel according to TS 05.03 * \param[out] bursts caller-allocated output buffer for bursts bits * \param[in] tch_data Codec input data in RTP payload format - * \param[in] len Length of \a tch_data in bytes + * \param[in] len Length of \a tch_data in bytes or 0 to generate a bad frame * \param[in] codec_mode_req Use CMR (1) or FT (0) * \param[in] codec Array of codecs (active codec set) * \param[in] codecs Number of entries in \a codec @@ -2870,7 +3007,7 @@ out: * \param[in] cmr Codec Mode Request (used in codec_mode_req = 1 only) * \returns 0 in case of success; negative on error */ int gsm0503_tch_ahs_encode(ubit_t *bursts, const uint8_t *tch_data, int len, - int codec_mode_req, uint8_t *codec, int codecs, uint8_t ft, + int codec_mode_req, const uint8_t *codec, int codecs, uint8_t ft, uint8_t cmr) { ubit_t iB[912], cB[456], h; @@ -2897,28 +3034,27 @@ int gsm0503_tch_ahs_encode(ubit_t *bursts, const uint8_t *tch_data, int len, h = 0; - if (codec_mode_req) { - if (cmr >= codecs) { - /* FIXME: CMR ID %d not in codec list */ - return -1; - } - id = cmr; - } else { - if (ft >= codecs) { - /* FIXME: FT ID %d not in codec list */ - return -1; - } - id = ft; - } + id = codec_mode_req ? cmr : ft; + if (OSMO_UNLIKELY(id >= ARRAY_SIZE(gsm0503_ahs_ic_ubit))) + return -1; + if (OSMO_UNLIKELY(ft >= codecs)) + return -1; switch (codec[ft]) { case 5: /* TCH/AHS7.95 */ - if (len != 20) - goto invalid_length; - - tch_amr_disassemble(d, tch_data, 159); - - osmo_crc8gen_set_bits(&gsm0503_amr_crc6, d, 67, p); + if (!len) { + /* No data, induce BFI in the receiver by inverted CRC bits. + * The data bit are all 0, so the correct parity bits would be 111111. */ + memset(d, 0, 159); + memset(p, 0, 6); + } else { + if (len != 20) + goto invalid_length; + + tch_amr_disassemble(d, tch_data, 159); + + osmo_crc8gen_set_bits(&gsm0503_amr_crc6, d, 67, p); + } tch_amr_merge(conv, d, p, 123, 67); @@ -2928,12 +3064,18 @@ int gsm0503_tch_ahs_encode(ubit_t *bursts, const uint8_t *tch_data, int len, break; case 4: /* TCH/AHS7.4 */ - if (len != 19) - goto invalid_length; + if (!len) { + /* See comment above. */ + memset(d, 0, 148); + memset(p, 0, 6); + } else { + if (len != 19) + goto invalid_length; - tch_amr_disassemble(d, tch_data, 148); + tch_amr_disassemble(d, tch_data, 148); - osmo_crc8gen_set_bits(&gsm0503_amr_crc6, d, 61, p); + osmo_crc8gen_set_bits(&gsm0503_amr_crc6, d, 61, p); + } tch_amr_merge(conv, d, p, 120, 61); @@ -2943,12 +3085,18 @@ int gsm0503_tch_ahs_encode(ubit_t *bursts, const uint8_t *tch_data, int len, break; case 3: /* TCH/AHS6.7 */ - if (len != 17) - goto invalid_length; + if (!len) { + /* See comment above. */ + memset(d, 0, 134); + memset(p, 0, 6); + } else { + if (len != 17) + goto invalid_length; - tch_amr_disassemble(d, tch_data, 134); + tch_amr_disassemble(d, tch_data, 134); - osmo_crc8gen_set_bits(&gsm0503_amr_crc6, d, 55, p); + osmo_crc8gen_set_bits(&gsm0503_amr_crc6, d, 55, p); + } tch_amr_merge(conv, d, p, 110, 55); @@ -2958,12 +3106,18 @@ int gsm0503_tch_ahs_encode(ubit_t *bursts, const uint8_t *tch_data, int len, break; case 2: /* TCH/AHS5.9 */ - if (len != 15) - goto invalid_length; + if (!len) { + /* See comment above. */ + memset(d, 0, 118); + memset(p, 0, 6); + } else { + if (len != 15) + goto invalid_length; - tch_amr_disassemble(d, tch_data, 118); + tch_amr_disassemble(d, tch_data, 118); - osmo_crc8gen_set_bits(&gsm0503_amr_crc6, d, 55, p); + osmo_crc8gen_set_bits(&gsm0503_amr_crc6, d, 55, p); + } tch_amr_merge(conv, d, p, 102, 55); @@ -2973,12 +3127,18 @@ int gsm0503_tch_ahs_encode(ubit_t *bursts, const uint8_t *tch_data, int len, break; case 1: /* TCH/AHS5.15 */ - if (len != 13) - goto invalid_length; + if (!len) { + /* See comment above. */ + memset(d, 0, 103); + memset(p, 0, 6); + } else { + if (len != 13) + goto invalid_length; - tch_amr_disassemble(d, tch_data, 103); + tch_amr_disassemble(d, tch_data, 103); - osmo_crc8gen_set_bits(&gsm0503_amr_crc6, d, 49, p); + osmo_crc8gen_set_bits(&gsm0503_amr_crc6, d, 49, p); + } tch_amr_merge(conv, d, p, 91, 49); @@ -2988,12 +3148,18 @@ int gsm0503_tch_ahs_encode(ubit_t *bursts, const uint8_t *tch_data, int len, break; case 0: /* TCH/AHS4.75 */ - if (len != 12) - goto invalid_length; + if (!len) { + /* See comment above. */ + memset(d, 0, 95); + memset(p, 0, 6); + } else { + if (len != 12) + goto invalid_length; - tch_amr_disassemble(d, tch_data, 95); + tch_amr_disassemble(d, tch_data, 95); - osmo_crc8gen_set_bits(&gsm0503_amr_crc6, d, 39, p); + osmo_crc8gen_set_bits(&gsm0503_amr_crc6, d, 39, p); + } tch_amr_merge(conv, d, p, 83, 39); @@ -3212,4 +3378,491 @@ int gsm0503_sch_encode(ubit_t *burst, const uint8_t *sb_info) return 0; } +/* + * GSM CSD transcoding + */ + +static inline void _tch_csd_burst_map(ubit_t *burst, const ubit_t *iB) +{ + unsigned int i; + + /* hu(B): copy *even* numbered bits if not stolen by FACCH */ + if (burst[58] == 0) { + for (i = 0; i < 57; i += 2) + burst[i] |= iB[i]; + for (i = 58; i < 114; i += 2) + burst[i + 2] |= iB[i]; + } + + /* hl(B): copy *odd* numbered bits if not stolen by FACCH */ + if (burst[57] == 0) { + for (i = 1; i < 57; i += 2) + burst[i] |= iB[i]; + for (i = 57; i < 114; i += 2) + burst[i + 2] |= iB[i]; + } +} + +/*! Perform channel encoding of a TCH/F9.6 channel as per section 3.3. + * \param[out] bursts Caller-allocated buffer for symbols of 22 bursts, + * 22 * 2 * 58 == 2552 bits total. + * \param[in] data Data to be encoded (240 unpacked bits). + * \returns 0 in case of success; negative on error. */ +int gsm0503_tch_fr96_encode(ubit_t *bursts, const ubit_t *data) +{ + ubit_t iB[22 * 114], cB[4 * 114]; + ubit_t conv[4 * 60 + 4]; + + /* 3.3.2 Block code: b1(60) + b2(60) + b3(60) + b4(60) + pad(4) */ + memcpy(&conv[0], &data[0], 4 * 60); + /* pad(4) is set to 0 by osmo_conv_encode() below */ + + /* 3.3.3 Convolutional encoder */ + osmo_conv_encode(&gsm0503_tch_f96, &conv[0], &cB[0]); + + /* 3.3.4 Interleaving */ + memset(&iB[0], 0, sizeof(iB)); + gsm0503_tch_f96_interleave(&cB[0], &iB[0]); + + /* 3.3.5 Mapping on a burst: as specified for TCH/FS in subclause 3.1.4 */ + for (unsigned int i = 0; i < 22; i++) + _tch_csd_burst_map(&bursts[i * 116], &iB[i * 114]); + + return 0; +} + +/*! Perform channel decoding of a TCH/F9.6 channel as per section 3.3. + * \param[out] data Caller-allocated buffer for decoded data (240 unpacked bits). + * \param[in] bursts Buffer containing the symbols of 22 bursts, + * 22 * 2 * 58 == 2552 bits total. + * \param[out] n_errors Number of detected bit errors. + * \param[out] n_bits_total Total number of bits. + * \returns Number of unpacked bits used in the output buffer; negative on error. */ +int gsm0503_tch_fr96_decode(ubit_t *data, const sbit_t *bursts, + int *n_errors, int *n_bits_total) +{ + sbit_t iB[22 * 114], cB[4 * 114]; + ubit_t conv[4 * 60 + 4]; + + /* 3.3.5 Mapping on a burst: as specified for TCH/FS in subclause 3.1.4 */ + for (unsigned int i = 0; i < 22; i++) { + memcpy(&iB[i * 114], &bursts[i * 116], 57); + memcpy(&iB[i * 114 + 57], &bursts[i * 116 + 59], 57); + } + + /* 3.3.4 Interleaving */ + gsm0503_tch_f96_deinterleave(&cB[0], &iB[0]); + + /* 3.3.3 Convolutional encoder */ + osmo_conv_decode_ber(&gsm0503_tch_f96, &cB[0], &conv[0], n_errors, n_bits_total); + + /* 3.3.2 Block code: b1(60) + b2(60) + b3(60) + b4(60) + pad(4) */ + memcpy(&data[0], &conv[0], 4 * 60); + + return 4 * 60; +} + +/*! Perform channel encoding of a TCH/F4.8 channel as per section 3.4. + * \param[out] bursts Caller-allocated buffer for symbols of 22 bursts, + * 22 * 2 * 58 == 2552 bits total. + * \param[in] data Data to be encoded (120 unpacked bits). + * \returns 0 in case of success; negative on error */ +int gsm0503_tch_fr48_encode(ubit_t *bursts, const ubit_t *data) +{ + ubit_t iB[22 * 114], cB[4 * 114]; + ubit_t conv[2 * 60 + 32]; + + /* 3.4.2 Block code: + * + * Sixteen bits equal to 0 are added to the 60 information bits, the result + * being a block of 76 bits, {u(0),u(1),...,u(75)}, with: + * + * u(19k+p) = d(15k+p) for k = 0,1,2,3 and p = 0,1,...,14; + * u(19k+p) = 0 for k = 0,1,2,3 and p = 15,16,17,18. + * + * Two such blocks forming a block of 152 bits: u1 + u2. */ + for (unsigned int k = 0; k < 2 * 4; k++) { + memcpy(&conv[19 * k], &data[15 * k], 15); + memset(&conv[19 * k + 15], 0, 4); + } + + /* 3.4.3 Convolutional encoder */ + osmo_conv_encode(&gsm0503_tch_f48, &conv[0], &cB[0]); + + /* 3.4.4 Interleaving: as specified for the TCH/F9.6 in subclause 3.3.4 */ + memset(&iB[0], 0, sizeof(iB)); + gsm0503_tch_f96_interleave(&cB[0], &iB[0]); + + /* 3.4.5 Mapping on a burst: as specified for TCH/FS in subclause 3.1.4 */ + for (unsigned int i = 0; i < 22; i++) + _tch_csd_burst_map(&bursts[i * 116], &iB[i * 114]); + + return 0; +} + +/*! Perform channel decoding of a TCH/F4.8 channel as per section 3.4. + * \param[out] data Caller-allocated buffer for decoded data (120 unpacked bits). + * \param[in] bursts Buffer containing the symbols of 22 bursts, + * 22 * 2 * 58 == 2552 bits total. + * \param[out] n_errors Number of detected bit errors. + * \param[out] n_bits_total Total number of bits. + * \returns Number of unpacked bits used in the output buffer; negative on error. */ +int gsm0503_tch_fr48_decode(ubit_t *data, const sbit_t *bursts, + int *n_errors, int *n_bits_total) +{ + sbit_t iB[22 * 114], cB[4 * 114]; + ubit_t conv[2 * 60 + 32]; + + /* 3.4.5 Mapping on a burst: as specified for TCH/FS in subclause 3.1.4 */ + for (unsigned int i = 0; i < 22; i++) { + memcpy(&iB[i * 114], &bursts[i * 116], 57); + memcpy(&iB[i * 114 + 57], &bursts[i * 116 + 59], 57); + } + + /* 3.4.4 Interleaving: as specified for the TCH/F9.6 in subclause 3.3.4 */ + gsm0503_tch_f96_deinterleave(&cB[0], &iB[0]); + + /* 3.4.3 Convolutional encoder */ + osmo_conv_decode_ber(&gsm0503_tch_f48, &cB[0], &conv[0], n_errors, n_bits_total); + + /* 3.4.2 Block code: + * + * Sixteen bits equal to 0 are added to the 60 information bits, the result + * being a block of 76 bits, {u(0),u(1),...,u(75)}, with: + * + * u(19k+p) = d(15k+p) for k = 0,1,2,3 and p = 0,1,...,14; + * u(19k+p) = 0 for k = 0,1,2,3 and p = 15,16,17,18. + * + * Two such blocks forming a block of 152 bits: u1 + u2. */ + for (unsigned int k = 0; k < 2 * 4; k++) + memcpy(&data[15 * k], &conv[19 * k], 15); + + return 2 * 60; +} + +/*! Perform channel encoding of a TCH/H4.8 channel as per section 3.5. + * The algorithm is identical to TCH/F9.6, so it's just a wrapper. + * \param[out] bursts Caller-allocated buffer for symbols of 22 bursts, + * 22 * 2 * 58 == 2552 bits total. + * \param[in] data Data to be encoded (240 unpacked bits). + * \returns 0 in case of success; negative on error */ +int gsm0503_tch_hr48_encode(ubit_t *bursts, const ubit_t *data) +{ + return gsm0503_tch_fr96_encode(bursts, data); +} + +/*! Perform channel decoding of a TCH/H4.8 channel as per section 3.5. + * The algorithm is identical to TCH/F9.6, so it's just a wrapper. + * \param[out] data Caller-allocated buffer for decoded data (240 unpacked bits). + * \param[in] bursts Buffer containing the symbols of 22 bursts, + * 22 * 2 * 58 == 2552 bits total. + * \param[out] n_errors Number of detected bit errors. + * \param[out] n_bits_total Total number of bits. + * \returns Number of unpacked bits used in the output buffer; negative on error. */ +int gsm0503_tch_hr48_decode(ubit_t *data, const sbit_t *bursts, + int *n_errors, int *n_bits_total) +{ + return gsm0503_tch_fr96_decode(data, bursts, n_errors, n_bits_total); +} + +/*! Perform channel encoding of a TCH/F2.4 channel as per section 3.6. + * \param[out] bursts Caller-allocated buffer for symbols of 8 bursts, + * 8 * 2 * 58 == 928 bits total. + * \param[in] data Data to be encoded (72 unpacked bits). + * \returns 0 in case of success; negative on error */ +int gsm0503_tch_fr24_encode(ubit_t *bursts, const ubit_t *data) +{ + ubit_t iB[8 * 114], cB[4 * 114]; + const ubit_t h = 0; + + /* 3.6.{1-3} Block code and Convolutional encoder */ + osmo_conv_encode(&gsm0503_tch_f24, &data[0], &cB[0]); + + /* 3.6.4 Interleaving: as specified for the TCH/FS in subclause 3.1.3 */ + gsm0503_tch_fr_interleave(&cB[0], &iB[0]); + + /* 3.6.5 Mapping on a burst: as specified for TCH/FS in subclause 3.1.4 */ + for (unsigned int i = 0; i < 8; i++) + gsm0503_tch_burst_map(&iB[i * 114], &bursts[i * 116], &h, i >> 2); + + return 0; +} + +/*! Perform channel decoding of a TCH/F2.4 channel as per section 3.6. + * \param[out] data Caller-allocated buffer for decoded data (72 unpacked bits). + * \param[in] bursts Buffer containing the symbols of 8 bursts, + * 8 * 2 * 58 == 928 bits total. + * \param[out] n_errors Number of detected bit errors. + * \param[out] n_bits_total Total number of bits. + * \returns Number of unpacked bits used in the output buffer; negative on error. */ +int gsm0503_tch_fr24_decode(ubit_t *data, const sbit_t *bursts, + int *n_errors, int *n_bits_total) +{ + sbit_t iB[8 * 114], cB[4 * 114]; + + /* 3.6.5 Mapping on a burst: as specified for TCH/FS in subclause 3.1.4 */ + for (unsigned int i = 0; i < 8; i++) + gsm0503_tch_burst_unmap(&iB[i * 114], &bursts[i * 116], NULL, i >> 2); + + /* 3.6.4 Interleaving: as specified for the TCH/FS in subclause 3.1.3 */ + gsm0503_tch_fr_deinterleave(&cB[0], &iB[0]); + + /* 3.6.{1-3} Block code and Convolutional encoder */ + osmo_conv_decode_ber(&gsm0503_tch_f24, &cB[0], &data[0], n_errors, n_bits_total); + + return 72; +} + +/*! Perform channel encoding of a TCH/H2.4 channel as per section 3.7. + * \param[out] bursts Caller-allocated buffer for symbols of 22 bursts, + * 22 * 2 * 58 == 2552 bits total. + * \param[in] data Data to be encoded (144 unpacked bits). + * \returns 0 in case of success; negative on error */ +int gsm0503_tch_hr24_encode(ubit_t *bursts, const ubit_t *data) +{ + ubit_t iB[22 * 114], cB[4 * 114]; + + /* 3.7.{1-3} Block code and Convolutional encoder */ + osmo_conv_encode(&gsm0503_tch_h24, &data[ 0], &cB[ 0]); + osmo_conv_encode(&gsm0503_tch_h24, &data[72], &cB[228]); + + /* 3.7.4 Interleaving: as specified for the TCH/F9.6 in subclause 3.3.4 */ + memset(&iB[0], 0, sizeof(iB)); + gsm0503_tch_f96_interleave(&cB[0], &iB[0]); + + /* 3.7.5 Mapping on a burst: as specified for TCH/FS in subclause 3.1.4 */ + for (unsigned int i = 0; i < 22; i++) + _tch_csd_burst_map(&bursts[i * 116], &iB[i * 114]); + + return 0; +} + +/*! Perform channel decoding of a TCH/H2.4 channel as per section 3.7. + * \param[out] data Caller-allocated buffer for decoded data (144 unpacked bits). + * \param[in] bursts Buffer containing the symbols of 22 bursts, + * 22 * 2 * 58 == 2552 bits total. + * \param[out] n_errors Number of detected bit errors. + * \param[out] n_bits_total Total number of bits. + * \returns Number of unpacked bits used in the output buffer; negative on error. */ +int gsm0503_tch_hr24_decode(ubit_t *data, const sbit_t *bursts, + int *n_errors, int *n_bits_total) +{ + int n_errors_l[2], n_bits_total_l[2]; + sbit_t iB[22 * 114], cB[4 * 114]; + + /* 3.7.5 Mapping on a burst: as specified for TCH/FS in subclause 3.1.4 */ + for (unsigned int i = 0; i < 22; i++) { + memcpy(&iB[i * 114], &bursts[i * 116], 57); + memcpy(&iB[i * 114 + 57], &bursts[i * 116 + 59], 57); + } + + /* 3.7.4 Interleaving: as specified for the TCH/F9.6 in subclause 3.3.4 */ + gsm0503_tch_f96_deinterleave(&cB[0], &iB[0]); + + /* 3.7.{1-3} Block code and Convolutional encoder */ + osmo_conv_decode_ber(&gsm0503_tch_h24, &cB[ 0], &data[ 0], &n_errors_l[0], &n_bits_total_l[0]); + osmo_conv_decode_ber(&gsm0503_tch_h24, &cB[228], &data[72], &n_errors_l[1], &n_bits_total_l[1]); + + if (n_errors) + *n_errors = n_errors_l[0] + n_errors_l[1]; + + if (n_bits_total) + *n_bits_total = n_bits_total_l[0] + n_bits_total_l[1]; + + return 2 * 72; +} + +/*! Perform channel encoding of a TCH/F14.4 channel as per section 3.8. + * \param[out] bursts Caller-allocated buffer for symbols of 22 bursts, + * 22 * 2 * 58 == 2552 bits total. + * \param[in] data Data to be encoded (290 unpacked bits). + * \returns 0 in case of success; negative on error */ +int gsm0503_tch_fr144_encode(ubit_t *bursts, const ubit_t *data) +{ + ubit_t iB[22 * 114], cB[4 * 114]; + ubit_t conv[290 + 4]; + + /* 3.8.2 Block code: b(290) + pad(4) */ + memcpy(&conv[0], &data[0], 290); + /* pad(4) is set to 0 by osmo_conv_encode() below */ + + /* 3.8.3 Convolutional encoder */ + osmo_conv_encode(&gsm0503_tch_f144, &conv[0], &cB[0]); + + /* 3.8.4 Interleaving */ + memset(&iB[0], 0, sizeof(iB)); + gsm0503_tch_f96_interleave(&cB[0], &iB[0]); + + /* 3.8.5 Mapping on a burst: as specified for TCH/FS in subclause 3.1.4 */ + for (unsigned int i = 0; i < 22; i++) + _tch_csd_burst_map(&bursts[i * 116], &iB[i * 114]); + + return 0; +} + +/*! Perform channel decoding of a TCH/14.4 channel as per section 3.8. + * \param[out] data Caller-allocated buffer for decoded data (290 unpacked bits). + * \param[in] bursts Buffer containing the symbols of 22 bursts, + * 22 * 2 * 58 == 2552 bits total. + * \param[out] n_errors Number of detected bit errors. + * \param[out] n_bits_total Total number of bits. + * \returns Number of unpacked bits used in the output buffer; negative on error. */ +int gsm0503_tch_fr144_decode(ubit_t *data, const sbit_t *bursts, + int *n_errors, int *n_bits_total) +{ + sbit_t iB[22 * 114], cB[4 * 114]; + ubit_t conv[294]; + + /* 3.8.5 Mapping on a burst: as specified for TCH/FS in subclause 3.1.4 */ + for (unsigned int i = 0; i < 22; i++) { + memcpy(&iB[i * 114], &bursts[i * 116], 57); + memcpy(&iB[i * 114 + 57], &bursts[i * 116 + 59], 57); + } + + /* 3.8.4 Interleaving: as specified for the TCH/F9.6 in subclause 3.3.4 */ + gsm0503_tch_f96_deinterleave(&cB[0], &iB[0]); + + /* 3.8.3 Convolutional encoder */ + osmo_conv_decode_ber(&gsm0503_tch_f144, &cB[0], &conv[0], n_errors, n_bits_total); + + /* 3.8.2 Block code: b(290) + pad(4) */ + memcpy(&data[0], &conv[0], 290); + + return 290; +} + +/* + * FACCH/[FH] transcoding + */ + +/*! Perform channel encoding of a FACCH/F data as per section 4.2. + * \param[out] bursts Caller-allocated buffer for symbols of 8 bursts, + * 8 * 2 * 58 == 928 bits total. + * \param[in] data FACCH MAC block to be encoded (GSM_MACBLOCK_LEN). + * \returns 0 in case of success; negative on error */ +int gsm0503_tch_fr_facch_encode(ubit_t *bursts, const uint8_t *data) +{ + ubit_t iB[8 * 114], cB[4 * 114]; + const ubit_t h = 1; + + /* 4.2.1-3 as specified for the SACCH in 4.1.1-3 */ + _xcch_encode_cB(&cB[0], &data[0]); + + /* 4.2.4 Interleaving: as specified for the TCH/FS in subclause 3.1.3 */ + gsm0503_tch_fr_interleave(&cB[0], &iB[0]); + + /* 4.2.5 Mapping on a Burst: + * - hu(B)=1 the even numbered bits in the first 4 bursts and + * - hl(B)=1 the odd numbered bits of the last 4 bursts are stolen. */ + for (unsigned int i = 0; i < 8; i++) + gsm0503_tch_burst_map(&iB[i * 114], &bursts[i * 116], &h, i >> 2); + + return 0; +} + +/*! Perform channel decoding of a FACCH/F data as per section 4.2. + * \param[out] data Caller-allocated buffer for decoded FACCH (GSM_MACBLOCK_LEN). + * \param[in] bursts Buffer containing the symbols of 8 bursts, + * 8 * 2 * 58 == 928 bits total. + * \param[out] n_errors Number of detected bit errors. + * \param[out] n_bits_total Total number of bits. + * \returns Number of bytes used in the output buffer; negative on error. */ +int gsm0503_tch_fr_facch_decode(uint8_t *data, const sbit_t *bursts, + int *n_errors, int *n_bits_total) +{ + sbit_t iB[8 * 114], cB[4 * 114]; + int steal = 0; + + /* FACCH decision: sum of 4 first hu(B) and 4 last hl(B) soft-bits */ + for (unsigned int i = 0; i < 4; i++) + steal -= bursts[i * 116 + 58]; /* hu(B) */ + for (unsigned int i = 4; i < 8; i++) + steal -= bursts[i * 116 + 57]; /* hl(B) */ + if (steal <= 0) + return -1; + + /* 4.2.5 Mapping on a Burst: + * - hu(B)=1 the even numbered bits in the first 4 bursts and + * - hl(B)=1 the odd numbered bits of the last 4 bursts are stolen. */ + for (unsigned int i = 0; i < 8; i++) + gsm0503_tch_burst_unmap(&iB[i * 114], &bursts[i * 116], NULL, i >> 2); + + /* 4.2.4 Interleaving: as specified for the TCH/FS in subclause 3.1.3 */ + gsm0503_tch_fr_deinterleave(&cB[0], &iB[0]); + + /* 4.2.1-3 as specified for the SACCH in 4.1.1-3 */ + if (_xcch_decode_cB(&data[0], &cB[0], n_errors, n_bits_total) != 0) + return -1; + + return GSM_MACBLOCK_LEN; +} + +/*! Perform channel encoding of a FACCH/H data as per section 4.3. + * \param[out] bursts Caller-allocated buffer for symbols of 6 bursts, + * 6 * 2 * 58 == 696 bits total. + * \param[in] data FACCH MAC block to be encoded (GSM_MACBLOCK_LEN). + * \returns 0 in case of success; negative on error */ +int gsm0503_tch_hr_facch_encode(ubit_t *bursts, const uint8_t *data) +{ + ubit_t iB[8 * 114], cB[4 * 114]; + const ubit_t h = 1; + + /* 4.3.1-3 as specified for the SACCH in 4.1.1-3 */ + _xcch_encode_cB(&cB[0], &data[0]); + + /* 4.3.4 Interleaving */ + gsm0503_tch_fr_interleave(&cB[0], &iB[0]); + + /* 4.3.5 Mapping on a Burst: + * - hu(B)=1 the even numbered bits of the first 2 bursts, + * - hu(B)=1 & hl(B)=1 all bits of the middle 2 bursts and + * - hl(B)=1 the odd numbered bits of the last 2 bursts are stolen. */ + for (unsigned int i = 0; i < 6; i++) + gsm0503_tch_burst_map(&iB[i * 114], &bursts[i * 116], &h, i >> 2); + for (unsigned int i = 2; i < 4; i++) + gsm0503_tch_burst_map(&iB[i * 114 + 456], &bursts[i * 116], &h, 1); + + return 0; +} + +/*! Perform channel decoding of a FACCH/H data as per section 4.3. + * \param[out] data Caller-allocated buffer for decoded FACCH (GSM_MACBLOCK_LEN). + * \param[in] bursts Buffer containing the symbols of 6 bursts, + * 6 * 2 * 58 == 696 bits total. + * \param[out] n_errors Number of detected bit errors. + * \param[out] n_bits_total Total number of bits. + * \returns Number of bytes used in the output buffer; negative on error. */ +int gsm0503_tch_hr_facch_decode(uint8_t *data, const sbit_t *bursts, + int *n_errors, int *n_bits_total) +{ + sbit_t iB[8 * 114], cB[4 * 114]; + int steal = 0; + + /* FACCH decision: sum of 4 first hu(B) and 4 last hl(B) soft-bits */ + for (unsigned int i = 0; i < 4; i++) + steal -= bursts[i * 116 + 58]; /* hu(B) */ + for (unsigned int i = 2; i < 6; i++) + steal -= bursts[i * 116 + 57]; /* hl(B) */ + if (steal <= 0) + return -1; + + /* 4.3.5 Mapping on a Burst: + * - hu(B)=1 the even numbered bits of the first 2 bursts, + * - hu(B)=1 & hl(B)=1 all bits of the middle 2 bursts and + * - hl(B)=1 the odd numbered bits of the last 2 bursts are stolen. */ + for (unsigned int i = 0; i < 6; i++) + gsm0503_tch_burst_unmap(&iB[i * 114], &bursts[i * 116], NULL, i >> 2); + for (unsigned int i = 2; i < 4; i++) + gsm0503_tch_burst_unmap(&iB[i * 114 + 456], &bursts[i * 116], NULL, 1); + + /* 4.3.4 Interleaving */ + gsm0503_tch_fr_deinterleave(&cB[0], &iB[0]); + + /* 4.3.1-3 as specified for the SACCH in 4.1.1-3 */ + if (_xcch_decode_cB(&data[0], &cB[0], n_errors, n_bits_total) != 0) + return -1; + + return GSM_MACBLOCK_LEN; +} + /*! @} */ diff --git a/src/coding/gsm0503_interleaving.c b/src/coding/gsm0503_interleaving.c index d5008d07..570d65aa 100644 --- a/src/coding/gsm0503_interleaving.c +++ b/src/coding/gsm0503_interleaving.c @@ -16,10 +16,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include <stdint.h> @@ -682,4 +678,56 @@ void gsm0503_tch_hr_interleave(const ubit_t *cB, ubit_t *iB) } } +/* 3GPP TS 45.003 Section 3.3.4 + * The coded bits are reordered and interleaved according to the following rule: + * i(B,j) = c(n,k) for k = 0,1,...,455 + * n = 0,1,...,N,N + 1,... + * B = B0 +4n + (k mod 19) + (k div 114) + * j = (k mod 19) + 19 (k mod 6) + * + * The result of the interleaving is a distribution of the reordered 114 + * bit of a given data block, n = N, over 19 blocks, 6 bits equally + * distributed in each block, in a diagonal way over consecutive blocks. + * + * Or in other words the interleaving is a distribution of the encoded, + * reordered 456 bits from four given input data blocks, which taken + * together give n = N, over 22 bursts, 6 bits equally distributed in + * the first and 22 nd bursts, 12 bits distributed in the second and 21 + * st bursts, 18 bits distributed in the third and 20 th bursts and 24 + * bits distributed in the other 16 bursts. + * + * The block of coded data is interleaved "diagonal", where a new block + * of coded data starts with every fourth burst and is distributed over + * 22 bursts. + * + * Also used for TCH/F4.8, TCH/H4.8, and TCH/H2.4 and TCH/F14.4 */ +void gsm0503_tch_f96_interleave(const ubit_t *cB, ubit_t *iB) +{ + int j, k, B; + + for (k = 0; k < 456; k++) { + /* upper bound for B: 4*n + 18 + 4 = 4*n + 22 */ + B = /* B0 + 4n + */ (k % 19) + (k / 114); + /* upper bound for j: 18 + 19*5 = 113 */ + j = (k % 19) + 19 * (k % 6); + /* upper iB index: 4*n+23*114-1 */ + iB[B * 114 + j] = cB[k]; + } +} + +void gsm0503_tch_f96_deinterleave(sbit_t *cB, const sbit_t *iB) +{ + int j, k, B; + + for (k = 0; k < 456; k++) { + /* upper bound for B: 4*n + 18 + 4 = 4*n + 22 */ + B = /* B0 + 4n + */ (k % 19) + (k / 114); + /* upper bound for j: 18 + 19*5 = 113 */ + j = (k % 19) + 19 * (k % 6); + /* upper iB index: 4*n+23*114-1 */ + cB[k] = iB[B * 114 + j]; + } +} + + /*! @} */ diff --git a/src/coding/gsm0503_mapping.c b/src/coding/gsm0503_mapping.c index f7532eb2..04acfd07 100644 --- a/src/coding/gsm0503_mapping.c +++ b/src/coding/gsm0503_mapping.c @@ -15,10 +15,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include <stdint.h> diff --git a/src/coding/gsm0503_parity.c b/src/coding/gsm0503_parity.c index a8daacc7..ef71d351 100644 --- a/src/coding/gsm0503_parity.c +++ b/src/coding/gsm0503_parity.c @@ -15,10 +15,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include <stdint.h> diff --git a/src/coding/gsm0503_tables.c b/src/coding/gsm0503_tables.c index df0abeed..25ea2fa3 100644 --- a/src/coding/gsm0503_tables.c +++ b/src/coding/gsm0503_tables.c @@ -15,10 +15,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include <stdint.h> diff --git a/src/coding/libosmocoding.map b/src/coding/libosmocoding.map index 325b6d80..0444690e 100644 --- a/src/coding/libosmocoding.map +++ b/src/coding/libosmocoding.map @@ -76,6 +76,8 @@ gsm0503_xcch_deinterleave; gsm0503_xcch_interleave; gsm0503_tch_fr_deinterleave; gsm0503_tch_fr_interleave; +gsm0503_tch_f96_deinterleave; +gsm0503_tch_f96_interleave; gsm0503_tch_hr_deinterleave; gsm0503_tch_hr_interleave; gsm0503_mcs1_ul_deinterleave; @@ -105,6 +107,7 @@ gsm0503_tch_fr_encode; gsm0503_tch_fr_decode; gsm0503_tch_hr_encode; gsm0503_tch_hr_decode; +gsm0503_tch_hr_decode2; gsm0503_tch_afs_encode; gsm0503_tch_afs_decode; gsm0503_tch_afs_decode_dtx; @@ -123,6 +126,26 @@ gsm0503_amr_dtx_frame_names; gsm0503_amr_dtx_frame_name; gsm0503_detect_afs_dtx_frame; gsm0503_detect_ahs_dtx_frame; +gsm0503_detect_afs_dtx_frame2; +gsm0503_detect_ahs_dtx_frame2; + +gsm0503_tch_fr96_encode; +gsm0503_tch_fr96_decode; +gsm0503_tch_fr48_encode; +gsm0503_tch_fr48_decode; +gsm0503_tch_hr48_encode; +gsm0503_tch_hr48_decode; +gsm0503_tch_fr24_encode; +gsm0503_tch_fr24_decode; +gsm0503_tch_hr24_encode; +gsm0503_tch_hr24_decode; +gsm0503_tch_fr144_encode; +gsm0503_tch_fr144_decode; + +gsm0503_tch_fr_facch_encode; +gsm0503_tch_fr_facch_decode; +gsm0503_tch_hr_facch_encode; +gsm0503_tch_hr_facch_decode; local: *; }; diff --git a/src/core/Makefile.am b/src/core/Makefile.am new file mode 100644 index 00000000..2efebd8d --- /dev/null +++ b/src/core/Makefile.am @@ -0,0 +1,166 @@ +# This is _NOT_ the library release version, it's an API version. +# Please read chapter "Library interface versions" of the libtool documentation +# before making any modifications: https://www.gnu.org/software/libtool/manual/html_node/Versioning.html +LIBVERSION=21:0:0 + +AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_builddir)/include -I$(top_builddir) +AM_CFLAGS = -Wall $(TALLOC_CFLAGS) $(PTHREAD_CFLAGS) $(LIBSCTP_CFLAGS) $(LIBMNL_CFLAGS) $(URING_CFLAGS) + +if ENABLE_PSEUDOTALLOC +AM_CPPFLAGS += -I$(top_srcdir)/src/pseudotalloc +endif + +lib_LTLIBRARIES = libosmocore.la + +libosmocore_la_LIBADD = \ + $(BACKTRACE_LIB) \ + $(TALLOC_LIBS) \ + $(LIBRARY_RT) \ + $(PTHREAD_LIBS) \ + $(LIBSCTP_LIBS) \ + $(URING_LIBS) \ + $(NULL) + +libosmocore_la_SOURCES = \ + application.c \ + backtrace.c \ + base64.c \ + bits.c \ + bitvec.c \ + bitcomp.c \ + context.c \ + conv.c \ + conv_acc.c \ + conv_acc_generic.c \ + counter.c \ + crc16.c \ + crc8gen.c \ + crc16gen.c \ + crc32gen.c \ + crc64gen.c \ + exec.c \ + fsm.c \ + gsmtap_util.c \ + isdnhdlc.c \ + it_q.c \ + logging.c \ + logging_syslog.c \ + logging_gsmtap.c \ + loggingrb.c \ + macaddr.c \ + msgb.c \ + netdev.c \ + netns.c \ + osmo_io.c \ + osmo_io_poll.c \ + panic.c \ + prbs.c \ + prim.c \ + rate_ctr.c \ + rbtree.c \ + select.c \ + sercomm.c \ + signal.c \ + sockaddr_str.c \ + socket.c \ + soft_uart.c \ + stat_item.c \ + stats.c \ + stats_statsd.c \ + stats_tcp.c \ + strrb.c \ + tdef.c \ + thread.c \ + time_cc.c \ + timer.c \ + timer_gettimeofday.c \ + timer_clockgettime.c \ + tun.c \ + use_count.c \ + utils.c \ + write_queue.c \ + probes.d \ + $(NULL) + +if HAVE_SSSE3 +libosmocore_la_SOURCES += conv_acc_sse.c +if HAVE_SSE4_1 +conv_acc_sse.lo : AM_CFLAGS += -mssse3 -msse4.1 +else +conv_acc_sse.lo : AM_CFLAGS += -mssse3 +endif + +if HAVE_AVX2 +libosmocore_la_SOURCES += conv_acc_sse_avx.c +if HAVE_SSE4_1 +conv_acc_sse_avx.lo : AM_CFLAGS += -mssse3 -mavx2 -msse4.1 +else +conv_acc_sse_avx.lo : AM_CFLAGS += -mssse3 -mavx2 +endif +endif +endif + +if HAVE_NEON +libosmocore_la_SOURCES += conv_acc_neon.c +# conv_acc_neon.lo : AM_CFLAGS += -mfpu=neon no, could as well be vfp with neon +endif + +BUILT_SOURCES = crc8gen.c crc16gen.c crc32gen.c crc64gen.c + +EXTRA_DIST = \ + conv_acc_sse_impl.h \ + conv_acc_neon_impl.h \ + crcXXgen.c.tpl \ + osmo_io_internal.h \ + stat_item_internal.h \ + libosmocore.map \ + $(NULL) + +EXTRA_libosmocore_la_DEPENDENCIES = libosmocore.map + +libosmocore_la_LDFLAGS = \ + $(LTLDFLAGS_OSMOCORE) \ + -version-info \ + $(LIBVERSION) \ + -no-undefined + +if ENABLE_PLUGIN +libosmocore_la_SOURCES += plugin.c +libosmocore_la_LIBADD += $(LIBRARY_DLOPEN) +endif + +if ENABLE_MSGFILE +libosmocore_la_SOURCES += msgfile.c +endif + +if ENABLE_SERIAL +libosmocore_la_SOURCES += serial.c +endif + +if ENABLE_SYSTEMD_LOGGING +libosmocore_la_SOURCES += logging_systemd.c +libosmocore_la_LIBADD += $(SYSTEMD_LIBS) +endif + +if ENABLE_LIBMNL +libosmocore_la_SOURCES += mnl.c +libosmocore_la_LIBADD += $(LIBMNL_LIBS) +endif + +if ENABLE_SYSTEMTAP +probes.h: probes.d + $(DTRACE) -C -h -s $< -o $@ + +probes.lo: probes.d + $(LIBTOOL) --mode=compile $(AM_V_lt) --tag=CC env CFLAGS="$(CFLAGS)" $(DTRACE) -C -G -s $< -o $@ + +BUILT_SOURCES += probes.h probes.lo +libosmocore_la_LIBADD += probes.lo +endif + +if ENABLE_URING +libosmocore_la_SOURCES += osmo_io_uring.c +endif + +crc%gen.c: crcXXgen.c.tpl + $(AM_V_GEN)sed -e's/XX/$*/g' $< > $@ diff --git a/src/application.c b/src/core/application.c index 7fd62808..f7e5816f 100644 --- a/src/application.c +++ b/src/core/application.c @@ -18,10 +18,6 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ /*! \mainpage libosmocore Documentation diff --git a/src/backtrace.c b/src/core/backtrace.c index a18bde02..60bd2381 100644 --- a/src/backtrace.c +++ b/src/core/backtrace.c @@ -18,10 +18,6 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ #include <stdio.h> diff --git a/src/core/base64.c b/src/core/base64.c new file mode 100644 index 00000000..0c161ceb --- /dev/null +++ b/src/core/base64.c @@ -0,0 +1,195 @@ +/* + * RFC 1521 base64 encoding/decoding + * + * Copyright (C) 2006-2015, ARM Limited, All Rights Reserved + * + * This file is part of mbed TLS (https://tls.mbed.org) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <osmocom/core/base64.h> + +#include <stdint.h> +#include <stdio.h> +#include <errno.h> + +static const unsigned char base64_enc_map[64] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', + 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', + 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', + 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', + 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', '+', '/' +}; + +static const unsigned char base64_dec_map[128] = { + 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 127, 127, 127, 127, 127, 127, 127, + 127, 127, 127, 62, 127, 127, 127, 63, 52, 53, + 54, 55, 56, 57, 58, 59, 60, 61, 127, 127, + 127, 64, 127, 127, 127, 0, 1, 2, 3, 4, + 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 127, 127, 127, 127, 127, 127, 26, 27, 28, + 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, + 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, + 49, 50, 51, 127, 127, 127, 127, 127 +}; + +/* + * Encode a buffer into base64 format + */ +int osmo_base64_encode(unsigned char *dst, size_t dlen, size_t *olen, + const unsigned char *src, size_t slen) +{ + size_t i, n; + int C1, C2, C3; + unsigned char *p; + + if (slen == 0) { + *olen = 0; + return 0; + } + + n = (slen << 3) / 6; + + switch ((slen << 3) - (n * 6)) { + case 2: + n += 3; + break; + case 4: + n += 2; + break; + default: + break; + } + + if (dlen < n + 1) { + *olen = n + 1; + return -ENOBUFS; + } + + n = (slen / 3) * 3; + + for (i = 0, p = dst; i < n; i += 3) { + C1 = *src++; + C2 = *src++; + C3 = *src++; + + *p++ = base64_enc_map[(C1 >> 2) & 0x3F]; + *p++ = base64_enc_map[(((C1 & 3) << 4) + (C2 >> 4)) & 0x3F]; + *p++ = base64_enc_map[(((C2 & 15) << 2) + (C3 >> 6)) & 0x3F]; + *p++ = base64_enc_map[C3 & 0x3F]; + } + + if (i < slen) { + C1 = *src++; + C2 = ((i + 1) < slen) ? *src++ : 0; + + *p++ = base64_enc_map[(C1 >> 2) & 0x3F]; + *p++ = base64_enc_map[(((C1 & 3) << 4) + (C2 >> 4)) & 0x3F]; + + if ((i + 1) < slen) + *p++ = base64_enc_map[((C2 & 15) << 2) & 0x3F]; + else + *p++ = '='; + + *p++ = '='; + } + + *olen = p - dst; + *p = 0; + + return 0; +} + +/* + * Decode a base64-formatted buffer + */ +int osmo_base64_decode(unsigned char *dst, size_t dlen, size_t *olen, + const unsigned char *src, size_t slen) +{ + size_t i, n; + uint32_t j, x; + unsigned char *p; + + /* First pass: check for validity and get output length */ + for (i = n = j = 0; i < slen; i++) { + /* Skip spaces before checking for EOL */ + x = 0; + while (i < slen && src[i] == ' ') { + ++i; + ++x; + } + + /* Spaces at end of buffer are OK */ + if (i == slen) + break; + + if ((slen - i) >= 2 && src[i] == '\r' && src[i + 1] == '\n') + continue; + + if (src[i] == '\n') + continue; + + /* Space inside a line is an error */ + if (x != 0) + return -EINVAL; + + if (src[i] == '=' && ++j > 2) + return -EINVAL; + + if (src[i] > 127 || base64_dec_map[src[i]] == 127) + return -EINVAL; + + if (base64_dec_map[src[i]] < 64 && j != 0) + return -EINVAL; + + n++; + } + + if (n == 0) + return 0; + + n = ((n * 6) + 7) >> 3; + n -= j; + + if (dst == NULL || dlen < n) { + *olen = n; + return -ENOBUFS; + } + + for (j = 3, n = x = 0, p = dst; i > 0; i--, src++) { + if (*src == '\r' || *src == '\n' || *src == ' ') + continue; + + j -= (base64_dec_map[*src] == 64); + x = (x << 6) | (base64_dec_map[*src] & 0x3F); + + if (++n == 4) { + n = 0; + if (j > 0) + *p++ = (unsigned char)(x >> 16); + if (j > 1) + *p++ = (unsigned char)(x >> 8); + if (j > 2) + *p++ = (unsigned char)(x); + } + } + + *olen = p - dst; + + return 0; +} diff --git a/src/bitcomp.c b/src/core/bitcomp.c index 6f2fb62b..5fb2cba2 100644 --- a/src/bitcomp.c +++ b/src/core/bitcomp.c @@ -18,10 +18,6 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ /*! \defgroup bitcomp Bit compression diff --git a/src/bits.c b/src/core/bits.c index aa117531..3da7d9b9 100644 --- a/src/bits.c +++ b/src/core/bits.c @@ -16,10 +16,6 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ #include <stdint.h> @@ -185,14 +181,14 @@ int osmo_pbit2ubit(ubit_t *out, const pbit_t *in, unsigned int num_bits) * \param[in] in input buffer of unpacked bits * \param[in] in_ofs offset into input buffer * \param[in] num_bits number of bits - * \param[in] lsb_mode Encode bits in LSB orde instead of MSB + * \param[in] lsb_mode Encode bits in LSB order instead of MSB * \returns length in bytes (max written offset of output buffer + 1) */ int osmo_ubit2pbit_ext(pbit_t *out, unsigned int out_ofs, const ubit_t *in, unsigned int in_ofs, unsigned int num_bits, int lsb_mode) { - int i, op, bn; + unsigned int i, op, bn; for (i=0; i<num_bits; i++) { op = out_ofs + i; bn = lsb_mode ? (op&7) : (7-(op&7)); @@ -210,14 +206,14 @@ int osmo_ubit2pbit_ext(pbit_t *out, unsigned int out_ofs, * \param[in] in input buffer of packed bits * \param[in] in_ofs offset into input buffer * \param[in] num_bits number of bits - * \param[in] lsb_mode Encode bits in LSB orde instead of MSB + * \param[in] lsb_mode Encode bits in LSB order instead of MSB * \returns length in bytes (max written offset of output buffer + 1) */ int osmo_pbit2ubit_ext(ubit_t *out, unsigned int out_ofs, const pbit_t *in, unsigned int in_ofs, unsigned int num_bits, int lsb_mode) { - int i, ip, bn; + unsigned int i, ip, bn; for (i=0; i<num_bits; i++) { ip = in_ofs + i; bn = lsb_mode ? (ip&7) : (7-(ip&7)); @@ -308,7 +304,7 @@ uint32_t osmo_revbytebits_8(uint8_t x) */ void osmo_revbytebits_buf(uint8_t *buf, int len) { - unsigned int i; + int i; for (i = 0; i < len; i++) buf[i] = flip_table[buf[i]]; diff --git a/src/bitvec.c b/src/core/bitvec.c index 2b4e8c98..ac702b94 100644 --- a/src/bitvec.c +++ b/src/core/bitvec.c @@ -16,10 +16,6 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ /*! \addtogroup bitvec @@ -201,7 +197,8 @@ int bitvec_get_bit_high(struct bitvec *bv) * \return 0 on success; negative in case of error */ int bitvec_set_bits(struct bitvec *bv, const enum bit_value *bits, unsigned int count) { - int i, rc; + unsigned int i; + int rc; for (i = 0; i < count; i++) { rc = bitvec_set_bit(bv, bits[i]); @@ -264,7 +261,7 @@ int16_t bitvec_get_int16_msb(const struct bitvec *bv, unsigned int num_bits) * \return integer value retrieved from bit vector */ int bitvec_get_uint(struct bitvec *bv, unsigned int num_bits) { - int i; + unsigned int i; unsigned int ui = 0; for (i = 0; i < num_bits; i++) { @@ -398,7 +395,7 @@ int bitvec_set_bytes(struct bitvec *bv, const uint8_t *bytes, unsigned int count * \param[in] size Number of bytes in the vector * \param[in] ctx Context from which to allocate * \return pointer to allocated vector; NULL in case of error */ -struct bitvec *bitvec_alloc(unsigned int size, TALLOC_CTX *ctx) +struct bitvec *bitvec_alloc(unsigned int size, void *ctx) { struct bitvec *bv = talloc(ctx, struct bitvec); if (!bv) @@ -472,19 +469,29 @@ int bitvec_unhex(struct bitvec *bv, const char *src) * \param[in] bv The boolean vector to work on * \param[in,out] read_index Where reading supposed to start in the vector * \param[in] len How many bits to read from vector - * \returns read bits or negative value on error + * \returns An integer made up of the bits read. + * + * In case of an error, errno is set to a non-zero value. Otherwise it holds 0. */ uint64_t bitvec_read_field(struct bitvec *bv, unsigned int *read_index, unsigned int len) { unsigned int i; uint64_t ui = 0; + + /* Prevent bitvec overrun due to incorrect index and/or length */ + if (len && bytenum_from_bitnum(*read_index + len - 1) >= bv->data_len) { + errno = EOVERFLOW; + return 0; + } + bv->cur_bit = *read_index; + errno = 0; for (i = 0; i < len; i++) { - int bit = bitvec_get_bit_pos((const struct bitvec *)bv, bv->cur_bit); - if (bit < 0) - return bit; - if (bit) + unsigned int bytenum = bytenum_from_bitnum(bv->cur_bit); + unsigned int bitnum = 7 - (bv->cur_bit % 8); + + if (bv->data[bytenum] & (1 << bitnum)) ui |= ((uint64_t)1 << (len - i - 1)); bv->cur_bit++; } @@ -533,13 +540,11 @@ char bit_value_to_char(enum bit_value v) */ void bitvec_to_string_r(const struct bitvec *bv, char *str) { - unsigned i, pos = 0; char *cur = str; - for (i = 0; i < bv->cur_bit; i++) { + for (unsigned int i = 0; i < bv->cur_bit; i++) { if (0 == i % 8) *cur++ = ' '; *cur++ = bit_value_to_char(bitvec_get_bit_pos(bv, i)); - pos++; } *cur = 0; } @@ -597,7 +602,7 @@ unsigned bitvec_rl(const struct bitvec *bv, bool b) * \returns Number of consecutive bits of \p b in \p bv and cur_bit will * \go to cur_bit + number of consecutive bit */ -unsigned bitvec_rl_curbit(struct bitvec *bv, bool b, int max_bits) +unsigned bitvec_rl_curbit(struct bitvec *bv, bool b, unsigned int max_bits) { unsigned i = 0; unsigned j = 8; diff --git a/src/context.c b/src/core/context.c index bad012bd..a0b3a555 100644 --- a/src/context.c +++ b/src/core/context.c @@ -2,7 +2,7 @@ * talloc context handling. * * (C) 2019 by Harald Welte <laforge@gnumonks.org> - * All Rights Reserverd. + * All Rights Reserved. * * SPDX-License-Identifier: GPL-2.0+ * @@ -15,11 +15,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, - * MA 02110-1301, USA. */ #include <string.h> #include <errno.h> @@ -44,7 +39,7 @@ int osmo_ctx_init(const char *id) } /* initialize osmo_ctx on main tread */ -static __attribute__((constructor)) void on_dso_load_ctx(void) +static __attribute__((constructor(101))) void on_dso_load_ctx(void) { OSMO_ASSERT(osmo_ctx_init("main") == 0); } diff --git a/src/conv.c b/src/core/conv.c index 06c4299b..8963018b 100644 --- a/src/conv.c +++ b/src/core/conv.c @@ -16,10 +16,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ /*! \addtogroup conv @@ -67,7 +63,7 @@ osmo_conv_get_output_length(const struct osmo_conv_code *code, int len) /* Count punctured bits */ if (code->puncture) { - for (pbits=0; code->puncture[pbits] >= 0; pbits++); + for (pbits = 0; code->puncture[pbits] >= 0; pbits++) {} out_len -= pbits; } @@ -85,7 +81,7 @@ osmo_conv_get_output_length(const struct osmo_conv_code *code, int len) */ void osmo_conv_encode_init(struct osmo_conv_encoder *encoder, - const struct osmo_conv_code *code) + const struct osmo_conv_code *code) { memset(encoder, 0x00, sizeof(struct osmo_conv_encoder)); OSMO_ASSERT(code != NULL); @@ -94,12 +90,12 @@ osmo_conv_encode_init(struct osmo_conv_encoder *encoder, void osmo_conv_encode_load_state(struct osmo_conv_encoder *encoder, - const ubit_t *input) + const ubit_t *input) { int i; uint8_t state = 0; - for (i=0; i<(encoder->code->K-1); i++) + for (i = 0; i < (encoder->code->K - 1); i++) state = (state << 1) | input[i]; encoder->state = state; @@ -107,15 +103,14 @@ osmo_conv_encode_load_state(struct osmo_conv_encoder *encoder, static inline int _conv_encode_do_output(struct osmo_conv_encoder *encoder, - uint8_t out, ubit_t *output) + uint8_t out, ubit_t *output) { const struct osmo_conv_code *code = encoder->code; int o_idx = 0; int j; if (code->puncture) { - for (j=0; j<code->N; j++) - { + for (j = 0; j < code->N; j++) { int bit_no = code->N - j - 1; int r_idx = encoder->i_idx * code->N + j; @@ -125,8 +120,7 @@ _conv_encode_do_output(struct osmo_conv_encoder *encoder, output[o_idx++] = (out >> bit_no) & 1; } } else { - for (j=0; j<code->N; j++) - { + for (j = 0; j < code->N; j++) { int bit_no = code->N - j - 1; output[o_idx++] = (out >> bit_no) & 1; } @@ -137,7 +131,7 @@ _conv_encode_do_output(struct osmo_conv_encoder *encoder, int osmo_conv_encode_raw(struct osmo_conv_encoder *encoder, - const ubit_t *input, ubit_t *output, int n) + const ubit_t *input, ubit_t *output, int n) { const struct osmo_conv_code *code = encoder->code; uint8_t state; @@ -147,11 +141,11 @@ osmo_conv_encode_raw(struct osmo_conv_encoder *encoder, o_idx = 0; state = encoder->state; - for (i=0; i<n; i++) { + for (i = 0; i < n; i++) { int bit = input[i]; uint8_t out; - out = code->next_output[state][bit]; + out = code->next_output[state][bit]; state = code->next_state[state][bit]; o_idx += _conv_encode_do_output(encoder, out, &output[o_idx]); @@ -165,8 +159,7 @@ osmo_conv_encode_raw(struct osmo_conv_encoder *encoder, } int -osmo_conv_encode_flush(struct osmo_conv_encoder *encoder, - ubit_t *output) +osmo_conv_encode_flush(struct osmo_conv_encoder *encoder, ubit_t *output) { const struct osmo_conv_code *code = encoder->code; uint8_t state; @@ -179,14 +172,14 @@ osmo_conv_encode_flush(struct osmo_conv_encoder *encoder, o_idx = 0; state = encoder->state; - for (i=0; i<n; i++) { + for (i = 0; i < n; i++) { uint8_t out; if (code->next_term_output) { - out = code->next_term_output[state]; + out = code->next_term_output[state]; state = code->next_term_state[state]; } else { - out = code->next_output[state][0]; + out = code->next_output[state][0]; state = code->next_state[state][0]; } @@ -212,7 +205,7 @@ osmo_conv_encode_flush(struct osmo_conv_encoder *encoder, */ int osmo_conv_encode(const struct osmo_conv_code *code, - const ubit_t *input, ubit_t *output) + const ubit_t *input, ubit_t *output) { struct osmo_conv_encoder encoder; int l; @@ -242,17 +235,18 @@ osmo_conv_encode(const struct osmo_conv_code *code, /* Forward declaration for accerlated decoding with certain codes */ int osmo_conv_decode_acc(const struct osmo_conv_code *code, - const sbit_t *input, ubit_t *output); + const sbit_t *input, ubit_t *output); void osmo_conv_decode_init(struct osmo_conv_decoder *decoder, - const struct osmo_conv_code *code, int len, int start_state) + const struct osmo_conv_code *code, int len, + int start_state) { int n_states; /* Init */ if (len <= 0) - len = code->len; + len = code->len; n_states = 1 << (code->K - 1); @@ -263,7 +257,7 @@ osmo_conv_decode_init(struct osmo_conv_decoder *decoder, decoder->len = len; /* Allocate arrays */ - decoder->ae = malloc(sizeof(unsigned int) * n_states); + decoder->ae = malloc(sizeof(unsigned int) * n_states); decoder->ae_next = malloc(sizeof(unsigned int) * n_states); decoder->state_history = malloc(sizeof(uint8_t) * n_states * (len + decoder->code->K - 1)); @@ -287,7 +281,7 @@ osmo_conv_decode_reset(struct osmo_conv_decoder *decoder, int start_state) memset(decoder->ae, 0x00, sizeof(unsigned int) * decoder->n_states); } else { /* Fixed start state */ - for (i=0; i<decoder->n_states; i++) { + for (i = 0; i < decoder->n_states; i++) { decoder->ae[i] = (i == start_state) ? 0 : MAX_AE; } } @@ -304,12 +298,12 @@ osmo_conv_decode_rewind(struct osmo_conv_decoder *decoder) decoder->p_idx = 0; /* Initial error normalize (remove constant) */ - for (i=0; i<decoder->n_states; i++) { + for (i = 0; i < decoder->n_states; i++) { if (decoder->ae[i] < min_ae) min_ae = decoder->ae[i]; } - for (i=0; i<decoder->n_states; i++) + for (i = 0; i < decoder->n_states; i++) decoder->ae[i] -= min_ae; } @@ -325,7 +319,7 @@ osmo_conv_decode_deinit(struct osmo_conv_decoder *decoder) int osmo_conv_decode_scan(struct osmo_conv_decoder *decoder, - const sbit_t *input, int n) + const sbit_t *input, int n) { const struct osmo_conv_code *code = decoder->code; @@ -342,27 +336,25 @@ osmo_conv_decode_scan(struct osmo_conv_decoder *decoder, /* Prepare */ n_states = decoder->n_states; - ae = decoder->ae; + ae = decoder->ae; ae_next = decoder->ae_next; state_history = &decoder->state_history[n_states * decoder->o_idx]; - in_sym = alloca(sizeof(sbit_t) * code->N); + in_sym = alloca(sizeof(sbit_t) * code->N); i_idx = 0; p_idx = decoder->p_idx; /* Scan the treillis */ - for (i=0; i<n; i++) - { + for (i = 0; i < n; i++) { /* Reset next accumulated error */ - for (s=0; s<n_states; s++) { + for (s = 0; s < n_states; s++) ae_next[s] = MAX_AE; - } /* Get input */ if (code->puncture) { /* Hard way ... */ - for (j=0; j<code->N; j++) { + for (j = 0; j < code->N; j++) { int idx = ((decoder->o_idx + i) * code->N) + j; if (idx == code->puncture[p_idx]) { in_sym[j] = 0; /* Undefined */ @@ -379,23 +371,21 @@ osmo_conv_decode_scan(struct osmo_conv_decoder *decoder, } /* Scan all state */ - for (s=0; s<n_states; s++) - { + for (s = 0; s < n_states; s++) { /* Scan possible input bits */ - for (b=0; b<2; b++) - { + for (b = 0; b < 2; b++) { int nae, ov, e; uint8_t m; /* Next output and state */ - uint8_t out = code->next_output[s][b]; + uint8_t out = code->next_output[s][b]; uint8_t state = code->next_state[s][b]; /* New error for this path */ nae = ae[s]; /* start from last error */ m = 1 << (code->N - 1); /* mask for 'out' bit selection */ - for (j=0; j<code->N; j++) { + for (j = 0; j < code->N; j++) { int is = (int)in_sym[j]; if (is) { ov = (out & m) ? -127 : 127; /* sbit_t value for it */ @@ -425,8 +415,7 @@ osmo_conv_decode_scan(struct osmo_conv_decoder *decoder, } int -osmo_conv_decode_flush(struct osmo_conv_decoder *decoder, - const sbit_t *input) +osmo_conv_decode_flush(struct osmo_conv_decoder *decoder, const sbit_t *input) { const struct osmo_conv_code *code = decoder->code; @@ -443,27 +432,25 @@ osmo_conv_decode_flush(struct osmo_conv_decoder *decoder, /* Prepare */ n_states = decoder->n_states; - ae = decoder->ae; + ae = decoder->ae; ae_next = decoder->ae_next; state_history = &decoder->state_history[n_states * decoder->o_idx]; - in_sym = alloca(sizeof(sbit_t) * code->N); + in_sym = alloca(sizeof(sbit_t) * code->N); i_idx = 0; p_idx = decoder->p_idx; /* Scan the treillis */ - for (i=0; i<code->K-1; i++) - { + for (i = 0; i < code->K - 1; i++) { /* Reset next accumulated error */ - for (s=0; s<n_states; s++) { + for (s = 0; s < n_states; s++) ae_next[s] = MAX_AE; - } /* Get input */ if (code->puncture) { /* Hard way ... */ - for (j=0; j<code->N; j++) { + for (j = 0; j < code->N; j++) { int idx = ((decoder->o_idx + i) * code->N) + j; if (idx == code->puncture[p_idx]) { in_sym[j] = 0; /* Undefined */ @@ -480,8 +467,7 @@ osmo_conv_decode_flush(struct osmo_conv_decoder *decoder, } /* Scan all state */ - for (s=0; s<n_states; s++) - { + for (s = 0; s < n_states; s++) { int nae, ov, e; uint8_t m; @@ -490,10 +476,10 @@ osmo_conv_decode_flush(struct osmo_conv_decoder *decoder, uint8_t state; if (code->next_term_output) { - out = code->next_term_output[s]; + out = code->next_term_output[s]; state = code->next_term_state[s]; } else { - out = code->next_output[s][0]; + out = code->next_output[s][0]; state = code->next_state[s][0]; } @@ -501,7 +487,7 @@ osmo_conv_decode_flush(struct osmo_conv_decoder *decoder, nae = ae[s]; /* start from last error */ m = 1 << (code->N - 1); /* mask for 'out' bit selection */ - for (j=0; j<code->N; j++) { + for (j = 0; j < code->N; j++) { int is = (int)in_sym[j]; if (is) { ov = (out & m) ? -127 : 127; /* sbit_t value for it */ @@ -530,38 +516,86 @@ osmo_conv_decode_flush(struct osmo_conv_decoder *decoder, } int -osmo_conv_decode_get_output(struct osmo_conv_decoder *decoder, - ubit_t *output, int has_flush, int end_state) +osmo_conv_decode_get_best_end_state(struct osmo_conv_decoder *decoder) { const struct osmo_conv_code *code = decoder->code; - int min_ae; - uint8_t min_state, cur_state; - int i, s, n; + int min_ae, min_state; + int s; - uint8_t *sh_ptr; + /* If flushed, we _know_ the end state */ + if (code->term == CONV_TERM_FLUSH) + return 0; - /* End state ? */ - if (end_state < 0) { - /* Find state with least error */ - min_ae = MAX_AE; - min_state = 0xff; + /* Search init */ + min_state = -1; + min_ae = MAX_AE; - for (s=0; s<decoder->n_states; s++) - { + /* If tail biting, we search for the minimum path metric that + * gives a circular traceback (i.e. start_state == end_state */ + if (code->term == CONV_TERM_TAIL_BITING) { + int t, n, i; + uint8_t *sh_ptr; + + for (s = 0; s < decoder->n_states; s++) { + /* Check if that state traces back to itself */ + n = decoder->o_idx; + sh_ptr = &decoder->state_history[decoder->n_states * (n-1)]; + t = s; + + for (i = n - 1; i >= 0; i--) { + t = sh_ptr[t]; + sh_ptr -= decoder->n_states; + } + + if (s != t) + continue; + + /* If it does, consider it */ if (decoder->ae[s] < min_ae) { min_ae = decoder->ae[s]; min_state = s; } } - if (min_state == 0xff) - return -1; - } else { - min_state = (uint8_t) end_state; - min_ae = decoder->ae[end_state]; + if (min_ae < MAX_AE) + return min_state; + } + + /* Finally, just the lowest path metric */ + for (s = 0; s < decoder->n_states; s++) { + /* Is it smaller ? */ + if (decoder->ae[s] < min_ae) { + min_ae = decoder->ae[s]; + min_state = s; + } } + return min_state; +} + +int +osmo_conv_decode_get_output(struct osmo_conv_decoder *decoder, + ubit_t *output, int has_flush, int end_state) +{ + const struct osmo_conv_code *code = decoder->code; + + int min_ae; + uint8_t min_state, cur_state; + int i, n; + + uint8_t *sh_ptr; + + /* End state ? */ + if (end_state < 0) + end_state = osmo_conv_decode_get_best_end_state(decoder); + + if (end_state < 0) + return -1; + + min_state = (uint8_t) end_state; + min_ae = decoder->ae[end_state]; + /* Traceback */ cur_state = min_state; @@ -571,16 +605,15 @@ osmo_conv_decode_get_output(struct osmo_conv_decoder *decoder, /* No output for the K-1 termination input bits */ if (has_flush) { - for (i=0; i<code->K-1; i++) { + for (i = 0; i < code->K - 1; i++) { cur_state = sh_ptr[cur_state]; sh_ptr -= decoder->n_states; } n -= code->K - 1; } - /* Generate output backward */ - for (i=n-1; i>=0; i--) - { + /* Generate output backward */ + for (i = n - 1; i >= 0; i--) { min_state = cur_state; cur_state = sh_ptr[cur_state]; @@ -602,12 +635,12 @@ osmo_conv_decode_get_output(struct osmo_conv_decoder *decoder, * * This is an all-in-one function, taking care of * \ref osmo_conv_decode_init, \ref osmo_conv_decode_scan, - * \ref osmo_conv_decode_flush, \ref osmo_conv_decode_get_output and - * \ref osmo_conv_decode_deinit. + * \ref osmo_conv_decode_flush, \ref osmo_conv_decode_get_best_end_state, + * \ref osmo_conv_decode_get_output and \ref osmo_conv_decode_deinit. */ int osmo_conv_decode(const struct osmo_conv_code *code, - const sbit_t *input, ubit_t *output) + const sbit_t *input, ubit_t *output) { struct osmo_conv_decoder decoder; int rv, l; @@ -630,7 +663,7 @@ osmo_conv_decode(const struct osmo_conv_code *code, rv = osmo_conv_decode_get_output(&decoder, output, code->term == CONV_TERM_FLUSH, /* has_flush */ - code->term == CONV_TERM_FLUSH ? 0 : -1 /* end_state */ + -1 /* end_state */ ); osmo_conv_decode_deinit(&decoder); diff --git a/src/conv_acc.c b/src/core/conv_acc.c index 0f6f7ca2..4bd3b076 100644 --- a/src/conv_acc.c +++ b/src/core/conv_acc.c @@ -16,10 +16,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include <stdlib.h> @@ -487,10 +483,27 @@ static void _traceback_rec(struct vdecoder *dec, */ static int traceback(struct vdecoder *dec, uint8_t *out, int term, int len) { - int i, sum, max = -1; - unsigned path, state = 0; + int i, j, sum, max = -1; + unsigned path, state = 0, state_scan; + + if (term == CONV_TERM_TAIL_BITING) { + for (i = 0; i < dec->trellis.num_states; i++) { + state_scan = i; + for (j = len - 1; j >= 0; j--) { + path = dec->paths[j][state_scan] + 1; + state_scan = vstate_lshift(state_scan, dec->k, path); + } + if (state_scan != i) + continue; + sum = dec->trellis.sums[i]; + if (sum > max) { + max = sum; + state = i; + } + } + } - if (term != CONV_TERM_FLUSH) { + if ((max < 0) && (term != CONV_TERM_FLUSH)) { for (i = 0; i < dec->trellis.num_states; i++) { sum = dec->trellis.sums[i]; if (sum > max) { diff --git a/src/conv_acc_generic.c b/src/core/conv_acc_generic.c index 28876738..2257e6a9 100644 --- a/src/conv_acc_generic.c +++ b/src/core/conv_acc_generic.c @@ -17,10 +17,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include <stdlib.h> diff --git a/src/conv_acc_neon.c b/src/core/conv_acc_neon.c index 72449468..fb180e3d 100644 --- a/src/conv_acc_neon.c +++ b/src/core/conv_acc_neon.c @@ -18,10 +18,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include <stdlib.h> diff --git a/src/conv_acc_neon_impl.h b/src/core/conv_acc_neon_impl.h index 4471127e..8a78c75b 100644 --- a/src/conv_acc_neon_impl.h +++ b/src/core/conv_acc_neon_impl.h @@ -18,10 +18,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ /* Some distributions (notably Alpine Linux) for some strange reason diff --git a/src/conv_acc_sse.c b/src/core/conv_acc_sse.c index 63d8722a..513ab052 100644 --- a/src/conv_acc_sse.c +++ b/src/core/conv_acc_sse.c @@ -17,10 +17,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include <stdint.h> diff --git a/src/conv_acc_sse_avx.c b/src/core/conv_acc_sse_avx.c index 5ac3c163..82b4fa62 100644 --- a/src/conv_acc_sse_avx.c +++ b/src/core/conv_acc_sse_avx.c @@ -17,10 +17,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include <stdint.h> diff --git a/src/conv_acc_sse_impl.h b/src/core/conv_acc_sse_impl.h index 9ebbfe9c..807dbe5e 100644 --- a/src/conv_acc_sse_impl.h +++ b/src/core/conv_acc_sse_impl.h @@ -18,10 +18,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ /* Some distributions (notably Alpine Linux) for some strange reason diff --git a/src/counter.c b/src/core/counter.c index 0fa31661..dace15f3 100644 --- a/src/counter.c +++ b/src/core/counter.c @@ -17,10 +17,6 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ #include <string.h> @@ -79,7 +75,7 @@ int osmo_counters_for_each(int (*handle_counter)(struct osmo_counter *, void *), /*! Counts the registered counter * \returns amount of counters */ -int osmo_counters_count() +int osmo_counters_count(void) { return llist_count(&counters); } diff --git a/src/crc16.c b/src/core/crc16.c index 29dace2e..29dace2e 100644 --- a/src/crc16.c +++ b/src/core/crc16.c diff --git a/src/crcXXgen.c.tpl b/src/core/crcXXgen.c.tpl index 74e6d521..154291cc 100644 --- a/src/crcXXgen.c.tpl +++ b/src/core/crcXXgen.c.tpl @@ -16,10 +16,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ /*! \addtogroup crc diff --git a/src/exec.c b/src/core/exec.c index 2a03ba80..2e33788e 100644 --- a/src/exec.c +++ b/src/core/exec.c @@ -14,10 +14,6 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ #include "config.h" @@ -78,7 +74,7 @@ static bool str_in_list(const char **list, const char *key) * * Constraints: Keys up to a maximum length of 255 characters are supported. * - * \oaram[out] out caller-allocated array of pointers for the generated output + * \param[out] out caller-allocated array of pointers for the generated output * \param[in] out_len size of out (number of pointers) * \param[in] in input environment (NULL-terminated list of pointers like **environ) * \param[in] whitelist whitelist of permitted keys in environment (like **environ) @@ -135,7 +131,7 @@ int osmo_environment_filter(char **out, size_t out_len, char **in, const char ** * Constraints: If the same key exists in 'out' and 'in', duplicate keys are * generated. It is a simple append, without any duplicate checks. * - * \oaram[out] out caller-allocated array of pointers for the generated output + * \param[out] out caller-allocated array of pointers for the generated output * \param[in] out_len size of out (number of pointers) * \param[in] in input environment (NULL-terminated list of pointers like **environ) * \returns number of entries filled in 'out'; negative on error */ @@ -217,10 +213,18 @@ int osmo_system_nowait2(const char *command, const char **env_whitelist, char ** int rc; if (user) { + if (getpw_buflen == -1) /* Value was indeterminate */ + getpw_buflen = 16384; /* Should be more than enough */ char buf[getpw_buflen]; - getpwnam_r(user, &_pw, buf, sizeof(buf), &pw); - if (!pw) + rc = getpwnam_r(user, &_pw, buf, sizeof(buf), &pw); + if (rc < 0) { + LOGP(DLGLOBAL, LOGL_ERROR, "getpwnam_r(\"%s\") failed: %s\n", user, strerror(-rc)); + return rc; + } + if (!pw) { + LOGP(DLGLOBAL, LOGL_ERROR, "getpwnam_r(\"%s\"): user not found!\n", user); return -EINVAL; + } } rc = fork(); @@ -268,6 +272,9 @@ int osmo_system_nowait2(const char *command, const char **env_whitelist, char ** return -EIO; } else { /* we are in the parent */ + if (rc == -1) + LOGP(DLGLOBAL, LOGL_ERROR, "fork() error executing command '%s': %s\n", + command, strerror(errno)); return rc; } } diff --git a/src/fsm.c b/src/core/fsm.c index 8f7ff1a4..9333cac5 100644 --- a/src/fsm.c +++ b/src/core/fsm.c @@ -14,11 +14,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, - * MA 02110-1301, USA. */ #include <errno.h> @@ -581,7 +576,7 @@ void osmo_fsm_inst_free(struct osmo_fsm_inst *fi) * \param[in] event Event integer value * \returns string rendering of the event */ -const char *osmo_fsm_event_name(struct osmo_fsm *fsm, uint32_t event) +const char *osmo_fsm_event_name(const struct osmo_fsm *fsm, uint32_t event) { static __thread char buf[32]; if (!fsm->event_names) { @@ -595,7 +590,7 @@ const char *osmo_fsm_event_name(struct osmo_fsm *fsm, uint32_t event) * \param[in] fi FSM instance * \returns string rendering of the FSM identity */ -const char *osmo_fsm_inst_name(struct osmo_fsm_inst *fi) +const char *osmo_fsm_inst_name(const struct osmo_fsm_inst *fi) { if (!fi) return "NULL"; @@ -611,7 +606,7 @@ const char *osmo_fsm_inst_name(struct osmo_fsm_inst *fi) * \param[in] state FSM state number * \returns string rendering of the FSM state */ -const char *osmo_fsm_state_name(struct osmo_fsm *fsm, uint32_t state) +const char *osmo_fsm_state_name(const struct osmo_fsm *fsm, uint32_t state) { static __thread char buf[32]; if (state >= fsm->num_states) { @@ -690,8 +685,11 @@ static int state_chg(struct osmo_fsm_inst *fi, uint32_t new_state, if (!keep_timer || (keep_timer && !osmo_timer_pending(&fi->timer))) { fi->T = T; - if (timeout_ms) - osmo_timer_schedule(&fi->timer, timeout_ms / 1000, timeout_ms % 1000); + if (timeout_ms) { + osmo_timer_schedule(&fi->timer, + /* seconds */ (timeout_ms / 1000), + /* microseconds */ (timeout_ms % 1000) * 1000); + } } /* Call 'onenter' last, user might terminate FSM from there */ diff --git a/src/gsmtap_util.c b/src/core/gsmtap_util.c index 9ae06d64..b64c7b05 100644 --- a/src/gsmtap_util.c +++ b/src/core/gsmtap_util.c @@ -17,13 +17,9 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ -#include "../config.h" +#include "config.h" #include <osmocom/core/gsmtap_util.h> #include <osmocom/core/logging.h> @@ -33,6 +29,7 @@ #include <osmocom/core/select.h> #include <osmocom/core/socket.h> #include <osmocom/core/byteswap.h> +#include <osmocom/core/utils.h> #include <osmocom/gsm/protocol/gsm_04_08.h> #include <osmocom/gsm/rsl.h> @@ -50,6 +47,43 @@ * * \file gsmtap_util.c */ +/*! one gsmtap instance + * Until gsmtap_inst_fd() is removed from the API at some point in the future, we have to keep the first member as + * 'int' and the second as 'struct osmo_wqueue' (this effectively makes sure that the struct member wq.bfd.fd maintains + * the same memory offset from the start of the struct) to ensure that inlined static 'instances' of gsmtap_inst_fd() in + * old binaries keep working the way they used to even with gsmtap_inst objects obtained from newer versions of libosmocore */ +struct gsmtap_inst { + int osmo_io_mode; /*!< Indicates whether or not to use Osmo IO mode for message output (thus enabling use of tx queues). + * This field member may not be changed or moved (backwards compatibility) */ + struct osmo_wqueue wq; /*!< the wait queue. This field member may not be changed or moved (backwards compatibility) */ + + struct osmo_io_fd *out; /*!< Used when osmo_io_mode is nonzero */ + int sink_fd; +}; + +struct _gsmtap_inst_legacy { + int ofd_wq_mode; + struct osmo_wqueue wq; + struct osmo_fd sink_ofd; +}; +osmo_static_assert(offsetof(struct gsmtap_inst, wq) == offsetof(struct _gsmtap_inst_legacy, wq), + gsmtap_inst_new_wq_offset_equals_legacy_wq_offset); + +/*! Deprecated, use gsmtap_inst_fd2() instead + * \param[in] gti GSMTAP instance + * \returns file descriptor of GSMTAP instance */ +int gsmtap_inst_fd(struct gsmtap_inst *gti) +{ + return gsmtap_inst_fd2(gti); +} + +/*! obtain the file descriptor associated with a gsmtap instance + * \param[in] gti GSMTAP instance + * \returns file descriptor of GSMTAP instance */ +int gsmtap_inst_fd2(const struct gsmtap_inst *gti) +{ + return gti->wq.bfd.fd; +} /*! convert RSL channel number to GSMTAP channel type * \param[in] rsl_chantype RSL channel type @@ -235,13 +269,14 @@ struct msgb *gsmtap_makemsg(uint16_t arfcn, uint8_t ts, uint8_t chan_type, #include <sys/socket.h> #include <netinet/in.h> +#include <osmocom/core/osmo_io.h> -/*! Create a new (sending) GSMTAP source socket +/*! Create a new (sending) GSMTAP source socket * \param[in] host host name or IP address in string format * \param[in] port UDP port number in host byte order * \return file descriptor of the new socket * - * Opens a GSMTAP source (sending) socket, conncet it to host/port and + * Opens a GSMTAP source (sending) socket, connect it to host/port and * return resulting fd. If \a host is NULL, the destination address * will be localhost. If \a port is 0, the default \ref * GSMTAP_UDP_PORT will be used. @@ -257,6 +292,30 @@ int gsmtap_source_init_fd(const char *host, uint16_t port) OSMO_SOCK_F_CONNECT); } +/*! Create a new (sending) GSMTAP source socket + * \param[in] local_host local host name or IP address in string format + * \param[in] local_port local UDP port number in host byte order + * \param[in] rem_host remote host name or IP address in string format + * \param[in] rem_port remote UDP port number in host byte order + * \return file descriptor of the new socket + * + * Opens a GSMTAP source (sending) socket, connect it to remote host/port, + * bind to local host/port and return resulting fd. + * If \a local_host is NULL, the default address is used. + * If \a local_port is 0, than random unused port will be selected by OS. + * If \a rem_host is NULL, the destination address will be localhost. + * If \a rem_port is 0, the default \ref GSMTAP_UDP_PORT will be used. + */ +int gsmtap_source_init_fd2(const char *local_host, uint16_t local_port, const char *rem_host, uint16_t rem_port) +{ + if (!local_host) + return gsmtap_source_init_fd(rem_host, rem_port); + + return osmo_sock_init2(AF_UNSPEC, SOCK_DGRAM, IPPROTO_UDP, local_host, local_port, + rem_host ? rem_host : "localhost", rem_port ? rem_port : GSMTAP_UDP_PORT, + OSMO_SOCK_F_BIND | OSMO_SOCK_F_CONNECT); +} + /*! Add a local sink to an existing GSMTAP source and return fd * \param[in] gsmtap_fd file descriptor of the gsmtap socket * \returns file descriptor of locally bound receive socket @@ -304,13 +363,13 @@ int gsmtap_sendmsg(struct gsmtap_inst *gti, struct msgb *msg) if (!gti) return -ENODEV; - if (gti->ofd_wq_mode) - return osmo_wqueue_enqueue(>i->wq, msg); + if (gti->osmo_io_mode) + return osmo_iofd_write_msgb(gti->out, msg); else { /* try immediate send and return error if any */ int rc; - rc = write(gsmtap_inst_fd(gti), msg->data, msg->len); + rc = write(gsmtap_inst_fd2(gti), msg->data, msg->len); if (rc < 0) { return rc; } else if (rc >= msg->len) { @@ -323,6 +382,20 @@ int gsmtap_sendmsg(struct gsmtap_inst *gti, struct msgb *msg) } } +/*! Send a \ref msgb through a GSMTAP source; free the message even if tx queue full. + * \param[in] gti GSMTAP instance + * \param[in] msg message buffer; always freed, caller must not reference it later. + * \return 0 in case of success; negative in case of error + */ +int gsmtap_sendmsg_free(struct gsmtap_inst *gti, struct msgb *msg) +{ + int rc; + rc = gsmtap_sendmsg(gti, msg); + if (rc < 0) + msgb_free(msg); + return rc; +} + /*! send an arbitrary type through GSMTAP. * See \ref gsmtap_makemsg_ex for arguments */ @@ -360,40 +433,6 @@ int gsmtap_send(struct gsmtap_inst *gti, uint16_t arfcn, uint8_t ts, signal_dbm, snr, data, len); } -/* Callback from select layer if we can write to the socket */ -static int gsmtap_wq_w_cb(struct osmo_fd *ofd, struct msgb *msg) -{ - int rc; - - rc = write(ofd->fd, msg->data, msg->len); - if (rc < 0) { - return rc; - } - if (rc != msg->len) { - return -EIO; - } - - return 0; -} - -/* Callback from select layer if we can read from the sink socket */ -static int gsmtap_sink_fd_cb(struct osmo_fd *fd, unsigned int flags) -{ - int rc; - uint8_t buf[4096]; - - if (!(flags & OSMO_FD_READ)) - return 0; - - rc = read(fd->fd, buf, sizeof(buf)); - if (rc < 0) { - return rc; - } - /* simply discard any data arriving on the socket */ - - return 0; -} - /*! Add a local sink to an existing GSMTAP source and return fd * \param[in] gti existing GSMTAP source * \returns file descriptor of locally bound receive socket @@ -411,30 +450,63 @@ static int gsmtap_sink_fd_cb(struct osmo_fd *fd, unsigned int flags) */ int gsmtap_source_add_sink(struct gsmtap_inst *gti) { - int fd, rc; + return gti->sink_fd = gsmtap_source_add_sink_fd(gsmtap_inst_fd2(gti)); +} - fd = gsmtap_source_add_sink_fd(gsmtap_inst_fd(gti)); - if (fd < 0) - return fd; +/* Registered in Osmo IO as a no-op to set the write callback. */ +static void gsmtap_ops_noop_cb(struct osmo_io_fd *iofd, int res, struct msgb *msg) +{ +} - if (gti->ofd_wq_mode) { - struct osmo_fd *sink_ofd; +static struct osmo_io_ops gsmtap_ops = { .write_cb = gsmtap_ops_noop_cb }; - sink_ofd = >i->sink_ofd; - sink_ofd->fd = fd; - sink_ofd->when = OSMO_FD_READ; - sink_ofd->cb = gsmtap_sink_fd_cb; +/*! Open GSMTAP source socket, connect and register osmo_fd + * \param[in] local_host IP address in string format + * \param[in] local_port UDP port number in host byte order + * \param[in] rem_host host name or IP address in string format + * \param[in] rem_port UDP port number in host byte order + * \param[in] ofd_wq_mode Register \ref osmo_wqueue (1) or not (0) + * \return callee-allocated \ref gsmtap_inst + * + * Open GSMTAP source (sending) socket, connect it to remote host/port, + * bind it local host/port, + * allocate 'struct gsmtap_inst' and optionally osmo_fd/osmo_wqueue + * registration. + */ +struct gsmtap_inst *gsmtap_source_init2(const char *local_host, uint16_t local_port, + const char *rem_host, uint16_t rem_port, int ofd_wq_mode) +{ + struct gsmtap_inst *gti; + int fd; - rc = osmo_fd_register(sink_ofd); - if (rc < 0) { - close(fd); - return rc; - } + fd = gsmtap_source_init_fd2(local_host, local_port, rem_host, rem_port); + if (fd < 0) + return NULL; + + gti = talloc_zero(NULL, struct gsmtap_inst); + gti->osmo_io_mode = ofd_wq_mode; + /* Still using the wq member for its 'fd' field only, since we are keeping it for now, anyways */ + gti->wq.bfd.fd = fd; + gti->sink_fd = -1; + + if (ofd_wq_mode) { + gti->out = osmo_iofd_setup(gti, gti->wq.bfd.fd, "gsmtap_inst.io_fd", OSMO_IO_FD_MODE_READ_WRITE, &gsmtap_ops, NULL); + if (gti->out == NULL) + goto err_cleanup; + if (osmo_iofd_register(gti->out, gti->wq.bfd.fd) < 0) + goto err_cleanup; + + /* osmo write queue previously used was set up with value of 64 */ + osmo_iofd_set_txqueue_max_length(gti->out, 64); } - return fd; -} + return gti; +err_cleanup: + talloc_free(gti); + close(fd); + return NULL; +} /*! Open GSMTAP source socket, connect and register osmo_fd * \param[in] host host name or IP address in string format @@ -449,31 +521,25 @@ int gsmtap_source_add_sink(struct gsmtap_inst *gti) struct gsmtap_inst *gsmtap_source_init(const char *host, uint16_t port, int ofd_wq_mode) { - struct gsmtap_inst *gti; - int fd, rc; - - fd = gsmtap_source_init_fd(host, port); - if (fd < 0) - return NULL; + return gsmtap_source_init2(NULL, 0, host, port, ofd_wq_mode); +} - gti = talloc_zero(NULL, struct gsmtap_inst); - gti->ofd_wq_mode = ofd_wq_mode; - gti->wq.bfd.fd = fd; - gti->sink_ofd.fd = -1; +void gsmtap_source_free(struct gsmtap_inst *gti) +{ + if (!gti) + return; - if (ofd_wq_mode) { - osmo_wqueue_init(>i->wq, 64); - gti->wq.write_cb = &gsmtap_wq_w_cb; + if (gti->osmo_io_mode) { + osmo_iofd_free(gti->out); - rc = osmo_fd_register(>i->wq.bfd); - if (rc < 0) { - talloc_free(gti); - close(fd); - return NULL; + if (gti->sink_fd != -1) { + close(gti->sink_fd); + gti->sink_fd = -1; } + } - return gti; + talloc_free(gti); } #endif /* HAVE_SYS_SOCKET_H */ @@ -514,6 +580,8 @@ const struct value_string gsmtap_type_names[] = { { GSMTAP_TYPE_TETRA_I1, "TETRA V+D" }, { GSMTAP_TYPE_TETRA_I1_BURST, "TETRA bursts" }, { GSMTAP_TYPE_WMX_BURST, "WiMAX burst" }, + { GSMTAP_TYPE_GB_LLC, "GPRS Gb LLC" }, + { GSMTAP_TYPE_GB_SNDCP, "GPRS Gb SNDCP" }, { GSMTAP_TYPE_GMR1_UM, "GMR-1 air interfeace (MES-MS<->GTS)"}, { GSMTAP_TYPE_UMTS_RLC_MAC, "UMTS RLC/MAC" }, { GSMTAP_TYPE_UMTS_RRC, "UMTS RRC" }, @@ -522,6 +590,9 @@ const struct value_string gsmtap_type_names[] = { { GSMTAP_TYPE_LTE_MAC_FRAMED, "LTE MAC with context hdr" }, { GSMTAP_TYPE_OSMOCORE_LOG, "libosmocore logging" }, { GSMTAP_TYPE_QC_DIAG, "Qualcomm DIAG" }, + { GSMTAP_TYPE_LTE_NAS, "LTE Non-Access Stratum" }, + { GSMTAP_TYPE_E1T1, "E1/T1 lines" }, + { GSMTAP_TYPE_GSM_RLP, "GSM Radio Link Protocol" }, { 0, NULL } }; diff --git a/src/isdnhdlc.c b/src/core/isdnhdlc.c index 58b4a661..4ced5afd 100644 --- a/src/isdnhdlc.c +++ b/src/core/isdnhdlc.c @@ -18,10 +18,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include <string.h> @@ -101,13 +97,12 @@ check_frame(struct osmo_isdnhdlc_vars *hdlc) When a new flag is found, the complete frame has been received and the CRC is checked. If a valid frame is found, the function returns the frame length - excluding the CRC with the bit HDLC_END_OF_FRAME set. + excluding the CRC. If the beginning of a valid frame is found, the function returns the length. If a framing error is found (too many 1s and not a flag) the function - returns the length with the bit OSMO_HDLC_FRAMING_ERROR set. - If a CRC error is found the function returns the length with the - bit OSMO_HDLC_CRC_ERROR set. + returns -OSMO_HDLC_FRAMING_ERROR. + If a CRC error is found the function returns -OSMO_HDLC_CRC_ERROR. If the frame length exceeds the destination buffer size, the function returns the length with the bit OSMO_HDLC_LENGTH_ERROR set. diff --git a/src/it_q.c b/src/core/it_q.c index 1bb0e155..810dc903 100644 --- a/src/it_q.c +++ b/src/core/it_q.c @@ -14,11 +14,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, - * MA 02110-1301, USA. */ /*! \addtogroup it_q @@ -37,7 +32,7 @@ * and call a queue-specific callback function. */ -#include "../config.h" +#include "config.h" #ifdef HAVE_SYS_EVENTFD_H @@ -250,7 +245,7 @@ int _osmo_it_q_enqueue(struct osmo_it_q *queue, struct llist_head *item) /*! Thread-safe de-queue from an inter-thread message queue. * \param[in] queue Inter-thread queue from which to dequeue - * \returns dequeued message buffer; NULL if none available + * \returns llist_head of dequeued message; NULL if none available */ struct llist_head *_osmo_it_q_dequeue(struct osmo_it_q *queue) { @@ -259,12 +254,9 @@ struct llist_head *_osmo_it_q_dequeue(struct osmo_it_q *queue) pthread_mutex_lock(&queue->mutex); - if (llist_empty(&queue->list)) - l = NULL; - l = queue->list.next; - OSMO_ASSERT(l); - llist_del(l); - queue->current_length--; + l = item_dequeue(&queue->list); + if (l != NULL) + queue->current_length--; pthread_mutex_unlock(&queue->mutex); diff --git a/src/core/libosmocore.map b/src/core/libosmocore.map new file mode 100644 index 00000000..c5ab6e37 --- /dev/null +++ b/src/core/libosmocore.map @@ -0,0 +1,631 @@ +LIBOSMOCORE_1.0 { +global: + +assert_loginfo; +bit_value_to_char; +bitvec_add_array; +bitvec_alloc; +bitvec_fill; +bitvec_find_bit_pos; +bitvec_free; +bitvec_get_bit_high; +bitvec_get_bit_pos; +bitvec_get_bit_pos_high; +bitvec_get_bytes; +bitvec_get_int16_msb; +bitvec_get_nth_set_bit; +bitvec_get_uint; +bitvec_pack; +bitvec_read_field; +bitvec_rl; +bitvec_rl_curbit; +bitvec_set_bit; +bitvec_set_bit_pos; +bitvec_set_bits; +bitvec_set_bytes; +bitvec_set_u64; +bitvec_set_uint; +bitvec_shiftl; +bitvec_spare_padding; +bitvec_to_string_r; +bitvec_unhex; +bitvec_unpack; +bitvec_write_field; +bitvec_zero; +chantype_gsmtap2rsl; +chantype_rsl2gsmtap; +chantype_rsl2gsmtap2; +get_string_value; +get_value_string; +get_value_string_or_null; +gsmtap_gsm_channel_names; +gsmtap_inst_fd; +gsmtap_inst_fd2; +gsmtap_makemsg; +gsmtap_makemsg_ex; +gsmtap_send; +gsmtap_send_ex; +gsmtap_sendmsg; +gsmtap_sendmsg_free; +gsmtap_source_add_sink; +gsmtap_source_add_sink_fd; +gsmtap_source_free; +gsmtap_source_init; +gsmtap_source_init2; +gsmtap_source_init_fd; +gsmtap_source_init_fd2; +gsmtap_type_names; +log_add_target; +log_category_name; +log_check_level; +log_cache_enable; +log_cache_update; +log_del_target; +log_enable_multithread; +log_fini; +log_init; +log_level_str; +loglevel_strs; +logp; +logp2; +log_parse_category; +log_parse_category_mask; +log_parse_level; +logp_stub; +log_reset_context; +log_set_all_filter; +log_set_category_filter; +log_set_context; +log_set_log_level; +log_set_print_category; +log_set_print_category_hex; +log_set_print_extended_timestamp; +log_set_print_filename; +log_set_print_filename2; +log_set_print_filename_pos; +log_set_print_level; +log_set_print_tid; +log_set_print_timestamp; +log_set_use_color; +log_target_create; +log_target_create_file; +log_target_create_file_stream; +log_target_create_gsmtap; +log_target_create_rb; +log_target_create_stderr; +log_target_create_syslog; +log_target_create_systemd; +log_target_destroy; +log_target_file_reopen; +log_target_file_switch_to_stream; +log_target_file_switch_to_wqueue; +log_target_find; +log_target_rb_avail_size; +log_target_rb_get; +log_target_rb_used_size; +log_target_systemd_set_raw; +log_targets_reopen; +log_tgt_mutex_lock_impl; +log_tgt_mutex_unlock_impl; +msgb_alloc; +msgb_alloc_c; +msgb_copy; +msgb_copy_c; +msgb_copy_resize; +msgb_copy_resize_c; +msgb_data; +msgb_dequeue; +msgb_enqueue; +_msgb_eq; +msgb_free; +msgb_hexdump; +msgb_hexdump_buf; +msgb_hexdump_c; +msgb_length; +msgb_printf; +msgb_reset; +msgb_resize_area; +msgb_set_talloc_ctx; +msgb_talloc_ctx_init; +osmo_base64_decode; +osmo_base64_encode; +osmo_bcd2char; +osmo_bcd2str; +osmo_bit_reversal; +osmo_char2bcd; +osmo_clock_gettime; +osmo_clock_override_add; +osmo_clock_override_enable; +osmo_clock_override_gettimespec; +osmo_close_all_fds_above; +osmo_config_list_parse; +osmo_constant_time_cmp; +osmo_conv_decode; +osmo_conv_decode_acc; +osmo_conv_decode_deinit; +osmo_conv_decode_flush; +osmo_conv_decode_get_best_end_state; +osmo_conv_decode_get_output; +osmo_conv_decode_init; +osmo_conv_decode_reset; +osmo_conv_decode_rewind; +osmo_conv_decode_scan; +osmo_conv_encode; +osmo_conv_encode_flush; +osmo_conv_encode_init; +osmo_conv_encode_load_state; +osmo_conv_encode_raw; +osmo_conv_get_input_length; +osmo_conv_get_output_length; +osmo_counter_alloc; +osmo_counter_difference; +osmo_counter_free; +osmo_counter_get_by_name; +osmo_counters_count; +osmo_counters_for_each; +osmo_crc16; +osmo_crc16_ccitt; +osmo_crc16_ccitt_table; +osmo_crc16_table; +osmo_crc16gen_check_bits; +osmo_crc16gen_compute_bits; +osmo_crc16gen_set_bits; +osmo_crc32gen_check_bits; +osmo_crc32gen_compute_bits; +osmo_crc32gen_set_bits; +osmo_crc64gen_check_bits; +osmo_crc64gen_compute_bits; +osmo_crc64gen_set_bits; +osmo_crc8gen_check_bits; +osmo_crc8gen_compute_bits; +osmo_crc8gen_set_bits; +osmo_ctx; +osmo_ctx_init; +osmo_daemonize; +osmo_decode_big_endian; +osmo_encode_big_endian; +osmo_environment_append; +osmo_environment_filter; +osmo_environment_whitelist; +osmo_escape_cstr_buf; +osmo_escape_cstr_c; +osmo_escape_str; +osmo_escape_str_buf; +osmo_escape_str_buf2; +osmo_escape_str_buf3; +osmo_escape_str_c; +osmo_event_for_prim; +osmo_fd_close; +osmo_fd_disp_fds; +osmo_fd_fill_fds; +osmo_fd_get_by_fd; +osmo_fd_is_registered; +osmo_fd_register; +osmo_fd_setup; +osmo_fd_unregister; +osmo_fd_update_when; +osmo_float_str_to_int; +osmo_fsm_event_name; +osmo_fsm_find_by_name; +osmo_fsm_inst_alloc; +osmo_fsm_inst_alloc_child; +_osmo_fsm_inst_broadcast_children; +osmo_fsm_inst_change_parent; +_osmo_fsm_inst_dispatch; +osmo_fsm_inst_find_by_id; +osmo_fsm_inst_find_by_name; +osmo_fsm_inst_free; +osmo_fsm_inst_name; +_osmo_fsm_inst_state_chg; +_osmo_fsm_inst_state_chg_keep_or_start_timer; +_osmo_fsm_inst_state_chg_keep_or_start_timer_ms; +_osmo_fsm_inst_state_chg_keep_timer; +_osmo_fsm_inst_state_chg_ms; +_osmo_fsm_inst_term; +_osmo_fsm_inst_term_children; +osmo_fsm_inst_unlink_parent; +osmo_fsm_inst_update_id; +osmo_fsm_inst_update_id_f; +osmo_fsm_inst_update_id_f_sanitize; +osmo_fsm_log_addr; +osmo_fsm_log_timeouts; +osmo_fsm_register; +osmo_fsm_set_dealloc_ctx; +osmo_fsm_state_name; +osmo_fsm_term_cause_names; +osmo_fsm_term_safely; +osmo_fsm_unregister; +osmo_generate_backtrace; +osmo_get_macaddr; +osmo_gettid; +osmo_gettimeofday; +osmo_gettimeofday_override; +osmo_gettimeofday_override_add; +osmo_gettimeofday_override_time; +osmo_g_fsms; +osmo_hexdump; +osmo_hexdump_buf; +osmo_hexdump_c; +osmo_hexdump_nospc; +osmo_hexdump_nospc_c; +osmo_hexparse; +osmo_identifier_sanitize_buf; +osmo_identifier_valid; +osmo_init_ignore_signals; +osmo_init_logging; +osmo_init_logging2; +osmo_int_to_float_str_buf; +osmo_int_to_float_str_c; +osmo_io_backend_names; +osmo_iofd_close; +osmo_iofd_free; +osmo_iofd_get_data; +osmo_iofd_get_ioops; +osmo_iofd_get_fd; +osmo_iofd_get_name; +osmo_iofd_set_name; +osmo_iofd_get_priv_nr; +osmo_iofd_init; +osmo_iofd_mode_names; +osmo_iofd_ops; +osmo_iofd_register; +osmo_iofd_sendto_msgb; +osmo_iofd_sctp_send_msgb; +osmo_iofd_sendmsg_msgb; +osmo_iofd_set_alloc_info; +osmo_iofd_set_cmsg_size; +osmo_iofd_set_data; +osmo_iofd_set_ioops; +osmo_iofd_set_priv_nr; +osmo_iofd_set_txqueue_max_length; +osmo_iofd_setup; +osmo_iofd_txqueue_clear; +osmo_iofd_txqueue_len; +osmo_iofd_unregister; +osmo_iofd_uring_init; +osmo_iofd_notify_connected; +osmo_iofd_write_msgb; +osmo_ip_str_type; +osmo_isdnhdlc_decode; +osmo_isdnhdlc_encode; +osmo_isdnhdlc_out_init; +osmo_isdnhdlc_rcv_init; +osmo_is_hexstr; +osmo_isqrt32; +osmo_it_q_alloc; +osmo_it_q_by_name; +_osmo_it_q_dequeue; +osmo_it_q_destroy; +_osmo_it_q_enqueue; +osmo_it_q_flush; +osmo_log_backtrace; +osmo_log_info; +osmo_log_target_list; +osmo_luhn; +osmo_macaddr_parse; +osmo_mnl_destroy; +osmo_mnl_init; +osmo_multiaddr_ip_and_port_snprintf; +osmo_netdev_add_addr; +osmo_netdev_add_route; +osmo_netdev_alloc; +osmo_netdev_free; +osmo_netdev_get_dev_name; +osmo_netdev_get_ifindex; +osmo_netdev_get_name; +osmo_netdev_get_netns_name; +osmo_netdev_get_priv_data; +osmo_netdev_ifupdown; +osmo_netdev_is_registered; +osmo_netdev_register; +osmo_netdev_set_dev_name_chg_cb; +osmo_netdev_set_ifindex; +osmo_netdev_set_ifupdown_ind_cb; +osmo_netdev_set_mtu_chg_cb; +osmo_netdev_set_netns_name; +osmo_netdev_set_priv_data; +osmo_netdev_unregister; +osmo_netns_open_fd; +osmo_netns_switch_enter; +osmo_netns_switch_exit; +osmo_nibble_shift_left_unal; +osmo_nibble_shift_right; +osmo_panic; +osmo_pbit2ubit; +osmo_pbit2ubit_ext; +osmo_plugin_load_all; +osmo_prbs11; +osmo_prbs15; +osmo_prbs7; +osmo_prbs9; +osmo_prbs_get_ubit; +osmo_prbs_get_ubits; +osmo_prbs_state_init; +osmo_prim_op_names; +osmo_print_n; +osmo_quote_cstr_buf; +osmo_quote_cstr_c; +osmo_quote_str; +osmo_quote_str_buf; +osmo_quote_str_buf2; +osmo_quote_str_buf3; +osmo_quote_str_c; +osmo_revbytebits_32; +osmo_revbytebits_8; +osmo_revbytebits_buf; +osmo_sbit2ubit; +osmo_select_init; +osmo_select_main; +osmo_select_main_ctx; +osmo_select_shutdown_done; +osmo_select_shutdown_request; +osmo_select_shutdown_requested; +osmo_separated_identifiers_valid; +osmo_sercomm_change_speed; +osmo_sercomm_drv_pull; +osmo_sercomm_drv_rx_char; +osmo_sercomm_init; +osmo_sercomm_initialized; +osmo_sercomm_register_rx_cb; +osmo_sercomm_sendmsg; +osmo_sercomm_tx_queue_depth; +osmo_serial_clear_custom_baudrate; +osmo_serial_init; +osmo_serial_set_baudrate; +osmo_serial_set_custom_baudrate; +osmo_serial_speed_t; +osmo_set_panic_handler; +osmo_signal_dispatch; +osmo_signalfd_setup; +osmo_signal_register_handler; +osmo_signal_talloc_ctx_init; +osmo_signal_unregister_handler; +osmo_sockaddr_cmp; +osmo_sockaddr_from_octets; +osmo_sockaddr_from_str_and_uint; +osmo_sockaddr_in_to_str_and_uint; +osmo_sockaddr_is_any; +osmo_sockaddr_is_local; +osmo_sockaddr_local_ip; +osmo_sockaddr_netmask_to_prefixlen; +osmo_sockaddr_ntop; +osmo_sockaddr_port; +osmo_sockaddr_set_port; +osmo_sockaddr_str_cmp; +osmo_sockaddr_str_from_32; +osmo_sockaddr_str_from_32h; +osmo_sockaddr_str_from_32n; +osmo_sockaddr_str_from_in6_addr; +osmo_sockaddr_str_from_in_addr; +osmo_sockaddr_str_from_sockaddr; +osmo_sockaddr_str_from_sockaddr_in; +osmo_sockaddr_str_from_sockaddr_in6; +osmo_sockaddr_str_from_osa; +osmo_sockaddr_str_from_str; +osmo_sockaddr_str_from_str2; +osmo_sockaddr_str_is_nonzero; +osmo_sockaddr_str_is_set; +osmo_sockaddr_str_to_32; +osmo_sockaddr_str_to_32h; +osmo_sockaddr_str_to_32n; +osmo_sockaddr_str_to_in6_addr; +osmo_sockaddr_str_to_in_addr; +osmo_sockaddr_str_to_sockaddr; +osmo_sockaddr_str_to_sockaddr_in; +osmo_sockaddr_str_to_sockaddr_in6; +osmo_sockaddr_str_to_osa; +osmo_sockaddr_to_octets; +osmo_sockaddr_to_str; +osmo_sockaddr_to_str_and_uint; +osmo_sockaddr_to_str_buf; +osmo_sockaddr_to_str_buf2; +osmo_sockaddr_to_str_c; +osmo_sock_get_ip_and_port; +osmo_sock_get_local_ip; +osmo_sock_get_local_ip_port; +osmo_sock_get_name; +osmo_sock_get_name2; +osmo_sock_get_name2_c; +osmo_sock_get_name_buf; +osmo_sock_get_remote_ip; +osmo_sock_get_remote_ip_port; +osmo_sock_init; +osmo_sock_init2; +osmo_sock_init2_multiaddr; +osmo_sock_init2_multiaddr2; +osmo_sock_init2_ofd; +osmo_sock_init_ofd; +osmo_sock_init_osa; +osmo_sock_init_osa_ofd; +osmo_sock_init_sa; +osmo_sock_local_ip; +osmo_sock_mcast_all_set; +osmo_sock_mcast_iface_set; +osmo_sock_mcast_loop_set; +osmo_sock_mcast_subscribe; +osmo_sock_mcast_ttl_set; +osmo_sock_multiaddr_add_local_addr; +osmo_sock_multiaddr_del_local_addr; +osmo_sock_multiaddr_get_ip_and_port; +osmo_sock_multiaddr_get_name_buf; +osmo_sock_sctp_get_peer_addr_info; +osmo_sock_set_dscp; +osmo_sock_set_priority; +osmo_sock_unix_init; +osmo_sock_unix_init_ofd; +osmo_soft_uart_default_cfg; +osmo_soft_uart_alloc; +osmo_soft_uart_free; +osmo_soft_uart_configure; +osmo_soft_uart_get_name; +osmo_soft_uart_set_name; +osmo_soft_uart_set_rx; +osmo_soft_uart_set_tx; +osmo_soft_uart_rx_ubits; +osmo_soft_uart_tx_ubits; +osmo_soft_uart_get_status; +osmo_soft_uart_set_status; +osmo_soft_uart_set_status_line; +osmo_soft_uart_flush_rx; +osmo_stat_item_dec; +osmo_stat_item_flush; +osmo_stat_item_for_each_group; +osmo_stat_item_for_each_item; +osmo_stat_item_get_by_name; +osmo_stat_item_get_desc; +osmo_stat_item_get_group_by_name_idx; +osmo_stat_item_get_group_by_name_idxname; +osmo_stat_item_get_last; +osmo_stat_item_group_alloc; +osmo_stat_item_group_free; +osmo_stat_item_group_get_item; +osmo_stat_item_group_reset; +osmo_stat_item_group_set_name; +osmo_stat_item_inc; +osmo_stat_item_init; +osmo_stat_item_reset; +osmo_stat_item_set; +osmo_stats_config; +osmo_stats_init; +osmo_stats_report; +osmo_stats_reporter_alloc; +osmo_stats_reporter_create_log; +osmo_stats_reporter_create_statsd; +osmo_stats_reporter_disable; +osmo_stats_reporter_enable; +osmo_stats_reporter_find; +osmo_stats_reporter_free; +osmo_stats_reporter_list; +osmo_stats_reporter_send; +osmo_stats_reporter_send_buffer; +osmo_stats_reporter_set_flush_period; +osmo_stats_reporter_set_local_addr; +osmo_stats_reporter_set_max_class; +osmo_stats_reporter_set_mtu; +osmo_stats_reporter_set_name_prefix; +osmo_stats_reporter_set_remote_addr; +osmo_stats_reporter_set_remote_port; +osmo_stats_reporter_udp_close; +osmo_stats_reporter_udp_open; +osmo_stats_set_interval; +osmo_stats_tcp_osmo_fd_register; +osmo_stats_tcp_osmo_fd_unregister; +osmo_stats_tcp_set_interval; +osmo_stderr_target; +osmo_str2bcd; +osmo_str2lower; +osmo_str2upper; +osmo_strlcpy; +osmo_strnchr; +osmo_strrb_add; +osmo_strrb_create; +osmo_strrb_elements; +osmo_strrb_get_nth; +_osmo_strrb_is_bufindex_valid; +osmo_strrb_is_empty; +osmo_strbuf_drop_tail; +osmo_strbuf_added_tail; +osmo_str_startswith; +osmo_str_to_int; +osmo_str_to_int64; +osmo_str_tolower; +osmo_str_tolower_buf; +osmo_str_tolower_c; +osmo_str_toupper; +osmo_str_toupper_buf; +osmo_str_toupper_c; +osmo_system_nowait; +osmo_system_nowait2; +osmo_t4_encode; +osmo_talloc_replace_string_fmt; +osmo_tcp_stats_config; +_osmo_tdef_fsm_inst_state_chg; +osmo_tdef_get; +osmo_tdef_get_entry; +osmo_tdef_get_state_timeout; +osmo_tdef_range_str_buf; +osmo_tdef_set; +osmo_tdefs_reset; +osmo_tdef_unit_names; +osmo_tdef_val_in_range; +osmo_time_cc_cleanup; +osmo_time_cc_init; +osmo_time_cc_set_flag; +osmo_timer_add; +osmo_timer_del; +osmo_timerfd_disable; +osmo_timerfd_schedule; +osmo_timerfd_setup; +osmo_timer_pending; +osmo_timer_remaining; +osmo_timers_check; +osmo_timer_schedule; +osmo_timer_setup; +osmo_timers_nearest; +osmo_timers_nearest_ms; +osmo_timers_prepare; +osmo_timers_update; +osmo_tundev_alloc; +osmo_tundev_close; +osmo_tundev_free; +osmo_tundev_get_dev_name; +osmo_tundev_get_name; +osmo_tundev_get_netdev; +osmo_tundev_get_netns_name; +osmo_tundev_get_priv_data; +osmo_tundev_is_open; +osmo_tundev_open; +osmo_tundev_send; +osmo_tundev_set_data_ind_cb; +osmo_tundev_set_dev_name; +osmo_tundev_set_netns_name; +osmo_tundev_set_priv_data; +osmo_ubit2pbit; +osmo_ubit2pbit_ext; +osmo_ubit2sbit; +osmo_ubit_dump; +osmo_ubit_dump_buf; +osmo_use_count_by; +osmo_use_count_find; +osmo_use_count_free; +_osmo_use_count_get_put; +osmo_use_count_make_static_entries; +osmo_use_count_name_buf; +osmo_use_count_to_str_buf; +osmo_use_count_to_str_c; +osmo_use_count_total; +osmo_vlogp; +osmo_wqueue_bfd_cb; +osmo_wqueue_clear; +osmo_wqueue_enqueue; +osmo_wqueue_enqueue_quiet; +osmo_wqueue_set_maxlen; +osmo_wqueue_init; +rate_ctr_add; +rate_ctr_difference; +rate_ctr_for_each_counter; +rate_ctr_for_each_group; +rate_ctr_get_by_name; +rate_ctr_get_group_by_name_idx; +rate_ctr_group_alloc; +rate_ctr_group_free; +rate_ctr_group_get_ctr; +rate_ctr_group_reset; +rate_ctr_group_set_name; +rate_ctr_init; +rate_ctr_reset; +rb_erase; +rb_first; +rb_insert_color; +rb_last; +rb_next; +rb_prev; +rb_replace_node; +sercomm_drv_lock; +sercomm_drv_unlock; +tall_ctr_ctx; /* deprecated */ +tall_log_ctx; +tall_msgb_ctx; /* deprecated */ + +local: *; +}; diff --git a/src/logging.c b/src/core/logging.c index 4517afcd..e1721241 100644 --- a/src/logging.c +++ b/src/core/logging.c @@ -17,10 +17,6 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ /*! \addtogroup logging @@ -29,12 +25,13 @@ * * \file logging.c */ -#include "../config.h" +#include "config.h" #include <stdarg.h> #include <stdlib.h> #include <stdio.h> #include <string.h> +#include <unistd.h> #ifdef HAVE_STRINGS_H #include <strings.h> @@ -57,6 +54,9 @@ #include <time.h> #include <sys/time.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> #include <errno.h> #include <pthread.h> @@ -65,12 +65,18 @@ #include <osmocom/core/logging.h> #include <osmocom/core/timer.h> #include <osmocom/core/thread.h> +#include <osmocom/core/select.h> +#include <osmocom/core/write_queue.h> +#include <osmocom/core/gsmtap_util.h> #include <osmocom/vty/logging.h> /* for LOGGING_STR. */ /* maximum length of the log string of a single log event (typically line) */ #define MAX_LOG_SIZE 4096 +/* maximum number of log statements we queue in file/stderr target write queue */ +#define LOG_WQUEUE_LEN 156 + osmo_static_assert(_LOG_CTX_COUNT <= ARRAY_SIZE(((struct log_context*)NULL)->ctx), enum_logging_ctx_items_fit_in_struct_log_context); osmo_static_assert(_LOG_FLT_COUNT <= ARRAY_SIZE(((struct log_target*)NULL)->filter_data), @@ -87,6 +93,80 @@ LLIST_HEAD(osmo_log_target_list); static __thread long int logging_tid; #if (!EMBEDDED) +/*! One global copy that contains the union of log levels for all targets +* for all categories, used for quick lock free checks of log targets. */ +static volatile uint8_t *log_level_lookup_cache; + +/*! Updates cache for all targets for all categies, caller must hold osmo_log_tgt_mutex. */ +static void log_cache_update_all(void) +{ + struct log_target *tgt; + uint8_t tmp_en[osmo_log_info->num_cat]; + uint8_t tmp_level[osmo_log_info->num_cat]; + + if (!log_level_lookup_cache) + return; + + memset(tmp_en, 0, osmo_log_info->num_cat); + memset(tmp_level, UINT8_MAX, osmo_log_info->num_cat); + + /* values can also decrease.. */ + llist_for_each_entry(tgt, &osmo_log_target_list, entry) { + for (int i = 0; i < osmo_log_info->num_cat; i++) { + struct log_category *cat = &tgt->categories[i]; + tmp_en[i] = OSMO_MAX(tmp_en[i], cat->enabled); + tmp_level[i] = OSMO_MIN(tmp_level[i], cat->loglevel); + tmp_level[i] = tgt->loglevel ? OSMO_MIN(tmp_level[i], tgt->loglevel) : tmp_level[i]; + } + } + + for (int i = 0; i < osmo_log_info->num_cat; i++) + log_level_lookup_cache[i] = tmp_en[i] ? tmp_level[i] : UINT8_MAX; +} + +/*! Updates single cache entry, caller must hold osmo_log_tgt_mutex. + * + * \param[in] mapped_subsys plain category index (after mapping) + * \param[in] enabled log category enabled? + * \param[in] level log level + */ +void log_cache_update(int mapped_subsys, uint8_t enabled, uint8_t level) +{ + struct log_target *tgt; + struct log_category tmp = { UINT8_MAX, 0 }; + + if (!log_level_lookup_cache) + return; + + /* values can also decrease.. */ + llist_for_each_entry(tgt, &osmo_log_target_list, entry) { + struct log_category *cat = &tgt->categories[mapped_subsys]; + tmp.enabled = OSMO_MAX(tmp.enabled, cat->enabled); + tmp.loglevel = OSMO_MIN(tmp.loglevel, cat->loglevel); + tmp.loglevel = tgt->loglevel ? OSMO_MIN(tmp.loglevel, tgt->loglevel) : tmp.loglevel; + } + tmp.enabled = OSMO_MAX(tmp.enabled, enabled); + tmp.loglevel = OSMO_MIN(tmp.loglevel, level); + + log_level_lookup_cache[mapped_subsys] = tmp.enabled ? tmp.loglevel : UINT8_MAX; +} + +/*! Queries log level cache. + * + * \param[in] mapped_subsys plain category index (after mapping) + * \param[in] level log level + * \returns true if logging should happen for at least one log target +*/ +static bool log_cache_check(int mapped_subsys, int level) +{ + if (!log_level_lookup_cache) { + /* log-cache is not enabled, so we simply behave like we did before the cache */ + return true; + } + + return (level < log_level_lookup_cache[mapped_subsys]) ? false : true; +} + /*! This mutex must be held while using osmo_log_target_list or any of its log_targets in a multithread program. Prevents race conditions between threads like producing unordered timestamps or VTY deleting a target while another @@ -159,7 +239,7 @@ static const struct log_info_cat internal_cat[OSMO_NUM_DLIB] = { .description = "LAPD in libosmogsm", .loglevel = LOGL_NOTICE, .enabled = 1, - .color = "\033[38;5;21m", + .color = "\033[38;5;12m", }, [INT2IDX(DLINP)] = { .name = "DLINP", @@ -289,6 +369,30 @@ static const struct log_info_cat internal_cat[OSMO_NUM_DLIB] = { .enabled = 1, .loglevel = LOGL_NOTICE, .color = "\033[38;5;63m", }, + [INT2IDX(DLIUUP)] = { + .name = "DLIUUP", + .description = "Iu UP layer", + .enabled = 1, .loglevel = LOGL_NOTICE, + .color = "\033[38;5;65m", + }, + [INT2IDX(DLPFCP)] = { + .name = "DLPFCP", + .description = "libosmo-pfcp Packet Forwarding Control Protocol", + .enabled = 1, .loglevel = LOGL_NOTICE, + .color = "\033[38;5;51m", + }, + [INT2IDX(DLCSN1)] = { + .name = "DLCSN1", + .description = "libosmo-csn1 Concrete Syntax Notation 1 codec", + .enabled = 1, .loglevel = LOGL_NOTICE, + .color = "\033[38;5;11m", + }, + [INT2IDX(DLIO)] = { + .name = "DLIO", + .description = "libosmocore IO Subsystem", + .enabled = 1, .loglevel = LOGL_NOTICE, + .color = "\033[38;5;67m", + }, }; void assert_loginfo(const char *src) @@ -396,6 +500,10 @@ void log_parse_category_mask(struct log_target* target, const char *_mask) } } while ((category_token = strtok(NULL, ":"))); +#if !defined(EMBEDDED) + log_cache_update_all(); +#endif + free(mask); } @@ -440,23 +548,34 @@ static const char *const_basename(const char *path) return bn + 1; } -static void _output(struct log_target *target, unsigned int subsys, - unsigned int level, const char *file, int line, int cont, - const char *format, va_list ap) +/*! main output formatting function for log lines. + * \param[out] buf caller-allocated output buffer for the generated string + * \param[in] buf_len number of bytes available in buf + * \param[in] target log target for which the string is to be formatted + * \param[in] subsys Log sub-system number + * \param[in] level Log level + * \param[in] file name of source code file generating the log + * \param[in] line line source code line number within 'file' generating the log + * \param[in] cont is this a continuation (true) or not (false) + * \param[in] format format string + * \param[in] ap variable argument list for format + * \returns number of bytes written to out */ +static int _output_buf(char *buf, int buf_len, struct log_target *target, unsigned int subsys, + unsigned int level, const char *file, int line, int cont, + const char *format, va_list ap) { - char buf[MAX_LOG_SIZE]; - int ret, len = 0, offset = 0, rem = sizeof(buf); + int ret; const char *c_subsys = NULL; + struct osmo_strbuf sb = { .buf = buf, .pos = buf, .len = buf_len }; + + /* safety net in case of encountering errors and returning nothing */ + buf[0] = '\0'; /* are we using color */ if (target->use_color) { c_subsys = color(subsys); - if (c_subsys) { - ret = snprintf(buf + offset, rem, "%s", c_subsys); - if (ret < 0) - goto err; - OSMO_SNPRINTF_RET(ret, rem, offset, len); - } + if (c_subsys) + OSMO_STRBUF_PRINTF(sb, "%s", c_subsys); } if (!cont) { if (target->print_ext_timestamp) { @@ -465,13 +584,10 @@ static void _output(struct log_target *target, unsigned int subsys, struct timeval tv; osmo_gettimeofday(&tv, NULL); localtime_r(&tv.tv_sec, &tm); - ret = snprintf(buf + offset, rem, "%04d%02d%02d%02d%02d%02d%03d ", - tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, - tm.tm_hour, tm.tm_min, tm.tm_sec, - (int)(tv.tv_usec / 1000)); - if (ret < 0) - goto err; - OSMO_SNPRINTF_RET(ret, rem, offset, len); + OSMO_STRBUF_PRINTF(sb, "%04d%02d%02d%02d%02d%02d%03d ", + tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, + tm.tm_hour, tm.tm_min, tm.tm_sec, + (int)(tv.tv_usec / 1000)); #endif } else if (target->print_timestamp) { time_t tm; @@ -479,108 +595,95 @@ static void _output(struct log_target *target, unsigned int subsys, goto err; /* Get human-readable representation of time. man ctime: we need at least 26 bytes in buf */ - if (rem < 26 || !ctime_r(&tm, buf + offset)) + if (OSMO_STRBUF_REMAIN(sb) < 26 || !ctime_r(&tm, sb.pos)) goto err; - ret = strlen(buf + offset); + ret = strnlen(sb.pos, 26); if (ret <= 0) goto err; + OSMO_STRBUF_ADDED_TAIL(sb, ret); /* Get rid of useless final '\n' added by ctime_r. We want a space instead. */ - buf[offset + ret - 1] = ' '; - OSMO_SNPRINTF_RET(ret, rem, offset, len); + OSMO_STRBUF_DROP_TAIL(sb, 1); + OSMO_STRBUF_PRINTF(sb, " "); } if (target->print_tid) { if (logging_tid == 0) logging_tid = (long int)osmo_gettid(); - ret = snprintf(buf + offset, rem, "%ld ", logging_tid); - if (ret < 0) - goto err; - OSMO_SNPRINTF_RET(ret, rem, offset, len); - } - if (target->print_category) { - ret = snprintf(buf + offset, rem, "%s%s%s%s ", - target->use_color ? level_color(level) : "", - log_category_name(subsys), - target->use_color ? OSMO_LOGCOLOR_END : "", - c_subsys ? c_subsys : ""); - if (ret < 0) - goto err; - OSMO_SNPRINTF_RET(ret, rem, offset, len); - } - if (target->print_level) { - ret = snprintf(buf + offset, rem, "%s%s%s%s ", - target->use_color ? level_color(level) : "", - log_level_str(level), - target->use_color ? OSMO_LOGCOLOR_END : "", - c_subsys ? c_subsys : ""); - if (ret < 0) - goto err; - OSMO_SNPRINTF_RET(ret, rem, offset, len); - } - if (target->print_category_hex) { - ret = snprintf(buf + offset, rem, "<%4.4x> ", subsys); - if (ret < 0) - goto err; - OSMO_SNPRINTF_RET(ret, rem, offset, len); + OSMO_STRBUF_PRINTF(sb, "%ld ", logging_tid); } + if (target->print_category) + OSMO_STRBUF_PRINTF(sb, "%s%s%s%s ", + target->use_color ? level_color(level) : "", + log_category_name(subsys), + target->use_color ? OSMO_LOGCOLOR_END : "", + c_subsys ? c_subsys : ""); + if (target->print_level) + OSMO_STRBUF_PRINTF(sb, "%s%s%s%s ", + target->use_color ? level_color(level) : "", + log_level_str(level), + target->use_color ? OSMO_LOGCOLOR_END : "", + c_subsys ? c_subsys : ""); + if (target->print_category_hex) + OSMO_STRBUF_PRINTF(sb, "<%4.4x> ", subsys); if (target->print_filename_pos == LOG_FILENAME_POS_HEADER_END) { switch (target->print_filename2) { case LOG_FILENAME_NONE: break; case LOG_FILENAME_PATH: - ret = snprintf(buf + offset, rem, "%s:%d ", file, line); - if (ret < 0) - goto err; - OSMO_SNPRINTF_RET(ret, rem, offset, len); + OSMO_STRBUF_PRINTF(sb, "%s:%d ", file, line); break; case LOG_FILENAME_BASENAME: - ret = snprintf(buf + offset, rem, "%s:%d ", const_basename(file), line); - if (ret < 0) - goto err; - OSMO_SNPRINTF_RET(ret, rem, offset, len); + OSMO_STRBUF_PRINTF(sb, "%s:%d ", const_basename(file), line); break; } } } - ret = vsnprintf(buf + offset, rem, format, ap); - if (ret < 0) - goto err; - OSMO_SNPRINTF_RET(ret, rem, offset, len); + OSMO_STRBUF_APPEND(sb, vsnprintf, format, ap); /* For LOG_FILENAME_POS_LAST, print the source file info only when the caller ended the log * message in '\n'. If so, nip the last '\n' away, insert the source file info and re-append an * '\n'. All this to allow LOGP("start..."); LOGPC("...end\n") constructs. */ if (target->print_filename_pos == LOG_FILENAME_POS_LINE_END - && offset > 0 && buf[offset-1] == '\n') { + && sb.pos > sb.buf && *(sb.pos - 1) == '\n') { switch (target->print_filename2) { case LOG_FILENAME_NONE: break; case LOG_FILENAME_PATH: - offset --; - ret = snprintf(buf + offset, rem, " (%s:%d)\n", file, line); - if (ret < 0) - goto err; - OSMO_SNPRINTF_RET(ret, rem, offset, len); + OSMO_STRBUF_DROP_TAIL(sb, 1); + OSMO_STRBUF_PRINTF(sb, " (%s:%d)\n", file, line); break; case LOG_FILENAME_BASENAME: - offset --; - ret = snprintf(buf + offset, rem, " (%s:%d)\n", const_basename(file), line); - if (ret < 0) - goto err; - OSMO_SNPRINTF_RET(ret, rem, offset, len); + OSMO_STRBUF_DROP_TAIL(sb, 1); + OSMO_STRBUF_PRINTF(sb, " (%s:%d)\n", const_basename(file), line); break; } } if (target->use_color && c_subsys) { - ret = snprintf(buf + offset, rem, OSMO_LOGCOLOR_END); - if (ret < 0) - goto err; - OSMO_SNPRINTF_RET(ret, rem, offset, len); + /* Ensure the last color escape is sent before the newline + * (to not clobber journald, which works on single-lines only) */ + if (sb.pos > sb.buf && *(sb.pos - 1) == '\n') { + OSMO_STRBUF_DROP_TAIL(sb, 1); + OSMO_STRBUF_PRINTF(sb, OSMO_LOGCOLOR_END "\n"); + } else { + OSMO_STRBUF_PRINTF(sb, OSMO_LOGCOLOR_END); + } } err: - buf[sizeof(buf)-1] = '\0'; - target->output(target, level, buf); + return OSMO_STRBUF_CHAR_COUNT(sb); +} + +/* Format the log line for given target; use a stack buffer and call target->output */ +static void _output(struct log_target *target, unsigned int subsys, + unsigned int level, const char *file, int line, int cont, + const char *format, va_list ap) +{ + char buf[MAX_LOG_SIZE]; + int rc; + + rc = _output_buf(buf, sizeof(buf), target, subsys, level, file, line, cont, format, ap); + if (rc > 0) + target->output(target, level, buf); } /* Catch internal logging category indexes as well as out-of-bounds indexes. @@ -655,6 +758,11 @@ void osmo_vlogp(int subsys, int level, const char *file, int line, subsys = map_subsys(subsys); +#if !defined(EMBEDDED) + if (!log_cache_check(subsys, level)) + return; +#endif + log_tgt_mutex_lock(); llist_for_each_entry(tar, &osmo_log_target_list, entry) { @@ -711,12 +819,27 @@ void logp2(int subsys, unsigned int level, const char *file, int line, int cont, TRACE(LIBOSMOCORE_LOG_DONE()); } +/* This logging function is used as a fallback when the logging framework is + * not is not properly initialized. */ +void logp_stub(const char *file, int line, int cont, const char *format, ...) +{ + va_list ap; + if (!cont) + fprintf(stderr, "%s:%d ", file, line); + va_start(ap, format); + vfprintf(stderr, format, ap); + va_end(ap); +} + /*! Register a new log target with the logging core * \param[in] target Log target to be registered */ void log_add_target(struct log_target *target) { llist_add_tail(&target->entry, &osmo_log_target_list); +#if (!EMBEDDED) + log_cache_update_all(); +#endif } /*! Unregister a log target from the logging core @@ -725,6 +848,9 @@ void log_add_target(struct log_target *target) void log_del_target(struct log_target *target) { llist_del(&target->entry); +#if (!EMBEDDED) + log_cache_update_all(); +#endif } /*! Reset (clear) the logging context */ @@ -885,6 +1011,9 @@ void log_set_print_level(struct log_target *target, int print_level) void log_set_log_level(struct log_target *target, int log_level) { target->loglevel = log_level; +#if !defined(EMBEDDED) + log_cache_update_all(); +#endif } /*! Set a category filter on a given log target @@ -901,15 +1030,73 @@ void log_set_category_filter(struct log_target *target, int category, category = map_subsys(category); target->categories[category].enabled = !!enable; target->categories[category].loglevel = level; + +#if !defined(EMBEDDED) + log_cache_update(category, !!enable, level); +#endif } #if (!EMBEDDED) -static void _file_output(struct log_target *target, unsigned int level, +/* write-queue tells us we should write another msgb (log line) to the output fd */ +static int _file_wq_write_cb(struct osmo_fd *ofd, struct msgb *msg) +{ + int rc; + + rc = write(ofd->fd, msgb_data(msg), msgb_length(msg)); + if (rc < 0) + return rc; + if (rc != msgb_length(msg)) { + /* pull the number of bytes we have already written */ + msgb_pull(msg, rc); + /* ask write_queue to re-insert the msgb at the head of the queue */ + return -EAGAIN; + } + return 0; +} + +/* output via buffered, blocking stdio streams */ +static void _file_output_stream(struct log_target *target, unsigned int level, const char *log) { - fprintf(target->tgt_file.out, "%s", log); + OSMO_ASSERT(target->tgt_file.out); + fputs(log, target->tgt_file.out); fflush(target->tgt_file.out); } + +/* output via non-blocking write_queue, doing internal buffering */ +static void _file_raw_output(struct log_target *target, int subsys, unsigned int level, const char *file, + int line, int cont, const char *format, va_list ap) +{ + struct msgb *msg; + int rc; + + OSMO_ASSERT(target->tgt_file.wqueue); + msg = msgb_alloc_c(target->tgt_file.wqueue, MAX_LOG_SIZE, "log_file_msg"); + if (!msg) + return; + + /* we simply enqueue the log message to a write queue here, to avoid any blocking + * writes on the output file. The write queue will tell us once the file is writable + * and call _file_wq_write_cb() */ + rc = _output_buf((char *)msgb_data(msg), msgb_tailroom(msg), target, subsys, level, file, line, cont, format, ap); + msgb_put(msg, rc); + + /* attempt a synchronous, non-blocking write, if the write queue is empty */ + if (target->tgt_file.wqueue->current_length == 0) { + rc = _file_wq_write_cb(&target->tgt_file.wqueue->bfd, msg); + if (rc == 0) { + /* the write was complete, we can exit early */ + msgb_free(msg); + return; + } + } + /* if we reach here, either we already had elements in the write_queue, or the synchronous write + * failed: enqueue the message to the write_queue (backlog) */ + if (osmo_wqueue_enqueue_quiet(target->tgt_file.wqueue, msg) < 0) { + msgb_free(msg); + /* TODO: increment some counter so we can see that messages were dropped */ + } +} #endif /*! Create a new log target skeleton @@ -955,6 +1142,15 @@ struct log_target *log_target_create(void) /* global log level */ target->loglevel = 0; + +#if !defined(EMBEDDED) + /* update cache */ + for (i = 0; i < osmo_log_info->num_cat; i++) { + const struct log_info_cat *c = &osmo_log_info->cat[i]; + log_cache_update(i, c->enabled, c->loglevel); + } +#endif + return target; } @@ -972,7 +1168,7 @@ struct log_target *log_target_create_stderr(void) target->type = LOG_TGT_TYPE_STDERR; target->tgt_file.out = stderr; - target->output = _file_output; + target->output = _file_output_stream; return target; #else return NULL; @@ -980,11 +1176,11 @@ struct log_target *log_target_create_stderr(void) } #if (!EMBEDDED) -/*! Create a new file-based log target +/*! Create a new file-based log target using buffered, blocking stream output * \param[in] fname File name of the new log file * \returns Log target in case of success, NULL otherwise */ -struct log_target *log_target_create_file(const char *fname) +struct log_target *log_target_create_file_stream(const char *fname) { struct log_target *target; @@ -998,9 +1194,165 @@ struct log_target *log_target_create_file(const char *fname) log_target_destroy(target); return NULL; } + target->output = _file_output_stream; + target->tgt_file.fname = talloc_strdup(target, fname); + + return target; +} + +/*! switch from non-blocking/write-queue to blocking + buffered stream output + * \param[in] target log target which we should switch + * \return 0 on success; 1 if already switched before; negative on error + * Must be called with mutex osmo_log_tgt_mutex held, see log_tgt_mutex_lock. + */ +int log_target_file_switch_to_stream(struct log_target *target) +{ + struct osmo_wqueue *wq; + + if (!target) + return -ENODEV; + + if (target->tgt_file.out) { + /* target has already been switched over */ + return 1; + } + + wq = target->tgt_file.wqueue; + OSMO_ASSERT(wq); + + /* re-open output as stream */ + if (target->type == LOG_TGT_TYPE_STDERR) + target->tgt_file.out = stderr; + else + target->tgt_file.out = fopen(target->tgt_file.fname, "a"); + if (!target->tgt_file.out) { + return -EIO; + } + + /* synchronously write anything left in the queue */ + while (!llist_empty(&wq->msg_queue)) { + struct msgb *msg = msgb_dequeue(&wq->msg_queue); + fwrite(msgb_data(msg), msgb_length(msg), 1, target->tgt_file.out); + msgb_free(msg); + } + + /* now that everything succeeded, we can finally close the old output fd */ + if (target->type == LOG_TGT_TYPE_FILE) { + osmo_fd_unregister(&wq->bfd); + close(wq->bfd.fd); + wq->bfd.fd = -1; + } + + /* release the queue itself */ + talloc_free(wq); + target->tgt_file.wqueue = NULL; + target->output = _file_output_stream; + target->raw_output = NULL; + + return 0; +} + +/*! switch from blocking + buffered file output to non-blocking write-queue based output. + * \param[in] target log target which we should switch + * \return 0 on success; 1 if already switched before; negative on error + * Must be called with mutex osmo_log_tgt_mutex held, see log_tgt_mutex_lock. + */ +int log_target_file_switch_to_wqueue(struct log_target *target) +{ + struct osmo_wqueue *wq; + int rc; + + if (!target) + return -ENODEV; + + if (!target->tgt_file.out) { + /* target has already been switched over */ + return 1; + } + + /* we create a ~640kB sized talloc pool within the write-queue to ensure individual + * log lines (stored as msgbs) will not put result in malloc() calls, and also to + * reduce the OOM probability within logging, as the pool is already allocated */ + wq = talloc_pooled_object(target, struct osmo_wqueue, LOG_WQUEUE_LEN, + LOG_WQUEUE_LEN*(sizeof(struct msgb)+MAX_LOG_SIZE)); + if (!wq) + return -ENOMEM; + osmo_wqueue_init(wq, LOG_WQUEUE_LEN); + + fflush(target->tgt_file.out); + if (target->type == LOG_TGT_TYPE_FILE) { + rc = open(target->tgt_file.fname, O_WRONLY|O_APPEND|O_CREAT|O_NONBLOCK, 0660); + if (rc < 0) { + talloc_free(wq); + return -errno; + } + } else { + rc = STDERR_FILENO; + } + wq->bfd.fd = rc; + wq->bfd.when = OSMO_FD_WRITE; + wq->write_cb = _file_wq_write_cb; + + rc = osmo_fd_register(&wq->bfd); + if (rc < 0) { + talloc_free(wq); + return -EIO; + } + target->tgt_file.wqueue = wq; + target->raw_output = _file_raw_output; + target->output = NULL; + + /* now that everything succeeded, we can finally close the old output stream */ + if (target->type == LOG_TGT_TYPE_FILE) + fclose(target->tgt_file.out); + target->tgt_file.out = NULL; + + return 0; +} + +/*! Create a new file-based log target using non-blocking write_queue + * \param[in] fname File name of the new log file + * \returns Log target in case of success, NULL otherwise + */ +struct log_target *log_target_create_file(const char *fname) +{ + struct log_target *target; + struct osmo_wqueue *wq; + int rc; + + target = log_target_create(); + if (!target) + return NULL; + + target->type = LOG_TGT_TYPE_FILE; + /* we create a ~640kB sized talloc pool within the write-queue to ensure individual + * log lines (stored as msgbs) will not put result in malloc() calls, and also to + * reduce the OOM probability within logging, as the pool is already allocated */ + wq = talloc_pooled_object(target, struct osmo_wqueue, LOG_WQUEUE_LEN, + LOG_WQUEUE_LEN*(sizeof(struct msgb)+MAX_LOG_SIZE)); + if (!wq) { + log_target_destroy(target); + return NULL; + } + osmo_wqueue_init(wq, LOG_WQUEUE_LEN); + wq->bfd.fd = open(fname, O_WRONLY|O_APPEND|O_CREAT|O_NONBLOCK, 0660); + if (wq->bfd.fd < 0) { + talloc_free(wq); + log_target_destroy(target); + return NULL; + } + wq->bfd.when = OSMO_FD_WRITE; + wq->write_cb = _file_wq_write_cb; - target->output = _file_output; + rc = osmo_fd_register(&wq->bfd); + if (rc < 0) { + talloc_free(wq); + log_target_destroy(target); + return NULL; + } + target->tgt_file.wqueue = wq; + target->raw_output = _file_raw_output; target->tgt_file.fname = talloc_strdup(target, fname); return target; @@ -1013,7 +1365,7 @@ struct log_target *log_target_create_file(const char *fname) * \returns Log target (if found), NULL otherwise * Must be called with mutex osmo_log_tgt_mutex held, see log_tgt_mutex_lock. */ -struct log_target *log_target_find(int type, const char *fname) +struct log_target *log_target_find(enum log_target_type type, const char *fname) { struct log_target *tgt; @@ -1040,17 +1392,36 @@ struct log_target *log_target_find(int type, const char *fname) * \param[in] target log target to unregister, close and delete */ void log_target_destroy(struct log_target *target) { - /* just in case, to make sure we don't have any references */ log_del_target(target); #if (!EMBEDDED) + struct osmo_wqueue *wq; switch (target->type) { case LOG_TGT_TYPE_FILE: - if (target->tgt_file.out == NULL) - break; - fclose(target->tgt_file.out); - target->tgt_file.out = NULL; + case LOG_TGT_TYPE_STDERR: + if (target->tgt_file.out) { + if (target->type == LOG_TGT_TYPE_FILE) + fclose(target->tgt_file.out); + target->tgt_file.out = NULL; + } + wq = target->tgt_file.wqueue; + if (wq) { + if (wq->bfd.fd >= 0) { + osmo_fd_unregister(&wq->bfd); + if (target->type == LOG_TGT_TYPE_FILE) + close(wq->bfd.fd); + wq->bfd.fd = -1; + } + osmo_wqueue_clear(wq); + talloc_free(wq); + target->tgt_file.wqueue = NULL; + } + talloc_free((void *)target->tgt_file.fname); + target->tgt_file.fname = NULL; + break; + case LOG_TGT_TYPE_GSMTAP: + gsmtap_source_free(target->tgt_gsmtap.gsmtap_inst); break; #ifdef HAVE_SYSLOG_H case LOG_TGT_TYPE_SYSLOG: @@ -1071,13 +1442,33 @@ void log_target_destroy(struct log_target *target) * \returns 0 in case of success; negative otherwise */ int log_target_file_reopen(struct log_target *target) { - fclose(target->tgt_file.out); + struct osmo_wqueue *wq; + int rc; - target->tgt_file.out = fopen(target->tgt_file.fname, "a"); - if (!target->tgt_file.out) - return -errno; + OSMO_ASSERT(target->type == LOG_TGT_TYPE_FILE || target->type == LOG_TGT_TYPE_STDERR); + OSMO_ASSERT(target->tgt_file.out || target->tgt_file.wqueue); - /* we assume target->output already to be set */ + if (target->tgt_file.out) { + fclose(target->tgt_file.out); + target->tgt_file.out = fopen(target->tgt_file.fname, "a"); + if (!target->tgt_file.out) + return -errno; + } else { + wq = target->tgt_file.wqueue; + if (wq->bfd.fd >= 0) { + osmo_fd_unregister(&wq->bfd); + close(wq->bfd.fd); + wq->bfd.fd = -1; + } + + rc = open(target->tgt_file.fname, O_WRONLY|O_APPEND|O_CREAT|O_NONBLOCK, 0660); + if (rc < 0) + return -errno; + wq->bfd.fd = rc; + rc = osmo_fd_register(&wq->bfd); + if (rc < 0) + return rc; + } return 0; } @@ -1107,6 +1498,31 @@ int log_targets_reopen(void) return rc; } +/*! Enable the log level lookup cache to bypass string formatting and other code for log statements which are + * not actually enabled/needed by any existing log target. + * \retruns 0 in case of success, negative -errno in case of error. */ +int log_cache_enable(void) +{ +#if !defined(EMBEDDED) + if (log_level_lookup_cache) + return -EEXIST; + + log_level_lookup_cache = talloc_zero_array(osmo_log_info, uint8_t, osmo_log_info->num_cat); + if (!log_level_lookup_cache) + return -ENOMEM; + + /* copy everything for level lookup cache */ + log_tgt_mutex_lock(); + log_cache_update_all(); + log_tgt_mutex_unlock(); + + return 0; +#else + return -ENOTSUP; +#endif +} + + /*! Initialize the Osmocom logging core * \param[in] inf Information regarding logging categories, could be NULL * \param[in] ctx talloc context for logging allocations @@ -1200,6 +1616,11 @@ int log_check_level(int subsys, unsigned int level) subsys = map_subsys(subsys); +#if !defined(EMBEDDED) + if (!log_cache_check(subsys, level)) + return 0; +#endif + /* TODO: The following could/should be cached (update on config) */ log_tgt_mutex_lock(); diff --git a/src/logging_gsmtap.c b/src/core/logging_gsmtap.c index e190f88f..7775c27d 100644 --- a/src/logging_gsmtap.c +++ b/src/core/logging_gsmtap.c @@ -21,17 +21,13 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ /*! \addtogroup logging * @{ * \file logging_gsmtap.c */ -#include "../config.h" +#include "config.h" #include <stdarg.h> #include <stdlib.h> @@ -69,6 +65,7 @@ static void _gsmtap_raw_output(struct log_target *target, int subsys, struct timeval tv; int rc; const char *file_basename; + unsigned int _level; /* get timestamp ASAP */ osmo_gettimeofday(&tv, NULL); @@ -118,7 +115,11 @@ static void _gsmtap_raw_output(struct log_target *target, int subsys, } msgb_put(msg, rc); + /* Ensure that any error occurring when sending the log message doesn't cause infinite recursion */ + _level = target->loglevel; + target->loglevel = UINT8_MAX; rc = gsmtap_sendmsg(target->tgt_gsmtap.gsmtap_inst, msg); + target->loglevel = _level; if (rc) msgb_free(msg); } diff --git a/src/logging_syslog.c b/src/core/logging_syslog.c index f980689d..1153bdf4 100644 --- a/src/logging_syslog.c +++ b/src/core/logging_syslog.c @@ -16,17 +16,13 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ /*! \addtogroup logging * @{ * \file logging_syslog.c */ -#include "../config.h" +#include "config.h" #ifdef HAVE_SYSLOG_H diff --git a/src/logging_systemd.c b/src/core/logging_systemd.c index 7c966863..2e86feb6 100644 --- a/src/logging_systemd.c +++ b/src/core/logging_systemd.c @@ -14,10 +14,6 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ /*! \addtogroup logging diff --git a/src/loggingrb.c b/src/core/loggingrb.c index 4a80cc8a..2bf7b665 100644 --- a/src/loggingrb.c +++ b/src/core/loggingrb.c @@ -16,10 +16,6 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ /*! \addtogroup loggingrb diff --git a/src/macaddr.c b/src/core/macaddr.c index 56fdf86e..3b231fb8 100644 --- a/src/macaddr.c +++ b/src/core/macaddr.c @@ -18,10 +18,6 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ /*! \addtogroup utils diff --git a/src/mnl.c b/src/core/mnl.c index a7d3511e..d148e1b3 100644 --- a/src/mnl.c +++ b/src/core/mnl.c @@ -8,7 +8,7 @@ /* * (C) 2020 by Harald Welte <laforge@gnumonks.org> - * All Rights Reserverd. + * All Rights Reserved. * * SPDX-License-Identifier: GPL-2.0+ * @@ -21,11 +21,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, - * MA 02110-1301, USA. */ #include <osmocom/core/select.h> @@ -59,7 +54,7 @@ static int osmo_mnl_fd_cb(struct osmo_fd *ofd, unsigned int what) } /*! create an osmocom-wrapped limnl netlink socket. - * \parma[in] ctx talloc context from which to allocate + * \param[in] ctx talloc context from which to allocate * \param[in] bus netlink socket bus ID (see NETLINK_* constants) * \param[in] groups groups of messages to bind/subscribe to * \param[in] mnl_cb callback function called for each incoming message diff --git a/src/msgb.c b/src/core/msgb.c index fbb157ae..713510c6 100644 --- a/src/msgb.c +++ b/src/core/msgb.c @@ -14,10 +14,6 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ /*! \addtogroup msgb @@ -318,24 +314,29 @@ void *msgb_talloc_ctx_init(void *root_ctx, unsigned int pool_size) return tall_msgb_ctx; } -/*! Copy an msgb. +/*! Copy an msgb with memory reallocation. * - * This function allocates a new msgb, copies the data buffer of msg, - * and adjusts the pointers (incl l1h-l4h) accordingly. The cb part - * is not copied. + * This function allocates a new msgb with new_len size, copies the data buffer of msg, + * and adjusts the pointers (incl l1h-l4h) accordingly. The cb part is not copied. + * \param[in] ctx talloc context on which allocation happens * \param[in] msg The old msgb object - * \param[in] name Human-readable name to be associated with msgb + * \param[in] new_len The length of new msgb object + * \param[in] name Human-readable name to be associated with new msgb */ -struct msgb *msgb_copy_c(const void *ctx, const struct msgb *msg, const char *name) +struct msgb *msgb_copy_resize_c(const void *ctx, const struct msgb *msg, uint16_t new_len, const char *name) { struct msgb *new_msg; - new_msg = msgb_alloc_c(ctx, msg->data_len, name); - if (!new_msg) + if (new_len < msgb_length(msg)) { + LOGP(DLGLOBAL, LOGL_ERROR, + "Data from old msgb (%u bytes) won't fit into new msgb (%u bytes) after reallocation\n", + msgb_length(msg), new_len); return NULL; + } - /* copy data */ - memcpy(new_msg->_data, msg->_data, new_msg->data_len); + new_msg = msgb_alloc_c(ctx, new_len, name); + if (!new_msg) + return NULL; /* copy header */ new_msg->len = msg->len; @@ -343,6 +344,9 @@ struct msgb *msgb_copy_c(const void *ctx, const struct msgb *msg, const char *na new_msg->head += msg->head - msg->_data; new_msg->tail += msg->tail - msg->_data; + /* copy data */ + memcpy(new_msg->data, msg->data, msgb_length(msg)); + if (msg->l1h) new_msg->l1h = new_msg->_data + (msg->l1h - msg->_data); if (msg->l2h) @@ -355,6 +359,32 @@ struct msgb *msgb_copy_c(const void *ctx, const struct msgb *msg, const char *na return new_msg; } +/*! Copy an msgb with memory reallocation. + * + * This function allocates a new msgb with new_len size, copies the data buffer of msg, + * and adjusts the pointers (incl l1h-l4h) accordingly. The cb part is not copied. + * \param[in] msg The old msgb object + * \param[in] name Human-readable name to be associated with new msgb + */ +struct msgb *msgb_copy_resize(const struct msgb *msg, uint16_t new_len, const char *name) +{ + return msgb_copy_resize_c(tall_msgb_ctx, msg, new_len, name); +} + +/*! Copy an msgb. + * + * This function allocates a new msgb, copies the data buffer of msg, + * and adjusts the pointers (incl l1h-l4h) accordingly. The cb part + * is not copied. + * \param[in] ctx talloc context on which allocation happens + * \param[in] msg The old msgb object + * \param[in] name Human-readable name to be associated with msgb + */ +struct msgb *msgb_copy_c(const void *ctx, const struct msgb *msg, const char *name) +{ + return msgb_copy_resize_c(ctx, msg, msg->data_len, name); +} + /*! Copy an msgb. * * This function allocates a new msgb, copies the data buffer of msg, @@ -430,11 +460,11 @@ int msgb_resize_area(struct msgb *msg, uint8_t *area, */ char *msgb_hexdump_buf(char *buf, size_t buf_len, const struct msgb *msg) { - int buf_offs = 0; + unsigned int buf_offs = 0; int nchars; const unsigned char *start = msg->data; const unsigned char *lxhs[4]; - int i; + unsigned int i; lxhs[0] = msg->l1h; lxhs[1] = msg->l2h; diff --git a/src/msgfile.c b/src/core/msgfile.c index 1f11aa60..abb4e7cf 100644 --- a/src/msgfile.c +++ b/src/core/msgfile.c @@ -17,10 +17,6 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ #define _WITH_GETLINE diff --git a/src/core/netdev.c b/src/core/netdev.c new file mode 100644 index 00000000..318134af --- /dev/null +++ b/src/core/netdev.c @@ -0,0 +1,962 @@ + +/* network device (interface) functions. + * (C) 2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * Author: Pau Espin Pedrol <pespin@sysmocom.de> + * + * All Rights Reserved + * + * SPDX-License-Identifier: GPL-2.0+ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include "config.h" + +/*! \addtogroup netdev + * @{ + * network device (interface) convenience functions + * + * \file netdev.c + * + * Example lifecycle use of the API: + * + * struct osmo_sockaddr_str osa_str = {}; + * struct osmo_sockaddr osa = {}; + * + * // Allocate object: + * struct osmo_netdev *netdev = osmo_netdev_alloc(parent_talloc_ctx, name); + * OSMO_ASSERT(netdev); + * + * // Configure object (before registration): + * rc = osmo_netdev_set_netns_name(netdev, "some_netns_name_or_null"); + * rc = osmo_netdev_set_ifindex(netdev, if_nametoindex("eth0")); + * + * // Register object: + * rc = osmo_netdev_register(netdev); + * // The network interface is now being monitored and the network interface + * // can be operated (see below) + * + * // Add a local IPv4 address: + * rc = osmo_sockaddr_str_from_str2(&osa_str, "192.168.200.1"); + * rc = osmo_sockaddr_str_to_sockaddr(&osa_str, &osa.u.sas); + * rc = osmo_netdev_add_addr(netdev, &osa, 24); + * + * // Bring network interface up: + * rc = osmo_netdev_ifupdown(netdev, true); + * + * // Add default route (0.0.0.0/0): + * rc = osmo_sockaddr_str_from_str2(&osa_str, "0.0.0.0"); + * rc = osmo_sockaddr_str_to_sockaddr(&osa_str, &osa.u.sas); + * rc = osmo_netdev_add_route(netdev, &osa, 0, NULL); + * + * // Unregister (can be freed directly too): + * rc = osmo_netdev_unregister(netdev); + * // Free the object: + * osmo_netdev_free(netdev); + */ + +#if (!EMBEDDED) + +#include <stdio.h> +#include <stdlib.h> +#include <stdio.h> +#include <fcntl.h> +#include <unistd.h> +#include <errno.h> +#include <ifaddrs.h> +#include <sys/types.h> +#include <sys/time.h> +#include <sys/ioctl.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <net/if.h> +#include <net/route.h> + +#if defined(__linux__) +#include <linux/if_link.h> +#include <linux/rtnetlink.h> +#else +#error "Unknown platform!" +#endif + +#include <osmocom/core/utils.h> +#include <osmocom/core/select.h> +#include <osmocom/core/linuxlist.h> +#include <osmocom/core/logging.h> +#include <osmocom/core/socket.h> +#include <osmocom/core/netns.h> +#include <osmocom/core/netdev.h> + +#if ENABLE_LIBMNL +#include <osmocom/core/mnl.h> +#endif + +#define IFINDEX_UNUSED 0 + +#define LOGNETDEV(netdev, lvl, fmt, args ...) \ + LOGP(DLGLOBAL, lvl, "NETDEV(%s,if=%s/%u,ns=%s): " fmt, \ + (netdev)->name, osmo_netdev_get_dev_name(netdev) ? : "", \ + (netdev)->ifindex, (netdev)->netns_name ? : "", ## args) + +static struct llist_head g_netdev_netns_ctx_list = LLIST_HEAD_INIT(g_netdev_netns_ctx_list); +static struct llist_head g_netdev_list = LLIST_HEAD_INIT(g_netdev_list); + +/* One per netns, shared by all osmo_netdev in a given netns: */ +struct netdev_netns_ctx { + struct llist_head entry; /* entry in g_netdev_netns_ctx_list */ + unsigned int refcount; /* Number of osmo_netdev currently registered on this netns */ + const char *netns_name; /* default netns has empty string "" (never NULL!) */ + int netns_fd; /* FD to the netns with name "netns_name" above */ +#if ENABLE_LIBMNL + struct osmo_mnl *omnl; +#endif +}; + +static struct netdev_netns_ctx *netdev_netns_ctx_alloc(void *ctx, const char *netns_name) +{ + struct netdev_netns_ctx *netns_ctx; + OSMO_ASSERT(netns_name); + + netns_ctx = talloc_zero(ctx, struct netdev_netns_ctx); + if (!netns_ctx) + return NULL; + + netns_ctx->netns_name = talloc_strdup(netns_ctx, netns_name); + netns_ctx->netns_fd = -1; + + llist_add_tail(&netns_ctx->entry, &g_netdev_netns_ctx_list); + return netns_ctx; + +} + +static void netdev_netns_ctx_free(struct netdev_netns_ctx *netns_ctx) +{ + if (!netns_ctx) + return; + + llist_del(&netns_ctx->entry); + +#if ENABLE_LIBMNL + if (netns_ctx->omnl) { + osmo_mnl_destroy(netns_ctx->omnl); + netns_ctx->omnl = NULL; + } +#endif + + if (netns_ctx->netns_fd != -1) { + close(netns_ctx->netns_fd); + netns_ctx->netns_fd = -1; + } + talloc_free(netns_ctx); +} + +#if ENABLE_LIBMNL +static int netdev_netns_ctx_mnl_cb(const struct nlmsghdr *nlh, void *data); +#endif + +static int netdev_netns_ctx_init(struct netdev_netns_ctx *netns_ctx) +{ + struct osmo_netns_switch_state switch_state; + int rc; + + if (netns_ctx->netns_name[0] != '\0') { + LOGP(DLGLOBAL, LOGL_INFO, "Prepare netns: Switch to netns '%s'\n", netns_ctx->netns_name); + netns_ctx->netns_fd = osmo_netns_open_fd(netns_ctx->netns_name); + if (netns_ctx->netns_fd < 0) { + LOGP(DLGLOBAL, LOGL_ERROR, "Prepare netns: Cannot switch to netns '%s': %s (%d)\n", + netns_ctx->netns_name, strerror(errno), errno); + return netns_ctx->netns_fd; + } + + /* temporarily switch to specified namespace to create netlink socket */ + rc = osmo_netns_switch_enter(netns_ctx->netns_fd, &switch_state); + if (rc < 0) { + LOGP(DLGLOBAL, LOGL_ERROR, "Prepare netns: Cannot switch to netns '%s': %s (%d)\n", + netns_ctx->netns_name, strerror(errno), errno); + /* netns_ctx->netns_fd will be freed by future call to netdev_netns_ctx_free() */ + return rc; + } + } + +#if ENABLE_LIBMNL + netns_ctx->omnl = osmo_mnl_init(NULL, NETLINK_ROUTE, RTMGRP_LINK, netdev_netns_ctx_mnl_cb, netns_ctx); + rc = (netns_ctx->omnl ? 0 : -EFAULT); +#else + rc = 0; +#endif + + /* switch back to default namespace */ + if (netns_ctx->netns_name[0] != '\0') { + int rc2 = osmo_netns_switch_exit(&switch_state); + if (rc2 < 0) { + LOGP(DLGLOBAL, LOGL_ERROR, "Prepare netns: Cannot switch back from netns '%s': %s\n", + netns_ctx->netns_name, strerror(errno)); + return rc2; + } + LOGP(DLGLOBAL, LOGL_INFO, "Prepare netns: Back from netns '%s'\n", + netns_ctx->netns_name); + } + return rc; +} + +static struct netdev_netns_ctx *netdev_netns_ctx_find_by_netns_name(const char *netns_name) +{ + struct netdev_netns_ctx *netns_ctx; + + llist_for_each_entry(netns_ctx, &g_netdev_netns_ctx_list, entry) { + if (strcmp(netns_ctx->netns_name, netns_name)) + continue; + return netns_ctx; + } + + return NULL; +} + +static struct netdev_netns_ctx *netdev_netns_ctx_get(const char *netns_name) +{ + struct netdev_netns_ctx *netns_ctx; + int rc; + + OSMO_ASSERT(netns_name); + netns_ctx = netdev_netns_ctx_find_by_netns_name(netns_name); + if (!netns_ctx) { + netns_ctx = netdev_netns_ctx_alloc(NULL, netns_name); + if (!netns_ctx) + return NULL; + rc = netdev_netns_ctx_init(netns_ctx); + if (rc < 0) { + netdev_netns_ctx_free(netns_ctx); + return NULL; + } + } + netns_ctx->refcount++; + return netns_ctx; +} + +static void netdev_netns_ctx_put(struct netdev_netns_ctx *netns_ctx) +{ + OSMO_ASSERT(netns_ctx); + netns_ctx->refcount--; + + if (netns_ctx->refcount == 0) + netdev_netns_ctx_free(netns_ctx); +} + +struct osmo_netdev { + /* entry in g_netdev_list */ + struct llist_head entry; + + /* Pointer to struct shared (refcounted) by all osmo_netdev in the same netns: */ + struct netdev_netns_ctx *netns_ctx; + + /* Name used to identify the osmo_netdev */ + char *name; + + /* ifindex of the network interface (address space is per netns) */ + unsigned int ifindex; + + /* Network interface name. Can change over lifetime of the interface. */ + char *dev_name; + + /* netns name where the netdev interface is created (NULL = default netns) */ + char *netns_name; + + /* API user private data */ + void *priv_data; + + /* Whether the netdev is in operation (managing the netdev interface) */ + bool registered; + + /* Called by netdev each time a new up/down state change is detected. Can be NULL. */ + osmo_netdev_ifupdown_ind_cb_t ifupdown_ind_cb; + + /* Called by netdev each time the registered network interface is renamed by the system. Can be NULL. */ + osmo_netdev_dev_name_chg_cb_t dev_name_chg_cb; + + /* Called by netdev each time the configured MTU changes in registered network interface. Can be NULL. */ + osmo_netdev_mtu_chg_cb_t mtu_chg_cb; + + /* Whether the netdev interface is UP */ + bool if_up; + /* Whether we know the interface updown state (aka if if_up holds information)*/ + bool if_up_known; + + /* The netdev interface MTU size */ + uint32_t if_mtu; + /* Whether we know the interface MTU size (aka if if_mtu holds information)*/ + bool if_mtu_known; +}; + +#define NETDEV_NETNS_ENTER(netdev, switch_state, str_prefix) \ + do { \ + if ((netdev)->netns_name) { \ + LOGNETDEV(netdev, LOGL_DEBUG, str_prefix ": Switch to netns '%s'\n", \ + (netdev)->netns_name); \ + int rc2 = osmo_netns_switch_enter((netdev)->netns_ctx->netns_fd, switch_state); \ + if (rc2 < 0) { \ + LOGNETDEV(netdev, LOGL_ERROR, str_prefix ": Cannot switch to netns '%s': %s (%d)\n", \ + (netdev)->netns_name, strerror(errno), errno); \ + return -EACCES; \ + } \ + } \ + } while (0) + +#define NETDEV_NETNS_EXIT(netdev, switch_state, str_prefix) \ + do { \ + if ((netdev)->netns_name) { \ + int rc2 = osmo_netns_switch_exit(switch_state); \ + if (rc2 < 0) { \ + LOGNETDEV(netdev, LOGL_ERROR, str_prefix ": Cannot switch back from netns '%s': %s\n", \ + (netdev)->netns_name, strerror(errno)); \ + return rc2; \ + } \ + LOGNETDEV(netdev, LOGL_DEBUG, str_prefix ": Back from netns '%s'\n", \ + (netdev)->netns_name); \ + } \ + } while (0) + +#if ENABLE_LIBMNL +/* validate the netlink attributes */ +static int netdev_mnl_data_attr_cb(const struct nlattr *attr, void *data) +{ + const struct nlattr **tb = data; + int type = mnl_attr_get_type(attr); + + if (mnl_attr_type_valid(attr, IFLA_MAX) < 0) + return MNL_CB_OK; + + switch (type) { + case IFLA_ADDRESS: + if (mnl_attr_validate(attr, MNL_TYPE_BINARY) < 0) + return MNL_CB_ERROR; + break; + case IFLA_MTU: + if (mnl_attr_validate(attr, MNL_TYPE_U32) < 0) + return MNL_CB_ERROR; + break; + case IFLA_IFNAME: + if (mnl_attr_validate(attr, MNL_TYPE_STRING) < 0) + return MNL_CB_ERROR; + break; + } + tb[type] = attr; + return MNL_CB_OK; +} + +static void netdev_mnl_check_mtu_change(struct osmo_netdev *netdev, uint32_t mtu) +{ + if (netdev->if_mtu_known && netdev->if_mtu == mtu) + return; + + LOGNETDEV(netdev, LOGL_NOTICE, "MTU changed: %u\n", mtu); + if (netdev->mtu_chg_cb) + netdev->mtu_chg_cb(netdev, mtu); + + netdev->if_mtu_known = true; + netdev->if_mtu = mtu; +} + +static void netdev_mnl_check_link_state_change(struct osmo_netdev *netdev, bool if_up) +{ + if (netdev->if_up_known && netdev->if_up == if_up) + return; + + LOGNETDEV(netdev, LOGL_NOTICE, "Physical link state changed: %s\n", + if_up ? "UP" : "DOWN"); + if (netdev->ifupdown_ind_cb) + netdev->ifupdown_ind_cb(netdev, if_up); + + netdev->if_up_known = true; + netdev->if_up = if_up; +} + +static int netdev_mnl_cb(struct osmo_netdev *netdev, struct ifinfomsg *ifm, struct nlattr **tb) +{ + char ifnamebuf[IF_NAMESIZE]; + const char *ifname = NULL; + bool if_running; + + if (tb[IFLA_IFNAME]) { + ifname = mnl_attr_get_str(tb[IFLA_IFNAME]); + LOGNETDEV(netdev, LOGL_DEBUG, "%s(): ifname=%s\n", __func__, ifname); + } else { + /* Try harder to obtain the ifname. This code path should in + * general not be triggered since usually IFLA_IFNAME is there */ + struct osmo_netns_switch_state switch_state; + NETDEV_NETNS_ENTER(netdev, &switch_state, "if_indextoname"); + ifname = if_indextoname(ifm->ifi_index, ifnamebuf); + NETDEV_NETNS_EXIT(netdev, &switch_state, "if_indextoname"); + } + if (ifname) { + /* Update dev_name if it changed: */ + if (strcmp(netdev->dev_name, ifname) != 0) { + if (netdev->dev_name_chg_cb) + netdev->dev_name_chg_cb(netdev, ifname); + osmo_talloc_replace_string(netdev, &netdev->dev_name, ifname); + } + } + + if (tb[IFLA_MTU]) { + uint32_t mtu = mnl_attr_get_u32(tb[IFLA_MTU]); + LOGNETDEV(netdev, LOGL_DEBUG, "%s(): mtu=%u\n", __func__, mtu); + netdev_mnl_check_mtu_change(netdev, mtu); + } + if (tb[IFLA_ADDRESS]) { + uint8_t *hwaddr = mnl_attr_get_payload(tb[IFLA_ADDRESS]); + uint16_t hwaddr_len = mnl_attr_get_payload_len(tb[IFLA_ADDRESS]); + LOGNETDEV(netdev, LOGL_DEBUG, "%s(): hwaddress=%s\n", + __func__, osmo_hexdump(hwaddr, hwaddr_len)); + } + + if_running = !!(ifm->ifi_flags & IFF_RUNNING); + LOGNETDEV(netdev, LOGL_DEBUG, "%s(): up=%u running=%u\n", + __func__, !!(ifm->ifi_flags & IFF_UP), if_running); + netdev_mnl_check_link_state_change(netdev, if_running); + + return MNL_CB_OK; +} + +static int netdev_netns_ctx_mnl_cb(const struct nlmsghdr *nlh, void *data) +{ + struct osmo_mnl *omnl = data; + struct netdev_netns_ctx *netns_ctx = (struct netdev_netns_ctx *)omnl->priv; + struct nlattr *tb[IFLA_MAX+1] = {}; + struct ifinfomsg *ifm = mnl_nlmsg_get_payload(nlh); + struct osmo_netdev *netdev; + + OSMO_ASSERT(omnl); + OSMO_ASSERT(ifm); + + mnl_attr_parse(nlh, sizeof(*ifm), netdev_mnl_data_attr_cb, tb); + + LOGP(DLGLOBAL, LOGL_DEBUG, + "%s(): index=%d type=%d flags=0x%x family=%d\n", __func__, + ifm->ifi_index, ifm->ifi_type, ifm->ifi_flags, ifm->ifi_family); + + if (ifm->ifi_index == IFINDEX_UNUSED) + return MNL_CB_ERROR; + + /* Find the netdev (if any) using key <netns,ifindex>. + * Different users of the API may have its own osmo_netdev object + * tracking potentially same netif, hence we need to iterate the whole list + * and dispatch to all matches: + */ + bool found_any = false; + llist_for_each_entry(netdev, &g_netdev_list, entry) { + if (!netdev->registered) + continue; + if (netdev->ifindex != ifm->ifi_index) + continue; + if (strcmp(netdev->netns_ctx->netns_name, netns_ctx->netns_name)) + continue; + found_any = true; + netdev_mnl_cb(netdev, ifm, &tb[0]); + } + + if (!found_any) { + LOGP(DLGLOBAL, LOGL_DEBUG, "%s(): device with ifindex %u on netns %s not registered\n", __func__, + ifm->ifi_index, netns_ctx->netns_name); + } + return MNL_CB_OK; +} + +/* Trigger an initial dump of the iface to get link information */ +static int netdev_mnl_request_initial_dump(struct osmo_mnl *omnl, unsigned int if_index) +{ + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh = mnl_nlmsg_put_header(buf); + struct ifinfomsg *ifm; + + nlh->nlmsg_type = RTM_GETLINK; + nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; + nlh->nlmsg_seq = time(NULL); + + ifm = mnl_nlmsg_put_extra_header(nlh, sizeof(*ifm)); + ifm->ifi_family = AF_UNSPEC; + ifm->ifi_index = if_index; + + if (mnl_socket_sendto(omnl->mnls, nlh, nlh->nlmsg_len) < 0) { + LOGP(DLGLOBAL, LOGL_ERROR, "mnl_socket_sendto\n"); + return -EIO; + } + + return 0; +} + +static int netdev_mnl_set_ifupdown(struct osmo_mnl *omnl, unsigned int if_index, + const char *dev_name, bool up) +{ + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh = mnl_nlmsg_put_header(buf); + struct ifinfomsg *ifm; + unsigned int change = 0; + unsigned int flags = 0; + + if (up) { + change |= IFF_UP; + flags |= IFF_UP; + } else { + change |= IFF_UP; + flags &= ~IFF_UP; + } + + nlh->nlmsg_type = RTM_NEWLINK; + nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; + nlh->nlmsg_seq = time(NULL); + + ifm = mnl_nlmsg_put_extra_header(nlh, sizeof(*ifm)); + ifm->ifi_family = AF_UNSPEC; + ifm->ifi_change = change; + ifm->ifi_flags = flags; + ifm->ifi_index = if_index; + + if (dev_name) + mnl_attr_put_str(nlh, IFLA_IFNAME, dev_name); + + if (mnl_socket_sendto(omnl->mnls, nlh, nlh->nlmsg_len) < 0) { + LOGP(DLGLOBAL, LOGL_ERROR, "mnl_socket_sendto\n"); + return -EIO; + } + + return 0; +} + +static int netdev_mnl_add_addr(struct osmo_mnl *omnl, unsigned int if_index, const struct osmo_sockaddr *osa, uint8_t prefix) +{ + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh = mnl_nlmsg_put_header(buf); + struct ifaddrmsg *ifm; + + nlh->nlmsg_type = RTM_NEWADDR; + nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_REPLACE | NLM_F_ACK; + nlh->nlmsg_seq = time(NULL); + + ifm = mnl_nlmsg_put_extra_header(nlh, sizeof(*ifm)); + ifm->ifa_family = osa->u.sa.sa_family; + ifm->ifa_prefixlen = prefix; + ifm->ifa_flags = IFA_F_PERMANENT; + ifm->ifa_scope = RT_SCOPE_UNIVERSE; + ifm->ifa_index = if_index; + + /* + * The exact meaning of IFA_LOCAL and IFA_ADDRESS depend + * on the address family being used and the device type. + * For broadcast devices (like the interfaces we use), + * for IPv4 we specify both and they are used interchangeably. + * For IPv6, only IFA_ADDRESS needs to be set. + */ + switch (osa->u.sa.sa_family) { + case AF_INET: + mnl_attr_put_u32(nlh, IFA_LOCAL, osa->u.sin.sin_addr.s_addr); + mnl_attr_put_u32(nlh, IFA_ADDRESS, osa->u.sin.sin_addr.s_addr); + break; + case AF_INET6: + mnl_attr_put(nlh, IFA_ADDRESS, sizeof(struct in6_addr), &osa->u.sin6.sin6_addr); + break; + default: + return -EINVAL; + } + + if (mnl_socket_sendto(omnl->mnls, nlh, nlh->nlmsg_len) < 0) { + LOGP(DLGLOBAL, LOGL_ERROR, "mnl_socket_sendto\n"); + return -EIO; + } + + return 0; +} + +static int netdev_mnl_add_route(struct osmo_mnl *omnl, + unsigned int if_index, + const struct osmo_sockaddr *dst_osa, + uint8_t dst_prefix, + const struct osmo_sockaddr *gw_osa) +{ + char buf[MNL_SOCKET_BUFFER_SIZE]; + struct nlmsghdr *nlh = mnl_nlmsg_put_header(buf); + struct rtmsg *rtm; + + nlh->nlmsg_type = RTM_NEWROUTE; + nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_ACK; + nlh->nlmsg_seq = time(NULL); + + rtm = mnl_nlmsg_put_extra_header(nlh, sizeof(*rtm)); + rtm->rtm_family = dst_osa->u.sa.sa_family; + rtm->rtm_dst_len = dst_prefix; + rtm->rtm_src_len = 0; + rtm->rtm_tos = 0; + rtm->rtm_protocol = RTPROT_STATIC; + rtm->rtm_table = RT_TABLE_MAIN; + rtm->rtm_type = RTN_UNICAST; + rtm->rtm_scope = gw_osa ? RT_SCOPE_UNIVERSE : RT_SCOPE_LINK; + rtm->rtm_flags = 0; + + switch (dst_osa->u.sa.sa_family) { + case AF_INET: + mnl_attr_put_u32(nlh, RTA_DST, dst_osa->u.sin.sin_addr.s_addr); + break; + case AF_INET6: + mnl_attr_put(nlh, RTA_DST, sizeof(struct in6_addr), &dst_osa->u.sin6.sin6_addr); + break; + default: + return -EINVAL; + } + + mnl_attr_put_u32(nlh, RTA_OIF, if_index); + + if (gw_osa) { + switch (gw_osa->u.sa.sa_family) { + case AF_INET: + mnl_attr_put_u32(nlh, RTA_GATEWAY, gw_osa->u.sin.sin_addr.s_addr); + break; + case AF_INET6: + mnl_attr_put(nlh, RTA_GATEWAY, sizeof(struct in6_addr), &gw_osa->u.sin6.sin6_addr); + break; + default: + return -EINVAL; + } + } + + if (mnl_socket_sendto(omnl->mnls, nlh, nlh->nlmsg_len) < 0) { + LOGP(DLGLOBAL, LOGL_ERROR, "mnl_socket_sendto\n"); + return -EIO; + } + + return 0; +} +#endif /* if ENABLE_LIBMNL */ + +/*! Allocate a new netdev object. + * \param[in] ctx talloc context to use as a parent when allocating the netdev object + * \param[in] name A name providen to identify the netdev object + * \returns newly allocated netdev object on success; NULL on error + */ +struct osmo_netdev *osmo_netdev_alloc(void *ctx, const char *name) +{ + struct osmo_netdev *netdev; + + netdev = talloc_zero(ctx, struct osmo_netdev); + if (!netdev) + return NULL; + + netdev->name = talloc_strdup(netdev, name); + + llist_add_tail(&netdev->entry, &g_netdev_list); + return netdev; +} + +/*! Free an allocated netdev object. + * \param[in] netdev The netdev object to free + */ +void osmo_netdev_free(struct osmo_netdev *netdev) +{ + if (!netdev) + return; + if (osmo_netdev_is_registered(netdev)) + osmo_netdev_unregister(netdev); + llist_del(&netdev->entry); + talloc_free(netdev); +} + +/*! Start managing the network device referenced by the netdev object. + * \param[in] netdev The netdev object to open + * \returns 0 on success; negative on error + */ +int osmo_netdev_register(struct osmo_netdev *netdev) +{ + char ifnamebuf[IF_NAMESIZE]; + struct osmo_netns_switch_state switch_state; + int rc = 0; + + if (netdev->registered) + return -EALREADY; + + netdev->netns_ctx = netdev_netns_ctx_get(netdev->netns_name ? : ""); + if (!netdev->netns_ctx) + return -EFAULT; + + NETDEV_NETNS_ENTER(netdev, &switch_state, "register"); + + if (!if_indextoname(netdev->ifindex, ifnamebuf)) { + rc = -ENODEV; + goto err_put_exit; + } + osmo_talloc_replace_string(netdev, &netdev->dev_name, ifnamebuf); + +#if ENABLE_LIBMNL + rc = netdev_mnl_request_initial_dump(netdev->netns_ctx->omnl, netdev->ifindex); +#endif + + NETDEV_NETNS_EXIT(netdev, &switch_state, "register"); + + netdev->registered = true; + return rc; + +err_put_exit: + NETDEV_NETNS_EXIT(netdev, &switch_state, "register"); + netdev_netns_ctx_put(netdev->netns_ctx); + return rc; +} + +/*! Unregister the netdev object (stop managing /moniutoring the interface) + * \param[in] netdev The netdev object to close + * \returns 0 on success; negative on error + */ +int osmo_netdev_unregister(struct osmo_netdev *netdev) +{ + if (!netdev->registered) + return -EALREADY; + + netdev->if_up_known = false; + netdev->if_mtu_known = false; + + netdev_netns_ctx_put(netdev->netns_ctx); + netdev->registered = false; + return 0; +} + +/*! Retrieve whether the netdev object is in "registered" state. + * \param[in] netdev The netdev object to check + * \returns true if in state "registered"; false otherwise + */ +bool osmo_netdev_is_registered(struct osmo_netdev *netdev) +{ + return netdev->registered; +} + +/*! Set private user data pointer on the netdev object. + * \param[in] netdev The netdev object where the field is set + */ +void osmo_netdev_set_priv_data(struct osmo_netdev *netdev, void *priv_data) +{ + netdev->priv_data = priv_data; +} + +/*! Get private user data pointer from the netdev object. + * \param[in] netdev The netdev object from where to retrieve the field + * \returns The current value of the priv_data field. + */ +void *osmo_netdev_get_priv_data(struct osmo_netdev *netdev) +{ + return netdev->priv_data; +} + +/*! Set data_ind_cb callback, called when a new packet is received on the network interface. + * \param[in] netdev The netdev object where the field is set + * \param[in] data_ind_cb the user provided function to be called when the link status (UP/DOWN) changes + */ +void osmo_netdev_set_ifupdown_ind_cb(struct osmo_netdev *netdev, osmo_netdev_ifupdown_ind_cb_t ifupdown_ind_cb) +{ + netdev->ifupdown_ind_cb = ifupdown_ind_cb; +} + +/*! Set dev_name_chg_cb callback, called when a change in the network name is detected + * \param[in] netdev The netdev object where the field is set + * \param[in] dev_name_chg_cb the user provided function to be called when a the interface is renamed + */ +void osmo_netdev_set_dev_name_chg_cb(struct osmo_netdev *netdev, osmo_netdev_dev_name_chg_cb_t dev_name_chg_cb) +{ + netdev->dev_name_chg_cb = dev_name_chg_cb; +} + +/*! Set mtu_chg_cb callback, called when a change in the network name is detected + * \param[in] netdev The netdev object where the field is set + * \param[in] mtu_chg_cb the user provided function to be called when the configured MTU at the interface changes + */ +void osmo_netdev_set_mtu_chg_cb(struct osmo_netdev *netdev, osmo_netdev_mtu_chg_cb_t mtu_chg_cb) +{ + netdev->mtu_chg_cb = mtu_chg_cb; +} + +/*! Get name used to identify the netdev object. + * \param[in] netdev The netdev object from where to retrieve the field + * \returns The current value of the name used to identify the netdev object + */ +const char *osmo_netdev_get_name(const struct osmo_netdev *netdev) +{ + return netdev->name; +} + +/*! Set (specify) interface index identifying the network interface to manage + * \param[in] netdev The netdev object where the field is set + * \param[in] ifindex The interface index identifying the interface + * \returns 0 on success; negative on error + * + * The ifindex, together with the netns_name (see + * osmo_netdev_netns_name_set()), form together the key identifiers of a + * network interface to manage. + * This field is used during osmo_netdev_register() time, and hence must be set + * before calling that API, and cannot be changed when the netdev object is in + * "registered" state. + */ +int osmo_netdev_set_ifindex(struct osmo_netdev *netdev, unsigned int ifindex) +{ + if (netdev->registered) + return -EALREADY; + netdev->ifindex = ifindex; + return 0; +} + +/*! Get interface index identifying the interface managed by netdev + * \param[in] netdev The netdev object from where to retrieve the field + * \returns The current value of the configured netdev interface ifindex to use (0 = unset) + */ +unsigned int osmo_netdev_get_ifindex(const struct osmo_netdev *netdev) +{ + return netdev->ifindex; +} + +/*! Set (specify) name of the network namespace where the network interface to manage is located + * \param[in] netdev The netdev object where the field is set + * \param[in] netns_name The network namespace where the network interface is located + * \returns 0 on success; negative on error + * + * The netns_name, together with the ifindex (see + * osmo_netdev_ifindex_set()), form together the key identifiers of a + * network interface to manage. + * This field is used during osmo_netdev_register() time, and hence must be set + * before calling that API, and cannot be changed when the netdev object is in + * "registered" state. + * If left as NULL (default), the management will be done in the current network namespace. + */ +int osmo_netdev_set_netns_name(struct osmo_netdev *netdev, const char *netns_name) +{ + if (netdev->registered) + return -EALREADY; + osmo_talloc_replace_string(netdev, &netdev->netns_name, netns_name); + return 0; +} + +/*! Get name of network namespace used when opening the netdev interface + * \param[in] netdev The netdev object from where to retrieve the field + * \returns The current value of the configured network namespace + */ +const char *osmo_netdev_get_netns_name(const struct osmo_netdev *netdev) +{ + return netdev->netns_name; +} + +/*! Get name used to name the network interface created by the netdev object + * \param[in] netdev The netdev object from where to retrieve the field + * \returns The interface name (or NULL if unknown) + * + * This information is retrieved internally once the netdev object enters the + * "registered" state. Hence, when not registered NULL can be returned. + */ +const char *osmo_netdev_get_dev_name(const struct osmo_netdev *netdev) +{ + return netdev->dev_name; +} + +/*! Bring netdev interface UP or DOWN. + * \param[in] netdev The netdev object managing the netdev interface + * \param[in] ifupdown true to set the interface UP, false to set it DOWN + * \returns 0 on succes; negative on error. + */ +int osmo_netdev_ifupdown(struct osmo_netdev *netdev, bool ifupdown) +{ + struct osmo_netns_switch_state switch_state; + int rc; + + if (!netdev->registered) + return -ENODEV; + + LOGNETDEV(netdev, LOGL_NOTICE, "Bringing dev %s %s\n", + netdev->dev_name, ifupdown ? "UP" : "DOWN"); + + NETDEV_NETNS_ENTER(netdev, &switch_state, "ifupdown"); + +#if ENABLE_LIBMNL + rc = netdev_mnl_set_ifupdown(netdev->netns_ctx->omnl, netdev->ifindex, + netdev->dev_name, ifupdown); +#else + LOGNETDEV(netdev, LOGL_ERROR, "%s: NOT SUPPORTED. Build libosmocore with --enable-libmnl.\n", __func__); + rc = -ENOTSUP; +#endif + + NETDEV_NETNS_EXIT(netdev, &switch_state, "ifupdown"); + + return rc; +} + +/*! Add IP address to netdev interface + * \param[in] netdev The netdev object managing the netdev interface + * \param[in] addr The local address to set on the interface + * \param[in] prefixlen The network prefix of addr + * \returns 0 on succes; negative on error. + */ +int osmo_netdev_add_addr(struct osmo_netdev *netdev, const struct osmo_sockaddr *addr, uint8_t prefixlen) +{ + struct osmo_netns_switch_state switch_state; + char buf[INET6_ADDRSTRLEN]; + int rc; + + if (!netdev->registered) + return -ENODEV; + + LOGNETDEV(netdev, LOGL_NOTICE, "Adding address %s/%u to dev %s\n", + osmo_sockaddr_ntop(&addr->u.sa, buf), prefixlen, netdev->dev_name); + + NETDEV_NETNS_ENTER(netdev, &switch_state, "Add address"); + +#if ENABLE_LIBMNL + rc = netdev_mnl_add_addr(netdev->netns_ctx->omnl, netdev->ifindex, addr, prefixlen); +#else + LOGNETDEV(netdev, LOGL_ERROR, "%s: NOT SUPPORTED. Build libosmocore with --enable-libmnl.\n", __func__); + rc = -ENOTSUP; +#endif + + NETDEV_NETNS_EXIT(netdev, &switch_state, "Add address"); + + return rc; +} + +/*! Add IP route to netdev interface + * \param[in] netdev The netdev object managing the netdev interface + * \param[in] dst_addr The destination address of the route + * \param[in] dst_prefixlen The network prefix of dst_addr + * \param[in] gw_addr The gateway address. Optional, can be NULL. + * \returns 0 on succes; negative on error. + */ +int osmo_netdev_add_route(struct osmo_netdev *netdev, const struct osmo_sockaddr *dst_addr, uint8_t dst_prefixlen, const struct osmo_sockaddr *gw_addr) +{ + struct osmo_netns_switch_state switch_state; + char buf_dst[INET6_ADDRSTRLEN]; + char buf_gw[INET6_ADDRSTRLEN]; + int rc; + + if (!netdev->registered) + return -ENODEV; + + LOGNETDEV(netdev, LOGL_NOTICE, "Adding route %s/%u%s%s dev %s\n", + osmo_sockaddr_ntop(&dst_addr->u.sa, buf_dst), dst_prefixlen, + gw_addr ? " via " : "", + gw_addr ? osmo_sockaddr_ntop(&gw_addr->u.sa, buf_gw) : "", + netdev->dev_name); + + NETDEV_NETNS_ENTER(netdev, &switch_state, "Add route"); + +#if ENABLE_LIBMNL + rc = netdev_mnl_add_route(netdev->netns_ctx->omnl, netdev->ifindex, dst_addr, dst_prefixlen, gw_addr); +#else + LOGNETDEV(netdev, LOGL_ERROR, "%s: NOT SUPPORTED. Build libosmocore with --enable-libmnl.\n", __func__); + rc = -ENOTSUP; +#endif + + NETDEV_NETNS_EXIT(netdev, &switch_state, "Add route"); + + return rc; +} + +#endif /* (!EMBEDDED) */ + +/*! @} */ diff --git a/src/core/netns.c b/src/core/netns.c new file mode 100644 index 00000000..c1d75b1e --- /dev/null +++ b/src/core/netns.c @@ -0,0 +1,208 @@ + +/* Network namespace convenience functions + * (C) 2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * + * All Rights Reserved + * + * SPDX-License-Identifier: GPL-2.0+ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include "config.h" + +/*! \addtogroup netns + * @{ + * Network namespace convenience functions + * + * \file netns.c */ + +#if defined(__linux__) + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <sched.h> +#include <signal.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/socket.h> +#include <sys/mount.h> +#include <sys/param.h> +#include <fcntl.h> +#include <errno.h> + +#include <osmocom/core/utils.h> +#include <osmocom/core/netns.h> + +#define NETNS_PREFIX_PATH "/var/run/netns" +#define NETNS_CURRENT_PATH "/proc/self/ns/net" + +/*! Open a file descriptor for the current network namespace. + * \returns fd of the current network namespace on success; negative in case of error + */ +static int netns_open_current_fd(void) +{ + int fd; + /* store the default namespace for later reference */ + if ((fd = open(NETNS_CURRENT_PATH, O_RDONLY)) < 0) + return -errno; + return fd; +} + +/*! switch to a (non-default) namespace, store existing signal mask in oldmask. + * \param[in] nsfd file descriptor representing the namespace to which we shall switch + * \param[out] state caller-provided memory location to which state of previous netns is stored + * \returns 0 on success; negative on error */ +int osmo_netns_switch_enter(int nsfd, struct osmo_netns_switch_state *state) +{ + sigset_t intmask; + int rc; + + state->prev_nsfd = -1; + + if (sigfillset(&intmask) < 0) + return -errno; + if ((rc = sigprocmask(SIG_BLOCK, &intmask, &state->prev_sigmask)) != 0) + return -rc; + state->prev_nsfd = netns_open_current_fd(); + + if (setns(nsfd, CLONE_NEWNET) < 0) { + /* restore old mask if we couldn't switch the netns */ + sigprocmask(SIG_SETMASK, &state->prev_sigmask, NULL); + close(state->prev_nsfd); + state->prev_nsfd = -1; + return -errno; + } + return 0; +} + +/*! switch back to the previous namespace, restoring signal mask. + * \param[in] state information about previous netns, filled by osmo_netns_switch_enter() + * \returns 0 on successs; negative on error */ +int osmo_netns_switch_exit(struct osmo_netns_switch_state *state) +{ + if (state->prev_nsfd < 0) + return -EINVAL; + + int rc; + if (setns(state->prev_nsfd, CLONE_NEWNET) < 0) + return -errno; + + close(state->prev_nsfd); + state->prev_nsfd = -1; + + if ((rc = sigprocmask(SIG_SETMASK, &state->prev_sigmask, NULL)) != 0) + return -rc; + return 0; +} + +static int create_netns(const char *name) +{ + char path[MAXPATHLEN]; + sigset_t intmask, oldmask; + int fd, prev_nsfd; + int rc, rc2; + + /* create /var/run/netns, if it doesn't exist already */ + rc = mkdir(NETNS_PREFIX_PATH, S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH); + if (rc < 0 && errno != EEXIST) + return rc; + + /* create /var/run/netns/[name], if it doesn't exist already */ + rc = snprintf(path, sizeof(path), "%s/%s", NETNS_PREFIX_PATH, name); + if (rc >= sizeof(path)) + return -ENAMETOOLONG; + fd = open(path, O_RDONLY|O_CREAT|O_EXCL, 0); + if (fd < 0) + return -errno; + if (close(fd) < 0) + return -errno; + + /* mask off all signals, store old signal mask */ + if (sigfillset(&intmask) < 0) + return -errno; + if ((rc = sigprocmask(SIG_BLOCK, &intmask, &oldmask)) != 0) + return -rc; + + prev_nsfd = netns_open_current_fd(); + if (prev_nsfd < 0) + return prev_nsfd; + + /* create a new network namespace */ + if (unshare(CLONE_NEWNET) < 0) { + rc = -errno; + goto restore_sigmask; + } + if (mount(NETNS_CURRENT_PATH, path, "none", MS_BIND, NULL) < 0) { + rc = -errno; + goto restore_sigmask; + } + + /* switch back to previous namespace */ + if (setns(prev_nsfd, CLONE_NEWNET) < 0) { + rc = -errno; + goto restore_sigmask; + } + +restore_sigmask: + close(prev_nsfd); + + /* restore process mask */ + if ((rc2 = sigprocmask(SIG_SETMASK, &oldmask, NULL)) != 0) + return -rc2; + + /* might have been set above in case mount fails */ + if (rc < 0) + return rc; + + /* finally, open the created namespace file descriptor from previous ns */ + if ((fd = open(path, O_RDONLY)) < 0) + return -errno; + + return fd; +} + +/*! Open a file descriptor for the network namespace with provided name. + * Creates /var/run/netns/ directory if it doesn't exist already. + * \param[in] name Name of the network namespace (in /var/run/netns/) + * \returns File descriptor of network namespace; negative in case of error + */ +int osmo_netns_open_fd(const char *name) +{ + int rc; + int fd; + char path[MAXPATHLEN]; + + /* path = /var/run/netns/[name] */ + rc = snprintf(path, sizeof(path), "%s/%s", NETNS_PREFIX_PATH, name); + if (rc >= sizeof(path)) + return -ENAMETOOLONG; + + /* If netns already exists, simply open it: */ + fd = open(path, O_RDONLY); + if (fd >= 0) + return fd; + + /* The netns doesn't exist yet, let's create it: */ + fd = create_netns(name); + return fd; +} + +#endif /* defined(__linux__) */ + +/*! @} */ diff --git a/src/core/osmo_io.c b/src/core/osmo_io.c new file mode 100644 index 00000000..b589cb71 --- /dev/null +++ b/src/core/osmo_io.c @@ -0,0 +1,1007 @@ +/* + * New osmocom async I/O API. + * + * (C) 2022-2024 by Harald Welte <laforge@osmocom.org> + * (C) 2022-2024 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * Author: Daniel Willmann <dwillmann@sysmocom.de> + * + * All Rights Reserved. + * + * SPDX-License-Identifier: GPL-2.0+ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "../config.h" +#ifndef EMBEDDED + +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <talloc.h> +#include <unistd.h> +#include <string.h> +#include <stdbool.h> +#include <errno.h> + +#include <osmocom/core/osmo_io.h> +#include <osmocom/core/linuxlist.h> +#include <osmocom/core/logging.h> +#include <osmocom/core/msgb.h> +#include <osmocom/core/socket.h> +#include <osmocom/core/talloc.h> +#include <osmocom/core/utils.h> + +#include "osmo_io_internal.h" + +/*! \addtogroup osmo_io + * @{ + * + * \file osmo_io.c */ + +/*! This environment variable can be set to manually set the backend used in osmo_io */ +#define OSMO_IO_BACKEND_ENV "LIBOSMO_IO_BACKEND" + +const struct value_string osmo_io_backend_names[] = { + { OSMO_IO_BACKEND_POLL, "poll" }, + { OSMO_IO_BACKEND_IO_URING, "io_uring" }, + { 0, NULL } +}; + +const struct value_string osmo_iofd_mode_names[] = { + { OSMO_IO_FD_MODE_READ_WRITE, "read/write" }, + { OSMO_IO_FD_MODE_RECVFROM_SENDTO, "recvfrom/sendto" }, + { OSMO_IO_FD_MODE_RECVMSG_SENDMSG, "recvmsg/sendmsg" }, + { 0, NULL } +}; + +static enum osmo_io_backend g_io_backend; + +/* Used by some tests, can't be static */ +struct iofd_backend_ops osmo_iofd_ops; + +#if defined(HAVE_URING) +void osmo_iofd_uring_init(void); +#endif + +/*! initialize osmo_io for the current thread */ +void osmo_iofd_init(void) +{ + switch (g_io_backend) { + case OSMO_IO_BACKEND_POLL: + break; +#if defined(HAVE_URING) + case OSMO_IO_BACKEND_IO_URING: + osmo_iofd_uring_init(); + break; +#endif + default: + OSMO_ASSERT(0); + break; + } +} + +/* ensure main thread always has pre-initialized osmo_io + * priority 103: run after on_dso_load_select */ +static __attribute__((constructor(103))) void on_dso_load_osmo_io(void) +{ + char *backend = getenv(OSMO_IO_BACKEND_ENV); + if (backend == NULL) + backend = OSMO_IO_BACKEND_DEFAULT; + + if (!strcmp("POLL", backend)) { + g_io_backend = OSMO_IO_BACKEND_POLL; + osmo_iofd_ops = iofd_poll_ops; +#if defined(HAVE_URING) + } else if (!strcmp("IO_URING", backend)) { + g_io_backend = OSMO_IO_BACKEND_IO_URING; + osmo_iofd_ops = iofd_uring_ops; +#endif + } else { + fprintf(stderr, "Invalid osmo_io backend requested: \"%s\"\nCheck the environment variable %s\n", backend, OSMO_IO_BACKEND_ENV); + exit(1); + } + + OSMO_ASSERT(osmo_iofd_ops.close); + OSMO_ASSERT(osmo_iofd_ops.register_fd); + OSMO_ASSERT(osmo_iofd_ops.unregister_fd); + OSMO_ASSERT(osmo_iofd_ops.write_enable); + OSMO_ASSERT(osmo_iofd_ops.write_disable); + OSMO_ASSERT(osmo_iofd_ops.read_enable); + OSMO_ASSERT(osmo_iofd_ops.read_disable); + OSMO_ASSERT(osmo_iofd_ops.notify_connected); + + osmo_iofd_init(); +} + +/*! Allocate the msghdr. + * \param[in] iofd the osmo_io file structure + * \param[in] action the action this msg(hdr) is for (read, write, ..) + * \param[in] msg the msg buffer to use. Will allocate a new one if NULL + * \param[in] cmsg_size size (in bytes) of iofd_msghdr.cmsg buffer. Can be 0 if cmsg is not used. + * \returns the newly allocated msghdr or NULL in case of error */ +struct iofd_msghdr *iofd_msghdr_alloc(struct osmo_io_fd *iofd, enum iofd_msg_action action, struct msgb *msg, + size_t cmsg_size) +{ + bool free_msg = false; + struct iofd_msghdr *hdr; + + if (!msg) { + msg = iofd_msgb_alloc(iofd); + if (!msg) + return NULL; + free_msg = true; + } else { + talloc_steal(iofd, msg); + } + + hdr = talloc_zero_size(iofd, sizeof(struct iofd_msghdr) + cmsg_size); + if (!hdr) { + if (free_msg) + talloc_free(msg); + return NULL; + } + + hdr->action = action; + hdr->iofd = iofd; + hdr->msg = msg; + + return hdr; +} + +/*! Free the msghdr. + * \param[in] msghdr the msghdr to free + */ +void iofd_msghdr_free(struct iofd_msghdr *msghdr) +{ + /* msghdr->msg is never owned by msghdr, it will either be freed in the send path or + * or passed on to the read callback which takes ownership. */ + talloc_free(msghdr); +} + +/*! convenience wrapper to call msgb_alloc with parameters from osmo_io_fd */ +struct msgb *iofd_msgb_alloc(struct osmo_io_fd *iofd) +{ + uint16_t headroom = iofd->msgb_alloc.headroom; + + OSMO_ASSERT(iofd->msgb_alloc.size < 0xffff - headroom); + return msgb_alloc_headroom_c(iofd, iofd->msgb_alloc.size + headroom, headroom, "osmo_io_msgb"); +} + +/*! return the pending msgb in iofd or NULL if there is none*/ +struct msgb *iofd_msgb_pending(struct osmo_io_fd *iofd) +{ + struct msgb *msg = NULL; + + msg = iofd->pending; + iofd->pending = NULL; + + return msg; +} + +/*! Return the pending msgb or allocate and return a new one */ +struct msgb *iofd_msgb_pending_or_alloc(struct osmo_io_fd *iofd) +{ + struct msgb *msg = NULL; + + msg = iofd_msgb_pending(iofd); + if (!msg) + msg = iofd_msgb_alloc(iofd); + + return msg; +} + +/*! Enqueue a message to be sent. + * + * Enqueues the message at the back of the queue provided there is enough space. + * \param[in] iofd the file descriptor + * \param[in] msghdr the message to enqueue + * \returns 0 if the message was enqueued succcessfully, + * -ENOSPC if the queue already contains the maximum number of messages + */ +int iofd_txqueue_enqueue(struct osmo_io_fd *iofd, struct iofd_msghdr *msghdr) +{ + if (iofd->tx_queue.current_length >= iofd->tx_queue.max_length) + return -ENOSPC; + + llist_add_tail(&msghdr->list, &iofd->tx_queue.msg_queue); + iofd->tx_queue.current_length++; + + if (iofd->tx_queue.current_length == 1 && !IOFD_FLAG_ISSET(iofd, IOFD_FLAG_CLOSED)) + osmo_iofd_ops.write_enable(iofd); + + return 0; +} + +/*! Enqueue a message at the front. + * + * Used to enqueue a msgb from a partial send again. This function will always + * enqueue the message, even if the maximum number of messages is reached. + * \param[in] iofd the file descriptor + * \param[in] msghdr the message to enqueue + */ +void iofd_txqueue_enqueue_front(struct osmo_io_fd *iofd, struct iofd_msghdr *msghdr) +{ + llist_add(&msghdr->list, &iofd->tx_queue.msg_queue); + iofd->tx_queue.current_length++; + + if (iofd->tx_queue.current_length == 1 && !IOFD_FLAG_ISSET(iofd, IOFD_FLAG_CLOSED)) + osmo_iofd_ops.write_enable(iofd); +} + +/*! Dequeue a message from the front. + * + * \param[in] iofd the file descriptor + * \returns the msghdr from the front of the queue or NULL if the queue is empty + */ +struct iofd_msghdr *iofd_txqueue_dequeue(struct osmo_io_fd *iofd) +{ + struct llist_head *lh; + + if (iofd->tx_queue.current_length == 0) + return NULL; + + lh = iofd->tx_queue.msg_queue.next; + + OSMO_ASSERT(lh); + iofd->tx_queue.current_length--; + llist_del(lh); + + if (iofd->tx_queue.current_length == 0) + osmo_iofd_ops.write_disable(iofd); + + return llist_entry(lh, struct iofd_msghdr, list); +} + +/*! Handle segmentation of the msg. If this function returns *_HANDLE_ONE or MORE then the data in msg will contain + * one complete message. + * If there are bytes left over, *pending_out will point to a msgb with the remaining data. +*/ +static enum iofd_seg_act iofd_handle_segmentation(struct osmo_io_fd *iofd, struct msgb *msg, struct msgb **pending_out) +{ + int extra_len, received_len; + struct msgb *msg_pending; + + /* Save the start of message before segmentation_cb (which could change it) */ + uint8_t *data = msg->data; + + received_len = msgb_length(msg); + + if (!iofd->io_ops.segmentation_cb) { + *pending_out = NULL; + return IOFD_SEG_ACT_HANDLE_ONE; + } + + int expected_len = iofd->io_ops.segmentation_cb(msg); + if (expected_len == -EAGAIN) { + goto defer; + } else if (expected_len < 0) { + /* Something is wrong, skip this msgb */ + LOGPIO(iofd, LOGL_ERROR, "segmentation_cb returned error (%d), skipping msg of size %d\n", + expected_len, received_len); + *pending_out = NULL; + msgb_free(msg); + return IOFD_SEG_ACT_DEFER; + } + + extra_len = received_len - expected_len; + /* No segmentation needed, return the whole msgb */ + if (extra_len == 0) { + *pending_out = NULL; + return IOFD_SEG_ACT_HANDLE_ONE; + /* segment is incomplete */ + } else if (extra_len < 0) { + goto defer; + } + + /* msgb contains more than one segment */ + /* Copy the trailing data over */ + msg_pending = iofd_msgb_alloc(iofd); + memcpy(msgb_data(msg_pending), data + expected_len, extra_len); + msgb_put(msg_pending, extra_len); + *pending_out = msg_pending; + + /* Trim the original msgb to size. Don't use msgb_trim because we need to reference + * msg->data from before it might have been modified by the segmentation_cb(). */ + msg->tail = data + expected_len; + msg->len = msg->tail - msg->data; + return IOFD_SEG_ACT_HANDLE_MORE; + +defer: + *pending_out = msg; + return IOFD_SEG_ACT_DEFER; +} + +/*! Restore message boundaries on read() and pass individual messages to the read callback + */ +void iofd_handle_segmented_read(struct osmo_io_fd *iofd, struct msgb *msg, int rc) +{ + int res; + struct msgb *pending = NULL; + + OSMO_ASSERT(iofd->mode == OSMO_IO_FD_MODE_READ_WRITE); + + if (rc <= 0) { + iofd->io_ops.read_cb(iofd, rc, msg); + return; + } + + do { + res = iofd_handle_segmentation(iofd, msg, &pending); + if (res != IOFD_SEG_ACT_DEFER || rc < 0) + iofd->io_ops.read_cb(iofd, rc, msg); + if (res == IOFD_SEG_ACT_HANDLE_MORE) + msg = pending; + } while (res == IOFD_SEG_ACT_HANDLE_MORE); + + OSMO_ASSERT(iofd->pending == NULL); + iofd->pending = pending; +} + +/*! completion handler: Internal function called by osmo_io_backend after a given I/O operation has completed + * \param[in] iofd I/O file-descriptor on which I/O has completed + * \param[in] msg message buffer containing data related to completed I/O + * \param[in] rc result code with read size or error (-errno) + * \param[in] hdr serialized msghdr containing state of completed I/O */ +void iofd_handle_recv(struct osmo_io_fd *iofd, struct msgb *msg, int rc, struct iofd_msghdr *hdr) +{ + talloc_steal(iofd->msgb_alloc.ctx, msg); + switch (iofd->mode) { + case OSMO_IO_FD_MODE_READ_WRITE: + iofd_handle_segmented_read(iofd, msg, rc); + break; + case OSMO_IO_FD_MODE_RECVFROM_SENDTO: + iofd->io_ops.recvfrom_cb(iofd, rc, msg, &hdr->osa); + break; + case OSMO_IO_FD_MODE_RECVMSG_SENDMSG: + iofd->io_ops.recvmsg_cb(iofd, rc, msg, &hdr->hdr); + break; + default: + OSMO_ASSERT(false); + break; + } +} + +/*! completion handler: Internal function called by osmo_io_backend after a given I/O operation has completed + * \param[in] iofd I/O file-descriptor on which I/O has completed + * \param[in] rc return value of the I/O operation + * \param[in] msghdr serialized msghdr containing state of completed I/O + */ +void iofd_handle_send_completion(struct osmo_io_fd *iofd, int rc, struct iofd_msghdr *msghdr) +{ + struct msgb *msg = msghdr->msg; + + /* Incomplete write */ + if (rc > 0 && rc < msgb_length(msg)) { + /* Re-enqueue remaining data */ + msgb_pull(msg, rc); + msghdr->iov[0].iov_len = msgb_length(msg); + iofd_txqueue_enqueue_front(iofd, msghdr); + return; + } + + /* Reenqueue the complete msgb */ + if (rc == -EAGAIN) { + iofd_txqueue_enqueue_front(iofd, msghdr); + return; + } + + /* All other failure and success cases are handled here */ + switch (msghdr->action) { + case IOFD_ACT_WRITE: + if (iofd->io_ops.write_cb) + iofd->io_ops.write_cb(iofd, rc, msg); + break; + case IOFD_ACT_SENDTO: + if (iofd->io_ops.sendto_cb) + iofd->io_ops.sendto_cb(iofd, rc, msg, &msghdr->osa); + break; + case IOFD_ACT_SENDMSG: + if (iofd->io_ops.sendmsg_cb) + iofd->io_ops.sendmsg_cb(iofd, rc, msg); + break; + default: + OSMO_ASSERT(0); + } + + msgb_free(msghdr->msg); + iofd_msghdr_free(msghdr); +} + +/* Public functions */ + +/*! Write a message to a file descriptor / connected socket. + * The osmo_io_fd must be using OSMO_IO_FD_MODE_READ_WRITE. + * + * Appends the message to the internal transmit queue for eventual non-blocking + * write to the underlying socket/file descriptor. + * + * If the function returns success (0) it will take ownership of the msgb and + * internally call msgb_free() after the write request completes. + * In case of an error, the msgb needs to be freed by the caller. + * + * \param[in] iofd osmo_io_fd file descriptor to write data to + * \param[in] msg message buffer containing the data to write + * \returns 0 in case of success; a negative value in case of error + */ +int osmo_iofd_write_msgb(struct osmo_io_fd *iofd, struct msgb *msg) +{ + int rc; + + if (OSMO_UNLIKELY(msgb_length(msg) == 0)) { + LOGPIO(iofd, LOGL_ERROR, "Length is 0, rejecting msgb.\n"); + return -EINVAL; + } + + OSMO_ASSERT(iofd->mode == OSMO_IO_FD_MODE_READ_WRITE); + + struct iofd_msghdr *msghdr = iofd_msghdr_alloc(iofd, IOFD_ACT_WRITE, msg, 0); + if (!msghdr) + return -ENOMEM; + + msghdr->flags = MSG_NOSIGNAL; + msghdr->iov[0].iov_base = msgb_data(msghdr->msg); + msghdr->iov[0].iov_len = msgb_length(msghdr->msg); + msghdr->hdr.msg_iov = &msghdr->iov[0]; + msghdr->hdr.msg_iovlen = 1; + + rc = iofd_txqueue_enqueue(iofd, msghdr); + if (rc < 0) { + iofd_msghdr_free(msghdr); + LOGPIO(iofd, LOGL_ERROR, "enqueueing message failed (%d). Rejecting msgb\n", rc); + return rc; + } + + return 0; +} + +/*! Send a message through an unconnected socket. + * The osmo_io_fd must be using OSMO_IO_FD_MODE_RECVFROM_SENDTO. + * + * Appends the message to the internal transmit queue for eventual non-blocking + * sendto on the underlying socket/file descriptor. + * + * If the function returns success (0), it will take ownership of the msgb and + * internally call msgb_free() after the sendto request completes. + * In case of an error the msgb needs to be freed by the caller. + * + * \param[in] iofd file descriptor to write to + * \param[in] msg message buffer to send + * \param[in] sendto_flags Flags to pass to the send call + * \param[in] dest destination address to send the message to + * \returns 0 in case of success; a negative value in case of error + */ +int osmo_iofd_sendto_msgb(struct osmo_io_fd *iofd, struct msgb *msg, int sendto_flags, const struct osmo_sockaddr *dest) +{ + int rc; + + if (OSMO_UNLIKELY(msgb_length(msg) == 0)) { + LOGPIO(iofd, LOGL_ERROR, "Length is 0, rejecting msgb.\n"); + return -EINVAL; + } + + OSMO_ASSERT(iofd->mode == OSMO_IO_FD_MODE_RECVFROM_SENDTO); + + struct iofd_msghdr *msghdr = iofd_msghdr_alloc(iofd, IOFD_ACT_SENDTO, msg, 0); + if (!msghdr) + return -ENOMEM; + + if (dest) { + msghdr->osa = *dest; + msghdr->hdr.msg_name = &msghdr->osa.u.sa; + msghdr->hdr.msg_namelen = osmo_sockaddr_size(&msghdr->osa); + } + msghdr->flags = sendto_flags; + msghdr->iov[0].iov_base = msgb_data(msghdr->msg); + msghdr->iov[0].iov_len = msgb_length(msghdr->msg); + msghdr->hdr.msg_iov = &msghdr->iov[0]; + msghdr->hdr.msg_iovlen = 1; + + rc = iofd_txqueue_enqueue(iofd, msghdr); + if (rc < 0) { + iofd_msghdr_free(msghdr); + LOGPIO(iofd, LOGL_ERROR, "enqueueing message failed (%d). Rejecting msgb\n", rc); + return rc; + } + + return 0; +} + +/*! osmo_io equivalent of the sendmsg(2) socket API call. + * The osmo_io_fd must be using OSMO_IO_FD_MODE_RECVMSG_SENDMSG. + * + * Appends the message to the internal transmit queue for eventual non-blocking + * sendmsg on the underlying socket/file descriptor. + * + * If the function returns success (0), it will take ownership of the msgb and + * internally call msgb_free() after the sendmsg request completes. + * In case of an error the msgb needs to be freed by the caller. + * + * \param[in] iofd file descriptor to write to + * \param[in] msg message buffer to send; is used to fill msgh->iov[] + * \param[in] sendmsg_flags Flags to pass to the send call + * \param[in] msgh 'struct msghdr' for name/control/flags. iov must be empty! + * \returns 0 in case of success; a negative value in case of error + */ +int osmo_iofd_sendmsg_msgb(struct osmo_io_fd *iofd, struct msgb *msg, int sendmsg_flags, const struct msghdr *msgh) +{ + int rc; + struct iofd_msghdr *msghdr; + + if (OSMO_UNLIKELY(msgb_length(msg) == 0)) { + LOGPIO(iofd, LOGL_ERROR, "Length is 0, rejecting msgb.\n"); + return -EINVAL; + } + + OSMO_ASSERT(iofd->mode == OSMO_IO_FD_MODE_RECVMSG_SENDMSG); + + if (OSMO_UNLIKELY(msgh->msg_namelen > sizeof(msghdr->osa))) { + LOGPIO(iofd, LOGL_ERROR, "osmo_iofd_sendmsg msg_namelen (%u) > supported %zu bytes\n", + msgh->msg_namelen, sizeof(msghdr->osa)); + return -EINVAL; + } + + if (OSMO_UNLIKELY(msgh->msg_iovlen)) { + LOGPIO(iofd, LOGL_ERROR, "osmo_iofd_sendmsg must have all in 'struct msgb', not in 'msg_iov'\n"); + return -EINVAL; + } + + msghdr = iofd_msghdr_alloc(iofd, IOFD_ACT_SENDMSG, msg, msgh->msg_controllen); + if (!msghdr) + return -ENOMEM; + + /* copy over optional address */ + if (msgh->msg_name) { + memcpy(&msghdr->osa, msgh->msg_name, msgh->msg_namelen); + msghdr->hdr.msg_name = &msghdr->osa.u.sa; + msghdr->hdr.msg_namelen = msgh->msg_namelen; + } + + /* build iov from msgb */ + msghdr->iov[0].iov_base = msgb_data(msghdr->msg); + msghdr->iov[0].iov_len = msgb_length(msghdr->msg); + msghdr->hdr.msg_iov = &msghdr->iov[0]; + msghdr->hdr.msg_iovlen = 1; + + /* copy over the cmsg from the msghdr */ + if (msgh->msg_control && msgh->msg_controllen) { + msghdr->hdr.msg_control = msghdr->cmsg; + msghdr->hdr.msg_controllen = msgh->msg_controllen; + memcpy(msghdr->cmsg, msgh->msg_control, msgh->msg_controllen); + } + + /* copy over msg_flags */ + msghdr->hdr.msg_flags = sendmsg_flags; + + rc = iofd_txqueue_enqueue(iofd, msghdr); + if (rc < 0) { + iofd_msghdr_free(msghdr); + LOGPIO(iofd, LOGL_ERROR, "enqueueing message failed (%d). Rejecting msgb\n", rc); + return rc; + } + + return 0; +} + +static int check_mode_callback_compat(enum osmo_io_fd_mode mode, const struct osmo_io_ops *ops) +{ + switch (mode) { + case OSMO_IO_FD_MODE_READ_WRITE: + if (ops->recvfrom_cb || ops->sendto_cb) + return false; + if (ops->recvmsg_cb || ops->sendmsg_cb) + return false; + break; + case OSMO_IO_FD_MODE_RECVFROM_SENDTO: + if (ops->read_cb || ops->write_cb) + return false; + if (ops->recvmsg_cb || ops->sendmsg_cb) + return false; + break; + case OSMO_IO_FD_MODE_RECVMSG_SENDMSG: + if (ops->recvfrom_cb || ops->sendto_cb) + return false; + if (ops->read_cb || ops->write_cb) + return false; + break; + default: + break; + } + + return true; +} + +/*! Allocate and setup a new iofd. + * + * Use this to create a new osmo_io_fd, specifying the osmo_io_fd_mode and osmo_io_ops, as well as optionally + * the file-descriptor number and a human-readable name. This is the first function you call for any + * osmo_io_fd. + * + * The created osmo_io_fd is not yet registered, and hence can not be used for any I/O until a subsequent + * call to osmo_iofd_register(). + * + * The created osmo_io_fd is initialized with some default settings: + * * msgb allocations size: OSMO_IO_DEFAULT_MSGB_SIZE (1024) + * * msgb headroom: OSMO_IO_DEFAULT_MSGB_HEADROOM (128) + * * tx_queue depth: 32 + * + * Those values may be adjusted from their defaults by using osmo_iofd_set_alloc_info() and + * osmo_iofd_set_txqueue_max_length() on the osmo_io_fd. + * + * \param[in] ctx the parent context from which to allocate + * \param[in] fd the underlying system file descriptor. May be -1 if not known yet; must then be specified + * at subsequent osmo_iofd_register() time. + * \param[in] name the optional human-readable name of the iofd; may be NULL + * \param[in] mode the osmo_io_fd_mode of the iofd, whether it should use read()/write(), sendto()/recvfrom() + * semantics. + * \param[in] ioops structure specifying the read/write/send/recv callbacks. Will be copied to the iofd, so + * the caller does not have to keep it around after issuing the osmo_iofd_setup call. + * \param[in] data opaque user data pointer accessible by the ioops callbacks + * \returns The newly allocated osmo_io_fd struct or NULL on failure + */ +struct osmo_io_fd *osmo_iofd_setup(const void *ctx, int fd, const char *name, enum osmo_io_fd_mode mode, + const struct osmo_io_ops *ioops, void *data) +{ + struct osmo_io_fd *iofd; + + /* reject unsupported/unknown modes */ + switch (mode) { + case OSMO_IO_FD_MODE_READ_WRITE: + case OSMO_IO_FD_MODE_RECVFROM_SENDTO: + case OSMO_IO_FD_MODE_RECVMSG_SENDMSG: + break; + default: + return NULL; + } + + if (ioops && !check_mode_callback_compat(mode, ioops)) { + LOGP(DLIO, LOGL_ERROR, "iofd(%s): rejecting call-backs incompatible with mode %s\n", + name ? name : "unknown", osmo_iofd_mode_name(mode)); + return NULL; + } + + iofd = talloc_zero(ctx, struct osmo_io_fd); + if (!iofd) + return NULL; + + iofd->fd = fd; + iofd->mode = mode; + IOFD_FLAG_SET(iofd, IOFD_FLAG_CLOSED); + + if (name) + iofd->name = talloc_strdup(iofd, name); + + if (ioops) + iofd->io_ops = *ioops; + + iofd->pending = NULL; + + iofd->data = data; + + iofd->msgb_alloc.ctx = ctx; + iofd->msgb_alloc.size = OSMO_IO_DEFAULT_MSGB_SIZE; + iofd->msgb_alloc.headroom = OSMO_IO_DEFAULT_MSGB_HEADROOM; + + iofd->tx_queue.max_length = 32; + INIT_LLIST_HEAD(&iofd->tx_queue.msg_queue); + + return iofd; +} + +/*! Set the size of the control message buffer allocated when submitting recvmsg. + * + * If your osmo_io_fd is in OSMO_IO_FD_MODE_RECVMSG_SENDMSG mode, this API function can be used to tell the + * osmo_io code how much memory should be allocated for the cmsg (control message) buffer when performing + * recvmsg(). */ +int osmo_iofd_set_cmsg_size(struct osmo_io_fd *iofd, size_t cmsg_size) +{ + if (iofd->mode != OSMO_IO_FD_MODE_RECVMSG_SENDMSG) + return -EINVAL; + + iofd->cmsg_size = cmsg_size; + return 0; +} + +/*! Register the osmo_io_fd for active I/O. + * + * Calling this function will register a previously initialized osmo_io_fd for performing I/O. + * + * If the osmo_iofd has a read_cb/recvfrom_cb_recvmsg_cb set in its osmo_io_ops, read/receive will be + * automatically enabled and the respective call-back is called at any time data becomes available. + * + * If there is to-be-transmitted data in the transmit queue, write will be automatically enabled, allowing + * the transmit queue to be drained as soon as the fd/socket becomes writable. + * + * \param[in] iofd the iofd file descriptor + * \param[in] fd the system fd number that will be registered. If you did not yet specify the file descriptor + * number during osmo_fd_setup(), or if it has changed since then, you can state the [new] file descriptor + * number as argument. If you wish to proceed with the previously specified file descriptor number, pass -1. + * \returns zero on success, a negative value on error +*/ +int osmo_iofd_register(struct osmo_io_fd *iofd, int fd) +{ + int rc = 0; + + if (fd >= 0) + iofd->fd = fd; + else if (iofd->fd < 0) { + /* this might happen if both osmo_iofd_setup() and osmo_iofd_register() are called with -1 */ + LOGPIO(iofd, LOGL_ERROR, "Cannot register io_fd using invalid fd == %d\n", iofd->fd); + return -EBADF; + } + + rc = osmo_iofd_ops.register_fd(iofd); + if (rc) + return rc; + + IOFD_FLAG_UNSET(iofd, IOFD_FLAG_CLOSED); + if ((iofd->mode == OSMO_IO_FD_MODE_READ_WRITE && iofd->io_ops.read_cb) || + (iofd->mode == OSMO_IO_FD_MODE_RECVFROM_SENDTO && iofd->io_ops.recvfrom_cb) || + (iofd->mode == OSMO_IO_FD_MODE_RECVMSG_SENDMSG && iofd->io_ops.recvmsg_cb)) { + osmo_iofd_ops.read_enable(iofd); + } + + if (iofd->tx_queue.current_length > 0) + osmo_iofd_ops.write_enable(iofd); + + return rc; +} + +/*! Unregister the given osmo_io_fd from osmo_io. + * + * After an osmo_io_fd has been successfully unregistered, it can no longer perform any I/O via osmo_io. + * However, it can be subsequently re-registered using osmo_iofd_register(). + * + * \param[in] iofd the file descriptor + * \returns zero on success, a negative value on error + */ +int osmo_iofd_unregister(struct osmo_io_fd *iofd) +{ + return osmo_iofd_ops.unregister_fd(iofd); +} + +/*! Retrieve the number of messages pending in the transmit queue. + * + * \param[in] iofd the file descriptor + */ +unsigned int osmo_iofd_txqueue_len(struct osmo_io_fd *iofd) +{ + return iofd->tx_queue.current_length; +} + +/*! Clear the transmit queue of the given osmo_io_fd. + * + * This function frees all messages currently pending in the transmit queue + * \param[in] iofd the file descriptor + */ +void osmo_iofd_txqueue_clear(struct osmo_io_fd *iofd) +{ + struct iofd_msghdr *hdr; + while ((hdr = iofd_txqueue_dequeue(iofd))) { + msgb_free(hdr->msg); + iofd_msghdr_free(hdr); + } +} + +/*! Free the given osmo_io_fd. + * + * The iofd will be automatically closed before via osmo_iofd_close() [which in turn will unregister + * it and clear any pending transmit queue items]. You must not reference the iofd + * after calling this function. However, it is safe to call this function from any of osmo_io + * call-backs; in this case, actual free will be internally delayed until that call-back completes. + * + * \param[in] iofd the file descriptor + */ +void osmo_iofd_free(struct osmo_io_fd *iofd) +{ + if (!iofd) + return; + + osmo_iofd_close(iofd); + + if (!IOFD_FLAG_ISSET(iofd, IOFD_FLAG_IN_CALLBACK)) { + talloc_free(iofd); + } else { + /* Prevent our parent context from freeing us prematurely */ + talloc_steal(NULL, iofd); + IOFD_FLAG_SET(iofd, IOFD_FLAG_TO_FREE); + } +} + +/*! Close the given osmo_io_fd. + * + * This function closes the underlying fd, unregisters it from osmo_io and clears any messages in the tx + * queue. The iofd itself is not freed and can be assigned a new file descriptor with osmo_iofd_register() + * \param[in] iofd the file descriptor + * \returns 0 on success, a negative value otherwise + */ +int osmo_iofd_close(struct osmo_io_fd *iofd) +{ + int rc = 0; + + if (IOFD_FLAG_ISSET(iofd, IOFD_FLAG_CLOSED)) + return rc; + + IOFD_FLAG_SET(iofd, IOFD_FLAG_CLOSED); + + /* Free pending msgs in tx queue */ + osmo_iofd_txqueue_clear(iofd); + msgb_free(iofd->pending); + + iofd->pending = NULL; + + rc = osmo_iofd_ops.close(iofd); + iofd->fd = -1; + return rc; +} + +/*! Set the size and headroom of the msgb allocated when receiving messages. + * \param[in] iofd the file descriptor + * \param[in] size the size of the msgb when receiving data + * \param[in] headroom the headroom of the msgb when receiving data + */ +void osmo_iofd_set_alloc_info(struct osmo_io_fd *iofd, unsigned int size, unsigned int headroom) +{ + iofd->msgb_alloc.headroom = headroom; + iofd->msgb_alloc.size = size; +} + +/*! Set the maximum number of messages enqueued for sending. + * \param[in] iofd the file descriptor + * \param[in] size the maximum size of the transmit queue + */ +void osmo_iofd_set_txqueue_max_length(struct osmo_io_fd *iofd, unsigned int max_length) +{ + iofd->tx_queue.max_length = max_length; +} + +/*! Retrieve the associated user-data from an osmo_io_fd. + * + * A call to this function will return the opaque user data pointer which was specified previously + * via osmo_iofd_setup() or via osmo_iofd_set_data(). + * + * \param[in] iofd the file descriptor + * \returns the data that was previously set with \ref osmo_iofd_setup() + */ +void *osmo_iofd_get_data(const struct osmo_io_fd *iofd) +{ + return iofd->data; +} + +/*! Set the associated user-data from an osmo_io_fd. + * + * Calling this function will set/overwrite the opaque user data pointer, which can later be retrieved using + * osmo_iofd_get_data(). + * + * \param[in] iofd the file descriptor + * \param[in] data the data to set + */ +void osmo_iofd_set_data(struct osmo_io_fd *iofd, void *data) +{ + iofd->data = data; +} + +/*! Retrieve the private number from an osmo_io_fd. + * Calling this function will retrieve the private user number previously set via osmo_iofd_set_priv_nr(). + * \param[in] iofd the file descriptor + * \returns the private number that was previously set with \ref osmo_iofd_set_priv_nr() + */ +unsigned int osmo_iofd_get_priv_nr(const struct osmo_io_fd *iofd) +{ + return iofd->priv_nr; +} + +/*! Set the private number of an osmo_io_fd. + * The priv_nr passed in via this call can later be retrieved via osmo_iofd_get_priv_nr(). It provides + * a way how additional context can be stored in the osmo_io_fd beyond the opaque 'data' pointer. + * \param[in] iofd the file descriptor + * \param[in] priv_nr the private number to set + */ +void osmo_iofd_set_priv_nr(struct osmo_io_fd *iofd, unsigned int priv_nr) +{ + iofd->priv_nr = priv_nr; +} + +/*! Retrieve the underlying file descriptor from an osmo_io_fd. + * \param[in] iofd the file descriptor + * \returns the underlying file descriptor number */ +int osmo_iofd_get_fd(const struct osmo_io_fd *iofd) +{ + return iofd->fd; +} + +/*! Retrieve the human-readable name of the given osmo_io_fd. + * \param[in] iofd the file descriptor + * \returns the name of the iofd as given in \ref osmo_iofd_setup() */ +const char *osmo_iofd_get_name(const struct osmo_io_fd *iofd) +{ + return iofd->name; +} + +/*! Set the human-readable name of the file descriptor. + * The given name will be used as context by all related logging and future calls to osmo_iofd_get_name(). + * \param[in] iofd the file descriptor + * \param[in] name the name to set on the file descriptor */ +void osmo_iofd_set_name(struct osmo_io_fd *iofd, const char *name) +{ + osmo_talloc_replace_string(iofd, &iofd->name, name); +} + +/*! Set the osmo_io_ops calbacks for an osmo_io_fd. + * This function can be used to update/overwrite the call-back functions for the given osmo_io_fd; it + * replaces the currently-set call-back function pointers from a previous call to osmo_iofd_set_ioops() + * or the original osmo_iofd_setup(). + * \param[in] iofd Target iofd file descriptor + * \param[in] ioops osmo_io_ops structure to be copied to the osmo_io_fd. + * \returns 0 on success, negative on error */ +int osmo_iofd_set_ioops(struct osmo_io_fd *iofd, const struct osmo_io_ops *ioops) +{ + if (!check_mode_callback_compat(iofd->mode, ioops)) { + LOGPIO(iofd, LOGL_ERROR, "rejecting call-backs incompatible with mode %s\n", + osmo_iofd_mode_name(iofd->mode)); + return -EINVAL; + } + + iofd->io_ops = *ioops; + + switch (iofd->mode) { + case OSMO_IO_FD_MODE_READ_WRITE: + if (iofd->io_ops.read_cb) + osmo_iofd_ops.read_enable(iofd); + else + osmo_iofd_ops.read_disable(iofd); + break; + case OSMO_IO_FD_MODE_RECVFROM_SENDTO: + if (iofd->io_ops.recvfrom_cb) + osmo_iofd_ops.read_enable(iofd); + else + osmo_iofd_ops.read_disable(iofd); + break; + case OSMO_IO_FD_MODE_RECVMSG_SENDMSG: + if (iofd->io_ops.recvmsg_cb) + osmo_iofd_ops.read_enable(iofd); + else + osmo_iofd_ops.read_disable(iofd); + break; + default: + OSMO_ASSERT(0); + } + + return 0; +} + +/*! Retrieve the osmo_io_ops for an iofd. + * \param[in] iofd Target iofd file descriptor + * \param[in] ioops caller-allocated osmo_io_ops structure to be filled */ +void osmo_iofd_get_ioops(struct osmo_io_fd *iofd, struct osmo_io_ops *ioops) +{ + *ioops = iofd->io_ops; +} + +/*! Request notification of the user if/when a client socket is connected. + * Calling this function will request osmo_io to notify the user (via + * write call-back) once a non-blocking outbound connect() of the + * socket completes. + * + * This only works for connection oriented sockets in either + * OSMO_IO_FD_MODE_READ_WRITE or OSMO_IO_FD_MODE_RECVMSG_SENDMSG mode. + * + * \param[in] iofd the file descriptor */ +void osmo_iofd_notify_connected(struct osmo_io_fd *iofd) +{ + OSMO_ASSERT(iofd->mode == OSMO_IO_FD_MODE_READ_WRITE || + iofd->mode == OSMO_IO_FD_MODE_RECVMSG_SENDMSG); + osmo_iofd_ops.notify_connected(iofd); +} + +/*! @} */ + +#endif /* ifndef(EMBEDDED) */ diff --git a/src/core/osmo_io_internal.h b/src/core/osmo_io_internal.h new file mode 100644 index 00000000..a4b0749d --- /dev/null +++ b/src/core/osmo_io_internal.h @@ -0,0 +1,166 @@ +/*! \file osmo_io_internal.h */ + +#pragma once + +#include <unistd.h> +#include <stdbool.h> +#include <netinet/sctp.h> + +#include <osmocom/core/osmo_io.h> +#include <osmocom/core/linuxlist.h> +#include <osmocom/core/msgb.h> +#include <osmocom/core/select.h> +#include <osmocom/core/socket.h> + +#include "../config.h" + +#define OSMO_IO_DEFAULT_MSGB_SIZE 1024 +#define OSMO_IO_DEFAULT_MSGB_HEADROOM 128 + +extern const struct iofd_backend_ops iofd_poll_ops; +#define OSMO_IO_BACKEND_DEFAULT "POLL" + +#if defined(HAVE_URING) +extern const struct iofd_backend_ops iofd_uring_ops; +#endif + +struct iofd_backend_ops { + int (*register_fd)(struct osmo_io_fd *iofd); + int (*unregister_fd)(struct osmo_io_fd *iofd); + int (*close)(struct osmo_io_fd *iofd); + void (*write_enable)(struct osmo_io_fd *iofd); + void (*write_disable)(struct osmo_io_fd *iofd); + void (*read_enable)(struct osmo_io_fd *iofd); + void (*read_disable)(struct osmo_io_fd *iofd); + void (*notify_connected)(struct osmo_io_fd *iofd); +}; + +#define IOFD_FLAG_CLOSED (1<<0) +#define IOFD_FLAG_IN_CALLBACK (1<<1) +#define IOFD_FLAG_TO_FREE (1<<2) +#define IOFD_FLAG_NOTIFY_CONNECTED (1<<3) +#define IOFD_FLAG_FD_REGISTERED (1<<4) + +#define IOFD_FLAG_SET(iofd, flag) \ + (iofd)->flags |= (flag) + +#define IOFD_FLAG_UNSET(iofd, flag) \ + (iofd)->flags &= ~(flag) + +#define IOFD_FLAG_ISSET(iofd, flag) ((iofd)->flags & (flag)) + +struct osmo_io_fd { + /*! linked list for internal management */ + struct llist_head list; + /*! actual operating-system level file decriptor */ + int fd; + /*! type of read/write mode to use */ + enum osmo_io_fd_mode mode; + + /*! flags to guard closing/freeing of iofd */ + uint32_t flags; + + /*! human-readable name to associte with fd */ + char *name; + + /*! send/recv (msg) callback functions */ + struct osmo_io_ops io_ops; + /*! Pending msgb to keep partial data during segmentation */ + struct msgb *pending; + + /*! data pointer passed through to call-back function */ + void *data; + /*! private number, extending \a data */ + unsigned int priv_nr; + + /*! size of iofd_msghdr.cmsg[] when allocated in recvmsg path */ + size_t cmsg_size; + + struct { + /*! talloc context from which to allocate msgb when reading */ + const void *ctx; + /*! size of msgb to allocate (excluding headroom) */ + unsigned int size; + /*! headroom to allocate when allocating msgb's */ + unsigned int headroom; + } msgb_alloc; + + struct { + /*! maximum length of write queue */ + unsigned int max_length; + /*! current length of write queue */ + unsigned int current_length; + /*! actual linked list implementing the transmit queue */ + struct llist_head msg_queue; + } tx_queue; + + union { + struct { + struct osmo_fd ofd; + } poll; + struct { + bool read_enabled; + bool write_enabled; + void *read_msghdr; + void *write_msghdr; + /* TODO: index into array of registered fd's? */ + /* osmo_fd for non-blocking connect handling */ + struct osmo_fd connect_ofd; + } uring; + } u; +}; + +enum iofd_msg_action { + IOFD_ACT_READ, + IOFD_ACT_WRITE, + IOFD_ACT_RECVFROM, + IOFD_ACT_SENDTO, + IOFD_ACT_RECVMSG, + IOFD_ACT_SENDMSG, +}; + + +/*! serialized version of 'struct msghdr' employed by sendmsg/recvmsg */ +struct iofd_msghdr { + /*! entry into osmo_io_fd.tx_queue.msg_queue */ + struct llist_head list; + enum iofd_msg_action action; + /*! the 'struct msghdr' we are wrapping/ecapsulating here */ + struct msghdr hdr; + /*! socket address of the remote peer */ + struct osmo_sockaddr osa; + /*! io-vector we need to pass as argument to sendmsg/recvmsg; is set up + * to point into msg below */ + struct iovec iov[1]; + /*! flags we pass as argument to sendmsg / recvmsg */ + int flags; + + /*! message-buffer containing data for this I/O operation */ + struct msgb *msg; + /*! I/O file descriptor on which we perform this I/O operation */ + struct osmo_io_fd *iofd; + + /*! control message buffer for passing sctp_sndrcvinfo along */ + char cmsg[0]; /* size is determined by iofd->cmsg_size on recvmsg, and by mcghdr->msg_controllen on sendmsg */ +}; + +enum iofd_seg_act { + IOFD_SEG_ACT_HANDLE_ONE, + IOFD_SEG_ACT_HANDLE_MORE, + IOFD_SEG_ACT_DEFER, +}; + +struct iofd_msghdr *iofd_msghdr_alloc(struct osmo_io_fd *iofd, enum iofd_msg_action action, struct msgb *msg, size_t cmsg_size); +void iofd_msghdr_free(struct iofd_msghdr *msghdr); + +struct msgb *iofd_msgb_alloc(struct osmo_io_fd *iofd); +struct msgb *iofd_msgb_pending(struct osmo_io_fd *iofd); +struct msgb *iofd_msgb_pending_or_alloc(struct osmo_io_fd *iofd); + +void iofd_handle_recv(struct osmo_io_fd *iofd, struct msgb *msg, int rc, struct iofd_msghdr *msghdr); +void iofd_handle_send_completion(struct osmo_io_fd *iofd, int rc, struct iofd_msghdr *msghdr); +void iofd_handle_segmented_read(struct osmo_io_fd *iofd, struct msgb *msg, int rc); + +int iofd_txqueue_enqueue(struct osmo_io_fd *iofd, struct iofd_msghdr *msghdr); +void iofd_txqueue_enqueue_front(struct osmo_io_fd *iofd, struct iofd_msghdr *msghdr); +struct iofd_msghdr *iofd_txqueue_dequeue(struct osmo_io_fd *iofd); diff --git a/src/core/osmo_io_poll.c b/src/core/osmo_io_poll.c new file mode 100644 index 00000000..c4bb376d --- /dev/null +++ b/src/core/osmo_io_poll.c @@ -0,0 +1,201 @@ +/*! \file osmo_io_poll.c + * New osmocom async I/O API. + * + * (C) 2022 by Harald Welte <laforge@osmocom.org> + * (C) 2022-2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * Author: Daniel Willmann <dwillmann@sysmocom.de> + * + * All Rights Reserved. + * + * SPDX-License-Identifier: GPL-2.0+ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include "../config.h" +#ifndef EMBEDDED + +#include <errno.h> +#include <stdio.h> +#include <talloc.h> +#include <unistd.h> +#include <stdbool.h> +#include <sys/socket.h> + +#include <osmocom/core/osmo_io.h> +#include <osmocom/core/linuxlist.h> +#include <osmocom/core/logging.h> +#include <osmocom/core/msgb.h> +#include <osmocom/core/select.h> +#include <osmocom/core/socket.h> +#include <osmocom/core/talloc.h> +#include <osmocom/core/utils.h> + +#include "osmo_io_internal.h" + +static void iofd_poll_ofd_cb_recvmsg_sendmsg(struct osmo_fd *ofd, unsigned int what) +{ + struct osmo_io_fd *iofd = ofd->data; + struct msgb *msg; + int rc, flags = 0; + + if (what & OSMO_FD_READ) { + struct iofd_msghdr hdr; + + msg = iofd_msgb_pending_or_alloc(iofd); + if (!msg) { + LOGPIO(iofd, LOGL_ERROR, "Could not allocate msgb for reading\n"); + OSMO_ASSERT(0); + } + + hdr.msg = msg; + hdr.iov[0].iov_base = msg->tail; + hdr.iov[0].iov_len = msgb_tailroom(msg); + hdr.hdr = (struct msghdr) { + .msg_iov = &hdr.iov[0], + .msg_iovlen = 1, + .msg_name = &hdr.osa.u.sa, + .msg_namelen = sizeof(struct osmo_sockaddr), + }; + if (iofd->mode == OSMO_IO_FD_MODE_RECVMSG_SENDMSG) { + hdr.hdr.msg_control = alloca(iofd->cmsg_size); + hdr.hdr.msg_controllen = iofd->cmsg_size; + } + + rc = recvmsg(ofd->fd, &hdr.hdr, flags); + if (rc > 0) + msgb_put(msg, rc); + + iofd_handle_recv(iofd, msg, (rc < 0 && errno > 0) ? -errno : rc, &hdr); + } + + if (IOFD_FLAG_ISSET(iofd, IOFD_FLAG_CLOSED)) + return; + + if (what & OSMO_FD_WRITE) { + struct iofd_msghdr *msghdr = iofd_txqueue_dequeue(iofd); + if (msghdr) { + rc = sendmsg(ofd->fd, &msghdr->hdr, msghdr->flags); + iofd_handle_send_completion(iofd, (rc < 0 && errno > 0) ? -errno : rc, msghdr); + } else { + /* Socket is writable, but we have no data to send. A non-blocking/async + connect() is signalled this way. */ + switch (iofd->mode) { + case OSMO_IO_FD_MODE_READ_WRITE: + iofd->io_ops.write_cb(iofd, 0, NULL); + break; + case OSMO_IO_FD_MODE_RECVFROM_SENDTO: + iofd->io_ops.sendto_cb(iofd, 0, NULL, NULL); + break; + case OSMO_IO_FD_MODE_RECVMSG_SENDMSG: + iofd->io_ops.sendmsg_cb(iofd, 0, NULL); + break; + default: + break; + } + if (osmo_iofd_txqueue_len(iofd) == 0) + iofd_poll_ops.write_disable(iofd); + } + } +} + +static int iofd_poll_ofd_cb_dispatch(struct osmo_fd *ofd, unsigned int what) +{ + struct osmo_io_fd *iofd = ofd->data; + + IOFD_FLAG_SET(iofd, IOFD_FLAG_IN_CALLBACK); + iofd_poll_ofd_cb_recvmsg_sendmsg(ofd, what); + IOFD_FLAG_UNSET(iofd, IOFD_FLAG_IN_CALLBACK); + + if (IOFD_FLAG_ISSET(iofd, IOFD_FLAG_TO_FREE)) { + talloc_free(iofd); + return 0; + } + + return 0; +} + +static int iofd_poll_register(struct osmo_io_fd *iofd) +{ + struct osmo_fd *ofd = &iofd->u.poll.ofd; + int rc; + + if (IOFD_FLAG_ISSET(iofd, IOFD_FLAG_FD_REGISTERED)) + return 0; + osmo_fd_setup(ofd, iofd->fd, 0, &iofd_poll_ofd_cb_dispatch, iofd, 0); + rc = osmo_fd_register(ofd); + if (!rc) + IOFD_FLAG_SET(iofd, IOFD_FLAG_FD_REGISTERED); + return rc; +} + +static int iofd_poll_unregister(struct osmo_io_fd *iofd) +{ + struct osmo_fd *ofd = &iofd->u.poll.ofd; + + if (!IOFD_FLAG_ISSET(iofd, IOFD_FLAG_FD_REGISTERED)) + return 0; + osmo_fd_unregister(ofd); + IOFD_FLAG_UNSET(iofd, IOFD_FLAG_FD_REGISTERED); + + return 0; +} + +static int iofd_poll_close(struct osmo_io_fd *iofd) +{ + iofd_poll_unregister(iofd); + osmo_fd_close(&iofd->u.poll.ofd); + + return 0; +} + +static void iofd_poll_read_enable(struct osmo_io_fd *iofd) +{ + osmo_fd_read_enable(&iofd->u.poll.ofd); +} + +static void iofd_poll_read_disable(struct osmo_io_fd *iofd) +{ + osmo_fd_read_disable(&iofd->u.poll.ofd); +} + +static void iofd_poll_write_enable(struct osmo_io_fd *iofd) +{ + osmo_fd_write_enable(&iofd->u.poll.ofd); +} + +static void iofd_poll_write_disable(struct osmo_io_fd *iofd) +{ + osmo_fd_write_disable(&iofd->u.poll.ofd); +} + +static void iofd_poll_notify_connected(struct osmo_io_fd *iofd) +{ + int rc; + + rc = iofd_poll_register(iofd); + if (rc < 0) + return; + osmo_fd_write_enable(&iofd->u.poll.ofd); +} + +const struct iofd_backend_ops iofd_poll_ops = { + .register_fd = iofd_poll_register, + .unregister_fd = iofd_poll_unregister, + .close = iofd_poll_close, + .write_enable = iofd_poll_write_enable, + .write_disable = iofd_poll_write_disable, + .read_enable = iofd_poll_read_enable, + .read_disable = iofd_poll_read_disable, + .notify_connected = iofd_poll_notify_connected, +}; + +#endif /* ifndef EMBEDDED */ diff --git a/src/core/osmo_io_uring.c b/src/core/osmo_io_uring.c new file mode 100644 index 00000000..569f1505 --- /dev/null +++ b/src/core/osmo_io_uring.c @@ -0,0 +1,532 @@ +/*! \file osmo_io_uring.c + * io_uring backend for osmo_io. + * + * (C) 2022-2023 by sysmocom s.f.m.c. + * Author: Daniel Willmann <daniel@sysmocom.de> + * (C) 2023-2024 by Harald Welte <laforge@osmocom.org> + * + * All Rights Reserved. + * + * SPDX-License-Identifier: GPL-2.0+ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/* TODO: + * Parameters: + * - number of simultaneous read/write in uring for given fd + * + */ + +#include "../config.h" +#if defined(__linux__) + +#include <stdio.h> +#include <talloc.h> +#include <unistd.h> +#include <string.h> +#include <stdbool.h> +#include <errno.h> + +#include <netinet/in.h> +#include <netinet/sctp.h> +#include <sys/eventfd.h> +#include <liburing.h> + +#include <osmocom/core/osmo_io.h> +#include <osmocom/core/linuxlist.h> +#include <osmocom/core/logging.h> +#include <osmocom/core/msgb.h> +#include <osmocom/core/select.h> +#include <osmocom/core/talloc.h> +#include <osmocom/core/utils.h> +#include <osmocom/core/socket.h> + +#include "osmo_io_internal.h" + +#define IOFD_URING_ENTRIES 4096 + +struct osmo_io_uring { + struct osmo_fd event_ofd; + struct io_uring ring; +}; + +static __thread struct osmo_io_uring g_ring; + +static void iofd_uring_cqe(struct io_uring *ring); + +/*! read call-back for eventfd notifying us if entries are in the completion queue */ +static int iofd_uring_poll_cb(struct osmo_fd *ofd, unsigned int what) +{ + struct io_uring *ring = ofd->data; + eventfd_t val; + int rc; + + if (what & OSMO_FD_READ) { + rc = eventfd_read(ofd->fd, &val); + if (rc < 0) { + LOGP(DLIO, LOGL_ERROR, "eventfd_read() returned error\n"); + return rc; + } + + iofd_uring_cqe(ring); + } + if (what & OSMO_FD_WRITE) + OSMO_ASSERT(0); + + return 0; +} + +/*! initialize the uring and tie it into our event loop */ +void osmo_iofd_uring_init(void) +{ + int rc, evfd; + + rc = io_uring_queue_init(IOFD_URING_ENTRIES, &g_ring.ring, 0); + if (rc < 0) + osmo_panic("failure during io_uring_queue_init(): %s\n", strerror(-rc)); + + rc = eventfd(0, 0); + if (rc < 0) { + io_uring_queue_exit(&g_ring.ring); + osmo_panic("failure creating eventfd(0, 0) for io_uring: %s\n", strerror(-rc)); + } + evfd = rc; + + osmo_fd_setup(&g_ring.event_ofd, evfd, OSMO_FD_READ, iofd_uring_poll_cb, &g_ring.ring, 0); + rc = osmo_fd_register(&g_ring.event_ofd); + if (rc < 0) { + close(evfd); + io_uring_queue_exit(&g_ring.ring); + osmo_panic("failure registering io_uring-eventfd as osmo_fd: %d\n", rc); + } + rc = io_uring_register_eventfd(&g_ring.ring, evfd); + if (rc < 0) { + osmo_fd_unregister(&g_ring.event_ofd); + close(evfd); + io_uring_queue_exit(&g_ring.ring); + osmo_panic("failure registering eventfd with io_uring: %s\n", strerror(-rc)); + } +} + + +static void iofd_uring_submit_recv(struct osmo_io_fd *iofd, enum iofd_msg_action action) +{ + struct msgb *msg; + struct iofd_msghdr *msghdr; + struct io_uring_sqe *sqe; + + msg = iofd_msgb_pending_or_alloc(iofd); + if (!msg) { + LOGPIO(iofd, LOGL_ERROR, "Could not allocate msgb for reading\n"); + OSMO_ASSERT(0); + } + + msghdr = iofd_msghdr_alloc(iofd, action, msg, iofd->cmsg_size); + if (!msghdr) { + LOGPIO(iofd, LOGL_ERROR, "Could not allocate msghdr for reading\n"); + OSMO_ASSERT(0); + } + + msghdr->iov[0].iov_base = msg->tail; + msghdr->iov[0].iov_len = msgb_tailroom(msg); + + switch (action) { + case IOFD_ACT_READ: + break; + case IOFD_ACT_RECVMSG: + msghdr->hdr.msg_control = msghdr->cmsg; + msghdr->hdr.msg_controllen = iofd->cmsg_size; + /* fall-through */ + case IOFD_ACT_RECVFROM: + msghdr->hdr.msg_iov = &msghdr->iov[0]; + msghdr->hdr.msg_iovlen = 1; + msghdr->hdr.msg_name = &msghdr->osa.u.sa; + msghdr->hdr.msg_namelen = osmo_sockaddr_size(&msghdr->osa); + break; + default: + OSMO_ASSERT(0); + } + + sqe = io_uring_get_sqe(&g_ring.ring); + if (!sqe) { + LOGPIO(iofd, LOGL_ERROR, "Could not get io_uring_sqe\n"); + OSMO_ASSERT(0); + } + + switch (action) { + case IOFD_ACT_READ: + io_uring_prep_readv(sqe, iofd->fd, msghdr->iov, 1, 0); + break; + case IOFD_ACT_RECVMSG: + case IOFD_ACT_RECVFROM: + io_uring_prep_recvmsg(sqe, iofd->fd, &msghdr->hdr, msghdr->flags); + break; + default: + OSMO_ASSERT(0); + } + io_uring_sqe_set_data(sqe, msghdr); + + io_uring_submit(&g_ring.ring); + /* NOTE: This only works if we have one read per fd */ + iofd->u.uring.read_msghdr = msghdr; +} + +/*! completion call-back for READ/RECVFROM */ +static void iofd_uring_handle_recv(struct iofd_msghdr *msghdr, int rc) +{ + struct osmo_io_fd *iofd = msghdr->iofd; + struct msgb *msg = msghdr->msg; + + if (rc > 0) + msgb_put(msg, rc); + + if (!IOFD_FLAG_ISSET(iofd, IOFD_FLAG_CLOSED)) + iofd_handle_recv(iofd, msg, rc, msghdr); + + if (iofd->u.uring.read_enabled && !IOFD_FLAG_ISSET(iofd, IOFD_FLAG_CLOSED)) + iofd_uring_submit_recv(iofd, msghdr->action); + else + iofd->u.uring.read_msghdr = NULL; + + + iofd_msghdr_free(msghdr); +} + +static int iofd_uring_submit_tx(struct osmo_io_fd *iofd); + +/*! completion call-back for WRITE/SENDTO */ +static void iofd_uring_handle_tx(struct iofd_msghdr *msghdr, int rc) +{ + struct osmo_io_fd *iofd = msghdr->iofd; + + /* Detach msghdr from iofd. It might get freed here or it is freed during iofd_handle_send_completion(). + * If there is pending data to send, iofd_uring_submit_tx() will attach it again. + * iofd_handle_send_completion() will invoke a callback function to signal the possibility of write/send. + * This callback function might close iofd, leading to the potential freeing of iofd->u.uring.write_msghdr if + * still attached. Since iofd_handle_send_completion() frees msghdr at the end of the function, detaching + * msghdr here prevents a double-free bug. */ + if (iofd->u.uring.write_msghdr == msghdr) + iofd->u.uring.write_msghdr = NULL; + + if (OSMO_UNLIKELY(IOFD_FLAG_ISSET(iofd, IOFD_FLAG_CLOSED))) { + msgb_free(msghdr->msg); + iofd_msghdr_free(msghdr); + } else { + iofd_handle_send_completion(iofd, rc, msghdr); + } + + /* submit the next to-be-transmitted message for this file descriptor */ + if (iofd->u.uring.write_enabled && !IOFD_FLAG_ISSET(iofd, IOFD_FLAG_CLOSED)) + iofd_uring_submit_tx(iofd); +} + +/*! handle completion of a single I/O message */ +static void iofd_uring_handle_completion(struct iofd_msghdr *msghdr, int res) +{ + struct osmo_io_fd *iofd = msghdr->iofd; + + IOFD_FLAG_SET(iofd, IOFD_FLAG_IN_CALLBACK); + + switch (msghdr->action) { + case IOFD_ACT_READ: + case IOFD_ACT_RECVFROM: + case IOFD_ACT_RECVMSG: + iofd_uring_handle_recv(msghdr, res); + break; + case IOFD_ACT_WRITE: + case IOFD_ACT_SENDTO: + case IOFD_ACT_SENDMSG: + iofd_uring_handle_tx(msghdr, res); + break; + default: + OSMO_ASSERT(0) + } + + IOFD_FLAG_UNSET(iofd, IOFD_FLAG_IN_CALLBACK); + + if (IOFD_FLAG_ISSET(iofd, IOFD_FLAG_TO_FREE) && !iofd->u.uring.read_msghdr && !iofd->u.uring.write_msghdr) + talloc_free(iofd); +} + +/*! process all pending completion queue entries in given io_uring */ +static void iofd_uring_cqe(struct io_uring *ring) +{ + int rc; + struct io_uring_cqe *cqe; + struct iofd_msghdr *msghdr; + + while (io_uring_peek_cqe(ring, &cqe) == 0) { + + msghdr = io_uring_cqe_get_data(cqe); + if (!msghdr) { + LOGP(DLIO, LOGL_DEBUG, "Cancellation returned\n"); + io_uring_cqe_seen(ring, cqe); + continue; + } + if (!msghdr->iofd) { + io_uring_cqe_seen(ring, cqe); + iofd_msghdr_free(msghdr); + continue; + } + + rc = cqe->res; + /* Hand the entry back to the kernel before */ + io_uring_cqe_seen(ring, cqe); + + iofd_uring_handle_completion(msghdr, rc); + + } +} + +/*! will submit the next to-be-transmitted message for given iofd */ +static int iofd_uring_submit_tx(struct osmo_io_fd *iofd) +{ + struct io_uring_sqe *sqe; + struct iofd_msghdr *msghdr; + + msghdr = iofd_txqueue_dequeue(iofd); + if (!msghdr) + return -ENODATA; + + sqe = io_uring_get_sqe(&g_ring.ring); + if (!sqe) { + LOGPIO(iofd, LOGL_ERROR, "Could not get io_uring_sqe\n"); + OSMO_ASSERT(0); + } + + io_uring_sqe_set_data(sqe, msghdr); + + switch (msghdr->action) { + case IOFD_ACT_WRITE: + case IOFD_ACT_SENDTO: + case IOFD_ACT_SENDMSG: + io_uring_prep_sendmsg(sqe, msghdr->iofd->fd, &msghdr->hdr, msghdr->flags); + break; + default: + OSMO_ASSERT(0); + } + + io_uring_submit(&g_ring.ring); + iofd->u.uring.write_msghdr = msghdr; + + return 0; +} + +static void iofd_uring_write_enable(struct osmo_io_fd *iofd); +static void iofd_uring_read_enable(struct osmo_io_fd *iofd); + +static int iofd_uring_register(struct osmo_io_fd *iofd) +{ + return 0; +} + +static int iofd_uring_unregister(struct osmo_io_fd *iofd) +{ + struct io_uring_sqe *sqe; + struct iofd_msghdr *msghdr; + + if (iofd->u.uring.read_msghdr) { + msghdr = iofd->u.uring.read_msghdr; + sqe = io_uring_get_sqe(&g_ring.ring); + OSMO_ASSERT(sqe != NULL); + io_uring_sqe_set_data(sqe, NULL); + LOGPIO(iofd, LOGL_DEBUG, "Cancelling read\n"); + iofd->u.uring.read_msghdr = NULL; + talloc_steal(OTC_GLOBAL, msghdr); + msghdr->iofd = NULL; + io_uring_prep_cancel(sqe, msghdr, 0); + } + + if (iofd->u.uring.write_msghdr) { + msghdr = iofd->u.uring.write_msghdr; + sqe = io_uring_get_sqe(&g_ring.ring); + OSMO_ASSERT(sqe != NULL); + io_uring_sqe_set_data(sqe, NULL); + LOGPIO(iofd, LOGL_DEBUG, "Cancelling write\n"); + iofd->u.uring.write_msghdr = NULL; + talloc_steal(OTC_GLOBAL, msghdr); + msgb_free(msghdr->msg); + msghdr->iofd = NULL; + io_uring_prep_cancel(sqe, msghdr, 0); + } + io_uring_submit(&g_ring.ring); + + if (IOFD_FLAG_ISSET(iofd, IOFD_FLAG_NOTIFY_CONNECTED)) { + osmo_fd_unregister(&iofd->u.uring.connect_ofd); + IOFD_FLAG_UNSET(iofd, IOFD_FLAG_NOTIFY_CONNECTED); + } + + return 0; +} + +static void iofd_uring_write_enable(struct osmo_io_fd *iofd) +{ + iofd->u.uring.write_enabled = true; + + if (iofd->u.uring.write_msghdr) + return; + + /* This function is called again, once the socket is connected. */ + if (IOFD_FLAG_ISSET(iofd, IOFD_FLAG_NOTIFY_CONNECTED)) + return; + + if (osmo_iofd_txqueue_len(iofd) > 0) + iofd_uring_submit_tx(iofd); + else if (iofd->mode == OSMO_IO_FD_MODE_READ_WRITE) { + /* Empty write request to check when the socket is connected */ + struct iofd_msghdr *msghdr; + struct io_uring_sqe *sqe; + struct msgb *msg = msgb_alloc_headroom(0, 0, "io_uring write dummy"); + if (!msg) { + LOGPIO(iofd, LOGL_ERROR, "Could not allocate msgb for writing\n"); + OSMO_ASSERT(0); + } + msghdr = iofd_msghdr_alloc(iofd, IOFD_ACT_WRITE, msg, 0); + if (!msghdr) { + LOGPIO(iofd, LOGL_ERROR, "Could not allocate msghdr for writing\n"); + OSMO_ASSERT(0); + } + + msghdr->iov[0].iov_base = msgb_data(msg); + msghdr->iov[0].iov_len = msgb_length(msg); + + sqe = io_uring_get_sqe(&g_ring.ring); + if (!sqe) { + LOGPIO(iofd, LOGL_ERROR, "Could not get io_uring_sqe\n"); + OSMO_ASSERT(0); + } + io_uring_prep_writev(sqe, iofd->fd, msghdr->iov, 1, 0); + io_uring_sqe_set_data(sqe, msghdr); + + io_uring_submit(&g_ring.ring); + iofd->u.uring.write_msghdr = msghdr; + } +} + +static void iofd_uring_write_disable(struct osmo_io_fd *iofd) +{ + iofd->u.uring.write_enabled = false; +} + +static void iofd_uring_read_enable(struct osmo_io_fd *iofd) +{ + iofd->u.uring.read_enabled = true; + + if (iofd->u.uring.read_msghdr) + return; + + /* This function is called again, once the socket is connected. */ + if (IOFD_FLAG_ISSET(iofd, IOFD_FLAG_NOTIFY_CONNECTED)) + return; + + switch (iofd->mode) { + case OSMO_IO_FD_MODE_READ_WRITE: + iofd_uring_submit_recv(iofd, IOFD_ACT_READ); + break; + case OSMO_IO_FD_MODE_RECVFROM_SENDTO: + iofd_uring_submit_recv(iofd, IOFD_ACT_RECVFROM); + break; + case OSMO_IO_FD_MODE_RECVMSG_SENDMSG: + iofd_uring_submit_recv(iofd, IOFD_ACT_RECVMSG); + break; + default: + OSMO_ASSERT(0); + } +} + +static void iofd_uring_read_disable(struct osmo_io_fd *iofd) +{ + iofd->u.uring.read_enabled = false; +} + +static int iofd_uring_close(struct osmo_io_fd *iofd) +{ + iofd_uring_read_disable(iofd); + iofd_uring_write_disable(iofd); + iofd_uring_unregister(iofd); + return close(iofd->fd); +} + +/* called via osmocom poll/select main handling once outbound non-blocking connect() completes */ +static int iofd_uring_connected_cb(struct osmo_fd *ofd, unsigned int what) +{ + struct osmo_io_fd *iofd = ofd->data; + + LOGPIO(iofd, LOGL_DEBUG, "Socket connected or failed.\n"); + + if (!(what & OSMO_FD_WRITE)) + return 0; + + /* Unregister from poll/select handling. */ + osmo_fd_unregister(ofd); + IOFD_FLAG_UNSET(iofd, IOFD_FLAG_NOTIFY_CONNECTED); + + /* Notify the application about this via a zero-length write completion call-back. */ + IOFD_FLAG_SET(iofd, IOFD_FLAG_IN_CALLBACK); + switch (iofd->mode) { + case OSMO_IO_FD_MODE_READ_WRITE: + iofd->io_ops.write_cb(iofd, 0, NULL); + break; + case OSMO_IO_FD_MODE_RECVFROM_SENDTO: + iofd->io_ops.sendto_cb(iofd, 0, NULL, NULL); + break; + case OSMO_IO_FD_MODE_RECVMSG_SENDMSG: + iofd->io_ops.sendmsg_cb(iofd, 0, NULL); + break; + } + IOFD_FLAG_UNSET(iofd, IOFD_FLAG_IN_CALLBACK); + + /* If write/read notifications are pending, enable it now. */ + if (iofd->u.uring.write_enabled && !IOFD_FLAG_ISSET(iofd, IOFD_FLAG_CLOSED)) + iofd_uring_write_enable(iofd); + if (iofd->u.uring.read_enabled && !IOFD_FLAG_ISSET(iofd, IOFD_FLAG_CLOSED)) + iofd_uring_read_enable(iofd); + + if (IOFD_FLAG_ISSET(iofd, IOFD_FLAG_TO_FREE) && !iofd->u.uring.read_msghdr && !iofd->u.uring.write_msghdr) + talloc_free(iofd); + return 0; +} + +static void iofd_uring_notify_connected(struct osmo_io_fd *iofd) +{ + if (iofd->mode == OSMO_IO_FD_MODE_RECVMSG_SENDMSG) { + /* Don't call this function after enabling read or write. */ + OSMO_ASSERT(!iofd->u.uring.write_enabled && !iofd->u.uring.read_enabled); + + /* Use a temporary osmo_fd which we can use to notify us once the connection is established + * or failed (indicated by FD becoming writable). + * This is needed as (at least for SCTP sockets) one cannot submit a zero-length writev/sendmsg + * in order to get notification when the socekt is writable.*/ + if (!IOFD_FLAG_ISSET(iofd, IOFD_FLAG_NOTIFY_CONNECTED)) { + osmo_fd_setup(&iofd->u.uring.connect_ofd, iofd->fd, OSMO_FD_WRITE, + iofd_uring_connected_cb, iofd, 0); + if (osmo_fd_register(&iofd->u.uring.connect_ofd) < 0) + LOGPIO(iofd, LOGL_ERROR, "Failed to register FD for connect event.\n"); + else + IOFD_FLAG_SET(iofd, IOFD_FLAG_NOTIFY_CONNECTED); + } + } else + iofd_uring_write_enable(iofd); +} + +const struct iofd_backend_ops iofd_uring_ops = { + .register_fd = iofd_uring_register, + .unregister_fd = iofd_uring_unregister, + .close = iofd_uring_close, + .write_enable = iofd_uring_write_enable, + .write_disable = iofd_uring_write_disable, + .read_enable = iofd_uring_read_enable, + .read_disable = iofd_uring_read_disable, + .notify_connected = iofd_uring_notify_connected, +}; + +#endif /* defined(__linux__) */ diff --git a/src/panic.c b/src/core/panic.c index 072f458b..bbf6d081 100644 --- a/src/panic.c +++ b/src/core/panic.c @@ -17,10 +17,6 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ /*! \addtogroup utils @@ -31,7 +27,7 @@ #include <osmocom/core/panic.h> #include <osmocom/core/backtrace.h> -#include "../config.h" +#include "config.h" static osmo_panic_handler_t osmo_panic_handler = (void*)0; diff --git a/src/plugin.c b/src/core/plugin.c index 40de4f8c..687ad406 100644 --- a/src/plugin.c +++ b/src/core/plugin.c @@ -17,17 +17,13 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ /*! \addtogroup utils * @{ * \file plugin.c */ -#include "../config.h" +#include "config.h" #if HAVE_DLFCN_H diff --git a/src/prbs.c b/src/core/prbs.c index 8fa04bb7..8fa04bb7 100644 --- a/src/prbs.c +++ b/src/core/prbs.c diff --git a/src/prim.c b/src/core/prim.c index 3c8a7f12..3c8a7f12 100644 --- a/src/prim.c +++ b/src/core/prim.c diff --git a/src/probes.d b/src/core/probes.d index e4150f0c..e4150f0c 100644 --- a/src/probes.d +++ b/src/core/probes.d diff --git a/src/rate_ctr.c b/src/core/rate_ctr.c index 4d99699e..44e26585 100644 --- a/src/rate_ctr.c +++ b/src/core/rate_ctr.c @@ -14,10 +14,6 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ /*! \addtogroup rate_ctr @@ -57,14 +53,17 @@ * * \file rate_ctr.c */ +#include <errno.h> #include <stdbool.h> #include <stdint.h> #include <string.h> +#include <unistd.h> +#include <inttypes.h> #include <osmocom/core/utils.h> #include <osmocom/core/linuxlist.h> #include <osmocom/core/talloc.h> -#include <osmocom/core/timer.h> +#include <osmocom/core/select.h> #include <osmocom/core/rate_ctr.h> #include <osmocom/core/logging.h> @@ -307,14 +306,9 @@ static void interval_expired(struct rate_ctr *ctr, enum rate_ctr_intv intv) ctr->intv[intv].rate = ctr->current - ctr->intv[intv].last; /* save current counter for next interval */ ctr->intv[intv].last = ctr->current; - - /* update the rate of the next bigger interval. This will - * be overwritten when that next larger interval expires */ - if (intv + 1 < ARRAY_SIZE(ctr->intv)) - ctr->intv[intv+1].rate += ctr->intv[intv].rate; } -static struct osmo_timer_list rate_ctr_timer; +static struct osmo_fd rate_ctr_timer = { .fd = -1 }; static uint64_t timer_ticks; /* The one-second interval has expired */ @@ -335,18 +329,35 @@ static void rate_ctr_group_intv(struct rate_ctr_group *grp) } } -static void rate_ctr_timer_cb(void *data) +static int rate_ctr_timer_cb(struct osmo_fd *ofd, unsigned int what) { struct rate_ctr_group *ctrg; + uint64_t expire_count; + int rc; + + /* check that the timer has actually expired */ + if (!(what & OSMO_FD_READ)) + return 0; - /* Increment number of ticks before we calculate intervals, - * as a counter value of 0 would already wrap all counters */ - timer_ticks++; + /* read from timerfd: number of expirations of periodic timer */ + rc = read(ofd->fd, (void *) &expire_count, sizeof(expire_count)); + if (rc < 0 && errno == EAGAIN) + return 0; - llist_for_each_entry(ctrg, &rate_ctr_groups, list) - rate_ctr_group_intv(ctrg); + OSMO_ASSERT(rc == sizeof(expire_count)); - osmo_timer_schedule(&rate_ctr_timer, 1, 0); + if (expire_count > 1) + LOGP(DLGLOBAL, LOGL_NOTICE, "Stats timer expire_count=%" PRIu64 ": We missed %" PRIu64 " timers\n", + expire_count, expire_count - 1); + + do { /* Increment number of ticks before we calculate intervals, + * as a counter value of 0 would already wrap all counters */ + timer_ticks++; + llist_for_each_entry(ctrg, &rate_ctr_groups, list) + rate_ctr_group_intv(ctrg); + } while (--expire_count); + + return 0; } /*! Initialize the counter module. Call this once from your application. @@ -354,9 +365,27 @@ static void rate_ctr_timer_cb(void *data) * \returns 0 on success; negative on error */ int rate_ctr_init(void *tall_ctx) { + struct timespec ts_interval = { .tv_sec = 1, .tv_nsec = 0 }; + int rc; + + /* ignore repeated initialization */ + if (osmo_fd_is_registered(&rate_ctr_timer)) + return 0; + tall_rate_ctr_ctx = tall_ctx; - osmo_timer_setup(&rate_ctr_timer, rate_ctr_timer_cb, NULL); - osmo_timer_schedule(&rate_ctr_timer, 1, 0); + + rc = osmo_timerfd_setup(&rate_ctr_timer, rate_ctr_timer_cb, NULL); + if (rc < 0) { + LOGP(DLGLOBAL, LOGL_ERROR, "Failed to setup the timer with error code %d (fd=%d)\n", + rc, rate_ctr_timer.fd); + return rc; + } + + rc = osmo_timerfd_schedule(&rate_ctr_timer, NULL, &ts_interval); + if (rc < 0) { + LOGP(DLGLOBAL, LOGL_ERROR, "Failed to schedule the timer with error code %d (fd=%d)\n", + rc, rate_ctr_timer.fd); + } return 0; } diff --git a/src/rbtree.c b/src/core/rbtree.c index 211978f1..f4dc219b 100644 --- a/src/rbtree.c +++ b/src/core/rbtree.c @@ -15,11 +15,6 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, - MA 02110-1301, USA - linux/lib/rbtree.c */ diff --git a/src/select.c b/src/core/select.c index f7eb5ea3..70047f09 100644 --- a/src/select.c +++ b/src/core/select.c @@ -5,7 +5,7 @@ * of the linux 2.4 netfilter subsystem. */ /* * (C) 2000-2020 by Harald Welte <laforge@gnumonks.org> - * All Rights Reserverd. + * All Rights Reserved. * * SPDX-License-Identifier: GPL-2.0+ * @@ -18,11 +18,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, - * MA 02110-1301, USA. */ #include <fcntl.h> @@ -38,8 +33,10 @@ #include <osmocom/core/logging.h> #include <osmocom/core/talloc.h> #include <osmocom/core/utils.h> +#include <osmocom/core/stat_item.h> +#include <osmocom/core/stats_tcp.h> -#include "../config.h" +#include "config.h" #if defined(HAVE_SYS_SELECT_H) && defined(HAVE_POLL_H) #include <sys/select.h> @@ -57,6 +54,39 @@ static __thread int maxfd = 0; static __thread struct llist_head osmo_fds; /* TLS cannot use LLIST_HEAD() */ static __thread int unregistered_count; +/* Array of struct osmo_fd * (size "max_fd") ordered by "ofd->fd" */ +static __thread struct { + struct osmo_fd **table; + unsigned int size; +} osmo_fd_lookup; + +static void osmo_fd_lookup_table_extend(unsigned int new_max_fd) +{ + unsigned int min_new_size; + unsigned int pw2; + unsigned int new_size; + + /* First calculate the minimally required new size of the array in bytes: */ + min_new_size = (new_max_fd + 1) * sizeof(struct osmo_fd *); + /* Now find the lower power of two of min_new_size (in bytes): */ + pw2 = 32 - __builtin_clz(min_new_size); + /* get next (upper side) power of 2: */ + pw2++; + OSMO_ASSERT(pw2 <= 31); /* Avoid shifting more than 31 bits */ + new_size = 1 << (pw2 + 1); + + /* Let's make it at least 1024B, to avoid reallocating quickly at startup */ + if (new_size < 1024) + new_size = 1024; + if (new_size > osmo_fd_lookup.size) { + uint8_t *ptr = talloc_realloc_size(OTC_GLOBAL, osmo_fd_lookup.table, new_size); + OSMO_ASSERT(ptr); + memset(ptr + osmo_fd_lookup.size, 0, new_size - osmo_fd_lookup.size); + osmo_fd_lookup.table = (struct osmo_fd **)ptr; + osmo_fd_lookup.size = new_size; + } +} + #ifndef FORCE_IO_SELECT struct poll_state { /* array of pollfd */ @@ -121,6 +151,9 @@ bool osmo_fd_is_registered(struct osmo_fd *fd) /*! Register a new file descriptor with select loop abstraction * \param[in] fd osmocom file descriptor to be registered * \returns 0 on success; negative in case of error + * + * The API expects fd field of the struct osmo_fd to remain unchanged while + * registered, ie until osmo_fd_unregister() is called on it. */ int osmo_fd_register(struct osmo_fd *fd) { @@ -145,8 +178,10 @@ int osmo_fd_register(struct osmo_fd *fd) return flags; /* Register FD */ - if (fd->fd > maxfd) + if (fd->fd > maxfd) { maxfd = fd->fd; + osmo_fd_lookup_table_extend(maxfd); + } #ifdef OSMO_FD_CHECK if (osmo_fd_is_registered(fd)) { @@ -169,12 +204,17 @@ int osmo_fd_register(struct osmo_fd *fd) #endif /* FORCE_IO_SELECT */ llist_add_tail(&fd->list, &osmo_fds); + osmo_fd_lookup.table[fd->fd] = fd; return 0; } /*! Unregister a file descriptor from select loop abstraction * \param[in] fd osmocom file descriptor to be unregistered + * + * Caller is responsible for ensuring the fd is really registered before calling this API. + * This function must be called before changing the value of the fd field in + * the struct osmo_fd. */ void osmo_fd_unregister(struct osmo_fd *fd) { @@ -186,6 +226,24 @@ void osmo_fd_unregister(struct osmo_fd *fd) #ifndef FORCE_IO_SELECT g_poll.num_registered--; #endif /* FORCE_IO_SELECT */ + + if (OSMO_UNLIKELY(fd->fd < 0 || fd->fd > maxfd)) { + /* Some old users used to incorrectly set fd = -1 *before* calling osmo_unregister(). + * Hence, in order to keep backward compatibility it's not possible to assert() here. + * Instead, print an error message since this is actually a bug in the API user. */ +#ifdef OSMO_FD_CHECK + osmo_panic("osmo_fd_unregister(fd=%u) out of expected range (0..%u), fix your code!!!\n", + fd->fd, maxfd); +#else + fprintf(stderr, "osmo_fd_unregister(fd=%u) out of expected range (0..%u), fix your code!!!\n", + fd->fd, maxfd); + return; +#endif + } + + osmo_fd_lookup.table[fd->fd] = NULL; + /* If existent, free any statistical data */ + osmo_stats_tcp_osmo_fd_unregister(fd); } /*! Close a file descriptor, mark it as closed + unregister from select loop abstraction @@ -316,7 +374,7 @@ static unsigned int poll_fill_fds(void) } /* iterate over first n_fd entries of g_poll.poll + dispatch */ -static int poll_disp_fds(int n_fd) +static int poll_disp_fds(unsigned int n_fd) { struct osmo_fd *ufd; unsigned int i; @@ -372,14 +430,20 @@ static int _osmo_select_main(int polling) { unsigned int n_poll; int rc; + int timeout = 0; /* prepare read and write fdsets */ n_poll = poll_fill_fds(); - if (!polling) + if (!polling) { osmo_timers_prepare(); + timeout = osmo_timers_nearest_ms(); - rc = poll(g_poll.poll, n_poll, polling ? 0 : osmo_timers_nearest_ms()); + if (_osmo_select_shutdown_requested && timeout == -1) + timeout = 0; + } + + rc = poll(g_poll.poll, n_poll, timeout); if (rc < 0) return 0; @@ -458,23 +522,21 @@ int osmo_select_main_ctx(int polling) * \returns \ref osmo_fd for \ref fd; NULL in case it doesn't exist */ struct osmo_fd *osmo_fd_get_by_fd(int fd) { - struct osmo_fd *ofd; - - llist_for_each_entry(ofd, &osmo_fds, list) { - if (ofd->fd == fd) - return ofd; - } - return NULL; + if (fd > maxfd || fd < 0) + return NULL; + return osmo_fd_lookup.table[fd]; } /*! initialize the osmocom select abstraction for the current thread */ void osmo_select_init(void) { INIT_LLIST_HEAD(&osmo_fds); + osmo_fd_lookup_table_extend(0); } -/* ensure main thread always has pre-initialized osmo_fds */ -static __attribute__((constructor)) void on_dso_load_select(void) +/* ensure main thread always has pre-initialized osmo_fds + * priority 102: must run after on_dso_load_ctx */ +static __attribute__((constructor(102))) void on_dso_load_select(void) { osmo_select_init(); } @@ -647,20 +709,20 @@ osmo_signalfd_setup(void *ctx, sigset_t set, osmo_signalfd_cb *cb, void *data) * } * } */ -void osmo_select_shutdown_request() +void osmo_select_shutdown_request(void) { _osmo_select_shutdown_requested++; }; /*! Return the number of times osmo_select_shutdown_request() was called before. */ -int osmo_select_shutdown_requested() +int osmo_select_shutdown_requested(void) { return _osmo_select_shutdown_requested; }; /*! Return true after osmo_select_shutdown_requested() was called, and after an osmo_select poll loop found no more * pending OSMO_FD_WRITE on any registered socket. */ -bool osmo_select_shutdown_done() { +bool osmo_select_shutdown_done(void) { return _osmo_select_shutdown_done; }; diff --git a/src/sercomm.c b/src/core/sercomm.c index 2639bf8a..1798acec 100644 --- a/src/sercomm.c +++ b/src/core/sercomm.c @@ -14,10 +14,6 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ /*! \addtogroup sercomm diff --git a/src/serial.c b/src/core/serial.c index c3bf5e89..117c049b 100644 --- a/src/serial.c +++ b/src/core/serial.c @@ -16,10 +16,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ /*! \addtogroup serial diff --git a/src/signal.c b/src/core/signal.c index be3b7778..ba1555ae 100644 --- a/src/signal.c +++ b/src/core/signal.c @@ -16,10 +16,6 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ #include <osmocom/core/signal.h> diff --git a/src/sockaddr_str.c b/src/core/sockaddr_str.c index f5508a03..bea61849 100644 --- a/src/sockaddr_str.c +++ b/src/core/sockaddr_str.c @@ -20,10 +20,6 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ #include "config.h" @@ -37,6 +33,7 @@ #include <osmocom/core/sockaddr_str.h> #include <osmocom/core/utils.h> #include <osmocom/core/byteswap.h> +#include <osmocom/core/socket.h> /*! \addtogroup sockaddr_str * @@ -363,6 +360,17 @@ int osmo_sockaddr_str_from_sockaddr(struct osmo_sockaddr_str *sockaddr_str, cons return -EINVAL; } +/*! Convert IPv4 or IPv6 address and port to osmo_sockaddr_str. + * \param[out] sockaddr_str The instance to copy to. + * \param[in] src IPv4 or IPv6 address and port data. + * \return 0 on success, negative if src does not indicate AF_INET nor AF_INET6 (or if the conversion fails, which + * should not be possible in practice). + */ +int osmo_sockaddr_str_from_osa(struct osmo_sockaddr_str *sockaddr_str, const struct osmo_sockaddr *src) +{ + return osmo_sockaddr_str_from_sockaddr(sockaddr_str, &src->u.sas); +} + /*! Convert osmo_sockaddr_str address string to IPv4 address data. * \param[in] sockaddr_str The instance to convert the IP of. * \param[out] dst IPv4 address data to write to. @@ -513,5 +521,15 @@ int osmo_sockaddr_str_to_sockaddr(const struct osmo_sockaddr_str *sockaddr_str, } } +/*! Convert osmo_sockaddr_str address string and port to IPv4 or IPv6 address and port data. + * \param[in] sockaddr_str The instance to convert the IP and port of. + * \param[out] dst IPv4/IPv6 address and port data to write to. + * \return 0 on success, negative on error (e.g. invalid IP address string for the family indicated by sockaddr_str->af). + */ +int osmo_sockaddr_str_to_osa(const struct osmo_sockaddr_str *sockaddr_str, struct osmo_sockaddr *dst) +{ + return osmo_sockaddr_str_to_sockaddr(sockaddr_str, &dst->u.sas); +} + /*! @} */ #endif // HAVE_NETINET_IN_H diff --git a/src/socket.c b/src/core/socket.c index 19d48e4f..80a9d0ec 100644 --- a/src/socket.c +++ b/src/core/socket.c @@ -15,13 +15,9 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ -#include "../config.h" +#include "config.h" /*! \addtogroup socket * @{ @@ -30,6 +26,7 @@ * \file socket.c */ #ifdef HAVE_SYS_SOCKET_H +#define _GNU_SOURCE /* for struct ucred on glibc >= 2.8 */ #include <osmocom/core/logging.h> #include <osmocom/core/select.h> @@ -118,7 +115,7 @@ static struct addrinfo *addrinfo_helper(uint16_t family, uint16_t type, uint8_t static int addrinfo_helper_multi(struct addrinfo **addrinfo, uint16_t family, uint16_t type, uint8_t proto, const char **hosts, size_t host_cnt, uint16_t port, bool passive) { - int i, j; + unsigned int i, j; for (i = 0; i < host_cnt; i++) { addrinfo[i] = addrinfo_helper(family, type, proto, hosts[i], port, passive); @@ -214,7 +211,8 @@ static int socket_helper_osa(const struct osmo_sockaddr *addr, uint16_t type, ui static int multiaddr_snprintf(char* buf, size_t buf_len, const char **hosts, size_t host_cnt) { int len = 0, offset = 0, rem = buf_len; - int ret, i; + size_t i; + int ret; char *after; if (buf_len < 3) @@ -252,7 +250,7 @@ static int osmo_sock_init_tail(int fd, uint16_t type, unsigned int flags) if (rc < 0) { LOGP(DLGLOBAL, LOGL_ERROR, "unable to listen on socket: %s\n", strerror(errno)); - return rc; + return -errno; } break; } @@ -487,8 +485,8 @@ int osmo_sock_init2(uint16_t family, uint16_t type, uint8_t proto, } #define _SOCKADDR_TO_STR(dest, sockaddr) do { \ - if (osmo_sockaddr_str_from_sockaddr(&dest, &sockaddr->u.sas)) \ - osmo_strlcpy(dest.ip, "Invalid IP", 11); \ + if (osmo_sockaddr_str_from_sockaddr(dest, &sockaddr->u.sas)) \ + osmo_strlcpy((dest)->ip, "Invalid IP", 11); \ } while (0) /*! Initialize a socket (including bind and/or connect) @@ -523,7 +521,8 @@ int osmo_sock_init_osa(uint16_t type, uint8_t proto, unsigned int flags) { int sfd = -1, rc, on = 1; - struct osmo_sockaddr_str sastr = {}; + struct osmo_sockaddr_str _sastr = {}; + struct osmo_sockaddr_str *sastr = &_sastr; if ((flags & (OSMO_SOCK_F_BIND | OSMO_SOCK_F_CONNECT)) == 0) { LOGP(DLGLOBAL, LOGL_ERROR, "invalid: you have to specify either " @@ -554,8 +553,8 @@ int osmo_sock_init_osa(uint16_t type, uint8_t proto, sfd = socket_helper_osa(local, type, proto, flags); if (sfd < 0) { _SOCKADDR_TO_STR(sastr, local); - LOGP(DLGLOBAL, LOGL_ERROR, "no suitable local addr found for: %s:%u\n", - sastr.ip, sastr.port); + LOGP(DLGLOBAL, LOGL_ERROR, "no suitable local addr found for: " OSMO_SOCKADDR_STR_FMT "\n", + OSMO_SOCKADDR_STR_FMT_ARGS(sastr)); return -ENODEV; } @@ -563,21 +562,21 @@ int osmo_sock_init_osa(uint16_t type, uint8_t proto, rc = setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); if (rc < 0) { + int err = errno; _SOCKADDR_TO_STR(sastr, local); LOGP(DLGLOBAL, LOGL_ERROR, - "cannot setsockopt socket:" - " %s:%u: %s\n", - sastr.ip, sastr.port, - strerror(errno)); + "cannot setsockopt socket: " OSMO_SOCKADDR_STR_FMT ": %s\n", + OSMO_SOCKADDR_STR_FMT_ARGS(sastr), strerror(err)); close(sfd); return rc; } } if (bind(sfd, &local->u.sa, sizeof(struct osmo_sockaddr)) == -1) { + int err = errno; _SOCKADDR_TO_STR(sastr, local); - LOGP(DLGLOBAL, LOGL_ERROR, "unable to bind socket: %s:%u: %s\n", - sastr.ip, sastr.port, strerror(errno)); + LOGP(DLGLOBAL, LOGL_ERROR, "unable to bind socket: " OSMO_SOCKADDR_STR_FMT ": %s\n", + OSMO_SOCKADDR_STR_FMT_ARGS(sastr), strerror(err)); close(sfd); return -1; } @@ -598,9 +597,10 @@ int osmo_sock_init_osa(uint16_t type, uint8_t proto, rc = connect(sfd, &remote->u.sa, sizeof(struct osmo_sockaddr)); if (rc != 0 && errno != EINPROGRESS) { + int err = errno; _SOCKADDR_TO_STR(sastr, remote); - LOGP(DLGLOBAL, LOGL_ERROR, "unable to connect socket: %s:%u: %s\n", - sastr.ip, sastr.port, strerror(errno)); + LOGP(DLGLOBAL, LOGL_ERROR, "unable to connect socket: " OSMO_SOCKADDR_STR_FMT ": %s\n", + OSMO_SOCKADDR_STR_FMT_ARGS(sastr), strerror(err)); close(sfd); return rc; } @@ -617,18 +617,45 @@ int osmo_sock_init_osa(uint16_t type, uint8_t proto, #ifdef HAVE_LIBSCTP -/* Check whether there's an IPv6 Addr as first option of any addrinfo item in the addrinfo set */ +/* Check whether there's an addrinfo item in the addrinfo set with an IPv4 or IPv6 option */ static void addrinfo_has_v4v6addr(const struct addrinfo **result, size_t result_count, bool *has_v4, bool *has_v6) { size_t host_idx; + const struct addrinfo *rp; *has_v4 = false; *has_v6 = false; for (host_idx = 0; host_idx < result_count; host_idx++) { - if (result[host_idx]->ai_family == AF_INET) - *has_v4 = true; - else if (result[host_idx]->ai_family == AF_INET6) - *has_v6 = true; + for (rp = result[host_idx]; rp != NULL; rp = rp->ai_next) { + if (result[host_idx]->ai_family == AF_INET) + *has_v4 = true; + else if (result[host_idx]->ai_family == AF_INET6) + *has_v6 = true; + } + } +} + +/* Check whether there's an addrinfo item in the addrinfo set with only an IPv4 or IPv6 option */ +static void addrinfo_has_v4v6only_addr(const struct addrinfo **result, size_t result_count, bool *has_v4only, bool *has_v6only) +{ + size_t host_idx; + const struct addrinfo *rp; + *has_v4only = false; + *has_v6only = false; + + for (host_idx = 0; host_idx < result_count; host_idx++) { + bool has_v4 = false; + bool has_v6 = false; + for (rp = result[host_idx]; rp != NULL; rp = rp->ai_next) { + if (rp->ai_family == AF_INET6) + has_v6 = true; + else + has_v4 = true; + } + if (has_v4 && !has_v6) + *has_v4only = true; + else if (has_v6 && !has_v4) + *has_v6only = true; } } @@ -637,13 +664,16 @@ static bool addrinfo_has_in6addr_any(const struct addrinfo **result, size_t resu { size_t host_idx; struct in6_addr in6addr_any = IN6ADDR_ANY_INIT; + const struct addrinfo *rp; for (host_idx = 0; host_idx < result_count; host_idx++) { - if (result[host_idx]->ai_family != AF_INET6) - continue; - if (memcmp(&((struct sockaddr_in6 *)result[host_idx]->ai_addr)->sin6_addr, - &in6addr_any, sizeof(in6addr_any)) == 0) - return true; + for (rp = result[host_idx]; rp != NULL; rp = rp->ai_next) { + if (rp->ai_family != AF_INET6) + continue; + if (memcmp(&((struct sockaddr_in6 *)rp->ai_addr)->sin6_addr, + &in6addr_any, sizeof(in6addr_any)) == 0) + return true; + } } return false; } @@ -669,7 +699,7 @@ static int socket_helper_multiaddr(uint16_t family, uint16_t type, uint8_t proto /* Build array of addresses taking first addrinfo result of the requested family * for each host in addrs_buf. */ static int addrinfo_to_sockaddr(uint16_t family, const struct addrinfo **result, - const char **hosts, int host_cont, + const char **hosts, unsigned int host_cont, uint8_t *addrs_buf, size_t addrs_buf_len) { size_t host_idx, offset = 0; const struct addrinfo *rp; @@ -677,26 +707,102 @@ static int addrinfo_to_sockaddr(uint16_t family, const struct addrinfo **result, for (host_idx = 0; host_idx < host_cont; host_idx++) { /* Addresses are ordered based on RFC 3484, see man getaddrinfo */ for (rp = result[host_idx]; rp != NULL; rp = rp->ai_next) { - if (family != AF_UNSPEC && rp->ai_family != family) - continue; - if (offset + rp->ai_addrlen > addrs_buf_len) { - LOGP(DLGLOBAL, LOGL_ERROR, "Output buffer to small: %zu\n", - addrs_buf_len); - return -ENOSPC; + if (family == AF_UNSPEC || rp->ai_family == family) + break; + } + if (!rp && family == AF_INET6) { + /* See if we can find an AF_INET addr for the AF_INET6 socket instead: */ + for (rp = result[host_idx]; rp != NULL; rp = rp->ai_next) { + if (rp->ai_family == AF_INET) + break; } - memcpy(addrs_buf + offset, rp->ai_addr, rp->ai_addrlen); - offset += rp->ai_addrlen; - break; } if (!rp) { /* No addr could be bound for this host! */ LOGP(DLGLOBAL, LOGL_ERROR, "No suitable remote address found for host: %s\n", hosts[host_idx]); return -ENODEV; } + if (offset + rp->ai_addrlen > addrs_buf_len) { + LOGP(DLGLOBAL, LOGL_ERROR, "Output buffer to small: %zu\n", + addrs_buf_len); + return -ENOSPC; + } + memcpy(addrs_buf + offset, rp->ai_addr, rp->ai_addrlen); + offset += rp->ai_addrlen; } return 0; } +static int setsockopt_sctp_auth_supported(int fd, uint32_t val) +{ +#ifdef SCTP_AUTH_SUPPORTED + struct sctp_assoc_value assoc_val = { + .assoc_id = SCTP_FUTURE_ASSOC, + .assoc_value = val, + }; + return setsockopt(fd, IPPROTO_SCTP, SCTP_AUTH_SUPPORTED, &assoc_val, sizeof(assoc_val)); +#else +#pragma message "setsockopt(SCTP_AUTH_SUPPORTED) not supported! some SCTP features may not be available!" + LOGP(DLGLOBAL, LOGL_NOTICE, "Built without support for setsockopt(SCTP_AUTH_SUPPORTED), skipping\n"); + return -ENOTSUP; +#endif +} + +static int setsockopt_sctp_asconf_supported(int fd, uint32_t val) +{ +#ifdef SCTP_ASCONF_SUPPORTED + struct sctp_assoc_value assoc_val = { + .assoc_id = SCTP_FUTURE_ASSOC, + .assoc_value = val, + }; + return setsockopt(fd, IPPROTO_SCTP, SCTP_ASCONF_SUPPORTED, &assoc_val, sizeof(assoc_val)); +#else +#pragma message "setsockopt(SCTP_ASCONF_SUPPORTED) not supported! some SCTP features may not be available!" + LOGP(DLGLOBAL, LOGL_NOTICE, "Built without support for setsockopt(SCTP_ASCONF_SUPPORTED), skipping\n"); + return -ENOTSUP; +#endif +} + +static int setsockopt_sctp_initmsg(int fd, const struct osmo_sock_init2_multiaddr_pars *pars) +{ + if (!pars->sctp.sockopt_initmsg.num_ostreams_present && + !pars->sctp.sockopt_initmsg.max_instreams_present && + !pars->sctp.sockopt_initmsg.max_attempts_present && + !pars->sctp.sockopt_initmsg.max_init_timeo_present) + return 0; /* nothing to set/do */ + +#ifdef SCTP_INITMSG + struct sctp_initmsg si = {0}; + socklen_t si_len = sizeof(si); + int rc; + + /* If at least one field not present, obtain current value from kernel: */ + if (!pars->sctp.sockopt_initmsg.num_ostreams_present || + !pars->sctp.sockopt_initmsg.max_instreams_present || + !pars->sctp.sockopt_initmsg.max_attempts_present || + !pars->sctp.sockopt_initmsg.max_init_timeo_present) { + rc = getsockopt(fd, IPPROTO_SCTP, SCTP_INITMSG, &si, &si_len); + if (rc < 0) + return rc; + } + + if (pars->sctp.sockopt_initmsg.num_ostreams_present) + si.sinit_num_ostreams = pars->sctp.sockopt_initmsg.num_ostreams_value; + if (pars->sctp.sockopt_initmsg.max_instreams_present) + si.sinit_max_instreams = pars->sctp.sockopt_initmsg.max_instreams_value; + if (pars->sctp.sockopt_initmsg.max_attempts_present) + si.sinit_max_attempts = pars->sctp.sockopt_initmsg.max_attempts_value; + if (pars->sctp.sockopt_initmsg.max_init_timeo_present) + si.sinit_max_init_timeo = pars->sctp.sockopt_initmsg.max_init_timeo_value; + + return setsockopt(fd, IPPROTO_SCTP, SCTP_INITMSG, &si, sizeof(si)); +#else +#pragma message "setsockopt(SCTP_INITMSG) not supported! some SCTP features may not be available!" + LOGP(DLGLOBAL, LOGL_NOTICE, "Built without support for setsockopt(SCTP_INITMSG), skipping\n"); + return -ENOTSUP +#endif +} + /*! Initialize a socket (including bind and/or connect) with multiple local or remote addresses. * \param[in] family Address Family like AF_INET, AF_INET6, AF_UNSPEC * \param[in] type Socket type like SOCK_DGRAM, SOCK_STREAM @@ -721,25 +827,58 @@ int osmo_sock_init2_multiaddr(uint16_t family, uint16_t type, uint8_t proto, const char **local_hosts, size_t local_hosts_cnt, uint16_t local_port, const char **remote_hosts, size_t remote_hosts_cnt, uint16_t remote_port, unsigned int flags) +{ + return osmo_sock_init2_multiaddr2(family, type, proto, local_hosts, local_hosts_cnt, local_port, + remote_hosts, remote_hosts_cnt, remote_port, flags, NULL); +} + +/*! Initialize a socket (including bind and/or connect) with multiple local or remote addresses. + * \param[in] family Address Family like AF_INET, AF_INET6, AF_UNSPEC + * \param[in] type Socket type like SOCK_DGRAM, SOCK_STREAM + * \param[in] proto Protocol like IPPROTO_TCP, IPPROTO_UDP + * \param[in] local_hosts array of char pointers (strings), each containing local host name or IP address in string form + * \param[in] local_hosts_cnt length of local_hosts (in items) + * \param[in] local_port local port number in host byte order + * \param[in] remote_host array of char pointers (strings), each containing remote host name or IP address in string form + * \param[in] remote_hosts_cnt length of remote_hosts (in items) + * \param[in] remote_port remote port number in host byte order + * \param[in] flags flags like \ref OSMO_SOCK_F_CONNECT + * \param[in] pars Extra parameters for multi-address specific protocols, such as SCTP. Can be NULL. + * \returns socket file descriptor on success; negative on error + * + * This function is similar to \ref osmo_sock_init2(), but can be passed an + * array of local or remote addresses for protocols supporting multiple + * addresses per socket, like SCTP (currently only one supported). This function + * should not be used by protocols not supporting this kind of features, but + * rather \ref osmo_sock_init2() should be used instead. + * See \ref osmo_sock_init2() for more information on flags and general behavior. + * + * pars: If "pars" parameter is passed to the function, sctp.version shall be set to 0. + */ +int osmo_sock_init2_multiaddr2(uint16_t family, uint16_t type, uint8_t proto, + const char **local_hosts, size_t local_hosts_cnt, uint16_t local_port, + const char **remote_hosts, size_t remote_hosts_cnt, uint16_t remote_port, + unsigned int flags, struct osmo_sock_init2_multiaddr_pars *pars) { struct addrinfo *res_loc[OSMO_SOCK_MAX_ADDRS], *res_rem[OSMO_SOCK_MAX_ADDRS]; int sfd = -1, rc, on = 1; - int i; - bool loc_has_v4addr, rem_has_v4addr; - bool loc_has_v6addr, rem_has_v6addr; + unsigned int i; + bool loc_has_v4addr = false, loc_has_v6addr = false; + bool rem_has_v4addr = false, rem_has_v6addr = false; + bool loc_has_v4only_addr, rem_has_v4only_addr; + bool loc_has_v6only_addr, rem_has_v6only_addr; struct sockaddr_in6 addrs_buf[OSMO_SOCK_MAX_ADDRS]; char strbuf[512]; - /* updated later in case of AF_UNSPEC */ - loc_has_v4addr = rem_has_v4addr = (family == AF_INET); - loc_has_v6addr = rem_has_v6addr = (family == AF_INET6); - /* TODO: So far this function is only aimed for SCTP, but could be reused in the future for other protocols with multi-addr support */ if (proto != IPPROTO_SCTP) return -ENOTSUP; + if (pars && pars->sctp.version != 0) + return -EINVAL; + if ((flags & (OSMO_SOCK_F_BIND | OSMO_SOCK_F_CONNECT)) == 0) { LOGP(DLGLOBAL, LOGL_ERROR, "invalid: you have to specify either " "BIND or CONNECT flags\n"); @@ -758,10 +897,17 @@ int osmo_sock_init2_multiaddr(uint16_t family, uint16_t type, uint8_t proto, local_hosts_cnt, local_port, true); if (rc < 0) return -EINVAL; - /* Figure out if there's any IPV4 or IPv6 addr in the set */ - if (family == AF_UNSPEC) - addrinfo_has_v4v6addr((const struct addrinfo **)res_loc, local_hosts_cnt, - &loc_has_v4addr, &loc_has_v6addr); + /* Figure out if there's any IPv4 or IPv6 entry in the result set */ + addrinfo_has_v4v6addr((const struct addrinfo **)res_loc, local_hosts_cnt, + &loc_has_v4addr, &loc_has_v6addr); + /* Figure out if there's any IPv4-only or IPv6-only addr in the result set */ + addrinfo_has_v4v6only_addr((const struct addrinfo **)res_loc, local_hosts_cnt, + &loc_has_v4only_addr, &loc_has_v6only_addr); + if (family == AF_INET && loc_has_v6only_addr) { + LOGP(DLGLOBAL, LOGL_ERROR, "Cannot bind an IPv6 address to an AF_INET socket\n"); + rc = -EINVAL; + goto ret_freeaddrinfo_loc; + } } /* figure out remote side of socket */ if (flags & OSMO_SOCK_F_CONNECT) { @@ -771,39 +917,109 @@ int osmo_sock_init2_multiaddr(uint16_t family, uint16_t type, uint8_t proto, rc = -EINVAL; goto ret_freeaddrinfo_loc; } - /* Figure out if there's any IPv4 or IPv6 addr in the set */ - if (family == AF_UNSPEC) - addrinfo_has_v4v6addr((const struct addrinfo **)res_rem, remote_hosts_cnt, - &rem_has_v4addr, &rem_has_v6addr); + /* Figure out if there's any IPv4 or IPv6 entry in the result set */ + addrinfo_has_v4v6addr((const struct addrinfo **)res_rem, remote_hosts_cnt, + &rem_has_v4addr, &rem_has_v6addr); + /* Figure out if there's any IPv4-only or IPv6-only addr in the result set */ + addrinfo_has_v4v6only_addr((const struct addrinfo **)res_rem, remote_hosts_cnt, + &rem_has_v4only_addr, &rem_has_v6only_addr); + if (family == AF_INET && rem_has_v6only_addr) { + LOGP(DLGLOBAL, LOGL_ERROR, "Cannot connect to an IPv6 address in an AF_INET socket\n"); + rc = -EINVAL; + goto ret_freeaddrinfo; + } + } + + /* Find out the socket family now if not established by caller: + * Both are checked here through "or" here to account for "bind flag set, + * connect flag not set" and viceversa. */ + if (family == AF_UNSPEC) { + if (!loc_has_v6addr && !rem_has_v6addr) + family = AF_INET; + else + family = AF_INET6; } - if (((flags & OSMO_SOCK_F_BIND) && (flags & OSMO_SOCK_F_CONNECT)) && - !addrinfo_has_in6addr_any((const struct addrinfo **)res_loc, local_hosts_cnt) && + /* if both sets are used, make sure there's at least 1 address of the + * same type on each set so that SCTP INIT/INIT-ACK can work. */ + if (family == AF_INET6 && ((flags & OSMO_SOCK_F_BIND) && (flags & OSMO_SOCK_F_CONNECT)) && (loc_has_v4addr != rem_has_v4addr || loc_has_v6addr != rem_has_v6addr)) { - LOGP(DLGLOBAL, LOGL_ERROR, "Invalid v4 vs v6 in local vs remote addresses\n"); - rc = -EINVAL; - goto ret_freeaddrinfo; + if (!addrinfo_has_in6addr_any((const struct addrinfo **)res_loc, local_hosts_cnt)) { + LOGP(DLGLOBAL, LOGL_ERROR, "Invalid v4 vs v6 in local vs remote addresses: " + "local:%s%s remote:%s%s\n", + loc_has_v4addr ? " v4" : "", loc_has_v6addr ? " v6" : "", + rem_has_v4addr ? " v4" : "", rem_has_v6addr ? " v6" : ""); + rc = -EINVAL; + goto ret_freeaddrinfo; + } } - sfd = socket_helper_multiaddr(loc_has_v6addr ? AF_INET6 : AF_INET, - type, proto, flags); + sfd = socket_helper_multiaddr(family, type, proto, flags); if (sfd < 0) { rc = sfd; goto ret_freeaddrinfo; } + if (pars) { + if (pars->sctp.sockopt_auth_supported.set) { + /* RFC 5061 4.2.7: ASCONF also requires AUTH feature. */ + rc = setsockopt_sctp_auth_supported(sfd, pars->sctp.sockopt_auth_supported.value); + if (rc < 0) { + int err = errno; + multiaddr_snprintf(strbuf, sizeof(strbuf), local_hosts, local_hosts_cnt); + LOGP(DLGLOBAL, LOGL_ERROR, + "cannot setsockopt(SCTP_AUTH_SUPPORTED) socket: %s:%u: %s\n", + strbuf, local_port, strerror(err)); + if (pars->sctp.sockopt_auth_supported.abort_on_failure) + goto ret_close; + /* do not fail, some features such as Peer Primary Address won't be available + * unless configured system-wide through sysctl */ + } + } + + if (pars->sctp.sockopt_asconf_supported.set) { + rc = setsockopt_sctp_asconf_supported(sfd, pars->sctp.sockopt_asconf_supported.value); + if (rc < 0) { + int err = errno; + multiaddr_snprintf(strbuf, sizeof(strbuf), local_hosts, local_hosts_cnt); + LOGP(DLGLOBAL, LOGL_ERROR, + "cannot setsockopt(SCTP_ASCONF_SUPPORTED) socket: %s:%u: %s\n", + strbuf, local_port, strerror(err)); + if (pars->sctp.sockopt_asconf_supported.abort_on_failure) + goto ret_close; + /* do not fail, some features such as Peer Primary Address won't be available + * unless configured system-wide through sysctl */ + } + } + + if (pars->sctp.sockopt_initmsg.set) { + rc = setsockopt_sctp_initmsg(sfd, pars); + if (rc < 0) { + int err = errno; + multiaddr_snprintf(strbuf, sizeof(strbuf), local_hosts, local_hosts_cnt); + LOGP(DLGLOBAL, LOGL_ERROR, + "cannot setsockopt(SCTP_INITMSG) socket: %s:%u: %s\n", + strbuf, local_port, strerror(err)); + if (pars->sctp.sockopt_initmsg.abort_on_failure) + goto ret_close; + /* do not fail, some parameters will be left as the global default */ + } + } + } + if (flags & OSMO_SOCK_F_BIND) { /* Since so far we only allow IPPROTO_SCTP in this function, no need to check below for "proto != IPPROTO_UDP || flags & OSMO_SOCK_F_UDP_REUSEADDR" */ rc = setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); if (rc < 0) { + int err = errno; multiaddr_snprintf(strbuf, sizeof(strbuf), local_hosts, local_hosts_cnt); LOGP(DLGLOBAL, LOGL_ERROR, "cannot setsockopt socket:" " %s:%u: %s\n", strbuf, local_port, - strerror(errno)); + strerror(err)); goto ret_close; } @@ -821,9 +1037,10 @@ int osmo_sock_init2_multiaddr(uint16_t family, uint16_t type, uint8_t proto, rc = sctp_bindx(sfd, (struct sockaddr *)addrs_buf, local_hosts_cnt, SCTP_BINDX_ADD_ADDR); if (rc == -1) { + int err = errno; multiaddr_snprintf(strbuf, sizeof(strbuf), local_hosts, local_hosts_cnt); LOGP(DLGLOBAL, LOGL_NOTICE, "unable to bind socket: %s:%u: %s\n", - strbuf, local_port, strerror(errno)); + strbuf, local_port, strerror(err)); rc = -ENODEV; goto ret_close; } @@ -843,9 +1060,10 @@ int osmo_sock_init2_multiaddr(uint16_t family, uint16_t type, uint8_t proto, rc = sctp_connectx(sfd, (struct sockaddr *)addrs_buf, remote_hosts_cnt, NULL); if (rc != 0 && errno != EINPROGRESS) { + int err = errno; multiaddr_snprintf(strbuf, sizeof(strbuf), remote_hosts, remote_hosts_cnt); LOGP(DLGLOBAL, LOGL_ERROR, "unable to connect socket: %s:%u: %s\n", - strbuf, remote_port, strerror(errno)); + strbuf, remote_port, strerror(err)); rc = -ENODEV; goto ret_close; } @@ -1101,6 +1319,160 @@ int osmo_sock_init_sa(struct sockaddr *ss, uint16_t type, return osmo_sock_init(ss->sa_family, type, proto, host, port, flags); } +#ifdef HAVE_LIBSCTP +/*! Add addresses to the multi-address (SCTP) socket active binding set + * \param[in] sfd The multi-address (SCTP) socket + * \param[in] addrs array of char pointers (strings), each containing local host name or IP address in string form + * \param[in] addrs_cnt length of addrs_hosts (in items) + * \returns 0 on success; negative on error + * + * This function only supports SCTP sockets so far, and hence it should be + * called only on socket file descriptions referencing that kind of sockets. + */ +int osmo_sock_multiaddr_add_local_addr(int sfd, const char **addrs, size_t addrs_cnt) +{ + struct osmo_sockaddr osa; + socklen_t slen = sizeof(osa); + uint16_t sfd_family; + uint16_t type = SOCK_STREAM ;/* Fixme: we assume fd is SOCK_STREAM */ + uint8_t proto = IPPROTO_SCTP; /* Fixme: we assume fd is IPPROTO_SCTP */ + struct addrinfo *res[OSMO_SOCK_MAX_ADDRS]; + uint16_t port; + struct sockaddr_in6 addrs_buf[OSMO_SOCK_MAX_ADDRS]; + unsigned int i; + int rc; + bool res_has_v4addr = false, res_has_v6addr = false; + + rc = getsockname(sfd, &osa.u.sa, &slen); + if (rc < 0) + return rc; /* TODO: log error? */ + sfd_family = osa.u.sa.sa_family; + port = osmo_sockaddr_port(&osa.u.sa); + + if (sfd_family != AF_INET && sfd_family != AF_INET6) + return -EINVAL; + + rc = addrinfo_helper_multi(res, AF_UNSPEC, type, proto, addrs, + addrs_cnt, port, true); + if (rc < 0) + return -EINVAL; + + addrinfo_has_v4v6addr((const struct addrinfo **)res, addrs_cnt, + &res_has_v4addr, &res_has_v6addr); + if (sfd_family == AF_INET && !res_has_v4addr) { + rc = -EINVAL; + goto ret_free; + } + + uint16_t new_addr_family; + if (sfd_family == AF_INET) + new_addr_family = AF_INET; + else if (sfd_family == AF_INET6 && !res_has_v4addr) + new_addr_family = AF_INET6; + else + new_addr_family = AF_UNSPEC; + rc = addrinfo_to_sockaddr(new_addr_family, (const struct addrinfo **)res, + addrs, addrs_cnt, + (uint8_t *)addrs_buf, sizeof(addrs_buf)); + if (rc < 0) { + rc = -ENODEV; + goto ret_free; + } + + rc = sctp_bindx(sfd, (struct sockaddr *)addrs_buf, addrs_cnt, SCTP_BINDX_ADD_ADDR); + if (rc == -1) { + int err = errno; + char strbuf[512]; + multiaddr_snprintf(strbuf, sizeof(strbuf), addrs, addrs_cnt); + LOGP(DLGLOBAL, LOGL_NOTICE, "Unable to bind socket to new addresses: %s:%u: %s\n", + strbuf, port, strerror(err)); + rc = -ENODEV; + goto ret_free; + } + +ret_free: + for (i = 0; i < addrs_cnt; i++) + freeaddrinfo(res[i]); + return rc; +} + +/*! Remove addresses from the multi-address (SCTP) socket active binding set + * \param[in] sfd The multi-address (SCTP) socket + * \param[in] addrs array of char pointers (strings), each containing local host name or IP address in string form + * \param[in] addrs_cnt length of addrs_hosts (in items) + * \returns 0 on success; negative on error + * + * This function only supports SCTP sockets so far, and hence it should be + * called only on socket file descriptions referencing that kind of sockets. + */ +int osmo_sock_multiaddr_del_local_addr(int sfd, const char **addrs, size_t addrs_cnt) +{ + struct osmo_sockaddr osa; + socklen_t slen = sizeof(osa); + uint16_t sfd_family; + uint16_t type = SOCK_STREAM ;/* Fixme: we assume fd is SOCK_STREAM */ + uint8_t proto = IPPROTO_SCTP; /* Fixme: we assume fd is IPPROTO_SCTP */ + struct addrinfo *res[OSMO_SOCK_MAX_ADDRS]; + uint16_t port; + struct sockaddr_in6 addrs_buf[OSMO_SOCK_MAX_ADDRS]; + unsigned int i; + int rc; + bool res_has_v4addr = false, res_has_v6addr = false; + + rc = getsockname(sfd, &osa.u.sa, &slen); + if (rc < 0) + return rc; /* TODO: log error? */ + sfd_family = osa.u.sa.sa_family; + port = osmo_sockaddr_port(&osa.u.sa); + + if (sfd_family != AF_INET && sfd_family != AF_INET6) + return -EINVAL; + + rc = addrinfo_helper_multi(res, AF_UNSPEC, type, proto, addrs, + addrs_cnt, port, true); + if (rc < 0) + return -EINVAL; + + addrinfo_has_v4v6addr((const struct addrinfo **)res, addrs_cnt, + &res_has_v4addr, &res_has_v6addr); + if (sfd_family == AF_INET && !res_has_v4addr) { + rc = -EINVAL; + goto ret_free; + } + + uint16_t del_addr_family; + if (sfd_family == AF_INET) + del_addr_family = AF_INET; + else if (sfd_family == AF_INET6 && !res_has_v4addr) + del_addr_family = AF_INET6; + else + del_addr_family = AF_UNSPEC; + rc = addrinfo_to_sockaddr(del_addr_family, (const struct addrinfo **)res, + addrs, addrs_cnt, + (uint8_t *)addrs_buf, sizeof(addrs_buf)); + if (rc < 0) { + rc = -ENODEV; + goto ret_free; + } + + rc = sctp_bindx(sfd, (struct sockaddr *)addrs_buf, addrs_cnt, SCTP_BINDX_REM_ADDR); + if (rc == -1) { + int err = errno; + char strbuf[512]; + multiaddr_snprintf(strbuf, sizeof(strbuf), addrs, addrs_cnt); + LOGP(DLGLOBAL, LOGL_NOTICE, "Unable to unbind socket from addresses: %s:%u: %s\n", + strbuf, port, strerror(err)); + rc = -ENODEV; + goto ret_free; + } + +ret_free: + for (i = 0; i < addrs_cnt; i++) + freeaddrinfo(res[i]); + return rc; +} +#endif /* HAVE_LIBSCTP */ + static int sockaddr_equal(const struct sockaddr *a, const struct sockaddr *b, unsigned int len) { @@ -1129,6 +1501,25 @@ static int sockaddr_equal(const struct sockaddr *a, return 0; } +/* linux has a default route: +local 127.0.0.0/8 dev lo proto kernel scope host src 127.0.0.1 +*/ +static int sockaddr_is_local_routed(const struct sockaddr *a) +{ +#if __linux__ + if (a->sa_family != AF_INET) + return 0; + + uint32_t address = ((struct sockaddr_in *)a)->sin_addr.s_addr; /* already BE */ + uint32_t eightmask = htonl(0xff000000); /* /8 mask */ + uint32_t local_prefix_127 = htonl(0x7f000000); /* 127.0.0.0 */ + + if ((address & eightmask) == local_prefix_127) + return 1; +#endif + return 0; +} + /*! Determine if the given address is a local address * \param[in] addr Socket Address * \param[in] addrlen Length of socket address in bytes @@ -1138,6 +1529,9 @@ int osmo_sockaddr_is_local(struct sockaddr *addr, unsigned int addrlen) { struct ifaddrs *ifaddr, *ifa; + if (sockaddr_is_local_routed(addr)) + return 1; + if (getifaddrs(&ifaddr) == -1) { LOGP(DLGLOBAL, LOGL_ERROR, "getifaddrs:" " %s\n", strerror(errno)); @@ -1157,6 +1551,26 @@ int osmo_sockaddr_is_local(struct sockaddr *addr, unsigned int addrlen) return 0; } +/*! Determine if the given address is an ANY address ("0.0.0.0", "::"). Port is not checked. + * \param[in] addr Socket Address + * \param[in] addrlen Length of socket address in bytes + * \returns 1 if address is ANY, 0 otherwise. -1 is address family not supported/detected. + */ +int osmo_sockaddr_is_any(const struct osmo_sockaddr *addr) +{ + switch (addr->u.sa.sa_family) { + case AF_INET6: { + struct in6_addr ip6_any = IN6ADDR_ANY_INIT; + return memcmp(&addr->u.sin6.sin6_addr, + &ip6_any, sizeof(ip6_any)) == 0; + } + case AF_INET: + return addr->u.sin.sin_addr.s_addr == INADDR_ANY; + default: + return -1; + } +} + /*! Convert sockaddr_in to IP address as char string and port as uint16_t. * \param[out] addr String buffer to write IP address to, or NULL. * \param[out] addr_len Size of \a addr. @@ -1236,6 +1650,101 @@ uint16_t osmo_sockaddr_port(const struct sockaddr *sa) return 0; } +/*! Set sockaddr port content (to network byte order). + * \param[out] sa sockaddr to set the port of. + * \param[in] port port nr to set. + */ +void osmo_sockaddr_set_port(struct sockaddr *sa, uint16_t port) +{ + struct osmo_sockaddr *osa = (struct osmo_sockaddr *)sa; + switch (osa->u.sa.sa_family) { + case AF_INET6: + osa->u.sin6.sin6_port = htons(port); + return; + case AF_INET: + osa->u.sin.sin_port = htons(port); + return; + } +} + +static unsigned int in6_addr_netmask_to_prefixlen(const struct in6_addr *netmask) +{ + #if defined(__linux__) + #define ADDRFIELD(i) s6_addr32[i] + #else + #define ADDRFIELD(i) __u6_addr.__u6_addr32[i] + #endif + + unsigned int i, j, prefix = 0; + + for (j = 0; j < 4; j++) { + uint32_t bits = netmask->ADDRFIELD(j); + uint8_t *b = (uint8_t *)&bits; + for (i = 0; i < 4; i++) { + while (b[i] & 0x80) { + prefix++; + b[i] = b[i] << 1; + } + } + } + + #undef ADDRFIELD + + return prefix; +} + +static unsigned int in_addr_netmask_to_prefixlen(const struct in_addr *netmask) +{ + uint32_t bits = netmask->s_addr; + uint8_t *b = (uint8_t *)&bits; + unsigned int i, prefix = 0; + + for (i = 0; i < 4; i++) { + while (b[i] & 0x80) { + prefix++; + b[i] = b[i] << 1; + } + } + return prefix; +} + +/*! Convert netmask to prefix length representation + * \param[in] netmask sockaddr containing a netmask (consecutive list of 1-bit followed by consecutive list of 0-bit) + * \returns prefix length representation of the netmask (count of 1-bit from the start of the netmask), negative on error. + */ +int osmo_sockaddr_netmask_to_prefixlen(const struct osmo_sockaddr *netmask) +{ + switch (netmask->u.sa.sa_family) { + case AF_INET6: + return in6_addr_netmask_to_prefixlen(&netmask->u.sin6.sin6_addr); + case AF_INET: + return in_addr_netmask_to_prefixlen(&netmask->u.sin.sin_addr); + default: + return -ENOTSUP; + } +} + +/*! Convert an IP address string (and port number) into a 'struct osmo_sockaddr'. + * \param[out] osa_out caller-allocated osmo_sockaddr storage + * \param[in] ipstr IP[v4,v6] address in string format + * \param[in] port port number (host byte order) + * \returns 0 on success; negative on error. */ +int osmo_sockaddr_from_str_and_uint(struct osmo_sockaddr *osa_out, const char *ipstr, uint16_t port) +{ + struct addrinfo *ai = addrinfo_helper(AF_UNSPEC, 0, 0, ipstr, port, true); + + if (!ai) + return -EIO; + + if (ai->ai_addrlen > sizeof(*osa_out)) + return -ENOSPC; + + memcpy(&osa_out->u.sa, ai->ai_addr, ai->ai_addrlen); + freeaddrinfo(ai); + + return 0; +} + /*! Initialize a unix domain socket (including bind/connect) * \param[in] type Socket type like SOCK_DGRAM, SOCK_STREAM * \param[in] proto Protocol like IPPROTO_TCP, IPPROTO_UDP @@ -1281,7 +1790,7 @@ int osmo_sock_unix_init(uint16_t type, uint8_t proto, sfd = socket(AF_UNIX, type, proto); if (sfd < 0) - return -1; + return -errno; if (flags & OSMO_SOCK_F_CONNECT) { rc = connect(sfd, (struct sockaddr *)&local, namelen); @@ -1301,13 +1810,13 @@ int osmo_sock_unix_init(uint16_t type, uint8_t proto, rc = osmo_sock_init_tail(sfd, type, flags); if (rc < 0) { close(sfd); - sfd = -1; + sfd = rc; } return sfd; err: close(sfd); - return -1; + return -errno; } /*! Initialize a unix domain socket and fill \ref osmo_fd @@ -1360,6 +1869,97 @@ int osmo_sock_get_ip_and_port(int fd, char *ip, size_t ip_len, char *port, size_ return 0; } +#ifdef HAVE_LIBSCTP +/*! Get multiple IP addresses and/or port number on socket in separate string buffers + * \param[in] fd file descriptor of socket. + * \param[out] ip_proto IPPROTO of the socket, eg: IPPROTO_SCTP. + * \param[out] ip Pointer to memory holding consecutive buffers of size ip_len. + * \param[out] ip_cnt length ip array pointer. on return it contains the number of addresses found. + * \param[in] ip_len length of each of the string buffer in the the ip array. + * \param[out] port number (will be filled in when not NULL). + * \param[in] port_len length of the port buffer. + * \param[in] local (true) or remote (false) name will get looked at. + * \returns 0 on success; negative otherwise. + * + * Upon return, ip_cnt can be set to a higher value than the one set by the + * caller. This can be used by the caller to find out the required array length + * and then obtaining by calling the function twice. Only up to ip_cnt addresses + * are filed in, as per the value provided by the caller. + * + * Usage example retrieving all (up to OSMO_SOCK_MAX_ADDRS, 32) bound IP addresses and bound port: + * char hostbuf[OSMO_SOCK_MAX_ADDRS][INET6_ADDRSTRLEN]; + * size_t num_hostbuf = ARRAY_SIZE(hostbuf); + * char portbuf[6]; + * rc = osmo_sock_multiaddr_get_ip_and_port(fd, IPPROTO_SCTP, &hostbuf[0][0], &num_hostbuf, + * sizeof(hostbuf[0]), portbuf, sizeof(portbuf), true); + * if (rc < 0) + * goto error; + * if (num_hostbuf > ARRAY_SIZE(hostbuf)) + * goto not_enough_buffers; + */ +int osmo_sock_multiaddr_get_ip_and_port(int fd, int ip_proto, char *ip, size_t *ip_cnt, size_t ip_len, + char *port, size_t port_len, bool local) +{ + struct sockaddr *addrs = NULL; + unsigned int n_addrs, i; + void *addr_buf; + int rc; + + switch (ip_proto) { + case IPPROTO_SCTP: + break; /* continue below */ + default: + if (*ip_cnt == 0) { + *ip_cnt = 1; + return 0; + } + *ip_cnt = 1; + return osmo_sock_get_ip_and_port(fd, ip, ip_len, port, port_len, local); + } + + rc = local ? sctp_getladdrs(fd, 0, &addrs) : sctp_getpaddrs(fd, 0, &addrs); + if (rc < 0) + return rc; + if (rc == 0) + return -ENOTCONN; + + n_addrs = rc; + addr_buf = (void *)addrs; + for (i = 0; i < n_addrs; i++) { + struct sockaddr *sa_addr = (struct sockaddr *)addr_buf; + size_t addrlen; + + if (i >= *ip_cnt) + break; + + switch (sa_addr->sa_family) { + case AF_INET: + addrlen = sizeof(struct sockaddr_in); + break; + case AF_INET6: + addrlen = sizeof(struct sockaddr_in6); + break; + default: + rc = -EINVAL; + goto free_addrs_ret; + } + + rc = getnameinfo(sa_addr, addrlen, &ip[i * ip_len], ip_len, + port, port_len, + NI_NUMERICHOST | NI_NUMERICSERV); + if (rc < 0) + goto free_addrs_ret; + addr_buf += addrlen; + } + + *ip_cnt = n_addrs; + rc = 0; +free_addrs_ret: + local ? sctp_freeladdrs(addrs) : sctp_freepaddrs(addrs); + return rc; +} +#endif + /*! Get local IP address on socket * \param[in] fd file descriptor of socket * \param[out] ip IP address (will be filled in) @@ -1421,6 +2021,114 @@ char *osmo_sock_get_name(const void *ctx, int fd) return talloc_asprintf(ctx, "(%s)", str); } +#ifdef HAVE_LIBSCTP +/*! Format multiple IP addresses and/or port number into a combined string buffer + * \param[out] str Destination string buffer. + * \param[in] str_len sizeof(str), usually OSMO_SOCK_MULTIADDR_PEER_STR_MAXLEN. + * \param[out] ip Pointer to memory holding ip_cnt consecutive buffers of size ip_len. + * \param[out] ip_cnt length ip array pointer. on return it contains the number of addresses found. + * \param[in] ip_len length of each of the string buffer in the the ip array. + * \param[out] port number (will be printed in when not NULL). + * \return String length as returned by snprintf(), or negative on error. + * + * This API expects an ip array as the one filled in by + * osmo_sock_multiaddr_get_ip_and_port(), and hence it's a good companion for + * that API. + */ +int osmo_multiaddr_ip_and_port_snprintf(char *str, size_t str_len, + const char *ip, size_t ip_cnt, size_t ip_len, + const char *portbuf) +{ + struct osmo_strbuf sb = { .buf = str, .len = str_len }; + bool is_v6 = false; + unsigned int i; + + if (ip_cnt == 0) { + OSMO_STRBUF_PRINTF(sb, "NULL:%s", portbuf); + return sb.chars_needed; + } + if (ip_cnt > 1) + OSMO_STRBUF_PRINTF(sb, "("); + else if ((is_v6 = !!strchr(&ip[0], ':'))) /* IPv6, add [] to separate from port. */ + OSMO_STRBUF_PRINTF(sb, "["); + + for (i = 0; i < ip_cnt - 1; i++) + OSMO_STRBUF_PRINTF(sb, "%s|", &ip[i * ip_len]); + OSMO_STRBUF_PRINTF(sb, "%s", &ip[i * ip_len]); + + if (ip_cnt > 1) + OSMO_STRBUF_PRINTF(sb, ")"); + else if (is_v6) + OSMO_STRBUF_PRINTF(sb, "]"); + if (portbuf) + OSMO_STRBUF_PRINTF(sb, ":%s", portbuf); + + return sb.chars_needed; +} + +/*! Get address/port information on socket in provided string buffer, like "r=1.2.3.4:5<->l=6.7.8.9:10". + * This does not include braces like osmo_sock_get_name(). + * \param[out] str Destination string buffer. + * \param[in] str_len sizeof(str), usually OSMO_SOCK_MULTIADDR_NAME_MAXLEN. + * \param[in] fd File descriptor of socket. + * \param[in] fd IPPROTO of the socket, eg: IPPROTO_SCTP. + * \return String length as returned by snprintf(), or negative on error. + */ +int osmo_sock_multiaddr_get_name_buf(char *str, size_t str_len, int fd, int sk_proto) +{ + char hostbuf[OSMO_SOCK_MAX_ADDRS][INET6_ADDRSTRLEN]; + size_t num_hostbuf = ARRAY_SIZE(hostbuf); + char portbuf[6]; + struct osmo_strbuf sb = { .buf = str, .len = str_len }; + + if (fd < 0) { + osmo_strlcpy(str, "<error-bad-fd>", str_len); + return sb.chars_needed; + } + + switch (sk_proto) { + case IPPROTO_SCTP: + break; /* continue below */ + default: + return osmo_sock_get_name_buf(str, str_len, fd); + } + + /* get remote */ + OSMO_STRBUF_PRINTF(sb, "r="); + if (osmo_sock_multiaddr_get_ip_and_port(fd, sk_proto, &hostbuf[0][0], &num_hostbuf, + sizeof(hostbuf[0]), portbuf, sizeof(portbuf), false) != 0) { + OSMO_STRBUF_PRINTF(sb, "NULL"); + } else { + const bool need_more_bufs = num_hostbuf > ARRAY_SIZE(hostbuf); + if (need_more_bufs) + num_hostbuf = ARRAY_SIZE(hostbuf); + OSMO_STRBUF_APPEND(sb, osmo_multiaddr_ip_and_port_snprintf, + &hostbuf[0][0], num_hostbuf, sizeof(hostbuf[0]), portbuf); + if (need_more_bufs) + OSMO_STRBUF_PRINTF(sb, "<need-more-bufs!>"); + } + + OSMO_STRBUF_PRINTF(sb, "<->l="); + + /* get local */ + num_hostbuf = ARRAY_SIZE(hostbuf); + if (osmo_sock_multiaddr_get_ip_and_port(fd, sk_proto, &hostbuf[0][0], &num_hostbuf, + sizeof(hostbuf[0]), portbuf, sizeof(portbuf), true) != 0) { + OSMO_STRBUF_PRINTF(sb, "NULL"); + } else { + const bool need_more_bufs = num_hostbuf > ARRAY_SIZE(hostbuf); + if (need_more_bufs) + num_hostbuf = ARRAY_SIZE(hostbuf); + OSMO_STRBUF_APPEND(sb, osmo_multiaddr_ip_and_port_snprintf, + &hostbuf[0][0], num_hostbuf, sizeof(hostbuf[0]), portbuf); + if (need_more_bufs) + OSMO_STRBUF_PRINTF(sb, "<need-more-bufs!>"); + } + + return sb.chars_needed; +} +#endif + /*! Get address/port information on socket in provided string buffer, like "r=1.2.3.4:5<->l=6.7.8.9:10". * This does not include braces like osmo_sock_get_name(). * \param[out] str Destination string buffer. @@ -1430,21 +2138,89 @@ char *osmo_sock_get_name(const void *ctx, int fd) */ int osmo_sock_get_name_buf(char *str, size_t str_len, int fd) { - char hostbuf_l[INET6_ADDRSTRLEN], hostbuf_r[INET6_ADDRSTRLEN]; - char portbuf_l[6], portbuf_r[6]; + struct osmo_strbuf sb = { .buf = str, .len = str_len }; + struct osmo_sockaddr osa; + struct sockaddr_un *sun; + socklen_t len; int rc; - /* get local */ - if ((rc = osmo_sock_get_ip_and_port(fd, hostbuf_l, sizeof(hostbuf_l), portbuf_l, sizeof(portbuf_l), true))) { + if (fd < 0) { + osmo_strlcpy(str, "<error-bad-fd>", str_len); + return -EBADF; + } + + + len = sizeof(osa.u.sas); + rc = getsockname(fd, &osa.u.sa, &len); + if (rc < 0) { osmo_strlcpy(str, "<error-in-getsockname>", str_len); return rc; } - /* get remote */ - if (osmo_sock_get_ip_and_port(fd, hostbuf_r, sizeof(hostbuf_r), portbuf_r, sizeof(portbuf_r), false) != 0) - return snprintf(str, str_len, "r=NULL<->l=%s:%s", hostbuf_l, portbuf_l); - - return snprintf(str, str_len, "r=%s:%s<->l=%s:%s", hostbuf_r, portbuf_r, hostbuf_l, portbuf_l); + switch (osa.u.sa.sa_family) { + case AF_INET: + case AF_INET6: + { + char hostbuf_l[INET6_ADDRSTRLEN], hostbuf_r[INET6_ADDRSTRLEN]; + char portbuf_l[6], portbuf_r[6]; + + len = sizeof(osa.u.sas); + rc = getnameinfo(&osa.u.sa, len, hostbuf_l, sizeof(hostbuf_l), + portbuf_l, sizeof(portbuf_l), + NI_NUMERICHOST | NI_NUMERICSERV); + if (rc < 0) { + osmo_strlcpy(str, "<error-in-getnameinfo>", str_len); + return rc; + } + /* Now attempt to get remote: */ + len = sizeof(osa.u.sas); + rc = getpeername(fd, &osa.u.sa, &len); + if (rc < 0) { + OSMO_STRBUF_PRINTF(sb, "r=NULL<->l=%s:%s", hostbuf_l, portbuf_l); + return sb.chars_needed; + } + len = sizeof(osa.u.sas); + rc = getnameinfo(&osa.u.sa, len, hostbuf_r, sizeof(hostbuf_r), + portbuf_r, sizeof(portbuf_r), + NI_NUMERICHOST | NI_NUMERICSERV); + if (rc < 0) { + OSMO_STRBUF_PRINTF(sb, "r=NULL<->l=%s:%s", hostbuf_l, portbuf_l); + return sb.chars_needed; + } + OSMO_STRBUF_PRINTF(sb, "r=%s:%s<->l=%s:%s", hostbuf_r, portbuf_r, hostbuf_l, portbuf_l); + return sb.chars_needed; + } + case AF_UNIX: + { + unsigned long long remote_pid; + bool have_remote_pid; +#if defined(SO_PEERCRED) + struct ucred ucred; + len = sizeof(struct ucred); + if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &ucred, &len) == -1) { + have_remote_pid = false; + } else { + have_remote_pid = true; + remote_pid = (unsigned long long)ucred.pid; + } +#else + #pragma message "SO_PEERCRED not available" + have_remote_pid = false; +#endif + /* Make sure sun_path is NULL terminated: */ + sun = (struct sockaddr_un *)&osa.u.sa; + sun->sun_path[sizeof(sun->sun_path) - 1] = '\0'; + if (have_remote_pid) + OSMO_STRBUF_PRINTF(sb, "r=%llu<->", remote_pid); + else + OSMO_STRBUF_PRINTF(sb, "r=NULL<->"); + OSMO_STRBUF_PRINTF(sb, "l=%s:%d", sun->sun_path, fd); + return sb.chars_needed; + } + default: + osmo_strlcpy(str, "<socket-family-no-supported>", str_len); + return -ENOTSUP; + } } /*! Get address/port information on socket in static string, like "r=1.2.3.4:5<->l=6.7.8.9:10". @@ -1715,6 +2491,65 @@ int osmo_sockaddr_local_ip(struct osmo_sockaddr *local_ip, const struct osmo_soc return rc; } +/*! Copy the addr part, the IP address octets in network byte order, to a buffer. + * Useful for encoding network protocols. + * \param[out] dst Write octets to this buffer. + * \param[in] dst_maxlen Space available in buffer. + * \param[in] os Sockaddr to copy IP of. + * \return nr of octets written on success, negative on error. + */ +int osmo_sockaddr_to_octets(uint8_t *dst, size_t dst_maxlen, const struct osmo_sockaddr *os) +{ + const void *addr; + size_t len; + switch (os->u.sa.sa_family) { + case AF_INET: + addr = &os->u.sin.sin_addr; + len = sizeof(os->u.sin.sin_addr); + break; + case AF_INET6: + addr = &os->u.sin6.sin6_addr; + len = sizeof(os->u.sin6.sin6_addr); + break; + default: + return -ENOTSUP; + } + if (dst_maxlen < len) + return -ENOSPC; + memcpy(dst, addr, len); + return len; +} + +/*! Copy the addr part, the IP address octets in network byte order, from a buffer. + * Useful for decoding network protocols. + * \param[out] os Write IP address to this sockaddr. + * \param[in] src Source buffer to read IP address octets from. + * \param[in] src_len Number of octets to copy. + * \return number of octets read on success, negative on error. + */ +int osmo_sockaddr_from_octets(struct osmo_sockaddr *os, const void *src, size_t src_len) +{ + void *addr; + size_t len; + *os = (struct osmo_sockaddr){0}; + switch (src_len) { + case sizeof(os->u.sin.sin_addr): + os->u.sa.sa_family = AF_INET; + addr = &os->u.sin.sin_addr; + len = sizeof(os->u.sin.sin_addr); + break; + case sizeof(os->u.sin6.sin6_addr): + os->u.sin6.sin6_family = AF_INET6; + addr = &os->u.sin6.sin6_addr; + len = sizeof(os->u.sin6.sin6_addr); + break; + default: + return -ENOTSUP; + } + memcpy(addr, src, len); + return len; +} + /*! Compare two osmo_sockaddr. * \param[in] a * \param[in] b @@ -1757,52 +2592,68 @@ const char *osmo_sockaddr_to_str(const struct osmo_sockaddr *sockaddr) return osmo_sockaddr_to_str_buf(buf, sizeof(buf), sockaddr); } -/*! string-format a given osmo_sockaddr address into a user-supplied buffer +/*! string-format a given osmo_sockaddr address into a user-supplied buffer. + * Same as osmo_sockaddr_to_str_buf() but returns a would-be length in snprintf() style. * \param[in] buf user-supplied output buffer * \param[in] buf_len size of the user-supplied output buffer in bytes * \param[in] sockaddr the osmo_sockaddr to print - * \return pointer to the string on success; NULL on error + * \return number of characters that would be written if the buffer is large enough, like snprintf(). */ -char *osmo_sockaddr_to_str_buf(char *buf, size_t buf_len, - const struct osmo_sockaddr *sockaddr) +int osmo_sockaddr_to_str_buf2(char *buf, size_t buf_len, const struct osmo_sockaddr *sockaddr) { + struct osmo_strbuf sb = { .buf = buf, .len = buf_len }; uint16_t port = 0; - size_t written; - if (buf_len < 5) - return NULL; - if (!sockaddr) - return NULL; + if (!sockaddr) { + OSMO_STRBUF_PRINTF(sb, "NULL"); + return sb.chars_needed; + } switch (sockaddr->u.sa.sa_family) { case AF_INET: - written = osmo_sockaddr_to_str_and_uint(buf, buf_len, &port, &sockaddr->u.sa); - if (written + 1 >= buf_len && port) - return NULL; + OSMO_STRBUF_APPEND(sb, osmo_sockaddr_to_str_and_uint, &port, &sockaddr->u.sa); if (port) - snprintf(buf + written, buf_len - written, ":%u", port); + OSMO_STRBUF_PRINTF(sb, ":%u", port); break; case AF_INET6: - buf[0] = '['; - written = osmo_sockaddr_to_str_and_uint(buf + 1, buf_len - 1, &port, &sockaddr->u.sa); - if (written + 2 >= buf_len) - return NULL; - - if (written + 3 >= buf_len && port) - return NULL; - + OSMO_STRBUF_PRINTF(sb, "["); + OSMO_STRBUF_APPEND(sb, osmo_sockaddr_to_str_and_uint, &port, &sockaddr->u.sa); + OSMO_STRBUF_PRINTF(sb, "]"); if (port) - snprintf(buf + 1 + written, buf_len - written - 1, "]:%u", port); - else { - buf[written + 1] = ']'; - buf[written + 2] = 0; - } + OSMO_STRBUF_PRINTF(sb, ":%u", port); break; default: - snprintf(buf, buf_len, "unsupported family %d", sockaddr->u.sa.sa_family); - return buf; + OSMO_STRBUF_PRINTF(sb, "unsupported family %d", sockaddr->u.sa.sa_family); + break; } + return sb.chars_needed; +} + +/*! string-format a given osmo_sockaddr address into a talloc allocated buffer. + * Like osmo_sockaddr_to_str_buf2() but returns a talloc allocated string. + * \param[in] ctx talloc context to allocate from, e.g. OTC_SELECT. + * \param[in] sockaddr the osmo_sockaddr to print. + * \return human readable string. + */ +char *osmo_sockaddr_to_str_c(void *ctx, const struct osmo_sockaddr *sockaddr) +{ + OSMO_NAME_C_IMPL(ctx, 64, "ERROR", osmo_sockaddr_to_str_buf2, sockaddr) +} + +/*! string-format a given osmo_sockaddr address into a user-supplied buffer. + * Like osmo_sockaddr_to_str_buf2() but returns buf, or NULL if too short. + * \param[in] buf user-supplied output buffer + * \param[in] buf_len size of the user-supplied output buffer in bytes + * \param[in] sockaddr the osmo_sockaddr to print + * \return pointer to the string on success; NULL on error + */ +char *osmo_sockaddr_to_str_buf(char *buf, size_t buf_len, + const struct osmo_sockaddr *sockaddr) +{ + int chars_needed = osmo_sockaddr_to_str_buf2(buf, buf_len, sockaddr); + if (chars_needed >= buf_len) + return NULL; return buf; } @@ -1870,6 +2721,83 @@ int osmo_sock_set_priority(int fd, int prio) return setsockopt(fd, SOL_SOCKET, SO_PRIORITY, &prio, sizeof(prio)); } +#ifdef HAVE_LIBSCTP +/*! Fill in array of struct sctp_paddrinfo with each of the remote addresses of an SCTP socket + * \param[in] fd file descriptor of SCTP socket + * \param[out] pinfo Pointer to memory holding an array of struct sctp_paddrinfo (pinfo_cnt length). + * \param[in,out] pinfo_cnt length of pinfo array (in elements). On return it contains the number of addresses found. + * \returns 0 on success; negative otherwise + * + * Upon return, pinfo_cnt can be set to a higher value than the one set by the + * caller. This can be used by the caller to find out the required array length + * and then obtaining by calling the function twice. Only up to pinfo_cnt addresses + * are filled in, as per the value provided by the caller. + * + * Usage example retrieving struct sctp_paddrinfo for all (up to OSMO_SOCK_MAX_ADDRS, 32) remote IP addresses: + * struct sctp_paddrinfo pinfo[OSMO_SOCK_MAX_ADDRS]; + * size_t pinfo_cnt = ARRAY_SIZE(pinfo); + * rc = osmo_sock_sctp_get_peer_addr_info(fd, &pinfo[0], &num_hostbuf, pinfo_cnt); + * if (rc < 0) + * goto error; + * if (pinfo_cnt > ARRAY_SIZE(hostbuf)) + * goto not_enough_buffers; + */ +int osmo_sock_sctp_get_peer_addr_info(int fd, struct sctp_paddrinfo *pinfo, size_t *pinfo_cnt) +{ + struct sockaddr *addrs = NULL; + unsigned int n_addrs, i; + void *addr_buf; + int rc; + socklen_t optlen; + + rc = sctp_getpaddrs(fd, 0, &addrs); + + if (rc < 0) + return rc; + if (rc == 0) + return -ENOTCONN; + + n_addrs = rc; + addr_buf = (void *)addrs; + for (i = 0; i < n_addrs; i++) { + struct sockaddr *sa_addr = (struct sockaddr *)addr_buf; + size_t addrlen; + + switch (sa_addr->sa_family) { + case AF_INET: + addrlen = sizeof(struct sockaddr_in); + break; + case AF_INET6: + addrlen = sizeof(struct sockaddr_in6); + break; + default: + rc = -EINVAL; + goto free_addrs_ret; + } + + if (i >= *pinfo_cnt) { + addr_buf += addrlen; + continue; + } + + memset(&pinfo[i], 0, sizeof(pinfo[0])); + memcpy(&pinfo[i].spinfo_address, sa_addr, addrlen); + optlen = sizeof(pinfo[0]); + rc = getsockopt(fd, SOL_SCTP, SCTP_GET_PEER_ADDR_INFO, &pinfo[i], &optlen); + if (rc < 0) + goto free_addrs_ret; + + addr_buf += addrlen; + } + + *pinfo_cnt = n_addrs; + rc = 0; +free_addrs_ret: + sctp_freepaddrs(addrs); + return rc; +} +#endif + #endif /* HAVE_SYS_SOCKET_H */ /*! @} */ diff --git a/src/core/soft_uart.c b/src/core/soft_uart.c new file mode 100644 index 00000000..f969ab70 --- /dev/null +++ b/src/core/soft_uart.c @@ -0,0 +1,518 @@ +/*! \file soft_uart.c + * Software UART implementation. */ +/* + * (C) 2022 by Harald Welte <laforge@gnumonks.org> + * (C) 2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * + * All Rights Reserved + * + * SPDX-License-Identifier: GPL-2.0+ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <stdbool.h> +#include <stdint.h> +#include <errno.h> + +#include <osmocom/core/utils.h> +#include <osmocom/core/timer.h> +#include <osmocom/core/soft_uart.h> + +/*! Rx/Tx flow state of a soft-UART */ +enum suart_flow_state { + SUART_FLOW_ST_IDLE, /*!< waiting for a start bit or Tx data */ + SUART_FLOW_ST_DATA, /*!< receiving/transmitting data bits */ + SUART_FLOW_ST_PARITY, /*!< receiving/transmitting parity bits */ + SUART_FLOW_ST_STOP, /*!< receiving/transmitting stop bits */ +}; + +/*! Internal state of a soft-UART */ +struct osmo_soft_uart { + struct osmo_soft_uart_cfg cfg; + const char *name; + /* modem status (bitmask of OSMO_SUART_STATUS_F_*) */ + unsigned int status; + struct { + bool running; + uint8_t bit_count; + uint8_t shift_reg; + struct msgb *msg; + ubit_t parity_bit; /* 0 (even) / 1 (odd) */ + unsigned int flags; + struct osmo_timer_list timer; + enum suart_flow_state flow_state; + } rx; + struct { + bool running; + uint8_t bit_count; + uint8_t shift_reg; + ubit_t parity_bit; /* 0 (even) / 1 (odd) */ + enum suart_flow_state flow_state; + } tx; +}; + +/*! Default soft-UART configuration (8-N-1) */ +const struct osmo_soft_uart_cfg osmo_soft_uart_default_cfg = { + .num_data_bits = 8, + .num_stop_bits = 1, + .parity_mode = OSMO_SUART_PARITY_NONE, + .rx_buf_size = 1024, + .rx_timeout_ms = 100, + .flow_ctrl_mode = OSMO_SUART_FLOW_CTRL_NONE, +}; + +/************************************************************************* + * Receiver + *************************************************************************/ + +/*! Flush the receive buffer, passing ownership of the msgb to the .rx_cb(). + * \param[in] suart soft-UART instance holding the receive buffer. */ +void osmo_soft_uart_flush_rx(struct osmo_soft_uart *suart) +{ + if (suart->rx.msg && msgb_length(suart->rx.msg)) { + osmo_timer_del(&suart->rx.timer); + if (suart->cfg.rx_cb) { + suart->cfg.rx_cb(suart->cfg.priv, suart->rx.msg, suart->rx.flags); + /* call-back has taken ownership of msgb, no need to free() here */ + suart->rx.msg = msgb_alloc_c(suart, suart->cfg.rx_buf_size, "soft_uart_rx"); + } else { + msgb_reset(suart->rx.msg); + } + } +} + +/* one character was received; add to receive buffer and notify user, if needed */ +static void suart_rx_ch(struct osmo_soft_uart *suart, uint8_t ch) +{ + unsigned int msg_len; + + OSMO_ASSERT(suart->rx.msg); + msgb_put_u8(suart->rx.msg, ch); + msg_len = msgb_length(suart->rx.msg); + + if (msg_len >= suart->cfg.rx_buf_size || suart->rx.flags) { + /* either the buffer is full, or we hit a parity and/or a framing error */ + osmo_soft_uart_flush_rx(suart); + } else if (msg_len == 1) { + /* first character in new message: start timer */ + osmo_timer_schedule(&suart->rx.timer, suart->cfg.rx_timeout_ms / 1000, + (suart->cfg.rx_timeout_ms % 1000) * 1000); + } +} + +/* receive a single bit */ +static inline void suart_rx_bit(struct osmo_soft_uart *suart, const ubit_t bit) +{ + switch (suart->rx.flow_state) { + case SUART_FLOW_ST_IDLE: + if (bit == 0) { /* start bit condition */ + suart->rx.flow_state = SUART_FLOW_ST_DATA; + suart->rx.flags = 0x00; + suart->rx.shift_reg = 0; + suart->rx.bit_count = 0; + suart->rx.parity_bit = 0; + } + break; + case SUART_FLOW_ST_DATA: + suart->rx.bit_count++; + suart->rx.shift_reg >>= 1; + if (bit != 0) { + suart->rx.parity_bit = !suart->rx.parity_bit; /* flip */ + suart->rx.shift_reg |= 0x80; + } + if (suart->rx.bit_count >= suart->cfg.num_data_bits) { + /* we have accumulated enough data bits */ + if (suart->cfg.parity_mode != OSMO_SUART_PARITY_NONE) + suart->rx.flow_state = SUART_FLOW_ST_PARITY; + else + suart->rx.flow_state = SUART_FLOW_ST_STOP; + /* align the register if needed */ + if (suart->cfg.num_data_bits < 8) + suart->rx.shift_reg >>= (8 - suart->cfg.num_data_bits); + } + break; + case SUART_FLOW_ST_PARITY: + switch (suart->cfg.parity_mode) { + case OSMO_SUART_PARITY_EVEN: + /* number of 1-bits (in both data and parity) shall be even */ + if (suart->rx.parity_bit != bit) + suart->rx.flags |= OSMO_SUART_F_PARITY_ERROR; + break; + case OSMO_SUART_PARITY_ODD: + /* number of 1-bits (in both data and parity) shall be odd */ + if (suart->rx.parity_bit == bit) + suart->rx.flags |= OSMO_SUART_F_PARITY_ERROR; + break; + case OSMO_SUART_PARITY_MARK: + /* parity bit must always be 1 */ + if (bit != 1) + suart->rx.flags |= OSMO_SUART_F_PARITY_ERROR; + break; + case OSMO_SUART_PARITY_SPACE: + /* parity bit must always be 0 */ + if (bit != 0) + suart->rx.flags |= OSMO_SUART_F_PARITY_ERROR; + break; + case OSMO_SUART_PARITY_NONE: /* shall not happen */ + default: + OSMO_ASSERT(0); + } + + suart->rx.flow_state = SUART_FLOW_ST_STOP; + break; + case SUART_FLOW_ST_STOP: + suart->rx.bit_count++; + if (bit != 1) + suart->rx.flags |= OSMO_SUART_F_FRAMING_ERROR; + + if (suart->rx.bit_count >= (suart->cfg.num_data_bits + suart->cfg.num_stop_bits)) { + /* we have accumulated enough stop bits */ + suart_rx_ch(suart, suart->rx.shift_reg); + suart->rx.flow_state = SUART_FLOW_ST_IDLE; + } + break; + } +} + +/* receive timer expiration: flush rx-buffer to user call-back */ +static void suart_rx_timer_cb(void *data) +{ + struct osmo_soft_uart *suart = data; + osmo_soft_uart_flush_rx(suart); +} + +/*! Feed a number of unpacked bits into the soft-UART receiver. + * \param[in] suart soft-UART instance to feed bits into. + * \param[in] ubits pointer to the unpacked bits. + * \param[in] n_ubits number of unpacked bits to be fed. + * \returns 0 on success; negative on error. + * -EAGAIN indicates that the receiver is disabled. */ +int osmo_soft_uart_rx_ubits(struct osmo_soft_uart *suart, const ubit_t *ubits, size_t n_ubits) +{ + if (!suart->rx.running) + return -EAGAIN; + for (size_t i = 0; i < n_ubits; i++) + suart_rx_bit(suart, ubits[i]); + return 0; +} + +/************************************************************************* + * Transmitter + *************************************************************************/ + +/* pull a single bit out of the UART transmitter */ +static inline ubit_t suart_tx_bit(struct osmo_soft_uart *suart, struct msgb *msg) +{ + ubit_t tx_bit = 1; + + switch (suart->tx.flow_state) { + case SUART_FLOW_ST_IDLE: + if (msg && msgb_length(msg) > 0) { /* if we have pending data */ + suart->tx.shift_reg = msgb_pull_u8(msg); + suart->tx.flow_state = SUART_FLOW_ST_DATA; + suart->tx.bit_count = 0; + suart->tx.parity_bit = 0; + tx_bit = 0; + } + break; + case SUART_FLOW_ST_DATA: + tx_bit = suart->tx.shift_reg & 1; + suart->tx.parity_bit ^= tx_bit; + suart->tx.shift_reg >>= 1; + suart->tx.bit_count++; + if (suart->tx.bit_count >= suart->cfg.num_data_bits) { + /* we have transmitted all data bits */ + if (suart->cfg.parity_mode != OSMO_SUART_PARITY_NONE) + suart->tx.flow_state = SUART_FLOW_ST_PARITY; + else + suart->tx.flow_state = SUART_FLOW_ST_STOP; + } + break; + case SUART_FLOW_ST_PARITY: + switch (suart->cfg.parity_mode) { + case OSMO_SUART_PARITY_EVEN: + /* number of 1-bits (in both data and parity) shall be even */ + tx_bit = suart->tx.parity_bit; + break; + case OSMO_SUART_PARITY_ODD: + /* number of 1-bits (in both data and parity) shall be odd */ + tx_bit = !suart->tx.parity_bit; + break; + case OSMO_SUART_PARITY_MARK: + /* parity bit must always be 1 */ + tx_bit = 1; + break; + case OSMO_SUART_PARITY_SPACE: + /* parity bit must always be 0 */ + tx_bit = 0; + break; + case OSMO_SUART_PARITY_NONE: + default: /* shall not happen */ + OSMO_ASSERT(0); + } + + suart->tx.flow_state = SUART_FLOW_ST_STOP; + break; + case SUART_FLOW_ST_STOP: + suart->tx.bit_count++; + if (suart->tx.bit_count >= (suart->cfg.num_data_bits + suart->cfg.num_stop_bits)) { + /* we have transmitted all stop bits, we're done */ + suart->tx.flow_state = SUART_FLOW_ST_IDLE; + } + break; + } + + return tx_bit; +} + +/* pull pending bits out of the UART */ +static size_t suart_tx_pending(struct osmo_soft_uart *suart, ubit_t *ubits, size_t n_ubits) +{ + size_t i; + + for (i = 0; i < n_ubits; i++) { + if (suart->tx.flow_state == SUART_FLOW_ST_IDLE) + break; + ubits[i] = suart_tx_bit(suart, NULL); + } + + return i; +} + +/*! Pull a number of unpacked bits out of the soft-UART transmitter. + * \param[in] suart soft-UART instance to pull the bits from. + * \param[out] ubits pointer to a buffer where to store pulled bits. + * \param[in] n_ubits number of unpacked bits to be pulled. + * \returns number of bits pulled (may be less than n_ubits); negative on error. + * -EAGAIN indicates that the transmitter is disabled. */ +int osmo_soft_uart_tx_ubits(struct osmo_soft_uart *suart, ubit_t *ubits, size_t n_ubits) +{ + const struct osmo_soft_uart_cfg *cfg = &suart->cfg; + size_t n_frame_bits, n_chars; + struct msgb *msg = NULL; + + if (OSMO_UNLIKELY(n_ubits == 0)) + return -EINVAL; + + if (!suart->tx.running) + return -EAGAIN; + + switch (suart->cfg.flow_ctrl_mode) { + case OSMO_SUART_FLOW_CTRL_DTR_DSR: + /* if DSR is de-asserted, Tx pending bits and suspend */ + if (~suart->status & OSMO_SUART_STATUS_F_DSR) + return suart_tx_pending(suart, ubits, n_ubits); + /* else: keep transmitting as usual */ + break; + case OSMO_SUART_FLOW_CTRL_RTS_CTS: + /* if CTS is de-asserted, Tx pending bits and suspend */ + if (~suart->status & OSMO_SUART_STATUS_F_CTS) + return suart_tx_pending(suart, ubits, n_ubits); + /* else: keep transmitting as usual */ + break; + case OSMO_SUART_FLOW_CTRL_NONE: + default: + break; + } + + /* calculate UART frame size for the effective config */ + n_frame_bits = 1 + cfg->num_data_bits + cfg->num_stop_bits; + if (cfg->parity_mode != OSMO_SUART_PARITY_NONE) + n_frame_bits += 1; + + /* calculate the number of characters we can fit into n_ubits */ + n_chars = n_ubits / n_frame_bits; + if (n_chars == 0) { + /* we can transmit at least one character */ + if (suart->tx.flow_state == SUART_FLOW_ST_IDLE) + n_chars = 1; + } + + if (n_chars > 0) { + /* allocate a Tx buffer msgb */ + msg = msgb_alloc_c(suart, n_chars, "soft_uart_tx"); + OSMO_ASSERT(msg != NULL); + + /* call the .tx_cb() to populate the Tx buffer */ + OSMO_ASSERT(cfg->tx_cb != NULL); + suart->cfg.tx_cb(cfg->priv, msg); + } + + for (size_t i = 0; i < n_ubits; i++) + ubits[i] = suart_tx_bit(suart, msg); + msgb_free(msg); + + return n_ubits; +} + +/*! Get the modem status bitmask of the given soft-UART. + * \param[in] suart soft-UART instance to get the modem status. + * \returns bitmask of OSMO_SUART_STATUS_F_*. */ +unsigned int osmo_soft_uart_get_status(const struct osmo_soft_uart *suart) +{ + return suart->status; +} + +/*! Set the modem status bitmask of the given soft-UART. + * \param[in] suart soft-UART instance to set the modem status. + * \param[in] status bitmask of OSMO_SUART_STATUS_F_*. + * \returns 0 on success; negative on error. */ +int osmo_soft_uart_set_status(struct osmo_soft_uart *suart, unsigned int status) +{ + const struct osmo_soft_uart_cfg *cfg = &suart->cfg; + + if (cfg->status_change_cb != NULL) { + if (suart->status != status) + cfg->status_change_cb(cfg->priv, status); + } + + suart->status = status; + return 0; +} + +/*! Activate/deactivate a modem status line of the given soft-UART. + * \param[in] suart soft-UART instance to update the modem status. + * \param[in] line a modem status line, one of OSMO_SUART_STATUS_F_*. + * \param[in] active activate (true) or deactivate (false) the line. */ +void osmo_soft_uart_set_status_line(struct osmo_soft_uart *suart, + enum osmo_soft_uart_status line, + bool active) +{ + unsigned int status = suart->status; + + if (active) /* assert the given line */ + status |= line; + else /* de-assert the given line */ + status &= ~line; + + osmo_soft_uart_set_status(suart, status); +} + + +/************************************************************************* + * Management / Initialization + *************************************************************************/ + +/*! Allocate a soft-UART instance. + * \param[in] ctx parent talloc context. + * \param[in] name name of the soft-UART instance. + * \param[in] cfg initial configuration of the soft-UART instance. + * \returns pointer to allocated soft-UART instance; NULL on error. */ +struct osmo_soft_uart *osmo_soft_uart_alloc(void *ctx, const char *name, + const struct osmo_soft_uart_cfg *cfg) +{ + struct osmo_soft_uart *suart = talloc_zero(ctx, struct osmo_soft_uart); + if (!suart) + return NULL; + suart->name = talloc_strdup(suart, name); + + OSMO_ASSERT(cfg != NULL); + suart->cfg = *cfg; + + return suart; +} + +/*! Release memory taken by the given soft-UART. + * \param[in] suart soft-UART instance to be free()d. */ +void osmo_soft_uart_free(struct osmo_soft_uart *suart) +{ + if (suart == NULL) + return; + + osmo_timer_del(&suart->rx.timer); + msgb_free(suart->rx.msg); + + talloc_free((void *)suart->name); + talloc_free(suart); +} + +/*! Change soft-UART configuration to the user-provided config. + * \param[in] suart soft-UART instance to be re-configured. + * \param[in] cfg the user-provided config to be applied. + * \returns 0 on success; negative on error. */ +int osmo_soft_uart_configure(struct osmo_soft_uart *suart, const struct osmo_soft_uart_cfg *cfg) +{ + /* consistency checks on the configuration */ + if (cfg->num_data_bits > 8 || cfg->num_data_bits == 0) + return -EINVAL; + if (cfg->num_stop_bits == 0) + return -EINVAL; + if (cfg->parity_mode < 0 || cfg->parity_mode >= _OSMO_SUART_PARITY_NUM) + return -EINVAL; + if (cfg->rx_buf_size == 0) + return -EINVAL; + + if (suart->cfg.rx_buf_size > cfg->rx_buf_size || + suart->cfg.rx_timeout_ms > cfg->rx_timeout_ms) { + osmo_soft_uart_flush_rx(suart); + } + + suart->cfg = *cfg; + + osmo_timer_setup(&suart->rx.timer, suart_rx_timer_cb, suart); + + return 0; +} + +/*! Get a name for the given soft-UART instance. + * \param[in] suart soft-UART instance to get the name from. + * \returns name of the given soft-UART instance. */ +const char *osmo_soft_uart_get_name(const struct osmo_soft_uart *suart) +{ + return suart->name; +} + +/*! Set a new name for the given soft-UART instance. + * \param[in] suart soft-UART instance to set the name for. + * \param[in] name the new name. */ +void osmo_soft_uart_set_name(struct osmo_soft_uart *suart, const char *name) +{ + osmo_talloc_replace_string(suart, (char **)&suart->name, name); +} + +/*! Enable/disable receiver of the given soft-UART. + * \param[in] suart soft-UART instance to be re-configured. + * \param[in] enable enable/disable state of the receiver. + * \returns 0 on success; negative on error. */ +int osmo_soft_uart_set_rx(struct osmo_soft_uart *suart, bool enable) +{ + if (!enable && suart->rx.running) { + osmo_soft_uart_flush_rx(suart); + suart->rx.running = false; + suart->rx.flow_state = SUART_FLOW_ST_IDLE; + } else if (enable && !suart->rx.running) { + if (!suart->rx.msg) + suart->rx.msg = msgb_alloc_c(suart, suart->cfg.rx_buf_size, "soft_uart_rx"); + suart->rx.running = true; + suart->rx.flow_state = SUART_FLOW_ST_IDLE; + } + + return 0; +} + +/*! Enable/disable transmitter of the given soft-UART. + * \param[in] suart soft-UART instance to be re-configured. + * \param[in] enable enable/disable state of the transmitter. + * \returns 0 on success; negative on error. */ +int osmo_soft_uart_set_tx(struct osmo_soft_uart *suart, bool enable) +{ + if (!enable && suart->tx.running) { + suart->tx.running = false; + suart->tx.flow_state = SUART_FLOW_ST_IDLE; + } else if (enable && !suart->tx.running) { + suart->tx.running = true; + suart->tx.flow_state = SUART_FLOW_ST_IDLE; + } + + return 0; +} diff --git a/src/stat_item.c b/src/core/stat_item.c index 17887464..804972bf 100644 --- a/src/stat_item.c +++ b/src/core/stat_item.c @@ -18,10 +18,6 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ /*! \addtogroup osmo_stat_item @@ -54,20 +50,105 @@ */ /* Struct overview: - * Each osmo_stat_item is attached to an osmo_stat_item_group, which is - * attached to the global osmo_stat_item_groups list. * - * osmo_stat_item_groups - * / \ - * group1 group2 - * / \ - * item1 item2 - * | - * values - * / \ - * 1 2 - * |-id |-id - * '-value '-value + * Group and item descriptions: + * Each group description exists once as osmo_stat_item_group_desc, + * each such group description lists N osmo_stat_item_desc, i.e. describes N stat items. + * + * Actual stats: + * The global osmo_stat_item_groups llist contains all group instances, each points at a group description. + * This list mixes all types of groups in a single llist, where each instance points at its group desc and has an index. + * There are one or more instances of each group, each storing stats for a distinct object (for example, one description + * for a BTS group, and any number of BTS instances with independent stats). A group is identified by a group index nr + * and possibly also a given name for that particular index (e.g. in osmo-mgw, a group instance is named + * "virtual-trunk-0" and can be looked up by that name instead of its more or less arbitrary group index number). + * + * Each group instance contains one osmo_stat_item instance per global stat item description. + * Each osmo_stat_item keeps track of the values for the current reporting period (min, last, max, sum, n), + * and also stores the set of values reported at the end of the previous reporting period. + * + * const osmo_stat_item_group_desc foo + * +-- group_name_prefix = "foo" + * +-- item_desc[] (array of osmo_stat_item_desc) + * +-- osmo_stat_item_desc bar + * | +-- name = "bar" + * | +-- description + * | +-- unit + * | +-- default_value + * | + * +-- osmo_stat_item_desc: baz + * +-- ... + * + * const osmo_stat_item_group_desc moo + * +-- group_name_prefix = "moo" + * +-- item_desc[] + * +-- osmo_stat_item_desc goo + * | +-- name = "goo" + * | +-- description + * | +-- unit + * | +-- default_value + * | + * +-- osmo_stat_item_desc: loo + * +-- ... + * + * osmo_stat_item_groups (llist of osmo_stat_item_group) + * | + * +-- group: foo[0] + * | +-- desc --> osmo_stat_item_group_desc foo + * | +-- idx = 0 + * | +-- name = NULL (no given name for this group instance) + * | +-- items[] + * | | + * | [0] --> osmo_stat_item instance for "bar" + * | | +-- desc --> osmo_stat_item_desc bar (see above) + * | | +-- value.{min, last, max, n, sum} + * | | +-- reported.{min, last, max, n, sum} + * | | + * | [1] --> osmo_stat_item instance for "baz" + * | | +-- desc --> osmo_stat_item_desc baz + * | | +-- value.{min, last, max, n, sum} + * | | +-- reported.{min, last, max, n, sum} + * | . + * | : + * | + * +-- group: foo[1] + * | +-- desc --> osmo_stat_item_group_desc foo + * | +-- idx = 1 + * | +-- name = "special-foo" (instance can be looked up by this index-name) + * | +-- items[] + * | | + * | [0] --> osmo_stat_item instance for "bar" + * | | +-- desc --> osmo_stat_item_desc bar + * | | +-- value.{min, last, max, n, sum} + * | | +-- reported.{min, last, max, n, sum} + * | | + * | [1] --> osmo_stat_item instance for "baz" + * | | +-- desc --> osmo_stat_item_desc baz + * | | +-- value.{min, last, max, n, sum} + * | | +-- reported.{min, last, max, n, sum} + * | . + * | : + * | + * +-- group: moo[0] + * | +-- desc --> osmo_stat_item_group_desc moo + * | +-- idx = 0 + * | +-- name = NULL + * | +-- items[] + * | | + * | [0] --> osmo_stat_item instance for "goo" + * | | +-- desc --> osmo_stat_item_desc goo + * | | +-- value.{min, last, max, n, sum} + * | | +-- reported.{min, last, max, n, sum} + * | | + * | [1] --> osmo_stat_item instance for "loo" + * | | +-- desc --> osmo_stat_item_desc loo + * | | +-- value.{min, last, max, n, sum} + * | | +-- reported.{min, last, max, n, sum} + * | . + * | : + * . + * : + * */ #include <stdint.h> @@ -80,6 +161,8 @@ #include <osmocom/core/timer.h> #include <osmocom/core/stat_item.h> +#include <stat_item_internal.h> + /*! global list of stat_item groups */ static LLIST_HEAD(osmo_stat_item_groups); @@ -98,9 +181,8 @@ struct osmo_stat_item_group *osmo_stat_item_group_alloc(void *ctx, unsigned int idx) { unsigned int group_size; - unsigned long items_size = 0; unsigned int item_idx; - void *items; + struct osmo_stat_item *items; struct osmo_stat_item_group *group; @@ -117,52 +199,34 @@ struct osmo_stat_item_group *osmo_stat_item_group_alloc(void *ctx, group->desc = group_desc; group->idx = idx; - /* Get combined size of all items */ - for (item_idx = 0; item_idx < group_desc->num_items; item_idx++) { - unsigned int size; - size = sizeof(struct osmo_stat_item) + - sizeof(struct osmo_stat_item_value) * - group_desc->item_desc[item_idx].num_values; - /* Align to pointer size */ - size = (size + sizeof(void *) - 1) & ~(sizeof(void *) - 1); - - /* Store offsets into the item array */ - group->items[item_idx] = (void *)items_size; - - items_size += size; - } - - items = talloc_zero_size(group, items_size); - if (!items) { - talloc_free(group); - return NULL; - } - - /* Update item pointers */ + items = talloc_array(group, struct osmo_stat_item, group_desc->num_items); + OSMO_ASSERT(items); for (item_idx = 0; item_idx < group_desc->num_items; item_idx++) { - struct osmo_stat_item *item = (struct osmo_stat_item *) - ((uint8_t *)items + (unsigned long)group->items[item_idx]); - unsigned int i; - + struct osmo_stat_item *item = &items[item_idx]; + const struct osmo_stat_item_desc *item_desc = &group_desc->item_desc[item_idx]; group->items[item_idx] = item; - item->last_offs = group_desc->item_desc[item_idx].num_values - 1; - item->stats_next_id = 1; - item->desc = &group_desc->item_desc[item_idx]; - - for (i = 0; i <= item->last_offs; i++) { - item->values[i].value = group_desc->item_desc[item_idx].default_value; - item->values[i].id = OSMO_STAT_ITEM_NOVALUE_ID; - } + *item = (struct osmo_stat_item){ + .desc = item_desc, + .value = { + .n = 0, + .last = item_desc->default_value, + .min = item_desc->default_value, + .max = item_desc->default_value, + .sum = 0, + }, + }; } llist_add(&group->list, &osmo_stat_item_groups); - return group; } /*! Free the memory for the specified group of stat items */ void osmo_stat_item_group_free(struct osmo_stat_item_group *grp) { + if (!grp) + return; + llist_del(&grp->list); talloc_free(grp); } @@ -195,8 +259,7 @@ void osmo_stat_item_group_set_name(struct osmo_stat_item_group *statg, const cha */ void osmo_stat_item_inc(struct osmo_stat_item *item, int32_t value) { - int32_t oldvalue = item->values[item->last_offs].value; - osmo_stat_item_set(item, oldvalue + value); + osmo_stat_item_set(item, item->value.last + value); } /*! Descrease the stat_item to the given value. @@ -207,8 +270,7 @@ void osmo_stat_item_inc(struct osmo_stat_item *item, int32_t value) */ void osmo_stat_item_dec(struct osmo_stat_item *item, int32_t value) { - int32_t oldvalue = item->values[item->last_offs].value; - osmo_stat_item_set(item, oldvalue - value); + osmo_stat_item_set(item, item->value.last - value); } /*! Set the a given stat_item to the given value. @@ -219,89 +281,37 @@ void osmo_stat_item_dec(struct osmo_stat_item *item, int32_t value) */ void osmo_stat_item_set(struct osmo_stat_item *item, int32_t value) { - int32_t id = item->values[item->last_offs].id + 1; - if (id == OSMO_STAT_ITEM_NOVALUE_ID) - id++; - - item->last_offs += 1; - if (item->last_offs >= item->desc->num_values) - item->last_offs = 0; - - item->values[item->last_offs].value = value; - item->values[item->last_offs].id = id; -} - -/*! Retrieve the next value from the osmo_stat_item object. - * If a new value has been set, it is returned. The next_id is used to decide - * which value to return. - * On success, *next_id is updated to refer to the next unread value. If - * values have been missed due to FIFO overflow, *next_id is incremented by - * (1 + num_lost). - * This way, the osmo_stat_item object can be kept stateless from the reader's - * perspective and therefore be used by several backends simultaneously. - * - * \param val the osmo_stat_item object - * \param next_id identifies the next value to be read - * \param value a pointer to store the value - * \returns the increment of the index (0: no value has been read, - * 1: one value has been taken, - * (1+n): n values have been skipped, one has been taken) - */ -int osmo_stat_item_get_next(const struct osmo_stat_item *item, int32_t *next_id, - int32_t *value) -{ - const struct osmo_stat_item_value *next_value; - const struct osmo_stat_item_value *item_value = NULL; - int id_delta; - int next_offs; - - next_offs = item->last_offs; - next_value = &item->values[next_offs]; - - while (next_value->id - *next_id >= 0 && - next_value->id != OSMO_STAT_ITEM_NOVALUE_ID) - { - item_value = next_value; - - next_offs -= 1; - if (next_offs < 0) - next_offs = item->desc->num_values - 1; - if (next_offs == item->last_offs) - break; - next_value = &item->values[next_offs]; - } - - if (!item_value) - /* All items have been read */ - return 0; - - *value = item_value->value; - - id_delta = item_value->id + 1 - *next_id; - - *next_id = item_value->id + 1; - - if (id_delta > 1) { - LOGP(DLSTATS, LOGL_ERROR, "%s: %d stats values skipped\n", item->desc->name, id_delta - 1); + item->value.last = value; + if (item->value.n == 0) { + /* No values recorded yet, clamp min and max to this first value. */ + item->value.min = item->value.max = value; + /* Overwrite any cruft remaining in value.sum */ + item->value.sum = value; + item->value.n = 1; + } else { + item->value.min = OSMO_MIN(item->value.min, value); + item->value.max = OSMO_MAX(item->value.max, value); + item->value.sum += value; + item->value.n++; } - - return id_delta; } -/*! Skip/discard all values of this item and update \a next_id accordingly */ -int osmo_stat_item_discard(const struct osmo_stat_item *item, int32_t *next_id) +/*! Indicate that a reporting period has elapsed, and prepare the stat item for a new period of collecting min/max/avg. + * \param item Stat item to flush. + */ +void osmo_stat_item_flush(struct osmo_stat_item *item) { - int discarded = item->values[item->last_offs].id + 1 - *next_id; - *next_id = item->values[item->last_offs].id + 1; + item->reported = item->value; - return discarded; -} + /* Indicate a new reporting period: no values have been received, but the previous value.last remains unchanged + * for the case that an entire period elapses without a new value appearing. */ + item->value.n = 0; + item->value.sum = 0; -/*! Skip all values of all items and update \a next_id accordingly */ -int osmo_stat_item_discard_all(int32_t *next_id) -{ - LOGP(DLSTATS, LOGL_ERROR, "osmo_stat_item_discard_all is deprecated (no-op)\n"); - return 0; + /* Also for the case that an entire period elapses without any osmo_stat_item_set(), put the min and max to the + * last value. As soon as one osmo_stat_item_set() occurs, these are both set to the new value (when n is still + * zero from above). */ + item->value.min = item->value.max = item->value.last; } /*! Initialize the stat item module. Call this once from your program. @@ -415,21 +425,20 @@ int osmo_stat_item_for_each_group(osmo_stat_item_group_handler_t handle_group, v return rc; } +/*! Get the last (freshest) value. */ +int32_t osmo_stat_item_get_last(const struct osmo_stat_item *item) +{ + return item->value.last; +} /*! Remove all values of a stat item * \param[in] item stat item to reset */ void osmo_stat_item_reset(struct osmo_stat_item *item) { - unsigned int i; - - item->last_offs = item->desc->num_values - 1; - item->stats_next_id = 1; - - for (i = 0; i <= item->last_offs; i++) { - item->values[i].value = item->desc->default_value; - item->values[i].id = OSMO_STAT_ITEM_NOVALUE_ID; - } + item->value.sum = 0; + item->value.n = 0; + item->value.last = item->value.min = item->value.max = item->desc->default_value; } /*! Reset all osmo stat items in a group @@ -444,4 +453,11 @@ void osmo_stat_item_group_reset(struct osmo_stat_item_group *statg) osmo_stat_item_reset(item); } } + +/*! Return the description for an osmo_stat_item. */ +const struct osmo_stat_item_desc *osmo_stat_item_get_desc(struct osmo_stat_item *item) +{ + return item->desc; +} + /*! @} */ diff --git a/src/core/stat_item_internal.h b/src/core/stat_item_internal.h new file mode 100644 index 00000000..9ede8c41 --- /dev/null +++ b/src/core/stat_item_internal.h @@ -0,0 +1,35 @@ +/*! \file stat_item_internal.h + * internal definitions for the osmo_stat_item API */ +#pragma once + +/*! \addtogroup osmo_stat_item + * @{ + */ + +struct osmo_stat_item_period { + /*! Number of osmo_stat_item_set() that occurred during the reporting period, zero if none. */ + uint32_t n; + /*! Smallest value seen in a reporting period. */ + int32_t min; + /*! Most recent value passed to osmo_stat_item_set(), or the item->desc->default_value if none. */ + int32_t last; + /*! Largest value seen in a reporting period. */ + int32_t max; + /*! Sum of all values passed to osmo_stat_item_set() in the reporting period. */ + int64_t sum; +}; + +/*! data we keep for each actual item */ +struct osmo_stat_item { + /*! back-reference to the item description */ + const struct osmo_stat_item_desc *desc; + + /*! Current reporting period / current value. */ + struct osmo_stat_item_period value; + + /*! The results of the previous reporting period. According to these, the stats reporter decides whether to + * re-send values or omit an unchanged value from a report. */ + struct osmo_stat_item_period reported; +}; + +/*! @} */ diff --git a/src/stats.c b/src/core/stats.c index f06515dd..16e6f620 100644 --- a/src/stats.c +++ b/src/core/stats.c @@ -16,10 +16,6 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ /*! \addtogroup stats @@ -89,6 +85,7 @@ #include <osmocom/core/select.h> #include <osmocom/core/counter.h> #include <osmocom/core/msgb.h> +#include <osmocom/core/stats_tcp.h> #ifdef HAVE_SYSTEMTAP /* include the generated probes header and put markers in code */ @@ -101,10 +98,12 @@ #define TRACE_ENABLED(probe) (0) #endif /* HAVE_SYSTEMTAP */ +#include <stat_item_internal.h> + #define STATS_DEFAULT_INTERVAL 5 /* secs */ #define STATS_DEFAULT_BUFLEN 256 -static LLIST_HEAD(osmo_stats_reporter_list); +LLIST_HEAD(osmo_stats_reporter_list); static void *osmo_stats_ctx = NULL; static int is_initialised = 0; @@ -176,7 +175,7 @@ static int osmo_stats_timer_cb(struct osmo_fd *ofd, unsigned int what) return 0; } -static int start_timer() +static int start_timer(void) { int rc; int interval = osmo_stats_config->interval; @@ -215,13 +214,15 @@ struct osmo_stats_reporter *osmo_stats_reporter_alloc(enum osmo_stats_reporter_t { struct osmo_stats_reporter *srep; srep = talloc_zero(osmo_stats_ctx, struct osmo_stats_reporter); - OSMO_ASSERT(srep); + if (!srep) + return NULL; + srep->type = type; if (name) srep->name = talloc_strdup(srep, name); srep->fd = -1; - llist_add(&srep->list, &osmo_stats_reporter_list); + llist_add_tail(&srep->list, &osmo_stats_reporter_list); return srep; } @@ -235,30 +236,17 @@ void osmo_stats_reporter_free(struct osmo_stats_reporter *srep) talloc_free(srep); } -static int osmo_stats_discard_item(struct osmo_stat_item_group *statg, struct osmo_stat_item *item, void *sctx_) -{ - return osmo_stat_item_discard(item, &item->stats_next_id); -} - -static int osmo_stats_discard_group(struct osmo_stat_item_group *statg, void *sctx_) -{ - return osmo_stat_item_for_each_item(statg, &osmo_stats_discard_item, NULL); -} - -static int osmo_stats_discard_all() -{ - return osmo_stat_item_for_each_group(&osmo_stats_discard_group, NULL); -} - -/*! Initilize the stats reporting module; call this once in your program +/*! Initialize the stats reporting module; call this once in your program. * \param[in] ctx Talloc context from which stats related memory is allocated */ void osmo_stats_init(void *ctx) { osmo_stats_ctx = ctx; - osmo_stats_discard_all(); - is_initialised = 1; start_timer(); + + /* Make sure that the tcp-stats interval timer also runs at its + * preconfigured rate. The vty might change this setting later. */ + osmo_stats_tcp_set_interval(osmo_tcp_stats_config->interval); } /*! Find a stats_reporter of given \a type and \a name. @@ -426,7 +414,7 @@ int osmo_stats_reporter_set_flush_period(struct osmo_stats_reporter *srep, unsig /*! Set the name prefix of a given stats_reporter. * \param[in] srep stats_reporter whose name prefix is to be set - * \param[in] prefix NAme perfix to pre-pend for any reported value + * \param[in] prefix Name prefix to pre-pend for any reported value * \returns 0 on success; negative on error */ int osmo_stats_reporter_set_name_prefix(struct osmo_stats_reporter *srep, const char *prefix) { @@ -500,6 +488,8 @@ int osmo_stats_reporter_udp_open(struct osmo_stats_reporter *srep) } srep->buffer = msgb_alloc(buffer_size, "stats buffer"); + if (!srep->buffer) + goto failed; return 0; @@ -583,6 +573,8 @@ struct osmo_stats_reporter *osmo_stats_reporter_create_log(const char *name) { struct osmo_stats_reporter *srep; srep = osmo_stats_reporter_alloc(OSMO_STATS_REPORTER_LOG, name); + if (!srep) + return NULL; srep->have_net_config = 0; @@ -708,43 +700,29 @@ static int osmo_stat_item_handler( struct osmo_stat_item_group *statg, struct osmo_stat_item *item, void *sctx_) { struct osmo_stats_reporter *srep; - int32_t value; - bool have_value; - - have_value = osmo_stat_item_get_next(item, &item->stats_next_id, &value) > 0; - if (!have_value) { - /* Send the last value in case a flush is requested */ - value = osmo_stat_item_get_last(item); - - /* Also send it in case a different max value was sent - * previously (OS#5215) */ - if (!item->stats_last_sent_was_max) - have_value = 1; - } else { - int32_t next_val; - /* If we have multiple values only send the max */ - while (osmo_stat_item_get_next(item, &item->stats_next_id, &next_val) > 0) - value = OSMO_MAX(value, next_val); - } - - item->stats_last_sent_was_max = (osmo_stat_item_get_last(item) == value); + int32_t prev_reported_value = item->reported.max; + int32_t new_value = item->value.max; llist_for_each_entry(srep, &osmo_stats_reporter_list, list) { if (!srep->running) continue; - if (!have_value && !srep->force_single_flush) + /* If the previously reported value is the same as the current value, skip resending the value. + * However, if the stats reporter is set to resend all values, do resend the current value regardless of + * repetitions. + */ + if (new_value == prev_reported_value && !srep->force_single_flush) continue; if (!osmo_stats_reporter_check_config(srep, statg->idx, statg->desc->class_id)) continue; - osmo_stats_reporter_send_item(srep, statg, - item->desc, value); - + osmo_stats_reporter_send_item(srep, statg, item->desc, new_value); } + osmo_stat_item_flush(item); + return 0; } @@ -786,7 +764,7 @@ static int handle_counter(struct osmo_counter *counter, void *sctx_) /*** main reporting function ***/ -static void flush_all_reporters() +static void flush_all_reporters(void) { struct osmo_stats_reporter *srep; @@ -809,7 +787,7 @@ static void flush_all_reporters() } } -int osmo_stats_report() +int osmo_stats_report(void) { /* per group actions */ TRACE(LIBOSMOCORE_STATS_START()); @@ -818,7 +796,6 @@ int osmo_stats_report() osmo_stat_item_for_each_group(osmo_stat_item_group_handler, NULL); /* global actions */ - osmo_stats_discard_all(); flush_all_reporters(); TRACE(LIBOSMOCORE_STATS_DONE()); diff --git a/src/stats_statsd.c b/src/core/stats_statsd.c index 1acfce83..b27baff8 100644 --- a/src/stats_statsd.c +++ b/src/core/stats_statsd.c @@ -15,10 +15,6 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ /*! \addtogroup stats @@ -58,6 +54,8 @@ struct osmo_stats_reporter *osmo_stats_reporter_create_statsd(const char *name) { struct osmo_stats_reporter *srep; srep = osmo_stats_reporter_alloc(OSMO_STATS_REPORTER_STATSD, name); + if (!srep) + return NULL; srep->have_net_config = 1; diff --git a/src/core/stats_tcp.c b/src/core/stats_tcp.c new file mode 100644 index 00000000..c6459fe8 --- /dev/null +++ b/src/core/stats_tcp.c @@ -0,0 +1,327 @@ +/* + * (C) 2021 by sysmocom - s.f.m.c. GmbH + * Author: Philipp Maier <pmaier@sysmocom.de> + * All Rights Reserved + * + * SPDX-License-Identifier: GPL-2.0+ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +/*! \addtogroup stats + * @{ + * \file stats_tcp.c */ + +#include "config.h" +#if !defined(EMBEDDED) + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netinet/ip.h> +#include <linux/tcp.h> +#include <errno.h> +#include <pthread.h> + +#include <osmocom/core/select.h> +#include <osmocom/core/linuxlist.h> +#include <osmocom/core/talloc.h> +#include <osmocom/core/utils.h> +#include <osmocom/core/timer.h> +#include <osmocom/core/stat_item.h> +#include <osmocom/core/stats.h> +#include <osmocom/core/socket.h> +#include <osmocom/core/stats_tcp.h> + +static struct osmo_tcp_stats_config s_tcp_stats_config = { + .interval = TCP_STATS_DEFAULT_INTERVAL, +}; + +struct osmo_tcp_stats_config *osmo_tcp_stats_config = &s_tcp_stats_config; + +static struct osmo_timer_list stats_tcp_poll_timer; + +static LLIST_HEAD(stats_tcp); +static struct stats_tcp_entry *stats_tcp_entry_cur; +pthread_mutex_t stats_tcp_lock; + +struct stats_tcp_entry { + struct llist_head entry; + const struct osmo_fd *fd; + struct osmo_stat_item_group *stats_tcp; + const char *name; +}; + +enum { + STATS_TCP_UNACKED, + STATS_TCP_LOST, + STATS_TCP_RETRANS, + STATS_TCP_RTT, + STATS_TCP_RCV_RTT, + STATS_TCP_NOTSENT_BYTES, + STATS_TCP_RWND_LIMITED, + STATS_TCP_SNDBUF_LIMITED, + STATS_TCP_REORD_SEEN, +}; + +static struct osmo_stat_item_desc stats_tcp_item_desc[] = { + [STATS_TCP_UNACKED] = { "tcp:unacked", "unacknowledged packets", "", 60, 0 }, + [STATS_TCP_LOST] = { "tcp:lost", "lost packets", "", 60, 0 }, + [STATS_TCP_RETRANS] = { "tcp:retrans", "retransmitted packets", "", 60, 0 }, + [STATS_TCP_RTT] = { "tcp:rtt", "roundtrip-time", "", 60, 0 }, + [STATS_TCP_RCV_RTT] = { "tcp:rcv_rtt", "roundtrip-time (receive)", "", 60, 0 }, + [STATS_TCP_NOTSENT_BYTES] = { "tcp:notsent_bytes", "bytes not yet sent", "", 60, 0 }, + [STATS_TCP_RWND_LIMITED] = { "tcp:rwnd_limited", "time (usec) limited by receive window", "", 60, 0 }, + [STATS_TCP_SNDBUF_LIMITED] = { "tcp:sndbuf_limited", "Time (usec) limited by send buffer", "", 60, 0 }, + [STATS_TCP_REORD_SEEN] = { "tcp:reord_seen", "reordering events seen", "", 60, 0 }, +}; + +static struct osmo_stat_item_group_desc stats_tcp_desc = { + .group_name_prefix = "tcp", + .group_description = "stats tcp", + .class_id = OSMO_STATS_CLASS_GLOBAL, + .num_items = ARRAY_SIZE(stats_tcp_item_desc), + .item_desc = stats_tcp_item_desc, +}; + +static void fill_stats(struct stats_tcp_entry *stats_tcp_entry) +{ + int rc; + struct tcp_info tcp_info; + socklen_t tcp_info_len = sizeof(tcp_info); + char stat_name[256]; + + /* Do not fill in anything before the socket is connected to a remote end */ + if (osmo_sock_get_ip_and_port(stats_tcp_entry->fd->fd, NULL, 0, NULL, 0, false) != 0) + return; + + /* Gather TCP statistics and update the stats items */ + rc = getsockopt(stats_tcp_entry->fd->fd, IPPROTO_TCP, TCP_INFO, &tcp_info, &tcp_info_len); + if (rc < 0) + return; + + /* Create stats items if they do not exist yet */ + if (!stats_tcp_entry->stats_tcp) { + stats_tcp_entry->stats_tcp = + osmo_stat_item_group_alloc(stats_tcp_entry, &stats_tcp_desc, stats_tcp_entry->fd->fd); + OSMO_ASSERT(stats_tcp_entry->stats_tcp); + } + + /* Update statistics */ + if (stats_tcp_entry->name) + snprintf(stat_name, sizeof(stat_name), "%s", stats_tcp_entry->name); + else + snprintf(stat_name, sizeof(stat_name), "%s", osmo_sock_get_name2(stats_tcp_entry->fd->fd)); + osmo_stat_item_group_set_name(stats_tcp_entry->stats_tcp, stat_name); + + osmo_stat_item_set(osmo_stat_item_group_get_item(stats_tcp_entry->stats_tcp, STATS_TCP_UNACKED), + tcp_info.tcpi_unacked); + osmo_stat_item_set(osmo_stat_item_group_get_item(stats_tcp_entry->stats_tcp, STATS_TCP_LOST), + tcp_info.tcpi_lost); + osmo_stat_item_set(osmo_stat_item_group_get_item(stats_tcp_entry->stats_tcp, STATS_TCP_RETRANS), + tcp_info.tcpi_retrans); + osmo_stat_item_set(osmo_stat_item_group_get_item(stats_tcp_entry->stats_tcp, STATS_TCP_RTT), tcp_info.tcpi_rtt); + osmo_stat_item_set(osmo_stat_item_group_get_item(stats_tcp_entry->stats_tcp, STATS_TCP_RCV_RTT), + tcp_info.tcpi_rcv_rtt); +#if HAVE_TCP_INFO_TCPI_NOTSENT_BYTES == 1 + osmo_stat_item_set(osmo_stat_item_group_get_item(stats_tcp_entry->stats_tcp, STATS_TCP_NOTSENT_BYTES), + tcp_info.tcpi_notsent_bytes); +#else + osmo_stat_item_set(osmo_stat_item_group_get_item(stats_tcp_entry->stats_tcp, STATS_TCP_NOTSENT_BYTES), -1); +#endif + +#if HAVE_TCP_INFO_TCPI_RWND_LIMITED == 1 + osmo_stat_item_set(osmo_stat_item_group_get_item(stats_tcp_entry->stats_tcp, STATS_TCP_RWND_LIMITED), + tcp_info.tcpi_rwnd_limited); +#else + osmo_stat_item_set(osmo_stat_item_group_get_item(stats_tcp_entry->stats_tcp, STATS_TCP_RWND_LIMITED), -1); +#endif + +#if STATS_TCP_SNDBUF_LIMITED == 1 + osmo_stat_item_set(osmo_stat_item_group_get_item(stats_tcp_entry->stats_tcp, STATS_TCP_REORD_SEEN), + tcp_info.tcpi_sndbuf_limited); +#else + osmo_stat_item_set(osmo_stat_item_group_get_item(stats_tcp_entry->stats_tcp, STATS_TCP_REORD_SEEN), -1); +#endif + +#if HAVE_TCP_INFO_TCPI_REORD_SEEN == 1 + osmo_stat_item_set(osmo_stat_item_group_get_item(stats_tcp_entry->stats_tcp, STATS_TCP_REORD_SEEN), + tcp_info.tcpi_reord_seen); +#else + osmo_stat_item_set(osmo_stat_item_group_get_item(stats_tcp_entry->stats_tcp, STATS_TCP_REORD_SEEN), -1); +#endif + +} + +static bool is_tcp(const struct osmo_fd *fd) +{ + int rc; + struct stat fd_stat; + int so_protocol = 0; + socklen_t so_protocol_len = sizeof(so_protocol); + + /* Is this a socket? */ + rc = fstat(fd->fd, &fd_stat); + if (rc < 0) + return false; + if (!S_ISSOCK(fd_stat.st_mode)) + return false; + + /* Is it a TCP socket? */ + rc = getsockopt(fd->fd, SOL_SOCKET, SO_PROTOCOL, &so_protocol, &so_protocol_len); + if (rc < 0) + return false; + if (so_protocol == IPPROTO_TCP) + return true; + + return false; +} + +/*! Register an osmo_fd for TCP stats monitoring. + * \param[in] fd osmocom file descriptor to be registered. + * \param[in] human readbla name that is used as prefix for the related stats item. + * \returns 0 on success; negative in case of error. */ +int osmo_stats_tcp_osmo_fd_register(const struct osmo_fd *fd, const char *name) +{ + struct stats_tcp_entry *stats_tcp_entry; + + /* Only TCP sockets can be registered for monitoring, anything else will fall through. */ + if (!is_tcp(fd)) + return -EINVAL; + + /* When the osmo_fd is registered and unregistered properly there shouldn't be any leftovers from already closed + * osmo_fds in the stats_tcp list. But lets proactively make sure that any leftovers are cleaned up. */ + osmo_stats_tcp_osmo_fd_unregister(fd); + + /* Make a new list object, attach the osmo_fd... */ + stats_tcp_entry = talloc_zero(OTC_GLOBAL, struct stats_tcp_entry); + OSMO_ASSERT(stats_tcp_entry); + stats_tcp_entry->fd = fd; + stats_tcp_entry->name = talloc_strdup(stats_tcp_entry, name); + + pthread_mutex_lock(&stats_tcp_lock); + llist_add_tail(&stats_tcp_entry->entry, &stats_tcp); + pthread_mutex_unlock(&stats_tcp_lock); + + return 0; +} + +static void next_stats_tcp_entry(void) +{ + struct stats_tcp_entry *last; + + if (llist_empty(&stats_tcp)) { + stats_tcp_entry_cur = NULL; + return; + } + + last = (struct stats_tcp_entry *)llist_last_entry(&stats_tcp, struct stats_tcp_entry, entry); + + if (!stats_tcp_entry_cur || stats_tcp_entry_cur == last) + stats_tcp_entry_cur = + (struct stats_tcp_entry *)llist_first_entry(&stats_tcp, struct stats_tcp_entry, entry); + else + stats_tcp_entry_cur = + (struct stats_tcp_entry *)llist_entry(stats_tcp_entry_cur->entry.next, struct stats_tcp_entry, + entry); +} + +/*! Register an osmo_fd for TCP stats monitoring. + * \param[in] fd osmocom file descriptor to be unregistered. + * \returns 0 on success; negative in case of error. */ +int osmo_stats_tcp_osmo_fd_unregister(const struct osmo_fd *fd) +{ + struct stats_tcp_entry *stats_tcp_entry; + int rc = -EINVAL; + + pthread_mutex_lock(&stats_tcp_lock); + llist_for_each_entry(stats_tcp_entry, &stats_tcp, entry) { + if (fd->fd == stats_tcp_entry->fd->fd) { + /* In case we want to remove exactly that item which is also + * selected as the current item, we must designate either a + * different item or invalidate the current item. + */ + if (stats_tcp_entry == stats_tcp_entry_cur) { + if (llist_count(&stats_tcp) > 2) + next_stats_tcp_entry(); + else + stats_tcp_entry_cur = NULL; + } + + /* Date item from list */ + llist_del(&stats_tcp_entry->entry); + osmo_stat_item_group_free(stats_tcp_entry->stats_tcp); + talloc_free(stats_tcp_entry); + rc = 0; + break; + } + } + pthread_mutex_unlock(&stats_tcp_lock); + + return rc; +} + +static void stats_tcp_poll_timer_cb(void *data) +{ + int i; + int batch_size; + int llist_size; + + pthread_mutex_lock(&stats_tcp_lock); + + /* Make sure we do not run over the same sockets multiple times if the + * configured llist_size is larger then the actual list */ + batch_size = osmo_tcp_stats_config->batch_size; + llist_size = llist_count(&stats_tcp); + if (llist_size < batch_size) + batch_size = llist_size; + + /* Process a batch of sockets */ + for (i = 0; i < batch_size; i++) { + next_stats_tcp_entry(); + if (stats_tcp_entry_cur) + fill_stats(stats_tcp_entry_cur); + } + + pthread_mutex_unlock(&stats_tcp_lock); + + if (osmo_tcp_stats_config->interval > 0) + osmo_timer_schedule(&stats_tcp_poll_timer, osmo_tcp_stats_config->interval, 0); +} + +/*! Set the polling interval (common for all sockets) + * \param[in] interval Poll interval in seconds + * \returns 0 on success; negative on error */ +int osmo_stats_tcp_set_interval(int interval) +{ + osmo_tcp_stats_config->interval = interval; + if (osmo_tcp_stats_config->interval > 0) + osmo_timer_schedule(&stats_tcp_poll_timer, osmo_tcp_stats_config->interval, 0); + return 0; +} + +static __attribute__((constructor)) +void on_dso_load_stats_tcp(void) +{ + stats_tcp_entry_cur = NULL; + pthread_mutex_init(&stats_tcp_lock, NULL); + + osmo_tcp_stats_config->interval = TCP_STATS_DEFAULT_INTERVAL; + osmo_tcp_stats_config->batch_size = TCP_STATS_DEFAULT_BATCH_SIZE; + + osmo_timer_setup(&stats_tcp_poll_timer, stats_tcp_poll_timer_cb, NULL); +} + +#endif /* !EMBEDDED */ + +/* @} */ diff --git a/src/strrb.c b/src/core/strrb.c index 461fdecc..c5a5ed6e 100644 --- a/src/strrb.c +++ b/src/core/strrb.c @@ -31,10 +31,6 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ /*! \addtogroup utils @@ -56,12 +52,12 @@ * This function creates and initializes a ringbuffer. * Note that the ringbuffer stores at most rb_size - 1 messages. */ -struct osmo_strrb *osmo_strrb_create(TALLOC_CTX * ctx, size_t rb_size) +struct osmo_strrb *osmo_strrb_create(void *talloc_ctx, size_t rb_size) { struct osmo_strrb *rb = NULL; unsigned int i; - rb = talloc_zero(ctx, struct osmo_strrb); + rb = talloc_zero(talloc_ctx, struct osmo_strrb); if (!rb) goto alloc_error; diff --git a/src/tdef.c b/src/core/tdef.c index 9890f95a..f0c0f2e8 100644 --- a/src/tdef.c +++ b/src/core/tdef.c @@ -52,18 +52,18 @@ * By keeping separate osmo_tdef arrays, several groups of timers can be kept * separately. The VTY tests in tests/tdef/ showcase different schemes: * - * - \ref tests/vty/tdef_vty_test_config_root.c: + * - \ref tests/tdef/tdef_vty_config_root_test.c: * Keep several timer definitions in separately named groups: showcase the * osmo_tdef_vty_groups*() API. Each timer group exists exactly once. * - * - \ref tests/vty/tdef_vty_test_config_subnode.c: + * - \ref tests/tdef/tdef_vty_config_subnode_test.c: * Keep a single list of timers without separate grouping. * Put this list on a specific subnode below the CONFIG_NODE. * There could be several separate subnodes with timers like this, i.e. * continuing from this example, sets of timers could be separated by placing * timers in specific config subnodes instead of using the global group name. * - * - \ref tests/vty/tdef_vty_test_dynamic.c: + * - \ref tests/tdef/tdef_vty_dynamic_test.c: * Dynamically allocate timer definitions per each new created object. * Thus there can be an arbitrary number of independent timer definitions, one * per allocated object. @@ -200,8 +200,10 @@ void osmo_tdefs_reset(struct osmo_tdef *tdefs) * \param[in] tdefs Array of timer definitions, last entry must be fully zero initialized. * \param[in] T Timer number to get the value for. * \param[in] as_unit Return timeout value in this unit. - * \param[in] val_if_not_present Fallback value to return if no timeout is defined. + * \param[in] val_if_not_present Fallback value to return if no timeout is defined; if this is a negative number, a + * missing T timer definition aborts the program via OSMO_ASSERT(). * \return Timeout value in the unit given by as_unit, rounded up if necessary, or val_if_not_present. + * If val_if_not_present is negative and no T timer is defined, trigger OSMO_ASSERT() and do not return. */ unsigned long osmo_tdef_get(const struct osmo_tdef *tdefs, int T, enum osmo_tdef_unit as_unit, long val_if_not_present) { @@ -335,26 +337,37 @@ int _osmo_tdef_fsm_inst_state_chg(struct osmo_fsm_inst *fi, uint32_t state, const char *file, int line) { const struct osmo_tdef_state_timeout *t = osmo_tdef_get_state_timeout(state, timeouts_array); - unsigned long val = 0; + unsigned long val_ms = 0; /* No timeout defined for this state? */ if (!t) return _osmo_fsm_inst_state_chg(fi, state, 0, 0, file, line); - if (t->T) - val = osmo_tdef_get(tdefs, t->T, OSMO_TDEF_S, default_timeout); + if (t->T) { + const struct osmo_tdef *tdef = osmo_tdef_get_entry((struct osmo_tdef *)tdefs, t->T); + if (tdef == NULL) { + /* emulate the old behavior: treat default_timeout as OSMO_TDEF_S */ + OSMO_ASSERT(default_timeout >= 0); + val_ms = default_timeout * 1000; + } else { + val_ms = osmo_tdef_round(tdef->val, tdef->unit, OSMO_TDEF_MS); + /* emulate the old behavior: treat OSMO_TDEF_CUSTOM as OSMO_TDEF_S */ + if (tdef->unit == OSMO_TDEF_CUSTOM) + val_ms *= 1000; + } + } if (t->keep_timer) { if (t->T) - return _osmo_fsm_inst_state_chg_keep_or_start_timer(fi, state, val, t->T, file, line); + return _osmo_fsm_inst_state_chg_keep_or_start_timer_ms(fi, state, val_ms, t->T, file, line); else return _osmo_fsm_inst_state_chg_keep_timer(fi, state, file, line); } - /* val is always initialized here, because if t->keep_timer is false, t->T must be != 0. + /* val_ms is always initialized here, because if t->keep_timer is false, t->T must be != 0. * Otherwise osmo_tdef_get_state_timeout() would have returned NULL. */ OSMO_ASSERT(t->T); - return _osmo_fsm_inst_state_chg(fi, state, val, t->T, file, line); + return _osmo_fsm_inst_state_chg_ms(fi, state, val_ms, t->T, file, line); } const struct value_string osmo_tdef_unit_names[] = { diff --git a/src/thread.c b/src/core/thread.c index 956fee76..d9a98422 100644 --- a/src/thread.c +++ b/src/core/thread.c @@ -14,10 +14,6 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ /*! \addtogroup thread diff --git a/src/core/time_cc.c b/src/core/time_cc.c new file mode 100644 index 00000000..0e6879e5 --- /dev/null +++ b/src/core/time_cc.c @@ -0,0 +1,228 @@ +/*! \file foo.c + * Report the cumulative counter of time for which a flag is true as rate counter. + */ +/* Copyright (C) 2021 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * + * All Rights Reserved + * + * Author: Neels Hofmeyr <nhofmeyr@sysmocom.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +/*! \addtogroup time_cc + * + * Report the cumulative counter of time for which a flag is true as rate counter. + * + * Useful for reporting cumulative time counters as defined in 3GPP TS 52.402, for example allAvailableSDCCHAllocated, + * allAvailableTCHAllocated, availablePDCHAllocatedTime. + * + * For a usage example, see the description of struct osmo_time_cc. + * + * @{ + * \file time_cc.c + */ +#include "config.h" +#ifdef HAVE_CLOCK_GETTIME + +#include <limits.h> +#include <time.h> + +#include <osmocom/core/tdef.h> +#include <osmocom/core/rate_ctr.h> +#include <osmocom/core/time_cc.h> + +#define GRAN_USEC(TIME_CC) ((TIME_CC)->cfg.gran_usec ? : 1000000) +#define ROUND_THRESHOLD_USEC(TIME_CC) ((TIME_CC)->cfg.round_threshold_usec ? \ + OSMO_MIN((TIME_CC)->cfg.round_threshold_usec, GRAN_USEC(TIME_CC)) \ + : (GRAN_USEC(TIME_CC) / 2)) + +static uint64_t time_now_usec(void) +{ + struct timespec tp; + if (osmo_clock_gettime(CLOCK_MONOTONIC, &tp)) + return 0; + return (uint64_t)tp.tv_sec * 1000000 + tp.tv_nsec / 1000; +} + +static void osmo_time_cc_forget_sum(struct osmo_time_cc *tc, uint64_t now); + +static void osmo_time_cc_update_from_tdef(struct osmo_time_cc *tc, uint64_t now) +{ + bool do_forget_sum = false; + if (!tc->cfg.T_defs) + return; + if (tc->cfg.T_gran) { + uint64_t was = GRAN_USEC(tc); + tc->cfg.gran_usec = osmo_tdef_get(tc->cfg.T_defs, tc->cfg.T_gran, OSMO_TDEF_US, -1); + if (was != GRAN_USEC(tc)) + do_forget_sum = true; + } + if (tc->cfg.T_round_threshold) + tc->cfg.round_threshold_usec = osmo_tdef_get(tc->cfg.T_defs, tc->cfg.T_round_threshold, + OSMO_TDEF_US, -1); + if (tc->cfg.T_forget_sum) { + uint64_t was = tc->cfg.forget_sum_usec; + tc->cfg.forget_sum_usec = osmo_tdef_get(tc->cfg.T_defs, tc->cfg.T_forget_sum, OSMO_TDEF_US, -1); + if (tc->cfg.forget_sum_usec && was != tc->cfg.forget_sum_usec) + do_forget_sum = true; + } + + if (do_forget_sum && tc->sum) + osmo_time_cc_forget_sum(tc, now); +} + +static void osmo_time_cc_schedule_timer(struct osmo_time_cc *tc, uint64_t now); + +/*! Clear out osmo_timer and internal counting state of struct osmo_time_cc. The .cfg remains unaffected. After calling, + * the osmo_time_cc instance can be used again to accumulate state as if it had just been initialized. */ +void osmo_time_cc_cleanup(struct osmo_time_cc *tc) +{ + osmo_timer_del(&tc->timer); + *tc = (struct osmo_time_cc){ + .cfg = tc->cfg, + }; +} + +static void osmo_time_cc_start(struct osmo_time_cc *tc, uint64_t now) +{ + osmo_time_cc_cleanup(tc); + tc->start_time = now; + tc->last_counted_time = now; + osmo_time_cc_update_from_tdef(tc, now); + osmo_time_cc_schedule_timer(tc, now); +} + +static void osmo_time_cc_count_time(struct osmo_time_cc *tc, uint64_t now) +{ + uint64_t time_delta = now - tc->last_counted_time; + tc->last_counted_time = now; + if (!tc->flag_state) + return; + /* Flag is currently true, cumulate the elapsed time */ + tc->total_sum += time_delta; + tc->sum += time_delta; +} + +static void osmo_time_cc_report(struct osmo_time_cc *tc, uint64_t now) +{ + uint64_t delta; + uint64_t n; + /* We report a sum "rounded up", ahead of time. If the granularity period has not yet elapsed after the last + * reporting, do not report again yet. */ + if (tc->reported_sum > tc->sum) + return; + delta = tc->sum - tc->reported_sum; + /* elapsed full periods */ + n = delta / GRAN_USEC(tc); + /* If the delta has passed round_threshold (normally half of gran_usec), increment. */ + delta -= n * GRAN_USEC(tc); + if (delta >= ROUND_THRESHOLD_USEC(tc)) + n++; + if (!n) + return; + + /* integer sanity, since rate_ctr_add() takes an int argument. */ + if (n > INT_MAX) + n = INT_MAX; + if (tc->cfg.rate_ctr) + rate_ctr_add(tc->cfg.rate_ctr, n); + /* Store the increments of gran_usec that were counted. */ + tc->reported_sum += n * GRAN_USEC(tc); +} + +static void osmo_time_cc_forget_sum(struct osmo_time_cc *tc, uint64_t now) +{ + tc->reported_sum = 0; + tc->sum = 0; + + if (tc->last_counted_time < now) + tc->last_counted_time = now; +} + +/*! Initialize struct osmo_time_cc. Call this once before use, and before setting up the .cfg items. */ +void osmo_time_cc_init(struct osmo_time_cc *tc) +{ + *tc = (struct osmo_time_cc){0}; +} + +/*! Report state to be recorded by osmo_time_cc instance. Setting an unchanged state repeatedly has no effect. */ +void osmo_time_cc_set_flag(struct osmo_time_cc *tc, bool flag) +{ + uint64_t now = time_now_usec(); + if (!tc->start_time) + osmo_time_cc_start(tc, now); + /* No flag change == no effect */ + if (flag == tc->flag_state) + return; + /* Sum up elapsed time, report increments for that. */ + osmo_time_cc_count_time(tc, now); + osmo_time_cc_report(tc, now); + tc->flag_state = flag; + osmo_time_cc_schedule_timer(tc, now); +} + +static void osmo_time_cc_timer_cb(void *data) +{ + struct osmo_time_cc *tc = data; + uint64_t now = time_now_usec(); + + osmo_time_cc_update_from_tdef(tc, now); + + if (tc->flag_state) { + osmo_time_cc_count_time(tc, now); + osmo_time_cc_report(tc, now); + } else if (tc->cfg.forget_sum_usec && tc->sum + && (now >= tc->last_counted_time + tc->cfg.forget_sum_usec)) { + osmo_time_cc_forget_sum(tc, now); + } + osmo_time_cc_schedule_timer(tc, now); +} + +/*! Figure out the next time we should do anything, if the flag state remains unchanged. */ +static void osmo_time_cc_schedule_timer(struct osmo_time_cc *tc, uint64_t now) +{ + uint64_t next_event = UINT64_MAX; + + osmo_time_cc_update_from_tdef(tc, now); + + /* If it is required, when will the next forget_sum happen? */ + if (tc->cfg.forget_sum_usec && !tc->flag_state && tc->sum > 0) { + uint64_t next_forget_time = tc->last_counted_time + tc->cfg.forget_sum_usec; + next_event = OSMO_MIN(next_event, next_forget_time); + } + /* Next rate_ctr increment? */ + if (tc->flag_state) { + uint64_t next_inc = now + (tc->reported_sum - tc->sum) + ROUND_THRESHOLD_USEC(tc); + next_event = OSMO_MIN(next_event, next_inc); + } + + /* No event coming up? */ + if (next_event == UINT64_MAX) + return; + + if (next_event <= now) + next_event = 0; + else + next_event -= now; + + osmo_timer_setup(&tc->timer, osmo_time_cc_timer_cb, tc); + osmo_timer_del(&tc->timer); + osmo_timer_schedule(&tc->timer, next_event / 1000000, next_event % 1000000); +} + +#endif /* HAVE_CLOCK_GETTIME */ + +/*! @} */ diff --git a/src/timer.c b/src/core/timer.c index ed5a6103..067bd875 100644 --- a/src/timer.c +++ b/src/core/timer.c @@ -19,10 +19,6 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ @@ -195,7 +191,16 @@ int osmo_timers_nearest_ms(void) return -1; nearest_ms = nearest_p->tv_sec * 1000; +#ifndef EMBEDDED + /* By adding 999 milliseconds, we ensure rounding up to the nearest + * whole millisecond. This approach prevents the return of 0 when the + * timer is still active, and it guarantees that the calling process + * does not wait for a duration shorter than the time remaining on the + * timer. */ + nearest_ms += (nearest_p->tv_usec + 999) / 1000; +#else nearest_ms += nearest_p->tv_usec / 1000; +#endif return nearest_ms; } diff --git a/src/timer_clockgettime.c b/src/core/timer_clockgettime.c index 7b17fd11..6112b8a5 100644 --- a/src/timer_clockgettime.c +++ b/src/core/timer_clockgettime.c @@ -14,10 +14,6 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ /*! \addtogroup timer diff --git a/src/timer_gettimeofday.c b/src/core/timer_gettimeofday.c index 34949465..e0212b5a 100644 --- a/src/timer_gettimeofday.c +++ b/src/core/timer_gettimeofday.c @@ -15,10 +15,6 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ /*! \addtogroup timer diff --git a/src/core/tun.c b/src/core/tun.c new file mode 100644 index 00000000..86128190 --- /dev/null +++ b/src/core/tun.c @@ -0,0 +1,577 @@ + +/* TUN interface functions. + * (C) 2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * Author: Pau Espin Pedrol <pespin@sysmocom.de> + * + * All Rights Reserved + * + * SPDX-License-Identifier: GPL-2.0+ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include "config.h" + +/*! \addtogroup tun + * @{ + * tun network device (interface) convenience functions + * + * \file tundev.c + * + * Example lifecycle use of the API: + * + * struct osmo_sockaddr_str osa_str = {}; + * struct osmo_sockaddr osa = {}; + * + * // Allocate object: + * struct osmo_tundev *tundev = osmo_tundev_alloc(parent_talloc_ctx, name); + * OSMO_ASSERT(tundev); + * + * // Configure object (before opening): + * osmo_tundev_set_data_ind_cb(tun, tun_data_ind_cb); + * rc = osmo_tundev_set_dev_name(tun, "mytunnel0"); + * rc = osmo_tundev_set_netns_name(tun, "some_netns_name_or_null"); + * + * // Open the tundev object: + * rc = osmo_tundev_open(tundev); + * // The tunnel device is now created and an associatd netdev object + * // is available to operate the device: + * struct osmo_netdev *netdev = osmo_tundev_get_netdev(tundev); + * OSMO_ASSERT(netdev); + * + * // Add a local IPv4 address: + * rc = osmo_sockaddr_str_from_str2(&osa_str, "192.168.200.1"); + * rc = osmo_sockaddr_str_to_sockaddr(&osa_str, &osa.u.sas); + * rc = osmo_netdev_add_addr(netdev, &osa, 24); + * + * // Bring network interface up: + * rc = osmo_netdev_ifupdown(netdev, true); + * + * // Add default route (0.0.0.0/0): + * rc = osmo_sockaddr_str_from_str2(&osa_str, "0.0.0.0"); + * rc = osmo_sockaddr_str_to_sockaddr(&osa_str, &osa.u.sas); + * rc = osmo_netdev_add_route(netdev, &osa, 0, NULL); + * + * // Close the tunnel (asssociated netdev object becomes unavailable) + * rc = osmo_tundev_close(tundev); + * // Free the object: + * osmo_tundev_free(tundev); + */ + +#if (!EMBEDDED) + +#include <stdio.h> +#include <stdlib.h> +#include <stdio.h> +#include <fcntl.h> +#include <unistd.h> +#include <errno.h> +#include <ifaddrs.h> +#include <sys/types.h> +#include <sys/time.h> +#include <sys/ioctl.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <net/if.h> + +#if defined(__linux__) +#include <linux/if_tun.h> +#else +#error "Unknown platform!" +#endif + +#include <osmocom/core/utils.h> +#include <osmocom/core/select.h> +#include <osmocom/core/linuxlist.h> +#include <osmocom/core/logging.h> +#include <osmocom/core/write_queue.h> +#include <osmocom/core/socket.h> +#include <osmocom/core/netns.h> +#include <osmocom/core/netdev.h> +#include <osmocom/core/tun.h> + +#define TUN_DEV_PATH "/dev/net/tun" +#define TUN_PACKET_MAX 8196 + +#define LOGTUN(tun, lvl, fmt, args ...) \ + LOGP(DLGLOBAL, lvl, "TUN(%s,if=%s/%u,ns=%s): " fmt, \ + (tun)->name, (tun)->dev_name ? : "", \ + (tun)->ifindex, (tun)->netns_name ? : "", ## args) + +struct osmo_tundev { + /* Name used to identify the osmo_tundev */ + char *name; + + /* netdev managing the tun interface: */ + struct osmo_netdev *netdev; + + /* ifindiex of the currently opened tunnel interface */ + unsigned int ifindex; + + /* Network interface name to use when setting up the tun device. + * NULL = let the system pick one. */ + char *dev_name; + /* Whether dev_name is set by user or dynamically allocated by system */ + bool dev_name_dynamic; + + /* Write queue used since tun fd is set non-blocking */ + struct osmo_wqueue wqueue; + + /* netns name where the tun interface is created (NULL = default netns) */ + char *netns_name; + + /* API user private data */ + void *priv_data; + + /* Called by tundev each time a new packet is received on the tun interface. Can be NULL. */ + osmo_tundev_data_ind_cb_t data_ind_cb; + + /* Whether the tundev is in opened state (managing the tun interface) */ + bool opened; +}; + +/* A new pkt arrived from the tun device, dispatch it to the API user */ +static int tundev_decaps(struct osmo_tundev *tundev) +{ + struct msgb *msg; + int rc; + + msg = msgb_alloc(TUN_PACKET_MAX, "tundev_rx"); + + if ((rc = read(tundev->wqueue.bfd.fd, msgb_data(msg), TUN_PACKET_MAX)) <= 0) { + LOGTUN(tundev, LOGL_ERROR, "read() failed: %s (%d)\n", strerror(errno), errno); + msgb_free(msg); + return -1; + } + msgb_put(msg, rc); + + if (tundev->data_ind_cb) + return tundev->data_ind_cb(tundev, msg); + + msgb_free(msg); + return 0; +} + +/* callback for tun device osmocom select loop integration */ +static int tundev_read_cb(struct osmo_fd *fd) +{ + struct osmo_tundev *tundev = fd->data; + return tundev_decaps(tundev); +} + +/* callback for tun device osmocom select loop integration */ +static int tundev_write_cb(struct osmo_fd *fd, struct msgb *msg) +{ + struct osmo_tundev *tundev = fd->data; + size_t pkt_len = msgb_length(msg); + + int rc; + rc = write(tundev->wqueue.bfd.fd, msgb_data(msg), pkt_len); + if (rc < 0) + LOGTUN(tundev, LOGL_ERROR, "write() failed: %s (%d)\n", strerror(errno), errno); + else if (rc < pkt_len) + LOGTUN(tundev, LOGL_ERROR, "short write() %d < %zu\n", rc, pkt_len); + return rc; +} + +static int tundev_ifupdown_ind_cb(struct osmo_netdev *netdev, bool ifupdown) +{ + struct osmo_tundev *tundev = osmo_netdev_get_priv_data(netdev); + LOGTUN(tundev, LOGL_NOTICE, "Physical link state changed: %s\n", + ifupdown ? "UP" : "DOWN"); + + /* free any backlog, both on IFUP and IFDOWN. Keep the LMI, as it makes + * sense to get one out of the door ASAP. */ + osmo_wqueue_clear(&tundev->wqueue); + return 0; +} + +static int tundev_dev_name_chg_cb(struct osmo_netdev *netdev, const char *new_dev_name) +{ + struct osmo_tundev *tundev = osmo_netdev_get_priv_data(netdev); + LOGTUN(tundev, LOGL_NOTICE, "netdev changed name: %s -> %s\n", + osmo_netdev_get_dev_name(netdev), new_dev_name); + + if (tundev->dev_name_dynamic) { + osmo_talloc_replace_string(tundev, &tundev->dev_name, new_dev_name); + } else { + /* TODO: in here we probably want to force the iface name back + * to tundev->dev_name one we have a osmo_netdev_set_ifname() API */ + osmo_talloc_replace_string(tundev, &tundev->dev_name, new_dev_name); + } + + return 0; +} + +static int tundev_mtu_chg_cb(struct osmo_netdev *netdev, uint32_t new_mtu) +{ + struct osmo_tundev *tundev = osmo_netdev_get_priv_data(netdev); + LOGTUN(tundev, LOGL_NOTICE, "netdev changed MTU: %u\n", new_mtu); + + return 0; +} + +/*! Allocate a new tundev object. + * \param[in] ctx talloc context to use as a parent when allocating the tundev object + * \param[in] name A name providen to identify the tundev object + * \returns newly allocated tundev object on success; NULL on error + */ +struct osmo_tundev *osmo_tundev_alloc(void *ctx, const char *name) +{ + struct osmo_tundev *tundev; + + tundev = talloc_zero(ctx, struct osmo_tundev); + if (!tundev) + return NULL; + + tundev->netdev = osmo_netdev_alloc(tundev, name); + if (!tundev->netdev) { + talloc_free(tundev); + return NULL; + } + osmo_netdev_set_priv_data(tundev->netdev, tundev); + osmo_netdev_set_ifupdown_ind_cb(tundev->netdev, tundev_ifupdown_ind_cb); + osmo_netdev_set_dev_name_chg_cb(tundev->netdev, tundev_dev_name_chg_cb); + osmo_netdev_set_mtu_chg_cb(tundev->netdev, tundev_mtu_chg_cb); + + tundev->name = talloc_strdup(tundev, name); + osmo_wqueue_init(&tundev->wqueue, 1000); + osmo_fd_setup(&tundev->wqueue.bfd, -1, OSMO_FD_READ, osmo_wqueue_bfd_cb, tundev, 0); + tundev->wqueue.read_cb = tundev_read_cb; + tundev->wqueue.write_cb = tundev_write_cb; + + return tundev; +} + +/*! Free an allocated tundev object. + * \param[in] tundev The tundev object to free + */ +void osmo_tundev_free(struct osmo_tundev *tundev) +{ + if (!tundev) + return; + osmo_tundev_close(tundev); + osmo_netdev_free(tundev->netdev); + talloc_free(tundev); +} + +/*! Open and configure fd of the tunnel device. + * \param[in] tundev The tundev object whose tunnel interface to open + * \param[in] flags internal linux flags to pass when creating the device (not used yet) + * \returns 0 on success; negative on error + */ +static int tundev_open_fd(struct osmo_tundev *tundev, int flags) +{ + struct ifreq ifr; + int fd, rc; + + fd = open(TUN_DEV_PATH, O_RDWR); + if (fd < 0) { + LOGTUN(tundev, LOGL_ERROR, "Cannot open " TUN_DEV_PATH ": %s\n", strerror(errno)); + return fd; + } + + memset(&ifr, 0, sizeof(ifr)); + ifr.ifr_flags = IFF_TUN | IFF_NO_PI | flags; + if (tundev->dev_name) { + /* if a TUN interface name was specified, put it in the structure; otherwise, + the kernel will try to allocate the "next" device of the specified type */ + osmo_strlcpy(ifr.ifr_name, tundev->dev_name, IFNAMSIZ); + } + + /* try to create the device */ + rc = ioctl(fd, TUNSETIFF, (void *) &ifr); + if (rc < 0) + goto close_ret; + + /* Read name back from device */ + if (!tundev->dev_name) { + ifr.ifr_name[IFNAMSIZ - 1] = '\0'; + tundev->dev_name = talloc_strdup(tundev, ifr.ifr_name); + tundev->dev_name_dynamic = true; + } + + /* Store interface index: + * (Note: there's a potential race condition here between creating the + * iface with a given name above and attempting to retrieve its ifindex based + * on that name. Someone (ie udev) could have the iface renamed in + * between here. It's a pity that TUNSETIFF doesn't copy back to us the + * newly allocated ifinidex as it does with ifname) + */ + tundev->ifindex = if_nametoindex(tundev->dev_name); + if (tundev->ifindex == 0) { + LOGTUN(tundev, LOGL_ERROR, "Unable to find ifinidex for dev %s\n", + tundev->dev_name); + rc = -ENODEV; + goto close_ret; + } + + LOGTUN(tundev, LOGL_INFO, "TUN device created\n"); + + /* set non-blocking: */ + rc = fcntl(fd, F_GETFL); + if (rc < 0) { + LOGTUN(tundev, LOGL_ERROR, "fcntl(F_GETFL) failed: %s (%d)\n", + strerror(errno), errno); + goto close_ret; + } + rc = fcntl(fd, F_SETFL, rc | O_NONBLOCK); + if (rc < 0) { + LOGTUN(tundev, LOGL_ERROR, "fcntl(F_SETFL, O_NONBLOCK) failed: %s (%d)\n", + strerror(errno), errno); + goto close_ret; + } + return fd; + +close_ret: + close(fd); + return rc; +} + +/*! Open the tunnel device owned by the tundev object. + * \param[in] tundev The tundev object to open + * \returns 0 on success; negative on error + */ +int osmo_tundev_open(struct osmo_tundev *tundev) +{ + struct osmo_netns_switch_state switch_state; + int rc; + int netns_fd = -1; + + if (tundev->opened) + return -EALREADY; + + /* temporarily switch to specified namespace to create tun device */ + if (tundev->netns_name) { + LOGTUN(tundev, LOGL_INFO, "Open tun: Switch to netns '%s'\n", + tundev->netns_name); + netns_fd = osmo_netns_open_fd(tundev->netns_name); + if (netns_fd < 0) { + LOGP(DLGLOBAL, LOGL_ERROR, "Open tun: Cannot switch to netns '%s': %s (%d)\n", + tundev->netns_name, strerror(errno), errno); + return netns_fd; + } + rc = osmo_netns_switch_enter(netns_fd, &switch_state); + if (rc < 0) { + LOGTUN(tundev, LOGL_ERROR, "Open tun: Cannot switch to netns '%s': %s (%d)\n", + tundev->netns_name, strerror(errno), errno); + goto err_close_netns_fd; + } + } + + tundev->wqueue.bfd.fd = tundev_open_fd(tundev, 0); + if (tundev->wqueue.bfd.fd < 0) { + LOGTUN(tundev, LOGL_ERROR, "Cannot open TUN device: %s\n", strerror(errno)); + rc = -ENODEV; + goto err_restore_ns; + } + + /* switch back to default namespace */ + if (tundev->netns_name) { + rc = osmo_netns_switch_exit(&switch_state); + if (rc < 0) { + LOGTUN(tundev, LOGL_ERROR, "Open tun: Cannot switch back from netns '%s': %s\n", + tundev->netns_name, strerror(errno)); + goto err_close_tun; + } + LOGTUN(tundev, LOGL_INFO, "Open tun: Back from netns '%s'\n", + tundev->netns_name); + } + + rc = osmo_netdev_set_netns_name(tundev->netdev, tundev->netns_name); + if (rc < 0) + goto err_close_tun; + rc = osmo_netdev_set_ifindex(tundev->netdev, tundev->ifindex); + if (rc < 0) + goto err_close_tun; + + rc = osmo_netdev_register(tundev->netdev); + if (rc < 0) + goto err_close_tun; + + rc = osmo_fd_register(&tundev->wqueue.bfd); + if (rc < 0) + goto err_unregister_netdev; + + tundev->opened = true; + return 0; + +err_unregister_netdev: + osmo_netdev_unregister(tundev->netdev); +err_close_tun: + close(tundev->wqueue.bfd.fd); + tundev->wqueue.bfd.fd = -1; +err_restore_ns: + if (tundev->netns_name) + osmo_netns_switch_exit(&switch_state); +err_close_netns_fd: + if (netns_fd >= 0) + close(netns_fd); + return rc; +} + +/*! Close the tunnel device owned by the tundev object. + * \param[in] tundev The tundev object to close + * \returns 0 on success; negative on error + */ +int osmo_tundev_close(struct osmo_tundev *tundev) +{ + if (!tundev->opened) + return -EALREADY; + + osmo_wqueue_clear(&tundev->wqueue); + if (tundev->wqueue.bfd.fd != -1) { + osmo_fd_unregister(&tundev->wqueue.bfd); + close(tundev->wqueue.bfd.fd); + tundev->wqueue.bfd.fd = -1; + } + + osmo_netdev_unregister(tundev->netdev); + if (tundev->dev_name_dynamic) { + TALLOC_FREE(tundev->dev_name); + tundev->dev_name_dynamic = false; + } + tundev->opened = false; + return 0; +} + +/*! Retrieve whether the tundev object is in "opened" state. + * \param[in] tundev The tundev object to check + * \returns true if in state "opened"; false otherwise + */ +bool osmo_tundev_is_open(struct osmo_tundev *tundev) +{ + return tundev->opened; +} + +/*! Set private user data pointer on the tundev object. + * \param[in] tundev The tundev object where the field is set + */ +void osmo_tundev_set_priv_data(struct osmo_tundev *tundev, void *priv_data) +{ + tundev->priv_data = priv_data; +} + +/*! Get private user data pointer from the tundev object. + * \param[in] tundev The tundev object from where to retrieve the field + * \returns The current value of the priv_data field. + */ +void *osmo_tundev_get_priv_data(struct osmo_tundev *tundev) +{ + return tundev->priv_data; +} + +/*! Set data_ind_cb callback, called when a new packet is received on the tun interface. + * \param[in] tundev The tundev object where the field is set + * \param[in] data_ind_cb the user provided function to be called when a new packet is received + */ +void osmo_tundev_set_data_ind_cb(struct osmo_tundev *tundev, osmo_tundev_data_ind_cb_t data_ind_cb) +{ + tundev->data_ind_cb = data_ind_cb; +} + +/*! Get name used to identify the tundev object. + * \param[in] tundev The tundev object from where to retrieve the field + * \returns The current value of the name used to identify the tundev object + */ +const char *osmo_tundev_get_name(const struct osmo_tundev *tundev) +{ + return tundev->name; +} + +/*! Set name used to name the tunnel interface created by the tundev object. + * \param[in] tundev The tundev object where the field is set + * \param[in] dev_name The tunnel interface name to use + * \returns 0 on success; negative on error + * + * This is used during osmo_tundev_open() time, and hence shouldn't be changed + * when the tundev object is in "opened" state. + * If left as NULL (default), the system will pick a suitable name during + * osmo_tundev_open(), and the field will be updated to the system-selected + * name, which can be retrieved later with osmo_tundev_get_dev_name(). + */ +int osmo_tundev_set_dev_name(struct osmo_tundev *tundev, const char *dev_name) +{ + if (tundev->opened) + return -EALREADY; + osmo_talloc_replace_string(tundev, &tundev->dev_name, dev_name); + tundev->dev_name_dynamic = false; + return 0; +} + +/*! Get name used to name the tunnel interface created by the tundev object + * \param[in] tundev The tundev object from where to retrieve the field + * \returns The current value of the configured tunnel interface name to use + */ +const char *osmo_tundev_get_dev_name(const struct osmo_tundev *tundev) +{ + return tundev->dev_name; +} + +/*! Set name of the network namespace to use when opening the tunnel interface + * \param[in] tundev The tundev object where the field is set + * \param[in] netns_name The network namespace to use during tunnel interface creation + * \returns 0 on success; negative on error + * + * This is used during osmo_tundev_open() time, and hence shouldn't be changed + * when the tundev object is in "opened" state. + * If left as NULL (default), the system will stay in the current network namespace. + */ +int osmo_tundev_set_netns_name(struct osmo_tundev *tundev, const char *netns_name) +{ + if (tundev->opened) + return -EALREADY; + osmo_talloc_replace_string(tundev, &tundev->netns_name, netns_name); + return 0; +} + +/*! Get name of network namespace used when opening the tunnel interface + * \param[in] tundev The tundev object from where to retrieve the field + * \returns The current value of the configured network namespace + */ +const char *osmo_tundev_get_netns_name(const struct osmo_tundev *tundev) +{ + return tundev->netns_name; +} + +/*! Get netdev managing the tunnel interface of tundev + * \param[in] tundev The tundev object from where to retrieve the field + * \returns The netdev objet managing the tun interface + */ +struct osmo_netdev *osmo_tundev_get_netdev(struct osmo_tundev *tundev) +{ + return tundev->netdev; +} + +/*! Submit a packet to the tunnel device managed by the tundev object + * \param[in] tundev The tundev object owning the tunnel device where to inject the packet + * \param[in] msg The msgb containg the packet to transfer + * \returns The current value of the configured network namespace + * + * This function takes the ownership of msg in all cases. + */ +int osmo_tundev_send(struct osmo_tundev *tundev, struct msgb *msg) +{ + int rc = osmo_wqueue_enqueue(&tundev->wqueue, msg); + if (rc < 0) { + LOGTUN(tundev, LOGL_ERROR, "Failed to enqueue the packet\n"); + msgb_free(msg); + return rc; + } + return rc; +} + + +#endif /* (!EMBEDDED) */ + +/*! @} */ diff --git a/src/use_count.c b/src/core/use_count.c index d07e47fc..9714403d 100644 --- a/src/use_count.c +++ b/src/core/use_count.c @@ -19,10 +19,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include <errno.h> @@ -239,6 +235,8 @@ int _osmo_use_count_get_put(struct osmo_use_count *uc, const char *use, int32_t { struct osmo_use_count_entry *e; int32_t old_use_count; + if (!uc) + return -EINVAL; if (!change) return 0; diff --git a/src/utils.c b/src/core/utils.c index 2b21dccd..1ec940d0 100644 --- a/src/utils.c +++ b/src/core/utils.c @@ -17,10 +17,6 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ @@ -151,13 +147,15 @@ uint8_t osmo_char2bcd(char c) */ int osmo_bcd2str(char *dst, size_t dst_size, const uint8_t *bcd, int start_nibble, int end_nibble, bool allow_hex) { - char *dst_end = dst + dst_size - 1; + char *dst_end; int nibble_i; int rc = 0; if (!dst || dst_size < 1 || start_nibble < 0) return -ENOMEM; + dst_end = dst + dst_size - 1; + for (nibble_i = start_nibble; nibble_i < end_nibble && dst < dst_end; nibble_i++, dst++) { uint8_t nibble = bcd[nibble_i >> 1]; if ((nibble_i & 1)) @@ -206,7 +204,7 @@ int osmo_str2bcd(uint8_t *dst, size_t dst_size, const char *digits, int start_ni if (end_nibble & 1) end_nibble++; } - if ((end_nibble / 2) > dst_size) + if ((unsigned int) (end_nibble / 2) > dst_size) return -ENOMEM; for (nibble_i = start_nibble; nibble_i < end_nibble; nibble_i++) { @@ -241,7 +239,7 @@ int osmo_str2bcd(uint8_t *dst, size_t dst_size, const char *digits, int start_ni * \param[in] max_len maximum space in output buffer * \returns number of parsed octets, or -1 on error */ -int osmo_hexparse(const char *str, uint8_t *b, int max_len) +int osmo_hexparse(const char *str, uint8_t *b, unsigned int max_len) { char c; @@ -316,7 +314,7 @@ const char *osmo_hexdump_buf(char *out_buf, size_t out_buf_size, const unsigned for (i = 0; i < len; i++) { const char *delimp = delim; int len_remain = out_buf_size - (cur - out_buf) - 1; - if (len_remain < (2 + delim_len) + if (len_remain < (int) (2 + delim_len) && !(!delim_after_last && i == (len - 1) && len_remain >= 2)) break; @@ -344,7 +342,7 @@ const char *osmo_hexdump_buf(char *out_buf, size_t out_buf_size, const unsigned */ char *osmo_ubit_dump_buf(char *buf, size_t buf_len, const uint8_t *bits, unsigned int len) { - int i; + unsigned int i; if (len > buf_len-1) len = buf_len-1; @@ -407,9 +405,6 @@ char *osmo_hexdump(const unsigned char *buf, int len) * * This function will print a sequence of bytes as hexadecimal numbers, * adding one space character between each byte (e.g. "1a ef d9") - * - * The maximum size of the output buffer is 4096 bytes, i.e. the maximum - * number of input bytes that can be printed in one call is 1365! */ char *osmo_hexdump_c(const void *ctx, const unsigned char *buf, int len) { @@ -446,9 +441,6 @@ char *osmo_hexdump_nospc(const unsigned char *buf, int len) * * This function will print a sequence of bytes as hexadecimal numbers, * without any space character between each byte (e.g. "1aefd9") - * - * The maximum size of the output buffer is 4096 bytes, i.e. the maximum - * number of input bytes that can be printed in one call is 2048! */ char *osmo_hexdump_nospc_c(const void *ctx, const unsigned char *buf, int len) { @@ -469,7 +461,7 @@ char *osmo_osmo_hexdump_nospc(const unsigned char *buf, int len) __attribute__((weak, alias("osmo_hexdump_nospc"))); #endif -#include "../config.h" +#include "config.h" #ifdef HAVE_CTYPE_H #include <ctype.h> /*! Convert an entire string to lower case @@ -764,7 +756,7 @@ int osmo_print_n(char *buf, size_t bufsize, const char *str, size_t n) * the non-legacy format also escapes those as "\xNN" sequences. * \return Number of characters that would be written if bufsize were large enough excluding '\0' (like snprintf()). */ -static size_t _osmo_escape_str_buf(char *buf, size_t bufsize, const char *str, int in_len, bool legacy_format) +static int _osmo_escape_str_buf(char *buf, size_t bufsize, const char *str, int in_len, bool legacy_format) { struct osmo_strbuf sb = { .buf = buf, .len = bufsize }; int in_pos = 0; @@ -836,6 +828,18 @@ done: * \param[in] bufsize sizeof(buf). * \param[in] str A string that may contain any characters. * \param[in] in_len Pass -1 to print until nul char, or >= 0 to force a length (also past nul chars). + * \return Number of characters that would be written if bufsize were large enough excluding '\0' (like snprintf()). + */ +int osmo_escape_str_buf3(char *buf, size_t bufsize, const char *str, int in_len) +{ + return _osmo_escape_str_buf(buf, bufsize, str, in_len, false); +} + +/*! Return the string with all non-printable characters escaped. + * \param[out] buf string buffer to write escaped characters to. + * \param[in] bufsize sizeof(buf). + * \param[in] str A string that may contain any characters. + * \param[in] in_len Pass -1 to print until nul char, or >= 0 to force a length (also past nul chars). * \return The output buffer (buf). */ char *osmo_escape_str_buf2(char *buf, size_t bufsize, const char *str, int in_len) @@ -893,6 +897,20 @@ static size_t _osmo_quote_str_buf(char *buf, size_t bufsize, const char *str, in return sb.chars_needed; } +/*! Like osmo_escape_str_buf3(), but returns double-quotes around a string, or "NULL" for a NULL string. + * This allows passing any char* value and get its C representation as string. + * The function signature is suitable for OSMO_STRBUF_APPEND_NOLEN(). + * \param[out] buf string buffer to write escaped characters to. + * \param[in] bufsize sizeof(buf). + * \param[in] str A string that may contain any characters. + * \param[in] in_len Pass -1 to print until nul char, or >= 0 to force a length. + * \return Number of characters that would be written if bufsize were large enough excluding '\0' (like snprintf()). + */ +int osmo_quote_str_buf3(char *buf, size_t bufsize, const char *str, int in_len) +{ + return _osmo_quote_str_buf(buf, bufsize, str, in_len, false); +} + /*! Like osmo_escape_str_buf2(), but returns double-quotes around a string, or "NULL" for a NULL string. * This allows passing any char* value and get its C representation as string. * The function signature is suitable for OSMO_STRBUF_APPEND_NOLEN(). @@ -1195,6 +1213,51 @@ char osmo_luhn(const char* in, int in_len) return (sum * 9) % 10 + '0'; } +/*! Remove up to N chars from the end of an osmo_strbuf. + * |--char-count---| - - chars_needed - - | + * |<---------drop----------| + */ +void osmo_strbuf_drop_tail(struct osmo_strbuf *sb, size_t n_chars) +{ + size_t drop_n; + if (sb->pos <= sb->buf) + return; + drop_n = OSMO_MIN(sb->chars_needed, n_chars); + sb->chars_needed -= drop_n; + /* chars_needed was reduced by n_chars, which may have been entirely behind the end of a full buffer, within the + * hypothetical chars_needed. Modify the buffer tail pos only if the buffer is not or longer full now. */ + if (sb->chars_needed >= OSMO_STRBUF_CHAR_COUNT(*sb)) + return; + sb->pos = sb->buf + sb->chars_needed; + *sb->pos = '\0'; +} + +/*! Let osmo_strbuf know that n_chars characters (excluding nul) were written to the end of the buffer. + * If sb is nonempty, the n_chars are assumed to have been written to sb->pos. If sb is still empty and pos == NULL, the + * n_chars are assumed to have been written to the start of the buffer. + * Advance sb->pos and sb->chars_needed by at most n_chars, or up to sb->len - 1. + * Ensure nul termination. */ +void osmo_strbuf_added_tail(struct osmo_strbuf *sb, size_t n_chars) +{ + /* On init of an osmo_strbuf, sb->pos == NULL, which is defined as semantically identical to pointing at the + * start of the buffer. A caller may just write to the buffer and call osmo_strbuf_added_tail(), in which case + * still pos == NULL. pos != NULL happens as soon as the first OSMO_STRBUF_*() API has acted on the strbuf. */ + if (!sb->pos) + sb->pos = sb->buf; + sb->chars_needed += n_chars; + /* first get remaining space, not counting trailing nul; but safeguard against empty buffer */ + size_t n_added = OSMO_STRBUF_REMAIN(*sb); + if (n_added) + n_added--; + /* do not add more than fit in sb->len, still ensuring nul termination */ + n_added = OSMO_MIN(n_added, n_chars); + if (n_added) + sb->pos += n_added; + /* when a strbuf is full, sb->pos may point after the final nul, so nul terminate only when pos is valid. */ + if (sb->pos < sb->buf + sb->len) + *sb->pos = '\0'; +} + /*! Compare start of a string. * This is an optimisation of 'strstr(str, startswith_str) == str' because it doesn't search through the entire string. * \param str (Longer) string to compare. @@ -1232,7 +1295,7 @@ int osmo_float_str_to_int(int64_t *val, const char *str, unsigned int precision) int64_t precision_factor; int64_t integer_max; int64_t decimal_max; - int i; + unsigned int i; OSMO_ASSERT(val); *val = 0; @@ -1256,7 +1319,7 @@ int osmo_float_str_to_int(int64_t *val, const char *str, unsigned int precision) if (point) return -EINVAL; point = p; - } else if (!isdigit(*p)) + } else if (!isdigit((unsigned char)*p)) return -EINVAL; } @@ -1499,4 +1562,25 @@ int osmo_str_to_int(int *result, const char *str, int base, int min_val, int max return rc; } +/*! Replace a string using talloc and release its prior content (if any). + * This is a format string capable equivalent of osmo_talloc_replace_string(). + * \param[in] ctx Talloc context to use for allocation. + * \param[out] dst Pointer to string, will be updated with ptr to new string. + * \param[in] fmt Format string that will be copied to newly allocated string. */ +void osmo_talloc_replace_string_fmt(void *ctx, char **dst, const char *fmt, ...) +{ + char *name = NULL; + + if (fmt != NULL) { + va_list ap; + + va_start(ap, fmt); + name = talloc_vasprintf(ctx, fmt, ap); + va_end(ap); + } + + talloc_free(*dst); + *dst = name; +} + /*! @} */ diff --git a/src/write_queue.c b/src/core/write_queue.c index b208b25e..8fb73a67 100644 --- a/src/write_queue.c +++ b/src/core/write_queue.c @@ -16,10 +16,6 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ #include <errno.h> @@ -106,7 +102,7 @@ void osmo_wqueue_init(struct osmo_wqueue *queue, int max_length) /*! Enqueue a new \ref msgb into a write queue (without logging full queue events) * \param[in] queue Write queue to be used * \param[in] data to-be-enqueued message buffer - * \returns 0 on success; negative on error + * \returns 0 on success; negative on error (MESSAGE NOT FREED IN CASE OF ERROR). */ int osmo_wqueue_enqueue_quiet(struct osmo_wqueue *queue, struct msgb *data) { @@ -122,7 +118,7 @@ int osmo_wqueue_enqueue_quiet(struct osmo_wqueue *queue, struct msgb *data) /*! Enqueue a new \ref msgb into a write queue * \param[in] queue Write queue to be used * \param[in] data to-be-enqueued message buffer - * \returns 0 on success; negative on error + * \returns 0 on success; negative on error (MESSAGE NOT FREED IN CASE OF ERROR). */ int osmo_wqueue_enqueue(struct osmo_wqueue *queue, struct msgb *data) { @@ -151,4 +147,24 @@ void osmo_wqueue_clear(struct osmo_wqueue *queue) queue->bfd.when &= ~OSMO_FD_WRITE; } +/*! Update write queue length & drop excess messages. + * \param[in] queue linked list header of message queue + * \param[in] len new max. wqueue length + * \returns Number of messages dropped. + * + * Messages beyond the new maximum message queue size will be dropped. + */ +size_t osmo_wqueue_set_maxlen(struct osmo_wqueue *queue, unsigned int len) +{ + size_t dropped_msgs = 0; + struct msgb *msg; + queue->max_length = len; + while (queue->current_length > queue->max_length) { + msg = msgb_dequeue_count(&queue->msg_queue, &queue->current_length); + msgb_free(msg); + dropped_msgs++; + } + return dropped_msgs; +} + /*! @} */ diff --git a/src/ctrl/Makefile.am b/src/ctrl/Makefile.am index a5f191d7..f9e34333 100644 --- a/src/ctrl/Makefile.am +++ b/src/ctrl/Makefile.am @@ -1,9 +1,10 @@ # This is _NOT_ the library release version, it's an API version. # Please read chapter "Library interface versions" of the libtool documentation # before making any modifications: https://www.gnu.org/software/libtool/manual/html_node/Versioning.html -LIBVERSION=5:0:5 +LIBVERSION=8:1:8 -AM_CFLAGS = -Wall $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)/include $(TALLOC_CFLAGS) +AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_builddir)/include -I$(top_builddir) +AM_CFLAGS = -Wall $(TALLOC_CFLAGS) if ENABLE_CTRL lib_LTLIBRARIES = libosmoctrl.la @@ -12,7 +13,7 @@ libosmoctrl_la_SOURCES = control_cmd.c control_if.c fsm_ctrl_commands.c libosmoctrl_la_LDFLAGS = $(LTLDFLAGS_OSMOCTRL) -version-info $(LIBVERSION) -no-undefined libosmoctrl_la_LIBADD = $(TALLOC_LIBS) \ - $(top_builddir)/src/libosmocore.la \ + $(top_builddir)/src/core/libosmocore.la \ $(top_builddir)/src/gsm/libosmogsm.la \ $(top_builddir)/src/vty/libosmovty.la @@ -21,5 +22,6 @@ libosmoctrl_la_SOURCES += control_vty.c endif EXTRA_DIST = libosmoctrl.map +EXTRA_libosmoctrl_la_DEPENDENCIES = libosmoctrl.map endif diff --git a/src/ctrl/control_cmd.c b/src/ctrl/control_cmd.c index e67df67e..db205510 100644 --- a/src/ctrl/control_cmd.c +++ b/src/ctrl/control_cmd.c @@ -20,10 +20,6 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ #include <ctype.h> @@ -35,6 +31,7 @@ #include <unistd.h> #include <osmocom/ctrl/control_cmd.h> +#include <osmocom/ctrl/control_if.h> #include <osmocom/core/msgb.h> #include <osmocom/core/talloc.h> @@ -214,6 +211,16 @@ int ctrl_cmd_install(enum ctrl_node_type node, struct ctrl_cmd_element *cmd) { vector cmds_vec; + /* If this assert triggers, it means the program forgot to initialize + * the CTRL interface first by calling ctrl_handle_alloc(2)() directly + * or indirectly through ctrl_interface_setup_dynip(2)() + */ + if (!ctrl_node_vec) { + LOGP(DLCTRL, LOGL_ERROR, + "ctrl_handle must be initialized prior to installing cmds.\n"); + return -ENODEV; + } + cmds_vec = vector_lookup_ensure(ctrl_node_vec, node); if (!cmds_vec) { @@ -641,7 +648,7 @@ int ctrl_cmd_def_send(struct ctrl_cmd_def *cd) cmd->type = CTRL_TYPE_ERROR; } - rc = ctrl_cmd_send(&cmd->ccon->write_queue, cmd); + rc = ctrl_cmd_send2(cmd->ccon, cmd); talloc_free(cmd); llist_del(&cd->list); diff --git a/src/ctrl/control_if.c b/src/ctrl/control_if.c index 5fda28fa..c265c3a9 100644 --- a/src/ctrl/control_if.c +++ b/src/ctrl/control_if.c @@ -20,10 +20,6 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ #include "config.h" @@ -51,6 +47,7 @@ #include <osmocom/ctrl/control_cmd.h> #include <osmocom/ctrl/control_if.h> +#include <osmocom/ctrl/control_vty.h> #include <osmocom/core/msgb.h> #include <osmocom/core/rate_ctr.h> @@ -84,26 +81,22 @@ static LLIST_HEAD(ctrl_lookup_helpers); * \returns 1 on success; 0 in case of error */ int ctrl_parse_get_num(vector vline, int i, long *num) { - char *token, *tmp; + char *token; + int64_t val; if (i >= vector_active(vline)) return 0; token = vector_slot(vline, i); - errno = 0; - if (token[0] == '\0') - return 0; - - *num = strtol(token, &tmp, 10); - if (tmp[0] != '\0' || errno != 0) + if (osmo_str_to_int64(&val, token, 10, LONG_MIN, LONG_MAX)) return 0; - + *num = (long)val; return 1; } /*! Send a CTRL command to all connections. * \param[in] ctrl global control handle - * \param[in] cmd command to send to all connections in \ctrl + * \param[in] cmd command to send to all connections in ctrl * \returns number of times the command has been sent */ int ctrl_cmd_send_to_all(struct ctrl_handle *ctrl, struct ctrl_cmd *cmd) { @@ -113,18 +106,28 @@ int ctrl_cmd_send_to_all(struct ctrl_handle *ctrl, struct ctrl_cmd *cmd) llist_for_each_entry(ccon, &ctrl->ccon_list, list_entry) { if (ccon == cmd->ccon) continue; - if (ctrl_cmd_send(&ccon->write_queue, cmd)) + if (ctrl_cmd_send2(ccon, cmd)) ret++; } return ret; } -/*! Encode a CTRL command and append it to the given write queue +/*! Encode a CTRL command and append it to the given ctrl_connection * \param[inout] queue write queue to which encoded \a cmd shall be appended * \param[in] cmd decoded command representation * \returns 0 in case of success; negative on error */ int ctrl_cmd_send(struct osmo_wqueue *queue, struct ctrl_cmd *cmd) { + struct ctrl_connection *ccon = container_of(queue, struct ctrl_connection, write_queue); + return ctrl_cmd_send2(ccon, cmd); +} + +/*! Encode a CTRL command and append it to the given ctrl_connection + * \param[inout] queue write queue to which encoded \a cmd shall be appended + * \param[in] cmd decoded command representation + * \returns 0 in case of success; negative on error */ +int ctrl_cmd_send2(struct ctrl_connection *ccon, struct ctrl_cmd *cmd) +{ int ret; struct msgb *msg; @@ -137,7 +140,7 @@ int ctrl_cmd_send(struct osmo_wqueue *queue, struct ctrl_cmd *cmd) ipa_prepend_header_ext(msg, IPAC_PROTO_EXT_CTRL); ipa_prepend_header(msg, IPAC_PROTO_OSMO); - ret = osmo_wqueue_enqueue(queue, msg); + ret = osmo_wqueue_enqueue(&ccon->write_queue, msg); if (ret != 0) { LOGP(DLCTRL, LOGL_ERROR, "Failed to enqueue the command.\n"); msgb_free(msg); @@ -471,7 +474,7 @@ int ctrl_handle_msg(struct ctrl_handle *ctrl, struct ctrl_connection *ccon, stru send_reply: /* There is a reply or error that should be reported back to the sender. */ - ctrl_cmd_send(&ccon->write_queue, cmd); + ctrl_cmd_send2(ccon, cmd); just_free: talloc_free(cmd); return 0; @@ -886,7 +889,7 @@ static int verify_counter(struct ctrl_cmd *cmd, const char *value, void *data) struct ctrl_handle *ctrl_interface_setup(void *data, uint16_t port, ctrl_cmd_lookup lookup) { - return ctrl_interface_setup_dynip(data, "127.0.0.1", port, lookup); + return ctrl_interface_setup2(data, port, lookup, 0); } static int ctrl_initialized = 0; @@ -1038,12 +1041,24 @@ struct ctrl_handle *ctrl_interface_setup_dynip(void *data, return ctrl_interface_setup_dynip2(data, bind_addr, port, lookup, 0); } +/*! Initializes CTRL interface using the configured bind addr/port. + * \param[in] data Pointer which will be made available to each set_..() get_..() verify_..() control command function + * \param[in] default_port TCP port number to bind to if not explicitly configured + * \param[in] lookup Lookup function pointer, can be NULL + * \param[in] node_count Number of CTRL nodes to allocate, 0 for default. + */ +struct ctrl_handle *ctrl_interface_setup2(void *data, uint16_t default_port, ctrl_cmd_lookup lookup, + unsigned int node_count) +{ + return ctrl_interface_setup_dynip2(data, ctrl_vty_get_bind_addr(), ctrl_vty_get_bind_port(default_port), lookup, + node_count); +} /*! Install a lookup helper function for control nodes * This function is used by e.g. library code to install lookup helpers * for additional nodes in the control interface. * \param[in] lookup The lookup helper function - * \retuns - on success; negative on error. + * \returns - on success; negative on error. */ int ctrl_lookup_register(ctrl_cmd_lookup lookup) { diff --git a/src/ctrl/control_vty.c b/src/ctrl/control_vty.c index e141a4c8..a7ebddc2 100644 --- a/src/ctrl/control_vty.c +++ b/src/ctrl/control_vty.c @@ -17,10 +17,6 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ #include <stdlib.h> @@ -30,16 +26,20 @@ static void *ctrl_vty_ctx = NULL; static const char *ctrl_vty_bind_addr = NULL; +/* Port the CTRL should bind to: -1 means not configured */ +static int ctrl_bind_port = -1; DEFUN(cfg_ctrl_bind_addr, cfg_ctrl_bind_addr_cmd, - "bind A.B.C.D", + "bind A.B.C.D [<0-65535>]", "Set bind address to listen for Control connections\n" - "Local IP address (default 127.0.0.1)\n") + "Local IP address (default 127.0.0.1)\n" + "Local TCP port number\n") { talloc_free((char*)ctrl_vty_bind_addr); ctrl_vty_bind_addr = NULL; ctrl_vty_bind_addr = talloc_strdup(ctrl_vty_ctx, argv[0]); + ctrl_bind_port = argc > 1 ? atoi(argv[1]) : -1; return CMD_SUCCESS; } @@ -50,6 +50,11 @@ const char *ctrl_vty_get_bind_addr(void) return ctrl_vty_bind_addr; } +uint16_t ctrl_vty_get_bind_port(uint16_t default_port) +{ + return ctrl_bind_port >= 0 ? ctrl_bind_port : default_port; +} + static struct cmd_node ctrl_node = { L_CTRL_NODE, "%s(config-ctrl)# ", diff --git a/src/ctrl/libosmoctrl.map b/src/ctrl/libosmoctrl.map index f995467b..3418e620 100644 --- a/src/ctrl/libosmoctrl.map +++ b/src/ctrl/libosmoctrl.map @@ -15,6 +15,7 @@ ctrl_cmd_parse; ctrl_cmd_parse2; ctrl_cmd_parse3; ctrl_cmd_send; +ctrl_cmd_send2; ctrl_cmd_send_to_all; ctrl_cmd_send_trap; ctrl_cmd_trap; @@ -22,12 +23,14 @@ ctrl_handle_alloc; /* could be removed? */ ctrl_handle_alloc2; /* could be removed? */ ctrl_handle_msg; /* only used in unit test */ ctrl_interface_setup; +ctrl_interface_setup2; ctrl_interface_setup_dynip; ctrl_interface_setup_dynip2; ctrl_lookup_register; ctrl_parse_get_num; ctrl_type_vals; ctrl_vty_get_bind_addr; +ctrl_vty_get_bind_port; ctrl_vty_init; osmo_ctrl_conn_alloc; diff --git a/src/gb/Makefile.am b/src/gb/Makefile.am index 2ccb7576..2f7ad5eb 100644 --- a/src/gb/Makefile.am +++ b/src/gb/Makefile.am @@ -1,12 +1,11 @@ # 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=12:0:0 +LIBVERSION=16:0:2 -AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_builddir)/include -AM_CFLAGS = -Wall ${GCC_FVISIBILITY_HIDDEN} -fno-strict-aliasing \ +AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_builddir)/include -I$(top_builddir) +AM_CFLAGS = -Wall -fno-strict-aliasing \ $(TALLOC_CFLAGS) \ - $(LIBMNL_CFLAGS) \ $(NULL) # FIXME: this should eventually go into a milenage/Makefile.am @@ -15,9 +14,13 @@ noinst_HEADERS = common_vty.h gb_internal.h gprs_bssgp_internal.h gprs_ns2_inter if ENABLE_GB lib_LTLIBRARIES = libosmogb.la -libosmogb_la_LDFLAGS = $(LTLDFLAGS_OSMOGB) -version-info $(LIBVERSION) +libosmogb_la_LDFLAGS = \ + $(LTLDFLAGS_OSMOGB) \ + -version-info $(LIBVERSION) \ + -no-undefined \ + $(NULL) libosmogb_la_LIBADD = $(TALLOC_LIBS) \ - $(top_builddir)/src/libosmocore.la \ + $(top_builddir)/src/core/libosmocore.la \ $(top_builddir)/src/vty/libosmovty.la \ $(top_builddir)/src/gsm/libosmogsm.la @@ -37,3 +40,4 @@ libosmogb_test_la_SOURCES= $(libosmogb_la_SOURCES) endif EXTRA_DIST = libosmogb.map +EXTRA_libosmogb_la_DEPENDENCIES = libosmogb.map diff --git a/src/gb/bssgp_bvc_fsm.c b/src/gb/bssgp_bvc_fsm.c index ce9079d5..3a36c7dc 100644 --- a/src/gb/bssgp_bvc_fsm.c +++ b/src/gb/bssgp_bvc_fsm.c @@ -375,6 +375,7 @@ static void bssgp_bvc_fsm_wait_reset_ack(struct osmo_fsm_inst *fi, uint32_t even struct bvc_fsm_priv *bfp = fi->priv; const struct tlv_parsed *tp = NULL; struct msgb *rx = NULL, *tx; + uint8_t cause; switch (event) { case BSSGP_BVCFSM_E_RX_RESET: @@ -385,6 +386,7 @@ static void bssgp_bvc_fsm_wait_reset_ack(struct osmo_fsm_inst *fi, uint32_t even /* fall-through */ case BSSGP_BVCFSM_E_RX_RESET_ACK: rx = data; + cause = bfp->last_reset_cause; tp = (const struct tlv_parsed *) msgb_bcid(rx); if (bfp->bvci == 0) update_negotiated_features(fi, tp); @@ -398,6 +400,8 @@ static void bssgp_bvc_fsm_wait_reset_ack(struct osmo_fsm_inst *fi, uint32_t even osmo_fsm_inst_state_chg(fi, BSSGP_BVCFSM_S_BLOCKED, T1_SECS, T1); } else osmo_fsm_inst_state_chg(fi, BSSGP_BVCFSM_S_UNBLOCKED, 0, 0); + if (bfp->ops && bfp->ops->reset_ack_notification) + bfp->ops->reset_ack_notification(bfp->nsei, bfp->bvci, &bfp->ra_id, bfp->cell_id, cause, bfp->ops_priv); break; } } @@ -415,8 +419,8 @@ static void bssgp_bvc_fsm_unblocked(struct osmo_fsm_inst *fi, uint32_t event, vo rx = data; tp = (const struct tlv_parsed *) msgb_bcid(rx); /* If BVC-UNBLOCK-ACK PDU is received by an BSS for the signalling BVC, the PDU is ignored. */ - LOGPFSML(fi, LOGL_ERROR, "Rx BVC-UNBLOCK-ACK on BVCI=0 is illegal\n"); if (bfp->bvci == 0) { + LOGPFSML(fi, LOGL_ERROR, "Rx BVC-UNBLOCK-ACK on BVCI=0 is illegal\n"); if (!bfp->role_sgsn) break; _tx_status(fi, BSSGP_CAUSE_SEM_INCORR_PDU, rx); diff --git a/src/gb/frame_relay.c b/src/gb/frame_relay.c index 4d1df672..e973b915 100644 --- a/src/gb/frame_relay.c +++ b/src/gb/frame_relay.c @@ -138,7 +138,7 @@ struct q933_a_pvc_sts { ext2:1; #elif OSMO_IS_BIG_ENDIAN -/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianess.py) */ +/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianness.py) */ uint8_t ext0:1, spare:1, dlci_msb:6; uint8_t ext1:1, dlci_lsb:4, space1:3; uint8_t ext2:1, spare2:3, new:1, delete:1, active:1, reserved:1; @@ -652,7 +652,7 @@ static int rx_lmi_q933_status(struct msgb *msg, struct tlv_parsed *tp) /* check for mandatory IEs */ if (!TLVP_PRES_LEN(tp, Q933_IEI_REPORT_TYPE, 1)) { - LOGPFRL(link, LOGL_NOTICE, "Rx STATUSL: Missing TLV Q933 Report Type\n"); + LOGPFRL(link, LOGL_NOTICE, "Rx STATUS: Missing TLV Q933 Report Type\n"); return -1; } diff --git a/src/gb/gprs_bssgp.c b/src/gb/gprs_bssgp.c index c967f730..7abef804 100644 --- a/src/gb/gprs_bssgp.c +++ b/src/gb/gprs_bssgp.c @@ -1428,7 +1428,7 @@ void bssgp_fc_flush_queue(struct bssgp_flow_control *fc) /*! * \brief Flush the queues of all BSSGP contexts. */ -void bssgp_flush_all_queues() +void bssgp_flush_all_queues(void) { struct bssgp_bvc_ctx *bctx; diff --git a/src/gb/gprs_bssgp2.c b/src/gb/gprs_bssgp2.c index 8e2ba66c..104fe08e 100644 --- a/src/gb/gprs_bssgp2.c +++ b/src/gb/gprs_bssgp2.c @@ -349,6 +349,35 @@ struct msgb *bssgp2_enc_fc_bvc(const struct bssgp2_flow_ctrl *fc, enum bssgp_fc_ return msg; } +/*! Encode BSSGP FLUSH-LL PDU as per TS 48.018 Section 10.4.1. + * \param[in] tlli - the TLLI of the MS + * \param[in] old_bvci BVCI + * \param[in] new_bvci2 optional BVCI - only encoded if non-NULL + * \param[in] nsei optional - only encoded if non-NULL + * \returns encoded PDU or NULL in case of error */ +struct msgb *bssgp2_enc_flush_ll(uint32_t tlli, uint16_t old_bvci, + const uint16_t *new_bvci, const uint16_t *nsei) +{ + struct msgb *msg = bssgp_msgb_alloc(); + struct bssgp_normal_hdr *bgph; + + if (!msg) + return NULL; + + bgph = (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph)); + bgph->pdu_type = BSSGP_PDUT_FLUSH_LL; + + msgb_tvlv_put_32be(msg, BSSGP_IE_TLLI, tlli); + msgb_tvlv_put_16be(msg, BSSGP_IE_BVCI, old_bvci); + if (new_bvci) + msgb_tvlv_put_16be(msg, BSSGP_IE_BVCI, *new_bvci); + + if (nsei) + msgb_tvlv_put_16be(msg, BSSGP_IE_BVCI, *nsei); + + return msg; +} + /*! Encode a FLOW-CONTROL-BVC-ACK PDU as per TS 48.018 Section 10.4.4. * \param[in] tag the tag IE value to encode * \returns encoded PDU or NULL in case of error */ diff --git a/src/gb/gprs_bssgp_rim.c b/src/gb/gprs_bssgp_rim.c index 63b303e5..9c09e1ef 100644 --- a/src/gb/gprs_bssgp_rim.c +++ b/src/gb/gprs_bssgp_rim.c @@ -49,12 +49,13 @@ const struct value_string bssgp_rim_routing_info_discr_strs[] = { { 0, NULL } }; -/*! Parse a RIM Routing information IE (3GPP TS 48.018, chapter 11.3.70). +/*! Parse a RIM Routing address IE (3GPP TS 29.060, chapter 7.7.57 and 7.7.77). * \param[out] ri user provided memory to store the parsed results. - * \param[in] buf input buffer of the value part of the IE. + * \param[in] buf input buffer of the value part of the RIM Routing address IE. + * \discr[in] discr value part (one byte) of the RIM Routing Address Discriminator IE. * \returns length of parsed octets, -EINVAL on error. */ -int bssgp_parse_rim_ri(struct bssgp_rim_routing_info *ri, const uint8_t *buf, - unsigned int len) +int bssgp_parse_rim_ra(struct bssgp_rim_routing_info *ri, const uint8_t *buf, + unsigned int len, uint8_t discr) { struct gprs_ra_id raid_temp; @@ -62,23 +63,22 @@ int bssgp_parse_rim_ri(struct bssgp_rim_routing_info *ri, const uint8_t *buf, if (len < 2) return -EINVAL; - ri->discr = buf[0] & 0x0f; - buf++; + ri->discr = discr; switch (ri->discr) { case BSSGP_RIM_ROUTING_INFO_GERAN: - if (len < 9) + if (len < 8) return -EINVAL; ri->geran.cid = bssgp_parse_cell_id(&ri->geran.raid, buf); - return 9; + return 8; case BSSGP_RIM_ROUTING_INFO_UTRAN: - if (len < 9) + if (len < 8) return -EINVAL; gsm48_parse_ra(&ri->utran.raid, buf); ri->utran.rncid = osmo_load16be(buf + 6); - return 9; + return 8; case BSSGP_RIM_ROUTING_INFO_EUTRAN: - if (len < 7 || len > 14) + if (len < 6 || len > 13) return -EINVAL; /* Note: 3GPP TS 24.301 Figure 9.9.3.32.1 and 3GPP TS 24.008 * Figure 10.5.130 specify MCC/MNC encoding in the same way, @@ -88,14 +88,35 @@ int bssgp_parse_rim_ri(struct bssgp_rim_routing_info *ri, const uint8_t *buf, ri->eutran.tai.mnc = raid_temp.mnc; ri->eutran.tai.mnc_3_digits = raid_temp.mnc_3_digits; ri->eutran.tai.tac = osmo_load16be(buf + 3); - memcpy(ri->eutran.global_enb_id, buf + 5, len - 6); - ri->eutran.global_enb_id_len = len - 6; + memcpy(ri->eutran.global_enb_id, buf + 5, len - 5); + ri->eutran.global_enb_id_len = len - 5; return len; default: return -EINVAL; } } +/*! Parse a RIM Routing information IE (3GPP TS 48.018, chapter 11.3.70). + * \param[out] ri user provided memory to store the parsed results. + * \param[in] buf input buffer of the value part of the IE. + * \returns length of parsed octets, -EINVAL on error. */ +int bssgp_parse_rim_ri(struct bssgp_rim_routing_info *ri, const uint8_t *buf, + unsigned int len) +{ + uint8_t discr; + int rc; + + if (len < 1) + return -EINVAL; + + discr = buf[0] & 0x0f; + + rc = bssgp_parse_rim_ra(ri, buf + 1, len - 1, discr); + if (rc < 0) + return rc; + return rc + 1; +} + /*! Encode a RIM Routing information IE (3GPP TS 48.018, chapter 11.3.70). * \param[out] buf user provided memory (at least 14 byte) for the generated value part of the IE. * \param[in] ri user provided input data struct. @@ -1064,7 +1085,6 @@ struct msgb *bssgp_encode_rim_pdu(const struct bssgp_ran_information_pdu *pdu) struct msgb *msg = bssgp_msgb_alloc(); struct bssgp_normal_hdr *bgph; uint8_t rim_ri_buf[BSSGP_RIM_ROUTING_INFO_MAXLEN]; - uint8_t *rim_cont_buf; int rc; if (!msg) @@ -1105,7 +1125,7 @@ struct msgb *bssgp_encode_rim_pdu(const struct bssgp_ran_information_pdu *pdu) /* Put RIM container */ if (pdu->decoded_present) { - rim_cont_buf = talloc_zero_size(msg, msg->data_len); + uint8_t *rim_cont_buf = talloc_zero_size(msg, msg->data_len); if (!rim_cont_buf) goto error; @@ -1130,8 +1150,10 @@ struct msgb *bssgp_encode_rim_pdu(const struct bssgp_ran_information_pdu *pdu) /* The API user must set the iei properly! */ OSMO_ASSERT(false); } - if (rc < 0) + if (rc < 0) { + talloc_free(rim_cont_buf); goto error; + } msgb_tvlv_put(msg, pdu->rim_cont_iei, rc, rim_cont_buf); talloc_free(rim_cont_buf); @@ -1143,7 +1165,6 @@ struct msgb *bssgp_encode_rim_pdu(const struct bssgp_ran_information_pdu *pdu) return msg; error: - talloc_free(rim_cont_buf); msgb_free(msg); return 0; } @@ -1171,14 +1192,33 @@ int bssgp_tx_rim(const struct bssgp_ran_information_pdu *pdu, uint16_t nsei) msgb_bvci(msg) = 0; /* Signalling */ bgph = (struct bssgp_normal_hdr *)msgb_bssgph(msg); - DEBUGP(DLBSSGP, "BSSGP BVCI=0 Tx RIM-PDU:%s, src=%s, dest=%s\n", - bssgp_pdu_str(bgph->pdu_type), + DEBUGP(DLBSSGP, "BSSGP BVCI=0 NSEI=%u Tx RIM-PDU:%s, src=%s, dest=%s\n", + nsei, bssgp_pdu_str(bgph->pdu_type), bssgp_rim_ri_name_buf(ri_src_str, sizeof(ri_src_str), &pdu->routing_info_src), bssgp_rim_ri_name_buf(ri_dest_str, sizeof(ri_dest_str), &pdu->routing_info_dest)); return bssgp_ns_send(bssgp_ns_send_data, msg); } +/*! Send encoded RAN TRANSPARENT CONTAINER via BSSGP (3GPP TS 29.060, section 7.7.43). + * \param[in] msg user provided memory for the encoded RAN TRANSPARENT CONTAINER to be sent. + * (this function will take ownership of msg). + * \param[in] nsei BSSGP network service entity identifier (NSEI). + * \returns 0 on sccess, -EINVAL on error. */ +int bssgp_tx_rim_encoded(struct msgb *msg, uint16_t nsei) +{ + struct bssgp_normal_hdr *bgph; + + msgb_nsei(msg) = nsei; + msgb_bvci(msg) = 0; /* Signalling */ + + bgph = (struct bssgp_normal_hdr *)msgb_bssgph(msg); + DEBUGP(DLBSSGP, "BSSGP BVCI=0 NSEI=%u Tx RIM-PDU:%s\n", + nsei, bssgp_pdu_str(bgph->pdu_type)); + + return bssgp_ns_send(bssgp_ns_send_data, msg); +} + /* For internal use only (called from gprs_bssgp.c) */ int bssgp_rx_rim(struct msgb *msg, struct tlv_parsed *tp, uint16_t bvci) { diff --git a/src/gb/gprs_ns.c b/src/gb/gprs_ns.c index 9f5df8f7..304fe7c1 100644 --- a/src/gb/gprs_ns.c +++ b/src/gb/gprs_ns.c @@ -352,8 +352,7 @@ struct gprs_nsvc *gprs_nsvc_create2(struct gprs_ns_inst *nsi, uint16_t nsvci, */ void gprs_nsvc_delete(struct gprs_nsvc *nsvc) { - if (osmo_timer_pending(&nsvc->timer)) - osmo_timer_del(&nsvc->timer); + osmo_timer_del(&nsvc->timer); llist_del(&nsvc->list); rate_ctr_group_free(nsvc->ctrg); osmo_stat_item_group_free(nsvc->statg); @@ -533,7 +532,7 @@ static int gprs_ns_tx_simple(struct gprs_nsvc *nsvc, uint8_t pdu_type) /*! Transmit a NS-RESET on a given NSVC * \param[in] nsvc NS-VC used for transmission - * \paam[in] cause Numeric NS cause value + * \param[in] cause Numeric NS cause value */ int gprs_ns_tx_reset(struct gprs_nsvc *nsvc, uint8_t cause) { @@ -750,8 +749,7 @@ static void nsvc_start_timer(struct gprs_nsvc *nsvc, enum nsvc_timer_mode mode) nsvc->nsei, get_value_string(timer_mode_strs, mode), seconds); - if (osmo_timer_pending(&nsvc->timer)) - osmo_timer_del(&nsvc->timer); + osmo_timer_del(&nsvc->timer); osmo_gettimeofday(&nsvc->timer_started, NULL); nsvc->timer_mode = mode; diff --git a/src/gb/gprs_ns2.c b/src/gb/gprs_ns2.c index 6c48ca6f..4e496c1f 100644 --- a/src/gb/gprs_ns2.c +++ b/src/gb/gprs_ns2.c @@ -621,6 +621,7 @@ struct gprs_ns2_vc *ns2_vc_alloc(struct gprs_ns2_vc_bind *bind, struct gprs_ns2_ llist_add_tail(&nsvc->list, &nse->nsvc); llist_add_tail(&nsvc->blist, &bind->nsvc); + osmo_clock_gettime(CLOCK_MONOTONIC, &nsvc->ts_alive_change); ns2_nse_update_mtu(nse); return nsvc; @@ -838,6 +839,7 @@ struct gprs_ns2_nse *gprs_ns2_create_nse2(struct gprs_ns2_inst *nsi, uint16_t ns nse->mtu = 0; llist_add_tail(&nse->list, &nsi->nse); INIT_LLIST_HEAD(&nse->nsvc); + osmo_clock_gettime(CLOCK_MONOTONIC, &nse->ts_alive_change); return nse; } @@ -1354,7 +1356,7 @@ int ns2_recv_vc(struct gprs_ns2_vc *nsvc, if (rc < 0) { LOGP(DLNS, LOGL_NOTICE, "Error during TLV Parse\n"); if (nsh->pdu_type != NS_PDUT_STATUS) - ns2_tx_status(nsvc, NS_CAUSE_PROTO_ERR_UNSPEC, 0, msg); + ns2_tx_status(nsvc, NS_CAUSE_PROTO_ERR_UNSPEC, 0, msg, NULL); return rc; } return ns2_vc_rx(nsvc, msg, &tp); @@ -1390,16 +1392,22 @@ void ns2_nse_data_sum(struct gprs_ns2_nse *nse) void ns2_nse_notify_unblocked(struct gprs_ns2_vc *nsvc, bool unblocked) { struct gprs_ns2_nse *nse = nsvc->nse; + struct gprs_ns2_inst *nsi = nse->nsi; + uint16_t nsei = nse->nsei; ns2_nse_data_sum(nse); ns2_sns_notify_alive(nse, nsvc, unblocked); - if (unblocked == nse->alive) + /* NSE could have been freed, try to get it again */ + nse = gprs_ns2_nse_by_nsei(nsi, nsei); + + if (!nse || unblocked == nse->alive) return; /* wait until both data_weight and sig_weight are != 0 before declaring NSE as alive */ if (unblocked && nse->sum_data_weight && nse->sum_sig_weight) { nse->alive = true; + osmo_clock_gettime(CLOCK_MONOTONIC, &nse->ts_alive_change); ns2_prim_status_ind(nse, NULL, 0, GPRS_NS2_AFF_CAUSE_RECOVERY); nse->first = false; return; @@ -1408,6 +1416,7 @@ void ns2_nse_notify_unblocked(struct gprs_ns2_vc *nsvc, bool unblocked) if (nse->alive && (nse->sum_data_weight == 0 || nse->sum_sig_weight == 0)) { /* nse became unavailable */ nse->alive = false; + osmo_clock_gettime(CLOCK_MONOTONIC, &nse->ts_alive_change); ns2_prim_status_ind(nse, NULL, 0, GPRS_NS2_AFF_CAUSE_FAILURE); } } @@ -1442,6 +1451,8 @@ struct gprs_ns2_inst *gprs_ns2_instantiate(void *ctx, osmo_prim_cb cb, void *cb_ nsi->timeout[NS_TOUT_TSNS_CONFIG_RETRIES] = 3; nsi->timeout[NS_TOUT_TSNS_PROCEDURES_RETRIES] = 3; + nsi->txqueue_max_length = NS_DEFAULT_TXQUEUE_MAX_LENGTH; + return nsi; } diff --git a/src/gb/gprs_ns2_fr.c b/src/gb/gprs_ns2_fr.c index 35e0dd9e..f6bee39c 100644 --- a/src/gb/gprs_ns2_fr.c +++ b/src/gb/gprs_ns2_fr.c @@ -55,13 +55,10 @@ #include <osmocom/core/timer.h> #include <osmocom/core/talloc.h> #include <osmocom/gprs/gprs_ns2.h> +#include <osmocom/core/netdev.h> #include <osmocom/gprs/protocol/gsm_08_16.h> #include <osmocom/gprs/protocol/gsm_08_18.h> -#ifdef ENABLE_LIBMNL -#include <osmocom/core/mnl.h> -#endif - #include "config.h" #include "common_vty.h" #include "gprs_ns2_internal.h" @@ -83,11 +80,6 @@ /* nanoseconds per bit (504) */ #define BIT_DURATION_NS (1000000000 / SUPERCHANNEL_LINERATE) -struct gre_hdr { - uint16_t flags; - uint16_t ptype; -} __attribute__ ((packed)); - static void free_bind(struct gprs_ns2_vc_bind *bind); static int fr_dlci_rx_cb(void *cb_data, struct msgb *msg); @@ -97,11 +89,12 @@ struct gprs_ns2_vc_driver vc_driver_fr = { }; struct priv_bind { + struct osmo_netdev *netdev; char netif[IFNAMSIZ]; struct osmo_fr_link *link; int ifindex; bool if_running; - /* backlog queue for AF_PACKET / ENOBUFS handling (see OS#4993) */ + /* backlog queue for AF_PACKET / ENOBUFS handling (see OS#4995) */ struct { /* file-descriptor for AF_PACKET socket */ struct osmo_fd ofd; @@ -177,6 +170,7 @@ static void free_bind(struct gprs_ns2_vc_bind *bind) } msgb_free(priv->backlog.lmi_msg); + osmo_netdev_free(priv->netdev); osmo_fr_link_free(priv->link); osmo_fd_close(&priv->backlog.ofd); talloc_free(priv); @@ -256,13 +250,13 @@ static int fr_netif_ofd_cb(struct osmo_fd *bfd, uint32_t what) if (!(what & OSMO_FD_READ)) return 0; - msg = msgb_alloc(NS_ALLOC_SIZE, "Gb/NS/FR/GRE Rx"); + msg = msgb_alloc(NS_ALLOC_SIZE, "Gb/NS/FR Rx"); if (!msg) return -ENOMEM; rc = recvfrom(bfd->fd, msg->data, NS_ALLOC_SIZE, 0, (struct sockaddr *)&sll, &sll_len); if (rc < 0) { - LOGBIND(bind, LOGL_ERROR, "recv error %s during NS-FR-GRE recv\n", strerror(errno)); + LOGBIND(bind, LOGL_ERROR, "recv error %s during NS-FR recv\n", strerror(errno)); goto out_err; } else if (rc == 0) { goto out_err; @@ -338,9 +332,19 @@ int gprs_ns2_is_fr_bind(struct gprs_ns2_vc_bind *bind) static int fr_vc_sendmsg(struct gprs_ns2_vc *nsvc, struct msgb *msg) { struct priv_vc *vcpriv = nsvc->priv; + unsigned int vc_len = msgb_length(msg); + int rc; msg->dst = vcpriv->dlc; - return osmo_fr_tx_dlc(msg); + rc = osmo_fr_tx_dlc(msg); + if (OSMO_LIKELY(rc >= 0)) { + RATE_CTR_INC_NS(nsvc, NS_CTR_PKTS_OUT); + RATE_CTR_ADD_NS(nsvc, NS_CTR_BYTES_OUT, vc_len); + } else { + RATE_CTR_INC_NS(nsvc, NS_CTR_PKTS_OUT_DROP); + RATE_CTR_ADD_NS(nsvc, NS_CTR_BYTES_OUT_DROP, vc_len); + } + return rc; } static void enqueue_at_head(struct gprs_ns2_vc_bind *bind, struct msgb *msg) @@ -520,60 +524,14 @@ static int open_socket(int ifindex, const struct gprs_ns2_vc_bind *nsbind) return fd; } -#ifdef ENABLE_LIBMNL - -#include <osmocom/core/mnl.h> -#include <linux/if_link.h> -#include <linux/rtnetlink.h> - -#ifndef ARPHRD_FRAD -#define ARPHRD_FRAD 770 -#endif - -/* validate the netlink attributes */ -static int data_attr_cb(const struct nlattr *attr, void *data) -{ - const struct nlattr **tb = data; - int type = mnl_attr_get_type(attr); - - if (mnl_attr_type_valid(attr, IFLA_MAX) < 0) - return MNL_CB_OK; - - switch (type) { - case IFLA_MTU: - if (mnl_attr_validate(attr, MNL_TYPE_U32) < 0) - return MNL_CB_ERROR; - break; - case IFLA_IFNAME: - if (mnl_attr_validate(attr, MNL_TYPE_STRING) < 0) - return MNL_CB_ERROR; - break; - } - tb[type] = attr; - return MNL_CB_OK; -} - -/* find the bind for the netdev (if any) */ -static struct gprs_ns2_vc_bind *bind4netdev(struct gprs_ns2_inst *nsi, const char *ifname) -{ - struct gprs_ns2_vc_bind *bind; - - llist_for_each_entry(bind, &nsi->binding, list) { - struct priv_bind *bpriv = bind->priv; - if (!strcmp(bpriv->netif, ifname)) - return bind; - } - - return NULL; -} - -static void link_state_change(struct gprs_ns2_vc_bind *bind, bool if_running) +static int gprs_n2_fr_ifupdown_ind_cb(struct osmo_netdev *netdev, bool if_running) { + struct gprs_ns2_vc_bind *bind = osmo_netdev_get_priv_data(netdev); struct priv_bind *bpriv = bind->priv; struct msgb *msg, *msg2; if (bpriv->if_running == if_running) - return; + return 0; LOGBIND(bind, LOGL_NOTICE, "FR net-device '%s': Physical link state changed: %s\n", bpriv->netif, if_running ? "UP" : "DOWN"); @@ -594,93 +552,36 @@ static void link_state_change(struct gprs_ns2_vc_bind *bind, bool if_running) } bpriv->if_running = if_running; + return 0; } -static void mtu_change(struct gprs_ns2_vc_bind *bind, uint32_t mtu) +static int gprs_n2_fr_mtu_chg_cb(struct osmo_netdev *netdev, uint32_t new_mtu) { + struct gprs_ns2_vc_bind *bind = osmo_netdev_get_priv_data(netdev); struct priv_bind *bpriv = bind->priv; struct gprs_ns2_nse *nse; /* 2 byte DLCI header */ - if (mtu <= 2) - return; - mtu -= 2; + if (new_mtu <= 2) + return 0; + new_mtu -= 2; - if (mtu == bind->mtu) - return; + if (new_mtu == bind->mtu) + return 0; LOGBIND(bind, LOGL_INFO, "MTU changed from %d to %d.\n", - bind->mtu + 2, mtu + 2); + bind->mtu + 2, new_mtu + 2); - bind->mtu = mtu; + bind->mtu = new_mtu; if (!bpriv->if_running) - return; + return 0; llist_for_each_entry(nse, &bind->nsi->nse, list) { ns2_nse_update_mtu(nse); } + return 0; } -/* handle a single netlink message received via libmnl */ -static int linkmon_mnl_cb(const struct nlmsghdr *nlh, void *data) -{ - struct osmo_mnl *omnl = data; - struct gprs_ns2_vc_bind *bind; - struct nlattr *tb[IFLA_MAX+1] = {}; - struct ifinfomsg *ifm = mnl_nlmsg_get_payload(nlh); - struct gprs_ns2_inst *nsi; - const char *ifname; - bool if_running; - - OSMO_ASSERT(omnl); - OSMO_ASSERT(ifm); - - nsi = omnl->priv; - - if (ifm->ifi_type != ARPHRD_FRAD) - return MNL_CB_OK; - - mnl_attr_parse(nlh, sizeof(*ifm), data_attr_cb, tb); - - if (!tb[IFLA_IFNAME]) - return MNL_CB_OK; - ifname = mnl_attr_get_str(tb[IFLA_IFNAME]); - if_running = !!(ifm->ifi_flags & IFF_RUNNING); - - bind = bind4netdev(nsi, ifname); - if (!bind) - return MNL_CB_OK; - - if (tb[IFLA_MTU]) { - mtu_change(bind, mnl_attr_get_u32(tb[IFLA_MTU])); - } - - link_state_change(bind, if_running); - - return MNL_CB_OK; -} - -/* trigger one initial dump of all link information */ -static void linkmon_initial_dump(struct osmo_mnl *omnl) -{ - char buf[MNL_SOCKET_BUFFER_SIZE]; - struct nlmsghdr *nlh = mnl_nlmsg_put_header(buf); - struct rtgenmsg *rt; - - nlh->nlmsg_type = RTM_GETLINK; - nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; - nlh->nlmsg_seq = time(NULL); - rt = mnl_nlmsg_put_extra_header(nlh, sizeof(struct rtgenmsg)); - rt->rtgen_family = AF_PACKET; - - if (mnl_socket_sendto(omnl->mnls, nlh, nlh->nlmsg_len) < 0) { - LOGP(DLNS, LOGL_ERROR, "linkmon: Cannot send rtnetlink message: %s\n", strerror(errno)); - } - - /* the response[s] will be handled just like the events */ -} -#endif /* LIBMNL */ - static int set_ifupdown(const char *netif, bool up) { int sock, rc; @@ -859,16 +760,31 @@ int gprs_ns2_fr_bind(struct gprs_ns2_inst *nsi, goto err_fr; } + priv->netdev = osmo_netdev_alloc(bind, name); + if (!priv->netdev) { + rc = -ENOENT; + goto err_fr; + } + osmo_netdev_set_priv_data(priv->netdev, bind); + osmo_netdev_set_ifupdown_ind_cb(priv->netdev, gprs_n2_fr_ifupdown_ind_cb); + osmo_netdev_set_mtu_chg_cb(priv->netdev, gprs_n2_fr_mtu_chg_cb); + rc = osmo_netdev_set_ifindex(priv->netdev, priv->ifindex); + if (rc < 0) + goto err_free_netdev; + rc = osmo_netdev_register(priv->netdev); + if (rc < 0) + goto err_free_netdev; + /* set protocol frame relay and lmi */ rc = setup_device(priv->netif, bind); if(rc < 0) { LOGBIND(bind, LOGL_ERROR, "Failed to setup the interface %s for frame relay and lmi\n", netif); - goto err_fr; + goto err_free_netdev; } rc = open_socket(priv->ifindex, bind); if (rc < 0) - goto err_fr; + goto err_free_netdev; priv->backlog.retry_us = 2500; /* start with some non-zero value; this corrsponds to 496 bytes */ osmo_timer_setup(&priv->backlog.timer, fr_backlog_timer_cb, bind); osmo_fd_setup(&priv->backlog.ofd, rc, OSMO_FD_READ, fr_netif_ofd_cb, bind, 0); @@ -876,16 +792,6 @@ int gprs_ns2_fr_bind(struct gprs_ns2_inst *nsi, if (rc < 0) goto err_fd; -#ifdef ENABLE_LIBMNL - if (!nsi->linkmon_mnl) - nsi->linkmon_mnl = osmo_mnl_init(nsi, NETLINK_ROUTE, RTMGRP_LINK, linkmon_mnl_cb, nsi); - - /* we get a new full dump after every bind. which is a bit excessive. But that's just once - * at start-up, so we can get away with it */ - if (nsi->linkmon_mnl) - linkmon_initial_dump(nsi->linkmon_mnl); -#endif - if (result) *result = bind; @@ -893,6 +799,9 @@ int gprs_ns2_fr_bind(struct gprs_ns2_inst *nsi, err_fd: close(priv->backlog.ofd.fd); +err_free_netdev: + osmo_netdev_free(priv->netdev); + priv->netdev = NULL; err_fr: osmo_fr_link_free(fr_link); priv->link = NULL; diff --git a/src/gb/gprs_ns2_frgre.c b/src/gb/gprs_ns2_frgre.c index 93913431..b99761eb 100644 --- a/src/gb/gprs_ns2_frgre.c +++ b/src/gb/gprs_ns2_frgre.c @@ -503,6 +503,8 @@ static int frgre_vc_sendmsg(struct gprs_ns2_vc *nsvc, struct msgb *msg) uint16_t dlci = osmo_htons(bindpriv->dlci); uint8_t *frh; struct gre_hdr *greh; + unsigned int vc_len = msgb_length(msg); + int rc; /* Prepend the FR header */ frh = msgb_push(msg, 2); @@ -514,7 +516,15 @@ static int frgre_vc_sendmsg(struct gprs_ns2_vc *nsvc, struct msgb *msg) greh->flags = 0; greh->ptype = osmo_htons(GRE_PTYPE_FR); - return frgre_sendmsg(bind, msg, &vcpriv->remote); + rc = frgre_sendmsg(bind, msg, &vcpriv->remote); + if (OSMO_LIKELY(rc >= 0)) { + RATE_CTR_INC_NS(nsvc, NS_CTR_PKTS_OUT); + RATE_CTR_ADD_NS(nsvc, NS_CTR_BYTES_OUT, rc); + } else { + RATE_CTR_INC_NS(nsvc, NS_CTR_PKTS_OUT_DROP); + RATE_CTR_ADD_NS(nsvc, NS_CTR_BYTES_OUT_DROP, vc_len); + } + return rc; } static int frgre_fd_cb(struct osmo_fd *bfd, unsigned int what) diff --git a/src/gb/gprs_ns2_internal.h b/src/gb/gprs_ns2_internal.h index afe6b69d..2e7dac3f 100644 --- a/src/gb/gprs_ns2_internal.h +++ b/src/gb/gprs_ns2_internal.h @@ -6,6 +6,7 @@ #include <stdint.h> #include <osmocom/core/logging.h> +#include <osmocom/core/rate_ctr.h> #include <osmocom/gprs/protocol/gsm_08_16.h> #include <osmocom/gprs/gprs_ns2.h> @@ -61,7 +62,22 @@ struct gprs_ns2_vc_driver; struct gprs_ns2_vc_bind; #define NS_TIMERS_COUNT 11 -#define NS_TIMERS "(tns-block|tns-block-retries|tns-reset|tns-reset-retries|tns-test|tns-alive|tns-alive-retries|tsns-prov|tsns-size-retries|tsns-config-retries|tsns-procedures-retries)" + +#define TNS_BLOCK_STR "tns-block" +#define TNS_BLOCK_RETRIES_STR "tns-block-retries" +#define TNS_RESET_STR "tns-reset" +#define TNS_RESET_RETRIES_STR "tns-reset-retries" +#define TNS_TEST_STR "tns-test" +#define TNS_ALIVE_STR "tns-alive" +#define TNS_ALIVE_RETRIES_STR "tns-alive-retries" +#define TSNS_PROV_STR "tsns-prov" +#define TSNS_SIZE_RETRIES_STR "tsns-size-retries" +#define TSNS_CONFIG_RETRIES_STR "tsns-config-retries" +#define TSNS_PROCEDURES_RETRIES_STR "tsns-procedures-retries" +#define NS_TIMERS "(" TNS_BLOCK_STR "|" TNS_BLOCK_RETRIES_STR "|" TNS_RESET_STR "|" TNS_RESET_RETRIES_STR "|" TNS_TEST_STR "|"\ + TNS_ALIVE_STR "|" TNS_ALIVE_RETRIES_STR "|" TSNS_PROV_STR "|" TSNS_SIZE_RETRIES_STR "|" TSNS_CONFIG_RETRIES_STR "|"\ + TSNS_PROCEDURES_RETRIES_STR ")" + #define NS_TIMERS_HELP \ "(un)blocking Timer (Tns-block) timeout\n" \ "(un)blocking Timer (Tns-block) number of retries\n" \ @@ -79,6 +95,8 @@ struct gprs_ns2_vc_bind; #define NS_ALLOC_SIZE 3072 #define NS_ALLOC_HEADROOM 20 +#define NS_DEFAULT_TXQUEUE_MAX_LENGTH 128 + enum ns2_timeout { NS_TOUT_TNS_BLOCK, NS_TOUT_TNS_BLOCK_RETRIES, @@ -164,8 +182,7 @@ struct gprs_ns2_inst { uint32_t nsvc_rate_ctr_idx; uint32_t bind_rate_ctr_idx; - /*! libmnl netlink socket for link state monitoring */ - struct osmo_mnl *linkmon_mnl; + uint32_t txqueue_max_length; }; @@ -220,6 +237,9 @@ struct gprs_ns2_nse { /*! recursive anchor */ bool freed; + + /*! when the NSE became alive or dead */ + struct timespec ts_alive_change; }; /*! Structure representing a single NS-VC */ @@ -267,6 +287,12 @@ struct gprs_ns2_vc { /*! recursive anchor */ bool freed; + + /*! if blocked by O&M/vty */ + bool om_blocked; + + /*! when the NSVC became alive or dead */ + struct timespec ts_alive_change; }; /*! Structure repesenting a bind instance. E.g. IPv4 listen port. */ @@ -416,8 +442,8 @@ int ns2_tx_sns_del(struct gprs_ns2_vc *nsvc, unsigned int num_ip6_elems); /* transmit message over a VC */ -int ns2_tx_block(struct gprs_ns2_vc *nsvc, uint8_t cause); -int ns2_tx_block_ack(struct gprs_ns2_vc *nsvc); +int ns2_tx_block(struct gprs_ns2_vc *nsvc, uint8_t cause, uint16_t *nsvci); +int ns2_tx_block_ack(struct gprs_ns2_vc *nsvc, uint16_t *nsvci); int ns2_tx_reset(struct gprs_ns2_vc *nsvc, uint8_t cause); int ns2_tx_reset_ack(struct gprs_ns2_vc *nsvc); @@ -433,7 +459,7 @@ int ns2_tx_unit_data(struct gprs_ns2_vc *nsvc, struct msgb *msg); int ns2_tx_status(struct gprs_ns2_vc *nsvc, uint8_t cause, - uint16_t bvci, struct msgb *orig_msg); + uint16_t bvci, struct msgb *orig_msg, uint16_t *nsvci); /* driver */ struct gprs_ns2_vc *ns2_ip_bind_connect(struct gprs_ns2_vc_bind *bind, @@ -443,6 +469,7 @@ int ns2_ip_count_bind(struct gprs_ns2_inst *nsi, struct osmo_sockaddr *remote); struct gprs_ns2_vc_bind *ns2_ip_get_bind_by_index(struct gprs_ns2_inst *nsi, struct osmo_sockaddr *remote, int index); +void ns2_ip_set_txqueue_max_length(struct gprs_ns2_vc_bind *bind, unsigned int max_length); /* sns */ int ns2_sns_rx(struct gprs_ns2_vc *nsvc, struct msgb *msg, struct tlv_parsed *tp); diff --git a/src/gb/gprs_ns2_message.c b/src/gb/gprs_ns2_message.c index 5e3e025d..de63b7aa 100644 --- a/src/gb/gprs_ns2_message.c +++ b/src/gb/gprs_ns2_message.c @@ -103,6 +103,11 @@ static int ns2_validate_status(struct gprs_ns2_vc *nsvc, struct msgb *msg, struc *cause = NS_CAUSE_MISSING_ESSENT_IE; return -1; } + + if (nsvc->mode != GPRS_NS2_VC_MODE_BLOCKRESET) { + *cause = NS_CAUSE_PDU_INCOMP_PSTATE; + return -1; + } break; case NS_CAUSE_SEM_INCORR_PDU: case NS_CAUSE_PDU_INCOMP_PSTATE: @@ -164,23 +169,9 @@ int ns2_validate(struct gprs_ns2_vc *nsvc, return 0; } - static int ns_vc_tx(struct gprs_ns2_vc *nsvc, struct msgb *msg) { - unsigned int bytes = msgb_length(msg); - int rc; - - - rc = nsvc->bind->send_vc(nsvc, msg); - if (rc < 0) { - RATE_CTR_INC_NS(nsvc, NS_CTR_PKTS_OUT_DROP); - RATE_CTR_ADD_NS(nsvc, NS_CTR_BYTES_OUT_DROP, bytes); - } else { - RATE_CTR_INC_NS(nsvc, NS_CTR_PKTS_OUT); - RATE_CTR_ADD_NS(nsvc, NS_CTR_BYTES_OUT, bytes); - } - - return rc; + return nsvc->bind->send_vc(nsvc, msg); } /* transmit functions */ @@ -206,12 +197,18 @@ static int ns2_tx_simple(struct gprs_ns2_vc *nsvc, uint8_t pdu_type) /*! Transmit a NS-BLOCK on a given NS-VC. * \param[in] vc NS-VC on which the NS-BLOCK is to be transmitted * \param[in] cause Numeric NS Cause value + * \param[in] nsvci if given this NSVCI will be encoded. If NULL the nsvc->nsvci will be used. * \returns 0 in case of success */ -int ns2_tx_block(struct gprs_ns2_vc *nsvc, uint8_t cause) +int ns2_tx_block(struct gprs_ns2_vc *nsvc, uint8_t cause, uint16_t *nsvci) { struct msgb *msg; struct gprs_ns_hdr *nsh; - uint16_t nsvci = osmo_htons(nsvc->nsvci); + uint16_t encoded_nsvci; + + if (nsvci) + encoded_nsvci = osmo_htons(*nsvci); + else + encoded_nsvci = osmo_htons(nsvc->nsvci); log_set_context(LOG_CTX_GB_NSE, nsvc->nse); log_set_context(LOG_CTX_GB_NSVC, nsvc); @@ -229,7 +226,7 @@ int ns2_tx_block(struct gprs_ns2_vc *nsvc, uint8_t cause) nsh->pdu_type = NS_PDUT_BLOCK; msgb_tvlv_put(msg, NS_IE_CAUSE, 1, &cause); - msgb_tvlv_put(msg, NS_IE_VCI, 2, (uint8_t *) &nsvci); + msgb_tvlv_put(msg, NS_IE_VCI, 2, (uint8_t *) &encoded_nsvci); LOG_NS_SIGNAL(nsvc, "Tx", nsh->pdu_type, LOGL_INFO, " cause=%s\n", gprs_ns2_cause_str(cause)); return ns_vc_tx(nsvc, msg); @@ -237,12 +234,18 @@ int ns2_tx_block(struct gprs_ns2_vc *nsvc, uint8_t cause) /*! Transmit a NS-BLOCK-ACK on a given NS-VC. * \param[in] nsvc NS-VC on which the NS-BLOCK is to be transmitted + * \param[in] nsvci if given this NSVCI will be encoded. If NULL the nsvc->nsvci will be used. * \returns 0 in case of success */ -int ns2_tx_block_ack(struct gprs_ns2_vc *nsvc) +int ns2_tx_block_ack(struct gprs_ns2_vc *nsvc, uint16_t *nsvci) { struct msgb *msg; struct gprs_ns_hdr *nsh; - uint16_t nsvci = osmo_htons(nsvc->nsvci); + uint16_t encoded_nsvci; + + if (nsvci) + encoded_nsvci = osmo_htons(*nsvci); + else + encoded_nsvci = osmo_htons(nsvc->nsvci); log_set_context(LOG_CTX_GB_NSE, nsvc->nse); log_set_context(LOG_CTX_GB_NSVC, nsvc); @@ -257,7 +260,7 @@ int ns2_tx_block_ack(struct gprs_ns2_vc *nsvc) nsh = (struct gprs_ns_hdr *) msg->l2h; nsh->pdu_type = NS_PDUT_BLOCK_ACK; - msgb_tvlv_put(msg, NS_IE_VCI, 2, (uint8_t *) &nsvci); + msgb_tvlv_put(msg, NS_IE_VCI, 2, (uint8_t *) &encoded_nsvci); LOG_NS_TX_SIGNAL(nsvc, nsh->pdu_type); return ns_vc_tx(nsvc, msg); @@ -265,7 +268,7 @@ int ns2_tx_block_ack(struct gprs_ns2_vc *nsvc) /*! Transmit a NS-RESET on a given NS-VC. * \param[in] nsvc NS-VC used for transmission - * \paam[in] cause Numeric NS cause value + * \param[in] cause Numeric NS cause value * \returns 0 in case of success */ int ns2_tx_reset(struct gprs_ns2_vc *nsvc, uint8_t cause) { @@ -415,13 +418,14 @@ int ns2_tx_unit_data(struct gprs_ns2_vc *nsvc, * \param[in] cause Numeric NS cause value * \param[in] bvci BVCI to be reset within NSVC * \param[in] orig_msg message causing the STATUS + * \param[in] nsvci if given this NSVCI will be encoded. If NULL the nsvc->nsvci will be used. * \returns 0 in case of success */ int ns2_tx_status(struct gprs_ns2_vc *nsvc, uint8_t cause, - uint16_t bvci, struct msgb *orig_msg) + uint16_t bvci, struct msgb *orig_msg, uint16_t *nsvci) { struct msgb *msg = ns2_msgb_alloc(); struct gprs_ns_hdr *nsh; - uint16_t nsvci = osmo_htons(nsvc->nsvci); + uint16_t encoded_nsvci; unsigned int orig_len, max_orig_len; log_set_context(LOG_CTX_GB_NSE, nsvc->nse); @@ -442,7 +446,11 @@ int ns2_tx_status(struct gprs_ns2_vc *nsvc, uint8_t cause, case NS_CAUSE_NSVC_BLOCKED: case NS_CAUSE_NSVC_UNKNOWN: /* Section 9.2.7.1: Static conditions for NS-VCI */ - msgb_tvlv_put(msg, NS_IE_VCI, 2, (uint8_t *)&nsvci); + if (nsvci) + encoded_nsvci = osmo_htons(*nsvci); + else + encoded_nsvci = osmo_htons(nsvc->nsvci); + msgb_tvlv_put(msg, NS_IE_VCI, 2, (uint8_t *)&encoded_nsvci); break; case NS_CAUSE_SEM_INCORR_PDU: case NS_CAUSE_PDU_INCOMP_PSTATE: diff --git a/src/gb/gprs_ns2_udp.c b/src/gb/gprs_ns2_udp.c index 48160218..1dcc3030 100644 --- a/src/gb/gprs_ns2_udp.c +++ b/src/gb/gprs_ns2_udp.c @@ -28,6 +28,7 @@ #include <errno.h> +#include <osmocom/core/osmo_io.h> #include <osmocom/core/select.h> #include <osmocom/core/sockaddr_str.h> #include <osmocom/core/socket.h> @@ -46,7 +47,7 @@ struct gprs_ns2_vc_driver vc_driver_ip = { }; struct priv_bind { - struct osmo_fd fd; + struct osmo_io_fd *iofd; struct osmo_sockaddr addr; int dscp; uint8_t priority; @@ -68,7 +69,8 @@ static void free_bind(struct gprs_ns2_vc_bind *bind) priv = bind->priv; - osmo_fd_close(&priv->fd); + osmo_iofd_free(priv->iofd); + priv->iofd = NULL; talloc_free(priv); } @@ -118,10 +120,10 @@ static void dump_vty(const struct gprs_ns2_vc_bind *bind, /*! Find a NS-VC by its remote socket address. * \param[in] bind in which to search - * \param[in] saddr remote peer socket adddress to search + * \param[in] rem_addr remote peer socket address to search * \returns NS-VC matching sockaddr; NULL if none found */ struct gprs_ns2_vc *gprs_ns2_nsvc_by_sockaddr_bind(struct gprs_ns2_vc_bind *bind, - const struct osmo_sockaddr *saddr) + const struct osmo_sockaddr *rem_addr) { struct gprs_ns2_vc *nsvc; struct priv_vc *vcpriv; @@ -130,9 +132,9 @@ struct gprs_ns2_vc *gprs_ns2_nsvc_by_sockaddr_bind(struct gprs_ns2_vc_bind *bind llist_for_each_entry(nsvc, &bind->nsvc, blist) { vcpriv = nsvc->priv; - if (vcpriv->remote.u.sa.sa_family != saddr->u.sa.sa_family) + if (vcpriv->remote.u.sa.sa_family != rem_addr->u.sa.sa_family) continue; - if (osmo_sockaddr_cmp(&vcpriv->remote, saddr)) + if (osmo_sockaddr_cmp(&vcpriv->remote, rem_addr)) continue; return nsvc; @@ -143,17 +145,11 @@ struct gprs_ns2_vc *gprs_ns2_nsvc_by_sockaddr_bind(struct gprs_ns2_vc_bind *bind static inline int nsip_sendmsg(struct gprs_ns2_vc_bind *bind, struct msgb *msg, - struct osmo_sockaddr *dest) + const struct osmo_sockaddr *dest) { - int rc; struct priv_bind *priv = bind->priv; - rc = sendto(priv->fd.fd, msg->data, msg->len, 0, - &dest->u.sa, sizeof(*dest)); - - msgb_free(msg); - - return rc; + return osmo_iofd_sendto_msgb(priv->iofd, msg, 0, dest); } /*! send the msg and free it afterwards. @@ -171,40 +167,7 @@ static int nsip_vc_sendmsg(struct gprs_ns2_vc *nsvc, struct msgb *msg) return rc; } -/* Read a single NS-over-IP message */ -static struct msgb *read_nsip_msg(struct osmo_fd *bfd, int *error, struct osmo_sockaddr *saddr, - const struct gprs_ns2_vc_bind *bind) -{ - struct msgb *msg = 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) { - LOGBIND(bind, LOGL_ERROR, "recv error %s during NSIP recvfrom %s\n", - strerror(errno), osmo_sock_get_name2(bfd->fd)); - msgb_free(msg); - *error = ret; - return NULL; - } else if (ret == 0) { - msgb_free(msg); - *error = ret; - return NULL; - } - - msg->l2h = msg->data; - msgb_put(msg, ret); - - return msg; -} - -static struct priv_vc *ns2_driver_alloc_vc(struct gprs_ns2_vc_bind *bind, struct gprs_ns2_vc *nsvc, struct osmo_sockaddr *remote) +static struct priv_vc *ns2_driver_alloc_vc(struct gprs_ns2_vc_bind *bind, struct gprs_ns2_vc *nsvc, const struct osmo_sockaddr *remote) { struct priv_vc *priv = talloc_zero(bind, struct priv_vc); if (!priv) @@ -216,24 +179,22 @@ static struct priv_vc *ns2_driver_alloc_vc(struct gprs_ns2_vc_bind *bind, struct return priv; } -static int handle_nsip_read(struct osmo_fd *bfd) +static void handle_nsip_recvfrom(struct osmo_io_fd *iofd, int error, struct msgb *msg, + const struct osmo_sockaddr *saddr) { int rc = 0; - int error = 0; - struct gprs_ns2_vc_bind *bind = bfd->data; - struct osmo_sockaddr saddr; + struct gprs_ns2_vc_bind *bind = osmo_iofd_get_data(iofd); struct gprs_ns2_vc *nsvc; - struct msgb *msg = read_nsip_msg(bfd, &error, &saddr, bind); + struct msgb *reject; - if (!msg) - return -EINVAL; + msg->l2h = msgb_data(msg); /* check if a vc is available */ - nsvc = gprs_ns2_nsvc_by_sockaddr_bind(bind, &saddr); + nsvc = gprs_ns2_nsvc_by_sockaddr_bind(bind, saddr); if (!nsvc) { /* VC not found */ - rc = ns2_create_vc(bind, msg, &saddr, "newconnection", &reject, &nsvc); + rc = ns2_create_vc(bind, msg, saddr, "newconnection", &reject, &nsvc); switch (rc) { case NS2_CS_FOUND: break; @@ -243,10 +204,10 @@ static int handle_nsip_read(struct osmo_fd *bfd) goto out; case NS2_CS_REJECTED: /* nsip_sendmsg will free reject */ - rc = nsip_sendmsg(bind, reject, &saddr); + rc = nsip_sendmsg(bind, reject, saddr); goto out; case NS2_CS_CREATED: - ns2_driver_alloc_vc(bind, nsvc, &saddr); + ns2_driver_alloc_vc(bind, nsvc, saddr); /* only start the fsm for non SNS. SNS will take care of its own */ if (nsvc->nse->dialect != GPRS_NS2_DIALECT_SNS) ns2_vc_fsm_start(nsvc); @@ -254,29 +215,31 @@ static int handle_nsip_read(struct osmo_fd *bfd) } } - return ns2_recv_vc(nsvc, msg); + ns2_recv_vc(nsvc, msg); + return; out: msgb_free(msg); - return rc; } -static int handle_nsip_write(struct osmo_fd *bfd) -{ - /* FIXME: actually send the data here instead of nsip_sendmsg() */ - return -EIO; -} - -static int nsip_fd_cb(struct osmo_fd *bfd, unsigned int what) +static void handle_nsip_sendto(struct osmo_io_fd *iofd, int res, + struct msgb *msg, + const struct osmo_sockaddr *daddr) { - int rc = 0; + struct gprs_ns2_vc_bind *bind = osmo_iofd_get_data(iofd); + struct gprs_ns2_vc *nsvc; - if (what & OSMO_FD_READ) - rc = handle_nsip_read(bfd); - if (what & OSMO_FD_WRITE) - rc = handle_nsip_write(bfd); + nsvc = gprs_ns2_nsvc_by_sockaddr_bind(bind, daddr); + if (!nsvc) + return; - return rc; + if (OSMO_LIKELY(res >= 0)) { + RATE_CTR_INC_NS(nsvc, NS_CTR_PKTS_OUT); + RATE_CTR_ADD_NS(nsvc, NS_CTR_BYTES_OUT, res); + } else { + RATE_CTR_INC_NS(nsvc, NS_CTR_PKTS_OUT_DROP); + RATE_CTR_ADD_NS(nsvc, NS_CTR_BYTES_OUT_DROP, msgb_length(msg)); + } } /*! Find NS bind for a given socket address @@ -321,6 +284,11 @@ int gprs_ns2_ip_bind(struct gprs_ns2_inst *nsi, struct priv_bind *priv; int rc; + struct osmo_io_ops ioops = { + .sendto_cb = &handle_nsip_sendto, + .recvfrom_cb = &handle_nsip_recvfrom, + }; + if (local->u.sa.sa_family != AF_INET && local->u.sa.sa_family != AF_INET6) return -EINVAL; @@ -353,12 +321,11 @@ int gprs_ns2_ip_bind(struct gprs_ns2_inst *nsi, gprs_ns2_free_bind(bind); return -ENOMEM; } - priv->fd.cb = nsip_fd_cb; - priv->fd.data = bind; + priv->addr = *local; priv->dscp = dscp; - rc = osmo_sock_init_osa_ofd(&priv->fd, SOCK_DGRAM, IPPROTO_UDP, + rc = osmo_sock_init_osa(SOCK_DGRAM, IPPROTO_UDP, local, NULL, OSMO_SOCK_F_BIND | OSMO_SOCK_F_DSCP(priv->dscp)); if (rc < 0) { @@ -366,6 +333,11 @@ int gprs_ns2_ip_bind(struct gprs_ns2_inst *nsi, return rc; } + priv->iofd = osmo_iofd_setup(bind, rc, "NS bind", OSMO_IO_FD_MODE_RECVFROM_SENDTO, &ioops, bind); + osmo_iofd_register(priv->iofd, rc); + osmo_iofd_set_alloc_info(priv->iofd, 4096, 128); + osmo_iofd_set_txqueue_max_length(priv->iofd, nsi->txqueue_max_length); + /* IPv4: max fragmented payload can be (13 bit) * 8 byte => 65535. * IPv6: max payload can be 65535 (RFC 2460). * UDP header = 8 byte */ @@ -521,7 +493,7 @@ int gprs_ns2_ip_bind_set_dscp(struct gprs_ns2_vc_bind *bind, int dscp) if (dscp != priv->dscp) { priv->dscp = dscp; - rc = osmo_sock_set_dscp(priv->fd.fd, dscp); + rc = osmo_sock_set_dscp(osmo_iofd_get_fd(priv->iofd), dscp); if (rc < 0) { LOGBIND(bind, LOGL_ERROR, "Failed to set the DSCP to %u with ret(%d) errno(%d)\n", dscp, rc, errno); @@ -543,7 +515,7 @@ int gprs_ns2_ip_bind_set_priority(struct gprs_ns2_vc_bind *bind, uint8_t priorit if (priority != priv->priority) { priv->priority = priority; - rc = osmo_sock_set_priority(priv->fd.fd, priority); + rc = osmo_sock_set_priority(osmo_iofd_get_fd(priv->iofd), priority); if (rc < 0) { LOGBIND(bind, LOGL_ERROR, "Failed to set the priority to %u with ret(%d) errno(%d)\n", priority, rc, errno); @@ -603,6 +575,14 @@ struct gprs_ns2_vc_bind *ns2_ip_get_bind_by_index(struct gprs_ns2_inst *nsi, return NULL; } +void ns2_ip_set_txqueue_max_length(struct gprs_ns2_vc_bind *bind, unsigned int max_length) +{ + struct priv_bind *priv = bind->priv; + OSMO_ASSERT(gprs_ns2_is_ip_bind(bind)); + + osmo_iofd_set_txqueue_max_length(priv->iofd, max_length); +} + /*! set the signalling and data weight for this bind * \param[in] bind * \param[in] signalling the signalling weight diff --git a/src/gb/gprs_ns2_vc_fsm.c b/src/gb/gprs_ns2_vc_fsm.c index 03a355b8..9cd83c4f 100644 --- a/src/gb/gprs_ns2_vc_fsm.c +++ b/src/gb/gprs_ns2_vc_fsm.c @@ -58,8 +58,6 @@ struct gprs_ns2_vc_priv { bool initiator; bool initiate_block; bool initiate_reset; - /* if blocked by O&M/vty */ - bool om_blocked; /* if unitdata is forwarded to the user */ bool accept_unitdata; @@ -118,6 +116,7 @@ enum gprs_ns2_vc_event { GPRS_NS2_EV_REQ_OM_RESET, /* vty cmd: reset */ GPRS_NS2_EV_REQ_OM_BLOCK, /* vty cmd: block */ GPRS_NS2_EV_REQ_OM_UNBLOCK, /* vty cmd: unblock*/ + GPRS_NS2_EV_RX_BLOCK_FOREIGN, /* received a BLOCK over another NSVC */ }; static const struct value_string ns2_vc_event_names[] = { @@ -127,6 +126,7 @@ static const struct value_string ns2_vc_event_names[] = { { GPRS_NS2_EV_RX_UNBLOCK, "RX-UNBLOCK" }, { GPRS_NS2_EV_RX_UNBLOCK_ACK, "RX-UNBLOCK_ACK" }, { GPRS_NS2_EV_RX_BLOCK, "RX-BLOCK" }, + { GPRS_NS2_EV_RX_BLOCK_FOREIGN, "RX-BLOCK_FOREIGN" }, { GPRS_NS2_EV_RX_BLOCK_ACK, "RX-BLOCK_ACK" }, { GPRS_NS2_EV_RX_ALIVE, "RX-ALIVE" }, { GPRS_NS2_EV_RX_ALIVE_ACK, "RX-ALIVE_ACK" }, @@ -266,7 +266,7 @@ static void ns2_st_unconfigured(struct osmo_fsm_inst *fi, uint32_t event, void * struct gprs_ns2_inst *nsi = priv->nsvc->nse->nsi; priv->initiate_reset = priv->initiate_block = priv->initiator; - priv->om_blocked = false; + priv->nsvc->om_blocked = false; switch (event) { case GPRS_NS2_EV_REQ_START: @@ -343,12 +343,12 @@ static void ns2_st_blocked_onenter(struct osmo_fsm_inst *fi, uint32_t old_state) } ns2_nse_notify_unblocked(priv->nsvc, false); - if (priv->om_blocked) { + if (priv->nsvc->om_blocked) { /* we are already blocked after a RESET */ if (old_state == GPRS_NS2_ST_RESET) { osmo_timer_del(&fi->timer); } else { - ns2_tx_block(priv->nsvc, NS_CAUSE_OM_INTERVENTION); + ns2_tx_block(priv->nsvc, NS_CAUSE_OM_INTERVENTION, NULL); } } else if (priv->initiate_block) { ns2_tx_unblock(priv->nsvc); @@ -361,28 +361,35 @@ static void ns2_st_blocked(struct osmo_fsm_inst *fi, uint32_t event, void *data) { struct gprs_ns2_vc_priv *priv = fi->priv; - if (priv->om_blocked) { + if (priv->nsvc->om_blocked) { switch (event) { case GPRS_NS2_EV_RX_BLOCK_ACK: priv->accept_unitdata = false; osmo_timer_del(&fi->timer); break; case GPRS_NS2_EV_RX_BLOCK: + ns2_tx_block_ack(priv->nsvc, NULL); + /* fall through */ + case GPRS_NS2_EV_RX_BLOCK_FOREIGN: + /* the BLOCK ACK for foreign BLOCK PDUs (rx over another nsvc) will be sent + * from the receiving nsvc */ priv->accept_unitdata = false; - ns2_tx_block_ack(priv->nsvc); osmo_timer_del(&fi->timer); break; case GPRS_NS2_EV_RX_UNBLOCK: priv->accept_unitdata = false; - ns2_tx_block(priv->nsvc, NS_CAUSE_OM_INTERVENTION); + ns2_tx_block(priv->nsvc, NS_CAUSE_OM_INTERVENTION, NULL); osmo_timer_add(&fi->timer); break; } } else if (priv->initiate_block) { switch (event) { + case GPRS_NS2_EV_RX_BLOCK_FOREIGN: + /* the block ack will be sent by the rx NSVC */ + break; case GPRS_NS2_EV_RX_BLOCK: /* TODO: BLOCK is a UNBLOCK_NACK */ - ns2_tx_block_ack(priv->nsvc); + ns2_tx_block_ack(priv->nsvc, NULL); break; case GPRS_NS2_EV_RX_UNBLOCK: ns2_tx_unblock_ack(priv->nsvc); @@ -396,8 +403,11 @@ static void ns2_st_blocked(struct osmo_fsm_inst *fi, uint32_t event, void *data) } else { /* we are on the receiving end. The initiator who sent RESET is responsible to UNBLOCK! */ switch (event) { + case GPRS_NS2_EV_RX_BLOCK_FOREIGN: + /* the block ack will be sent by the rx NSVC */ + break; case GPRS_NS2_EV_RX_BLOCK: - ns2_tx_block_ack(priv->nsvc); + ns2_tx_block_ack(priv->nsvc, NULL); break; case GPRS_NS2_EV_RX_UNBLOCK: ns2_tx_unblock_ack(priv->nsvc); @@ -414,8 +424,10 @@ static void ns2_st_unblocked_on_enter(struct osmo_fsm_inst *fi, uint32_t old_sta struct gprs_ns2_vc *nsvc = priv->nsvc; struct gprs_ns2_nse *nse = nsvc->nse; - if (old_state != GPRS_NS2_ST_UNBLOCKED) + if (old_state != GPRS_NS2_ST_UNBLOCKED) { RATE_CTR_INC_NS(nsvc, NS_CTR_UNBLOCKED); + osmo_clock_gettime(CLOCK_MONOTONIC, &nsvc->ts_alive_change); + } priv->accept_unitdata = true; ns2_nse_notify_unblocked(nsvc, true); @@ -437,9 +449,13 @@ static void ns2_st_unblocked(struct osmo_fsm_inst *fi, uint32_t event, void *dat ns2_tx_unblock_ack(priv->nsvc); break; case GPRS_NS2_EV_RX_BLOCK: + ns2_tx_block_ack(priv->nsvc, NULL); + /* fall through */ + case GPRS_NS2_EV_RX_BLOCK_FOREIGN: + /* the BLOCK ACK for foreign BLOCK PDUs (rx over another nsvc) will be sent + * from the receiving nsvc */ priv->initiate_block = false; priv->accept_unitdata = false; - ns2_tx_block_ack(priv->nsvc); osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_BLOCKED, 0, 2); break; @@ -491,7 +507,8 @@ static const struct osmo_fsm_state ns2_vc_states[] = { }, [GPRS_NS2_ST_BLOCKED] = { .in_event_mask = S(GPRS_NS2_EV_RX_BLOCK) | S(GPRS_NS2_EV_RX_BLOCK_ACK) | - S(GPRS_NS2_EV_RX_UNBLOCK) | S(GPRS_NS2_EV_RX_UNBLOCK_ACK), + S(GPRS_NS2_EV_RX_UNBLOCK) | S(GPRS_NS2_EV_RX_UNBLOCK_ACK) | + S(GPRS_NS2_EV_RX_BLOCK_FOREIGN), .out_state_mask = S(GPRS_NS2_ST_RESET) | S(GPRS_NS2_ST_UNBLOCKED) | S(GPRS_NS2_ST_BLOCKED) | @@ -502,7 +519,7 @@ static const struct osmo_fsm_state ns2_vc_states[] = { }, [GPRS_NS2_ST_UNBLOCKED] = { .in_event_mask = S(GPRS_NS2_EV_RX_BLOCK) | S(GPRS_NS2_EV_RX_UNBLOCK_ACK) | - S(GPRS_NS2_EV_RX_UNBLOCK), + S(GPRS_NS2_EV_RX_UNBLOCK) | S(GPRS_NS2_EV_RX_BLOCK_FOREIGN), .out_state_mask = S(GPRS_NS2_ST_RESET) | S(GPRS_NS2_ST_RECOVERING) | S(GPRS_NS2_ST_BLOCKED) | S(GPRS_NS2_ST_UNCONFIGURED), @@ -544,7 +561,7 @@ static int ns2_vc_fsm_timer_cb(struct osmo_fsm_inst *fi) case GPRS_NS2_ST_BLOCKED: if (priv->initiate_block) { priv->N++; - if (priv->om_blocked) { + if (priv->nsvc->om_blocked) { if (priv->N <= nsi->timeout[NS_TOUT_TNS_BLOCK_RETRIES]) { osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_BLOCKED, nsi->timeout[NS_TOUT_TNS_BLOCK], 0); } else { @@ -677,7 +694,7 @@ static void ns2_vc_fsm_allstate_action(struct osmo_fsm_inst *fi, ns2_tx_status(priv->nsvc, NS_CAUSE_NSVC_BLOCKED, - 0, msg); + 0, msg, NULL); break; /* ALIVE can receive UNITDATA if the ALIVE_ACK is lost */ case GPRS_NS2_ST_RECOVERING: @@ -698,14 +715,14 @@ static void ns2_vc_fsm_allstate_action(struct osmo_fsm_inst *fi, case GPRS_NS2_EV_REQ_OM_BLOCK: /* vty cmd: block */ priv->initiate_block = true; - priv->om_blocked = true; + priv->nsvc->om_blocked = true; osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_BLOCKED, nsi->timeout[NS_TOUT_TNS_BLOCK], 0); break; case GPRS_NS2_EV_REQ_OM_UNBLOCK: /* vty cmd: unblock*/ - if (!priv->om_blocked) + if (!priv->nsvc->om_blocked) return; - priv->om_blocked = false; + priv->nsvc->om_blocked = false; if (fi->state == GPRS_NS2_ST_BLOCKED) osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_BLOCKED, nsi->timeout[NS_TOUT_TNS_BLOCK], 0); break; @@ -815,7 +832,7 @@ int ns2_vc_force_unconfigured(struct gprs_ns2_vc *nsvc) int ns2_vc_block(struct gprs_ns2_vc *nsvc) { struct gprs_ns2_vc_priv *priv = nsvc->fi->priv; - if (priv->om_blocked) + if (priv->nsvc->om_blocked) return -EALREADY; return osmo_fsm_inst_dispatch(nsvc->fi, GPRS_NS2_EV_REQ_OM_BLOCK, NULL); @@ -827,7 +844,7 @@ int ns2_vc_block(struct gprs_ns2_vc *nsvc) int ns2_vc_unblock(struct gprs_ns2_vc *nsvc) { struct gprs_ns2_vc_priv *priv = nsvc->fi->priv; - if (!priv->om_blocked) + if (!priv->nsvc->om_blocked) return -EALREADY; return osmo_fsm_inst_dispatch(nsvc->fi, GPRS_NS2_EV_REQ_OM_UNBLOCK, NULL); @@ -849,6 +866,7 @@ int ns2_vc_reset(struct gprs_ns2_vc *nsvc) int ns2_vc_rx(struct gprs_ns2_vc *nsvc, struct msgb *msg, struct tlv_parsed *tp) { struct gprs_ns_hdr *nsh = (struct gprs_ns_hdr *) msg->l2h; + struct gprs_ns2_vc *target_nsvc = nsvc; struct osmo_fsm_inst *fi = nsvc->fi; int rc = 0; uint8_t cause; @@ -859,8 +877,9 @@ int ns2_vc_rx(struct gprs_ns2_vc *nsvc, struct msgb *msg, struct tlv_parsed *tp) /* TODO: handle BLOCK/UNBLOCK/ALIVE with different VCI */ if (ns2_validate(nsvc, nsh->pdu_type, msg, tp, &cause)) { + /* don't answer on a STATUS with a STATUS */ if (nsh->pdu_type != NS_PDUT_STATUS) { - rc = ns2_tx_status(nsvc, cause, 0, msg); + rc = ns2_tx_status(nsvc, cause, 0, msg, NULL); goto out; } } @@ -872,7 +891,7 @@ int ns2_vc_rx(struct gprs_ns2_vc *nsvc, struct msgb *msg, struct tlv_parsed *tp) if (nsh->pdu_type == NS_PDUT_RESET) ns2_tx_reset_ack(nsvc); - LOG_NS_SIGNAL(nsvc, "Rx", nsh->pdu_type, LOGL_ERROR, " with wrong NSEI=%05u. Ignoring PDU.\n", nsei); + LOG_NS_SIGNAL(nsvc, "Rx", nsh->pdu_type, LOGL_ERROR, " with wrong NSEI (exp: %05u, got %05u). Ignoring PDU.\n", nsvc->nse->nsei, nsei); goto out; } } @@ -881,11 +900,32 @@ int ns2_vc_rx(struct gprs_ns2_vc *nsvc, struct msgb *msg, struct tlv_parsed *tp) nsvci = tlvp_val16be(tp, NS_IE_VCI); if (nsvci != nsvc->nsvci) { /* 48.016 § 7.3.1 send RESET_ACK to wrong NSVCI + ignore */ - if (nsh->pdu_type == NS_PDUT_RESET) + if (nsh->pdu_type == NS_PDUT_RESET) { ns2_tx_reset_ack(nsvc); + LOG_NS_SIGNAL(nsvc, "Rx", nsh->pdu_type, LOGL_ERROR, " with wrong NSVCI (exp: %05u, got %05u). Ignoring PDU.\n", nsvc->nsvci, nsvci); + goto out; + } else if (nsh->pdu_type == NS_PDUT_BLOCK || nsh->pdu_type == NS_PDUT_STATUS) { + /* this is a PDU received over a NSVC and reports a status/block for another NSVC */ + target_nsvc = gprs_ns2_nsvc_by_nsvci(nsvc->nse->nsi, nsvci); + if (!target_nsvc) { + LOGPFSML(fi, LOGL_ERROR, "Received a %s PDU for unknown NSVC (NSVCI %d)\n", + get_value_string(gprs_ns_pdu_strings, nsh->pdu_type), nsvci); + if (nsh->pdu_type == NS_PDUT_BLOCK) + ns2_tx_status(nsvc, NS_CAUSE_NSVC_UNKNOWN, 0, msg, &nsvci); + goto out; + } - LOG_NS_SIGNAL(nsvc, "Rx", nsh->pdu_type, LOGL_ERROR, " with wrong NSVCI=%05u. Ignoring PDU.\n", nsvci); - goto out; + if (target_nsvc->nse != nsvc->nse) { + LOGPFSML(fi, LOGL_ERROR, "Received a %s PDU for a NSVC (NSVCI %d) but it belongs to a different NSE!\n", + get_value_string(gprs_ns_pdu_strings, nsh->pdu_type), nsvci); + goto out; + } + + /* the status/block will be passed to the nsvc/target nsvc in the switch */ + } else { + LOG_NS_SIGNAL(nsvc, "Rx", nsh->pdu_type, LOGL_ERROR, " with wrong NSVCI=%05u. Ignoring PDU.\n", nsvci); + goto out; + } } } @@ -897,7 +937,12 @@ int ns2_vc_rx(struct gprs_ns2_vc *nsvc, struct msgb *msg, struct tlv_parsed *tp) osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_RX_RESET_ACK, tp); break; case NS_PDUT_BLOCK: - osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_RX_BLOCK, tp); + if (target_nsvc != nsvc) { + osmo_fsm_inst_dispatch(target_nsvc->fi, GPRS_NS2_EV_RX_BLOCK_FOREIGN, tp); + ns2_tx_block_ack(nsvc, &nsvci); + } else { + osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_RX_BLOCK, tp); + } break; case NS_PDUT_BLOCK_ACK: osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_RX_BLOCK_ACK, tp); @@ -919,7 +964,7 @@ int ns2_vc_rx(struct gprs_ns2_vc *nsvc, struct msgb *msg, struct tlv_parsed *tp) osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_RX_UNITDATA, msg); return 0; case NS_PDUT_STATUS: - osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_RX_STATUS, tp); + osmo_fsm_inst_dispatch(target_nsvc->fi, GPRS_NS2_EV_RX_STATUS, tp); break; default: LOGPFSML(fi, LOGL_ERROR, "NSEI=%u Rx unknown NS PDU type %s\n", nsvc->nse->nsei, diff --git a/src/gb/gprs_ns2_vty.c b/src/gb/gprs_ns2_vty.c index 52ce2073..32de49d4 100644 --- a/src/gb/gprs_ns2_vty.c +++ b/src/gb/gprs_ns2_vty.c @@ -36,6 +36,7 @@ #include <osmocom/core/fsm.h> #include <osmocom/core/linuxlist.h> #include <osmocom/core/msgb.h> +#include <osmocom/core/osmo_io.h> #include <osmocom/core/rate_ctr.h> #include <osmocom/core/select.h> #include <osmocom/core/talloc.h> @@ -90,18 +91,18 @@ struct vty_nse_bind { }; /* TODO: this should into osmo timer */ -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" }, - { 8, "tsns-size-retries" }, - { 9, "tsns-config-retries" }, - {10, "tsns-procedures-retries" }, +const struct value_string gprs_ns_timer_strs[] = { + { NS_TOUT_TNS_BLOCK, TNS_BLOCK_STR }, + { NS_TOUT_TNS_BLOCK_RETRIES, TNS_BLOCK_RETRIES_STR }, + { NS_TOUT_TNS_RESET, TNS_RESET_STR }, + { NS_TOUT_TNS_RESET_RETRIES, TNS_RESET_RETRIES_STR }, + { NS_TOUT_TNS_TEST, TNS_TEST_STR }, + { NS_TOUT_TNS_ALIVE, TNS_ALIVE_STR }, + { NS_TOUT_TNS_ALIVE_RETRIES, TNS_ALIVE_RETRIES_STR }, + { NS_TOUT_TSNS_PROV, TSNS_PROV_STR }, + { NS_TOUT_TSNS_SIZE_RETRIES, TSNS_SIZE_RETRIES_STR }, + { NS_TOUT_TSNS_CONFIG_RETRIES, TSNS_CONFIG_RETRIES_STR }, + { NS_TOUT_TSNS_PROCEDURES_RETRIES, TSNS_PROCEDURES_RETRIES_STR }, { 0, NULL } }; @@ -595,6 +596,9 @@ static int config_write_ns(struct vty *vty) get_value_string(gprs_ns_timer_strs, i), vty_nsi->timeout[i], VTY_NEWLINE); + if (vty_nsi->txqueue_max_length != NS_DEFAULT_TXQUEUE_MAX_LENGTH) + vty_out(vty, " txqueue-max-length %u%s", vty_nsi->txqueue_max_length, VTY_NEWLINE); + ret = config_write_ns_bind(vty); if (ret) return ret; @@ -1707,6 +1711,27 @@ DEFUN(cfg_no_ns_ip_sns_default_bind, cfg_no_ns_ip_sns_default_bind_cmd, return CMD_WARNING; } +DEFUN(cfg_ns_txqueue_max_length, cfg_ns_txqueue_max_length_cmd, + "txqueue-max-length <1-4096>", + "Set the maximum length of the txqueue (for UDP)\n" + "Maximum length of the txqueue\n") +{ + struct gprs_ns2_vc_bind *bind; + uint32_t max_length = atoi(argv[0]); + vty_nsi->txqueue_max_length = max_length; + + + llist_for_each_entry(bind, &vty_nsi->binding, list) { + if (!gprs_ns2_is_ip_bind(bind)) + continue; + + ns2_ip_set_txqueue_max_length(bind, max_length); + } + + + return CMD_SUCCESS; +} + DEFUN(cfg_ns_nse_ip_sns_bind, cfg_ns_nse_ip_sns_bind_cmd, "ip-sns-bind BINDID", "IP SNS binds\n" @@ -1875,16 +1900,24 @@ DEFUN(cfg_no_ns_nse_ip_sns_bind, cfg_no_ns_nse_ip_sns_bind_cmd, void ns2_vty_dump_nsvc(struct vty *vty, struct gprs_ns2_vc *nsvc, bool stats) { if (nsvc->nsvci_is_valid) - vty_out(vty, " NSVCI %05u: %s %s %s%s", nsvc->nsvci, + vty_out(vty, " NSVCI %05u: %s %s %s %s %ssince ", nsvc->nsvci, osmo_fsm_inst_state_name(nsvc->fi), nsvc->persistent ? "PERSIST" : "DYNAMIC", - gprs_ns2_ll_str(nsvc), VTY_NEWLINE); + gprs_ns2_ll_str(nsvc), + ns2_vc_is_unblocked(nsvc) ? "ALIVE" : "DEAD", + nsvc->om_blocked ? "(blocked by O&M/vty) " : + !ns2_vc_is_unblocked(nsvc) ? "(cause: remote) " : ""); else - vty_out(vty, " %s %s sig_weight=%u data_weight=%u %s%s", + vty_out(vty, " %s %s sig_weight=%u data_weight=%u %s %s %ssince ", osmo_fsm_inst_state_name(nsvc->fi), nsvc->persistent ? "PERSIST" : "DYNAMIC", nsvc->sig_weight, nsvc->data_weight, - gprs_ns2_ll_str(nsvc), VTY_NEWLINE); + gprs_ns2_ll_str(nsvc), + ns2_vc_is_unblocked(nsvc) ? "ALIVE" : "DEAD", + !ns2_vc_is_unblocked(nsvc) ? "(cause: remote) " : ""); + + vty_out_uptime(vty, &nsvc->ts_alive_change); + vty_out_newline(vty); if (stats) { vty_out_rate_ctr_group(vty, " ", nsvc->ctrg); @@ -1900,8 +1933,10 @@ static void dump_nse(struct vty *vty, const struct gprs_ns2_nse *nse, bool stats if (persistent_only && !nse->persistent) return; - vty_out(vty, "NSEI %05u: %s, %s%s", nse->nsei, gprs_ns2_lltype_str(nse->ll), - nse->alive ? "ALIVE" : "DEAD", VTY_NEWLINE); + vty_out(vty, "NSEI %05u: %s, %s since ", nse->nsei, gprs_ns2_lltype_str(nse->ll), + nse->alive ? "ALIVE" : "DEAD"); + vty_out_uptime(vty, &nse->ts_alive_change); + vty_out_newline(vty); ns2_sns_dump_vty(vty, " ", nse, stats); llist_for_each_entry(nsvc, &nse->nsvc, list) { @@ -2062,6 +2097,31 @@ DEFUN_HIDDEN(nsvc_force_unconf, nsvc_force_unconf_cmd, return CMD_SUCCESS; } +DEFUN(nse_restart_sns, nse_restart_sns_cmd, + "nse <0-65535> restart-sns", + "NSE specific commands\n" + "NS Entity ID (NSEI)\n" + "Restart SNS procedure\n") +{ + struct gprs_ns2_inst *nsi = vty_nsi; + struct gprs_ns2_nse *nse; + + uint16_t id = atoi(argv[0]); + nse = gprs_ns2_nse_by_nsei(nsi, id); + if (!nse) { + vty_out(vty, "Could not find NSE for NSEI %u%s", id, VTY_NEWLINE); + return CMD_WARNING; + } + + if (nse->dialect != GPRS_NS2_DIALECT_SNS) { + vty_out(vty, "Given NSEI %u doesn't use IP-SNS%s", id, VTY_NEWLINE); + return CMD_WARNING; + } + + gprs_ns2_free_nsvcs(nse); + return CMD_SUCCESS; +} + DEFUN(nsvc_block, nsvc_block_cmd, "nsvc <0-65535> (block|unblock|reset)", "NS Virtual Connection\n" @@ -2229,6 +2289,7 @@ int gprs_ns2_vty_init_reduced(struct gprs_ns2_inst *nsi) install_lib_element(ENABLE_NODE, &nsvc_force_unconf_cmd); install_lib_element(ENABLE_NODE, &nsvc_block_cmd); + install_lib_element(ENABLE_NODE, &nse_restart_sns_cmd); install_lib_element(CFG_LOG_NODE, &logging_fltr_nse_cmd); install_lib_element(CFG_LOG_NODE, &logging_fltr_nsvc_cmd); @@ -2256,6 +2317,8 @@ int gprs_ns2_vty_init(struct gprs_ns2_inst *nsi) install_lib_element(L_NS_NODE, &cfg_ns_ip_sns_default_bind_cmd); install_lib_element(L_NS_NODE, &cfg_no_ns_ip_sns_default_bind_cmd); + install_lib_element(L_NS_NODE, &cfg_ns_txqueue_max_length_cmd); + install_node(&ns_bind_node, NULL); install_lib_element(L_NS_BIND_NODE, &cfg_ns_bind_listen_cmd); install_lib_element(L_NS_BIND_NODE, &cfg_no_ns_bind_listen_cmd); diff --git a/src/gb/libosmogb.map b/src/gb/libosmogb.map index ff5b34a2..e5a5c8fd 100644 --- a/src/gb/libosmogb.map +++ b/src/gb/libosmogb.map @@ -34,6 +34,7 @@ bssgp_nacc_cause_strs; bssgp_parse_cell_id; bssgp_parse_rim_pdu; bssgp_parse_rim_ri; +bssgp_parse_rim_ra; bssgp_ran_inf_app_id_strs; bssgp_rim_routing_info_discr_strs; bssgp_rim_ri_name_buf; @@ -56,6 +57,7 @@ bssgp_tx_resume; bssgp_tx_resume_ack; bssgp_tx_resume_nack; bssgp_tx_rim; +bssgp_tx_rim_encoded; bssgp_tx_simple_bvci; bssgp_tx_status; bssgp_tx_suspend; @@ -85,6 +87,7 @@ bssgp2_enc_fc_bvc; bssgp2_enc_fc_bvc_ack; bssgp2_enc_fc_ms; bssgp2_enc_fc_ms_ack; +bssgp2_enc_flush_ll; bssgp2_enc_status; bssgp_bvc_fsm_alloc_sig_bss; diff --git a/src/gsm/Makefile.am b/src/gsm/Makefile.am index b336239a..e6b687d2 100644 --- a/src/gsm/Makefile.am +++ b/src/gsm/Makefile.am @@ -1,10 +1,10 @@ # This is _NOT_ the library release version, it's an API version. # Please read chapter "Library interface versions" of the libtool documentation # before making any modifications: https://www.gnu.org/software/libtool/manual/html_node/Versioning.html -LIBVERSION=16:0:0 +LIBVERSION=20:0:0 -AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_builddir)/include $(TALLOC_CFLAGS) -AM_CFLAGS = -Wall ${GCC_FVISIBILITY_HIDDEN} +AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_builddir)/include +AM_CFLAGS = -Wall $(TALLOC_CFLAGS) if ENABLE_PSEUDOTALLOC AM_CPPFLAGS += -I$(top_srcdir)/src/pseudotalloc @@ -15,6 +15,8 @@ noinst_HEADERS = milenage/aes.h milenage/aes_i.h milenage/aes_wrap.h \ milenage/common.h milenage/crypto.h milenage/includes.h \ milenage/milenage.h +noinst_HEADERS += tuak/KeccakP-1600-3gpp.h tuak/tuak.h + noinst_LTLIBRARIES = libgsmint.la lib_LTLIBRARIES = libosmogsm.la @@ -25,25 +27,29 @@ libgsmint_la_SOURCES = a5.c rxlev_stat.c tlv_parser.c comp128.c comp128v23.c \ gsm48_ie.c gsm0808.c sysinfo.c \ gprs_cipher_core.c gprs_rlc.c gsm0480.c abis_nm.c gsm0502.c \ gsm0411_utils.c gsm0411_smc.c gsm0411_smr.c gsm0414.c \ - lapd_core.c lapdm.c kasumi.c gsm29205.c gsm_04_08_gprs.c \ - auth_core.c auth_comp128v1.c auth_comp128v23.c auth_xor.c \ + lapdm.c kasumi.c gsm29205.c gsm_04_08_gprs.c \ + auth_core.c auth_comp128v1.c auth_comp128v23.c auth_xor.c auth_xor_2g.c \ auth_milenage.c milenage/aes-encblock.c gea.c \ milenage/aes-internal.c milenage/aes-internal-enc.c \ milenage/milenage.c gan.c ipa.c gsm0341.c apn.c \ - gsup.c gsup_sms.c gprs_gea.c gsm0503_conv.c oap.c gsm0808_utils.c \ + tuak/KeccakP-1600-3gpp.c tuak/tuak.c auth_tuak.c \ + gprs_gea.c gsm0503_conv.c oap.c gsm0808_utils.c \ gsm23003.c gsm23236.c mncc.c bts_features.c oap_client.c \ - gsm29118.c gsm48_rest_octets.c cbsp.c gsm48049.c i460_mux.c \ - gad.c bsslap.c bssmap_le.c kdf.c + gsm29118.c gsm48_rest_octets.c cbsp.c gsm48049.c \ + gad.c bsslap.c bssmap_le.c kdf.c iuup.c gsm44021.c gsm44068.c rlp.c +if !EMBEDDED +libgsmint_la_SOURCES += gsup.c gsup_sms.c +endif # !EMBEDDED libgsmint_la_LDFLAGS = -no-undefined -libgsmint_la_LIBADD = $(top_builddir)/src/libosmocore.la +libgsmint_la_LIBADD = $(top_builddir)/src/core/libosmocore.la $(top_builddir)/src/isdn/libosmoisdn.la libosmogsm_la_SOURCES = libosmogsm_la_LDFLAGS = $(LTLDFLAGS_OSMOGSM) -version-info $(LIBVERSION) -no-undefined libosmogsm_la_LIBADD = libgsmint.la $(TALLOC_LIBS) if ENABLE_GNUTLS -AM_CPPFLAGS += $(LIBGNUTLS_CFLAGS) +AM_CFLAGS += $(LIBGNUTLS_CFLAGS) libosmogsm_la_LIBADD += $(LIBGNUTLS_LIBS) else noinst_HEADERS += kdf/sha1.h kdf/sha256.h kdf/common.h kdf/sha1_i.h kdf/sha256_i.h @@ -52,6 +58,7 @@ libgsmint_la_SOURCES += kdf/sha256.c kdf/sha256-internal.c \ endif EXTRA_DIST = libosmogsm.map +EXTRA_libosmogsm_la_DEPENDENCIES = libosmogsm.map # Convolutional codes generation gsm0503_conv.c: $(top_srcdir)/utils/conv_gen.py $(top_srcdir)/utils/conv_codes_gsm.py diff --git a/src/gsm/a5.c b/src/gsm/a5.c index 223d3ad8..d223cb76 100644 --- a/src/gsm/a5.c +++ b/src/gsm/a5.c @@ -14,10 +14,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ /*! \addtogroup a5 diff --git a/src/gsm/abis_nm.c b/src/gsm/abis_nm.c index 38cd1948..c4060e01 100644 --- a/src/gsm/abis_nm.c +++ b/src/gsm/abis_nm.c @@ -707,6 +707,100 @@ static const enum abis_nm_chan_comb chcomb4pchan[] = { /* FIXME: bounds check */ }; +const struct value_string abis_nm_ipacc_freq_band_desc[] = { + { NM_IPAC_F_FREQ_BAND_PGSM, "P-GSM" }, + { NM_IPAC_F_FREQ_BAND_EGSM, "E-GSM" }, + { NM_IPAC_F_FREQ_BAND_RGSM, "R-GSM" }, + { NM_IPAC_F_FREQ_BAND_DCS, "DCS-1800" }, + { NM_IPAC_F_FREQ_BAND_PCS, "PCS-1900" }, + { NM_IPAC_F_FREQ_BAND_850, "850" }, + { NM_IPAC_F_FREQ_BAND_480, "480" }, + { NM_IPAC_F_FREQ_BAND_450, "450" }, + { 0, NULL } +}; + +const struct value_string abis_nm_ipacc_ciph_algo_desc[] = { + { NM_IPAC_F_CIPH_ALGO_A51, "A5/1" }, + { NM_IPAC_F_CIPH_ALGO_A52, "A5/2" }, + { NM_IPAC_F_CIPH_ALGO_A53, "A5/3" }, + { NM_IPAC_F_CIPH_ALGO_A54, "A5/4" }, + { NM_IPAC_F_CIPH_ALGO_A55, "A5/5" }, + { NM_IPAC_F_CIPH_ALGO_A56, "A5/6" }, + { NM_IPAC_F_CIPH_ALGO_A57, "A5/7" }, + { NM_IPAC_F_CIPH_ALGO_A58, "A5/8" }, + { 0, NULL } +}; + +const struct value_string abis_nm_ipacc_chant_desc[] = { + { NM_IPAC_F_CHANT_TCHF, "TCH/F" }, + { NM_IPAC_F_CHANT_TCHH, "TCH/H" }, + { NM_IPAC_F_CHANT_SDCCH8, "SDCCH/8" }, + { NM_IPAC_F_CHANT_BCCH, "BCCH" }, + { NM_IPAC_F_CHANT_BCCH_SDCCH4, "BCCH+SDCCH/4" }, + { NM_IPAC_F_CHANT_BCH, "BCH" }, + { NM_IPAC_F_CHANT_BCCH_SDCCH4_CBCH, "BCCH+SDCCH/4+CBCH" }, + { NM_IPAC_F_CHANT_SDCCH8_CBCH, "SDCCH/8+CBCH" }, + { NM_IPAC_F_CHANT_PDCHF, "PDCH/F" }, + { NM_IPAC_F_CHANT_TCHF_PDCHF, "TCH/F or PDCH/F" }, + { NM_IPAC_F_CHANT_TCHH_PDCHH, "TCH/H+PDCH/H" }, + { NM_IPAC_F_CHANT_TCHF_TCHH, "TCH/F or TCH/H" }, + { 0, NULL } +}; + +const struct value_string abis_nm_ipacc_chanm_desc[] = { + { NM_IPAC_F_CHANM_SPEECH_FS, "FR1/FS" }, + { NM_IPAC_F_CHANM_SPEECH_EFS, "FR2/EFS" }, + { NM_IPAC_F_CHANM_SPEECH_AFS, "FR3/AFS" }, + { NM_IPAC_F_CHANM_SPEECH_HS, "HR1/HS" }, + { NM_IPAC_F_CHANM_SPEECH_AHS, "HR3/AHS" }, + { NM_IPAC_F_CHANM_CSD_NT_4k8, "CSD NT 4.8 kbit/s" }, + { NM_IPAC_F_CHANM_CSD_NT_9k6, "CSD NT 9.6 kbit/s" }, + { NM_IPAC_F_CHANM_CSD_NT_14k4, "CSD NT 14.4 kbit/s" }, + { NM_IPAC_F_CHANM_CSD_T_1200_75, "CSD T 1200/75 kbit/s" }, + { NM_IPAC_F_CHANM_CSD_T_600, "CSD T 600 kbit/s" }, + { NM_IPAC_F_CHANM_CSD_T_1k2, "CSD T 1k2 kbit/s" }, + { NM_IPAC_F_CHANM_CSD_T_2k4, "CSD T 2.4 kbit/s" }, + { NM_IPAC_F_CHANM_CSD_T_4k8, "CSD T 4.8 kbit/s" }, + { NM_IPAC_F_CHANM_CSD_T_9k6, "CSD T 9.6 kbit/s" }, + { NM_IPAC_F_CHANM_CSD_T_14k4, "CSD T 14.4 kbit/s" }, + { 0, NULL } +}; + +const struct value_string abis_nm_ipacc_gprs_coding_desc[] = { + { NM_IPAC_F_GPRS_CODING_CS1, "CS1" }, + { NM_IPAC_F_GPRS_CODING_CS2, "CS2" }, + { NM_IPAC_F_GPRS_CODING_CS3, "CS3" }, + { NM_IPAC_F_GPRS_CODING_CS4, "CS4" }, + { NM_IPAC_F_GPRS_CODING_MCS1, "MCS1" }, + { NM_IPAC_F_GPRS_CODING_MCS2, "MCS2" }, + { NM_IPAC_F_GPRS_CODING_MCS3, "MCS3" }, + { NM_IPAC_F_GPRS_CODING_MCS4, "MCS4" }, + { NM_IPAC_F_GPRS_CODING_MCS5, "MCS5" }, + { NM_IPAC_F_GPRS_CODING_MCS6, "MCS6" }, + { NM_IPAC_F_GPRS_CODING_MCS7, "MCS7" }, + { NM_IPAC_F_GPRS_CODING_MCS8, "MCS8" }, + { NM_IPAC_F_GPRS_CODING_MCS9, "MCS9" }, + { 0, NULL } +}; + +const struct value_string abis_nm_ipacc_rtp_feat_desc[] = { + { NM_IPAC_F_RTP_FEAT_COMPR_CONTROL, "Compression Control" }, + { NM_IPAC_F_RTP_FEAT_IR_8k, "Intermediate Rate 8 kbit/s" }, + { NM_IPAC_F_RTP_FEAT_IR_16k, "Intermediate Rate 16 kbit/s" }, + { NM_IPAC_F_RTP_FEAT_IR_32k, "Intermediate Rate 32 kbit/s" }, + { NM_IPAC_F_RTP_FEAT_IR_64k, "Intermediate Rate 64 kbit/s" }, + { NM_IPAC_F_RTP_FEAT_MULTIPLEX_RTP, "RTP Multiplex" }, + { NM_IPAC_F_RTP_FEAT_MULTIPLEX_SRTP, "SRTP Multiplex" }, + { 0, NULL } +}; + +const struct value_string abis_nm_ipacc_rsl_feat_desc[] = { + { NM_IPAC_F_RSL_FEAT_PHYSICAL_CONTEXT, "Physical Context" }, + { NM_IPAC_F_RSL_FEAT_DYN_PDCH_ACT, "Dynamic PDCH Activation" }, + { NM_IPAC_F_RSL_FEAT_RTP_PT2, "RTP Payload Type 2" }, + { 0, NULL } +}; + /*! Pack 3GPP TS 12.21 § 8.8.2 Failure Event Report into msgb */ struct msgb *abis_nm_fail_evt_rep(enum abis_nm_event_type t, enum abis_nm_severity s, diff --git a/src/gsm/apn.c b/src/gsm/apn.c index a7074eab..4800702c 100644 --- a/src/gsm/apn.c +++ b/src/gsm/apn.c @@ -24,8 +24,8 @@ #include <stdint.h> #include <stdlib.h> #include <string.h> -#include <talloc.h> +#include <osmocom/core/talloc.h> #include <osmocom/gsm/apn.h> #define APN_OI_GPRS_FMT "mnc%03u.mcc%03u.gprs" diff --git a/src/gsm/auth_comp128v1.c b/src/gsm/auth_comp128v1.c index 493ebfd0..ded3f8a6 100644 --- a/src/gsm/auth_comp128v1.c +++ b/src/gsm/auth_comp128v1.c @@ -17,10 +17,6 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ #include <osmocom/crypt/auth.h> @@ -31,9 +27,10 @@ */ static int c128v1_gen_vec(struct osmo_auth_vector *vec, - struct osmo_sub_auth_data *aud, + struct osmo_sub_auth_data2 *aud, const uint8_t *_rand) { + OSMO_ASSERT(aud->algo == OSMO_AUTH_ALG_COMP128v1); comp128v1(aud->u.gsm.ki, _rand, vec->sres, vec->kc); vec->auth_types = OSMO_AUTH_TYPE_GSM; diff --git a/src/gsm/auth_comp128v23.c b/src/gsm/auth_comp128v23.c index 279d2b74..f942bc02 100644 --- a/src/gsm/auth_comp128v23.c +++ b/src/gsm/auth_comp128v23.c @@ -19,10 +19,6 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ #include <osmocom/crypt/auth.h> @@ -33,9 +29,10 @@ */ static int c128v2_gen_vec(struct osmo_auth_vector *vec, - struct osmo_sub_auth_data *aud, + struct osmo_sub_auth_data2 *aud, const uint8_t *_rand) { + OSMO_ASSERT(aud->algo == OSMO_AUTH_ALG_COMP128v2); comp128v2(aud->u.gsm.ki, _rand, vec->sres, vec->kc); vec->auth_types = OSMO_AUTH_TYPE_GSM; @@ -50,9 +47,10 @@ static struct osmo_auth_impl c128v2_alg = { }; static int c128v3_gen_vec(struct osmo_auth_vector *vec, - struct osmo_sub_auth_data *aud, + struct osmo_sub_auth_data2 *aud, const uint8_t *_rand) { + OSMO_ASSERT(aud->algo == OSMO_AUTH_ALG_COMP128v3); comp128v3(aud->u.gsm.ki, _rand, vec->sres, vec->kc); vec->auth_types = OSMO_AUTH_TYPE_GSM; diff --git a/src/gsm/auth_core.c b/src/gsm/auth_core.c index 9e750a01..6972cb77 100644 --- a/src/gsm/auth_core.c +++ b/src/gsm/auth_core.c @@ -1,4 +1,4 @@ -/* (C) 2010-2012 by Harald Welte <laforge@gnumonks.org> +/* (C) 2010-2023 by Harald Welte <laforge@gnumonks.org> * * All Rights Reserved * @@ -14,10 +14,6 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ #include "config.h" @@ -40,6 +36,35 @@ static LLIST_HEAD(osmo_auths); +/* generate auth_data2 from auth_data (for legacy API/ABI compatibility */ +static int auth_data2auth_data2(struct osmo_sub_auth_data2 *out, const struct osmo_sub_auth_data *in) +{ + out->type = in->type; + out->algo = in->algo; + switch (in->type) { + case OSMO_AUTH_TYPE_NONE: + return 0; + case OSMO_AUTH_TYPE_GSM: + memcpy(out->u.gsm.ki, in->u.gsm.ki, sizeof(out->u.gsm.ki)); + break; + case OSMO_AUTH_TYPE_UMTS: + memcpy(out->u.umts.opc, in->u.umts.opc, sizeof(in->u.umts.opc)); + out->u.umts.opc_len = sizeof(in->u.umts.opc); + memcpy(out->u.umts.k, in->u.umts.k, sizeof(in->u.umts.k)); + out->u.umts.k_len = sizeof(in->u.umts.k); + memcpy(out->u.umts.amf, in->u.umts.amf, sizeof(out->u.umts.amf)); + out->u.umts.sqn = in->u.umts.sqn; + out->u.umts.opc_is_op = in->u.umts.opc_is_op; + out->u.umts.ind_bitlen = in->u.umts.ind_bitlen; + out->u.umts.ind = in->u.umts.ind; + out->u.umts.sqn_ms = in->u.umts.sqn_ms; + break; + default: + return -EINVAL; + } + return 0; +} + static struct osmo_auth_impl *selected_auths[_OSMO_AUTH_ALG_NUM]; /*! Register an authentication algorithm implementation with the core @@ -142,6 +167,42 @@ int osmo_auth_3g_from_2g(struct osmo_auth_vector *vec) } /*! Generate authentication vector + * \param[out] vec Generated authentication vector. See below! + * \param[in] aud Subscriber-specific key material + * \param[in] _rand Random challenge to be used + * \returns 0 on success, negative error on failure + * + * This function performs the core cryptographic function of the AUC, + * computing authentication triples/quintuples based on the permanent + * subscriber data and a random value. The result is what is forwarded + * by the AUC via HLR and VLR to the MSC which will then be able to + * invoke authentication with the MS. + * + * Contrary to the older osmo_auth_gen_vec(), the caller must specify + * the desired RES length in the vec->res_len field prior to calling + * this function. The requested length must match the capabilities of + * the chosen algorithm (e.g. 4/8 for MILENAGE). + */ +int osmo_auth_gen_vec2(struct osmo_auth_vector *vec, + struct osmo_sub_auth_data2 *aud, + const uint8_t *_rand) +{ + struct osmo_auth_impl *impl = selected_auths[aud->algo]; + int rc; + + if (!impl) + return -ENOENT; + + rc = impl->gen_vec(vec, aud, _rand); + if (rc < 0) + return rc; + + memcpy(vec->rand, _rand, sizeof(vec->rand)); + + return 0; +} + +/*! Generate authentication vector * \param[out] vec Generated authentication vector * \param[in] aud Subscriber-specific key material * \param[in] _rand Random challenge to be used @@ -157,13 +218,58 @@ int osmo_auth_gen_vec(struct osmo_auth_vector *vec, struct osmo_sub_auth_data *aud, const uint8_t *_rand) { + struct osmo_sub_auth_data2 aud2; + int rc; + + if (aud->type == OSMO_AUTH_TYPE_UMTS) { + /* old API callers are not expected to initialize this struct field, + * and always expect an 8-byte RES value */ + vec->res_len = 8; + } + + rc = auth_data2auth_data2(&aud2, aud); + if (rc < 0) + return rc; + + rc = osmo_auth_gen_vec2(vec, &aud2, _rand); + if (aud->type == OSMO_AUTH_TYPE_UMTS) + aud->u.umts.sqn = aud2.u.umts.sqn; + + return rc; +} + +/*! Generate authentication vector and re-sync sequence + * \param[out] vec Generated authentication vector. See below! + * \param[in] aud Subscriber-specific key material + * \param[in] auts AUTS value sent by the SIM/MS + * \param[in] rand_auts RAND value sent by the SIM/MS + * \param[in] _rand Random challenge to be used to generate vector + * \returns 0 on success, negative error on failure + * + * This function performs a special variant of the core cryptographic + * function of the AUC: computing authentication triples/quintuples + * based on the permanent subscriber data, a random value as well as the + * AUTS and RAND values returned by the SIM/MS. This special variant is + * needed if the sequence numbers between MS and AUC have for some + * reason become different. + * + * Contrary to the older osmo_auth_gen_vec_auts(), the caller must specify + * the desired RES length in the vec->res_len field prior to calling + * this function. The requested length must match the capabilities of + * the chosen algorithm (e.g. 4/8 for MILENAGE). + */ +int osmo_auth_gen_vec_auts2(struct osmo_auth_vector *vec, + struct osmo_sub_auth_data2 *aud, + const uint8_t *auts, const uint8_t *rand_auts, + const uint8_t *_rand) +{ struct osmo_auth_impl *impl = selected_auths[aud->algo]; int rc; - if (!impl) + if (!impl || !impl->gen_vec_auts) return -ENOENT; - rc = impl->gen_vec(vec, aud, _rand); + rc = impl->gen_vec_auts(vec, aud, auts, rand_auts, _rand); if (rc < 0) return rc; @@ -192,19 +298,26 @@ int osmo_auth_gen_vec_auts(struct osmo_auth_vector *vec, const uint8_t *auts, const uint8_t *rand_auts, const uint8_t *_rand) { - struct osmo_auth_impl *impl = selected_auths[aud->algo]; + struct osmo_sub_auth_data2 aud2; int rc; - if (!impl || !impl->gen_vec_auts) - return -ENOENT; + if (aud->type == OSMO_AUTH_TYPE_UMTS) { + /* old API callers are not expected to initialize this struct field, + * and always expect an 8-byte RES value */ + vec->res_len = 8; + } - rc = impl->gen_vec_auts(vec, aud, auts, rand_auts, _rand); + rc = auth_data2auth_data2(&aud2, aud); if (rc < 0) return rc; - memcpy(vec->rand, _rand, sizeof(vec->rand)); + rc = osmo_auth_gen_vec_auts2(vec, &aud2, auts, rand_auts, _rand); + if (aud->type == OSMO_AUTH_TYPE_UMTS) { + aud->u.umts.sqn = aud2.u.umts.sqn; + aud->u.umts.sqn_ms = aud2.u.umts.sqn_ms; + } - return 0; + return rc; } static const struct value_string auth_alg_vals[] = { @@ -212,8 +325,10 @@ static const struct value_string auth_alg_vals[] = { { OSMO_AUTH_ALG_COMP128v1, "COMP128v1" }, { OSMO_AUTH_ALG_COMP128v2, "COMP128v2" }, { OSMO_AUTH_ALG_COMP128v3, "COMP128v3" }, - { OSMO_AUTH_ALG_XOR, "XOR" }, + { OSMO_AUTH_ALG_XOR_3G, "XOR-3G" }, { OSMO_AUTH_ALG_MILENAGE, "MILENAGE" }, + { OSMO_AUTH_ALG_XOR_2G, "XOR-2G" }, + { OSMO_AUTH_ALG_TUAK, "TUAK" }, { 0, NULL } }; @@ -249,4 +364,33 @@ void osmo_auth_c3(uint8_t kc[], const uint8_t ck[], const uint8_t ik[]) kc[i] = ck[i] ^ ck[i + 8] ^ ik[i] ^ ik[i + 8]; } +/*! Derive GSM SRES from UMTS [X]RES (auth function c2 from 3GPP TS 33.103 Section 6.8.1.2 + * \param[out] sres GSM SRES value, 4 byte target buffer + * \param[in] res UMTS XRES, 4..16 bytes input buffer + * \param[in] res_len length of res parameter (in bytes) + * \param[in] sres_deriv_func SRES derivation function (1 or 2, see 3GPP TS 55.205 Section 4 + */ +void osmo_auth_c2(uint8_t sres[4], const uint8_t *res, size_t res_len, uint8_t sres_deriv_func) +{ + uint8_t xres[16]; + + OSMO_ASSERT(sres_deriv_func == 1 || sres_deriv_func == 2); + OSMO_ASSERT(res_len <= sizeof(xres)); + + memcpy(xres, res, res_len); + + /* zero-pad the end, if XRES is < 16 bytes */ + if (res_len < sizeof(xres)) + memset(xres+res_len, 0, sizeof(xres)-res_len); + + if (sres_deriv_func == 1) { + /* SRES derivation function #1 */ + for (unsigned int i = 0; i < 4; i++) + sres[i] = xres[i] ^ xres[4+i] ^ xres[8+i] ^ xres[12+i]; + } else { + /* SRES derivation function #2 */ + memcpy(sres, xres, 4); + } +} + /*! @} */ diff --git a/src/gsm/auth_milenage.c b/src/gsm/auth_milenage.c index 95891000..2bd05a6d 100644 --- a/src/gsm/auth_milenage.c +++ b/src/gsm/auth_milenage.c @@ -17,12 +17,9 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ +#include <errno.h> #include <osmocom/crypt/auth.h> #include <osmocom/core/bits.h> #include "milenage/common.h" @@ -32,7 +29,7 @@ * @{ */ -static const uint8_t *gen_opc_if_needed(const struct osmo_sub_auth_data *aud, uint8_t *gen_opc) +static const uint8_t *gen_opc_if_needed(const struct osmo_sub_auth_data2 *aud, uint8_t *gen_opc) { int rc; @@ -47,7 +44,7 @@ static const uint8_t *gen_opc_if_needed(const struct osmo_sub_auth_data *aud, ui } static int milenage_gen_vec(struct osmo_auth_vector *vec, - struct osmo_sub_auth_data *aud, + struct osmo_sub_auth_data2 *aud, const uint8_t *_rand) { size_t res_len = sizeof(vec->res); @@ -57,7 +54,15 @@ static int milenage_gen_vec(struct osmo_auth_vector *vec, uint8_t sqn[6]; uint64_t ind_mask; uint64_t seq_1; - int rc; + + OSMO_ASSERT(aud->algo == OSMO_AUTH_ALG_MILENAGE); + + if (aud->u.umts.k_len != 16) + return -EINVAL; + if (aud->u.umts.opc_len != 16) + return -EINVAL; + if (vec->res_len != 4 && vec->res_len != 8) + return -EINVAL; opc = gen_opc_if_needed(aud, gen_opc); if (!opc) @@ -131,10 +136,9 @@ static int milenage_gen_vec(struct osmo_auth_vector *vec, milenage_generate(opc, aud->u.umts.amf, aud->u.umts.k, sqn, _rand, vec->autn, vec->ik, vec->ck, vec->res, &res_len); - vec->res_len = res_len; - rc = gsm_milenage(opc, aud->u.umts.k, _rand, vec->sres, vec->kc); - if (rc < 0) - return rc; + + osmo_auth_c3(vec->kc, vec->ck, vec->ik); + osmo_auth_c2(vec->sres, vec->res, vec->res_len, 1); vec->auth_types = OSMO_AUTH_TYPE_UMTS | OSMO_AUTH_TYPE_GSM; @@ -145,7 +149,7 @@ static int milenage_gen_vec(struct osmo_auth_vector *vec, } static int milenage_gen_vec_auts(struct osmo_auth_vector *vec, - struct osmo_sub_auth_data *aud, + struct osmo_sub_auth_data2 *aud, const uint8_t *auts, const uint8_t *rand_auts, const uint8_t *_rand) { @@ -154,6 +158,13 @@ static int milenage_gen_vec_auts(struct osmo_auth_vector *vec, const uint8_t *opc; int rc; + OSMO_ASSERT(aud->algo == OSMO_AUTH_ALG_MILENAGE); + + if (aud->u.umts.k_len != 16) + return -EINVAL; + if (aud->u.umts.opc_len != 16) + return -EINVAL; + opc = gen_opc_if_needed(aud, gen_opc); rc = milenage_auts(opc, aud->u.umts.k, rand_auts, auts, sqn_out); diff --git a/src/gsm/auth_tuak.c b/src/gsm/auth_tuak.c new file mode 100644 index 00000000..05fbf691 --- /dev/null +++ b/src/gsm/auth_tuak.c @@ -0,0 +1,207 @@ +/*! \file auth_tuak.c + * GSM/GPRS/3G authentication core infrastructure */ +/* + * (C) 2023 by Harald Welte <laforge@osmocom.org> + * + * All Rights Reserved + * + * SPDX-License-Identifier: GPL-2.0+ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +/* NOTE: TUAK offers a lot of size variability in terms of size of length of MAC_A, MAC_S, + * but this is not used within 3GPP. The different sizes of Kc and RES are handled via + * osmo_sub_auth_data2. */ + +#include <errno.h> +#include <osmocom/crypt/auth.h> +#include <osmocom/core/bits.h> +#include "tuak/tuak.h" + +/*! \addtogroup auth + * @{ + */ + +static const uint8_t *gen_opc_if_needed(const struct osmo_sub_auth_data2 *aud, uint8_t *gen_opc) +{ + int rc; + + /* Check if we only know OP and compute OPC if required */ + if (aud->type == OSMO_AUTH_TYPE_UMTS && aud->u.umts.opc_is_op) { + rc = tuak_opc_gen(gen_opc, aud->u.umts.k, aud->u.umts.k_len, aud->u.umts.opc); + if (rc < 0) + return NULL; + return gen_opc; + } + + return aud->u.umts.opc; +} + +static int tuak_gen_vec(struct osmo_auth_vector *vec, + struct osmo_sub_auth_data2 *aud, + const uint8_t *_rand) +{ + size_t res_len = vec->res_len; + uint64_t next_sqn; + uint8_t gen_opc[32]; + const uint8_t *opc; + uint8_t sqn[6]; + uint64_t ind_mask; + uint64_t seq_1; + + switch (vec->res_len) { + case 4: + case 8: + case 16: + break; + default: + return -EINVAL; + } + + OSMO_ASSERT(aud->algo == OSMO_AUTH_ALG_TUAK); + + if (aud->u.umts.k_len != 16 && aud->u.umts.k_len != 32) + return -EINVAL; + if (aud->u.umts.opc_len != 32) + return -EINVAL; + + opc = gen_opc_if_needed(aud, gen_opc); + if (!opc) + return -1; + + /* Determine next SQN, according to 3GPP TS 33.102: + * SQN consists of SEQ and a lower significant part of IND bits: + * + * |----------SEQ------------| + * |------------------------SQN-----------| + * |-----IND----| + * + * The IND part is used as "slots": e.g. a given HLR client will always + * get the same IND part, called ind here, with incrementing SEQ. In + * the USIM, each IND slot enforces that its SEQ are used in ascending + * order -- as long as that constraint is satisfied, the SQN may jump + * forwards and backwards. For example, for ind_bitlen == 5, asking the + * USIM for SQN = 32, 64, 33 is allowed, because 32 and 64 are + * SEQ || (ind == 0), and though 33 is below 64, it is ind == 1 and + * allowed. Not allowed would be 32, 96, 64, because 64 would go + * backwards after 96, both being ind == 0. + * + * From the last used SQN, we want to increment SEQ + 1, and then pick + * the matching IND part. + * + * IND size is suggested in TS 33.102 as 5 bits. SQN is 48 bits long. + * If ind_bitlen is passed too large here, the algorithms will break + * down. But at which point should we return an error? A sane limit + * seems to be ind_bitlen == 10, but to protect against failure, + * limiting ind_bitlen to 28 is enough, 28 being the number of bits + * suggested for the delta in 33.102, which is discussed to still + * require 2^15 > 32000 authentications to wrap the SQN back to the + * start. + * + * Note that if a caller with ind == 1 generates N vectors, the SQN + * stored after this will reflect SEQ + N. If then another caller with + * ind == 2 generates another N vectors, this will then use SEQ + N + * onwards and end up with SEQ + N + N. In other words, most of each + * SEQ's IND slots will remain unused. When looking at SQN being 48 + * bits wide, after dropping ind_bitlen (say 5) from it, we will still + * have a sequence range of 2^43 = 8.8e12, eight trillion sequences, + * which is large enough to not bother further. With the maximum + * ind_bitlen of 28 enforced below, we still get more than 1 million + * sequences, which is also sufficiently large. + * + * An ind_bitlen of zero may be passed from legacy callers that are not + * aware of the IND extension. For these, below algorithm works out as + * before, simply incrementing SQN by 1. + * + * This is also a mechanism for tools like the osmo-auc-gen to directly + * request a given SQN to be used. With ind_bitlen == 0 the caller can + * be sure that this code will increment SQN by exactly one before + * generating a tuple, thus a caller would simply pass + * { .ind_bitlen = 0, .ind = 0, .sqn = (desired_sqn - 1) } + */ + + if (aud->u.umts.ind_bitlen > OSMO_MILENAGE_IND_BITLEN_MAX) + return -2; + + seq_1 = 1LL << aud->u.umts.ind_bitlen; + ind_mask = ~(seq_1 - 1); + + /* the ind index must not affect the SEQ part */ + if (aud->u.umts.ind >= seq_1) + return -3; + + /* keep the incremented SQN local until gsm_milenage() succeeded. */ + next_sqn = ((aud->u.umts.sqn + seq_1) & ind_mask) + aud->u.umts.ind; + + osmo_store64be_ext(next_sqn, sqn, 6); + + tuak_generate(opc, aud->u.umts.amf, aud->u.umts.k, aud->u.umts.k_len, + sqn, _rand, vec->autn, vec->ik, vec->ck, vec->res, &res_len); + + /* generate the GSM Kc + SRES values using C2 + C3 functions */ + osmo_auth_c3(vec->kc, vec->ck, vec->ik); + osmo_auth_c2(vec->sres, vec->res, vec->res_len, 1); + + vec->auth_types = OSMO_AUTH_TYPE_UMTS | OSMO_AUTH_TYPE_GSM; + + /* for storage in the caller's AUC database */ + aud->u.umts.sqn = next_sqn; + + return 0; +} + +static int tuak_gen_vec_auts(struct osmo_auth_vector *vec, + struct osmo_sub_auth_data2 *aud, + const uint8_t *auts, const uint8_t *rand_auts, + const uint8_t *_rand) +{ + uint8_t sqn_out[6]; + uint8_t gen_opc[32]; + const uint8_t *opc; + int rc; + + OSMO_ASSERT(aud->algo == OSMO_AUTH_ALG_TUAK); + + if (aud->u.umts.k_len != 16 && aud->u.umts.k_len != 32) + return -EINVAL; + if (aud->u.umts.opc_len != 32) + return -EINVAL; + + opc = gen_opc_if_needed(aud, gen_opc); + + rc = tuak_auts(opc, aud->u.umts.k, sizeof(aud->u.umts.k), rand_auts, auts, sqn_out); + if (rc < 0) + return rc; + + aud->u.umts.sqn_ms = osmo_load64be_ext(sqn_out, 6) >> 16; + /* Update our "largest used SQN" from the USIM -- milenage_gen_vec() + * below will increment SQN. */ + aud->u.umts.sqn = aud->u.umts.sqn_ms; + + return tuak_gen_vec(vec, aud, _rand); +} + +static struct osmo_auth_impl tuak_alg = { + .algo = OSMO_AUTH_ALG_TUAK, + .name = "TUAK (libosmogsm built-in)", + .priority = 1000, + .gen_vec = &tuak_gen_vec, + .gen_vec_auts = &tuak_gen_vec_auts, +}; + +static __attribute__((constructor)) void on_dso_load_tuak(void) +{ + osmo_auth_register(&tuak_alg); +} + +/*! @} */ diff --git a/src/gsm/auth_xor.c b/src/gsm/auth_xor.c index 36e006e6..a506a03d 100644 --- a/src/gsm/auth_xor.c +++ b/src/gsm/auth_xor.c @@ -20,10 +20,6 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ #include <string.h> @@ -47,19 +43,23 @@ static void xor(uint8_t *out, const uint8_t *a, const uint8_t *b, size_t len) /* 3GPP TS 34.108, section 8.1.2.1 */ static int xor_gen_vec(struct osmo_auth_vector *vec, - struct osmo_sub_auth_data *aud, + struct osmo_sub_auth_data2 *aud, const uint8_t *_rand) { uint8_t xdout[16], cdout[8]; uint8_t ak[6], xmac[8]; int i; + OSMO_ASSERT(aud->algo == OSMO_AUTH_ALG_XOR_3G); + /* Step 1: xdout = (ki or k) ^ rand */ if (aud->type == OSMO_AUTH_TYPE_GSM) xor(xdout, aud->u.gsm.ki, _rand, sizeof(xdout)); - else if (aud->type == OSMO_AUTH_TYPE_UMTS) + else if (aud->type == OSMO_AUTH_TYPE_UMTS) { + if (aud->u.umts.k_len != 16) + return -EINVAL; xor(xdout, aud->u.umts.k, _rand, sizeof(xdout)); - else + } else return -ENOTSUP; /** @@ -129,7 +129,7 @@ static int xor_gen_vec(struct osmo_auth_vector *vec, /* 3GPP TS 34.108, section 8.1.2.2 */ static int xor_gen_vec_auts(struct osmo_auth_vector *vec, - struct osmo_sub_auth_data *aud, + struct osmo_sub_auth_data2 *aud, const uint8_t *auts, const uint8_t *rand_auts, const uint8_t *_rand) @@ -138,12 +138,16 @@ static int xor_gen_vec_auts(struct osmo_auth_vector *vec, uint8_t ak[6], xmac[8]; uint8_t sqnms[6]; + OSMO_ASSERT(aud->algo == OSMO_AUTH_ALG_XOR_3G); + /* Step 1: xdout = (ki or k) ^ rand */ if (aud->type == OSMO_AUTH_TYPE_GSM) xor(xdout, aud->u.gsm.ki, _rand, sizeof(xdout)); - else if (aud->type == OSMO_AUTH_TYPE_UMTS) + else if (aud->type == OSMO_AUTH_TYPE_UMTS) { + if (aud->u.umts.k_len != 16) + return -EINVAL; xor(xdout, aud->u.umts.k, _rand, sizeof(xdout)); - else + } else return -ENOTSUP; /* Step 2: ak = xdout[2-8] */ @@ -172,8 +176,8 @@ static int xor_gen_vec_auts(struct osmo_auth_vector *vec, } static struct osmo_auth_impl xor_alg = { - .algo = OSMO_AUTH_ALG_XOR, - .name = "XOR (libosmogsm built-in)", + .algo = OSMO_AUTH_ALG_XOR_3G, + .name = "XOR-3G (libosmogsm built-in)", .priority = 1000, .gen_vec = &xor_gen_vec, .gen_vec_auts = &xor_gen_vec_auts, diff --git a/src/gsm/auth_xor_2g.c b/src/gsm/auth_xor_2g.c new file mode 100644 index 00000000..367c79d4 --- /dev/null +++ b/src/gsm/auth_xor_2g.c @@ -0,0 +1,81 @@ +/*! \file auth_xor.c + * GSM XOR-2G algorithm as specified in Annex 4 (A.4.1.2) of 3GPP TS 51.010-1. + * This is implemented by typical GSM MS tester */ + +/* + * (C) 2023 by Harald Welte <laforge@gnumonks.org> + * + * All Rights Reserved + * + * Author: Daniel Willmann <dwillmann@sysmocom.de> + * + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <string.h> +#include <stdint.h> +#include <errno.h> + +#include <osmocom/crypt/auth.h> + +/*! \addtogroup auth + * @{ + */ + +static void xor(uint8_t *out, const uint8_t *a, const uint8_t *b, size_t len) +{ + size_t i; + + for (i = 0; i < len; i++) + out[i] = a[i] ^ b[i]; +} + +/* GSM XOR-2G algorithm as specified in Annex 4 (A.4.1.2) of 3GPP TS 51.010-1. */ +static int xor2g_gen_vec(struct osmo_auth_vector *vec, + struct osmo_sub_auth_data2 *aud, + const uint8_t *_rand) +{ + uint8_t res1[16]; + + OSMO_ASSERT(aud->algo == OSMO_AUTH_ALG_XOR_2G); + + if (aud->type != OSMO_AUTH_TYPE_GSM) + return -ENOTSUP; + + /* Step 1: XOR to the challenge RAND, a predefined number Ki, having the same bit length (128 bits) as + * RAND. */ + xor(res1, aud->u.gsm.ki, _rand, sizeof(res1)); + + /* Step 2: The most significant 32 bits of RES1 form SRES. */ + memcpy(vec->sres, res1, 4); + /* The next 64 bits of RES1 form Kc */ + memcpy(vec->kc, res1+4, 8); + + vec->auth_types = OSMO_AUTH_TYPE_GSM; + return 0; +} + +static struct osmo_auth_impl xor2g_alg = { + .algo = OSMO_AUTH_ALG_XOR_2G, + .name = "XOR-2G (libosmogsm built-in)", + .priority = 1000, + .gen_vec = &xor2g_gen_vec, +}; + +static __attribute__((constructor)) void on_dso_load_xor(void) +{ + osmo_auth_register(&xor2g_alg); +} + +/*! @} */ diff --git a/src/gsm/bsslap.c b/src/gsm/bsslap.c index 7886da68..70ded13f 100644 --- a/src/gsm/bsslap.c +++ b/src/gsm/bsslap.c @@ -17,10 +17,6 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ #include <errno.h> @@ -221,7 +217,7 @@ int osmo_bsslap_dec(struct bsslap_pdu *pdu, int ies_len; struct tlv_parsed tp; - *pdu = (struct bsslap_pdu){}; + memset(pdu, 0x00, sizeof(*pdu)); if (err) *err = NULL; diff --git a/src/gsm/bssmap_le.c b/src/gsm/bssmap_le.c index 79546532..1ee45517 100644 --- a/src/gsm/bssmap_le.c +++ b/src/gsm/bssmap_le.c @@ -17,10 +17,6 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ #include <string.h> @@ -184,7 +180,7 @@ int osmo_bssmap_le_ie_dec_location_type(struct bssmap_le_location_type *lt, struct osmo_bssmap_le_err **err, void *err_ctx, const uint8_t *elem, uint8_t len) { - *lt = (struct bssmap_le_location_type){}; + memset(lt, 0x00, sizeof(*lt)); if (!elem || len < 1) DEC_ERR(-EINVAL, msgt, iei, LCS_CAUSE_UNSPECIFIED, "zero length"); @@ -265,6 +261,57 @@ static int osmo_bssmap_le_ie_dec_lcs_client_type(enum bssmap_le_lcs_client_type } } +/*! Encode full BSSMAP-LE LCS Priority IE, including IEI tag and length. + * \param[inout] msg Message buffer to append to. + * \param[in] priority Value to enconde. + * \returns length of bytes written to the msgb. + */ +static uint8_t osmo_bssmap_le_ie_enc_lcs_priority(struct msgb *msg, uint8_t priority) +{ + OSMO_ASSERT(msg); + msgb_put_u8(msg, BSSMAP_LE_IEI_LCS_PRIORITY); + /* length */ + msgb_put_u8(msg, 1); + msgb_put_u8(msg, priority); + return 3; +} + +static int osmo_bssmap_le_ie_dec_lcs_priority(uint8_t *priority, + enum bssmap_le_msgt msgt, enum bssmap_le_iei iei, + struct osmo_bssmap_le_err **err, void *err_ctx, + const uint8_t *elem, uint8_t len) +{ + if (!elem || len != 1) + DEC_ERR(-EINVAL, msgt, iei, LCS_CAUSE_UNSPECIFIED, "unexpected length"); + + *priority = elem[0]; + return 0; +} + +/*! Encode full BSSMAP-LE LCS QoS IE, including IEI tag and length. + * \param[inout] msg Message buffer to append to. + * \param[in] priority Value to enconde. + * \returns length of bytes written to the msgb. + */ +static uint8_t osmo_bssmap_le_ie_enc_lcs_qos(struct msgb *msg, const struct osmo_bssmap_le_lcs_qos *qos) +{ + OSMO_ASSERT(msg); + msgb_tlv_put(msg, BSSMAP_LE_IEI_LCS_QoS, sizeof(*qos), (const uint8_t *)qos); + return 2 + sizeof(*qos); +} + +static int osmo_bssmap_le_ie_dec_lcs_qos(struct osmo_bssmap_le_lcs_qos *qos, + enum bssmap_le_msgt msgt, enum bssmap_le_iei iei, + struct osmo_bssmap_le_err **err, void *err_ctx, + const uint8_t *elem, uint8_t len) +{ + if (!elem || len != sizeof(*qos)) + DEC_ERR(-EINVAL, msgt, iei, LCS_CAUSE_UNSPECIFIED, "unexpected length"); + + memcpy(qos, elem, len); + return 0; +} + /*! Encode the value part of 3GPP TS 49.031 10.13 LCS Cause, without IEI and len. * Identically used in 3GPP TS 48.008 3.2.2.66. Usage example: * @@ -301,7 +348,7 @@ int osmo_lcs_cause_dec(struct lcs_cause_ie *lcs_cause, struct osmo_bssmap_le_err **err, void *err_ctx, const uint8_t *data, uint8_t len) { - *lcs_cause = (struct lcs_cause_ie){}; + memset(lcs_cause, 0x00, sizeof(*lcs_cause)); if (!data || len < 1) DEC_ERR(-EINVAL, msgt, iei, LCS_CAUSE_UNSPECIFIED, "zero length"); @@ -476,6 +523,12 @@ static int osmo_bssmap_le_enc_perform_loc_req(struct msgb *msg, const struct bss if (params->lcs_client_type_present) osmo_bssmap_le_ie_enc_lcs_client_type(msg, params->lcs_client_type); + if (params->more_items && params->lcs_priority_present) + osmo_bssmap_le_ie_enc_lcs_priority(msg, params->lcs_priority); + + if (params->more_items && params->lcs_qos_present) + osmo_bssmap_le_ie_enc_lcs_qos(msg, ¶ms->lcs_qos); + if (params->apdu_present) { int rc = osmo_bssmap_le_ie_enc_apdu(msg, ¶ms->apdu); if (rc < 0) @@ -505,7 +558,7 @@ static int osmo_bssmap_le_dec_perform_loc_req(struct bssmap_le_perform_loc_req * struct osmo_bssmap_le_err **err, void *err_ctx, const struct tlv_parsed *tp) { - *params = (struct bssmap_le_perform_loc_req){}; + memset(params, 0x00, sizeof(*params)); DEC_IE_MANDATORY(msgt, BSSMAP_LE_IEI_LOCATION_TYPE, osmo_bssmap_le_ie_dec_location_type, ¶ms->location_type); @@ -513,11 +566,18 @@ static int osmo_bssmap_le_dec_perform_loc_req(struct bssmap_le_perform_loc_req * ¶ms->cell_id); DEC_IE_OPTIONAL_FLAG(msgt, BSSMAP_LE_IEI_LCS_CLIENT_TYPE, osmo_bssmap_le_ie_dec_lcs_client_type, ¶ms->lcs_client_type, params->lcs_client_type_present); + DEC_IE_OPTIONAL_FLAG(msgt, BSSMAP_LE_IEI_LCS_PRIORITY, osmo_bssmap_le_ie_dec_lcs_priority, + ¶ms->lcs_priority, params->lcs_priority_present); + DEC_IE_OPTIONAL_FLAG(msgt, BSSMAP_LE_IEI_LCS_QoS, osmo_bssmap_le_ie_dec_lcs_qos, + ¶ms->lcs_qos, params->lcs_qos_present); DEC_IE_OPTIONAL_FLAG(msgt, BSSMAP_LE_IEI_APDU, osmo_bssmap_le_ie_dec_apdu, ¶ms->apdu, params->apdu_present); DEC_IE_OPTIONAL(msgt, BSSMAP_LE_IEI_IMSI, osmo_bssmap_le_ie_dec_imsi, ¶ms->imsi); DEC_IE_OPTIONAL(msgt, BSSMAP_LE_IEI_IMEI, osmo_bssmap_le_ie_dec_imei, ¶ms->imei); + if (params->lcs_priority_present || params->lcs_qos_present) + params->more_items = true; + return 0; } @@ -546,7 +606,7 @@ static int osmo_bssmap_le_dec_perform_loc_resp(struct bssmap_le_perform_loc_resp struct osmo_bssmap_le_err **err, void *err_ctx, const struct tlv_parsed *tp) { - *params = (struct bssmap_le_perform_loc_resp){}; + memset(params, 0x00, sizeof(*params)); DEC_IE_OPTIONAL_FLAG(msgt, BSSMAP_LE_IEI_GEO_LOCATION, osmo_bssmap_le_ie_dec_gad, ¶ms->location_estimate, params->location_estimate_present); @@ -570,7 +630,7 @@ static int osmo_bssmap_le_dec_perform_loc_abort(struct lcs_cause_ie *params, struct osmo_bssmap_le_err **err, void *err_ctx, const struct tlv_parsed *tp) { - *params = (struct lcs_cause_ie){}; + memset(params, 0x00, sizeof(*params)); DEC_IE_MANDATORY(msgt, BSSMAP_LE_IEI_LCS_CAUSE, osmo_lcs_cause_dec, params); return 0; @@ -587,7 +647,8 @@ static int osmo_bssmap_le_dec_conn_oriented_info(struct bssmap_le_conn_oriented_ struct osmo_bssmap_le_err **err, void *err_ctx, const struct tlv_parsed *tp) { - *params = (struct bssmap_le_conn_oriented_info){}; + memset(params, 0x00, sizeof(*params)); + DEC_IE_MANDATORY(msgt, BSSMAP_LE_IEI_APDU, osmo_bssmap_le_ie_dec_apdu, ¶ms->apdu); return 0; } @@ -651,7 +712,7 @@ static int osmo_bssmap_le_dec(struct bssmap_le_pdu *pdu, int ies_len; struct tlv_parsed tp; - *pdu = (struct bssmap_le_pdu){}; + memset(pdu, 0x00, sizeof(*pdu)); if (len < 1) DEC_ERR(-EINVAL, -1, -1, LCS_CAUSE_UNSPECIFIED, "zero length"); @@ -738,7 +799,7 @@ int osmo_bssap_le_dec(struct bssap_le_pdu *pdu, struct osmo_bssap_le_err **err, return RC; \ } while(0) - *pdu = (struct bssap_le_pdu){}; + memset(pdu, 0x00, sizeof(*pdu)); h = msgb_l2(msg); if (!h) diff --git a/src/gsm/bts_features.c b/src/gsm/bts_features.c index 614758b4..b6cd82ec 100644 --- a/src/gsm/bts_features.c +++ b/src/gsm/bts_features.c @@ -14,13 +14,9 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, - * MA 02110-1301, USA. */ +#include <osmocom/core/utils.h> #include <osmocom/gsm/bts_features.h> const struct value_string osmo_bts_features_descs[] = { @@ -48,9 +44,15 @@ const struct value_string osmo_bts_features_descs[] = { { BTS_FEAT_BCCH_POWER_RED, "BCCH carrier power reduction mode" }, { BTS_FEAT_DYN_TS_SDCCH8, "Dynamic Timeslot configuration as SDCCH8" }, { BTS_FEAT_ACCH_TEMP_OVP, "FACCH/SACCH Temporary overpower" }, + { BTS_FEAT_OSMUX, "Osmux (Osmocom RTP multiplexing)" }, + { BTS_FEAT_VBS, "Voice Broadcast Service" }, + { BTS_FEAT_VGCS, "Voice Group Call Service" }, { 0, NULL } }; +/* Ensure that all BTS_FEAT_* entries are present in osmo_bts_features_descs[] */ +osmo_static_assert(ARRAY_SIZE(osmo_bts_features_descs) == _NUM_BTS_FEAT + 1, _bts_features_descs); + /*! return description string of a BTS feature (osmo_bts_features_descs). * To get the plain feature name, use osmo_bts_features_name() instead. */ const char *osmo_bts_feature_name(enum osmo_bts_features feature) @@ -82,5 +84,12 @@ const struct value_string osmo_bts_features_names[] = { { BTS_FEAT_ABIS_OSMO_PCU, "ABIS_OSMO_PCU" }, { BTS_FEAT_BCCH_POWER_RED, "BCCH_PWR_RED" }, { BTS_FEAT_DYN_TS_SDCCH8, "DYN_TS_SDCCH8" }, + { BTS_FEAT_ACCH_TEMP_OVP, "ACCH_TEMP_OVP" }, + { BTS_FEAT_OSMUX, "OSMUX" }, + { BTS_FEAT_VBS, "VBS" }, + { BTS_FEAT_VGCS, "VGCS" }, {} }; + +/* Ensure that all BTS_FEAT_* entries are present in osmo_bts_features_names[] */ +osmo_static_assert(ARRAY_SIZE(osmo_bts_features_names) == _NUM_BTS_FEAT + 1, _bts_features_names); diff --git a/src/gsm/cbsp.c b/src/gsm/cbsp.c index 353f53f4..a5e58f4c 100644 --- a/src/gsm/cbsp.c +++ b/src/gsm/cbsp.c @@ -15,10 +15,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "config.h" @@ -34,7 +30,7 @@ #include <osmocom/gsm/cbsp.h> #include <osmocom/gsm/gsm0808_utils.h> -const __thread char *osmo_cbsp_errstr; +__thread const char *osmo_cbsp_errstr; struct msgb *osmo_cbsp_msgb_alloc(void *ctx, const char *name) { @@ -541,8 +537,8 @@ static int cbsp_decode_fail_list(struct llist_head *fl, void *ctx, struct osmo_cbsp_fail_ent *ent = talloc_zero(ctx, struct osmo_cbsp_fail_ent); unsigned int len_remain = len - (cur - buf); OSMO_ASSERT(ent); - ent->id_discr = cur[0]; - rc = gsm0808_decode_cell_id_u(&ent->cell_id, ent->id_discr, cur+1, len_remain-1); + ent->id_discr = *cur++; + rc = gsm0808_decode_cell_id_u(&ent->cell_id, ent->id_discr, cur, len_remain-1); if (rc < 0) { osmo_cbsp_errstr = "fail list: error decoding cell_id_union"; return rc; @@ -643,6 +639,7 @@ static int cbsp_dec_write_repl(struct osmo_cbsp_write_replace *out, const struct struct msgb *in, void *ctx) { unsigned int i; + int rc; /* check for mandatory IEs */ if (!TLVP_PRESENT(tp, CBSP_IEI_MSG_ID) || @@ -660,8 +657,10 @@ static int cbsp_dec_write_repl(struct osmo_cbsp_write_replace *out, const struct } INIT_LLIST_HEAD(&out->cell_list.list); - cbsp_decode_cell_list(&out->cell_list, ctx, TLVP_VAL(tp, CBSP_IEI_CELL_LIST), - TLVP_LEN(tp, CBSP_IEI_CELL_LIST)); + rc = cbsp_decode_cell_list(&out->cell_list, ctx, TLVP_VAL(tp, CBSP_IEI_CELL_LIST), + TLVP_LEN(tp, CBSP_IEI_CELL_LIST)); + if (rc < 0) + return rc; if (TLVP_PRESENT(tp, CBSP_IEI_CHANNEL_IND)) { uint8_t num_of_pages; @@ -722,6 +721,8 @@ static int cbsp_dec_write_repl(struct osmo_cbsp_write_replace *out, const struct static int cbsp_dec_write_repl_compl(struct osmo_cbsp_write_replace_complete *out, const struct tlv_parsed *tp, struct msgb *in, void *ctx) { + int rc; + if (!TLVP_PRES_LEN(tp, CBSP_IEI_MSG_ID, 2) || !TLVP_PRES_LEN(tp, CBSP_IEI_NEW_SERIAL_NR, 2)) { osmo_cbsp_errstr = "missing/short mandatory IE"; @@ -737,14 +738,18 @@ static int cbsp_dec_write_repl_compl(struct osmo_cbsp_write_replace_complete *ou INIT_LLIST_HEAD(&out->num_compl_list.list); if (TLVP_PRES_LEN(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST, 7)) { - cbsp_decode_num_compl_list(&out->num_compl_list, ctx, - TLVP_VAL(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST), - TLVP_LEN(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST)); + rc = cbsp_decode_num_compl_list(&out->num_compl_list, ctx, + TLVP_VAL(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST), + TLVP_LEN(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST)); + if (rc < 0) + return rc; } INIT_LLIST_HEAD(&out->cell_list.list); - cbsp_decode_cell_list(&out->cell_list, ctx, TLVP_VAL(tp, CBSP_IEI_CELL_LIST), - TLVP_LEN(tp, CBSP_IEI_CELL_LIST)); + rc = cbsp_decode_cell_list(&out->cell_list, ctx, TLVP_VAL(tp, CBSP_IEI_CELL_LIST), + TLVP_LEN(tp, CBSP_IEI_CELL_LIST)); + if (rc < 0) + return rc; if (TLVP_PRES_LEN(tp, CBSP_IEI_CHANNEL_IND, 1)) { out->channel_ind = talloc(ctx, enum cbsp_channel_ind); @@ -757,6 +762,8 @@ static int cbsp_dec_write_repl_compl(struct osmo_cbsp_write_replace_complete *ou static int cbsp_dec_write_repl_fail(struct osmo_cbsp_write_replace_failure *out, const struct tlv_parsed *tp, struct msgb *in, void *ctx) { + int rc; + if (!TLVP_PRES_LEN(tp, CBSP_IEI_MSG_ID, 2) || !TLVP_PRES_LEN(tp, CBSP_IEI_NEW_SERIAL_NR, 2) || !TLVP_PRES_LEN(tp, CBSP_IEI_FAILURE_LIST, 5)) { @@ -772,21 +779,27 @@ static int cbsp_dec_write_repl_fail(struct osmo_cbsp_write_replace_failure *out, } INIT_LLIST_HEAD(&out->fail_list); - cbsp_decode_fail_list(&out->fail_list, ctx, - TLVP_VAL(tp, CBSP_IEI_FAILURE_LIST), - TLVP_LEN(tp, CBSP_IEI_FAILURE_LIST)); + rc = cbsp_decode_fail_list(&out->fail_list, ctx, + TLVP_VAL(tp, CBSP_IEI_FAILURE_LIST), + TLVP_LEN(tp, CBSP_IEI_FAILURE_LIST)); + if (rc < 0) + return rc; INIT_LLIST_HEAD(&out->num_compl_list.list); if (TLVP_PRES_LEN(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST, 7)) { - cbsp_decode_num_compl_list(&out->num_compl_list, ctx, - TLVP_VAL(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST), - TLVP_LEN(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST)); + rc = cbsp_decode_num_compl_list(&out->num_compl_list, ctx, + TLVP_VAL(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST), + TLVP_LEN(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST)); + if (rc < 0) + return rc; } INIT_LLIST_HEAD(&out->cell_list.list); if (TLVP_PRES_LEN(tp, CBSP_IEI_CELL_LIST, 1)) { - cbsp_decode_cell_list(&out->cell_list, ctx, TLVP_VAL(tp, CBSP_IEI_CELL_LIST), - TLVP_LEN(tp, CBSP_IEI_CELL_LIST)); + rc = cbsp_decode_cell_list(&out->cell_list, ctx, TLVP_VAL(tp, CBSP_IEI_CELL_LIST), + TLVP_LEN(tp, CBSP_IEI_CELL_LIST)); + if (rc < 0) + return rc; } if (TLVP_PRES_LEN(tp, CBSP_IEI_CHANNEL_IND, 1)) { @@ -800,6 +813,8 @@ static int cbsp_dec_write_repl_fail(struct osmo_cbsp_write_replace_failure *out, static int cbsp_dec_kill(struct osmo_cbsp_kill *out, const struct tlv_parsed *tp, struct msgb *in, void *ctx) { + int rc; + if (!TLVP_PRES_LEN(tp, CBSP_IEI_MSG_ID, 2) || !TLVP_PRES_LEN(tp, CBSP_IEI_OLD_SERIAL_NR, 2) || !TLVP_PRES_LEN(tp, CBSP_IEI_CELL_LIST, 1)) { @@ -811,8 +826,10 @@ static int cbsp_dec_kill(struct osmo_cbsp_kill *out, const struct tlv_parsed *tp out->old_serial_nr = tlvp_val16be(tp, CBSP_IEI_OLD_SERIAL_NR); INIT_LLIST_HEAD(&out->cell_list.list); - cbsp_decode_cell_list(&out->cell_list, ctx, TLVP_VAL(tp, CBSP_IEI_CELL_LIST), - TLVP_LEN(tp, CBSP_IEI_CELL_LIST)); + rc = cbsp_decode_cell_list(&out->cell_list, ctx, TLVP_VAL(tp, CBSP_IEI_CELL_LIST), + TLVP_LEN(tp, CBSP_IEI_CELL_LIST)); + if (rc < 0) + return rc; if (TLVP_PRES_LEN(tp, CBSP_IEI_CHANNEL_IND, 1)) { out->channel_ind = talloc(ctx, enum cbsp_channel_ind); @@ -825,6 +842,8 @@ static int cbsp_dec_kill(struct osmo_cbsp_kill *out, const struct tlv_parsed *tp static int cbsp_dec_kill_compl(struct osmo_cbsp_kill_complete *out, const struct tlv_parsed *tp, struct msgb *in, void *ctx) { + int rc; + if (!TLVP_PRES_LEN(tp, CBSP_IEI_MSG_ID, 2) || !TLVP_PRES_LEN(tp, CBSP_IEI_OLD_SERIAL_NR, 2) || !TLVP_PRES_LEN(tp, CBSP_IEI_CELL_LIST, 1)) { @@ -837,14 +856,18 @@ static int cbsp_dec_kill_compl(struct osmo_cbsp_kill_complete *out, const struct INIT_LLIST_HEAD(&out->num_compl_list.list); if (TLVP_PRES_LEN(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST, 7)) { - cbsp_decode_num_compl_list(&out->num_compl_list, ctx, - TLVP_VAL(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST), - TLVP_LEN(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST)); + rc = cbsp_decode_num_compl_list(&out->num_compl_list, ctx, + TLVP_VAL(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST), + TLVP_LEN(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST)); + if (rc < 0) + return rc; } INIT_LLIST_HEAD(&out->cell_list.list); - cbsp_decode_cell_list(&out->cell_list, ctx, TLVP_VAL(tp, CBSP_IEI_CELL_LIST), - TLVP_LEN(tp, CBSP_IEI_CELL_LIST)); + rc = cbsp_decode_cell_list(&out->cell_list, ctx, TLVP_VAL(tp, CBSP_IEI_CELL_LIST), + TLVP_LEN(tp, CBSP_IEI_CELL_LIST)); + if (rc < 0) + return rc; if (TLVP_PRES_LEN(tp, CBSP_IEI_CHANNEL_IND, 1)) { out->channel_ind = talloc(ctx, enum cbsp_channel_ind); @@ -857,6 +880,8 @@ static int cbsp_dec_kill_compl(struct osmo_cbsp_kill_complete *out, const struct static int cbsp_dec_kill_fail(struct osmo_cbsp_kill_failure *out, const struct tlv_parsed *tp, struct msgb *in, void *ctx) { + int rc; + if (!TLVP_PRES_LEN(tp, CBSP_IEI_MSG_ID, 2) || !TLVP_PRES_LEN(tp, CBSP_IEI_OLD_SERIAL_NR, 2) || !TLVP_PRES_LEN(tp, CBSP_IEI_FAILURE_LIST, 5)) { @@ -868,21 +893,27 @@ static int cbsp_dec_kill_fail(struct osmo_cbsp_kill_failure *out, const struct t out->old_serial_nr = tlvp_val16be(tp, CBSP_IEI_OLD_SERIAL_NR); INIT_LLIST_HEAD(&out->fail_list); - cbsp_decode_fail_list(&out->fail_list, ctx, - TLVP_VAL(tp, CBSP_IEI_FAILURE_LIST), - TLVP_LEN(tp, CBSP_IEI_FAILURE_LIST)); + rc = cbsp_decode_fail_list(&out->fail_list, ctx, + TLVP_VAL(tp, CBSP_IEI_FAILURE_LIST), + TLVP_LEN(tp, CBSP_IEI_FAILURE_LIST)); + if (rc < 0) + return rc; INIT_LLIST_HEAD(&out->num_compl_list.list); if (TLVP_PRES_LEN(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST, 7)) { - cbsp_decode_num_compl_list(&out->num_compl_list, ctx, - TLVP_VAL(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST), - TLVP_LEN(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST)); + rc = cbsp_decode_num_compl_list(&out->num_compl_list, ctx, + TLVP_VAL(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST), + TLVP_LEN(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST)); + if (rc < 0) + return rc; } INIT_LLIST_HEAD(&out->cell_list.list); if (TLVP_PRES_LEN(tp, CBSP_IEI_CELL_LIST, 1)) { - cbsp_decode_cell_list(&out->cell_list, ctx, TLVP_VAL(tp, CBSP_IEI_CELL_LIST), - TLVP_LEN(tp, CBSP_IEI_CELL_LIST)); + rc = cbsp_decode_cell_list(&out->cell_list, ctx, TLVP_VAL(tp, CBSP_IEI_CELL_LIST), + TLVP_LEN(tp, CBSP_IEI_CELL_LIST)); + if (rc < 0) + return rc; } if (TLVP_PRES_LEN(tp, CBSP_IEI_CHANNEL_IND, 1)) { @@ -896,6 +927,8 @@ static int cbsp_dec_kill_fail(struct osmo_cbsp_kill_failure *out, const struct t static int cbsp_dec_load_query(struct osmo_cbsp_load_query *out, const struct tlv_parsed *tp, struct msgb *in, void *ctx) { + int rc; + if (!TLVP_PRES_LEN(tp, CBSP_IEI_CELL_LIST, 1) || !TLVP_PRES_LEN(tp, CBSP_IEI_CHANNEL_IND, 1)) { osmo_cbsp_errstr = "missing/short mandatory IE"; @@ -903,8 +936,10 @@ static int cbsp_dec_load_query(struct osmo_cbsp_load_query *out, const struct tl } INIT_LLIST_HEAD(&out->cell_list.list); - cbsp_decode_cell_list(&out->cell_list, ctx, TLVP_VAL(tp, CBSP_IEI_CELL_LIST), - TLVP_LEN(tp, CBSP_IEI_CELL_LIST)); + rc = cbsp_decode_cell_list(&out->cell_list, ctx, TLVP_VAL(tp, CBSP_IEI_CELL_LIST), + TLVP_LEN(tp, CBSP_IEI_CELL_LIST)); + if (rc < 0) + return rc; out->channel_ind = *TLVP_VAL(tp, CBSP_IEI_CHANNEL_IND); return 0; @@ -914,6 +949,8 @@ static int cbsp_dec_load_query(struct osmo_cbsp_load_query *out, const struct tl static int cbsp_dec_load_query_compl(struct osmo_cbsp_load_query_complete *out, const struct tlv_parsed *tp, struct msgb *in, void *ctx) { + int rc; + if (!TLVP_PRES_LEN(tp, CBSP_IEI_RR_LOADING_LIST, 6) || !TLVP_PRES_LEN(tp, CBSP_IEI_CHANNEL_IND, 1)) { osmo_cbsp_errstr = "missing/short mandatory IE"; @@ -921,9 +958,11 @@ static int cbsp_dec_load_query_compl(struct osmo_cbsp_load_query_complete *out, } INIT_LLIST_HEAD(&out->loading_list.list); - cbsp_decode_loading_list(&out->loading_list, ctx, - TLVP_VAL(tp, CBSP_IEI_RR_LOADING_LIST), - TLVP_LEN(tp, CBSP_IEI_RR_LOADING_LIST)); + rc = cbsp_decode_loading_list(&out->loading_list, ctx, + TLVP_VAL(tp, CBSP_IEI_RR_LOADING_LIST), + TLVP_LEN(tp, CBSP_IEI_RR_LOADING_LIST)); + if (rc < 0) + return rc; out->channel_ind = *TLVP_VAL(tp, CBSP_IEI_CHANNEL_IND); return 0; @@ -933,6 +972,8 @@ static int cbsp_dec_load_query_compl(struct osmo_cbsp_load_query_complete *out, static int cbsp_dec_load_query_fail(struct osmo_cbsp_load_query_failure *out, const struct tlv_parsed *tp, struct msgb *in, void *ctx) { + int rc; + if (!TLVP_PRES_LEN(tp, CBSP_IEI_FAILURE_LIST, 5) || !TLVP_PRES_LEN(tp, CBSP_IEI_CHANNEL_IND, 1)) { osmo_cbsp_errstr = "missing/short mandatory IE"; @@ -940,25 +981,29 @@ static int cbsp_dec_load_query_fail(struct osmo_cbsp_load_query_failure *out, } INIT_LLIST_HEAD(&out->fail_list); - cbsp_decode_fail_list(&out->fail_list, ctx, - TLVP_VAL(tp, CBSP_IEI_FAILURE_LIST), - TLVP_LEN(tp, CBSP_IEI_FAILURE_LIST)); + rc = cbsp_decode_fail_list(&out->fail_list, ctx, + TLVP_VAL(tp, CBSP_IEI_FAILURE_LIST), + TLVP_LEN(tp, CBSP_IEI_FAILURE_LIST)); + if (rc < 0) + return rc; out->channel_ind = *TLVP_VAL(tp, CBSP_IEI_CHANNEL_IND); INIT_LLIST_HEAD(&out->loading_list.list); if (TLVP_PRES_LEN(tp, CBSP_IEI_RR_LOADING_LIST, 6)) { - cbsp_decode_loading_list(&out->loading_list, ctx, - TLVP_VAL(tp, CBSP_IEI_RR_LOADING_LIST), - TLVP_LEN(tp, CBSP_IEI_RR_LOADING_LIST)); + rc = cbsp_decode_loading_list(&out->loading_list, ctx, + TLVP_VAL(tp, CBSP_IEI_RR_LOADING_LIST), + TLVP_LEN(tp, CBSP_IEI_RR_LOADING_LIST)); } - return 0; + return rc; } /* 8.1.3.10 STATUS QUERY */ static int cbsp_dec_msg_status_query(struct osmo_cbsp_msg_status_query *out, const struct tlv_parsed *tp, struct msgb *in, void *ctx) { + int rc; + if (!TLVP_PRES_LEN(tp, CBSP_IEI_MSG_ID, 2) || !TLVP_PRES_LEN(tp, CBSP_IEI_OLD_SERIAL_NR, 2) || !TLVP_PRES_LEN(tp, CBSP_IEI_CELL_LIST, 1) || @@ -971,8 +1016,10 @@ static int cbsp_dec_msg_status_query(struct osmo_cbsp_msg_status_query *out, out->old_serial_nr = tlvp_val16be(tp, CBSP_IEI_OLD_SERIAL_NR); INIT_LLIST_HEAD(&out->cell_list.list); - cbsp_decode_cell_list(&out->cell_list, ctx, TLVP_VAL(tp, CBSP_IEI_CELL_LIST), - TLVP_LEN(tp, CBSP_IEI_CELL_LIST)); + rc = cbsp_decode_cell_list(&out->cell_list, ctx, TLVP_VAL(tp, CBSP_IEI_CELL_LIST), + TLVP_LEN(tp, CBSP_IEI_CELL_LIST)); + if (rc < 0) + return rc; out->channel_ind = *TLVP_VAL(tp, CBSP_IEI_CHANNEL_IND); return 0; @@ -982,6 +1029,8 @@ static int cbsp_dec_msg_status_query(struct osmo_cbsp_msg_status_query *out, static int cbsp_dec_msg_status_query_compl(struct osmo_cbsp_msg_status_query_complete *out, const struct tlv_parsed *tp, struct msgb *in, void *ctx) { + int rc; + if (!TLVP_PRES_LEN(tp, CBSP_IEI_MSG_ID, 2) || !TLVP_PRES_LEN(tp, CBSP_IEI_OLD_SERIAL_NR, 2) || !TLVP_PRES_LEN(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST, 7) || @@ -994,9 +1043,11 @@ static int cbsp_dec_msg_status_query_compl(struct osmo_cbsp_msg_status_query_com out->old_serial_nr = tlvp_val16be(tp, CBSP_IEI_OLD_SERIAL_NR); INIT_LLIST_HEAD(&out->num_compl_list.list); - cbsp_decode_num_compl_list(&out->num_compl_list, ctx, - TLVP_VAL(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST), - TLVP_LEN(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST)); + rc = cbsp_decode_num_compl_list(&out->num_compl_list, ctx, + TLVP_VAL(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST), + TLVP_LEN(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST)); + if (rc < 0) + return rc; out->channel_ind = *TLVP_VAL(tp, CBSP_IEI_CHANNEL_IND); return 0; } @@ -1005,6 +1056,8 @@ static int cbsp_dec_msg_status_query_compl(struct osmo_cbsp_msg_status_query_com static int cbsp_dec_msg_status_query_fail(struct osmo_cbsp_msg_status_query_failure *out, const struct tlv_parsed *tp, struct msgb *in, void *ctx) { + int rc; + if (!TLVP_PRES_LEN(tp, CBSP_IEI_MSG_ID, 2) || !TLVP_PRES_LEN(tp, CBSP_IEI_OLD_SERIAL_NR, 2) || !TLVP_PRES_LEN(tp, CBSP_IEI_FAILURE_LIST, 5) || @@ -1017,17 +1070,21 @@ static int cbsp_dec_msg_status_query_fail(struct osmo_cbsp_msg_status_query_fail out->old_serial_nr = tlvp_val16be(tp, CBSP_IEI_OLD_SERIAL_NR); INIT_LLIST_HEAD(&out->fail_list); - cbsp_decode_fail_list(&out->fail_list, ctx, - TLVP_VAL(tp, CBSP_IEI_FAILURE_LIST), - TLVP_LEN(tp, CBSP_IEI_FAILURE_LIST)); + rc = cbsp_decode_fail_list(&out->fail_list, ctx, + TLVP_VAL(tp, CBSP_IEI_FAILURE_LIST), + TLVP_LEN(tp, CBSP_IEI_FAILURE_LIST)); + if (rc < 0) + return rc; out->channel_ind = *TLVP_VAL(tp, CBSP_IEI_CHANNEL_IND); INIT_LLIST_HEAD(&out->num_compl_list.list); if (TLVP_PRES_LEN(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST, 7)) { - cbsp_decode_num_compl_list(&out->num_compl_list, ctx, - TLVP_VAL(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST), - TLVP_LEN(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST)); + rc = cbsp_decode_num_compl_list(&out->num_compl_list, ctx, + TLVP_VAL(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST), + TLVP_LEN(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST)); + if (rc < 0) + return rc; } return 0; } @@ -1036,14 +1093,19 @@ static int cbsp_dec_msg_status_query_fail(struct osmo_cbsp_msg_status_query_fail static int cbsp_dec_reset(struct osmo_cbsp_reset *out, const struct tlv_parsed *tp, struct msgb *in, void *ctx) { + int rc; + if (!TLVP_PRES_LEN(tp, CBSP_IEI_CELL_LIST, 1)) { osmo_cbsp_errstr = "missing/short mandatory IE"; return -EINVAL; } INIT_LLIST_HEAD(&out->cell_list.list); - cbsp_decode_cell_list(&out->cell_list, ctx, TLVP_VAL(tp, CBSP_IEI_CELL_LIST), - TLVP_LEN(tp, CBSP_IEI_CELL_LIST)); + rc = cbsp_decode_cell_list(&out->cell_list, ctx, TLVP_VAL(tp, CBSP_IEI_CELL_LIST), + TLVP_LEN(tp, CBSP_IEI_CELL_LIST)); + if (rc < 0) + return rc; + return 0; } @@ -1051,14 +1113,19 @@ static int cbsp_dec_reset(struct osmo_cbsp_reset *out, const struct tlv_parsed * static int cbsp_dec_reset_compl(struct osmo_cbsp_reset_complete *out, const struct tlv_parsed *tp, struct msgb *in, void *ctx) { + int rc; + if (!TLVP_PRES_LEN(tp, CBSP_IEI_CELL_LIST, 1)) { osmo_cbsp_errstr = "missing/short mandatory IE"; return -EINVAL; } INIT_LLIST_HEAD(&out->cell_list.list); - cbsp_decode_cell_list(&out->cell_list, ctx, TLVP_VAL(tp, CBSP_IEI_CELL_LIST), - TLVP_LEN(tp, CBSP_IEI_CELL_LIST)); + rc = cbsp_decode_cell_list(&out->cell_list, ctx, TLVP_VAL(tp, CBSP_IEI_CELL_LIST), + TLVP_LEN(tp, CBSP_IEI_CELL_LIST)); + if (rc < 0) + return rc; + return 0; } @@ -1066,20 +1133,26 @@ static int cbsp_dec_reset_compl(struct osmo_cbsp_reset_complete *out, const stru static int cbsp_dec_reset_fail(struct osmo_cbsp_reset_failure *out, const struct tlv_parsed *tp, struct msgb *in, void *ctx) { + int rc; + if (!TLVP_PRES_LEN(tp, CBSP_IEI_FAILURE_LIST, 5)) { osmo_cbsp_errstr = "missing/short mandatory IE"; return -EINVAL; } INIT_LLIST_HEAD(&out->fail_list); - cbsp_decode_fail_list(&out->fail_list, ctx, - TLVP_VAL(tp, CBSP_IEI_FAILURE_LIST), - TLVP_LEN(tp, CBSP_IEI_FAILURE_LIST)); + rc = cbsp_decode_fail_list(&out->fail_list, ctx, + TLVP_VAL(tp, CBSP_IEI_FAILURE_LIST), + TLVP_LEN(tp, CBSP_IEI_FAILURE_LIST)); + if (rc < 0) + return rc; INIT_LLIST_HEAD(&out->cell_list.list); if (TLVP_PRES_LEN(tp, CBSP_IEI_CELL_LIST, 1)) { - cbsp_decode_cell_list(&out->cell_list, ctx, TLVP_VAL(tp, CBSP_IEI_CELL_LIST), - TLVP_LEN(tp, CBSP_IEI_CELL_LIST)); + rc = cbsp_decode_cell_list(&out->cell_list, ctx, TLVP_VAL(tp, CBSP_IEI_CELL_LIST), + TLVP_LEN(tp, CBSP_IEI_CELL_LIST)); + if (rc < 0) + return rc; } return 0; } @@ -1110,6 +1183,8 @@ static int cbsp_dec_keep_alive_compl(struct osmo_cbsp_keep_alive_complete *out, static int cbsp_dec_restart(struct osmo_cbsp_restart *out, const struct tlv_parsed *tp, struct msgb *in, void *ctx) { + int rc; + if (!TLVP_PRES_LEN(tp, CBSP_IEI_CELL_LIST, 1) || !TLVP_PRES_LEN(tp, CBSP_IEI_BCAST_MSG_TYPE, 1) || !TLVP_PRES_LEN(tp, CBSP_IEI_RECOVERY_IND, 1)) { @@ -1118,8 +1193,10 @@ static int cbsp_dec_restart(struct osmo_cbsp_restart *out, const struct tlv_pars } INIT_LLIST_HEAD(&out->cell_list.list); - cbsp_decode_cell_list(&out->cell_list, ctx, TLVP_VAL(tp, CBSP_IEI_CELL_LIST), - TLVP_LEN(tp, CBSP_IEI_CELL_LIST)); + rc = cbsp_decode_cell_list(&out->cell_list, ctx, TLVP_VAL(tp, CBSP_IEI_CELL_LIST), + TLVP_LEN(tp, CBSP_IEI_CELL_LIST)); + if (rc < 0) + return rc; out->bcast_msg_type = *TLVP_VAL(tp, CBSP_IEI_BCAST_MSG_TYPE); out->recovery_ind = *TLVP_VAL(tp, CBSP_IEI_RECOVERY_IND); @@ -1130,6 +1207,8 @@ static int cbsp_dec_restart(struct osmo_cbsp_restart *out, const struct tlv_pars static int cbsp_dec_failure(struct osmo_cbsp_failure *out, const struct tlv_parsed *tp, struct msgb *in, void *ctx) { + int rc; + if (!TLVP_PRES_LEN(tp, CBSP_IEI_FAILURE_LIST, 5) || !TLVP_PRES_LEN(tp, CBSP_IEI_BCAST_MSG_TYPE, 1)) { osmo_cbsp_errstr = "missing/short mandatory IE"; @@ -1137,9 +1216,11 @@ static int cbsp_dec_failure(struct osmo_cbsp_failure *out, const struct tlv_pars } INIT_LLIST_HEAD(&out->fail_list); - cbsp_decode_fail_list(&out->fail_list, ctx, - TLVP_VAL(tp, CBSP_IEI_FAILURE_LIST), - TLVP_LEN(tp, CBSP_IEI_FAILURE_LIST)); + rc = cbsp_decode_fail_list(&out->fail_list, ctx, + TLVP_VAL(tp, CBSP_IEI_FAILURE_LIST), + TLVP_LEN(tp, CBSP_IEI_FAILURE_LIST)); + if (rc < 0) + return rc; out->bcast_msg_type = *TLVP_VAL(tp, CBSP_IEI_BCAST_MSG_TYPE); return 0; @@ -1180,6 +1261,7 @@ static int cbsp_dec_error_ind(struct osmo_cbsp_error_ind *out, const struct tlv_ * \returns callee-allocated decoded representation of CBSP message; NULL on error */ struct osmo_cbsp_decoded *osmo_cbsp_decode(void *ctx, struct msgb *in) { + OSMO_ASSERT(in->l1h != NULL && in->l2h != NULL); struct osmo_cbsp_decoded *out = talloc_zero(ctx, struct osmo_cbsp_decoded); const struct cbsp_header *h = msgb_l1(in); struct tlv_parsed tp[16]; /* max. number of pages in a given CBS message */ @@ -1445,6 +1527,10 @@ int osmo_cbsp_recv_buffered(void *ctx, int fd, struct msgb **rmsg, struct msgb * needed = len - msgb_l2len(msg); if (needed > 0) { + if (needed > msgb_tailroom(msg)) { + rc = -ENOMEM; + goto discard_msg; + } rc = recv(fd, msg->tail, needed, 0); if (rc == 0) goto discard_msg; @@ -1481,4 +1567,45 @@ discard_msg: return rc; } +/*! call-back function to segment the data at message boundaries. + * Returns the size of the next message. If it returns -EAGAIN or a value larger than msgb_length() (message + * is incomplete), the caller (e.g. osmo_io) has to wait for more data to be read. */ +int osmo_cbsp_segmentation_cb(struct msgb *msg) +{ + const struct cbsp_header *h; + int len; + + if (msgb_length(msg) < sizeof(*h)) + return -EAGAIN; + + h = (const struct cbsp_header *) msg->data; + msg->l1h = msg->data; + msg->l2h = msg->data + sizeof(*h); + /* then read the length as specified in the header */ + len = h->len[0] << 16 | h->len[1] << 8 | h->len[2]; + + return sizeof(*h) + len; +} + +/*! value_string[] for enum osmo_cbsp_cause. */ +const struct value_string osmo_cbsp_cause_names[] = { + { OSMO_CBSP_CAUSE_PARAM_NOT_RECOGNISED, "Parameter-not-recognised" }, + { OSMO_CBSP_CAUSE_PARAM_VALUE_INVALID, "Parameter-value-invalid" }, + { OSMO_CBSP_CAUSE_MSG_REF_NOT_IDENTIFIED, "Message-reference-not-identified" }, + { OSMO_CBSP_CAUSE_CELL_ID_NOT_VALID, "Cell-identity-not-valid" }, + { OSMO_CBSP_CAUSE_UNRECOGNISED_MESSAGE, "Unrecognised-message" }, + { OSMO_CBSP_CAUSE_MISSING_MANDATORY_ELEMENT, "Missing-mandatory-element" }, + { OSMO_CBSP_CAUSE_BSC_CAPACITY_EXCEEDED, "BSC-capacity-exceeded" }, + { OSMO_CBSP_CAUSE_CELL_MEMORY_EXCEEDED, "Cell-memory-exceeded" }, + { OSMO_CBSP_CAUSE_BSC_MEMORY_EXCEEDED, "BSC-memory-exceeded" }, + { OSMO_CBSP_CAUSE_CELL_BROADCAST_NOT_SUPPORTED, "Cell-broadcast-not-supported" }, + { OSMO_CBSP_CAUSE_CELL_BROADCAST_NOT_OPERATIONAL, "Cell-broadcast-not-operational" }, + { OSMO_CBSP_CAUSE_INCOMPATIBLE_DRX_PARAM, "Incompatible-DRX-parameter:"}, + { OSMO_CBSP_CAUSE_EXT_CHAN_NOT_SUPPORTED, "Extended-channel-not-supported"}, + { OSMO_CBSP_CAUSE_MSG_REF_ALREADY_USED, "Message-reference-already-used"}, + { OSMO_CBSP_CAUSE_UNSPECIFIED_ERROR, "Unspecified-error"}, + { OSMO_CBSP_CAUSE_LAI_OR_LAC_NOT_VALID, "LAI-or-LAC-not-valid"}, + { 0, NULL } +}; + #endif /* HAVE_SYS_SOCKET_H */ diff --git a/src/gsm/comp128.c b/src/gsm/comp128.c index b28a8437..e252b04c 100644 --- a/src/gsm/comp128.c +++ b/src/gsm/comp128.c @@ -58,10 +58,6 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ #include <string.h> diff --git a/src/gsm/comp128v23.c b/src/gsm/comp128v23.c index 550f6a49..0947017c 100644 --- a/src/gsm/comp128v23.c +++ b/src/gsm/comp128v23.c @@ -23,10 +23,6 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ #include <stdint.h> diff --git a/src/gsm/gea.c b/src/gsm/gea.c index 5756bb08..aedc898e 100644 --- a/src/gsm/gea.c +++ b/src/gsm/gea.c @@ -12,10 +12,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include <osmocom/core/bits.h> diff --git a/src/gsm/gprs_cipher_core.c b/src/gsm/gprs_cipher_core.c index 7f2b1a50..97e581db 100644 --- a/src/gsm/gprs_cipher_core.c +++ b/src/gsm/gprs_cipher_core.c @@ -17,10 +17,6 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ #include "config.h" diff --git a/src/gsm/gprs_gea.c b/src/gsm/gprs_gea.c index 73147886..eae1cf12 100644 --- a/src/gsm/gprs_gea.c +++ b/src/gsm/gprs_gea.c @@ -16,10 +16,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include <osmocom/crypt/gprs_cipher.h> diff --git a/src/gsm/gprs_rlc.c b/src/gsm/gprs_rlc.c index bdfc8eac..4f02a7a9 100644 --- a/src/gsm/gprs_rlc.c +++ b/src/gsm/gprs_rlc.c @@ -13,9 +13,8 @@ #include <string.h> #include <osmocom/core/utils.h> -#include <osmocom/gprs/gprs_rlc.h> #include <osmocom/coding/gsm0503_coding.h> -#include <osmocom/gprs/protocol/gsm_04_60.h> +#include <osmocom/gsm/protocol/gsm_44_060.h> #define EGPRS_CPS_TYPE1_TBL_SZ 29 #define EGPRS_CPS_TYPE2_TBL_SZ 8 diff --git a/src/gsm/gsm0341.c b/src/gsm/gsm0341.c index 89f5de3f..a6238b36 100644 --- a/src/gsm/gsm0341.c +++ b/src/gsm/gsm0341.c @@ -14,10 +14,6 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ diff --git a/src/gsm/gsm0411_utils.c b/src/gsm/gsm0411_utils.c index ccefe546..470b017f 100644 --- a/src/gsm/gsm0411_utils.c +++ b/src/gsm/gsm0411_utils.c @@ -27,7 +27,7 @@ * */ -#include "../../config.h" +#include "config.h" #include <time.h> #include <string.h> @@ -293,7 +293,7 @@ enum sms_alphabet gsm338_get_sms_alphabet(uint8_t dcs) * \param[in] type GSM340_TYPE_* * \param[in] plan Numbering Plan * \param[in] number string containing number - * \reurns number of bytes of \a oa that have been used */ + * \returns number of bytes of \a oa that have been used */ int gsm340_gen_oa(uint8_t *oa, unsigned int oa_len, uint8_t type, uint8_t plan, const char *number) { @@ -348,7 +348,7 @@ int gsm411_push_rp_header(struct msgb *msg, uint8_t rp_msg_type, * \param[in] proto Protocol * \param[in] trans Transaction * \param[in] msg_type Message Type - * \retrns 0 */ + * \returns 0 */ int gsm411_push_cp_header(struct msgb *msg, uint8_t proto, uint8_t trans, uint8_t msg_type) { diff --git a/src/gsm/gsm0480.c b/src/gsm/gsm0480.c index 1d5cde95..7a7f71f0 100644 --- a/src/gsm/gsm0480.c +++ b/src/gsm/gsm0480.c @@ -19,10 +19,6 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ #include <osmocom/gsm/gsm48.h> diff --git a/src/gsm/gsm0502.c b/src/gsm/gsm0502.c index e34d3f57..e4a761da 100644 --- a/src/gsm/gsm0502.c +++ b/src/gsm/gsm0502.c @@ -34,7 +34,7 @@ #include <inttypes.h> unsigned int -gsm0502_calc_paging_group(struct gsm48_control_channel_descr *chan_desc, uint64_t imsi) +gsm0502_calc_paging_group(const struct gsm48_control_channel_descr *chan_desc, uint64_t imsi) { int ccch_conf; int bs_cc_chans; @@ -179,7 +179,6 @@ uint32_t gsm0502_fn_remap(uint32_t fn, enum gsm0502_fn_remap_channel channel) uint8_t fn_cycle; uint8_t i; int sub = -1; - uint32_t fn_map; struct fn_remap_table *table; OSMO_ASSERT(channel < ARRAY_SIZE(fn_remap_table_ptr)); @@ -199,9 +198,7 @@ uint32_t gsm0502_fn_remap(uint32_t fn, enum gsm0502_fn_remap_channel channel) return fn; } - fn_map = (fn + GSM_MAX_FN - sub) % GSM_MAX_FN; - - return fn_map; + return GSM_TDMA_FN_SUB(fn, sub); } /* Magic numbers (RNTABLE) for pseudo-random hopping sequence generation. */ @@ -263,3 +260,38 @@ uint16_t gsm0502_hop_seq_gen(const struct gsm_time *t, return ma ? ma[mai] : mai; } + +#define CB_FCCH -1 +#define CB_SCH -2 +#define CB_BCCH -3 +#define CB_IDLE -4 + +/* Clause 7 Table 3 and Figure 8a */ +static const int ccch_block_table[51] = { + CB_FCCH, CB_SCH, /* 0..1 */ + CB_BCCH, CB_BCCH, CB_BCCH, CB_BCCH, /* 2..5: BCCH */ + 0, 0, 0, 0, /* 6..9: B0 */ + CB_FCCH, CB_SCH, /* 10..11 */ + 1, 1, 1, 1, /* 12..15: B1 */ + 2, 2, 2, 2, /* 16..19: B2 */ + CB_FCCH, CB_SCH, /* 20..21 */ + 3, 3, 3, 3, /* 22..25: B3 */ + 4, 4, 4, 4, /* 26..29: B4 */ + CB_FCCH, CB_SCH, /* 30..31 */ + 5, 5, 5, 5, /* 32..35: B5 */ + 6, 6, 6, 6, /* 36..39: B6 */ + CB_FCCH, CB_SCH, /* 40..41 */ + 7, 7, 7, 7, /* 42..45: B7 */ + 8, 8, 8, 8, /* 46..49: B8 */ + CB_IDLE /* 50: Idle */ +}; + +/*! Calculate CCCH block number from the given TDMA frame number. + * \param[in] fn TDMA frame number (of first or last burst). + * \returns CCCH block number 0..8 or a negative value, + * if the given frame number cannot carry CCCH. + */ +int gsm0502_fn2ccch_block(uint32_t fn) +{ + return ccch_block_table[fn % ARRAY_SIZE(ccch_block_table)]; +} diff --git a/src/gsm/gsm0808.c b/src/gsm/gsm0808.c index 158c4903..529dbdfe 100644 --- a/src/gsm/gsm0808.c +++ b/src/gsm/gsm0808.c @@ -15,12 +15,10 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ +#include "config.h" + #include <string.h> #include <osmocom/core/byteswap.h> @@ -104,13 +102,19 @@ struct msgb *gsm0808_create_layer3_2(const struct msgb *msg_l3, const struct osm msgb_l3len(msg_l3), msg_l3->l3h); /* AoIP: add Codec List (BSS Supported) 3.2.2.103 */ - if (scl) - gsm0808_enc_speech_codec_list(msg, scl); + if (scl) { + if (gsm0808_enc_speech_codec_list2(msg, scl) < 0) + goto exit_free; + } /* push the bssmap header */ msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg)); return msg; + +exit_free: + msgb_free(msg); + return NULL; } /*! Create "Complete L3 Info" for A, legacy implementation. @@ -377,7 +381,7 @@ struct msgb *gsm0808_create_lcls_conn_ctrl(enum gsm0808_lcls_config config, if (config != GSM0808_LCLS_CFG_NA) msgb_tv_put(msg, GSM0808_IE_LCLS_CONFIG, config); if (control != GSM0808_LCLS_CSC_NA) - msgb_tv_put(msg, GSM0808_IE_LCLS_CONFIG, control); + msgb_tv_put(msg, GSM0808_IE_LCLS_CONN_STATUS_CTRL, control); msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg)); return msg; @@ -422,7 +426,7 @@ struct msgb *gsm0808_create_lcls_notification(enum gsm0808_lcls_status status, b /*! Create BSSMAP Classmark Request message * \returns callee-allocated msgb with BSSMAP Classmark Request message */ -struct msgb *gsm0808_create_classmark_request() +struct msgb *gsm0808_create_classmark_request(void) { struct msgb *msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM, "classmark-request"); @@ -534,8 +538,10 @@ struct msgb *gsm0808_create_ass2(const struct gsm0808_channel_type *ct, } /* AoIP: Codec List (MSC Preferred) 3.2.2.103 */ - if (scl) - gsm0808_enc_speech_codec_list(msg, scl); + if (scl) { + if (gsm0808_enc_speech_codec_list2(msg, scl) < 0) + goto exit_free; + } /* AoIP: Call Identifier 3.2.2.105 */ if (ci) { @@ -556,6 +562,10 @@ struct msgb *gsm0808_create_ass2(const struct gsm0808_channel_type *ct, msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg)); return msg; + +exit_free: + msgb_free(msg); + return NULL; } /*! Create BSSMAP Assignment Request message, 3GPP TS 48.008 §3.2.1.1. @@ -620,12 +630,16 @@ struct msgb *gsm0808_create_ass_compl2(uint8_t rr_cause, uint8_t chosen_channel, gsm0808_enc_aoip_trasp_addr(msg, ss); /* AoIP: Speech Codec (Chosen) 3.2.2.104 */ - if (sc) - gsm0808_enc_speech_codec(msg, sc); + if (sc) { + if (gsm0808_enc_speech_codec2(msg, sc) < 0) + goto exit_free; + } /* AoIP: add Codec List (BSS Supported) 3.2.2.103 */ - if (scl) - gsm0808_enc_speech_codec_list(msg, scl); + if (scl) { + if (gsm0808_enc_speech_codec_list2(msg, scl) < 0) + goto exit_free; + } /* FIXME: write LSA identifier 3.2.2.15 - see 3GPP TS 43.073 */ @@ -636,6 +650,10 @@ struct msgb *gsm0808_create_ass_compl2(uint8_t rr_cause, uint8_t chosen_channel, msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg)); return msg; + +exit_free: + msgb_free(msg); + return NULL; } /*! Create BSSMAP Assignment Completed message @@ -697,13 +715,19 @@ struct msgb *gsm0808_create_ass_fail(uint8_t cause, const uint8_t *rr_cause, /* Circuit pool list 3.2.2.46 */ /* AoIP: add Codec List (BSS Supported) 3.2.2.103 */ - if (scl) - gsm0808_enc_speech_codec_list(msg, scl); + if (scl) { + if (gsm0808_enc_speech_codec_list2(msg, scl) < 0) + goto exit_free; + } /* update the size */ msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg)); return msg; + +exit_free: + msgb_free(msg); + return NULL; } /*! Create BSSMAP Assignment Failure message @@ -996,8 +1020,10 @@ struct msgb *gsm0808_create_handover_request(const struct gsm0808_handover_reque if (params->aoip_transport_layer) gsm0808_enc_aoip_trasp_addr(msg, params->aoip_transport_layer); - if (params->codec_list_msc_preferred) - gsm0808_enc_speech_codec_list(msg, params->codec_list_msc_preferred); + if (params->codec_list_msc_preferred) { + if (gsm0808_enc_speech_codec_list2(msg, params->codec_list_msc_preferred) < 0) + goto exit_free; + } if (params->call_id_present) { uint8_t val[4]; @@ -1017,6 +1043,10 @@ struct msgb *gsm0808_create_handover_request(const struct gsm0808_handover_reque msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg)); return msg; + +exit_free: + msgb_free(msg); + return NULL; } /*! Create BSSMAP HANDOVER REQUEST ACKNOWLEDGE message, 3GPP TS 48.008 3.2.1.10. @@ -1049,14 +1079,28 @@ struct msgb *gsm0808_create_handover_request_ack2(const struct gsm0808_handover_ if (params->aoip_transport_layer) gsm0808_enc_aoip_trasp_addr(msg, params->aoip_transport_layer); + /* AoIP: add Codec List (BSS Supported) 3.2.2.103. + * (codec_list_bss_supported was added to struct gsm0808_handover_request_ack later than speech_codec_chosen + * below, but it needs to come before it in the message coding). */ + if (params->more_items && params->codec_list_bss_supported.len) { + if (gsm0808_enc_speech_codec_list2(msg, ¶ms->codec_list_bss_supported) < 0) + goto exit_free; + } + /* AoIP: Speech Codec (Chosen) 3.2.2.104 */ - if (params->speech_codec_chosen_present) - gsm0808_enc_speech_codec(msg, ¶ms->speech_codec_chosen); + if (params->speech_codec_chosen_present) { + if (gsm0808_enc_speech_codec2(msg, ¶ms->speech_codec_chosen) < 0) + goto exit_free; + } /* prepend header with final length */ msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg)); return msg; + +exit_free: + msgb_free(msg); + return NULL; } /*! Same as gsm0808_create_handover_request_ack2() but with less parameters. @@ -1108,7 +1152,7 @@ struct msgb *gsm0808_create_handover_command(const struct gsm0808_handover_comma /*! Create BSSMAP HANDOVER DETECT message, 3GPP TS 48.008 3.2.1.40. * Sent from the MT BSC back to the MSC when the MS has sent a handover RACH request and the MT BSC has * received the Handover Detect message. */ -struct msgb *gsm0808_create_handover_detect() +struct msgb *gsm0808_create_handover_detect(void) { struct msgb *msg; @@ -1127,7 +1171,7 @@ struct msgb *gsm0808_create_handover_detect() /*! Create BSSMAP HANDOVER SUCCEEDED message, 3GPP TS 48.008 3.2.1.13. * Sent from the MSC back to the old BSS to notify that the MS has successfully accessed the new BSS. */ -struct msgb *gsm0808_create_handover_succeeded() +struct msgb *gsm0808_create_handover_succeeded(void) { struct msgb *msg; @@ -1162,12 +1206,16 @@ struct msgb *gsm0808_create_handover_complete(const struct gsm0808_handover_comp msgb_tlv_put(msg, GSM0808_IE_RR_CAUSE, 1, ¶ms->rr_cause); /* AoIP: Speech Codec (Chosen) 3.2.2.104 */ - if (params->speech_codec_chosen_present) - gsm0808_enc_speech_codec(msg, ¶ms->speech_codec_chosen); + if (params->speech_codec_chosen_present) { + if (gsm0808_enc_speech_codec2(msg, ¶ms->speech_codec_chosen) < 0) + goto exit_free; + } /* AoIP: add Codec List (BSS Supported) 3.2.2.103 */ - if (params->codec_list_bss_supported.len) - gsm0808_enc_speech_codec_list(msg, ¶ms->codec_list_bss_supported); + if (params->codec_list_bss_supported.len) { + if (gsm0808_enc_speech_codec_list2(msg, ¶ms->codec_list_bss_supported) < 0) + goto exit_free; + } /* Chosen Encryption Algorithm 3.2.2.44 */ if (params->chosen_encr_alg_present && params->chosen_encr_alg > 0) @@ -1181,6 +1229,10 @@ struct msgb *gsm0808_create_handover_complete(const struct gsm0808_handover_comp msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg)); return msg; + +exit_free: + msgb_free(msg); + return NULL; } /*! Create BSSMAP HANDOVER FAILURE message, 3GPP TS 48.008 3.2.1.16. @@ -1204,13 +1256,19 @@ struct msgb *gsm0808_create_handover_failure(const struct gsm0808_handover_failu msgb_tlv_put(msg, GSM0808_IE_RR_CAUSE, 1, ¶ms->rr_cause); /* AoIP: add Codec List (BSS Supported) 3.2.2.103 */ - if (params->codec_list_bss_supported.len) - gsm0808_enc_speech_codec_list(msg, ¶ms->codec_list_bss_supported); + if (params->codec_list_bss_supported.len) { + if (gsm0808_enc_speech_codec_list2(msg, ¶ms->codec_list_bss_supported) < 0) + goto exit_free; + } /* prepend header with final length */ msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg)); return msg; + +exit_free: + msgb_free(msg); + return NULL; } /*! Create BSSMAP HANDOVER PERFORMED message, 3GPP TS 48.008 3.2.1.25. @@ -1246,8 +1304,10 @@ struct msgb *gsm0808_create_handover_performed(const struct gsm0808_handover_per msgb_tv_put(msg, GSM0808_IE_SPEECH_VERSION, params->speech_version_chosen); /* AoIP: Speech Codec (chosen) 3.2.2.104 */ - if (params->speech_codec_chosen_present) - gsm0808_enc_speech_codec(msg, ¶ms->speech_codec_chosen); + if (params->speech_codec_chosen_present) { + if (gsm0808_enc_speech_codec2(msg, ¶ms->speech_codec_chosen) < 0) + goto exit_free; + } /* LCLS-BSS-Status 3.2.2.119 */ if (params->lcls_bss_status_present) @@ -1257,6 +1317,10 @@ struct msgb *gsm0808_create_handover_performed(const struct gsm0808_handover_per msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg)); return msg; + +exit_free: + msgb_free(msg); + return NULL; } /*! Create BSSMAP COMMON ID message, 3GPP TS 48.008 3.2.1.68. @@ -1452,6 +1516,663 @@ struct msgb *gsm0808_create_perform_location_abort(const struct lcs_cause_ie *lc return msg; } +/*! Create BSSMAP VGCS/VBS SETUP message, 3GPP TS 48.008 3.2.1.50. + * Sent from the MSC to the BSC to request VGCS/VBS call. */ +struct msgb *gsm0808_create_vgcs_vbs_setup(const struct gsm0808_vgcs_vbs_setup *params) +{ + struct msgb *msg; + + msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM, "BSSMAP-VGCS/VBS-SETUP"); + if (!msg) + return NULL; + + /* Message Type, 3.2.2.1 */ + msgb_v_put(msg, BSS_MAP_MSG_VGCS_VBS_SETUP); + + /* Group Call Reference, 3.2.2.55 */ + gsm0808_enc_group_callref(msg, ¶ms->callref); + + /* Priority, 3.2.2.18 */ + if (params->priority_present) + gsm0808_enc_priority(msg, ¶ms->priority); + + /* VGCS Feature Flags, 3.2.2.88 */ + if (params->vgcs_feature_flags_present) + gsm0808_enc_vgcs_feature_flags(msg, ¶ms->flags); + + /* prepend header with final length */ + msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg)); + + return msg; +} + +/*! Create BSSMAP VGCS/VBS SETUP ACK message, 3GPP TS 48.008 3.2.1.51. + * Sent from the BSC to the MSC to confirm VGCS/VBS call. */ +struct msgb *gsm0808_create_vgcs_vbs_setup_ack(const struct gsm0808_vgcs_vbs_setup_ack *params) +{ + struct msgb *msg; + + msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM, "BSSMAP-VGCS/VBS-SETUP-ACK"); + if (!msg) + return NULL; + + /* Message Type, 3.2.2.1 */ + msgb_v_put(msg, BSS_MAP_MSG_VGCS_VBS_SETUP_ACK); + + /* VGCS Feature Flags, 3.2.2.88 */ + if (params->vgcs_feature_flags_present) + gsm0808_enc_vgcs_feature_flags(msg, ¶ms->flags); + + /* prepend header with final length */ + msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg)); + + return msg; +} + +/*! Create BSSMAP VGCS/VBS SETUP REFUSE message, 3GPP TS 48.008 3.2.1.52. + * Sent from the BSC to the MSC to reject VGCS/VBS call. */ +struct msgb *gsm0808_create_vgcs_vbs_setup_refuse(enum gsm0808_cause cause) +{ + struct msgb *msg; + + msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM, "BSSMAP-VGCS/VBS-SETUP-REFUSE"); + if (!msg) + return NULL; + + /* Message Type, 3.2.2.1 */ + msgb_v_put(msg, BSS_MAP_MSG_VGCS_VBS_SETUP_REFUSE); + + /* Cause, 3.2.2.5 */ + gsm0808_enc_cause(msg, cause); + + /* prepend header with final length */ + msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg)); + + return msg; +} + +/*! Create BSSMAP VGCS/VBS ASSIGNMENT REQUEST message, 3GPP TS 48.008 3.2.1.53. + * Sent from the MSC to the BSC to assign radio resources for a VGCS/VBS. */ +struct msgb *gsm0808_create_vgcs_vbs_assign_req(const struct gsm0808_vgcs_vbs_assign_req *params) +{ + struct msgb *msg; + + msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM, "BSSMAP-VGCS/VBS-ASSIGNMENT-REQUEST"); + if (!msg) + return NULL; + + /* Message Type, 3.2.2.1 */ + msgb_v_put(msg, BSS_MAP_MSG_VGCS_VBS_ASSIGNMENT_RQST); + + /* Channel Type, 3.2.2.11 */ + gsm0808_enc_channel_type(msg, ¶ms->channel_type); + + /* Assignment Requrirement, 3.2.2.52 */ + gsm0808_enc_assign_req(msg, params->ass_req); + + /* Cell Identifier, 3.2.2.17 */ + gsm0808_enc_cell_id(msg, ¶ms->cell_identifier); + + /* Group Call Reference, 3.2.2.55 */ + gsm0808_enc_group_callref(msg, ¶ms->callref); + + /* Priority, 3.2.2.18 */ + if (params->priority_present) + gsm0808_enc_priority(msg, ¶ms->priority); + + /* Circuit Identity Code, 3.2.2.2 */ + if (params->cic_present) + msgb_tv16_put(msg, GSM0808_IE_CIRCUIT_IDENTITY_CODE, params->cic); + + /* Downlink DTX Flag, 3.2.2.26 */ + if (params->downlink_dtx_flag_present) + msgb_tv_put(msg, GSM0808_IE_DOWNLINK_DTX_FLAG, params->downlink_dtx_flag); + + /* Encryption Information, 3.2.2.10 */ + if (params->encryption_information_present) + gsm0808_enc_encrypt_info(msg, ¶ms->encryption_information); + + /* VSTK_RAND Imformation, 3.2.2.83 */ + if (params->vstk_rand_present) + msgb_tlv_put(msg, GSM0808_IE_VSTK_RAND_INFO, sizeof(params->vstk_rand), params->vstk_rand); + + /* VSTK Information, 3.2.2.84 */ + if (params->vstk_present) + msgb_tlv_put(msg, GSM0808_IE_VSTK_INFO, sizeof(params->vstk), params->vstk); + + /* Cell Identifier List Segment, 3.2.2.27a */ + if (params->cils_present) + gsm0808_enc_cell_id_list_segment(msg, GSM0808_IE_CELL_ID_LIST_SEGMENT, ¶ms->cils); + + /* AoIP Transport Layer Address (MGW), 3.2.2.102 */ + if (params->aoip_transport_layer_present) + gsm0808_enc_aoip_trasp_addr(msg, ¶ms->aoip_transport_layer); + + /* Call Identifier, 3.2.2.105 */ + if (params->call_id_present) { + /* NOTE: 3GPP TS 48.008, section 3.2.2.105 specifies that + * the least significant byte shall be transmitted first. */ + msgb_v_put(msg, GSM0808_IE_CALL_ID); + osmo_store32le(params->call_id, msgb_put(msg, sizeof(uint32_t))); + } + + /* Codec List (MSC Preferred) 3.2.2.103 */ + if (params->codec_list_present) { + if (gsm0808_enc_speech_codec_list2(msg, ¶ms->codec_list_msc_preferred) < 0) + goto exit_free; + } + + /* prepend header with final length */ + msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg)); + + return msg; + +exit_free: + msgb_free(msg); + return NULL; +} + +/*! Create BSSMAP VGCS/VBS ASSIGNMENT RESULT message, 3GPP TS 48.008 3.2.1.54. + * Sent from the BSC to the MSC to indicate assignment/deassingment of radio resources for a VGCS/VBS. */ +struct msgb *gsm0808_create_vgcs_vbs_assign_res(const struct gsm0808_vgcs_vbs_assign_res *params) +{ + struct msgb *msg; + + msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM, "BSSMAP-VGCS/VBS-ASSIGNMENT-RESULT"); + if (!msg) + return NULL; + + /* Message Type, 3.2.2.1 */ + msgb_v_put(msg, BSS_MAP_MSG_VGCS_VBS_ASSIGNMENT_RESULT); + + /* Channel Type, 3.2.2.11 */ + gsm0808_enc_channel_type(msg, ¶ms->channel_type); + + /* Cell Identifier, 3.2.2.17 */ + gsm0808_enc_cell_id(msg, ¶ms->cell_identifier); + + /* Chosen Channel, 3.2.2.33 */ + if (params->chosen_channel_present) + msgb_tv_put(msg, GSM0808_IE_CHOSEN_CHANNEL, params->chosen_channel); + + /* Circuit Identity Code, 3.2.2.2 */ + if (params->cic_present) + msgb_tv16_put(msg, GSM0808_IE_CIRCUIT_IDENTITY_CODE, params->cic); + + /* Circuit Pool, 3.2.2.45 */ + if (params->circuit_pool_present) + msgb_tv_put(msg, GSM0808_IE_CIRCUIT_POOL, params->circuit_pool); + + /* AoIP Transport Layer Address (BSS), 3.2.2.102 */ + if (params->aoip_transport_layer_present) + gsm0808_enc_aoip_trasp_addr(msg, ¶ms->aoip_transport_layer); + + /* Codec (MSC Chosen) 3.2.2.103 */ + if (params->codec_present) { + if (gsm0808_enc_speech_codec2(msg, ¶ms->codec_msc_chosen) < 0) + goto exit_free; + } + + /* Call Identifier, 3.2.2.105 */ + if (params->call_id_present) { + /* NOTE: 3GPP TS 48.008, section 3.2.2.105 specifies that + * the least significant byte shall be transmitted first. */ + msgb_v_put(msg, GSM0808_IE_CALL_ID); + osmo_store32le(params->call_id, msgb_put(msg, sizeof(uint32_t))); + } + + /* prepend header with final length */ + msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg)); + + return msg; + +exit_free: + msgb_free(msg); + return NULL; +} + +/*! Create BSSMAP VGCS/VBS ASSIGNMENT FAILURE message, 3GPP TS 48.008 3.2.1.55. + * Sent from the BSC to the MSC to indicate assignment failure for a VGCS/VBS. */ +struct msgb *gsm0808_create_vgcs_vbs_assign_fail(const struct gsm0808_vgcs_vbs_assign_fail *params) +{ + struct msgb *msg; + + msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM, "BSSMAP-VGCS/VBS-ASSIGNMENT-RESULT"); + if (!msg) + return NULL; + + /* Message Type, 3.2.2.1 */ + msgb_v_put(msg, BSS_MAP_MSG_VGCS_VBS_ASSIGNMENT_FAILURE); + + /* Cause, 3.2.2.5 */ + gsm0808_enc_cause(msg, params->cause); + + /* Circuit Pool, 3.2.2.45 */ + if (params->circuit_pool_present) + msgb_tv_put(msg, GSM0808_IE_CIRCUIT_POOL, params->circuit_pool); + + /* Circuit Pool List, 3.2.2.46 */ + if (params->circuit_pool_present) + msgb_tlv_put(msg, GSM0808_IE_CIRCUIT_POOL_LIST, params->cpl.list_len, params->cpl.pool); + + /* Codec List (BSS Supported) 3.2.2.103 */ + if (params->codec_list_present) { + if (gsm0808_enc_speech_codec_list2(msg, ¶ms->codec_list_bss_supported) < 0) + goto exit_free; + } + + /* prepend header with final length */ + msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg)); + + return msg; + +exit_free: + msgb_free(msg); + return NULL; +} + +/*! Create BSSMAP VGCS/VBS QUEUING INDICATION message, 3GPP TS 48.008 3.2.1.56. + * Sent from the BSC to the MSC to indicate delay in assignment for a VGCS/VBS. */ +struct msgb *gsm0808_create_vgcs_queuing_ind(void) +{ + struct msgb *msg; + uint8_t val = BSS_MAP_MSG_VGCS_VBS_QUEUING_INDICATION; + + msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM, "BSSMAP-VGCS/VBS-QUEUING-INDICATION"); + if (!msg) + return NULL; + + msg->l3h = msg->data; + msgb_tlv_put(msg, BSSAP_MSG_BSS_MANAGEMENT, 1, &val); + + return msg; +} + +/*! Create BSSMAP (VGCS) UPLINK REQUEST message, 3GPP TS 48.008 3.2.1.57. + * Sent from the BSC to the MSC to indicate that a mobile requested access to uplink. */ +struct msgb *gsm0808_create_uplink_request(const struct gsm0808_uplink_request *params) +{ + struct msgb *msg; + int rc; + + msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM, "BSSMAP-UPLINK-REQUEST"); + if (!msg) + return NULL; + + /* Message Type, 3.2.2.1 */ + msgb_v_put(msg, BSS_MAP_MSG_UPLINK_RQST); + + /* Talker Priority, 3.2.2.89 */ + if (params->talker_priority_present) + msgb_tv_put(msg, GSM0808_IE_TALKER_PRIORITY, params->talker_priority); + + /* Cell Identifier, 3.2.2.17 */ + if (params->cell_identifier_present) + gsm0808_enc_cell_id(msg, ¶ms->cell_identifier); + + /* Layer 3 Information, 3.2.2.24 */ + if (params->l3_present) + msgb_tlv_put(msg, GSM0808_IE_LAYER_3_INFORMATION, params->l3.l3_len, params->l3.l3); + + /* Mobile Identity, 3.2.2.41 */ + if (params->mi_present) { + rc = osmo_mobile_identity_encode_msgb(msg, ¶ms->mi, false); + if (rc < 0) { + msgb_free(msg); + return NULL; + } + } + + /* prepend header with final length */ + msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg)); + + return msg; +} + +/*! Create BSSMAP (VGCS) UPLINK REQUEST ACKNOWLEDGE message, 3GPP TS 48.008 3.2.1.58. + * Sent from the MSC to the BSC to indicate that access to uplink was granted. */ +struct msgb *gsm0808_create_uplink_request_ack(const struct gsm0808_uplink_request_ack *params) +{ + struct msgb *msg; + + msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM, "BSSMAP-UPLINK-REQUEST-ACKNOWLEDGE"); + if (!msg) + return NULL; + + /* Message Type, 3.2.2.1 */ + msgb_v_put(msg, BSS_MAP_MSG_UPLINK_RQST_ACKNOWLEDGE); + + /* Talker Priority, 3.2.2.89 */ + if (params->talker_priority_present) + msgb_tv_put(msg, GSM0808_IE_TALKER_PRIORITY, params->talker_priority); + + /* Emergency set indication, 3.2.2.90 */ + if (params->emerg_set_ind_present) + msgb_v_put(msg, GSM0808_IE_EMERGENCY_SET_INDICATION); + + /* Talker Identity, 3.2.2.91 */ + if (params->talker_identity_present) + gsm0808_enc_talker_identity(msg, ¶ms->talker_identity); + + /* prepend header with final length */ + msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg)); + + return msg; +} + +/*! Create BSSMAP (VGCS) UPLINK CONFIRM message, 3GPP TS 48.008 3.2.1.59. + * Sent from the BSC to the MSC to indicate that access to uplink was has been successfully established. */ +struct msgb *gsm0808_create_uplink_request_cnf(const struct gsm0808_uplink_request_cnf *params) +{ + struct msgb *msg; + + msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM, "BSSMAP-UPLINK-REQUEST-CONFIRM"); + if (!msg) + return NULL; + + /* Message Type, 3.2.2.1 */ + msgb_v_put(msg, BSS_MAP_MSG_UPLINK_RQST_CONFIRMATION); + + /* Cell Identifier, 3.2.2.17 */ + gsm0808_enc_cell_id(msg, ¶ms->cell_identifier); + + /* Talker Identity, 3.2.2.91 */ + if (params->talker_identity_present) + gsm0808_enc_talker_identity(msg, ¶ms->talker_identity); + + /* Layer 3 Information, 3.2.2.24 */ + msgb_tlv_put(msg, GSM0808_IE_LAYER_3_INFORMATION, params->l3.l3_len, params->l3.l3); + + /* prepend header with final length */ + msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg)); + + return msg; +} + +/*! Create BSSMAP (VGCS) UPLINK APPLICATION DATA message, 3GPP TS 48.008 3.2.1.59a. + * Sent from the BSC to the MSC to pass L3 info from the talker. */ +struct msgb *gsm0808_create_uplink_app_data(const struct gsm0808_uplink_app_data *params) +{ + struct msgb *msg; + uint8_t val; + + msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM, "BSSMAP-UPLINK-APPLICATION-DATA"); + if (!msg) + return NULL; + + /* Message Type, 3.2.2.1 */ + msgb_v_put(msg, BSS_MAP_MSG_UPLINK_APP_DATA); + + /* Cell Identifier, 3.2.2.17 */ + gsm0808_enc_cell_id(msg, ¶ms->cell_identifier); + + /* Layer 3 Information, 3.2.2.24 */ + msgb_tlv_put(msg, GSM0808_IE_LAYER_3_INFORMATION, params->l3.l3_len, params->l3.l3); + + /* Application Data Information, 3.2.2.100 */ + val = params->bt_ind; + msgb_tlv_put(msg, GSM0808_IE_APP_DATA_INFO, 1, &val); + + /* prepend header with final length */ + msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg)); + + return msg; +} + +/*! Create BSSMAP (VGCS) UPLINK RELEASE INDICATION message, 3GPP TS 48.008 3.2.1.60. + * Sent from the BSC to the MSC to indicate that the uplink has been released. */ +struct msgb *gsm0808_create_uplink_release_ind(const struct gsm0808_uplink_release_ind *params) +{ + struct msgb *msg; + + msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM, "BSSMAP-UPLINK-RELEASE-INDICATION"); + if (!msg) + return NULL; + + /* Message Type, 3.2.2.1 */ + msgb_v_put(msg, BSS_MAP_MSG_UPLINK_RELEASE_INDICATION); + + /* Cause, 3.2.2.5 */ + gsm0808_enc_cause(msg, params->cause); + + /* Talker Priority, 3.2.2.89 */ + if (params->talker_priority_present) + msgb_tv_put(msg, GSM0808_IE_TALKER_PRIORITY, params->talker_priority); + + /* prepend header with final length */ + msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg)); + + return msg; +} + +/*! Create BSSMAP (VGCS) UPLINK REJECT COMMAND message, 3GPP TS 48.008 3.2.1.61. + * Sent from the MSC to the BSC to indicate that the uplink is not available for allocation. */ +struct msgb *gsm0808_create_uplink_reject_cmd(const struct gsm0808_uplink_reject_cmd *params) +{ + struct msgb *msg; + + msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM, "BSSMAP-UPLINK-REJECT-COMMAND"); + if (!msg) + return NULL; + + /* Message Type, 3.2.2.1 */ + msgb_v_put(msg, BSS_MAP_MSG_UPLINK_REJECT_CMD); + + /* Cause, 3.2.2.5 */ + gsm0808_enc_cause(msg, params->cause); + + /* Talker Priority, 3.2.2.89 */ + if (params->current_talker_priority_present) + msgb_tv_put(msg, GSM0808_IE_TALKER_PRIORITY, params->current_talker_priority); + + /* Talker Priority, 3.2.2.89 */ + if (params->rejected_talker_priority_present) + msgb_tv_put(msg, GSM0808_IE_TALKER_PRIORITY, params->rejected_talker_priority); + + /* Talker Identity, 3.2.2.91 */ + if (params->talker_identity_present) + gsm0808_enc_talker_identity(msg, ¶ms->talker_identity); + + /* prepend header with final length */ + msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg)); + + return msg; +} + +/*! Create BSSMAP (VGCS) UPLINK RELEASE COMMAND message, 3GPP TS 48.008 3.2.1.62. + * Sent from the MSC to the BSC to indicate that the uplink is available for allocation. */ +struct msgb *gsm0808_create_uplink_release_cmd(const enum gsm0808_cause cause) +{ + struct msgb *msg; + + msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM, "BSSMAP-UPLINK-RELEASE-COMMAND"); + if (!msg) + return NULL; + + /* Message Type, 3.2.2.1 */ + msgb_v_put(msg, BSS_MAP_MSG_UPLINK_RELEASE_CMD); + + /* Cause, 3.2.2.5 */ + gsm0808_enc_cause(msg, cause); + + /* prepend header with final length */ + msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg)); + + return msg; +} + +/*! Create BSSMAP (VGCS) UPLINK SEIZED COMMAND message, 3GPP TS 48.008 3.2.1.62. + * Sent from the MSC to the BSC to indicate that the uplink is no longer available for allocation. */ +struct msgb *gsm0808_create_uplink_seized_cmd(const struct gsm0808_uplink_seized_cmd *params) +{ + struct msgb *msg; + + msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM, "BSSMAP-UPLINK-SEIZED-COMMAND"); + if (!msg) + return NULL; + + /* Message Type, 3.2.2.1 */ + msgb_v_put(msg, BSS_MAP_MSG_UPLINK_SEIZED_CMD); + + /* Cause, 3.2.2.5 */ + gsm0808_enc_cause(msg, params->cause); + + /* Talker Priority, 3.2.2.89 */ + if (params->talker_priority_present) + msgb_tv_put(msg, GSM0808_IE_TALKER_PRIORITY, params->talker_priority); + + /* Emergency set indication, 3.2.2.90 */ + if (params->emerg_set_ind_present) + msgb_v_put(msg, GSM0808_IE_EMERGENCY_SET_INDICATION); + + /* Talker Identity, 3.2.2.91 */ + if (params->talker_identity_present) + gsm0808_enc_talker_identity(msg, ¶ms->talker_identity); + + /* prepend header with final length */ + msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg)); + + return msg; +} + +/*! Create BSSMAP VGCS ADDITIONAL INFORMATION message, 3GPP TS 48.008 3.2.1.78. + * Sent from the MSC to the BSC to transfer talker identity. */ +struct msgb *gsm0808_create_vgcs_additional_info(const struct gsm0808_talker_identity *ti) +{ + struct msgb *msg; + + msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM, "BSSMAP-VGCS-ADDITIONAL-INFO"); + if (!msg) + return NULL; + + /* Message Type, 3.2.2.1 */ + msgb_v_put(msg, BSS_MAP_MSG_VGCS_ADDL_INFO); + + /* Talker Identity, 3.2.2.91 */ + gsm0808_enc_talker_identity(msg, ti); + + /* prepend header with final length */ + msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg)); + + return msg; +} + +/*! Create BSSMAP VGCS/VBS AREA CELL INFO message, 3GPP TS 48.008 3.2.1.79. + * Sent from the BSC to the MSC to transfer additional infos about cells. */ +struct msgb *gsm0808_create_vgcs_vbs_area_cell_info(const struct gsm0808_vgcs_vbs_area_cell_info *params) +{ + struct msgb *msg; + + msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM, "BSSMAP-VGCS/VBS-AREA-CELL-INFO"); + if (!msg) + return NULL; + + /* Message Type, 3.2.2.1 */ + msgb_v_put(msg, BSS_MAP_MSG_VGCS_VBS_ASSIGNMENT_RQST); + + /* Cell Identifier List Segment, 3.2.2.27a */ + gsm0808_enc_cell_id_list_segment(msg, GSM0808_IE_CELL_ID_LIST_SEGMENT, ¶ms->cils); + + /* Assignment Requrirement, 3.2.2.52 */ + if (params->ass_req_present) + gsm0808_enc_assign_req(msg, params->ass_req); + + /* prepend header with final length */ + msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg)); + + return msg; +} + +/*! Create BSSMAP VGCS/VBS ASSIGNMENT STATUS message, 3GPP TS 48.008 3.2.1.80. + * Sent from the BSC to the MSC to indicate assignment status for each cell. */ +struct msgb *gsm0808_create_vgcs_vbs_assign_stat(const struct gsm0808_vgcs_vbs_assign_stat *params) +{ + struct msgb *msg; + uint8_t val; + + msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM, "BSSMAP-VGCS/VBS-ASSIGNMENT-STATUS"); + if (!msg) + return NULL; + + /* Message Type, 3.2.2.1 */ + msgb_v_put(msg, BSS_MAP_MSG_VGCS_VBS_ASSIGNMENT_STATUS); + + /* Cell Identifier List Segment, 3.2.2.27b */ + if (params->cils_est_present) + gsm0808_enc_cell_id_list_segment(msg, GSM0808_IE_CELL_ID_LIST_SEG_EST_CELLS, ¶ms->cils_est); + /* Cell Identifier List Segment, 3.2.2.27c */ + if (params->cils_tbe_present) + gsm0808_enc_cell_id_list_segment(msg, GSM0808_IE_CELL_ID_LIST_SEG_CELLS_TBE, ¶ms->cils_tbe); + /* Cell Identifier List Segment, 3.2.2.27e */ + if (params->cils_rel_present) + gsm0808_enc_cell_id_list_segment(msg, GSM0808_IE_CELL_ID_LIST_SEG_REL_CELLS, ¶ms->cils_rel); + /* Cell Identifier List Segment, 3.2.2.27f */ + if (params->cils_ne_present) + gsm0808_enc_cell_id_list_segment(msg, GSM0808_IE_CELL_ID_LIST_SEG_NE_CELLS, ¶ms->cils_ne); + + /* VGCS/VBS Cell Status, 3.2.2.94 */ + if (params->cell_status_present) { + val = params->cell_status; + msgb_tlv_put(msg, GSM0808_IE_VGCS_VBS_CELL_STATUS, 1, &val); + } + + /* prepend header with final length */ + msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg)); + + return msg; +} + +/*! Create BSSMAP VGCS SMS message, 3GPP TS 48.008 3.2.1.81. + * Sent from the MSC to the BSC to send an SMS to VGCS. */ +struct msgb *gsm0808_create_vgcs_sms(const struct gsm0808_sms_to_vgcs *sms) +{ + struct msgb *msg; + + msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM, "BSSMAP-VGCS-SMS"); + if (!msg) + return NULL; + + /* SMS to VGCS, 3.2.2.92 */ + msgb_tlv_put(msg, GSM0808_IE_VGCS_VBS_CELL_STATUS, sms->sms_len, sms->sms); + + /* prepend header with final length */ + msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg)); + + return msg; +} + +/*! Create BSSMAP (VGCS/VBS) NOTIFICATION DATA message, 3GPP TS 48.008 3.2.1.82. + * Sent from the MSC to the BSC to send application specific data. */ +struct msgb *gsm0808_create_notification_data(const struct gsm0808_notification_data *params) +{ + struct msgb *msg; + + msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM, "BSSMAP-VGCS-SMS"); + if (!msg) + return NULL; + + /* Message Type, 3.2.2.1 */ + msgb_v_put(msg, BSS_MAP_MSG_NOTIFICATION_DATA); + + /* Application Data, 3.2.2.98 */ + msgb_tlv_put(msg, GSM0808_IE_APP_DATA, params->app_data.data_len, params->app_data.data); + + /* Data Identity, 3.2.2.99 */ + gsm0808_enc_data_identity(msg, ¶ms->data_ident); + + /* MSISDN, 3.2.2.101 */ + if (params->msisdn_present) + gsm0808_enc_msisdn(msg, params->msisdn); + + /* prepend header with final length */ + msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg)); + + return msg; +} + +/* Note that EMERGENCY RESET INDICATION and EMERGENCY RESET COMMAND cannot be implemented, due to lack of + * message type information in the specifications. */ + /* As per 3GPP TS 48.008 version 11.7.0 Release 11 */ static const struct tlv_definition bss_att_tlvdef = { .def = { @@ -1523,6 +2244,7 @@ static const struct tlv_definition bss_att_tlvdef = { [GSM0808_IE_LOCATION_ESTIMATE] = { TLV_TYPE_TLV }, [GSM0808_IE_POSITIONING_DATA] = { TLV_TYPE_TLV }, [GSM0808_IE_LCS_CAUSE] = { TLV_TYPE_TLV }, + [GSM0808_IE_LCS_CLIENT_TYPE] = { TLV_TYPE_TLV }, [GSM0808_IE_APDU] = { TLV_TYPE_TLV }, [GSM0808_IE_NETWORK_ELEMENT_IDENTITY] = { TLV_TYPE_TLV }, [GSM0808_IE_GPS_ASSISTANCE_DATA] = { TLV_TYPE_TLV }, @@ -1539,6 +2261,7 @@ static const struct tlv_definition bss_att_tlvdef = { [GSM0800_IE_INTER_SYSTEM_INFO] = { TLV_TYPE_TLV }, [GSM0808_IE_SNA_ACCESS_INFO] = { TLV_TYPE_TLV }, [GSM0808_IE_VSTK_RAND_INFO] = { TLV_TYPE_TLV }, + [GSM0808_IE_VSTK_INFO] = { TLV_TYPE_TLV }, [GSM0808_IE_PAGING_INFO] = { TLV_TYPE_TV }, [GSM0808_IE_IMEI] = { TLV_TYPE_TLV }, [GSM0808_IE_VELOCITY_ESTIMATE] = { TLV_TYPE_TLV }, @@ -1581,6 +2304,11 @@ static const struct tlv_definition bss_att_tlvdef = { [GSM0808_IE_CN_TO_MS_TRANSP_INFO] = { TLV_TYPE_TLV }, [GSM0808_IE_SELECTED_PLMN_ID] = { TLV_TYPE_FIXED, 3 }, [GSM0808_IE_LAST_USED_EUTRAN_PLMN_ID] = { TLV_TYPE_FIXED, 3 }, + [GSM0808_IE_OLD_LAI] = { TLV_TYPE_FIXED, 5 }, + [GSM0808_IE_ATTACH_INDICATOR] = { TLV_TYPE_T }, + [GSM0808_IE_SELECTED_OPERATOR] = { TLV_TYPE_FIXED, 3 }, + [GSM0808_IE_PS_REGISTERED_OPERATOR] = { TLV_TYPE_FIXED, 3 }, + [GSM0808_IE_CS_REGISTERED_OPERATOR] = { TLV_TYPE_FIXED, 3 }, /* Osmocom extensions */ [GSM0808_IE_OSMO_OSMUX_SUPPORT] = { TLV_TYPE_T }, @@ -1706,7 +2434,9 @@ static const struct value_string gsm0808_msgt_names[] = { { BSS_MAP_MSG_VGCS_VBS_ASSIGNMENT_RQST, "VGCS/VBS ASSIGN REQ" }, { BSS_MAP_MSG_VGCS_VBS_ASSIGNMENT_RESULT, "VGCS/VBS ASSIGN RES" }, { BSS_MAP_MSG_VGCS_VBS_ASSIGNMENT_FAILURE, "VGCS/VBS ASSIGN FAIL" }, + { BSS_MAP_MSG_VGCS_VBS_ASSIGNMENT_STATUS, "VGCS/VBS ASSIGN STATUS" }, { BSS_MAP_MSG_VGCS_VBS_QUEUING_INDICATION, "VGCS/VBS QUEUING IND" }, + { BSS_MAP_MSG_VGCS_VBS_AREA_CELL_INFO, "VGCS/VBS AREA CELL INFO" }, { BSS_MAP_MSG_UPLINK_RQST, "UPLINK REQ" }, { BSS_MAP_MSG_UPLINK_RQST_ACKNOWLEDGE, "UPLINK REQ ACK" }, { BSS_MAP_MSG_UPLINK_RQST_CONFIRMATION, "UPLINK REQ CONF" }, @@ -1715,11 +2445,12 @@ static const struct value_string gsm0808_msgt_names[] = { { BSS_MAP_MSG_UPLINK_RELEASE_CMD, "UPLINK REL CMD" }, { BSS_MAP_MSG_UPLINK_SEIZED_CMD, "UPLINK SEIZED CMD" }, { BSS_MAP_MSG_VGCS_ADDL_INFO, "VGCS ADDL INFO" }, + { BSS_MAP_MSG_VGCS_SMS, "VGCS SMS" }, { BSS_MAP_MSG_NOTIFICATION_DATA, "NOTIF DATA" }, { BSS_MAP_MSG_UPLINK_APP_DATA, "UPLINK APP DATA" }, { BSS_MAP_MSG_LCLS_CONNECT_CTRL, "LCLS-CONNECT-CONTROL" }, - { BSS_MAP_MSG_LCLS_CONNECT_CTRL_ACK, "CLS-CONNECT-CONTROL-ACK" }, + { BSS_MAP_MSG_LCLS_CONNECT_CTRL_ACK, "LCLS-CONNECT-CONTROL-ACK" }, { BSS_MAP_MSG_LCLS_NOTIFICATION, "LCLS-NOTIFICATION" }, { 0, NULL } @@ -1753,6 +2484,7 @@ const struct value_string gsm0808_speech_codec_type_names[] = { { GSM0808_SCT_HR3, "HR3" }, { GSM0808_SCT_HR4, "HR4" }, { GSM0808_SCT_HR6, "HR6" }, + { GSM0808_SCT_EXT, "Codec Extension" }, { GSM0808_SCT_CSD, "CSD" }, { 0, NULL } }; @@ -1959,4 +2691,101 @@ const struct value_string gsm0808_lcls_status_names[] = { { 0, NULL } }; +/* Convert one S0-S15 bit to its set of AMR modes, for HR AMR and FR AMR. + * This is 3GPP TS 28.062 Table 7.11.3.1.3-2: "Preferred Configurations", with some configurations removed as specified + * in 3GPP TS 48.008 3.2.2.103: + * + * FR_AMR is coded ‘0011’. + * S11, S13 and S15 are reserved and coded with zeroes. + * + * HR_AMR is coded ‘0100’. + * S6 - S7 and S11 – S15 are reserved and coded with zeroes. + * + * Meaning: for FR, exclude all Optimisation Mode configurations. + * For HR, exclude all that are not supported by HR AMR -- drop all that include at least one of + * 10.2 or 12.2. + * + * Also, for HR, drop 12.2k from S1. + * + * The first array dimension is 0 for half rate and 1 for full rate. + * The second array dimension is the configuration number (0..15) aka Sn. + * The values are bitmask combinations of (1 << GSM0808_AMR_MODE_nnnn). + * + * For example, accumulate all modes that are possible in a given my_s15_s0: + * + * uint8_t modes = 0; + * for (s_bit = 0; s_bit < 15; s_bit++) + * if (my_s15_s0 & (1 << s_bit)) + * modes |= gsm0808_amr_modes_from_cfg[full_rate ? 1 : 0][s_bit]; + * for (i = 0; i < 8; i++) + * if (modes & (1 << i)) + * printf(" %s", gsm0808_amr_mode_name(i)); + */ +const uint8_t gsm0808_amr_modes_from_cfg[2][16] = { +#define B(X) (1 << (GSM0808_AMR_MODE_##X)) + /* HR */ + { + /* Sn = modes */ + [0] = B(4_75), + [1] = B(4_75) | B(5_90) | B(7_40), + [2] = B(5_90), + [3] = B(6_70), + [4] = B(7_40), + [5] = B(7_95), + [6] = 0, + [7] = 0, + + [8] = B(4_75) | B(5_90), + [9] = B(4_75) | B(5_90) | B(6_70), + [10] = B(4_75) | B(5_90) | B(6_70) | B(7_40), + [11] = 0, + [12] = 0, + [13] = 0, + [14] = 0, + [15] = 0, + }, + /* FR */ + { + /* Sn = modes */ + [0] = B(4_75), + [1] = B(4_75) | B(5_90) | B(7_40) | B(12_2), + [2] = B(5_90), + [3] = B(6_70), + [4] = B(7_40), + [5] = B(7_95), + [6] = B(10_2), + [7] = B(12_2), + + [8] = B(4_75) | B(5_90), + [9] = B(4_75) | B(5_90) | B(6_70), + [10] = B(4_75) | B(5_90) | B(6_70) | B(7_40), + [11] = 0, + [12] = B(4_75) | B(5_90) | B(6_70) | B(10_2), + [13] = 0, + [14] = B(4_75) | B(5_90) | B(7_95) | B(12_2), + [15] = 0, + } +}; + +/* AMR mode names from GSM0808_AMR_MODE_*, for use with gsm0808_amr_modes_from_cfg. + * + * For example: + * printf("S9: "); + * uint8_t s9_modes = gsm0808_amr_modes_from_cfg[full_rate ? 1 : 0][9]; + * for (bit = 0; bit < 8; bit++) + * if (s9_modes & (1 << bit)) + * printf("%s,", gsm0808_amr_mode_name(bit)); + */ +const struct value_string gsm0808_amr_mode_names[] = { + { GSM0808_AMR_MODE_4_75, "4.75" }, + { GSM0808_AMR_MODE_5_15, "5.15" }, + { GSM0808_AMR_MODE_5_90, "5.90" }, + { GSM0808_AMR_MODE_6_70, "6.70" }, + { GSM0808_AMR_MODE_7_40, "7.40" }, + { GSM0808_AMR_MODE_7_95, "7.95" }, + { GSM0808_AMR_MODE_10_2, "10.2" }, + { GSM0808_AMR_MODE_12_2, "12.2" }, + {} +}; + /*! @} */ diff --git a/src/gsm/gsm0808_utils.c b/src/gsm/gsm0808_utils.c index 78cbe92e..c9f26d35 100644 --- a/src/gsm/gsm0808_utils.c +++ b/src/gsm/gsm0808_utils.c @@ -78,7 +78,7 @@ uint8_t gsm0808_enc_cause(struct msgb *msg, uint16_t cause) /*! Encode TS 08.08 AoIP transport address IE * \param[out] msg Message Buffer to which to append IE * \param[in] ss Socket Address to be used in IE - * \returns number of bytes added to \a msg */ + * \returns number of bytes added to \a msg; 0 if msg is too small */ uint8_t gsm0808_enc_aoip_trasp_addr(struct msgb *msg, const struct sockaddr_storage *ss) { @@ -87,16 +87,25 @@ uint8_t gsm0808_enc_aoip_trasp_addr(struct msgb *msg, struct sockaddr_in6 *sin6; uint16_t port = 0; uint8_t *ptr; - uint8_t *old_tail; - uint8_t *tlv_len; + const uint8_t len_tl = 2; + uint8_t len_v = sizeof(port); - OSMO_ASSERT(msg); - OSMO_ASSERT(ss); OSMO_ASSERT(ss->ss_family == AF_INET || ss->ss_family == AF_INET6); + switch (ss->ss_family) { + case AF_INET: + len_v += IP_V4_ADDR_LEN; + break; + case AF_INET6: + len_v += IP_V6_ADDR_LEN; + break; + } + + if (msgb_tailroom(msg) < len_tl + len_v) + return 0; + msgb_put_u8(msg, GSM0808_IE_AOIP_TRASP_ADDR); - tlv_len = msgb_put(msg,1); - old_tail = msg->tail; + msgb_put_u8(msg, len_v); switch (ss->ss_family) { case AF_INET: @@ -114,9 +123,7 @@ uint8_t gsm0808_enc_aoip_trasp_addr(struct msgb *msg, } msgb_put_u16(msg, port); - - *tlv_len = (uint8_t) (msg->tail - old_tail); - return *tlv_len + 2; + return len_tl + len_v; } /*! Decode TS 08.08 AoIP transport address IE @@ -132,7 +139,6 @@ int gsm0808_dec_aoip_trasp_addr(struct sockaddr_storage *ss, struct sockaddr_in6 sin6; const uint8_t *old_elem = elem; - OSMO_ASSERT(ss); if (!elem) return -EINVAL; if (len == 0) @@ -180,7 +186,6 @@ int gsm0808_dec_aoip_trasp_addr(struct sockaddr_storage *ss, * \returns number of bytes parsed */ int gsm0808_dec_osmux_cid(uint8_t *cid, const uint8_t *elem, uint8_t len) { - OSMO_ASSERT(cid); if (!elem) return -EINVAL; if (len != 1) @@ -204,21 +209,21 @@ static void decode_lai(const uint8_t *data, struct osmo_location_area_id *decode gsm48_decode_lai2(&lai, decoded); } -/* Helper function for gsm0808_enc_speech_codec() - * and gsm0808_enc_speech_codec_list() */ -static uint8_t enc_speech_codec(struct msgb *msg, - const struct gsm0808_speech_codec *sc) +/* Helper function for gsm0808_enc_speech_codec[_list](). + * Returns number of bytes appended; negative on error. */ +static int enc_speech_codec(struct msgb *msg, + const struct gsm0808_speech_codec *sc) { /* See also 3GPP TS 48.008 3.2.2.103 Speech Codec List */ uint8_t header = 0; uint8_t *old_tail; - bool type_extended = false; + bool type_extended; /* Note: Extended codec types are codec types that require 8 instead * of 4 bit to fully specify the selected codec. In the following, * we check if we work with an extended type or not. We also check * if the codec type is valid at all. */ - switch(sc->type) { + switch (sc->type) { case GSM0808_SCT_FR1: case GSM0808_SCT_FR2: case GSM0808_SCT_FR3: @@ -235,8 +240,7 @@ static uint8_t enc_speech_codec(struct msgb *msg, break; default: /* Invalid codec type specified */ - OSMO_ASSERT(false); - break; + return -EINVAL; } old_tail = msg->tail; @@ -251,11 +255,10 @@ static uint8_t enc_speech_codec(struct msgb *msg, header |= (1 << 4); if (type_extended) { - header |= 0x0f; + header |= GSM0808_SCT_EXT; msgb_put_u8(msg, header); msgb_put_u8(msg, sc->type); } else { - OSMO_ASSERT(sc->type < 0x0f); header |= sc->type; msgb_put_u8(msg, header); } @@ -273,11 +276,13 @@ static uint8_t enc_speech_codec(struct msgb *msg, case GSM0808_SCT_FR5: case GSM0808_SCT_HR4: case GSM0808_SCT_CSD: - OSMO_ASSERT((sc->cfg & 0xff00) == 0); + if (sc->cfg >> 8) + return -EINVAL; msgb_put_u8(msg, (uint8_t) sc->cfg & 0xff); break; default: - OSMO_ASSERT(sc->cfg == 0); + if (sc->cfg != 0) + return -EINVAL; break; } @@ -287,27 +292,38 @@ static uint8_t enc_speech_codec(struct msgb *msg, /*! Encode TS 08.08 Speech Codec IE * \param[out] msg Message Buffer to which IE will be appended * \param[in] sc Speech Codec to be encoded into IE - * \returns number of bytes appended to \a msg */ -uint8_t gsm0808_enc_speech_codec(struct msgb *msg, - const struct gsm0808_speech_codec *sc) + * \returns number of bytes appended to \a msg; negative on error */ +int gsm0808_enc_speech_codec2(struct msgb *msg, + const struct gsm0808_speech_codec *sc) { /*! See also 3GPP TS 48.008 3.2.2.103 Speech Codec List */ - uint8_t *old_tail; uint8_t *tlv_len; - - OSMO_ASSERT(msg); - OSMO_ASSERT(sc); + int rc; msgb_put_u8(msg, GSM0808_IE_SPEECH_CODEC); tlv_len = msgb_put(msg, 1); - old_tail = msg->tail; - enc_speech_codec(msg, sc); + rc = enc_speech_codec(msg, sc); + if (rc < 0) + return rc; - *tlv_len = (uint8_t) (msg->tail - old_tail); + *tlv_len = rc; return *tlv_len + 2; } +/*! Deprecated: gsm0808_enc_speech_codec2() wrapper for backwards compatibility. + * Mimics the old behavior: crash on missing and/or invalid input. */ +uint8_t gsm0808_enc_speech_codec(struct msgb *msg, + const struct gsm0808_speech_codec *sc) +{ + int rc; + + rc = gsm0808_enc_speech_codec2(msg, sc); + OSMO_ASSERT(rc > 0); + + return rc; +} + /*! Decode TS 08.08 Speech Codec IE * \param[out] sc Caller-allocated memory for Speech Codec * \param[in] elem IE value to be decoded @@ -320,7 +336,6 @@ int gsm0808_dec_speech_codec(struct gsm0808_speech_codec *sc, uint8_t header; const uint8_t *old_elem = elem; - OSMO_ASSERT(sc); if (!elem) return -EINVAL; if (len == 0) @@ -332,7 +347,7 @@ int gsm0808_dec_speech_codec(struct gsm0808_speech_codec *sc, /* An extended codec type needs at least two fields, * bail if the input data length is not sufficient. */ - if ((header & 0x0F) == 0x0F && len < 2) + if ((header & 0x0F) == GSM0808_SCT_EXT && len < 2) return -EINVAL; elem++; @@ -347,7 +362,7 @@ int gsm0808_dec_speech_codec(struct gsm0808_speech_codec *sc, if (header & (1 << 4)) sc->tf = true; - if ((header & 0x0F) != 0x0F) { + if ((header & 0x0F) != GSM0808_SCT_EXT) { sc->type = (header & 0x0F); } else { sc->type = *elem; @@ -392,35 +407,46 @@ int gsm0808_dec_speech_codec(struct gsm0808_speech_codec *sc, /*! Encode TS 08.08 Speech Codec list * \param[out] msg Message Buffer to which IE is to be appended * \param[in] scl Speech Codec List to be encoded into IE - * \returns number of bytes added to \a msg */ -uint8_t gsm0808_enc_speech_codec_list(struct msgb *msg, - const struct gsm0808_speech_codec_list *scl) + * \returns number of bytes added to \a msg; negative on error */ +int gsm0808_enc_speech_codec_list2(struct msgb *msg, + const struct gsm0808_speech_codec_list *scl) { /*! See also 3GPP TS 48.008 3.2.2.103 Speech Codec List */ uint8_t *old_tail; uint8_t *tlv_len; unsigned int i; - uint8_t rc; unsigned int bytes_used = 0; - OSMO_ASSERT(msg); - OSMO_ASSERT(scl); - msgb_put_u8(msg, GSM0808_IE_SPEECH_CODEC_LIST); tlv_len = msgb_put(msg, 1); old_tail = msg->tail; for (i = 0; i < scl->len; i++) { - rc = enc_speech_codec(msg, &scl->codec[i]); - OSMO_ASSERT(rc >= 1); + int rc = enc_speech_codec(msg, &scl->codec[i]); + if (rc < 1) + return rc; bytes_used += rc; - OSMO_ASSERT(bytes_used <= 255); + if (bytes_used > 0xff) + return -EOVERFLOW; } *tlv_len = (uint8_t) (msg->tail - old_tail); return *tlv_len + 2; } +/*! Deprecated: gsm0808_enc_speech_codec_list2() wrapper for backwards compatibility. + * Mimics the old behavior: crash on missing and/or invalid input. */ +uint8_t gsm0808_enc_speech_codec_list(struct msgb *msg, + const struct gsm0808_speech_codec_list *scl) +{ + int rc; + + rc = gsm0808_enc_speech_codec_list2(msg, scl); + OSMO_ASSERT(rc > 0); + + return rc; +} + /*! Decode TS 08.08 Speech Codec list IE * \param[out] scl Caller-provided memory to store codec list * \param[in] elem IE value to be decoded @@ -435,7 +461,6 @@ int gsm0808_dec_speech_codec_list(struct gsm0808_speech_codec_list *scl, int rc; uint8_t decoded = 0; - OSMO_ASSERT(scl); if (!elem) return -EINVAL; @@ -472,16 +497,8 @@ uint8_t gsm0808_enc_channel_type(struct msgb *msg, uint8_t *old_tail; uint8_t *tlv_len; - OSMO_ASSERT(msg); - OSMO_ASSERT(ct); OSMO_ASSERT(ct->perm_spch_len <= CHANNEL_TYPE_ELEMENT_MAXLEN - 2); - /* FIXME: Implement encoding support for Data - * and Speech + CTM Text Telephony */ - if ((ct->ch_indctr & 0x0f) != GSM0808_CHAN_SPEECH - && (ct->ch_indctr & 0x0f) != GSM0808_CHAN_SIGN) - OSMO_ASSERT(false); - msgb_put_u8(msg, GSM0808_IE_CHANNEL_TYPE); tlv_len = msgb_put(msg, 1); old_tail = msg->tail; @@ -489,12 +506,45 @@ uint8_t gsm0808_enc_channel_type(struct msgb *msg, msgb_put_u8(msg, ct->ch_indctr & 0x0f); msgb_put_u8(msg, ct->ch_rate_type); - for (i = 0; i < ct->perm_spch_len; i++) { - byte = ct->perm_spch[i]; + switch (ct->ch_indctr) { + case GSM0808_CHAN_DATA: + byte = ct->data_rate; + + if (!ct->data_transparent) + byte |= 0x40; /* Set T/NT */ + + if (ct->data_rate_allowed_is_set) { + OSMO_ASSERT(!ct->data_transparent); + byte |= 0x80; /* Set ext */ + msgb_put_u8(msg, byte); + + byte = ct->data_rate_allowed; + if (ct->data_asym_pref_is_set) { + byte |= 0x80; /* Set ext */ + msgb_put_u8(msg, byte); - if (i < ct->perm_spch_len - 1) - byte |= 0x80; + /* Set asymmetry indication, rest is spare */ + byte = ct->data_asym_pref << 5; + } + } msgb_put_u8(msg, byte); + break; + case GSM0808_CHAN_SPEECH: + case GSM0808_CHAN_SPEECH_CTM_TEXT_TELEPHONY: + for (i = 0; i < ct->perm_spch_len; i++) { + byte = ct->perm_spch[i]; + + if (i < ct->perm_spch_len - 1) + byte |= 0x80; + msgb_put_u8(msg, byte); + } + break; + case GSM0808_CHAN_SIGN: + /* Octet 5 is spare */ + msgb_put_u8(msg, 0); + break; + default: + OSMO_ASSERT(false); } *tlv_len = (uint8_t) (msg->tail - old_tail); @@ -514,7 +564,6 @@ int gsm0808_dec_channel_type(struct gsm0808_channel_type *ct, uint8_t byte; const uint8_t *old_elem = elem; - OSMO_ASSERT(ct); if (!elem) return -EINVAL; if (len < 3 || len > 11) @@ -524,17 +573,60 @@ int gsm0808_dec_channel_type(struct gsm0808_channel_type *ct, ct->ch_indctr = (*elem) & 0x0f; elem++; - ct->ch_rate_type = (*elem) & 0x0f; + ct->ch_rate_type = *elem; elem++; - for (i = 0; i < ARRAY_SIZE(ct->perm_spch); i++) { + switch (ct->ch_indctr) { + case GSM0808_CHAN_DATA: byte = *elem; elem++; - ct->perm_spch[i] = byte & 0x7f; - if ((byte & 0x80) == 0x00) - break; + ct->data_transparent = !(byte & 0x40); /* T/NT */ + ct->data_rate = byte & 0x3f; + + /* Optional extension for non-transparent service */ + if (byte & 0x80) { + if (ct->data_transparent) + return -EINVAL; + if (elem - old_elem >= len) + return -EOVERFLOW; + byte = *elem; + elem++; + + ct->data_rate_allowed_is_set = true; + ct->data_rate_allowed = byte & 0x7f; + + /* Optional extension */ + if (byte & 0x80) { + if (elem - old_elem >= len) + return -EOVERFLOW; + byte = *elem; + elem++; + + ct->data_asym_pref_is_set = true; + ct->data_asym_pref = byte & 0x60 >> 5; + } + } + break; + case GSM0808_CHAN_SPEECH: + case GSM0808_CHAN_SPEECH_CTM_TEXT_TELEPHONY: + for (i = 0; i < ARRAY_SIZE(ct->perm_spch); i++) { + if (elem - old_elem >= len) + return -EOVERFLOW; + + byte = *elem; + elem++; + ct->perm_spch[i] = byte & 0x7f; + if ((byte & 0x80) == 0x00) + break; + } + ct->perm_spch_len = i + 1; + break; + case GSM0808_CHAN_SIGN: + /* Octet 5 is spare */ + break; + default: + return -ENOTSUP; } - ct->perm_spch_len = i + 1; return (int)(elem - old_elem); } @@ -703,8 +795,6 @@ uint8_t gsm0808_enc_encrypt_info(struct msgb *msg, uint8_t *old_tail; uint8_t *tlv_len; - OSMO_ASSERT(msg); - OSMO_ASSERT(ei); OSMO_ASSERT(ei->key_len <= ARRAY_SIZE(ei->key)); OSMO_ASSERT(ei->perm_algo_len <= ENCRY_INFO_PERM_ALGO_MAXLEN); @@ -743,7 +833,6 @@ int gsm0808_dec_encrypt_info(struct gsm0808_encrypt_info *ei, unsigned int perm_algo_len = 0; const uint8_t *old_elem = elem; - OSMO_ASSERT(ei); if (!elem) return -EINVAL; if (len == 0) @@ -831,6 +920,10 @@ static void cell_id_to_cgi(struct osmo_cell_global_id *dst, dst->lai.lac = u->lac; return; + case CELL_IDENT_SAI: + dst->lai = u->sai.lai; + return; + case CELL_IDENT_NO_CELL: case CELL_IDENT_BSS: case CELL_IDENT_UTRAN_PLMN_LAC_RNC: @@ -858,6 +951,8 @@ int gsm0808_cell_id_size(enum CELL_IDENT discr) case CELL_IDENT_BSS: case CELL_IDENT_NO_CELL: return 0; + case CELL_IDENT_SAI: + return 7; case CELL_IDENT_WHOLE_GLOBAL_PS: return 8; default: @@ -904,6 +999,12 @@ int gsm0808_decode_cell_id_u(union gsm0808_cell_id_u *out, enum CELL_IDENT discr case CELL_IDENT_NO_CELL: /* Does not have any list items */ break; + case CELL_IDENT_SAI: + if (len < 7) + return -EINVAL; + decode_lai(buf, &out->sai.lai); + out->sai.sac = osmo_load16be(buf + sizeof(struct gsm48_loc_area_id)); + break; case CELL_IDENT_WHOLE_GLOBAL_PS: /* 3GPP TS 48.018 sec 11.3.9 "Cell Identifier" */ if (len < 8) @@ -953,6 +1054,16 @@ void gsm0808_msgb_put_cell_id_u(struct msgb *msg, enum CELL_IDENT id_discr, cons case CELL_IDENT_NO_CELL: /* Does not have any list items */ break; + + case CELL_IDENT_SAI: { + const struct osmo_service_area_id *id = &u->sai; + struct gsm48_loc_area_id lai; + gsm48_generate_lai2(&lai, &id->lai); + memcpy(msgb_put(msg, sizeof(lai)), &lai, sizeof(lai)); + msgb_put_u16(msg, id->sac); + break; + } + case CELL_IDENT_WHOLE_GLOBAL_PS: { /* 3GPP TS 48.018 sec 11.3.9 "Cell Identifier" */ const struct osmo_cell_global_id_ps *id = &u->global_ps; @@ -981,9 +1092,6 @@ uint8_t gsm0808_enc_cell_id_list2(struct msgb *msg, unsigned int i; uint8_t id_discr; - OSMO_ASSERT(msg); - OSMO_ASSERT(cil); - msgb_put_u8(msg, GSM0808_IE_CELL_IDENTIFIER_LIST); tlv_len = msgb_put(msg, 1); old_tail = msg->tail; @@ -1025,9 +1133,6 @@ uint8_t gsm0808_enc_cell_id_list(struct msgb *msg, uint8_t *tlv_len; unsigned int i; - OSMO_ASSERT(msg); - OSMO_ASSERT(cil); - msgb_put_u8(msg, GSM0808_IE_CELL_IDENTIFIER_LIST); tlv_len = msgb_put(msg, 1); old_tail = msg->tail; @@ -1166,6 +1271,31 @@ static int parse_cell_id_lac_list(struct gsm0808_cell_id_list2 *cil, const uint8 return i; } +static int parse_cell_id_sai_list(struct gsm0808_cell_id_list2 *cil, const uint8_t *data, size_t remain, size_t *consumed) +{ + struct osmo_service_area_id *id; + uint16_t *sac_be; + size_t lai_offset; + int i = 0; + const size_t elemlen = sizeof(struct gsm48_loc_area_id) + sizeof(*sac_be); + + *consumed = 0; + while (remain >= elemlen) { + if (i >= GSM0808_CELL_ID_LIST2_MAXLEN) + return -ENOSPC; + id = &cil->id_list[i].sai; + lai_offset = i * elemlen; + decode_lai(&data[lai_offset], &id->lai); + sac_be = (uint16_t *)(&data[lai_offset + sizeof(struct gsm48_loc_area_id)]); + id->sac = osmo_load16be(sac_be); + *consumed += elemlen; + remain -= elemlen; + i++; + } + + return i; +} + /*! Decode Cell Identifier List IE * \param[out] cil Caller-provided memory to store Cell ID list * \param[in] elem IE value to be decoded @@ -1178,7 +1308,6 @@ int gsm0808_dec_cell_id_list2(struct gsm0808_cell_id_list2 *cil, size_t bytes_elem = 0; int list_len = 0; - OSMO_ASSERT(cil); if (!elem) return -EINVAL; if (len == 0) @@ -1210,6 +1339,12 @@ int gsm0808_dec_cell_id_list2(struct gsm0808_cell_id_list2 *cil, case CELL_IDENT_NO_CELL: /* Does not have any list items */ break; + case CELL_IDENT_SAI: + list_len = parse_cell_id_sai_list(cil, elem, len, &bytes_elem); + break; + case CELL_IDENT_UTRAN_PLMN_LAC_RNC: + case CELL_IDENT_UTRAN_RNC: + case CELL_IDENT_UTRAN_LAC_RNC: default: /* Remaining cell identification types are not implemented. */ return -EINVAL; @@ -1240,7 +1375,6 @@ int gsm0808_dec_cell_id_list(struct gsm0808_cell_id_list *cil, const uint8_t *old_elem = elem; unsigned int item_count = 0; - OSMO_ASSERT(cil); if (!elem) return -EINVAL; if (len == 0) @@ -1398,9 +1532,6 @@ uint8_t gsm0808_enc_cell_id(struct msgb *msg, const struct gsm0808_cell_id *ci) .id_list_len = 1, }; - OSMO_ASSERT(msg); - OSMO_ASSERT(ci); - ie_tag = msg->tail; rc = gsm0808_enc_cell_id_list2(msg, &cil); @@ -1703,6 +1834,8 @@ int gsm0808_cell_id_u_name(char *buf, size_t buflen, return snprintf(buf, buflen, "%s", osmo_cgi_name(&u->global)); case CELL_IDENT_WHOLE_GLOBAL_PS: return snprintf(buf, buflen, "%s", osmo_cgi_ps_name(&u->global_ps)); + case CELL_IDENT_SAI: + return snprintf(buf, buflen, "%s", osmo_sai_name(&u->sai)); default: /* For CELL_IDENT_BSS and CELL_IDENT_NO_CELL, just print the discriminator. * Same for kinds we have no string representation of yet. */ @@ -1859,6 +1992,12 @@ void gsm0808_cell_id_from_cgi(struct gsm0808_cell_id *cid, enum CELL_IDENT id_di cid->id.lac = cgi->lai.lac; return; + case CELL_IDENT_SAI: + cid->id.sai = (struct osmo_service_area_id){ + .lai = cgi->lai, + }; + return; + case CELL_IDENT_NO_CELL: case CELL_IDENT_BSS: case CELL_IDENT_UTRAN_PLMN_LAC_RNC: @@ -1905,6 +2044,10 @@ int gsm0808_cell_id_to_cgi(struct osmo_cell_global_id *cgi, const struct gsm0808 cgi->lai.lac = cid->id.lac; return OSMO_CGI_PART_LAC; + case CELL_IDENT_SAI: + cgi->lai = cid->id.sai.lai; + return OSMO_CGI_PART_PLMN | OSMO_CGI_PART_LAC; + case CELL_IDENT_NO_CELL: case CELL_IDENT_BSS: case CELL_IDENT_UTRAN_PLMN_LAC_RNC: @@ -1927,29 +2070,17 @@ const struct value_string gsm0808_cell_id_discr_names[] = { { CELL_IDENT_UTRAN_PLMN_LAC_RNC, "UTRAN-PLMN-LAC-RNC" }, { CELL_IDENT_UTRAN_RNC, "UTRAN-RNC" }, { CELL_IDENT_UTRAN_LAC_RNC, "UTRAN-LAC-RNC" }, + { CELL_IDENT_SAI, "SAI" }, { CELL_IDENT_WHOLE_GLOBAL_PS, "CGI-PS"}, { 0, NULL } }; -#define APPEND_THING(func, args...) do { \ - int remain = buflen - (pos - buf); \ - int l = func(pos, remain, ##args); \ - if (l < 0 || l > remain) \ - pos = buf + buflen; \ - else \ - pos += l; \ - if (l > 0) \ - total_len += l; \ - } while(0) -#define APPEND_STR(fmt, args...) APPEND_THING(snprintf, fmt, ##args) -#define APPEND_CELL_ID_U(DISCR, U) APPEND_THING(gsm0808_cell_id_u_name, DISCR, U) - char *gsm0808_cell_id_name_buf(char *buf, size_t buflen, const struct gsm0808_cell_id *cid) { - char *pos = buf; - int total_len = 0; - APPEND_STR("%s:", gsm0808_cell_id_discr_name(cid->id_discr)); - APPEND_CELL_ID_U(cid->id_discr, &cid->id); + struct osmo_strbuf sb = { .buf = buf, .len = buflen }; + + OSMO_STRBUF_PRINTF(sb, "%s:", gsm0808_cell_id_discr_name(cid->id_discr)); + OSMO_STRBUF_APPEND(sb, gsm0808_cell_id_u_name, cid->id_discr, &cid->id); return buf; } @@ -1996,30 +2127,31 @@ char *gsm0808_cell_id_name_c(const void *ctx, const struct gsm0808_cell_id *cid) */ int gsm0808_cell_id_list_name_buf(char *buf, size_t buflen, const struct gsm0808_cell_id_list2 *cil) { - char *pos = buf; - int total_len = 0; - int i; + struct osmo_strbuf sb = { .buf = buf, .len = buflen }; - APPEND_STR("%s[%u]", gsm0808_cell_id_discr_name(cil->id_discr), cil->id_list_len); + OSMO_STRBUF_PRINTF(sb, "%s[%u]", + gsm0808_cell_id_discr_name(cil->id_discr), + cil->id_list_len); switch (cil->id_discr) { case CELL_IDENT_BSS: case CELL_IDENT_NO_CELL: - return total_len; + return sb.chars_needed; default: break; } - APPEND_STR(":{"); + OSMO_STRBUF_PRINTF(sb, ":{"); - for (i = 0; i < cil->id_list_len; i++) { + for (unsigned int i = 0; i < cil->id_list_len; i++) { if (i) - APPEND_STR(", "); - APPEND_CELL_ID_U(cil->id_discr, &cil->id_list[i]); + OSMO_STRBUF_PRINTF(sb, ", "); + OSMO_STRBUF_APPEND(sb, gsm0808_cell_id_u_name, + cil->id_discr, &cil->id_list[i]); } - APPEND_STR("}"); - return total_len; + OSMO_STRBUF_PRINTF(sb, "}"); + return sb.chars_needed; } /*! Return a human-readable representation of \a cil in a static buffer. @@ -2041,9 +2173,6 @@ char *gsm0808_cell_id_list_name_c(const void *ctx, const struct gsm0808_cell_id_ return buf; } -#undef APPEND_STR -#undef APPEND_CELL_ID_U - char *gsm0808_channel_type_name_buf(char *buf, size_t buf_len, const struct gsm0808_channel_type *ct) { snprintf(buf, buf_len, "ch_indctr=0x%x ch_rate_type=0x%x perm_spch=%s", @@ -2066,4 +2195,367 @@ char *gsm0808_channel_type_name_c(const void *ctx, const struct gsm0808_channel_ return gsm0808_channel_type_name_buf(buf, 128, ct); } +/*! Encode Group Call Reference IE (3GPP TS 48.008 3.2.2.55). + * \param[out] msg Message Buffer to which IE is to be appended + * \param[in] gc Group Call Reference to be encoded + * \returns number of bytes appended to \a msg */ +uint8_t gsm0808_enc_group_callref(struct msgb *msg, const struct gsm0808_group_callref *gc) +{ + uint8_t *old_tail; + uint8_t *tlv_len; + + OSMO_ASSERT(msg); + OSMO_ASSERT(gc); + + msgb_put_u8(msg, GSM0808_IE_GROUP_CALL_REFERENCE); + tlv_len = msgb_put(msg, 1); + old_tail = msg->tail; + + memcpy(msgb_put(msg, sizeof(*gc)), gc, sizeof(*gc)); + + *tlv_len = (uint8_t) (msg->tail - old_tail); + return *tlv_len + 2; +} + +/*! Decode Group Call Reference IE (3GPP TS 48.008 3.2.2.55). + * \param[out] gc Group Call Reference structure to store data + * \param[in] elem IE value to be decoded. + * \param[in] len Length of \a elem in bytes. + * \returns number of bytes parsed; negative on error */ +int gsm0808_dec_group_callref(struct gsm0808_group_callref *gc, const uint8_t *elem, uint8_t len) +{ + OSMO_ASSERT(gc); + OSMO_ASSERT(elem); + + if (len != sizeof(*gc)) + return -EINVAL; + + memcpy(gc, elem, sizeof(*gc)); + + return len; +} + +/*! Encode Priority IE (3GPP TS 48.008 3.2.2.18). + * \param[out] msg Message Buffer to which IE is to be appended + * \param[in] pri Priority to be encoded + * \returns number of bytes appended to \a msg */ +uint8_t gsm0808_enc_priority(struct msgb *msg, const struct gsm0808_priority *pri) +{ + uint8_t *old_tail; + uint8_t *tlv_len; + + OSMO_ASSERT(msg); + OSMO_ASSERT(pri); + + msgb_put_u8(msg, GSM0808_IE_PRIORITY); + tlv_len = msgb_put(msg, 1); + old_tail = msg->tail; + + memcpy(msgb_put(msg, sizeof(*pri)), pri, sizeof(*pri)); + + *tlv_len = (uint8_t) (msg->tail - old_tail); + return *tlv_len + 2; +} + +/*! Decode Priority IE (3GPP TS 48.008 3.2.2.18). + * \param[out] pri Priority structure to store data + * \param[in] elem IE value to be decoded. + * \param[in] len Length of \a elem in bytes. + * \returns number of bytes parsed; negative on error */ +int gsm0808_dec_priority(struct gsm0808_priority *pri, const uint8_t *elem, uint8_t len) +{ + OSMO_ASSERT(pri); + OSMO_ASSERT(elem); + + if (len != sizeof(*pri)) + return -EINVAL; + + memcpy(pri, elem, sizeof(*pri)); + + return len; +} + +/*! Encode VGCS Feature Flags IE (3GPP TS 48.008 3.2.2.88). + * \param[out] msg Message Buffer to which IE is to be appended + * \param[in] ff VGCS Feature Flags to be encoded + * \returns number of bytes appended to \a msg */ +uint8_t gsm0808_enc_vgcs_feature_flags(struct msgb *msg, const struct gsm0808_vgcs_feature_flags *ff) +{ + uint8_t *old_tail; + uint8_t *tlv_len; + + OSMO_ASSERT(msg); + OSMO_ASSERT(ff); + + msgb_put_u8(msg, GSM0808_IE_VGCS_FEATURE_FLAGS); + tlv_len = msgb_put(msg, 1); + old_tail = msg->tail; + + memcpy(msgb_put(msg, sizeof(*ff)), ff, sizeof(*ff)); + + *tlv_len = (uint8_t) (msg->tail - old_tail); + return *tlv_len + 2; +} + +/*! Decode VGCS Feature Flags IE (3GPP TS 48.008 3.2.2.88). + * \param[out] ff VGCS Feature Flags structure to store data + * \param[in] elem IE value to be decoded. + * \param[in] len Length of \a elem in bytes. + * \returns number of bytes parsed; negative on error */ +int gsm0808_dec_vgcs_feature_flags(struct gsm0808_vgcs_feature_flags *ff, const uint8_t *elem, uint8_t len) +{ + OSMO_ASSERT(ff); + OSMO_ASSERT(elem); + + if (len != sizeof(*ff)) + return -EINVAL; + + memcpy(ff, elem, sizeof(*ff)); + + return len; +} + +/*! Encode Data Identity IE (3GPP TS 48.008 3.2.2.99). + * \param[out] msg Message Buffer to which IE is to be appended + * \param[in] di Data Identity to be encoded + * \returns number of bytes appended to \a msg */ +uint8_t gsm0808_enc_data_identity(struct msgb *msg, const struct gsm0808_data_identity *ti) +{ + uint8_t *old_tail; + uint8_t *tlv_len; + + OSMO_ASSERT(msg); + OSMO_ASSERT(ti); + + msgb_put_u8(msg, GSM0808_IE_DATA_IDENTITY); + tlv_len = msgb_put(msg, 1); + old_tail = msg->tail; + + memcpy(msgb_put(msg, sizeof(*ti)), ti, sizeof(*ti)); + + *tlv_len = (uint8_t) (msg->tail - old_tail); + return *tlv_len + 2; +} + +/*! Decode Data Identity IE (3GPP TS 48.008 3.2.2.99). + * \param[out] di Data Identity structure to store data + * \param[in] elem IE value to be decoded. + * \param[in] len Length of \a elem in bytes. + * \returns number of bytes parsed; negative on error */ +int gsm0808_dec_data_identity(struct gsm0808_data_identity *ti, const uint8_t *elem, uint8_t len) +{ + OSMO_ASSERT(ti); + OSMO_ASSERT(elem); + + if (len < sizeof(*ti)) + return -EINVAL; + + memcpy(ti, elem, sizeof(*ti)); + + return len; +} + +/*! Encode MSISDN IE (3GPP TS 48.008 3.2.2.101). + * \param[out] msg Message Buffer to which IE is to be appended + * \param[in] msisdn MSISDN to be encoded + * \returns number of bytes appended to \a msg */ +uint8_t gsm0808_enc_msisdn(struct msgb *msg, const char *msisdn) +{ + uint8_t *tlv_len; + uint8_t bcd_lv[11]; + int rc; + + OSMO_ASSERT(msg); + OSMO_ASSERT(msisdn); + + rc = gsm48_encode_bcd_number(bcd_lv, sizeof(bcd_lv), 0, msisdn); + if (rc < 1) + return 0; + + msgb_put_u8(msg, GSM0808_IE_MSISDN); + tlv_len = msgb_put(msg, rc); + + memcpy(tlv_len, bcd_lv, rc); + + *tlv_len = rc - 1; + return *tlv_len + 2; +} + +/*! Decode MSISDN IE (3GPP TS 48.008 3.2.2.101). + * \param[out] msisdn MSISDN structure to store data + * \param[in] elem IE value to be decoded. + * \param[in] len Length of \a elem in bytes. + * \returns number of bytes parsed; negative on error */ +int gsm0808_dec_msisdn(char *msisdn, const char *elem, uint8_t len) +{ + OSMO_ASSERT(msisdn); + OSMO_ASSERT(elem); + + if (len < 1) + return -EINVAL; + + gsm48_decode_bcd_number(msisdn, MSISDN_MAXLEN + 1, (uint8_t *)(elem - 1), 0); + + return len; +} + +/*! Encode Talker Identity IE (3GPP TS 48.008 3.2.2.91). + * \param[out] msg Message Buffer to which IE is to be appended + * \param[in] ti Talker Identity to be encoded + * \returns number of bytes appended to \a msg */ +uint8_t gsm0808_enc_talker_identity(struct msgb *msg, const struct gsm0808_talker_identity *ti) +{ + uint8_t *old_tail; + uint8_t *tlv_len; + uint8_t *ptr; + unsigned int bytes; + + OSMO_ASSERT(msg); + OSMO_ASSERT(ti); + + msgb_put_u8(msg, GSM0808_IE_TALKER_IDENTITY); + tlv_len = msgb_put(msg, 1); + old_tail = msg->tail; + + bytes = (ti->id_bits + 7) >> 3; + ptr = msgb_put(msg, 1 + bytes); + ptr[0] = -ti->id_bits & 0x7; + memcpy(ptr + 1, ti->talker_id, bytes); + + *tlv_len = (uint8_t) (msg->tail - old_tail); + return *tlv_len + 2; +} + +/*! Decode Talker Identity IE (3GPP TS 48.008 3.2.2.91). + * \param[out] ti Talker Identity structure to store data + * \param[in] elem IE value to be decoded. + * \param[in] len Length of \a elem in bytes. + * \returns number of bytes parsed; negative on error */ +int gsm0808_dec_talker_identity(struct gsm0808_talker_identity *ti, const uint8_t *elem, uint8_t len) +{ + OSMO_ASSERT(ti); + OSMO_ASSERT(elem); + + if (len < 2) + return -EINVAL; + unsigned int bytes; + + bytes = len - 1; + if (bytes > TALKER_IDENTITY_MAXLEN) + return -EINVAL; + ti->id_bits = (bytes << 3) - (elem[0] & 0x7); + memcpy(ti->talker_id, elem + 1, bytes); + + return len; +} + +/*! Encode Assignment Requirements IE (3GPP TS 48.008 3.2.2.52). + * \param[out] msg Message Buffer to which IE is to be appended + * \param[in] ar Assignment Requirement to be encoded + * \returns number of bytes appended to \a msg */ +uint8_t gsm0808_enc_assign_req(struct msgb *msg, const enum gsm0808_assignment_requirement ar) +{ + uint8_t *ptr; + + OSMO_ASSERT(msg); + + msgb_put_u8(msg, GSM0808_IE_ASSIGNMENT_REQUIREMENT); + + ptr = msgb_put(msg, 1); + ptr[0] = ar; + + return 2; +} + +/*! Decode Assignment Requirements IE (3GPP TS 48.008 3.2.2.52). + * \param[out] ar Assignment Requirements enum to store data + * \param[in] elem IE value to be decoded. + * \param[in] len Length of \a elem in bytes. + * \returns number of bytes parsed; negative on error */ +int gsm0808_dec_assign_req(enum gsm0808_assignment_requirement *ar, const uint8_t *elem, uint8_t len) +{ + OSMO_ASSERT(ar); + OSMO_ASSERT(elem); + + if (len != 1) + return -EINVAL; + + *ar = elem[0]; + + return 1; +} + +/*! Encode Cell Identifier List Segment IE (3GPP TS 48.008 3.2.2.27a). + * \param[out] msg Message Buffer to which IE is to be appended + * \param[in] ie_type Type of IE to use (5 different lists are specified.) + * \param[in] ci Cell Identifier List Segment to be encoded + * \returns number of bytes appended to \a msg */ +uint8_t gsm0808_enc_cell_id_list_segment(struct msgb *msg, uint8_t ie_type, + const struct gsm0808_cell_id_list_segment *ci) +{ + uint8_t *old_tail; + uint8_t *tlv_len; + int rc; + + OSMO_ASSERT(msg); + OSMO_ASSERT(ci); + + msgb_put_u8(msg, ie_type); + tlv_len = msgb_put(msg, 1); + old_tail = msg->tail; + + msgb_put_u8(msg, (ci->seq_last << 4) | ci->seq_number); + + rc = gsm0808_enc_cell_id_list2(msg, &ci->cil); + if (rc <= 0) + return rc; + + *tlv_len = (uint8_t) (msg->tail - old_tail); + return *tlv_len + 2; +} + +/*! Decode Cell Identifier List Segment IE (3GPP TS 48.008 3.2.2.27a). + * \param[out] ci Cell Identifier List Segment structure to store data + * \param[in] elem IE value to be decoded. + * \param[in] len Length of \a elem in bytes. + * \returns number of bytes parsed; negative on error */ +int gsm0808_dec_cell_id_list_segment(struct gsm0808_cell_id_list_segment *ci, const uint8_t *elem, uint8_t len) +{ + int rc; + + OSMO_ASSERT(ci); + OSMO_ASSERT(elem); + + if (len < 1) + return -EINVAL; + + ci->seq_last = elem[0] >> 4; + ci->seq_number = elem[0] & 0x0f; + + rc = gsm0808_dec_cell_id_list2(&ci->cil, elem + 1, len - 1); + if (rc < 0) + return rc; + + return len; +} + +/*! Decode Call Identifier IE (3GPP TS 48.008 3.2.2.105). + * \param[out] ci Call Identifier structure to store data + * \param[in] elem IE value to be decoded. + * \param[in] len Length of \a elem in bytes. + * \returns number of bytes parsed; negative on error */ +int gsm0808_dec_call_id(uint32_t *ci, const uint8_t *elem, uint8_t len) +{ + OSMO_ASSERT(ci); + OSMO_ASSERT(elem); + + if (len != 4) + return -EINVAL; + + /* The Call Identifier is stored as little endian. */ + *ci = osmo_load32le(elem); + + return 4; +} + /*! @} */ diff --git a/src/gsm/gsm23003.c b/src/gsm/gsm23003.c index c2b3de86..1eed41fe 100644 --- a/src/gsm/gsm23003.c +++ b/src/gsm/gsm23003.c @@ -17,10 +17,6 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ #include <ctype.h> @@ -372,6 +368,51 @@ char *osmo_cgi_ps_name_c(const void *ctx, const struct osmo_cell_global_id_ps *c return osmo_cgi_ps_name_buf(buf, 32, cgi_ps); } +/*! Return MCC-MNC-LAC-SAC as string, in caller-provided output buffer. + * \param[out] buf caller-allocated output buffer + * \param[in] buf_len size of buf in bytes + * \param[in] sai SAI to encode. + * \returns buf + */ +char *osmo_sai_name_buf(char *buf, size_t buf_len, const struct osmo_service_area_id *sai) +{ + snprintf(buf, buf_len, "%s-%u", osmo_lai_name(&sai->lai), sai->sac); + return buf; +} + +/*! Return MCC-MNC-LAC-SAC as string, in a static buffer. + * \param[in] sai SAI to encode. + * \returns Static string buffer. + */ +const char *osmo_sai_name(const struct osmo_service_area_id *sai) +{ + static __thread char buf[32]; + return osmo_sai_name_buf(buf, sizeof(buf), sai); +} + +/*! Same as osmo_cgi_name(), but uses a different static buffer. + * Useful for printing two distinct CGIs in the same printf format. + * \param[in] sai SAI to encode. + * \returns Static string buffer. + */ +const char *osmo_sai_name2(const struct osmo_service_area_id *sai) +{ + static __thread char buf[32]; + return osmo_sai_name_buf(buf, sizeof(buf), sai); +} + +/*! Return MCC-MNC-LAC-SAC as string, in a talloc-allocated output buffer. + * \param[in] ctx talloc context from which to allocate output buffer + * \param[in] sai SAI to encode. + * \returns string representation of CGI in dynamically-allocated buffer. + */ +char *osmo_sai_name_c(const void *ctx, const struct osmo_service_area_id *sai) +{ + char *buf = talloc_size(ctx, 32); + return osmo_sai_name_buf(buf, 32, sai); +} + + static void to_bcd(uint8_t *bcd, uint16_t val) { bcd[2] = val % 10; @@ -449,14 +490,12 @@ void osmo_plmn_to_bcd(uint8_t *bcd_dst, const struct osmo_plmn_id *plmn) } } -/* Convert given 3-byte BCD buffer to integers and write results to *mcc and - * *mnc. The first three BCD digits result in the MCC and the remaining ones in - * the MNC. Return mnc_3_digits as false if the MNC's most significant digit is encoded as 0xF, true - * otherwise; i.e. true if MNC > 99 or if it is represented with leading zeros instead of 0xF. +/* Convert given 3-byte BCD buffer to integers and write results to plmn->mcc and plmn->mnc. The first three BCD digits + * result in the MCC and the remaining ones in the MNC. Set plmn->mnc_3_digits as false if the MNC's most significant + * digit is encoded as 0xF, true otherwise; i.e. true if MNC > 99 or if it is represented with leading zeros instead of + * 0xF. * \param[in] bcd_src 3-byte BCD buffer containing MCC+MNC representations. - * \param[out] mcc MCC result buffer, or NULL. - * \param[out] mnc MNC result buffer, or NULL. - * \param[out] mnc_3_digits Result buffer for 3-digit flag, or NULL. + * \param[out] plmn user provided memory to store the result. */ void osmo_plmn_from_bcd(const uint8_t *bcd_src, struct osmo_plmn_id *plmn) { @@ -487,22 +526,23 @@ void osmo_plmn_from_bcd(const uint8_t *bcd_src, struct osmo_plmn_id *plmn) */ int osmo_mnc_from_str(const char *mnc_str, uint16_t *mnc, bool *mnc_3_digits) { - long int _mnc = 0; + int _mnc = 0; bool _mnc_3_digits = false; - char *endptr; int rc = 0; if (!mnc_str || !isdigit((unsigned char)mnc_str[0]) || strlen(mnc_str) > 3) return -EINVAL; - errno = 0; - _mnc = strtol(mnc_str, &endptr, 10); - if (errno) - rc = -errno; - else if (*endptr) + rc = osmo_str_to_int(&_mnc, mnc_str, 10, 0, 999); + /* Heed the API definition to return -EINVAL in case of surplus chars */ + if (rc == -E2BIG) return -EINVAL; - if (_mnc < 0 || _mnc > 999) - return -ERANGE; + /* Heed the API definition to always return negative errno */ + if (rc > 0) + return -rc; + if (rc < 0) + return rc; + _mnc_3_digits = strlen(mnc_str) > 2; if (mnc) diff --git a/src/gsm/gsm23236.c b/src/gsm/gsm23236.c index 01d0eb36..4a83ec82 100644 --- a/src/gsm/gsm23236.c +++ b/src/gsm/gsm23236.c @@ -17,10 +17,6 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ #include <errno.h> @@ -436,27 +432,13 @@ char *osmo_nri_ranges_to_str_c(void *ctx, const struct osmo_nri_ranges *nri_rang */ static int osmo_nri_parse(int16_t *dst, const char *str) { - char *endp; - int64_t val; + int val; int base = 10; - - if (osmo_str_startswith(str, "0x")) { - str += 2; + if (osmo_str_startswith(str, "0x")) base = 16; - } - - if (!str || !str[0]) + if (osmo_str_to_int(&val, str, base, 0, INT16_MAX)) return -1; - - errno = 0; - val = strtoull(str, &endp, base); - if (errno || *endp != '\0') - return -1; - - if (val < 0 || val > INT16_MAX) - return -1; - - *dst = val; + *dst = (int16_t)val; return 0; } diff --git a/src/gsm/gsm29118.c b/src/gsm/gsm29118.c index 2c02b2fd..0ca5b52e 100644 --- a/src/gsm/gsm29118.c +++ b/src/gsm/gsm29118.c @@ -14,10 +14,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include <osmocom/core/utils.h> diff --git a/src/gsm/gsm29205.c b/src/gsm/gsm29205.c index 6ceb8ee4..8fed0206 100644 --- a/src/gsm/gsm29205.c +++ b/src/gsm/gsm29205.c @@ -14,10 +14,6 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ #include "config.h" diff --git a/src/gsm/gsm44021.c b/src/gsm/gsm44021.c new file mode 100644 index 00000000..5e9d5ab8 --- /dev/null +++ b/src/gsm/gsm44021.c @@ -0,0 +1,303 @@ +/************************************************************************* + * GSM CSD modified V.110 frame decoding/encoding (ubits <-> struct with D/S/X/E bits) + *************************************************************************/ + +/* (C) 2022 by Harald Welte <laforge@osmocom.org> + * + * SPDX-License-Identifier: GPL-2.0+ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <errno.h> +#include <osmocom/core/bits.h> +#include <osmocom/isdn/v110.h> + +/*! Decode a 60-bit GSM 12kbit/s CSD frame present as 60 ubits into a struct osmo_v110_decoded_frame. + * \param[out] caller-allocated output data structure, filled by this function + * \param[in] ra_bits One V.110 frame as 60 unpacked bits. */ +int osmo_csd_12k_6k_decode_frame(struct osmo_v110_decoded_frame *fr, const ubit_t *ra_bits, size_t n_bits) +{ + /* 3GPP TS 44.021 Section 8.1.2 / 8.1.3 + D1 D2 D3 D4 D5 D6 S1 + D7 D8 D9 D10 D11 D12 X + D13 D14 D15 D16 D17 D18 S3 + D19 D20 D21 D22 D23 D24 S4 + E4 E5 E6 E7 D25 D26 D27 + D28 D29 D30 S6 D31 D32 D33 + D34 D35 D36 X D37 D38 D39 + D40 D41 D42 S8 D43 D44 D45 + D46 D47 D48 S9 */ + + if (n_bits < 60) + return -EINVAL; + + /* X1 .. X2 */ + fr->x_bits[0] = ra_bits[1 * 7 + 6]; + fr->x_bits[1] = ra_bits[6 * 7 + 3]; + + /* S1, S3, S4, S6, S8, S9 */ + fr->s_bits[0] = ra_bits[0 * 7 + 6]; + fr->s_bits[2] = ra_bits[2 * 7 + 6]; + fr->s_bits[3] = ra_bits[3 * 7 + 6]; + fr->s_bits[5] = ra_bits[5 * 7 + 3]; + fr->s_bits[7] = ra_bits[7 * 7 + 3]; + fr->s_bits[8] = ra_bits[8 * 7 + 3]; + + /* E1 .. E3 must be set by out-of-band knowledge! */ + + /* E4 .. E7 */ + memcpy(fr->e_bits+3, ra_bits + 4 * 7 + 0, 4); + + /* D-bits */ + memcpy(fr->d_bits + 0 * 6 + 0, ra_bits + 0 * 7 + 0, 6); + memcpy(fr->d_bits + 1 * 6 + 0, ra_bits + 1 * 7 + 0, 6); + memcpy(fr->d_bits + 2 * 6 + 0, ra_bits + 2 * 7 + 0, 6); + memcpy(fr->d_bits + 3 * 6 + 0, ra_bits + 3 * 7 + 0, 6); + memcpy(fr->d_bits + 4 * 6 + 0, ra_bits + 4 * 7 + 4, 3); + memcpy(fr->d_bits + 4 * 6 + 3, ra_bits + 5 * 7 + 0, 3); + memcpy(fr->d_bits + 5 * 6 + 0, ra_bits + 5 * 7 + 4, 3); + memcpy(fr->d_bits + 5 * 6 + 3, ra_bits + 6 * 7 + 0, 3); + memcpy(fr->d_bits + 6 * 6 + 0, ra_bits + 6 * 7 + 4, 3); + memcpy(fr->d_bits + 6 * 6 + 3, ra_bits + 7 * 7 + 0, 3); + memcpy(fr->d_bits + 7 * 6 + 0, ra_bits + 7 * 7 + 4, 3); + memcpy(fr->d_bits + 7 * 6 + 3, ra_bits + 8 * 7 + 0, 3); + + return 0; +} + +int osmo_csd_12k_6k_encode_frame(ubit_t *ra_bits, size_t ra_bits_size, const struct osmo_v110_decoded_frame *fr) +{ + if (ra_bits_size < 60) + return -EINVAL; + + /* X1 .. X2 */ + ra_bits[1 * 7 + 6] = fr->x_bits[0]; + ra_bits[6 * 7 + 3] = fr->x_bits[1]; + + /* S1, S3, S4, S6, S8, S9 */ + ra_bits[0 * 7 + 6] = fr->s_bits[0]; + ra_bits[2 * 7 + 6] = fr->s_bits[2]; + ra_bits[3 * 7 + 6] = fr->s_bits[3]; + ra_bits[5 * 7 + 3] = fr->s_bits[5]; + ra_bits[7 * 7 + 3] = fr->s_bits[7]; + ra_bits[8 * 7 + 3] = fr->s_bits[8]; + + /* E1 .. E3 are dropped */ + + /* E4 .. E7 */ + memcpy(ra_bits + 4 * 7 + 0, fr->e_bits+3, 4); + + /* D-bits */ + memcpy(ra_bits + 0 * 7 + 0, fr->d_bits + 0 * 6 + 0, 6); + memcpy(ra_bits + 1 * 7 + 0, fr->d_bits + 1 * 6 + 0, 6); + memcpy(ra_bits + 2 * 7 + 0, fr->d_bits + 2 * 6 + 0, 6); + memcpy(ra_bits + 3 * 7 + 0, fr->d_bits + 3 * 6 + 0, 6); + memcpy(ra_bits + 4 * 7 + 4, fr->d_bits + 4 * 6 + 0, 3); + memcpy(ra_bits + 5 * 7 + 0, fr->d_bits + 4 * 6 + 3, 3); + memcpy(ra_bits + 5 * 7 + 4, fr->d_bits + 5 * 6 + 0, 3); + memcpy(ra_bits + 6 * 7 + 0, fr->d_bits + 5 * 6 + 3, 3); + memcpy(ra_bits + 6 * 7 + 4, fr->d_bits + 6 * 6 + 0, 3); + memcpy(ra_bits + 7 * 7 + 0, fr->d_bits + 6 * 6 + 3, 3); + memcpy(ra_bits + 7 * 7 + 4, fr->d_bits + 7 * 6 + 0, 3); + memcpy(ra_bits + 8 * 7 + 0, fr->d_bits + 7 * 6 + 3, 3); + + return 60; +} + +/*! Decode a 36-bit GSM 3k6kbit/s CSD frame present as 36 ubits into a struct osmo_v110_decoded_frame. + * \param[out] caller-allocated output data structure, filled by this function + * \param[in] ra_bits One V.110 frame as 36 unpacked bits. */ +int osmo_csd_3k6_decode_frame(struct osmo_v110_decoded_frame *fr, const ubit_t *ra_bits, size_t n_bits) +{ + + /* 3GPP TS 44.021 Section 8.1.4 + D1 D2 D3 S1 D4 D5 D6 X + D7 D8 D9 S3 D10 D11 D12 S4 + E4 E5 E6 E7 D13 D14 D15 S6 + D16 D17 D18 X D19 D20 D21 S8 + D22 D23 D24 S9 + */ + + if (n_bits < 36) + return -EINVAL; + + /* X1 .. X2 */ + fr->x_bits[0] = ra_bits[0 * 8 + 7]; + fr->x_bits[1] = ra_bits[3 * 8 + 3]; + + /* S1, S3, S4, S6, S8, S9 */ + fr->s_bits[0] = ra_bits[0 * 8 + 3]; + fr->s_bits[2] = ra_bits[1 * 8 + 3]; + fr->s_bits[3] = ra_bits[1 * 8 + 7]; + fr->s_bits[5] = ra_bits[2 * 8 + 7]; + fr->s_bits[7] = ra_bits[3 * 8 + 7]; + fr->s_bits[8] = ra_bits[4 * 8 + 3]; + + /* E1 .. E3 must be set by out-of-band knowledge! */ + + /* E4 .. E7 */ + memcpy(fr->e_bits+3, ra_bits + 2 * 8 + 0, 4); + + /* D-bits */ + unsigned int d_idx = 0; + fr->d_bits[d_idx++] = ra_bits[0 * 8 + 0]; /* D1 */ + fr->d_bits[d_idx++] = ra_bits[0 * 8 + 0]; /* D1 */ + fr->d_bits[d_idx++] = ra_bits[0 * 8 + 1]; /* D2 */ + fr->d_bits[d_idx++] = ra_bits[0 * 8 + 1]; /* D2 */ + fr->d_bits[d_idx++] = ra_bits[0 * 8 + 2]; /* D3 */ + fr->d_bits[d_idx++] = ra_bits[0 * 8 + 2]; /* D3 */ + fr->d_bits[d_idx++] = ra_bits[0 * 8 + 4]; /* D4 */ + fr->d_bits[d_idx++] = ra_bits[0 * 8 + 4]; /* D4 */ + fr->d_bits[d_idx++] = ra_bits[0 * 8 + 5]; /* D5 */ + fr->d_bits[d_idx++] = ra_bits[0 * 8 + 5]; /* D5 */ + fr->d_bits[d_idx++] = ra_bits[0 * 8 + 6]; /* D6 */ + fr->d_bits[d_idx++] = ra_bits[0 * 8 + 6]; /* D6 */ + + fr->d_bits[d_idx++] = ra_bits[1 * 8 + 0]; /* D7 */ + fr->d_bits[d_idx++] = ra_bits[1 * 8 + 0]; /* D7 */ + fr->d_bits[d_idx++] = ra_bits[1 * 8 + 1]; /* D8 */ + fr->d_bits[d_idx++] = ra_bits[1 * 8 + 1]; /* D8 */ + fr->d_bits[d_idx++] = ra_bits[1 * 8 + 2]; /* D9 */ + fr->d_bits[d_idx++] = ra_bits[1 * 8 + 2]; /* D9 */ + fr->d_bits[d_idx++] = ra_bits[1 * 8 + 4]; /* D10 */ + fr->d_bits[d_idx++] = ra_bits[1 * 8 + 4]; /* D10 */ + fr->d_bits[d_idx++] = ra_bits[1 * 8 + 5]; /* D11 */ + fr->d_bits[d_idx++] = ra_bits[1 * 8 + 5]; /* D11 */ + fr->d_bits[d_idx++] = ra_bits[1 * 8 + 6]; /* D12 */ + fr->d_bits[d_idx++] = ra_bits[1 * 8 + 6]; /* D12 */ + + fr->d_bits[d_idx++] = ra_bits[2 * 8 + 4]; /* D13 */ + fr->d_bits[d_idx++] = ra_bits[2 * 8 + 4]; /* D13 */ + fr->d_bits[d_idx++] = ra_bits[2 * 8 + 5]; /* D14 */ + fr->d_bits[d_idx++] = ra_bits[2 * 8 + 5]; /* D14 */ + fr->d_bits[d_idx++] = ra_bits[2 * 8 + 6]; /* D15 */ + fr->d_bits[d_idx++] = ra_bits[2 * 8 + 6]; /* D15 */ + + fr->d_bits[d_idx++] = ra_bits[3 * 8 + 0]; /* D16 */ + fr->d_bits[d_idx++] = ra_bits[3 * 8 + 0]; /* D16 */ + fr->d_bits[d_idx++] = ra_bits[3 * 8 + 1]; /* D17 */ + fr->d_bits[d_idx++] = ra_bits[3 * 8 + 1]; /* D17 */ + fr->d_bits[d_idx++] = ra_bits[3 * 8 + 2]; /* D18 */ + fr->d_bits[d_idx++] = ra_bits[3 * 8 + 2]; /* D18 */ + fr->d_bits[d_idx++] = ra_bits[3 * 8 + 4]; /* D19 */ + fr->d_bits[d_idx++] = ra_bits[3 * 8 + 4]; /* D19 */ + fr->d_bits[d_idx++] = ra_bits[3 * 8 + 5]; /* D20 */ + fr->d_bits[d_idx++] = ra_bits[3 * 8 + 5]; /* D20 */ + fr->d_bits[d_idx++] = ra_bits[3 * 8 + 6]; /* D21 */ + fr->d_bits[d_idx++] = ra_bits[3 * 8 + 6]; /* D21 */ + + fr->d_bits[d_idx++] = ra_bits[4 * 8 + 0]; /* D22 */ + fr->d_bits[d_idx++] = ra_bits[4 * 8 + 0]; /* D22 */ + fr->d_bits[d_idx++] = ra_bits[4 * 8 + 1]; /* D23 */ + fr->d_bits[d_idx++] = ra_bits[4 * 8 + 1]; /* D23 */ + fr->d_bits[d_idx++] = ra_bits[4 * 8 + 2]; /* D24 */ + fr->d_bits[d_idx++] = ra_bits[4 * 8 + 2]; /* D24 */ + + OSMO_ASSERT(d_idx == 48); + + return 0; +} + +int osmo_csd_3k6_encode_frame(ubit_t *ra_bits, size_t ra_bits_size, const struct osmo_v110_decoded_frame *fr) +{ + if (ra_bits_size < 36) + return -EINVAL; + + /* X1 .. X2 */ + ra_bits[0 * 8 + 7] = fr->x_bits[0]; + ra_bits[3 * 8 + 3] = fr->x_bits[1]; + + /* S1, S3, S4, S6, S8, S9 */ + ra_bits[0 * 8 + 3] = fr->s_bits[0]; + ra_bits[1 * 8 + 3] = fr->s_bits[2]; + ra_bits[1 * 8 + 7] = fr->s_bits[3]; + ra_bits[2 * 8 + 7] = fr->s_bits[5]; + ra_bits[3 * 8 + 7] = fr->s_bits[7]; + ra_bits[4 * 8 + 3] = fr->s_bits[8]; + + /* E1 .. E3 are ignored */ + + /* E4 .. E7 */ + memcpy(ra_bits + 2 * 8 + 0, fr->e_bits+3, 4); + + /* D-bits */ + unsigned int d_idx = 0; + ra_bits[0 * 8 + 0] = fr->d_bits[d_idx]; d_idx += 2; + ra_bits[0 * 8 + 1] = fr->d_bits[d_idx]; d_idx += 2; + ra_bits[0 * 8 + 2] = fr->d_bits[d_idx]; d_idx += 2; + ra_bits[0 * 8 + 4] = fr->d_bits[d_idx]; d_idx += 2; + ra_bits[0 * 8 + 5] = fr->d_bits[d_idx]; d_idx += 2; + ra_bits[0 * 8 + 6] = fr->d_bits[d_idx]; d_idx += 2; + + ra_bits[1 * 8 + 0] = fr->d_bits[d_idx]; d_idx += 2; + ra_bits[1 * 8 + 1] = fr->d_bits[d_idx]; d_idx += 2; + ra_bits[1 * 8 + 2] = fr->d_bits[d_idx]; d_idx += 2; + ra_bits[1 * 8 + 4] = fr->d_bits[d_idx]; d_idx += 2; + ra_bits[1 * 8 + 5] = fr->d_bits[d_idx]; d_idx += 2; + ra_bits[1 * 8 + 6] = fr->d_bits[d_idx]; d_idx += 2; + + ra_bits[2 * 8 + 4] = fr->d_bits[d_idx]; d_idx += 2; + ra_bits[2 * 8 + 5] = fr->d_bits[d_idx]; d_idx += 2; + ra_bits[2 * 8 + 6] = fr->d_bits[d_idx]; d_idx += 2; + + ra_bits[3 * 8 + 0] = fr->d_bits[d_idx]; d_idx += 2; + ra_bits[3 * 8 + 1] = fr->d_bits[d_idx]; d_idx += 2; + ra_bits[3 * 8 + 2] = fr->d_bits[d_idx]; d_idx += 2; + ra_bits[3 * 8 + 4] = fr->d_bits[d_idx]; d_idx += 2; + ra_bits[3 * 8 + 5] = fr->d_bits[d_idx]; d_idx += 2; + ra_bits[3 * 8 + 6] = fr->d_bits[d_idx]; d_idx += 2; + + ra_bits[4 * 8 + 0] = fr->d_bits[d_idx]; d_idx += 2; + ra_bits[4 * 8 + 1] = fr->d_bits[d_idx]; d_idx += 2; + ra_bits[4 * 8 + 2] = fr->d_bits[d_idx]; d_idx += 2; + + OSMO_ASSERT(d_idx == 48); + + return 36; +} + +/*! Print a encoded "CSD modififed V.110" frame in the same table-like structure as the spec. + * \param outf output FILE stream to which to dump + * \param[in] fr unpacked bits to dump + * \param[in] in_len length of unpacked bits available at fr. */ +void osmo_csd_ubit_dump(FILE *outf, const ubit_t *fr, size_t in_len) +{ + switch (in_len) { + case 60: + for (unsigned int septet = 0; septet < 9; septet++) { + if (septet < 8) { + fprintf(outf, "%d\t%d\t%d\t%d\t%d\t%d\t%d\n", fr[septet * 7 + 0], + fr[septet * 7 + 1], fr[septet * 7 + 2], fr[septet * 7 + 3], + fr[septet * 7 + 4], fr[septet * 7 + 5], fr[septet*7 + 6]); + } else { + fprintf(outf, "%d\t%d\t%d\t%d\n", fr[septet * 7 + 0], + fr[septet * 7 + 1], fr[septet * 7 + 2], fr[septet * 7 + 3]); + } + } + break; + case 36: + for (unsigned int octet = 0; octet < 5; octet++) { + if (octet < 4) { + fprintf(outf, "%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\n", + fr[octet * 8 + 0], fr[octet * 8 + 1], fr[octet * 8 + 2], + fr[octet * 8 + 3], fr[octet * 8 + 4], fr[octet * 8 + 5], + fr[octet * 8 + 6], fr[octet * 8 + 7]); + } else { + fprintf(outf, "%d\t%d\t%d\t%d\n", fr[octet * 8 + 0], + fr[octet * 8 + 1], fr[octet * 8 + 2], fr[octet * 8 + 3]); + } + } + break; + default: + fprintf(outf, "invalid input data length: %zu\n", in_len); + } +} diff --git a/src/gsm/gsm44068.c b/src/gsm/gsm44068.c new file mode 100644 index 00000000..2556b128 --- /dev/null +++ b/src/gsm/gsm44068.c @@ -0,0 +1,121 @@ +/* Group Call Control (GCC) is an ETSI/3GPP standard protocol used between + * MS (Mobile Station) and MSC (Mobile Switchting Center) in 2G/GSM-R network. + * It is specified in 3GPP TS 44.068. + * + * (C) 2023 by Sysmocom s.f.m.c. GmbH + * All Rights Reserved + * + * Author: Andreas Eversberg + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include <stddef.h> +#include <osmocom/gsm/tlv.h> +#include <osmocom/gsm/protocol/gsm_44_068.h> + +/*********************************************************************** + * Protocol Definitions + ***********************************************************************/ + +const struct value_string osmo_gsm44068_msg_type_names[] = { + { OSMO_GSM44068_MSGT_IMMEDIATE_SETUP, "IMMEDIATE SETUP" }, + { OSMO_GSM44068_MSGT_SETUP, "SETUP" }, + { OSMO_GSM44068_MSGT_CONNECT, "CONNECT" }, + { OSMO_GSM44068_MSGT_TERMINATION, "TERMINATION" }, + { OSMO_GSM44068_MSGT_TERMINATION_REQUEST, "TERMINATION REQUEST" }, + { OSMO_GSM44068_MSGT_TERMINATION_REJECT, "TERMINATION REJECT" }, + { OSMO_GSM44068_MSGT_STATUS, "STATUS" }, + { OSMO_GSM44068_MSGT_GET_STATUS, "GET STATUS" }, + { OSMO_GSM44068_MSGT_SET_PARAMETER, "SET PARAMETER" }, + { OSMO_GSM44068_MSGT_IMMEDIATE_SETUP_2, "IMMEDIATE SETUP 2" }, + { 0, NULL } +}; + +const struct value_string osmo_gsm44068_priority_level_names[] = { + { OSMO_GSM44068_PRIO_LEVEL_4, "priority level 4" }, + { OSMO_GSM44068_PRIO_LEVEL_3, "priority level 3" }, + { OSMO_GSM44068_PRIO_LEVEL_2, "priority level 2" }, + { OSMO_GSM44068_PRIO_LEVEL_1, "priority level 1" }, + { OSMO_GSM44068_PRIO_LEVEL_0, "priority level 0" }, + { OSMO_GSM44068_PRIO_LEVEL_B, "priority level B" }, + { OSMO_GSM44068_PRIO_LEVEL_A, "priority level A" }, + { 0, NULL } +}; + +const struct value_string osmo_gsm44068_cause_names[] = { + { OSMO_GSM44068_CAUSE_ILLEGAL_MS, "Illegal MS" }, + { OSMO_GSM44068_CAUSE_IMEI_NOT_ACCEPTED, "IMEI not accepted" }, + { OSMO_GSM44068_CAUSE_ILLEGAL_ME, "Illegal ME" }, + { OSMO_GSM44068_CAUSE_SERVICE_NOT_AUTHORIZED, "Service not authorized" }, + { OSMO_GSM44068_CAUSE_APP_NOT_SUPPORTED_ON_PROTO, "Application not supported on the protocol" }, + { OSMO_GSM44068_CAUSE_RR_CONNECTION_ABORTED, "RR connection aborted" }, + { OSMO_GSM44068_CAUSE_NORMAL_CALL_CLEARING, "Normal call clearing" }, + { OSMO_GSM44068_CAUSE_NETWORK_FAILURE, "Network failure" }, + { OSMO_GSM44068_CAUSE_BUSY, "Busy" }, + { OSMO_GSM44068_CAUSE_CONGESTION, "Congestion" }, + { OSMO_GSM44068_CAUSE_USER_NOT_ORIGINATOR, "User not originator of call" }, + { OSMO_GSM44068_CAUSE_NET_WANTS_TO_MAINTAIN_CALL, "Network wants to maintain call" }, + { OSMO_GSM44068_CAUSE_RESPONSE_TO_GET_STATUS, "Response to GET STATUS" }, + { OSMO_GSM44068_CAUSE_SERVICE_OPTION_NOT_SUBSCR, "Service option not supported" }, + { OSMO_GSM44068_CAUSE_REQUESTED_SERVICE_NOT_SUB, "Requested service option not subscribed" }, + { OSMO_GSM44068_CAUSE_SERVICE_OPTION_OOO, "Service option temporarily out of order" }, + { OSMO_GSM44068_CAUSE_CALL_CANNOT_BE_IDENTIFIED, "Call cannot be identified" }, + { OSMO_GSM44068_CAUSE_RETRY_UPON_ENTRY_NEW_CALL, "retry upon entry into a new cell" }, + { OSMO_GSM44068_CAUSE_INVALID_TRANSACTION_ID, "Invalid transaction identifier value" }, + { OSMO_GSM44068_CAUSE_SEMANTICALLY_INCORRECT_MSG, "Semantically incorrect message" }, + { OSMO_GSM44068_CAUSE_INVALID_MANDATORY_INFO, "Invalid mandatory information" }, + { OSMO_GSM44068_CAUSE_MESSAGE_TYPE_NON_EXISTENT, "Message type non-existent or not implemented" }, + { OSMO_GSM44068_CAUSE_MESSAGE_TYPE_NOT_COMPAT, "Message type not compatible with the protocol state" }, + { OSMO_GSM44068_CAUSE_IE_NON_EXISTENT, "Information element non-existent or not implemented" }, + { OSMO_GSM44068_CAUSE_IE_NOT_COMPAT, "Message type not compatible with the protocol state" }, + { OSMO_GSM44068_CAUSE_PROTOCOL_ERROR, "Protocol error, unspecified" }, + { 0, NULL } +}; + +const struct value_string osmo_gsm44068_call_state_names[] = { + { OSMO_GSM44068_CSTATE_U0, "U0" }, + { OSMO_GSM44068_CSTATE_U1, "U1" }, + { OSMO_GSM44068_CSTATE_U2sl_U2, "U2sl/U2" }, + { OSMO_GSM44068_CSTATE_U3, "U3" }, + { OSMO_GSM44068_CSTATE_U4, "U4" }, + { OSMO_GSM44068_CSTATE_U5, "U5" }, + { OSMO_GSM44068_CSTATE_U0p, "U0.p" }, + { OSMO_GSM44068_CSTATE_U2wr_U6, "U2wr/U6" }, + { OSMO_GSM44068_CSTATE_U2r, "U2r" }, + { OSMO_GSM44068_CSTATE_U2ws, "U2ws" }, + { OSMO_GSM44068_CSTATE_U2sr, "U2sr" }, + { OSMO_GSM44068_CSTATE_U2nc, "U2nc" }, + { 0, NULL } +}; + +const struct value_string osmo_gsm44068_talker_priority_names[] = { + { OSMO_GSM44068_PRIO_NORMAL, "Normal" }, + { OSMO_GSM44068_PRIO_PRIVILEGED, "Privileged" }, + { OSMO_GSM44068_PRIO_EMERGENCY, "Emergency" }, + { 0, NULL } +}; + +const struct tlv_definition osmo_gsm44068_att_tlvdef = { + .def = { + [OSMO_GSM44068_IEI_MOBILE_IDENTITY] = { TLV_TYPE_TLV }, + [OSMO_GSM44068_IEI_USER_USER] = { TLV_TYPE_TLV }, + [OSMO_GSM44068_IEI_CALL_STATE] = { TLV_TYPE_SINGLE_TV }, + [OSMO_GSM44068_IEI_STATE_ATTRIBUTES] = { TLV_TYPE_SINGLE_TV }, + [OSMO_GSM44068_IEI_TALKER_PRIORITY] = { TLV_TYPE_SINGLE_TV }, + [OSMO_GSM44068_IEI_SMS_INDICATIONS] = { TLV_TYPE_SINGLE_TV }, + }, +}; diff --git a/src/gsm/gsm48.c b/src/gsm/gsm48.c index 44ce7767..64b17658 100644 --- a/src/gsm/gsm48.c +++ b/src/gsm/gsm48.c @@ -19,10 +19,6 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ #include <stdint.h> @@ -46,6 +42,7 @@ #include <osmocom/gsm/protocol/gsm_08_58.h> #include <osmocom/gsm/protocol/gsm_04_08_gprs.h> #include <osmocom/gsm/protocol/gsm_23_003.h> +#include <osmocom/gsm/protocol/gsm_44_068.h> /*! \addtogroup gsm0408 * @{ @@ -133,10 +130,10 @@ const struct tlv_definition gsm48_rr_att_tlvdef = { [GSM48_IE_REALTIME_DIFF] = { TLV_TYPE_TLV }, [GSM48_IE_START_TIME] = { TLV_TYPE_FIXED, 2 }, [GSM48_IE_TIMING_ADVANCE] = { TLV_TYPE_TV }, - [GSM48_IE_GROUP_CIP_SEQ] = { TLV_TYPE_SINGLE_TV }, - [GSM48_IE_CIP_MODE_SET] = { TLV_TYPE_SINGLE_TV }, - [GSM48_IE_GPRS_RESUMPT] = { TLV_TYPE_SINGLE_TV }, - [GSM48_IE_SYNC_IND] = { TLV_TYPE_SINGLE_TV }, + [GSM48_IE_GROUP_CIP_SEQ_HO] = { TLV_TYPE_SINGLE_TV }, + [GSM48_IE_CIP_MODE_SET_HO] = { TLV_TYPE_SINGLE_TV }, + [GSM48_IE_GPRS_RESUMPT_HO] = { TLV_TYPE_SINGLE_TV }, + [GSM48_IE_SYNC_IND_HO] = { TLV_TYPE_SINGLE_TV }, }, }; @@ -152,7 +149,7 @@ const struct tlv_definition gsm48_mm_att_tlvdef = { [GSM48_IE_NET_DST] = { TLV_TYPE_TLV }, [GSM48_IE_LOCATION_AREA] = { TLV_TYPE_FIXED, 5 }, - [GSM48_IE_PRIORITY_LEV] = { TLV_TYPE_SINGLE_TV }, + [GSM48_IE_PRIORITY_LEV_HO] = { TLV_TYPE_SINGLE_TV }, [GSM48_IE_FOLLOW_ON_PROC] = { TLV_TYPE_T }, [GSM48_IE_CTS_PERMISSION] = { TLV_TYPE_T }, }, @@ -423,16 +420,54 @@ const char *gsm48_rr_msg_name(uint8_t msgtype) return get_value_string(rr_msg_names, msgtype); } +/* 3GPP TS 44.018 Table 10.4.2 */ +static const struct value_string rr_msg_type_short_names[] = { + { GSM48_MT_RR_SH_SI10, "System Information Type 10" }, + { GSM48_MT_RR_SH_FACCH, "Notification/FACCH" }, + { GSM48_MT_RR_SH_UL_FREE, "Uplink Free" }, + { GSM48_MT_RR_SH_MEAS_REP, "Enhanced Measurement Report (uplink)" }, + { GSM48_MT_RR_SH_MEAS_INFO, "Measurement Information (downlink)" }, + { GSM48_MT_RR_SH_VGCS_RECON, "VBS/VGCS Reconfigure" }, + { GSM48_MT_RR_SH_VGCS_RECON2, "VBS/VGCS Reconfigure2" }, + { GSM48_MT_RR_SH_VGCS_INFO, "VGCS Additional Information" }, + { GSM48_MT_RR_SH_VGCS_SMS, "VGCS SMS Information" }, + { GSM48_MT_RR_SH_SI10bis, "System Information Type 10bis" }, + { GSM48_MT_RR_SH_SI10ter, "System Information Type 10ter" }, + { GSM48_MT_RR_SH_VGCS_NEIGH, "VGCS Neighbour Cell Information" }, + { GSM48_MT_RR_SH_APP_DATA, "Notify Application Data" }, + { 0, NULL } +}; + +/*! return string representation of RR Message Type using the RR short protocol discriminator */ +const char *gsm48_rr_short_pd_msg_name(uint8_t msgtype) +{ + return get_value_string(rr_msg_type_short_names, msgtype); +} const struct value_string gsm48_chan_mode_names[] = { { GSM48_CMODE_SIGN, "SIGNALLING" }, { GSM48_CMODE_SPEECH_V1, "SPEECH_V1" }, { GSM48_CMODE_SPEECH_EFR, "SPEECH_EFR" }, { GSM48_CMODE_SPEECH_AMR, "SPEECH_AMR" }, + { GSM48_CMODE_SPEECH_V4, "SPEECH_V4" }, + { GSM48_CMODE_SPEECH_V5, "SPEECH_V5" }, + { GSM48_CMODE_SPEECH_V6, "SPEECH_V6" }, + + { GSM48_CMODE_DATA_43k5_14k5, "DATA_43k5_14k5" }, + { GSM48_CMODE_DATA_29k0_14k5, "DATA_29k0_14k5" }, + { GSM48_CMODE_DATA_43k5_29k0, "DATA_43k5_29k0" }, + { GSM48_CMODE_DATA_14k5_43k5, "DATA_14k5_43k5" }, + { GSM48_CMODE_DATA_14k5_29k0, "DATA_14k5_29k0" }, + { GSM48_CMODE_DATA_29k0_43k5, "DATA_29k0_43k5" }, + + { GSM48_CMODE_DATA_43k5, "DATA_43k5" }, + { GSM48_CMODE_DATA_32k0, "DATA_32k0" }, + { GSM48_CMODE_DATA_29k0, "DATA_29k0" }, { GSM48_CMODE_DATA_14k5, "DATA_14k5" }, { GSM48_CMODE_DATA_12k0, "DATA_12k0" }, { GSM48_CMODE_DATA_6k0, "DATA_6k0" }, { GSM48_CMODE_DATA_3k6, "DATA_3k6" }, + { GSM48_CMODE_SPEECH_V1_VAMOS, "SPEECH_V1_VAMOS" }, { GSM48_CMODE_SPEECH_V2_VAMOS, "SPEECH_V2_VAMOS" }, { GSM48_CMODE_SPEECH_V3_VAMOS, "SPEECH_V3_VAMOS" }, @@ -474,6 +509,8 @@ enum gsm48_chan_mode gsm48_chan_mode_to_non_vamos(enum gsm48_chan_mode mode) return GSM48_CMODE_SPEECH_EFR; case GSM48_CMODE_SPEECH_V3_VAMOS: return GSM48_CMODE_SPEECH_AMR; + case GSM48_CMODE_SPEECH_V5_VAMOS: + return GSM48_CMODE_SPEECH_V5; default: return mode; } @@ -604,8 +641,10 @@ int osmo_mobile_identity_decode(struct osmo_mobile_identity *mi, const uint8_t * char *str = NULL; /* initialize to avoid uninitialized false warnings on some gcc versions (11.1.0) */ size_t str_size = 0; /* initialize to avoid uninitialized false warnings on some gcc versions (11.1.0) */ - if (!mi_data || mi_len < 1) - return -EBADMSG; + if (!mi_data || mi_len < 1) { + rc = -EBADMSG; + goto return_error; + } nibbles_len = (mi_len - 1) * 2 + ((mi_data[0] & GSM_MI_ODD) ? 1 : 0); @@ -827,11 +866,13 @@ int osmo_mobile_identity_encode_msgb(struct msgb *msg, const struct osmo_mobile_ * osmo_mobile_identity. * * \param[out] mi Return buffer for decoded Mobile Identity. - * \param[in] msg The Complete Layer 3 message to extract from (LU, CM Service Req or Paging Resp). + * \param[in] l3_data The Complete Layer 3 message to extract from (LU, CM Service Req or Paging Resp). + * \param[in] l3_len Length of l3_data in bytes. * \returns 0 on success, negative on error: return codes as defined in osmo_mobile_identity_decode(), or * -ENOTSUP = not a Complete Layer 3 message, */ -int osmo_mobile_identity_decode_from_l3(struct osmo_mobile_identity *mi, struct msgb *msg, bool allow_hex) +int osmo_mobile_identity_decode_from_l3_buf(struct osmo_mobile_identity *mi, const uint8_t *l3_data, size_t l3_len, + bool allow_hex) { const struct gsm48_hdr *gh; int8_t pdisc = 0; @@ -850,10 +891,10 @@ int osmo_mobile_identity_decode_from_l3(struct osmo_mobile_identity *mi, struct .tmsi = GSM_RESERVED_TMSI, }; - if (msgb_l3len(msg) < sizeof(*gh)) + if (l3_len < sizeof(*gh)) return -EBADMSG; - gh = msgb_l3(msg); + gh = (void *)l3_data; pdisc = gsm48_hdr_pdisc(gh); mtype = gsm48_hdr_msg_type(gh); @@ -863,12 +904,12 @@ int osmo_mobile_identity_decode_from_l3(struct osmo_mobile_identity *mi, struct switch (mtype) { case GSM48_MT_MM_LOC_UPD_REQUEST: /* First make sure that lu-> can be dereferenced */ - if (msgb_l3len(msg) < sizeof(*gh) + sizeof(*lu)) + if (l3_len < sizeof(*gh) + sizeof(*lu)) return -EBADMSG; /* Now we know there is enough msgb data to read a lu->mi_len, so also check that */ lu = (struct gsm48_loc_upd_req*)gh->data; - if (msgb_l3len(msg) < sizeof(*gh) + sizeof(*lu) + lu->mi_len) + if (l3_len < sizeof(*gh) + sizeof(*lu) + lu->mi_len) return -EBADMSG; mi_data = lu->mi; mi_len = lu->mi_len; @@ -878,7 +919,7 @@ int osmo_mobile_identity_decode_from_l3(struct osmo_mobile_identity *mi, struct case GSM48_MT_MM_CM_REEST_REQ: /* Unfortunately in Phase1 the Classmark2 length is variable, so we cannot * just use gsm48_service_request struct, and need to parse it manually. */ - if (msgb_l3len(msg) < sizeof(*gh) + 2) + if (l3_len < sizeof(*gh) + 2) return -EBADMSG; cm2_len = gh->data[1]; @@ -886,7 +927,7 @@ int osmo_mobile_identity_decode_from_l3(struct osmo_mobile_identity *mi, struct goto got_cm2; case GSM48_MT_MM_IMSI_DETACH_IND: - if (msgb_l3len(msg) < sizeof(*gh) + sizeof(*idi)) + if (l3_len < sizeof(*gh) + sizeof(*idi)) return -EBADMSG; idi = (struct gsm48_imsi_detach_ind*) gh->data; mi_data = idi->mi; @@ -894,7 +935,7 @@ int osmo_mobile_identity_decode_from_l3(struct osmo_mobile_identity *mi, struct goto got_mi; case GSM48_MT_MM_ID_RESP: - if (msgb_l3len(msg) < sizeof(*gh) + 2) + if (l3_len < sizeof(*gh) + 2) return -EBADMSG; mi_data = gh->data+1; mi_len = gh->data[0]; @@ -909,13 +950,24 @@ int osmo_mobile_identity_decode_from_l3(struct osmo_mobile_identity *mi, struct switch (mtype) { case GSM48_MT_RR_PAG_RESP: - if (msgb_l3len(msg) < sizeof(*gh) + sizeof(*paging_response)) + if (l3_len < sizeof(*gh) + sizeof(*paging_response)) return -EBADMSG; paging_response = (struct gsm48_pag_resp*)gh->data; cm2_len = paging_response->cm2_len; cm2_buf = (uint8_t*)&paging_response->cm2; goto got_cm2; + case GSM48_MT_RR_TALKER_IND: + /* Check minimum size: Header + CM2 LV + minimum MI LV */ + if (l3_len < sizeof(*gh) + 4 + 2) + return -EBADMSG; + /* CM2 shall be always 3 bytes in length */ + if (gh->data[0] != 3) + return -EBADMSG; + cm2_len = gh->data[0]; + cm2_buf = gh->data + 1; + goto got_cm2; + default: break; } @@ -928,7 +980,7 @@ got_cm2: /* MI (Mobile Identity) LV follows the Classmark2 */ /* There must be at least a mi_len byte after the CM2 */ - if (cm2_buf + cm2_len + 1 > msg->tail) + if (cm2_buf + cm2_len + 1 > l3_data + l3_len) return -EBADMSG; mi_start = cm2_buf + cm2_len; @@ -937,12 +989,27 @@ got_cm2: got_mi: /* mi_data points at the start of the Mobile Identity coding of mi_len bytes */ - if (mi_data + mi_len > msg->tail) + if (mi_data + mi_len > l3_data + l3_len) return -EBADMSG; return osmo_mobile_identity_decode(mi, mi_data, mi_len, allow_hex); } +/*! Extract Mobile Identity from a Complete Layer 3 message. + * + * Determine the Mobile Identity data and call osmo_mobile_identity_decode() to return a decoded struct + * osmo_mobile_identity. + * + * \param[out] mi Return buffer for decoded Mobile Identity. + * \param[in] msg The Complete Layer 3 message to extract from (LU, CM Service Req or Paging Resp). + * \returns 0 on success, negative on error: return codes as defined in osmo_mobile_identity_decode(), or + * -ENOTSUP = not a Complete Layer 3 message, + */ +int osmo_mobile_identity_decode_from_l3(struct osmo_mobile_identity *mi, struct msgb *msg, bool allow_hex) +{ + return osmo_mobile_identity_decode_from_l3_buf(mi, msgb_l3(msg), msgb_l3len(msg), allow_hex); +} + /*! Return a human readable representation of a struct osmo_mobile_identity. * Write a string like "IMSI-1234567", "TMSI-0x1234ABCD" or "NONE", "NULL". * \param[out] buf String buffer to write to. @@ -1281,7 +1348,64 @@ int gsm48_mi_to_string(char *string, int str_len, const uint8_t *mi, int mi_len) return 1; } -/*! Parse TS 04.08 Routing Area Identifier +/*! Decode to struct osmo_routing_area_id from a 3GPP TS 24.008 § 10.5.5.15 Routing area identification. + * \param[out] dst Store the decoded result here. + * \param[in] ra_data The start of a Routing Area ID in encoded form, to be decoded. + * \param[in] ra_data_len Buffer size available to read from at *ra_data. + * \return the number of decoded bytes on success, or negative on error (if the input buffer is too small). + */ +int osmo_routing_area_id_decode(struct osmo_routing_area_id *dst, const uint8_t *ra_data, size_t ra_data_len) +{ + const struct gsm48_ra_id *ra_id; + if (ra_data_len < sizeof(*ra_id)) + return -ENOSPC; + + gsm48_decode_lai2((void *)ra_data, &dst->lac); + + ra_id = (void *)ra_data; + dst->rac = ra_id->rac; + + return sizeof(*ra_id); +} + +/*! Encode struct osmo_routing_area_id to a 3GPP TS 24.008 § 10.5.5.15 Routing area identification: write to a buffer. + * \param[out] buf Return buffer for encoded Mobile Identity. + * \param[in] buflen sizeof(buf). + * \param[in] src RA to encode. + * \return Amount of bytes written to buf, or negative on error. + */ +int osmo_routing_area_id_encode_buf(uint8_t *buf, size_t buflen, const struct osmo_routing_area_id *src) +{ + struct gsm48_ra_id *ra_id; + if (buflen < sizeof(*ra_id)) + return -ENOSPC; + + gsm48_generate_lai2((void *)buf, &src->lac); + + ra_id = (void *)buf; + ra_id->rac = src->rac; + + return sizeof(*ra_id); +} + +/*! Encode struct osmo_routing_area_id to a 3GPP TS 24.008 § 10.5.5.15 Routing area identification: append to msgb. + * To succeed, the msgb must have tailroom >= sizeof(struct gsm48_ra_id). + * \param[out] msg Append to this msgb. + * \param[in] src Encode this Routing Area ID. + * \return Number of bytes appended to msgb, or negative on error. + */ +int osmo_routing_area_id_encode_msgb(struct msgb *msg, const struct osmo_routing_area_id *src) +{ + int rc = osmo_routing_area_id_encode_buf(msg->tail, msgb_tailroom(msg), src); + if (rc <= 0) + return rc; + msgb_put(msg, rc); + return rc; +} + +/*! Parse TS 04.08 Routing Area Identifier. + * Preferably use osmo_routing_area_id_decode() instead: struct osmo_routing_area_id is better integrated with other API + * like osmo_plmn_cmp(). * \param[out] Caller-provided memory for decoded RA ID * \param[in] buf Input buffer pointing to RAI IE value */ void gsm48_parse_ra(struct gprs_ra_id *raid, const uint8_t *buf) @@ -1365,7 +1489,7 @@ bool gsm48_ra_equal(const struct gprs_ra_id *raid1, const struct gprs_ra_id *rai * Uses From Table 10.5.33 of GSM 04.08 to determine the number of * paging sub-channels in the given control channel configuration */ -int gsm48_number_of_paging_subchannels(struct gsm48_control_channel_descr *chan_desc) +int gsm48_number_of_paging_subchannels(const struct gsm48_control_channel_descr *chan_desc) { unsigned int n_pag_blocks = gsm0502_get_n_pag_blocks(chan_desc); @@ -1645,6 +1769,10 @@ char *gsm48_pdisc_msgtype_name_buf(char *buf, size_t buf_len, uint8_t pdisc, uin case GSM48_PDISC_CC: msgt_names = gsm48_cc_msgtype_names; break; + case GSM48_PDISC_GROUP_CC: + case GSM48_PDISC_BCAST_CC: + msgt_names = osmo_gsm44068_msg_type_names; + break; case GSM48_PDISC_NC_SS: msgt_names = gsm48_nc_ss_msgtype_names; break; diff --git a/src/gsm/gsm48_ie.c b/src/gsm/gsm48_ie.c index 6070e5cc..b95609fe 100644 --- a/src/gsm/gsm48_ie.c +++ b/src/gsm/gsm48_ie.c @@ -19,10 +19,6 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ @@ -48,10 +44,11 @@ static const char bcd_num_digits[] = { }; /*! Like gsm48_decode_bcd_number2() but with less airtight bounds checking. - * \param[out] Caller-provided output buffer + * \param[out] output Caller-provided output buffer + * \param[in] output_len sizeof(output) * \param[in] bcd_lv Length-Value portion of to-be-decoded IE * \param[in] h_len Length of an optional heder between L and V portion - * \returns - in case of success; negative on error */ + * \returns 0 in case of success; negative on error */ int gsm48_decode_bcd_number(char *output, int output_len, const uint8_t *bcd_lv, int h_len) { @@ -143,7 +140,7 @@ static int asc_to_bcd(const char asc) * \param[in] max_len Maximum Length of \a bcd_lv * \param[in] h_len Length of an optional heder between L and V portion * \param[in] input phone number as 0-terminated ASCII - * \returns number of bytes used in \a bcd_lv + * \returns number of bytes used in \a bcd_lv; negative on error * * Depending on a context (e.g. called or calling party BCD number), the * optional header between L and V parts can contain TON (Type Of Number), @@ -183,8 +180,8 @@ int gsm48_encode_bcd_number(uint8_t *bcd_lv, uint8_t max_len, } /*! Decode TS 04.08 Bearer Capability IE (10.5.4.5) - * \param[out] Caller-provided memory for decoded output - * \[aram[in] LV portion of TS 04.08 Bearer Capability + * \param[out] bcap Caller-provided memory for decoded output + * \param[in] lv LV portion of TS 04.08 Bearer Capability * \returns 0 on success; negative on error */ int gsm48_decode_bearer_cap(struct gsm_mncc_bearer_cap *bcap, const uint8_t *lv) @@ -207,7 +204,24 @@ int gsm48_decode_bearer_cap(struct gsm_mncc_bearer_cap *bcap, case GSM_MNCC_BCAP_SPEECH: i = 1; s = 0; - while(!(lv[i] & 0x80)) { + if ((lv[1] & 0x80) != 0) { /* octet 3a is absent */ + switch (bcap->radio) { + case GSM48_BCAP_RRQ_FR_ONLY: + bcap->speech_ver[s++] = GSM48_BCAP_SV_FR; + break; + case GSM48_BCAP_RRQ_DUAL_HR: + bcap->speech_ver[s++] = GSM48_BCAP_SV_HR; + bcap->speech_ver[s++] = GSM48_BCAP_SV_FR; + break; + case GSM48_BCAP_RRQ_DUAL_FR: + bcap->speech_ver[s++] = GSM48_BCAP_SV_FR; + bcap->speech_ver[s++] = GSM48_BCAP_SV_HR; + break; + } + bcap->speech_ver[s] = -1; /* end of list */ + return 0; + } + while (!(lv[i] & 0x80)) { i++; /* octet 3a etc */ if (in_len < i) return 0; @@ -220,9 +234,10 @@ int gsm48_decode_bearer_cap(struct gsm_mncc_bearer_cap *bcap, } break; case GSM_MNCC_BCAP_UNR_DIG: + case GSM_MNCC_BCAP_AUDIO: case GSM_MNCC_BCAP_FAX_G3: i = 1; - while(!(lv[i] & 0x80)) { + while (!(lv[i] & 0x80)) { i++; /* octet 3a etc */ if (in_len < i) return 0; @@ -236,7 +251,7 @@ int gsm48_decode_bearer_cap(struct gsm_mncc_bearer_cap *bcap, return 0; bcap->data.rate_adaption = (lv[i] >> 3) & 3; bcap->data.sig_access = lv[i] & 7; - while(!(lv[i] & 0x80)) { + while (!(lv[i] & 0x80)) { i++; /* octet 5a etc */ if (in_len < i) return 0; @@ -321,10 +336,11 @@ int gsm48_encode_bearer_cap(struct msgb *msg, int lv_only, lv[i] |= 0x80; /* last IE of octet 3 etc */ break; case GSM48_BCAP_ITCAP_UNR_DIG_INF: + case GSM48_BCAP_ITCAP_3k1_AUDIO: case GSM48_BCAP_ITCAP_FAX_G3: lv[i++] |= 0x80; /* last IE of octet 3 etc */ /* octet 4 */ - lv[i++] = 0xb8; + lv[i++] = 0x88; /* octet 5 */ lv[i++] = 0x80 | ((bcap->data.rate_adaption & 3) << 3) | (bcap->data.sig_access & 7); @@ -338,7 +354,9 @@ int gsm48_encode_bearer_cap(struct msgb *msg, int lv_only, lv[i++] = (bcap->data.parity & 7) | ((bcap->data.interm_rate & 3) << 5); /* octet 6c */ - lv[i] = 0x80 | (bcap->data.modem_type & 0x1f); + lv[i] = 0x80 | + ((bcap->data.transp & 3) << 5) | + (bcap->data.modem_type & 0x1f); break; default: return -EINVAL; @@ -354,9 +372,9 @@ int gsm48_encode_bearer_cap(struct msgb *msg, int lv_only, } /*! Decode TS 04.08 Call Control Capabilities IE (10.5.4.5a) - * \param[out] Caller-provided memory for decoded CC capabilities + * \param[out] ccap Caller-provided memory for decoded CC capabilities * \param[in] lv Length-Value of IE - * \retursns 0 on success; negative on error */ + * \returns 0 on success; negative on error */ int gsm48_decode_cccap(struct gsm_mncc_cccap *ccap, const uint8_t *lv) { uint8_t in_len = lv[0]; @@ -440,7 +458,7 @@ int gsm48_encode_called(struct msgb *msg, } /*! Decode TS 04.08 Caller ID - * \param[out] called Caller-provided memory for decoded number + * \param[out] callerid Caller-provided memory for decoded number * \param[in] lv Length-Value portion of IE * \returns 0 on success; negative on error */ int gsm48_decode_callerid(struct gsm_mncc_number *callerid, @@ -812,7 +830,7 @@ int gsm48_decode_ssversion(struct gsm_mncc_ssversion *ssv, { uint8_t in_len = lv[0]; - if (in_len < 1 || in_len < sizeof(ssv->info)) + if (in_len < 1 || in_len > sizeof(ssv->info)) return -EINVAL; memcpy(ssv->info, lv + 1, in_len); @@ -867,8 +885,9 @@ static int32_t smod(int32_t n, int32_t m) * \param[in] cd Cell Channel Description IE * \param[in] len Length of \a cd in bytes * \returns 0 on success; negative on error */ -int gsm48_decode_freq_list(struct gsm_sysinfo_freq *f, uint8_t *cd, - uint8_t len, uint8_t mask, uint8_t frqt) +int gsm48_decode_freq_list(struct gsm_sysinfo_freq *f, + const uint8_t *cd, uint8_t len, + uint8_t mask, uint8_t frqt) { int i; diff --git a/src/gsm/gsm48_rest_octets.c b/src/gsm/gsm48_rest_octets.c index 77fd349b..57c2c99a 100644 --- a/src/gsm/gsm48_rest_octets.c +++ b/src/gsm/gsm48_rest_octets.c @@ -59,6 +59,82 @@ int osmo_gsm48_rest_octets_si1_encode(uint8_t *data, uint8_t *nch_pos, int is180 return bv.data_len; } +struct nch_pos { + uint8_t num_blocks; + uint8_t first_block; +}; + +/* 3GPP TS 44.010 Table 10.5.2.32.1b */ +static const struct nch_pos si1ro_nch_positions[] = { + [0x00] = {1, 0}, + [0x01] = {1, 1}, + [0x02] = {1, 2}, + [0x03] = {1, 3}, + [0x04] = {1, 4}, + [0x05] = {1, 5}, + [0x06] = {1, 6}, + + [0x07] = {2, 0}, + [0x08] = {2, 1}, + [0x09] = {2, 2}, + [0x0a] = {2, 3}, + [0x0b] = {2, 4}, + [0x0c] = {2, 5}, + + [0x0d] = {3, 0}, + [0x0e] = {3, 1}, + [0x0f] = {3, 2}, + [0x10] = {3, 3}, + [0x11] = {3, 4}, + + [0x12] = {4, 0}, + [0x13] = {4, 1}, + [0x14] = {4, 2}, + [0x15] = {4, 3}, + + [0x16] = {5, 0}, + [0x17] = {5, 1}, + [0x18] = {5, 2}, + + [0x19] = {6, 0}, + [0x1a] = {6, 1}, + + [0x1b] = {7, 0}, +}; + +/*! Decode the 5-bit 'NCH position' field within SI1 Rest Octets. + * \param[in] value 5-bit value from SI1 rest octets + * \param[out] num_blocks Number of CCCH used for NCH + * \param[out] first_block First CCCH block used for NCH + * \returns 0 on success; negative in case of error */ +int osmo_gsm48_si1ro_nch_pos_decode(uint8_t value, uint8_t *num_blocks, uint8_t *first_block) +{ + if (value >= ARRAY_SIZE(si1ro_nch_positions)) + return -EINVAL; + + *num_blocks = si1ro_nch_positions[value].num_blocks; + *first_block = si1ro_nch_positions[value].first_block; + + return 0; +} + +/*! Encode the 5-bit 'NCH position' field within SI1 Rest Octets. + * \param[in] num_blocks Number of CCCH used for NCH + * \param[in] first_block First CCCH block used for NCH + * \returns 5-bit value for SI1 rest octets on success; negative in case of error */ +int osmo_gsm48_si1ro_nch_pos_encode(uint8_t num_blocks, uint8_t first_block) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(si1ro_nch_positions); i++) { + if (si1ro_nch_positions[i].num_blocks == num_blocks && + si1ro_nch_positions[i].first_block == first_block) { + return i; + } + } + return -EINVAL; +} + /* Append Repeated E-UTRAN Neighbour Cell to bitvec: see 3GPP TS 44.018 Table 10.5.2.33b.1 */ static inline bool append_eutran_neib_cell(struct bitvec *bv, const struct osmo_earfcn_si2q *e, size_t *e_offset, uint8_t budget) diff --git a/src/gsm/gsm_utils.c b/src/gsm/gsm_utils.c index 07e082d3..bb403392 100644 --- a/src/gsm/gsm_utils.c +++ b/src/gsm/gsm_utils.c @@ -19,10 +19,6 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ /*! \mainpage libosmogsm Documentation @@ -84,6 +80,7 @@ #include <osmocom/gsm/gsm_utils.h> #include <osmocom/gsm/meas_rep.h> #include <osmocom/gsm/protocol/gsm_04_08.h> +#include <osmocom/gsm/gsm0502.h> #include <stdlib.h> #include <stdint.h> @@ -96,7 +93,7 @@ #include <time.h> #include <unistd.h> -#include "../../config.h" +#include "config.h" #if (!EMBEDDED) /* FIXME: this can be removed once we bump glibc requirements to 2.25: */ @@ -329,12 +326,13 @@ int gsm_septet_pack(uint8_t *result, const uint8_t *rdata, size_t septet_len, ui int i = 0, z = 0; uint8_t cb, nb; int shift = 0; - uint8_t *data = calloc(septet_len + 1, sizeof(uint8_t)); + uint8_t *data = malloc(septet_len + 1); if (padding) { shift = 7 - padding; /* the first zero is needed for padding */ memcpy(data + 1, rdata, septet_len); + data[0] = 0x00; septet_len++; } else memcpy(data, rdata, septet_len); @@ -388,7 +386,7 @@ int gsm_7bit_encode_n(uint8_t *result, size_t n, const char *data, int *octets) size_t max_septets = n * 8 / 7; /* prepare for the worst case, every character expanding to two bytes */ - uint8_t *rdata = calloc(strlen(data) * 2, sizeof(uint8_t)); + uint8_t *rdata = malloc(strlen(data) * 2); y = gsm_septet_encode(rdata, data); if (y > max_septets) { @@ -493,7 +491,7 @@ int osmo_get_rand_id(uint8_t *out, size_t len) * \param[out] buf Pre-allocated bufer for storing IE * \returns Number of bytes filled in buf */ -size_t gsm0858_rsl_ul_meas_enc(struct gsm_meas_rep_unidir *mru, bool dtxd_used, +size_t gsm0858_rsl_ul_meas_enc(const struct gsm_meas_rep_unidir *mru, bool dtxd_used, uint8_t *buf) { buf[0] = dtxd_used ? (1 << 6) : 0; @@ -888,10 +886,21 @@ char *gsm_fn_as_gsmtime_str(uint32_t fn) /*! Encode decoded \ref gsm_time to Frame Number * \param[in] time GSM Time in decoded structure * \returns GSM Frame Number */ -uint32_t gsm_gsmtime2fn(struct gsm_time *time) +uint32_t gsm_gsmtime2fn(const struct gsm_time *time) { - /* TS 05.02 Chapter 4.3.3 TDMA frame number */ - return (51 * ((time->t3 - time->t2 + 26) % 26) + time->t3 + (26 * 51 * time->t1)); + uint32_t fn; + + /* See also: + * 3GPP TS 44.018, section 10.5.2.38, 3GPP TS 45.002 section 4.3.3, and + * 3GPP TS 48.058, section 9.3.8 */ + fn = 51 * OSMO_MOD_FLR((time->t3-time->t2), 26) + time->t3 + 51 * 26 * time->t1; + + /* Note: Corrupted input values may cause a resulting frame number + * larger then the maximum permitted value of GSM_MAX_FN. Even though + * the caller is expected to check the input values beforehand we must + * make sure that the result cannot exceed the value range of a valid + * GSM frame number. */ + return fn % GSM_MAX_FN; } char *osmo_dump_gsmtime_buf(char *buf, size_t buf_len, const struct gsm_time *tm) @@ -916,6 +925,45 @@ char *osmo_dump_gsmtime_c(const void *ctx, const struct gsm_time *tm) return osmo_dump_gsmtime_buf(buf, 64, tm); } +#define GSM_RFN_THRESHOLD (GSM_RFN_MODULUS / 2) +uint32_t gsm_rfn2fn(uint16_t rfn, uint32_t curr_fn) +{ + uint32_t curr_rfn; + uint32_t fn_rounded; + const uint32_t rfn32 = rfn; /* used as 32bit for calculations */ + + /* Ensure that all following calculations are performed with the + * relative frame number */ + OSMO_ASSERT(rfn32 < GSM_RFN_MODULUS); + + /* Compute an internal relative frame number from the full internal + frame number */ + curr_rfn = gsm_fn2rfn(curr_fn); + + /* Compute a "rounded" version of the internal frame number, which + * exactly fits in the RFN_MODULUS raster */ + fn_rounded = GSM_TDMA_FN_SUB(curr_fn, curr_rfn); + + /* If the delta between the internal and the external relative frame + * number exceeds a certain limit, we need to assume that the incoming + * request belongs to a the previous rfn period. To correct this, + * we roll back the rounded frame number by one RFN_MODULUS */ + if (GSM_TDMA_FN_DIFF(rfn32, curr_rfn) > GSM_RFN_THRESHOLD) { + /* Race condition between rfn and curr_fn detected: rfn belongs + to the previous RFN_MODULUS cycle, wrapping... */ + if (fn_rounded < GSM_RFN_MODULUS) { + /* Corner case detected: wrapping crosses GSM_MAX_FN border */ + fn_rounded = GSM_TDMA_FN_SUB(GSM_MAX_FN, (GSM_TDMA_FN_SUB(GSM_RFN_MODULUS, fn_rounded))); + } else { + fn_rounded = GSM_TDMA_FN_SUB(fn_rounded, GSM_RFN_MODULUS); + } + } + + /* The real frame number is the sum of the rounded frame number and the + * relative framenumber computed via RACH */ + return GSM_TDMA_FN_SUM(fn_rounded, rfn32); +} + /*! append range1024 encoded data to bit vector * \param[out] bv Caller-provided output bit-vector * \param[in] r Input Range1024 sructure */ diff --git a/src/gsm/gsup.c b/src/gsm/gsup.c index ef33ed08..4f0a1b5f 100644 --- a/src/gsm/gsup.c +++ b/src/gsm/gsup.c @@ -103,6 +103,10 @@ const struct value_string osmo_gsup_message_type_names[] = { OSMO_VALUE_STRING(OSMO_GSUP_MSGT_ROUTING_ERROR), + OSMO_VALUE_STRING(OSMO_GSUP_MSGT_EPDG_TUNNEL_REQUEST), + OSMO_VALUE_STRING(OSMO_GSUP_MSGT_EPDG_TUNNEL_RESULT), + OSMO_VALUE_STRING(OSMO_GSUP_MSGT_EPDG_TUNNEL_ERROR), + { 0, NULL } }; @@ -122,6 +126,62 @@ int osmo_gsup_get_err_msg_type(enum osmo_gsup_message_type type_in) return OSMO_GSUP_TO_MSGT_ERROR(type_in); } +static int decode_pdp_address(const uint8_t *data, size_t data_len, struct osmo_gsup_pdp_info *pdp_info) +{ + const struct gsm48_pdp_address *pdp_addr = (const struct gsm48_pdp_address *)data; + /* Explicitly pre-nitialize them to AF_UNSPEC to signal they are empty: */ + pdp_info->pdp_address[0].u.sa.sa_family = AF_UNSPEC; + pdp_info->pdp_address[1].u.sa.sa_family = AF_UNSPEC; + + if (data_len < 2) + return -GMM_CAUSE_PROTO_ERR_UNSPEC; + + pdp_info->pdp_type_org = pdp_addr->organization; + pdp_info->pdp_type_nr = pdp_addr->type; + + if (pdp_info->pdp_type_org != PDP_TYPE_ORG_IETF) + return -GMM_CAUSE_PROTO_ERR_UNSPEC; + + /* Skip type-org + type-nr for easy calculations below: */ + data_len -= 2; + + switch (pdp_info->pdp_type_nr) { + case PDP_TYPE_N_IETF_IPv4: + if (data_len == 0) + return 0; /* empty, marked as AF_UNSET. */ + if (data_len != sizeof(pdp_addr->ietf.v4)) + return -GMM_CAUSE_PROTO_ERR_UNSPEC; + pdp_info->pdp_address[0].u.sa.sa_family = AF_INET; + pdp_info->pdp_address[0].u.sin.sin_addr.s_addr = pdp_addr->ietf.v4; + return 0; + case PDP_TYPE_N_IETF_IPv6: + if (data_len == 0) + return 0; /* empty, marked as AF_UNSET. */ + if (data_len != sizeof(pdp_addr->ietf.v6)) + return -GMM_CAUSE_PROTO_ERR_UNSPEC; + pdp_info->pdp_address[0].u.sa.sa_family = AF_INET6; + memcpy(&pdp_info->pdp_address[0].u.sin6.sin6_addr, + pdp_addr->ietf.v6, + sizeof(pdp_addr->ietf.v6)); + return 0; + case PDP_TYPE_N_IETF_IPv4v6: + if (data_len == 0) + return 0; /* empty, marked as AF_UNSET. */ + if (data_len != sizeof(pdp_addr->ietf.v4v6)) + return -GMM_CAUSE_PROTO_ERR_UNSPEC; + pdp_info->pdp_address[0].u.sa.sa_family = AF_INET; + pdp_info->pdp_address[0].u.sin.sin_addr.s_addr = pdp_addr->ietf.v4v6.v4; + pdp_info->pdp_address[1].u.sa.sa_family = AF_INET6; + memcpy(&pdp_info->pdp_address[1].u.sin6.sin6_addr, + pdp_addr->ietf.v4v6.v6, + sizeof(pdp_addr->ietf.v4v6.v6)); + return 0; + default: + /* reserved, both pdp_info->pdp_address are preinitialied to AF_UNSET. */ + return 0; + } +} + static int decode_pdp_info(uint8_t *data, size_t data_len, struct osmo_gsup_pdp_info *pdp_info) { @@ -145,9 +205,9 @@ static int decode_pdp_info(uint8_t *data, size_t data_len, pdp_info->context_id = osmo_decode_big_endian(value, value_len); break; - case OSMO_GSUP_PDP_TYPE_IE: - pdp_info->pdp_type = - osmo_decode_big_endian(value, value_len) & 0x0fff; + case OSMO_GSUP_PDP_ADDRESS_IE: + if ((rc = decode_pdp_address(value, value_len, pdp_info)) < 0) + return rc; break; case OSMO_GSUP_ACCESS_POINT_NAME_IE: @@ -262,7 +322,7 @@ static int decode_auth_info(uint8_t *data, size_t data_len, parse_error: LOGP(DLGSUP, LOGL_ERROR, - "GSUP IE type %d, length %zu invalid in PDP info\n", iei, value_len); + "GSUP IE type %d, length %zu invalid in auth info\n", iei, value_len); return -1; } @@ -353,7 +413,7 @@ int osmo_gsup_decode(const uint8_t *const_data, size_t data_len, switch (iei) { case OSMO_GSUP_IMSI_IE: - case OSMO_GSUP_PDP_TYPE_IE: + case OSMO_GSUP_PDP_ADDRESS_IE: case OSMO_GSUP_ACCESS_POINT_NAME_IE: case OSMO_GSUP_SRES_IE: case OSMO_GSUP_KC_IE: @@ -446,6 +506,11 @@ int osmo_gsup_decode(const uint8_t *const_data, size_t data_len, gsup_msg->rand = value; break; + case OSMO_GSUP_PCO_IE: + gsup_msg->pco = value; + gsup_msg->pco_len = value_len; + break; + case OSMO_GSUP_MSISDN_IE: gsup_msg->msisdn_enc = value; gsup_msg->msisdn_enc_len = value_len; @@ -597,11 +662,45 @@ static void encode_pdp_info(struct msgb *msg, enum osmo_gsup_iei iei, u8 = pdp_info->context_id; msgb_tlv_put(msg, OSMO_GSUP_PDP_CONTEXT_ID_IE, sizeof(u8), &u8); - if (pdp_info->pdp_type) { - msgb_tlv_put(msg, OSMO_GSUP_PDP_TYPE_IE, - OSMO_GSUP_PDP_TYPE_SIZE, - osmo_encode_big_endian(pdp_info->pdp_type | 0xf000, - OSMO_GSUP_PDP_TYPE_SIZE)); + if (pdp_info->pdp_type_org == PDP_TYPE_ORG_IETF) { + struct gsm48_pdp_address pdp_addr; + uint8_t pdp_addr_len = 2; + pdp_addr.spare = 0x0f; + pdp_addr.organization = pdp_info->pdp_type_org; + pdp_addr.type = pdp_info->pdp_type_nr; + + switch (pdp_info->pdp_type_nr) { + case PDP_TYPE_N_IETF_IPv4: + if (pdp_info->pdp_address[0].u.sa.sa_family == AF_INET) { + pdp_addr.ietf.v4 = pdp_info->pdp_address[0].u.sin.sin_addr.s_addr; + pdp_addr_len += sizeof(pdp_addr.ietf.v4); + } + break; + case PDP_TYPE_N_IETF_IPv6: + if (pdp_info->pdp_address[0].u.sa.sa_family == AF_INET6) { + memcpy(pdp_addr.ietf.v6, + &pdp_info->pdp_address[0].u.sin6.sin6_addr, + sizeof(pdp_addr.ietf.v6)); + pdp_addr_len += sizeof(pdp_addr.ietf.v6); + } + break; + case PDP_TYPE_N_IETF_IPv4v6: + if (pdp_info->pdp_address[0].u.sa.sa_family == AF_INET) { + pdp_addr.ietf.v4v6.v4 = pdp_info->pdp_address[0].u.sin.sin_addr.s_addr; + pdp_addr_len += sizeof(pdp_addr.ietf.v4v6.v4); + } + if (pdp_info->pdp_address[0].u.sa.sa_family == AF_INET6) { + memcpy(pdp_addr.ietf.v4v6.v6, + &pdp_info->pdp_address[1].u.sin6.sin6_addr, + sizeof(pdp_addr.ietf.v4v6.v6)); + pdp_addr_len += sizeof(pdp_addr.ietf.v4v6.v6); + } + break; + } + + msgb_tlv_put(msg, OSMO_GSUP_PDP_ADDRESS_IE, + pdp_addr_len, + (const uint8_t *)&pdp_addr); } if (pdp_info->apn_enc) { @@ -778,6 +877,11 @@ int osmo_gsup_encode(struct msgb *msg, const struct osmo_gsup_message *gsup_msg) if (gsup_msg->rand) msgb_tlv_put(msg, OSMO_GSUP_RAND_IE, 16, gsup_msg->rand); + if (gsup_msg->pco && gsup_msg->pco_len > 0) { + if (gsup_msg->pco_len > OSMO_GSUP_MAX_PCO_LEN) + return -EINVAL; + msgb_tlv_put(msg, OSMO_GSUP_PCO_IE, gsup_msg->pco_len, gsup_msg->pco); + } if (gsup_msg->cn_domain) { uint8_t dn = gsup_msg->cn_domain; msgb_tlv_put(msg, OSMO_GSUP_CN_DOMAIN_IE, 1, &dn); @@ -911,6 +1015,7 @@ const struct value_string osmo_gsup_message_class_names[] = { { OSMO_GSUP_MESSAGE_CLASS_SMS, "SMS" }, { OSMO_GSUP_MESSAGE_CLASS_USSD, "USSD" }, { OSMO_GSUP_MESSAGE_CLASS_INTER_MSC, "Inter-MSC" }, + { OSMO_GSUP_MESSAGE_CLASS_IPSEC_EPDG, "IPsec-ePDG" }, {} }; diff --git a/src/gsm/ipa.c b/src/gsm/ipa.c index 447e8e3d..6e41fd98 100644 --- a/src/gsm/ipa.c +++ b/src/gsm/ipa.c @@ -267,34 +267,43 @@ int ipa_parse_unitid(const char *str, struct ipaccess_unit *unit_data) return 0; } +/*! Fill ud struct from tp structure. + * \param[in,out] ud ipaccess_unit to fill + * \param[in] tp the decoded TLV structure from eg. ID_RESP message + * \returns zero on success, negative on error + * + * This function expects parameter ud's fields to be initialized to zero if not yet set. + * Existing incoming string pointer fields are expected to be allocated using + * talloc and will be deallocated as such if replaced with the content of tp. + **/ int ipa_ccm_tlv_to_unitdata(struct ipaccess_unit *ud, const struct tlv_parsed *tp) { int rc = 0; if (TLVP_PRES_LEN(tp, IPAC_IDTAG_SERNR, 1)) - ud->serno = talloc_strdup(ud, (char *) - TLVP_VAL(tp, IPAC_IDTAG_SERNR)); + osmo_talloc_replace_string(ud, &ud->serno, + (char *)TLVP_VAL(tp, IPAC_IDTAG_SERNR)); if (TLVP_PRES_LEN(tp, IPAC_IDTAG_UNITNAME, 1)) - ud->unit_name = talloc_strdup(ud, (char *) - TLVP_VAL(tp, IPAC_IDTAG_UNITNAME)); + osmo_talloc_replace_string(ud, &ud->unit_name, + (char *)TLVP_VAL(tp, IPAC_IDTAG_UNITNAME)); if (TLVP_PRES_LEN(tp, IPAC_IDTAG_LOCATION1, 1)) - ud->location1 = talloc_strdup(ud, (char *) - TLVP_VAL(tp, IPAC_IDTAG_LOCATION1)); + osmo_talloc_replace_string(ud, &ud->location1, + (char *)TLVP_VAL(tp, IPAC_IDTAG_LOCATION1)); if (TLVP_PRES_LEN(tp, IPAC_IDTAG_LOCATION2, 1)) - ud->location2 = talloc_strdup(ud, (char *) - TLVP_VAL(tp, IPAC_IDTAG_LOCATION2)); + osmo_talloc_replace_string(ud, &ud->location2, + (char *)TLVP_VAL(tp, IPAC_IDTAG_LOCATION2)); if (TLVP_PRES_LEN(tp, IPAC_IDTAG_EQUIPVERS, 1)) - ud->equipvers = talloc_strdup(ud, (char *) - TLVP_VAL(tp, IPAC_IDTAG_EQUIPVERS)); + osmo_talloc_replace_string(ud, &ud->equipvers, + (char *)TLVP_VAL(tp, IPAC_IDTAG_EQUIPVERS)); if (TLVP_PRES_LEN(tp, IPAC_IDTAG_SWVERSION, 1)) - ud->swversion = talloc_strdup(ud, (char *) - TLVP_VAL(tp, IPAC_IDTAG_SWVERSION)); + osmo_talloc_replace_string(ud, &ud->swversion, + (char *)TLVP_VAL(tp, IPAC_IDTAG_SWVERSION)); if (TLVP_PRES_LEN(tp, IPAC_IDTAG_MACADDR, 17)) { rc = osmo_macaddr_parse(ud->mac_addr, (char *) @@ -478,7 +487,7 @@ int ipa_ccm_rcvmsg_base(struct msgb *msg, struct osmo_fd *bfd) case IPAC_MSGT_PING: ret = ipa_ccm_send_pong(bfd->fd); if (ret < 0) { - LOGP(DLINP, LOGL_ERROR, "Cannot send PING " + LOGP(DLINP, LOGL_ERROR, "Cannot send PONG " "message. Reason: %s\n", strerror(errno)); break; } diff --git a/src/gsm/iuup.c b/src/gsm/iuup.c new file mode 100644 index 00000000..16a6f5e0 --- /dev/null +++ b/src/gsm/iuup.c @@ -0,0 +1,1064 @@ +/*! \file iu_up.c + * IuUP (Iu User Plane) according to 3GPP TS 25.415 */ +/* + * (C) 2017 by Harald Welte <laforge@gnumonks.org> + * + * SPDX-License-Identifier: GPL-2.0+ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <errno.h> +#include <inttypes.h> + +#include <osmocom/core/crc8gen.h> +#include <osmocom/core/crc16gen.h> +#include <osmocom/core/fsm.h> +#include <osmocom/core/prim.h> +#include <osmocom/core/timer.h> +#include <osmocom/core/logging.h> + +#include <osmocom/gsm/prim.h> +#include <osmocom/gsm/protocol/gsm_25_415.h> +#include <osmocom/gsm/iuup.h> + +/*********************************************************************** + * CRC Calculation + ***********************************************************************/ + +/* Section 6.6.3.8 Header CRC */ +const struct osmo_crc8gen_code iuup_hdr_crc_code = { + .bits = 6, + .poly = 47, + .init = 0, + .remainder = 0, +}; + +/* Section 6.6.3.9 Payload CRC */ +const struct osmo_crc16gen_code iuup_data_crc_code = { + .bits = 10, + .poly = 563, + .init = 0, + .remainder = 0, +}; + +static int iuup_get_payload_offset(const uint8_t *iuup_pdu) +{ + uint8_t pdu_type = iuup_pdu[0] >> 4; + switch (pdu_type) { + case 0: + case 14: + return 4; + case 1: + return 3; + default: + return -1; + } +} + +int osmo_iuup_compute_payload_crc(const uint8_t *iuup_pdu, unsigned int pdu_len) +{ + ubit_t buf[1024*8]; + uint8_t pdu_type; + int offset, payload_len_bytes; + + if (pdu_len < 1) + return -1; + + pdu_type = iuup_pdu[0] >> 4; + + /* Type 1 has no CRC */ + if (pdu_type == 1) + return 0; + + offset = iuup_get_payload_offset(iuup_pdu); + if (offset < 0) + return offset; + + if (pdu_len < offset) + return -1; + + payload_len_bytes = pdu_len - offset; + osmo_pbit2ubit(buf, iuup_pdu+offset, payload_len_bytes*8); + return osmo_crc16gen_compute_bits(&iuup_data_crc_code, buf, payload_len_bytes*8); +} + +int osmo_iuup_compute_header_crc(const uint8_t *iuup_pdu, unsigned int pdu_len) +{ + ubit_t buf[2*8]; + + if (pdu_len < 2) + return -1; + + osmo_pbit2ubit(buf, iuup_pdu, 2*8); + return osmo_crc8gen_compute_bits(&iuup_hdr_crc_code, buf, 2*8); +} + +/*********************************************************************** + * Internal State / FSM (Annex B) + ***********************************************************************/ + +#define S(x) (1 << (x)) + +#define IUUP_TIMER_INIT 1 +#define IUUP_TIMER_TA 2 +#define IUUP_TIMER_RC 3 + +struct osmo_timer_nt { + uint32_t n; /* number of repetitions */ + struct osmo_iuup_tnl_prim *retrans_itp; + struct osmo_timer_list timer; +}; + +struct osmo_iuup_instance { + struct osmo_iuup_rnl_config config; + struct osmo_fsm_inst *fi; + + uint8_t mode_version; + + struct { + struct osmo_timer_nt init; + struct osmo_timer_nt ta; + struct osmo_timer_nt rc; + } timer; + /* call-back function to pass primitives up to the user */ + osmo_prim_cb user_prim_cb; + void *user_prim_priv; + osmo_prim_cb transport_prim_cb; + void *transport_prim_priv; + uint8_t type14_fn; /* 2 bits */ +}; + +enum iuup_fsm_state { + IUUP_FSM_ST_NULL, + IUUP_FSM_ST_INIT, + IUUP_FSM_ST_TrM_DATA_XFER_READY, + IUUP_FSM_ST_SMpSDU_DATA_XFER_READY, +}; + +enum iuup_fsm_event { + IUUP_FSM_EVT_IUUP_CONFIG_REQ, + IUUP_FSM_EVT_IUUP_DATA_REQ, + IUUP_FSM_EVT_IUUP_DATA_IND, + IUUP_FSM_EVT_IUUP_STATUS_REQ, + IUUP_FSM_EVT_IUUP_STATUS_IND, + IUUP_FSM_EVT_SSASAR_UNITDATA_REQ, + IUUP_FSM_EVT_SSASAR_UNITDATA_IND, + IUUP_FSM_EVT_IUUP_UNITDATA_REQ, + IUUP_FSM_EVT_IUUP_UNITDATA_IND, + IUUP_FSM_EVT_INIT, + IUUP_FSM_EVT_LAST_INIT_ACK, + IUUP_FSM_EVT_INIT_NACK, +}; + +static const struct value_string iuup_fsm_event_names[] = { + { IUUP_FSM_EVT_IUUP_CONFIG_REQ, "IuUP-CONFIG-req" }, + { IUUP_FSM_EVT_IUUP_DATA_REQ, "IuUP-DATA-req" }, + { IUUP_FSM_EVT_IUUP_DATA_IND, "IuUP-DATA-ind" }, + { IUUP_FSM_EVT_IUUP_STATUS_REQ, "IuUP-STATUS-req" }, + { IUUP_FSM_EVT_IUUP_STATUS_IND, "IuUP-STATUS-ind" }, + { IUUP_FSM_EVT_SSASAR_UNITDATA_REQ, "SSSAR-UNITDATA-req" }, + { IUUP_FSM_EVT_SSASAR_UNITDATA_IND, "SSSAR-UNITDATA-ind" }, + { IUUP_FSM_EVT_IUUP_UNITDATA_REQ, "IuUP-UNITDATA-req" }, + { IUUP_FSM_EVT_IUUP_UNITDATA_IND, "IuUP-UNITDATA-ind" }, + { IUUP_FSM_EVT_INIT, "INIT" }, + { IUUP_FSM_EVT_LAST_INIT_ACK, "LAST_INIT_ACK" }, + { IUUP_FSM_EVT_INIT_NACK, "INIT_NACK" }, + { 0, NULL } +}; + +static inline uint8_t iuup_get_pdu_type(const uint8_t *data) +{ + return data[0] >> 4; +} + +static inline uint8_t iuup_get_hdr_crc(const uint8_t *data) +{ + return data[2] >> 2; +} + +/* Helper functions to store non-packed structs in msgb so that pointers are properly aligned: */ +#define IUUP_MSGB_SIZE 4096 +#define PTR_ALIGNMENT_BYTES 8 +#define IUUP_MSGB_HEADROOM_MIN_REQUIRED (OSMO_MAX(sizeof(struct osmo_iuup_tnl_prim), sizeof(struct osmo_iuup_rnl_prim)) + (PTR_ALIGNMENT_BYTES - 1)) +static inline struct msgb *osmo_iuup_msgb_alloc_c(void *ctx, size_t size) +{ + OSMO_ASSERT(size > IUUP_MSGB_HEADROOM_MIN_REQUIRED); + return msgb_alloc_headroom_c(ctx, size, IUUP_MSGB_HEADROOM_MIN_REQUIRED, "iuup-msgb"); +} + +/* push data so that the resulting pointer to write to is aligned to 8 byte */ +static inline __attribute__((assume_aligned(PTR_ALIGNMENT_BYTES))) +unsigned char *aligned_msgb_push(struct msgb *msg, unsigned int len) +{ + uint8_t *ptr = (msgb_data(msg) - len); + size_t extra_size = ((uintptr_t)ptr & (PTR_ALIGNMENT_BYTES - 1)); + + return msgb_push(msg, len + extra_size); +} + +struct osmo_iuup_rnl_prim *osmo_iuup_rnl_prim_alloc(void *ctx, unsigned int primitive, unsigned int operation, unsigned int size) +{ + struct msgb *msg; + struct osmo_iuup_rnl_prim *irp; + + msg = osmo_iuup_msgb_alloc_c(ctx, size); + irp = (struct osmo_iuup_rnl_prim *)aligned_msgb_push(msg, sizeof(*irp)); + osmo_prim_init(&irp->oph, SAP_IUUP_RNL, primitive, operation, msg); + return irp; +} + +struct osmo_iuup_tnl_prim *osmo_iuup_tnl_prim_alloc(void *ctx, unsigned int primitive, unsigned int operation, unsigned int size) +{ + struct msgb *msg; + struct osmo_iuup_tnl_prim *itp; + + msg = osmo_iuup_msgb_alloc_c(ctx, size); + itp = (struct osmo_iuup_tnl_prim *) aligned_msgb_push(msg, sizeof(*itp)); + osmo_prim_init(&itp->oph, SAP_IUUP_TNL, primitive, operation, msg); + return itp; +} + +/* 6.6.2.3.2 */ +static struct osmo_iuup_tnl_prim *itp_ctrl_ack_alloc(struct osmo_iuup_instance *iui, enum iuup_procedure proc_ind, uint8_t fn) +{ + struct osmo_iuup_tnl_prim *itp; + struct iuup_ctrl_ack *ack; + itp = osmo_iuup_tnl_prim_alloc(iui, OSMO_IUUP_TNL_UNITDATA, PRIM_OP_REQUEST, IUUP_MSGB_SIZE); + itp->oph.msg->l2h = msgb_put(itp->oph.msg, sizeof(struct iuup_ctrl_ack)); + ack = (struct iuup_ctrl_ack *) msgb_l2(itp->oph.msg); + *ack = (struct iuup_ctrl_ack){ + .hdr = { + .frame_nr = fn, + .ack_nack = IUUP_AN_ACK, + .pdu_type = IUUP_PDU_T_CONTROL, + .proc_ind = proc_ind, + .mode_version = iui->mode_version, + .payload_crc_hi = 0, + .header_crc = 0, + .payload_crc_lo = 0, + }, + }; + ack->hdr.header_crc = osmo_iuup_compute_header_crc(msgb_l2(itp->oph.msg), msgb_l2len(itp->oph.msg)); + return itp; +} + +/* 6.6.2.3.3 */ +static struct osmo_iuup_tnl_prim *tnp_ctrl_nack_alloc(struct osmo_iuup_instance *iui, enum iuup_procedure proc_ind, enum iuup_error_cause error_cause, uint8_t fn) +{ + struct osmo_iuup_tnl_prim *itp; + struct iuup_ctrl_nack *nack; + itp = osmo_iuup_tnl_prim_alloc(iui, OSMO_IUUP_TNL_UNITDATA, PRIM_OP_REQUEST, IUUP_MSGB_SIZE); + itp->oph.msg->l2h = msgb_put(itp->oph.msg, sizeof(struct iuup_ctrl_nack)); + nack = (struct iuup_ctrl_nack *) msgb_l2(itp->oph.msg); + *nack = (struct iuup_ctrl_nack){ + .hdr = { + .frame_nr = fn, + .ack_nack = IUUP_AN_NACK, + .pdu_type = IUUP_PDU_T_CONTROL, + .proc_ind = proc_ind, + .mode_version = iui->mode_version, + .payload_crc_hi = 0, + .header_crc = 0, + .payload_crc_lo = 0, + }, + .spare = 0, + .error_cause = error_cause, + }; + nack->hdr.header_crc = osmo_iuup_compute_header_crc(msgb_l2(itp->oph.msg), msgb_l2len(itp->oph.msg)); + return itp; +} + +/* 6.6.2.3.4.1 */ +static struct osmo_iuup_tnl_prim *tnp_ctrl_init_alloc(struct osmo_iuup_instance *iui) +{ + struct osmo_iuup_tnl_prim *itp; + struct iuup_pdutype14_hdr *hdr; + struct iuup_ctrl_init_hdr *ihdr; + struct iuup_ctrl_init_rfci_hdr *ihdr_rfci; + struct iuup_ctrl_init_tail *itail; + unsigned int i, j; + uint16_t payload_crc; + uint8_t rfci_cnt; + struct msgb *msg; + + itp = osmo_iuup_tnl_prim_alloc(iui, OSMO_IUUP_TNL_UNITDATA, PRIM_OP_REQUEST, IUUP_MSGB_SIZE); + msg = itp->oph.msg; + + msg->l2h = msgb_put(msg, sizeof(*hdr)); + hdr = (struct iuup_pdutype14_hdr *)msgb_l2(msg); + hdr->frame_nr = iui->type14_fn++; + hdr->ack_nack = IUUP_AN_PROCEDURE; + hdr->pdu_type = IUUP_PDU_T_CONTROL; + hdr->proc_ind = IUUP_PROC_INIT; + hdr->mode_version = 0; /* Use here the minimum version required to negotiate */ + hdr->header_crc = osmo_iuup_compute_header_crc(msgb_l2(msg), msgb_l2len(msg)); + + ihdr = (struct iuup_ctrl_init_hdr *)msgb_put(msg, sizeof(*ihdr)); + ihdr->chain_ind = 0; /* this frame is the last frame for the procedure. TODO: support several */ + ihdr->num_subflows_per_rfci = iui->config.num_subflows; + ihdr->ti = iui->config.IPTIs_present ? 1 : 0; + ihdr->spare = 0; + + /* RFCI + subflow size part: */ + rfci_cnt = 0; + for (i = 0; i < ARRAY_SIZE(iui->config.rfci); i++) { + bool last; + uint8_t len_size; + struct osmo_iuup_rfci *rfci = &iui->config.rfci[i]; + if (!rfci->used) + continue; + rfci_cnt++; + last = (rfci_cnt == iui->config.num_rfci); + + len_size = 1; + for (j = 0; j < iui->config.num_subflows; j++) { + if (rfci->subflow_sizes[j] > UINT8_MAX) + len_size = 2; + } + + ihdr_rfci = (struct iuup_ctrl_init_rfci_hdr *)msgb_put(msg, sizeof(*ihdr_rfci) + len_size * iui->config.num_subflows); + ihdr_rfci->rfci = rfci->id; + ihdr_rfci->li = len_size - 1; + ihdr_rfci->lri = last; + if (len_size == 2) { + uint16_t *buf = (uint16_t *)&ihdr_rfci->subflow_length[0]; + for (j = 0; j < iui->config.num_subflows; j++) + osmo_store16be(rfci->subflow_sizes[j], buf++); + } else { + for (j = 0; j < iui->config.num_subflows; j++) + ihdr_rfci->subflow_length[j] = rfci->subflow_sizes[j]; + } + /* early loop termination: */ + if (last) + break; + } + /* Sanity check: */ + if (rfci_cnt != iui->config.num_rfci) { + LOGP(DLIUUP, LOGL_ERROR, "rfci_cnt %u != num_rfci %u\n", + rfci_cnt, iui->config.num_rfci); + msgb_free(msg); + return NULL; + } + + if (iui->config.IPTIs_present) { + uint8_t num_bytes = (iui->config.num_rfci + 1) / 2; + uint8_t *buf = msgb_put(msg, num_bytes); + rfci_cnt = 0; + for (i = 0; i < ARRAY_SIZE(iui->config.rfci); i++) { + struct osmo_iuup_rfci *rfci = &iui->config.rfci[i]; + if (!rfci->used) + continue; + if (!(rfci_cnt & 0x01)) /* is even: */ + buf[rfci_cnt / 2] = (((uint8_t)rfci->IPTI) << 4); + else + buf[rfci_cnt / 2] |= (rfci->IPTI & 0x0F); + rfci_cnt++; + /* early loop termination: */ + if (rfci_cnt == iui->config.num_rfci) + break; + } + } + + itail = (struct iuup_ctrl_init_tail *)msgb_put(msg, sizeof(*itail)); + osmo_store16be(iui->config.supported_versions_mask, &itail->versions_supported); + itail->spare = 0; + itail->data_pdu_type = iui->config.data_pdu_type; + + payload_crc = osmo_iuup_compute_payload_crc(msgb_l2(msg), msgb_l2len(msg)); + hdr->payload_crc_hi = (payload_crc >> 8) & 0x03; + hdr->payload_crc_lo = payload_crc & 0xff; + + + return itp; +} + +static struct osmo_iuup_rnl_prim *irp_init_ind_alloc(struct osmo_iuup_instance *iui) +{ + struct osmo_iuup_rnl_prim *irp; + + irp = osmo_iuup_rnl_prim_alloc(iui, OSMO_IUUP_RNL_STATUS, PRIM_OP_INDICATION, IUUP_MSGB_SIZE); + irp->u.status.procedure = IUUP_PROC_INIT; + irp->u.status.u.initialization.mode_version = iui->mode_version; + irp->u.status.u.initialization.data_pdu_type = iui->config.data_pdu_type; + irp->u.status.u.initialization.num_subflows = iui->config.num_subflows; + irp->u.status.u.initialization.num_rfci = iui->config.num_rfci; + irp->u.status.u.initialization.IPTIs_present = iui->config.IPTIs_present; + memcpy(irp->u.status.u.initialization.rfci, iui->config.rfci, sizeof(iui->config.rfci)); + return irp; +} + +/* transform a RNL data primitive into a TNL data primitive (down the stack) */ +static struct osmo_iuup_tnl_prim *rnl_to_tnl_data(struct osmo_iuup_instance *iui, + struct osmo_iuup_rnl_prim *irp) +{ + struct osmo_iuup_tnl_prim *itp; + struct osmo_iuup_rnl_data dt; + struct msgb *msg; + uint16_t payload_crc; + struct iuup_pdutype0_hdr *h0; + struct iuup_pdutype1_hdr *h1; + + OSMO_ASSERT(OSMO_PRIM_HDR(&irp->oph) == OSMO_PRIM(OSMO_IUUP_RNL_DATA, PRIM_OP_REQUEST)); + + msg = irp->oph.msg; + dt = irp->u.data; + + /* pull up to the IuUP payload and push a new primitive header in front */ + msgb_pull_to_l3(msg); + + /* push the PDU TYPE 0 / 1 header in front of the payload */ + switch (iui->config.data_pdu_type) { + case 0: + msg->l2h = msgb_push(msg, sizeof(*h0)); + h0 = (struct iuup_pdutype0_hdr *)msg->l2h; + h0->frame_nr = dt.frame_nr; + h0->pdu_type = IUUP_PDU_T_DATA_CRC; + h0->rfci = dt.rfci; + h0->fqc = dt.fqc; + h0->header_crc = osmo_iuup_compute_header_crc(msgb_l2(msg), msgb_l2len(msg)); + payload_crc = osmo_iuup_compute_payload_crc(msgb_l2(msg), msgb_l2len(msg)); + h0->payload_crc_hi = (payload_crc >> 8) & 0x03; + h0->payload_crc_lo = payload_crc & 0xff; + break; + case 1: + msg->l2h = msgb_push(msg, sizeof(*h1)); + h1 = (struct iuup_pdutype1_hdr *)msg->l2h; + h1->frame_nr = dt.frame_nr; + h1->pdu_type = IUUP_PDU_T_DATA_NOCRC; + h1->rfci = dt.rfci; + h1->fqc = dt.fqc; + h1->header_crc = osmo_iuup_compute_header_crc(msgb_l2(msg), msgb_l2len(msg)); + h1->spare = 0; + break; + default: + OSMO_ASSERT(0); + } + + /* Avoid allocating irp out of 8byte-aligned address, Asan is not happy with it */ + itp = (struct osmo_iuup_tnl_prim *) aligned_msgb_push(msg, sizeof(*itp)); + osmo_prim_init(&itp->oph, SAP_IUUP_TNL, OSMO_IUUP_TNL_UNITDATA, PRIM_OP_REQUEST, msg); + + return itp; +} + +/* transform a TNL primitive into a RNL primitive (up the stack) */ +static struct osmo_iuup_rnl_prim *tnl_to_rnl_data(struct osmo_iuup_tnl_prim *itp) +{ + struct msgb *msg; + struct iuup_pdutype0_hdr *h0; + struct iuup_pdutype1_hdr *h1; + struct osmo_iuup_rnl_data dt; + struct osmo_iuup_rnl_prim *irp; + + msg = itp->oph.msg; + + OSMO_ASSERT(OSMO_PRIM_HDR(&itp->oph) == OSMO_PRIM(OSMO_IUUP_TNL_UNITDATA, PRIM_OP_INDICATION)); + + switch (iuup_get_pdu_type(msgb_l2(msg))) { + case IUUP_PDU_T_DATA_CRC: + h0 = (struct iuup_pdutype0_hdr *) msgb_l2(msg); + dt.rfci = h0->rfci; + dt.frame_nr = h0->frame_nr; + dt.fqc = h0->fqc; + break; + case IUUP_PDU_T_DATA_NOCRC: + h1 = (struct iuup_pdutype1_hdr *) msgb_l2(msg); + dt.rfci = h1->rfci; + dt.frame_nr = h1->frame_nr; + dt.fqc = h1->fqc; + break; + default: + OSMO_ASSERT(0); + } + + /* pull up to the IuUP payload and push a new primitive header in front */ + msgb_pull_to_l3(msg); + + /* Avoid allocating irp out of 8byte-aligned address, Asan is not happy with it */ + irp = (struct osmo_iuup_rnl_prim *) aligned_msgb_push(msg, sizeof(*irp)); + osmo_prim_init(&irp->oph, SAP_IUUP_RNL, OSMO_IUUP_RNL_DATA, PRIM_OP_INDICATION, msg); + irp->u.data = dt; + + return irp; +} + +static struct osmo_iuup_rnl_prim *irp_error_event_alloc_c(void *ctx, enum iuup_error_cause cause, enum iuup_error_distance distance) +{ + struct osmo_iuup_rnl_prim *irp; + struct msgb *msg; + msg = msgb_alloc_c(ctx, sizeof(*irp), "iuup-tx"); + irp = (struct osmo_iuup_rnl_prim *) msgb_put(msg, sizeof(*irp)); + osmo_prim_init(&irp->oph, SAP_IUUP_RNL, OSMO_IUUP_RNL_STATUS, PRIM_OP_INDICATION, msg); + irp->u.status.procedure = IUUP_PROC_ERR_EVENT; + irp->u.status.u.error_event.cause = cause; + irp->u.status.u.error_event.distance = distance; + return irp; +} + +static struct osmo_iuup_tnl_prim *itp_copy_c(void *ctx, const struct osmo_iuup_tnl_prim *src_itp) +{ + struct msgb *msg; + struct osmo_iuup_tnl_prim *dst_itp; + + msg = msgb_copy_c(ctx, src_itp->oph.msg, "iuup-tx-retrans"); + dst_itp = (struct osmo_iuup_tnl_prim *)msgb_data(msg); + dst_itp->oph.msg = msg; + return dst_itp; +} + +static void retransmit_initialization(struct osmo_iuup_instance *iui) +{ + struct osmo_iuup_tnl_prim *itp; + iui->fi->T = IUUP_TIMER_INIT; + osmo_timer_schedule(&iui->fi->timer, iui->config.t_init.t_ms / 1000, (iui->config.t_init.t_ms % 1000) * 1000); + itp = itp_copy_c(iui, iui->timer.init.retrans_itp); + iui->transport_prim_cb(&itp->oph, iui->transport_prim_priv); +} + +/* return: whether the last Init was Acked correctly and hence can transition to next state */ +static bool iuup_rx_initialization(struct osmo_iuup_instance *iui, struct osmo_iuup_tnl_prim *itp) +{ + struct iuup_pdutype14_hdr *hdr; + struct iuup_ctrl_init_hdr *ihdr; + struct iuup_ctrl_init_rfci_hdr *ihdr_rfci; + struct iuup_ctrl_init_tail *itail; + enum iuup_error_cause err_cause; + uint8_t num_rfci = 0; + int i; + bool is_last; + uint16_t remote_mask, match_mask; + struct osmo_iuup_rnl_prim *irp; + struct osmo_iuup_tnl_prim *resp; + + /* TODO: whenever we check message boundaries, length, etc. and we fail, send NACK */ + + hdr = (struct iuup_pdutype14_hdr *)msgb_l2(itp->oph.msg); + ihdr = (struct iuup_ctrl_init_hdr *)hdr->payload; + if (ihdr->num_subflows_per_rfci == 0) { + LOGPFSML(iui->fi, LOGL_NOTICE, "Initialization: Unexpected num_subflows=0 received\n"); + err_cause = IUUP_ERR_CAUSE_UNEXPECTED_VALUE; + goto send_nack; + } + ihdr_rfci = (struct iuup_ctrl_init_rfci_hdr *)ihdr->rfci_data; + + do { + struct osmo_iuup_rfci *rfci = &iui->config.rfci[num_rfci]; + uint8_t l_size_bytes = ihdr_rfci->li + 1; + is_last = ihdr_rfci->lri; + if (num_rfci >= IUUP_MAX_RFCIS) { + LOGPFSML(iui->fi, LOGL_NOTICE, "Initialization: Too many RFCIs received (%u)\n", + num_rfci); + err_cause = IUUP_ERR_CAUSE_UNEXPECTED_RFCI; + goto send_nack; + } + rfci->used = 1; + rfci->id = ihdr_rfci->rfci; + if (l_size_bytes == 2) { + uint16_t *subflow_size = (uint16_t *)ihdr_rfci->subflow_length; + for (i = 0; i < ihdr->num_subflows_per_rfci; i++) { + rfci->subflow_sizes[i] = osmo_load16be(subflow_size); + subflow_size++; + } + } else { + uint8_t *subflow_size = ihdr_rfci->subflow_length; + for (i = 0; i < ihdr->num_subflows_per_rfci; i++) { + rfci->subflow_sizes[i] = *subflow_size; + subflow_size++; + } + } + num_rfci++; + ihdr_rfci++; + ihdr_rfci = (struct iuup_ctrl_init_rfci_hdr *)(((uint8_t *)ihdr_rfci) + ihdr->num_subflows_per_rfci * l_size_bytes); + } while (!is_last); + + if (ihdr->ti) { /* Timing information present */ + uint8_t *buf = (uint8_t *)ihdr_rfci; + uint8_t num_bytes = (num_rfci + 1) / 2; + iui->config.IPTIs_present = true; + for (i = 0; i < num_bytes - 1; i++) { + iui->config.rfci[i*2].IPTI = *buf >> 4; + iui->config.rfci[i*2 + 1].IPTI = *buf & 0x0f; + buf++; + } + iui->config.rfci[i*2].IPTI = *buf >> 4; + if (!(num_rfci & 0x01)) /* is even: */ + iui->config.rfci[i*2 + 1].IPTI = *buf & 0x0f; + buf++; + itail = (struct iuup_ctrl_init_tail *)buf; + } else { + iui->config.IPTIs_present = false; + itail = (struct iuup_ctrl_init_tail *)ihdr_rfci; + } + if (itail->data_pdu_type > 1) { + LOGPFSML(iui->fi, LOGL_NOTICE, "Initialization: Unexpected Data PDU Type %u received\n", itail->data_pdu_type); + err_cause = IUUP_ERR_CAUSE_UNEXPECTED_VALUE; + goto send_nack; + } + + remote_mask = osmo_load16be(&itail->versions_supported); + match_mask = (remote_mask & iui->config.supported_versions_mask); + if (match_mask == 0x0000) { + LOGPFSML(iui->fi, LOGL_NOTICE, + "Initialization: No match in supported versions local=0x%04x vs remote=0x%04x\n", + iui->config.supported_versions_mask, remote_mask); + err_cause = IUUP_ERR_CAUSE_UNEXPECTED_VALUE; + goto send_nack; + } + for (i = 15; i >= 0; i--) { + if (match_mask & (1<<i)) { + iui->mode_version = i; + break; + } + } + + iui->config.num_rfci = num_rfci; + iui->config.num_subflows = ihdr->num_subflows_per_rfci; + iui->config.data_pdu_type = itail->data_pdu_type; + + irp = irp_init_ind_alloc(iui); + iui->user_prim_cb(&irp->oph, iui->user_prim_priv); + + LOGPFSML(iui->fi, LOGL_DEBUG, "Tx Initialization ACK\n"); + resp = itp_ctrl_ack_alloc(iui, IUUP_PROC_INIT, hdr->frame_nr); + iui->transport_prim_cb(&resp->oph, iui->transport_prim_priv); + return ihdr->chain_ind == 0; +send_nack: + LOGPFSML(iui->fi, LOGL_NOTICE, "Tx Initialization NACK cause=%u orig_message=%s\n", + err_cause, osmo_hexdump((const unsigned char *) msgb_l2(itp->oph.msg), msgb_l2len(itp->oph.msg))); + resp = tnp_ctrl_nack_alloc(iui, IUUP_PROC_INIT, err_cause, hdr->frame_nr); + iui->transport_prim_cb(&resp->oph, iui->transport_prim_priv); + return false; +} + +/********************** + * FSM STATE FUNCTIONS + **********************/ +static void iuup_fsm_null(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct osmo_iuup_instance *iui = fi->priv; + struct osmo_iuup_rnl_prim *user_prim = NULL; + + switch (event) { + case IUUP_FSM_EVT_IUUP_CONFIG_REQ: + user_prim = data; + iui->config = user_prim->u.config; + iui->config.supported_versions_mask &= 0x0003; /* We only support versions 1 and 2 ourselves */ + //TODO: if supported_versions_mask == 0x0000,no supported versions, send error to upper layers + + if (iui->config.transparent) + osmo_fsm_inst_state_chg(fi, IUUP_FSM_ST_TrM_DATA_XFER_READY, 0, 0); + else { + osmo_fsm_inst_state_chg(fi, IUUP_FSM_ST_INIT, 0, 0); + } + break; + } +} + +/* transparent mode data transfer */ +static void iuup_fsm_trm_data(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + //struct osmo_iuup_instance *iui = fi->priv; + + switch (event) { + case IUUP_FSM_EVT_IUUP_CONFIG_REQ: + osmo_fsm_inst_state_chg(fi, IUUP_FSM_ST_NULL, 0, 0); + break; + case IUUP_FSM_EVT_IUUP_DATA_REQ: + /* Data coming down from RNL (user) towards TNL (transport) */ + break; + case IUUP_FSM_EVT_IUUP_DATA_IND: + /* Data coming up from TNL (transport) towards RNL (user) */ + break; + case IUUP_FSM_EVT_IUUP_UNITDATA_REQ: + case IUUP_FSM_EVT_IUUP_UNITDATA_IND: + case IUUP_FSM_EVT_SSASAR_UNITDATA_REQ: + case IUUP_FSM_EVT_SSASAR_UNITDATA_IND: + /* no state change */ + break; + } +} + +static void iuup_fsm_init_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct osmo_iuup_instance *iui = fi->priv; + + iui->type14_fn = 0; + if (iui->config.active) { + iui->timer.init.n = 0; + iui->timer.init.retrans_itp = tnp_ctrl_init_alloc(iui); + retransmit_initialization(iui); + } +} + +static void iuup_fsm_init(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct osmo_iuup_instance *iui = fi->priv; + struct osmo_iuup_rnl_prim *irp; + struct osmo_iuup_tnl_prim *itp; + + switch (event) { + case IUUP_FSM_EVT_IUUP_CONFIG_REQ: + /* the only permitted 'config req' type is the request to release the instance */ + osmo_fsm_inst_state_chg(fi, IUUP_FSM_ST_NULL, 0, 0); + break; + case IUUP_FSM_EVT_INIT: + itp = data; + if (iuup_rx_initialization(iui, itp)) + osmo_fsm_inst_state_chg(fi, IUUP_FSM_ST_SMpSDU_DATA_XFER_READY, 0, 0); + break; + case IUUP_FSM_EVT_LAST_INIT_ACK: + /* last INIT ACK was received, transition to DATA_XFER_READY state */ + osmo_fsm_inst_state_chg(fi, IUUP_FSM_ST_SMpSDU_DATA_XFER_READY, 0, 0); + break; + case IUUP_FSM_EVT_INIT_NACK: + LOGPFSML(fi, LOGL_NOTICE, "Rx Initialization NACK N=%" PRIu32 "/%" PRIu32 "\n", + iui->timer.init.n, iui->config.t_init.n_max); + osmo_timer_del(&fi->timer); + if (iui->timer.init.n == iui->config.t_init.n_max) { + irp = irp_error_event_alloc_c(iui, IUUP_ERR_CAUSE_INIT_FAILURE_REP_NACK, IUUP_ERR_DIST_SECOND_FWD); + iui->user_prim_cb(&irp->oph, iui->user_prim_priv); + return; + } + iui->timer.init.n++; + retransmit_initialization(iui); + break; + default: + OSMO_ASSERT(false); + } +} + +/* 3GPP TS 25.415 B.2.3 "Support Mode Data Transfer Ready State" */ +static void iuup_fsm_smpsdu_data(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct osmo_iuup_instance *iui = fi->priv; + struct osmo_iuup_rnl_prim *irp = NULL; + struct osmo_iuup_tnl_prim *itp = NULL; + + switch (event) { + case IUUP_FSM_EVT_IUUP_CONFIG_REQ: + irp = data; + osmo_fsm_inst_state_chg(fi, IUUP_FSM_ST_NULL, 0, 0); + break; + case IUUP_FSM_EVT_INIT: + /* "In case of handover or relocation, Initialisation procedures + * may have to be performed and Iu UP instance may have to enter + * the initialisation state." */ + itp = data; + if (!iuup_rx_initialization(iui, itp)) + osmo_fsm_inst_state_chg(fi, IUUP_FSM_ST_INIT, 0, 0); + break; + case IUUP_FSM_EVT_IUUP_DATA_REQ: + /* Data coming down from RNL (user) towards TNL (transport) */ + irp = data; + itp = rnl_to_tnl_data(iui, irp); + iui->transport_prim_cb(&itp->oph, iui->transport_prim_priv); + break; + case IUUP_FSM_EVT_IUUP_DATA_IND: + /* Data coming up from TNL (transport) towards RNL (user) */ + itp = data; + irp = tnl_to_rnl_data(itp); + iui->user_prim_cb(&irp->oph, iui->user_prim_priv); + break; + } +} + +static int iuup_fsm_timer_cb(struct osmo_fsm_inst *fi) +{ + struct osmo_iuup_instance *iui = fi->priv; + struct osmo_iuup_rnl_prim *irp; + + switch (fi->T) { + case IUUP_TIMER_INIT: + OSMO_ASSERT(fi->state == IUUP_FSM_ST_INIT); + if (iui->timer.init.n == iui->config.t_init.n_max) { + irp = irp_error_event_alloc_c(iui, IUUP_ERR_CAUSE_INIT_FAILURE_NET_TMR, IUUP_ERR_DIST_LOCAL); + iui->user_prim_cb(&irp->oph, iui->user_prim_priv); + return 0; + } + iui->timer.init.n++; + retransmit_initialization(iui); + break; + case IUUP_TIMER_TA: + break; + case IUUP_TIMER_RC: + break; + default: + OSMO_ASSERT(0); + } + return 0; +} + + +static const struct osmo_fsm_state iuup_fsm_states[] = { + [IUUP_FSM_ST_NULL] = { + .in_event_mask = S(IUUP_FSM_EVT_IUUP_CONFIG_REQ), + .out_state_mask = S(IUUP_FSM_ST_INIT) | + S(IUUP_FSM_ST_TrM_DATA_XFER_READY), + .name = "NULL", + .action = iuup_fsm_null, + }, + [IUUP_FSM_ST_TrM_DATA_XFER_READY] = { + .in_event_mask = S(IUUP_FSM_EVT_IUUP_CONFIG_REQ) | + S(IUUP_FSM_EVT_IUUP_STATUS_REQ) | + S(IUUP_FSM_EVT_IUUP_DATA_REQ) | + S(IUUP_FSM_EVT_IUUP_DATA_IND) | + S(IUUP_FSM_EVT_IUUP_UNITDATA_REQ) | + S(IUUP_FSM_EVT_IUUP_UNITDATA_IND) | + S(IUUP_FSM_EVT_SSASAR_UNITDATA_REQ) | + S(IUUP_FSM_EVT_SSASAR_UNITDATA_IND), + .out_state_mask = S(IUUP_FSM_ST_NULL), + .name = "TrM_Data_Transfer_Ready", + .action = iuup_fsm_trm_data, + }, + [IUUP_FSM_ST_INIT] = { + .in_event_mask = S(IUUP_FSM_EVT_IUUP_CONFIG_REQ) | + S(IUUP_FSM_EVT_INIT) | + S(IUUP_FSM_EVT_LAST_INIT_ACK) | + S(IUUP_FSM_EVT_INIT_NACK), + .out_state_mask = S(IUUP_FSM_ST_NULL) | + S(IUUP_FSM_ST_SMpSDU_DATA_XFER_READY), + .name = "Initialisation", + .onenter = iuup_fsm_init_on_enter, + .action = iuup_fsm_init, + }, + [IUUP_FSM_ST_SMpSDU_DATA_XFER_READY] = { + .in_event_mask = S(IUUP_FSM_EVT_IUUP_CONFIG_REQ) | + S(IUUP_FSM_EVT_INIT) | + S(IUUP_FSM_EVT_IUUP_DATA_REQ) | + S(IUUP_FSM_EVT_IUUP_DATA_IND), + .out_state_mask = S(IUUP_FSM_ST_NULL) | + S(IUUP_FSM_ST_INIT), + .name = "SMpSDU_Data_Transfer_Ready", + .action = iuup_fsm_smpsdu_data, + }, +}; + +static struct osmo_fsm iuup_fsm = { + .name = "IuUP", + .states = iuup_fsm_states, + .num_states = ARRAY_SIZE(iuup_fsm_states), + .timer_cb = iuup_fsm_timer_cb, + .log_subsys = DLIUUP, + .event_names = iuup_fsm_event_names, +}; + +static int iuup_verify_pdu(const uint8_t *data, unsigned int len) +{ + int header_crc_computed, payload_crc_computed; + uint16_t payload_crc; + uint8_t pdu_type = iuup_get_pdu_type(data); + struct iuup_pdutype0_hdr *t0h; + struct iuup_pdutype14_hdr *t14h; + + if (len < 3) + return -EINVAL; + + header_crc_computed = osmo_iuup_compute_header_crc(data, len); + if (iuup_get_hdr_crc(data) != header_crc_computed) { + LOGP(DLIUUP, LOGL_NOTICE, "Header Checksum error: rx 0x%02x vs exp 0x%02x\n", + iuup_get_hdr_crc(data), header_crc_computed); + return -EIO; + } + switch (pdu_type) { + case IUUP_PDU_T_DATA_NOCRC: + if (len < 4) + return -EINVAL; + break; + case IUUP_PDU_T_DATA_CRC: + t0h = (struct iuup_pdutype0_hdr *) data; + payload_crc = ((uint16_t)t0h->payload_crc_hi << 8) | t0h->payload_crc_lo; + payload_crc_computed = osmo_iuup_compute_payload_crc(data, len); + if (payload_crc != payload_crc_computed) + goto payload_crc_err; + break; + case IUUP_PDU_T_CONTROL: + t14h = (struct iuup_pdutype14_hdr *) data; + if (t14h->ack_nack == IUUP_AN_PROCEDURE) { + payload_crc = ((uint16_t)t14h->payload_crc_hi << 8) | t14h->payload_crc_lo; + payload_crc_computed = osmo_iuup_compute_payload_crc(data, len); + if (payload_crc != payload_crc_computed) + goto payload_crc_err; + } + break; + default: + return -EINVAL; + } + return 0; + +payload_crc_err: + LOGP(DLIUUP, LOGL_NOTICE, "Payload Checksum error (pdu type %u): rx 0x%02x vs exp 0x%02x\n", + pdu_type, payload_crc, payload_crc_computed); + return -EIO; +} + +/* A IuUP TNL SAP primitive from transport (lower layer) */ +int osmo_iuup_tnl_prim_up(struct osmo_iuup_instance *inst, struct osmo_iuup_tnl_prim *itp) +{ + struct osmo_prim_hdr *oph = &itp->oph; + struct iuup_pdutype14_hdr *t14h; + int rc = 0; + + OSMO_ASSERT(oph->sap == SAP_IUUP_TNL); + + switch (OSMO_PRIM_HDR(oph)) { + case OSMO_PRIM(OSMO_IUUP_TNL_UNITDATA, PRIM_OP_INDICATION): + if (iuup_verify_pdu(msgb_l2(oph->msg), msgb_l2len(oph->msg)) < 0) { + LOGPFSML(inst->fi, LOGL_NOTICE, "Discarding invalid IuUP PDU: %s\n", + osmo_hexdump((const unsigned char *) msgb_l2(oph->msg), msgb_l2len(oph->msg))); + /* don't return error as the caller is not responsible for the PDU which + * was transmitted from some remote peer */ + return 0; + } + switch (iuup_get_pdu_type(msgb_l2(oph->msg))) { + case IUUP_PDU_T_DATA_CRC: + oph->msg->l3h = msgb_l2(oph->msg) + sizeof(struct iuup_pdutype0_hdr); + rc = osmo_fsm_inst_dispatch(inst->fi, IUUP_FSM_EVT_IUUP_DATA_IND, itp); + break; + case IUUP_PDU_T_DATA_NOCRC: + oph->msg->l3h = msgb_l2(oph->msg) + sizeof(struct iuup_pdutype1_hdr); + rc = osmo_fsm_inst_dispatch(inst->fi, IUUP_FSM_EVT_IUUP_DATA_IND, itp); + break; + case IUUP_PDU_T_CONTROL: + t14h = (struct iuup_pdutype14_hdr *) msgb_l2(oph->msg); + switch (t14h->ack_nack) { + case IUUP_AN_PROCEDURE: + switch (t14h->proc_ind) { + case IUUP_PROC_INIT: + rc = osmo_fsm_inst_dispatch(inst->fi, IUUP_FSM_EVT_INIT, itp); + break; + case IUUP_PROC_RATE_CTRL: + case IUUP_PROC_TIME_ALIGN: + case IUUP_PROC_ERR_EVENT: + LOGPFSML(inst->fi, LOGL_NOTICE, "Received Request for " + "unsupported IuUP procedure %u\n", t14h->proc_ind); + break; + default: + LOGPFSML(inst->fi, LOGL_NOTICE, "Received Request for " + "unknown IuUP procedure %u\n", t14h->proc_ind); + break; + } + break; + case IUUP_AN_ACK: + switch (t14h->proc_ind) { + case IUUP_PROC_INIT: + rc = osmo_fsm_inst_dispatch(inst->fi, + IUUP_FSM_EVT_LAST_INIT_ACK, itp); + break; + default: + LOGPFSML(inst->fi, LOGL_ERROR, "Received ACK for " + "unknown IuUP procedure %u\n", t14h->proc_ind); + break; + } + break; + case IUUP_AN_NACK: + switch (t14h->proc_ind) { + case IUUP_PROC_INIT: + rc = osmo_fsm_inst_dispatch(inst->fi, + IUUP_FSM_EVT_INIT_NACK, itp); + break; + default: + LOGPFSML(inst->fi, LOGL_ERROR, "Received NACK for " + "unknown IuUP procedure %u\n", t14h->proc_ind); + break; + } + break; + default: + LOGPFSML(inst->fi, LOGL_ERROR, "Received unknown IuUP ACK/NACK\n"); + break; + } + break; + default: + LOGPFSML(inst->fi, LOGL_NOTICE, "Received unknown IuUP PDU type %u\n", + iuup_get_pdu_type(msgb_l2(oph->msg))); + break; + } + break; + default: + /* exception: return an error code due to a wrong primitive */ + return -EINVAL; + } + + return rc; +} + +/* A IuUP RNL SAP primitive from user (higher layer) */ +int osmo_iuup_rnl_prim_down(struct osmo_iuup_instance *inst, struct osmo_iuup_rnl_prim *irp) +{ + struct osmo_prim_hdr *oph = &irp->oph; + int rc; + + OSMO_ASSERT(oph->sap == SAP_IUUP_RNL); + + switch (OSMO_PRIM_HDR(oph)) { + case OSMO_PRIM(OSMO_IUUP_RNL_CONFIG, PRIM_OP_REQUEST): + rc = osmo_fsm_inst_dispatch(inst->fi, IUUP_FSM_EVT_IUUP_CONFIG_REQ, irp); + msgb_free(irp->oph.msg); + break; + case OSMO_PRIM(OSMO_IUUP_RNL_DATA, PRIM_OP_REQUEST): + rc = osmo_fsm_inst_dispatch(inst->fi, IUUP_FSM_EVT_IUUP_DATA_REQ, irp); + if (rc != 0) + msgb_free(irp->oph.msg); + break; + case OSMO_PRIM(OSMO_IUUP_RNL_STATUS, PRIM_OP_REQUEST): + rc = osmo_fsm_inst_dispatch(inst->fi, IUUP_FSM_EVT_IUUP_STATUS_REQ, irp); + msgb_free(irp->oph.msg); + break; + default: + rc = -EINVAL; + msgb_free(irp->oph.msg); + } + return rc; +} + +struct osmo_iuup_instance *osmo_iuup_instance_alloc(void *ctx, const char *id) +{ + struct osmo_iuup_instance *iui; + iui = talloc_zero(ctx, struct osmo_iuup_instance); + if (!iui) + return NULL; + + iui->fi = osmo_fsm_inst_alloc(&iuup_fsm, NULL, iui, LOGL_DEBUG, id); + if (!iui->fi) + goto free_ret; + + return iui; +free_ret: + talloc_free(iui); + return NULL; +} + +void osmo_iuup_instance_free(struct osmo_iuup_instance *iui) +{ + if (!iui) + return; + + if (iui->fi) + osmo_fsm_inst_free(iui->fi); + iui->fi = NULL; + talloc_free(iui); +} + +void osmo_iuup_instance_set_user_prim_cb(struct osmo_iuup_instance *iui, osmo_prim_cb func, void *priv) +{ + iui->user_prim_cb = func; + iui->user_prim_priv = priv; +} +void osmo_iuup_instance_set_transport_prim_cb(struct osmo_iuup_instance *iui, osmo_prim_cb func, void *priv) +{ + iui->transport_prim_cb = func; + iui->transport_prim_priv = priv; +} + +static __attribute__((constructor)) void on_dso_load_iuup_fsm(void) +{ + OSMO_ASSERT(osmo_fsm_register(&iuup_fsm) == 0); +} diff --git a/src/gsm/kasumi.c b/src/gsm/kasumi.c index f93c002b..78885bdd 100644 --- a/src/gsm/kasumi.c +++ b/src/gsm/kasumi.c @@ -16,10 +16,6 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ #include <stdint.h> diff --git a/src/gsm/kdf.c b/src/gsm/kdf.c index 2ebe41a9..4113aada 100644 --- a/src/gsm/kdf.c +++ b/src/gsm/kdf.c @@ -17,16 +17,12 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ #include <stdint.h> #include <string.h> -#include "../../config.h" +#include "config.h" #if (USE_GNUTLS) #include <gnutls/gnutls.h> #include <gnutls/crypto.h> diff --git a/src/gsm/lapdm.c b/src/gsm/lapdm.c index 8620cabe..1fc986d6 100644 --- a/src/gsm/lapdm.c +++ b/src/gsm/lapdm.c @@ -19,10 +19,6 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ /*! \addtogroup lapdm @@ -31,6 +27,7 @@ #include <stdio.h> #include <stdint.h> +#include <inttypes.h> #include <string.h> #include <errno.h> @@ -61,6 +58,7 @@ #define LAPDm_ADDR_SAPI(addr) (((addr) >> 2) & 0x7) #define LAPDm_ADDR_CR(addr) (((addr) >> 1) & 0x1) #define LAPDm_ADDR_EA(addr) ((addr) & 0x1) +#define LAPDm_ADDR_SHORT_L2(addr) ((addr) & 0x3) /* TS 04.06 Table 3 / Section 3.4.3 */ #define LAPDm_CTRL_I(nr, ns, p) ((((nr) & 0x7) << 5) | (((p) & 0x1) << 4) | (((ns) & 0x7) << 1)) @@ -199,9 +197,10 @@ void lapdm_entity_init3(struct lapdm_entity *le, enum lapdm_mode mode, char name[256]; if (name_pfx) { snprintf(name, sizeof(name), "%s[%s]", name_pfx, i == 0 ? "0" : "3"); - lapdm_dl_init(&le->datalink[i], le, t200_ms[i], n200, name); + lapdm_dl_init(&le->datalink[i], le, (t200_ms) ? t200_ms[i] : 0, n200, name); } else - lapdm_dl_init(&le->datalink[i], le, t200_ms[i], n200, NULL); + lapdm_dl_init(&le->datalink[i], le, (t200_ms) ? t200_ms[i] : 0, n200, NULL); + INIT_LLIST_HEAD(&le->datalink[i].tx_ui_queue); } lapdm_entity_set_mode(le, mode); @@ -257,7 +256,7 @@ int lapdm_channel_init2(struct lapdm_channel *lc, enum lapdm_mode mode, * \param[in] t200_ms_dcch per-SAPI array of T200 in milli-seconds for DCCH * \param[in] t200_ms_acch per-SAPI array of T200 in milli-seconds for SACCH * \param[in] chan_t GSM channel type (to correctly set N200) - * \parma[in] name_pfx human-readable name (copied by function + extended with ACCH/DCCH) + * \param[in] name_pfx human-readable name (copied by function + extended with ACCH/DCCH) */ int lapdm_channel_init3(struct lapdm_channel *lc, enum lapdm_mode mode, const int *t200_ms_dcch, const int *t200_ms_acch, enum gsm_chan_t chan_t, @@ -298,6 +297,7 @@ void lapdm_entity_exit(struct lapdm_entity *le) for (i = 0; i < ARRAY_SIZE(le->datalink); i++) { dl = &le->datalink[i]; lapd_dl_exit(&dl->dl); + msgb_queue_free(&dl->tx_ui_queue); } } @@ -361,11 +361,50 @@ static int tx_ph_data_enqueue(struct lapdm_datalink *dl, struct msgb *msg, /* if there is a pending message, queue it */ if (le->tx_pending || le->flags & LAPDM_ENT_F_POLLING_ONLY) { + struct msgb *old_msg; + + /* In 'RTS' mode there can be only one message. */ + if (le->flags & LAPDM_ENT_F_RTS) { + /* Overwrite existing message by removing it first. */ + if ((old_msg = msgb_dequeue(&dl->dl.tx_queue))) { + msgb_free(old_msg); + /* Reset V(S) to V(A), because there is no outstanding message now. */ + dl->dl.v_send = dl->dl.v_ack; + } + } + *msgb_push(msg, 1) = pad; *msgb_push(msg, 1) = link_id; *msgb_push(msg, 1) = chan_nr; msgb_enqueue(&dl->dl.tx_queue, msg); - return -EBUSY; + return 0; + } + + osmo_prim_init(&pp.oph, SAP_GSM_PH, PRIM_PH_DATA, + PRIM_OP_REQUEST, msg); + pp.u.data.chan_nr = chan_nr; + pp.u.data.link_id = link_id; + + /* send the frame now */ + le->tx_pending = 0; /* disabled flow control */ + lapdm_pad_msgb(msg, pad); + + return le->l1_prim_cb(&pp.oph, le->l1_ctx); +} + +static int tx_ph_data_enqueue_ui(struct lapdm_datalink *dl, struct msgb *msg, + uint8_t chan_nr, uint8_t link_id, uint8_t pad) +{ + struct lapdm_entity *le = dl->entity; + struct osmo_phsap_prim pp; + + /* if there is a pending message, queue it */ + if (le->tx_pending || le->flags & LAPDM_ENT_F_POLLING_ONLY) { + *msgb_push(msg, 1) = pad; + *msgb_push(msg, 1) = link_id; + *msgb_push(msg, 1) = chan_nr; + msgb_enqueue(&dl->tx_ui_queue, msg); + return 0; } osmo_prim_init(&pp.oph, SAP_GSM_PH, PRIM_PH_DATA, @@ -380,21 +419,60 @@ static int tx_ph_data_enqueue(struct lapdm_datalink *dl, struct msgb *msg, return le->l1_prim_cb(&pp.oph, le->l1_ctx); } +/* Get transmit frame from queue, if any. In polling mode, indicate RTS to LAPD and start T200, if pending. */ +static struct msgb *tx_dequeue_msgb(struct lapdm_datalink *dl, uint32_t fn) +{ + struct msgb *msg; + + /* Call RTS function of LAPD, to queue next frame. */ + if (dl->entity->flags & LAPDM_ENT_F_RTS) { + struct lapd_msg_ctx lctx; + int rc; + + /* Poll next frame. */ + lctx.dl = &dl->dl; + rc = lapd_ph_rts_ind(&lctx); + + /* If T200 has been started, calculate timeout FN. */ + if (rc == 1) { + /* Set T200 in advance. */ + dl->t200_timeout = fn; + ADD_MODULO(dl->t200_timeout, dl->t200_fn, GSM_MAX_FN); + + LOGDL(&dl->dl, LOGL_INFO, + "T200 running from FN %"PRIu32" to FN %"PRIu32" (%"PRIu32" frames).\n", + fn, dl->t200_timeout, dl->t200_fn); + } + } + + /* If there is no frame from LAPD, send UI frame, if any. */ + msg = msgb_dequeue(&dl->dl.tx_queue); + if (msg) + LOGDL(&dl->dl, LOGL_INFO, "Sending frame from TX queue. (FN %"PRIu32")\n", fn); + else { + msg = msgb_dequeue(&dl->tx_ui_queue); + if (msg) + LOGDL(&dl->dl, LOGL_INFO, "Sending UI frame from TX queue. (FN %"PRIu32")\n", fn); + } + return msg; +} + /* Dequeue a Downlink message for DCCH (dedicated channel) */ -static struct msgb *tx_dequeue_dcch_msgb(struct lapdm_entity *le) +static struct msgb *tx_dequeue_dcch_msgb(struct lapdm_entity *le, uint32_t fn) { struct msgb *msg; /* SAPI=0 always has higher priority than SAPI=3 */ - msg = msgb_dequeue(&le->datalink[DL_SAPI0].dl.tx_queue); - if (msg == NULL) /* no SAPI=0 messages, dequeue SAPI=3 (if any) */ - msg = msgb_dequeue(&le->datalink[DL_SAPI3].dl.tx_queue); + msg = tx_dequeue_msgb(&le->datalink[DL_SAPI0], fn); + if (msg == NULL) { /* no SAPI=0 messages, dequeue SAPI=3 (if any) */ + msg = tx_dequeue_msgb(&le->datalink[DL_SAPI3], fn); + } return msg; } /* Dequeue a Downlink message for ACCH (associated channel) */ -static struct msgb *tx_dequeue_acch_msgb(struct lapdm_entity *le) +static struct msgb *tx_dequeue_acch_msgb(struct lapdm_entity *le, uint32_t fn) { struct lapdm_datalink *dl; int last = le->last_tx_dequeue; @@ -406,7 +484,7 @@ static struct msgb *tx_dequeue_acch_msgb(struct lapdm_entity *le) /* next */ i = (i + 1) % n; dl = &le->datalink[i]; - if ((msg = msgb_dequeue(&dl->dl.tx_queue))) + if ((msg = tx_dequeue_msgb(dl, fn))) break; } while (i != last); @@ -420,7 +498,7 @@ static struct msgb *tx_dequeue_acch_msgb(struct lapdm_entity *le) /*! dequeue a msg that's pending transmission via L1 and wrap it into * a osmo_phsap_prim */ -int lapdm_phsap_dequeue_prim(struct lapdm_entity *le, struct osmo_phsap_prim *pp) +int lapdm_phsap_dequeue_prim_fn(struct lapdm_entity *le, struct osmo_phsap_prim *pp, uint32_t fn) { struct msgb *msg; uint8_t pad; @@ -428,9 +506,9 @@ int lapdm_phsap_dequeue_prim(struct lapdm_entity *le, struct osmo_phsap_prim *pp /* Dequeue depending on channel type: DCCH or ACCH. * See 3GPP TS 44.005, section 4.2.2 "Priority". */ if (le == &le->lapdm_ch->lapdm_dcch) - msg = tx_dequeue_dcch_msgb(le); + msg = tx_dequeue_dcch_msgb(le, fn); else - msg = tx_dequeue_acch_msgb(le); + msg = tx_dequeue_acch_msgb(le, fn); if (!msg) return -ENODEV; @@ -452,6 +530,57 @@ int lapdm_phsap_dequeue_prim(struct lapdm_entity *le, struct osmo_phsap_prim *pp return 0; } +static void lapdm_t200_fn_dl(struct lapdm_datalink *dl, uint32_t fn) +{ + uint32_t diff; + + OSMO_ASSERT((dl->dl.lapd_flags & LAPD_F_RTS)); + + /* If T200 is running, check if it has fired. */ + if (dl->dl.t200_rts != LAPD_T200_RTS_RUNNING) + return; + + /* Calculate how many frames fn is behind t200_timeout. + * If it is negative (>= GSM_MAX_FN / 2), we have not reached t200_timeout yet. + * If it is 0 or positive, we reached it or we are a bit too late, which is not a problem. + */ + diff = fn; + ADD_MODULO(diff, GSM_MAX_FN - dl->t200_timeout, GSM_MAX_FN); + if (diff >= GSM_MAX_FN / 2) + return; + + LOGDL(&dl->dl, LOGL_INFO, "T200 timeout at FN %"PRIu32", detected at FN %"PRIu32".\n", dl->t200_timeout, fn); + + lapd_t200_timeout(&dl->dl); +} + +/*! Get receive frame number from L1. It is used to check the T200 timeout. + * This function is used if LAPD is in RTS mode only. (Applies if the LAPDM_ENT_F_POLLING_ONLY flag is set.) + * This function must be called for every valid or invalid data frame received. + * The frame number fn must be the frame number of the first burst of a data frame. + * This function must be called after the frame is delivered to layer 2. + * In case of TCH, this this function must be called for every speech frame received, meaning that there was no valid + * data frame. */ +void lapdm_t200_fn(struct lapdm_entity *le, uint32_t fn) +{ + unsigned int i; + + if (!(le->flags & LAPDM_ENT_F_POLLING_ONLY)) { + LOGP(DLLAPD, LOGL_ERROR, "Function call not allowed on timer based T200.\n"); + return; + } + + for (i = 0; i < ARRAY_SIZE(le->datalink); i++) + lapdm_t200_fn_dl(&le->datalink[i], fn); +} + +/*! dequeue a msg that's pending transmission via L1 and wrap it into + * a osmo_phsap_prim */ +int lapdm_phsap_dequeue_prim(struct lapdm_entity *le, struct osmo_phsap_prim *pp) +{ + return lapdm_phsap_dequeue_prim_fn(le, pp, 0); +} + /* get next frame from the tx queue. because the ms has multiple datalinks, * each datalink's queue is read round-robin. */ @@ -661,7 +790,8 @@ static int update_pending_frames(struct lapd_msg_ctx *lctx) LAPDm_CTRL_PF_BIT(msg->l2h[1])); rc = 0; } else if (LAPDm_CTRL_is_S(msg->l2h[1])) { - LOGDL(dl, LOGL_ERROR, "Supervisory frame in queue, this shouldn't happen\n"); + msg->l2h[1] = LAPDm_CTRL_S(dl->v_recv, LAPDm_CTRL_S_BITS(msg->l2h[1]), + LAPDm_CTRL_PF_BIT(msg->l2h[1])); } } @@ -703,7 +833,7 @@ static int lapdm_rx_not_permitted(const struct lapdm_entity *le, /* input into layer2 (from layer 1) */ static int l2_ph_data_ind(struct msgb *msg, struct lapdm_entity *le, - uint8_t chan_nr, uint8_t link_id) + uint8_t chan_nr, uint8_t link_id, uint32_t fn) { uint8_t cbits = chan_nr >> 3; uint8_t sapi; /* we cannot take SAPI from link_id, as L1 has no clue */ @@ -718,6 +848,7 @@ static int l2_ph_data_ind(struct msgb *msg, struct lapdm_entity *le, memset(&mctx, 0, sizeof(mctx)); mctx.chan_nr = chan_nr; mctx.link_id = link_id; + mctx.fn = fn; /* check for L1 chan_nr/link_id and determine LAPDm hdr format */ if (cbits == 0x10 || cbits == 0x12) { @@ -729,15 +860,24 @@ static int l2_ph_data_ind(struct msgb *msg, struct lapdm_entity *le, if (mctx.link_id & 0x40) { /* It was received from network on SACCH */ - /* If UI on SACCH sent by BTS, lapdm_fmt must be B4 */ - if (le->mode == LAPDM_MODE_MS - && LAPDm_CTRL_is_U(msg->l2h[3]) - && LAPDm_CTRL_U_BITS(msg->l2h[3]) == 0) { + /* A Short L3 header has both bits == 0. */ + if (LAPDm_ADDR_SHORT_L2(msg->l2h[2]) == 0) { + mctx.lapdm_fmt = LAPDm_FMT_Bter; + n201 = N201_Bter_SACCH; + sapi = 0; + } else if (le->mode == LAPDM_MODE_MS + && LAPDm_CTRL_is_U(msg->l2h[3]) + && LAPDm_CTRL_U_BITS(msg->l2h[3]) == 0) { + /* If UI on SACCH sent by BTS, lapdm_fmt must be B4 */ mctx.lapdm_fmt = LAPDm_FMT_B4; n201 = N201_B4; + /* sapi is found after two-btyte L1 header */ + sapi = (msg->l2h[2] >> 2) & 7; } else { mctx.lapdm_fmt = LAPDm_FMT_B; n201 = N201_AB_SACCH; + /* sapi is found after two-btyte L1 header */ + sapi = (msg->l2h[2] >> 2) & 7; } /* SACCH frames have a two-byte L1 header that * OsmocomBB L1 doesn't strip */ @@ -745,11 +885,17 @@ static int l2_ph_data_ind(struct msgb *msg, struct lapdm_entity *le, mctx.ta_ind = msg->l2h[1]; msgb_pull(msg, 2); msg->l2h += 2; - sapi = (msg->l2h[0] >> 2) & 7; } else { - mctx.lapdm_fmt = LAPDm_FMT_B; - n201 = N201_AB_SDCCH; - sapi = (msg->l2h[0] >> 2) & 7; + /* A Short L3 header has both bits == 0. */ + if (LAPDm_ADDR_SHORT_L2(msg->l2h[0]) == 0) { + mctx.lapdm_fmt = LAPDm_FMT_Bter; + n201 = N201_Bter_SDCCH; + sapi = 0; + } else { + mctx.lapdm_fmt = LAPDm_FMT_B; + n201 = N201_AB_SDCCH; + sapi = (msg->l2h[0] >> 2) & 7; + } } } @@ -839,13 +985,14 @@ static int l2_ph_data_ind(struct msgb *msg, struct lapdm_entity *le, return -EINVAL; } /* send to LAPD */ + LOGDL(lctx.dl, LOGL_DEBUG, "Frame received at FN %"PRIu32".\n", fn); rc = lapd_ph_data_ind(msg, &lctx); break; case LAPDm_FMT_Bter: - /* FIXME */ - msgb_free(msg); - break; + /* fall-through */ case LAPDm_FMT_Bbis: + /* Update context so that users can read fields like fn: */ + memcpy(&mctx.dl->mctx, &mctx, sizeof(mctx.dl->mctx)); /* directly pass up to layer3 */ msg->l3h = msg->l2h; msgb_pull_to_l3(msg); @@ -899,56 +1046,33 @@ int lapdm_phsap_up(struct osmo_prim_hdr *oph, struct lapdm_entity *le) if (oph->sap != SAP_GSM_PH) { LOGP(DLLAPD, LOGL_ERROR, "primitive for unknown SAP %u\n", oph->sap); - rc = -ENODEV; - goto free; + msgb_free(oph->msg); + return -ENODEV; } - switch (oph->primitive) { - case PRIM_PH_DATA: - if (oph->operation != PRIM_OP_INDICATION) { - LOGP(DLLAPD, LOGL_ERROR, "PH_DATA is not INDICATION %u\n", - oph->operation); - rc = -ENODEV; - goto free; - } + switch (OSMO_PRIM_HDR(oph)) { + case OSMO_PRIM(PRIM_PH_DATA, PRIM_OP_INDICATION): rc = l2_ph_data_ind(oph->msg, le, pp->u.data.chan_nr, - pp->u.data.link_id); + pp->u.data.link_id, pp->u.data.fn); break; - case PRIM_PH_RTS: - if (oph->operation != PRIM_OP_INDICATION) { - LOGP(DLLAPD, LOGL_ERROR, "PH_RTS is not INDICATION %u\n", - oph->operation); - rc = -ENODEV; - goto free; - } + case OSMO_PRIM(PRIM_PH_RTS, PRIM_OP_INDICATION): rc = l2_ph_data_conf(oph->msg, le); break; - case PRIM_PH_RACH: - switch (oph->operation) { - case PRIM_OP_INDICATION: - rc = l2_ph_rach_ind(le, pp->u.rach_ind.ra, pp->u.rach_ind.fn, - pp->u.rach_ind.acc_delay); - break; - case PRIM_OP_CONFIRM: - rc = l2_ph_chan_conf(oph->msg, le, pp->u.rach_ind.fn); - break; - default: - rc = -EIO; - goto free; - } + case OSMO_PRIM(PRIM_PH_RACH, PRIM_OP_INDICATION): + rc = l2_ph_rach_ind(le, pp->u.rach_ind.ra, pp->u.rach_ind.fn, + pp->u.rach_ind.acc_delay); + break; + case OSMO_PRIM(PRIM_PH_RACH, PRIM_OP_CONFIRM): + rc = l2_ph_chan_conf(oph->msg, le, pp->u.rach_ind.fn); break; default: LOGP(DLLAPD, LOGL_ERROR, "Unknown primitive %u\n", oph->primitive); - rc = -EINVAL; - goto free; + msgb_free(oph->msg); + return -EINVAL; } return rc; - -free: - msgb_free(oph->msg); - return rc; } @@ -1034,6 +1158,7 @@ static int rslms_rx_rll_udata_req(struct msgb *msg, struct lapdm_datalink *dl) uint8_t sapi = link_id & 7; struct tlv_parsed tv; int length, ui_bts; + bool use_b_ter; if (!le) { LOGDL(&dl->dl, LOGL_ERROR, "lapdm_datalink without entity error\n"); @@ -1059,8 +1184,10 @@ static int rslms_rx_rll_udata_req(struct msgb *msg, struct lapdm_datalink *dl) } msg->l3h = (uint8_t *) TLVP_VAL(&tv, RSL_IE_L3_INFO); length = TLVP_LEN(&tv, RSL_IE_L3_INFO); + /* check for Bter frame */ + use_b_ter = (length == ((link_id & 0x40) ? 21 : 23) && sapi == 0); /* check if the layer3 message length exceeds N201 */ - if (length + ((link_id & 0x40) ? 4 : 2) + !ui_bts > 23) { + if (length + ((link_id & 0x40) ? 4 : 2) + !ui_bts > 23 && !use_b_ter) { LOGDL(&dl->dl, LOGL_ERROR, "frame too large: %d > N201(%d) " "(discarding)\n", length, ((link_id & 0x40) ? 18 : 20) + ui_bts); @@ -1075,11 +1202,14 @@ static int rslms_rx_rll_udata_req(struct msgb *msg, struct lapdm_datalink *dl) msgb_trim(msg, length); /* Push L1 + LAPDm header on msgb */ - msg->l2h = msgb_push(msg, 2 + !ui_bts); - msg->l2h[0] = LAPDm_ADDR(LAPDm_LPD_NORMAL, sapi, dl->dl.cr.loc2rem.cmd); - msg->l2h[1] = LAPDm_CTRL_U(LAPDm_U_UI, 0); - if (!ui_bts) - msg->l2h[2] = LAPDm_LEN(length); + if (!use_b_ter) { + msg->l2h = msgb_push(msg, 2 + !ui_bts); + msg->l2h[0] = LAPDm_ADDR(LAPDm_LPD_NORMAL, sapi, dl->dl.cr.loc2rem.cmd); + msg->l2h[1] = LAPDm_CTRL_U(LAPDm_U_UI, 0); + if (!ui_bts) + msg->l2h[2] = LAPDm_LEN(length); + } else + msg->l2h = msg->data; if (link_id & 0x40) { msg->l2h = msgb_push(msg, 2); msg->l2h[0] = le->tx_power; @@ -1087,7 +1217,7 @@ static int rslms_rx_rll_udata_req(struct msgb *msg, struct lapdm_datalink *dl) } /* Tramsmit */ - return tx_ph_data_enqueue(dl, msg, chan_nr, link_id, 23); + return tx_ph_data_enqueue_ui(dl, msg, chan_nr, link_id, 23); } /* L3 requests transfer of acknowledged information */ @@ -1484,6 +1614,7 @@ void lapdm_entity_reset(struct lapdm_entity *le) for (i = 0; i < ARRAY_SIZE(le->datalink); i++) { dl = &le->datalink[i]; lapd_dl_reset(&dl->dl); + msgb_queue_free(&dl->tx_ui_queue); } } @@ -1497,7 +1628,22 @@ void lapdm_channel_reset(struct lapdm_channel *lc) /*! Set the flags of a LAPDm entity */ void lapdm_entity_set_flags(struct lapdm_entity *le, unsigned int flags) { + unsigned int dl_flags = 0; + struct lapdm_datalink *dl; + int i; + le->flags = flags; + + /* Set flags at LAPD. */ + if (le->flags & LAPDM_ENT_F_RTS) + dl_flags |= LAPD_F_RTS; + if (le->flags & LAPDM_ENT_F_DROP_2ND_REJ) + dl_flags |= LAPD_F_DROP_2ND_REJ; + + for (i = 0; i < ARRAY_SIZE(le->datalink); i++) { + dl = &le->datalink[i]; + lapd_dl_set_flags(&dl->dl, dl_flags); + } } /*! Set the flags of all LAPDm entities in a LAPDm channel */ @@ -1507,4 +1653,25 @@ void lapdm_channel_set_flags(struct lapdm_channel *lc, unsigned int flags) lapdm_entity_set_flags(&lc->lapdm_acch, flags); } +/*! Set the T200 FN timer of a LAPDm entity + * \param[in] \ref lapdm_entity + * \param[in] t200_fn Array of T200 timeout in frame numbers for all SAPIs (0, 3) */ +void lapdm_entity_set_t200_fn(struct lapdm_entity *le, const uint32_t *t200_fn) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(le->datalink); i++) + le->datalink[i].t200_fn = t200_fn[i]; +} + +/*! Set the T200 FN timer of all LAPDm entities in a LAPDm channel + * \param[in] \ref lapdm_channel + * \param[in] t200_fn_dcch Array of T200 timeout in frame numbers for all SAPIs (0, 3) on SDCCH/FACCH + * \param[in] t200_fn_acch Array of T200 timeout in frame numbers for all SAPIs (0, 3) on SACCH */ +void lapdm_channel_set_t200_fn(struct lapdm_channel *lc, const uint32_t *t200_fn_dcch, const uint32_t *t200_fn_acch) +{ + lapdm_entity_set_t200_fn(&lc->lapdm_dcch, t200_fn_dcch); + lapdm_entity_set_t200_fn(&lc->lapdm_acch, t200_fn_acch); +} + /*! @} */ diff --git a/src/gsm/libosmogsm.map b/src/gsm/libosmogsm.map index 475ec02a..2c4c621c 100644 --- a/src/gsm/libosmogsm.map +++ b/src/gsm/libosmogsm.map @@ -40,6 +40,14 @@ abis_nm_put_sw_file; abis_nm_get_sw_conf; abis_nm_get_sw_desc_len; +abis_nm_ipacc_freq_band_desc; +abis_nm_ipacc_ciph_algo_desc; +abis_nm_ipacc_chant_desc; +abis_nm_ipacc_chanm_desc; +abis_nm_ipacc_gprs_coding_desc; +abis_nm_ipacc_rtp_feat_desc; +abis_nm_ipacc_rsl_feat_desc; + osmo_sitype_strs; osmo_c4; osmo_get_rand_id; @@ -111,6 +119,7 @@ gsm0480_gen_reject; gsm0502_calc_paging_group; gsm0502_fn_remap; gsm0502_hop_seq_gen; +gsm0502_fn2ccch_block; gsm0503_xcch; gsm0503_rach; @@ -121,6 +130,11 @@ gsm0503_cs3; gsm0503_cs2_np; gsm0503_cs3_np; gsm0503_tch_fr; +gsm0503_tch_f24; +gsm0503_tch_h24; +gsm0503_tch_f48; +gsm0503_tch_f96; +gsm0503_tch_f144; gsm0503_tch_hr; gsm0503_tch_afs_12_2; gsm0503_tch_afs_10_2; @@ -205,6 +219,25 @@ gsm0808_create_handover_succeeded; gsm0808_create_handover_complete; gsm0808_create_handover_failure; gsm0808_create_handover_performed; +gsm0808_create_vgcs_vbs_setup; +gsm0808_create_vgcs_vbs_setup_ack; +gsm0808_create_vgcs_vbs_setup_refuse; +gsm0808_create_vgcs_vbs_assign_req; +gsm0808_create_vgcs_vbs_assign_res; +gsm0808_create_vgcs_vbs_assign_fail; +gsm0808_create_uplink_request; +gsm0808_create_uplink_request_ack; +gsm0808_create_uplink_request_cnf; +gsm0808_create_uplink_app_data; +gsm0808_create_uplink_release_ind; +gsm0808_create_uplink_reject_cmd; +gsm0808_create_uplink_release_cmd; +gsm0808_create_uplink_seized_cmd; +gsm0808_create_vgcs_additional_info; +gsm0808_create_vgcs_vbs_area_cell_info; +gsm0808_create_vgcs_vbs_assign_stat; +gsm0808_create_vgcs_sms; +gsm0808_create_notification_data; gsm0808_create_common_id; gsm0808_prepend_dtap_header; gsm0808_enc_cause; @@ -212,8 +245,10 @@ gsm0808_enc_aoip_trasp_addr; gsm0808_dec_aoip_trasp_addr; gsm0808_dec_osmux_cid; gsm0808_enc_speech_codec; +gsm0808_enc_speech_codec2; gsm0808_dec_speech_codec; gsm0808_enc_speech_codec_list; +gsm0808_enc_speech_codec_list2; gsm0808_dec_speech_codec_list; gsm0808_enc_channel_type; gsm0808_dec_channel_type; @@ -225,6 +260,23 @@ gsm0808_enc_cell_id_list; gsm0808_enc_cell_id_list2; gsm0808_dec_cell_id_list; gsm0808_dec_cell_id_list2; +gsm0808_enc_group_callref; +gsm0808_dec_group_callref; +gsm0808_enc_priority; +gsm0808_dec_priority; +gsm0808_enc_vgcs_feature_flags; +gsm0808_dec_vgcs_feature_flags; +gsm0808_enc_data_identity; +gsm0808_dec_data_identity; +gsm0808_enc_msisdn; +gsm0808_dec_msisdn; +gsm0808_enc_talker_identity; +gsm0808_dec_talker_identity; +gsm0808_enc_assign_req; +gsm0808_dec_assign_req; +gsm0808_enc_cell_id_list_segment; +gsm0808_dec_cell_id_list_segment; +gsm0808_dec_call_id; gsm0808_cell_id_list_add; gsm0808_cell_id_to_list; gsm0808_cell_id_to_cgi; @@ -246,6 +298,8 @@ gsm0808_chan_type_to_speech_codec; gsm0808_speech_codec_from_chan_type; gsm0808_sc_cfg_from_gsm48_mr_cfg; gsm48_mr_cfg_from_gsm0808_sc_cfg; +gsm0808_amr_modes_from_cfg; +gsm0808_amr_mode_names; gsm0808_speech_codec_type_names; gsm0808_permitted_speech_names; gsm0808_chosen_enc_alg_names; @@ -328,6 +382,9 @@ osmo_gsm48_rest_octets_si13_decode; osmo_gsm48_rest_octets_si13_encode; osmo_gsm48_rest_octets_si3_decode; osmo_gsm48_rest_octets_si4_decode; +osmo_gsm48_si1ro_nch_pos_encode; +osmo_gsm48_si1ro_nch_pos_decode; +gsm48_rr_short_pd_msg_name; gsm48_rr_msg_name; gsm48_cc_state_name; gsm48_construct_ra; @@ -382,10 +439,14 @@ osmo_mobile_identity_to_str_buf; osmo_mobile_identity_to_str_c; osmo_mobile_identity_cmp; osmo_mobile_identity_decode; +osmo_mobile_identity_decode_from_l3_buf; osmo_mobile_identity_decode_from_l3; osmo_mobile_identity_encoded_len; osmo_mobile_identity_encode_buf; osmo_mobile_identity_encode_msgb; +osmo_routing_area_id_decode; +osmo_routing_area_id_encode_buf; +osmo_routing_area_id_encode_msgb; gsm48_mm_att_tlvdef; gsm48_number_of_paging_subchannels; gsm48_parse_ra; @@ -459,6 +520,13 @@ gsm48_pdisc_msgtype_name_buf; gsm48_pdisc_msgtype_name_c; gsm48_reject_value_names; +osmo_gsm44068_msg_type_names; +osmo_gsm44068_priority_level_names; +osmo_gsm44068_cause_names; +osmo_gsm44068_call_state_names; +osmo_gsm44068_talker_priority_names; +osmo_gsm44068_att_tlvdef; + gsm_7bit_decode; gsm_7bit_decode_ussd; gsm_7bit_encode; @@ -484,6 +552,7 @@ gsm_gsmtime2fn; osmo_dump_gsmtime; osmo_dump_gsmtime_buf; osmo_dump_gsmtime_c; +gsm_rfn2fn; gsm_milenage; gsm_septet_encode; @@ -518,7 +587,11 @@ lapdm_entity_init3; lapdm_entity_reset; lapdm_entity_set_flags; lapdm_entity_set_mode; +lapdm_entity_set_t200_fn; +lapdm_channel_set_t200_fn; lapdm_phsap_dequeue_prim; +lapdm_phsap_dequeue_prim_fn; +lapdm_t200_fn; lapdm_phsap_up; lapdm_rslms_recvmsg; @@ -542,11 +615,14 @@ osmo_a5_2; osmo_auth_alg_name; osmo_auth_alg_parse; osmo_auth_gen_vec; +osmo_auth_gen_vec2; osmo_auth_gen_vec_auts; +osmo_auth_gen_vec_auts2; osmo_auth_3g_from_2g; osmo_auth_load; osmo_auth_register; osmo_auth_supported; +osmo_auth_c2; osmo_auth_c3; osmo_sub_auth_type_names; @@ -717,6 +793,7 @@ osmo_cbsp_encode; osmo_cbsp_decode; osmo_cbsp_recv_buffered; osmo_cbsp_errstr; +osmo_cbsp_segmentation_cb; osmo_i460_demux_in; osmo_i460_mux_enqueue; @@ -771,5 +848,29 @@ osmo_gad_raw_read; osmo_gad_raw_write; osmo_gad_type_names; +osmo_iuup_compute_header_crc; +osmo_iuup_compute_payload_crc; +osmo_iuup_instance_alloc; +osmo_iuup_instance_free; +osmo_iuup_instance_set_user_prim_cb; +osmo_iuup_instance_set_transport_prim_cb; +osmo_iuup_tnl_prim_up; +osmo_iuup_rnl_prim_down; +osmo_iuup_rnl_prim_alloc; +osmo_iuup_tnl_prim_alloc; + +osmo_csd_12k_6k_decode_frame; +osmo_csd_12k_6k_encode_frame; +osmo_csd_3k6_decode_frame; +osmo_csd_3k6_encode_frame; +osmo_csd_ubit_dump; + +osmo_rlp_decode; +osmo_rlp_encode; +osmo_rlp_fcs_compute; +osmo_rlp_ftype_s_vals; +osmo_rlp_ftype_u_vals; +osmo_rlp_ftype_vals; + local: *; }; diff --git a/src/gsm/milenage/milenage.c b/src/gsm/milenage/milenage.c index 3c14ab96..ba9ab33b 100644 --- a/src/gsm/milenage/milenage.c +++ b/src/gsm/milenage/milenage.c @@ -244,19 +244,13 @@ int milenage_auts(const u8 *opc, const u8 *k, const u8 *_rand, const u8 *auts, int gsm_milenage(const u8 *opc, const u8 *k, const u8 *_rand, u8 *sres, u8 *kc) { u8 res[8], ck[16], ik[16]; - int i; if (milenage_f2345(opc, k, _rand, res, ck, ik, NULL, NULL)) return -1; osmo_auth_c3(kc, ck, ik); + osmo_auth_c2(sres, res, sizeof(res), 1); -#ifdef GSM_MILENAGE_ALT_SRES - os_memcpy(sres, res, 4); -#else /* GSM_MILENAGE_ALT_SRES */ - for (i = 0; i < 4; i++) - sres[i] = res[i] ^ res[i + 4]; -#endif /* GSM_MILENAGE_ALT_SRES */ return 0; } diff --git a/src/gsm/mncc.c b/src/gsm/mncc.c index 938cf9a6..8a7dc4d6 100644 --- a/src/gsm/mncc.c +++ b/src/gsm/mncc.c @@ -20,7 +20,7 @@ * */ -#include "../config.h" +#include "config.h" #ifdef HAVE_SYS_SOCKET_H diff --git a/src/gsm/rlp.c b/src/gsm/rlp.c new file mode 100644 index 00000000..1e90a689 --- /dev/null +++ b/src/gsm/rlp.c @@ -0,0 +1,243 @@ +/* + * Copyright (C) 2022-2023 Harald Welte <laforge@osmocom.org> + * + * All Rights Reserved + * + * SPDX-License-Identifier: GPL-2.0+ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +/*! \addtogroup rlp + * @{ + * RLP (Radio Link Protocol) as per 3GPP TS 24.022 + * + */ + +#include <stdint.h> +#include <stddef.h> +#include <string.h> +#include <errno.h> + +#include <osmocom/gsm/rlp.h> + +const struct value_string osmo_rlp_ftype_vals[] = { + { OSMO_RLP_FT_U, "U" }, + { OSMO_RLP_FT_S, "S" }, + { OSMO_RLP_FT_IS, "IS" }, + { 0, NULL } +}; + +const struct value_string osmo_rlp_ftype_u_vals[] = { + { OSMO_RLP_U_FT_SABM, "SABM" }, + { OSMO_RLP_U_FT_UA, "UA" }, + { OSMO_RLP_U_FT_DISC, "DISC" }, + { OSMO_RLP_U_FT_DM, "DM" }, + { OSMO_RLP_U_FT_NULL, "NULL" }, + { OSMO_RLP_U_FT_UI, "UI" }, + { OSMO_RLP_U_FT_XID, "XID" }, + { OSMO_RLP_U_FT_TEST, "TEST" }, + { OSMO_RLP_U_FT_REMAP, "REMAP" }, + { 0, NULL } +}; + +const struct value_string osmo_rlp_ftype_s_vals[] = { + { OSMO_RLP_S_FT_RR, "RR" }, + { OSMO_RLP_S_FT_REJ, "REJ" }, + { OSMO_RLP_S_FT_RNR, "RNR" }, + { OSMO_RLP_S_FT_SREJ, "SREJ" }, + { 0, NULL } +}; + +/* number of bytes used up by FCS */ +#define FCS_SIZE_BYTES 3 + +/*! decode a RLP frame into its abstract representation. Doesn't check FCS correctness. + * \param[out] out caller-allocated memory for output of decoded frame + * \param[in] version RLP version number to use when decoding + * \param[in] data raw RLP frame input data + * \param[in] data_len length of data (in octets); must be 30 (240bit) or 72 (576bit) + * \returns 0 in case of success; negative on error */ +int osmo_rlp_decode(struct osmo_rlp_frame_decoded *out, uint8_t version, const uint8_t *data, size_t data_len) +{ + const uint8_t hdr_len = 2; /* will become a variable when we introduce v2 support */ + uint8_t n_s, n_r; + + if (data_len != 240/8 && data_len != 576/8) + return -EINVAL; + + /* we only support version 0+1 so far */ + if (version >= 2) + return -ENOTSUP; + + memset(out, 0, sizeof(*out)); + out->version = version; + + out->c_r = data[0] & 1; + n_s = (data[0] >> 3) | (data[1] & 1) << 5; + n_r = (data[1] >> 2); + out->fcs = (data[data_len-1] << 16) | (data[data_len-2]) << 8 | (data[data_len-3] << 0); + out->p_f = (data[1] >> 1) & 1; + + switch (n_s) { + case 0x3f: + out->ftype = OSMO_RLP_FT_U; + out->u_ftype = n_r & 0x1f; + if (out->u_ftype == OSMO_RLP_U_FT_XID) { + memcpy(out->info, data + hdr_len, data_len - (hdr_len + FCS_SIZE_BYTES)); + out->info_len = data_len - (hdr_len + FCS_SIZE_BYTES); + } + break; + case 0x3e: + out->ftype = OSMO_RLP_FT_S; + out->s_ftype = (data[0] >> 1) & 3; + out->n_r = n_r; + break; + default: + out->ftype = OSMO_RLP_FT_IS; + out->s_ftype = (data[0] >> 1) & 3; + out->n_s = n_s; + out->n_r = n_r; + memcpy(out->info, data + hdr_len, data_len - (hdr_len + FCS_SIZE_BYTES)); + out->info_len = data_len - (2 + 3); + break; + } + + return 0; +} + +/*! encode a RLP frame from its abstract representation. Generates FCS. + * \param[out] out caller-allocated output buffer + * \param[in] out_size size of output buffer (in octets); must be 30 (240bit) or 72 (576bit) + * \param[in] in decoded RLP frame which is to be encoded + * \returns number of output bytes used; negative on error */ +int osmo_rlp_encode(uint8_t *out, size_t out_size, const struct osmo_rlp_frame_decoded *in) +{ + const uint8_t hdr_len = 2; /* will become a variable when we introduce v2 support */ + uint8_t n_s, n_r, s_bits; + uint32_t fcs; + + /* we only support version 0+1 so far */ + if (in->version >= 2) + return -ENOTSUP; + + if (out_size != 240/8 && out_size != 576/8) + return -EINVAL; + + memset(out, 0, out_size); + + if (in->c_r) + out[0] |= 0x01; + if (in->p_f) + out[1] |= 0x02; + + switch (in->ftype) { + case OSMO_RLP_FT_U: + n_s = 0x3f; + n_r = in->u_ftype; + s_bits = 0; + if (in->u_ftype == OSMO_RLP_U_FT_XID) { + if (in->info_len > out_size - (hdr_len + FCS_SIZE_BYTES)) + return -EINVAL; + memcpy(out+hdr_len, in->info, in->info_len); + } + break; + case OSMO_RLP_FT_S: + n_s = 0x3e; + n_r = in->n_r; + s_bits = in->s_ftype; + break; + case OSMO_RLP_FT_IS: + /* we only support 240 bit so far */ + if (in->info_len > out_size - (hdr_len + FCS_SIZE_BYTES)) + return -EINVAL; + n_s = in->n_s; + n_r = in->n_r; + s_bits = in->s_ftype; + memcpy(out+hdr_len, in->info, in->info_len); + break; + default: + return -EINVAL; + } + + /* patch N(S) into output data */ + out[0] |= (n_s & 0x1F) << 3; + out[1] |= (n_s & 0x20) >> 5; + + /* patch N(R) / M-bits into output data */ + out[1] |= (n_r & 0x3f) << 2; + + /* patch S-bits into output data */ + out[0] |= (s_bits & 3) << 1; + + /* compute FCS + add it to end of frame */ + fcs = osmo_rlp_fcs_compute(out, out_size - FCS_SIZE_BYTES); + out[out_size - 3] = (fcs >> 0) & 0xff; + out[out_size - 2] = (fcs >> 8) & 0xff; + out[out_size - 1] = (fcs >> 16) & 0xff; + + return out_size; +} + + +static const uint32_t rlp_fcs_table[256] = { + 0x00B29D2D, 0x00643A5B, 0x0044D87A, 0x00927F0C, 0x00051C38, 0x00D3BB4E, 0x00F3596F, 0x0025FE19, + 0x008694BC, 0x005033CA, 0x0070D1EB, 0x00A6769D, 0x003115A9, 0x00E7B2DF, 0x00C750FE, 0x0011F788, + 0x00DA8E0F, 0x000C2979, 0x002CCB58, 0x00FA6C2E, 0x006D0F1A, 0x00BBA86C, 0x009B4A4D, 0x004DED3B, + 0x00EE879E, 0x003820E8, 0x0018C2C9, 0x00CE65BF, 0x0059068B, 0x008FA1FD, 0x00AF43DC, 0x0079E4AA, + 0x0062BB69, 0x00B41C1F, 0x0094FE3E, 0x00425948, 0x00D53A7C, 0x00039D0A, 0x00237F2B, 0x00F5D85D, + 0x0056B2F8, 0x0080158E, 0x00A0F7AF, 0x007650D9, 0x00E133ED, 0x0037949B, 0x001776BA, 0x00C1D1CC, + 0x000AA84B, 0x00DC0F3D, 0x00FCED1C, 0x002A4A6A, 0x00BD295E, 0x006B8E28, 0x004B6C09, 0x009DCB7F, + 0x003EA1DA, 0x00E806AC, 0x00C8E48D, 0x001E43FB, 0x008920CF, 0x005F87B9, 0x007F6598, 0x00A9C2EE, + 0x0049DA1E, 0x009F7D68, 0x00BF9F49, 0x0069383F, 0x00FE5B0B, 0x0028FC7D, 0x00081E5C, 0x00DEB92A, + 0x007DD38F, 0x00AB74F9, 0x008B96D8, 0x005D31AE, 0x00CA529A, 0x001CF5EC, 0x003C17CD, 0x00EAB0BB, + 0x0021C93C, 0x00F76E4A, 0x00D78C6B, 0x00012B1D, 0x00964829, 0x0040EF5F, 0x00600D7E, 0x00B6AA08, + 0x0015C0AD, 0x00C367DB, 0x00E385FA, 0x0035228C, 0x00A241B8, 0x0074E6CE, 0x005404EF, 0x0082A399, + 0x0099FC5A, 0x004F5B2C, 0x006FB90D, 0x00B91E7B, 0x002E7D4F, 0x00F8DA39, 0x00D83818, 0x000E9F6E, + 0x00ADF5CB, 0x007B52BD, 0x005BB09C, 0x008D17EA, 0x001A74DE, 0x00CCD3A8, 0x00EC3189, 0x003A96FF, + 0x00F1EF78, 0x0027480E, 0x0007AA2F, 0x00D10D59, 0x00466E6D, 0x0090C91B, 0x00B02B3A, 0x00668C4C, + 0x00C5E6E9, 0x0013419F, 0x0033A3BE, 0x00E504C8, 0x007267FC, 0x00A4C08A, 0x008422AB, 0x005285DD, + 0x001F18F0, 0x00C9BF86, 0x00E95DA7, 0x003FFAD1, 0x00A899E5, 0x007E3E93, 0x005EDCB2, 0x00887BC4, + 0x002B1161, 0x00FDB617, 0x00DD5436, 0x000BF340, 0x009C9074, 0x004A3702, 0x006AD523, 0x00BC7255, + 0x00770BD2, 0x00A1ACA4, 0x00814E85, 0x0057E9F3, 0x00C08AC7, 0x00162DB1, 0x0036CF90, 0x00E068E6, + 0x00430243, 0x0095A535, 0x00B54714, 0x0063E062, 0x00F48356, 0x00222420, 0x0002C601, 0x00D46177, + 0x00CF3EB4, 0x001999C2, 0x00397BE3, 0x00EFDC95, 0x0078BFA1, 0x00AE18D7, 0x008EFAF6, 0x00585D80, + 0x00FB3725, 0x002D9053, 0x000D7272, 0x00DBD504, 0x004CB630, 0x009A1146, 0x00BAF367, 0x006C5411, + 0x00A72D96, 0x00718AE0, 0x005168C1, 0x0087CFB7, 0x0010AC83, 0x00C60BF5, 0x00E6E9D4, 0x00304EA2, + 0x00932407, 0x00458371, 0x00656150, 0x00B3C626, 0x0024A512, 0x00F20264, 0x00D2E045, 0x00044733, + 0x00E45FC3, 0x0032F8B5, 0x00121A94, 0x00C4BDE2, 0x0053DED6, 0x008579A0, 0x00A59B81, 0x00733CF7, + 0x00D05652, 0x0006F124, 0x00261305, 0x00F0B473, 0x0067D747, 0x00B17031, 0x00919210, 0x00473566, + 0x008C4CE1, 0x005AEB97, 0x007A09B6, 0x00ACAEC0, 0x003BCDF4, 0x00ED6A82, 0x00CD88A3, 0x001B2FD5, + 0x00B84570, 0x006EE206, 0x004E0027, 0x0098A751, 0x000FC465, 0x00D96313, 0x00F98132, 0x002F2644, + 0x00347987, 0x00E2DEF1, 0x00C23CD0, 0x00149BA6, 0x0083F892, 0x00555FE4, 0x0075BDC5, 0x00A31AB3, + 0x00007016, 0x00D6D760, 0x00F63541, 0x00209237, 0x00B7F103, 0x00615675, 0x0041B454, 0x00971322, + 0x005C6AA5, 0x008ACDD3, 0x00AA2FF2, 0x007C8884, 0x00EBEBB0, 0x003D4CC6, 0x001DAEE7, 0x00CB0991, + 0x00686334, 0x00BEC442, 0x009E2663, 0x00488115, 0x00DFE221, 0x00094557, 0x0029A776, 0x00FF0000 +}; + +/*! compute RLP FCS according to 3GPP TS 24.022 Section 4.4. + * \param[in] in input data over which to compute FCS + * \param[in] in_len length of input data (in octets) + * \returns computed frame check sequence (FCS). */ +uint32_t osmo_rlp_fcs_compute(const uint8_t *in, size_t in_len) +{ + uint32_t divider = 0; + size_t i; + + for (i = 0; i < in_len; i++) { + uint8_t input = in[i] ^ (divider & 0xff); + divider = (divider >> 8) ^ rlp_fcs_table[input]; + } + + return divider; +} + +/*! @} */ diff --git a/src/gsm/rsl.c b/src/gsm/rsl.c index 0574966e..fbba982a 100644 --- a/src/gsm/rsl.c +++ b/src/gsm/rsl.c @@ -17,10 +17,6 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ #include <stdint.h> @@ -129,6 +125,8 @@ const struct tlv_definition rsl_att_tlvdef = { [RSL_IE_OSMO_REP_ACCH_CAP] = { TLV_TYPE_TLV }, [RSL_IE_OSMO_TRAINING_SEQUENCE] = { TLV_TYPE_TLV }, [RSL_IE_OSMO_TEMP_OVP_ACCH_CAP] = { TLV_TYPE_TLV }, + [RSL_IE_OSMO_OSMUX_CID] = { TLV_TYPE_TLV }, + [RSL_IE_IPAC_SRTP_CONFIG] = { TLV_TYPE_TLV }, [RSL_IE_IPAC_PROXY_UDP] = { TLV_TYPE_FIXED, 2 }, [RSL_IE_IPAC_BSCMPL_TOUT] = { TLV_TYPE_TV }, [RSL_IE_IPAC_REMOTE_IP] = { TLV_TYPE_FIXED, 4 }, @@ -137,6 +135,8 @@ const struct tlv_definition rsl_att_tlvdef = { [RSL_IE_IPAC_LOCAL_PORT] = { TLV_TYPE_FIXED, 2 }, [RSL_IE_IPAC_SPEECH_MODE] = { TLV_TYPE_TV }, [RSL_IE_IPAC_LOCAL_IP] = { TLV_TYPE_FIXED, 4 }, + [RSL_IE_IPAC_CONN_STAT] = { TLV_TYPE_TLV, 28 }, + [RSL_IE_IPAC_HO_C_PARMS] = { TLV_TYPE_TLV }, [RSL_IE_IPAC_CONN_ID] = { TLV_TYPE_FIXED, 2 }, [RSL_IE_IPAC_RTP_CSD_FMT] = { TLV_TYPE_TV }, [RSL_IE_IPAC_RTP_JIT_BUF] = { TLV_TYPE_FIXED, 2 }, @@ -268,7 +268,7 @@ char *rsl_chan_nr_str_buf(char *buf, size_t buf_len, uint8_t chan_nr) */ const char *rsl_chan_nr_str(uint8_t chan_nr) { - static __thread char str[20]; + static __thread char str[32]; return rsl_chan_nr_str_buf(str, sizeof(str), chan_nr); } @@ -279,10 +279,10 @@ const char *rsl_chan_nr_str(uint8_t chan_nr) */ char *rsl_chan_nr_str_c(const void *ctx, uint8_t chan_nr) { - char *str = talloc_size(ctx, 20); + char *str = talloc_size(ctx, 32); if (!str) return NULL; - return rsl_chan_nr_str_buf(str, 20, chan_nr); + return rsl_chan_nr_str_buf(str, 32, chan_nr); } static const struct value_string rsl_err_vals[] = { @@ -553,7 +553,7 @@ void rsl_rll_push_l3(struct msgb *msg, uint8_t msg_type, uint8_t chan_nr, /* construct a RSLms RLL message (DATA INDICATION, UNIT DATA * INDICATION) and send it off via RSLms */ - /* Push the L3 IE tag and lengh */ + /* Push the L3 IE tag and length */ msgb_tv16_push(msg, RSL_IE_L3_INFO, l3_len); /* Then push the RSL header */ diff --git a/src/gsm/rxlev_stat.c b/src/gsm/rxlev_stat.c index 9c650cc1..f1b77f4c 100644 --- a/src/gsm/rxlev_stat.c +++ b/src/gsm/rxlev_stat.c @@ -17,10 +17,6 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ #include <unistd.h> diff --git a/src/gsm/sysinfo.c b/src/gsm/sysinfo.c index b615871f..40e26524 100644 --- a/src/gsm/sysinfo.c +++ b/src/gsm/sysinfo.c @@ -46,7 +46,7 @@ osmo_static_assert(sizeof(struct gsm48_system_information_type_4) == 13, _si4_si /* bs11 forgot the l2 len, 0-6 rest octets */ osmo_static_assert(sizeof(struct gsm48_system_information_type_5) == 18, _si5_size); osmo_static_assert(sizeof(struct gsm48_system_information_type_6) == 11, _si6_size); - +osmo_static_assert(sizeof(struct gsm48_system_information_type_10) == 1, _si10_size); osmo_static_assert(sizeof(struct gsm48_system_information_type_13) == 3, _si13_size); static const uint8_t sitype2rsl[_MAX_SYSINFO_TYPE] = { diff --git a/src/gsm/tlv_parser.c b/src/gsm/tlv_parser.c index c5aac97c..8dd460db 100644 --- a/src/gsm/tlv_parser.c +++ b/src/gsm/tlv_parser.c @@ -126,7 +126,7 @@ int osmo_tlvp_merge(struct tlv_parsed *dst, const struct tlv_parsed *src) * \param[inout] msg Caller-allocated message buffer with sufficient tailroom * \param[in] type TLV type/format to use during encode * \param[in] tag Tag of TLV to be encoded - * \parma[in] len Length of TLV to be encoded + * \param[in] len Length of TLV to be encoded * \param[in] val Value part of TLV to be encoded * \returns 0 on success; negative in case of error */ int tlv_encode_one(struct msgb *msg, enum tlv_type type, uint8_t tag, @@ -225,7 +225,11 @@ int tlv_encode_ordered(struct msgb *msg, const struct tlv_definition *def, const * \param[in] def structure defining the valid TLV tags / configurations * \param[in] buf the input data buffer to be parsed * \param[in] buf_len length of the input data buffer - * \returns number of bytes consumed by the TLV entry / IE parsed; negative in case of error + * \returns number of bytes consumed by the TLV entry / IE parsed; negative in case of error. + * + * In IEs of type TLV_TYPE_SINGLE_TV, the data pointer \ref o_val will point to the + * byte shared by both the Tag and te Value, hence the tag is to be trimmed + * by the caller. */ int tlv_parse_one(uint8_t *o_tag, uint16_t *o_len, const uint8_t **o_val, const struct tlv_definition *def, @@ -241,7 +245,13 @@ int tlv_parse_one(uint8_t *o_tag, uint16_t *o_len, const uint8_t **o_val, *o_tag = tag; /* single octet TV IE */ - if (def->def[tag & 0xf0].type == TLV_TYPE_SINGLE_TV) { + if (def->def[tag >> 4].type == TLV_TYPE_SINGLE_TV) { + *o_tag = tag >> 4; + *o_val = buf; + *o_len = 1; + return 1; + } else if ((tag > 0x0f) && (def->def[tag & 0xf0].type == TLV_TYPE_SINGLE_TV)) { + /* backward compat for old IEs with half-octet tag defined as 0xN0: */ *o_tag = tag & 0xf0; *o_val = buf; *o_len = 1; diff --git a/src/gsm/tuak/KeccakP-1600-3gpp.c b/src/gsm/tuak/KeccakP-1600-3gpp.c new file mode 100644 index 00000000..3f5e2ad4 --- /dev/null +++ b/src/gsm/tuak/KeccakP-1600-3gpp.c @@ -0,0 +1,176 @@ +/* ----------------------------------------------------------------------- + * code extracted from 3GPP TS 35.231, annex E for Keccak core functions + * https://portal.3gpp.org/desktopmodules/Specifications/SpecificationDetails.aspx?specificationId=2402 + *-----------------------------------------------------------------------*/ + +/* This code may be freely used or adapted. +*/ + +#include "KeccakP-1600-3gpp.h" + + +const uint8_t Rho[25] = {0,1,62,28,27,36,44,6,55,20,3,10,43,25,39,41,45, + 15,21,8,18,2,61,56,14}; + +const uint8_t Pi[25] = {0,6,12,18,24,3,9,10,16,22,1,7,13,19,20,4,5,11,17, + 23,2,8,14,15,21}; + +const uint8_t Iota[24] = {1,146,218,112,155,33,241,89,138,136,57,42,187,203, + 217,83,82,192,26,106,241,208,33,120}; + +#define ROTATE64(value, n) \ +((((uint64_t)(value))<<(n)) | (((uint64_t)(value))>>(64-(n)))) + +/* --------------------------------------------------------------------- + 64-bit version of Keccak_f(1600) + --------------------------------------------------------------------- +*/ +void Keccak_f_64(uint64_t s[25]) +{ uint64_t t[5]; + uint8_t i, j, round; + + for(round=0; round<24; ++round) + { /* Theta function */ + for(i=0; i<5; ++i) + t[i] = s[i] ^ s[5+i] ^ s[10+i] ^ s[15+i] ^ s[20+i]; + for(i=0; i<5; ++i, s+=5) + { s[0] ^= t[4] ^ ROTATE64(t[1], 1); + s[1] ^= t[0] ^ ROTATE64(t[2], 1); + s[2] ^= t[1] ^ ROTATE64(t[3], 1); + s[3] ^= t[2] ^ ROTATE64(t[4], 1); + s[4] ^= t[3] ^ ROTATE64(t[0], 1); + } + s -= 25; + + /* Rho function */ + for(i=1; i<25; ++i) + s[i] = ROTATE64(s[i], Rho[i]); + + /* Pi function */ + for(t[1] = s[i=1]; (j=Pi[i]) > 1; s[i]=s[j], i=j); + s[i] = t[1]; + + /* Chi function */ + for(i=0; i<5; ++i, s += 5) + { t[0] = (~s[1]) & s[2]; + t[1] = (~s[2]) & s[3]; + t[2] = (~s[3]) & s[4]; + t[3] = (~s[4]) & s[0]; + t[4] = (~s[0]) & s[1]; + for(j=0; j<5; ++j) s[j] ^= t[j]; + } + s -= 25; + + /* Iota function */ + t[0] = Iota[round]; + *s ^= (t[0] | (t[0]<<11) | (t[0]<<26) | (t[0]<<57)) + & 0x800000008000808BULL; /* set & mask bits 0,1,3,7,15,31,63 */ + } +} + + +/* --------------------------------------------------------------------- + 8-bit version of Keccak_f(1600) + --------------------------------------------------------------------- +*/ +void Keccak_f_8(uint8_t s[200]) +{ uint8_t t[40], i, j, k, round; + + for(round=0; round<24; ++round) + { /* Theta function */ + for(i=0; i<40; ++i) + t[i]=s[i]^s[40+i]^s[80+i]^s[120+i]^s[160+i]; + for(i=0; i<200; i+=8) + for(j = (i+32)%40, k=0; k<8; ++k) + s[i+k] ^= t[j+k]; + for(i=0; i<40; t[i] = (t[i]<<1)|j, i+=8) + for(j = t[i+7]>>7, k=7; k; --k) + t[i+k] = (t[i+k]<<1)|(t[i+k-1]>>7); + for(i=0; i<200; i+=8) + for(j = (i+8)%40, k=0; k<8; ++k) + s[i+k] ^= t[j+k]; + + /* Rho function */ + for(i=8; i<200; i+=8) + { for(j = Rho[i>>3]>>3, k=0; k<8; ++k) /* j:=bytes to shift, s->t */ + t[(k+j)&7] = s[i+k]; + for(j = Rho[i>>3]&7, k=7; k; --k) /* j:=bits to shift, t->s */ + s[i+k] = (t[k]<<j) | (t[k-1]>>(8-j)); + s[i] = (t[0]<<j) | (t[7]>>(8-j)); + } + + /* Pi function */ + for(k=8; k<16; ++k) t[k] = s[k]; /* =memcpy(t+8, s+8, 8) */ + for(i=1; (j=Pi[i])>1; i=j) + for(k=0; k<8; ++k) /* =memcpy(s+(i<<3), s+(j<<3), 8) */ + s[(i<<3)|k] = s[(j<<3)|k]; + for(k=0; k<8; ++k) /* =memcpy(s+(i<<3), t+8, 8) */ + s[(i<<3)|k] = t[k+8]; + + /* Chi function */ + for(i=0; i<200; i+=40) + { for(j=0; j<40; ++j) + t[j]=(~s[i+(j+8)%40]) & s[i+(j+16)%40]; + for(j=0; j<40; ++j) s[i+j]^=t[j]; + } + + /* Iota function */ + k = Iota[round]; + s[0] ^= k & 0x8B; /* bits 0, 1, 3, 7 */ + s[1] ^= (k<<3)&0x80; /* bit 15 */ + s[3] ^= (k<<2)&0x80; /* bit 31 */ + s[7] ^= (k<<1)&0x80; /* bit 63 */ + + } +} + +/* --------------------------------------------------------------------- + 32-bit version of Keccak_f(1600) + --------------------------------------------------------------------- +*/ +void Keccak_f_32(uint32_t s[50]) +{ uint32_t t[10]; + uint8_t i, j, round, k; + + for(round=0; round<24; ++round) + { /* Theta function */ + for(i=0; i<10; ++i) + t[i] = s[i] ^ s[10+i] ^ s[20+i] ^ s[30+i] ^ s[40+i]; + for(i=0; i<5; ++i) + for(j=8, k=2; ; j%=10, k=(k+2)%10) + { *s++ ^= t[j++] ^ ((t[k]<<1)|(t[k+1]>>31)); + *s++ ^= t[j++] ^ ((t[k+1]<<1)|(t[k]>>31)); + if(j==8) break; + } + s -= 50; + + /* Rho function */ + for(i=2; i<50; i+=2) + { k = Rho[i>>1] & 0x1f; + t[0] = (s[i+1] << k) | (s[i] >> (32-k)); + t[1] = (s[i] << k) | (s[i+1] >> (32-k)); + k = Rho[i>>1] >> 5; + s[i] = t[1-k], s[i+1] = t[k]; + } + + /* Pi function */ + for(i=2, t[0]=s[2], t[1]=s[3]; (j=(Pi[i>>1]<<1))>2; i=j) + s[i]=s[j], s[i+1]=s[j+1]; + s[i]=t[0], s[i+1]=t[1]; + + /* Chi function */ + for(i=0; i<5; ++i, s+=10) + { for(j=0; j<10; ++j) + t[j] = (~s[(j+2)%10]) & s[(j+4)%10]; + for(j=0; j<10; ++j) + s[j] ^= t[j]; + } + s -= 50; + + /* Iota function */ + t[0] = Iota[round]; + s[0] ^= (t[0] | (t[0]<<11) | (t[0]<<26)) & 0x8000808B; + s[1] ^= (t[0]<<25) & 0x80000000; + } +} + diff --git a/src/gsm/tuak/KeccakP-1600-3gpp.h b/src/gsm/tuak/KeccakP-1600-3gpp.h new file mode 100644 index 00000000..a23cc460 --- /dev/null +++ b/src/gsm/tuak/KeccakP-1600-3gpp.h @@ -0,0 +1,25 @@ +/* ----------------------------------------------------------------------- + * code extracted from 3GPP TS 35.231, annex E for Keccak core functions + * https://portal.3gpp.org/desktopmodules/Specifications/SpecificationDetails.aspx?specificationId=2402 + *-----------------------------------------------------------------------*/ + +/* this is the trick to make the code cross-platform + * at least, Win32 / Linux */ + +#if defined(_WIN32) || defined(__WIN32__) +# include <windows.h> +# define EXPORTIT __declspec(dllexport) +#else +# define EXPORTIT +#endif + +#include <stdint.h> + +/*------------------------------------------------------------------------ + * KeccakP-1600-3gpp.h + *------------------------------------------------------------------------*/ + +EXPORTIT void Keccak_f_8 (uint8_t s[200]); +EXPORTIT void Keccak_f_32(uint32_t s[50]); +EXPORTIT void Keccak_f_64(uint64_t s[25]); + diff --git a/src/gsm/tuak/tuak.c b/src/gsm/tuak/tuak.c new file mode 100644 index 00000000..c044a377 --- /dev/null +++ b/src/gsm/tuak/tuak.c @@ -0,0 +1,372 @@ +/* (C) 2023 by Harald Welte <laforge@osmocom.org> + * + * All Rights Reserved + * + * SPDX-License-Identifier: GPL-2.0+ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <stdint.h> +#include <string.h> +#include <string.h> +#include <errno.h> + +#include <osmocom/core/utils.h> + +#include "KeccakP-1600-3gpp.h" + +/* TUAK authentication algorithm + * as proposed by 3GPP as an alternative to Milenage + * algorithm based on SHA-3 (more exactly its KeccakP-1600 permutation) + * see 3GPP TS 35.231, 232 and 233 */ + +static unsigned int g_keccak_iterations = 1; +static const char algoname[] = "TUAK1.0"; +const uint8_t zero16[16] = { 0, }; + +void tuak_set_keccak_iterations(unsigned int i) +{ + g_keccak_iterations = i; +} + +/* append data from 'input' to 'buf' at 'idx', reversing byte order */ +#define PUSH_DATA(buf, idx, input, nbytes) \ + for (int i = nbytes-1; i >= 0; i--) { \ + buf[idx++] = input[i]; \ + } + +/* like memcpy(), but reversing they order of bytes */ +void memcpy_reverse(uint8_t *dst, const uint8_t *src, size_t len) +{ + for (size_t i = 0; i < len; i++) + dst[i] = src[len-i-1]; +} + +static void tuak_core(uint8_t buf[200], const uint8_t *opc, uint8_t instance, const uint8_t *_rand, + const uint8_t *amf, const uint8_t *sqn, const uint8_t *k, uint8_t k_len_bytes, + unsigned int keccac_iterations) +{ + unsigned int idx = 0; + + PUSH_DATA(buf, idx, opc, 32); + buf[idx++] = instance; + PUSH_DATA(buf, idx, algoname, strlen(algoname)); /* without trailing NUL */ + PUSH_DATA(buf, idx, _rand, 16); + PUSH_DATA(buf, idx, amf, 2); + PUSH_DATA(buf, idx, sqn, 6); + PUSH_DATA(buf, idx, k, k_len_bytes); + memset(buf+idx, 0, 32-k_len_bytes); idx += 32-k_len_bytes; + buf[idx++] = 0x1f; + memset(buf+idx, 0, 38); idx += 38; + buf[idx++] = 0x80; + memset(buf+idx, 0, 64); idx += 64; + OSMO_ASSERT(idx == 200); + + for (unsigned int i = 0; i < keccac_iterations; i++) + Keccak_f_64((uint64_t *) buf); +} + +/** + * tuak_f1 - TUAK f1 algorithm + * @opc: OPc = 256-bit value derived from OP and K + * @k: K = 128-bit or 256-bit subscriber key + * @_rand: RAND = 128-bit random challenge + * @sqn: SQN = 48-bit sequence number + * @amf: AMF = 16-bit authentication management field + * @mac_a: Buffer for MAC-A = 64/128/256-bit network authentication code + * Returns: 0 on success, -1 on failure + */ +int tuak_f1(const uint8_t *opc, const uint8_t *k, uint8_t k_len_bytes, const uint8_t *_rand, + const uint8_t *sqn, const uint8_t *amf, uint8_t *mac_a, uint8_t mac_a_len_bytes, + unsigned int keccac_iterations) +{ + uint8_t buf[200]; + uint8_t instance = 0x00; + + switch (mac_a_len_bytes) { + case 8: + instance |= 0x08; + break; + case 16: + instance |= 0x10; + break; + case 32: + instance |= 0x20; + break; + default: + return -EINVAL; + } + + switch (k_len_bytes) { + case 16: + break; + case 32: + instance |= 0x01; + break; + default: + return -EINVAL; + } + + tuak_core(buf, opc, instance, _rand, amf, sqn, k, k_len_bytes, keccac_iterations); + + memcpy_reverse(mac_a, buf, mac_a_len_bytes); + + return 0; +} + +/** + * tuak_f1star - TUAK f1* algorithm + * @opc: OPc = 256-bit value derived from OP and K + * @k: K = 128-bit or 256-bit subscriber key + * @_rand: RAND = 128-bit random challenge + * @sqn: SQN = 48-bit sequence number + * @amf: AMF = 16-bit authentication management field + * @mac_s: Buffer for MAC-S = 64/128/256-bit resync authentication code + * Returns: 0 on success, -1 on failure + */ +int tuak_f1star(const uint8_t *opc, const uint8_t *k, uint8_t k_len_bytes, const uint8_t *_rand, + const uint8_t *sqn, const uint8_t *amf, uint8_t *mac_s, uint8_t mac_s_len_bytes, + unsigned int keccac_iterations) +{ + uint8_t buf[200]; + uint8_t instance = 0x80; + + switch (mac_s_len_bytes) { + case 8: + instance |= 0x08; + break; + case 16: + instance |= 0x10; + break; + case 32: + instance |= 0x20; + break; + default: + return -EINVAL; + } + + switch (k_len_bytes) { + case 16: + break; + case 32: + instance |= 0x01; + break; + default: + return -EINVAL; + } + + tuak_core(buf, opc, instance, _rand, amf, sqn, k, k_len_bytes, keccac_iterations); + + memcpy_reverse(mac_s, buf, mac_s_len_bytes); + + return 0; +} + +/** + * tuak_f2345 - TUAK f2, f3, f4, f5, algorithms + * @opc: OPc = 256-bit value derived from OP and K + * @k: K = 128/256-bit subscriber key + * @_rand: RAND = 128-bit random challenge + * @res: Buffer for RES = 32/64/128/256-bit signed response (f2), or %NULL + * @ck: Buffer for CK = 128/256-bit confidentiality key (f3), or %NULL + * @ik: Buffer for IK = 128/256-bit integrity key (f4), or %NULL + * @ak: Buffer for AK = 48-bit anonymity key (f5), or %NULL + * Returns: 0 on success, -1 on failure + */ +int tuak_f2345(const uint8_t *opc, const uint8_t *k, uint8_t k_len_bytes, + const uint8_t *_rand, uint8_t *res, uint8_t res_len_bytes, + uint8_t *ck, uint8_t ck_len_bytes, + uint8_t *ik, uint8_t ik_len_bytes, uint8_t *ak, unsigned int keccac_iterations) +{ + uint8_t buf[200]; + uint8_t instance = 0x40; + + switch (res_len_bytes) { + case 4: + break; + case 8: + instance |= 0x08; + break; + case 16: + instance |= 0x10; + break; + case 32: + instance |= 0x20; + break; + default: + return -EINVAL; + } + + switch (ck_len_bytes) { + case 16: + break; + case 32: + instance |= 0x04; + break; + default: + return -EINVAL; + } + + switch (ik_len_bytes) { + case 16: + break; + case 32: + instance |= 0x02; + break; + default: + return -EINVAL; + } + + switch (k_len_bytes) { + case 16: + break; + case 32: + instance |= 0x01; + break; + default: + return -EINVAL; + } + + tuak_core(buf, opc, instance, _rand, zero16, zero16, k, k_len_bytes, keccac_iterations); + + if (res) + memcpy_reverse(res, buf, res_len_bytes); + + if (ck) + memcpy_reverse(ck, buf + 32, ck_len_bytes); + + if (ik) + memcpy_reverse(ik, buf + 64, ik_len_bytes); + + if (ak) + memcpy_reverse(ak, buf + 96, 6); + + return 0; +} + +/** + * tuak_f5star - TUAK f5* algorithm + * @opc: OPc = 256-bit value derived from OP and K + * @k: K = 128/256-bit subscriber key + * @_rand: RAND = 128-bit random challenge + * @ak: Buffer for AK = 48-bit anonymity key (f5) + * Returns: 0 on success, -1 on failure + */ +int tuak_f5star(const uint8_t *opc, const uint8_t *k, uint8_t k_len_bytes, + const uint8_t *_rand, uint8_t *ak, unsigned int keccac_iterations) +{ + uint8_t buf[200]; + uint8_t instance = 0xc0; + + switch (k_len_bytes) { + case 16: + break; + case 32: + instance += 1; + break; + default: + return -EINVAL; + } + + tuak_core(buf, opc, instance, _rand, zero16, zero16, k, k_len_bytes, keccac_iterations); + + memcpy_reverse(ak, buf + 96, 6); + + return 0; +} + +/** + * tuak_generate - Generate AKA AUTN,IK,CK,RES + * @opc: OPc = 256-bit operator variant algorithm configuration field (encr.) + * @amf: AMF = 16-bit authentication management field + * @k: K = 128/256-bit subscriber key + * @sqn: SQN = 48-bit sequence number + * @_rand: RAND = 128-bit random challenge + * @autn: Buffer for AUTN = 128-bit authentication token + * @ik: Buffer for IK = 128/256-bit integrity key (f4), or %NULL + * @ck: Buffer for CK = 128/256-bit confidentiality key (f3), or %NULL + * @res: Buffer for RES = 32/64/128-bit signed response (f2), or %NULL + * @res_len: Max length for res; set to used length or 0 on failure + */ +void tuak_generate(const uint8_t *opc, const uint8_t *amf, const uint8_t *k, uint8_t k_len_bytes, + const uint8_t *sqn, const uint8_t *_rand, uint8_t *autn, uint8_t *ik, + uint8_t *ck, uint8_t *res, size_t *res_len) +{ + int i; + uint8_t mac_a[8], ak[6]; + + if (*res_len < 4) { + *res_len = 0; + return; + } + if (tuak_f1(opc, k, k_len_bytes, _rand, sqn, amf, mac_a, sizeof(mac_a), g_keccak_iterations) || + tuak_f2345(opc, k, k_len_bytes, _rand, res, *res_len, ck, 16, ik, 16, ak, g_keccak_iterations)) { + *res_len = 0; + return; + } + + /* AUTN = (SQN ^ AK) || AMF || MAC */ + for (i = 0; i < 6; i++) + autn[i] = sqn[i] ^ ak[i]; + memcpy(autn + 6, amf, 2); + memcpy(autn + 8, mac_a, 8); +} + + +/** + * tuak_auts - Milenage AUTS validation + * @opc: OPc = 256-bit operator variant algorithm configuration field (encr.) + * @k: K = 128/256-bit subscriber key + * @_rand: RAND = 128-bit random challenge + * @auts: AUTS = 112-bit authentication token from client + * @sqn: Buffer for SQN = 48-bit sequence number + * Returns: 0 = success (sqn filled), -1 on failure + */ +int tuak_auts(const uint8_t *opc, const uint8_t *k, uint8_t k_len_bytes, + const uint8_t *_rand, const uint8_t *auts, uint8_t *sqn) +{ + uint8_t amf[2] = { 0x00, 0x00 }; /* TS 33.102 v7.0.0, 6.3.3 */ + uint8_t ak[6], mac_s[8]; + int i; + + if (tuak_f5star(opc, k, k_len_bytes, _rand, ak, g_keccak_iterations)) + return -1; + for (i = 0; i < 6; i++) + sqn[i] = auts[i] ^ ak[i]; + if (tuak_f1star(opc, k, k_len_bytes, _rand, sqn, amf, mac_s, 8, g_keccak_iterations) || + memcmp(mac_s, auts + 6, 8) != 0) + return -1; + return 0; +} + +int tuak_opc_gen(uint8_t *opc, const uint8_t *k, uint8_t k_len_bytes, const uint8_t *op) +{ + uint8_t buf[200]; + uint8_t instance; + + switch (k_len_bytes) { + case 16: + instance = 0x00; + break; + case 32: + instance = 0x01; + break; + default: + return -EINVAL; + } + + tuak_core(buf, op, instance, zero16, zero16, zero16, k, k_len_bytes, g_keccak_iterations); + + memcpy_reverse(opc, buf, 32); + + return 0; +} diff --git a/src/gsm/tuak/tuak.h b/src/gsm/tuak/tuak.h new file mode 100644 index 00000000..1a808224 --- /dev/null +++ b/src/gsm/tuak/tuak.h @@ -0,0 +1,33 @@ +#pragma once +#include <stdint.h> + +/* low-level functions */ + +int tuak_f1(const uint8_t *opc, const uint8_t *k, uint8_t k_len_bytes, const uint8_t *_rand, + const uint8_t *sqn, const uint8_t *amf, uint8_t *mac_a, uint8_t mac_a_len_bytes, + unsigned int keccac_iterations); + +int tuak_f1star(const uint8_t *opc, const uint8_t *k, uint8_t k_len_bytes, const uint8_t *_rand, + const uint8_t *sqn, const uint8_t *amf, uint8_t *mac_s, uint8_t mac_s_len_bytes, + unsigned int keccac_iterations); + +int tuak_f2345(const uint8_t *opc, const uint8_t *k, uint8_t k_len_bytes, + const uint8_t *_rand, uint8_t *res, uint8_t res_len_bytes, + uint8_t *ck, uint8_t ck_len_bytes, + uint8_t *ik, uint8_t ik_len_bytes, uint8_t *ak, unsigned int keccac_iterations); + +int tuak_f5star(const uint8_t *opc, const uint8_t *k, uint8_t k_len_bytes, + const uint8_t *_rand, uint8_t *ak, unsigned int keccac_iterations); + +/* high-level API */ + +void tuak_set_keccak_iterations(unsigned int i); + +void tuak_generate(const uint8_t *opc, const uint8_t *amf, const uint8_t *k, uint8_t k_len_bytes, + const uint8_t *sqn, const uint8_t *_rand, uint8_t *autn, uint8_t *ik, + uint8_t *ck, uint8_t *res, size_t *res_len); + +int tuak_auts(const uint8_t *opc, const uint8_t *k, uint8_t k_len_bytes, + const uint8_t *_rand, const uint8_t *auts, uint8_t *sqn); + +int tuak_opc_gen(uint8_t *opc, const uint8_t *k, uint8_t k_len_bytes, const uint8_t *op); diff --git a/src/isdn/Makefile.am b/src/isdn/Makefile.am new file mode 100644 index 00000000..3e7f86eb --- /dev/null +++ b/src/isdn/Makefile.am @@ -0,0 +1,26 @@ +# This is _NOT_ the library release version, it's an API version. +# Please read chapter "Library interface versions" of the libtool documentation +# before making any modifications: https://www.gnu.org/software/libtool/manual/html_node/Versioning.html +LIBVERSION=1:0:1 + +AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_builddir)/include +AM_CFLAGS = -Wall $(TALLOC_CFLAGS) + +if ENABLE_PSEUDOTALLOC +AM_CPPFLAGS += -I$(top_srcdir)/src/pseudotalloc +endif + +noinst_LTLIBRARIES = libisdnint.la +lib_LTLIBRARIES = libosmoisdn.la + +libisdnint_la_SOURCES = i460_mux.c lapd_core.c v110.c v110_ta.c + +libisdnint_la_LDFLAGS = -no-undefined +libisdnint_la_LIBADD = $(top_builddir)/src/core/libosmocore.la + +libosmoisdn_la_SOURCES = +libosmoisdn_la_LDFLAGS = $(LTLDFLAGS_OSMOISDN) -version-info $(LIBVERSION) -no-undefined +libosmoisdn_la_LIBADD = libisdnint.la $(TALLOC_LIBS) + +EXTRA_DIST = libosmoisdn.map +EXTRA_libosmoisdn_la_DEPENDENCIES = libosmoisdn.map diff --git a/src/gsm/i460_mux.c b/src/isdn/i460_mux.c index 91ab2a19..b070bbdc 100644 --- a/src/gsm/i460_mux.c +++ b/src/isdn/i460_mux.c @@ -14,11 +14,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, - * MA 02110-1301, USA. */ #include <errno.h> @@ -26,10 +21,12 @@ #include <osmocom/core/bits.h> #include <osmocom/core/utils.h> #include <osmocom/core/msgb.h> -#include <osmocom/gsm/i460_mux.h> +#include <osmocom/isdn/i460_mux.h> -/* count the number of sub-channels in this I460 slot */ -static int osmo_i460_subchan_count(struct osmo_i460_timeslot *ts) +/*! count the number of sub-channels in this I.460 slot. + * \param[in] ts timeslot that holds the I.460 subchannels. + * \return number of subchannels. */ +int osmo_i460_subchan_count(struct osmo_i460_timeslot *ts) { int i, num_used = 0; @@ -124,10 +121,10 @@ static void demux_subchan_extract_bits(struct osmo_i460_subchan *schan, const ui } } -/*! Data from E1 timeslot into de-multiplexer - * \param[in] ts timeslot state - * \param[in] data input data bytes as received from E1/T1 - * \param[in] data_len length of data in bytes */ +/*! Feed multiplexed data (from an E1 timeslot) into de-multiplexer. + * \param[in] ts timeslot state. + * \param[in] data input data bytes as received from E1/T1. + * \param[in] data_len length of data in bytes. */ void osmo_i460_demux_in(struct osmo_i460_timeslot *ts, const uint8_t *data, size_t data_len) { struct osmo_i460_subchan *schan; @@ -200,7 +197,7 @@ static ubit_t mux_schan_provide_bit(struct osmo_i460_subchan *schan) /*! provide one byte with the subchan-specific bits of given sub-channel. * \param[in] schan sub-channel that is to provide bits - * \parma[out] mask bitmask of those bits filled in + * \param[out] mask bitmask of those bits filled in * \returns bits of given sub-channel */ static uint8_t mux_subchan_provide_bits(struct osmo_i460_subchan *schan, uint8_t *mask) { @@ -248,16 +245,14 @@ static uint8_t mux_subchan_provide_bits(struct osmo_i460_subchan *schan, uint8_t /* provide one byte of multiplexed I.460 bits */ static uint8_t mux_timeslot_provide_bits(struct osmo_i460_timeslot *ts) { - int i, count = 0; uint8_t ret = 0xff; /* unused bits must be '1' as per I.460 */ - for (i = 0; i < ARRAY_SIZE(ts->schan); i++) { + for (int i = 0; i < ARRAY_SIZE(ts->schan); i++) { struct osmo_i460_subchan *schan = &ts->schan[i]; uint8_t bits, mask; if (schan->rate == OSMO_I460_RATE_NONE) continue; - count++; bits = mux_subchan_provide_bits(schan, &mask); ret &= ~mask; ret |= bits; @@ -267,11 +262,10 @@ static uint8_t mux_timeslot_provide_bits(struct osmo_i460_timeslot *ts) } -/*! Data from E1 timeslot into de-multiplexer - * \param[in] ts timeslot state - * \param[out] out caller-provided buffer where to store generated output bytes - * \param[in] out_len number of bytes to be stored at out - */ +/*! Get multiplexed data from de-multiplexer (for feeding it into an E1 timeslot). + * \param[in] ts timeslot state. + * \param[out] out caller-provided buffer where to store generated output bytes. + * \param[in] out_len number of bytes to be stored at out. */ int osmo_i460_mux_out(struct osmo_i460_timeslot *ts, uint8_t *out, size_t out_len) { int i; diff --git a/src/gsm/lapd_core.c b/src/isdn/lapd_core.c index e0c232fe..b32ed263 100644 --- a/src/gsm/lapd_core.c +++ b/src/isdn/lapd_core.c @@ -18,10 +18,6 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ /*! \addtogroup lapd @@ -73,6 +69,7 @@ //#define TEST_CONTENT_RESOLUTION_NETWORK #include <stdio.h> +#include <stdbool.h> #include <stdint.h> #include <string.h> #include <errno.h> @@ -82,7 +79,7 @@ #include <osmocom/core/msgb.h> #include <osmocom/core/utils.h> #include <osmocom/core/talloc.h> -#include <osmocom/gsm/lapd_core.h> +#include <osmocom/isdn/lapd_core.h> #include <osmocom/gsm/rsl.h> /* TS 04.06 Table 4 / Section 3.8.1 */ @@ -111,7 +108,7 @@ static void lapd_t200_cb(void *data); static void lapd_t203_cb(void *data); -static int lapd_send_i(struct lapd_msg_ctx *lctx, int line); +static int lapd_send_i(struct lapd_datalink *dl, int line, bool rts); static int lapd_est_req(struct osmo_dlsap_prim *dp, struct lapd_msg_ctx *lctx); /* UTILITY FUNCTIONS */ @@ -172,12 +169,17 @@ static void lapd_dl_flush_hist(struct lapd_datalink *dl) } } -static void lapd_dl_flush_tx(struct lapd_datalink *dl) +static void lapd_dl_flush_tx_queue(struct lapd_datalink *dl) { struct msgb *msg; while ((msg = msgb_dequeue(&dl->tx_queue))) msgb_free(msg); +} + +static void lapd_dl_flush_tx(struct lapd_datalink *dl) +{ + lapd_dl_flush_tx_queue(dl); lapd_dl_flush_hist(dl); } @@ -202,11 +204,35 @@ static inline const char *lapd_state_name(enum lapd_state state) static void lapd_start_t200(struct lapd_datalink *dl) { - if (osmo_timer_pending(&dl->t200)) - return; - LOGDL(dl, LOGL_INFO, "start T200 (timeout=%d.%06ds)\n", - dl->t200_sec, dl->t200_usec); - osmo_timer_schedule(&dl->t200, dl->t200_sec, dl->t200_usec); + if ((dl->lapd_flags & LAPD_F_RTS)) { + if (dl->t200_rts != LAPD_T200_RTS_OFF) + return; + LOGDL(dl, LOGL_INFO, "Start T200. (pending until triggered by RTS)\n"); + dl->t200_rts = LAPD_T200_RTS_PENDING; + } else { + if (osmo_timer_pending(&dl->t200)) + return; + LOGDL(dl, LOGL_INFO, "Start T200 (timeout=%d.%06ds).\n", dl->t200_sec, dl->t200_usec); + osmo_timer_schedule(&dl->t200, dl->t200_sec, dl->t200_usec); + } +} + +/*! Handle timeout condition of T200 in RTS mode. + * The caller (LAPDm code) implements the T200 timer and must detect timeout condition. + * The function gets called by LAPDm code when it detects a timeout of T200. + * \param[in] dl caller-allocated datalink structure */ +int lapd_t200_timeout(struct lapd_datalink *dl) +{ + OSMO_ASSERT((dl->lapd_flags & LAPD_F_RTS)); + + if (dl->t200_rts != LAPD_T200_RTS_RUNNING) + return -EINVAL; + + dl->t200_rts = LAPD_T200_RTS_OFF; + + lapd_t200_cb(dl); + + return 0; } static void lapd_start_t203(struct lapd_datalink *dl) @@ -219,10 +245,24 @@ static void lapd_start_t203(struct lapd_datalink *dl) static void lapd_stop_t200(struct lapd_datalink *dl) { - if (!osmo_timer_pending(&dl->t200)) - return; + if ((dl->lapd_flags & LAPD_F_RTS)) { + if (dl->t200_rts == LAPD_T200_RTS_OFF) + return; + dl->t200_rts = LAPD_T200_RTS_OFF; + } else { + if (!osmo_timer_pending(&dl->t200)) + return; + osmo_timer_del(&dl->t200); + } LOGDL(dl, LOGL_INFO, "stop T200\n"); - osmo_timer_del(&dl->t200); +} + +static bool lapd_is_t200_started(struct lapd_datalink *dl) +{ + if ((dl->lapd_flags & LAPD_F_RTS)) + return (dl->t200_rts != LAPD_T200_RTS_OFF); + else + return osmo_timer_pending(&dl->t200); } static void lapd_stop_t203(struct lapd_datalink *dl) @@ -357,6 +397,21 @@ void lapd_dl_reset(struct lapd_datalink *dl) lapd_dl_newstate(dl, LAPD_STATE_IDLE); } +/*! Set lapd_flags to change behaviour + * \param[in] dl \ref lapd_datalink instance + * \param[in] flags \ref lapd_flags */ +int lapd_dl_set_flags(struct lapd_datalink *dl, unsigned int flags) +{ + if (lapd_is_t200_started(dl) && (flags & LAPD_F_RTS) != (dl->lapd_flags & LAPD_F_RTS)) { + LOGDL(dl, LOGL_ERROR, "Changing RTS flag not allowed while T200 is running.\n"); + return -EINVAL; + } + + dl->lapd_flags = flags; + + return 0; +} + /* reset and de-allocate history buffer */ void lapd_dl_exit(struct lapd_datalink *dl) { @@ -596,6 +651,8 @@ static void lapd_t200_cb(void *data) switch (dl->state) { case LAPD_STATE_SABM_SENT: /* 5.4.1.3 */ + /* increment re-transmission counter */ + dl->retrans_ctr++; if (dl->retrans_ctr >= dl->n200_est_rel + 1) { /* flush tx and send buffers */ lapd_dl_flush_tx(dl); @@ -614,13 +671,13 @@ static void lapd_t200_cb(void *data) } /* retransmit SABM command */ lapd_send_resend(dl); - /* increment re-transmission counter */ - dl->retrans_ctr++; /* restart T200 (PH-READY-TO-SEND) */ lapd_start_t200(dl); break; case LAPD_STATE_DISC_SENT: /* 5.4.4.3 */ + /* increment re-transmission counter */ + dl->retrans_ctr++; if (dl->retrans_ctr >= dl->n200_est_rel + 1) { /* send MDL ERROR INIDCATION to L3 */ mdl_error(MDL_CAUSE_T200_EXPIRED, &dl->lctx); @@ -638,8 +695,6 @@ static void lapd_t200_cb(void *data) } /* retransmit DISC command */ lapd_send_resend(dl); - /* increment re-transmission counter */ - dl->retrans_ctr++; /* restart T200 (PH-READY-TO-SEND) */ lapd_start_t200(dl); break; @@ -741,12 +796,14 @@ static void lapd_t203_cb(void *data) lapd_start_t200(dl); } -/* 5.5.3.1: Common function to acknowlege frames up to the given N(R) value */ -static void lapd_acknowledge(struct lapd_msg_ctx *lctx) +/* 5.5.3.1: Common function to acknowlege frames up to the given N(R) value + * In case of a sequence error, the cause is returned with negative sign. */ +static int lapd_acknowledge(struct lapd_msg_ctx *lctx) { struct lapd_datalink *dl = lctx->dl; uint8_t nr = lctx->n_recv; - int s = 0, rej = 0, t200_reset = 0; + int s = 0, rej = 0; + bool t200_reset = false; int i, h; /* supervisory frame ? */ @@ -771,9 +828,8 @@ static void lapd_acknowledge(struct lapd_msg_ctx *lctx) * link layer entity shall reset the timer T200 on * receipt of a valid I frame with N(R) higher than V(A), * or an REJ with an N(R) equal to V(A). */ - if ((!rej && nr != dl->v_ack) - || (rej && nr == dl->v_ack)) { - t200_reset = 1; + if ((!rej && nr != dl->v_ack) || (rej && nr == dl->v_ack)) { + t200_reset = true; lapd_stop_t200(dl); /* 5.5.3.1 Note 1 + 2 imply timer recovery cond. */ } @@ -781,10 +837,9 @@ static void lapd_acknowledge(struct lapd_msg_ctx *lctx) * N(R) is called valid, if and only if * (N(R)-V(A)) mod 8 <= (V(S)-V(A)) mod 8. */ - if (sub_mod(nr, dl->v_ack, dl->v_range) - > sub_mod(dl->v_send, dl->v_ack, dl->v_range)) { + if (sub_mod(nr, dl->v_ack, dl->v_range) > sub_mod(dl->v_send, dl->v_ack, dl->v_range)) { LOGDL(dl, LOGL_NOTICE, "N(R) sequence error\n"); - mdl_error(MDL_CAUSE_SEQ_ERR, lctx); + return -MDL_CAUSE_SEQ_ERR; } } @@ -805,11 +860,10 @@ static void lapd_acknowledge(struct lapd_msg_ctx *lctx) /* Stop T203, if running */ lapd_stop_t203(dl); /* Start T203, if T200 is not running in MF EST state, if enabled */ - if (!osmo_timer_pending(&dl->t200) - && (dl->t203_sec || dl->t203_usec) - && (dl->state == LAPD_STATE_MF_EST)) { + if (!lapd_is_t200_started(dl) && (dl->t203_sec || dl->t203_usec) && (dl->state == LAPD_STATE_MF_EST)) lapd_start_t203(dl); - } + + return 0; } /* L1 -> L2 */ @@ -946,7 +1000,7 @@ static int lapd_rx_u_sabm(struct msgb *msg, struct lapd_msg_ctx *lctx) dl->cont_res = lapd_msgb_alloc(length, "CONT RES"); memcpy(msgb_put(dl->cont_res, length), msg->l3h, length); - LOGDL(dl, LOGL_NOTICE, "Store content res.\n"); + LOGDL(dl, LOGL_INFO, "Store content res.\n"); } /* send notification to L3 */ if (length == 0) { @@ -1246,7 +1300,7 @@ static int lapd_rx_u_ua(struct msgb *msg, struct lapd_msg_ctx *lctx) /* enter multiple-frame-established state */ lapd_dl_newstate(dl, LAPD_STATE_MF_EST); /* send outstanding frames, if any (resume / reconnect) */ - lapd_send_i(lctx, __LINE__); + lapd_send_i(dl, __LINE__, false); /* send notification to L3 */ rc = send_dl_simple(PRIM_DL_EST, PRIM_OP_CONFIRM, lctx); msgb_free(msg); @@ -1369,7 +1423,7 @@ static int lapd_rx_s(struct msgb *msg, struct lapd_msg_ctx *lctx) lapd_dl_newstate(dl, LAPD_STATE_MF_EST); } /* Send message, if possible due to acknowledged data */ - lapd_send_i(lctx, __LINE__); + lapd_send_i(dl, __LINE__, false); break; case LAPD_S_RNR: @@ -1380,6 +1434,10 @@ static int lapd_rx_s(struct msgb *msg, struct lapd_msg_ctx *lctx) /* 5.5.5 */ /* Set peer receiver busy condition */ dl->peer_busy = 1; + /* Flush pending messages in TX queue. */ + lapd_dl_flush_tx_queue(dl); + /* stop Timer T200 */ + lapd_stop_t200(dl); if (lctx->p_f) { if (lctx->cr == dl->cr.rem2loc.cmd) { @@ -1406,7 +1464,7 @@ static int lapd_rx_s(struct msgb *msg, struct lapd_msg_ctx *lctx) LOGDL(dl, LOGL_INFO, "RNR not polling/final state received\n"); /* Send message, if possible due to acknowledged data */ - lapd_send_i(lctx, __LINE__); + lapd_send_i(dl, __LINE__, false); break; case LAPD_S_REJ: @@ -1420,6 +1478,8 @@ static int lapd_rx_s(struct msgb *msg, struct lapd_msg_ctx *lctx) dl->peer_busy = 0; /* V(S) and V(A) to the N(R) in the REJ frame */ dl->v_send = dl->v_ack = lctx->n_recv; + /* Flush pending messages in TX queue. */ + lapd_dl_flush_tx_queue(dl); /* stop Timer T200 */ lapd_stop_t200(dl); /* 5.5.3.2 */ @@ -1456,6 +1516,8 @@ static int lapd_rx_s(struct msgb *msg, struct lapd_msg_ctx *lctx) dl->peer_busy = 0; /* V(S) and V(A) to the N(R) in the REJ frame */ dl->v_send = dl->v_ack = lctx->n_recv; + /* Flush pending messages in TX queue. */ + lapd_dl_flush_tx_queue(dl); /* stop Timer T200 */ lapd_stop_t200(dl); /* 5.5.7 Clear timer recovery condition */ @@ -1465,6 +1527,8 @@ static int lapd_rx_s(struct msgb *msg, struct lapd_msg_ctx *lctx) dl->peer_busy = 0; /* V(S) and V(A) to the N(R) in the REJ frame */ dl->v_send = dl->v_ack = lctx->n_recv; + /* Flush pending messages in TX queue. */ + lapd_dl_flush_tx_queue(dl); /* 5.5.3.2 */ if (lctx->cr == dl->cr.rem2loc.cmd && lctx->p_f) { if (!dl->own_busy && !dl->seq_err_cond) { @@ -1491,8 +1555,8 @@ static int lapd_rx_s(struct msgb *msg, struct lapd_msg_ctx *lctx) /* FIXME: 5.5.4.2 2) */ - /* Send message, if possible due to acknowledged data */ - lapd_send_i(lctx, __LINE__); + /* Send message, if possible due to acknowledged data and new V(S) and V(A). */ + lapd_send_i(dl, __LINE__, false); break; default: @@ -1514,6 +1578,8 @@ static int lapd_rx_i(struct msgb *msg, struct lapd_msg_ctx *lctx) uint8_t ns = lctx->n_send; int length = lctx->length; int rc; + bool i_frame_in_queue = false; + int mdl_cause = 0; LOGDL(dl, LOGL_INFO, "I received in state %s on SAPI(%u)\n", lapd_state_name(dl->state), lctx->sapi); @@ -1571,9 +1637,8 @@ static int lapd_rx_i(struct msgb *msg, struct lapd_msg_ctx *lctx) ns, dl->v_recv, lapd_state_name(dl->state)); /* discard data */ msgb_free(msg); - if (dl->seq_err_cond != 1) { - /* FIXME: help me understand what exactly todo here - */ + /* Send reject, but suppress second reject if LAPD_F_DROP_2ND_REJ flag is set. */ + if (dl->seq_err_cond != 1 || !(dl->lapd_flags & LAPD_F_DROP_2ND_REJ)) { dl->seq_err_cond = 1; lapd_send_rej(lctx, lctx->p_f); } else { @@ -1591,10 +1656,12 @@ static int lapd_rx_i(struct msgb *msg, struct lapd_msg_ctx *lctx) } /* Even if N(s) sequence error, acknowledge to N(R)-1 */ /* 5.5.3.1: Acknowlege all transmitted frames up the N(R)-1 */ - lapd_acknowledge(lctx); /* V(A) is also set here */ + mdl_cause = lapd_acknowledge(lctx); /* V(A) is also set here */ + if (mdl_cause < 0) + mdl_error(-mdl_cause, lctx); /* Send message, if possible due to acknowledged data */ - lapd_send_i(lctx, __LINE__); + lapd_send_i(dl, __LINE__, false); return 0; } @@ -1604,8 +1671,15 @@ static int lapd_rx_i(struct msgb *msg, struct lapd_msg_ctx *lctx) dl->v_recv = inc_mod(dl->v_recv, dl->v_range); LOGDL(dl, LOGL_INFO, "incrementing V(R) to %u\n", dl->v_recv); + /* Update all pending frames in the queue to the new V(R) state. */ + if (dl->update_pending_frames) { + rc = dl->update_pending_frames(lctx); + if (!rc) + i_frame_in_queue = true; + } + /* 5.5.3.1: Acknowlege all transmitted frames up the the N(R)-1 */ - lapd_acknowledge(lctx); /* V(A) is also set here */ + mdl_cause = lapd_acknowledge(lctx); /* V(A) is also set here */ /* Only if we are not in own receiver busy condition */ if (!dl->own_busy) { @@ -1650,6 +1724,10 @@ static int lapd_rx_i(struct msgb *msg, struct lapd_msg_ctx *lctx) } else LOGDL(dl, LOGL_INFO, "I frame ignored during own receiver busy condition\n"); + /* Indicate sequence error, if exists. */ + if (mdl_cause < 0) + mdl_error(-mdl_cause, lctx); + /* Check for P bit */ if (lctx->p_f) { /* 5.5.2.1 */ @@ -1668,14 +1746,8 @@ static int lapd_rx_i(struct msgb *msg, struct lapd_msg_ctx *lctx) /* check if we are not in own receiver busy */ if (!dl->own_busy) { /* NOTE: V(R) is already set above */ - rc = lapd_send_i(lctx, __LINE__); - - /* if update_pending_iframe returns 0 it updated - * the lapd header of an iframe in the tx queue */ - if (rc && dl->update_pending_frames) - rc = dl->update_pending_frames(lctx); - - if (rc) { + rc = lapd_send_i(dl, __LINE__, false); + if (rc && !i_frame_in_queue) { LOGDL(dl, LOGL_INFO, "we are not busy and have no pending data, " "send RR\n"); /* Send RR with F=0 */ @@ -1691,7 +1763,7 @@ static int lapd_rx_i(struct msgb *msg, struct lapd_msg_ctx *lctx) } /* Send message, if possible due to acknowledged data */ - lapd_send_i(lctx, __LINE__); + lapd_send_i(dl, __LINE__, false); return rc; } @@ -1712,13 +1784,38 @@ int lapd_ph_data_ind(struct msgb *msg, struct lapd_msg_ctx *lctx) rc = lapd_rx_i(msg, lctx); break; default: - LOGDL(lctx->dl, LOGL_NOTICE, "unknown LAPD format\n"); + LOGDL(lctx->dl, LOGL_NOTICE, "unknown LAPD format 0x%02x\n", lctx->format); msgb_free(msg); rc = -EINVAL; } return rc; } +/*! Enqueue next LAPD frame and run pending T200. (Must be called when frame is ready to send.) + * The caller (LAPDm code) calls this function before it sends the next frame. + * If there is no frame in the TX queue, LAPD will enqueue next I-frame, if possible. + * If the T200 is pending, it is changed to running state. + * \param[in] lctx LAPD context + * \param[out] rc set to 1, if timer T200 state changed to running, set to 0, if not. */ +int lapd_ph_rts_ind(struct lapd_msg_ctx *lctx) +{ + struct lapd_datalink *dl = lctx->dl; + + /* If there is no pending frame, try to enqueue next I frame. */ + if (llist_empty(&dl->tx_queue) && (dl->state == LAPD_STATE_MF_EST || dl->state == LAPD_STATE_TIMER_RECOV)) { + /* Send an I frame, if there are pending outgoing messages. */ + lapd_send_i(dl, __LINE__, true); + } + + /* Run T200 at RTS, if pending. Tell caller that is has been started. (rc = 1) */ + if (dl->t200_rts == LAPD_T200_RTS_PENDING) { + dl->t200_rts = LAPD_T200_RTS_RUNNING; + return 1; + } + + return 0; +} + /* L3 -> L2 */ /* send unit data */ @@ -1742,6 +1839,20 @@ static int lapd_udata_req(struct osmo_dlsap_prim *dp, struct lapd_msg_ctx *lctx) return dl->send_ph_data_req(&nctx, msg); } +static void msg_to_tx_hist(struct lapd_history *tx_hist, const struct msgb *msg, int length, int more) +{ + tx_hist->msg = lapd_msgb_alloc(msg->len, "HIST"); + tx_hist->more = more; + msgb_put(tx_hist->msg, msg->len); + if (length) + memcpy(tx_hist->msg->data, msg->l3h, msg->len); +} + +static void msg_to_tx_hist0(struct lapd_datalink *dl, const struct msgb *msg) +{ + return msg_to_tx_hist(&dl->tx_hist[0], msg, msg->len, 0); +} + /* request link establishment */ static int lapd_est_req(struct osmo_dlsap_prim *dp, struct lapd_msg_ctx *lctx) { @@ -1780,11 +1891,8 @@ static int lapd_est_req(struct osmo_dlsap_prim *dp, struct lapd_msg_ctx *lctx) nctx.more = 0; /* Transmit-buffer carries exactly one segment */ - dl->tx_hist[0].msg = lapd_msgb_alloc(msg->len, "HIST"); - msgb_put(dl->tx_hist[0].msg, msg->len); - if (msg->len) - memcpy(dl->tx_hist[0].msg->data, msg->l3h, msg->len); - dl->tx_hist[0].more = 0; + msg_to_tx_hist0(dl, msg); + /* set Vs to 0, because it is used as index when resending SABM */ dl->v_send = 0; @@ -1818,15 +1926,15 @@ static int lapd_data_req(struct osmo_dlsap_prim *dp, struct lapd_msg_ctx *lctx) msgb_enqueue(&dl->send_queue, msg); /* Send message, if possible */ - lapd_send_i(&dl->lctx, __LINE__); + lapd_send_i(dl, __LINE__, false); return 0; } /* Send next I frame from queued/buffered data */ -static int lapd_send_i(struct lapd_msg_ctx *lctx, int line) +static int lapd_send_i(struct lapd_datalink *dl, int line, bool rts) { - struct lapd_datalink *dl = lctx->dl; + struct lapd_msg_ctx *lctx = &dl->lctx; uint8_t k = dl->k; uint8_t h; struct msgb *msg; @@ -1834,18 +1942,26 @@ static int lapd_send_i(struct lapd_msg_ctx *lctx, int line) int rc = - 1; /* we sent nothing */ struct lapd_msg_ctx nctx; + if (!rts) + LOGDL(dl, LOGL_INFO, "%s() called from line %d\n", __func__, line); - LOGDL(dl, LOGL_INFO, "%s() called from line %d\n", __func__, line); + if ((dl->lapd_flags & LAPD_F_RTS) && !llist_empty(&dl->tx_queue)) { + if (!rts) + LOGDL(dl, LOGL_INFO, "There is a frame in the TX queue, not checking for sending I frame.\n"); + return rc; + } next_frame: if (dl->peer_busy) { - LOGDL(dl, LOGL_INFO, "peer busy, not sending\n"); + if (!rts) + LOGDL(dl, LOGL_INFO, "Peer busy, not sending.\n"); return rc; } if (dl->state == LAPD_STATE_TIMER_RECOV) { - LOGDL(dl, LOGL_INFO, "timer recovery, not sending\n"); + if (!rts) + LOGDL(dl, LOGL_INFO, "Timer recovery, not sending.\n"); return rc; } @@ -1856,8 +1972,9 @@ static int lapd_send_i(struct lapd_msg_ctx *lctx, int line) * of the error recovery procedures as described in subclauses 5.5.4 and * 5.5.7. */ if (dl->v_send == add_mod(dl->v_ack, k, dl->v_range)) { - LOGDL(dl, LOGL_INFO, "k frames outstanding, not sending more " - "(k=%u V(S)=%u V(A)=%u)\n", k, dl->v_send, dl->v_ack); + if (!rts) + LOGDL(dl, LOGL_INFO, "k frames outstanding, not sending more. (k=%u V(S)=%u V(A)=%u)\n", + k, dl->v_send, dl->v_ack); return rc; } @@ -1899,7 +2016,7 @@ static int lapd_send_i(struct lapd_msg_ctx *lctx, int line) msg = lapd_msgb_alloc(length, "LAPD I"); msg->l3h = msgb_put(msg, length); /* assemble message */ - memcpy(&nctx, &dl->lctx, sizeof(nctx)); + memcpy(&nctx, lctx, sizeof(nctx)); /* keep nctx.ldp */ /* keep nctx.sapi */ /* keep nctx.tei */ @@ -1917,11 +2034,8 @@ static int lapd_send_i(struct lapd_msg_ctx *lctx, int line) memcpy(msg->l3h, dl->send_buffer->l3h + dl->send_out, length); /* store in tx_hist */ - dl->tx_hist[h].msg = lapd_msgb_alloc(msg->len, "HIST"); - msgb_put(dl->tx_hist[h].msg, msg->len); - if (length) - memcpy(dl->tx_hist[h].msg->data, msg->l3h, msg->len); - dl->tx_hist[h].more = nctx.more; + msg_to_tx_hist(&dl->tx_hist[h], msg, length, nctx.more); + /* Add length to track how much is already in the tx buffer */ dl->send_out += length; } else { @@ -1932,7 +2046,7 @@ static int lapd_send_i(struct lapd_msg_ctx *lctx, int line) msg = lapd_msgb_alloc(length, "LAPD I resend"); msg->l3h = msgb_put(msg, length); /* assemble message */ - memcpy(&nctx, &dl->lctx, sizeof(nctx)); + memcpy(&nctx, lctx, sizeof(nctx)); /* keep nctx.ldp */ /* keep nctx.sapi */ /* keep nctx.tei */ @@ -1954,7 +2068,7 @@ static int lapd_send_i(struct lapd_msg_ctx *lctx, int line) /* If timer T200 is not running at the time right before transmitting a * frame, when the PH-READY-TO-SEND primitive is received from the * physical layer., it shall be set. */ - if (!osmo_timer_pending(&dl->t200)) { + if (!lapd_is_t200_started(dl)) { /* stop Timer T203, if running */ lapd_stop_t203(dl); /* start Timer T200 */ @@ -1963,7 +2077,11 @@ static int lapd_send_i(struct lapd_msg_ctx *lctx, int line) dl->send_ph_data_req(&nctx, msg); - rc = 0; /* we sent something */ + /* When using RTS, we send only one frame. */ + if ((dl->lapd_flags & LAPD_F_RTS)) + return 0; + + rc = 0; /* We sent an I frame, so sending RR frame is not required. */ goto next_frame; } @@ -2042,11 +2160,8 @@ static int lapd_res_req(struct osmo_dlsap_prim *dp, struct lapd_msg_ctx *lctx) nctx.length = 0; nctx.more = 0; - dl->tx_hist[0].msg = lapd_msgb_alloc(msg->len, "HIST"); - msgb_put(dl->tx_hist[0].msg, msg->len); - if (msg->len) - memcpy(dl->tx_hist[0].msg->data, msg->l3h, msg->len); - dl->tx_hist[0].more = 0; + msg_to_tx_hist0(dl, msg); + /* set Vs to 0, because it is used as index when resending SABM */ dl->v_send = 0; @@ -2106,11 +2221,8 @@ static int lapd_rel_req(struct osmo_dlsap_prim *dp, struct lapd_msg_ctx *lctx) nctx.length = 0; nctx.more = 0; - dl->tx_hist[0].msg = lapd_msgb_alloc(msg->len, "HIST"); - msgb_put(dl->tx_hist[0].msg, msg->len); - if (msg->len) - memcpy(dl->tx_hist[0].msg->data, msg->l3h, msg->len); - dl->tx_hist[0].more = 0; + msg_to_tx_hist0(dl, msg); + /* set Vs to 0, because it is used as index when resending DISC */ dl->v_send = 0; diff --git a/src/isdn/libosmoisdn.map b/src/isdn/libosmoisdn.map new file mode 100644 index 00000000..8f34f0d8 --- /dev/null +++ b/src/isdn/libosmoisdn.map @@ -0,0 +1,52 @@ +LIBOSMOISDN_1.0 { +global: + +tall_lapd_ctx; +lapd_dl_exit; +lapd_dl_init; +lapd_dl_init2; +lapd_dl_set_name; +lapd_dl_reset; +lapd_dl_set_flags; +lapd_msgb_alloc; +lapd_ph_data_ind; +lapd_ph_rts_ind; +lapd_recv_dlsap; +lapd_set_mode; +lapd_state_names; +lapd_t200_timeout; + +osmo_i460_demux_in; +osmo_i460_mux_enqueue; +osmo_i460_mux_out; +osmo_i460_subchan_add; +osmo_i460_subchan_del; +osmo_i460_subchan_count; +osmo_i460_ts_init; + +osmo_v110_decode_frame; +osmo_v110_encode_frame; +osmo_v110_ubit_dump; +osmo_v110_e1e2e3; +osmo_v110_sync_ra1_get_user_data_chunk_bitlen; +osmo_v110_sync_ra1_get_user_data_rate; +osmo_v110_sync_ra1_get_intermediate_rate; +osmo_v110_sync_ra1_user_to_ir; +osmo_v110_sync_ra1_ir_to_user; + +osmo_v110_ta_alloc; +osmo_v110_ta_free; +osmo_v110_ta_set_timer_val_ms; +osmo_v110_ta_frame_in; +osmo_v110_ta_frame_out; +osmo_v110_ta_sync_ind; +osmo_v110_ta_desync_ind; +osmo_v110_ta_get_status; +osmo_v110_ta_get_circuit; +osmo_v110_ta_set_circuit; + +osmo_v110_ta_circuit_names; +osmo_v110_ta_circuit_descs; + +local: *; +}; diff --git a/src/isdn/v110.c b/src/isdn/v110.c new file mode 100644 index 00000000..b4bc4e30 --- /dev/null +++ b/src/isdn/v110.c @@ -0,0 +1,590 @@ +/* V.110 frames according to ITU-T V.110 + * + * This code implements the following functionality: + * - parsing/encoding of osmo_v110_decoded_frame from/to actual 80-bit V.110 frame + * - synchronous rate adapting of user bit rate to V.110 D-bits as per Table 6 + * + * It is (at least initially) a very "naive" implementation, as it first and foremost + * aims to be functional and correct, rather than efficient in any way. Hence it + * operates on unpacked bits (ubit_t, 1 bit per byte), and has various intermediate + * representations and indirect function calls. If needed, a more optimized variant + * can always be developed later on. + */ + +/* (C) 2022 by Harald Welte <laforge@osmocom.org> + * + * SPDX-License-Identifier: GPL-2.0+ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <stdint.h> +#include <errno.h> + +#include <osmocom/core/bits.h> + +#include <osmocom/isdn/v110.h> + +/************************************************************************* + * V.110 frame decoding/encoding (ubits <-> struct with D/S/X/E bits) + *************************************************************************/ + +/*! Decode a 80-bit V.110 frame present as 80 ubits into a struct osmo_v110_decoded_frame. + * \param[out] fr caller-allocated output data structure, filled by this function + * \param[in] ra_bits One V.110 frame as 80 unpacked bits. + * \param[in] n_bits number of unpacked bits provided in ra_bits + * \returns 0 in case of success; negative on error. */ +int osmo_v110_decode_frame(struct osmo_v110_decoded_frame *fr, const ubit_t *ra_bits, size_t n_bits) +{ + if (n_bits < 80) + return -EINVAL; + + /* X1 .. X2 */ + fr->x_bits[0] = ra_bits[2 * 8 + 7]; + fr->x_bits[1] = ra_bits[7 * 8 + 7]; + + /* S1, S3, S4, S6, S8, S9 */ + fr->s_bits[0] = ra_bits[1 * 8 + 7]; + fr->s_bits[2] = ra_bits[3 * 8 + 7]; + fr->s_bits[3] = ra_bits[4 * 8 + 7]; + fr->s_bits[5] = ra_bits[6 * 8 + 7]; + fr->s_bits[7] = ra_bits[8 * 8 + 7]; + fr->s_bits[8] = ra_bits[9 * 8 + 7]; + + /* E1 .. E7 */ + memcpy(fr->e_bits, ra_bits + 5 * 8 + 1, 7); + + /* D-bits */ + memcpy(fr->d_bits + 0 * 6, ra_bits + 1 * 8 + 1, 6); + memcpy(fr->d_bits + 1 * 6, ra_bits + 2 * 8 + 1, 6); + memcpy(fr->d_bits + 2 * 6, ra_bits + 3 * 8 + 1, 6); + memcpy(fr->d_bits + 3 * 6, ra_bits + 4 * 8 + 1, 6); + + memcpy(fr->d_bits + 4 * 6, ra_bits + 6 * 8 + 1, 6); + memcpy(fr->d_bits + 5 * 6, ra_bits + 7 * 8 + 1, 6); + memcpy(fr->d_bits + 6 * 6, ra_bits + 8 * 8 + 1, 6); + memcpy(fr->d_bits + 7 * 6, ra_bits + 9 * 8 + 1, 6); + + return 0; +} + +/*! Encode a struct osmo_v110_decoded_frame into an 80-bit V.110 frame as ubits. + * \param[out] ra_bits caller-provided output buffer at leat 80 ubits large + * \param[in] n_bits length of ra_bits. Must be at least 80. + * \param[in] input data structure + * \returns number of bits written to ra_bits */ +int osmo_v110_encode_frame(ubit_t *ra_bits, size_t n_bits, const struct osmo_v110_decoded_frame *fr) +{ + if (n_bits < 80) + return -ENOSPC; + + /* alignment pattern */ + memset(ra_bits+0, 0, 8); + for (int i = 1; i < 10; i++) + ra_bits[i*8] = 1; + + /* X1 .. X2 */ + ra_bits[2 * 8 + 7] = fr->x_bits[0]; + ra_bits[7 * 8 + 7] = fr->x_bits[1]; + + /* S1, S3, S4, S6, S8, S9 */ + ra_bits[1 * 8 + 7] = fr->s_bits[0]; + ra_bits[3 * 8 + 7] = fr->s_bits[2]; + ra_bits[4 * 8 + 7] = fr->s_bits[3]; + ra_bits[6 * 8 + 7] = fr->s_bits[5]; + ra_bits[8 * 8 + 7] = fr->s_bits[7]; + ra_bits[9 * 8 + 7] = fr->s_bits[8]; + + /* E1 .. E7 */ + memcpy(ra_bits + 5 * 8 + 1, fr->e_bits, 7); + + /* D-bits */ + memcpy(ra_bits + 1 * 8 + 1, fr->d_bits + 0 * 6, 6); + memcpy(ra_bits + 2 * 8 + 1, fr->d_bits + 1 * 6, 6); + memcpy(ra_bits + 3 * 8 + 1, fr->d_bits + 2 * 6, 6); + memcpy(ra_bits + 4 * 8 + 1, fr->d_bits + 3 * 6, 6); + + memcpy(ra_bits + 6 * 8 + 1, fr->d_bits + 4 * 6, 6); + memcpy(ra_bits + 7 * 8 + 1, fr->d_bits + 5 * 6, 6); + memcpy(ra_bits + 8 * 8 + 1, fr->d_bits + 6 * 6, 6); + memcpy(ra_bits + 9 * 8 + 1, fr->d_bits + 7 * 6, 6); + + return 10 * 8; +} + +/*! Print a encoded V.110 frame in the same table-like structure as the spec. + * \param outf output FILE stream to which to dump + * \param[in] fr unpacked bits to dump + * \param[in] in_len length of unpacked bits available at fr. */ +void osmo_v110_ubit_dump(FILE *outf, const ubit_t *fr, size_t in_len) +{ + if (in_len < 80) + fprintf(outf, "short input data\n"); + + for (unsigned int octet = 0; octet < 10; octet++) { + fprintf(outf, "%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\n", + fr[octet * 8 + 0], fr[octet * 8 + 1], fr[octet * 8 + 2], fr[octet * 8 + 3], + fr[octet * 8 + 4], fr[octet * 8 + 5], fr[octet * 8 + 6], fr[octet * 8 + 7]); + } +} + +/************************************************************************* + * RA1 synchronous rate adaptation + *************************************************************************/ + +/* I actually couldn't find any reference as to the value of F(ill) bits */ +#define F 1 + +/*! E1/E2/E3 bit values as per Table 5/V.110 */ +const ubit_t osmo_v110_e1e2e3[_NUM_OSMO_V110_SYNC_RA1][3] = { + [OSMO_V110_SYNC_RA1_600] = { 1, 0, 0 }, + [OSMO_V110_SYNC_RA1_1200] = { 0, 1, 0 }, + [OSMO_V110_SYNC_RA1_2400] = { 1, 1, 0 }, + [OSMO_V110_SYNC_RA1_4800] = { 0, 1, 1 }, + [OSMO_V110_SYNC_RA1_7200] = { 1, 0, 1 }, + [OSMO_V110_SYNC_RA1_9600] = { 0, 1, 1 }, + [OSMO_V110_SYNC_RA1_12000] = { 0, 0, 1 }, + [OSMO_V110_SYNC_RA1_14400] = { 1, 0, 1 }, + [OSMO_V110_SYNC_RA1_19200] = { 0, 1, 1 }, + [OSMO_V110_SYNC_RA1_24000] = { 0, 0, 1 }, + [OSMO_V110_SYNC_RA1_28800] = { 1, 0, 1 }, + [OSMO_V110_SYNC_RA1_38400] = { 0, 1, 1 }, +}; + +/*! Adapt from 6 synchronous 600bit/s input bits to a decoded V.110 frame. + * \param[out] fr caller-allocated output frame to which E+D bits are stored + * \param[in] d_in input user bits + * \param[in] in_len number of bits in d_in. Must be 6. + * \returns 0 on success; negative in case of error. */ +static int v110_adapt_600_to_IR8000(struct osmo_v110_decoded_frame *fr, const ubit_t *d_in, size_t in_len) +{ + if (in_len != 6) + return -EINVAL; + + /* Table 6a / V.110 */ + osmo_v110_e1e2e3_set(fr->e_bits, OSMO_V110_SYNC_RA1_600); + for (int i = 0; i < 6; i++) + memset(fr->d_bits + i*8, d_in[i], 8); + + return 0; +} + +static int v110_adapt_IR8000_to_600(ubit_t *d_out, size_t out_len, const struct osmo_v110_decoded_frame *fr) +{ + if (out_len < 6) + return -ENOSPC; + + if (osmo_v110_e1e2e3_cmp(fr->e_bits, OSMO_V110_SYNC_RA1_600)) + return -EINVAL; + + for (int i = 0; i < 6; i++) { + /* we only use one of the bits, not some kind of consistency check or majority vote */ + d_out[i] = fr->d_bits[i*8]; + } + + return 6; +} + +/*! Adapt from 12 synchronous 1200bit/s input bits to a decoded V.110 frame. + * \param[out] fr caller-allocated output frame to which E+D bits are stored + * \param[in] d_in input user bits + * \param[in] in_len number of bits in d_in. Must be 12. + * \returns 0 on success; negative in case of error. */ +static int v110_adapt_1200_to_IR8000(struct osmo_v110_decoded_frame *fr, const ubit_t *d_in, size_t in_len) +{ + if (in_len != 12) + return -EINVAL; + + /* Table 6b / V.110 */ + osmo_v110_e1e2e3_set(fr->e_bits, OSMO_V110_SYNC_RA1_1200); + for (int i = 0; i < 12; i++) + memset(fr->d_bits + i*4, d_in[i], 4); + + return 0; +} + +static int v110_adapt_IR8000_to_1200(ubit_t *d_out, size_t out_len, const struct osmo_v110_decoded_frame *fr) +{ + if (out_len < 12) + return -ENOSPC; + + if (osmo_v110_e1e2e3_cmp(fr->e_bits, OSMO_V110_SYNC_RA1_1200)) + return -EINVAL; + + for (int i = 0; i < 12; i++) { + /* we only use one of the bits, not some kind of consistency check or majority vote */ + d_out[i] = fr->d_bits[i*4]; + } + + return 12; +} + +/*! Adapt from 24 synchronous 2400bit/s input bits to a decoded V.110 frame. + * \param[out] fr caller-allocated output frame to which E+D bits are stored + * \param[in] d_in input user bits + * \param[in] in_len number of bits in d_in. Must be 24. + * \returns 0 on success; negative in case of error. */ +static int v110_adapt_2400_to_IR8000(struct osmo_v110_decoded_frame *fr, const ubit_t *d_in, size_t in_len) +{ + if (in_len != 24) + return -EINVAL; + + /* Table 6c / V.110 */ + osmo_v110_e1e2e3_set(fr->e_bits, OSMO_V110_SYNC_RA1_2400); + for (int i = 0; i < 24; i++) { + fr->d_bits[i*2 + 0] = d_in[i]; + fr->d_bits[i*2 + 1] = d_in[i]; + } + + return 0; +} + +static int v110_adapt_IR8000_to_2400(ubit_t *d_out, size_t out_len, const struct osmo_v110_decoded_frame *fr) +{ + if (out_len < 24) + return -ENOSPC; + + /* Table 6c / V.110 */ + if (osmo_v110_e1e2e3_cmp(fr->e_bits, OSMO_V110_SYNC_RA1_2400)) + return -EINVAL; + + for (int i = 0; i < 24; i++) { + /* we only use one of the bits, not some kind of consistency check or majority vote */ + d_out[i] = fr->d_bits[i*2]; + } + + return 24; +} + +/*! Adapt from 36 synchronous N x 3600bit/s input bits to a decoded V.110 frame. + * \param[out] fr caller-allocated output frame to which E+D bits are stored + * \param[in] d_in input user bits + * \param[in] in_len number of bits in d_in. Must be 36. + * \returns 0 on success; negative in case of error. */ +static int v110_adapt_Nx3600_to_IR(struct osmo_v110_decoded_frame *fr, const ubit_t *d_in, size_t in_len) +{ + int d_idx = 0; + + if (in_len != 36) + return -EINVAL; + + /* Table 6d / V.110 (7200 is one of Nx3600) */ + osmo_v110_e1e2e3_set(fr->e_bits, OSMO_V110_SYNC_RA1_7200); + + memcpy(fr->d_bits + d_idx, d_in + 0, 10); d_idx += 10; /* D1..D10 */ + memset(fr->d_bits + d_idx, F, 2); d_idx += 2; + memcpy(fr->d_bits + d_idx, d_in + 10, 2); d_idx += 2; /* D11..D12 */ + memset(fr->d_bits + d_idx, F, 2); d_idx += 2; + memcpy(fr->d_bits + d_idx, d_in + 12, 2); d_idx += 2; /* D13..D14 */ + memset(fr->d_bits + d_idx, F, 2); d_idx += 2; + memcpy(fr->d_bits + d_idx, d_in + 14, 14); d_idx += 14; /* D15..D28 */ + memset(fr->d_bits + d_idx, F, 2); d_idx += 2; + memcpy(fr->d_bits + d_idx, d_in + 28, 2); d_idx += 2; /* D29..D30 */ + memset(fr->d_bits + d_idx, F, 2); d_idx += 2; + memcpy(fr->d_bits + d_idx, d_in + 30, 2); d_idx += 2; /* D31..D32 */ + memset(fr->d_bits + d_idx, F, 2); d_idx += 2; + memcpy(fr->d_bits + d_idx, d_in + 32, 4); d_idx += 4; /* D33..D36 */ + + OSMO_ASSERT(d_idx == 48); + + return 0; +} + +static int v110_adapt_IR_to_Nx3600(ubit_t *d_out, size_t out_len, const struct osmo_v110_decoded_frame *fr) +{ + int d_idx = 0; + + if (out_len < 36) + return -ENOSPC; + + /* Table 6d / V.110 (7200 is one of Nx3600) */ + if (osmo_v110_e1e2e3_cmp(fr->e_bits, OSMO_V110_SYNC_RA1_7200)) + return -EINVAL; + + memcpy(d_out + 0, fr->d_bits + d_idx, 10); d_idx += 10; /* D1..D10 */ + d_idx += 2; + memcpy(d_out + 10, fr->d_bits + d_idx, 2); d_idx += 2; /* D11..D12 */ + d_idx += 2; + memcpy(d_out + 12, fr->d_bits + d_idx, 2); d_idx += 2; /* D13..D14 */ + d_idx += 2; + memcpy(d_out + 14, fr->d_bits + d_idx, 14); d_idx += 14;/* D15..D28 */ + d_idx += 2; + memcpy(d_out + 28, fr->d_bits + d_idx, 2); d_idx += 2; /* D29..D30 */ + d_idx += 2; + memcpy(d_out + 30, fr->d_bits + d_idx, 2); d_idx += 2; /* D31..D32 */ + d_idx += 2; + memcpy(d_out + 32, fr->d_bits + d_idx, 4); d_idx += 4; /* D33..D36 */ + + OSMO_ASSERT(d_idx == 48); + + return 36; +} + + +/*! Adapt from 48 synchronous N x 4800bit/s input bits to a decoded V.110 frame. + * \param[out] fr caller-allocated output frame to which E+D bits are stored + * \param[in] d_in input user bits + * \param[in] in_len number of bits in d_in. Must be 48. + * \returns 0 on success; negative in case of error. */ +static int v110_adapt_Nx4800_to_IR(struct osmo_v110_decoded_frame *fr, const ubit_t *d_in, size_t in_len) +{ + if (in_len != 48) + return -EINVAL; + + /* Table 6e / V.110 (4800 is one of Nx4800) */ + osmo_v110_e1e2e3_set(fr->e_bits, OSMO_V110_SYNC_RA1_4800); + + memcpy(fr->d_bits, d_in, 48); + + return 0; +} + +static int v110_adapt_IR_to_Nx4800(ubit_t *d_out, size_t out_len, const struct osmo_v110_decoded_frame *fr) +{ + if (out_len < 48) + return -ENOSPC; + + /* Table 6e / V.110 (4800 is one of Nx4800) */ + if (osmo_v110_e1e2e3_cmp(fr->e_bits, OSMO_V110_SYNC_RA1_4800)) + return -EINVAL; + + memcpy(d_out, fr->d_bits, 48); + + return 48; +} + +/*! Adapt from 30 synchronous N x 12000bit/s input bits to a decoded V.110 frame. + * \param[out] fr caller-allocated output frame to which E+D bits are stored + * \param[in] d_in input user bits + * \param[in] in_len number of bits in d_in. Must be 30. + * \returns 0 on success; negative in case of error. */ +static int v110_adapt_Nx12000_to_IR(struct osmo_v110_decoded_frame *fr, const ubit_t *d_in, size_t in_len) +{ + int d_idx = 0; + + if (in_len != 30) + return -EINVAL; + + /* Table 6f / V.110 (12000 is one of Nx12000) */ + osmo_v110_e1e2e3_set(fr->e_bits, OSMO_V110_SYNC_RA1_12000); + + memcpy(fr->d_bits + d_idx, d_in + 0, 10); d_idx += 10; /* D1..D10 */ + memset(fr->d_bits + d_idx, F, 2); d_idx += 2; + memcpy(fr->d_bits + d_idx, d_in + 10, 2); d_idx += 2; /* D11..D12 */ + memset(fr->d_bits + d_idx, F, 2); d_idx += 2; + memcpy(fr->d_bits + d_idx, d_in + 12, 2); d_idx += 2; /* D13..D14 */ + memset(fr->d_bits + d_idx, F, 2); d_idx += 2; + fr->d_bits[d_idx++] = d_in[14]; /* D15 */ + memset(fr->d_bits + d_idx, F, 3); d_idx += 3; + memcpy(fr->d_bits + d_idx, d_in + 15, 10); d_idx += 10; /* D16..D25 */ + memset(fr->d_bits + d_idx, F, 2); d_idx += 2; + memcpy(fr->d_bits + d_idx, d_in + 25, 2); d_idx += 2; /* D26..D27 */ + memset(fr->d_bits + d_idx, F, 2); d_idx += 2; + memcpy(fr->d_bits + d_idx, d_in + 27, 2); d_idx += 2; /* D28..D29 */ + memset(fr->d_bits + d_idx, F, 2); d_idx += 2; + fr->d_bits[d_idx++] = d_in[29]; /* D30 */ + memset(fr->d_bits + d_idx, F, 3); d_idx += 3; + + OSMO_ASSERT(d_idx == 48); + + return 0; +} + +static int v110_adapt_IR_to_Nx12000(ubit_t *d_out, size_t out_len, const struct osmo_v110_decoded_frame *fr) +{ + int d_idx = 0; + + if (out_len < 30) + return -ENOSPC; + + /* Table 6f / V.110 (12000 is one of Nx12000) */ + if (osmo_v110_e1e2e3_cmp(fr->e_bits, OSMO_V110_SYNC_RA1_12000)) + return -EINVAL; + + memcpy(d_out + 0, fr->d_bits + d_idx, 10); d_idx += 10; /* D1..D10 */ + d_idx += 2; + memcpy(d_out + 10, fr->d_bits + d_idx, 2); d_idx += 2; /* D11..D12 */ + d_idx += 2; + memcpy(d_out + 12, fr->d_bits + d_idx, 2); d_idx += 2; /* D13..D14 */ + d_idx += 2; + d_out[14] = fr->d_bits[d_idx++]; /* D15 */ + d_idx += 3; + memcpy(d_out + 15, fr->d_bits + d_idx, 10); d_idx += 10;/* D16..D25 */ + d_idx += 2; + memcpy(d_out + 25, fr->d_bits + d_idx, 2); d_idx += 2; /* D26..D27 */ + d_idx += 2; + memcpy(d_out + 27, fr->d_bits + d_idx, 2); d_idx += 2; /* D28..D29 */ + d_idx += 2; + d_out[29] = fr->d_bits[d_idx++]; /* D30 */ + d_idx += 3; + + OSMO_ASSERT(d_idx == 48); + + return 30; +} + +/* definition of a synchronous V.110 RA1 rate adaptation. There is one for each supported tuple + * of user data rate and intermediate rate (IR). */ +struct osmo_v110_sync_ra1 { + unsigned int data_rate; + unsigned int intermediate_rate; + unsigned int user_data_chunk_bits; + /*! RA1 function in user bitrate -> intermediate rate direction */ + int (*adapt_user_to_ir)(struct osmo_v110_decoded_frame *fr, const ubit_t *d_in, size_t in_len); + /*! RA1 function in intermediate rate -> user bitrate direction */ + int (*adapt_ir_to_user)(ubit_t *d_out, size_t out_len, const struct osmo_v110_decoded_frame *fr); +}; + +/* all of the synchronous data signalling rates; see Table 1/V.110 */ +static const struct osmo_v110_sync_ra1 osmo_v110_sync_ra1_def[_NUM_OSMO_V110_SYNC_RA1] = { + [OSMO_V110_SYNC_RA1_600] = { + .data_rate = 600, + .intermediate_rate = 8000, + .user_data_chunk_bits = 6, + .adapt_user_to_ir = v110_adapt_600_to_IR8000, + .adapt_ir_to_user = v110_adapt_IR8000_to_600, + }, + [OSMO_V110_SYNC_RA1_1200] = { + .data_rate = 1200, + .intermediate_rate = 8000, + .user_data_chunk_bits = 12, + .adapt_user_to_ir = v110_adapt_1200_to_IR8000, + .adapt_ir_to_user = v110_adapt_IR8000_to_1200, + }, + [OSMO_V110_SYNC_RA1_2400] = { + .data_rate = 2400, + .intermediate_rate = 8000, + .user_data_chunk_bits = 24, + .adapt_user_to_ir = v110_adapt_2400_to_IR8000, + .adapt_ir_to_user = v110_adapt_IR8000_to_2400, + }, + [OSMO_V110_SYNC_RA1_4800] = { + .data_rate = 4800, + .intermediate_rate = 8000, + .user_data_chunk_bits = 48, + .adapt_user_to_ir = v110_adapt_Nx4800_to_IR, + .adapt_ir_to_user = v110_adapt_IR_to_Nx4800, + }, + [OSMO_V110_SYNC_RA1_7200] = { + .data_rate = 7200, + .intermediate_rate = 16000, + .user_data_chunk_bits = 36, + .adapt_user_to_ir = v110_adapt_Nx3600_to_IR, + .adapt_ir_to_user = v110_adapt_IR_to_Nx3600, + }, + [OSMO_V110_SYNC_RA1_9600] = { + .data_rate = 9600, + .intermediate_rate = 16000, + .user_data_chunk_bits = 48, + .adapt_user_to_ir = v110_adapt_Nx4800_to_IR, + .adapt_ir_to_user = v110_adapt_IR_to_Nx4800, + }, + [OSMO_V110_SYNC_RA1_12000] = { + .data_rate = 12000, + .intermediate_rate = 32000, + .user_data_chunk_bits = 30, + .adapt_user_to_ir = v110_adapt_Nx12000_to_IR, + .adapt_ir_to_user = v110_adapt_IR_to_Nx12000, + }, + [OSMO_V110_SYNC_RA1_14400] = { + .data_rate = 14400, + .intermediate_rate = 32000, + .user_data_chunk_bits = 36, + .adapt_user_to_ir = v110_adapt_Nx3600_to_IR, + .adapt_ir_to_user = v110_adapt_IR_to_Nx3600, + }, + [OSMO_V110_SYNC_RA1_19200] = { + .data_rate = 19200, + .intermediate_rate = 32000, + .user_data_chunk_bits = 48, + .adapt_user_to_ir = v110_adapt_Nx4800_to_IR, + .adapt_ir_to_user = v110_adapt_IR_to_Nx4800, + }, + [OSMO_V110_SYNC_RA1_24000] = { + .data_rate = 24000, + .intermediate_rate = 64000, + .user_data_chunk_bits = 30, + .adapt_user_to_ir = v110_adapt_Nx12000_to_IR, + .adapt_ir_to_user = v110_adapt_IR_to_Nx12000, + }, + [OSMO_V110_SYNC_RA1_28800] = { + .data_rate = 28800, + .intermediate_rate = 64000, + .user_data_chunk_bits = 36, + .adapt_user_to_ir = v110_adapt_Nx3600_to_IR, + .adapt_ir_to_user = v110_adapt_IR_to_Nx3600, + }, + [OSMO_V110_SYNC_RA1_38400] = { + .data_rate = 38400, + .intermediate_rate = 64000, + .user_data_chunk_bits = 48, + .adapt_user_to_ir = v110_adapt_Nx4800_to_IR, + .adapt_ir_to_user = v110_adapt_IR_to_Nx4800, + }, +}; + +/*! obtain the size (in number of bits) of the user data bits in one V.110 + * frame for specified RA1 rate */ +int osmo_v110_sync_ra1_get_user_data_chunk_bitlen(enum osmo_v100_sync_ra1_rate rate) +{ + if (rate < 0 || rate >= _NUM_OSMO_V110_SYNC_RA1) + return -EINVAL; + + return osmo_v110_sync_ra1_def[rate].user_data_chunk_bits; +} + +/*! obtain the user data rate (in bits/s) for specified RA1 rate */ +int osmo_v110_sync_ra1_get_user_data_rate(enum osmo_v100_sync_ra1_rate rate) +{ + if (rate < 0 || rate >= _NUM_OSMO_V110_SYNC_RA1) + return -EINVAL; + + return osmo_v110_sync_ra1_def[rate].data_rate; +} + +/*! obtain the intermediate rate (in bits/s) for specified RA1 rate */ +int osmo_v110_sync_ra1_get_intermediate_rate(enum osmo_v100_sync_ra1_rate rate) +{ + if (rate < 0 || rate >= _NUM_OSMO_V110_SYNC_RA1) + return -EINVAL; + + return osmo_v110_sync_ra1_def[rate].intermediate_rate; +} + +/*! perform V.110 RA1 function in user rate -> intermediate rate direction. + * \param[in] rate specification of the user bitrate + * \param[out] fr caller-allocated output buffer for the [decoded] V.110 frame generated + * \param[in] d_in input user data (unpacked bits) + * \param[in] in_len length of user input data (in number of bits) + * \returns 0 on success; negative in case of error */ +int osmo_v110_sync_ra1_user_to_ir(enum osmo_v100_sync_ra1_rate rate, struct osmo_v110_decoded_frame *fr, + const ubit_t *d_in, size_t in_len) +{ + if (rate < 0 || rate >= _NUM_OSMO_V110_SYNC_RA1) + return -EINVAL; + + return osmo_v110_sync_ra1_def[rate].adapt_user_to_ir(fr, d_in, in_len); +} + +/*! perform V.110 RA1 function in intermediate rate -> user rate direction. + * \param[in] rate specification of the user bitrate + * \param[out] d_out caller-allocated output user data (unpacked bits) + * \param[out] out_len length of d_out output buffer + * \param[in] fr [decoded] V.110 frame used as input + * \returns number of unpacked bits written to d_out on success; negative in case of error */ +int osmo_v110_sync_ra1_ir_to_user(enum osmo_v100_sync_ra1_rate rate, ubit_t *d_out, size_t out_len, + const struct osmo_v110_decoded_frame *fr) +{ + if (rate < 0 || rate >= _NUM_OSMO_V110_SYNC_RA1) + return -EINVAL; + + return osmo_v110_sync_ra1_def[rate].adapt_ir_to_user(d_out, out_len, fr); +} diff --git a/src/isdn/v110_ta.c b/src/isdn/v110_ta.c new file mode 100644 index 00000000..6730b20c --- /dev/null +++ b/src/isdn/v110_ta.c @@ -0,0 +1,881 @@ +/*! \file v110_ta.c + * TA (Terminal Adapter) implementation as per ITU-T V.110. */ +/* + * (C) 2022 by Harald Welte <laforge@gnumonks.org> + * (C) 2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * + * Initial (Work-in-Progress) implementation by Harald Welte, + * completed and co-authored by Vadim Yanitskiy. + * + * All Rights Reserved + * + * SPDX-License-Identifier: GPL-2.0+ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <stdbool.h> +#include <stdint.h> +#include <errno.h> + +#include <osmocom/core/logging.h> +#include <osmocom/core/talloc.h> +#include <osmocom/core/utils.h> +#include <osmocom/core/bits.h> +#include <osmocom/core/tdef.h> +#include <osmocom/core/fsm.h> + +#include <osmocom/isdn/v110.h> +#include <osmocom/isdn/v110_ta.h> + +#define S(x) (1 << (x)) + +#define V24_FLAGMASK_IS_ON(flags, circuit) \ + (((flags) & S(circuit)) != 0) + +#define V24_FLAGMASK_IS_OFF(flags, circuit) \ + (((flags) & S(circuit)) == 0) + +#define V24_FLAGMASK_SET_ON(flags, circuit) \ + (flags) |= S(circuit) + +#define V24_FLAGMASK_SET_OFF(flags, circuit) \ + (flags) &= ~S(circuit) + +/* inverse logic: ON = binary 0; OFF = binary 1 */ +#define V110_SX_BIT_ON 0 +#define V110_SX_BIT_OFF 1 + +const struct value_string osmo_v110_ta_circuit_names[] = { + { OSMO_V110_TA_C_105, "105/RTS" }, + { OSMO_V110_TA_C_106, "106/CTS" }, + { OSMO_V110_TA_C_107, "107/DSR" }, + { OSMO_V110_TA_C_108, "108/DTR" }, + { OSMO_V110_TA_C_109, "109/DCD" }, + { OSMO_V110_TA_C_133, "133" }, + { 0, NULL } +}; + +const struct value_string osmo_v110_ta_circuit_descs[] = { + { OSMO_V110_TA_C_105, "Request to Send" }, + { OSMO_V110_TA_C_106, "Clear to Send" }, + { OSMO_V110_TA_C_107, "Data Set Ready" }, + { OSMO_V110_TA_C_108, "Data Terminal Ready" }, + { OSMO_V110_TA_C_109, "Data Carrier Detect" }, + { OSMO_V110_TA_C_133, "Ready for receiving" }, + { 0, NULL } +}; + +static const struct osmo_tdef v110_ta_tdef[] = { + { .T = OSMO_V110_TA_TIMER_X1, + .unit = OSMO_TDEF_MS, .default_val = 3000, /* suggested in 7.1.5 e) */ + .desc = "ITU-T V.110 7.1.5 Loss of frame synchronization: sync recovery timer" }, + { .T = OSMO_V110_TA_TIMER_T1, + .unit = OSMO_TDEF_MS, .default_val = 10000, /* suggested in 7.1.2.2 */ + .desc = "ITU-T V.110 7.1.2 Connect TA to line: sync establishment timer" }, + { .T = OSMO_V110_TA_TIMER_T2, + .unit = OSMO_TDEF_MS, .default_val = 5000, /* suggested in 7.1.4.1 */ + .desc = "ITU-T V.110 7.1.4 Disconnect mode: disconnect confirmation timer" }, + { /* end of list */ } +}; + +/********************************************************************************* + * V.110 TERMINAL ADAPTER FSM + *********************************************************************************/ + +enum v110_ta_fsm_state { + V110_TA_ST_IDLE_READY, /* 7.1.1 Idle (or ready) state */ + V110_TA_ST_CON_TA_TO_LINE, /* 7.1.2 Connect TA to line state */ + V110_TA_ST_DATA_TRANSFER, /* 7.1.3 Data transfer state */ + V110_TA_ST_DISCONNECTING, /* 7.1.4 Disconnect mode */ + V110_TA_ST_RESYNCING, /* 7.1.5 Re-synchronizing state */ +}; + +enum v110_ta_fsm_event { + V110_TA_EV_RX_FRAME_IND, /* a V.110 frame was received by the lower layer */ + V110_TA_EV_TX_FRAME_RTS, /* a V.110 frame is to be sent by the lower layer */ + V110_TA_EV_V24_STATUS_CHG, /* V.24 flag-mask has been updated by TE */ + V110_TA_EV_SYNC_IND, /* the lower layer has synchronized to the frame clock */ + V110_TA_EV_DESYNC_IND, /* the lower layer has lost frame clock synchronization */ + V110_TA_EV_TIMEOUT, /* generic event for handling a timeout condition */ +}; + +static const struct value_string v110_ta_fsm_event_names[] = { + { V110_TA_EV_RX_FRAME_IND, "RX_FRAME_IND" }, + { V110_TA_EV_TX_FRAME_RTS, "TX_FRAME_RTS" }, + { V110_TA_EV_V24_STATUS_CHG, "V24_STATUS_CHG" }, + { V110_TA_EV_SYNC_IND, "SYNC_IND" }, + { V110_TA_EV_DESYNC_IND, "DESYNC_IND" }, + { V110_TA_EV_TIMEOUT, "TIMEOUT" }, + { 0, NULL } +}; + +enum v110_ta_d_bit_mode { + V110_TA_DBIT_M_ALL_ZERO = 0, /* set all bits to binary '0' */ + V110_TA_DBIT_M_ALL_ONE = 1, /* set all bits to binary '1' */ + V110_TA_DBIT_M_FORWARD, /* forward D-bits to/from DTE */ +}; + +struct v110_ta_state { + /*! V.24 status flags shared between DTE (user) and DCE (TA, us) */ + unsigned int v24_flags; + struct { + /* what kind of D-bits to transmit in V.110 frames */ + enum v110_ta_d_bit_mode d_bit_mode; + /* what to put in S-bits of transmitted V.110 frames */ + ubit_t s_bits; + /* what to put in X-bits of transmitted V.110 frames */ + ubit_t x_bits; + } tx; + struct { + enum v110_ta_d_bit_mode d_bit_mode; + } rx; +}; + +struct osmo_v110_ta { + const char *name; + struct osmo_tdef *Tdefs; + struct osmo_fsm_inst *fi; + struct osmo_v110_ta_cfg *cfg; + struct v110_ta_state state; +}; + +static inline bool v110_df_x_bits_are(const struct osmo_v110_decoded_frame *df, ubit_t cmp) +{ + return (df->x_bits[0] == cmp) && (df->x_bits[1] == cmp); +} + +static inline bool v110_df_s_bits_are(const struct osmo_v110_decoded_frame *df, ubit_t cmp) +{ + /* ITU-T Table 2/V.110 (see also 5.1.2.3) defines the following S-bits: + * S1, S3, S4, S6, S8, S9 (6 bits total). However, fr->s_bits[] contains + * 9 (MAX_S_BITS) bits, including the undefined bits S2, S5, S7. + * Hence we must skip those undefined bits. */ + static const uint8_t sbit_map[] = { 0, 2, 3, 5, 7, 8 }; + + for (unsigned int i = 0; i < ARRAY_SIZE(sbit_map); i++) { + uint8_t idx = sbit_map[i]; + if (df->s_bits[idx] != cmp) + return false; + } + + return true; +} + +static inline bool v110_df_d_bits_are(const struct osmo_v110_decoded_frame *df, ubit_t cmp) +{ + for (unsigned int i = 0; i < MAX_D_BITS; i++) { + if (df->d_bits[i] != cmp) + return false; + } + + return true; +} + +/* handle one V.110 frame and forward user bits to the application */ +static void v110_ta_handle_frame(const struct osmo_v110_ta *ta, + const struct osmo_v110_decoded_frame *df) +{ + const struct osmo_v110_ta_cfg *cfg = ta->cfg; + const struct v110_ta_state *ts = &ta->state; + ubit_t user_bits[MAX_D_BITS]; + int num_user_bits; + int rc; + + switch (ts->rx.d_bit_mode) { + case V110_TA_DBIT_M_ALL_ZERO: + case V110_TA_DBIT_M_ALL_ONE: + /* generate as many user bits as needed for the configured rate */ + num_user_bits = osmo_v110_sync_ra1_get_user_data_chunk_bitlen(cfg->rate); + OSMO_ASSERT(num_user_bits > 0); + /* set them all to binary '0' or binary '1' */ + memset(&user_bits[0], (int)ts->rx.d_bit_mode, num_user_bits); + cfg->rx_cb(cfg->priv, &user_bits[0], num_user_bits); + break; + case V110_TA_DBIT_M_FORWARD: + rc = osmo_v110_sync_ra1_ir_to_user(cfg->rate, &user_bits[0], sizeof(user_bits), df); + if (rc > 0) + cfg->rx_cb(cfg->priv, &user_bits[0], rc); + /* XXX else: indicate an error somehow? */ + break; + } +} + +/* build one V.110 frame to transmit */ +static void v110_ta_build_frame(const struct osmo_v110_ta *ta, + struct osmo_v110_decoded_frame *df) +{ + const struct osmo_v110_ta_cfg *cfg = ta->cfg; + const struct v110_ta_state *ts = &ta->state; + ubit_t user_bits[MAX_D_BITS]; + int num_user_bits; + int rc; + + /* E-bits (E1/E2/E3 may be overwritten below) */ + memset(df->e_bits, 1, sizeof(df->e_bits)); + /* S-bits */ + memset(df->s_bits, ts->tx.s_bits, sizeof(df->s_bits)); + /* X-bits */ + memset(df->x_bits, ts->tx.x_bits, sizeof(df->x_bits)); + + /* D-bits */ + switch (ts->tx.d_bit_mode) { + case V110_TA_DBIT_M_ALL_ZERO: + case V110_TA_DBIT_M_ALL_ONE: + /* set them all to binary '0' or binary '1' */ + memset(df->d_bits, (int)ts->tx.d_bit_mode, sizeof(df->d_bits)); + break; + case V110_TA_DBIT_M_FORWARD: + /* how many user bits to retrieve */ + num_user_bits = osmo_v110_sync_ra1_get_user_data_chunk_bitlen(cfg->rate); + OSMO_ASSERT(num_user_bits > 0); + /* retrieve user bits from the application */ + cfg->tx_cb(cfg->priv, &user_bits[0], num_user_bits); + /* convert user bits to intermediate rate (store to df) */ + rc = osmo_v110_sync_ra1_user_to_ir(cfg->rate, df, &user_bits[0], num_user_bits); + OSMO_ASSERT(rc == 0); + break; + } +} + +static void v110_ta_flags_update(struct osmo_v110_ta *ta, unsigned int v24_flags) +{ + struct osmo_v110_ta_cfg *cfg = ta->cfg; + + if (ta->state.v24_flags == v24_flags) + return; + if (cfg->status_update_cb != NULL) + cfg->status_update_cb(cfg->priv, v24_flags); + ta->state.v24_flags = v24_flags; +} + +static const struct osmo_tdef_state_timeout v110_ta_fsm_timeouts[32] = { + [V110_TA_ST_RESYNCING] = { .T = OSMO_V110_TA_TIMER_X1 }, + [V110_TA_ST_CON_TA_TO_LINE] = { .T = OSMO_V110_TA_TIMER_T1 }, + [V110_TA_ST_DISCONNECTING] = { .T = OSMO_V110_TA_TIMER_T2 }, +}; + +#define v110_ta_fsm_state_chg(state) \ + osmo_tdef_fsm_inst_state_chg(fi, state, \ + v110_ta_fsm_timeouts, \ + ((struct osmo_v110_ta *)(fi->priv))->Tdefs, \ + 0) + +/* ITU-T V.110 Section 7.1.1 */ +static void v110_ta_fsm_idle_ready_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct osmo_v110_ta *ta = (struct osmo_v110_ta *)fi->priv; + struct v110_ta_state *ts = &ta->state; + unsigned int v24_flags = ta->state.v24_flags; + + /* 7.1.1.2 During the idle (or ready) state the TA will transmit continuous binary 1s into the B-channel */ + ts->tx.d_bit_mode = V110_TA_DBIT_M_ALL_ONE; /* circuit 103: continuous binary '1' */ + ts->tx.s_bits = V110_SX_BIT_OFF; /* OFF is binary '1' */ + ts->tx.x_bits = V110_SX_BIT_OFF; /* OFF is binary '1' */ + + /* 7.1.1.3 During the idle (or ready) state the TA (DCE) will transmit the following toward the DTE: */ + /* - circuit 104: continuous binary '1' */ + ts->rx.d_bit_mode = V110_TA_DBIT_M_ALL_ONE; + /* - circuits 107, 106, 109 = OFF */ + V24_FLAGMASK_SET_OFF(v24_flags, OSMO_V110_TA_C_106); + V24_FLAGMASK_SET_OFF(v24_flags, OSMO_V110_TA_C_107); + V24_FLAGMASK_SET_OFF(v24_flags, OSMO_V110_TA_C_109); + v110_ta_flags_update(ta, v24_flags); +} + +/* ITU-T V.110 Section 7.1.1 */ +static void v110_ta_fsm_idle_ready(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct osmo_v110_ta *ta = (struct osmo_v110_ta *)fi->priv; + struct v110_ta_state *ts = &ta->state; + + switch (event) { + case V110_TA_EV_V24_STATUS_CHG: + /* When the TA is to be switched to the data mode, circuit 108 must be ON */ + if (V24_FLAGMASK_IS_ON(ts->v24_flags, OSMO_V110_TA_C_108)) { + /* 7.12.2: Start timer T1 when switching to CON_TA_LINE */ + v110_ta_fsm_state_chg(V110_TA_ST_CON_TA_TO_LINE); + } + break; + case V110_TA_EV_RX_FRAME_IND: + v110_ta_handle_frame(ta, (const struct osmo_v110_decoded_frame *)data); + break; + case V110_TA_EV_TX_FRAME_RTS: + v110_ta_build_frame(ta, (struct osmo_v110_decoded_frame *)data); + break; + default: + OSMO_ASSERT(0); + } +} + +/* ITU-T V.110 Section 7.1.2 */ +static void v110_ta_fsm_connect_ta_to_line_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct osmo_v110_ta *ta = (struct osmo_v110_ta *)fi->priv; + struct v110_ta_state *ts = &ta->state; + + /* 7.1.2.1 Switching to the data mode causes the TA to transmit the following towards the ISDN: */ + /* a) frame synchronization pattern as described in 5.1.3.1 and 5.2.1 (done by the API user) */ + /* b) circuit 103: continuous binary '1' */ + ts->tx.d_bit_mode = V110_TA_DBIT_M_ALL_ONE; + /* c) status bits S = OFF and X = OFF */ + ts->tx.s_bits = V110_SX_BIT_OFF; /* OFF is binary '1' */ + ts->tx.x_bits = V110_SX_BIT_OFF; /* OFF is binary '1' */ + + /* 7.1.2.2 ... the receiver in the TA will begin to search for the frame synchronization + * pattern in the received bit stream (see 5.1.3.1 and 5.2.1) and start timer T1. */ + OSMO_ASSERT(fi->T == OSMO_V110_TA_TIMER_T1); +} + +/* ITU-T V.110 Section 7.1.2 */ +static void v110_ta_fsm_connect_ta_to_line(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct osmo_v110_ta *ta = (struct osmo_v110_ta *)fi->priv; + struct v110_ta_state *ts = &ta->state; + + switch (event) { + case V110_TA_EV_V24_STATUS_CHG: + /* If circuit 108 is OFF, we go back to IDLE/READY */ + if (V24_FLAGMASK_IS_OFF(ts->v24_flags, OSMO_V110_TA_C_108)) + v110_ta_fsm_state_chg(V110_TA_ST_IDLE_READY); + break; + case V110_TA_EV_SYNC_IND: + /* 7.1.2.3 When the receiver recognizes the frame synchronization pattern, it causes the S- + * and X-bits in the transmitted frames to be turned ON (provided that circuit 108 is ON). */ + OSMO_ASSERT(V24_FLAGMASK_IS_ON(ts->v24_flags, OSMO_V110_TA_C_108)); + ts->tx.s_bits = V110_SX_BIT_ON; + ts->tx.x_bits = V110_SX_BIT_ON; + break; + case V110_TA_EV_RX_FRAME_IND: + { + const struct osmo_v110_decoded_frame *df = data; + unsigned int v24_flags = ta->state.v24_flags; + + /* 7.1.2.4 When the receiver recognizes that the status of bits S and X are ON */ + if (v110_df_s_bits_are(df, V110_SX_BIT_ON) && + v110_df_x_bits_are(df, V110_SX_BIT_ON)) { + /* ... it will perform the following functions: */ + /* a) Turn ON circuit 107 toward the DTE and stop timer T1 */ + V24_FLAGMASK_SET_ON(v24_flags, OSMO_V110_TA_C_107); + osmo_timer_del(&fi->timer); + /* b) Then, circuit 103 may be connected to the data bits in the frame; however, the + * DTE must maintain a binary 1 condition on circuit 103 until circuit 106 is turned + * ON in the next portion of the sequence. */ + /* c) Turn ON circuit 109 and connect the data bits to circuit 104. */ + V24_FLAGMASK_SET_ON(v24_flags, OSMO_V110_TA_C_109); + ts->rx.d_bit_mode = V110_TA_DBIT_M_FORWARD; + /* d) After an interval of N bits (see 6.3), it will turn ON circuit 106. */ + V24_FLAGMASK_SET_ON(v24_flags, OSMO_V110_TA_C_106); + ts->tx.d_bit_mode = V110_TA_DBIT_M_FORWARD; + v110_ta_flags_update(ta, v24_flags); + /* Circuit 106 transitioning from OFF to ON will cause the transmitted data to + * transition from binary 1 to the data mode. */ + v110_ta_fsm_state_chg(V110_TA_ST_DATA_TRANSFER); + } + + v110_ta_handle_frame(ta, df); + break; + } + case V110_TA_EV_TX_FRAME_RTS: + v110_ta_build_frame(ta, (struct osmo_v110_decoded_frame *)data); + break; + case V110_TA_EV_TIMEOUT: + v110_ta_fsm_state_chg(V110_TA_ST_IDLE_READY); + break; + default: + OSMO_ASSERT(0); + } +} + +/* ITU-T V.110 Section 7.1.3 */ +static void v110_ta_fsm_data_transfer_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct osmo_v110_ta *ta = (struct osmo_v110_ta *)fi->priv; + struct v110_ta_state *ts = &ta->state; + unsigned int v24_flags = ta->state.v24_flags; + + /* 7.1.3.1 While in the data transfer state, the following circuit conditions exist: + * a): 105, 107, 108, and 109 are in the ON condition */ + /* XXX: OSMO_ASSERT(V24_FLAGMASK_IS_ON(ts->v24_flags, OSMO_V110_TA_C_105)); */ + V24_FLAGMASK_SET_ON(v24_flags, OSMO_V110_TA_C_107); + /* XXX: OSMO_ASSERT(V24_FLAGMASK_IS_ON(ts->v24_flags, OSMO_V110_TA_C_108)); */ + V24_FLAGMASK_SET_ON(v24_flags, OSMO_V110_TA_C_109); + /* b) data is being transmitted on circuit 103 and received on circuit 104 */ + ts->rx.d_bit_mode = V110_TA_DBIT_M_FORWARD; + ts->tx.d_bit_mode = V110_TA_DBIT_M_FORWARD; + /* c) circuits 133 (when implemented) and 106 are in the ON condition unless local out-of-band + * flow control is being used, either or both circuits may be in the ON or the OFF condition. */ + if (!ta->cfg->flow_ctrl.end_to_end) { + /* XXX: OSMO_ASSERT(V24_FLAGMASK_IS_ON(ts->v24_flags, OSMO_V110_TA_C_133)); */ + V24_FLAGMASK_SET_ON(v24_flags, OSMO_V110_TA_C_106); + } + v110_ta_flags_update(ta, v24_flags); + + /* 7.1.3.2 While in the data transfer state, the following status bit conditions exist: */ + /* a) status bits S in both directions are in the ON condition; */ + ts->tx.s_bits = V110_SX_BIT_ON; + /* b) status bits X in both directions are in the ON condition unless end-to-end flow control + * is being used, in which case status bit X in either or both directions may be in the + * ON or the OFF condition. */ + if (!ta->cfg->flow_ctrl.end_to_end) + ts->tx.x_bits = V110_SX_BIT_ON; +} + +/* ITU-T V.110 Section 7.1.3 */ +static void v110_ta_fsm_data_transfer(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct osmo_v110_ta *ta = (struct osmo_v110_ta *)fi->priv; + struct v110_ta_state *ts = &ta->state; + + /* 7.1.3.3 While in the data transfer state: */ + /* a) the S status bits shall *not* be mapped to/from the interchange circuits */ + /* b) the X status bits shall *not* be mapped according to Table 3, + * unless end-to-end flow control is implemented */ + /* TODO: if (ta->cfg->flow_ctrl.end_to_end) { ... } */ + + switch (event) { + case V110_TA_EV_V24_STATUS_CHG: + /* 7.1.4.1 At the completion of the data transfer phase, the local DTE will indicate a + * disconnect request by turning OFF circuit 108 */ + if (V24_FLAGMASK_IS_ON(ts->v24_flags, OSMO_V110_TA_C_108)) + break; + v110_ta_fsm_state_chg(V110_TA_ST_DISCONNECTING); + break; + case V110_TA_EV_DESYNC_IND: + v110_ta_fsm_state_chg(V110_TA_ST_RESYNCING); + break; + case V110_TA_EV_TX_FRAME_RTS: + v110_ta_build_frame(ta, (struct osmo_v110_decoded_frame *)data); + break; + case V110_TA_EV_RX_FRAME_IND: + { + const struct osmo_v110_decoded_frame *df = data; + unsigned int v24_flags = ta->state.v24_flags; + + /* 7.1.4.2 ... this TA will recognize the transition of the status bits S from + * ON to OFF and the data bits from data to binary 0 as a disconnect request */ + if (v110_df_s_bits_are(df, V110_SX_BIT_OFF) && v110_df_d_bits_are(df, 0)) { + /* ... and it will turn OFF circuits 107 and 109. */ + V24_FLAGMASK_SET_OFF(v24_flags, OSMO_V110_TA_C_107); + V24_FLAGMASK_SET_OFF(v24_flags, OSMO_V110_TA_C_109); + v110_ta_flags_update(ta, v24_flags); + /* DTE should respond by turning OFF circuit 108 */ + break; /* XXX: shall we forward D-bits to DTE anyway? */ + } + + v110_ta_handle_frame(ta, df); + break; + } + default: + OSMO_ASSERT(0); + } +} + +/* ITU-T V.110 Section 7.1.4 */ +static void v110_ta_fsm_disconnect_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct osmo_v110_ta *ta = (struct osmo_v110_ta *)fi->priv; + struct v110_ta_state *ts = &ta->state; + unsigned int v24_flags = ta->state.v24_flags; + + /* 7.1.4.1 At the completion of the data transfer phase, the local DTE will indicate a + * disconnect request by turning OFF circuit 108. This will cause the following to occur: */ + /* a) the status bits S in the frame toward ISDN will turn OFF, status bits X are kept ON */ + ts->tx.s_bits = V110_SX_BIT_OFF; + /* b) circuit 106 will be turned OFF */ + V24_FLAGMASK_SET_OFF(v24_flags, OSMO_V110_TA_C_106); + v110_ta_flags_update(ta, v24_flags); + /* c) the data bits in the frame will be set to binary 0. */ + ts->tx.d_bit_mode = V110_TA_DBIT_M_ALL_ZERO; + + /* To guard against the failure of the remote TA to respond to the disconnect request, + * the local TA may start a timer T2 (suggested value 5 s) which is stopped by the + * reception or transmission of any D-channel clearing message (DISCONNECT, RELEASE, + * RELEASE COMPLETE). */ + OSMO_ASSERT(fi->T == OSMO_V110_TA_TIMER_T2); +} + +/* ITU-T V.110 Section 7.1.4 */ +static void v110_ta_fsm_disconnect(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct osmo_v110_ta *ta = (struct osmo_v110_ta *)fi->priv; + + switch (event) { + case V110_TA_EV_V24_STATUS_CHG: + break; /* nothing to do */ + case V110_TA_EV_TX_FRAME_RTS: + v110_ta_build_frame(ta, (struct osmo_v110_decoded_frame *)data); + break; + case V110_TA_EV_RX_FRAME_IND: + { + const struct osmo_v110_decoded_frame *df = data; + + /* 7.1.4.3 The TA at the station that originated the disconnect request will + * recognize reception of S = OFF or the loss of framing signals as a disconnect + * acknowledgement and turn OFF circuits 107 and 109. */ + if (v110_df_s_bits_are(df, V110_SX_BIT_OFF)) { + /* circuits 107 and 109 set to off in .onenter() */ + v110_ta_fsm_state_chg(V110_TA_ST_IDLE_READY); + } + + v110_ta_handle_frame(ta, df); + break; + } + case V110_TA_EV_DESYNC_IND: + case V110_TA_EV_TIMEOUT: + /* circuits 107 and 109 set to off in .onenter() */ + v110_ta_fsm_state_chg(V110_TA_ST_IDLE_READY); + break; + default: + OSMO_ASSERT(0); + } +} + +/* ITU-T V.110 Section 7.1.5 */ +static void v110_ta_fsm_resyncing_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct osmo_v110_ta *ta = (struct osmo_v110_ta *)fi->priv; + struct v110_ta_state *ts = &ta->state; + + /* 7.1.5 In the event of loss of frame synchronization, the (local) TA should + * attempt to resynchronize as follows: */ + /* a) Place circuit 104 in binary 1 condition (passes from the data mode) */ + ts->rx.d_bit_mode = V110_TA_DBIT_M_ALL_ONE; + /* b) Turn OFF status bit X in the transmitted frame */ + ts->tx.x_bits = V110_SX_BIT_OFF; + + /* guard timeout, see 7.1.5 e) */ + OSMO_ASSERT(fi->T == OSMO_V110_TA_TIMER_X1); +} + +/* ITU-T V.110 Section 7.1.5 */ +static void v110_ta_fsm_resyncing(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct osmo_v110_ta *ta = (struct osmo_v110_ta *)fi->priv; + struct v110_ta_state *ts = &ta->state; + unsigned int v24_flags = ta->state.v24_flags; + + switch (event) { + case V110_TA_EV_V24_STATUS_CHG: + break; /* TODO: handle circuit 108 being set to OFF? */ + case V110_TA_EV_TX_FRAME_RTS: + v110_ta_build_frame(ta, (struct osmo_v110_decoded_frame *)data); + break; + case V110_TA_EV_SYNC_IND: + /* f) If resynchronization is achieved, the local TA should turn ON status bit X */ + ts->tx.x_bits = V110_SX_BIT_ON; + v110_ta_fsm_state_chg(V110_TA_ST_DATA_TRANSFER); + break; + case V110_TA_EV_TIMEOUT: + /* e) If after an interval of X1 seconds the local TA cannot attain synchronization, + * it should send a disconnect request by turning OFF all of the status bits for several + * (at least three) frames with data bits set to binary 0 and then disconnect by turning + * OFF circuit 107 and transferring to the disconnected mode as discussed in 7.1.4.2. */ + ts->tx.s_bits = V110_SX_BIT_OFF; + ts->tx.x_bits = V110_SX_BIT_OFF; + ts->tx.d_bit_mode = V110_TA_DBIT_M_ALL_ZERO; + /* TODO: actually Tx those frames (delay state transition) */ + V24_FLAGMASK_SET_OFF(v24_flags, OSMO_V110_TA_C_107); + v110_ta_flags_update(ta, v24_flags); + v110_ta_fsm_state_chg(V110_TA_ST_DISCONNECTING); + break; + default: + OSMO_ASSERT(0); + } +} + +static int v110_ta_timer_cb(struct osmo_fsm_inst *fi) +{ + osmo_fsm_inst_dispatch(fi, V110_TA_EV_TIMEOUT, NULL); + return 0; +} + +static const struct osmo_fsm_state v110_ta_states[] = { + [V110_TA_ST_IDLE_READY] = { + .name = "IDLE_READY", + .in_event_mask = S(V110_TA_EV_V24_STATUS_CHG) + | S(V110_TA_EV_TX_FRAME_RTS) + | S(V110_TA_EV_RX_FRAME_IND), + .out_state_mask = S(V110_TA_ST_IDLE_READY) + | S(V110_TA_ST_CON_TA_TO_LINE), + .action = &v110_ta_fsm_idle_ready, + .onenter = &v110_ta_fsm_idle_ready_onenter, + }, + [V110_TA_ST_CON_TA_TO_LINE] = { + .name = "CONNECT_TA_TO_LINE", + .in_event_mask = S(V110_TA_EV_V24_STATUS_CHG) + | S(V110_TA_EV_TIMEOUT) + | S(V110_TA_EV_SYNC_IND) + | S(V110_TA_EV_TX_FRAME_RTS) + | S(V110_TA_EV_RX_FRAME_IND), + .out_state_mask = S(V110_TA_ST_DATA_TRANSFER) + | S(V110_TA_ST_IDLE_READY), + .action = &v110_ta_fsm_connect_ta_to_line, + .onenter = &v110_ta_fsm_connect_ta_to_line_onenter, + }, + [V110_TA_ST_DATA_TRANSFER] = { + .name = "DATA_TRANSFER", + .in_event_mask = S(V110_TA_EV_V24_STATUS_CHG) + | S(V110_TA_EV_DESYNC_IND) + | S(V110_TA_EV_TX_FRAME_RTS) + | S(V110_TA_EV_RX_FRAME_IND), + .out_state_mask = S(V110_TA_ST_RESYNCING) + | S(V110_TA_ST_DISCONNECTING), + .action = &v110_ta_fsm_data_transfer, + .onenter = &v110_ta_fsm_data_transfer_onenter, + }, + [V110_TA_ST_DISCONNECTING] = { + .name = "DISCONNECTING", + .in_event_mask = S(V110_TA_EV_V24_STATUS_CHG) + | S(V110_TA_EV_TIMEOUT) + | S(V110_TA_EV_TX_FRAME_RTS) + | S(V110_TA_EV_RX_FRAME_IND) + | S(V110_TA_EV_DESYNC_IND), + .out_state_mask = S(V110_TA_ST_IDLE_READY), + .action = &v110_ta_fsm_disconnect, + .onenter = &v110_ta_fsm_disconnect_onenter, + }, + [V110_TA_ST_RESYNCING] = { + .name = "RESYNCING", + .in_event_mask = S(V110_TA_EV_V24_STATUS_CHG) + | S(V110_TA_EV_TIMEOUT) + | S(V110_TA_EV_TX_FRAME_RTS) + | S(V110_TA_EV_SYNC_IND), + .out_state_mask = S(V110_TA_ST_IDLE_READY) + | S(V110_TA_ST_DATA_TRANSFER), + .action = &v110_ta_fsm_resyncing, + .onenter = &v110_ta_fsm_resyncing_onenter, + }, +}; + +static struct osmo_fsm osmo_v110_ta_fsm = { + .name = "V110-TA", + .states = v110_ta_states, + .num_states = ARRAY_SIZE(v110_ta_states), + .timer_cb = v110_ta_timer_cb, + .log_subsys = DLGLOBAL, + .event_names = v110_ta_fsm_event_names, +}; + +static __attribute__((constructor)) void on_dso_load(void) +{ + OSMO_ASSERT(osmo_fsm_register(&osmo_v110_ta_fsm) == 0); +} + +/*! Allocate a V.110 TA (Terminal Adapter) instance. + * \param[in] ctx parent talloc context. + * \param[in] name name of the TA instance. + * \param[in] cfg initial configuration of the TA instance. + * \returns pointer to allocated TA instance; NULL on error. */ +struct osmo_v110_ta *osmo_v110_ta_alloc(void *ctx, const char *name, + const struct osmo_v110_ta_cfg *cfg) +{ + struct osmo_v110_ta *ta; + + OSMO_ASSERT(cfg != NULL); + OSMO_ASSERT(cfg->rx_cb != NULL); + OSMO_ASSERT(cfg->tx_cb != NULL); + + /* local (TE-TA) flow control is not implemented */ + if (cfg->flow_ctrl.local != OSMO_V110_LOCAL_FLOW_CTRL_NONE) { + LOGP(DLGLOBAL, LOGL_ERROR, "Local (TE-TA) flow control is not implemented\n"); + return NULL; + } + + ta = talloc_zero(ctx, struct osmo_v110_ta); + if (ta == NULL) + return NULL; + + ta->name = talloc_strdup(ta, name); + ta->cfg = talloc_memdup(ta, cfg, sizeof(*cfg)); + if (ta->name == NULL || ta->cfg == NULL) + goto exit_free; + + ta->Tdefs = talloc_memdup(ta, v110_ta_tdef, sizeof(v110_ta_tdef)); + if (ta->Tdefs == NULL) + goto exit_free; + osmo_tdefs_reset(ta->Tdefs); /* apply default values */ + + ta->fi = osmo_fsm_inst_alloc(&osmo_v110_ta_fsm, ta, ta, LOGL_DEBUG, name); + if (ta->fi == NULL) + goto exit_free; + + /* perform a loop transition to init the internal state */ + osmo_fsm_inst_state_chg(ta->fi, V110_TA_ST_IDLE_READY, 0, 0); + + return ta; + +exit_free: + if (ta->fi != NULL) + osmo_fsm_inst_free(ta->fi); + talloc_free(ta); + return NULL; +} + +/*! Release memory taken by the given V.110 TA instance. + * \param[in] ta TA instance to be free()d. */ +void osmo_v110_ta_free(struct osmo_v110_ta *ta) +{ + if (ta == NULL) + return; + if (ta->fi != NULL) + osmo_fsm_inst_free(ta->fi); + talloc_free(ta); /* also free()s name and cfg */ +} + +/*! Configure a timer of the given V.110 TA instance. + * \param[in] ta TA instance to be configured. + * \param[in] timer a timer to be configured. + * \param[in] val_ms the new timeout value to set (in milliseconds). + * \returns 0 in case of success; negative on error. */ +int osmo_v110_ta_set_timer_val_ms(struct osmo_v110_ta *ta, + enum osmo_v110_ta_timer timer, + unsigned long val_ms) +{ + return osmo_tdef_set(ta->Tdefs, (int)timer, val_ms, OSMO_TDEF_MS); +} + +/*! Feed a [decoded] V.110 frame into the given TA instance. + * + * This function, like its _out counterpart, is intended to be used by the lower layers + * receiving V.110 frames over some medium. The caller of this function is responsible + * for finding the synchronization pattern (if needed), aligning to the frame boundaries, + * and decoding frames using osmo_v110_decode_frame() or osmo_csd_*_decode_frame(). + * + * Bits E1/E2/E3 are expected to be set by the caller (if not being transmitted + * over the medium) in accordance with the configured synchronous user rate. + * + * Bits D1..D48 are passed to the bit rate adaption function RA1. The resulting output + * is then passed to the upper layer (application) via the configured .rx_cb(). Though, + * in certain states of the TA's FSM, bits D1..D48 are ignored and the upper layer + * gets a sequence of binary '0' or '1'. + * + * \param[in] ta TA instance to feed the given frame into. + * \param[in] in pointer to a [decoded] V.110 frame. + * \returns 0 in case of success; negative on error. */ +int osmo_v110_ta_frame_in(struct osmo_v110_ta *ta, const struct osmo_v110_decoded_frame *in) +{ + return osmo_fsm_inst_dispatch(ta->fi, V110_TA_EV_RX_FRAME_IND, (void *)in); +} + +/*! Pull a [decoded] V.110 frame out of the given TA instance. + * + * This function, like its _in counterpart, is intended to be used by the lower layers + * transmitting V.110 frames over some medium. The caller of this function is responsible + * for encoding the output frame using osmo_v110_encode_frame() or osmo_csd_*_encode_frame(). + * + * Bits E1/E2/E3 are set in accordance with the configured synchronous user rate. + * Bits E4/E5/E6/E7 are unconditionally set to binary '1'. + * + * Bits D1..D48 are set depending on the state of TA's FSM: + * + * - In data transfer mode, the user bits are obtained from the upper layer (application) + * via the configured .tx_cb(), and then passed to the bit rate adaption function RA1, + * which generates bits D1..D48. + * - In other modes, bits D1..D48 are all set to binary '0' or '1'. + * + * \param[in] ta TA instance to pull a frame from. + * \param[out] out where to store a [decoded] V.110 frame. + * \returns 0 in case of success; negative on error. */ +int osmo_v110_ta_frame_out(struct osmo_v110_ta *ta, struct osmo_v110_decoded_frame *out) +{ + return osmo_fsm_inst_dispatch(ta->fi, V110_TA_EV_TX_FRAME_RTS, (void *)out); +} + +/*! Indicate a synchronization establishment event. + * + * This function is intended to be called when the lower layer + * achieves synchronization to the frame clock. + * + * \param[in] ta TA instance to indicate the event to. + * \returns 0 in case of success; negative on error. */ +int osmo_v110_ta_sync_ind(struct osmo_v110_ta *ta) +{ + return osmo_fsm_inst_dispatch(ta->fi, V110_TA_EV_SYNC_IND, NULL); +} + +/*! Indicate a synchronization loss event. + * + * This function is intended to be called when the lower layer + * experiences a loss of synchronization with the frame clock. + * + * \param[in] ta TA instance to indicate the event to. + * \returns 0 in case of success; negative on error. */ +int osmo_v110_ta_desync_ind(struct osmo_v110_ta *ta) +{ + return osmo_fsm_inst_dispatch(ta->fi, V110_TA_EV_DESYNC_IND, NULL); +} + +/*! Get the V.24 status bit-mask of the given TA instance. + * \param[in] ta TA instance to get the circuit bit-mask. + * \returns bitmask of OSMO_V110_TA_C_*. */ +unsigned int osmo_v110_ta_get_status(const struct osmo_v110_ta *ta) +{ + return ta->state.v24_flags; +} + +/*! Set the V.24 status bit-mask of the given TA instance. + * \param[in] ta TA instance to update the circuit state. + * \param[in] status bit-mask of OSMO_V110_TA_C_*. + * \returns 0 on success; negative on error. */ +static int v110_ta_set_status(struct osmo_v110_ta *ta, unsigned int status) +{ + const unsigned int old_status = ta->state.v24_flags; + int rc = 0; + + ta->state.v24_flags = status; + if (status != old_status) + rc = osmo_fsm_inst_dispatch(ta->fi, V110_TA_EV_V24_STATUS_CHG, NULL); + + return rc; +} + +/*! Get state of a V.24 circuit of the given TA instance. + * \param[in] ta TA instance to get the circuit state. + * \param[in] circuit a V.24 circuit, one of OSMO_V110_TA_C_*. + * \returns circuit state: active (true) or inactive (false). */ +bool osmo_v110_ta_get_circuit(const struct osmo_v110_ta *ta, + enum osmo_v110_ta_circuit circuit) +{ + return V24_FLAGMASK_IS_ON(ta->state.v24_flags, circuit); +} + +/*! Activate/deactivate a V.24 circuit of the given TA instance. + * \param[in] ta TA instance to update the circuit state. + * \param[in] circuit a V.24 circuit, one of OSMO_V110_TA_C_* (DTE->DCE). + * \param[in] active activate (true) or deactivate (false) the circuit. + * \returns 0 on success; negative on error. */ +int osmo_v110_ta_set_circuit(struct osmo_v110_ta *ta, + enum osmo_v110_ta_circuit circuit, bool active) +{ + unsigned int status = ta->state.v24_flags; + + /* permit setting only DTE->DCE circuits */ + switch (circuit) { + case OSMO_V110_TA_C_105: + case OSMO_V110_TA_C_108: + case OSMO_V110_TA_C_133: + break; + default: + LOGPFSML(ta->fi, LOGL_ERROR, + "Setting circuit %s is not permitted (wrong direction?)\n", + osmo_v110_ta_circuit_name(circuit)); + return -EACCES; + } + + if (active) + V24_FLAGMASK_SET_ON(status, circuit); + else + V24_FLAGMASK_SET_OFF(status, circuit); + + return v110_ta_set_status(ta, status); +} diff --git a/src/pseudotalloc/Makefile.am b/src/pseudotalloc/Makefile.am index 3c78bba8..030b281b 100644 --- a/src/pseudotalloc/Makefile.am +++ b/src/pseudotalloc/Makefile.am @@ -1,4 +1,5 @@ -AM_CFLAGS = -Wall -I. $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)/include +AM_CPPFLAGS = -I. -I$(top_srcdir)/include -I$(top_builddir)/include -I$(top_builddir) +AM_CFLAGS = -Wall if ENABLE_PSEUDOTALLOC diff --git a/src/pseudotalloc/pseudotalloc.c b/src/pseudotalloc/pseudotalloc.c index 25425e57..895c6dd0 100644 --- a/src/pseudotalloc/pseudotalloc.c +++ b/src/pseudotalloc/pseudotalloc.c @@ -55,21 +55,21 @@ void talloc_set_name_const(const void *ptr, const char *name) { } -char *talloc_strdup(const void *context, const char *p) +void *talloc_memdup(const void *ctx, const void *p, size_t size) { - char *ptr; - size_t len; + void *ptr; - if (!p) - return NULL; - len = strlen(p); + ptr = talloc_size(ctx, size); + if (ptr && p) + memcpy(ptr, p, size); + return ptr; +} - ptr = talloc_size(context, len+1); - if (!ptr) +char *talloc_strdup(const void *ctx, const char *p) +{ + if (!p) return NULL; - memcpy(ptr, p, len+1); - - return ptr; + return talloc_memdup(ctx, p, strlen(p) + 1); } void *talloc_pool(const void *context, size_t size) diff --git a/src/pseudotalloc/talloc.h b/src/pseudotalloc/talloc.h index d257a981..38935991 100644 --- a/src/pseudotalloc/talloc.h +++ b/src/pseudotalloc/talloc.h @@ -50,7 +50,8 @@ int _talloc_free(void *ptr, const char *location); void *talloc_named_const(const void *context, size_t size, const char *name); void *talloc_named(const void *context, size_t size, const char *fmt, ...); void talloc_set_name_const(const void *ptr, const char *name); -char *talloc_strdup(const void *t, const char *p); +void *talloc_memdup(const void *ctx, const void *p, size_t size); +char *talloc_strdup(const void *ctx, const char *p); void *talloc_pool(const void *context, size_t size); #define talloc_array(ctx, type, count) (type *)_talloc_array(ctx, sizeof(type), count, #type) void *_talloc_array(const void *ctx, size_t el_size, unsigned count, const char *name); diff --git a/src/sim/Makefile.am b/src/sim/Makefile.am index 52f3c6ad..0f6be576 100644 --- a/src/sim/Makefile.am +++ b/src/sim/Makefile.am @@ -1,9 +1,9 @@ # This is _NOT_ the library release version, it's an API version. # Please read chapter "Library interface versions" of the libtool documentation # before making any modifications: https://www.gnu.org/software/libtool/manual/html_node/Versioning.html -LIBVERSION=2:1:0 +LIBVERSION=3:2:1 -AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)/include +AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_builddir)/include -I$(top_builddir) AM_CFLAGS = -fPIC -Wall $(TALLOC_CFLAGS) AM_LDFLAGS = $(COVERAGE_LDFLAGS) @@ -15,9 +15,12 @@ lib_LTLIBRARIES = libosmosim.la libosmosim_la_SOURCES = core.c reader.c class_tables.c \ card_fs_sim.c card_fs_usim.c card_fs_uicc.c \ card_fs_isim.c card_fs_hpsim.c card_fs_tetra.c -libosmosim_la_LDFLAGS = -version-info $(LIBVERSION) +libosmosim_la_LDFLAGS = \ + -version-info $(LIBVERSION) \ + -no-undefined \ + $(NULL) libosmosim_la_LIBADD = \ - $(top_builddir)/src/libosmocore.la \ + $(top_builddir)/src/core/libosmocore.la \ $(top_builddir)/src/gsm/libosmogsm.la \ $(TALLOC_LIBS) if ENABLE_PCSC diff --git a/src/sim/card_fs_hpsim.c b/src/sim/card_fs_hpsim.c index 4a5f7d9a..2c115b75 100644 --- a/src/sim/card_fs_hpsim.c +++ b/src/sim/card_fs_hpsim.c @@ -17,10 +17,6 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ diff --git a/src/sim/card_fs_isim.c b/src/sim/card_fs_isim.c index f11c0294..1a38da2f 100644 --- a/src/sim/card_fs_isim.c +++ b/src/sim/card_fs_isim.c @@ -17,10 +17,6 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ diff --git a/src/sim/card_fs_sim.c b/src/sim/card_fs_sim.c index 55ce9af7..f07a2370 100644 --- a/src/sim/card_fs_sim.c +++ b/src/sim/card_fs_sim.c @@ -17,10 +17,6 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ #include <errno.h> diff --git a/src/sim/card_fs_tetra.c b/src/sim/card_fs_tetra.c index 12853a52..597e38b7 100644 --- a/src/sim/card_fs_tetra.c +++ b/src/sim/card_fs_tetra.c @@ -17,10 +17,6 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ #include <errno.h> diff --git a/src/sim/card_fs_uicc.c b/src/sim/card_fs_uicc.c index 5dcaaa12..87def0e4 100644 --- a/src/sim/card_fs_uicc.c +++ b/src/sim/card_fs_uicc.c @@ -17,10 +17,6 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ diff --git a/src/sim/card_fs_usim.c b/src/sim/card_fs_usim.c index 8f880e75..8cff3fc3 100644 --- a/src/sim/card_fs_usim.c +++ b/src/sim/card_fs_usim.c @@ -15,10 +15,6 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ diff --git a/src/sim/class_tables.c b/src/sim/class_tables.c index 6f541ee2..29c1e40e 100644 --- a/src/sim/class_tables.c +++ b/src/sim/class_tables.c @@ -13,17 +13,13 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include <stdint.h> #include <osmocom/core/utils.h> #include <osmocom/sim/class_tables.h> -static const uint8_t iso7816_ins_tbl[] = { +static const uint8_t iso7816_ins_tbl[256] = { [0xB0] = 2, /* READ BIN */ [0xD0] = 3, /* WRITE BIN */ [0xD6] = 3, /* UPDATE BIN */ @@ -117,7 +113,7 @@ static const uint8_t uicc_ins_tbl_046[256] = { [0xD6] = 3, /* UPDATE BINARY */ [0xB2] = 2, /* READ RECORD */ [0xDC] = 3, /* UPDATE RECORD */ - [0xA2] = 4, /* SEEK */ + [0xA2] = 4, /* SEARCH RECORD */ [0x20] = 3, /* VERIFY PIN */ [0x24] = 3, /* CHANGE PIN */ [0x26] = 3, /* DISABLE PIN */ @@ -177,6 +173,24 @@ static int uicc046_cla_ins_helper(const struct osim_cla_ins_case *cic, return 0; } +static int gp_cla_ins_helper(const struct osim_cla_ins_case *cic, + const uint8_t *hdr) +{ + uint8_t ins = hdr[1]; + uint8_t p1 = hdr[2]; + + switch (ins) { + case 0xE2: /* STORE DATA */ + switch (p1 & 0x01) { + case 1: + return 4; + default: + return 3; + } + } + return 0; +} + /* ETSI TS 102 221, Table 10.5, CLA = 0x8x, 0xCx or 0xEx */ static const uint8_t uicc_ins_tbl_8ce[256] = { [0xF2] = 2, /* STATUS */ @@ -184,6 +198,7 @@ static const uint8_t uicc_ins_tbl_8ce[256] = { [0xCB] = 4, /* RETRIEVE DATA */ [0xDB] = 3, /* SET DATA */ [0xAA] = 3, /* TERMINAL CAPABILITY */ + [0x78] = 4, /* GET IDENTITY */ }; /* ETSI TS 102 221, Table 10.5, CLA = 0x80 */ @@ -192,6 +207,22 @@ static const uint8_t uicc_ins_tbl_80[256] = { [0xC2] = 4, /* ENVELOPE */ [0x12] = 2, /* FETCH */ [0x14] = 3, /* TERMINAL RESPONSE */ + [0x76] = 4, /* SUSPEND UICC */ + [0x7A] = 4, /* EXCHANGE CAPABILITIES */ +}; + +/* Card Specification v2.3.1*/ +static const uint8_t gp_ins_tbl_8ce[256] = { + [0xE4] = 4, /* DELETE */ + [0xE2] = 0x80, /* STORE DATA */ + [0xCA] = 4, /* GET DATA */ + [0xCB] = 4, /* GET DATA */ + [0xF2] = 4, /* GET STATUS */ + [0xE6] = 4, /* INSTALL */ + [0xE8] = 4, /* LOAD */ + [0xD8] = 4, /* PUT KEY */ + [0xF0] = 3, /* SET STATUS */ + [0xC0] = 2, /* GET RESPONSE */ }; static const struct osim_cla_ins_case uicc_ins_case[] = { @@ -226,6 +257,21 @@ static const struct osim_cla_ins_case uicc_ins_case[] = { .cla = 0xE0, .cla_mask = 0xF0, .ins_tbl = uicc_ins_tbl_8ce, + }, { + .cla = 0x80, + .cla_mask = 0xF0, + .helper = gp_cla_ins_helper, + .ins_tbl = gp_ins_tbl_8ce, + }, { + .cla = 0xC0, + .cla_mask = 0xF0, + .helper = gp_cla_ins_helper, + .ins_tbl = gp_ins_tbl_8ce, + }, { + .cla = 0xE0, + .cla_mask = 0xF0, + .helper = gp_cla_ins_helper, + .ins_tbl = gp_ins_tbl_8ce, }, }; @@ -273,7 +319,23 @@ static const struct osim_cla_ins_case uicc_sim_ins_case[] = { .cla = 0xE0, .cla_mask = 0xF0, .ins_tbl = uicc_ins_tbl_8ce, + }, { + .cla = 0x80, + .cla_mask = 0xF0, + .helper = gp_cla_ins_helper, + .ins_tbl = gp_ins_tbl_8ce, + }, { + .cla = 0xC0, + .cla_mask = 0xF0, + .helper = gp_cla_ins_helper, + .ins_tbl = gp_ins_tbl_8ce, + }, { + .cla = 0xE0, + .cla_mask = 0xF0, + .helper = gp_cla_ins_helper, + .ins_tbl = gp_ins_tbl_8ce, }, + }; const struct osim_cla_ins_card_profile osim_uicc_sim_cic_profile = { @@ -288,6 +350,13 @@ const uint8_t usim_ins_case[256] = { [0x88] = 4, /* AUTHENTICATE */ }; +/* https://learn.microsoft.com/en-us/windows-hardware/drivers/smartcard/discovery-process */ +static const uint8_t microsoft_discovery_ins_tbl[256] = { + [0xA4] = 4, /* SELECT FILE */ + [0xCA] = 2, /* GET DATA */ + [0xC0] = 2, /* GET RESPONSE */ +}; + int osim_determine_apdu_case(const struct osim_cla_ins_card_profile *prof, const uint8_t *hdr) { @@ -305,12 +374,23 @@ int osim_determine_apdu_case(const struct osim_cla_ins_card_profile *prof, case 0x80: return cic->helper(cic, hdr); case 0x00: - /* continue with fruther cic, rather than abort + /* continue with further cic, rather than abort * now */ continue; default: return rc; } } + /* special handling for Microsoft who insists to use INS=0xCA in CLA=0x00 which is not + * really part of GSM SIM, ETSI UICC or 3GPP USIM specifications, but only ISO7816. Rather than adding + * it to each and every card profile, let's add the instructions listed at + * https://learn.microsoft.com/en-us/windows-hardware/drivers/smartcard/discovery-process explicitly + * here. They will only be used in case no more specific match was found in the actual profile above. */ + if (cla == 0x00) { + rc = microsoft_discovery_ins_tbl[ins]; + if (rc) + return rc; + } + return 0; } diff --git a/src/sim/core.c b/src/sim/core.c index d11c2d83..fa17e12d 100644 --- a/src/sim/core.c +++ b/src/sim/core.c @@ -17,10 +17,6 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ diff --git a/src/sim/reader.c b/src/sim/reader.c index 7f3f18d9..982b2eef 100644 --- a/src/sim/reader.c +++ b/src/sim/reader.c @@ -17,10 +17,6 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ @@ -44,7 +40,7 @@ static int get_sw(struct msgb *resp) { int ret; - if (!msgb_apdu_de(resp) || msgb_apdu_le(resp) < 2) + if (!resp->l2h || msgb_apdu_le(resp) < 2) return -EIO; ret = msgb_get_u16(resp); diff --git a/src/sim/reader_pcsc.c b/src/sim/reader_pcsc.c index fa867c08..c37072ee 100644 --- a/src/sim/reader_pcsc.c +++ b/src/sim/reader_pcsc.c @@ -17,10 +17,6 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ diff --git a/src/usb/Makefile.am b/src/usb/Makefile.am index 2dee434b..c7d7a2a2 100644 --- a/src/usb/Makefile.am +++ b/src/usb/Makefile.am @@ -1,9 +1,9 @@ # This is _NOT_ the library release version, it's an API version. # Please read chapter "Library interface versions" of the libtool documentation # before making any modifications: https://www.gnu.org/software/libtool/manual/html_node/Versioning.html -LIBVERSION=0:0:0 +LIBVERSION=0:1:0 -AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)/include +AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_builddir)/include -I$(top_builddir) AM_CFLAGS = -fPIC -Wall $(LIBUSB_CFLAGS) $(TALLOC_CFLAGS) AM_LDFLAGS = $(COVERAGE_LDFLAGS) @@ -12,9 +12,12 @@ if ENABLE_LIBUSB lib_LTLIBRARIES = libosmousb.la libosmousb_la_SOURCES = osmo_libusb.c -libosmousb_la_LDFLAGS = -version-info $(LIBVERSION) +libosmousb_la_LDFLAGS = \ + -version-info $(LIBVERSION) \ + -no-undefined \ + $(NULL) libosmousb_la_LIBADD = \ - $(top_builddir)/src/libosmocore.la \ + $(top_builddir)/src/core/libosmocore.la \ $(TALLOC_LIBS) \ $(LIBUSB_LIBS) diff --git a/src/usb/osmo_libusb.c b/src/usb/osmo_libusb.c index bb862067..a249d100 100644 --- a/src/usb/osmo_libusb.c +++ b/src/usb/osmo_libusb.c @@ -14,10 +14,6 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include <errno.h> #include <unistd.h> @@ -103,7 +99,7 @@ static void osmo_usb_added_cb(int fd, short events, void *user_data) static void osmo_usb_removed_cb(int fd, void *user_data) { struct osmo_fd *ofd = osmo_fd_get_by_fd(fd); - if (!fd) + if (!ofd) return; osmo_fd_unregister(ofd); talloc_free(ofd); @@ -541,7 +537,8 @@ libusb_device_handle *osmo_libusb_open_claim_interface(void *ctx, libusb_context addr = libusb_get_device_address(*dev); path = osmo_libusb_dev_get_path_buf(pathbuf, sizeof(pathbuf), *dev); if ((ifm->addr && addr == ifm->addr) || - (strlen(ifm->path) && !strcmp(path, ifm->path))) { + (strlen(ifm->path) && !strcmp(path, ifm->path)) || + (!ifm->addr && !strlen(ifm->path) && !list[1] /* only one device */)) { rc = libusb_open(*dev, &usb_devh); if (rc < 0) { fprintf(stderr, "Cannot open device: %s\n", libusb_error_name(rc)); @@ -739,11 +736,15 @@ int osmo_libusb_get_ep_addrs(libusb_device_handle *devh, unsigned int if_num, int osmo_libusb_init(libusb_context **pluctx) { libusb_context *luctx = NULL; + const struct libusb_pollfd **pfds; + int rc; rc = libusb_init(pluctx); - if (rc != 0) + if (rc != 0) { + LOGP(DLUSB, LOGL_ERROR, "Error initializing libusb: %s\n", libusb_strerror(rc)); return rc; + } if (pluctx) luctx = *pluctx; @@ -754,6 +755,17 @@ int osmo_libusb_init(libusb_context **pluctx) libusb_set_pollfd_notifiers(luctx, osmo_usb_added_cb, osmo_usb_removed_cb, luctx); + /* get the initial file descriptors which were created even before during libusb_init() */ + pfds = libusb_get_pollfds(luctx); + if (pfds) { + const struct libusb_pollfd **pfds2 = pfds; + const struct libusb_pollfd *pfd; + /* synthesize 'add' call-backs. not sure why libusb doesn't do that by itself? */ + for (pfd = *pfds2; pfd; pfd = *++pfds2) + osmo_usb_added_cb(pfd->fd, pfd->events, luctx); + libusb_free_pollfds(pfds); + } + return 0; } diff --git a/src/vty/Makefile.am b/src/vty/Makefile.am index 794b96ca..c314a141 100644 --- a/src/vty/Makefile.am +++ b/src/vty/Makefile.am @@ -1,10 +1,10 @@ # This is _NOT_ the library release version, it's an API version. # Please read chapter "Library interface versions" of the libtool documentation # before making any modifications: https://www.gnu.org/software/libtool/manual/html_node/Versioning.html -LIBVERSION=9:0:0 +LIBVERSION=13:0:0 -AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_builddir)/include -AM_CFLAGS = -Wall $(TALLOC_CFLAGS) +AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_builddir)/include -I$(top_builddir) +AM_CFLAGS = -Wall $(TALLOC_CFLAGS) $(PTHREAD_CFLAGS) if ENABLE_VTY lib_LTLIBRARIES = libosmovty.la @@ -14,5 +14,5 @@ libosmovty_la_SOURCES = buffer.c command.c vty.c vector.c utils.c \ fsm_vty.c talloc_ctx_vty.c \ cpu_sched_vty.c tdef_vty.c libosmovty_la_LDFLAGS = -version-info $(LIBVERSION) -no-undefined -libosmovty_la_LIBADD = $(top_builddir)/src/libosmocore.la $(TALLOC_LIBS) +libosmovty_la_LIBADD = $(top_builddir)/src/core/libosmocore.la $(TALLOC_LIBS) $(PTHREAD_LIBS) endif diff --git a/src/vty/command.c b/src/vty/command.c index bb6a6651..a3e0e36b 100644 --- a/src/vty/command.c +++ b/src/vty/command.c @@ -77,6 +77,21 @@ vector cmdvec; /* Host information structure. */ struct host host; +struct vty_parent_node { + struct llist_head entry; + + /*! private data, specified by creator */ + void *priv; + void *index; + + /*! Node status of this vty */ + int node; + + /*! When reading from a config file, these are the indenting characters expected for children of + * this VTY node. */ + char *indent; +}; + /* Standard command node structures. */ struct cmd_node auth_node = { AUTH_NODE, @@ -1092,6 +1107,7 @@ static int config_write_host(struct vty *vty) static vector cmd_node_vector(vector v, enum node_type ntype) { struct cmd_node *cnode = vector_slot(v, ntype); + OSMO_ASSERT(cnode != NULL); return cnode->cmd_vector; } @@ -1349,7 +1365,6 @@ static enum match_type cmd_ipv6_prefix_match(const char *str) int colons = 0, nums = 0, double_colon = 0; int mask; const char *sp = NULL; - char *endptr = NULL; if (str == NULL) return PARTLY_MATCH; @@ -1447,11 +1462,7 @@ static enum match_type cmd_ipv6_prefix_match(const char *str) if (state < STATE_MASK) return PARTLY_MATCH; - mask = strtol(str, &endptr, 10); - if (*endptr != '\0') - return NO_MATCH; - - if (mask < 0 || mask > 128) + if (osmo_str_to_int(&mask, str, 10, 0, 128)) return NO_MATCH; /* I don't know why mask < 13 makes command match partly. @@ -1484,19 +1495,52 @@ static enum match_type cmd_ipv6_prefix_match(const char *str) #error "LONG_MAX not defined!" #endif +/* This function is aimed at quickly guessing & filtering the numeric base a + * string can contain, by no means validates the entire value. + * Returns 16 if string follows pattern "({+,-}0x[digits])" + * Returns 10 if string follows pattern "({+,-}[digits])" + * Returns a negative value if something other is detected (error) +*/ +static int check_base(const char *str) +{ + const char *ptr = str; + /* Skip any space */ + while (isspace(*ptr)) ptr++; + + /* Skip optional sign: */ + if (*ptr == '+' || *ptr == '-') + ptr++; + if (*ptr == '0') { + ptr++; + if (*ptr == '\0' || isdigit(*ptr)) + return 10; + if (!(*ptr == 'x' || *ptr == 'X')) + return -1; + ptr++; + if (isxdigit(*ptr)) + return 16; + return -1; + } + return 10; +} + int vty_cmd_range_match(const char *range, const char *str) { char *p; char buf[DECIMAL_STRLEN_MAX_UNSIGNED + 1]; char *endptr = NULL; + int min_base, max_base, val_base; if (str == NULL) return 1; + if ((val_base = check_base(str)) < 0) + return 0; + if (range[1] == '-') { signed long min = 0, max = 0, val; - val = strtol(str, &endptr, 10); + val = strtol(str, &endptr, val_base); if (*endptr != '\0') return 0; @@ -1508,7 +1552,9 @@ int vty_cmd_range_match(const char *range, const char *str) return 0; strncpy(buf, range, p - range); buf[p - range] = '\0'; - min = -strtol(buf, &endptr, 10); + if ((min_base = check_base(buf)) < 0) + return 0; + min = -strtol(buf, &endptr, min_base); if (*endptr != '\0') return 0; @@ -1520,7 +1566,9 @@ int vty_cmd_range_match(const char *range, const char *str) return 0; strncpy(buf, range, p - range); buf[p - range] = '\0'; - max = strtol(buf, &endptr, 10); + if ((max_base = check_base(buf)) < 0) + return 0; + max = strtol(buf, &endptr, max_base); if (*endptr != '\0') return 0; @@ -1532,7 +1580,7 @@ int vty_cmd_range_match(const char *range, const char *str) if (str[0] == '-') return 0; - val = strtoul(str, &endptr, 10); + val = strtoul(str, &endptr, val_base); if (*endptr != '\0') return 0; @@ -1544,7 +1592,9 @@ int vty_cmd_range_match(const char *range, const char *str) return 0; strncpy(buf, range, p - range); buf[p - range] = '\0'; - min = strtoul(buf, &endptr, 10); + if ((min_base = check_base(buf)) < 0) + return 0; + min = strtoul(buf, &endptr, min_base); if (*endptr != '\0') return 0; @@ -1556,7 +1606,9 @@ int vty_cmd_range_match(const char *range, const char *str) return 0; strncpy(buf, range, p - range); buf[p - range] = '\0'; - max = strtoul(buf, &endptr, 10); + if ((max_base = check_base(buf)) < 0) + return 0; + max = strtoul(buf, &endptr, max_base); if (*endptr != '\0') return 0; @@ -1564,6 +1616,14 @@ int vty_cmd_range_match(const char *range, const char *str) return 0; } + /* Don't allow ranges specified by min and max with different bases */ + if (min_base != max_base) + return 0; + /* arg value passed must match the base of the range */ + if (min_base != val_base) + return 0; + + /* Everything's fine */ return 1; } @@ -2393,6 +2453,7 @@ static bool vty_pop_parent(struct vty *vty) llist_del(&parent->entry); vty->node = parent->node; vty->priv = parent->priv; + vty->index = parent->index; if (vty->indent) talloc_free(vty->indent); vty->indent = parent->indent; @@ -2603,6 +2664,7 @@ cmd_execute_command_real(vector vline, struct vty *vty, struct vty_parent_node this_node = { .node = vty->node, .priv = vty->priv, + .index = vty->index, .indent = vty->indent, }; struct vty_parent_node *parent = vty_parent(vty); @@ -2878,6 +2940,7 @@ int config_from_file(struct vty *vty, FILE * fp) this_node = (struct vty_parent_node){ .node = vty->node, .priv = vty->priv, + .index = vty->index, .indent = vty->indent, }; @@ -2934,7 +2997,7 @@ return_invalid_indent: /* Configration from terminal */ DEFUN(config_terminal, config_terminal_cmd, - "configure terminal", + "configure [terminal]", "Configuration from vty interface\n" "Configuration terminal\n") { if (vty_config_lock(vty)) @@ -3066,18 +3129,9 @@ DEFUN(show_pid, DEFUN(show_uptime, show_uptime_cmd, "show uptime", SHOW_STR "Displays how long the program has been running\n") { - struct timespec now; - struct timespec uptime; - - osmo_clock_gettime(CLOCK_MONOTONIC, &now); - timespecsub(&now, &starttime, &uptime); - - int d = uptime.tv_sec / (3600 * 24); - int h = uptime.tv_sec / 3600 % 24; - int m = uptime.tv_sec / 60 % 60; - int s = uptime.tv_sec % 60; - - vty_out(vty, "%s has been running for %dd %dh %dm %ds%s", host.app_info->name, d, h, m, s, VTY_NEWLINE); + vty_out(vty, "%s has been running for ", host.app_info->name); + vty_out_uptime(vty, &starttime); + vty_out_newline(vty); return CMD_SUCCESS; } @@ -3803,16 +3857,7 @@ DEFUN(config_terminal_length, config_terminal_length_cmd, "Set number of lines on a screen\n" "Number of lines on screen (0 for no pausing)\n") { - int lines; - char *endptr = NULL; - - lines = strtol(argv[0], &endptr, 10); - if (lines < 0 || lines > 512 || *endptr != '\0') { - vty_out(vty, "length is malformed%s", VTY_NEWLINE); - return CMD_WARNING; - } - vty->lines = lines; - + vty->lines = atoi(argv[0]); return CMD_SUCCESS; } @@ -3831,16 +3876,7 @@ DEFUN(service_terminal_length, service_terminal_length_cmd, "System wide terminal length configuration\n" "Number of lines of VTY (0 means no line control)\n") { - int lines; - char *endptr = NULL; - - lines = strtol(argv[0], &endptr, 10); - if (lines < 0 || lines > 512 || *endptr != '\0') { - vty_out(vty, "length is malformed%s", VTY_NEWLINE); - return CMD_WARNING; - } - host.lines = lines; - + host.lines = atoi(argv[0]); return CMD_SUCCESS; } diff --git a/src/vty/cpu_sched_vty.c b/src/vty/cpu_sched_vty.c index 4ccc6274..7198f747 100644 --- a/src/vty/cpu_sched_vty.c +++ b/src/vty/cpu_sched_vty.c @@ -25,6 +25,8 @@ #define _GNU_SOURCE +#include "config.h" + #include <string.h> #include <stdlib.h> #include <errno.h> @@ -87,7 +89,7 @@ static struct cmd_node sched_node = { }; /* returns number of configured CPUs in the system, or negative otherwise */ -static int get_num_cpus() { +static int get_num_cpus(void) { static unsigned int num_cpus = 0; long ln; @@ -276,7 +278,6 @@ static bool proc_name_exists(const char *name, pid_t *res_pid) static enum sched_vty_thread_id procname2pid(pid_t *res_pid, const char *str, bool applynow) { size_t i, len; - char *end; bool is_pid = true; if (strcmp(str, "all") == 0) { @@ -297,12 +298,12 @@ static enum sched_vty_thread_id procname2pid(pid_t *res_pid, const char *str, bo } } if (is_pid) { - errno = 0; - *res_pid = strtoul(str, &end, 0); - if ((errno == ERANGE && *res_pid == ULONG_MAX) || (errno && !*res_pid) || - str == end) { + int64_t val; + if (osmo_str_to_int64(&val, str, 0, 0, INT64_MAX)) + return SCHED_VTY_THREAD_UNKNOWN; + *res_pid = (pid_t)val; + if (*res_pid != val) return SCHED_VTY_THREAD_UNKNOWN; - } if (!applynow || proc_tid_exists(*res_pid)) return SCHED_VTY_THREAD_ID; else @@ -638,8 +639,10 @@ int osmo_cpu_sched_vty_apply_localthread(void) return 0; } +#ifdef HAVE_PTHREAD_GETNAME_NP if (pthread_getname_np(pthread_self(), name, sizeof(name)) == 0) has_name = true; +#endif /* Get latest matching mask for the thread */ pthread_mutex_lock(&sched_vty_opts->cpu_affinity_li_mutex); diff --git a/src/vty/fsm_vty.c b/src/vty/fsm_vty.c index 83a8e791..da6038fa 100644 --- a/src/vty/fsm_vty.c +++ b/src/vty/fsm_vty.c @@ -14,16 +14,12 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ #include <stdlib.h> #include <string.h> -#include "../../config.h" +#include "config.h" #include <osmocom/vty/command.h> #include <osmocom/vty/buffer.h> @@ -65,7 +61,7 @@ void vty_out_fsm2(struct vty *vty, const char *prefix, struct osmo_fsm *fsm) if (fsm->event_names) { for (evt_name = fsm->event_names; evt_name->str != NULL; evt_name++) { vty_out(vty, "%s Event %02u (0x%08x): '%s'%s", prefix, evt_name->value, - (1 << evt_name->value), evt_name->str, VTY_NEWLINE); + (1U << evt_name->value), evt_name->str, VTY_NEWLINE); } } else vty_out(vty, "%s No event names are defined for this FSM! Please fix!%s", prefix, VTY_NEWLINE); diff --git a/src/vty/logging_vty.c b/src/vty/logging_vty.c index 3b131c27..678ae686 100644 --- a/src/vty/logging_vty.c +++ b/src/vty/logging_vty.c @@ -15,16 +15,12 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ #include <stdlib.h> #include <string.h> -#include "../../config.h" +#include "config.h" #include <osmocom/core/talloc.h> #include <osmocom/core/logging.h> @@ -386,6 +382,10 @@ DEFUN(logging_level, tgt->categories[category].enabled = 1; tgt->categories[category].loglevel = level; +#if !defined(EMBEDDED) + log_cache_update(category, 1, level); +#endif + RET_WITH_UNLOCK(CMD_SUCCESS); } @@ -410,6 +410,9 @@ DEFUN(logging_level_set_all, logging_level_set_all_cmd, cat->enabled = 1; cat->loglevel = level; +#if !defined(EMBEDDED) + log_cache_update(i, 1, level); +#endif } RET_WITH_UNLOCK(CMD_SUCCESS); } @@ -834,9 +837,31 @@ DEFUN(cfg_log_gsmtap, cfg_log_gsmtap_cmd, RET_WITH_UNLOCK(CMD_SUCCESS); } +DEFUN(cfg_no_log_gsmtap, cfg_no_log_gsmtap_cmd, + "no log gsmtap [HOSTNAME]", + NO_STR LOG_STR "Logging via GSMTAP\n" + "Host name to send the GSMTAP logging to (UDP port 4729)\n") +{ + const char *hostname = argc ? argv[0] : "127.0.0.1"; + struct log_target *tgt; + + log_tgt_mutex_lock(); + tgt = log_target_find(LOG_TGT_TYPE_GSMTAP, hostname); + if (tgt == NULL) { + vty_out(vty, "%% Unable to find GSMTAP log target for %s%s", + hostname, VTY_NEWLINE); + RET_WITH_UNLOCK(CMD_WARNING); + } + + log_target_destroy(tgt); + + RET_WITH_UNLOCK(CMD_SUCCESS); +} + DEFUN(cfg_log_stderr, cfg_log_stderr_cmd, - "log stderr", - LOG_STR "Logging via STDERR of the process\n") + "log stderr [blocking-io]", + LOG_STR "Logging via STDERR of the process\n" + "Use blocking, synchronous I/O\n") { struct log_target *tgt; @@ -852,6 +877,11 @@ DEFUN(cfg_log_stderr, cfg_log_stderr_cmd, log_add_target(tgt); } + if (argc > 0 && !strcmp(argv[0], "blocking-io")) + log_target_file_switch_to_stream(tgt); + else + log_target_file_switch_to_wqueue(tgt); + vty->index = tgt; vty->node = CFG_LOG_NODE; @@ -878,8 +908,9 @@ DEFUN(cfg_no_log_stderr, cfg_no_log_stderr_cmd, } DEFUN(cfg_log_file, cfg_log_file_cmd, - "log file .FILENAME", - LOG_STR "Logging to text file\n" "Filename\n") + "log file FILENAME [blocking-io]", + LOG_STR "Logging to text file\n" "Filename\n" + "Use blocking, synchronous I/O\n") { const char *fname = argv[0]; struct log_target *tgt; @@ -896,6 +927,11 @@ DEFUN(cfg_log_file, cfg_log_file_cmd, log_add_target(tgt); } + if (argc > 1 && !strcmp(argv[1], "blocking-io")) + log_target_file_switch_to_stream(tgt); + else + log_target_file_switch_to_wqueue(tgt); + vty->index = tgt; vty->node = CFG_LOG_NODE; @@ -904,7 +940,7 @@ DEFUN(cfg_log_file, cfg_log_file_cmd, DEFUN(cfg_no_log_file, cfg_no_log_file_cmd, - "no log file .FILENAME", + "no log file FILENAME", NO_STR LOG_STR "Logging to text file\n" "Filename\n") { const char *fname = argv[0]; @@ -979,7 +1015,10 @@ static int config_write_log_single(struct vty *vty, struct log_target *tgt) return 1; break; case LOG_TGT_TYPE_STDERR: - vty_out(vty, "log stderr%s", VTY_NEWLINE); + if (tgt->tgt_file.wqueue) + vty_out(vty, "log stderr%s", VTY_NEWLINE); + else + vty_out(vty, "log stderr blocking-io%s", VTY_NEWLINE); break; case LOG_TGT_TYPE_SYSLOG: #ifdef HAVE_SYSLOG_H @@ -990,7 +1029,10 @@ static int config_write_log_single(struct vty *vty, struct log_target *tgt) #endif break; case LOG_TGT_TYPE_FILE: - vty_out(vty, "log file %s%s", tgt->tgt_file.fname, VTY_NEWLINE); + if (tgt->tgt_file.wqueue) + vty_out(vty, "log file %s%s", tgt->tgt_file.fname, VTY_NEWLINE); + else + vty_out(vty, "log file %s blocking-io%s", tgt->tgt_file.fname, VTY_NEWLINE); break; case LOG_TGT_TYPE_STRRB: vty_out(vty, "log alarms %zu%s", @@ -1161,7 +1203,7 @@ static void gen_vty_logp_cmd_strs(struct cmd_element *cmd) /*! Register logging related commands to the VTY. Call this once from * your application if you want to support those commands. */ -void logging_vty_add_cmds() +void logging_vty_add_cmds(void) { install_lib_element_ve(&enable_logging_cmd); install_lib_element_ve(&disable_logging_cmd); @@ -1231,4 +1273,5 @@ void logging_vty_add_cmds() install_lib_element(CONFIG_NODE, &cfg_log_systemd_journal_cmd); install_lib_element(CONFIG_NODE, &cfg_no_log_systemd_journal_cmd); install_lib_element(CONFIG_NODE, &cfg_log_gsmtap_cmd); + install_lib_element(CONFIG_NODE, &cfg_no_log_gsmtap_cmd); } diff --git a/src/vty/stats_vty.c b/src/vty/stats_vty.c index c9ae0fbc..f940018f 100644 --- a/src/vty/stats_vty.c +++ b/src/vty/stats_vty.c @@ -1,5 +1,5 @@ /* - * (C) 2009-2010 by Harald Welte <laforge@gnumonks.org> + * (C) 2009-2022 by Harald Welte <laforge@gnumonks.org> * (C) 2009-2014 by Holger Hans Peter Freyther * (C) 2015 by sysmocom - s.f.m.c. GmbH * All Rights Reserved @@ -16,16 +16,12 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ #include <stdlib.h> #include <string.h> -#include "../../config.h" +#include "config.h" #include <osmocom/vty/command.h> #include <osmocom/vty/buffer.h> @@ -37,11 +33,13 @@ #include <osmocom/core/stats.h> #include <osmocom/core/counter.h> #include <osmocom/core/rate_ctr.h> +#include <osmocom/core/stats_tcp.h> #define CFG_STATS_STR "Configure stats sub-system\n" #define CFG_REPORTER_STR "Configure a stats reporter\n" #define SHOW_STATS_STR "Show statistical values\n" +#define SKIP_ZERO_STR "Skip items with total count zero\n" #define STATS_STR "Stats related commands\n" @@ -269,14 +267,20 @@ DEFUN(cfg_stats_reporter_flush_period, cfg_stats_reporter_flush_period_cmd, } DEFUN(cfg_stats_reporter_statsd, cfg_stats_reporter_statsd_cmd, - "stats reporter statsd", - CFG_STATS_STR CFG_REPORTER_STR "Report to a STATSD server\n") + "stats reporter statsd [NAME]", + CFG_STATS_STR CFG_REPORTER_STR + "Report to a STATSD server\n" + "Name of the reporter\n") { struct osmo_stats_reporter *srep; + const char *name = NULL; + + if (argc > 0) + name = argv[0]; - srep = osmo_stats_reporter_find(OSMO_STATS_REPORTER_STATSD, NULL); + srep = osmo_stats_reporter_find(OSMO_STATS_REPORTER_STATSD, name); if (!srep) { - srep = osmo_stats_reporter_create_statsd(NULL); + srep = osmo_stats_reporter_create_statsd(name); if (!srep) { vty_out(vty, "%% Unable to create statsd reporter%s", VTY_NEWLINE); @@ -293,15 +297,21 @@ DEFUN(cfg_stats_reporter_statsd, cfg_stats_reporter_statsd_cmd, } DEFUN(cfg_no_stats_reporter_statsd, cfg_no_stats_reporter_statsd_cmd, - "no stats reporter statsd", - NO_STR CFG_STATS_STR CFG_REPORTER_STR "Report to a STATSD server\n") + "no stats reporter statsd [NAME]", + NO_STR CFG_STATS_STR CFG_REPORTER_STR + "Report to a STATSD server\n" + "Name of the reporter\n") { struct osmo_stats_reporter *srep; + const char *name = NULL; + + if (argc > 0) + name = argv[0]; - srep = osmo_stats_reporter_find(OSMO_STATS_REPORTER_STATSD, NULL); + srep = osmo_stats_reporter_find(OSMO_STATS_REPORTER_STATSD, name); if (!srep) { - vty_out(vty, "%% No statsd logging active%s", - VTY_NEWLINE); + vty_out(vty, "%% There is no such statsd reporter with name '%s'%s", + name ? name : "", VTY_NEWLINE); return CMD_WARNING; } @@ -311,14 +321,20 @@ DEFUN(cfg_no_stats_reporter_statsd, cfg_no_stats_reporter_statsd_cmd, } DEFUN(cfg_stats_reporter_log, cfg_stats_reporter_log_cmd, - "stats reporter log", - CFG_STATS_STR CFG_REPORTER_STR "Report to the logger\n") + "stats reporter log [NAME]", + CFG_STATS_STR CFG_REPORTER_STR + "Report to the logger\n" + "Name of the reporter\n") { struct osmo_stats_reporter *srep; + const char *name = NULL; + + if (argc > 0) + name = argv[0]; - srep = osmo_stats_reporter_find(OSMO_STATS_REPORTER_LOG, NULL); + srep = osmo_stats_reporter_find(OSMO_STATS_REPORTER_LOG, name); if (!srep) { - srep = osmo_stats_reporter_create_log(NULL); + srep = osmo_stats_reporter_create_log(name); if (!srep) { vty_out(vty, "%% Unable to create log reporter%s", VTY_NEWLINE); @@ -335,15 +351,21 @@ DEFUN(cfg_stats_reporter_log, cfg_stats_reporter_log_cmd, } DEFUN(cfg_no_stats_reporter_log, cfg_no_stats_reporter_log_cmd, - "no stats reporter log", - NO_STR CFG_STATS_STR CFG_REPORTER_STR "Report to the logger\n") + "no stats reporter log [NAME]", + NO_STR CFG_STATS_STR CFG_REPORTER_STR + "Report to the logger\n" + "Name of the reporter\n") { struct osmo_stats_reporter *srep; + const char *name = NULL; - srep = osmo_stats_reporter_find(OSMO_STATS_REPORTER_LOG, NULL); + if (argc > 0) + name = argv[0]; + + srep = osmo_stats_reporter_find(OSMO_STATS_REPORTER_LOG, name); if (!srep) { - vty_out(vty, "%% No log reporting active%s", - VTY_NEWLINE); + vty_out(vty, "%% There is no such log reporter with name '%s'%s", + name ? name : "", VTY_NEWLINE); return CMD_WARNING; } @@ -369,27 +391,60 @@ DEFUN(cfg_stats_interval, cfg_stats_interval_cmd, return CMD_SUCCESS; } +DEFUN(cfg_tcp_stats_interval, cfg_tcp_stats_interval_cmd, + "stats-tcp interval <0-65535>", + CFG_STATS_STR "Set the tcp socket stats polling interval\n" + "Interval in seconds (0 disables the polling interval)\n") +{ + int rc; + int interval = atoi(argv[0]); + rc = osmo_stats_tcp_set_interval(interval); + if (rc < 0) { + vty_out(vty, "%% Unable to set interval: %s%s", + strerror(-rc), VTY_NEWLINE); + return CMD_WARNING; + } + + return CMD_SUCCESS; +} + +DEFUN(cfg_tcp_stats_batch_size, cfg_tcp_stats_batch_size_cmd, + "stats-tcp batch-size <1-65535>", + CFG_STATS_STR "Set the number of tcp sockets that are processed per stats polling interval\n" + "Number of sockets per interval\n") +{ + osmo_tcp_stats_config->batch_size = atoi(argv[0]); + return CMD_SUCCESS; +} + DEFUN(show_stats, show_stats_cmd, - "show stats", - SHOW_STR SHOW_STATS_STR) + "show stats [skip-zero]", + SHOW_STR SHOW_STATS_STR SKIP_ZERO_STR) { - vty_out_statistics_full(vty, ""); + bool skip_zero = false; + if (argc > 0) + skip_zero = true; + + vty_out_statistics_full2(vty, "", skip_zero); return CMD_SUCCESS; } DEFUN(show_stats_level, show_stats_level_cmd, - "show stats level (global|peer|subscriber)", + "show stats level (global|peer|subscriber) [skip-zero]", SHOW_STR SHOW_STATS_STR "Set the maximum group level\n" "Show global groups only\n" "Show global and network peer related groups\n" - "Show global, peer, and subscriber groups\n") + "Show global, peer, and subscriber groups\n" SKIP_ZERO_STR) { int level = get_string_value(stats_class_strs, argv[0]); - vty_out_statistics_partial(vty, "", level); + bool skip_zero = false; + if (argc > 1) + skip_zero = true; + vty_out_statistics_partial2(vty, "", level, skip_zero); return CMD_SUCCESS; } @@ -475,10 +530,11 @@ static int asciidoc_osmo_stat_item_handler( { struct vty *vty = sctx_; - char *name = osmo_asciidoc_escape(item->desc->name); - char *description = osmo_asciidoc_escape(item->desc->description); + const struct osmo_stat_item_desc *desc = osmo_stat_item_get_desc(item); + char *name = osmo_asciidoc_escape(desc->name); + char *description = osmo_asciidoc_escape(desc->description); char *group_name_prefix = osmo_asciidoc_escape(statg->desc->group_name_prefix); - char *unit = osmo_asciidoc_escape(item->desc->unit); + char *unit = osmo_asciidoc_escape(desc->unit); /* | Name | Reference | Description | Unit | */ vty_out(vty, "| %s | <<%s_%s>> | %s | %s%s", @@ -551,23 +607,32 @@ DEFUN(show_stats_asciidoc_table, return CMD_SUCCESS; } +struct rctr_vty_ctx { + struct vty *vty; + bool skip_zero; +}; + static int rate_ctr_group_handler(struct rate_ctr_group *ctrg, void *sctx_) { - struct vty *vty = sctx_; + struct rctr_vty_ctx *sctx = sctx_; + struct vty *vty = sctx->vty; vty_out(vty, "%s %u", ctrg->desc->group_description, ctrg->idx); if (ctrg->name != NULL) vty_out(vty, " (%s)", ctrg->name); vty_out(vty, ":%s", VTY_NEWLINE); - vty_out_rate_ctr_group_fmt(vty, "%25n: %10c (%S/s %M/m %H/h %D/d) %d", ctrg); + vty_out_rate_ctr_group_fmt2(vty, "%25n: %10c (%S/s %M/m %H/h %D/d) %d", ctrg, sctx->skip_zero); return 0; } DEFUN(show_rate_counters, show_rate_counters_cmd, - "show rate-counters", - SHOW_STR "Show all rate counters\n") + "show rate-counters [skip-zero]", + SHOW_STR "Show all rate counters\n" SKIP_ZERO_STR) { - rate_ctr_for_each_group(rate_ctr_group_handler, vty); + struct rctr_vty_ctx rctx = { .vty = vty, .skip_zero = false }; + if (argc > 0) + rctx.skip_zero = true; + rate_ctr_for_each_group(rate_ctr_group_handler, &rctx); return CMD_SUCCESS; } @@ -597,19 +662,26 @@ DEFUN(stats_reset, static int config_write_stats_reporter(struct vty *vty, struct osmo_stats_reporter *srep) { - if (srep == NULL) - return 0; + const char *type = NULL; switch (srep->type) { case OSMO_STATS_REPORTER_STATSD: - vty_out(vty, "stats reporter statsd%s", VTY_NEWLINE); + type = "statsd"; break; case OSMO_STATS_REPORTER_LOG: - vty_out(vty, "stats reporter log%s", VTY_NEWLINE); + type = "log"; break; + default: + /* don't try to save unknown stats reporters to the VTY. Imagine some + * application registering a new application specific stats reporter that + * this VTY code knows nothing about! */ + return 0; } - vty_out(vty, " disable%s", VTY_NEWLINE); + vty_out(vty, "stats reporter %s", type); + if (srep->name != NULL) + vty_out(vty, " %s", srep->name); + vty_out(vty, "%s", VTY_NEWLINE); if (srep->have_net_config) { if (srep->dest_addr_str) @@ -643,6 +715,8 @@ static int config_write_stats_reporter(struct vty *vty, struct osmo_stats_report if (srep->enabled) vty_out(vty, " enable%s", VTY_NEWLINE); + else + vty_out(vty, " disable%s", VTY_NEWLINE); return 1; } @@ -651,13 +725,15 @@ static int config_write_stats(struct vty *vty) { struct osmo_stats_reporter *srep; - /* TODO: loop through all reporters */ - srep = osmo_stats_reporter_find(OSMO_STATS_REPORTER_STATSD, NULL); - config_write_stats_reporter(vty, srep); - srep = osmo_stats_reporter_find(OSMO_STATS_REPORTER_LOG, NULL); - config_write_stats_reporter(vty, srep); - vty_out(vty, "stats interval %d%s", osmo_stats_config->interval, VTY_NEWLINE); + if (osmo_tcp_stats_config->interval != TCP_STATS_DEFAULT_INTERVAL) + vty_out(vty, "stats-tcp interval %d%s", osmo_tcp_stats_config->interval, VTY_NEWLINE); + if (osmo_tcp_stats_config->batch_size != TCP_STATS_DEFAULT_BATCH_SIZE) + vty_out(vty, "stats-tcp batch-size %d%s", osmo_tcp_stats_config->batch_size, VTY_NEWLINE); + + /* Loop through all reporters */ + llist_for_each_entry(srep, &osmo_stats_reporter_list, list) + config_write_stats_reporter(vty, srep); return 1; } @@ -666,7 +742,7 @@ static int config_write_stats(struct vty *vty) * Call this once during your application initialization if you would * like to have stats VTY commands enabled. */ -void osmo_stats_vty_add_cmds() +void osmo_stats_vty_add_cmds(void) { install_lib_element_ve(&show_stats_cmd); install_lib_element_ve(&show_stats_level_cmd); @@ -676,6 +752,8 @@ void osmo_stats_vty_add_cmds() install_lib_element(CONFIG_NODE, &cfg_stats_reporter_log_cmd); install_lib_element(CONFIG_NODE, &cfg_no_stats_reporter_log_cmd); install_lib_element(CONFIG_NODE, &cfg_stats_interval_cmd); + install_lib_element(CONFIG_NODE, &cfg_tcp_stats_interval_cmd); + install_lib_element(CONFIG_NODE, &cfg_tcp_stats_batch_size_cmd); install_node(&cfg_stats_node, config_write_stats); diff --git a/src/vty/talloc_ctx_vty.c b/src/vty/talloc_ctx_vty.c index 39664787..ea8ebe70 100644 --- a/src/vty/talloc_ctx_vty.c +++ b/src/vty/talloc_ctx_vty.c @@ -17,17 +17,13 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ #include <stdio.h> #include <regex.h> #include <string.h> -#include <talloc.h> +#include <osmocom/core/talloc.h> #include <osmocom/vty/command.h> #include <osmocom/vty/vty.h> @@ -151,6 +147,8 @@ static void talloc_ctx_walk(const char *ctx, const char *depth, /* Determine a context for report */ if (!strncmp(ctx, "app", 3)) talloc_ctx = host.app_info->tall_ctx; + else if (!strcmp(ctx, "global")) + talloc_ctx = OTC_GLOBAL; else if (!strncmp(ctx, "all", 3)) talloc_ctx = NULL; @@ -167,11 +165,12 @@ static void talloc_ctx_walk(const char *ctx, const char *depth, } #define BASE_CMD_STR \ - "show talloc-context (application|all) (full|brief|DEPTH)" + "show talloc-context (application|global|all) (full|brief|DEPTH)" #define BASE_CMD_DESCR \ SHOW_STR "Show talloc memory hierarchy\n" \ "Application's context\n" \ + "Global context (OTC_GLOBAL)\n" \ "All contexts, if NULL-context tracking is enabled\n" \ "Display a full talloc memory hierarchy\n" \ "Display a brief talloc memory hierarchy\n" \ diff --git a/src/vty/tdef_vty.c b/src/vty/tdef_vty.c index 0556d8c9..bd209ae0 100644 --- a/src/vty/tdef_vty.c +++ b/src/vty/tdef_vty.c @@ -50,10 +50,9 @@ */ struct osmo_tdef *osmo_tdef_vty_parse_T_arg(struct vty *vty, struct osmo_tdef *tdefs, const char *T_str) { - long l; + int l; int T; struct osmo_tdef *t; - char *endptr; const char *T_nr_str; int sign = 1; @@ -77,9 +76,7 @@ struct osmo_tdef *osmo_tdef_vty_parse_T_arg(struct vty *vty, struct osmo_tdef *t return NULL; } - errno = 0; - l = strtol(T_nr_str, &endptr, 10); - if (errno || *endptr || l > INT_MAX || l < 0) { + if (osmo_str_to_int(&l, T_nr_str, 10, 0, INT_MAX)) { vty_out(vty, "%% Invalid T timer argument (should be 'T1234' or 'X1234'): '%s'%s", T_str, VTY_NEWLINE); return NULL; } @@ -245,7 +242,7 @@ void osmo_tdef_vty_out_all(struct vty *vty, struct osmo_tdef *tdefs, const char /*! Write current timer configuration arguments to the vty. Skip all entries that reflect their default value. * The passed prefix string must contain both necessary indent and the VTY command the specific implementation is using. - * See tdef_vty_test_config_subnode.c and tdef_vty_test_dynamic.c for examples. + * See tdef_vty_config_subnode_test.c and tdef_vty_dynamic_test.c for examples. * \param[in] vty VTY context. * \param[in] tdefs Array of timers to print, ended with a fully zero-initialized entry. * \param[in] prefix_fmt Arbitrary string to start each line with, with variable printf like arguments. diff --git a/src/vty/telnet_interface.c b/src/vty/telnet_interface.c index cd32e68f..8fa5dbff 100644 --- a/src/vty/telnet_interface.c +++ b/src/vty/telnet_interface.c @@ -14,10 +14,6 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ #include <sys/socket.h> @@ -46,7 +42,7 @@ * process in order to enable interactive command-line introspection, * interaction and configuration. * - * You typically call \ref telnet_init or \ref telnet_init_dynif once + * You typically call telnet_init_default once * from your application code to enable this. */ @@ -64,26 +60,14 @@ static struct osmo_fd server_socket = { .priv_nr = 0, }; -/*! Initialize telnet based VTY interface listening to 127.0.0.1 - * \param[in] tall_ctx \ref talloc context - * \param[in] priv private data to be passed to callback - * \param[in] port TCP port number to bind to - */ -int telnet_init(void *tall_ctx, void *priv, int port) -{ - return telnet_init_dynif(tall_ctx, priv, "127.0.0.1", port); -} - -/*! Initialize telnet based VTY interface - * \param[in] tall_ctx \ref talloc context - * \param[in] priv private data to be passed to callback - * \param[in] ip IP to listen to ('::1' for localhost, '::0' for all, ...) - * \param[in] port TCP port number to bind to - */ -int telnet_init_dynif(void *tall_ctx, void *priv, const char *ip, int port) +/* Helper for deprecating telnet_init_dynif(), which previously held this code */ +static int _telnet_init_dynif(void *tall_ctx, void *priv, const char *ip, int port) { int rc; + if (port < 0) + return -EINVAL; + tall_telnet_ctx = talloc_named_const(tall_ctx, 1, "telnet_connection"); @@ -98,22 +82,45 @@ int telnet_init_dynif(void *tall_ctx, void *priv, const char *ip, int port) if (rc < 0) { LOGP(DLGLOBAL, LOGL_ERROR, "Cannot bind telnet at %s %d\n", ip, port); - return -1; + return rc; } LOGP(DLGLOBAL, LOGL_NOTICE, "Available via telnet %s %d\n", ip, port); return 0; } +/*! Initialize telnet based VTY interface listening to 127.0.0.1 + * \param[in] tall_ctx \ref talloc context + * \param[in] priv private data to be passed to callback + * \param[in] port TCP port number to bind to + * \deprecated use telnet_init_default() instead + */ +int telnet_init(void *tall_ctx, void *priv, int port) +{ + return _telnet_init_dynif(tall_ctx, priv, "127.0.0.1", port); +} + +/*! Initialize telnet based VTY interface + * \param[in] tall_ctx \ref talloc context + * \param[in] priv private data to be passed to callback + * \param[in] ip IP to listen to ('::1' for localhost, '::0' for all, ...) + * \param[in] port TCP port number to bind to + * \deprecated use telnet_init_default() instead + */ +int telnet_init_dynif(void *tall_ctx, void *priv, const char *ip, int port) +{ + return _telnet_init_dynif(tall_ctx, priv, ip, port); +} + /*! Initializes telnet based VTY interface using the configured bind addr/port. * \param[in] tall_ctx \ref talloc context * \param[in] priv private data to be passed to callback - * \param[in] default_port TCP port number to bind to if not explicitely configured + * \param[in] default_port TCP port number to bind to if not explicitly configured */ int telnet_init_default(void *tall_ctx, void *priv, int default_port) { - return telnet_init_dynif(tall_ctx, priv, vty_get_bind_addr(), - vty_get_bind_port(default_port)); + return _telnet_init_dynif(tall_ctx, priv, vty_get_bind_addr(), + vty_get_bind_port(default_port)); } diff --git a/src/vty/utils.c b/src/vty/utils.c index 1137f2b5..0f5a34e4 100644 --- a/src/vty/utils.c +++ b/src/vty/utils.c @@ -18,13 +18,10 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - * */ #include <stdint.h> +#include <stdbool.h> #include <inttypes.h> #include <string.h> #include <ctype.h> @@ -48,6 +45,7 @@ struct vty_out_context { struct vty *vty; const char *prefix; int max_level; + bool skip_zero; }; static int rate_ctr_handler( @@ -57,6 +55,9 @@ static int rate_ctr_handler( struct vty_out_context *vctx = vctx_; struct vty *vty = vctx->vty; + if (vctx->skip_zero && ctr->current == 0) + return 0; + vty_out(vty, " %s%s: %8" PRIu64 " " "(%" PRIu64 "/s %" PRIu64 "/m %" PRIu64 "/h %" PRIu64 "/d)%s", vctx->prefix, desc->description, ctr->current, @@ -73,17 +74,24 @@ static int rate_ctr_handler( * \param[in] vty The VTY to which it should be printed * \param[in] prefix Any additional log prefix ahead of each line * \param[in] ctrg Rate counter group to be printed + * \param[in] skip_zero Skip all zero-valued counters */ -void vty_out_rate_ctr_group(struct vty *vty, const char *prefix, - struct rate_ctr_group *ctrg) +void vty_out_rate_ctr_group2(struct vty *vty, const char *prefix, + struct rate_ctr_group *ctrg, bool skip_zero) { - struct vty_out_context vctx = {vty, prefix}; + struct vty_out_context vctx = {vty, prefix, 0, skip_zero}; vty_out(vty, "%s%s:%s", prefix, ctrg->desc->group_description, VTY_NEWLINE); rate_ctr_for_each_counter(ctrg, rate_ctr_handler, &vctx); } +void vty_out_rate_ctr_group(struct vty *vty, const char *prefix, + struct rate_ctr_group *ctrg) +{ + vty_out_rate_ctr_group2(vty, prefix, ctrg, false); +} + static char * pad_append_str(char *s, const char *a, int minwidth) { @@ -107,7 +115,12 @@ static int rate_ctr_handler_fmt( struct vty_out_context *vctx = vctx_; struct vty *vty = vctx->vty; const char *fmt = vctx->prefix; - char *s = talloc_strdup(vty, ""); + char *s; + + if (vctx->skip_zero && ctr->current == 0) + return 0; + + s = talloc_strdup(vty, ""); OSMO_ASSERT(s); while (*fmt) { @@ -209,14 +222,20 @@ static int rate_ctr_handler_fmt( * \param[in] vty The VTY to which it should be printed * \param[in] ctrg Rate counter group to be printed * \param[in] fmt A format which may contain the above directives. + * \param[in] skip_zero Skip all zero-valued counters */ -void vty_out_rate_ctr_group_fmt(struct vty *vty, const char *fmt, - struct rate_ctr_group *ctrg) +void vty_out_rate_ctr_group_fmt2(struct vty *vty, const char *fmt, + struct rate_ctr_group *ctrg, bool skip_zero) { - struct vty_out_context vctx = {vty, fmt}; + struct vty_out_context vctx = {vty, fmt, 0, skip_zero}; rate_ctr_for_each_counter(ctrg, rate_ctr_handler_fmt, &vctx); } +void vty_out_rate_ctr_group_fmt(struct vty *vty, const char *fmt, + struct rate_ctr_group *ctrg) +{ + vty_out_rate_ctr_group_fmt2(vty, fmt, ctrg, false); +} static int rate_ctr_group_handler(struct rate_ctr_group *ctrg, void *vctx_) { struct vty_out_context *vctx = vctx_; @@ -247,14 +266,15 @@ static int osmo_stat_item_handler( { struct vty_out_context *vctx = vctx_; struct vty *vty = vctx->vty; - const char *unit = - item->desc->unit != OSMO_STAT_ITEM_NO_UNIT ? - item->desc->unit : ""; + const struct osmo_stat_item_desc *desc = osmo_stat_item_get_desc(item); + int32_t value = osmo_stat_item_get_last(item); + const char *unit = (desc->unit != OSMO_STAT_ITEM_NO_UNIT) ? desc->unit : ""; + + if (vctx->skip_zero && value == 0) + return 0; vty_out(vty, " %s%s: %8" PRIi32 " %s%s", - vctx->prefix, item->desc->description, - osmo_stat_item_get_last(item), - unit, VTY_NEWLINE); + vctx->prefix, desc->description, value, unit, VTY_NEWLINE); return 0; } @@ -263,17 +283,24 @@ static int osmo_stat_item_handler( * \param[in] vty The VTY to which it should be printed * \param[in] prefix Any additional log prefix ahead of each line * \param[in] statg Stat item group to be printed + * \param[in] skip_zero Skip all zero-valued counters */ -void vty_out_stat_item_group(struct vty *vty, const char *prefix, - struct osmo_stat_item_group *statg) +void vty_out_stat_item_group2(struct vty *vty, const char *prefix, + struct osmo_stat_item_group *statg, bool skip_zero) { - struct vty_out_context vctx = {vty, prefix}; + struct vty_out_context vctx = {vty, prefix, 0, skip_zero}; vty_out(vty, "%s%s:%s", prefix, statg->desc->group_description, VTY_NEWLINE); osmo_stat_item_for_each_item(statg, osmo_stat_item_handler, &vctx); } +void vty_out_stat_item_group(struct vty *vty, const char *prefix, + struct osmo_stat_item_group *statg) +{ + return vty_out_stat_item_group2(vty, prefix, statg, false); +} + static int osmo_stat_item_group_handler(struct osmo_stat_item_group *statg, void *vctx_) { struct vty_out_context *vctx = vctx_; @@ -303,21 +330,22 @@ static int handle_counter(struct osmo_counter *counter, void *vctx_) struct vty_out_context *vctx = vctx_; struct vty *vty = vctx->vty; const char *description = counter->description; + unsigned long value = osmo_counter_get(counter); + + if (vctx->skip_zero && value == 0) + return 0; if (!counter->description) description = counter->name; - vty_out(vty, " %s%s: %8lu%s", - vctx->prefix, description, - osmo_counter_get(counter), VTY_NEWLINE); + vty_out(vty, " %s%s: %8lu%s", vctx->prefix, description, value, VTY_NEWLINE); return 0; } -void vty_out_statistics_partial(struct vty *vty, const char *prefix, - int max_level) +void vty_out_statistics_partial2(struct vty *vty, const char *prefix, int max_level, bool skip_zero) { - struct vty_out_context vctx = {vty, prefix, max_level}; + struct vty_out_context vctx = {vty, prefix, max_level, skip_zero}; vty_out(vty, "%sUngrouped counters:%s", prefix, VTY_NEWLINE); osmo_counters_for_each(handle_counter, &vctx); @@ -325,9 +353,19 @@ void vty_out_statistics_partial(struct vty *vty, const char *prefix, osmo_stat_item_for_each_group(osmo_stat_item_group_handler, &vctx); } +void vty_out_statistics_partial(struct vty *vty, const char *prefix, int max_level) +{ + return vty_out_statistics_partial2(vty, prefix, max_level, false); +} + +void vty_out_statistics_full2(struct vty *vty, const char *prefix, bool skip_zero) +{ + vty_out_statistics_partial2(vty, prefix, INT_MAX, skip_zero); +} + void vty_out_statistics_full(struct vty *vty, const char *prefix) { - vty_out_statistics_partial(vty, prefix, INT_MAX); + vty_out_statistics_full2(vty, prefix, false); } /*! Generate a VTY command string from value_string */ @@ -377,6 +415,7 @@ char *vty_cmd_string_from_valstr(void *ctx, const struct value_string *vals, if (ret < 0) goto err; OSMO_SNPRINTF_RET(ret, rem, offset, len); + (void)len; /* suppress warnings about len being set but not used */ err: str[size-1] = '\0'; return str; diff --git a/src/vty/vector.c b/src/vty/vector.c index f9e5ec3e..34b161d8 100644 --- a/src/vty/vector.c +++ b/src/vty/vector.c @@ -16,11 +16,6 @@ * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with GNU Zebra; see the file COPYING. If not, write to the Free - * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - * Boston, MA 02110-1301, USA. */ #include <stdlib.h> diff --git a/src/vty/vty.c b/src/vty/vty.c index 1ad84f53..3a549b43 100644 --- a/src/vty/vty.c +++ b/src/vty/vty.c @@ -66,6 +66,7 @@ #include <osmocom/vty/command.h> #include <osmocom/vty/buffer.h> #include <osmocom/core/talloc.h> +#include <osmocom/core/timer.h> #include <osmocom/core/utils.h> #ifndef MAXPATHLEN @@ -338,6 +339,25 @@ int vty_out_newline(struct vty *vty) return 0; } +/*! calculates the time difference of a give timespec to the current time + * and prints in a human readable format (days, hours, minutes, seconds). + */ +int vty_out_uptime(struct vty *vty, const struct timespec *starttime) +{ + struct timespec now; + struct timespec uptime; + + osmo_clock_gettime(CLOCK_MONOTONIC, &now); + timespecsub(&now, starttime, &uptime); + + int d = uptime.tv_sec / (3600 * 24); + int h = uptime.tv_sec / 3600 % 24; + int m = uptime.tv_sec / 60 % 60; + int s = uptime.tv_sec % 60; + + return vty_out(vty, "%dd %dh %dm %ds", d, h, m, s); +} + /*! return the current index of a given VTY */ void *vty_current_index(struct vty *vty) { |