aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.checkpatch.conf1
-rw-r--r--.gitignore11
-rw-r--r--Doxyfile.codec.in2
-rw-r--r--Doxyfile.coding.in2
-rw-r--r--Doxyfile.core.in4
-rw-r--r--Doxyfile.ctrl.in2
-rw-r--r--Doxyfile.gb.in2
-rw-r--r--Doxyfile.gsm.in2
-rw-r--r--Doxyfile.isdn.in1716
-rw-r--r--Doxyfile.sim.in1716
-rw-r--r--Doxyfile.usb.in1716
-rw-r--r--Doxyfile.vty.in2
-rw-r--r--Makefile.am129
-rw-r--r--TODO-RELEASE2
-rw-r--r--configure.ac52
-rwxr-xr-xcontrib/jenkins_arm.sh1
-rw-r--r--contrib/libosmocore.spec.in115
-rwxr-xr-xcontrib/struct_endianness.py (renamed from contrib/struct_endianess.py)4
-rwxr-xr-xcontrib/talloc_count.sh53
-rw-r--r--debian/changelog439
-rw-r--r--debian/compat2
-rw-r--r--debian/control100
-rw-r--r--debian/copyright16
-rw-r--r--debian/libosmocodec4.install (renamed from debian/libosmocodec0.install)0
-rw-r--r--debian/libosmocore-utils.install2
-rw-r--r--debian/libosmocore.dirs1
-rw-r--r--debian/libosmocore21.install (renamed from debian/libosmocore19.install)0
-rw-r--r--debian/libosmogsm20.install (renamed from debian/libosmogsm18.install)0
-rw-r--r--debian/libosmoisdn-doc.doc-base7
-rw-r--r--debian/libosmoisdn-doc.install1
-rw-r--r--debian/libosmoisdn0.install1
-rw-r--r--debian/libosmosim-doc.doc-base7
-rw-r--r--debian/libosmosim-doc.install1
-rw-r--r--debian/libosmousb-doc.doc-base7
-rw-r--r--debian/libosmousb-doc.install1
-rw-r--r--debian/libosmovty13.install (renamed from debian/libosmovty9.install)0
-rwxr-xr-xdebian/rules22
-rw-r--r--include/Makefile.am226
-rw-r--r--include/osmocom/Makefile.am13
-rw-r--r--include/osmocom/codec/Makefile.am7
-rw-r--r--include/osmocom/codec/codec.h51
-rw-r--r--include/osmocom/codec/ecu.h6
-rw-r--r--include/osmocom/coding/Makefile.am10
-rw-r--r--include/osmocom/coding/gsm0503_coding.h35
-rw-r--r--include/osmocom/coding/gsm0503_interleaving.h3
-rw-r--r--include/osmocom/core/Makefile.am102
-rw-r--r--include/osmocom/core/bitvec.h3
-rw-r--r--include/osmocom/core/counter.h2
-rw-r--r--include/osmocom/core/endian.h2
-rw-r--r--include/osmocom/core/gsmtap.h7
-rw-r--r--include/osmocom/core/gsmtap_util.h23
-rw-r--r--include/osmocom/core/isdnhdlc.h5
-rw-r--r--include/osmocom/core/logging.h6
-rw-r--r--include/osmocom/core/msgb.h20
-rw-r--r--include/osmocom/core/netdev.h49
-rw-r--r--include/osmocom/core/netns.h24
-rw-r--r--include/osmocom/core/osmo_io.h101
-rw-r--r--include/osmocom/core/prim.h6
-rw-r--r--include/osmocom/core/rate_ctr.h9
-rw-r--r--include/osmocom/core/select.h6
-rw-r--r--include/osmocom/core/sercomm.h5
-rw-r--r--include/osmocom/core/sockaddr_str.h13
-rw-r--r--include/osmocom/core/socket.h116
-rw-r--r--include/osmocom/core/socket_compat.h.tpl10
-rw-r--r--include/osmocom/core/strrb.h4
-rw-r--r--include/osmocom/core/tun.h43
-rw-r--r--include/osmocom/core/utils.h14
-rw-r--r--include/osmocom/core/write_queue.h1
-rw-r--r--include/osmocom/crypt/Makefile.am8
-rw-r--r--include/osmocom/crypt/auth.h52
-rw-r--r--include/osmocom/ctrl/Makefile.am13
-rw-r--r--include/osmocom/ctrl/control_cmd.h4
-rw-r--r--include/osmocom/ctrl/control_if.h7
-rw-r--r--include/osmocom/ctrl/control_vty.h5
-rw-r--r--include/osmocom/gprs/Makefile.am17
-rw-r--r--include/osmocom/gprs/gprs_bssgp.h2
-rw-r--r--include/osmocom/gprs/gprs_bssgp2.h2
-rw-r--r--include/osmocom/gprs/gprs_bssgp_rim.h2
-rw-r--r--include/osmocom/gprs/gprs_ns2.h2
-rw-r--r--include/osmocom/gprs/gprs_rlc.h54
-rw-r--r--include/osmocom/gprs/protocol/Makefile.am8
-rw-r--r--include/osmocom/gprs/protocol/gsm_04_60.h201
-rw-r--r--include/osmocom/gprs/protocol/gsm_08_18.h2
-rw-r--r--include/osmocom/gsm/Makefile.am68
-rw-r--r--include/osmocom/gsm/bts_features.h4
-rw-r--r--include/osmocom/gsm/cbsp.h2
-rw-r--r--include/osmocom/gsm/gsm0502.h66
-rw-r--r--include/osmocom/gsm/gsm0808.h247
-rw-r--r--include/osmocom/gsm/gsm0808_utils.h274
-rw-r--r--include/osmocom/gsm/gsm44021.h8
-rw-r--r--include/osmocom/gsm/gsm48.h12
-rw-r--r--include/osmocom/gsm/gsm48_ie.h5
-rw-r--r--include/osmocom/gsm/gsm48_rest_octets.h5
-rw-r--r--include/osmocom/gsm/gsm_utils.h11
-rw-r--r--include/osmocom/gsm/i460_mux.h116
-rw-r--r--include/osmocom/gsm/lapd_core.h177
-rw-r--r--include/osmocom/gsm/lapdm.h1
-rw-r--r--include/osmocom/gsm/protocol/Makefile.am30
-rw-r--r--include/osmocom/gsm/protocol/gsm_03_41.h8
-rw-r--r--include/osmocom/gsm/protocol/gsm_04_08.h200
-rw-r--r--include/osmocom/gsm/protocol/gsm_04_08_gprs.h28
-rw-r--r--include/osmocom/gsm/protocol/gsm_04_11.h10
-rw-r--r--include/osmocom/gsm/protocol/gsm_04_12.h4
-rw-r--r--include/osmocom/gsm/protocol/gsm_04_14.h8
-rw-r--r--include/osmocom/gsm/protocol/gsm_08_08.h148
-rw-r--r--include/osmocom/gsm/protocol/gsm_08_58.h152
-rw-r--r--include/osmocom/gsm/protocol/gsm_09_02.h5
-rw-r--r--include/osmocom/gsm/protocol/gsm_12_21.h148
-rw-r--r--include/osmocom/gsm/protocol/gsm_23_032.h16
-rw-r--r--include/osmocom/gsm/protocol/gsm_23_041.h2
-rw-r--r--include/osmocom/gsm/protocol/gsm_25_415.h16
-rw-r--r--include/osmocom/gsm/protocol/gsm_44_004.h2
-rw-r--r--include/osmocom/gsm/protocol/gsm_44_060.h252
-rw-r--r--include/osmocom/gsm/protocol/gsm_44_068.h136
-rw-r--r--include/osmocom/gsm/protocol/gsm_44_318.h4
-rw-r--r--include/osmocom/gsm/protocol/gsm_49_031.h1
-rw-r--r--include/osmocom/gsm/tlv.h10
-rw-r--r--include/osmocom/isdn/Makefile.am7
-rw-r--r--include/osmocom/isdn/i460_mux.h120
-rw-r--r--include/osmocom/isdn/lapd_core.h176
-rw-r--r--include/osmocom/isdn/v110.h50
-rw-r--r--include/osmocom/sim/Makefile.am6
-rw-r--r--include/osmocom/sim/sim.h4
-rw-r--r--include/osmocom/usb/Makefile.am7
-rw-r--r--include/osmocom/vty/Makefile.am17
-rw-r--r--include/osmocom/vty/command.h4
-rw-r--r--include/osmocom/vty/logging.h6
-rw-r--r--include/osmocom/vty/ports.h1
-rw-r--r--include/osmocom/vty/telnet_interface.h8
-rw-r--r--include/osmocom/vty/vty.h14
-rw-r--r--libosmocore.pc.in2
-rw-r--r--libosmogb.pc.in2
-rw-r--r--libosmogsm.pc.in2
-rw-r--r--libosmoisdn.pc.in11
-rw-r--r--src/Makefile.am121
-rw-r--r--src/codec/Makefile.am18
-rw-r--r--src/codec/ecu.c16
-rw-r--r--src/codec/ecu_fr.c369
-rw-r--r--src/codec/ecu_fr_old.c166
-rw-r--r--src/codec/gsm610.c156
-rw-r--r--src/codec/gsm620.c29
-rw-r--r--src/codec/gsm660.c162
-rw-r--r--src/coding/Makefile.am21
-rw-r--r--src/coding/gsm0503_coding.c619
-rw-r--r--src/coding/gsm0503_interleaving.c52
-rw-r--r--src/coding/libosmocoding.map21
-rw-r--r--src/core/Makefile.am165
-rw-r--r--src/core/application.c (renamed from src/application.c)0
-rw-r--r--src/core/backtrace.c (renamed from src/backtrace.c)0
-rw-r--r--src/core/base64.c (renamed from src/base64.c)0
-rw-r--r--src/core/bitcomp.c (renamed from src/bitcomp.c)0
-rw-r--r--src/core/bits.c (renamed from src/bits.c)4
-rw-r--r--src/core/bitvec.c (renamed from src/bitvec.c)2
-rw-r--r--src/core/context.c (renamed from src/context.c)4
-rw-r--r--src/core/conv.c (renamed from src/conv.c)0
-rw-r--r--src/core/conv_acc.c (renamed from src/conv_acc.c)0
-rw-r--r--src/core/conv_acc_generic.c (renamed from src/conv_acc_generic.c)0
-rw-r--r--src/core/conv_acc_neon.c (renamed from src/conv_acc_neon.c)0
-rw-r--r--src/core/conv_acc_neon_impl.h (renamed from src/conv_acc_neon_impl.h)0
-rw-r--r--src/core/conv_acc_sse.c (renamed from src/conv_acc_sse.c)0
-rw-r--r--src/core/conv_acc_sse_avx.c (renamed from src/conv_acc_sse_avx.c)0
-rw-r--r--src/core/conv_acc_sse_impl.h (renamed from src/conv_acc_sse_impl.h)0
-rw-r--r--src/core/counter.c (renamed from src/counter.c)2
-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)0
-rw-r--r--src/core/exec.c (renamed from src/exec.c)19
-rw-r--r--src/core/fsm.c (renamed from src/fsm.c)0
-rw-r--r--src/core/gsmtap_util.c (renamed from src/gsmtap_util.c)95
-rw-r--r--src/core/isdnhdlc.c (renamed from src/isdnhdlc.c)7
-rw-r--r--src/core/it_q.c (renamed from src/it_q.c)2
-rw-r--r--src/core/libosmocore.map601
-rw-r--r--src/core/logging.c (renamed from src/logging.c)13
-rw-r--r--src/core/logging_gsmtap.c (renamed from src/logging_gsmtap.c)2
-rw-r--r--src/core/logging_syslog.c (renamed from src/logging_syslog.c)2
-rw-r--r--src/core/logging_systemd.c (renamed from src/logging_systemd.c)0
-rw-r--r--src/core/loggingrb.c (renamed from src/loggingrb.c)0
-rw-r--r--src/core/macaddr.c (renamed from src/macaddr.c)0
-rw-r--r--src/core/mnl.c (renamed from src/mnl.c)4
-rw-r--r--src/core/msgb.c (renamed from src/msgb.c)54
-rw-r--r--src/core/msgfile.c (renamed from src/msgfile.c)0
-rw-r--r--src/core/netdev.c962
-rw-r--r--src/core/netns.c208
-rw-r--r--src/core/osmo_io.c702
-rw-r--r--src/core/osmo_io_internal.h145
-rw-r--r--src/core/osmo_io_poll.c188
-rw-r--r--src/core/osmo_io_uring.c428
-rw-r--r--src/core/panic.c (renamed from src/panic.c)2
-rw-r--r--src/core/plugin.c (renamed from src/plugin.c)2
-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)63
-rw-r--r--src/core/rbtree.c (renamed from src/rbtree.c)0
-rw-r--r--src/core/select.c (renamed from src/select.c)86
-rw-r--r--src/core/sercomm.c (renamed from src/sercomm.c)0
-rw-r--r--src/core/serial.c (renamed from src/serial.c)0
-rw-r--r--src/core/signal.c (renamed from src/signal.c)0
-rw-r--r--src/core/sockaddr_str.c (renamed from src/sockaddr_str.c)0
-rw-r--r--src/core/socket.c (renamed from src/socket.c)420
-rw-r--r--src/core/stat_item.c (renamed from src/stat_item.c)0
-rw-r--r--src/core/stat_item_internal.h (renamed from src/stat_item_internal.h)0
-rw-r--r--src/core/stats.c (renamed from src/stats.c)0
-rw-r--r--src/core/stats_statsd.c (renamed from src/stats_statsd.c)0
-rw-r--r--src/core/stats_tcp.c (renamed from src/stats_tcp.c)6
-rw-r--r--src/core/strrb.c (renamed from src/strrb.c)4
-rw-r--r--src/core/tdef.c (renamed from src/tdef.c)4
-rw-r--r--src/core/thread.c (renamed from src/thread.c)0
-rw-r--r--src/core/time_cc.c (renamed from src/time_cc.c)2
-rw-r--r--src/core/timer.c (renamed from src/timer.c)0
-rw-r--r--src/core/timer_clockgettime.c (renamed from src/timer_clockgettime.c)0
-rw-r--r--src/core/timer_gettimeofday.c (renamed from src/timer_gettimeofday.c)0
-rw-r--r--src/core/tun.c577
-rw-r--r--src/core/use_count.c (renamed from src/use_count.c)0
-rw-r--r--src/core/utils.c (renamed from src/utils.c)2
-rw-r--r--src/core/write_queue.c (renamed from src/write_queue.c)20
-rw-r--r--src/ctrl/Makefile.am8
-rw-r--r--src/ctrl/control_cmd.c10
-rw-r--r--src/ctrl/control_if.c19
-rw-r--r--src/ctrl/control_vty.c13
-rw-r--r--src/ctrl/libosmoctrl.map2
-rw-r--r--src/gb/Makefile.am10
-rw-r--r--src/gb/frame_relay.c2
-rw-r--r--src/gb/gprs_bssgp.c2
-rw-r--r--src/gb/gprs_bssgp2.c29
-rw-r--r--src/gb/gprs_bssgp_rim.c70
-rw-r--r--src/gb/gprs_ns.c2
-rw-r--r--src/gb/gprs_ns2.c2
-rw-r--r--src/gb/gprs_ns2_fr.c186
-rw-r--r--src/gb/gprs_ns2_frgre.c12
-rw-r--r--src/gb/gprs_ns2_internal.h10
-rw-r--r--src/gb/gprs_ns2_message.c18
-rw-r--r--src/gb/gprs_ns2_udp.c138
-rw-r--r--src/gb/gprs_ns2_vc_fsm.c20
-rw-r--r--src/gb/gprs_ns2_vty.c38
-rw-r--r--src/gb/libosmogb.map3
-rw-r--r--src/gsm/Makefile.am22
-rw-r--r--src/gsm/abis_nm.c94
-rw-r--r--src/gsm/auth_comp128v1.c3
-rw-r--r--src/gsm/auth_comp128v23.c6
-rw-r--r--src/gsm/auth_core.c168
-rw-r--r--src/gsm/auth_milenage.c31
-rw-r--r--src/gsm/auth_tuak.c207
-rw-r--r--src/gsm/auth_xor.c24
-rw-r--r--src/gsm/auth_xor_2g.c81
-rw-r--r--src/gsm/bsslap.c2
-rw-r--r--src/gsm/bssmap_le.c17
-rw-r--r--src/gsm/bts_features.c11
-rw-r--r--src/gsm/cbsp.c2
-rw-r--r--src/gsm/gprs_rlc.c3
-rw-r--r--src/gsm/gsm0411_utils.c6
-rw-r--r--src/gsm/gsm0502.c42
-rw-r--r--src/gsm/gsm0808.c878
-rw-r--r--src/gsm/gsm0808_utils.c578
-rw-r--r--src/gsm/gsm44021.c303
-rw-r--r--src/gsm/gsm44068.c121
-rw-r--r--src/gsm/gsm48.c168
-rw-r--r--src/gsm/gsm48_ie.c17
-rw-r--r--src/gsm/gsm48_rest_octets.c76
-rw-r--r--src/gsm/gsm_utils.c59
-rw-r--r--src/gsm/ipa.c2
-rw-r--r--src/gsm/iuup.c2
-rw-r--r--src/gsm/kdf.c2
-rw-r--r--src/gsm/lapdm.c114
-rw-r--r--src/gsm/libosmogsm.map78
-rw-r--r--src/gsm/milenage/milenage.c8
-rw-r--r--src/gsm/mncc.c2
-rw-r--r--src/gsm/rsl.c2
-rw-r--r--src/gsm/sysinfo.c2
-rw-r--r--src/gsm/tlv_parser.c16
-rw-r--r--src/gsm/tuak/KeccakP-1600-3gpp.c176
-rw-r--r--src/gsm/tuak/KeccakP-1600-3gpp.h25
-rw-r--r--src/gsm/tuak/tuak.c372
-rw-r--r--src/gsm/tuak/tuak.h33
-rw-r--r--src/isdn/Makefile.am26
-rw-r--r--src/isdn/i460_mux.c (renamed from src/gsm/i460_mux.c)31
-rw-r--r--src/isdn/lapd_core.c (renamed from src/gsm/lapd_core.c)66
-rw-r--r--src/isdn/libosmoisdn.map34
-rw-r--r--src/isdn/v110.c583
-rw-r--r--src/pseudotalloc/Makefile.am3
-rw-r--r--src/sim/Makefile.am6
-rw-r--r--src/sim/class_tables.c1
-rw-r--r--src/usb/Makefile.am4
-rw-r--r--src/vty/Makefile.am6
-rw-r--r--src/vty/command.c81
-rw-r--r--src/vty/cpu_sched_vty.c4
-rw-r--r--src/vty/fsm_vty.c2
-rw-r--r--src/vty/logging_vty.c4
-rw-r--r--src/vty/stats_vty.c4
-rw-r--r--src/vty/telnet_interface.c55
-rw-r--r--tests/Makefile.am221
-rw-r--r--tests/abis/abis_test.c6
-rw-r--r--tests/auth/tuak_test.c309
-rw-r--r--tests/auth/tuak_test.ok48
-rw-r--r--tests/auth/xor2g_test.c77
-rw-r--r--tests/auth/xor2g_test.ok6
-rw-r--r--tests/bitgen/bitgen_test.c6
-rw-r--r--tests/bitvec/bitvec_test.c8
-rw-r--r--tests/bsslap/bsslap_test.c4
-rw-r--r--tests/bssmap_le/bssmap_le_test.c4
-rw-r--r--tests/codec/codec_ecu_fr_test.c6
-rw-r--r--tests/codec/codec_ecu_fr_test.ok84
-rw-r--r--tests/coding/coding_test.c186
-rw-r--r--tests/coding/coding_test.ok238
-rw-r--r--tests/conv/conv_gsm0503_test.ok40
-rw-r--r--tests/ctrl/ctrl_test.c4
-rw-r--r--tests/fr/fr_test.c2
-rw-r--r--tests/fsm/fsm_dealloc_test.c6
-rw-r--r--tests/fsm/fsm_test.c8
-rw-r--r--tests/gad/gad_test.c8
-rw-r--r--tests/gb/gprs_bssgp_rim_test.c40
-rw-r--r--tests/gb/gprs_bssgp_test.c4
-rw-r--r--tests/gb/gprs_ns2_vty.vty16
-rw-r--r--tests/gb/gprs_ns_test.c14
-rw-r--r--tests/gsm0408/gsm0408_test.c234
-rw-r--r--tests/gsm0408/gsm0408_test.ok34
-rw-r--r--tests/gsm0502/gsm0502_test.c22
-rw-r--r--tests/gsm0808/gsm0808_test.c358
-rw-r--r--tests/gsm0808/gsm0808_test.ok804
-rw-r--r--tests/gsm23003/gsm23003_test.c14
-rw-r--r--tests/gsm23236/gsm23236_test.c12
-rw-r--r--tests/gsm29205/gsm29205_test.c2
-rw-r--r--tests/gsm44021/frame_csd_test.c93
-rw-r--r--tests/gsm44021/frame_csd_test.ok31
-rw-r--r--tests/gsm48/rest_octets_test.c4
-rw-r--r--tests/i460_mux/i460_mux_test.c2
-rw-r--r--tests/lapd/lapd_test.c12
-rw-r--r--tests/logging/logging_vty_test.c12
-rw-r--r--tests/logging/logging_vty_test.vty101
-rw-r--r--tests/msgb/msgb_test.c30
-rw-r--r--tests/osmo-auc-gen/osmo-auc-gen_test.ok38
-rw-r--r--tests/osmo_io/osmo_io_test.c178
-rw-r--r--tests/osmo_io/osmo_io_test.err0
-rw-r--r--tests/osmo_io/osmo_io_test.ok9
-rw-r--r--tests/sms/sms_test.c2
-rw-r--r--tests/smscb/cbsp_test.c2
-rw-r--r--tests/sockaddr_str/sockaddr_str_test.c4
-rw-r--r--tests/socket/socket_sctp_test.c2
-rw-r--r--tests/socket/socket_sctp_test.err8
-rw-r--r--tests/socket/socket_test.c62
-rw-r--r--tests/stats/stats_test.c4
-rw-r--r--tests/stats/stats_vty_test.c2
-rw-r--r--tests/tdef/tdef_test.c6
-rw-r--r--tests/tdef/tdef_vty_config_root_test.c6
-rw-r--r--tests/tdef/tdef_vty_config_subnode_test.c6
-rw-r--r--tests/tdef/tdef_vty_dynamic_test.c6
-rw-r--r--tests/testsuite.at32
-rw-r--r--tests/time_cc/time_cc_test.c2
-rw-r--r--tests/timer/timer_test.c2
-rw-r--r--tests/tlv/tlv_test.c35
-rw-r--r--tests/use_count/use_count_test.c4
-rw-r--r--tests/utils/utils_test.c80
-rw-r--r--tests/utils/utils_test.ok30
-rw-r--r--tests/v110/frame_test.c54
-rw-r--r--tests/v110/frame_test.ok20
-rw-r--r--tests/v110/ra1_test.c74
-rw-r--r--tests/v110/ra1_test.ok216
-rw-r--r--tests/vty/vty_test.c73
-rw-r--r--tests/vty/vty_test.err10
-rw-r--r--tests/vty/vty_test.ok48
-rw-r--r--tests/vty/vty_transcript_test.c76
-rw-r--r--tests/vty/vty_transcript_test.vty30
-rw-r--r--tests/write_queue/wqueue_test.c52
-rw-r--r--utils/Makefile.am24
-rw-r--r--utils/conv_codes_gsm.py105
-rw-r--r--utils/gsmtap-logsend.c139
-rw-r--r--utils/osmo-aka-verify.c2
-rw-r--r--utils/osmo-auc-gen.c57
-rwxr-xr-xutils/osmo-gsm-shark3594
-rw-r--r--utils/osmo-ns-dummy.c32
-rw-r--r--utils/osmo-stat-dummy/Makefile.am10
-rw-r--r--utils/osmo-stat-dummy/README.md12
-rw-r--r--utils/osmo-stat-dummy/osmo-stat-dummy.c335
-rw-r--r--utils/osmo-stat-dummy/osmo-stat-dummy.cfg40
-rw-r--r--utils/osmo-stat-dummy/osmo-stat-dummy.html59
374 files changed, 27494 insertions, 3055 deletions
diff --git a/.checkpatch.conf b/.checkpatch.conf
new file mode 100644
index 00000000..46bf0eb1
--- /dev/null
+++ b/.checkpatch.conf
@@ -0,0 +1 @@
+--exclude ^src/gsm/tuak/KeccakP-1600-3gpp\.(c|h)$
diff --git a/.gitignore b/.gitignore
index 936773e0..264e739e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -37,10 +37,13 @@ libosmocore-*.tar.*
Doxyfile.core
Doxyfile.ctrl
Doxyfile.gsm
+Doxyfile.isdn
Doxyfile.vty
Doxyfile.codec
Doxyfile.coding
Doxyfile.gb
+Doxyfile.sim
+Doxyfile.usb
debian/autoreconf.after
debian/autoreconf.before
@@ -69,6 +72,7 @@ tests/*/*_test
utils/osmo-arfcn
utils/osmo-auc-gen
utils/osmo-config-merge
+utils/osmo-gsmtap-logsend
utils/osmo-sim-test
utils/osmo-aka-verify
@@ -80,17 +84,21 @@ doc/vty/latex
doc/vty/html
doc/vty/doxygen_sqlite3.db
doc/gsm
+doc/isdn
doc/gb
+doc/sim
+doc/usb
doc/html.tar
doc/*.tag
doc/*.tag.prep
tags
-src/crc*gen.c
+src/core/crc*gen.c
src/gsm/gsm0503_conv.c
src/gsm/*_gen.c
include/osmocom/core/crc*gen.h
include/osmocom/core/bit*gen.h
+include/osmocom/core/socket_compat.h
include/osmocom/gsm/gsm0503.h
tests/conv/gsm0503_test_vectors.c
@@ -104,3 +112,4 @@ coverage-cobertura.xml
contrib/libosmocore.spec
/utils/osmo-ns-dummy
+utils/osmo-stat-dummy/osmo-stat-dummy
diff --git a/Doxyfile.codec.in b/Doxyfile.codec.in
index 88f0d27a..40be69fe 100644
--- a/Doxyfile.codec.in
+++ b/Doxyfile.codec.in
@@ -1485,7 +1485,7 @@ SKIP_FUNCTION_MACROS = YES
# If a tag file is not located in the directory in which doxygen
# is run, you must also specify the path to the tagfile here.
-TAGFILES = doc/libosmocore.tag=../../core/html doc/libosmogsm.tag=../../gsm/html doc/libosmovty.tag=../../vty/html doc/libosmocoding.tag=../../coding/html doc/libosmoctrl.tag=../../ctrl/html doc/libosmogb.tag=../../gb/html
+TAGFILES = doc/libosmocore.tag=../../core/html doc/libosmogsm.tag=../../gsm/html doc/libosmoisdn.tag=../../isdn/html doc/libosmovty.tag=../../vty/html doc/libosmocoding.tag=../../coding/html doc/libosmoctrl.tag=../../ctrl/html doc/libosmogb.tag=../../gb/html
# When a file name is specified after GENERATE_TAGFILE, doxygen will create
# a tag file that is based on the input files it reads.
diff --git a/Doxyfile.coding.in b/Doxyfile.coding.in
index d0e99425..50355de4 100644
--- a/Doxyfile.coding.in
+++ b/Doxyfile.coding.in
@@ -1485,7 +1485,7 @@ SKIP_FUNCTION_MACROS = YES
# If a tag file is not located in the directory in which doxygen
# is run, you must also specify the path to the tagfile here.
-TAGFILES = doc/libosmocore.tag=../../core/html doc/libosmogsm.tag=../../gsm/html doc/libosmovty.tag=../../vty/html doc/libosmocodec.tag=../../codec/html doc/libosmoctrl.tag=../../ctrl/html doc/libosmogb.tag=../../gb/html
+TAGFILES = doc/libosmocore.tag=../../core/html doc/libosmogsm.tag=../../gsm/html doc/libosmoisdn.tag=../../isdn/html doc/libosmovty.tag=../../vty/html doc/libosmocodec.tag=../../codec/html doc/libosmoctrl.tag=../../ctrl/html doc/libosmogb.tag=../../gb/html
# When a file name is specified after GENERATE_TAGFILE, doxygen will create
# a tag file that is based on the input files it reads.
diff --git a/Doxyfile.core.in b/Doxyfile.core.in
index c2bfcd5d..fb4ceffb 100644
--- a/Doxyfile.core.in
+++ b/Doxyfile.core.in
@@ -610,7 +610,7 @@ WARN_LOGFILE =
# directories like "/usr/src/myproject". Separate the files or directories
# with spaces.
-INPUT = @srcdir@/include/osmocom/core @srcdir@/src
+INPUT = @srcdir@/include/osmocom/core @srcdir@/src/core
# This tag can be used to specify the character encoding of the source files
# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is
@@ -1485,7 +1485,7 @@ SKIP_FUNCTION_MACROS = YES
# If a tag file is not located in the directory in which doxygen
# is run, you must also specify the path to the tagfile here.
-TAGFILES = doc/libosmogsm.tag=../../gsm/html doc/libosmovty.tag=../../vty/html doc/libosmocodec.tag=../../codec/html doc/libosmocoding.tag=../../coding/html doc/libosmoctrl.tag=../../ctrl/html doc/libosmogb.tag=../../gb/html
+TAGFILES = doc/libosmogsm.tag=../../gsm/html doc/libosmoisdn.tag=../../isdn/html doc/libosmovty.tag=../../vty/html doc/libosmocodec.tag=../../codec/html doc/libosmocoding.tag=../../coding/html doc/libosmoctrl.tag=../../ctrl/html doc/libosmogb.tag=../../gb/html
# When a file name is specified after GENERATE_TAGFILE, doxygen will create
# a tag file that is based on the input files it reads.
diff --git a/Doxyfile.ctrl.in b/Doxyfile.ctrl.in
index 5612a741..6d101dfc 100644
--- a/Doxyfile.ctrl.in
+++ b/Doxyfile.ctrl.in
@@ -1485,7 +1485,7 @@ SKIP_FUNCTION_MACROS = YES
# If a tag file is not located in the directory in which doxygen
# is run, you must also specify the path to the tagfile here.
-TAGFILES = doc/libosmocore.tag=../../core/html doc/libosmogsm.tag=../../gsm/html doc/libosmovty.tag=../../vty/html doc/libosmocodec.tag=../../codec/html doc/libosmocoding.tag=../../coding/html doc/libosmogb.tag=../../gb/html
+TAGFILES = doc/libosmocore.tag=../../core/html doc/libosmogsm.tag=../../gsm/html doc/libosmoisdn.tag=../../isdn/html doc/libosmovty.tag=../../vty/html doc/libosmocodec.tag=../../codec/html doc/libosmocoding.tag=../../coding/html doc/libosmogb.tag=../../gb/html
# When a file name is specified after GENERATE_TAGFILE, doxygen will create
# a tag file that is based on the input files it reads.
diff --git a/Doxyfile.gb.in b/Doxyfile.gb.in
index b3b2128d..da8e7705 100644
--- a/Doxyfile.gb.in
+++ b/Doxyfile.gb.in
@@ -1485,7 +1485,7 @@ SKIP_FUNCTION_MACROS = YES
# If a tag file is not located in the directory in which doxygen
# is run, you must also specify the path to the tagfile here.
-TAGFILES = doc/libosmocore.tag=../../core/html doc/libosmogsm.tag=../../gsm/html doc/libosmovty.tag=../../vty/html doc/libosmocodec.tag=../../codec/html doc/libosmocoding.tag=../../coding/html doc/libosmoctrl.tag=../../ctrl/html
+TAGFILES = doc/libosmocore.tag=../../core/html doc/libosmogsm.tag=../../gsm/html doc/libosmoisdn.tag=../../isdn/html doc/libosmovty.tag=../../vty/html doc/libosmocodec.tag=../../codec/html doc/libosmocoding.tag=../../coding/html doc/libosmoctrl.tag=../../ctrl/html
# When a file name is specified after GENERATE_TAGFILE, doxygen will create
# a tag file that is based on the input files it reads.
diff --git a/Doxyfile.gsm.in b/Doxyfile.gsm.in
index fd893907..c04a1f6a 100644
--- a/Doxyfile.gsm.in
+++ b/Doxyfile.gsm.in
@@ -1485,7 +1485,7 @@ SKIP_FUNCTION_MACROS = YES
# If a tag file is not located in the directory in which doxygen
# is run, you must also specify the path to the tagfile here.
-TAGFILES = doc/libosmocore.tag=../../core/html doc/libosmovty.tag=../../vty/html doc/libosmocodec.tag=../../codec/html doc/libosmocoding.tag=../../coding/html doc/libosmoctrl.tag=../../ctrl/html doc/libosmogb.tag=../../gb/html
+TAGFILES = doc/libosmocore.tag=../../core/html doc/libosmoisdn.tag=../../isdn/html doc/libosmovty.tag=../../vty/html doc/libosmocodec.tag=../../codec/html doc/libosmocoding.tag=../../coding/html doc/libosmoctrl.tag=../../ctrl/html doc/libosmogb.tag=../../gb/html
# When a file name is specified after GENERATE_TAGFILE, doxygen will create
# a tag file that is based on the input files it reads.
diff --git a/Doxyfile.isdn.in b/Doxyfile.isdn.in
new file mode 100644
index 00000000..9476ab2a
--- /dev/null
+++ b/Doxyfile.isdn.in
@@ -0,0 +1,1716 @@
+# Doxyfile 1.7.4
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project.
+#
+# All text after a hash (#) is considered a comment and will be ignored.
+# The format is:
+# TAG = value [value, ...]
+# For lists items can also be appended using:
+# TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (" ").
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# This tag specifies the encoding used for all characters in the config file
+# that follow. The default is UTF-8 which is also the encoding used for all
+# text before the first occurrence of this tag. Doxygen uses libiconv (or the
+# iconv built into libc) for the transcoding. See
+# http://www.gnu.org/software/libiconv for the list of possible encodings.
+
+DOXYFILE_ENCODING = UTF-8
+
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded
+# by quotes) that should identify the project.
+
+PROJECT_NAME = libosmoisdn
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number.
+# This could be handy for archiving the generated documentation or
+# if some version control system is used.
+
+PROJECT_NUMBER = @VERSION@
+
+# Using the PROJECT_BRIEF tag one can provide an optional one line description
+# for a project that appears at the top of each page and should give viewer
+# a quick idea about the purpose of the project. Keep the description short.
+
+PROJECT_BRIEF = "Osmocom ISDN library"
+
+# With the PROJECT_LOGO tag one can specify an logo or icon that is
+# included in the documentation. The maximum height of the logo should not
+# exceed 55 pixels and the maximum width should not exceed 200 pixels.
+# Doxygen will copy the logo to the output directory.
+
+PROJECT_LOGO =
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute)
+# base path where the generated documentation will be put.
+# If a relative path is entered, it will be relative to the location
+# where doxygen was started. If left blank the current directory will be used.
+
+OUTPUT_DIRECTORY = doc/isdn
+
+# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create
+# 4096 sub-directories (in 2 levels) under the output directory of each output
+# format and will distribute the generated files over these directories.
+# Enabling this option can be useful when feeding doxygen a huge amount of
+# source files, where putting all generated files in the same directory would
+# otherwise cause performance problems for the file system.
+
+CREATE_SUBDIRS = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all
+# documentation generated by doxygen is written. Doxygen will use this
+# information to generate all constant output in the proper language.
+# The default language is English, other supported languages are:
+# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional,
+# Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German,
+# Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English
+# messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian,
+# Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak,
+# Slovene, Spanish, Swedish, Ukrainian, and Vietnamese.
+
+OUTPUT_LANGUAGE = English
+
+# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will
+# include brief member descriptions after the members that are listed in
+# the file and class documentation (similar to JavaDoc).
+# Set to NO to disable this.
+
+BRIEF_MEMBER_DESC = YES
+
+# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend
+# the brief description of a member or function before the detailed description.
+# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
+# brief descriptions will be completely suppressed.
+
+REPEAT_BRIEF = YES
+
+# This tag implements a quasi-intelligent brief description abbreviator
+# that is used to form the text in various listings. Each string
+# in this list, if found as the leading text of the brief description, will be
+# stripped from the text and the result after processing the whole list, is
+# used as the annotated text. Otherwise, the brief description is used as-is.
+# If left blank, the following values are used ("$name" is automatically
+# replaced with the name of the entity): "The $name class" "The $name widget"
+# "The $name file" "is" "provides" "specifies" "contains"
+# "represents" "a" "an" "the"
+
+ABBREVIATE_BRIEF =
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
+# Doxygen will generate a detailed section even if there is only a brief
+# description.
+
+ALWAYS_DETAILED_SEC = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
+# inherited members of a class in the documentation of that class as if those
+# members were ordinary class members. Constructors, destructors and assignment
+# operators of the base classes will not be shown.
+
+INLINE_INHERITED_MEMB = NO
+
+# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full
+# path before files name in the file list and in the header files. If set
+# to NO the shortest path that makes the file name unique will be used.
+
+FULL_PATH_NAMES = YES
+
+# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag
+# can be used to strip a user-defined part of the path. Stripping is
+# only done if one of the specified strings matches the left-hand part of
+# the path. The tag can be used to show relative paths in the file list.
+# If left blank the directory from which doxygen is run is used as the
+# path to strip.
+
+STRIP_FROM_PATH =
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of
+# the path mentioned in the documentation of a class, which tells
+# the reader which header file to include in order to use a class.
+# If left blank only the name of the header file containing the class
+# definition is used. Otherwise one should specify the include paths that
+# are normally passed to the compiler using the -I flag.
+
+STRIP_FROM_INC_PATH =
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter
+# (but less readable) file names. This can be useful if your file system
+# doesn't support long names like on DOS, Mac, or CD-ROM.
+
+SHORT_NAMES = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen
+# will interpret the first line (until the first dot) of a JavaDoc-style
+# comment as the brief description. If set to NO, the JavaDoc
+# comments will behave just like regular Qt-style comments
+# (thus requiring an explicit @brief command for a brief description.)
+
+JAVADOC_AUTOBRIEF = YES
+
+# If the QT_AUTOBRIEF tag is set to YES then Doxygen will
+# interpret the first line (until the first dot) of a Qt-style
+# comment as the brief description. If set to NO, the comments
+# will behave just like regular Qt-style comments (thus requiring
+# an explicit command for a brief description.)
+
+QT_AUTOBRIEF = YES
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen
+# treat a multi-line C++ special comment block (i.e. a block of //! or ///
+# comments) as a brief description. This used to be the default behaviour.
+# The new default is to treat a multi-line C++ comment block as a detailed
+# description. Set this tag to YES if you prefer the old behaviour instead.
+
+MULTILINE_CPP_IS_BRIEF = NO
+
+# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented
+# member inherits the documentation from any documented member that it
+# re-implements.
+
+INHERIT_DOCS = YES
+
+# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce
+# a new page for each member. If set to NO, the documentation of a member will
+# be part of the file/class/namespace that contains it.
+
+SEPARATE_MEMBER_PAGES = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab.
+# Doxygen uses this value to replace tabs by spaces in code fragments.
+
+TAB_SIZE = 8
+
+# This tag can be used to specify a number of aliases that acts
+# as commands in the documentation. An alias has the form "name=value".
+# For example adding "sideeffect=\par Side Effects:\n" will allow you to
+# put the command \sideeffect (or @sideeffect) in the documentation, which
+# will result in a user-defined paragraph with heading "Side Effects:".
+# You can put \n's in the value part of an alias to insert newlines.
+
+ALIASES =
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C
+# sources only. Doxygen will then generate output that is more tailored for C.
+# For instance, some of the names that are used will be different. The list
+# of all members will be omitted, etc.
+
+OPTIMIZE_OUTPUT_FOR_C = YES
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java
+# sources only. Doxygen will then generate output that is more tailored for
+# Java. For instance, namespaces will be presented as packages, qualified
+# scopes will look different, etc.
+
+OPTIMIZE_OUTPUT_JAVA = NO
+
+# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
+# sources only. Doxygen will then generate output that is more tailored for
+# Fortran.
+
+OPTIMIZE_FOR_FORTRAN = NO
+
+# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
+# sources. Doxygen will then generate output that is tailored for
+# VHDL.
+
+OPTIMIZE_OUTPUT_VHDL = NO
+
+# Doxygen selects the parser to use depending on the extension of the files it
+# parses. With this tag you can assign which parser to use for a given extension.
+# Doxygen has a built-in mapping, but you can override or extend it using this
+# tag. The format is ext=language, where ext is a file extension, and language
+# is one of the parsers supported by doxygen: IDL, Java, Javascript, CSharp, C,
+# C++, D, PHP, Objective-C, Python, Fortran, VHDL, C, C++. For instance to make
+# doxygen treat .inc files as Fortran files (default is PHP), and .f files as C
+# (default is Fortran), use: inc=Fortran f=C. Note that for custom extensions
+# you also need to set FILE_PATTERNS otherwise the files are not read by doxygen.
+
+EXTENSION_MAPPING =
+
+# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
+# to include (a tag file for) the STL sources as input, then you should
+# set this tag to YES in order to let doxygen match functions declarations and
+# definitions whose arguments contain STL classes (e.g. func(std::string); v.s.
+# func(std::string) {}). This also makes the inheritance and collaboration
+# diagrams that involve STL classes more complete and accurate.
+
+BUILTIN_STL_SUPPORT = NO
+
+# If you use Microsoft's C++/CLI language, you should set this option to YES to
+# enable parsing support.
+
+CPP_CLI_SUPPORT = NO
+
+# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only.
+# Doxygen will parse them like normal C++ but will assume all classes use public
+# instead of private inheritance when no explicit protection keyword is present.
+
+SIP_SUPPORT = NO
+
+# For Microsoft's IDL there are propget and propput attributes to indicate getter
+# and setter methods for a property. Setting this option to YES (the default)
+# will make doxygen replace the get and set methods by a property in the
+# documentation. This will only work if the methods are indeed getting or
+# setting a simple type. If this is not the case, or you want to show the
+# methods anyway, you should set this option to NO.
+
+IDL_PROPERTY_SUPPORT = YES
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
+# tag is set to YES, then doxygen will reuse the documentation of the first
+# member in the group (if any) for the other members of the group. By default
+# all members of a group must be documented explicitly.
+
+DISTRIBUTE_GROUP_DOC = NO
+
+# Set the SUBGROUPING tag to YES (the default) to allow class member groups of
+# the same type (for instance a group of public functions) to be put as a
+# subgroup of that type (e.g. under the Public Functions section). Set it to
+# NO to prevent subgrouping. Alternatively, this can be done per class using
+# the \nosubgrouping command.
+
+SUBGROUPING = YES
+
+# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and
+# unions are shown inside the group in which they are included (e.g. using
+# @ingroup) instead of on a separate page (for HTML and Man pages) or
+# section (for LaTeX and RTF).
+
+INLINE_GROUPED_CLASSES = NO
+
+# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum
+# is documented as struct, union, or enum with the name of the typedef. So
+# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
+# with name TypeT. When disabled the typedef will appear as a member of a file,
+# namespace, or class. And the struct will be named TypeS. This can typically
+# be useful for C code in case the coding convention dictates that all compound
+# types are typedef'ed and only the typedef is referenced, never the tag name.
+
+TYPEDEF_HIDES_STRUCT = NO
+
+# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to
+# determine which symbols to keep in memory and which to flush to disk.
+# When the cache is full, less often used symbols will be written to disk.
+# For small to medium size projects (<1000 input files) the default value is
+# probably good enough. For larger projects a too small cache size can cause
+# doxygen to be busy swapping symbols to and from disk most of the time
+# causing a significant performance penalty.
+# If the system has enough physical memory increasing the cache will improve the
+# performance by keeping more symbols in memory. Note that the value works on
+# a logarithmic scale so increasing the size by one will roughly double the
+# memory usage. The cache size is given by this formula:
+# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0,
+# corresponding to a cache size of 2^16 = 65536 symbols
+
+SYMBOL_CACHE_SIZE = 0
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in
+# documentation are documented, even if no documentation was available.
+# Private class members and static file members will be hidden unless
+# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES
+
+EXTRACT_ALL = YES
+
+# If the EXTRACT_PRIVATE tag is set to YES all private members of a class
+# will be included in the documentation.
+
+EXTRACT_PRIVATE = NO
+
+# If the EXTRACT_STATIC tag is set to YES all static members of a file
+# will be included in the documentation.
+
+EXTRACT_STATIC = YES
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs)
+# defined locally in source files will be included in the documentation.
+# If set to NO only classes defined in header files are included.
+
+EXTRACT_LOCAL_CLASSES = YES
+
+# This flag is only useful for Objective-C code. When set to YES local
+# methods, which are defined in the implementation section but not in
+# the interface are included in the documentation.
+# If set to NO (the default) only methods in the interface are included.
+
+EXTRACT_LOCAL_METHODS = NO
+
+# If this flag is set to YES, the members of anonymous namespaces will be
+# extracted and appear in the documentation as a namespace called
+# 'anonymous_namespace{file}', where file will be replaced with the base
+# name of the file that contains the anonymous namespace. By default
+# anonymous namespaces are hidden.
+
+EXTRACT_ANON_NSPACES = NO
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all
+# undocumented members of documented classes, files or namespaces.
+# If set to NO (the default) these members will be included in the
+# various overviews, but no documentation section is generated.
+# This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_MEMBERS = NO
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all
+# undocumented classes that are normally visible in the class hierarchy.
+# If set to NO (the default) these classes will be included in the various
+# overviews. This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_CLASSES = NO
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all
+# friend (class|struct|union) declarations.
+# If set to NO (the default) these declarations will be included in the
+# documentation.
+
+HIDE_FRIEND_COMPOUNDS = NO
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any
+# documentation blocks found inside the body of a function.
+# If set to NO (the default) these blocks will be appended to the
+# function's detailed documentation block.
+
+HIDE_IN_BODY_DOCS = NO
+
+# The INTERNAL_DOCS tag determines if documentation
+# that is typed after a \internal command is included. If the tag is set
+# to NO (the default) then the documentation will be excluded.
+# Set it to YES to include the internal documentation.
+
+INTERNAL_DOCS = NO
+
+# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate
+# file names in lower-case letters. If set to YES upper-case letters are also
+# allowed. This is useful if you have classes or files whose names only differ
+# in case and if your file system supports case sensitive file names. Windows
+# and Mac users are advised to set this option to NO.
+
+CASE_SENSE_NAMES = YES
+
+# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen
+# will show members with their full class and namespace scopes in the
+# documentation. If set to YES the scope will be hidden.
+
+HIDE_SCOPE_NAMES = NO
+
+# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen
+# will put a list of the files that are included by a file in the documentation
+# of that file.
+
+SHOW_INCLUDE_FILES = YES
+
+# If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen
+# will list include files with double quotes in the documentation
+# rather than with sharp brackets.
+
+FORCE_LOCAL_INCLUDES = NO
+
+# If the INLINE_INFO tag is set to YES (the default) then a tag [inline]
+# is inserted in the documentation for inline members.
+
+INLINE_INFO = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen
+# will sort the (detailed) documentation of file and class members
+# alphabetically by member name. If set to NO the members will appear in
+# declaration order.
+
+SORT_MEMBER_DOCS = YES
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the
+# brief documentation of file, namespace and class members alphabetically
+# by member name. If set to NO (the default) the members will appear in
+# declaration order.
+
+SORT_BRIEF_DOCS = NO
+
+# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen
+# will sort the (brief and detailed) documentation of class members so that
+# constructors and destructors are listed first. If set to NO (the default)
+# the constructors will appear in the respective orders defined by
+# SORT_MEMBER_DOCS and SORT_BRIEF_DOCS.
+# This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO
+# and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO.
+
+SORT_MEMBERS_CTORS_1ST = NO
+
+# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the
+# hierarchy of group names into alphabetical order. If set to NO (the default)
+# the group names will appear in their defined order.
+
+SORT_GROUP_NAMES = NO
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be
+# sorted by fully-qualified names, including namespaces. If set to
+# NO (the default), the class list will be sorted only by class name,
+# not including the namespace part.
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
+# Note: This option applies only to the class list, not to the
+# alphabetical list.
+
+SORT_BY_SCOPE_NAME = NO
+
+# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to
+# do proper type resolution of all parameters of a function it will reject a
+# match between the prototype and the implementation of a member function even
+# if there is only one candidate or it is obvious which candidate to choose
+# by doing a simple string match. By disabling STRICT_PROTO_MATCHING doxygen
+# will still accept a match between prototype and implementation in such cases.
+
+STRICT_PROTO_MATCHING = NO
+
+# The GENERATE_TODOLIST tag can be used to enable (YES) or
+# disable (NO) the todo list. This list is created by putting \todo
+# commands in the documentation.
+
+GENERATE_TODOLIST = YES
+
+# The GENERATE_TESTLIST tag can be used to enable (YES) or
+# disable (NO) the test list. This list is created by putting \test
+# commands in the documentation.
+
+GENERATE_TESTLIST = YES
+
+# The GENERATE_BUGLIST tag can be used to enable (YES) or
+# disable (NO) the bug list. This list is created by putting \bug
+# commands in the documentation.
+
+GENERATE_BUGLIST = YES
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or
+# disable (NO) the deprecated list. This list is created by putting
+# \deprecated commands in the documentation.
+
+GENERATE_DEPRECATEDLIST= YES
+
+# The ENABLED_SECTIONS tag can be used to enable conditional
+# documentation sections, marked by \if sectionname ... \endif.
+
+ENABLED_SECTIONS =
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines
+# the initial value of a variable or macro consists of for it to appear in
+# the documentation. If the initializer consists of more lines than specified
+# here it will be hidden. Use a value of 0 to hide initializers completely.
+# The appearance of the initializer of individual variables and macros in the
+# documentation can be controlled using \showinitializer or \hideinitializer
+# command in the documentation regardless of this setting.
+
+MAX_INITIALIZER_LINES = 30
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated
+# at the bottom of the documentation of classes and structs. If set to YES the
+# list will mention the files that were used to generate the documentation.
+
+SHOW_USED_FILES = YES
+
+# If the sources in your project are distributed over multiple directories
+# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy
+# in the documentation. The default is NO.
+
+SHOW_DIRECTORIES = NO
+
+# Set the SHOW_FILES tag to NO to disable the generation of the Files page.
+# This will remove the Files entry from the Quick Index and from the
+# Folder Tree View (if specified). The default is YES.
+
+SHOW_FILES = YES
+
+# Set the SHOW_NAMESPACES tag to NO to disable the generation of the
+# Namespaces page.
+# This will remove the Namespaces entry from the Quick Index
+# and from the Folder Tree View (if specified). The default is YES.
+
+SHOW_NAMESPACES = YES
+
+# The FILE_VERSION_FILTER tag can be used to specify a program or script that
+# doxygen should invoke to get the current version for each file (typically from
+# the version control system). Doxygen will invoke the program by executing (via
+# popen()) the command <command> <input-file>, where <command> is the value of
+# the FILE_VERSION_FILTER tag, and <input-file> is the name of an input file
+# provided by doxygen. Whatever the program writes to standard output
+# is used as the file version. See the manual for examples.
+
+FILE_VERSION_FILTER =
+
+# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed
+# by doxygen. The layout file controls the global structure of the generated
+# output files in an output format independent way. The create the layout file
+# that represents doxygen's defaults, run doxygen with the -l option.
+# You can optionally specify a file name after the option, if omitted
+# DoxygenLayout.xml will be used as the name of the layout file.
+
+LAYOUT_FILE =
+
+#---------------------------------------------------------------------------
+# configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated
+# by doxygen. Possible values are YES and NO. If left blank NO is used.
+
+QUIET = NO
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are
+# generated by doxygen. Possible values are YES and NO. If left blank
+# NO is used.
+
+WARNINGS = YES
+
+# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings
+# for undocumented members. If EXTRACT_ALL is set to YES then this flag will
+# automatically be disabled.
+
+WARN_IF_UNDOCUMENTED = YES
+
+# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for
+# potential errors in the documentation, such as not documenting some
+# parameters in a documented function, or documenting parameters that
+# don't exist or using markup commands wrongly.
+
+WARN_IF_DOC_ERROR = YES
+
+# The WARN_NO_PARAMDOC option can be enabled to get warnings for
+# functions that are documented, but have no documentation for their parameters
+# or return value. If set to NO (the default) doxygen will only warn about
+# wrong or incomplete parameter documentation, but not about the absence of
+# documentation.
+
+WARN_NO_PARAMDOC = NO
+
+# The WARN_FORMAT tag determines the format of the warning messages that
+# doxygen can produce. The string should contain the $file, $line, and $text
+# tags, which will be replaced by the file and line number from which the
+# warning originated and the warning text. Optionally the format may contain
+# $version, which will be replaced by the version of the file (if it could
+# be obtained via FILE_VERSION_FILTER)
+
+WARN_FORMAT = "$file:$line: $text"
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning
+# and error messages should be written. If left blank the output is written
+# to stderr.
+
+WARN_LOGFILE =
+
+#---------------------------------------------------------------------------
+# configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag can be used to specify the files and/or directories that contain
+# documented source files. You may enter file names like "myfile.cpp" or
+# directories like "/usr/src/myproject". Separate the files or directories
+# with spaces.
+
+INPUT = @srcdir@/include/osmocom/isdn @srcdir@/src/isdn
+
+# This tag can be used to specify the character encoding of the source files
+# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is
+# also the default input encoding. Doxygen uses libiconv (or the iconv built
+# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for
+# the list of possible encodings.
+
+INPUT_ENCODING = UTF-8
+
+# If the value of the INPUT tag contains directories, you can use the
+# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
+# and *.h) to filter out the source-files in the directories. If left
+# blank the following patterns are tested:
+# *.c *.cc *.cxx *.cpp *.c++ *.d *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh
+# *.hxx *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.dox *.py
+# *.f90 *.f *.for *.vhd *.vhdl
+
+FILE_PATTERNS =
+
+# The RECURSIVE tag can be used to turn specify whether or not subdirectories
+# should be searched for input files as well. Possible values are YES and NO.
+# If left blank NO is used.
+
+RECURSIVE = YES
+
+# The EXCLUDE tag can be used to specify files and/or directories that should
+# excluded from the INPUT source files. This way you can easily exclude a
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+
+EXCLUDE =
+
+# The EXCLUDE_SYMLINKS tag can be used select whether or not files or
+# directories that are symbolic links (a Unix file system feature) are excluded
+# from the input.
+
+EXCLUDE_SYMLINKS = NO
+
+# If the value of the INPUT tag contains directories, you can use the
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
+# certain files from those directories. Note that the wildcards are matched
+# against the file with absolute path, so to exclude all test directories
+# for example use the pattern */test/*
+
+EXCLUDE_PATTERNS =
+
+# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
+# (namespaces, classes, functions, etc.) that should be excluded from the
+# output. The symbol name can be a fully qualified name, a word, or if the
+# wildcard * is used, a substring. Examples: ANamespace, AClass,
+# AClass::ANamespace, ANamespace::*Test
+
+EXCLUDE_SYMBOLS =
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or
+# directories that contain example code fragments that are included (see
+# the \include command).
+
+EXAMPLE_PATH =
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
+# and *.h) to filter out the source-files in the directories. If left
+# blank all files are included.
+
+EXAMPLE_PATTERNS =
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
+# searched for input files to be used with the \include or \dontinclude
+# commands irrespective of the value of the RECURSIVE tag.
+# Possible values are YES and NO. If left blank NO is used.
+
+EXAMPLE_RECURSIVE = NO
+
+# The IMAGE_PATH tag can be used to specify one or more files or
+# directories that contain image that are included in the documentation (see
+# the \image command).
+
+# IMAGE_PATH = images/
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should
+# invoke to filter for each input file. Doxygen will invoke the filter program
+# by executing (via popen()) the command <filter> <input-file>, where <filter>
+# is the value of the INPUT_FILTER tag, and <input-file> is the name of an
+# input file. Doxygen will then use the output that the filter program writes
+# to standard output.
+# If FILTER_PATTERNS is specified, this tag will be
+# ignored.
+
+INPUT_FILTER =
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
+# basis.
+# Doxygen will compare the file name with each pattern and apply the
+# filter if there is a match.
+# The filters are a list of the form:
+# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further
+# info on how filters are used. If FILTER_PATTERNS is empty or if
+# non of the patterns match the file name, INPUT_FILTER is applied.
+
+FILTER_PATTERNS =
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
+# INPUT_FILTER) will be used to filter the input files when producing source
+# files to browse (i.e. when SOURCE_BROWSER is set to YES).
+
+FILTER_SOURCE_FILES = NO
+
+# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file
+# pattern. A pattern will override the setting for FILTER_PATTERN (if any)
+# and it is also possible to disable source filtering for a specific pattern
+# using *.ext= (so without naming a filter). This option only has effect when
+# FILTER_SOURCE_FILES is enabled.
+
+FILTER_SOURCE_PATTERNS =
+
+#---------------------------------------------------------------------------
+# configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will
+# be generated. Documented entities will be cross-referenced with these sources.
+# Note: To get rid of all source code in the generated output, make sure also
+# VERBATIM_HEADERS is set to NO.
+
+SOURCE_BROWSER = NO
+
+# Setting the INLINE_SOURCES tag to YES will include the body
+# of functions and classes directly in the documentation.
+
+INLINE_SOURCES = NO
+
+# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct
+# doxygen to hide any special comment blocks from generated source code
+# fragments. Normal C and C++ comments will always remain visible.
+
+STRIP_CODE_COMMENTS = YES
+
+# If the REFERENCED_BY_RELATION tag is set to YES
+# then for each documented function all documented
+# functions referencing it will be listed.
+
+REFERENCED_BY_RELATION = YES
+
+# If the REFERENCES_RELATION tag is set to YES
+# then for each documented function all documented entities
+# called/used by that function will be listed.
+
+REFERENCES_RELATION = YES
+
+# If the REFERENCES_LINK_SOURCE tag is set to YES (the default)
+# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from
+# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will
+# link to the source code.
+# Otherwise they will link to the documentation.
+
+REFERENCES_LINK_SOURCE = YES
+
+# If the USE_HTAGS tag is set to YES then the references to source code
+# will point to the HTML generated by the htags(1) tool instead of doxygen
+# built-in source browser. The htags tool is part of GNU's global source
+# tagging system (see http://www.gnu.org/software/global/global.html). You
+# will need version 4.8.6 or higher.
+
+USE_HTAGS = NO
+
+# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen
+# will generate a verbatim copy of the header file for each class for
+# which an include is specified. Set to NO to disable this.
+
+VERBATIM_HEADERS = YES
+
+#---------------------------------------------------------------------------
+# configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index
+# of all compounds will be generated. Enable this if the project
+# contains a lot of classes, structs, unions or interfaces.
+
+ALPHABETICAL_INDEX = NO
+
+# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then
+# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns
+# in which this list will be split (can be a number in the range [1..20])
+
+COLS_IN_ALPHA_INDEX = 5
+
+# In case all classes in a project start with a common prefix, all
+# classes will be put under the same header in the alphabetical index.
+# The IGNORE_PREFIX tag can be used to specify one or more prefixes that
+# should be ignored while generating the index headers.
+
+IGNORE_PREFIX =
+
+#---------------------------------------------------------------------------
+# configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES (the default) Doxygen will
+# generate HTML output.
+
+GENERATE_HTML = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `html' will be used as the default path.
+
+HTML_OUTPUT = html
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for
+# each generated HTML page (for example: .htm,.php,.asp). If it is left blank
+# doxygen will generate files with .html extension.
+
+HTML_FILE_EXTENSION = .html
+
+# The HTML_HEADER tag can be used to specify a personal HTML header for
+# each generated HTML page. If it is left blank doxygen will generate a
+# standard header. Note that when using a custom header you are responsible
+# for the proper inclusion of any scripts and style sheets that doxygen
+# needs, which is dependent on the configuration options used.
+# It is advised to generate a default header using "doxygen -w html
+# header.html footer.html stylesheet.css YourConfigFile" and then modify
+# that header. Note that the header is subject to change so you typically
+# have to redo this when upgrading to a newer version of doxygen or when changing the value of configuration settings such as GENERATE_TREEVIEW!
+
+HTML_HEADER =
+
+# The HTML_FOOTER tag can be used to specify a personal HTML footer for
+# each generated HTML page. If it is left blank doxygen will generate a
+# standard footer.
+
+HTML_FOOTER =
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading
+# style sheet that is used by each HTML page. It can be used to
+# fine-tune the look of the HTML output. If the tag is left blank doxygen
+# will generate a default style sheet. Note that doxygen will try to copy
+# the style sheet file to the HTML output directory, so don't put your own
+# stylesheet in the HTML output directory as well, or it will be erased!
+
+HTML_STYLESHEET =
+
+# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the HTML output directory. Note
+# that these files will be copied to the base HTML output directory. Use the
+# $relpath$ marker in the HTML_HEADER and/or HTML_FOOTER files to load these
+# files. In the HTML_STYLESHEET file, use the file name only. Also note that
+# the files will be copied as-is; there are no commands or markers available.
+
+HTML_EXTRA_FILES =
+
+# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output.
+# Doxygen will adjust the colors in the stylesheet and background images
+# according to this color. Hue is specified as an angle on a colorwheel,
+# see http://en.wikipedia.org/wiki/Hue for more information.
+# For instance the value 0 represents red, 60 is yellow, 120 is green,
+# 180 is cyan, 240 is blue, 300 purple, and 360 is red again.
+# The allowed range is 0 to 359.
+
+HTML_COLORSTYLE_HUE = 220
+
+# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of
+# the colors in the HTML output. For a value of 0 the output will use
+# grayscales only. A value of 255 will produce the most vivid colors.
+
+HTML_COLORSTYLE_SAT = 100
+
+# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to
+# the luminance component of the colors in the HTML output. Values below
+# 100 gradually make the output lighter, whereas values above 100 make
+# the output darker. The value divided by 100 is the actual gamma applied,
+# so 80 represents a gamma of 0.8, The value 220 represents a gamma of 2.2,
+# and 100 does not change the gamma.
+
+HTML_COLORSTYLE_GAMMA = 80
+
+# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML
+# page will contain the date and time when the page was generated. Setting
+# this to NO can help when comparing the output of multiple runs.
+
+HTML_TIMESTAMP = NO
+
+# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes,
+# files or namespaces will be aligned in HTML using tables. If set to
+# NO a bullet list will be used.
+
+HTML_ALIGN_MEMBERS = YES
+
+# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
+# documentation will contain sections that can be hidden and shown after the
+# page has loaded. For this to work a browser that supports
+# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox
+# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari).
+
+HTML_DYNAMIC_SECTIONS = NO
+
+# If the GENERATE_DOCSET tag is set to YES, additional index files
+# will be generated that can be used as input for Apple's Xcode 3
+# integrated development environment, introduced with OSX 10.5 (Leopard).
+# To create a documentation set, doxygen will generate a Makefile in the
+# HTML output directory. Running make will produce the docset in that
+# directory and running "make install" will install the docset in
+# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find
+# it at startup.
+# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html
+# for more information.
+
+GENERATE_DOCSET = NO
+
+# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the
+# feed. A documentation feed provides an umbrella under which multiple
+# documentation sets from a single provider (such as a company or product suite)
+# can be grouped.
+
+DOCSET_FEEDNAME = "Doxygen generated docs"
+
+# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that
+# should uniquely identify the documentation set bundle. This should be a
+# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen
+# will append .docset to the name.
+
+DOCSET_BUNDLE_ID = org.doxygen.Project
+
+# When GENERATE_PUBLISHER_ID tag specifies a string that should uniquely identify
+# the documentation publisher. This should be a reverse domain-name style
+# string, e.g. com.mycompany.MyDocSet.documentation.
+
+DOCSET_PUBLISHER_ID = org.doxygen.Publisher
+
+# The GENERATE_PUBLISHER_NAME tag identifies the documentation publisher.
+
+DOCSET_PUBLISHER_NAME = Publisher
+
+# If the GENERATE_HTMLHELP tag is set to YES, additional index files
+# will be generated that can be used as input for tools like the
+# Microsoft HTML help workshop to generate a compiled HTML help file (.chm)
+# of the generated HTML documentation.
+
+GENERATE_HTMLHELP = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can
+# be used to specify the file name of the resulting .chm file. You
+# can add a path in front of the file if the result should not be
+# written to the html output directory.
+
+CHM_FILE =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can
+# be used to specify the location (absolute path including file name) of
+# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run
+# the HTML help compiler on the generated index.hhp.
+
+HHC_LOCATION =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag
+# controls if a separate .chi index file is generated (YES) or that
+# it should be included in the master .chm file (NO).
+
+GENERATE_CHI = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING
+# is used to encode HtmlHelp index (hhk), content (hhc) and project file
+# content.
+
+CHM_INDEX_ENCODING =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag
+# controls whether a binary table of contents is generated (YES) or a
+# normal table of contents (NO) in the .chm file.
+
+BINARY_TOC = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members
+# to the contents of the HTML help documentation and to the tree view.
+
+TOC_EXPAND = NO
+
+# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and
+# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated
+# that can be used as input for Qt's qhelpgenerator to generate a
+# Qt Compressed Help (.qch) of the generated HTML documentation.
+
+GENERATE_QHP = NO
+
+# If the QHG_LOCATION tag is specified, the QCH_FILE tag can
+# be used to specify the file name of the resulting .qch file.
+# The path specified is relative to the HTML output folder.
+
+QCH_FILE =
+
+# The QHP_NAMESPACE tag specifies the namespace to use when generating
+# Qt Help Project output. For more information please see
+# http://doc.trolltech.com/qthelpproject.html#namespace
+
+QHP_NAMESPACE = org.doxygen.Project
+
+# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating
+# Qt Help Project output. For more information please see
+# http://doc.trolltech.com/qthelpproject.html#virtual-folders
+
+QHP_VIRTUAL_FOLDER = doc
+
+# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to
+# add. For more information please see
+# http://doc.trolltech.com/qthelpproject.html#custom-filters
+
+QHP_CUST_FILTER_NAME =
+
+# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the
+# custom filter to add. For more information please see
+# <a href="http://doc.trolltech.com/qthelpproject.html#custom-filters">
+# Qt Help Project / Custom Filters</a>.
+
+QHP_CUST_FILTER_ATTRS =
+
+# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this
+# project's
+# filter section matches.
+# <a href="http://doc.trolltech.com/qthelpproject.html#filter-attributes">
+# Qt Help Project / Filter Attributes</a>.
+
+QHP_SECT_FILTER_ATTRS =
+
+# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can
+# be used to specify the location of Qt's qhelpgenerator.
+# If non-empty doxygen will try to run qhelpgenerator on the generated
+# .qhp file.
+
+QHG_LOCATION =
+
+# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files
+# will be generated, which together with the HTML files, form an Eclipse help
+# plugin. To install this plugin and make it available under the help contents
+# menu in Eclipse, the contents of the directory containing the HTML and XML
+# files needs to be copied into the plugins directory of eclipse. The name of
+# the directory within the plugins directory should be the same as
+# the ECLIPSE_DOC_ID value. After copying Eclipse needs to be restarted before
+# the help appears.
+
+GENERATE_ECLIPSEHELP = NO
+
+# A unique identifier for the eclipse help plugin. When installing the plugin
+# the directory name containing the HTML and XML files should also have
+# this name.
+
+ECLIPSE_DOC_ID = org.doxygen.Project
+
+# The DISABLE_INDEX tag can be used to turn on/off the condensed index at
+# top of each HTML page. The value NO (the default) enables the index and
+# the value YES disables it.
+
+DISABLE_INDEX = NO
+
+# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values
+# (range [0,1..20]) that doxygen will group on one line in the generated HTML
+# documentation. Note that a value of 0 will completely suppress the enum
+# values from appearing in the overview section.
+
+ENUM_VALUES_PER_LINE = 1
+
+# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
+# structure should be generated to display hierarchical information.
+# If the tag value is set to YES, a side panel will be generated
+# containing a tree-like index structure (just like the one that
+# is generated for HTML Help). For this to work a browser that supports
+# JavaScript, DHTML, CSS and frames is required (i.e. any modern browser).
+# Windows users are probably better off using the HTML help feature.
+
+GENERATE_TREEVIEW = YES
+
+# By enabling USE_INLINE_TREES, doxygen will generate the Groups, Directories,
+# and Class Hierarchy pages using a tree view instead of an ordered list.
+
+USE_INLINE_TREES = NO
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be
+# used to set the initial width (in pixels) of the frame in which the tree
+# is shown.
+
+TREEVIEW_WIDTH = 250
+
+# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open
+# links to external symbols imported via tag files in a separate window.
+
+EXT_LINKS_IN_WINDOW = NO
+
+# Use this tag to change the font size of Latex formulas included
+# as images in the HTML documentation. The default is 10. Note that
+# when you change the font size after a successful doxygen run you need
+# to manually remove any form_*.png images from the HTML output directory
+# to force them to be regenerated.
+
+FORMULA_FONTSIZE = 10
+
+# Use the FORMULA_TRANPARENT tag to determine whether or not the images
+# generated for formulas are transparent PNGs. Transparent PNGs are
+# not supported properly for IE 6.0, but are supported on all modern browsers.
+# Note that when changing this option you need to delete any form_*.png files
+# in the HTML output before the changes have effect.
+
+FORMULA_TRANSPARENT = YES
+
+# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax
+# (see http://www.mathjax.org) which uses client side Javascript for the
+# rendering instead of using prerendered bitmaps. Use this if you do not
+# have LaTeX installed or if you want to formulas look prettier in the HTML
+# output. When enabled you also need to install MathJax separately and
+# configure the path to it using the MATHJAX_RELPATH option.
+
+USE_MATHJAX = NO
+
+# When MathJax is enabled you need to specify the location relative to the
+# HTML output directory using the MATHJAX_RELPATH option. The destination
+# directory should contain the MathJax.js script. For instance, if the mathjax
+# directory is located at the same level as the HTML output directory, then
+# MATHJAX_RELPATH should be ../mathjax. The default value points to the
+# mathjax.org site, so you can quickly see the result without installing
+# MathJax, but it is strongly recommended to install a local copy of MathJax
+# before deployment.
+
+MATHJAX_RELPATH = http://www.mathjax.org/mathjax
+
+# When the SEARCHENGINE tag is enabled doxygen will generate a search box
+# for the HTML output. The underlying search engine uses javascript
+# and DHTML and should work on any modern browser. Note that when using
+# HTML help (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets
+# (GENERATE_DOCSET) there is already a search function so this one should
+# typically be disabled. For large projects the javascript based search engine
+# can be slow, then enabling SERVER_BASED_SEARCH may provide a better solution.
+
+SEARCHENGINE = YES
+
+# When the SERVER_BASED_SEARCH tag is enabled the search engine will be
+# implemented using a PHP enabled web server instead of at the web client
+# using Javascript. Doxygen will generate the search PHP script and index
+# file to put on the web server. The advantage of the server
+# based approach is that it scales better to large projects and allows
+# full text search. The disadvantages are that it is more difficult to setup
+# and does not have live searching capabilities.
+
+SERVER_BASED_SEARCH = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will
+# generate Latex output.
+
+GENERATE_LATEX = YES
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `latex' will be used as the default path.
+
+LATEX_OUTPUT = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
+# invoked. If left blank `latex' will be used as the default command name.
+# Note that when enabling USE_PDFLATEX this option is only used for
+# generating bitmaps for formulas in the HTML output, but not in the
+# Makefile that is written to the output directory.
+
+LATEX_CMD_NAME = latex
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to
+# generate index for LaTeX. If left blank `makeindex' will be used as the
+# default command name.
+
+MAKEINDEX_CMD_NAME = makeindex
+
+# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact
+# LaTeX documents. This may be useful for small projects and may help to
+# save some trees in general.
+
+COMPACT_LATEX = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used
+# by the printer. Possible values are: a4, letter, legal and
+# executive. If left blank a4wide will be used.
+
+PAPER_TYPE = a4wide
+
+# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX
+# packages that should be included in the LaTeX output.
+
+EXTRA_PACKAGES =
+
+# The LATEX_HEADER tag can be used to specify a personal LaTeX header for
+# the generated latex document. The header should contain everything until
+# the first chapter. If it is left blank doxygen will generate a
+# standard header. Notice: only use this tag if you know what you are doing!
+
+LATEX_HEADER =
+
+# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for
+# the generated latex document. The footer should contain everything after
+# the last chapter. If it is left blank doxygen will generate a
+# standard footer. Notice: only use this tag if you know what you are doing!
+
+LATEX_FOOTER =
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated
+# is prepared for conversion to pdf (using ps2pdf). The pdf file will
+# contain links (just like the HTML output) instead of page references
+# This makes the output suitable for online browsing using a pdf viewer.
+
+PDF_HYPERLINKS = NO
+
+# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of
+# plain latex in the generated Makefile. Set this option to YES to get a
+# higher quality PDF documentation.
+
+USE_PDFLATEX = NO
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode.
+# command to the generated LaTeX files. This will instruct LaTeX to keep
+# running if errors occur, instead of asking the user for help.
+# This option is also used when generating formulas in HTML.
+
+LATEX_BATCHMODE = NO
+
+# If LATEX_HIDE_INDICES is set to YES then doxygen will not
+# include the index chapters (such as File Index, Compound Index, etc.)
+# in the output.
+
+LATEX_HIDE_INDICES = NO
+
+# If LATEX_SOURCE_CODE is set to YES then doxygen will include
+# source code with syntax highlighting in the LaTeX output.
+# Note that which sources are shown also depends on other settings
+# such as SOURCE_BROWSER.
+
+LATEX_SOURCE_CODE = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output
+# The RTF output is optimized for Word 97 and may not look very pretty with
+# other RTF readers or editors.
+
+GENERATE_RTF = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `rtf' will be used as the default path.
+
+RTF_OUTPUT = rtf
+
+# If the COMPACT_RTF tag is set to YES Doxygen generates more compact
+# RTF documents. This may be useful for small projects and may help to
+# save some trees in general.
+
+COMPACT_RTF = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated
+# will contain hyperlink fields. The RTF file will
+# contain links (just like the HTML output) instead of page references.
+# This makes the output suitable for online browsing using WORD or other
+# programs which support those fields.
+# Note: wordpad (write) and others do not support links.
+
+RTF_HYPERLINKS = NO
+
+# Load stylesheet definitions from file. Syntax is similar to doxygen's
+# config file, i.e. a series of assignments. You only have to provide
+# replacements, missing definitions are set to their default value.
+
+RTF_STYLESHEET_FILE =
+
+# Set optional variables used in the generation of an rtf document.
+# Syntax is similar to doxygen's config file.
+
+RTF_EXTENSIONS_FILE =
+
+#---------------------------------------------------------------------------
+# configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES (the default) Doxygen will
+# generate man pages
+
+GENERATE_MAN = NO
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `man' will be used as the default path.
+
+MAN_OUTPUT = man
+
+# The MAN_EXTENSION tag determines the extension that is added to
+# the generated man pages (default is the subroutine's section .3)
+
+MAN_EXTENSION = .3
+
+# If the MAN_LINKS tag is set to YES and Doxygen generates man output,
+# then it will generate one additional man file for each entity
+# documented in the real man page(s). These additional files
+# only source the real man page, but without them the man command
+# would be unable to find the correct page. The default is NO.
+
+MAN_LINKS = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES Doxygen will
+# generate an XML file that captures the structure of
+# the code including all documentation.
+
+GENERATE_XML = NO
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `xml' will be used as the default path.
+
+XML_OUTPUT = xml
+
+# The XML_SCHEMA tag can be used to specify an XML schema,
+# which can be used by a validating XML parser to check the
+# syntax of the XML files.
+
+XML_SCHEMA =
+
+# The XML_DTD tag can be used to specify an XML DTD,
+# which can be used by a validating XML parser to check the
+# syntax of the XML files.
+
+XML_DTD =
+
+# If the XML_PROGRAMLISTING tag is set to YES Doxygen will
+# dump the program listings (including syntax highlighting
+# and cross-referencing information) to the XML output. Note that
+# enabling this will significantly increase the size of the XML output.
+
+XML_PROGRAMLISTING = YES
+
+#---------------------------------------------------------------------------
+# configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will
+# generate an AutoGen Definitions (see autogen.sf.net) file
+# that captures the structure of the code including all
+# documentation. Note that this feature is still experimental
+# and incomplete at the moment.
+
+GENERATE_AUTOGEN_DEF = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES Doxygen will
+# generate a Perl module file that captures the structure of
+# the code including all documentation. Note that this
+# feature is still experimental and incomplete at the
+# moment.
+
+GENERATE_PERLMOD = NO
+
+# If the PERLMOD_LATEX tag is set to YES Doxygen will generate
+# the necessary Makefile rules, Perl scripts and LaTeX code to be able
+# to generate PDF and DVI output from the Perl module output.
+
+PERLMOD_LATEX = NO
+
+# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be
+# nicely formatted so it can be parsed by a human reader.
+# This is useful
+# if you want to understand what is going on.
+# On the other hand, if this
+# tag is set to NO the size of the Perl module output will be much smaller
+# and Perl will parse it just the same.
+
+PERLMOD_PRETTY = YES
+
+# The names of the make variables in the generated doxyrules.make file
+# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX.
+# This is useful so different doxyrules.make files included by the same
+# Makefile don't overwrite each other's variables.
+
+PERLMOD_MAKEVAR_PREFIX =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will
+# evaluate all C-preprocessor directives found in the sources and include
+# files.
+
+ENABLE_PREPROCESSING = YES
+
+# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro
+# names in the source code. If set to NO (the default) only conditional
+# compilation will be performed. Macro expansion can be done in a controlled
+# way by setting EXPAND_ONLY_PREDEF to YES.
+
+MACRO_EXPANSION = NO
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES
+# then the macro expansion is limited to the macros specified with the
+# PREDEFINED and EXPAND_AS_DEFINED tags.
+
+EXPAND_ONLY_PREDEF = NO
+
+# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files
+# pointed to by INCLUDE_PATH will be searched when a #include is found.
+
+SEARCH_INCLUDES = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that
+# contain include files that are not input files but should be processed by
+# the preprocessor.
+
+INCLUDE_PATH =
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
+# patterns (like *.h and *.hpp) to filter out the header-files in the
+# directories. If left blank, the patterns specified with FILE_PATTERNS will
+# be used.
+
+INCLUDE_FILE_PATTERNS =
+
+# The PREDEFINED tag can be used to specify one or more macro names that
+# are defined before the preprocessor is started (similar to the -D option of
+# gcc). The argument of the tag is a list of macros of the form: name
+# or name=definition (no spaces). If the definition and the = are
+# omitted =1 is assumed. To prevent a macro definition from being
+# undefined via #undef or recursively expanded use the := operator
+# instead of the = operator.
+
+PREDEFINED =
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then
+# this tag can be used to specify a list of macro names that should be expanded.
+# The macro definition that is found in the sources will be used.
+# Use the PREDEFINED tag if you want to use a different macro definition that
+# overrules the definition found in the source code.
+
+EXPAND_AS_DEFINED =
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then
+# doxygen's preprocessor will remove all references to function-like macros
+# that are alone on a line, have an all uppercase name, and do not end with a
+# semicolon, because these will confuse the parser if not removed.
+
+SKIP_FUNCTION_MACROS = YES
+
+#---------------------------------------------------------------------------
+# Configuration::additions related to external references
+#---------------------------------------------------------------------------
+
+# The TAGFILES option can be used to specify one or more tagfiles.
+# Optionally an initial location of the external documentation
+# can be added for each tagfile. The format of a tag file without
+# this location is as follows:
+#
+# TAGFILES = file1 file2 ...
+# Adding location for the tag files is done as follows:
+#
+# TAGFILES = file1=loc1 "file2 = loc2" ...
+# where "loc1" and "loc2" can be relative or absolute paths or
+# URLs. If a location is present for each tag, the installdox tool
+# does not have to be run to correct the links.
+# Note that each tag file must have a unique name
+# (where the name does NOT include the path)
+# If a tag file is not located in the directory in which doxygen
+# is run, you must also specify the path to the tagfile here.
+
+TAGFILES = doc/libosmocore.tag=../../core/html doc/libosmovty.tag=../../vty/html doc/libosmocodec.tag=../../codec/html doc/libosmocoding.tag=../../coding/html doc/libosmoctrl.tag=../../ctrl/html doc/libosmogb.tag=../../gb/html
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create
+# a tag file that is based on the input files it reads.
+
+GENERATE_TAGFILE = doc/libosmoisdn.tag
+
+# If the ALLEXTERNALS tag is set to YES all external classes will be listed
+# in the class index. If set to NO only the inherited external classes
+# will be listed.
+
+ALLEXTERNALS = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed
+# in the modules index. If set to NO, only the current project's groups will
+# be listed.
+
+EXTERNAL_GROUPS = YES
+
+# The PERL_PATH should be the absolute path and name of the perl script
+# interpreter (i.e. the result of `which perl').
+
+PERL_PATH = /usr/bin/perl
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool
+#---------------------------------------------------------------------------
+
+# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will
+# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base
+# or super classes. Setting the tag to NO turns the diagrams off. Note that
+# this option also works with HAVE_DOT disabled, but it is recommended to
+# install and use dot, since it yields more powerful graphs.
+
+CLASS_DIAGRAMS = YES
+
+# You can define message sequence charts within doxygen comments using the \msc
+# command. Doxygen will then run the mscgen tool (see
+# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the
+# documentation. The MSCGEN_PATH tag allows you to specify the directory where
+# the mscgen tool resides. If left empty the tool is assumed to be found in the
+# default search path.
+
+MSCGEN_PATH =
+
+# If set to YES, the inheritance and collaboration graphs will hide
+# inheritance and usage relations if the target is undocumented
+# or is not a class.
+
+HIDE_UNDOC_RELATIONS = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
+# available from the path. This tool is part of Graphviz, a graph visualization
+# toolkit from AT&T and Lucent Bell Labs. The other options in this section
+# have no effect if this option is set to NO (the default)
+
+HAVE_DOT = NO
+
+# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is
+# allowed to run in parallel. When set to 0 (the default) doxygen will
+# base this on the number of processors available in the system. You can set it
+# explicitly to a value larger than 0 to get control over the balance
+# between CPU load and processing speed.
+
+DOT_NUM_THREADS = 0
+
+# By default doxygen will write a font called Helvetica to the output
+# directory and reference it in all dot files that doxygen generates.
+# When you want a differently looking font you can specify the font name
+# using DOT_FONTNAME. You need to make sure dot is able to find the font,
+# which can be done by putting it in a standard location or by setting the
+# DOTFONTPATH environment variable or by setting DOT_FONTPATH to the directory
+# containing the font.
+
+DOT_FONTNAME = Helvetica
+
+# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs.
+# The default size is 10pt.
+
+DOT_FONTSIZE = 10
+
+# By default doxygen will tell dot to use the output directory to look for the
+# FreeSans.ttf font (which doxygen will put there itself). If you specify a
+# different font using DOT_FONTNAME you can set the path where dot
+# can find it using this tag.
+
+DOT_FONTPATH =
+
+# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for each documented class showing the direct and
+# indirect inheritance relations. Setting this tag to YES will force the
+# the CLASS_DIAGRAMS tag to NO.
+
+CLASS_GRAPH = YES
+
+# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for each documented class showing the direct and
+# indirect implementation dependencies (inheritance, containment, and
+# class references variables) of the class with other documented classes.
+
+COLLABORATION_GRAPH = YES
+
+# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for groups, showing the direct groups dependencies
+
+GROUP_GRAPHS = YES
+
+# If the UML_LOOK tag is set to YES doxygen will generate inheritance and
+# collaboration diagrams in a style similar to the OMG's Unified Modeling
+# Language.
+
+UML_LOOK = NO
+
+# If set to YES, the inheritance and collaboration graphs will show the
+# relations between templates and their instances.
+
+TEMPLATE_RELATIONS = NO
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT
+# tags are set to YES then doxygen will generate a graph for each documented
+# file showing the direct and indirect include dependencies of the file with
+# other documented files.
+
+INCLUDE_GRAPH = YES
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and
+# HAVE_DOT tags are set to YES then doxygen will generate a graph for each
+# documented header file showing the documented files that directly or
+# indirectly include this file.
+
+INCLUDED_BY_GRAPH = YES
+
+# If the CALL_GRAPH and HAVE_DOT options are set to YES then
+# doxygen will generate a call dependency graph for every global function
+# or class method. Note that enabling this option will significantly increase
+# the time of a run. So in most cases it will be better to enable call graphs
+# for selected functions only using the \callgraph command.
+
+CALL_GRAPH = NO
+
+# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then
+# doxygen will generate a caller dependency graph for every global function
+# or class method. Note that enabling this option will significantly increase
+# the time of a run. So in most cases it will be better to enable caller
+# graphs for selected functions only using the \callergraph command.
+
+CALLER_GRAPH = NO
+
+# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen
+# will generate a graphical hierarchy of all classes instead of a textual one.
+
+GRAPHICAL_HIERARCHY = YES
+
+# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES
+# then doxygen will show the dependencies a directory has on other directories
+# in a graphical way. The dependency relations are determined by the #include
+# relations between the files in the directories.
+
+DIRECTORY_GRAPH = YES
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
+# generated by dot. Possible values are svg, png, jpg, or gif.
+# If left blank png will be used.
+
+DOT_IMAGE_FORMAT = png
+
+# The tag DOT_PATH can be used to specify the path where the dot tool can be
+# found. If left blank, it is assumed the dot tool can be found in the path.
+
+DOT_PATH = /usr/bin/dot
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that
+# contain dot files that are included in the documentation (see the
+# \dotfile command).
+
+DOTFILE_DIRS =
+
+# The MSCFILE_DIRS tag can be used to specify one or more directories that
+# contain msc files that are included in the documentation (see the
+# \mscfile command).
+
+MSCFILE_DIRS =
+
+# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of
+# nodes that will be shown in the graph. If the number of nodes in a graph
+# becomes larger than this value, doxygen will truncate the graph, which is
+# visualized by representing a node as a red box. Note that doxygen if the
+# number of direct children of the root node in a graph is already larger than
+# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note
+# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
+
+DOT_GRAPH_MAX_NODES = 50
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the
+# graphs generated by dot. A depth value of 3 means that only nodes reachable
+# from the root by following a path via at most 3 edges will be shown. Nodes
+# that lay further from the root node will be omitted. Note that setting this
+# option to 1 or 2 may greatly reduce the computation time needed for large
+# code bases. Also note that the size of a graph can be further restricted by
+# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
+
+MAX_DOT_GRAPH_DEPTH = 0
+
+# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
+# background. This is disabled by default, because dot on Windows does not
+# seem to support this out of the box. Warning: Depending on the platform used,
+# enabling this option may lead to badly anti-aliased labels on the edges of
+# a graph (i.e. they become hard to read).
+
+DOT_TRANSPARENT = NO
+
+# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output
+# files in one run (i.e. multiple -o and -T options on the command line). This
+# makes dot run faster, but since only newer versions of dot (>1.8.10)
+# support this, this feature is disabled by default.
+
+DOT_MULTI_TARGETS = NO
+
+# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will
+# generate a legend page explaining the meaning of the various boxes and
+# arrows in the dot generated graphs.
+
+GENERATE_LEGEND = YES
+
+# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will
+# remove the intermediate dot files that are used to generate
+# the various graphs.
+
+DOT_CLEANUP = YES
diff --git a/Doxyfile.sim.in b/Doxyfile.sim.in
new file mode 100644
index 00000000..0c9dd73d
--- /dev/null
+++ b/Doxyfile.sim.in
@@ -0,0 +1,1716 @@
+# Doxyfile 1.7.4
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project.
+#
+# All text after a hash (#) is considered a comment and will be ignored.
+# The format is:
+# TAG = value [value, ...]
+# For lists items can also be appended using:
+# TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (" ").
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# This tag specifies the encoding used for all characters in the config file
+# that follow. The default is UTF-8 which is also the encoding used for all
+# text before the first occurrence of this tag. Doxygen uses libiconv (or the
+# iconv built into libc) for the transcoding. See
+# http://www.gnu.org/software/libiconv for the list of possible encodings.
+
+DOXYFILE_ENCODING = UTF-8
+
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded
+# by quotes) that should identify the project.
+
+PROJECT_NAME = libosmosim
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number.
+# This could be handy for archiving the generated documentation or
+# if some version control system is used.
+
+PROJECT_NUMBER = @VERSION@
+
+# Using the PROJECT_BRIEF tag one can provide an optional one line description
+# for a project that appears at the top of each page and should give viewer
+# a quick idea about the purpose of the project. Keep the description short.
+
+PROJECT_BRIEF = "Osmocom SIM library"
+
+# With the PROJECT_LOGO tag one can specify an logo or icon that is
+# included in the documentation. The maximum height of the logo should not
+# exceed 55 pixels and the maximum width should not exceed 200 pixels.
+# Doxygen will copy the logo to the output directory.
+
+PROJECT_LOGO =
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute)
+# base path where the generated documentation will be put.
+# If a relative path is entered, it will be relative to the location
+# where doxygen was started. If left blank the current directory will be used.
+
+OUTPUT_DIRECTORY = doc/sim
+
+# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create
+# 4096 sub-directories (in 2 levels) under the output directory of each output
+# format and will distribute the generated files over these directories.
+# Enabling this option can be useful when feeding doxygen a huge amount of
+# source files, where putting all generated files in the same directory would
+# otherwise cause performance problems for the file system.
+
+CREATE_SUBDIRS = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all
+# documentation generated by doxygen is written. Doxygen will use this
+# information to generate all constant output in the proper language.
+# The default language is English, other supported languages are:
+# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional,
+# Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German,
+# Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English
+# messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian,
+# Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak,
+# Slovene, Spanish, Swedish, Ukrainian, and Vietnamese.
+
+OUTPUT_LANGUAGE = English
+
+# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will
+# include brief member descriptions after the members that are listed in
+# the file and class documentation (similar to JavaDoc).
+# Set to NO to disable this.
+
+BRIEF_MEMBER_DESC = YES
+
+# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend
+# the brief description of a member or function before the detailed description.
+# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
+# brief descriptions will be completely suppressed.
+
+REPEAT_BRIEF = YES
+
+# This tag implements a quasi-intelligent brief description abbreviator
+# that is used to form the text in various listings. Each string
+# in this list, if found as the leading text of the brief description, will be
+# stripped from the text and the result after processing the whole list, is
+# used as the annotated text. Otherwise, the brief description is used as-is.
+# If left blank, the following values are used ("$name" is automatically
+# replaced with the name of the entity): "The $name class" "The $name widget"
+# "The $name file" "is" "provides" "specifies" "contains"
+# "represents" "a" "an" "the"
+
+ABBREVIATE_BRIEF =
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
+# Doxygen will generate a detailed section even if there is only a brief
+# description.
+
+ALWAYS_DETAILED_SEC = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
+# inherited members of a class in the documentation of that class as if those
+# members were ordinary class members. Constructors, destructors and assignment
+# operators of the base classes will not be shown.
+
+INLINE_INHERITED_MEMB = NO
+
+# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full
+# path before files name in the file list and in the header files. If set
+# to NO the shortest path that makes the file name unique will be used.
+
+FULL_PATH_NAMES = YES
+
+# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag
+# can be used to strip a user-defined part of the path. Stripping is
+# only done if one of the specified strings matches the left-hand part of
+# the path. The tag can be used to show relative paths in the file list.
+# If left blank the directory from which doxygen is run is used as the
+# path to strip.
+
+STRIP_FROM_PATH =
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of
+# the path mentioned in the documentation of a class, which tells
+# the reader which header file to include in order to use a class.
+# If left blank only the name of the header file containing the class
+# definition is used. Otherwise one should specify the include paths that
+# are normally passed to the compiler using the -I flag.
+
+STRIP_FROM_INC_PATH =
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter
+# (but less readable) file names. This can be useful if your file system
+# doesn't support long names like on DOS, Mac, or CD-ROM.
+
+SHORT_NAMES = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen
+# will interpret the first line (until the first dot) of a JavaDoc-style
+# comment as the brief description. If set to NO, the JavaDoc
+# comments will behave just like regular Qt-style comments
+# (thus requiring an explicit @brief command for a brief description.)
+
+JAVADOC_AUTOBRIEF = YES
+
+# If the QT_AUTOBRIEF tag is set to YES then Doxygen will
+# interpret the first line (until the first dot) of a Qt-style
+# comment as the brief description. If set to NO, the comments
+# will behave just like regular Qt-style comments (thus requiring
+# an explicit command for a brief description.)
+
+QT_AUTOBRIEF = YES
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen
+# treat a multi-line C++ special comment block (i.e. a block of //! or ///
+# comments) as a brief description. This used to be the default behaviour.
+# The new default is to treat a multi-line C++ comment block as a detailed
+# description. Set this tag to YES if you prefer the old behaviour instead.
+
+MULTILINE_CPP_IS_BRIEF = NO
+
+# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented
+# member inherits the documentation from any documented member that it
+# re-implements.
+
+INHERIT_DOCS = YES
+
+# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce
+# a new page for each member. If set to NO, the documentation of a member will
+# be part of the file/class/namespace that contains it.
+
+SEPARATE_MEMBER_PAGES = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab.
+# Doxygen uses this value to replace tabs by spaces in code fragments.
+
+TAB_SIZE = 8
+
+# This tag can be used to specify a number of aliases that acts
+# as commands in the documentation. An alias has the form "name=value".
+# For example adding "sideeffect=\par Side Effects:\n" will allow you to
+# put the command \sideeffect (or @sideeffect) in the documentation, which
+# will result in a user-defined paragraph with heading "Side Effects:".
+# You can put \n's in the value part of an alias to insert newlines.
+
+ALIASES =
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C
+# sources only. Doxygen will then generate output that is more tailored for C.
+# For instance, some of the names that are used will be different. The list
+# of all members will be omitted, etc.
+
+OPTIMIZE_OUTPUT_FOR_C = YES
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java
+# sources only. Doxygen will then generate output that is more tailored for
+# Java. For instance, namespaces will be presented as packages, qualified
+# scopes will look different, etc.
+
+OPTIMIZE_OUTPUT_JAVA = NO
+
+# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
+# sources only. Doxygen will then generate output that is more tailored for
+# Fortran.
+
+OPTIMIZE_FOR_FORTRAN = NO
+
+# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
+# sources. Doxygen will then generate output that is tailored for
+# VHDL.
+
+OPTIMIZE_OUTPUT_VHDL = NO
+
+# Doxygen selects the parser to use depending on the extension of the files it
+# parses. With this tag you can assign which parser to use for a given extension.
+# Doxygen has a built-in mapping, but you can override or extend it using this
+# tag. The format is ext=language, where ext is a file extension, and language
+# is one of the parsers supported by doxygen: IDL, Java, Javascript, CSharp, C,
+# C++, D, PHP, Objective-C, Python, Fortran, VHDL, C, C++. For instance to make
+# doxygen treat .inc files as Fortran files (default is PHP), and .f files as C
+# (default is Fortran), use: inc=Fortran f=C. Note that for custom extensions
+# you also need to set FILE_PATTERNS otherwise the files are not read by doxygen.
+
+EXTENSION_MAPPING =
+
+# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
+# to include (a tag file for) the STL sources as input, then you should
+# set this tag to YES in order to let doxygen match functions declarations and
+# definitions whose arguments contain STL classes (e.g. func(std::string); v.s.
+# func(std::string) {}). This also makes the inheritance and collaboration
+# diagrams that involve STL classes more complete and accurate.
+
+BUILTIN_STL_SUPPORT = NO
+
+# If you use Microsoft's C++/CLI language, you should set this option to YES to
+# enable parsing support.
+
+CPP_CLI_SUPPORT = NO
+
+# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only.
+# Doxygen will parse them like normal C++ but will assume all classes use public
+# instead of private inheritance when no explicit protection keyword is present.
+
+SIP_SUPPORT = NO
+
+# For Microsoft's IDL there are propget and propput attributes to indicate getter
+# and setter methods for a property. Setting this option to YES (the default)
+# will make doxygen replace the get and set methods by a property in the
+# documentation. This will only work if the methods are indeed getting or
+# setting a simple type. If this is not the case, or you want to show the
+# methods anyway, you should set this option to NO.
+
+IDL_PROPERTY_SUPPORT = YES
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
+# tag is set to YES, then doxygen will reuse the documentation of the first
+# member in the group (if any) for the other members of the group. By default
+# all members of a group must be documented explicitly.
+
+DISTRIBUTE_GROUP_DOC = NO
+
+# Set the SUBGROUPING tag to YES (the default) to allow class member groups of
+# the same type (for instance a group of public functions) to be put as a
+# subgroup of that type (e.g. under the Public Functions section). Set it to
+# NO to prevent subgrouping. Alternatively, this can be done per class using
+# the \nosubgrouping command.
+
+SUBGROUPING = YES
+
+# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and
+# unions are shown inside the group in which they are included (e.g. using
+# @ingroup) instead of on a separate page (for HTML and Man pages) or
+# section (for LaTeX and RTF).
+
+INLINE_GROUPED_CLASSES = NO
+
+# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum
+# is documented as struct, union, or enum with the name of the typedef. So
+# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
+# with name TypeT. When disabled the typedef will appear as a member of a file,
+# namespace, or class. And the struct will be named TypeS. This can typically
+# be useful for C code in case the coding convention dictates that all compound
+# types are typedef'ed and only the typedef is referenced, never the tag name.
+
+TYPEDEF_HIDES_STRUCT = NO
+
+# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to
+# determine which symbols to keep in memory and which to flush to disk.
+# When the cache is full, less often used symbols will be written to disk.
+# For small to medium size projects (<1000 input files) the default value is
+# probably good enough. For larger projects a too small cache size can cause
+# doxygen to be busy swapping symbols to and from disk most of the time
+# causing a significant performance penalty.
+# If the system has enough physical memory increasing the cache will improve the
+# performance by keeping more symbols in memory. Note that the value works on
+# a logarithmic scale so increasing the size by one will roughly double the
+# memory usage. The cache size is given by this formula:
+# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0,
+# corresponding to a cache size of 2^16 = 65536 symbols
+
+SYMBOL_CACHE_SIZE = 0
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in
+# documentation are documented, even if no documentation was available.
+# Private class members and static file members will be hidden unless
+# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES
+
+EXTRACT_ALL = YES
+
+# If the EXTRACT_PRIVATE tag is set to YES all private members of a class
+# will be included in the documentation.
+
+EXTRACT_PRIVATE = NO
+
+# If the EXTRACT_STATIC tag is set to YES all static members of a file
+# will be included in the documentation.
+
+EXTRACT_STATIC = YES
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs)
+# defined locally in source files will be included in the documentation.
+# If set to NO only classes defined in header files are included.
+
+EXTRACT_LOCAL_CLASSES = YES
+
+# This flag is only useful for Objective-C code. When set to YES local
+# methods, which are defined in the implementation section but not in
+# the interface are included in the documentation.
+# If set to NO (the default) only methods in the interface are included.
+
+EXTRACT_LOCAL_METHODS = NO
+
+# If this flag is set to YES, the members of anonymous namespaces will be
+# extracted and appear in the documentation as a namespace called
+# 'anonymous_namespace{file}', where file will be replaced with the base
+# name of the file that contains the anonymous namespace. By default
+# anonymous namespaces are hidden.
+
+EXTRACT_ANON_NSPACES = NO
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all
+# undocumented members of documented classes, files or namespaces.
+# If set to NO (the default) these members will be included in the
+# various overviews, but no documentation section is generated.
+# This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_MEMBERS = NO
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all
+# undocumented classes that are normally visible in the class hierarchy.
+# If set to NO (the default) these classes will be included in the various
+# overviews. This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_CLASSES = NO
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all
+# friend (class|struct|union) declarations.
+# If set to NO (the default) these declarations will be included in the
+# documentation.
+
+HIDE_FRIEND_COMPOUNDS = NO
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any
+# documentation blocks found inside the body of a function.
+# If set to NO (the default) these blocks will be appended to the
+# function's detailed documentation block.
+
+HIDE_IN_BODY_DOCS = NO
+
+# The INTERNAL_DOCS tag determines if documentation
+# that is typed after a \internal command is included. If the tag is set
+# to NO (the default) then the documentation will be excluded.
+# Set it to YES to include the internal documentation.
+
+INTERNAL_DOCS = NO
+
+# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate
+# file names in lower-case letters. If set to YES upper-case letters are also
+# allowed. This is useful if you have classes or files whose names only differ
+# in case and if your file system supports case sensitive file names. Windows
+# and Mac users are advised to set this option to NO.
+
+CASE_SENSE_NAMES = YES
+
+# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen
+# will show members with their full class and namespace scopes in the
+# documentation. If set to YES the scope will be hidden.
+
+HIDE_SCOPE_NAMES = NO
+
+# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen
+# will put a list of the files that are included by a file in the documentation
+# of that file.
+
+SHOW_INCLUDE_FILES = YES
+
+# If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen
+# will list include files with double quotes in the documentation
+# rather than with sharp brackets.
+
+FORCE_LOCAL_INCLUDES = NO
+
+# If the INLINE_INFO tag is set to YES (the default) then a tag [inline]
+# is inserted in the documentation for inline members.
+
+INLINE_INFO = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen
+# will sort the (detailed) documentation of file and class members
+# alphabetically by member name. If set to NO the members will appear in
+# declaration order.
+
+SORT_MEMBER_DOCS = YES
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the
+# brief documentation of file, namespace and class members alphabetically
+# by member name. If set to NO (the default) the members will appear in
+# declaration order.
+
+SORT_BRIEF_DOCS = NO
+
+# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen
+# will sort the (brief and detailed) documentation of class members so that
+# constructors and destructors are listed first. If set to NO (the default)
+# the constructors will appear in the respective orders defined by
+# SORT_MEMBER_DOCS and SORT_BRIEF_DOCS.
+# This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO
+# and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO.
+
+SORT_MEMBERS_CTORS_1ST = NO
+
+# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the
+# hierarchy of group names into alphabetical order. If set to NO (the default)
+# the group names will appear in their defined order.
+
+SORT_GROUP_NAMES = NO
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be
+# sorted by fully-qualified names, including namespaces. If set to
+# NO (the default), the class list will be sorted only by class name,
+# not including the namespace part.
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
+# Note: This option applies only to the class list, not to the
+# alphabetical list.
+
+SORT_BY_SCOPE_NAME = NO
+
+# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to
+# do proper type resolution of all parameters of a function it will reject a
+# match between the prototype and the implementation of a member function even
+# if there is only one candidate or it is obvious which candidate to choose
+# by doing a simple string match. By disabling STRICT_PROTO_MATCHING doxygen
+# will still accept a match between prototype and implementation in such cases.
+
+STRICT_PROTO_MATCHING = NO
+
+# The GENERATE_TODOLIST tag can be used to enable (YES) or
+# disable (NO) the todo list. This list is created by putting \todo
+# commands in the documentation.
+
+GENERATE_TODOLIST = YES
+
+# The GENERATE_TESTLIST tag can be used to enable (YES) or
+# disable (NO) the test list. This list is created by putting \test
+# commands in the documentation.
+
+GENERATE_TESTLIST = YES
+
+# The GENERATE_BUGLIST tag can be used to enable (YES) or
+# disable (NO) the bug list. This list is created by putting \bug
+# commands in the documentation.
+
+GENERATE_BUGLIST = YES
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or
+# disable (NO) the deprecated list. This list is created by putting
+# \deprecated commands in the documentation.
+
+GENERATE_DEPRECATEDLIST= YES
+
+# The ENABLED_SECTIONS tag can be used to enable conditional
+# documentation sections, marked by \if sectionname ... \endif.
+
+ENABLED_SECTIONS =
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines
+# the initial value of a variable or macro consists of for it to appear in
+# the documentation. If the initializer consists of more lines than specified
+# here it will be hidden. Use a value of 0 to hide initializers completely.
+# The appearance of the initializer of individual variables and macros in the
+# documentation can be controlled using \showinitializer or \hideinitializer
+# command in the documentation regardless of this setting.
+
+MAX_INITIALIZER_LINES = 30
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated
+# at the bottom of the documentation of classes and structs. If set to YES the
+# list will mention the files that were used to generate the documentation.
+
+SHOW_USED_FILES = YES
+
+# If the sources in your project are distributed over multiple directories
+# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy
+# in the documentation. The default is NO.
+
+SHOW_DIRECTORIES = NO
+
+# Set the SHOW_FILES tag to NO to disable the generation of the Files page.
+# This will remove the Files entry from the Quick Index and from the
+# Folder Tree View (if specified). The default is YES.
+
+SHOW_FILES = YES
+
+# Set the SHOW_NAMESPACES tag to NO to disable the generation of the
+# Namespaces page.
+# This will remove the Namespaces entry from the Quick Index
+# and from the Folder Tree View (if specified). The default is YES.
+
+SHOW_NAMESPACES = YES
+
+# The FILE_VERSION_FILTER tag can be used to specify a program or script that
+# doxygen should invoke to get the current version for each file (typically from
+# the version control system). Doxygen will invoke the program by executing (via
+# popen()) the command <command> <input-file>, where <command> is the value of
+# the FILE_VERSION_FILTER tag, and <input-file> is the name of an input file
+# provided by doxygen. Whatever the program writes to standard output
+# is used as the file version. See the manual for examples.
+
+FILE_VERSION_FILTER =
+
+# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed
+# by doxygen. The layout file controls the global structure of the generated
+# output files in an output format independent way. The create the layout file
+# that represents doxygen's defaults, run doxygen with the -l option.
+# You can optionally specify a file name after the option, if omitted
+# DoxygenLayout.xml will be used as the name of the layout file.
+
+LAYOUT_FILE =
+
+#---------------------------------------------------------------------------
+# configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated
+# by doxygen. Possible values are YES and NO. If left blank NO is used.
+
+QUIET = NO
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are
+# generated by doxygen. Possible values are YES and NO. If left blank
+# NO is used.
+
+WARNINGS = YES
+
+# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings
+# for undocumented members. If EXTRACT_ALL is set to YES then this flag will
+# automatically be disabled.
+
+WARN_IF_UNDOCUMENTED = YES
+
+# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for
+# potential errors in the documentation, such as not documenting some
+# parameters in a documented function, or documenting parameters that
+# don't exist or using markup commands wrongly.
+
+WARN_IF_DOC_ERROR = YES
+
+# The WARN_NO_PARAMDOC option can be enabled to get warnings for
+# functions that are documented, but have no documentation for their parameters
+# or return value. If set to NO (the default) doxygen will only warn about
+# wrong or incomplete parameter documentation, but not about the absence of
+# documentation.
+
+WARN_NO_PARAMDOC = NO
+
+# The WARN_FORMAT tag determines the format of the warning messages that
+# doxygen can produce. The string should contain the $file, $line, and $text
+# tags, which will be replaced by the file and line number from which the
+# warning originated and the warning text. Optionally the format may contain
+# $version, which will be replaced by the version of the file (if it could
+# be obtained via FILE_VERSION_FILTER)
+
+WARN_FORMAT = "$file:$line: $text"
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning
+# and error messages should be written. If left blank the output is written
+# to stderr.
+
+WARN_LOGFILE =
+
+#---------------------------------------------------------------------------
+# configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag can be used to specify the files and/or directories that contain
+# documented source files. You may enter file names like "myfile.cpp" or
+# directories like "/usr/src/myproject". Separate the files or directories
+# with spaces.
+
+INPUT = @srcdir@/include/osmocom/sim @srcdir@/src/sim
+
+# This tag can be used to specify the character encoding of the source files
+# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is
+# also the default input encoding. Doxygen uses libiconv (or the iconv built
+# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for
+# the list of possible encodings.
+
+INPUT_ENCODING = UTF-8
+
+# If the value of the INPUT tag contains directories, you can use the
+# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
+# and *.h) to filter out the source-files in the directories. If left
+# blank the following patterns are tested:
+# *.c *.cc *.cxx *.cpp *.c++ *.d *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh
+# *.hxx *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.dox *.py
+# *.f90 *.f *.for *.vhd *.vhdl
+
+FILE_PATTERNS =
+
+# The RECURSIVE tag can be used to turn specify whether or not subdirectories
+# should be searched for input files as well. Possible values are YES and NO.
+# If left blank NO is used.
+
+RECURSIVE = YES
+
+# The EXCLUDE tag can be used to specify files and/or directories that should
+# excluded from the INPUT source files. This way you can easily exclude a
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+
+EXCLUDE =
+
+# The EXCLUDE_SYMLINKS tag can be used select whether or not files or
+# directories that are symbolic links (a Unix file system feature) are excluded
+# from the input.
+
+EXCLUDE_SYMLINKS = NO
+
+# If the value of the INPUT tag contains directories, you can use the
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
+# certain files from those directories. Note that the wildcards are matched
+# against the file with absolute path, so to exclude all test directories
+# for example use the pattern */test/*
+
+EXCLUDE_PATTERNS =
+
+# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
+# (namespaces, classes, functions, etc.) that should be excluded from the
+# output. The symbol name can be a fully qualified name, a word, or if the
+# wildcard * is used, a substring. Examples: ANamespace, AClass,
+# AClass::ANamespace, ANamespace::*Test
+
+EXCLUDE_SYMBOLS =
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or
+# directories that contain example code fragments that are included (see
+# the \include command).
+
+EXAMPLE_PATH =
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
+# and *.h) to filter out the source-files in the directories. If left
+# blank all files are included.
+
+EXAMPLE_PATTERNS =
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
+# searched for input files to be used with the \include or \dontinclude
+# commands irrespective of the value of the RECURSIVE tag.
+# Possible values are YES and NO. If left blank NO is used.
+
+EXAMPLE_RECURSIVE = NO
+
+# The IMAGE_PATH tag can be used to specify one or more files or
+# directories that contain image that are included in the documentation (see
+# the \image command).
+
+# IMAGE_PATH = images/
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should
+# invoke to filter for each input file. Doxygen will invoke the filter program
+# by executing (via popen()) the command <filter> <input-file>, where <filter>
+# is the value of the INPUT_FILTER tag, and <input-file> is the name of an
+# input file. Doxygen will then use the output that the filter program writes
+# to standard output.
+# If FILTER_PATTERNS is specified, this tag will be
+# ignored.
+
+INPUT_FILTER =
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
+# basis.
+# Doxygen will compare the file name with each pattern and apply the
+# filter if there is a match.
+# The filters are a list of the form:
+# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further
+# info on how filters are used. If FILTER_PATTERNS is empty or if
+# non of the patterns match the file name, INPUT_FILTER is applied.
+
+FILTER_PATTERNS =
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
+# INPUT_FILTER) will be used to filter the input files when producing source
+# files to browse (i.e. when SOURCE_BROWSER is set to YES).
+
+FILTER_SOURCE_FILES = NO
+
+# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file
+# pattern. A pattern will override the setting for FILTER_PATTERN (if any)
+# and it is also possible to disable source filtering for a specific pattern
+# using *.ext= (so without naming a filter). This option only has effect when
+# FILTER_SOURCE_FILES is enabled.
+
+FILTER_SOURCE_PATTERNS =
+
+#---------------------------------------------------------------------------
+# configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will
+# be generated. Documented entities will be cross-referenced with these sources.
+# Note: To get rid of all source code in the generated output, make sure also
+# VERBATIM_HEADERS is set to NO.
+
+SOURCE_BROWSER = NO
+
+# Setting the INLINE_SOURCES tag to YES will include the body
+# of functions and classes directly in the documentation.
+
+INLINE_SOURCES = NO
+
+# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct
+# doxygen to hide any special comment blocks from generated source code
+# fragments. Normal C and C++ comments will always remain visible.
+
+STRIP_CODE_COMMENTS = YES
+
+# If the REFERENCED_BY_RELATION tag is set to YES
+# then for each documented function all documented
+# functions referencing it will be listed.
+
+REFERENCED_BY_RELATION = YES
+
+# If the REFERENCES_RELATION tag is set to YES
+# then for each documented function all documented entities
+# called/used by that function will be listed.
+
+REFERENCES_RELATION = YES
+
+# If the REFERENCES_LINK_SOURCE tag is set to YES (the default)
+# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from
+# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will
+# link to the source code.
+# Otherwise they will link to the documentation.
+
+REFERENCES_LINK_SOURCE = YES
+
+# If the USE_HTAGS tag is set to YES then the references to source code
+# will point to the HTML generated by the htags(1) tool instead of doxygen
+# built-in source browser. The htags tool is part of GNU's global source
+# tagging system (see http://www.gnu.org/software/global/global.html). You
+# will need version 4.8.6 or higher.
+
+USE_HTAGS = NO
+
+# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen
+# will generate a verbatim copy of the header file for each class for
+# which an include is specified. Set to NO to disable this.
+
+VERBATIM_HEADERS = YES
+
+#---------------------------------------------------------------------------
+# configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index
+# of all compounds will be generated. Enable this if the project
+# contains a lot of classes, structs, unions or interfaces.
+
+ALPHABETICAL_INDEX = NO
+
+# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then
+# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns
+# in which this list will be split (can be a number in the range [1..20])
+
+COLS_IN_ALPHA_INDEX = 5
+
+# In case all classes in a project start with a common prefix, all
+# classes will be put under the same header in the alphabetical index.
+# The IGNORE_PREFIX tag can be used to specify one or more prefixes that
+# should be ignored while generating the index headers.
+
+IGNORE_PREFIX =
+
+#---------------------------------------------------------------------------
+# configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES (the default) Doxygen will
+# generate HTML output.
+
+GENERATE_HTML = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `html' will be used as the default path.
+
+HTML_OUTPUT = html
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for
+# each generated HTML page (for example: .htm,.php,.asp). If it is left blank
+# doxygen will generate files with .html extension.
+
+HTML_FILE_EXTENSION = .html
+
+# The HTML_HEADER tag can be used to specify a personal HTML header for
+# each generated HTML page. If it is left blank doxygen will generate a
+# standard header. Note that when using a custom header you are responsible
+# for the proper inclusion of any scripts and style sheets that doxygen
+# needs, which is dependent on the configuration options used.
+# It is advised to generate a default header using "doxygen -w html
+# header.html footer.html stylesheet.css YourConfigFile" and then modify
+# that header. Note that the header is subject to change so you typically
+# have to redo this when upgrading to a newer version of doxygen or when changing the value of configuration settings such as GENERATE_TREEVIEW!
+
+HTML_HEADER =
+
+# The HTML_FOOTER tag can be used to specify a personal HTML footer for
+# each generated HTML page. If it is left blank doxygen will generate a
+# standard footer.
+
+HTML_FOOTER =
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading
+# style sheet that is used by each HTML page. It can be used to
+# fine-tune the look of the HTML output. If the tag is left blank doxygen
+# will generate a default style sheet. Note that doxygen will try to copy
+# the style sheet file to the HTML output directory, so don't put your own
+# stylesheet in the HTML output directory as well, or it will be erased!
+
+HTML_STYLESHEET =
+
+# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the HTML output directory. Note
+# that these files will be copied to the base HTML output directory. Use the
+# $relpath$ marker in the HTML_HEADER and/or HTML_FOOTER files to load these
+# files. In the HTML_STYLESHEET file, use the file name only. Also note that
+# the files will be copied as-is; there are no commands or markers available.
+
+HTML_EXTRA_FILES =
+
+# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output.
+# Doxygen will adjust the colors in the stylesheet and background images
+# according to this color. Hue is specified as an angle on a colorwheel,
+# see http://en.wikipedia.org/wiki/Hue for more information.
+# For instance the value 0 represents red, 60 is yellow, 120 is green,
+# 180 is cyan, 240 is blue, 300 purple, and 360 is red again.
+# The allowed range is 0 to 359.
+
+HTML_COLORSTYLE_HUE = 220
+
+# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of
+# the colors in the HTML output. For a value of 0 the output will use
+# grayscales only. A value of 255 will produce the most vivid colors.
+
+HTML_COLORSTYLE_SAT = 100
+
+# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to
+# the luminance component of the colors in the HTML output. Values below
+# 100 gradually make the output lighter, whereas values above 100 make
+# the output darker. The value divided by 100 is the actual gamma applied,
+# so 80 represents a gamma of 0.8, The value 220 represents a gamma of 2.2,
+# and 100 does not change the gamma.
+
+HTML_COLORSTYLE_GAMMA = 80
+
+# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML
+# page will contain the date and time when the page was generated. Setting
+# this to NO can help when comparing the output of multiple runs.
+
+HTML_TIMESTAMP = NO
+
+# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes,
+# files or namespaces will be aligned in HTML using tables. If set to
+# NO a bullet list will be used.
+
+HTML_ALIGN_MEMBERS = YES
+
+# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
+# documentation will contain sections that can be hidden and shown after the
+# page has loaded. For this to work a browser that supports
+# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox
+# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari).
+
+HTML_DYNAMIC_SECTIONS = NO
+
+# If the GENERATE_DOCSET tag is set to YES, additional index files
+# will be generated that can be used as input for Apple's Xcode 3
+# integrated development environment, introduced with OSX 10.5 (Leopard).
+# To create a documentation set, doxygen will generate a Makefile in the
+# HTML output directory. Running make will produce the docset in that
+# directory and running "make install" will install the docset in
+# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find
+# it at startup.
+# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html
+# for more information.
+
+GENERATE_DOCSET = NO
+
+# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the
+# feed. A documentation feed provides an umbrella under which multiple
+# documentation sets from a single provider (such as a company or product suite)
+# can be grouped.
+
+DOCSET_FEEDNAME = "Doxygen generated docs"
+
+# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that
+# should uniquely identify the documentation set bundle. This should be a
+# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen
+# will append .docset to the name.
+
+DOCSET_BUNDLE_ID = org.doxygen.Project
+
+# When GENERATE_PUBLISHER_ID tag specifies a string that should uniquely identify
+# the documentation publisher. This should be a reverse domain-name style
+# string, e.g. com.mycompany.MyDocSet.documentation.
+
+DOCSET_PUBLISHER_ID = org.doxygen.Publisher
+
+# The GENERATE_PUBLISHER_NAME tag identifies the documentation publisher.
+
+DOCSET_PUBLISHER_NAME = Publisher
+
+# If the GENERATE_HTMLHELP tag is set to YES, additional index files
+# will be generated that can be used as input for tools like the
+# Microsoft HTML help workshop to generate a compiled HTML help file (.chm)
+# of the generated HTML documentation.
+
+GENERATE_HTMLHELP = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can
+# be used to specify the file name of the resulting .chm file. You
+# can add a path in front of the file if the result should not be
+# written to the html output directory.
+
+CHM_FILE =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can
+# be used to specify the location (absolute path including file name) of
+# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run
+# the HTML help compiler on the generated index.hhp.
+
+HHC_LOCATION =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag
+# controls if a separate .chi index file is generated (YES) or that
+# it should be included in the master .chm file (NO).
+
+GENERATE_CHI = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING
+# is used to encode HtmlHelp index (hhk), content (hhc) and project file
+# content.
+
+CHM_INDEX_ENCODING =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag
+# controls whether a binary table of contents is generated (YES) or a
+# normal table of contents (NO) in the .chm file.
+
+BINARY_TOC = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members
+# to the contents of the HTML help documentation and to the tree view.
+
+TOC_EXPAND = NO
+
+# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and
+# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated
+# that can be used as input for Qt's qhelpgenerator to generate a
+# Qt Compressed Help (.qch) of the generated HTML documentation.
+
+GENERATE_QHP = NO
+
+# If the QHG_LOCATION tag is specified, the QCH_FILE tag can
+# be used to specify the file name of the resulting .qch file.
+# The path specified is relative to the HTML output folder.
+
+QCH_FILE =
+
+# The QHP_NAMESPACE tag specifies the namespace to use when generating
+# Qt Help Project output. For more information please see
+# http://doc.trolltech.com/qthelpproject.html#namespace
+
+QHP_NAMESPACE = org.doxygen.Project
+
+# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating
+# Qt Help Project output. For more information please see
+# http://doc.trolltech.com/qthelpproject.html#virtual-folders
+
+QHP_VIRTUAL_FOLDER = doc
+
+# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to
+# add. For more information please see
+# http://doc.trolltech.com/qthelpproject.html#custom-filters
+
+QHP_CUST_FILTER_NAME =
+
+# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the
+# custom filter to add. For more information please see
+# <a href="http://doc.trolltech.com/qthelpproject.html#custom-filters">
+# Qt Help Project / Custom Filters</a>.
+
+QHP_CUST_FILTER_ATTRS =
+
+# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this
+# project's
+# filter section matches.
+# <a href="http://doc.trolltech.com/qthelpproject.html#filter-attributes">
+# Qt Help Project / Filter Attributes</a>.
+
+QHP_SECT_FILTER_ATTRS =
+
+# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can
+# be used to specify the location of Qt's qhelpgenerator.
+# If non-empty doxygen will try to run qhelpgenerator on the generated
+# .qhp file.
+
+QHG_LOCATION =
+
+# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files
+# will be generated, which together with the HTML files, form an Eclipse help
+# plugin. To install this plugin and make it available under the help contents
+# menu in Eclipse, the contents of the directory containing the HTML and XML
+# files needs to be copied into the plugins directory of eclipse. The name of
+# the directory within the plugins directory should be the same as
+# the ECLIPSE_DOC_ID value. After copying Eclipse needs to be restarted before
+# the help appears.
+
+GENERATE_ECLIPSEHELP = NO
+
+# A unique identifier for the eclipse help plugin. When installing the plugin
+# the directory name containing the HTML and XML files should also have
+# this name.
+
+ECLIPSE_DOC_ID = org.doxygen.Project
+
+# The DISABLE_INDEX tag can be used to turn on/off the condensed index at
+# top of each HTML page. The value NO (the default) enables the index and
+# the value YES disables it.
+
+DISABLE_INDEX = NO
+
+# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values
+# (range [0,1..20]) that doxygen will group on one line in the generated HTML
+# documentation. Note that a value of 0 will completely suppress the enum
+# values from appearing in the overview section.
+
+ENUM_VALUES_PER_LINE = 1
+
+# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
+# structure should be generated to display hierarchical information.
+# If the tag value is set to YES, a side panel will be generated
+# containing a tree-like index structure (just like the one that
+# is generated for HTML Help). For this to work a browser that supports
+# JavaScript, DHTML, CSS and frames is required (i.e. any modern browser).
+# Windows users are probably better off using the HTML help feature.
+
+GENERATE_TREEVIEW = YES
+
+# By enabling USE_INLINE_TREES, doxygen will generate the Groups, Directories,
+# and Class Hierarchy pages using a tree view instead of an ordered list.
+
+USE_INLINE_TREES = NO
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be
+# used to set the initial width (in pixels) of the frame in which the tree
+# is shown.
+
+TREEVIEW_WIDTH = 250
+
+# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open
+# links to external symbols imported via tag files in a separate window.
+
+EXT_LINKS_IN_WINDOW = NO
+
+# Use this tag to change the font size of Latex formulas included
+# as images in the HTML documentation. The default is 10. Note that
+# when you change the font size after a successful doxygen run you need
+# to manually remove any form_*.png images from the HTML output directory
+# to force them to be regenerated.
+
+FORMULA_FONTSIZE = 10
+
+# Use the FORMULA_TRANPARENT tag to determine whether or not the images
+# generated for formulas are transparent PNGs. Transparent PNGs are
+# not supported properly for IE 6.0, but are supported on all modern browsers.
+# Note that when changing this option you need to delete any form_*.png files
+# in the HTML output before the changes have effect.
+
+FORMULA_TRANSPARENT = YES
+
+# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax
+# (see http://www.mathjax.org) which uses client side Javascript for the
+# rendering instead of using prerendered bitmaps. Use this if you do not
+# have LaTeX installed or if you want to formulas look prettier in the HTML
+# output. When enabled you also need to install MathJax separately and
+# configure the path to it using the MATHJAX_RELPATH option.
+
+USE_MATHJAX = NO
+
+# When MathJax is enabled you need to specify the location relative to the
+# HTML output directory using the MATHJAX_RELPATH option. The destination
+# directory should contain the MathJax.js script. For instance, if the mathjax
+# directory is located at the same level as the HTML output directory, then
+# MATHJAX_RELPATH should be ../mathjax. The default value points to the
+# mathjax.org site, so you can quickly see the result without installing
+# MathJax, but it is strongly recommended to install a local copy of MathJax
+# before deployment.
+
+MATHJAX_RELPATH = http://www.mathjax.org/mathjax
+
+# When the SEARCHENGINE tag is enabled doxygen will generate a search box
+# for the HTML output. The underlying search engine uses javascript
+# and DHTML and should work on any modern browser. Note that when using
+# HTML help (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets
+# (GENERATE_DOCSET) there is already a search function so this one should
+# typically be disabled. For large projects the javascript based search engine
+# can be slow, then enabling SERVER_BASED_SEARCH may provide a better solution.
+
+SEARCHENGINE = YES
+
+# When the SERVER_BASED_SEARCH tag is enabled the search engine will be
+# implemented using a PHP enabled web server instead of at the web client
+# using Javascript. Doxygen will generate the search PHP script and index
+# file to put on the web server. The advantage of the server
+# based approach is that it scales better to large projects and allows
+# full text search. The disadvantages are that it is more difficult to setup
+# and does not have live searching capabilities.
+
+SERVER_BASED_SEARCH = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will
+# generate Latex output.
+
+GENERATE_LATEX = YES
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `latex' will be used as the default path.
+
+LATEX_OUTPUT = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
+# invoked. If left blank `latex' will be used as the default command name.
+# Note that when enabling USE_PDFLATEX this option is only used for
+# generating bitmaps for formulas in the HTML output, but not in the
+# Makefile that is written to the output directory.
+
+LATEX_CMD_NAME = latex
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to
+# generate index for LaTeX. If left blank `makeindex' will be used as the
+# default command name.
+
+MAKEINDEX_CMD_NAME = makeindex
+
+# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact
+# LaTeX documents. This may be useful for small projects and may help to
+# save some trees in general.
+
+COMPACT_LATEX = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used
+# by the printer. Possible values are: a4, letter, legal and
+# executive. If left blank a4wide will be used.
+
+PAPER_TYPE = a4wide
+
+# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX
+# packages that should be included in the LaTeX output.
+
+EXTRA_PACKAGES =
+
+# The LATEX_HEADER tag can be used to specify a personal LaTeX header for
+# the generated latex document. The header should contain everything until
+# the first chapter. If it is left blank doxygen will generate a
+# standard header. Notice: only use this tag if you know what you are doing!
+
+LATEX_HEADER =
+
+# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for
+# the generated latex document. The footer should contain everything after
+# the last chapter. If it is left blank doxygen will generate a
+# standard footer. Notice: only use this tag if you know what you are doing!
+
+LATEX_FOOTER =
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated
+# is prepared for conversion to pdf (using ps2pdf). The pdf file will
+# contain links (just like the HTML output) instead of page references
+# This makes the output suitable for online browsing using a pdf viewer.
+
+PDF_HYPERLINKS = NO
+
+# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of
+# plain latex in the generated Makefile. Set this option to YES to get a
+# higher quality PDF documentation.
+
+USE_PDFLATEX = NO
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode.
+# command to the generated LaTeX files. This will instruct LaTeX to keep
+# running if errors occur, instead of asking the user for help.
+# This option is also used when generating formulas in HTML.
+
+LATEX_BATCHMODE = NO
+
+# If LATEX_HIDE_INDICES is set to YES then doxygen will not
+# include the index chapters (such as File Index, Compound Index, etc.)
+# in the output.
+
+LATEX_HIDE_INDICES = NO
+
+# If LATEX_SOURCE_CODE is set to YES then doxygen will include
+# source code with syntax highlighting in the LaTeX output.
+# Note that which sources are shown also depends on other settings
+# such as SOURCE_BROWSER.
+
+LATEX_SOURCE_CODE = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output
+# The RTF output is optimized for Word 97 and may not look very pretty with
+# other RTF readers or editors.
+
+GENERATE_RTF = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `rtf' will be used as the default path.
+
+RTF_OUTPUT = rtf
+
+# If the COMPACT_RTF tag is set to YES Doxygen generates more compact
+# RTF documents. This may be useful for small projects and may help to
+# save some trees in general.
+
+COMPACT_RTF = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated
+# will contain hyperlink fields. The RTF file will
+# contain links (just like the HTML output) instead of page references.
+# This makes the output suitable for online browsing using WORD or other
+# programs which support those fields.
+# Note: wordpad (write) and others do not support links.
+
+RTF_HYPERLINKS = NO
+
+# Load stylesheet definitions from file. Syntax is similar to doxygen's
+# config file, i.e. a series of assignments. You only have to provide
+# replacements, missing definitions are set to their default value.
+
+RTF_STYLESHEET_FILE =
+
+# Set optional variables used in the generation of an rtf document.
+# Syntax is similar to doxygen's config file.
+
+RTF_EXTENSIONS_FILE =
+
+#---------------------------------------------------------------------------
+# configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES (the default) Doxygen will
+# generate man pages
+
+GENERATE_MAN = NO
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `man' will be used as the default path.
+
+MAN_OUTPUT = man
+
+# The MAN_EXTENSION tag determines the extension that is added to
+# the generated man pages (default is the subroutine's section .3)
+
+MAN_EXTENSION = .3
+
+# If the MAN_LINKS tag is set to YES and Doxygen generates man output,
+# then it will generate one additional man file for each entity
+# documented in the real man page(s). These additional files
+# only source the real man page, but without them the man command
+# would be unable to find the correct page. The default is NO.
+
+MAN_LINKS = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES Doxygen will
+# generate an XML file that captures the structure of
+# the code including all documentation.
+
+GENERATE_XML = NO
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `xml' will be used as the default path.
+
+XML_OUTPUT = xml
+
+# The XML_SCHEMA tag can be used to specify an XML schema,
+# which can be used by a validating XML parser to check the
+# syntax of the XML files.
+
+XML_SCHEMA =
+
+# The XML_DTD tag can be used to specify an XML DTD,
+# which can be used by a validating XML parser to check the
+# syntax of the XML files.
+
+XML_DTD =
+
+# If the XML_PROGRAMLISTING tag is set to YES Doxygen will
+# dump the program listings (including syntax highlighting
+# and cross-referencing information) to the XML output. Note that
+# enabling this will significantly increase the size of the XML output.
+
+XML_PROGRAMLISTING = YES
+
+#---------------------------------------------------------------------------
+# configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will
+# generate an AutoGen Definitions (see autogen.sf.net) file
+# that captures the structure of the code including all
+# documentation. Note that this feature is still experimental
+# and incomplete at the moment.
+
+GENERATE_AUTOGEN_DEF = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES Doxygen will
+# generate a Perl module file that captures the structure of
+# the code including all documentation. Note that this
+# feature is still experimental and incomplete at the
+# moment.
+
+GENERATE_PERLMOD = NO
+
+# If the PERLMOD_LATEX tag is set to YES Doxygen will generate
+# the necessary Makefile rules, Perl scripts and LaTeX code to be able
+# to generate PDF and DVI output from the Perl module output.
+
+PERLMOD_LATEX = NO
+
+# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be
+# nicely formatted so it can be parsed by a human reader.
+# This is useful
+# if you want to understand what is going on.
+# On the other hand, if this
+# tag is set to NO the size of the Perl module output will be much smaller
+# and Perl will parse it just the same.
+
+PERLMOD_PRETTY = YES
+
+# The names of the make variables in the generated doxyrules.make file
+# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX.
+# This is useful so different doxyrules.make files included by the same
+# Makefile don't overwrite each other's variables.
+
+PERLMOD_MAKEVAR_PREFIX =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will
+# evaluate all C-preprocessor directives found in the sources and include
+# files.
+
+ENABLE_PREPROCESSING = YES
+
+# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro
+# names in the source code. If set to NO (the default) only conditional
+# compilation will be performed. Macro expansion can be done in a controlled
+# way by setting EXPAND_ONLY_PREDEF to YES.
+
+MACRO_EXPANSION = NO
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES
+# then the macro expansion is limited to the macros specified with the
+# PREDEFINED and EXPAND_AS_DEFINED tags.
+
+EXPAND_ONLY_PREDEF = NO
+
+# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files
+# pointed to by INCLUDE_PATH will be searched when a #include is found.
+
+SEARCH_INCLUDES = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that
+# contain include files that are not input files but should be processed by
+# the preprocessor.
+
+INCLUDE_PATH =
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
+# patterns (like *.h and *.hpp) to filter out the header-files in the
+# directories. If left blank, the patterns specified with FILE_PATTERNS will
+# be used.
+
+INCLUDE_FILE_PATTERNS =
+
+# The PREDEFINED tag can be used to specify one or more macro names that
+# are defined before the preprocessor is started (similar to the -D option of
+# gcc). The argument of the tag is a list of macros of the form: name
+# or name=definition (no spaces). If the definition and the = are
+# omitted =1 is assumed. To prevent a macro definition from being
+# undefined via #undef or recursively expanded use the := operator
+# instead of the = operator.
+
+PREDEFINED =
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then
+# this tag can be used to specify a list of macro names that should be expanded.
+# The macro definition that is found in the sources will be used.
+# Use the PREDEFINED tag if you want to use a different macro definition that
+# overrules the definition found in the source code.
+
+EXPAND_AS_DEFINED =
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then
+# doxygen's preprocessor will remove all references to function-like macros
+# that are alone on a line, have an all uppercase name, and do not end with a
+# semicolon, because these will confuse the parser if not removed.
+
+SKIP_FUNCTION_MACROS = YES
+
+#---------------------------------------------------------------------------
+# Configuration::additions related to external references
+#---------------------------------------------------------------------------
+
+# The TAGFILES option can be used to specify one or more tagfiles.
+# Optionally an initial location of the external documentation
+# can be added for each tagfile. The format of a tag file without
+# this location is as follows:
+#
+# TAGFILES = file1 file2 ...
+# Adding location for the tag files is done as follows:
+#
+# TAGFILES = file1=loc1 "file2 = loc2" ...
+# where "loc1" and "loc2" can be relative or absolute paths or
+# URLs. If a location is present for each tag, the installdox tool
+# does not have to be run to correct the links.
+# Note that each tag file must have a unique name
+# (where the name does NOT include the path)
+# If a tag file is not located in the directory in which doxygen
+# is run, you must also specify the path to the tagfile here.
+
+TAGFILES = doc/libosmocore.tag=../../core/html doc/libosmovty.tag=../../vty/html doc/libosmocodec.tag=../../codec/html doc/libosmocoding.tag=../../coding/html doc/libosmoctrl.tag=../../ctrl/html doc/libosmogb.tag=../../gb/html
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create
+# a tag file that is based on the input files it reads.
+
+GENERATE_TAGFILE = doc/libosmosim.tag
+
+# If the ALLEXTERNALS tag is set to YES all external classes will be listed
+# in the class index. If set to NO only the inherited external classes
+# will be listed.
+
+ALLEXTERNALS = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed
+# in the modules index. If set to NO, only the current project's groups will
+# be listed.
+
+EXTERNAL_GROUPS = YES
+
+# The PERL_PATH should be the absolute path and name of the perl script
+# interpreter (i.e. the result of `which perl').
+
+PERL_PATH = /usr/bin/perl
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool
+#---------------------------------------------------------------------------
+
+# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will
+# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base
+# or super classes. Setting the tag to NO turns the diagrams off. Note that
+# this option also works with HAVE_DOT disabled, but it is recommended to
+# install and use dot, since it yields more powerful graphs.
+
+CLASS_DIAGRAMS = YES
+
+# You can define message sequence charts within doxygen comments using the \msc
+# command. Doxygen will then run the mscgen tool (see
+# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the
+# documentation. The MSCGEN_PATH tag allows you to specify the directory where
+# the mscgen tool resides. If left empty the tool is assumed to be found in the
+# default search path.
+
+MSCGEN_PATH =
+
+# If set to YES, the inheritance and collaboration graphs will hide
+# inheritance and usage relations if the target is undocumented
+# or is not a class.
+
+HIDE_UNDOC_RELATIONS = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
+# available from the path. This tool is part of Graphviz, a graph visualization
+# toolkit from AT&T and Lucent Bell Labs. The other options in this section
+# have no effect if this option is set to NO (the default)
+
+HAVE_DOT = NO
+
+# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is
+# allowed to run in parallel. When set to 0 (the default) doxygen will
+# base this on the number of processors available in the system. You can set it
+# explicitly to a value larger than 0 to get control over the balance
+# between CPU load and processing speed.
+
+DOT_NUM_THREADS = 0
+
+# By default doxygen will write a font called Helvetica to the output
+# directory and reference it in all dot files that doxygen generates.
+# When you want a differently looking font you can specify the font name
+# using DOT_FONTNAME. You need to make sure dot is able to find the font,
+# which can be done by putting it in a standard location or by setting the
+# DOTFONTPATH environment variable or by setting DOT_FONTPATH to the directory
+# containing the font.
+
+DOT_FONTNAME = Helvetica
+
+# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs.
+# The default size is 10pt.
+
+DOT_FONTSIZE = 10
+
+# By default doxygen will tell dot to use the output directory to look for the
+# FreeSans.ttf font (which doxygen will put there itself). If you specify a
+# different font using DOT_FONTNAME you can set the path where dot
+# can find it using this tag.
+
+DOT_FONTPATH =
+
+# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for each documented class showing the direct and
+# indirect inheritance relations. Setting this tag to YES will force the
+# the CLASS_DIAGRAMS tag to NO.
+
+CLASS_GRAPH = YES
+
+# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for each documented class showing the direct and
+# indirect implementation dependencies (inheritance, containment, and
+# class references variables) of the class with other documented classes.
+
+COLLABORATION_GRAPH = YES
+
+# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for groups, showing the direct groups dependencies
+
+GROUP_GRAPHS = YES
+
+# If the UML_LOOK tag is set to YES doxygen will generate inheritance and
+# collaboration diagrams in a style similar to the OMG's Unified Modeling
+# Language.
+
+UML_LOOK = NO
+
+# If set to YES, the inheritance and collaboration graphs will show the
+# relations between templates and their instances.
+
+TEMPLATE_RELATIONS = NO
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT
+# tags are set to YES then doxygen will generate a graph for each documented
+# file showing the direct and indirect include dependencies of the file with
+# other documented files.
+
+INCLUDE_GRAPH = YES
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and
+# HAVE_DOT tags are set to YES then doxygen will generate a graph for each
+# documented header file showing the documented files that directly or
+# indirectly include this file.
+
+INCLUDED_BY_GRAPH = YES
+
+# If the CALL_GRAPH and HAVE_DOT options are set to YES then
+# doxygen will generate a call dependency graph for every global function
+# or class method. Note that enabling this option will significantly increase
+# the time of a run. So in most cases it will be better to enable call graphs
+# for selected functions only using the \callgraph command.
+
+CALL_GRAPH = NO
+
+# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then
+# doxygen will generate a caller dependency graph for every global function
+# or class method. Note that enabling this option will significantly increase
+# the time of a run. So in most cases it will be better to enable caller
+# graphs for selected functions only using the \callergraph command.
+
+CALLER_GRAPH = NO
+
+# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen
+# will generate a graphical hierarchy of all classes instead of a textual one.
+
+GRAPHICAL_HIERARCHY = YES
+
+# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES
+# then doxygen will show the dependencies a directory has on other directories
+# in a graphical way. The dependency relations are determined by the #include
+# relations between the files in the directories.
+
+DIRECTORY_GRAPH = YES
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
+# generated by dot. Possible values are svg, png, jpg, or gif.
+# If left blank png will be used.
+
+DOT_IMAGE_FORMAT = png
+
+# The tag DOT_PATH can be used to specify the path where the dot tool can be
+# found. If left blank, it is assumed the dot tool can be found in the path.
+
+DOT_PATH = /usr/bin/dot
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that
+# contain dot files that are included in the documentation (see the
+# \dotfile command).
+
+DOTFILE_DIRS =
+
+# The MSCFILE_DIRS tag can be used to specify one or more directories that
+# contain msc files that are included in the documentation (see the
+# \mscfile command).
+
+MSCFILE_DIRS =
+
+# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of
+# nodes that will be shown in the graph. If the number of nodes in a graph
+# becomes larger than this value, doxygen will truncate the graph, which is
+# visualized by representing a node as a red box. Note that doxygen if the
+# number of direct children of the root node in a graph is already larger than
+# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note
+# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
+
+DOT_GRAPH_MAX_NODES = 50
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the
+# graphs generated by dot. A depth value of 3 means that only nodes reachable
+# from the root by following a path via at most 3 edges will be shown. Nodes
+# that lay further from the root node will be omitted. Note that setting this
+# option to 1 or 2 may greatly reduce the computation time needed for large
+# code bases. Also note that the size of a graph can be further restricted by
+# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
+
+MAX_DOT_GRAPH_DEPTH = 0
+
+# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
+# background. This is disabled by default, because dot on Windows does not
+# seem to support this out of the box. Warning: Depending on the platform used,
+# enabling this option may lead to badly anti-aliased labels on the edges of
+# a graph (i.e. they become hard to read).
+
+DOT_TRANSPARENT = NO
+
+# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output
+# files in one run (i.e. multiple -o and -T options on the command line). This
+# makes dot run faster, but since only newer versions of dot (>1.8.10)
+# support this, this feature is disabled by default.
+
+DOT_MULTI_TARGETS = NO
+
+# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will
+# generate a legend page explaining the meaning of the various boxes and
+# arrows in the dot generated graphs.
+
+GENERATE_LEGEND = YES
+
+# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will
+# remove the intermediate dot files that are used to generate
+# the various graphs.
+
+DOT_CLEANUP = YES
diff --git a/Doxyfile.usb.in b/Doxyfile.usb.in
new file mode 100644
index 00000000..7037713d
--- /dev/null
+++ b/Doxyfile.usb.in
@@ -0,0 +1,1716 @@
+# Doxyfile 1.7.4
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project.
+#
+# All text after a hash (#) is considered a comment and will be ignored.
+# The format is:
+# TAG = value [value, ...]
+# For lists items can also be appended using:
+# TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (" ").
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# This tag specifies the encoding used for all characters in the config file
+# that follow. The default is UTF-8 which is also the encoding used for all
+# text before the first occurrence of this tag. Doxygen uses libiconv (or the
+# iconv built into libc) for the transcoding. See
+# http://www.gnu.org/software/libiconv for the list of possible encodings.
+
+DOXYFILE_ENCODING = UTF-8
+
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded
+# by quotes) that should identify the project.
+
+PROJECT_NAME = libosmousb
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number.
+# This could be handy for archiving the generated documentation or
+# if some version control system is used.
+
+PROJECT_NUMBER = @VERSION@
+
+# Using the PROJECT_BRIEF tag one can provide an optional one line description
+# for a project that appears at the top of each page and should give viewer
+# a quick idea about the purpose of the project. Keep the description short.
+
+PROJECT_BRIEF = "Osmocom USB library"
+
+# With the PROJECT_LOGO tag one can specify an logo or icon that is
+# included in the documentation. The maximum height of the logo should not
+# exceed 55 pixels and the maximum width should not exceed 200 pixels.
+# Doxygen will copy the logo to the output directory.
+
+PROJECT_LOGO =
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute)
+# base path where the generated documentation will be put.
+# If a relative path is entered, it will be relative to the location
+# where doxygen was started. If left blank the current directory will be used.
+
+OUTPUT_DIRECTORY = doc/usb
+
+# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create
+# 4096 sub-directories (in 2 levels) under the output directory of each output
+# format and will distribute the generated files over these directories.
+# Enabling this option can be useful when feeding doxygen a huge amount of
+# source files, where putting all generated files in the same directory would
+# otherwise cause performance problems for the file system.
+
+CREATE_SUBDIRS = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all
+# documentation generated by doxygen is written. Doxygen will use this
+# information to generate all constant output in the proper language.
+# The default language is English, other supported languages are:
+# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional,
+# Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German,
+# Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English
+# messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian,
+# Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak,
+# Slovene, Spanish, Swedish, Ukrainian, and Vietnamese.
+
+OUTPUT_LANGUAGE = English
+
+# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will
+# include brief member descriptions after the members that are listed in
+# the file and class documentation (similar to JavaDoc).
+# Set to NO to disable this.
+
+BRIEF_MEMBER_DESC = YES
+
+# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend
+# the brief description of a member or function before the detailed description.
+# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the
+# brief descriptions will be completely suppressed.
+
+REPEAT_BRIEF = YES
+
+# This tag implements a quasi-intelligent brief description abbreviator
+# that is used to form the text in various listings. Each string
+# in this list, if found as the leading text of the brief description, will be
+# stripped from the text and the result after processing the whole list, is
+# used as the annotated text. Otherwise, the brief description is used as-is.
+# If left blank, the following values are used ("$name" is automatically
+# replaced with the name of the entity): "The $name class" "The $name widget"
+# "The $name file" "is" "provides" "specifies" "contains"
+# "represents" "a" "an" "the"
+
+ABBREVIATE_BRIEF =
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then
+# Doxygen will generate a detailed section even if there is only a brief
+# description.
+
+ALWAYS_DETAILED_SEC = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all
+# inherited members of a class in the documentation of that class as if those
+# members were ordinary class members. Constructors, destructors and assignment
+# operators of the base classes will not be shown.
+
+INLINE_INHERITED_MEMB = NO
+
+# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full
+# path before files name in the file list and in the header files. If set
+# to NO the shortest path that makes the file name unique will be used.
+
+FULL_PATH_NAMES = YES
+
+# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag
+# can be used to strip a user-defined part of the path. Stripping is
+# only done if one of the specified strings matches the left-hand part of
+# the path. The tag can be used to show relative paths in the file list.
+# If left blank the directory from which doxygen is run is used as the
+# path to strip.
+
+STRIP_FROM_PATH =
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of
+# the path mentioned in the documentation of a class, which tells
+# the reader which header file to include in order to use a class.
+# If left blank only the name of the header file containing the class
+# definition is used. Otherwise one should specify the include paths that
+# are normally passed to the compiler using the -I flag.
+
+STRIP_FROM_INC_PATH =
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter
+# (but less readable) file names. This can be useful if your file system
+# doesn't support long names like on DOS, Mac, or CD-ROM.
+
+SHORT_NAMES = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen
+# will interpret the first line (until the first dot) of a JavaDoc-style
+# comment as the brief description. If set to NO, the JavaDoc
+# comments will behave just like regular Qt-style comments
+# (thus requiring an explicit @brief command for a brief description.)
+
+JAVADOC_AUTOBRIEF = YES
+
+# If the QT_AUTOBRIEF tag is set to YES then Doxygen will
+# interpret the first line (until the first dot) of a Qt-style
+# comment as the brief description. If set to NO, the comments
+# will behave just like regular Qt-style comments (thus requiring
+# an explicit command for a brief description.)
+
+QT_AUTOBRIEF = YES
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen
+# treat a multi-line C++ special comment block (i.e. a block of //! or ///
+# comments) as a brief description. This used to be the default behaviour.
+# The new default is to treat a multi-line C++ comment block as a detailed
+# description. Set this tag to YES if you prefer the old behaviour instead.
+
+MULTILINE_CPP_IS_BRIEF = NO
+
+# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented
+# member inherits the documentation from any documented member that it
+# re-implements.
+
+INHERIT_DOCS = YES
+
+# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce
+# a new page for each member. If set to NO, the documentation of a member will
+# be part of the file/class/namespace that contains it.
+
+SEPARATE_MEMBER_PAGES = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab.
+# Doxygen uses this value to replace tabs by spaces in code fragments.
+
+TAB_SIZE = 8
+
+# This tag can be used to specify a number of aliases that acts
+# as commands in the documentation. An alias has the form "name=value".
+# For example adding "sideeffect=\par Side Effects:\n" will allow you to
+# put the command \sideeffect (or @sideeffect) in the documentation, which
+# will result in a user-defined paragraph with heading "Side Effects:".
+# You can put \n's in the value part of an alias to insert newlines.
+
+ALIASES =
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C
+# sources only. Doxygen will then generate output that is more tailored for C.
+# For instance, some of the names that are used will be different. The list
+# of all members will be omitted, etc.
+
+OPTIMIZE_OUTPUT_FOR_C = YES
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java
+# sources only. Doxygen will then generate output that is more tailored for
+# Java. For instance, namespaces will be presented as packages, qualified
+# scopes will look different, etc.
+
+OPTIMIZE_OUTPUT_JAVA = NO
+
+# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran
+# sources only. Doxygen will then generate output that is more tailored for
+# Fortran.
+
+OPTIMIZE_FOR_FORTRAN = NO
+
+# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL
+# sources. Doxygen will then generate output that is tailored for
+# VHDL.
+
+OPTIMIZE_OUTPUT_VHDL = NO
+
+# Doxygen selects the parser to use depending on the extension of the files it
+# parses. With this tag you can assign which parser to use for a given extension.
+# Doxygen has a built-in mapping, but you can override or extend it using this
+# tag. The format is ext=language, where ext is a file extension, and language
+# is one of the parsers supported by doxygen: IDL, Java, Javascript, CSharp, C,
+# C++, D, PHP, Objective-C, Python, Fortran, VHDL, C, C++. For instance to make
+# doxygen treat .inc files as Fortran files (default is PHP), and .f files as C
+# (default is Fortran), use: inc=Fortran f=C. Note that for custom extensions
+# you also need to set FILE_PATTERNS otherwise the files are not read by doxygen.
+
+EXTENSION_MAPPING =
+
+# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
+# to include (a tag file for) the STL sources as input, then you should
+# set this tag to YES in order to let doxygen match functions declarations and
+# definitions whose arguments contain STL classes (e.g. func(std::string); v.s.
+# func(std::string) {}). This also makes the inheritance and collaboration
+# diagrams that involve STL classes more complete and accurate.
+
+BUILTIN_STL_SUPPORT = NO
+
+# If you use Microsoft's C++/CLI language, you should set this option to YES to
+# enable parsing support.
+
+CPP_CLI_SUPPORT = NO
+
+# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only.
+# Doxygen will parse them like normal C++ but will assume all classes use public
+# instead of private inheritance when no explicit protection keyword is present.
+
+SIP_SUPPORT = NO
+
+# For Microsoft's IDL there are propget and propput attributes to indicate getter
+# and setter methods for a property. Setting this option to YES (the default)
+# will make doxygen replace the get and set methods by a property in the
+# documentation. This will only work if the methods are indeed getting or
+# setting a simple type. If this is not the case, or you want to show the
+# methods anyway, you should set this option to NO.
+
+IDL_PROPERTY_SUPPORT = YES
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC
+# tag is set to YES, then doxygen will reuse the documentation of the first
+# member in the group (if any) for the other members of the group. By default
+# all members of a group must be documented explicitly.
+
+DISTRIBUTE_GROUP_DOC = NO
+
+# Set the SUBGROUPING tag to YES (the default) to allow class member groups of
+# the same type (for instance a group of public functions) to be put as a
+# subgroup of that type (e.g. under the Public Functions section). Set it to
+# NO to prevent subgrouping. Alternatively, this can be done per class using
+# the \nosubgrouping command.
+
+SUBGROUPING = YES
+
+# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and
+# unions are shown inside the group in which they are included (e.g. using
+# @ingroup) instead of on a separate page (for HTML and Man pages) or
+# section (for LaTeX and RTF).
+
+INLINE_GROUPED_CLASSES = NO
+
+# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum
+# is documented as struct, union, or enum with the name of the typedef. So
+# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
+# with name TypeT. When disabled the typedef will appear as a member of a file,
+# namespace, or class. And the struct will be named TypeS. This can typically
+# be useful for C code in case the coding convention dictates that all compound
+# types are typedef'ed and only the typedef is referenced, never the tag name.
+
+TYPEDEF_HIDES_STRUCT = NO
+
+# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to
+# determine which symbols to keep in memory and which to flush to disk.
+# When the cache is full, less often used symbols will be written to disk.
+# For small to medium size projects (<1000 input files) the default value is
+# probably good enough. For larger projects a too small cache size can cause
+# doxygen to be busy swapping symbols to and from disk most of the time
+# causing a significant performance penalty.
+# If the system has enough physical memory increasing the cache will improve the
+# performance by keeping more symbols in memory. Note that the value works on
+# a logarithmic scale so increasing the size by one will roughly double the
+# memory usage. The cache size is given by this formula:
+# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0,
+# corresponding to a cache size of 2^16 = 65536 symbols
+
+SYMBOL_CACHE_SIZE = 0
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in
+# documentation are documented, even if no documentation was available.
+# Private class members and static file members will be hidden unless
+# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES
+
+EXTRACT_ALL = YES
+
+# If the EXTRACT_PRIVATE tag is set to YES all private members of a class
+# will be included in the documentation.
+
+EXTRACT_PRIVATE = NO
+
+# If the EXTRACT_STATIC tag is set to YES all static members of a file
+# will be included in the documentation.
+
+EXTRACT_STATIC = YES
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs)
+# defined locally in source files will be included in the documentation.
+# If set to NO only classes defined in header files are included.
+
+EXTRACT_LOCAL_CLASSES = YES
+
+# This flag is only useful for Objective-C code. When set to YES local
+# methods, which are defined in the implementation section but not in
+# the interface are included in the documentation.
+# If set to NO (the default) only methods in the interface are included.
+
+EXTRACT_LOCAL_METHODS = NO
+
+# If this flag is set to YES, the members of anonymous namespaces will be
+# extracted and appear in the documentation as a namespace called
+# 'anonymous_namespace{file}', where file will be replaced with the base
+# name of the file that contains the anonymous namespace. By default
+# anonymous namespaces are hidden.
+
+EXTRACT_ANON_NSPACES = NO
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all
+# undocumented members of documented classes, files or namespaces.
+# If set to NO (the default) these members will be included in the
+# various overviews, but no documentation section is generated.
+# This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_MEMBERS = NO
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all
+# undocumented classes that are normally visible in the class hierarchy.
+# If set to NO (the default) these classes will be included in the various
+# overviews. This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_CLASSES = NO
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all
+# friend (class|struct|union) declarations.
+# If set to NO (the default) these declarations will be included in the
+# documentation.
+
+HIDE_FRIEND_COMPOUNDS = NO
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any
+# documentation blocks found inside the body of a function.
+# If set to NO (the default) these blocks will be appended to the
+# function's detailed documentation block.
+
+HIDE_IN_BODY_DOCS = NO
+
+# The INTERNAL_DOCS tag determines if documentation
+# that is typed after a \internal command is included. If the tag is set
+# to NO (the default) then the documentation will be excluded.
+# Set it to YES to include the internal documentation.
+
+INTERNAL_DOCS = NO
+
+# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate
+# file names in lower-case letters. If set to YES upper-case letters are also
+# allowed. This is useful if you have classes or files whose names only differ
+# in case and if your file system supports case sensitive file names. Windows
+# and Mac users are advised to set this option to NO.
+
+CASE_SENSE_NAMES = YES
+
+# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen
+# will show members with their full class and namespace scopes in the
+# documentation. If set to YES the scope will be hidden.
+
+HIDE_SCOPE_NAMES = NO
+
+# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen
+# will put a list of the files that are included by a file in the documentation
+# of that file.
+
+SHOW_INCLUDE_FILES = YES
+
+# If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen
+# will list include files with double quotes in the documentation
+# rather than with sharp brackets.
+
+FORCE_LOCAL_INCLUDES = NO
+
+# If the INLINE_INFO tag is set to YES (the default) then a tag [inline]
+# is inserted in the documentation for inline members.
+
+INLINE_INFO = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen
+# will sort the (detailed) documentation of file and class members
+# alphabetically by member name. If set to NO the members will appear in
+# declaration order.
+
+SORT_MEMBER_DOCS = YES
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the
+# brief documentation of file, namespace and class members alphabetically
+# by member name. If set to NO (the default) the members will appear in
+# declaration order.
+
+SORT_BRIEF_DOCS = NO
+
+# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen
+# will sort the (brief and detailed) documentation of class members so that
+# constructors and destructors are listed first. If set to NO (the default)
+# the constructors will appear in the respective orders defined by
+# SORT_MEMBER_DOCS and SORT_BRIEF_DOCS.
+# This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO
+# and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO.
+
+SORT_MEMBERS_CTORS_1ST = NO
+
+# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the
+# hierarchy of group names into alphabetical order. If set to NO (the default)
+# the group names will appear in their defined order.
+
+SORT_GROUP_NAMES = NO
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be
+# sorted by fully-qualified names, including namespaces. If set to
+# NO (the default), the class list will be sorted only by class name,
+# not including the namespace part.
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
+# Note: This option applies only to the class list, not to the
+# alphabetical list.
+
+SORT_BY_SCOPE_NAME = NO
+
+# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to
+# do proper type resolution of all parameters of a function it will reject a
+# match between the prototype and the implementation of a member function even
+# if there is only one candidate or it is obvious which candidate to choose
+# by doing a simple string match. By disabling STRICT_PROTO_MATCHING doxygen
+# will still accept a match between prototype and implementation in such cases.
+
+STRICT_PROTO_MATCHING = NO
+
+# The GENERATE_TODOLIST tag can be used to enable (YES) or
+# disable (NO) the todo list. This list is created by putting \todo
+# commands in the documentation.
+
+GENERATE_TODOLIST = YES
+
+# The GENERATE_TESTLIST tag can be used to enable (YES) or
+# disable (NO) the test list. This list is created by putting \test
+# commands in the documentation.
+
+GENERATE_TESTLIST = YES
+
+# The GENERATE_BUGLIST tag can be used to enable (YES) or
+# disable (NO) the bug list. This list is created by putting \bug
+# commands in the documentation.
+
+GENERATE_BUGLIST = YES
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or
+# disable (NO) the deprecated list. This list is created by putting
+# \deprecated commands in the documentation.
+
+GENERATE_DEPRECATEDLIST= YES
+
+# The ENABLED_SECTIONS tag can be used to enable conditional
+# documentation sections, marked by \if sectionname ... \endif.
+
+ENABLED_SECTIONS =
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines
+# the initial value of a variable or macro consists of for it to appear in
+# the documentation. If the initializer consists of more lines than specified
+# here it will be hidden. Use a value of 0 to hide initializers completely.
+# The appearance of the initializer of individual variables and macros in the
+# documentation can be controlled using \showinitializer or \hideinitializer
+# command in the documentation regardless of this setting.
+
+MAX_INITIALIZER_LINES = 30
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated
+# at the bottom of the documentation of classes and structs. If set to YES the
+# list will mention the files that were used to generate the documentation.
+
+SHOW_USED_FILES = YES
+
+# If the sources in your project are distributed over multiple directories
+# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy
+# in the documentation. The default is NO.
+
+SHOW_DIRECTORIES = NO
+
+# Set the SHOW_FILES tag to NO to disable the generation of the Files page.
+# This will remove the Files entry from the Quick Index and from the
+# Folder Tree View (if specified). The default is YES.
+
+SHOW_FILES = YES
+
+# Set the SHOW_NAMESPACES tag to NO to disable the generation of the
+# Namespaces page.
+# This will remove the Namespaces entry from the Quick Index
+# and from the Folder Tree View (if specified). The default is YES.
+
+SHOW_NAMESPACES = YES
+
+# The FILE_VERSION_FILTER tag can be used to specify a program or script that
+# doxygen should invoke to get the current version for each file (typically from
+# the version control system). Doxygen will invoke the program by executing (via
+# popen()) the command <command> <input-file>, where <command> is the value of
+# the FILE_VERSION_FILTER tag, and <input-file> is the name of an input file
+# provided by doxygen. Whatever the program writes to standard output
+# is used as the file version. See the manual for examples.
+
+FILE_VERSION_FILTER =
+
+# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed
+# by doxygen. The layout file controls the global structure of the generated
+# output files in an output format independent way. The create the layout file
+# that represents doxygen's defaults, run doxygen with the -l option.
+# You can optionally specify a file name after the option, if omitted
+# DoxygenLayout.xml will be used as the name of the layout file.
+
+LAYOUT_FILE =
+
+#---------------------------------------------------------------------------
+# configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated
+# by doxygen. Possible values are YES and NO. If left blank NO is used.
+
+QUIET = NO
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are
+# generated by doxygen. Possible values are YES and NO. If left blank
+# NO is used.
+
+WARNINGS = YES
+
+# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings
+# for undocumented members. If EXTRACT_ALL is set to YES then this flag will
+# automatically be disabled.
+
+WARN_IF_UNDOCUMENTED = YES
+
+# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for
+# potential errors in the documentation, such as not documenting some
+# parameters in a documented function, or documenting parameters that
+# don't exist or using markup commands wrongly.
+
+WARN_IF_DOC_ERROR = YES
+
+# The WARN_NO_PARAMDOC option can be enabled to get warnings for
+# functions that are documented, but have no documentation for their parameters
+# or return value. If set to NO (the default) doxygen will only warn about
+# wrong or incomplete parameter documentation, but not about the absence of
+# documentation.
+
+WARN_NO_PARAMDOC = NO
+
+# The WARN_FORMAT tag determines the format of the warning messages that
+# doxygen can produce. The string should contain the $file, $line, and $text
+# tags, which will be replaced by the file and line number from which the
+# warning originated and the warning text. Optionally the format may contain
+# $version, which will be replaced by the version of the file (if it could
+# be obtained via FILE_VERSION_FILTER)
+
+WARN_FORMAT = "$file:$line: $text"
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning
+# and error messages should be written. If left blank the output is written
+# to stderr.
+
+WARN_LOGFILE =
+
+#---------------------------------------------------------------------------
+# configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag can be used to specify the files and/or directories that contain
+# documented source files. You may enter file names like "myfile.cpp" or
+# directories like "/usr/src/myproject". Separate the files or directories
+# with spaces.
+
+INPUT = @srcdir@/include/osmocom/usb @srcdir@/src/usb
+
+# This tag can be used to specify the character encoding of the source files
+# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is
+# also the default input encoding. Doxygen uses libiconv (or the iconv built
+# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for
+# the list of possible encodings.
+
+INPUT_ENCODING = UTF-8
+
+# If the value of the INPUT tag contains directories, you can use the
+# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
+# and *.h) to filter out the source-files in the directories. If left
+# blank the following patterns are tested:
+# *.c *.cc *.cxx *.cpp *.c++ *.d *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh
+# *.hxx *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.dox *.py
+# *.f90 *.f *.for *.vhd *.vhdl
+
+FILE_PATTERNS =
+
+# The RECURSIVE tag can be used to turn specify whether or not subdirectories
+# should be searched for input files as well. Possible values are YES and NO.
+# If left blank NO is used.
+
+RECURSIVE = YES
+
+# The EXCLUDE tag can be used to specify files and/or directories that should
+# excluded from the INPUT source files. This way you can easily exclude a
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+
+EXCLUDE =
+
+# The EXCLUDE_SYMLINKS tag can be used select whether or not files or
+# directories that are symbolic links (a Unix file system feature) are excluded
+# from the input.
+
+EXCLUDE_SYMLINKS = NO
+
+# If the value of the INPUT tag contains directories, you can use the
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude
+# certain files from those directories. Note that the wildcards are matched
+# against the file with absolute path, so to exclude all test directories
+# for example use the pattern */test/*
+
+EXCLUDE_PATTERNS =
+
+# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
+# (namespaces, classes, functions, etc.) that should be excluded from the
+# output. The symbol name can be a fully qualified name, a word, or if the
+# wildcard * is used, a substring. Examples: ANamespace, AClass,
+# AClass::ANamespace, ANamespace::*Test
+
+EXCLUDE_SYMBOLS =
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or
+# directories that contain example code fragments that are included (see
+# the \include command).
+
+EXAMPLE_PATH =
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
+# and *.h) to filter out the source-files in the directories. If left
+# blank all files are included.
+
+EXAMPLE_PATTERNS =
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be
+# searched for input files to be used with the \include or \dontinclude
+# commands irrespective of the value of the RECURSIVE tag.
+# Possible values are YES and NO. If left blank NO is used.
+
+EXAMPLE_RECURSIVE = NO
+
+# The IMAGE_PATH tag can be used to specify one or more files or
+# directories that contain image that are included in the documentation (see
+# the \image command).
+
+# IMAGE_PATH = images/
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should
+# invoke to filter for each input file. Doxygen will invoke the filter program
+# by executing (via popen()) the command <filter> <input-file>, where <filter>
+# is the value of the INPUT_FILTER tag, and <input-file> is the name of an
+# input file. Doxygen will then use the output that the filter program writes
+# to standard output.
+# If FILTER_PATTERNS is specified, this tag will be
+# ignored.
+
+INPUT_FILTER =
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern
+# basis.
+# Doxygen will compare the file name with each pattern and apply the
+# filter if there is a match.
+# The filters are a list of the form:
+# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further
+# info on how filters are used. If FILTER_PATTERNS is empty or if
+# non of the patterns match the file name, INPUT_FILTER is applied.
+
+FILTER_PATTERNS =
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using
+# INPUT_FILTER) will be used to filter the input files when producing source
+# files to browse (i.e. when SOURCE_BROWSER is set to YES).
+
+FILTER_SOURCE_FILES = NO
+
+# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file
+# pattern. A pattern will override the setting for FILTER_PATTERN (if any)
+# and it is also possible to disable source filtering for a specific pattern
+# using *.ext= (so without naming a filter). This option only has effect when
+# FILTER_SOURCE_FILES is enabled.
+
+FILTER_SOURCE_PATTERNS =
+
+#---------------------------------------------------------------------------
+# configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will
+# be generated. Documented entities will be cross-referenced with these sources.
+# Note: To get rid of all source code in the generated output, make sure also
+# VERBATIM_HEADERS is set to NO.
+
+SOURCE_BROWSER = NO
+
+# Setting the INLINE_SOURCES tag to YES will include the body
+# of functions and classes directly in the documentation.
+
+INLINE_SOURCES = NO
+
+# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct
+# doxygen to hide any special comment blocks from generated source code
+# fragments. Normal C and C++ comments will always remain visible.
+
+STRIP_CODE_COMMENTS = YES
+
+# If the REFERENCED_BY_RELATION tag is set to YES
+# then for each documented function all documented
+# functions referencing it will be listed.
+
+REFERENCED_BY_RELATION = YES
+
+# If the REFERENCES_RELATION tag is set to YES
+# then for each documented function all documented entities
+# called/used by that function will be listed.
+
+REFERENCES_RELATION = YES
+
+# If the REFERENCES_LINK_SOURCE tag is set to YES (the default)
+# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from
+# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will
+# link to the source code.
+# Otherwise they will link to the documentation.
+
+REFERENCES_LINK_SOURCE = YES
+
+# If the USE_HTAGS tag is set to YES then the references to source code
+# will point to the HTML generated by the htags(1) tool instead of doxygen
+# built-in source browser. The htags tool is part of GNU's global source
+# tagging system (see http://www.gnu.org/software/global/global.html). You
+# will need version 4.8.6 or higher.
+
+USE_HTAGS = NO
+
+# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen
+# will generate a verbatim copy of the header file for each class for
+# which an include is specified. Set to NO to disable this.
+
+VERBATIM_HEADERS = YES
+
+#---------------------------------------------------------------------------
+# configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index
+# of all compounds will be generated. Enable this if the project
+# contains a lot of classes, structs, unions or interfaces.
+
+ALPHABETICAL_INDEX = NO
+
+# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then
+# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns
+# in which this list will be split (can be a number in the range [1..20])
+
+COLS_IN_ALPHA_INDEX = 5
+
+# In case all classes in a project start with a common prefix, all
+# classes will be put under the same header in the alphabetical index.
+# The IGNORE_PREFIX tag can be used to specify one or more prefixes that
+# should be ignored while generating the index headers.
+
+IGNORE_PREFIX =
+
+#---------------------------------------------------------------------------
+# configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES (the default) Doxygen will
+# generate HTML output.
+
+GENERATE_HTML = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `html' will be used as the default path.
+
+HTML_OUTPUT = html
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for
+# each generated HTML page (for example: .htm,.php,.asp). If it is left blank
+# doxygen will generate files with .html extension.
+
+HTML_FILE_EXTENSION = .html
+
+# The HTML_HEADER tag can be used to specify a personal HTML header for
+# each generated HTML page. If it is left blank doxygen will generate a
+# standard header. Note that when using a custom header you are responsible
+# for the proper inclusion of any scripts and style sheets that doxygen
+# needs, which is dependent on the configuration options used.
+# It is advised to generate a default header using "doxygen -w html
+# header.html footer.html stylesheet.css YourConfigFile" and then modify
+# that header. Note that the header is subject to change so you typically
+# have to redo this when upgrading to a newer version of doxygen or when changing the value of configuration settings such as GENERATE_TREEVIEW!
+
+HTML_HEADER =
+
+# The HTML_FOOTER tag can be used to specify a personal HTML footer for
+# each generated HTML page. If it is left blank doxygen will generate a
+# standard footer.
+
+HTML_FOOTER =
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading
+# style sheet that is used by each HTML page. It can be used to
+# fine-tune the look of the HTML output. If the tag is left blank doxygen
+# will generate a default style sheet. Note that doxygen will try to copy
+# the style sheet file to the HTML output directory, so don't put your own
+# stylesheet in the HTML output directory as well, or it will be erased!
+
+HTML_STYLESHEET =
+
+# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the HTML output directory. Note
+# that these files will be copied to the base HTML output directory. Use the
+# $relpath$ marker in the HTML_HEADER and/or HTML_FOOTER files to load these
+# files. In the HTML_STYLESHEET file, use the file name only. Also note that
+# the files will be copied as-is; there are no commands or markers available.
+
+HTML_EXTRA_FILES =
+
+# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output.
+# Doxygen will adjust the colors in the stylesheet and background images
+# according to this color. Hue is specified as an angle on a colorwheel,
+# see http://en.wikipedia.org/wiki/Hue for more information.
+# For instance the value 0 represents red, 60 is yellow, 120 is green,
+# 180 is cyan, 240 is blue, 300 purple, and 360 is red again.
+# The allowed range is 0 to 359.
+
+HTML_COLORSTYLE_HUE = 220
+
+# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of
+# the colors in the HTML output. For a value of 0 the output will use
+# grayscales only. A value of 255 will produce the most vivid colors.
+
+HTML_COLORSTYLE_SAT = 100
+
+# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to
+# the luminance component of the colors in the HTML output. Values below
+# 100 gradually make the output lighter, whereas values above 100 make
+# the output darker. The value divided by 100 is the actual gamma applied,
+# so 80 represents a gamma of 0.8, The value 220 represents a gamma of 2.2,
+# and 100 does not change the gamma.
+
+HTML_COLORSTYLE_GAMMA = 80
+
+# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML
+# page will contain the date and time when the page was generated. Setting
+# this to NO can help when comparing the output of multiple runs.
+
+HTML_TIMESTAMP = NO
+
+# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes,
+# files or namespaces will be aligned in HTML using tables. If set to
+# NO a bullet list will be used.
+
+HTML_ALIGN_MEMBERS = YES
+
+# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
+# documentation will contain sections that can be hidden and shown after the
+# page has loaded. For this to work a browser that supports
+# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox
+# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari).
+
+HTML_DYNAMIC_SECTIONS = NO
+
+# If the GENERATE_DOCSET tag is set to YES, additional index files
+# will be generated that can be used as input for Apple's Xcode 3
+# integrated development environment, introduced with OSX 10.5 (Leopard).
+# To create a documentation set, doxygen will generate a Makefile in the
+# HTML output directory. Running make will produce the docset in that
+# directory and running "make install" will install the docset in
+# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find
+# it at startup.
+# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html
+# for more information.
+
+GENERATE_DOCSET = NO
+
+# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the
+# feed. A documentation feed provides an umbrella under which multiple
+# documentation sets from a single provider (such as a company or product suite)
+# can be grouped.
+
+DOCSET_FEEDNAME = "Doxygen generated docs"
+
+# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that
+# should uniquely identify the documentation set bundle. This should be a
+# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen
+# will append .docset to the name.
+
+DOCSET_BUNDLE_ID = org.doxygen.Project
+
+# When GENERATE_PUBLISHER_ID tag specifies a string that should uniquely identify
+# the documentation publisher. This should be a reverse domain-name style
+# string, e.g. com.mycompany.MyDocSet.documentation.
+
+DOCSET_PUBLISHER_ID = org.doxygen.Publisher
+
+# The GENERATE_PUBLISHER_NAME tag identifies the documentation publisher.
+
+DOCSET_PUBLISHER_NAME = Publisher
+
+# If the GENERATE_HTMLHELP tag is set to YES, additional index files
+# will be generated that can be used as input for tools like the
+# Microsoft HTML help workshop to generate a compiled HTML help file (.chm)
+# of the generated HTML documentation.
+
+GENERATE_HTMLHELP = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can
+# be used to specify the file name of the resulting .chm file. You
+# can add a path in front of the file if the result should not be
+# written to the html output directory.
+
+CHM_FILE =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can
+# be used to specify the location (absolute path including file name) of
+# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run
+# the HTML help compiler on the generated index.hhp.
+
+HHC_LOCATION =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag
+# controls if a separate .chi index file is generated (YES) or that
+# it should be included in the master .chm file (NO).
+
+GENERATE_CHI = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING
+# is used to encode HtmlHelp index (hhk), content (hhc) and project file
+# content.
+
+CHM_INDEX_ENCODING =
+
+# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag
+# controls whether a binary table of contents is generated (YES) or a
+# normal table of contents (NO) in the .chm file.
+
+BINARY_TOC = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members
+# to the contents of the HTML help documentation and to the tree view.
+
+TOC_EXPAND = NO
+
+# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and
+# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated
+# that can be used as input for Qt's qhelpgenerator to generate a
+# Qt Compressed Help (.qch) of the generated HTML documentation.
+
+GENERATE_QHP = NO
+
+# If the QHG_LOCATION tag is specified, the QCH_FILE tag can
+# be used to specify the file name of the resulting .qch file.
+# The path specified is relative to the HTML output folder.
+
+QCH_FILE =
+
+# The QHP_NAMESPACE tag specifies the namespace to use when generating
+# Qt Help Project output. For more information please see
+# http://doc.trolltech.com/qthelpproject.html#namespace
+
+QHP_NAMESPACE = org.doxygen.Project
+
+# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating
+# Qt Help Project output. For more information please see
+# http://doc.trolltech.com/qthelpproject.html#virtual-folders
+
+QHP_VIRTUAL_FOLDER = doc
+
+# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to
+# add. For more information please see
+# http://doc.trolltech.com/qthelpproject.html#custom-filters
+
+QHP_CUST_FILTER_NAME =
+
+# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the
+# custom filter to add. For more information please see
+# <a href="http://doc.trolltech.com/qthelpproject.html#custom-filters">
+# Qt Help Project / Custom Filters</a>.
+
+QHP_CUST_FILTER_ATTRS =
+
+# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this
+# project's
+# filter section matches.
+# <a href="http://doc.trolltech.com/qthelpproject.html#filter-attributes">
+# Qt Help Project / Filter Attributes</a>.
+
+QHP_SECT_FILTER_ATTRS =
+
+# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can
+# be used to specify the location of Qt's qhelpgenerator.
+# If non-empty doxygen will try to run qhelpgenerator on the generated
+# .qhp file.
+
+QHG_LOCATION =
+
+# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files
+# will be generated, which together with the HTML files, form an Eclipse help
+# plugin. To install this plugin and make it available under the help contents
+# menu in Eclipse, the contents of the directory containing the HTML and XML
+# files needs to be copied into the plugins directory of eclipse. The name of
+# the directory within the plugins directory should be the same as
+# the ECLIPSE_DOC_ID value. After copying Eclipse needs to be restarted before
+# the help appears.
+
+GENERATE_ECLIPSEHELP = NO
+
+# A unique identifier for the eclipse help plugin. When installing the plugin
+# the directory name containing the HTML and XML files should also have
+# this name.
+
+ECLIPSE_DOC_ID = org.doxygen.Project
+
+# The DISABLE_INDEX tag can be used to turn on/off the condensed index at
+# top of each HTML page. The value NO (the default) enables the index and
+# the value YES disables it.
+
+DISABLE_INDEX = NO
+
+# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values
+# (range [0,1..20]) that doxygen will group on one line in the generated HTML
+# documentation. Note that a value of 0 will completely suppress the enum
+# values from appearing in the overview section.
+
+ENUM_VALUES_PER_LINE = 1
+
+# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
+# structure should be generated to display hierarchical information.
+# If the tag value is set to YES, a side panel will be generated
+# containing a tree-like index structure (just like the one that
+# is generated for HTML Help). For this to work a browser that supports
+# JavaScript, DHTML, CSS and frames is required (i.e. any modern browser).
+# Windows users are probably better off using the HTML help feature.
+
+GENERATE_TREEVIEW = YES
+
+# By enabling USE_INLINE_TREES, doxygen will generate the Groups, Directories,
+# and Class Hierarchy pages using a tree view instead of an ordered list.
+
+USE_INLINE_TREES = NO
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be
+# used to set the initial width (in pixels) of the frame in which the tree
+# is shown.
+
+TREEVIEW_WIDTH = 250
+
+# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open
+# links to external symbols imported via tag files in a separate window.
+
+EXT_LINKS_IN_WINDOW = NO
+
+# Use this tag to change the font size of Latex formulas included
+# as images in the HTML documentation. The default is 10. Note that
+# when you change the font size after a successful doxygen run you need
+# to manually remove any form_*.png images from the HTML output directory
+# to force them to be regenerated.
+
+FORMULA_FONTSIZE = 10
+
+# Use the FORMULA_TRANPARENT tag to determine whether or not the images
+# generated for formulas are transparent PNGs. Transparent PNGs are
+# not supported properly for IE 6.0, but are supported on all modern browsers.
+# Note that when changing this option you need to delete any form_*.png files
+# in the HTML output before the changes have effect.
+
+FORMULA_TRANSPARENT = YES
+
+# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax
+# (see http://www.mathjax.org) which uses client side Javascript for the
+# rendering instead of using prerendered bitmaps. Use this if you do not
+# have LaTeX installed or if you want to formulas look prettier in the HTML
+# output. When enabled you also need to install MathJax separately and
+# configure the path to it using the MATHJAX_RELPATH option.
+
+USE_MATHJAX = NO
+
+# When MathJax is enabled you need to specify the location relative to the
+# HTML output directory using the MATHJAX_RELPATH option. The destination
+# directory should contain the MathJax.js script. For instance, if the mathjax
+# directory is located at the same level as the HTML output directory, then
+# MATHJAX_RELPATH should be ../mathjax. The default value points to the
+# mathjax.org site, so you can quickly see the result without installing
+# MathJax, but it is strongly recommended to install a local copy of MathJax
+# before deployment.
+
+MATHJAX_RELPATH = http://www.mathjax.org/mathjax
+
+# When the SEARCHENGINE tag is enabled doxygen will generate a search box
+# for the HTML output. The underlying search engine uses javascript
+# and DHTML and should work on any modern browser. Note that when using
+# HTML help (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets
+# (GENERATE_DOCSET) there is already a search function so this one should
+# typically be disabled. For large projects the javascript based search engine
+# can be slow, then enabling SERVER_BASED_SEARCH may provide a better solution.
+
+SEARCHENGINE = YES
+
+# When the SERVER_BASED_SEARCH tag is enabled the search engine will be
+# implemented using a PHP enabled web server instead of at the web client
+# using Javascript. Doxygen will generate the search PHP script and index
+# file to put on the web server. The advantage of the server
+# based approach is that it scales better to large projects and allows
+# full text search. The disadvantages are that it is more difficult to setup
+# and does not have live searching capabilities.
+
+SERVER_BASED_SEARCH = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will
+# generate Latex output.
+
+GENERATE_LATEX = YES
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `latex' will be used as the default path.
+
+LATEX_OUTPUT = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
+# invoked. If left blank `latex' will be used as the default command name.
+# Note that when enabling USE_PDFLATEX this option is only used for
+# generating bitmaps for formulas in the HTML output, but not in the
+# Makefile that is written to the output directory.
+
+LATEX_CMD_NAME = latex
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to
+# generate index for LaTeX. If left blank `makeindex' will be used as the
+# default command name.
+
+MAKEINDEX_CMD_NAME = makeindex
+
+# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact
+# LaTeX documents. This may be useful for small projects and may help to
+# save some trees in general.
+
+COMPACT_LATEX = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used
+# by the printer. Possible values are: a4, letter, legal and
+# executive. If left blank a4wide will be used.
+
+PAPER_TYPE = a4wide
+
+# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX
+# packages that should be included in the LaTeX output.
+
+EXTRA_PACKAGES =
+
+# The LATEX_HEADER tag can be used to specify a personal LaTeX header for
+# the generated latex document. The header should contain everything until
+# the first chapter. If it is left blank doxygen will generate a
+# standard header. Notice: only use this tag if you know what you are doing!
+
+LATEX_HEADER =
+
+# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for
+# the generated latex document. The footer should contain everything after
+# the last chapter. If it is left blank doxygen will generate a
+# standard footer. Notice: only use this tag if you know what you are doing!
+
+LATEX_FOOTER =
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated
+# is prepared for conversion to pdf (using ps2pdf). The pdf file will
+# contain links (just like the HTML output) instead of page references
+# This makes the output suitable for online browsing using a pdf viewer.
+
+PDF_HYPERLINKS = NO
+
+# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of
+# plain latex in the generated Makefile. Set this option to YES to get a
+# higher quality PDF documentation.
+
+USE_PDFLATEX = NO
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode.
+# command to the generated LaTeX files. This will instruct LaTeX to keep
+# running if errors occur, instead of asking the user for help.
+# This option is also used when generating formulas in HTML.
+
+LATEX_BATCHMODE = NO
+
+# If LATEX_HIDE_INDICES is set to YES then doxygen will not
+# include the index chapters (such as File Index, Compound Index, etc.)
+# in the output.
+
+LATEX_HIDE_INDICES = NO
+
+# If LATEX_SOURCE_CODE is set to YES then doxygen will include
+# source code with syntax highlighting in the LaTeX output.
+# Note that which sources are shown also depends on other settings
+# such as SOURCE_BROWSER.
+
+LATEX_SOURCE_CODE = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output
+# The RTF output is optimized for Word 97 and may not look very pretty with
+# other RTF readers or editors.
+
+GENERATE_RTF = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `rtf' will be used as the default path.
+
+RTF_OUTPUT = rtf
+
+# If the COMPACT_RTF tag is set to YES Doxygen generates more compact
+# RTF documents. This may be useful for small projects and may help to
+# save some trees in general.
+
+COMPACT_RTF = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated
+# will contain hyperlink fields. The RTF file will
+# contain links (just like the HTML output) instead of page references.
+# This makes the output suitable for online browsing using WORD or other
+# programs which support those fields.
+# Note: wordpad (write) and others do not support links.
+
+RTF_HYPERLINKS = NO
+
+# Load stylesheet definitions from file. Syntax is similar to doxygen's
+# config file, i.e. a series of assignments. You only have to provide
+# replacements, missing definitions are set to their default value.
+
+RTF_STYLESHEET_FILE =
+
+# Set optional variables used in the generation of an rtf document.
+# Syntax is similar to doxygen's config file.
+
+RTF_EXTENSIONS_FILE =
+
+#---------------------------------------------------------------------------
+# configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES (the default) Doxygen will
+# generate man pages
+
+GENERATE_MAN = NO
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `man' will be used as the default path.
+
+MAN_OUTPUT = man
+
+# The MAN_EXTENSION tag determines the extension that is added to
+# the generated man pages (default is the subroutine's section .3)
+
+MAN_EXTENSION = .3
+
+# If the MAN_LINKS tag is set to YES and Doxygen generates man output,
+# then it will generate one additional man file for each entity
+# documented in the real man page(s). These additional files
+# only source the real man page, but without them the man command
+# would be unable to find the correct page. The default is NO.
+
+MAN_LINKS = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES Doxygen will
+# generate an XML file that captures the structure of
+# the code including all documentation.
+
+GENERATE_XML = NO
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put.
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be
+# put in front of it. If left blank `xml' will be used as the default path.
+
+XML_OUTPUT = xml
+
+# The XML_SCHEMA tag can be used to specify an XML schema,
+# which can be used by a validating XML parser to check the
+# syntax of the XML files.
+
+XML_SCHEMA =
+
+# The XML_DTD tag can be used to specify an XML DTD,
+# which can be used by a validating XML parser to check the
+# syntax of the XML files.
+
+XML_DTD =
+
+# If the XML_PROGRAMLISTING tag is set to YES Doxygen will
+# dump the program listings (including syntax highlighting
+# and cross-referencing information) to the XML output. Note that
+# enabling this will significantly increase the size of the XML output.
+
+XML_PROGRAMLISTING = YES
+
+#---------------------------------------------------------------------------
+# configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will
+# generate an AutoGen Definitions (see autogen.sf.net) file
+# that captures the structure of the code including all
+# documentation. Note that this feature is still experimental
+# and incomplete at the moment.
+
+GENERATE_AUTOGEN_DEF = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES Doxygen will
+# generate a Perl module file that captures the structure of
+# the code including all documentation. Note that this
+# feature is still experimental and incomplete at the
+# moment.
+
+GENERATE_PERLMOD = NO
+
+# If the PERLMOD_LATEX tag is set to YES Doxygen will generate
+# the necessary Makefile rules, Perl scripts and LaTeX code to be able
+# to generate PDF and DVI output from the Perl module output.
+
+PERLMOD_LATEX = NO
+
+# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be
+# nicely formatted so it can be parsed by a human reader.
+# This is useful
+# if you want to understand what is going on.
+# On the other hand, if this
+# tag is set to NO the size of the Perl module output will be much smaller
+# and Perl will parse it just the same.
+
+PERLMOD_PRETTY = YES
+
+# The names of the make variables in the generated doxyrules.make file
+# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX.
+# This is useful so different doxyrules.make files included by the same
+# Makefile don't overwrite each other's variables.
+
+PERLMOD_MAKEVAR_PREFIX =
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will
+# evaluate all C-preprocessor directives found in the sources and include
+# files.
+
+ENABLE_PREPROCESSING = YES
+
+# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro
+# names in the source code. If set to NO (the default) only conditional
+# compilation will be performed. Macro expansion can be done in a controlled
+# way by setting EXPAND_ONLY_PREDEF to YES.
+
+MACRO_EXPANSION = NO
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES
+# then the macro expansion is limited to the macros specified with the
+# PREDEFINED and EXPAND_AS_DEFINED tags.
+
+EXPAND_ONLY_PREDEF = NO
+
+# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files
+# pointed to by INCLUDE_PATH will be searched when a #include is found.
+
+SEARCH_INCLUDES = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that
+# contain include files that are not input files but should be processed by
+# the preprocessor.
+
+INCLUDE_PATH =
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
+# patterns (like *.h and *.hpp) to filter out the header-files in the
+# directories. If left blank, the patterns specified with FILE_PATTERNS will
+# be used.
+
+INCLUDE_FILE_PATTERNS =
+
+# The PREDEFINED tag can be used to specify one or more macro names that
+# are defined before the preprocessor is started (similar to the -D option of
+# gcc). The argument of the tag is a list of macros of the form: name
+# or name=definition (no spaces). If the definition and the = are
+# omitted =1 is assumed. To prevent a macro definition from being
+# undefined via #undef or recursively expanded use the := operator
+# instead of the = operator.
+
+PREDEFINED =
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then
+# this tag can be used to specify a list of macro names that should be expanded.
+# The macro definition that is found in the sources will be used.
+# Use the PREDEFINED tag if you want to use a different macro definition that
+# overrules the definition found in the source code.
+
+EXPAND_AS_DEFINED =
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then
+# doxygen's preprocessor will remove all references to function-like macros
+# that are alone on a line, have an all uppercase name, and do not end with a
+# semicolon, because these will confuse the parser if not removed.
+
+SKIP_FUNCTION_MACROS = YES
+
+#---------------------------------------------------------------------------
+# Configuration::additions related to external references
+#---------------------------------------------------------------------------
+
+# The TAGFILES option can be used to specify one or more tagfiles.
+# Optionally an initial location of the external documentation
+# can be added for each tagfile. The format of a tag file without
+# this location is as follows:
+#
+# TAGFILES = file1 file2 ...
+# Adding location for the tag files is done as follows:
+#
+# TAGFILES = file1=loc1 "file2 = loc2" ...
+# where "loc1" and "loc2" can be relative or absolute paths or
+# URLs. If a location is present for each tag, the installdox tool
+# does not have to be run to correct the links.
+# Note that each tag file must have a unique name
+# (where the name does NOT include the path)
+# If a tag file is not located in the directory in which doxygen
+# is run, you must also specify the path to the tagfile here.
+
+TAGFILES = doc/libosmocore.tag=../../core/html doc/libosmovty.tag=../../vty/html doc/libosmocodec.tag=../../codec/html doc/libosmocoding.tag=../../coding/html doc/libosmoctrl.tag=../../ctrl/html doc/libosmogb.tag=../../gb/html
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create
+# a tag file that is based on the input files it reads.
+
+GENERATE_TAGFILE = doc/libosmousb.tag
+
+# If the ALLEXTERNALS tag is set to YES all external classes will be listed
+# in the class index. If set to NO only the inherited external classes
+# will be listed.
+
+ALLEXTERNALS = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed
+# in the modules index. If set to NO, only the current project's groups will
+# be listed.
+
+EXTERNAL_GROUPS = YES
+
+# The PERL_PATH should be the absolute path and name of the perl script
+# interpreter (i.e. the result of `which perl').
+
+PERL_PATH = /usr/bin/perl
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool
+#---------------------------------------------------------------------------
+
+# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will
+# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base
+# or super classes. Setting the tag to NO turns the diagrams off. Note that
+# this option also works with HAVE_DOT disabled, but it is recommended to
+# install and use dot, since it yields more powerful graphs.
+
+CLASS_DIAGRAMS = YES
+
+# You can define message sequence charts within doxygen comments using the \msc
+# command. Doxygen will then run the mscgen tool (see
+# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the
+# documentation. The MSCGEN_PATH tag allows you to specify the directory where
+# the mscgen tool resides. If left empty the tool is assumed to be found in the
+# default search path.
+
+MSCGEN_PATH =
+
+# If set to YES, the inheritance and collaboration graphs will hide
+# inheritance and usage relations if the target is undocumented
+# or is not a class.
+
+HIDE_UNDOC_RELATIONS = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
+# available from the path. This tool is part of Graphviz, a graph visualization
+# toolkit from AT&T and Lucent Bell Labs. The other options in this section
+# have no effect if this option is set to NO (the default)
+
+HAVE_DOT = NO
+
+# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is
+# allowed to run in parallel. When set to 0 (the default) doxygen will
+# base this on the number of processors available in the system. You can set it
+# explicitly to a value larger than 0 to get control over the balance
+# between CPU load and processing speed.
+
+DOT_NUM_THREADS = 0
+
+# By default doxygen will write a font called Helvetica to the output
+# directory and reference it in all dot files that doxygen generates.
+# When you want a differently looking font you can specify the font name
+# using DOT_FONTNAME. You need to make sure dot is able to find the font,
+# which can be done by putting it in a standard location or by setting the
+# DOTFONTPATH environment variable or by setting DOT_FONTPATH to the directory
+# containing the font.
+
+DOT_FONTNAME = Helvetica
+
+# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs.
+# The default size is 10pt.
+
+DOT_FONTSIZE = 10
+
+# By default doxygen will tell dot to use the output directory to look for the
+# FreeSans.ttf font (which doxygen will put there itself). If you specify a
+# different font using DOT_FONTNAME you can set the path where dot
+# can find it using this tag.
+
+DOT_FONTPATH =
+
+# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for each documented class showing the direct and
+# indirect inheritance relations. Setting this tag to YES will force the
+# the CLASS_DIAGRAMS tag to NO.
+
+CLASS_GRAPH = YES
+
+# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for each documented class showing the direct and
+# indirect implementation dependencies (inheritance, containment, and
+# class references variables) of the class with other documented classes.
+
+COLLABORATION_GRAPH = YES
+
+# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen
+# will generate a graph for groups, showing the direct groups dependencies
+
+GROUP_GRAPHS = YES
+
+# If the UML_LOOK tag is set to YES doxygen will generate inheritance and
+# collaboration diagrams in a style similar to the OMG's Unified Modeling
+# Language.
+
+UML_LOOK = NO
+
+# If set to YES, the inheritance and collaboration graphs will show the
+# relations between templates and their instances.
+
+TEMPLATE_RELATIONS = NO
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT
+# tags are set to YES then doxygen will generate a graph for each documented
+# file showing the direct and indirect include dependencies of the file with
+# other documented files.
+
+INCLUDE_GRAPH = YES
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and
+# HAVE_DOT tags are set to YES then doxygen will generate a graph for each
+# documented header file showing the documented files that directly or
+# indirectly include this file.
+
+INCLUDED_BY_GRAPH = YES
+
+# If the CALL_GRAPH and HAVE_DOT options are set to YES then
+# doxygen will generate a call dependency graph for every global function
+# or class method. Note that enabling this option will significantly increase
+# the time of a run. So in most cases it will be better to enable call graphs
+# for selected functions only using the \callgraph command.
+
+CALL_GRAPH = NO
+
+# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then
+# doxygen will generate a caller dependency graph for every global function
+# or class method. Note that enabling this option will significantly increase
+# the time of a run. So in most cases it will be better to enable caller
+# graphs for selected functions only using the \callergraph command.
+
+CALLER_GRAPH = NO
+
+# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen
+# will generate a graphical hierarchy of all classes instead of a textual one.
+
+GRAPHICAL_HIERARCHY = YES
+
+# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES
+# then doxygen will show the dependencies a directory has on other directories
+# in a graphical way. The dependency relations are determined by the #include
+# relations between the files in the directories.
+
+DIRECTORY_GRAPH = YES
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
+# generated by dot. Possible values are svg, png, jpg, or gif.
+# If left blank png will be used.
+
+DOT_IMAGE_FORMAT = png
+
+# The tag DOT_PATH can be used to specify the path where the dot tool can be
+# found. If left blank, it is assumed the dot tool can be found in the path.
+
+DOT_PATH = /usr/bin/dot
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that
+# contain dot files that are included in the documentation (see the
+# \dotfile command).
+
+DOTFILE_DIRS =
+
+# The MSCFILE_DIRS tag can be used to specify one or more directories that
+# contain msc files that are included in the documentation (see the
+# \mscfile command).
+
+MSCFILE_DIRS =
+
+# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of
+# nodes that will be shown in the graph. If the number of nodes in a graph
+# becomes larger than this value, doxygen will truncate the graph, which is
+# visualized by representing a node as a red box. Note that doxygen if the
+# number of direct children of the root node in a graph is already larger than
+# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note
+# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
+
+DOT_GRAPH_MAX_NODES = 50
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the
+# graphs generated by dot. A depth value of 3 means that only nodes reachable
+# from the root by following a path via at most 3 edges will be shown. Nodes
+# that lay further from the root node will be omitted. Note that setting this
+# option to 1 or 2 may greatly reduce the computation time needed for large
+# code bases. Also note that the size of a graph can be further restricted by
+# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
+
+MAX_DOT_GRAPH_DEPTH = 0
+
+# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
+# background. This is disabled by default, because dot on Windows does not
+# seem to support this out of the box. Warning: Depending on the platform used,
+# enabling this option may lead to badly anti-aliased labels on the edges of
+# a graph (i.e. they become hard to read).
+
+DOT_TRANSPARENT = NO
+
+# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output
+# files in one run (i.e. multiple -o and -T options on the command line). This
+# makes dot run faster, but since only newer versions of dot (>1.8.10)
+# support this, this feature is disabled by default.
+
+DOT_MULTI_TARGETS = NO
+
+# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will
+# generate a legend page explaining the meaning of the various boxes and
+# arrows in the dot generated graphs.
+
+GENERATE_LEGEND = YES
+
+# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will
+# remove the intermediate dot files that are used to generate
+# the various graphs.
+
+DOT_CLEANUP = YES
diff --git a/Doxyfile.vty.in b/Doxyfile.vty.in
index 9f56fd7f..8d9fc69e 100644
--- a/Doxyfile.vty.in
+++ b/Doxyfile.vty.in
@@ -1485,7 +1485,7 @@ SKIP_FUNCTION_MACROS = YES
# If a tag file is not located in the directory in which doxygen
# is run, you must also specify the path to the tagfile here.
-TAGFILES = doc/libosmocore.tag=../../core/html doc/libosmogsm.tag=../../gsm/html doc/libosmocodec.tag=../../codec/html doc/libosmocoding.tag=../../coding/html doc/libosmoctrl.tag=../../ctrl/html doc/libosmogb.tag=../../gb/html
+TAGFILES = doc/libosmocore.tag=../../core/html doc/libosmogsm.tag=../../gsm/html doc/libosmoisdn.tag=../../isdn/html doc/libosmocodec.tag=../../codec/html doc/libosmocoding.tag=../../coding/html doc/libosmoctrl.tag=../../ctrl/html doc/libosmogb.tag=../../gb/html
# When a file name is specified after GENERATE_TAGFILE, doxygen will create
# a tag file that is based on the input files it reads.
diff --git a/Makefile.am b/Makefile.am
index b3663baf..8a8f9636 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1,18 +1,9 @@
ACLOCAL_AMFLAGS = -I m4
-AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include
+AM_CPPFLAGS = -I$(top_srcdir)/include
SUBDIRS = \
include \
src \
- src/vty \
- src/codec \
- src/gsm \
- src/coding \
- src/gb \
- src/ctrl \
- src/sim \
- src/pseudotalloc \
- src/usb \
utils \
tapset \
tests \
@@ -21,7 +12,7 @@ SUBDIRS = \
pkgconfigdir = $(libdir)/pkgconfig
pkgconfig_DATA = libosmocore.pc libosmocodec.pc libosmovty.pc libosmogsm.pc \
libosmogb.pc libosmoctrl.pc libosmocoding.pc libosmosim.pc \
- libosmousb.pc
+ libosmousb.pc libosmoisdn.pc
aclocaldir = $(datadir)/aclocal
dist_aclocal_DATA = m4/osmo_ac_code_coverage.m4 \
@@ -56,6 +47,7 @@ EXTRA_DIST = \
HTML = \
$(top_builddir)/doc/core/html/index.html \
$(top_builddir)/doc/gsm/html/index.html \
+ $(top_builddir)/doc/isdn/html/index.html \
$(top_builddir)/doc/vty/html/index.html \
$(top_builddir)/doc/codec/html/index.html \
$(top_builddir)/doc/coding/html/index.html \
@@ -90,8 +82,8 @@ apidoc: $(HTML)
$(top_builddir)/doc/libosmocore.tag.prep: $(top_builddir)/Doxyfile.core \
$(top_srcdir)/include/osmocom/core/*.h \
- $(top_srcdir)/src/*.[hc] \
- $(top_srcdir)/src/crcXXgen.c.tpl \
+ $(top_srcdir)/src/core/*.[hc] \
+ $(top_srcdir)/src/core/crcXXgen.c.tpl \
$(top_srcdir)/src/pseudotalloc/*.[hc]
rm -rf $(top_builddir)/doc/core; mkdir -p $(top_builddir)/doc/core
rm -rf $(top_builddir)/doc/libosmocore.map
@@ -109,6 +101,14 @@ $(top_builddir)/doc/libosmogsm.tag.prep: $(top_builddir)/Doxyfile.gsm \
-$(DOXYGEN) $(top_builddir)/Doxyfile.gsm
touch "$@"
+$(top_builddir)/doc/libosmoisdn.tag.prep: $(top_builddir)/Doxyfile.isdn \
+ $(top_srcdir)/include/osmocom/isdn/*.h \
+ $(top_srcdir)/src/isdn/*.c
+ rm -rf $(top_builddir)/doc/isdn; mkdir -p $(top_builddir)/doc/isdn
+ rm -rf $(top_builddir)/doc/libosmoisdn.map
+ -$(DOXYGEN) $(top_builddir)/Doxyfile.isdn
+ touch "$@"
+
# Don't delete the entire doc/vty, it contains example.xml and vtydoc.xsd (OS#3986)
$(top_builddir)/doc/libosmovty.tag.prep: $(top_builddir)/Doxyfile.vty \
$(top_srcdir)/include/osmocom/vty/*.h \
@@ -150,6 +150,22 @@ $(top_builddir)/doc/libosmogb.tag.prep: $(top_builddir)/Doxyfile.gb \
-$(DOXYGEN) $(top_builddir)/Doxyfile.gb
touch "$@"
+$(top_builddir)/doc/libosmosim.tag.prep: $(top_builddir)/Doxyfile.sim \
+ $(top_srcdir)/include/osmocom/sim/*.h \
+ $(top_srcdir)/src/sim/*.[hc]
+ rm -rf $(top_builddir)/doc/sim; mkdir -p $(top_builddir)/doc/sim
+ rm -rf $(top_builddir)/doc/libosmosim.map
+ -$(DOXYGEN) $(top_builddir)/Doxyfile.sim
+ touch "$@"
+
+$(top_builddir)/doc/libosmousb.tag.prep: $(top_builddir)/Doxyfile.usb \
+ $(top_srcdir)/include/osmocom/usb/*.h \
+ $(top_srcdir)/src/usb/*.[hc]
+ rm -rf $(top_builddir)/doc/usb; mkdir -p $(top_builddir)/doc/usb
+ rm -rf $(top_builddir)/doc/libosmousb.map
+ -$(DOXYGEN) $(top_builddir)/Doxyfile.usb
+ touch "$@"
+
# Build for real once all depending .tag files are in place.
# Depend on the own .tag.prep file to avoid concurrent builds of the same doc
# tree, and to also depend on all the source files listed above.
@@ -164,83 +180,150 @@ $(top_builddir)/doc/libosmogb.tag.prep: $(top_builddir)/Doxyfile.gb \
$(top_builddir)/doc/core/html/index.html: $(top_builddir)/doc/libosmocore.tag.prep \
$(top_builddir)/doc/libosmogsm.tag.prep \
+ $(top_builddir)/doc/libosmoisdn.tag.prep \
$(top_builddir)/doc/libosmovty.tag.prep \
$(top_builddir)/doc/libosmocodec.tag.prep \
$(top_builddir)/doc/libosmocoding.tag.prep \
$(top_builddir)/doc/libosmoctrl.tag.prep \
- $(top_builddir)/doc/libosmogb.tag.prep
+ $(top_builddir)/doc/libosmogb.tag.prep \
+ $(top_builddir)/doc/libosmosim.tag.prep \
+ $(top_builddir)/doc/libosmousb.tag.prep
rm -rf $(top_builddir)/doc/core; mkdir -p $(top_builddir)/doc/core
$(DOXYGEN) Doxyfile.core
$(top_builddir)/doc/gsm/html/index.html: $(top_builddir)/doc/libosmogsm.tag.prep \
$(top_builddir)/doc/libosmocore.tag.prep \
+ $(top_builddir)/doc/libosmoisdn.tag.prep \
$(top_builddir)/doc/libosmovty.tag.prep \
$(top_builddir)/doc/libosmocodec.tag.prep \
$(top_builddir)/doc/libosmocoding.tag.prep \
$(top_builddir)/doc/libosmoctrl.tag.prep \
- $(top_builddir)/doc/libosmogb.tag.prep
+ $(top_builddir)/doc/libosmogb.tag.prep \
+ $(top_builddir)/doc/libosmosim.tag.prep \
+ $(top_builddir)/doc/libosmousb.tag.prep
rm -rf $(top_builddir)/doc/gsm; mkdir -p $(top_builddir)/doc/gsm
$(DOXYGEN) Doxyfile.gsm
+$(top_builddir)/doc/isdn/html/index.html: $(top_builddir)/doc/libosmoisdn.tag.prep \
+ $(top_builddir)/doc/libosmocore.tag.prep \
+ $(top_builddir)/doc/libosmogsm.tag.prep \
+ $(top_builddir)/doc/libosmovty.tag.prep \
+ $(top_builddir)/doc/libosmocodec.tag.prep \
+ $(top_builddir)/doc/libosmocoding.tag.prep \
+ $(top_builddir)/doc/libosmoctrl.tag.prep \
+ $(top_builddir)/doc/libosmogb.tag.prep \
+ $(top_builddir)/doc/libosmosim.tag.prep \
+ $(top_builddir)/doc/libosmousb.tag.prep
+ rm -rf $(top_builddir)/doc/isdn; mkdir -p $(top_builddir)/doc/isdn
+ $(DOXYGEN) Doxyfile.isdn
+
# Don't delete the entire doc/vty, it contains example.xml and vtydoc.xsd (OS#3986)
$(top_builddir)/doc/vty/html/index.html: $(top_builddir)/doc/libosmovty.tag.prep \
$(top_builddir)/doc/libosmocore.tag.prep \
$(top_builddir)/doc/libosmogsm.tag.prep \
+ $(top_builddir)/doc/libosmoisdn.tag.prep \
$(top_builddir)/doc/libosmocodec.tag.prep \
$(top_builddir)/doc/libosmocoding.tag.prep \
$(top_builddir)/doc/libosmoctrl.tag.prep \
- $(top_builddir)/doc/libosmogb.tag.prep
+ $(top_builddir)/doc/libosmogb.tag.prep \
+ $(top_builddir)/doc/libosmosim.tag.prep \
+ $(top_builddir)/doc/libosmousb.tag.prep
rm -rf $(top_builddir)/doc/vty/html $(top_builddir)/doc/vty/latex
$(DOXYGEN) Doxyfile.vty
$(top_builddir)/doc/codec/html/index.html: $(top_builddir)/doc/libosmocodec.tag.prep \
$(top_builddir)/doc/libosmocore.tag.prep \
$(top_builddir)/doc/libosmogsm.tag.prep \
+ $(top_builddir)/doc/libosmoisdn.tag.prep \
$(top_builddir)/doc/libosmovty.tag.prep \
$(top_builddir)/doc/libosmocoding.tag.prep \
$(top_builddir)/doc/libosmoctrl.tag.prep \
- $(top_builddir)/doc/libosmogb.tag.prep
+ $(top_builddir)/doc/libosmogb.tag.prep \
+ $(top_builddir)/doc/libosmosim.tag.prep \
+ $(top_builddir)/doc/libosmousb.tag.prep
rm -rf $(top_builddir)/doc/codec; mkdir -p $(top_builddir)/doc/codec
$(DOXYGEN) Doxyfile.codec
$(top_builddir)/doc/coding/html/index.html: $(top_builddir)/doc/libosmocoding.tag.prep \
$(top_builddir)/doc/libosmocore.tag.prep \
$(top_builddir)/doc/libosmogsm.tag.prep \
+ $(top_builddir)/doc/libosmoisdn.tag.prep \
$(top_builddir)/doc/libosmovty.tag.prep \
$(top_builddir)/doc/libosmocodec.tag.prep \
$(top_builddir)/doc/libosmoctrl.tag.prep \
- $(top_builddir)/doc/libosmogb.tag.prep
+ $(top_builddir)/doc/libosmogb.tag.prep \
+ $(top_builddir)/doc/libosmosim.tag.prep \
+ $(top_builddir)/doc/libosmousb.tag.prep
rm -rf $(top_builddir)/doc/coding; mkdir -p $(top_builddir)/doc/coding
$(DOXYGEN) Doxyfile.coding
$(top_builddir)/doc/ctrl/html/index.html: $(top_builddir)/doc/libosmoctrl.tag.prep \
$(top_builddir)/doc/libosmocore.tag.prep \
$(top_builddir)/doc/libosmogsm.tag.prep \
+ $(top_builddir)/doc/libosmoisdn.tag.prep \
$(top_builddir)/doc/libosmovty.tag.prep \
$(top_builddir)/doc/libosmocodec.tag.prep \
$(top_builddir)/doc/libosmocoding.tag.prep \
- $(top_builddir)/doc/libosmogb.tag.prep
+ $(top_builddir)/doc/libosmogb.tag.prep \
+ $(top_builddir)/doc/libosmosim.tag.prep \
+ $(top_builddir)/doc/libosmousb.tag.prep
rm -rf $(top_builddir)/doc/ctrl; mkdir -p $(top_builddir)/doc/ctrl
$(DOXYGEN) Doxyfile.ctrl
$(top_builddir)/doc/gb/html/index.html: $(top_builddir)/doc/libosmogb.tag.prep \
$(top_builddir)/doc/libosmocore.tag.prep \
$(top_builddir)/doc/libosmogsm.tag.prep \
+ $(top_builddir)/doc/libosmoisdn.tag.prep \
$(top_builddir)/doc/libosmovty.tag.prep \
$(top_builddir)/doc/libosmocodec.tag.prep \
$(top_builddir)/doc/libosmocoding.tag.prep \
- $(top_builddir)/doc/libosmoctrl.tag.prep
+ $(top_builddir)/doc/libosmoctrl.tag.prep \
+ $(top_builddir)/doc/libosmosim.tag.prep \
+ $(top_builddir)/doc/libosmousb.tag.prep
rm -rf $(top_builddir)/doc/gb; mkdir -p $(top_builddir)/doc/gb
$(DOXYGEN) Doxyfile.gb
+$(top_builddir)/doc/sim/html/index.html: $(top_builddir)/doc/libosmosim.tag.prep \
+ $(top_builddir)/doc/libosmocore.tag.prep \
+ $(top_builddir)/doc/libosmogsm.tag.prep \
+ $(top_builddir)/doc/libosmoisdn.tag.prep \
+ $(top_builddir)/doc/libosmovty.tag.prep \
+ $(top_builddir)/doc/libosmocodec.tag.prep \
+ $(top_builddir)/doc/libosmocoding.tag.prep \
+ $(top_builddir)/doc/libosmoctrl.tag.prep \
+ $(top_builddir)/doc/libosmogb.tag.prep \
+ $(top_builddir)/doc/libosmousb.tag.prep
+ rm -rf $(top_builddir)/doc/sim; mkdir -p $(top_builddir)/doc/sim
+ $(DOXYGEN) Doxyfile.sim
+
+$(top_builddir)/doc/usb/html/index.html: $(top_builddir)/doc/libosmousb.tag.prep \
+ $(top_builddir)/doc/libosmocore.tag.prep \
+ $(top_builddir)/doc/libosmogsm.tag.prep \
+ $(top_builddir)/doc/libosmoisdn.tag.prep \
+ $(top_builddir)/doc/libosmovty.tag.prep \
+ $(top_builddir)/doc/libosmocodec.tag.prep \
+ $(top_builddir)/doc/libosmocoding.tag.prep \
+ $(top_builddir)/doc/libosmoctrl.tag.prep \
+ $(top_builddir)/doc/libosmogb.tag.prep \
+ $(top_builddir)/doc/libosmosim.tag.prep
+ rm -rf $(top_builddir)/doc/usb; mkdir -p $(top_builddir)/doc/usb
+ $(DOXYGEN) Doxyfile.usb
+
if HAVE_DOXYGEN
install-data-hook:
cd $(DESTDIR)$(htmldir) && tar xf html.tar && rm -f html.tar
uninstall-hook:
- cd $(DESTDIR)$(htmldir) && rm -rf {core,gsm,vty,codec,coding,ctrl,gb}
-
-DX_CLEAN = doc/{core,gsm,vty,codec,coding,ctrl,gb}/html/search/* doc/{core,gsm,vty,codec,coding,ctrl,gb}/{html,latex}/* doc/html.tar doc/{core,gsm,vty,codec,coding,ctrl,gb}/doxygen_sqlite3.db doc/*.tag doc/*.tag.prep
+ cd $(DESTDIR)$(htmldir) && rm -rf {core,gsm,isdn,vty,codec,coding,ctrl,gb,sim,usb}
+
+DX_CLEAN = \
+ doc/{core,gsm,isdn,vty,codec,coding,ctrl,gb,sim,usb}/html/search/* \
+ doc/{core,gsm,isdn,vty,codec,coding,ctrl,gb,sim,usb}/{html,latex}/* \
+ doc/{core,gsm,isdn,vty,codec,coding,ctrl,gb,sim,usb}/doxygen_sqlite3.db \
+ doc/*.tag.prep \
+ doc/*.tag \
+ doc/html.tar \
+ $(NULL)
endif
MOSTLYCLEANFILES = $(DX_CLEAN)
diff --git a/TODO-RELEASE b/TODO-RELEASE
index 8ccfa491..b67161dd 100644
--- a/TODO-RELEASE
+++ b/TODO-RELEASE
@@ -7,3 +7,5 @@
# If any interfaces have been added since the last public release: c:r:a + 1.
# If any interfaces have been removed or changed since the last public release: c:r:0.
#library what description / commit summary line
+core ADD osmo_sock_multiaddr_{add,del}_local_addr()
+core ADD gsmtap_inst_fd2() core, DEPRECATE gsmtap_inst_fd()
diff --git a/configure.ac b/configure.ac
index 2467c68d..2fc98958 100644
--- a/configure.ac
+++ b/configure.ac
@@ -53,18 +53,23 @@ case $host in
*)
LTLDFLAGS_OSMOGB='-Wl,--version-script=$(srcdir)/libosmogb.map'
LTLDFLAGS_OSMOGSM='-Wl,--version-script=$(srcdir)/libosmogsm.map'
+ LTLDFLAGS_OSMOISDN='-Wl,--version-script=$(srcdir)/libosmoisdn.map'
LTLDFLAGS_OSMOCODING='-Wl,--version-script=$(srcdir)/libosmocoding.map'
+ LTLDFLAGS_OSMOCORE='-Wl,--version-script=$(srcdir)/libosmocore.map'
LTLDFLAGS_OSMOCTRL='-Wl,--version-script=$(srcdir)/libosmoctrl.map'
;;
esac
AC_SUBST(LTLDFLAGS_OSMOGB)
AC_SUBST(LTLDFLAGS_OSMOGSM)
+AC_SUBST(LTLDFLAGS_OSMOISDN)
AC_SUBST(LTLDFLAGS_OSMOCODING)
+AC_SUBST(LTLDFLAGS_OSMOCORE)
AC_SUBST(LTLDFLAGS_OSMOCTRL)
dnl checks for header files
AC_HEADER_STDC
AC_CHECK_HEADERS(execinfo.h poll.h sys/select.h sys/socket.h sys/signalfd.h sys/eventfd.h sys/timerfd.h syslog.h ctype.h netinet/tcp.h netinet/in.h)
+AC_CHECK_DECL(HAVE_SYS_SOCKET_H, AC_SUBST(HAVE_SYS_SOCKET_H, 1), AC_SUBST(HAVE_SYS_SOCKET_H, 0))
# for src/conv.c
AC_FUNC_ALLOCA
AC_SEARCH_LIBS([dlopen], [dl dld], [LIBRARY_DLOPEN="$LIBS";LIBS=""])
@@ -170,6 +175,20 @@ AC_CONFIG_HEADER(config.h)
PKG_CHECK_MODULES(TALLOC, [talloc >= 2.1.0])
+AC_ARG_ENABLE([uring], [AS_HELP_STRING([--disable-uring], [Build without io_uring support])],
+ [
+ ENABLE_URING=$enableval
+ ],
+ [
+ ENABLE_URING="yes"
+ ])
+AS_IF([test "x$ENABLE_URING" = "xyes"], [
+ PKG_CHECK_MODULES(URING, [liburing >= 0.7])
+ AC_DEFINE([HAVE_URING],[1],[Build with io_uring support])
+])
+AM_CONDITIONAL(ENABLE_URING, test "x$ENABLE_URING" = "xyes")
+AC_SUBST(ENABLE_URING)
+
AC_ARG_ENABLE([pcsc], [AS_HELP_STRING([--disable-pcsc], [Build without PC/SC support])],
[
ENABLE_PCSC=$enableval
@@ -231,7 +250,7 @@ AC_ARG_ENABLE([libmnl],
)],
[mnl=$enableval], [mnl="yes"])
AS_IF([test "x$mnl" = "xyes"], [
- PKG_CHECK_MODULES(LIBMNL, libmnl)
+ PKG_CHECK_MODULES(LIBMNL, libmnl, [AC_SUBST(LIBMNL_PC, [libmnl])])
AC_DEFINE([ENABLE_LIBMNL], [1], [Enable netlink socket support via libmnl])
])
AM_CONDITIONAL(ENABLE_LIBMNL, test "x$mnl" = "xyes")
@@ -292,13 +311,13 @@ fi
AC_ARG_ENABLE(bsc_fd_check,
[AS_HELP_STRING(
- [--enable-bsc-fd-check],
+ [--enable-ofd-check],
[Instrument osmo_fd_register to check that the fd is registered]
)],
[fd_check=$enableval], [fd_check="no"])
-if test x"$fd_check" = x"no"
+if test x"$fd_check" = x"yes"
then
- AC_DEFINE([OSMO_FD_CHECK],[1],[Instrument the osmo_fd_register])
+ AC_DEFINE([OSMO_FD_CHECK], [1], [Instrument the osmo_fd_register])
fi
AC_ARG_ENABLE([force_io_select],
@@ -308,7 +327,7 @@ AC_ARG_ENABLE([force_io_select],
)],
[force_io_select=$enableval], [force_io_select="no"])
AS_IF([test "x$force_io_select" = "xyes"], [
- AC_DEFINE([FORCE_IO_SELECT], [1], [Force the use of select() instaed of poll()])
+ AC_DEFINE([FORCE_IO_SELECT], [1], [Force the use of select() instead of poll()])
])
AC_ARG_ENABLE(msgfile,
@@ -380,7 +399,6 @@ then
AM_CONDITIONAL(ENABLE_CTRL, false)
AM_CONDITIONAL(ENABLE_UTILITIES, false)
AM_CONDITIONAL(ENABLE_GB, false)
- AM_CONDITIONAL(ENABLE_GNUTLS, false)
AM_CONDITIONAL(ENABLE_LIBMNL, false)
AM_CONDITIONAL(ENABLE_LIBSCTP, false)
AM_CONDITIONAL(ENABLE_LIBUSB, false)
@@ -427,6 +445,7 @@ AC_ARG_ENABLE(werror,
if test x"$werror" = x"yes"
then
WERROR_FLAGS="-Werror"
+ WERROR_FLAGS+=" -Werror=implicit-int -Werror=int-conversion -Werror=old-style-definition"
WERROR_FLAGS+=" -Wno-error=deprecated -Wno-error=deprecated-declarations"
WERROR_FLAGS+=" -Wno-error=cpp" # "#warning"
CFLAGS="$CFLAGS $WERROR_FLAGS"
@@ -580,18 +599,35 @@ AC_OUTPUT(
libosmocoding.pc
libosmovty.pc
libosmogsm.pc
+ libosmoisdn.pc
libosmogb.pc
libosmoctrl.pc
libosmosim.pc
libosmousb.pc
include/Makefile
+ include/osmocom/Makefile
+ include/osmocom/codec/Makefile
+ include/osmocom/coding/Makefile
+ include/osmocom/core/Makefile
+ include/osmocom/crypt/Makefile
+ include/osmocom/ctrl/Makefile
+ include/osmocom/gprs/Makefile
+ include/osmocom/gprs/protocol/Makefile
+ include/osmocom/gsm/Makefile
+ include/osmocom/gsm/protocol/Makefile
+ include/osmocom/isdn/Makefile
+ include/osmocom/sim/Makefile
+ include/osmocom/usb/Makefile
+ include/osmocom/vty/Makefile
src/Makefile
+ src/core/Makefile
src/vty/Makefile
src/codec/Makefile
src/coding/Makefile
src/sim/Makefile
src/usb/Makefile
src/gsm/Makefile
+ src/isdn/Makefile
src/gb/Makefile
src/ctrl/Makefile
src/pseudotalloc/Makefile
@@ -599,12 +635,16 @@ AC_OUTPUT(
tests/Makefile
tests/atlocal
utils/Makefile
+ utils/osmo-stat-dummy/Makefile
Doxyfile.core
Doxyfile.gsm
+ Doxyfile.isdn
Doxyfile.vty
Doxyfile.codec
Doxyfile.coding
Doxyfile.gb
Doxyfile.ctrl
+ Doxyfile.sim
+ Doxyfile.usb
Makefile
contrib/libosmocore.spec)
diff --git a/contrib/jenkins_arm.sh b/contrib/jenkins_arm.sh
index e7df37d1..87934158 100755
--- a/contrib/jenkins_arm.sh
+++ b/contrib/jenkins_arm.sh
@@ -21,7 +21,6 @@ build() {
--disable-libsctp \
--disable-libusb \
--disable-libmnl \
- --enable-external-tests \
CFLAGS="-Os -ffunction-sections -fdata-sections -nostartfiles -nodefaultlibs $WERROR_FLAGS"
$MAKE $PARALLEL_MAKE
diff --git a/contrib/libosmocore.spec.in b/contrib/libosmocore.spec.in
index 208f42da..60ca3977 100644
--- a/contrib/libosmocore.spec.in
+++ b/contrib/libosmocore.spec.in
@@ -32,6 +32,9 @@ BuildRequires: pkgconfig(libusb-1.0)
BuildRequires: pkgconfig(talloc) >= 2.1.0
BuildRequires: pkgconfig(libmnl)
BuildRequires: pkgconfig(libsystemd)
+%if 0%{?centos_ver} != 7
+BuildRequires: pkgconfig(liburing)
+%endif
%description
libosmocore is a package with various utility functions that were
@@ -57,12 +60,12 @@ called "osmo-arfcn", and a program called "osmo-auc-gen" that is used
for testing GSM authentication, as well as "osmo-config-merge", a tool
for merging Osmocom configuration files.
-%package -n libosmocodec0
+%package -n libosmocodec4
Summary: GSM 06.10, 06.20, 06.60, 06.90 codec library
License: GPL-2.0-or-later
Group: System/Libraries
-%description -n libosmocodec0
+%description -n libosmocodec4
The libosmocodec library contains an implementation of multiple
GSM codecs:
@@ -75,7 +78,7 @@ GSM codecs:
Summary: Development files for the Osmocom GSM codec library
License: GPL-2.0-or-later
Group: Development/Libraries/C and C++
-Requires: libosmocodec0 = %version
+Requires: libosmocodec4 = %version
%description -n libosmocodec-devel
The libosmocodec library contains an implementation of multiple
@@ -112,13 +115,13 @@ transcoding routines.
This subpackage contains libraries and header files for developing
applications that want to make use of libosmocoding.
-%package -n libosmocore19
+%package -n libosmocore21
Summary: Osmocom core library
# crc16.c has GPL2-only clauses, the rest (*.c) is GPL-2.0+
License: GPL-2.0-only AND GPL-2.0-or-later
Group: System/Libraries
-%description -n libosmocore19
+%description -n libosmocore21
libosmocore is a library with various utility functions shared
between OpenBSC and OsmocomBB.
@@ -127,7 +130,7 @@ Summary: Development files for the Osmocom core library
# crc16.h has GPL2-only clauses, the rest (*.h) is GPL-2.0+
License: GPL-2.0-only AND GPL-2.0-or-later
Group: Development/Libraries/C and C++
-Requires: libosmocore19 = %version
+Requires: libosmocore21 = %version
Requires: libtalloc-devel
Requires: lksctp-tools-devel
@@ -190,12 +193,12 @@ The libosmogb library contains a GPRS BSSGP protocol implementation.
This subpackage contains libraries and header files for developing
applications that want to make use of libosmogb.
-%package -n libosmogsm18
+%package -n libosmogsm20
Summary: Osmocom GSM utility library
License: GPL-2.0-or-later AND AGPL-3.0-or-later
Group: System/Libraries
-%description -n libosmogsm18
+%description -n libosmogsm20
libosmocore is a package with various utility functions that were
originally developed as part of the OpenBSC project.
@@ -210,7 +213,9 @@ Summary: Development files for the Osmocom GSM utility library
License: GPL-2.0-or-later AND AGPL-3.0-or-later
Group: Development/Libraries/C and C++
Requires: libosmocore-devel = %version
-Requires: libosmogsm18 = %version
+Requires: libosmogsm20 = %version
+Requires: libosmoisdn-devel = %version
+Requires: libosmoisdn0 = %version
%description -n libosmogsm-devel
The libosmogsm library in particular is a collection of common code
@@ -222,6 +227,34 @@ protocol definitions for a series of protocols.
This subpackage contains libraries and header files for developing
applications that want to make use of libosmogsm.
+%package -n libosmoisdn0
+Summary: Osmocom ISDN utility library
+License: GPL-2.0-or-later
+Group: System/Libraries
+
+%description -n libosmoisdn0
+libosmocore is a package with various utility functions that were
+originally developed as part of the OpenBSC project.
+
+The libosmoisdn library in particular is a collection of common code used in
+various ISDN related sub-projects inside the Osmocom family of projects. It
+includes an I.460 sub-channel multiplex and a generic LAPD core.
+
+%package -n libosmoisdn-devel
+Summary: Development files for the Osmocom ISDN utility library
+License: GPL-2.0-or-later
+Group: Development/Libraries/C and C++
+Requires: libosmocore-devel = %version
+Requires: libosmoisdn0 = %version
+
+%description -n libosmoisdn-devel
+The libosmoisdn library in particular is a collection of common code used in
+various ISDN related sub-projects inside the Osmocom family of projects. It
+includes an I.460 sub-channel multiplex and a generic LAPD core.
+
+This subpackage contains libraries and header files for developing
+applications that want to make use of libosmogsm.
+
%package -n libosmosim2
Summary: Osmocom SIM card related utility library
License: GPL-2.0-or-later
@@ -248,12 +281,12 @@ access.
This subpackage contains libraries and header files for developing
applications that want to make use of libosmosim.
-%package -n libosmovty9
+%package -n libosmovty13
Summary: Osmocom VTY interface library
License: GPL-2.0-or-later
Group: System/Libraries
-%description -n libosmovty9
+%description -n libosmovty13
libosmocore is a package with various utility functions that were
originally developed as part of the OpenBSC project.
@@ -265,7 +298,7 @@ Summary: Development files for the Osmocom VTY interface library
License: GPL-2.0-or-later
Group: Development/Libraries/C and C++
Requires: libosmocore-devel = %version
-Requires: libosmovty9 = %version
+Requires: libosmovty13 = %version
%description -n libosmovty-devel
The libosmovty library implements the interactive command-line on the
@@ -308,8 +341,18 @@ applications that want to make use of libosmousb.
%build
echo "%version" >.tarball-version
autoreconf -fiv
-%configure --enable-shared --disable-static --enable-systemd-logging \
+
+CONFIGURE_FLAGS="
+ --enable-shared \
+ --disable-static \
+ --enable-systemd-logging \
--includedir="%_includedir/%name"
+"
+%if 0%{?centos_ver} == 7
+ CONFIGURE_FLAGS="$CONFIGURE_FLAGS --disable-uring"
+%endif
+
+%configure $CONFIGURE_FLAGS
make %{?_smp_mflags} V=1
%install
@@ -320,22 +363,24 @@ find "$b/%_libdir" -type f -name "*.la" -delete
%check
make %{?_smp_mflags} check || (find . -name testsuite.log -exec cat {} +)
-%post -n libosmocodec0 -p /sbin/ldconfig
-%postun -n libosmocodec0 -p /sbin/ldconfig
+%post -n libosmocodec4 -p /sbin/ldconfig
+%postun -n libosmocodec4 -p /sbin/ldconfig
%post -n libosmocoding0 -p /sbin/ldconfig
%postun -n libosmocoding0 -p /sbin/ldconfig
-%post -n libosmocore19 -p /sbin/ldconfig
-%postun -n libosmocore19 -p /sbin/ldconfig
+%post -n libosmocore21 -p /sbin/ldconfig
+%postun -n libosmocore21 -p /sbin/ldconfig
%post -n libosmoctrl0 -p /sbin/ldconfig
%postun -n libosmoctrl0 -p /sbin/ldconfig
%post -n libosmogb14 -p /sbin/ldconfig
%postun -n libosmogb14 -p /sbin/ldconfig
-%post -n libosmogsm18 -p /sbin/ldconfig
-%postun -n libosmogsm18 -p /sbin/ldconfig
+%post -n libosmogsm20 -p /sbin/ldconfig
+%postun -n libosmogsm20 -p /sbin/ldconfig
+%post -n libosmoisdn0 -p /sbin/ldconfig
+%postun -n libosmoisdn0 -p /sbin/ldconfig
%post -n libosmosim2 -p /sbin/ldconfig
%postun -n libosmosim2 -p /sbin/ldconfig
-%post -n libosmovty9 -p /sbin/ldconfig
-%postun -n libosmovty9 -p /sbin/ldconfig
+%post -n libosmovty13 -p /sbin/ldconfig
+%postun -n libosmovty13 -p /sbin/ldconfig
%post -n libosmousb0 -p /sbin/ldconfig
%postun -n libosmousb0 -p /sbin/ldconfig
@@ -343,9 +388,9 @@ make %{?_smp_mflags} check || (find . -name testsuite.log -exec cat {} +)
%defattr(-,root,root)
%_bindir/osmo-*
-%files -n libosmocodec0
+%files -n libosmocodec4
%defattr(-,root,root)
-%_libdir/libosmocodec.so.0*
+%_libdir/libosmocodec.so.4*
%files -n libosmocodec-devel
%defattr(-,root,root)
@@ -367,9 +412,9 @@ make %{?_smp_mflags} check || (find . -name testsuite.log -exec cat {} +)
%_libdir/libosmocoding.so
%_libdir/pkgconfig/libosmocoding.pc
-%files -n libosmocore19
+%files -n libosmocore21
%defattr(-,root,root)
-%_libdir/libosmocore.so.19*
+%_libdir/libosmocore.so.21*
%files -n libosmocore-devel
%defattr(-,root,root)
@@ -405,9 +450,9 @@ make %{?_smp_mflags} check || (find . -name testsuite.log -exec cat {} +)
%_libdir/libosmogb.so
%_libdir/pkgconfig/libosmogb.pc
-%files -n libosmogsm18
+%files -n libosmogsm20
%defattr(-,root,root)
-%_libdir/libosmogsm.so.18*
+%_libdir/libosmogsm.so.20*
%files -n libosmogsm-devel
%defattr(-,root,root)
@@ -418,6 +463,18 @@ make %{?_smp_mflags} check || (find . -name testsuite.log -exec cat {} +)
%_libdir/libosmogsm.so
%_libdir/pkgconfig/libosmogsm.pc
+%files -n libosmoisdn0
+%defattr(-,root,root)
+%_libdir/libosmoisdn.so.0*
+
+%files -n libosmoisdn-devel
+%defattr(-,root,root)
+%dir %_includedir/%name
+%dir %_includedir/%name/osmocom
+%_includedir/%name/osmocom/isdn/
+%_libdir/libosmoisdn.so
+%_libdir/pkgconfig/libosmoisdn.pc
+
%files -n libosmosim2
%defattr(-,root,root)
%_libdir/libosmosim.so.2*
@@ -430,9 +487,9 @@ make %{?_smp_mflags} check || (find . -name testsuite.log -exec cat {} +)
%_libdir/libosmosim.so
%_libdir/pkgconfig/libosmosim.pc
-%files -n libosmovty9
+%files -n libosmovty13
%defattr(-,root,root)
-%_libdir/libosmovty.so.9*
+%_libdir/libosmovty.so.13*
%files -n libosmovty-devel
%defattr(-,root,root)
diff --git a/contrib/struct_endianess.py b/contrib/struct_endianness.py
index 6ce75fcb..e6cbe00b 100755
--- a/contrib/struct_endianess.py
+++ b/contrib/struct_endianness.py
@@ -253,7 +253,7 @@ def handle_struct_body(body_str):
new_lines = ['#if OSMO_IS_LITTLE_ENDIAN\n']
new_lines.append(body_str)
new_lines.append('#elif OSMO_IS_BIG_ENDIAN\n'
- '/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianess.py) */\n')
+ '/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianness.py) */\n')
new_lines.append(big_endian_body_str)
new_lines.append('\n#endif\n')
return ''.join(new_lines)
@@ -261,7 +261,7 @@ def handle_struct_body(body_str):
return body_str
def _check_file(f):
- if not (f.endswith('.h') or f.endswith('.c') or f.endswith('.cpp')):
+ if not f.endswith(('.h', '.c', '.cpp')):
return
# section the file into
diff --git a/contrib/talloc_count.sh b/contrib/talloc_count.sh
new file mode 100755
index 00000000..61aa3f57
--- /dev/null
+++ b/contrib/talloc_count.sh
@@ -0,0 +1,53 @@
+#!/bin/sh
+#
+# Print a summary of how often each named object appears in a talloc report.
+#
+# usage:
+# talloc_count.sh my_talloc_report.txt
+# or:
+# osmo_interact_vty.py -p 4242 -c 'show talloc-context application full' | talloc_count.sh
+#
+# produces output like:
+# 1 struct foo
+# 1 struct log_info
+# 1 struct log_info_cat
+# 21 msgb
+# 1391 SCCP-SCOC(N)[N]
+# 1402 struct osmo_fsm_inst
+# [...]
+
+f="$1"
+
+tmpdir="$(mktemp -d)"
+trap "rm -rf \"$tmpdir\"" EXIT
+
+# without input file, read stdin
+if [ "x$f" = "x" ]; then
+ f="$tmpdir/input"
+ cat > $f
+fi
+
+mangled="$tmpdir/mangled"
+grep contains "$f" \
+ | sed 's/[ \t]*contains.*//' \
+ | sed 's/^[ \t]*//' \
+ | sed 's/[ \t][ \t]*/ /g' \
+ | grep -v '^$' \
+ | grep -v '^[0-9]\+$' \
+ | sed 's/0x[0-9a-fA-F]\+/N/g' \
+ | sed 's/\<[0-9a-fA-F]\+\>/N/g' \
+ | sed 's/[0-9]\+/N/g' \
+ | sort \
+ > "$mangled"
+
+count() {
+ name="$1"
+ nr="$(grep -Fx "$name" "$mangled" | wc -l)"
+ printf "%6d $name\\n" $nr
+}
+
+{
+ cat "$mangled" | uniq | while read type; do
+ count "$type"
+ done
+} | sort -h
diff --git a/debian/changelog b/debian/changelog
index 05eb1b4e..c9184df0 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,442 @@
+libosmocore (1.9.0) unstable; urgency=medium
+
+ [ Oliver Smith ]
+ * gsm0808_dec_channel_type: add missing len check
+ * test_gsm0808_enc_dec_channel_type -> …_speech
+ * gsm0808_chan_indicator: add SPEECH_CTM_TEXT_TELEPHONY
+ * gsm0808_enc/dec_channel_type: support data
+ * Fix typo endianess -> endianness
+ * Run struct_endianness.py
+ * gsm0808_dec_channel_type: fix dec of ch_rate_type
+ * tests: add test_gsm0808_enc_dec_channel_type_sign
+ * gsm0808_enc/dec_channel_type: fix transparent flag
+ * libosmocore.map: add tall_{ctr/msgb}_ctx
+ * rsl: put values for Channel Mode into enums
+ * rsl: RSL_CMOD_CSD_T: make enum values consistent
+ * gsm0808: make CSD enum values consistent with RSL
+ * on_dso_load_select: run after on_dso_load_ctx
+ * debian: set compat level to 10
+ * debian: depend on liburing-dev for debian >= 11
+ * contrib/libosmocore.spec: centos7: disable uring
+ * debian: fix build on ubuntu 20.04 without liburing
+
+ [ Neels Hofmeyr ]
+ * add contrib/talloc_count.sh
+ * add osmo_prim_operation_name()
+ * add gsm0808_amr_modes_from_cfg
+ * improve test output for gsm0808_sc_cfg_from_gsm48_mr_cfg()
+ * fix 'make vty-test' for --disable-gb
+ * fix 'make vty-test' for --disable-external-tests --enable-gb
+ * contrib/talloc_count.sh: improve hexadecimal masking
+ * error log: osmo_sock_init2_multiaddr() v4/v6 mix
+ * logging vty: probe 'print' and 'logging timestamp' cmds
+ * vty: show bug in implicit go_parent_node
+ * vty: fix vty->index for implicit go_parent_node
+ * vty: move struct vty_parent_node to private API
+ * gsm: add osmo_mobile_identity_decode_from_l3_buf()
+ * gsm_04_08_gprs: add IEI "GMM TMSI Based NRI Container"
+ * improve API for osmo_routing_area_id
+
+ [ Max ]
+ * socket: propagate error in osmo_sock_unix_init() to the caller
+ * GSMTAP: fix typo
+ * GSMTAP: add gsmtap_source_init*2()
+ * GSMTAP: add missing parameter docstrings
+ * logging: print talloc report on exit from vty test
+
+ [ Philipp Maier ]
+ * i460_mux.c fix apidoc
+ * i460_mux: make osmo_i460_subchan_count public
+ * gsmtap_util: remove whitespace at the end of line
+ * i460_mux: add define constant for maximum number of subchannels
+ * codec: add define constants for RFC5993 and TS101318
+ * gprs_bssgp_rim: also print NSEI when sending RIM messages
+ * gprs_bssgp_rim: allow sending of encoded RIM messages
+ * gprs_bssgp_rim: add decoder for RIM ROUTING ADDRESS
+
+ [ Vadim Yanitskiy ]
+ * gsm: fix invalid check in gsm48_decode_ssversion()
+ * gsm: add missing features to osmo_bts_features_names[]
+ * gsm: ensure completeness of osmo_bts_features_{descs,names}[]
+ * gsm/{bsslap,bssmap_le}: zero-initialize structs using memset()
+ * msgb: use OSMO_ASSERT in msgb_alloc_headroom[_c]()
+ * gsm: use OSMO_ASSERT() in osmo_iuup_msgb_alloc_c()
+ * debian/control: make libosmocore-doc depend on libosmo{ctrl,gb}-doc
+ * debian/control: fix typo
+ * doxygen: also generate documentation for libosmo{sim,usb}
+ * contrib/struct_endianness.py: simplify file extension check
+ * doxygen: remove documentation for non-existent params
+ * doxygen: fix various typos in commands \param and \returns
+ * gsm0502: add burst length definitions from chapter 5.2
+ * coding: clean up Makefile.am
+ * utils/Makefile.am: remove duplicate libosmogsm.la
+ * utils/Makefile.am: do not overwrite AM_CFLAGS
+ * utils/osmo-stat-dummy/Makefile.am: drop empty variables
+ * gsmtap: add missing entries to gsmtap_type_names[]
+ * tests/v110: assert(user_data_chunk_bits) in test_ra1()
+ * isdn: fix identical operands in v110_adapt_IR8000_to_2400()
+ * gsm_04_08: document/clarify enum gsm48_chan_mode values
+ * gsm_04_08: add more enum gsm48_chan_mode speech values
+ * gsm_04_08: add more enum gsm48_chan_mode data values
+ * gsm0808: handle new enum gsm48_chan_mode speech/data values
+ * tests: make VTY tests depend on the respective binaries
+ * fixup (partial revert): "coding: clean up Makefile.am"
+ * core: remove unnecessary #include <osmocom/core/talloc.h>
+ * libosmocore.map: add missing symbols needed for osmo-qcdiag
+ * coding: fix doxygen doc for _xcch_encode_cB()
+ * copyright: fix typo: sysmocom s/s.m.f.c./s.f.m.c./ GmbH
+ * coding: use GSM_MACBLOCK_LEN gsm0503_tch_fr_decode()
+ * coding: gsm0503_tch_f96_[de]interleave() not applicable to TCH/F2.4
+ * coding: declare gsm0503_tch_f96_[de]interleave()
+ * coding: fix API doc: TCH/H needs 6 bursts, not 8
+ * coding: fix API doc: TCH/AFS vs TCH/AHS
+ * coding: use gsm0503_tch_hr_decode2() in coding_test
+ * gsm: fix convolutional code definition for TCH/F4.8
+ * coding: implement TCH/F9.6, TCH/[FH]4.8, TCH/H2.4, TCH/F14.4
+ * coding: implement dedicated codec API for FACCH/[FH]
+ * coding: test FACCH/[FH] bitstealing in test_csd()
+ * coding: fix _tch_csd_burst_map(): do not overwrite FACCH
+ * struct osmo_sub_auth_data: remove OSMO_DEPRECATED_OUTSIDE
+ * coding: fix a copy-paste bug in gsm0503_tch_afs_decode_dtx()
+ * gsm: add gsm0502_fn2ccch_block()
+ * gsm0502: cosmetic: use ARRAY_SIZE in gsm0502_fn2ccch_block()
+ * ipa: fix a typo in ipa_ccm_rcvmsg_base(): PING -> PONG
+ * core: fix pointer access in msgb_l[1-4] macros
+ * coding: remove redundant memset()s in gsm0503_tch_fr{96,144}_encode()
+ * coding: implement encoding/decoding API for TCH/F2.4
+ * lapdm: cosmetic: simplify lapdm_phsap_up(), use OSMO_PRIM[_HDR]
+ * gsm_08_08: define GSM0808_SCT_EXT (separately)
+ * gsm48_ie: fix gsm48_encode_bearer_cap(): encode bcap->data.transp
+ * isdn: mux_timeslot_provide_bits(): remove unused 'count'
+ * .gitignore: add include/osmocom/core/socket_compat.h
+ * tests/{v110,gsm44021}: change naming: 'test_' -> '_test'
+ * gsm_12_21.h: add flags for NM_ATT_IPACC_SUPP_FEATURES
+ * gsm_12_21.h: fix typo: NM_IPAC_F_CHANT_P{C->D}CHF
+
+ [ Pau Espin Pedrol ]
+ * gsm_04_60.h: Better describe origin of enum osmo_gprs_nmo
+ * Move libosmogsm TS 44.060 declarations under include/osmocom/gsm/
+ * libosmogb.pc.in: Fix missing dependency on libosmogsm
+ * gsm: gsm_gsmtime2fn(): constify param
+ * cosmetic: stats_tcp: Fix typo in comment
+ * logging: Unregister osmo_fd before closing fd
+ * select: Optimize osmo_fd_get_by_fd
+ * select.c: Clarify osmo_fd_(un)register() API expectations of registered fd
+ * select.c: Clarify osmo_fd_unregister() can only be called on registered osmo_fds
+ * configure.ac: Fix logic enabling osmo_fd fd checks
+ * configure.ac: Fix typo in enable flag description
+ * select.c: osmo_fd_unregister(): Avoid assert hit with old buggy users of the API
+ * tests/Makefile.am: Move system libs at the end of list
+ * tests/Makefile.am: Drop duplicated libosmogb.la in LDADD
+ * tests/Makefile.am: Move LDADD to right position
+ * logging.c: Sanitize calls to osmo_fd_unregister()
+ * Fix parsing of TLV_TYPE_SINGLE_TV
+ * gsm: Add missing TS 24.008 SM layer IEs
+ * gsm_04_08_gprs.h: Add missing GMM IEs for T3302 and T3346
+ * gsm_04_08_gprs.h: Add enum field for GMM 'P-TMSI type' IE
+ * gb: ns2: Rename parameter name in gprs_ns2_nsvc_by_sockaddr_bind()
+ * tlv: Show bug in decoded tlv_parsed for type TLV_TYPE_SINGLE_TV
+ * Fix 'Fix parsing of TLV_TYPE_SINGLE_TV'
+ * ns2: Count transmitted/dropped in each layer implementation
+ * cosmetic: codec/Makefile.am: list sources one file per line
+ * osmo_io: Make name optional, add _set_name() API
+ * socket: Cache errno before calling further functions
+ * gsm0502.h: Document spec number
+ * gsm: Add missing IE definition for GMM Receive N-PDU Number list
+ * exec: osmo_system_nowait2(): Improve logging and error checks
+ * sockaddr_str: Introduce macro OSMO_SOCKADDR_STR_FMT_ARGS_NOT_NULL
+ * cosmetic: lapdm: Fix typo in comment
+ * tlv: Introduce API msgb_tv32_push()
+ * lapdm: Track fn of primitives in struct lapdm_msg_ctx
+ * gsm: Introduce functions to convert between FN and RFN (Reduced FN)
+ * socket: Add osmo_sock_init flag to enable SCTP ASCONF features
+ * rsl: Introduce new osmocom extension IE RSL_IE_OSMO_ABS_FRAME_NUMBER
+ * lapdm: Append RSL_IE_OSMO_ABS_FRAME_NUMBER to RSLms msgs towards upper layers
+ * Revert "lapdm: Append RSL_IE_OSMO_ABS_FRAME_NUMBER to RSLms msgs towards upper layers"
+ * Revert "rsl: Introduce new osmocom extension IE RSL_IE_OSMO_ABS_FRAME_NUMBER"
+ * lapdm: Update public lapdm_msg_ctx upon CCCH data ind
+ * socket: Avoid aborting socket creation if setsockopt for ASCONF fails
+ * gsm_12_21.h: Introduce packed structs for NM_ATT_IPACC_RLC_CFG{,_2,_3} values
+ * gsm_12_21.h: Introduce packed structs for NM_ATT_IPACC_BSSGP_CFG values
+ * gsm_12_21.h: Introduce packed structs for NM_ATT_IPACC_NS_CFG values
+ * socket: Remove OSMO_SOCK_F_SCTP_ASCONF_SUPPORTED, add osmo_sock_init2_multiaddr2()
+ * socket: Support setsokopt SCTP_INITMSG in osmo_sock_init2_multiaddr2()
+
+ [ Matan Perelman ]
+ * gsm0808_enc_channel_type: Add spare byte
+
+ [ Harald Welte ]
+ * Implement the XOR-2G authentication algorithm
+ * New unit test for XOR-2G authentication
+ * Rename OSMO_AUTH_ALG_XOR to OSMO_AUTH_ALG_XOR_3G
+ * convolutional coding for CSD
+ * Interleaving for CSD
+ * libosmocore.map: Add two missing entries for gsmtap*2() API
+ * Makefile.am: Make libraries depend on .map files
+ * isdn: Add V.110 encoder/decoder
+ * gsm: TS 44.021 modified V.110 frame encoding/decoding support
+ * gsm_08_58: Document IPAC RTP CSD modes in at least a few words
+ * New osmo-gsmtap-logsend utility
+ * gsmtap.h: Define a packet type for encapsulation of GSM RLP frames
+ * rate_ctr: Add rate_ctr_add2() similar to rate_ctr_inc2()
+ * Add osmo_io with initial poll backend
+ * Add osmo_gsm48_si1ro_nch_pos_{encode,decode} functions
+ * cosmetic: Fix spec reference in RSL header file
+ * gsm_08_58.h: Add 'struct rsl_ie_nch_drx_info'
+ * libosmogsm: Support authentication with 256-bit K and/or OP/OPc
+ * libosmogsm: Add OSMO_ASSERT() to ensure correct algorithm
+ * libosmogsm: Ensure MILENAGE + XOR-3G K length is 128 bit
+ * osmo-auc-gen: Convert over to osmo_auth_gen_vec*2 API
+ * libosmogsm: Allow auth API caller to specify RES length
+ * libosmogsm: Factor out the C2 derivation function
+ * libosmogsm: Avoid executing MILENAGE crypto twice (for UMTS and GSM)
+ * libosmogsm: Add support for TUAK authentication algorithm
+ * gsmtap_source_free(): Don't crash if NULL is passed
+
+ [ Daniel Willmann ]
+ * Add libosmocore.map
+ * Add osmo_sockaddr_size() to return the size of the variant used
+ * tests: Add initial osmo_io tests
+ * osmo_io: Avoid read of uninitialized variable
+ * gpsr_ns2_udp: Use osmo_io_fd instead of osmo_fd
+ * osmo_io: Improve handling and documentation of segmentation_cb
+ * osmo_io: Support detecting non-blocking connect()
+ * osmo_io: Consistency - put read/recv callback first in osmo_io_ops
+ * osmo_io: Don't make msg in write_cb const
+ * osmo_io: Remove osmo_iofd_read/write_enable/disable
+ * socket: Ensure fd is not negative in osmo_sock_get_name_buf()
+ * osmo_io: Return early on error in osmo_iofd_register()
+ * osmo_io: Use LOGPIO instead of LOGP
+ * osmo_io: Use bitfield for various boolean flags
+ * osmo_io: Make the test more deterministic between backends
+ * osmo_io: Fix write_enable handling in iofd_txqueue
+ * osmo_io: Remove missing functions from map file
+ * osmo_io: Add osmo_iofd_notify_connected()
+ * osmo_io: Document expectation that segmentation_cb() can modify msgb
+ * osmo_io: Add function to change the maximum length of the tx_queue
+ * cosmetic: Fix doc comment
+ * osmo_io(cosmetic): End in a dot for doxygen AUTO_BRIEF
+ * osmo_io: Fix length calculation in iofd_handle_segmentation()
+ * osmo_io: Ensure correct ownership of msgb when sending
+ * osmo_io: Use MSG_NOSIGNAL to avoid SIGPIPE on write
+ * osmo_io: Add iofd_handle_recv()
+ * osmo_io: Avoid potential double free when sending msgb
+ * osmo_io: Add io_uring backend
+ * ns2: Add VTY option to change the max write queue size for UDP
+ * osmo_io: Change parent of msghdr to iofd (instead of msg)
+ * osmo_io: Use local variable to reference msghdr->msg
+
+ [ Eric ]
+ * fix _thread order
+ * logging: remove log_initialized(void)
+ * gsm48_rest_octets: fix wrong value
+
+ [ Mychaela N. Falconia ]
+ * codec: add osmo_efr_check_sid() function
+ * codec: add SID classification functions per GSM 06.31 & 06.81
+ * codec: add SID preening functions for FR & EFR
+ * codec: add osmo_{fr,efr}_is_any_sid() inline functions
+ * codec: add osmo_gsm611_silence_frame[] datum
+ * codec cosmetic: move old FR ECU code to ecu_fr_old.c
+ * codec: replace GSM-FR ECU with new implementation
+ * coding: fix decoding of EFR triplicated bits
+ * gsm0503_tch_hr_decode(): look at all 8 stealing bits
+ * gsm0503_tch_hr_encode(): accept both TS101318 and RFC5993 payloads
+ * gsm0503_tch_hr_decode2(): new function, emits TS101318 format
+ * libosmocoding.map: export gsm0503_tch_hr_decode2()
+ * coding cosmetic: gsm0503_tch_{fr,hr}_encode(): remove extra spacing
+ * codec: new functions osmo_{fr,efr}_sid_reset()
+ * codec: new function osmo_hr_sid_reset()
+ * coding: gsm0503_tch_{fr,hr}_encode(): add ability to emit BFI
+ * ecu: add is_dtx_pause() method
+
+ [ arehbein ]
+ * core: Check return value of osmo_fd_register()
+ * core: Add function to update osmo_io_ops field for osmo_io_fd
+ * core/osmo_io: Rename variables for readability
+ * core/osmo_io: Fix reception of partial packets
+ * gsm/ipa: Add segmentation callback
+ * Revert "gsm/ipa: Add segmentation callback"
+ * select: Prevent negative index lookup on osmo_fd_lookup.table
+
+ [ Andreas Eversberg ]
+ * ASCI: Add 3GPP TS 44.068 and 44.069 protocol definitions
+ * ASCI: Add IE transcoding according to 3GPP TS 48.008
+ * Added generation of include/osmocom/core/socket_compat.h
+ * ASCI: Add message definition and encoding according to 3GPP TS 48.008
+ * Add support for sending Bter UI frames at lapdm.c
+ * Add support for receiving Bter UI frames at lapdm.c
+ * Add short L3 header to gsm_04_08.h
+ * Fix short L3 header of SI 10 at gsm_04_08.h
+ * ASCI: Add Notification/NCH message to gsm_04_08.h
+ * lapdm: Do not return an error when enqueuing a frame
+ * Add VGCS UPLINK GRANT message structure to gsm_04_08.h
+ * ASCI: Also display group/broadcast call message names
+ * Allow 'configure <cr>' at VTY to enter config mode
+ * ASCI: Add decoding of mobile identity in TALKER INDICATION
+ * ASCI: Add missing check for return value of gsm0808_enc_speech_codec_list2()
+ * ASCI: Add BCC call state definitions
+ * LAPDM: Use correct offset to short header on recevied frame
+ * ASCI: Add definition for TALKER INDICATION and UPLINK RELEASE
+
+ [ Sylvain Munaut ]
+ * gsm: Fix comment for TCH/F4.8 code
+ * gsm: Improve the TCH/H2.4 coding routines
+
+ -- Pau Espin Pedrol <pespin@sysmocom.de> Tue, 12 Sep 2023 13:15:52 +0200
+
+libosmocore (1.8.0) unstable; urgency=medium
+
+ [ Vadim Yanitskiy ]
+ * fix uninitialized err pointer passed to osmo_bssap_le_dec()
+ * gsm0408_test: do not return early in test_bearer_cap()
+ * gsm0408_test: add a testcase for gsm48_decode_bearer_cap()
+ * gsm48_ie: fix coding style: while is not a function
+ * gb: fix uninitialized ptr access in bssgp_encode_rim_pdu()
+ * fsm: add unit tests verifying state timeout s/ms accuracy
+ * fsm: fix state_chg(): pass microseconds to osmo_timer_schedule()
+ * tests/tdef: assert pointer returned by osmo_tdef_get_entry()
+ * gb/gprs_ns: call osmo_timer_del() unconditionally
+ * fsm: osmo_fsm_{event,inst,state}_name(): make *fi pointer const
+ * logging: add a new category DLCSN1 for libosmo-csn1
+ * {gb,sim,usb}: ensure -no-undefined is present in *_la_LDFLAGS
+ * include: use '#pragma once' everywhere
+ * gsm0502: use parentheses in GSM_TDMA_FN_{SUM,SUB} macros
+ * configure.ac: fix 'AM_CONDITIONAL(ENABLE_GNUTLS, false)' listed twice
+ * {gsm,gb}/Makefile.am: drop undefined $GCC_FVISIBILITY_HIDDEN
+ * gsm0502: gsm0502_fn_remap(): use GSM_TDMA_FN_SUB() macro
+ * */Makefile.am: do not mix up AM_CFLAGS with AM_CPPFLAGS
+ * gsm0808: cosmetic: switch is not a function
+ * gsm0808: remove unneeded assignment in enc_speech_codec()
+ * gsm0808: remove redundant assert() in enc_speech_codec()
+ * gsm0808: remove over-defensive assert()s for function parameters
+ * gsm0808: add gsm0808_enc_speech_codec[_list]2()
+ * gsm0808: use new gsm0808_enc_speech_codec[_list]2() API
+ * gsm48_ie: gsm48_decode_freq_list(): make 'cd' argument const
+
+ [ Pau Espin Pedrol ]
+ * iuup: Explicitly mark default case as unexpected with assert
+ * cbsp: avoid potential msgb write overflow in osmo_cbsp_recv_buffered
+ * gsm_23_041.h: Define CBS ETWS Warning Type values
+ * cbsp: Return error if decoding any of the cell id lists fail
+ * tests: Run smscb/gsm0341_test during make check
+ * cbsp: Guard against malformed msgb without l1h,l2h being passed
+ * cbsp: Fix decoding of Fail List
+ * cosmetic: tlv.h: Fix trailing whistespace
+ * tlv.h: Fix TLVP_PRESENT returning a pointer instead of a boolean
+ * gsm: Add BTS feature for Osmux
+ * gsm: rsl: Define new osmocom extension TLV IE to pass Osmux CID
+ * gsm: bts_features: Add missing entries to osmo_bts_features_names
+ * utils.h: protect param with parenthesis in OSMO_BYTES_FOR_BITS()
+ * vty: Allow using hex representations in cmd numeric ranges
+ * socket.h: Reorder sockaddr APIs to have them all together
+ * socket: Introduce API osmo_sockaddr_is_any
+ * gsm: constify several readonly params
+ * ctrl: error if program forgot to initialize the ctr handler before installing cmds
+ * socket.h: Introduce API osmo_sockaddr_netmask_to_prefixlen()
+ * Move src/*.{c,h} to src/core/
+ * src/core/Makefile.am: reformat SOURCES list
+ * Split include/Makefile.am content into subdirs
+ * Makefile.am: Remove unexsiting all_includes variable
+ * Fix all references to config.h
+ * Introduce netns API
+ * Introduce netdev API
+ * Introduce tundev API
+ * configure --enable-libmnl: Add libmnl to libosmocore.pc.in Requires
+ * netdev: Fix compilation building with --disable-libmnl
+ * tun: Fix potential unpaired call to osmo_netns_switch_exit()
+ * gprs_ns2_fr: use osmo_netdev to monitor and operate network device
+ * debian/rules: Fix moved path crc*gen.c
+
+ [ Mychaela Falconia ]
+ * gsm48_ie: fix parsing of Bearer capability IE without octet 3a
+
+ [ Harald Welte ]
+ * sim/class_tables: Add GET IDENTITY, SUSPEND UICC, EXCHANGE CAPABILITIES
+ * allocate VTY port number 4270 for osmo-isdntap
+ * logging.h: Allocate DLM2PA and DLM2UA for libosmo-sigtran
+ * Support building with -Werror=strict-prototypes / -Werror=old-style-definition
+ * Disable -Wstrict-prototypes for logging_vty_add_cmds()
+ * vty/logging.h: Avoid -Werror=pragmas error in C++ code
+ * Add -Werror=implicit-int -Werror=int-conversion -Werror=old-style-definition
+ * Fix typos in copyright statements.
+ * gsmtap.h: Add definitions for various ISDN sub-types
+ * create libosmoisdn sub-library
+ * isdndlc: Fix documentation
+
+ [ Oliver Smith ]
+ * gsm0808_enc_aoip_trasp_addr: add length check
+ * utils/osmo-stat-dummy: check for ENABLE_UTILITIES
+ * d/control: libosmocore-dev: depend on libmnl-dev
+ * gsm_08_08.h: fix typo in GSM0808_DATA_FULL_PREF
+
+ [ Alexander Couzens ]
+ * gprs_ns2: add vty `nse <0-65535> restart sns`
+ * gb: add bssgp2_enc_flush_ll encode FLUSH-LL
+
+ [ Neels Hofmeyr ]
+ * enrich API doc for gsm0808_speech_codec
+ * gsm0408_test: do not print errno in expected output
+ * comments: gsm_08_08.h: AMR cfg: explain in much more detail
+ * osmo_tdef_get(): clarify API doc on val_if_not_present
+
+ [ Max ]
+ * Ignore osmo-ns-dummy
+ * Add function to guess AF_UNSPEC address
+ * Add osmo_sockaddr_strs_to_str()
+ * cosmetic: remove trailing space
+ * cosmetic: make linter happy with LAPD code
+ * LAPD: log unknown format value
+ * LAPD: use bool for T200 reset flags
+ * msgb: expand copy test
+ * doc: correct typo in ticket reference
+ * msgb: introduce extended copy functions
+ * Add define for unset Frame Number
+ * LAPD: move tx_hist code into static functions
+ * osmo-ns-dummy: add ctrl interface
+ * jenkins_arm.sh: disable external tests
+ * vty: fix doc typo
+ * telnet_init_dynif: propagate error
+ * telnet_init_dynif: don't allow negative port
+ * rate_ctr: convert to timerfd
+ * rate_ctr: drop rate estimation code
+ * osmo-stat-dummy: add rate counters and statsd tester
+ * ctrl: add optional port to bind command
+ * ASCI: add VBS/VGCS support to BTS features list
+ * SI: add RR short PD message types
+ * Fixup .gitignore
+ * SI: add missing header
+ * Add SI10 support
+
+ [ neels ]
+ * Revert "Add osmo_sockaddr_strs_to_str()"
+ * Revert "Add function to guess AF_UNSPEC address"
+
+ [ Daniel Willmann ]
+ * use_count: Return if uc is NULL
+
+ [ Keith Whyte ]
+ * Fix LCLS-CONNECT-CONTROL generation
+ * Fix Typo in gsm0808_msgt_names[]
+
+ [ Philipp Maier ]
+ * msgb: assert msgb->lXh to be not NULL
+ * msgb: do not use msgb_l4 instead of msgb_sms
+ * bits: fix typo
+ * uitils: add floored and euclidian modulo functions
+ * gsm0408_test: add unittest for gsm_gsmtime2fn()
+ * gsm_utils: improve gsm_gsmtime2fn()
+
+ [ arehbein ]
+ * gb/vty: Show if NSVC is blocked locally by O&M/vty or by remote
+ * libosmocore: Deprecate APIs telnet_init(_dynip)()
+ * libosmocore: Transition to use of 'telnet_init_default'
+
+ [ Eric ]
+ * bitgen test: fix concat macro
+
+ -- Pau Espin Pedrol <pespin@sysmocom.de> Tue, 07 Feb 2023 11:20:41 +0100
+
libosmocore (1.7.0) unstable; urgency=medium
[ Vadim Yanitskiy ]
diff --git a/debian/compat b/debian/compat
index ec635144..f599e28b 100644
--- a/debian/compat
+++ b/debian/compat
@@ -1 +1 @@
-9
+10
diff --git a/debian/control b/debian/control
index ec231336..5fb26cbc 100644
--- a/debian/control
+++ b/debian/control
@@ -2,7 +2,8 @@ Source: libosmocore
Maintainer: Osmocom team <openbsc@lists.osmocom.org>
Section: libs
Priority: optional
-Build-Depends: debhelper (>= 9),
+# liburing-dev: don't try to install it on debian 10 and ubuntu 20.04
+Build-Depends: debhelper (>= 10),
autotools-dev,
autoconf,
automake,
@@ -19,6 +20,7 @@ Build-Depends: debhelper (>= 9),
libusb-1.0-0-dev,
libmnl-dev,
libsystemd-dev,
+ liburing-dev | base-files (<< 11) | ubuntu-keyring (<< 2021),
python3:native
Standards-Version: 3.9.8
Vcs-Git: https://gitea.osmocom.org/osmocom/libosmocore
@@ -29,12 +31,13 @@ Package: libosmocore
Section: libs
Architecture: any
Multi-Arch: foreign
-Depends: libosmocodec0 (= ${binary:Version}),
+Depends: libosmocodec4 (= ${binary:Version}),
libosmocoding0 (= ${binary:Version}),
- libosmocore19 (= ${binary:Version}),
+ libosmocore21 (= ${binary:Version}),
libosmogb14 (= ${binary:Version}),
- libosmogsm18 (= ${binary:Version}),
- libosmovty9 (= ${binary:Version}),
+ libosmogsm20 (= ${binary:Version}),
+ libosmoisdn0 (= ${binary:Version}),
+ libosmovty13 (= ${binary:Version}),
libosmoctrl0 (= ${binary:Version}),
libosmosim2 (= ${binary:Version}),
libosmousb0 (= ${binary:Version}),
@@ -46,7 +49,7 @@ Description: Open Source MObile COMmunications CORE library (metapackage)
least) other programs that are developed in the sphere of Free Software / Open
Source mobile communication.
-Package: libosmocodec0
+Package: libosmocodec4
Section: libs
Architecture: any
Multi-Arch: same
@@ -72,7 +75,7 @@ Package: libosmocodec-doc
Architecture: all
Section: doc
Depends: ${misc:Depends},
- libosmocodec0,
+ libosmocodec4,
libjs-jquery
Description: Documentation for the osmo codec library
This is part of the libosmocore "meta"-library. The libosmocore library
@@ -115,7 +118,7 @@ Description: Documentation for the osmo coding library
.
This package contains the documentation for the libosmocoding library.
-Package: libosmocore19
+Package: libosmocore21
Section: libs
Architecture: any
Multi-Arch: same
@@ -136,12 +139,17 @@ Package: libosmocore-doc
Architecture: all
Section: doc
Depends: ${misc:Depends},
- libosmocore19,
+ libosmocore21,
libjs-jquery,
libosmocodec-doc,
libosmocoding-doc,
libosmogsm-doc,
- libosmovty-doc
+ libosmoisdn-doc,
+ libosmovty-doc,
+ libosmoctrl-doc,
+ libosmogb-doc,
+ libosmosim-doc,
+ libosmousb-doc
Description: Documentation for the Osmo Core library
This is part of the libosmocore "meta"-library. The libosmocore library
contains various utility functions that were originally developed as part of
@@ -183,7 +191,7 @@ Description: Documentation for the Osmo GPRS Gb library
.
This package contains the documentation for the libosmogb library.
-Package: libosmogsm18
+Package: libosmogsm20
Section: libs
Architecture: any
Multi-Arch: same
@@ -207,7 +215,7 @@ Package: libosmogsm-doc
Architecture: all
Section: doc
Depends: ${misc:Depends},
- libosmogsm18,
+ libosmogsm20,
libjs-jquery
Description: Documentation for the Osmo GSM utility library
This is part of the libosmocore "meta"-library. The libosmocore library
@@ -218,7 +226,40 @@ Description: Documentation for the Osmo GSM utility library
.
This package contains the documentation for the libosmogsm library.
-Package: libosmovty9
+Package: libosmoisdn0
+Section: libs
+Architecture: any
+Multi-Arch: same
+Depends: ${shlibs:Depends},
+ ${misc:Depends}
+Pre-Depends: ${misc:Pre-Depends}
+Description: Osmo ISDN utility library
+ This is part of the libosmocore "meta"-library. The libosmocore library
+ contains various utility functions that were originally developed as part of
+ the OpenBSC project, but which are of a more generic nature and thus useful to
+ (at least) other programs that are developed in the sphere of Free Software /
+ Open Source mobile communication.
+ .
+ The libosmoisdn library in particular is a collection of common code used in
+ various ISDN related sub-projects inside the Osmocom family of projects. It
+ includes an I.460 sub-channel multiplex and a generic LAPD core.
+
+Package: libosmoisdn-doc
+Architecture: all
+Section: doc
+Depends: ${misc:Depends},
+ libosmoisdn0,
+ libjs-jquery
+Description: Documentation for the Osmo ISDN utility library
+ This is part of the libosmocore "meta"-library. The libosmocore library
+ contains various utility functions that were originally developed as part of
+ the OpenBSC project, but which are of a more generic nature and thus useful to
+ (at least) other programs that are developed in the sphere of Free Software /
+ Open Source mobile communication.
+ .
+ This package contains the documentation for the libosmoisdn library.
+
+Package: libosmovty13
Section: libs
Architecture: any
Multi-Arch: same
@@ -239,7 +280,7 @@ Package: libosmovty-doc
Architecture: all
Section: doc
Depends: ${misc:Depends},
- libosmovty9,
+ libosmovty13,
libjs-jquery
Description: Documentation for the Osmo VTY library
This is part of the libosmocore "meta"-library. The libosmocore library
@@ -297,6 +338,21 @@ Description: Osmo SIM library
.
The libosmosim library in particular contains routines for SIM card access.
+Package: libosmosim-doc
+Architecture: all
+Section: doc
+Depends: ${misc:Depends},
+ libosmosim2,
+ libjs-jquery
+Description: Documentation for the Osmo SIM library
+ This is part of the libosmocore "meta"-library. The libosmocore library
+ contains various utility functions that were originally developed as part of
+ the OpenBSC project, but which are of a more generic nature and thus useful to
+ (at least) other programs that are developed in the sphere of Free Software /
+ Open Source mobile communication.
+ .
+ This package contains the documentation for the libosmosim library.
+
Package: libosmousb0
Section: libs
Architecture: any
@@ -314,6 +370,21 @@ Description: Osmo USB library
The libosmosub library in particular contains routines for USB device access
via libusb-1.0, integrated into the libosmocore select event loop.
+Package: libosmousb-doc
+Architecture: all
+Section: doc
+Depends: ${misc:Depends},
+ libosmousb0,
+ libjs-jquery
+Description: Documentation for the Osmo USB library
+ This is part of the libosmocore "meta"-library. The libosmocore library
+ contains various utility functions that were originally developed as part of
+ the OpenBSC project, but which are of a more generic nature and thus useful to
+ (at least) other programs that are developed in the sphere of Free Software /
+ Open Source mobile communication.
+ .
+ This package contains the documentation for the libosmousb library.
+
Package: libosmocore-dev
Architecture: any
Multi-Arch: same
@@ -322,6 +393,7 @@ Depends: libosmocore,
libtalloc-dev (>= 2.1.0),
libsctp-dev,
libusb-1.0-0-dev,
+ libmnl-dev,
${misc:Depends}
Description: Development headers for Open Source MObile COMmunications CORE library
The header files provided by this package may be used to develop
diff --git a/debian/copyright b/debian/copyright
index 79c5876a..9281f03d 100644
--- a/debian/copyright
+++ b/debian/copyright
@@ -13,30 +13,30 @@ License: GPL-2+
Files: include/osmocom/core/loggingrb.h
include/osmocom/core/strrb.h
- src/strrb.c
- src/loggingrb.c
+ src/core/strrb.c
+ src/core/loggingrb.c
Copyright: 2012-2013 Katerina Barone-Adesi <kat.obsc@gmail.com>
License: GPL-2+
Files: include/osmocom/core/linuxrbtree.h
- src/rbtree.c
+ src/core/rbtree.c
Copyright: 1999 Andrea Arcangeli <andrea@suse.de>
2002 David Woodhouse <dwmw2@infradead.org>
License: GPL-2+
Files: include/osmocom/core/crc16.h
- src/crc16.c
+ src/core/crc16.c
Copyright: 2005 Ben Gardner <bgardner@wabtec.com>
License: GPL-2
-Files: src/utils.c
+Files: src/core/utils.c
Copyright: 2011 Harald Welte <laforge@gnumonks.org>
2011 Sylvain Munaut <tnt@246tNt.com>
2014 Nils O. Selåsdal <noselasd@fiane.dyndns.org>
License: GPL-2+
Files: src/gsm/gsm48_ie.c
- src/gsm/lapd_core.c
+ src/isdn/lapd_core.c
src/gsm/lapdm.c
Copyright: 2008,2010-2011 Harald Welte <laforge@gnumonks.org>
2009-2011 Andreas Eversberg <jolly@eversberg.eu>
@@ -100,8 +100,8 @@ Copyright: 1997,1998 Kunihiro Ishiguro
License: GPL-2+
Files: include/osmocom/core/stats.h
- src/stat_item.c
- src/stats.c
+ src/core/stat_item.c
+ src/core/stats.c
src/vty/stats_vty.c
tests/stats/stats_test.c
Copyright: 2009-2010 by Harald Welte <laforge@gnumonks.org>
diff --git a/debian/libosmocodec0.install b/debian/libosmocodec4.install
index 2676133e..2676133e 100644
--- a/debian/libosmocodec0.install
+++ b/debian/libosmocodec4.install
diff --git a/debian/libosmocore-utils.install b/debian/libosmocore-utils.install
index 9501bec9..7245db9c 100644
--- a/debian/libosmocore-utils.install
+++ b/debian/libosmocore-utils.install
@@ -2,3 +2,5 @@ usr/bin/osmo-arfcn
usr/bin/osmo-auc-gen
usr/bin/osmo-aka-verify
usr/bin/osmo-config-merge
+usr/bin/osmo-gsmtap-logsend
+usr/bin/osmo-gsm-shark
diff --git a/debian/libosmocore.dirs b/debian/libosmocore.dirs
index 94090a39..9c10e3aa 100644
--- a/debian/libosmocore.dirs
+++ b/debian/libosmocore.dirs
@@ -5,4 +5,5 @@ usr/include/osmocom/codec
usr/include/osmocom/core
usr/include/osmocom/crypt
usr/include/osmocom/gsm
+usr/include/osmocom/isdn
usr/include/osmocom/vty
diff --git a/debian/libosmocore19.install b/debian/libosmocore21.install
index b73331b9..b73331b9 100644
--- a/debian/libosmocore19.install
+++ b/debian/libosmocore21.install
diff --git a/debian/libosmogsm18.install b/debian/libosmogsm20.install
index 5e617298..5e617298 100644
--- a/debian/libosmogsm18.install
+++ b/debian/libosmogsm20.install
diff --git a/debian/libosmoisdn-doc.doc-base b/debian/libosmoisdn-doc.doc-base
new file mode 100644
index 00000000..bca6e1d0
--- /dev/null
+++ b/debian/libosmoisdn-doc.doc-base
@@ -0,0 +1,7 @@
+Document: libosmoisdn-doc
+Title: Documentation for the libosmoisdn library
+Section: Programming
+
+Format: HTML
+Index: /usr/share/doc/libosmocore/isdn/html/index.html
+Files: /usr/share/doc/libosmocore/isdn/html/*.html
diff --git a/debian/libosmoisdn-doc.install b/debian/libosmoisdn-doc.install
new file mode 100644
index 00000000..9c985503
--- /dev/null
+++ b/debian/libosmoisdn-doc.install
@@ -0,0 +1 @@
+usr/share/doc/libosmocore/isdn/
diff --git a/debian/libosmoisdn0.install b/debian/libosmoisdn0.install
new file mode 100644
index 00000000..691a3e0f
--- /dev/null
+++ b/debian/libosmoisdn0.install
@@ -0,0 +1 @@
+usr/lib/*/libosmoisdn*.so.*
diff --git a/debian/libosmosim-doc.doc-base b/debian/libosmosim-doc.doc-base
new file mode 100644
index 00000000..0c2678db
--- /dev/null
+++ b/debian/libosmosim-doc.doc-base
@@ -0,0 +1,7 @@
+Document: libosmosim-doc
+Title: Documentation for the libosmosim library
+Section: Programming
+
+Format: HTML
+Index: /usr/share/doc/libosmocore/sim/html/index.html
+Files: /usr/share/doc/libosmocore/sim/html/*.html
diff --git a/debian/libosmosim-doc.install b/debian/libosmosim-doc.install
new file mode 100644
index 00000000..2f37b409
--- /dev/null
+++ b/debian/libosmosim-doc.install
@@ -0,0 +1 @@
+usr/share/doc/libosmocore/sim/
diff --git a/debian/libosmousb-doc.doc-base b/debian/libosmousb-doc.doc-base
new file mode 100644
index 00000000..6121918b
--- /dev/null
+++ b/debian/libosmousb-doc.doc-base
@@ -0,0 +1,7 @@
+Document: libosmousb-doc
+Title: Documentation for the libosmousb library
+Section: Programming
+
+Format: HTML
+Index: /usr/share/doc/libosmocore/usb/html/index.html
+Files: /usr/share/doc/libosmocore/usb/html/*.html
diff --git a/debian/libosmousb-doc.install b/debian/libosmousb-doc.install
new file mode 100644
index 00000000..081159a9
--- /dev/null
+++ b/debian/libosmousb-doc.install
@@ -0,0 +1 @@
+usr/share/doc/libosmocore/usb/
diff --git a/debian/libosmovty9.install b/debian/libosmovty13.install
index fbf6a5fa..fbf6a5fa 100644
--- a/debian/libosmovty9.install
+++ b/debian/libosmovty13.install
diff --git a/debian/rules b/debian/rules
index f5b71ebb..f0105b43 100755
--- a/debian/rules
+++ b/debian/rules
@@ -25,8 +25,19 @@ override_dh_install:
override_dh_auto_test:
dh_auto_test || (find . -name testsuite.log -exec cat {} \; ; false)
+# Set --disable-uring for debian 10 and ubuntu 20.04
override_dh_auto_configure:
- dh_auto_configure -- --enable-static --disable-sctp-tests --enable-systemd-logging
+ set -x && \
+ CONFIGURE_FLAGS=" \
+ --disable-sctp-tests \
+ --enable-static \
+ --enable-systemd-logging \
+ "; \
+ distro_v=$$(. /etc/os-release && echo $$VERSION_ID); \
+ if [ "$$distro_v" = 10 ] || [ "$$distro_v" = 20.04 ]; then \
+ CONFIGURE_FLAGS="$$CONFIGURE_FLAGS --disable-uring"; \
+ fi; \
+ dh_auto_configure -- $$CONFIGURE_FLAGS
override_dh_clean:
dh_clean
@@ -40,16 +51,17 @@ override_dh_clean:
$(RM) include/osmocom/core/crc32gen.h
$(RM) include/osmocom/core/crc64gen.h
$(RM) include/osmocom/core/crc8gen.h
- $(RM) src/crc16gen.c
- $(RM) src/crc32gen.c
- $(RM) src/crc64gen.c
- $(RM) src/crc8gen.c
+ $(RM) src/core/crc16gen.c
+ $(RM) src/core/crc32gen.c
+ $(RM) src/core/crc64gen.c
+ $(RM) src/core/crc8gen.c
$(RM) tests/package.m4
$(RM) tests/testsuite
$(RM) -r doc/codec/
$(RM) -r doc/core/
$(RM) -r doc/ctrl/
$(RM) -r doc/gsm/
+ $(RM) -r doc/isdn/
$(RM) -r doc/gb/
$(RM) -r doc/vty/html/
$(RM) -r doc/vty/latex/
diff --git a/include/Makefile.am b/include/Makefile.am
index a2340147..3578a80e 100644
--- a/include/Makefile.am
+++ b/include/Makefile.am
@@ -1,225 +1 @@
-BUILT_SOURCES = osmocom/gsm/gsm0503.h
-
-nobase_include_HEADERS = \
- osmocom/codec/ecu.h \
- osmocom/codec/codec.h \
- osmocom/codec/gsm610_bits.h \
- osmocom/core/application.h \
- osmocom/core/backtrace.h \
- osmocom/core/base64.h \
- osmocom/core/bit16gen.h \
- osmocom/core/bit32gen.h \
- osmocom/core/bit64gen.h \
- osmocom/core/bits.h \
- osmocom/core/bitvec.h \
- osmocom/core/bitcomp.h \
- osmocom/core/byteswap.h \
- osmocom/core/conv.h \
- osmocom/core/counter.h \
- osmocom/core/crc16.h \
- osmocom/core/crc16gen.h \
- osmocom/core/crc32gen.h \
- osmocom/core/crc64gen.h \
- osmocom/core/crc8gen.h \
- osmocom/core/crcgen.h \
- osmocom/core/endian.h \
- osmocom/core/defs.h \
- osmocom/core/exec.h \
- osmocom/core/fsm.h \
- osmocom/core/gsmtap.h \
- osmocom/core/gsmtap_util.h \
- osmocom/core/hash.h \
- osmocom/core/hashtable.h \
- osmocom/core/isdnhdlc.h \
- osmocom/core/it_q.h \
- osmocom/core/linuxlist.h \
- osmocom/core/linuxrbtree.h \
- osmocom/core/log2.h \
- osmocom/core/logging.h \
- osmocom/core/loggingrb.h \
- osmocom/core/stats.h \
- osmocom/core/macaddr.h \
- osmocom/core/msgb.h \
- osmocom/core/panic.h \
- osmocom/core/prbs.h \
- osmocom/core/prim.h \
- osmocom/core/process.h \
- osmocom/core/rate_ctr.h \
- osmocom/core/stat_item.h \
- osmocom/core/stats_tcp.h \
- osmocom/core/select.h \
- osmocom/core/sercomm.h \
- osmocom/core/signal.h \
- osmocom/core/socket.h \
- osmocom/core/statistics.h \
- osmocom/core/strrb.h \
- osmocom/core/talloc.h \
- osmocom/core/tdef.h \
- osmocom/core/thread.h \
- osmocom/core/timer.h \
- osmocom/core/timer_compat.h \
- osmocom/core/utils.h \
- osmocom/core/write_queue.h \
- osmocom/core/sockaddr_str.h \
- osmocom/core/time_cc.h \
- osmocom/core/use_count.h \
- osmocom/crypt/auth.h \
- osmocom/crypt/gprs_cipher.h \
- osmocom/crypt/kdf.h \
- osmocom/crypt/utran_cipher.h \
- osmocom/ctrl/control_cmd.h \
- osmocom/ctrl/control_if.h \
- osmocom/ctrl/ports.h \
- osmocom/gprs/frame_relay.h \
- osmocom/gprs/bssgp_bvc_fsm.h \
- osmocom/gprs/gprs_bssgp.h \
- osmocom/gprs/gprs_bssgp2.h \
- osmocom/gprs/gprs_bssgp_bss.h \
- osmocom/gprs/gprs_bssgp_rim.h \
- osmocom/gprs/gprs_msgb.h \
- osmocom/gprs/gprs_ns.h \
- osmocom/gprs/gprs_ns_frgre.h \
- osmocom/gprs/gprs_ns2.h \
- osmocom/gprs/gprs_rlc.h \
- osmocom/gprs/protocol/gsm_04_60.h \
- osmocom/gprs/protocol/gsm_08_16.h \
- osmocom/gprs/protocol/gsm_08_18.h \
- osmocom/gprs/protocol/gsm_24_301.h \
- osmocom/gsm/a5.h \
- osmocom/gsm/abis_nm.h \
- osmocom/gsm/apn.h \
- osmocom/gsm/bts_features.h \
- osmocom/gsm/cbsp.h \
- osmocom/gsm/comp128.h \
- osmocom/gsm/comp128v23.h \
- osmocom/gsm/bitvec_gsm.h \
- osmocom/gsm/gan.h \
- osmocom/gsm/gsm0341.h \
- osmocom/gsm/gsm0411_smc.h \
- osmocom/gsm/gsm0411_smr.h \
- osmocom/gsm/gsm0411_utils.h \
- osmocom/gsm/gsm0480.h \
- osmocom/gsm/gsm0502.h \
- osmocom/gsm/gsm0503.h \
- osmocom/coding/gsm0503_tables.h \
- osmocom/coding/gsm0503_parity.h \
- osmocom/coding/gsm0503_mapping.h \
- osmocom/coding/gsm0503_interleaving.h \
- osmocom/coding/gsm0503_coding.h \
- osmocom/coding/gsm0503_amr_dtx.h \
- osmocom/gsm/bsslap.h \
- osmocom/gsm/bssmap_le.h \
- osmocom/gsm/gad.h \
- osmocom/gsm/gsm0808.h \
- osmocom/gsm/gsm0808_lcs.h \
- osmocom/gsm/gsm29205.h \
- osmocom/gsm/gsm0808_utils.h \
- osmocom/gsm/gsm23003.h \
- osmocom/gsm/gsm23236.h \
- osmocom/gsm/gsm29118.h \
- osmocom/gsm/gsm48.h \
- osmocom/gsm/gsm48_arfcn_range_encode.h \
- osmocom/gsm/gsm48_ie.h \
- osmocom/gsm/gsm48_rest_octets.h \
- osmocom/gsm/gsm_utils.h \
- osmocom/gsm/gsup.h \
- osmocom/gsm/gsup_sms.h \
- osmocom/gsm/i460_mux.h \
- osmocom/gsm/ipa.h \
- osmocom/gsm/iuup.h \
- osmocom/gsm/lapd_core.h \
- osmocom/gsm/lapdm.h \
- osmocom/gsm/meas_rep.h \
- osmocom/gsm/mncc.h \
- osmocom/gsm/prim.h \
- osmocom/gsm/l1sap.h \
- osmocom/gsm/oap.h \
- osmocom/gsm/oap_client.h \
- osmocom/gsm/protocol/gsm_23_032.h \
- osmocom/gsm/protocol/gsm_03_40.h \
- osmocom/gsm/protocol/gsm_03_41.h \
- osmocom/gsm/protocol/gsm_04_08.h \
- osmocom/gsm/protocol/gsm_04_08_gprs.h \
- osmocom/gsm/protocol/gsm_04_11.h \
- osmocom/gsm/protocol/gsm_04_12.h \
- osmocom/gsm/protocol/gsm_04_14.h \
- osmocom/gsm/protocol/gsm_04_80.h \
- osmocom/gsm/protocol/gsm_08_08.h \
- osmocom/gsm/protocol/gsm_08_58.h \
- osmocom/gsm/protocol/gsm_09_02.h \
- osmocom/gsm/protocol/gsm_12_21.h \
- osmocom/gsm/protocol/gsm_23_003.h \
- osmocom/gsm/protocol/gsm_23_041.h \
- osmocom/gsm/protocol/gsm_25_415.h \
- osmocom/gsm/protocol/gsm_29_118.h \
- osmocom/gsm/protocol/gsm_44_004.h \
- osmocom/gsm/protocol/gsm_44_318.h \
- osmocom/gsm/protocol/gsm_48_049.h \
- osmocom/gsm/protocol/gsm_48_071.h \
- osmocom/gsm/protocol/gsm_49_031.h \
- osmocom/gsm/protocol/ipaccess.h \
- osmocom/gsm/protocol/smpp34_osmocom.h \
- osmocom/gsm/rsl.h \
- osmocom/gsm/rxlev_stat.h \
- osmocom/gsm/sysinfo.h \
- osmocom/gsm/tlv.h \
- osmocom/sim/class_tables.h \
- osmocom/sim/sim.h
-
-if ENABLE_PLUGIN
-nobase_include_HEADERS += osmocom/core/plugin.h
-endif
-
-if ENABLE_MSGFILE
-nobase_include_HEADERS += osmocom/core/msgfile.h
-endif
-
-if ENABLE_SERIAL
-nobase_include_HEADERS += osmocom/core/serial.h
-endif
-
-
-if ENABLE_VTY
-nobase_include_HEADERS += \
- osmocom/vty/buffer.h \
- osmocom/vty/command.h \
- osmocom/vty/logging.h \
- osmocom/vty/stats.h \
- osmocom/vty/misc.h \
- osmocom/vty/telnet_interface.h \
- osmocom/vty/vector.h \
- osmocom/vty/vty.h \
- osmocom/vty/ports.h \
- osmocom/vty/cpu_sched_vty.h \
- osmocom/vty/tdef_vty.h \
- osmocom/ctrl/control_vty.h
-endif
-
-if ENABLE_LIBUSB
-nobase_include_HEADERS += \
- osmocom/usb/libusb.h
-endif
-
-if ENABLE_LIBMNL
-nobase_include_HEADERS += osmocom/core/mnl.h
-endif
-
-noinst_HEADERS = \
- osmocom/gsm/kasumi.h \
- osmocom/gsm/gea.h \
- osmocom/core/logging_internal.h \
- $(NULL)
-
-osmocom/core/bit%gen.h: osmocom/core/bitXXgen.h.tpl
- $(AM_V_GEN)$(MKDIR_P) $(dir $@)
- $(AM_V_GEN)sed -e's/XX/$*/g' $< > $@
-
-osmocom/core/crc%gen.h: osmocom/core/crcXXgen.h.tpl
- $(AM_V_GEN)$(MKDIR_P) $(dir $@)
- $(AM_V_GEN)sed -e's/XX/$*/g' $< > $@
-
-osmocom/gsm/gsm0503.h: $(top_srcdir)/utils/conv_gen.py $(top_srcdir)/utils/conv_codes_gsm.py
- $(AM_V_GEN)python3 $(top_srcdir)/utils/conv_gen.py gen_header gsm \
- --target-path $(builddir)/osmocom/gsm
-
-CLEANFILES = osmocom/gsm/gsm0503.h
+SUBDIRS = osmocom
diff --git a/include/osmocom/Makefile.am b/include/osmocom/Makefile.am
new file mode 100644
index 00000000..33538094
--- /dev/null
+++ b/include/osmocom/Makefile.am
@@ -0,0 +1,13 @@
+SUBDIRS = \
+ core \
+ vty \
+ codec \
+ gsm \
+ isdn \
+ crypt \
+ coding \
+ gprs \
+ ctrl \
+ sim \
+ usb \
+ $(NULL)
diff --git a/include/osmocom/codec/Makefile.am b/include/osmocom/codec/Makefile.am
new file mode 100644
index 00000000..63ae4024
--- /dev/null
+++ b/include/osmocom/codec/Makefile.am
@@ -0,0 +1,7 @@
+osmocodec_HEADERS = \
+ ecu.h \
+ codec.h \
+ gsm610_bits.h \
+ $(NULL)
+
+osmocodecdir = $(includedir)/osmocom/codec
diff --git a/include/osmocom/codec/codec.h b/include/osmocom/codec/codec.h
index 58e3a296..c5981f89 100644
--- a/include/osmocom/codec/codec.h
+++ b/include/osmocom/codec/codec.h
@@ -15,6 +15,10 @@
/* TS 101318 Chapter 5.3: 244 bits + 4bit sig */
#define GSM_EFR_BYTES 31
+/* Number of bytes of an GSM_HR RTP payload */
+#define GSM_HR_BYTES_RTP_RFC5993 (GSM_HR_BYTES + 1)
+#define GSM_HR_BYTES_RTP_TS101318 (GSM_HR_BYTES)
+
extern const uint16_t gsm610_bitorder[]; /* FR */
extern const uint16_t gsm620_unvoiced_bitorder[]; /* HR unvoiced */
extern const uint16_t gsm620_voiced_bitorder[]; /* HR voiced */
@@ -29,6 +33,8 @@ extern const uint16_t gsm690_5_9_bitorder[]; /* AMR 5.9 kbits */
extern const uint16_t gsm690_5_15_bitorder[]; /* AMR 5.15 kbits */
extern const uint16_t gsm690_4_75_bitorder[]; /* AMR 4.75 kbits */
+extern const uint8_t osmo_gsm611_silence_frame[GSM_FR_BYTES];
+
extern const struct value_string osmo_amr_type_names[];
enum osmo_amr_type {
@@ -81,8 +87,53 @@ static inline bool osmo_amr_is_speech(enum osmo_amr_type ft)
}
}
+/* SID ternary classification per GSM 06.31 & 06.81 section 6.1.1 */
+enum osmo_gsm631_sid_class {
+ OSMO_GSM631_SID_CLASS_SPEECH = 0,
+ OSMO_GSM631_SID_CLASS_INVALID = 1,
+ OSMO_GSM631_SID_CLASS_VALID = 2,
+};
+
bool osmo_fr_check_sid(const uint8_t *rtp_payload, size_t payload_len);
bool osmo_hr_check_sid(const uint8_t *rtp_payload, size_t payload_len);
+bool osmo_efr_check_sid(const uint8_t *rtp_payload, size_t payload_len);
+
+enum osmo_gsm631_sid_class osmo_fr_sid_classify(const uint8_t *rtp_payload);
+enum osmo_gsm631_sid_class osmo_efr_sid_classify(const uint8_t *rtp_payload);
+
+/*! Check if given FR codec frame is any kind of SID, valid or invalid
+ * \param[in] rtp_payload Buffer with RTP payload
+ * \returns true if the frame is an "accepted SID frame" in GSM 06.31
+ * definition, false otherwise.
+ */
+static inline bool osmo_fr_is_any_sid(const uint8_t *rtp_payload)
+{
+ enum osmo_gsm631_sid_class sidc;
+
+ sidc = osmo_fr_sid_classify(rtp_payload);
+ return sidc != OSMO_GSM631_SID_CLASS_SPEECH;
+}
+
+/*! Check if given EFR codec frame is any kind of SID, valid or invalid
+ * \param[in] rtp_payload Buffer with RTP payload
+ * \returns true if the frame is an "accepted SID frame" in GSM 06.81
+ * definition, false otherwise.
+ */
+static inline bool osmo_efr_is_any_sid(const uint8_t *rtp_payload)
+{
+ enum osmo_gsm631_sid_class sidc;
+
+ sidc = osmo_efr_sid_classify(rtp_payload);
+ return sidc != OSMO_GSM631_SID_CLASS_SPEECH;
+}
+
+bool osmo_fr_sid_preen(uint8_t *rtp_payload);
+bool osmo_efr_sid_preen(uint8_t *rtp_payload);
+
+void osmo_fr_sid_reset(uint8_t *rtp_payload);
+void osmo_hr_sid_reset(uint8_t *rtp_payload);
+void osmo_efr_sid_reset(uint8_t *rtp_payload);
+
int osmo_amr_rtp_enc(uint8_t *payload, uint8_t cmr, enum osmo_amr_type ft,
enum osmo_amr_quality bfi);
int osmo_amr_rtp_dec(const uint8_t *payload, int payload_len, uint8_t *cmr,
diff --git a/include/osmocom/codec/ecu.h b/include/osmocom/codec/ecu.h
index 668df367..64928609 100644
--- a/include/osmocom/codec/ecu.h
+++ b/include/osmocom/codec/ecu.h
@@ -6,7 +6,7 @@
#include <osmocom/core/defs.h>
#include <osmocom/codec/codec.h>
-/* ECU state for GSM-FR */
+/* ECU state for GSM-FR - deprecated version only! */
struct osmo_ecu_fr_state {
bool subsequent_lost_frame;
uint8_t frame_backup[GSM_FR_BYTES];
@@ -62,12 +62,16 @@ int osmo_ecu_frame_in(struct osmo_ecu_state *st, bool bfi,
/* generate output data for a substitute/erroneous frame */
int osmo_ecu_frame_out(struct osmo_ecu_state *st, uint8_t *frame_out);
+/* is the stream handled by this ECU currently in a DTX pause? */
+bool osmo_ecu_is_dtx_pause(struct osmo_ecu_state *st);
+
struct osmo_ecu_ops {
struct osmo_ecu_state * (*init)(void *ctx, enum osmo_ecu_codec codec);
void (*destroy)(struct osmo_ecu_state *);
int (*frame_in)(struct osmo_ecu_state *st, bool bfi,
const uint8_t *frame, unsigned int frame_bytes);
int (*frame_out)(struct osmo_ecu_state *st, uint8_t *frame_out);
+ bool (*is_dtx_pause)(struct osmo_ecu_state *st);
};
int osmo_ecu_register(const struct osmo_ecu_ops *ops, enum osmo_ecu_codec codec);
diff --git a/include/osmocom/coding/Makefile.am b/include/osmocom/coding/Makefile.am
new file mode 100644
index 00000000..713b1932
--- /dev/null
+++ b/include/osmocom/coding/Makefile.am
@@ -0,0 +1,10 @@
+osmocoding_HEADERS = \
+ gsm0503_tables.h \
+ gsm0503_parity.h \
+ gsm0503_mapping.h \
+ gsm0503_interleaving.h \
+ gsm0503_coding.h \
+ gsm0503_amr_dtx.h \
+ $(NULL)
+
+osmocodingdir = $(includedir)/osmocom/coding
diff --git a/include/osmocom/coding/gsm0503_coding.h b/include/osmocom/coding/gsm0503_coding.h
index 2afa049b..13d53447 100644
--- a/include/osmocom/coding/gsm0503_coding.h
+++ b/include/osmocom/coding/gsm0503_coding.h
@@ -50,6 +50,9 @@ int gsm0503_tch_fr_decode(uint8_t *tch_data, const sbit_t *bursts, int net_order
int gsm0503_tch_hr_encode(ubit_t *bursts, const uint8_t *tch_data, int len);
int gsm0503_tch_hr_decode(uint8_t *tch_data, const sbit_t *bursts, int odd,
+ int *n_errors, int *n_bits_total)
+ OSMO_DEPRECATED("Use gsm0503_tch_hr_decode2() instead");
+int gsm0503_tch_hr_decode2(uint8_t *tch_data, const sbit_t *bursts, int odd,
int *n_errors, int *n_bits_total);
int gsm0503_tch_afs_encode(ubit_t *bursts, const uint8_t *tch_data, int len,
@@ -86,4 +89,36 @@ int gsm0503_rach_ext_decode_ber(uint16_t *ra, const sbit_t *burst, uint8_t bsic,
int gsm0503_sch_encode(ubit_t *burst, const uint8_t *sb_info);
int gsm0503_sch_decode(uint8_t *sb_info, const sbit_t *burst);
+int gsm0503_tch_fr96_encode(ubit_t *bursts, const ubit_t *data);
+int gsm0503_tch_fr96_decode(ubit_t *data, const sbit_t *bursts,
+ int *n_errors, int *n_bits_total);
+
+int gsm0503_tch_fr48_encode(ubit_t *bursts, const ubit_t *data);
+int gsm0503_tch_fr48_decode(ubit_t *data, const sbit_t *bursts,
+ int *n_errors, int *n_bits_total);
+
+int gsm0503_tch_hr48_encode(ubit_t *bursts, const ubit_t *data);
+int gsm0503_tch_hr48_decode(ubit_t *data, const sbit_t *bursts,
+ int *n_errors, int *n_bits_total);
+
+int gsm0503_tch_fr24_encode(ubit_t *bursts, const ubit_t *data);
+int gsm0503_tch_fr24_decode(ubit_t *data, const sbit_t *bursts,
+ int *n_errors, int *n_bits_total);
+
+int gsm0503_tch_hr24_encode(ubit_t *bursts, const ubit_t *data);
+int gsm0503_tch_hr24_decode(ubit_t *data, const sbit_t *bursts,
+ int *n_errors, int *n_bits_total);
+
+int gsm0503_tch_fr144_encode(ubit_t *bursts, const ubit_t *data);
+int gsm0503_tch_fr144_decode(ubit_t *data, const sbit_t *bursts,
+ int *n_errors, int *n_bits_total);
+
+int gsm0503_tch_fr_facch_encode(ubit_t *bursts, const uint8_t *data);
+int gsm0503_tch_fr_facch_decode(uint8_t *data, const sbit_t *bursts,
+ int *n_errors, int *n_bits_total);
+
+int gsm0503_tch_hr_facch_encode(ubit_t *bursts, const uint8_t *data);
+int gsm0503_tch_hr_facch_decode(uint8_t *data, const sbit_t *bursts,
+ int *n_errors, int *n_bits_total);
+
/*! @} */
diff --git a/include/osmocom/coding/gsm0503_interleaving.h b/include/osmocom/coding/gsm0503_interleaving.h
index 05b5e278..fab4d3d0 100644
--- a/include/osmocom/coding/gsm0503_interleaving.h
+++ b/include/osmocom/coding/gsm0503_interleaving.h
@@ -58,4 +58,7 @@ void gsm0503_mcs8_dl_deinterleave(sbit_t *hc, sbit_t *c1, sbit_t *c2,
void gsm0503_mcs8_dl_interleave(const ubit_t *hc, const ubit_t *c1,
const ubit_t *c2, ubit_t *hi, ubit_t *di);
+void gsm0503_tch_f96_deinterleave(sbit_t *cB, const sbit_t *iB);
+void gsm0503_tch_f96_interleave(const ubit_t *cB, ubit_t *iB);
+
/*! @} */
diff --git a/include/osmocom/core/Makefile.am b/include/osmocom/core/Makefile.am
new file mode 100644
index 00000000..e1b52d51
--- /dev/null
+++ b/include/osmocom/core/Makefile.am
@@ -0,0 +1,102 @@
+osmocore_HEADERS = \
+ application.h \
+ backtrace.h \
+ base64.h \
+ bit16gen.h \
+ bit32gen.h \
+ bit64gen.h \
+ bits.h \
+ bitvec.h \
+ bitcomp.h \
+ byteswap.h \
+ conv.h \
+ counter.h \
+ crc16.h \
+ crc16gen.h \
+ crc32gen.h \
+ crc64gen.h \
+ crc8gen.h \
+ crcgen.h \
+ endian.h \
+ defs.h \
+ exec.h \
+ fsm.h \
+ gsmtap.h \
+ gsmtap_util.h \
+ hash.h \
+ hashtable.h \
+ isdnhdlc.h \
+ it_q.h \
+ linuxlist.h \
+ linuxrbtree.h \
+ log2.h \
+ logging.h \
+ loggingrb.h \
+ stats.h \
+ macaddr.h \
+ msgb.h \
+ netdev.h \
+ netns.h \
+ osmo_io.h \
+ panic.h \
+ prbs.h \
+ prim.h \
+ process.h \
+ rate_ctr.h \
+ stat_item.h \
+ stats_tcp.h \
+ select.h \
+ sercomm.h \
+ signal.h \
+ socket.h \
+ statistics.h \
+ strrb.h \
+ talloc.h \
+ tdef.h \
+ thread.h \
+ timer.h \
+ timer_compat.h \
+ tun.h \
+ utils.h \
+ write_queue.h \
+ sockaddr_str.h \
+ time_cc.h \
+ use_count.h \
+ socket_compat.h \
+ $(NULL)
+
+if ENABLE_PLUGIN
+osmocore_HEADERS += plugin.h
+endif
+
+if ENABLE_MSGFILE
+osmocore_HEADERS += msgfile.h
+endif
+
+if ENABLE_SERIAL
+osmocore_HEADERS += serial.h
+endif
+
+if ENABLE_LIBMNL
+osmocore_HEADERS += mnl.h
+endif
+
+osmocoredir = $(includedir)/osmocom/core
+
+noinst_HEADERS = \
+ logging_internal.h \
+ $(NULL)
+
+bit%gen.h: bitXXgen.h.tpl
+ $(AM_V_GEN)$(MKDIR_P) $(dir $@)
+ $(AM_V_GEN)sed -e's/XX/$*/g' $< > $@
+
+crc%gen.h: crcXXgen.h.tpl
+ $(AM_V_GEN)$(MKDIR_P) $(dir $@)
+ $(AM_V_GEN)sed -e's/XX/$*/g' $< > $@
+
+socket_compat.h: socket_compat.h.tpl
+ $(AM_V_GEN)$(MKDIR_P) $(dir $@)
+ $(AM_V_GEN)sed -e's/XX/$(HAVE_SYS_SOCKET_H)/g' $< > $@
+
+EXTRA_DIST = socket_compat.h.tpl
diff --git a/include/osmocom/core/bitvec.h b/include/osmocom/core/bitvec.h
index 70b0e27d..1e2fe7b4 100644
--- a/include/osmocom/core/bitvec.h
+++ b/include/osmocom/core/bitvec.h
@@ -23,7 +23,6 @@
* \file bitvec.h */
#include <stdint.h>
-#include <osmocom/core/talloc.h>
#include <osmocom/core/defs.h>
#include <stdbool.h>
@@ -61,7 +60,7 @@ int bitvec_find_bit_pos(const struct bitvec *bv, unsigned int n, enum bit_value
int bitvec_spare_padding(struct bitvec *bv, unsigned int up_to_bit);
int bitvec_get_bytes(struct bitvec *bv, uint8_t *bytes, unsigned int count);
int bitvec_set_bytes(struct bitvec *bv, const uint8_t *bytes, unsigned int count);
-struct bitvec *bitvec_alloc(unsigned int size, TALLOC_CTX *bvctx);
+struct bitvec *bitvec_alloc(unsigned int size, void *bvctx);
void bitvec_free(struct bitvec *bv);
int bitvec_unhex(struct bitvec *bv, const char *src);
unsigned int bitvec_pack(const struct bitvec *bv, uint8_t *buffer);
diff --git a/include/osmocom/core/counter.h b/include/osmocom/core/counter.h
index 0d56bc4a..7b677cb1 100644
--- a/include/osmocom/core/counter.h
+++ b/include/osmocom/core/counter.h
@@ -52,7 +52,7 @@ void osmo_counter_free(struct osmo_counter *ctr)
int osmo_counters_for_each(int (*handle_counter)(struct osmo_counter *, void *), void *data);
-int osmo_counters_count();
+int osmo_counters_count(void);
struct osmo_counter *osmo_counter_get_by_name(const char *name);
diff --git a/include/osmocom/core/endian.h b/include/osmocom/core/endian.h
index 6107b12f..426ab680 100644
--- a/include/osmocom/core/endian.h
+++ b/include/osmocom/core/endian.h
@@ -1,7 +1,7 @@
/*! \file endian.h
*
* GNU and FreeBSD have various ways to express the
- * endianess but none of them is similiar enough. This
+ * endianness but none of them is similar enough. This
* will create two defines that allows to decide on the
* endian. The following will be defined to either 0 or
* 1 at the end of the file.
diff --git a/include/osmocom/core/gsmtap.h b/include/osmocom/core/gsmtap.h
index 11b2d137..ebb52960 100644
--- a/include/osmocom/core/gsmtap.h
+++ b/include/osmocom/core/gsmtap.h
@@ -49,6 +49,7 @@
#define GSMTAP_TYPE_QC_DIAG 0x11 /* Qualcomm DIAG frame */
#define GSMTAP_TYPE_LTE_NAS 0x12 /* LTE Non-Access Stratum */
#define GSMTAP_TYPE_E1T1 0x13 /* E1/T1 Lines */
+#define GSMTAP_TYPE_GSM_RLP 0x14 /* GSM RLP frames as per 3GPP TS 24.022 */
/* ====== DO NOT MAKE UNAPPROVED MODIFICATIONS HERE ===== */
@@ -179,6 +180,12 @@
#define GSMTAP_E1T1_RAW 0x03 /* raw/transparent B-channel */
#define GSMTAP_E1T1_TRAU16 0x04 /* 16k TRAU frames; sub-slot 0-3 */
#define GSMTAP_E1T1_TRAU8 0x05 /* 8k TRAU frames; sub-slot 0-7 */
+#define GSMTAP_E1T1_V5EF 0x06 /* V5 Envelope Function */
+#define GSMTAP_E1T1_X75 0x07 /* X.75 B-channel data */
+#define GSMTAP_E1T1_V120 0x08 /* V.120 B-channel data */
+#define GSMTAP_E1T1_V110 0x09 /* V.110 B-channel data */
+#define GSMTAP_E1T1_H221 0x0a /* H.221 B-channel data */
+#define GSMTAP_E1T1_PPP 0x0b /* PPP */
/* flags for the ARFCN */
#define GSMTAP_ARFCN_F_PCS 0x8000
diff --git a/include/osmocom/core/gsmtap_util.h b/include/osmocom/core/gsmtap_util.h
index 33ade95c..d24ee95f 100644
--- a/include/osmocom/core/gsmtap_util.h
+++ b/include/osmocom/core/gsmtap_util.h
@@ -24,26 +24,23 @@ struct msgb *gsmtap_makemsg(uint16_t arfcn, uint8_t ts, uint8_t chan_type,
int8_t snr, const uint8_t *data, unsigned int len);
/*! one gsmtap instance */
-struct gsmtap_inst {
- int ofd_wq_mode; /*!< wait queue mode? */
- struct osmo_wqueue wq; /*!< the wait queue */
- struct osmo_fd sink_ofd;/*!< file descriptor */
-};
-
-/*! obtain the file descriptor associated with a gsmtap instance
- * \param[in] gti GSMTAP instance
- * \returns file descriptor of GSMTAP instance */
-static inline int gsmtap_inst_fd(struct gsmtap_inst *gti)
-{
- return gti->wq.bfd.fd;
-}
+struct gsmtap_inst;
+
+int gsmtap_inst_fd(struct gsmtap_inst *gti)
+ OSMO_DEPRECATED("Use gsmtap_inst_fd2() instead");
+
+int gsmtap_inst_fd2(const struct gsmtap_inst *gti);
int gsmtap_source_init_fd(const char *host, uint16_t port);
+int gsmtap_source_init_fd2(const char *local_host, uint16_t local_port, const char *rem_host, uint16_t rem_port);
int gsmtap_source_add_sink_fd(int gsmtap_fd);
struct gsmtap_inst *gsmtap_source_init(const char *host, uint16_t port,
int ofd_wq_mode);
+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);
+
void gsmtap_source_free(struct gsmtap_inst *gti);
int gsmtap_source_add_sink(struct gsmtap_inst *gti);
diff --git a/include/osmocom/core/isdnhdlc.h b/include/osmocom/core/isdnhdlc.h
index 43e6e7ac..c8cfdc3a 100644
--- a/include/osmocom/core/isdnhdlc.h
+++ b/include/osmocom/core/isdnhdlc.h
@@ -22,8 +22,7 @@
* GNU General Public License for more details.
*/
-#ifndef __ISDNHDLC_H__
-#define __ISDNHDLC_H__
+#pragma once
#include <stdint.h>
@@ -76,5 +75,3 @@ extern void osmo_isdnhdlc_out_init(struct osmo_isdnhdlc_vars *hdlc, uint32_t fea
extern int osmo_isdnhdlc_encode(struct osmo_isdnhdlc_vars *hdlc, const uint8_t *src,
uint16_t slen, int *count, uint8_t *dst, int dsize);
-
-#endif /* __ISDNHDLC_H__ */
diff --git a/include/osmocom/core/logging.h b/include/osmocom/core/logging.h
index f1304f59..e8433a14 100644
--- a/include/osmocom/core/logging.h
+++ b/include/osmocom/core/logging.h
@@ -152,7 +152,10 @@ void logp(int subsys, const char *file, int line, int cont, const char *format,
#define DLIUUP -24 /*!< Osmocom IuUP layer */
#define DLPFCP -25 /*!< Osmocom Packet Forwarding Control Protocol */
#define DLCSN1 -26 /*!< CSN.1 (Concrete Syntax Notation 1) codec */
-#define OSMO_NUM_DLIB 26 /*!< Number of logging sub-systems in libraries */
+#define DLM2PA -27 /*!< Osmocom M2PA (libosmo-sigtran) */
+#define DLM2UA -28 /*!< Reserved for future Osmocom M2UA (libosmo-sigtran) */
+#define DLIO -29 /*!< Osmocom IO sub-system */
+#define OSMO_NUM_DLIB 29 /*!< Number of logging sub-systems in libraries */
/* Colors that can be used in log_info_cat.color */
#define OSMO_LOGCOLOR_NORMAL NULL
@@ -397,7 +400,6 @@ void logp2(int subsys, unsigned int level, const char *file,
__attribute__ ((format (printf, 6, 7)));
void logp_stub(const char *file, int line, int cont, const char *format, ...);
int log_init(const struct log_info *inf, void *talloc_ctx);
-int log_initialized(void);
void log_fini(void);
int log_check_level(int subsys, unsigned int level);
diff --git a/include/osmocom/core/msgb.h b/include/osmocom/core/msgb.h
index 117fcb01..5c58c845 100644
--- a/include/osmocom/core/msgb.h
+++ b/include/osmocom/core/msgb.h
@@ -70,6 +70,8 @@ extern int msgb_resize_area(struct msgb *msg, uint8_t *area,
int old_size, int new_size);
extern struct msgb *msgb_copy(const struct msgb *msg, const char *name);
extern struct msgb *msgb_copy_c(const void *ctx, const struct msgb *msg, const char *name);
+extern struct msgb *msgb_copy_resize(const struct msgb *msg, uint16_t new_len, const char *name);
+extern struct msgb *msgb_copy_resize_c(const void *ctx, const struct msgb *msg, uint16_t new_len, const char *name);
static int msgb_test_invariant(const struct msgb *msg) __attribute__((pure));
/*! Free all msgbs from a queue built with msgb_enqueue().
@@ -125,13 +127,13 @@ static inline struct msgb *msgb_dequeue_count(struct llist_head *queue,
#endif
/*! obtain L1 header of msgb */
-#define msgb_l1(m) ((void *)(m->l1h))
+#define msgb_l1(m) ((void *)((m)->l1h))
/*! obtain L2 header of msgb */
-#define msgb_l2(m) ((void *)(m->l2h))
+#define msgb_l2(m) ((void *)((m)->l2h))
/*! obtain L3 header of msgb */
-#define msgb_l3(m) ((void *)(m->l3h))
+#define msgb_l3(m) ((void *)((m)->l3h))
/*! obtain L4 header of msgb */
-#define msgb_l4(m) ((void *)(m->l4h))
+#define msgb_l4(m) ((void *)((m)->l4h))
/*! obtain SMS header of msgb */
#define msgb_sms(m) msgb_l4(m)
@@ -144,6 +146,7 @@ static inline struct msgb *msgb_dequeue_count(struct llist_head *queue,
*/
static inline unsigned int msgb_l1len(const struct msgb *msgb)
{
+ OSMO_ASSERT(msgb->l1h);
return msgb->tail - (uint8_t *)msgb_l1(msgb);
}
@@ -156,6 +159,7 @@ static inline unsigned int msgb_l1len(const struct msgb *msgb)
*/
static inline unsigned int msgb_l2len(const struct msgb *msgb)
{
+ OSMO_ASSERT(msgb->l2h);
return msgb->tail - (uint8_t *)msgb_l2(msgb);
}
@@ -168,6 +172,7 @@ static inline unsigned int msgb_l2len(const struct msgb *msgb)
*/
static inline unsigned int msgb_l3len(const struct msgb *msgb)
{
+ OSMO_ASSERT(msgb->l3h);
return msgb->tail - (uint8_t *)msgb_l3(msgb);
}
@@ -180,7 +185,8 @@ static inline unsigned int msgb_l3len(const struct msgb *msgb)
*/
static inline unsigned int msgb_l4len(const struct msgb *msgb)
{
- return msgb->tail - (uint8_t *)msgb_sms(msgb);
+ OSMO_ASSERT(msgb->l4h);
+ return msgb->tail - (uint8_t *)msgb_l4(msgb);
}
/*! determine the length of the header
@@ -523,7 +529,7 @@ static inline int msgb_l3trim(struct msgb *msg, int l3len)
static inline struct msgb *msgb_alloc_headroom_c(const void *ctx, uint16_t size, uint16_t headroom,
const char *name)
{
- osmo_static_assert(size >= headroom, headroom_bigger);
+ OSMO_ASSERT(size >= headroom);
struct msgb *msg = msgb_alloc_c(ctx, size, name);
if (OSMO_LIKELY(msg))
@@ -545,7 +551,7 @@ static inline struct msgb *msgb_alloc_headroom_c(const void *ctx, uint16_t size,
static inline struct msgb *msgb_alloc_headroom(uint16_t size, uint16_t headroom,
const char *name)
{
- osmo_static_assert(size >= headroom, headroom_bigger);
+ OSMO_ASSERT(size >= headroom);
struct msgb *msg = msgb_alloc(size, name);
if (OSMO_LIKELY(msg))
diff --git a/include/osmocom/core/netdev.h b/include/osmocom/core/netdev.h
new file mode 100644
index 00000000..3238ec33
--- /dev/null
+++ b/include/osmocom/core/netdev.h
@@ -0,0 +1,49 @@
+/*! \file netdev.h
+ * network device (interface) convenience functions. */
+
+#pragma once
+#if (!EMBEDDED)
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/socket.h>
+
+struct osmo_netdev;
+
+typedef int (*osmo_netdev_ifupdown_ind_cb_t)(struct osmo_netdev *netdev, bool ifupdown);
+typedef int (*osmo_netdev_dev_name_chg_cb_t)(struct osmo_netdev *netdev, const char *new_dev_name);
+typedef int (*osmo_netdev_mtu_chg_cb_t)(struct osmo_netdev *netdev, unsigned int new_mtu);
+
+struct osmo_netdev *osmo_netdev_alloc(void *ctx, const char *name);
+void osmo_netdev_free(struct osmo_netdev *netdev);
+
+int osmo_netdev_register(struct osmo_netdev *netdev);
+int osmo_netdev_unregister(struct osmo_netdev *netdev);
+bool osmo_netdev_is_registered(struct osmo_netdev *netdev);
+
+const char *osmo_netdev_get_name(const struct osmo_netdev *netdev);
+
+void osmo_netdev_set_priv_data(struct osmo_netdev *netdev, void *priv_data);
+void *osmo_netdev_get_priv_data(struct osmo_netdev *netdev);
+
+int osmo_netdev_set_ifindex(struct osmo_netdev *netdev, unsigned int ifindex);
+unsigned int osmo_netdev_get_ifindex(const struct osmo_netdev *netdev);
+
+const char *osmo_netdev_get_dev_name(const struct osmo_netdev *netdev);
+
+int osmo_netdev_set_netns_name(struct osmo_netdev *netdev, const char *netns);
+const char *osmo_netdev_get_netns_name(const struct osmo_netdev *netdev);
+
+void osmo_netdev_set_ifupdown_ind_cb(struct osmo_netdev *netdev, osmo_netdev_ifupdown_ind_cb_t ifupdown_ind_cb);
+void osmo_netdev_set_dev_name_chg_cb(struct osmo_netdev *netdev, osmo_netdev_dev_name_chg_cb_t dev_name_chg_cb);
+void osmo_netdev_set_mtu_chg_cb(struct osmo_netdev *netdev, osmo_netdev_mtu_chg_cb_t mtu_chg_cb);
+
+int osmo_netdev_add_addr(struct osmo_netdev *netdev, const struct osmo_sockaddr *addr, uint8_t prefixlen);
+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);
+int osmo_netdev_ifupdown(struct osmo_netdev *netdev, bool ifupdown);
+
+#endif /* (!EMBEDDED) */
+/*! @} */
diff --git a/include/osmocom/core/netns.h b/include/osmocom/core/netns.h
new file mode 100644
index 00000000..5bbf2242
--- /dev/null
+++ b/include/osmocom/core/netns.h
@@ -0,0 +1,24 @@
+/*! \file netns.h
+ * Network namespace convenience functions. */
+
+#pragma once
+#if (!EMBEDDED)
+
+#if defined(__linux__)
+
+#include <signal.h>
+
+struct osmo_netns_switch_state {
+ sigset_t prev_sigmask;
+ int prev_nsfd;
+};
+
+int osmo_netns_open_fd(const char *name);
+int osmo_netns_switch_enter(int nsfd, struct osmo_netns_switch_state *state);
+int osmo_netns_switch_exit(struct osmo_netns_switch_state *state);
+
+
+#endif /* defined(__linux__) */
+
+#endif /* (!EMBEDDED) */
+/*! @} */
diff --git a/include/osmocom/core/osmo_io.h b/include/osmocom/core/osmo_io.h
new file mode 100644
index 00000000..b3d248f2
--- /dev/null
+++ b/include/osmocom/core/osmo_io.h
@@ -0,0 +1,101 @@
+/*! \file osmo_io.h
+ * io(_uring) abstraction osmo fd compatibility
+ */
+
+#pragma once
+
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/core/utils.h>
+
+
+#define LOGPIO(iofd, level, fmt, args...) \
+ LOGP(DLIO, level, "iofd(%s)" fmt, iofd->name, ## args)
+
+struct osmo_io_fd;
+
+enum osmo_io_fd_mode {
+ /*! use read() / write() calls */
+ OSMO_IO_FD_MODE_READ_WRITE,
+ /*! use recvfrom() / sendto() calls */
+ OSMO_IO_FD_MODE_RECVFROM_SENDTO,
+ /*! emulate sctp_recvmsg() and sctp_sendmsg() */
+ OSMO_IO_FD_MODE_SCTP_RECVMSG_SENDMSG,
+};
+
+enum osmo_io_backend {
+ OSMO_IO_BACKEND_POLL,
+ OSMO_IO_BACKEND_IO_URING,
+};
+
+extern const struct value_string osmo_io_backend_names[];
+static inline const char *osmo_io_backend_name(enum osmo_io_backend val)
+{ return get_value_string(osmo_io_backend_names, val); }
+
+struct osmo_io_ops {
+ union {
+ /* mode OSMO_IO_FD_MODE_READ_WRITE: */
+ struct {
+ /*! call-back function when something was read from fd */
+ void (*read_cb)(struct osmo_io_fd *iofd, int res, struct msgb *msg);
+ /*! call-back function when write has completed on fd */
+ void (*write_cb)(struct osmo_io_fd *iofd, int res,
+ struct msgb *msg);
+ /*! call-back function to segment the data at message boundaries.
+ * Needs to return the size of the next message. If it returns
+ * -EAGAIN or a value larger than msgb_length() (message is incomplete)
+ * osmo_io will wait for more data to be read. Other negative values
+ * cause the msg to be discarded.
+ * If a full message was received (segmentation_cb() returns a value <= msgb_length())
+ * the msgb will be trimmed to size by osmo_io and forwarded to the read call-back. Any
+ * parsing done to the msgb by segmentation_cb() will be preserved for the read_cb()
+ * (e.g. setting lxh or msgb->cb). */
+ int (*segmentation_cb)(struct msgb *msg);
+ };
+
+ /* mode OSMO_IO_FD_MODE_RECVFROM_SENDTO: */
+ struct {
+ /*! call-back function emulating recvfrom */
+ void (*recvfrom_cb)(struct osmo_io_fd *iofd, int res,
+ struct msgb *msg,
+ const struct osmo_sockaddr *saddr);
+ /*! call-back function emulating sendto */
+ void (*sendto_cb)(struct osmo_io_fd *iofd, int res,
+ struct msgb *msg,
+ const struct osmo_sockaddr *daddr);
+ };
+ };
+};
+
+void osmo_iofd_init(void);
+
+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);
+int osmo_iofd_register(struct osmo_io_fd *iofd, int fd);
+int osmo_iofd_unregister(struct osmo_io_fd *iofd);
+unsigned int osmo_iofd_txqueue_len(struct osmo_io_fd *iofd);
+void osmo_iofd_txqueue_clear(struct osmo_io_fd *iofd);
+int osmo_iofd_close(struct osmo_io_fd *iofd);
+void osmo_iofd_free(struct osmo_io_fd *iofd);
+
+void osmo_iofd_notify_connected(struct osmo_io_fd *iofd);
+
+int osmo_iofd_write_msgb(struct osmo_io_fd *iofd, struct msgb *msg);
+int osmo_iofd_sendto_msgb(struct osmo_io_fd *iofd, struct msgb *msg, int sendto_flags,
+ const struct osmo_sockaddr *dest);
+
+void osmo_iofd_set_alloc_info(struct osmo_io_fd *iofd, unsigned int size, unsigned int headroom);
+void osmo_iofd_set_txqueue_max_length(struct osmo_io_fd *iofd, unsigned int size);
+void *osmo_iofd_get_data(const struct osmo_io_fd *iofd);
+void osmo_iofd_set_data(struct osmo_io_fd *iofd, void *data);
+
+unsigned int osmo_iofd_get_priv_nr(const struct osmo_io_fd *iofd);
+void osmo_iofd_set_priv_nr(struct osmo_io_fd *iofd, unsigned int priv_nr);
+
+int osmo_iofd_get_fd(const struct osmo_io_fd *iofd);
+const char *osmo_iofd_get_name(const struct osmo_io_fd *iofd);
+void osmo_iofd_set_name(struct osmo_io_fd *iofd, const char *name);
+
+void osmo_iofd_set_ioops(struct osmo_io_fd *iofd, const struct osmo_io_ops *ioops);
diff --git a/include/osmocom/core/prim.h b/include/osmocom/core/prim.h
index 99eabff0..8e6b436d 100644
--- a/include/osmocom/core/prim.h
+++ b/include/osmocom/core/prim.h
@@ -30,7 +30,11 @@ enum osmo_prim_operation {
PRIM_OP_CONFIRM, /*!< confirm */
};
-extern const struct value_string osmo_prim_op_names[5];
+extern const struct value_string osmo_prim_op_names[];
+static inline const char *osmo_prim_operation_name(enum osmo_prim_operation val)
+{
+ return get_value_string(osmo_prim_op_names, val);
+}
/*!< The upper 8 byte of the technology, the lower 24 bits for the SAP */
#define _SAP_GSM_SHIFT 24
diff --git a/include/osmocom/core/rate_ctr.h b/include/osmocom/core/rate_ctr.h
index d944cc0c..0a3eb2ce 100644
--- a/include/osmocom/core/rate_ctr.h
+++ b/include/osmocom/core/rate_ctr.h
@@ -86,6 +86,15 @@ void rate_ctr_group_free(struct rate_ctr_group *grp);
* \param inc quantity to increment \a ctr by */
void rate_ctr_add(struct rate_ctr *ctr, int inc);
+/*! Increment the counter by \a inc
+ * \param ctrg \ref rate_ctr_group of counter
+ * \param idx index into \a ctrg counter group
+ * \param inc quantity to increment \a ctr by */
+static inline void rate_ctr_add2(struct rate_ctr_group *ctrg, unsigned int idx, int inc)
+{
+ rate_ctr_add(rate_ctr_group_get_ctr(ctrg, idx), inc);
+}
+
/*! Increment the counter by 1
* \param ctr \ref rate_ctr to increment */
static inline void rate_ctr_inc(struct rate_ctr *ctr)
diff --git a/include/osmocom/core/select.h b/include/osmocom/core/select.h
index e9f19a56..fc148512 100644
--- a/include/osmocom/core/select.h
+++ b/include/osmocom/core/select.h
@@ -105,8 +105,8 @@ struct osmo_signalfd {
struct osmo_signalfd *
osmo_signalfd_setup(void *ctx, sigset_t set, osmo_signalfd_cb *cb, void *data);
-void osmo_select_shutdown_request();
-int osmo_select_shutdown_requested();
-bool osmo_select_shutdown_done();
+void osmo_select_shutdown_request(void);
+int osmo_select_shutdown_requested(void);
+bool osmo_select_shutdown_done(void);
/*! @} */
diff --git a/include/osmocom/core/sercomm.h b/include/osmocom/core/sercomm.h
index 072f4d9c..38e6271c 100644
--- a/include/osmocom/core/sercomm.h
+++ b/include/osmocom/core/sercomm.h
@@ -2,8 +2,7 @@
* Osmocom Sercomm HDLC (de)multiplex.
*/
-#ifndef _SERCOMM_H
-#define _SERCOMM_H
+#pragma once
#include <osmocom/core/msgb.h>
@@ -110,5 +109,3 @@ static inline struct msgb *osmo_sercomm_alloc_msgb(unsigned int len)
}
/*! @} */
-
-#endif /* _SERCOMM_H */
diff --git a/include/osmocom/core/sockaddr_str.h b/include/osmocom/core/sockaddr_str.h
index f474fa03..c646f499 100644
--- a/include/osmocom/core/sockaddr_str.h
+++ b/include/osmocom/core/sockaddr_str.h
@@ -59,11 +59,16 @@ struct osmo_sockaddr_str {
* printf("got " OSMO_SOCKADDR_STR_FMT "\n", OSMO_SOCKADDR_STR_FMT_ARGS(my_sockaddr_str));
*/
#define OSMO_SOCKADDR_STR_FMT "%s%s%s:%u"
+#define OSMO_SOCKADDR_STR_FMT_ARGS_NOT_NULL(R) \
+ ((R)->af == AF_INET6) ? "[" : "", \
+ (R)->ip, \
+ ((R)->af == AF_INET6) ? "]" : "", \
+ (R)->port
#define OSMO_SOCKADDR_STR_FMT_ARGS(R) \
- ((R) && (R)->af == AF_INET6)? "[" : "", \
- (R)? (R)->ip : "NULL", \
- ((R) && (R)->af == AF_INET6)? "]" : "", \
- (R)? (R)->port : 0
+ ((R) && (R)->af == AF_INET6) ? "[" : "", \
+ (R) ? (R)->ip : "NULL", \
+ ((R) && (R)->af == AF_INET6) ? "]" : "", \
+ (R) ? (R)->port : 0
bool osmo_sockaddr_str_is_set(const struct osmo_sockaddr_str *sockaddr_str);
bool osmo_sockaddr_str_is_nonzero(const struct osmo_sockaddr_str *sockaddr_str);
diff --git a/include/osmocom/core/socket.h b/include/osmocom/core/socket.h
index 183220d5..db55863c 100644
--- a/include/osmocom/core/socket.h
+++ b/include/osmocom/core/socket.h
@@ -14,6 +14,8 @@
#include <arpa/inet.h>
+#include <osmocom/core/defs.h>
+
/*! maximum length of a socket name ("r=1.2.3.4:123<->l=5.6.7.8:987") */
#define OSMO_SOCK_NAME_MAXLEN (2 + INET6_ADDRSTRLEN + 1 + 5 + 3 + 2 + INET6_ADDRSTRLEN + 1 + 5 + 1)
@@ -30,6 +32,57 @@ struct osmo_sockaddr {
} u;
};
+int osmo_sockaddr_is_local(struct sockaddr *addr, unsigned int addrlen);
+int osmo_sockaddr_is_any(const struct osmo_sockaddr *addr);
+
+/*! Return the size of the variant used in the address
+ * NOTE: This does not return the size of the in{,6}_addr, but rather the size of the
+ * surrounding sockaddr_in{,6}.
+ * \param[in] addr the osmo_sockaddr to get the size of
+ * \return the size of the struct variant being used. If the value in sa_family is unsupported it will return
+ * the size of struct osmo_sockaddr. Returns 0 if addr is NULL. This way it can simply be a wrapper for sendto()
+ * which can be called with NULL/0 for dest_addr / addrlen (and then behaves like a send() call).
+ */
+static inline socklen_t osmo_sockaddr_size(const struct osmo_sockaddr *addr)
+{
+ if (!addr)
+ return 0;
+
+ switch (addr->u.sa.sa_family) {
+ case AF_INET:
+ return sizeof(struct sockaddr_in);
+ case AF_INET6:
+ return sizeof(struct sockaddr_in6);
+ default:
+ return sizeof(struct osmo_sockaddr);
+ }
+}
+
+unsigned int osmo_sockaddr_to_str_and_uint(char *addr, unsigned int addr_len, uint16_t *port,
+ const struct sockaddr *sa);
+size_t osmo_sockaddr_in_to_str_and_uint(char *addr, unsigned int addr_len, uint16_t *port,
+ const struct sockaddr_in *sin);
+
+const char *osmo_sockaddr_ntop(const struct sockaddr *sa, char *dst);
+uint16_t osmo_sockaddr_port(const struct sockaddr *sa);
+void osmo_sockaddr_set_port(struct sockaddr *sa, uint16_t port);
+
+int osmo_sockaddr_local_ip(struct osmo_sockaddr *local_ip,
+ const struct osmo_sockaddr *remote_ip);
+int osmo_sockaddr_cmp(const struct osmo_sockaddr *a,
+ const struct osmo_sockaddr *b);
+
+int osmo_sockaddr_to_octets(uint8_t *dst, size_t dst_maxlen, const struct osmo_sockaddr *os);
+int osmo_sockaddr_from_octets(struct osmo_sockaddr *os, const void *src, size_t src_len);
+
+int osmo_sockaddr_netmask_to_prefixlen(const struct osmo_sockaddr *addr);
+
+const char *osmo_sockaddr_to_str(const struct osmo_sockaddr *sockaddr);
+char *osmo_sockaddr_to_str_buf(char *buf, size_t buf_len,
+ const struct osmo_sockaddr *sockaddr);
+int osmo_sockaddr_to_str_buf2(char *buf, size_t buf_len, const struct osmo_sockaddr *sockaddr);
+char *osmo_sockaddr_to_str_c(void *ctx, const struct osmo_sockaddr *sockaddr);
+
/* flags for osmo_sock_init. */
/*! connect the socket to a remote peer */
#define OSMO_SOCK_F_CONNECT (1 << 0)
@@ -63,9 +116,43 @@ int osmo_sock_init2(uint16_t family, uint16_t type, uint8_t proto,
const char *local_host, uint16_t local_port,
const char *remote_host, uint16_t remote_port, unsigned int flags);
+struct osmo_sock_init2_multiaddr_pars {
+ union {
+ struct {
+ uint8_t version; /* set to 0 */
+ struct {
+ bool set;
+ bool abort_on_failure;
+ uint32_t value;
+ } sockopt_auth_supported;
+ struct {
+ bool set;
+ bool abort_on_failure;
+ uint32_t value;
+ } sockopt_asconf_supported;
+ struct {
+ bool set;
+ bool abort_on_failure;
+ bool num_ostreams_present;
+ bool max_instreams_present;
+ bool max_attempts_present;
+ bool max_init_timeo_present;
+ uint16_t num_ostreams_value;
+ uint16_t max_instreams_value;
+ uint16_t max_attempts_value;
+ uint16_t max_init_timeo_value;
+ } sockopt_initmsg;
+ } sctp;
+ };
+};
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);
+ const char **remote_hosts, size_t remote_hosts_cnt, uint16_t remote_port, unsigned int flags)
+ OSMO_DEPRECATED_OUTSIDE("Use osmo_sock_init2_multiaddr2() instead");
+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);
int osmo_sock_init_osa(uint16_t type, uint8_t proto,
const struct osmo_sockaddr *local,
@@ -87,17 +174,6 @@ int osmo_sock_init_osa_ofd(struct osmo_fd *ofd, int type, int proto,
int osmo_sock_init_sa(struct sockaddr *ss, uint16_t type,
uint8_t proto, unsigned int flags);
-int osmo_sockaddr_is_local(struct sockaddr *addr, unsigned int addrlen);
-
-unsigned int osmo_sockaddr_to_str_and_uint(char *addr, unsigned int addr_len, uint16_t *port,
- const struct sockaddr *sa);
-size_t osmo_sockaddr_in_to_str_and_uint(char *addr, unsigned int addr_len, uint16_t *port,
- const struct sockaddr_in *sin);
-
-const char *osmo_sockaddr_ntop(const struct sockaddr *sa, char *dst);
-uint16_t osmo_sockaddr_port(const struct sockaddr *sa);
-void osmo_sockaddr_set_port(struct sockaddr *sa, uint16_t port);
-
int osmo_sock_unix_init(uint16_t type, uint8_t proto,
const char *socket_path, unsigned int flags);
@@ -114,6 +190,8 @@ int osmo_sock_get_local_ip_port(int fd, char *port, size_t len);
int osmo_sock_get_remote_ip(int fd, char *host, size_t len);
int osmo_sock_get_remote_ip_port(int fd, char *port, size_t len);
+int osmo_sock_multiaddr_add_local_addr(int sfd, const char **addrs, size_t addrs_cnt);
+int osmo_sock_multiaddr_del_local_addr(int sfd, const char **addrs, size_t addrs_cnt);
int osmo_sock_mcast_loop_set(int fd, bool enable);
int osmo_sock_mcast_ttl_set(int fd, uint8_t ttl);
@@ -123,20 +201,6 @@ int osmo_sock_mcast_subscribe(int fd, const char *grp_addr);
int osmo_sock_local_ip(char *local_ip, const char *remote_ip);
-int osmo_sockaddr_local_ip(struct osmo_sockaddr *local_ip,
- const struct osmo_sockaddr *remote_ip);
-int osmo_sockaddr_cmp(const struct osmo_sockaddr *a,
- const struct osmo_sockaddr *b);
-
-int osmo_sockaddr_to_octets(uint8_t *dst, size_t dst_maxlen, const struct osmo_sockaddr *os);
-int osmo_sockaddr_from_octets(struct osmo_sockaddr *os, const void *src, size_t src_len);
-
-const char *osmo_sockaddr_to_str(const struct osmo_sockaddr *sockaddr);
-char *osmo_sockaddr_to_str_buf(char *buf, size_t buf_len,
- const struct osmo_sockaddr *sockaddr);
-int osmo_sockaddr_to_str_buf2(char *buf, size_t buf_len, const struct osmo_sockaddr *sockaddr);
-char *osmo_sockaddr_to_str_c(void *ctx, const struct osmo_sockaddr *sockaddr);
-
int osmo_sock_set_dscp(int fd, uint8_t dscp);
int osmo_sock_set_priority(int fd, int prio);
diff --git a/include/osmocom/core/socket_compat.h.tpl b/include/osmocom/core/socket_compat.h.tpl
new file mode 100644
index 00000000..43bee9ee
--- /dev/null
+++ b/include/osmocom/core/socket_compat.h.tpl
@@ -0,0 +1,10 @@
+#define HAVE_STRUCT_SOCKADDR_STORAGE XX
+
+#if HAVE_STRUCT_SOCKADDR_STORAGE
+ #include <sys/socket.h>
+#else
+struct sockaddr_storage {
+ unsigned short ss_family;
+ char __data[128 - sizeof(unsigned short)];
+};
+#endif
diff --git a/include/osmocom/core/strrb.h b/include/osmocom/core/strrb.h
index e3d3201c..92d6a2f2 100644
--- a/include/osmocom/core/strrb.h
+++ b/include/osmocom/core/strrb.h
@@ -26,8 +26,6 @@
#include <stdbool.h>
#include <stdint.h>
-#include <osmocom/core/talloc.h>
-
/*! A structure representing an osmocom string ringbuffer */
#define RB_MAX_MESSAGE_SIZE 240
@@ -38,7 +36,7 @@ struct osmo_strrb {
char **buffer; /*!< storage for 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);
bool osmo_strrb_is_empty(const struct osmo_strrb *rb);
const char *osmo_strrb_get_nth(const struct osmo_strrb *rb,
unsigned int string_index);
diff --git a/include/osmocom/core/tun.h b/include/osmocom/core/tun.h
new file mode 100644
index 00000000..86bd8df0
--- /dev/null
+++ b/include/osmocom/core/tun.h
@@ -0,0 +1,43 @@
+/*! \file tun.h
+ * tunnel network device convenience functions. */
+
+#pragma once
+#if (!EMBEDDED)
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/core/netdev.h>
+
+struct osmo_tundev;
+
+/* callback user gets ownership of the msgb and is expected to free it. */
+typedef int (*osmo_tundev_data_ind_cb_t)(struct osmo_tundev *tun, struct msgb *msg);
+
+struct osmo_tundev *osmo_tundev_alloc(void *ctx, const char *name);
+void osmo_tundev_free(struct osmo_tundev *tundev);
+int osmo_tundev_open(struct osmo_tundev *tundev);
+int osmo_tundev_close(struct osmo_tundev *tundev);
+bool osmo_tundev_is_open(struct osmo_tundev *tundev);
+
+void osmo_tundev_set_priv_data(struct osmo_tundev *tundev, void *priv_data);
+void *osmo_tundev_get_priv_data(struct osmo_tundev *tundev);
+
+void osmo_tundev_set_data_ind_cb(struct osmo_tundev *tundev, osmo_tundev_data_ind_cb_t data_ind_cb);
+
+const char *osmo_tundev_get_name(const struct osmo_tundev *tundev);
+
+int osmo_tundev_set_dev_name(struct osmo_tundev *tundev, const char *dev_name);
+const char *osmo_tundev_get_dev_name(const struct osmo_tundev *tundev);
+
+int osmo_tundev_set_netns_name(struct osmo_tundev *tundev, const char *netns);
+const char *osmo_tundev_get_netns_name(const struct osmo_tundev *tundev);
+
+struct osmo_netdev *osmo_tundev_get_netdev(struct osmo_tundev *tundev);
+
+int osmo_tundev_send(struct osmo_tundev *tundev, struct msgb *msg);
+
+#endif /* (!EMBEDDED) */
+/*! @} */
diff --git a/include/osmocom/core/utils.h b/include/osmocom/core/utils.h
index ba2cafbe..ee7cfa49 100644
--- a/include/osmocom/core/utils.h
+++ b/include/osmocom/core/utils.h
@@ -33,7 +33,7 @@
/*! Make a value_string entry from an enum value name */
#define OSMO_VALUE_STRING(x) { x, #x }
/*! Number of bytes necessary to store given BITS */
-#define OSMO_BYTES_FOR_BITS(BITS) ((BITS + 8 - 1) / 8)
+#define OSMO_BYTES_FOR_BITS(BITS) (((BITS) + 7) / 8)
/*! Copy a C-string into a sized buffer using sizeof to detect buffer's size */
#define OSMO_STRLCPY_ARRAY(array, src) osmo_strlcpy(array, src, sizeof(array))
@@ -182,6 +182,18 @@ int osmo_print_n(char *buf, size_t bufsize, const char *str, size_t n);
uint32_t osmo_isqrt32(uint32_t x);
+/*! Floored Modulo (See also: Daan Leijen, Division and Modulus for Computer Scientists).
+ * \param[in] x dividend.
+ * \param[in] y divisor.
+ * \returns remainder of x divided by y. */
+#define OSMO_MOD_FLR(x, y) (((x) > 0 && (y) < 0) || ((x) < 0 && (y) > 0) ? (x) % (y) + (y) : (x) % (y))
+
+/*! Euclidean Modulo (See also: Daan Leijen, Division and Modulus for Computer Scientists).
+ * \param[in] x dividend.
+ * \param[in] y divisor.
+ * \returns remainder of x divided by y. */
+#define OSMO_MOD_EUC(x, y) ((x) % (y) < 0 ? (y) > 0 ? (x) % (y) + (y) : (x) % (y) - (y) : (x) % (y))
+
char osmo_luhn(const char* in, int in_len);
/*! State for OSMO_STRBUF_APPEND() and OSMO_STRBUF_PRINTF(). See there for examples. */
diff --git a/include/osmocom/core/write_queue.h b/include/osmocom/core/write_queue.h
index 6cb0a6b7..fe762829 100644
--- a/include/osmocom/core/write_queue.h
+++ b/include/osmocom/core/write_queue.h
@@ -50,6 +50,7 @@ void osmo_wqueue_init(struct osmo_wqueue *queue, int max_length);
void osmo_wqueue_clear(struct osmo_wqueue *queue);
int osmo_wqueue_enqueue(struct osmo_wqueue *queue, struct msgb *data);
int osmo_wqueue_enqueue_quiet(struct osmo_wqueue *queue, struct msgb *data);
+size_t osmo_wqueue_set_maxlen(struct osmo_wqueue *queue, unsigned int len);
int osmo_wqueue_bfd_cb(struct osmo_fd *fd, unsigned int what);
/*! @} */
diff --git a/include/osmocom/crypt/Makefile.am b/include/osmocom/crypt/Makefile.am
new file mode 100644
index 00000000..fa391bb3
--- /dev/null
+++ b/include/osmocom/crypt/Makefile.am
@@ -0,0 +1,8 @@
+osmocrypt_HEADERS = \
+ auth.h \
+ gprs_cipher.h \
+ kdf.h \
+ utran_cipher.h \
+ $(NULL)
+
+osmocryptdir = $(includedir)/osmocom/crypt
diff --git a/include/osmocom/crypt/auth.h b/include/osmocom/crypt/auth.h
index c653b616..1499ef85 100644
--- a/include/osmocom/crypt/auth.h
+++ b/include/osmocom/crypt/auth.h
@@ -30,12 +30,41 @@ enum osmo_auth_algo {
OSMO_AUTH_ALG_COMP128v1,
OSMO_AUTH_ALG_COMP128v2,
OSMO_AUTH_ALG_COMP128v3,
- OSMO_AUTH_ALG_XOR,
+ OSMO_AUTH_ALG_XOR_3G,
OSMO_AUTH_ALG_MILENAGE,
+ OSMO_AUTH_ALG_XOR_2G,
+ OSMO_AUTH_ALG_TUAK,
_OSMO_AUTH_ALG_NUM,
};
+/* Backwards-compatibility. We used to call XOR-3G just "XOR" which became ambiguous when
+ * we started to add XOR-2G support. */
+#define OSMO_AUTH_ALG_XOR OSMO_AUTH_ALG_XOR_3G
/*! permanent (secret) subscriber auth data */
+struct osmo_sub_auth_data2 {
+ enum osmo_sub_auth_type type;
+ enum osmo_auth_algo algo;
+ union {
+ struct {
+ /* See 3GPP TS 33.102 Section 9.3.7 Length of authentication parameters */
+ uint8_t opc[32]; /*!< operator invariant value */
+ uint8_t opc_len; /*!< OPc length (in bytes): 16 or 32 */
+ uint8_t k[32]; /*!< secret key of the subscriber */
+ uint8_t k_len; /*!< K length (in bytes): 16 or 32 */
+ uint8_t amf[2];
+ uint64_t sqn; /*!< sequence number (in: prev sqn; out: used sqn) */
+ int opc_is_op; /*!< is the OPC field OPC (0) or OP (1) ? */
+ unsigned int ind_bitlen; /*!< nr of bits not in SEQ, only SQN */
+ unsigned int ind; /*!< which IND slot to use an SQN from */
+ uint64_t sqn_ms; /*!< sqn from AUTS (output value only) */
+ } umts;
+ struct {
+ uint8_t ki[OSMO_A5_MAX_KEY_LEN_BYTES]; /*!< secret key */
+ } gsm;
+ } u;
+};
+
+/* deprecated older structure without support for 32-byte K/OP[c] */
struct osmo_sub_auth_data {
enum osmo_sub_auth_type type;
enum osmo_auth_algo algo;
@@ -63,7 +92,7 @@ struct osmo_auth_vector {
uint8_t ck[OSMO_A5_MAX_KEY_LEN_BYTES]; /*!< ciphering key */
uint8_t ik[OSMO_A5_MAX_KEY_LEN_BYTES]; /*!< integrity key */
uint8_t res[16]; /*!< authentication result */
- uint8_t res_len; /*!< length (in bytes) of res */
+ uint8_t res_len; /*!< length (in bytes) of res: 4..16 bytes */
uint8_t kc[8]; /*!< Kc for GSM encryption (A5) */
uint8_t sres[4]; /*!< authentication result for GSM */
uint32_t auth_types; /*!< bitmask of OSMO_AUTH_TYPE_* */
@@ -78,22 +107,32 @@ struct osmo_auth_impl {
/*! callback for generate authentication vectors */
int (*gen_vec)(struct osmo_auth_vector *vec,
- struct osmo_sub_auth_data *aud,
+ struct osmo_sub_auth_data2 *aud,
const uint8_t *_rand);
- /* callback for generationg auth vectors + re-sync */
+ /*! callback for generating auth vectors + re-sync */
int (*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);
};
int osmo_auth_gen_vec(struct osmo_auth_vector *vec,
- struct osmo_sub_auth_data *aud, const uint8_t *_rand);
+ struct osmo_sub_auth_data *aud, const uint8_t *_rand)
+ OSMO_DEPRECATED_OUTSIDE("Use osmo_auth_gen_vec2 instead");
+
+int osmo_auth_gen_vec2(struct osmo_auth_vector *vec,
+ struct osmo_sub_auth_data2 *aud, const uint8_t *_rand);
int osmo_auth_gen_vec_auts(struct osmo_auth_vector *vec,
struct osmo_sub_auth_data *aud,
const uint8_t *auts, const uint8_t *rand_auts,
+ const uint8_t *_rand)
+ OSMO_DEPRECATED_OUTSIDE("Use osmo_auth_gen_vec_auts2 instead");
+
+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);
int osmo_auth_register(struct osmo_auth_impl *impl);
@@ -106,5 +145,6 @@ const char *osmo_auth_alg_name(enum osmo_auth_algo alg);
enum osmo_auth_algo osmo_auth_alg_parse(const char *name);
void osmo_auth_c3(uint8_t kc[], const uint8_t ck[], const uint8_t ik[]);
+void osmo_auth_c2(uint8_t sres[4], const uint8_t *res, size_t res_len, uint8_t sres_deriv_func);
/* @} */
diff --git a/include/osmocom/ctrl/Makefile.am b/include/osmocom/ctrl/Makefile.am
new file mode 100644
index 00000000..416e3ab4
--- /dev/null
+++ b/include/osmocom/ctrl/Makefile.am
@@ -0,0 +1,13 @@
+osmoctrl_HEADERS = \
+ control_cmd.h \
+ control_if.h \
+ ports.h \
+ $(NULL)
+
+if ENABLE_VTY
+osmoctrl_HEADERS += \
+ control_vty.h \
+ $(NULL)
+endif
+
+osmoctrldir = $(includedir)/osmocom/ctrl
diff --git a/include/osmocom/ctrl/control_cmd.h b/include/osmocom/ctrl/control_cmd.h
index 276a7def..1451ffb8 100644
--- a/include/osmocom/ctrl/control_cmd.h
+++ b/include/osmocom/ctrl/control_cmd.h
@@ -234,9 +234,7 @@ static int set_##cmdname(struct ctrl_cmd *cmd, void *_data) \
* \param[in] cmdname symbol name of the command related function
* \param[in] cmdstr string name exposed on CTRL
* \param[in] dtype name of outer struct of user data
- * \param[in] element name of field within \a dtype
- * \param[in] min minimum permitted integer value
- * \param[in] max maximum permitted integer value */
+ * \param[in] element name of field within \a dtype */
#define CTRL_CMD_DEFINE_STRING(cmdname, cmdstr, dtype, element) \
CTRL_HELPER_GET_STRING(cmdname, dtype, element) \
CTRL_HELPER_SET_STRING(cmdname, dtype, element) \
diff --git a/include/osmocom/ctrl/control_if.h b/include/osmocom/ctrl/control_if.h
index b73296f6..98cd100f 100644
--- a/include/osmocom/ctrl/control_if.h
+++ b/include/osmocom/ctrl/control_if.h
@@ -36,19 +36,20 @@ struct ctrl_handle *ctrl_handle_alloc2(void *ctx, void *data,
unsigned int node_count);
struct ctrl_handle *ctrl_interface_setup(void *data, uint16_t port,
ctrl_cmd_lookup lookup);
+struct ctrl_handle *ctrl_interface_setup2(void *data, uint16_t default_port, ctrl_cmd_lookup lookup,
+ unsigned int node_count);
struct ctrl_handle *ctrl_interface_setup_dynip(void *data,
const char *bind_addr,
uint16_t port,
- ctrl_cmd_lookup lookup);
+ ctrl_cmd_lookup lookup) OSMO_DEPRECATED_OUTSIDE_LIBOSMOCORE;
struct ctrl_handle *ctrl_interface_setup_dynip2(void *data,
const char *bind_addr,
uint16_t port,
ctrl_cmd_lookup lookup,
- unsigned int node_count);
+ unsigned int node_count) OSMO_DEPRECATED_OUTSIDE_LIBOSMOCORE;
struct ctrl_connection *osmo_ctrl_conn_alloc(void *ctx, void *data);
int ctrl_cmd_handle(struct ctrl_handle *ctrl, struct ctrl_cmd *cmd, void *data);
struct ctrl_cmd *ctrl_cmd_exec_from_string(struct ctrl_handle *ch, const char *cmdstr);
int ctrl_lookup_register(ctrl_cmd_lookup lookup);
-
int ctrl_handle_msg(struct ctrl_handle *ctrl, struct ctrl_connection *ccon, struct msgb *msg);
diff --git a/include/osmocom/ctrl/control_vty.h b/include/osmocom/ctrl/control_vty.h
index af9ee991..a6cd012e 100644
--- a/include/osmocom/ctrl/control_vty.h
+++ b/include/osmocom/ctrl/control_vty.h
@@ -2,6 +2,8 @@
#pragma once
+#include <stdint.h>
+
/* Add the 'ctrl' section to VTY, containing the 'bind' command. */
int ctrl_vty_init(void *ctx);
@@ -9,3 +11,6 @@ int ctrl_vty_init(void *ctx);
* This should be fed to ctrl_interface_setup() once the configuration has been
* read. */
const char *ctrl_vty_get_bind_addr(void);
+
+/* Returns configured port passed to the 'line ctrl'/'bind' command or default_port. */
+uint16_t ctrl_vty_get_bind_port(uint16_t default_port);
diff --git a/include/osmocom/gprs/Makefile.am b/include/osmocom/gprs/Makefile.am
new file mode 100644
index 00000000..289be578
--- /dev/null
+++ b/include/osmocom/gprs/Makefile.am
@@ -0,0 +1,17 @@
+SUBDIRS = protocol
+
+osmogprs_HEADERS = \
+ frame_relay.h \
+ bssgp_bvc_fsm.h \
+ gprs_bssgp.h \
+ gprs_bssgp2.h \
+ gprs_bssgp_bss.h \
+ gprs_bssgp_rim.h \
+ gprs_msgb.h \
+ gprs_ns.h \
+ gprs_ns_frgre.h \
+ gprs_ns2.h \
+ gprs_rlc.h \
+ $(NULL)
+
+osmogprsdir = $(includedir)/osmocom/gprs
diff --git a/include/osmocom/gprs/gprs_bssgp.h b/include/osmocom/gprs/gprs_bssgp.h
index 09f640dc..6c043327 100644
--- a/include/osmocom/gprs/gprs_bssgp.h
+++ b/include/osmocom/gprs/gprs_bssgp.h
@@ -235,7 +235,7 @@ int bssgp_fc_in(struct bssgp_flow_control *fc, struct msgb *msg,
int bssgp_fc_ms_init(struct bssgp_flow_control *fc_ms, uint16_t bvci,
uint16_t nsei, uint32_t max_queue_depth);
-void bssgp_flush_all_queues();
+void bssgp_flush_all_queues(void);
void bssgp_fc_flush_queue(struct bssgp_flow_control *fc);
/* gprs_bssgp_vty.c */
diff --git a/include/osmocom/gprs/gprs_bssgp2.h b/include/osmocom/gprs/gprs_bssgp2.h
index 3b5b6e82..53e76e3a 100644
--- a/include/osmocom/gprs/gprs_bssgp2.h
+++ b/include/osmocom/gprs/gprs_bssgp2.h
@@ -59,6 +59,8 @@ struct msgb *bssgp2_enc_bvc_reset(uint16_t bvci, enum gprs_bssgp_cause cause,
struct msgb *bssgp2_enc_bvc_reset_ack(uint16_t bvci, const struct gprs_ra_id *ra_id, uint16_t cell_id,
const uint8_t *feat_bm, const uint8_t *ext_feat_bm);
+struct msgb *bssgp2_enc_flush_ll(uint32_t tlli, uint16_t old_bvci,
+ const uint16_t *new_bvci, const uint16_t *nsei);
struct msgb *bssgp2_enc_status(uint8_t cause, const uint16_t *bvci, const struct msgb *orig_msg, uint16_t max_pdu_len);
diff --git a/include/osmocom/gprs/gprs_bssgp_rim.h b/include/osmocom/gprs/gprs_bssgp_rim.h
index 5f397c98..10ea58bd 100644
--- a/include/osmocom/gprs/gprs_bssgp_rim.h
+++ b/include/osmocom/gprs/gprs_bssgp_rim.h
@@ -69,6 +69,7 @@ struct bssgp_rim_routing_info {
char *bssgp_rim_ri_name_buf(char *buf, size_t buf_len, const struct bssgp_rim_routing_info *ri);
const char *bssgp_rim_ri_name(const struct bssgp_rim_routing_info *ri);
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);
int bssgp_create_rim_ri(uint8_t *buf, const struct bssgp_rim_routing_info *ri);
/* 3GPP TS 48.018, table 11.3.63.1.1: RAN-INFORMATION-REQUEST Application Container coding for NACC */
@@ -270,3 +271,4 @@ int bssgp_parse_rim_pdu(struct bssgp_ran_information_pdu *pdu, const struct msgb
struct msgb *bssgp_encode_rim_pdu(const struct bssgp_ran_information_pdu *pdu);
int bssgp_tx_rim(const struct bssgp_ran_information_pdu *pdu, uint16_t nsei);
+int bssgp_tx_rim_encoded(struct msgb *msg, uint16_t nsei);
diff --git a/include/osmocom/gprs/gprs_ns2.h b/include/osmocom/gprs/gprs_ns2.h
index a9c144cc..7c7e2211 100644
--- a/include/osmocom/gprs/gprs_ns2.h
+++ b/include/osmocom/gprs/gprs_ns2.h
@@ -249,7 +249,7 @@ int gprs_ns2_ip_bind_set_dscp(struct gprs_ns2_vc_bind *bind, int dscp);
int gprs_ns2_ip_bind_set_priority(struct gprs_ns2_vc_bind *bind, uint8_t priority);
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);
int gprs_ns2_frgre_bind(struct gprs_ns2_inst *nsi,
const char *name,
diff --git a/include/osmocom/gprs/gprs_rlc.h b/include/osmocom/gprs/gprs_rlc.h
index b74f9e40..060424ae 100644
--- a/include/osmocom/gprs/gprs_rlc.h
+++ b/include/osmocom/gprs/gprs_rlc.h
@@ -1,54 +1,4 @@
-/*! \file gprs_rlc.h */
-
#pragma once
-#include <stdint.h>
-
-/*! Structure for CPS coding and puncturing scheme (TS 04.60 10.4.8a) */
-struct egprs_cps {
- uint8_t bits;
- uint8_t mcs;
- uint8_t p[2];
-};
-
-/*! CPS puncturing table selection (TS 04.60 10.4.8a) */
-enum egprs_cps_punc {
- EGPRS_CPS_P1,
- EGPRS_CPS_P2,
- EGPRS_CPS_P3,
- EGPRS_CPS_NONE = -1,
-};
-
-/*! EGPRS header types (TS 04.60 10.0a.2) */
-enum egprs_hdr_type {
- EGPRS_HDR_TYPE1,
- EGPRS_HDR_TYPE2,
- EGPRS_HDR_TYPE3,
-};
-
-enum osmo_gprs_cs {
- OSMO_GPRS_CS_NONE,
- OSMO_GPRS_CS1,
- OSMO_GPRS_CS2,
- OSMO_GPRS_CS3,
- OSMO_GPRS_CS4,
- OSMO_GPRS_MCS1,
- OSMO_GPRS_MCS2,
- OSMO_GPRS_MCS3,
- OSMO_GPRS_MCS4,
- OSMO_GPRS_MCS5,
- OSMO_GPRS_MCS6,
- OSMO_GPRS_MCS7,
- OSMO_GPRS_MCS8,
- OSMO_GPRS_MCS9,
- _NUM_OSMO_GPRS_CS
-};
-
-int egprs_get_cps(struct egprs_cps *cps, uint8_t type, uint8_t bits);
-
-int osmo_gprs_ul_block_size_bits(enum osmo_gprs_cs cs);
-int osmo_gprs_dl_block_size_bits(enum osmo_gprs_cs cs);
-int osmo_gprs_ul_block_size_bytes(enum osmo_gprs_cs cs);
-int osmo_gprs_dl_block_size_bytes(enum osmo_gprs_cs cs);
-enum osmo_gprs_cs osmo_gprs_ul_cs_by_block_bytes(uint8_t block_size);
-enum osmo_gprs_cs osmo_gprs_dl_cs_by_block_bytes(uint8_t block_size);
+#pragma message "Header osmocom/gprs/gprs_rlc.h is deprecated, include osmocom/gsm/protocol/gsm_44_060.h instead"
+#include <osmocom/gsm/protocol/gsm_44_060.h>
diff --git a/include/osmocom/gprs/protocol/Makefile.am b/include/osmocom/gprs/protocol/Makefile.am
new file mode 100644
index 00000000..e69527d0
--- /dev/null
+++ b/include/osmocom/gprs/protocol/Makefile.am
@@ -0,0 +1,8 @@
+osmogprsproto_HEADERS = \
+ gsm_04_60.h \
+ gsm_08_16.h \
+ gsm_08_18.h \
+ gsm_24_301.h \
+ $(NULL)
+
+osmogprsprotodir = $(includedir)/osmocom/gprs/protocol
diff --git a/include/osmocom/gprs/protocol/gsm_04_60.h b/include/osmocom/gprs/protocol/gsm_04_60.h
index ed63fe00..c2c11a7d 100644
--- a/include/osmocom/gprs/protocol/gsm_04_60.h
+++ b/include/osmocom/gprs/protocol/gsm_04_60.h
@@ -1,201 +1,4 @@
-/*! \file gsm_04_60.h
- * General Packet Radio Service (GPRS).
- * Radio Link Control / Medium Access Control (RLC/MAC) protocol
- * 3GPP TS 04.60 version 8.27.0 Release 1999
- */
-
#pragma once
-#include <stdint.h>
-#include <osmocom/core/endian.h>
-
-/* TS 04.60 10.3a.4.1.1 */
-struct gprs_rlc_ul_header_egprs_1 {
-#if OSMO_IS_LITTLE_ENDIAN
- uint8_t r:1,
- si:1,
- cv:4,
- tfi_hi:2;
- uint8_t tfi_lo:3,
- bsn1_hi:5;
- uint8_t bsn1_lo:6,
- bsn2_hi:2;
- uint8_t bsn2_lo:8;
- uint8_t cps:5,
- rsb:1,
- pi:1,
- spare_hi:1;
- uint8_t spare_lo:6,
- dummy:2;
-#elif OSMO_IS_BIG_ENDIAN
-/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianess.py) */
- uint8_t tfi_hi:2, cv:4, si:1, r:1;
- uint8_t bsn1_hi:5, tfi_lo:3;
- uint8_t bsn2_hi:2, bsn1_lo:6;
- uint8_t bsn2_lo:8;
- uint8_t spare_hi:1, pi:1, rsb:1, cps:5;
- uint8_t dummy:2, spare_lo:6;
-#endif
-} __attribute__ ((packed));
-
-/* TS 04.60 10.3a.4.2.1 */
-struct gprs_rlc_ul_header_egprs_2 {
-#if OSMO_IS_LITTLE_ENDIAN
- uint8_t r:1,
- si:1,
- cv:4,
- tfi_hi:2;
- uint8_t tfi_lo:3,
- bsn1_hi:5;
- uint8_t bsn1_lo:6,
- cps_hi:2;
- uint8_t cps_lo:1,
- rsb:1,
- pi:1,
- spare_hi:5;
- uint8_t spare_lo:5,
- dummy:3;
-#elif OSMO_IS_BIG_ENDIAN
-/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianess.py) */
- uint8_t tfi_hi:2, cv:4, si:1, r:1;
- uint8_t bsn1_hi:5, tfi_lo:3;
- uint8_t cps_hi:2, bsn1_lo:6;
- uint8_t spare_hi:5, pi:1, rsb:1, cps_lo:1;
- uint8_t dummy:3, spare_lo:5;
-#endif
-} __attribute__ ((packed));
-
-/* TS 04.60 10.3a.4.3.1 */
-struct gprs_rlc_ul_header_egprs_3 {
-#if OSMO_IS_LITTLE_ENDIAN
- uint8_t r:1,
- si:1,
- cv:4,
- tfi_hi:2;
- uint8_t tfi_lo:3,
- bsn1_hi:5;
- uint8_t bsn1_lo:6,
- cps_hi:2;
- uint8_t cps_lo:2,
- spb:2,
- rsb:1,
- pi:1,
- spare:1,
- dummy:1;
-#elif OSMO_IS_BIG_ENDIAN
-/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianess.py) */
- uint8_t tfi_hi:2, cv:4, si:1, r:1;
- uint8_t bsn1_hi:5, tfi_lo:3;
- uint8_t cps_hi:2, bsn1_lo:6;
- uint8_t dummy:1, spare:1, pi:1, rsb:1, spb:2, cps_lo:2;
-#endif
-} __attribute__ ((packed));
-
-struct gprs_rlc_dl_header_egprs_1 {
-#if OSMO_IS_LITTLE_ENDIAN
- uint8_t usf:3,
- es_p:2,
- rrbp:2,
- tfi_hi:1;
- uint8_t tfi_lo:4,
- pr:2,
- bsn1_hi:2;
- uint8_t bsn1_mid:8;
- uint8_t bsn1_lo:1,
- bsn2_hi:7;
- uint8_t bsn2_lo:3,
- cps:5;
-#elif OSMO_IS_BIG_ENDIAN
-/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianess.py) */
- uint8_t tfi_hi:1, rrbp:2, es_p:2, usf:3;
- uint8_t bsn1_hi:2, pr:2, tfi_lo:4;
- uint8_t bsn1_mid:8;
- uint8_t bsn2_hi:7, bsn1_lo:1;
- uint8_t cps:5, bsn2_lo:3;
-#endif
-} __attribute__ ((packed));
-
-struct gprs_rlc_dl_header_egprs_2 {
-#if OSMO_IS_LITTLE_ENDIAN
- uint8_t usf:3,
- es_p:2,
- rrbp:2,
- tfi_hi:1;
- uint8_t tfi_lo:4,
- pr:2,
- bsn1_hi:2;
- uint8_t bsn1_mid:8;
- uint8_t bsn1_lo:1,
- cps:3,
- dummy:4;
-#elif OSMO_IS_BIG_ENDIAN
-/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianess.py) */
- uint8_t tfi_hi:1, rrbp:2, es_p:2, usf:3;
- uint8_t bsn1_hi:2, pr:2, tfi_lo:4;
- uint8_t bsn1_mid:8;
- uint8_t dummy:4, cps:3, bsn1_lo:1;
-#endif
-} __attribute__ ((packed));
-
-struct gprs_rlc_dl_header_egprs_3 {
-#if OSMO_IS_LITTLE_ENDIAN
- uint8_t usf:3,
- es_p:2,
- rrbp:2,
- tfi_hi:1;
- uint8_t tfi_lo:4,
- pr:2,
- bsn1_hi:2;
- uint8_t bsn1_mid:8;
- uint8_t bsn1_lo:1,
- cps:4,
- spb:2,
- dummy:1;
-#elif OSMO_IS_BIG_ENDIAN
-/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianess.py) */
- uint8_t tfi_hi:1, rrbp:2, es_p:2, usf:3;
- uint8_t bsn1_hi:2, pr:2, tfi_lo:4;
- uint8_t bsn1_mid:8;
- uint8_t dummy:1, spb:2, cps:4, bsn1_lo:1;
-#endif
-} __attribute__ ((packed));
-
-/* TS 03.60 Chapter 6.3.3.1: Network Mode of Operation */
-enum osmo_gprs_nmo {
- GPRS_NMO_I = 0, /* CS pagin on GPRS paging or traffic channel */
- GPRS_NMO_II = 1, /* all paging on CCCH */
- GPRS_NMO_III = 2, /* no paging coordination */
-};
-
-/* TS 44.060 12.24 */
-struct osmo_gprs_cell_options {
- enum osmo_gprs_nmo nmo;
- /* T3168: wait for packet uplink assignment message */
- uint32_t t3168; /* in milliseconds */
- /* T3192: wait for release of the TBF after reception of the final block */
- uint32_t t3192; /* in milliseconds */
- uint32_t drx_timer_max;/* in seconds */
- uint32_t bs_cv_max;
- uint8_t supports_egprs_11bit_rach;
- bool ctrl_ack_type_use_block; /* use PACKET CONTROL ACKNOWLEDGMENT */
-
- uint8_t ext_info_present;
- struct {
- uint8_t egprs_supported;
- uint8_t use_egprs_p_ch_req;
- uint8_t bep_period;
- uint8_t pfc_supported;
- uint8_t dtm_supported;
- uint8_t bss_paging_coordination;
- bool ccn_active;
- } ext_info;
-};
-
-/* TS 04.60 Table 12.9.2 */
-struct osmo_gprs_power_ctrl_pars {
- uint8_t alpha;
- uint8_t t_avg_w;
- uint8_t t_avg_t;
- uint8_t pc_meas_chan;
- uint8_t n_avg_i;
-};
+#pragma message "Header osmocom/gprs/protocol/gsm_04_60.h is deprecated, include osmocom/gsm/protocol/gsm_44_060.h instead"
+#include <osmocom/gsm/protocol/gsm_44_060.h>
diff --git a/include/osmocom/gprs/protocol/gsm_08_18.h b/include/osmocom/gprs/protocol/gsm_08_18.h
index d6f0fe64..1152eb6c 100644
--- a/include/osmocom/gprs/protocol/gsm_08_18.h
+++ b/include/osmocom/gprs/protocol/gsm_08_18.h
@@ -367,7 +367,7 @@ struct bssgp_rim_pdu_ind {
pdu_type_ext:3,
reserved:4;
#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 reserved:4, pdu_type_ext:3, ack_requested:1;
#endif
} __attribute__ ((packed));
diff --git a/include/osmocom/gsm/Makefile.am b/include/osmocom/gsm/Makefile.am
new file mode 100644
index 00000000..f840a275
--- /dev/null
+++ b/include/osmocom/gsm/Makefile.am
@@ -0,0 +1,68 @@
+SUBDIRS = protocol
+
+BUILT_SOURCES = gsm0503.h
+
+osmogsm_HEADERS = \
+ a5.h \
+ abis_nm.h \
+ apn.h \
+ bts_features.h \
+ cbsp.h \
+ comp128.h \
+ comp128v23.h \
+ bitvec_gsm.h \
+ gan.h \
+ gsm0341.h \
+ gsm0411_smc.h \
+ gsm0411_smr.h \
+ gsm0411_utils.h \
+ gsm0480.h \
+ gsm0502.h \
+ gsm0503.h \
+ bsslap.h \
+ bssmap_le.h \
+ gad.h \
+ gsm0808.h \
+ gsm0808_lcs.h \
+ gsm29205.h \
+ gsm0808_utils.h \
+ gsm23003.h \
+ gsm23236.h \
+ gsm29118.h \
+ gsm44021.h \
+ gsm48.h \
+ gsm48_arfcn_range_encode.h \
+ gsm48_ie.h \
+ gsm48_rest_octets.h \
+ gsm_utils.h \
+ gsup.h \
+ gsup_sms.h \
+ i460_mux.h \
+ ipa.h \
+ iuup.h \
+ lapd_core.h \
+ lapdm.h \
+ meas_rep.h \
+ mncc.h \
+ prim.h \
+ l1sap.h \
+ oap.h \
+ oap_client.h \
+ rsl.h \
+ rxlev_stat.h \
+ sysinfo.h \
+ tlv.h \
+ $(NULL)
+
+osmogsmdir = $(includedir)/osmocom/gsm
+
+noinst_HEADERS = \
+ kasumi.h \
+ gea.h \
+ $(NULL)
+
+gsm0503.h: $(top_srcdir)/utils/conv_gen.py $(top_srcdir)/utils/conv_codes_gsm.py
+ $(AM_V_GEN)python3 $(top_srcdir)/utils/conv_gen.py gen_header gsm \
+ --target-path $(builddir)/
+
+CLEANFILES = gsm0503.h
diff --git a/include/osmocom/gsm/bts_features.h b/include/osmocom/gsm/bts_features.h
index ae372d39..cf1db4ac 100644
--- a/include/osmocom/gsm/bts_features.h
+++ b/include/osmocom/gsm/bts_features.h
@@ -7,7 +7,7 @@
/* N. B: always add new features to the end of the list (right before _NUM_BTS_FEAT) to avoid breaking compatibility
with BTS compiled against earlier version of this header. Also make sure that the description strings
- osmo_bts_features_descs[] in gsm_data.c are also updated accordingly! */
+ osmo_bts_features_{descs,names}[] in bts_features.c are also updated accordingly! */
enum osmo_bts_features {
BTS_FEAT_HSCSD,
BTS_FEAT_GPRS,
@@ -34,6 +34,8 @@ enum osmo_bts_features {
BTS_FEAT_DYN_TS_SDCCH8, /* Osmo Dynamic TS supports configured as SDCCH8 */
BTS_FEAT_ACCH_TEMP_OVP, /* FACCH/SACCH Temporary overpower */
BTS_FEAT_OSMUX, /* Osmux (Osmocom RTP muxing) support */
+ BTS_FEAT_VBS, /* Voice Broadcast Service support, 3GPP TS 43.069 */
+ BTS_FEAT_VGCS, /* Voice Group Call Service support, 3GPP TS 44.068 */
_NUM_BTS_FEAT
};
diff --git a/include/osmocom/gsm/cbsp.h b/include/osmocom/gsm/cbsp.h
index 1e705e54..536c54d6 100644
--- a/include/osmocom/gsm/cbsp.h
+++ b/include/osmocom/gsm/cbsp.h
@@ -303,7 +303,7 @@ struct osmo_cbsp_decoded {
} u;
};
-extern const __thread char *osmo_cbsp_errstr;
+extern __thread const char *osmo_cbsp_errstr;
struct msgb *osmo_cbsp_msgb_alloc(void *ctx, const char *name);
struct msgb *osmo_cbsp_encode(void *ctx, const struct osmo_cbsp_decoded *in);
diff --git a/include/osmocom/gsm/gsm0502.h b/include/osmocom/gsm/gsm0502.h
index 1be2cc39..0ac13873 100644
--- a/include/osmocom/gsm/gsm0502.h
+++ b/include/osmocom/gsm/gsm0502.h
@@ -1,4 +1,7 @@
-/*! \file gsm0502.h */
+/*! \defgroup gsm0502 GSM 05.02 / 3GPP TS 45.002
+ * @{
+ * \file gsm0502.h
+ */
#pragma once
@@ -16,10 +19,10 @@
/*! Return the sum of two specified TDMA frame numbers (summation) */
#define GSM_TDMA_FN_SUM(a, b) \
- ((a + b) % GSM_TDMA_HYPERFRAME)
+ (((a) + (b)) % GSM_TDMA_HYPERFRAME)
/*! Return the difference of two specified TDMA frame numbers (subtraction) */
#define GSM_TDMA_FN_SUB(a, b) \
- ((a + GSM_TDMA_HYPERFRAME - b) % GSM_TDMA_HYPERFRAME)
+ (((a) + GSM_TDMA_HYPERFRAME - (b)) % GSM_TDMA_HYPERFRAME)
/*! Return the *minimum* difference of two specified TDMA frame numbers (distance) */
#define GSM_TDMA_FN_DIFF(a, b) \
OSMO_MIN(GSM_TDMA_FN_SUB(a, b), GSM_TDMA_FN_SUB(b, a))
@@ -31,9 +34,58 @@
#define GSM_TDMA_FN_DEC(fn) \
((fn) = GSM_TDMA_FN_SUB((fn), 1))
+/* 5.2.3.1 Normal burst for GMSK (1 bit per symbol) */
+#define GSM_NBITS_NB_GMSK_TAIL 3
+#define GSM_NBITS_NB_GMSK_PAYLOAD (2 * 58)
+#define GSM_NBITS_NB_GMSK_TRAIN_SEQ 26
+#define GSM_NBITS_NB_GMSK_BURST 148 /* without guard period */
+
+/* 5.2.3.3 Normal burst for 8-PSK (3 bits per symbol) */
+#define GSM_NBITS_NB_8PSK_TAIL (GSM_NBITS_NB_GMSK_TAIL * 3)
+#define GSM_NBITS_NB_8PSK_PAYLOAD (GSM_NBITS_NB_GMSK_PAYLOAD * 3)
+#define GSM_NBITS_NB_8PSK_TRAIN_SEQ (GSM_NBITS_NB_GMSK_TRAIN_SEQ * 3)
+#define GSM_NBITS_NB_8PSK_BURST (GSM_NBITS_NB_GMSK_BURST * 3)
+
+/* 5.2.5 Synchronization burst (also GMSK) */
+#define GSM_NBITS_SB_GMSK_TAIL GSM_NBITS_NB_GMSK_TAIL
+#define GSM_NBITS_SB_GMSK_PAYLOAD (2 * 39)
+#define GSM_NBITS_SB_GMSK_ETRAIN_SEQ 64
+#define GSM_NBITS_SB_GMSK_BURST GSM_NBITS_NB_GMSK_BURST
+
+/* 5.2.6 Dummy burst (also GMSK) */
+#define GSM_NBITS_DB_GMSK_TAIL GSM_NBITS_NB_GMSK_TAIL
+#define GSM_NBITS_DB_GMSK_MIXED 142
+#define GSM_NBITS_DB_GMSK_BURST GSM_NBITS_NB_GMSK_BURST
+
+/* 5.2.7 Access burst (also GMSK) */
+#define GSM_NBITS_AB_GMSK_ETAIL 8
+#define GSM_NBITS_AB_GMSK_SYNCH_SEQ 41
+#define GSM_NBITS_AB_GMSK_PAYLOAD 36
+#define GSM_NBITS_AB_GMSK_TAIL GSM_NBITS_NB_GMSK_TAIL
+#define GSM_NBITS_AB_GMSK_BURST GSM_NBITS_NB_GMSK_BURST
+
+/*! Compare the given TDMA FNs, taking the wrapping into account.
+ * \param[in] fn1 First TDMA Fn value to compare.
+ * \param[in] fn2 Second TDMA Fn value to compare.
+ * \returns similarly to memcmp(), -1 if fn1 goes before fn2;
+ * 0 if fn1 equals fn2;
+ * 1 if fn1 goes after fn2. */
+static inline int gsm0502_fncmp(uint32_t fn1, uint32_t fn2)
+{
+ const uint32_t thresh = GSM_TDMA_HYPERFRAME / 2;
+
+ if (fn1 == fn2)
+ return 0;
+ if ((fn1 < fn2 && (fn2 - fn1) < thresh) ||
+ (fn1 > fn2 && (fn1 - fn2) > thresh))
+ return -1;
+
+ return 1;
+}
+
/* Table 5 Clause 7 TS 05.02 */
static inline unsigned int
-gsm0502_get_n_pag_blocks(struct gsm48_control_channel_descr *chan_desc)
+gsm0502_get_n_pag_blocks(const struct gsm48_control_channel_descr *chan_desc)
{
if (chan_desc->ccch_conf == RSL_BCCH_CCCH_CONF_1_C)
return 3 - chan_desc->bs_ag_blks_res;
@@ -58,7 +110,7 @@ gsm0502_get_paging_group(uint64_t imsi, unsigned int bs_cc_chans,
}
unsigned int
-gsm0502_calc_paging_group(struct gsm48_control_channel_descr *chan_desc, uint64_t imsi);
+gsm0502_calc_paging_group(const struct gsm48_control_channel_descr *chan_desc, uint64_t imsi);
enum gsm0502_fn_remap_channel {
FN_REMAP_TCH_F,
@@ -75,3 +127,7 @@ uint32_t gsm0502_fn_remap(uint32_t fn, enum gsm0502_fn_remap_channel channel);
uint16_t gsm0502_hop_seq_gen(const struct gsm_time *t,
uint8_t hsn, uint8_t maio,
size_t n, const uint16_t *ma);
+
+int gsm0502_fn2ccch_block(uint32_t fn);
+
+/*! @} */
diff --git a/include/osmocom/gsm/gsm0808.h b/include/osmocom/gsm/gsm0808.h
index b4c78032..80ef6836 100644
--- a/include/osmocom/gsm/gsm0808.h
+++ b/include/osmocom/gsm/gsm0808.h
@@ -26,11 +26,11 @@
#include <osmocom/gsm/protocol/gsm_23_003.h>
#include <osmocom/core/utils.h>
+#include <osmocom/core/socket_compat.h>
+
#define BSSMAP_MSG_SIZE 1024
#define BSSMAP_MSG_HEADROOM 512
-struct sockaddr_storage;
-
struct msgb;
struct gsm0808_cell_id_list2;
@@ -73,7 +73,7 @@ struct msgb *gsm0808_create_cipher2(const struct gsm0808_cipher_mode_command *cm
struct msgb *gsm0808_create_cipher_complete(struct msgb *layer3, uint8_t alg_id);
struct msgb *gsm0808_create_cipher_reject(enum gsm0808_cause cause);
struct msgb *gsm0808_create_cipher_reject_ext(enum gsm0808_cause_class class, uint8_t ext);
-struct msgb *gsm0808_create_classmark_request();
+struct msgb *gsm0808_create_classmark_request(void);
struct msgb *gsm0808_create_classmark_update(const uint8_t *cm2, uint8_t cm2_len,
const uint8_t *cm3, uint8_t cm3_len);
struct msgb *gsm0808_create_sapi_reject_cause(uint8_t link_id, uint16_t cause);
@@ -269,8 +269,8 @@ struct gsm0808_handover_command {
};
struct msgb *gsm0808_create_handover_command(const struct gsm0808_handover_command *params);
-struct msgb *gsm0808_create_handover_detect();
-struct msgb *gsm0808_create_handover_succeeded();
+struct msgb *gsm0808_create_handover_detect(void);
+struct msgb *gsm0808_create_handover_succeeded(void);
struct gsm0808_handover_complete {
bool rr_cause_present;
@@ -332,6 +332,243 @@ struct gsm0808_handover_performed {
};
struct msgb *gsm0808_create_handover_performed(const struct gsm0808_handover_performed *params);
+/*! 3GPP TS 48.008 §3.2.1.50 VGCS/VBS SETUP */
+struct gsm0808_vgcs_vbs_setup {
+ struct gsm0808_group_callref callref;
+
+ bool priority_present;
+ struct gsm0808_priority priority;
+
+ bool vgcs_feature_flags_present;
+ struct gsm0808_vgcs_feature_flags flags;
+};
+struct msgb *gsm0808_create_vgcs_vbs_setup(const struct gsm0808_vgcs_vbs_setup *params);
+
+/*! 3GPP TS 48.008 §3.2.1.51 VGCS/VBS SETUP ACK */
+struct gsm0808_vgcs_vbs_setup_ack {
+ bool vgcs_feature_flags_present;
+ struct gsm0808_vgcs_feature_flags flags;
+};
+struct msgb *gsm0808_create_vgcs_vbs_setup_ack(const struct gsm0808_vgcs_vbs_setup_ack *params);
+
+/*! 3GPP TS 48.008 §3.2.1.52 VGCS/VBS SETUP REFUSE */
+struct msgb *gsm0808_create_vgcs_vbs_setup_refuse(enum gsm0808_cause cause);
+
+/*! 3GPP TS 48.008 §3.2.1.53 VGCS/VBS ASSIGNMENT REQUEST */
+struct gsm0808_vgcs_vbs_assign_req {
+ struct gsm0808_channel_type channel_type;
+ enum gsm0808_assignment_requirement ass_req;
+ struct gsm0808_cell_id cell_identifier;
+ struct gsm0808_group_callref callref;
+
+ bool priority_present;
+ struct gsm0808_priority priority;
+
+ bool cic_present;
+ uint16_t cic;
+
+ bool downlink_dtx_flag_present;
+ enum gsm0808_downlink_dtx_flag downlink_dtx_flag;
+
+ bool encryption_information_present;
+ struct gsm0808_encrypt_info encryption_information;
+
+ bool vstk_rand_present;
+ uint8_t vstk_rand[5];
+
+ bool vstk_present;
+ uint8_t vstk[16];
+
+ bool cils_present;
+ struct gsm0808_cell_id_list_segment cils;
+
+ bool aoip_transport_layer_present;
+ struct sockaddr_storage aoip_transport_layer;
+
+ bool call_id_present;
+ uint32_t call_id;
+
+ bool codec_list_present;
+ struct gsm0808_speech_codec_list codec_list_msc_preferred;
+};
+struct msgb *gsm0808_create_vgcs_vbs_assign_req(const struct gsm0808_vgcs_vbs_assign_req *params);
+
+/*! 3GPP TS 48.008 §3.2.1.54 VGCS/VBS ASSIGNMENT RESULT */
+struct gsm0808_vgcs_vbs_assign_res {
+ struct gsm0808_channel_type channel_type;
+ struct gsm0808_cell_id cell_identifier;
+
+ bool chosen_channel_present;
+ uint8_t chosen_channel;
+
+ bool cic_present;
+ uint16_t cic;
+
+ bool circuit_pool_present;
+ uint8_t circuit_pool;
+
+ bool aoip_transport_layer_present;
+ struct sockaddr_storage aoip_transport_layer;
+
+ bool codec_present;
+ struct gsm0808_speech_codec codec_msc_chosen;
+
+ bool call_id_present;
+ uint32_t call_id;
+};
+struct msgb *gsm0808_create_vgcs_vbs_assign_res(const struct gsm0808_vgcs_vbs_assign_res *params);
+
+/*! 3GPP TS 48.008 §3.2.1.55 VGCS/VBS ASSIGNMENT FAILURE */
+struct gsm0808_vgcs_vbs_assign_fail {
+ enum gsm0808_cause cause;
+
+ bool circuit_pool_present;
+ uint8_t circuit_pool;
+
+ bool cpl_present;
+ struct gsm0808_circuit_pool_list cpl;
+
+ bool codec_list_present;
+ struct gsm0808_speech_codec_list codec_list_bss_supported;
+};
+struct msgb *gsm0808_create_vgcs_vbs_assign_fail(const struct gsm0808_vgcs_vbs_assign_fail *params);
+
+/*! 3GPP TS 48.008 §3.2.1.57 (VGCS) UPLINK REQUEST */
+struct gsm0808_uplink_request {
+ bool talker_priority_present;
+ enum gsm0808_talker_priority talker_priority;
+
+ bool cell_identifier_present;
+ struct gsm0808_cell_id cell_identifier;
+
+ bool l3_present;
+ struct gsm0808_layer_3_information l3;
+
+ bool mi_present;
+ struct osmo_mobile_identity mi;
+};
+struct msgb *gsm0808_create_uplink_request(const struct gsm0808_uplink_request *params);
+
+/*! 3GPP TS 48.008 §3.2.1.58 (VGCS) UPLINK REQUEST ACKNOWLEDGE */
+struct gsm0808_uplink_request_ack {
+ bool talker_priority_present;
+ enum gsm0808_talker_priority talker_priority;
+
+ bool emerg_set_ind_present;
+
+ bool talker_identity_present;
+ struct gsm0808_talker_identity talker_identity;
+};
+struct msgb *gsm0808_create_uplink_request_ack(const struct gsm0808_uplink_request_ack *params);
+
+/*! 3GPP TS 48.008 §3.2.1.59 (VGCS) UPLINK REQUEST CONFIRM */
+struct gsm0808_uplink_request_cnf {
+ struct gsm0808_cell_id cell_identifier;
+
+ bool talker_identity_present;
+ struct gsm0808_talker_identity talker_identity;
+
+ /* mandatory! */
+ struct gsm0808_layer_3_information l3;
+};
+struct msgb *gsm0808_create_uplink_request_cnf(const struct gsm0808_uplink_request_cnf *params);
+
+/*! 3GPP TS 48.008 §3.2.1.59a (VGCS) UPLINK APPLICATION DATA */
+struct gsm0808_uplink_app_data {
+ struct gsm0808_cell_id cell_identifier;
+ struct gsm0808_layer_3_information l3;
+ bool bt_ind;
+};
+struct msgb *gsm0808_create_uplink_app_data(const struct gsm0808_uplink_app_data *params);
+
+/*! 3GPP TS 48.008 §3.2.1.60 (VGCS) UPLINK RELEASE INDICATION */
+struct gsm0808_uplink_release_ind {
+ enum gsm0808_cause cause;
+
+ bool talker_priority_present;
+ enum gsm0808_talker_priority talker_priority;
+};
+struct msgb *gsm0808_create_uplink_release_ind(const struct gsm0808_uplink_release_ind *params);
+
+/*! 3GPP TS 48.008 §3.2.1.61 (VGCS) UPLINK REJECT COMMAND */
+struct gsm0808_uplink_reject_cmd {
+ enum gsm0808_cause cause;
+
+ bool current_talker_priority_present;
+ enum gsm0808_talker_priority current_talker_priority;
+ bool rejected_talker_priority_present;
+ enum gsm0808_talker_priority rejected_talker_priority;
+
+ bool talker_identity_present;
+ struct gsm0808_talker_identity talker_identity;
+};
+struct msgb *gsm0808_create_uplink_reject_cmd(const struct gsm0808_uplink_reject_cmd *params);
+
+/*! 3GPP TS 48.008 §3.2.1.62 (VGCS) UPLINK RELEASE COMMAND */
+struct msgb *gsm0808_create_uplink_release_cmd(const enum gsm0808_cause cause);
+
+/*! 3GPP TS 48.008 §3.2.1.63 (VGCS) UPLINK SEIZED COMMAND */
+struct gsm0808_uplink_seized_cmd {
+ enum gsm0808_cause cause;
+
+ bool talker_priority_present;
+ enum gsm0808_talker_priority talker_priority;
+
+ bool emerg_set_ind_present;
+
+ bool talker_identity_present;
+ struct gsm0808_talker_identity talker_identity;
+};
+struct msgb *gsm0808_create_uplink_seized_cmd(const struct gsm0808_uplink_seized_cmd *params);
+
+/*! 3GPP TS 48.008 §3.2.1.78 VGCS ADDITIONAL INFORMATION */
+struct msgb *gsm0808_create_vgcs_additional_info(const struct gsm0808_talker_identity *ti);
+
+/*! 3GPP TS 48.008 §3.2.1.79 VGCS/VBS AREA CELL INFO */
+struct gsm0808_vgcs_vbs_area_cell_info {
+ struct gsm0808_cell_id_list_segment cils;
+
+ bool ass_req_present;
+ enum gsm0808_assignment_requirement ass_req;
+};
+struct msgb *gsm0808_create_vgcs_vbs_area_cell_info(const struct gsm0808_vgcs_vbs_area_cell_info *params);
+
+/*! 3GPP TS 48.008 §3.2.1.80 VGCS/VBS ASSIGNMENT STATUS */
+struct gsm0808_vgcs_vbs_assign_stat {
+ /* established cells */
+ bool cils_est_present;
+ struct gsm0808_cell_id_list_segment cils_est;
+
+ /* cells to be established */
+ bool cils_tbe_present;
+ struct gsm0808_cell_id_list_segment cils_tbe;
+
+ /* released cells - no user present */
+ bool cils_rel_present;
+ struct gsm0808_cell_id_list_segment cils_rel;
+
+ /* not established cells - no establishment possible */
+ bool cils_ne_present;
+ struct gsm0808_cell_id_list_segment cils_ne;
+
+ bool cell_status_present;
+ enum gsm0808_vgcs_vbs_cell_status cell_status;
+};
+struct msgb *gsm0808_create_vgcs_vbs_assign_stat(const struct gsm0808_vgcs_vbs_assign_stat *params);
+
+/*! 3GPP TS 48.008 §3.2.1.81 VGCS SMS */
+struct msgb *gsm0808_create_vgcs_sms(const struct gsm0808_sms_to_vgcs *sms);
+
+/*! 3GPP TS 48.008 §3.2.1.82 (VGCS/VBS) NOTIFICATION DATA */
+struct gsm0808_notification_data {
+ struct gsm0808_application_data app_data;
+ struct gsm0808_data_identity data_ident;
+
+ bool msisdn_present;
+ char msisdn[MSISDN_MAXLEN + 1];
+};
+struct msgb *gsm0808_create_notification_data(const struct gsm0808_notification_data *parms);
+
struct msgb *gsm0808_create_dtap(struct msgb *msg, uint8_t link_id);
void gsm0808_prepend_dtap_header(struct msgb *msg, uint8_t link_id);
diff --git a/include/osmocom/gsm/gsm0808_utils.h b/include/osmocom/gsm/gsm0808_utils.h
index 6abfeecc..dbe2abf9 100644
--- a/include/osmocom/gsm/gsm0808_utils.h
+++ b/include/osmocom/gsm/gsm0808_utils.h
@@ -31,6 +31,8 @@ struct sockaddr_storage;
#include <osmocom/gsm/gsm23003.h>
#include <osmocom/gsm/gsm_utils.h>
#include <osmocom/gsm/tlv.h>
+#include <osmocom/gsm/gsm48.h>
+#include <osmocom/core/endian.h>
/*! (225-1)/2 is the maximum number of elements in a cell identifier list. */
#define GSM0808_CELL_ID_LIST2_MAXLEN 127
@@ -62,6 +64,164 @@ struct gsm0808_cell_id_list2 {
unsigned int id_list_len;
};
+/*! Packed representation of a Priority IE (GGPP TS 48.008 3.2.2.18) */
+struct gsm0808_priority {
+#if OSMO_IS_LITTLE_ENDIAN
+ uint8_t pvi:1, /* Preemption Vulnerability indicator */
+ qa:1, /* Queuing allowed indicator */
+ priority_level:4, /* Priority level: 1 == hightest, 14 == lowest */
+ pci:1, /* Preemption Capability indicator */
+ spare:1;
+#elif OSMO_IS_BIG_ENDIAN
+/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianness.py) */
+ uint8_t spare:1, pci:1, priority_level:4, qa:1, pvi:1;
+#endif
+} __attribute__ ((packed));
+
+/*! Packed representation of a VGCS Feature Flags IE (3GPP TS 48.008 3.2.2.88) */
+struct gsm0808_vgcs_feature_flags {
+#if OSMO_IS_LITTLE_ENDIAN
+ uint8_t tp_ind:1, /* Talker priority supported */
+ as_ind_circuit:1, /* A-interface circuit sharing supported */
+ as_ind_link:1, /* A-interface link sharing supported */
+ bss_res:1, /* BSS supports re-establishment */
+ tcp:1, /* Talker channel parameter supported */
+ spare:3;
+#elif OSMO_IS_BIG_ENDIAN
+/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianness.py) */
+ uint8_t spare:3, tcp:1, bss_res:1, as_ind_link:1, as_ind_circuit:1, tp_ind:1;
+#endif
+} __attribute__ ((packed));
+
+/* TS 48.008 3.2.2.52 */
+enum gsm0808_assignment_requirement {
+ GSM0808_ASRQ_DELAY_ALLOWED = 0x00,
+ GSM0808_ASRQ_IMMEDIATE = 0x01,
+ GSM0808_ASRQ_IMMEDIATE_ON_DEMAND = 0x02,
+};
+
+/* TS 48.008 Table 10.5.8 */
+enum gsm0808_service_flag {
+ GSM0808_SF_VBS = 0,
+ GSM0808_SF_VGCS = 1,
+};
+
+enum gsm0808_call_priority {
+ GSM0808_CALL_PRIORITY_NONE = 0x00,
+ GSM0808_CALL_PRIORITY_LEVEL_4 = 0x01,
+ GSM0808_CALL_PRIORITY_LEVEL_3 = 0x02,
+ GSM0808_CALL_PRIORITY_LEVEL_2 = 0x03,
+ GSM0808_CALL_PRIORITY_LEVEL_1 = 0x04,
+ GSM0808_CALL_PRIORITY_LEVEL_0 = 0x05,
+ GSM0808_CALL_PRIORITY_LEVEL_B = 0x06,
+ GSM0808_CALL_PRIORITY_LEVEL_A = 0x07,
+};
+
+/*! Packed representation of a Group Call Reference IE (3GPP TS 48.008 3.2.2.55) */
+struct gsm0808_group_callref {
+#if OSMO_IS_LITTLE_ENDIAN
+ uint8_t call_ref_hi[3];
+ uint8_t call_priority:3,
+ af:1, /* Acknowledgement flag */
+ sf:1, /* Service flag */
+ call_ref_lo:3;
+ uint8_t spare:4,
+ ciphering_info:4;
+#elif OSMO_IS_BIG_ENDIAN
+/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianness.py) */
+ uint8_t call_ref_hi[3];
+ uint8_t call_ref_lo:3, sf:1, af:1, call_priority:3;
+ uint8_t ciphering_info:4, spare:4;
+#endif
+} __attribute__ ((packed));
+
+/* TS 48.008 3.2.2.26 */
+enum gsm0808_downlink_dtx_flag {
+ GSM0808_DTX_FLAG_ALLOW = 0,
+ GSM0808_DTX_FLAG_FORBID = 1,
+};
+
+/*! Parsed representation of a Cell Identifier List Segment IE (3GPP TS 48.008 3.2.2.27a) */
+struct gsm0808_cell_id_list_segment {
+ uint8_t seq_last;
+ uint8_t seq_number;
+ struct gsm0808_cell_id_list2 cil;
+};
+
+/*! Parsed representation of a Circuit Pool List IE (3GPP TS 48.008 3.2.2.26) */
+#define CIRCUIT_POOL_LIST_MAXLEN 252
+struct gsm0808_circuit_pool_list {
+ uint8_t pool[CIRCUIT_POOL_LIST_MAXLEN];
+ unsigned int list_len;
+};
+
+/* 3GPP TS 48.008 Table 3.2.2.90.1 Talker Priority */
+enum gsm0808_talker_priority {
+ GSM0808_TALKER_PRIORITY_NORMAL = 0x00,
+ GSM0808_TALKER_PRIORITY_PRIVILEGED = 0x01,
+ GSM0808_TALKER_PRIORITY_EMERGENCY = 0x02,
+};
+
+/*! Parsed representation of a Layer 3 Information IE (3GPP TS 48.008 3.2.2.24) */
+#define LAYER_3_INFORMATION_MAXLEN 252
+struct gsm0808_layer_3_information {
+ uint8_t l3[LAYER_3_INFORMATION_MAXLEN];
+ unsigned int l3_len;
+};
+
+/*! Parsed representation of a Talker Identity IE (3GPP TS 48.008 3.2.2.91) */
+#define TALKER_IDENTITY_MAXLEN 17
+struct gsm0808_talker_identity {
+ uint8_t talker_id[TALKER_IDENTITY_MAXLEN];
+ unsigned int id_bits;
+};
+
+/* 3GPP TS 48.008 3.2.2.94 VGCS/VBS Cell Status */
+enum gsm0808_vgcs_vbs_cell_status {
+ GSM0808_CSTAT_ESTABLISHED = 0x00,
+ GSM0808_CSTAT_NOT_ESTABLISHED1 = 0x01,
+ GSM0808_CSTAT_RELEASED_NO_USER = 0x02,
+ GSM0808_CSTAT_NOT_ESTABLISHED2 = 0x03,
+};
+
+/*! Parsed representation of a SMS to VGCS IE (3GPP TS 48.008 3.2.2.92) */
+#define SMS_TO_VGCS_MAXLEN 252
+struct gsm0808_sms_to_vgcs {
+ uint8_t sms[SMS_TO_VGCS_MAXLEN];
+ unsigned int sms_len;
+};
+
+/*! Parsed representation of a Application Data IE (3GPP TS 48.008 3.2.2.98) */
+#define APP_DATA_MAXLEN 9
+struct gsm0808_application_data {
+ uint8_t data[APP_DATA_MAXLEN];
+ unsigned int data_len;
+};
+
+/*! Packed representation of a Data Identity IE (GGPP TS 48.008 3.2.2.99) */
+enum gsm0808_application_idndicator {
+ GSM0808_AI_APP_DATA = 0x00,
+ GSM0808_AI_CONFIRM_APP_DATA = 0x01,
+};
+
+#define GSM0808_DP_MASK_TALKERS_LISTENERS 0x04
+#define GSM0808_DP_MASK_DISPATCHERS 0x02
+#define GSM0808_DP_MASK_NETWORK_APP 0x01
+
+struct gsm0808_data_identity {
+#if OSMO_IS_LITTLE_ENDIAN
+ uint8_t ai:1, /* Application Indicator */
+ di:4, /* Data identifier */
+ dp:3; /* Distribution parameter (bit mask) */
+#elif OSMO_IS_BIG_ENDIAN
+/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianness.py) */
+ uint8_t dp:3, di:4, ai:1;
+#endif
+} __attribute__ ((packed));
+
+/*! Parsed representation of a MSISDN IE (3GPP TS 48.008 3.2.2.101) */
+#define MSISDN_MAXLEN 20
+
/*! LCLS-related parameters from 3GPP TS 48.008 */
struct osmo_lcls {
enum gsm0808_lcls_config config; /**< §3.2.2.116 Configuration */
@@ -111,12 +271,17 @@ uint8_t gsm0808_enc_lcls(struct msgb *msg, const struct osmo_lcls *lcls);
int gsm0808_dec_lcls(struct osmo_lcls *lcls, const struct tlv_parsed *tp);
uint8_t gsm0808_enc_speech_codec(struct msgb *msg,
- const struct gsm0808_speech_codec *sc);
+ const struct gsm0808_speech_codec *sc)
+ OSMO_DEPRECATED("use gsm0808_enc_speech_codec2() instead");
+int gsm0808_enc_speech_codec2(struct msgb *msg,
+ const struct gsm0808_speech_codec *sc);
int gsm0808_dec_speech_codec(struct gsm0808_speech_codec *sc,
const uint8_t *elem, uint8_t len);
uint8_t gsm0808_enc_speech_codec_list(struct msgb *msg,
- const struct gsm0808_speech_codec_list
- *scl);
+ const struct gsm0808_speech_codec_list *scl)
+ OSMO_DEPRECATED("use gsm0808_enc_speech_codec_list2() instead");
+int gsm0808_enc_speech_codec_list2(struct msgb *msg,
+ const struct gsm0808_speech_codec_list *scl);
int gsm0808_dec_speech_codec_list(struct gsm0808_speech_codec_list *scl,
const uint8_t *elem, uint8_t len);
uint8_t gsm0808_enc_channel_type(struct msgb *msg,
@@ -183,37 +348,34 @@ static inline uint8_t gsm0808_current_channel_type_1(enum gsm_chan_t type)
static inline enum gsm0808_permitted_speech gsm0808_permitted_speech(enum gsm_chan_t type,
enum gsm48_chan_mode mode)
{
- switch (mode) {
- case GSM48_CMODE_SPEECH_V1:
- switch (type) {
- case GSM_LCHAN_TCH_F:
- return GSM0808_PERM_FR1;
- case GSM_LCHAN_TCH_H:
- return GSM0808_PERM_HR1;
- default:
- return 0;
- }
- case GSM48_CMODE_SPEECH_EFR:
- switch (type) {
- case GSM_LCHAN_TCH_F:
- return GSM0808_PERM_FR2;
- case GSM_LCHAN_TCH_H:
- return GSM0808_PERM_HR2;
- default:
- return 0;
- }
- case GSM48_CMODE_SPEECH_AMR:
- switch (type) {
- case GSM_LCHAN_TCH_F:
- return GSM0808_PERM_FR3;
- case GSM_LCHAN_TCH_H:
- return GSM0808_PERM_HR3;
- default:
- return 0;
- }
+#define MODE_TYPE(mode, type) ((mode << 16) | type)
+
+ switch (MODE_TYPE(mode, type)) {
+ case MODE_TYPE(GSM48_CMODE_SPEECH_V1, GSM_LCHAN_TCH_F):
+ return GSM0808_PERM_FR1;
+ case MODE_TYPE(GSM48_CMODE_SPEECH_V1, GSM_LCHAN_TCH_H):
+ return GSM0808_PERM_HR1;
+ case MODE_TYPE(GSM48_CMODE_SPEECH_EFR, GSM_LCHAN_TCH_F):
+ return GSM0808_PERM_FR2;
+ case MODE_TYPE(GSM48_CMODE_SPEECH_EFR, GSM_LCHAN_TCH_H):
+ return GSM0808_PERM_HR2; /* (deprecated) */
+ case MODE_TYPE(GSM48_CMODE_SPEECH_AMR, GSM_LCHAN_TCH_F):
+ return GSM0808_PERM_FR3;
+ case MODE_TYPE(GSM48_CMODE_SPEECH_AMR, GSM_LCHAN_TCH_H):
+ return GSM0808_PERM_HR3;
+ case MODE_TYPE(GSM48_CMODE_SPEECH_V4, GSM_LCHAN_TCH_F):
+ return GSM0808_PERM_FR4;
+ case MODE_TYPE(GSM48_CMODE_SPEECH_V4, GSM_LCHAN_TCH_H):
+ return GSM0808_PERM_HR4;
+ case MODE_TYPE(GSM48_CMODE_SPEECH_V5, GSM_LCHAN_TCH_F):
+ return GSM0808_PERM_FR5; /* FR only */
+ case MODE_TYPE(GSM48_CMODE_SPEECH_V6, GSM_LCHAN_TCH_H):
+ return GSM0808_PERM_HR6; /* HR only */
default:
return 0;
}
+
+#undef MODE_TYPE
}
/*! Return 3GPP TS 48.008 3.2.2.33 Chosen Channel. */
@@ -225,6 +387,9 @@ static inline uint8_t gsm0808_chosen_channel(enum gsm_chan_t type, enum gsm48_ch
case GSM48_CMODE_SPEECH_V1:
case GSM48_CMODE_SPEECH_EFR:
case GSM48_CMODE_SPEECH_AMR:
+ case GSM48_CMODE_SPEECH_V4:
+ case GSM48_CMODE_SPEECH_V5:
+ case GSM48_CMODE_SPEECH_V6:
channel_mode = 0x9;
break;
case GSM48_CMODE_SIGN:
@@ -242,6 +407,33 @@ static inline uint8_t gsm0808_chosen_channel(enum gsm_chan_t type, enum gsm48_ch
case GSM48_CMODE_DATA_3k6:
channel_mode = 0xd;
break;
+ case GSM48_CMODE_DATA_29k0:
+ channel_mode = 0x1;
+ break;
+ case GSM48_CMODE_DATA_32k0:
+ channel_mode = 0x2;
+ break;
+ case GSM48_CMODE_DATA_43k5:
+ channel_mode = 0x3;
+ break;
+ case GSM48_CMODE_DATA_43k5_14k5:
+ channel_mode = 0x4;
+ break;
+ case GSM48_CMODE_DATA_29k0_14k5:
+ channel_mode = 0x5;
+ break;
+ case GSM48_CMODE_DATA_43k5_29k0:
+ channel_mode = 0x6;
+ break;
+ case GSM48_CMODE_DATA_14k5_43k5:
+ channel_mode = 0x7;
+ break;
+ case GSM48_CMODE_DATA_14k5_29k0:
+ channel_mode = 0xa;
+ break;
+ case GSM48_CMODE_DATA_29k0_43k5:
+ channel_mode = 0xf;
+ break;
default:
return 0;
}
@@ -259,6 +451,7 @@ static inline uint8_t gsm0808_chosen_channel(enum gsm_chan_t type, enum gsm48_ch
case GSM_LCHAN_TCH_H:
channel = 0x9;
break;
+ /* TODO: more than 1 TCHs? */
default:
return 0;
}
@@ -270,4 +463,23 @@ const char *gsm0808_channel_type_name(const struct gsm0808_channel_type *ct);
char *gsm0808_channel_type_name_buf(char *buf, size_t buf_len, const struct gsm0808_channel_type *ct);
char *gsm0808_channel_type_name_c(const void *ctx, const struct gsm0808_channel_type *ct);
+uint8_t gsm0808_enc_group_callref(struct msgb *msg, const struct gsm0808_group_callref *gc);
+int gsm0808_dec_group_callref(struct gsm0808_group_callref *gc, const uint8_t *elem, uint8_t len);
+uint8_t gsm0808_enc_priority(struct msgb *msg, const struct gsm0808_priority *pri);
+int gsm0808_dec_priority(struct gsm0808_priority *pri, const uint8_t *elem, uint8_t len);
+uint8_t gsm0808_enc_vgcs_feature_flags(struct msgb *msg, const struct gsm0808_vgcs_feature_flags *ff);
+int gsm0808_dec_vgcs_feature_flags(struct gsm0808_vgcs_feature_flags *ff, const uint8_t *elem, uint8_t len);
+uint8_t gsm0808_enc_talker_identity(struct msgb *msg, const struct gsm0808_talker_identity *ti);
+int gsm0808_dec_talker_identity(struct gsm0808_talker_identity *ti, const uint8_t *elem, uint8_t len);
+uint8_t gsm0808_enc_data_identity(struct msgb *msg, const struct gsm0808_data_identity *ai);
+int gsm0808_dec_data_identity(struct gsm0808_data_identity *ai, const uint8_t *elem, uint8_t len);
+uint8_t gsm0808_enc_msisdn(struct msgb *msg, const char *msisdn);
+int gsm0808_dec_msisdn(char *msisdn, const char *elem, uint8_t len);
+uint8_t gsm0808_enc_assign_req(struct msgb *msg, const enum gsm0808_assignment_requirement ar);
+int gsm0808_dec_assign_req(enum gsm0808_assignment_requirement *ar, const uint8_t *elem, uint8_t len);
+uint8_t gsm0808_enc_cell_id_list_segment(struct msgb *msg, uint8_t ie_type,
+ const struct gsm0808_cell_id_list_segment *ci);
+int gsm0808_dec_cell_id_list_segment(struct gsm0808_cell_id_list_segment *ci, const uint8_t *elem, uint8_t len);
+int gsm0808_dec_call_id(uint32_t *ci, const uint8_t *elem, uint8_t len);
+
/*! @} */
diff --git a/include/osmocom/gsm/gsm44021.h b/include/osmocom/gsm/gsm44021.h
new file mode 100644
index 00000000..8f89c56b
--- /dev/null
+++ b/include/osmocom/gsm/gsm44021.h
@@ -0,0 +1,8 @@
+#pragma once
+#include <osmocom/isdn/v110.h>
+
+int osmo_csd_12k_6k_decode_frame(struct osmo_v110_decoded_frame *fr, const ubit_t *ra_bits, size_t n_bits);
+int osmo_csd_12k_6k_encode_frame(ubit_t *ra_bits, size_t ra_bits_size, const struct osmo_v110_decoded_frame *fr);
+int osmo_csd_3k6_decode_frame(struct osmo_v110_decoded_frame *fr, const ubit_t *ra_bits, size_t n_bits);
+int osmo_csd_3k6_encode_frame(ubit_t *ra_bits, size_t ra_bits_size, const struct osmo_v110_decoded_frame *fr);
+void osmo_csd_ubit_dump(FILE *outf, const ubit_t *fr, size_t in_len);
diff --git a/include/osmocom/gsm/gsm48.h b/include/osmocom/gsm/gsm48.h
index c1ca6308..00fb6f40 100644
--- a/include/osmocom/gsm/gsm48.h
+++ b/include/osmocom/gsm/gsm48.h
@@ -20,7 +20,9 @@
* To mark an invalid / unset MNC, this value shall be used. */
#define GSM_MCC_MNC_INVALID 0xFFFF
-/* A parsed GPRS routing area */
+/* A parsed GPRS routing area.
+ * Preferably use struct osmo_routing_area_id, it is better integrated with API like osmo_plmn_cmp().
+ */
struct gprs_ra_id {
uint16_t mcc;
uint16_t mnc;
@@ -35,6 +37,7 @@ extern const struct tlv_definition gsm48_mm_att_tlvdef;
const char *gsm48_cc_state_name(uint8_t state);
const char *gsm48_cc_msg_name(uint8_t msgtype);
const char *gsm48_rr_msg_name(uint8_t msgtype);
+const char *gsm48_rr_short_pd_msg_name(uint8_t msgtype);
const char *rr_cause_name(uint8_t cause);
const char *osmo_rai_name(const struct gprs_ra_id *rai);
char *osmo_rai_name_buf(char *buf, size_t buf_len, const struct gprs_ra_id *rai);
@@ -95,18 +98,23 @@ char *osmo_mobile_identity_to_str_c(void *ctx, const struct osmo_mobile_identity
int osmo_mobile_identity_cmp(const struct osmo_mobile_identity *a, const struct osmo_mobile_identity *b);
int osmo_mobile_identity_decode(struct osmo_mobile_identity *mi, const uint8_t *mi_data, uint8_t mi_len,
bool allow_hex);
+int osmo_mobile_identity_decode_from_l3_buf(struct osmo_mobile_identity *mi, const uint8_t *l3_data, size_t l3_len,
+ bool allow_hex);
int osmo_mobile_identity_decode_from_l3(struct osmo_mobile_identity *mi, struct msgb *msg, bool allow_hex);
int osmo_mobile_identity_encoded_len(const struct osmo_mobile_identity *mi, int *mi_digits);
int osmo_mobile_identity_encode_buf(uint8_t *buf, size_t buflen, const struct osmo_mobile_identity *mi, bool allow_hex);
int osmo_mobile_identity_encode_msgb(struct msgb *msg, const struct osmo_mobile_identity *mi, bool allow_hex);
/* Parse Routeing Area Identifier */
+int osmo_routing_area_id_decode(struct osmo_routing_area_id *dst, const uint8_t *ra_data, size_t ra_data_len);
+int osmo_routing_area_id_encode_buf(uint8_t *buf, size_t buflen, const struct osmo_routing_area_id *src);
+int osmo_routing_area_id_encode_msgb(struct msgb *msg, const struct osmo_routing_area_id *src);
void gsm48_parse_ra(struct gprs_ra_id *raid, const uint8_t *buf);
void gsm48_encode_ra(struct gsm48_ra_id *out, const struct gprs_ra_id *raid);
int gsm48_construct_ra(uint8_t *buf, const struct gprs_ra_id *raid) OSMO_DEPRECATED("Use gsm48_encode_ra() instead");
bool gsm48_ra_equal(const struct gprs_ra_id *raid1, const struct gprs_ra_id *raid2);
-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);
void gsm48_mcc_mnc_to_bcd(uint8_t *bcd_dst, uint16_t mcc, uint16_t mnc)
OSMO_DEPRECATED("Use osmo_plmn_to_bcd() instead, to not lose leading zeros in the MNC");
diff --git a/include/osmocom/gsm/gsm48_ie.h b/include/osmocom/gsm/gsm48_ie.h
index b79cbfcb..4768283f 100644
--- a/include/osmocom/gsm/gsm48_ie.h
+++ b/include/osmocom/gsm/gsm48_ie.h
@@ -117,8 +117,9 @@ struct gsm_sysinfo_freq {
};
/* decode "Cell Channel Description" (10.5.2.1b) and other frequency lists */
-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);
/* decode "CSN.1 encoded Classmark 3" (10.5.1.7) */
int gsm48_decode_classmark3(struct gsm48_classmark3 *classmark3_out,
diff --git a/include/osmocom/gsm/gsm48_rest_octets.h b/include/osmocom/gsm/gsm48_rest_octets.h
index f2958249..cdb2e807 100644
--- a/include/osmocom/gsm/gsm48_rest_octets.h
+++ b/include/osmocom/gsm/gsm48_rest_octets.h
@@ -2,7 +2,7 @@
#include <stdbool.h>
#include <osmocom/gsm/sysinfo.h>
-#include <osmocom/gprs/protocol/gsm_04_60.h>
+#include <osmocom/gsm/protocol/gsm_44_060.h>
/* 16 is the max. number of SI2quater messages according to 3GPP TS 44.018 Table 10.5.2.33b.1:
4-bit index is used (2#1111 = 10#15) */
@@ -13,6 +13,9 @@
/* generate SI1 rest octets */
int osmo_gsm48_rest_octets_si1_encode(uint8_t *data, uint8_t *nch_pos, int is1800_net);
+int osmo_gsm48_si1ro_nch_pos_decode(uint8_t value, uint8_t *num_blocks, uint8_t *first_block);
+int osmo_gsm48_si1ro_nch_pos_encode(uint8_t num_blocks, uint8_t first_block);
+
int osmo_gsm48_rest_octets_si2quater_encode(uint8_t *data, uint8_t si2q_index, uint8_t si2q_count,
const uint16_t *uarfcn_list, size_t *u_offset,
size_t uarfcn_length, uint16_t *scramble_list,
diff --git a/include/osmocom/gsm/gsm_utils.h b/include/osmocom/gsm/gsm_utils.h
index fef2fdd1..b250c594 100644
--- a/include/osmocom/gsm/gsm_utils.h
+++ b/include/osmocom/gsm/gsm_utils.h
@@ -33,6 +33,7 @@
} while (0)
#define GSM_MAX_FN (26*51*2048)
+#define GSM_FN_UNSET 0xFFFFFFFF
/* Max length of random identifier which can be requested via osmo_get_rand_id() */
#define OSMO_MAX_RAND_ID_LEN 16
@@ -180,13 +181,21 @@ void gsm_fn2gsmtime(struct gsm_time *time, uint32_t fn);
char *gsm_fn_as_gsmtime_str(uint32_t fn);
/* Convert from GSM time to frame number */
-uint32_t gsm_gsmtime2fn(struct gsm_time *time);
+uint32_t gsm_gsmtime2fn(const struct gsm_time *time);
/* Returns static buffer with string representation of a GSM Time */
char *osmo_dump_gsmtime(const struct gsm_time *tm);
char *osmo_dump_gsmtime_buf(char *buf, size_t buf_len, const struct gsm_time *tm);
char *osmo_dump_gsmtime_c(const void *ctx, const struct gsm_time *tm);
+/* Reduced Frame Number (3GPP TS 44.018 §10.5.2.38) */
+#define GSM_RFN_MODULUS 42432
+uint32_t gsm_rfn2fn(uint16_t rfn, uint32_t curr_fn);
+static inline uint16_t gsm_fn2rfn(uint32_t fn)
+{
+ return fn % GSM_RFN_MODULUS;
+}
+
/* GSM TS 03.03 Chapter 2.6 */
enum gprs_tlli_type {
TLLI_LOCAL,
diff --git a/include/osmocom/gsm/i460_mux.h b/include/osmocom/gsm/i460_mux.h
index 770b1e15..7c2f4af3 100644
--- a/include/osmocom/gsm/i460_mux.h
+++ b/include/osmocom/gsm/i460_mux.h
@@ -1,116 +1,2 @@
-/*! \file i460_mux.h
- * ITU-T I.460 sub-channel multiplexer + demultiplexer */
-/*
- * (C) 2020 by Harald Welte <laforge@gnumonks.org>
- *
- * SPDX-License-Identifier: GPL-2.0+
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- */
-
#pragma once
-#include <stdint.h>
-#include <osmocom/core/bits.h>
-#include <osmocom/core/linuxlist.h>
-#include <osmocom/core/msgb.h>
-
-/* I.460 sub-slot rate */
-enum osmo_i460_rate {
- OSMO_I460_RATE_NONE, /* disabled */
- OSMO_I460_RATE_64k,
- OSMO_I460_RATE_32k,
- OSMO_I460_RATE_16k,
- OSMO_I460_RATE_8k,
-};
-
-struct osmo_i460_subchan;
-
-typedef void (*out_cb_bits_t)(struct osmo_i460_subchan *schan, void *user_data,
- const ubit_t *bits, unsigned int num_bits);
-typedef void (*out_cb_bytes_t)(struct osmo_i460_subchan *schan, void *user_data,
- const uint8_t *bytes, unsigned int num_bytes);
-
-struct osmo_i460_subchan_demux {
- /*! bit-buffer for output bits */
- uint8_t *out_bitbuf;
- /*! size of out_bitbuf in bytes */
- unsigned int out_bitbuf_size;
- /*! offset of next bit to be written in out_bitbuf */
- unsigned int out_idx;
- /*! callback to be called once we have received out_bitbuf_size bits */
- out_cb_bits_t out_cb_bits;
- out_cb_bytes_t out_cb_bytes;
- void *user_data;
-};
-
-typedef void (*in_cb_queue_empty_t)(struct osmo_i460_subchan *schan, void *user_data);
-
-struct osmo_i460_subchan_mux {
- /*! list of to-be-transmitted message buffers */
- struct llist_head tx_queue;
- in_cb_queue_empty_t in_cb_queue_empty;
- void *user_data;
-};
-
-struct osmo_i460_subchan {
- struct osmo_i460_timeslot *ts; /* back-pointer */
- enum osmo_i460_rate rate; /* 8/16/32/64k */
- uint8_t bit_offset; /* bit offset inside each byte of the B-channel */
- struct osmo_i460_subchan_demux demux;
- struct osmo_i460_subchan_mux mux;
-};
-
-struct osmo_i460_timeslot {
- struct osmo_i460_subchan schan[8];
-};
-
-/*! description of a sub-channel; passed by caller */
-struct osmo_i460_schan_desc {
- enum osmo_i460_rate rate;
- uint8_t bit_offset;
- struct {
- /* size (in bits) of the internal buffer; determines granularity */
- size_t num_bits;
- /*! call-back function called whenever we received num_bits */
- out_cb_bits_t out_cb_bits;
- /*! out_cb_bytes call-back function called whenever we received num_bits.
- * The user is usually expected to provide either out_cb_bits or out_cb_bytes. If only
- * out_cb_bits is provided, output data will always be provided as unpacked bits; if only
- * out_cb_bytes is provided, output data will always be provided as packet bits (bytes). If
- * both are provided, it is up to the I.460 multiplex to decide if it calls either of the two,
- * depending on what can be provided without extra conversion. */
- out_cb_bytes_t out_cb_bytes;
- /* opaque user data pointer to pass to out_cb */
- void *user_data;
- } demux;
-
- struct {
- /* call-back function whenever the muxer requires more input data from the sub-channels,
- * but has nothing enqueued yet. A typical function would then call osmo_i460_mux_enqueue() */
- in_cb_queue_empty_t in_cb_queue_empty;
- /* opaque user data pointer to pass to in_cb */
- void *user_data;
- } mux;
-};
-
-void osmo_i460_demux_in(struct osmo_i460_timeslot *ts, const uint8_t *data, size_t data_len);
-
-void osmo_i460_mux_enqueue(struct osmo_i460_subchan *schan, struct msgb *msg);
-int osmo_i460_mux_out(struct osmo_i460_timeslot *ts, uint8_t *out, size_t out_len);
-
-void osmo_i460_ts_init(struct osmo_i460_timeslot *ts);
-
-struct osmo_i460_subchan *
-osmo_i460_subchan_add(void *ctx, struct osmo_i460_timeslot *ts, const struct osmo_i460_schan_desc *chd);
-
-void osmo_i460_subchan_del(struct osmo_i460_subchan *schan);
-
-/*! @} */
+#include <osmocom/isdn/i460_mux.h>
diff --git a/include/osmocom/gsm/lapd_core.h b/include/osmocom/gsm/lapd_core.h
index 69e10875..a4dc2505 100644
--- a/include/osmocom/gsm/lapd_core.h
+++ b/include/osmocom/gsm/lapd_core.h
@@ -1,177 +1,2 @@
-/*! \file lapd_core.h
- * primitive related stuff
- */
#pragma once
-
-#include <stdint.h>
-
-#include <osmocom/core/timer.h>
-#include <osmocom/core/msgb.h>
-#include <osmocom/gsm/prim.h>
-
-/*! \defgroup lapd LAPD implementation common part
- * @{
- * \file lapd_core.h
- */
-
-#define LOGDL(dl, level, fmt, args...) \
- LOGP(DLLAPD, level, "(%s) " fmt, (dl)->name, ## args)
-
-/*! LAPD related primitives (L2<->L3 SAP)*/
-enum osmo_dl_prim {
- PRIM_DL_UNIT_DATA, /*!< DL-UNIT-DATA */
- PRIM_DL_DATA, /*!< DL-DATA */
- PRIM_DL_EST, /*!< DL-ESTABLISH */
- PRIM_DL_REL, /*!< DL-RLEEASE */
- PRIM_DL_SUSP, /*!< DL-SUSPEND */
- PRIM_DL_RES, /*!< DL-RESUME */
- PRIM_DL_RECON, /*!< DL-RECONNECT */
- PRIM_MDL_ERROR, /*!< MDL-ERROR */
-};
-
-/* Uses the same values as RLL, so no conversion for GSM is required. */
-#define MDL_CAUSE_T200_EXPIRED 0x01
-#define MDL_CAUSE_REEST_REQ 0x02
-#define MDL_CAUSE_UNSOL_UA_RESP 0x03
-#define MDL_CAUSE_UNSOL_DM_RESP 0x04
-#define MDL_CAUSE_UNSOL_DM_RESP_MF 0x05
-#define MDL_CAUSE_UNSOL_SPRV_RESP 0x06
-#define MDL_CAUSE_SEQ_ERR 0x07
-#define MDL_CAUSE_UFRM_INC_PARAM 0x08
-#define MDL_CAUSE_SFRM_INC_PARAM 0x09
-#define MDL_CAUSE_IFRM_INC_MBITS 0x0a
-#define MDL_CAUSE_IFRM_INC_LEN 0x0b
-#define MDL_CAUSE_FRM_UNIMPL 0x0c
-#define MDL_CAUSE_SABM_MF 0x0d
-#define MDL_CAUSE_SABM_INFO_NOTALL 0x0e
-#define MDL_CAUSE_FRMR 0x0f
-
-/*! for MDL-ERROR.ind */
-struct mdl_error_ind_param {
- uint8_t cause; /*!< generic cause value */
-};
-
-/*! for DL-REL.req */
-struct dl_rel_req_param {
- uint8_t mode; /*!< release mode */
-};
-
-/*! primitive header for LAPD DL-SAP primitives */
-struct osmo_dlsap_prim {
- struct osmo_prim_hdr oph; /*!< generic primitive header */
- union {
- struct mdl_error_ind_param error_ind;
- struct dl_rel_req_param rel_req;
- } u; /*!< request-specific data */
-};
-
-/*! LAPD mode/role */
-enum lapd_mode {
- LAPD_MODE_USER, /*!< behave like user */
- LAPD_MODE_NETWORK, /*!< behave like network */
-};
-
-/*! LAPD state (Figure B.2/Q.921)*/
-enum lapd_state {
- LAPD_STATE_NULL = 0,
- LAPD_STATE_TEI_UNASS,
- LAPD_STATE_ASS_TEI_WAIT,
- LAPD_STATE_EST_TEI_WAIT,
- LAPD_STATE_IDLE,
- LAPD_STATE_SABM_SENT,
- LAPD_STATE_DISC_SENT,
- LAPD_STATE_MF_EST,
- LAPD_STATE_TIMER_RECOV,
-};
-
-/*! LAPD message format (I / S / U) */
-enum lapd_format {
- LAPD_FORM_UKN = 0,
- LAPD_FORM_I,
- LAPD_FORM_S,
- LAPD_FORM_U,
-};
-
-/*! LAPD message context */
-struct lapd_msg_ctx {
- struct lapd_datalink *dl;
- int n201;
- /* address */
- uint8_t cr;
- uint8_t sapi;
- uint8_t tei;
- uint8_t lpd;
- /* control */
- uint8_t format;
- uint8_t p_f; /* poll / final bit */
- uint8_t n_send;
- uint8_t n_recv;
- uint8_t s_u; /* S or repectivly U function bits */
- /* length */
- int length;
- uint8_t more;
-};
-
-struct lapd_cr_ent {
- uint8_t cmd;
- uint8_t resp;
-};
-
-struct lapd_history {
- struct msgb *msg; /* message to be sent / NULL, if histoy is empty */
- int more; /* if message is fragmented */
-};
-
-/*! LAPD datalink */
-struct lapd_datalink {
- int (*send_dlsap)(struct osmo_dlsap_prim *dp,
- struct lapd_msg_ctx *lctx);
- int (*send_ph_data_req)(struct lapd_msg_ctx *lctx, struct msgb *msg);
- int (*update_pending_frames)(struct lapd_msg_ctx *lctx);
- struct {
- /*! filled-in once we set the lapd_mode above */
- struct lapd_cr_ent loc2rem;
- struct lapd_cr_ent rem2loc;
- } cr;
- enum lapd_mode mode; /*!< current mode of link */
- int use_sabme; /*!< use SABME instead of SABM */
- int reestablish; /*!< enable reestablish support */
- int n200, n200_est_rel; /*!< number of retranmissions */
- struct lapd_msg_ctx lctx; /*!< LAPD context */
- int maxf; /*!< maximum frame size (after defragmentation) */
- uint8_t k; /*!< maximum number of unacknowledged frames */
- uint8_t v_range; /*!< range of sequence numbers */
- uint8_t v_send; /*!< seq nr of next I frame to be transmitted */
- uint8_t v_ack; /*!< last frame ACKed by peer */
- uint8_t v_recv; /*!< seq nr of next I frame expected to be received */
- uint32_t state; /*!< LAPD state (\ref lapd_state) */
- int seq_err_cond; /*!< condition of sequence error */
- uint8_t own_busy; /*!< receiver busy on our side */
- uint8_t peer_busy; /*!< receiver busy on remote side */
- int t200_sec, t200_usec; /*!< retry timer (default 1 sec) */
- int t203_sec, t203_usec; /*!< retry timer (default 10 secs) */
- struct osmo_timer_list t200; /*!< T200 timer */
- struct osmo_timer_list t203; /*!< T203 timer */
- uint8_t retrans_ctr; /*!< re-transmission counter */
- struct llist_head tx_queue; /*!< frames to L1 */
- struct llist_head send_queue; /*!< frames from L3 */
- struct msgb *send_buffer; /*!< current frame transmitting */
- int send_out; /*!< how much was sent from send_buffer */
- struct lapd_history *tx_hist; /*!< tx history structure array */
- uint8_t range_hist; /*!< range of history buffer 2..2^n */
- struct msgb *rcv_buffer; /*!< buffer to assemble the received message */
- struct msgb *cont_res; /*!< buffer to store content resolution data on network side, to detect multiple phones on same channel */
- char *name; /*!< user-provided name */
-};
-
-void lapd_dl_init(struct lapd_datalink *dl, uint8_t k, uint8_t v_range, int maxf)
- OSMO_DEPRECATED("Use lapd_dl_init2() instead");
-void lapd_dl_init2(struct lapd_datalink *dl, uint8_t k, uint8_t v_range, int maxf, const char *name);
-void lapd_dl_set_name(struct lapd_datalink *dl, const char *name);
-void lapd_dl_exit(struct lapd_datalink *dl);
-void lapd_dl_reset(struct lapd_datalink *dl);
-int lapd_set_mode(struct lapd_datalink *dl, enum lapd_mode mode);
-int lapd_ph_data_ind(struct msgb *msg, struct lapd_msg_ctx *lctx);
-int lapd_recv_dlsap(struct osmo_dlsap_prim *dp, struct lapd_msg_ctx *lctx);
-
-/*! @} */
+#include <osmocom/isdn/lapd_core.h>
diff --git a/include/osmocom/gsm/lapdm.h b/include/osmocom/gsm/lapdm.h
index 0e997431..1a39fcaf 100644
--- a/include/osmocom/gsm/lapdm.h
+++ b/include/osmocom/gsm/lapdm.h
@@ -24,6 +24,7 @@ struct lapdm_msg_ctx {
uint8_t link_id;
uint8_t ta_ind; /* TA indicated by network */
uint8_t tx_power_ind; /* MS power indicated by network */
+ uint32_t fn;
};
/*! LAPDm datalink like TS 04.06 / Section 3.5.2 */
diff --git a/include/osmocom/gsm/protocol/Makefile.am b/include/osmocom/gsm/protocol/Makefile.am
new file mode 100644
index 00000000..23429dcd
--- /dev/null
+++ b/include/osmocom/gsm/protocol/Makefile.am
@@ -0,0 +1,30 @@
+osmogsmproto_HEADERS = \
+ gsm_23_032.h \
+ gsm_03_40.h \
+ gsm_03_41.h \
+ gsm_04_08.h \
+ gsm_04_08_gprs.h \
+ gsm_04_11.h \
+ gsm_04_12.h \
+ gsm_04_14.h \
+ gsm_04_80.h \
+ gsm_08_08.h \
+ gsm_08_58.h \
+ gsm_09_02.h \
+ gsm_12_21.h \
+ gsm_23_003.h \
+ gsm_23_041.h \
+ gsm_25_415.h \
+ gsm_29_118.h \
+ gsm_44_004.h \
+ gsm_44_060.h \
+ gsm_44_068.h \
+ gsm_44_318.h \
+ gsm_48_049.h \
+ gsm_48_071.h \
+ gsm_49_031.h \
+ ipaccess.h \
+ smpp34_osmocom.h \
+ $(NULL)
+
+osmogsmprotodir = $(includedir)/osmocom/gsm/protocol
diff --git a/include/osmocom/gsm/protocol/gsm_03_41.h b/include/osmocom/gsm/protocol/gsm_03_41.h
index 1b399aee..f6c36cad 100644
--- a/include/osmocom/gsm/protocol/gsm_03_41.h
+++ b/include/osmocom/gsm/protocol/gsm_03_41.h
@@ -25,7 +25,7 @@ struct gsm341_ms_message {
uint8_t update:4;
uint8_t code_lo:4;
#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 gs:2, code_hi:6;
uint8_t code_lo:4, update:4;
#endif
@@ -36,7 +36,7 @@ struct gsm341_ms_message {
uint8_t language:4;
uint8_t group:4;
#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 group:4, language:4;
#endif
} dcs;
@@ -45,7 +45,7 @@ struct gsm341_ms_message {
uint8_t total:4;
uint8_t current:4;
#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 current:4, total:4;
#endif
} page;
@@ -63,7 +63,7 @@ struct gsm341_etws_message {
uint8_t update:4;
uint8_t code_lo:4;
#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 gs:2, alert:1, popup:1, code_hi:4;
uint8_t code_lo:4, update:4;
#endif
diff --git a/include/osmocom/gsm/protocol/gsm_04_08.h b/include/osmocom/gsm/protocol/gsm_04_08.h
index ea77e764..84b2b2a2 100644
--- a/include/osmocom/gsm/protocol/gsm_04_08.h
+++ b/include/osmocom/gsm/protocol/gsm_04_08.h
@@ -22,7 +22,7 @@ struct gsm48_classmark1 {
rev_lev:2,
spare: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 spare:1, rev_lev:2, es_ind:1, a5_1:1, pwr_lev:3;
#endif
} __attribute__ ((packed));
@@ -51,7 +51,7 @@ struct gsm48_classmark2 {
spare4:1,
cm3: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 spare:1, rev_lev:2, es_ind:1, a5_1:1, pwr_lev:3;
uint8_t spare2:1, ps_cap:1, ss_scr:2, sm_cap:1, vbs:1, vgcs:1, fc:1;
uint8_t cm3:1, spare4:1, lcsva_cap:1, spare3:1, solsa:1, cmsp:1, a5_3:1, a5_2:1;
@@ -280,7 +280,7 @@ struct gsm48_range_1024 {
uint8_t w16:6,
w15_lo:2;
#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 form_id:5, f0:1, w1_hi:2;
uint8_t w1_lo;
uint8_t w2_hi;
@@ -335,7 +335,7 @@ struct gsm48_range_512 {
uint8_t w17:5,
w16_lo:3;
#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 form_id:7, orig_arfcn_hi:1;
uint8_t orig_arfcn_mid;
uint8_t orig_arfcn_lo:1, w1_hi:7;
@@ -396,7 +396,7 @@ struct gsm48_range_256 {
w21:4,
w20_lo:3;
#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 form_id:7, orig_arfcn_hi:1;
uint8_t orig_arfcn_mid;
uint8_t orig_arfcn_lo:1, w1_hi:7;
@@ -459,7 +459,7 @@ struct gsm48_range_128 {
w27:3,
w26_lo: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 form_id:7, orig_arfcn_hi:1;
uint8_t orig_arfcn_mid;
uint8_t orig_arfcn_lo:1, w1:7;
@@ -489,7 +489,7 @@ struct gsm48_var_bit {
orig_arfcn_lo:1;
uint8_t rrfcn8_111[13];
#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 form_id:7, orig_arfcn_hi:1;
uint8_t orig_arfcn_mid;
uint8_t orig_arfcn_lo:1, rrfcn1_7:7;
@@ -509,7 +509,7 @@ struct gsm48_chan_desc {
uint8_t hsn:6,
maio_low:2;
#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 tsc:3, h:1, maio_high:4;
uint8_t maio_low:2, hsn:6;
#endif
@@ -522,7 +522,7 @@ struct gsm48_chan_desc {
tsc:3;
uint8_t arfcn_low;
#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 tsc:3, h:1, spare:2, arfcn_high:2;
uint8_t arfcn_low;
#endif
@@ -573,7 +573,7 @@ struct gsm48_meas_res {
uint8_t bsic_nc6:6,
bcch_f_nc6_lo:2;
#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 ba_used:1, dtx_used:1, rxlev_full:6;
uint8_t spare:1, meas_valid:1, rxlev_sub:6;
uint8_t spare2:1, rxqual_full:3, rxqual_sub:3, no_nc_n_hi:1;
@@ -616,7 +616,7 @@ struct gsm48_multi_rate_conf {
m10_2 : 1,
m12_2 : 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 ver:3, nscb:1, icmi:1, spare:1, smod:2;
uint8_t m12_2:1, m10_2:1, m7_95:1, m7_40:1, m6_70:1, m5_90:1, m5_15:1, m4_75:1;
#endif
@@ -629,7 +629,7 @@ struct gsm48_power_cmd {
spare:2,
atc: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 atc:1, spare:2, power_level:5;
#endif
} __attribute__((packed));
@@ -644,7 +644,7 @@ struct gsm48_rach_control {
uint8_t t2; /* ACC 8-15 barred flags */
uint8_t t3; /* ACC 0-7 barred flags */
#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 max_trans:2, tx_integer:4, cell_bar:1, re:1;
uint8_t t2;
uint8_t t3;
@@ -702,7 +702,7 @@ struct gsm48_req_ref {
uint8_t t2:5,
t3_low:3;
#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 ra;
uint8_t t1:5, t3_high:3;
uint8_t t3_low:3, t2:5;
@@ -717,7 +717,7 @@ struct gsm48_start_time {
uint8_t t2:5,
t3_low:3;
#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 t1:5, t3_high:3;
uint8_t t3_low:3, t2:5;
#endif
@@ -731,7 +731,7 @@ struct gsm48_sync_ind {
nci:1,
sync_ie:4;
#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 sync_ie:4, nci:1, rot:1, si:2;
#endif
} __attribute__((packed));
@@ -746,18 +746,58 @@ struct gsm48_chan_mode_modify {
uint8_t mode;
} __attribute__ ((packed));
+/*! 10.5.2.6 Channel Mode value */
enum gsm48_chan_mode {
+ /*! Signalling only (TCH/F or TCH/H) */
GSM48_CMODE_SIGN = 0x00,
+ /*! Speech: FR (TCH/FS) or HR (TCH/HS) */
GSM48_CMODE_SPEECH_V1 = 0x01,
- GSM48_CMODE_SPEECH_EFR = 0x21,
- GSM48_CMODE_SPEECH_AMR = 0x41,
+ /*! Speech: EFR (TCH/EFS) */
+ GSM48_CMODE_SPEECH_EFR = 0x21, /*!< a.k.a. V2 */
+ /*! Speech: AMR (TCH/AFS or TCH/AHS) */
+ GSM48_CMODE_SPEECH_AMR = 0x41, /*!< a.k.a. V3 */
+ /*! Speech: OFR AMR-WB (O-TCH/WFS) or OHR AMR-WB (O-TCH/WHS) */
+ GSM48_CMODE_SPEECH_V4 = 0x81,
+ /*! Speech: FR AMR-WB (TCH/WFS) */
+ GSM48_CMODE_SPEECH_V5 = 0x82,
+ /*! Speech: OHR AMR (O-TCH/AHS) */
+ GSM48_CMODE_SPEECH_V6 = 0x83,
+
+ /* ECSD: 43.5 kbit/s (DL) + 14.5 kbit/s (UL) */
+ GSM48_CMODE_DATA_43k5_14k5 = 0x61,
+ /* ECSD: 29.0 kbit/s (DL) + 14.5 kbit/s (UL) */
+ GSM48_CMODE_DATA_29k0_14k5 = 0x62,
+ /* ECSD: 43.5 kbit/s (DL) + 29.0 kbit/s (UL) */
+ GSM48_CMODE_DATA_43k5_29k0 = 0x64,
+ /* ECSD: 14.5 kbit/s (DL) + 43.5 kbit/s (UL) */
+ GSM48_CMODE_DATA_14k5_43k5 = 0x67,
+ /* ECSD: 14.5 kbit/s (DL) + 29.0 kbit/s (UL) */
+ GSM48_CMODE_DATA_14k5_29k0 = 0x65,
+ /* ECSD: 29.0 kbit/s (DL) + 43.5 kbit/s (UL) */
+ GSM48_CMODE_DATA_29k0_43k5 = 0x66,
+
+ /*! ECSD: 43.5 kbit/s radio interface rate, 43.2 kbit/s services (E-TCH/F43.2) */
+ GSM48_CMODE_DATA_43k5 = 0x27,
+ /*! ECSD: 32.0 kbit/s radio interface rate, 32.0 kbit/s services (E-TCH/F32.0) */
+ GSM48_CMODE_DATA_32k0 = 0x63,
+ /*! ECSD: 29.0 kbit/s radio interface rate, 28.8 kbit/s services (E-TCH/F28.8) */
+ GSM48_CMODE_DATA_29k0 = 0x43,
+ /*! CSD: 14.5 kbit/s radio interface rate, 14.4 kbit/s services (TCH/F14.4) */
GSM48_CMODE_DATA_14k5 = 0x0f,
+ /*! CSD: 12.0 kbit/s radio interface rate, 9.6 kbit/s services (TCH/F9.6) */
GSM48_CMODE_DATA_12k0 = 0x03,
+ /*! CSD: 6.0 kbit/s radio interface rate, 4.8 kbit/s services (TCH/{F,H}4.8) */
GSM48_CMODE_DATA_6k0 = 0x0b,
+ /*! CSD: 3.6 kbit/s radio interface rate, 2.4 kbit/s and less services (TCH/{F,H}2.4) */
GSM48_CMODE_DATA_3k6 = 0x13,
+
+ /*! Same as GSM48_CMODE_SPEECH_V1, in VAMOS mode */
GSM48_CMODE_SPEECH_V1_VAMOS = 0xc1,
+ /*! Same as GSM48_CMODE_SPEECH_EFR, in VAMOS mode */
GSM48_CMODE_SPEECH_V2_VAMOS = 0xc2,
+ /*! Same as GSM48_CMODE_SPEECH_AMR, in VAMOS mode */
GSM48_CMODE_SPEECH_V3_VAMOS = 0xc3,
+ /*! Speech: GSM48_CMODE_SPEECH_V5, in VAMOS mode */
GSM48_CMODE_SPEECH_V5_VAMOS = 0xc5,
};
@@ -797,7 +837,7 @@ struct gsm48_cell_desc {
arfcn_hi:2;
uint8_t arfcn_lo;
#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 arfcn_hi:2, ncc:3, bcc:3;
uint8_t arfcn_lo;
#endif
@@ -840,7 +880,7 @@ struct gsm48_pag_resp {
uint8_t mi_len;
uint8_t mi[0];
#elif OSMO_IS_BIG_ENDIAN
-/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianess.py) */
+/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianness.py) */
uint8_t key_seq:4, spare:4;
union {
uint32_t classmark2;
@@ -867,7 +907,7 @@ struct gsm48_auth_req {
spare:4;
uint8_t rand[16];
#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 spare:4, key_seq:4;
uint8_t rand[16];
#endif
@@ -888,7 +928,7 @@ struct gsm48_loc_upd_req {
uint8_t mi_len;
uint8_t mi[0];
#elif OSMO_IS_BIG_ENDIAN
-/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianess.py) */
+/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianness.py) */
uint8_t key_seq:4, type:4;
struct gsm48_loc_area_id lai;
struct gsm48_classmark1 classmark1;
@@ -904,6 +944,20 @@ struct gsm48_hdr {
uint8_t data[0];
} __attribute__ ((packed));
+/* Short header */
+struct gsm48_hdr_sh {
+#if OSMO_IS_LITTLE_ENDIAN
+ uint8_t l2_header:2, /* < short layer 2 header : bit(2) > See 3GPP TS 44.006 §6.4a */
+ msg_type:5, /* < message type : bit(5) > See 3GPP TS 44.018 Table 10.4.2 */
+ rr_short_pd:1; /* < RR short PD : bit > See 3GPP TS 24.007 §11.3.2 */
+ uint8_t data[0];
+#elif OSMO_IS_BIG_ENDIAN
+/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianness.py) */
+ uint8_t rr_short_pd:1, msg_type:5, l2_header:2;
+ uint8_t data[0];
+#endif
+} __attribute__ ((packed));
+
/* Section 9.1.3x System information Type header */
struct gsm48_system_information_type_header {
#if OSMO_IS_LITTLE_ENDIAN
@@ -912,7 +966,7 @@ struct gsm48_system_information_type_header {
skip_indicator:4;
uint8_t system_information;
#elif OSMO_IS_BIG_ENDIAN
-/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianess.py) */
+/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianness.py) */
uint8_t l2_plen;
uint8_t skip_indicator:4, rr_protocol_discriminator:4;
uint8_t system_information;
@@ -928,7 +982,7 @@ struct gsm48_cell_sel_par {
neci:1,
acs: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 cell_resel_hyst:3, ms_txpwr_max_ccch:5;
uint8_t acs:1, neci:1, rxlev_acc_min:6;
#endif
@@ -947,7 +1001,7 @@ struct gsm48_control_channel_descr {
spare_2 :1;
uint8_t t3212;
#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 mscr:1, att:1, bs_ag_blks_res:3, ccch_conf:3;
uint8_t spare_2:1, cbq3:2, spare_1:2, bs_pa_mfrms:3;
uint8_t t3212;
@@ -970,7 +1024,7 @@ struct gsm48_cell_options {
/* either DN-IND or top bit of DTX IND */
d: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 d:1, pwrc:1, dtx:2, radio_link_timeout:4;
#endif
} __attribute__ ((packed));
@@ -991,7 +1045,7 @@ struct gsm48_service_request {
uint8_t mi[0];
/* optional priority level */
#elif OSMO_IS_BIG_ENDIAN
-/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianess.py) */
+/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianness.py) */
uint8_t cipher_key_seq:4, cm_service_type:4;
union {
uint32_t classmark;
@@ -1074,7 +1128,7 @@ struct gsm48_system_information_type_5 {
uint8_t system_information;
uint8_t bcch_frequency_list[16];
#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 skip_indicator:4, rr_protocol_discriminator:4;
uint8_t system_information;
uint8_t bcch_frequency_list[16];
@@ -1089,7 +1143,7 @@ struct gsm48_system_information_type_5bis {
uint8_t system_information;
uint8_t bcch_frequency_list[16];
#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 skip_indicator:4, rr_protocol_discriminator:4;
uint8_t system_information;
uint8_t bcch_frequency_list[16];
@@ -1104,7 +1158,7 @@ struct gsm48_system_information_type_5ter {
uint8_t system_information;
uint8_t bcch_frequency_list[16];
#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 skip_indicator:4, rr_protocol_discriminator:4;
uint8_t system_information;
uint8_t bcch_frequency_list[16];
@@ -1123,7 +1177,7 @@ struct gsm48_system_information_type_6 {
uint8_t ncc_permitted;
uint8_t rest_octets[0];
#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 skip_indicator:4, rr_protocol_discriminator:4;
uint8_t system_information;
uint16_t cell_identity;
@@ -1134,6 +1188,27 @@ struct gsm48_system_information_type_6 {
#endif
} __attribute__ ((packed));
+/* Section 9.1.50 System Information type 10 (ASCI) */
+struct gsm48_system_information_type_10 {
+#if OSMO_IS_LITTLE_ENDIAN
+ uint8_t l2_header:2, /* < short layer 2 header : bit(2) > See 3GPP TS 44.006 §6.4a */
+ msg_type:5, /* < message type : bit(5) > See 3GPP TS 44.018 Table 10.4.2 */
+ rr_short_pd:1; /* < RR short PD : bit > See 3GPP TS 24.007 §11.3.2 */
+ uint8_t rest_octets[0]; /* < SI10 Rest Octets : bit(160) > See 3GPP TS 44.018 §10.5.2.44 */
+#elif OSMO_IS_BIG_ENDIAN
+/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianness.py) */
+ uint8_t rr_short_pd:1, msg_type:5, l2_header:2;
+ uint8_t rest_octets[0];
+#endif
+} __attribute__ ((packed));
+
+/* TS 44.018 Section 9.1.49 */
+struct gsm0408_vgcs_ul_grant {
+ struct gsm48_hdr hdr;
+ struct gsm48_req_ref req_ref;
+ uint8_t ta;
+} __attribute__ ((packed));
+
/* Section 9.1.43a System Information type 13 */
struct gsm48_system_information_type_13 {
struct gsm48_system_information_type_header header;
@@ -1189,7 +1264,7 @@ struct gsm48_cip_mode_cmd {
cr:1,
spare:3;
#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 spare:3, cr:1, alg_id:3, sc:1;
#endif
} __attribute__((packed));
@@ -1234,6 +1309,14 @@ struct gsm48_imm_ass_rej {
uint8_t rest[0];
} __attribute__ ((packed));
+/* Section 9.1.21b */
+struct gsm48_notification_nch {
+ uint8_t l2_plen;
+ uint8_t proto_discr;
+ uint8_t msg_type;
+ uint8_t data[0];
+} __attribute__((packed));
+
/* Section 9.1.22 */
struct gsm48_paging1 {
#if OSMO_IS_LITTLE_ENDIAN
@@ -1246,7 +1329,7 @@ struct gsm48_paging1 {
cneed2:2;
uint8_t data[0];
#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 l2_plen;
uint8_t proto_discr;
uint8_t msg_type;
@@ -1269,7 +1352,7 @@ struct gsm48_paging2 {
uint32_t tmsi2;
uint8_t data[0];
#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 l2_plen;
uint8_t proto_discr;
uint8_t msg_type;
@@ -1299,7 +1382,7 @@ struct gsm48_paging3 {
spare2:4;
uint8_t rest[0];
#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 l2_plen;
uint8_t proto_discr;
uint8_t msg_type;
@@ -1322,7 +1405,7 @@ struct gsm48_pag_rsp {
struct gsm48_classmark2 cm2;
uint8_t data[0];
#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 spare:5, key_seq:3;
uint8_t cm2_len;
struct gsm48_classmark2 cm2;
@@ -1335,6 +1418,18 @@ struct gsm48_rr_status {
uint8_t rr_cause;
} __attribute__((packed));
+/* Section 9.1.44 */
+struct gsm48_talker_indication {
+ uint8_t cm2_len;
+ struct gsm48_classmark2 cm2;
+ uint8_t data[0];
+} __attribute__((packed));
+
+/* Section 9.1.48 */
+struct gsm48_uplink_release {
+ uint8_t rr_cause;
+} __attribute__((packed));
+
/* Section 10.2 + GSM 04.07 12.2.3.1.1 + 3GPP TS 24.007 11.2.3.1.1 */
#define GSM48_PDISC_GROUP_CC 0x00
#define GSM48_PDISC_BCAST_CC 0x01
@@ -1353,6 +1448,9 @@ struct gsm48_rr_status {
#define GSM48_PDISC_TEST 0x0f /* as per 11.10, 04.14 */
#define GSM48_PDISC_MASK 0x0f
+/* Section 11.3.2.1 3GPP TS 24.007: Short PDISC */
+#define GSM48_PDISC_SH_RR 0
+
extern const struct value_string gsm48_pdisc_names[];
static inline const char *gsm48_pdisc_name(uint8_t val)
{ return get_value_string(gsm48_pdisc_names, val); }
@@ -1540,6 +1638,21 @@ void gsm48_set_dtx(struct gsm48_cell_options *op, enum gsm48_dtx_mode full,
#define GSM48_MT_RR_APP_INFO 0x38
+/* 3GPP TS 44.018 Table 10.4.2 */
+#define GSM48_MT_RR_SH_SI10 0x0
+#define GSM48_MT_RR_SH_FACCH 0x1
+#define GSM48_MT_RR_SH_UL_FREE 0x2
+#define GSM48_MT_RR_SH_MEAS_REP 0x4
+#define GSM48_MT_RR_SH_MEAS_INFO 0x5
+#define GSM48_MT_RR_SH_VGCS_RECON 0x6
+#define GSM48_MT_RR_SH_VGCS_RECON2 0x7
+#define GSM48_MT_RR_SH_VGCS_INFO 0x8
+#define GSM48_MT_RR_SH_VGCS_SMS 0x9
+#define GSM48_MT_RR_SH_SI10bis 0xA
+#define GSM48_MT_RR_SH_SI10ter 0xB
+#define GSM48_MT_RR_SH_VGCS_NEIGH 0xC
+#define GSM48_MT_RR_SH_APP_DATA 0xD
+
/* Table 10.2/3GPP TS 04.08 */
#define GSM48_MT_MM_IMSI_DETACH_IND 0x01
#define GSM48_MT_MM_LOC_UPD_ACCEPT 0x02
@@ -1705,6 +1818,10 @@ static inline const char *osmo_lu_type_name(uint8_t lu_type)
#define GSM48_IE_FRQSHORT_AFTER 0x02
#define GSM48_IE_MUL_RATE_CFG 0x03 /* 10.5.2.21aa */
#define GSM48_IE_FREQ_L_AFTER 0x05
+#define GSM48_IE_GROUP_CIP_SEQ_HO 0x08 /* HO = Half Octet Tag */
+#define GSM48_IE_CIP_MODE_SET_HO 0x09 /* HO = Half Octet Tag */
+#define GSM48_IE_GPRS_RESUMPT_HO 0xc0 /* HO = Half Octet Tag */
+#define GSM48_IE_SYNC_IND_HO 0x0d /* HO = Half Octet Tag */
#define GSM48_IE_MSLOT_DESC 0x10
#define GSM48_IE_CHANMODE_2 0x11
#define GSM48_IE_FRQSHORT_BEFORE 0x12
@@ -1746,20 +1863,21 @@ static inline const char *osmo_lu_type_name(uint8_t lu_type)
#define GSM48_IE_START_TIME 0x7c
#define GSM48_IE_INDIVIDUAL_PRIORITIES 0x7c /* 44.018 Section 9.1.7 */
#define GSM48_IE_TIMING_ADVANCE 0x7d
-#define GSM48_IE_GROUP_CIP_SEQ 0x80
-#define GSM48_IE_CIP_MODE_SET 0x90
-#define GSM48_IE_GPRS_RESUMPT 0xc0
-#define GSM48_IE_SYNC_IND 0xd0
+#define GSM48_IE_GROUP_CIP_SEQ 0x80 /* DEPRECATED, use GSM48_IE_GROUP_CIP_SEQ_HO instead */
+#define GSM48_IE_CIP_MODE_SET 0x90 /* DEPRECATED, use GSM48_IE_CIP_MODE_SET_HO instead */
+#define GSM48_IE_GPRS_RESUMPT 0xc0 /* DEPRECATED, use GSM48_IE_GPRS_RESUMPT_HO instead */
+#define GSM48_IE_SYNC_IND 0xd0 /* DEPRECATED, use GSM48_IE_SYNC_IND_HO instead */
/* System Information 4 (types are equal IEs above) */
#define GSM48_IE_CBCH_CHAN_DESC 0x64
#define GSM48_IE_CBCH_MOB_AL 0x72
/* Additional MM elements */
+#define GSM48_IE_PRIORITY_LEV_HO 0x08 /* HO = Half Octet Tag */
#define GSM48_IE_LOCATION_AREA 0x13
#define GSM48_IE_AUTN 0x20
#define GSM48_IE_AUTH_RES_EXT 0x21
#define GSM48_IE_AUTS 0x22
-#define GSM48_IE_PRIORITY_LEV 0x80
+#define GSM48_IE_PRIORITY_LEV 0x80 /* DEPRECATED, use GSM48_IE_PRIORITY_LEV_HO instead */
#define GSM48_IE_FOLLOW_ON_PROC 0xa1
#define GSM48_IE_CTS_PERMISSION 0xa2
diff --git a/include/osmocom/gsm/protocol/gsm_04_08_gprs.h b/include/osmocom/gsm/protocol/gsm_04_08_gprs.h
index 4729e141..8799d654 100644
--- a/include/osmocom/gsm/protocol/gsm_04_08_gprs.h
+++ b/include/osmocom/gsm/protocol/gsm_04_08_gprs.h
@@ -1,7 +1,6 @@
/*! \file gsm_04_08_gprs.h */
-#ifndef _GSM48_GPRS_H
-#define _GSM48_GPRS_H
+#pragma once
#include <stdint.h>
#include <stdbool.h>
@@ -83,6 +82,8 @@ extern const struct value_string *gprs_upd_t_strs;
enum gsm48_gprs_ie_mm {
GSM48_IE_GMM_CIPH_CKSN = 0x08, /* 10.5.1.2 */
+ GSM48_IE_GMM_PTMSI_TYPE = 0x0e, /* 10.5.5.29 */
+ GSM48_IE_GMM_TMSI_BASED_NRI_C = 0x10, /* 10.5.5.31 */
GSM48_IE_GMM_TIMER_READY = 0x17, /* 10.5.7.3 */
GSM48_IE_GMM_ALLOC_PTMSI = 0x18, /* 10.5.1.4 */
GSM48_IE_GMM_PTMSI_SIG = 0x19, /* 10.5.5.8 */
@@ -90,27 +91,39 @@ enum gsm48_gprs_ie_mm {
GSM48_IE_GMM_AUTH_SRES = 0x22, /* 10.5.3.2 */
GSM48_IE_GMM_IMEISV = 0x23, /* 10.5.1.4 */
GSM48_IE_GMM_CAUSE = 0x25, /* 10.5.5.14 */
+ GSM48_IE_GMM_RX_NPDU_NUM_LIST = 0x26, /* 10.5.5.11 */
GSM48_IE_GMM_DRX_PARAM = 0x27, /* 10.5.5.6 */
GSM48_IE_GMM_AUTN = 0x28, /* 10.5.3.1.1 */
GSM48_IE_GMM_AUTH_RES_EXT = 0x29, /* 10.5.3.2.1 */
+ GSM48_IE_GMM_TIMER_T3302 = 0x2A, /* 10.5.7.4 */
GSM48_IE_GMM_AUTH_FAIL_PAR = 0x30, /* 10.5.3.2.2 */
GSM48_IE_GMM_MS_NET_CAPA = 0x31, /* 10.5.5.12 */
GSM48_IE_GMM_PDP_CTX_STATUS = 0x32, /* 10.5.7.1 */
GSM48_IE_GMM_PS_LCS_CAPA = 0x33, /* 10.5.5.22 */
GSM48_IE_GMM_GMM_MBMS_CTX_ST = 0x35, /* 10.5.7.6 */
+ GSM48_IE_GMM_TIMER_T3346 = 0x3A, /* 10.5.7.4 */
GSM48_IE_GMM_NET_FEAT_SUPPORT = 0xB0, /* 10.5.5.23 */
};
enum gsm48_gprs_ie_sm {
+ GSM48_IE_GSM_RADIO_PRIO = 0x08, /* 10.5.7.2 */
+ GSM48_IE_GSM_DEV_PROP = 0x0C, /* 10.5.7.8 */
GSM48_IE_GSM_APN = 0x28, /* 10.5.6.1 */
GSM48_IE_GSM_PROTO_CONF_OPT = 0x27, /* 10.5.6.3 */
GSM48_IE_GSM_PDP_ADDR = 0x2b, /* 10.5.6.4 */
GSM48_IE_GSM_AA_TMR = 0x29, /* 10.5.7.3 */
+ GSM48_IE_GSM_QOS = 0x30, /* 10.5.6.5 */
+ GSM48_IE_GSM_TFT = 0x31, /* 10.5.6.12 */
+ GSM48_IE_GSM_LLC_SAPI = 0x32, /* 10.5.6.9 */
+ GSM48_IE_GSM_MBIFOM_CONT = 0x33, /* 10.5.6.21 */
+ GSM48_IE_GSM_PFI = 0x34, /* 10.5.6.11 */
GSM48_IE_GSM_NAME_FULL = 0x43, /* 10.5.3.5a */
GSM48_IE_GSM_NAME_SHORT = 0x45, /* 10.5.3.5a */
GSM48_IE_GSM_TIMEZONE = 0x46, /* 10.5.3.8 */
GSM48_IE_GSM_UTC_AND_TZ = 0x47, /* 10.5.3.9 */
GSM48_IE_GSM_LSA_ID = 0x48, /* 10.5.3.11 */
+ GSM48_IE_GSM_EXT_QOS = 0x5C, /* 10.5.6.5B */
+ GSM48_IE_GSM_EXT_PROTO_CONF_OPT = 0x7B, /* 10.5.6.3a */
/* Fake IEs that are not present on the Layer3 air interface,
* but which we use to simplify internal APIs */
@@ -129,7 +142,7 @@ struct gsm48_ra_upd_ack {
struct gsm48_ra_id ra_id; /* 10.5.5.15 */
uint8_t data[0];
#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 upd_result:4, force_stby:4;
uint8_t ra_upd_timer;
struct gsm48_ra_id ra_id;
@@ -158,7 +171,7 @@ struct gsm48_attach_ack {
struct gsm48_ra_id ra_id; /* 10.5.5.15 */
uint8_t data[0];
#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 force_stby:4, att_result:4;
uint8_t ra_upd_timer;
uint8_t radio_prio;
@@ -176,7 +189,7 @@ struct gsm48_auth_ciph_req {
ac_ref_nr:4; /* 10.5.5.19 */
uint8_t data[0];
#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 imeisv_req:4, ciph_alg:4;
uint8_t ac_ref_nr:4, force_stby:4;
uint8_t data[0];
@@ -190,7 +203,7 @@ struct gsm48_auth_ciph_resp {
spare:4;
uint8_t data[0];
#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 spare:4, ac_ref_nr:4;
uint8_t data[0];
#endif
@@ -465,6 +478,3 @@ struct gsm48_qos {
/* octet 16 */
uint8_t guar_bitrate_down_ext;
};
-
-
-#endif /* _GSM48_GPRS_H */
diff --git a/include/osmocom/gsm/protocol/gsm_04_11.h b/include/osmocom/gsm/protocol/gsm_04_11.h
index 31f25acb..90543020 100644
--- a/include/osmocom/gsm/protocol/gsm_04_11.h
+++ b/include/osmocom/gsm/protocol/gsm_04_11.h
@@ -62,6 +62,16 @@ enum gsm411_rp_ie {
GSM411_IE_RP_CAUSE = 0x42, /* 8.2.5.4 */
};
+/* Sections 8.2.5.1 and 8.2.5.2 set limits on the length of an SMSC-address.
+ * The spec states these limits in terms of min and max values of the length
+ * octet in type 4 IEs SM-RP-OA and SM-RP-DA; these IE length limits translate
+ * into a minimum of 1 digit and a maximum of 20 digits.
+ */
+#define GSM411_SMSC_ADDR_MIN_OCTETS 2
+#define GSM411_SMSC_ADDR_MAX_OCTETS 11
+#define GSM411_SMSC_ADDR_MIN_DIGITS 1
+#define GSM411_SMSC_ADDR_MAX_DIGITS 20
+
/* Chapter 8.2.5.4 Table 8.4 */
enum gsm411_rp_cause {
/* valid only for MO */
diff --git a/include/osmocom/gsm/protocol/gsm_04_12.h b/include/osmocom/gsm/protocol/gsm_04_12.h
index 3f34ee7f..17ac6454 100644
--- a/include/osmocom/gsm/protocol/gsm_04_12.h
+++ b/include/osmocom/gsm/protocol/gsm_04_12.h
@@ -23,7 +23,7 @@ struct gsm412_block_type {
lpd : 2,
spare : 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 spare:1, lpd:2, lb:1, seq_nr:4;
#endif
} __attribute__((packed));
@@ -37,7 +37,7 @@ struct gsm412_sched_msg {
uint8_t cbsms_msg_map[6];
uint8_t data[0];
#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 type:2, beg_slot_nr:6;
uint8_t spare2:1, spare1:1, end_slot_nr:6;
uint8_t cbsms_msg_map[6];
diff --git a/include/osmocom/gsm/protocol/gsm_04_14.h b/include/osmocom/gsm/protocol/gsm_04_14.h
index deb474ec..dddec519 100644
--- a/include/osmocom/gsm/protocol/gsm_04_14.h
+++ b/include/osmocom/gsm/protocol/gsm_04_14.h
@@ -30,7 +30,7 @@ struct gsm414_close_mslot_loop_cmd {
loop_mech:3,
tn:3;
#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 tn:3, loop_mech:3, chc:2;
#endif
} __attribute__((packed));
@@ -43,7 +43,7 @@ struct gsm414_close_mslot_loop_ack {
chc:2,
spare:2;
#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 spare:2, chc:2, loop_mech:3, err_ind:1;
#endif
} __attribute__((packed));
@@ -70,7 +70,7 @@ struct gsm414_gprs_test_mode_cmd {
dl_tx_offset:3,
_spare:4;
#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) */
uint16_t d:12,
spare:3,
l:1;
@@ -86,7 +86,7 @@ struct gsm414_egprs_st_sb_loop_cmd {
dl_tx_offset:3,
m: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 m:1, dl_tx_offset:3, _spare:4;
#endif
} __attribute__((packed));
diff --git a/include/osmocom/gsm/protocol/gsm_08_08.h b/include/osmocom/gsm/protocol/gsm_08_08.h
index 611196aa..1e211dc9 100644
--- a/include/osmocom/gsm/protocol/gsm_08_08.h
+++ b/include/osmocom/gsm/protocol/gsm_08_08.h
@@ -54,7 +54,7 @@ struct dtap_header {
dlci_spare:3,
dlci_cc:2;
#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 dlci_cc:2, dlci_spare:3, dlci_sapi:3;
#endif
};
@@ -168,12 +168,15 @@ enum BSS_MAP_MSG_TYPE {
BSS_MAP_MSG_VGCS_VBS_QUEUING_INDICATION = 30,
BSS_MAP_MSG_UPLINK_RQST = 31,
BSS_MAP_MSG_UPLINK_RQST_ACKNOWLEDGE = 39,
+ BSS_MAP_MSG_VGCS_VBS_ASSIGNMENT_STATUS = 59,
+ BSS_MAP_MSG_VGCS_VBS_AREA_CELL_INFO = 60,
BSS_MAP_MSG_UPLINK_RQST_CONFIRMATION = 73,
BSS_MAP_MSG_UPLINK_RELEASE_INDICATION = 74,
BSS_MAP_MSG_UPLINK_REJECT_CMD = 75,
BSS_MAP_MSG_UPLINK_RELEASE_CMD = 76,
BSS_MAP_MSG_UPLINK_SEIZED_CMD = 77,
BSS_MAP_MSG_VGCS_ADDL_INFO = 0x60,
+ BSS_MAP_MSG_VGCS_SMS = 0x61,
BSS_MAP_MSG_NOTIFICATION_DATA = 0x62,
BSS_MAP_MSG_UPLINK_APP_DATA = 0x63,
@@ -443,13 +446,15 @@ enum gsm0808_chan_indicator {
GSM0808_CHAN_SPEECH = 1,
GSM0808_CHAN_DATA = 2,
GSM0808_CHAN_SIGN = 3,
+ GSM0808_CHAN_SPEECH_CTM_TEXT_TELEPHONY = 4,
};
/* GSM 08.08 3.2.2.11 Channel Type */
+#define GSM0808_DATA_FULL_RPREF GSM0808_DATA_FULL_PREF
enum gsm0808_chan_rate_type_data {
GSM0808_DATA_FULL_BM = 0x8,
GSM0808_DATA_HALF_LM = 0x9,
- GSM0808_DATA_FULL_RPREF = 0xa,
+ GSM0808_DATA_FULL_PREF = 0xa,
GSM0808_DATA_HALF_PREF = 0xb,
GSM0808_DATA_FULL_PREF_NO_CHANGE = 0x1a,
GSM0808_DATA_HALF_PREF_NO_CHANGE = 0x1b,
@@ -498,6 +503,42 @@ enum gsm0808_permitted_speech {
GSM0808_PERM_HR6 = 0x45, /*!< OHR AMR */
};
+/* 3GPP TS 48.008 3.2.2.11 Channel Type
+ * Transparent: Data Rate */
+enum gsm0808_data_rate_transp {
+ GSM0808_DATA_RATE_TRANSP_32k0 = 0x3a,
+ GSM0808_DATA_RATE_TRANSP_28k8 = 0x39,
+ GSM0808_DATA_RATE_TRANSP_14k4 = 0x18,
+ GSM0808_DATA_RATE_TRANSP_9k6 = 0x10,
+ GSM0808_DATA_RATE_TRANSP_4k8 = 0x11,
+ GSM0808_DATA_RATE_TRANSP_2k4 = 0x12,
+ GSM0808_DATA_RATE_TRANSP_1k2 = 0x13,
+ GSM0808_DATA_RATE_TRANSP_600 = 0x14,
+ GSM0808_DATA_RATE_TRANSP_1200_75 = 0x15,
+};
+
+/* 3GPP TS 48.008 3.2.2.11 Channel Type
+ * Non-Transparent: Radio Interface Data Rate (preferred) */
+enum gsm0808_data_rate_non_transp {
+ GSM0808_DATA_RATE_NON_TRANSP_12000_6000 = 0x00,
+ GSM0808_DATA_RATE_NON_TRANSP_43k5 = 0x34,
+ GSM0808_DATA_RATE_NON_TRANSP_29k0 = 0x31,
+ GSM0808_DATA_RATE_NON_TRANSP_14k5 = 0x14,
+ GSM0808_DATA_RATE_NON_TRANSP_12k0 = 0x10,
+ GSM0808_DATA_RATE_NON_TRANSP_6k0 = 0x11,
+};
+
+/* 3GPP TS 48.008 3.2.2.11 Channel Type
+ * Non-Transparent: Allowed Radio Interface Data Rate (all possible allowed) */
+enum gsm0808_data_rate_allowed_r_if {
+ GSM0808_DATA_RATE_NON_TRANSP_ALLOWED_43k5 = 0x40,
+ GSM0808_DATA_RATE_NON_TRANSP_ALLOWED_32k0 = 0x20,
+ GSM0808_DATA_RATE_NON_TRANSP_ALLOWED_29k0 = 0x10,
+ GSM0808_DATA_RATE_NON_TRANSP_ALLOWED_14k5 = 0x08,
+ GSM0808_DATA_RATE_NON_TRANSP_ALLOWED_12k0 = 0x02,
+ GSM0808_DATA_RATE_NON_TRANSP_ALLOWED_6k0 = 0x01,
+};
+
extern const struct value_string gsm0808_permitted_speech_names[];
static inline const char *gsm0808_permitted_speech_name(enum gsm0808_permitted_speech val)
{ return get_value_string(gsm0808_permitted_speech_names, val); }
@@ -516,6 +557,12 @@ enum gsm0808_speech_codec_type {
GSM0808_SCT_CSD = 0xfd, /*!< CSData (see also TS 26.103) */
};
+/* Codec Extension (the real Codec Type follows in the next octet).
+ * This value is intentionally not included in gsm0808_speech_codec_type,
+ * because {enc,dec}_speech_codec() functions take care of the extended
+ * encoding internally. It shall not be used in struct gsm0808_speech_codec. */
+#define GSM0808_SCT_EXT 0x0f
+
extern const struct value_string gsm0808_speech_codec_type_names[];
static inline const char *gsm0808_speech_codec_type_name(enum gsm0808_speech_codec_type val)
{ return get_value_string(gsm0808_speech_codec_type_names, val); }
@@ -568,7 +615,44 @@ struct gsm0808_speech_codec {
*
* Default values for FR_AMR_WB, OFR_AMR_WB and OHR_AMR_WB:
* See also: 3GPP TS 26.103, Table 5.7-1: Allowed Configurations
- * for the Adaptive Multi-Rate - Wideband Codec Types */
+ * for the Adaptive Multi-Rate - Wideband Codec Types
+ *
+ * This is a copy of 3GPP TS 28.062, Table 7.11.3.1.3-2:
+ *
+ * S0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
+ * 12,20 (x) x x x
+ * 10,20 x x x
+ * 7,95 x x x
+ * 7,40 x x x x
+ * 6,70 x x x x x x
+ * 5,90 x x x x x x x x x x
+ * 5,15
+ * 4,75 x x x x x x x x x x
+ *
+ * OM F F F F F F F F F F F A F A F A
+ *
+ * HR Y Y Y Y Y Y Y Y Y
+ * FR Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y Y
+ *
+ * Each bit allows one Codec Configuration.
+ * E.g. when bit S3 is set, look at column labeled "3", and see that only 6,7k is active in this configuration; it is
+ * "F" forbidden to change in Optimisation Mode, "Y" HR AMR supports this mode, and "Y" FR AMR can also do it.
+ *
+ * This means that whichever configuration is chosen from S0 thru S15, there are never more than four rates active.
+ *
+ * The spec praises S1 as the most desired configuration: "because it leads in all call cases to TFO/TrFO compatible
+ * connections with optimal voice quality." (Since HR AMR supports up to 7.95k, it seems that S14 would be more optimal
+ * voice quality, but it is not marked as supported by HR AMR.)
+ *
+ * For FR_AMR below, the default of 0x57ff means:
+ * 0x57ff = 0101 0111 1111 1111
+ * ^14 ^10 ^0
+ * allow config 0 thru 10, and configs 12 and 14.
+ *
+ * For HR_AMR, drop all those where there is no "Y" in the HR row:
+ * 0x073f = 0000 0111 0011 1111
+ * ^15 ^11 ^6 ^0
+ */
enum gsm0808_speech_codec_defaults {
GSM0808_SC_CFG_DEFAULT_FR_AMR = 0x57ff,
GSM0808_SC_CFG_DEFAULT_HR_AMR = 0x073f,
@@ -578,9 +662,12 @@ enum gsm0808_speech_codec_defaults {
GSM0808_SC_CFG_DEFAULT_OHR_AMR_WB = 0x01,
};
-/*! Default speech codec configurations broken down by reate.
+/*! Default speech codec configurations broken down by rate.
* See also: 3GPP TS 28.062, Table 7.11.3.1.3-2: Preferred Configurations for
- * the Adaptive Multi-Rate Codec Types. */
+ * the Adaptive Multi-Rate Codec Types.
+ *
+ * Set all Sn bits that have this rate listed as active.
+ */
enum gsm0808_speech_codec_rate_defaults {
GSM0808_SC_CFG_DEFAULT_AMR_4_75 = 0xff03,
GSM0808_SC_CFG_DEFAULT_AMR_5_15 = 0x0000,
@@ -592,9 +679,12 @@ enum gsm0808_speech_codec_rate_defaults {
GSM0808_SC_CFG_DEFAULT_AMR_12_2 = 0xc082
};
-/*! Single speech codec configurations broken down by reate.
+/*! Single speech codec configurations broken down by rate.
* See also: 3GPP TS 28.062, Table 7.11.3.1.3-2: Preferred Configurations for
- * the Adaptive Multi-Rate Codec Types. */
+ * the Adaptive Multi-Rate Codec Types.
+ *
+ * Set bit Sn (S0 = 0x01), where Sn is identified by a descriptive name.
+ */
enum gsm0808_speech_codec_rate {
GSM0808_SC_CFG_AMR_4_75 = 0x0001,
GSM0808_SC_CFG_AMR_4_75_5_90_7_40_12_20 = 0x0002,
@@ -606,6 +696,29 @@ enum gsm0808_speech_codec_rate {
GSM0808_SC_CFG_AMR_12_2 = 0x0080,
};
+/* Bit index of a mode as returned by gsm0808_amr_modes_from_cfg[].
+ * Example:
+ * if (gsm0808_amr_modes_from_cfg[full_rate ? 1 : 0][9] & GSM0808_AMR_MODE_4_75)
+ * printf("S9 supports 4k75");
+ */
+enum gsm0808_amr_mode {
+ GSM0808_AMR_MODE_4_75 = 0,
+ GSM0808_AMR_MODE_5_15,
+ GSM0808_AMR_MODE_5_90,
+ GSM0808_AMR_MODE_6_70,
+ GSM0808_AMR_MODE_7_40,
+ GSM0808_AMR_MODE_7_95,
+ GSM0808_AMR_MODE_10_2,
+ GSM0808_AMR_MODE_12_2,
+};
+extern const struct value_string gsm0808_amr_mode_names[];
+static inline const char *gsm0808_amr_mode_name(enum gsm0808_amr_mode val)
+{
+ return get_value_string(gsm0808_amr_mode_names, val);
+}
+
+extern const uint8_t gsm0808_amr_modes_from_cfg[2][16];
+
/* 3GPP TS 48.008 3.2.2.103 Speech Codec List */
#define SPEECH_CODEC_MAXLEN 255
struct gsm0808_speech_codec_list {
@@ -613,13 +726,32 @@ struct gsm0808_speech_codec_list {
uint8_t len;
};
+/* 3GPP TS 48.008 3.2.2.11 Channel Type
+ * Asymmetry Preference (used for data, non-transparent service) */
+enum gsm0808_channel_type_asym_pref {
+ GSM0808_CT_ASYM_PREF_NOT_APPLICABLE = 0,
+ GSM0808_CT_ASYM_PREF_UL = 1,
+ GSM0808_CT_ASYM_PREF_DL = 2,
+ GSM0808_CT_ASYM_PREF_SPARE = 3,
+};
+
/* 3GPP TS 48.008 3.2.2.11 Channel Type */
#define CH_TYPE_PERM_SPCH_MAXLEN 9
struct gsm0808_channel_type {
uint8_t ch_indctr;
uint8_t ch_rate_type;
+
+ /* Speech only */
uint8_t perm_spch[CH_TYPE_PERM_SPCH_MAXLEN];
unsigned int perm_spch_len;
+
+ /* Data only */
+ bool data_transparent;
+ uint8_t data_rate;
+ bool data_rate_allowed_is_set;
+ uint8_t data_rate_allowed;
+ bool data_asym_pref_is_set;
+ enum gsm0808_channel_type_asym_pref data_asym_pref;
};
/* 3GPP TS 48.008 3.2.2.10 Encryption Information */
@@ -683,7 +815,7 @@ struct gsm0808_diagnostics {
uint8_t error_pointer_bit_spare:4,
error_pointer_bit:4;
#elif OSMO_IS_BIG_ENDIAN
-/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianess.py) */
+/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianness.py) */
uint8_t error_pointer_bit:4, error_pointer_bit_spare:4;
#endif
uint8_t msg[0]; /*! received message which provoked the error */
diff --git a/include/osmocom/gsm/protocol/gsm_08_58.h b/include/osmocom/gsm/protocol/gsm_08_58.h
index 7ccd918a..0a44db90 100644
--- a/include/osmocom/gsm/protocol/gsm_08_58.h
+++ b/include/osmocom/gsm/protocol/gsm_08_58.h
@@ -126,7 +126,7 @@ struct abis_rsl_osmo_rep_acch_cap {
rxqual:3,
reserved: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 reserved:1, rxqual:3, ul_sacch:1, dl_sacch:1, dl_facch_all:1, dl_facch_cmd:1;
#endif
} __attribute__ ((packed));
@@ -139,7 +139,7 @@ struct abis_rsl_osmo_temp_ovp_acch_cap {
facch_enable:1,
sacch_enable: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 sacch_enable:1, facch_enable:1, rxqual:3, overpower_db:3;
#endif
} __attribute__ ((packed));
@@ -496,51 +496,66 @@ enum rsl_cmod_spd {
RSL_CMOD_SPD_DATA = 0x02,
RSL_CMOD_SPD_SIGN = 0x03,
};
-#define RSL_CMOD_CRT_SDCCH 0x01
-#define RSL_CMOD_CRT_TCH_Bm 0x08 /* full-rate */
-#define RSL_CMOD_CRT_TCH_Lm 0x09 /* half-rate */
-#define RSL_CMOD_CRT_TCH_BI_Bm 0x0a /* full-rate: bi-directional (multislot) */
-#define RSL_CMOD_CRT_TCH_UNI_Bm 0x1a /* full-rate: uni-directional (multislot) */
-#define RSL_CMOD_CRT_TCH_GROUP_Bm 0x18 /* full-rate: group call channel */
-#define RSL_CMOD_CRT_TCH_GROUP_Lm 0x19 /* half-rate: group call channel */
-#define RSL_CMOD_CRT_TCH_BCAST_Bm 0x28 /* full-rate: broadcast call channel */
-#define RSL_CMOD_CRT_TCH_BCAST_Lm 0x29 /* half-rate: broadcast call channel */
-#define RSL_CMOD_CRT_OSMO_TCH_VAMOS_Bm 0x88 /* full-rate in VAMOS mode */
-#define RSL_CMOD_CRT_OSMO_TCH_VAMOS_Lm 0x89 /* half-rate in VAMOS mode */
-/* Speech */
-#define RSL_CMOD_SP_GSM1 0x01
-#define RSL_CMOD_SP_GSM2 0x11
-#define RSL_CMOD_SP_GSM3 0x21
-#define RSL_CMOD_SP_GSM4 0x31
-#define RSL_CMOD_SP_GSM5 0x09
-#define RSL_CMOD_SP_GSM6 0x0d
-/* non-transparent data (asymmetric) */
-#define RSL_CMOD_CSD_NTA_43k5_14k5 0x61 /* asymmetric 43.5 kbit/s (DL) + 14.5 kbit/s (UL) */
-#define RSL_CMOD_CSD_NTA_29k0_14k5 0x62 /* asymmetric 29.0 kbit/s (DL) + 14.5 kbit/s (UL) */
-#define RSL_CMOD_CSD_NTA_43k5_29k0 0x63 /* asymmetric 43.5 kbit/s (DL) + 29.0 kbit/s (UL) */
-#define RSL_CMOD_CSD_NTA_14k5_43k5 0x69 /* asymmetric 14.5 kbit/s (DL) + 43.5 kbit/s (UL) */
-#define RSL_CMOD_CSD_NTA_14k5_29k0 0x6a /* asymmetric 14.5 kbit/s (DL) + 29.0 kbit/s (UL) */
-#define RSL_CMOD_CSD_NTA_29k0_43k5 0x6b /* asymmetric 29.0 kbit/s (DL) + 43.5 kbit/s (UL) */
-/* non-transparent data */
-#define RSL_CMOD_CSD_NT_43k5 0x74
-#define RSL_CMOD_CSD_NT_28k8 0x71
-#define RSL_CMOD_CSD_NT_14k5 0x58
-#define RSL_CMOD_CSD_NT_12k0 0x50
-#define RSL_CMOD_CSD_NT_6k0 0x51
+/*! Channel rate and type */
+enum rsl_cmod_crt {
+ RSL_CMOD_CRT_SDCCH = 0x01,
+ RSL_CMOD_CRT_TCH_Bm = 0x08, /* full-rate */
+ RSL_CMOD_CRT_TCH_Lm = 0x09, /* half-rate */
+ RSL_CMOD_CRT_TCH_BI_Bm = 0x0a, /* full-rate: bi-directional (multislot) */
+ RSL_CMOD_CRT_TCH_UNI_Bm = 0x1a, /* full-rate: uni-directional (multislot) */
+ RSL_CMOD_CRT_TCH_GROUP_Bm = 0x18, /* full-rate: group call channel */
+ RSL_CMOD_CRT_TCH_GROUP_Lm = 0x19, /* half-rate: group call channel */
+ RSL_CMOD_CRT_TCH_BCAST_Bm = 0x28, /* full-rate: broadcast call channel */
+ RSL_CMOD_CRT_TCH_BCAST_Lm = 0x29, /* half-rate: broadcast call channel */
+ RSL_CMOD_CRT_OSMO_TCH_VAMOS_Bm = 0x88, /* full-rate in VAMOS mode */
+ RSL_CMOD_CRT_OSMO_TCH_VAMOS_Lm = 0x89, /* half-rate in VAMOS mode */
+};
+/*! Speech */
+enum rsl_cmod_sp {
+ RSL_CMOD_SP_GSM1 = 0x01,
+ RSL_CMOD_SP_GSM2 = 0x11,
+ RSL_CMOD_SP_GSM3 = 0x21,
+ RSL_CMOD_SP_GSM4 = 0x31,
+ RSL_CMOD_SP_GSM5 = 0x09,
+ RSL_CMOD_SP_GSM6 = 0x0d,
+};
+/*! Non-transparent data */
+enum rsl_cmod_csd_nt {
+ RSL_CMOD_CSD_NTA_43k5_14k5 = 0x61, /* asymmetric 43.5 kbit/s (DL) + 14.5 kbit/s (UL) */
+ RSL_CMOD_CSD_NTA_29k0_14k5 = 0x62, /* asymmetric 29.0 kbit/s (DL) + 14.5 kbit/s (UL) */
+ RSL_CMOD_CSD_NTA_43k5_29k0 = 0x63, /* asymmetric 43.5 kbit/s (DL) + 29.0 kbit/s (UL) */
+ RSL_CMOD_CSD_NTA_14k5_43k5 = 0x69, /* asymmetric 14.5 kbit/s (DL) + 43.5 kbit/s (UL) */
+ RSL_CMOD_CSD_NTA_14k5_29k0 = 0x6a, /* asymmetric 14.5 kbit/s (DL) + 29.0 kbit/s (UL) */
+ RSL_CMOD_CSD_NTA_29k0_43k5 = 0x6b, /* asymmetric 29.0 kbit/s (DL) + 43.5 kbit/s (UL) */
+ RSL_CMOD_CSD_NT_43k5 = 0x74,
+ RSL_CMOD_CSD_NT_28k8 = 0x71,
+ RSL_CMOD_CSD_NT_14k5 = 0x58,
+ RSL_CMOD_CSD_NT_12k0 = 0x50,
+ RSL_CMOD_CSD_NT_6k0 = 0x51,
+};
/* legacy #defines with wrong name */
#define RSL_CMOD_SP_NT_14k5 RSL_CMOD_CSD_NT_14k5
#define RSL_CMOD_SP_NT_12k0 RSL_CMOD_CSD_NT_12k0
#define RSL_CMOD_SP_NT_6k0 RSL_CMOD_CSD_NT_6k0
-/* transparent data */
-#define RSL_CMOD_CSD_T_32000 0x38
-#define RSL_CMOD_CSD_T_29000 0x39
-#define RSL_CMOD_CSD_T_14400 0x18
-#define RSL_CMOD_CSD_T_9600 0x10
-#define RSL_CMOD_CSD_T_4800 0x11
-#define RSL_CMOD_CSD_T_2400 0x12
-#define RSL_CMOD_CSD_T_1200 0x13
-#define RSL_CMOD_CSD_T_600 0x14
-#define RSL_CMOD_CSD_T_1200_75 0x15
+#define RSL_CMOD_CSD_T_32000 RSL_CMOD_CSD_T_32k0
+#define RSL_CMOD_CSD_T_29000 RSL_CMOD_CSD_T_29k0
+#define RSL_CMOD_CSD_T_14400 RSL_CMOD_CSD_T_14k4
+#define RSL_CMOD_CSD_T_9600 RSL_CMOD_CSD_T_9k6
+#define RSL_CMOD_CSD_T_4800 RSL_CMOD_CSD_T_4k8
+#define RSL_CMOD_CSD_T_2400 RSL_CMOD_CSD_T_2k4
+#define RSL_CMOD_CSD_T_1200 RSL_CMOD_CSD_T_1k2
+/*! Transparent data */
+enum rsl_cmod_csd_t {
+ RSL_CMOD_CSD_T_32k0 = 0x38,
+ RSL_CMOD_CSD_T_29k0 = 0x39,
+ RSL_CMOD_CSD_T_14k4 = 0x18,
+ RSL_CMOD_CSD_T_9k6 = 0x10,
+ RSL_CMOD_CSD_T_4k8 = 0x11,
+ RSL_CMOD_CSD_T_2k4 = 0x12,
+ RSL_CMOD_CSD_T_1k2 = 0x13,
+ RSL_CMOD_CSD_T_600 = 0x14,
+ RSL_CMOD_CSD_T_1200_75 = 0x15,
+};
/*! RSL Channel Identification IE (Chapter 9.3.5) */
struct rsl_ie_chan_ident {
@@ -658,7 +673,7 @@ struct rsl_ie_chan_ident {
#define RSL_CHANNEED_TCH_F 0x02
#define RSL_CHANNEED_TCH_ForH 0x03
-/*! RSL Cell Broadcast Command (Chapter 9.3.45) */
+/*! RSL Cell Broadcast Command (Chapter 9.3.41) */
struct rsl_ie_cb_cmd_type {
#if OSMO_IS_LITTLE_ENDIAN
uint8_t last_block:2;
@@ -666,7 +681,7 @@ struct rsl_ie_cb_cmd_type {
uint8_t def_bcast:1;
uint8_t command:4;
#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 command:4, def_bcast:1, spare:1, last_block:2;
#endif
} __attribute__ ((packed));
@@ -684,6 +699,23 @@ struct rsl_ie_cb_cmd_type {
#define RSL_CB_CMD_LASTBLOCK_2 2
#define RSL_CB_CMD_LASTBLOCK_3 3
+/*! NCH DRX Information (Chapter 9.3.47) */
+struct rsl_ie_nch_drx_info {
+#if OSMO_IS_LITTLE_ENDIAN
+ uint8_t nln:2;
+ uint8_t emlpp_priority:3;
+ uint8_t nln_status:1;
+ uint8_t spare:2;
+#elif OSMO_IS_BIG_ENDIAN
+/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianness.py) */
+ uint8_t spare:2, nln_status:1, emlpp_priority:3, nln:2;
+#endif
+} __attribute__ ((packed));
+
+/*! Command Indicator (Chapter 9.3.48) */
+#define RSL_CMD_INDICATOR_START 0x00
+#define RSL_CMD_INDICATOR_STOP 0x01
+
/* Chapter 3.3.2.3 Brocast control channel */
/* CCCH-CONF, NC is not combined */
#define RSL_BCCH_CCCH_CONF_1_NC 0x00
@@ -729,10 +761,10 @@ enum rsl_ipac_speech_mode_m {
/* RSL_IE_IPAC_RTP_CSD_FMT, lower four bits */
enum rsl_ipac_rtp_csd_format_d {
- RSL_IPAC_RTP_CSD_EXT_TRAU = 0,
- RSL_IPAC_RTP_CSD_NON_TRAU = 1,
- RSL_IPAC_RTP_CSD_TRAU_BTS = 2,
- RSL_IPAC_RTP_CSD_IWF_FREE = 3,
+ RSL_IPAC_RTP_CSD_EXT_TRAU = 0, /*!< TRAU-like RTP format, without leading zero-bits */
+ RSL_IPAC_RTP_CSD_NON_TRAU = 1, /*!< packed 16k (252/288 bit) / 8k (126 bit) in RTP */
+ RSL_IPAC_RTP_CSD_TRAU_BTS = 2, /*!< TRAU in BTS; V.110 in RTP/CLEARMODE */
+ RSL_IPAC_RTP_CSD_IWF_FREE = 3, /*!< unknown proprietary IWF-free BTS-BTS data */
};
/* RSL_IE_IPAC_RTP_CSD_FMT, upper four bits */
enum rsl_ipac_rtp_csd_format_ir {
@@ -817,7 +849,7 @@ struct ipac_preproc_ave_cfg {
ave_method:3;
uint8_t params[0];
#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 reserved:1, param_id:2, h_reqave:5;
uint8_t ave_method:3, h_reqt:5;
uint8_t params[0];
@@ -833,7 +865,7 @@ struct osmo_preproc_ave_cfg_field {
uint8_t h_reqt:5,
ave_method:3;
#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 reserved:2, ave_enabled:1, h_reqave:5;
uint8_t ave_method:3, h_reqt:5;
#endif
@@ -857,7 +889,7 @@ struct ipac_preproc_pc_thresh {
uint8_t u_rxqual:3, reserved_u_rxqual:1,
l_rxqual:3, reserved_l_rxqual: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 reserved_l_rxlev:2, l_rxlev:6;
uint8_t reserved_u_rxlev:2, u_rxlev:6;
uint8_t reserved_l_rxqual:1, l_rxqual:3, reserved_u_rxqual:1, u_rxqual:3;
@@ -893,7 +925,7 @@ struct ipac_preproc_ho_thresh {
uint8_t ms_range_max:6,
reserved_ms_range:2;
#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 reserved_l_rxlev_ul:2, l_rxlev_ul_h:6;
uint8_t reserved_l_rxlev_dl:2, l_rxlev_dl_h:6;
uint8_t reserved_rxlev_ul:2, rxlev_ul_ih:6;
@@ -917,7 +949,7 @@ struct ipac_preproc_pc_comp {
uint8_t pc_interval:5, reserved_pc:3;
uint8_t red_step_size:4, inc_step_size:4;
#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 reserved_p1:3, p1:5;
uint8_t reserved_n1:3, n1:5;
uint8_t reserved_p2:3, p2:5;
@@ -939,7 +971,7 @@ struct ipac_preproc_pc_comp_field {
uint8_t upper_p:5, reserved_upper_p:3;
uint8_t upper_n:5, reserved_upper_n:3;
#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 reserved_lower_p:3, lower_p:5;
uint8_t reserved_lower_n:3, lower_n:5;
uint8_t reserved_upper_p:3, upper_p:5;
@@ -980,7 +1012,7 @@ struct ipac_preproc_ho_comp {
uint8_t reserved;
#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 reserved_p5:3, p5:5;
uint8_t reserved_n5:3, n5:5;
uint8_t reserved_p6:3, p6:5;
@@ -1003,7 +1035,7 @@ struct ipac_preproc_ho_candidates {
s:1,
reserved1: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 reserved0:2, bsic:6;
uint8_t reserved1:1, s:1, ba_used:1, bcch_freq:5;
#endif
@@ -1018,7 +1050,7 @@ struct ipac_preproc_ncell_dflts {
uint8_t ms_txpwr_max_def:5,
reserved_ms_txpwr_max_def:3;
#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 reserved_rxlev_min_def:2, rxlev_min_def:6;
uint8_t reserved_ho_margin_def:3, ho_margin_def:5;
uint8_t reserved_ms_txpwr_max_def:3, ms_txpwr_max_def:5;
@@ -1031,7 +1063,7 @@ struct ipac_preproc_ho_ctl_param {
sdcch_ho_umts:1,
reserved:6;
#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 reserved:6, sdcch_ho_umts:1, sdcch_ho_gsm:1;
#endif
}__attribute__ ((packed));
@@ -1055,7 +1087,7 @@ struct rsl_l1_info {
ms_pwr:5;
uint8_t ta;
#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 ms_pwr:5, fpc_epc:1, srr_sro:1, reserved:1;
uint8_t ta;
#endif
diff --git a/include/osmocom/gsm/protocol/gsm_09_02.h b/include/osmocom/gsm/protocol/gsm_09_02.h
index 0b54fb74..4d5ff13c 100644
--- a/include/osmocom/gsm/protocol/gsm_09_02.h
+++ b/include/osmocom/gsm/protocol/gsm_09_02.h
@@ -1,8 +1,7 @@
/*! \file gsm_09_02.h
* GSM TS 09.02 definitions (MAP). */
-#ifndef PROTO_GSM_09_02_H
-#define PROTO_GSM_09_02_H
+#pragma once
/* Section 17.7.4 */
/* SS-Status */
@@ -134,5 +133,3 @@
#define GSM0902_TS_CODE_PLMN_SPECIFIC_TS_C 0xDC
#define GSM0902_TS_CODE_PLMN_SPECIFIC_TS_D 0xDD
#define GSM0902_TS_CODE_PLMN_SPECIFIC_TS_E 0xDE
-
-#endif /* PROTO_GSM_09_02_H */
diff --git a/include/osmocom/gsm/protocol/gsm_12_21.h b/include/osmocom/gsm/protocol/gsm_12_21.h
index 96a6f51b..39b1d45c 100644
--- a/include/osmocom/gsm/protocol/gsm_12_21.h
+++ b/include/osmocom/gsm/protocol/gsm_12_21.h
@@ -775,6 +775,98 @@ enum ipac_eie {
NM_IPAC_EIE_BTS_ID = 0x25,
};
+/*! ip.access support flags for NM_IPAC_EIE_FREQ_BANDS */
+#define NM_IPAC_F_FREQ_BAND_PGSM (1 << 0)
+#define NM_IPAC_F_FREQ_BAND_EGSM (1 << 1)
+#define NM_IPAC_F_FREQ_BAND_RGSM (1 << 2)
+#define NM_IPAC_F_FREQ_BAND_DCS (1 << 3)
+#define NM_IPAC_F_FREQ_BAND_PCS (1 << 4)
+#define NM_IPAC_F_FREQ_BAND_850 (1 << 5)
+#define NM_IPAC_F_FREQ_BAND_480 (1 << 6)
+#define NM_IPAC_F_FREQ_BAND_450 (1 << 7)
+
+/*! ip.access support flags for NM_IPAC_EIE_CIPH_ALGOS */
+#define NM_IPAC_F_CIPH_ALGO_A51 (1 << 0)
+#define NM_IPAC_F_CIPH_ALGO_A52 (1 << 1)
+#define NM_IPAC_F_CIPH_ALGO_A53 (1 << 2)
+#define NM_IPAC_F_CIPH_ALGO_A54 (1 << 3)
+#define NM_IPAC_F_CIPH_ALGO_A55 (1 << 4)
+#define NM_IPAC_F_CIPH_ALGO_A56 (1 << 5)
+#define NM_IPAC_F_CIPH_ALGO_A57 (1 << 6)
+#define NM_IPAC_F_CIPH_ALGO_A58 (1 << 7)
+
+/*! ip.access support flags for NM_IPAC_EIE_CHAN_TYPES (1st octet) */
+#define NM_IPAC_F_CHANT_TCHF (1 << 0)
+#define NM_IPAC_F_CHANT_TCHH (1 << 1)
+#define NM_IPAC_F_CHANT_SDCCH8 (1 << 2)
+#define NM_IPAC_F_CHANT_BCCH (1 << 3)
+#define NM_IPAC_F_CHANT_BCCH_SDCCH4 (1 << 4)
+#define NM_IPAC_F_CHANT_BCH (1 << 5)
+#define NM_IPAC_F_CHANT_BCCH_SDCCH4_CBCH (1 << 6)
+#define NM_IPAC_F_CHANT_SDCCH8_CBCH (1 << 7)
+/*! ip.access support flags for NM_IPAC_EIE_CHAN_TYPES (2nd octet) */
+#define NM_IPAC_F_CHANT_PDCHF (1 << 8)
+#define NM_IPAC_F_CHANT_TCHF_PDCHF (1 << 9)
+#define NM_IPAC_F_CHANT_TCHH_PDCHH (1 << 10)
+#define NM_IPAC_F_CHANT_TCHF_TCHH (1 << 11)
+
+/*! ip.access support flags for NM_IPAC_EIE_CHAN_MODES (speech codecs) */
+#define NM_IPAC_F_CHANM_SPEECH_FS (1 << 0)
+#define NM_IPAC_F_CHANM_SPEECH_EFS (1 << 1)
+#define NM_IPAC_F_CHANM_SPEECH_AFS (1 << 2)
+#define NM_IPAC_F_CHANM_SPEECH_HS (1 << 3)
+#define NM_IPAC_F_CHANM_SPEECH_AHS (1 << 4)
+/*! ip.access support flags for NM_IPAC_EIE_CHAN_MODES (CSD non-transparent) */
+#define NM_IPAC_F_CHANM_CSD_NT_4k8 (1 << 8)
+#define NM_IPAC_F_CHANM_CSD_NT_9k6 (1 << 9)
+#define NM_IPAC_F_CHANM_CSD_NT_14k4 (1 << 10)
+/*! ip.access support flags for NM_IPAC_EIE_CHAN_MODES (CSD transparent) */
+#define NM_IPAC_F_CHANM_CSD_T_1200_75 (1 << 16)
+#define NM_IPAC_F_CHANM_CSD_T_600 (1 << 17)
+#define NM_IPAC_F_CHANM_CSD_T_1k2 (1 << 18)
+#define NM_IPAC_F_CHANM_CSD_T_2k4 (1 << 19)
+#define NM_IPAC_F_CHANM_CSD_T_4k8 (1 << 20)
+#define NM_IPAC_F_CHANM_CSD_T_9k6 (1 << 21)
+#define NM_IPAC_F_CHANM_CSD_T_14k4 (1 << 22)
+
+/*! ip.access support flags for NM_IPAC_EIE_GPRS_CODING (GPRS) */
+#define NM_IPAC_F_GPRS_CODING_CS1 (1 << 0)
+#define NM_IPAC_F_GPRS_CODING_CS2 (1 << 1)
+#define NM_IPAC_F_GPRS_CODING_CS3 (1 << 2)
+#define NM_IPAC_F_GPRS_CODING_CS4 (1 << 3)
+/*! ip.access support flags for NM_IPAC_EIE_GPRS_CODING (EGPRS) */
+#define NM_IPAC_F_GPRS_CODING_MCS1 (1 << 7)
+#define NM_IPAC_F_GPRS_CODING_MCS2 (1 << 8)
+#define NM_IPAC_F_GPRS_CODING_MCS3 (1 << 9)
+#define NM_IPAC_F_GPRS_CODING_MCS4 (1 << 10)
+#define NM_IPAC_F_GPRS_CODING_MCS5 (1 << 11)
+#define NM_IPAC_F_GPRS_CODING_MCS6 (1 << 12)
+#define NM_IPAC_F_GPRS_CODING_MCS7 (1 << 13)
+#define NM_IPAC_F_GPRS_CODING_MCS8 (1 << 14)
+#define NM_IPAC_F_GPRS_CODING_MCS9 (1 << 15)
+
+/*! ip.access support flags for NM_IPAC_EIE_RTP_FEATURES */
+#define NM_IPAC_F_RTP_FEAT_COMPR_CONTROL (1 << 0) /* RTP Compression Control */
+#define NM_IPAC_F_RTP_FEAT_IR_8k (1 << 1) /* IR 8 kbit/s */
+#define NM_IPAC_F_RTP_FEAT_IR_16k (1 << 2) /* IR 16 kbit/s */
+#define NM_IPAC_F_RTP_FEAT_IR_32k (1 << 3) /* IR 32 kbit/s */
+#define NM_IPAC_F_RTP_FEAT_IR_64k (1 << 4) /* IR 64 kbit/s */
+#define NM_IPAC_F_RTP_FEAT_MULTIPLEX_RTP (1 << 6) /* RTP Multiplexing */
+#define NM_IPAC_F_RTP_FEAT_MULTIPLEX_SRTP (1 << 7) /* SRTP Multiplexing */
+
+/*! ip.access support flags for NM_IPAC_EIE_RSL_FEATURES */
+#define NM_IPAC_F_RSL_FEAT_PHYSICAL_CONTEXT (1 << 0)
+#define NM_IPAC_F_RSL_FEAT_DYN_PDCH_ACT (1 << 1)
+#define NM_IPAC_F_RSL_FEAT_RTP_PT2 (1 << 2)
+
+extern const struct value_string abis_nm_ipacc_freq_band_desc[];
+extern const struct value_string abis_nm_ipacc_ciph_algo_desc[];
+extern const struct value_string abis_nm_ipacc_chant_desc[];
+extern const struct value_string abis_nm_ipacc_chanm_desc[];
+extern const struct value_string abis_nm_ipacc_gprs_coding_desc[];
+extern const struct value_string abis_nm_ipacc_rtp_feat_desc[];
+extern const struct value_string abis_nm_ipacc_rsl_feat_desc[];
+
/*! ip.access NWL BCCH information type */
enum ipac_bcch_info_type {
IPAC_BINF_RXLEV = (1 << 8),
@@ -790,6 +882,62 @@ enum ipac_bcch_info_type {
IPAC_BINF_CELL_ALLOC = (1 << 2),
};
+/*! ip.access NM_ATT_IPACC_NS_CFG value */
+struct abis_nm_ipacc_att_ns_cfg {
+ uint8_t un_blocking_timer; /* (un)blocking Timer (Tns-block) timeout */
+ uint8_t un_blocking_retries; /* (un)blocking Timer (Tns-block) number of retries */
+ uint8_t reset_timer; /* Reset Timer (Tns-reset) timeout */
+ uint8_t reset_retries; /* Reset Timer (Tns-reset) number of retries */
+ uint8_t test_timer; /* Test Timer (Tns-test) timeout */
+ uint8_t alive_timer; /* Alive Timer (Tns-alive) timeout */
+ uint8_t alive_retries; /* Alive Timer (Tns-alive) number of retries */
+} __attribute__((packed));
+
+/*! ip.access NM_ATT_IPACC_BSSGP_CFG value */
+struct abis_nm_ipacc_att_bssgp_cfg {
+ uint8_t t1_s; /* blocking timer (T1) */
+ uint8_t t1_blocking_retries; /* blocking retries */
+ uint8_t t1_unblocking_retries; /* unblocking retries */
+ uint8_t t2_s; /* reset timer (T2) */
+ uint8_t t2_retries; /* reset retries */
+ uint8_t t3_100ms; /* suspend timer (T3) in 100ms */
+ uint8_t t3_retries; /* suspend retries */
+ uint8_t t4_100ms; /* resume timer (T4) in 100ms */
+ uint8_t t4_retries; /* resume retries */
+ uint8_t t5_s; /* capability update timer (T5) */
+ uint8_t t5_retries; /* capability update retries */
+} __attribute__((packed));
+
+/*! ip.access NM_ATT_IPACC_RLC_CFG value */
+struct abis_nm_ipacc_att_rlc_cfg {
+ uint8_t t3142;
+ uint8_t t3169;
+ uint8_t t3191;
+ uint8_t t3193_10ms;
+ uint8_t t3195;
+ uint8_t n3101;
+ uint8_t n3103;
+ uint8_t n3105;
+ uint8_t rlc_cv_countdown;
+} __attribute__((packed));
+
+/*! ip.access NM_ATT_IPACC_RLC_CFG_2 value */
+struct abis_nm_ipacc_att_rlc_cfg_2 {
+ /* T downlink TBF extension (0..500, network order) */
+ uint16_t t_dl_tbf_ext_10ms;
+ /* T uplink TBF extension (0..500, network order) */
+ uint16_t t_ul_tbf_ext_10ms;
+ /* Initial CS to use: CS1 -> 1, CS2 -> 2, CS3 -> 3, CS4 -> 4 */
+ uint8_t initial_cs;
+} __attribute__((packed));
+
+/*! ip.access NM_ATT_IPACC_RLC_CFG_3 value */
+struct abis_nm_ipacc_att_rlc_cfg_3 {
+ /* Initial MCS to use when EGPRS is used:
+ * MCS1 -> 1, MCS2 -> 2, ..., MCS9 -> 9 */
+ uint8_t initial_mcs;
+} __attribute__((packed));
+
/*! Osmocom NSVC address type for NM_ATT_OSMO_NS_LINK_CFG */
enum osmo_oml_nsvc_address_type {
OSMO_NSVC_ADDR_UNSPEC = 0x00,
diff --git a/include/osmocom/gsm/protocol/gsm_23_032.h b/include/osmocom/gsm/protocol/gsm_23_032.h
index a4c05061..6eb65ca2 100644
--- a/include/osmocom/gsm/protocol/gsm_23_032.h
+++ b/include/osmocom/gsm/protocol/gsm_23_032.h
@@ -54,7 +54,7 @@ struct gad_raw_head {
uint8_t spare:4,
type:4;
#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 type:4, spare:4;
#endif
} __attribute__ ((packed));
@@ -73,7 +73,7 @@ struct gad_raw_ell_point_unc_circle {
uint8_t unc:7,
spare2: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) */
struct gad_raw_head h;
uint8_t lat[3];
uint8_t lon[3];
@@ -94,7 +94,7 @@ struct gad_raw_ell_point_unc_ellipse {
uint8_t confidence:7,
spare3: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) */
struct gad_raw_head h;
uint8_t lat[3];
uint8_t lon[3];
@@ -111,7 +111,7 @@ struct gad_raw_polygon {
uint8_t num_points:4;
uint8_t type:4; /*!< type = GAD_TYPE_POLYGON */
#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 type:4, num_points:4;
#endif
} h;
@@ -144,7 +144,7 @@ struct gad_raw_ell_point_alt_unc_ell {
uint8_t confidence:7,
spare4: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) */
struct gad_raw_head h;
uint8_t lat[3];
uint8_t lon[3];
@@ -170,7 +170,7 @@ struct gad_raw_ell_arc {
uint8_t confidence:7,
spare2: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) */
struct gad_raw_head h;
uint8_t lat[3];
uint8_t lon[3];
@@ -194,7 +194,7 @@ struct gad_raw_ha_ell_point_unc_ell {
uint8_t confidence:7,
spare1: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) */
struct gad_raw_head h;
uint8_t lat[4];
uint8_t lon[4];
@@ -221,7 +221,7 @@ struct gad_raw_ha_ell_point_alt_unc_ell {
uint8_t v_confidence:7,
spare2: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) */
struct gad_raw_head h;
uint8_t lat[4];
uint8_t lon[4];
diff --git a/include/osmocom/gsm/protocol/gsm_23_041.h b/include/osmocom/gsm/protocol/gsm_23_041.h
index 564946c6..2a2b006f 100644
--- a/include/osmocom/gsm/protocol/gsm_23_041.h
+++ b/include/osmocom/gsm/protocol/gsm_23_041.h
@@ -22,7 +22,7 @@ struct gsm23041_msg_param_gsm {
uint8_t num_pages:4,
page_nr:4;
#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 page_nr:4, num_pages:4;
#endif
} page_param;
diff --git a/include/osmocom/gsm/protocol/gsm_25_415.h b/include/osmocom/gsm/protocol/gsm_25_415.h
index 5d90fd35..5c4dd2bb 100644
--- a/include/osmocom/gsm/protocol/gsm_25_415.h
+++ b/include/osmocom/gsm/protocol/gsm_25_415.h
@@ -24,7 +24,7 @@ struct iuup_pdutype0_hdr {
/* payload part */
uint8_t payload[0];
#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 pdu_type:4, frame_nr:4;
uint8_t fqc:2, rfci:6;
uint8_t header_crc:6, payload_crc_hi:2;
@@ -47,7 +47,7 @@ struct iuup_pdutype1_hdr {
/* payload part */
uint8_t payload[0];
#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 pdu_type:4, frame_nr:4;
uint8_t fqc:2, rfci:6;
uint8_t header_crc:6, spare:2;
@@ -70,7 +70,7 @@ struct iuup_pdutype14_hdr {
/* payload part */
uint8_t payload[0];
#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 pdu_type:4, ack_nack:2, frame_nr:2;
uint8_t mode_version:4, proc_ind:4;
uint8_t header_crc:6, payload_crc_hi:2;
@@ -87,7 +87,7 @@ struct iuup_ctrl_init_rfci_hdr {
lri:1;
uint8_t subflow_length[0]; /* 1 or 2 bytes depending on li */
#elif OSMO_IS_BIG_ENDIAN
-/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianess.py) */
+/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianness.py) */
uint8_t lri:1, li:1, rfci:6;
uint8_t subflow_length[0];
#endif
@@ -100,7 +100,7 @@ struct iuup_ctrl_init_hdr {
spare:3;
uint8_t rfci_data[0]; /* struct iuup_ctrl_init_rfci_hdr* */
#elif OSMO_IS_BIG_ENDIAN
-/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianess.py) */
+/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianness.py) */
uint8_t spare:3, ti:1, num_subflows_per_rfci:3, chain_ind:1;
uint8_t rfci_data[0]; /* struct iuup_ctrl_init_rfci_hdr* */
;
@@ -113,7 +113,7 @@ struct iuup_ctrl_init_tail {
data_pdu_type:4;
uint8_t spare_extension[0];
#elif OSMO_IS_BIG_ENDIAN
-/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianess.py) */
+/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianness.py) */
uint16_t versions_supported;
uint8_t data_pdu_type:4, spare:4;
uint8_t spare_extension[0];
@@ -128,7 +128,7 @@ struct iuup_ctrl_error_event {
error_distance:2;
uint8_t spare_extension[0];
#elif OSMO_IS_BIG_ENDIAN
-/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianess.py) */
+/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianness.py) */
struct iuup_pdutype14_hdr hdr;
uint8_t error_distance:2, error_cause:6;
uint8_t spare_extension[0];
@@ -149,7 +149,7 @@ struct iuup_ctrl_nack {
error_cause:6;
uint8_t spare_extension[0];
#elif OSMO_IS_BIG_ENDIAN
-/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianess.py) */
+/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianness.py) */
struct iuup_pdutype14_hdr hdr;
uint8_t error_cause:6, spare:2;
uint8_t spare_extension[0];
diff --git a/include/osmocom/gsm/protocol/gsm_44_004.h b/include/osmocom/gsm/protocol/gsm_44_004.h
index 616e436a..c30ba0c9 100644
--- a/include/osmocom/gsm/protocol/gsm_44_004.h
+++ b/include/osmocom/gsm/protocol/gsm_44_004.h
@@ -12,7 +12,7 @@ struct gsm_sacch_l1_hdr {
reserved:1;
uint8_t ta;
#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 reserved:1, srr_sro:1, fpc_epc:1, ms_pwr:5;
uint8_t ta;
#endif
diff --git a/include/osmocom/gsm/protocol/gsm_44_060.h b/include/osmocom/gsm/protocol/gsm_44_060.h
new file mode 100644
index 00000000..1df2f800
--- /dev/null
+++ b/include/osmocom/gsm/protocol/gsm_44_060.h
@@ -0,0 +1,252 @@
+/*! \file gsm_44_060.h
+ * General Packet Radio Service (GPRS).
+ * Radio Link Control / Medium Access Control (RLC/MAC) protocol
+ * 3GPP TS 44.060
+ */
+
+#pragma once
+
+#include <stdint.h>
+#include <osmocom/core/endian.h>
+
+/* TS 44.060 10.3a.4.1.1 */
+struct gprs_rlc_ul_header_egprs_1 {
+#if OSMO_IS_LITTLE_ENDIAN
+ uint8_t r:1,
+ si:1,
+ cv:4,
+ tfi_hi:2;
+ uint8_t tfi_lo:3,
+ bsn1_hi:5;
+ uint8_t bsn1_lo:6,
+ bsn2_hi:2;
+ uint8_t bsn2_lo:8;
+ uint8_t cps:5,
+ rsb:1,
+ pi:1,
+ spare_hi:1;
+ uint8_t spare_lo:6,
+ dummy:2;
+#elif OSMO_IS_BIG_ENDIAN
+/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianness.py) */
+ uint8_t tfi_hi:2, cv:4, si:1, r:1;
+ uint8_t bsn1_hi:5, tfi_lo:3;
+ uint8_t bsn2_hi:2, bsn1_lo:6;
+ uint8_t bsn2_lo:8;
+ uint8_t spare_hi:1, pi:1, rsb:1, cps:5;
+ uint8_t dummy:2, spare_lo:6;
+#endif
+} __attribute__ ((packed));
+
+/* TS 44.060 10.3a.4.2.1 */
+struct gprs_rlc_ul_header_egprs_2 {
+#if OSMO_IS_LITTLE_ENDIAN
+ uint8_t r:1,
+ si:1,
+ cv:4,
+ tfi_hi:2;
+ uint8_t tfi_lo:3,
+ bsn1_hi:5;
+ uint8_t bsn1_lo:6,
+ cps_hi:2;
+ uint8_t cps_lo:1,
+ rsb:1,
+ pi:1,
+ spare_hi:5;
+ uint8_t spare_lo:5,
+ dummy:3;
+#elif OSMO_IS_BIG_ENDIAN
+/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianness.py) */
+ uint8_t tfi_hi:2, cv:4, si:1, r:1;
+ uint8_t bsn1_hi:5, tfi_lo:3;
+ uint8_t cps_hi:2, bsn1_lo:6;
+ uint8_t spare_hi:5, pi:1, rsb:1, cps_lo:1;
+ uint8_t dummy:3, spare_lo:5;
+#endif
+} __attribute__ ((packed));
+
+/* TS 44.060 10.3a.4.3.1 */
+struct gprs_rlc_ul_header_egprs_3 {
+#if OSMO_IS_LITTLE_ENDIAN
+ uint8_t r:1,
+ si:1,
+ cv:4,
+ tfi_hi:2;
+ uint8_t tfi_lo:3,
+ bsn1_hi:5;
+ uint8_t bsn1_lo:6,
+ cps_hi:2;
+ uint8_t cps_lo:2,
+ spb:2,
+ rsb:1,
+ pi:1,
+ spare:1,
+ dummy:1;
+#elif OSMO_IS_BIG_ENDIAN
+/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianness.py) */
+ uint8_t tfi_hi:2, cv:4, si:1, r:1;
+ uint8_t bsn1_hi:5, tfi_lo:3;
+ uint8_t cps_hi:2, bsn1_lo:6;
+ uint8_t dummy:1, spare:1, pi:1, rsb:1, spb:2, cps_lo:2;
+#endif
+} __attribute__ ((packed));
+
+struct gprs_rlc_dl_header_egprs_1 {
+#if OSMO_IS_LITTLE_ENDIAN
+ uint8_t usf:3,
+ es_p:2,
+ rrbp:2,
+ tfi_hi:1;
+ uint8_t tfi_lo:4,
+ pr:2,
+ bsn1_hi:2;
+ uint8_t bsn1_mid:8;
+ uint8_t bsn1_lo:1,
+ bsn2_hi:7;
+ uint8_t bsn2_lo:3,
+ cps:5;
+#elif OSMO_IS_BIG_ENDIAN
+/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianness.py) */
+ uint8_t tfi_hi:1, rrbp:2, es_p:2, usf:3;
+ uint8_t bsn1_hi:2, pr:2, tfi_lo:4;
+ uint8_t bsn1_mid:8;
+ uint8_t bsn2_hi:7, bsn1_lo:1;
+ uint8_t cps:5, bsn2_lo:3;
+#endif
+} __attribute__ ((packed));
+
+struct gprs_rlc_dl_header_egprs_2 {
+#if OSMO_IS_LITTLE_ENDIAN
+ uint8_t usf:3,
+ es_p:2,
+ rrbp:2,
+ tfi_hi:1;
+ uint8_t tfi_lo:4,
+ pr:2,
+ bsn1_hi:2;
+ uint8_t bsn1_mid:8;
+ uint8_t bsn1_lo:1,
+ cps:3,
+ dummy:4;
+#elif OSMO_IS_BIG_ENDIAN
+/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianness.py) */
+ uint8_t tfi_hi:1, rrbp:2, es_p:2, usf:3;
+ uint8_t bsn1_hi:2, pr:2, tfi_lo:4;
+ uint8_t bsn1_mid:8;
+ uint8_t dummy:4, cps:3, bsn1_lo:1;
+#endif
+} __attribute__ ((packed));
+
+struct gprs_rlc_dl_header_egprs_3 {
+#if OSMO_IS_LITTLE_ENDIAN
+ uint8_t usf:3,
+ es_p:2,
+ rrbp:2,
+ tfi_hi:1;
+ uint8_t tfi_lo:4,
+ pr:2,
+ bsn1_hi:2;
+ uint8_t bsn1_mid:8;
+ uint8_t bsn1_lo:1,
+ cps:4,
+ spb:2,
+ dummy:1;
+#elif OSMO_IS_BIG_ENDIAN
+/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianness.py) */
+ uint8_t tfi_hi:1, rrbp:2, es_p:2, usf:3;
+ uint8_t bsn1_hi:2, pr:2, tfi_lo:4;
+ uint8_t bsn1_mid:8;
+ uint8_t dummy:1, spb:2, cps:4, bsn1_lo:1;
+#endif
+} __attribute__ ((packed));
+
+/* TS 44.060 Table 12.24.2
+* Meaning of values documented in TS 23.060 Chapter 6.3.3.1: Network Mode of Operation */
+enum osmo_gprs_nmo {
+ GPRS_NMO_I = 0, /* CS pagin on GPRS paging or traffic channel */
+ GPRS_NMO_II = 1, /* all paging on CCCH */
+ GPRS_NMO_III = 2, /* no paging coordination */
+};
+
+/* TS 44.060 12.24 */
+struct osmo_gprs_cell_options {
+ enum osmo_gprs_nmo nmo;
+ /* T3168: wait for packet uplink assignment message */
+ uint32_t t3168; /* in milliseconds */
+ /* T3192: wait for release of the TBF after reception of the final block */
+ uint32_t t3192; /* in milliseconds */
+ uint32_t drx_timer_max;/* in seconds */
+ uint32_t bs_cv_max;
+ uint8_t supports_egprs_11bit_rach;
+ bool ctrl_ack_type_use_block; /* use PACKET CONTROL ACKNOWLEDGMENT */
+
+ uint8_t ext_info_present;
+ struct {
+ uint8_t egprs_supported;
+ uint8_t use_egprs_p_ch_req;
+ uint8_t bep_period;
+ uint8_t pfc_supported;
+ uint8_t dtm_supported;
+ uint8_t bss_paging_coordination;
+ bool ccn_active;
+ } ext_info;
+};
+
+/* TS 44.060 Table 12.9.2 */
+struct osmo_gprs_power_ctrl_pars {
+ uint8_t alpha;
+ uint8_t t_avg_w;
+ uint8_t t_avg_t;
+ uint8_t pc_meas_chan;
+ uint8_t n_avg_i;
+};
+
+
+/*! Structure for CPS coding and puncturing scheme (TS 44.060 10.4.8a) */
+struct egprs_cps {
+ uint8_t bits;
+ uint8_t mcs;
+ uint8_t p[2];
+};
+
+/*! CPS puncturing table selection (TS 44.060 10.4.8a) */
+enum egprs_cps_punc {
+ EGPRS_CPS_P1,
+ EGPRS_CPS_P2,
+ EGPRS_CPS_P3,
+ EGPRS_CPS_NONE = -1,
+};
+
+/*! EGPRS header types (TS 44.060 10.0a.2) */
+enum egprs_hdr_type {
+ EGPRS_HDR_TYPE1,
+ EGPRS_HDR_TYPE2,
+ EGPRS_HDR_TYPE3,
+};
+
+enum osmo_gprs_cs {
+ OSMO_GPRS_CS_NONE,
+ OSMO_GPRS_CS1,
+ OSMO_GPRS_CS2,
+ OSMO_GPRS_CS3,
+ OSMO_GPRS_CS4,
+ OSMO_GPRS_MCS1,
+ OSMO_GPRS_MCS2,
+ OSMO_GPRS_MCS3,
+ OSMO_GPRS_MCS4,
+ OSMO_GPRS_MCS5,
+ OSMO_GPRS_MCS6,
+ OSMO_GPRS_MCS7,
+ OSMO_GPRS_MCS8,
+ OSMO_GPRS_MCS9,
+ _NUM_OSMO_GPRS_CS
+};
+
+int egprs_get_cps(struct egprs_cps *cps, uint8_t type, uint8_t bits);
+
+int osmo_gprs_ul_block_size_bits(enum osmo_gprs_cs cs);
+int osmo_gprs_dl_block_size_bits(enum osmo_gprs_cs cs);
+int osmo_gprs_ul_block_size_bytes(enum osmo_gprs_cs cs);
+int osmo_gprs_dl_block_size_bytes(enum osmo_gprs_cs cs);
+enum osmo_gprs_cs osmo_gprs_ul_cs_by_block_bytes(uint8_t block_size);
+enum osmo_gprs_cs osmo_gprs_dl_cs_by_block_bytes(uint8_t block_size);
diff --git a/include/osmocom/gsm/protocol/gsm_44_068.h b/include/osmocom/gsm/protocol/gsm_44_068.h
new file mode 100644
index 00000000..3a33c163
--- /dev/null
+++ b/include/osmocom/gsm/protocol/gsm_44_068.h
@@ -0,0 +1,136 @@
+#pragma once
+#include <stdint.h>
+#include <osmocom/core/utils.h>
+
+/* 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+
+ */
+
+/* 9 Information Element Identifiers */
+enum osmo_gsm44068_iei {
+ OSMO_GSM44068_IEI_MOBILE_IDENTITY = 0x17,
+ OSMO_GSM44068_IEI_USER_USER = 0x7E,
+ OSMO_GSM44068_IEI_CALL_STATE = 0xA0,
+ OSMO_GSM44068_IEI_STATE_ATTRIBUTES = 0xB0,
+ OSMO_GSM44068_IEI_TALKER_PRIORITY = 0xC0,
+ OSMO_GSM44068_IEI_SMS_INDICATIONS = 0xD0,
+};
+
+/* 9.3 Message Type */
+enum osmo_gsm44068_msg_type {
+ OSMO_GSM44068_MSGT_IMMEDIATE_SETUP = 0x31,
+ OSMO_GSM44068_MSGT_SETUP = 0x32,
+ OSMO_GSM44068_MSGT_CONNECT = 0x33,
+ OSMO_GSM44068_MSGT_TERMINATION = 0x34,
+ OSMO_GSM44068_MSGT_TERMINATION_REQUEST = 0x35,
+ OSMO_GSM44068_MSGT_TERMINATION_REJECT = 0x36,
+ OSMO_GSM44068_MSGT_STATUS = 0x38,
+ OSMO_GSM44068_MSGT_GET_STATUS = 0x39,
+ OSMO_GSM44068_MSGT_SET_PARAMETER = 0x3a,
+ OSMO_GSM44068_MSGT_IMMEDIATE_SETUP_2 = 0x3b,
+};
+
+/* Table 9.2 priority */
+enum osmo_gsm44068_priority_level {
+ OSMO_GSM44068_PRIO_LEVEL_4 = 0x1,
+ OSMO_GSM44068_PRIO_LEVEL_3 = 0x2,
+ OSMO_GSM44068_PRIO_LEVEL_2 = 0x3,
+ OSMO_GSM44068_PRIO_LEVEL_1 = 0x4,
+ OSMO_GSM44068_PRIO_LEVEL_0 = 0x5,
+ OSMO_GSM44068_PRIO_LEVEL_B = 0x6,
+ OSMO_GSM44068_PRIO_LEVEL_A = 0x7,
+};
+
+/* 9.4.2 Call State */
+enum osmo_gsm44068_call_state {
+ OSMO_GSM44068_CSTATE_U0 = 0x0,
+ OSMO_GSM44068_CSTATE_U1 = 0x1,
+ OSMO_GSM44068_CSTATE_U2sl_U2 = 0x2,
+ OSMO_GSM44068_CSTATE_U3 = 0x3,
+ OSMO_GSM44068_CSTATE_U4 = 0x4,
+ OSMO_GSM44068_CSTATE_U5 = 0x5,
+ OSMO_GSM44068_CSTATE_U0p = 0x6,
+ OSMO_GSM44068_CSTATE_U2wr_U6 = 0x7,
+ OSMO_GSM44068_CSTATE_U2r = 0x8,
+ OSMO_GSM44068_CSTATE_U2ws = 0x9,
+ OSMO_GSM44068_CSTATE_U2sr = 0xa,
+ OSMO_GSM44068_CSTATE_U2nc = 0xb,
+};
+
+/* 9.4.3 Cause */
+enum osmo_gsm44068_cause {
+ OSMO_GSM44068_CAUSE_ILLEGAL_MS = 0x03,
+ OSMO_GSM44068_CAUSE_IMEI_NOT_ACCEPTED = 0x05,
+ OSMO_GSM44068_CAUSE_ILLEGAL_ME = 0x06,
+ OSMO_GSM44068_CAUSE_SERVICE_NOT_AUTHORIZED = 0x08,
+ OSMO_GSM44068_CAUSE_APP_NOT_SUPPORTED_ON_PROTO = 0x09,
+ OSMO_GSM44068_CAUSE_RR_CONNECTION_ABORTED = 0x0a,
+ OSMO_GSM44068_CAUSE_NORMAL_CALL_CLEARING = 0x10,
+ OSMO_GSM44068_CAUSE_NETWORK_FAILURE = 0x11,
+ OSMO_GSM44068_CAUSE_BUSY = 0x14,
+ OSMO_GSM44068_CAUSE_CONGESTION = 0x16,
+ OSMO_GSM44068_CAUSE_USER_NOT_ORIGINATOR = 0x17,
+ OSMO_GSM44068_CAUSE_NET_WANTS_TO_MAINTAIN_CALL = 0x18,
+ OSMO_GSM44068_CAUSE_RESPONSE_TO_GET_STATUS = 0x1e,
+ OSMO_GSM44068_CAUSE_SERVICE_OPTION_NOT_SUBSCR = 0x20,
+ OSMO_GSM44068_CAUSE_REQUESTED_SERVICE_NOT_SUB = 0x21,
+ OSMO_GSM44068_CAUSE_SERVICE_OPTION_OOO = 0x22,
+ OSMO_GSM44068_CAUSE_CALL_CANNOT_BE_IDENTIFIED = 0x26,
+ OSMO_GSM44068_CAUSE_RETRY_UPON_ENTRY_NEW_CALL = 0x30, /* up to 0x3f */
+ OSMO_GSM44068_CAUSE_INVALID_TRANSACTION_ID = 0x51,
+ OSMO_GSM44068_CAUSE_SEMANTICALLY_INCORRECT_MSG = 0x5f,
+ OSMO_GSM44068_CAUSE_INVALID_MANDATORY_INFO = 0x60,
+ OSMO_GSM44068_CAUSE_MESSAGE_TYPE_NON_EXISTENT = 0x61,
+ OSMO_GSM44068_CAUSE_MESSAGE_TYPE_NOT_COMPAT = 0x62,
+ OSMO_GSM44068_CAUSE_IE_NON_EXISTENT = 0x63,
+ OSMO_GSM44068_CAUSE_IE_NOT_COMPAT = 0x64,
+ OSMO_GSM44068_CAUSE_PROTOCOL_ERROR = 0x70,
+};
+
+/* 9.4.4 Originator Indication */
+#define OSMO_GSM44068_OI_MS_IS_ORIGINATOR 0x01
+
+/* 9.4.7 State Attributes */
+#define OSMO_GSM44068_DA_DOWNLINK_ATTACHED 0x08
+#define OSMO_GSM44068_UA_UPLINK_ATTACHED 0x04
+#define OSMO_GSM44068_COMM_T 0x02
+
+/* 9.4.9 Talker Priority */
+enum osmo_gsm44068_talker_priority {
+ OSMO_GSM44068_PRIO_NORMAL = 0x0,
+ OSMO_GSM44068_PRIO_PRIVILEGED = 0x1,
+ OSMO_GSM44068_PRIO_EMERGENCY = 0x2,
+};
+
+/* 9.4.10 SMS Indications */
+#define OSMO_GSM44068_DC_DATA_CONFIDENTALLY_RQD 0x02
+#define OSMO_GSM44068_GP_GUARANTEED_PRIVACY_RQD 0x01
+
+extern const struct value_string osmo_gsm44068_msg_type_names[];
+extern const struct value_string osmo_gsm44068_priority_level_names[];
+extern const struct value_string osmo_gsm44068_cause_names[];
+extern const struct value_string osmo_gsm44068_call_state_names[];
+extern const struct value_string osmo_gsm44068_talker_priority_names[];
+
+extern const struct tlv_definition osmo_gsm44068_att_tlvdef;
diff --git a/include/osmocom/gsm/protocol/gsm_44_318.h b/include/osmocom/gsm/protocol/gsm_44_318.h
index b3942be2..f31a80ae 100644
--- a/include/osmocom/gsm/protocol/gsm_44_318.h
+++ b/include/osmocom/gsm/protocol/gsm_44_318.h
@@ -162,7 +162,7 @@ struct gan_rc_csr_hdr {
uint8_t msg_type;
uint8_t data[0];
#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) */
uint16_t len;
uint8_t skip_ind:4, pdisc:4;
uint8_t msg_type;
@@ -190,7 +190,7 @@ struct gan_cch_desc_ie {
spare2:2;
uint8_t access_class[2];
#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 mscr:1, att:1, dtm:1, gprs:1, nmo:2, ecmc:1, spare:1;
uint8_t t3212;
uint8_t rac;
diff --git a/include/osmocom/gsm/protocol/gsm_49_031.h b/include/osmocom/gsm/protocol/gsm_49_031.h
index 8429ccc3..463fabf5 100644
--- a/include/osmocom/gsm/protocol/gsm_49_031.h
+++ b/include/osmocom/gsm/protocol/gsm_49_031.h
@@ -68,6 +68,7 @@ struct osmo_bssmap_le_lcs_qos {
uint8_t va_val:7, va_ind:1;
uint8_t spare3:6, rt:2;
#elif OSMO_IS_BIG_ENDIAN
+/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianness.py) */
uint8_t spare1:6, vel:1, vert:1;
uint8_t ha_ind:1, ha_val:7;
uint8_t va_ind:1, va_val:7;
diff --git a/include/osmocom/gsm/tlv.h b/include/osmocom/gsm/tlv.h
index fd6659c6..28e897d7 100644
--- a/include/osmocom/gsm/tlv.h
+++ b/include/osmocom/gsm/tlv.h
@@ -457,6 +457,16 @@ static inline uint8_t *msgb_tv16_push(struct msgb *msg, uint8_t tag, uint16_t va
return buf;
}
+/*! push (prepend) a TV32 field to a \ref msgb
+ * \returns pointer to first byte of newly-pushed information */
+static inline uint8_t *msgb_tv32_push(struct msgb *msg, uint8_t tag, uint32_t val)
+{
+ uint8_t *buf = msgb_push(msg, 5);
+ *buf++ = tag;
+ osmo_store32be(val, buf);
+ return buf;
+}
+
/*! push (prepend) a TvLV field to a \ref msgb
* \returns pointer to first byte of newly-pushed information */
static inline uint8_t *msgb_tvlv_push(struct msgb *msg, uint8_t tag, uint16_t len,
diff --git a/include/osmocom/isdn/Makefile.am b/include/osmocom/isdn/Makefile.am
new file mode 100644
index 00000000..ad2d8dd5
--- /dev/null
+++ b/include/osmocom/isdn/Makefile.am
@@ -0,0 +1,7 @@
+osmoisdn_HEADERS = \
+ i460_mux.h \
+ lapd_core.h \
+ v110.h \
+ $(NULL)
+
+osmoisdndir = $(includedir)/osmocom/isdn
diff --git a/include/osmocom/isdn/i460_mux.h b/include/osmocom/isdn/i460_mux.h
new file mode 100644
index 00000000..537e3257
--- /dev/null
+++ b/include/osmocom/isdn/i460_mux.h
@@ -0,0 +1,120 @@
+/*! \file i460_mux.h
+ * ITU-T I.460 sub-channel multiplexer + demultiplexer */
+/*
+ * (C) 2020 by Harald Welte <laforge@gnumonks.org>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#pragma once
+#include <stdint.h>
+#include <osmocom/core/bits.h>
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/msgb.h>
+
+#define OSMO_I460_NUM_SUBCHAN 8
+
+/* I.460 sub-slot rate */
+enum osmo_i460_rate {
+ OSMO_I460_RATE_NONE, /* disabled */
+ OSMO_I460_RATE_64k,
+ OSMO_I460_RATE_32k,
+ OSMO_I460_RATE_16k,
+ OSMO_I460_RATE_8k,
+};
+
+struct osmo_i460_subchan;
+
+typedef void (*out_cb_bits_t)(struct osmo_i460_subchan *schan, void *user_data,
+ const ubit_t *bits, unsigned int num_bits);
+typedef void (*out_cb_bytes_t)(struct osmo_i460_subchan *schan, void *user_data,
+ const uint8_t *bytes, unsigned int num_bytes);
+
+struct osmo_i460_subchan_demux {
+ /*! bit-buffer for output bits */
+ uint8_t *out_bitbuf;
+ /*! size of out_bitbuf in bytes */
+ unsigned int out_bitbuf_size;
+ /*! offset of next bit to be written in out_bitbuf */
+ unsigned int out_idx;
+ /*! callback to be called once we have received out_bitbuf_size bits */
+ out_cb_bits_t out_cb_bits;
+ out_cb_bytes_t out_cb_bytes;
+ void *user_data;
+};
+
+typedef void (*in_cb_queue_empty_t)(struct osmo_i460_subchan *schan, void *user_data);
+
+struct osmo_i460_subchan_mux {
+ /*! list of to-be-transmitted message buffers */
+ struct llist_head tx_queue;
+ in_cb_queue_empty_t in_cb_queue_empty;
+ void *user_data;
+};
+
+struct osmo_i460_subchan {
+ struct osmo_i460_timeslot *ts; /* back-pointer */
+ enum osmo_i460_rate rate; /* 8/16/32/64k */
+ uint8_t bit_offset; /* bit offset inside each byte of the B-channel */
+ struct osmo_i460_subchan_demux demux;
+ struct osmo_i460_subchan_mux mux;
+};
+
+struct osmo_i460_timeslot {
+ struct osmo_i460_subchan schan[OSMO_I460_NUM_SUBCHAN];
+};
+
+/*! description of a sub-channel; passed by caller */
+struct osmo_i460_schan_desc {
+ enum osmo_i460_rate rate;
+ uint8_t bit_offset;
+ struct {
+ /* size (in bits) of the internal buffer; determines granularity */
+ size_t num_bits;
+ /*! call-back function called whenever we received num_bits */
+ out_cb_bits_t out_cb_bits;
+ /*! out_cb_bytes call-back function called whenever we received num_bits.
+ * The user is usually expected to provide either out_cb_bits or out_cb_bytes. If only
+ * out_cb_bits is provided, output data will always be provided as unpacked bits; if only
+ * out_cb_bytes is provided, output data will always be provided as packet bits (bytes). If
+ * both are provided, it is up to the I.460 multiplex to decide if it calls either of the two,
+ * depending on what can be provided without extra conversion. */
+ out_cb_bytes_t out_cb_bytes;
+ /* opaque user data pointer to pass to out_cb */
+ void *user_data;
+ } demux;
+
+ struct {
+ /* call-back function whenever the muxer requires more input data from the sub-channels,
+ * but has nothing enqueued yet. A typical function would then call osmo_i460_mux_enqueue() */
+ in_cb_queue_empty_t in_cb_queue_empty;
+ /* opaque user data pointer to pass to in_cb */
+ void *user_data;
+ } mux;
+};
+
+void osmo_i460_demux_in(struct osmo_i460_timeslot *ts, const uint8_t *data, size_t data_len);
+
+void osmo_i460_mux_enqueue(struct osmo_i460_subchan *schan, struct msgb *msg);
+int osmo_i460_mux_out(struct osmo_i460_timeslot *ts, uint8_t *out, size_t out_len);
+
+void osmo_i460_ts_init(struct osmo_i460_timeslot *ts);
+
+struct osmo_i460_subchan *
+osmo_i460_subchan_add(void *ctx, struct osmo_i460_timeslot *ts, const struct osmo_i460_schan_desc *chd);
+
+void osmo_i460_subchan_del(struct osmo_i460_subchan *schan);
+
+int osmo_i460_subchan_count(struct osmo_i460_timeslot *ts);
+
+/*! @} */
diff --git a/include/osmocom/isdn/lapd_core.h b/include/osmocom/isdn/lapd_core.h
new file mode 100644
index 00000000..e4e8b469
--- /dev/null
+++ b/include/osmocom/isdn/lapd_core.h
@@ -0,0 +1,176 @@
+/*! \file lapd_core.h
+ * primitive related stuff
+ */
+#pragma once
+
+#include <stdint.h>
+
+#include <osmocom/core/timer.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/gsm/prim.h>
+
+/*! \defgroup lapd LAPD implementation common part
+ * @{
+ * \file lapd_core.h
+ */
+
+#define LOGDL(dl, level, fmt, args...) \
+ LOGP(DLLAPD, level, "(%s) " fmt, (dl)->name, ## args)
+
+/*! LAPD related primitives (L2<->L3 SAP)*/
+enum osmo_dl_prim {
+ PRIM_DL_UNIT_DATA, /*!< DL-UNIT-DATA */
+ PRIM_DL_DATA, /*!< DL-DATA */
+ PRIM_DL_EST, /*!< DL-ESTABLISH */
+ PRIM_DL_REL, /*!< DL-RLEEASE */
+ PRIM_DL_SUSP, /*!< DL-SUSPEND */
+ PRIM_DL_RES, /*!< DL-RESUME */
+ PRIM_DL_RECON, /*!< DL-RECONNECT */
+ PRIM_MDL_ERROR, /*!< MDL-ERROR */
+};
+
+/* Uses the same values as RLL, so no conversion for GSM is required. */
+#define MDL_CAUSE_T200_EXPIRED 0x01
+#define MDL_CAUSE_REEST_REQ 0x02
+#define MDL_CAUSE_UNSOL_UA_RESP 0x03
+#define MDL_CAUSE_UNSOL_DM_RESP 0x04
+#define MDL_CAUSE_UNSOL_DM_RESP_MF 0x05
+#define MDL_CAUSE_UNSOL_SPRV_RESP 0x06
+#define MDL_CAUSE_SEQ_ERR 0x07
+#define MDL_CAUSE_UFRM_INC_PARAM 0x08
+#define MDL_CAUSE_SFRM_INC_PARAM 0x09
+#define MDL_CAUSE_IFRM_INC_MBITS 0x0a
+#define MDL_CAUSE_IFRM_INC_LEN 0x0b
+#define MDL_CAUSE_FRM_UNIMPL 0x0c
+#define MDL_CAUSE_SABM_MF 0x0d
+#define MDL_CAUSE_SABM_INFO_NOTALL 0x0e
+#define MDL_CAUSE_FRMR 0x0f
+
+/*! for MDL-ERROR.ind */
+struct mdl_error_ind_param {
+ uint8_t cause; /*!< generic cause value */
+};
+
+/*! for DL-REL.req */
+struct dl_rel_req_param {
+ uint8_t mode; /*!< release mode */
+};
+
+/*! primitive header for LAPD DL-SAP primitives */
+struct osmo_dlsap_prim {
+ struct osmo_prim_hdr oph; /*!< generic primitive header */
+ union {
+ struct mdl_error_ind_param error_ind;
+ struct dl_rel_req_param rel_req;
+ } u; /*!< request-specific data */
+};
+
+/*! LAPD mode/role */
+enum lapd_mode {
+ LAPD_MODE_USER, /*!< behave like user */
+ LAPD_MODE_NETWORK, /*!< behave like network */
+};
+
+/*! LAPD state (Figure B.2/Q.921)*/
+enum lapd_state {
+ LAPD_STATE_NULL = 0,
+ LAPD_STATE_TEI_UNASS,
+ LAPD_STATE_ASS_TEI_WAIT,
+ LAPD_STATE_EST_TEI_WAIT,
+ LAPD_STATE_IDLE,
+ LAPD_STATE_SABM_SENT,
+ LAPD_STATE_DISC_SENT,
+ LAPD_STATE_MF_EST,
+ LAPD_STATE_TIMER_RECOV,
+};
+
+/*! LAPD message format (I / S / U) */
+enum lapd_format {
+ LAPD_FORM_UKN = 0,
+ LAPD_FORM_I,
+ LAPD_FORM_S,
+ LAPD_FORM_U,
+};
+
+/*! LAPD message context */
+struct lapd_msg_ctx {
+ struct lapd_datalink *dl;
+ int n201;
+ /* address */
+ uint8_t cr;
+ uint8_t sapi;
+ uint8_t tei;
+ uint8_t lpd;
+ /* control */
+ uint8_t format;
+ uint8_t p_f; /* poll / final bit */
+ uint8_t n_send;
+ uint8_t n_recv;
+ uint8_t s_u; /* S or repectivly U function bits */
+ /* length */
+ int length;
+ uint8_t more;
+};
+
+struct lapd_cr_ent {
+ uint8_t cmd;
+ uint8_t resp;
+};
+
+struct lapd_history {
+ struct msgb *msg; /* message to be sent / NULL, if histoy is empty */
+ int more; /* if message is fragmented */
+};
+
+/*! LAPD datalink */
+struct lapd_datalink {
+ int (*send_dlsap)(struct osmo_dlsap_prim *dp, struct lapd_msg_ctx *lctx);
+ int (*send_ph_data_req)(struct lapd_msg_ctx *lctx, struct msgb *msg);
+ int (*update_pending_frames)(struct lapd_msg_ctx *lctx);
+ struct {
+ /*! filled-in once we set the lapd_mode above */
+ struct lapd_cr_ent loc2rem;
+ struct lapd_cr_ent rem2loc;
+ } cr;
+ enum lapd_mode mode; /*!< current mode of link */
+ int use_sabme; /*!< use SABME instead of SABM */
+ int reestablish; /*!< enable reestablish support */
+ int n200, n200_est_rel; /*!< number of retranmissions */
+ struct lapd_msg_ctx lctx; /*!< LAPD context */
+ int maxf; /*!< maximum frame size (after defragmentation) */
+ uint8_t k; /*!< maximum number of unacknowledged frames */
+ uint8_t v_range; /*!< range of sequence numbers */
+ uint8_t v_send; /*!< seq nr of next I frame to be transmitted */
+ uint8_t v_ack; /*!< last frame ACKed by peer */
+ uint8_t v_recv; /*!< seq nr of next I frame expected to be received */
+ uint32_t state; /*!< LAPD state (\ref lapd_state) */
+ int seq_err_cond; /*!< condition of sequence error */
+ uint8_t own_busy; /*!< receiver busy on our side */
+ uint8_t peer_busy; /*!< receiver busy on remote side */
+ int t200_sec, t200_usec; /*!< retry timer (default 1 sec) */
+ int t203_sec, t203_usec; /*!< retry timer (default 10 secs) */
+ struct osmo_timer_list t200; /*!< T200 timer */
+ struct osmo_timer_list t203; /*!< T203 timer */
+ uint8_t retrans_ctr; /*!< re-transmission counter */
+ struct llist_head tx_queue; /*!< frames to L1 */
+ struct llist_head send_queue; /*!< frames from L3 */
+ struct msgb *send_buffer; /*!< current frame transmitting */
+ int send_out; /*!< how much was sent from send_buffer */
+ struct lapd_history *tx_hist; /*!< tx history structure array */
+ uint8_t range_hist; /*!< range of history buffer 2..2^n */
+ struct msgb *rcv_buffer; /*!< buffer to assemble the received message */
+ struct msgb *cont_res; /*!< buffer to store content resolution data on network side, to detect multiple phones on same channel */
+ char *name; /*!< user-provided name */
+};
+
+void lapd_dl_init(struct lapd_datalink *dl, uint8_t k, uint8_t v_range, int maxf)
+ OSMO_DEPRECATED("Use lapd_dl_init2() instead");
+void lapd_dl_init2(struct lapd_datalink *dl, uint8_t k, uint8_t v_range, int maxf, const char *name);
+void lapd_dl_set_name(struct lapd_datalink *dl, const char *name);
+void lapd_dl_exit(struct lapd_datalink *dl);
+void lapd_dl_reset(struct lapd_datalink *dl);
+int lapd_set_mode(struct lapd_datalink *dl, enum lapd_mode mode);
+int lapd_ph_data_ind(struct msgb *msg, struct lapd_msg_ctx *lctx);
+int lapd_recv_dlsap(struct osmo_dlsap_prim *dp, struct lapd_msg_ctx *lctx);
+
+/*! @} */
diff --git a/include/osmocom/isdn/v110.h b/include/osmocom/isdn/v110.h
new file mode 100644
index 00000000..4c1214c3
--- /dev/null
+++ b/include/osmocom/isdn/v110.h
@@ -0,0 +1,50 @@
+#pragma once
+
+#include <osmocom/core/bits.h>
+
+/* See Section 5.1.2.1 of ITU-T V.110 */
+#define MAX_D_BITS 48
+#define MAX_E_BITS 7
+#define MAX_S_BITS 9
+#define MAX_X_BITS 2
+
+/*! a 'decoded' representation of a single V.110 frame. contains unpacket D, E, S and X bits */
+struct osmo_v110_decoded_frame {
+ ubit_t d_bits[MAX_D_BITS];
+ ubit_t e_bits[MAX_E_BITS];
+ ubit_t s_bits[MAX_S_BITS];
+ ubit_t x_bits[MAX_X_BITS];
+};
+
+int osmo_v110_decode_frame(struct osmo_v110_decoded_frame *fr, const ubit_t *ra_bits, size_t n_bits);
+int osmo_v110_encode_frame(ubit_t *ra_bits, size_t n_bits, const struct osmo_v110_decoded_frame *fr);
+
+void osmo_v110_ubit_dump(FILE *outf, const ubit_t *fr, size_t in_len);
+
+
+/*! enum for each supported V.110 synchronous RA1 function (one for each user bitrate) */
+enum osmo_v100_sync_ra1_rate {
+ OSMO_V110_SYNC_RA1_600,
+ OSMO_V110_SYNC_RA1_1200,
+ OSMO_V110_SYNC_RA1_2400,
+ OSMO_V110_SYNC_RA1_4800,
+ OSMO_V110_SYNC_RA1_7200,
+ OSMO_V110_SYNC_RA1_9600,
+ OSMO_V110_SYNC_RA1_12000,
+ OSMO_V110_SYNC_RA1_14400,
+ OSMO_V110_SYNC_RA1_19200,
+ OSMO_V110_SYNC_RA1_24000,
+ OSMO_V110_SYNC_RA1_28800,
+ OSMO_V110_SYNC_RA1_38400,
+ _NUM_OSMO_V110_SYNC_RA1
+};
+
+int osmo_v110_sync_ra1_get_user_data_chunk_bitlen(enum osmo_v100_sync_ra1_rate rate);
+int osmo_v110_sync_ra1_get_user_data_rate(enum osmo_v100_sync_ra1_rate rate);
+int osmo_v110_sync_ra1_get_intermediate_rate(enum osmo_v100_sync_ra1_rate rate);
+
+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);
+
+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);
diff --git a/include/osmocom/sim/Makefile.am b/include/osmocom/sim/Makefile.am
new file mode 100644
index 00000000..bcc9459f
--- /dev/null
+++ b/include/osmocom/sim/Makefile.am
@@ -0,0 +1,6 @@
+osmosim_HEADERS = \
+ class_tables.h \
+ sim.h
+ $(NULL)
+
+osmosimdir = $(includedir)/osmocom/sim
diff --git a/include/osmocom/sim/sim.h b/include/osmocom/sim/sim.h
index 5e7099f4..639682a0 100644
--- a/include/osmocom/sim/sim.h
+++ b/include/osmocom/sim/sim.h
@@ -2,8 +2,7 @@
* Routines for helping with SIM (ISO/IEC 7816-4 more generally) communication.
*/
-#ifndef _OSMOCOM_SIM_H
-#define _OSMOCOM_SIM_H
+#pragma once
#include <osmocom/core/msgb.h>
#include <osmocom/core/linuxlist.h>
@@ -445,4 +444,3 @@ struct osim_reader_hdl *osim_reader_open(enum osim_reader_driver drv, int idx,
struct osim_card_hdl *osim_card_open(struct osim_reader_hdl *rh, enum osim_proto proto);
int osim_card_reset(struct osim_card_hdl *card, bool cold_reset);
int osim_card_close(struct osim_card_hdl *card);
-#endif /* _OSMOCOM_SIM_H */
diff --git a/include/osmocom/usb/Makefile.am b/include/osmocom/usb/Makefile.am
new file mode 100644
index 00000000..3b2b8b4d
--- /dev/null
+++ b/include/osmocom/usb/Makefile.am
@@ -0,0 +1,7 @@
+if ENABLE_LIBUSB
+osmousb_HEADERS = \
+ libusb.h \
+ $(NULL)
+endif
+
+osmousbdir = $(includedir)/osmocom/usb
diff --git a/include/osmocom/vty/Makefile.am b/include/osmocom/vty/Makefile.am
new file mode 100644
index 00000000..adb05dcb
--- /dev/null
+++ b/include/osmocom/vty/Makefile.am
@@ -0,0 +1,17 @@
+if ENABLE_VTY
+osmovty_HEADERS = \
+ buffer.h \
+ command.h \
+ logging.h \
+ stats.h \
+ misc.h \
+ telnet_interface.h \
+ vector.h \
+ vty.h \
+ ports.h \
+ cpu_sched_vty.h \
+ tdef_vty.h \
+ $(NULL)
+endif
+
+osmovtydir = $(includedir)/osmocom/vty
diff --git a/include/osmocom/vty/command.h b/include/osmocom/vty/command.h
index e3919fb8..61b58815 100644
--- a/include/osmocom/vty/command.h
+++ b/include/osmocom/vty/command.h
@@ -442,8 +442,8 @@ char *argv_concat(const char **argv, int argc, int shift);
vector cmd_make_strvec(const char *);
int cmd_make_strvec2(const char *string, char **indent, vector *strvec_p);
void cmd_free_strvec(vector);
-vector cmd_describe_command();
-char **cmd_complete_command();
+vector cmd_describe_command(vector vline, struct vty *vty, int *status);
+char **cmd_complete_command(vector vline, struct vty *vty, int *status);
const char *cmd_prompt(enum node_type);
int config_from_file(struct vty *, FILE *);
enum node_type node_parent(enum node_type);
diff --git a/include/osmocom/vty/logging.h b/include/osmocom/vty/logging.h
index 90c8fa17..b3ce92c7 100644
--- a/include/osmocom/vty/logging.h
+++ b/include/osmocom/vty/logging.h
@@ -6,7 +6,13 @@
#define FILTER_STR "Filter log messages\n"
struct log_info;
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wpragmas"
+#pragma GCC diagnostic ignored "-Wstrict-prototypes"
+/* note this undefined argument declaration is intentional. There used
+ * to be an argument until 2017 which we no longer need .*/
void logging_vty_add_cmds();
+#pragma GCC diagnostic pop
void logging_vty_add_deprecated_subsys(void *ctx, const char *name);
struct vty;
struct log_target *osmo_log_vty2tgt(struct vty *vty);
diff --git a/include/osmocom/vty/ports.h b/include/osmocom/vty/ports.h
index d3b1d7a5..bc001282 100644
--- a/include/osmocom/vty/ports.h
+++ b/include/osmocom/vty/ports.h
@@ -38,6 +38,7 @@
#define OSMO_VTY_PORT_CBC 4264
#define OSMO_VTY_PORT_UECUPS 4268
#define OSMO_VTY_PORT_E1D 4269
+#define OSMO_VTY_PORT_ISDNTAP 4270
#define OSMO_VTY_PORT_SMLC 4271
/* 4272 used by control interface */
#define OSMO_VTY_PORT_HNODEB 4273
diff --git a/include/osmocom/vty/telnet_interface.h b/include/osmocom/vty/telnet_interface.h
index a28df470..73b79df1 100644
--- a/include/osmocom/vty/telnet_interface.h
+++ b/include/osmocom/vty/telnet_interface.h
@@ -18,6 +18,7 @@
#pragma once
+#include <osmocom/core/defs.h>
#include <osmocom/core/logging.h>
#include <osmocom/core/select.h>
@@ -41,10 +42,13 @@ struct telnet_connection {
struct log_target *dbg;
};
-int telnet_init(void *tall_ctx, void *priv, int port);
-int telnet_init_dynif(void *tall_ctx, void *priv, const char *ip, int port);
int telnet_init_default(void *tall_ctx, void *priv, int default_port);
+int telnet_init(void *tall_ctx, void *priv, int port)
+ OSMO_DEPRECATED("This function ignores dynamic port configuration. Use telnet_init_default() instead");
+int telnet_init_dynif(void *tall_ctx, void *priv, const char *ip, int port)
+ OSMO_DEPRECATED("This function ignores dynamic port configuration. Use telnet_init_default() instead");
+
void telnet_exit(void);
/*! @} */
diff --git a/include/osmocom/vty/vty.h b/include/osmocom/vty/vty.h
index ed9f439c..3a2ec6f6 100644
--- a/include/osmocom/vty/vty.h
+++ b/include/osmocom/vty/vty.h
@@ -56,20 +56,6 @@ enum vty_type {
VTY_SHELL_SERV
};
-struct vty_parent_node {
- struct llist_head entry;
-
- /*! private data, specified by creator */
- void *priv;
-
- /*! 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;
-};
-
/*! Internal representation of a single VTY */
struct vty {
/*! underlying file (if any) */
diff --git a/libosmocore.pc.in b/libosmocore.pc.in
index d804b8e2..6479221e 100644
--- a/libosmocore.pc.in
+++ b/libosmocore.pc.in
@@ -6,7 +6,7 @@ includedir=@includedir@
Name: Osmocom Core Library
Description: C Utility Library
Version: @VERSION@
-Requires: talloc
+Requires: talloc @LIBMNL_PC@
Requires.private: @LIBSCTP_PC@
Libs: -L${libdir} -losmocore
Libs.private: @PTHREAD_LIBS@ @LIBSCTP_LIBS@
diff --git a/libosmogb.pc.in b/libosmogb.pc.in
index 0a62426f..5573bd98 100644
--- a/libosmogb.pc.in
+++ b/libosmogb.pc.in
@@ -6,6 +6,6 @@ includedir=@includedir@
Name: Osmocom GPRS Gb Library
Description: Osmocom GPRS Gb Interface (NS/BSSGP) Library
Version: @VERSION@
-Requires: talloc, libosmocore, libosmovty
+Requires: talloc, libosmocore, libosmovty, libosmogsm
Libs: -L${libdir} -losmogb
Cflags: -I${includedir}/ -fno-strict-aliasing
diff --git a/libosmogsm.pc.in b/libosmogsm.pc.in
index 0aaf51ed..17b9212a 100644
--- a/libosmogsm.pc.in
+++ b/libosmogsm.pc.in
@@ -6,6 +6,6 @@ includedir=@includedir@
Name: Osmocom GSM Core Library
Description: GSM Core Library
Version: @VERSION@
-Requires: talloc, libosmocore
+Requires: talloc, libosmocore, libosmoisdn
Libs: -L${libdir} -losmogsm
Cflags: -I${includedir}/
diff --git a/libosmoisdn.pc.in b/libosmoisdn.pc.in
new file mode 100644
index 00000000..fedbb694
--- /dev/null
+++ b/libosmoisdn.pc.in
@@ -0,0 +1,11 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: Osmocom ISDN Library
+Description: ISDN Library
+Version: @VERSION@
+Requires: talloc, libosmocore
+Libs: -L${libdir} -losmoisdn
+Cflags: -I${includedir}/
diff --git a/src/Makefile.am b/src/Makefile.am
index 2c73af66..86066466 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1,108 +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=19: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 \
- stats_tcp.c \
- conv_acc.c conv_acc_generic.c sercomm.c prbs.c \
- isdnhdlc.c \
- tdef.c \
- thread.c \
- time_cc.c \
- sockaddr_str.c \
- use_count.c \
- exec.c \
- it_q.c \
- probes.d \
- base64.c \
- $(NULL)
-
-if HAVE_SSSE3
-libosmocore_la_SOURCES += conv_acc_sse.c
-if HAVE_SSE4_1
-conv_acc_sse.lo : AM_CFLAGS += -mssse3 -msse4.1
-else
-conv_acc_sse.lo : AM_CFLAGS += -mssse3
-endif
-
-if HAVE_AVX2
-libosmocore_la_SOURCES += conv_acc_sse_avx.c
-if HAVE_SSE4_1
-conv_acc_sse_avx.lo : AM_CFLAGS += -mssse3 -mavx2 -msse4.1
-else
-conv_acc_sse_avx.lo : AM_CFLAGS += -mssse3 -mavx2
-endif
-endif
-endif
-
-if HAVE_NEON
-libosmocore_la_SOURCES += conv_acc_neon.c
-# conv_acc_neon.lo : AM_CFLAGS += -mfpu=neon no, could as well be vfp with neon
-endif
-
-BUILT_SOURCES = crc8gen.c crc16gen.c crc32gen.c crc64gen.c
-
-EXTRA_DIST = \
- conv_acc_sse_impl.h \
- conv_acc_neon_impl.h \
- crcXXgen.c.tpl \
- stat_item_internal.h \
- $(NULL)
-
-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 b712591e..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=3:1:3
+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 59194ae0..cdb3e62e 100644
--- a/src/codec/ecu.c
+++ b/src/codec/ecu.c
@@ -44,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))
@@ -96,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 218d8377..adde03e5 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,6 +20,46 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
+ *
+ * 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>
@@ -21,144 +67,214 @@
#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>
-/* See also GSM 06.11, chapter 6 Example solution */
-#define GSM610_XMAXC_REDUCE 4
-#define GSM610_XMAXC_LEN 6
+/* See TS 46.011, Chapter 6 Example solution */
+#define GSM611_XMAXC_REDUCE 4
-/**
- * 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;
+/* 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
- 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);
+enum ecu_principal_state {
+ STATE_NO_DATA,
+ STATE_SPEECH,
+ STATE_SP_MUTING,
+ STATE_SID,
+ STATE_SID_MUTING,
+};
- return field == 0;
-}
+struct fr_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 all XMAXC fields in the frame. When all XMAXC fields
- * reach zero, then 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_all(struct bitvec *frame_bitvec)
+static void fr_ecu_input(struct fr_ecu_state *fr, const 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;
+ 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);
+ }
}
-/* Use certain modifications to conceal the errors in a full rate frame */
-static int conceal_frame(uint8_t *frame)
+/* 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_xmaxc(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;
+ 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;
+}
- /* Fudge frame parameters */
- silent = reduce_xmaxcr_all(frame_bitvec);
+/* 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)
+{
+ uint8_t sub;
- /* 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;
}
/***********************************************************************
@@ -168,7 +284,8 @@ 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;
+ size_t size = sizeof(*st) + sizeof(*fr);
st = talloc_named_const(ctx, size, "ecu_state_FR");
if (!st)
@@ -176,6 +293,9 @@ static struct osmo_ecu_state *ecu_fr_init(void *ctx, enum osmo_ecu_codec codec)
memset(st, 0, size);
st->codec = codec;
+ fr = (struct fr_ecu_state *) &st->data;
+ fr->pr_state = STATE_NO_DATA;
+ osmo_prbs_state_init(&fr->prng, &osmo_prbs15);
return st;
}
@@ -183,28 +303,39 @@ static struct osmo_ecu_state *ecu_fr_init(void *ctx, enum osmo_ecu_codec codec)
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 = (struct fr_ecu_state *) &st->data;
+
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 = (struct fr_ecu_state *) &st->data;
+
+ 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 = (struct fr_ecu_state *) &st->data;
- 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 ff9952ad..5cc4f148 100644
--- a/src/codec/gsm610.c
+++ b/src/codec/gsm610.c
@@ -296,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
@@ -305,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)
@@ -323,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 4eae514e..ef1d3b9b 100644
--- a/src/codec/gsm620.c
+++ b/src/codec/gsm620.c
@@ -21,6 +21,7 @@
#include <stdint.h>
#include <stdbool.h>
+#include <string.h>
#include <osmocom/core/bitvec.h>
#include <osmocom/core/utils.h>
@@ -285,3 +286,31 @@ bool osmo_hr_check_sid(const uint8_t *rtp_payload, size_t payload_len)
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 34b10dea..b15bdf37 100644
--- a/src/codec/gsm660.c
+++ b/src/codec/gsm660.c
@@ -20,6 +20,10 @@
*/
#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 */
@@ -253,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/coding/Makefile.am b/src/coding/Makefile.am
index 9a24f389..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=2:0:2
+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_coding.c b/src/coding/gsm0503_coding.c
index 6f33b78a..0e7f6892 100644
--- a/src/coding/gsm0503_coding.c
+++ b/src/coding/gsm0503_coding.c
@@ -31,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>
@@ -608,7 +606,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)
{
@@ -1574,24 +1572,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 */
@@ -1806,7 +1802,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);
@@ -1862,7 +1858,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);
@@ -1929,40 +1925,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;
@@ -1979,13 +1974,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;
@@ -1999,7 +1994,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;
}
@@ -2052,7 +2047,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
@@ -2067,46 +2090,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;
@@ -2244,7 +2262,7 @@ int gsm0503_tch_afs_decode_dtx(uint8_t *tch_data, const sbit_t *bursts,
tch_amr_sid_update_append(sid_first_dummy, 0,
(codec_mode_req) ? codec[*ft]
: codec[id > 0 ? id : 0]);
- tch_amr_reassemble(tch_data, conv, 39);
+ tch_amr_reassemble(tch_data, sid_first_dummy, 39);
len = 5;
goto out;
case AFS_SID_UPDATE: /* TODO: parse CMI _and_ CMC/CMR (16 + 16 bit) */
@@ -2608,9 +2626,9 @@ static uint8_t gsm0503_tch_ahs_decode_inband(const sbit_t *cB)
return id;
}
-/*! 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)
@@ -2631,9 +2649,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)
@@ -3267,4 +3285,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 7809e832..570d65aa 100644
--- a/src/coding/gsm0503_interleaving.c
+++ b/src/coding/gsm0503_interleaving.c
@@ -678,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/libosmocoding.map b/src/coding/libosmocoding.map
index b083564a..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;
@@ -126,5 +129,23 @@ 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..326c68d3
--- /dev/null
+++ b/src/core/Makefile.am
@@ -0,0 +1,165 @@
+# 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 \
+ 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 f7e5816f..f7e5816f 100644
--- a/src/application.c
+++ b/src/core/application.c
diff --git a/src/backtrace.c b/src/core/backtrace.c
index 60bd2381..60bd2381 100644
--- a/src/backtrace.c
+++ b/src/core/backtrace.c
diff --git a/src/base64.c b/src/core/base64.c
index 0c161ceb..0c161ceb 100644
--- a/src/base64.c
+++ b/src/core/base64.c
diff --git a/src/bitcomp.c b/src/core/bitcomp.c
index 5fb2cba2..5fb2cba2 100644
--- a/src/bitcomp.c
+++ b/src/core/bitcomp.c
diff --git a/src/bits.c b/src/core/bits.c
index c1c3f90f..3da7d9b9 100644
--- a/src/bits.c
+++ b/src/core/bits.c
@@ -181,7 +181,7 @@ 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,
@@ -206,7 +206,7 @@ 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,
diff --git a/src/bitvec.c b/src/core/bitvec.c
index 350f7383..38ea1bb5 100644
--- a/src/bitvec.c
+++ b/src/core/bitvec.c
@@ -395,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)
diff --git a/src/context.c b/src/core/context.c
index 8ab6f877..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+
*
@@ -39,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 8963018b..8963018b 100644
--- a/src/conv.c
+++ b/src/core/conv.c
diff --git a/src/conv_acc.c b/src/core/conv_acc.c
index 4bd3b076..4bd3b076 100644
--- a/src/conv_acc.c
+++ b/src/core/conv_acc.c
diff --git a/src/conv_acc_generic.c b/src/core/conv_acc_generic.c
index 2257e6a9..2257e6a9 100644
--- a/src/conv_acc_generic.c
+++ b/src/core/conv_acc_generic.c
diff --git a/src/conv_acc_neon.c b/src/core/conv_acc_neon.c
index fb180e3d..fb180e3d 100644
--- a/src/conv_acc_neon.c
+++ b/src/core/conv_acc_neon.c
diff --git a/src/conv_acc_neon_impl.h b/src/core/conv_acc_neon_impl.h
index 8a78c75b..8a78c75b 100644
--- a/src/conv_acc_neon_impl.h
+++ b/src/core/conv_acc_neon_impl.h
diff --git a/src/conv_acc_sse.c b/src/core/conv_acc_sse.c
index 513ab052..513ab052 100644
--- a/src/conv_acc_sse.c
+++ b/src/core/conv_acc_sse.c
diff --git a/src/conv_acc_sse_avx.c b/src/core/conv_acc_sse_avx.c
index 82b4fa62..82b4fa62 100644
--- a/src/conv_acc_sse_avx.c
+++ b/src/core/conv_acc_sse_avx.c
diff --git a/src/conv_acc_sse_impl.h b/src/core/conv_acc_sse_impl.h
index 807dbe5e..807dbe5e 100644
--- a/src/conv_acc_sse_impl.h
+++ b/src/core/conv_acc_sse_impl.h
diff --git a/src/counter.c b/src/core/counter.c
index cbee7b9e..dace15f3 100644
--- a/src/counter.c
+++ b/src/core/counter.c
@@ -75,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 154291cc..154291cc 100644
--- a/src/crcXXgen.c.tpl
+++ b/src/core/crcXXgen.c.tpl
diff --git a/src/exec.c b/src/core/exec.c
index fd63d858..2e33788e 100644
--- a/src/exec.c
+++ b/src/core/exec.c
@@ -74,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)
@@ -131,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 */
@@ -213,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();
@@ -264,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 9333cac5..9333cac5 100644
--- a/src/fsm.c
+++ b/src/core/fsm.c
diff --git a/src/gsmtap_util.c b/src/core/gsmtap_util.c
index 2571b859..dcbd3049 100644
--- a/src/gsmtap_util.c
+++ b/src/core/gsmtap_util.c
@@ -19,7 +19,7 @@
*
*/
-#include "../config.h"
+#include "config.h"
#include <osmocom/core/gsmtap_util.h>
#include <osmocom/core/logging.h>
@@ -46,6 +46,28 @@
*
* \file gsmtap_util.c */
+/*! one gsmtap instance */
+struct gsmtap_inst {
+ int ofd_wq_mode; /*!< wait queue mode? 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_fd sink_ofd; /*!< file descriptor */
+};
+
+/*! 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
@@ -232,12 +254,12 @@ struct msgb *gsmtap_makemsg(uint16_t arfcn, uint8_t ts, uint8_t chan_type,
#include <sys/socket.h>
#include <netinet/in.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.
@@ -253,6 +275,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
@@ -306,7 +352,7 @@ int gsmtap_sendmsg(struct gsmtap_inst *gti, struct msgb *msg)
/* 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) {
@@ -423,7 +469,7 @@ int gsmtap_source_add_sink(struct gsmtap_inst *gti)
{
int fd, rc;
- fd = gsmtap_source_add_sink_fd(gsmtap_inst_fd(gti));
+ fd = gsmtap_source_add_sink_fd(gsmtap_inst_fd2(gti));
if (fd < 0)
return fd;
@@ -447,22 +493,25 @@ int gsmtap_source_add_sink(struct gsmtap_inst *gti)
/*! Open GSMTAP source socket, connect and register osmo_fd
- * \param[in] host host name or IP address in string format
- * \param[in] port UDP port number in host byte order
+ * \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 host/port,
+ * 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_init(const char *host, uint16_t port,
- int ofd_wq_mode)
+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;
- fd = gsmtap_source_init_fd(host, port);
+ fd = gsmtap_source_init_fd2(local_host, local_port, rem_host, rem_port);
if (fd < 0)
return NULL;
@@ -486,8 +535,27 @@ struct gsmtap_inst *gsmtap_source_init(const char *host, uint16_t port,
return gti;
}
+/*! Open GSMTAP source socket, connect and register osmo_fd
+ * \param[in] host host name or IP address in string format
+ * \param[in] 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 host/port,
+ * allocate 'struct gsmtap_inst' and optionally osmo_fd/osmo_wqueue
+ * registration.
+ */
+struct gsmtap_inst *gsmtap_source_init(const char *host, uint16_t port,
+ int ofd_wq_mode)
+{
+ return gsmtap_source_init2(NULL, 0, host, port, ofd_wq_mode);
+}
+
void gsmtap_source_free(struct gsmtap_inst *gti)
{
+ if (!gti)
+ return;
+
if (gti->ofd_wq_mode) {
osmo_fd_unregister(&gti->wq.bfd);
osmo_wqueue_clear(&gti->wq);
@@ -540,6 +608,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" },
@@ -548,6 +618,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 8ec1c956..4ced5afd 100644
--- a/src/isdnhdlc.c
+++ b/src/core/isdnhdlc.c
@@ -97,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 fda6c1ff..a3ff420c 100644
--- a/src/it_q.c
+++ b/src/core/it_q.c
@@ -32,7 +32,7 @@
* and call a queue-specific callback function.
*/
-#include "../config.h"
+#include "config.h"
#ifdef HAVE_SYS_EVENTFD_H
diff --git a/src/core/libosmocore.map b/src/core/libosmocore.map
new file mode 100644
index 00000000..e5f8bd8c
--- /dev/null
+++ b/src/core/libosmocore.map
@@ -0,0 +1,601 @@
+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_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_fd;
+osmo_iofd_get_name;
+osmo_iofd_set_name;
+osmo_iofd_get_priv_nr;
+osmo_iofd_init;
+osmo_iofd_ops;
+osmo_iofd_register;
+osmo_iofd_sendto_msgb;
+osmo_iofd_set_alloc_info;
+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_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_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_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_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_set_dscp;
+osmo_sock_set_priority;
+osmo_sock_unix_init;
+osmo_sock_unix_init_ofd;
+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_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 ce42e4c9..c6774f57 100644
--- a/src/logging.c
+++ b/src/core/logging.c
@@ -25,7 +25,7 @@
*
* \file logging.c */
-#include "../config.h"
+#include "config.h"
#include <stdarg.h>
#include <stdlib.h>
@@ -313,6 +313,12 @@ static const struct log_info_cat internal_cat[OSMO_NUM_DLIB] = {
.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)
@@ -1161,6 +1167,7 @@ int log_target_file_switch_to_stream(struct log_target *target)
if (target->type == LOG_TGT_TYPE_FILE) {
osmo_fd_unregister(&wq->bfd);
close(wq->bfd.fd);
+ wq->bfd.fd = -1;
}
/* release the queue itself */
@@ -1328,11 +1335,11 @@ void log_target_destroy(struct log_target *target)
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_fd_unregister(&wq->bfd);
osmo_wqueue_clear(wq);
talloc_free(wq);
target->tgt_file.wqueue = NULL;
@@ -1375,8 +1382,8 @@ int log_target_file_reopen(struct log_target *target)
return -errno;
} else {
wq = target->tgt_file.wqueue;
- osmo_fd_unregister(&wq->bfd);
if (wq->bfd.fd >= 0) {
+ osmo_fd_unregister(&wq->bfd);
close(wq->bfd.fd);
wq->bfd.fd = -1;
}
diff --git a/src/logging_gsmtap.c b/src/core/logging_gsmtap.c
index cc95388f..dfd059b7 100644
--- a/src/logging_gsmtap.c
+++ b/src/core/logging_gsmtap.c
@@ -27,7 +27,7 @@
* @{
* \file logging_gsmtap.c */
-#include "../config.h"
+#include "config.h"
#include <stdarg.h>
#include <stdlib.h>
diff --git a/src/logging_syslog.c b/src/core/logging_syslog.c
index 20908564..1153bdf4 100644
--- a/src/logging_syslog.c
+++ b/src/core/logging_syslog.c
@@ -22,7 +22,7 @@
* @{
* \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 2e86feb6..2e86feb6 100644
--- a/src/logging_systemd.c
+++ b/src/core/logging_systemd.c
diff --git a/src/loggingrb.c b/src/core/loggingrb.c
index 2bf7b665..2bf7b665 100644
--- a/src/loggingrb.c
+++ b/src/core/loggingrb.c
diff --git a/src/macaddr.c b/src/core/macaddr.c
index 3b231fb8..3b231fb8 100644
--- a/src/macaddr.c
+++ b/src/core/macaddr.c
diff --git a/src/mnl.c b/src/core/mnl.c
index f32367db..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+
*
@@ -54,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 0881a55d..713510c6 100644
--- a/src/msgb.c
+++ b/src/core/msgb.c
@@ -314,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;
@@ -339,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)
@@ -351,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,
diff --git a/src/msgfile.c b/src/core/msgfile.c
index abb4e7cf..abb4e7cf 100644
--- a/src/msgfile.c
+++ b/src/core/msgfile.c
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..2b2b7ddb
--- /dev/null
+++ b/src/core/osmo_io.c
@@ -0,0 +1,702 @@
+/*! \file osmo_io.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"
+#if defined(__linux__)
+
+#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"
+
+/*! 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 }
+};
+
+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_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
+ * \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)
+{
+ 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(iofd, struct iofd_msghdr);
+ 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,
+ iofd->name ? : "iofd_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;
+
+ 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;
+}
+
+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_SCTP_RECVMSG_SENDMSG:
+ /* TODO Implement */
+ OSMO_ASSERT(false);
+ break;
+ }
+}
+
+/* Public functions */
+
+/*! Send a message through a connected socket.
+ *
+ * Appends the message to the internal transmit queue.
+ * 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 file descriptor to write to
+ * \param[in] msg message buffer 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(!iofd->io_ops.write_cb)) {
+ LOGPIO(iofd, LOGL_ERROR, "write_cb not set, Rejecting msgb\n");
+ return -EINVAL;
+ }
+
+ struct iofd_msghdr *msghdr = iofd_msghdr_alloc(iofd, IOFD_ACT_WRITE, msg);
+ 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.
+ *
+ * Appends the message to the internal transmit queue.
+ * 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 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;
+
+ OSMO_ASSERT(iofd->mode == OSMO_IO_FD_MODE_RECVFROM_SENDTO);
+ if (OSMO_UNLIKELY(!iofd->io_ops.sendto_cb)) {
+ LOGPIO(iofd, LOGL_ERROR, "sendto_cb not set, Rejecting msgb\n");
+ return -EINVAL;
+ }
+
+ struct iofd_msghdr *msghdr = iofd_msghdr_alloc(iofd, IOFD_ACT_SENDTO, msg);
+ 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;
+}
+
+/*! Allocate and setup a new iofd.
+ * \param[in] ctx the parent context from which to allocate
+ * \param[in] fd the underlying system file descriptor
+ * \param[in] name the name of the iofd
+ * \param[in] mode the mode of the iofd, whether it should use read()/write(), sendto()/recvfrom()
+ * \param[in] ioops structure with read/write/send/recv callbacks
+ * \param[in] data 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 = 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;
+}
+
+/*! Register the fd with the underlying backend.
+ *
+ * \param[in] iofd the iofd file descriptor
+ * \param[in] fd the system fd number that will be registeres. If negative will use the one already set.
+ * \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;
+
+ if (osmo_iofd_ops.register_fd)
+ rc = osmo_iofd_ops.register_fd(iofd);
+ if (rc)
+ return rc;
+
+ IOFD_FLAG_UNSET(iofd, IOFD_FLAG_CLOSED);
+ if (iofd->io_ops.read_cb)
+ osmo_iofd_ops.read_enable(iofd);
+
+ if (iofd->tx_queue.current_length > 0)
+ osmo_iofd_ops.write_enable(iofd);
+
+ return rc;
+}
+
+/*! Unregister the fd from the underlying backend.
+ *
+ * \param[in] iofd the file descriptor
+ * \returns zero on success, a negative value on error
+ */
+int osmo_iofd_unregister(struct osmo_io_fd *iofd)
+{
+ if (osmo_iofd_ops.unregister_fd)
+ return osmo_iofd_ops.unregister_fd(iofd);
+ IOFD_FLAG_SET(iofd, IOFD_FLAG_CLOSED);
+
+ return 0;
+}
+
+/*! Get the number of messages in the tx 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 the iofd.
+ *
+ * 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 iofd.
+ *
+ * This function is safe to use in the read/write callbacks and will defer freeing it until safe to do so.
+ * The iofd will be closed before.
+ * \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 iofd.
+ *
+ * This function closes the underlying fd and clears any messages in the tx queue
+ * The iofd 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;
+
+ if (osmo_iofd_ops.close)
+ 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;
+}
+
+/*! Get the associated user-data from an iofd.
+ * \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 iofd.
+ * \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;
+}
+
+/*! Get the private number from an iofd.
+ * \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 from an iofd.
+ * \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;
+}
+
+/*! Get the underlying file descriptor from an iofd.
+ * \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;
+}
+
+/*! Get the name of the file descriptor.
+ * \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 name of the file descriptor.
+ * \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 for an iofd.
+ * \param[in] iofd Target iofd file descriptor
+ * \param[in] ioops osmo_io_ops structure to be set */
+void osmo_iofd_set_ioops(struct osmo_io_fd *iofd, const struct osmo_io_ops *ioops)
+{
+ 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_SCTP_RECVMSG_SENDMSG:
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+/*! Notify the user if/when the socket is connected.
+ * When the socket is connected the write_cb will be called.
+ * \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);
+ osmo_iofd_ops.write_enable(iofd);
+}
+
+
+#endif /* defined(__linux__) */
diff --git a/src/core/osmo_io_internal.h b/src/core/osmo_io_internal.h
new file mode 100644
index 00000000..5b7ab908
--- /dev/null
+++ b/src/core/osmo_io_internal.h
@@ -0,0 +1,145 @@
+/*! \file osmo_io_internal.h */
+
+#pragma once
+
+#include <unistd.h>
+#include <stdbool.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);
+};
+
+#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_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;
+
+ 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? */
+ } uring;
+ } u;
+};
+
+enum iofd_msg_action {
+ IOFD_ACT_READ,
+ IOFD_ACT_WRITE,
+ IOFD_ACT_RECVFROM,
+ IOFD_ACT_SENDTO,
+ // TODO: SCTP_*
+};
+
+
+/* serialized version of 'struct msghdr' employed by sendmsg/recvmsg */
+struct iofd_msghdr {
+ struct llist_head list;
+ enum iofd_msg_action action;
+ struct msghdr hdr;
+ struct osmo_sockaddr osa;
+ struct iovec iov[1];
+ int flags;
+
+ struct msgb *msg;
+ struct osmo_io_fd *iofd;
+};
+
+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);
+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_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..a9aaea4e
--- /dev/null
+++ b/src/core/osmo_io_poll.c
@@ -0,0 +1,188 @@
+/*! \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"
+#if defined(__linux__)
+
+#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),
+ };
+
+ rc = recvmsg(ofd->fd, &hdr.hdr, flags);
+ if (rc > 0)
+ msgb_put(msg, rc);
+
+ iofd_handle_recv(iofd, msg, 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) {
+ msg = msghdr->msg;
+
+ rc = sendmsg(ofd->fd, &msghdr->hdr, msghdr->flags);
+ if (rc > 0 && rc < msgb_length(msg)) {
+ msgb_pull(msg, rc);
+ iofd_txqueue_enqueue_front(iofd, msghdr);
+ return;
+ }
+ if (rc == -EAGAIN) {
+ iofd_txqueue_enqueue_front(iofd, msghdr);
+ return;
+ }
+
+ switch (iofd->mode) {
+ case OSMO_IO_FD_MODE_READ_WRITE:
+ iofd->io_ops.write_cb(iofd, rc, msg);
+ break;
+ case OSMO_IO_FD_MODE_RECVFROM_SENDTO:
+ iofd->io_ops.sendto_cb(iofd, rc, msg, &msghdr->osa);
+ break;
+ case OSMO_IO_FD_MODE_SCTP_RECVMSG_SENDMSG:
+ OSMO_ASSERT(false);
+ break;
+ }
+
+ talloc_free(msghdr);
+ msgb_free(msg);
+ } else {
+ if (iofd->mode == OSMO_IO_FD_MODE_READ_WRITE)
+ /* Socket is writable, but we have no data to send. A non-blocking/async
+ connect() is signalled this way. */
+ iofd->io_ops.write_cb(iofd, 0, NULL);
+ 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;
+}
+
+int iofd_poll_register(struct osmo_io_fd *iofd)
+{
+ struct osmo_fd *ofd = &iofd->u.poll.ofd;
+ osmo_fd_setup(ofd, iofd->fd, 0, &iofd_poll_ofd_cb_dispatch, iofd, 0);
+ return osmo_fd_register(ofd);
+}
+
+int iofd_poll_unregister(struct osmo_io_fd *iofd)
+{
+ struct osmo_fd *ofd = &iofd->u.poll.ofd;
+ osmo_fd_unregister(ofd);
+
+ return 0;
+}
+
+int iofd_poll_close(struct osmo_io_fd *iofd)
+{
+ osmo_fd_close(&iofd->u.poll.ofd);
+
+ return 0;
+}
+
+void iofd_poll_read_enable(struct osmo_io_fd *iofd)
+{
+ osmo_fd_read_enable(&iofd->u.poll.ofd);
+}
+
+void iofd_poll_read_disable(struct osmo_io_fd *iofd)
+{
+ osmo_fd_read_disable(&iofd->u.poll.ofd);
+}
+
+void iofd_poll_write_enable(struct osmo_io_fd *iofd)
+{
+ osmo_fd_write_enable(&iofd->u.poll.ofd);
+}
+
+void iofd_poll_write_disable(struct osmo_io_fd *iofd)
+{
+ osmo_fd_write_disable(&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,
+};
+
+#endif /* defined(__linux__) */
diff --git a/src/core/osmo_io_uring.c b/src/core/osmo_io_uring.c
new file mode 100644
index 00000000..a6395fea
--- /dev/null
+++ b/src/core/osmo_io_uring.c
@@ -0,0 +1,428 @@
+/*! \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>
+ *
+ * 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 <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);
+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;
+ rc = io_uring_queue_init(IOFD_URING_ENTRIES, &g_ring.ring, 0);
+ if (rc < 0)
+ OSMO_ASSERT(0);
+
+ rc = eventfd(0, 0);
+ if (rc < 0) {
+ io_uring_queue_exit(&g_ring.ring);
+ OSMO_ASSERT(0);
+ }
+
+ osmo_fd_setup(&g_ring.event_ofd, rc, OSMO_FD_READ, iofd_uring_poll_cb, &g_ring.ring, 0);
+ osmo_fd_register(&g_ring.event_ofd);
+ io_uring_register_eventfd(&g_ring.ring, 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);
+ 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_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_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;
+}
+
+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);
+
+static void iofd_uring_handle_tx(struct iofd_msghdr *msghdr, int rc)
+{
+ struct osmo_io_fd *iofd = msghdr->iofd;
+ struct msgb *msg = msghdr->msg;
+
+ if (IOFD_FLAG_ISSET(iofd, IOFD_FLAG_CLOSED))
+ goto out_free;
+
+ /* Error during write */
+ if (rc < 0) {
+ if (msghdr->action == IOFD_ACT_WRITE)
+ iofd->io_ops.write_cb(iofd, rc, msg);
+ else if (msghdr->action == IOFD_ACT_SENDTO)
+ iofd->io_ops.sendto_cb(iofd, rc, msg, &msghdr->osa);
+ else
+ OSMO_ASSERT(0);
+ goto out_free;
+ }
+
+ /* Incomplete write */
+ if (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);
+ goto out;
+ }
+
+ if (msghdr->action == IOFD_ACT_WRITE)
+ iofd->io_ops.write_cb(iofd, rc, msg);
+ else if (msghdr->action == IOFD_ACT_SENDTO)
+ iofd->io_ops.sendto_cb(iofd, rc, msg, &msghdr->osa);
+ else
+ OSMO_ASSERT(0);
+
+out_free:
+ msgb_free(msghdr->msg);
+ iofd_msghdr_free(msghdr);
+
+out:
+ iofd->u.uring.write_msghdr = NULL;
+ if (iofd->u.uring.write_enabled && !IOFD_FLAG_ISSET(iofd, IOFD_FLAG_CLOSED))
+ iofd_uring_submit_tx(iofd);
+}
+
+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:
+ iofd_uring_handle_recv(msghdr, res);
+ break;
+ case IOFD_ACT_WRITE:
+ case IOFD_ACT_SENDTO:
+ iofd_uring_handle_tx(msghdr, res);
+ break;
+ default:
+ OSMO_ASSERT(0)
+ }
+
+ if (!iofd->u.uring.read_msghdr && !iofd->u.uring.write_msghdr)
+ 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);
+}
+
+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;
+ }
+
+ rc = cqe->res;
+ /* Hand the entry back to the kernel before */
+ io_uring_cqe_seen(ring, cqe);
+
+ iofd_uring_handle_completion(msghdr, rc);
+
+ }
+}
+
+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:
+ 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;
+ if (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");
+ io_uring_prep_cancel(sqe, iofd->u.uring.read_msghdr, 0);
+ }
+
+ if (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");
+ io_uring_prep_cancel(sqe, iofd->u.uring.write_msghdr, 0);
+ }
+ io_uring_submit(&g_ring.ring);
+
+ 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;
+
+ 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);
+ 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_tailroom(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);
+ }
+ // Prep msgb/iov
+ 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;
+
+ 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;
+ 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);
+}
+
+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,
+};
+
+#endif /* defined(__linux__) */
diff --git a/src/panic.c b/src/core/panic.c
index 6c925229..bbf6d081 100644
--- a/src/panic.c
+++ b/src/core/panic.c
@@ -27,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 5f44a407..687ad406 100644
--- a/src/plugin.c
+++ b/src/core/plugin.c
@@ -23,7 +23,7 @@
* @{
* \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 ffae1b86..44e26585 100644
--- a/src/rate_ctr.c
+++ b/src/core/rate_ctr.c
@@ -53,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>
@@ -303,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 */
@@ -331,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;
+
+ /* read from timerfd: number of expirations of periodic timer */
+ rc = read(ofd->fd, (void *) &expire_count, sizeof(expire_count));
+ if (rc < 0 && errno == EAGAIN)
+ return 0;
+
+ OSMO_ASSERT(rc == sizeof(expire_count));
- /* Increment number of ticks before we calculate intervals,
- * as a counter value of 0 would already wrap all counters */
- timer_ticks++;
+ if (expire_count > 1)
+ LOGP(DLGLOBAL, LOGL_NOTICE, "Stats timer expire_count=%" PRIu64 ": We missed %" PRIu64 " timers\n",
+ expire_count, expire_count - 1);
- llist_for_each_entry(ctrg, &rate_ctr_groups, list)
- rate_ctr_group_intv(ctrg);
+ 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);
- osmo_timer_schedule(&rate_ctr_timer, 1, 0);
+ return 0;
}
/*! Initialize the counter module. Call this once from your application.
@@ -350,13 +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_timer_pending(&rate_ctr_timer))
+ 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 f4dc219b..f4dc219b 100644
--- a/src/rbtree.c
+++ b/src/core/rbtree.c
diff --git a/src/select.c b/src/core/select.c
index 735ea3ef..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+
*
@@ -36,7 +36,7 @@
#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>
@@ -54,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 */
@@ -118,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)
{
@@ -142,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)) {
@@ -166,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)
{
@@ -184,6 +227,21 @@ void osmo_fd_unregister(struct osmo_fd *fd)
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);
}
@@ -464,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();
}
@@ -653,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 1798acec..1798acec 100644
--- a/src/sercomm.c
+++ b/src/core/sercomm.c
diff --git a/src/serial.c b/src/core/serial.c
index 117c049b..117c049b 100644
--- a/src/serial.c
+++ b/src/core/serial.c
diff --git a/src/signal.c b/src/core/signal.c
index ba1555ae..ba1555ae 100644
--- a/src/signal.c
+++ b/src/core/signal.c
diff --git a/src/sockaddr_str.c b/src/core/sockaddr_str.c
index 9f1e8972..9f1e8972 100644
--- a/src/sockaddr_str.c
+++ b/src/core/sockaddr_str.c
diff --git a/src/socket.c b/src/core/socket.c
index 4605db4c..fa5fb88a 100644
--- a/src/socket.c
+++ b/src/core/socket.c
@@ -17,7 +17,7 @@
*
*/
-#include "../config.h"
+#include "config.h"
/*! \addtogroup socket
* @{
@@ -249,7 +249,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;
}
@@ -561,19 +561,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: " OSMO_SOCKADDR_STR_FMT ": %s\n",
- OSMO_SOCKADDR_STR_FMT_ARGS(sastr), strerror(errno));
+ 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: " OSMO_SOCKADDR_STR_FMT ": %s\n",
- OSMO_SOCKADDR_STR_FMT_ARGS(sastr), strerror(errno));
+ OSMO_SOCKADDR_STR_FMT_ARGS(sastr), strerror(err));
close(sfd);
return -1;
}
@@ -594,9 +596,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: " OSMO_SOCKADDR_STR_FMT ": %s\n",
- OSMO_SOCKADDR_STR_FMT_ARGS(sastr), strerror(errno));
+ OSMO_SOCKADDR_STR_FMT_ARGS(sastr), strerror(err));
close(sfd);
return rc;
}
@@ -693,6 +696,76 @@ static int addrinfo_to_sockaddr(uint16_t family, const struct addrinfo **result,
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
@@ -717,6 +790,38 @@ 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];
@@ -736,6 +841,9 @@ int osmo_sock_init2_multiaddr(uint16_t family, uint16_t type, uint8_t proto,
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");
@@ -776,7 +884,11 @@ int osmo_sock_init2_multiaddr(uint16_t family, uint16_t type, uint8_t proto,
if (((flags & OSMO_SOCK_F_BIND) && (flags & OSMO_SOCK_F_CONNECT)) &&
!addrinfo_has_in6addr_any((const struct addrinfo **)res_loc, local_hosts_cnt) &&
(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");
+ 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;
}
@@ -794,15 +906,61 @@ int osmo_sock_init2_multiaddr(uint16_t family, uint16_t type, uint8_t proto,
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;
}
+ if (pars && 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 && 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 && 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 */
+ }
+ }
+
/* Build array of addresses taking first entry for each host.
TODO: Ideally we should use backtracking storing last used
indexes and trying next combination if connect() fails .*/
@@ -817,9 +975,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;
}
@@ -839,9 +998,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;
}
@@ -1097,6 +1257,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)
{
@@ -1175,6 +1489,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.
@@ -1271,6 +1605,63 @@ void osmo_sockaddr_set_port(struct sockaddr *sa, uint16_t port)
}
}
+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;
+ }
+}
+
/*! 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
@@ -1316,7 +1707,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);
@@ -1336,13 +1727,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
@@ -1469,6 +1860,11 @@ int osmo_sock_get_name_buf(char *str, size_t str_len, int fd)
char portbuf_l[6], portbuf_r[6];
int rc;
+ if (fd < 0) {
+ osmo_strlcpy(str, "<error-bad-fd>", str_len);
+ return -EBADF;
+ }
+
/* get local */
if ((rc = osmo_sock_get_ip_and_port(fd, hostbuf_l, sizeof(hostbuf_l), portbuf_l, sizeof(portbuf_l), true))) {
osmo_strlcpy(str, "<error-in-getsockname>", str_len);
diff --git a/src/stat_item.c b/src/core/stat_item.c
index 804972bf..804972bf 100644
--- a/src/stat_item.c
+++ b/src/core/stat_item.c
diff --git a/src/stat_item_internal.h b/src/core/stat_item_internal.h
index 9ede8c41..9ede8c41 100644
--- a/src/stat_item_internal.h
+++ b/src/core/stat_item_internal.h
diff --git a/src/stats.c b/src/core/stats.c
index 16e6f620..16e6f620 100644
--- a/src/stats.c
+++ b/src/core/stats.c
diff --git a/src/stats_statsd.c b/src/core/stats_statsd.c
index b27baff8..b27baff8 100644
--- a/src/stats_statsd.c
+++ b/src/core/stats_statsd.c
diff --git a/src/stats_tcp.c b/src/core/stats_tcp.c
index ebb380e8..c6459fe8 100644
--- a/src/stats_tcp.c
+++ b/src/core/stats_tcp.c
@@ -247,8 +247,10 @@ int osmo_stats_tcp_osmo_fd_unregister(const struct osmo_fd *fd)
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 itemy, we
- * must designate either a different item or invalidate the current item. */
+ /* 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();
diff --git a/src/strrb.c b/src/core/strrb.c
index df7edb31..c5a5ed6e 100644
--- a/src/strrb.c
+++ b/src/core/strrb.c
@@ -52,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 7741a449..abbe581a 100644
--- a/src/tdef.c
+++ b/src/core/tdef.c
@@ -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)
{
diff --git a/src/thread.c b/src/core/thread.c
index d9a98422..d9a98422 100644
--- a/src/thread.c
+++ b/src/core/thread.c
diff --git a/src/time_cc.c b/src/core/time_cc.c
index 05971fe8..0e6879e5 100644
--- a/src/time_cc.c
+++ b/src/core/time_cc.c
@@ -49,7 +49,7 @@
OSMO_MIN((TIME_CC)->cfg.round_threshold_usec, GRAN_USEC(TIME_CC)) \
: (GRAN_USEC(TIME_CC) / 2))
-static uint64_t time_now_usec()
+static uint64_t time_now_usec(void)
{
struct timespec tp;
if (osmo_clock_gettime(CLOCK_MONOTONIC, &tp))
diff --git a/src/timer.c b/src/core/timer.c
index 20d87a05..20d87a05 100644
--- a/src/timer.c
+++ b/src/core/timer.c
diff --git a/src/timer_clockgettime.c b/src/core/timer_clockgettime.c
index 6112b8a5..6112b8a5 100644
--- a/src/timer_clockgettime.c
+++ b/src/core/timer_clockgettime.c
diff --git a/src/timer_gettimeofday.c b/src/core/timer_gettimeofday.c
index e0212b5a..e0212b5a 100644
--- a/src/timer_gettimeofday.c
+++ b/src/core/timer_gettimeofday.c
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 9714403d..9714403d 100644
--- a/src/use_count.c
+++ b/src/core/use_count.c
diff --git a/src/utils.c b/src/core/utils.c
index 2012b740..231b65c9 100644
--- a/src/utils.c
+++ b/src/core/utils.c
@@ -459,7 +459,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
diff --git a/src/write_queue.c b/src/core/write_queue.c
index 884cebdd..8fb73a67 100644
--- a/src/write_queue.c
+++ b/src/core/write_queue.c
@@ -147,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 5921f048..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=7:0:7
+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 b069ca46..dec19b9b 100644
--- a/src/ctrl/control_cmd.c
+++ b/src/ctrl/control_cmd.c
@@ -210,6 +210,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) {
diff --git a/src/ctrl/control_if.c b/src/ctrl/control_if.c
index 37e430fb..94b8975e 100644
--- a/src/ctrl/control_if.c
+++ b/src/ctrl/control_if.c
@@ -47,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>
@@ -95,7 +96,7 @@ int ctrl_parse_get_num(vector vline, int i, long *num)
/*! 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)
{
@@ -878,7 +879,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;
@@ -1030,12 +1031,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 6910d4cc..a7ebddc2 100644
--- a/src/ctrl/control_vty.c
+++ b/src/ctrl/control_vty.c
@@ -26,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;
}
@@ -46,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..306ad3f1 100644
--- a/src/ctrl/libosmoctrl.map
+++ b/src/ctrl/libosmoctrl.map
@@ -22,12 +22,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 b0fcaa98..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=14: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
@@ -21,7 +20,7 @@ libosmogb_la_LDFLAGS = \
-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
@@ -41,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/frame_relay.c b/src/gb/frame_relay.c
index 2c252fd5..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;
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 71f7ea84..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.
@@ -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 e250eda8..304fe7c1 100644
--- a/src/gb/gprs_ns.c
+++ b/src/gb/gprs_ns.c
@@ -532,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)
{
diff --git a/src/gb/gprs_ns2.c b/src/gb/gprs_ns2.c
index 02d2266a..4e496c1f 100644
--- a/src/gb/gprs_ns2.c
+++ b/src/gb/gprs_ns2.c
@@ -1451,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 4e848be8..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"
@@ -92,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;
@@ -172,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);
@@ -333,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)
@@ -515,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");
@@ -589,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;
@@ -854,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);
@@ -871,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;
@@ -888,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 0959d2b0..7ac77e56 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>
@@ -79,6 +80,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 +167,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;
};
@@ -271,6 +273,9 @@ 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;
};
@@ -449,6 +454,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 cc95334e..de63b7aa 100644
--- a/src/gb/gprs_ns2_message.c
+++ b/src/gb/gprs_ns2_message.c
@@ -169,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 */
@@ -282,7 +268,7 @@ int ns2_tx_block_ack(struct gprs_ns2_vc *nsvc, uint16_t *nsvci)
/*! 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)
{
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 1db2a8ea..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;
@@ -268,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:
@@ -345,7 +343,7 @@ 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);
@@ -363,7 +361,7 @@ 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;
@@ -563,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 {
@@ -717,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;
@@ -834,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);
@@ -846,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);
diff --git a/src/gb/gprs_ns2_vty.c b/src/gb/gprs_ns2_vty.c
index 016199da..3132e347 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>
@@ -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,18 +1900,21 @@ 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 since ", 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),
- ns2_vc_is_unblocked(nsvc) ? "ALIVE" : "DEAD");
+ 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 since ",
+ vty_out(vty, " %s %s sig_weight=%u data_weight=%u %s %s %ssince ",
osmo_fsm_inst_state_name(nsvc->fi),
nsvc->persistent ? "PERSIST" : "DYNAMIC",
nsvc->sig_weight, nsvc->data_weight,
gprs_ns2_ll_str(nsvc),
- ns2_vc_is_unblocked(nsvc) ? "ALIVE" : "DEAD");
+ ns2_vc_is_unblocked(nsvc) ? "ALIVE" : "DEAD",
+ !ns2_vc_is_unblocked(nsvc) ? "(cause: remote) " : "");
vty_out_uptime(vty, &nsvc->ts_alive_change);
vty_out_newline(vty);
@@ -2289,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 7e2949e8..a01189ca 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=18: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,26 @@ 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 \
+ tuak/KeccakP-1600-3gpp.c tuak/tuak.c auth_tuak.c \
gsup.c gsup_sms.c gprs_gea.c gsm0503_conv.c oap.c gsm0808_utils.c \
gsm23003.c gsm23236.c mncc.c bts_features.c oap_client.c \
- gsm29118.c gsm48_rest_octets.c cbsp.c gsm48049.c i460_mux.c \
- gad.c bsslap.c bssmap_le.c kdf.c iuup.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
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 +55,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/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/auth_comp128v1.c b/src/gsm/auth_comp128v1.c
index ba3cf601..ded3f8a6 100644
--- a/src/gsm/auth_comp128v1.c
+++ b/src/gsm/auth_comp128v1.c
@@ -27,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 91fb2597..f942bc02 100644
--- a/src/gsm/auth_comp128v23.c
+++ b/src/gsm/auth_comp128v23.c
@@ -29,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;
@@ -46,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 f4508501..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
*
@@ -36,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
@@ -138,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
@@ -153,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;
@@ -188,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[] = {
@@ -208,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 }
};
@@ -245,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 4a430b3c..2bd05a6d 100644
--- a/src/gsm/auth_milenage.c
+++ b/src/gsm/auth_milenage.c
@@ -19,6 +19,7 @@
*
*/
+#include <errno.h>
#include <osmocom/crypt/auth.h>
#include <osmocom/core/bits.h>
#include "milenage/common.h"
@@ -28,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;
@@ -43,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);
@@ -53,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)
@@ -127,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;
@@ -141,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)
{
@@ -150,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 45075421..a506a03d 100644
--- a/src/gsm/auth_xor.c
+++ b/src/gsm/auth_xor.c
@@ -43,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;
/**
@@ -125,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)
@@ -134,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] */
@@ -168,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 7b31d1f8..70ded13f 100644
--- a/src/gsm/bsslap.c
+++ b/src/gsm/bsslap.c
@@ -217,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 3e90a43b..1ee45517 100644
--- a/src/gsm/bssmap_le.c
+++ b/src/gsm/bssmap_le.c
@@ -180,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");
@@ -348,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");
@@ -558,7 +558,7 @@ static int osmo_bssmap_le_dec_perform_loc_req(struct bssmap_le_perform_loc_req *
struct osmo_bssmap_le_err **err, void *err_ctx,
const struct tlv_parsed *tp)
{
- *params = (struct bssmap_le_perform_loc_req){};
+ memset(params, 0x00, sizeof(*params));
DEC_IE_MANDATORY(msgt, BSSMAP_LE_IEI_LOCATION_TYPE, osmo_bssmap_le_ie_dec_location_type,
&params->location_type);
@@ -606,7 +606,7 @@ static int osmo_bssmap_le_dec_perform_loc_resp(struct bssmap_le_perform_loc_resp
struct osmo_bssmap_le_err **err, void *err_ctx,
const struct tlv_parsed *tp)
{
- *params = (struct bssmap_le_perform_loc_resp){};
+ memset(params, 0x00, sizeof(*params));
DEC_IE_OPTIONAL_FLAG(msgt, BSSMAP_LE_IEI_GEO_LOCATION, osmo_bssmap_le_ie_dec_gad, &params->location_estimate,
params->location_estimate_present);
@@ -630,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;
@@ -647,7 +647,8 @@ static int osmo_bssmap_le_dec_conn_oriented_info(struct bssmap_le_conn_oriented_
struct osmo_bssmap_le_err **err, void *err_ctx,
const struct tlv_parsed *tp)
{
- *params = (struct bssmap_le_conn_oriented_info){};
+ memset(params, 0x00, sizeof(*params));
+
DEC_IE_MANDATORY(msgt, BSSMAP_LE_IEI_APDU, osmo_bssmap_le_ie_dec_apdu, &params->apdu);
return 0;
}
@@ -711,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");
@@ -798,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 6811038a..b6cd82ec 100644
--- a/src/gsm/bts_features.c
+++ b/src/gsm/bts_features.c
@@ -16,6 +16,7 @@
* GNU General Public License for more details.
*/
+#include <osmocom/core/utils.h>
#include <osmocom/gsm/bts_features.h>
const struct value_string osmo_bts_features_descs[] = {
@@ -44,9 +45,14 @@ const struct value_string osmo_bts_features_descs[] = {
{ 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)
@@ -80,5 +86,10 @@ const struct value_string osmo_bts_features_names[] = {
{ 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 7411056f..28852f6f 100644
--- a/src/gsm/cbsp.c
+++ b/src/gsm/cbsp.c
@@ -30,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)
{
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/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/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 4bb73038..529dbdfe 100644
--- a/src/gsm/gsm0808.c
+++ b/src/gsm/gsm0808.c
@@ -17,6 +17,8 @@
*
*/
+#include "config.h"
+
#include <string.h>
#include <osmocom/core/byteswap.h>
@@ -100,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.
@@ -373,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;
@@ -418,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");
@@ -530,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) {
@@ -552,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.
@@ -616,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 */
@@ -632,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
@@ -693,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
@@ -992,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];
@@ -1013,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.
@@ -1048,17 +1082,25 @@ struct msgb *gsm0808_create_handover_request_ack2(const struct gsm0808_handover_
/* 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)
- gsm0808_enc_speech_codec_list(msg, &params->codec_list_bss_supported);
+ if (params->more_items && params->codec_list_bss_supported.len) {
+ if (gsm0808_enc_speech_codec_list2(msg, &params->codec_list_bss_supported) < 0)
+ goto exit_free;
+ }
/* AoIP: Speech Codec (Chosen) 3.2.2.104 */
- if (params->speech_codec_chosen_present)
- gsm0808_enc_speech_codec(msg, &params->speech_codec_chosen);
+ if (params->speech_codec_chosen_present) {
+ if (gsm0808_enc_speech_codec2(msg, &params->speech_codec_chosen) < 0)
+ goto exit_free;
+ }
/* prepend header with final length */
msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg));
return msg;
+
+exit_free:
+ msgb_free(msg);
+ return NULL;
}
/*! Same as gsm0808_create_handover_request_ack2() but with less parameters.
@@ -1110,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;
@@ -1129,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;
@@ -1164,12 +1206,16 @@ struct msgb *gsm0808_create_handover_complete(const struct gsm0808_handover_comp
msgb_tlv_put(msg, GSM0808_IE_RR_CAUSE, 1, &params->rr_cause);
/* AoIP: Speech Codec (Chosen) 3.2.2.104 */
- if (params->speech_codec_chosen_present)
- gsm0808_enc_speech_codec(msg, &params->speech_codec_chosen);
+ if (params->speech_codec_chosen_present) {
+ if (gsm0808_enc_speech_codec2(msg, &params->speech_codec_chosen) < 0)
+ goto exit_free;
+ }
/* AoIP: add Codec List (BSS Supported) 3.2.2.103 */
- if (params->codec_list_bss_supported.len)
- gsm0808_enc_speech_codec_list(msg, &params->codec_list_bss_supported);
+ if (params->codec_list_bss_supported.len) {
+ if (gsm0808_enc_speech_codec_list2(msg, &params->codec_list_bss_supported) < 0)
+ goto exit_free;
+ }
/* Chosen Encryption Algorithm 3.2.2.44 */
if (params->chosen_encr_alg_present && params->chosen_encr_alg > 0)
@@ -1183,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.
@@ -1206,13 +1256,19 @@ struct msgb *gsm0808_create_handover_failure(const struct gsm0808_handover_failu
msgb_tlv_put(msg, GSM0808_IE_RR_CAUSE, 1, &params->rr_cause);
/* AoIP: add Codec List (BSS Supported) 3.2.2.103 */
- if (params->codec_list_bss_supported.len)
- gsm0808_enc_speech_codec_list(msg, &params->codec_list_bss_supported);
+ if (params->codec_list_bss_supported.len) {
+ if (gsm0808_enc_speech_codec_list2(msg, &params->codec_list_bss_supported) < 0)
+ goto exit_free;
+ }
/* prepend header with final length */
msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg));
return msg;
+
+exit_free:
+ msgb_free(msg);
+ return NULL;
}
/*! Create BSSMAP HANDOVER PERFORMED message, 3GPP TS 48.008 3.2.1.25.
@@ -1248,8 +1304,10 @@ struct msgb *gsm0808_create_handover_performed(const struct gsm0808_handover_per
msgb_tv_put(msg, GSM0808_IE_SPEECH_VERSION, params->speech_version_chosen);
/* AoIP: Speech Codec (chosen) 3.2.2.104 */
- if (params->speech_codec_chosen_present)
- gsm0808_enc_speech_codec(msg, &params->speech_codec_chosen);
+ if (params->speech_codec_chosen_present) {
+ if (gsm0808_enc_speech_codec2(msg, &params->speech_codec_chosen) < 0)
+ goto exit_free;
+ }
/* LCLS-BSS-Status 3.2.2.119 */
if (params->lcls_bss_status_present)
@@ -1259,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.
@@ -1454,6 +1516,663 @@ struct msgb *gsm0808_create_perform_location_abort(const struct lcs_cause_ie *lc
return msg;
}
+/*! Create BSSMAP VGCS/VBS SETUP message, 3GPP TS 48.008 3.2.1.50.
+ * Sent from the MSC to the BSC to request VGCS/VBS call. */
+struct msgb *gsm0808_create_vgcs_vbs_setup(const struct gsm0808_vgcs_vbs_setup *params)
+{
+ struct msgb *msg;
+
+ msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM, "BSSMAP-VGCS/VBS-SETUP");
+ if (!msg)
+ return NULL;
+
+ /* Message Type, 3.2.2.1 */
+ msgb_v_put(msg, BSS_MAP_MSG_VGCS_VBS_SETUP);
+
+ /* Group Call Reference, 3.2.2.55 */
+ gsm0808_enc_group_callref(msg, &params->callref);
+
+ /* Priority, 3.2.2.18 */
+ if (params->priority_present)
+ gsm0808_enc_priority(msg, &params->priority);
+
+ /* VGCS Feature Flags, 3.2.2.88 */
+ if (params->vgcs_feature_flags_present)
+ gsm0808_enc_vgcs_feature_flags(msg, &params->flags);
+
+ /* prepend header with final length */
+ msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg));
+
+ return msg;
+}
+
+/*! Create BSSMAP VGCS/VBS SETUP ACK message, 3GPP TS 48.008 3.2.1.51.
+ * Sent from the BSC to the MSC to confirm VGCS/VBS call. */
+struct msgb *gsm0808_create_vgcs_vbs_setup_ack(const struct gsm0808_vgcs_vbs_setup_ack *params)
+{
+ struct msgb *msg;
+
+ msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM, "BSSMAP-VGCS/VBS-SETUP-ACK");
+ if (!msg)
+ return NULL;
+
+ /* Message Type, 3.2.2.1 */
+ msgb_v_put(msg, BSS_MAP_MSG_VGCS_VBS_SETUP_ACK);
+
+ /* VGCS Feature Flags, 3.2.2.88 */
+ if (params->vgcs_feature_flags_present)
+ gsm0808_enc_vgcs_feature_flags(msg, &params->flags);
+
+ /* prepend header with final length */
+ msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg));
+
+ return msg;
+}
+
+/*! Create BSSMAP VGCS/VBS SETUP REFUSE message, 3GPP TS 48.008 3.2.1.52.
+ * Sent from the BSC to the MSC to reject VGCS/VBS call. */
+struct msgb *gsm0808_create_vgcs_vbs_setup_refuse(enum gsm0808_cause cause)
+{
+ struct msgb *msg;
+
+ msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM, "BSSMAP-VGCS/VBS-SETUP-REFUSE");
+ if (!msg)
+ return NULL;
+
+ /* Message Type, 3.2.2.1 */
+ msgb_v_put(msg, BSS_MAP_MSG_VGCS_VBS_SETUP_REFUSE);
+
+ /* Cause, 3.2.2.5 */
+ gsm0808_enc_cause(msg, cause);
+
+ /* prepend header with final length */
+ msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg));
+
+ return msg;
+}
+
+/*! Create BSSMAP VGCS/VBS ASSIGNMENT REQUEST message, 3GPP TS 48.008 3.2.1.53.
+ * Sent from the MSC to the BSC to assign radio resources for a VGCS/VBS. */
+struct msgb *gsm0808_create_vgcs_vbs_assign_req(const struct gsm0808_vgcs_vbs_assign_req *params)
+{
+ struct msgb *msg;
+
+ msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM, "BSSMAP-VGCS/VBS-ASSIGNMENT-REQUEST");
+ if (!msg)
+ return NULL;
+
+ /* Message Type, 3.2.2.1 */
+ msgb_v_put(msg, BSS_MAP_MSG_VGCS_VBS_ASSIGNMENT_RQST);
+
+ /* Channel Type, 3.2.2.11 */
+ gsm0808_enc_channel_type(msg, &params->channel_type);
+
+ /* Assignment Requrirement, 3.2.2.52 */
+ gsm0808_enc_assign_req(msg, params->ass_req);
+
+ /* Cell Identifier, 3.2.2.17 */
+ gsm0808_enc_cell_id(msg, &params->cell_identifier);
+
+ /* Group Call Reference, 3.2.2.55 */
+ gsm0808_enc_group_callref(msg, &params->callref);
+
+ /* Priority, 3.2.2.18 */
+ if (params->priority_present)
+ gsm0808_enc_priority(msg, &params->priority);
+
+ /* Circuit Identity Code, 3.2.2.2 */
+ if (params->cic_present)
+ msgb_tv16_put(msg, GSM0808_IE_CIRCUIT_IDENTITY_CODE, params->cic);
+
+ /* Downlink DTX Flag, 3.2.2.26 */
+ if (params->downlink_dtx_flag_present)
+ msgb_tv_put(msg, GSM0808_IE_DOWNLINK_DTX_FLAG, params->downlink_dtx_flag);
+
+ /* Encryption Information, 3.2.2.10 */
+ if (params->encryption_information_present)
+ gsm0808_enc_encrypt_info(msg, &params->encryption_information);
+
+ /* VSTK_RAND Imformation, 3.2.2.83 */
+ if (params->vstk_rand_present)
+ msgb_tlv_put(msg, GSM0808_IE_VSTK_RAND_INFO, sizeof(params->vstk_rand), params->vstk_rand);
+
+ /* VSTK Information, 3.2.2.84 */
+ if (params->vstk_present)
+ msgb_tlv_put(msg, GSM0808_IE_VSTK_INFO, sizeof(params->vstk), params->vstk);
+
+ /* Cell Identifier List Segment, 3.2.2.27a */
+ if (params->cils_present)
+ gsm0808_enc_cell_id_list_segment(msg, GSM0808_IE_CELL_ID_LIST_SEGMENT, &params->cils);
+
+ /* AoIP Transport Layer Address (MGW), 3.2.2.102 */
+ if (params->aoip_transport_layer_present)
+ gsm0808_enc_aoip_trasp_addr(msg, &params->aoip_transport_layer);
+
+ /* Call Identifier, 3.2.2.105 */
+ if (params->call_id_present) {
+ /* NOTE: 3GPP TS 48.008, section 3.2.2.105 specifies that
+ * the least significant byte shall be transmitted first. */
+ msgb_v_put(msg, GSM0808_IE_CALL_ID);
+ osmo_store32le(params->call_id, msgb_put(msg, sizeof(uint32_t)));
+ }
+
+ /* Codec List (MSC Preferred) 3.2.2.103 */
+ if (params->codec_list_present) {
+ if (gsm0808_enc_speech_codec_list2(msg, &params->codec_list_msc_preferred) < 0)
+ goto exit_free;
+ }
+
+ /* prepend header with final length */
+ msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg));
+
+ return msg;
+
+exit_free:
+ msgb_free(msg);
+ return NULL;
+}
+
+/*! Create BSSMAP VGCS/VBS ASSIGNMENT RESULT message, 3GPP TS 48.008 3.2.1.54.
+ * Sent from the BSC to the MSC to indicate assignment/deassingment of radio resources for a VGCS/VBS. */
+struct msgb *gsm0808_create_vgcs_vbs_assign_res(const struct gsm0808_vgcs_vbs_assign_res *params)
+{
+ struct msgb *msg;
+
+ msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM, "BSSMAP-VGCS/VBS-ASSIGNMENT-RESULT");
+ if (!msg)
+ return NULL;
+
+ /* Message Type, 3.2.2.1 */
+ msgb_v_put(msg, BSS_MAP_MSG_VGCS_VBS_ASSIGNMENT_RESULT);
+
+ /* Channel Type, 3.2.2.11 */
+ gsm0808_enc_channel_type(msg, &params->channel_type);
+
+ /* Cell Identifier, 3.2.2.17 */
+ gsm0808_enc_cell_id(msg, &params->cell_identifier);
+
+ /* Chosen Channel, 3.2.2.33 */
+ if (params->chosen_channel_present)
+ msgb_tv_put(msg, GSM0808_IE_CHOSEN_CHANNEL, params->chosen_channel);
+
+ /* Circuit Identity Code, 3.2.2.2 */
+ if (params->cic_present)
+ msgb_tv16_put(msg, GSM0808_IE_CIRCUIT_IDENTITY_CODE, params->cic);
+
+ /* Circuit Pool, 3.2.2.45 */
+ if (params->circuit_pool_present)
+ msgb_tv_put(msg, GSM0808_IE_CIRCUIT_POOL, params->circuit_pool);
+
+ /* AoIP Transport Layer Address (BSS), 3.2.2.102 */
+ if (params->aoip_transport_layer_present)
+ gsm0808_enc_aoip_trasp_addr(msg, &params->aoip_transport_layer);
+
+ /* Codec (MSC Chosen) 3.2.2.103 */
+ if (params->codec_present) {
+ if (gsm0808_enc_speech_codec2(msg, &params->codec_msc_chosen) < 0)
+ goto exit_free;
+ }
+
+ /* Call Identifier, 3.2.2.105 */
+ if (params->call_id_present) {
+ /* NOTE: 3GPP TS 48.008, section 3.2.2.105 specifies that
+ * the least significant byte shall be transmitted first. */
+ msgb_v_put(msg, GSM0808_IE_CALL_ID);
+ osmo_store32le(params->call_id, msgb_put(msg, sizeof(uint32_t)));
+ }
+
+ /* prepend header with final length */
+ msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg));
+
+ return msg;
+
+exit_free:
+ msgb_free(msg);
+ return NULL;
+}
+
+/*! Create BSSMAP VGCS/VBS ASSIGNMENT FAILURE message, 3GPP TS 48.008 3.2.1.55.
+ * Sent from the BSC to the MSC to indicate assignment failure for a VGCS/VBS. */
+struct msgb *gsm0808_create_vgcs_vbs_assign_fail(const struct gsm0808_vgcs_vbs_assign_fail *params)
+{
+ struct msgb *msg;
+
+ msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM, "BSSMAP-VGCS/VBS-ASSIGNMENT-RESULT");
+ if (!msg)
+ return NULL;
+
+ /* Message Type, 3.2.2.1 */
+ msgb_v_put(msg, BSS_MAP_MSG_VGCS_VBS_ASSIGNMENT_FAILURE);
+
+ /* Cause, 3.2.2.5 */
+ gsm0808_enc_cause(msg, params->cause);
+
+ /* Circuit Pool, 3.2.2.45 */
+ if (params->circuit_pool_present)
+ msgb_tv_put(msg, GSM0808_IE_CIRCUIT_POOL, params->circuit_pool);
+
+ /* Circuit Pool List, 3.2.2.46 */
+ if (params->circuit_pool_present)
+ msgb_tlv_put(msg, GSM0808_IE_CIRCUIT_POOL_LIST, params->cpl.list_len, params->cpl.pool);
+
+ /* Codec List (BSS Supported) 3.2.2.103 */
+ if (params->codec_list_present) {
+ if (gsm0808_enc_speech_codec_list2(msg, &params->codec_list_bss_supported) < 0)
+ goto exit_free;
+ }
+
+ /* prepend header with final length */
+ msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg));
+
+ return msg;
+
+exit_free:
+ msgb_free(msg);
+ return NULL;
+}
+
+/*! Create BSSMAP VGCS/VBS QUEUING INDICATION message, 3GPP TS 48.008 3.2.1.56.
+ * Sent from the BSC to the MSC to indicate delay in assignment for a VGCS/VBS. */
+struct msgb *gsm0808_create_vgcs_queuing_ind(void)
+{
+ struct msgb *msg;
+ uint8_t val = BSS_MAP_MSG_VGCS_VBS_QUEUING_INDICATION;
+
+ msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM, "BSSMAP-VGCS/VBS-QUEUING-INDICATION");
+ if (!msg)
+ return NULL;
+
+ msg->l3h = msg->data;
+ msgb_tlv_put(msg, BSSAP_MSG_BSS_MANAGEMENT, 1, &val);
+
+ return msg;
+}
+
+/*! Create BSSMAP (VGCS) UPLINK REQUEST message, 3GPP TS 48.008 3.2.1.57.
+ * Sent from the BSC to the MSC to indicate that a mobile requested access to uplink. */
+struct msgb *gsm0808_create_uplink_request(const struct gsm0808_uplink_request *params)
+{
+ struct msgb *msg;
+ int rc;
+
+ msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM, "BSSMAP-UPLINK-REQUEST");
+ if (!msg)
+ return NULL;
+
+ /* Message Type, 3.2.2.1 */
+ msgb_v_put(msg, BSS_MAP_MSG_UPLINK_RQST);
+
+ /* Talker Priority, 3.2.2.89 */
+ if (params->talker_priority_present)
+ msgb_tv_put(msg, GSM0808_IE_TALKER_PRIORITY, params->talker_priority);
+
+ /* Cell Identifier, 3.2.2.17 */
+ if (params->cell_identifier_present)
+ gsm0808_enc_cell_id(msg, &params->cell_identifier);
+
+ /* Layer 3 Information, 3.2.2.24 */
+ if (params->l3_present)
+ msgb_tlv_put(msg, GSM0808_IE_LAYER_3_INFORMATION, params->l3.l3_len, params->l3.l3);
+
+ /* Mobile Identity, 3.2.2.41 */
+ if (params->mi_present) {
+ rc = osmo_mobile_identity_encode_msgb(msg, &params->mi, false);
+ if (rc < 0) {
+ msgb_free(msg);
+ return NULL;
+ }
+ }
+
+ /* prepend header with final length */
+ msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg));
+
+ return msg;
+}
+
+/*! Create BSSMAP (VGCS) UPLINK REQUEST ACKNOWLEDGE message, 3GPP TS 48.008 3.2.1.58.
+ * Sent from the MSC to the BSC to indicate that access to uplink was granted. */
+struct msgb *gsm0808_create_uplink_request_ack(const struct gsm0808_uplink_request_ack *params)
+{
+ struct msgb *msg;
+
+ msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM, "BSSMAP-UPLINK-REQUEST-ACKNOWLEDGE");
+ if (!msg)
+ return NULL;
+
+ /* Message Type, 3.2.2.1 */
+ msgb_v_put(msg, BSS_MAP_MSG_UPLINK_RQST_ACKNOWLEDGE);
+
+ /* Talker Priority, 3.2.2.89 */
+ if (params->talker_priority_present)
+ msgb_tv_put(msg, GSM0808_IE_TALKER_PRIORITY, params->talker_priority);
+
+ /* Emergency set indication, 3.2.2.90 */
+ if (params->emerg_set_ind_present)
+ msgb_v_put(msg, GSM0808_IE_EMERGENCY_SET_INDICATION);
+
+ /* Talker Identity, 3.2.2.91 */
+ if (params->talker_identity_present)
+ gsm0808_enc_talker_identity(msg, &params->talker_identity);
+
+ /* prepend header with final length */
+ msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg));
+
+ return msg;
+}
+
+/*! Create BSSMAP (VGCS) UPLINK CONFIRM message, 3GPP TS 48.008 3.2.1.59.
+ * Sent from the BSC to the MSC to indicate that access to uplink was has been successfully established. */
+struct msgb *gsm0808_create_uplink_request_cnf(const struct gsm0808_uplink_request_cnf *params)
+{
+ struct msgb *msg;
+
+ msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM, "BSSMAP-UPLINK-REQUEST-CONFIRM");
+ if (!msg)
+ return NULL;
+
+ /* Message Type, 3.2.2.1 */
+ msgb_v_put(msg, BSS_MAP_MSG_UPLINK_RQST_CONFIRMATION);
+
+ /* Cell Identifier, 3.2.2.17 */
+ gsm0808_enc_cell_id(msg, &params->cell_identifier);
+
+ /* Talker Identity, 3.2.2.91 */
+ if (params->talker_identity_present)
+ gsm0808_enc_talker_identity(msg, &params->talker_identity);
+
+ /* Layer 3 Information, 3.2.2.24 */
+ msgb_tlv_put(msg, GSM0808_IE_LAYER_3_INFORMATION, params->l3.l3_len, params->l3.l3);
+
+ /* prepend header with final length */
+ msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg));
+
+ return msg;
+}
+
+/*! Create BSSMAP (VGCS) UPLINK APPLICATION DATA message, 3GPP TS 48.008 3.2.1.59a.
+ * Sent from the BSC to the MSC to pass L3 info from the talker. */
+struct msgb *gsm0808_create_uplink_app_data(const struct gsm0808_uplink_app_data *params)
+{
+ struct msgb *msg;
+ uint8_t val;
+
+ msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM, "BSSMAP-UPLINK-APPLICATION-DATA");
+ if (!msg)
+ return NULL;
+
+ /* Message Type, 3.2.2.1 */
+ msgb_v_put(msg, BSS_MAP_MSG_UPLINK_APP_DATA);
+
+ /* Cell Identifier, 3.2.2.17 */
+ gsm0808_enc_cell_id(msg, &params->cell_identifier);
+
+ /* Layer 3 Information, 3.2.2.24 */
+ msgb_tlv_put(msg, GSM0808_IE_LAYER_3_INFORMATION, params->l3.l3_len, params->l3.l3);
+
+ /* Application Data Information, 3.2.2.100 */
+ val = params->bt_ind;
+ msgb_tlv_put(msg, GSM0808_IE_APP_DATA_INFO, 1, &val);
+
+ /* prepend header with final length */
+ msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg));
+
+ return msg;
+}
+
+/*! Create BSSMAP (VGCS) UPLINK RELEASE INDICATION message, 3GPP TS 48.008 3.2.1.60.
+ * Sent from the BSC to the MSC to indicate that the uplink has been released. */
+struct msgb *gsm0808_create_uplink_release_ind(const struct gsm0808_uplink_release_ind *params)
+{
+ struct msgb *msg;
+
+ msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM, "BSSMAP-UPLINK-RELEASE-INDICATION");
+ if (!msg)
+ return NULL;
+
+ /* Message Type, 3.2.2.1 */
+ msgb_v_put(msg, BSS_MAP_MSG_UPLINK_RELEASE_INDICATION);
+
+ /* Cause, 3.2.2.5 */
+ gsm0808_enc_cause(msg, params->cause);
+
+ /* Talker Priority, 3.2.2.89 */
+ if (params->talker_priority_present)
+ msgb_tv_put(msg, GSM0808_IE_TALKER_PRIORITY, params->talker_priority);
+
+ /* prepend header with final length */
+ msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg));
+
+ return msg;
+}
+
+/*! Create BSSMAP (VGCS) UPLINK REJECT COMMAND message, 3GPP TS 48.008 3.2.1.61.
+ * Sent from the MSC to the BSC to indicate that the uplink is not available for allocation. */
+struct msgb *gsm0808_create_uplink_reject_cmd(const struct gsm0808_uplink_reject_cmd *params)
+{
+ struct msgb *msg;
+
+ msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM, "BSSMAP-UPLINK-REJECT-COMMAND");
+ if (!msg)
+ return NULL;
+
+ /* Message Type, 3.2.2.1 */
+ msgb_v_put(msg, BSS_MAP_MSG_UPLINK_REJECT_CMD);
+
+ /* Cause, 3.2.2.5 */
+ gsm0808_enc_cause(msg, params->cause);
+
+ /* Talker Priority, 3.2.2.89 */
+ if (params->current_talker_priority_present)
+ msgb_tv_put(msg, GSM0808_IE_TALKER_PRIORITY, params->current_talker_priority);
+
+ /* Talker Priority, 3.2.2.89 */
+ if (params->rejected_talker_priority_present)
+ msgb_tv_put(msg, GSM0808_IE_TALKER_PRIORITY, params->rejected_talker_priority);
+
+ /* Talker Identity, 3.2.2.91 */
+ if (params->talker_identity_present)
+ gsm0808_enc_talker_identity(msg, &params->talker_identity);
+
+ /* prepend header with final length */
+ msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg));
+
+ return msg;
+}
+
+/*! Create BSSMAP (VGCS) UPLINK RELEASE COMMAND message, 3GPP TS 48.008 3.2.1.62.
+ * Sent from the MSC to the BSC to indicate that the uplink is available for allocation. */
+struct msgb *gsm0808_create_uplink_release_cmd(const enum gsm0808_cause cause)
+{
+ struct msgb *msg;
+
+ msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM, "BSSMAP-UPLINK-RELEASE-COMMAND");
+ if (!msg)
+ return NULL;
+
+ /* Message Type, 3.2.2.1 */
+ msgb_v_put(msg, BSS_MAP_MSG_UPLINK_RELEASE_CMD);
+
+ /* Cause, 3.2.2.5 */
+ gsm0808_enc_cause(msg, cause);
+
+ /* prepend header with final length */
+ msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg));
+
+ return msg;
+}
+
+/*! Create BSSMAP (VGCS) UPLINK SEIZED COMMAND message, 3GPP TS 48.008 3.2.1.62.
+ * Sent from the MSC to the BSC to indicate that the uplink is no longer available for allocation. */
+struct msgb *gsm0808_create_uplink_seized_cmd(const struct gsm0808_uplink_seized_cmd *params)
+{
+ struct msgb *msg;
+
+ msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM, "BSSMAP-UPLINK-SEIZED-COMMAND");
+ if (!msg)
+ return NULL;
+
+ /* Message Type, 3.2.2.1 */
+ msgb_v_put(msg, BSS_MAP_MSG_UPLINK_SEIZED_CMD);
+
+ /* Cause, 3.2.2.5 */
+ gsm0808_enc_cause(msg, params->cause);
+
+ /* Talker Priority, 3.2.2.89 */
+ if (params->talker_priority_present)
+ msgb_tv_put(msg, GSM0808_IE_TALKER_PRIORITY, params->talker_priority);
+
+ /* Emergency set indication, 3.2.2.90 */
+ if (params->emerg_set_ind_present)
+ msgb_v_put(msg, GSM0808_IE_EMERGENCY_SET_INDICATION);
+
+ /* Talker Identity, 3.2.2.91 */
+ if (params->talker_identity_present)
+ gsm0808_enc_talker_identity(msg, &params->talker_identity);
+
+ /* prepend header with final length */
+ msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg));
+
+ return msg;
+}
+
+/*! Create BSSMAP VGCS ADDITIONAL INFORMATION message, 3GPP TS 48.008 3.2.1.78.
+ * Sent from the MSC to the BSC to transfer talker identity. */
+struct msgb *gsm0808_create_vgcs_additional_info(const struct gsm0808_talker_identity *ti)
+{
+ struct msgb *msg;
+
+ msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM, "BSSMAP-VGCS-ADDITIONAL-INFO");
+ if (!msg)
+ return NULL;
+
+ /* Message Type, 3.2.2.1 */
+ msgb_v_put(msg, BSS_MAP_MSG_VGCS_ADDL_INFO);
+
+ /* Talker Identity, 3.2.2.91 */
+ gsm0808_enc_talker_identity(msg, ti);
+
+ /* prepend header with final length */
+ msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg));
+
+ return msg;
+}
+
+/*! Create BSSMAP VGCS/VBS AREA CELL INFO message, 3GPP TS 48.008 3.2.1.79.
+ * Sent from the BSC to the MSC to transfer additional infos about cells. */
+struct msgb *gsm0808_create_vgcs_vbs_area_cell_info(const struct gsm0808_vgcs_vbs_area_cell_info *params)
+{
+ struct msgb *msg;
+
+ msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM, "BSSMAP-VGCS/VBS-AREA-CELL-INFO");
+ if (!msg)
+ return NULL;
+
+ /* Message Type, 3.2.2.1 */
+ msgb_v_put(msg, BSS_MAP_MSG_VGCS_VBS_ASSIGNMENT_RQST);
+
+ /* Cell Identifier List Segment, 3.2.2.27a */
+ gsm0808_enc_cell_id_list_segment(msg, GSM0808_IE_CELL_ID_LIST_SEGMENT, &params->cils);
+
+ /* Assignment Requrirement, 3.2.2.52 */
+ if (params->ass_req_present)
+ gsm0808_enc_assign_req(msg, params->ass_req);
+
+ /* prepend header with final length */
+ msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg));
+
+ return msg;
+}
+
+/*! Create BSSMAP VGCS/VBS ASSIGNMENT STATUS message, 3GPP TS 48.008 3.2.1.80.
+ * Sent from the BSC to the MSC to indicate assignment status for each cell. */
+struct msgb *gsm0808_create_vgcs_vbs_assign_stat(const struct gsm0808_vgcs_vbs_assign_stat *params)
+{
+ struct msgb *msg;
+ uint8_t val;
+
+ msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM, "BSSMAP-VGCS/VBS-ASSIGNMENT-STATUS");
+ if (!msg)
+ return NULL;
+
+ /* Message Type, 3.2.2.1 */
+ msgb_v_put(msg, BSS_MAP_MSG_VGCS_VBS_ASSIGNMENT_STATUS);
+
+ /* Cell Identifier List Segment, 3.2.2.27b */
+ if (params->cils_est_present)
+ gsm0808_enc_cell_id_list_segment(msg, GSM0808_IE_CELL_ID_LIST_SEG_EST_CELLS, &params->cils_est);
+ /* Cell Identifier List Segment, 3.2.2.27c */
+ if (params->cils_tbe_present)
+ gsm0808_enc_cell_id_list_segment(msg, GSM0808_IE_CELL_ID_LIST_SEG_CELLS_TBE, &params->cils_tbe);
+ /* Cell Identifier List Segment, 3.2.2.27e */
+ if (params->cils_rel_present)
+ gsm0808_enc_cell_id_list_segment(msg, GSM0808_IE_CELL_ID_LIST_SEG_REL_CELLS, &params->cils_rel);
+ /* Cell Identifier List Segment, 3.2.2.27f */
+ if (params->cils_ne_present)
+ gsm0808_enc_cell_id_list_segment(msg, GSM0808_IE_CELL_ID_LIST_SEG_NE_CELLS, &params->cils_ne);
+
+ /* VGCS/VBS Cell Status, 3.2.2.94 */
+ if (params->cell_status_present) {
+ val = params->cell_status;
+ msgb_tlv_put(msg, GSM0808_IE_VGCS_VBS_CELL_STATUS, 1, &val);
+ }
+
+ /* prepend header with final length */
+ msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg));
+
+ return msg;
+}
+
+/*! Create BSSMAP VGCS SMS message, 3GPP TS 48.008 3.2.1.81.
+ * Sent from the MSC to the BSC to send an SMS to VGCS. */
+struct msgb *gsm0808_create_vgcs_sms(const struct gsm0808_sms_to_vgcs *sms)
+{
+ struct msgb *msg;
+
+ msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM, "BSSMAP-VGCS-SMS");
+ if (!msg)
+ return NULL;
+
+ /* SMS to VGCS, 3.2.2.92 */
+ msgb_tlv_put(msg, GSM0808_IE_VGCS_VBS_CELL_STATUS, sms->sms_len, sms->sms);
+
+ /* prepend header with final length */
+ msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg));
+
+ return msg;
+}
+
+/*! Create BSSMAP (VGCS/VBS) NOTIFICATION DATA message, 3GPP TS 48.008 3.2.1.82.
+ * Sent from the MSC to the BSC to send application specific data. */
+struct msgb *gsm0808_create_notification_data(const struct gsm0808_notification_data *params)
+{
+ struct msgb *msg;
+
+ msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM, "BSSMAP-VGCS-SMS");
+ if (!msg)
+ return NULL;
+
+ /* Message Type, 3.2.2.1 */
+ msgb_v_put(msg, BSS_MAP_MSG_NOTIFICATION_DATA);
+
+ /* Application Data, 3.2.2.98 */
+ msgb_tlv_put(msg, GSM0808_IE_APP_DATA, params->app_data.data_len, params->app_data.data);
+
+ /* Data Identity, 3.2.2.99 */
+ gsm0808_enc_data_identity(msg, &params->data_ident);
+
+ /* MSISDN, 3.2.2.101 */
+ if (params->msisdn_present)
+ gsm0808_enc_msisdn(msg, params->msisdn);
+
+ /* prepend header with final length */
+ msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg));
+
+ return msg;
+}
+
+/* Note that EMERGENCY RESET INDICATION and EMERGENCY RESET COMMAND cannot be implemented, due to lack of
+ * message type information in the specifications. */
+
/* As per 3GPP TS 48.008 version 11.7.0 Release 11 */
static const struct tlv_definition bss_att_tlvdef = {
.def = {
@@ -1715,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" },
@@ -1724,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 }
@@ -1762,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 }
};
@@ -1968,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 f1569c90..778630d6 100644
--- a/src/gsm/gsm0808_utils.c
+++ b/src/gsm/gsm0808_utils.c
@@ -90,8 +90,6 @@ uint8_t gsm0808_enc_aoip_trasp_addr(struct msgb *msg,
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) {
@@ -141,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)
@@ -189,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)
@@ -213,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:
@@ -244,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;
@@ -260,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);
}
@@ -282,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;
}
@@ -296,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
@@ -329,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)
@@ -341,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++;
@@ -356,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;
@@ -401,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
@@ -444,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;
@@ -481,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;
@@ -498,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);
@@ -523,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)
@@ -533,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);
}
@@ -712,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);
@@ -752,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)
@@ -1012,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;
@@ -1056,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;
@@ -1234,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)
@@ -1302,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)
@@ -1460,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);
@@ -2141,4 +2210,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/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 30bba919..4f790261 100644
--- a/src/gsm/gsm48.c
+++ b/src/gsm/gsm48.c
@@ -42,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
* @{
@@ -129,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 },
},
};
@@ -148,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 },
},
@@ -419,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" },
@@ -470,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;
}
@@ -823,11 +864,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;
@@ -846,10 +889,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);
@@ -859,12 +902,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;
@@ -874,7 +917,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];
@@ -882,7 +925,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;
@@ -890,7 +933,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];
@@ -905,13 +948,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;
}
@@ -924,7 +978,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;
@@ -933,12 +987,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.
@@ -1277,7 +1346,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)
@@ -1361,7 +1487,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);
@@ -1641,6 +1767,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 bbaec3da..bb86cf42 100644
--- a/src/gsm/gsm48_ie.c
+++ b/src/gsm/gsm48_ie.c
@@ -233,6 +233,7 @@ 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)) {
@@ -334,10 +335,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);
@@ -351,7 +353,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;
@@ -369,7 +373,7 @@ 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[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];
@@ -825,7 +829,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);
@@ -880,8 +884,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 3b0ec6a8..bb403392 100644
--- a/src/gsm/gsm_utils.c
+++ b/src/gsm/gsm_utils.c
@@ -80,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>
@@ -92,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: */
@@ -885,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)
@@ -913,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/ipa.c b/src/gsm/ipa.c
index 447e8e3d..cd95c6bb 100644
--- a/src/gsm/ipa.c
+++ b/src/gsm/ipa.c
@@ -478,7 +478,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
index c6a575e4..16a6f5e0 100644
--- a/src/gsm/iuup.c
+++ b/src/gsm/iuup.c
@@ -191,7 +191,7 @@ static inline uint8_t iuup_get_hdr_crc(const uint8_t *data)
#define IUUP_MSGB_HEADROOM_MIN_REQUIRED (OSMO_MAX(sizeof(struct osmo_iuup_tnl_prim), sizeof(struct osmo_iuup_rnl_prim)) + (PTR_ALIGNMENT_BYTES - 1))
static inline struct msgb *osmo_iuup_msgb_alloc_c(void *ctx, size_t size)
{
- osmo_static_assert(size > IUUP_MSGB_HEADROOM_MIN_REQUIRED, iuup_msgb_alloc_headroom_bigger);
+ OSMO_ASSERT(size > IUUP_MSGB_HEADROOM_MIN_REQUIRED);
return msgb_alloc_headroom_c(ctx, size, IUUP_MSGB_HEADROOM_MIN_REQUIRED, "iuup-msgb");
}
diff --git a/src/gsm/kdf.c b/src/gsm/kdf.c
index 6982f6f7..4113aada 100644
--- a/src/gsm/kdf.c
+++ b/src/gsm/kdf.c
@@ -22,7 +22,7 @@
#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 e7d46e63..86dc242c 100644
--- a/src/gsm/lapdm.c
+++ b/src/gsm/lapdm.c
@@ -57,6 +57,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))
@@ -253,7 +254,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,
@@ -361,7 +362,7 @@ static int tx_ph_data_enqueue(struct lapdm_datalink *dl, struct msgb *msg,
*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,
@@ -699,7 +700,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 */
@@ -714,6 +715,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) {
@@ -725,15 +727,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 */
@@ -741,11 +752,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;
+ }
}
}
@@ -838,10 +855,10 @@ static int l2_ph_data_ind(struct msgb *msg, struct lapdm_entity *le,
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);
@@ -895,56 +912,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;
}
@@ -1030,6 +1024,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");
@@ -1055,8 +1050,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);
@@ -1071,11 +1068,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;
diff --git a/src/gsm/libosmogsm.map b/src/gsm/libosmogsm.map
index b971ca01..056d3208 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;
@@ -542,11 +611,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;
@@ -782,5 +854,11 @@ 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;
+
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/rsl.c b/src/gsm/rsl.c
index 3668ceac..fbba982a 100644
--- a/src/gsm/rsl.c
+++ b/src/gsm/rsl.c
@@ -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/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..f8314bde
--- /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
+
+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 a6a0835c..b070bbdc 100644
--- a/src/gsm/i460_mux.c
+++ b/src/isdn/i460_mux.c
@@ -21,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;
@@ -119,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;
@@ -195,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)
{
@@ -243,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;
@@ -262,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 ced8bc05..5674c661 100644
--- a/src/gsm/lapd_core.c
+++ b/src/isdn/lapd_core.c
@@ -69,6 +69,7 @@
//#define TEST_CONTENT_RESOLUTION_NETWORK
#include <stdio.h>
+#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include <errno.h>
@@ -78,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 */
@@ -592,6 +593,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);
@@ -610,13 +613,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);
@@ -634,8 +637,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;
@@ -742,7 +743,8 @@ static void 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 ? */
@@ -767,9 +769,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. */
}
@@ -777,8 +778,7 @@ 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);
}
@@ -1708,7 +1708,7 @@ 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;
}
@@ -1738,6 +1738,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)
{
@@ -1776,11 +1790,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;
@@ -1913,11 +1924,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 {
@@ -2038,11 +2046,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;
@@ -2102,11 +2107,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..3269771c
--- /dev/null
+++ b/src/isdn/libosmoisdn.map
@@ -0,0 +1,34 @@
+LIBOSMOISDN_1.0 {
+global:
+
+tall_lapd_ctx;
+lapd_dl_exit;
+lapd_dl_init;
+lapd_dl_init2;
+lapd_dl_set_name;
+lapd_dl_reset;
+lapd_msgb_alloc;
+lapd_ph_data_ind;
+lapd_recv_dlsap;
+lapd_set_mode;
+lapd_state_names;
+
+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_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;
+
+local: *;
+};
diff --git a/src/isdn/v110.c b/src/isdn/v110.c
new file mode 100644
index 00000000..46962bfa
--- /dev/null
+++ b/src/isdn/v110.c
@@ -0,0 +1,583 @@
+/* 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
+
+/*! 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 */
+ fr->e_bits[0] = 1;
+ fr->e_bits[1] = 0;
+ fr->e_bits[2] = 0;
+ 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 (fr->e_bits[0] != 1 || fr->e_bits[1] != 0 || fr->e_bits[2] != 0)
+ 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 */
+ fr->e_bits[0] = 0;
+ fr->e_bits[1] = 1;
+ fr->e_bits[2] = 0;
+ 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 (fr->e_bits[0] != 0 || fr->e_bits[1] != 1 || fr->e_bits[2] != 0)
+ 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 */
+ fr->e_bits[0] = 1;
+ fr->e_bits[1] = 1;
+ fr->e_bits[2] = 0;
+ 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 (fr->e_bits[0] != 1 || fr->e_bits[1] != 1 || fr->e_bits[2] != 0)
+ 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 */
+ fr->e_bits[0] = 1;
+ fr->e_bits[1] = 0;
+ fr->e_bits[2] = 1;
+
+ 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;
+
+ if (fr->e_bits[0] != 1 || fr->e_bits[1] != 0 || fr->e_bits[2] != 1)
+ 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 */
+ fr->e_bits[0] = 0;
+ fr->e_bits[1] = 1;
+ fr->e_bits[2] = 1;
+
+ 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;
+
+ if (fr->e_bits[0] != 0 || fr->e_bits[1] != 1 || fr->e_bits[2] != 1)
+ 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 */
+ fr->e_bits[0] = 0;
+ fr->e_bits[1] = 0;
+ fr->e_bits[2] = 1;
+
+ 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;
+
+ if (fr->e_bits[0] != 0 || fr->e_bits[1] != 0 || fr->e_bits[2] != 1)
+ 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/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/sim/Makefile.am b/src/sim/Makefile.am
index 3d305067..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=3:1:1
+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)
@@ -20,7 +20,7 @@ libosmosim_la_LDFLAGS = \
-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/class_tables.c b/src/sim/class_tables.c
index 9c513879..724c0777 100644
--- a/src/sim/class_tables.c
+++ b/src/sim/class_tables.c
@@ -222,6 +222,7 @@ static const uint8_t gp_ins_tbl_8ce[256] = {
[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[] = {
diff --git a/src/usb/Makefile.am b/src/usb/Makefile.am
index a1426f9b..c7d7a2a2 100644
--- a/src/usb/Makefile.am
+++ b/src/usb/Makefile.am
@@ -3,7 +3,7 @@
# before making any modifications: https://www.gnu.org/software/libtool/manual/html_node/Versioning.html
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)
@@ -17,7 +17,7 @@ libosmousb_la_LDFLAGS = \
-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/vty/Makefile.am b/src/vty/Makefile.am
index 3142d754..c314a141 100644
--- a/src/vty/Makefile.am
+++ b/src/vty/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=11:0:2
+LIBVERSION=13:0:0
-AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_builddir)/include
+AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_builddir)/include -I$(top_builddir)
AM_CFLAGS = -Wall $(TALLOC_CFLAGS) $(PTHREAD_CFLAGS)
if ENABLE_VTY
@@ -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) $(PTHREAD_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 d7b8026f..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,
@@ -1480,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;
@@ -1504,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;
@@ -1516,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;
@@ -1528,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;
@@ -1540,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;
@@ -1552,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;
@@ -1560,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;
}
@@ -2389,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;
@@ -2599,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);
@@ -2874,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,
};
@@ -2930,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))
diff --git a/src/vty/cpu_sched_vty.c b/src/vty/cpu_sched_vty.c
index dbb3cd59..7198f747 100644
--- a/src/vty/cpu_sched_vty.c
+++ b/src/vty/cpu_sched_vty.c
@@ -25,7 +25,7 @@
#define _GNU_SOURCE
-#include "../../config.h"
+#include "config.h"
#include <string.h>
#include <stdlib.h>
@@ -89,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;
diff --git a/src/vty/fsm_vty.c b/src/vty/fsm_vty.c
index 777d38ce..da6038fa 100644
--- a/src/vty/fsm_vty.c
+++ b/src/vty/fsm_vty.c
@@ -19,7 +19,7 @@
#include <stdlib.h>
#include <string.h>
-#include "../../config.h"
+#include "config.h"
#include <osmocom/vty/command.h>
#include <osmocom/vty/buffer.h>
diff --git a/src/vty/logging_vty.c b/src/vty/logging_vty.c
index c83dafdd..2a074222 100644
--- a/src/vty/logging_vty.c
+++ b/src/vty/logging_vty.c
@@ -20,7 +20,7 @@
#include <stdlib.h>
#include <string.h>
-#include "../../config.h"
+#include "config.h"
#include <osmocom/core/talloc.h>
#include <osmocom/core/logging.h>
@@ -1196,7 +1196,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);
diff --git a/src/vty/stats_vty.c b/src/vty/stats_vty.c
index e5acfa2c..f940018f 100644
--- a/src/vty/stats_vty.c
+++ b/src/vty/stats_vty.c
@@ -21,7 +21,7 @@
#include <stdlib.h>
#include <string.h>
-#include "../../config.h"
+#include "config.h"
#include <osmocom/vty/command.h>
#include <osmocom/vty/buffer.h>
@@ -742,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);
diff --git a/src/vty/telnet_interface.c b/src/vty/telnet_interface.c
index 374578cb..8fa5dbff 100644
--- a/src/vty/telnet_interface.c
+++ b/src/vty/telnet_interface.c
@@ -42,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.
*/
@@ -60,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");
@@ -94,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/tests/Makefile.am b/tests/Makefile.am
index 8c7e40a0..5f4914e2 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -1,7 +1,7 @@
-AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_builddir)/include
+AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_builddir)/include -I$(top_builddir)
AM_CFLAGS = -Wall $(TALLOC_CFLAGS) $(PTHREAD_CFLAGS)
AM_LDFLAGS = -no-install
-LDADD = $(top_builddir)/src/libosmocore.la $(TALLOC_LIBS) $(PTHREAD_LIBS)
+LDADD = $(top_builddir)/src/core/libosmocore.la $(TALLOC_LIBS) $(PTHREAD_LIBS)
if ENABLE_SERCOM_STUB
noinst_LIBRARIES = libsercomstub.a
@@ -10,7 +10,8 @@ endif
check_PROGRAMS = timer/timer_test sms/sms_test ussd/ussd_test \
bits/bitrev_test a5/a5_test \
- conv/conv_test auth/milenage_test lapd/lapd_test \
+ conv/conv_test auth/milenage_test auth/tuak_test \
+ lapd/lapd_test \
gsm0808/gsm0808_test gsm0408/gsm0408_test \
gprs/gprs_test kasumi/kasumi_test gea/gea_test \
logging/logging_test codec/codec_test \
@@ -49,6 +50,11 @@ check_PROGRAMS = timer/timer_test sms/sms_test ussd/ussd_test \
smscb/smscb_test \
smscb/gsm0341_test \
smscb/cbsp_test \
+ auth/xor2g_test \
+ v110/frame_test \
+ v110/ra1_test \
+ gsm44021/frame_csd_test \
+ osmo_io/osmo_io_test \
$(NULL)
if ENABLE_MSGFILE
@@ -89,38 +95,46 @@ endif
base64_base64_test_SOURCES = base64/base64_test.c
utils_utils_test_SOURCES = utils/utils_test.c
-utils_utils_test_LDADD = $(LDADD) $(top_builddir)/src/gsm/libosmogsm.la
+utils_utils_test_LDADD = $(top_builddir)/src/gsm/libosmogsm.la $(LDADD)
stats_stats_test_SOURCES = stats/stats_test.c
-stats_stats_test_LDADD = $(LDADD) $(top_builddir)/src/gsm/libosmogsm.la
-stats_stats_test_CPPFLAGS = $(AM_CPPFLAGS) -I$(top_srcdir)/src
+stats_stats_test_LDADD = $(top_builddir)/src/gsm/libosmogsm.la $(LDADD)
+stats_stats_test_CPPFLAGS = $(AM_CPPFLAGS) -I$(top_srcdir)/src/core
stats_stats_vty_test_SOURCES = stats/stats_vty_test.c
-stats_stats_vty_test_LDADD = $(LDADD) $(top_builddir)/src/vty/libosmovty.la
+stats_stats_vty_test_LDADD = $(top_builddir)/src/vty/libosmovty.la $(LDADD)
a5_a5_test_SOURCES = a5/a5_test.c
-a5_a5_test_LDADD = $(LDADD) $(top_builddir)/src/gsm/libgsmint.la
+a5_a5_test_LDADD = $(top_builddir)/src/gsm/libgsmint.la $(LDADD)
kasumi_kasumi_test_SOURCES = kasumi/kasumi_test.c
-kasumi_kasumi_test_LDADD = $(LDADD) $(top_builddir)/src/gsm/libgsmint.la
+kasumi_kasumi_test_LDADD = $(top_builddir)/src/gsm/libgsmint.la $(LDADD)
comp128_comp128_test_SOURCES = comp128/comp128_test.c
-comp128_comp128_test_LDADD = $(LDADD) $(top_builddir)/src/gsm/libosmogsm.la
+comp128_comp128_test_LDADD = $(top_builddir)/src/gsm/libosmogsm.la $(LDADD)
auth_milenage_test_SOURCES = auth/milenage_test.c
-auth_milenage_test_LDADD = $(LDADD) $(top_builddir)/src/gsm/libosmogsm.la
+auth_milenage_test_LDADD = $(top_builddir)/src/gsm/libosmogsm.la $(LDADD)
+
+auth_tuak_test_SOURCES = auth/tuak_test.c
+auth_tuak_test_LDADD = $(top_builddir)/src/gsm/libgsmint.la $(LDADD)
+auth_tuak_test_CPPFLAGS = $(AM_CPPFLAGS) -I$(top_srcdir)/src
+
+auth_xor2g_test_SOURCES = auth/xor2g_test.c
+auth_xor2g_test_LDADD = $(top_builddir)/src/gsm/libosmogsm.la $(LDADD)
abis_abis_test_SOURCES = abis/abis_test.c
-abis_abis_test_LDADD = $(LDADD) $(top_builddir)/src/gsm/libosmogsm.la
+abis_abis_test_LDADD = $(top_builddir)/src/gsm/libosmogsm.la $(LDADD)
ctrl_ctrl_test_SOURCES = ctrl/ctrl_test.c
-ctrl_ctrl_test_LDADD = $(LDADD) \
+ctrl_ctrl_test_LDADD = \
$(top_builddir)/src/ctrl/libosmoctrl.la \
$(top_builddir)/src/gsm/libosmogsm.la \
- $(top_builddir)/src/vty/libosmovty.la
+ $(top_builddir)/src/vty/libosmovty.la \
+ $(LDADD)
gea_gea_test_SOURCES = gea/gea_test.c
-gea_gea_test_LDADD = $(LDADD) $(top_builddir)/src/gsm/libosmogsm.la
+gea_gea_test_LDADD = $(top_builddir)/src/gsm/libosmogsm.la $(LDADD)
bits_bitrev_test_SOURCES = bits/bitrev_test.c
@@ -131,108 +145,116 @@ bits_bitcomp_test_SOURCES = bits/bitcomp_test.c
bits_bitfield_test_SOURCES = bits/bitfield_test.c
conv_conv_test_SOURCES = conv/conv_test.c conv/conv.c
-conv_conv_test_LDADD = $(LDADD) $(top_builddir)/src/gsm/libgsmint.la
+conv_conv_test_LDADD = $(top_builddir)/src/gsm/libgsmint.la $(LDADD)
gsm0502_gsm0502_test_SOURCES = gsm0502/gsm0502_test.c
-gsm0502_gsm0502_test_LDADD = $(LDADD) $(top_builddir)/src/gsm/libosmogsm.la
+gsm0502_gsm0502_test_LDADD = $(top_builddir)/src/gsm/libosmogsm.la $(LDADD)
dtx_dtx_gsm0503_test_SOURCES = dtx/dtx_gsm0503_test.c
-dtx_dtx_gsm0503_test_LDADD = $(LDADD) $(top_builddir)/src/gsm/libosmogsm.la \
- $(top_builddir)/src/coding/libosmocoding.la
+dtx_dtx_gsm0503_test_LDADD = $(top_builddir)/src/gsm/libosmogsm.la \
+ $(top_builddir)/src/coding/libosmocoding.la \
+ $(LDADD)
conv_conv_gsm0503_test_SOURCES = conv/conv_gsm0503_test.c conv/conv.c conv/gsm0503_test_vectors.c
-conv_conv_gsm0503_test_LDADD = $(LDADD) $(top_builddir)/src/gsm/libgsmint.la
+conv_conv_gsm0503_test_LDADD = $(top_builddir)/src/gsm/libgsmint.la $(LDADD)
conv_conv_gsm0503_test_CPPFLAGS = $(AM_CPPFLAGS) -I$(top_srcdir)/tests/conv
gsm0808_gsm0808_test_SOURCES = gsm0808/gsm0808_test.c
-gsm0808_gsm0808_test_LDADD = $(LDADD) $(top_builddir)/src/gsm/libosmogsm.la
+gsm0808_gsm0808_test_LDADD = $(top_builddir)/src/gsm/libosmogsm.la $(LDADD)
gsm29205_gsm29205_test_SOURCES = gsm29205/gsm29205_test.c
-gsm29205_gsm29205_test_LDADD = $(LDADD) $(top_builddir)/src/gsm/libosmogsm.la
+gsm29205_gsm29205_test_LDADD = $(top_builddir)/src/gsm/libosmogsm.la $(LDADD)
gsm0408_gsm0408_test_SOURCES = gsm0408/gsm0408_test.c
-gsm0408_gsm0408_test_LDADD = $(LDADD) $(top_builddir)/src/gsm/libosmogsm.la
+gsm0408_gsm0408_test_LDADD = $(top_builddir)/src/gsm/libosmogsm.la $(LDADD)
gsm48_rest_octets_test_SOURCES = gsm48/rest_octets_test.c
-gsm48_rest_octets_test_LDADD = $(LDADD) $(top_builddir)/src/gsm/libosmogsm.la
+gsm48_rest_octets_test_LDADD = $(top_builddir)/src/gsm/libosmogsm.la $(LDADD)
gprs_gprs_test_SOURCES = gprs/gprs_test.c
-gprs_gprs_test_LDADD = $(LDADD) $(top_builddir)/src/gsm/libosmogsm.la
+gprs_gprs_test_LDADD = $(top_builddir)/src/gsm/libosmogsm.la $(LDADD)
lapd_lapd_test_SOURCES = lapd/lapd_test.c
-lapd_lapd_test_LDADD = $(LDADD) $(top_builddir)/src/gsm/libosmogsm.la
+lapd_lapd_test_LDADD = $(top_builddir)/src/gsm/libosmogsm.la \
+ $(top_builddir)/src/isdn/libosmoisdn.la \
+ $(LDADD)
msgb_msgb_test_SOURCES = msgb/msgb_test.c
msgfile_msgfile_test_SOURCES = msgfile/msgfile_test.c
smscb_smscb_test_SOURCES = smscb/smscb_test.c
-smscb_smscb_test_LDADD = $(LDADD) $(top_builddir)/src/gsm/libosmogsm.la
+smscb_smscb_test_LDADD = $(top_builddir)/src/gsm/libosmogsm.la $(LDADD)
smscb_gsm0341_test_SOURCES = smscb/gsm0341_test.c
-smscb_gsm0341_test_LDADD = $(LDADD) $(top_builddir)/src/gsm/libosmogsm.la
+smscb_gsm0341_test_LDADD = $(top_builddir)/src/gsm/libosmogsm.la $(LDADD)
smscb_cbsp_test_SOURCES = smscb/cbsp_test.c
-smscb_cbsp_test_LDADD = $(LDADD) $(top_builddir)/src/gsm/libosmogsm.la
+smscb_cbsp_test_LDADD = $(top_builddir)/src/gsm/libosmogsm.la $(LDADD)
sms_sms_test_SOURCES = sms/sms_test.c
-sms_sms_test_LDADD = $(LDADD) $(top_builddir)/src/gsm/libosmogsm.la
+sms_sms_test_LDADD = $(top_builddir)/src/gsm/libosmogsm.la $(LDADD)
timer_timer_test_SOURCES = timer/timer_test.c
timer_clk_override_test_SOURCES = timer/clk_override_test.c
ussd_ussd_test_SOURCES = ussd/ussd_test.c
-ussd_ussd_test_LDADD = $(LDADD) $(top_builddir)/src/gsm/libosmogsm.la
+ussd_ussd_test_LDADD = $(top_builddir)/src/gsm/libosmogsm.la $(LDADD)
gb_bssgp_fc_test_SOURCES = gb/bssgp_fc_test.c
-gb_bssgp_fc_test_LDADD = $(LDADD) $(top_builddir)/src/gb/libosmogb.la \
+gb_bssgp_fc_test_LDADD = $(top_builddir)/src/gb/libosmogb.la \
$(top_builddir)/src/vty/libosmovty.la \
- $(top_builddir)/src/gsm/libosmogsm.la
+ $(top_builddir)/src/gsm/libosmogsm.la \
+ $(LDADD)
gb_gprs_bssgp_test_SOURCES = gb/gprs_bssgp_test.c
-gb_gprs_bssgp_test_LDADD = $(LDADD) $(top_builddir)/src/gb/libosmogb.la $(LIBRARY_DLSYM) \
- $(top_builddir)/src/vty/libosmovty.la \
+gb_gprs_bssgp_test_LDADD = $(top_builddir)/src/vty/libosmovty.la \
$(top_builddir)/src/gsm/libosmogsm.la \
- $(top_builddir)/src/gb/libosmogb.la
+ $(top_builddir)/src/gb/libosmogb.la \
+ $(LDADD) \
+ $(LIBRARY_DLSYM)
gb_gprs_bssgp_rim_test_SOURCES = gb/gprs_bssgp_rim_test.c
-gb_gprs_bssgp_rim_test_LDADD = $(LDADD) $(top_builddir)/src/gb/libosmogb.la $(LIBRARY_DLSYM) \
- $(top_builddir)/src/gb/libosmogb.la
+gb_gprs_bssgp_rim_test_LDADD = $(top_builddir)/src/gb/libosmogb.la \
+ $(LDADD) \
+ $(LIBRARY_DLSYM)
gb_gprs_ns_test_SOURCES = gb/gprs_ns_test.c
-gb_gprs_ns_test_LDADD = $(LDADD) $(top_builddir)/src/gb/libosmogb.la $(LIBRARY_DLSYM) \
+gb_gprs_ns_test_LDADD = $(top_builddir)/src/gb/libosmogb.la \
$(top_builddir)/src/vty/libosmovty.la \
- $(top_builddir)/src/gsm/libosmogsm.la
+ $(top_builddir)/src/gsm/libosmogsm.la \
+ $(LDADD) \
+ $(LIBRARY_DLSYM)
gb_gprs_ns2_test_SOURCES = gb/gprs_ns2_test.c
-gb_gprs_ns2_test_LDADD = $(LDADD) $(LIBRARY_DLSYM) \
- $(top_builddir)/src/vty/libosmovty.la \
+gb_gprs_ns2_test_LDADD = $(top_builddir)/src/vty/libosmovty.la \
$(top_builddir)/src/gsm/libosmogsm.la \
- $(top_builddir)/src/libosmocore.la \
- $(top_builddir)/src/gb/libosmogb-test.la
-if ENABLE_LIBMNL
-gb_gprs_ns2_test_LDADD += $(LIBMNL_LIBS)
-endif
+ $(top_builddir)/src/core/libosmocore.la \
+ $(top_builddir)/src/gb/libosmogb-test.la \
+ $(LDADD) \
+ $(LIBRARY_DLSYM)
logging_logging_test_SOURCES = logging/logging_test.c
logging_logging_vty_test_SOURCES = logging/logging_vty_test.c
-logging_logging_vty_test_LDADD = $(LDADD) $(top_builddir)/src/vty/libosmovty.la
+logging_logging_vty_test_LDADD = $(top_builddir)/src/vty/libosmovty.la $(LDADD)
vty_vty_transcript_test_SOURCES = vty/vty_transcript_test.c
-vty_vty_transcript_test_LDADD = $(LDADD) $(top_builddir)/src/vty/libosmovty.la
+vty_vty_transcript_test_LDADD = $(top_builddir)/src/vty/libosmovty.la $(LDADD)
fr_fr_test_SOURCES = fr/fr_test.c
-fr_fr_test_LDADD = $(LDADD) $(top_builddir)/src/gb/libosmogb.la $(LIBRARY_DLSYM) \
+fr_fr_test_LDADD = $(top_builddir)/src/gb/libosmogb.la \
$(top_builddir)/src/vty/libosmovty.la \
- $(top_builddir)/src/gsm/libosmogsm.la
+ $(top_builddir)/src/gsm/libosmogsm.la \
+ $(LDADD) \
+ $(LIBRARY_DLSYM)
codec_codec_test_SOURCES = codec/codec_test.c
-codec_codec_test_LDADD = $(LDADD) $(top_builddir)/src/codec/libosmocodec.la
+codec_codec_test_LDADD = $(top_builddir)/src/codec/libosmocodec.la $(LDADD)
codec_codec_ecu_fr_test_SOURCES = codec/codec_ecu_fr_test.c
-codec_codec_ecu_fr_test_LDADD = $(LDADD) $(top_builddir)/src/codec/libosmocodec.la
+codec_codec_ecu_fr_test_LDADD = $(top_builddir)/src/codec/libosmocodec.la $(LDADD)
loggingrb_loggingrb_test_SOURCES = loggingrb/loggingrb_test.c
loggingrb_loggingrb_test_LDADD = $(LDADD)
@@ -240,30 +262,31 @@ loggingrb_loggingrb_test_LDADD = $(LDADD)
strrb_strrb_test_SOURCES = strrb/strrb_test.c
vty_vty_test_SOURCES = vty/vty_test.c
-vty_vty_test_LDADD = $(LDADD) $(top_builddir)/src/vty/libosmovty.la
+vty_vty_test_LDADD = $(top_builddir)/src/vty/libosmovty.la $(LDADD)
sim_sim_test_SOURCES = sim/sim_test.c
-sim_sim_test_LDADD = $(LDADD) $(top_builddir)/src/sim/libosmosim.la \
- $(top_builddir)/src/gsm/libosmogsm.la
+sim_sim_test_LDADD = $(top_builddir)/src/sim/libosmosim.la \
+ $(top_builddir)/src/gsm/libosmogsm.la \
+ $(LDADD)
tlv_tlv_test_SOURCES = tlv/tlv_test.c
-tlv_tlv_test_LDADD = $(LDADD) $(top_builddir)/src/gsm/libosmogsm.la
+tlv_tlv_test_LDADD = $(top_builddir)/src/gsm/libosmogsm.la $(LDADD)
gsup_gsup_test_SOURCES = gsup/gsup_test.c
-gsup_gsup_test_LDADD = $(LDADD) $(top_builddir)/src/gsm/libosmogsm.la
+gsup_gsup_test_LDADD = $(top_builddir)/src/gsm/libosmogsm.la $(LDADD)
oap_oap_test_SOURCES = oap/oap_test.c
-oap_oap_test_LDADD = $(LDADD) $(top_builddir)/src/gsm/libosmogsm.la
+oap_oap_test_LDADD = $(top_builddir)/src/gsm/libosmogsm.la $(LDADD)
oap_oap_client_test_SOURCES = oap/oap_client_test.c
-oap_oap_client_test_LDADD = $(LDADD) $(top_builddir)/src/gsm/libosmogsm.la
+oap_oap_client_test_LDADD = $(top_builddir)/src/gsm/libosmogsm.la $(LDADD)
fsm_fsm_test_SOURCES = fsm/fsm_test.c
fsm_fsm_test_LDADD = \
- $(LDADD) \
$(top_builddir)/src/ctrl/libosmoctrl.la \
$(top_builddir)/src/gsm/libosmogsm.la \
- $(top_builddir)/src/vty/libosmovty.la
+ $(top_builddir)/src/vty/libosmovty.la \
+ $(LDADD)
fsm_fsm_dealloc_test_SOURCES = fsm/fsm_dealloc_test.c
fsm_fsm_dealloc_test_LDADD = $(LDADD)
@@ -273,10 +296,11 @@ write_queue_wqueue_test_SOURCES = write_queue/wqueue_test.c
socket_socket_test_SOURCES = socket/socket_test.c
coding_coding_test_SOURCES = coding/coding_test.c
-coding_coding_test_LDADD = $(LDADD) \
+coding_coding_test_LDADD = \
$(top_builddir)/src/gsm/libosmogsm.la \
$(top_builddir)/src/codec/libosmocodec.la \
- $(top_builddir)/src/coding/libosmocoding.la
+ $(top_builddir)/src/coding/libosmocoding.la \
+ $(LDADD)
endian_endian_test_SOURCES = endian/endian_test.c
@@ -285,22 +309,22 @@ sercomm_sercomm_test_SOURCES = sercomm/sercomm_test.c
prbs_prbs_test_SOURCES = prbs/prbs_test.c
gsm23003_gsm23003_test_SOURCES = gsm23003/gsm23003_test.c
-gsm23003_gsm23003_test_LDADD = $(LDADD) $(top_builddir)/src/gsm/libosmogsm.la
+gsm23003_gsm23003_test_LDADD = $(top_builddir)/src/gsm/libosmogsm.la $(LDADD)
gsm23236_gsm23236_test_SOURCES = gsm23236/gsm23236_test.c
-gsm23236_gsm23236_test_LDADD = $(LDADD) $(top_builddir)/src/gsm/libosmogsm.la
+gsm23236_gsm23236_test_LDADD = $(top_builddir)/src/gsm/libosmogsm.la $(LDADD)
tdef_tdef_test_SOURCES = tdef/tdef_test.c
tdef_tdef_test_LDADD = $(LDADD)
tdef_tdef_vty_config_root_test_SOURCES = tdef/tdef_vty_config_root_test.c
-tdef_tdef_vty_config_root_test_LDADD = $(LDADD) $(top_builddir)/src/vty/libosmovty.la
+tdef_tdef_vty_config_root_test_LDADD = $(top_builddir)/src/vty/libosmovty.la $(LDADD)
tdef_tdef_vty_config_subnode_test_SOURCES = tdef/tdef_vty_config_subnode_test.c
-tdef_tdef_vty_config_subnode_test_LDADD = $(LDADD) $(top_builddir)/src/vty/libosmovty.la
+tdef_tdef_vty_config_subnode_test_LDADD = $(top_builddir)/src/vty/libosmovty.la $(LDADD)
tdef_tdef_vty_dynamic_test_SOURCES = tdef/tdef_vty_dynamic_test.c
-tdef_tdef_vty_dynamic_test_LDADD = $(LDADD) $(top_builddir)/src/vty/libosmovty.la
+tdef_tdef_vty_dynamic_test_LDADD = $(top_builddir)/src/vty/libosmovty.la $(LDADD)
sockaddr_str_sockaddr_str_test_SOURCES = sockaddr_str/sockaddr_str_test.c
sockaddr_str_sockaddr_str_test_LDADD = $(LDADD)
@@ -315,19 +339,19 @@ exec_exec_test_SOURCES = exec/exec_test.c
exec_exec_test_LDADD = $(LDADD)
i460_mux_i460_mux_test_SOURCES = i460_mux/i460_mux_test.c
-i460_mux_i460_mux_test_LDADD = $(LDADD) $(top_builddir)/src/gsm/libosmogsm.la
+i460_mux_i460_mux_test_LDADD = $(top_builddir)/src/isdn/libosmoisdn.la $(LDADD)
bitgen_bitgen_test_SOURCES = bitgen/bitgen_test.c
bitgen_bitgen_test_LDADD = $(LDADD)
gad_gad_test_SOURCES = gad/gad_test.c
-gad_gad_test_LDADD = $(LDADD) $(top_builddir)/src/gsm/libosmogsm.la
+gad_gad_test_LDADD = $(top_builddir)/src/gsm/libosmogsm.la $(LDADD)
bsslap_bsslap_test_SOURCES = bsslap/bsslap_test.c
-bsslap_bsslap_test_LDADD = $(LDADD) $(top_builddir)/src/gsm/libosmogsm.la
+bsslap_bsslap_test_LDADD = $(top_builddir)/src/gsm/libosmogsm.la $(LDADD)
bssmap_le_bssmap_le_test_SOURCES = bssmap_le/bssmap_le_test.c
-bssmap_le_bssmap_le_test_LDADD = $(LDADD) $(top_builddir)/src/gsm/libosmogsm.la
+bssmap_le_bssmap_le_test_LDADD = $(top_builddir)/src/gsm/libosmogsm.la $(LDADD)
it_q_it_q_test_SOURCES = it_q/it_q_test.c
it_q_it_q_test_LDADD = $(LDADD)
@@ -336,7 +360,21 @@ time_cc_time_cc_test_SOURCES = time_cc/time_cc_test.c
time_cc_time_cc_test_LDADD = $(LDADD)
iuup_iuup_test_SOURCES = iuup/iuup_test.c
-iuup_iuup_test_LDADD = $(LDADD) $(top_builddir)/src/gsm/libosmogsm.la
+iuup_iuup_test_LDADD = $(top_builddir)/src/gsm/libosmogsm.la $(LDADD)
+
+v110_frame_test_SOURCES = v110/frame_test.c
+v110_frame_test_LDADD = $(top_builddir)/src/isdn/libosmoisdn.la $(LDADD)
+
+v110_ra1_test_SOURCES = v110/ra1_test.c
+v110_ra1_test_LDADD = $(top_builddir)/src/isdn/libosmoisdn.la $(LDADD)
+
+gsm44021_frame_csd_test_SOURCES = gsm44021/frame_csd_test.c
+gsm44021_frame_csd_test_LDADD = $(top_builddir)/src/isdn/libosmoisdn.la \
+ $(top_builddir)/src/gsm/libosmogsm.la \
+ $(LDADD)
+
+osmo_io_osmo_io_test_SOURCES = osmo_io/osmo_io_test.c
+
# The `:;' works around a Bash 3.2 bug when the output is not writeable.
$(srcdir)/package.m4: $(top_srcdir)/configure.ac
@@ -360,6 +398,8 @@ EXTRA_DIST = testsuite.at $(srcdir)/package.m4 $(TESTSUITE) \
timer/timer_test.ok sms/sms_test.ok ussd/ussd_test.ok \
bits/bitrev_test.ok a5/a5_test.ok \
conv/conv_test.ok auth/milenage_test.ok ctrl/ctrl_test.ok \
+ auth/tuak_test.ok \
+ auth/xor2g_test.ok \
lapd/lapd_test.ok \
gsm0408/gsm0408_test.ok gsm0408/gsm0408_test.err \
gsm0808/gsm0808_test.ok gb/bssgp_fc_tests.err \
@@ -435,6 +475,10 @@ EXTRA_DIST = testsuite.at $(srcdir)/package.m4 $(TESTSUITE) \
smscb/smscb_test.ok \
smscb/gsm0341_test.ok \
smscb/cbsp_test.ok \
+ v110/frame_test.ok \
+ v110/ra1_test.ok \
+ gsm44021/frame_csd_test.ok \
+ osmo_io/osmo_io_test.ok osmo_io/osmo_io_test.err \
$(NULL)
if ENABLE_LIBSCTP
@@ -497,6 +541,8 @@ endif
>$(srcdir)/ussd/ussd_test.ok
auth/milenage_test \
>$(srcdir)/auth/milenage_test.ok
+ auth/tuak_test \
+ >$(srcdir)/auth/tuak_test.ok
comp128/comp128_test \
>$(srcdir)/comp128/comp128_test.ok
lapd/lapd_test \
@@ -638,6 +684,16 @@ endif
>$(srcdir)/time_cc/time_cc_test.ok
iuup/iuup_test \
>$(srcdir)/iuup/iuup_test.ok
+ v110/frame_test \
+ >$(srcdir)/v110/frame_test.ok
+ v110/ra1_test \
+ >$(srcdir)/v110/ra1_test.ok
+ gsm44021/frame_csd_test \
+ >$(srcdir)/gsm44021/frame_csd_test.ok
+ osmo_io/osmo_io_test \
+ >$(srcdir)/osmo_io/osmo_io_test.ok \
+ 2>$(srcdir)/osmo_io/osmo_io_test.err
+
check-local: atconfig $(TESTSUITE)
[ -e /proc/cpuinfo ] && cat /proc/cpuinfo
@@ -676,7 +732,8 @@ endif
# pass -u to osmo_verify_transcript_vty.py by doing:
# make vty-test U=-u
-vty-test-ns2:
+if ENABLE_GB
+vty-test-ns2: $(top_builddir)/utils/osmo-ns-dummy
$(MAKE) -C $(top_builddir)/utils osmo-ns-dummy
osmo_verify_transcript_vty.py -v \
-p 42042 \
@@ -684,20 +741,26 @@ vty-test-ns2:
$(U) $(srcdir)/gb/gprs_ns2*.vty
osmotestvty.py -p $(abs_top_srcdir)/tests/gb -w $(abs_top_builddir)/tests/gb -v
osmotestconfig.py -p $(abs_top_srcdir)/tests/gb -w $(abs_top_builddir)/tests/gb -v
+else
+vty-test-ns2:
+ echo "Not running vty-test-ns2 because osmo-ns-dummy is not built (--disable-gb)"
+endif
-vty-test-logging:
+vty-test-logging: $(top_builddir)/tests/logging/logging_vty_test
osmo_verify_transcript_vty.py -v \
-p 42042 \
-r "$(top_builddir)/tests/logging/logging_vty_test" \
$(U) $(srcdir)/logging/*.vty
-vty-test-vty:
+vty-test-vty: $(top_builddir)/tests/vty/vty_transcript_test
osmo_verify_transcript_vty.py -v \
-p 42042 \
-r "$(top_builddir)/tests/vty/vty_transcript_test" \
$(U) $(srcdir)/vty/*.vty
-vty-test-tdef:
+vty-test-tdef: $(top_builddir)/tests/tdef/tdef_vty_config_root_test \
+ $(top_builddir)/tests/tdef/tdef_vty_config_subnode_test \
+ $(top_builddir)/tests/tdef/tdef_vty_dynamic_test
osmo_verify_transcript_vty.py -v \
-p 42042 \
-r "$(top_builddir)/tests/tdef/tdef_vty_config_root_test" \
@@ -711,7 +774,7 @@ vty-test-tdef:
-r "$(top_builddir)/tests/tdef/tdef_vty_dynamic_test" \
$(U) $(srcdir)/tdef/tdef_vty_dynamic_test.vty
-vty-test-stats:
+vty-test-stats: $(top_builddir)/tests/stats/stats_vty_test
osmo_verify_transcript_vty.py -v \
-p 42042 \
-r "$(top_builddir)/tests/stats/stats_vty_test" \
diff --git a/tests/abis/abis_test.c b/tests/abis/abis_test.c
index 8ba5f172..ac470fc7 100644
--- a/tests/abis/abis_test.c
+++ b/tests/abis/abis_test.c
@@ -1,6 +1,6 @@
/*
* (C) 2012 by Holger Hans Peter Freyther <zecke@selfish.org>
- * (C) 2017 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de>
+ * (C) 2017 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
@@ -167,7 +167,7 @@ static inline void chk_descr(struct msgb *msg, const char *f_id, const char *f_v
}
}
-static void test_sw_descr()
+static void test_sw_descr(void)
{
const char *f_id = "TEST.L0L", *f_ver = "0.1.666~deadbeeffacefeed-dirty";
uint8_t chain[] = { 0x42, 0x12, 0x00, 0x03, 0x01, 0x02, 0x03, 0x13, 0x00, 0x03, 0x03, 0x04, 0x05, 0x42, 0x12,
@@ -194,7 +194,7 @@ static void test_sw_descr()
}
/* Test decode IPAC_DLCX_IND obtained from SYS#5915 */
-static void test_dec_ipac_dlc_indx()
+static void test_dec_ipac_dlc_indx(void)
{
/* Radio Signalling Link (RSL)
0111 111. = Message discriminator: ip.access Vendor Specific messages (63)
diff --git a/tests/auth/tuak_test.c b/tests/auth/tuak_test.c
new file mode 100644
index 00000000..a00ab2ce
--- /dev/null
+++ b/tests/auth/tuak_test.c
@@ -0,0 +1,309 @@
+
+#include <stdint.h>
+#include <osmocom/core/utils.h>
+#include "gsm/tuak/tuak.h"
+
+/* user-friendly test specification, uses hex-strings for all parameters for
+ * copy+pasting from the spec. */
+struct tuak_testspec {
+ const char *name;
+ struct {
+ const char *k;
+ const char *rand;
+ const char *sqn;
+ const char *amf;
+ const char *top;
+ unsigned int keccak_iterations;
+ } in;
+ struct {
+ const char *topc;
+ const char *f1;
+ const char *f1star;
+ const char *f2;
+ const char *f3;
+ const char *f4;
+ const char *f5;
+ const char *f5star;
+ } out;
+};
+
+static const struct tuak_testspec testspecs[] = {
+ {
+ .name = "TS 35.233 Section 6.3 Test Set 1",
+ .in = {
+ .k = "abababababababababababababababab",
+ .rand = "42424242424242424242424242424242",
+ .sqn = "111111111111",
+ .amf = "ffff",
+ .top = "5555555555555555555555555555555555555555555555555555555555555555",
+ .keccak_iterations = 1,
+ },
+ .out = {
+ .topc = "bd04d9530e87513c5d837ac2ad954623a8e2330c115305a73eb45d1f40cccbff",
+ .f1 = "f9a54e6aeaa8618d",
+ .f1star = "e94b4dc6c7297df3",
+ .f2 = "657acd64",
+ .f3 = "d71a1e5c6caffe986a26f783e5c78be1",
+ .f4 = "be849fa2564f869aecee6f62d4337e72",
+ .f5 = "719f1e9b9054",
+ .f5star = "e7af6b3d0e38",
+ },
+ }, {
+ .name = "TS 35.233 Section 6.4 Test Set 2",
+ .in = {
+ .k = "fffefdfcfbfaf9f8f7f6f5f4f3f2f1f0efeeedecebeae9e8e7e6e5e4e3e2e1e0",
+ .rand = "0123456789abcdef0123456789abcdef",
+ .sqn = "0123456789ab",
+ .amf = "abcd",
+ .top = "808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f",
+ .keccak_iterations = 1,
+ },
+ .out = {
+ .topc = "305425427e18c503c8a4b294ea72c95d0c36c6c6b29d0c65de5974d5977f8524",
+ .f1 = "c0b8c2d4148ec7aa5f1d78a97e4d1d58",
+ .f1star = "ef81af7290f7842c6ceafa537fa0745b",
+ .f2 = "e9d749dc4eea0035",
+ .f3 = "a4cb6f6529ab17f8337f27baa8234d47",
+ .f4 = "2274155ccf4199d5e2abcbf621907f90",
+ .f5 = "480a9345cc1e",
+ .f5star = "f84eb338848c",
+ },
+ }, {
+ .name = "TS 35.233 Section 6.5 Test Set 3",
+ .in = {
+ .k = "fffefdfcfbfaf9f8f7f6f5f4f3f2f1f0efeeedecebeae9e8e7e6e5e4e3e2e1e0",
+ .rand = "0123456789abcdef0123456789abcdef",
+ .sqn = "0123456789ab",
+ .amf = "abcd",
+ .top = "808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f",
+ .keccak_iterations = 1,
+ },
+ .out = {
+ .topc = "305425427e18c503c8a4b294ea72c95d0c36c6c6b29d0c65de5974d5977f8524",
+ .f1 = "d97b75a1776065271b1e212bc3b1bf173f438b21e6c64a55a96c372e085e5cc5",
+ .f1star = "427bbf07c6e3a86c54f8c5216499f3909a6fd4a164c9fe235b1550258111b821",
+ .f2 = "07021c73e7635c7d",
+ .f3 = "4d59ac796834eb85d11fa148a5058c3c",
+ .f4 = "126d47500136fdc5ddfd14f19ebf16749ce4b6435323fbb5715a3a796a6082bd",
+ .f5 = "1d6622c4e59a",
+ .f5star = "f84eb338848c",
+ },
+ }, {
+ .name = "TS 35.233 Section 6.6 Test Set 4",
+ .in = {
+ .k = "b8da837a50652d6ac7c97da14f6acc61",
+ .rand = "6887e55425a966bd86c9661a5fa72be8",
+ .sqn = "0dea2ee2c5af",
+ .amf = "df1e",
+ .top = "0952be13556c32ebc58195d9dd930493e12a9003669988ffde5fa1f0fe35cc01",
+ .keccak_iterations = 1,
+ },
+ .out = {
+ .topc = "2bc16eb657a68e1f446f08f57c0efb1d493527a2e652ce281eb6ca0e4487760a",
+ .f1 = "749214087958dd8f58bfcdf869d8ae3f",
+ .f1star = "619e865afe80e382aee13063f9dfb56d",
+ .f2 = "4041ce438e3e38e8aa96562eed83ac43",
+ .f3 = "3e3bc01bea0cd914c4c2c83ce2d92757",
+ .f4 = "666a8e6f577b1aa77b7fd53cebb8a3d6",
+ .f5 = "1f880d005119",
+ .f5star = "45e617d77fe5",
+ },
+ }, {
+ .name = "TS 35.233 Section 6.7 Test Set 5",
+ .in = {
+ .k = "1574ca56881d05c189c82880f789c9cd4244955f4426aa2b69c29f15770e5aa5",
+ .rand = "c570aac68cde651fb1e3088322498bef",
+ .sqn = "c89bb71f3a41",
+ .amf = "297d",
+ .top = "e59f6eb10ea406813f4991b0b9e02f181edf4c7e17b480f66d34da35ee88c95e",
+ .keccak_iterations = 1,
+ },
+ .out = {
+ .topc = "3c6052e41532a28a47aa3cbb89f223e8f3aaa976aecd48bc3e7d6165a55eff62",
+ .f1 = "d7340dad02b4cb01",
+ .f1star = "c6021e2e66accb15",
+ .f2 = "84d89b41db1867ffd4c7ba1d82163f4d526a20fbae5418fbb526940b1eeb905c",
+ .f3 = "d419676afe5ab58c1d8bee0d43523a4d2f52ef0b31a4676a0c334427a988fe65",
+ .f4 = "205533e505661b61d05cc0eac87818f4",
+ .f5 = "d7b3d2d4980a",
+ .f5star = "ca9655264986",
+ },
+ }, {
+ .name = "TS 35.233 Section 6.8 Test Set 6",
+ .in = {
+ .k = "1574ca56881d05c189c82880f789c9cd4244955f4426aa2b69c29f15770e5aa5",
+ .rand = "c570aac68cde651fb1e3088322498bef",
+ .sqn = "c89bb71f3a41",
+ .amf = "297d",
+ .top = "e59f6eb10ea406813f4991b0b9e02f181edf4c7e17b480f66d34da35ee88c95e",
+ .keccak_iterations = 2,
+ },
+ .out = {
+ .topc = "b04a66f26c62fcd6c82de22a179ab65506ecf47f56245cd149966cfa9cec7a51",
+ .f1 = "90d2289ed1ca1c3dbc2247bb480d431ac71d2e4a7677f6e997cfddb0cbad88b7",
+ .f1star = "427355dbac30e825063aba61b556e87583abac638e3ab01c4c884ad9d458dc2f",
+ .f2 = "d67e6e64590d22eecba7324afa4af4460c93f01b24506d6e12047d789a94c867",
+ .f3 = "ede57edfc57cdffe1aae75066a1b7479bbc3837438e88d37a801cccc9f972b89",
+ .f4 = "48ed9299126e5057402fe01f9201cf25249f9c5c0ed2afcf084755daff1d3999",
+ .f5 = "6aae8d18c448",
+ .f5star = "8c5f33b61f4e",
+ },
+ },
+};
+
+
+struct tuak_testset {
+ const char *name;
+ struct {
+ uint8_t k[32];
+ uint8_t k_len_bytes;
+ uint8_t rand[16];
+ uint8_t sqn[6];
+ uint8_t amf[2];
+ uint8_t top[32];
+ unsigned int keccak_iterations;
+ } in;
+ struct {
+ uint8_t topc[32];
+ uint8_t mac_a[32];
+ uint8_t mac_s[32];
+ uint8_t mac_len_bytes;
+
+ uint8_t res[32];
+ uint8_t res_len_bytes;
+
+ uint8_t ck[32];
+ uint8_t ck_len_bytes;
+ uint8_t ik[32];
+ uint8_t ik_len_bytes;
+ uint8_t ak[6];
+ uint8_t f5star[6];
+ } out;
+};
+
+static void expect_equal(const char *name, const uint8_t *actual, const uint8_t *expected, size_t len)
+{
+ if (!memcmp(actual, expected, len)) {
+ printf("\t%s: %s\r\n", name, osmo_hexdump_nospc(actual, len));
+ } else {
+ char buf[len*2+1];
+ printf("\t%s: %s != %s\r\n", name, osmo_hexdump_nospc(actual, len),
+ osmo_hexdump_buf(buf, sizeof(buf), expected, len, "", true));
+ }
+}
+
+static void execute_testset(const struct tuak_testset *tset)
+{
+ uint8_t topc[32];
+
+ printf("==> %s\n", tset->name);
+
+ tuak_set_keccak_iterations(tset->in.keccak_iterations);
+ tuak_opc_gen(topc, tset->in.k, tset->in.k_len_bytes, tset->in.top);
+ expect_equal("TOPc", topc, tset->out.topc, sizeof(topc));
+
+ if (tset->out.mac_len_bytes) {
+ uint8_t mac_a[32];
+ uint8_t mac_s[32];
+
+ tuak_f1(topc, tset->in.k, tset->in.k_len_bytes, tset->in.rand, tset->in.sqn, tset->in.amf,
+ mac_a, tset->out.mac_len_bytes, tset->in.keccak_iterations);
+ expect_equal("MAC_A", mac_a, tset->out.mac_a, tset->out.mac_len_bytes);
+
+ tuak_f1star(topc, tset->in.k, tset->in.k_len_bytes, tset->in.rand, tset->in.sqn, tset->in.amf,
+ mac_s, tset->out.mac_len_bytes, tset->in.keccak_iterations);
+ expect_equal("MAC_S", mac_s, tset->out.mac_s, tset->out.mac_len_bytes);
+ }
+
+ if (tset->out.ck_len_bytes || tset->out.ik_len_bytes || tset->out.res_len_bytes) {
+ uint8_t res[32];
+ uint8_t ck[32];
+ uint8_t ik[32];
+ uint8_t ak[6];
+
+ tuak_f2345(topc, tset->in.k, tset->in.k_len_bytes, tset->in.rand,
+ tset->out.res_len_bytes ? res : NULL, tset->out.res_len_bytes,
+ tset->out.ck_len_bytes ? ck : NULL, tset->out.ck_len_bytes,
+ tset->out.ik_len_bytes ? ik : NULL, tset->out.ik_len_bytes,
+ ak, tset->in.keccak_iterations);
+
+ if (tset->out.res_len_bytes)
+ expect_equal("RES", res, tset->out.res, tset->out.res_len_bytes);
+
+ if (tset->out.ck_len_bytes)
+ expect_equal("CK", ck, tset->out.ck, tset->out.ck_len_bytes);
+
+ if (tset->out.ik_len_bytes)
+ expect_equal("IK", ik, tset->out.ik, tset->out.ik_len_bytes);
+
+ expect_equal("AK", ak, tset->out.ak, 6);
+ }
+}
+
+/* convert string-testspec to binary-testset and execute it */
+static void execute_testspec(const struct tuak_testspec *tcase)
+{
+ struct tuak_testset _tset, *tset = &_tset;
+
+ tset->name = tcase->name;
+ tset->in.keccak_iterations = tcase->in.keccak_iterations;
+
+ osmo_hexparse(tcase->in.k, tset->in.k, sizeof(tset->in.k));
+ tset->in.k_len_bytes = strlen(tcase->in.k)/2;
+ OSMO_ASSERT(tset->in.k_len_bytes == 16 || tset->in.k_len_bytes == 32);
+
+ osmo_hexparse(tcase->in.rand, tset->in.rand, sizeof(tset->in.rand));
+ OSMO_ASSERT(strlen(tcase->in.rand)/2 == 16);
+
+ osmo_hexparse(tcase->in.sqn, tset->in.sqn, sizeof(tset->in.sqn));
+ OSMO_ASSERT(strlen(tcase->in.sqn)/2 == 6);
+
+ osmo_hexparse(tcase->in.amf, tset->in.amf, sizeof(tset->in.amf));
+ OSMO_ASSERT(strlen(tcase->in.amf)/2 == 2);
+
+ osmo_hexparse(tcase->in.top, tset->in.top, sizeof(tset->in.top));
+ OSMO_ASSERT(strlen(tcase->in.top)/2 == 32);
+
+ osmo_hexparse(tcase->out.topc, tset->out.topc, sizeof(tset->out.topc));
+ OSMO_ASSERT(strlen(tcase->out.topc)/2 == 32);
+
+ osmo_hexparse(tcase->out.f1, tset->out.mac_a, sizeof(tset->out.mac_a));
+ osmo_hexparse(tcase->out.f1star, tset->out.mac_s, sizeof(tset->out.mac_s));
+ OSMO_ASSERT(strlen(tcase->out.f1) == strlen(tcase->out.f1star));
+ tset->out.mac_len_bytes = strlen(tcase->out.f1)/2;
+ OSMO_ASSERT(tset->out.mac_len_bytes == 8 || tset->out.mac_len_bytes == 16 ||
+ tset->out.mac_len_bytes == 32);
+
+ osmo_hexparse(tcase->out.f2, tset->out.res, sizeof(tset->out.res));
+ tset->out.res_len_bytes = strlen(tcase->out.f2)/2;
+ OSMO_ASSERT(tset->out.res_len_bytes == 4 || tset->out.res_len_bytes == 8 ||
+ tset->out.res_len_bytes == 16 || tset->out.res_len_bytes == 32);
+
+ osmo_hexparse(tcase->out.f3, tset->out.ck, sizeof(tset->out.ck));
+ tset->out.ck_len_bytes = strlen(tcase->out.f3)/2;
+ OSMO_ASSERT(tset->out.ck_len_bytes == 16 || tset->out.ck_len_bytes == 32);
+
+ osmo_hexparse(tcase->out.f4, tset->out.ik, sizeof(tset->out.ik));
+ tset->out.ik_len_bytes = strlen(tcase->out.f4)/2;
+ OSMO_ASSERT(tset->out.ik_len_bytes == 16 || tset->out.ik_len_bytes == 32);
+
+ osmo_hexparse(tcase->out.f5, tset->out.ak, sizeof(tset->out.ak));
+ OSMO_ASSERT(strlen(tcase->out.f5)/2 == 6);
+
+ osmo_hexparse(tcase->out.f5star, tset->out.f5star, sizeof(tset->out.f5star));
+ OSMO_ASSERT(strlen(tcase->out.f5star)/2 == 6);
+
+ execute_testset(tset);
+}
+
+int main(int argc, char **argv)
+{
+#if 0
+ for (unsigned int i = 0; i < ARRAY_SIZE(testsets); i++)
+ execute_testset(&testsets[i]);
+#endif
+
+ for (unsigned int i = 0; i < ARRAY_SIZE(testspecs); i++)
+ execute_testspec(&testspecs[i]);
+
+}
diff --git a/tests/auth/tuak_test.ok b/tests/auth/tuak_test.ok
new file mode 100644
index 00000000..976fb59e
--- /dev/null
+++ b/tests/auth/tuak_test.ok
@@ -0,0 +1,48 @@
+==> TS 35.233 Section 6.3 Test Set 1
+ TOPc: bd04d9530e87513c5d837ac2ad954623a8e2330c115305a73eb45d1f40cccbff
+ MAC_A: f9a54e6aeaa8618d
+ MAC_S: e94b4dc6c7297df3
+ RES: 657acd64
+ CK: d71a1e5c6caffe986a26f783e5c78be1
+ IK: be849fa2564f869aecee6f62d4337e72
+ AK: 719f1e9b9054
+==> TS 35.233 Section 6.4 Test Set 2
+ TOPc: 305425427e18c503c8a4b294ea72c95d0c36c6c6b29d0c65de5974d5977f8524
+ MAC_A: c0b8c2d4148ec7aa5f1d78a97e4d1d58
+ MAC_S: ef81af7290f7842c6ceafa537fa0745b
+ RES: e9d749dc4eea0035
+ CK: a4cb6f6529ab17f8337f27baa8234d47
+ IK: 2274155ccf4199d5e2abcbf621907f90
+ AK: 480a9345cc1e
+==> TS 35.233 Section 6.5 Test Set 3
+ TOPc: 305425427e18c503c8a4b294ea72c95d0c36c6c6b29d0c65de5974d5977f8524
+ MAC_A: d97b75a1776065271b1e212bc3b1bf173f438b21e6c64a55a96c372e085e5cc5
+ MAC_S: 427bbf07c6e3a86c54f8c5216499f3909a6fd4a164c9fe235b1550258111b821
+ RES: 07021c73e7635c7d
+ CK: 4d59ac796834eb85d11fa148a5058c3c
+ IK: 126d47500136fdc5ddfd14f19ebf16749ce4b6435323fbb5715a3a796a6082bd
+ AK: 1d6622c4e59a
+==> TS 35.233 Section 6.6 Test Set 4
+ TOPc: 2bc16eb657a68e1f446f08f57c0efb1d493527a2e652ce281eb6ca0e4487760a
+ MAC_A: 749214087958dd8f58bfcdf869d8ae3f
+ MAC_S: 619e865afe80e382aee13063f9dfb56d
+ RES: 4041ce438e3e38e8aa96562eed83ac43
+ CK: 3e3bc01bea0cd914c4c2c83ce2d92757
+ IK: 666a8e6f577b1aa77b7fd53cebb8a3d6
+ AK: 1f880d005119
+==> TS 35.233 Section 6.7 Test Set 5
+ TOPc: 3c6052e41532a28a47aa3cbb89f223e8f3aaa976aecd48bc3e7d6165a55eff62
+ MAC_A: d7340dad02b4cb01
+ MAC_S: c6021e2e66accb15
+ RES: 84d89b41db1867ffd4c7ba1d82163f4d526a20fbae5418fbb526940b1eeb905c
+ CK: d419676afe5ab58c1d8bee0d43523a4d2f52ef0b31a4676a0c334427a988fe65
+ IK: 205533e505661b61d05cc0eac87818f4
+ AK: d7b3d2d4980a
+==> TS 35.233 Section 6.8 Test Set 6
+ TOPc: b04a66f26c62fcd6c82de22a179ab65506ecf47f56245cd149966cfa9cec7a51
+ MAC_A: 90d2289ed1ca1c3dbc2247bb480d431ac71d2e4a7677f6e997cfddb0cbad88b7
+ MAC_S: 427355dbac30e825063aba61b556e87583abac638e3ab01c4c884ad9d458dc2f
+ RES: d67e6e64590d22eecba7324afa4af4460c93f01b24506d6e12047d789a94c867
+ CK: ede57edfc57cdffe1aae75066a1b7479bbc3837438e88d37a801cccc9f972b89
+ IK: 48ed9299126e5057402fe01f9201cf25249f9c5c0ed2afcf084755daff1d3999
+ AK: 6aae8d18c448
diff --git a/tests/auth/xor2g_test.c b/tests/auth/xor2g_test.c
new file mode 100644
index 00000000..82ab25ac
--- /dev/null
+++ b/tests/auth/xor2g_test.c
@@ -0,0 +1,77 @@
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <inttypes.h>
+
+#include <osmocom/crypt/auth.h>
+#include <osmocom/core/utils.h>
+
+static void dump_auth_vec(struct osmo_auth_vector *vec)
+{
+ printf("RAND:\t%s\n", osmo_hexdump(vec->rand, sizeof(vec->rand)));
+
+ if (vec->auth_types & OSMO_AUTH_TYPE_UMTS) {
+ printf("AUTN:\t%s\n", osmo_hexdump(vec->autn, sizeof(vec->autn)));
+ printf("IK:\t%s\n", osmo_hexdump(vec->ik, sizeof(vec->ik)));
+ printf("CK:\t%s\n", osmo_hexdump(vec->ck, sizeof(vec->ck)));
+ printf("RES:\t%s\n", osmo_hexdump(vec->res, vec->res_len));
+ }
+
+ if (vec->auth_types & OSMO_AUTH_TYPE_GSM) {
+ printf("SRES:\t%s\n", osmo_hexdump(vec->sres, sizeof(vec->sres)));
+ /* According to 3GPP TS 55.205 Sec. 4 the GSM-MILENAGE output is limited to 64 bits.
+ According to 3GPP TS 33.102 Annex. B5 in UMTS security context Kc can be 128 bits.
+ Here we test the former, so make sure we only print interesting Kc bits. */
+ printf("Kc:\t%s\n", osmo_hexdump(vec->kc, OSMO_A5_MAX_KEY_LEN_BYTES/2));
+ }
+}
+
+static struct osmo_sub_auth_data test_aud = {
+ .type = OSMO_AUTH_TYPE_GSM,
+ .algo = OSMO_AUTH_ALG_XOR_2G,
+ .u.gsm = {
+ .ki = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f },
+ },
+};
+
+int main(int argc, char **argv)
+{
+ struct osmo_auth_vector _vec;
+ struct osmo_auth_vector *vec = &_vec;
+ uint8_t _rand[16];
+ int rc;
+
+#if 0
+ srand(time(NULL));
+ *(uint32_t *)&_rand[0] = rand();
+ *(uint32_t *)(&_rand[4]) = rand();
+ *(uint32_t *)(&_rand[8]) = rand();
+ *(uint32_t *)(&_rand[12]) = rand();
+#else
+ memset(_rand, 0, sizeof(_rand));
+#endif
+ memset(vec, 0, sizeof(*vec));
+
+ rc = osmo_auth_gen_vec(vec, &test_aud, _rand);
+ if (rc < 0) {
+ fprintf(stderr, "error generating auth vector\n");
+ exit(1);
+ }
+ dump_auth_vec(vec);
+
+ /* test once more with non-zero RAND to see it show in result */
+ for (int i = 0; i < sizeof(_rand); i++)
+ _rand[i] = i << 4;
+
+ rc = osmo_auth_gen_vec(vec, &test_aud, _rand);
+ if (rc < 0) {
+ fprintf(stderr, "error generating auth vector\n");
+ exit(1);
+ }
+ dump_auth_vec(vec);
+
+ exit(0);
+}
diff --git a/tests/auth/xor2g_test.ok b/tests/auth/xor2g_test.ok
new file mode 100644
index 00000000..58becf65
--- /dev/null
+++ b/tests/auth/xor2g_test.ok
@@ -0,0 +1,6 @@
+RAND: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+SRES: 00 01 02 03
+Kc: 04 05 06 07 08 09 0a 0b
+RAND: 00 10 20 30 40 50 60 70 80 90 a0 b0 c0 d0 e0 f0
+SRES: 00 11 22 33
+Kc: 44 55 66 77 88 99 aa bb
diff --git a/tests/bitgen/bitgen_test.c b/tests/bitgen/bitgen_test.c
index 8657bbef..bd472916 100644
--- a/tests/bitgen/bitgen_test.c
+++ b/tests/bitgen/bitgen_test.c
@@ -14,17 +14,17 @@
for (at_idx = 0; at_idx < len; at_idx++) { \
uint##SIZE##_t read_val = 0; \
memset(buf, 0, sizeof(buf)); \
- osmo_store##SIZE####BE_LE##_ext(val, &buf[at_idx], len); \
+ osmo_store##SIZE##BE_LE##_ext(val, &buf[at_idx], len); \
printf("osmo_store" #SIZE #BE_LE "_ext(0x%" PRIx##SIZE ", &buf[%d], %d) = %s\n", \
val, \
at_idx, len, osmo_hexdump(buf, sizeof(buf))); \
\
- read_val = osmo_load##SIZE####BE_LE##_ext(&buf[at_idx], len); \
+ read_val = osmo_load##SIZE##BE_LE##_ext(&buf[at_idx], len); \
printf("osmo_load" #SIZE #BE_LE "_ext(&buf[%d], %d) = 0x%" PRIx##SIZE "\n", \
at_idx, len, read_val); \
\
if (!strcmp(#BE_LE, "be")) { \
- read_val = osmo_load##SIZE####BE_LE##_ext_2(&buf[at_idx], len); \
+ read_val = osmo_load##SIZE##BE_LE##_ext_2(&buf[at_idx], len); \
printf("osmo_load" #SIZE #BE_LE "_ext_2(&buf[%d], %d) = 0x%" PRIx##SIZE "\n", \
at_idx, len, read_val); \
} \
diff --git a/tests/bitvec/bitvec_test.c b/tests/bitvec/bitvec_test.c
index ea7ea9a4..29c3e33d 100644
--- a/tests/bitvec/bitvec_test.c
+++ b/tests/bitvec/bitvec_test.c
@@ -69,7 +69,7 @@ static inline void test_set(struct bitvec *bv, enum bit_value bit)
printf(" %s [%d]\n\n", lol, bv->cur_bit);
}
-static void test_byte_ops()
+static void test_byte_ops(void)
{
struct bitvec bv;
const uint8_t *in = (const uint8_t *)"ABCDEFGHIJKLMNOPQRSTUVWXYZ";
@@ -162,7 +162,7 @@ static inline void test_bitvec_rl_curbit(struct bitvec *bv, bool b, int max_bits
OSMO_ASSERT(num == result);
}
-static void test_array()
+static void test_array(void)
{
struct bitvec b;
uint8_t d[4096];
@@ -181,7 +181,7 @@ static void test_array()
test_array_item(17, &b, n, array, n * 3);
}
-static void test_used_bytes()
+static void test_used_bytes(void)
{
struct bitvec b;
uint8_t d[32];
@@ -204,7 +204,7 @@ static void test_used_bytes()
}
}
-static void test_tailroom()
+static void test_tailroom(void)
{
struct bitvec b;
uint8_t d[32];
diff --git a/tests/bsslap/bsslap_test.c b/tests/bsslap/bsslap_test.c
index f20f7db4..fc5ce754 100644
--- a/tests/bsslap/bsslap_test.c
+++ b/tests/bsslap/bsslap_test.c
@@ -48,7 +48,7 @@ struct bsslap_pdu bsslap_test_pdus[] = {
},
};
-void test_bsslap_enc_dec()
+void test_bsslap_enc_dec(void)
{
struct bsslap_pdu *pdu;
printf("--- %s\n", __func__);
@@ -96,7 +96,7 @@ loop_end:
}
}
-int main()
+int main(int argc, char **argv)
{
test_bsslap_enc_dec();
return 0;
diff --git a/tests/bssmap_le/bssmap_le_test.c b/tests/bssmap_le/bssmap_le_test.c
index 08ed4c6e..a14c38a3 100644
--- a/tests/bssmap_le/bssmap_le_test.c
+++ b/tests/bssmap_le/bssmap_le_test.c
@@ -150,7 +150,7 @@ struct bssmap_le_pdu bssmap_le_test_pdus[] = {
},
};
-void test_bssmap_le_enc_dec()
+void test_bssmap_le_enc_dec(void)
{
struct bssmap_le_pdu *pdu;
printf("--- %s\n", __func__);
@@ -200,7 +200,7 @@ loop_end:
}
}
-int main()
+int main(int argc, char **argv)
{
test_bssmap_le_enc_dec();
return 0;
diff --git a/tests/codec/codec_ecu_fr_test.c b/tests/codec/codec_ecu_fr_test.c
index 4040ce94..4e5b71d7 100644
--- a/tests/codec/codec_ecu_fr_test.c
+++ b/tests/codec/codec_ecu_fr_test.c
@@ -151,7 +151,7 @@ void test_fr_concealment_core(void)
int i, rc;
int j = 0;
- printf("=> Testing FR concealment (simple, consecutive bad frames)\n");
+ printf("=> Testing FR concealment (simple, using ECU abstraction)\n");
while (sample_frame_hex[j] != NULL) {
/* Parse frame from string to hex */
@@ -183,7 +183,7 @@ void test_fr_concealment_core(void)
}
/* Simulate a real life situation: voice frames with a few dropouts */
-void test_fr_concealment_realistic()
+void test_fr_concealment_realistic(void)
{
struct osmo_ecu_fr_state state;
uint8_t frame[GSM_FR_BYTES];
@@ -219,7 +219,7 @@ void test_fr_concealment_realistic()
}
/* Simulate a real life situation: voice frames with a few dropouts, using generic core */
-void test_fr_concealment_realistic_core()
+void test_fr_concealment_realistic_core(void)
{
struct osmo_ecu_state *state = osmo_ecu_init(NULL, OSMO_ECU_CODEC_FR);
uint8_t frame[GSM_FR_BYTES];
diff --git a/tests/codec/codec_ecu_fr_test.ok b/tests/codec/codec_ecu_fr_test.ok
index d925e285..ed5eb9d8 100644
--- a/tests/codec/codec_ecu_fr_test.ok
+++ b/tests/codec/codec_ecu_fr_test.ok
@@ -41,49 +41,49 @@ conceal: 16, result: d0000000000000000000000000000000000000000000000000000000000
conceal: 17, result: d00000000000000000000000000000000000000000000000000000000000000000 XMAXC: [0, 0, 0, 0]
conceal: 18, result: d00000000000000000000000000000000000000000000000000000000000000000 XMAXC: [0, 0, 0, 0]
conceal: 19, result: d00000000000000000000000000000000000000000000000000000000000000000 XMAXC: [0, 0, 0, 0]
-=> Testing FR concealment (simple, consecutive bad frames)
+=> Testing FR concealment (simple, using ECU abstraction)
Start with: d9ec9be212901f802335598c501f805bad3d4ba01f809b69df5a501f809cd1b4da, XMAXC: [3f, 3f, 3f, 3f]
conceal: 00, result: d9ec9be212901f802335598c501f805bad3d4ba01f809b69df5a501f809cd1b4da XMAXC: [3f, 3f, 3f, 3f]
-conceal: 01, result: d9ec9be212901d802335598c501d805bad3d4ba01d809b69df5a501d809cd1b4da XMAXC: [3b, 3b, 3b, 3b]
-conceal: 02, result: d9ec9be212901b802335598c501b805bad3d4ba01b809b69df5a501b809cd1b4da XMAXC: [37, 37, 37, 37]
+conceal: 01, result: d9ec9be212905d802335598c501d805bad3d4ba01d809b69df5a501d809cd1b4da XMAXC: [3b, 3b, 3b, 3b]
+conceal: 02, result: d9ec9be212901b802335598c501b805bad3d4ba01b809b69df5a507b809cd1b4da XMAXC: [37, 37, 37, 37]
conceal: 03, result: d9ec9be2129019802335598c5019805bad3d4ba019809b69df5a5019809cd1b4da XMAXC: [33, 33, 33, 33]
-conceal: 04, result: d9ec9be2129017802335598c5017805bad3d4ba017809b69df5a5017809cd1b4da XMAXC: [2f, 2f, 2f, 2f]
+conceal: 04, result: d9ec9be2129017802335598c5017805bad3d4ba057809b69df5a5057809cd1b4da XMAXC: [2f, 2f, 2f, 2f]
conceal: 05, result: d9ec9be2129015802335598c5015805bad3d4ba015809b69df5a5015809cd1b4da XMAXC: [2b, 2b, 2b, 2b]
-conceal: 06, result: d9ec9be2129013802335598c5013805bad3d4ba013809b69df5a5013809cd1b4da XMAXC: [27, 27, 27, 27]
+conceal: 06, result: d9ec9be2129013802335598c5073805bad3d4ba073809b69df5a5013809cd1b4da XMAXC: [27, 27, 27, 27]
conceal: 07, result: d9ec9be2129011802335598c5011805bad3d4ba011809b69df5a5011809cd1b4da XMAXC: [23, 23, 23, 23]
-conceal: 08, result: d9ec9be212900f802335598c500f805bad3d4ba00f809b69df5a500f809cd1b4da XMAXC: [1f, 1f, 1f, 1f]
-conceal: 09, result: d9ec9be212900d802335598c500d805bad3d4ba00d809b69df5a500d809cd1b4da XMAXC: [1b, 1b, 1b, 1b]
-conceal: 10, result: d9ec9be212900b802335598c500b805bad3d4ba00b809b69df5a500b809cd1b4da XMAXC: [17, 17, 17, 17]
-conceal: 11, result: d9ec9be2129009802335598c5009805bad3d4ba009809b69df5a5009809cd1b4da XMAXC: [13, 13, 13, 13]
-conceal: 12, result: d9ec9be2129007802335598c5007805bad3d4ba007809b69df5a5007809cd1b4da XMAXC: [f, f, f, f]
-conceal: 13, result: d9ec9be2129005802335598c5005805bad3d4ba005809b69df5a5005809cd1b4da XMAXC: [b, b, b, b]
-conceal: 14, result: d9ec9be2129003802335598c5003805bad3d4ba003809b69df5a5003809cd1b4da XMAXC: [7, 7, 7, 7]
-conceal: 15, result: d9ec9be2129001802335598c5001805bad3d4ba001809b69df5a5001809cd1b4da XMAXC: [3, 3, 3, 3]
-conceal: 16, result: d00000000000000000000000000000000000000000000000000000000000000000 XMAXC: [0, 0, 0, 0]
-conceal: 17, result: d00000000000000000000000000000000000000000000000000000000000000000 XMAXC: [0, 0, 0, 0]
-conceal: 18, result: d00000000000000000000000000000000000000000000000000000000000000000 XMAXC: [0, 0, 0, 0]
-conceal: 19, result: d00000000000000000000000000000000000000000000000000000000000000000 XMAXC: [0, 0, 0, 0]
+conceal: 08, result: d9ec9be212904f802335598c500f805bad3d4ba04f809b69df5a500f809cd1b4da XMAXC: [1f, 1f, 1f, 1f]
+conceal: 09, result: d9ec9be212900d802335598c500d805bad3d4ba00d809b69df5a506d809cd1b4da XMAXC: [1b, 1b, 1b, 1b]
+conceal: 10, result: d9ec9be212900b802335598c506b805bad3d4ba00b809b69df5a500b809cd1b4da XMAXC: [17, 17, 17, 17]
+conceal: 11, result: d9ec9be2129009802335598c5009805bad3d4ba049809b69df5a5049809cd1b4da XMAXC: [13, 13, 13, 13]
+conceal: 12, result: d9ec9be2129047802335598c5047805bad3d4ba007809b69df5a5007809cd1b4da XMAXC: [f, f, f, f]
+conceal: 13, result: d9ec9be2129005802335598c5065805bad3d4ba065809b69df5a5065809cd1b4da XMAXC: [b, b, b, b]
+conceal: 14, result: d9ec9be2129063802335598c5003805bad3d4ba003809b69df5a5003809cd1b4da XMAXC: [7, 7, 7, 7]
+conceal: 15, result: d9ec9be2129041802335598c5001805bad3d4ba001809b69df5a5001809cd1b4da XMAXC: [3, 3, 3, 3]
+conceal: 16, result: d9ec9be2129040002335598c5000005bad3d4ba000009b69df5a5060009cd1b4da XMAXC: [0, 0, 0, 0]
+conceal: 17, result: daa7aaa51a502038e46db91b502038e46db91b502038e46db91b502038e46db91b XMAXC: [0, 0, 0, 0]
+conceal: 18, result: daa7aaa51a502038e46db91b502038e46db91b502038e46db91b502038e46db91b XMAXC: [0, 0, 0, 0]
+conceal: 19, result: daa7aaa51a502038e46db91b502038e46db91b502038e46db91b502038e46db91b XMAXC: [0, 0, 0, 0]
Start with: d9ec9be212901d802335598c5013805bad3d4ba01f809b69df5a5019809cd1b4da, XMAXC: [3b, 27, 3f, 33]
conceal: 00, result: d9ec9be212901d802335598c5013805bad3d4ba01f809b69df5a5019809cd1b4da XMAXC: [3b, 27, 3f, 33]
-conceal: 01, result: d9ec9be212901b802335598c5011805bad3d4ba01d809b69df5a5017809cd1b4da XMAXC: [37, 23, 3b, 2f]
-conceal: 02, result: d9ec9be2129019802335598c500f805bad3d4ba01b809b69df5a5015809cd1b4da XMAXC: [33, 1f, 37, 2b]
-conceal: 03, result: d9ec9be2129017802335598c500d805bad3d4ba019809b69df5a5013809cd1b4da XMAXC: [2f, 1b, 33, 27]
-conceal: 04, result: d9ec9be2129015802335598c500b805bad3d4ba017809b69df5a5011809cd1b4da XMAXC: [2b, 17, 2f, 23]
-conceal: 05, result: d9ec9be2129013802335598c5009805bad3d4ba015809b69df5a500f809cd1b4da XMAXC: [27, 13, 2b, 1f]
-conceal: 06, result: d9ec9be2129011802335598c5007805bad3d4ba013809b69df5a500d809cd1b4da XMAXC: [23, f, 27, 1b]
-conceal: 07, result: d9ec9be212900f802335598c5005805bad3d4ba011809b69df5a500b809cd1b4da XMAXC: [1f, b, 23, 17]
-conceal: 08, result: d9ec9be212900d802335598c5003805bad3d4ba00f809b69df5a5009809cd1b4da XMAXC: [1b, 7, 1f, 13]
-conceal: 09, result: d9ec9be212900b802335598c5001805bad3d4ba00d809b69df5a5007809cd1b4da XMAXC: [17, 3, 1b, f]
-conceal: 10, result: d9ec9be2129009802335598c5000005bad3d4ba00b809b69df5a5005809cd1b4da XMAXC: [13, 0, 17, b]
-conceal: 11, result: d9ec9be2129007802335598c5000005bad3d4ba009809b69df5a5003809cd1b4da XMAXC: [f, 0, 13, 7]
-conceal: 12, result: d9ec9be2129005802335598c5000005bad3d4ba007809b69df5a5001809cd1b4da XMAXC: [b, 0, f, 3]
-conceal: 13, result: d9ec9be2129003802335598c5000005bad3d4ba005809b69df5a5000009cd1b4da XMAXC: [7, 0, b, 0]
-conceal: 14, result: d9ec9be2129001802335598c5000005bad3d4ba003809b69df5a5000009cd1b4da XMAXC: [3, 0, 7, 0]
-conceal: 15, result: d9ec9be2129000002335598c5000005bad3d4ba001809b69df5a5000009cd1b4da XMAXC: [0, 0, 3, 0]
-conceal: 16, result: d00000000000000000000000000000000000000000000000000000000000000000 XMAXC: [0, 0, 0, 0]
-conceal: 17, result: d00000000000000000000000000000000000000000000000000000000000000000 XMAXC: [0, 0, 0, 0]
-conceal: 18, result: d00000000000000000000000000000000000000000000000000000000000000000 XMAXC: [0, 0, 0, 0]
-conceal: 19, result: d00000000000000000000000000000000000000000000000000000000000000000 XMAXC: [0, 0, 0, 0]
+conceal: 01, result: d9ec9be212901b802335598c5011805bad3d4ba01d809b69df5a5077809cd1b4da XMAXC: [37, 23, 3b, 2f]
+conceal: 02, result: d9ec9be2129019802335598c500f805bad3d4ba05b809b69df5a5055809cd1b4da XMAXC: [33, 1f, 37, 2b]
+conceal: 03, result: d9ec9be2129017802335598c500d805bad3d4ba059809b69df5a5053809cd1b4da XMAXC: [2f, 1b, 33, 27]
+conceal: 04, result: d9ec9be2129015802335598c506b805bad3d4ba077809b69df5a5011809cd1b4da XMAXC: [2b, 17, 2f, 23]
+conceal: 05, result: d9ec9be2129013802335598c5069805bad3d4ba075809b69df5a500f809cd1b4da XMAXC: [27, 13, 2b, 1f]
+conceal: 06, result: d9ec9be2129051802335598c5007805bad3d4ba053809b69df5a500d809cd1b4da XMAXC: [23, f, 27, 1b]
+conceal: 07, result: d9ec9be212904f802335598c5005805bad3d4ba051809b69df5a506b809cd1b4da XMAXC: [1f, b, 23, 17]
+conceal: 08, result: d9ec9be212900d802335598c5063805bad3d4ba00f809b69df5a5069809cd1b4da XMAXC: [1b, 7, 1f, 13]
+conceal: 09, result: d9ec9be212900b802335598c5061805bad3d4ba04d809b69df5a5047809cd1b4da XMAXC: [17, 3, 1b, f]
+conceal: 10, result: d9ec9be2129049802335598c5040005bad3d4ba04b809b69df5a5045809cd1b4da XMAXC: [13, 0, 17, b]
+conceal: 11, result: d9ec9be2129047802335598c5020005bad3d4ba069809b69df5a5063809cd1b4da XMAXC: [f, 0, 13, 7]
+conceal: 12, result: d9ec9be2129065802335598c5060005bad3d4ba067809b69df5a5061809cd1b4da XMAXC: [b, 0, f, 3]
+conceal: 13, result: d9ec9be2129023802335598c5000005bad3d4ba005809b69df5a5000009cd1b4da XMAXC: [7, 0, b, 0]
+conceal: 14, result: d9ec9be2129001802335598c5000005bad3d4ba003809b69df5a5060009cd1b4da XMAXC: [3, 0, 7, 0]
+conceal: 15, result: d9ec9be2129040002335598c5000005bad3d4ba001809b69df5a5000009cd1b4da XMAXC: [0, 0, 3, 0]
+conceal: 16, result: d9ec9be2129000002335598c5000005bad3d4ba040009b69df5a5020009cd1b4da XMAXC: [0, 0, 0, 0]
+conceal: 17, result: daa7aaa51a502038e46db91b502038e46db91b502038e46db91b502038e46db91b XMAXC: [0, 0, 0, 0]
+conceal: 18, result: daa7aaa51a502038e46db91b502038e46db91b502038e46db91b502038e46db91b XMAXC: [0, 0, 0, 0]
+conceal: 19, result: daa7aaa51a502038e46db91b502038e46db91b502038e46db91b502038e46db91b XMAXC: [0, 0, 0, 0]
=> Testing FR concealment (realistic, various bad frames)
Frame No. 000:
@@ -318,7 +318,7 @@ Frame No. 018:
* output: d9689ba5e3d260491b516adb5e4027256e27227ee0351c8e549a5c60492471971b
Frame No. 019:
* input: (bad)
- * output: d00000000000000000000000000000000000000000000000000000000000000000
+ * output: d9689ba5e3d240491b516adb5e0027256e27227e80351c8e549a5c00492471971b
Frame No. 020:
* input: d8e6a2e1d3d2605b1376c8d35280392451391cbc80392a71b6db8aa049238dc8ab
* output: d8e6a2e1d3d2605b1376c8d35280392451391cbc80392a71b6db8aa049238dc8ab
@@ -357,19 +357,19 @@ Frame No. 031:
* output: d9a99361a276403b1a6ad6dcd40026e489c8e3bc40371c4dc564e2c036e28eb963
Frame No. 032:
* input: (bad)
- * output: d00000000000000000000000000000000000000000000000000000000000000000
+ * output: d9a99361a276003b1a6ad6dcd40026e489c8e3bc00371c4dc564e2e036e28eb963
Frame No. 033:
* input: (bad)
- * output: d00000000000000000000000000000000000000000000000000000000000000000
+ * output: daa7aaa51a502038e46db91b502038e46db91b502038e46db91b502038e46db91b
Frame No. 034:
* input: (bad)
- * output: d00000000000000000000000000000000000000000000000000000000000000000
+ * output: daa7aaa51a502038e46db91b502038e46db91b502038e46db91b502038e46db91b
Frame No. 035:
* input: (bad)
- * output: d00000000000000000000000000000000000000000000000000000000000000000
+ * output: daa7aaa51a502038e46db91b502038e46db91b502038e46db91b502038e46db91b
Frame No. 036:
* input: (bad)
- * output: d00000000000000000000000000000000000000000000000000000000000000000
+ * output: daa7aaa51a502038e46db91b502038e46db91b502038e46db91b502038e46db91b
Frame No. 037:
* input: d92c8b6d5aee4034ebb22724862047145634a5c0a038e371b8e4a880485c89dd25
* output: d92c8b6d5aee4034ebb22724862047145634a5c0a038e371b8e4a880485c89dd25
diff --git a/tests/coding/coding_test.c b/tests/coding/coding_test.c
index 5a6f6e06..c9508f7d 100644
--- a/tests/coding/coding_test.c
+++ b/tests/coding/coding_test.c
@@ -24,6 +24,7 @@
#include <osmocom/core/bits.h>
#include <osmocom/core/utils.h>
+#include <osmocom/gsm/protocol/gsm_04_08.h>
#include <osmocom/coding/gsm0503_coding.h>
#define DUMP_U_AT(b, x, u) do { \
@@ -299,7 +300,7 @@ static void test_hr(uint8_t *speech, int len)
memset(bursts_s + 6, 0, 20);
/* Decode, correcting errors */
- rc = gsm0503_tch_hr_decode(result, bursts_s, 0,
+ rc = gsm0503_tch_hr_decode2(result, bursts_s, 0,
&n_errors, &n_bits_total);
CHECK_RC_OR_RET(rc == len, "decoding");
@@ -312,6 +313,51 @@ static void test_hr(uint8_t *speech, int len)
printf("\n");
}
+static void test_facch(const uint8_t *data, bool half_rate)
+{
+ ubit_t bursts_u[116 * 8 * 2] = { 0 };
+ sbit_t bursts_s[116 * 8 * 2] = { 0 };
+ int rc;
+
+ /* Encode the given FACCH message three times (at different offsets) */
+ printf("%s(FACCH/%c): encoding: %s\n",
+ __func__, half_rate ? 'H' : 'F',
+ osmo_hexdump(&data[0], GSM_MACBLOCK_LEN));
+ for (unsigned int i = 0; i < 3; i++) {
+ ubit_t *pos = &bursts_u[116 * 4 * i];
+
+ if (half_rate)
+ rc = gsm0503_tch_hr_facch_encode(pos, &data[0]);
+ else
+ rc = gsm0503_tch_fr_facch_encode(pos, &data[0]);
+ CHECK_RC_OR_RET(rc == 0, "encoding");
+ }
+
+ /* Prepare soft-bits */
+ osmo_ubit2sbit(bursts_s, bursts_u, sizeof(bursts_s));
+
+ /* Decode three FACCH messages (at different offsets) */
+ for (unsigned int i = 0; i < 3; i++) {
+ const sbit_t *pos = &bursts_s[116 * 4 * i];
+ uint8_t result[GSM_MACBLOCK_LEN];
+ int n_errors, n_bits_total;
+
+ if (half_rate)
+ rc = gsm0503_tch_hr_facch_decode(&result[0], pos,
+ &n_errors, &n_bits_total);
+ else
+ rc = gsm0503_tch_fr_facch_decode(&result[0], pos,
+ &n_errors, &n_bits_total);
+ CHECK_RC_OR_RET(rc == GSM_MACBLOCK_LEN, "decoding");
+
+ printf("%s(FACCH/%c): decoded (BER=%d/%d): %s\n",
+ __func__, half_rate ? 'H' : 'F', n_errors, n_bits_total,
+ osmo_hexdump(result, GSM_MACBLOCK_LEN));
+ }
+
+ printf("\n");
+}
+
struct test_macblock {
bool is_egprs;
uint16_t exp_burst_bits;
@@ -487,7 +533,128 @@ static const sbit_t test_rach_11bit[6][36] = {
uint8_t test_speech_fr[33];
uint8_t test_speech_efr[31];
-uint8_t test_speech_hr[15];
+uint8_t test_speech_hr[14];
+
+struct csd_test_case {
+ const char *name;
+ unsigned int num_bits;
+ int (*enc_fn)(ubit_t *out, const ubit_t *in);
+ int (*dec_fn)(ubit_t *out, const sbit_t *in, int *ne, int *nb);
+ bool half_rate;
+};
+
+static const struct csd_test_case csd_tests[] = {
+ {
+ .name = "TCH/F9.6",
+ .num_bits = 4 * 60,
+ .enc_fn = &gsm0503_tch_fr96_encode,
+ .dec_fn = &gsm0503_tch_fr96_decode,
+ },
+ {
+ .name = "TCH/F4.8",
+ .num_bits = 2 * 60,
+ .enc_fn = &gsm0503_tch_fr48_encode,
+ .dec_fn = &gsm0503_tch_fr48_decode,
+ },
+ {
+ .name = "TCH/H4.8",
+ .num_bits = 4 * 60,
+ .enc_fn = &gsm0503_tch_hr48_encode,
+ .dec_fn = &gsm0503_tch_hr48_decode,
+ .half_rate = true,
+ },
+ {
+ .name = "TCH/F2.4",
+ .num_bits = 2 * 36,
+ .enc_fn = &gsm0503_tch_fr24_encode,
+ .dec_fn = &gsm0503_tch_fr24_decode,
+ },
+ {
+ .name = "TCH/H2.4",
+ .num_bits = 4 * 36,
+ .enc_fn = &gsm0503_tch_hr24_encode,
+ .dec_fn = &gsm0503_tch_hr24_decode,
+ .half_rate = true,
+ },
+ {
+ .name = "TCH/F14.4",
+ .num_bits = 290,
+ .enc_fn = &gsm0503_tch_fr144_encode,
+ .dec_fn = &gsm0503_tch_fr144_decode,
+ },
+};
+
+static void test_csd(const struct csd_test_case *tc, bool facch)
+{
+ const uint8_t patterns[] = { 0x00, 0xaa, 0xff };
+ ubit_t bursts_u[116 * (22 + 8)] = { 0 };
+ sbit_t bursts_s[116 * (22 + 8)] = { 0 };
+ ubit_t data[512];
+ int rc;
+
+ /* Encode three data blocks, each block filled-in with a pattern */
+ for (unsigned int i = 0; i < ARRAY_SIZE(patterns); i++) {
+ for (unsigned int j = 0; j < tc->num_bits; j++)
+ data[j] = (patterns[i] & (1 << (j % 8))) != 0;
+
+ rc = tc->enc_fn(&bursts_u[i * 4 * 116], &data[0]);
+ CHECK_RC_OR_RET(rc == 0, "encoding");
+
+ /* Test FACCH bitstealing */
+ if (facch && i == 1) {
+ memset(&data, GSM_MACBLOCK_PADDING, GSM_MACBLOCK_LEN);
+ if (tc->half_rate)
+ rc = gsm0503_tch_hr_facch_encode(&bursts_u[116 * 4], &data[0]);
+ else
+ rc = gsm0503_tch_fr_facch_encode(&bursts_u[116 * 4], &data[0]);
+ CHECK_RC_OR_RET(rc == 0, "encoding FACCH");
+ }
+ }
+
+ /* Prepare soft-bits */
+ osmo_ubit2sbit(&bursts_s[0], &bursts_u[0], sizeof(bursts_s));
+
+ /* Decode the soft-bits, print decoded blocks */
+ for (unsigned int i = 0; i < ARRAY_SIZE(patterns); i++) {
+ int n_errors, n_bits_total;
+
+ rc = tc->dec_fn(&data[0], &bursts_s[i * 4 * 116],
+ &n_errors, &n_bits_total);
+ CHECK_RC_OR_RET(rc == tc->num_bits, "decoding");
+
+ printf("%s(%s): block #%u (pattern 0x%02x): n_errors=%d / n_bits_total=%d\n",
+ __func__, tc->name, i, patterns[i], n_errors, n_bits_total);
+
+ for (unsigned int j = 0; j < tc->num_bits; j++) {
+ if (j && j % 64 == 0)
+ printf("\n");
+ else if (j && j % 8 == 0)
+ printf(" ");
+ printf("%c", data[j] ? '1' : '0');
+ }
+ printf("\n");
+ }
+
+ /* Test FACCH bitstealing if requested */
+ if (facch) {
+ int n_errors = 0, n_bits_total = 0;
+
+ if (tc->half_rate) {
+ rc = gsm0503_tch_hr_facch_decode(&data[0], &bursts_s[116 * 4],
+ &n_errors, &n_bits_total);
+ } else {
+ rc = gsm0503_tch_fr_facch_decode(&data[0], &bursts_s[116 * 4],
+ &n_errors, &n_bits_total);
+ }
+ CHECK_RC_OR_RET(rc == GSM_MACBLOCK_LEN, "decoding FACCH");
+
+ printf("%s(%s): FACCH/%c (pattern 0x2b): n_errors=%d / n_bits_total=%d\n",
+ __func__, tc->name, tc->half_rate ? 'H' : 'F', n_errors, n_bits_total);
+ printf("%s\n", osmo_hexdump(&data[0], GSM_MACBLOCK_LEN));
+ }
+
+ printf("\n");
+}
int main(int argc, char **argv)
{
@@ -533,7 +700,6 @@ int main(int argc, char **argv)
for (i = 0; i < sizeof(test_speech_hr); i++)
test_speech_hr[i] = i * 17;
- test_speech_hr[0] = 0x00;
test_hr(test_speech_hr, sizeof(test_speech_hr));
for (i = 0; i < len_l2; i++)
@@ -550,6 +716,20 @@ int main(int argc, char **argv)
}
}
+ printf("\nTesting FACCH/F codec:\n");
+ for (i = 0; i < ARRAY_SIZE(test_l2); i++)
+ test_facch(test_l2[i], false);
+ printf("\nTesting FACCH/H codec:\n");
+ for (i = 0; i < ARRAY_SIZE(test_l2); i++)
+ test_facch(test_l2[i], true);
+
+ printf("\nTesting CSD functions (no FACCH):\n");
+ for (i = 0; i < ARRAY_SIZE(csd_tests); i++)
+ test_csd(&csd_tests[i], false);
+ printf("\nTesting CSD functions (with FACCH):\n");
+ for (i = 0; i < ARRAY_SIZE(csd_tests); i++)
+ test_csd(&csd_tests[i], true);
+
printf("Success\n");
return 0;
diff --git a/tests/coding/coding_test.ok b/tests/coding/coding_test.ok
index cb562e9c..1fc2f1d8 100644
--- a/tests/coding/coding_test.ok
+++ b/tests/coding/coding_test.ok
@@ -172,22 +172,22 @@ S-Bits:
Decoded: 01 02 03 00 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17
tch_fr_decode: n_errors=10 n_bits_total=456 ber=0.02
-Encoding: 00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee
+Encoding: 00 11 22 33 44 55 66 77 88 99 aa bb cc dd
U-Bits:
-0E1E0E0E1E0E0E1E1E0E1E0E0E0E0E0E0E0E0E1E1E0E0E1E1E1E1E1E1 23 00 E1E1E1E1E1E0E1E1E1E0E0E1E0E1E1E1E0E0E1E0E0E1E1E1E1E0E0E0E
-0E1E1E0E0E0E0E1E0E1E1E1E0E0E1E1E0E0E1E0E1E0E1E0E0E0E1E0E1 23 00 E0E0E0E1E1E1E1E1E0E1E1E1E0E0E0E0E1E1E1E0E0E1E1E0E1E1E1E0E
-E0E0E0E1E1E1E1E1E0E1E0E0E1E1E0E0E1E0E1E0E0E1E1E0E0E0E0E0E 00 23 0E0E0E1E0E1E0E1E0E0E1E1E0E1E0E0E0E1E1E1E0E0E0E0E1E0E1E0E1
-E1E0E0E1E1E1E1E1E1E0E0E1E1E1E0E0E0E1E1E1E1E1E1E1E1E0E1E1E 00 23 0E0E1E0E1E0E0E0E1E0E0E1E1E0E1E1E1E1E0E1E1E0E1E0E0E1E0E1E1
+0E0E0E0E1E0E0E0E0E0E0E1E1E0E1E0E0E0E1E1E1E0E1E0E0E0E1E1E1 23 00 E1E0E1E0E1E1E1E0E1E1E0E0E0E0E1E0E1E0E0E0E1E1E0E1E1E0E1E0E
+1E0E1E0E0E1E0E0E0E1E1E1E0E0E1E1E1E0E1E1E0E1E0E0E1E0E1E0E0 23 00 E1E1E1E1E1E1E0E1E1E0E0E1E1E0E0E0E0E0E0E0E1E1E1E1E0E0E1E1E
+E0E1E1E1E0E0E0E1E1E1E1E1E1E0E0E1E0E1E1E1E0E0E1E0E0E1E1E1E 00 23 0E1E0E0E1E1E0E1E1E1E1E1E1E1E1E1E0E1E0E0E1E1E1E0E0E0E0E1E1
+E0E0E0E1E0E0E0E1E1E0E1E0E0E0E1E0E1E0E1E0E0E0E0E0E1E0E1E0E 00 23 1E0E1E0E0E1E0E1E0E0E0E0E1E0E1E1E1E1E1E0E1E0E1E1E1E1E0E1E0
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE 23 23 EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE 23 23 EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
S-Bits:
-7f 81 81 81 7f 81 7f 81 81 81 7f 81 7f 81 81 81 81 81 7f 81 81 81 7f 81 7f 81 7f 81 7f 81 7f 81 7f 81 7f 81 7f 81 81 81 81 81 7f 81 7f 81 81 81 81 81 81 81 81 81 81 81 81 81 7f 81 81 81 81 81 81 81 81 81 81 81 7f 81 81 81 81 81 81 81 7f 81 7f 81 81 81 7f 81 81 81 81 81 81 81 7f 81 7f 81 81 81 7f 81 7f 81 81 81 81 81 81 81 81 81 7f 81 7f 81 7f 81
-7f 81 81 81 81 81 7f 81 7f 81 7f 81 7f 81 81 81 7f 81 81 81 81 81 81 81 7f 81 7f 81 81 81 81 81 7f 81 7f 81 81 81 7f 81 81 81 7f 81 81 81 7f 81 7f 81 7f 81 81 81 7f 81 81 81 7f 81 7f 81 7f 81 7f 81 81 81 81 81 81 81 81 81 81 81 7f 81 81 81 81 81 81 81 7f 81 7f 81 7f 81 7f 81 81 81 81 81 81 81 7f 81 7f 81 81 81 81 81 7f 81 81 81 81 81 81 81 7f 81
-81 7f 81 7f 81 7f 81 81 81 81 81 81 81 81 81 81 81 7f 81 81 81 7f 81 7f 81 81 81 81 81 7f 81 7f 81 81 81 7f 81 81 81 7f 81 7f 81 81 81 81 81 7f 81 7f 81 7f 81 7f 81 7f 81 7f 81 7f 81 7f 81 7f 81 81 81 7f 81 81 81 7f 81 81 81 7f 81 7f 81 81 81 81 81 7f 81 81 81 7f 81 7f 81 7f 81 81 81 81 81 81 81 7f 81 7f 81 7f 81 7f 81 81 81 7f 81 81 81 7f 81 81
-81 81 81 7f 81 7f 81 81 81 81 81 81 81 81 81 81 81 81 81 7f 81 7f 81 81 81 81 81 81 81 7f 81 7f 81 7f 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 7f 81 81 81 81 81 7f 81 7f 81 7f 81 81 81 7f 81 81 81 7f 81 7f 81 7f 81 81 81 7f 81 7f 81 81 81 81 81 7f 81 81 81 81 81 81 81 81 81 7f 81 81 81 81 81 7f 81 81 81 7f 81 7f 81 81 81 7f 81 81 81 81
+7f 81 7f 81 7f 81 7f 81 81 81 7f 81 7f 81 7f 81 7f 81 7f 81 7f 81 81 81 81 81 7f 81 81 81 7f 81 7f 81 7f 81 81 81 81 81 81 81 7f 81 81 81 7f 81 7f 81 7f 81 81 81 81 81 81 81 7f 81 81 81 7f 81 81 81 7f 81 81 81 81 81 81 81 7f 81 81 81 81 81 7f 81 7f 81 7f 81 7f 81 81 81 7f 81 81 81 7f 81 7f 81 7f 81 81 81 81 81 7f 81 81 81 81 81 7f 81 81 81 7f 81
+81 81 7f 81 81 81 7f 81 7f 81 81 81 7f 81 7f 81 7f 81 81 81 81 81 81 81 7f 81 7f 81 81 81 81 81 81 81 7f 81 81 81 81 81 7f 81 81 81 7f 81 7f 81 81 81 7f 81 81 81 7f 81 7f 81 7f 81 81 81 81 81 81 81 81 81 81 81 81 81 7f 81 81 81 81 81 7f 81 7f 81 81 81 81 81 7f 81 7f 81 7f 81 7f 81 7f 81 7f 81 7f 81 81 81 81 81 81 81 81 81 7f 81 7f 81 81 81 81 81
+81 7f 81 81 81 81 81 81 81 7f 81 7f 81 7f 81 81 81 81 81 81 81 81 81 81 81 81 81 7f 81 7f 81 81 81 7f 81 81 81 81 81 81 81 7f 81 7f 81 81 81 7f 81 7f 81 81 81 81 81 81 81 7f 81 7f 81 81 81 7f 81 7f 81 81 81 81 81 7f 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 7f 81 81 81 7f 81 7f 81 81 81 81 81 81 81 7f 81 7f 81 7f 81 7f 81 81 81 81
+81 7f 81 7f 81 7f 81 81 81 7f 81 7f 81 7f 81 81 81 81 81 7f 81 81 81 7f 81 7f 81 7f 81 81 81 7f 81 81 81 7f 81 81 81 7f 81 7f 81 7f 81 7f 81 7f 81 81 81 7f 81 81 81 7f 81 7f 81 81 81 7f 81 81 81 7f 81 7f 81 81 81 7f 81 81 81 7f 81 7f 81 7f 81 7f 81 81 81 7f 81 81 81 81 81 81 81 81 81 81 81 7f 81 81 81 7f 81 81 81 81 81 81 81 81 81 7f 81 81 81 7f
81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81
81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81 81
-Decoded: 00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee
+Decoded: 00 11 22 33 44 55 66 77 88 99 aa bb cc dd
tch_hr_decode: n_errors=10 n_bits_total=211 ber=0.05
Encoding: 03 03 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
@@ -367,4 +367,222 @@ S-Bits:
81 7f 7f 7f 81 7f 7f 81 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 81 81 7f 81 7f 7f 81 81 81 81 81 81 7f 7f 81 7f 81 7f 7f 81 7f 7f 7f 7f 81 7f 7f 7f 81 7f 7f 81 81 81 7f 81 7f 7f 81 7f 7f 81 7f 7f 81 7f 81 7f 81 7f 81 81 7f 7f 7f 81 7f 81 81 81 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 81 7f 81 81 81 81 7f 7f 7f 81 81 81 7f 7f 7f 81 81 7f 81 7f 7f 7f 81 7f 7f 81
81 81 81 81 7f 7f 7f 7f 81 81 7f 81 7f 7f 81 7f 81 81 7f 81 7f 7f 7f 7f 81 81 7f 81 81 81 81 7f 7f 7f 7f 81 7f 7f 81 7f 7f 81 7f 7f 7f 7f 7f 81 7f 7f 7f 81 7f 7f 81 81 7f 7f 81 7f 7f 7f 7f 7f 7f 81 81 7f 81 81 7f 81 81 7f 81 7f 7f 7f 81 81 81 81 81 7f 81 81 81 81 7f 7f 81 81 7f 7f 81 81 7f 81 81 7f 7f 7f 7f 7f 81 81 81 81 7f 7f 7f 81 7f 7f 7f 81
7f 7f 81 7f 7f 7f 81 81 7f 7f 81 81 7f 81 7f 81 81 7f 7f 81 81 7f 81 81 7f 7f 81 7f 81 81 81 7f 7f 81 7f 7f 7f 81 7f 7f 7f 81 81 7f 81 81 7f 81 7f 81 81 81 7f 7f 7f 7f 7f 81 7f 7f 7f 7f 7f 81 7f 7f 7f 7f 7f 81 7f 7f 81 7f 81 81 7f 7f 7f 81 81 81 81 81 81 81 7f 7f 81 7f 81 81 81 7f 81 7f 81 81 7f 7f 7f 7f 7f 7f 7f 81 81 81 7f 81 81 7f 7f 7f 81 7f
+
+Testing FACCH/F codec:
+test_facch(FACCH/F): encoding: 03 03 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+test_facch(FACCH/F): decoded (BER=0/456): 03 03 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+test_facch(FACCH/F): decoded (BER=0/456): 03 03 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+test_facch(FACCH/F): decoded (BER=0/456): 03 03 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+
+test_facch(FACCH/F): encoding: a3 af 5f 00 36 43 44 ab d9 6d 7d 62 24 c9 d2 92 fa 27 5d 71 7a 59 a8
+test_facch(FACCH/F): decoded (BER=0/456): a3 af 5f 00 36 43 44 ab d9 6d 7d 62 24 c9 d2 92 fa 27 5d 71 7a 59 a8
+test_facch(FACCH/F): decoded (BER=0/456): a3 af 5f 00 36 43 44 ab d9 6d 7d 62 24 c9 d2 92 fa 27 5d 71 7a 59 a8
+test_facch(FACCH/F): decoded (BER=0/456): a3 af 5f 00 36 43 44 ab d9 6d 7d 62 24 c9 d2 92 fa 27 5d 71 7a 59 a8
+
+test_facch(FACCH/F): encoding: 01 02 03 00 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17
+test_facch(FACCH/F): decoded (BER=0/456): 01 02 03 00 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17
+test_facch(FACCH/F): decoded (BER=0/456): 01 02 03 00 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17
+test_facch(FACCH/F): decoded (BER=0/456): 01 02 03 00 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17
+
+
+Testing FACCH/H codec:
+test_facch(FACCH/H): encoding: 03 03 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+test_facch(FACCH/H): decoded (BER=0/456): 03 03 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+test_facch(FACCH/H): decoded (BER=0/456): 03 03 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+test_facch(FACCH/H): decoded (BER=0/456): 03 03 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+
+test_facch(FACCH/H): encoding: a3 af 5f 00 36 43 44 ab d9 6d 7d 62 24 c9 d2 92 fa 27 5d 71 7a 59 a8
+test_facch(FACCH/H): decoded (BER=0/456): a3 af 5f 00 36 43 44 ab d9 6d 7d 62 24 c9 d2 92 fa 27 5d 71 7a 59 a8
+test_facch(FACCH/H): decoded (BER=0/456): a3 af 5f 00 36 43 44 ab d9 6d 7d 62 24 c9 d2 92 fa 27 5d 71 7a 59 a8
+test_facch(FACCH/H): decoded (BER=0/456): a3 af 5f 00 36 43 44 ab d9 6d 7d 62 24 c9 d2 92 fa 27 5d 71 7a 59 a8
+
+test_facch(FACCH/H): encoding: 01 02 03 00 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17
+test_facch(FACCH/H): decoded (BER=0/456): 01 02 03 00 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17
+test_facch(FACCH/H): decoded (BER=0/456): 01 02 03 00 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17
+test_facch(FACCH/H): decoded (BER=0/456): 01 02 03 00 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17
+
+
+Testing CSD functions (no FACCH):
+test_csd(TCH/F9.6): block #0 (pattern 0x00): n_errors=0 / n_bits_total=456
+00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
+00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
+00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
+00000000 00000000 00000000 00000000 00000000 00000000
+test_csd(TCH/F9.6): block #1 (pattern 0xaa): n_errors=0 / n_bits_total=456
+01010101 01010101 01010101 01010101 01010101 01010101 01010101 01010101
+01010101 01010101 01010101 01010101 01010101 01010101 01010101 01010101
+01010101 01010101 01010101 01010101 01010101 01010101 01010101 01010101
+01010101 01010101 01010101 01010101 01010101 01010101
+test_csd(TCH/F9.6): block #2 (pattern 0xff): n_errors=0 / n_bits_total=456
+11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111
+11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111
+11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111
+11111111 11111111 11111111 11111111 11111111 11111111
+
+test_csd(TCH/F4.8): block #0 (pattern 0x00): n_errors=0 / n_bits_total=456
+00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
+00000000 00000000 00000000 00000000 00000000 00000000 00000000
+test_csd(TCH/F4.8): block #1 (pattern 0xaa): n_errors=0 / n_bits_total=456
+01010101 01010101 01010101 01010101 01010101 01010101 01010101 01010101
+01010101 01010101 01010101 01010101 01010101 01010101 01010101
+test_csd(TCH/F4.8): block #2 (pattern 0xff): n_errors=0 / n_bits_total=456
+11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111
+11111111 11111111 11111111 11111111 11111111 11111111 11111111
+
+test_csd(TCH/H4.8): block #0 (pattern 0x00): n_errors=0 / n_bits_total=456
+00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
+00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
+00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
+00000000 00000000 00000000 00000000 00000000 00000000
+test_csd(TCH/H4.8): block #1 (pattern 0xaa): n_errors=0 / n_bits_total=456
+01010101 01010101 01010101 01010101 01010101 01010101 01010101 01010101
+01010101 01010101 01010101 01010101 01010101 01010101 01010101 01010101
+01010101 01010101 01010101 01010101 01010101 01010101 01010101 01010101
+01010101 01010101 01010101 01010101 01010101 01010101
+test_csd(TCH/H4.8): block #2 (pattern 0xff): n_errors=0 / n_bits_total=456
+11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111
+11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111
+11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111
+11111111 11111111 11111111 11111111 11111111 11111111
+
+test_csd(TCH/F2.4): block #0 (pattern 0x00): n_errors=0 / n_bits_total=456
+00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
+00000000
+test_csd(TCH/F2.4): block #1 (pattern 0xaa): n_errors=0 / n_bits_total=456
+01010101 01010101 01010101 01010101 01010101 01010101 01010101 01010101
+01010101
+test_csd(TCH/F2.4): block #2 (pattern 0xff): n_errors=0 / n_bits_total=456
+11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111
+11111111
+
+test_csd(TCH/H2.4): block #0 (pattern 0x00): n_errors=0 / n_bits_total=456
+00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
+00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
+00000000 00000000
+test_csd(TCH/H2.4): block #1 (pattern 0xaa): n_errors=0 / n_bits_total=456
+01010101 01010101 01010101 01010101 01010101 01010101 01010101 01010101
+01010101 01010101 01010101 01010101 01010101 01010101 01010101 01010101
+01010101 01010101
+test_csd(TCH/H2.4): block #2 (pattern 0xff): n_errors=0 / n_bits_total=456
+11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111
+11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111
+11111111 11111111
+
+test_csd(TCH/F14.4): block #0 (pattern 0x00): n_errors=0 / n_bits_total=456
+00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
+00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
+00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
+00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
+00000000 00000000 00000000 00000000 00
+test_csd(TCH/F14.4): block #1 (pattern 0xaa): n_errors=0 / n_bits_total=456
+01010101 01010101 01010101 01010101 01010101 01010101 01010101 01010101
+01010101 01010101 01010101 01010101 01010101 01010101 01010101 01010101
+01010101 01010101 01010101 01010101 01010101 01010101 01010101 01010101
+01010101 01010101 01010101 01010101 01010101 01010101 01010101 01010101
+01010101 01010101 01010101 01010101 01
+test_csd(TCH/F14.4): block #2 (pattern 0xff): n_errors=0 / n_bits_total=456
+11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111
+11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111
+11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111
+11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111
+11111111 11111111 11111111 11111111 11
+
+
+Testing CSD functions (with FACCH):
+test_csd(TCH/F9.6): block #0 (pattern 0x00): n_errors=34 / n_bits_total=456
+00000000 00000000 00000000 00000000 00010010 01100000 00000000 00000000
+00000000 00010011 01111000 00000000 00000000 00000000 00100100 10000000
+00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
+00000000 00000000 00000000 00000000 00000000 00000000
+test_csd(TCH/F9.6): block #1 (pattern 0xaa): n_errors=34 / n_bits_total=456
+01010101 01010101 01010101 01010101 01010101 01010101 01010101 01010101
+01010101 01010101 01010101 01010101 01010101 01010101 00101011 01010101
+01010101 01010101 01010101 01010101 01010101 01010101 01010101 01010101
+01010101 01010101 01010101 01010101 01010101 01010101
+test_csd(TCH/F9.6): block #2 (pattern 0xff): n_errors=14 / n_bits_total=456
+11111111 11111111 11111111 11110111 11111111 11111111 11111111 11111111
+11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111
+11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111
+11111111 11111111 11111111 11111111 11111111 11111111
+test_csd(TCH/F9.6): FACCH/F (pattern 0x2b): n_errors=0 / n_bits_total=456
+2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b
+
+test_csd(TCH/F4.8): block #0 (pattern 0x00): n_errors=38 / n_bits_total=456
+00000000 00000000 01010000 00000000 00000000 00000000 00000000 00000000
+00000000 00000000 00000000 00000000 00000000 00000000 00000000
+test_csd(TCH/F4.8): block #1 (pattern 0xaa): n_errors=37 / n_bits_total=456
+01010101 01010101 01010101 01010101 01010101 01010101 01010101 01010101
+01010101 01010101 01010101 01010101 01010101 01010101 01010101
+test_csd(TCH/F4.8): block #2 (pattern 0xff): n_errors=13 / n_bits_total=456
+11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111
+11111111 11111111 11111111 11111111 11111111 11111111 11111111
+test_csd(TCH/F4.8): FACCH/F (pattern 0x2b): n_errors=0 / n_bits_total=456
+2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b
+
+test_csd(TCH/H4.8): block #0 (pattern 0x00): n_errors=35 / n_bits_total=456
+00000000 00000000 00000000 00000000 00111000 10000000 00000000 00000000
+00000001 00000000 00000000 00000000 00000000 00000000 00011000 10000000
+00000000 00000000 00000000 00000000 00000000 00001000 00000000 00000000
+00000000 00000000 00000000 00000000 00000000 00000000
+test_csd(TCH/H4.8): block #1 (pattern 0xaa): n_errors=38 / n_bits_total=456
+01010101 01010101 01010101 01010101 01010101 01010101 01010101 01010101
+01010101 01010101 01010101 01010101 01010101 01010101 00101011 01010101
+01010101 01010101 01010101 00111010 01010101 01010101 01010101 01010101
+01010101 01010101 01010101 01010101 01010101 01010101
+test_csd(TCH/H4.8): block #2 (pattern 0xff): n_errors=3 / n_bits_total=456
+11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111
+11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111
+11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111
+11111111 11111111 11111111 11111111 11111111 11111111
+test_csd(TCH/H4.8): FACCH/H (pattern 0x2b): n_errors=0 / n_bits_total=456
+2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b
+
+test_csd(TCH/F2.4): block #0 (pattern 0x00): n_errors=0 / n_bits_total=456
+00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
+00000000
+test_csd(TCH/F2.4): block #1 (pattern 0xaa): n_errors=138 / n_bits_total=456
+11000010 10010010 10010010 10010010 10010010 10010010 10010101 00011000
+01001010
+test_csd(TCH/F2.4): block #2 (pattern 0xff): n_errors=0 / n_bits_total=456
+11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111
+11111111
+test_csd(TCH/F2.4): FACCH/F (pattern 0x2b): n_errors=0 / n_bits_total=456
+2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b
+
+test_csd(TCH/H2.4): block #0 (pattern 0x00): n_errors=38 / n_bits_total=456
+00000000 00000000 00000101 00000000 00000000 00000000 00000000 00000000
+00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
+00000000 00000000
+test_csd(TCH/H2.4): block #1 (pattern 0xaa): n_errors=32 / n_bits_total=456
+01010101 01010101 01010101 01010101 01010101 01010101 01010101 01010101
+01010101 01010101 01010101 01010101 01010101 01010101 01010101 01010101
+01010101 01010101
+test_csd(TCH/H2.4): block #2 (pattern 0xff): n_errors=2 / n_bits_total=456
+11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111
+11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111
+11111111 11111111
+test_csd(TCH/H2.4): FACCH/H (pattern 0x2b): n_errors=0 / n_bits_total=456
+2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b
+
+test_csd(TCH/F14.4): block #0 (pattern 0x00): n_errors=27 / n_bits_total=456
+00000000 00000000 00000000 00000000 00000000 00111100 01000000 00000000
+00000000 00000000 00000000 00111000 10011000 00000000 10000000 00000000
+00000000 00000000 00001111 10001101 01110000 00000000 00000000 00000000
+00000000 00000000 00001001 11000000 00000000 00000000 00000000 00000000
+00000000 00000010 00100010 01000000 00
+test_csd(TCH/F14.4): block #1 (pattern 0xaa): n_errors=35 / n_bits_total=456
+01010101 01010100 10110111 01010101 01010101 00011000 10010001 11000110
+11011100 11011101 11000110 01010101 01010101 01010101 10100100 10010001
+11000110 00001010 01010101 01010101 11101101 11010101 01010100 01010101
+01010101 01010101 00101001 00100101 01010101 01010101 01010101 01010101
+01010101 01010101 01010101 01010101 01
+test_csd(TCH/F14.4): block #2 (pattern 0xff): n_errors=10 / n_bits_total=456
+11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111
+11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111
+11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111
+11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111
+11111111 11111111 11111111 11111111 11
+test_csd(TCH/F14.4): FACCH/F (pattern 0x2b): n_errors=0 / n_bits_total=456
+2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b
+
Success
diff --git a/tests/conv/conv_gsm0503_test.ok b/tests/conv/conv_gsm0503_test.ok
index 764bd436..dba52bdf 100644
--- a/tests/conv/conv_gsm0503_test.ok
+++ b/tests/conv/conv_gsm0503_test.ok
@@ -6,6 +6,46 @@
[..] Encoding / Decoding cycle : OK
[..] Encoding / Decoding cycle : OK
+[+] Testing: gsm0503_tch_f24
+[.] Input length : ret = 72 exp = 72 -> OK
+[.] Output length : ret = 456 exp = 456 -> OK
+[.] Random vector checks:
+[..] Encoding / Decoding cycle : OK
+[..] Encoding / Decoding cycle : OK
+[..] Encoding / Decoding cycle : OK
+
+[+] Testing: gsm0503_tch_h24
+[.] Input length : ret = 72 exp = 72 -> OK
+[.] Output length : ret = 228 exp = 228 -> OK
+[.] Random vector checks:
+[..] Encoding / Decoding cycle : OK
+[..] Encoding / Decoding cycle : OK
+[..] Encoding / Decoding cycle : OK
+
+[+] Testing: gsm0503_tch_f48
+[.] Input length : ret = 148 exp = 148 -> OK
+[.] Output length : ret = 456 exp = 456 -> OK
+[.] Random vector checks:
+[..] Encoding / Decoding cycle : OK
+[..] Encoding / Decoding cycle : OK
+[..] Encoding / Decoding cycle : OK
+
+[+] Testing: gsm0503_tch_f96
+[.] Input length : ret = 240 exp = 240 -> OK
+[.] Output length : ret = 456 exp = 456 -> OK
+[.] Random vector checks:
+[..] Encoding / Decoding cycle : OK
+[..] Encoding / Decoding cycle : OK
+[..] Encoding / Decoding cycle : OK
+
+[+] Testing: gsm0503_tch_f144
+[.] Input length : ret = 290 exp = 290 -> OK
+[.] Output length : ret = 456 exp = 456 -> OK
+[.] Random vector checks:
+[..] Encoding / Decoding cycle : OK
+[..] Encoding / Decoding cycle : OK
+[..] Encoding / Decoding cycle : OK
+
[+] Testing: gsm0503_rach
[.] Input length : ret = 14 exp = 14 -> OK
[.] Output length : ret = 36 exp = 36 -> OK
diff --git a/tests/ctrl/ctrl_test.c b/tests/ctrl/ctrl_test.c
index 01fb9f71..1d4d4d74 100644
--- a/tests/ctrl/ctrl_test.c
+++ b/tests/ctrl/ctrl_test.c
@@ -334,7 +334,7 @@ static const struct one_test test_messages_list[] = {
},
};
-static void test_messages()
+static void test_messages(void)
{
struct ctrl_handle *ctrl;
struct ctrl_connection *ccon;
@@ -390,7 +390,7 @@ static int verify_test_defer(struct ctrl_cmd *cmd, const char *value, void *data
return 0;
}
-static void test_deferred_cmd()
+static void test_deferred_cmd(void)
{
struct ctrl_handle *ctrl;
struct ctrl_connection *ccon;
diff --git a/tests/fr/fr_test.c b/tests/fr/fr_test.c
index 3fb18f3c..cdcdb434 100644
--- a/tests/fr/fr_test.c
+++ b/tests/fr/fr_test.c
@@ -52,7 +52,7 @@ int socket(int domain, int type, int protocol)
return fd;
}
-void bssgp_prim_cb()
+void bssgp_prim_cb(void)
{
}
diff --git a/tests/fsm/fsm_dealloc_test.c b/tests/fsm/fsm_dealloc_test.c
index f4be93dd..246ba563 100644
--- a/tests/fsm/fsm_dealloc_test.c
+++ b/tests/fsm/fsm_dealloc_test.c
@@ -290,7 +290,7 @@ void obj_set_other(struct obj *a, struct obj *b)
obj_add_other(b, a);
}
-static struct scene *scene_alloc()
+static struct scene *scene_alloc(void)
{
struct scene *s = talloc_zero(ctx, struct scene);
s->use_count.talloc_object = s;
@@ -419,7 +419,7 @@ static void trigger_tests(void *loop_ctx)
}
}
-void test_osmo_fsm_term_safely()
+void test_osmo_fsm_term_safely(void)
{
fprintf(stderr, "\n\n%s()\n", __func__);
osmo_fsm_term_safely(true);
@@ -428,7 +428,7 @@ void test_osmo_fsm_term_safely()
fprintf(stderr, "\n\n%s() done\n", __func__);
}
-void test_osmo_fsm_set_dealloc_ctx()
+void test_osmo_fsm_set_dealloc_ctx(void)
{
fprintf(stderr, "\n\n%s()\n", __func__);
void *dealloc_ctx = talloc_named_const(ctx, 0, "fsm_dealloc");
diff --git a/tests/fsm/fsm_test.c b/tests/fsm/fsm_test.c
index 6fa0ae71..960042c7 100644
--- a/tests/fsm/fsm_test.c
+++ b/tests/fsm/fsm_test.c
@@ -172,7 +172,7 @@ static struct osmo_fsm_inst *foo(void)
return fi;
}
-static void test_id_api()
+static void test_id_api(void)
{
struct osmo_fsm_inst *fi;
@@ -280,7 +280,7 @@ const struct timeval fake_time_start_time = { 123, 456 };
osmo_timers_update(); \
} while (0)
-void fake_time_start()
+void fake_time_start(void)
{
struct timespec *clock_override;
@@ -301,7 +301,7 @@ static int timer_cb(struct osmo_fsm_inst *fi)
return 0;
}
-static void test_state_chg_keep_timer()
+static void test_state_chg_keep_timer(void)
{
struct osmo_fsm_inst *fi;
@@ -349,7 +349,7 @@ static void test_state_chg_keep_timer()
fprintf(stderr, "--- %s() done\n", __func__);
}
-static void test_state_chg_T()
+static void test_state_chg_T(void)
{
struct osmo_fsm_inst *fi;
diff --git a/tests/gad/gad_test.c b/tests/gad/gad_test.c
index ca8c4fa8..19be6a61 100644
--- a/tests/gad/gad_test.c
+++ b/tests/gad/gad_test.c
@@ -4,7 +4,7 @@
#include <osmocom/core/msgb.h>
#include <osmocom/gsm/gad.h>
-void test_gad_lat_lon_dec_enc_stability()
+void test_gad_lat_lon_dec_enc_stability(void)
{
uint32_t lat_enc;
uint32_t lon_enc;
@@ -49,7 +49,7 @@ struct osmo_gad gad_test_values[] = {
},
};
-void test_gad_enc_dec()
+void test_gad_enc_dec(void)
{
int i;
printf("--- %s\n", __func__);
@@ -115,7 +115,7 @@ loop_end:
}
}
-void test_gad_to_str()
+void test_gad_to_str(void)
{
int i;
printf("--- %s\n", __func__);
@@ -134,7 +134,7 @@ void test_gad_to_str()
}
}
-int main()
+int main(int argc, char **argv)
{
test_gad_lat_lon_dec_enc_stability();
test_gad_enc_dec();
diff --git a/tests/gb/gprs_bssgp_rim_test.c b/tests/gb/gprs_bssgp_rim_test.c
index e2d4f661..bb533f6d 100644
--- a/tests/gb/gprs_bssgp_rim_test.c
+++ b/tests/gb/gprs_bssgp_rim_test.c
@@ -60,7 +60,7 @@ void dump_rim_ri(struct bssgp_rim_routing_info *ri)
}
}
-static void test_bssgp_parse_rim_ri()
+static void test_bssgp_parse_rim_ri(void)
{
int rc;
struct bssgp_rim_routing_info result;
@@ -94,7 +94,7 @@ static void test_bssgp_parse_rim_ri()
printf("----- %s END\n", __func__);
}
-static void test_bssgp_create_rim_ri()
+static void test_bssgp_create_rim_ri(void)
{
int rc;
struct bssgp_rim_routing_info ri;
@@ -194,7 +194,7 @@ void dump_bssgp_ran_inf_req_rim_cont(struct bssgp_ran_inf_req_rim_cont *rim_cont
}
}
-static void test_bssgp_dec_ran_inf_req_rim_cont_nacc()
+static void test_bssgp_dec_ran_inf_req_rim_cont_nacc(void)
{
int rc;
struct bssgp_ran_inf_req_rim_cont rim_cont_dec;
@@ -212,7 +212,7 @@ static void test_bssgp_dec_ran_inf_req_rim_cont_nacc()
printf("----- %s END\n", __func__);
}
-static void test_bssgp_enc_ran_inf_req_rim_cont_nacc()
+static void test_bssgp_enc_ran_inf_req_rim_cont_nacc(void)
{
int rc;
struct bssgp_ran_inf_req_rim_cont rim_cont = { };
@@ -321,7 +321,7 @@ static void dump_bssgp_ran_inf_rim_cont(struct bssgp_ran_inf_rim_cont *rim_cont)
}
}
-static void test_bssgp_dec_ran_inf_rim_cont_nacc()
+static void test_bssgp_dec_ran_inf_rim_cont_nacc(void)
{
int rc;
struct bssgp_ran_inf_rim_cont rim_cont_dec;
@@ -346,7 +346,7 @@ static void test_bssgp_dec_ran_inf_rim_cont_nacc()
printf("----- %s END\n", __func__);
}
-static void test_bssgp_dec_ran_inf_rim_cont_err_nacc()
+static void test_bssgp_dec_ran_inf_rim_cont_err_nacc(void)
{
int rc;
struct bssgp_ran_inf_rim_cont rim_cont_dec;
@@ -364,7 +364,7 @@ static void test_bssgp_dec_ran_inf_rim_cont_err_nacc()
printf("----- %s END\n", __func__);
}
-static void test_bssgp_enc_ran_inf_rim_cont_nacc()
+static void test_bssgp_enc_ran_inf_rim_cont_nacc(void)
{
int rc;
struct bssgp_ran_inf_rim_cont rim_cont = { };
@@ -415,7 +415,7 @@ static void test_bssgp_enc_ran_inf_rim_cont_nacc()
printf("----- %s END\n", __func__);
}
-static void test_bssgp_enc_ran_inf_rim_cont_err_nacc()
+static void test_bssgp_enc_ran_inf_rim_cont_err_nacc(void)
{
int rc;
struct bssgp_ran_inf_rim_cont rim_cont = { };
@@ -458,7 +458,7 @@ static void dump_bssgp_ran_inf_ack_rim_cont(struct bssgp_ran_inf_ack_rim_cont *r
}
}
-static void test_bssgp_dec_ran_inf_ack_rim_cont()
+static void test_bssgp_dec_ran_inf_ack_rim_cont(void)
{
int rc;
struct bssgp_ran_inf_ack_rim_cont rim_cont_dec;
@@ -474,7 +474,7 @@ static void test_bssgp_dec_ran_inf_ack_rim_cont()
printf("----- %s END\n", __func__);
}
-static void test_bssgp_enc_ran_inf_ack_rim_cont()
+static void test_bssgp_enc_ran_inf_ack_rim_cont(void)
{
int rc;
struct bssgp_ran_inf_ack_rim_cont rim_cont = { };
@@ -513,7 +513,7 @@ void dump_bssgp_ran_inf_err_rim_cont(struct bssgp_ran_inf_err_rim_cont *rim_cont
}
}
-static void test_bssgp_dec_ran_inf_err_rim_cont()
+static void test_bssgp_dec_ran_inf_err_rim_cont(void)
{
int rc;
struct bssgp_ran_inf_err_rim_cont rim_cont_dec;
@@ -530,7 +530,7 @@ static void test_bssgp_dec_ran_inf_err_rim_cont()
printf("----- %s END\n", __func__);
}
-static void test_bssgp_enc_ran_inf_err_rim_cont()
+static void test_bssgp_enc_ran_inf_err_rim_cont(void)
{
int rc;
struct bssgp_ran_inf_err_rim_cont rim_cont = { };
@@ -578,7 +578,7 @@ void dump_bssgp_ran_inf_app_err_rim_cont(struct bssgp_ran_inf_app_err_rim_cont *
}
}
-static void test_bssgp_dec_ran_inf_app_err_rim_cont_nacc()
+static void test_bssgp_dec_ran_inf_app_err_rim_cont_nacc(void)
{
int rc;
struct bssgp_ran_inf_app_err_rim_cont rim_cont_dec;
@@ -597,7 +597,7 @@ static void test_bssgp_dec_ran_inf_app_err_rim_cont_nacc()
printf("----- %s END\n", __func__);
}
-static void test_bssgp_enc_ran_inf_app_err_rim_cont_nacc()
+static void test_bssgp_enc_ran_inf_app_err_rim_cont_nacc(void)
{
int rc;
struct bssgp_ran_inf_app_err_rim_cont rim_cont = { };
@@ -623,7 +623,7 @@ static void test_bssgp_enc_ran_inf_app_err_rim_cont_nacc()
printf("----- %s END\n", __func__);
}
-static void test_bssgp_dec_ran_inf_req_app_cont_nacc()
+static void test_bssgp_dec_ran_inf_req_app_cont_nacc(void)
{
int rc;
struct bssgp_ran_inf_req_app_cont_nacc app_cont_dec;
@@ -639,7 +639,7 @@ static void test_bssgp_dec_ran_inf_req_app_cont_nacc()
printf("----- %s END\n", __func__);
}
-static void test_bssgp_enc_ran_inf_req_app_cont_nacc()
+static void test_bssgp_enc_ran_inf_req_app_cont_nacc(void)
{
int rc;
struct bssgp_ran_inf_req_app_cont_nacc app_cont = { };
@@ -662,7 +662,7 @@ static void test_bssgp_enc_ran_inf_req_app_cont_nacc()
printf("----- %s END\n", __func__);
}
-static void test_bssgp_dec_ran_inf_app_cont_nacc()
+static void test_bssgp_dec_ran_inf_app_cont_nacc(void)
{
int rc;
struct bssgp_ran_inf_app_cont_nacc app_cont_dec;
@@ -682,7 +682,7 @@ static void test_bssgp_dec_ran_inf_app_cont_nacc()
printf("----- %s END\n", __func__);
}
-static void test_bssgp_enc_ran_inf_app_cont_nacc()
+static void test_bssgp_enc_ran_inf_app_cont_nacc(void)
{
int rc;
struct bssgp_ran_inf_app_cont_nacc app_cont = { };
@@ -721,7 +721,7 @@ static void test_bssgp_enc_ran_inf_app_cont_nacc()
printf("----- %s END\n", __func__);
}
-static void test_bssgp_dec_app_err_cont_nacc()
+static void test_bssgp_dec_app_err_cont_nacc(void)
{
int rc;
struct bssgp_app_err_cont_nacc app_cont_dec;
@@ -737,7 +737,7 @@ static void test_bssgp_dec_app_err_cont_nacc()
printf("----- %s END\n", __func__);
}
-static void test_bssgp_enc_app_err_cont_nacc()
+static void test_bssgp_enc_app_err_cont_nacc(void)
{
int rc;
struct bssgp_app_err_cont_nacc app_cont = { };
diff --git a/tests/gb/gprs_bssgp_test.c b/tests/gb/gprs_bssgp_test.c
index 43fc056c..775ae43b 100644
--- a/tests/gb/gprs_bssgp_test.c
+++ b/tests/gb/gprs_bssgp_test.c
@@ -173,7 +173,7 @@ static void test_bssgp_status(void)
printf("----- %s END\n", __func__);
}
-static void test_bssgp_bad_reset()
+static void test_bssgp_bad_reset(void)
{
struct msgb *msg;
uint16_t bvci_be = htons(2);
@@ -256,7 +256,7 @@ static void test_bssgp_flow_control_bvc(void)
printf("----- %s END\n", __func__);
}
-static void test_bssgp_msgb_copy()
+static void test_bssgp_msgb_copy(void)
{
struct msgb *msg, *msg2;
uint16_t bvci_be = htons(2);
diff --git a/tests/gb/gprs_ns2_vty.vty b/tests/gb/gprs_ns2_vty.vty
index 4be10d55..8db5fb50 100644
--- a/tests/gb/gprs_ns2_vty.vty
+++ b/tests/gb/gprs_ns2_vty.vty
@@ -40,11 +40,11 @@ OsmoNSdummy(config-ns-nse)# end
OsmoNSdummy# show ns
NSEI 01234: UDP, DEAD since 0d 0h 0m 0s
1 NS-VC:
- RECOVERING PERSIST sig_weight=1 data_weight=1 udp)[127.0.0.14]:42999<>[127.0.0.15]:9496 DEAD since 0d 0h 0m 0s
+ RECOVERING PERSIST sig_weight=1 data_weight=1 udp)[127.0.0.14]:42999<>[127.0.0.15]:9496 DEAD (cause: remote) since 0d 0h 0m 0s
UDP bind: 127.0.0.14:42999 DSCP: 0 Priority: 0
IP-SNS signalling weight: 1 data weight: 1
1 NS-VC:
- RECOVERING PERSIST sig_weight=1 data_weight=1 udp)[127.0.0.14]:42999<>[127.0.0.15]:9496 DEAD since 0d 0h 0m 0s
+ RECOVERING PERSIST sig_weight=1 data_weight=1 udp)[127.0.0.14]:42999<>[127.0.0.15]:9496 DEAD (cause: remote) since 0d 0h 0m 0s
OsmoNSdummy# configure terminal
OsmoNSdummy(config)# ns
OsmoNSdummy(config-ns)# nse 1234
@@ -54,15 +54,15 @@ OsmoNSdummy(config-ns-nse)# end
OsmoNSdummy# show ns
NSEI 01234: UDP, DEAD since 0d 0h 0m 0s
3 NS-VC:
- RECOVERING PERSIST sig_weight=1 data_weight=1 udp)[127.0.0.14]:42999<>[127.0.0.15]:9496 DEAD since 0d 0h 0m 0s
- RECOVERING PERSIST sig_weight=0 data_weight=9 udp)[127.0.0.14]:42999<>[127.0.0.16]:9496 DEAD since 0d 0h 0m 0s
- RECOVERING PERSIST sig_weight=0 data_weight=0 udp)[127.0.0.14]:42999<>[127.0.0.17]:9496 DEAD since 0d 0h 0m 0s
+ RECOVERING PERSIST sig_weight=1 data_weight=1 udp)[127.0.0.14]:42999<>[127.0.0.15]:9496 DEAD (cause: remote) since 0d 0h 0m 0s
+ RECOVERING PERSIST sig_weight=0 data_weight=9 udp)[127.0.0.14]:42999<>[127.0.0.16]:9496 DEAD (cause: remote) since 0d 0h 0m 0s
+ RECOVERING PERSIST sig_weight=0 data_weight=0 udp)[127.0.0.14]:42999<>[127.0.0.17]:9496 DEAD (cause: remote) since 0d 0h 0m 0s
UDP bind: 127.0.0.14:42999 DSCP: 0 Priority: 0
IP-SNS signalling weight: 1 data weight: 1
3 NS-VC:
- RECOVERING PERSIST sig_weight=1 data_weight=1 udp)[127.0.0.14]:42999<>[127.0.0.15]:9496 DEAD since 0d 0h 0m 0s
- RECOVERING PERSIST sig_weight=0 data_weight=9 udp)[127.0.0.14]:42999<>[127.0.0.16]:9496 DEAD since 0d 0h 0m 0s
- RECOVERING PERSIST sig_weight=0 data_weight=0 udp)[127.0.0.14]:42999<>[127.0.0.17]:9496 DEAD since 0d 0h 0m 0s
+ RECOVERING PERSIST sig_weight=1 data_weight=1 udp)[127.0.0.14]:42999<>[127.0.0.15]:9496 DEAD (cause: remote) since 0d 0h 0m 0s
+ RECOVERING PERSIST sig_weight=0 data_weight=9 udp)[127.0.0.14]:42999<>[127.0.0.16]:9496 DEAD (cause: remote) since 0d 0h 0m 0s
+ RECOVERING PERSIST sig_weight=0 data_weight=0 udp)[127.0.0.14]:42999<>[127.0.0.17]:9496 DEAD (cause: remote) since 0d 0h 0m 0s
OsmoNSdummy# configure terminal
OsmoNSdummy(config)# ns
OsmoNSdummy(config-ns)# nse 1234
diff --git a/tests/gb/gprs_ns_test.c b/tests/gb/gprs_ns_test.c
index 6ced6a3b..fc1ec605 100644
--- a/tests/gb/gprs_ns_test.c
+++ b/tests/gb/gprs_ns_test.c
@@ -420,7 +420,7 @@ static int expire_nsvc_timer(struct gprs_nsvc *nsvc)
return rc;
}
-static void test_nsvc()
+static void test_nsvc(void)
{
struct gprs_ns_inst *nsi = gprs_ns_instantiate(gprs_ns_callback, NULL);
struct sockaddr_in peer[1] = {{0},};
@@ -459,7 +459,7 @@ static void test_nsvc()
alarm(0);
}
-static void test_ignored_messages()
+static void test_ignored_messages(void)
{
struct gprs_ns_inst *nsi = gprs_ns_instantiate(gprs_ns_callback, NULL);
struct sockaddr_in peer[1] = {{0},};
@@ -486,7 +486,7 @@ static void test_ignored_messages()
nsi = NULL;
}
-static void test_bss_port_changes()
+static void test_bss_port_changes(void)
{
struct gprs_ns_inst *nsi = gprs_ns_instantiate(gprs_ns_callback, NULL);
struct sockaddr_in peer[4] = {{0},};
@@ -540,7 +540,7 @@ static void test_bss_port_changes()
nsi = NULL;
}
-static void test_bss_reset_ack()
+static void test_bss_reset_ack(void)
{
struct gprs_ns_inst *nsi = gprs_ns_instantiate(gprs_ns_callback, NULL);
struct sockaddr_in peer[4] = {{0},};
@@ -687,7 +687,7 @@ static void test_bss_reset_ack()
}
-static void test_sgsn_reset()
+static void test_sgsn_reset(void)
{
struct gprs_ns_inst *nsi = gprs_ns_instantiate(gprs_ns_callback, NULL);
struct sockaddr_in sgsn_peer= {0};
@@ -765,7 +765,7 @@ static void test_sgsn_reset()
nsi = NULL;
}
-static void test_sgsn_reset_invalid_state()
+static void test_sgsn_reset_invalid_state(void)
{
struct gprs_ns_inst *nsi = gprs_ns_instantiate(gprs_ns_callback, NULL);
struct sockaddr_in sgsn_peer= {0};
@@ -833,7 +833,7 @@ static void test_sgsn_reset_invalid_state()
nsi = NULL;
}
-static void test_sgsn_output()
+static void test_sgsn_output(void)
{
struct gprs_ns_inst *nsi = gprs_ns_instantiate(gprs_ns_callback, NULL);
struct sockaddr_in sgsn_peer= {0};
diff --git a/tests/gsm0408/gsm0408_test.c b/tests/gsm0408/gsm0408_test.c
index 38200c70..e82178d4 100644
--- a/tests/gsm0408/gsm0408_test.c
+++ b/tests/gsm0408/gsm0408_test.c
@@ -31,7 +31,7 @@
#include <osmocom/core/msgb.h>
-static const uint8_t csd_9600_v110_lv[] = { 0x07, 0xa1, 0xb8, 0x89, 0x21, 0x15, 0x63, 0x80 };
+static const uint8_t csd_9600_v110_lv[] = { 0x07, 0xa1, 0x88, 0x89, 0x21, 0x15, 0x63, 0x80 };
static const struct gsm_mncc_bearer_cap bcap_csd_9600_v110 = {
.transfer = GSM48_BCAP_ITCAP_UNR_DIG_INF,
@@ -90,7 +90,7 @@ static const struct bcap_test bcap_tests[] = {
{ speech_no3a_lv, &bcap_speech_no3a, "Speech, without octet 3a" },
};
-static int test_bearer_cap()
+static int test_bearer_cap(void)
{
struct gsm_mncc_bearer_cap bc;
int i, rc;
@@ -341,6 +341,171 @@ static void test_lai_encode_decode(void)
}
}
+static struct osmo_routing_area_id test_osmo_routing_area_id_items[] = {
+ {
+ .lac = {
+ .plmn = {
+ .mcc = 77,
+ .mnc = 121,
+ },
+ .lac = 666,
+ },
+ .rac = 5,
+ },
+ {
+ .lac = {
+ .plmn = {
+ .mcc = 84,
+ .mnc = 98,
+ },
+ .lac = 11,
+ },
+ .rac = 89,
+ },
+ {
+ .lac = {
+ .plmn = {
+ .mcc = 0,
+ .mnc = 0,
+ .mnc_3_digits = false,
+ /* expecting 000-00, BCD = 00 f0 00 */
+ },
+ .lac = 0,
+ },
+ .rac = 0,
+ },
+ {
+ .lac = {
+ .plmn = {
+ .mcc = 0,
+ .mnc = 0,
+ .mnc_3_digits = true,
+ /* expecting 000-000, BCD = 00 00 00 */
+ },
+ .lac = 0,
+ },
+ .rac = 0,
+ },
+ {
+ .lac = {
+ .plmn = {
+ .mcc = 999,
+ .mnc = 999,
+ },
+ .lac = 65535,
+ },
+ .rac = 255,
+ },
+ {
+ .lac = {
+ .plmn = {
+ .mcc = 1,
+ .mnc = 2,
+ .mnc_3_digits = false,
+ /* expecting 001-02, BCD = 00 f1 20 */
+ },
+ .lac = 23,
+ },
+ .rac = 42,
+ },
+ {
+ .lac = {
+ .plmn = {
+ .mcc = 1,
+ .mnc = 2,
+ .mnc_3_digits = true,
+ /* expecting 001-002, BCD = 00 21 00 */
+ },
+ .lac = 23,
+ },
+ .rac = 42,
+ },
+ {
+ .lac = {
+ .plmn = {
+ .mcc = 12,
+ .mnc = 34,
+ .mnc_3_digits = false,
+ /* expecting 012-34, BCD = 10 f2 43 */
+ },
+ .lac = 56,
+ },
+ .rac = 78,
+ },
+ {
+ .lac = {
+ .plmn = {
+ .mcc = 12,
+ .mnc = 34,
+ .mnc_3_digits = true,
+ /* expecting 012-034, BCD = 10 42 30 */
+ },
+ .lac = 23,
+ },
+ .rac = 42,
+ },
+ {
+ .lac = {
+ .plmn = {
+ .mcc = 123,
+ .mnc = 456,
+ .mnc_3_digits = false,
+ /* expecting 123-456, BCD = 21 63 54 (false flag has no effect) */
+ },
+ .lac = 23,
+ },
+ .rac = 42,
+ },
+ {
+ .lac = {
+ .plmn = {
+ .mcc = 123,
+ .mnc = 456,
+ .mnc_3_digits = true,
+ /* expecting 123-456, BCD = 21 63 54 (same) */
+ },
+ .lac = 23,
+ },
+ .rac = 42,
+ },
+};
+
+static inline void dump_osmo_routing_area_id(const struct osmo_routing_area_id *raid)
+{
+ printf("%s%s", osmo_rai_name2(raid), raid->lac.plmn.mnc_3_digits ? " (3-digit MNC)" : "");
+}
+
+static inline void check_osmo_routing_area_id(const struct osmo_routing_area_id *raid)
+{
+ uint8_t buf[sizeof(struct gsm48_ra_id)] = {};
+ struct osmo_routing_area_id raid0 = {};
+ int rc;
+
+ printf("RA ID: ");
+ dump_osmo_routing_area_id(raid);
+
+ rc = osmo_routing_area_id_encode_buf(buf, sizeof(buf), raid);
+ printf("osmo_routing_area_id_encode_buf(): %src=%d\n", osmo_hexdump(buf, sizeof(buf)), rc);
+
+ rc = osmo_routing_area_id_decode(&raid0, buf, sizeof(buf));
+ printf("osmo_routing_area_id_decode(): ");
+ dump_osmo_routing_area_id(&raid0);
+ printf(" rc=%d\n", rc);
+
+ if (osmo_rai_cmp(raid, &raid0))
+ printf("FAIL\n");
+ else
+ printf("ok\n");
+}
+
+static void test_osmo_routing_area_id(void)
+{
+ int i;
+ printf("==%s()==\n", __func__);
+ for (i = 0; i < ARRAY_SIZE(test_osmo_routing_area_id_items); i++)
+ check_osmo_routing_area_id(&test_osmo_routing_area_id_items[i]);
+}
+
static void dump_cm3(struct gsm48_classmark3 *cm3)
{
printf("mult_band_supp=%02x\n", cm3->mult_band_supp);
@@ -1154,7 +1319,7 @@ struct mobile_identity_tc mobile_identity_tests[] = {
},
};
-void test_struct_mobile_identity()
+void test_struct_mobile_identity(void)
{
struct mobile_identity_tc *t;
printf("%s()\n", __func__);
@@ -1316,7 +1481,7 @@ static const struct bcd_number_test {
},
};
-static void test_bcd_number_encode_decode()
+static void test_bcd_number_encode_decode(void)
{
const struct bcd_number_test *test;
uint8_t buf_enc[0xff] = { 0xff };
@@ -1550,7 +1715,7 @@ static void test_random_range_encoding(int range, int max_arfcn_num)
}
}
-static void test_range_encoding()
+static void test_range_encoding(void)
{
int *arfcns;
int arfcns_num = 0;
@@ -1611,7 +1776,7 @@ static int range512[] = {
__FILE__, __LINE__, (int) res, # cmp, (int) wanted); \
}
-static void test_arfcn_filter()
+static void test_arfcn_filter(void)
{
int arfcns[50], i, res, f0_included;
for (i = 0; i < ARRAY_SIZE(arfcns); ++i)
@@ -1644,7 +1809,7 @@ static void test_arfcn_filter()
VERIFY(arfcns[i], ==, ((i + 1) * 2) - 1);
}
-static void test_print_encoding()
+static void test_print_encoding(void)
{
int rc;
int w[17];
@@ -1669,7 +1834,7 @@ static void test_print_encoding()
printf("Range512: %s\n", osmo_hexdump(chan_list, ARRAY_SIZE(chan_list)));
}
-static void test_si_range_helpers()
+static void test_si_range_helpers(void)
{
int ws[(sizeof(freqs1)/sizeof(freqs1[0]))];
int i, f0 = 0xFFFFFF;
@@ -1708,7 +1873,7 @@ static void test_si_range_helpers()
VERIFY(f0, ==, 1);
}
-static void test_power_ctrl()
+static void test_power_ctrl(void)
{
int8_t rc8;
int rc;
@@ -1750,7 +1915,7 @@ static void test_power_ctrl()
VERIFY(rc, <, 0);
}
-static void test_rach_tx_integer_raw2val()
+static void test_rach_tx_integer_raw2val(void)
{
unsigned int raw;
for (raw = 0; raw <= 0x0f; raw++) {
@@ -1760,6 +1925,52 @@ static void test_rach_tx_integer_raw2val()
}
}
+static void test_gsm_gsmtime2fn(void)
+{
+ struct gsm_time gsm_time;
+ uint32_t fn;
+ uint32_t fn_recovered;
+
+ for (fn = 0; fn < 42432; fn++) {
+ gsm_time.t1 = (fn / 1326) % 32;
+ gsm_time.t2 = fn % 26;
+ gsm_time.t3 = fn % 51;
+
+ fn_recovered = gsm_gsmtime2fn(&gsm_time);
+
+ if (fn_recovered != fn) {
+ printf(" Wrong frame number computed! t1=%d, t2=%d, t3=%d ==> fn=%d, expected fn=%d\n",
+ gsm_time.t1, gsm_time.t2, gsm_time.t3, fn_recovered, fn);
+ OSMO_ASSERT(false);
+ }
+ }
+}
+
+static void test_gsm_rfn2fn(void)
+{
+ unsigned int i;
+ struct {
+ uint32_t curr_fn;
+ uint16_t rfn;
+ uint32_t exp_fn;
+ } input[] = {
+ { .curr_fn = 0, .rfn = 0, .exp_fn = 0 },
+ { .curr_fn = 0, .rfn = 4, .exp_fn = 4 },
+ { .curr_fn = 2229729, .rfn = 23322, .exp_fn = 2229786 },
+ { .curr_fn = 2229777, .rfn = 23322, .exp_fn = 2229786 },
+ { .curr_fn = 1320458, .rfn = 5070, .exp_fn = 1320462 },
+ };
+
+ for (i = 0; i < ARRAY_SIZE(input); i++) {
+ uint32_t fn = gsm_rfn2fn(input[i].rfn, input[i].curr_fn);
+ if (fn != input[i].exp_fn) {
+ printf("Wrong frame number computed! curr_fn=%u, rfn=%u ==> fn=%u, expected fn=%u\n",
+ input[i].curr_fn, input[i].rfn, fn, input[i].exp_fn);
+ OSMO_ASSERT(false);
+ }
+ }
+}
+
int main(int argc, char **argv)
{
test_bearer_cap();
@@ -1771,6 +1982,7 @@ int main(int argc, char **argv)
test_bcd_number_encode_decode();
test_ra_cap();
test_lai_encode_decode();
+ test_osmo_routing_area_id();
test_decode_classmark3();
test_si_range_helpers();
@@ -1779,6 +1991,8 @@ int main(int argc, char **argv)
test_range_encoding();
test_power_ctrl();
test_rach_tx_integer_raw2val();
+ test_gsm_gsmtime2fn();
+ test_gsm_rfn2fn();
return EXIT_SUCCESS;
}
diff --git a/tests/gsm0408/gsm0408_test.ok b/tests/gsm0408/gsm0408_test.ok
index dc48f84a..b966865d 100644
--- a/tests/gsm0408/gsm0408_test.ok
+++ b/tests/gsm0408/gsm0408_test.ok
@@ -386,6 +386,40 @@ RA test...passed
Encoded 21 63 54 00 17
gsm48_decode_lai2() gives 123-456-23 (3-digit MNC)
passed
+==test_osmo_routing_area_id()==
+RA ID: 077-121-666-5osmo_routing_area_id_encode_buf(): 70 17 21 02 9a 05 rc=6
+osmo_routing_area_id_decode(): 077-121-666-5 (3-digit MNC) rc=6
+ok
+RA ID: 084-98-11-89osmo_routing_area_id_encode_buf(): 80 f4 89 00 0b 59 rc=6
+osmo_routing_area_id_decode(): 084-98-11-89 rc=6
+ok
+RA ID: 000-00-0-0osmo_routing_area_id_encode_buf(): 00 f0 00 00 00 00 rc=6
+osmo_routing_area_id_decode(): 000-00-0-0 rc=6
+ok
+RA ID: 000-000-0-0 (3-digit MNC)osmo_routing_area_id_encode_buf(): 00 00 00 00 00 00 rc=6
+osmo_routing_area_id_decode(): 000-000-0-0 (3-digit MNC) rc=6
+ok
+RA ID: 999-999-65535-255osmo_routing_area_id_encode_buf(): 99 99 99 ff ff ff rc=6
+osmo_routing_area_id_decode(): 999-999-65535-255 (3-digit MNC) rc=6
+ok
+RA ID: 001-02-23-42osmo_routing_area_id_encode_buf(): 00 f1 20 00 17 2a rc=6
+osmo_routing_area_id_decode(): 001-02-23-42 rc=6
+ok
+RA ID: 001-002-23-42 (3-digit MNC)osmo_routing_area_id_encode_buf(): 00 21 00 00 17 2a rc=6
+osmo_routing_area_id_decode(): 001-002-23-42 (3-digit MNC) rc=6
+ok
+RA ID: 012-34-56-78osmo_routing_area_id_encode_buf(): 10 f2 43 00 38 4e rc=6
+osmo_routing_area_id_decode(): 012-34-56-78 rc=6
+ok
+RA ID: 012-034-23-42 (3-digit MNC)osmo_routing_area_id_encode_buf(): 10 42 30 00 17 2a rc=6
+osmo_routing_area_id_decode(): 012-034-23-42 (3-digit MNC) rc=6
+ok
+RA ID: 123-456-23-42osmo_routing_area_id_encode_buf(): 21 63 54 00 17 2a rc=6
+osmo_routing_area_id_decode(): 123-456-23-42 (3-digit MNC) rc=6
+ok
+RA ID: 123-456-23-42 (3-digit MNC)osmo_routing_area_id_encode_buf(): 21 63 54 00 17 2a rc=6
+osmo_routing_area_id_decode(): 123-456-23-42 (3-digit MNC) rc=6
+ok
=====cm3_1=====
mult_band_supp=06
a5_bits=00
diff --git a/tests/gsm0502/gsm0502_test.c b/tests/gsm0502/gsm0502_test.c
index f4f6530e..7768abb0 100644
--- a/tests/gsm0502/gsm0502_test.c
+++ b/tests/gsm0502/gsm0502_test.c
@@ -86,7 +86,7 @@ uint32_t facch_h1_fn_samples[] = { 500728, 500771, 500797, 500841, 500875, 50091
502782, 502825, 502869, 502903, 502955, 502999
};
-static void test_gsm0502_fn_remap()
+static void test_gsm0502_fn_remap(void)
{
unsigned int i;
uint32_t fn_begin;
@@ -148,8 +148,28 @@ static void test_gsm0502_fn_remap()
printf("\n");
}
+static void test_gsm0502_fncmp(void)
+{
+ OSMO_ASSERT(gsm0502_fncmp(1337, 1337) == 0);
+ OSMO_ASSERT(gsm0502_fncmp(42, 1337) == -1);
+ OSMO_ASSERT(gsm0502_fncmp(1337, 42) == 1);
+ OSMO_ASSERT(gsm0502_fncmp(42, 0) == 1);
+
+ /* 2715642 is very close to the Fn period (GSM_TDMA_HYPERFRAME) */
+ OSMO_ASSERT(gsm0502_fncmp(2715642, 42) == -1);
+ OSMO_ASSERT(gsm0502_fncmp(42, 2715642) == 1);
+ OSMO_ASSERT(gsm0502_fncmp(0, 2715642) == 1);
+
+ /* 1357824 is half of the Fn period (GSM_TDMA_HYPERFRAME) */
+ OSMO_ASSERT(gsm0502_fncmp(1357820, 1357824) == -1);
+ OSMO_ASSERT(gsm0502_fncmp(1357820, 1357825) == -1);
+ OSMO_ASSERT(gsm0502_fncmp(1357824, 1357820) == 1);
+ OSMO_ASSERT(gsm0502_fncmp(1357825, 1357820) == 1);
+}
+
int main(int argc, char **argv)
{
test_gsm0502_fn_remap();
+ test_gsm0502_fncmp();
return EXIT_SUCCESS;
}
diff --git a/tests/gsm0808/gsm0808_test.c b/tests/gsm0808/gsm0808_test.c
index b3aa3ab8..aa086a23 100644
--- a/tests/gsm0808/gsm0808_test.c
+++ b/tests/gsm0808/gsm0808_test.c
@@ -111,7 +111,7 @@ static void test_create_layer3(void)
msgb_free(in_msg);
}
-static void test_create_layer3_aoip()
+static void test_create_layer3_aoip(void)
{
static const uint8_t res[] = {
0x00, 0x17, 0x57, 0x05, 0x08, 0x00, 0x77, 0x62,
@@ -148,7 +148,7 @@ static void test_create_layer3_aoip()
msgb_free(in_msg);
}
-static void test_create_reset()
+static void test_create_reset(void)
{
static const uint8_t res[] = { 0x00, 0x04, 0x30, 0x04, 0x01, 0x20 };
struct msgb *msg;
@@ -159,7 +159,7 @@ static void test_create_reset()
msgb_free(msg);
}
-static void test_create_reset_ack()
+static void test_create_reset_ack(void)
{
static const uint8_t res[] = { 0x00, 0x01, 0x31 };
struct msgb *msg;
@@ -171,7 +171,7 @@ static void test_create_reset_ack()
}
-static void test_create_clear_command()
+static void test_create_clear_command(void)
{
static const uint8_t res[] = { 0x20, 0x04, 0x01, 0x23 };
struct msgb *msg;
@@ -182,7 +182,7 @@ static void test_create_clear_command()
msgb_free(msg);
}
-static void test_create_clear_command2()
+static void test_create_clear_command2(void)
{
static const uint8_t res[] = { 0x00, 0x04, 0x20, 0x04, 0x01, 0x23 };
struct msgb *msg;
@@ -193,7 +193,7 @@ static void test_create_clear_command2()
msgb_free(msg);
}
-static void test_create_clear_command2_csfb()
+static void test_create_clear_command2_csfb(void)
{
static const uint8_t res[] = { 0x00, 0x05, 0x20, 0x04, 0x01, 0x23, 0x8F };
struct msgb *msg;
@@ -204,7 +204,7 @@ static void test_create_clear_command2_csfb()
msgb_free(msg);
}
-static void test_create_clear_complete()
+static void test_create_clear_complete(void)
{
static const uint8_t res[] = { 0x00, 0x01, 0x21 };
struct msgb *msg;
@@ -215,7 +215,7 @@ static void test_create_clear_complete()
msgb_free(msg);
}
-static void test_create_cipher()
+static void test_create_cipher(void)
{
static const uint8_t res[] =
{ 0x00, 0x0c, 0x53, 0x0a, 0x09, 0x03, 0xaa,
@@ -255,7 +255,7 @@ static void test_create_cipher()
msgb_free(msg);
}
-static void test_create_cipher_complete()
+static void test_create_cipher_complete(void)
{
static const uint8_t res1[] = {
0x00, 0x08, 0x55, 0x20, 0x03, 0x23, 0x42, 0x21, 0x2c, 0x04 };
@@ -312,7 +312,7 @@ static inline void parse_cipher_reject(struct msgb *msg, uint8_t exp)
rc, exp, OSMO_BIT_PRINT(exp), msgb_hexdump(msg));
}
-static void test_create_cipher_reject()
+static void test_create_cipher_reject(void)
{
static const uint8_t res[] = { 0x00, 0x04, 0x59, 0x04, 0x01, 0x23 };
enum gsm0808_cause cause = GSM0808_CAUSE_CCCH_OVERLOAD;
@@ -327,7 +327,7 @@ static void test_create_cipher_reject()
msgb_free(msg);
}
-static void test_create_cipher_reject_ext()
+static void test_create_cipher_reject_ext(void)
{
static const uint8_t res[] = { 0x00, 0x05, 0x59, 0x04, 0x02, 0xd0, 0xFA };
uint8_t cause = 0xFA;
@@ -342,7 +342,7 @@ static void test_create_cipher_reject_ext()
msgb_free(msg);
}
-static void test_create_cm_u()
+static void test_create_cm_u(void)
{
static const uint8_t res[] = {
0x00, 0x07, 0x54, 0x12, 0x01, 0x23, 0x13, 0x01, 0x42 };
@@ -364,7 +364,7 @@ static void test_create_cm_u()
msgb_free(msg);
}
-static void test_create_sapi_reject()
+static void test_create_sapi_reject(void)
{
static const uint8_t res[] = { 0x00, 0x06, 0x25, 0x18, 0x03, 0x04, 0x01, 0x25 };
struct msgb *msg;
@@ -375,7 +375,7 @@ static void test_create_sapi_reject()
msgb_free(msg);
}
-static void test_dec_confusion()
+static void test_dec_confusion(void)
{
static const uint8_t hex[] =
{ 0x26, 0x04, 0x01, 0x52, 0x1f, 0x07, 0x00, 0xff, 0x00, 0x03, 0x25, 0x03, 0x25 };
@@ -425,7 +425,7 @@ static void test_dec_confusion()
}
/* Test Perform Location Report SYS#5891 */
-static void test_dec_perform_location_report_sys5891()
+static void test_dec_perform_location_report_sys5891(void)
{
/* Message Type Perform Location Request
Location Type
@@ -477,7 +477,7 @@ static void test_dec_perform_location_report_sys5891()
OSMO_ASSERT(rc == 5);
}
-static void test_create_ass()
+static void test_create_ass(void)
{
static const uint8_t res1[] =
{ 0x00, 0x0a, 0x01, 0x0b, 0x04, 0x01, 0x0b, 0xa1, 0x25, 0x01, 0x00,
@@ -527,7 +527,7 @@ static void test_create_ass()
msgb_free(msg);
}
-static void test_create_ass2()
+static void test_create_ass2(void)
{
static const uint8_t res[] = {
BSSAP_MSG_BSS_MANAGEMENT,
@@ -604,7 +604,7 @@ static void test_create_ass2()
msgb_free(msg);
}
-static void test_create_ass_compl()
+static void test_create_ass_compl(void)
{
static const uint8_t res1[] = {
0x00, 0x09, 0x02, 0x15, 0x23, 0x21, 0x42, 0x2c,
@@ -623,7 +623,7 @@ static void test_create_ass_compl()
msgb_free(msg);
}
-static void test_create_ass_compl_aoip()
+static void test_create_ass_compl_aoip(void)
{
struct sockaddr_storage ss;
struct sockaddr_in sin;
@@ -660,7 +660,7 @@ static void test_create_ass_compl_aoip()
msgb_free(msg);
}
-static void test_create_ass_fail()
+static void test_create_ass_fail(void)
{
static const uint8_t res1[] = { 0x00, 0x04, 0x03, 0x04, 0x01, 0x23 };
static const uint8_t res2[] = {
@@ -678,7 +678,7 @@ static void test_create_ass_fail()
msgb_free(msg);
}
-static void test_create_ass_fail_aoip()
+static void test_create_ass_fail_aoip(void)
{
static const uint8_t res1[] =
{ 0x00, 0x0d, 0x03, 0x04, 0x01, 0x23, GSM0808_IE_SPEECH_CODEC_LIST,
@@ -704,7 +704,7 @@ static void test_create_ass_fail_aoip()
msgb_free(msg);
}
-static void test_create_clear_rqst()
+static void test_create_clear_rqst(void)
{
static const uint8_t res[] = { 0x00, 0x04, 0x22, 0x04, 0x01, 0x23 };
struct msgb *msg;
@@ -715,7 +715,7 @@ static void test_create_clear_rqst()
msgb_free(msg);
}
-static void test_create_paging()
+static void test_create_paging(void)
{
static const uint8_t res[] =
{ 0x00, 0x10, 0x52, 0x08, 0x08, 0x09, 0x10, 0x10, 0x00, 0x00, 0x00,
@@ -755,7 +755,7 @@ static void test_create_paging()
msgb_free(msg);
}
-static void test_create_dtap()
+static void test_create_dtap(void)
{
static const uint8_t res[] = { 0x01, 0x03, 0x02, 0x23, 0x42 };
struct msgb *msg, *l3;
@@ -772,7 +772,7 @@ static void test_create_dtap()
msgb_free(l3);
}
-static void test_prepend_dtap()
+static void test_prepend_dtap(void)
{
static const uint8_t res[] = { 0x01, 0x03, 0x02, 0x23, 0x42 };
struct msgb *in_msg;
@@ -789,7 +789,7 @@ static void test_prepend_dtap()
msgb_free(in_msg);
}
-static void test_enc_dec_lcls()
+static void test_enc_dec_lcls(void)
{
static const uint8_t res[] = {
GSM0808_IE_GLOBAL_CALL_REF,
@@ -867,7 +867,7 @@ static void test_enc_dec_lcls()
msgb_free(msg);
}
-static void test_enc_dec_aoip_trasp_addr_v4()
+static void test_enc_dec_aoip_trasp_addr_v4(void)
{
struct sockaddr_storage enc_addr;
struct sockaddr_storage dec_addr;
@@ -895,7 +895,7 @@ static void test_enc_dec_aoip_trasp_addr_v4()
msgb_free(msg);
}
-static void test_enc_dec_aoip_trasp_addr_v6()
+static void test_enc_dec_aoip_trasp_addr_v6(void)
{
struct sockaddr_storage enc_addr;
struct sockaddr_storage dec_addr;
@@ -924,7 +924,7 @@ static void test_enc_dec_aoip_trasp_addr_v6()
msgb_free(msg);
}
-static void test_enc_aoip_trasp_addr_msg_too_small()
+static void test_enc_aoip_trasp_addr_msg_too_small(void)
{
struct msgb *msg;
struct sockaddr_storage enc_addr;
@@ -946,7 +946,7 @@ static void test_enc_aoip_trasp_addr_msg_too_small()
msgb_free(msg);
}
-static void test_gsm0808_enc_dec_speech_codec()
+static void test_gsm0808_enc_dec_speech_codec(void)
{
struct gsm0808_speech_codec enc_sc = {
.pi = true,
@@ -959,7 +959,7 @@ static void test_gsm0808_enc_dec_speech_codec()
int rc_dec;
msg = msgb_alloc(1024, "output buffer");
- rc_enc = gsm0808_enc_speech_codec(msg, &enc_sc);
+ rc_enc = gsm0808_enc_speech_codec2(msg, &enc_sc);
OSMO_ASSERT(rc_enc == 3);
rc_dec = gsm0808_dec_speech_codec(&dec_sc, msg->data + 2, msg->len - 2);
@@ -971,7 +971,7 @@ static void test_gsm0808_enc_dec_speech_codec()
}
-static void test_gsm0808_enc_dec_speech_codec_with_cfg()
+static void test_gsm0808_enc_dec_speech_codec_with_cfg(void)
{
struct gsm0808_speech_codec enc_sc = {
.pi = true,
@@ -985,7 +985,7 @@ static void test_gsm0808_enc_dec_speech_codec_with_cfg()
int rc_dec;
msg = msgb_alloc(1024, "output buffer");
- rc_enc = gsm0808_enc_speech_codec(msg, &enc_sc);
+ rc_enc = gsm0808_enc_speech_codec2(msg, &enc_sc);
OSMO_ASSERT(rc_enc == 5);
rc_dec = gsm0808_dec_speech_codec(&dec_sc, msg->data + 2, msg->len - 2);
@@ -996,7 +996,7 @@ static void test_gsm0808_enc_dec_speech_codec_with_cfg()
msgb_free(msg);
}
-static void test_gsm0808_enc_dec_speech_codec_ext_with_cfg()
+static void test_gsm0808_enc_dec_speech_codec_ext_with_cfg(void)
{
struct gsm0808_speech_codec enc_sc = {
.pi = true,
@@ -1010,7 +1010,7 @@ static void test_gsm0808_enc_dec_speech_codec_ext_with_cfg()
int rc_dec;
msg = msgb_alloc(1024, "output buffer");
- rc_enc = gsm0808_enc_speech_codec(msg, &enc_sc);
+ rc_enc = gsm0808_enc_speech_codec2(msg, &enc_sc);
OSMO_ASSERT(rc_enc == 5);
rc_dec = gsm0808_dec_speech_codec(&dec_sc, msg->data + 2, msg->len - 2);
@@ -1021,7 +1021,7 @@ static void test_gsm0808_enc_dec_speech_codec_ext_with_cfg()
msgb_free(msg);
}
-static void test_gsm0808_enc_dec_speech_codec_list()
+static void test_gsm0808_enc_dec_speech_codec_list(void)
{
struct gsm0808_speech_codec_list enc_scl = {
.codec = {
@@ -1053,7 +1053,7 @@ static void test_gsm0808_enc_dec_speech_codec_list()
int rc_dec;
msg = msgb_alloc(1024, "output buffer");
- rc_enc = gsm0808_enc_speech_codec_list(msg, &enc_scl);
+ rc_enc = gsm0808_enc_speech_codec_list2(msg, &enc_scl);
OSMO_ASSERT(rc_enc == 9);
rc_dec = gsm0808_dec_speech_codec_list(&dec_scl, msg->data + 2, msg->len - 2);
@@ -1064,7 +1064,7 @@ static void test_gsm0808_enc_dec_speech_codec_list()
msgb_free(msg);
}
-static void test_gsm0808_enc_dec_empty_speech_codec_list()
+static void test_gsm0808_enc_dec_empty_speech_codec_list(void)
{
struct gsm0808_speech_codec_list enc_scl = {
.len = 0,
@@ -1075,7 +1075,7 @@ static void test_gsm0808_enc_dec_empty_speech_codec_list()
int rc_dec;
msg = msgb_alloc(1024, "output buffer");
- rc_enc = gsm0808_enc_speech_codec_list(msg, &enc_scl);
+ rc_enc = gsm0808_enc_speech_codec_list2(msg, &enc_scl);
OSMO_ASSERT(rc_enc == 2);
rc_dec = gsm0808_dec_speech_codec_list(&dec_scl, msg->data + 2, msg->len - 2);
@@ -1086,7 +1086,89 @@ static void test_gsm0808_enc_dec_empty_speech_codec_list()
msgb_free(msg);
}
-static void test_gsm0808_enc_dec_channel_type()
+static void test_gsm0808_enc_dec_channel_type_data(void)
+{
+ struct gsm0808_channel_type enc_ct = {
+ .ch_indctr = GSM0808_CHAN_DATA,
+ .ch_rate_type = GSM0808_DATA_HALF_PREF,
+
+ .data_transparent = true,
+ .data_rate = GSM0808_DATA_RATE_TRANSP_4k8,
+ };
+ struct gsm0808_channel_type dec_ct = {};
+ struct msgb *msg;
+ uint8_t ct_enc_expected[] = { GSM0808_IE_CHANNEL_TYPE,
+ 0x03, 0x02, 0x0b, 0x11,
+ };
+ uint8_t rc_enc;
+ int rc_dec;
+
+ msg = msgb_alloc(1024, "output buffer");
+ rc_enc = gsm0808_enc_channel_type(msg, &enc_ct);
+ OSMO_ASSERT(rc_enc == 5);
+ if (memcmp(ct_enc_expected, msg->data, msg->len)) {
+ printf(" got: %s\n", osmo_hexdump(msg->data, msg->len));
+ printf("expect: %s\n", osmo_hexdump(ct_enc_expected, sizeof(ct_enc_expected)));
+ OSMO_ASSERT(false);
+ }
+
+ rc_dec = gsm0808_dec_channel_type(&dec_ct, msg->data + 2, msg->len - 2);
+ OSMO_ASSERT(rc_dec == 3);
+ OSMO_ASSERT(dec_ct.ch_indctr == enc_ct.ch_indctr);
+ OSMO_ASSERT(dec_ct.ch_rate_type == enc_ct.ch_rate_type);
+ OSMO_ASSERT(dec_ct.data_transparent == enc_ct.data_transparent);
+ OSMO_ASSERT(dec_ct.data_rate == enc_ct.data_rate);
+ OSMO_ASSERT(dec_ct.data_rate_allowed_is_set == enc_ct.data_rate_allowed_is_set);
+ OSMO_ASSERT(dec_ct.data_asym_pref_is_set == enc_ct.data_asym_pref_is_set);
+
+ msgb_free(msg);
+}
+
+static void test_gsm0808_enc_dec_channel_type_data_asym_pref(void)
+{
+ struct gsm0808_channel_type enc_ct = {
+ .ch_indctr = GSM0808_CHAN_DATA,
+ .ch_rate_type = GSM0808_DATA_HALF_PREF,
+
+ .data_transparent = false,
+ .data_rate = GSM0808_DATA_RATE_NON_TRANSP_6k0,
+ .data_rate_allowed_is_set = true,
+ .data_rate_allowed = GSM0808_DATA_RATE_NON_TRANSP_ALLOWED_6k0
+ | GSM0808_DATA_RATE_NON_TRANSP_ALLOWED_12k0
+ | GSM0808_DATA_RATE_NON_TRANSP_ALLOWED_14k5,
+ .data_asym_pref_is_set = true,
+ .data_asym_pref = GSM0808_CT_ASYM_PREF_UL,
+ };
+ struct gsm0808_channel_type dec_ct = {};
+ struct msgb *msg;
+ uint8_t ct_enc_expected[] = { GSM0808_IE_CHANNEL_TYPE,
+ 0x05, 0x02, 0x0b, 0xd1, 0x8b, 0x20,
+ };
+ uint8_t rc_enc;
+ int rc_dec;
+
+ msg = msgb_alloc(1024, "output buffer");
+ rc_enc = gsm0808_enc_channel_type(msg, &enc_ct);
+ OSMO_ASSERT(rc_enc == 7);
+ if (memcmp(ct_enc_expected, msg->data, msg->len)) {
+ printf(" got: %s\n", osmo_hexdump(msg->data, msg->len));
+ printf("expect: %s\n", osmo_hexdump(ct_enc_expected, sizeof(ct_enc_expected)));
+ OSMO_ASSERT(false);
+ }
+
+ rc_dec = gsm0808_dec_channel_type(&dec_ct, msg->data + 2, msg->len - 2);
+ OSMO_ASSERT(rc_dec == 5);
+ OSMO_ASSERT(dec_ct.ch_indctr == enc_ct.ch_indctr);
+ OSMO_ASSERT(dec_ct.ch_rate_type == enc_ct.ch_rate_type);
+ OSMO_ASSERT(dec_ct.data_transparent == enc_ct.data_transparent);
+ OSMO_ASSERT(dec_ct.data_rate == enc_ct.data_rate);
+ OSMO_ASSERT(dec_ct.data_rate_allowed_is_set == enc_ct.data_rate_allowed_is_set);
+ OSMO_ASSERT(dec_ct.data_asym_pref_is_set == enc_ct.data_asym_pref_is_set);
+
+ msgb_free(msg);
+}
+
+static void test_gsm0808_enc_dec_channel_type_speech(void)
{
struct gsm0808_channel_type enc_ct = {
.ch_indctr = GSM0808_CHAN_SPEECH,
@@ -1117,7 +1199,65 @@ static void test_gsm0808_enc_dec_channel_type()
msgb_free(msg);
}
-static void test_gsm0808_enc_dec_encrypt_info()
+static void test_gsm0808_enc_dec_channel_type_sign(void)
+{
+ struct gsm0808_channel_type enc_ct = {
+ .ch_indctr = GSM0808_CHAN_SIGN,
+ .ch_rate_type = GSM0808_SIGN_FULL_PREF_NO_CHANGE,
+ };
+ struct gsm0808_channel_type dec_ct = {};
+ struct msgb *msg;
+ uint8_t ct_enc_expected[] = { GSM0808_IE_CHANNEL_TYPE,
+ 0x03, 0x03, 0x1a, 0x00
+ };
+ uint8_t rc_enc;
+ int rc_dec;
+
+ msg = msgb_alloc(1024, "output buffer");
+ rc_enc = gsm0808_enc_channel_type(msg, &enc_ct);
+ OSMO_ASSERT(rc_enc == 5);
+ OSMO_ASSERT(memcmp(ct_enc_expected, msg->data, msg->len) == 0);
+
+ rc_dec = gsm0808_dec_channel_type(&dec_ct, msg->data + 2, msg->len - 2);
+ OSMO_ASSERT(rc_dec == 2);
+ OSMO_ASSERT(enc_ct.ch_indctr == dec_ct.ch_indctr);
+ OSMO_ASSERT(enc_ct.ch_rate_type == dec_ct.ch_rate_type);
+
+ msgb_free(msg);
+}
+
+static void test_gsm0808_dec_channel_type_err(void)
+{
+ struct gsm0808_channel_type ct;
+ int rc;
+
+ /* Unknown channel indicator */
+ const uint8_t hex1[] = { 0x05, 0x0b, 0xa1, 0x25 };
+ rc = gsm0808_dec_channel_type(&ct, hex1, sizeof(hex1));
+ OSMO_ASSERT(rc == -ENOTSUP);
+
+ /* Data: ext in Octet 5 with transparent service */
+ const uint8_t hex2[] = { 0x02, 0x0b, 0x80, 0x00 };
+ rc = gsm0808_dec_channel_type(&ct, hex2, sizeof(hex2));
+ OSMO_ASSERT(rc == -EINVAL);
+
+ /* Data: ext in Octet 5, but too short */
+ const uint8_t hex3[] = { 0x02, 0x0b, 0xc0 };
+ rc = gsm0808_dec_channel_type(&ct, hex3, sizeof(hex3));
+ OSMO_ASSERT(rc == -EOVERFLOW);
+
+ /* Data: ext in Octet 5a, but too short */
+ const uint8_t hex4[] = { 0x02, 0x0b, 0xc0, 0x80 };
+ rc = gsm0808_dec_channel_type(&ct, hex4, sizeof(hex4));
+ OSMO_ASSERT(rc == -EOVERFLOW);
+
+ /* Speech: extension bit is set in last byte */
+ const uint8_t hex5[] = { 0x01, 0x0b, 0xa1, 0xa5 };
+ rc = gsm0808_dec_channel_type(&ct, hex5, sizeof(hex5));
+ OSMO_ASSERT(rc == -EOVERFLOW);
+}
+
+static void test_gsm0808_enc_dec_encrypt_info(void)
{
struct gsm0808_encrypt_info enc_ei = {
.perm_algo = { GSM0808_ALG_ID_A5_0, GSM0808_ALG_ID_A5_1 },
@@ -1147,7 +1287,7 @@ static void test_gsm0808_enc_dec_encrypt_info()
msgb_free(msg);
}
-static void test_gsm0808_dec_cell_id_list_srvcc()
+static void test_gsm0808_dec_cell_id_list_srvcc(void)
{
/* taken from a pcap file of a real-world 3rd party MSC (SYS#5838) */
const uint8_t enc_cil[] = { 0x0b, 0x2, 0xf2, 0x10, 0x4e, 0x20, 0x15, 0xbe};
@@ -1160,7 +1300,7 @@ static void test_gsm0808_dec_cell_id_list_srvcc()
OSMO_ASSERT(dec_cil.id_list_len = 1);
}
-static void test_gsm0808_enc_dec_cell_id_list_lac()
+static void test_gsm0808_enc_dec_cell_id_list_lac(void)
{
struct gsm0808_cell_id_list2 enc_cil;
struct gsm0808_cell_id_list2 dec_cil;
@@ -1187,7 +1327,7 @@ static void test_gsm0808_enc_dec_cell_id_list_lac()
msgb_free(msg);
}
-static void test_gsm0808_enc_dec_cell_id_list_single_lac()
+static void test_gsm0808_enc_dec_cell_id_list_single_lac(void)
{
struct gsm0808_cell_id_list2 enc_cil;
struct gsm0808_cell_id_list2 dec_cil;
@@ -1216,7 +1356,7 @@ static void test_gsm0808_enc_dec_cell_id_list_single_lac()
msgb_free(msg);
}
-static void test_gsm0808_enc_dec_cell_id_list_multi_lac()
+static void test_gsm0808_enc_dec_cell_id_list_multi_lac(void)
{
struct gsm0808_cell_id_list2 enc_cil;
struct gsm0808_cell_id_list2 dec_cil;
@@ -1252,7 +1392,7 @@ static void test_gsm0808_enc_dec_cell_id_list_multi_lac()
msgb_free(msg);
}
-static void test_gsm0808_enc_dec_cell_id_list_bss()
+static void test_gsm0808_enc_dec_cell_id_list_bss(void)
{
struct gsm0808_cell_id_list2 enc_cil;
struct gsm0808_cell_id_list2 dec_cil;
@@ -1275,7 +1415,7 @@ static void test_gsm0808_enc_dec_cell_id_list_bss()
msgb_free(msg);
}
-static void test_gsm0808_enc_dec_cell_id_list_multi_lai_and_lac()
+static void test_gsm0808_enc_dec_cell_id_list_multi_lai_and_lac(void)
{
struct gsm0808_cell_id_list2 enc_cil;
struct gsm0808_cell_id_list2 dec_cil;
@@ -1331,7 +1471,7 @@ static void test_gsm0808_enc_dec_cell_id_list_multi_lai_and_lac()
msgb_free(msg);
}
-static void test_gsm0808_enc_dec_cell_id_list_multi_ci()
+static void test_gsm0808_enc_dec_cell_id_list_multi_ci(void)
{
struct gsm0808_cell_id_list2 enc_cil;
struct gsm0808_cell_id_list2 dec_cil;
@@ -1365,7 +1505,7 @@ static void test_gsm0808_enc_dec_cell_id_list_multi_ci()
msgb_free(msg);
}
-static void test_gsm0808_enc_dec_cell_id_list_multi_lac_and_ci()
+static void test_gsm0808_enc_dec_cell_id_list_multi_lac_and_ci(void)
{
struct gsm0808_cell_id_list2 enc_cil;
struct gsm0808_cell_id_list2 dec_cil;
@@ -1406,7 +1546,7 @@ static void test_gsm0808_enc_dec_cell_id_list_multi_lac_and_ci()
msgb_free(msg);
}
-static void test_gsm0808_enc_dec_cell_id_list_multi_global()
+static void test_gsm0808_enc_dec_cell_id_list_multi_global(void)
{
struct gsm0808_cell_id_list2 enc_cil;
struct gsm0808_cell_id_list2 dec_cil;
@@ -1479,7 +1619,7 @@ static void print_cil(const struct gsm0808_cell_id_list2 *cil)
printf(" cell_id_list == %s\n", gsm0808_cell_id_list_name(cil));
}
-void test_cell_id_list_add() {
+void test_cell_id_list_add(void) {
size_t zu;
const struct gsm0808_cell_id_list2 cgi1 = {
@@ -1661,7 +1801,7 @@ void test_cell_id_list_add() {
printf("------- %s done\n", __func__);
}
-static void test_gsm0808_enc_dec_cell_id_lac()
+static void test_gsm0808_enc_dec_cell_id_lac(void)
{
struct gsm0808_cell_id enc_ci = {
.id_discr = CELL_IDENT_LAC,
@@ -1687,7 +1827,7 @@ static void test_gsm0808_enc_dec_cell_id_lac()
msgb_free(msg);
}
-static void test_gsm0808_enc_dec_cell_id_bss()
+static void test_gsm0808_enc_dec_cell_id_bss(void)
{
struct gsm0808_cell_id enc_ci = {
.id_discr = CELL_IDENT_BSS,
@@ -1709,7 +1849,7 @@ static void test_gsm0808_enc_dec_cell_id_bss()
msgb_free(msg);
}
-static void test_gsm0808_enc_dec_cell_id_no_cell()
+static void test_gsm0808_enc_dec_cell_id_no_cell(void)
{
struct gsm0808_cell_id enc_ci = {
.id_discr = CELL_IDENT_NO_CELL,
@@ -1731,7 +1871,7 @@ static void test_gsm0808_enc_dec_cell_id_no_cell()
msgb_free(msg);
}
-static void test_gsm0808_enc_dec_cell_id_lai_and_lac()
+static void test_gsm0808_enc_dec_cell_id_lai_and_lac(void)
{
struct gsm0808_cell_id enc_ci = {
.id_discr = CELL_IDENT_LAI_AND_LAC,
@@ -1762,7 +1902,7 @@ static void test_gsm0808_enc_dec_cell_id_lai_and_lac()
msgb_free(msg);
}
-static void test_gsm0808_enc_dec_cell_id_ci()
+static void test_gsm0808_enc_dec_cell_id_ci(void)
{
struct gsm0808_cell_id enc_ci = {
.id_discr = CELL_IDENT_CI,
@@ -1785,7 +1925,7 @@ static void test_gsm0808_enc_dec_cell_id_ci()
msgb_free(msg);
}
-static void test_gsm0808_enc_dec_cell_id_lac_and_ci()
+static void test_gsm0808_enc_dec_cell_id_lac_and_ci(void)
{
struct gsm0808_cell_id enc_ci = {
.id_discr = CELL_IDENT_LAC_AND_CI,
@@ -1812,7 +1952,7 @@ static void test_gsm0808_enc_dec_cell_id_lac_and_ci()
msgb_free(msg);
}
-static void test_gsm0808_enc_dec_cell_id_global()
+static void test_gsm0808_enc_dec_cell_id_global(void)
{
struct gsm0808_cell_id enc_ci = {
.id_discr = CELL_IDENT_WHOLE_GLOBAL,
@@ -1844,7 +1984,7 @@ static void test_gsm0808_enc_dec_cell_id_global()
msgb_free(msg);
}
-static void test_gsm0808_enc_dec_cell_id_global_ps()
+static void test_gsm0808_enc_dec_cell_id_global_ps(void)
{
struct gsm0808_cell_id enc_cgi = {
.id_discr = CELL_IDENT_WHOLE_GLOBAL,
@@ -1886,29 +2026,81 @@ static void test_gsm0808_enc_dec_cell_id_global_ps()
msgb_free(msg_cgi_ps);
}
+static void print_s15_s0(uint16_t s15_s0, bool full_rate)
+{
+ int i;
+ printf(" S15-S0 = 0x%04x = 0b" OSMO_BIN_SPEC OSMO_BIN_SPEC "\n", s15_s0,
+ OSMO_BIN_PRINT(s15_s0 >> 8), OSMO_BIN_PRINT(s15_s0));
+ for (i = 0; i < 16; i++) {
+ uint8_t modes;
+ int m;
+ int space;
+
+ if (!(s15_s0 & (1 << i)))
+ continue;
+
+ space = 6;
+ if (i < 10)
+ space++;
+
+ printf(" S%d", i);
+
+ modes = gsm0808_amr_modes_from_cfg[full_rate ? 1 : 0][i];
+ if (!modes) {
+ printf(" (empty)\n");
+ continue;
+ }
+
+ for (m = 0; m < 8; m++) {
+ if (!(modes & (1 << m))) {
+ /* avoid whitespace at line ends -- accumulate whitespace width until there is
+ * non-whitespace to actually be printed.*/
+ space += 8;
+ continue;
+ }
+ printf("%*s", space, gsm0808_amr_mode_name(m));
+ space = 8;
+ }
+ printf("\n");
+ }
+}
+
+static void print_mr_cfg(const struct gsm48_multi_rate_conf *cfg)
+{
+ printf(" cfg.smod=%u spare=%u icmi=%u nscb=%u ver=%u\n",
+ cfg->smod, cfg->spare, cfg->icmi, cfg->nscb, cfg->ver);
+ printf(" ");
+#define PRINT_MODE_BIT(NAME) do { \
+ if (cfg->NAME) \
+ printf(" " #NAME "=1"); \
+ else \
+ printf(" -------"); \
+ } while (0)
+ PRINT_MODE_BIT(m4_75);
+ PRINT_MODE_BIT(m5_15);
+ PRINT_MODE_BIT(m5_90);
+ PRINT_MODE_BIT(m6_70);
+ PRINT_MODE_BIT(m7_40);
+ PRINT_MODE_BIT(m7_95);
+ PRINT_MODE_BIT(m10_2);
+ PRINT_MODE_BIT(m12_2);
+ printf("\n");
+}
+
static void test_gsm0808_sc_cfg_from_gsm48_mr_cfg_single(struct gsm48_multi_rate_conf *cfg)
{
uint16_t s15_s0;
printf("Input:\n");
- printf(" m4_75= %u smod= %u\n", cfg->m4_75, cfg->smod);
- printf(" m5_15= %u spare= %u\n", cfg->m5_15, cfg->spare);
- printf(" m5_90= %u icmi= %u\n", cfg->m5_90, cfg->icmi);
- printf(" m6_70= %u nscb= %u\n", cfg->m6_70, cfg->nscb);
- printf(" m7_40= %u ver= %u\n", cfg->m7_40, cfg->ver);
- printf(" m7_95= %u\n", cfg->m7_95);
- printf(" m10_2= %u\n", cfg->m10_2);
- printf(" m12_2= %u\n", cfg->m12_2);
+ print_mr_cfg(cfg);
s15_s0 = gsm0808_sc_cfg_from_gsm48_mr_cfg(cfg, true);
printf("Result (fr):\n");
- printf(" S15-S0 = %04x = 0b" OSMO_BIN_SPEC OSMO_BIN_SPEC "\n", s15_s0,
- OSMO_BIN_PRINT(s15_s0 >> 8), OSMO_BIN_PRINT(s15_s0));
+ print_s15_s0(s15_s0, true);
s15_s0 = gsm0808_sc_cfg_from_gsm48_mr_cfg(cfg, false);
printf("Result (hr):\n");
- printf(" S15-S0 = %04x = 0b" OSMO_BIN_SPEC OSMO_BIN_SPEC "\n", s15_s0,
- OSMO_BIN_PRINT(s15_s0 >> 8), OSMO_BIN_PRINT(s15_s0));
+ print_s15_s0(s15_s0, false);
printf("\n");
}
@@ -2109,20 +2301,12 @@ static void test_gsm48_mr_cfg_from_gsm0808_sc_cfg_single(uint16_t s15_s0)
int rc;
printf("Input:\n");
- printf(" S15-S0 = %04x = 0b" OSMO_BIN_SPEC OSMO_BIN_SPEC "\n", s15_s0,
- OSMO_BIN_PRINT(s15_s0 >> 8), OSMO_BIN_PRINT(s15_s0));
+ print_s15_s0(s15_s0, true);
rc = gsm48_mr_cfg_from_gsm0808_sc_cfg(&cfg, s15_s0);
printf("Output:\n");
- printf(" m4_75= %u smod= %u\n", cfg.m4_75, cfg.smod);
- printf(" m5_15= %u spare= %u\n", cfg.m5_15, cfg.spare);
- printf(" m5_90= %u icmi= %u\n", cfg.m5_90, cfg.icmi);
- printf(" m6_70= %u nscb= %u\n", cfg.m6_70, cfg.nscb);
- printf(" m7_40= %u ver= %u\n", cfg.m7_40, cfg.ver);
- printf(" m7_95= %u\n", cfg.m7_95);
- printf(" m10_2= %u\n", cfg.m10_2);
- printf(" m12_2= %u\n", cfg.m12_2);
+ print_mr_cfg(&cfg);
if (rc != 0)
printf(" Result invalid!\n");
@@ -2130,7 +2314,7 @@ static void test_gsm48_mr_cfg_from_gsm0808_sc_cfg_single(uint16_t s15_s0)
printf("\n");
}
-void test_gsm48_mr_cfg_from_gsm0808_sc_cfg()
+void test_gsm48_mr_cfg_from_gsm0808_sc_cfg(void)
{
printf("Testing gsm48_mr_cfg_from_gsm0808_sc_cfg():\n");
@@ -2315,7 +2499,7 @@ static const struct test_cell_id_matching_data test_cell_id_matching_tests[] = {
{ .id = cgi_23_042_23_5, .match_id = cgi_23_99_23_5, .expect_match = false, .expect_exact_match = false },
};
-static void test_cell_id_matching()
+static void test_cell_id_matching(void)
{
int i;
bool ok = true;
@@ -2478,7 +2662,7 @@ static const struct gsm0808_cell_id test_gsm0808_cell_id_to_from_cgi_data[] = {
{ .id_discr = 423 },
};
-static void test_gsm0808_cell_id_to_from_cgi()
+static void test_gsm0808_cell_id_to_from_cgi(void)
{
int i;
int j;
@@ -2568,7 +2752,11 @@ int main(int argc, char **argv)
test_gsm0808_enc_dec_speech_codec_with_cfg();
test_gsm0808_enc_dec_speech_codec_list();
test_gsm0808_enc_dec_empty_speech_codec_list();
- test_gsm0808_enc_dec_channel_type();
+ test_gsm0808_enc_dec_channel_type_data();
+ test_gsm0808_enc_dec_channel_type_data_asym_pref();
+ test_gsm0808_enc_dec_channel_type_speech();
+ test_gsm0808_enc_dec_channel_type_sign();
+ test_gsm0808_dec_channel_type_err();
test_gsm0808_enc_dec_encrypt_info();
test_gsm0808_enc_dec_cell_id_list_lac();
diff --git a/tests/gsm0808/gsm0808_test.ok b/tests/gsm0808/gsm0808_test.ok
index 8f91eb16..2329fcdc 100644
--- a/tests/gsm0808/gsm0808_test.ok
+++ b/tests/gsm0808/gsm0808_test.ok
@@ -87,549 +87,549 @@ test_gsm0808_enc_dec_cell_id_lac_and_ci: encoded: 05 05 01 04 23 02 35 (rc = 7)
test_gsm0808_enc_dec_cell_id_global: encoded: 05 08 00 21 63 54 23 42 04 23 (rc = 10)
Testing gsm0808_sc_cfg_from_gsm48_mr_cfg():
Input:
- m4_75= 0 smod= 0
- m5_15= 0 spare= 0
- m5_90= 0 icmi= 0
- m6_70= 0 nscb= 0
- m7_40= 0 ver= 0
- m7_95= 0
- m10_2= 0
- m12_2= 0
+ cfg.smod=0 spare=0 icmi=0 nscb=0 ver=0
+ ------- ------- ------- ------- ------- ------- ------- -------
Result (fr):
- S15-S0 = 0000 = 0b0000000000000000
+ S15-S0 = 0x0000 = 0b0000000000000000
Result (hr):
- S15-S0 = 0000 = 0b0000000000000000
+ S15-S0 = 0x0000 = 0b0000000000000000
Input:
- m4_75= 1 smod= 0
- m5_15= 0 spare= 0
- m5_90= 0 icmi= 0
- m6_70= 0 nscb= 0
- m7_40= 0 ver= 0
- m7_95= 0
- m10_2= 0
- m12_2= 0
+ cfg.smod=0 spare=0 icmi=0 nscb=0 ver=0
+ m4_75=1 ------- ------- ------- ------- ------- ------- -------
Result (fr):
- S15-S0 = 5701 = 0b0101011100000001
+ S15-S0 = 0x5701 = 0b0101011100000001
+ S0 4.75
+ S8 4.75 5.90
+ S9 4.75 5.90 6.70
+ S10 4.75 5.90 6.70 7.40
+ S12 4.75 5.90 6.70 10.2
+ S14 4.75 5.90 7.95 12.2
Result (hr):
- S15-S0 = 0701 = 0b0000011100000001
+ S15-S0 = 0x0701 = 0b0000011100000001
+ S0 4.75
+ S8 4.75 5.90
+ S9 4.75 5.90 6.70
+ S10 4.75 5.90 6.70 7.40
Input:
- m4_75= 0 smod= 0
- m5_15= 1 spare= 0
- m5_90= 0 icmi= 0
- m6_70= 0 nscb= 0
- m7_40= 0 ver= 0
- m7_95= 0
- m10_2= 0
- m12_2= 0
+ cfg.smod=0 spare=0 icmi=0 nscb=0 ver=0
+ ------- m5_15=1 ------- ------- ------- ------- ------- -------
Result (fr):
- S15-S0 = 0000 = 0b0000000000000000
+ S15-S0 = 0x0000 = 0b0000000000000000
Result (hr):
- S15-S0 = 0000 = 0b0000000000000000
+ S15-S0 = 0x0000 = 0b0000000000000000
Input:
- m4_75= 0 smod= 0
- m5_15= 0 spare= 0
- m5_90= 1 icmi= 0
- m6_70= 0 nscb= 0
- m7_40= 0 ver= 0
- m7_95= 0
- m10_2= 0
- m12_2= 0
+ cfg.smod=0 spare=0 icmi=0 nscb=0 ver=0
+ ------- ------- m5_90=1 ------- ------- ------- ------- -------
Result (fr):
- S15-S0 = 5704 = 0b0101011100000100
+ S15-S0 = 0x5704 = 0b0101011100000100
+ S2 5.90
+ S8 4.75 5.90
+ S9 4.75 5.90 6.70
+ S10 4.75 5.90 6.70 7.40
+ S12 4.75 5.90 6.70 10.2
+ S14 4.75 5.90 7.95 12.2
Result (hr):
- S15-S0 = 0704 = 0b0000011100000100
+ S15-S0 = 0x0704 = 0b0000011100000100
+ S2 5.90
+ S8 4.75 5.90
+ S9 4.75 5.90 6.70
+ S10 4.75 5.90 6.70 7.40
Input:
- m4_75= 0 smod= 0
- m5_15= 0 spare= 0
- m5_90= 0 icmi= 0
- m6_70= 1 nscb= 0
- m7_40= 0 ver= 0
- m7_95= 0
- m10_2= 0
- m12_2= 0
+ cfg.smod=0 spare=0 icmi=0 nscb=0 ver=0
+ ------- ------- ------- m6_70=1 ------- ------- ------- -------
Result (fr):
- S15-S0 = 1608 = 0b0001011000001000
+ S15-S0 = 0x1608 = 0b0001011000001000
+ S3 6.70
+ S9 4.75 5.90 6.70
+ S10 4.75 5.90 6.70 7.40
+ S12 4.75 5.90 6.70 10.2
Result (hr):
- S15-S0 = 0608 = 0b0000011000001000
+ S15-S0 = 0x0608 = 0b0000011000001000
+ S3 6.70
+ S9 4.75 5.90 6.70
+ S10 4.75 5.90 6.70 7.40
Input:
- m4_75= 0 smod= 0
- m5_15= 0 spare= 0
- m5_90= 0 icmi= 0
- m6_70= 0 nscb= 0
- m7_40= 1 ver= 0
- m7_95= 0
- m10_2= 0
- m12_2= 0
+ cfg.smod=0 spare=0 icmi=0 nscb=0 ver=0
+ ------- ------- ------- ------- m7_40=1 ------- ------- -------
Result (fr):
- S15-S0 = 0410 = 0b0000010000010000
+ S15-S0 = 0x0410 = 0b0000010000010000
+ S4 7.40
+ S10 4.75 5.90 6.70 7.40
Result (hr):
- S15-S0 = 0410 = 0b0000010000010000
+ S15-S0 = 0x0410 = 0b0000010000010000
+ S4 7.40
+ S10 4.75 5.90 6.70 7.40
Input:
- m4_75= 0 smod= 0
- m5_15= 0 spare= 0
- m5_90= 0 icmi= 0
- m6_70= 0 nscb= 0
- m7_40= 0 ver= 0
- m7_95= 1
- m10_2= 0
- m12_2= 0
+ cfg.smod=0 spare=0 icmi=0 nscb=0 ver=0
+ ------- ------- ------- ------- ------- m7_95=1 ------- -------
Result (fr):
- S15-S0 = 4020 = 0b0100000000100000
+ S15-S0 = 0x4020 = 0b0100000000100000
+ S5 7.95
+ S14 4.75 5.90 7.95 12.2
Result (hr):
- S15-S0 = 0020 = 0b0000000000100000
+ S15-S0 = 0x0020 = 0b0000000000100000
+ S5 7.95
Input:
- m4_75= 0 smod= 0
- m5_15= 0 spare= 0
- m5_90= 0 icmi= 0
- m6_70= 0 nscb= 0
- m7_40= 0 ver= 0
- m7_95= 0
- m10_2= 1
- m12_2= 0
+ cfg.smod=0 spare=0 icmi=0 nscb=0 ver=0
+ ------- ------- ------- ------- ------- ------- m10_2=1 -------
Result (fr):
- S15-S0 = 1040 = 0b0001000001000000
+ S15-S0 = 0x1040 = 0b0001000001000000
+ S6 10.2
+ S12 4.75 5.90 6.70 10.2
Result (hr):
- S15-S0 = 0000 = 0b0000000000000000
+ S15-S0 = 0x0000 = 0b0000000000000000
Input:
- m4_75= 0 smod= 0
- m5_15= 0 spare= 0
- m5_90= 0 icmi= 0
- m6_70= 0 nscb= 0
- m7_40= 0 ver= 0
- m7_95= 0
- m10_2= 0
- m12_2= 1
+ cfg.smod=0 spare=0 icmi=0 nscb=0 ver=0
+ ------- ------- ------- ------- ------- ------- ------- m12_2=1
Result (fr):
- S15-S0 = 4080 = 0b0100000010000000
+ S15-S0 = 0x4080 = 0b0100000010000000
+ S7 12.2
+ S14 4.75 5.90 7.95 12.2
Result (hr):
- S15-S0 = 0000 = 0b0000000000000000
+ S15-S0 = 0x0000 = 0b0000000000000000
Input:
- m4_75= 1 smod= 0
- m5_15= 1 spare= 0
- m5_90= 1 icmi= 0
- m6_70= 1 nscb= 0
- m7_40= 0 ver= 0
- m7_95= 0
- m10_2= 0
- m12_2= 0
+ cfg.smod=0 spare=0 icmi=0 nscb=0 ver=0
+ m4_75=1 m5_15=1 m5_90=1 m6_70=1 ------- ------- ------- -------
Result (fr):
- S15-S0 = 570d = 0b0101011100001101
+ S15-S0 = 0x570d = 0b0101011100001101
+ S0 4.75
+ S2 5.90
+ S3 6.70
+ S8 4.75 5.90
+ S9 4.75 5.90 6.70
+ S10 4.75 5.90 6.70 7.40
+ S12 4.75 5.90 6.70 10.2
+ S14 4.75 5.90 7.95 12.2
Result (hr):
- S15-S0 = 070d = 0b0000011100001101
+ S15-S0 = 0x070d = 0b0000011100001101
+ S0 4.75
+ S2 5.90
+ S3 6.70
+ S8 4.75 5.90
+ S9 4.75 5.90 6.70
+ S10 4.75 5.90 6.70 7.40
Input:
- m4_75= 0 smod= 0
- m5_15= 0 spare= 0
- m5_90= 0 icmi= 0
- m6_70= 0 nscb= 0
- m7_40= 1 ver= 0
- m7_95= 1
- m10_2= 1
- m12_2= 1
+ cfg.smod=0 spare=0 icmi=0 nscb=0 ver=0
+ ------- ------- ------- ------- m7_40=1 m7_95=1 m10_2=1 m12_2=1
Result (fr):
- S15-S0 = 54f0 = 0b0101010011110000
+ S15-S0 = 0x54f0 = 0b0101010011110000
+ S4 7.40
+ S5 7.95
+ S6 10.2
+ S7 12.2
+ S10 4.75 5.90 6.70 7.40
+ S12 4.75 5.90 6.70 10.2
+ S14 4.75 5.90 7.95 12.2
Result (hr):
- S15-S0 = 0430 = 0b0000010000110000
+ S15-S0 = 0x0430 = 0b0000010000110000
+ S4 7.40
+ S5 7.95
+ S10 4.75 5.90 6.70 7.40
Input:
- m4_75= 0 smod= 0
- m5_15= 0 spare= 0
- m5_90= 1 icmi= 0
- m6_70= 1 nscb= 0
- m7_40= 0 ver= 0
- m7_95= 0
- m10_2= 1
- m12_2= 1
+ cfg.smod=0 spare=0 icmi=0 nscb=0 ver=0
+ ------- ------- m5_90=1 m6_70=1 ------- ------- m10_2=1 m12_2=1
Result (fr):
- S15-S0 = 57cc = 0b0101011111001100
+ S15-S0 = 0x57cc = 0b0101011111001100
+ S2 5.90
+ S3 6.70
+ S6 10.2
+ S7 12.2
+ S8 4.75 5.90
+ S9 4.75 5.90 6.70
+ S10 4.75 5.90 6.70 7.40
+ S12 4.75 5.90 6.70 10.2
+ S14 4.75 5.90 7.95 12.2
Result (hr):
- S15-S0 = 070c = 0b0000011100001100
+ S15-S0 = 0x070c = 0b0000011100001100
+ S2 5.90
+ S3 6.70
+ S8 4.75 5.90
+ S9 4.75 5.90 6.70
+ S10 4.75 5.90 6.70 7.40
Input:
- m4_75= 1 smod= 0
- m5_15= 1 spare= 0
- m5_90= 0 icmi= 0
- m6_70= 0 nscb= 0
- m7_40= 1 ver= 0
- m7_95= 1
- m10_2= 0
- m12_2= 0
+ cfg.smod=0 spare=0 icmi=0 nscb=0 ver=0
+ m4_75=1 m5_15=1 ------- ------- m7_40=1 m7_95=1 ------- -------
Result (fr):
- S15-S0 = 5731 = 0b0101011100110001
+ S15-S0 = 0x5731 = 0b0101011100110001
+ S0 4.75
+ S4 7.40
+ S5 7.95
+ S8 4.75 5.90
+ S9 4.75 5.90 6.70
+ S10 4.75 5.90 6.70 7.40
+ S12 4.75 5.90 6.70 10.2
+ S14 4.75 5.90 7.95 12.2
Result (hr):
- S15-S0 = 0731 = 0b0000011100110001
+ S15-S0 = 0x0731 = 0b0000011100110001
+ S0 4.75
+ S4 7.40
+ S5 7.95
+ S8 4.75 5.90
+ S9 4.75 5.90 6.70
+ S10 4.75 5.90 6.70 7.40
Input:
- m4_75= 0 smod= 0
- m5_15= 1 spare= 0
- m5_90= 0 icmi= 0
- m6_70= 1 nscb= 0
- m7_40= 0 ver= 0
- m7_95= 1
- m10_2= 0
- m12_2= 1
+ cfg.smod=0 spare=0 icmi=0 nscb=0 ver=0
+ ------- m5_15=1 ------- m6_70=1 ------- m7_95=1 ------- m12_2=1
Result (fr):
- S15-S0 = 56a8 = 0b0101011010101000
+ S15-S0 = 0x56a8 = 0b0101011010101000
+ S3 6.70
+ S5 7.95
+ S7 12.2
+ S9 4.75 5.90 6.70
+ S10 4.75 5.90 6.70 7.40
+ S12 4.75 5.90 6.70 10.2
+ S14 4.75 5.90 7.95 12.2
Result (hr):
- S15-S0 = 0628 = 0b0000011000101000
+ S15-S0 = 0x0628 = 0b0000011000101000
+ S3 6.70
+ S5 7.95
+ S9 4.75 5.90 6.70
+ S10 4.75 5.90 6.70 7.40
Input:
- m4_75= 1 smod= 0
- m5_15= 0 spare= 0
- m5_90= 1 icmi= 0
- m6_70= 0 nscb= 0
- m7_40= 1 ver= 0
- m7_95= 0
- m10_2= 1
- m12_2= 0
+ cfg.smod=0 spare=0 icmi=0 nscb=0 ver=0
+ m4_75=1 ------- m5_90=1 ------- m7_40=1 ------- m10_2=1 -------
Result (fr):
- S15-S0 = 5755 = 0b0101011101010101
+ S15-S0 = 0x5755 = 0b0101011101010101
+ S0 4.75
+ S2 5.90
+ S4 7.40
+ S6 10.2
+ S8 4.75 5.90
+ S9 4.75 5.90 6.70
+ S10 4.75 5.90 6.70 7.40
+ S12 4.75 5.90 6.70 10.2
+ S14 4.75 5.90 7.95 12.2
Result (hr):
- S15-S0 = 0717 = 0b0000011100010111
-
-Input:
- m4_75= 1 smod= 0
- m5_15= 1 spare= 0
- m5_90= 1 icmi= 0
- m6_70= 1 nscb= 0
- m7_40= 1 ver= 0
- m7_95= 1
- m10_2= 1
- m12_2= 1
+ S15-S0 = 0x0717 = 0b0000011100010111
+ S0 4.75
+ S1 4.75 5.90 7.40
+ S2 5.90
+ S4 7.40
+ S8 4.75 5.90
+ S9 4.75 5.90 6.70
+ S10 4.75 5.90 6.70 7.40
+
+Input:
+ cfg.smod=0 spare=0 icmi=0 nscb=0 ver=0
+ m4_75=1 m5_15=1 m5_90=1 m6_70=1 m7_40=1 m7_95=1 m10_2=1 m12_2=1
Result (fr):
- S15-S0 = 57ff = 0b0101011111111111
+ S15-S0 = 0x57ff = 0b0101011111111111
+ S0 4.75
+ S1 4.75 5.90 7.40 12.2
+ S2 5.90
+ S3 6.70
+ S4 7.40
+ S5 7.95
+ S6 10.2
+ S7 12.2
+ S8 4.75 5.90
+ S9 4.75 5.90 6.70
+ S10 4.75 5.90 6.70 7.40
+ S12 4.75 5.90 6.70 10.2
+ S14 4.75 5.90 7.95 12.2
Result (hr):
- S15-S0 = 073f = 0b0000011100111111
-
-Input:
- m4_75= 1 smod= 0
- m5_15= 0 spare= 0
- m5_90= 1 icmi= 0
- m6_70= 0 nscb= 0
- m7_40= 1 ver= 0
- m7_95= 0
- m10_2= 0
- m12_2= 1
+ S15-S0 = 0x073f = 0b0000011100111111
+ S0 4.75
+ S1 4.75 5.90 7.40
+ S2 5.90
+ S3 6.70
+ S4 7.40
+ S5 7.95
+ S8 4.75 5.90
+ S9 4.75 5.90 6.70
+ S10 4.75 5.90 6.70 7.40
+
+Input:
+ cfg.smod=0 spare=0 icmi=0 nscb=0 ver=0
+ m4_75=1 ------- m5_90=1 ------- m7_40=1 ------- ------- m12_2=1
Result (fr):
- S15-S0 = 5797 = 0b0101011110010111
+ S15-S0 = 0x5797 = 0b0101011110010111
+ S0 4.75
+ S1 4.75 5.90 7.40 12.2
+ S2 5.90
+ S4 7.40
+ S7 12.2
+ S8 4.75 5.90
+ S9 4.75 5.90 6.70
+ S10 4.75 5.90 6.70 7.40
+ S12 4.75 5.90 6.70 10.2
+ S14 4.75 5.90 7.95 12.2
Result (hr):
- S15-S0 = 0717 = 0b0000011100010111
-
-Input:
- m4_75= 1 smod= 0
- m5_15= 0 spare= 0
- m5_90= 1 icmi= 0
- m6_70= 0 nscb= 0
- m7_40= 1 ver= 0
- m7_95= 0
- m10_2= 0
- m12_2= 0
+ S15-S0 = 0x0717 = 0b0000011100010111
+ S0 4.75
+ S1 4.75 5.90 7.40
+ S2 5.90
+ S4 7.40
+ S8 4.75 5.90
+ S9 4.75 5.90 6.70
+ S10 4.75 5.90 6.70 7.40
+
+Input:
+ cfg.smod=0 spare=0 icmi=0 nscb=0 ver=0
+ m4_75=1 ------- m5_90=1 ------- m7_40=1 ------- ------- -------
Result (fr):
- S15-S0 = 5715 = 0b0101011100010101
+ S15-S0 = 0x5715 = 0b0101011100010101
+ S0 4.75
+ S2 5.90
+ S4 7.40
+ S8 4.75 5.90
+ S9 4.75 5.90 6.70
+ S10 4.75 5.90 6.70 7.40
+ S12 4.75 5.90 6.70 10.2
+ S14 4.75 5.90 7.95 12.2
Result (hr):
- S15-S0 = 0717 = 0b0000011100010111
+ S15-S0 = 0x0717 = 0b0000011100010111
+ S0 4.75
+ S1 4.75 5.90 7.40
+ S2 5.90
+ S4 7.40
+ S8 4.75 5.90
+ S9 4.75 5.90 6.70
+ S10 4.75 5.90 6.70 7.40
Testing gsm48_mr_cfg_from_gsm0808_sc_cfg():
Input:
- S15-S0 = ff03 = 0b1111111100000011
+ S15-S0 = 0xff03 = 0b1111111100000011
+ S0 4.75
+ S1 4.75 5.90 7.40 12.2
+ S8 4.75 5.90
+ S9 4.75 5.90 6.70
+ S10 4.75 5.90 6.70 7.40
+ S11 (empty)
+ S12 4.75 5.90 6.70 10.2
+ S13 (empty)
+ S14 4.75 5.90 7.95 12.2
+ S15 (empty)
Output:
- m4_75= 1 smod= 0
- m5_15= 0 spare= 0
- m5_90= 1 icmi= 1
- m6_70= 0 nscb= 0
- m7_40= 1 ver= 1
- m7_95= 0
- m10_2= 0
- m12_2= 1
+ cfg.smod=0 spare=0 icmi=1 nscb=0 ver=1
+ m4_75=1 ------- m5_90=1 ------- m7_40=1 ------- ------- m12_2=1
Input:
- S15-S0 = 0000 = 0b0000000000000000
+ S15-S0 = 0x0000 = 0b0000000000000000
Output:
- m4_75= 0 smod= 0
- m5_15= 0 spare= 0
- m5_90= 0 icmi= 1
- m6_70= 0 nscb= 0
- m7_40= 0 ver= 1
- m7_95= 0
- m10_2= 0
- m12_2= 0
+ cfg.smod=0 spare=0 icmi=1 nscb=0 ver=1
+ ------- ------- ------- ------- ------- ------- ------- -------
Result invalid!
Input:
- S15-S0 = ff06 = 0b1111111100000110
+ S15-S0 = 0xff06 = 0b1111111100000110
+ S1 4.75 5.90 7.40 12.2
+ S2 5.90
+ S8 4.75 5.90
+ S9 4.75 5.90 6.70
+ S10 4.75 5.90 6.70 7.40
+ S11 (empty)
+ S12 4.75 5.90 6.70 10.2
+ S13 (empty)
+ S14 4.75 5.90 7.95 12.2
+ S15 (empty)
Output:
- m4_75= 1 smod= 0
- m5_15= 0 spare= 0
- m5_90= 1 icmi= 1
- m6_70= 0 nscb= 0
- m7_40= 1 ver= 1
- m7_95= 0
- m10_2= 0
- m12_2= 1
+ cfg.smod=0 spare=0 icmi=1 nscb=0 ver=1
+ m4_75=1 ------- m5_90=1 ------- m7_40=1 ------- ------- m12_2=1
Input:
- S15-S0 = 3e08 = 0b0011111000001000
+ S15-S0 = 0x3e08 = 0b0011111000001000
+ S3 6.70
+ S9 4.75 5.90 6.70
+ S10 4.75 5.90 6.70 7.40
+ S11 (empty)
+ S12 4.75 5.90 6.70 10.2
+ S13 (empty)
Output:
- m4_75= 0 smod= 0
- m5_15= 0 spare= 0
- m5_90= 0 icmi= 1
- m6_70= 1 nscb= 0
- m7_40= 0 ver= 1
- m7_95= 0
- m10_2= 0
- m12_2= 0
+ cfg.smod=0 spare=0 icmi=1 nscb=0 ver=1
+ ------- ------- ------- m6_70=1 ------- ------- ------- -------
Input:
- S15-S0 = 0c12 = 0b0000110000010010
+ S15-S0 = 0x0c12 = 0b0000110000010010
+ S1 4.75 5.90 7.40 12.2
+ S4 7.40
+ S10 4.75 5.90 6.70 7.40
+ S11 (empty)
Output:
- m4_75= 1 smod= 0
- m5_15= 0 spare= 0
- m5_90= 1 icmi= 1
- m6_70= 0 nscb= 0
- m7_40= 1 ver= 1
- m7_95= 0
- m10_2= 0
- m12_2= 1
+ cfg.smod=0 spare=0 icmi=1 nscb=0 ver=1
+ m4_75=1 ------- m5_90=1 ------- m7_40=1 ------- ------- m12_2=1
Input:
- S15-S0 = c020 = 0b1100000000100000
+ S15-S0 = 0xc020 = 0b1100000000100000
+ S5 7.95
+ S14 4.75 5.90 7.95 12.2
+ S15 (empty)
Output:
- m4_75= 0 smod= 0
- m5_15= 0 spare= 0
- m5_90= 0 icmi= 1
- m6_70= 0 nscb= 0
- m7_40= 0 ver= 1
- m7_95= 1
- m10_2= 0
- m12_2= 0
+ cfg.smod=0 spare=0 icmi=1 nscb=0 ver=1
+ ------- ------- ------- ------- ------- m7_95=1 ------- -------
Input:
- S15-S0 = 3040 = 0b0011000001000000
+ S15-S0 = 0x3040 = 0b0011000001000000
+ S6 10.2
+ S12 4.75 5.90 6.70 10.2
+ S13 (empty)
Output:
- m4_75= 0 smod= 0
- m5_15= 0 spare= 0
- m5_90= 0 icmi= 1
- m6_70= 0 nscb= 0
- m7_40= 0 ver= 1
- m7_95= 0
- m10_2= 1
- m12_2= 0
+ cfg.smod=0 spare=0 icmi=1 nscb=0 ver=1
+ ------- ------- ------- ------- ------- ------- m10_2=1 -------
Input:
- S15-S0 = c082 = 0b1100000010000010
+ S15-S0 = 0xc082 = 0b1100000010000010
+ S1 4.75 5.90 7.40 12.2
+ S7 12.2
+ S14 4.75 5.90 7.95 12.2
+ S15 (empty)
Output:
- m4_75= 1 smod= 0
- m5_15= 0 spare= 0
- m5_90= 1 icmi= 1
- m6_70= 0 nscb= 0
- m7_40= 1 ver= 1
- m7_95= 0
- m10_2= 0
- m12_2= 1
+ cfg.smod=0 spare=0 icmi=1 nscb=0 ver=1
+ m4_75=1 ------- m5_90=1 ------- m7_40=1 ------- ------- m12_2=1
Input:
- S15-S0 = 0001 = 0b0000000000000001
+ S15-S0 = 0x0001 = 0b0000000000000001
+ S0 4.75
Output:
- m4_75= 1 smod= 0
- m5_15= 0 spare= 0
- m5_90= 0 icmi= 1
- m6_70= 0 nscb= 0
- m7_40= 0 ver= 1
- m7_95= 0
- m10_2= 0
- m12_2= 0
+ cfg.smod=0 spare=0 icmi=1 nscb=0 ver=1
+ m4_75=1 ------- ------- ------- ------- ------- ------- -------
Input:
- S15-S0 = 0002 = 0b0000000000000010
+ S15-S0 = 0x0002 = 0b0000000000000010
+ S1 4.75 5.90 7.40 12.2
Output:
- m4_75= 1 smod= 0
- m5_15= 0 spare= 0
- m5_90= 1 icmi= 1
- m6_70= 0 nscb= 0
- m7_40= 1 ver= 1
- m7_95= 0
- m10_2= 0
- m12_2= 1
+ cfg.smod=0 spare=0 icmi=1 nscb=0 ver=1
+ m4_75=1 ------- m5_90=1 ------- m7_40=1 ------- ------- m12_2=1
Input:
- S15-S0 = 0004 = 0b0000000000000100
+ S15-S0 = 0x0004 = 0b0000000000000100
+ S2 5.90
Output:
- m4_75= 0 smod= 0
- m5_15= 0 spare= 0
- m5_90= 1 icmi= 1
- m6_70= 0 nscb= 0
- m7_40= 0 ver= 1
- m7_95= 0
- m10_2= 0
- m12_2= 0
+ cfg.smod=0 spare=0 icmi=1 nscb=0 ver=1
+ ------- ------- m5_90=1 ------- ------- ------- ------- -------
Input:
- S15-S0 = 0008 = 0b0000000000001000
+ S15-S0 = 0x0008 = 0b0000000000001000
+ S3 6.70
Output:
- m4_75= 0 smod= 0
- m5_15= 0 spare= 0
- m5_90= 0 icmi= 1
- m6_70= 1 nscb= 0
- m7_40= 0 ver= 1
- m7_95= 0
- m10_2= 0
- m12_2= 0
+ cfg.smod=0 spare=0 icmi=1 nscb=0 ver=1
+ ------- ------- ------- m6_70=1 ------- ------- ------- -------
Input:
- S15-S0 = 0010 = 0b0000000000010000
+ S15-S0 = 0x0010 = 0b0000000000010000
+ S4 7.40
Output:
- m4_75= 0 smod= 0
- m5_15= 0 spare= 0
- m5_90= 0 icmi= 1
- m6_70= 0 nscb= 0
- m7_40= 1 ver= 1
- m7_95= 0
- m10_2= 0
- m12_2= 0
+ cfg.smod=0 spare=0 icmi=1 nscb=0 ver=1
+ ------- ------- ------- ------- m7_40=1 ------- ------- -------
Input:
- S15-S0 = 0020 = 0b0000000000100000
+ S15-S0 = 0x0020 = 0b0000000000100000
+ S5 7.95
Output:
- m4_75= 0 smod= 0
- m5_15= 0 spare= 0
- m5_90= 0 icmi= 1
- m6_70= 0 nscb= 0
- m7_40= 0 ver= 1
- m7_95= 1
- m10_2= 0
- m12_2= 0
+ cfg.smod=0 spare=0 icmi=1 nscb=0 ver=1
+ ------- ------- ------- ------- ------- m7_95=1 ------- -------
Input:
- S15-S0 = 0040 = 0b0000000001000000
+ S15-S0 = 0x0040 = 0b0000000001000000
+ S6 10.2
Output:
- m4_75= 0 smod= 0
- m5_15= 0 spare= 0
- m5_90= 0 icmi= 1
- m6_70= 0 nscb= 0
- m7_40= 0 ver= 1
- m7_95= 0
- m10_2= 1
- m12_2= 0
+ cfg.smod=0 spare=0 icmi=1 nscb=0 ver=1
+ ------- ------- ------- ------- ------- ------- m10_2=1 -------
Input:
- S15-S0 = 0080 = 0b0000000010000000
+ S15-S0 = 0x0080 = 0b0000000010000000
+ S7 12.2
Output:
- m4_75= 0 smod= 0
- m5_15= 0 spare= 0
- m5_90= 0 icmi= 1
- m6_70= 0 nscb= 0
- m7_40= 0 ver= 1
- m7_95= 0
- m10_2= 0
- m12_2= 1
+ cfg.smod=0 spare=0 icmi=1 nscb=0 ver=1
+ ------- ------- ------- ------- ------- ------- ------- m12_2=1
Input:
- S15-S0 = 0058 = 0b0000000001011000
+ S15-S0 = 0x0058 = 0b0000000001011000
+ S3 6.70
+ S4 7.40
+ S6 10.2
Output:
- m4_75= 0 smod= 0
- m5_15= 0 spare= 0
- m5_90= 0 icmi= 1
- m6_70= 1 nscb= 0
- m7_40= 1 ver= 1
- m7_95= 0
- m10_2= 1
- m12_2= 0
+ cfg.smod=0 spare=0 icmi=1 nscb=0 ver=1
+ ------- ------- ------- m6_70=1 m7_40=1 ------- m10_2=1 -------
Input:
- S15-S0 = 0021 = 0b0000000000100001
+ S15-S0 = 0x0021 = 0b0000000000100001
+ S0 4.75
+ S5 7.95
Output:
- m4_75= 1 smod= 0
- m5_15= 0 spare= 0
- m5_90= 0 icmi= 1
- m6_70= 0 nscb= 0
- m7_40= 0 ver= 1
- m7_95= 1
- m10_2= 0
- m12_2= 0
+ cfg.smod=0 spare=0 icmi=1 nscb=0 ver=1
+ m4_75=1 ------- ------- ------- ------- m7_95=1 ------- -------
Input:
- S15-S0 = 0084 = 0b0000000010000100
+ S15-S0 = 0x0084 = 0b0000000010000100
+ S2 5.90
+ S7 12.2
Output:
- m4_75= 0 smod= 0
- m5_15= 0 spare= 0
- m5_90= 1 icmi= 1
- m6_70= 0 nscb= 0
- m7_40= 0 ver= 1
- m7_95= 0
- m10_2= 0
- m12_2= 1
+ cfg.smod=0 spare=0 icmi=1 nscb=0 ver=1
+ ------- ------- m5_90=1 ------- ------- ------- ------- m12_2=1
Input:
- S15-S0 = 0086 = 0b0000000010000110
+ S15-S0 = 0x0086 = 0b0000000010000110
+ S1 4.75 5.90 7.40 12.2
+ S2 5.90
+ S7 12.2
Output:
- m4_75= 1 smod= 0
- m5_15= 0 spare= 0
- m5_90= 1 icmi= 1
- m6_70= 0 nscb= 0
- m7_40= 1 ver= 1
- m7_95= 0
- m10_2= 0
- m12_2= 1
+ cfg.smod=0 spare=0 icmi=1 nscb=0 ver=1
+ m4_75=1 ------- m5_90=1 ------- m7_40=1 ------- ------- m12_2=1
Input:
- S15-S0 = 000a = 0b0000000000001010
+ S15-S0 = 0x000a = 0b0000000000001010
+ S1 4.75 5.90 7.40 12.2
+ S3 6.70
Output:
- m4_75= 1 smod= 0
- m5_15= 0 spare= 0
- m5_90= 1 icmi= 1
- m6_70= 0 nscb= 0
- m7_40= 1 ver= 1
- m7_95= 0
- m10_2= 0
- m12_2= 1
+ cfg.smod=0 spare=0 icmi=1 nscb=0 ver=1
+ m4_75=1 ------- m5_90=1 ------- m7_40=1 ------- ------- m12_2=1
Result invalid!
Input:
- S15-S0 = 0079 = 0b0000000001111001
+ S15-S0 = 0x0079 = 0b0000000001111001
+ S0 4.75
+ S3 6.70
+ S4 7.40
+ S5 7.95
+ S6 10.2
Output:
- m4_75= 1 smod= 0
- m5_15= 0 spare= 0
- m5_90= 0 icmi= 1
- m6_70= 1 nscb= 0
- m7_40= 1 ver= 1
- m7_95= 1
- m10_2= 0
- m12_2= 0
+ cfg.smod=0 spare=0 icmi=1 nscb=0 ver=1
+ m4_75=1 ------- ------- m6_70=1 m7_40=1 m7_95=1 ------- -------
Result invalid!
Input:
- S15-S0 = 0000 = 0b0000000000000000
+ S15-S0 = 0x0000 = 0b0000000000000000
Output:
- m4_75= 0 smod= 0
- m5_15= 0 spare= 0
- m5_90= 0 icmi= 1
- m6_70= 0 nscb= 0
- m7_40= 0 ver= 1
- m7_95= 0
- m10_2= 0
- m12_2= 0
+ cfg.smod=0 spare=0 icmi=1 nscb=0 ver=1
+ ------- ------- ------- ------- ------- ------- ------- -------
Result invalid!
Input:
- S15-S0 = ffff = 0b1111111111111111
+ S15-S0 = 0xffff = 0b1111111111111111
+ S0 4.75
+ S1 4.75 5.90 7.40 12.2
+ S2 5.90
+ S3 6.70
+ S4 7.40
+ S5 7.95
+ S6 10.2
+ S7 12.2
+ S8 4.75 5.90
+ S9 4.75 5.90 6.70
+ S10 4.75 5.90 6.70 7.40
+ S11 (empty)
+ S12 4.75 5.90 6.70 10.2
+ S13 (empty)
+ S14 4.75 5.90 7.95 12.2
+ S15 (empty)
Output:
- m4_75= 1 smod= 0
- m5_15= 0 spare= 0
- m5_90= 1 icmi= 1
- m6_70= 0 nscb= 0
- m7_40= 1 ver= 1
- m7_95= 0
- m10_2= 0
- m12_2= 1
+ cfg.smod=0 spare=0 icmi=1 nscb=0 ver=1
+ m4_75=1 ------- m5_90=1 ------- m7_40=1 ------- ------- m12_2=1
Result invalid!
diff --git a/tests/gsm23003/gsm23003_test.c b/tests/gsm23003/gsm23003_test.c
index e73ef43a..77d3173e 100644
--- a/tests/gsm23003/gsm23003_test.c
+++ b/tests/gsm23003/gsm23003_test.c
@@ -54,7 +54,7 @@ static struct {
{ NULL, false },
};
-bool test_valid_imsi()
+bool test_valid_imsi(void)
{
int i;
bool pass = true;
@@ -97,7 +97,7 @@ static struct {
{ NULL, false },
};
-bool test_valid_msisdn()
+bool test_valid_msisdn(void)
{
int i;
bool pass = true;
@@ -138,7 +138,7 @@ static struct {
{ NULL, false, false },
};
-bool test_valid_imei()
+bool test_valid_imei(void)
{
int i;
bool pass = true;
@@ -185,7 +185,7 @@ static struct test_mnc_from_str test_mnc_from_strs[] = {
{ "023 ", { -EINVAL, 0, false } },
};
-static bool test_mnc_from_str()
+static bool test_mnc_from_str(void)
{
int i;
bool pass = true;
@@ -209,7 +209,7 @@ static bool test_mnc_from_str()
return pass;
}
-static bool test_gummei_name()
+static bool test_gummei_name(void)
{
static const struct osmo_gummei gummei = {
.plmn = { .mcc = 901, .mnc = 70 },
@@ -226,7 +226,7 @@ static bool test_gummei_name()
return pass;
}
-static bool test_domain_gen()
+static bool test_domain_gen(void)
{
static const struct osmo_gummei gummei = {
.plmn = { .mcc = 901, .mnc = 70 },
@@ -252,7 +252,7 @@ static bool test_domain_gen()
}
-static bool test_domain_parse()
+static bool test_domain_parse(void)
{
static const char *mme_dom_valid = "mmec01.mmegiA001.mme.epc.mnc070.mcc901.3gppnetwork.org";
static const char *home_dom_valid = "epc.mnc070.mcc901.3gppnetwork.org";
diff --git a/tests/gsm23236/gsm23236_test.c b/tests/gsm23236/gsm23236_test.c
index 77e20e38..6cbdbeb2 100644
--- a/tests/gsm23236/gsm23236_test.c
+++ b/tests/gsm23236/gsm23236_test.c
@@ -138,7 +138,7 @@ struct nri_v_get_set_test nri_v_get_set_tests[] = {
},
};
-void test_nri_v_get_set()
+void test_nri_v_get_set(void)
{
struct nri_v_get_set_test *t;
@@ -222,7 +222,7 @@ struct nri_validate_tc nri_validate_tests[] = {
{ .nri = INT16_MAX, .nri_bitlen = 0, .expect_rc = 1 },
};
-void test_nri_validate()
+void test_nri_validate(void)
{
struct nri_validate_tc *t;
printf("\n%s()\n", __func__);
@@ -327,7 +327,7 @@ struct nri_range_validate_tc nri_range_validate_tests[] = {
};
-void test_nri_range_validate()
+void test_nri_range_validate(void)
{
struct nri_range_validate_tc *t;
printf("\n%s()\n", __func__);
@@ -357,7 +357,7 @@ void dump_list(const struct osmo_nri_ranges *nri_ranges)
printf("};\n");
}
-void test_nri_list()
+void test_nri_list(void)
{
struct osmo_nri_ranges *nri_ranges = osmo_nri_ranges_alloc(ctx);
printf("\n%s()\n", __func__);
@@ -545,7 +545,7 @@ void test_nri_list()
DEL(100, 1);
}
-void test_nri_limit_by_ranges()
+void test_nri_limit_by_ranges(void)
{
const uint8_t nri_bitlen = 8;
const int16_t expect_nri_vals[] = { 10, 20, 21, 30, 31, 32 };
@@ -594,7 +594,7 @@ void test_nri_limit_by_ranges()
}
}
-int main()
+int main(int argc, char **argv)
{
ctx = talloc_named_const(NULL, 0, "nri_test");
diff --git a/tests/gsm29205/gsm29205_test.c b/tests/gsm29205/gsm29205_test.c
index 5d4835b7..6598f894 100644
--- a/tests/gsm29205/gsm29205_test.c
+++ b/tests/gsm29205/gsm29205_test.c
@@ -28,7 +28,7 @@
#include <string.h>
#include <errno.h>
-static void test_gcr()
+static void test_gcr(void)
{
static const uint8_t res[] = {
0x03, /* .net_len */
diff --git a/tests/gsm44021/frame_csd_test.c b/tests/gsm44021/frame_csd_test.c
new file mode 100644
index 00000000..98efdacb
--- /dev/null
+++ b/tests/gsm44021/frame_csd_test.c
@@ -0,0 +1,93 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <unistd.h>
+
+#include <osmocom/core/bits.h>
+#include <osmocom/isdn/v110.h>
+#include <osmocom/gsm/gsm44021.h>
+
+
+static void fill_v110_frame(struct osmo_v110_decoded_frame *fr)
+{
+ unsigned int i;
+
+ memset(fr, 0, sizeof(*fr));
+
+ /* we abuse the fact that ubit_t is 8bit so we can actually
+ * store integer values to clearly identify which bit ends up where */
+
+ /* D1..D48: 101..148 */
+ for (i = 0; i < ARRAY_SIZE(fr->d_bits); i++)
+ fr->d_bits[i] = 101 + i;
+ /* E1..E7: 201..207 */
+ for (i = 0; i < ARRAY_SIZE(fr->e_bits); i++)
+ fr->e_bits[i] = 201 + i;
+ /* S1..S9: 211..219 */
+ for (i = 0; i < ARRAY_SIZE(fr->s_bits); i++)
+ fr->s_bits[i] = 211 + i;
+ /* X1..X2: 221..222 */
+ for (i = 0; i < ARRAY_SIZE(fr->x_bits); i++)
+ fr->x_bits[i] = 221 + i;
+}
+
+
+static void test_frame_enc_12k_6k(void)
+{
+ struct osmo_v110_decoded_frame fr;
+ ubit_t bits[60];
+
+ printf("Testing Frame Encoding for 12k/6k radio interface rate\n");
+
+ fill_v110_frame(&fr);
+
+ /* run encoder and dump to stdout */
+ memset(bits, 0xff, sizeof(bits));
+ osmo_csd_12k_6k_encode_frame(bits, sizeof(bits), &fr);
+ osmo_csd_ubit_dump(stdout, bits, sizeof(bits));
+
+ /* run decoder on what we just encoded */
+ memset(&fr, 0, sizeof(fr));
+ osmo_csd_12k_6k_decode_frame(&fr, bits, sizeof(bits));
+
+ /* re-encode and dump again 'expout' will match it. */
+ memset(bits, 0xff, sizeof(bits));
+ osmo_csd_12k_6k_encode_frame(bits, sizeof(bits), &fr);
+ osmo_csd_ubit_dump(stdout, bits, sizeof(bits));
+}
+
+static void test_frame_enc_3k6(void)
+{
+ struct osmo_v110_decoded_frame fr;
+ ubit_t bits[36];
+
+ printf("Testing Frame Encoding for 3.6k radio interface rate\n");
+
+ fill_v110_frame(&fr);
+ /* different D-bit numbering for 3k6, see TS 44.021 Section 8.1.4 */
+ for (unsigned int i = 0; i < ARRAY_SIZE(fr.d_bits); i++)
+ fr.d_bits[i] = 101 + i/2;
+
+ /* run encoder and dump to stdout */
+ memset(bits, 0xff, sizeof(bits));
+ osmo_csd_3k6_encode_frame(bits, sizeof(bits), &fr);
+ osmo_csd_ubit_dump(stdout, bits, sizeof(bits));
+
+ /* run decoder on what we just encoded */
+ memset(&fr, 0, sizeof(fr));
+ osmo_csd_3k6_decode_frame(&fr, bits, sizeof(bits));
+
+ /* re-encode and dump again 'expout' will match it. */
+ memset(bits, 0xff, sizeof(bits));
+ osmo_csd_3k6_encode_frame(bits, sizeof(bits), &fr);
+ osmo_csd_ubit_dump(stdout, bits, sizeof(bits));
+}
+
+
+int main(int argc, char **argv)
+{
+ test_frame_enc_12k_6k();
+ printf("\n");
+ test_frame_enc_3k6();
+}
+
diff --git a/tests/gsm44021/frame_csd_test.ok b/tests/gsm44021/frame_csd_test.ok
new file mode 100644
index 00000000..1a5d3f25
--- /dev/null
+++ b/tests/gsm44021/frame_csd_test.ok
@@ -0,0 +1,31 @@
+Testing Frame Encoding for 12k/6k radio interface rate
+101 102 103 104 105 106 211
+107 108 109 110 111 112 221
+113 114 115 116 117 118 213
+119 120 121 122 123 124 214
+204 205 206 207 125 126 127
+128 129 130 216 131 132 133
+134 135 136 222 137 138 139
+140 141 142 218 143 144 145
+146 147 148 219
+101 102 103 104 105 106 211
+107 108 109 110 111 112 221
+113 114 115 116 117 118 213
+119 120 121 122 123 124 214
+204 205 206 207 125 126 127
+128 129 130 216 131 132 133
+134 135 136 222 137 138 139
+140 141 142 218 143 144 145
+146 147 148 219
+
+Testing Frame Encoding for 3.6k radio interface rate
+101 102 103 211 104 105 106 221
+107 108 109 213 110 111 112 214
+204 205 206 207 113 114 115 216
+116 117 118 222 119 120 121 218
+122 123 124 219
+101 102 103 211 104 105 106 221
+107 108 109 213 110 111 112 214
+204 205 206 207 113 114 115 216
+116 117 118 222 119 120 121 218
+122 123 124 219
diff --git a/tests/gsm48/rest_octets_test.c b/tests/gsm48/rest_octets_test.c
index fdab1432..6e11f941 100644
--- a/tests/gsm48/rest_octets_test.c
+++ b/tests/gsm48/rest_octets_test.c
@@ -1,5 +1,5 @@
/*
- * (C) 2021 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de>
+ * (C) 2021 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* Author: Pau Espin Pedrol <pespin@sysmocom.de>
* All Rights Reserved
*
@@ -77,7 +77,7 @@ static const struct si13_test test_si13_arr[] = {
},
};
-static void test_si13()
+static void test_si13(void)
{
int i, rc;
uint8_t data[GSM_MACBLOCK_LEN];
diff --git a/tests/i460_mux/i460_mux_test.c b/tests/i460_mux/i460_mux_test.c
index 7695cb4a..1be35e7d 100644
--- a/tests/i460_mux/i460_mux_test.c
+++ b/tests/i460_mux/i460_mux_test.c
@@ -1,7 +1,7 @@
#include <osmocom/core/utils.h>
-#include <osmocom/gsm/i460_mux.h>
+#include <osmocom/isdn/i460_mux.h>
static void bits_cb(struct osmo_i460_subchan *schan, void *user_data,
const ubit_t *bits, unsigned int num_bits)
diff --git a/tests/lapd/lapd_test.c b/tests/lapd/lapd_test.c
index ab0f3160..2f2a7f42 100644
--- a/tests/lapd/lapd_test.c
+++ b/tests/lapd/lapd_test.c
@@ -184,6 +184,7 @@ static int send(struct msgb *in_msg, struct lapdm_channel *chan)
/* LAPDm requires those... */
pp.u.data.chan_nr = 0;
pp.u.data.link_id = 0;
+ pp.u.data.fn = 0;
/* feed into the LAPDm code of libosmogsm */
rc = lapdm_phsap_up(&pp.oph, &chan->lapdm_dcch);
OSMO_ASSERT(rc == 0 || rc == -EBUSY);
@@ -206,6 +207,7 @@ static int send_buf(const uint8_t *buf, size_t len, struct lapdm_channel *chan)
/* LAPDm requires those... */
pp.u.data.chan_nr = 0;
pp.u.data.link_id = 0;
+ pp.u.data.fn = 0;
/* feed into the LAPDm code of libosmogsm */
rc = lapdm_phsap_up(&pp.oph, &chan->lapdm_dcch);
OSMO_ASSERT(rc == 0 || rc == -EBUSY);
@@ -376,7 +378,7 @@ static int ms_to_bts_tx_cb(struct msgb *msg, struct lapdm_entity *le, void *_ctx
return 0;
}
-static void test_lapdm_polling()
+static void test_lapdm_polling(void)
{
printf("I do some very simple LAPDm test.\n");
@@ -470,7 +472,7 @@ static void test_lapdm_polling()
lapdm_channel_exit(&ms_to_bts_channel);
}
-static void test_lapdm_contention_resolution()
+static void test_lapdm_contention_resolution(void)
{
printf("I test contention resultion by having two mobiles collide and "
"first mobile repeating SABM.\n");
@@ -523,7 +525,7 @@ static void test_lapdm_contention_resolution()
lapdm_channel_exit(&bts_to_ms_channel);
}
-static void test_lapdm_early_release()
+static void test_lapdm_early_release(void)
{
printf("I test RF channel release of an unestablished channel.\n");
@@ -603,7 +605,7 @@ static void lapdm_establish(const uint8_t *est_req, size_t est_req_size)
msgb_free(msg);
}
-static void test_lapdm_establishment()
+static void test_lapdm_establishment(void)
{
printf("I test RF channel establishment.\n");
printf("Testing SAPI3/SDCCH\n");
@@ -677,7 +679,7 @@ static void dump_queue(struct llist_head *head)
printf("\n");
}
-static void test_lapdm_desync()
+static void test_lapdm_desync(void)
{
printf("I test if desync problems exist in LAPDm\n");
diff --git a/tests/logging/logging_vty_test.c b/tests/logging/logging_vty_test.c
index f67746b2..c1a28533 100644
--- a/tests/logging/logging_vty_test.c
+++ b/tests/logging/logging_vty_test.c
@@ -78,7 +78,7 @@ DEFUN(log_sweep, log_sweep_cmd,
return CMD_SUCCESS;
}
-static void vty_commands_init()
+static void vty_commands_init(void)
{
install_element_ve(&log_sweep_cmd);
}
@@ -121,7 +121,7 @@ const struct log_info log_info = {
.num_cat = ARRAY_SIZE(default_categories),
};
-static void print_help()
+static void print_help(void)
{
printf( "options:\n"
" -h --help this text\n"
@@ -261,7 +261,7 @@ int main(int argc, char **argv)
}
}
- rc = telnet_init_dynif(root_ctx, NULL, vty_get_bind_addr(), 42042);
+ rc = telnet_init_default(root_ctx, NULL, 42042);
if (rc < 0)
return 2;
@@ -285,10 +285,10 @@ int main(int argc, char **argv)
osmo_select_main(0);
}
- log_fini();
+ talloc_report(tall_vty_ctx, stderr);
+ talloc_report_full(root_ctx, stderr);
- talloc_free(root_ctx);
- talloc_free(tall_vty_ctx);
+ log_fini();
return 0;
}
diff --git a/tests/logging/logging_vty_test.vty b/tests/logging/logging_vty_test.vty
index 2cff62c1..da09be76 100644
--- a/tests/logging/logging_vty_test.vty
+++ b/tests/logging/logging_vty_test.vty
@@ -54,7 +54,7 @@ logging_vty_test# list
logging print level (0|1)
logging print file (0|1|basename) [last]
logging set-log-mask MASK
- logging level (aa|bb|ccc|dddd|eee|lglobal|llapd|linp|lmux|lmi|lmib|lsms|lctrl|lgtp|lstats|lgsup|loap|lss7|lsccp|lsua|lm3ua|lmgcp|ljibuf|lrspro|lns|lbssgp|lnsdata|lnssignal|liuup|lpfcp|lcsn1) (debug|info|notice|error|fatal)
+ logging level (aa|bb|ccc|dddd|eee|lglobal|llapd|linp|lmux|lmi|lmib|lsms|lctrl|lgtp|lstats|lgsup|loap|lss7|lsccp|lsua|lm3ua|lmgcp|ljibuf|lrspro|lns|lbssgp|lnsdata|lnssignal|liuup|lpfcp|lcsn1|lio) (debug|info|notice|error|fatal)
logging level set-all (debug|info|notice|error|fatal)
logging level force-all (debug|info|notice|error|fatal)
no logging level force-all
@@ -70,6 +70,10 @@ logging_vty_test# logging ?
set-log-mask Set the logmask of this logging target
level Set the log level for a specified category
+logging_vty_test# logging timestamp ?
+ 0 Don't prefix each log message
+ 1 Prefix each log message with current timestamp
+
logging_vty_test# logging level ?
... ! all
aa Antropomorphic Armadillos (AA)
@@ -109,6 +113,98 @@ logging_vty_test# logging level set-all ?
error Log error messages and higher levels
fatal Log only fatal messages
+logging_vty_test# logging print ?
+ extended-timestamp Configure log message timestamping
+ thread-id Configure log message logging Thread ID
+ category Configure log message
+ category-hex Configure log message
+ level Configure log message
+ file Configure log message
+
+logging_vty_test# logging print extended-timestamp ?
+ 0 Don't prefix each log message
+ 1 Prefix each log message with current timestamp with YYYYMMDDhhmmssnnn
+
+logging_vty_test# logging print thread-id ?
+ 0 Don't prefix each log message
+ 1 Prefix each log message with current Thread ID
+
+logging_vty_test# logging print category ?
+ 0 Don't prefix each log message
+ 1 Prefix each log message with category/subsystem name
+
+logging_vty_test# logging print category-hex ?
+ 0 Don't prefix each log message
+ 1 Prefix each log message with category/subsystem nr in hex ('<000b>')
+
+logging_vty_test# logging print level ?
+ 0 Don't prefix each log message
+ 1 Prefix each log message with the log level name
+
+logging_vty_test# logging print file ?
+ 0 Don't prefix each log message
+ 1 Prefix each log message with the source file and line
+ basename Prefix each log message with the source file's basename (strip leading paths) and line
+
+logging_vty_test# logging print file basename ?
+ [last] Log source file info at the end of a log line. If omitted, log source file info just before the log text.
+
+logging_vty_test# configure terminal
+logging_vty_test(config)# log stderr
+logging_vty_test(config-log)# show running-config
+...
+log stderr
+... !timestamp
+ logging timestamp 0
+... !timestamp
+
+logging_vty_test(config-log)# logging timestamp 1
+logging_vty_test(config-log)# show running-config
+...
+log stderr
+... !timestamp
+ logging timestamp 1
+... !timestamp
+
+logging_vty_test(config-log)# ### with 'extended-timestamp 1', 'logging timestamp' is not shown
+logging_vty_test(config-log)# logging print extended-timestamp 1
+logging_vty_test(config-log)# show running-config
+...
+log stderr
+... !timestamp
+ logging print extended-timestamp 1
+... !timestamp
+
+logging_vty_test(config-log)# ### 'logging timestamp 0' effect not shown while 'extended-timestamp' == 1
+logging_vty_test(config-log)# logging timestamp 0
+logging_vty_test(config-log)# show running-config
+...
+log stderr
+... !timestamp
+ logging print extended-timestamp 1
+... !timestamp
+
+logging_vty_test(config-log)# ### 'logging timestamp 1' remains set upon 'extended-timestamp 0'
+logging_vty_test(config-log)# logging timestamp 1
+logging_vty_test(config-log)# logging print extended-timestamp 0
+logging_vty_test(config-log)# show running-config
+...
+log stderr
+... !timestamp
+ logging timestamp 1
+... !timestamp
+
+logging_vty_test(config-log)# logging timestamp 0
+logging_vty_test(config-log)# show running-config
+...
+log stderr
+... !timestamp
+ logging timestamp 0
+... !timestamp
+
+logging_vty_test(config-log)# exit
+logging_vty_test(config)# no log stderr
+logging_vty_test(config)# exit
logging_vty_test# log-sweep
DAA DEBUG Log message for DAA on level LOGL_DEBUG
@@ -472,7 +568,7 @@ DEEE FATAL Log message for DEEE on level LOGL_FATAL
logging_vty_test# list
...
- logp (aa|bb|ccc|dddd|eee|lglobal|llapd|linp|lmux|lmi|lmib|lsms|lctrl|lgtp|lstats|lgsup|loap|lss7|lsccp|lsua|lm3ua|lmgcp|ljibuf|lrspro|lns|lbssgp|lnsdata|lnssignal|liuup|lpfcp|lcsn1) (debug|info|notice|error|fatal) .LOGMESSAGE
+ logp (aa|bb|ccc|dddd|eee|lglobal|llapd|linp|lmux|lmi|lmib|lsms|lctrl|lgtp|lstats|lgsup|loap|lss7|lsccp|lsua|lm3ua|lmgcp|ljibuf|lrspro|lns|lbssgp|lnsdata|lnssignal|liuup|lpfcp|lcsn1|lio) (debug|info|notice|error|fatal) .LOGMESSAGE
...
logging_vty_test# logp?
@@ -510,6 +606,7 @@ logging_vty_test# logp ?
liuup Iu UP layer
lpfcp libosmo-pfcp Packet Forwarding Control Protocol
lcsn1 libosmo-csn1 Concrete Syntax Notation 1 codec
+ lio libosmocore IO Subsystem
logging_vty_test# logp lglobal ?
debug Log debug messages and higher levels
diff --git a/tests/msgb/msgb_test.c b/tests/msgb/msgb_test.c
index aebb68cc..7966103b 100644
--- a/tests/msgb/msgb_test.c
+++ b/tests/msgb/msgb_test.c
@@ -61,7 +61,7 @@ static int osmo_panic_try(volatile int *exception, int setjmp_result)
return *exception == 0;
}
-static void test_msgb_api()
+static void test_msgb_api(void)
{
struct msgb *msg = msgb_alloc_headroom(4096, 128, "data");
unsigned char *cptr = NULL;
@@ -117,7 +117,7 @@ static void test_msgb_api()
msgb_free(msg);
}
-static void test_msgb_api_errors()
+static void test_msgb_api_errors(void)
{
struct msgb *msg = msgb_alloc_headroom(4096, 128, "data");
volatile int e = 0;
@@ -138,7 +138,7 @@ static void test_msgb_api_errors()
osmo_set_panic_handler(NULL);
}
-static void test_msgb_copy()
+static void test_msgb_copy(void)
{
struct msgb *msg = msgb_alloc_headroom(4096, 128, "data");
struct msgb *msg2;
@@ -161,19 +161,31 @@ static void test_msgb_copy()
OSMO_ASSERT(msgb_l1len(msg) == msgb_l1len(msg2));
OSMO_ASSERT(msgb_l2len(msg) == msgb_l2len(msg2));
OSMO_ASSERT(msgb_l3len(msg) == msgb_l3len(msg2));
- OSMO_ASSERT(msg->tail - msg->l4h == msg2->tail - msg2->l4h);
+ OSMO_ASSERT(msgb_l4len(msg) == msgb_l4len(msg2));
- for (i = 0; i < msgb_length(msg2); i++)
- OSMO_ASSERT(msg2->data[i] == (uint8_t)i);
+ if (!msgb_eq_data_print(msg2, msg->data, msgb_length(msg)))
+ printf("copy test failed!\n");
+
+ if (!msgb_eq_l1_data_print(msg2, msgb_l1(msg), msgb_l1len(msg)))
+ printf("copy test failed at L1!\n");
+ if (!msgb_eq_l2_data_print(msg2, msgb_l2(msg), msgb_l2len(msg)))
+ printf("copy test failed at L2!\n");
+ if (!msgb_eq_l3_data_print(msg2, msgb_l3(msg), msgb_l3len(msg)))
+ printf("copy test failed at L3!\n");
+ if (!msgb_eq_l4_data_print(msg2, msgb_l4(msg), msgb_l4len(msg)))
+ printf("copy test failed at L4!\n");
printf("Src: %s\n", msgb_hexdump(msg));
- printf("Dst: %s\n", msgb_hexdump(msg));
+ printf("Dst: %s\n", msgb_hexdump(msg2));
+
+ OSMO_ASSERT(msgb_test_invariant(msg));
+ OSMO_ASSERT(msgb_test_invariant(msg2));
msgb_free(msg);
msgb_free(msg2);
}
-static void test_msgb_resize_area()
+static void test_msgb_resize_area(void)
{
struct msgb *msg = msgb_alloc_headroom(4096, 128, "data");
int rc;
@@ -273,7 +285,7 @@ static void test_msgb_resize_area()
osmo_set_panic_handler(NULL);
}
-static void test_msgb_printf()
+static void test_msgb_printf(void)
{
struct msgb *msg;
struct msgb *msg_ref;
diff --git a/tests/osmo-auc-gen/osmo-auc-gen_test.ok b/tests/osmo-auc-gen/osmo-auc-gen_test.ok
index 3c41f41b..2da0b024 100644
--- a/tests/osmo-auc-gen/osmo-auc-gen_test.ok
+++ b/tests/osmo-auc-gen/osmo-auc-gen_test.ok
@@ -1,7 +1,7 @@
> osmo-auc-gen -3 -a milenage -r 6a61050765caa32c90371370e5d6dc2d -k 1dc4f974325cce611e54f516dc1fec56 -o 2a48162ff3edca4adf0b7b5e527d6c16 -s 0
-osmo-auc-gen (C) 2011-2012 by Harald Welte
+osmo-auc-gen (C) 2011-2023 by Harald Welte
This is FREE SOFTWARE with ABSOLUTELY NO WARRANTY
RAND: 6a61050765caa32c90371370e5d6dc2d
@@ -18,7 +18,7 @@ IND: 0
> osmo-auc-gen -3 -a milenage -r 6a61050765caa32c90371370e5d6dc2d -k 1dc4f974325cce611e54f516dc1fec56 -o 2a48162ff3edca4adf0b7b5e527d6c16 -s 1
-osmo-auc-gen (C) 2011-2012 by Harald Welte
+osmo-auc-gen (C) 2011-2023 by Harald Welte
This is FREE SOFTWARE with ABSOLUTELY NO WARRANTY
RAND: 6a61050765caa32c90371370e5d6dc2d
@@ -35,7 +35,7 @@ IND: 1
> osmo-auc-gen -3 -a milenage -r 6a61050765caa32c90371370e5d6dc2d -k 1dc4f974325cce611e54f516dc1fec56 -o 2a48162ff3edca4adf0b7b5e527d6c16 -s 23
-osmo-auc-gen (C) 2011-2012 by Harald Welte
+osmo-auc-gen (C) 2011-2023 by Harald Welte
This is FREE SOFTWARE with ABSOLUTELY NO WARRANTY
RAND: 6a61050765caa32c90371370e5d6dc2d
@@ -52,7 +52,7 @@ IND: 23
> osmo-auc-gen -3 -a milenage -r 1dc4f974325cce611e54f516dc1fec56 -k 2a48162ff3edca4adf0b7b5e527d6c16 -o 6a61050765caa32c90371370e5d6dc2d -s 42
-osmo-auc-gen (C) 2011-2012 by Harald Welte
+osmo-auc-gen (C) 2011-2023 by Harald Welte
This is FREE SOFTWARE with ABSOLUTELY NO WARRANTY
RAND: 1dc4f974325cce611e54f516dc1fec56
@@ -69,7 +69,7 @@ IND: 10
> osmo-auc-gen -3 -a milenage -r 2a48162ff3edca4adf0b7b5e527d6c16 -k 6a61050765caa32c90371370e5d6dc2d -o 1dc4f974325cce611e54f516dc1fec56 -s 99
-osmo-auc-gen (C) 2011-2012 by Harald Welte
+osmo-auc-gen (C) 2011-2023 by Harald Welte
This is FREE SOFTWARE with ABSOLUTELY NO WARRANTY
RAND: 2a48162ff3edca4adf0b7b5e527d6c16
@@ -86,7 +86,7 @@ IND: 3
> osmo-auc-gen -3 -a milenage -r 6a61050765caa32c90371370e5d6dc2d -k 2a48162ff3edca4adf0b7b5e527d6c16 -o 1dc4f974325cce611e54f516dc1fec56 -s 281474976710655
-osmo-auc-gen (C) 2011-2012 by Harald Welte
+osmo-auc-gen (C) 2011-2023 by Harald Welte
This is FREE SOFTWARE with ABSOLUTELY NO WARRANTY
RAND: 6a61050765caa32c90371370e5d6dc2d
@@ -103,7 +103,7 @@ IND: 31
> osmo-auc-gen -3 -a milenage -r 39fa2f4e3d523d8619a73b4f65c3e14d -k EB215756028D60E3275E613320AEC880 -o FB2A3D1B360F599ABAB99DB8669F8308 -A 979498b1f72d3e28c59fa2e72f9c
-osmo-auc-gen (C) 2011-2012 by Harald Welte
+osmo-auc-gen (C) 2011-2023 by Harald Welte
This is FREE SOFTWARE with ABSOLUTELY NO WARRANTY
RAND: 39fa2f4e3d523d8619a73b4f65c3e14d
@@ -121,7 +121,7 @@ SQN.MS: 23
> osmo-auc-gen -3 -a milenage -r 39fa2f4e3d523d8619a73b4f65c3e14d -k EB215756028D60E3275E613320AEC880 -o FB2A3D1B360F599ABAB99DB8669F8308 -A 979498b1f72d3e28c59fa2e72f9c --ind 5
-osmo-auc-gen (C) 2011-2012 by Harald Welte
+osmo-auc-gen (C) 2011-2023 by Harald Welte
This is FREE SOFTWARE with ABSOLUTELY NO WARRANTY
RAND: 39fa2f4e3d523d8619a73b4f65c3e14d
@@ -139,7 +139,7 @@ SQN.MS: 23
> osmo-auc-gen -3 -a milenage -r 39fa2f4e3d523d8619a73b4f65c3e14d -k EB215756028D60E3275E613320AEC880 -o FB2A3D1B360F599ABAB99DB8669F8308 -A 979498b1f72d3e28c59fa2e72f9c --ind 23
-osmo-auc-gen (C) 2011-2012 by Harald Welte
+osmo-auc-gen (C) 2011-2023 by Harald Welte
This is FREE SOFTWARE with ABSOLUTELY NO WARRANTY
RAND: 39fa2f4e3d523d8619a73b4f65c3e14d
@@ -157,7 +157,7 @@ SQN.MS: 23
> osmo-auc-gen -3 -a milenage -r 39fa2f4e3d523d8619a73b4f65c3e14d -k EB215756028D60E3275E613320AEC880 -o FB2A3D1B360F599ABAB99DB8669F8308 -A 979498b1f72d3e28c59fa2e72f9c --ind 31
-osmo-auc-gen (C) 2011-2012 by Harald Welte
+osmo-auc-gen (C) 2011-2023 by Harald Welte
This is FREE SOFTWARE with ABSOLUTELY NO WARRANTY
RAND: 39fa2f4e3d523d8619a73b4f65c3e14d
@@ -175,7 +175,7 @@ SQN.MS: 23
> osmo-auc-gen -3 -a milenage -r 39fa2f4e3d523d8619a73b4f65c3e14d -k EB215756028D60E3275E613320AEC880 -o FB2A3D1B360F599ABAB99DB8669F8308 -A 979498b1f72d3e28c59fa2e72f9c --ind-len 0
-osmo-auc-gen (C) 2011-2012 by Harald Welte
+osmo-auc-gen (C) 2011-2023 by Harald Welte
This is FREE SOFTWARE with ABSOLUTELY NO WARRANTY
RAND: 39fa2f4e3d523d8619a73b4f65c3e14d
@@ -193,7 +193,7 @@ SQN.MS: 23
> osmo-auc-gen -3 -a milenage -r 39fa2f4e3d523d8619a73b4f65c3e14d -k EB215756028D60E3275E613320AEC880 -o FB2A3D1B360F599ABAB99DB8669F8308 -A 979498b1f72d3e28c59fa2e72f9c --ind-len 1
-osmo-auc-gen (C) 2011-2012 by Harald Welte
+osmo-auc-gen (C) 2011-2023 by Harald Welte
This is FREE SOFTWARE with ABSOLUTELY NO WARRANTY
RAND: 39fa2f4e3d523d8619a73b4f65c3e14d
@@ -211,7 +211,7 @@ SQN.MS: 23
> osmo-auc-gen -3 -a milenage -r 39fa2f4e3d523d8619a73b4f65c3e14d -k EB215756028D60E3275E613320AEC880 -o FB2A3D1B360F599ABAB99DB8669F8308 -A 979498b1f72d3e28c59fa2e72f9c --ind-len 1 --ind 1
-osmo-auc-gen (C) 2011-2012 by Harald Welte
+osmo-auc-gen (C) 2011-2023 by Harald Welte
This is FREE SOFTWARE with ABSOLUTELY NO WARRANTY
RAND: 39fa2f4e3d523d8619a73b4f65c3e14d
@@ -229,7 +229,7 @@ SQN.MS: 23
> osmo-auc-gen -3 -a milenage -r 39fa2f4e3d523d8619a73b4f65c3e14d -k EB215756028D60E3275E613320AEC880 -o FB2A3D1B360F599ABAB99DB8669F8308 -A 979498b1f72d3e28c59fa2e72f9c --ind-len 8
-osmo-auc-gen (C) 2011-2012 by Harald Welte
+osmo-auc-gen (C) 2011-2023 by Harald Welte
This is FREE SOFTWARE with ABSOLUTELY NO WARRANTY
RAND: 39fa2f4e3d523d8619a73b4f65c3e14d
@@ -247,7 +247,7 @@ SQN.MS: 23
> osmo-auc-gen -3 -a milenage -r 39fa2f4e3d523d8619a73b4f65c3e14d -k EB215756028D60E3275E613320AEC880 -o FB2A3D1B360F599ABAB99DB8669F8308 -A 979498b1f72d3e28c59fa2e72f9c --ind-len 8 --ind 1
-osmo-auc-gen (C) 2011-2012 by Harald Welte
+osmo-auc-gen (C) 2011-2023 by Harald Welte
This is FREE SOFTWARE with ABSOLUTELY NO WARRANTY
RAND: 39fa2f4e3d523d8619a73b4f65c3e14d
@@ -266,27 +266,27 @@ SQN.MS: 23
expecting error:
> osmo-auc-gen -3 -a milenage -r 39fa2f4e3d523d8619a73b4f65c3e14d -k EB215756028D60E3275E613320AEC880 -o FB2A3D1B360F599ABAB99DB8669F8308 -A 979498b1f72d3e28c59fa2e72f9c --ind -1
-osmo-auc-gen (C) 2011-2012 by Harald Welte
+osmo-auc-gen (C) 2011-2023 by Harald Welte
This is FREE SOFTWARE with ABSOLUTELY NO WARRANTY
expecting error:
> osmo-auc-gen -3 -a milenage -r 39fa2f4e3d523d8619a73b4f65c3e14d -k EB215756028D60E3275E613320AEC880 -o FB2A3D1B360F599ABAB99DB8669F8308 -A 979498b1f72d3e28c59fa2e72f9c --ind 32
-osmo-auc-gen (C) 2011-2012 by Harald Welte
+osmo-auc-gen (C) 2011-2023 by Harald Welte
This is FREE SOFTWARE with ABSOLUTELY NO WARRANTY
expecting error:
> osmo-auc-gen -3 -a milenage -r 39fa2f4e3d523d8619a73b4f65c3e14d -k EB215756028D60E3275E613320AEC880 -o FB2A3D1B360F599ABAB99DB8669F8308 -A 979498b1f72d3e28c59fa2e72f9c --ind 42
-osmo-auc-gen (C) 2011-2012 by Harald Welte
+osmo-auc-gen (C) 2011-2023 by Harald Welte
This is FREE SOFTWARE with ABSOLUTELY NO WARRANTY
expecting error:
> osmo-auc-gen -3 -a milenage -r 39fa2f4e3d523d8619a73b4f65c3e14d -k EB215756028D60E3275E613320AEC880 -o FB2A3D1B360F599ABAB99DB8669F8308 -A 979498b1f72d3e28c59fa2e72f9c --ind-len 0 --ind 1
-osmo-auc-gen (C) 2011-2012 by Harald Welte
+osmo-auc-gen (C) 2011-2023 by Harald Welte
This is FREE SOFTWARE with ABSOLUTELY NO WARRANTY
diff --git a/tests/osmo_io/osmo_io_test.c b/tests/osmo_io/osmo_io_test.c
new file mode 100644
index 00000000..93beef4f
--- /dev/null
+++ b/tests/osmo_io/osmo_io_test.c
@@ -0,0 +1,178 @@
+/*
+ * (C) 2023 by sysmocom s.f.m.c
+ * Author: Daniel Willmann <daniel@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 <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <errno.h>
+
+#include <osmocom/core/application.h>
+#include <osmocom/core/bits.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/osmo_io.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/utils.h>
+
+#include "config.h"
+
+#define TEST_START() printf("Running %s\n", __func__)
+
+static uint8_t TESTDATA[] = {
+ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16
+};
+
+
+static void *ctx = NULL;
+
+static void read_cb(struct osmo_io_fd *iofd, int rc, struct msgb *msg)
+{
+ printf("%s: read() msg with len=%d\n", osmo_iofd_get_name(iofd), rc);
+ if (msg)
+ printf("%s\n", osmo_hexdump(msgb_data(msg), msgb_length(msg)));
+
+ talloc_free(msg);
+}
+
+static void write_cb(struct osmo_io_fd *iofd, int rc, struct msgb *msg)
+{
+ uint8_t *buf;
+ printf("%s: write() returned rc=%d\n", osmo_iofd_get_name(iofd), rc);
+ if (rc == 0) {
+ msg = msgb_alloc(1024, "Test data");
+ buf = msgb_put(msg, sizeof(TESTDATA));
+ memcpy(buf, TESTDATA, sizeof(TESTDATA));
+
+ osmo_iofd_write_msgb(iofd, msg);
+ }
+}
+
+struct osmo_io_ops ioops_conn_read_write = {
+ .read_cb = read_cb,
+ .write_cb = write_cb,
+};
+
+static void test_connected(void)
+{
+ int fds[2] = {0, 0}, rc;
+ struct osmo_io_fd *iofd1, *iofd2;
+
+ TEST_START();
+
+ rc = socketpair(AF_UNIX, SOCK_STREAM, 0, fds);
+ OSMO_ASSERT(rc == 0);
+
+ iofd1 = osmo_iofd_setup(ctx, fds[0], "ep1", OSMO_IO_FD_MODE_READ_WRITE, &ioops_conn_read_write, NULL);
+ osmo_iofd_register(iofd1, fds[0]);
+ iofd2 = osmo_iofd_setup(ctx, fds[1], "ep2", OSMO_IO_FD_MODE_READ_WRITE, &ioops_conn_read_write, NULL);
+ osmo_iofd_register(iofd2, fds[1]);
+ // Explicitly check if ep1 is connected through write_cb
+ osmo_iofd_notify_connected(iofd1);
+
+ /* Allow enough cycles to handle the messages */
+ for (int i = 0; i < 128; i++)
+ osmo_select_main(1);
+
+ osmo_iofd_free(iofd1);
+ osmo_iofd_free(iofd2);
+
+ for (int i = 0; i < 128; i++)
+ osmo_select_main(1);
+}
+
+static void recvfrom_cb(struct osmo_io_fd *iofd, int rc, struct msgb *msg,
+ const struct osmo_sockaddr *saddr)
+{
+ printf("%s: recvfrom() msg with len=%d\n", osmo_iofd_get_name(iofd), rc);
+ if (msg)
+ printf("%s\n", osmo_hexdump(msgb_data(msg), msgb_length(msg)));
+
+ talloc_free(msg);
+}
+
+static void sendto_cb(struct osmo_io_fd *iofd, int rc, struct msgb *msg,
+ const struct osmo_sockaddr *daddr)
+{
+ printf("%s: sendto() returned rc=%d\n", osmo_iofd_get_name(iofd), rc);
+}
+
+struct osmo_io_ops ioops_conn_recvfrom_sendto = {
+ .sendto_cb = sendto_cb,
+ .recvfrom_cb = recvfrom_cb,
+};
+
+static void test_unconnected(void)
+{
+ int fds[2] = {0, 0}, rc;
+ struct osmo_io_fd *iofd1, *iofd2;
+ struct msgb *msg;
+ uint8_t *buf;
+
+ TEST_START();
+
+ rc = socketpair(AF_UNIX, SOCK_DGRAM, 0, fds);
+ OSMO_ASSERT(rc == 0);
+
+ iofd1 = osmo_iofd_setup(ctx, fds[0], "ep1", OSMO_IO_FD_MODE_RECVFROM_SENDTO, &ioops_conn_recvfrom_sendto, NULL);
+ osmo_iofd_register(iofd1, fds[0]);
+ iofd2 = osmo_iofd_setup(ctx, fds[1], "ep2", OSMO_IO_FD_MODE_RECVFROM_SENDTO, &ioops_conn_recvfrom_sendto, NULL);
+ osmo_iofd_register(iofd2, fds[1]);
+
+ msg = msgb_alloc(1024, "Test data");
+ buf = msgb_put(msg, sizeof(TESTDATA));
+ memcpy(buf, TESTDATA, sizeof(TESTDATA));
+
+ osmo_iofd_sendto_msgb(iofd1, msg, 0, NULL);
+
+ /* Allow enough cycles to handle the messages */
+ for (int i = 0; i < 128; i++)
+ osmo_select_main(1);
+
+ osmo_iofd_free(iofd1);
+ osmo_iofd_free(iofd2);
+
+ for (int i = 0; i < 128; i++)
+ osmo_select_main(1);
+}
+static const struct log_info_cat default_categories[] = {
+};
+
+static struct log_info info = {
+ .cat = default_categories,
+ .num_cat = ARRAY_SIZE(default_categories),
+};
+
+int main(int argc, char *argv[])
+{
+ ctx = talloc_named_const(NULL, 0, "osmo_io_test");
+ osmo_init_logging2(ctx, &info);
+ log_set_use_color(osmo_stderr_target, 0);
+ log_set_print_filename2(osmo_stderr_target, LOG_FILENAME_NONE);
+ log_set_print_category(osmo_stderr_target, 0);
+ log_set_print_category_hex(osmo_stderr_target, 0);
+
+ test_connected();
+ test_unconnected();
+
+ return EXIT_SUCCESS;
+}
diff --git a/tests/osmo_io/osmo_io_test.err b/tests/osmo_io/osmo_io_test.err
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/tests/osmo_io/osmo_io_test.err
diff --git a/tests/osmo_io/osmo_io_test.ok b/tests/osmo_io/osmo_io_test.ok
new file mode 100644
index 00000000..6527c191
--- /dev/null
+++ b/tests/osmo_io/osmo_io_test.ok
@@ -0,0 +1,9 @@
+Running test_connected
+ep1: write() returned rc=0
+ep1: write() returned rc=16
+ep2: read() msg with len=16
+01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10
+Running test_unconnected
+ep1: sendto() returned rc=16
+ep2: recvfrom() msg with len=16
+01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10
diff --git a/tests/sms/sms_test.c b/tests/sms/sms_test.c
index 9ca83a55..912c0829 100644
--- a/tests/sms/sms_test.c
+++ b/tests/sms/sms_test.c
@@ -215,7 +215,7 @@ static const struct test_case test_decode[] =
},
};
-static void test_octet_return()
+static void test_octet_return(void)
{
char out[256];
int oct, septets;
diff --git a/tests/smscb/cbsp_test.c b/tests/smscb/cbsp_test.c
index d530798d..2dbdded7 100644
--- a/tests/smscb/cbsp_test.c
+++ b/tests/smscb/cbsp_test.c
@@ -81,7 +81,7 @@ static struct msgb *msgb_from_hex(unsigned int size, const char *hex)
return msg;
}
-static void test_decode()
+static void test_decode(void)
{
struct msgb *msg;
struct osmo_cbsp_decoded *cbsp_dec;
diff --git a/tests/sockaddr_str/sockaddr_str_test.c b/tests/sockaddr_str/sockaddr_str_test.c
index 0d0674b6..541c3a15 100644
--- a/tests/sockaddr_str/sockaddr_str_test.c
+++ b/tests/sockaddr_str/sockaddr_str_test.c
@@ -90,7 +90,7 @@ void dump_oip(const struct osmo_sockaddr_str *oip)
printf("{ .af = %s, .ip = %s, .port = %u }\n", af_name(oip->af), osmo_quote_str(oip->ip, -1), oip->port);
}
-void sockaddr_str_test_conversions()
+void sockaddr_str_test_conversions(void)
{
int i;
char buf[1024];
@@ -235,7 +235,7 @@ void sockaddr_str_test_conversions()
}
-static void test_osmo_sockaddr_str_cmp()
+static void test_osmo_sockaddr_str_cmp(void)
{
int i;
printf("\n\n%s\n", __func__);
diff --git a/tests/socket/socket_sctp_test.c b/tests/socket/socket_sctp_test.c
index b318fe5d..e099be92 100644
--- a/tests/socket/socket_sctp_test.c
+++ b/tests/socket/socket_sctp_test.c
@@ -31,7 +31,7 @@
#include <osmocom/core/logging.h>
#include <osmocom/core/bits.h>
-#include "../config.h"
+#include "config.h"
void *ctx = NULL;
diff --git a/tests/socket/socket_sctp_test.err b/tests/socket/socket_sctp_test.err
index 996d0922..7583a2a2 100644
--- a/tests/socket/socket_sctp_test.err
+++ b/tests/socket/socket_sctp_test.err
@@ -1,8 +1,8 @@
invalid: you have to specify either BIND or CONNECT flags
-Invalid v4 vs v6 in local vs remote addresses
-Invalid v4 vs v6 in local vs remote addresses
+Invalid v4 vs v6 in local vs remote addresses: local: v4 remote: v6
+Invalid v4 vs v6 in local vs remote addresses: local: v6 remote: v4
invalid: you have to specify either BIND or CONNECT flags
-Invalid v4 vs v6 in local vs remote addresses
-Invalid v4 vs v6 in local vs remote addresses
+Invalid v4 vs v6 in local vs remote addresses: local: v4 remote: v6
+Invalid v4 vs v6 in local vs remote addresses: local: v6 remote: v4
getaddrinfo(::1, 0) failed: Address family for hostname not supported
getaddrinfo(127.0.0.1, 0) failed: Address family for hostname not supported
diff --git a/tests/socket/socket_test.c b/tests/socket/socket_test.c
index fe77b92e..ddb69268 100644
--- a/tests/socket/socket_test.c
+++ b/tests/socket/socket_test.c
@@ -32,7 +32,7 @@
#include <osmocom/core/logging.h>
#include <osmocom/core/bits.h>
-#include "../config.h"
+#include "config.h"
void *ctx = NULL;
@@ -150,7 +150,7 @@ static int test_sockinit2(void)
return 0;
}
-static int test_get_ip_and_port()
+static int test_get_ip_and_port(void)
{
int fd, rc;
char ip[INET6_ADDRSTRLEN] = { };
@@ -385,6 +385,63 @@ static void test_osa_str(void)
OSMO_ASSERT(!strncmp("[2003:1234:5678:90ab:cdef:1234:4321:4321]:23420", result, sizeof(buf)));
}
+static void test_osa_netmask_prefixlen(void)
+{
+ struct osmo_sockaddr ipv4;
+ struct osmo_sockaddr ipv6;
+ int rc;
+
+ ipv4.u.sin = (struct sockaddr_in){
+ .sin_family = AF_INET,
+ };
+
+ ipv4.u.sin.sin_addr.s_addr = inet_addr("0.0.0.0");
+ rc = osmo_sockaddr_netmask_to_prefixlen(&ipv4);
+ OSMO_ASSERT(rc == 0);
+
+ ipv4.u.sin.sin_addr.s_addr = inet_addr("255.0.0.0");
+ rc = osmo_sockaddr_netmask_to_prefixlen(&ipv4);
+ OSMO_ASSERT(rc == 8);
+
+ ipv4.u.sin.sin_addr.s_addr = inet_addr("255.255.0.0");
+ rc = osmo_sockaddr_netmask_to_prefixlen(&ipv4);
+ OSMO_ASSERT(rc == 16);
+
+ ipv4.u.sin.sin_addr.s_addr = inet_addr("255.255.255.0");
+ rc = osmo_sockaddr_netmask_to_prefixlen(&ipv4);
+ OSMO_ASSERT(rc == 24);
+
+ ipv4.u.sin.sin_addr.s_addr = inet_addr("255.255.255.255");
+ rc = osmo_sockaddr_netmask_to_prefixlen(&ipv4);
+ OSMO_ASSERT(rc == 32);
+
+ ipv4.u.sin.sin_addr.s_addr = inet_addr("0.255.0.0");
+ rc = osmo_sockaddr_netmask_to_prefixlen(&ipv4);
+ /* FIXME: This shows the implementation is not that robust checking validity of input netmask: */
+ OSMO_ASSERT(rc == 8);
+
+ ipv6.u.sin6 = (struct sockaddr_in6){
+ .sin6_family = AF_INET6,
+ };
+
+ inet_pton(AF_INET6, "fe::", &ipv6.u.sin6.sin6_addr);
+ rc = osmo_sockaddr_netmask_to_prefixlen(&ipv6);
+ OSMO_ASSERT(rc == 7);
+
+ inet_pton(AF_INET6, "ff::", &ipv6.u.sin6.sin6_addr);
+ rc = osmo_sockaddr_netmask_to_prefixlen(&ipv6);
+ OSMO_ASSERT(rc == 8);
+
+ inet_pton(AF_INET6, "ff:ff::", &ipv6.u.sin6.sin6_addr);
+ rc = osmo_sockaddr_netmask_to_prefixlen(&ipv6);
+ OSMO_ASSERT(rc == 16);
+
+ inet_pton(AF_INET6, "ff:ff::ff", &ipv6.u.sin6.sin6_addr);
+ rc = osmo_sockaddr_netmask_to_prefixlen(&ipv6);
+ /* FIXME: This shows the implementation is not that robust checking validity of input netmask: */
+ OSMO_ASSERT(rc == 24);
+}
+
const struct log_info_cat default_categories[] = {
};
@@ -407,6 +464,7 @@ int main(int argc, char *argv[])
test_get_ip_and_port();
test_sockinit_osa();
test_osa_str();
+ test_osa_netmask_prefixlen();
return EXIT_SUCCESS;
}
diff --git a/tests/stats/stats_test.c b/tests/stats/stats_test.c
index 4e362234..eda4129b 100644
--- a/tests/stats/stats_test.c
+++ b/tests/stats/stats_test.c
@@ -1,6 +1,6 @@
/* tests for statistics */
/*
- * (C) 2015 sysmocom - s.m.f.c. GmbH
+ * (C) 2015 sysmocom - s.f.m.c. GmbH
*
* All Rights Reserved
*
@@ -340,7 +340,7 @@ static void _do_report(int expect_counter_vals, int expect_stat_item_vals, int l
#define do_report(A, B) _do_report(A, B, __LINE__)
-static void test_reporting()
+static void test_reporting(void)
{
struct osmo_stats_reporter *srep1, *srep2, *srep;
struct osmo_stat_item_group *statg1, *statg2;
diff --git a/tests/stats/stats_vty_test.c b/tests/stats/stats_vty_test.c
index 09b125ac..29cbf5ec 100644
--- a/tests/stats/stats_vty_test.c
+++ b/tests/stats/stats_vty_test.c
@@ -70,7 +70,7 @@ int main(int argc, char **argv)
osmo_stats_vty_add_cmds();
- rc = telnet_init_dynif(root_ctx, NULL, vty_get_bind_addr(), 42042);
+ rc = telnet_init_default(root_ctx, NULL, 42042);
if (rc < 0)
return 2;
diff --git a/tests/tdef/tdef_test.c b/tests/tdef/tdef_test.c
index c348e694..a0fcc9a9 100644
--- a/tests/tdef/tdef_test.c
+++ b/tests/tdef/tdef_test.c
@@ -125,7 +125,7 @@ static void test_tdef_get(bool test_range)
}
}
-static void test_tdef_get_nonexisting()
+static void test_tdef_get_nonexisting(void)
{
printf("\n%s()\n", __func__);
@@ -136,7 +136,7 @@ static void test_tdef_get_nonexisting()
print_tdef_get(tdefs, 5, OSMO_TDEF_US);
}
-static void test_tdef_set_and_get()
+static void test_tdef_set_and_get(void)
{
struct osmo_tdef *t;
printf("\n%s()\n", __func__);
@@ -317,7 +317,7 @@ const struct timeval fake_time_start_time = { 123, 456 };
osmo_timers_update(); \
} while (0)
-void fake_time_start()
+void fake_time_start(void)
{
struct timespec *clock_override;
diff --git a/tests/tdef/tdef_vty_config_root_test.c b/tests/tdef/tdef_vty_config_root_test.c
index aa5ff1b6..8c46d958 100644
--- a/tests/tdef/tdef_vty_config_root_test.c
+++ b/tests/tdef/tdef_vty_config_root_test.c
@@ -111,7 +111,7 @@ static int config_write_timer(struct vty *vty)
return CMD_SUCCESS;
}
-static void timer_init_vty()
+static void timer_init_vty(void)
{
/* Again, this is merely to get a vty write hook, see above. */
install_node(&timer_node, config_write_timer);
@@ -123,7 +123,7 @@ static void timer_init_vty()
void *root_ctx = NULL;
-static void print_help()
+static void print_help(void)
{
printf( "options:\n"
" -h --help this text\n"
@@ -260,7 +260,7 @@ int main(int argc, char **argv)
}
}
- rc = telnet_init_dynif(root_ctx, NULL, vty_get_bind_addr(), 42042);
+ rc = telnet_init_default(root_ctx, NULL, 42042);
if (rc < 0)
return 2;
diff --git a/tests/tdef/tdef_vty_config_subnode_test.c b/tests/tdef/tdef_vty_config_subnode_test.c
index 90c79682..e3e165da 100644
--- a/tests/tdef/tdef_vty_config_subnode_test.c
+++ b/tests/tdef/tdef_vty_config_subnode_test.c
@@ -102,7 +102,7 @@ static int config_write_gsmnet(struct vty *vty)
return CMD_SUCCESS;
}
-static void gsmnet_init_vty()
+static void gsmnet_init_vty(void)
{
install_node(&gsmnet_node, config_write_gsmnet);
install_element(CONFIG_NODE, &cfg_net_cmd);
@@ -116,7 +116,7 @@ static void gsmnet_init_vty()
void *root_ctx = NULL;
-static void print_help()
+static void print_help(void)
{
printf( "options:\n"
" -h --help this text\n"
@@ -253,7 +253,7 @@ int main(int argc, char **argv)
}
}
- rc = telnet_init_dynif(root_ctx, NULL, vty_get_bind_addr(), 42042);
+ rc = telnet_init_default(root_ctx, NULL, 42042);
if (rc < 0)
return 2;
diff --git a/tests/tdef/tdef_vty_dynamic_test.c b/tests/tdef/tdef_vty_dynamic_test.c
index a181d91b..b646c54e 100644
--- a/tests/tdef/tdef_vty_dynamic_test.c
+++ b/tests/tdef/tdef_vty_dynamic_test.c
@@ -179,7 +179,7 @@ static int config_write_member(struct vty *vty)
return CMD_SUCCESS;
}
-static void member_init_vty()
+static void member_init_vty(void)
{
install_node(&member_node, config_write_member);
install_element(CONFIG_NODE, &cfg_member_cmd);
@@ -190,7 +190,7 @@ static void member_init_vty()
/* ------------------- THE REST is just boilerplate osmo main() ------------------- */
-static void print_help()
+static void print_help(void)
{
printf( "options:\n"
" -h --help this text\n"
@@ -327,7 +327,7 @@ int main(int argc, char **argv)
}
}
- rc = telnet_init_dynif(root_ctx, NULL, vty_get_bind_addr(), 42042);
+ rc = telnet_init_default(root_ctx, NULL, 42042);
if (rc < 0)
return 2;
diff --git a/tests/testsuite.at b/tests/testsuite.at
index 6b2e75f9..33669956 100644
--- a/tests/testsuite.at
+++ b/tests/testsuite.at
@@ -126,6 +126,12 @@ cat $abs_srcdir/auth/milenage_test.ok > expout
AT_CHECK([$abs_top_builddir/tests/auth/milenage_test], [0], [expout], [ignore])
AT_CLEANUP
+AT_SETUP([auth_xor2g])
+AT_KEYWORDS([auth_xor2g])
+cat $abs_srcdir/auth/xor2g_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/auth/xor2g_test], [0], [expout], [ignore])
+AT_CLEANUP
+
AT_SETUP([comp128])
AT_KEYWORDS([comp128])
cat $abs_srcdir/comp128/comp128_test.ok > expout
@@ -484,3 +490,29 @@ AT_KEYWORDS([iuup])
cat $abs_srcdir/iuup/iuup_test.ok > expout
AT_CHECK([$abs_top_builddir/tests/iuup/iuup_test], [0], [expout], [ignore])
AT_CLEANUP
+
+AT_SETUP([v110_frame_test])
+AT_KEYWORDS([v110_frame_test])
+cat $abs_srcdir/v110/frame_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/v110/frame_test], [], [expout],[])
+AT_CLEANUP
+
+AT_SETUP([v110_ra1_test])
+AT_KEYWORDS([v110_ra1_test])
+cat $abs_srcdir/v110/ra1_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/v110/ra1_test], [], [expout],[])
+AT_CLEANUP
+
+AT_SETUP([gsm44021_frame_csd_test])
+AT_KEYWORDS([gsm44021_frame_csd_test])
+cat $abs_srcdir/gsm44021/frame_csd_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/gsm44021/frame_csd_test], [], [expout],[])
+AT_CLEANUP
+
+AT_SETUP([osmo_io])
+AT_KEYWORDS([osmo_io])
+cat $abs_srcdir/osmo_io/osmo_io_test.ok > expout
+cat $abs_srcdir/osmo_io/osmo_io_test.err > experr
+touch experr
+AT_CHECK([$abs_top_builddir/tests/osmo_io/osmo_io_test], [0], [expout], [experr])
+AT_CLEANUP
diff --git a/tests/time_cc/time_cc_test.c b/tests/time_cc/time_cc_test.c
index 22ea7f65..e4a5aaf3 100644
--- a/tests/time_cc/time_cc_test.c
+++ b/tests/time_cc/time_cc_test.c
@@ -125,7 +125,7 @@ static const struct log_info log_info = {
.num_cat = ARRAY_SIZE(log_categories),
};
-int main()
+int main(int argc, char **argv)
{
void *ctx = talloc_named_const(NULL, 0, "time_cc_test");
struct timespec *now;
diff --git a/tests/timer/timer_test.c b/tests/timer/timer_test.c
index b06c7740..9c51ad94 100644
--- a/tests/timer/timer_test.c
+++ b/tests/timer/timer_test.c
@@ -29,7 +29,7 @@
#include <osmocom/core/select.h>
#include <osmocom/core/linuxlist.h>
-#include "../config.h"
+#include "config.h"
static void main_timer_fired(void *data);
static void secondary_timer_fired(void *data);
diff --git a/tests/tlv/tlv_test.c b/tests/tlv/tlv_test.c
index fdd15ab0..8e8bd603 100644
--- a/tests/tlv/tlv_test.c
+++ b/tests/tlv/tlv_test.c
@@ -188,7 +188,7 @@ static void check_lv_shift_data_len(size_t data_len,
}
}
-static void test_tlv_shift_functions()
+static void test_tlv_shift_functions(void)
{
uint8_t test_data[1024];
uint8_t buf[1024];
@@ -250,7 +250,7 @@ static void test_tlv_shift_functions()
/* Most GSM related protocols clearly indicate that in case of duplicate
* IEs, only the first occurrence shall be used, while any further occurrences
* shall be ignored. See e.g. 3GPP TS 24.008 Section 8.6.3 */
-static void test_tlv_repeated_ie()
+static void test_tlv_repeated_ie(void)
{
uint8_t test_data[768];
int i, rc;
@@ -288,7 +288,7 @@ static void test_tlv_repeated_ie()
OSMO_ASSERT(dec3[2].lv[tag].val == &test_data[2 + 3 + 3]);
}
-static void test_tlv_encoder()
+static void test_tlv_encoder(void)
{
const uint8_t enc_ies[] = {
0x17, 0x14, 0x06, 0x2b, 0x12, 0x2b, 0x0b, 0x40, 0x2b, 0xb7, 0x05, 0xd0, 0x63, 0x82, 0x95, 0x03, 0x05, 0x40,
@@ -332,7 +332,7 @@ static void test_tlv_encoder()
msgb_free(msg);
}
-static void test_tlv_parser_bounds()
+static void test_tlv_parser_bounds(void)
{
struct tlv_definition tdef;
struct tlv_parsed dec;
@@ -423,7 +423,7 @@ static void test_tlv_parser_bounds()
OSMO_ASSERT(TLVP_VAL(&dec, 0x23) == NULL);
}
-static void test_tlv_lens()
+static void test_tlv_lens(void)
{
uint16_t buf_len;
uint8_t buf[512];
@@ -454,6 +454,30 @@ static void test_tlv_lens()
}
}
+static void test_tlv_type_single_tv(void)
+{
+ #define SAMPLE_SINGLE_TV_IE 0x08
+ const struct tlv_definition att_tlvdef = {
+ .def = {
+ [SAMPLE_SINGLE_TV_IE] = { TLV_TYPE_SINGLE_TV, 0 },
+ },
+ };
+ struct tlv_parsed tp;
+ int rc;
+ uint8_t exp_val = 0x03;
+ uint8_t buf[] = { (SAMPLE_SINGLE_TV_IE << 4) | (exp_val & 0x0f) };
+ const uint8_t *val;
+
+ rc = tlv_parse(&tp, &att_tlvdef, buf, sizeof(buf), 0, 0);
+ OSMO_ASSERT(rc == 1);
+ OSMO_ASSERT(TLVP_PRESENT(&tp, SAMPLE_SINGLE_TV_IE));
+ val = TLVP_VAL(&tp, SAMPLE_SINGLE_TV_IE);
+ OSMO_ASSERT(val);
+ OSMO_ASSERT(val == &buf[0]);
+ OSMO_ASSERT(*val == buf[0]);
+ OSMO_ASSERT((*val & 0x0f) == exp_val);
+}
+
int main(int argc, char **argv)
{
//osmo_init_logging2(ctx, &info);
@@ -463,6 +487,7 @@ int main(int argc, char **argv)
test_tlv_encoder();
test_tlv_parser_bounds();
test_tlv_lens();
+ test_tlv_type_single_tv();
printf("Done.\n");
return EXIT_SUCCESS;
diff --git a/tests/use_count/use_count_test.c b/tests/use_count/use_count_test.c
index 2942c697..b784aeb4 100644
--- a/tests/use_count/use_count_test.c
+++ b/tests/use_count/use_count_test.c
@@ -192,7 +192,7 @@ static struct foo *foo_alloc(const char *name, size_t static_entries)
return foo;
}
-void print_foos()
+void print_foos(void)
{
int count = 0;
struct foo *foo;
@@ -204,7 +204,7 @@ void print_foos()
fprintf(stderr, "%d foos\n\n", count);
}
-static void test_use_count_fsm()
+static void test_use_count_fsm(void)
{
struct foo *a, *b, *c;
log("\n%s()\n", __func__);
diff --git a/tests/utils/utils_test.c b/tests/utils/utils_test.c
index 3ac5d1e5..0b7bfe44 100644
--- a/tests/utils/utils_test.c
+++ b/tests/utils/utils_test.c
@@ -342,7 +342,7 @@ static struct {
{ "DeafBeddedBabeAcceededFadedDecaff", 32, 32, false, false },
};
-bool test_is_hexstr()
+bool test_is_hexstr(void)
{
int i;
bool pass = true;
@@ -766,6 +766,65 @@ static void isqrt_test(void)
}
}
+static void mod_test_mod(int x, int y, int expected_result)
+{
+ int result;
+ result = x % y;
+ printf(" %d mod %d = %d = %d\n", x, y, result, expected_result);
+ OSMO_ASSERT(result == expected_result);
+}
+
+static void mod_test_mod_flr(int x, int y, int expected_result)
+{
+ int result;
+ result = OSMO_MOD_FLR(x, y);
+ printf(" %d mod_flr %d = %d = %d\n", x, y, result, expected_result);
+ OSMO_ASSERT(result == expected_result);
+}
+
+static void mod_test_mod_euc(int x, int y, int expected_result)
+{
+ int result;
+ result = OSMO_MOD_EUC(x, y);
+ printf(" %d mod_euc %d = %d = %d\n", x, y, result, expected_result);
+ OSMO_ASSERT(result == expected_result);
+}
+
+static void mod_test(void)
+{
+ /* See also: Daan Leijen, Division and Modulus for Computer
+ * Scientists, section 1.3 */
+
+ printf("\nTesting built in truncated modulo for comparison:\n");
+ mod_test_mod(8, 3, 2);
+ mod_test_mod(8, -3, 2);
+ mod_test_mod(-8, 3, -2);
+ mod_test_mod(-8, -3, -2);
+ mod_test_mod(1, 2, 1);
+ mod_test_mod(1, -2, 1);
+ mod_test_mod(-1, 2, -1);
+ mod_test_mod(-1, -2, -1);
+
+ printf("\nTesting OSMO_MOD_FLR():\n");
+ mod_test_mod_flr(8, 3, 2);
+ mod_test_mod_flr(8, -3, -1);
+ mod_test_mod_flr(-8, 3, 1);
+ mod_test_mod_flr(-8, -3, -2);
+ mod_test_mod_flr(1, 2, 1);
+ mod_test_mod_flr(1, -2, -1);
+ mod_test_mod_flr(-1, 2, 1);
+ mod_test_mod_flr(-1, -2, -1);
+
+ printf("\nTesting OSMO_MOD_EUC():\n");
+ mod_test_mod_euc(8, 3, 2);
+ mod_test_mod_euc(8, -3, 2);
+ mod_test_mod_euc(-8, 3, 1);
+ mod_test_mod_euc(-8, -3, 1);
+ mod_test_mod_euc(1, 2, 1);
+ mod_test_mod_euc(1, -2, 1);
+ mod_test_mod_euc(-1, 2, 1);
+ mod_test_mod_euc(-1, -2, 1);
+}
struct osmo_sockaddr_to_str_and_uint_test_case {
uint16_t port;
@@ -1023,7 +1082,7 @@ struct osmo_str_tolowupper_test_data osmo_str_tolowupper_tests[] = {
};
-static void osmo_str_tolowupper_test()
+static void osmo_str_tolowupper_test(void)
{
int i;
char buf[128];
@@ -1197,7 +1256,7 @@ int strbuf_cascade(char *buf, size_t buflen)
return sb.chars_needed;
}
-void strbuf_test()
+void strbuf_test(void)
{
char buf[256];
int rc;
@@ -1230,7 +1289,7 @@ void strbuf_test()
printf("(need %d chars, had size=63) %s\n", rc, buf);
}
-void strbuf_test_nolen()
+void strbuf_test_nolen(void)
{
char buf[20];
struct osmo_strbuf sb = { .buf = buf, .len = sizeof(buf) };
@@ -1256,7 +1315,7 @@ static void startswith_test_str(const char *str, const char *startswith_str, boo
printf(" ERROR: EXPECTED %s\n", expect_rc ? "true" : "false");
}
-static void startswith_test()
+static void startswith_test(void)
{
printf("\n%s()\n", __func__);
startswith_test_str(NULL, NULL, true);
@@ -1301,7 +1360,7 @@ static char *foo_name_c_zero_null(void *ctx, const char *arg)
OSMO_NAME_C_IMPL(ctx, 0, NULL, foo_name_buf, arg)
}
-static void name_c_impl_test()
+static void name_c_impl_test(void)
{
char *test_strs[] = {
"test",
@@ -1703,7 +1762,7 @@ const char *errno_str(int rc)
return "";
}
}
-void test_float_str_to_int()
+void test_float_str_to_int(void)
{
const struct float_str_to_int_test *t;
printf("--- %s\n", __func__);
@@ -1865,7 +1924,7 @@ struct int_to_float_str_test int_to_float_str_tests[] = {
{ 23, -9223372036854775807, "-0.00009223372036854775807" },
{ 23, INT64_MIN, "-ERR" },
};
-void test_int_to_float_str()
+void test_int_to_float_str(void)
{
const struct int_to_float_str_test *t;
printf("--- %s\n", __func__);
@@ -1956,7 +2015,7 @@ struct str_to_int_test str_to_int_tests[] = {
{ "123 ", 10, -1000, 1000, -E2BIG, 123 },
{ "123.4", 10, -1000, 1000, -E2BIG, 123 },
};
-void test_str_to_int()
+void test_str_to_int(void)
{
const struct str_to_int_test *t;
printf("--- %s\n", __func__);
@@ -2053,7 +2112,7 @@ struct str_to_int64_test str_to_int64_tests[] = {
{ "-9223372036854775809", 10, -1000, 1000, -EOVERFLOW, INT64_MIN },
{ "9223372036854775808", 10, -1000, 1000, -EOVERFLOW, INT64_MAX },
};
-void test_str_to_int64()
+void test_str_to_int64(void)
{
const struct str_to_int64_test *t;
printf("--- %s\n", __func__);
@@ -2088,6 +2147,7 @@ int main(int argc, char **argv)
str_escape3_test();
str_quote3_test();
isqrt_test();
+ mod_test();
osmo_sockaddr_to_str_and_uint_test();
osmo_str_tolowupper_test();
strbuf_test();
diff --git a/tests/utils/utils_test.ok b/tests/utils/utils_test.ok
index 1409d81a..3f453e95 100644
--- a/tests/utils/utils_test.ok
+++ b/tests/utils/utils_test.ok
@@ -354,6 +354,36 @@ strcmp("NULL", osmo_quote_cstr_c(ctx, NULL, -1)) == 0
Testing integer square-root
+Testing built in truncated modulo for comparison:
+ 8 mod 3 = 2 = 2
+ 8 mod -3 = 2 = 2
+ -8 mod 3 = -2 = -2
+ -8 mod -3 = -2 = -2
+ 1 mod 2 = 1 = 1
+ 1 mod -2 = 1 = 1
+ -1 mod 2 = -1 = -1
+ -1 mod -2 = -1 = -1
+
+Testing OSMO_MOD_FLR():
+ 8 mod_flr 3 = 2 = 2
+ 8 mod_flr -3 = -1 = -1
+ -8 mod_flr 3 = 1 = 1
+ -8 mod_flr -3 = -2 = -2
+ 1 mod_flr 2 = 1 = 1
+ 1 mod_flr -2 = -1 = -1
+ -1 mod_flr 2 = 1 = 1
+ -1 mod_flr -2 = -1 = -1
+
+Testing OSMO_MOD_EUC():
+ 8 mod_euc 3 = 2 = 2
+ 8 mod_euc -3 = 2 = 2
+ -8 mod_euc 3 = 1 = 1
+ -8 mod_euc -3 = 1 = 1
+ 1 mod_euc 2 = 1 = 1
+ 1 mod_euc -2 = 1 = 1
+ -1 mod_euc 2 = 1 = 1
+ -1 mod_euc -2 = 1 = 1
+
osmo_sockaddr_to_str_and_uint_test
[0] [0.0.0.0]:0 addr_len=20 --> [0.0.0.0]:0 rc=7
[1] [255.255.255.255]:65535 addr_len=20 --> [255.255.255.255]:65535 rc=15
diff --git a/tests/v110/frame_test.c b/tests/v110/frame_test.c
new file mode 100644
index 00000000..ebc617c0
--- /dev/null
+++ b/tests/v110/frame_test.c
@@ -0,0 +1,54 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <unistd.h>
+
+#include <osmocom/core/bits.h>
+#include <osmocom/isdn/v110.h>
+
+
+static void test_frame_enc(void)
+{
+ struct osmo_v110_decoded_frame fr;
+ ubit_t bits[80];
+ unsigned int i;
+
+ memset(&fr, 0, sizeof(fr));
+
+ /* we abuse the fact that ubit_t is 8bit so we can actually
+ * store integer values to clearly identify which bit ends up where */
+
+ /* D1..D48: 101..148 */
+ for (i = 0; i < ARRAY_SIZE(fr.d_bits); i++)
+ fr.d_bits[i] = 101 + i;
+ /* E1..E7: 201..207 */
+ for (i = 0; i < ARRAY_SIZE(fr.e_bits); i++)
+ fr.e_bits[i] = 201 + i;
+ /* S1..S9: 211..219 */
+ for (i = 0; i < ARRAY_SIZE(fr.s_bits); i++)
+ fr.s_bits[i] = 211 + i;
+ /* X1..X2: 221..222 */
+ for (i = 0; i < ARRAY_SIZE(fr.x_bits); i++)
+ fr.x_bits[i] = 221 + i;
+
+ /* run encoder and dump to stdout */
+ memset(bits, 0xff, sizeof(bits));
+ osmo_v110_encode_frame(bits, sizeof(bits), &fr);
+ osmo_v110_ubit_dump(stdout, bits, sizeof(bits));
+
+ /* run decoder on what we just encoded */
+ memset(&fr, 0, sizeof(fr));
+ osmo_v110_decode_frame(&fr, bits, sizeof(bits));
+
+ /* re-encode and dump again 'expout' will match it. */
+ memset(bits, 0xff, sizeof(bits));
+ osmo_v110_encode_frame(bits, sizeof(bits), &fr);
+ osmo_v110_ubit_dump(stdout, bits, sizeof(bits));
+}
+
+
+int main(int argc, char **argv)
+{
+ test_frame_enc();
+}
+
diff --git a/tests/v110/frame_test.ok b/tests/v110/frame_test.ok
new file mode 100644
index 00000000..ecefaa87
--- /dev/null
+++ b/tests/v110/frame_test.ok
@@ -0,0 +1,20 @@
+0 0 0 0 0 0 0 0
+1 101 102 103 104 105 106 211
+1 107 108 109 110 111 112 221
+1 113 114 115 116 117 118 213
+1 119 120 121 122 123 124 214
+1 201 202 203 204 205 206 207
+1 125 126 127 128 129 130 216
+1 131 132 133 134 135 136 222
+1 137 138 139 140 141 142 218
+1 143 144 145 146 147 148 219
+0 0 0 0 0 0 0 0
+1 101 102 103 104 105 106 211
+1 107 108 109 110 111 112 221
+1 113 114 115 116 117 118 213
+1 119 120 121 122 123 124 214
+1 201 202 203 204 205 206 207
+1 125 126 127 128 129 130 216
+1 131 132 133 134 135 136 222
+1 137 138 139 140 141 142 218
+1 143 144 145 146 147 148 219
diff --git a/tests/v110/ra1_test.c b/tests/v110/ra1_test.c
new file mode 100644
index 00000000..48db553d
--- /dev/null
+++ b/tests/v110/ra1_test.c
@@ -0,0 +1,74 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <unistd.h>
+
+#include <osmocom/core/bits.h>
+#include <osmocom/isdn/v110.h>
+
+
+static void test_ra1(enum osmo_v100_sync_ra1_rate rate)
+{
+ int user_rate = osmo_v110_sync_ra1_get_user_data_rate(rate);
+ int user_data_chunk_bits;
+ struct osmo_v110_decoded_frame fr;
+ ubit_t user_bits[48];
+ ubit_t bits[80];
+ unsigned int i;
+ int rc;
+
+ printf("\n======= User data rate %u\n", user_rate);
+
+ user_data_chunk_bits = osmo_v110_sync_ra1_get_user_data_chunk_bitlen(rate);
+ OSMO_ASSERT(user_data_chunk_bits >= 0);
+
+ /* we abuse the fact that ubit_t is 8bit so we can actually
+ * store integer values to clearly identify which bit ends up where */
+ memset(user_bits, 0xFE, sizeof(user_bits));
+ for (i = 0; i < user_data_chunk_bits; i++)
+ user_bits[i] = 101 + i;
+
+ printf("user_bits: ");
+ for (i = 0; i < user_data_chunk_bits; i++)
+ printf("%03d ", user_bits[i]);
+ printf("\n");
+
+ /* generate the decoded v.110 frame */
+ memset(&fr, 0, sizeof(fr));
+ rc = osmo_v110_sync_ra1_user_to_ir(rate, &fr, user_bits, user_data_chunk_bits);
+ OSMO_ASSERT(rc == 0);
+
+ /* run encoder and dump to stdout */
+ memset(bits, 0xff, sizeof(bits));
+ osmo_v110_encode_frame(bits, sizeof(bits), &fr);
+ printf("dumping %u encoded bits in V.110 frame:\n", user_data_chunk_bits);
+ osmo_v110_ubit_dump(stdout, bits, sizeof(bits));
+
+ /* run decoder on what we just encoded */
+ memset(&fr, 0, sizeof(fr));
+ osmo_v110_decode_frame(&fr, bits, sizeof(bits));
+ printf("dumping re-decoded V.110 frame:\n");
+ printf("E-bits: %s\n", osmo_hexdump(fr.e_bits, sizeof(fr.e_bits)));
+ printf("S-bits: %s\n", osmo_hexdump(fr.s_bits, sizeof(fr.s_bits)));
+
+ /* re-encode and dump again 'expout' will match it. */
+ memset(user_bits, 0xff, sizeof(user_bits));
+ rc = osmo_v110_sync_ra1_ir_to_user(rate, user_bits, sizeof(user_bits), &fr);
+ if (rc != user_data_chunk_bits) {
+ fprintf(stderr, "ERROR: adapt_ir_to_user() returned %d, expected %u\n", rc,
+ user_data_chunk_bits);
+ exit(23);
+ }
+ fprintf(stdout, "re-decoded user bits: ");
+ for (i = 0; i < user_data_chunk_bits; i++)
+ printf("%03d ", user_bits[i]);
+ printf("\n");
+}
+
+
+int main(int argc, char **argv)
+{
+ for (int i = 0; i < _NUM_OSMO_V110_SYNC_RA1; i++)
+ test_ra1(i);
+}
+
diff --git a/tests/v110/ra1_test.ok b/tests/v110/ra1_test.ok
new file mode 100644
index 00000000..8a61fc3f
--- /dev/null
+++ b/tests/v110/ra1_test.ok
@@ -0,0 +1,216 @@
+
+======= User data rate 600
+user_bits: 101 102 103 104 105 106
+dumping 6 encoded bits in V.110 frame:
+0 0 0 0 0 0 0 0
+1 101 101 101 101 101 101 0
+1 101 101 102 102 102 102 0
+1 102 102 102 102 103 103 0
+1 103 103 103 103 103 103 0
+1 1 0 0 0 0 0 0
+1 104 104 104 104 104 104 0
+1 104 104 105 105 105 105 0
+1 105 105 105 105 106 106 0
+1 106 106 106 106 106 106 0
+dumping re-decoded V.110 frame:
+E-bits: 01 00 00 00 00 00 00
+S-bits: 00 00 00 00 00 00 00 00 00
+re-decoded user bits: 101 102 103 104 105 106
+
+======= User data rate 1200
+user_bits: 101 102 103 104 105 106 107 108 109 110 111 112
+dumping 12 encoded bits in V.110 frame:
+0 0 0 0 0 0 0 0
+1 101 101 101 101 102 102 0
+1 102 102 103 103 103 103 0
+1 104 104 104 104 105 105 0
+1 105 105 106 106 106 106 0
+1 0 1 0 0 0 0 0
+1 107 107 107 107 108 108 0
+1 108 108 109 109 109 109 0
+1 110 110 110 110 111 111 0
+1 111 111 112 112 112 112 0
+dumping re-decoded V.110 frame:
+E-bits: 00 01 00 00 00 00 00
+S-bits: 00 00 00 00 00 00 00 00 00
+re-decoded user bits: 101 102 103 104 105 106 107 108 109 110 111 112
+
+======= User data rate 2400
+user_bits: 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
+dumping 24 encoded bits in V.110 frame:
+0 0 0 0 0 0 0 0
+1 101 101 102 102 103 103 0
+1 104 104 105 105 106 106 0
+1 107 107 108 108 109 109 0
+1 110 110 111 111 112 112 0
+1 1 1 0 0 0 0 0
+1 113 113 114 114 115 115 0
+1 116 116 117 117 118 118 0
+1 119 119 120 120 121 121 0
+1 122 122 123 123 124 124 0
+dumping re-decoded V.110 frame:
+E-bits: 01 01 00 00 00 00 00
+S-bits: 00 00 00 00 00 00 00 00 00
+re-decoded user bits: 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
+
+======= User data rate 4800
+user_bits: 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148
+dumping 48 encoded bits in V.110 frame:
+0 0 0 0 0 0 0 0
+1 101 102 103 104 105 106 0
+1 107 108 109 110 111 112 0
+1 113 114 115 116 117 118 0
+1 119 120 121 122 123 124 0
+1 0 1 1 0 0 0 0
+1 125 126 127 128 129 130 0
+1 131 132 133 134 135 136 0
+1 137 138 139 140 141 142 0
+1 143 144 145 146 147 148 0
+dumping re-decoded V.110 frame:
+E-bits: 00 01 01 00 00 00 00
+S-bits: 00 00 00 00 00 00 00 00 00
+re-decoded user bits: 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148
+
+======= User data rate 7200
+user_bits: 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
+dumping 36 encoded bits in V.110 frame:
+0 0 0 0 0 0 0 0
+1 101 102 103 104 105 106 0
+1 107 108 109 110 1 1 0
+1 111 112 1 1 113 114 0
+1 1 1 115 116 117 118 0
+1 1 0 1 0 0 0 0
+1 119 120 121 122 123 124 0
+1 125 126 127 128 1 1 0
+1 129 130 1 1 131 132 0
+1 1 1 133 134 135 136 0
+dumping re-decoded V.110 frame:
+E-bits: 01 00 01 00 00 00 00
+S-bits: 00 00 00 00 00 00 00 00 00
+re-decoded user bits: 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
+
+======= User data rate 9600
+user_bits: 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148
+dumping 48 encoded bits in V.110 frame:
+0 0 0 0 0 0 0 0
+1 101 102 103 104 105 106 0
+1 107 108 109 110 111 112 0
+1 113 114 115 116 117 118 0
+1 119 120 121 122 123 124 0
+1 0 1 1 0 0 0 0
+1 125 126 127 128 129 130 0
+1 131 132 133 134 135 136 0
+1 137 138 139 140 141 142 0
+1 143 144 145 146 147 148 0
+dumping re-decoded V.110 frame:
+E-bits: 00 01 01 00 00 00 00
+S-bits: 00 00 00 00 00 00 00 00 00
+re-decoded user bits: 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148
+
+======= User data rate 12000
+user_bits: 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130
+dumping 30 encoded bits in V.110 frame:
+0 0 0 0 0 0 0 0
+1 101 102 103 104 105 106 0
+1 107 108 109 110 1 1 0
+1 111 112 1 1 113 114 0
+1 1 1 115 1 1 1 0
+1 0 0 1 0 0 0 0
+1 116 117 118 119 120 121 0
+1 122 123 124 125 1 1 0
+1 126 127 1 1 128 129 0
+1 1 1 130 1 1 1 0
+dumping re-decoded V.110 frame:
+E-bits: 00 00 01 00 00 00 00
+S-bits: 00 00 00 00 00 00 00 00 00
+re-decoded user bits: 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130
+
+======= User data rate 14400
+user_bits: 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
+dumping 36 encoded bits in V.110 frame:
+0 0 0 0 0 0 0 0
+1 101 102 103 104 105 106 0
+1 107 108 109 110 1 1 0
+1 111 112 1 1 113 114 0
+1 1 1 115 116 117 118 0
+1 1 0 1 0 0 0 0
+1 119 120 121 122 123 124 0
+1 125 126 127 128 1 1 0
+1 129 130 1 1 131 132 0
+1 1 1 133 134 135 136 0
+dumping re-decoded V.110 frame:
+E-bits: 01 00 01 00 00 00 00
+S-bits: 00 00 00 00 00 00 00 00 00
+re-decoded user bits: 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
+
+======= User data rate 19200
+user_bits: 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148
+dumping 48 encoded bits in V.110 frame:
+0 0 0 0 0 0 0 0
+1 101 102 103 104 105 106 0
+1 107 108 109 110 111 112 0
+1 113 114 115 116 117 118 0
+1 119 120 121 122 123 124 0
+1 0 1 1 0 0 0 0
+1 125 126 127 128 129 130 0
+1 131 132 133 134 135 136 0
+1 137 138 139 140 141 142 0
+1 143 144 145 146 147 148 0
+dumping re-decoded V.110 frame:
+E-bits: 00 01 01 00 00 00 00
+S-bits: 00 00 00 00 00 00 00 00 00
+re-decoded user bits: 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148
+
+======= User data rate 24000
+user_bits: 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130
+dumping 30 encoded bits in V.110 frame:
+0 0 0 0 0 0 0 0
+1 101 102 103 104 105 106 0
+1 107 108 109 110 1 1 0
+1 111 112 1 1 113 114 0
+1 1 1 115 1 1 1 0
+1 0 0 1 0 0 0 0
+1 116 117 118 119 120 121 0
+1 122 123 124 125 1 1 0
+1 126 127 1 1 128 129 0
+1 1 1 130 1 1 1 0
+dumping re-decoded V.110 frame:
+E-bits: 00 00 01 00 00 00 00
+S-bits: 00 00 00 00 00 00 00 00 00
+re-decoded user bits: 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130
+
+======= User data rate 28800
+user_bits: 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
+dumping 36 encoded bits in V.110 frame:
+0 0 0 0 0 0 0 0
+1 101 102 103 104 105 106 0
+1 107 108 109 110 1 1 0
+1 111 112 1 1 113 114 0
+1 1 1 115 116 117 118 0
+1 1 0 1 0 0 0 0
+1 119 120 121 122 123 124 0
+1 125 126 127 128 1 1 0
+1 129 130 1 1 131 132 0
+1 1 1 133 134 135 136 0
+dumping re-decoded V.110 frame:
+E-bits: 01 00 01 00 00 00 00
+S-bits: 00 00 00 00 00 00 00 00 00
+re-decoded user bits: 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
+
+======= User data rate 38400
+user_bits: 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148
+dumping 48 encoded bits in V.110 frame:
+0 0 0 0 0 0 0 0
+1 101 102 103 104 105 106 0
+1 107 108 109 110 111 112 0
+1 113 114 115 116 117 118 0
+1 119 120 121 122 123 124 0
+1 0 1 1 0 0 0 0
+1 125 126 127 128 129 130 0
+1 131 132 133 134 135 136 0
+1 137 138 139 140 141 142 0
+1 143 144 145 146 147 148 0
+dumping re-decoded V.110 frame:
+E-bits: 00 01 01 00 00 00 00
+S-bits: 00 00 00 00 00 00 00 00 00
+re-decoded user bits: 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148
diff --git a/tests/vty/vty_test.c b/tests/vty/vty_test.c
index c84e4c9c..01e323ed 100644
--- a/tests/vty/vty_test.c
+++ b/tests/vty/vty_test.c
@@ -448,7 +448,35 @@ DEFUN(cfg_numeric_range, cfg_numeric_range_cmd,
return CMD_SUCCESS;
}
-void test_vty_add_cmds()
+DEFUN(cfg_range_base10, cfg_range_base10_cmd,
+ "range-base10 <0-999999>",
+ "testing decimal range\n"
+ "the decimal range\n")
+{
+ printf("Called: 'return-success'\n");
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_range_base16, cfg_range_base16_cmd,
+ "range-base16 <0x0-0x8888>",
+ "testing hexadecimal range\n"
+ "the hexadecimal range\n")
+{
+ printf("Called: 'return-success'\n");
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_range_baseboth, cfg_range_baseboth_cmd,
+ "range-baseboth (<0-999999>|<0x0-0x8888>)",
+ "testing both ranges\n"
+ "the decimal range\n"
+ "the hexadecimal range\n")
+{
+ printf("Called: 'return-success'\n");
+ return CMD_SUCCESS;
+}
+
+void test_vty_add_cmds(void)
{
install_element(CONFIG_NODE, &cfg_ret_warning_cmd);
install_element(CONFIG_NODE, &cfg_ret_success_cmd);
@@ -473,9 +501,13 @@ void test_vty_add_cmds()
install_element_ve(&cfg_ambiguous_str_2_cmd);
install_element_ve(&cfg_numeric_range_cmd);
+
+ install_element_ve(&cfg_range_base10_cmd);
+ install_element_ve(&cfg_range_base16_cmd);
+ install_element_ve(&cfg_range_baseboth_cmd);
}
-void test_is_cmd_ambiguous()
+void test_is_cmd_ambiguous(void)
{
struct vty *vty;
struct vty_test test;
@@ -494,7 +526,7 @@ void test_is_cmd_ambiguous()
destroy_test_vty(&test, vty);
}
-void test_numeric_range()
+void test_numeric_range(void)
{
struct vty *vty;
struct vty_test test;
@@ -509,6 +541,40 @@ void test_numeric_range()
destroy_test_vty(&test, vty);
}
+void test_ranges(void)
+{
+ struct vty *vty;
+ struct vty_test test;
+
+ printf("Going to test test_ranges()\n");
+ vty = create_test_vty(&test);
+
+ printf("test range-base10\n");
+ OSMO_ASSERT(do_vty_command(vty, "range-base10 0") == CMD_SUCCESS);
+ OSMO_ASSERT(do_vty_command(vty, "range-base10 40000") == CMD_SUCCESS);
+ OSMO_ASSERT(do_vty_command(vty, "range-base10 -400000") == CMD_ERR_NO_MATCH);
+ OSMO_ASSERT(do_vty_command(vty, "range-base10 0x0") == CMD_ERR_NO_MATCH);
+ OSMO_ASSERT(do_vty_command(vty, "range-base10 0x343") == CMD_ERR_NO_MATCH);
+ OSMO_ASSERT(do_vty_command(vty, "range-base10 -0x343") == CMD_ERR_NO_MATCH);
+
+ printf("test range-base16\n");
+ OSMO_ASSERT(do_vty_command(vty, "range-base16 0") == CMD_ERR_NO_MATCH);
+ OSMO_ASSERT(do_vty_command(vty, "range-base16 40000") == CMD_ERR_NO_MATCH);
+ OSMO_ASSERT(do_vty_command(vty, "range-base16 -400000") == CMD_ERR_NO_MATCH);
+ OSMO_ASSERT(do_vty_command(vty, "range-base16 0x0") == CMD_SUCCESS);
+ OSMO_ASSERT(do_vty_command(vty, "range-base16 0x343") == CMD_SUCCESS);
+ OSMO_ASSERT(do_vty_command(vty, "range-base16 -0x343") == CMD_ERR_NO_MATCH);
+
+ printf("test range-baseboth\n");
+ OSMO_ASSERT(do_vty_command(vty, "range-baseboth 0") == CMD_SUCCESS);
+ OSMO_ASSERT(do_vty_command(vty, "range-baseboth 40000") == CMD_SUCCESS);
+ OSMO_ASSERT(do_vty_command(vty, "range-baseboth -400000") == CMD_ERR_NO_MATCH);
+ OSMO_ASSERT(do_vty_command(vty, "range-baseboth 0x0") == CMD_SUCCESS);
+ OSMO_ASSERT(do_vty_command(vty, "range-baseboth 0x343") == CMD_SUCCESS);
+ OSMO_ASSERT(do_vty_command(vty, "range-baseboth -0x343") == CMD_ERR_NO_MATCH);
+
+ destroy_test_vty(&test, vty);
+}
/* Application specific attributes */
enum vty_test_attr {
VTY_TEST_ATTR_FOO = 0,
@@ -591,6 +657,7 @@ int main(int argc, char **argv)
test_is_cmd_ambiguous();
test_numeric_range();
+ test_ranges();
/* Leak check */
OSMO_ASSERT(talloc_total_blocks(stats_ctx) == 1);
diff --git a/tests/vty/vty_test.err b/tests/vty/vty_test.err
index 1cb4190c..b021425d 100644
--- a/tests/vty/vty_test.err
+++ b/tests/vty/vty_test.err
@@ -65,3 +65,13 @@ Got VTY event: 2
Got VTY event: 2
Got VTY event: 1
Got VTY event: 3
+Got VTY event: 2
+Got VTY event: 2
+Got VTY event: 2
+Got VTY event: 2
+Got VTY event: 2
+Got VTY event: 2
+Got VTY event: 2
+Got VTY event: 2
+Got VTY event: 1
+Got VTY event: 3
diff --git a/tests/vty/vty_test.ok b/tests/vty/vty_test.ok
index 5f509f65..e97fbfc4 100644
--- a/tests/vty/vty_test.ok
+++ b/tests/vty/vty_test.ok
@@ -320,4 +320,52 @@ Called: 'return-success'
Returned: 0, Current node: 1 '%s> '
Going to execute 'numeric-range -400000'
Returned: 2, Current node: 1 '%s> '
+Going to test test_ranges()
+test range-base10
+Going to execute 'range-base10 0'
+Called: 'return-success'
+Returned: 0, Current node: 1 '%s> '
+Going to execute 'range-base10 40000'
+Called: 'return-success'
+Returned: 0, Current node: 1 '%s> '
+Going to execute 'range-base10 -400000'
+Returned: 2, Current node: 1 '%s> '
+Going to execute 'range-base10 0x0'
+Returned: 2, Current node: 1 '%s> '
+Going to execute 'range-base10 0x343'
+Returned: 2, Current node: 1 '%s> '
+Going to execute 'range-base10 -0x343'
+Returned: 2, Current node: 1 '%s> '
+test range-base16
+Going to execute 'range-base16 0'
+Returned: 2, Current node: 1 '%s> '
+Going to execute 'range-base16 40000'
+Returned: 2, Current node: 1 '%s> '
+Going to execute 'range-base16 -400000'
+Returned: 2, Current node: 1 '%s> '
+Going to execute 'range-base16 0x0'
+Called: 'return-success'
+Returned: 0, Current node: 1 '%s> '
+Going to execute 'range-base16 0x343'
+Called: 'return-success'
+Returned: 0, Current node: 1 '%s> '
+Going to execute 'range-base16 -0x343'
+Returned: 2, Current node: 1 '%s> '
+test range-baseboth
+Going to execute 'range-baseboth 0'
+Called: 'return-success'
+Returned: 0, Current node: 1 '%s> '
+Going to execute 'range-baseboth 40000'
+Called: 'return-success'
+Returned: 0, Current node: 1 '%s> '
+Going to execute 'range-baseboth -400000'
+Returned: 2, Current node: 1 '%s> '
+Going to execute 'range-baseboth 0x0'
+Called: 'return-success'
+Returned: 0, Current node: 1 '%s> '
+Going to execute 'range-baseboth 0x343'
+Called: 'return-success'
+Returned: 0, Current node: 1 '%s> '
+Going to execute 'range-baseboth -0x343'
+Returned: 2, Current node: 1 '%s> '
All tests passed
diff --git a/tests/vty/vty_transcript_test.c b/tests/vty/vty_transcript_test.c
index 84664f92..5602c505 100644
--- a/tests/vty/vty_transcript_test.c
+++ b/tests/vty/vty_transcript_test.c
@@ -37,7 +37,7 @@
void *root_ctx = NULL;
-static void print_help()
+static void print_help(void)
{
printf( "options:\n"
" -h --help this text\n"
@@ -211,6 +211,9 @@ DEFUN(multi2, multi2_cmd,
enum {
ATTR_TEST_NODE = _LAST_OSMOVTY_NODE + 1,
+ NEST_A_NODE,
+ NEST_B_NODE,
+ NEST_C_NODE,
};
static struct cmd_node attr_test_node = {
@@ -315,7 +318,63 @@ DEFUN_ATTR_USRATTR(cfg_attr_hidden_app_attr_unbelievable,
return CMD_SUCCESS;
}
-static void init_vty_cmds()
+static struct cmd_node nest_a_node = {
+ NEST_A_NODE,
+ "%s(config-a)# ",
+ 1
+};
+
+static struct cmd_node nest_b_node = {
+ NEST_B_NODE,
+ "%s(config-b)# ",
+ 1
+};
+
+static struct cmd_node nest_c_node = {
+ NEST_C_NODE,
+ "%s(config-c)# ",
+ 1
+};
+
+DEFUN(cfg_nest_a, cfg_nest_a_cmd,
+ "nest NAME",
+ "Enter nest level a\n"
+ "Set a name to mark the node's state\n")
+{
+ vty->index = talloc_strdup(root_ctx, argv[0]);
+ vty->node = NEST_A_NODE;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nest_b, cfg_nest_b_cmd,
+ "nest NAME",
+ "Enter nest level b\n"
+ "Set a name to mark the node's state\n")
+{
+ vty->index = talloc_strdup(root_ctx, argv[0]);
+ vty->node = NEST_B_NODE;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nest_c, cfg_nest_c_cmd,
+ "nest NAME",
+ "Enter nest level c\n"
+ "Set a name to mark the node's state\n")
+{
+ vty->index = talloc_strdup(root_ctx, argv[0]);
+ vty->node = NEST_C_NODE;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_nest_state, cfg_nest_state_cmd,
+ "state",
+ "Show this node's mark\n")
+{
+ vty_out(vty, "%s%s", (const char *)vty->index, VTY_NEWLINE);
+ return CMD_SUCCESS;
+}
+
+static void init_vty_cmds(void)
{
install_element_ve(&single0_cmd);
install_element_ve(&multi0_cmd);
@@ -336,6 +395,17 @@ static void init_vty_cmds()
install_element(ATTR_TEST_NODE, &cfg_app_attr_unbelievable_magnificent_cmd);
install_element(ATTR_TEST_NODE, &cfg_app_attr_unbelievable_wonderful_cmd);
install_element(ATTR_TEST_NODE, &cfg_attr_hidden_app_attr_unbelievable_cmd);
+
+ install_element(CONFIG_NODE, &cfg_nest_a_cmd);
+ install_node(&nest_a_node, NULL);
+ install_element(NEST_A_NODE, &cfg_nest_b_cmd);
+ install_node(&nest_b_node, NULL);
+ install_element(NEST_B_NODE, &cfg_nest_c_cmd);
+ install_node(&nest_c_node, NULL);
+
+ install_element(NEST_A_NODE, &cfg_nest_state_cmd);
+ install_element(NEST_B_NODE, &cfg_nest_state_cmd);
+ install_element(NEST_C_NODE, &cfg_nest_state_cmd);
}
int main(int argc, char **argv)
@@ -361,7 +431,7 @@ int main(int argc, char **argv)
}
}
- rc = telnet_init_dynif(root_ctx, NULL, vty_get_bind_addr(), 42042);
+ rc = telnet_init_default(root_ctx, NULL, 42042);
if (rc < 0)
return 2;
diff --git a/tests/vty/vty_transcript_test.vty b/tests/vty/vty_transcript_test.vty
index 7b8241eb..7df2a606 100644
--- a/tests/vty/vty_transcript_test.vty
+++ b/tests/vty/vty_transcript_test.vty
@@ -177,3 +177,33 @@ vty_transcript_test(config-attr-test)# foo-hidden ?
[expert-mode] But can be seen in the expert mode
vty_transcript_test(config-attr-test)# app-hidden-unbelievable?
app-hidden-unbelievable Hidden, but still unbelievable help message
+
+vty_transcript_test(config-attr-test)# exit
+
+vty_transcript_test(config)# nest A
+vty_transcript_test(config-a)# state
+A
+vty_transcript_test(config-a)# nest B
+vty_transcript_test(config-b)# state
+B
+vty_transcript_test(config-b)# nest C
+vty_transcript_test(config-c)# state
+C
+vty_transcript_test(config-c)# exit
+vty_transcript_test(config-b)# state
+B
+vty_transcript_test(config-b)# exit
+vty_transcript_test(config-a)# state
+A
+vty_transcript_test(config-a)# nest B2
+vty_transcript_test(config-b)# state
+B2
+vty_transcript_test(config-b)# nest C2
+vty_transcript_test(config-c)# state
+C2
+vty_transcript_test(config-c)# exit
+vty_transcript_test(config-b)# state
+B2
+vty_transcript_test(config-b)# exit
+vty_transcript_test(config-a)# state
+A
diff --git a/tests/write_queue/wqueue_test.c b/tests/write_queue/wqueue_test.c
index 3823ef5b..d4476f16 100644
--- a/tests/write_queue/wqueue_test.c
+++ b/tests/write_queue/wqueue_test.c
@@ -1,3 +1,14 @@
+/*
+ * (C) 2023 by sysmocom - s.f.m.c. GmbH.
+ * Authors: Holger Hans Peter Freyther
+ * Alexander Rehbein
+ *
+ * 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.
+ */
+
#include <osmocom/core/logging.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/write_queue.h>
@@ -15,6 +26,7 @@ static void test_wqueue_limit(void)
struct msgb *msg;
struct osmo_wqueue wqueue;
int rc;
+ size_t dropped_msgs;
osmo_wqueue_init(&wqueue, 0);
OSMO_ASSERT(wqueue.max_length == 0);
@@ -63,6 +75,46 @@ static void test_wqueue_limit(void)
OSMO_ASSERT(wqueue.current_length == 2);
msgb_free(msg);
osmo_wqueue_clear(&wqueue);
+
+ /* Update limit */
+ OSMO_ASSERT(osmo_wqueue_set_maxlen(&wqueue, 5) == 0);
+ OSMO_ASSERT(osmo_wqueue_set_maxlen(&wqueue, 1) == 0);
+ OSMO_ASSERT(osmo_wqueue_set_maxlen(&wqueue, 4) == 0);
+
+ /* Add three, update limit to 1 */
+ OSMO_ASSERT(wqueue.max_length == 4);
+ msg = msgb_alloc(4096, "msg6");
+ rc = osmo_wqueue_enqueue(&wqueue, msg);
+ OSMO_ASSERT(rc == 0);
+ OSMO_ASSERT(wqueue.current_length == 1);
+ msg = msgb_alloc(4096, "msg7");
+ rc = osmo_wqueue_enqueue(&wqueue, msg);
+ OSMO_ASSERT(rc == 0);
+ OSMO_ASSERT(wqueue.current_length == 2);
+ msg = msgb_alloc(4096, "msg8");
+ rc = osmo_wqueue_enqueue(&wqueue, msg);
+ OSMO_ASSERT(wqueue.current_length == 3);
+ dropped_msgs = osmo_wqueue_set_maxlen(&wqueue, 1);
+ OSMO_ASSERT(dropped_msgs == 2);
+ osmo_wqueue_clear(&wqueue);
+
+ /* Add three, reduce limit to 3 from 6 */
+ OSMO_ASSERT(osmo_wqueue_set_maxlen(&wqueue, 6) == 0);
+ OSMO_ASSERT(wqueue.max_length == 6);
+ msg = msgb_alloc(4096, "msg9");
+ rc = osmo_wqueue_enqueue(&wqueue, msg);
+ OSMO_ASSERT(rc == 0);
+ OSMO_ASSERT(wqueue.current_length == 1);
+ msg = msgb_alloc(4096, "msg10");
+ rc = osmo_wqueue_enqueue(&wqueue, msg);
+ OSMO_ASSERT(rc == 0);
+ OSMO_ASSERT(wqueue.current_length == 2);
+ msg = msgb_alloc(4096, "msg11");
+ rc = osmo_wqueue_enqueue(&wqueue, msg);
+ OSMO_ASSERT(wqueue.current_length == 3);
+ dropped_msgs = osmo_wqueue_set_maxlen(&wqueue, 3);
+ OSMO_ASSERT(dropped_msgs == 0);
+ osmo_wqueue_clear(&wqueue);
}
int main(int argc, char **argv)
diff --git a/utils/Makefile.am b/utils/Makefile.am
index 3f2b13f6..af0b5ce1 100644
--- a/utils/Makefile.am
+++ b/utils/Makefile.am
@@ -1,14 +1,15 @@
bin_PROGRAMS =
noinst_PROGRAMS =
-AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_builddir)/include $(TALLOC_CFLAGS)
-AM_CFLAGS = -Wall $(PTHREAD_CFLAGS)
-LDADD = $(top_builddir)/src/libosmocore.la $(top_builddir)/src/gsm/libosmogsm.la $(PTHREAD_LIBS)
+AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_builddir)/include
+AM_CFLAGS = -Wall $(TALLOC_CFLAGS) $(PTHREAD_CFLAGS)
+LDADD = $(top_builddir)/src/core/libosmocore.la $(top_builddir)/src/gsm/libosmogsm.la $(PTHREAD_LIBS)
if ENABLE_UTILITIES
EXTRA_DIST = conv_gen.py conv_codes_gsm.py
-bin_PROGRAMS += osmo-arfcn osmo-auc-gen osmo-config-merge osmo-aka-verify
+bin_PROGRAMS += osmo-arfcn osmo-auc-gen osmo-config-merge osmo-aka-verify osmo-gsmtap-logsend
+bin_SCRIPTS = osmo-gsm-shark
osmo_arfcn_SOURCES = osmo-arfcn.c
@@ -16,26 +17,31 @@ osmo_auc_gen_SOURCES = osmo-auc-gen.c
osmo_aka_verify_SOURCES = osmo-aka-verify.c
+osmo_gsmtap_logsend_SOURCES = gsmtap-logsend.c
+
osmo_config_merge_SOURCES = osmo-config-merge.c
osmo_config_merge_LDADD = $(LDADD) $(TALLOC_LIBS)
-osmo_config_merge_CFLAGS = $(TALLOC_CFLAGS)
if ENABLE_PCSC
noinst_PROGRAMS += osmo-sim-test
osmo_sim_test_SOURCES = osmo-sim-test.c
osmo_sim_test_LDADD = $(LDADD) $(top_builddir)/src/sim/libosmosim.la $(PCSC_LIBS)
-osmo_sim_test_CFLAGS = $(PCSC_CFLAGS)
+osmo_sim_test_CFLAGS = $(AM_CFLAGS) $(PCSC_CFLAGS)
endif
endif
if ENABLE_EXT_TESTS
+SUBDIRS = \
+ osmo-stat-dummy \
+ $(NULL)
+endif
+
if ENABLE_GB
noinst_PROGRAMS += osmo-ns-dummy
osmo_ns_dummy_SOURCES = osmo-ns-dummy.c osmo-ns-dummy-vty.c
osmo_ns_dummy_LDADD = $(LDADD) $(TALLOC_LIBS) \
$(top_builddir)/src/gb/libosmogb.la \
$(top_builddir)/src/vty/libosmovty.la \
- $(top_builddir)/src/gsm/libosmogsm.la
-osmo_ns_dummy_CFLAGS = $(TALLOC_CFLAGS)
-endif
+ $(top_builddir)/src/ctrl/libosmoctrl.la \
+ $(NULL)
endif
diff --git a/utils/conv_codes_gsm.py b/utils/conv_codes_gsm.py
index 42f340b9..24a8ee21 100644
--- a/utils/conv_codes_gsm.py
+++ b/utils/conv_codes_gsm.py
@@ -42,6 +42,111 @@ conv_codes = [
]
),
+ # TCH/F2.4 definition
+ ConvolutionalCode(
+ 72,
+ [
+ (G1, 1),
+ (G2, 1),
+ (G3, 1),
+ (G1, 1),
+ (G2, 1),
+ (G3, 1),
+ ],
+ name = "tch_f24",
+ description = [
+ "TCH/F2.4 convolutional code:",
+ "72 bits blocks, rate 1/6, k = 5",
+ "G1 = 1 + D + D3 + D4",
+ "G2 = 1 + D2 + D4",
+ "G3 = 1 + D + D2 + D3 + D4",
+ "G1 = 1 + D + D3 + D4",
+ "G2 = 1 + D2 + D4",
+ "G3 = 1 + D + D2 + D3 + D4",
+ ]
+ ),
+
+ # TCH/H2.4 definition
+ ConvolutionalCode(
+ 72,
+ [
+ (G1, 1),
+ (G2, 1),
+ (G3, 1),
+ ],
+ name = "tch_h24",
+ description = [
+ "TCH/H2.4 convolutional code:",
+ "72 bits blocks, rate 1/3, k = 5",
+ "G1 = 1 + D + D3 + D4",
+ "G2 = 1 + D2 + D4",
+ "G3 = 1 + D + D2 + D3 + D4",
+ ]
+ ),
+
+ # TCH/F4.8 definition
+ ConvolutionalCode(
+ 148,
+ [
+ (G1, 1),
+ (G2, 1),
+ (G3, 1),
+ ],
+ name = "tch_f48",
+ description = [
+ "TCH/F4.8 convolutional code:",
+ "148 bits blocks, rate 1/3, k = 5",
+ "G1 = 1 + D + D3 + D4",
+ "G2 = 1 + D2 + D4",
+ "G3 = 1 + D + D2 + D3 + D4",
+ ]
+ ),
+
+ # TCH/F9.6 definition
+ ConvolutionalCode(
+ 240,
+ shared_polys["xcch"],
+ puncture = [
+ 11, 26, 41, 56, 71, 86, 101, 116, 131, 146, 161, 176,
+ 191, 206, 221, 236, 251, 266, 281, 296, 311, 326, 341, 356,
+ 371, 386, 401, 416, 431, 446, 461, 476, -1
+ ],
+ name = "tch_f96",
+ description = [
+ "TCH/F9.6 convolutional code:",
+ "240 bits blocks, rate 1/2, k = 5",
+ "G0 = 1 + D3 + D4",
+ "G1 = 1 + D + D3 + D4",
+ ]
+ ),
+
+ # TCH/F14.4 definition
+ ConvolutionalCode(
+ 290,
+ shared_polys["xcch"],
+ puncture = [
+ 1, 6, 11, 15, 19, 24, 29, 33, 37, 42, 47, 51,
+ 55, 60, 65, 69, 73, 78, 83, 87, 91, 96, 101, 105,
+ 109, 114, 119, 123, 127, 132, 137, 141, 145, 150, 155, 159,
+ 163, 168, 173, 177, 181, 186, 191, 195, 199, 204, 209, 213,
+ 217, 222, 227, 231, 235, 240, 245, 249, 253, 258, 263, 267,
+ 271, 276, 281, 285, 289, 294, 299, 303, 307, 312, 317, 321,
+ 325, 330, 335, 339, 343, 348, 353, 357, 361, 366, 371, 375,
+ 379, 384, 389, 393, 397, 402, 407, 411, 415, 420, 425, 429,
+ 433, 438, 443, 447, 451, 456, 461, 465, 469, 474, 479, 483,
+ 487, 492, 497, 501, 505, 510, 515, 519, 523, 528, 533, 537,
+ 541, 546, 551, 555, 559, 564, 569, 573, 577, 582, 584, 587,
+ -1
+ ],
+ name = "tch_f144",
+ description = [
+ "TCH/F14.4 convolutional code:",
+ "290 bits blocks, rate 1/2, k = 5",
+ "G0 = 1 + D3 + D4",
+ "G1 = 1 + D + D3 + D4",
+ ]
+ ),
+
# RACH definition
ConvolutionalCode(
14,
diff --git a/utils/gsmtap-logsend.c b/utils/gsmtap-logsend.c
new file mode 100644
index 00000000..97a838b5
--- /dev/null
+++ b/utils/gsmtap-logsend.c
@@ -0,0 +1,139 @@
+/* Small program to read an input file / stdin and send each line via GSMTAP logging */
+/* (C) 2023 by Harald Welte <laforge@osmocom.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <stdio.h>
+#include <errno.h>
+#include <sys/uio.h>
+#include <getopt.h>
+
+#include <osmocom/core/byteswap.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/core/gsmtap.h>
+#include <osmocom/core/gsmtap_util.h>
+
+static char *proc_name = "gsmtap-logsend";
+static char *subsys_name = "unknown";
+static char *dest_host = "localhost";
+static int dest_port = GSMTAP_UDP_PORT;
+
+static void help(void)
+{
+ printf("osmo-gsmtap-logsend Usage:\n"
+ "\t[ -r DESTADDR ] [ -p PORTNR ] [ -n PROC_NAME ] [ -s SUBSYS ] [ INFILE ]\n"
+ "\n"
+ " -a --remote-address HOSTNAME Destination IP destination address (default: localhost)\n"
+ " -p --remote-port PORTNR Destination UDP Port number (default: 4729)\n"
+ " -n --process-name PROC_NAME Process name to include in GSMTAP LOG header\n"
+ " -s --subsys-name SUBSYS Subsystem name to include in GSMTAP LOG header\n"
+ " -h --help This help message\n"
+ );
+}
+
+int main(int argc, char **argv)
+{
+ char buf[1024];
+ int gsmtap_fd;
+ FILE *infile;
+ char *line;
+ int rc;
+
+ while (1) {
+ static const struct option long_options[] = {
+ { "remote-address", 1, 0, 'a' },
+ { "remote-port", 1, 0, 'p' },
+ { "process-name", 1, 0, 'n' },
+ { "subsys-name", 1, 0, 's' },
+ { "help", 0, 0, 'h' },
+ { 0, 0, 0, 0 }
+ };
+ int c, option_index;
+
+ c = getopt_long(argc, argv, "a:p:n:s:h", long_options, &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'a':
+ dest_host = optarg;
+ break;
+ case 'p':
+ dest_port = atoi(optarg);
+ break;
+ case 'n':
+ proc_name = optarg;
+ break;
+ case 's':
+ subsys_name = optarg;
+ break;
+ case 'h':
+ help();
+ exit(0);
+ break;
+ default:
+ help();
+ exit(1);
+ }
+ }
+
+ if (argc <= optind) {
+ infile = stdin;
+ } else {
+ infile = fopen(argv[optind], "r");
+ if (!infile) {
+ fprintf(stderr, "Unable to open %s: %s\n", argv[optind], strerror(errno));
+ exit(2);
+ }
+ }
+
+ gsmtap_fd = gsmtap_source_init_fd(dest_host, dest_port);
+ if (gsmtap_fd < 0) {
+ fprintf(stderr, "Unable to create GSMTAP soicket: %s\n", strerror(errno));
+ exit(2);
+ }
+
+ /* prepare all the data structures that don't change for each line */
+ struct {
+ struct gsmtap_hdr gsmtap;
+ struct gsmtap_osmocore_log_hdr log;
+ } __attribute__ ((packed)) hdr;
+ struct timeval tv;
+
+ memset(&hdr, 0, sizeof(hdr));
+ hdr.gsmtap.version = GSMTAP_VERSION;
+ hdr.gsmtap.hdr_len = sizeof(hdr.gsmtap)/4;
+ hdr.gsmtap.type = GSMTAP_TYPE_OSMOCORE_LOG;
+
+ OSMO_STRLCPY_ARRAY(hdr.log.proc_name, proc_name);
+ OSMO_STRLCPY_ARRAY(hdr.log.subsys, subsys_name);
+ hdr.log.level = LOGL_INFO;
+
+ while ((line = fgets(buf, sizeof(buf), infile))) {
+ struct iovec iov[2] = {
+ { .iov_base = &hdr, .iov_len = sizeof(hdr) },
+ { .iov_base = buf, .iov_len = strlen(line) + 1 },
+ };
+ osmo_gettimeofday(&tv, NULL);
+ hdr.log.ts.sec = osmo_htonl(tv.tv_sec);
+ hdr.log.ts.usec = osmo_htonl(tv.tv_usec);
+
+ rc = writev(gsmtap_fd, iov, ARRAY_SIZE(iov));
+ if (rc <= 0) {
+ fprintf(stderr, "Short write on GSMTAP socket: %d (%s)\n", rc, strerror(errno));
+ exit(1);
+ }
+ }
+}
diff --git a/utils/osmo-aka-verify.c b/utils/osmo-aka-verify.c
index bbc65ef7..f23c349b 100644
--- a/utils/osmo-aka-verify.c
+++ b/utils/osmo-aka-verify.c
@@ -88,7 +88,7 @@ static int milenage_check(const uint8_t *opc, const uint8_t *k, const uint8_t *s
}
-static void help()
+static void help(void)
{
printf( "Static SIM card parameters:\n"
"-k --key\tSpecify Ki / K\n"
diff --git a/utils/osmo-auc-gen.c b/utils/osmo-auc-gen.c
index 72f1fcd4..50419a43 100644
--- a/utils/osmo-auc-gen.c
+++ b/utils/osmo-auc-gen.c
@@ -1,7 +1,7 @@
/*! \file osmo-auc-gen.c
* GSM/GPRS/3G authentication testing tool. */
/*
- * (C) 2010-2021 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2010-2023 by Harald Welte <laforge@gnumonks.org>
*
* All Rights Reserved
*
@@ -80,12 +80,12 @@ static void dump_auth_vec(struct osmo_auth_vector *vec)
}
}
-static struct osmo_sub_auth_data test_aud = {
+static struct osmo_sub_auth_data2 test_aud = {
.type = OSMO_AUTH_TYPE_NONE,
.algo = OSMO_AUTH_ALG_NONE,
};
-static void help()
+static void help(void)
{
int alg;
printf( "-2 --2g\tUse 2G (GSM) authentication\n"
@@ -98,6 +98,7 @@ static void help()
"-s --sqn\tSpecify SQN (only for 3G)\n"
"-i --ind\tSpecify IND slot for new SQN after AUTS (only for 3G)\n"
"-l --ind-len\tSpecify IND bit length (default=5) (only for 3G)\n"
+ "-L --res-len\tSpecify RES byte length (default=8) (only for 3G)\n"
"-A --auts\tSpecify AUTS (only for 3G)\n"
"-r --rand\tSpecify random value\n"
"-I --ipsec\tOutput in triplets.dat format for strongswan\n");
@@ -123,10 +124,12 @@ int main(int argc, char **argv)
int fmt_triplets_dat = 0;
uint64_t ind_mask = 0;
- printf("osmo-auc-gen (C) 2011-2012 by Harald Welte\n");
+ printf("osmo-auc-gen (C) 2011-2023 by Harald Welte\n");
printf("This is FREE SOFTWARE with ABSOLUTELY NO WARRANTY\n\n");
memset(_auts, 0, sizeof(_auts));
+ memset(vec, 0, sizeof(*vec));
+ vec->res_len = 8; /* default */
while (1) {
int c;
@@ -141,6 +144,7 @@ int main(int argc, char **argv)
{ "sqn", 1, 0, 's' },
{ "ind", 1, 0, 'i' },
{ "ind-len", 1, 0, 'l' },
+ { "res-len", 1, 0, 'L' },
{ "rand", 1, 0, 'r' },
{ "auts", 1, 0, 'A' },
{ "help", 0, 0, 'h' },
@@ -149,7 +153,7 @@ int main(int argc, char **argv)
rc = 0;
- c = getopt_long(argc, argv, "23a:k:o:f:s:i:l:r:hO:A:I", long_options,
+ c = getopt_long(argc, argv, "23a:k:o:f:s:i:l:L:r:hO:A:I", long_options,
&option_index);
if (c == -1)
@@ -174,10 +178,21 @@ int main(int argc, char **argv)
case OSMO_AUTH_TYPE_GSM:
rc = osmo_hexparse(optarg, test_aud.u.gsm.ki,
sizeof(test_aud.u.gsm.ki));
+ if (rc != sizeof(test_aud.u.gsm.ki)) {
+ fprintf(stderr, "Invalid Ki length %d\n", rc);
+ exit(2);
+ }
break;
case OSMO_AUTH_TYPE_UMTS:
rc = osmo_hexparse(optarg, test_aud.u.umts.k,
sizeof(test_aud.u.umts.k));
+ /* 3GPP TS 33.102 6.3.7: "The authentication key (K) shall have a length of
+ * 128 bits or 256 bits." */
+ if (rc != 16 && rc != 32) {
+ fprintf(stderr, "Invalid K length %d\n", rc);
+ exit(2);
+ }
+ test_aud.u.umts.k_len = rc;
break;
default:
fprintf(stderr, "please specify 2g/3g first!\n");
@@ -190,6 +205,11 @@ int main(int argc, char **argv)
}
rc = osmo_hexparse(optarg, test_aud.u.umts.opc,
sizeof(test_aud.u.umts.opc));
+ if (rc != 16 && rc != 32) {
+ fprintf(stderr, "Invalid OPC length %d\n", rc);
+ exit(2);
+ }
+ test_aud.u.umts.opc_len = rc;
test_aud.u.umts.opc_is_op = 0;
break;
case 'O':
@@ -199,6 +219,11 @@ int main(int argc, char **argv)
}
rc = osmo_hexparse(optarg, test_aud.u.umts.opc,
sizeof(test_aud.u.umts.opc));
+ if (rc != 16 && rc != 32) {
+ fprintf(stderr, "Invalid OP length %d\n", rc);
+ exit(2);
+ }
+ test_aud.u.umts.opc_len = rc;
test_aud.u.umts.opc_is_op = 1;
break;
case 'A':
@@ -216,6 +241,10 @@ int main(int argc, char **argv)
}
rc = osmo_hexparse(optarg, test_aud.u.umts.amf,
sizeof(test_aud.u.umts.amf));
+ if (rc != 2) {
+ fprintf(stderr, "Invalid AMF length %d\n", rc);
+ exit(2);
+ }
break;
case 's':
if (test_aud.type != OSMO_AUTH_TYPE_UMTS) {
@@ -240,8 +269,20 @@ int main(int argc, char **argv)
}
test_aud.u.umts.ind_bitlen = atoi(optarg);
break;
+ case 'L':
+ rc = atoi(optarg);
+ if (rc != 4 && rc != 8 && rc != 16) {
+ fprintf(stderr, "Invalid RES length %u\n", rc);
+ exit(2);
+ }
+ vec->res_len = rc;
+ break;
case 'r':
rc = osmo_hexparse(optarg, _rand, sizeof(_rand));
+ if (rc != sizeof(_rand)) {
+ fprintf(stderr, "Invalid RAND length %d\n", rc);
+ exit(2);
+ }
rand_is_set = 1;
break;
case 'I':
@@ -284,8 +325,6 @@ int main(int argc, char **argv)
exit(2);
}
- memset(vec, 0, sizeof(*vec));
-
if (test_aud.type == OSMO_AUTH_TYPE_UMTS) {
uint64_t seq_1 = 1LL << test_aud.u.umts.ind_bitlen;
ind_mask = seq_1 - 1;
@@ -316,9 +355,9 @@ int main(int argc, char **argv)
}
if (!auts_is_set)
- rc = osmo_auth_gen_vec(vec, &test_aud, _rand);
+ rc = osmo_auth_gen_vec2(vec, &test_aud, _rand);
else
- rc = osmo_auth_gen_vec_auts(vec, &test_aud, _auts, _rand, _rand);
+ rc = osmo_auth_gen_vec_auts2(vec, &test_aud, _auts, _rand, _rand);
if (rc < 0) {
if (!auts_is_set)
fprintf(stderr, "error generating auth vector\n");
diff --git a/utils/osmo-gsm-shark b/utils/osmo-gsm-shark
new file mode 100755
index 00000000..abfd4e42
--- /dev/null
+++ b/utils/osmo-gsm-shark
@@ -0,0 +1,3594 @@
+#!/usr/bin/env python3
+
+# osmo-gsm-shark: produce a ladder diagram from and/or filter a GSM network pcap by subscriber.
+# Copyright (C) 2019 by Neels Hofmeyr <neels@hofmeyr.de>
+#
+# All Rights Reserved
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+'''osmo-gsm-shark: produce a ladder diagram from and/or filter a GSM network pcap by subscriber.
+
+Copyright (C) 2019 by Neels Hofmeyr <neels@hofmeyr.de>
+SPDX-License-Identifier: GPL-2.0+
+
+This tool uses tshark (pyshark) to analyze a pcap file or a live network capture to:
+
+- Associate almost all messages with a subscriber. It is possible to filter by subscriber.
+- Separate the different network elements (BSC, MSC, hNodeB, ...).
+- Output a ladder diagram.
+- Combine repetitive messages.
+- Combine/abstract messages into short activity summary.
+
+Examples:
+
+ osmo-gsm-shark -f trace.pcapng
+ osmo-gsm-shark -l any
+
+ osmo-gsm-shark -l any --filter-subscr 901701234567123
+ osmo-gsm-shark -f trace.pcapng --filter-msg dtap
+'''
+
+import collections
+import pyshark
+import re
+import sys
+import types
+import time
+import traceback
+
+SHOW_ALL_DEBUG = False
+
+SHOW_ALL_LAYERS = False
+SCCP_COLLAPSE_STP = True
+IUH_COLLAPSE_HNBGW = True # doesnt work
+
+DTAP_COMPL_L3 = ('Location-Updating-Request', 'CM-Service-Request', 'Paging-Response', 'IMSI-Detach-Indication')
+GMM_COMPL_L3 = ('Attach-Request', 'Detach-Request')
+
+class Color:
+ codes = (
+ ('red', '\033[1;31m'),
+ ('green', '\033[1;32m'),
+ ('yellow', '\033[1;33m'),
+ ('blue', '\033[1;34m'),
+ ('purple', '\033[1;35m'),
+ ('cyan', '\033[1;36m'),
+ ('darkred', '\033[31m'),
+ ('darkgreen', '\033[32m'),
+ ('darkyellow', '\033[33m'),
+ ('darkblue', '\033[34m'),
+ ('darkpurple', '\033[35m'),
+ ('darkcyan', '\033[36m'),
+ ('darkgrey', '\033[1;30m'),
+ ('brightwhite', '\033[1;37m'),
+ )
+ codes_dict = dict(codes)
+ end = '\033[0;m'
+
+ def colored(code, text):
+ if type(code) is int:
+ code = Color.codes[code % len(Color.codes)][1]
+ else:
+ code = Color.codes_dict[code]
+ return f'{code}{text}{Color.end}'
+
+
+def set_instance_vars_from_args(*ignore, self='s'):
+ f = sys._getframe(1).f_locals
+ s = f.get(self)
+ for k,v in f.items():
+ if v is s:
+ continue
+ if k in ignore:
+ continue
+ setattr(s, k, v)
+
+def same_nonempty(a, b):
+ if isinstance(a, types.GeneratorType):
+ return list(a) == list(b)
+ return a and a == b
+
+def str_drop(a_str, drop_str):
+ if a_str and a_str.startswith(drop_str):
+ return a_str[len(drop_str):]
+ return a_str
+
+def sane_msgtype(msgtype):
+ if not msgtype:
+ return msgtype
+ return sane_showname(msgtype).replace(' ','-')
+
+re_msgtype_label = re.compile('message.type *', re.I)
+def sane_showname(showname):
+ if not showname:
+ return showname
+ if ': ' in showname:
+ showname = showname[showname.index(': ')+1:]
+ if '(' in showname:
+ showname = showname[:showname.index('(')]
+ showname = re_msgtype_label.sub('', showname)
+ return showname.strip()
+
+def dir_vals(elem):
+ strs = []
+ for name in dir(elem):
+ if name.startswith('_'):
+ continue
+ try:
+ strs.append('%r=%r' % (name, getattr(elem, name)))
+ except:
+ strs.append('%r=<Exception>' % (name))
+ return '\n' + '\n'.join(strs)
+
+def dir_p(p, name):
+ return f'=== {name}\n{dir_vals(p.get(name))}\n---{name}'
+
+def str_to_int(nr_str):
+ 'convert hex or decimal string to int'
+ if not nr_str:
+ return None
+ elif nr_str.startswith('0x'):
+ return int(nr_str, 16)
+ else:
+ return int(nr_str, 10)
+
+def out_text(*args, **kwargs):
+ print(*args, **kwargs)
+
+g_ui = None
+g_current_msg = None
+g_debug_full = False
+
+def to_text(*args, **kwargs):
+ if kwargs:
+ args = list(args) + [repr(kwargs)]
+ return ' '.join(str(arg) for arg in args)
+
+def out_text_now(*args, **kwargs):
+ if g_ui is not None:
+ g_ui.out_text_now(*args, **kwargs)
+ else:
+ print(to_text(*args, **kwargs))
+
+def LOG(*args, **kwargs):
+ if g_current_msg is not None:
+ g_current_msg.log(*args, **kwargs)
+ else:
+ out_text_now(*args, **kwargs)
+
+def DBG(*args, **kwargs):
+ if g_current_msg is not None:
+ g_current_msg.dbg(*args, **kwargs)
+ else:
+ out_text_now(*args, **kwargs)
+
+def ERR(*args, **kwargs):
+ LOG(Color.colored('red', '***** ERROR:'), *args, **kwargs)
+
+
+def trace():
+ return ''.join(traceback.format_stack())
+
+def because_str(because):
+ if not because:
+ return '-'
+ t = ['BECAUSE']
+ for b in because:
+ if isinstance(b, tuple) or isinstance(b, list):
+ t.extend(because_str(b).splitlines())
+ elif isinstance(b, str):
+ t.append(b)
+ elif isinstance(b, Message):
+ t.append(b.str(show_traits=True, show_conns=True))
+ else:
+ t.append(str(b))
+ return '\n|'.join(t)
+
+def out_error(*args, **kwargs):
+ if g_ui is not None:
+ g_ui.out_error(*args, **kwargs)
+ else:
+ out_text_now(Color.colored('red', '*** ERROR:'), *args, **kwargs)
+ if g_current_msg:
+ out_text_now(Color.colored('red', '*** ERROR: while processing msg'), g_current_msg.str(show_traits=True, show_conns=True))
+ out_text_now(trace())
+
+def tmsi_standardize(tmsi):
+ try:
+ return format(int(tmsi), '08x')
+ except:
+ return None
+
+# a dict containing a list for each key; l.add(name, item) adds item to the list at key=name.
+class listdict(dict):
+ '''A dict where each entry is a list of items'''
+ def _have(ld, name):
+ l = ld.get(name)
+ if not l:
+ l = []
+ ld[name] = l
+ return l
+
+ def add(ld, name, item):
+ l = ld._have(name)
+ l.append(item)
+ return ld
+
+ def add_dict(ld, d):
+ for k,v in d.items():
+ ld.add(k, v)
+
+ def update(ld, other_ld):
+ for name, items in other_ld.items():
+ ld.extend(name, items)
+ return ld
+
+ def extend(ld, name, vals):
+ l = ld._have(name)
+ l.extend(vals)
+ return ld
+
+ def remove(ld, name, item):
+ l = ld.get(name)
+ if item in l:
+ l.remove(item)
+ def _have(ld, name):
+ l = ld.get(name)
+ if not l:
+ l = []
+ ld[name] = l
+ return l
+
+ def add(ld, name, item):
+ l = ld._have(name)
+ l.append(item)
+ return ld
+
+ def add_dict(ld, d):
+ for k,v in d.items():
+ ld.add(k, v)
+
+ def update(ld, other_ld):
+ for name, items in other_ld.items():
+ ld.extend(name, items)
+ return ld
+
+ def extend(ld, name, vals):
+ l = ld._have(name)
+ l.extend(vals)
+ return ld
+
+ def remove(ld, name, item):
+ l = ld.get(name)
+ if item in l:
+ l.remove(item)
+
+class UniqueList(list):
+ def append(s, item):
+ if item in s or item is None:
+ return False
+ super().append(item)
+ return True
+
+ def insert(s, idx, item):
+ if item in s or item is None:
+ return False
+ super().insert(idx, item)
+ return True
+
+ def extend(s, items):
+ added = 0
+ for item in items:
+ if s.append(item):
+ added += 1
+ return added
+
+class NamedIds(dict):
+ def __init__(s, start_id:int=1):
+ set_instance_vars_from_args()
+
+ def next(s, name):
+ next_id = s.get(name, s.start_id)
+ s[name] = next_id + 1
+ return next_id
+
+class Packet:
+ def __init__(s, idx, cap_p):
+ set_instance_vars_from_args()
+ # sanitize impossible attr with dot in its name,
+ # seen gsm_a.bssmap and gsm_a.dtap
+ for name in dir(s.cap_p):
+ if '.' in name:
+ new_name = name.replace('.', '_')
+ elif not name:
+ new_name = 'unnamed'
+ else:
+ continue
+ setattr(s.cap_p, new_name, getattr(s.cap_p, name))
+
+ @classmethod
+ def pget(cls, cap_p, tokens, ifnone=None):
+ if cap_p is None or len(tokens) < 1:
+ return ifnone
+ p_field = getattr(cap_p, tokens[0], None)
+ if p_field is None:
+ p_field = getattr(cap_p, '.'.join(tokens), None)
+ if p_field is None:
+ return ifnone
+ if len(tokens) > 1:
+ return Packet.pget(p_field, tokens[1:], ifnone=ifnone)
+ return p_field
+
+ def get(s, field, ifnone=None):
+ return Packet.pget(s.cap_p, field.split('.'), ifnone=ifnone)
+
+ def str(s, elem_name=None):
+ strs = []
+ if elem_name:
+ elem = s.get(elem_name)
+ else:
+ elem = s.cap_p
+ return dir_vals(elem);
+
+ def field_names(s, elem_name=None, elem=None):
+ strs = ['', f'=== {elem_name} ===']
+ if elem is None:
+ elem = s.get(elem_name)
+ if not elem:
+ strs.append('None')
+ else:
+ for f in elem._get_all_fields_with_alternates():
+ for n in dir(f):
+ if n.startswith('_'):
+ continue
+ strs.append('%r=%r' % (n, getattr(f, n)))
+ return '\n'.join(strs)
+
+ def all_str(s, elem_name=None, elem=None, depth=1000):
+ strs = []
+ if elem is None:
+ if elem_name:
+ elem = s.get(elem_name)
+ else:
+ elem = s.cap_p
+ elem_name = '/'
+ strs.append('%s:' % elem_name)
+ for name in dir(elem):
+ if name.startswith('_') or name.endswith('_value') or name in [
+ 'sort', 'reverse', 'remove', 'pop', 'insert', 'index', 'extend', 'count',
+ 'copy', 'clear', 'append', 'zfill', 'max', 'min', 'resolution',
+ ]:
+ continue
+ try:
+ full_name = '%s.%s' % (elem_name, name)
+ val = getattr(elem, name)
+ if callable(val) or name in ['base16_value']:
+ continue
+ strs.append('%r=%r' % (full_name, val))
+ if depth and type(val) not in [int, str]:
+ strs.append(s.all_str(full_name, val, depth-1))
+ except:
+ pass
+ if hasattr(elem, '_get_all_fields_with_alternates'):
+ for f in elem._get_all_fields_with_alternates():
+ for n in dir(f):
+ if n.startswith('_'):
+ continue
+ full_name = '%r[%r]' % (elem_name, n)
+ try:
+ val = getattr(f, n)
+ except:
+ continue
+ if callable(val):
+ continue
+ strs.append('%s=%r' % (full_name, val))
+ if depth:
+ strs.append(s.all_str(full_name, val, depth-1))
+ return '\n'.join(strs)
+
+class IpPort:
+ all_ports = {}
+
+ @classmethod
+ def _key(cls, ip, port):
+ return f'{ip}:{port}'
+
+ @classmethod
+ def get(cls, ip, port, entity=None, proto=None, create=True):
+ o = IpPort.all_ports.get(IpPort._key(ip, port))
+ if o is None and create:
+ return IpPort(ip, port, entity, proto)
+ if o is not None:
+ if entity is not None:
+ if o.entity is not None and o.entity is not entity:
+ ERR('Port changes:', o, 'to', entity)
+ entity.add_port(o.proto, o)
+ return o
+
+ def __init__(s, ip:str=None, port:str=None, entity=None, proto=None):
+ set_instance_vars_from_args()
+ IpPort.all_ports[s.key()] = s
+
+ def __repr__(s):
+ r = ''
+ tokens = []
+ if s.entity:
+ tokens.append(f'{s.entity}')
+ if s.proto:
+ tokens.append(f'{s.proto}')
+ tokens.append(s.key())
+ return '-'.join(tokens)
+
+ def __eq__(s, other):
+ return s is other
+
+ def __hash__(s):
+ return hash(s.key())
+
+ def key(s):
+ return IpPort._key(s.ip, s.port)
+
+ @classmethod
+ def from_sdp(p:Packet):
+ ip = p.get('sdp.connection_info_address')
+ port = p.get('sdp.media_port')
+ return IpPort.get(ip, port)
+
+ @classmethod
+ def _from_ip(cls, p:Packet, src_dst:str, port:int):
+ ip = p.get('ip.' + src_dst)
+ if ip is None:
+ ipv6 = p.get('ipv6.' + src_dst)
+ if ipv6 is not None:
+ ip = '[' + ipv6 + ']'
+ return IpPort.get(ip, port)
+
+ @classmethod
+ def from_ip_source(cls, p:Packet, port:int):
+ return cls._from_ip(p, 'src', port)
+
+ @classmethod
+ def from_ip_dest(cls, p:Packet, port:int):
+ return cls._from_ip(p, 'dst', port)
+
+ @classmethod
+ def from_udp_source(cls, p:Packet):
+ return cls.from_ip_source(p, p.get('udp.srcport'))
+
+ @classmethod
+ def from_udp_dest(cls, p:Packet):
+ return cls.from_ip_dest(p, p.get('udp.dstport'))
+
+ @classmethod
+ def from_tcp_source(cls, p:Packet):
+ return cls.from_ip_source(p, p.get('tcp.srcport'))
+
+ @classmethod
+ def from_tcp_dest(cls, p:Packet):
+ return cls.from_ip_dest(p, p.get('tcp.dstport'))
+
+ @classmethod
+ def from_sctp_source(cls, p:Packet):
+ return cls.from_ip_source(p, p.get('sctp.srcport'))
+
+ @classmethod
+ def from_sctp_dest(cls, p:Packet):
+ return cls.from_ip_dest(p, p.get('sctp.dstport'))
+
+class Message:
+ pass
+
+class Trait:
+ def __init__(s, **kwargs):
+ if len(kwargs) > 1:
+ raise Exception('only one trait allowed per Trait(): %r' % kwargs)
+ for k, v in kwargs.items():
+ s.name = k
+ s.val = v
+
+ def __repr__(s):
+ return '%r=%r' % (s.name, s.val)
+
+class Traits(collections.OrderedDict):
+ def __init__(s, *args, **kwargs):
+ for arg in args:
+ s.add(arg)
+ s.set_vals(**kwargs)
+
+ def add(s, trait:Trait):
+ s[trait.name] = trait
+
+ def set(s, name, val):
+ if val is not None:
+ s.add(Trait(**{name: val}))
+
+ def set_vals(s, **kwargs):
+ for k,v in kwargs.items():
+ if v is None:
+ continue
+ if type(v) not in (str, int, float, bool, IpPort):
+ v = str(v)
+ s.set(k, v)
+
+ def __repr__(s):
+ strs = []
+ for k,trait in s.items():
+ assert k == trait.name
+ strs.append(repr(trait))
+ return '{%s}' % ', '.join(strs)
+
+def find_recent_msg(msg:Message, messages:list, my_idx:int, condition_func, max_t=1):
+ for i in reversed(range(my_idx)):
+ prev_msg = messages[i]
+ if not prev_msg:
+ continue
+ if prev_msg.finalized:
+ return None
+ if msg.timestamp - prev_msg.timestamp > max_t:
+ return None
+ if condition_func(prev_msg):
+ yield prev_msg
+ return None
+
+def find_same_trait(msg:Message, messages:list, my_idx:int, proto:str, name:str, max_t=1, operator=any):
+ def same_traits(prev_msg):
+ return msg.same_traits(prev_msg, proto, name, operator=operator)
+ return find_recent_msg(msg, messages, my_idx, same_traits, max_t)
+
+class dddict(dict):
+ @classmethod
+ def _get(cls, d, keys, create=False):
+ if not keys:
+ return d
+ k = keys[0]
+ v = d.get(k)
+ if len(keys) > 0:
+ if v is None:
+ if create:
+ v = {}
+ d[k] = v
+ else:
+ return None
+ r = cls._get(v, keys[1:], create=create)
+ return r
+
+ def gget(s, keys, create=None):
+ v = dddict._get(s, keys, create=False)
+ if v is None:
+ if create is None:
+ return None
+ else:
+ return s.sset(keys, create)
+ return v
+
+ def sset(s, keys, item):
+ d = dddict._get(s, keys[:-1], create=True)
+ d[keys[-1]] = item
+ return item
+
+ def ppop(s, keys):
+ d = dddict._get(s, keys[:-1])
+ if d is None:
+ return None
+ r = d.pop(keys[-1])
+ if not d:
+ if len(keys) > 1:
+ s.ppop(keys[:-1])
+ return r
+
+ @classmethod
+ def _count(cls, d):
+ if isinstance(d, dict):
+ count = 0
+ for val in d.values():
+ count += cls._count(val)
+ return count
+ return 1
+
+ def count(s):
+ return dddict._count(s)
+
+ @classmethod
+ def _all(cls, d):
+ if isinstance(d, dict):
+ for val in d.values():
+ yield from cls._all(val)
+ else:
+ yield d
+
+ def all(s):
+ return dddict._all(s)
+
+
+class Conn:
+ open_conns = dddict()
+ closed_conns = dddict()
+
+ '''One end of a time-limited connection for a protocol layer'''
+ def __init__(s, proto:str, port:IpPort, conn_id:str, start_msg:Message, close_msg:Message=None, idx=-1,
+ entity=None, counterparts:list=[], subscriber_conn=None, merge_counterparts=True, add_message=True,
+ meta=False):
+ set_instance_vars_from_args('entity', 'add_message', 'merge_counterparts')
+ s.tx_messages = UniqueList()
+ s.rx_messages = UniqueList()
+ if s.subscriber_conn:
+ s.subscriber_conn.conns.append(s)
+
+ s.keys = (proto, port.key(), conn_id)
+ Conn.open_conns.sset(s.keys, s)
+ # A counterpart is the same conn seen from the other side.
+ # For example, if a BSC opens a conn, the conterpart is the MSC's perspective on the same conn.
+ s.counterparts = UniqueList()
+ for cp in counterparts:
+ if cp is None:
+ continue
+ s.counterparts.append(cp)
+ cp.counterparts.append(s)
+
+ if entity:
+ entity.add_port(proto, port, from_msg=start_msg)
+
+ if add_message:
+ s.add_message(start_msg)
+
+ if not counterparts:
+ LOG(Color.colored('green', f'New conn (now {Conn.count_open_conns()})'), s.proto, '%r' % s.conn_id)
+ if merge_counterparts:
+ for other_conn in s.counterparts:
+ s.merge_subscr_conns(other_conn)
+
+ @classmethod
+ def _find(cls, keys, find_in_closed_conns=False):
+ ret = Conn.open_conns.gget(keys)
+ if ret is not None:
+ return ret
+ if find_in_closed_conns:
+ return Conn.closed_conns.gget(keys)
+ return None
+
+ @classmethod
+ def find(cls, proto:str, port:IpPort, conn_id:str, find_in_closed_conns=False):
+ return cls._find((proto, port.key(), conn_id), find_in_closed_conns=find_in_closed_conns)
+
+ @classmethod
+ def open(cls, proto:str, port:IpPort, conn_id:str, *args, **kwargs):
+ exists = cls.find(proto, port, conn_id)
+ if exists is not None:
+ ERR('Conn already open:', type(exists), repr(exists))
+ LOG(trace())
+ return Conn(proto, port, conn_id, *args, **kwargs)
+
+ @classmethod
+ def find_or_open(cls, proto:str, port:IpPort, conn_id:str, *args, **kwargs):
+ exists = cls.find(proto, port, conn_id)
+ if exists is not None:
+ return exists
+ return Conn(proto, port, conn_id, *args, **kwargs)
+
+ @classmethod
+ def open_2way(cls, proto:str, conn_id:str, msg:Message, *args, **kwargs):
+ conn1 = cls.open(proto, msg.src, conn_id, msg, *args, **kwargs)
+ cls.open(proto, msg.dst, conn_id, msg, *args, counterparts=[conn1], **kwargs)
+ return conn1
+
+ @classmethod
+ def close_conn(cls, conn, msg):
+ conn._add_message(msg)
+ conn.close_msg = msg
+ Conn.open_conns.ppop(conn.keys)
+ l = Conn.closed_conns.gget(conn.keys, create=[])
+ l.append(conn)
+ LOG(Color.colored('blue', f'Close conn ({Conn.count_open_conns()} left)'), conn)
+
+ @classmethod
+ def close(cls, proto, port, conn_id, close_msg, close_counterparts=True, if_exists=False):
+ conn = Conn.find(proto, port, conn_id)
+ if conn is None or not conn.is_open():
+ if if_exists == False:
+ ERR('Cannot close, conn not open:', Conn.key_label(proto, port, conn_id))
+ return None
+ Conn.close_conn(conn, close_msg)
+ assert Conn.find(proto, port, conn_id) is None
+ if close_counterparts:
+ for cp in conn.counterparts:
+ if cp.is_open():
+ Conn.close_conn(cp, close_msg)
+ return conn
+
+ @classmethod
+ def message(cls, proto, port, conn_id, msg):
+ conn = cls.find(proto, port, conn_id)
+ if conn is None:
+ return None
+ conn.add_message(msg)
+ return conn
+
+ def _add_message(s, msg):
+ if s.port == msg.src:
+ if s.meta:
+ msg.meta_conns.append(s)
+ else:
+ msg.src_conns.append(s)
+ s.tx_messages.append(msg)
+ elif s.port == msg.dst:
+ if s.meta:
+ msg.meta_conns.append(s)
+ else:
+ msg.dst_conns.append(s)
+ s.rx_messages.append(msg)
+
+ def add_message(s, msg, add_to_counterparts=True):
+ if s.close_msg:
+ out_error('Message on a closed conn', s, msg)
+ s._add_message(msg)
+ if add_to_counterparts:
+ for cp in s.counterparts:
+ cp._add_message(msg)
+
+ @classmethod
+ def key_label(cls, proto, port, conn_id):
+ if conn_id:
+ return f'{proto}:{conn_id}@{port}'
+ else:
+ return f'{proto}@{port}'
+
+ def label(s):
+ return s.key_label(s.proto, s.port, s.conn_id)
+
+ def __repr__(s):
+ tokens = [s.label()]
+ for r in s.counterparts:
+ if r is None:
+ tokens.append('None')
+ else:
+ tokens.append(r.label())
+ return ' -> '.join(tokens)
+
+ def merge_subscr_conns(s, other_conn):
+ if other_conn is None:
+ return
+ if not isinstance(other_conn, Conn):
+ for item in other_conn:
+ s.merge_subscr_conns(item)
+ return
+ assert isinstance(other_conn, Conn)
+ if s.subscriber_conn is not None and s.subscriber_conn is other_conn.subscriber_conn:
+ return
+ s.subscriber_conn = SubscriberConn.merge(s.subscriber_conn, other_conn.subscriber_conn)
+ if s.subscriber_conn is None:
+ s.subscriber_conn = SubscriberConn()
+ s.subscriber_conn.add_conn(s)
+ other_conn.subscriber_conn = s.subscriber_conn
+ s.subscriber_conn.add_conn(other_conn)
+
+ def find_entity(s, kind, with_port=None):
+ if s.subscriber_conn:
+ return s.subscriber_conn.find_entity(kind, with_port=with_port)
+ return None, None
+
+ def is_open(s):
+ return s.close_msg is None and Conn._find(s.keys) is s
+
+ @classmethod
+ def count_open_conns(cls):
+ count = 0
+ have = set()
+ for conn in cls.open_conns.all():
+ do_count = True
+ for conn2 in conn.counterparts:
+ if conn2.label() in have:
+ do_count = False
+ have.add(conn2.label())
+ if conn.label() in have:
+ do_count = False
+ have.add(conn.label())
+ if do_count:
+ count += 1
+ return count
+
+
+class Layer:
+
+ _classes = {}
+ traits = None
+ proto = None
+ cap_p_name = None
+
+ def __init__(s, msg:Message, proto:str, msgtype:str, traits:Traits, minor=False, hidden=False, cap_p_name:str=None):
+ set_instance_vars_from_args()
+ if proto in msg.layers:
+ raise Exception(f'duplicate proto {proto} for message')
+ msg.layers[proto] = s
+ s.msgtype = sane_msgtype(s.msgtype)
+ if not s.cap_p_name:
+ s.cap_p_name = s.proto
+ s.__class__.proto = s.proto
+ s.__class__.cap_p_name = s.cap_p_name
+
+ def label(s):
+ if s.msgtype:
+ return f'{s.proto}.{s.msgtype}'
+ else:
+ return s.proto
+
+ def identify_entities(s, msg:Message, messages, my_idx):
+ '''return a list of Message.EntityIdent instances describing source and/or destination entity that message msg identifies.'''
+ return None
+
+ @classmethod
+ def identify_conns(s, messages, my_idx):
+ pass
+
+ def collapse(s, messages, my_idx):
+ '''return the message itself if it remains in messages, if another absorbed it return the other message,
+ or if if it is dropped completely return None'''
+ return messages[my_idx]
+
+ @classmethod
+ def classes(cls):
+ if not Layer._classes:
+ for cls in Layer.__subclasses__():
+ name = cls.__name__
+ if not name.startswith('Layer_'):
+ continue
+ proto_name = name[len('Layer_'):]
+ Layer._classes[proto_name] = cls
+ #cls.proto = proto_name
+ return Layer._classes
+
+ @classmethod
+ def parse(cls, msg:Message):
+
+ for proto_name,child_class in Layer.classes().items():
+ if not msg.p.get(proto_name):
+ continue
+ child_class(msg)
+
+
+class Message:
+
+ def __init__(s, p:Packet, finalized=False):
+ set_instance_vars_from_args()
+ s.layers = collections.OrderedDict()
+ s.count = 1
+ s.count_back = 0
+ s.timestamp = float(p.cap_p.sniff_timestamp)
+ s.hide = False
+ s.src = None
+ s.dst = None
+ s.src_conns = []
+ s.dst_conns = []
+ s.meta_conns = []
+ s.absorbed = UniqueList()
+ s.strong_relation = True
+ s.debug = SHOW_ALL_DEBUG
+ s._log = []
+
+ def log(s, *text, **kwtext):
+ s._log.append(to_text(*text, *kwtext))
+
+ def dbg(s, *text, **kwtext):
+ if s.debug:
+ s._log.append(to_text(*text, *kwtext))
+
+ def is_minor(s):
+ return all(l.minor for l in s.layers.values())
+
+ def get_trait(s, proto:str, name:str, ifnone=None):
+ # allow alternative lists for proto, like s.get_trait(('tcp', 'udp'), 'src')
+ if proto is None:
+ proto = s.layers.keys()
+ if type(proto) is not str:
+ for proto_ in proto:
+ val = s.get_trait(proto_, name, None)
+ if val is not None:
+ return val
+ return ifnone
+ # allow alternative lists for name, like s.get_trait('tcp', ('src', 'dst))
+ if type(name) is not str:
+ for name_ in name:
+ val = s.get_trait(proto, name_, None)
+ if val is not None:
+ return val
+ return ifnone
+
+ if name == 'timestamp':
+ return int(s.timestamp)
+ layer = s.layers.get(proto, None)
+ if not layer:
+ return ifnone
+ if name == 'msgtype':
+ return layer.msgtype
+ trait = layer.traits.get(name, None)
+ if trait is None:
+ return ifnone
+ if trait.val is None:
+ return ifnone
+ return trait.val
+
+ def get_traits(s, proto=None, names=None, proto_and_names=None):
+ pn = []
+ if proto or names:
+ if proto is None or isinstance(proto, str):
+ proto = [proto]
+ for p in proto:
+ pn.append((p, names))
+ if proto_and_names:
+ pn.extend(proto_and_names)
+ for proto_, names in pn:
+ if proto_ is None:
+ proto_ = s.layers.keys()
+ if isinstance(proto_, str):
+ proto_ = [proto_]
+ for proto in proto_:
+ if names is None:
+ l = s.layers.get(proto, None)
+ if not l:
+ continue
+ names = l.traits.keys()
+ if type(names) is str:
+ names = [names]
+ for name in names:
+ result = s.get_trait(proto, name, ifnone=None)
+ if result is not None:
+ yield (proto, name, result)
+
+ def get_all_traits(s, proto:str):
+ layer = s.layers.get(proto)
+ if not layer:
+ return {}
+ return layer.traits
+
+ def same_traits(s, other_msg, proto:str, name:str, allow_unset=False, operator=all):
+ if type(proto) is not str:
+ return operator(
+ s.same_traits(other_msg, proto_, name, allow_unset=allow_unset)
+ for proto_ in proto
+ )
+
+ if name is None:
+ my_traits = s.get_all_traits(proto)
+ other_traits = other_msg.get_all_traits(proto)
+ names = set(my_traits.keys())
+ names.update(other_traits.keys())
+ name = list(names)
+
+ if type(name) is not str:
+ return operator(
+ s.same_traits(other_msg, proto, name_, allow_unset=allow_unset)
+ for name_ in name
+ )
+
+ val = s.get_trait(proto, name)
+ other_val = other_msg.get_trait(proto, name)
+ if not allow_unset:
+ if val is None or other_val is None:
+ return False
+ return val == other_val
+
+ def set_trait(s, proto, name, val):
+ layer = s.layers.get(proto, None)
+ if layer is None:
+ layer = Layer(s, proto, None, Traits(Trait(name, val)))
+ else:
+ layer.traits.set(name, val)
+
+ def collapse(s, messages, my_idx):
+ '''iterate backwards over recent messages and see if messages can be combined'''
+ orig_msg = messages[my_idx]
+ for layer in s.layers.values():
+ msg = layer.collapse(messages, my_idx)
+ if orig_msg is not msg:
+ break
+ return msg
+
+ def absorb_msg(s, other_msg):
+ global g_current_msg
+ if g_current_msg is other_msg:
+ g_current_msg = s
+ s._log.extend(other_msg._log)
+ if other_msg and other_msg is not s:
+ s.absorbed.append(other_msg)
+ if other_msg.absorbed:
+ other_absorbed = other_msg.absorbed
+ other_msg.absorbed = []
+ for oa in other_absorbed:
+ s.absorb_msg(oa)
+
+ def identify_conns(s, messages, my_idx):
+ msg = messages[my_idx]
+ for layer_class in Layer.classes().values():
+ if layer_class.proto not in msg.layers:
+ continue
+ layer_class.identify_conns(messages, my_idx)
+
+ class EntityIdent:
+ def __init__(s, proto=None, src_port=None, src_kind=None, src_entity=None, dst_port=None, dst_kind=None, dst_entity=None, rename=False):
+ set_instance_vars_from_args()
+
+ def identify_entities(s, messages, my_idx):
+ '''From protocol and message discriminators, see if we can identify the src and dst port of the message
+ to be of a specific core network entity.'''
+ for layer in s.layers.values():
+ try:
+ identifieds = layer.identify_entities(s, messages, my_idx)
+ if identifieds is None:
+ continue
+ if isinstance(identifieds, Message.EntityIdent):
+ identifieds = [identifieds]
+
+ for ident in identifieds:
+ if ident is None:
+ continue
+ Entity.find_or_create(ident.proto, ident.src_kind,
+ ident.src_port or s.src,
+ ident.src_entity, from_msg=s,
+ rename=(ident.rename is True or ident.rename == 'src'))
+ Entity.find_or_create(ident.proto, ident.dst_kind,
+ ident.dst_port or s.dst,
+ ident.dst_entity, from_msg=s,
+ rename=(ident.rename is True or ident.rename == 'dst'))
+ except:
+ raise Exception(f'In layer {layer} {s}')
+
+ def find_entity(s, kind, with_port=None):
+ for conn in (s.src_conns + s.dst_conns):
+ ret = conn.find_entity(kind, with_port=with_port)
+ if ret and ret[0] is not None:
+ return ret
+ return None, None
+
+ def get_port(s, entity_kind:str):
+ if s.src_entity_is(entity_kind):
+ return s.src
+ elif s.dst_entity_is(entity_kind):
+ return s.dst
+ return None
+
+ def entity(s, *kinds):
+ if s.src.entity and s.src.entity.kind in kinds:
+ return s.src.entity
+ if s.dst.entity and s.dst.entity.kind in kinds:
+ return s.dst.entity
+
+ def src_entity_is(s, *kinds):
+ return s.src.entity and s.src.entity.kind in kinds
+
+ def dst_entity_is(s, *kinds):
+ return s.dst.entity and s.dst.entity.kind in kinds
+
+ def same_src_dst(s, other, forward=None, reverse=None):
+ # assume forward and reverse if neither are set.
+ # if one of them is set to True, assume the other as False.
+ if forward is None and reverse is None:
+ forward = True
+ reverse = True
+ a = (s.src.key(), s.dst.key())
+ b = (other.src.key(), other.dst.key())
+ if forward and reverse:
+ return set(a) == set(b)
+ elif forward:
+ return a == b
+ elif reverse:
+ return a == tuple(reversed(b))
+ else:
+ return False
+
+ @classmethod
+ def parse(cls, p:Packet):
+ msg = Message(p)
+ Layer.parse(msg)
+ msg.src = msg.get_trait(('tcp','udp','sctp'), 'src')
+ msg.dst = msg.get_trait(('tcp','udp','sctp'), 'dst')
+ if msg.src is None or msg.dst is None:
+ return None
+ assert isinstance(msg.src, IpPort)
+ assert isinstance(msg.dst, IpPort)
+ return msg
+
+ def label(s):
+ label = []
+ for l in s.layers.values():
+ if not SHOW_ALL_LAYERS:
+ if l.minor:
+ continue
+ if l.hidden and not all((ll.minor or ll.hidden) for ll in s.layers.values()):
+ continue
+ label.insert(0, l.label())
+ return '/'.join(label)
+
+ def related_subscribers(s):
+ subscribers = UniqueList()
+ src_sc = s.src_subscriber_conn()
+ if src_sc:
+ subscribers.append(src_sc.subscriber)
+ dst_sc = s.dst_subscriber_conn()
+ if dst_sc:
+ subscribers.append(dst_sc.subscriber)
+ for a in s.absorbed:
+ subscribers.extend(a.related_subscribers())
+ return subscribers
+
+ def is_subscriber_related(s, subscriber):
+ return subscriber in s.related_subscribers()
+
+ def __repr__(s):
+ return s.__str__()
+
+ def __str__(s):
+ return s.str()
+
+ def str(s, ladder=False, one_column_per_kind=False, show_traits=True, show_conns=True):
+ t_name = []
+ t_name.extend(subscr.label() for subscr in s.related_subscribers())
+ name = s.label()
+ if name:
+ t_name.append(name)
+
+ src = str(s.src)
+ dst = str(s.dst)
+
+ if s.src.entity is not None:
+ src_str = s.src.entity.label()
+ else:
+ src_str = src
+
+ if s.count and dst == src:
+ dst_str = '(self)'
+ elif s.dst.entity is not None:
+ dst_str = s.dst.entity.label()
+ else:
+ dst_str = dst
+
+ src_pos = 0
+ dst_pos = 0
+ if s.src.entity:
+ src_pos = s.src.entity.labelcolumn(one_column_per_kind)
+ if s.dst.entity:
+ dst_pos = s.dst.entity.labelcolumn(one_column_per_kind)
+
+ # allows injecting informational fake messages (Entity.news)
+ if not s.count and not s.count_back:
+ dst_pos = src_pos
+
+ if not s.src.entity and not s.dst.entity:
+ if src > dst:
+ src_pos = dst_pos + 1
+ else:
+ dst_pos = src_pos + 1
+
+ if not ladder:
+ if src_pos > dst_pos:
+ src_pos = 1
+ dst_pos = 0
+ else:
+ src_pos = 0
+ dst_pos = 1
+
+ if src_pos <= dst_pos:
+ left_pos = src_pos
+ right_pos = dst_pos
+ left_label = src_str
+ right_label = dst_str
+ to_left_count = s.count_back
+ to_right_count = s.count
+ else:
+ left_pos = dst_pos
+ right_pos = src_pos
+ left_label = dst_str
+ right_label = src_str
+ to_left_count = s.count
+ to_right_count = s.count_back
+
+ left_strs = []
+ left_strs.append(left_label)
+ if to_left_count:
+ left_strs.append('<')
+ if to_left_count > 1:
+ left_strs.append(f'{to_left_count}')
+
+ right_strs = []
+ if to_right_count:
+ if to_right_count > 1:
+ right_strs.append(f'{to_right_count}')
+ right_strs.append('>')
+ right_strs.append(right_label)
+
+ real_left_pos = max(0, left_pos - (len(left_label)//2))
+ real_right_pos = right_pos - (len(right_label)//2) + len(right_label) + 1 - (len(right_label)&1)
+
+ left_str = ''.join(left_strs)
+ right_str = ''.join(right_strs)
+
+ mid_gap = real_right_pos - real_left_pos - len(right_str) - len(left_str)
+ mid_gap = max(1, mid_gap)
+
+ if not ladder:
+ mid_name_margin = 6
+ else:
+ mid_name_margin = mid_gap - len(name)
+ if to_left_count or to_right_count:
+ if mid_name_margin > 50:
+ mid_gap_strs = ['-' * int(mid_name_margin // 2),
+ name,
+ '-' * int(mid_name_margin - (mid_name_margin//2))]
+ name = ''
+ else:
+ mid_gap_strs = ['-' * int(mid_gap)]
+ else:
+ mid_gap_strs = []
+
+ t = [' ' * int(real_left_pos),
+ left_str,]
+ t.extend(mid_gap_strs)
+ t.append(right_str)
+
+ if ladder:
+ t = [''.join(t)]
+ right_end = len(t[0])
+ label_pos = Entity.textcol_one_per_kind if one_column_per_kind else Entity.textcol_one_per_entity
+ diff = label_pos - right_end
+ if diff > 0:
+ t.append(' ' * int(diff))
+
+ if show_traits:
+ if isinstance(show_traits, str):
+ show_traits = [show_traits]
+ for proto,l in s.layers.items():
+ if not l.traits:
+ continue
+ if (show_traits is not True) and (proto not in show_traits):
+ continue
+ t_name.append('%s%s' % (proto, l.traits))
+
+ if show_conns:
+ conns = set()
+ for c in (s.src_conns + s.dst_conns):
+ conns.add(f'{c.proto}:{c.conn_id}')
+ #conns.add(f'{c}')
+ t_name.append(' '.join(conns))
+
+ idxs = [s.p.idx] + [a.p.idx for a in s.absorbed]
+ if len(idxs) <= 3:
+ t_name.append('+'.join(str(i) for i in sorted(idxs)))
+ else:
+ t_name.append(f'{min(idxs)}-{max(idxs)}')
+ t_name.append(f'{s.timestamp:.3f}')
+ t.append(' ')
+ t = [''.join(t)]
+ indent = '\n' + (' ' * len(t[0]) + ' | ')
+ t.append(' '.join(t_name))
+
+ for l in s._log:
+ t.append(indent)
+ t.append(l)
+ return ''.join(t)
+
+
+ def src_subscriber_conn(s):
+ for conn in s.src_conns:
+ if conn.subscriber_conn is not None:
+ return conn.subscriber_conn
+ return None
+
+ def dst_subscriber_conn(s):
+ for conn in s.dst_conns:
+ if conn.subscriber_conn is not None:
+ return conn.subscriber_conn
+ return None
+
+ def find_message(s, proto, trait, value):
+ for subscr_conn in (s.src_subscriber_conn(), s.dst_subscriber_conn()):
+ if subscr_conn is None:
+ continue
+ msg = subscr_conn.find_message(proto, trait, value)
+ if msg:
+ return msg
+ return None
+
+
+
+class Entity:
+ '''A core network program like BSC, MSC, ...'''
+ KINDS_SORTING = ('MS', 'BTS', 'PCU', 'hNodeB', 'BSC', 'MGW@BSC', 'HNBGW', 'STP', 'MSC', 'MGW@MSC', 'MGW',
+ 'SGSN', 'HLR', 'SIP', 'SIPcon', 'PBX', 'GGSN')
+ KINDS_SORTING_EXIST = ()
+ entities = listdict()
+ state_version = 1 # whether to update cached text columns
+ spacing = 5
+ label_spacing = 2
+ textcol_one_per_kind = 0
+ textcol_one_per_entity = 0
+
+ # proxy / forwarding addresses to ignore, like the STP port
+ blacklist = []
+
+ def __init__(s, kind:str):
+ set_instance_vars_from_args()
+ s.idx = None
+ s.state_version = 0
+ s.ports = listdict()
+ s.labelcol_one_per_kind = 0
+ s.labelcol_one_per_entity = 0
+ Entity.add(s)
+
+ @classmethod
+ def add(cls, entity):
+ Entity.entities.add(entity.kind, entity)
+ entity.idx = entity.idx_in_kind()
+ if entity.kind not in Entity.KINDS_SORTING_EXIST:
+ # a new kind has come up, refresh Entity.KINDS_SORTING_EXIST
+ exist = []
+ for k in Entity.KINDS_SORTING:
+ if k in Entity.entities.keys():
+ exist.append(k)
+ for k in Entity.entities.keys():
+ if k not in exist:
+ exist.append(k)
+ Entity.KINDS_SORTING_EXIST = tuple(exist)
+
+ Entity.state_version += 1
+
+ @classmethod
+ def count_entities(cls, kind):
+ l = Entity.entities.get(kind)
+ return len(l)
+
+ @classmethod
+ def add_to_blacklist(cls, port:IpPort):
+ if port in cls.blacklist:
+ return
+ cls.blacklist.append(port);
+
+ def rename(s, to_kind):
+ Entity.entities.remove(s.kind, s)
+ was_kind = s.kind
+ s.kind = to_kind
+ Entity.add(s)
+ for proto,l in s.ports.items():
+ for port in l:
+ LOG(Color.colored('yellow', 'Rename port'), 'from', was_kind, 'to', s, proto, port)
+
+ @classmethod
+ def find_or_create(cls, proto, kind, port, matched_entity=None, from_msg=None, rename=False):
+ if not port:
+ return None
+ if port in Entity.blacklist:
+ return None
+
+ found_entity = port.entity
+ if found_entity and matched_entity and (found_entity is not matched_entity):
+ LOG(Color.colored('purple', 'Renaming entity port:'), port, 'to', matched_entity)
+ if not matched_entity:
+ matched_entity = found_entity
+ if matched_entity:
+ if kind and matched_entity.kind != kind and rename:
+ matched_entity.rename(kind)
+ matched_entity.add_port(proto, port, from_msg=from_msg)
+ return matched_entity
+
+ if kind is None or rename:
+ for l in Entity.entities.values():
+ for e in l:
+ if e.has_port(port):
+ if kind and e.kind != kind and rename:
+ e.rename(kind)
+ return e
+
+ if kind is None:
+ return None
+ else:
+ l = Entity.entities.get(kind)
+ if l:
+ for e in l:
+ if e.has_port(port):
+ return e
+ e = Entity(kind)
+ e.add_port(proto, port, from_msg=from_msg)
+ return e
+
+ def absorb(s, other_entity):
+ '''Merge two entities to one, adopting the other's ports'''
+ for port in other_entity.ports:
+ s.add_port(port)
+ del other_entity
+
+ def label(s):
+ idx = ''
+ if s.idx:
+ idx = str(s.idx + 1)
+ return f'{s.kind}{idx}'
+
+ def __repr__(s):
+ return s.label()
+ def __str__(s):
+ return repr(s)
+
+ def kind_idx(s):
+ '''this entity kind's position in the currently known entity kinds:
+ For 'BSC', if we've seen BTS, BSC and MSC, return 1.'''
+ return Entity.KINDS_SORTING_EXIST.index(s.kind)
+
+ def idx_in_all(s):
+ '''this entity kind's position in all currently known entities:
+ For the second 'BSC', if we've seen 2 BTS, 3 BSC and 1 MSC, return 2 (BTS) + 1 (second BSC) = 3.'''
+ idx = 0
+ for k in Entity.KINDS_SORTING_EXIST:
+ if k == s.kind:
+ idx += Entity.entities.get(s.kind).index(s)
+ return idx
+ idx += Entity.count_entities(k)
+ return idx
+
+ def idx_in_kind(s):
+ '''this entity kind's position in the list of entities of the same kind'''
+ return Entity.entities.get(s.kind).index(s)
+
+ def check_update_state(s):
+ if s.state_version == Entity.state_version:
+ return
+ Entity.calculate_textcolumns()
+ s.state_version = Entity.state_version
+
+ def labelcolumn(s, one_column_per_kind=False, mid=True):
+ s.check_update_state()
+ if one_column_per_kind:
+ midcol = s.labelcol_one_per_kind
+ else:
+ midcol = s.labelcol_one_per_entity
+
+ if mid:
+ ret = midcol
+ else:
+ ret = int(midcol - (len(s.label()) // 2))
+ return ret
+
+ @classmethod
+ def calculate_textcolumns(cls):
+ '''In text rendering of a ladder diagram, return the text column for this entity,
+ if rendering each entity in its own column (not sharing one column per entity kind)'''
+ entity_col = 0
+ kind_col = 0
+ for k in Entity.KINDS_SORTING_EXIST:
+ l = Entity.entities.get(k)
+
+ kind_col += len(k) // 2
+ for e in l:
+ e.labelcol_one_per_kind = kind_col
+ Entity.textcol_one_per_kind = kind_col + len(e.label()) + Entity.spacing
+
+ entity_col += len(e.kind)//2
+ e.labelcol_one_per_entity = entity_col
+ entity_col += len(e.label()) + Entity.spacing
+ Entity.textcol_one_per_entity = entity_col
+
+ kind_col += len(k) + Entity.spacing
+
+ def has_port(s, port, proto=None):
+ if proto:
+ if port in s.ports.get(proto, []):
+ return proto
+ return None
+ for proto,l in s.ports.items():
+ if port in l:
+ return proto
+ return None
+
+ def remove_port(s, port):
+ for proto,l in s.ports.items():
+ if port in l:
+ l.remove(port)
+ port.entity = None
+ return
+
+ def add_port(s, proto, port, from_msg=None):
+ if port.entity and port.entity is not s:
+ port.entity.remove_port(port)
+ if s.has_port(port, proto=proto):
+ return
+ s.ports.add(proto, port)
+ port.entity = s
+ port.proto = proto
+ LOG(Color.colored('cyan', 'New port:'), port)
+
+
+class Subscriber:
+ next_ident = 1
+ next_tmsi_idx = 1
+ imsis = {}
+ tmsis = {}
+ imeis = {}
+ msisdns = {}
+
+ def __init__(s, imsi:str=None, tmsi=None, imei=None, msisdn=None):
+ set_instance_vars_from_args()
+ s.subscriber_conns = UniqueList()
+ s.tmsis = set()
+ s.tmsi_idx = 0
+ s.set_last_tmsi(tmsi)
+ s.ident = Subscriber.next_ident
+ Subscriber.next_ident += 1
+ Subscriber.update_dicts(s)
+
+ def set_last_tmsi(s, tmsi):
+ if tmsi is None:
+ return
+ s.tmsi = tmsi
+ if (not isinstance(tmsi, str)) or len(str(tmsi)) < 8:
+ raise Exception('Invalid TMSI: ' + str(tmsi))
+ s.tmsi_idx = Subscriber.next_tmsi_idx
+ Subscriber.next_tmsi_idx += 1
+ s.tmsis.add(tmsi)
+ Subscriber.tmsis[s.tmsi] = s
+
+ @classmethod
+ def update_dicts(cls, s):
+ if s.imsi:
+ Subscriber.imsis[s.imsi] = s
+ for tmsi in s.tmsis:
+ Subscriber.tmsis[tmsi] = s
+ if s.imei:
+ Subscriber.imeis[s.imei] = s
+ if s.msisdn:
+ Subscriber.msisdns[s.msisdn] = s
+
+ def label(s, full=False):
+ l = []
+ if full or (not s.imsi and not s.tmsi and not s.imei and not s.msisdn):
+ l.append(f'subscr{s.ident}')
+ if s.imsi and (full or not s.msisdn):
+ l.append(f'imsi{s.imsi}')
+
+ if s.tmsi and (full or not s.imsi):
+ l.append(f'tmsi{s.tmsi}')
+ if s.imei and (full or (not s.imsi and not s.tmsi)):
+ l.append(f'imei{s.imei}')
+ if s.msisdn:
+ l.append(f'msisdn{s.msisdn}')
+ return Color.colored(s.ident, ':'.join(l))
+
+ def __repr__(s):
+ return s.label(full=True)
+ def __str__(s):
+ return s.label()
+
+ @classmethod
+ def identify_subscriber(cls, msg:Message):
+ imsi = msg.get_trait(('dtap','bssmap','rsl','gsup'), 'imsi')
+ tmsi = tmsi_standardize(msg.get_trait(('dtap','bssmap','rsl'), 'tmsi'))
+ imei = msg.get_trait('dtap', 'imei')
+ msisdn = msg.get_trait('gsup', 'msisdn')
+
+ if not (imsi or tmsi or imei or msisdn):
+ return
+ #if not msg.src_conns and not msg.dst_conns:
+ # return
+
+ # could start out with subscr = None, but to use a few less subscriber.ids start out with a present
+ # subscriber, if any.
+ subscr_conn = msg.src_subscriber_conn() or msg.dst_subscriber_conn()
+ if subscr_conn is not None:
+ subscr = subscr_conn.subscriber
+ else:
+ subscr = None
+
+ if imsi:
+ subscr = Subscriber.merge(Subscriber.by_imsi(imsi), subscr)
+ if tmsi:
+ subscr = Subscriber.merge(Subscriber.by_tmsi(tmsi), subscr)
+ if imei:
+ subscr = Subscriber.merge(Subscriber.by_imei(imei), subscr)
+ if msisdn:
+ subscr = Subscriber.merge(Subscriber.by_msisdn(msisdn), subscr)
+
+ if subscr is None:
+ return
+ if subscr_conn is None:
+ subscr_conn = SubscriberConn()
+ subscr_conn.set_subscriber(subscr)
+
+ for c in (msg.src_conns + msg.dst_conns):
+ if c.subscriber_conn is None:
+ subscr_conn.add_conn(c)
+ c.subscriber_conn = subscr_conn
+ else:
+ c.subscriber_conn = SubscriberConn.merge(c.subscriber_conn, subscr_conn)
+
+ @classmethod
+ def by_imsi(cls, imsi):
+ subscr = Subscriber.imsis.get(imsi)
+ if not subscr:
+ subscr = Subscriber(imsi=imsi)
+ return subscr
+
+ @classmethod
+ def by_tmsi(cls, tmsi):
+ subscr = Subscriber.tmsis.get(tmsi)
+ if not subscr:
+ subscr = Subscriber(tmsi=tmsi)
+ return subscr
+
+ @classmethod
+ def by_imei(cls, imei):
+ subscr = Subscriber.imeis.get(imei)
+ if not subscr:
+ subscr = Subscriber(imei=imei)
+ return subscr
+
+ @classmethod
+ def by_msisdn(cls, msisdn):
+ subscr = Subscriber.msisdns.get(msisdn)
+ if not subscr:
+ subscr = Subscriber(msisdn=msisdn)
+ return subscr
+
+ @classmethod
+ def merge(cls, a, b):
+ assert a is None or isinstance(a, Subscriber)
+ assert b is None or isinstance(b, Subscriber)
+ if a is None and b is None:
+ return None
+ if a is None:
+ return b
+ if b is None or b is a:
+ return a
+
+ if not a.imsi and a.ident > b.ident:
+ return cls.merge(b, a)
+
+ if a.imsi and b.imsi and a.imsi != b.imsi:
+ out_error(f'cannot absorb, subscriber would change IMSI: {b.imsi} -> {a.imsi}')
+ return None
+ if a.imei and b.imei and a.imei != b.imei:
+ out_error(f'cannot absorb, subscriber would change IMEI: {b.imei} -> {a.imei}')
+ return None
+
+ if b.imsi:
+ a.imsi = b.imsi
+ b.imsi = None
+
+ if b.tmsis:
+ a.tmsis.update(b.tmsis)
+ b.tmsis = set()
+ if b.tmsi_idx > a.tmsi_idx:
+ a.set_last_tmsi(b.tmsi)
+ b.tmsi = None
+
+ if b.imei:
+ a.imei = b.imei
+ b.imei = None
+
+ if b.msisdn:
+ if a.msisdn and a.msisdn != b.msisdn:
+ LOG(f'subscriber {a} changes MSISDN: {a.msisdn} -> {b.msisdn}')
+ a.msisdn = b.msisdn
+ b.msisdn = None
+
+ Subscriber.update_dicts(a)
+
+ for sc in b.subscriber_conns:
+ a.add_subscriber_conn(sc)
+ b.subscriber_conns = []
+ return a
+
+ def add_subscriber_conn(s, subscriber_conn):
+ if subscriber_conn.subscriber is s:
+ return
+ if subscriber_conn.subscriber:
+ subscriber_conn.subscriber.subscriber_conns.remove(subscriber_conn)
+ s.subscriber_conns.append(subscriber_conn)
+ subscriber_conn.subscriber = s
+ assert subscriber_conn in subscriber_conn.subscriber.subscriber_conns
+
+ def find_entity(s, kind, with_port=None):
+ for subscriber_conn in reversed(s.subscriber_conns):
+ found, found_subscriber_conn = subscriber_conn.find_entity(kind, ask_subscriber=False,
+ with_port=with_port)
+ if found is not None:
+ return found, found_subscriber_conn
+ return None, None
+
+class SubscriberConn:
+ '''A SubscriberConn is a collection of conns that feed into each other.
+ For example, the Abis and the BSSMAP link for the same subscriber are related, as well as the MGCP and
+ RTP spoken for that subscriber.
+ If a subscriber disconnects and connects again, that is a new separate SubscriberConn;
+ also if a subscriber would concurrently attach in twice somehow, that would be separate SubscriberConn
+ instances.
+
+ Note that a Message's src_conns and a dst_conns are not necessarily listed in the same SubscriberConn, for example
+ for RTP, SIP or SMS, the messages may pass from one subscriber to another.'''
+
+ def __init__(s, subscriber=None):
+ set_instance_vars_from_args()
+ s.conns = UniqueList()
+
+ @classmethod
+ def merge(cls, a, b):
+ assert a is None or isinstance(a, SubscriberConn)
+ assert b is None or isinstance(b, SubscriberConn)
+ if a is None and b is None:
+ return None
+ if a is None:
+ return b
+ if b is None or b is a:
+ return a
+ b_subscr = b.subscriber
+ if b.subscriber:
+ b.subscriber.subscriber_conns.remove(b)
+ b.subscriber = None
+ a.subscriber = Subscriber.merge(a.subscriber, b_subscr)
+ if a.subscriber:
+ a.subscriber.subscriber_conns.append(a)
+ for conn in b.conns:
+ conn.subscriber_conn = a
+ a.conns.append(conn)
+ b.conns = None
+ return a
+
+ def find_entity(s, kind, with_port=None, ask_subscriber=True):
+ if ask_subscriber and s.subscriber is not None:
+ return s.subscriber.find_entity(kind, with_port=with_port)
+ if with_port is not None and isinstance(with_port, str):
+ with_port = [with_port]
+ for conn in reversed(s.conns):
+ if with_port is not None and conn.port.proto not in with_port:
+ continue
+ if conn.port.entity is not None and conn.port.entity.kind == kind:
+ return conn.port.entity, s
+ return None, None
+
+ def find_message(s, proto, trait, val):
+ for conn in reversed(s.conns):
+ for msgs in (conn.tx_messages, conn.rx_messages):
+ for msg in msgs:
+ for p, t, v in msg.get_traits(proto, trait):
+ if val is None or val == v:
+ return msg
+ return None
+
+ def set_subscriber(s, subscriber):
+ s.subscriber = subscriber
+ if subscriber is not None:
+ subscriber.subscriber_conns.append(s)
+
+ def add_conn(s, conn):
+ s.conns.append(conn)
+ conn.subscriber_conn = s
+
+ def __repr__(s):
+ return f'{s.subscriber}~{s.conns}'
+
+
+class Layer_tcp(Layer):
+ def __init__(s, msg:Message):
+ p = msg.p
+ traits = Traits(
+ src=IpPort.from_tcp_source(p),
+ dst=IpPort.from_tcp_dest(p),
+ )
+ super().__init__(msg=msg, proto='tcp', msgtype=None, traits=traits, minor=True)
+
+class Layer_udp(Layer):
+ def __init__(s, msg:Message):
+ p = msg.p
+ traits = Traits(
+ src=IpPort.from_udp_source(p),
+ dst=IpPort.from_udp_dest(p),
+ )
+ super().__init__(msg=msg, proto='udp', msgtype=None, traits=traits, minor=True)
+
+class Layer_sctp(Layer):
+ def __init__(s, msg:Message):
+ p = msg.p
+ traits = Traits(
+ src=IpPort.from_sctp_source(p),
+ dst=IpPort.from_sctp_dest(p),
+ stream_id = p.get('sctp.data_sid'),
+ stream_seq = p.get('sctp.data_ssn'),
+ )
+ super().__init__(msg=msg, proto='sctp', msgtype=None, traits=traits, minor=True)
+
+class Layer_rtp(Layer):
+ def __init__(s, msg:Message):
+ pt = msg.p.get('rtp.p_type')
+
+ iuup_msgtype = sane_showname(msg.p.get('iuup.pdu_type.showname'))
+
+ if iuup_msgtype is not None:
+ msgtype = f'{pt}.{iuup_msgtype}'
+ else:
+ msgtype = pt
+
+ traits = Traits(
+ pt=pt,
+ iuup=iuup_msgtype
+ )
+ super().__init__(msg=msg, proto='rtp', msgtype=msgtype, traits=traits)
+
+ def collapse(s, messages, my_idx):
+ msgtype = s.msg.get_trait('rtp', 'msgtype')
+ src = s.msg.src
+ dst = s.msg.dst
+ for i in reversed(range(my_idx)):
+ prev_msg = messages[i]
+ if not prev_msg:
+ continue
+ if prev_msg.finalized:
+ break
+ if not 'rtp' in prev_msg.layers:
+ if prev_msg.is_minor():
+ continue
+ else:
+ break
+ if prev_msg.get_trait('rtp', 'msgtype') != msgtype:
+ continue
+ if s.msg.same_src_dst(prev_msg, forward=True):
+ # found a recent RTP similar RTP packet, combine
+ prev_msg.count += 1
+ messages[my_idx] = None
+ prev_msg.absorb_msg(s.msg)
+ return prev_msg
+ if 1 and s.msg.same_src_dst(prev_msg, reverse=True):
+ # same but backwards
+ prev_msg.count_back += 1
+ messages[my_idx] = None
+ prev_msg.absorb_msg(s.msg)
+ return prev_msg
+ return s.msg
+
+ # identify_entities: RTP ports are identified by watching RSL and MGCP, see Layer_gsm_abis_rsl.identify_conns_rtp
+
+ @classmethod
+ def identify_conns(cls, messages:list, my_idx:int):
+ msg = messages[my_idx]
+
+ conn = Conn.find('rtp', msg.src, conn_id=msg.src.key())
+ if conn is not None:
+ conn.add_message(msg)
+
+ conn = Conn.find('rtp', msg.dst, conn_id=msg.dst.key())
+ if conn is not None:
+ conn.add_message(msg)
+
+class Layer_mgcp(Layer):
+ def __init__(s, msg:Message):
+ p = msg.p
+ verb = p.get('mgcp.req_verb')
+ rsp = p.get('mgcp.rsp_rspstring')
+ msgtype = verb or rsp or '?'
+ tid = p.get('mgcp.transid', '')
+
+ rtp_port = None
+ sdp_rtp_ip = p.get('sdp.connection_info_address')
+ sdp_rtp_port = p.get('sdp.media_port')
+ if sdp_rtp_ip and sdp_rtp_port:
+ rtp_port = IpPort.get(sdp_rtp_ip, sdp_rtp_port)
+
+ if rsp:
+ endp = p.get('mgcp.param_specificendpointid')
+ else:
+ endp = p.get('mgcp.req_endpoint')
+ if endp and endp.startswith('rtpbridge/*@'):
+ endp = None
+ traits = Traits(
+ tid=tid,
+ endp=endp,
+ ci=p.get('mgcp.param_connectionid'),
+ verb=verb,
+ rsp=rsp,
+ rtp_port=rtp_port,
+ )
+
+ s.tid = tid
+ super().__init__(msg=msg, proto='mgcp', msgtype=msgtype, traits=traits)
+
+ def label(s):
+ return f'mgcp.{s.tid}.{s.msgtype}'
+
+ def identify_entities(s, msg:Message, messages, my_idx):
+ if msg.get_trait('mgcp', 'verb') == 'CRCX':
+ dst_kind = 'MGW'
+ if msg.src_entity_is('BSC'):
+ dst_kind = 'MGW@BSC'
+ elif msg.src_entity_is('MSC'):
+ dst_kind = 'MGW@MSC'
+ return Message.EntityIdent(proto='mgcp', dst_kind=dst_kind)
+ elif msg.get_trait('mgcp', 'rsp') and msg.src_entity_is('MGW', 'MGW@BSC','MGW@MSC'):
+ rtp = msg.get_trait('mgcp', 'rtp_port')
+ if rtp:
+ msg.src.entity.add_port('rtp', rtp)
+ return None
+
+ @classmethod
+ def find_req(cls, messages, my_idx):
+ msg = messages[my_idx]
+ for match in find_same_trait(msg, messages, my_idx, 'mgcp', 'tid'):
+ if match.get_trait('mgcp', 'rsp'):
+ continue
+ if not match.same_src_dst(msg, reverse=True):
+ continue
+ return match
+ return None
+
+ @classmethod
+ def identify_conns(cls, messages:list, my_idx:int):
+ msg = messages[my_idx]
+ proto = 'mgcp'
+
+ if msg.get_trait('mgcp', 'verb'):
+ endp = msg.get_trait('mgcp', 'endp')
+ mgw_port = msg.dst
+ endp_conn = Conn.find(proto, mgw_port, endp)
+ if endp_conn is not None:
+ endp_conn.add_message(msg)
+ for c in (msg.src_conns + msg.dst_conns):
+ endp_conn.merge_subscr_conns(c)
+
+ ci = msg.get_trait('mgcp', 'ci')
+ conn_id = f'{endp}:{ci}'
+ conn = Conn.find(proto, mgw_port, conn_id)
+ if conn:
+ conn.add_message(msg)
+ for c in (msg.src_conns + msg.dst_conns + msg.meta_conns):
+ conn.merge_subscr_conns(c)
+
+ if msg.get_trait('mgcp', 'rsp') == 'OK':
+ req = cls.find_req(messages, my_idx)
+ if req is None:
+ ERR('MGCP response without request')
+ return
+ mgw_port = req.dst
+
+ verb = req.get_trait('mgcp', 'verb')
+ if verb == 'CRCX':
+ # The MGCP connection
+ endp = msg.get_trait('mgcp', 'endp') or req.get_trait('mgcp', 'endp')
+ ci = msg.get_trait('mgcp', 'ci')
+ if not endp or not ci:
+ ERR('MGCP CRCX with endp =', endp, 'ci =', ci)
+
+ # creating two levels of conn: a meta conn with just endp,
+ # and a proper one with endp+ci
+ # endp:
+ endp_conn = Conn.find(proto, mgw_port, endp)
+ if not endp_conn:
+ endp_conn = Conn.open(proto, mgw_port, conn_id=endp, start_msg=msg)
+
+ # endp+ci:
+ conn_id = f'{endp}:{ci}'
+ conn = Conn.find(proto, mgw_port, conn_id)
+ if not conn:
+ conn = Conn.open(proto, mgw_port, conn_id, msg)
+ conn.add_message(req)
+ conn.add_message(msg)
+ conn.merge_subscr_conns(endp_conn)
+
+ # The RTP connection set up by MGCP
+ rtp_port = msg.get_trait('mgcp', 'rtp_port')
+ if rtp_port:
+ rtp_conn = Conn.open('rtp', rtp_port, conn_id=rtp_port.key(), start_msg=msg, merge_counterparts=False,
+ entity=conn.port.entity)
+ rtp_conn.merge_subscr_conns(conn)
+ # bssmap or ranap layer will mention this rtp_port in their Assignment / RAB-Assignment
+
+ else:
+ endp = req.get_trait('mgcp', 'endp')
+ ci = req.get_trait('mgcp', 'ci')
+ Conn.message(proto, mgw_port, endp, msg)
+
+ conn_id = f'{endp}:{ci}'
+ Conn.message(proto, mgw_port, conn_id, msg)
+
+ if verb == 'DLCX':
+ # The MGCP connection
+ endp = req.get_trait('mgcp', 'endp')
+ ci = req.get_trait('mgcp', 'ci')
+ if not endp:
+ ERR('MGCP DLCX without endp')
+
+ def close_ci(ci):
+ ci_conn_id = f'{endp}:{ci}'
+ # go through all RTP ports created in this conn
+ ci_conn = Conn.find(proto, mgw_port, ci_conn_id)
+ if ci_conn is not None:
+ all_rtp_ports = UniqueList()
+ for msgs in (ci_conn.rx_messages, ci_conn.tx_messages):
+ for msg in msgs:
+ all_rtp_ports.append(msg.get_trait('mgcp', 'rtp_port'))
+
+ for rtp_port in all_rtp_ports:
+ Conn.close('rtp', rtp_port, conn_id=rtp_port.key(), close_msg=msg, if_exists=True)
+ Conn.close(proto, mgw_port, ci_conn_id, msg)
+
+ if ci:
+ close_ci(ci)
+ else:
+ endp_conn = Conn.find(proto, mgw_port, endp)
+ all_ci = UniqueList()
+ if endp_conn:
+ for msgs in (endp_conn.tx_messages, endp_conn.rx_messages):
+ for msg in msgs:
+ all_ci.append(msg.get_trait('mgcp', 'ci'))
+ for ci in all_ci:
+ close_ci(ci)
+
+ Conn.close(proto, mgw_port, endp, msg, if_exists=True)
+
+
+ elif msg.get_trait('mgcp', 'verb') == 'MDCX':
+ # The RTP connection set up by BSC or MSC
+ rtp_port = msg.get_trait('mgcp', 'rtp_port')
+ if rtp_port and Conn.find('rtp', rtp_port, conn_id=rtp_port.key()) is None:
+ rtp_conn = Conn.open('rtp', rtp_port, conn_id=rtp_port.key(), start_msg=msg, merge_counterparts=False)
+ for c in msg.src_conns:
+ rtp_conn.merge_subscr_conns(c)
+
+class Layer_sccp(Layer):
+ def __init__(s, msg:Message):
+ p = msg.p
+ msgtype = p.get('sccp.message_type.showname')
+ traits = Traits(
+ src_lref=p.get('sccp.slr'),
+ dst_lref=p.get('sccp.dlr'),
+ )
+ super().__init__(msg=msg, proto='sccp', msgtype=msgtype, traits=traits, hidden=True)
+
+ def collapse(s, messages, my_idx):
+ msg = s.msg
+
+ # cut out STP hop
+ if SCCP_COLLAPSE_STP:
+ src = msg.src
+ t = msg.timestamp
+ for i in reversed(range(my_idx)):
+ prev_msg = messages[i]
+ if not prev_msg:
+ continue
+ if t - prev_msg.timestamp > 1:
+ break
+ if prev_msg.absorbed:
+ continue
+ prev_sccp = prev_msg.layers.get(s.proto, None)
+ if prev_sccp is None:
+ continue
+ #if src != prev_msg.dst:
+ # continue
+ if s.msgtype != prev_sccp.msgtype:
+ continue
+ if not msg.same_traits(prev_msg, 'sccp', ('src_lref', 'dst_lref'), allow_unset=True):
+ continue
+ if not msg.same_traits(prev_msg, 'sctp', 'stream_id'):
+ continue
+ if not msg.same_traits(prev_msg, 'm3ua', ('opc', 'dpc')):
+ continue
+ if not msg.same_traits(prev_msg, 'm3ua', 'message_length'):
+ continue
+
+ prev_msg.set_trait('sctp', 'dst', msg.get_trait('sctp', 'dst'))
+ prev_msg.dst = msg.dst
+ prev_msg.absorb_msg(msg)
+ messages[i] = None
+ messages[my_idx] = prev_msg
+ Entity.add_to_blacklist(src)
+ return prev_msg
+ return msg
+
+ @classmethod
+ def identify_conns(cls, messages:list, my_idx:int):
+ msg = messages[my_idx]
+ proto = 'sccp'
+ msgtype = msg.get_trait(proto, 'msgtype')
+ if SCCP_COLLAPSE_STP and not msg.absorbed:
+ return
+ src_id = msg.get_trait(proto, 'src_lref')
+ dst_id = msg.get_trait(proto, 'dst_lref')
+ if msgtype == 'Connection-Request':
+ Conn.open(proto, msg.src, src_id, msg)
+ elif msgtype == 'Connection-Confirm':
+ Conn.open(proto, msg.src, src_id, msg,
+ counterparts=[Conn.find(proto, msg.dst, dst_id)])
+ elif msgtype in ('Release-Complete',):
+ Conn.close(proto, msg.src, src_id, msg)
+ else:
+ if src_id:
+ Conn.message(proto, msg.src, src_id, msg)
+ if dst_id:
+ Conn.message(proto, msg.dst, dst_id, msg)
+
+
+class Layer_m3ua(Layer):
+ def __init__(s, msg:Message):
+ traits = Traits(
+ opc = msg.p.get('m3ua.protocol_data_opc'),
+ dpc = msg.p.get('m3ua.protocol_data_dpc'),
+ message_length = msg.p.get('m3ua.message_length'),
+ )
+ super().__init__(msg=msg, proto='m3ua', msgtype=None, traits=traits, minor=True)
+
+# wireshark commonly falsely classifies a BSSMAP Ciphering Mode Command as RNSAP PDU
+class Layer_rnsap(Layer):
+ def __init__(s, msg:Message):
+ p = msg.p
+ traits = Traits()
+ msgtype = 'Cipher Mode Command'
+ super().__init__(msg=msg, proto='bssmap', msgtype=msgtype, traits=traits)
+
+class Layer_bssap(Layer):
+ 'BSSAP, either BSS Management (see Layer_gsm_a_bssmap) or Direct Transfer (see Layer_gsm_a_dtap)'
+ def __init__(s, msg:Message):
+ msgtype = msg.p.get('bssap.msgtype.showname')
+ msgtype = msg.p.get('bssap.pdu_type.showname')
+
+ traits = Traits(
+ msgtype_nr=int(msg.p.get('bssap.pdu_type'), 16),
+ )
+
+ super().__init__(msg=msg, proto='bssap', msgtype=msgtype, traits=traits, minor=True)
+
+class Layer_bssgp(Layer):
+ def __init__(s, msg:Message):
+ msgtype = sane_msgtype(msg.p.get('bssgp.pdu_type.showname'))
+ traits = Traits(
+ tlli=msg.p.get('bssgp.gsm_a_rr_tlli'),
+ )
+ super().__init__(msg=msg, proto='bssgp', msgtype=msgtype, traits=traits)
+
+ def identify_entities(s, msg:Message, messages, my_idx):
+ if msg.get_trait('bssgp', 'msgtype') == 'FLOW-CONTROL-BVC':
+ return Message.EntityIdent(proto='bssgp', src_kind='PCU', dst_kind='SGSN')
+ return None
+
+ @classmethod
+ def identify_conns(cls, messages:list, my_idx:int):
+ msg = messages[my_idx]
+ proto = 'bssgp'
+ msgtype = msg.get_trait('dtap', 'msgtype')
+ tlli = msg.get_trait('bssgp', 'tlli')
+ conn_id = tlli
+ if not conn_id:
+ return
+ if msgtype == 'Attach-Request':
+ Conn.open(proto, msg.src, conn_id, msg,
+ counterparts=[Conn.open(proto, msg.dst, conn_id, msg)])
+ elif msgtype == 'Attach-Accept':
+ conn = Conn.close(proto, msg.src, conn_id, msg)
+ new_conn_id = msg.get_trait('dtap', 'tmsi')
+ new_conn = Conn.open(proto, msg.src, new_conn_id, msg,
+ counterparts=[Conn.open(proto, msg.dst, new_conn_id, msg)])
+ new_conn.merge_subscr_conns(conn)
+ elif msgtype == 'Attach-Complete':
+ Conn.close(proto, msg.src, conn_id, msg)
+ else:
+ conn = Conn.message(proto, msg.src, conn_id, msg)
+
+
+
+
+class Layer_hnbap(Layer):
+ def __init__(s, msg:Message):
+ def strip_till_dash(dashstr):
+ if not dashstr or not '-' in dashstr:
+ return dashstr
+ dash = dashstr.rindex('-')
+ return dashstr[dash+1:]
+
+ msgtype = strip_till_dash(msg.p.get('hnbap.procedurecode.showname'))
+ pdutype = strip_till_dash(sane_msgtype(msg.p.get('hnbap.hnbap_pdu.showname')))
+ pdutype_nr = msg.p.get('hnbap.hnbap_pdu')
+ traits = Traits(
+ msgtype_nr=int(msg.p.get('hnbap.procedurecode')),
+ pdutype=pdutype,
+ pdutype_nr=int(pdutype_nr),
+ )
+ super().__init__(msg=msg, proto='hnbap', msgtype=msgtype, traits=traits)
+
+ def identify_entities(s, msg:Message, messages, my_idx):
+ if (msg.get_trait('hnbap', 'msgtype') in ('Register', 'HNBRegister', 'UERegister')) and (msg.get_trait('hnbap', 'pdutype_nr') == 0):
+ return Message.EntityIdent(proto='Iuh', src_kind='hNodeB', dst_kind='HNBGW')
+ return None
+
+class Layer_rua(Layer):
+ def __init__(s, msg:Message):
+ def strip_till_dash(dashstr):
+ if not dashstr or not '-' in dashstr:
+ return dashstr
+ dash = dashstr.rindex('-')
+ return dashstr[dash+1:]
+
+ msgtype = strip_till_dash(msg.p.get('rua.procedurecode.showname'))
+ pdutype = strip_till_dash(sane_msgtype(msg.p.get('rua.rua_pdu.showname')))
+ pdutype_nr = msg.p.get('rua.rua_pdu')
+ cn_domain_i = msg.p.get('rua.cn_domainindicator')
+ cn_domain = None
+ if cn_domain_i == '0':
+ cn_domain = 'cs'
+ elif cn_domain_i == '1':
+ cn_domain = 'ps'
+ traits = Traits(
+ msgtype_nr=int(msg.p.get('rua.procedurecode')),
+ pdutype=pdutype,
+ cn_domain=cn_domain,
+ rua_ctx=msg.p.get('rua.context_id'),
+ )
+ super().__init__(msg=msg, proto='rua', msgtype=msgtype, traits=traits, cap_p_name='rua')
+
+ @classmethod
+ def identify_conns(cls, messages:list, my_idx:int):
+ msg = messages[my_idx]
+ proto = 'rua'
+ msgtype = msg.get_trait(proto, 'msgtype')
+ conn_id = (msg.get_trait(proto, 'cn_domain') or '?') + ':' + (msg.get_trait(proto, 'rua_ctx') or '?')
+ if msgtype == 'Connect':
+ conn = Conn.open(proto, msg.src, conn_id, msg)
+ Conn.open(proto, msg.dst, conn_id, msg, counterparts=[conn])
+ elif msgtype == 'Disconnect':
+ Conn.close(proto, msg.dst, conn_id, msg)
+ else:
+ Conn.message(proto, msg.src, conn_id, msg)
+
+
+class Layer_ranap(Layer):
+ def __init__(s, msg:Message):
+ msgtype = msg.p.get('ranap.rab_assignmentrequest_element'
+ ) or msg.p.get('ranap.rab_assignmentresponse_element'
+ ) or msg.p.get('ranap.initiatingmessage_element')
+
+ rtp_port = None
+ ip = msg.p.get('ranap.nsap_ipv4_addr')
+ port_bin = msg.p.get('ranap.bindingid.binary_value')
+ if ip and port_bin and len(port_bin) >= 2:
+ port = int.from_bytes(port_bin[:2], "big")
+ rtp_port = IpPort.get(ip, port)
+
+ traits = Traits(
+ rtp_port=rtp_port,
+ )
+ super().__init__(msg=msg, proto='ranap', msgtype=msgtype, traits=traits)
+
+ def collapse(s, messages, my_idx):
+ msg = s.msg
+
+ # cut out HNBGW hop
+ if IUH_COLLAPSE_HNBGW:
+ src = msg.src
+ t = msg.timestamp
+ for i in reversed(range(my_idx)):
+ prev_msg = messages[i]
+ if not prev_msg:
+ continue
+ if t - prev_msg.timestamp > 1:
+ break
+ if src != prev_msg.dst:
+ continue
+ if msg.src.entity is not prev_msg.dst.entity:
+ continue
+ # DOESNT WORK
+ if not msg.same_traits(prev_msg, 'ranap', None):
+ continue
+ if not msg.same_traits(prev_msg, 'sccp', ('src_lref', 'dst_lref'), allow_unset=True):
+ continue
+ if not msg.same_traits(prev_msg, 'sctp', 'stream_id'):
+ continue
+ if not msg.same_traits(prev_msg, 'm3ua', ('opc', 'dpc')):
+ continue
+
+ prev_msg.set_trait('sctp', 'dst', msg.get_trait('sctp', 'dst'))
+ prev_msg.dst = msg.dst
+ prev_msg.absorb_msg(msg)
+ messages[i] = None
+ messages[my_idx] = prev_msg
+ Entity.add_to_blacklist(src)
+ return prev_msg
+ return msg
+
+ def identify_entities(s, msg:Message, messages, my_idx):
+ ids = []
+ ids.append(s.identify_attach(msg, messages, my_idx))
+
+ msgtype = msg.get_trait('ranap', 'msgtype')
+ rtp_port = msg.get_trait('ranap', 'rtp_port')
+
+ if rtp_port and msgtype == 'RAB-AssignmentRequest':
+ # associate the MSC's MGCP port, but take care to not say the STP is an MSC
+ crcx_ok = msg.find_message('mgcp', 'rtp_port', rtp_port)
+ if crcx_ok and crcx_ok.src_entity_is('MGW'):
+ mgw = crcx_ok.src.entity
+
+ msc = None
+ if msg.src_entity_is('MSC'):
+ msc = msg.src.entity
+
+ ids.append(Message.EntityIdent(proto='mgcp', src_port=crcx_ok.src, src_entity=mgw, src_kind='MGW@MSC', rename=True,
+ dst_entity=msc, dst_port=crcx_ok.dst if msc else None))
+
+ if rtp_port and msgtype == 'RAB-AssignmentResponse' and msg.src_entity_is('hNodeB'):
+ ids.append(Message.EntityIdent(proto='rtp', src_kind='hNodeB', src_port=rtp_port, src_entity=msg.src.entity))
+
+ return ids
+
+ def identify_attach(s, msg:Message, messages, my_idx):
+ ids = []
+ dst_kind = None
+ msgtype = msg.get_trait('dtap', 'msgtype')
+ if msgtype in DTAP_COMPL_L3:
+ dst_kind = 'MSC'
+ proto = 'IuCS'
+ elif msgtype in GMM_COMPL_L3:
+ dst_kind = 'SGSN'
+ proto = 'IuPS'
+
+ if not dst_kind:
+ return None
+
+ if 'rua' in msg.layers:
+ return Message.EntityIdent(proto='Iuh', src_kind='hNodeB', dst_kind='HNBGW')
+
+ # don't mistake the STP as MSC or SGSN
+ if SCCP_COLLAPSE_STP and not msg.absorbed:
+ return None
+ if not SCCP_COLLAPSE_STP and msg.src_entity_is('HNBGW'):
+ return None
+
+ # FIXME: below only makes sense with SCCP_COLLAPSE_STP == True
+ if not SCCP_COLLAPSE_STP:
+ return None
+
+ # find a HNBGW that has recently received the same LU,
+ # associate IuCS port
+ src_entity = None
+ for match in find_same_trait(msg, messages, my_idx, 'dtap', None):
+ if 'rua' not in match.layers:
+ continue
+ if not match.dst_entity_is('HNBGW'):
+ continue
+ src_entity = match.dst.entity
+ if src_entity:
+ break
+
+ return Message.EntityIdent(proto=proto, src_kind='HNBGW', src_entity=src_entity, dst_kind=dst_kind)
+
+ @classmethod
+ def identify_conns(cls, messages, my_idx):
+ msg = messages[my_idx]
+
+ if SCCP_COLLAPSE_STP and not msg.absorbed:
+ return
+
+ rtp_port = msg.get_trait('ranap', 'rtp_port')
+ if rtp_port:
+ rtp_conn = Conn.find('rtp', rtp_port, rtp_port.key())
+ if rtp_conn:
+ for c in msg.src_conns:
+ rtp_conn.merge_subscr_conns(c)
+
+
+class Layer_gsm_a_bssmap(Layer):
+ def __init__(s, msg:Message):
+ p = msg.p
+ msgtype = p.get('gsm_a_bssmap.msgtype.showname')
+
+ rtp_port = None
+ ip = p.get('gsm_a_bssmap.aoip_trans_ipv4')
+ port = p.get('gsm_a_bssmap.aoip_trans_port')
+ if ip and port:
+ rtp_port = IpPort.get(ip, port)
+
+ tmsi = tmsi_standardize(p.get('gsm_a_bssmap.gsm_a_tmsi'))
+
+ traits = Traits(
+ msgtype_nr=str_to_int(p.get('gsm_a_bssmap.msgtype')),
+ rtp_port=rtp_port,
+ imsi=p.get('gsm_a_bssmap.e212_imsi'),
+ tmsi=tmsi,
+ )
+ super().__init__(msg=msg, proto='bssmap', msgtype=msgtype, traits=traits, cap_p_name='gsm_a_bssmap')
+
+ def identify_entities(s, msg:Message, messages, my_idx):
+ # don't mistake the STP as MSC or BSC
+ if SCCP_COLLAPSE_STP and not msg.absorbed:
+ return None
+
+ msgtype = msg.get_trait('bssmap', 'msgtype')
+ if msgtype in ('Complete-Layer-3-Information', 'Clear-Complete'):
+
+ # associate BSC BSSMAP port with BSC RSL port
+ src_entity = None
+ for match in find_same_trait(msg, messages, my_idx, 'dtap', ('tmsi', 'imsi')):
+ if 'rsl' not in match.layers:
+ continue
+ if not match.dst.entity or match.dst.entity.kind != 'BSC':
+ continue
+ src_entity = match.dst.entity
+ if src_entity:
+ break
+
+ return Message.EntityIdent(proto='bssmap', src_kind='BSC', dst_kind='MSC', src_entity = src_entity)
+
+ if msgtype == 'Assignment-Request':
+ # This Assignment-Request's rtp_port should match an earlier MGCP CRCX-OK message, and we now
+ # know that this MSC asked for it.
+ rtp_port = msg.get_trait('bssmap', 'rtp_port')
+ if not rtp_port:
+ return None
+
+ def cond(prev_msg):
+ return prev_msg.get_trait('mgcp', 'rtp_port') == rtp_port
+ crcx_ok = None
+ for prev_msg in find_recent_msg(msg, messages, my_idx, cond):
+ crcx_ok = prev_msg
+ break
+ if not crcx_ok:
+ return None
+ msc = msg.src.entity
+ mgw = crcx_ok.src.entity
+ return Message.EntityIdent(proto='mgcp', src_port=crcx_ok.src, src_entity=mgw, src_kind='MGW@MSC', rename=True,
+ dst_port=crcx_ok.dst, dst_entity=msc, dst_kind='MSC')
+
+
+ return None
+
+ @classmethod
+ def identify_conns(cls, messages, my_idx):
+ msg = messages[my_idx]
+ msgtype = msg.get_trait('bssmap', 'msgtype')
+
+ if SCCP_COLLAPSE_STP and not msg.absorbed:
+ return
+
+ # Paging does not have a proper end, it may never be answered.
+ # The RSL paging command hopefully happens, and closes this Conn.
+ if msgtype == 'Paging':
+ imsi = msg.get_trait('bssmap', 'imsi')
+ if imsi is not None:
+ c = Conn.open('bssmap', msg.dst, f'page_imsi{imsi}', msg)
+ Conn.close_conn(c, msg)
+ tmsi = msg.get_trait('bssmap', 'tmsi')
+ if tmsi is not None:
+ c = Conn.open('bssmap', msg.dst, f'page_tmsi{tmsi}', msg)
+ Conn.close_conn(c, msg)
+
+ rtp_port = msg.get_trait('bssmap', 'rtp_port')
+ if rtp_port is None:
+ return
+
+ rtp_conn = Conn.find('rtp', rtp_port, rtp_port.key())
+ if rtp_conn is None:
+ return
+ for c in msg.src_conns:
+ rtp_conn.merge_subscr_conns(c)
+
+ def cond(prev_msg):
+ return prev_msg.get_trait('mgcp', 'rtp_port') == rtp_port
+ crcx_ok = None
+ for prev_msg in find_recent_msg(msg, messages, my_idx, cond):
+ for c in (prev_msg.src_conns + prev_msg.dst_conns):
+ rtp_conn.merge_subscr_conns(c)
+
+
+class Layer_gsm_abis_rsl(Layer):
+ def __init__(s, msg:Message):
+ p = msg.p
+ msgtype = sane_msgtype(p.get('gsm_abis_rsl.msg_type.showname'))
+ msgtype_nr = p.get('gsm_abis_rsl.msg_type')
+ msgtype_nr = str_to_int(msgtype_nr)
+
+ ch = None
+ ch_imm_ass = None
+
+ # For Immediate Assignment, the assigned TS/chan is more interesting
+ ts = p.get('gsm_a_ccch.gsm_a_rr_timeslot')
+ cbits = (p.get('gsm_a_ccch.gsm_a_rr_sdcch4_sdcchc4_cbch')
+ or p.get('gsm_a_ccch.gsm_a_rr_sdcch8_sdcchc8_cbch'))
+ try:
+ ch_ts = int(ts)
+ ch_cbits = int(cbits)
+ ch_imm_ass = f'{ch_ts}-{ch_cbits}'
+ except:
+ pass
+
+ # normal RSL messages on a given TS/chan
+ ts = p.get('gsm_abis_rsl.ch_no_tn')
+ cbits = p.get('gsm_abis_rsl.ch_no_cbits')
+ if ts is not None and cbits is not None:
+ try:
+ ch_ts = int(ts)
+ ch_cbits = int(cbits)
+ ch = f'{ch_ts}-{ch_cbits}'
+ except:
+ raise
+
+ ch_assign = None
+ new_ch_ts = p.get('gsm_a_dtap.gsm_a_rr_timeslot')
+ new_ch_ss = p.get('gsm_a_dtap.gsm_a_rr_tch_facch_sacchf')
+ if new_ch_ts and new_ch_ss:
+ ch_assign = f'{new_ch_ts}-{new_ch_ss}'
+
+ rtp_local_port = None
+ ipacc_rtp_local_ip = p.get('gsm_abis_rsl.ipacc_local_ip')
+ ipacc_rtp_local_port = p.get('gsm_abis_rsl.ipacc_local_port')
+ if ipacc_rtp_local_ip and ipacc_rtp_local_port:
+ rtp_local_port = IpPort.get(ipacc_rtp_local_ip, ipacc_rtp_local_port)
+
+ rtp_remote_port = None
+ ipacc_rtp_remote_ip = p.get('gsm_abis_rsl.ipacc_remote_ip')
+ ipacc_rtp_remote_port = p.get('gsm_abis_rsl.ipacc_remote_port')
+ if ipacc_rtp_remote_ip and ipacc_rtp_remote_port:
+ rtp_remote_port = IpPort.get(ipacc_rtp_remote_ip, ipacc_rtp_remote_port)
+
+ req_ref_l = (p.get('gsm_abis_rsl.req_ref_ra'),
+ p.get('gsm_abis_rsl.req_ref_t1prim'),
+ p.get('gsm_abis_rsl.req_ref_t2'),
+ p.get('gsm_abis_rsl.req_ref_t3'))
+ if not all(req_ref_l):
+ req_ref_l = (p.get('gsm_a_ccch.gsm_a_rr_ra'),
+ p.get('gsm_a_ccch.gsm_a_rr_t1prim'),
+ p.get('gsm_a_ccch.gsm_a_rr_t2'),
+ p.get('gsm_a_ccch.gsm_a_rr_t3'))
+ req_ref = None
+ if all(req_ref_l):
+ req_ref = '-'.join(req_ref_l)
+
+ try:
+ page_tmsi = tmsi_standardize(msg.p.cap_p.gsm_abis_rsl._all_fields['3gpp.tmsi'])
+ except:
+ page_tmsi = None
+
+ traits = Traits(
+ msgtype_nr=msgtype_nr,
+ ch=ch,
+ ch_imm_ass=ch_imm_ass,
+ ch_assign=ch_assign,
+ chan_type=p.get('gsm_abis_rsl.ch_type'),
+ rtp_port=rtp_local_port,
+ rtp_remote_port=rtp_remote_port,
+ tmsi=tmsi_standardize(p.get('gsm_abis_rsl.gsm_a_tmsi')),
+ imsi=p.get('gsm_abis_rsl.gsm_a_imsi'),
+ page_tmsi=page_tmsi,
+ arfcn = p.get('gsm_a_dtap.gsm_a_rr_single_channel_arfcn') or p.get('gsm_abis_rsl.gsm_a_rr_single_channel_arfcn'),
+ req_ref = req_ref,
+ )
+
+ super().__init__(msg=msg, proto='rsl', msgtype=msgtype, traits=traits, cap_p_name='gsm_abis_rsl')
+ # ignore CCCH Load INDication
+ #if msgtype_nr == 18:
+ # msg.hide = True
+
+ def identify_entities(s, msg:Message, messages, my_idx):
+ ids = []
+ msgtype = msg.get_trait('rsl', 'msgtype')
+
+ if msgtype in ('RF-RESource-INDication', 'CCCH-LOAD-INDication', 'CHANnel-ReQuireD', ):
+ # INDication from BTS to BSC
+ ids.append(Message.EntityIdent(proto='rsl', src_kind='BTS', dst_kind='BSC'))
+ if msgtype in ('CHANnel-ACTIVation', 'IMMEDIATE-ASSIGN-COMMAND'):
+ # from BSC to BTS
+ ids.append(Message.EntityIdent(proto='rsl', dst_kind='BTS', src_kind='BSC'))
+
+ rtp_port = msg.get_trait('rsl', 'rtp_port')
+ if rtp_port and msg.src_entity_is('BTS') and msgtype in (
+ 'ip.access-CRCX-ACK', 'ip.access-MDCX-ACK'):
+ ids.append(Message.EntityIdent(proto='rtp', src_kind='BTS', src_entity=msg.src.entity, src_port=rtp_port))
+
+ if msgtype == 'ip.access-MDCX':
+ # This ip.a MDCX's rtp_remote_port should match an earlier MGCP CRCX-OK message, and we now
+ # know that this BSC asked for it.
+
+ rtp_port = msg.get_trait('rsl', 'rtp_remote_port')
+ if not rtp_port:
+ return None
+
+ def cond(prev_msg):
+ return prev_msg.get_trait('mgcp', 'rtp_port') == rtp_port
+ crcx_ok = None
+ for prev_msg in find_recent_msg(msg, messages, my_idx, cond):
+ crcx_ok = prev_msg
+ break
+ if not crcx_ok:
+ return None
+ bsc = msg.src.entity
+ mgw = crcx_ok.src.entity
+ ids.append( Message.EntityIdent(proto='mgcp', src_port=crcx_ok.src, src_entity=mgw, src_kind='MGW@BSC', rename=True,
+ dst_port=crcx_ok.dst, dst_entity=bsc, dst_kind='BSC') )
+
+ return ids
+
+ def collapse(s, messages, my_idx):
+ # combine duplicates like rsl.CCCH-LOAD-INDication
+ for i in reversed(range(my_idx)):
+ prev_msg = messages[i]
+ if not prev_msg:
+ continue
+ if prev_msg.finalized:
+ break
+ # stop combining at any non-rsl (and non-minor) message
+ if not 'rsl' in prev_msg.layers:
+ if all(l.minor for l in prev_msg.layers.values()):
+ continue
+ else:
+ break
+ if not same_nonempty(prev_msg.get_traits('rsl'), s.msg.get_traits('rsl')):
+ continue
+ if s.msg.same_src_dst(prev_msg, forward=True):
+ # found a recent similar packet, combine
+ prev_msg.count += 1
+ messages[my_idx] = None
+ prev_msg.absorb_msg(s.msg)
+ return prev_msg
+ return s.msg
+
+ @classmethod
+ def identify_conns_ra(cls, messages:list, my_idx:int):
+ msg = messages[my_idx]
+
+ msgtype = msg.get_trait('rsl', 'msgtype')
+
+ bts = msg.entity('BTS')
+ if bts is None:
+ return None
+ bts_port = msg.get_port('BTS')
+ if bts_port is None:
+ return None
+ ra = msg.get_trait('rsl', 'req_ref')
+ if ra is None:
+ return None
+ bts_ra = f'{bts_port.entity.label()}.ra{ra}'
+
+ proto = 'rsl'
+ conn_id = bts_ra
+
+ conn = None
+
+ if msgtype == 'CHANnel-ReQuireD':
+ if not msg.src_entity_is('BTS'):
+ return
+ bts = msg.src.entity
+ bts_port = msg.src
+ bsc = msg.dst.entity
+ bsc_port = msg.dst
+ conn = Conn.open(proto, bts_port, conn_id, msg, entity=bts)
+ Conn.open(proto, bsc_port, conn_id, msg, entity=bsc, counterparts=[conn])
+
+ elif msgtype in ('CHANnel-ACTIVation'):
+ conn = Conn.message(proto, msg.dst, conn_id, msg)
+
+ elif msgtype == 'IMMEDIATE-ASSIGN-COMMAND':
+ # the RA token has fulfilled its use as soon as an IMM ASS happened
+ Conn.close(proto, msg.dst, conn_id, msg)
+
+ else:
+ Conn.message(proto, msg.dst, conn_id, msg)
+
+ if conn:
+ for c in (msg.src_conns + msg.dst_conns):
+ conn.merge_subscr_conns(c)
+
+ @classmethod
+ def identify_conns_ch(cls, messages:list, my_idx:int):
+ msg = messages[my_idx]
+ proto = 'rsl'
+ msgtype = msg.get_trait('rsl', 'msgtype')
+
+ # For Immediate Assignment, the assigned TS/chan is the one to match on
+ if msgtype == 'IMMEDIATE-ASSIGN-COMMAND':
+ ch = msg.get_trait('rsl', 'ch_imm_ass')
+ else:
+ ch = msg.get_trait('rsl', 'ch')
+
+ if ch is None:
+ return None
+
+ if msgtype in ('CHANnel-ACTIVation') or msg.src_entity_is('BSC'):
+ bsc = msg.src.entity
+ bsc_port = msg.src
+ bts = msg.dst.entity
+ bts_port = msg.dst
+ elif msg.src_entity_is('BTS'):
+ bts = msg.src.entity
+ bts_port = msg.src
+ bsc = msg.dst.entity
+ bsc_port = msg.dst
+ else:
+ return None
+
+ if bts_port.entity is None:
+ return None
+
+ bts_ch = f'{bts_port.entity.label()}.ch{ch}'
+ conn_id = bts_ch
+
+ if msgtype == 'CHANnel-ACTIVation':
+ conn = Conn.open(proto, bts_port, conn_id, msg, entity=bts)
+ Conn.open(proto, bsc_port, conn_id, msg, entity=bsc, counterparts=[conn])
+ elif msgtype == 'RF-CHANnel-RELease-ACKnowledge':
+ conn = Conn.close(proto, msg.src, conn_id, msg)
+ else:
+ conn = Conn.message(proto, bts_port, conn_id, msg)
+ if conn:
+ for c in (msg.src_conns + msg.dst_conns):
+ conn.merge_subscr_conns(c)
+
+ # when changing to a new ch, e.g. a regular Assignment Command to change from SDCCH to TCH,
+ # associate the new channel with the conn
+ ch_assign = msg.get_trait('rsl', 'ch_assign')
+ if ch_assign:
+ conn_id = f'{bts_port.entity.label()}.ch{ch_assign}'
+ conn = Conn.find(proto, bts_port, conn_id)
+ if conn:
+ conn.add_message(msg)
+ for c in (msg.src_conns + msg.dst_conns):
+ conn.merge_subscr_conns(c)
+
+
+ @classmethod
+ def identify_conns_rtp(cls, messages:list, my_idx:int):
+ msg = messages[my_idx]
+ # BTS RTP
+ rtp_port = msg.get_trait('rsl', 'rtp_port')
+ if rtp_port and msg.src_entity_is('BTS') and msg.get_trait('rsl','msgtype') == 'ip.access-CRCX-ACK':
+ rtp_port.entity = msg.src.entity
+ rtp_conn = Conn.open('rtp', rtp_port, conn_id=rtp_port.key(), start_msg=msg, add_message=False)
+ for c in (msg.src_conns + msg.dst_conns):
+ rtp_conn.merge_subscr_conns(c)
+
+ # MGW@BSC RTP towards BTS
+ rtp_port = msg.get_trait('rsl', 'rtp_remote_port')
+ if rtp_port:
+ rtp_conn = Conn.find('rtp', rtp_port, conn_id=rtp_port.key())
+ if rtp_conn:
+ rtp_conn.add_message(msg)
+ for c in (msg.src_conns + msg.dst_conns):
+ rtp_conn.merge_subscr_conns(c)
+
+ @classmethod
+ def identify_conns_paging(cls, messages:list, my_idx:int):
+ msg = messages[my_idx]
+ msgtype = msg.get_trait('rsl', 'msgtype')
+
+ if msgtype != 'PAGING-CoMmanD':
+ return
+
+ tmsi = msg.get_trait('rsl', 'page_tmsi')
+ if tmsi is None:
+ return
+ conn_id = f'page_tmsi{tmsi}'
+
+ subscr = Subscriber.by_tmsi(tmsi)
+ if subscr is None:
+ return
+
+ rsl_conn = Conn.open('rsl', msg.src, conn_id, msg)
+ subscr_conn = SubscriberConn()
+ subscr.add_subscriber_conn(subscr_conn)
+ subscr_conn.add_conn(rsl_conn)
+
+ # Paging does not have a proper end, it may never be answered.
+ # A Conn wants to be closed at some point. Just close it directly.
+ Conn.close_conn(rsl_conn, msg)
+
+ # Expecting a recent Paging on BSSMAP
+ bssmap_paging = None
+ def cond(prev_msg):
+ return (prev_msg.get_trait('bssmap', 'msgtype') == 'Paging'
+ and prev_msg.get_trait('bssmap', 'tmsi') == tmsi)
+ for match in find_recent_msg(msg, messages, my_idx, cond):
+ bssmap_paging = match
+ break
+
+ if bssmap_paging is None:
+ return
+
+ bssmap_conn = Conn.find('bssmap', bssmap_paging.dst, conn_id, find_in_closed_conns=True)
+ if bssmap_conn:
+ rsl_conn.merge_subscr_conns(bssmap_conn)
+
+ @classmethod
+ def identify_conns(cls, messages:list, my_idx:int):
+ cls.identify_conns_ra(messages, my_idx)
+ cls.identify_conns_ch(messages, my_idx)
+ cls.identify_conns_rtp(messages, my_idx)
+ cls.identify_conns_paging(messages, my_idx)
+
+class Layer_gsm_a_dtap(Layer):
+ def __init__(s, msg:Message):
+ dtap = msg.p.get('gsm_a_dtap')
+ assert dtap is not None
+
+ msgtype = None
+ msgtype_nr = None
+ for f in dtap._get_all_fields_with_alternates():
+ if f.name.startswith('gsm_a.dtap.msg_') and f.name.endswith('_type'):
+ msgtype = f.showname_value
+ try:
+ msgtype_nr = int(f.raw_value)
+ except:
+ pass
+ traits = Traits(
+ msgtype_nr=msgtype_nr,
+ imsi=msg.p.get('gsm_a_dtap.e212_imsi') or msg.p.get('gsm_a_dtap.gsm_a_imsi'),
+ tmsi=tmsi_standardize(msg.p.get('gsm_a_dtap.gsm_a_tmsi') or msg.p.get('gsm_a_dtap.3gpp_tmsi')),
+ imei=msg.p.get('gsm_a_dtap.gsm_a_imei'),
+ to_msisdn=msg.p.get('gsm_a_dtap.cld_party_bcd_num'),
+ )
+ super().__init__(msg=msg, proto='dtap', msgtype=msgtype, traits=traits, cap_p_name='gsm_a_dtap')
+
+
+class Layer_gsup(Layer):
+ def __init__(s, msg:Message):
+ msgtype = sane_msgtype(msg.p.get('gsup.msg_type.showname'))
+ msisdn = None
+ if '-forwardSM-' not in msgtype:
+ msisdn = msg.p.get('gsup.e164_msisdn')
+ to_msisdn = msg.p.get('gsup.gsm_sms_tp_da')
+
+ msgtype_nr = None
+ is_request = None
+ try:
+ msgtype_nr = int(msg.p.get('gsup.msg_type'))
+ is_request = not (msgtype_nr & 0x3)
+ except:
+ pass
+
+ session_state = None
+ try:
+ session_state = int(msg.p.get('gsup.session_state'))
+ except:
+ pass
+
+ traits = Traits(
+ imsi=msg.p.get('gsup.e212_imsi'),
+ msgtype_nr=msgtype_nr,
+ is_request=is_request,
+ cn_domain=sane_showname(msg.p.get('gsup.cn_domain.showname')),
+ msisdn=msisdn,
+ to_msisdn=to_msisdn,
+ source_name=msg.p.get('gsup.source_name_text'),
+ destination_name=msg.p.get('gsup.destination_name_text'),
+ session_id=msg.p.get('gsup.session_id'),
+ session_state=session_state,
+ )
+ super().__init__(msg=msg, proto='gsup', msgtype=msgtype, traits=traits)
+
+ @classmethod
+ def identify_conns(cls, messages:list, my_idx:int):
+ msg = messages[my_idx]
+ imsi = msg.get_trait('gsup', 'imsi')
+ is_request = msg.get_trait('gsup', 'is_request')
+ session_id = msg.get_trait('gsup', 'session_id')
+ session_state = msg.get_trait('gsup', 'session_state')
+ if not imsi:
+ return
+ proto = 'gsup'
+ if session_id:
+ conn_id = f'{imsi}:{session_id}'
+ have = Conn.find(proto, msg.src, conn_id) or Conn.find(proto, msg.dst, conn_id)
+ if have is None:
+ have = Conn.open(proto, msg.src, conn_id, msg,
+ counterparts=[Conn.open(proto, msg.dst, conn_id, msg)])
+ if session_state is not None and session_state == 0x3:
+ Conn.close(proto, msg.src, conn_id, msg)
+ else:
+ have.add_message(msg)
+
+ else:
+ conn_id = f'{imsi}'
+
+ if is_request:
+ have = Conn.find(proto, msg.src, conn_id)
+ if have is None:
+ Conn.open(proto, msg.src, conn_id, msg)
+ else:
+ have.add_message(msg)
+ else:
+ have = Conn.find(proto, msg.dst, conn_id)
+ if have is None:
+ # it's a stray response? anyway create a conn for association with a subscriber
+ Conn.open(proto, msg.dst, conn_id, msg)
+ Conn.close(proto, msg.dst, conn_id, msg)
+
+ def identify_entities(s, msg:Message, messages, my_idx):
+ if msg.get_trait('gsup', 'msgtype') in ('SendAuthInfo-Request', 'UpdateLocation-Request', 'PurgeMS-Request'):
+ cn = msg.get_trait('gsup', 'cn_domain')
+ src_kind = None
+ src_entity = None
+ src_subscr_conn = None
+ if msg.get_trait('gsup', 'source_name'):
+ # proxy forwarding
+ src_kind = 'HLR'
+ traits = [name for proto, name, result in msg.get_traits('gsup') if name not in ('source_name', 'destination_name')]
+ for match in find_same_trait(msg, messages, my_idx, 'gsup', traits):
+ if not match.dst_entity_is('HLR'):
+ continue
+ src_entity = match.dst.entity
+ break
+ else:
+ if cn == 'CS':
+ src_kind = 'MSC'
+ elif cn == 'PS':
+ src_kind = 'SGSN'
+
+ if src_kind:
+ # associate MSC GSUP port with MSC BSSMAP port
+ imsi = msg.get_trait('gsup', 'imsi')
+ if imsi:
+ subscr = Subscriber.by_imsi(imsi)
+ def cond(prev_msg):
+ return (prev_msg.dst_entity_is(src_kind)
+ and prev_msg.is_subscriber_related(subscr))
+ for match in find_recent_msg(msg, messages, my_idx, cond):
+ src_entity = match.dst.entity
+ break
+
+ # i forgot whatever this does:
+ if not src_entity:
+ src_entity, src_subscr_conn = s.msg.find_entity(src_kind,
+ with_port=('bssgp', 'IuPS'))
+ else:
+ # no cn_domain in the GSUP message, try to guess
+ msc, msc_subscr_conn = s.msg.find_entity('MSC')
+ sgsn, sgsn_subscr_conn = s.msg.find_entity('SGSN')
+ if msc and not sgsn:
+ src_entity = msc
+ src_subscr_conn = msc_subscr_conn
+ if sgsn and not msc:
+ src_entity = sgsn
+ src_subscr_conn = sgsn_subscr_conn
+
+ if src_subscr_conn is not None and msg.src_conns:
+ for c in msg.src_conns:
+ c.subscriber_conn = SubscriberConn.merge(c.subscriber_conn, src_subscr_conn)
+
+ return Message.EntityIdent(proto='gsup', src_kind=src_kind, dst_kind='HLR', src_entity=src_entity, dst_entity=msg.dst.entity)
+
+class Layer_sip(Layer):
+ def __init__(s, msg:Message):
+ method = msg.p.get('sip.method')
+ cseq_method = msg.p.get('sip.cseq_method')
+ status_code = msg.p.get('sip.status_code')
+ status_line = msg.p.get('sip.status_line')
+ if status_line:
+ if '--' in status_line:
+ status_line = status_line[:status_line.index('--')]
+ status_line = status_line.strip()
+ status = status_line.split()[-1]
+ else:
+ status = status_code
+ if status:
+ msgtype = f'{cseq_method}-{status}'
+ elif method:
+ msgtype = method
+ else:
+ msgtype = cseq_method
+
+ sip_from_host = msg.p.get('sip.from_host')
+ sip_from_port = msg.p.get('sip.from_port')
+ sip_from = IpPort.get(sip_from_host, sip_from_port)
+
+ rtp_port = None
+ ip = msg.p.get('sip.sdp_connection_info_address')
+ port = msg.p.get('sip.sdp_media_port')
+ if ip and port:
+ rtp_port = IpPort.get(ip, port)
+
+ server = msg.p.get('sip.Server')
+ agent = msg.p.get('sip.User-Agent')
+
+ traits = Traits(
+ sip_agent=server or agent,
+ sip_from=sip_from,
+ call_id = msg.p.get('sip.call_id'),
+ method = cseq_method,
+ seq = msg.p.get('sip.cseq_seq'),
+ to_msisdn = msg.p.get('sip.to_user'),
+ from_msisdn = msg.p.get('sip.from_user'),
+ from_tag = msg.p.get('sip.from_tag'),
+ r_uri = msg.p.get('sip.r_uri'),
+ status_code = status_code,
+ rtp_port=rtp_port,
+ )
+ super().__init__(msg=msg, proto='sip', msgtype=msgtype, traits=traits)
+
+ def identify_entities(s, msg:Message, messages, my_idx):
+ src_kind = 'SIP'
+ sip_from = msg.get_trait('sip', 'sip_from')
+ agent = msg.get_trait('sip', 'sip_agent')
+
+ rename = False
+ if agent and sip_from and sip_from == msg.src:
+ rename = 'src'
+ if agent.startswith('kamailio'):
+ src_kind = 'PBX'
+ elif agent.startswith('sofia'):
+ src_kind = 'SIPCON'
+ else:
+ rename = False
+
+ return Message.EntityIdent(proto='sip', src_kind=src_kind, dst_kind='SIP', rename=rename)
+
+ @classmethod
+ def identify_conns(cls, messages:list, my_idx:int):
+ msg = messages[my_idx]
+
+ msgtype = msg.get_trait('sip', 'msgtype')
+ call_id = msg.get_trait('sip', 'call_id')
+ sip_from = msg.get_trait('sip', 'sip_from')
+
+ if msgtype in ('INVITE','INVITE-OK') and sip_from and sip_from == msg.src:
+ conn = Conn.open('sip', msg.src, call_id, start_msg=msg)
+ rtp_port = msg.get_trait('sip', 'rtp_port')
+ if rtp_port is None:
+ return
+
+ rtp_conn = Conn.find('rtp', rtp_port, conn_id=rtp_port.key())
+ if rtp_conn is None:
+ return
+ if not conn.subscriber_conn:
+ conn.merge_subscr_conns(rtp_conn)
+ else:
+ conn = Conn.message('sip', msg.src, call_id, msg)
+
+ if conn is None:
+ return
+
+
+class Layer_gsmtap_log(Layer):
+ def __init__(s, msg:Message):
+ app = msg.p.get('gsmtap_log.ident')
+ level = msg.p.get('gsmtap_log.level')
+ level_str = sane_showname(msg.p.get('gsmtap_log.level.showname'))
+ logmsg = msg.p.get('gsmtap_log.string')
+ cat = msg.p.get('gsmtap_log.subsys')
+
+ if level_str != 'ERROR':
+ return
+
+ return
+
+ msgtype = f'{app}.{level_str}'
+ msg.log(Color.colored('red', logmsg))
+
+ traits = Traits(
+ msgtype=msgtype,
+ level=level,
+ app=app,
+ cat=cat,
+ logmsg=logmsg,
+ )
+ super().__init__(msg=msg, proto='log', msgtype=msgtype, traits=traits)
+
+ def identify_entities(s, msg:Message, messages, my_idx):
+ app = msg.get_trait('log', 'app')
+ if app.startswith('Osmo'):
+ app = app[4:]
+ return Message.EntityIdent(proto='log', src_kind=app, dst_kind='LOG')
+
+
+class MessageFilter:
+ def __init__(s, layer=None, idx=None, values=[], negate=False):
+ set_instance_vars_from_args()
+
+ def matches(s, msg:Message):
+ r = s._matches(msg)
+ if s.negate:
+ return not r
+ return r
+
+ def _matches(s, msg:Message):
+ if s.layer and s.layer not in msg.layers:
+ return False
+ if s.idx and not (msg.p.idx == s.idx or any(a.p.idx == s.idx for a in msg.absorbed)):
+ return False
+ if s.values:
+ layers = s.layer or msg.layers.keys()
+ for k,v in s.values:
+ for proto, name, result in msg.get_traits(layers, k):
+ if v is None and name == k:
+ return True
+ if result is None or result == v:
+ return True
+ p_layers = None
+ if s.layer:
+ msg_layer = msg.layers.get(s.layer)
+ if msg_layer:
+ p_layers = [msg_layer.cap_p_name]
+ else:
+ p_layers = [s.layer]
+ else:
+ p_layers = [layer.cap_p_name for layer in msg.layers.values()]
+ for k,v in s.values:
+ for p_layer in p_layers:
+ p_val = msg.p.get(p_layer + '.' + k)
+ if p_val is None:
+ continue
+ if v is None:
+ return True
+ if v == str(p_val):
+ return True
+ return False
+ return True
+
+ @classmethod
+ def debug(cls, flt_list, msg:Message):
+ layers = set()
+ for flt in flt_list:
+ if flt.negate:
+ continue
+ if flt.layer is None:
+ layers.update(msg.layers.keys())
+ else:
+ layers.add(flt.layer)
+ for layer_name, layer in msg.layers.items():
+ if layer_name not in layers:
+ continue
+ LOG(dir_p(msg.p, layer.cap_p_name))
+
+ def __repr__(s):
+ t = []
+ if s.negate:
+ t.append('NOT')
+ if s.layer:
+ t.append(f'layer {s.layer!r}')
+ if s.idx:
+ t.append(f'packet nr {s.idx}')
+ if s.values:
+ t_v = [(f'{k} == {v!r}' if v is not None else f'has {k}') for k,v in s.values]
+ t.append('values: ' + (' or '.join(t_v)))
+ return ', '.join(t)
+
+ word_re = re.compile('^[a-zA-Z0-9_=-]*')
+ @classmethod
+ def parse(cls, spec_str):
+ if spec_str is None:
+ return []
+ if not spec_str:
+ return [MessageFilter()]
+ filters = []
+ token = None
+ try:
+ for token in spec_str.split(','):
+ negate = False
+ if token.startswith('!'):
+ token = token[1:]
+ negate = True
+ layer = cls.word_re.match(token).group()
+ flt = MessageFilter(layer=layer, negate=negate)
+ rest = cls.word_re.split(token)[1]
+ while rest:
+ char = rest[0]
+ rest = rest[1:]
+ word = cls.word_re.match(rest).group()
+ rest = cls.word_re.split(rest)[1]
+ if char == '#':
+ flt.idx = int(word)
+ elif char == '.':
+ if not flt.values:
+ flt.values = []
+ if '=' in word:
+ name = word[:word.index('=')]
+ val = word[word.index('=')+1:]
+ flt.values.append((name, val))
+ else:
+ flt.values.append((word, None))
+ else:
+ raise Exception('Unknown token: %r' % (char + word))
+ filters.append(flt)
+ return filters
+ except:
+ out_error('Some mistake in message filter: %r in token %r' % (spec_str, token))
+ raise
+
+ @classmethod
+ def match(cls, flt_list, msg):
+ any_match = False
+ for flt in flt_list:
+ r = flt.matches(msg)
+ if flt.negate and not r:
+ return False
+ any_match = any_match or r
+ return any_match
+
+
+ DOC = '''messagefilter examples, for --filter-msg and --debug:
+ dtap
+ All messages that contain a DTAP layer.
+ dtap.msgtype=Identity-Request
+ All DTAP msgtype=Identity-Request messages; for debug, show
+ dtap layer.
+ .msgtype=Identity-Request
+ All msgtype=Identity-Request messages; for debug, show all
+ layers. (Value names can be either parsed traits or raw packet
+ names.)
+ sccp.src_lref=0x00010000.dst_lref=0x00010000
+ All messages with an SCCP layer and either src_lref or dst_lref
+ == 0x00010000.
+ .imsi
+ All messages where any layer contains a value named 'imsi'.
+ '#123'
+ Message number 123 (don't forget to quote for the shell).
+ 'gsup,#614,sccp.src_lref=0x00010000'
+ Show all GSUP messages, packet number 614 and all SCCP with
+ given source local reference.
+ '!rsl.msgtype=CCCH-LOAD-INDication'
+ Don't show CCCH-LOAD-INDication message types (quote for the
+ shell).
+ '''
+
+class UI:
+ def __init__(s, opts, finalize_after_seconds=5):
+ set_instance_vars_from_args()
+
+ s.messages = []
+ s.finalized_idx = -1
+
+ s.show_traits = None
+ if s.opts.show_traits:
+ if s.opts.show_traits == 'all':
+ s.show_traits = True
+ else:
+ s.show_traits = s.opts.show_traits.split(',')
+ s.show_conns = None
+ if s.opts.show_conns:
+ if s.opts.show_conns == 'all':
+ s.show_conns = True
+ else:
+ s.show_conns = s.opts.show_conns.split(',')
+ s.filter_msg = MessageFilter.parse(s.opts.filter_msg)
+ for flt in s.filter_msg:
+ out_text_now('Filter-msg:', flt)
+ s.debug = MessageFilter.parse(s.opts.debug)
+ for dbg in s.debug:
+ out_text_now('Debug:', dbg)
+
+ s.filter_subscr = []
+ if s.opts.filter_subscr:
+ tokens = s.opts.filter_subscr.split(',')
+ names = ('imsi', '0x', 'imei', 'msisdn', 'tmsi')
+ for token in tokens:
+ handled = False
+ for name in names:
+ if token.startswith(name):
+ token_val = token[len(name):]
+ if not token_val.isdigit():
+ continue
+ s.filter_subscr.append(name + token[len(name):])
+ handled = True
+ break
+ if not handled:
+ s.filter_subscr.append(token)
+ s.filter_subscr.extend([name + token for name in names])
+
+ def out_text_now(s, *args, **kwargs):
+ 'to be implemented by child class'
+ assert False
+
+ def out_error(s, *args, **kwargs):
+ 'to be implemented by child class'
+ assert False
+
+ def msg_finalized(s, msg, apply_filter=True):
+ 'to be implemented by child class'
+ assert False
+
+ def flush_msg(s, msg):
+ msg.finalized = True
+ s.msg_finalized(msg)
+
+ def msg_filter(s, msg):
+ '''Return True when the message passes active message filtering, False if it should be hidden'''
+ if s.filter_msg and not MessageFilter.match(s.filter_msg, msg):
+ return False
+ if all(l.minor for l in msg.layers.values()):
+ return False
+ if msg.hide:
+ return False
+ if s.filter_subscr:
+ match = False
+ match_vals = set()
+ for subscr in msg.related_subscribers():
+ match_vals.update((f'imsi{subscr.imsi}', f'imei{subscr.imei}', f'msisdn{subscr.msisdn}'))
+ match_vals.update(subscr.tmsis)
+ match_vals.update(f'tmsi{tmsi[2:]}' for tmsi in subscr.tmsis)
+ match_vals.update(f'tmsi{tmsi}' for tmsi in subscr.tmsis)
+
+ if not any(token in match_vals for token in s.filter_subscr):
+ return False
+ return True
+
+ def flush(s, timestamp_now=0, finalize_after_seconds=0):
+ flush_t = timestamp_now - finalize_after_seconds
+ for i in range(s.finalized_idx+1, len(s.messages)):
+ msg = s.messages[i]
+ if not msg:
+ continue
+ if timestamp_now and msg.timestamp > flush_t:
+ break
+ s.finalized_idx = i
+ s.flush_msg(msg)
+
+ def start(s):
+ pass
+
+ def stop(s):
+ pass
+
+ def add_msg(s, msg):
+ global g_current_msg
+ s.flush(msg.timestamp, s.finalize_after_seconds)
+ try:
+ if s.debug and any(dbg_filter.matches(msg) for dbg_filter in s.debug):
+ msg.debug = True
+ g_current_msg = msg
+ if not msg.layers:
+ return
+ s.messages.append(msg)
+ idx = len(s.messages) - 1
+ changed_msg = msg.collapse(s.messages, idx)
+ # if the received message was absorbed by another, continue to identify the modified message using the
+ # new index
+ if changed_msg is not None and changed_msg is not msg:
+ msg = changed_msg
+ idx = s.messages.index(msg)
+ msg.identify_entities(s.messages, idx)
+ msg.identify_conns(s.messages, idx)
+ Subscriber.identify_subscriber(msg)
+
+ except:
+ s.flush()
+
+ out_error('Exception')
+ raise
+
+ def process_messages(s, msg_src):
+ for msg in msg_src.next():
+ if 0 and msg.p.idx in (3676,):
+ msg.log('rsl all_fields ', msg.p.cap_p.gsm_abis_rsl._all_fields)
+ msg.log(msg.p.all_str('gsm_abis_rsl'))
+
+ s.add_msg(msg)
+ s.flush()
+ msg_src.done()
+
+class UI_Plain(UI):
+ def out_text_now(s, *args, **kwargs):
+ print(to_text(*args, **kwargs))
+
+ def out_error(s, *args, **kwargs):
+ s.out_text_now(Color.colored('red', '*** ERROR:'), *args, **kwargs)
+ if g_current_msg:
+ s.out_text_now(Color.colored('red', '*** ERROR: while processing msg'), g_current_msg.str(show_traits=True, show_conns=True))
+ s.out_text_now(trace())
+
+ def msg_print(s, msg, apply_filter=True):
+ if apply_filter and not s.msg_filter(msg):
+ return
+ s.out_text_now(msg.str(ladder=True, one_column_per_kind=True, show_traits=s.show_traits, show_conns=s.show_conns))
+ if s.debug and MessageFilter.match(s.debug, msg):
+ MessageFilter.debug(s.debug, msg)
+
+ def msg_finalized(s, msg):
+ s.msg_print(msg, apply_filter=True)
+
+class UI_Quiet(UI_Plain):
+ def msg_finalized(s, msg):
+ pass
+
+class UI_Curses(UI):
+ pass
+
+class MsgSource:
+ def __init__(s, opts):
+ set_instance_vars_from_args()
+ s.start_t = None
+ s.p_min_t = None
+ s.p_max_t = None
+ s.end_t = None
+
+ def _next_cap_p(s):
+ assert False
+
+ def next(s) -> Message:
+ p_idx = 0
+ s.start_t = time.time()
+ warn_t = s.start_t
+ warn_p_t = None
+ for cap_p in s._next_cap_p():
+ p_idx += 1
+ if p_idx < s.opts.packet_start:
+ continue
+ if s.opts.packet_count and (p_idx - s.opts.packet_start) > s.opts.packet_count:
+ break
+ if s.opts.packet_end and p_idx > s.opts.packet_end:
+ break
+ msg = Message.parse(Packet(p_idx, cap_p))
+ if msg is None or not msg.layers:
+ continue
+ s.p_min_t = msg.timestamp if s.p_min_t is None else min(s.p_min_t, msg.timestamp)
+ s.p_max_t = msg.timestamp if s.p_max_t is None else max(s.p_max_t, msg.timestamp)
+
+ now = time.time()
+ if warn_p_t is None or now > warn_t + 3:
+ if warn_p_t:
+ packet_time = s.p_max_t - warn_p_t
+ real_time = now - warn_t
+ if real_time > (1.3 * packet_time):
+ out_text_now(f'! taking longer to calculate than packets arrive by {100.*(real_time - packet_time)/packet_time:.1f}%')
+ warn_t = now
+ warn_p_t = s.p_max_t
+
+ yield msg
+
+ def done(s):
+ if s.start_t is None or s.p_min_t is None:
+ out_text_now('Nothing processed.')
+ s.end_t = time.time()
+ out_text_now(f'packet time: {s.p_max_t - s.p_min_t:.1f}s in real time: {s.end_t - s.start_t:.1f}s')
+
+class MsgSource_File(MsgSource):
+ def __init__(s, path, opts):
+ set_instance_vars_from_args()
+ super().__init__(opts)
+
+ def _next_cap_p(s):
+ for cap_p in pyshark.FileCapture(s.path):
+ yield cap_p
+
+class MsgSource_Live(MsgSource):
+ def __init__(s, iface, opts):
+ set_instance_vars_from_args()
+ super().__init__(opts)
+
+ def _next_cap_p(s):
+ for cap_p in pyshark.LiveCapture(s.iface).sniff_continuously():
+ yield cap_p
+
+class MsgSource_Pipe(MsgSource):
+ def __init__(s, opts):
+ super().__init__(opts)
+
+ def _next_cap_p(s):
+ from pyshark.capture.pipe_capture import PipeCapture
+ for cap_p in PipeCapture(sys.stdin):
+ yield cap_p
+
+def run_tests():
+ def out_test(*args, **kwargs):
+ print(*args, **kwargs)
+
+ d = dddict()
+ d.sset(('a', 'b', 'c'), 'abc')
+ d.sset(('a', 'b', 'd'), 'abd')
+ out_test(d)
+ assert d == {'a': {'b': {'c': 'abc', 'd': 'abd'}}}
+ def verify_gget(keys, expect):
+ val = d.gget(keys)
+ out_test('gget:', keys,'=',val)
+ assert val == expect
+ verify_gget(('a', 'b', 'c'), 'abc')
+ verify_gget(('a', 'b', 'd'), 'abd')
+ verify_gget(('a', 'b', 'x'), None)
+ verify_gget(('a', 'b'), {'c': 'abc', 'd': 'abd'})
+ verify_gget(('a',), {'b': {'c': 'abc', 'd': 'abd'}})
+ verify_gget(('x',), None)
+ def verify_ppop(keys, expect):
+ try:
+ val = d.ppop(keys)
+ assert expect is not None
+ except KeyError:
+ assert expect is None
+ out_test('ppop:', keys,'=',None)
+ return
+ out_test('ppop:', keys,'=',val)
+ assert val == expect
+ assert d.gget(keys) is None
+ verify_ppop(('a', 'b', 'c'), 'abc')
+ verify_ppop(('a', 'b', 'd'), 'abd')
+ verify_ppop(('a', 'b', 'x'), None)
+ d.sset(('a', 'b', 'c'), 'abc')
+ d.sset(('a', 'b', 'd'), 'abd')
+ verify_ppop(('a', 'b'), {'c': 'abc', 'd': 'abd'})
+ d.sset(('a', 'b', 'c'), 'abc')
+ d.sset(('a', 'b', 'd'), 'abd')
+ verify_ppop(('a',), {'b': {'c': 'abc', 'd': 'abd'}})
+ verify_ppop(('x',), None)
+
+SUBSCRIBERFILTER_DOC = '''subscriberfilter examples, for --filter-subscr:
+ 123
+ Show subscriber where any value matches 123 (probably only MSISDN will
+ match, because '123' is too short for IMSI etc).
+ imsi123456789012345
+ imei123456789012345
+ msisdn123456789012345
+ tmsi1234abcd
+ Show subscriber with the given IMSI/IMEI/MSISDN/TMSI.
+ imsi123456,imsi987654
+ Show both these IMSIs.
+ imsi123456,msisdn123,1234abcd,imei987654
+ Show all of these subscribers: IMSI 123456, MSISDN 123, TMSI 0x1234abcd
+ and IMEI 987654.
+'''
+def parse_args():
+ import argparse
+ parser = argparse.ArgumentParser(description=__doc__
+ + '\n' + SUBSCRIBERFILTER_DOC
+ +'\n' + MessageFilter.DOC,
+ formatter_class=argparse.RawDescriptionHelpFormatter)
+ parser.add_argument('--pcap-file', '-f', metavar='file')
+ parser.add_argument('--live-capture', '-l', metavar='interface')
+ parser.add_argument('--stdin-capture', '-i', action='store_true')
+ parser.add_argument('--packet-start', '-S', default=0, type=int)
+ parser.add_argument('--packet-count', '-C', default=0, type=int)
+ parser.add_argument('--packet-end', '-E', default=0, type=int)
+ parser.add_argument('--ui', '-u', metavar='USER-INTERFACE',
+ help='How to display messages: plain / p = print to stdout as plain-text;'
+ ' curses / c = interactive curses interface; none / n = quiet',
+ default='plain')
+ parser.add_argument('--filter-subscr', default=None,
+ help='Show only messages related to the given subscriberfilter')
+ parser.add_argument('--filter-msg', default=None, metavar='messagefilter',
+ help='Show only messages matching this messagefilter')
+ parser.add_argument('--show-traits', default=None)
+ parser.add_argument('--show-conns', default=None, help="'all' for all, or specific conn names (comma separated)")
+ parser.add_argument('--collapse-stp', '-s', default=None, help="BSSAP via STP may appear duplicated. '-s1' collapses the duplicates, -s0 does not.")
+ parser.add_argument('--test', action='store_true')
+ parser.add_argument('--debug', metavar='messagefilter',
+ help='Show a lot more info on messages matching this messagefilter')
+ return parser.parse_args()
+
+def main():
+ opts = parse_args()
+
+ if opts.test:
+ run_tests()
+ else:
+ if (opts.collapse_stp or '').upper() in ['1', 'Y', 'YES', 'TRUE']:
+ SCCP_COLLAPSE_STP = True
+ if (opts.collapse_stp or '').upper() in ['0', 'N', 'NO', 'FALSE']:
+ SCCP_COLLAPSE_STP = False
+ ui_class = None
+ ui_type = opts.ui or 'none'
+ if 'plain'.startswith(ui_type):
+ ui_class = UI_Plain
+ elif 'curses'.startswith(ui_type):
+ ui_class = UI_Curses
+ elif 'none'.startswith(ui_type):
+ ui_class = UI_Quiet
+ else:
+ ERR('Unknown UI type:', repr(ui_type))
+ return 1
+ g_ui = ui_class(opts)
+ msg_source = None
+ if opts.pcap_file:
+ msg_source = MsgSource_File(opts.pcap_file, opts)
+ elif opts.live_capture:
+ msg_source = MsgSource_Live(opts.live_capture, opts)
+ elif opts.stdin_capture:
+ msg_source = MsgSource_Pipe(opts)
+ else:
+ ERR('No message source, try `-l any` or `-f my.pcap`')
+ return 1
+ g_ui.process_messages(msg_source)
+ g_ui.flush()
+ if Conn.open_conns:
+ print('still open conns:', repr(Conn.open_conns))
+ return 0
+
+if __name__ == '__main__':
+ if False:
+ import cProfile
+ cProfile.run('main()', sort='tottime')
+ else:
+ exit(main())
+# vim: noexpandtab tabstop=8 shiftwidth=8
diff --git a/utils/osmo-ns-dummy.c b/utils/osmo-ns-dummy.c
index 0af1a897..890444cf 100644
--- a/utils/osmo-ns-dummy.c
+++ b/utils/osmo-ns-dummy.c
@@ -8,7 +8,8 @@
#include <osmocom/core/select.h>
#include <osmocom/core/application.h>
#include <osmocom/core/stats.h>
-
+#include <osmocom/ctrl/control_if.h>
+#include <osmocom/ctrl/control_vty.h>
#include <osmocom/gprs/gprs_ns2.h>
#include <osmocom/vty/vty.h>
#include <osmocom/vty/telnet_interface.h>
@@ -27,6 +28,7 @@ static bool quit = false;
static bool config_given = false;
static bool daemonize = false;
static int vty_port = 0;
+static int ctrl_port = 0;
static char *config_file = NULL;
struct gprs_ns2_inst *g_nsi;
@@ -43,7 +45,7 @@ static struct vty_app_info vty_info = {
.copyright = vty_copyright,
};
-static void print_help()
+static void print_help(void)
{
printf( "Some useful options:\n"
" -h --help This text\n"
@@ -51,6 +53,7 @@ static void print_help()
" -V --version Print version\n"
" -D --daemonize Fork the process into a background daemon\n"
" -p --vty-port PORT Set the vty port to listen on.\n"
+ " -r --ctrl-port PORT Set the ctrl port to listen on.\n"
"\nVTY reference generation:\n"
" --vty-ref-mode MODE VTY reference generation mode (e.g. 'expert').\n"
" --vty-ref-xml Generate the VTY reference XML output and exit.\n"
@@ -93,12 +96,13 @@ static void handle_options(int argc, char **argv)
{ "version", 0, 0, 'V' },
{ "daemonize", 0, 0, 'D' },
{ "vty-port", 1, 0, 'p' },
+ { "ctrl-port", 1, 0, 'r' },
{ "vty-ref-mode", 1, &long_option, 1 },
{ "vty-ref-xml", 0, &long_option, 2 },
{ 0, 0, 0, 0 }
};
- c = getopt_long(argc, argv, "hc:p:VD",
+ c = getopt_long(argc, argv, "hc:p:r:VD",
long_options, &option_idx);
if (c == -1)
break;
@@ -120,7 +124,14 @@ static void handle_options(int argc, char **argv)
case 'p':
vty_port = atoi(optarg);
if (vty_port < 0 || vty_port > 65535) {
- fprintf(stderr, "Invalid port %d given!\n", vty_port);
+ fprintf(stderr, "Invalid VTY port %d given!\n", vty_port);
+ exit(1);
+ }
+ break;
+ case 'r':
+ ctrl_port = atoi(optarg);
+ if (ctrl_port < 0 || ctrl_port > 65535) {
+ fprintf(stderr, "Invalid CTRL port %d given!\n", ctrl_port);
exit(1);
}
break;
@@ -223,6 +234,7 @@ extern int nsdummy_vty_init(void);
int main (int argc, char *argv[])
{
void *ctx = tall_nsdummy_ctx = talloc_named_const(NULL, 0, "osmo-ns-dummy");
+ struct ctrl_handle *ctrl;
int rc = 0;
osmo_init_logging2(ctx, &log_info);
@@ -235,6 +247,7 @@ int main (int argc, char *argv[])
vty_info.tall_ctx = ctx;
vty_init(&vty_info);
+ ctrl_vty_init(ctx);
logging_vty_add_cmds();
osmo_stats_vty_add_cmds();
osmo_talloc_vty_add_cmds();
@@ -259,13 +272,20 @@ int main (int argc, char *argv[])
fprintf(stderr, "No config file: '%s' Using default config.\n",
config_file);
- rc = telnet_init_dynif(ctx, NULL, vty_get_bind_addr(),
- vty_port);
+ rc = telnet_init_default(ctx, NULL, vty_port);
if (rc < 0) {
fprintf(stderr, "Error initializing telnet\n");
exit(1);
}
+ if (ctrl_port > 0) {
+ ctrl = ctrl_interface_setup(NULL, ctrl_port, NULL);
+ if (!ctrl) {
+ fprintf(stderr, "Failed to initialize control interface. Exiting.\n");
+ exit(1);
+ }
+ }
+
signal(SIGINT, sighandler);
signal(SIGTERM, sighandler);
signal(SIGPIPE, sighandler);
diff --git a/utils/osmo-stat-dummy/Makefile.am b/utils/osmo-stat-dummy/Makefile.am
new file mode 100644
index 00000000..0232736b
--- /dev/null
+++ b/utils/osmo-stat-dummy/Makefile.am
@@ -0,0 +1,10 @@
+if ENABLE_UTILITIES
+noinst_PROGRAMS = osmo-stat-dummy
+osmo_stat_dummy_SOURCES = osmo-stat-dummy.c
+osmo_stat_dummy_LDADD = $(TALLOC_LIBS) \
+ $(top_builddir)/src/vty/libosmovty.la \
+ $(top_builddir)/src/ctrl/libosmoctrl.la \
+ $(top_builddir)/src/core/libosmocore.la
+osmo_stat_dummy_CFLAGS = -Wall $(TALLOC_CFLAGS)
+osmo_stat_dummy_CPPFLAGS = -I$(top_srcdir)/include -I$(top_builddir)/include
+endif
diff --git a/utils/osmo-stat-dummy/README.md b/utils/osmo-stat-dummy/README.md
new file mode 100644
index 00000000..34ffbb4c
--- /dev/null
+++ b/utils/osmo-stat-dummy/README.md
@@ -0,0 +1,12 @@
+# Osmocom utilities
+
+* osmo-stat-dummy: utility for rate counter and statsd testing
+
+It has 2 rate counters: one ticks twice a seconds, another one can be manually updated with 'update-rate-ctr' command via vty.
+
+The raw value is sent via statsd protocol. If you install "netdata" monitoring tool than you can open http://localhost:19999 in browser
+and observe live counters monitoring under "StatsD dummy" without any additional setup.
+
+Opening osmo-stat-dummy.html in browser while both netdata and osmo-stat-dummy are running will show dimensioned (per min/hour/day) rate counters as well as raw data.
+
+The latter is handy for troubleshooting and comparing libosmocore's internal rate counter computation.
diff --git a/utils/osmo-stat-dummy/osmo-stat-dummy.c b/utils/osmo-stat-dummy/osmo-stat-dummy.c
new file mode 100644
index 00000000..37da7b32
--- /dev/null
+++ b/utils/osmo-stat-dummy/osmo-stat-dummy.c
@@ -0,0 +1,335 @@
+/* Rate counter and statsd test application */
+/* (C) 2022 by by sysmocom - s.f.m.c. GmbH
+ * 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <getopt.h>
+#include <signal.h>
+#include <unistd.h>
+#include <inttypes.h>
+
+#include <osmocom/core/select.h>
+#include <osmocom/core/application.h>
+#include <osmocom/core/stats.h>
+#include <osmocom/ctrl/control_if.h>
+#include <osmocom/ctrl/control_vty.h>
+#include <osmocom/vty/vty.h>
+#include <osmocom/vty/telnet_interface.h>
+#include <osmocom/vty/command.h>
+#include <osmocom/vty/ports.h>
+#include <osmocom/vty/tdef_vty.h>
+#include <osmocom/vty/logging.h>
+#include <osmocom/vty/stats.h>
+#include <osmocom/vty/misc.h>
+
+#include "config.h"
+
+void *tall_statdummy_ctx = NULL;
+static bool quit = false;
+static bool config_given = false;
+struct rate_ctr_group *g_ctrg;
+
+enum dummy_rate_ctr_idx {
+ DUMMY_VTY = 0,
+ DUMMY_AUTO,
+};
+
+static void print_help(void)
+{
+ printf("Some useful options:\n"
+ " -h --help This text\n"
+ " -c --config-file Specify the filename of the config file\n"
+ " -V --version Print version\n"
+ "\nVTY reference generation:\n"
+ " --vty-ref-mode MODE VTY reference generation mode (e.g. 'expert').\n"
+ " --vty-ref-xml Generate the VTY reference XML output and exit.\n"
+ );
+}
+
+static void handle_long_options(const char *prog_name, const int long_option)
+{
+ static int vty_ref_mode = VTY_REF_GEN_MODE_DEFAULT;
+
+ switch (long_option) {
+ case 1:
+ vty_ref_mode = get_string_value(vty_ref_gen_mode_names, optarg);
+ if (vty_ref_mode < 0) {
+ fprintf(stderr, "%s: Unknown VTY reference generation mode '%s'\n", prog_name, optarg);
+ exit(2);
+ }
+ break;
+ case 2:
+ fprintf(stderr, "Generating the VTY reference in mode '%s' (%s)\n",
+ get_value_string(vty_ref_gen_mode_names, vty_ref_mode),
+ get_value_string(vty_ref_gen_mode_desc, vty_ref_mode));
+ vty_dump_xml_ref_mode(stdout, (enum vty_ref_gen_mode) vty_ref_mode);
+ exit(0);
+ default:
+ fprintf(stderr, "%s: error parsing cmdline options\n", prog_name);
+ exit(2);
+ }
+}
+
+static char *handle_options(int argc, char **argv)
+{
+ char *config_file = NULL;
+
+ while (1) {
+ int option_idx = 0, c;
+ static int long_option = 0;
+ static const struct option long_options[] = {
+ { "help", 0, 0, 'h' },
+ { "config-file", 1, 0, 'c' },
+ { "version", 0, 0, 'V' },
+ { "vty-ref-mode", 1, &long_option, 1 },
+ { "vty-ref-xml", 0, &long_option, 2 },
+ { 0, 0, 0, 0 }
+ };
+
+ c = getopt_long(argc, argv, "hc:V", long_options, &option_idx);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'h':
+ print_help();
+ exit(0);
+ break;
+ case 0:
+ handle_long_options(argv[0], long_option);
+ break;
+ case 'c':
+ if (config_file)
+ free(config_file);
+ config_file = optarg;
+ config_given = true;
+ break;
+ case 'V':
+ print_version(1);
+ exit(0);
+ break;
+ default:
+ fprintf(stderr, "Unknown option '%c'\n", c);
+ exit(0);
+ break;
+ }
+ }
+
+ if (!config_file)
+ return "osmo-stat-dummy.cfg";
+
+ return config_file;
+}
+
+void sighandler(int sigset)
+{
+ if (sigset == SIGPIPE)
+ return;
+
+ fprintf(stderr, "Signal %d received.\n", sigset);
+
+ switch (sigset) {
+ case SIGINT:
+ case SIGTERM:
+ /* If another signal is received afterwards, the program
+ * is terminated without finishing shutdown process.
+ */
+ signal(SIGINT, SIG_DFL);
+ signal(SIGTERM, SIG_DFL);
+ signal(SIGPIPE, SIG_DFL);
+ signal(SIGABRT, SIG_DFL);
+ signal(SIGUSR1, SIG_DFL);
+ signal(SIGUSR2, SIG_DFL);
+
+ quit = 1;
+ break;
+ case SIGABRT:
+ /* in case of abort, we want to obtain a talloc report and
+ * then run default SIGABRT handler, who will generate coredump
+ * and abort the process. abort() should do this for us after we
+ * return, but program wouldn't exit if an external SIGABRT is
+ * received.
+ */
+ talloc_report_full(tall_statdummy_ctx, stderr);
+ signal(SIGABRT, SIG_DFL);
+ raise(SIGABRT);
+ break;
+ case SIGUSR1:
+ case SIGUSR2:
+ talloc_report_full(tall_statdummy_ctx, stderr);
+ break;
+ }
+}
+
+static int rate_ctr_timer_cb(struct osmo_fd *ofd, unsigned int what)
+{
+ uint64_t expire_count;
+ int rc;
+
+ /* check that the timer has actually expired */
+ if (!(what & OSMO_FD_READ))
+ return 0;
+
+ /* read from timerfd: number of expirations of periodic timer */
+ rc = read(ofd->fd, (void *) &expire_count, sizeof(expire_count));
+ if (rc < 0 && errno == EAGAIN)
+ return 0;
+
+ OSMO_ASSERT(rc == sizeof(expire_count));
+
+ if (expire_count > 1)
+ LOGP(DLGLOBAL, LOGL_NOTICE, "Stats timer expire_count=%" PRIu64 ": We missed %" PRIu64 " timers\n",
+ expire_count, expire_count-1);
+
+ /* Increment the counter value */
+ rate_ctr_inc(rate_ctr_group_get_ctr(g_ctrg, DUMMY_AUTO));
+
+ return 0;
+}
+
+DEFUN(update_rate_ctr, update_rate_ctr_cmd,
+ "update-rate-ctr <0-100000>",
+ "Update dummy rate counter\n"
+ "Value to add to rate counter\n")
+{
+ rate_ctr_add(rate_ctr_group_get_ctr(g_ctrg, DUMMY_VTY), atoi(argv[0]));
+
+ return CMD_SUCCESS;
+}
+
+static int statdummy_vty_init(void)
+{
+ install_element_ve(&update_rate_ctr_cmd);
+
+ return 0;
+}
+
+int main(int argc, char *argv[])
+{
+ struct log_info log_info = {};
+ char *config_file;
+ void *ctx = tall_statdummy_ctx = talloc_named_const(NULL, 0, "osmo-stat-dummy");
+ struct ctrl_handle *ctrl;
+ struct osmo_fd rate_ctr_timer = { .fd = -1 };
+ struct timespec ts_interval = { .tv_sec = 0, .tv_nsec = 500000000 }; /* 0.5 seconds */
+ int rc = 0;
+
+ const char vty_copyright[] =
+ "Copyright (C) 2022 by by sysmocom - s.f.m.c. GmbH\r\n"
+ "Author: Max Suraev <msuraev@sysmocom.de>\r\n"
+ "License GNU GPL version 3 or later\r\n"
+ "This is free software: you are free to change and redistribute it.\r\n"
+ "There is NO WARRANTY, to the extent permitted by law.\r\n";
+
+ struct vty_app_info vty_info = {
+ .name = "OsmoSTATdummy",
+ .version = PACKAGE_VERSION,
+ .copyright = vty_copyright,
+ .tall_ctx = ctx
+ };
+
+ const struct rate_ctr_desc dummy_ctr_desc[] = {
+ [DUMMY_VTY] = { "dummy:vty", "Dummy counter updated via VTY" },
+ [DUMMY_AUTO] = { "dummy:auto", "Dummy counter autoupdated via timer" },
+ };
+
+ const struct rate_ctr_group_desc dummy_ctrg_desc = {
+ "dummy",
+ "dummy stat tester",
+ OSMO_STATS_CLASS_GLOBAL,
+ ARRAY_SIZE(dummy_ctr_desc),
+ dummy_ctr_desc,
+ };
+
+ osmo_init_logging2(ctx, &log_info);
+ log_set_use_color(osmo_stderr_target, 0);
+ log_set_print_filename2(osmo_stderr_target, LOG_FILENAME_NONE);
+ log_set_log_level(osmo_stderr_target, LOGL_INFO);
+
+ msgb_talloc_ctx_init(ctx, 0);
+
+ vty_init(&vty_info);
+ ctrl_vty_init(ctx);
+ logging_vty_add_cmds();
+ osmo_stats_vty_add_cmds();
+ osmo_talloc_vty_add_cmds();
+
+ config_file = handle_options(argc, argv);
+
+ statdummy_vty_init();
+ rc = vty_read_config_file(config_file, NULL);
+ if (rc < 0) {
+ if (config_given) {
+ fprintf(stderr, "Failed to parse the config file: '%s'\n", config_file);
+ exit(1);
+ }
+ fprintf(stderr, "No config file: '%s' Using default config.\n", config_file);
+ }
+
+ rc = telnet_init_default(ctx, NULL, -1);
+ if (rc < 0) {
+ fprintf(stderr, "Error initializing telnet\n");
+ exit(1);
+ }
+
+ ctrl = ctrl_interface_setup(NULL, 1234, NULL);
+ if (!ctrl) {
+ fprintf(stderr, "Failed to initialize control interface. Exiting.\n");
+ exit(1);
+ }
+
+ g_ctrg = rate_ctr_group_alloc(ctx, &dummy_ctrg_desc, 0);
+ if (!g_ctrg) {
+ fprintf(stderr, "Failed to initialize rate counters. Exiting.\n");
+ return -1;
+ }
+
+ osmo_stats_init(ctx);
+ rate_ctr_init(ctx);
+
+ 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);
+ }
+
+ signal(SIGINT, sighandler);
+ signal(SIGTERM, sighandler);
+ signal(SIGPIPE, sighandler);
+ signal(SIGABRT, sighandler);
+ signal(SIGUSR1, sighandler);
+ signal(SIGUSR2, sighandler);
+ osmo_init_ignore_signals();
+
+ while (!quit) {
+ osmo_select_main(0);
+ }
+
+ telnet_exit();
+
+ talloc_report_full(tall_statdummy_ctx, stderr);
+ talloc_free(tall_statdummy_ctx);
+
+ return 0;
+}
diff --git a/utils/osmo-stat-dummy/osmo-stat-dummy.cfg b/utils/osmo-stat-dummy/osmo-stat-dummy.cfg
new file mode 100644
index 00000000..559fe4ed
--- /dev/null
+++ b/utils/osmo-stat-dummy/osmo-stat-dummy.cfg
@@ -0,0 +1,40 @@
+log stderr
+ logging filter all 1
+ logging color 0
+ logging timestamp 0
+ logging print extended-timestamp 0
+ logging print category-hex 0
+ logging print category 1
+ !logging print file basename
+ ! log-levels defined by libosmocore and hence available everywhere, can be overridden by inidividual per-app configs below
+ logging level lstats notice
+ logging level lglobal notice
+ logging level llapd notice
+ logging level linp notice
+ logging level lmux notice
+ logging level lmi notice
+ logging level lmib notice
+ logging level lsms notice
+ logging level lctrl notice
+ logging level lgtp notice
+ logging level lgsup notice
+ logging level loap notice
+ logging level lss7 error
+ logging level lsccp notice
+ logging level lsua notice
+ logging level lm3ua notice
+ logging level lmgcp notice
+ logging level ljibuf notice
+ logging level lrspro notice
+stats reporter statsd
+ !use default https://learn.netdata.cloud/ statsd plugin:
+ remote-ip 127.0.0.1
+ remote-port 8125
+ level global
+ no prefix
+ enable
+stats interval 1
+line vty
+ bind 127.0.0.2 6969
+ctrl
+ bind 127.0.0.11 1234
diff --git a/utils/osmo-stat-dummy/osmo-stat-dummy.html b/utils/osmo-stat-dummy/osmo-stat-dummy.html
new file mode 100644
index 00000000..fc772573
--- /dev/null
+++ b/utils/osmo-stat-dummy/osmo-stat-dummy.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <meta charset="utf-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ <meta name="apple-mobile-web-app-capable" content="yes">
+ <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
+</head>
+<body>
+ <div data-netdata="statsd_dummy.0.dummy.auto_counter"
+ data-chart-library="dygraph"
+ data-title="raw data (timerfd ticks twice per second)"
+ data-width="600"
+ data-height="200"
+ data-after="-600"
+ ></div>
+ <div data-netdata="statsd_dummy.0.dummy.auto_counter"
+ data-chart-library="dygraph"
+ data-title="aggregated per minute (timerfd ticks twice per second)"
+ data-width="600"
+ data-height="200"
+ data-after="-600"
+ data-method="average"
+ data-gtime="60"
+ data-units="events/min"
+ ></div>
+ <div data-netdata="statsd_dummy.0.dummy.auto_counter"
+ data-chart-library="dygraph"
+ data-title="aggregated per hour (timerfd ticks twice per second)"
+ data-width="600"
+ data-height="200"
+ data-after="-600"
+ data-method="average"
+ data-gtime="3600"
+ data-units="events/hour"
+ ></div>
+ <div data-netdata="statsd_dummy.0.dummy.auto_counter"
+ data-chart-library="dygraph"
+ data-title="aggregated per day (timerfd ticks twice per second)"
+ data-width="600"
+ data-height="200"
+ data-after="-600"
+ data-method="average"
+ data-gtime="86400"
+ data-units="events/day"
+ ></div>
+
+ <div data-netdata="statsd_dummy.0.dummy.vty_counter"
+ data-chart-library="dygraph"
+ data-title="raw data (updated manually via vty command)"
+ data-width="600"
+ data-height="200"
+ data-after="-600"
+ ></div>
+</body>
+<script type="text/javascript" src="http://localhost:19999/dashboard.js"></script>
+</html>