aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore12
-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.am128
-rw-r--r--README.md6
-rw-r--r--TODO-RELEASE4
-rw-r--r--configure.ac203
-rwxr-xr-xcontrib/jenkins_amd64.sh10
-rwxr-xr-xcontrib/jenkins_arm.sh2
-rw-r--r--contrib/jenkins_common.sh31
-rwxr-xr-xcontrib/ladder_to_msc.py5
-rw-r--r--contrib/ladder_to_msc_test.ladder5
-rw-r--r--contrib/libosmocore.spec.in110
-rwxr-xr-xcontrib/struct_endianness.py (renamed from contrib/struct_endianess.py)4
-rwxr-xr-xcontrib/talloc_count.sh52
-rw-r--r--debian/changelog1189
-rw-r--r--debian/control110
-rw-r--r--debian/copyright18
-rw-r--r--debian/libosmocore-utils.install1
-rw-r--r--debian/libosmocore.dirs1
-rw-r--r--debian/libosmocore20.install (renamed from debian/libosmocore16.install)0
-rw-r--r--debian/libosmogb14.install (renamed from debian/libosmogb11.install)0
-rw-r--r--debian/libosmogsm18.install (renamed from debian/libosmogsm15.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/libosmovty9.install (renamed from debian/libosmovty4.install)0
-rwxr-xr-xdebian/rules11
-rw-r--r--include/Makefile.am197
-rw-r--r--include/osmocom/Makefile.am13
-rw-r--r--include/osmocom/codec/Makefile.am7
-rw-r--r--include/osmocom/codec/codec.h3
-rw-r--r--include/osmocom/coding/Makefile.am10
-rw-r--r--include/osmocom/coding/gsm0503_amr_dtx.h11
-rw-r--r--include/osmocom/core/Makefile.am94
-rw-r--r--include/osmocom/core/application.h9
-rw-r--r--include/osmocom/core/base64.h69
-rw-r--r--include/osmocom/core/bitXXgen.h.tpl4
-rw-r--r--include/osmocom/core/bitcomp.h4
-rw-r--r--include/osmocom/core/bitvec.h6
-rw-r--r--include/osmocom/core/conv.h5
-rw-r--r--include/osmocom/core/counter.h4
-rw-r--r--include/osmocom/core/crcXXgen.h.tpl4
-rw-r--r--include/osmocom/core/crcgen.h4
-rw-r--r--include/osmocom/core/endian.h2
-rw-r--r--include/osmocom/core/exec.h4
-rw-r--r--include/osmocom/core/fsm.h17
-rw-r--r--include/osmocom/core/gsmtap.h5
-rw-r--r--include/osmocom/core/gsmtap_util.h14
-rw-r--r--include/osmocom/core/hash.h101
-rw-r--r--include/osmocom/core/hashtable.h141
-rw-r--r--include/osmocom/core/isdnhdlc.h9
-rw-r--r--include/osmocom/core/it_q.h62
-rw-r--r--include/osmocom/core/linuxlist.h256
-rw-r--r--include/osmocom/core/linuxrbtree.h5
-rw-r--r--include/osmocom/core/log2.h184
-rw-r--r--include/osmocom/core/logging.h77
-rw-r--r--include/osmocom/core/loggingrb.h4
-rw-r--r--include/osmocom/core/mnl.h22
-rw-r--r--include/osmocom/core/msgb.h36
-rw-r--r--include/osmocom/core/msgfile.h4
-rw-r--r--include/osmocom/core/netdev.h49
-rw-r--r--include/osmocom/core/netns.h24
-rw-r--r--include/osmocom/core/prim.h6
-rw-r--r--include/osmocom/core/rate_ctr.h9
-rw-r--r--include/osmocom/core/select.h23
-rw-r--r--include/osmocom/core/sercomm.h5
-rw-r--r--include/osmocom/core/serial.h5
-rw-r--r--include/osmocom/core/sockaddr_str.h4
-rw-r--r--include/osmocom/core/socket.h75
-rw-r--r--include/osmocom/core/stat_item.h58
-rw-r--r--include/osmocom/core/stats.h7
-rw-r--r--include/osmocom/core/stats_tcp.h16
-rw-r--r--include/osmocom/core/strrb.h4
-rw-r--r--include/osmocom/core/talloc.h2
-rw-r--r--include/osmocom/core/tdef.h3
-rw-r--r--include/osmocom/core/thread.h29
-rw-r--r--include/osmocom/core/time_cc.h187
-rw-r--r--include/osmocom/core/timer.h7
-rw-r--r--include/osmocom/core/timer_compat.h4
-rw-r--r--include/osmocom/core/tun.h43
-rw-r--r--include/osmocom/core/use_count.h4
-rw-r--r--include/osmocom/core/utils.h48
-rw-r--r--include/osmocom/core/write_queue.h5
-rw-r--r--include/osmocom/crypt/Makefile.am8
-rw-r--r--include/osmocom/crypt/auth.h6
-rw-r--r--include/osmocom/crypt/kdf.h21
-rw-r--r--include/osmocom/crypt/utran_cipher.h19
-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.h14
-rw-r--r--include/osmocom/ctrl/control_vty.h5
-rw-r--r--include/osmocom/ctrl/ports.h7
-rw-r--r--include/osmocom/gprs/Makefile.am17
-rw-r--r--include/osmocom/gprs/bssgp_bvc_fsm.h71
-rw-r--r--include/osmocom/gprs/frame_relay.h151
-rw-r--r--include/osmocom/gprs/gprs_bssgp.h31
-rw-r--r--include/osmocom/gprs/gprs_bssgp2.h72
-rw-r--r--include/osmocom/gprs/gprs_bssgp_rim.h272
-rw-r--r--include/osmocom/gprs/gprs_ns2.h207
-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.h200
-rw-r--r--include/osmocom/gprs/protocol/gsm_08_16.h1
-rw-r--r--include/osmocom/gprs/protocol/gsm_08_18.h199
-rw-r--r--include/osmocom/gprs/protocol/gsm_24_301.h11
-rw-r--r--include/osmocom/gsm/Makefile.am68
-rw-r--r--include/osmocom/gsm/a5.h4
-rw-r--r--include/osmocom/gsm/bsslap.h53
-rw-r--r--include/osmocom/gsm/bssmap_le.h77
-rw-r--r--include/osmocom/gsm/bts_features.h23
-rw-r--r--include/osmocom/gsm/cbsp.h32
-rw-r--r--include/osmocom/gsm/gad.h190
-rw-r--r--include/osmocom/gsm/gsm0502.h38
-rw-r--r--include/osmocom/gsm/gsm0808.h49
-rw-r--r--include/osmocom/gsm/gsm0808_lcs.h48
-rw-r--r--include/osmocom/gsm/gsm0808_utils.h16
-rw-r--r--include/osmocom/gsm/gsm23003.h22
-rw-r--r--include/osmocom/gsm/gsm29118.h4
-rw-r--r--include/osmocom/gsm/gsm29205.h8
-rw-r--r--include/osmocom/gsm/gsm44021.h8
-rw-r--r--include/osmocom/gsm/gsm48.h7
-rw-r--r--include/osmocom/gsm/gsm48_ie.h9
-rw-r--r--include/osmocom/gsm/gsm48_rest_octets.h8
-rw-r--r--include/osmocom/gsm/gsm_utils.h23
-rw-r--r--include/osmocom/gsm/i460_mux.h121
-rw-r--r--include/osmocom/gsm/ipa.h2
-rw-r--r--include/osmocom/gsm/iuup.h129
-rw-r--r--include/osmocom/gsm/lapd_core.h177
-rw-r--r--include/osmocom/gsm/lapdm.h2
-rw-r--r--include/osmocom/gsm/meas_rep.h4
-rw-r--r--include/osmocom/gsm/prim.h5
-rw-r--r--include/osmocom/gsm/protocol/Makefile.am29
-rw-r--r--include/osmocom/gsm/protocol/gsm_03_41.h8
-rw-r--r--include/osmocom/gsm/protocol/gsm_04_08.h280
-rw-r--r--include/osmocom/gsm/protocol/gsm_04_08_gprs.h16
-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.h163
-rw-r--r--include/osmocom/gsm/protocol/gsm_08_58.h286
-rw-r--r--include/osmocom/gsm/protocol/gsm_09_02.h5
-rw-r--r--include/osmocom/gsm/protocol/gsm_12_21.h24
-rw-r--r--include/osmocom/gsm/protocol/gsm_23_032.h252
-rw-r--r--include/osmocom/gsm/protocol/gsm_23_041.h12
-rw-r--r--include/osmocom/gsm/protocol/gsm_25_415.h222
-rw-r--r--include/osmocom/gsm/protocol/gsm_44_004.h19
-rw-r--r--include/osmocom/gsm/protocol/gsm_44_060.h252
-rw-r--r--include/osmocom/gsm/protocol/gsm_44_318.h4
-rw-r--r--include/osmocom/gsm/protocol/gsm_48_071.h118
-rw-r--r--include/osmocom/gsm/protocol/gsm_49_031.h234
-rw-r--r--include/osmocom/gsm/protocol/ipaccess.h1
-rw-r--r--include/osmocom/gsm/tlv.h97
-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/class_tables.h4
-rw-r--r--include/osmocom/sim/sim.h21
-rw-r--r--include/osmocom/usb/Makefile.am7
-rw-r--r--include/osmocom/usb/libusb.h4
-rw-r--r--include/osmocom/vty/Makefile.am17
-rw-r--r--include/osmocom/vty/command.h60
-rw-r--r--include/osmocom/vty/logging.h6
-rw-r--r--include/osmocom/vty/misc.h14
-rw-r--r--include/osmocom/vty/ports.h54
-rw-r--r--include/osmocom/vty/telnet_interface.h12
-rw-r--r--include/osmocom/vty/vector.h5
-rw-r--r--include/osmocom/vty/vty.h19
-rw-r--r--libosmocodec.pc.in6
-rw-r--r--libosmocoding.pc.in6
-rw-r--r--libosmocore.pc.in6
-rw-r--r--libosmoctrl.pc.in4
-rw-r--r--libosmogb.pc.in4
-rw-r--r--libosmogsm.pc.in4
-rw-r--r--libosmoisdn.pc.in11
-rw-r--r--libosmosim.pc.in4
-rw-r--r--libosmousb.pc.in4
-rw-r--r--libosmovty.pc.in4
-rwxr-xr-xosmo-release.sh225
-rw-r--r--src/Makefile.am88
-rw-r--r--src/codec/Makefile.am8
-rw-r--r--src/codec/ecu.c6
-rw-r--r--src/codec/ecu_fr.c4
-rw-r--r--src/codec/gsm610.c4
-rw-r--r--src/codec/gsm620.c26
-rw-r--r--src/codec/gsm660.c4
-rw-r--r--src/codec/gsm690.c4
-rw-r--r--src/coding/Makefile.am16
-rw-r--r--src/coding/gsm0503_amr_dtx.c191
-rw-r--r--src/coding/gsm0503_coding.c207
-rw-r--r--src/coding/gsm0503_interleaving.c56
-rw-r--r--src/coding/gsm0503_mapping.c4
-rw-r--r--src/coding/gsm0503_parity.c4
-rw-r--r--src/coding/gsm0503_tables.c4
-rw-r--r--src/coding/libosmocoding.map4
-rw-r--r--src/core/Makefile.am157
-rw-r--r--src/core/application.c (renamed from src/application.c)4
-rw-r--r--src/core/backtrace.c (renamed from src/backtrace.c)4
-rw-r--r--src/core/base64.c195
-rw-r--r--src/core/bitcomp.c (renamed from src/bitcomp.c)4
-rw-r--r--src/core/bits.c (renamed from src/bits.c)14
-rw-r--r--src/core/bitvec.c (renamed from src/bitvec.c)33
-rw-r--r--src/core/context.c (renamed from src/context.c)7
-rw-r--r--src/core/conv.c (renamed from src/conv.c)199
-rw-r--r--src/core/conv_acc.c (renamed from src/conv_acc.c)27
-rw-r--r--src/core/conv_acc_generic.c (renamed from src/conv_acc_generic.c)4
-rw-r--r--src/core/conv_acc_neon.c (renamed from src/conv_acc_neon.c)4
-rw-r--r--src/core/conv_acc_neon_impl.h (renamed from src/conv_acc_neon_impl.h)4
-rw-r--r--src/core/conv_acc_sse.c (renamed from src/conv_acc_sse.c)4
-rw-r--r--src/core/conv_acc_sse_avx.c (renamed from src/conv_acc_sse_avx.c)4
-rw-r--r--src/core/conv_acc_sse_impl.h (renamed from src/conv_acc_sse_impl.h)4
-rw-r--r--src/core/counter.c (renamed from src/counter.c)6
-rw-r--r--src/core/crc16.c (renamed from src/crc16.c)0
-rw-r--r--src/core/crcXXgen.c.tpl (renamed from src/crcXXgen.c.tpl)4
-rw-r--r--src/core/exec.c (renamed from src/exec.c)8
-rw-r--r--src/core/fsm.c (renamed from src/fsm.c)42
-rw-r--r--src/core/gsmtap_util.c (renamed from src/gsmtap_util.c)105
-rw-r--r--src/core/isdnhdlc.c (renamed from src/isdnhdlc.c)11
-rw-r--r--src/core/it_q.c272
-rw-r--r--src/core/libosmocore.map569
-rw-r--r--src/core/logging.c (renamed from src/logging.c)451
-rw-r--r--src/core/logging_gsmtap.c (renamed from src/logging_gsmtap.c)13
-rw-r--r--src/core/logging_syslog.c (renamed from src/logging_syslog.c)6
-rw-r--r--src/core/logging_systemd.c117
-rw-r--r--src/core/loggingrb.c (renamed from src/loggingrb.c)4
-rw-r--r--src/core/macaddr.c (renamed from src/macaddr.c)4
-rw-r--r--src/core/mnl.c111
-rw-r--r--src/core/msgb.c (renamed from src/msgb.c)64
-rw-r--r--src/core/msgfile.c (renamed from src/msgfile.c)4
-rw-r--r--src/core/netdev.c962
-rw-r--r--src/core/netns.c208
-rw-r--r--src/core/panic.c (renamed from src/panic.c)6
-rw-r--r--src/core/plugin.c (renamed from src/plugin.c)6
-rw-r--r--src/core/prbs.c (renamed from src/prbs.c)0
-rw-r--r--src/core/prim.c (renamed from src/prim.c)0
-rw-r--r--src/core/probes.d6
-rw-r--r--src/core/rate_ctr.c (renamed from src/rate_ctr.c)89
-rw-r--r--src/core/rbtree.c (renamed from src/rbtree.c)5
-rw-r--r--src/core/select.c (renamed from src/select.c)235
-rw-r--r--src/core/sercomm.c (renamed from src/sercomm.c)4
-rw-r--r--src/core/serial.c (renamed from src/serial.c)56
-rw-r--r--src/core/signal.c (renamed from src/signal.c)4
-rw-r--r--src/core/sockaddr_str.c (renamed from src/sockaddr_str.c)4
-rw-r--r--src/core/socket.c (renamed from src/socket.c)487
-rw-r--r--src/core/stat_item.c463
-rw-r--r--src/core/stat_item_internal.h35
-rw-r--r--src/core/stats.c (renamed from src/stats.c)92
-rw-r--r--src/core/stats_statsd.c (renamed from src/stats_statsd.c)65
-rw-r--r--src/core/stats_tcp.c325
-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)24
-rw-r--r--src/core/thread.c56
-rw-r--r--src/core/time_cc.c228
-rw-r--r--src/core/timer.c (renamed from src/timer.c)22
-rw-r--r--src/core/timer_clockgettime.c (renamed from src/timer_clockgettime.c)4
-rw-r--r--src/core/timer_gettimeofday.c (renamed from src/timer_gettimeofday.c)4
-rw-r--r--src/core/tun.c572
-rw-r--r--src/core/use_count.c (renamed from src/use_count.c)11
-rw-r--r--src/core/utils.c (renamed from src/utils.c)368
-rw-r--r--src/core/write_queue.c (renamed from src/write_queue.c)45
-rw-r--r--src/ctrl/Makefile.am8
-rw-r--r--src/ctrl/control_cmd.c103
-rw-r--r--src/ctrl/control_if.c151
-rw-r--r--src/ctrl/control_vty.c21
-rw-r--r--src/ctrl/libosmoctrl.map2
-rw-r--r--src/gb/Makefile.am30
-rw-r--r--src/gb/bssgp_bvc_fsm.c857
-rw-r--r--src/gb/common_vty.c10
-rw-r--r--src/gb/common_vty.h2
-rw-r--r--src/gb/frame_relay.c1051
-rw-r--r--src/gb/gprs_bssgp.c284
-rw-r--r--src/gb/gprs_bssgp2.c486
-rw-r--r--src/gb/gprs_bssgp_bss.c77
-rw-r--r--src/gb/gprs_bssgp_internal.h2
-rw-r--r--src/gb/gprs_bssgp_rim.c1221
-rw-r--r--src/gb/gprs_bssgp_util.c345
-rw-r--r--src/gb/gprs_bssgp_vty.c16
-rw-r--r--src/gb/gprs_ns.c67
-rw-r--r--src/gb/gprs_ns2.c1192
-rw-r--r--src/gb/gprs_ns2_fr.c978
-rw-r--r--src/gb/gprs_ns2_frgre.c161
-rw-r--r--src/gb/gprs_ns2_internal.h308
-rw-r--r--src/gb/gprs_ns2_message.c409
-rw-r--r--src/gb/gprs_ns2_sns.c2709
-rw-r--r--src/gb/gprs_ns2_udp.c380
-rw-r--r--src/gb/gprs_ns2_vc_fsm.c707
-rw-r--r--src/gb/gprs_ns2_vty.c2491
-rw-r--r--src/gb/gprs_ns_vty.c46
-rw-r--r--src/gb/libosmogb.map107
-rw-r--r--src/gsm/Makefile.am23
-rw-r--r--src/gsm/a5.c4
-rw-r--r--src/gsm/abis_nm.c6
-rw-r--r--src/gsm/apn.c2
-rw-r--r--src/gsm/auth_comp128v1.c4
-rw-r--r--src/gsm/auth_comp128v23.c4
-rw-r--r--src/gsm/auth_core.c7
-rw-r--r--src/gsm/auth_milenage.c4
-rw-r--r--src/gsm/auth_xor.c8
-rw-r--r--src/gsm/auth_xor_2g.c79
-rw-r--r--src/gsm/bsslap.c325
-rw-r--r--src/gsm/bssmap_le.c937
-rw-r--r--src/gsm/bts_features.c56
-rw-r--r--src/gsm/cbsp.c289
-rw-r--r--src/gsm/comp128.c4
-rw-r--r--src/gsm/comp128v23.c4
-rw-r--r--src/gsm/gad.c491
-rw-r--r--src/gsm/gea.c4
-rw-r--r--src/gsm/gprs_cipher_core.c4
-rw-r--r--src/gsm/gprs_gea.c4
-rw-r--r--src/gsm/gprs_rlc.c3
-rw-r--r--src/gsm/gsm0341.c4
-rw-r--r--src/gsm/gsm0411_smr.c2
-rw-r--r--src/gsm/gsm0411_utils.c6
-rw-r--r--src/gsm/gsm0480.c4
-rw-r--r--src/gsm/gsm0502.c7
-rw-r--r--src/gsm/gsm0808.c423
-rw-r--r--src/gsm/gsm0808_utils.c474
-rw-r--r--src/gsm/gsm23003.c195
-rw-r--r--src/gsm/gsm23236.c26
-rw-r--r--src/gsm/gsm29118.c4
-rw-r--r--src/gsm/gsm29205.c4
-rw-r--r--src/gsm/gsm44021.c303
-rw-r--r--src/gsm/gsm48.c116
-rw-r--r--src/gsm/gsm48_arfcn_range_encode.c4
-rw-r--r--src/gsm/gsm48_ie.c282
-rw-r--r--src/gsm/gsm48_rest_octets.c285
-rw-r--r--src/gsm/gsm_04_08_gprs.c4
-rw-r--r--src/gsm/gsm_utils.c40
-rw-r--r--src/gsm/ipa.c15
-rw-r--r--src/gsm/iuup.c1064
-rw-r--r--src/gsm/kasumi.c4
-rw-r--r--src/gsm/kdf.c163
-rw-r--r--src/gsm/kdf/common.h101
-rw-r--r--src/gsm/kdf/crypto.h470
-rw-r--r--src/gsm/kdf/sha1-internal.c307
-rw-r--r--src/gsm/kdf/sha1.c162
-rw-r--r--src/gsm/kdf/sha1.h33
-rw-r--r--src/gsm/kdf/sha1_i.h29
-rw-r--r--src/gsm/kdf/sha256-internal.c231
-rw-r--r--src/gsm/kdf/sha256.c156
-rw-r--r--src/gsm/kdf/sha256.h30
-rw-r--r--src/gsm/kdf/sha256_i.h31
-rw-r--r--src/gsm/lapdm.c10
-rw-r--r--src/gsm/libosmogsm.map92
-rw-r--r--src/gsm/mncc.c2
-rw-r--r--src/gsm/rsl.c104
-rw-r--r--src/gsm/rxlev_stat.c4
-rw-r--r--src/gsm/sysinfo.c2
-rw-r--r--src/gsm/tlv_parser.c156
-rw-r--r--src/isdn/Makefile.am26
-rw-r--r--src/isdn/i460_mux.c (renamed from src/gsm/i460_mux.c)32
-rw-r--r--src/isdn/lapd_core.c (renamed from src/gsm/lapd_core.c)855
-rw-r--r--src/isdn/libosmoisdn.map34
-rw-r--r--src/isdn/v110.c582
-rw-r--r--src/pseudotalloc/Makefile.am3
-rw-r--r--src/sim/Makefile.am11
-rw-r--r--src/sim/card_fs_hpsim.c4
-rw-r--r--src/sim/card_fs_isim.c4
-rw-r--r--src/sim/card_fs_sim.c4
-rw-r--r--src/sim/card_fs_tetra.c4
-rw-r--r--src/sim/card_fs_uicc.c4
-rw-r--r--src/sim/card_fs_usim.c72
-rw-r--r--src/sim/class_tables.c73
-rw-r--r--src/sim/core.c4
-rw-r--r--src/sim/reader.c28
-rw-r--r--src/sim/reader_pcsc.c63
-rw-r--r--src/stat_item.c388
-rw-r--r--src/usb/Makefile.am11
-rw-r--r--src/usb/osmo_libusb.c26
-rw-r--r--src/vty/Makefile.am8
-rw-r--r--src/vty/command.c700
-rw-r--r--src/vty/cpu_sched_vty.c35
-rw-r--r--src/vty/fsm_vty.c60
-rw-r--r--src/vty/logging_vty.c283
-rw-r--r--src/vty/stats_vty.c263
-rw-r--r--src/vty/talloc_ctx_vty.c17
-rw-r--r--src/vty/tdef_vty.c13
-rw-r--r--src/vty/telnet_interface.c61
-rw-r--r--src/vty/utils.c113
-rw-r--r--src/vty/vector.c5
-rw-r--r--src/vty/vty.c138
-rw-r--r--tapset/Makefile.am22
-rw-r--r--tapset/libosmocore.stp29
-rw-r--r--tests/Makefile.am364
-rw-r--r--tests/abis/abis_test.c53
-rw-r--r--tests/abis/abis_test.ok1
-rw-r--r--tests/auth/xor2g_test.c77
-rw-r--r--tests/auth/xor2g_test.ok6
-rw-r--r--tests/base64/base64_test.c55
-rw-r--r--tests/base64/base64_test.ok3
-rw-r--r--tests/bitgen/bitgen_test.c6
-rw-r--r--tests/bitvec/bitvec_test.c11
-rw-r--r--tests/bitvec/bitvec_test.ok32
-rw-r--r--tests/bsslap/bsslap_test.c103
-rw-r--r--tests/bsslap/bsslap_test.ok7
-rw-r--r--tests/bssmap_le/bssmap_le_test.c207
-rw-r--r--tests/bssmap_le/bssmap_le_test.ok12
-rw-r--r--tests/codec/codec_ecu_fr_test.c4
-rw-r--r--tests/coding/coding_test.c4
-rw-r--r--tests/conv/conv_gsm0503_test.ok32
-rw-r--r--tests/ctrl/ctrl_test.c13
-rw-r--r--tests/dtx/dtx_gsm0503_test.c168
-rw-r--r--tests/dtx/dtx_gsm0503_test.ok34
-rw-r--r--tests/fr/fr_test.c6
-rw-r--r--tests/fsm/fsm_dealloc_test.c8
-rw-r--r--tests/fsm/fsm_test.c82
-rw-r--r--tests/fsm/fsm_test.err28
-rw-r--r--tests/gad/gad_test.c143
-rw-r--r--tests/gad/gad_test.ok8
-rw-r--r--tests/gb/bssgp_fc_test.c4
-rw-r--r--tests/gb/gprs_bssgp_rim_test.c799
-rw-r--r--tests/gb/gprs_bssgp_rim_test.ok276
-rw-r--r--tests/gb/gprs_bssgp_test.c6
-rw-r--r--tests/gb/gprs_ns2_test.c670
-rw-r--r--tests/gb/gprs_ns2_test.ok51
-rw-r--r--tests/gb/gprs_ns2_vty.vty89
-rw-r--r--tests/gb/gprs_ns_test.c24
-rw-r--r--tests/gb/osmo-ns-dummy.cfg25
-rw-r--r--tests/gb/osmoappdesc.py27
-rw-r--r--tests/gsm0408/gsm0408_test.c283
-rw-r--r--tests/gsm0408/gsm0408_test.err3
-rw-r--r--tests/gsm0408/gsm0408_test.ok457
-rw-r--r--tests/gsm0502/gsm0502_test.c6
-rw-r--r--tests/gsm0808/gsm0808_test.c492
-rw-r--r--tests/gsm0808/gsm0808_test.ok805
-rw-r--r--tests/gsm23003/gsm23003_test.c18
-rw-r--r--tests/gsm23236/gsm23236_test.c16
-rw-r--r--tests/gsm29205/gsm29205_test.c6
-rw-r--r--tests/gsm44021/test_frame_csd.c93
-rw-r--r--tests/gsm44021/test_frame_csd.ok31
-rw-r--r--tests/gsm48/rest_octets_test.c116
-rw-r--r--tests/gsm48/rest_octets_test.ok2
-rw-r--r--tests/gsup/gsup_test.c3
-rw-r--r--tests/i460_mux/i460_mux_test.c3
-rw-r--r--tests/it_q/it_q_test.c120
-rw-r--r--tests/it_q/it_q_test.ok15
-rw-r--r--tests/iuup/iuup_test.c764
-rw-r--r--tests/iuup/iuup_test.ok61
-rw-r--r--tests/lapd/lapd_test.c14
-rw-r--r--tests/logging/logging_test.c20
-rw-r--r--tests/logging/logging_test.err2
-rw-r--r--tests/logging/logging_vty_test.c11
-rw-r--r--tests/logging/logging_vty_test.vty66
-rw-r--r--tests/loggingrb/loggingrb_test.c8
-rw-r--r--tests/msgb/msgb_test.c34
-rw-r--r--tests/msgfile/msgfile_test.c4
-rw-r--r--tests/oap/oap_client_test.c3
-rw-r--r--tests/oap/oap_test.c4
-rw-r--r--tests/osmo-auc-gen/osmo-auc-gen_test.ok30
-rw-r--r--tests/sercomm/sercomm_test.c4
-rw-r--r--tests/sim/sim_test.c4
-rw-r--r--tests/sms/sms_test.c59
-rw-r--r--tests/sms/sms_test.ok16
-rw-r--r--tests/smscb/cbsp_test.c108
-rw-r--r--tests/smscb/cbsp_test.ok2
-rw-r--r--tests/smscb/gsm0341_test.c14
-rw-r--r--tests/smscb/gsm0341_test.ok1
-rw-r--r--tests/smscb/smscb_test.c4
-rw-r--r--tests/sockaddr_str/sockaddr_str_test.c8
-rw-r--r--tests/socket/socket_sctp_test.c10
-rw-r--r--tests/socket/socket_test.c147
-rw-r--r--tests/socket/socket_test.ok10
-rw-r--r--tests/stats/stats_test.c416
-rw-r--r--tests/stats/stats_test.err163
-rw-r--r--tests/stats/stats_test.ok132
-rw-r--r--tests/stats/stats_vty_test.c88
-rw-r--r--tests/stats/stats_vty_test.vty217
-rw-r--r--tests/strrb/strrb_test.c4
-rw-r--r--tests/tdef/tdef_test.c25
-rw-r--r--tests/tdef/tdef_test.ok42
-rw-r--r--tests/tdef/tdef_vty_config_root_test.c (renamed from tests/tdef/tdef_vty_test_config_root.c)14
-rw-r--r--tests/tdef/tdef_vty_config_root_test.vty (renamed from tests/tdef/tdef_vty_test_config_root.vty)0
-rw-r--r--tests/tdef/tdef_vty_config_subnode_test.c (renamed from tests/tdef/tdef_vty_test_config_subnode.c)12
-rw-r--r--tests/tdef/tdef_vty_config_subnode_test.vty (renamed from tests/tdef/tdef_vty_test_config_subnode.vty)0
-rw-r--r--tests/tdef/tdef_vty_dynamic_test.c (renamed from tests/tdef/tdef_vty_test_dynamic.c)10
-rw-r--r--tests/tdef/tdef_vty_dynamic_test.vty (renamed from tests/tdef/tdef_vty_test_dynamic.vty)0
-rw-r--r--tests/testsuite.at120
-rw-r--r--tests/time_cc/time_cc_test.c768
-rw-r--r--tests/time_cc/time_cc_test.ok328
-rw-r--r--tests/timer/clk_override_test.c4
-rw-r--r--tests/timer/timer_test.c6
-rw-r--r--tests/tlv/tlv_test.c130
-rw-r--r--tests/tlv/tlv_test.ok7
-rw-r--r--tests/use_count/use_count_test.c8
-rw-r--r--tests/use_count/use_count_test.err10
-rw-r--r--tests/ussd/ussd_test.c4
-rw-r--r--tests/utils/utils_test.c712
-rw-r--r--tests/utils/utils_test.ok514
-rw-r--r--tests/v110/test_frame.c54
-rw-r--r--tests/v110/test_frame.ok20
-rw-r--r--tests/v110/test_ra1.c71
-rw-r--r--tests/v110/test_ra1.ok216
-rw-r--r--tests/vty/vty_test.c105
-rw-r--r--tests/vty/vty_test.err77
-rw-r--r--tests/vty/vty_test.ok48
-rw-r--r--tests/vty/vty_transcript_test.c158
-rw-r--r--tests/vty/vty_transcript_test.vty93
-rw-r--r--tests/write_queue/wqueue_test.c4
-rw-r--r--utils/Makefile.am34
-rw-r--r--utils/conv_codes_gsm.py87
-rw-r--r--utils/conv_gen.py4
-rw-r--r--utils/osmo-aka-verify.c249
-rw-r--r--utils/osmo-arfcn.c6
-rw-r--r--utils/osmo-auc-gen.c29
-rw-r--r--utils/osmo-config-merge.c4
-rw-r--r--utils/osmo-ns-dummy-vty.c325
-rw-r--r--utils/osmo-ns-dummy.c316
-rw-r--r--utils/osmo-sim-test.c4
-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
526 files changed, 53563 insertions, 7639 deletions
diff --git a/.gitignore b/.gitignore
index a9dd3039..167d1e20 100644
--- a/.gitignore
+++ b/.gitignore
@@ -29,6 +29,7 @@ install-sh
stamp-h1
libtool
libosmocore-*.tar.*
+*~
# libtool for different platforms like arm-poky-linux-gnueabi-libtool
*-libtool
@@ -36,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 +73,7 @@ utils/osmo-arfcn
utils/osmo-auc-gen
utils/osmo-config-merge
utils/osmo-sim-test
+utils/osmo-aka-verify
doc/codec
doc/coding
@@ -78,13 +83,16 @@ 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
@@ -101,3 +109,5 @@ libosmocore-*-coverage*
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 f2a05a96..8a8f9636 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1,12 +1,18 @@
ACLOCAL_AMFLAGS = -I m4
-AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include
-SUBDIRS = include src src/vty src/codec src/gsm src/coding src/gb src/ctrl src/sim src/pseudotalloc src/usb utils tests
+AM_CPPFLAGS = -I$(top_srcdir)/include
+SUBDIRS = \
+ include \
+ src \
+ utils \
+ tapset \
+ tests \
+ $(NULL)
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 \
@@ -41,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 \
@@ -75,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
@@ -94,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 \
@@ -135,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.
@@ -149,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/README.md b/README.md
index 07c1a2af..ed9bcd6b 100644
--- a/README.md
+++ b/README.md
@@ -41,16 +41,16 @@ GIT Repository
You can clone from the official libosmocore.git repository using
- git clone git://git.osmocom.org/libosmocore.git
+ git clone https://gitea.osmocom.org/osmocom/libosmocore
-There is a cgit interface at <http://git.osmocom.org/libosmocore/>
+There is a web interface at <https://gitea.osmocom.org/osmocom/libosmocore>
Documentation
-------------
Doxygen-generated API documentation is generated during the build
process, but also available online for each of the sub-libraries at
-<http://ftp.osmocom.org/api/latest/libosmocore/>
+<https://ftp.osmocom.org/api/latest/libosmocore/>
Mailing List
------------
diff --git a/TODO-RELEASE b/TODO-RELEASE
index 7699aff3..8ecd7a69 100644
--- a/TODO-RELEASE
+++ b/TODO-RELEASE
@@ -7,6 +7,4 @@
# 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
-libosmogsm new API gsm0808_create_sapi_reject_cause() with cause argument
-libosmovty ABI change struct cmd_element: add a field for program specific attributes
-libosmovty ABI change struct vty_app_info: optional program specific attributes description
+libosmogsm new header osmocom/gsm/protocol/gsm_44_060.h \ No newline at end of file
diff --git a/configure.ac b/configure.ac
index b07a3bd6..108c0a18 100644
--- a/configure.ac
+++ b/configure.ac
@@ -8,6 +8,8 @@ AC_CONFIG_AUX_DIR([.])
AM_INIT_AUTOMAKE([foreign dist-bzip2 no-dist-gzip 1.6 subdir-objects])
AC_CONFIG_TESTDIR(tests)
+CFLAGS="$CFLAGS -std=gnu11"
+
dnl kernel style compile messages
m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])
@@ -51,18 +53,22 @@ 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 sys/select.h sys/socket.h sys/signalfd.h sys/timerfd.h syslog.h ctype.h netinet/tcp.h netinet/in.h)
+AC_CHECK_HEADERS(execinfo.h poll.h sys/select.h sys/socket.h sys/signalfd.h sys/eventfd.h sys/timerfd.h syslog.h ctype.h netinet/tcp.h netinet/in.h)
# for src/conv.c
AC_FUNC_ALLOCA
AC_SEARCH_LIBS([dlopen], [dl dld], [LIBRARY_DLOPEN="$LIBS";LIBS=""])
@@ -76,6 +82,24 @@ AC_SUBST(BACKTRACE_LIB)
# check for pthread (PTHREAD_CFLAGS, PTHREAD_LIBS)
AX_PTHREAD
+AC_MSG_CHECKING(for pthread_getname_np(pthread_t, char*, size_t))
+saved_CFLAGS="$CFLAGS"
+saved_LIBS="$LIBS"
+CFLAGS="$CFLAGS $PTHREAD_CFLAGS"
+LIBS="$LIBS $PTHREAD_LIBS"
+AC_LINK_IFELSE(
+ [AC_LANG_PROGRAM(
+ [#define _GNU_SOURCE
+ #include <pthread.h>],
+ [pthread_getname_np(pthread_self(),"example",0)])],
+ [AC_MSG_RESULT(yes)
+ AC_DEFINE(HAVE_PTHREAD_GETNAME_NP,1,
+ [Have function pthread_setname_np(const char*)])],
+ [AC_MSG_RESULT(no)])
+CFLAGS="$saved_CFLAGS"
+LIBS="$saved_LIBS"
+
+
# check for old glibc < 2.17 to get clock_gettime
AC_SEARCH_LIBS([clock_gettime], [rt posix4],
[AC_DEFINE(HAVE_CLOCK_GETTIME, 1, [Define if clock_gettime is available])
@@ -132,13 +156,23 @@ AC_DEFUN([CHECK_TM_INCLUDES_TM_GMTOFF], [
CHECK_TM_INCLUDES_TM_GMTOFF
+# Check if gettid is available (despite not being documented in glibc doc,
+# it requires __USE_GNU on some systems)
+# C compiler is used since __USE_GNU seems to be always defined for g++.
+save_CPPFLAGS=$CPPFLAGS
+AC_LANG_PUSH(C)
+CPPFLAGS="$CPPFLAGS -D_GNU_SOURCE"
+AC_CHECK_FUNCS([gettid])
+AC_LANG_POP(C)
+CPPFLAGS=$save_CPPFLAGS
+
dnl Check if We need to apply workaround for TLS bug on ARM platform for GCC < 7.3.0:
ARG_ENABLE_DETECT_TLS_GCC_ARM_BUG
dnl Generate the output
AC_CONFIG_HEADER(config.h)
-PKG_CHECK_MODULES(TALLOC, [talloc >= 2.0.1])
+PKG_CHECK_MODULES(TALLOC, [talloc >= 2.1.0])
AC_ARG_ENABLE([pcsc], [AS_HELP_STRING([--disable-pcsc], [Build without PC/SC support])],
[
@@ -181,20 +215,52 @@ then
AC_DEFINE([USE_GNUTLS], [1], [Use GnuTLS as a fallback for missing getrandom()])
fi
+AC_ARG_ENABLE([systemd_logging],
+ [AS_HELP_STRING(
+ [--enable-systemd-logging],
+ [Build with systemd-journal logging support]
+ )],
+ [systemd_logging=$enableval], [systemd_logging="no"])
+AS_IF([test "x$systemd_logging" = "xyes"], [
+ PKG_CHECK_MODULES(SYSTEMD, libsystemd)
+ AC_DEFINE([ENABLE_SYSTEMD_LOGGING], [1], [Enable systemd-journal logging target])
+])
+AM_CONDITIONAL(ENABLE_SYSTEMD_LOGGING, test "x$systemd_logging" = "xyes")
+AC_SUBST(ENABLE_SYSTEMD_LOGGING)
+
+AC_ARG_ENABLE([libmnl],
+ [AS_HELP_STRING(
+ [--disable-libmnl],
+ [Build without netlink socket support via libmnl]
+ )],
+ [mnl=$enableval], [mnl="yes"])
+AS_IF([test "x$mnl" = "xyes"], [
+ 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")
+AC_SUBST(ENABLE_LIBMNL)
+
AC_ARG_ENABLE([libsctp], [AS_HELP_STRING([--disable-libsctp], [Do not enable socket multiaddr APIs requiring libsctp])],
[ENABLE_LIBSCTP=$enableval], [ENABLE_LIBSCTP="yes"])
AM_CONDITIONAL(ENABLE_LIBSCTP, test x"$ENABLE_LIBSCTP" = x"yes")
AS_IF([test "x$ENABLE_LIBSCTP" = "xyes"], [
- old_LIBS=$LIBS
- AC_SEARCH_LIBS([sctp_bindx], [sctp], [
- AC_DEFINE(HAVE_LIBSCTP, 1, [Define 1 to enable SCTP support])
- AC_SUBST(HAVE_LIBSCTP, [1])
- if test -n "$ac_lib"; then
- AC_SUBST(LIBSCTP_LIBS, [-l$ac_lib])
- fi
- ], [
- AC_MSG_ERROR([sctp_bindx not found in searched libs])])
- LIBS=$old_LIBS
+ AC_DEFINE(HAVE_LIBSCTP, 1, [Define 1 to enable SCTP support])
+ # Attempt finding .pc, otherwise set manually (<1.0.17 have no .pc file)
+ PKG_CHECK_MODULES(LIBSCTP, libsctp,
+ [AC_SUBST(LIBSCTP_PC, [libsctp])],
+ [
+ AC_MSG_NOTICE([libsctp.pc not found (building against <1.0.17 ?), attempting manual lib lookup])
+ old_LIBS=$LIBS
+ AC_SEARCH_LIBS([sctp_bindx], [sctp], [
+ AC_SUBST(HAVE_LIBSCTP, [1])
+ if test -n "$ac_lib"; then
+ AC_SUBST(LIBSCTP_LIBS, [-l$ac_lib])
+ fi
+ ], [
+ AC_MSG_ERROR([sctp_bindx not found in searched libs])])
+ LIBS=$old_LIBS
+ ])
])
AC_ARG_ENABLE([sctp-tests], [AS_HELP_STRING([--disable-sctp-tests], [Do not run socket tests requiring system SCTP support])],
@@ -239,6 +305,16 @@ then
AC_DEFINE([OSMO_FD_CHECK],[1],[Instrument the osmo_fd_register])
fi
+AC_ARG_ENABLE([force_io_select],
+ [AS_HELP_STRING(
+ [--enable-force-io-select],
+ [Build with old select I/O instead of poll]
+ )],
+ [force_io_select=$enableval], [force_io_select="no"])
+AS_IF([test "x$force_io_select" = "xyes"], [
+ AC_DEFINE([FORCE_IO_SELECT], [1], [Force the use of select() instaed of poll()])
+])
+
AC_ARG_ENABLE(msgfile,
[AS_HELP_STRING(
[--disable-msgfile],
@@ -308,8 +384,9 @@ 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)
AM_CONDITIONAL(ENABLE_PCSC, false)
AM_CONDITIONAL(ENABLE_PSEUDOTALLOC, true)
AM_CONDITIONAL(ENABLE_SERCOM_STUB, true)
@@ -318,6 +395,17 @@ then
AC_DEFINE([PANIC_INFLOOP],[1],[Use infinite loop on panic rather than fprintf/abort])
fi
+AC_ARG_ENABLE(log_macros,
+ [AS_HELP_STRING(
+ [--disable-log-macros],
+ [Disable logging macros that are also used internally to print information]
+ )],
+ [log_macros="yes"], [log_macros="no"])
+if test x"$log_macros" == x"yes"
+then
+ AC_DEFINE([LIBOSMOCORE_NO_LOGGING],[1],[Disable logging macros])
+fi
+
AC_ARG_ENABLE(sanitize,
[AS_HELP_STRING(
[--enable-sanitize],
@@ -342,6 +430,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"
@@ -356,7 +445,7 @@ if test "x$enable_ext_tests" = "xyes" ; then
AM_PATH_PYTHON
AC_CHECK_PROG(OSMOTESTEXT_CHECK,osmo_verify_transcript_vty.py,yes)
if test "x$OSMOTESTEXT_CHECK" != "xyes" ; then
- AC_MSG_ERROR([Please install git://osmocom.org/python/osmo-python-tests to run the VTY/CTRL tests.])
+ AC_MSG_ERROR([Please install https://gitea.osmocom.org/cellular-infrastructure/osmo-python-tests to run the VTY/CTRL tests.])
fi
fi
AC_MSG_CHECKING([whether to enable VTY/CTRL tests])
@@ -388,10 +477,45 @@ AC_ARG_ENABLE(neon,
[Enable ARM NEON instructions support [default=no]]
)],
[neon=$enableval], [neon="no"])
+AS_IF([test "x$neon" = "xyes"], [
+ AC_DEFINE([HAVE_NEON],, [Support ARM NEON instructions])
+])
AC_MSG_CHECKING([whether to enable ARM NEON instructions support])
AC_MSG_RESULT([$neon])
AM_CONDITIONAL(HAVE_NEON, [test "x$neon" != "xno"])
+#
+# SystemTap support
+#
+AC_MSG_CHECKING([whether to include systemtap tracing support])
+AC_ARG_ENABLE([systemtap],
+ [AS_HELP_STRING([--enable-systemtap],
+ [Enable inclusion of systemtap trace support])],
+ [ENABLE_SYSTEMTAP="${enableval}"], [ENABLE_SYSTEMTAP='no'])
+AM_CONDITIONAL([ENABLE_SYSTEMTAP], [test x$ENABLE_SYSTEMTAP = xyes])
+AC_MSG_RESULT(${ENABLE_SYSTEMTAP})
+
+if test "x${ENABLE_SYSTEMTAP}" = xyes; then
+ # Additional configuration for --enable-systemtap is HERE
+ AC_CHECK_PROGS(DTRACE, dtrace)
+ if test -z "$DTRACE"; then
+ AC_MSG_ERROR([dtrace not found])
+ fi
+ AC_CHECK_HEADER([sys/sdt.h], [SDT_H_FOUND='yes'],
+ [SDT_H_FOUND='no';
+ AC_MSG_ERROR([systemtap support needs sys/sdt.h header])])
+ AC_DEFINE([HAVE_SYSTEMTAP], [1], [Define to 1 if using SystemTap probes.])
+ AC_ARG_WITH([tapset-install-dir],
+ [AS_HELP_STRING([--with-tapset-install-dir],
+ [The absolute path where the tapset dir will be installed])],
+ [if test "x${withval}" = x; then
+ ABS_TAPSET_DIR="\$(datadir)/systemtap/tapset"
+ else
+ ABS_TAPSET_DIR="${withval}"
+ fi], [ABS_TAPSET_DIR="\$(datadir)/systemtap/tapset"])
+ AC_SUBST(ABS_TAPSET_DIR)
+fi
+
OSMO_AC_CODE_COVERAGE
@@ -422,6 +546,35 @@ dnl Check if the compiler supports runtime SIMD detection
CHECK_BUILTIN_SUPPORT([__builtin_cpu_supports],
[Runtime SIMD detection will be disabled])
+dnl There are some members in struct tcp_info that might not exist on all linux versions
+AC_CHECK_MEMBER([struct tcp_info.tcpi_notsent_bytes],
+ AC_DEFINE([HAVE_TCP_INFO_TCPI_NOTSENT_BYTES],
+ [1],
+ [Define to 1 if your <linux/tcp.h> header file have the tcpi_notsent_bytes member in struct tcp_info]),
+ [],
+ [#include <linux/tcp.h>])
+
+AC_CHECK_MEMBER([struct tcp_info.tcpi_rwnd_limited],
+ AC_DEFINE([HAVE_TCP_INFO_TCPI_RWND_LIMITED],
+ [1],
+ [Define to 1 if your <linux/tcp.h> header file have the tcpi_rwnd_limited member in struct tcp_info]),
+ [],
+ [#include <linux/tcp.h>])
+
+AC_CHECK_MEMBER([struct tcp_info.tcpi_sndbuf_limited],
+ AC_DEFINE([HAVE_TCP_INFO_TCPI_SNDBUF_LIMITED],
+ [1],
+ [Define to 1 if your <linux/tcp.h> header file have the tcpi_sndbuf_limited member in struct tcp_info]),
+ [],
+ [#include <linux/tcp.h>])
+
+AC_CHECK_MEMBER([struct tcp_info.tcpi_reord_seen],
+ AC_DEFINE([HAVE_TCP_INFO_TCPI_REORD_SEEN],
+ [1],
+ [Define to 1 if your <linux/tcp.h> header file have the tcpi_reord_seen member in struct tcp_info]),
+ [],
+ [#include <linux/tcp.h>])
+
AC_MSG_RESULT([CFLAGS="$CFLAGS"])
AC_MSG_RESULT([CPPFLAGS="$CPPFLAGS"])
@@ -431,30 +584,52 @@ 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
+ tapset/Makefile
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_amd64.sh b/contrib/jenkins_amd64.sh
index c79e26ab..f26faa86 100755
--- a/contrib/jenkins_amd64.sh
+++ b/contrib/jenkins_amd64.sh
@@ -3,19 +3,13 @@
. $(dirname "$0")/jenkins_common.sh
-ENABLE_SANITIZE="--enable-sanitize"
-
-if [ "x$label" = "xFreeBSD_amd64" ]; then
- ENABLE_SANITIZE=""
-fi
-
src_dir="$PWD"
build() {
build_dir="$1"
prep_build "$src_dir" "$build_dir"
- "$src_dir"/configure --disable-silent-rules --enable-static $ENABLE_SANITIZE --enable-werror \
+ "$src_dir"/configure --disable-silent-rules --enable-static --enable-sanitize --enable-werror \
--enable-external-tests
run_make
@@ -29,7 +23,7 @@ build .
# do distcheck only once, which is fine from built source tree, since distcheck
# is well separated from the source tree state.
DISTCHECK_CONFIGURE_FLAGS=--enable-external-tests \
- $MAKE distcheck \
+ $MAKE $PARALLEL_MAKE distcheck \
|| cat-testlogs.sh
$MAKE maintainer-clean
diff --git a/contrib/jenkins_arm.sh b/contrib/jenkins_arm.sh
index c9cd922e..87934158 100755
--- a/contrib/jenkins_arm.sh
+++ b/contrib/jenkins_arm.sh
@@ -20,7 +20,7 @@ build() {
--disable-shared \
--disable-libsctp \
--disable-libusb \
- --enable-external-tests \
+ --disable-libmnl \
CFLAGS="-Os -ffunction-sections -fdata-sections -nostartfiles -nodefaultlibs $WERROR_FLAGS"
$MAKE $PARALLEL_MAKE
diff --git a/contrib/jenkins_common.sh b/contrib/jenkins_common.sh
index b86a479a..82dd471a 100644
--- a/contrib/jenkins_common.sh
+++ b/contrib/jenkins_common.sh
@@ -12,6 +12,37 @@ osmo-clean-workspace.sh
verify_value_string_arrays_are_terminated.py
+# Validate enum fields in header are added to tlv_definition in source file (SYS#5891):
+
+verify_gsm0808_tlv_definition() {
+ set +x;
+ enums=$(grep "GSM0808_IE_" include/osmocom/gsm/protocol/gsm_08_08.h | grep "=" | awk '{ print $1 }')
+ counted_enums=$(for f in $enums; do printf "%-60s %s\n" "$f" "$(grep -c "\[$f\]" src/gsm/gsm0808.c)"; done)
+ missing_enums=$(echo "$counted_enums" | grep -v GSM0808_IE_RESERVED | grep "0$" || true)
+ if [ "x$missing_enums" != "x" ]; then
+ echo "Missing IEs in src/gsm/gsm0808.c!"
+ echo "$missing_enums"
+ exit 1
+ fi
+ set -x;
+}
+verify_gsm0808_tlv_definition
+
+verify_gsm_08_05_tlv_definition() {
+ set +x;
+ enums=$(grep "RSL_IE_" include/osmocom/gsm/protocol/gsm_08_58.h | grep -e "=" -e ",$" | awk '{ print $1 }' | tr -d ',')
+ counted_enums=$(for f in $enums; do printf "%-60s %s\n" "$f" "$(grep -c "\[$f\]" src/gsm/rsl.c)"; done)
+ # TODO: Add RSL_IE_SIEMENS_* to the tlv struct definitions.
+ missing_enums=$(echo "$counted_enums" | grep -v RSL_IE_SIEMENS |grep "0$" || true)
+ if [ "x$missing_enums" != "x" ]; then
+ echo "Missing IEs in src/gsm/rsl.c!"
+ echo "$missing_enums"
+ exit 1
+ fi
+ set -x;
+}
+verify_gsm_08_05_tlv_definition
+
prep_build() {
_src_dir="$1"
_build_dir="$2"
diff --git a/contrib/ladder_to_msc.py b/contrib/ladder_to_msc.py
index 6efcb190..9bac110e 100755
--- a/contrib/ladder_to_msc.py
+++ b/contrib/ladder_to_msc.py
@@ -166,6 +166,11 @@ class Parse:
'<>' : 'abox',
'()' : 'rbox',
'[]' : 'note',
+
+ '<->' : '<=>',
+ '<-->' : '<<=>>',
+ '<~>' : '<->',
+ '<=>' : '<:>',
}
def __init__(self, output):
diff --git a/contrib/ladder_to_msc_test.ladder b/contrib/ladder_to_msc_test.ladder
index 9ec4de09..319d6051 100644
--- a/contrib/ladder_to_msc_test.ladder
+++ b/contrib/ladder_to_msc_test.ladder
@@ -52,6 +52,11 @@ foo <= bar
foo ><- bar
* < bar
+foo <-> bar
+foo <--> bar
+foo <~> bar
+foo <=> bar
+
foo <> . angled box
foo () . rounded box
foo [] . note
diff --git a/contrib/libosmocore.spec.in b/contrib/libosmocore.spec.in
index fb45516e..fdd1a657 100644
--- a/contrib/libosmocore.spec.in
+++ b/contrib/libosmocore.spec.in
@@ -29,7 +29,9 @@ BuildRequires: xz
BuildRequires: pkgconfig(gnutls) >= 2.12.0
BuildRequires: pkgconfig(libpcsclite)
BuildRequires: pkgconfig(libusb-1.0)
-BuildRequires: pkgconfig(talloc) >= 2.0.1
+BuildRequires: pkgconfig(talloc) >= 2.1.0
+BuildRequires: pkgconfig(libmnl)
+BuildRequires: pkgconfig(libsystemd)
%description
libosmocore is a package with various utility functions that were
@@ -52,7 +54,8 @@ originally developed as part of the OpenBSC project.
This package contains a program for frequency calculation for GSM
called "osmo-arfcn", and a program called "osmo-auc-gen" that is used
-for testing GSM authentication.
+for testing GSM authentication, as well as "osmo-config-merge", a tool
+for merging Osmocom configuration files.
%package -n libosmocodec0
Summary: GSM 06.10, 06.20, 06.60, 06.90 codec library
@@ -109,13 +112,13 @@ transcoding routines.
This subpackage contains libraries and header files for developing
applications that want to make use of libosmocoding.
-%package -n libosmocore16
+%package -n libosmocore20
Summary: Osmocom core library
# crc16.c has GPL2-only clauses, the rest (*.c) is GPL-2.0+
License: GPL-2.0-only AND GPL-2.0-or-later
Group: System/Libraries
-%description -n libosmocore16
+%description -n libosmocore20
libosmocore is a library with various utility functions shared
between OpenBSC and OsmocomBB.
@@ -124,8 +127,9 @@ Summary: Development files for the Osmocom core library
# crc16.h has GPL2-only clauses, the rest (*.h) is GPL-2.0+
License: GPL-2.0-only AND GPL-2.0-or-later
Group: Development/Libraries/C and C++
-Requires: libosmocore16 = %version
+Requires: libosmocore20 = %version
Requires: libtalloc-devel
+Requires: lksctp-tools-devel
%description -n libosmocore-devel
libosmocore is a library with various utility functions shared
@@ -161,12 +165,12 @@ interface, the control interface is meant to be used by programs.
This subpackage contains libraries and header files for developing
applications that want to make use of libosmoctrl.
-%package -n libosmogb11
+%package -n libosmogb14
Summary: Osmocom GPRS Gb Interface (NS/BSSGP) library
License: AGPL-3.0-or-later
Group: System/Libraries
-%description -n libosmogb11
+%description -n libosmogb14
libosmocore is a package with various utility functions that were
originally developed as part of the OpenBSC project.
@@ -177,7 +181,7 @@ Summary: Development files for the Osmocom GPRS Gb interface library
License: AGPL-3.0-or-later
Group: Development/Libraries/C and C++
Requires: libosmocore-devel = %version
-Requires: libosmogb11 = %version
+Requires: libosmogb14 = %version
Requires: libosmovty-devel = %version
%description -n libosmogb-devel
@@ -186,12 +190,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 libosmogsm15
+%package -n libosmogsm18
Summary: Osmocom GSM utility library
License: GPL-2.0-or-later AND AGPL-3.0-or-later
Group: System/Libraries
-%description -n libosmogsm15
+%description -n libosmogsm18
libosmocore is a package with various utility functions that were
originally developed as part of the OpenBSC project.
@@ -206,7 +210,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: libosmogsm15 = %version
+Requires: libosmogsm18 = %version
+Requires: libosmoisdn-devel = %version
+Requires: libosmoisdn0 = %version
%description -n libosmogsm-devel
The libosmogsm library in particular is a collection of common code
@@ -218,6 +224,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
@@ -244,12 +278,12 @@ access.
This subpackage contains libraries and header files for developing
applications that want to make use of libosmosim.
-%package -n libosmovty4
+%package -n libosmovty9
Summary: Osmocom VTY interface library
License: GPL-2.0-or-later
Group: System/Libraries
-%description -n libosmovty4
+%description -n libosmovty9
libosmocore is a package with various utility functions that were
originally developed as part of the OpenBSC project.
@@ -261,7 +295,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: libosmovty4 = %version
+Requires: libosmovty9 = %version
%description -n libosmovty-devel
The libosmovty library implements the interactive command-line on the
@@ -304,7 +338,7 @@ applications that want to make use of libosmousb.
%build
echo "%version" >.tarball-version
autoreconf -fiv
-%configure --enable-shared --disable-static \
+%configure --enable-shared --disable-static --enable-systemd-logging \
--includedir="%_includedir/%name"
make %{?_smp_mflags} V=1
@@ -320,18 +354,20 @@ make %{?_smp_mflags} check || (find . -name testsuite.log -exec cat {} +)
%postun -n libosmocodec0 -p /sbin/ldconfig
%post -n libosmocoding0 -p /sbin/ldconfig
%postun -n libosmocoding0 -p /sbin/ldconfig
-%post -n libosmocore16 -p /sbin/ldconfig
-%postun -n libosmocore16 -p /sbin/ldconfig
+%post -n libosmocore20 -p /sbin/ldconfig
+%postun -n libosmocore20 -p /sbin/ldconfig
%post -n libosmoctrl0 -p /sbin/ldconfig
%postun -n libosmoctrl0 -p /sbin/ldconfig
-%post -n libosmogb11 -p /sbin/ldconfig
-%postun -n libosmogb11 -p /sbin/ldconfig
-%post -n libosmogsm15 -p /sbin/ldconfig
-%postun -n libosmogsm15 -p /sbin/ldconfig
+%post -n libosmogb14 -p /sbin/ldconfig
+%postun -n libosmogb14 -p /sbin/ldconfig
+%post -n libosmogsm18 -p /sbin/ldconfig
+%postun -n libosmogsm18 -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 libosmovty4 -p /sbin/ldconfig
-%postun -n libosmovty4 -p /sbin/ldconfig
+%post -n libosmovty9 -p /sbin/ldconfig
+%postun -n libosmovty9 -p /sbin/ldconfig
%post -n libosmousb0 -p /sbin/ldconfig
%postun -n libosmousb0 -p /sbin/ldconfig
@@ -363,9 +399,9 @@ make %{?_smp_mflags} check || (find . -name testsuite.log -exec cat {} +)
%_libdir/libosmocoding.so
%_libdir/pkgconfig/libosmocoding.pc
-%files -n libosmocore16
+%files -n libosmocore20
%defattr(-,root,root)
-%_libdir/libosmocore.so.16*
+%_libdir/libosmocore.so.20*
%files -n libosmocore-devel
%defattr(-,root,root)
@@ -389,9 +425,9 @@ make %{?_smp_mflags} check || (find . -name testsuite.log -exec cat {} +)
%_libdir/libosmoctrl.so
%_libdir/pkgconfig/libosmoctrl.pc
-%files -n libosmogb11
+%files -n libosmogb14
%defattr(-,root,root)
-%_libdir/libosmogb.so.11*
+%_libdir/libosmogb.so.14*
%files -n libosmogb-devel
%defattr(-,root,root)
@@ -401,9 +437,9 @@ make %{?_smp_mflags} check || (find . -name testsuite.log -exec cat {} +)
%_libdir/libosmogb.so
%_libdir/pkgconfig/libosmogb.pc
-%files -n libosmogsm15
+%files -n libosmogsm18
%defattr(-,root,root)
-%_libdir/libosmogsm.so.15*
+%_libdir/libosmogsm.so.18*
%files -n libosmogsm-devel
%defattr(-,root,root)
@@ -414,6 +450,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*
@@ -426,9 +474,9 @@ make %{?_smp_mflags} check || (find . -name testsuite.log -exec cat {} +)
%_libdir/libosmosim.so
%_libdir/pkgconfig/libosmosim.pc
-%files -n libosmovty4
+%files -n libosmovty9
%defattr(-,root,root)
-%_libdir/libosmovty.so.4*
+%_libdir/libosmovty.so.9*
%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..3b035854
--- /dev/null
+++ b/contrib/talloc_count.sh
@@ -0,0 +1,52 @@
+#!/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-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 317fefe2..a1f96700 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,1192 @@
+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 ]
+ * .gitignore: add utils/osmo-aka-verify binary
+ * tests/vty: fix use of GNU 'missing =' extension in designator
+ * debian/control: minimum version of libtalloc-dev must be >= 2.1.0
+ * tests/tdef: rename the binaries to end with '_test'
+ * tdef: fix wrong path in documentation: tests/vty -> tests/tdef
+ * bitvec_read_field(): indicate errors using errno
+ * bitvec_read_field(): fix incorrect bit-shift issue found by UBSan
+ * bitvec_read_field(): optimize by expanding bytenum_from_bitnum()
+ * tests/testsuite.at: ensure empty stderr for the bitvec_test
+ * VTY: enable talloc introspection for OTC_GLOBAL
+ * VTY: implement 'no log gsmtap [HOSTNAME]' command
+ * contrib/libosmocore.spec.in: mention osmo-config-merge in utils
+ * gsm_7bit_encode_n(): use regular malloc() instead of calloc()
+ * tests/logging: ensure both stream and wqueue modes are tested
+ * tests/logging: also test printing the filename information
+ * logging: fix coding style issues in _output_buf()
+ * logging: fix printing of '\0' when filename printed last
+ * tests/logging: merge both logging_test_{stream,wqueue}.err
+ * core/utils.h: add OSMO_LIKELY / OSMO_UNLIKELY macros
+ * core/utils.h: wrap OSMO_ASSERT() with do { ... } while (0)
+ * core/msgb.h: make use of OSMO_LIKELY / OSMO_UNLIKELY
+ * core/utils.h: make use of OSMO_LIKELY in OSMO_ASSERT
+ * libosmocodec: osmo_hr_check_sid(): simplify the logic
+ * contrib/jenkins_amd64.sh: remove FreeBSD specific quirks
+ * bssmap_le: support additional IEs in Perform Location Request
+ * Use internal <osmocom/core/talloc.h> everywhere
+ * coding: fix comments for detect_afs_sid_{first,update,onset}
+ * coding: cosmetic: move 'dtx_prev' to the scope where it's used
+ * coding: use switch statement in gsm0503_tch_a[fh]s_decode_dtx()
+ * coding: properly handle AFS_SID_UPDATE frames in DTX mode
+ * coding: prevent marking FACCH frames as AMR's special DTX frames
+ * tests/dtx: test detection/decoding of A[FH]S_SID_UPDATE
+ * tests/dtx: test tagging of FACCH/[FH] frames
+ * coding: fix decoding of AHS_SID_UPDATE frames (BER ~50%)
+ * coding: do not reset codec ID on receipt of DTX frames
+ * coding: add gsm0503_detect_a[fh]s_dtx_frame2()
+ * coding: separate gsm0503_tch_a[fh]s_decode_inband()
+
+ [ Eric ]
+ * fix isdigit taking unsigned as input
+ * logging: allow disabling macros using a new define: LIBOSMOCORE_NO_LOGGING
+
+ [ Daniel Willmann ]
+ * bssgp_bvc_fsm: Move log message to the correct place
+ * bssgp_bvc_fsm: Add a hook to notify when a reset was acknowledged
+
+ [ Harald Welte ]
+ * logging: Fix memory leak in case async log write queue overflows
+ * write_queue: Document it that caller is responsible if enqueue fails
+ * gsmtap: Add gsmtap_sendmsg_free() as alternative to gsmtap_sendmsg()
+ * Introduce CRC and FSM for IuUP (user plane) as used in 3G RTP data
+ * tcp_stats: fix compilation on CentOS 7
+ * iuup: Fix signed/unsigned loop counter control flow issue
+ * src/conv.c: Align better with Osmocom coding style
+ * bitvec: Fix -Wsign-compare warnings
+ * utils: Fix -Wsign-compare warnings
+ * log_taget_find() should use enum log_target_type, not int
+ * bits.c: Fix -Wsign-compare warnings
+ * socket, select: Fix -Wsign-compare warnings
+ * msgb: Fix -Wsign-compare warnings
+ * osmo_libusb: Fix NULL check in osmo_usb_removed_cb()
+ * osmo_libusb: Use libusb_get_pollfds() to get initial file descriptors
+ * osmo_libusb: Print log message on libusb initialization error
+ * gsm0808: Test if we properly decode a SRVCC cell identifier list
+ * usb: Match device by VID/PID without path/addr if it is unique
+ * clean-up pkg-config files: Make use of "Requires" as documented
+ * debian/control: libosmocore-dev must depend on libsctp-dev and libusb-1.0-0-dev
+ * libosmo{gb,vty}.pc.in: Add talloc to 'Requires'
+ * libosmocore.spec: Make libosmocore-devel require libsctp
+ * libosmovty: Link libosmovty against libpthread
+ * vty: Support platforms that don't support pthread_getname_np()
+ * vty: Add a 'skip-zero' version of 'show stats' and 'show rate-counters'
+ * stats: Functions with no arguments should specify(void)
+ * stats: Avoid NULL pointer deref in allocation failure paths.
+ * stats: don't try to save unknown stats reporter types
+ * fsm_vty: use unsigned int when left-shifting 31 bits!
+ * update git URLs (git -> https; gitea)
+
+ [ Eric Wild ]
+ * logging: make LIBOSMOCORE_NO_LOGGING work as expected
+
+ [ Oliver Smith ]
+ * treewide: remove FSF address
+ * select_main: don't poll forever during shutdown
+ * Cosmetic: linuxlist.h: fix misleading comment
+
+ [ Philipp Maier ]
+ * stats: fix typo
+ * stat_item: tolerate NULL pointer argument in osmo_stat_item_group_free
+ * select: gather statistics for TCP connections
+ * stats_tcp: use a default batch size of 5 instead of 1
+ * stats_tcp: fix stats item identifier
+ * gsm23003: fix docstring for osmo_plmn_from_bcd()
+ * iuup: do not use illegal characters in state/event names.
+ * logging: log to stderr when logging is not initialized
+ * reader: more meaningful null pointer check in get_sw
+
+ [ Pau Espin Pedrol ]
+ * include/: Adapt some headers to match contrib/struct_endianess.py format
+ * logging: Fix Not enough tailroom msgb_put in _output_buf callers
+ * osmo-release.sh: Use variable containing bumpversion path everywhere
+ * iuup: Fix decoding of 1byte-length subflow size fields
+ * iuup: Submit RNL-STATUS-Initialization.ind upon rx of Init
+ * iuup: Improve CRC checksum error logging
+ * gsm: [ABI BREAK] Support CellId SAI, change CellId CGI-PS id number
+ * gsm: lapd_core: Change log line NOTICE->INFO
+ * gsm0808_test: Add new unit test showing dec error
+ * gsm0808: Fix decoding of IE GSM0808_IE_LCS_CLIENT_TYPE
+ * cosmetic: gsm_08_08.h: Add space between assignment sides
+ * gsm0808: Add missing IEs in bss_att_tlvdef
+ * jenkins: Validate IEs are added to tlv_definition
+ * libosmocore.pc.in: put libsctp in Requires.private
+ * configure: Support libsctp < 1.0.17 without libsctp.pc
+ * rsl: Fix tlv_parse of IPAC_DLCX_IND message
+ * jenkins: Validate gsm 08.58 IEs are added to tlv_definition
+ * gsm: Introduce helper rach_tx_integer_raw2val()
+ * cosmetic: logging.h: fix indentation
+ * gsm_12_21.h: Fix abis_nm_avail_state InTest and Failed values
+ * gsm_12_21.h: Add header description pointing to TS files
+ * coding: Refactor function to avoid gcc false positive warn
+ * osmo-arfcn: Fix false positive in gcc 12.1.0
+ * coding: Use ARRAY_SIZE macro
+ * iuup: Rework API to support RFCI IDs != RFCI index
+ * cosmetic: iuup.h: Fix indentation
+ * iuup: Fix IPTIs_present not set to 0 if no IPTIs received
+ * cbsp: Add enum and value string for Cause
+ * iuup: Add missing state to bitmask for st SMpSDU_Data_Transfer_Ready
+ * iuup: Drop unused events
+ * tests/iuup: Showcase IuUP stack not answering subsequent Init msgs
+ * iuup: Fix Handling of subsequent Initialization msgs
+ * vty: command.c: Add assert
+
+ [ Sylvain Munaut ]
+ * conv: Fix the traceback for tail biting codes
+ * build: Disable libusb and libmnl for embedded builds
+
+ [ Neels Hofmeyr ]
+ * PFCP: add DLPFCP and osmo-upf port numbers
+ * ports.h: add osmo-pfcp-tool ports
+ * add osmo_sockaddr_to_str_c(), osmo_sockaddr_to_str_buf2()
+ * log: socket.c: rather use the osmo_sockaddr_str _FMT
+ * add osmo_quote_str_buf3, osmo_escape_str_buf3
+ * add osmo_sockaddr_from/to_octets()
+ * follow-up to osmo_sockaddr_from/to_octets()
+ * BSSAP: HO Request Ack: add missing Codec List (BSS Supported)
+ * add osmo_sockaddr_set_port()
+ * osmo_time_cc: rate_ctr presence should not affect counting
+ * cosmetic tweak in Makefile.am
+
+ [ Michael Iedema ]
+ * stats: use tcp stat names as provided
+
+ [ Karsten Ohme ]
+ * APDU parsing support for GlobalPlatform
+
+ -- Pau Espin Pedrol <pespin@sysmocom.de> Tue, 28 Jun 2022 17:00:51 +0200
+
+libosmocore (1.6.0) unstable; urgency=medium
+
+ [ Pau Espin Pedrol ]
+ * osmo-release.sh: Check configure.ac dependency versions match those in rpm *.spec.in
+ * osmo-release.sh: Drop repeated DRY_RUN early exit
+ * osmo-release.sh: Check LIBVERSION matches rpm *.spec.in
+ * osmo-release.sh: Properly rearrange var init and sanity checks
+ * tests/gb: Fix printf format errors on ARM 32 bits
+ * Skip RPM checks if no *.spec.in available
+ * gsm_08_08.h: Add extra field elements defined in other sections
+ * cosmetic: tlv_parser: fix typo in func description
+ * cosmetic: Fix trailing whitespace
+ * gsm: Support Sending Last EUTRAN PLMN Id in Handover Required
+ * cosmetic: fix typo in comment
+ * gsm0808: Introduce gsm0808_old_bss_to_new_bss_info_att_tlvdef
+ * osmo_timer_pending: Make arg const
+ * gb: Fix naming and export symbol bssgp_enc_rim_pdu
+ * Revert "gb: Fix naming and export symbol bssgp_enc_rim_pdu"
+ * gb: Fix missing exporting symbol bssgp_encode_rim_pdu
+ * vty: Implement missing public API host_config_file()
+ * vty: Introduce API vty_read_config_filep
+ * range_enc_determine_range(): Don't dereference array on size=0
+ * stat,rate_ctr: Introduce new API to get counter at given index
+ * Use new stat item/ctr getter APIs
+ * stat,rate_ctr: Allow setting group name and use it at report time
+ * ns2: Use NSVC bufid in stats report
+ * ipaccess: Add new osmo extension IPAC_PROTO_EXT_PCU
+ * bts_feature: Introduce feature to speak to PCU
+ * msgb_alloc_headroom: Change size args to be uint16_t
+ * ctrl: Pre-calculate required size before allocating msgb
+ * ctrl: Support recovering from short write
+ * cosmetic: gsm_12_21.h: Fix trailing whitespace
+ * Rename osmo dyn ts enums
+ * bts_feature: Introduce feature to confiure dyn ts as sdcch8
+ * Make gcc 11.1.0 false positivies happy
+ * cosmetic: gb/gprs_ns.c: fix trailing whitespace
+ * gb/bssgp: Abort clearly if backward-compat API is used in wrong way
+ * utils: Fix c++ warn in OSMO_STRBUF_APPEND
+ * cosmetic: Fix missing space in comment
+ * gsm_08_58.h: Extend IPA Power Control Params IEs to pass C/I params
+ * gsm: Mark gsm0858_rsl_ul_meas_enc() pointer param const
+ * cosmetic: gsm: meas_rep.h: Fix typo in comment
+ * logging: Change LLAPD category color to purple-like one
+ * {ctrl,vty}/ports.h: Allocate ports for osmo-hnodeb
+ * configure.ac: Depend on talloc 2.1.0
+ * logging: Fix double lock of log_tgt_mutex
+ * contrib/libosmocore.spec.in: Depends on talloc 2.1.0
+ * osmo-release.sh: Blacklist script file from LIBVERS matches
+
+ [ Oliver Smith ]
+ * osmo-release.sh: support epoch
+ * tests/stats: enable logging in test output
+ * stats: log error when missing stats values
+ * Revert "stats: log error when missing stats values"
+ * stat_item: make next_id argument name consistent
+ * stat_item: add comment with struct overview
+ * stat_item: make value ids item specific
+ * stats_test: restore stat_item_get_next asserts
+ * stats: log error when missing stats values (v2)
+ * stats: have_value is a bool, not int
+ * vty: add "show uptime"
+ * vty: add "show pid"
+ * vty: add "shutdown"
+ * vty: show uptime: use timespecsub
+ * vty: clear screen with ^L
+ * vty/ports: prepare formatting for a long define
+ * vty/ports: move pcap server/client ports
+ * tests: add 'make update_exp' target
+ * tests/stats: show how last item sent may be wrong
+ * stats: send real last value if no new values come
+
+ [ Alexander Couzens ]
+ * gprs_bssgp: add support for SGSN oriented RESET
+ * gprs_bssgp: use BVCI_SIGNALLING/BVCI_PTM instead of 0/1
+ * gprs_ns2: don't OSMO_ASSERT() while freeing NS-VC.
+ * gprs_ns2: rework logging of Rx and Tx NS PDU
+ * gprs_ns2: always use the same method to print NSVCs
+ * gprs_ns2_vty: hide dynamic NSE information when ask for persistant only
+ * gprs_ns2_vty: make the `show ns entities` and `show ns binds` look similiar
+ * gprs_ns2: dump_nsvc: correct indention
+ * gprs_ns2: add vty command `nsvc <nsvci> reset`
+ * gprs_ns2: fix memory leaks when receiving SNS or invalid packets
+ * gprs_ns2: vty: remove a white space in `show binds`
+ * gprs_ns2: nsvc_fsm: reorder notification st_alive_on_enter()
+ * gprs_ns2: sns: ensure the sns->alive state is correct
+ * gprs_ns2: sns: remove the initial SNS NSVC if it's not part
+ * gprs_ns2_vty: print a response to vty `nsvc <nsvci> (block|unblock|reset)
+ * gprs_ns2: fix nsvc block and unblock vty command
+ * gprs_ns2: SNS: allow transition missing transition GPRS_SNS_ST_UNCONFIGURED
+ * gprs_ns2: add functions for SNS add/del/change-weight messages
+ * gprs_ns2: sns: fix del bind()
+ * gprs_ns2: vty: fix removing a bind from a SNS
+ * gprs_ns2: rework id strings of nsvcs
+ * gprs_ns2_sns: move selection of the next bind into own function
+ * gprs_ns2_sns: bss: improve validation of configuration
+ * gprs_ns2_sns: refactor local and remote entries into a struct
+ * gprs_ns2_sns: refactor ns2_clear_ipv46_entries_local to use new elems functions
+ * gprs_ns2_sns: use struct ns2_sns_elems in add/update/remove remote_elems
+ * gprs_ns2_sns: move gss->remote specific check out of add_ip4_elem/add_ip6_elem
+ * gprs_ns2_sns: add check for duplicates to add_ip6_elem()
+ * gprs_ns2_sns: refactor ip4_weight_sum/ip6_weight_sum
+ * gprs_ns2_sns: refactor nss_weight_sum_data -> ip46_weight_sum_data
+ * gprs_ns2_sns: replace ns2_sns_type with address family
+ * gprs_ns2_sns: bss: set gss->family
+ * gprs_ns2: use llist_add_tail to keep order
+ * gprs_ns2: fix missing notify towards the NSE when NSVC become blocked
+ * gprs_ns2_vc_fsm: reset the ALIVE response time when stopping test
+ * gprs_ns2: fix crash when changing the MTU
+ * gprs_ns2: fix check of MTU changes for frame relay
+ * gprs_ns2: correct mtu value in the log line
+ * gprs_ns2: use gprs_ns2_free_bind() to clean up a bind
+ * gprs_ns2: fix wrong format string in Tx Size logline
+ * gprs_ns2_vty: dump_nsvc: change output depending on NSVCI
+ * gprs_ns2: ensure the NSE becomes dead when FR link went down
+ * gprs_ns2_udp: don't start the NSVC fsm for SNS
+ * gprs_ns2_sns: refactor SNS failures into a function
+ * gprs_ns2_sns: free the NSE if the SIZE PDU is not valid
+ * gprs_ns2: add recursive anchor to protect against double free
+ * gprs_ns2: move sns_event into internal.h to direct emit events
+ * gprs_ns2_sns: rework sns clean up
+ * gprs_ns2: use an event to free the nsvscs when using SNS
+ * gprs_ns2: gprs_ns2_free_bind() should remove itself before removing nsvcs
+ * gprs_ns2: don't use llist_for_each when freeing an element
+ * gprs_ns2_sns: implement local change weight procedure
+ * gprs_ns2_sns: implement outbound SNS ADD procedures
+ * gprs_ns2_sns: implement outbound SNS DEL procedures
+ * gprs_ns2: also prevent recursive events when SGSN side cleans up
+ * gprs_ns2: calculate the nse->*_sums before notifing the sns fsm
+ * gprs_ns2: add correct filename/linenr to sns failed log message
+ * gprs_ns2: improve reselection protection
+ * gprs_ns2_sns: ensure the SNS fsm behave correct when no signalling NSVCs are present
+ * gprs_ns2: ensure the incoming NSVC is also the outgoing NSVC
+ * ns2: nsvc: reject UNITDATA when the remote BLOCK'ed
+ * ns2: fix a crash when receiving a SIZE while configured
+ * gprs_ns2: fix NS STATUS validation
+ * gprs_ns2: fix a white space
+ * gprs_ns2: nsvc: react on STATUS PDUs with cause code NSVC UNKNOWN/NSVC BLOCKED
+ * vty: add vty_out_uptime() print the uptime to the vty
+ * ns2: nse: add a uptime/downtime to track the last state change
+ * ns2: nsvc: add a uptime/downtime to track the last state change
+ * ns2: message: BLOCK/BLOCK ACK allow to use a given NSVCI instead of using the nsvc nsvci
+ * ns2: ensure the NSVC is in the correct mode for NSVC UNKNOWN/NSVC BLOCKED cause codes
+ * ns2: fsm: add comment don't answer on a STATUS with a STATUS
+ * ns2: message: allow to pass a foreign NSVCI to STATUS PDU
+ * ns2: correct parse a STATUS PDU which was received over a different NSVC
+ * ns2: improve log line when receving a PDU with wrong NSE
+ * ns2: don't forward an invalid RESET PDU to the FSM
+ * ns2: correct parse a BLOCK PDU which was received over a different NSVC
+ * include: add enum for UTRAN cipher
+
+ [ Harald Welte ]
+ * cosmetic: ssn: some more comments
+ * gprs_ns2: Log all transmitted SNS messages
+ * gprs_ns2_sns: Dispatch inbound SNS-ACK to FSM
+ * gprs_ns2: Remove any references to DNS; we use DLNS in NS2.
+ * gprs_ns2: Pass peer/remote sockaddr argument to ns2_create_vc()
+ * TODO-RELEASE: Request increasing _LAST_OSMOVTY_NODE next release
+ * gprs_ns2_sns: Implement error log in case no binds found for NSE
+ * gprs_ns2_sns: Remove TODO (spec agrees, the correct cause code is used)
+ * gprs_ns2_vty: Clarify VTY help string wording
+ * gprs_ns2_sns: refactor ns2_sns_st_size_onenter()
+ * gprs_ns2_sns: Unify handling of SNS-CONFIG for IPv4 + IPv6
+ * gprs_ns2: Encapsulate setting NSE dialect
+ * gprs_ns2_sns: Split allstate action in generic and BSS-specific part
+ * utils: osmo-aka-verify to verify UMTS AKA (SIM side)
+ * osmo-aka-verify: Fix use case with OP and not OPc
+ * gprs_ns2_sns: Support for SGSN-side IP-SNS
+ * gprs_ns2_sns: Add some more OSMO_ASSERT about BSS role
+ * gprs_ns2_sns: Rename BSS-side states to include 'bss' in name
+ * gprs_ns2: Introduce gprs_ns2_create_nse2() for SGSN side SNS
+ * gprs_ns2_vty: Allow creating NSE in sgsn-role
+ * gprs_ns2_sns: Don't clear remote IP endpoints in SGSN role
+ * gprs_ns2_sns: Verify mandatory IE presence in incoming SNS-SIZE
+ * gprs_ns2_sns: SNS-SIZE contains the actual number of local endpoints
+ * gprs_ns2_sns: Assume the SGSN has a very large number of max. NSVC
+ * gprs_ns2_sns: Implement checks during processing of inbound SNS-SIZE
+ * gprs_ns2_vty: Permit VTY configuration of bind->accept_sns
+ * gprs_ns2: dynamic NS-VC + NSE creation for IP-SNS in SGSN role
+ * gprs_ns2_sns: Allow VTY configuration of default binds for IP-SNS
+ * gprs_ns2: Add comments explaining the nsvc->sns_only field
+ * gprs_ns2_vc_fsm: In IP-SNS/ALIVE mode, initial state is ALIVE/UNBLOCKED
+ * gprs_ns2: Actually start Tns-test after SNS-CONFIG creates NS-VC
+ * gprs_ns2_sns: Compute local endpoints before using them
+ * gprs_ns2_sns: remove code duplication in create_missing_nsvcs()
+ * gprs_ns2_sns: Don't create NS-VCs for binds outside the NSE
+ * gprs_ns2_sns: Fix memory leak when creating ip[46]_local arrays
+ * sim: Obtain card ATR when opening the card
+ * sim: Remove 'printf' from library code
+ * frame_relay: Export osmo_fr_network_free()
+ * ns2: Dump frame relay state to VTY during "show ns"
+ * socket: Introduce osmo_sock_set_dscp() to set socket DSCP value
+ * ns2: Fix setting the DSCP value.
+ * socket: Introduce osmo_sock_set_priority() helper function
+ * socket: reduce code duplication, introduce socket_helper_tail()
+ * socket: QoS support for all our socket init functions
+ * socket: IPv6 support for osmo_sock_set_dscp()
+ * gprs_ns2: Fix yet another DSCP vs. TOS mix-up
+ * gprs_ns2_frgre: Ensure DSCP is sin premitted value range
+ * ns2: migrate from osmo_sock_set_dscp() to OSMO_SOCK_F_DSCP()
+ * gprs_ns: Fix another DSCP vs. TOS mistake in old NS code
+ * ns2: Allow setting the socket priority for a UDP bind
+ * Fix ipa_ccm_make_id_resp_from_req
+ * Fix ipa_ccm_make_id_resp_from_req to work at all
+ * ipa_ccm_make_id_resp: Make it work at all
+ * ns2: change the 'priority' setting name to 'socket-priority'
+ * osmo_sock_*_ofd(): Mark OSMO_FD_WRITE on non-blocking connect()
+ * sim: Add osim_card_{reset,close}() API
+ * osmo-auc-gen: Permit specifying the SQN in hex (0x12345) format
+ * copy base64 implementation from mbedtls
+ * base64: Migrate over to osmocom
+ * base64: reformat using Lindent to conform to our coding style
+ * osmo-auc-gen: Print RFC3310 IMS HTTP-AKA style base64 nonce/res
+ * logging: Change stderr + file target to use non-blocking write
+ * logging: Avoid memcpy from stack to msgb in _file_output()
+ * logging: Attempt a synchronous, non-blocking write first (file, stderr)
+ * rate_ctr: Make it safe to call rate_ctr_init() several times
+
+ [ Daniel Willmann ]
+ * stats: Ensure that each osmo_stat_item only reports once per interval
+ * tlv: Fix length returned by t{l16,16l}v_put
+ * stats_vty: Improve generation of osmo counters
+ * ns2: Ignore NSVC with data_weight 0 for data
+ * Aggregate NSVC stats inside the NSE
+ * ns2: Avoid use-after-free when SGSN-side non-persistent SNS-NSE fails
+ * frame_relay, gprs_ns2_fr: Fix log messages, remove unused struct
+
+ [ Vadim Yanitskiy ]
+ * utils/osmo-aka-verify: fix swapped CK/IK arguments
+ * vty/logging: use consistent quiting in warning messages
+ * vty/logging: ensure consistent '%' prefix for warnings
+ * vty/logging: logp: properly handle library specific sub-systems
+ * protocol/gsm_08_58.h: add RSL_CMOD_SP_{GSM4,GSM5,GSM6}
+ * protocol/gsm_08_58.h: add more 'Channel rate and type' values
+ * protocol/gsm_08_58.h: add asymmetric CSD data rates
+ * gsm/abis_nm: add missing NM_OC_IPAC_* value-string entries
+ * rsl: make rsl_dec_chan_nr() more readable, use RSL_CHAN_NR_MASK
+ * gsm_08_58.h: add Osmocom specific Bm/Lm CBITs for VAMOS
+ * gsm_08_58.h: add mask for Osmocom specific VAMOS C-bits
+ * fsm: cosmetic: fix weird spacing in osmo_fsm_inst_alloc()
+ * stats_vty: also show rate counter group name (if present)
+ * bts_features: add feature for BCCH carrier power reduction mode
+ * fix rsl_chan_nr_str_{buf,c}(): enlarge the buffer size
+ * utils: remove misleading comments for osmo_hexdump[_nospc]_c()
+ * utils: introduce osmo_talloc_replace_string_fmt()
+ * gsm_08_58: extend struct abis_rsl_osmo_temp_ovp_acch_cap
+ * Revert "Prevent GCR encoder/decoder functions from being used directly"
+ * gsm/protocol/gsm_44_004.h: fix missing include of 'endian.h'
+ * gsm/protocol/gsm_04_08.h: add gsm48_meas_res_is_valid()
+ * tests/stats: add VTY transcript tests
+ * stats: use llist_add_tail() in osmo_stats_reporter_alloc()
+ * stats: allow configuring reporter's name in the VTY
+ * stats: cosmetic: print 'stats interval' before the reporters
+ * stats: don't mark reporter as 'disable' beforehand
+ * stats: clarify error messages in cfg_no_stats_reporter_{statsd,log}
+
+ [ Neels Hofmeyr ]
+ * add BTS_FEAT_VAMOS
+ * deprecate osmo_bts_feature_name(), add osmo_bts_features_desc()
+ * add osmo_bts_features_names: short BTS feature strings
+ * RR: add VAMOS channel modes
+ * RR: add missing Extended TSC Set IE
+ * gsm48_mr_cfg_from_gsm0808_sc_cfg(): drop bitmask without effect
+ * fix default_timeout type of osmo_tdef_fsm_inst_state_chg default_timeout
+ * add RSL_IE_OSMO_TRAINING_SEQUENCE
+ * add RSL_CMOD_CRT_OSMO_TCH_VAMOS_Bm, RSL_CMOD_CRT_OSMO_TCH_VAMOS_Lm
+ * fixup for gsm48_chan_mode_to_non_vamos()
+ * osmo_select_shutdown_request(): allow finishing pending writes on SIGTERM
+ * add Kc128 to gsm0808 Create Ciphering Command
+ * gsm0808: add Kc128 to Handover Request
+ * add fixme: enforce 8 byte length of Kc
+ * fix api doc of osmo_identifier_sanitize_buf()
+ * vty 'stats reset': do not reset stat_items
+ * utils: add osmo_str_to_int() and osmo_str_to_int64()
+ * add osmo_stat_item_get_group_by_name_idxname()
+ * CTRL: expose stat_item groups on CTRL
+ * stat_item: cosmetic: s/desc/group_desc in osmo_stat_item_group_alloc()
+ * stats_test: assert counter and stat item val counts separately
+ * refactor stat_item: get rid of FIFO and "skipped" error
+ * refactor stat_item: report only changed values
+ * cosmetic: get rid of 3 deprecation warnings
+ * revisit some calls of strtol(), stroul(), strtoull()
+ * add osmo_time_cc, moved from osmo-bsc
+
+ [ Eric ]
+ * kdf: add key derivation functions
+ * vty: allow flushing
+ * gsmtap: allow 127.0.0.x local listeners
+
+ [ Michael Iedema ]
+ * ns2: use same name in ctr_group as stat_item_group
+
+ [ Philipp Maier ]
+ * control_cmd: fix typo
+ * vty: make function cmd_range_match() public
+ * linuxlist: add macro to get last element of a list
+ * command: fix sourcecode formatting
+ * codec: add missing osmo_amr_type_name function.
+ * gsm0503_coding: use ahs tables when encoding ahs codec id
+ * rsl: add new RSL IE to signal temporary overpower
+ * bts_features: Add new feature to indicate support for temporary overpower
+ * gsm_08_58: fix sourcecode formatting
+ * gsm_08_58: fix sourcecode formatting
+
+ [ Keith ]
+ * GPRS: Add PDP_TYPE_N_IETF_IPv4v6
+
+ -- Pau Espin Pedrol <pespin@sysmocom.de> Tue, 16 Nov 2021 13:08:22 +0100
+
+libosmocore (1.5.1) unstable; urgency=medium
+
+ [ Pau Espin Pedrol ]
+ * osmo-release.sh: Fix rc!=0 on TODO-RELEASE file without comment lines
+
+ [ Harald Welte ]
+ * attempt to fix RPM spec file after recent soversion bump
+
+ -- Harald Welte <laforge@osmocom.org> Wed, 24 Feb 2021 09:35:21 +0100
+
+libosmocore (1.5.0) unstable; urgency=medium
+
+ [ Vadim Yanitskiy ]
+ * debian/control: change maintainer to the Osmocom team / mailing list
+ * vty/command: cosmetic: simplify conditions in in config_list_cmd
+ * vty/command: cosmetic: fix formatting of config_help_cmd
+ * vty/command: cosmetic: drop redundant line break
+ * vty/command: fix switch / case coding style in vty_go_parent()
+ * vty/command: cosmetic: swap i and j in vty_dump_element()
+ * gsm0808: add gsm0808_create_sapi_reject_cause()
+ * macaddr: fix osmo_get_macaddr(): return -1 if no device is found
+ * macaddr: fix osmo_macaddr_parse(): return meaningful error codes
+ * lapdm_pad_msgb(): cosmetic: use GSM_MACBLOCK_PADDING
+ * lapd_test: fix wrong comment in lapdm_establish()
+ * lapd_test: rename func=UA (RR) CM Service Request: s/ua/ua_cm/
+ * lapd_test: fix: print all messages to stdout, not stderr
+ * lapd_test: add a test checking SAPI0/SAPI3 prioritization
+ * lapdm: fix SAPI-0/SAPI-3 frame prioritization on DCCH
+ * logging: refactor and simplify log_target_destroy()
+ * logging: fix log_target_destroy(): properly close syslog
+ * logging: fix memleak in log_target_create_file()
+ * vty: fix 'Unsigned compared against 0' generate_cpu_hex_mask()
+ * gsm0808: fix: do not encode invalid encryption algorithm
+ * vty: add program specific attributes to VTY commands
+ * vty: print program specific attributes in the XML reference
+ * vty: cosmetic: drop redundant 'break' statements
+ * vty: cosmetic: s/width/cmd_width/g in vty_describe_command()
+ * vty: cosmetic: fix missing curly braces in vty_describe_command()
+ * vty/command: introduce new attribute CMD_ATTR_IMMEDIATE
+ * vty/command: reflect global attributes in the XML reference
+ * tests: do not ignore stderr of vty_test, also match it
+ * vty: check for duplicate flags in application specific attributes
+ * vty: check for reserved flags in application specific attributes
+ * vty/command: add global command attribute CMD_ATTR_NODE_EXIT
+ * vty/command: add CMD_ATTR_LIB_COMMAND and install() API wrappers
+ * vty: use install_lib_element() and install_lib_element_ve()
+ * vty/command: introduce API for the library specific attributes
+ * vty: introduce and use VTY_CMD_USR_ATTR_NUM
+ * vty/command: introduce a command to list attributes
+ * vty/command: print attribute flags in the output of 'list'
+ * tests/vty: verify 'show vty-attributes' / 'list' commands
+ * vty/command: assign flags to CMD_ATTR_{IMMEDIATE,NODE_EXIT}
+ * vty/command: restrict the use of '.', '!', and '@' as flags
+ * vty: fix vty_dump_element(): do not print empty <attributes>
+ * socket: make the arguments of osmo_sockaddr_cmp() const
+ * gprs_ns2: make struct osmo_sockaddr pointers const
+ * rsl: rsl_chan_nr_str_buf(): use ABIS_RSL_CHAN_NR_CBITS_* macros
+ * logging: introduce 'systemd-journal' target
+ * vty: introduce the expert mode and a command to enable it
+ * vty/command: make some 'struct cmd_element' pointers const
+ * vty/command: introduce vty_dump_xml_ref_mode()
+ * vty/command: add CMD_ATTR_HIDDEN to CMD_ATTR_PUBLIC_MASK
+ * application: do not document unrelated forward-declarations
+ * vty/command: fix: restrict the expert mode to the current session
+ * fix spelling in 'value_string' arrays: existAnt -> existEnt
+ * gsm48: add missing RR cause value definitions
+ * vty/command: add 'hidden only' VTY reference generation mode
+ * bts_features: add missing description for BTS_FEAT_ACCH_REP
+ * core/linuxlist: do not use 'new' as a parameter name
+ * protocol/gsm_08_58.h: add ip.access Power Control structures
+ * protocol/gsm_08_58.h: add Osmocom specific EWMA AVG algo
+ * logging: revert color of LGLOBAL category back to white
+ * gsm_08_58: fix wrong field order in 'struct ipac_preproc_pc_thresh'
+ * gsm_08_58: add flexible array member to 'struct ipac_preproc_ave_cfg'
+ * gsmtap_util: SNR can be negative, use a signed integer
+ * gprs_ns2_sns: always check rc of osmo_sockaddr_str_from_sockaddr()
+ * gprs_ns2_fr: fix resource leaks due to early return in set_ifupdown()
+ * gprs_bssgp: fix uninitialized struct fields in bssgp_create_rim_ri()
+ * bts_features: s/Repeation/Repetition/ in osmo_bts_features_descs[]
+ * gprs_bssgp: abuse gsm48_encode_ra() to encode TAC
+ * fixup: configure.ac: fix: do not define HAVE_NEON unconditionally
+ * gsm_7bit_encode_n(): test encoding of more than 250 septets
+ * gsm_7bit_encode_n(): fix integer overflow in gsm_septets2octets()
+ * gsm0808: use msgb_tv16_put() and osmo_store32be()
+ * bssgp_bvc_fsm: check return value of osmo_fsm_register()
+ * tlv: add msgb_tv32_put(), similar to msgb_tv16_put()
+ * tlv: clarify documentation for msgb_tv{16,32}_put()
+
+ [ Eric ]
+ * vty cpu sched: do not assert if sched impossible
+ * pkgconfig: link to mnl if available
+
+ [ Pau Espin Pedrol ]
+ * vty: Fix cpu-sched VTY node name
+ * sock: osmo_sock_init2_multiaddr: decouple addr resolution from socket creation
+ * socket: multiaddr: Support IPv4 + IPv6 addresses in SCTP associations
+ * socket: Log proper getaddrinfo() error
+ * osmo_sock_get_ip_and_port(): Support IPv6 sockets
+ * l1sap.h: Fix typo in doxygen documentation
+ * osmo_sock_inti2_multiaddr: Fix memleak and free uninitialized mem
+ * socker: Remove AI_ADDRCONFIG from getaddrinfo flags
+ * tests: Split SCTP tests to its own file and run them conditionally
+ * ipa: Fix wrong output log formatting
+ * socket: Allow binding to :: (IPv6) and connecting to IPv4-only on the remote
+ * socket: Add support for AF_INET6 in osmo_sockaddr_to_str_and_uint()
+ * socket: Use AF_UNSPEC instead of PF_UNSPEC calling getaddrinfo
+ * socket: Fix stack-buffer-overflow in osmo_sock_local_ip()
+ * socket: fix wrong ipv6 dst buf size passed in osmo_sock_local_ip
+ * socket: Add some osmo_sockaddr print helpers
+ * tests: Add test to showcase osmo_sock_init2 bug with AF_UNSPEC
+ * socket: Fix bug in osmo_sock_init2(AF_UNSPEC) matching IP versions
+ * logging: Avoid printing OSMO_LOGCOLOR_END if no color was used
+ * osmo_strlcpy: Avoid calling memcpy with size=0
+ * osmo_strlcpy: Clarify length calculation
+ * cosmetic: Fix typo in API doc
+ * gprs_ns2_udp: Avoid dangling freed struct in list if binding fails
+ * vty: Fix left shifting out of range on signed variable
+ * cosmetic: vty: Fix trailing whitespace
+ * gsm: Fix make distcheck with parallel make
+ * contrib: jenkins: Enable parallel make in make distcheck
+ * lapdm: Drop log lines printing fmt=B
+ * lapdm: Split lapd_rx_u() spaghetti into one function per message type
+ * lapdm: Allow SABM L=0 in Timer Recovery State
+ * gb: ns2_sns: Fix missing trailing newline char in log line
+ * gb: ns2_sns: Add missing value_string entry for GPRS_SNS_EV_NO_NSVC
+ * vty: Mark cpu_sched_vty commands with attr immediate
+ * tdef: Introduce OSMO_TDEF_US unit
+ * tests: Fix tdef_test on 32bit platforms
+ * bitvec: Fix left shifting out of range on signed variable
+ * cosmetic: serial: Fix typo in comment
+ * serial: Fix typo in debug log line
+ * serial: Log error if tcgetattr() or tcsetattr() fail
+ * serial: Introduce API osmo_serial_speed_t
+ * Revert "tests: Fix tdef_test on 32bit platforms"
+ * tests: Fix tdef_test on 32bit platforms
+ * statsd report: Fix wrong fmt specificier generating wrong stats
+ * gsm: Add enum for Network Feature Support IE
+ * Include mnl.h iif --enable-libmnl
+ * gb: Import mnl.h iif --enable-libmnl
+ * bssgp: Remove newly added log line warning about NOOP
+ * rest_octets: add Serving Cell Priority Parameters
+ * gsm: si13: Fix encode of EGPRS_PACKET_CHANNEL_REQUEST
+ * si2quater: fix budget calculation for multiple EARFCNs
+ * gsm: append_eutran_neib_cell: Fix SI2quater EARFCN list
+ * GPRS Cell Options (SI13): Add REL-4 CCN_ACTIVE bit
+ * Revert "rest_octets: fix encoding of 3G Early Classmark Sending Restriction"
+ * rest_octets: Fix decoding of SI3 3G Early Classmark Sending Restriction
+ * gitignore: Ignore *~
+ * ctrl: Allow handling CTRL get/set replies in user defined code
+ * Intoduce Packet Switch CGI
+ * ctrl: ports.h: Add OSMO_CTRL_PORT_BSC_NEIGH
+ * gsm: Add missing osmo_*_cmp symbols to libosmogsm.map
+ * gsm: Introduce osmo_{rai,cgi_ps}_cmp() APIs
+ * gprs_bssgp_prim.h: Add missing includes
+ * Revert "gprs_ns2: drop gprs_ns2_vty, rename vty2 -> vty"
+ * ctrl_connection: Initialize write_queue.bfd.fd to -1 during allocation
+ * cosmetic: fix typo in comment
+ * gsm: Fix wrong length in SI13 GPRS Cell Options IE
+ * gsm: bts_features: Introduce BTS_FEAT_CCN
+ * Fix struct bitfields on big endian systems
+ * cosmetic: Move comment one line below in append_gprs_cell_opt
+ * gsm: Fix bitfield order in dtap_header
+ * comsetic: gsm0808_test: Fix trailing whitespace
+ * gsm: Support converting to cgi-ps in gsm0808_cell_id_from_cgi()
+ * gsm0808_utils: Move static function further up in file
+ * gsm: Fix encoding of gsm0808_cell_id_list2 with CGI-PS types
+ * logging: gsmtap: Fill PID field for each message
+ * Introduce osmo_gettid() API
+ * logging: gsmtap: Fix fill PID field not stored in network byte order
+ * logging: gsmtap: Store TID instead of PID in pkt hdr
+ * gsm: Introduce API osmo_gsm48_rest_octets_si13_decode
+ * logging: Allow prefixing thread ID to each log line
+ * tests: Set print_category values explicitly
+ * Drop use of log_set_print_filename() API inside libosmocore
+ * logging: Deprecate API log_set_print_filename
+ * osmo-release.sh: Omit tab whitespace matching debian/control versions
+
+ [ Harald Welte ]
+ * gsmtap: Add definitions for E1/T1 payload (LAPD, TRAU, FR) in GSMTAP
+ * Add VTY + CTRL ports for upcoming OsmoSMLC
+ * gprs_ns2_sns: Fix compilation on Debian 8
+ * ipaccess.h: Add more enum values and 'official' names
+ * ns2: Improve/extend doxygen comments for new ns2 implementation
+ * ns2: Use NULL and not '0' when returning a NULL-Pointer
+ * gprs_ns2: Mark gprs_ns2_validate_* as static
+ * write_queue: Add osmo_wqueue_enqueue_quiet()
+ * write_queue: use msgb_{en,de}queue_count()
+ * write_queue: Re-enqueue msgb if write_cb returns -EAGAIN
+ * logging_vty: set osmo_stderr_target to NULL on "no log stderr"
+ * logging: Introduce MAX_LOG_SIZE for the magic number 4096
+ * bssgp: Don't include RA-ID in BVC-RESET for BVCI=0 (signalling)
+ * bssmap_le/bsslap tests: We must use %td for ptrdiff_t printing
+ * osmo_float_str_to_int: When using strtoll(), use LLONG_{MAX,MIN}
+ * gprs_ns2: Fix parsing of SNS-{ADD,DEL,CHANGE_WEIGHT}
+ * gsm48_rest_octets: Add parser for SI4 rest octets
+ * [cosmetic] rest_octets: Follow coding style regarding {}
+ * ns2: More verbose comments on gprs_ns2_vc_mode
+ * select: Introduce osmo_fd_{read,write}_{enable,disable}()
+ * select: Migrate over to poll()
+ * card_fs_usim.c: Add support for DF.5GS directory
+ * Revert "gsm_04_08: add parser for Mobile Station Classmark 3"
+ * card_fs_usim: Add definitions of DF.ProSe and DF.ACDC
+ * bssgp: Input argument to bssgp_tlv_parse() should be 'const'
+ * ns2: permit multiple nsvci in one nse in VTY
+ * frame_relay: Fix some spelling/language issues
+ * Revert "ns2: permit multiple nsvci in one nse in VTY"
+ * gprs_ns2_vty: Fix VTY documentation errors
+ * frame_relay: Fix null pointer dereference in DLC/PVC delete
+ * frame_relay: Fix error path on ioctl() failure
+ * gprs_ns2: Introduce gprs_ns2_lltype_str() for link layer name
+ * gprs_ns2_fr: Print Frame Relay 'role' in VTY
+ * gprs-ns2: Fix stringification of NS/FR NSVCI
+ * gprs_ns2: Don't return an empty string in case of unknown LL
+ * cosmetic: frame_relay: Fix typos
+ * gprs_ns2: Print link layer and global ALIVE/DEAD state
+ * gprs_ns2_vty: Differentiate 'show ns binds' and 'show ns entities'
+ * gprs_ns2_vty: Show NSVCI in dump_nsvc()
+ * gprs_ns_vty: Unify display of NSVCI on VTY in 'show ns entities'
+ * gprs_ns2_vty: Print all relevant data on each NS-VC
+ * gprs_ns2_udp.c: Fix typo - it's a DSCP and not a DCSP
+ * Integrate libmnl (minimal netlink) library with libosmocore select loop
+ * gprs_ns2_fr: Monitor the kernel net-device link state
+ * gsm_08_18.h: Update enums for message types and IEs with Release 15
+ * gprs_ns2_vty: Re-introduce a 'show ns' command
+ * libosmogb: Add missing LIBMNL_CFLAGS
+ * mnl: Use mnl_socket_open() insatead of mnl_socket_open2()
+ * gprs_ns2_fr: remove include <linux/if.h>
+ * bssgp: Use TLVP_PRES_LEN instead of TLVP_PRESENT
+ * gprs_ns2: Use TLVP_PRES_LEN instead of TLVP_PRESENT
+ * bssgp: Update bssgp_pdu_strings with Release 15
+ * bssgp: Fix typo in BSSGP Message Type enum
+ * Add hlist and hashtable from Linux kernel
+ * Use explicit type-casting in hlist_del() for C++ compatibility
+ * gsm_08_18.h: Add some PDU definitions still missing
+ * hash/log2: Add generic implementations of fls() and fls64()
+ * log2.h: Use uintXX_t instead of kernel specific types
+ * log2.h: Avoid redefining __always_inline
+ * ns2: Accept NS-UNBLOCK-ACK in UNBLOCKED state
+ * Introduce 'osmo_tlv_prot' abstraction for validation of TLV protocols
+ * tlv: Introduce enum with error codes for TLV parser functions
+ * bssgp: Add osmo_tlv_prot_def for BSSGP
+ * gsm_08_18.h: Add #defines for [extended] feature bits
+ * tlv.h: Add msgb_tvlv_put_{16,32}be()
+ * logging: Introduce DLBSSGP logging constant
+ * gb: Add beginnings of a new BSSGP implementation
+ * logging: Assing different 8bit colors to built-in subsystems
+ * bssgp2: Encoding + Decoding functions for BVC and MS flow control
+ * bssgp_bvc_fsm: Add basic BVC flow control rx/tx support
+ * gprs_ns2_fr.c: Skip extraneous FIONBIO
+ * gprs_ns2_fr: guard against race between socket(AF_PACKET) and bind()
+ * gprs_ns2_fr: use ETH_P_HDLC instead of ETH_P_ALL
+ * Fix VTY syntax for newly-introduced NS2 timers
+ * gors_ns2_vty: Fix saving of new NS2 timers
+ * gprs_ns2_fr: Avoid stringop-truncation warning
+ * gprs_ns2_fr: Use OSMO_STRLCPY_ARRAY() where possible
+ * fsm: Add osmo_fsm_inst_broadcast_children()
+ * vty: Fix left shifting out of range on signed variable
+ * lapd_core: Don't dereference data link after sending PRIM_DL_REL
+ * cbsp: Fix encoding of "ETWS Warning Security Info" IE
+ * cbsp: Fix osmo_cbsp_recv_buffered() for KEEP-ALIVE-COMPLETE
+ * cbsp: Fix parsing DCS in decode of WRITE-REPLACE
+ * Add inter-thread queue
+ * tlv_parser: Fix various out-of-bounds accesses
+ * gprs_ns2_fr: reduce duplication between gprs_ns2_fr_connect / connect2
+ * gprs_ns2: Give NS-VC FSMs a proper name/identifier
+ * ns2: delay NS_AFF_CAUSE_RECOVERY until NS-VC for data + sig are unblocked
+ * vty/fsm_vty: Add vty_out_fsm2() + vty_out_fsm_inst2() with prefix
+ * ns2: Properly indent VTY output
+ * ns2: Unify logging context via log macros
+ * logging_vty: Fix saving of "logging print file .. last"
+ * gprs_ns2_test: Fix compilation on Debian 8
+ * Revert "gprs_ns2_vc_fsm: check NSVCI match the NSE"
+ * Revert "gprs_ns2_vc_fsm: check NSEI match the NSE"
+ * ns2: Use proper return value from write_queue callback function
+ * ns2: Log ERROR if we cannot transmit a packet due to ENOBUFS
+ * osmo-ns-dummy: Add simple NS traffic generator
+ * ns2: Work around AF_PACKET socket ENOBUFS problems
+ * ns2: Use named array initializers to avoid mistakes
+ * ns2: encapsulate calls to nsvc->bind->send_vc()
+ * ns2: Increment Rx and Tx byte / packet counters
+ * ns2: Properly report packet drops in FR code
+ * ns2: count number of dropped packets / bytes on transmit
+ * ns2: Implement more rate counters
+ * ns2: Add a rate_ctr for each NS-UNBLOCK
+ * ns2: Move to one common/shared ns2_bind_alloc()
+ * ns2: Memory allocation failures are ENOMEM, not ENOSPC
+ * ns2: Rename nsi->rate_ctr_idx to nsi->nsvc_rate_ctr_idx
+ * ns2: Introduce a per-bind stat_item group with backlog length
+ * ns2: Fix typos in comments
+ * ns2: Print NS-STATUS.ind primitives to the log
+ * ns2: cosmetic: fix indent levels
+ * frame_relay: Send "Fuil Status" ENQUIRY after link recovers
+ * frame_relay: Add status call-backs for link + DLC status changes
+ * frame_relay: Discard received messages for DLC not yet active
+ * frame_relay: cosmetic: Unify log syntax when discarding Rx packets
+ * ns2: Don't automatically re-start FSM at FORCE_UNCONFIGURED
+ * ns2: Stop test procedure when going into unconfigured state
+ * ns2: Don't start sending NS-RESET until FR DLC is available
+ * osmo-ns-dummy: Add "mirror-mode" to mirror back any received packets
+ * initial support for static userspace probes via systemtap
+ * Enable systemd-journald log target in debian + rpm packaging
+ * gb: frame_relay: Factor-out function to set link as dead
+ * gb: frame_relay: Detect link outage on "last receive seq nr == 0"
+ * frame_relay; Fix Q.933 async STATUS at DLC creation
+ * ns2: Name NSVC FSM events consistently
+ * ns2: Don't queue Q.933 LMI messages; only store most recent ones
+ * ns2: improve backlog handling on interface up/down
+ * ns2: Don't try to add packets to the backlog on real errors
+ * README.md: Use https everywhere
+ * ns2_fr: Fix null pointer deref in error path
+ * ns2_fr: Fix heap-use-after-free in error recovery path
+ * BVC FSM: Treat overlapping BVC-RESET as implicit ACK
+ * support for stats static userspace probes via systemtap
+ * CBSP: fix encoding/decoding of keep-alive repetition period
+ * CBSP: Fix encoding of warning period
+
+ [ Neels Hofmeyr ]
+ * comment: mention spec reference in cbsp.[ch]
+ * comment: typo in cbsp.h
+ * bitXXgen: ensure not reading/storing past valid size
+ * bitXXgen: add bitgen_test.c
+ * bitXXgen: add osmo_loadXXbe_ext_2() to get right-adjusted values
+ * add osmo_use_count_to_str_c()
+ * osmo_use_count_to_str: make robust against unused use_count
+ * add osmo_float_str_to_int() and osmo_int_to_float_str_*()
+ * add GAD coding for Location Services
+ * add BSSLAP coding for Location Services
+ * add BSSMAP-LE coding for Location Services
+ * add BSSMAP coding for Location Services
+ * doc tweaks for osmo_float_str_to_int(), osmo_int_to_float_str*()
+ * gad.c: try to workaround warning for "h.type >= 0"
+ * gad.c: fix rc for osmo_gad_enc_ell_point_unc_circle()
+ * gprs_ns2_fr.c: compiler error: replace strncpy() with OSMO_STRLCPY_ARRAY()
+ * fix strncpy bug in gprs_ns2_fr_bind()
+
+ [ Alexander Couzens ]
+ * sockaddr_str: add osmo_sockaddr_str_from_str2() which doesn't set the port
+ * tests/socket: add testcase test_get_ip_and_port
+ * socket: introduce osmo_sock_init_osa & osmo_sock_init_osa_ofd
+ * socket: add osmo_sockaddr_cmp()
+ * gb/gprs_bssgp: remove superfluous whitespace
+ * gb/common_vty: use void *
+ * add osmo_sockaddr_local_ip() to determine the local address for a remote.
+ * Gb: add a second NS implementation
+ * libgb/ns: allow to create NS_ALIVE NSVC
+ * gb/gprs_bssgb: check if talloc failed on btsctx->fc
+ * gb/gprs_bssgb: ensure the fc timer has been stopped when freeing bssgp_bvc
+ * ns2: remove bssgp specific msgb->cb parts
+ * ns2: vty: add missing docs for IPv6 address
+ * ns2: check the specific bit of NS SDU Control bits
+ * ns2: refactor nsvc_by_ functions
+ * ns2: ns2_recv_vc: remove unused parameter nsi
+ * ns2: vty: fix behavior of vtyvc_by_nsei when vtyvc isn't found
+ * ns2: refactor handle_nsip_read/handle_nsfrgre_read
+ * ns2: fix a msg leak when receiving REJECTED messages
+ * gsm_12_21: add osmo_oml_nsvc_address_type for OML NM_ATT_OSMO_NS_LINK_CFG
+ * ns2: parse the return code of gprs_ns2_ip_bind
+ * ns2: refactor ns2_prim_status_ind()
+ * ns2: status ind: add additional flags (first and persistent)
+ * ns2: vty: show all nse
+ * ns2: vty: allow the users (pcu/sgsn) to set a default bind
+ * ns2: vty: on `show ns` add information of NS binds
+ * gprs_ns2_vty_create: remove bind pointer check
+ * gprs_ns2: gprs_ns2_free(): add missing talloc_free
+ * gprs_ns2: fix SNS_ADD for IPv4
+ * gprs_ns2: fix empty prefix in TLV Parse error
+ * gprs_ns2: add gprs_ns2_ip_bind_by_sockaddr() to search for binds by sockaddr
+ * gprs_ns2: gprs_ns2_ip_bind() check if the bind already exists
+ * gprs_ns2: add gprs_ns2_nse_nsei() to get the nsei of a nse
+ * gprs_ns2: add gprs_ns2_free_binds() to free all binds
+ * gprs_ns2: add gprs_ns2_free_nses() to free all NS-E
+ * gprs_ns2: const the return value of gprs_ns2_ip_vc_sockaddr / gprs_ns2_ip_bind_sockaddr
+ * gprs_ns2: rename gprs_ns2_ip_vc_sockaddr -> gprs_ns2_ip_vc_remote
+ * gprs_ns2: add gprs_ns2_ip_vc_local() return the local sockaddr
+ * gprs_ns2: add gprs_ns2_nse_sns_remote() returns the initial SNS address
+ * gprs_ns2: gprs_ns2_ip_vc_remote() the nsvc can be also const
+ * gprs_ns2: add gprs_ns2_nse_foreach_nsvc()
+ * gprs_ns2: Partial revert 48f63867 allow to create NS_ALIVE NSVC
+ * gprs_ns2: add gprs_ns2_ip_vc_equal()
+ * add osmo_sockaddr_to_str_buf/osmo_sockaddr_to_str
+ * gprs_ns2: remove the nsvc fsm timer when cleaning up the fsm
+ * ns2: add value strings for ns2_affecting_cause_prim_str & ns2_prim_str
+ * gprs_ns2: convert gprs_ns2_cause_str into an static inline
+ * libosmocore: change the memory management of NS2
+ * ns2: fixup gprs_ns2_prim_strs
+ * ns2: check if ns_vc_alloc() fails in bind_connect()
+ * ns2: add support for frame relay
+ * ns2: implement link sharing selector
+ * ns2: move LL into public api
+ * ns2: move link layer type into NSE
+ * ns2: remove obsolete type GPRS_NS2_LL_E1
+ * gprs_ns2_fr: remove include <linux/if.h>
+ * gprs_ns2: fix typo in comment
+ * gprs_ns2: use switch() case instead of multiple if in ns2_create_vc()
+ * gprs_ns2_sns: add missing transistion UNCONFIGURED -> SIZE
+ * gprs_ns2_sns: dynamic calculate the maximum NS-VCs
+ * gprs_ns2_sns: add missing S() to allow GPRS_SNS_EV_NO_NSVC happen
+ * gprs_ns2_sns: fix whitespaces and superflous comment
+ * gprs_ns2_sns: correct dynamic calculation
+ * gprs_ns2: introduce NS dialects
+ * gprs_ns2: move allocation of the SNS fsm into create_nse
+ * gprs_ns2: add member name to bind
+ * gprs_ns2: rework gprs_ns2_fr_connect*()
+ * gprs_ns2: add gprs_ns2_free_nsvcs() to free all NS-VC of a NSE
+ * gprs_ns2_sns: rework IP-SNS initial remote
+ * gprs_ns2_sns: use different binds for the initial connection
+ * gprs_ns2_sns: introduce SNS Size/Config retries
+ * gprs_ns2: make nsvc argument const
+ * gprs_ns2: add gprs_ns2_fr_bind_role() to retrieve the fr role
+ * gprs_ns2: on ns2_create_vc parse the tlv before using it
+ * gprs_ns2_vty: udp: fixup dialect changes
+ * gprs_ns2: fr: fix crash when frame relay interface doesn't exists
+ * gprs_ns2: improve handling of TLV errors on new nsvcs
+ * gprs_ns2: don't pass the return code of reject_status_msg
+ * gprs_ns2: fr: implement a write queue
+ * gprs_ns2: fr: setup the device to correct FR/LMI settings
+ * gprs_ns2: add new vty2
+ * gprs_ns2: sns: correct log message when no nsvcs available
+ * gprs_ns2: sns: don't send duplicated packets on retries
+ * gprs_ns2: sns: add log message when size/config retries exhausted
+ * gprs_ns2: don't start unconfigured fsm via FORCE_UNCONFIGURED
+ * gprs_ns2: fix force-unconfigured for IP-SNS NSE
+ * frame_relay: prevent null pointer exception when talloc fails
+ * frame_relay: link_alloc: move log message to the end
+ * gprs_ns2: rework frame relay load distribution function
+ * gprs_ns2_fr: setup_device: allow to setup a new dahdi device
+ * gprs_ns2: fr: check the device state before changing state
+ * gprs_ns2: use zero initialized memory for vty_binds
+ * utils: add osmo-ns-dummy
+ * gprs_ns2: call python vty tests
+ * gprs_ns2: set transfer cap in NS Status primitive
+ * gprs_ns2: check if persistent nsei or nsvc exists when creating dynamic NSE
+ * osmo-ns-dummy: allow to create dynamic NSEs
+ * gprs_ns2_vc: answer UNBLOCK on unblocked nsvc
+ * gprs_ns2: when calling nsvc_force_unconf for a dynamic NSE drop the NSE.
+ * gprs_ns2_sns: clear local and remote entries when SNS failed
+ * gprs_ns2: flag then NSE as dead in nse_free()
+ * gprs_ns2_vc_fsm: fix spaces and remove wrong comment
+ * test: gprs_ns2: don't leak the talloc context
+ * test: gprs_ns2: replace free_bind() with clear_pdus()
+ * test: gprs_ns2: free the nsi after each test
+ * gprs_ns2: add assert on most bind calls
+ * gprs_ns2: allow to use free_vc() with NULL
+ * osmo-ns-dummy: don't leak primitive messages
+ * gprs_ns2: implement BLOCK/UNBLOCK of a NSVC by vty
+ * gprs_ns2: correct handle BLOCK message on initator
+ * gprs_ns2_vc_fsm: rename all event to match RX_ or REQ_
+ * gprs_ns2_vc_fsm: ensure all state is resetted via force_unconf
+ * gprs_ns2_vc_fsm: in RESET accept RESET as ACK
+ * gprs_ns2_vc_fsm: fix transitions for ALIVE state
+ * gprs_ns2_message: remove wrong comment
+ * gprs_ns2_vc_fsm: check NSEI match the NSE
+ * gprs_ns2_vc_fsm: check NSVCI match the NSE
+ * tests: gprs_ns2: add unitdata unit test
+ * gprs_ns2: always pass a tp to gprs_ns2_vc_rx
+ * gprs_ns2_vc_fsm: check NSEI match the NSE
+ * gprs_ns2_vc_fsm: check NSVCI match the NSE
+ * gprs_ns2: drop GPRS_ prefix of gprs_ns2_cs
+ * gprs_ns2: drop prefix of all internal exposed function
+ * gprs_ns2: drop the public prefix gprs_ from all static functions/structs/..
+ * gprs_ns2: refactor: ensure all enums have GPRS_NS2_
+ * gprs_ns2: drop gprs_ns2_vty, rename vty2 -> vty
+ * gprs_ns2: drop gprs_ns2_vty, rename vty2 -> vty
+ * gprs_ns2: remove api call gprs_ns2_dynamic_create_nse
+ * gprs_ns2: introduce gprs_ns2_vty_init_reduced() for the PCU
+ * gprs_ns2: correct vty config write
+ * gprs_ns2: prevent division by zero in load_sharing
+ * gprs_ns2_vc_fsm: reset ALIVE N counter when restarting the test procedure.
+ * gprs_ns2_vc_fsm: rename ST_ALIVE -> ST_RECOVERING
+ * gprs_ns2: unify the handling of **result when bind already present.
+ * gprs_ns2_fr: free_bind(): first do the NULL check before using members
+ * frame_relay: fix NULL pointer deref
+ * gprs_ns2_frgre: set a correct gre pointer for ipv6
+ * gprs_ns2_frgre: check iph/ip6h before passing them to rx functions
+ * gprs_ns2_sns: rework tracking of NS-VC unblocked/alive state
+ * gprs_ns2_sns: add timeout to ST_CONFIG_SGSN
+ * gprs_ns2_sns: reset the N of timeout when entering a new state
+ * gprs_ns2: rename vty-command ip-sns -> ip-sns-remote
+ * gprs_ns2_vty: fix memory leak of vty_binds
+ * gprs_ns2: add signalling & data weights for UDP binds
+ * gprs_ns2_vc_fsm: use CLOCK_MONOTONIC for alive elapsed timer
+ * gprs_ns2_vc_fsm: reset the alive elapsed timeout everytime
+ * gprs_ns2_vty: use strcmp() instead of strncmp()
+ * gprs_ns2_vc_fsm: remove debug line of the alive_timer
+ * gprs_ns2: fix comment of alive NS-VC
+ * gprs_ns2_sns: fix typo in doxygen comment
+ * gprs_ns2: inform the NS user (BSSGP) about the MTU of a NSE
+ * gprs_ns2: rework IP-SNS binds
+ * gprs_ns2_vty: add optional argument signalling and data weights to `nsvc udp`
+ * gprs_ns2: implement a simple load sharing for UDP
+ * gprs_ns2: fix typo in function name ns2_load_sharing_modulo
+ * gprs_ns2: make gprs_ns2_recv_prim() always take msgb ownership
+ * gprs_ns2_sns: rename fsm events to include RX or REQ prefix
+ * gprs_ns2_fr: pass MTU changes to the NSE
+ * gprs_ns2: truncate the NS_STATUS to the MTU
+ * gprs_ns2_message: tx_status: move all cause dependent code into the switch/case
+ * gprs_ns2: add value_string for GPRS_NS2_AFF_CAUSE_SNS_NO_ENDPOINTS
+ * gprs_ns2: ensure no duplicate UDP NSVC can be created
+ * gprs_ns2: free_nse: free the SNS fsm early
+
+ [ Daniel Willmann ]
+ * gsm_08_16.h: Add missing header
+ * gprs_ns2: Make reason const
+ * libosmogb: Add a function to tx BVC RESET by nsei/bvci
+ * Add exported function btsctx_alloc to public header
+ * ns2: Add gprs_ns2_nsvc_state_name() to get the current state of a VC
+ * ns2: Send NSVC representation in NS_AFF_CAUSE_VC_* status indication
+ * ns2: Improve NSVC output
+ * ns2: Split 'initiater' into initiate_{reset,block}
+ * ns2: Add a function to set the NSVC FSM back to unconfigured
+ * ns2: Add a VTY command to reset NSVC FSM
+ * ns2: Fix docs for some NS2 vty commands
+ * logging: Calculate LOG_MAX_{CTX,FILTERS} from the enum
+ * ns2: Add log filtering by NSE/NSEI, fix NSVC filter on receive
+ * ns2: Fix argv index in logging filter VTY commands
+ * Declare osmo_ctx_init() in talloc.h
+ * bssgp: Add SUSPEND_NACK to osmo_pdef_bssgp
+ * logging: Remove duplicate color for DLSMS
+ * ns2_frgre: Fix missing break statement for recv from IPV6
+ * bssgp_bvc_fsm: Handle block request from application correctly
+ * NS2: Fix bind selection of SNS NSVCs
+ * ns2: Add sanity check
+ * Refactor ns2_nsvc_create_ip*
+ * ns2: Fix memory leak in IP-SNS
+ * ns2: Fix assert when removing a bind listen
+ * ns2: Fix incompatible VTY configs when writing config
+ * tests/gb: Add more complex osmo-ns-dummy.cfg
+ * bssgp_bvc_fsm: Set/get maximum BSSGP PDU length
+ * bssgp2_enc_status: Truncate STATUS message to maximum PDU length
+ * bssgp_bvc_fsm: Consistent naming
+
+ [ Philipp Maier ]
+ * command: add library command attribute for libosmo-sccp
+ * command: add library command attribute for libosmo-abis
+ * gsm_08_58: add proprietary IE to signal Repeated ACCH Capability
+ * gsm_04_08: add parser for Mobile Station Classmark 3
+ * gsm_04_08: add parser for Mobile Station Classmark 3
+ * bts_features: add feature BTS_FEAT_ACCH_REP
+ * gsm_08_58: add struct for RSL_IE_OSMO_REP_ACCH_CAP
+ * gsm_08_58: add rxqual field to RSL_IE_OSMO_REP_ACCH_CAP
+ * gprs_bssgp_util: complete bssgp_pdu_strings
+ * gprs_bssgp: add IE parser/generator for RIM Routing Information
+ * gprs_bssgp: add handling for BSSGP RIM primitives
+ * gsm_08_18: add struct to parse RIM PDU Indications
+ * bssgp_rim: add encoder/decoder for NACC related RIM containers
+ * bssgp_rim: move bssgp_parse_rim_ri and bssgp_create_rim_ri to gprs_bssgp_rim
+ * gprs_bssgp: add utilities to send and parse BSSGP rim PDUs
+ * gprs_bssgp_rim: Return with EOPNOTSUPP on unsupported containers
+ * gprs_bssgp_rim: fix bug in dub_tlvp_header()
+ * gprs_bssgp_rim: add value strings for enum bssgp_nacc_cause
+ * gprs_bssgp_rim: add value strings for enum bssgp_ran_inf_app_id
+ * gprs_bssgp_rim: add value strings for enum bssgp_rim_routing_info_discr
+ * gprs_bssgp_rim: add functions to convert a RIM-RI to a string
+ * gprs_bssgp_rim: cosmetic: connect routing identifier strings with "-"
+ * gprs_bssgp: log source and destination RIM routing information
+ * gprs_bssgp: agregate RIM related code in gprs_bssgp_rim.c
+ * gsm48: add compare function for struct gprs_ra_id
+ * gsm_08_58, gsm_44_004: add struct for l1 information
+
+ [ laforge ]
+ * Revert "pkgconfig: link to mnl if available"
+
+ [ Oliver Smith ]
+ * tests/*: fix control reaches end of non-void func
+ * configure.ac: set -std=gnu11
+ * sim: fix gcc 4.9.2 + -std=gnu11 error
+
+ -- Pau Espin Pedrol <pespin@espeweb.net> Tue, 23 Feb 2021 14:03:31 +0100
+
libosmocore (1.4.0) unstable; urgency=medium
[ Pau Espin Pedrol ]
diff --git a/debian/control b/debian/control
index 381e2914..8f3a1ef0 100644
--- a/debian/control
+++ b/debian/control
@@ -14,13 +14,15 @@ Build-Depends: debhelper (>= 9),
doxygen,
libpcsclite-dev,
pkg-config,
- libtalloc-dev,
+ libtalloc-dev (>= 2.1.0),
libsctp-dev,
libusb-1.0-0-dev,
+ libmnl-dev,
+ libsystemd-dev,
python3:native
Standards-Version: 3.9.8
-Vcs-Git: git://git.osmocom.org/libosmocore.git
-Vcs-Browser: http://git.osmocom.org/libosmocore/
+Vcs-Git: https://gitea.osmocom.org/osmocom/libosmocore
+Vcs-Browser: https://gitea.osmocom.org/osmocom/libosmocore
Homepage: https://projects.osmocom.org/projects/libosmocore
Package: libosmocore
@@ -29,10 +31,11 @@ Architecture: any
Multi-Arch: foreign
Depends: libosmocodec0 (= ${binary:Version}),
libosmocoding0 (= ${binary:Version}),
- libosmocore16 (= ${binary:Version}),
- libosmogb11 (= ${binary:Version}),
- libosmogsm15 (= ${binary:Version}),
- libosmovty4 (= ${binary:Version}),
+ libosmocore20 (= ${binary:Version}),
+ libosmogb14 (= ${binary:Version}),
+ libosmogsm18 (= ${binary:Version}),
+ libosmoisdn0 (= ${binary:Version}),
+ libosmovty9 (= ${binary:Version}),
libosmoctrl0 (= ${binary:Version}),
libosmosim2 (= ${binary:Version}),
libosmousb0 (= ${binary:Version}),
@@ -113,7 +116,7 @@ Description: Documentation for the osmo coding library
.
This package contains the documentation for the libosmocoding library.
-Package: libosmocore16
+Package: libosmocore20
Section: libs
Architecture: any
Multi-Arch: same
@@ -127,19 +130,24 @@ Description: Osmo Core library
(at least) other programs that are developed in the sphere of Free Software /
Open Source mobile communication.
.
- The libosmocore16 library in particular is a collection of common code used in
+ The libosmocore library in particular is a collection of common code used in
various sub-projects inside the Osmocom family of projects.
Package: libosmocore-doc
Architecture: all
Section: doc
Depends: ${misc:Depends},
- libosmocore16,
+ libosmocore20,
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
@@ -149,7 +157,7 @@ Description: Documentation for the Osmo Core library
.
This package contains the documentation for the libosmocore library.
-Package: libosmogb11
+Package: libosmogb14
Section: libs
Architecture: any
Multi-Arch: same
@@ -170,7 +178,7 @@ Package: libosmogb-doc
Architecture: all
Section: doc
Depends: ${misc:Depends},
- libosmogb11,
+ libosmogb14,
libjs-jquery
Description: Documentation for the Osmo GPRS Gb library
This is part of the libosmocore "meta"-library. The libosmocore library
@@ -181,7 +189,7 @@ Description: Documentation for the Osmo GPRS Gb library
.
This package contains the documentation for the libosmogb library.
-Package: libosmogsm15
+Package: libosmogsm18
Section: libs
Architecture: any
Multi-Arch: same
@@ -205,7 +213,7 @@ Package: libosmogsm-doc
Architecture: all
Section: doc
Depends: ${misc:Depends},
- libosmogsm15,
+ libosmogsm18,
libjs-jquery
Description: Documentation for the Osmo GSM utility library
This is part of the libosmocore "meta"-library. The libosmocore library
@@ -216,7 +224,40 @@ Description: Documentation for the Osmo GSM utility library
.
This package contains the documentation for the libosmogsm library.
-Package: libosmovty4
+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: libosmovty9
Section: libs
Architecture: any
Multi-Arch: same
@@ -237,7 +278,7 @@ Package: libosmovty-doc
Architecture: all
Section: doc
Depends: ${misc:Depends},
- libosmovty4,
+ libosmovty9,
libjs-jquery
Description: Documentation for the Osmo VTY library
This is part of the libosmocore "meta"-library. The libosmocore library
@@ -295,6 +336,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
@@ -312,12 +368,30 @@ 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
Section: libdevel
Depends: libosmocore,
- libtalloc-dev,
+ 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 b119569a..9281f03d 100644
--- a/debian/copyright
+++ b/debian/copyright
@@ -1,6 +1,6 @@
Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: libosmocore
-Source: git://git.osmocom.org/libosmocore.git
+Source: https://gitea.osmocom.org/osmocom/libosmocore
Files: *
Copyright: 2008 Daniel Willmann <daniel@totalueberwachung.de>
@@ -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/libosmocore-utils.install b/debian/libosmocore-utils.install
index d23cc73a..9501bec9 100644
--- a/debian/libosmocore-utils.install
+++ b/debian/libosmocore-utils.install
@@ -1,3 +1,4 @@
usr/bin/osmo-arfcn
usr/bin/osmo-auc-gen
+usr/bin/osmo-aka-verify
usr/bin/osmo-config-merge
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/libosmocore16.install b/debian/libosmocore20.install
index b73331b9..b73331b9 100644
--- a/debian/libosmocore16.install
+++ b/debian/libosmocore20.install
diff --git a/debian/libosmogb11.install b/debian/libosmogb14.install
index 4c474255..4c474255 100644
--- a/debian/libosmogb11.install
+++ b/debian/libosmogb14.install
diff --git a/debian/libosmogsm15.install b/debian/libosmogsm18.install
index 5e617298..5e617298 100644
--- a/debian/libosmogsm15.install
+++ b/debian/libosmogsm18.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/libosmovty4.install b/debian/libosmovty9.install
index fbf6a5fa..fbf6a5fa 100644
--- a/debian/libosmovty4.install
+++ b/debian/libosmovty9.install
diff --git a/debian/rules b/debian/rules
index afff17fa..91d56789 100755
--- a/debian/rules
+++ b/debian/rules
@@ -26,7 +26,7 @@ override_dh_auto_test:
dh_auto_test || (find . -name testsuite.log -exec cat {} \; ; false)
override_dh_auto_configure:
- dh_auto_configure -- --enable-static --disable-sctp-tests
+ dh_auto_configure -- --enable-static --disable-sctp-tests --enable-systemd-logging
override_dh_clean:
dh_clean
@@ -40,16 +40,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 a6341f1a..3578a80e 100644
--- a/include/Makefile.am
+++ b/include/Makefile.am
@@ -1,196 +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/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/isdnhdlc.h \
- osmocom/core/linuxlist.h \
- osmocom/core/linuxrbtree.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/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/timer.h \
- osmocom/core/timer_compat.h \
- osmocom/core/utils.h \
- osmocom/core/write_queue.h \
- osmocom/core/sockaddr_str.h \
- osmocom/core/use_count.h \
- osmocom/crypt/auth.h \
- osmocom/crypt/gprs_cipher.h \
- osmocom/ctrl/control_cmd.h \
- osmocom/ctrl/control_if.h \
- osmocom/ctrl/ports.h \
- osmocom/gprs/gprs_bssgp.h \
- osmocom/gprs/gprs_bssgp_bss.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/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/gsm0808.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/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_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_29_118.h \
- osmocom/gsm/protocol/gsm_44_318.h \
- osmocom/gsm/protocol/gsm_48_049.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
-
-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 cbdad75e..58e3a296 100644
--- a/include/osmocom/codec/codec.h
+++ b/include/osmocom/codec/codec.h
@@ -47,6 +47,9 @@ enum osmo_amr_type {
AMR_NO_DATA = 15,
};
+static inline const char *osmo_amr_type_name(enum osmo_amr_type type)
+{ return get_value_string(osmo_amr_type_names, type); }
+
enum osmo_amr_quality {
AMR_BAD = 0,
AMR_GOOD = 1
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_amr_dtx.h b/include/osmocom/coding/gsm0503_amr_dtx.h
index f048a6e3..dc22ec7e 100644
--- a/include/osmocom/coding/gsm0503_amr_dtx.h
+++ b/include/osmocom/coding/gsm0503_amr_dtx.h
@@ -34,7 +34,14 @@ static inline const char *gsm0503_amr_dtx_frame_name(enum gsm0503_amr_dtx_frames
return get_value_string(gsm0503_amr_dtx_frame_names, frame);
}
-enum gsm0503_amr_dtx_frames gsm0503_detect_afs_dtx_frame(int *n_errors, int *n_bits_total, const ubit_t *ubits);
-enum gsm0503_amr_dtx_frames gsm0503_detect_ahs_dtx_frame(int *n_errors, int *n_bits_total, const ubit_t *ubits);
+enum gsm0503_amr_dtx_frames gsm0503_detect_afs_dtx_frame(int *n_errors, int *n_bits_total, const ubit_t *ubits)
+ OSMO_DEPRECATED("Use gsm0503_detect_afs_dtx_frame2() instead");
+enum gsm0503_amr_dtx_frames gsm0503_detect_ahs_dtx_frame(int *n_errors, int *n_bits_total, const ubit_t *ubits)
+ OSMO_DEPRECATED("Use gsm0503_detect_ahs_dtx_frame2() instead");
+
+enum gsm0503_amr_dtx_frames gsm0503_detect_afs_dtx_frame2(int *n_errors, int *n_bits_total,
+ int *mode_id, const sbit_t *sbits);
+enum gsm0503_amr_dtx_frames gsm0503_detect_ahs_dtx_frame2(int *n_errors, int *n_bits_total,
+ int *mode_id, const sbit_t *sbits);
/*! @} */
diff --git a/include/osmocom/core/Makefile.am b/include/osmocom/core/Makefile.am
new file mode 100644
index 00000000..e1cd92ae
--- /dev/null
+++ b/include/osmocom/core/Makefile.am
@@ -0,0 +1,94 @@
+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 \
+ 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 \
+ $(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' $< > $@
diff --git a/include/osmocom/core/application.h b/include/osmocom/core/application.h
index edf59ed4..67a59088 100644
--- a/include/osmocom/core/application.h
+++ b/include/osmocom/core/application.h
@@ -2,17 +2,14 @@
#include <osmocom/core/defs.h>
+struct log_info;
+struct log_target;
+
/*!
* \file application.h
* Routines for helping with the osmocom application setup.
*/
-/*! information containing the available logging subsystems */
-struct log_info;
-
-/*! one instance of a logging target (file, stderr, ...) */
-struct log_target;
-
/*! the default logging target, logging to stderr */
extern struct log_target *osmo_stderr_target;
diff --git a/include/osmocom/core/base64.h b/include/osmocom/core/base64.h
new file mode 100644
index 00000000..a6c97206
--- /dev/null
+++ b/include/osmocom/core/base64.h
@@ -0,0 +1,69 @@
+/**
+ * \file base64.h
+ *
+ * \brief RFC 1521 base64 encoding/decoding
+ *
+ * Copyright (C) 2006-2015, ARM Limited, All Rights Reserved
+ *
+ * This file is part of mbed TLS (https://tls.mbed.org)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+#pragma once
+
+#include <stddef.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * \brief Encode a buffer into base64 format
+ *
+ * \param dst destination buffer
+ * \param dlen size of the destination buffer
+ * \param olen number of bytes written
+ * \param src source buffer
+ * \param slen amount of data to be encoded
+ *
+ * \return 0 if successful, or MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL.
+ * *olen is always updated to reflect the amount
+ * of data that has (or would have) been written.
+ *
+ * \note Call this function with dlen = 0 to obtain the
+ * required buffer size in *olen
+ */
+int osmo_base64_encode(unsigned char *dst, size_t dlen, size_t *olen,
+ const unsigned char *src, size_t slen);
+
+/**
+ * \brief Decode a base64-formatted buffer
+ *
+ * \param dst destination buffer (can be NULL for checking size)
+ * \param dlen size of the destination buffer
+ * \param olen number of bytes written
+ * \param src source buffer
+ * \param slen amount of data to be decoded
+ *
+ * \return 0 if successful, MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL, or
+ * MBEDTLS_ERR_BASE64_INVALID_CHARACTER if the input data is
+ * not correct. *olen is always updated to reflect the amount
+ * of data that has (or would have) been written.
+ *
+ * \note Call this function with *dst = NULL or dlen = 0 to obtain
+ * the required buffer size in *olen
+ */
+int osmo_base64_decode(unsigned char *dst, size_t dlen, size_t *olen,
+ const unsigned char *src, size_t slen);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/include/osmocom/core/bitXXgen.h.tpl b/include/osmocom/core/bitXXgen.h.tpl
index 258fccb6..ab54ba9c 100644
--- a/include/osmocom/core/bitXXgen.h.tpl
+++ b/include/osmocom/core/bitXXgen.h.tpl
@@ -14,10 +14,6 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#pragma once
diff --git a/include/osmocom/core/bitcomp.h b/include/osmocom/core/bitcomp.h
index 5faa5ea4..21679577 100644
--- a/include/osmocom/core/bitcomp.h
+++ b/include/osmocom/core/bitcomp.h
@@ -17,10 +17,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#pragma once
diff --git a/include/osmocom/core/bitvec.h b/include/osmocom/core/bitvec.h
index bd107093..70b0e27d 100644
--- a/include/osmocom/core/bitvec.h
+++ b/include/osmocom/core/bitvec.h
@@ -14,10 +14,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#pragma once
@@ -77,7 +73,7 @@ char bit_value_to_char(enum bit_value v);
void bitvec_to_string_r(const struct bitvec *bv, char *str);
void bitvec_zero(struct bitvec *bv);
unsigned bitvec_rl(const struct bitvec *bv, bool b);
-unsigned bitvec_rl_curbit(struct bitvec *bv, bool b, int max_bits);
+unsigned bitvec_rl_curbit(struct bitvec *bv, bool b, unsigned int max_bits);
void bitvec_shiftl(struct bitvec *bv, unsigned int n);
int16_t bitvec_get_int16_msb(const struct bitvec *bv, unsigned int num_bits);
unsigned int bitvec_add_array(struct bitvec *bv, const uint32_t *array,
diff --git a/include/osmocom/core/conv.h b/include/osmocom/core/conv.h
index 8b344f4d..84ef0f85 100644
--- a/include/osmocom/core/conv.h
+++ b/include/osmocom/core/conv.h
@@ -14,10 +14,6 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
/*! \defgroup conv Convolutional encoding and decoding routines
@@ -128,6 +124,7 @@ int osmo_conv_decode_scan(struct osmo_conv_decoder *decoder,
const sbit_t *input, int n);
int osmo_conv_decode_flush(struct osmo_conv_decoder *decoder,
const sbit_t *input);
+int osmo_conv_decode_get_best_end_state(struct osmo_conv_decoder *decoder);
int osmo_conv_decode_get_output(struct osmo_conv_decoder *decoder,
ubit_t *output, int has_flush, int end_state);
diff --git a/include/osmocom/core/counter.h b/include/osmocom/core/counter.h
index dc627918..7b677cb1 100644
--- a/include/osmocom/core/counter.h
+++ b/include/osmocom/core/counter.h
@@ -31,7 +31,7 @@ static inline void osmo_counter_inc(struct osmo_counter *ctr)
}
/*! Get current value of counter */
-OSMO_DEPRECATED("Implement as osmo_stat_item instead")
+OSMO_DEPRECATED_OUTSIDE("Implement as osmo_stat_item instead")
static inline unsigned long osmo_counter_get(struct osmo_counter *ctr)
{
return ctr->value;
@@ -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/crcXXgen.h.tpl b/include/osmocom/core/crcXXgen.h.tpl
index 823f21f2..13a3623e 100644
--- a/include/osmocom/core/crcXXgen.h.tpl
+++ b/include/osmocom/core/crcXXgen.h.tpl
@@ -14,10 +14,6 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#pragma once
diff --git a/include/osmocom/core/crcgen.h b/include/osmocom/core/crcgen.h
index 7cfe8699..24d29e74 100644
--- a/include/osmocom/core/crcgen.h
+++ b/include/osmocom/core/crcgen.h
@@ -14,10 +14,6 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#pragma once
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/exec.h b/include/osmocom/core/exec.h
index e63ec114..a4fdcfd3 100644
--- a/include/osmocom/core/exec.h
+++ b/include/osmocom/core/exec.h
@@ -15,10 +15,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
extern const char *osmo_environment_whitelist[];
diff --git a/include/osmocom/core/fsm.h b/include/osmocom/core/fsm.h
index 7b262c71..5e5b091c 100644
--- a/include/osmocom/core/fsm.h
+++ b/include/osmocom/core/fsm.h
@@ -224,9 +224,9 @@ int osmo_fsm_inst_update_id(struct osmo_fsm_inst *fi, const char *id);
int osmo_fsm_inst_update_id_f(struct osmo_fsm_inst *fi, const char *fmt, ...);
int osmo_fsm_inst_update_id_f_sanitize(struct osmo_fsm_inst *fi, char replace_with, const char *fmt, ...);
-const char *osmo_fsm_event_name(struct osmo_fsm *fsm, uint32_t event);
-const char *osmo_fsm_inst_name(struct osmo_fsm_inst *fi);
-const char *osmo_fsm_state_name(struct osmo_fsm *fsm, uint32_t state);
+const char *osmo_fsm_event_name(const struct osmo_fsm *fsm, uint32_t event);
+const char *osmo_fsm_inst_name(const struct osmo_fsm_inst *fi);
+const char *osmo_fsm_state_name(const struct osmo_fsm *fsm, uint32_t state);
/*! return the name of the state the FSM instance is currently in. */
static inline const char *osmo_fsm_inst_state_name(struct osmo_fsm_inst *fi)
@@ -326,4 +326,15 @@ void _osmo_fsm_inst_term_children(struct osmo_fsm_inst *fi,
void *data,
const char *file, int line);
+/*! dispatch an event to all children of an osmocom finite state machine instance
+ *
+ * This is a macro that calls _osmo_fsm_inst_broadcast_children() with the given
+ * parameters as well as the caller's source file and line number for logging
+ * purposes. See there for documentation.
+ */
+#define osmo_fsm_inst_broadcast_children(fi, cause, data) \
+ _osmo_fsm_inst_broadcast_children(fi, cause, data, __FILE__, __LINE__)
+void _osmo_fsm_inst_broadcast_children(struct osmo_fsm_inst *fi, uint32_t event,
+ void *data, const char *file, int line);
+
/*! @} */
diff --git a/include/osmocom/core/gsmtap.h b/include/osmocom/core/gsmtap.h
index 11b2d137..b0985f02 100644
--- a/include/osmocom/core/gsmtap.h
+++ b/include/osmocom/core/gsmtap.h
@@ -179,6 +179,11 @@
#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 */
/* 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 9b215be3..9137c1d2 100644
--- a/include/osmocom/core/gsmtap_util.h
+++ b/include/osmocom/core/gsmtap_util.h
@@ -17,11 +17,11 @@ void chantype_gsmtap2rsl(uint8_t gsmtap_chantype, uint8_t *rsl_chantype, uint8_t
struct msgb *gsmtap_makemsg_ex(uint8_t type, uint16_t arfcn, uint8_t ts, uint8_t chan_type,
uint8_t ss, uint32_t fn, int8_t signal_dbm,
- uint8_t snr, const uint8_t *data, unsigned int len);
+ int8_t snr, const uint8_t *data, unsigned int len);
struct msgb *gsmtap_makemsg(uint16_t arfcn, uint8_t ts, uint8_t chan_type,
uint8_t ss, uint32_t fn, int8_t signal_dbm,
- uint8_t snr, const uint8_t *data, unsigned int len);
+ int8_t snr, const uint8_t *data, unsigned int len);
/*! one gsmtap instance */
struct gsmtap_inst {
@@ -39,24 +39,30 @@ static inline int gsmtap_inst_fd(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);
int gsmtap_sendmsg(struct gsmtap_inst *gti, struct msgb *msg);
+int gsmtap_sendmsg_free(struct gsmtap_inst *gti, struct msgb *msg);
int gsmtap_send_ex(struct gsmtap_inst *gti, uint8_t type, uint16_t arfcn, uint8_t ts,
uint8_t chan_type, uint8_t ss, uint32_t fn,
- int8_t signal_dbm, uint8_t snr, const uint8_t *data,
+ int8_t signal_dbm, int8_t snr, const uint8_t *data,
unsigned int len);
int gsmtap_send(struct gsmtap_inst *gti, uint16_t arfcn, uint8_t ts,
uint8_t chan_type, uint8_t ss, uint32_t fn,
- int8_t signal_dbm, uint8_t snr, const uint8_t *data,
+ int8_t signal_dbm, int8_t snr, const uint8_t *data,
unsigned int len);
extern const struct value_string gsmtap_gsm_channel_names[];
diff --git a/include/osmocom/core/hash.h b/include/osmocom/core/hash.h
new file mode 100644
index 00000000..b45c0361
--- /dev/null
+++ b/include/osmocom/core/hash.h
@@ -0,0 +1,101 @@
+#pragma once
+#include <osmocom/core/log2.h>
+/* Fast hashing routine for ints, longs and pointers.
+ (C) 2002 Nadia Yvette Chambers, IBM */
+
+#include <limits.h>
+#if ULONG_MAX == 4294967295
+#define BITS_PER_LONG 32
+#else
+#define BITS_PER_LONG 64
+#endif
+
+/*
+ * The "GOLDEN_RATIO_PRIME" is used in ifs/btrfs/brtfs_inode.h and
+ * fs/inode.c. It's not actually prime any more (the previous primes
+ * were actively bad for hashing), but the name remains.
+ */
+#if BITS_PER_LONG == 32
+#define GOLDEN_RATIO_PRIME GOLDEN_RATIO_32
+#define hash_long(val, bits) hash_32(val, bits)
+#elif BITS_PER_LONG == 64
+#define hash_long(val, bits) hash_64(val, bits)
+#define GOLDEN_RATIO_PRIME GOLDEN_RATIO_64
+#else
+#error Wordsize not 32 or 64
+#endif
+
+/*
+ * This hash multiplies the input by a large odd number and takes the
+ * high bits. Since multiplication propagates changes to the most
+ * significant end only, it is essential that the high bits of the
+ * product be used for the hash value.
+ *
+ * Chuck Lever verified the effectiveness of this technique:
+ * http://www.citi.umich.edu/techreports/reports/citi-tr-00-1.pdf
+ *
+ * Although a random odd number will do, it turns out that the golden
+ * ratio phi = (sqrt(5)-1)/2, or its negative, has particularly nice
+ * properties. (See Knuth vol 3, section 6.4, exercise 9.)
+ *
+ * These are the negative, (1 - phi) = phi**2 = (3 - sqrt(5))/2,
+ * which is very slightly easier to multiply by and makes no
+ * difference to the hash distribution.
+ */
+#define GOLDEN_RATIO_32 0x61C88647
+#define GOLDEN_RATIO_64 0x61C8864680B583EBull
+
+/*
+ * The _generic versions exist only so lib/test_hash.c can compare
+ * the arch-optimized versions with the generic.
+ *
+ * Note that if you change these, any <asm/hash.h> that aren't updated
+ * to match need to have their HAVE_ARCH_* define values updated so the
+ * self-test will not false-positive.
+ */
+#ifndef HAVE_ARCH__HASH_32
+#define __hash_32 __hash_32_generic
+#endif
+static inline uint32_t __hash_32_generic(uint32_t val)
+{
+ return val * GOLDEN_RATIO_32;
+}
+
+#ifndef HAVE_ARCH_HASH_32
+#define hash_32 hash_32_generic
+#endif
+static inline uint32_t hash_32_generic(uint32_t val, unsigned int bits)
+{
+ /* High bits are more random, so use them. */
+ return __hash_32(val) >> (32 - bits);
+}
+
+#ifndef HAVE_ARCH_HASH_64
+#define hash_64 hash_64_generic
+#endif
+static __always_inline uint32_t hash_64_generic(uint64_t val, unsigned int bits)
+{
+#if BITS_PER_LONG == 64
+ /* 64x64-bit multiply is efficient on all 64-bit processors */
+ return val * GOLDEN_RATIO_64 >> (64 - bits);
+#else
+ /* Hash 64 bits using only 32x32-bit multiply. */
+ return hash_32((uint32_t)val ^ __hash_32(val >> 32), bits);
+#endif
+}
+
+static inline uint32_t hash_ptr(const void *ptr, unsigned int bits)
+{
+ return hash_long((unsigned long)ptr, bits);
+}
+
+/* This really should be called fold32_ptr; it does no hashing to speak of. */
+static inline uint32_t hash32_ptr(const void *ptr)
+{
+ unsigned long val = (unsigned long)ptr;
+
+#if BITS_PER_LONG == 64
+ val ^= (val >> 32);
+#endif
+ return (uint32_t)val;
+}
diff --git a/include/osmocom/core/hashtable.h b/include/osmocom/core/hashtable.h
new file mode 100644
index 00000000..acaf6b91
--- /dev/null
+++ b/include/osmocom/core/hashtable.h
@@ -0,0 +1,141 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Statically sized hash table implementation
+ * (C) 2012 Sasha Levin <levinsasha928@gmail.com>
+ */
+
+#pragma once
+
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/hash.h>
+
+#define DEFINE_HASHTABLE(name, bits) \
+ struct hlist_head name[1 << (bits)] = \
+ { [0 ... ((1 << (bits)) - 1)] = HLIST_HEAD_INIT }
+
+#define DECLARE_HASHTABLE(name, bits) \
+ struct hlist_head name[1 << (bits)]
+
+#define HASH_SIZE(name) (ARRAY_SIZE(name))
+#define HASH_BITS(name) ilog2(HASH_SIZE(name))
+
+/* Use hash_32 when possible to allow for fast 32bit hashing in 64bit kernels. */
+#define hash_min(val, bits) \
+ (sizeof(val) <= 4 ? hash_32(val, bits) : hash_long(val, bits))
+
+static inline void __hash_init(struct hlist_head *ht, unsigned int sz)
+{
+ unsigned int i;
+
+ for (i = 0; i < sz; i++)
+ INIT_HLIST_HEAD(&ht[i]);
+}
+
+/**
+ * hash_init - initialize a hash table
+ * @hashtable: hashtable to be initialized
+ *
+ * Calculates the size of the hashtable from the given parameter, otherwise
+ * same as hash_init_size.
+ *
+ * This has to be a macro since HASH_BITS() will not work on pointers since
+ * it calculates the size during preprocessing.
+ */
+#define hash_init(hashtable) __hash_init(hashtable, HASH_SIZE(hashtable))
+
+/**
+ * hash_add - add an object to a hashtable
+ * @hashtable: hashtable to add to
+ * @node: the &struct hlist_node of the object to be added
+ * @key: the key of the object to be added
+ */
+#define hash_add(hashtable, node, key) \
+ hlist_add_head(node, &hashtable[hash_min(key, HASH_BITS(hashtable))])
+
+/**
+ * hash_hashed - check whether an object is in any hashtable
+ * @node: the &struct hlist_node of the object to be checked
+ */
+static inline bool hash_hashed(struct hlist_node *node)
+{
+ return !hlist_unhashed(node);
+}
+
+static inline bool __hash_empty(struct hlist_head *ht, unsigned int sz)
+{
+ unsigned int i;
+
+ for (i = 0; i < sz; i++)
+ if (!hlist_empty(&ht[i]))
+ return false;
+
+ return true;
+}
+
+/**
+ * hash_empty - check whether a hashtable is empty
+ * @hashtable: hashtable to check
+ *
+ * This has to be a macro since HASH_BITS() will not work on pointers since
+ * it calculates the size during preprocessing.
+ */
+#define hash_empty(hashtable) __hash_empty(hashtable, HASH_SIZE(hashtable))
+
+/**
+ * hash_del - remove an object from a hashtable
+ * @node: &struct hlist_node of the object to remove
+ */
+static inline void hash_del(struct hlist_node *node)
+{
+ hlist_del_init(node);
+}
+
+/**
+ * hash_for_each - iterate over a hashtable
+ * @name: hashtable to iterate
+ * @bkt: integer to use as bucket loop cursor
+ * @obj: the type * to use as a loop cursor for each entry
+ * @member: the name of the hlist_node within the struct
+ */
+#define hash_for_each(name, bkt, obj, member) \
+ for ((bkt) = 0, obj = NULL; obj == NULL && (bkt) < HASH_SIZE(name);\
+ (bkt)++)\
+ hlist_for_each_entry(obj, &name[bkt], member)
+
+/**
+ * hash_for_each_safe - iterate over a hashtable safe against removal of
+ * hash entry
+ * @name: hashtable to iterate
+ * @bkt: integer to use as bucket loop cursor
+ * @tmp: a &struct hlist_node used for temporary storage
+ * @obj: the type * to use as a loop cursor for each entry
+ * @member: the name of the hlist_node within the struct
+ */
+#define hash_for_each_safe(name, bkt, tmp, obj, member) \
+ for ((bkt) = 0, obj = NULL; obj == NULL && (bkt) < HASH_SIZE(name);\
+ (bkt)++)\
+ hlist_for_each_entry_safe(obj, tmp, &name[bkt], member)
+
+/**
+ * hash_for_each_possible - iterate over all possible objects hashing to the
+ * same bucket
+ * @name: hashtable to iterate
+ * @obj: the type * to use as a loop cursor for each entry
+ * @member: the name of the hlist_node within the struct
+ * @key: the key of the objects to iterate over
+ */
+#define hash_for_each_possible(name, obj, member, key) \
+ hlist_for_each_entry(obj, &name[hash_min(key, HASH_BITS(name))], member)
+
+/**
+ * hash_for_each_possible_safe - iterate over all possible objects hashing to the
+ * same bucket safe against removals
+ * @name: hashtable to iterate
+ * @obj: the type * to use as a loop cursor for each entry
+ * @tmp: a &struct hlist_node used for temporary storage
+ * @member: the name of the hlist_node within the struct
+ * @key: the key of the objects to iterate over
+ */
+#define hash_for_each_possible_safe(name, obj, tmp, member, key) \
+ hlist_for_each_entry_safe(obj, tmp,\
+ &name[hash_min(key, HASH_BITS(name))], member)
diff --git a/include/osmocom/core/isdnhdlc.h b/include/osmocom/core/isdnhdlc.h
index 56369bfd..c8cfdc3a 100644
--- a/include/osmocom/core/isdnhdlc.h
+++ b/include/osmocom/core/isdnhdlc.h
@@ -20,14 +20,9 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
-#ifndef __ISDNHDLC_H__
-#define __ISDNHDLC_H__
+#pragma once
#include <stdint.h>
@@ -80,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/it_q.h b/include/osmocom/core/it_q.h
new file mode 100644
index 00000000..a28f524e
--- /dev/null
+++ b/include/osmocom/core/it_q.h
@@ -0,0 +1,62 @@
+#pragma once
+
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/select.h>
+#include <pthread.h>
+
+/*! \defgroup osmo_it_q Inter-Thread Queue
+ * @{
+ * \file osmo_it_q.h */
+
+/*! One instance of an inter-thread queue. The user can use this to queue messages
+ * between different threads. The enqueue operation is non-blocking (but of course
+ * grabs a mutex for the actual list operations to safeguard against races). The
+ * receiving thread is woken up by an event_fd which can be registered in the libosmocore
+ * select loop handling. */
+struct osmo_it_q {
+ /* entry in global list of message queues */
+ struct llist_head entry;
+
+ /* the actual list of user structs. HEAD: first in queue; TAIL: last in queue */
+ struct llist_head list;
+ /* A pthread mutex to safeguard accesses to the queue. No rwlock as we always write. */
+ pthread_mutex_t mutex;
+ /* Current count of messages in the queue */
+ unsigned int current_length;
+ /* osmo-fd wrapped eventfd */
+ struct osmo_fd event_ofd;
+
+ /* a user-defined name for this queue */
+ const char *name;
+ /* maximum permitted length of queue */
+ unsigned int max_length;
+ /* read call-back, called for each de-queued message */
+ void (*read_cb)(struct osmo_it_q *q, struct llist_head *item);
+ /* opaque data pointer passed through to call-back function */
+ void *data;
+};
+
+struct osmo_it_q *osmo_it_q_by_name(const char *name);
+
+int _osmo_it_q_enqueue(struct osmo_it_q *queue, struct llist_head *item);
+#define osmo_it_q_enqueue(queue, item, member) \
+ _osmo_it_q_enqueue(queue, &(item)->member)
+
+struct llist_head *_osmo_it_q_dequeue(struct osmo_it_q *queue);
+#define osmo_it_q_dequeue(queue, item, member) do { \
+ struct llist_head *l = _osmo_it_q_dequeue(queue); \
+ if (!l) \
+ *item = NULL; \
+ else \
+ *item = llist_entry(l, typeof(**item), member); \
+} while (0)
+
+
+struct osmo_it_q *osmo_it_q_alloc(void *ctx, const char *name, unsigned int max_length,
+
+ void (*read_cb)(struct osmo_it_q *q, struct llist_head *item),
+ void *data);
+void osmo_it_q_destroy(struct osmo_it_q *q);
+void osmo_it_q_flush(struct osmo_it_q *q);
+
+/*! @} */
diff --git a/include/osmocom/core/linuxlist.h b/include/osmocom/core/linuxlist.h
index 867605e5..2fc3fa75 100644
--- a/include/osmocom/core/linuxlist.h
+++ b/include/osmocom/core/linuxlist.h
@@ -16,6 +16,7 @@
* \file linuxlist.h */
#include <stddef.h>
+#include <stdbool.h>
#ifndef inline
#define inline __inline__
@@ -237,6 +238,12 @@ static inline void llist_splice_init(struct llist_head *llist,
#define llist_last_entry(ptr, type, member) \
llist_entry((ptr)->prev, type, member)
+/*! Return the last element of the list.
+ * \param head the llist head of the list.
+ * \returns last element of the list, head if the list is empty.
+ */
+#define llist_last(head) (head)->prev
+
/*! Get the first element from a list, or NULL.
* \param ptr the list head to take the element from.
* \param type the type of the struct this is embedded in.
@@ -321,8 +328,7 @@ static inline void llist_splice_init(struct llist_head *llist,
pos = llist_entry(pos->member.next, typeof(*pos), member), \
prefetch(pos->member.next))
-/*! Iterate over llist of given type, safe against removal of
- * non-consecutive(!) llist entries.
+/*! Iterate over llist of given type, safe against removal of llist entry.
* \param pos the 'type *' to use as a loop counter.
* \param n another 'type *' to use as temporary storage.
* \param head the head of the list over which to iterate.
@@ -393,6 +399,252 @@ static inline unsigned int llist_count(const struct llist_head *head)
return i;
}
+
+
+/*! Double linked lists with a single pointer list head.
+ * Mostly useful for hash tables where the two pointer list head is
+ * too wasteful.
+ * You lose the ability to access the tail in O(1).
+ */
+
+struct hlist_head {
+ struct hlist_node *first;
+};
+
+struct hlist_node {
+ struct hlist_node *next, **pprev;
+};
+
+#define HLIST_HEAD_INIT { .first = NULL }
+#define HLIST_HEAD(name) struct hlist_head name = { .first = NULL }
+#define INIT_HLIST_HEAD(ptr) ((ptr)->first = NULL)
+static inline void INIT_HLIST_NODE(struct hlist_node *h)
+{
+ h->next = NULL;
+ h->pprev = NULL;
+}
+
+#define READ_ONCE(x) x
+#define WRITE_ONCE(a, b) a = b
+
+/*! Has node been removed from list and reinitialized?.
+ * \param[in] h: Node to be checked
+ * \return 1 if node is unhashed; 0 if not
+ *
+ * Not that not all removal functions will leave a node in unhashed
+ * state. For example, hlist_nulls_del_init_rcu() does leave the
+ * node in unhashed state, but hlist_nulls_del() does not.
+ */
+static inline int hlist_unhashed(const struct hlist_node *h)
+{
+ return !h->pprev;
+}
+
+/*! Version of hlist_unhashed for lockless use.
+ * \param[in] n Node to be checked
+ * \return 1 if node is unhashed; 0 if not
+ *
+ * This variant of hlist_unhashed() must be used in lockless contexts
+ * to avoid potential load-tearing. The READ_ONCE() is paired with the
+ * various WRITE_ONCE() in hlist helpers that are defined below.
+ */
+static inline int hlist_unhashed_lockless(const struct hlist_node *h)
+{
+ return !READ_ONCE(h->pprev);
+}
+
+/*!Is the specified hlist_head structure an empty hlist?.
+ * \param[in] h Structure to check.
+ * \return 1 if hlist is empty; 0 if not
+ */
+static inline int hlist_empty(const struct hlist_head *h)
+{
+ return !READ_ONCE(h->first);
+}
+
+static inline void __hlist_del(struct hlist_node *n)
+{
+ struct hlist_node *next = n->next;
+ struct hlist_node **pprev = n->pprev;
+
+ WRITE_ONCE(*pprev, next);
+ if (next)
+ WRITE_ONCE(next->pprev, pprev);
+}
+
+/*! Delete the specified hlist_node from its list.
+ * \param[in] n: Node to delete.
+ *
+ * Note that this function leaves the node in hashed state. Use
+ * hlist_del_init() or similar instead to unhash @n.
+ */
+static inline void hlist_del(struct hlist_node *n)
+{
+ __hlist_del(n);
+ n->next = (struct hlist_node *)LLIST_POISON1;
+ n->pprev = (struct hlist_node **)LLIST_POISON2;
+}
+
+/*! Delete the specified hlist_node from its list and initialize.
+ * \param[in] n Node to delete.
+ *
+ * Note that this function leaves the node in unhashed state.
+ */
+static inline void hlist_del_init(struct hlist_node *n)
+{
+ if (!hlist_unhashed(n)) {
+ __hlist_del(n);
+ INIT_HLIST_NODE(n);
+ }
+}
+
+/*! add a new entry at the beginning of the hlist.
+ * \param[in] n new entry to be added
+ * \param[in] h hlist head to add it after
+ *
+ * Insert a new entry after the specified head.
+ * This is good for implementing stacks.
+ */
+static inline void hlist_add_head(struct hlist_node *n, struct hlist_head *h)
+{
+ struct hlist_node *first = h->first;
+ WRITE_ONCE(n->next, first);
+ if (first)
+ WRITE_ONCE(first->pprev, &n->next);
+ WRITE_ONCE(h->first, n);
+ WRITE_ONCE(n->pprev, &h->first);
+}
+
+/*! add a new entry before the one specified.
+ * @n: new entry to be added
+ * @next: hlist node to add it before, which must be non-NULL
+ */
+static inline void hlist_add_before(struct hlist_node *n,
+ struct hlist_node *next)
+{
+ WRITE_ONCE(n->pprev, next->pprev);
+ WRITE_ONCE(n->next, next);
+ WRITE_ONCE(next->pprev, &n->next);
+ WRITE_ONCE(*(n->pprev), n);
+}
+
+/*! add a new entry after the one specified
+ * \param[in] n new entry to be added
+ * \param[in] prev hlist node to add it after, which must be non-NULL
+ */
+static inline void hlist_add_behind(struct hlist_node *n,
+ struct hlist_node *prev)
+{
+ WRITE_ONCE(n->next, prev->next);
+ WRITE_ONCE(prev->next, n);
+ WRITE_ONCE(n->pprev, &prev->next);
+
+ if (n->next)
+ WRITE_ONCE(n->next->pprev, &n->next);
+}
+
+/*! create a fake hlist consisting of a single headless node.
+ * \param[in] n Node to make a fake list out of
+ *
+ * This makes @n appear to be its own predecessor on a headless hlist.
+ * The point of this is to allow things like hlist_del() to work correctly
+ * in cases where there is no list.
+ */
+static inline void hlist_add_fake(struct hlist_node *n)
+{
+ n->pprev = &n->next;
+}
+
+/*! Is this node a fake hlist?.
+ * \param[in] h Node to check for being a self-referential fake hlist.
+ */
+static inline bool hlist_fake(struct hlist_node *h)
+{
+ return h->pprev == &h->next;
+}
+
+/*!is node the only element of the specified hlist?.
+ * \param[in] n Node to check for singularity.
+ * \param[in] h Header for potentially singular list.
+ *
+ * Check whether the node is the only node of the head without
+ * accessing head, thus avoiding unnecessary cache misses.
+ */
+static inline bool
+hlist_is_singular_node(struct hlist_node *n, struct hlist_head *h)
+{
+ return !n->next && n->pprev == &h->first;
+}
+
+/*! Move an hlist.
+ * \param[in] old hlist_head for old list.
+ * \param[in] new hlist_head for new list.
+ *
+ * Move a list from one list head to another. Fixup the pprev
+ * reference of the first entry if it exists.
+ */
+static inline void hlist_move_list(struct hlist_head *old,
+ struct hlist_head *_new)
+{
+ _new->first = old->first;
+ if (_new->first)
+ _new->first->pprev = &_new->first;
+ old->first = NULL;
+}
+
+#define hlist_entry(ptr, type, member) container_of(ptr,type,member)
+
+#define hlist_for_each(pos, head) \
+ for (pos = (head)->first; pos ; pos = pos->next)
+
+#define hlist_for_each_safe(pos, n, head) \
+ for (pos = (head)->first; pos && ({ n = pos->next; 1; }); \
+ pos = n)
+
+#define hlist_entry_safe(ptr, type, member) \
+ ({ typeof(ptr) ____ptr = (ptr); \
+ ____ptr ? hlist_entry(____ptr, type, member) : NULL; \
+ })
+
+/*! iterate over list of given type.
+ * \param[out] pos the type * to use as a loop cursor.
+ * \param[in] head the head for your list.
+ * \param[in] member the name of the hlist_node within the struct.
+ */
+#define hlist_for_each_entry(pos, head, member) \
+ for (pos = hlist_entry_safe((head)->first, typeof(*(pos)), member);\
+ pos; \
+ pos = hlist_entry_safe((pos)->member.next, typeof(*(pos)), member))
+
+/*! iterate over a hlist continuing after current point.
+ * \param[out] pos the type * to use as a loop cursor.
+ * \param[in] member the name of the hlist_node within the struct.
+ */
+#define hlist_for_each_entry_continue(pos, member) \
+ for (pos = hlist_entry_safe((pos)->member.next, typeof(*(pos)), member);\
+ pos; \
+ pos = hlist_entry_safe((pos)->member.next, typeof(*(pos)), member))
+
+/*! iterate over a hlist continuing from current point.
+ * \param[out] pos the type * to use as a loop cursor.
+ * \param[in] member the name of the hlist_node within the struct.
+ */
+#define hlist_for_each_entry_from(pos, member) \
+ for (; pos; \
+ pos = hlist_entry_safe((pos)->member.next, typeof(*(pos)), member))
+
+/*! hlist_for_each_entry_safe - iterate over list of given type safe against removal of list entry.
+ * \param[out] pos the type * to use as a loop cursor.
+ * \param[out] n a &struct hlist_node to use as temporary storage
+ * \param[in] head the head for your list.
+ * \param[in] member the name of the hlist_node within the struct
+ */
+#define hlist_for_each_entry_safe(pos, n, head, member) \
+ for (pos = hlist_entry_safe((head)->first, typeof(*pos), member);\
+ pos && ({ n = pos->member.next; 1; }); \
+ pos = hlist_entry_safe(n, typeof(*pos), member))
+
+
/*!
* @}
*/
diff --git a/include/osmocom/core/linuxrbtree.h b/include/osmocom/core/linuxrbtree.h
index d3f9fd12..e15317ec 100644
--- a/include/osmocom/core/linuxrbtree.h
+++ b/include/osmocom/core/linuxrbtree.h
@@ -12,11 +12,6 @@
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
- MA 02110-1301, USA.
-
linux/include/linux/rbtree.h
To use rbtrees you'll have to implement your own insert and search cores.
diff --git a/include/osmocom/core/log2.h b/include/osmocom/core/log2.h
new file mode 100644
index 00000000..8c65768f
--- /dev/null
+++ b/include/osmocom/core/log2.h
@@ -0,0 +1,184 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/* Integer base 2 logarithm calculation
+ *
+ * Copyright (C) 2006 Red Hat, Inc. All Rights Reserved.
+ * Written by David Howells (dhowells@redhat.com)
+ */
+
+#pragma once
+#include <stdint.h>
+
+/* from linux/asm-generic/bitops/{fls,fls64}.h - could later be enhanced
+ * with architecture specific optimized versions */
+
+/**
+ * fls - find last (most-significant) bit set
+ * @x: the word to search
+ *
+ * This is defined the same way as ffs.
+ * Note fls(0) = 0, fls(1) = 1, fls(0x80000000) = 32.
+ */
+static inline __attribute__((always_inline)) int fls(unsigned int x)
+{
+ int r = 32;
+
+ if (!x)
+ return 0;
+ if (!(x & 0xffff0000u)) {
+ x <<= 16;
+ r -= 16;
+ }
+ if (!(x & 0xff000000u)) {
+ x <<= 8;
+ r -= 8;
+ }
+ if (!(x & 0xf0000000u)) {
+ x <<= 4;
+ r -= 4;
+ }
+ if (!(x & 0xc0000000u)) {
+ x <<= 2;
+ r -= 2;
+ }
+ if (!(x & 0x80000000u)) {
+ x <<= 1;
+ r -= 1;
+ }
+ return r;
+}
+
+/**
+ * fls64 - find last set bit in a 64-bit word
+ * @x: the word to search
+ *
+ * This is defined in a similar way as the libc and compiler builtin
+ * ffsll, but returns the position of the most significant set bit.
+ *
+ * fls64(value) returns 0 if value is 0 or the position of the last
+ * set bit if value is nonzero. The last (most significant) bit is
+ * at position 64.
+ */
+static inline __attribute__((always_inline)) int fls64(uint64_t x)
+{
+ uint32_t h = x >> 32;
+ if (h)
+ return fls(h) + 32;
+ return fls(x);
+}
+
+/*
+ * non-constant log of base 2 calculators
+ * - the arch may override these in asm/bitops.h if they can be implemented
+ * more efficiently than using fls() and fls64()
+ * - the arch is not required to handle n==0 if implementing the fallback
+ */
+#ifndef CONFIG_ARCH_HAS_ILOG2_U32
+static inline __attribute__((const))
+int __ilog2_u32(uint32_t n)
+{
+ return fls(n) - 1;
+}
+#endif
+
+#ifndef CONFIG_ARCH_HAS_ILOG2_U64
+static inline __attribute__((const))
+int __ilog2_u64(uint64_t n)
+{
+ return fls64(n) - 1;
+}
+#endif
+
+/**
+ * const_ilog2 - log base 2 of 32-bit or a 64-bit constant unsigned value
+ * @n: parameter
+ *
+ * Use this where sparse expects a true constant expression, e.g. for array
+ * indices.
+ */
+#define const_ilog2(n) \
+( \
+ __builtin_constant_p(n) ? ( \
+ (n) < 2 ? 0 : \
+ (n) & (1ULL << 63) ? 63 : \
+ (n) & (1ULL << 62) ? 62 : \
+ (n) & (1ULL << 61) ? 61 : \
+ (n) & (1ULL << 60) ? 60 : \
+ (n) & (1ULL << 59) ? 59 : \
+ (n) & (1ULL << 58) ? 58 : \
+ (n) & (1ULL << 57) ? 57 : \
+ (n) & (1ULL << 56) ? 56 : \
+ (n) & (1ULL << 55) ? 55 : \
+ (n) & (1ULL << 54) ? 54 : \
+ (n) & (1ULL << 53) ? 53 : \
+ (n) & (1ULL << 52) ? 52 : \
+ (n) & (1ULL << 51) ? 51 : \
+ (n) & (1ULL << 50) ? 50 : \
+ (n) & (1ULL << 49) ? 49 : \
+ (n) & (1ULL << 48) ? 48 : \
+ (n) & (1ULL << 47) ? 47 : \
+ (n) & (1ULL << 46) ? 46 : \
+ (n) & (1ULL << 45) ? 45 : \
+ (n) & (1ULL << 44) ? 44 : \
+ (n) & (1ULL << 43) ? 43 : \
+ (n) & (1ULL << 42) ? 42 : \
+ (n) & (1ULL << 41) ? 41 : \
+ (n) & (1ULL << 40) ? 40 : \
+ (n) & (1ULL << 39) ? 39 : \
+ (n) & (1ULL << 38) ? 38 : \
+ (n) & (1ULL << 37) ? 37 : \
+ (n) & (1ULL << 36) ? 36 : \
+ (n) & (1ULL << 35) ? 35 : \
+ (n) & (1ULL << 34) ? 34 : \
+ (n) & (1ULL << 33) ? 33 : \
+ (n) & (1ULL << 32) ? 32 : \
+ (n) & (1ULL << 31) ? 31 : \
+ (n) & (1ULL << 30) ? 30 : \
+ (n) & (1ULL << 29) ? 29 : \
+ (n) & (1ULL << 28) ? 28 : \
+ (n) & (1ULL << 27) ? 27 : \
+ (n) & (1ULL << 26) ? 26 : \
+ (n) & (1ULL << 25) ? 25 : \
+ (n) & (1ULL << 24) ? 24 : \
+ (n) & (1ULL << 23) ? 23 : \
+ (n) & (1ULL << 22) ? 22 : \
+ (n) & (1ULL << 21) ? 21 : \
+ (n) & (1ULL << 20) ? 20 : \
+ (n) & (1ULL << 19) ? 19 : \
+ (n) & (1ULL << 18) ? 18 : \
+ (n) & (1ULL << 17) ? 17 : \
+ (n) & (1ULL << 16) ? 16 : \
+ (n) & (1ULL << 15) ? 15 : \
+ (n) & (1ULL << 14) ? 14 : \
+ (n) & (1ULL << 13) ? 13 : \
+ (n) & (1ULL << 12) ? 12 : \
+ (n) & (1ULL << 11) ? 11 : \
+ (n) & (1ULL << 10) ? 10 : \
+ (n) & (1ULL << 9) ? 9 : \
+ (n) & (1ULL << 8) ? 8 : \
+ (n) & (1ULL << 7) ? 7 : \
+ (n) & (1ULL << 6) ? 6 : \
+ (n) & (1ULL << 5) ? 5 : \
+ (n) & (1ULL << 4) ? 4 : \
+ (n) & (1ULL << 3) ? 3 : \
+ (n) & (1ULL << 2) ? 2 : \
+ 1) : \
+ -1)
+
+/**
+ * ilog2 - log base 2 of 32-bit or a 64-bit unsigned value
+ * @n: parameter
+ *
+ * constant-capable log of base 2 calculation
+ * - this can be used to initialise global variables from constant data, hence
+ * the massive ternary operator construction
+ *
+ * selects the appropriately-sized optimised version depending on sizeof(n)
+ */
+#define ilog2(n) \
+( \
+ __builtin_constant_p(n) ? \
+ const_ilog2(n) : \
+ (sizeof(n) <= 4) ? \
+ __ilog2_u32(n) : \
+ __ilog2_u64(n) \
+ )
diff --git a/include/osmocom/core/logging.h b/include/osmocom/core/logging.h
index 36ce941c..755564d3 100644
--- a/include/osmocom/core/logging.h
+++ b/include/osmocom/core/logging.h
@@ -11,15 +11,16 @@
#include <osmocom/core/defs.h>
#include <osmocom/core/linuxlist.h>
-/*! Maximum number of logging contexts */
-#define LOG_MAX_CTX 8
-/*! Maximum number of logging filters */
-#define LOG_MAX_FILTERS 8
+extern struct log_info *osmo_log_info;
#ifndef DEBUG
#define DEBUG
#endif
+#ifdef LIBOSMOCORE_NO_LOGGING
+#undef DEBUG
+#endif
+
#ifdef DEBUG
/*! Log a debug message through the Osmocom logging framework
* \param[in] ss logging subsystem (e.g. \ref DLGLOBAL)
@@ -54,11 +55,19 @@ void logp(int subsys, const char *file, int line, int cont, const char *format,
* \param[in] fmt format string
* \param[in] args variable argument list
*/
+#ifndef LIBOSMOCORE_NO_LOGGING
#define LOGPC(ss, level, fmt, args...) \
do { \
+ if (!osmo_log_info) { \
+ logp_stub(__FILE__, __LINE__, 1, fmt, ##args); \
+ break; \
+ } \
if (log_check_level(ss, level)) \
logp2(ss, level, __FILE__, __LINE__, 1, fmt, ##args); \
} while(0)
+#else
+#define LOGPC(ss, level, fmt, args...)
+#endif
/*! Log through the Osmocom logging framework with explicit source.
* If caller_file is passed as NULL, __FILE__ and __LINE__ are used
@@ -88,8 +97,16 @@ void logp(int subsys, const char *file, int line, int cont, const char *format,
* \param[in] fmt format string
* \param[in] args variable argument list
*/
+#ifndef LIBOSMOCORE_NO_LOGGING
#define LOGPSRCC(ss, level, caller_file, caller_line, cont, fmt, args...) \
do { \
+ if (!osmo_log_info) { \
+ if (caller_file) \
+ logp_stub(caller_file, caller_line, cont, fmt, ##args); \
+ else \
+ logp_stub(__FILE__, __LINE__, cont, fmt, ##args); \
+ break; \
+ } \
if (log_check_level(ss, level)) {\
if (caller_file) \
logp2(ss, level, caller_file, caller_line, cont, fmt, ##args); \
@@ -97,6 +114,9 @@ void logp(int subsys, const char *file, int line, int cont, const char *format,
logp2(ss, level, __FILE__, __LINE__, cont, fmt, ##args); \
}\
} while(0)
+#else
+#define LOGPSRCC(ss, level, caller_file, caller_line, cont, fmt, args...)
+#endif
/*! different log levels */
#define LOGL_DEBUG 1 /*!< debugging information */
@@ -126,7 +146,15 @@ void logp(int subsys, const char *file, int line, int cont, const char *format,
#define DLJIBUF -18 /*!< Osmocom Jitter Buffer */
#define DLRSPRO -19 /*!< Osmocom Remote SIM Protocol */
#define DLNS -20 /*!< Osmocom NS layer */
-#define OSMO_NUM_DLIB 20 /*!< Number of logging sub-systems in libraries */
+#define DLBSSGP -21 /*!< Osmocom BSSGP layer */
+#define DLNSDATA -22 /*!< Osmocom NS layer data pdus */
+#define DLNSSIGNAL -23 /*!< Osmocom NS layer signal pdus */
+#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 DLM2PA -27 /*!< Osmocom M2PA (libosmo-sigtran) */
+#define DLM2UA -28 /*!< Reserved for future Osmocom M2UA (libosmo-sigtran) */
+#define OSMO_NUM_DLIB 28 /*!< Number of logging sub-systems in libraries */
/* Colors that can be used in log_info_cat.color */
#define OSMO_LOGCOLOR_NORMAL NULL
@@ -162,11 +190,6 @@ struct log_info_cat {
uint8_t enabled; /*!< is this category enabled or not */
};
-/*! Log context information, passed to filter */
-struct log_context {
- void *ctx[LOG_MAX_CTX+1];
-};
-
/*! Indexes to indicate the object currently acted upon.
* Array indexes for the global \a log_context array. */
enum log_ctx_index {
@@ -175,6 +198,7 @@ enum log_ctx_index {
LOG_CTX_BSC_SUBSCR,
LOG_CTX_VLR_SUBSCR,
LOG_CTX_L1_SAPI,
+ LOG_CTX_GB_NSE,
_LOG_CTX_COUNT
};
@@ -188,9 +212,20 @@ enum log_filter_index {
LOG_FLT_BSC_SUBSCR,
LOG_FLT_VLR_SUBSCR,
LOG_FLT_L1_SAPI,
+ LOG_FLT_GB_NSE,
_LOG_FLT_COUNT
};
+/*! Maximum number of logging contexts */
+#define LOG_MAX_CTX _LOG_CTX_COUNT
+/*! Maximum number of logging filters */
+#define LOG_MAX_FILTERS _LOG_FLT_COUNT
+
+/*! Log context information, passed to filter */
+struct log_context {
+ void *ctx[LOG_MAX_CTX+1];
+};
+
/*! Compatibility with older libosmocore versions */
#define LOG_FILTER_ALL (1<<LOG_FLT_ALL)
/*! Compatibility with older libosmocore versions */
@@ -244,6 +279,7 @@ enum log_target_type {
LOG_TGT_TYPE_STDERR, /*!< stderr logging */
LOG_TGT_TYPE_STRRB, /*!< osmo_strrb-backed logging */
LOG_TGT_TYPE_GSMTAP, /*!< GSMTAP network logging */
+ LOG_TGT_TYPE_SYSTEMD, /*!< systemd journal logging */
};
/*! Whether/how to log the source filename (and line number). */
@@ -261,7 +297,7 @@ enum log_filename_pos {
/*! structure representing a logging target */
struct log_target {
- struct llist_head entry; /*!< linked list */
+ struct llist_head entry; /*!< linked list */
/*! Internal data for filtering */
int filter_map;
@@ -277,6 +313,8 @@ struct log_target {
unsigned int use_color:1;
/*! should log messages be prefixed with a timestamp? */
unsigned int print_timestamp:1;
+ /*! should log messages be prefixed with the logger Thread ID? */
+ unsigned int print_tid:1;
/*! DEPRECATED: use print_filename2 instead. */
unsigned int print_filename:1;
/*! should log messages be prefixed with a category name? */
@@ -289,8 +327,11 @@ struct log_target {
union {
struct {
+ /* direct, blocking output via stdio */
FILE *out;
const char *fname;
+ /* indirect output via write_queue and osmo_select_main() */
+ struct osmo_wqueue *wqueue;
} tgt_file;
struct {
@@ -311,6 +352,10 @@ struct log_target {
const char *ident;
const char *hostname;
} tgt_gsmtap;
+
+ struct {
+ bool raw;
+ } sd_journal;
};
/*! call-back function to be called when the logging framework
@@ -352,6 +397,7 @@ struct log_target {
void logp2(int subsys, unsigned int level, const char *file,
int line, int cont, const char *format, ...)
__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);
void log_fini(void);
int log_check_level(int subsys, unsigned int level);
@@ -366,7 +412,8 @@ void log_set_all_filter(struct log_target *target, int);
void log_set_use_color(struct log_target *target, int);
void log_set_print_extended_timestamp(struct log_target *target, int);
void log_set_print_timestamp(struct log_target *target, int);
-void log_set_print_filename(struct log_target *target, int);
+void log_set_print_tid(struct log_target *target, int);
+void log_set_print_filename(struct log_target *target, int) OSMO_DEPRECATED("Use log_set_print_filename2() instead");
void log_set_print_filename2(struct log_target *target, enum log_filename_type lft);
void log_set_print_filename_pos(struct log_target *target, enum log_filename_pos pos);
void log_set_print_category(struct log_target *target, int);
@@ -392,13 +439,17 @@ struct log_target *log_target_create_gsmtap(const char *host, uint16_t port,
const char *ident,
bool ofd_wq_mode,
bool add_sink);
+struct log_target *log_target_create_systemd(bool raw);
+void log_target_systemd_set_raw(struct log_target *target, bool raw);
int log_target_file_reopen(struct log_target *tgt);
+int log_target_file_switch_to_stream(struct log_target *tgt);
+int log_target_file_switch_to_wqueue(struct log_target *tgt);
int log_targets_reopen(void);
void log_add_target(struct log_target *target);
void log_del_target(struct log_target *target);
-struct log_target *log_target_find(int type, const char *fname);
+struct log_target *log_target_find(enum log_target_type type, const char *fname);
void log_enable_multithread(void);
diff --git a/include/osmocom/core/loggingrb.h b/include/osmocom/core/loggingrb.h
index a9fb4047..6d501466 100644
--- a/include/osmocom/core/loggingrb.h
+++ b/include/osmocom/core/loggingrb.h
@@ -11,10 +11,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#pragma once
diff --git a/include/osmocom/core/mnl.h b/include/osmocom/core/mnl.h
new file mode 100644
index 00000000..11c83530
--- /dev/null
+++ b/include/osmocom/core/mnl.h
@@ -0,0 +1,22 @@
+/*! \file select.h
+ * libmnl integration
+ */
+#pragma once
+
+#include <osmocom/core/select.h>
+#include <libmnl/libmnl.h>
+
+/*! osmocom wrapper around libmnl abstraction of netlink socket */
+struct osmo_mnl {
+ /*! osmo-wrapped netlink file descriptor */
+ struct osmo_fd ofd;
+ /*! libmnl socket abstraction */
+ struct mnl_socket *mnls;
+ /*! call-back called for received netlink messages */
+ mnl_cb_t mnl_cb;
+ /*! opaque data provided by user */
+ void *priv;
+};
+
+struct osmo_mnl *osmo_mnl_init(void *ctx, int bus, unsigned int groups, mnl_cb_t mnl_cb, void *priv);
+void osmo_mnl_destroy(struct osmo_mnl *omnl);
diff --git a/include/osmocom/core/msgb.h b/include/osmocom/core/msgb.h
index cc76e3ad..2529c0e8 100644
--- a/include/osmocom/core/msgb.h
+++ b/include/osmocom/core/msgb.h
@@ -13,10 +13,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <stdint.h>
@@ -74,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().
@@ -148,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);
}
@@ -160,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);
}
@@ -172,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);
}
@@ -184,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
@@ -238,7 +240,7 @@ static inline int msgb_headroom(const struct msgb *msgb)
static inline unsigned char *msgb_put(struct msgb *msgb, unsigned int len)
{
unsigned char *tmp = msgb->tail;
- if (msgb_tailroom(msgb) < (int) len)
+ if (OSMO_UNLIKELY(msgb_tailroom(msgb) < (int) len))
MSGB_ABORT(msgb, "Not enough tailroom msgb_put"
" (allocated %u, head at %u, len %u, tailroom %u < want tailroom %u)\n",
msgb->data_len - sizeof(struct msgb),
@@ -286,7 +288,7 @@ static inline void msgb_put_u32(struct msgb *msgb, uint32_t word)
*/
static inline unsigned char *msgb_get(struct msgb *msgb, unsigned int len)
{
- if (msgb_length(msgb) < len)
+ if (OSMO_UNLIKELY(msgb_length(msgb) < len))
MSGB_ABORT(msgb, "msgb too small to get %u (len %u)\n",
len, msgb_length(msgb));
msgb->tail -= len;
@@ -338,7 +340,7 @@ static inline uint32_t msgb_get_u32(struct msgb *msgb)
*/
static inline unsigned char *msgb_push(struct msgb *msgb, unsigned int len)
{
- if (msgb_headroom(msgb) < (int) len)
+ if (OSMO_UNLIKELY(msgb_headroom(msgb) < (int) len))
MSGB_ABORT(msgb, "Not enough headroom msgb_push"
" (allocated %u, head at %u < want headroom %u, len %u, tailroom %u)\n",
msgb->data_len - sizeof(struct msgb),
@@ -401,7 +403,7 @@ static inline unsigned char *msgb_push_tl(struct msgb *msgb, uint8_t tag)
*/
static inline unsigned char *msgb_pull(struct msgb *msgb, unsigned int len)
{
- if (msgb_length(msgb) < len)
+ if (OSMO_UNLIKELY(msgb_length(msgb) < len))
MSGB_ABORT(msgb, "msgb too small to pull %u (len %u)\n",
len, msgb_length(msgb));
msgb->len -= len;
@@ -492,9 +494,9 @@ static inline void msgb_reserve(struct msgb *msg, int len)
*/
static inline int msgb_trim(struct msgb *msg, int len)
{
- if (len < 0)
+ if (OSMO_UNLIKELY(len < 0))
MSGB_ABORT(msg, "Negative length is not allowed\n");
- if (len > msg->data_len)
+ if (OSMO_UNLIKELY(len > msg->data_len))
return -1;
msg->len = len;
@@ -524,13 +526,13 @@ static inline int msgb_l3trim(struct msgb *msg, int l3len)
* followed by \ref msgb_reserve in order to create a new \ref msgb with
* user-specified amount of headroom.
*/
-static inline struct msgb *msgb_alloc_headroom_c(const void *ctx, int size, int headroom,
+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 (msg)
+ if (OSMO_LIKELY(msg))
msgb_reserve(msg, headroom);
return msg;
}
@@ -546,13 +548,13 @@ static inline struct msgb *msgb_alloc_headroom_c(const void *ctx, int size, int
* followed by \ref msgb_reserve in order to create a new \ref msgb with
* user-specified amount of headroom.
*/
-static inline struct msgb *msgb_alloc_headroom(int size, int headroom,
+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 (msg)
+ if (OSMO_LIKELY(msg))
msgb_reserve(msg, headroom);
return msg;
}
diff --git a/include/osmocom/core/msgfile.h b/include/osmocom/core/msgfile.h
index 800b4311..cfa95905 100644
--- a/include/osmocom/core/msgfile.h
+++ b/include/osmocom/core/msgfile.h
@@ -14,10 +14,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#pragma once
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/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 1669ce49..d944cc0c 100644
--- a/include/osmocom/core/rate_ctr.h
+++ b/include/osmocom/core/rate_ctr.h
@@ -61,7 +61,9 @@ struct rate_ctr_group {
const struct rate_ctr_group_desc *desc;
/*! The index of this ctr_group within its class */
unsigned int idx;
- /*! Actual counter structures below */
+ /*! Optional string-based identifier to be used instead of index at report time */
+ char *name;
+ /*! Actual counter structures below. Don't access it directly, use APIs below! */
struct rate_ctr ctr[0];
};
@@ -73,6 +75,9 @@ static inline void rate_ctr_group_upd_idx(struct rate_ctr_group *grp, unsigned i
{
grp->idx = idx;
}
+void rate_ctr_group_set_name(struct rate_ctr_group *grp, const char *name);
+
+struct rate_ctr *rate_ctr_group_get_ctr(struct rate_ctr_group *grp, unsigned int idx);
void rate_ctr_group_free(struct rate_ctr_group *grp);
@@ -93,7 +98,7 @@ static inline void rate_ctr_inc(struct rate_ctr *ctr)
* \param idx index into \a ctrg counter group */
static inline void rate_ctr_inc2(struct rate_ctr_group *ctrg, unsigned int idx)
{
- rate_ctr_inc(&ctrg->ctr[idx]);
+ rate_ctr_inc(rate_ctr_group_get_ctr(ctrg, idx));
}
diff --git a/include/osmocom/core/select.h b/include/osmocom/core/select.h
index bc601982..fc148512 100644
--- a/include/osmocom/core/select.h
+++ b/include/osmocom/core/select.h
@@ -19,6 +19,8 @@
#define OSMO_FD_WRITE 0x0002
/*! Indicate interest in exceptions from the file descriptor */
#define OSMO_FD_EXCEPT 0x0004
+/*! Used as when_mask in osmo_fd_update_when() */
+#define OSMO_FD_MASK 0xFFFF
/* legacy naming dating back to early OpenBSC / bsc_hack of 2008 */
#define BSC_FD_READ OSMO_FD_READ
@@ -47,6 +49,24 @@ void osmo_fd_setup(struct osmo_fd *ofd, int fd, unsigned int when,
int (*cb)(struct osmo_fd *fd, unsigned int what),
void *data, unsigned int priv_nr);
+void osmo_fd_update_when(struct osmo_fd *ofd, unsigned int when_mask, unsigned int when);
+
+static inline void osmo_fd_read_enable(struct osmo_fd *ofd) {
+ osmo_fd_update_when(ofd, OSMO_FD_MASK, OSMO_FD_READ);
+}
+
+static inline void osmo_fd_read_disable(struct osmo_fd *ofd) {
+ osmo_fd_update_when(ofd, ~OSMO_FD_READ, 0);
+}
+
+static inline void osmo_fd_write_enable(struct osmo_fd *ofd) {
+ osmo_fd_update_when(ofd, OSMO_FD_MASK, OSMO_FD_WRITE);
+}
+
+static inline void osmo_fd_write_disable(struct osmo_fd *ofd) {
+ osmo_fd_update_when(ofd, ~OSMO_FD_WRITE, 0);
+}
+
bool osmo_fd_is_registered(struct osmo_fd *fd);
int osmo_fd_register(struct osmo_fd *fd);
void osmo_fd_unregister(struct osmo_fd *fd);
@@ -85,5 +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(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/serial.h b/include/osmocom/core/serial.h
index 39614a47..0ac29681 100644
--- a/include/osmocom/core/serial.h
+++ b/include/osmocom/core/serial.h
@@ -14,10 +14,6 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
/*! \defgroup serial Utility functions to deal with serial ports
@@ -32,5 +28,6 @@ int osmo_serial_init(const char *dev, speed_t baudrate);
int osmo_serial_set_baudrate(int fd, speed_t baudrate);
int osmo_serial_set_custom_baudrate(int fd, int baudrate);
int osmo_serial_clear_custom_baudrate(int fd);
+int osmo_serial_speed_t(unsigned int baudrate, speed_t *speed);
/*! @} */
diff --git a/include/osmocom/core/sockaddr_str.h b/include/osmocom/core/sockaddr_str.h
index d96b7434..f474fa03 100644
--- a/include/osmocom/core/sockaddr_str.h
+++ b/include/osmocom/core/sockaddr_str.h
@@ -20,10 +20,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#pragma once
diff --git a/include/osmocom/core/socket.h b/include/osmocom/core/socket.h
index 47a372c7..6bac87d5 100644
--- a/include/osmocom/core/socket.h
+++ b/include/osmocom/core/socket.h
@@ -30,6 +30,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)
@@ -44,6 +95,15 @@ struct osmo_sockaddr {
/*! use SO_REUSEADDR on UDP ports (required for multicast) */
#define OSMO_SOCK_F_UDP_REUSEADDR (1 << 5)
+/*! use OSMO_SOCK_F_DSCP(x) to set IP DSCP 'x' for packets transmitted on the socket */
+#define OSMO_SOCK_F_DSCP(x) (((x)&0x3f) << 24)
+#define GET_OSMO_SOCK_F_DSCP(f) (((f) >> 24) & 0x3f)
+
+/*! use OSMO_SOCK_F_PRIO(x) to set priority 'x' for packets transmitted on the socket */
+#define OSMO_SOCK_F_PRIO(x) (((x)&0xff) << 16)
+#define GET_OSMO_SOCK_F_PRIO(f) (((f) >> 16) & 0xff)
+
+
/*! maximum number of local or remote addresses supported by an osmo_sock instance */
#define OSMO_SOCK_MAX_ADDRS 32
@@ -78,16 +138,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);
-
int osmo_sock_unix_init(uint16_t type, uint8_t proto,
const char *socket_path, unsigned int flags);
@@ -113,9 +163,8 @@ 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(struct osmo_sockaddr *a, struct osmo_sockaddr *b);
+int osmo_sock_set_dscp(int fd, uint8_t dscp);
+int osmo_sock_set_priority(int fd, int prio);
#endif /* (!EMBEDDED) */
/*! @} */
diff --git a/include/osmocom/core/stat_item.h b/include/osmocom/core/stat_item.h
index 4710dba7..7f6857c0 100644
--- a/include/osmocom/core/stat_item.h
+++ b/include/osmocom/core/stat_item.h
@@ -6,6 +6,7 @@
#include <stdint.h>
+#include <osmocom/core/defs.h>
#include <osmocom/core/linuxlist.h>
struct osmo_stat_item_desc;
@@ -13,30 +14,16 @@ struct osmo_stat_item_desc;
#define OSMO_STAT_ITEM_NOVALUE_ID 0
#define OSMO_STAT_ITEM_NO_UNIT NULL
-/*! Individual entry in value FIFO */
-struct osmo_stat_item_value {
- int32_t id; /*!< identifier of value */
- int32_t value; /*!< actual value */
-};
-
-/*! data we keep for each actual item */
-struct osmo_stat_item {
- /*! back-reference to the item description */
- const struct osmo_stat_item_desc *desc;
- /*! the index of the freshest value */
- int32_t last_value_index;
- /*! offset to the freshest value in the value FIFO */
- int16_t last_offs;
- /*! value FIFO */
- struct osmo_stat_item_value values[0];
-};
+/*! data we keep for each actual item. Access via API functions only.
+ * (This struct was made opaque after libosmocore 1.5.1)*/
+struct osmo_stat_item;
/*! Statistics item description */
struct osmo_stat_item_desc {
const char *name; /*!< name of the item */
const char *description;/*!< description of the item */
const char *unit; /*!< unit of a value */
- unsigned int num_values;/*!< number of values to store in FIFO */
+ unsigned int num_values;/*!< DEPRECATED, this value is ignored after libosmocore version 1.5.1 */
int32_t default_value; /*!< default value */
};
@@ -62,13 +49,15 @@ struct osmo_stat_item_group {
const struct osmo_stat_item_group_desc *desc;
/*! The index of this value group within its class */
unsigned int idx;
+ /*! Optional string-based identifier to be used instead of index at report time */
+ char *name;
/*! Actual counter structures below */
struct osmo_stat_item *items[0];
};
struct osmo_stat_item_group *osmo_stat_item_group_alloc(
void *ctx,
- const struct osmo_stat_item_group_desc *desc,
+ const struct osmo_stat_item_group_desc *group_desc,
unsigned int idx);
static inline void osmo_stat_item_group_udp_idx(
@@ -76,7 +65,8 @@ static inline void osmo_stat_item_group_udp_idx(
{
grp->idx = idx;
}
-
+struct osmo_stat_item *osmo_stat_item_group_get_item(struct osmo_stat_item_group *grp, unsigned int idx);
+void osmo_stat_item_group_set_name(struct osmo_stat_item_group *statg, const char *name);
void osmo_stat_item_group_free(struct osmo_stat_item_group *statg);
void osmo_stat_item_inc(struct osmo_stat_item *item, int32_t value);
@@ -87,18 +77,14 @@ int osmo_stat_item_init(void *tall_ctx);
struct osmo_stat_item_group *osmo_stat_item_get_group_by_name_idx(
const char *name, const unsigned int idx);
+struct osmo_stat_item_group *osmo_stat_item_get_group_by_name_idxname(const char *group_name, const char *idx_name);
const struct osmo_stat_item *osmo_stat_item_get_by_name(
const struct osmo_stat_item_group *statg, const char *name);
-int osmo_stat_item_get_next(const struct osmo_stat_item *item, int32_t *idx, int32_t *value);
-
-/*! Get the last (freshest) value */
-static int32_t osmo_stat_item_get_last(const struct osmo_stat_item *item);
+int32_t osmo_stat_item_get_last(const struct osmo_stat_item *item);
-int osmo_stat_item_discard(const struct osmo_stat_item *item, int32_t *idx);
-
-int osmo_stat_item_discard_all(int32_t *idx);
+void osmo_stat_item_flush(struct osmo_stat_item *item);
typedef int (*osmo_stat_item_handler_t)(
struct osmo_stat_item_group *, struct osmo_stat_item *, void *);
@@ -110,12 +96,20 @@ int osmo_stat_item_for_each_item(struct osmo_stat_item_group *statg,
int osmo_stat_item_for_each_group(osmo_stat_item_group_handler_t handle_group, void *data);
-static inline int32_t osmo_stat_item_get_last(const struct osmo_stat_item *item)
-{
- return item->values[item->last_offs].value;
-}
-
void osmo_stat_item_reset(struct osmo_stat_item *item);
void osmo_stat_item_group_reset(struct osmo_stat_item_group *statg);
+const struct osmo_stat_item_desc *osmo_stat_item_get_desc(struct osmo_stat_item *item);
+
+/* DEPRECATION: up until libosmocore 1.5.1, this API also defined
+ * - struct osmo_stat_item_value
+ * - osmo_stat_item_get_next()
+ * - osmo_stat_item_discard()
+ * - osmo_stat_item_discard_all()
+ * Despite our principle of never breaking API compatibility, we have decided to remove these, because there are no
+ * known users. These items were never practically used/usable outside of libosmocore since the generic stats reporter
+ * (stats.c) was introduced.
+ * We also decided to make struct osmo_stat_item opaque to allow future changes of the struct without API breakage.
+ */
+
/*! @} */
diff --git a/include/osmocom/core/stats.h b/include/osmocom/core/stats.h
index b9edac2a..a034a616 100644
--- a/include/osmocom/core/stats.h
+++ b/include/osmocom/core/stats.h
@@ -13,10 +13,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#pragma once
@@ -108,10 +104,11 @@ struct osmo_stats_config {
int interval;
};
+extern struct llist_head osmo_stats_reporter_list;
extern struct osmo_stats_config *osmo_stats_config;
void osmo_stats_init(void *ctx);
-int osmo_stats_report();
+int osmo_stats_report(void);
int osmo_stats_set_interval(int interval);
diff --git a/include/osmocom/core/stats_tcp.h b/include/osmocom/core/stats_tcp.h
new file mode 100644
index 00000000..9bc7111a
--- /dev/null
+++ b/include/osmocom/core/stats_tcp.h
@@ -0,0 +1,16 @@
+#pragma once
+
+#define TCP_STATS_DEFAULT_INTERVAL 0 /* secs */
+#define TCP_STATS_DEFAULT_BATCH_SIZE 5 /* sockets per interval */
+
+struct osmo_tcp_stats_config {
+ /* poll interval in seconds, use osmo_stats_tcp_set_interval() to manipulate this value */
+ int interval;
+ /* specify how many sockets are processed when the interval timer expires */
+ int batch_size;
+};
+extern struct osmo_tcp_stats_config *osmo_tcp_stats_config;
+
+int osmo_stats_tcp_osmo_fd_register(const struct osmo_fd *fd, const char *name);
+int osmo_stats_tcp_osmo_fd_unregister(const struct osmo_fd *fd);
+int osmo_stats_tcp_set_interval(int interval);
diff --git a/include/osmocom/core/strrb.h b/include/osmocom/core/strrb.h
index b87239da..e3d3201c 100644
--- a/include/osmocom/core/strrb.h
+++ b/include/osmocom/core/strrb.h
@@ -14,10 +14,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#pragma once
diff --git a/include/osmocom/core/talloc.h b/include/osmocom/core/talloc.h
index c68a56cf..f15cd2a2 100644
--- a/include/osmocom/core/talloc.h
+++ b/include/osmocom/core/talloc.h
@@ -25,3 +25,5 @@ extern __thread struct osmo_talloc_contexts *osmo_ctx;
* to the various _c functions like msgb_alloc_c() */
#define OTC_GLOBAL (osmo_ctx->global)
#define OTC_SELECT (osmo_ctx->select)
+
+int osmo_ctx_init(const char *id);
diff --git a/include/osmocom/core/tdef.h b/include/osmocom/core/tdef.h
index 54819d95..d9d26751 100644
--- a/include/osmocom/core/tdef.h
+++ b/include/osmocom/core/tdef.h
@@ -40,6 +40,7 @@ enum osmo_tdef_unit {
OSMO_TDEF_MS, /*!< milliseconds */
OSMO_TDEF_M, /*!< minutes */
OSMO_TDEF_CUSTOM, /*!< unspecified unit, explained in osmo_tdef.desc. */
+ OSMO_TDEF_US, /*!< microseconds */
};
extern const struct value_string osmo_tdef_unit_names[];
@@ -161,7 +162,7 @@ const struct osmo_tdef_state_timeout *osmo_tdef_get_state_timeout(uint32_t state
__FILE__, __LINE__)
int _osmo_tdef_fsm_inst_state_chg(struct osmo_fsm_inst *fi, uint32_t state,
const struct osmo_tdef_state_timeout *timeouts_array,
- const struct osmo_tdef *tdefs, unsigned long default_timeout,
+ const struct osmo_tdef *tdefs, signed long default_timeout,
const char *file, int line);
/*! Manage timer definitions in named groups.
diff --git a/include/osmocom/core/thread.h b/include/osmocom/core/thread.h
new file mode 100644
index 00000000..d857268d
--- /dev/null
+++ b/include/osmocom/core/thread.h
@@ -0,0 +1,29 @@
+/*! \file thread.h
+ * Compatibility header with some thread related helpers
+ */
+/*
+ * (C) 2021 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ * Author: Pau Espin Pedrol <pespin@sysmocom.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+/*! \defgroup thread Osmocom thread helpers
+ * @{
+ * \file thread.h */
+
+#pragma once
+
+#include <sys/types.h>
+
+pid_t osmo_gettid(void);
diff --git a/include/osmocom/core/time_cc.h b/include/osmocom/core/time_cc.h
new file mode 100644
index 00000000..36fdee46
--- /dev/null
+++ b/include/osmocom/core/time_cc.h
@@ -0,0 +1,187 @@
+/*! \file time_cc.h
+ * Report the cumulative counter of time for which a flag is true as rate counter.
+ */
+/* Copyright (C) 2021 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * Author: Neels Hofmeyr <nhofmeyr@sysmocom.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+#pragma once
+
+#include <stdint.h>
+
+#include <osmocom/core/timer.h>
+
+/*! \defgroup time_cc Cumulative counter of time as rate counter.
+ * @{
+ * \file time_cc.h
+ */
+
+struct osmo_tdef;
+struct rate_ctr;
+
+/*! Configuration for osmo_time_cc.
+ * Report the cumulative counter of time for which a flag is true as rate counter.
+ * For example, for each second that the flag is true, increment a rate counter.
+ *
+ * The flag to be monitored is reported by osmo_time_cc_set_flag().
+ *
+ * The granularity defines how much time one rate counter increment represents:
+ * the default configuration is gran_usec = 1000000, i.e. one rate counter increment represents one second.
+ *
+ * Reporting as rate counter is configurable by round_threshold_usec and forget_sum_usec, examples:
+ *
+ * round_threshold_usec:
+ * - To get "ceil()" behavior, set round_threshold_usec = 1. This increments the rate counter for each gran_usec period
+ * where the flag was seen true, even if it was true for only a very short fraction of a gran_usec period.
+ * - To get "round()" behavior, set round_threshold_usec = half of gran_usec. The rate counter increments when the flag
+ * has been true for 0.5 of a gran_usec (and then again at 1.5 * gran_usec) of 'true' flag. round_threshold_usec = 0
+ * is a special value that means to use half of gran_usec.
+ * - To get "floor()" behavior, set round_threshold_usec >= gran_usec. The rate counter increments when reaching full
+ * gran_usec periods of the flag being true.
+ *
+ * forget_sum_usec:
+ * This is a tradeoff between the accuracy of the reported rate counter and making sure that the events reported are not
+ * irrelevantly long ago.
+ * - To keep sub-granularity-period surplus time forever, set forget_sum_usec = 0.
+ * - To keep surplus time for up to a minute, set forget_sum_usec = 60000000 (60 seconds).
+ * - To get rid of "leftover" time (almost) immediately after the flag goes false, set forget_sum_usec = 1.
+ * - If gran_usec is set to one second and forget_sum_usec is set to one minute, the reported rate counter has a
+ * possible inaccuracy of 1/60th, but makes sure that no timings older than a minute affect the current reports.
+ *
+ * Reporting modes in detail:
+ *
+ * The rate_ctr increments when the cumulative counter passes round_threshold_usec (default: half of gran_usec).
+ *
+ * sum ^
+ * | ________
+ * | /
+ * | /
+ * | /
+ * 3*gran --+--------------------------------------+
+ * | /:
+ * | / :
+ * | - - - - - - - - - - - - - - - - - / :
+ * | /. :
+ * | / . :
+ * 2*gran --+--------------------------------+ . :
+ * | /: . :
+ * | / : . :
+ * | - - - - - - - - - -_________/ : . :
+ * | / . : . :
+ * | / . : . :
+ * 1*gran --+-----------------+ . : . :
+ * | /: . : . :
+ * | / : . : . :
+ * | - - - - - - -/ : . : . :
+ * | /. : . : . :
+ * | ....-------' . : . : . :
+ * 0 +------------------------------------------------------------------------> elapsed time
+ * . : . : . :
+ * _ _ _______ ____________
+ * flag: __| |_| |____| . : |_______|. : . : |__________
+ * f t f t f t . : f t. : . : f
+ * round_threshold_usec : . : . : . :
+ * = 1 usec: 0 1 . :2 . :3 . :4 = "ceil()"
+ * = 0 == gran_usec/2: 0 1 : 2 : 3 : = "round()"
+ * >= gran_usec: 0 1 2 3 = "floor()"
+ *
+ */
+struct osmo_time_cc_cfg {
+ /*! Granularity in microseconds: nr of microseconds that one rate_ctr increment represents. A typical value is
+ * gran_usec = 1000000, meaning one rate counter increment represents one second. When zero, use 1000000. */
+ uint64_t gran_usec;
+ /*! Nr of microseconds above n * gran_usec at which to trigger a counter increment. When zero, use half a
+ * gran_usec. */
+ uint64_t round_threshold_usec;
+ /*! Forget counted sub-gran time after the flag was false for this long. */
+ uint64_t forget_sum_usec;
+ /*! Rate counter to report to, or NULL to not use it. */
+ struct rate_ctr *rate_ctr;
+
+ /*! Update gran_usec from this T timer value, or zero to not use any T timer. */
+ int T_gran;
+ /*! Update round_threshold_usec from this T timer value, or zero to not use any T timer. */
+ int T_round_threshold;
+ /*! Update forget_sum_usec from this T timer value, or zero to not use any T timer. */
+ int T_forget_sum;
+ /*! Look up T_gran and T_forget_sum in this list of timers, or NULL to not use any T timers. */
+ struct osmo_tdef *T_defs;
+};
+
+/*! Report the cumulative counter of time for which a flag is true as rate counter.
+ * See also osmo_time_cc_cfg for details on configuring.
+ *
+ * Usage:
+ *
+ * struct my_obj {
+ * struct osmo_time_cc flag_cc;
+ * };
+ *
+ * void my_obj_init(struct my_obj *my_obj)
+ * {
+ * osmo_time_cc_init(&my_obj->flag_cc);
+ * my_obj->flag_cc.cfg = (struct osmo_time_cc_cfg){
+ * .gran_usec = 1000000,
+ * .forget_sum_usec = 60000000,
+ * .rate_ctr = rate_ctr_group_get_ctr(my_ctrg, MY_CTR_IDX),
+ * };
+ * // optional: set initial flag state, default is 'false':
+ * // osmo_time_cc_set_flag(&my_obj->flag_cc, false);
+ * }
+ *
+ * void my_obj_event(struct my_obj *my_obj, bool flag)
+ * {
+ * osmo_time_cc_set_flag(&my_obj->flag_cc, flag);
+ * }
+ *
+ * void my_obj_destruct(struct my_obj *my_obj)
+ * {
+ * osmo_time_cc_cleanup(&my_obj->flag_cc);
+ * }
+ */
+struct osmo_time_cc {
+ struct osmo_time_cc_cfg cfg;
+
+ bool flag_state;
+
+ /*! Overall cumulative sum. Does not get reset for the entire lifetime of an osmo_time_cc.
+ * (Informational only, not used by the osmo_time_cc implementation.) */
+ uint64_t total_sum;
+
+ struct osmo_timer_list timer;
+
+ /*! CLOCK_MONOTONIC reading in microseconds, at the time when the osmo_time_cc instance started counting. */
+ uint64_t start_time;
+ /*! CLOCK_MONOTONIC reading in microseconds, at the time when the osmo_time_cc last evaluated the flag state and
+ * possibly added to the cumulated sum. */
+ uint64_t last_counted_time;
+
+ /*! Internal cumulative counter of time that flag_state was true. It may get reset to zero regularly, depending
+ * on cfg.forget_sum_usec. This is the basis for incrementing cfg.rate_ctr. */
+ uint64_t sum;
+ /*! The amount of time that already reported cfg.rate_ctr increments account for. This may be ahead of or behind
+ * 'sum', depending on cfg.round_threshold_usec. */
+ uint64_t reported_sum;
+};
+
+void osmo_time_cc_init(struct osmo_time_cc *tc);
+void osmo_time_cc_set_flag(struct osmo_time_cc *tc, bool flag);
+void osmo_time_cc_cleanup(struct osmo_time_cc *tc);
+
+/*! @} */
diff --git a/include/osmocom/core/timer.h b/include/osmocom/core/timer.h
index 19797662..16338dad 100644
--- a/include/osmocom/core/timer.h
+++ b/include/osmocom/core/timer.h
@@ -14,10 +14,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
/*! \defgroup timer Osmocom timers
@@ -75,7 +71,7 @@ void osmo_timer_schedule(struct osmo_timer_list *timer, int seconds, int microse
void osmo_timer_del(struct osmo_timer_list *timer);
-int osmo_timer_pending(struct osmo_timer_list *timer);
+int osmo_timer_pending(const struct osmo_timer_list *timer);
int osmo_timer_remaining(const struct osmo_timer_list *timer,
const struct timeval *now,
@@ -84,6 +80,7 @@ int osmo_timer_remaining(const struct osmo_timer_list *timer,
* internal timer list management
*/
struct timeval *osmo_timers_nearest(void);
+int osmo_timers_nearest_ms(void);
void osmo_timers_prepare(void);
int osmo_timers_update(void);
int osmo_timers_check(void);
diff --git a/include/osmocom/core/timer_compat.h b/include/osmocom/core/timer_compat.h
index 916f5684..fd52ae36 100644
--- a/include/osmocom/core/timer_compat.h
+++ b/include/osmocom/core/timer_compat.h
@@ -15,10 +15,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
/*! \defgroup timer Osmocom timers
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/use_count.h b/include/osmocom/core/use_count.h
index cc5493c7..532d8b14 100644
--- a/include/osmocom/core/use_count.h
+++ b/include/osmocom/core/use_count.h
@@ -19,10 +19,6 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#pragma once
diff --git a/include/osmocom/core/utils.h b/include/osmocom/core/utils.h
index 86191209..ee7cfa49 100644
--- a/include/osmocom/core/utils.h
+++ b/include/osmocom/core/utils.h
@@ -33,14 +33,23 @@
/*! 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))
+/*! Branch prediction optimizations */
+#if defined(__GNUC__)
+#define OSMO_LIKELY(exp) __builtin_expect(!!(exp), 1)
+#define OSMO_UNLIKELY(exp) __builtin_expect(!!(exp), 0)
+#else
+#define OSMO_LIKELY(exp) exp
+#define OSMO_UNLIKELY(exp) exp
+#endif
+
/*! A mapping between human-readable string and numeric value */
struct value_string {
- int value; /*!< numeric value */
+ uint32_t value; /*!< numeric value */
const char *str; /*!< human-readable string */
};
@@ -57,7 +66,7 @@ uint8_t osmo_char2bcd(char c);
int osmo_bcd2str(char *dst, size_t dst_size, const uint8_t *bcd, int start_nibble, int end_nibble, bool allow_hex);
int osmo_str2bcd(uint8_t *dst, size_t dst_size, const char *digits, int start_nibble, int end_nibble, bool allow_hex);
-int osmo_hexparse(const char *str, uint8_t *b, int max_len);
+int osmo_hexparse(const char *str, uint8_t *b, unsigned int max_len);
char *osmo_ubit_dump_buf(char *buf, size_t buf_len, const uint8_t *bits, unsigned int len);
char *osmo_ubit_dump(const uint8_t *bits, unsigned int len);
@@ -102,9 +111,11 @@ do { \
* the predicate evaluates to false (0).
*/
#define OSMO_ASSERT(exp) \
- if (!(exp)) { \
+do { \
+ if (OSMO_UNLIKELY(!(exp))) { \
osmo_panic("Assert failed %s %s:%d\n", #exp, __FILE__, __LINE__); \
- }
+ } \
+} while (0); /* some code invokes OSMO_ASSERT() without the semicolon */
/*! duplicate a string using talloc and release its prior content (if any)
* \param[in] ctx Talloc context to use for allocation
@@ -117,6 +128,8 @@ static inline void osmo_talloc_replace_string(void *ctx, char **dst, const char
*dst = talloc_strdup(ctx, newstr);
}
+void osmo_talloc_replace_string_fmt(void *ctx, char **dst, const char *fmt, ...);
+
/*! Append to a string and re-/allocate if necessary.
* \param[in] ctx Talloc context to use for initial allocation.
* \param[in,out] dest char* to re-/allocate and append to.
@@ -155,10 +168,12 @@ size_t osmo_quote_cstr_buf(char *buf, size_t bufsize, const char *str, int in_le
char *osmo_quote_cstr_c(void *ctx, const char *str, int in_len);
const char *osmo_escape_str(const char *str, int len);
+int osmo_escape_str_buf3(char *buf, size_t bufsize, const char *str, int in_len);
char *osmo_escape_str_buf2(char *buf, size_t bufsize, const char *str, int in_len);
const char *osmo_escape_str_buf(const char *str, int in_len, char *buf, size_t bufsize);
char *osmo_escape_str_c(const void *ctx, const char *str, int in_len);
const char *osmo_quote_str(const char *str, int in_len);
+int osmo_quote_str_buf3(char *buf, size_t bufsize, const char *str, int in_len);
char *osmo_quote_str_buf2(char *buf, size_t bufsize, const char *str, int in_len);
const char *osmo_quote_str_buf(const char *str, int in_len, char *buf, size_t bufsize);
char *osmo_quote_str_c(const void *ctx, const char *str, int in_len);
@@ -167,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. */
@@ -224,7 +251,7 @@ struct osmo_strbuf {
(STRBUF).pos = (STRBUF).buf; \
size_t _sb_remain = (STRBUF).buf ? (STRBUF).len - ((STRBUF).pos - (STRBUF).buf) : 0; \
int _sb_l = func((STRBUF).pos, _sb_remain, ##args); \
- if (_sb_l < 0 || _sb_l > _sb_remain) \
+ if (_sb_l < 0 || (size_t)_sb_l > _sb_remain) \
(STRBUF).pos = (STRBUF).buf + (STRBUF).len; \
else if ((STRBUF).pos) \
(STRBUF).pos += _sb_l; \
@@ -279,6 +306,13 @@ struct osmo_strbuf {
bool osmo_str_startswith(const char *str, const char *startswith_str);
+int osmo_float_str_to_int(int64_t *val, const char *str, unsigned int precision);
+int osmo_int_to_float_str_buf(char *buf, size_t buflen, int64_t val, unsigned int precision);
+char *osmo_int_to_float_str_c(void *ctx, int64_t val, unsigned int precision);
+
+int osmo_str_to_int64(int64_t *result, const char *str, int base, int64_t min_val, int64_t max_val);
+int osmo_str_to_int(int *result, const char *str, int base, int min_val, int max_val);
+
/*! Translate a buffer function to a talloc context function.
* This is the full function body of a char *foo_name_c(void *ctx, val...) function, implemented by an
* int foo_name_buf(buf, buflen, val...) function:
@@ -314,7 +348,7 @@ bool osmo_str_startswith(const char *str, const char *startswith_str);
_needed = FUNC_BUF(_str, _len, ## FUNC_BUF_ARGS); \
if (_needed < 0) \
goto OSMO_NAME_C_on_error; \
- if (_needed < _len) \
+ if ((unsigned int) _needed < _len) \
return _str; \
_len = _needed + 1; \
if (_str) \
diff --git a/include/osmocom/core/write_queue.h b/include/osmocom/core/write_queue.h
index 071621d1..6cb0a6b7 100644
--- a/include/osmocom/core/write_queue.h
+++ b/include/osmocom/core/write_queue.h
@@ -16,10 +16,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#pragma once
@@ -53,6 +49,7 @@ struct osmo_wqueue {
void osmo_wqueue_init(struct osmo_wqueue *queue, int max_length);
void osmo_wqueue_clear(struct osmo_wqueue *queue);
int osmo_wqueue_enqueue(struct osmo_wqueue *queue, struct msgb *data);
+int osmo_wqueue_enqueue_quiet(struct osmo_wqueue *queue, struct msgb *data);
int osmo_wqueue_bfd_cb(struct osmo_fd *fd, unsigned int what);
/*! @} */
diff --git a/include/osmocom/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..d872f642 100644
--- a/include/osmocom/crypt/auth.h
+++ b/include/osmocom/crypt/auth.h
@@ -30,10 +30,14 @@ 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_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_data {
diff --git a/include/osmocom/crypt/kdf.h b/include/osmocom/crypt/kdf.h
new file mode 100644
index 00000000..4a3b3b2e
--- /dev/null
+++ b/include/osmocom/crypt/kdf.h
@@ -0,0 +1,21 @@
+#pragma once
+
+/*! \defgroup kdf key derivation functions
+ * @{
+ * \file kdf.h */
+
+#include <stdint.h>
+
+void osmo_kdf_kc128(const uint8_t* ck, const uint8_t* ik, uint8_t* kc128);
+
+void osmo_kdf_kasme(const uint8_t *ck, const uint8_t *ik, const uint8_t* plmn_id,
+ const uint8_t *sqn, const uint8_t *ak, uint8_t *kasme);
+
+void osmo_kdf_enb(const uint8_t *kasme, uint32_t ul_count, uint8_t *kenb);
+
+void osmo_kdf_nh(const uint8_t *kasme, const uint8_t *sync_input, uint8_t *nh);
+
+void osmo_kdf_nas(uint8_t algo_type, uint8_t algo_id, const uint8_t *kasme, uint8_t *knas);
+
+
+/* @} */
diff --git a/include/osmocom/crypt/utran_cipher.h b/include/osmocom/crypt/utran_cipher.h
new file mode 100644
index 00000000..7dab3bc9
--- /dev/null
+++ b/include/osmocom/crypt/utran_cipher.h
@@ -0,0 +1,19 @@
+/*! \file utran_cipher.h */
+
+#pragma once
+
+/* 3GPP TS 25.413 § 9.2.1.11 */
+enum osmo_utran_integrity_algo {
+ OSMO_UTRAN_UIA1 = 0,
+ OSMO_UTRAN_UIA2 = 1,
+ _OSMO_UTRAN_UIA_NUM
+};
+
+/* 3GPP TS 25.413 § 9.2.1.12 */
+enum osmo_utran_encryption_algo {
+ OSMO_UTRAN_UEA0 = 0,
+ OSMO_UTRAN_UEA1 = 1,
+ OSMO_UTRAN_UEA2 = 2,
+ _OSMO_UTRAN_UEA_NUM
+};
+
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 5fa9588d..98cd100f 100644
--- a/include/osmocom/ctrl/control_if.h
+++ b/include/osmocom/ctrl/control_if.h
@@ -9,6 +9,7 @@ int ctrl_parse_get_num(vector vline, int i, long *num);
typedef int (*ctrl_cmd_lookup)(void *data, vector vline, int *node_type,
void **node_data, int *i);
+typedef void (*ctrl_cmd_reply_cb)(struct ctrl_handle *ctrl, struct ctrl_cmd *cmd, void *data);
struct ctrl_handle {
struct osmo_fd listen_fd;
@@ -18,6 +19,12 @@ struct ctrl_handle {
/* List of control connections */
struct llist_head ccon_list;
+
+ /* User defined GET/SET REPLY handler. User can set cmd->defer to 1 in
+ order to own and keep the cmd pointer and free it after the function
+ returns. "data" param is the user data pointer supplied during
+ ctrl_handle allocation */
+ ctrl_cmd_reply_cb reply_cb;
};
@@ -29,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/ctrl/ports.h b/include/osmocom/ctrl/ports.h
index 9759dc1e..b4bceef3 100644
--- a/include/osmocom/ctrl/ports.h
+++ b/include/osmocom/ctrl/ports.h
@@ -12,6 +12,7 @@
#define OSMO_CTRL_PORT_TRX 4236
/* 4237 used by VTY interface */
#define OSMO_CTRL_PORT_BTS 4238
+#define OSMO_CTRL_PORT_BSC_NEIGH 4248 /* osmo-bsc Neighbor Resloution Service */
#define OSMO_CTRL_PORT_NITB_BSC 4249
#define OSMO_CTRL_PORT_BSC_NAT 4250
#define OSMO_CTRL_PORT_SGSN 4251
@@ -27,4 +28,10 @@
/* 4266 used by D-GSM mDNS */
#define OSMO_CTRL_PORT_MGW 4267
#define OSMO_CTRL_PORT_SMLC 4272
+/* 4273 used by VTY interface */
+#define OSMO_CTRL_PORT_HNODEB 4274
+/* 4275: OSMO_VTY_PORT_UPF */
+#define OSMO_CTRL_PORT_UPF 4276
+/* 4277: OSMO_VTY_PORT_PFCP_TOOL */
+#define OSMO_CTRL_PORT_PFCP_TOOL 4278
/* When adding/changing port numbers, keep docs and wiki in sync. See above. */
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/bssgp_bvc_fsm.h b/include/osmocom/gprs/bssgp_bvc_fsm.h
new file mode 100644
index 00000000..9d3a6205
--- /dev/null
+++ b/include/osmocom/gprs/bssgp_bvc_fsm.h
@@ -0,0 +1,71 @@
+#pragma once
+#include <stdint.h>
+
+struct gprs_ns2_inst;
+struct osmo_fsm_inst;
+struct gprs_ra_id;
+struct bssgp2_flow_ctrl;
+
+enum bssp_ptp_bvc_fsm_state {
+ BSSGP_BVCFSM_S_NULL,
+ BSSGP_BVCFSM_S_BLOCKED,
+ BSSGP_BVCFSM_S_WAIT_RESET_ACK,
+ BSSGP_BVCFSM_S_UNBLOCKED,
+};
+
+enum bssgp_ptp_bvc_fsm_event {
+ /* Rx of BSSGP PDUs from the remote side; 'data' is 'struct tlv_parsed', and
+ * the assumption is that the caller has already validated all mandatory IEs
+ * are present and of sufficient length */
+ BSSGP_BVCFSM_E_RX_BLOCK,
+ BSSGP_BVCFSM_E_RX_BLOCK_ACK,
+ BSSGP_BVCFSM_E_RX_UNBLOCK,
+ BSSGP_BVCFSM_E_RX_UNBLOCK_ACK,
+ BSSGP_BVCFSM_E_RX_RESET,
+ BSSGP_BVCFSM_E_RX_RESET_ACK,
+ BSSGP_BVCFSM_E_RX_FC_BVC,
+ BSSGP_BVCFSM_E_RX_FC_BVC_ACK,
+ /* Requests of the local user */
+ BSSGP_BVCFSM_E_REQ_BLOCK, /* data: uint8_t *cause */
+ BSSGP_BVCFSM_E_REQ_UNBLOCK,
+ BSSGP_BVCFSM_E_REQ_RESET, /* data: uint8_t *cause */
+ BSSGP_BVCFSM_E_REQ_FC_BVC, /* data: struct bssgp2_flow_ctrl */
+};
+
+struct bssgp_bvc_fsm_ops {
+ /* call-back notifying the user of a state change */
+ void (*state_chg_notification)(uint16_t nsei, uint16_t bvci, int old_state, int new_state,
+ void *priv);
+ /* call-back notifying the user of a BVC-RESET event */
+ void (*reset_notification)(uint16_t nsei, uint16_t bvci, const struct gprs_ra_id *ra_id,
+ uint16_t cell_id, uint8_t cause, void *priv);
+ void (*rx_fc_bvc)(uint16_t nsei, uint16_t bvci, const struct bssgp2_flow_ctrl *fc, void *priv);
+ void (*reset_ack_notification)(uint16_t nsei, uint16_t bvci, const struct gprs_ra_id *ra_id,
+ uint16_t cell_id, uint8_t cause, void *priv);
+};
+
+struct osmo_fsm_inst *
+bssgp_bvc_fsm_alloc_sig_bss(void *ctx, struct gprs_ns2_inst *nsi, uint16_t nsei, uint32_t features);
+
+struct osmo_fsm_inst *
+bssgp_bvc_fsm_alloc_ptp_bss(void *ctx, struct gprs_ns2_inst *nsi, uint16_t nsei, uint16_t bvci,
+ const struct gprs_ra_id *ra_id, uint16_t cell_id);
+
+struct osmo_fsm_inst *
+bssgp_bvc_fsm_alloc_sig_sgsn(void *ctx, struct gprs_ns2_inst *nsi, uint16_t nsei, uint32_t features);
+
+struct osmo_fsm_inst *
+bssgp_bvc_fsm_alloc_ptp_sgsn(void *ctx, struct gprs_ns2_inst *nsi, uint16_t nsei, uint16_t bvci);
+
+void bssgp_bvc_fsm_set_ops(struct osmo_fsm_inst *fi, const struct bssgp_bvc_fsm_ops *ops, void *ops_priv);
+
+bool bssgp_bvc_fsm_is_unblocked(struct osmo_fsm_inst *fi);
+
+uint8_t bssgp_bvc_fsm_get_block_cause(struct osmo_fsm_inst *fi);
+
+uint32_t bssgp_bvc_fsm_get_features_advertised(struct osmo_fsm_inst *fi);
+uint32_t bssgp_bvc_fsm_get_features_received(struct osmo_fsm_inst *fi);
+uint32_t bssgp_bvc_fsm_get_features_negotiated(struct osmo_fsm_inst *fi);
+
+void bssgp_bvc_fsm_set_max_pdu_len(struct osmo_fsm_inst *fi, uint16_t max_pdu_len);
+uint16_t bssgp_bvc_fsm_get_max_pdu_len(const struct osmo_fsm_inst *fi); \ No newline at end of file
diff --git a/include/osmocom/gprs/frame_relay.h b/include/osmocom/gprs/frame_relay.h
new file mode 100644
index 00000000..81b42a69
--- /dev/null
+++ b/include/osmocom/gprs/frame_relay.h
@@ -0,0 +1,151 @@
+/*! \file frame_relay.h */
+
+/* (C) 2020 Harald Welte <laforge@gnumonks.org>
+ * (C) 2020 sysmocom - s.f.m.c. GmbH
+ * Author: Alexander Couzens <lynxis@fe80.eu>
+ *
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#pragma once
+
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/core/utils.h>
+
+#include <stdint.h>
+
+struct osmo_tdef;
+struct msgb;
+struct vty;
+
+enum osmo_fr_role {
+ FR_ROLE_USER_EQUIPMENT,
+ FR_ROLE_NETWORK_EQUIPMENT,
+};
+
+/* 48.016 § 6.1.4.2 default maximum information field size of 1600 octets */
+#define FRAME_RELAY_MTU 1600
+/* FR DLC header is 2 byte */
+#define FRAME_RELAY_SDU (FRAME_RELAY_MTU - 2)
+
+extern const struct value_string osmo_fr_role_names[];
+
+static inline const char *osmo_fr_role_str(enum osmo_fr_role role) {
+ return get_value_string(osmo_fr_role_names, role);
+}
+
+struct osmo_fr_network {
+ struct llist_head links;
+
+ unsigned int n391; /* full status polling counter */
+ unsigned int n392; /* error threshold */
+ unsigned int n393; /* monitored events count */
+
+ struct osmo_tdef *T_defs; /* T391, T392 */
+};
+
+struct osmo_fr_dlc;
+
+/* Frame Relay Link */
+struct osmo_fr_link {
+ /* list in osmo_fr_network.links */
+ struct llist_head list;
+ struct osmo_fr_network *net;
+ enum osmo_fr_role role;
+ /* human-readable name */
+ const char *name;
+
+ /* value of the last received send sequence number field in the
+ * link integrity verification information element */
+ uint8_t last_rx_seq;
+
+ /* value of the send sequence number field of the last link
+ * integrity verification information element sent */
+ uint8_t last_tx_seq;
+
+ struct osmo_timer_list t391;
+ struct osmo_timer_list t392;
+
+ unsigned int polling_count;
+ unsigned int err_count;
+ unsigned int succeed;
+ /* the type of the last status enquiry */
+ uint8_t expected_rep;
+ bool state;
+
+ /* list of data link connections at this link */
+ struct llist_head dlc_list;
+
+ /* optional call-back to be called for each PDU received on an unknown DLC */
+ int (*unknown_dlc_rx_cb)(void *cb_data, struct msgb *msg);
+ void *unknown_dlc_rx_cb_data;
+
+ /* call-back to be called for transmitting on the underlying hardware */
+ int (*tx_cb)(void *data, struct msgb *msg);
+ /* optional call-back to be called each time the status changes active/inactive */
+ void (*status_cb)(struct osmo_fr_link *link, void *cb_data, bool active);
+ void *cb_data;
+};
+
+/* Frame Relay Data Link Connection */
+struct osmo_fr_dlc {
+ /* entry in fr_link.dlc_list */
+ struct llist_head list;
+ struct osmo_fr_link *link;
+
+ uint16_t dlci;
+
+ /* is this DLC marked active for traffic? */
+ bool active;
+ /* was this DLC newly added? */
+ bool add;
+ /* is this DLC about to be destroyed */
+ bool del;
+
+ /* The local state needs to be transferred to the USER;
+ * NET must wait until USER confirms it implicitly by a seq number check */
+ bool state_send;
+
+ /* call-back to be called for each PDU received on this DLC */
+ int (*rx_cb)(void *cb_data, struct msgb *msg);
+ /* optional call-back to be called each time the status changes active/inactive */
+ void (*status_cb)(struct osmo_fr_dlc *dlc, void *cb_data, bool active);
+ void *cb_data;
+};
+
+/* allocate a frame relay network */
+struct osmo_fr_network *osmo_fr_network_alloc(void *ctx);
+void osmo_fr_network_free(struct osmo_fr_network *net);
+void osmo_fr_network_dump_vty(struct vty *vty, const struct osmo_fr_network *net);
+
+/* allocate a frame relay link in a given network */
+struct osmo_fr_link *osmo_fr_link_alloc(struct osmo_fr_network *net, enum osmo_fr_role role, const char *name);
+
+/* free a frame link in a given network */
+void osmo_fr_link_free(struct osmo_fr_link *link);
+
+/* allocate a data link connectoin on a given framerelay link */
+struct osmo_fr_dlc *osmo_fr_dlc_alloc(struct osmo_fr_link *link, uint16_t dlci);
+void osmo_fr_dlc_free(struct osmo_fr_dlc *dlc);
+
+struct osmo_fr_dlc *osmo_fr_dlc_by_dlci(struct osmo_fr_link *link, uint16_t dlci);
+
+int osmo_fr_rx(struct msgb *msg);
+int osmo_fr_tx_dlc(struct msgb *msg);
diff --git a/include/osmocom/gprs/gprs_bssgp.h b/include/osmocom/gprs/gprs_bssgp.h
index b9d251cc..6c043327 100644
--- a/include/osmocom/gprs/gprs_bssgp.h
+++ b/include/osmocom/gprs/gprs_bssgp.h
@@ -10,16 +10,32 @@
#include <osmocom/gsm/prim.h>
#include <osmocom/gprs/protocol/gsm_08_18.h>
+#include <osmocom/gprs/protocol/gsm_24_301.h>
+#include <osmocom/gprs/gprs_bssgp_rim.h>
/* gprs_bssgp_util.c */
-typedef int (*bssgp_bvc_send)(void *ctx, struct msgb *msg);
+#define BSSGP_PDUF_UL 0x0001 /* PDU may occur in uplink */
+#define BSSGP_PDUF_DL 0x0002 /* PDU may occur in downlink */
+#define BSSGP_PDUF_SIG 0x0004 /* PDU may occur on Signaling BVC */
+#define BSSGP_PDUF_PTP 0x0008 /* PDU may occur on PTP BVC */
+#define BSSGP_PDUF_PTM 0x0010 /* PDU may occur on PTM BVC */
+
+extern const struct osmo_tlv_prot_def osmo_pdef_bssgp;
+
+/*! return the PDU type flags (UL/DL/SIG/PTP/PTM) of specified PDU type */
+static inline uint32_t bssgp_pdu_type_flags(uint8_t pdu_type) {
+ return osmo_tlv_prot_msgt_flags(&osmo_pdef_bssgp, pdu_type);
+}
+
+typedef int (*bssgp_bvc_send)(void *ctx, struct msgb *msg);
extern struct gprs_ns_inst *bssgp_nsi;
void bssgp_set_bssgp_callback(bssgp_bvc_send ns_send, void *data);
struct msgb *bssgp_msgb_alloc(void);
struct msgb *bssgp_msgb_copy(const struct msgb *msg, const char *name);
const char *bssgp_cause_str(enum gprs_bssgp_cause cause);
const char *bssgp_pdu_str(enum bssgp_pdu_type pdu);
+int bssgp_tx_bvc_reset_nsei_bvci(uint16_t nsei, uint16_t bvci, enum gprs_bssgp_cause cause, const struct gprs_ra_id *ra_id, uint16_t cell_id);
/* Transmit a simple response such as BLOCK/UNBLOCK/RESET ACK/NACK */
int bssgp_tx_simple_bvci(uint8_t pdu_type, uint16_t nsei,
uint16_t bvci, uint16_t ns_bvci);
@@ -41,6 +57,8 @@ enum bssgp_prim {
PRIM_NM_BVC_BLOCK,
PRIM_NM_BVC_UNBLOCK,
PRIM_NM_STATUS,
+
+ PRIM_BSSGP_RIM_PDU_TRANSFER,
};
struct osmo_bssgp_prim {
@@ -58,6 +76,7 @@ struct osmo_bssgp_prim {
struct {
uint8_t suspend_ref;
} resume;
+ struct bssgp_ran_information_pdu rim_pdu;
} u;
};
@@ -106,11 +125,15 @@ struct bssgp_bvc_ctx {
/*! default bucket leak rate of per-MS bucket in octests/s */
uint32_t r_default_ms;
+ /*! BSS or SGSN. This defines the local state. */
+ bool is_sgsn;
/* we might want to add this as a shortcut later, avoiding the NSVC
* lookup for every packet, similar to a routing cache */
//struct gprs_nsvc *nsvc;
};
extern struct llist_head bssgp_bvc_ctxts;
+/* Create a BTS Context with BVCI+NSEI */
+struct bssgp_bvc_ctx *btsctx_alloc(uint16_t bvci, uint16_t nsei);
/* Find a BTS Context based on parsed RA ID and Cell ID */
struct bssgp_bvc_ctx *btsctx_by_raid_cid(const struct gprs_ra_id *raid, uint16_t cid);
/* Find a BTS context based on BVCI+NSEI tuple */
@@ -160,7 +183,7 @@ int bssgp_create_cell_id(uint8_t *buf, const struct gprs_ra_id *raid,
uint16_t cid);
/* Wrapper around TLV parser to parse BSSGP IEs */
-static inline int bssgp_tlv_parse(struct tlv_parsed *tp, uint8_t *buf, int len)
+static inline int bssgp_tlv_parse(struct tlv_parsed *tp, const uint8_t *buf, int len)
{
return tlv_parse(tp, &tvlv_att_def, buf, len, 0, 0);
}
@@ -212,11 +235,11 @@ 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 */
int bssgp_vty_init(void);
-void bssgp_set_log_ss(int ss);
+void bssgp_set_log_ss(int ss) OSMO_DEPRECATED("Use DLBSSGP instead!\n");
int bssgp_prim_cb(struct osmo_prim_hdr *oph, void *ctx);
diff --git a/include/osmocom/gprs/gprs_bssgp2.h b/include/osmocom/gprs/gprs_bssgp2.h
new file mode 100644
index 00000000..53e76e3a
--- /dev/null
+++ b/include/osmocom/gprs/gprs_bssgp2.h
@@ -0,0 +1,72 @@
+#pragma once
+#include <stdint.h>
+
+#include <osmocom/gprs/protocol/gsm_08_18.h>
+#include <osmocom/gprs/gprs_ns2.h>
+
+struct bssgp2_flow_ctrl;
+struct gprs_ns2_inst;
+struct gprs_ra_id;
+struct msgb;
+
+struct bssgp2_flow_ctrl {
+ uint8_t tag;
+ /* maximum bucket size (Bmax) in bytes */
+ uint64_t bucket_size_max;
+ /*! bucket leak rate in _bytes_ per second */
+ uint64_t bucket_leak_rate;
+ /* percentage how full the given bucket is */
+ uint8_t bucket_full_ratio;
+ bool bucket_full_ratio_present;
+ union {
+ /*! FC-BVC specifi members */
+ struct {
+ /*! default maximum bucket size per MS in bytes */
+ uint64_t bmax_default_ms;
+ /*! default bucket leak rate (R) for MS flow control bucket */
+ uint64_t r_default_ms;
+
+ /*! average milliseconds of queueing delay for a BVC */
+ uint32_t measurement;
+ bool measurement_present;
+ } bvc;
+ /*! FC-MS specifi members */
+ struct {
+ /*! TLLI of the MS */
+ uint32_t tlli;
+ } ms;
+ } u;
+};
+
+
+int bssgp2_nsi_tx_ptp(struct gprs_ns2_inst *nsi, uint16_t nsei, uint16_t bvci,
+ struct msgb *msg, uint32_t lsp);
+
+int bssgp2_nsi_tx_sig(struct gprs_ns2_inst *nsi, uint16_t nsei, struct msgb *msg, uint32_t lsp);
+
+struct msgb *bssgp2_enc_bvc_block(uint16_t bvci, enum gprs_bssgp_cause cause);
+
+struct msgb *bssgp2_enc_bvc_block_ack(uint16_t bvci);
+
+struct msgb *bssgp2_enc_bvc_unblock(uint16_t bvci);
+
+struct msgb *bssgp2_enc_bvc_unblock_ack(uint16_t bvci);
+
+struct msgb *bssgp2_enc_bvc_reset(uint16_t bvci, enum gprs_bssgp_cause cause,
+ const struct gprs_ra_id *ra_id, uint16_t cell_id,
+ const uint8_t *feat_bm, const uint8_t *ext_feat_bm);
+
+struct msgb *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);
+
+
+int bssgp2_dec_fc_bvc(struct bssgp2_flow_ctrl *fc, const struct tlv_parsed *tp);
+struct msgb *bssgp2_enc_fc_bvc(const struct bssgp2_flow_ctrl *fc, enum bssgp_fc_granularity *gran);
+struct msgb *bssgp2_enc_fc_bvc_ack(uint8_t tag);
+int bssgp2_dec_fc_ms(struct bssgp2_flow_ctrl *fc, struct tlv_parsed *tp);
+struct msgb *bssgp2_enc_fc_ms(const struct bssgp2_flow_ctrl *fc, enum bssgp_fc_granularity *gran);
+struct msgb *bssgp2_enc_fc_ms_ack(uint32_t tlli, uint8_t tag);
diff --git a/include/osmocom/gprs/gprs_bssgp_rim.h b/include/osmocom/gprs/gprs_bssgp_rim.h
new file mode 100644
index 00000000..5f397c98
--- /dev/null
+++ b/include/osmocom/gprs/gprs_bssgp_rim.h
@@ -0,0 +1,272 @@
+/*! \file gprs_bssgp.h
+ * GPRS BSSGP RIM protocol implementation as per 3GPP TS 48.018. */
+/*
+ * (C) 2020-2021 by sysmocom - s.f.m.c. GmbH
+ * Author: Philipp Maier <pmaier@sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#pragma once
+
+#include <osmocom/gsm/gsm48.h>
+#include <osmocom/gprs/protocol/gsm_08_18.h>
+#include <osmocom/gprs/protocol/gsm_24_301.h>
+
+enum bssgp_rim_routing_info_discr {
+ BSSGP_RIM_ROUTING_INFO_GERAN,
+ BSSGP_RIM_ROUTING_INFO_UTRAN,
+ BSSGP_RIM_ROUTING_INFO_EUTRAN,
+};
+
+extern const struct value_string bssgp_rim_routing_info_discr_strs[];
+
+/*! Obtain a human-readable string for NACC Cause code */
+static inline const char *bssgp_rim_routing_info_discr_str(enum bssgp_rim_routing_info_discr val)
+{ return get_value_string(bssgp_rim_routing_info_discr_strs, val); }
+
+/*! BSSGP RIM Routing information, see also 3GPP TS 48.018, section 11.3.70 */
+struct bssgp_rim_routing_info {
+ enum bssgp_rim_routing_info_discr discr;
+ union {
+ struct {
+ struct gprs_ra_id raid;
+ uint16_t cid;
+ } geran;
+ struct {
+ struct gprs_ra_id raid;
+ uint16_t rncid;
+ } utran;
+ struct {
+ struct osmo_eutran_tai tai;
+ /* See also 3GPP TS 36.413 9.2.1.37 and 3GPP TS 36.401 */
+ uint8_t global_enb_id[8];
+ uint8_t global_enb_id_len;
+ } eutran;
+ };
+};
+
+/* The encoded result of the rim routing information is, depending on the
+ * address type (discr) of variable length. */
+#define BSSGP_RIM_ROUTING_INFO_MAXLEN 14
+
+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_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 */
+struct bssgp_ran_inf_req_app_cont_nacc {
+ struct osmo_cell_global_id_ps reprt_cell;
+};
+
+int bssgp_dec_ran_inf_req_app_cont_nacc(struct bssgp_ran_inf_req_app_cont_nacc *cont, const uint8_t *buf, size_t len);
+int bssgp_enc_ran_inf_req_app_cont_nacc(uint8_t *buf, size_t len, const struct bssgp_ran_inf_req_app_cont_nacc *cont);
+
+/* Length of NACC system information, see also: 3GPP TS 48.018 11.3.63.2.1 */
+#define BSSGP_RIM_SI_LEN 21
+#define BSSGP_RIM_PSI_LEN 22
+
+/* 3GPP TS 48.018, table 11.3.63.2.1.a: RAN-INFORMATION Application Container coding for NACC */
+struct bssgp_ran_inf_app_cont_nacc {
+ struct osmo_cell_global_id_ps reprt_cell;
+ bool type_psi;
+ uint8_t num_si;
+
+ /* Pointer to system information messages */
+ const uint8_t *si[127];
+};
+
+int bssgp_dec_ran_inf_app_cont_nacc(struct bssgp_ran_inf_app_cont_nacc *cont, const uint8_t *buf, size_t len);
+int bssgp_enc_ran_inf_app_cont_nacc(uint8_t *buf, size_t len, const struct bssgp_ran_inf_app_cont_nacc *cont);
+
+/* 3GPP TS 48.018, table 11.3.64.1.b, NACC Cause coding */
+enum bssgp_nacc_cause {
+ BSSGP_NACC_CAUSE_UNSPEC,
+ BSSGP_NACC_CAUSE_SYNTAX_ERR,
+ BSSGP_NACC_CAUSE_RPRT_CELL_MISSMTCH,
+ BSSGP_NACC_CAUSE_SIPSI_TYPE_ERR,
+ BSSGP_NACC_CAUSE_SIPSI_LEN_ERR,
+ BSSGP_NACC_CAUSE_SIPSI_SET_ERR,
+};
+
+extern const struct value_string bssgp_nacc_cause_strs[];
+
+/*! Obtain a human-readable string for NACC Cause code */
+static inline const char *bssgp_nacc_cause_str(enum bssgp_nacc_cause val)
+{ return get_value_string(bssgp_nacc_cause_strs, val); }
+
+/* 3GPP TS 48.018, table 11.3.64.1.a, Application Error Container coding for NACC */
+struct bssgp_app_err_cont_nacc {
+ enum bssgp_nacc_cause nacc_cause;
+
+ /* Pointer to errornous application container */
+ const uint8_t *err_app_cont;
+ size_t err_app_cont_len;
+};
+
+int bssgp_dec_app_err_cont_nacc(struct bssgp_app_err_cont_nacc *cont, const uint8_t *buf, size_t len);
+int bssgp_enc_app_err_cont_nacc(uint8_t *buf, size_t len, const struct bssgp_app_err_cont_nacc *cont);
+
+/* 3GPP TS 48.018, table 11.3.61.b: RIM Application Identity coding */
+enum bssgp_ran_inf_app_id {
+ BSSGP_RAN_INF_APP_ID_NACC = 1,
+ BSSGP_RAN_INF_APP_ID_SI3 = 2,
+ BSSGP_RAN_INF_APP_ID_MBMS = 3,
+ BSSGP_RAN_INF_APP_ID_SON = 4,
+ BSSGP_RAN_INF_APP_ID_UTRA_SI = 5,
+};
+
+extern const struct value_string bssgp_ran_inf_app_id_strs[];
+
+/*! Obtain a human-readable string for RIM Application Identity code */
+static inline const char *bssgp_ran_inf_app_id_str(enum bssgp_ran_inf_app_id val)
+{ return get_value_string(bssgp_ran_inf_app_id_strs, val); }
+
+/* 3GPP TS 48.018, table 11.3.62a.1.b: RAN-INFORMATION-REQUEST RIM Container Contents */
+struct bssgp_ran_inf_req_rim_cont {
+ enum bssgp_ran_inf_app_id app_id;
+ uint32_t seq_num;
+ struct bssgp_rim_pdu_ind pdu_ind;
+ uint8_t prot_ver;
+
+ /* Nested application container */
+ union {
+ struct bssgp_ran_inf_req_app_cont_nacc app_cont_nacc;
+ /* TODO: add containers for Si3, MBMS, SON, UTRA-SI */
+ } u;
+
+ /* Pointer to SON-transfer application identity, only present if app_id is indicating "son-transfer",
+ * see also 3GPP TS 48.018, section 11.3.108 and 3GPP TS 36.413 annex B.1.1 */
+ const uint8_t *son_trans_app_id;
+ size_t son_trans_app_id_len;
+};
+
+int bssgp_dec_ran_inf_req_rim_cont(struct bssgp_ran_inf_req_rim_cont *cont, const uint8_t *buf, size_t len);
+int bssgp_enc_ran_inf_req_rim_cont(uint8_t *buf, size_t len, const struct bssgp_ran_inf_req_rim_cont *cont);
+
+/* 3GPP TS 48.018, table 11.3.62a.2.b: RAN-INFORMATION RIM Container Contents */
+struct bssgp_ran_inf_rim_cont {
+ enum bssgp_ran_inf_app_id app_id;
+ uint32_t seq_num;
+ struct bssgp_rim_pdu_ind pdu_ind;
+ uint8_t prot_ver;
+ bool app_err;
+
+ /* Nested application container */
+ union {
+ struct bssgp_ran_inf_app_cont_nacc app_cont_nacc;
+ struct bssgp_app_err_cont_nacc app_err_cont_nacc;
+ /* TODO: add containers for Si3, MBMS, SON, UTRA-SI */
+ } u;
+
+ /* Pointer to SON-transfer application identity, only present if app_id is indicating "son-transfer",
+ * see also 3GPP TS 48.018, section 11.3.108 and 3GPP TS 36.413 annex B.1.1 */
+ const uint8_t *son_trans_app_id;
+ size_t son_trans_app_id_len;
+};
+
+int bssgp_dec_ran_inf_rim_cont(struct bssgp_ran_inf_rim_cont *cont, const uint8_t *buf, size_t len);
+int bssgp_enc_ran_inf_rim_cont(uint8_t *buf, size_t len, const struct bssgp_ran_inf_rim_cont *cont);
+
+/* 3GPP TS 48.018, table 11.3.62a.3.b: RAN-INFORMATION-ACK RIM Container Contents */
+struct bssgp_ran_inf_ack_rim_cont {
+ enum bssgp_ran_inf_app_id app_id;
+ uint32_t seq_num;
+ uint8_t prot_ver;
+
+ /* Pointer to SON-transfer application identity, only present if app_id is indicating "son-transfer",
+ * see also 3GPP TS 48.018, section 11.3.108 and 3GPP TS 36.413 annex B.1.1 */
+ const uint8_t *son_trans_app_id;
+ size_t son_trans_app_id_len;
+};
+
+int bssgp_dec_ran_inf_ack_rim_cont(struct bssgp_ran_inf_ack_rim_cont *cont, const uint8_t *buf, size_t len);
+int bssgp_enc_ran_inf_ack_rim_cont(uint8_t *buf, size_t len, const struct bssgp_ran_inf_ack_rim_cont *cont);
+
+/* 3GPP TS 48.018, table 11.3.62a.4.b: RAN-INFORMATION-ERROR RIM Container Contents */
+struct bssgp_ran_inf_err_rim_cont {
+ enum bssgp_ran_inf_app_id app_id;
+ uint8_t cause;
+ uint8_t prot_ver;
+
+ /* Pointer to (encoded) errornous PDU,
+ * see also: 3GPP TS 48.018, section 11.3.24 */
+ const uint8_t *err_pdu;
+ size_t err_pdu_len;
+
+ /* Pointer to SON-transfer application identity, only present if app_id is indicating "son-transfer",
+ * see also 3GPP TS 48.018, section 11.3.108 and 3GPP TS 36.413 annex B.1.1 */
+ const uint8_t *son_trans_app_id;
+ size_t son_trans_app_id_len;
+};
+
+int bssgp_dec_ran_inf_err_rim_cont(struct bssgp_ran_inf_err_rim_cont *cont, const uint8_t *buf, size_t len);
+int bssgp_enc_ran_inf_err_rim_cont(uint8_t *buf, size_t len, const struct bssgp_ran_inf_err_rim_cont *cont);
+
+/* 3GPP TS 48.018, table 11.3.62a.5.b: RAN-INFORMATION-APPLICATION-ERROR RIM Container Contents */
+struct bssgp_ran_inf_app_err_rim_cont {
+ enum bssgp_ran_inf_app_id app_id;
+ uint32_t seq_num;
+ struct bssgp_rim_pdu_ind pdu_ind;
+ uint8_t prot_ver;
+
+ /* Nested application container */
+ union {
+ struct bssgp_app_err_cont_nacc app_err_cont_nacc;
+ /* TODO: add containers for Si3, MBMS, SON, UTRA-SI */
+ } u;
+};
+
+int bssgp_dec_ran_inf_app_err_rim_cont(struct bssgp_ran_inf_app_err_rim_cont *cont, const uint8_t *buf, size_t len);
+int bssgp_enc_ran_inf_app_err_rim_cont(uint8_t *buf, size_t len, const struct bssgp_ran_inf_app_err_rim_cont *cont);
+
+/* Chapter 10.6.1: RAN-INFORMATION-REQUEST */
+struct bssgp_ran_information_pdu {
+ struct bssgp_rim_routing_info routing_info_dest;
+ struct bssgp_rim_routing_info routing_info_src;
+
+ /* Encoded variant of the RIM container */
+ uint8_t rim_cont_iei;
+ const uint8_t *rim_cont;
+ unsigned int rim_cont_len;
+
+ /* Decoded variant of the RIM container */
+ bool decoded_present;
+ union {
+ struct bssgp_ran_inf_req_rim_cont req_rim_cont;
+ struct bssgp_ran_inf_rim_cont rim_cont;
+ struct bssgp_ran_inf_ack_rim_cont ack_rim_cont;
+ struct bssgp_ran_inf_err_rim_cont err_rim_cont;
+ struct bssgp_ran_inf_app_err_rim_cont app_err_rim_cont;
+ } decoded;
+
+ /* When receiving a PDU from BSSGP the encoded variant of the RIM
+ * container will always be present. The decoded variant will be
+ * present in addition whenever BSSGP was able to decode the container.
+ *
+ * When sending a PDU to BSSGP, then the decoded variant is used when
+ * it is available. The encoded variant (if present) will be ignored
+ * then. */
+};
+
+int bssgp_parse_rim_pdu(struct bssgp_ran_information_pdu *pdu, const struct msgb *msg);
+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);
diff --git a/include/osmocom/gprs/gprs_ns2.h b/include/osmocom/gprs/gprs_ns2.h
index 68484c14..a9c144cc 100644
--- a/include/osmocom/gprs/gprs_ns2.h
+++ b/include/osmocom/gprs/gprs_ns2.h
@@ -7,8 +7,12 @@
#include <netinet/in.h>
#include <osmocom/core/prim.h>
+#include <osmocom/gprs/protocol/gsm_08_16.h>
+#include <osmocom/gprs/frame_relay.h>
struct osmo_sockaddr;
+struct osmo_sockaddr_str;
+struct osmo_fr_network;
struct gprs_ns2_inst;
struct gprs_ns2_nse;
@@ -19,43 +23,89 @@ struct gprs_ns_ie_ip4_elem;
struct gprs_ns_ie_ip6_elem;
enum gprs_ns2_vc_mode {
- NS2_VC_MODE_BLOCKRESET, /* The VC will use RESET/BLOCK/UNBLOCK to start the connection and do ALIVE/ACK */
- NS2_VC_MODE_ALIVE, /* The will only use ALIVE/ACK */
+ /*! The VC will use RESET/BLOCK/UNBLOCK to start the connection and do ALIVE/ACK.
+ * This is what is needed for Frame Relay transport, and if you use a R97/R99 Gb
+ * interface over an IP transport (never standardized by 3GPP) */
+ GPRS_NS2_VC_MODE_BLOCKRESET,
+ /*! The VC will only use ALIVE/ACK (no RESET/BLOCK/UNBLOCK), which is for Gb-IP
+ * interface compliant to 3GPP Rel=4 or later. */
+ GPRS_NS2_VC_MODE_ALIVE,
};
-/*! Osmocom NS primitives according to 48.016 5.2 Service primitves */
+enum gprs_ns2_dialect {
+ GPRS_NS2_DIALECT_UNDEF,
+ GPRS_NS2_DIALECT_STATIC_ALIVE,
+ GPRS_NS2_DIALECT_STATIC_RESETBLOCK,
+ GPRS_NS2_DIALECT_IPACCESS,
+ GPRS_NS2_DIALECT_SNS,
+};
+
+/*! Osmocom NS link layer types */
+enum gprs_ns2_ll {
+ GPRS_NS2_LL_UNDEF, /*!< undefined, used by vty */
+ GPRS_NS2_LL_UDP, /*!< NS/UDP/IP */
+ GPRS_NS2_LL_FR, /*!< NS/FR */
+ GPRS_NS2_LL_FR_GRE, /*!< NS/FR/GRE/IP */
+};
+
+/*! Osmocom NS primitives according to 48.016 5.2 Service primitives */
enum gprs_ns2_prim {
- PRIM_NS_UNIT_DATA,
- PRIM_NS_CONGESTION,
- PRIM_NS_STATUS,
+ GPRS_NS2_PRIM_UNIT_DATA,
+ GPRS_NS2_PRIM_CONGESTION,
+ GPRS_NS2_PRIM_STATUS,
};
-/*! Osmocom NS primitives according to 48.016 5.2.2.4 Service primitves */
+extern const struct value_string gprs_ns2_prim_strs[];
+extern const struct value_string gprs_ns2_lltype_strs[];
+
+/*! Obtain a human-readable string for NS primitives */
+static inline const char *gprs_ns2_prim_str(enum gprs_ns2_prim val)
+{ return get_value_string(gprs_ns2_prim_strs, val); }
+
+/*! Obtain a human-readable string for NS link-layer type */
+static inline const char *gprs_ns2_lltype_str(enum gprs_ns2_ll val)
+{ return get_value_string(gprs_ns2_lltype_strs, val); }
+
+/*! Osmocom NS primitives according to 48.016 5.2.2.4 Service primitives */
enum gprs_ns2_congestion_cause {
- NS_CONG_CAUSE_BACKWARD_BEGIN,
- NS_CONG_CAUSE_BACKWARD_END,
- NS_CONG_CAUSE_FORWARD_BEGIN,
- NS_CONG_CAUSE_FORWARD_END,
+ GPRS_NS2_CONG_CAUSE_BACKWARD_BEGIN,
+ GPRS_NS2_CONG_CAUSE_BACKWARD_END,
+ GPRS_NS2_CONG_CAUSE_FORWARD_BEGIN,
+ GPRS_NS2_CONG_CAUSE_FORWARD_END,
};
-/*! Osmocom NS primitives according to 48.016 5.2.2.6 Service primitves */
+/*! Osmocom NS primitives according to 48.016 5.2.2.6 Service primitives */
enum gprs_ns2_affecting_cause {
- NS_AFF_CAUSE_VC_FAILURE,
- NS_AFF_CAUSE_VC_RECOVERY,
- NS_AFF_CAUSE_FAILURE,
- NS_AFF_CAUSE_RECOVERY,
+ GPRS_NS2_AFF_CAUSE_VC_FAILURE,
+ GPRS_NS2_AFF_CAUSE_VC_RECOVERY,
+ GPRS_NS2_AFF_CAUSE_FAILURE,
+ GPRS_NS2_AFF_CAUSE_RECOVERY,
/* osmocom own causes */
- NS_AFF_CAUSE_SNS_CONFIGURED,
- NS_AFF_CAUSE_SNS_FAILURE,
+ GPRS_NS2_AFF_CAUSE_SNS_CONFIGURED,
+ GPRS_NS2_AFF_CAUSE_SNS_FAILURE,
+ GPRS_NS2_AFF_CAUSE_SNS_NO_ENDPOINTS,
+ GPRS_NS2_AFF_CAUSE_MTU_CHANGE,
};
-/*! Osmocom NS primitives according to 48.016 5.2.2.7 Service primitves */
+extern const struct value_string gprs_ns2_aff_cause_prim_strs[];
+
+/*! Obtain a human-readable string for NS affecting cause in primitives */
+static inline const char *gprs_ns2_aff_cause_prim_str(enum gprs_ns2_affecting_cause val)
+{ return get_value_string(gprs_ns2_aff_cause_prim_strs, val); }
+
+/*! Osmocom NS primitives according to 48.016 5.2.2.7 Service primitives */
enum gprs_ns2_change_ip_endpoint {
- NS_ENDPOINT_NO_CHANGE,
- NS_ENDPOINT_REQUEST_CHANGE,
- NS_ENDPOINT_CONFIRM_CHANGE,
+ GRPS_NS2_ENDPOINT_NO_CHANGE,
+ GPRS_NS2_ENDPOINT_REQUEST_CHANGE,
+ GPRS_NS2_ENDPOINT_CONFIRM_CHANGE,
};
+extern const struct value_string gprs_ns2_cause_strs[];
+
+/*! Obtain a human-readable string for NS primitives */
+static inline const char *gprs_ns2_cause_str(enum ns_cause val)
+{ return get_value_string(gprs_ns2_cause_strs, val); }
+
struct osmo_gprs_ns2_prim {
struct osmo_prim_hdr oph;
@@ -65,6 +115,7 @@ struct osmo_gprs_ns2_prim {
union {
struct {
enum gprs_ns2_change_ip_endpoint change;
+ uint32_t link_selector;
/* TODO: implement resource distribution
* add place holder for the link selector */
long long _resource_distribution_placeholder1;
@@ -76,8 +127,17 @@ struct osmo_gprs_ns2_prim {
} congestion;
struct {
enum gprs_ns2_affecting_cause cause;
+ char *nsvc;
/* 48.016 5.2.2.6 transfer capability */
int transfer;
+ /* osmocom specific */
+ /* Persistent NSE/NSVC are configured by vty */
+ bool persistent;
+ /* Only true on the first time it's available.
+ * Allow the BSSGP layer to reset persistent NSE */
+ bool first;
+ /* MTU of a NS SDU. It's the lowest MTU of all (alive & dead) NSVCs */
+ uint16_t mtu;
} status;
} u;
};
@@ -85,75 +145,132 @@ struct osmo_gprs_ns2_prim {
/* instance */
struct gprs_ns2_inst *gprs_ns2_instantiate(void *ctx, osmo_prim_cb cb, void *cb_data);
void gprs_ns2_free(struct gprs_ns2_inst *inst);
-int gprs_ns2_dynamic_create_nse(struct gprs_ns2_inst *nsi, bool create_nse);
/* Entrypoint for primitives from the NS USER */
int gprs_ns2_recv_prim(struct gprs_ns2_inst *nsi, struct osmo_prim_hdr *oph);
+/*! a callback to iterate over all NSVC */
+typedef int (*gprs_ns2_foreach_nsvc_cb)(struct gprs_ns2_vc *nsvc, void *ctx);
+
+int gprs_ns2_nse_foreach_nsvc(struct gprs_ns2_nse *nse,
+ gprs_ns2_foreach_nsvc_cb cb, void *cb_data);
struct gprs_ns2_nse *gprs_ns2_nse_by_nsei(struct gprs_ns2_inst *nsi, uint16_t nsei);
-struct gprs_ns2_nse *gprs_ns2_create_nse(struct gprs_ns2_inst *nsi, uint16_t nsei);
+struct gprs_ns2_nse *gprs_ns2_create_nse(struct gprs_ns2_inst *nsi, uint16_t nsei,
+ enum gprs_ns2_ll linklayer,
+ enum gprs_ns2_dialect dialect);
+struct gprs_ns2_nse *gprs_ns2_create_nse2(struct gprs_ns2_inst *nsi, uint16_t nsei,
+ enum gprs_ns2_ll linklayer,
+ enum gprs_ns2_dialect dialect, bool local_sgsn_role);
+uint16_t gprs_ns2_nse_nsei(struct gprs_ns2_nse *nse);
void gprs_ns2_free_nse(struct gprs_ns2_nse *nse);
+void gprs_ns2_free_nses(struct gprs_ns2_inst *nsi);
/* create vc */
void gprs_ns2_free_nsvc(struct gprs_ns2_vc *nsvc);
+void gprs_ns2_free_nsvcs(struct gprs_ns2_nse *nse);
struct gprs_ns2_vc *gprs_ns2_nsvc_by_nsvci(struct gprs_ns2_inst *nsi, uint16_t nsvci);
+/* generic VL driver */
+struct gprs_ns2_vc_bind *gprs_ns2_bind_by_name(struct gprs_ns2_inst *nsi,
+ const char *name);
+
/* IP VL driver */
int gprs_ns2_ip_bind(struct gprs_ns2_inst *nsi,
- struct osmo_sockaddr *local,
+ const char *name,
+ const struct osmo_sockaddr *local,
int dscp,
struct gprs_ns2_vc_bind **result);
-void gprs_ns2_bind_set_mode(struct gprs_ns2_vc_bind *bind, enum gprs_ns2_vc_mode mode);
+struct gprs_ns2_vc_bind *gprs_ns2_ip_bind_by_sockaddr(struct gprs_ns2_inst *nsi,
+ const struct osmo_sockaddr *sockaddr);
+
+/* FR VL driver */
+struct gprs_ns2_vc_bind *gprs_ns2_fr_bind_by_netif(
+ struct gprs_ns2_inst *nsi,
+ const char *netif);
+const char *gprs_ns2_fr_bind_netif(struct gprs_ns2_vc_bind *bind);
+enum osmo_fr_role gprs_ns2_fr_bind_role(struct gprs_ns2_vc_bind *bind);
+int gprs_ns2_fr_bind(struct gprs_ns2_inst *nsi,
+ const char *name,
+ const char *netif,
+ struct osmo_fr_network *fr_network,
+ enum osmo_fr_role fr_role,
+ struct gprs_ns2_vc_bind **result);
+int gprs_ns2_is_fr_bind(struct gprs_ns2_vc_bind *bind);
+struct gprs_ns2_vc *gprs_ns2_fr_nsvc_by_dlci(struct gprs_ns2_vc_bind *bind, uint16_t dlci);
+struct gprs_ns2_vc *gprs_ns2_fr_connect(struct gprs_ns2_vc_bind *bind,
+ struct gprs_ns2_nse *nse,
+ uint16_t nsvci,
+ uint16_t dlci);
+struct gprs_ns2_vc *gprs_ns2_fr_connect2(struct gprs_ns2_vc_bind *bind,
+ uint16_t nsei,
+ uint16_t nsvci,
+ uint16_t dlci);
/* create a VC connection */
struct gprs_ns2_vc *gprs_ns2_ip_connect(struct gprs_ns2_vc_bind *bind,
- struct osmo_sockaddr *remote,
+ const struct osmo_sockaddr *remote,
struct gprs_ns2_nse *nse,
uint16_t nsvci);
struct gprs_ns2_vc *gprs_ns2_ip_connect2(struct gprs_ns2_vc_bind *bind,
- struct osmo_sockaddr *remote,
+ const struct osmo_sockaddr *remote,
uint16_t nsei,
- uint16_t nsvci);
+ uint16_t nsvci,
+ enum gprs_ns2_dialect dialect);
struct gprs_ns2_vc *gprs_ns2_ip_connect_inactive(struct gprs_ns2_vc_bind *bind,
- struct osmo_sockaddr *remote,
+ const struct osmo_sockaddr *remote,
struct gprs_ns2_nse *nse,
uint16_t nsvci);
+void gprs_ns2_ip_bind_set_sns_weight(struct gprs_ns2_vc_bind *bind,
+ uint8_t signalling, uint8_t data);
void gprs_ns2_free_bind(struct gprs_ns2_vc_bind *bind);
+void gprs_ns2_free_binds(struct gprs_ns2_inst *nsi);
/* create a VC SNS connection */
-int gprs_ns2_ip_connect_sns(struct gprs_ns2_vc_bind *bind,
- struct osmo_sockaddr *remote,
- uint16_t nsei);
+int gprs_ns2_sns_count(struct gprs_ns2_nse *nse);
+int gprs_ns2_sns_add_endpoint(struct gprs_ns2_nse *nse,
+ const struct osmo_sockaddr *saddr);
+int gprs_ns2_sns_del_endpoint(struct gprs_ns2_nse *nse,
+ const struct osmo_sockaddr *saddr);
+int gprs_ns2_sns_add_bind(struct gprs_ns2_nse *nse, struct gprs_ns2_vc_bind *bind);
+int gprs_ns2_sns_del_bind(struct gprs_ns2_nse *nse, struct gprs_ns2_vc_bind *bind);
+const struct osmo_sockaddr *gprs_ns2_nse_sns_remote(struct gprs_ns2_nse *nse);
-struct osmo_sockaddr *gprs_ns2_ip_vc_sockaddr(struct gprs_ns2_vc *nsvc);
-struct osmo_sockaddr *gprs_ns2_ip_bind_sockaddr(struct gprs_ns2_vc_bind *bind);
+const struct osmo_sockaddr *gprs_ns2_ip_vc_remote(const struct gprs_ns2_vc *nsvc);
+const struct osmo_sockaddr *gprs_ns2_ip_vc_local(const struct gprs_ns2_vc *nsvc);
+bool gprs_ns2_ip_vc_equal(const struct gprs_ns2_vc *nsvc,
+ const struct osmo_sockaddr *local,
+ const struct osmo_sockaddr *remote,
+ uint16_t nsvci);
+const struct osmo_sockaddr *gprs_ns2_ip_bind_sockaddr(struct gprs_ns2_vc_bind *bind);
int gprs_ns2_is_ip_bind(struct gprs_ns2_vc_bind *bind);
int gprs_ns2_ip_bind_set_dscp(struct gprs_ns2_vc_bind *bind, int dscp);
-int gprs_ns2_find_vc_by_sockaddr(
+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,
- struct osmo_sockaddr *saddr,
- struct gprs_ns2_vc **result);
+ const struct osmo_sockaddr *saddr);
int gprs_ns2_frgre_bind(struct gprs_ns2_inst *nsi,
- struct osmo_sockaddr *local,
+ const char *name,
+ const struct osmo_sockaddr *local,
int dscp,
struct gprs_ns2_vc_bind **result);
int gprs_ns2_is_frgre_bind(struct gprs_ns2_vc_bind *bind);
+uint16_t gprs_ns2_fr_nsvc_dlci(const struct gprs_ns2_vc *nsvc);
-struct gprs_ns2_vc *gprs_ns2_nsvc_by_sockaddr(struct gprs_ns2_nse *nsei,
- struct osmo_sockaddr *sockaddr);
+struct gprs_ns2_vc *gprs_ns2_nsvc_by_sockaddr_nse(
+ struct gprs_ns2_nse *nse,
+ const struct osmo_sockaddr *sockaddr);
void gprs_ns2_start_alive_all_nsvcs(struct gprs_ns2_nse *nse);
-const char *gprs_ns2_cause_str(int cause);
+
+/* VC information */
const char *gprs_ns2_ll_str(struct gprs_ns2_vc *nsvc);
char *gprs_ns2_ll_str_buf(char *buf, size_t buf_len, struct gprs_ns2_vc *nsvc);
char *gprs_ns2_ll_str_c(const void *ctx, struct gprs_ns2_vc *nsvc);
+const char *gprs_ns2_nsvc_state_name(struct gprs_ns2_vc *nsvc);
/* vty */
int gprs_ns2_vty_init(struct gprs_ns2_inst *nsi);
-int gprs_ns2_vty_create();
-void gprs_ns2_vty_force_vc_mode(bool force, enum gprs_ns2_vc_mode mode, char *reason);
-
/*! @} */
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 05728f49..c2c11a7d 100644
--- a/include/osmocom/gprs/protocol/gsm_04_60.h
+++ b/include/osmocom/gprs/protocol/gsm_04_60.h
@@ -1,200 +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 04.60 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;
- } 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_16.h b/include/osmocom/gprs/protocol/gsm_08_16.h
index e035ebf0..f98f68dd 100644
--- a/include/osmocom/gprs/protocol/gsm_08_16.h
+++ b/include/osmocom/gprs/protocol/gsm_08_16.h
@@ -7,6 +7,7 @@
#include <stdint.h>
#include <arpa/inet.h>
+#include <osmocom/core/utils.h>
/*! \addtogroup libgb
* @{
diff --git a/include/osmocom/gprs/protocol/gsm_08_18.h b/include/osmocom/gprs/protocol/gsm_08_18.h
index af6caf32..1152eb6c 100644
--- a/include/osmocom/gprs/protocol/gsm_08_18.h
+++ b/include/osmocom/gprs/protocol/gsm_08_18.h
@@ -1,24 +1,32 @@
/*! \file gsm_08_18.h */
+/* Updated to reflect TS 48.018 version 15.0.0 Release 15 */
#pragma once
#include <stdint.h>
+#include <osmocom/core/endian.h>
/*! Fixed BVCI definitions (Section 5.4.1) */
#define BVCI_SIGNALLING 0x0000
#define BVCI_PTM 0x0001
+/* typo backwards compatiblity */
+#define BSSGP_PDUT_RA_CAPA_UDPATE BSSGP_PDUT_RA_CAPA_UPDATE
+
/*! BSSGP PDU types (Section 11.3.26 / Table 11.27) */
enum bssgp_pdu_type {
/* PDUs between RL and BSSGP SAPs */
BSSGP_PDUT_DL_UNITDATA = 0x00,
BSSGP_PDUT_UL_UNITDATA = 0x01,
BSSGP_PDUT_RA_CAPABILITY = 0x02,
- BSSGP_PDUT_PTM_UNITDATA = 0x03,
+ /* PDUs between MBMS SAPs */
+ BSSGP_PDUT_PTM_UNITDATA = 0x03, /* reserved in later specs */
+ BSSGP_PDUT_DL_MMBS_UNITDATA = 0x04,
+ BSSGP_PDUT_UL_MMBS_UNITDATA = 0x05,
/* PDUs between GMM SAPs */
BSSGP_PDUT_PAGING_PS = 0x06,
BSSGP_PDUT_PAGING_CS = 0x07,
- BSSGP_PDUT_RA_CAPA_UDPATE = 0x08,
+ BSSGP_PDUT_RA_CAPA_UPDATE = 0x08,
BSSGP_PDUT_RA_CAPA_UPDATE_ACK = 0x09,
BSSGP_PDUT_RADIO_STATUS = 0x0a,
BSSGP_PDUT_SUSPEND = 0x0b,
@@ -27,6 +35,11 @@ enum bssgp_pdu_type {
BSSGP_PDUT_RESUME = 0x0e,
BSSGP_PDUT_RESUME_ACK = 0x0f,
BSSGP_PDUT_RESUME_NACK = 0x10,
+ BSSGP_PDUT_PAGING_PS_REJECT = 0x11,
+ BSSGP_PDUT_DUMMY_PAGING_PS = 0x12,
+ BSSGP_PDUT_DUMMY_PAGING_PS_RESP = 0x13,
+ BSSGP_PDUT_MS_REGISTR_ENQ = 0x14,
+ BSSGP_PDUT_MS_REGISTR_ENQ_RESP = 0x15,
/* PDus between NM SAPs */
BSSGP_PDUT_BVC_BLOCK = 0x20,
BSSGP_PDUT_BVC_BLOCK_ACK = 0x21,
@@ -41,8 +54,11 @@ enum bssgp_pdu_type {
BSSGP_PDUT_FLUSH_LL = 0x2a,
BSSGP_PDUT_FLUSH_LL_ACK = 0x2b,
BSSGP_PDUT_LLC_DISCARD = 0x2c,
+ BSSGP_PDUT_FLOW_CONTROL_PFC = 0x2d,
+ BSSGP_PDUT_FLOW_CONTROL_PFC_ACK = 0x2e,
BSSGP_PDUT_SGSN_INVOKE_TRACE = 0x40,
BSSGP_PDUT_STATUS = 0x41,
+ BSSGP_PDUT_OVERLOAD = 0x42,
/* PDUs between PFM SAP's */
BSSGP_PDUT_DOWNLOAD_BSS_PFC = 0x50,
BSSGP_PDUT_CREATE_BSS_PFC = 0x51,
@@ -52,6 +68,35 @@ enum bssgp_pdu_type {
BSSGP_PDUT_MODIFY_BSS_PFC_ACK = 0x55,
BSSGP_PDUT_DELETE_BSS_PFC = 0x56,
BSSGP_PDUT_DELETE_BSS_PFC_ACK = 0x57,
+ BSSGP_PDUT_DELETE_BSS_PFC_REQ = 0x58,
+ BSSGP_PDUT_PS_HO_REQUIRED = 0x59,
+ BSSGP_PDUT_PS_HO_REQUIRED_ACK = 0x5a,
+ BSSGP_PDUT_PS_HO_REQUIRED_NACK = 0x5b,
+ BSSGP_PDUT_PS_HO_REQUEST = 0x5c,
+ BSSGP_PDUT_PS_HO_REQUEST_ACK = 0x5d,
+ BSSGP_PDUT_PS_HO_REQUEST_NACK = 0x5e,
+ BSSGP_PDUT_PS_HO_COMPLETE = 0x91,
+ BSSGP_PDUT_PS_HO_CANCEL = 0x92,
+ BSSGP_PDUT_PS_HO_COMPLETE_ACK = 0x93,
+ /* PDUs between LCS SAPs */
+ BSSGP_PDUT_PERFORM_LOC_REQ = 0x60,
+ BSSGP_PDUT_PERFORM_LOC_RESP = 0x61,
+ BSSGP_PDUT_PERFORM_LOC_ABORT = 0x62,
+ BSSGP_PDUT_POSITION_COMMAND = 0x63,
+ BSSGP_PDUT_POSITION_RESPONSE = 0x64,
+ /* PDUs between RIM SAPs */
+ BSSGP_PDUT_RAN_INFO = 0x70,
+ BSSGP_PDUT_RAN_INFO_REQ = 0x71,
+ BSSGP_PDUT_RAN_INFO_ACK = 0x72,
+ BSSGP_PDUT_RAN_INFO_ERROR = 0x73,
+ BSSGP_PDUT_RAN_INFO_APP_ERROR = 0x74,
+ /* PDUs between MBMS SAPs */
+ BSSGP_PDUT_MBMS_START_REQ = 0x80,
+ BSSGP_PDUT_MBMS_START_RESP = 0x81,
+ BSSGP_PDUT_MBMS_STOP_REQ = 0x82,
+ BSSGP_PDUT_MBMS_STOP_RESP = 0x83,
+ BSSGP_PDUT_MBMS_UPDATE_REQ = 0x84,
+ BSSGP_PDUT_MBMS_UPDATE_RESP = 0x85,
};
/*! BSSGP User-Data header (Section 10.2.1 and 10.2.2) */
@@ -68,7 +113,7 @@ struct bssgp_normal_hdr {
uint8_t data[0]; /*!< optional/conditional IEs as TLVs */
};
-/*! BSSGP Information Element Identifiers */
+/*! BSSGP Information Element Identifiers (Section 11.3 / Table 11.3) */
enum bssgp_iei_type {
BSSGP_IE_ALIGNMENT = 0x00,
BSSGP_IE_BMAX_DEFAULT_MS = 0x01,
@@ -116,6 +161,105 @@ enum bssgp_iei_type {
BSSGP_IE_FEATURE_BITMAP = 0x3b,
BSSGP_IE_BUCKET_FULL_RATIO = 0x3c,
BSSGP_IE_SERVICE_UTRAN_CCO = 0x3d,
+ BSSGP_IE_NSEI = 0x3e,
+ BSSGP_IE_RRLP_APDU = 0x3f,
+ BSSGP_IE_LCS_QOS = 0x40,
+ BSSGP_IE_LCS_CLIENT_TYPE = 0x41,
+ BSSGP_IE_REQUESTED_GPS_AST_DATA = 0x42,
+ BSSGP_IE_LOCATION_TYPE = 0x43,
+ BSSGP_IE_LOCATION_ESTIMATE = 0x44,
+ BSSGP_IE_POSITIONING_DATA = 0x45,
+ BSSGP_IE_DECIPHERING_KEYS = 0x46,
+ BSSGP_IE_LCS_PRIORITY = 0x47,
+ BSSGP_IE_LCS_CAUSE = 0x48,
+ BSSGP_IE_LCS_CAPABILITY = 0x49,
+ BSSGP_IE_RRLP_FLAGS = 0x4a,
+ BSSGP_IE_RIM_APP_IDENTITY = 0x4b,
+ BSSGP_IE_RIM_SEQ_NR = 0x4c,
+ BSSGP_IE_RIM_REQ_APP_CONTAINER = 0x4d,
+ BSSGP_IE_RAN_INFO_APP_CONTAINER = 0x4e,
+ BSSGP_IE_RIM_PDU_INDICATIONS = 0x4f,
+ BSSGP_IE_PFC_FLOW_CTRL_PARAMS = 0x52,
+ BSSGP_IE_GLOBAL_CN_ID = 0x53,
+ BSSGP_IE_RIM_ROUTING_INFO = 0x54,
+ BSSGP_IE_RIM_PROTOCOL_VERSION = 0x55,
+ BSSGP_IE_APP_ERROR_CONTAINER = 0x56,
+ BSSGP_IE_RI_REQ_RIM_CONTAINER = 0x57,
+ BSSGP_IE_RI_RIM_CONTAINER = 0x58,
+ BSSGP_IE_RI_APP_ERROR_RIM_CONT = 0x59,
+ BSSGP_IE_RI_ACK_RIM_CONTAINER = 0x5a,
+ BSSGP_IE_RI_ERROR_RIM_COINTAINER= 0x5b,
+ BSSGP_IE_TMGI = 0x5c,
+ BSSGP_IE_MBMS_SESSION_ID = 0x5d,
+ BSSGP_IE_MBMS_SESSION_DURATION = 0x5e,
+ BSSGP_IE_MBMS_SA_ID_LIST = 0x5f,
+ BSSGP_IE_MBMS_RESPONSE = 0x60,
+ BSSGP_IE_MBMS_RA_LIST = 0x61,
+ BSSGP_IE_MBMS_SESSION_INFO = 0x62,
+ BSSGP_IE_MBMS_STOP_CAUSE = 0x63,
+ BSSGP_IE_SBSS_TO_TBSS_TR_CONT = 0x64,
+ BSSGP_IE_TBSS_TO_SBSS_TR_CONT = 0x65,
+ BSSGP_IE_NAS_CONT_FOR_PS_HO = 0x66,
+ BSSGP_IE_PFC_TO_BE_SETUP_LIST = 0x67,
+ BSSGP_IE_LIST_OF_SETUP_PFC = 0x68,
+ BSSGP_IE_EXT_FEATURE_BITMAP = 0x69,
+ BSSGP_IE_SRC_TO_TGT_TR_CONT = 0x6a,
+ BSSGP_IE_TGT_TO_SRC_TR_CONT = 0x6b,
+ BSSGP_IE_NC_ID = 0x6c,
+ BSSGP_IE_PAGE_MODE = 0x6d,
+ BSSGP_IE_CONTAINER_ID = 0x6e,
+ BSSGP_IE_GLOBAL_TFI = 0x6f,
+ BSSGP_IE_IMEI = 0x70,
+ BSSGP_IE_TIME_TO_MBMS_DATA_XFR = 0x71,
+ BSSGP_IE_MBMS_SESSION_REP_NR = 0x72,
+ BSSGP_IE_INTER_RAT_HO_INFO = 0x73,
+ BSSGP_IE_PS_HO_COMMAND = 0x74,
+ BSSGP_IE_PS_HO_INDICATIONS = 0x75,
+ BSSGP_IE_SI_PSI_CONTAINER = 0x76,
+ BSSGP_IE_ACTIVE_PFC_LIST = 0x77,
+ BSSGP_IE_VELOCITY_DATA = 0x78,
+ BSSGP_IE_DTM_HO_COMMAND = 0x79,
+ BSSGP_IE_CS_INDICATION = 0x7a,
+ BSSGP_IE_RQD_GANNS_AST_DATA = 0x7b,
+ BSSGP_IE_GANSS_LOCATION_TYPE = 0x7c,
+ BSSGP_IE_GANSS_POSITIONING_DATA = 0x7d,
+ BSSGP_IE_FLOW_CTRL_GRANULARITY = 0x7e,
+ BSSGP_IE_ENB_ID = 0x7f,
+ BSSGP_IE_EUTRAN_IRAT_HO_INFO = 0x80,
+ BSSGP_IE_SUB_PID4RAT_FREQ_PRIO = 0x81,
+ BSSGP_IE_REQ4IRAT_HO_INFO = 0x82,
+ BSSGP_IE_RELIABLE_IRAT_HO_INFO = 0x83,
+ BSSGP_IE_SON_TRANSFER_APP_ID = 0x84,
+ BSSGP_IE_CSG_ID = 0x85,
+ BSSGP_IE_TAC = 0x86,
+ BSSGP_IE_REDIRECT_ATTEMPT_FLAG = 0x87,
+ BSSGP_IE_REDIRECTION_INDICATION = 0x88,
+ BSSGP_IE_REDIRECTION_COMPLETED = 0x89,
+ BSSGP_IE_UNCONF_SEND_STATE_VAR = 0x8a,
+ BSSGP_IE_IRAT_MEASUREMENT_CONF = 0x8b,
+ BSSGP_IE_SCI = 0x8c,
+ BSSGP_IE_GGSN_PGW_LOCATION = 0x8d,
+ BSSGP_IE_SELECTED_PLMN_ID = 0x8e,
+ BSSGP_IE_PRIO_CLASS_IND = 0x8f,
+ BSSGP_IE_SOURCE_CELL_ID = 0x90,
+ BSSGP_IE_IRAT_MEAS_CFG_E_EARFCN = 0x91,
+ BSSGP_IE_EDRX_PARAMETERS = 0x92,
+ BSSGP_IE_T_UNTIL_NEXT_PAGING = 0x93,
+ BSSGP_IE_COVERAGE_CLASS = 0x98,
+ BSSGP_IE_PAGING_ATTEMPT_INFO = 0x99,
+ BSSGP_IE_EXCEPTION_REPORT_FLAG = 0x9a,
+ BSSGP_IE_OLD_RA_ID = 0x9b,
+ BSSGP_IE_ATTACH_IND = 0x9c,
+ BSSGP_IE_PLMN_ID = 0x9d,
+ BSSGP_IE_MME_QUERY = 0x9e,
+ BSSGP_IE_SGSN_GROUP_ID = 0x9f,
+ BSSGP_IE_ADDITIONAL_PTMSI = 0xa0,
+ BSSGP_IE_UE_USAGE_TYPE = 0xa1,
+ BSSGP_IE_MLAT_TIMER = 0xa2,
+ BSSGP_IE_MLAT_TA = 0xa3,
+ BSSGP_IE_MS_SYNC_ACCURACY = 0xa4,
+ BSSGP_IE_BTS_RX_ACCURACY_LVL = 0xa5,
+ BSSGP_IE_TA_REQ = 0xa6,
};
/*! Cause coding (Section 11.3.8 / Table 11.10) */
@@ -178,3 +322,52 @@ enum gprs_bssgp_cause {
BSSGP_CAUSE_DTM_HO_MSC_ERR = 0x4a,
BSSGP_CAUSE_INVAL_CSG_CELL = 0x4b,
};
+
+/* Feature Bitmap (Section 11.3.45) */
+#define BSSGP_FEAT_PFC 0x01 /* Packet Flow Context */
+#define BSSGP_FEAT_CBL 0x02 /* Current Bucket Level */
+#define BSSGP_FEAT_INR 0x04 /* Inter-NSE re-routing */
+#define BSSGP_FEAT_LCS 0x08 /* Location Services */
+#define BSSGP_FEAT_RIM 0x10 /* RAN Inoformation Management */
+#define BSSGP_FEAT_PFC_FC 0x20 /* PFC Flow Control */
+#define BSSGP_FEAT_ERS 0x40 /* Enhanced Radio Status */
+#define BSSGP_FEAT_MBMS 0x80 /* Multimedia Broadcast */
+
+/* Extended Feature Bitmap (Section 11.3.84) */
+#define BSSGP_XFEAT_PSHO 0x01 /* PS Handover */
+#define BSSGP_XFEAT_GBIT 0x02 /* Gigabit Interface */
+#define BSSGP_XFEAT_MOCN 0x04 /* Multi-Operator CN */
+#define BSSGP_XFEAT_CSPS 0x08 /* CS/PS coordination enhancements */
+#define BSSGP_XFEAT_ECIoT 0x10 /* EC-GSM-IoT */
+#define BSSGP_XFEAT_DCN 0x20 /* Dedicated CN */
+#define BSSGP_XFEAT_eDRX 0x40 /* eDRX */
+#define BSSGP_XFEAT_MSAD 0x80 /* MS-assisted Dedicated CN selection */
+
+/* Flow Control Granularity (Section 11.3.102) */
+enum bssgp_fc_granularity {
+ BSSGP_FC_GRAN_100 = 0,
+ BSSGP_FC_GRAN_1000 = 1,
+ BSSGP_FC_GRAN_10000 = 2,
+ BSSGP_FC_GRAN_100000 = 3,
+};
+
+/* RAN-INFORMATION-REQUEST PDU Type Extension
+ * 3GPP TS 48.018, table 11.3.65.1 */
+enum bssgp_rim_pdu_type {
+ RIM_PDU_TYPE_STOP = 0,
+ RIM_PDU_TYPE_SING_REP = 1,
+ RIM_PDU_TYPE_MULT_REP = 2,
+};
+
+/* RIM PDU Indications
+ * 3GPP TS 48.018, section 11.3.65.0 */
+struct bssgp_rim_pdu_ind {
+#if OSMO_IS_LITTLE_ENDIAN
+ uint8_t ack_requested:1,
+ pdu_type_ext:3,
+ reserved:4;
+#elif OSMO_IS_BIG_ENDIAN
+/* 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/gprs/protocol/gsm_24_301.h b/include/osmocom/gprs/protocol/gsm_24_301.h
new file mode 100644
index 00000000..d4bcd87e
--- /dev/null
+++ b/include/osmocom/gprs/protocol/gsm_24_301.h
@@ -0,0 +1,11 @@
+/*! \file gsm_24_301.h */
+
+#pragma once
+
+/*! Tracking area TS 24.301, section 9.9.3.32 */
+struct osmo_eutran_tai {
+ uint16_t mcc;
+ uint16_t mnc;
+ bool mnc_3_digits;
+ uint16_t tac;
+};
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/a5.h b/include/osmocom/gsm/a5.h
index fa63246d..1b26842f 100644
--- a/include/osmocom/gsm/a5.h
+++ b/include/osmocom/gsm/a5.h
@@ -12,10 +12,6 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#pragma once
diff --git a/include/osmocom/gsm/bsslap.h b/include/osmocom/gsm/bsslap.h
new file mode 100644
index 00000000..56f2e6c8
--- /dev/null
+++ b/include/osmocom/gsm/bsslap.h
@@ -0,0 +1,53 @@
+/*! \addtogroup bsslap
+ * @{
+ * \file bsslap.h
+ * Message encoding and decoding for 3GPP TS 48.071 BSS LCS Assistance Protocol (BSSLAP).
+ */
+/*
+ * (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Neels Hofmeyr <neels@hofmeyr.de>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+#pragma once
+
+#include <osmocom/gsm/protocol/gsm_48_071.h>
+#include <osmocom/gsm/protocol/gsm_49_031.h>
+
+struct msgb;
+
+struct osmo_bsslap_err {
+ int rc;
+ enum bsslap_msgt msg_type;
+ enum bsslap_iei iei;
+ enum lcs_cause cause;
+ char *logmsg;
+};
+
+extern const struct value_string osmo_bsslap_msgt_names[];
+static inline const char *osmo_bsslap_msgt_name(enum bsslap_msgt val)
+{ return get_value_string(osmo_bsslap_msgt_names, val); }
+
+extern const struct value_string osmo_bsslap_iei_names[];
+static inline const char *osmo_bsslap_iei_name(enum bsslap_iei val)
+{ return get_value_string(osmo_bsslap_iei_names, val); }
+
+int osmo_bsslap_enc(struct msgb *msg, const struct bsslap_pdu *pdu);
+int osmo_bsslap_dec(struct bsslap_pdu *pdu,
+ struct osmo_bsslap_err **err, void *err_ctx,
+ const uint8_t *data, size_t len);
+
+/*! @} */
diff --git a/include/osmocom/gsm/bssmap_le.h b/include/osmocom/gsm/bssmap_le.h
new file mode 100644
index 00000000..113d4bd1
--- /dev/null
+++ b/include/osmocom/gsm/bssmap_le.h
@@ -0,0 +1,77 @@
+/*! \addtogroup bssmap_le
+ * @{
+ * \file bssmap_le.h
+ * Message encoding and decoding for 3GPP TS 49.031 BSSMAP-LE.
+ */
+/*
+ * (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Neels Hofmeyr <neels@hofmeyr.de>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+#pragma once
+
+#include <osmocom/gsm/protocol/gsm_49_031.h>
+
+struct osmo_bsslap_err;
+struct osmo_gad_err;
+
+struct osmo_bssmap_le_err {
+ int rc;
+ enum bssmap_le_msgt msg_type;
+ enum bssmap_le_iei iei;
+ enum lcs_cause cause;
+ struct osmo_bsslap_err *bsslap_err;
+ struct osmo_gad_err *gad_err;
+ char *logmsg;
+};
+
+struct osmo_bssap_le_err {
+ int rc;
+ struct osmo_bssmap_le_err *bssmap_le_err;
+ void *dtap_err;
+ char *logmsg;
+};
+
+enum bssmap_le_msgt osmo_bssmap_le_msgt(const uint8_t *data, uint8_t len);
+
+extern const struct value_string osmo_bssmap_le_msgt_names[];
+static inline const char *osmo_bssmap_le_msgt_name(enum bssmap_le_msgt val)
+{ return get_value_string(osmo_bssmap_le_msgt_names, val); }
+
+extern const struct value_string osmo_bssmap_le_iei_names[];
+static inline const char *osmo_bssmap_le_iei_name(enum bssmap_le_iei val)
+{ return get_value_string(osmo_bssmap_le_iei_names, val); }
+
+int osmo_lcs_cause_enc(struct msgb *msg, const struct lcs_cause_ie *lcs_cause);
+int osmo_lcs_cause_dec(struct lcs_cause_ie *lcs_cause,
+ enum bssmap_le_msgt msgt, enum bssmap_le_iei iei,
+ struct osmo_bssmap_le_err **err, void *err_ctx,
+ const uint8_t *data, uint8_t len);
+
+int osmo_bssap_le_pdu_to_str_buf(char *buf, size_t buflen, const struct bssap_le_pdu *bssap_le);
+char *osmo_bssap_le_pdu_to_str_c(void *ctx, const struct bssap_le_pdu *bssap_le);
+
+struct msgb *osmo_bssap_le_enc(const struct bssap_le_pdu *pdu);
+int osmo_bssap_le_dec(struct bssap_le_pdu *pdu, struct osmo_bssap_le_err **err, void *err_ctx, struct msgb *msg);
+
+uint8_t osmo_bssmap_le_ie_enc_location_type(struct msgb *msg, const struct bssmap_le_location_type *location_type);
+int osmo_bssmap_le_ie_dec_location_type(struct bssmap_le_location_type *lt,
+ enum bssmap_le_msgt msgt, enum bssmap_le_iei iei,
+ struct osmo_bssmap_le_err **err, void *err_ctx,
+ const uint8_t *elem, uint8_t len);
+
+/*! @} */
diff --git a/include/osmocom/gsm/bts_features.h b/include/osmocom/gsm/bts_features.h
index 341ba405..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
- gsm_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,
@@ -26,12 +26,31 @@ enum osmo_bts_features {
BTS_FEAT_ETWS_PN,
BTS_FEAT_PAGING_COORDINATION, /* BTS hands CS paging to PCU/PACCH */
BTS_FEAT_IPV6_NSVC,
+ BTS_FEAT_ACCH_REP,
+ BTS_FEAT_CCN, /* Is CCN supported by the cell? TS 44.060 sec 8.8.2 */
+ BTS_FEAT_VAMOS, /* Is the BTS VAMOS capable? */
+ BTS_FEAT_ABIS_OSMO_PCU, /* BTS supports forwarding data to PCUIF over IPA OML multiplex */
+ BTS_FEAT_BCCH_POWER_RED,
+ 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
};
extern const struct value_string osmo_bts_features_descs[];
-const char *osmo_bts_feature_name(enum osmo_bts_features feature);
+static inline const char *osmo_bts_features_desc(enum osmo_bts_features val)
+{ return get_value_string(osmo_bts_features_descs, val); }
+
+const char *osmo_bts_feature_name(enum osmo_bts_features feature)
+ OSMO_DEPRECATED("Use osmo_bts_features_desc() instead");
+
+extern const struct value_string osmo_bts_features_names[];
+
+static inline const char *osmo_bts_features_name(enum osmo_bts_features val)
+{ return get_value_string(osmo_bts_features_names, val); }
static inline int osmo_bts_set_feature(struct bitvec *features, enum osmo_bts_features feature)
{
diff --git a/include/osmocom/gsm/cbsp.h b/include/osmocom/gsm/cbsp.h
index d456ce2f..536c54d6 100644
--- a/include/osmocom/gsm/cbsp.h
+++ b/include/osmocom/gsm/cbsp.h
@@ -24,10 +24,6 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
/* Decoded 8.2.3 Message Content */
@@ -75,7 +71,7 @@ struct osmo_cbsp_fail_ent {
struct llist_head list; /* entry in a fail_list below */
enum CELL_IDENT id_discr;
union gsm0808_cell_id_u cell_id;
- uint8_t cause;
+ uint8_t cause; /* enum osmo_cbsp_cause */
};
@@ -245,6 +241,30 @@ struct osmo_cbsp_error_ind {
enum cbsp_channel_ind *channel_ind;
};
+/* 8.2.13 Cause */
+enum osmo_cbsp_cause {
+ OSMO_CBSP_CAUSE_PARAM_NOT_RECOGNISED = 0,
+ OSMO_CBSP_CAUSE_PARAM_VALUE_INVALID,
+ OSMO_CBSP_CAUSE_MSG_REF_NOT_IDENTIFIED,
+ OSMO_CBSP_CAUSE_CELL_ID_NOT_VALID,
+ OSMO_CBSP_CAUSE_UNRECOGNISED_MESSAGE,
+ OSMO_CBSP_CAUSE_MISSING_MANDATORY_ELEMENT,
+ OSMO_CBSP_CAUSE_BSC_CAPACITY_EXCEEDED,
+ OSMO_CBSP_CAUSE_CELL_MEMORY_EXCEEDED,
+ OSMO_CBSP_CAUSE_BSC_MEMORY_EXCEEDED,
+ OSMO_CBSP_CAUSE_CELL_BROADCAST_NOT_SUPPORTED,
+ OSMO_CBSP_CAUSE_CELL_BROADCAST_NOT_OPERATIONAL,
+ OSMO_CBSP_CAUSE_INCOMPATIBLE_DRX_PARAM,
+ OSMO_CBSP_CAUSE_EXT_CHAN_NOT_SUPPORTED,
+ OSMO_CBSP_CAUSE_MSG_REF_ALREADY_USED,
+ OSMO_CBSP_CAUSE_UNSPECIFIED_ERROR,
+ OSMO_CBSP_CAUSE_LAI_OR_LAC_NOT_VALID,
+};
+extern const struct value_string osmo_cbsp_cause_names[];
+static inline const char *osmo_cbsp_cause_name(enum osmo_cbsp_cause cause)
+{
+ return get_value_string(osmo_cbsp_cause_names, cause);
+}
/* decoded CBSP message */
struct osmo_cbsp_decoded {
@@ -283,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/gad.h b/include/osmocom/gsm/gad.h
new file mode 100644
index 00000000..57d0e37a
--- /dev/null
+++ b/include/osmocom/gsm/gad.h
@@ -0,0 +1,190 @@
+/*! \addtogroup gad
+ * @{
+ * \file gad.h
+ * Message encoding and decoding for 3GPP TS 23.032 GAD: Universal Geographical Area Description.
+ */
+/*
+ * (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * Author: Neels Hofmeyr <neels@hofmeyr.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#pragma once
+
+#include <osmocom/gsm/protocol/gsm_23_032.h>
+#include <osmocom/core/utils.h>
+
+struct msgb;
+
+struct osmo_gad_ell_point {
+ /*! Latitude in micro degrees (degrees * 1e6), -90'000'000 (S) .. 90'000'000 (N). */
+ int32_t lat;
+ /*! Longitude in micro degrees (degrees * 1e6), -180'000'000 (W) .. 180'000'000 (E). */
+ int32_t lon;
+};
+
+struct osmo_gad_ell_point_unc_circle {
+ /*! Latitude in micro degrees (degrees * 1e6), -90'000'000 (S) .. 90'000'000 (N). */
+ int32_t lat;
+ /*! Longitude in micro degrees (degrees * 1e6), -180'000'000 (W) .. 180'000'000 (E). */
+ int32_t lon;
+ /*! Uncertainty circle radius in millimeters (m * 1e3), 0 .. 18'000'000. */
+ uint32_t unc;
+};
+
+struct osmo_gad_ell_point_unc_ellipse {
+ /*! Latitude in micro degrees (degrees * 1e6), -90'000'000 (S) .. 90'000'000 (N). */
+ int32_t lat;
+ /*! Longitude in micro degrees (degrees * 1e6), -180'000'000 (W) .. 180'000'000 (E). */
+ int32_t lon;
+ /*! Uncertainty ellipsoid radius of major axis in millimeters, 0 .. 18'000'000.
+ * Coding of uncertainty is non-linear, use osmo_gad_dec_unc(osmo_gad_enc_unc(val)) to clamp. */
+ uint32_t unc_semi_major;
+ /*! Uncertainty ellipsoid radius of minor axis in millimeters, 0 .. 18'000'000.
+ * Coding of uncertainty is non-linear, use osmo_gad_dec_unc(osmo_gad_enc_unc(val)) to clamp. */
+ uint32_t unc_semi_minor;
+ /*! Major axis orientation in degrees (DEG), 0 (N) .. 90 (E) .. 179 (SSE). */
+ uint8_t major_ori;
+ /*! Confidence in percent, 0 = no information, 1..100%, 101..128 = no information. */
+ uint8_t confidence;
+};
+
+struct osmo_gad_polygon {
+ uint8_t num_points;
+ struct osmo_gad_ell_point point[15];
+};
+
+struct osmo_gad_ell_point_alt {
+ /*! latitude in micro degrees (degrees * 1e6), -90'000'000 (S) .. 90'000'000 (N). */
+ int32_t lat;
+ /*! longitude in micro degrees (degrees * 1e6), -180'000'000 (W) .. 180'000'000 (E). */
+ int32_t lon;
+ /*! Altitude in meters, -32767 (depth) .. 32767 (height) */
+ int16_t alt;
+};
+
+struct osmo_gad_ell_point_alt_unc_ell {
+ /*! latitude in micro degrees (degrees * 1e6), -90'000'000 (S) .. 90'000'000 (N). */
+ int32_t lat;
+ /*! longitude in micro degrees (degrees * 1e6), -180'000'000 (W) .. 180'000'000 (E). */
+ int32_t lon;
+ /*! Altitude in meters, -32767 (depth) .. 32767 (height) */
+ int16_t alt;
+ /*! Uncertainty ellipsoid radius of major axis in millimeters, 0 .. 18'000'000.
+ * Coding of uncertainty is non-linear, use osmo_gad_dec_unc(osmo_gad_enc_unc(val)) to clamp. */
+ uint32_t unc_semi_major;
+ /*! Uncertainty ellipsoid radius of minor axis in millimeters, 0 .. 18'000'000.
+ * Coding of uncertainty is non-linear, use osmo_gad_dec_unc(osmo_gad_enc_unc(val)) to clamp. */
+ uint32_t unc_semi_minor;
+ /*! Major axis orientation in degrees (DEG), 0 (N) .. 90 (E) .. 179 (SSE). */
+ uint8_t major_ori;
+ /*! Uncertainty altitude in millimeters, 0 .. 990'000.
+ * Coding of uncertainty altitude is non-linear, and distinct from the non-altitude uncertainty coding. Use
+ * osmo_gad_dec_unc_alt(osmo_gad_enc_unc_alt(val)) to clamp. */
+ int32_t unc_alt;
+ /*! Confidence in percent, 0 = no information, 1..100%, 101..128 = no information. */
+ uint8_t confidence;
+};
+
+struct osmo_gad_ell_arc {
+ /*! latitude in micro degrees (degrees * 1e6), -90'000'000 (S) .. 90'000'000 (N). */
+ int32_t lat;
+ /*! longitude in micro degrees (degrees * 1e6), -180'000'000 (W) .. 180'000'000 (E). */
+ int32_t lon;
+ /*! inner circle radius in mm (m * 1e3) */
+ uint32_t inner_r;
+ /*! Uncertainty circle radius in millimeters, 0 .. 18'000'000.
+ * Coding of uncertainty is non-linear, use osmo_gad_dec_unc(osmo_gad_enc_unc(val)) to clamp. */
+ uint32_t unc_r;
+ /*! Offset angle of first arc edge in degrees from North clockwise (eastwards), 0..359.
+ * Angle is coded in increments of 2 degrees. */
+ uint16_t ofs_angle;
+ /*! Included angle defining the angular width of the arc, in degrees clockwise, 1..360.
+ * Angle is coded in increments of 2 degrees. */
+ uint16_t incl_angle;
+ /*! Confidence in percent, 0 = no information, 1..100%, 101..128 = no information. */
+ uint8_t confidence;
+};
+
+struct osmo_gad_ha_ell_point_alt_unc_ell {
+ /*! latitude in micro degrees (degrees * 1e6), -90'000'000 (S) .. 90'000'000 (N). */
+ int32_t lat;
+ /*! longitude in micro degrees (degrees * 1e6), -180'000'000 (W) .. 180'000'000 (E). */
+ int32_t lon;
+ /*! Altitude in millimeters, -500'000 (depth) .. 10'000'000 (height) */
+ int32_t alt;
+ /*! Uncertainty ellipsoid radius of major axis in millimeters, 0 .. 46'491.
+ * Coding of high-accuracy uncertainty is non-linear, use osmo_gad_dec_ha_unc(osmo_gad_enc_ha_unc(val)) to
+ * clamp. */
+ uint32_t unc_semi_major;
+ /*! Uncertainty ellipsoid radius of minor axis in millimeters, 0 .. 46'491.
+ * Coding of high-accuracy uncertainty is non-linear, use osmo_gad_dec_ha_unc(osmo_gad_enc_ha_unc(val)) to
+ * clamp. */
+ uint32_t unc_semi_minor;
+ /*! Major axis orientation in degrees (DEG), 0 (N) .. 90 (E) .. 179 (SSE). */
+ uint8_t major_ori;
+ /*! Horizontal confidence in percent, 0 = no information, 1..100%, 101..128 = no information. */
+ uint8_t h_confidence;
+ /*! High-Accuracy uncertainty altitude */
+ int32_t unc_alt;
+ /*! Vertical confidence in percent, 0 = no information, 1..100%, 101..128 = no information. */
+ uint8_t v_confidence;
+};
+
+struct osmo_gad {
+ enum gad_type type;
+ union {
+ struct osmo_gad_ell_point ell_point;
+ struct osmo_gad_ell_point_unc_circle ell_point_unc_circle;
+ struct osmo_gad_ell_point_unc_ellipse ell_point_unc_ellipse;
+ struct osmo_gad_polygon polygon;
+ struct osmo_gad_ell_point_alt ell_point_alt;
+ struct osmo_gad_ell_point_alt_unc_ell ell_point_alt_unc_ell;
+ struct osmo_gad_ell_arc ell_arc;
+ struct osmo_gad_ell_point_unc_ellipse ha_ell_point_unc_ellipse;
+ struct osmo_gad_ha_ell_point_alt_unc_ell ha_ell_point_alt_unc_ell;
+ };
+};
+
+struct osmo_gad_err {
+ int rc;
+ enum gad_type type;
+ char *logmsg;
+};
+
+extern const struct value_string osmo_gad_type_names[];
+static inline const char *osmo_gad_type_name(enum gad_type val)
+{ return get_value_string(osmo_gad_type_names, val); }
+
+int osmo_gad_raw_write(struct msgb *msg, const union gad_raw *gad_raw);
+int osmo_gad_raw_read(union gad_raw *gad_raw, struct osmo_gad_err **err, void *err_ctx, const uint8_t *data, uint8_t len);
+
+int osmo_gad_enc(union gad_raw *gad_raw, const struct osmo_gad *gad);
+int osmo_gad_dec(struct osmo_gad *gad, struct osmo_gad_err **err, void *err_ctx, const union gad_raw *gad_raw);
+
+int osmo_gad_to_str_buf(char *buf, size_t buflen, const struct osmo_gad *gad);
+char *osmo_gad_to_str_c(void *ctx, const struct osmo_gad *gad);
+
+uint32_t osmo_gad_enc_lat(int32_t deg_1e6);
+int32_t osmo_gad_dec_lat(uint32_t lat);
+uint32_t osmo_gad_enc_lon(int32_t deg_1e6);
+int32_t osmo_gad_dec_lon(uint32_t lon);
+uint8_t osmo_gad_enc_unc(uint32_t mm);
+uint32_t osmo_gad_dec_unc(uint8_t unc);
+/*! @} */
diff --git a/include/osmocom/gsm/gsm0502.h b/include/osmocom/gsm/gsm0502.h
index 1be2cc39..ac697d99 100644
--- a/include/osmocom/gsm/gsm0502.h
+++ b/include/osmocom/gsm/gsm0502.h
@@ -16,10 +16,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 +31,39 @@
#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
+
/* 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 +88,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,
diff --git a/include/osmocom/gsm/gsm0808.h b/include/osmocom/gsm/gsm0808.h
index 20137daf..dcff4159 100644
--- a/include/osmocom/gsm/gsm0808.h
+++ b/include/osmocom/gsm/gsm0808.h
@@ -16,10 +16,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#pragma once
@@ -54,10 +50,30 @@ struct msgb *gsm0808_create_clear_command2(uint8_t cause, bool csfb_ind);
struct msgb *gsm0808_create_clear_complete(void);
struct msgb *gsm0808_create_cipher(const struct gsm0808_encrypt_info *ei,
const uint8_t *cipher_response_mode);
+
+struct gsm0808_cipher_mode_command {
+ struct gsm0808_encrypt_info ei;
+
+ /*! 3GPP TS 48.008 3.2.2.34 Cipher Response Mode, optional IE */
+ bool cipher_response_mode_present;
+ /*! 3GPP TS 48.008 3.2.2.34 Cipher Response Mode:
+ * 0 - IMEISV must not be included by the Mobile Station;
+ * 1 - IMEISV must be included by the Mobile Station.
+ */
+ uint8_t cipher_response_mode;
+
+ bool kc128_present;
+ uint8_t kc128[16];
+
+ /* more items are defined in the spec and may be added later */
+ bool more_items; /*< always set this to false */
+};
+struct msgb *gsm0808_create_cipher2(const struct gsm0808_cipher_mode_command *cmc);
+
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);
@@ -126,6 +142,9 @@ struct gsm0808_old_bss_to_new_bss_info {
uint8_t field;
} current_channel_type_2;
+ bool last_eutran_plmn_id_present;
+ struct osmo_plmn_id last_eutran_plmn_id;
+
/* more items are defined in the spec and may be added later */
bool more_items; /*< always set this to false */
};
@@ -195,7 +214,12 @@ struct gsm0808_handover_request {
uint8_t global_call_reference_len;
/* more items are defined in the spec and may be added later */
- bool more_items; /*!< always set this to false */
+ bool more_items; /*!< set this to true iff any fields below are used */
+
+ bool kc128_present;
+ uint8_t kc128[16];
+
+ bool more_items2; /*!< always set this to false */
};
struct msgb *gsm0808_create_handover_request(const struct gsm0808_handover_request *params);
@@ -222,8 +246,12 @@ struct gsm0808_handover_request_ack {
const struct sockaddr_storage *aoip_transport_layer;
+ bool more_items; /*!< set this to true iff any fields below are used */
+
+ struct gsm0808_speech_codec_list codec_list_bss_supported; /*< omit when .len == 0 */
+
/* more items are defined in the spec and may be added later */
- bool more_items; /*!< always set this to false */
+ bool more_items2; /*!< always set this to false */
};
struct msgb *gsm0808_create_handover_request_ack2(const struct gsm0808_handover_request_ack *params);
@@ -241,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;
@@ -255,7 +283,7 @@ struct gsm0808_handover_complete {
bool chosen_encr_alg_present;
uint8_t chosen_encr_alg;
-
+
bool chosen_channel_present;
uint8_t chosen_channel;
@@ -308,6 +336,7 @@ struct msgb *gsm0808_create_dtap(struct msgb *msg, uint8_t link_id);
void gsm0808_prepend_dtap_header(struct msgb *msg, uint8_t link_id);
const struct tlv_definition *gsm0808_att_tlvdef(void);
+extern const struct tlv_definition gsm0808_old_bss_to_new_bss_info_att_tlvdef;
/*! Parse BSSAP TLV structure using \ref tlv_parse */
#define osmo_bssap_tlv_parse(dec, buf, len) tlv_parse(dec, gsm0808_att_tlvdef(), buf, len, 0, 0)
diff --git a/include/osmocom/gsm/gsm0808_lcs.h b/include/osmocom/gsm/gsm0808_lcs.h
new file mode 100644
index 00000000..71665013
--- /dev/null
+++ b/include/osmocom/gsm/gsm0808_lcs.h
@@ -0,0 +1,48 @@
+/*! \addtogroup gsm0808
+ * @{
+ * \file gsm0808_lcs.h
+ *
+ * Declarations that depend on both gsm0808.h and bssmap_le.h: LCS related message coding.
+ * (This file prevents circular dependency between struct definitions for BSSMAP messages, since BSSMAP references
+ * struct lcs_cause and struct bssmap_le_location_type, and BSSMAP-LE references gsm0808_cause.
+ */
+/*
+ * (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+#pragma once
+
+#include <osmocom/gsm/gsm0808.h>
+#include <osmocom/gsm/bssmap_le.h>
+
+struct gsm0808_perform_location_request {
+ struct bssmap_le_location_type location_type;
+ struct osmo_mobile_identity imsi;
+
+ bool more_items; /*!< always set this to false */
+};
+struct msgb *gsm0808_create_perform_location_request(const struct gsm0808_perform_location_request *params);
+
+struct gsm0808_perform_location_response {
+ bool location_estimate_present;
+ union gad_raw location_estimate;
+
+ struct lcs_cause_ie lcs_cause;
+};
+struct msgb *gsm0808_create_perform_location_response(const struct gsm0808_perform_location_response *params);
+
+int gsm0808_enc_lcs_cause(struct msgb *msg, const struct lcs_cause_ie *lcs_cause);
+struct msgb *gsm0808_create_perform_location_abort(const struct lcs_cause_ie *lcs_cause);
+
+/*! @} */
diff --git a/include/osmocom/gsm/gsm0808_utils.h b/include/osmocom/gsm/gsm0808_utils.h
index 59db6edc..18f3ebe5 100644
--- a/include/osmocom/gsm/gsm0808_utils.h
+++ b/include/osmocom/gsm/gsm0808_utils.h
@@ -44,6 +44,9 @@ union gsm0808_cell_id_u {
uint16_t ci;
struct osmo_location_area_id lai_and_lac;
uint16_t lac;
+ struct osmo_service_area_id sai;
+ /* osmocom specific: */
+ struct osmo_cell_global_id_ps global_ps;
};
/*! Parsed representation of Cell Identifier IE (3GPP TS 48.008 3.2.2.17) */
@@ -108,12 +111,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,
@@ -124,6 +132,8 @@ uint8_t gsm0808_enc_encrypt_info(struct msgb *msg,
const struct gsm0808_encrypt_info *ei);
int gsm0808_dec_encrypt_info(struct gsm0808_encrypt_info *ei,
const uint8_t *elem, uint8_t len);
+int gsm0808_enc_kc128(struct msgb *msg, const uint8_t *kc128);
+int gsm0808_dec_kc128(uint8_t *kc128, const uint8_t *elem, uint8_t len);
uint8_t gsm0808_enc_cell_id_list2(struct msgb *msg, const struct gsm0808_cell_id_list2 *cil);
uint8_t gsm0808_enc_cell_id_list(struct msgb *msg,
const struct gsm0808_cell_id_list *cil)
diff --git a/include/osmocom/gsm/gsm23003.h b/include/osmocom/gsm/gsm23003.h
index 69f00f68..4070581f 100644
--- a/include/osmocom/gsm/gsm23003.h
+++ b/include/osmocom/gsm/gsm23003.h
@@ -30,6 +30,14 @@ struct osmo_cell_global_id {
uint16_t cell_identity;
};
+/* 3GPP TS 48.018:
+ * 8c.1.4.1.1 GERAN BSS identification (RIM)
+ * sec 11.3.9 Cell Identifier */
+struct osmo_cell_global_id_ps {
+ struct osmo_routing_area_id rai;
+ uint16_t cell_identity;
+};
+
/*! Bitmask of items contained in a struct osmo_cell_global_id.
* See also gsm0808_cell_id_to_cgi().
*/
@@ -37,6 +45,7 @@ enum osmo_cgi_part {
OSMO_CGI_PART_PLMN = 1,
OSMO_CGI_PART_LAC = 2,
OSMO_CGI_PART_CI = 4,
+ OSMO_CGI_PART_RAC = 8,
};
/* Actually defined in 3GPP TS 48.008 3.2.2.27 Cell Identifier List,
@@ -117,10 +126,21 @@ char *osmo_plmn_name_c(const void *ctx, const struct osmo_plmn_id *plmn);
const char *osmo_lai_name(const struct osmo_location_area_id *lai);
char *osmo_lai_name_buf(char *buf, size_t buf_len, const struct osmo_location_area_id *lai);
char *osmo_lai_name_c(const void *ctx, const struct osmo_location_area_id *lai);
+const char *osmo_rai_name2(const struct osmo_routing_area_id *rai);
+char *osmo_rai_name2_buf(char *buf, size_t buf_len, const struct osmo_routing_area_id *rai);
+char *osmo_rai_name2_c(const void *ctx, const struct osmo_routing_area_id *rai);
const char *osmo_cgi_name(const struct osmo_cell_global_id *cgi);
const char *osmo_cgi_name2(const struct osmo_cell_global_id *cgi);
char *osmo_cgi_name_buf(char *buf, size_t buf_len, const struct osmo_cell_global_id *cgi);
char *osmo_cgi_name_c(const void *ctx, const struct osmo_cell_global_id *cgi);
+const char *osmo_cgi_ps_name(const struct osmo_cell_global_id_ps *cgi_ps);
+const char *osmo_cgi_ps_name2(const struct osmo_cell_global_id_ps *cgi_ps);
+char *osmo_cgi_ps_name_buf(char *buf, size_t buf_len, const struct osmo_cell_global_id_ps *cgi_ps);
+char *osmo_cgi_ps_name_c(const void *ctx, const struct osmo_cell_global_id_ps *cgi_ps);
+const char *osmo_sai_name(const struct osmo_service_area_id *sai);
+const char *osmo_sai_name2(const struct osmo_service_area_id *sai);
+char *osmo_sai_name_buf(char *buf, size_t buf_len, const struct osmo_service_area_id *sai);
+char *osmo_sai_name_c(const void *ctx, const struct osmo_service_area_id *sai);
const char *osmo_gummei_name(const struct osmo_gummei *gummei);
char *osmo_gummei_name_buf(char *buf, size_t buf_len, const struct osmo_gummei *gummei);
char *osmo_gummei_name_c(const void *ctx, const struct osmo_gummei *gummei);
@@ -144,7 +164,9 @@ static inline int osmo_mcc_from_str(const char *mcc_str, uint16_t *mcc)
int osmo_mnc_cmp(uint16_t a_mnc, bool a_mnc_3_digits, uint16_t b_mnc, bool b_mnc_3_digits);
int osmo_plmn_cmp(const struct osmo_plmn_id *a, const struct osmo_plmn_id *b);
int osmo_lai_cmp(const struct osmo_location_area_id *a, const struct osmo_location_area_id *b);
+int osmo_rai_cmp(const struct osmo_routing_area_id *a, const struct osmo_routing_area_id *b);
int osmo_cgi_cmp(const struct osmo_cell_global_id *a, const struct osmo_cell_global_id *b);
+int osmo_cgi_ps_cmp(const struct osmo_cell_global_id_ps *a, const struct osmo_cell_global_id_ps *b);
int osmo_gen_home_network_domain(char *out, const struct osmo_plmn_id *plmn);
int osmo_parse_home_network_domain(struct osmo_plmn_id *out, const char *in);
diff --git a/include/osmocom/gsm/gsm29118.h b/include/osmocom/gsm/gsm29118.h
index e81cce4c..33c40d36 100644
--- a/include/osmocom/gsm/gsm29118.h
+++ b/include/osmocom/gsm/gsm29118.h
@@ -17,10 +17,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#pragma once
diff --git a/include/osmocom/gsm/gsm29205.h b/include/osmocom/gsm/gsm29205.h
index 95754567..6c845fb9 100644
--- a/include/osmocom/gsm/gsm29205.h
+++ b/include/osmocom/gsm/gsm29205.h
@@ -15,10 +15,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#pragma once
@@ -37,6 +33,6 @@ struct osmo_gcr_parsed {
uint8_t cr[5]; /** Call Reference ID */
};
-uint8_t osmo_enc_gcr(struct msgb *msg, const struct osmo_gcr_parsed *g) OSMO_DEPRECATED_OUTSIDE_LIBOSMOCORE;
-int osmo_dec_gcr(struct osmo_gcr_parsed *gcr, const uint8_t *elem, uint8_t len) OSMO_DEPRECATED_OUTSIDE_LIBOSMOCORE;
+uint8_t osmo_enc_gcr(struct msgb *msg, const struct osmo_gcr_parsed *g);
+int osmo_dec_gcr(struct osmo_gcr_parsed *gcr, const uint8_t *elem, uint8_t len);
bool osmo_gcr_eq(const struct osmo_gcr_parsed *gcr1, const struct osmo_gcr_parsed *gcr2);
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 f772f4a1..93a9e21d 100644
--- a/include/osmocom/gsm/gsm48.h
+++ b/include/osmocom/gsm/gsm48.h
@@ -35,6 +35,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);
@@ -104,8 +105,9 @@ int osmo_mobile_identity_encode_msgb(struct msgb *msg, const struct osmo_mobile_
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");
@@ -117,3 +119,6 @@ struct gsm48_hdr *gsm48_push_l3hdr(struct msgb *msg,
#define gsm48_push_l3hdr_tid(msg, pdisc, tid, msg_type) \
gsm48_push_l3hdr(msg, (pdisc & 0x0f) | (tid << 4), msg_type)
+
+enum gsm48_chan_mode gsm48_chan_mode_to_vamos(enum gsm48_chan_mode mode);
+enum gsm48_chan_mode gsm48_chan_mode_to_non_vamos(enum gsm48_chan_mode mode);
diff --git a/include/osmocom/gsm/gsm48_ie.h b/include/osmocom/gsm/gsm48_ie.h
index 339aa136..4768283f 100644
--- a/include/osmocom/gsm/gsm48_ie.h
+++ b/include/osmocom/gsm/gsm48_ie.h
@@ -117,5 +117,10 @@ 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,
+ const uint8_t *classmark3, size_t classmark3_len);
diff --git a/include/osmocom/gsm/gsm48_rest_octets.h b/include/osmocom/gsm/gsm48_rest_octets.h
index d3bb878e..c8d63b21 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) */
@@ -120,8 +120,12 @@ struct osmo_gsm48_si13_info {
uint8_t prio_acc_thr;
};
-/* Generate SI13 Rest Octests (Chapter 10.5.2.37b) */
+/* Parse/Generate SI13 Rest Octests (Chapter 10.5.2.37b) */
+int osmo_gsm48_rest_octets_si13_decode(struct osmo_gsm48_si13_info *si13, const uint8_t *data);
int osmo_gsm48_rest_octets_si13_encode(uint8_t *data, const struct osmo_gsm48_si13_info *si13);
/* Parse SI3 Rest Octets */
void osmo_gsm48_rest_octets_si3_decode(struct osmo_gsm48_si_ro_info *si3, const uint8_t *data);
+
+/* Parse SI4 Rest Octets */
+void osmo_gsm48_rest_octets_si4_decode(struct osmo_gsm48_si_ro_info *si4, const uint8_t *data, int len);
diff --git a/include/osmocom/gsm/gsm_utils.h b/include/osmocom/gsm/gsm_utils.h
index de634348..721456d2 100644
--- a/include/osmocom/gsm/gsm_utils.h
+++ b/include/osmocom/gsm/gsm_utils.h
@@ -17,10 +17,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#pragma once
@@ -37,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
@@ -110,7 +107,10 @@ int gsm_7bit_encode_n(uint8_t *result, size_t n, const char *data, int *octets_w
int gsm_7bit_encode_n_ussd(uint8_t *result, size_t n, const char *data, int *octets_written);
/* the four functions below are helper functions and here for the unit test */
-int gsm_septets2octets(uint8_t *result, const uint8_t *rdata, uint8_t septet_len, uint8_t padding);
+int gsm_septets2octets(uint8_t *result, const uint8_t *rdata, uint8_t septet_len, uint8_t padding)
+ OSMO_DEPRECATED("This function is unable to handle more than 255 septets, "
+ "use gsm_septet_pack() instead.");
+int gsm_septet_pack(uint8_t *result, const uint8_t *rdata, size_t septet_len, uint8_t padding);
int gsm_septet_encode(uint8_t *result, const char *data);
uint8_t gsm_get_octet_len(const uint8_t sept_len);
int gsm_7bit_decode_n_hdr(char *decoded, size_t n, const uint8_t *user_data, uint8_t length, uint8_t ud_hdr_ind);
@@ -153,6 +153,13 @@ static inline int rach_max_trans_raw2val(int raw) {
const int tbl[4] = { 1, 2, 4, 7 };
return tbl[raw & 3];
}
+static inline uint8_t rach_tx_integer_raw2val(uint8_t raw) {
+ const int tbl[6] = { 14, 16, 20, 25, 32, 50 };
+ raw &= 0x0f;
+ if (raw <= 9)
+ return raw + 3;
+ return tbl[raw - 10];
+}
#define ARFCN_PCS 0x8000
#define ARFCN_UPLINK 0x4000
@@ -174,7 +181,7 @@ 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);
@@ -210,9 +217,11 @@ enum gsm_phys_chan_config {
GSM_PCHAN_UNKNOWN,
GSM_PCHAN_CCCH_SDCCH4_CBCH,
GSM_PCHAN_SDCCH8_SACCH8C_CBCH,
- GSM_PCHAN_TCH_F_TCH_H_PDCH,
+ GSM_PCHAN_OSMO_DYN,
_GSM_PCHAN_MAX
};
+/* Backward compatibility with older naming: */
+#define GSM_PCHAN_TCH_F_TCH_H_PDCH GSM_PCHAN_OSMO_DYN
/* Osmocom internal, not part of any gsm spec */
enum gsm_chan_t {
diff --git a/include/osmocom/gsm/i460_mux.h b/include/osmocom/gsm/i460_mux.h
index 8e392434..7c2f4af3 100644
--- a/include/osmocom/gsm/i460_mux.h
+++ b/include/osmocom/gsm/i460_mux.h
@@ -1,121 +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.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
- * MA 02110-1301, USA.
- */
-
#pragma once
-#include <stdint.h>
-#include <osmocom/core/bits.h>
-#include <osmocom/core/linuxlist.h>
-#include <osmocom/core/msgb.h>
-
-/* I.460 sub-slot rate */
-enum osmo_i460_rate {
- OSMO_I460_RATE_NONE, /* disabled */
- OSMO_I460_RATE_64k,
- OSMO_I460_RATE_32k,
- OSMO_I460_RATE_16k,
- OSMO_I460_RATE_8k,
-};
-
-struct osmo_i460_subchan;
-
-typedef void (*out_cb_bits_t)(struct osmo_i460_subchan *schan, void *user_data,
- const ubit_t *bits, unsigned int num_bits);
-typedef void (*out_cb_bytes_t)(struct osmo_i460_subchan *schan, void *user_data,
- const uint8_t *bytes, unsigned int num_bytes);
-
-struct osmo_i460_subchan_demux {
- /*! bit-buffer for output bits */
- uint8_t *out_bitbuf;
- /*! size of out_bitbuf in bytes */
- unsigned int out_bitbuf_size;
- /*! offset of next bit to be written in out_bitbuf */
- unsigned int out_idx;
- /*! callback to be called once we have received out_bitbuf_size bits */
- out_cb_bits_t out_cb_bits;
- out_cb_bytes_t out_cb_bytes;
- void *user_data;
-};
-
-typedef void (*in_cb_queue_empty_t)(struct osmo_i460_subchan *schan, void *user_data);
-
-struct osmo_i460_subchan_mux {
- /*! list of to-be-transmitted message buffers */
- struct llist_head tx_queue;
- in_cb_queue_empty_t in_cb_queue_empty;
- void *user_data;
-};
-
-struct osmo_i460_subchan {
- struct osmo_i460_timeslot *ts; /* back-pointer */
- enum osmo_i460_rate rate; /* 8/16/32/64k */
- uint8_t bit_offset; /* bit offset inside each byte of the B-channel */
- struct osmo_i460_subchan_demux demux;
- struct osmo_i460_subchan_mux mux;
-};
-
-struct osmo_i460_timeslot {
- struct osmo_i460_subchan schan[8];
-};
-
-/*! description of a sub-channel; passed by caller */
-struct osmo_i460_schan_desc {
- enum osmo_i460_rate rate;
- uint8_t bit_offset;
- struct {
- /* size (in bits) of the internal buffer; determines granularity */
- size_t num_bits;
- /*! call-back function called whenever we received num_bits */
- out_cb_bits_t out_cb_bits;
- /*! out_cb_bytes call-back function called whenever we received num_bits.
- * The user is usually expected to provide either out_cb_bits or out_cb_bytes. If only
- * out_cb_bits is provided, output data will always be provided as unpacked bits; if only
- * out_cb_bytes is provided, output data will always be provided as packet bits (bytes). If
- * both are provided, it is up to the I.460 multiplex to decide if it calls either of the two,
- * depending on what can be provided without extra conversion. */
- out_cb_bytes_t out_cb_bytes;
- /* opaque user data pointer to pass to out_cb */
- void *user_data;
- } demux;
-
- struct {
- /* call-back function whenever the muxer requires more input data from the sub-channels,
- * but has nothing enqueued yet. A typical function would then call osmo_i460_mux_enqueue() */
- in_cb_queue_empty_t in_cb_queue_empty;
- /* opaque user data pointer to pass to in_cb */
- void *user_data;
- } mux;
-};
-
-void osmo_i460_demux_in(struct osmo_i460_timeslot *ts, const uint8_t *data, size_t data_len);
-
-void osmo_i460_mux_enqueue(struct osmo_i460_subchan *schan, struct msgb *msg);
-int osmo_i460_mux_out(struct osmo_i460_timeslot *ts, uint8_t *out, size_t out_len);
-
-void osmo_i460_ts_init(struct osmo_i460_timeslot *ts);
-
-struct osmo_i460_subchan *
-osmo_i460_subchan_add(void *ctx, struct osmo_i460_timeslot *ts, const struct osmo_i460_schan_desc *chd);
-
-void osmo_i460_subchan_del(struct osmo_i460_subchan *schan);
-
-/*! @} */
+#include <osmocom/isdn/i460_mux.h>
diff --git a/include/osmocom/gsm/ipa.h b/include/osmocom/gsm/ipa.h
index 93cb1bf1..851b58e0 100644
--- a/include/osmocom/gsm/ipa.h
+++ b/include/osmocom/gsm/ipa.h
@@ -29,7 +29,7 @@ const char *ipa_ccm_idtag_name(uint8_t tag);
int ipa_ccm_idtag_parse(struct tlv_parsed *dec, unsigned char *buf, int len)
OSMO_DEPRECATED("Use ipa_ccm_id_{get,resp}_parse instead");
int ipa_ccm_idtag_parse_off(struct tlv_parsed *dec, unsigned char *buf, int len, const int len_offset)
- OSMO_DEPRECATED("Use ipa_ccm_id_{get,resp}_parse instead");
+ OSMO_DEPRECATED_OUTSIDE("Use ipa_ccm_id_{get,resp}_parse instead");
/* parse payload of IPA CCM ID GET into a osmocom TLV style representation */
int ipa_ccm_id_get_parse(struct tlv_parsed *dec, const uint8_t *buf, unsigned int len);
diff --git a/include/osmocom/gsm/iuup.h b/include/osmocom/gsm/iuup.h
new file mode 100644
index 00000000..ab6b8acf
--- /dev/null
+++ b/include/osmocom/gsm/iuup.h
@@ -0,0 +1,129 @@
+#pragma once
+
+#include <stdint.h>
+
+#include <osmocom/core/prim.h>
+#include <osmocom/gsm/protocol/gsm_25_415.h>
+
+/***********************************************************************
+ * Primitives towards the lower layers (typically RTP transport)
+ ***********************************************************************/
+enum osmo_iuup_tnl_prim_type {
+ OSMO_IUUP_TNL_UNITDATA,
+};
+
+struct osmo_iuup_tnl_prim {
+ struct osmo_prim_hdr oph;
+};
+
+/***********************************************************************
+ * Primitives towards the upper layers at the RNL SAP
+ ***********************************************************************/
+
+/* 3GPP TS 25.415 Section 7.2.1 */
+enum osmo_iuup_rnl_prim_type {
+ OSMO_IUUP_RNL_CONFIG,
+ OSMO_IUUP_RNL_DATA,
+ OSMO_IUUP_RNL_STATUS,
+ OSMO_IUUP_RNL_UNIT_DATA,
+};
+
+/* TS 25.413 9.2.1.3*/
+#define IUUP_MAX_SUBFLOWS 7
+#define IUUP_MAX_RFCIS 64
+
+#define IUUP_TIMER_INIT_T_DEFAULT 1000
+#define IUUP_TIMER_TA_T_DEFAULT 500
+#define IUUP_TIMER_RC_T_DEFAULT 500
+#define IUUP_TIMER_INIT_N_DEFAULT 3
+#define IUUP_TIMER_TA_N_DEFAULT 1
+#define IUUP_TIMER_RC_N_DEFAULT 1
+struct osmo_iuup_rnl_config_timer {
+ uint32_t t_ms; /* time in ms */
+ uint32_t n_max; /* max number of repetitions */
+};
+struct osmo_iuup_rfci {
+ uint8_t used:1,
+ spare1:1,
+ id:6;
+ uint8_t spare2:4,
+ IPTI:4; /* values range 0-15, 4 bits */;
+ uint16_t subflow_sizes[IUUP_MAX_SUBFLOWS];
+};
+struct osmo_iuup_rnl_config {
+ /* transparent (true) or SMpSDU (false): */
+ bool transparent;
+
+ /* should we actively transmit INIT in SmpSDU mode? */
+ bool active;
+
+ /* Currently Version 0 or 1: */
+ uint8_t data_pdu_type;
+
+ /* Supported mode versions */
+ uint16_t supported_versions_mask;
+ uint8_t num_rfci;
+ uint8_t num_subflows;
+ bool IPTIs_present;
+ struct osmo_iuup_rfci rfci[IUUP_MAX_RFCIS];
+
+ /* TODO: Indication of delivery of erroneous SDUs*/
+ struct osmo_iuup_rnl_config_timer t_init;
+ struct osmo_iuup_rnl_config_timer t_ta;
+ struct osmo_iuup_rnl_config_timer t_rc;
+};
+
+struct osmo_iuup_rnl_data {
+ uint8_t rfci;
+ uint8_t frame_nr;
+ uint8_t fqc;
+};
+
+struct osmo_iuup_rnl_status {
+ enum iuup_procedure procedure;
+ union {
+ struct {
+ enum iuup_error_cause cause;
+ enum iuup_error_distance distance;
+ } error_event;
+ struct {
+ uint16_t mode_version;
+ uint8_t data_pdu_type;
+ uint8_t num_rfci;
+ uint8_t num_subflows;
+ bool IPTIs_present;
+ struct osmo_iuup_rfci rfci[IUUP_MAX_RFCIS];
+ } initialization;
+ struct {
+ } rate_control;
+ struct {
+ } time_alignment;
+ } u;
+};
+
+/* SAP on the upper side of IuUP, towards the user */
+struct osmo_iuup_rnl_prim {
+ struct osmo_prim_hdr oph;
+ union {
+ struct osmo_iuup_rnl_config config;
+ struct osmo_iuup_rnl_data data;
+ struct osmo_iuup_rnl_status status;
+ //struct osmo_iuup_rnl_unitdata unitdata;
+ } u;
+};
+
+struct osmo_iuup_instance;
+struct osmo_iuup_instance *osmo_iuup_instance_alloc(void *ctx, const char *id);
+void osmo_iuup_instance_free(struct osmo_iuup_instance *iui);
+
+void osmo_iuup_instance_set_user_prim_cb(struct osmo_iuup_instance *iui, osmo_prim_cb func, void *priv);
+void osmo_iuup_instance_set_transport_prim_cb(struct osmo_iuup_instance *iui, osmo_prim_cb func, void *priv);
+int osmo_iuup_tnl_prim_up(struct osmo_iuup_instance *iui, struct osmo_iuup_tnl_prim *itp);
+int osmo_iuup_rnl_prim_down(struct osmo_iuup_instance *inst, struct osmo_iuup_rnl_prim *irp);
+
+
+int osmo_iuup_compute_header_crc(const uint8_t *iuup_pdu, unsigned int pdu_len);
+int osmo_iuup_compute_payload_crc(const uint8_t *iuup_pdu, unsigned int pdu_len);
+
+struct osmo_iuup_rnl_prim *osmo_iuup_rnl_prim_alloc(void *ctx, unsigned int primitive, unsigned int operation, unsigned int size);
+struct osmo_iuup_tnl_prim *osmo_iuup_tnl_prim_alloc(void *ctx, unsigned int primitive, unsigned int operation, unsigned int size);
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 633df1ad..0e997431 100644
--- a/include/osmocom/gsm/lapdm.h
+++ b/include/osmocom/gsm/lapdm.h
@@ -90,7 +90,7 @@ void lapdm_entity_init2(struct lapdm_entity *le, enum lapdm_mode mode,
void lapdm_entity_init3(struct lapdm_entity *le, enum lapdm_mode mode,
const int *t200_ms, int n200, const char *name_pfx);
void lapdm_channel_init(struct lapdm_channel *lc, enum lapdm_mode mode)
- OSMO_DEPRECATED("Use lapdm_channel_init3() instead");
+ OSMO_DEPRECATED_OUTSIDE("Use lapdm_channel_init3() instead");
int lapdm_channel_init2(struct lapdm_channel *lc, enum lapdm_mode mode,
const int *t200_ms_dcch, const int *t200_ms_acch, enum gsm_chan_t chan_t);
int lapdm_channel_init3(struct lapdm_channel *lc, enum lapdm_mode mode,
diff --git a/include/osmocom/gsm/meas_rep.h b/include/osmocom/gsm/meas_rep.h
index 79f9f06b..5628d8ec 100644
--- a/include/osmocom/gsm/meas_rep.h
+++ b/include/osmocom/gsm/meas_rep.h
@@ -11,7 +11,7 @@ struct gsm_rx_lev_qual {
uint8_t rx_qual;
};
-/* unidirectional measumrement report */
+/* unidirectional measurement report */
struct gsm_meas_rep_unidir {
struct gsm_rx_lev_qual full;
struct gsm_rx_lev_qual sub;
@@ -28,5 +28,5 @@ enum meas_rep_field {
MEAS_REP_UL_RXQUAL_SUB,
};
-size_t gsm0858_rsl_ul_meas_enc(struct gsm_meas_rep_unidir *mru, bool dtxd_used,
+size_t gsm0858_rsl_ul_meas_enc(const struct gsm_meas_rep_unidir *mru, bool dtxd_used,
uint8_t *buf);
diff --git a/include/osmocom/gsm/prim.h b/include/osmocom/gsm/prim.h
index e7a60e30..73cd7f71 100644
--- a/include/osmocom/gsm/prim.h
+++ b/include/osmocom/gsm/prim.h
@@ -16,4 +16,9 @@ enum osmo_gsm_sap {
SAP_BSSGP_PFM,
SAP_NS,
+
+ SAP_BSSGP_RIM,
+
+ SAP_IUUP_TNL,
+ SAP_IUUP_RNL,
};
diff --git a/include/osmocom/gsm/protocol/Makefile.am b/include/osmocom/gsm/protocol/Makefile.am
new file mode 100644
index 00000000..5c8e2a68
--- /dev/null
+++ b/include/osmocom/gsm/protocol/Makefile.am
@@ -0,0 +1,29 @@
+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_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 f8f2eabd..66aa135c 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,13 +51,182 @@ 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;
#endif
} __attribute__ ((packed));
+/* Chapter 10.5.1.7 */
+struct gsm48_classmark3 {
+ uint8_t a5_bits;
+ uint8_t mult_band_supp;
+ uint8_t assoc_radio_cap_1;
+ uint8_t assoc_radio_cap_2;
+
+ struct {
+ bool present;
+ uint8_t r_gsm_assoc_radio_cap;
+ } r_support;
+
+ struct {
+ bool present;
+ uint8_t mslot_class;
+ } hscsd_mult_slot_cap;
+
+ bool ucs2_treatment;
+ bool extended_meas_cap;
+
+ struct {
+ bool present;
+ uint8_t sms_value;
+ uint8_t sm_value;
+ } ms_meas_cap;
+
+ struct {
+ bool present;
+ uint8_t method;
+ } ms_pos_method_cap;
+
+ struct {
+ bool present;
+ uint8_t mslot_class;
+ } ecsd_multislot_cap;
+
+ struct {
+ bool present;
+ bool mod_cap;
+
+ struct {
+ bool present;
+ uint8_t value;
+ } rf_pwr_cap_1;
+
+ struct {
+ bool present;
+ uint8_t value;
+ } rf_pwr_cap_2;
+
+ } psk8_struct;
+
+ struct {
+ bool present;
+ uint8_t value;
+ uint8_t assoc_radio_cap;
+ } gsm_400_bands_supp;
+
+ struct {
+ bool present;
+ uint8_t value;
+ } gsm_850_assoc_radio_cap;
+
+ struct {
+ bool present;
+ uint8_t value;
+ } gsm_1900_assoc_radio_cap;
+
+ bool umts_fdd_rat_cap;
+ bool umts_tdd_rat_cap;
+ bool cdma200_rat_cap;
+
+ struct {
+ bool present;
+ uint8_t mslot_class;
+ bool single_slot_dtm;
+ struct {
+ bool present;
+ uint8_t mslot_class;
+ } dtm_egprs_multislot_cap;
+ } dtm_gprs_multislot_cap;
+
+ struct {
+ bool present;
+ uint8_t value;
+ } single_band_supp;
+
+ struct {
+ bool present;
+ uint8_t value;
+ } gsm_750_assoc_radio_cap;
+
+ bool umts_1_28_mcps_tdd_rat_cap;
+ bool geran_feature_package;
+
+ struct {
+ bool present;
+ uint8_t mslot_class;
+ struct {
+ bool present;
+ uint8_t mslot_class;
+ } extended_dtm_egprs_multislot_cap;
+ } extended_dtm_gprs_multislot_cap;
+
+ struct {
+ bool present;
+ uint8_t value;
+ } high_multislot_cap;
+
+ bool geran_feature_package_2;
+ uint8_t gmsk_multislot_power_prof;
+ uint8_t psk8_multislot_power_prof;
+
+ struct {
+ bool present;
+ uint8_t value;
+ uint8_t assoc_radio_cap;
+ } t_gsm_400_bands_supp;
+
+ uint8_t dl_advanced_rx_perf;
+ bool dtm_enhancements_cap;
+
+ struct {
+ bool present;
+ uint8_t mslot_class;
+ bool offset_required;
+ struct {
+ bool present;
+ uint8_t mslot_class;
+ } dtm_egprs_high_multislot_cap;
+ } dtm_gprs_high_multislot_cap;
+
+ bool repeated_acch_capability;
+
+ struct {
+ bool present;
+ uint8_t value;
+ } gsm_710_assoc_radio_cap;
+
+ struct {
+ bool present;
+ uint8_t value;
+ } t_gsm_810_assoc_radio_cap;
+
+ bool ciphering_mode_setting_cap;
+ bool add_pos_cap;
+ bool e_utra_fdd_supp;
+ bool e_utra_tdd_supp;
+ bool e_utra_meas_rep_supp;
+ bool prio_resel_supp;
+ bool utra_csg_cells_rep;
+
+ uint8_t vamos_level;
+ uint8_t tighter_capability;
+
+ bool sel_ciph_dl_sacch;
+
+ uint8_t cs_ps_srvcc_geran_utra;
+ uint8_t cs_ps_srvcc_geran_eutra;
+
+ bool geran_net_sharing;
+ bool e_utra_wb_rsrq_meas_supp;
+ bool er_band_support;
+ bool utra_mult_band_ind_supp;
+ bool e_utra_mult_band_ind_supp;
+ bool extended_tsc_set_cap_supp;
+ bool extended_earfcn_val_range;
+};
+
struct osmo_gsm48_classmark {
bool classmark1_set;
struct gsm48_classmark1 classmark1;
@@ -111,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;
@@ -166,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;
@@ -227,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;
@@ -290,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;
@@ -320,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;
@@ -340,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
@@ -353,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
@@ -404,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;
@@ -424,6 +593,12 @@ struct gsm48_meas_res {
#endif
} __attribute__ ((packed));
+/*! Check if the given mr contains valid measurement results */
+static inline bool gsm48_meas_res_is_valid(const struct gsm48_meas_res *mr)
+{
+ return (mr->meas_valid == 0); /* 0 means valid */
+}
+
/* Chapter 10.5.2.21aa */
struct gsm48_multi_rate_conf {
#if OSMO_IS_LITTLE_ENDIAN
@@ -441,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
@@ -454,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));
@@ -469,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;
@@ -527,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;
@@ -542,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
@@ -556,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));
@@ -580,6 +755,10 @@ enum gsm48_chan_mode {
GSM48_CMODE_DATA_12k0 = 0x03,
GSM48_CMODE_DATA_6k0 = 0x0b,
GSM48_CMODE_DATA_3k6 = 0x13,
+ GSM48_CMODE_SPEECH_V1_VAMOS = 0xc1,
+ GSM48_CMODE_SPEECH_V2_VAMOS = 0xc2,
+ GSM48_CMODE_SPEECH_V3_VAMOS = 0xc3,
+ GSM48_CMODE_SPEECH_V5_VAMOS = 0xc5,
};
extern const struct value_string gsm48_chan_mode_names[];
@@ -618,7 +797,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
@@ -661,7 +840,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;
@@ -688,7 +867,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
@@ -709,7 +888,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;
@@ -733,7 +912,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;
@@ -749,7 +928,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
@@ -768,7 +947,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;
@@ -791,7 +970,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));
@@ -812,7 +991,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;
@@ -895,7 +1074,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];
@@ -910,7 +1089,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];
@@ -925,7 +1104,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];
@@ -944,7 +1123,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;
@@ -955,6 +1134,20 @@ 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 rr_short_pd:1, /* < RR short PD : bit > See 3GPP TS 24.007 §11.3.2 */
+ msg_type:5, /* < message type : bit(5) > See 3GPP TS 44.018 Table 10.4.2 */
+ l2_header:2; /* < short layer 2 header : bit(2) > See 3GPP TS 44.006 §6.4a */
+ 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 l2_header:2, msg_type:5, rr_short_pd:1;
+ uint8_t rest_octets[0];
+#endif
+} __attribute__ ((packed));
+
/* Section 9.1.43a System Information type 13 */
struct gsm48_system_information_type_13 {
struct gsm48_system_information_type_header header;
@@ -1010,7 +1203,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));
@@ -1067,7 +1260,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;
@@ -1090,7 +1283,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;
@@ -1120,7 +1313,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;
@@ -1143,7 +1336,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;
@@ -1361,6 +1554,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
@@ -1556,6 +1764,7 @@ static inline const char *osmo_lu_type_name(uint8_t lu_type)
#define GSM48_IE_CHDES_2_AFTER 0x64
#define GSM48_IE_MODE_SEC_CH 0x66
#define GSM48_IE_F_CH_SEQ_AFTER 0x69
+#define GSM48_IE_EXTENDED_TSC_SET 0x6d
#define GSM48_IE_MA_AFTER 0x72
#define GSM48_IE_BA_RANGE 0x73
#define GSM48_IE_GROUP_CHDES 0x74
@@ -1638,9 +1847,12 @@ enum gsm48_rr_cause {
GSM48_RR_CAUSE_ABNORMAL_TIMER = 0x03,
GSM48_RR_CAUSE_ABNORMAL_NOACT = 0x04,
GSM48_RR_CAUSE_PREMPTIVE_REL = 0x05,
+ GSM48_RR_CAUSE_UTRAN_CFG_UNK = 0x06,
GSM48_RR_CAUSE_HNDOVER_IMP = 0x08,
GSM48_RR_CAUSE_CHAN_MODE_UNACCT = 0x09,
GSM48_RR_CAUSE_FREQ_NOT_IMPL = 0x0a,
+ GSM48_RR_CAUSE_LEAVE_GROUP_CA = 0x0b,
+ GSM48_RR_CAUSE_LOW_LEVEL_FAIL = 0x0c,
GSM48_RR_CAUSE_CALL_CLEARED = 0x41,
GSM48_RR_CAUSE_SEMANT_INCORR = 0x5f,
GSM48_RR_CAUSE_INVALID_MAND_INF = 0x60,
diff --git a/include/osmocom/gsm/protocol/gsm_04_08_gprs.h b/include/osmocom/gsm/protocol/gsm_04_08_gprs.h
index 86c5b016..40c84132 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>
@@ -98,6 +97,7 @@ enum gsm48_gprs_ie_mm {
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_NET_FEAT_SUPPORT = 0xB0, /* 10.5.5.23 */
};
enum gsm48_gprs_ie_sm {
@@ -128,7 +128,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;
@@ -157,7 +157,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;
@@ -175,7 +175,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];
@@ -189,7 +189,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
@@ -290,6 +290,7 @@ enum gsm48_pdp_type_nr {
PDP_TYPE_N_ETSI_PPP = 0x01,
PDP_TYPE_N_IETF_IPv4 = 0x21,
PDP_TYPE_N_IETF_IPv6 = 0x57,
+ PDP_TYPE_N_IETF_IPv4v6 = 0x8D,
};
/* Figure 10.5.138/24.008 / Chapter 10.5.6.5 */
@@ -463,6 +464,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_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 1390f0e8..fad75c2a 100644
--- a/include/osmocom/gsm/protocol/gsm_08_08.h
+++ b/include/osmocom/gsm/protocol/gsm_08_08.h
@@ -25,6 +25,10 @@ enum CELL_IDENT {
CELL_IDENT_UTRAN_PLMN_LAC_RNC = 8,
CELL_IDENT_UTRAN_RNC = 9,
CELL_IDENT_UTRAN_LAC_RNC = 10,
+ CELL_IDENT_SAI = 11,
+
+ /* Not in 03.03 nor 08.08. Place them > 0x0f (discr_id is 4 bits) */
+ CELL_IDENT_WHOLE_GLOBAL_PS = 128, /* CGI + RAC, TS 48.018 8c.1.4.1.1 */
};
/* Keep this misnamed CELL_IDENT for API backwards compatibility (see OS#3124). */
#define CELL_IDENT_LAI_AND_LAC CELL_IDENT_LAI
@@ -46,12 +50,12 @@ struct dtap_header {
uint8_t link_id; /* Backward compatibility */
struct {
#if OSMO_IS_LITTLE_ENDIAN
- uint8_t dlci_cc:2,
+ uint8_t dlci_sapi:3, /* enum gsm0406_dlci_sapi */
dlci_spare:3,
- dlci_sapi:3; /* enum gsm0406_dlc_sapi */
+ dlci_cc:2;
#elif OSMO_IS_BIG_ENDIAN
-/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianess.py) */
- uint8_t dlci_sapi:3, dlci_spare:3, dlci_cc:2;
+/* 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
};
};
@@ -262,7 +266,7 @@ enum GSM0808_IE_CODING {
GSM0808_IE_SEGMENTATION = 79,
GSM0808_IE_SERVICE_HANDOVER = 80,
GSM0808_IE_SOURCE_RNC_TO_TARGET_RNC_TRANSPARENT_UMTS = 81,
- GSM0808_IE_SOURCE_RNC_TO_TARGET_RNC_TRANSPARENT_CDMA2000= 82,
+ GSM0808_IE_SOURCE_RNC_TO_TARGET_RNC_TRANSPARENT_CDMA2000 = 82,
GSM0808_IE_RESERVED_5 = 65,
GSM0808_IE_RESERVED_6 = 66,
GSM0808_IE_GERAN_CLASSMARK = 0x53,
@@ -330,7 +334,8 @@ enum GSM0808_IE_CODING {
GSM0808_IE_OSMO_OSMUX_CID = 0xf1,
};
-/* 3GPP TS 48.008 3.2.3 Signalling Field Element Coding */
+/* 3GPP TS 48.008 3.2.3 Signalling Field Element Coding.
+ See also extra fields in 3.2.2.58 and 3.2.2.80 */
enum GSM0808_SIGNALLING_FIELD_ELEMENT_CODING {
GSM0808_FE_IE_EXTRA_INFORMATION = 0x01, /*< 3.2.3.1 */
GSM0808_FE_IE_CURRENT_CHANNEL_TYPE_2 = 0x02, /*< 3.2.3.2 */
@@ -350,6 +355,8 @@ enum GSM0808_SIGNALLING_FIELD_ELEMENT_CODING {
GSM0808_FE_IE_IRAT_MEASUREMENT_CONFIGURATION = 0x0f, /*< 3.2.3.16 */
GSM0808_FE_IE_SOURCE_CELL_ID = 0x10, /*< 3.2.3.17 */
GSM0808_FE_IE_IRAT_MEASUREMENT_CONFIGURATION_EXTENDED_E_ARFCNS = 0x11, /*< 3.2.3.18 */
+ GSM0808_FE_IE_VGCS_TALKER_MODE = 0x6f, /*< 3.2.2.93 */
+ GSM0808_FE_IE_LAST_USED_EUTRAN_PLMN_ID = 0x95, /*< 3.2.2.127 */
};
/* 3GPP TS 48.008 3.2.2.5 Cause */
@@ -436,13 +443,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,
@@ -491,6 +500,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); }
@@ -533,11 +578,16 @@ enum gsm0808_paging_info {
GSM0808_PAGINF_FOR_USSD = 0x02,
};
-/*! 3GPP TS 48.008 3.2.2.104 Speech Codec */
+/*! 3GPP TS 48.008 3.2.2.104 Speech Codec.
+ * Valid if (fi || pi || pt) == true, otherwise ignore. */
struct gsm0808_speech_codec {
+ /*! Full IP: AoIP with compressed speech via RTP/UDP/IP. */
bool fi;
+ /*! PCMoIP: PCM over A-Interface via RTP/UPD/IP. */
bool pi;
+ /*! PCMoTDM: PCM over A-Interface with TDM as transport. */
bool pt;
+ /*! TFO (Inband Tandem Free Operation). Only valid if (pi || pt) == true. */
bool tf;
/*! See enum gsm0808_speech_codec_type. */
uint8_t type;
@@ -556,7 +606,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,
@@ -566,9 +653,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,
@@ -580,9 +670,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,
@@ -594,6 +687,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 {
@@ -601,13 +717,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 */
@@ -671,7 +806,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 cd13a7dc..e932ff8a 100644
--- a/include/osmocom/gsm/protocol/gsm_08_58.h
+++ b/include/osmocom/gsm/protocol/gsm_08_58.h
@@ -15,10 +15,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#pragma once
@@ -53,6 +49,10 @@ union abis_rsl_chan_nr {
#define ABIS_RSL_CHAN_NR_CBITS_OSMO_CBCH4 0x19 /*< non-standard, for CBCH/SDCCH4 */
#define ABIS_RSL_CHAN_NR_CBITS_OSMO_CBCH8 0x1a /*< non-standard, for CBCH/SDCCH8 */
+/* non-standard, Osmocom specific Bm/Lm equivalents for VAMOS */
+#define ABIS_RSL_CHAN_NR_CBITS_OSMO_VAMOS_Bm_ACCHs 0x1d /*< VAMOS TCH/F */
+#define ABIS_RSL_CHAN_NR_CBITS_OSMO_VAMOS_Lm_ACCHs(ss) (0x1e + (ss)) /*< VAMOS TCH/H */
+
/* Link Identifier 9.3.2 */
union abis_rsl_link_id {
#if OSMO_IS_BIG_ENDIAN
@@ -116,6 +116,33 @@ struct abis_rsl_cchan_hdr {
uint8_t data[0]; /*!< message payload data */
} __attribute__ ((packed));
+/* Osmocom specific IE to negotiate repeated ACCH capabilities */
+struct abis_rsl_osmo_rep_acch_cap {
+#if OSMO_IS_LITTLE_ENDIAN
+ uint8_t dl_facch_cmd:1,
+ dl_facch_all:1,
+ dl_sacch:1,
+ ul_sacch:1,
+ rxqual:3,
+ reserved:1;
+#elif OSMO_IS_BIG_ENDIAN
+/* 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));
+
+/* Osmocom specific IE to negotiate temporary overpower of ACCH channels */
+struct abis_rsl_osmo_temp_ovp_acch_cap {
+#if OSMO_IS_LITTLE_ENDIAN
+ uint8_t overpower_db:3,
+ rxqual:3,
+ facch_enable:1,
+ sacch_enable:1;
+#elif OSMO_IS_BIG_ENDIAN
+/* 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));
/* Chapter 9.1 */
/* RSL Message Discriminator: RLL */
@@ -337,6 +364,12 @@ enum abis_rsl_ie {
RSL_IE_SIEMENS_HIGHEST_RATE = 0x4e,
RSL_IE_SIEMENS_SUGGESTED_RATE = 0x4f,
+ /* Osmocom specific */
+ RSL_IE_OSMO_REP_ACCH_CAP = 0x60,
+ RSL_IE_OSMO_TRAINING_SEQUENCE = 0x61,
+ RSL_IE_OSMO_TEMP_OVP_ACCH_CAP = 0x62,
+ RSL_IE_OSMO_OSMUX_CID = 0x63,
+
/* ip.access */
RSL_IE_IPAC_SRTP_CONFIG = 0xe0,
RSL_IE_IPAC_PROXY_UDP = 0xe1,
@@ -353,9 +386,9 @@ enum abis_rsl_ie {
RSL_IE_IPAC_RTP_CSD_FMT = 0xf9,
RSL_IE_IPAC_RTP_JIT_BUF = 0xfa,
RSL_IE_IPAC_RTP_COMPR = 0xfb,
- RSL_IE_IPAC_RTP_PAYLOAD2= 0xfc,
+ RSL_IE_IPAC_RTP_PAYLOAD2 = 0xfc,
RSL_IE_IPAC_RTP_MPLEX = 0xfd,
- RSL_IE_IPAC_RTP_MPLEX_ID= 0xfe,
+ RSL_IE_IPAC_RTP_MPLEX_ID = 0xfe,
};
/* Ericsson specific IEs, clash with above partially, so they're not
@@ -385,10 +418,12 @@ enum abis_rsl_ie {
enum {
IPAC_UNWEIGHTED_AVE = 0,
IPAC_WEIGHTED_AVE,
- IPAC_MEDIAN_AVE
+ IPAC_MEDIAN_AVE,
+ /* EWMA is an Osmocom specific extension */
+ IPAC_OSMO_EWMA_AVE,
};
-/* IPAC MEAS_PREPROC AVERAGING PARAMID */
+/* IPAC MEAS_PREPROC AVERAGING PARAM ID */
enum {
IPAC_RXLEV_AVE = 0,
IPAC_RXQUAL_AVE,
@@ -431,6 +466,11 @@ enum {
#define RSL_CHAN_OSMO_CBCH4 0xc8 /*< non-standard, for CBCH/SDCCH4 */
#define RSL_CHAN_OSMO_CBCH8 0xd0 /*< non-standard, for CBCH/SDCCH8 */
+/* non-standard, Osmocom specific Bm/Lm equivalents for VAMOS */
+#define RSL_CHAN_OSMO_VAMOS_Bm_ACCHs 0xe8 /* VAMOS TCH/F */
+#define RSL_CHAN_OSMO_VAMOS_Lm_ACCHs 0xf0 /* VAMOS TCH/H */
+#define RSL_CHAN_OSMO_VAMOS_MASK 0xe0 /* VAMOS TCH/{F,H} */
+
/* Chapter 9.3.3 */
#define RSL_ACT_TYPE_INITIAL 0x00
#define RSL_ACT_TYPE_REACT 0x80
@@ -456,34 +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 */
-/* FIXME: More CRT types */
-/* Speech */
-#define RSL_CMOD_SP_GSM1 0x01
-#define RSL_CMOD_SP_GSM2 0x11
-#define RSL_CMOD_SP_GSM3 0x21
-/* 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 {
@@ -609,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));
@@ -672,10 +744,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 {
@@ -743,8 +815,14 @@ enum rsl_ipac_embedded_ie {
RSL_IPAC_EIE_SDCCH_CTL_PARAM = 0x1a,
RSL_IPAC_EIE_AMR_CONV_THRESH = 0x1b,
+ /* Osmocom specific extensions: */
+ RSL_IPAC_EIE_OSMO_MEAS_AVG_CFG = 0xf0,
+ RSL_IPAC_EIE_OSMO_MS_PWR_CTL = 0xf1,
+ RSL_IPAC_EIE_OSMO_PC_THRESH_COMP = 0xf2,
+
};
+/* Value of TLV IE RSL_IPAC_EIE_MEAS_AVG_CFG */
struct ipac_preproc_ave_cfg {
#if OSMO_IS_LITTLE_ENDIAN
uint8_t h_reqave:5,
@@ -752,13 +830,67 @@ struct ipac_preproc_ave_cfg {
reserved:1;
uint8_t h_reqt:5,
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];
#endif
}__attribute__ ((packed));
+
+struct osmo_preproc_ave_cfg_field {
+#if OSMO_IS_LITTLE_ENDIAN
+ uint8_t h_reqave:5,
+ ave_enabled:1,
+ reserved:2;
+ uint8_t h_reqt:5,
+ ave_method:3;
+#elif OSMO_IS_BIG_ENDIAN
+/* 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
+}__attribute__ ((packed));
+/* Value of TLV IE RSL_IPAC_EIE_OSMO_MEAS_AVG_CFG: */
+struct osmo_preproc_ave_cfg {
+ struct osmo_preproc_ave_cfg_field ci_fr;
+ struct osmo_preproc_ave_cfg_field ci_hr;
+ struct osmo_preproc_ave_cfg_field ci_amr_fr;
+ struct osmo_preproc_ave_cfg_field ci_amr_hr;
+ struct osmo_preproc_ave_cfg_field ci_sdcch;
+ struct osmo_preproc_ave_cfg_field ci_gprs;
+ uint8_t params[0]; /* Contains params for each above, appended one after the other */
+}__attribute__ ((packed));
+
+/*! MS/BS Power Control Thresholds (RSL_IPAC_EIE_MS_PWR_CTL) */
+struct ipac_preproc_pc_thresh {
+#if OSMO_IS_LITTLE_ENDIAN
+ uint8_t l_rxlev:6, reserved_l_rxlev:2;
+ uint8_t u_rxlev:6, reserved_u_rxlev:2;
+ 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_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;
+#endif
+}__attribute__ ((packed));
+
+/*! Osmocom extension for: MS/BS Power Control Thresholds (RSL_IPAC_EIE_OSMO_MS_PWR_CTL) */
+struct osmo_preproc_pc_thresh {
+ /* Carrier-to-Interference (C/I), in dB: */
+ int8_t l_ci_fr; int8_t u_ci_fr; /* FR/EFR */
+ int8_t l_ci_hr; int8_t u_ci_hr; /* HR */
+ int8_t l_ci_amr_fr; int8_t u_ci_amr_fr; /* AMR FR */
+ int8_t l_ci_amr_hr; int8_t u_ci_amr_hr; /* AMR HR */
+ int8_t l_ci_sdcch; int8_t u_ci_sdcch; /* SDCCH */
+ int8_t l_ci_gprs; int8_t u_ci_gprs; /* GPRS */
+}__attribute__ ((packed));
+
+/*! Handover Thresholds */
struct ipac_preproc_ho_thresh {
#if OSMO_IS_LITTLE_ENDIAN
uint8_t l_rxlev_ul_h:6,
@@ -776,7 +908,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;
@@ -786,6 +918,60 @@ struct ipac_preproc_ho_thresh {
#endif
}__attribute__ ((packed));
+/*! PC Threshold Comparators (RSL_IPAC_EIE_PC_THRESH_COMP) */
+struct ipac_preproc_pc_comp {
+#if OSMO_IS_LITTLE_ENDIAN
+ uint8_t p1:5, reserved_p1:3;
+ uint8_t n1:5, reserved_n1:3;
+ uint8_t p2:5, reserved_p2:3;
+ uint8_t n2:5, reserved_n2:3;
+ uint8_t p3:5, reserved_p3:3;
+ uint8_t n3:5, reserved_n3:3;
+ uint8_t p4:5, reserved_p4:3;
+ uint8_t n4:5, reserved_n4:3;
+ 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_endianness.py) */
+ uint8_t reserved_p1:3, p1:5;
+ uint8_t reserved_n1:3, n1:5;
+ uint8_t reserved_p2:3, p2:5;
+ uint8_t reserved_n2:3, n2:5;
+ uint8_t reserved_p3:3, p3:5;
+ uint8_t reserved_n3:3, n3:5;
+ uint8_t reserved_p4:3, p4:5;
+ uint8_t reserved_n4:3, n4:5;
+ uint8_t reserved_pc:3, pc_interval:5;
+ uint8_t inc_step_size:4, red_step_size:4;
+#endif
+}__attribute__ ((packed));
+
+/*! Osmocom extension for: PC Threshold Comparators (RSL_IPAC_EIE_OSMO_PC_THRESH_COMP) */
+struct ipac_preproc_pc_comp_field {
+#if OSMO_IS_LITTLE_ENDIAN
+ uint8_t lower_p:5, reserved_lower_p:3;
+ uint8_t lower_n:5, reserved_lower_n:3;
+ 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_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;
+ uint8_t reserved_upper_n:3, upper_n:5;
+#endif
+}__attribute__ ((packed));
+struct osmo_preproc_pc_comp {
+ /* Used for Carrier-to-Interference (C/I), in dB: */
+ struct ipac_preproc_pc_comp_field ci_fr;
+ struct ipac_preproc_pc_comp_field ci_hr;
+ struct ipac_preproc_pc_comp_field ci_amr_fr;
+ struct ipac_preproc_pc_comp_field ci_amr_hr;
+ struct ipac_preproc_pc_comp_field ci_sdcch;
+ struct ipac_preproc_pc_comp_field ci_gprs;
+}__attribute__ ((packed));
+
+/*! HO Threshold Comparators */
struct ipac_preproc_ho_comp {
#if OSMO_IS_LITTLE_ENDIAN
uint8_t p5:5,
@@ -809,7 +995,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;
@@ -832,7 +1018,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
@@ -847,7 +1033,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;
@@ -860,7 +1046,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));
@@ -876,4 +1062,18 @@ struct ipac_preproc_cfg {
struct ipac_preproc_ho_ctl_param ho_ctl_param;
};
+struct rsl_l1_info {
+#if OSMO_IS_LITTLE_ENDIAN
+ uint8_t reserved:1,
+ srr_sro:1,
+ fpc_epc:1,
+ ms_pwr:5;
+ uint8_t ta;
+#elif OSMO_IS_BIG_ENDIAN
+/* 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
+} __attribute__ ((packed));
+
/*! @} */
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 06755c93..96a6f51b 100644
--- a/include/osmocom/gsm/protocol/gsm_12_21.h
+++ b/include/osmocom/gsm/protocol/gsm_12_21.h
@@ -1,3 +1,4 @@
+/* 3GPP TS 12.21, nowadays 3GPP TS 52.021 */
/*
* (C) 2008-2009 by Harald Welte <laforge@gnumonks.org>
* All Rights Reserved
@@ -12,10 +13,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#pragma once
@@ -445,7 +442,7 @@ enum abis_nm_attr {
NM_ATT_IPACC_NS_CFG = 0xa0,
NM_ATT_IPACC_BSSGP_CFG = 0xa1,
NM_ATT_IPACC_NS_LINK_CFG = 0xa2,
- NM_ATT_IPACC_RLC_CFG = 0xa3,
+ NM_ATT_IPACC_RLC_CFG = 0xa3,
NM_ATT_IPACC_ALM_THRESH_LIST = 0xa4,
NM_ATT_IPACC_MONIT_VAL_LIST = 0xa5,
NM_ATT_IPACC_TIB_CONTROL = 0xa6,
@@ -462,7 +459,7 @@ enum abis_nm_attr {
NM_ATT_BS11_RF_RES_IND_PER = 0x8f,
-
+
NM_ATT_BS11_RX_LEV_MIN_CELL = 0x90,
NM_ATT_BS11_ABIS_EXT_TIME = 0x91,
NM_ATT_BS11_TIMER_HO_REQUEST = 0x92,
@@ -526,9 +523,11 @@ enum abis_nm_adm_state {
/*! OML Availability State (Section 9.4.7) */
enum abis_nm_avail_state {
- NM_AVSTATE_IN_TEST = 1,
+ NM_AVSTATE_IN_TEST = 0,
+ NM_AVSTATE_FAILED = 1,
NM_AVSTATE_POWER_OFF = 2,
NM_AVSTATE_OFF_LINE = 3,
+ /* <not used> = 4, */
NM_AVSTATE_DEPENDENCY = 5,
NM_AVSTATE_DEGRADED = 6,
NM_AVSTATE_NOT_INSTALLED= 7,
@@ -564,8 +563,10 @@ enum abis_nm_chan_comb {
NM_CHANC_IPAC_TCHFull_PDCH = 0x80,
NM_CHANC_IPAC_TCHFull_TCHHalf = 0x81,
/* osmocom */
- NM_CHANC_OSMO_TCHFull_TCHHalf_PDCH = 0x90,
+ NM_CHANC_OSMO_DYN = 0x90,
};
+/* Backward compatibility with older naming: */
+#define NM_CHANC_OSMO_TCHFull_TCHHalf_PDCH NM_CHANC_OSMO_DYN
/*! Event Type (Section 9.4.16) */
enum abis_nm_event_type {
@@ -789,6 +790,13 @@ enum ipac_bcch_info_type {
IPAC_BINF_CELL_ALLOC = (1 << 2),
};
+/*! Osmocom NSVC address type for NM_ATT_OSMO_NS_LINK_CFG */
+enum osmo_oml_nsvc_address_type {
+ OSMO_NSVC_ADDR_UNSPEC = 0x00,
+ OSMO_NSVC_ADDR_IPV4 = 0x04,
+ OSMO_NSVC_ADDR_IPV6 = 0x29,
+};
+
/*! 3GPP TS 52.021 §9.4.62 SW Description */
struct abis_nm_sw_desc {
uint8_t file_id[UINT8_MAX];
diff --git a/include/osmocom/gsm/protocol/gsm_23_032.h b/include/osmocom/gsm/protocol/gsm_23_032.h
new file mode 100644
index 00000000..6eb65ca2
--- /dev/null
+++ b/include/osmocom/gsm/protocol/gsm_23_032.h
@@ -0,0 +1,252 @@
+/*! \defgroup gad 3GPP TS 23.032 GAD: Universal Geographical Area Description.
+ * @{
+ * \file gsm_23_032.h
+ */
+/*
+ * (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * Author: Neels Hofmeyr <neels@hofmeyr.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#pragma once
+
+#include <stdint.h>
+#include <osmocom/core/endian.h>
+
+enum gad_type {
+ /*! Ellipsoid point */
+ GAD_TYPE_ELL_POINT = 0,
+ /*! Ellipsoid point with uncertainty circle. */
+ GAD_TYPE_ELL_POINT_UNC_CIRCLE = 1,
+ /*! Ellipsoid point with uncertainty ellipse. */
+ GAD_TYPE_ELL_POINT_UNC_ELLIPSE = 3,
+ GAD_TYPE_POLYGON = 5,
+ /*! Ellipsoid point with altitude. */
+ GAD_TYPE_ELL_POINT_ALT = 8,
+ /*! Ellipsoid point with altitude and uncertainty ellipsoid. */
+ GAD_TYPE_ELL_POINT_ALT_UNC_ELL = 9,
+ /*! Ellipsoid arc */
+ GAD_TYPE_ELL_ARC = 10,
+ /*! High accuracy ellipsoid point with uncertainty ellipse. */
+ GAD_TYPE_HA_ELL_POINT_UNC_ELLIPSE = 11,
+ /*! High accuracy ellipsoid point with altitude and uncertainty ellipsoid. */
+ GAD_TYPE_HA_ELL_POINT_ALT_UNC_ELL = 12,
+};
+
+struct gad_raw_head {
+#if OSMO_IS_LITTLE_ENDIAN
+ uint8_t spare:4,
+ type:4;
+#elif OSMO_IS_BIG_ENDIAN
+/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianness.py) */
+ uint8_t type:4, spare:4;
+#endif
+} __attribute__ ((packed));
+
+struct gad_raw_ell_point {
+ struct gad_raw_head h; /*!< type = GAD_TYPE_ELL_POINT */
+ uint8_t lat[3];
+ uint8_t lon[3];
+} __attribute__ ((packed));
+
+struct gad_raw_ell_point_unc_circle {
+#if OSMO_IS_LITTLE_ENDIAN
+ struct gad_raw_head h; /*!< type = GAD_TYPE_ELL_POINT_UNC_CIRCLE */
+ uint8_t lat[3];
+ uint8_t lon[3];
+ uint8_t unc:7,
+ spare2:1;
+#elif OSMO_IS_BIG_ENDIAN
+/* 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];
+ uint8_t spare2:1, unc:7;
+#endif
+} __attribute__ ((packed));
+
+struct gad_raw_ell_point_unc_ellipse {
+#if OSMO_IS_LITTLE_ENDIAN
+ struct gad_raw_head h; /*!< type = GAD_TYPE_ELL_POINT_UNC_ELLIPSE */
+ uint8_t lat[3];
+ uint8_t lon[3];
+ uint8_t unc_semi_major:7,
+ spare1:1;
+ uint8_t unc_semi_minor:7,
+ spare2:1;
+ uint8_t major_ori;
+ uint8_t confidence:7,
+ spare3:1;
+#elif OSMO_IS_BIG_ENDIAN
+/* 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];
+ uint8_t spare1:1, unc_semi_major:7;
+ uint8_t spare2:1, unc_semi_minor:7;
+ uint8_t major_ori;
+ uint8_t spare3:1, confidence:7;
+#endif
+} __attribute__ ((packed));
+
+struct gad_raw_polygon {
+ struct {
+#if OSMO_IS_LITTLE_ENDIAN
+ 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_endianness.py) */
+ uint8_t type:4, num_points:4;
+#endif
+ } h;
+ struct {
+ uint8_t lat[3];
+ uint8_t lon[3];
+ } point[15];
+} __attribute__ ((packed));
+
+struct gad_raw_ell_point_alt {
+ struct gad_raw_head h; /*!< type = GAD_TYPE_ELL_POINT_ALT */
+ uint8_t lat[3];
+ uint8_t lon[3];
+ uint8_t alt[2];
+} __attribute__ ((packed));
+
+struct gad_raw_ell_point_alt_unc_ell {
+#if OSMO_IS_LITTLE_ENDIAN
+ struct gad_raw_head h; /*!< type = GAD_TYPE_ELL_POINT_ALT_UNC_ELL */
+ uint8_t lat[3];
+ uint8_t lon[3];
+ uint8_t alt[2];
+ uint8_t unc_semi_major:7,
+ spare1:1;
+ uint8_t unc_semi_minor:7,
+ spare2:1;
+ uint8_t major_ori;
+ uint8_t unc_alt:7,
+ spare3:1;
+ uint8_t confidence:7,
+ spare4:1;
+#elif OSMO_IS_BIG_ENDIAN
+/* 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];
+ uint8_t alt[2];
+ uint8_t spare1:1, unc_semi_major:7;
+ uint8_t spare2:1, unc_semi_minor:7;
+ uint8_t major_ori;
+ uint8_t spare3:1, unc_alt:7;
+ uint8_t spare4:1, confidence:7;
+#endif
+} __attribute__ ((packed));
+
+struct gad_raw_ell_arc {
+#if OSMO_IS_LITTLE_ENDIAN
+ struct gad_raw_head h; /*!< type = GAD_TYPE_ELL_ARC */
+ uint8_t lat[3];
+ uint8_t lon[3];
+ uint8_t inner_r[2];
+ uint8_t unc_r:7,
+ spare1:1;
+ uint8_t ofs_angle;
+ uint8_t incl_angle;
+ uint8_t confidence:7,
+ spare2:1;
+#elif OSMO_IS_BIG_ENDIAN
+/* 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];
+ uint8_t inner_r[2];
+ uint8_t spare1:1, unc_r:7;
+ uint8_t ofs_angle;
+ uint8_t incl_angle;
+ uint8_t spare2:1, confidence:7;
+#endif
+} __attribute__ ((packed));
+
+struct gad_raw_ha_ell_point_unc_ell {
+#if OSMO_IS_LITTLE_ENDIAN
+ struct gad_raw_head h; /*!< type = GAD_TYPE_HA_ELL_POINT_UNC_ELLIPSE */
+ uint8_t lat[4];
+ uint8_t lon[4];
+ uint8_t alt[3];
+ uint8_t unc_semi_major;
+ uint8_t unc_semi_minor;
+ uint8_t major_ori;
+ uint8_t confidence:7,
+ spare1:1;
+#elif OSMO_IS_BIG_ENDIAN
+/* 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];
+ uint8_t alt[3];
+ uint8_t unc_semi_major;
+ uint8_t unc_semi_minor;
+ uint8_t major_ori;
+ uint8_t spare1:1, confidence:7;
+#endif
+} __attribute__ ((packed));
+
+struct gad_raw_ha_ell_point_alt_unc_ell {
+#if OSMO_IS_LITTLE_ENDIAN
+ struct gad_raw_head h; /*!< type = GAD_TYPE_HA_ELL_POINT_ALT_UNC_ELL */
+ uint8_t lat[4];
+ uint8_t lon[4];
+ uint8_t alt[3];
+ uint8_t unc_semi_major;
+ uint8_t unc_semi_minor;
+ uint8_t major_ori;
+ uint8_t h_confidence:7,
+ spare1:1;
+ uint8_t unc_alt;
+ uint8_t v_confidence:7,
+ spare2:1;
+#elif OSMO_IS_BIG_ENDIAN
+/* 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];
+ uint8_t alt[3];
+ uint8_t unc_semi_major;
+ uint8_t unc_semi_minor;
+ uint8_t major_ori;
+ uint8_t spare1:1, h_confidence:7;
+ uint8_t unc_alt;
+ uint8_t spare2:1, v_confidence:7;
+#endif
+} __attribute__ ((packed));
+
+/*! GAD PDU in network-byte-order according to 3GPP TS 23.032 GAD: Universal Geographical Area Description. */
+union gad_raw {
+ struct gad_raw_head h;
+ struct gad_raw_ell_point ell_point;
+ struct gad_raw_ell_point_unc_circle ell_point_unc_circle;
+ struct gad_raw_ell_point_unc_ellipse ell_point_unc_ellipse;
+ struct gad_raw_polygon polygon;
+ struct gad_raw_ell_point_alt ell_point_alt;
+ struct gad_raw_ell_point_alt_unc_ell ell_point_alt_unc_ell;
+ struct gad_raw_ell_arc ell_arc;
+ struct gad_raw_ha_ell_point_unc_ell ha_ell_point_unc_ell;
+ struct gad_raw_ha_ell_point_alt_unc_ell ha_ell_point_alt_unc_ell;
+} __attribute__ ((packed));
+
+/*! @} */
diff --git a/include/osmocom/gsm/protocol/gsm_23_041.h b/include/osmocom/gsm/protocol/gsm_23_041.h
index e726cff2..2a2b006f 100644
--- a/include/osmocom/gsm/protocol/gsm_23_041.h
+++ b/include/osmocom/gsm/protocol/gsm_23_041.h
@@ -2,6 +2,16 @@
#include <osmocom/core/endian.h>
+/* Section 9.3.24: Warning-Type */
+enum gsm23041_warning_type_value {
+ CBS_ETWS_WARN_TYPE_EARTHQUAKE = 0,
+ CBS_ETWS_WARN_TYPE_TSUNAMI = 1,
+ CBS_ETWS_WARN_TYPE_EARTHQUAKE_AND_TSUNAMI = 2,
+ CBS_ETWS_WARN_TYPE_TEST = 3,
+ CBS_ETWS_WARN_TYPE_OTHER = 4,
+ /* 0000101-1111111 Reserved for future use */
+};
+
/* Section 9.4.1.2: GSM Message Format */
struct gsm23041_msg_param_gsm {
uint16_t serial_nr;
@@ -12,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
new file mode 100644
index 00000000..5c4dd2bb
--- /dev/null
+++ b/include/osmocom/gsm/protocol/gsm_25_415.h
@@ -0,0 +1,222 @@
+#pragma once
+/* Iu User Plane (IuUP) Definitions as per 3GPP TS 25.415 */
+/* (C) 2017 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved.
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+#include <stdint.h>
+#include <osmocom/core/endian.h>
+
+/* 3GPP TS 25.415 Section 6.6.2.1 */
+struct iuup_pdutype0_hdr {
+#if OSMO_IS_LITTLE_ENDIAN
+ /* control part */
+ uint8_t frame_nr:4,
+ pdu_type:4;
+ uint8_t rfci:6,
+ fqc:2;
+ /* checksum part */
+ uint8_t payload_crc_hi:2, header_crc:6;
+ uint8_t payload_crc_lo;
+
+ /* payload part */
+ uint8_t payload[0];
+#elif OSMO_IS_BIG_ENDIAN
+/* 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;
+ uint8_t payload_crc_lo;
+ uint8_t payload[0];
+#endif
+} __attribute__((packed));
+
+/* 3GPP TS 25.415 Section 6.6.2.2 */
+struct iuup_pdutype1_hdr {
+#if OSMO_IS_LITTLE_ENDIAN
+ /* control part */
+ uint8_t frame_nr:4,
+ pdu_type:4;
+ uint8_t rfci:6,
+ fqc:2;
+ /* checksum part */
+ uint8_t spare:2,
+ header_crc:6;
+ /* payload part */
+ uint8_t payload[0];
+#elif OSMO_IS_BIG_ENDIAN
+/* 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;
+ uint8_t payload[0];
+#endif
+} __attribute__((packed));
+
+/* 3GPP TS 25.415 Section 6.6.2.3 */
+struct iuup_pdutype14_hdr {
+#if OSMO_IS_LITTLE_ENDIAN
+ /* control part */
+ uint8_t frame_nr:2,
+ ack_nack:2,
+ pdu_type:4;
+ uint8_t proc_ind:4,
+ mode_version:4;
+ /* checksum part */
+ uint8_t payload_crc_hi:2, header_crc:6;
+ uint8_t payload_crc_lo;
+ /* payload part */
+ uint8_t payload[0];
+#elif OSMO_IS_BIG_ENDIAN
+/* 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;
+ uint8_t payload_crc_lo;
+ uint8_t payload[0];
+#endif
+} __attribute__((packed));
+
+/* 3GPP TS 25.415 Section 6.6.2.3.4.1 */
+struct iuup_ctrl_init_rfci_hdr {
+#if OSMO_IS_LITTLE_ENDIAN
+ uint8_t rfci:6,
+ li:1,
+ 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_endianness.py) */
+ uint8_t lri:1, li:1, rfci:6;
+ uint8_t subflow_length[0];
+#endif
+} __attribute__((packed));
+struct iuup_ctrl_init_hdr {
+#if OSMO_IS_LITTLE_ENDIAN
+ uint8_t chain_ind:1,
+ num_subflows_per_rfci:3,
+ ti:1,
+ 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_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* */
+;
+#endif
+} __attribute__((packed));
+struct iuup_ctrl_init_tail {
+#if OSMO_IS_LITTLE_ENDIAN
+ uint16_t versions_supported;
+ uint8_t spare:4,
+ 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_endianness.py) */
+ uint16_t versions_supported;
+ uint8_t data_pdu_type:4, spare:4;
+ uint8_t spare_extension[0];
+#endif
+} __attribute__((packed));
+
+/* 3GPP TS 25.415 Section 6.6.2.3.4.4 */
+struct iuup_ctrl_error_event {
+#if OSMO_IS_LITTLE_ENDIAN
+ struct iuup_pdutype14_hdr hdr;
+ uint8_t error_cause:6,
+ error_distance:2;
+ uint8_t spare_extension[0];
+#elif OSMO_IS_BIG_ENDIAN
+/* 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];
+#endif
+} __attribute__((packed));
+
+/* 3GPP TS 25.415 Section 6.6.2.3.2 */
+struct iuup_ctrl_ack {
+ struct iuup_pdutype14_hdr hdr;
+ uint8_t spare_extension[0];
+} __attribute__((packed));
+
+/* 3GPP TS 25.415 Section 6.6.2.3.3 */
+struct iuup_ctrl_nack {
+#if OSMO_IS_LITTLE_ENDIAN
+ struct iuup_pdutype14_hdr hdr;
+ uint8_t spare:2,
+ error_cause:6;
+ uint8_t spare_extension[0];
+#elif OSMO_IS_BIG_ENDIAN
+/* 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];
+#endif
+} __attribute__((packed));
+
+/* 3GPP TS 25.415 Section 6.6.2 + 6.6.3.1 */
+enum iuup_pdu_type {
+ IUUP_PDU_T_DATA_CRC = 0,
+ IUUP_PDU_T_DATA_NOCRC = 1,
+ IUUP_PDU_T_CONTROL = 14,
+};
+
+/* 3GPP TS 25.415 Section 6.6.3.2 */
+enum iuup_ack_nack {
+ IUUP_AN_PROCEDURE = 0,
+ IUUP_AN_ACK = 1,
+ IUUP_AN_NACK = 2,
+};
+
+/* 3GPP TS 25.415 Section 6.6.3.5 */
+enum iuup_fqc {
+ IUUP_FQC_FRAME_GOOD = 0,
+ IUUP_FQC_FRAME_BAD = 1,
+ IUUP_FQC_FRAME_BAD_RADIO = 2,
+};
+
+/* 3GPP TS 25.415 Section 6.6.3.7 */
+enum iuup_procedure {
+ IUUP_PROC_INIT = 0,
+ IUUP_PROC_RATE_CTRL = 1,
+ IUUP_PROC_TIME_ALIGN = 2,
+ IUUP_PROC_ERR_EVENT = 3,
+};
+
+
+/* 3GPP TS 25.415 Section 6.6.3.15 */
+enum iuup_error_distance {
+ IUUP_ERR_DIST_LOCAL = 0,
+ IUUP_ERR_DIST_FIRST_FWD = 1,
+ IUUP_ERR_DIST_SECOND_FWD = 2,
+ IUUP_ERR_DIST_RESERVED = 3,
+};
+
+
+/* 3GPP TS 25.415 Section 6.6.3.16 */
+enum iuup_error_cause {
+ IUUP_ERR_CAUSE_CRC_ERR_HDR = 0,
+ IUUP_ERR_CAUSE_CRC_ERR_DATA = 1,
+ IUUP_ERR_CAUSE_UNEXPECTED_FN = 2,
+ IUUP_ERR_CAUSE_FRAME_LOSS = 3,
+ IUUP_ERR_CAUSE_UNKNOWN_PDUTYPE = 4,
+ IUUP_ERR_CAUSE_UNKNOWN_PROC = 5,
+ IUUP_ERR_CAUSE_UNKNNOWN_RES_VAL = 6,
+ IUUP_ERR_CAUSE_UNKNNOWN_FIELD = 7,
+ IUUP_ERR_CAUSE_FRAME_TOO_SHORT = 8,
+ IUUP_ERR_CAUSE_MISSING_FIELDS = 9,
+ IUUP_ERR_CAUSE_UNEXPECTED_PDU_T = 16,
+ IUUP_ERR_CAUSE_UNEXPECTED_PROC = 18,
+ IUUP_ERR_CAUSE_UNEXPECTED_RFCI = 19,
+ IUUP_ERR_CAUSE_UNEXPECTED_VALUE = 20,
+ IUUP_ERR_CAUSE_INIT_FAILURE = 42,
+ IUUP_ERR_CAUSE_INIT_FAILURE_NET_TMR = 43,
+ IUUP_ERR_CAUSE_INIT_FAILURE_REP_NACK = 44,
+ IUUP_ERR_CAUSE_RATE_CTRL_FAILURE = 45,
+ IUUP_ERR_CAUSE_ERR_EVENT_FAIL = 46,
+ IUUP_ERR_CAUSE_TIME_ALIGN_NOTSUPP = 47,
+ IUUP_ERR_CAUSE_REQ_TIME_ALIGN_NOTPOSS = 48,
+ IUUP_ERR_CAUSE_MODE_VERSION_NOT_SUPPORTED = 49,
+};
diff --git a/include/osmocom/gsm/protocol/gsm_44_004.h b/include/osmocom/gsm/protocol/gsm_44_004.h
new file mode 100644
index 00000000..c30ba0c9
--- /dev/null
+++ b/include/osmocom/gsm/protocol/gsm_44_004.h
@@ -0,0 +1,19 @@
+#pragma once
+
+#include <osmocom/core/endian.h>
+
+/* TS 44.004 Section 7.1 */
+
+struct gsm_sacch_l1_hdr {
+#if OSMO_IS_LITTLE_ENDIAN
+ uint8_t ms_pwr:5,
+ fpc_epc:1,
+ srr_sro:1,
+ reserved:1;
+ uint8_t ta;
+#elif OSMO_IS_BIG_ENDIAN
+/* 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
+} __attribute__ ((packed));
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_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_48_071.h b/include/osmocom/gsm/protocol/gsm_48_071.h
new file mode 100644
index 00000000..961211b3
--- /dev/null
+++ b/include/osmocom/gsm/protocol/gsm_48_071.h
@@ -0,0 +1,118 @@
+/*! \defgroup bsslap 3GPP TS 48.071 BSS LCS Assistance Protocol (BSSLAP).
+ * @{
+ * \file gsm_48_071.h
+ */
+/*
+ * (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Neels Hofmeyr <neels@hofmeyr.de>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+#pragma once
+
+#include <osmocom/gsm/protocol/gsm_04_08.h>
+
+enum bsslap_msgt {
+ BSSLAP_MSGT_TA_REQUEST = 0x1,
+ BSSLAP_MSGT_TA_RESPONSE = 0x2,
+ BSSLAP_MSGT_REJECT = 0xa,
+ BSSLAP_MSGT_RESET = 0xb,
+ BSSLAP_MSGT_ABORT = 0xc,
+ BSSLAP_MSGT_TA_LAYER3 = 0xd,
+ BSSLAP_MSGT_MS_POS_CMD = 0xf,
+ BSSLAP_MSGT_MS_POS_RESP = 0x10,
+ BSSLAP_MSGT_UTDOA_REQ = 0x11,
+ BSSLAP_MSGT_UTDOA_RESP = 0x12,
+};
+
+enum bsslap_cause {
+ BSSLAP_CAUSE_CONGESTION = 0x0,
+ BSSLAP_CAUSE_CHAN_MODE_NOT_SUPP = 0x1,
+ BSSLAP_CAUSE_POS_PROC_NOT_SUPP = 0x2,
+ BSSLAP_CAUSE_OTHER_RADIO_EVT_FAIL = 0x3,
+ BSSLAP_CAUSE_INTRA_BSS_HO = 0x4,
+ BSSLAP_CAUSE_SUPERV_TIMER_EXPIRED = 0x5,
+ BSSLAP_CAUSE_INTER_BSS_HO = 0x6,
+ BSSLAP_CAUSE_LOSS_SIG_CONN_MS = 0x7,
+ BSSLAP_CAUSE_INCORR_SERV_CELL_ID = 0x8,
+ BSSLAP_CAUSE_BSSAP_LE_SEGMENT_ERR = 0x9,
+ BSSLAP_CAUSE_CONCUR_POS_PROC_NOT_EN = 0xa,
+};
+
+enum bsslap_iei {
+ BSSLAP_IEI_TA = 0x1,
+ BSSLAP_IEI_CELL_ID = 0x9,
+ BSSLAP_IEI_CHAN_DESC = 0x10,
+ BSSLAP_IEI_MEAS_REP = 0x14,
+ BSSLAP_IEI_CAUSE = 0x18,
+ BSSLAP_IEI_RRLP_FLAG = 0x19,
+ BSSLAP_IEI_RRLP = 0x1b,
+ BSSLAP_IEI_CELL_ID_LIST = 0x1c,
+ BSSLAP_IEI_ENH_MEAS_REP = 0x1d,
+ BSSLAP_IEI_LAC = 0x1e,
+ BSSLAP_IEI_FREQ_LIST = 0x21,
+ BSSLAP_IEI_MS_POWER = 0x22,
+ BSSLAP_IEI_DELTA_TIMER = 0x23,
+ BSSLAP_IEI_SERVING_CELL_ID = 0x24,
+ BSSLAP_IEI_ENCR_KEY = 0x25,
+ BSSLAP_IEI_CIPH_MODE_SET = 0x26,
+ BSSLAP_IEI_CHAN_MODE = 0x27,
+ BSSLAP_IEI_MR_CONFIG = 0x28,
+ BSSLAP_IEI_POLLING_REPETITION = 0x29,
+ BSSLAP_IEI_PACKET_CHAN_DESC = 0x2a,
+ BSSLAP_IEI_TLLI = 0x2b,
+ BSSLAP_IEI_TFI = 0x2c,
+ BSSLAP_IEI_TBF_START_TIME = 0x2d,
+ BSSLAP_IEI_PWRUP_START_TIME = 0x2e,
+ BSSLAP_IEI_LONG_ENCR_KEY = 0x2f,
+ BSSLAP_IEI_CONCUR_POS_PROC_F = 0x30,
+};
+
+struct bsslap_ta_response {
+ uint16_t cell_id;
+ uint8_t ta;
+
+ bool more_items; /*!< always set this to false */
+};
+
+struct bsslap_ta_layer3 {
+ uint8_t ta;
+
+ bool more_items; /*!< always set this to false */
+};
+
+struct bsslap_reset {
+ uint16_t cell_id;
+ uint8_t ta;
+ struct gsm48_chan_desc chan_desc;
+ enum bsslap_cause cause;
+
+ bool more_items; /*!< always set this to false */
+};
+
+struct bsslap_pdu {
+ enum bsslap_msgt msg_type;
+ union {
+ /* ta_request: a TA Request message consists only of the message type. */
+ struct bsslap_ta_response ta_response;
+ enum bsslap_cause reject;
+ struct bsslap_reset reset;
+ enum bsslap_cause abort;
+ struct bsslap_ta_layer3 ta_layer3;
+ };
+};
+
+/*! @} */
diff --git a/include/osmocom/gsm/protocol/gsm_49_031.h b/include/osmocom/gsm/protocol/gsm_49_031.h
new file mode 100644
index 00000000..463fabf5
--- /dev/null
+++ b/include/osmocom/gsm/protocol/gsm_49_031.h
@@ -0,0 +1,234 @@
+/*! \defgroup bssmap_le 3GPP TS 49.031 BSSMAP-LE.
+ * @{
+ * \file gsm_49_031.h
+ */
+/*
+ * (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Neels Hofmeyr <neels@hofmeyr.de>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+#pragma once
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#include <osmocom/core/endian.h>
+#include <osmocom/gsm/protocol/gsm_48_071.h>
+#include <osmocom/gsm/protocol/gsm_23_032.h>
+#include <osmocom/gsm/gsm0808_utils.h>
+#include <osmocom/gsm/gsm48.h>
+
+/*! 3GPP TS 49.031 10.13 LCS Cause, also in 3GPP TS 48.008 3.2.2.66, which simply refers to the former. */
+enum lcs_cause {
+ LCS_CAUSE_UNSPECIFIED = 0,
+ LCS_CAUSE_SYSTEM_FAILURE = 1,
+ LCS_CAUSE_PROTOCOL_ERROR = 2,
+ LCS_CAUSE_DATA_MISSING_IN_REQ = 3,
+ LCS_CAUSE_UNEXP_DATA_IN_REQ = 4,
+ LCS_CAUSE_POS_METH_FAILURE = 5,
+ LCS_CAUSE_TGT_MS_UNREACHABLE = 6,
+ LCS_CAUSE_REQUEST_ABORTED = 7,
+ LCS_CAUSE_FACILITY_NOTSUPP = 8,
+ LCS_CAUSE_INTER_BSC_HO = 9,
+ LCS_CAUSE_INTRA_BSC_HO = 10,
+ LCS_CAUSE_CONGESTION = 11,
+ LCS_CAUSE_INTER_NSE_CHG = 12,
+ LCS_CAUSE_RA_UPDAT = 13,
+ LCS_CAUSE_PTMSI_REALLOC = 14,
+ LCS_CAUSE_GPRS_SUSPENSION = 15,
+};
+
+/*! 3GPP TS 49.031 10.13 LCS Cause, also in 3GPP TS 48.008 3.2.2.66, which simply refers to the former. */
+struct lcs_cause_ie {
+ bool present;
+ enum lcs_cause cause_val;
+ bool diag_val_present;
+ uint8_t diag_val;
+};
+
+/* 3GPP TS 49.031 10.16 LCS QoS IE */
+struct osmo_bssmap_le_lcs_qos {
+#if OSMO_IS_LITTLE_ENDIAN
+ uint8_t vert:1, vel:1, spare1:6;
+ uint8_t ha_val:7, ha_ind:1;
+ 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;
+ uint8_t rt:2, spare3:6;
+#endif
+} __attribute__ ((packed));
+
+enum bssap_le_msg_discr {
+ BSSAP_LE_MSG_DISCR_BSSMAP_LE = 0,
+};
+
+enum bssmap_le_msgt {
+ BSSMAP_LE_MSGT_PERFORM_LOC_REQ = 0x2b,
+ BSSMAP_LE_MSGT_PERFORM_LOC_RESP = 0x2d,
+ BSSMAP_LE_MSGT_PERFORM_LOC_ABORT = 0x2e,
+ BSSMAP_LE_MSGT_PERFORM_LOC_INFO = 0x2f,
+ BSSMAP_LE_MSGT_ASSIST_INFO_REQ = 0x20,
+ BSSMAP_LE_MSGT_ASSIST_INFO_RESP = 0x21,
+ BSSMAP_LE_MSGT_CONN_ORIENTED_INFO = 0x2a,
+ BSSMAP_LE_MSGT_CONN_LESS_INFO = 0x3a,
+ BSSMAP_LE_MSGT_RESET = 0x30,
+ BSSMAP_LE_MSGT_RESET_ACK = 0x31,
+};
+
+enum bssmap_le_iei {
+ BSSMAP_LE_IEI_LCS_QoS = 0x3e,
+ BSSMAP_LE_IEI_LCS_PRIORITY = 0x43,
+ BSSMAP_LE_IEI_LOCATION_TYPE = 0x44,
+ BSSMAP_LE_IEI_GANSS_LOCATION_TYPE = 0x82,
+ BSSMAP_LE_IEI_GEO_LOCATION = 0x45,
+ BSSMAP_LE_IEI_POSITIONING_DATA = 0x46,
+ BSSMAP_LE_IEI_GANSS_POS_DATA = 0x83,
+ BSSMAP_LE_IEI_VELOCITY_DATA = 0x55,
+ BSSMAP_LE_IEI_LCS_CAUSE = 0x47,
+ BSSMAP_LE_IEI_LCS_CLIENT_TYPE = 0x48,
+ BSSMAP_LE_IEI_APDU = 0x49,
+ BSSMAP_LE_IEI_NET_ELEM_ID = 0x4a,
+ BSSMAP_LE_IEI_REQ_GPS_ASS_D = 0x4b,
+ BSSMAP_LE_IEI_REQ_GANSS_ASS_D = 0x41,
+ BSSMAP_LE_IEI_DECIPH_KEYS = 0x4c,
+ BSSMAP_LE_IEI_RET_ERR_REQ = 0x4d,
+ BSSMAP_LE_IEI_RET_ERR_CAUSE = 0x4e,
+ BSSMAP_LE_IEI_SEGMENTATION = 0x4f,
+ BSSMAP_LE_IEI_CLASSMARK3_INFO = 0x13,
+ BSSMAP_LE_IEI_CAUSE = 0x4,
+ BSSMAP_LE_IEI_CELL_ID = 0x5,
+ BSSMAP_LE_IEI_CHOSEN_CHAN = 0x21,
+ BSSMAP_LE_IEI_IMSI = 0x0,
+ BSSMAP_LE_IEI_LCS_CAPABILITY = 0x50,
+ BSSMAP_LE_IEI_PKT_MEAS_REP = 0x51,
+ BSSMAP_LE_IEI_CELL_ID_LIST = 0x52,
+ BSSMAP_LE_IEI_IMEI = 0x80,
+ BSSMAP_LE_IEI_BSS_MLAT_CAP = 0x84,
+ BSSMAP_LE_IEI_CELL_INFO_LIST = 0x85,
+ BSSMAP_LE_IEI_BTS_RX_ACC_LVL = 0x86,
+ BSSMAP_LE_IEI_MLAT_METHOD = 0x87,
+ BSSMAP_LE_IEI_MLAT_TA = 0x88,
+ BSSMAP_LE_IEI_MS_SYNC_ACC = 0x89,
+ BSSMAP_LE_IEI_SHORT_ID_SET = 0x8a,
+ BSSMAP_LE_IEI_RANDOM_ID_SET = 0x8b,
+ BSSMAP_LE_IEI_SHORT_BSS_ID = 0x8c,
+ BSSMAP_LE_IEI_RANDOM_ID = 0x8d,
+ BSSMAP_LE_IEI_SHORT_ID = 0x8e,
+ BSSMAP_LE_IEI_COVERAGE_CLASS = 0x8f,
+ BSSMAP_LE_IEI_MTA_ACC_SEC_RQD = 0x90,
+};
+
+enum bssmap_le_apdu_proto {
+ BSSMAP_LE_APDU_PROT_RESERVED = 0,
+ BSSMAP_LE_APDU_PROT_BSSLAP = 1,
+ BSSMAP_LE_APDU_PROT_LLP = 2,
+ BSSMAP_LE_APDU_PROT_SMLCPP = 3,
+};
+
+enum bssmap_le_location_information {
+ BSSMAP_LE_LOC_INFO_CURRENT_GEOGRAPHIC = 0x0,
+ BSSMAP_LE_LOC_INFO_ASSIST_TARGET_MS = 0x1,
+ BSSMAP_LE_LOC_INFO_BC_DECIPHER_KEYS = 0x2,
+};
+
+enum bssmap_le_positioning_method {
+ BSSMAP_LE_POS_METHOD_OMITTED = 0x0,
+ BSSMAP_LE_POS_METHOD_MOBILE_ASSISTED_E_OTD = 0x1,
+ BSSMAP_LE_POS_METHOD_MOBILE_BASED_E_OTD = 0x2,
+ BSSMAP_LE_POS_METHOD_ASSISTED_GPS = 0x3,
+};
+
+struct bssmap_le_location_type {
+ enum bssmap_le_location_information location_information;
+ enum bssmap_le_positioning_method positioning_method;
+};
+
+enum bssmap_le_lcs_client_type {
+ BSSMAP_LE_LCS_CTYPE_VALUE_ADDED_UNSPECIFIED = 0x0,
+ BSSMAP_LE_LCS_CTYPE_PLMN_OPER_UNSPECIFIED = 0x20,
+ BSSMAP_LE_LCS_CTYPE_PLMN_OPER_BCAST_SERVICE = 0x21,
+ BSSMAP_LE_LCS_CTYPE_PLMN_OPER_OAM = 0x22,
+ BSSMAP_LE_LCS_CTYPE_PLMN_OPER_ANON_STATS = 0x23,
+ BSSMAP_LE_LCS_CTYPE_PLMN_OPER_TGT_MS_SVC = 0x24,
+ BSSMAP_LE_LCS_CTYPE_EMERG_SVC_UNSPECIFIED = 0x30,
+ BSSMAP_LE_LCS_CTYPE_LI_UNSPECIFIED = 0x40,
+};
+
+struct bssmap_le_perform_loc_req {
+ struct bssmap_le_location_type location_type;
+ struct gsm0808_cell_id cell_id;
+
+ bool lcs_client_type_present;
+ enum bssmap_le_lcs_client_type lcs_client_type;
+
+ struct osmo_mobile_identity imsi;
+ struct osmo_mobile_identity imei;
+
+ bool apdu_present;
+ struct bsslap_pdu apdu;
+
+ bool more_items; /*!< set this to true iff any fields below are used */
+
+ bool lcs_priority_present;
+ uint8_t lcs_priority; /*!< see in 3GPP TS 29.002 */
+
+ bool lcs_qos_present;
+ struct osmo_bssmap_le_lcs_qos lcs_qos;
+
+ bool more_items2; /*!< always set this to false */
+};
+
+struct bssmap_le_perform_loc_resp {
+ bool location_estimate_present;
+ union gad_raw location_estimate;
+
+ struct lcs_cause_ie lcs_cause;
+
+ bool more_items; /*!< always set this to false */
+};
+
+struct bssmap_le_conn_oriented_info {
+ struct bsslap_pdu apdu;
+
+ bool more_items; /*!< always set this to false */
+};
+
+struct bssmap_le_pdu {
+ enum bssmap_le_msgt msg_type;
+ union {
+ enum gsm0808_cause reset;
+ /* reset_ack consists only of the message type */
+ struct bssmap_le_perform_loc_req perform_loc_req;
+ struct bssmap_le_perform_loc_resp perform_loc_resp;
+ struct lcs_cause_ie perform_loc_abort;
+ struct bssmap_le_conn_oriented_info conn_oriented_info;
+ };
+};
+
+struct bssap_le_pdu {
+ enum bssap_le_msg_discr discr;
+ union {
+ struct bssmap_le_pdu bssmap_le;
+ /* future: add DTAP PDU, currently not implemented */
+ };
+};
+
+/*! @} */
diff --git a/include/osmocom/gsm/protocol/ipaccess.h b/include/osmocom/gsm/protocol/ipaccess.h
index 4f1d0b16..51827607 100644
--- a/include/osmocom/gsm/protocol/ipaccess.h
+++ b/include/osmocom/gsm/protocol/ipaccess.h
@@ -39,6 +39,7 @@ enum ipaccess_proto_ext {
IPAC_PROTO_EXT_GSUP = 0x05, /* GSUP GPRS extension */
IPAC_PROTO_EXT_OAP = 0x06, /* Osmocom Authn Protocol */
IPAC_PROTO_EXT_RSPRO = 0x07, /* Remote SIM protocol */
+ IPAC_PROTO_EXT_PCU = 0x08, /* BSC<->BTS<->PCU communication */
};
enum ipaccess_msgtype {
diff --git a/include/osmocom/gsm/tlv.h b/include/osmocom/gsm/tlv.h
index 254c21bc..fd6659c6 100644
--- a/include/osmocom/gsm/tlv.h
+++ b/include/osmocom/gsm/tlv.h
@@ -4,6 +4,7 @@
#include <string.h>
#include <osmocom/core/msgb.h>
+#include <osmocom/core/byteswap.h>
#include <osmocom/core/bit16gen.h>
#include <osmocom/core/bit32gen.h>
@@ -40,6 +41,16 @@
/*! maximum length of TLV of one byte length */
#define TVLV_MAX_ONEBYTE 0x7f
+/*! error return codes of various TLV parser functions */
+enum osmo_tlv_parser_error {
+ OSMO_TLVP_ERR_OFS_BEYOND_BUFFER = -1,
+ OSMO_TLVP_ERR_OFS_LEN_BEYOND_BUFFER = -2,
+ OSMO_TLVP_ERR_UNKNOWN_TLV_TYPE = -3,
+
+ OSMO_TLVP_ERR_MAND_IE_MISSING = -50,
+ OSMO_TLVP_ERR_IE_TOO_SHORT = -51,
+};
+
/*! gross length of a TVLV type field */
static inline uint16_t TVLV_GROSS_LEN(uint16_t len)
{
@@ -137,7 +148,7 @@ static inline uint8_t *tl16v_put(uint8_t *buf, uint8_t tag, uint16_t len,
*buf++ = len >> 8;
*buf++ = len & 0xff;
memcpy(buf, val, len);
- return buf + len*2;
+ return buf + len;
}
/*! put (append) a TL16 field. */
@@ -157,7 +168,7 @@ static inline uint8_t *t16lv_put(uint8_t *buf, uint16_t tag, uint8_t len,
*buf++ = tag & 0xff;
*buf++ = len;
memcpy(buf, val, len);
- return buf + len + 2;
+ return buf + len;
}
/*! put (append) a TvLV field */
@@ -268,6 +279,20 @@ static inline uint8_t *msgb_tvlv_put(struct msgb *msg, uint8_t tag, uint16_t len
return tvlv_put(buf, tag, len, val);
}
+/*! put (append) a TvLV field containing a big-endian 16bit value to msgb. */
+static inline uint8_t *msgb_tvlv_put_16be(struct msgb *msg, uint8_t tag, uint16_t val)
+{
+ uint16_t val_be = osmo_htons(val);
+ return msgb_tvlv_put(msg, tag, 2, (const uint8_t *)&val_be);
+}
+
+/*! put (append) a TvLV field containing a big-endian 16bit value to msgb. */
+static inline uint8_t *msgb_tvlv_put_32be(struct msgb *msg, uint8_t tag, uint32_t val)
+{
+ uint32_t val_be = osmo_htonl(val);
+ return msgb_tvlv_put(msg, tag, 4, (const uint8_t *)&val_be);
+}
+
/*! put (append) a vTvLV field to \ref msgb */
static inline uint8_t *msgb_vtvlv_gan_put(struct msgb *msg, uint16_t tag,
uint16_t len, const uint8_t *val)
@@ -297,7 +322,7 @@ static inline uint8_t *v_put(uint8_t *buf, uint8_t val)
}
/*! put (append) a TV field */
-static inline uint8_t *tv_put(uint8_t *buf, uint8_t tag,
+static inline uint8_t *tv_put(uint8_t *buf, uint8_t tag,
uint8_t val)
{
*buf++ = tag;
@@ -319,7 +344,7 @@ static inline uint8_t *tv_fixed_put(uint8_t *buf, uint8_t tag,
* \param[in] tag Tag value
* \param[in] val Value (in host byte order!)
*/
-static inline uint8_t *tv16_put(uint8_t *buf, uint8_t tag,
+static inline uint8_t *tv16_put(uint8_t *buf, uint8_t tag,
uint16_t val)
{
*buf++ = tag;
@@ -381,7 +406,7 @@ static inline uint8_t *msgb_tl_put(struct msgb *msg, uint8_t tag)
return len;
}
-/*! put (append) a TV16 field to a \ref msgb
+/*! put (append) a TV16 field (network order) to the given msgb
* \returns pointer to first byte after newly-put information */
static inline uint8_t *msgb_tv16_put(struct msgb *msg, uint8_t tag, uint16_t val)
{
@@ -389,6 +414,16 @@ static inline uint8_t *msgb_tv16_put(struct msgb *msg, uint8_t tag, uint16_t val
return tv16_put(buf, tag, val);
}
+/*! put (append) a TV32 field (network order) to the given msgb
+ * \returns pointer to first byte after newly-put information */
+static inline uint8_t *msgb_tv32_put(struct msgb *msg, uint8_t tag, uint32_t val)
+{
+ uint8_t *buf = msgb_put(msg, 1 + 4);
+ *buf++ = tag;
+ osmo_store32be(val, buf);
+ return msg->tail;
+}
+
/*! push (prepend) a TLV field to a \ref msgb
* \returns pointer to first byte of newly-pushed information */
static inline uint8_t *msgb_tlv_push(struct msgb *msg, uint8_t tag, uint8_t len, const uint8_t *val)
@@ -508,7 +543,7 @@ int tlv_encode(struct msgb *msg, const struct tlv_definition *def, const struct
int tlv_encode_ordered(struct msgb *msg, const struct tlv_definition *def, const struct tlv_parsed *tp,
const uint8_t *tag_order, unsigned int tag_order_len);
-#define TLVP_PRESENT(x, y) ((x)->lv[y].val)
+#define TLVP_PRESENT(x, y) (!!((x)->lv[y].val))
#define TLVP_LEN(x, y) (x)->lv[y].len
#define TLVP_VAL(x, y) (x)->lv[y].val
@@ -620,4 +655,54 @@ int osmo_match_shift_tlv(uint8_t **data, size_t *data_len,
int osmo_shift_lv(uint8_t **data, size_t *data_len,
uint8_t **value, size_t *value_len);
+#define MSG_DEF(name, mand_ies, flags) { name, mand_ies, ARRAY_SIZE(mand_ies), flags }
+
+struct osmo_tlv_prot_msg_def {
+ /*! human-readable name of message type (optional) */
+ const char *name;
+ /*! array of mandatory IEs */
+ const uint8_t *mand_ies;
+ /*! number of entries in 'mand_ies' above */
+ uint8_t mand_count;
+ /*! user-defined flags (like uplink/downlink/...) */
+ uint32_t flags;
+};
+struct osmo_tlv_prot_ie_def {
+ /*! minimum length of IE value part, in octets */
+ uint16_t min_len;
+ /*! huamn-readable name (optional) */
+ const char *name;
+};
+
+/*! Osmocom TLV protocol definition */
+struct osmo_tlv_prot_def {
+ /*! human-readable name of protocol */
+ const char *name;
+ /*! TLV parser definition (optional) */
+ const struct tlv_definition *tlv_def;
+ /*! definition of each message (8-bit message type) */
+ struct osmo_tlv_prot_msg_def msg_def[256];
+ /*! definition of IE for each 8-bit tag */
+ struct osmo_tlv_prot_ie_def ie_def[256];
+ /*! value_string array of message type names (legacy, if not populated in msg_def) */
+ const struct value_string *msgt_names;
+};
+
+const char *osmo_tlv_prot_msg_name(const struct osmo_tlv_prot_def *pdef, uint8_t msg_type);
+const char *osmo_tlv_prot_ie_name(const struct osmo_tlv_prot_def *pdef, uint8_t iei);
+
+int osmo_tlv_prot_validate_tp(const struct osmo_tlv_prot_def *pdef, uint8_t msg_type,
+ const struct tlv_parsed *tp, int log_subsys, const char *log_pfx);
+
+int osmo_tlv_prot_parse(const struct osmo_tlv_prot_def *pdef,
+ struct tlv_parsed *dec, unsigned int dec_multiples, uint8_t msg_type,
+ const uint8_t *buf, unsigned int buf_len, uint8_t lv_tag, uint8_t lv_tag2,
+ int log_subsys, const char *log_pfx);
+
+static inline uint32_t osmo_tlv_prot_msgt_flags(const struct osmo_tlv_prot_def *pdef, uint8_t msg_type)
+{
+ return pdef->msg_def[msg_type].flags;
+}
+
+
/*! @} */
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/class_tables.h b/include/osmocom/sim/class_tables.h
index d5be39dd..ec9ec490 100644
--- a/include/osmocom/sim/class_tables.h
+++ b/include/osmocom/sim/class_tables.h
@@ -11,10 +11,6 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#pragma once
diff --git a/include/osmocom/sim/sim.h b/include/osmocom/sim/sim.h
index bfd1ac94..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>
@@ -11,6 +10,13 @@
#define APDU_HDR_LEN 5
#define MAX_AID_LEN 16 /* Table 13.2 of TS 102 221 */
+
+/*! Maximum Answer-To-Reset (ATR) size in bytes
+ * @note defined in ISO/IEC 7816-3:2006(E) section 8.2.1 as 32, on top the initial character TS of section 8.1
+ * @remark technical there is no size limitation since Yi present in T0,TDi will indicate if more interface bytes are present, including TDi+i
+ */
+#define OSIM_MAX_ATR_LEN 33
+
/*! command-response pairs cases
*
* Enumeration used to identify the APDU structure based on command-response pair case , as specified in ISO/IEC 7816-3:2006(E) §12.1.
@@ -283,7 +289,7 @@ struct osim_card_sw {
} u;
};
-#define OSIM_CARD_SW_LAST (const struct osim_card_sw) { \
+#define OSIM_CARD_SW_LAST { \
.code = 0, .mask = 0, .type = SW_TYPE_NONE, \
.class = SW_CLS_NONE, .u.str = NULL \
}
@@ -368,6 +374,8 @@ struct osim_reader_ops {
const char *name;
struct osim_reader_hdl *(*reader_open)(int idx, const char *name, void *ctx);
struct osim_card_hdl *(*card_open)(struct osim_reader_hdl *rh, enum osim_proto proto);
+ int (*card_reset)(struct osim_card_hdl *card, bool cold_reset);
+ int (*card_close)(struct osim_card_hdl *card);
int (*transceive)(struct osim_reader_hdl *rh, struct msgb *msg);
};
@@ -409,6 +417,10 @@ struct osim_card_hdl {
/*! list of applications found on card */
struct llist_head apps;
+
+ /*! ATR (Answer To Reset) of the card */
+ uint8_t atr[OSIM_MAX_ATR_LEN];
+ unsigned int atr_len;
};
struct osim_chan_hdl {
@@ -430,4 +442,5 @@ int osim_transceive_apdu(struct osim_chan_hdl *st, struct msgb *amsg);
struct osim_reader_hdl *osim_reader_open(enum osim_reader_driver drv, int idx,
const char *name, void *ctx);
struct osim_card_hdl *osim_card_open(struct osim_reader_hdl *rh, enum osim_proto proto);
-#endif /* _OSMOCOM_SIM_H */
+int osim_card_reset(struct osim_card_hdl *card, bool cold_reset);
+int osim_card_close(struct osim_card_hdl *card);
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/usb/libusb.h b/include/osmocom/usb/libusb.h
index 9ad3f71a..33caa862 100644
--- a/include/osmocom/usb/libusb.h
+++ b/include/osmocom/usb/libusb.h
@@ -12,10 +12,6 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <libusb.h>
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 47d2e5f3..61b58815 100644
--- a/include/osmocom/vty/command.h
+++ b/include/osmocom/vty/command.h
@@ -24,10 +24,12 @@
#pragma once
#include <stdio.h>
+#include <stdbool.h>
#include <sys/types.h>
#include "vector.h"
#include <osmocom/core/defs.h>
+#include <osmocom/core/utils.h>
/*! \defgroup command VTY Command
* @{
@@ -97,12 +99,21 @@ enum node_type {
L_CS7_SCCPADDR_NODE, /*!< SS7 SCCP Address */
L_CS7_SCCPADDR_GT_NODE, /*!< SS7 SCCP Global Title */
- L_CPU_SCHED_NODE, /*!< CPU Sched related options node */
+ L_CPU_SCHED_NODE, /*!< CPU Sched related options node */
+ L_NS_BIND_NODE, /*!< NS bind node */
+ L_NS_NSE_NODE, /*!< NS NSE node */
/*
* When adding new nodes to the libosmocore project, these nodes can be
* used to avoid ABI changes for unrelated projects.
*/
+ RESERVED1_NODE, /*!< Reserved for later extensions */
+ RESERVED2_NODE, /*!< Reserved for later extensions */
RESERVED3_NODE, /*!< Reserved for later extensions */
+ RESERVED4_NODE, /*!< Reserved for later extensions */
+ RESERVED5_NODE, /*!< Reserved for later extensions */
+ RESERVED6_NODE, /*!< Reserved for later extensions */
+ RESERVED7_NODE, /*!< Reserved for later extensions */
+ RESERVED8_NODE, /*!< Reserved for later extensions */
_LAST_OSMOVTY_NODE
};
@@ -137,6 +148,27 @@ struct cmd_node {
enum {
CMD_ATTR_DEPRECATED = (1 << 0),
CMD_ATTR_HIDDEN = (1 << 1),
+ CMD_ATTR_IMMEDIATE = (1 << 2),
+ CMD_ATTR_NODE_EXIT = (1 << 3),
+ CMD_ATTR_LIB_COMMAND = (1 << 4),
+};
+
+/*! Attributes shared between libraries (up to 32 entries). */
+enum {
+ /* The entries of this enum shall conform the following requirements:
+ * 1. Naming format: 'OSMO_' + <LIBNAME> + '_LIB_ATTR_' + <ATTRNAME>,
+ * where LIBNAME is a short name of the library, e.g. 'ABIS', 'MGCP',
+ * and ATTRNAME is a brief name of the attribute, e.g. RTP_CONN_EST;
+ * for example: 'OSMO_ABIS_LIB_ATTR_RSL_LINK_UP'.
+ * 2. Brevity: shortenings and abbreviations are welcome!
+ * 3. Values are not flags but indexes, unlike CMD_ATTR_*.
+ * 4. Ordering: new entries added before _OSMO_CORE_LIB_ATTR_COUNT. */
+ OSMO_SCCP_LIB_ATTR_RSTRT_ASP,
+ OSMO_ABIS_LIB_ATTR_IPA_NEW_LNK,
+ OSMO_ABIS_LIB_ATTR_LINE_UPD,
+
+ /* Keep this floating entry last, it's needed for count check. */
+ _OSMO_CORE_LIB_ATTR_COUNT
};
/*! Structure of a command element */
@@ -395,7 +427,9 @@ struct desc {
void install_node(struct cmd_node *, int (*)(struct vty *));
void install_default(int node_type) OSMO_DEPRECATED("Now happens implicitly with install_node()");
void install_element(int node_type, struct cmd_element *);
+void install_lib_element(int node_type, struct cmd_element *);
void install_element_ve(struct cmd_element *cmd);
+void install_lib_element_ve(struct cmd_element *cmd);
void sort_node(void);
void vty_install_default(int node_type) OSMO_DEPRECATED("Now happens implicitly with install_node()");
@@ -408,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);
@@ -423,7 +457,7 @@ extern struct cmd_element config_exit_cmd;
extern struct cmd_element config_help_cmd;
extern struct cmd_element config_list_cmd;
extern struct cmd_element config_end_cmd;
-char *host_config_file();
+const char *host_config_file(void);
void host_config_set(const char *);
char *osmo_asciidoc_escape(const char *inp);
@@ -433,6 +467,22 @@ void print_version(int print_copyright);
extern void *tall_vty_cmd_ctx;
-int vty_dump_xml_ref(FILE *stream);
+/*! VTY reference generation mode. */
+enum vty_ref_gen_mode {
+ /*! Default mode: all commands except deprecated and hidden. */
+ VTY_REF_GEN_MODE_DEFAULT = 0,
+ /*! Expert mode: all commands including hidden, excluding deprecated. */
+ VTY_REF_GEN_MODE_EXPERT,
+ /*! "Inverse" mode: only hidden commands. */
+ VTY_REF_GEN_MODE_HIDDEN,
+};
+
+extern const struct value_string vty_ref_gen_mode_names[];
+extern const struct value_string vty_ref_gen_mode_desc[];
+
+int vty_dump_xml_ref_mode(FILE *stream, enum vty_ref_gen_mode mode);
+int vty_dump_xml_ref(FILE *stream) OSMO_DEPRECATED("Use vty_dump_xml_ref_mode() instead");
+
+int vty_cmd_range_match(const char *range, const char *str);
/*! @} */
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/misc.h b/include/osmocom/vty/misc.h
index 2ad96508..f031c4ab 100644
--- a/include/osmocom/vty/misc.h
+++ b/include/osmocom/vty/misc.h
@@ -14,21 +14,31 @@ char *vty_cmd_string_from_valstr(void *ctx, const struct value_string *vals,
void vty_out_rate_ctr_group(struct vty *vty, const char *prefix,
struct rate_ctr_group *ctrg);
+void vty_out_rate_ctr_group2(struct vty *vty, const char *prefix,
+ struct rate_ctr_group *ctrg, bool skip_zero);
void vty_out_rate_ctr_group_fmt(struct vty *vty, const char *fmt,
struct rate_ctr_group *ctrg);
+void vty_out_rate_ctr_group_fmt2(struct vty *vty, const char *fmt,
+ struct rate_ctr_group *ctrg, bool skip_zero);
+
void vty_out_stat_item_group(struct vty *vty, const char *prefix,
struct osmo_stat_item_group *statg);
+void vty_out_stat_item_group2(struct vty *vty, const char *prefix,
+ struct osmo_stat_item_group *statg, bool skip_zero);
void vty_out_statistics_full(struct vty *vty, const char *prefix);
-void vty_out_statistics_partial(struct vty *vty, const char *prefix,
- int max_level);
+void vty_out_statistics_full2(struct vty *vty, const char *prefix, bool skip_zero);
+void vty_out_statistics_partial(struct vty *vty, const char *prefix, int max_level);
+void vty_out_statistics_partial2(struct vty *vty, const char *prefix, int max_level, bool skip_zero);
struct osmo_fsm;
struct osmo_fsm_inst;
void vty_out_fsm(struct vty *vty, struct osmo_fsm *fsm);
+void vty_out_fsm2(struct vty *vty, const char *prefix, struct osmo_fsm *fsm);
void vty_out_fsm_inst(struct vty *vty, struct osmo_fsm_inst *fsmi);
+void vty_out_fsm_inst2(struct vty *vty, const char *prefix, struct osmo_fsm_inst *fsmi);
void osmo_fsm_vty_add_cmds(void);
void osmo_talloc_vty_add_cmds(void);
diff --git a/include/osmocom/vty/ports.h b/include/osmocom/vty/ports.h
index b3550d15..bc001282 100644
--- a/include/osmocom/vty/ports.h
+++ b/include/osmocom/vty/ports.h
@@ -8,33 +8,43 @@
#pragma once
+#define OSMO_VTY_PORT_PCAP_CLIENT 4227
+#define OSMO_VTY_PORT_PCAP_SERVER 4228
/* 4236 used by control interface */
-#define OSMO_VTY_PORT_TRX 4237
+#define OSMO_VTY_PORT_TRX 4237
/* 4238 used by osmo-bts control interface */
-#define OSMO_VTY_PORT_STP 4239
-#define OSMO_VTY_PORT_PCU 4240 /* also: osmo_pcap_client */
-#define OSMO_VTY_PORT_BTS 4241 /* also: osmo_pcap_server */
-#define OSMO_VTY_PORT_NITB_BSC 4242
-#define OSMO_VTY_PORT_BSC_MGCP 4243
-#define OSMO_VTY_PORT_MGW OSMO_VTY_PORT_BSC_MGCP
-#define OSMO_VTY_PORT_BSC_NAT 4244
-#define OSMO_VTY_PORT_SGSN 4245
-#define OSMO_VTY_PORT_GBPROXY 4246
-#define OSMO_VTY_PORT_BB 4247
+#define OSMO_VTY_PORT_STP 4239
+#define OSMO_VTY_PORT_PCU 4240
+#define OSMO_VTY_PORT_BTS 4241
+#define OSMO_VTY_PORT_NITB_BSC 4242
+#define OSMO_VTY_PORT_BSC_MGCP 4243
+#define OSMO_VTY_PORT_MGW OSMO_VTY_PORT_BSC_MGCP
+#define OSMO_VTY_PORT_BSC_NAT 4244
+#define OSMO_VTY_PORT_SGSN 4245
+#define OSMO_VTY_PORT_GBPROXY 4246
+#define OSMO_VTY_PORT_BB 4247
/* 4249-4251 used by control interface */
-#define OSMO_VTY_PORT_BTSMGR 4252
-#define OSMO_VTY_PORT_GTPHUB 4253
-#define OSMO_VTY_PORT_MSC 4254
+#define OSMO_VTY_PORT_BTSMGR 4252
+#define OSMO_VTY_PORT_GTPHUB 4253
+#define OSMO_VTY_PORT_MSC 4254
/* 4255 used by control interface */
-#define OSMO_VTY_PORT_MNCC_SIP 4256
+#define OSMO_VTY_PORT_MNCC_SIP 4256
/* 4257 used by control interface */
-#define OSMO_VTY_PORT_HLR 4258
+#define OSMO_VTY_PORT_HLR 4258
/* 4259 used by control interface */
-#define OSMO_VTY_PORT_GGSN 4260
-#define OSMO_VTY_PORT_HNBGW 4261
+#define OSMO_VTY_PORT_GGSN 4260
+#define OSMO_VTY_PORT_HNBGW 4261
/* 4262-4263 used by control interface */
-#define OSMO_VTY_PORT_CBC 4264
-#define OSMO_VTY_PORT_UECUPS 4268
-#define OSMO_VTY_PORT_E1D 4269
-#define OSMO_VTY_PORT_SMLC 4271
+#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
+/* 4274 used by control interface */
+#define OSMO_VTY_PORT_UPF 4275
+/* 4276: OSMO_CTRL_PORT_UPF */
+#define OSMO_VTY_PORT_PFCP_TOOL 4277
+/* 4278: OSMO_CTRL_PORT_UPF */
/* When adding/changing port numbers, keep docs and wiki in sync. See above. */
diff --git a/include/osmocom/vty/telnet_interface.h b/include/osmocom/vty/telnet_interface.h
index da7cf839..73b79df1 100644
--- a/include/osmocom/vty/telnet_interface.h
+++ b/include/osmocom/vty/telnet_interface.h
@@ -14,14 +14,11 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#pragma once
+#include <osmocom/core/defs.h>
#include <osmocom/core/logging.h>
#include <osmocom/core/select.h>
@@ -45,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/vector.h b/include/osmocom/vty/vector.h
index 0a639ad9..ac524bc3 100644
--- a/include/osmocom/vty/vector.h
+++ b/include/osmocom/vty/vector.h
@@ -14,11 +14,6 @@
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with GNU Zebra; see the file COPYING. If not, write to the Free
- * Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
*/
#pragma once
diff --git a/include/osmocom/vty/vty.h b/include/osmocom/vty/vty.h
index fcef08e0..ed9f439c 100644
--- a/include/osmocom/vty/vty.h
+++ b/include/osmocom/vty/vty.h
@@ -3,6 +3,7 @@
#include <stdio.h>
#include <stdarg.h>
#include <stdbool.h>
+#include <time.h>
#include <osmocom/core/linuxlist.h>
#include <osmocom/core/defs.h>
@@ -28,6 +29,12 @@
#define VTY_BUFSIZ 512
#define VTY_MAXHIST 20
+/* Number of application / library specific VTY attributes */
+#define VTY_CMD_USR_ATTR_NUM 32
+/* Flag characters reserved for global VTY attributes */
+#define VTY_CMD_ATTR_FLAGS_RESERVED \
+ { '.', '!', '@', '^' }
+
/*! VTY events */
enum event {
VTY_SERV,
@@ -135,7 +142,7 @@ struct vty {
#define TELNET_NAWS_SB_LEN 5
/*! sub-negotiation buffer */
unsigned char sb_buf[TELNET_NAWS_SB_LEN];
- /*! How many subnegotiation characters have we received?
+ /*! How many subnegotiation characters have we received?
*
* We just drop those that do not fit in the buffer. */
size_t sb_len;
@@ -159,6 +166,9 @@ struct vty {
/*! When reading from a config file, these are the indenting characters expected for children of
* the current VTY node. */
char *indent;
+
+ /*! Whether the expert mode is enabled. */
+ bool expert_mode;
};
/* Small macro to determine newline is newline only or linefeed needed. */
@@ -193,14 +203,15 @@ struct vty_app_info {
/*! Check if the config is consistent before write */
int (*config_is_consistent)(struct vty *vty);
/*! Description of the application specific VTY attributes (optional). */
- const char * usr_attr_desc[32];
+ const char * usr_attr_desc[VTY_CMD_USR_ATTR_NUM];
/*! Flag letters of the application specific VTY attributes (optional). */
- char usr_attr_letters[32];
+ char usr_attr_letters[VTY_CMD_USR_ATTR_NUM];
};
/* Prototypes. */
void vty_init(struct vty_app_info *app_info);
int vty_read_config_file(const char *file_name, void *priv);
+int vty_read_config_filep(FILE *confp, void *priv);
void vty_init_vtysh (void);
void vty_reset (void);
struct vty *vty_new (void);
@@ -209,9 +220,11 @@ bool vty_is_active(struct vty *vty);
int vty_out (struct vty *, const char *, ...) VTY_PRINTF_ATTRIBUTE(2, 3);
int vty_out_va(struct vty *vty, const char *format, va_list ap);
int vty_out_newline(struct vty *);
+int vty_out_uptime(struct vty *vty, const struct timespec *starttime);
int vty_read(struct vty *vty);
//void vty_time_print (struct vty *, int);
void vty_close (struct vty *);
+void vty_flush(struct vty *vty);
char *vty_get_cwd (void);
void vty_log (const char *level, const char *proto, const char *fmt, va_list);
int vty_config_lock (struct vty *);
diff --git a/libosmocodec.pc.in b/libosmocodec.pc.in
index 9e058ef7..0c4de8d5 100644
--- a/libosmocodec.pc.in
+++ b/libosmocodec.pc.in
@@ -6,6 +6,6 @@ includedir=@includedir@
Name: Osmocom Codec related utilities Library
Description: C Utility Library
Version: @VERSION@
-Libs: -L${libdir} @TALLOC_LIBS@ -losmocodec
-Cflags: -I${includedir}/ @TALLOC_CFLAGS@
-
+Requires: talloc, libosmocore
+Libs: -L${libdir} -losmocodec
+Cflags: -I${includedir}/
diff --git a/libosmocoding.pc.in b/libosmocoding.pc.in
index d1d03c42..5b91a606 100644
--- a/libosmocoding.pc.in
+++ b/libosmocoding.pc.in
@@ -6,6 +6,6 @@ includedir=@includedir@
Name: Osmocom L1 transcoding Library
Description: C Utility Library
Version: @VERSION@
-Libs: -L${libdir} @TALLOC_LIBS@ -losmocoding -losmocodec -losmogsm -losmocore
-Cflags: -I${includedir}/ @TALLOC_CFLAGS@
-
+Requires: talloc, libosmocore, libosmogsm, libosmocodec
+Libs: -L${libdir} -losmocoding
+Cflags: -I${includedir}/
diff --git a/libosmocore.pc.in b/libosmocore.pc.in
index 80f17c8b..6479221e 100644
--- a/libosmocore.pc.in
+++ b/libosmocore.pc.in
@@ -6,6 +6,8 @@ includedir=@includedir@
Name: Osmocom Core Library
Description: C Utility Library
Version: @VERSION@
-Libs: -L${libdir} @TALLOC_LIBS@ -losmocore
+Requires: talloc @LIBMNL_PC@
+Requires.private: @LIBSCTP_PC@
+Libs: -L${libdir} -losmocore
Libs.private: @PTHREAD_LIBS@ @LIBSCTP_LIBS@
-Cflags: -I${includedir}/ @TALLOC_CFLAGS@ @PTHREAD_CFLAGS@
+Cflags: -I${includedir}/ @PTHREAD_CFLAGS@
diff --git a/libosmoctrl.pc.in b/libosmoctrl.pc.in
index 4676b31d..e9947dce 100644
--- a/libosmoctrl.pc.in
+++ b/libosmoctrl.pc.in
@@ -6,6 +6,6 @@ includedir=@includedir@
Name: Osmocom Control Interface Library
Description: C Utility Library
Version: @VERSION@
-Libs: -L${libdir} @TALLOC_LIBS@ -losmoctrl -losmogsm -losmocore
+Requires: talloc, libosmocore, libosmogsm
+Libs: -L${libdir} -losmoctrl
Cflags: -I${includedir}/
-
diff --git a/libosmogb.pc.in b/libosmogb.pc.in
index a163cc42..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@
-Libs: -L${libdir} @TALLOC_LIBS@ -losmogb -losmovty -losmocore
+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 0160be8d..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@
-Libs: -L${libdir} @TALLOC_LIBS@ -losmogsm -losmocore
+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/libosmosim.pc.in b/libosmosim.pc.in
index 83777c31..a8f50d6f 100644
--- a/libosmosim.pc.in
+++ b/libosmosim.pc.in
@@ -6,6 +6,6 @@ includedir=@includedir@
Name: Osmocom SIM card related utilities Library
Description: C Utility Library
Version: @VERSION@
-Libs: -L${libdir} @TALLOC_LIBS@ -losmosim -losmocore
+Requires: talloc, libosmocore
+Libs: -L${libdir} -losmosim
Cflags: -I${includedir}/
-
diff --git a/libosmousb.pc.in b/libosmousb.pc.in
index ce6d2715..dbedb0e5 100644
--- a/libosmousb.pc.in
+++ b/libosmousb.pc.in
@@ -6,6 +6,6 @@ includedir=@includedir@
Name: Osmocom libusb (USB) integration
Description: C Utility Library
Version: @VERSION@
-Libs: -L${libdir} @TALLOC_LIBS@ -losmousb -losmocore
+Requires: talloc, libusb-1.0, libosmocore
+Libs: -L${libdir} -losmousb
Cflags: -I${includedir}/
-
diff --git a/libosmovty.pc.in b/libosmovty.pc.in
index 6204e455..5d9286a2 100644
--- a/libosmovty.pc.in
+++ b/libosmovty.pc.in
@@ -6,6 +6,6 @@ includedir=@includedir@
Name: Osmocom VTY Interface Library
Description: C Utility Library
Version: @VERSION@
-Libs: -L${libdir} @TALLOC_LIBS@ -losmovty -losmocore
+Requires: talloc, libosmocore
+Libs: -L${libdir} -losmovty
Cflags: -I${includedir}/
-
diff --git a/osmo-release.sh b/osmo-release.sh
index 31cd1a03..5f31c5ff 100755
--- a/osmo-release.sh
+++ b/osmo-release.sh
@@ -9,10 +9,11 @@ fi
ALLOW_NO_LIBVERSION_CHANGE="${ALLOW_NO_LIBVERSION_CHANGE:-0}"
ALLOW_NO_LIBVERSION_DEB_MATCH="${ALLOW_NO_LIBVERSION_DEB_MATCH:-0}"
+ALLOW_NO_LIBVERSION_RPM_MATCH="${ALLOW_NO_LIBVERSION_RPM_MATCH:-0}"
# Test stuff but don't modify stuff:
DRY_RUN="${DRY_RUN:-0}"
-libversion_to_deb_major() {
+libversion_to_lib_major() {
libversion="$1"
current="$(echo "$libversion" | cut -d ":" -f 1)"
#revision="$(echo "$libversion" | cut -d ":" -f 2)"
@@ -21,16 +22,19 @@ libversion_to_deb_major() {
echo "$major"
}
-# Make sure that depedency requirement versions match in configure.ac vs debian/control.
-#eg: "PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore >= 1.1.0)" vs "libosmocore-dev (>= 1.0.0),"
-check_configureac_debctrl_deps_match() {
+get_configureac_pkg_check_modules_list() {
if [ -f "${GIT_TOPDIR}/openbsc/configure.ac" ]; then
configureac_file="openbsc/configure.ac"
else
configureac_file="configure.ac"
fi
- configureac_list=$(grep -e "PKG_CHECK_MODULES(" "${GIT_TOPDIR}/${configureac_file}" | cut -d "," -f 2 | tr -d ")" | tr -d "[" | tr -d "]" | tr -d " " | sed "s/>=/ /g")
- echo "$configureac_list" | \
+ grep -e "PKG_CHECK_MODULES(" "${GIT_TOPDIR}/${configureac_file}" | cut -d "," -f 2 | tr -d ")" | tr -d "[" | tr -d "]" | tr -d " " | sed "s/>=/ /g"
+}
+
+# Make sure that depedency requirement versions match in configure.ac vs debian/control.
+#eg: "PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore >= 1.1.0)" vs "libosmocore-dev (>= 1.0.0),"
+check_configureac_debctrl_deps_match() {
+ get_configureac_pkg_check_modules_list | \
{ return_error=0
while read -r dep ver; do
@@ -41,7 +45,7 @@ check_configureac_debctrl_deps_match() {
if [ "z$debctrl_match_count" != "z1" ]; then
echo "WARN: configure.ac <$dep, $ver> matches debian/control $debctrl_match_count times, manual check required!"
else # 1 match:
- parsed_match=$(echo "$debctrl_match" | tr -d "(" | tr -d ")" | tr -d "," | tr -d " " | sed "s/>=/ /g")
+ parsed_match=$(echo "$debctrl_match" | tr -d "(" | tr -d ")" | tr -d "," | tr -d " " | tr -d "\t" | sed "s/>=/ /g")
debctrl_dep=$(echo "$parsed_match" | cut -d " " -f 1 | sed "s/-dev//g")
debctrl_ver=$(echo "$parsed_match" | cut -d " " -f 2)
if [ "z$dep" != "z$debctrl_dep" ] || [ "z$ver" != "z$debctrl_ver" ]; then
@@ -66,6 +70,51 @@ check_configureac_debctrl_deps_match() {
echo "OK: dependency specific versions in configure.ac and debian/control match"
}
+# Make sure that depedency requirement versions match in configure.ac vs contrib/*.spec.in.
+#eg: "PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore >= 1.1.0)" vs "pkgconfig(libosmocore-dev) >= 1.0.0,"
+check_configureac_rpmspecin_deps_match() {
+ # Some projects don't have rpm spec files:
+ if [ "z$(find "${GIT_TOPDIR}/contrib" -name "*.spec.in" | wc -l)" = "z0" ]; then
+ echo "INFO: Project has no 'contrib/*.spec.in' files, skipping RPM specific configure.ac dependency version checks"
+ return
+ fi
+
+ get_configureac_pkg_check_modules_list | \
+ { return_error=0
+ while read -r dep ver; do
+
+ rpmspecin_match="$(grep -e "pkgconfig(${dep})" ${GIT_TOPDIR}/contrib/*.spec.in | grep BuildRequires | grep pkgconfig | grep ">=")"
+ rpmspecin_match_count="$(echo "$rpmspecin_match" | grep -c ">=")"
+ if [ "z$rpmspecin_match_count" != "z0" ]; then
+ #echo "Dependency <$dep, $ver> from configure.ac matched in contrib/*.spec.in! ($rpmspecin_match_count)"
+ if [ "z$rpmspecin_match_count" != "z1" ]; then
+ echo "WARN: configure.ac <$dep, $ver> matches contrib/*.spec.in $rpmspecin_match_count times, manual check required!"
+ else # 1 match:
+ parsed_match=$(echo "$rpmspecin_match" | tr -d "(" | tr -d ")" | tr -d ":" | tr -d " " | tr -d "\t" | sed "s/BuildRequires//g" | sed "s/pkgconfig//g" |sed "s/>=/ /g")
+ rpmspecin_dep=$(echo "$parsed_match" | cut -d " " -f 1)
+ rpmspecin_ver=$(echo "$parsed_match" | cut -d " " -f 2)
+ if [ "z$dep" != "z$rpmspecin_dep" ] || [ "z$ver" != "z$rpmspecin_ver" ]; then
+ echo "ERROR: configure.ac <$dep, $ver> does NOT match contrib/*.spec.in <$rpmspecin_dep, $rpmspecin_ver>!"
+ return_error=1
+ #else
+ # echo "OK: configure.ac <$dep, $ver> matches contrib/*.spec.in <$debctrl_dep, $debctrl_ver>"
+ fi
+ fi
+ fi
+ done
+ if [ $return_error -ne 0 ]; then
+ exit 1
+ fi
+ }
+
+ # catch and forward exit from pipe subshell "while read":
+ if [ $? -ne 0 ]; then
+ echo "ERROR: exiting due to previous errors"
+ exit 1
+ fi
+ echo "OK: dependency specific versions in configure.ac and contrib/*.spec.in match"
+}
+
# Make sure that patches under debian/patches/ apply:
check_debian_patch_apply() {
if [ ! -d "${GIT_TOPDIR}/debian/patches" ]; then
@@ -82,26 +131,106 @@ check_debian_patch_apply() {
done
}
-BUMPVER=`command -v bumpversion`
-GIT_TOPDIR="$(git rev-parse --show-toplevel)"
-NEW_VER=`bumpversion --list --current-version $VERSION $REL --allow-dirty | awk -F '=' '{ print $2 }'`
-LIBVERS=`git grep -n LIBVERSION | grep '=' | grep am | grep -v LDFLAGS`
-MAKEMOD=`git diff --cached -GLIBVERSION --stat | grep Makefile.am`
-ISODATE=`date -I`
+libversion_deb_match() {
+ echo "$LIBVERS" | while read -r line; do
+ libversion=$(echo "$line" | cut -d "=" -f 2 | tr -d "[:space:]")
+ major="$(libversion_to_lib_major "$libversion")"
+ file_matches="$(find "${GIT_TOPDIR}/debian" -name "lib*${major}.install" | wc -l)"
+ if [ "z$file_matches" = "z0" ]; then
+ echo "ERROR: Found no matching debian/lib*$major.install file for LIBVERSION=$libversion"
+ exit 1
+ elif [ "z$file_matches" = "z1" ]; then
+ echo "OK: Found matching debian/lib*$major.install for LIBVERSION=$libversion"
+ else
+ echo "WARN: Found $file_matches files matching debian/lib*$major.install for LIBVERSION=$libversion, manual check required!"
+ fi
+ control_matches="$(grep -e "Package" "${GIT_TOPDIR}/debian/control" | grep "lib" | grep "$major$" | wc -l)"
+ if [ "z$control_matches" = "z0" ]; then
+ echo "ERROR: Found no matching Package lib*$major in debian/control for LIBVERSION=$libversion"
+ exit 1
+ elif [ "z$control_matches" = "z1" ]; then
+ echo "OK: Found 'Package: lib*$major' in debian/control for LIBVERSION=$libversion"
+ else
+ echo "WARN: Found $file_matches files matching 'Package: lib*$major' in debian/control for LIBVERSION=$libversion, manual check required!"
+ fi
+
+ dhstrip_lib_total="$(grep -e "dh_strip" "${GIT_TOPDIR}/debian/rules" | grep "\-plib" | wc -l)"
+ dhstrip_lib_matches="$(grep -e "dh_strip" "${GIT_TOPDIR}/debian/rules" | grep "\-plib" | grep "$major" | wc -l)"
+ if [ "z$dhstrip_lib_total" != "z0" ]; then
+ if [ "z$dhstrip_lib_matches" = "z0" ] ; then
+ echo "ERROR: Found no matching 'dh_strip -plib*$major' line in debian/rules for LIBVERSION=$libversion"
+ exit 1
+ elif [ "z$dhstrip_lib_total" = "z1" ]; then
+ echo "OK: Found 'dh_strip -plib*$major' in debian/rules for LIBVERSION=$libversion"
+ else
+ echo "WARN: Found $dhstrip_lib_matches/$dhstrip_lib_total dh_strip matches 'dh_strip -plib*$major' in debian/rules for LIBVERSION=$libversion, manual check required!"
+ fi
+ fi
+ done
+ # catch and forward exit from pipe subshell "while read":
+ if [ $? -ne 0 ]; then
+ exit 1
+ fi
+}
+
+libversion_rpmspecin_match() {
+ # Some projects don't have rpm spec files:
+ if [ "z$(find "${GIT_TOPDIR}/contrib" -name "*.spec.in" | wc -l)" = "z0" ]; then
+ echo "INFO: Project has no 'contrib/*.spec.in' files, skipping RPM specific LIBVERSION checks"
+ return
+ fi
+
+ echo "$LIBVERS" | while read -r line; do
+ libversion=$(echo "$line" | cut -d "=" -f 2 | tr -d "[:space:]")
+ major="$(libversion_to_lib_major "$libversion")"
+
+ control_matches="$(grep -e "%files" "${GIT_TOPDIR}/contrib/"*.spec.in | grep "lib" | grep "$major$" | wc -l)"
+ if [ "z$control_matches" = "z0" ]; then
+ echo "ERROR: Found no matching '%files -n lib*$major' in contrib/*.spec.in for LIBVERSION=$libversion"
+ exit 1
+ elif [ "z$control_matches" = "z1" ]; then
+ echo "OK: Found '%files -n lib*$major' in contrib/*.spec.in for LIBVERSION=$libversion"
+ else
+ echo "WARN: Found $file_matches files matching '%files -n lib*$major' in contrib/*.spec.in for LIBVERSION=$libversion, manual check required!"
+ fi
+
+ control_matches="$(grep -e "_libdir" "${GIT_TOPDIR}/contrib/"*.spec.in | grep "/lib" | grep "so.$major" | wc -l)"
+ if [ "z$control_matches" = "z0" ]; then
+ echo "ERROR: Found no matching '%_libdir/lib*.so.$major*' in contrib/*.spec.in for LIBVERSION=$libversion"
+ exit 1
+ elif [ "z$control_matches" = "z1" ]; then
+ echo "OK: Found '%_libdir/lib*.so.$major*' in contrib/*.spec.in for LIBVERSION=$libversion"
+ else
+ echo "WARN: Found $file_matches files matching '%_libdir/lib*.so.$major*' in contrib/*.spec.in for LIBVERSION=$libversion, manual check required!"
+ fi
+ done
+ # catch and forward exit from pipe subshell "while read":
+ if [ $? -ne 0 ]; then
+ exit 1
+ fi
+}
+
+
+BUMPVER=`command -v bumpversion`
if [ "z$BUMPVER" = "z" ]; then
echo Unable to find 'bumpversion' command.
exit 1
fi
-
+NEW_VER=`$BUMPVER --list --current-version $VERSION $REL --allow-dirty | awk -F '=' '{ print $2 }'`
if [ "z$NEW_VER" = "z" ]; then
echo "Please fix versioning to match http://semver.org/ spec (current is $VERSION) before proceeding."
exit 1
fi
+GIT_TOPDIR="$(git rev-parse --show-toplevel)"
+LIBVERS=`git grep -n LIBVERSION | grep '=' | grep am | grep -v LDFLAGS | grep -v osmo-release.sh`
+MAKEMOD=`git diff --cached -GLIBVERSION --stat | grep Makefile.am`
+ISODATE=`date -I`
echo "Releasing $VERSION -> $NEW_VER..."
check_configureac_debctrl_deps_match
+check_configureac_rpmspecin_deps_match
check_debian_patch_apply
if [ "z$LIBVERS" != "z" ]; then
@@ -112,49 +241,10 @@ if [ "z$LIBVERS" != "z" ]; then
exit 1
fi
if [ "z$ALLOW_NO_LIBVERSION_DEB_MATCH" = "z0" ]; then
- echo "$LIBVERS" | while read -r line; do
- libversion=$(echo "$line" | cut -d "=" -f 2 | tr -d "[:space:]")
- major="$(libversion_to_deb_major "$libversion")"
- file_matches="$(find "${GIT_TOPDIR}/debian" -name "lib*${major}.install" | wc -l)"
- if [ "z$file_matches" = "z0" ]; then
- echo "ERROR: Found no matching debian/lib*$major.install file for LIBVERSION=$libversion"
- exit 1
- elif [ "z$file_matches" = "z1" ]; then
- echo "OK: Found matching debian/lib*$major.install for LIBVERSION=$libversion"
- else
- echo "WARN: Found $file_matches files matching debian/lib*$major.install for LIBVERSION=$libversion, manual check required!"
- fi
-
- control_matches="$(grep -e "Package" "${GIT_TOPDIR}/debian/control" | grep "lib" | grep "$major$" | wc -l)"
- if [ "z$control_matches" = "z0" ]; then
- echo "ERROR: Found no matching Package lib*$major in debian/control for LIBVERSION=$libversion"
- exit 1
- elif [ "z$control_matches" = "z1" ]; then
- echo "OK: Found 'Package: lib*$major' in debian/control for LIBVERSION=$libversion"
- else
- echo "WARN: Found $file_matches files matching 'Package: lib*$major' in debian/control for LIBVERSION=$libversion, manual check required!"
- fi
-
- dhstrip_lib_total="$(grep -e "dh_strip" "${GIT_TOPDIR}/debian/rules" | grep "\-plib" | wc -l)"
- dhstrip_lib_matches="$(grep -e "dh_strip" "${GIT_TOPDIR}/debian/rules" | grep "\-plib" | grep "$major" | wc -l)"
- if [ "z$dhstrip_lib_total" != "z0" ]; then
- if [ "z$dhstrip_lib_matches" = "z0" ] ; then
- echo "ERROR: Found no matching 'dh_strip -plib*$major' line in debian/rules for LIBVERSION=$libversion"
- exit 1
- elif [ "z$dhstrip_lib_total" = "z1" ]; then
- echo "OK: Found 'dh_strip -plib*$major' in debian/rules for LIBVERSION=$libversion"
- else
- echo "WARN: Found $dhstrip_lib_matches/$dhstrip_lib_total dh_strip matches 'dh_strip -plib*$major' in debian/rules for LIBVERSION=$libversion, manual check required!"
- fi
- fi
- done
- # catch and forward exit from pipe subshell "while read":
- if [ $? -ne 0 ]; then
- exit 1
- fi
+ libversion_deb_match
fi
- if [ "z$DRY_RUN" != "z0" ]; then
- exit 0
+ if [ "z$ALLOW_NO_LIBVERSION_RPM_MATCH" = "z0" ]; then
+ libversion_rpmspecin_match
fi
fi
@@ -164,15 +254,32 @@ fi
set -e
if [ -f "TODO-RELEASE" ]; then
- grep '#' TODO-RELEASE > TODO-RELEASE.clean
+ grep '#' TODO-RELEASE > TODO-RELEASE.clean || true
mv TODO-RELEASE.clean TODO-RELEASE
git add TODO-RELEASE
fi
-gbp dch --debian-tag='%(version)s' --auto --meta --git-author --multimaint-merge --ignore-branch --new-version="$NEW_VER"
+# Add missing epoch (OS#5046)
+DEB_VER=$(head -1 debian/changelog | cut -d ' ' -f 2 | sed 's,(,,' | sed 's,),,')
+NEW_VER_WITH_EPOCH="$NEW_VER"
+case "$DEB_VER" in
+*:*)
+ epoch="$(echo "$DEB_VER" | cut -d: -f1)"
+ NEW_VER_WITH_EPOCH="$epoch:$NEW_VER"
+ ;;
+esac
+
+gbp dch \
+ --debian-tag='%(version)s' \
+ --auto \
+ --meta \
+ --git-author \
+ --multimaint-merge \
+ --ignore-branch \
+ --new-version="$NEW_VER_WITH_EPOCH"
dch -r -m --distribution "unstable" ""
git add ${GIT_TOPDIR}/debian/changelog
-bumpversion --current-version $VERSION $REL --tag --commit --tag-name $NEW_VER --allow-dirty
+$BUMPVER --current-version $VERSION $REL --tag --commit --tag-name $NEW_VER --allow-dirty
git commit --amend # let the user add extra information to the release commit.
git tag -s $NEW_VER -f -m "Release v$NEW_VER on $ISODATE."
echo "Release $NEW_VER prepared, tagged and signed."
diff --git a/src/Makefile.am b/src/Makefile.am
index 891b4a6f..86066466 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1,75 +1,13 @@
-# This is _NOT_ the library release version, it's an API version.
-# Please read chapter "Library interface versions" of the libtool documentation
-# before making any modifications: https://www.gnu.org/software/libtool/manual/html_node/Versioning.html
-LIBVERSION=16:0:0
-
-AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_builddir)/include
-AM_CFLAGS = -Wall $(TALLOC_CFLAGS) $(PTHREAD_CFLAGS) $(LIBSCTP_CFLAGS)
-
-if ENABLE_PSEUDOTALLOC
-AM_CPPFLAGS += -I$(top_srcdir)/src/pseudotalloc
-endif
-
-lib_LTLIBRARIES = libosmocore.la
-
-libosmocore_la_LIBADD = $(BACKTRACE_LIB) $(TALLOC_LIBS) $(LIBRARY_RT) $(PTHREAD_LIBS) $(LIBSCTP_LIBS)
-libosmocore_la_SOURCES = context.c timer.c timer_gettimeofday.c timer_clockgettime.c \
- select.c signal.c msgb.c bits.c \
- bitvec.c bitcomp.c counter.c fsm.c \
- write_queue.c utils.c socket.c \
- logging.c logging_syslog.c logging_gsmtap.c rate_ctr.c \
- gsmtap_util.c crc16.c panic.c backtrace.c \
- conv.c application.c rbtree.c strrb.c \
- loggingrb.c crc8gen.c crc16gen.c crc32gen.c crc64gen.c \
- macaddr.c stat_item.c stats.c stats_statsd.c prim.c \
- conv_acc.c conv_acc_generic.c sercomm.c prbs.c \
- isdnhdlc.c \
- tdef.c \
- sockaddr_str.c \
- use_count.c \
- exec.c \
- $(NULL)
-
-if HAVE_SSSE3
-libosmocore_la_SOURCES += conv_acc_sse.c
-if HAVE_SSE4_1
-conv_acc_sse.lo : AM_CFLAGS += -mssse3 -msse4.1
-else
-conv_acc_sse.lo : AM_CFLAGS += -mssse3
-endif
-
-if HAVE_AVX2
-libosmocore_la_SOURCES += conv_acc_sse_avx.c
-if HAVE_SSE4_1
-conv_acc_sse_avx.lo : AM_CFLAGS += -mssse3 -mavx2 -msse4.1
-else
-conv_acc_sse_avx.lo : AM_CFLAGS += -mssse3 -mavx2
-endif
-endif
-endif
-
-if HAVE_NEON
-libosmocore_la_SOURCES += conv_acc_neon.c
-# conv_acc_neon.lo : AM_CFLAGS += -mfpu=neon no, could as well be vfp with neon
-endif
-
-BUILT_SOURCES = crc8gen.c crc16gen.c crc32gen.c crc64gen.c
-EXTRA_DIST = conv_acc_sse_impl.h conv_acc_neon_impl.h crcXXgen.c.tpl
-
-libosmocore_la_LDFLAGS = -version-info $(LIBVERSION) -no-undefined
-
-if ENABLE_PLUGIN
-libosmocore_la_SOURCES += plugin.c
-libosmocore_la_LIBADD += $(LIBRARY_DLOPEN)
-endif
-
-if ENABLE_MSGFILE
-libosmocore_la_SOURCES += msgfile.c
-endif
-
-if ENABLE_SERIAL
-libosmocore_la_SOURCES += serial.c
-endif
-
-crc%gen.c: crcXXgen.c.tpl
- $(AM_V_GEN)sed -e's/XX/$*/g' $< > $@
+SUBDIRS = \
+ core \
+ vty \
+ isdn \
+ codec \
+ gsm \
+ coding \
+ gb \
+ ctrl \
+ pseudotalloc \
+ sim \
+ usb \
+ $(NULL) \ No newline at end of file
diff --git a/src/codec/Makefile.am b/src/codec/Makefile.am
index 778eb2ad..f05ac1f7 100644
--- a/src/codec/Makefile.am
+++ b/src/codec/Makefile.am
@@ -1,10 +1,10 @@
# This is _NOT_ the library release version, it's an API version.
# Please read chapter "Library interface versions" of the libtool documentation
# before making any modifications: https://www.gnu.org/software/libtool/manual/html_node/Versioning.html
-LIBVERSION=2:0:2
+LIBVERSION=3:1:3
-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
@@ -15,4 +15,4 @@ lib_LTLIBRARIES = libosmocodec.la
libosmocodec_la_SOURCES = gsm610.c gsm620.c gsm660.c gsm690.c ecu.c ecu_fr.c
libosmocodec_la_LDFLAGS = -version-info $(LIBVERSION) -no-undefined
-libosmocodec_la_LIBADD = $(top_builddir)/src/libosmocore.la
+libosmocodec_la_LIBADD = $(top_builddir)/src/core/libosmocore.la
diff --git a/src/codec/ecu.c b/src/codec/ecu.c
index db7148ce..def72e6d 100644
--- a/src/codec/ecu.c
+++ b/src/codec/ecu.c
@@ -14,10 +14,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
/* As the developer and copyright holder of the related code, I hereby
@@ -48,7 +44,7 @@ static const struct osmo_ecu_ops *g_ecu_ops[_NUM_OSMO_ECU_CODECS];
/*! initialize an ECU instance for given codec.
* \param[in] ctx talloc context from which to allocate
- * \parma[in] codec codec for which to initialize/create ECU */
+ * \param[in] codec codec for which to initialize/create ECU */
struct osmo_ecu_state *osmo_ecu_init(void *ctx, enum osmo_ecu_codec codec)
{
if (codec >= ARRAY_SIZE(g_ecu_ops))
diff --git a/src/codec/ecu_fr.c b/src/codec/ecu_fr.c
index 4545172a..218d8377 100644
--- a/src/codec/ecu_fr.c
+++ b/src/codec/ecu_fr.c
@@ -14,10 +14,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <stdbool.h>
diff --git a/src/codec/gsm610.c b/src/codec/gsm610.c
index a05eabab..ff9952ad 100644
--- a/src/codec/gsm610.c
+++ b/src/codec/gsm610.c
@@ -17,10 +17,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <stdint.h>
diff --git a/src/codec/gsm620.c b/src/codec/gsm620.c
index 282781fd..4eae514e 100644
--- a/src/codec/gsm620.c
+++ b/src/codec/gsm620.c
@@ -17,10 +17,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <stdint.h>
@@ -268,12 +264,6 @@ const uint16_t gsm620_voiced_bitorder[112] = {
81, /* Code 3:7 */
};
-static inline uint16_t mask(const uint8_t msb)
-{
- const uint16_t m = (uint16_t)1 << (msb - 1);
- return (m - 1) ^ m;
-}
-
/*! Check whether RTP frame contains HR SID code word according to
* TS 101 318 §5.2.2
* \param[in] rtp_payload Buffer with RTP payload
@@ -282,15 +272,15 @@ static inline uint16_t mask(const uint8_t msb)
*/
bool osmo_hr_check_sid(const uint8_t *rtp_payload, size_t payload_len)
{
- uint8_t i, bits[] = { 1, 2, 8, 9, 5, 4, 9, 5, 4, 9, 5, 4, 9, 5 };
- struct bitvec bv;
- bv.data = (uint8_t *) rtp_payload;
- bv.data_len = payload_len;
- bv.cur_bit = 33;
+ struct bitvec bv = {
+ .data = (uint8_t *)rtp_payload,
+ .data_len = payload_len,
+ };
- /* code word is all 1 at given bits, numbered from 1, MODE is always 3 */
- for (i = 0; i < ARRAY_SIZE(bits); i++)
- if (bitvec_get_uint(&bv, bits[i]) != mask(bits[i]))
+ /* A SID frame is identified by a SID codeword consisting of 79 bits which are all 1,
+ * so we basically check if all bits in range r34..r112 (inclusive) are 1. */
+ for (bv.cur_bit = 33; bv.cur_bit < bv.data_len * 8; bv.cur_bit++)
+ if (bitvec_get_bit_pos(&bv, bv.cur_bit) != ONE)
return false;
return true;
diff --git a/src/codec/gsm660.c b/src/codec/gsm660.c
index 4f7bb099..34b10dea 100644
--- a/src/codec/gsm660.c
+++ b/src/codec/gsm660.c
@@ -17,10 +17,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <stdint.h>
diff --git a/src/codec/gsm690.c b/src/codec/gsm690.c
index cc6cdf0c..3b2e6694 100644
--- a/src/codec/gsm690.c
+++ b/src/codec/gsm690.c
@@ -18,10 +18,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <stdint.h>
diff --git a/src/coding/Makefile.am b/src/coding/Makefile.am
index b023668e..905d6840 100644
--- a/src/coding/Makefile.am
+++ b/src/coding/Makefile.am
@@ -1,13 +1,14 @@
# This is _NOT_ the library release version, it's an API version.
# Please read Chapter 6 "Library interface versions" of the libtool
# documentation before making any modification
-LIBVERSION=1:1:1
+LIBVERSION=2:0:2
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
@@ -28,9 +29,12 @@ libosmocoding_la_LDFLAGS = \
$(LIBVERSION) \
-no-undefined \
$(TALLOC_LIBS)
+
libosmocoding_la_LIBADD = \
- ../libosmocore.la \
- ../gsm/libosmogsm.la \
- ../codec/libosmocodec.la
+ $(top_builddir)/src/core/libosmocore.la \
+ $(top_builddir)/src/gsm/libosmogsm.la \
+ $(top_builddir)/src/codec/libosmocodec.la \
+ $(NULL)
EXTRA_DIST = libosmocoding.map
+EXTRA_libosmocoding_la_DEPENDENCIES = libosmocoding.map
diff --git a/src/coding/gsm0503_amr_dtx.c b/src/coding/gsm0503_amr_dtx.c
index 7069b967..7de3b281 100644
--- a/src/coding/gsm0503_amr_dtx.c
+++ b/src/coding/gsm0503_amr_dtx.c
@@ -1,5 +1,5 @@
/*
- * (C) 2020 by sysmocom - s.f.m.c. GmbH, Author: Philipp Maier
+ * (C) 2020-2022 by sysmocom - s.f.m.c. GmbH, Author: Philipp Maier
* All Rights Reserved
*
* SPDX-License-Identifier: GPL-2.0+
@@ -32,6 +32,8 @@
#include <osmocom/coding/gsm0503_parity.h>
#include <osmocom/gsm/gsm0503.h>
+#define S2U(b) ((b) < 0)
+
/* See also: 3GPP TS 05.03, chapter 3.10.1.3, 3.10.5.2 Identification marker */
static const ubit_t id_marker_1[] = { 1, 0, 1, 1, 0, 0, 0, 0, 1 };
@@ -60,7 +62,7 @@ const struct value_string gsm0503_amr_dtx_frame_names[] = {
{ 0, NULL }
};
-static bool detect_afs_id_marker(int *n_errors, int *n_bits_total, const ubit_t * ubits, uint8_t offset, uint8_t count,
+static bool detect_afs_id_marker(int *n_errors, int *n_bits_total, const sbit_t *sbits, uint8_t offset, uint8_t count,
const ubit_t * id_marker, uint8_t id_marker_len)
{
unsigned int i, k;
@@ -69,20 +71,20 @@ static bool detect_afs_id_marker(int *n_errors, int *n_bits_total, const ubit_t
int bits = 0;
/* Override coded in-band data */
- ubits += offset;
+ sbits += offset;
/* Check for identification marker bits */
for (i = 0; i < count; i++) {
for (k = 0; k < 4; k++) {
- if (id_marker[id_bit_nr % id_marker_len] != *ubits)
+ if (*sbits == 0 || id_marker[id_bit_nr % id_marker_len] != S2U(*sbits))
errors++;
id_bit_nr++;
- ubits++;
+ sbits++;
bits++;
}
/* Jump to the next block of 4 bits */
- ubits += 4;
+ sbits += 4;
}
*n_errors = errors;
@@ -92,30 +94,30 @@ static bool detect_afs_id_marker(int *n_errors, int *n_bits_total, const ubit_t
return *n_errors < *n_bits_total / 8;
}
-static bool detect_ahs_id_marker(int *n_errors, int *n_bits_total, const ubit_t * ubits, const ubit_t * id_marker)
+static bool detect_ahs_id_marker(int *n_errors, int *n_bits_total, const sbit_t *sbits, const ubit_t *id_marker)
{
unsigned int i, k;
int errors = 0;
int bits = 0;
/* Override coded in-band data */
- ubits += 16;
+ sbits += 16;
/* Check first identification marker bits (23*9 bits) */
for (i = 0; i < 23; i++) {
for (k = 0; k < 9; k++) {
- if (id_marker[k] != *ubits)
+ if (*sbits == 0 || id_marker[k] != S2U(*sbits))
errors++;
- ubits++;
+ sbits++;
bits++;
}
}
/* Check remaining identification marker bits (5 bits) */
for (k = 0; k < 5; k++) {
- if (id_marker[k] != *ubits)
+ if (*sbits == 0 || id_marker[k] != S2U(*sbits))
errors++;
- ubits++;
+ sbits++;
bits++;
}
@@ -126,7 +128,7 @@ static bool detect_ahs_id_marker(int *n_errors, int *n_bits_total, const ubit_t
return *n_errors < *n_bits_total / 8;
}
-static bool detect_interleaved_ahs_id_marker(int *n_errors, int *n_bits_total, const ubit_t * ubits, uint8_t offset,
+static bool detect_interleaved_ahs_id_marker(int *n_errors, int *n_bits_total, const sbit_t *sbits, uint8_t offset,
uint8_t n_bits, const ubit_t * id_marker, uint8_t id_marker_len)
{
unsigned int i, k;
@@ -136,23 +138,23 @@ static bool detect_interleaved_ahs_id_marker(int *n_errors, int *n_bits_total, c
uint8_t remainder = n_bits % id_marker_len;
/* Override coded in-band data */
- ubits += offset;
+ sbits += offset;
/* Check first identification marker bits (23*9 bits) */
for (i = 0; i < full_rounds; i++) {
for (k = 0; k < id_marker_len; k++) {
- if (id_marker[k] != *ubits)
+ if (*sbits == 0 || id_marker[k] != S2U(*sbits))
errors++;
- ubits += 2;
+ sbits += 2;
bits++;
}
}
/* Check remaining identification marker bits (5 bits) */
for (k = 0; k < remainder; k++) {
- if (id_marker[k] != *ubits)
+ if (*sbits == 0 || id_marker[k] != S2U(*sbits))
errors++;
- ubits += 2;
+ sbits += 2;
bits++;
}
@@ -163,126 +165,128 @@ static bool detect_interleaved_ahs_id_marker(int *n_errors, int *n_bits_total, c
return *n_errors < *n_bits_total / 8;
}
-/* Detect a an FR AMR SID_FIRST frame by its identifcation marker */
-static bool detect_afs_sid_first(int *n_errors, int *n_bits_total, const ubit_t * ubits)
+/* Detect an FR AMR SID_FIRST frame by its identifcation marker */
+static bool detect_afs_sid_first(int *n_errors, int *n_bits_total, const sbit_t *sbits)
{
- return detect_afs_id_marker(n_errors, n_bits_total, ubits, 32, 53, id_marker_0, 9);
+ return detect_afs_id_marker(n_errors, n_bits_total, sbits, 32, 53, id_marker_0, 9);
}
-/* Detect an FR AMR SID_FIRST frame by its identification marker */
-static bool detect_afs_sid_update(int *n_errors, int *n_bits_total, const ubit_t * ubits)
+/* Detect an FR AMR SID_UPDATE frame by its identification marker */
+static bool detect_afs_sid_update(int *n_errors, int *n_bits_total, const sbit_t *sbits)
{
- return detect_afs_id_marker(n_errors, n_bits_total, ubits, 36, 53, id_marker_0, 9);
+ return detect_afs_id_marker(n_errors, n_bits_total, sbits, 36, 53, id_marker_0, 9);
}
-/* Detect an FR AMR SID_FIRST frame by its repeating coded inband data */
-static bool detect_afs_onset(int *n_errors, int *n_bits_total, const ubit_t * ubits)
+/* Detect an FR AMR ONSET frame by its repeating coded inband data */
+static int detect_afs_onset(int *n_errors, int *n_bits_total, const sbit_t *sbits)
{
bool rc;
- rc = detect_afs_id_marker(n_errors, n_bits_total, ubits, 4, 57, codec_mode_1_sid, 16);
+ rc = detect_afs_id_marker(n_errors, n_bits_total, sbits, 4, 57, codec_mode_1_sid, 16);
if (rc)
- return true;
+ return 0;
- rc = detect_afs_id_marker(n_errors, n_bits_total, ubits, 4, 57, codec_mode_2_sid, 16);
+ rc = detect_afs_id_marker(n_errors, n_bits_total, sbits, 4, 57, codec_mode_2_sid, 16);
if (rc)
- return true;
+ return 1;
- rc = detect_afs_id_marker(n_errors, n_bits_total, ubits, 4, 57, codec_mode_3_sid, 16);
+ rc = detect_afs_id_marker(n_errors, n_bits_total, sbits, 4, 57, codec_mode_3_sid, 16);
if (rc)
- return true;
+ return 2;
- rc = detect_afs_id_marker(n_errors, n_bits_total, ubits, 4, 57, codec_mode_4_sid, 16);
+ rc = detect_afs_id_marker(n_errors, n_bits_total, sbits, 4, 57, codec_mode_4_sid, 16);
if (rc)
- return true;
+ return 3;
- return false;
+ return -1;
}
/* Detect an HR AMR SID UPDATE frame by its identification marker */
-static bool detect_ahs_sid_update(int *n_errors, int *n_bits_total, const ubit_t * ubits)
+static bool detect_ahs_sid_update(int *n_errors, int *n_bits_total, const sbit_t *sbits)
{
- return detect_ahs_id_marker(n_errors, n_bits_total, ubits, id_marker_1);
+ return detect_ahs_id_marker(n_errors, n_bits_total, sbits, id_marker_1);
}
/* Detect an HR AMR SID FIRST (part 1) frame by its identification marker */
-static bool detect_ahs_sid_first_p1(int *n_errors, int *n_bits_total, const ubit_t * ubits)
+static bool detect_ahs_sid_first_p1(int *n_errors, int *n_bits_total, const sbit_t *sbits)
{
- return detect_ahs_id_marker(n_errors, n_bits_total, ubits, id_marker_0);
+ return detect_ahs_id_marker(n_errors, n_bits_total, sbits, id_marker_0);
}
/* Detect an HR AMR SID FIRST (part 2) frame by its repeating coded inband data */
-static bool detect_ahs_sid_first_p2(int *n_errors, int *n_bits_total, const ubit_t * ubits)
+static int detect_ahs_sid_first_p2(int *n_errors, int *n_bits_total, const sbit_t *sbits)
{
bool rc;
- rc = detect_interleaved_ahs_id_marker(n_errors, n_bits_total, ubits, 0, 114, codec_mode_1_sid, 16);
+ rc = detect_interleaved_ahs_id_marker(n_errors, n_bits_total, sbits, 0, 114, codec_mode_1_sid, 16);
if (rc)
- return true;
+ return 0;
- rc = detect_interleaved_ahs_id_marker(n_errors, n_bits_total, ubits, 0, 114, codec_mode_2_sid, 16);
+ rc = detect_interleaved_ahs_id_marker(n_errors, n_bits_total, sbits, 0, 114, codec_mode_2_sid, 16);
if (rc)
- return true;
+ return 1;
- rc = detect_interleaved_ahs_id_marker(n_errors, n_bits_total, ubits, 0, 114, codec_mode_3_sid, 16);
+ rc = detect_interleaved_ahs_id_marker(n_errors, n_bits_total, sbits, 0, 114, codec_mode_3_sid, 16);
if (rc)
- return true;
+ return 2;
- rc = detect_interleaved_ahs_id_marker(n_errors, n_bits_total, ubits, 0, 114, codec_mode_4_sid, 16);
+ rc = detect_interleaved_ahs_id_marker(n_errors, n_bits_total, sbits, 0, 114, codec_mode_4_sid, 16);
if (rc)
- return true;
+ return 3;
- return false;
+ return -1;
}
/* Detect an HR AMR ONSET frame by its repeating coded inband data */
-static bool detect_ahs_onset(int *n_errors, int *n_bits_total, const ubit_t * ubits)
+static int detect_ahs_onset(int *n_errors, int *n_bits_total, const sbit_t *sbits)
{
bool rc;
- rc = detect_interleaved_ahs_id_marker(n_errors, n_bits_total, ubits, 1, 114, codec_mode_1_sid, 16);
+ rc = detect_interleaved_ahs_id_marker(n_errors, n_bits_total, sbits, 1, 114, codec_mode_1_sid, 16);
if (rc)
- return true;
+ return 0;
- rc = detect_interleaved_ahs_id_marker(n_errors, n_bits_total, ubits, 1, 114, codec_mode_2_sid, 16);
+ rc = detect_interleaved_ahs_id_marker(n_errors, n_bits_total, sbits, 1, 114, codec_mode_2_sid, 16);
if (rc)
- return true;
+ return 1;
- rc = detect_interleaved_ahs_id_marker(n_errors, n_bits_total, ubits, 1, 114, codec_mode_3_sid, 16);
+ rc = detect_interleaved_ahs_id_marker(n_errors, n_bits_total, sbits, 1, 114, codec_mode_3_sid, 16);
if (rc)
- return true;
+ return 2;
- rc = detect_interleaved_ahs_id_marker(n_errors, n_bits_total, ubits, 1, 114, codec_mode_4_sid, 16);
+ rc = detect_interleaved_ahs_id_marker(n_errors, n_bits_total, sbits, 1, 114, codec_mode_4_sid, 16);
if (rc)
- return true;
+ return 3;
- return false;
+ return -1;
}
/* Detect an HR AMR SID FIRST INHIBIT frame by its identification marker */
-static bool detect_ahs_sid_first_inh(int *n_errors, int *n_bits_total, const ubit_t * ubits)
+static bool detect_ahs_sid_first_inh(int *n_errors, int *n_bits_total, const sbit_t *sbits)
{
- return detect_interleaved_ahs_id_marker(n_errors, n_bits_total, ubits, 33, 212, id_marker_1, 9);
+ return detect_interleaved_ahs_id_marker(n_errors, n_bits_total, sbits, 33, 212, id_marker_1, 9);
}
/* Detect an HR AMR SID UPDATE INHIBIT frame by its identification marker */
-static bool detect_ahs_sid_update_inh(int *n_errors, int *n_bits_total, const ubit_t * ubits)
+static bool detect_ahs_sid_update_inh(int *n_errors, int *n_bits_total, const sbit_t *sbits)
{
- return detect_interleaved_ahs_id_marker(n_errors, n_bits_total, ubits, 33, 212, id_marker_0, 9);
+ return detect_interleaved_ahs_id_marker(n_errors, n_bits_total, sbits, 33, 212, id_marker_0, 9);
}
/*! Detect FR AMR DTX frame in unmapped, deinterleaved frame bits.
- * \param[in] ubits input bits (456 bit).
* \param[out] n_errors number of errornous bits.
* \param[out] n_bits_total number of checked bits.
+ * \param[out] mode_id codec mode ID (0..3 or -1).
+ * \param[in] sbits input soft-bits (456 bit).
* \returns dtx frame type. */
-enum gsm0503_amr_dtx_frames gsm0503_detect_afs_dtx_frame(int *n_errors, int *n_bits_total, const ubit_t * ubits)
+enum gsm0503_amr_dtx_frames gsm0503_detect_afs_dtx_frame2(int *n_errors, int *n_bits_total,
+ int *mode_id, const sbit_t *sbits)
{
- if (detect_afs_sid_first(n_errors, n_bits_total, ubits))
+ if (detect_afs_sid_first(n_errors, n_bits_total, sbits))
return AFS_SID_FIRST;
- if (detect_afs_sid_update(n_errors, n_bits_total, ubits))
+ if (detect_afs_sid_update(n_errors, n_bits_total, sbits))
return AFS_SID_UPDATE;
- if (detect_afs_onset(n_errors, n_bits_total, ubits))
+ if ((*mode_id = detect_afs_onset(n_errors, n_bits_total, sbits)) != -1)
return AFS_ONSET;
*n_errors = 0;
@@ -290,27 +294,62 @@ enum gsm0503_amr_dtx_frames gsm0503_detect_afs_dtx_frame(int *n_errors, int *n_b
return AMR_OTHER;
}
-/*! Detect HR AMR DTX frame in unmapped, deinterleaved frame bits.
+/*! Detect FR AMR DTX frame in unmapped, deinterleaved frame bits.
+ * DEPRECATED: use gsm0503_detect_afs_dtx_frame2() instead.
+ * \param[out] n_errors number of errornous bits.
+ * \param[out] n_bits_total number of checked bits.
* \param[in] ubits input bits (456 bit).
+ * \returns dtx frame type. */
+enum gsm0503_amr_dtx_frames gsm0503_detect_afs_dtx_frame(int *n_errors, int *n_bits_total,
+ const ubit_t *ubits)
+{
+ int mode_id; /* unused */
+ sbit_t sbits[456];
+
+ osmo_ubit2sbit(&sbits[0], &ubits[0], sizeof(sbits));
+ return gsm0503_detect_afs_dtx_frame2(n_errors, n_bits_total, &mode_id, &sbits[0]);
+}
+
+/*! Detect HR AMR DTX frame in unmapped, deinterleaved frame bits.
* \param[out] n_errors number of errornous bits.
* \param[out] n_bits_total number of checked bits.
+ * \param[out] mode_id codec ID (CMI or CMC/CMR).
+ * \param[out] mode_id codec mode ID (0..3 or -1).
+ * \param[in] sbits input soft-bits (456 bit).
* \returns dtx frame type, */
-enum gsm0503_amr_dtx_frames gsm0503_detect_ahs_dtx_frame(int *n_errors, int *n_bits_total, const ubit_t * ubits)
+enum gsm0503_amr_dtx_frames gsm0503_detect_ahs_dtx_frame2(int *n_errors, int *n_bits_total,
+ int *mode_id, const sbit_t *sbits)
{
- if (detect_ahs_sid_update(n_errors, n_bits_total, ubits))
+ if (detect_ahs_sid_update(n_errors, n_bits_total, sbits))
return AHS_SID_UPDATE;
- if (detect_ahs_sid_first_inh(n_errors, n_bits_total, ubits))
+ if (detect_ahs_sid_first_inh(n_errors, n_bits_total, sbits))
return AHS_SID_FIRST_INH;
- if (detect_ahs_sid_update_inh(n_errors, n_bits_total, ubits))
+ if (detect_ahs_sid_update_inh(n_errors, n_bits_total, sbits))
return AHS_SID_UPDATE_INH;
- if (detect_ahs_sid_first_p1(n_errors, n_bits_total, ubits))
+ if (detect_ahs_sid_first_p1(n_errors, n_bits_total, sbits))
return AHS_SID_FIRST_P1;
- if (detect_ahs_sid_first_p2(n_errors, n_bits_total, ubits))
+ if ((*mode_id = detect_ahs_sid_first_p2(n_errors, n_bits_total, sbits)) != -1)
return AHS_SID_FIRST_P2;
- if (detect_ahs_onset(n_errors, n_bits_total, ubits))
+ if ((*mode_id = detect_ahs_onset(n_errors, n_bits_total, sbits)) != -1)
return AHS_ONSET;
*n_errors = 0;
*n_bits_total = 0;
return AMR_OTHER;
}
+
+/*! Detect HR AMR DTX frame in unmapped, deinterleaved frame bits.
+ * DEPRECATED: use gsm0503_detect_ahs_dtx_frame2() instead.
+ * \param[out] n_errors number of errornous bits.
+ * \param[out] n_bits_total number of checked bits.
+ * \param[in] ubits input bits (456 bit).
+ * \returns dtx frame type, */
+enum gsm0503_amr_dtx_frames gsm0503_detect_ahs_dtx_frame(int *n_errors, int *n_bits_total,
+ const ubit_t *ubits)
+{
+ int mode_id; /* unused */
+ sbit_t sbits[456];
+
+ osmo_ubit2sbit(&sbits[0], &ubits[0], sizeof(sbits));
+ return gsm0503_detect_ahs_dtx_frame2(n_errors, n_bits_total, &mode_id, &sbits[0]);
+}
diff --git a/src/coding/gsm0503_coding.c b/src/coding/gsm0503_coding.c
index 1bec56ea..f2b01803 100644
--- a/src/coding/gsm0503_coding.c
+++ b/src/coding/gsm0503_coding.c
@@ -17,10 +17,6 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <stdio.h>
@@ -35,9 +31,7 @@
#include <osmocom/core/crcgen.h>
#include <osmocom/core/endian.h>
-#include <osmocom/gprs/protocol/gsm_04_60.h>
-#include <osmocom/gprs/gprs_rlc.h>
-
+#include <osmocom/gsm/protocol/gsm_44_060.h>
#include <osmocom/gsm/protocol/gsm_04_08.h>
#include <osmocom/gsm/gsm0503.h>
#include <osmocom/codec/codec.h>
@@ -547,10 +541,11 @@ static int osmo_conv_decode_ber_punctured(const struct osmo_conv_code *code,
res = osmo_conv_decode(code, input, output);
- if (n_bits_total || n_errors) {
- coded_len = osmo_conv_encode(code, output, recoded);
- OSMO_ASSERT(sizeof(recoded) / sizeof(recoded[0]) >= coded_len);
- }
+ if (!n_bits_total && !n_errors)
+ return res;
+
+ coded_len = osmo_conv_encode(code, output, recoded);
+ OSMO_ASSERT(ARRAY_SIZE(recoded) >= coded_len);
/* Count bit errors */
if (n_errors) {
@@ -2118,6 +2113,26 @@ int gsm0503_tch_hr_encode(ubit_t *bursts, const uint8_t *tch_data, int len)
return 0;
}
+/* TCH/AFS: parse codec ID (CMI or CMC/CMR) from coded in-band data (16 bit) */
+static uint8_t gsm0503_tch_afs_decode_inband(const sbit_t *cB)
+{
+ unsigned int id = 0, best = 0;
+ unsigned int i, j, k;
+
+ for (i = 0; i < 4; i++) {
+ /* FIXME: why not using remaining (16 - 8) soft-bits here? */
+ for (j = 0, k = 0; j < 8; j++)
+ k += abs(((int)gsm0503_afs_ic_sbit[i][j]) - ((int)cB[j]));
+
+ if (i == 0 || k < best) {
+ best = k;
+ id = i;
+ }
+ }
+
+ return id;
+}
+
/*! Perform channel decoding of a TCH/AFS channel according TS 05.03
* \param[out] tch_data Codec frame in RTP payload format
* \param[in] bursts buffer containing the symbols of 8 bursts
@@ -2160,12 +2175,10 @@ int gsm0503_tch_afs_decode_dtx(uint8_t *tch_data, const sbit_t *bursts,
{
sbit_t iB[912], cB[456], h;
ubit_t d[244], p[6], conv[250];
- int i, j, k, best = 0, rv, len, steal = 0, id = 0;
- ubit_t cBd[456];
+ int i, rv, len, steal = 0, id = -1;
*n_errors = 0; *n_bits_total = 0;
static ubit_t sid_first_dummy[64] = { 0 };
sbit_t sid_update_enc[256];
- uint8_t dtx_prev;
for (i=0; i<8; i++) {
gsm0503_tch_burst_unmap(&iB[i * 114], &bursts[i * 116], &h, i >> 2);
@@ -2175,6 +2188,12 @@ int gsm0503_tch_afs_decode_dtx(uint8_t *tch_data, const sbit_t *bursts,
gsm0503_tch_fr_deinterleave(cB, iB);
if (steal > 0) {
+ /* If not NULL, dtx indicates type of previously decoded TCH/AFS frame.
+ * It's normally updated by gsm0503_detect_afs_dtx_frame2(), which is not
+ * reached in case of FACCH. Reset it here to avoid FACCH/F frames being
+ * misinterpreted as AMR's special DTX frames. */
+ if (dtx != NULL)
+ *dtx = AMR_OTHER;
rv = _xcch_decode_cB(tch_data, cB, n_errors, n_bits_total);
if (rv) {
/* Error decoding FACCH frame */
@@ -2186,16 +2205,20 @@ int gsm0503_tch_afs_decode_dtx(uint8_t *tch_data, const sbit_t *bursts,
/* Determine the DTX frame type (SID_UPDATE, ONSET etc...) */
if (dtx) {
- osmo_sbit2ubit(cBd, cB, 456);
- dtx_prev = *dtx;
- *dtx = gsm0503_detect_afs_dtx_frame(n_errors, n_bits_total, cBd);
+ const enum gsm0503_amr_dtx_frames dtx_prev = *dtx;
- if (dtx_prev == AFS_SID_UPDATE && *dtx == AMR_OTHER) {
+ *dtx = gsm0503_detect_afs_dtx_frame2(n_errors, n_bits_total, &id, cB);
+
+ switch (*dtx) {
+ case AMR_OTHER:
/* NOTE: The AFS_SID_UPDATE frame is splitted into
* two half rate frames. If the id marker frame
* (AFS_SID_UPDATE) is detected the following frame
* contains the actual comfort noised data part of
* (AFS_SID_UPDATE_CN). */
+ if (dtx_prev != AFS_SID_UPDATE)
+ break;
+ /* TODO: parse CMI _and_ CMC/CMR (16 + 16 bit) */
*dtx = AFS_SID_UPDATE_CN;
extract_afs_sid_update(sid_update_enc, cB);
@@ -2211,35 +2234,28 @@ int gsm0503_tch_afs_decode_dtx(uint8_t *tch_data, const sbit_t *bursts,
tch_amr_sid_update_append(conv, 1,
(codec_mode_req) ? codec[*ft]
- : codec[id]);
+ : codec[id > 0 ? id : 0]);
tch_amr_reassemble(tch_data, conv, 39);
len = 5;
goto out;
- } else if (*dtx == AFS_SID_FIRST) {
+ case AFS_SID_FIRST: /* TODO: parse CMI or CMC/CMR (16 bit) */
tch_amr_sid_update_append(sid_first_dummy, 0,
(codec_mode_req) ? codec[*ft]
- : codec[id]);
+ : codec[id > 0 ? id : 0]);
tch_amr_reassemble(tch_data, conv, 39);
len = 5;
goto out;
- } else if (*dtx == AFS_ONSET) {
+ case AFS_SID_UPDATE: /* TODO: parse CMI _and_ CMC/CMR (16 + 16 bit) */
+ case AFS_ONSET:
len = 0;
goto out;
+ default:
+ break;
}
}
- for (i = 0; i < 4; i++) {
- for (j = 0, k = 0; j < 8; j++)
- k += abs(((int)gsm0503_afs_ic_sbit[i][j]) - ((int)cB[j]));
-
- if (i == 0 || k < best) {
- best = k;
- id = i;
- }
- }
-
- /* Check if indicated codec fits into range of codecs */
- if (id >= codecs) {
+ /* Parse codec ID (CMI or CMC/CMR) and check if it fits into range of codecs */
+ if ((id = gsm0503_tch_afs_decode_inband(&cB[0])) >= codecs) {
/* Codec mode out of range, return id */
return id;
}
@@ -2390,10 +2406,12 @@ int gsm0503_tch_afs_decode_dtx(uint8_t *tch_data, const sbit_t *bursts,
out:
/* Change codec request / indication, if frame is valid */
- if (codec_mode_req)
- *cmr = id;
- else
- *ft = id;
+ if (id != -1) {
+ if (codec_mode_req)
+ *cmr = id;
+ else
+ *ft = id;
+ }
return len;
}
@@ -2568,6 +2586,26 @@ invalid_length:
return -1;
}
+/* TCH/AHS: parse codec ID (CMI or CMC/CMR) from coded in-band data (16 bit) */
+static uint8_t gsm0503_tch_ahs_decode_inband(const sbit_t *cB)
+{
+ unsigned int id = 0, best = 0;
+ unsigned int i, j, k;
+
+ for (i = 0, k = 0; i < 4; i++) {
+ /* FIXME: why not using remaining (16 - 4) soft-bits here? */
+ for (j = 0, k = 0; j < 4; j++)
+ k += abs(((int)gsm0503_ahs_ic_sbit[i][j]) - ((int)cB[j]));
+
+ if (i == 0 || k < best) {
+ best = k;
+ id = i;
+ }
+ }
+
+ return id;
+}
+
/*! Perform channel decoding of a TCH/AFS channel according TS 05.03
* \param[out] tch_data Codec frame in RTP payload format
* \param[in] bursts buffer containing the symbols of 8 bursts
@@ -2612,10 +2650,8 @@ int gsm0503_tch_ahs_decode_dtx(uint8_t *tch_data, const sbit_t *bursts, int odd,
{
sbit_t iB[912], cB[456], h;
ubit_t d[244], p[6], conv[135];
- int i, j, k, best = 0, rv, len, steal = 0, id = 0;
- ubit_t cBd[456];
+ int i, rv, len, steal = 0, id = -1;
static ubit_t sid_first_dummy[64] = { 0 };
- uint8_t dtx_prev;
/* only unmap the stealing bits */
if (!odd) {
@@ -2631,6 +2667,13 @@ int gsm0503_tch_ahs_decode_dtx(uint8_t *tch_data, const sbit_t *bursts, int odd,
/* if we found a stole FACCH, but only at correct alignment */
if (steal > 0) {
+ /* If not NULL, dtx indicates type of previously decoded TCH/AHS frame.
+ * It's normally updated by gsm0503_detect_ahs_dtx_frame2(), which is not
+ * reached in case of FACCH. Reset it here to avoid FACCH/H frames being
+ * misinterpreted as AMR's special DTX frames. */
+ if (dtx != NULL)
+ *dtx = AMR_OTHER;
+
for (i = 0; i < 6; i++) {
gsm0503_tch_burst_unmap(&iB[i * 114],
&bursts[i * 116], NULL, i >> 2);
@@ -2661,21 +2704,37 @@ int gsm0503_tch_ahs_decode_dtx(uint8_t *tch_data, const sbit_t *bursts, int odd,
/* Determine the DTX frame type (SID_UPDATE, ONSET etc...) */
if (dtx) {
- osmo_sbit2ubit(cBd, cB, 456);
- dtx_prev = *dtx;
- *dtx = gsm0503_detect_ahs_dtx_frame(n_errors, n_bits_total, cBd);
-
- if (dtx_prev == AHS_SID_UPDATE && *dtx == AMR_OTHER) {
- /* NOTE: The AHS_SID_UPDATE frame is splitted into
- * two half rate frames. If the id marker frame
- * (AHS_SID_UPDATE) is detected the following frame
- * contains the actual comfort noised data part of
- * (AHS_SID_UPDATE_CN). */
+ int n_bits_total_sid;
+ int n_errors_sid;
+
+ *dtx = gsm0503_detect_ahs_dtx_frame2(n_errors, n_bits_total, &id, cB);
+ /* TODO: detect and handle AHS_SID_UPDATE + AHS_SID_UPDATE_INH */
+
+ switch (*dtx) {
+ case AHS_SID_UPDATE: /* TODO: parse CMI _and_ CMC/CMR (16 + 16 bit) */
+ /* cB[] contains 16 bits of coded in-band data and 212 bits containing
+ * the identification marker. We need to unmap/deinterleave 114 odd
+ * bits from the last two blocks, 114 even bits from the first two
+ * blocks and combine them together. */
+ gsm0503_tch_burst_unmap(&iB[0 * 114], &bursts[2 * 116], NULL, 0);
+ gsm0503_tch_burst_unmap(&iB[1 * 114], &bursts[3 * 116], NULL, 0);
+ gsm0503_tch_burst_unmap(&iB[2 * 114], &bursts[0 * 116], NULL, 1);
+ gsm0503_tch_burst_unmap(&iB[3 * 114], &bursts[1 * 116], NULL, 1);
+ gsm0503_tch_hr_deinterleave(cB, iB);
+
+ /* cB[] is expected to contain 16 bits of coded in-band data and
+ * 212 bits containing the coded data (53 bits coded at 1/4 rate). */
*dtx = AHS_SID_UPDATE_CN;
osmo_conv_decode_ber(&gsm0503_tch_axs_sid_update,
- cB + 16, conv, n_errors,
- n_bits_total);
+ cB + 16, conv, &n_errors_sid,
+ &n_bits_total_sid);
+ /* gsm0503_detect_ahs_dtx_frame2() calculates BER for the marker,
+ * osmo_conv_decode_ber() calculates BER for the coded data. */
+ if (n_errors != NULL)
+ *n_errors += n_errors_sid;
+ if (n_bits_total != NULL)
+ *n_bits_total += n_bits_total_sid;
rv = osmo_crc16gen_check_bits(&gsm0503_amr_crc14, conv,
35, conv + 35);
if (rv != 0) {
@@ -2685,38 +2744,30 @@ int gsm0503_tch_ahs_decode_dtx(uint8_t *tch_data, const sbit_t *bursts, int odd,
tch_amr_sid_update_append(conv, 1,
(codec_mode_req) ? codec[*ft]
- : codec[id]);
+ : codec[id > 0 ? id : 0]);
tch_amr_reassemble(tch_data, conv, 39);
len = 5;
goto out;
- } else if (*dtx == AHS_SID_FIRST_P2) {
+ case AHS_SID_FIRST_P2:
tch_amr_sid_update_append(sid_first_dummy, 0,
(codec_mode_req) ? codec[*ft]
- : codec[id]);
+ : codec[id > 0 ? id : 0]);
tch_amr_reassemble(tch_data, sid_first_dummy, 39);
len = 5;
goto out;
- } else if (*dtx == AHS_SID_UPDATE || *dtx == AHS_ONSET
- || *dtx == AHS_SID_FIRST_INH
- || *dtx == AHS_SID_UPDATE_INH
- || *dtx == AHS_SID_FIRST_P1) {
+ case AHS_ONSET:
+ case AHS_SID_FIRST_INH: /* TODO: parse CMI or CMC/CMR (16 bit) */
+ case AHS_SID_UPDATE_INH: /* TODO: parse CMI or CMC/CMR (16 bit) */
+ case AHS_SID_FIRST_P1: /* TODO: parse CMI or CMC/CMR (16 bit) */
len = 0;
goto out;
+ default:
+ break;
}
}
- for (i = 0; i < 4; i++) {
- for (j = 0, k = 0; j < 4; j++)
- k += abs(((int)gsm0503_ahs_ic_sbit[i][j]) - ((int)cB[j]));
-
- if (i == 0 || k < best) {
- best = k;
- id = i;
- }
- }
-
- /* Check if indicated codec fits into range of codecs */
- if (id >= codecs) {
+ /* Parse codec ID (CMI or CMC/CMR) and check if it fits into range of codecs */
+ if ((id = gsm0503_tch_ahs_decode_inband(&cB[0])) >= codecs) {
/* Codec mode out of range, return id */
return id;
}
@@ -2851,10 +2902,12 @@ int gsm0503_tch_ahs_decode_dtx(uint8_t *tch_data, const sbit_t *bursts, int odd,
out:
/* Change codec request / indication, if frame is valid */
- if (codec_mode_req)
- *cmr = id;
- else
- *ft = id;
+ if (id != -1) {
+ if (codec_mode_req)
+ *cmr = id;
+ else
+ *ft = id;
+ }
return len;
}
@@ -3007,7 +3060,7 @@ int gsm0503_tch_ahs_encode(ubit_t *bursts, const uint8_t *tch_data, int len,
return -1;
}
- memcpy(cB, gsm0503_afs_ic_ubit[id], 4);
+ memcpy(cB, gsm0503_ahs_ic_ubit[id], 4);
gsm0503_tch_hr_interleave(cB, iB);
diff --git a/src/coding/gsm0503_interleaving.c b/src/coding/gsm0503_interleaving.c
index d5008d07..cd2f2356 100644
--- a/src/coding/gsm0503_interleaving.c
+++ b/src/coding/gsm0503_interleaving.c
@@ -16,10 +16,6 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <stdint.h>
@@ -682,4 +678,56 @@ void gsm0503_tch_hr_interleave(const ubit_t *cB, ubit_t *iB)
}
}
+/* 3GPP TS 45.003 Section 3.3.4
+ * The coded bits are reordered and interleaved according to the following rule:
+ * i(B,j) = c(n,k) for k = 0,1,...,455
+ * n = 0,1,...,N,N + 1,...
+ * B = B0 +4n + (k mod 19) + (k div 114)
+ * j = (k mod 19) + 19 (k mod 6)
+ *
+ * The result of the interleaving is a distribution of the reordered 114
+ * bit of a given data block, n = N, over 19 blocks, 6 bits equally
+ * distributed in each block, in a diagonal way over consecutive blocks.
+ *
+ * Or in other words the interleaving is a distribution of the encoded,
+ * reordered 456 bits from four given input data blocks, which taken
+ * together give n = N, over 22 bursts, 6 bits equally distributed in
+ * the first and 22 nd bursts, 12 bits distributed in the second and 21
+ * st bursts, 18 bits distributed in the third and 20 th bursts and 24
+ * bits distributed in the other 16 bursts.
+ *
+ * The block of coded data is interleaved "diagonal", where a new block
+ * of coded data starts with every fourth burst and is distributed over
+ * 22 bursts.
+ *
+ * Also used for TCH/F4.8, TCH/H4.8, TCH/F2.4 and TCH/H2.4 and TCH/F14.4 */
+void gsm0503_tch_f96_interleave(const ubit_t *cB, ubit_t *iB)
+{
+ int j, k, B;
+
+ for (k = 0; k < 456; k++) {
+ /* upper bound for B: 4*n + 18 + 4 = 4*n + 22 */
+ B = /* B0 + 4n + */ (k % 19) + (k / 114);
+ /* upper bound for j: 18 + 19*5 = 113 */
+ j = (k % 19) + 19 * (k % 6);
+ /* upper iB index: 4*n+23*114-1 */
+ iB[B * 114 + j] = cB[k];
+ }
+}
+
+void gsm0503_tch_f96_deinterleave(sbit_t *cB, const sbit_t *iB)
+{
+ int j, k, B;
+
+ for (k = 0; k < 456; k++) {
+ /* upper bound for B: 4*n + 18 + 4 = 4*n + 22 */
+ B = /* B0 + 4n + */ (k % 19) + (k / 114);
+ /* upper bound for j: 18 + 19*5 = 113 */
+ j = (k % 19) + 19 * (k % 6);
+ /* upper iB index: 4*n+23*114-1 */
+ cB[k] = iB[B * 114 + j];
+ }
+}
+
+
/*! @} */
diff --git a/src/coding/gsm0503_mapping.c b/src/coding/gsm0503_mapping.c
index f7532eb2..04acfd07 100644
--- a/src/coding/gsm0503_mapping.c
+++ b/src/coding/gsm0503_mapping.c
@@ -15,10 +15,6 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <stdint.h>
diff --git a/src/coding/gsm0503_parity.c b/src/coding/gsm0503_parity.c
index a8daacc7..ef71d351 100644
--- a/src/coding/gsm0503_parity.c
+++ b/src/coding/gsm0503_parity.c
@@ -15,10 +15,6 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <stdint.h>
diff --git a/src/coding/gsm0503_tables.c b/src/coding/gsm0503_tables.c
index df0abeed..25ea2fa3 100644
--- a/src/coding/gsm0503_tables.c
+++ b/src/coding/gsm0503_tables.c
@@ -15,10 +15,6 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <stdint.h>
diff --git a/src/coding/libosmocoding.map b/src/coding/libosmocoding.map
index 325b6d80..ff6ea802 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;
@@ -123,6 +125,8 @@ gsm0503_amr_dtx_frame_names;
gsm0503_amr_dtx_frame_name;
gsm0503_detect_afs_dtx_frame;
gsm0503_detect_ahs_dtx_frame;
+gsm0503_detect_afs_dtx_frame2;
+gsm0503_detect_ahs_dtx_frame2;
local: *;
};
diff --git a/src/core/Makefile.am b/src/core/Makefile.am
new file mode 100644
index 00000000..50c39d1c
--- /dev/null
+++ b/src/core/Makefile.am
@@ -0,0 +1,157 @@
+# 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=20: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)
+
+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) \
+ $(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 \
+ 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 \
+ 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
+
+crc%gen.c: crcXXgen.c.tpl
+ $(AM_V_GEN)sed -e's/XX/$*/g' $< > $@
diff --git a/src/application.c b/src/core/application.c
index 7fd62808..f7e5816f 100644
--- a/src/application.c
+++ b/src/core/application.c
@@ -18,10 +18,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
/*! \mainpage libosmocore Documentation
diff --git a/src/backtrace.c b/src/core/backtrace.c
index a18bde02..60bd2381 100644
--- a/src/backtrace.c
+++ b/src/core/backtrace.c
@@ -18,10 +18,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <stdio.h>
diff --git a/src/core/base64.c b/src/core/base64.c
new file mode 100644
index 00000000..0c161ceb
--- /dev/null
+++ b/src/core/base64.c
@@ -0,0 +1,195 @@
+/*
+ * RFC 1521 base64 encoding/decoding
+ *
+ * Copyright (C) 2006-2015, ARM Limited, All Rights Reserved
+ *
+ * This file is part of mbed TLS (https://tls.mbed.org)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <osmocom/core/base64.h>
+
+#include <stdint.h>
+#include <stdio.h>
+#include <errno.h>
+
+static const unsigned char base64_enc_map[64] = {
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
+ 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
+ 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd',
+ 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
+ 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
+ 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', '+', '/'
+};
+
+static const unsigned char base64_dec_map[128] = {
+ 127, 127, 127, 127, 127, 127, 127, 127, 127, 127,
+ 127, 127, 127, 127, 127, 127, 127, 127, 127, 127,
+ 127, 127, 127, 127, 127, 127, 127, 127, 127, 127,
+ 127, 127, 127, 127, 127, 127, 127, 127, 127, 127,
+ 127, 127, 127, 62, 127, 127, 127, 63, 52, 53,
+ 54, 55, 56, 57, 58, 59, 60, 61, 127, 127,
+ 127, 64, 127, 127, 127, 0, 1, 2, 3, 4,
+ 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
+ 25, 127, 127, 127, 127, 127, 127, 26, 27, 28,
+ 29, 30, 31, 32, 33, 34, 35, 36, 37, 38,
+ 39, 40, 41, 42, 43, 44, 45, 46, 47, 48,
+ 49, 50, 51, 127, 127, 127, 127, 127
+};
+
+/*
+ * Encode a buffer into base64 format
+ */
+int osmo_base64_encode(unsigned char *dst, size_t dlen, size_t *olen,
+ const unsigned char *src, size_t slen)
+{
+ size_t i, n;
+ int C1, C2, C3;
+ unsigned char *p;
+
+ if (slen == 0) {
+ *olen = 0;
+ return 0;
+ }
+
+ n = (slen << 3) / 6;
+
+ switch ((slen << 3) - (n * 6)) {
+ case 2:
+ n += 3;
+ break;
+ case 4:
+ n += 2;
+ break;
+ default:
+ break;
+ }
+
+ if (dlen < n + 1) {
+ *olen = n + 1;
+ return -ENOBUFS;
+ }
+
+ n = (slen / 3) * 3;
+
+ for (i = 0, p = dst; i < n; i += 3) {
+ C1 = *src++;
+ C2 = *src++;
+ C3 = *src++;
+
+ *p++ = base64_enc_map[(C1 >> 2) & 0x3F];
+ *p++ = base64_enc_map[(((C1 & 3) << 4) + (C2 >> 4)) & 0x3F];
+ *p++ = base64_enc_map[(((C2 & 15) << 2) + (C3 >> 6)) & 0x3F];
+ *p++ = base64_enc_map[C3 & 0x3F];
+ }
+
+ if (i < slen) {
+ C1 = *src++;
+ C2 = ((i + 1) < slen) ? *src++ : 0;
+
+ *p++ = base64_enc_map[(C1 >> 2) & 0x3F];
+ *p++ = base64_enc_map[(((C1 & 3) << 4) + (C2 >> 4)) & 0x3F];
+
+ if ((i + 1) < slen)
+ *p++ = base64_enc_map[((C2 & 15) << 2) & 0x3F];
+ else
+ *p++ = '=';
+
+ *p++ = '=';
+ }
+
+ *olen = p - dst;
+ *p = 0;
+
+ return 0;
+}
+
+/*
+ * Decode a base64-formatted buffer
+ */
+int osmo_base64_decode(unsigned char *dst, size_t dlen, size_t *olen,
+ const unsigned char *src, size_t slen)
+{
+ size_t i, n;
+ uint32_t j, x;
+ unsigned char *p;
+
+ /* First pass: check for validity and get output length */
+ for (i = n = j = 0; i < slen; i++) {
+ /* Skip spaces before checking for EOL */
+ x = 0;
+ while (i < slen && src[i] == ' ') {
+ ++i;
+ ++x;
+ }
+
+ /* Spaces at end of buffer are OK */
+ if (i == slen)
+ break;
+
+ if ((slen - i) >= 2 && src[i] == '\r' && src[i + 1] == '\n')
+ continue;
+
+ if (src[i] == '\n')
+ continue;
+
+ /* Space inside a line is an error */
+ if (x != 0)
+ return -EINVAL;
+
+ if (src[i] == '=' && ++j > 2)
+ return -EINVAL;
+
+ if (src[i] > 127 || base64_dec_map[src[i]] == 127)
+ return -EINVAL;
+
+ if (base64_dec_map[src[i]] < 64 && j != 0)
+ return -EINVAL;
+
+ n++;
+ }
+
+ if (n == 0)
+ return 0;
+
+ n = ((n * 6) + 7) >> 3;
+ n -= j;
+
+ if (dst == NULL || dlen < n) {
+ *olen = n;
+ return -ENOBUFS;
+ }
+
+ for (j = 3, n = x = 0, p = dst; i > 0; i--, src++) {
+ if (*src == '\r' || *src == '\n' || *src == ' ')
+ continue;
+
+ j -= (base64_dec_map[*src] == 64);
+ x = (x << 6) | (base64_dec_map[*src] & 0x3F);
+
+ if (++n == 4) {
+ n = 0;
+ if (j > 0)
+ *p++ = (unsigned char)(x >> 16);
+ if (j > 1)
+ *p++ = (unsigned char)(x >> 8);
+ if (j > 2)
+ *p++ = (unsigned char)(x);
+ }
+ }
+
+ *olen = p - dst;
+
+ return 0;
+}
diff --git a/src/bitcomp.c b/src/core/bitcomp.c
index 6f2fb62b..5fb2cba2 100644
--- a/src/bitcomp.c
+++ b/src/core/bitcomp.c
@@ -18,10 +18,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
/*! \defgroup bitcomp Bit compression
diff --git a/src/bits.c b/src/core/bits.c
index aa117531..3da7d9b9 100644
--- a/src/bits.c
+++ b/src/core/bits.c
@@ -16,10 +16,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <stdint.h>
@@ -185,14 +181,14 @@ int osmo_pbit2ubit(ubit_t *out, const pbit_t *in, unsigned int num_bits)
* \param[in] in input buffer of unpacked bits
* \param[in] in_ofs offset into input buffer
* \param[in] num_bits number of bits
- * \param[in] lsb_mode Encode bits in LSB orde instead of MSB
+ * \param[in] lsb_mode Encode bits in LSB order instead of MSB
* \returns length in bytes (max written offset of output buffer + 1)
*/
int osmo_ubit2pbit_ext(pbit_t *out, unsigned int out_ofs,
const ubit_t *in, unsigned int in_ofs,
unsigned int num_bits, int lsb_mode)
{
- int i, op, bn;
+ unsigned int i, op, bn;
for (i=0; i<num_bits; i++) {
op = out_ofs + i;
bn = lsb_mode ? (op&7) : (7-(op&7));
@@ -210,14 +206,14 @@ int osmo_ubit2pbit_ext(pbit_t *out, unsigned int out_ofs,
* \param[in] in input buffer of packed bits
* \param[in] in_ofs offset into input buffer
* \param[in] num_bits number of bits
- * \param[in] lsb_mode Encode bits in LSB orde instead of MSB
+ * \param[in] lsb_mode Encode bits in LSB order instead of MSB
* \returns length in bytes (max written offset of output buffer + 1)
*/
int osmo_pbit2ubit_ext(ubit_t *out, unsigned int out_ofs,
const pbit_t *in, unsigned int in_ofs,
unsigned int num_bits, int lsb_mode)
{
- int i, ip, bn;
+ unsigned int i, ip, bn;
for (i=0; i<num_bits; i++) {
ip = in_ofs + i;
bn = lsb_mode ? (ip&7) : (7-(ip&7));
@@ -308,7 +304,7 @@ uint32_t osmo_revbytebits_8(uint8_t x)
*/
void osmo_revbytebits_buf(uint8_t *buf, int len)
{
- unsigned int i;
+ int i;
for (i = 0; i < len; i++)
buf[i] = flip_table[buf[i]];
diff --git a/src/bitvec.c b/src/core/bitvec.c
index d7f32fbd..350f7383 100644
--- a/src/bitvec.c
+++ b/src/core/bitvec.c
@@ -16,10 +16,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
/*! \addtogroup bitvec
@@ -201,7 +197,8 @@ int bitvec_get_bit_high(struct bitvec *bv)
* \return 0 on success; negative in case of error */
int bitvec_set_bits(struct bitvec *bv, const enum bit_value *bits, unsigned int count)
{
- int i, rc;
+ unsigned int i;
+ int rc;
for (i = 0; i < count; i++) {
rc = bitvec_set_bit(bv, bits[i]);
@@ -264,7 +261,7 @@ int16_t bitvec_get_int16_msb(const struct bitvec *bv, unsigned int num_bits)
* \return integer value retrieved from bit vector */
int bitvec_get_uint(struct bitvec *bv, unsigned int num_bits)
{
- int i;
+ unsigned int i;
unsigned int ui = 0;
for (i = 0; i < num_bits; i++) {
@@ -272,7 +269,7 @@ int bitvec_get_uint(struct bitvec *bv, unsigned int num_bits)
if (bit < 0)
return bit;
if (bit)
- ui |= (1 << (num_bits - i - 1));
+ ui |= ((unsigned)1 << (num_bits - i - 1));
bv->cur_bit++;
}
@@ -472,19 +469,29 @@ int bitvec_unhex(struct bitvec *bv, const char *src)
* \param[in] bv The boolean vector to work on
* \param[in,out] read_index Where reading supposed to start in the vector
* \param[in] len How many bits to read from vector
- * \returns read bits or negative value on error
+ * \returns An integer made up of the bits read.
+ *
+ * In case of an error, errno is set to a non-zero value. Otherwise it holds 0.
*/
uint64_t bitvec_read_field(struct bitvec *bv, unsigned int *read_index, unsigned int len)
{
unsigned int i;
uint64_t ui = 0;
+
+ /* Prevent bitvec overrun due to incorrect index and/or length */
+ if (len && bytenum_from_bitnum(*read_index + len - 1) >= bv->data_len) {
+ errno = EOVERFLOW;
+ return 0;
+ }
+
bv->cur_bit = *read_index;
+ errno = 0;
for (i = 0; i < len; i++) {
- int bit = bitvec_get_bit_pos((const struct bitvec *)bv, bv->cur_bit);
- if (bit < 0)
- return bit;
- if (bit)
+ unsigned int bytenum = bytenum_from_bitnum(bv->cur_bit);
+ unsigned int bitnum = 7 - (bv->cur_bit % 8);
+
+ if (bv->data[bytenum] & (1 << bitnum))
ui |= ((uint64_t)1 << (len - i - 1));
bv->cur_bit++;
}
@@ -597,7 +604,7 @@ unsigned bitvec_rl(const struct bitvec *bv, bool b)
* \returns Number of consecutive bits of \p b in \p bv and cur_bit will
* \go to cur_bit + number of consecutive bit
*/
-unsigned bitvec_rl_curbit(struct bitvec *bv, bool b, int max_bits)
+unsigned bitvec_rl_curbit(struct bitvec *bv, bool b, unsigned int max_bits)
{
unsigned i = 0;
unsigned j = 8;
diff --git a/src/context.c b/src/core/context.c
index bad012bd..6b585659 100644
--- a/src/context.c
+++ b/src/core/context.c
@@ -2,7 +2,7 @@
* talloc context handling.
*
* (C) 2019 by Harald Welte <laforge@gnumonks.org>
- * All Rights Reserverd.
+ * All Rights Reserved.
*
* SPDX-License-Identifier: GPL-2.0+
*
@@ -15,11 +15,6 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
- * MA 02110-1301, USA.
*/
#include <string.h>
#include <errno.h>
diff --git a/src/conv.c b/src/core/conv.c
index 06c4299b..8963018b 100644
--- a/src/conv.c
+++ b/src/core/conv.c
@@ -16,10 +16,6 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
/*! \addtogroup conv
@@ -67,7 +63,7 @@ osmo_conv_get_output_length(const struct osmo_conv_code *code, int len)
/* Count punctured bits */
if (code->puncture) {
- for (pbits=0; code->puncture[pbits] >= 0; pbits++);
+ for (pbits = 0; code->puncture[pbits] >= 0; pbits++) {}
out_len -= pbits;
}
@@ -85,7 +81,7 @@ osmo_conv_get_output_length(const struct osmo_conv_code *code, int len)
*/
void
osmo_conv_encode_init(struct osmo_conv_encoder *encoder,
- const struct osmo_conv_code *code)
+ const struct osmo_conv_code *code)
{
memset(encoder, 0x00, sizeof(struct osmo_conv_encoder));
OSMO_ASSERT(code != NULL);
@@ -94,12 +90,12 @@ osmo_conv_encode_init(struct osmo_conv_encoder *encoder,
void
osmo_conv_encode_load_state(struct osmo_conv_encoder *encoder,
- const ubit_t *input)
+ const ubit_t *input)
{
int i;
uint8_t state = 0;
- for (i=0; i<(encoder->code->K-1); i++)
+ for (i = 0; i < (encoder->code->K - 1); i++)
state = (state << 1) | input[i];
encoder->state = state;
@@ -107,15 +103,14 @@ osmo_conv_encode_load_state(struct osmo_conv_encoder *encoder,
static inline int
_conv_encode_do_output(struct osmo_conv_encoder *encoder,
- uint8_t out, ubit_t *output)
+ uint8_t out, ubit_t *output)
{
const struct osmo_conv_code *code = encoder->code;
int o_idx = 0;
int j;
if (code->puncture) {
- for (j=0; j<code->N; j++)
- {
+ for (j = 0; j < code->N; j++) {
int bit_no = code->N - j - 1;
int r_idx = encoder->i_idx * code->N + j;
@@ -125,8 +120,7 @@ _conv_encode_do_output(struct osmo_conv_encoder *encoder,
output[o_idx++] = (out >> bit_no) & 1;
}
} else {
- for (j=0; j<code->N; j++)
- {
+ for (j = 0; j < code->N; j++) {
int bit_no = code->N - j - 1;
output[o_idx++] = (out >> bit_no) & 1;
}
@@ -137,7 +131,7 @@ _conv_encode_do_output(struct osmo_conv_encoder *encoder,
int
osmo_conv_encode_raw(struct osmo_conv_encoder *encoder,
- const ubit_t *input, ubit_t *output, int n)
+ const ubit_t *input, ubit_t *output, int n)
{
const struct osmo_conv_code *code = encoder->code;
uint8_t state;
@@ -147,11 +141,11 @@ osmo_conv_encode_raw(struct osmo_conv_encoder *encoder,
o_idx = 0;
state = encoder->state;
- for (i=0; i<n; i++) {
+ for (i = 0; i < n; i++) {
int bit = input[i];
uint8_t out;
- out = code->next_output[state][bit];
+ out = code->next_output[state][bit];
state = code->next_state[state][bit];
o_idx += _conv_encode_do_output(encoder, out, &output[o_idx]);
@@ -165,8 +159,7 @@ osmo_conv_encode_raw(struct osmo_conv_encoder *encoder,
}
int
-osmo_conv_encode_flush(struct osmo_conv_encoder *encoder,
- ubit_t *output)
+osmo_conv_encode_flush(struct osmo_conv_encoder *encoder, ubit_t *output)
{
const struct osmo_conv_code *code = encoder->code;
uint8_t state;
@@ -179,14 +172,14 @@ osmo_conv_encode_flush(struct osmo_conv_encoder *encoder,
o_idx = 0;
state = encoder->state;
- for (i=0; i<n; i++) {
+ for (i = 0; i < n; i++) {
uint8_t out;
if (code->next_term_output) {
- out = code->next_term_output[state];
+ out = code->next_term_output[state];
state = code->next_term_state[state];
} else {
- out = code->next_output[state][0];
+ out = code->next_output[state][0];
state = code->next_state[state][0];
}
@@ -212,7 +205,7 @@ osmo_conv_encode_flush(struct osmo_conv_encoder *encoder,
*/
int
osmo_conv_encode(const struct osmo_conv_code *code,
- const ubit_t *input, ubit_t *output)
+ const ubit_t *input, ubit_t *output)
{
struct osmo_conv_encoder encoder;
int l;
@@ -242,17 +235,18 @@ osmo_conv_encode(const struct osmo_conv_code *code,
/* Forward declaration for accerlated decoding with certain codes */
int
osmo_conv_decode_acc(const struct osmo_conv_code *code,
- const sbit_t *input, ubit_t *output);
+ const sbit_t *input, ubit_t *output);
void
osmo_conv_decode_init(struct osmo_conv_decoder *decoder,
- const struct osmo_conv_code *code, int len, int start_state)
+ const struct osmo_conv_code *code, int len,
+ int start_state)
{
int n_states;
/* Init */
if (len <= 0)
- len = code->len;
+ len = code->len;
n_states = 1 << (code->K - 1);
@@ -263,7 +257,7 @@ osmo_conv_decode_init(struct osmo_conv_decoder *decoder,
decoder->len = len;
/* Allocate arrays */
- decoder->ae = malloc(sizeof(unsigned int) * n_states);
+ decoder->ae = malloc(sizeof(unsigned int) * n_states);
decoder->ae_next = malloc(sizeof(unsigned int) * n_states);
decoder->state_history = malloc(sizeof(uint8_t) * n_states * (len + decoder->code->K - 1));
@@ -287,7 +281,7 @@ osmo_conv_decode_reset(struct osmo_conv_decoder *decoder, int start_state)
memset(decoder->ae, 0x00, sizeof(unsigned int) * decoder->n_states);
} else {
/* Fixed start state */
- for (i=0; i<decoder->n_states; i++) {
+ for (i = 0; i < decoder->n_states; i++) {
decoder->ae[i] = (i == start_state) ? 0 : MAX_AE;
}
}
@@ -304,12 +298,12 @@ osmo_conv_decode_rewind(struct osmo_conv_decoder *decoder)
decoder->p_idx = 0;
/* Initial error normalize (remove constant) */
- for (i=0; i<decoder->n_states; i++) {
+ for (i = 0; i < decoder->n_states; i++) {
if (decoder->ae[i] < min_ae)
min_ae = decoder->ae[i];
}
- for (i=0; i<decoder->n_states; i++)
+ for (i = 0; i < decoder->n_states; i++)
decoder->ae[i] -= min_ae;
}
@@ -325,7 +319,7 @@ osmo_conv_decode_deinit(struct osmo_conv_decoder *decoder)
int
osmo_conv_decode_scan(struct osmo_conv_decoder *decoder,
- const sbit_t *input, int n)
+ const sbit_t *input, int n)
{
const struct osmo_conv_code *code = decoder->code;
@@ -342,27 +336,25 @@ osmo_conv_decode_scan(struct osmo_conv_decoder *decoder,
/* Prepare */
n_states = decoder->n_states;
- ae = decoder->ae;
+ ae = decoder->ae;
ae_next = decoder->ae_next;
state_history = &decoder->state_history[n_states * decoder->o_idx];
- in_sym = alloca(sizeof(sbit_t) * code->N);
+ in_sym = alloca(sizeof(sbit_t) * code->N);
i_idx = 0;
p_idx = decoder->p_idx;
/* Scan the treillis */
- for (i=0; i<n; i++)
- {
+ for (i = 0; i < n; i++) {
/* Reset next accumulated error */
- for (s=0; s<n_states; s++) {
+ for (s = 0; s < n_states; s++)
ae_next[s] = MAX_AE;
- }
/* Get input */
if (code->puncture) {
/* Hard way ... */
- for (j=0; j<code->N; j++) {
+ for (j = 0; j < code->N; j++) {
int idx = ((decoder->o_idx + i) * code->N) + j;
if (idx == code->puncture[p_idx]) {
in_sym[j] = 0; /* Undefined */
@@ -379,23 +371,21 @@ osmo_conv_decode_scan(struct osmo_conv_decoder *decoder,
}
/* Scan all state */
- for (s=0; s<n_states; s++)
- {
+ for (s = 0; s < n_states; s++) {
/* Scan possible input bits */
- for (b=0; b<2; b++)
- {
+ for (b = 0; b < 2; b++) {
int nae, ov, e;
uint8_t m;
/* Next output and state */
- uint8_t out = code->next_output[s][b];
+ uint8_t out = code->next_output[s][b];
uint8_t state = code->next_state[s][b];
/* New error for this path */
nae = ae[s]; /* start from last error */
m = 1 << (code->N - 1); /* mask for 'out' bit selection */
- for (j=0; j<code->N; j++) {
+ for (j = 0; j < code->N; j++) {
int is = (int)in_sym[j];
if (is) {
ov = (out & m) ? -127 : 127; /* sbit_t value for it */
@@ -425,8 +415,7 @@ osmo_conv_decode_scan(struct osmo_conv_decoder *decoder,
}
int
-osmo_conv_decode_flush(struct osmo_conv_decoder *decoder,
- const sbit_t *input)
+osmo_conv_decode_flush(struct osmo_conv_decoder *decoder, const sbit_t *input)
{
const struct osmo_conv_code *code = decoder->code;
@@ -443,27 +432,25 @@ osmo_conv_decode_flush(struct osmo_conv_decoder *decoder,
/* Prepare */
n_states = decoder->n_states;
- ae = decoder->ae;
+ ae = decoder->ae;
ae_next = decoder->ae_next;
state_history = &decoder->state_history[n_states * decoder->o_idx];
- in_sym = alloca(sizeof(sbit_t) * code->N);
+ in_sym = alloca(sizeof(sbit_t) * code->N);
i_idx = 0;
p_idx = decoder->p_idx;
/* Scan the treillis */
- for (i=0; i<code->K-1; i++)
- {
+ for (i = 0; i < code->K - 1; i++) {
/* Reset next accumulated error */
- for (s=0; s<n_states; s++) {
+ for (s = 0; s < n_states; s++)
ae_next[s] = MAX_AE;
- }
/* Get input */
if (code->puncture) {
/* Hard way ... */
- for (j=0; j<code->N; j++) {
+ for (j = 0; j < code->N; j++) {
int idx = ((decoder->o_idx + i) * code->N) + j;
if (idx == code->puncture[p_idx]) {
in_sym[j] = 0; /* Undefined */
@@ -480,8 +467,7 @@ osmo_conv_decode_flush(struct osmo_conv_decoder *decoder,
}
/* Scan all state */
- for (s=0; s<n_states; s++)
- {
+ for (s = 0; s < n_states; s++) {
int nae, ov, e;
uint8_t m;
@@ -490,10 +476,10 @@ osmo_conv_decode_flush(struct osmo_conv_decoder *decoder,
uint8_t state;
if (code->next_term_output) {
- out = code->next_term_output[s];
+ out = code->next_term_output[s];
state = code->next_term_state[s];
} else {
- out = code->next_output[s][0];
+ out = code->next_output[s][0];
state = code->next_state[s][0];
}
@@ -501,7 +487,7 @@ osmo_conv_decode_flush(struct osmo_conv_decoder *decoder,
nae = ae[s]; /* start from last error */
m = 1 << (code->N - 1); /* mask for 'out' bit selection */
- for (j=0; j<code->N; j++) {
+ for (j = 0; j < code->N; j++) {
int is = (int)in_sym[j];
if (is) {
ov = (out & m) ? -127 : 127; /* sbit_t value for it */
@@ -530,38 +516,86 @@ osmo_conv_decode_flush(struct osmo_conv_decoder *decoder,
}
int
-osmo_conv_decode_get_output(struct osmo_conv_decoder *decoder,
- ubit_t *output, int has_flush, int end_state)
+osmo_conv_decode_get_best_end_state(struct osmo_conv_decoder *decoder)
{
const struct osmo_conv_code *code = decoder->code;
- int min_ae;
- uint8_t min_state, cur_state;
- int i, s, n;
+ int min_ae, min_state;
+ int s;
- uint8_t *sh_ptr;
+ /* If flushed, we _know_ the end state */
+ if (code->term == CONV_TERM_FLUSH)
+ return 0;
- /* End state ? */
- if (end_state < 0) {
- /* Find state with least error */
- min_ae = MAX_AE;
- min_state = 0xff;
+ /* Search init */
+ min_state = -1;
+ min_ae = MAX_AE;
- for (s=0; s<decoder->n_states; s++)
- {
+ /* If tail biting, we search for the minimum path metric that
+ * gives a circular traceback (i.e. start_state == end_state */
+ if (code->term == CONV_TERM_TAIL_BITING) {
+ int t, n, i;
+ uint8_t *sh_ptr;
+
+ for (s = 0; s < decoder->n_states; s++) {
+ /* Check if that state traces back to itself */
+ n = decoder->o_idx;
+ sh_ptr = &decoder->state_history[decoder->n_states * (n-1)];
+ t = s;
+
+ for (i = n - 1; i >= 0; i--) {
+ t = sh_ptr[t];
+ sh_ptr -= decoder->n_states;
+ }
+
+ if (s != t)
+ continue;
+
+ /* If it does, consider it */
if (decoder->ae[s] < min_ae) {
min_ae = decoder->ae[s];
min_state = s;
}
}
- if (min_state == 0xff)
- return -1;
- } else {
- min_state = (uint8_t) end_state;
- min_ae = decoder->ae[end_state];
+ if (min_ae < MAX_AE)
+ return min_state;
+ }
+
+ /* Finally, just the lowest path metric */
+ for (s = 0; s < decoder->n_states; s++) {
+ /* Is it smaller ? */
+ if (decoder->ae[s] < min_ae) {
+ min_ae = decoder->ae[s];
+ min_state = s;
+ }
}
+ return min_state;
+}
+
+int
+osmo_conv_decode_get_output(struct osmo_conv_decoder *decoder,
+ ubit_t *output, int has_flush, int end_state)
+{
+ const struct osmo_conv_code *code = decoder->code;
+
+ int min_ae;
+ uint8_t min_state, cur_state;
+ int i, n;
+
+ uint8_t *sh_ptr;
+
+ /* End state ? */
+ if (end_state < 0)
+ end_state = osmo_conv_decode_get_best_end_state(decoder);
+
+ if (end_state < 0)
+ return -1;
+
+ min_state = (uint8_t) end_state;
+ min_ae = decoder->ae[end_state];
+
/* Traceback */
cur_state = min_state;
@@ -571,16 +605,15 @@ osmo_conv_decode_get_output(struct osmo_conv_decoder *decoder,
/* No output for the K-1 termination input bits */
if (has_flush) {
- for (i=0; i<code->K-1; i++) {
+ for (i = 0; i < code->K - 1; i++) {
cur_state = sh_ptr[cur_state];
sh_ptr -= decoder->n_states;
}
n -= code->K - 1;
}
- /* Generate output backward */
- for (i=n-1; i>=0; i--)
- {
+ /* Generate output backward */
+ for (i = n - 1; i >= 0; i--) {
min_state = cur_state;
cur_state = sh_ptr[cur_state];
@@ -602,12 +635,12 @@ osmo_conv_decode_get_output(struct osmo_conv_decoder *decoder,
*
* This is an all-in-one function, taking care of
* \ref osmo_conv_decode_init, \ref osmo_conv_decode_scan,
- * \ref osmo_conv_decode_flush, \ref osmo_conv_decode_get_output and
- * \ref osmo_conv_decode_deinit.
+ * \ref osmo_conv_decode_flush, \ref osmo_conv_decode_get_best_end_state,
+ * \ref osmo_conv_decode_get_output and \ref osmo_conv_decode_deinit.
*/
int
osmo_conv_decode(const struct osmo_conv_code *code,
- const sbit_t *input, ubit_t *output)
+ const sbit_t *input, ubit_t *output)
{
struct osmo_conv_decoder decoder;
int rv, l;
@@ -630,7 +663,7 @@ osmo_conv_decode(const struct osmo_conv_code *code,
rv = osmo_conv_decode_get_output(&decoder, output,
code->term == CONV_TERM_FLUSH, /* has_flush */
- code->term == CONV_TERM_FLUSH ? 0 : -1 /* end_state */
+ -1 /* end_state */
);
osmo_conv_decode_deinit(&decoder);
diff --git a/src/conv_acc.c b/src/core/conv_acc.c
index 0f6f7ca2..4bd3b076 100644
--- a/src/conv_acc.c
+++ b/src/core/conv_acc.c
@@ -16,10 +16,6 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <stdlib.h>
@@ -487,10 +483,27 @@ static void _traceback_rec(struct vdecoder *dec,
*/
static int traceback(struct vdecoder *dec, uint8_t *out, int term, int len)
{
- int i, sum, max = -1;
- unsigned path, state = 0;
+ int i, j, sum, max = -1;
+ unsigned path, state = 0, state_scan;
+
+ if (term == CONV_TERM_TAIL_BITING) {
+ for (i = 0; i < dec->trellis.num_states; i++) {
+ state_scan = i;
+ for (j = len - 1; j >= 0; j--) {
+ path = dec->paths[j][state_scan] + 1;
+ state_scan = vstate_lshift(state_scan, dec->k, path);
+ }
+ if (state_scan != i)
+ continue;
+ sum = dec->trellis.sums[i];
+ if (sum > max) {
+ max = sum;
+ state = i;
+ }
+ }
+ }
- if (term != CONV_TERM_FLUSH) {
+ if ((max < 0) && (term != CONV_TERM_FLUSH)) {
for (i = 0; i < dec->trellis.num_states; i++) {
sum = dec->trellis.sums[i];
if (sum > max) {
diff --git a/src/conv_acc_generic.c b/src/core/conv_acc_generic.c
index 28876738..2257e6a9 100644
--- a/src/conv_acc_generic.c
+++ b/src/core/conv_acc_generic.c
@@ -17,10 +17,6 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <stdlib.h>
diff --git a/src/conv_acc_neon.c b/src/core/conv_acc_neon.c
index 72449468..fb180e3d 100644
--- a/src/conv_acc_neon.c
+++ b/src/core/conv_acc_neon.c
@@ -18,10 +18,6 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <stdlib.h>
diff --git a/src/conv_acc_neon_impl.h b/src/core/conv_acc_neon_impl.h
index 4471127e..8a78c75b 100644
--- a/src/conv_acc_neon_impl.h
+++ b/src/core/conv_acc_neon_impl.h
@@ -18,10 +18,6 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
/* Some distributions (notably Alpine Linux) for some strange reason
diff --git a/src/conv_acc_sse.c b/src/core/conv_acc_sse.c
index 63d8722a..513ab052 100644
--- a/src/conv_acc_sse.c
+++ b/src/core/conv_acc_sse.c
@@ -17,10 +17,6 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <stdint.h>
diff --git a/src/conv_acc_sse_avx.c b/src/core/conv_acc_sse_avx.c
index 5ac3c163..82b4fa62 100644
--- a/src/conv_acc_sse_avx.c
+++ b/src/core/conv_acc_sse_avx.c
@@ -17,10 +17,6 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <stdint.h>
diff --git a/src/conv_acc_sse_impl.h b/src/core/conv_acc_sse_impl.h
index 9ebbfe9c..807dbe5e 100644
--- a/src/conv_acc_sse_impl.h
+++ b/src/core/conv_acc_sse_impl.h
@@ -18,10 +18,6 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
/* Some distributions (notably Alpine Linux) for some strange reason
diff --git a/src/counter.c b/src/core/counter.c
index 0fa31661..dace15f3 100644
--- a/src/counter.c
+++ b/src/core/counter.c
@@ -17,10 +17,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <string.h>
@@ -79,7 +75,7 @@ int osmo_counters_for_each(int (*handle_counter)(struct osmo_counter *, void *),
/*! Counts the registered counter
* \returns amount of counters */
-int osmo_counters_count()
+int osmo_counters_count(void)
{
return llist_count(&counters);
}
diff --git a/src/crc16.c b/src/core/crc16.c
index 29dace2e..29dace2e 100644
--- a/src/crc16.c
+++ b/src/core/crc16.c
diff --git a/src/crcXXgen.c.tpl b/src/core/crcXXgen.c.tpl
index 74e6d521..154291cc 100644
--- a/src/crcXXgen.c.tpl
+++ b/src/core/crcXXgen.c.tpl
@@ -16,10 +16,6 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
/*! \addtogroup crc
diff --git a/src/exec.c b/src/core/exec.c
index 2a03ba80..6412270f 100644
--- a/src/exec.c
+++ b/src/core/exec.c
@@ -14,10 +14,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include "config.h"
@@ -78,7 +74,7 @@ static bool str_in_list(const char **list, const char *key)
*
* Constraints: Keys up to a maximum length of 255 characters are supported.
*
- * \oaram[out] out caller-allocated array of pointers for the generated output
+ * \param[out] out caller-allocated array of pointers for the generated output
* \param[in] out_len size of out (number of pointers)
* \param[in] in input environment (NULL-terminated list of pointers like **environ)
* \param[in] whitelist whitelist of permitted keys in environment (like **environ)
@@ -135,7 +131,7 @@ int osmo_environment_filter(char **out, size_t out_len, char **in, const char **
* Constraints: If the same key exists in 'out' and 'in', duplicate keys are
* generated. It is a simple append, without any duplicate checks.
*
- * \oaram[out] out caller-allocated array of pointers for the generated output
+ * \param[out] out caller-allocated array of pointers for the generated output
* \param[in] out_len size of out (number of pointers)
* \param[in] in input environment (NULL-terminated list of pointers like **environ)
* \returns number of entries filled in 'out'; negative on error */
diff --git a/src/fsm.c b/src/core/fsm.c
index 1e8909ec..9333cac5 100644
--- a/src/fsm.c
+++ b/src/core/fsm.c
@@ -14,11 +14,6 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
- * MA 02110-1301, USA.
*/
#include <errno.h>
@@ -450,8 +445,8 @@ struct osmo_fsm_inst *osmo_fsm_inst_alloc(struct osmo_fsm *fsm, void *ctx, void
osmo_timer_setup(&fi->timer, fsm_tmr_cb, fi);
if (osmo_fsm_inst_update_id(fi, id) < 0) {
- fsm_free_or_steal(fi);
- return NULL;
+ fsm_free_or_steal(fi);
+ return NULL;
}
INIT_LLIST_HEAD(&fi->proc.children);
@@ -581,7 +576,7 @@ void osmo_fsm_inst_free(struct osmo_fsm_inst *fi)
* \param[in] event Event integer value
* \returns string rendering of the event
*/
-const char *osmo_fsm_event_name(struct osmo_fsm *fsm, uint32_t event)
+const char *osmo_fsm_event_name(const struct osmo_fsm *fsm, uint32_t event)
{
static __thread char buf[32];
if (!fsm->event_names) {
@@ -595,7 +590,7 @@ const char *osmo_fsm_event_name(struct osmo_fsm *fsm, uint32_t event)
* \param[in] fi FSM instance
* \returns string rendering of the FSM identity
*/
-const char *osmo_fsm_inst_name(struct osmo_fsm_inst *fi)
+const char *osmo_fsm_inst_name(const struct osmo_fsm_inst *fi)
{
if (!fi)
return "NULL";
@@ -611,7 +606,7 @@ const char *osmo_fsm_inst_name(struct osmo_fsm_inst *fi)
* \param[in] state FSM state number
* \returns string rendering of the FSM state
*/
-const char *osmo_fsm_state_name(struct osmo_fsm *fsm, uint32_t state)
+const char *osmo_fsm_state_name(const struct osmo_fsm *fsm, uint32_t state)
{
static __thread char buf[32];
if (state >= fsm->num_states) {
@@ -690,8 +685,11 @@ static int state_chg(struct osmo_fsm_inst *fi, uint32_t new_state,
if (!keep_timer
|| (keep_timer && !osmo_timer_pending(&fi->timer))) {
fi->T = T;
- if (timeout_ms)
- osmo_timer_schedule(&fi->timer, timeout_ms / 1000, timeout_ms % 1000);
+ if (timeout_ms) {
+ osmo_timer_schedule(&fi->timer,
+ /* seconds */ (timeout_ms / 1000),
+ /* microseconds */ (timeout_ms % 1000) * 1000);
+ }
}
/* Call 'onenter' last, user might terminate FSM from there */
@@ -1016,6 +1014,26 @@ void _osmo_fsm_inst_term_children(struct osmo_fsm_inst *fi,
}
}
+/*! Broadcast an event to all the FSMs children.
+ *
+ * Iterate over all children and send them the specified event.
+ *
+ * \param[in] fi FSM instance of the parent
+ * \param[in] event Event to send to children of FSM instance
+ * \param[in] data Data to pass along with the event
+ * \param[in] file Calling source file (from osmo_fsm_inst_dispatch macro)
+ * \param[in] line Calling source line (from osmo_fsm_inst_dispatch macro)
+ */
+void _osmo_fsm_inst_broadcast_children(struct osmo_fsm_inst *fi,
+ uint32_t event, void *data,
+ const char *file, int line)
+{
+ struct osmo_fsm_inst *child, *tmp;
+ llist_for_each_entry_safe(child, tmp, &fi->proc.children, proc.child) {
+ _osmo_fsm_inst_dispatch(child, event, data, file, line);
+ }
+}
+
const struct value_string osmo_fsm_term_cause_names[] = {
OSMO_VALUE_STRING(OSMO_FSM_TERM_PARENT),
OSMO_VALUE_STRING(OSMO_FSM_TERM_REQUEST),
diff --git a/src/gsmtap_util.c b/src/core/gsmtap_util.c
index 9a0ac027..1d5fbab7 100644
--- a/src/gsmtap_util.c
+++ b/src/core/gsmtap_util.c
@@ -17,13 +17,9 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
-#include "../config.h"
+#include "config.h"
#include <osmocom/core/gsmtap_util.h>
#include <osmocom/core/logging.h>
@@ -63,12 +59,14 @@ uint8_t chantype_rsl2gsmtap2(uint8_t rsl_chantype, uint8_t link_id, bool user_pl
switch (rsl_chantype) {
case RSL_CHAN_Bm_ACCHs:
+ case RSL_CHAN_OSMO_VAMOS_Bm_ACCHs:
if (user_plane)
ret = GSMTAP_CHANNEL_VOICE_F;
else
ret = GSMTAP_CHANNEL_FACCH_F;
break;
case RSL_CHAN_Lm_ACCHs:
+ case RSL_CHAN_OSMO_VAMOS_Lm_ACCHs:
if (user_plane)
ret = GSMTAP_CHANNEL_VOICE_H;
else
@@ -176,7 +174,7 @@ void chantype_gsmtap2rsl(uint8_t gsmtap_chantype, uint8_t *rsl_chantype,
*/
struct msgb *gsmtap_makemsg_ex(uint8_t type, uint16_t arfcn, uint8_t ts, uint8_t chan_type,
uint8_t ss, uint32_t fn, int8_t signal_dbm,
- uint8_t snr, const uint8_t *data, unsigned int len)
+ int8_t snr, const uint8_t *data, unsigned int len)
{
struct msgb *msg;
struct gsmtap_hdr *gh;
@@ -223,7 +221,7 @@ struct msgb *gsmtap_makemsg_ex(uint8_t type, uint16_t arfcn, uint8_t ts, uint8_t
*/
struct msgb *gsmtap_makemsg(uint16_t arfcn, uint8_t ts, uint8_t chan_type,
uint8_t ss, uint32_t fn, int8_t signal_dbm,
- uint8_t snr, const uint8_t *data, unsigned int len)
+ int8_t snr, const uint8_t *data, unsigned int len)
{
return gsmtap_makemsg_ex(GSMTAP_TYPE_UM, arfcn, ts, chan_type,
ss, fn, signal_dbm, snr, data, len);
@@ -234,12 +232,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.
@@ -255,6 +253,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
@@ -321,12 +343,26 @@ int gsmtap_sendmsg(struct gsmtap_inst *gti, struct msgb *msg)
}
}
+/*! Send a \ref msgb through a GSMTAP source; free the message even if tx queue full.
+ * \param[in] gti GSMTAP instance
+ * \param[in] msg message buffer; always freed, caller must not reference it later.
+ * \return 0 in case of success; negative in case of error
+ */
+int gsmtap_sendmsg_free(struct gsmtap_inst *gti, struct msgb *msg)
+{
+ int rc;
+ rc = gsmtap_sendmsg(gti, msg);
+ if (rc < 0)
+ msgb_free(msg);
+ return rc;
+}
+
/*! send an arbitrary type through GSMTAP.
* See \ref gsmtap_makemsg_ex for arguments
*/
int gsmtap_send_ex(struct gsmtap_inst *gti, uint8_t type, uint16_t arfcn, uint8_t ts,
uint8_t chan_type, uint8_t ss, uint32_t fn,
- int8_t signal_dbm, uint8_t snr, const uint8_t *data,
+ int8_t signal_dbm, int8_t snr, const uint8_t *data,
unsigned int len)
{
struct msgb *msg;
@@ -351,7 +387,7 @@ int gsmtap_send_ex(struct gsmtap_inst *gti, uint8_t type, uint16_t arfcn, uint8_
*/
int gsmtap_send(struct gsmtap_inst *gti, uint16_t arfcn, uint8_t ts,
uint8_t chan_type, uint8_t ss, uint32_t fn,
- int8_t signal_dbm, uint8_t snr, const uint8_t *data,
+ int8_t signal_dbm, int8_t snr, const uint8_t *data,
unsigned int len)
{
return gsmtap_send_ex(gti, GSMTAP_TYPE_UM, arfcn, ts, chan_type, ss, fn,
@@ -435,22 +471,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;
@@ -474,6 +513,38 @@ 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->ofd_wq_mode) {
+ osmo_fd_unregister(&gti->wq.bfd);
+ osmo_wqueue_clear(&gti->wq);
+
+ if (gti->sink_ofd.fd != -1) {
+ osmo_fd_unregister(&gti->sink_ofd);
+ close(gti->sink_ofd.fd);
+ }
+ }
+
+ close(gti->wq.bfd.fd);
+ talloc_free(gti);
+}
+
#endif /* HAVE_SYS_SOCKET_H */
const struct value_string gsmtap_gsm_channel_names[] = {
diff --git a/src/isdnhdlc.c b/src/core/isdnhdlc.c
index 58b4a661..4ced5afd 100644
--- a/src/isdnhdlc.c
+++ b/src/core/isdnhdlc.c
@@ -18,10 +18,6 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <string.h>
@@ -101,13 +97,12 @@ check_frame(struct osmo_isdnhdlc_vars *hdlc)
When a new flag is found, the complete frame has been received
and the CRC is checked.
If a valid frame is found, the function returns the frame length
- excluding the CRC with the bit HDLC_END_OF_FRAME set.
+ excluding the CRC.
If the beginning of a valid frame is found, the function returns
the length.
If a framing error is found (too many 1s and not a flag) the function
- returns the length with the bit OSMO_HDLC_FRAMING_ERROR set.
- If a CRC error is found the function returns the length with the
- bit OSMO_HDLC_CRC_ERROR set.
+ returns -OSMO_HDLC_FRAMING_ERROR.
+ If a CRC error is found the function returns -OSMO_HDLC_CRC_ERROR.
If the frame length exceeds the destination buffer size, the function
returns the length with the bit OSMO_HDLC_LENGTH_ERROR set.
diff --git a/src/core/it_q.c b/src/core/it_q.c
new file mode 100644
index 00000000..a3ff420c
--- /dev/null
+++ b/src/core/it_q.c
@@ -0,0 +1,272 @@
+/*! \file it_q.c
+ * Osmocom Inter-Thread queue implementation */
+/* (C) 2019 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved.
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+/*! \addtogroup it_q
+ * @{
+ * Inter-Thread Message Queue.
+ *
+ * This implements a general-purpose queue between threads. It uses
+ * user-provided data types (containing a llist_head as initial member)
+ * as elements in the queue and an eventfd-based notification mechanism.
+ * Hence, it can be used for pretty much anything, including but not
+ * limited to msgbs, including msgb-wrapped osmo_prim.
+ *
+ * The idea is that the sending thread simply calls osmo_it_q_enqueue().
+ * The receiving thread is woken up from its osmo_select_main() loop by eventfd,
+ * and a general osmo_fd callback function for the eventfd will dequeue each item
+ * and call a queue-specific callback function.
+ */
+
+#include "config.h"
+
+#ifdef HAVE_SYS_EVENTFD_H
+
+#include <pthread.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/eventfd.h>
+
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/it_q.h>
+
+/* "increment" the eventfd by specified 'inc' */
+static int eventfd_increment(int fd, uint64_t inc)
+{
+ int rc;
+
+ rc = write(fd, &inc, sizeof(inc));
+ if (rc != sizeof(inc))
+ return -1;
+
+ return 0;
+}
+
+/* global (for all threads) list of message queues in a program + associated lock */
+static LLIST_HEAD(it_queues);
+static pthread_rwlock_t it_queues_rwlock = PTHREAD_RWLOCK_INITIALIZER;
+
+/* resolve it-queue by its [globally unique] name; must be called with rwlock held */
+static struct osmo_it_q *_osmo_it_q_by_name(const char *name)
+{
+ struct osmo_it_q *q;
+ llist_for_each_entry(q, &it_queues, entry) {
+ if (!strcmp(q->name, name))
+ return q;
+ }
+ return NULL;
+}
+
+/*! resolve it-queue by its [globally unique] name */
+struct osmo_it_q *osmo_it_q_by_name(const char *name)
+{
+ struct osmo_it_q *q;
+ pthread_rwlock_rdlock(&it_queues_rwlock);
+ q = _osmo_it_q_by_name(name);
+ pthread_rwlock_unlock(&it_queues_rwlock);
+ return q;
+}
+
+/* osmo_fd call-back when eventfd is readable */
+static int osmo_it_q_fd_cb(struct osmo_fd *ofd, unsigned int what)
+{
+ struct osmo_it_q *q = (struct osmo_it_q *) ofd->data;
+ uint64_t val;
+ int i, rc;
+
+ if (!(what & OSMO_FD_READ))
+ return 0;
+
+ rc = read(ofd->fd, &val, sizeof(val));
+ if (rc < sizeof(val))
+ return rc;
+
+ for (i = 0; i < val; i++) {
+ struct llist_head *item = _osmo_it_q_dequeue(q);
+ /* in case the user might have called osmo_it_q_flush() we may
+ * end up in the eventfd-dispatch but without any messages left in the queue,
+ * otherwise I'd have loved to OSMO_ASSERT(msg) here. */
+ if (!item)
+ break;
+ q->read_cb(q, item);
+ }
+
+ return 0;
+}
+
+/*! Allocate a new inter-thread message queue.
+ * \param[in] ctx talloc context from which to allocate the queue
+ * \param[in] name human-readable string name of the queue; function creates a copy.
+ * \param[in] read_cb call-back function to be called for each de-queued message; may be
+ * NULL in case you don't want eventfd/osmo_select integration and
+ * will manually take care of noticing if and when to dequeue.
+ * \returns a newly-allocated inter-thread message queue; NULL in case of error */
+struct osmo_it_q *osmo_it_q_alloc(void *ctx, const char *name, unsigned int max_length,
+ void (*read_cb)(struct osmo_it_q *q, struct llist_head *item),
+ void *data)
+{
+ struct osmo_it_q *q;
+ int fd;
+
+ q = talloc_zero(ctx, struct osmo_it_q);
+ if (!q)
+ return NULL;
+ q->data = data;
+ q->name = talloc_strdup(q, name);
+ q->current_length = 0;
+ q->max_length = max_length;
+ q->read_cb = read_cb;
+ INIT_LLIST_HEAD(&q->list);
+ pthread_mutex_init(&q->mutex, NULL);
+ q->event_ofd.fd = -1;
+
+ if (q->read_cb) {
+ /* create eventfd *if* the user has provided a read_cb function */
+ fd = eventfd(0, 0);
+ if (fd < 0) {
+ talloc_free(q);
+ return NULL;
+ }
+
+ /* initialize BUT NOT REGISTER the osmo_fd. The receiving thread must
+ * take are to select/poll/read/... on it */
+ osmo_fd_setup(&q->event_ofd, fd, OSMO_FD_READ, osmo_it_q_fd_cb, q, 0);
+ }
+
+ /* add to global list of queues, checking for duplicate names */
+ pthread_rwlock_wrlock(&it_queues_rwlock);
+ if (_osmo_it_q_by_name(q->name)) {
+ pthread_rwlock_unlock(&it_queues_rwlock);
+ if (q->event_ofd.fd >= 0)
+ osmo_fd_close(&q->event_ofd);
+ talloc_free(q);
+ return NULL;
+ }
+ llist_add_tail(&q->entry, &it_queues);
+ pthread_rwlock_unlock(&it_queues_rwlock);
+
+ return q;
+}
+
+static void *item_dequeue(struct llist_head *queue)
+{
+ struct llist_head *lh;
+
+ if (llist_empty(queue))
+ return NULL;
+
+ lh = queue->next;
+ if (lh) {
+ llist_del(lh);
+ return lh;
+ } else
+ return NULL;
+}
+
+/*! Flush all messages currently present in queue */
+static void _osmo_it_q_flush(struct osmo_it_q *q)
+{
+ void *item;
+ while ((item = item_dequeue(&q->list))) {
+ talloc_free(item);
+ }
+ q->current_length = 0;
+}
+
+/*! Flush all messages currently present in queue */
+void osmo_it_q_flush(struct osmo_it_q *q)
+{
+ OSMO_ASSERT(q);
+
+ pthread_mutex_lock(&q->mutex);
+ _osmo_it_q_flush(q);
+ pthread_mutex_unlock(&q->mutex);
+}
+
+/*! Destroy a message queue */
+void osmo_it_q_destroy(struct osmo_it_q *q)
+{
+ OSMO_ASSERT(q);
+
+ /* first remove from global list of queues */
+ pthread_rwlock_wrlock(&it_queues_rwlock);
+ llist_del(&q->entry);
+ pthread_rwlock_unlock(&it_queues_rwlock);
+ /* next, close the eventfd */
+ if (q->event_ofd.fd >= 0)
+ osmo_fd_close(&q->event_ofd);
+ /* flush all messages still present */
+ osmo_it_q_flush(q);
+ pthread_mutex_destroy(&q->mutex);
+ /* and finally release memory */
+ talloc_free(q);
+}
+
+/*! Thread-safe en-queue to an inter-thread message queue.
+ * \param[in] queue Inter-thread queue on which to enqueue
+ * \param[in] item Item to enqueue. Must have llist_head as first member!
+ * \returns 0 on success; negative on error */
+int _osmo_it_q_enqueue(struct osmo_it_q *queue, struct llist_head *item)
+{
+ OSMO_ASSERT(queue);
+ OSMO_ASSERT(item);
+
+ pthread_mutex_lock(&queue->mutex);
+ if (queue->current_length+1 > queue->max_length) {
+ pthread_mutex_unlock(&queue->mutex);
+ return -ENOSPC;
+ }
+ llist_add_tail(item, &queue->list);
+ queue->current_length++;
+ pthread_mutex_unlock(&queue->mutex);
+ /* increment eventfd counter by one */
+ if (queue->event_ofd.fd >= 0)
+ eventfd_increment(queue->event_ofd.fd, 1);
+ return 0;
+}
+
+
+/*! Thread-safe de-queue from an inter-thread message queue.
+ * \param[in] queue Inter-thread queue from which to dequeue
+ * \returns dequeued message buffer; NULL if none available
+ */
+struct llist_head *_osmo_it_q_dequeue(struct osmo_it_q *queue)
+{
+ struct llist_head *l;
+ OSMO_ASSERT(queue);
+
+ pthread_mutex_lock(&queue->mutex);
+
+ if (llist_empty(&queue->list))
+ l = NULL;
+ l = queue->list.next;
+ OSMO_ASSERT(l);
+ llist_del(l);
+ queue->current_length--;
+
+ pthread_mutex_unlock(&queue->mutex);
+
+ return l;
+}
+
+
+#endif /* HAVE_SYS_EVENTFD_H */
+
+/*! @} */
diff --git a/src/core/libosmocore.map b/src/core/libosmocore.map
new file mode 100644
index 00000000..bd1b3d92
--- /dev/null
+++ b/src/core/libosmocore.map
@@ -0,0 +1,569 @@
+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_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_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_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_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_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_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 212b0b99..938c65f2 100644
--- a/src/logging.c
+++ b/src/core/logging.c
@@ -17,10 +17,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
/*! \addtogroup logging
@@ -29,12 +25,13 @@
*
* \file logging.c */
-#include "../config.h"
+#include "config.h"
#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
+#include <unistd.h>
#ifdef HAVE_STRINGS_H
#include <strings.h>
@@ -44,8 +41,22 @@
#include <syslog.h>
#endif
+#ifdef HAVE_SYSTEMTAP
+/* include the generated probes header and put markers in code */
+#include "probes.h"
+#define TRACE(probe) probe
+#define TRACE_ENABLED(probe) probe ## _ENABLED()
+#else
+/* Wrap the probe to allow it to be removed when no systemtap available */
+#define TRACE(probe)
+#define TRACE_ENABLED(probe) (0)
+#endif /* HAVE_SYSTEMTAP */
+
#include <time.h>
#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
#include <errno.h>
#include <pthread.h>
@@ -53,9 +64,19 @@
#include <osmocom/core/utils.h>
#include <osmocom/core/logging.h>
#include <osmocom/core/timer.h>
+#include <osmocom/core/thread.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/write_queue.h>
+#include <osmocom/core/gsmtap_util.h>
#include <osmocom/vty/logging.h> /* for LOGGING_STR. */
+/* maximum length of the log string of a single log event (typically line) */
+#define MAX_LOG_SIZE 4096
+
+/* maximum number of log statements we queue in file/stderr target write queue */
+#define LOG_WQUEUE_LEN 156
+
osmo_static_assert(_LOG_CTX_COUNT <= ARRAY_SIZE(((struct log_context*)NULL)->ctx),
enum_logging_ctx_items_fit_in_struct_log_context);
osmo_static_assert(_LOG_FLT_COUNT <= ARRAY_SIZE(((struct log_target*)NULL)->filter_data),
@@ -69,6 +90,8 @@ static struct log_context log_context;
void *tall_log_ctx = NULL;
LLIST_HEAD(osmo_log_target_list);
+static __thread long int logging_tid;
+
#if (!EMBEDDED)
/*! This mutex must be held while using osmo_log_target_list or any of its
log_targets in a multithread program. Prevents race conditions between threads
@@ -128,6 +151,7 @@ const struct value_string loglevel_strs[] = {
{ 0, NULL },
};
+/* 256 color palette see https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit */
#define INT2IDX(x) (-1*(x)-1)
static const struct log_info_cat internal_cat[OSMO_NUM_DLIB] = {
[INT2IDX(DLGLOBAL)] = { /* -1 becomes 0 */
@@ -141,99 +165,153 @@ static const struct log_info_cat internal_cat[OSMO_NUM_DLIB] = {
.description = "LAPD in libosmogsm",
.loglevel = LOGL_NOTICE,
.enabled = 1,
+ .color = "\033[38;5;12m",
},
[INT2IDX(DLINP)] = {
.name = "DLINP",
.description = "A-bis Intput Subsystem",
.loglevel = LOGL_NOTICE,
.enabled = 1,
+ .color = "\033[38;5;23m",
},
[INT2IDX(DLMUX)] = {
.name = "DLMUX",
.description = "A-bis B-Subchannel TRAU Frame Multiplex",
.loglevel = LOGL_NOTICE,
.enabled = 1,
+ .color = "\033[38;5;25m",
},
[INT2IDX(DLMI)] = {
.name = "DLMI",
.description = "A-bis Input Driver for Signalling",
.enabled = 0, .loglevel = LOGL_NOTICE,
+ .color = "\033[38;5;27m",
},
[INT2IDX(DLMIB)] = {
.name = "DLMIB",
.description = "A-bis Input Driver for B-Channels (voice)",
.enabled = 0, .loglevel = LOGL_NOTICE,
+ .color = "\033[38;5;29m",
},
[INT2IDX(DLSMS)] = {
.name = "DLSMS",
.description = "Layer3 Short Message Service (SMS)",
.enabled = 1, .loglevel = LOGL_NOTICE,
- .color = OSMO_LOGCOLOR_BRIGHTWHITE,
+ .color = "\033[38;5;31m",
},
[INT2IDX(DLCTRL)] = {
.name = "DLCTRL",
.description = "Control Interface",
.enabled = 1, .loglevel = LOGL_NOTICE,
+ .color = "\033[38;5;33m",
},
[INT2IDX(DLGTP)] = {
.name = "DLGTP",
.description = "GPRS GTP library",
.enabled = 1, .loglevel = LOGL_NOTICE,
+ .color = "\033[38;5;35m",
},
[INT2IDX(DLSTATS)] = {
.name = "DLSTATS",
.description = "Statistics messages and logging",
.enabled = 1, .loglevel = LOGL_NOTICE,
+ .color = "\033[38;5;37m",
},
[INT2IDX(DLGSUP)] = {
.name = "DLGSUP",
.description = "Generic Subscriber Update Protocol",
.enabled = 1, .loglevel = LOGL_NOTICE,
+ .color = "\033[38;5;39m",
},
[INT2IDX(DLOAP)] = {
.name = "DLOAP",
.description = "Osmocom Authentication Protocol",
.enabled = 1, .loglevel = LOGL_NOTICE,
+ .color = "\033[38;5;41m",
},
[INT2IDX(DLSS7)] = {
.name = "DLSS7",
.description = "libosmo-sigtran Signalling System 7",
.enabled = 1, .loglevel = LOGL_NOTICE,
+ .color = "\033[38;5;43m",
},
[INT2IDX(DLSCCP)] = {
.name = "DLSCCP",
.description = "libosmo-sigtran SCCP Implementation",
.enabled = 1, .loglevel = LOGL_NOTICE,
+ .color = "\033[38;5;45m",
},
[INT2IDX(DLSUA)] = {
.name = "DLSUA",
.description = "libosmo-sigtran SCCP User Adaptation",
.enabled = 1, .loglevel = LOGL_NOTICE,
+ .color = "\033[38;5;47m",
},
[INT2IDX(DLM3UA)] = {
.name = "DLM3UA",
.description = "libosmo-sigtran MTP3 User Adaptation",
.enabled = 1, .loglevel = LOGL_NOTICE,
+ .color = "\033[38;5;49m",
},
[INT2IDX(DLMGCP)] = {
.name = "DLMGCP",
.description = "libosmo-mgcp Media Gateway Control Protocol",
.enabled = 1, .loglevel = LOGL_NOTICE,
+ .color = "\033[38;5;51m",
},
[INT2IDX(DLJIBUF)] = {
.name = "DLJIBUF",
.description = "libosmo-netif Jitter Buffer",
.enabled = 1, .loglevel = LOGL_NOTICE,
+ .color = "\033[38;5;53m",
},
[INT2IDX(DLRSPRO)] = {
.name = "DLRSPRO",
.description = "Remote SIM protocol",
.enabled = 1, .loglevel = LOGL_NOTICE,
+ .color = "\033[38;5;55m",
},
[INT2IDX(DLNS)] = {
.name = "DLNS",
.description = "GPRS NS layer",
.enabled = 1, .loglevel = LOGL_NOTICE,
+ .color = "\033[38;5;57m",
+ },
+ [INT2IDX(DLBSSGP)] = {
+ .name = "DLBSSGP",
+ .description = "GPRS BSSGP layer",
+ .enabled = 1, .loglevel = LOGL_NOTICE,
+ .color = "\033[38;5;59m",
+ },
+ [INT2IDX(DLNSDATA)] = {
+ .name = "DLNSDATA",
+ .description = "GPRS NS layer data PDU",
+ .enabled = 1, .loglevel = LOGL_NOTICE,
+ .color = "\033[38;5;61m",
+ },
+ [INT2IDX(DLNSSIGNAL)] = {
+ .name = "DLNSSIGNAL",
+ .description = "GPRS NS layer signal PDU",
+ .enabled = 1, .loglevel = LOGL_NOTICE,
+ .color = "\033[38;5;63m",
+ },
+ [INT2IDX(DLIUUP)] = {
+ .name = "DLIUUP",
+ .description = "Iu UP layer",
+ .enabled = 1, .loglevel = LOGL_NOTICE,
+ .color = "\033[38;5;65m",
+ },
+ [INT2IDX(DLPFCP)] = {
+ .name = "DLPFCP",
+ .description = "libosmo-pfcp Packet Forwarding Control Protocol",
+ .enabled = 1, .loglevel = LOGL_NOTICE,
+ .color = "\033[38;5;51m",
+ },
+ [INT2IDX(DLCSN1)] = {
+ .name = "DLCSN1",
+ .description = "libosmo-csn1 Concrete Syntax Notation 1 codec",
+ .enabled = 1, .loglevel = LOGL_NOTICE,
+ .color = "\033[38;5;11m",
},
};
@@ -386,12 +464,23 @@ static const char *const_basename(const char *path)
return bn + 1;
}
-static void _output(struct log_target *target, unsigned int subsys,
- unsigned int level, const char *file, int line, int cont,
- const char *format, va_list ap)
+/*! main output formatting function for log lines.
+ * \param[out] buf caller-allocated output buffer for the generated string
+ * \param[in] buf_len number of bytes available in buf
+ * \param[in] target log target for which the string is to be formatted
+ * \param[in] subsys Log sub-system number
+ * \param[in] level Log level
+ * \param[in] file name of source code file generating the log
+ * \param[in] line line source code line number within 'file' generating the log
+ * \param[in] cont is this a continuation (true) or not (false)
+ * \param[in] format format string
+ * \param[in] ap variable argument list for format
+ * \returns number of bytes written to out */
+static int _output_buf(char *buf, int buf_len, struct log_target *target, unsigned int subsys,
+ unsigned int level, const char *file, int line, int cont,
+ const char *format, va_list ap)
{
- char buf[4096];
- int ret, len = 0, offset = 0, rem = sizeof(buf);
+ int ret, len = 0, offset = 0, rem = buf_len;
const char *c_subsys = NULL;
/* are we using color */
@@ -434,6 +523,14 @@ static void _output(struct log_target *target, unsigned int subsys,
buf[offset + ret - 1] = ' ';
OSMO_SNPRINTF_RET(ret, rem, offset, len);
}
+ if (target->print_tid) {
+ if (logging_tid == 0)
+ logging_tid = (long int)osmo_gettid();
+ ret = snprintf(buf + offset, rem, "%ld ", logging_tid);
+ if (ret < 0)
+ goto err;
+ OSMO_SNPRINTF_RET(ret, rem, offset, len);
+ }
if (target->print_category) {
ret = snprintf(buf + offset, rem, "%s%s%s%s ",
target->use_color ? level_color(level) : "",
@@ -489,19 +586,21 @@ static void _output(struct log_target *target, unsigned int subsys,
* message in '\n'. If so, nip the last '\n' away, insert the source file info and re-append an
* '\n'. All this to allow LOGP("start..."); LOGPC("...end\n") constructs. */
if (target->print_filename_pos == LOG_FILENAME_POS_LINE_END
- && offset > 0 && buf[offset-1] == '\n') {
+ && offset > 0 && buf[offset - 1] == '\n') {
switch (target->print_filename2) {
case LOG_FILENAME_NONE:
break;
case LOG_FILENAME_PATH:
- offset --;
+ offset--;
+ len--;
ret = snprintf(buf + offset, rem, " (%s:%d)\n", file, line);
if (ret < 0)
goto err;
OSMO_SNPRINTF_RET(ret, rem, offset, len);
break;
case LOG_FILENAME_BASENAME:
- offset --;
+ offset--;
+ len--;
ret = snprintf(buf + offset, rem, " (%s:%d)\n", const_basename(file), line);
if (ret < 0)
goto err;
@@ -517,8 +616,22 @@ static void _output(struct log_target *target, unsigned int subsys,
OSMO_SNPRINTF_RET(ret, rem, offset, len);
}
err:
- buf[sizeof(buf)-1] = '\0';
- target->output(target, level, buf);
+ len = OSMO_MIN(buf_len - 1, len);
+ buf[len] = '\0';
+ return len;
+}
+
+/* Format the log line for given target; use a stack buffer and call target->output */
+static void _output(struct log_target *target, unsigned int subsys,
+ unsigned int level, const char *file, int line, int cont,
+ const char *format, va_list ap)
+{
+ char buf[MAX_LOG_SIZE];
+ int rc;
+
+ rc = _output_buf(buf, sizeof(buf), target, subsys, level, file, line, cont, format, ap);
+ if (rc > 0)
+ target->output(target, level, buf);
}
/* Catch internal logging category indexes as well as out-of-bounds indexes.
@@ -642,9 +755,23 @@ void logp2(int subsys, unsigned int level, const char *file, int line, int cont,
{
va_list ap;
+ TRACE(LIBOSMOCORE_LOG_START());
va_start(ap, format);
osmo_vlogp(subsys, level, file, line, cont, format, ap);
va_end(ap);
+ TRACE(LIBOSMOCORE_LOG_DONE());
+}
+
+/* This logging function is used as a fallback when the logging framework is
+ * not is not properly initialized. */
+void logp_stub(const char *file, int line, int cont, const char *format, ...)
+{
+ va_list ap;
+ if (!cont)
+ fprintf(stderr, "%s:%d ", file, line);
+ va_start(ap, format);
+ vfprintf(stderr, format, ap);
+ va_end(ap);
}
/*! Register a new log target with the logging core
@@ -737,6 +864,15 @@ void log_set_print_extended_timestamp(struct log_target *target, int print_times
target->print_ext_timestamp = print_timestamp;
}
+/*! Enable or disable printing of timestamps while logging
+ * \param[in] target Log target to be affected
+ * \param[in] print_tid Enable (1) or disable (0) Thread ID logging
+ */
+void log_set_print_tid(struct log_target *target, int print_tid)
+{
+ target->print_tid = print_tid;
+}
+
/*! Use log_set_print_filename2() instead.
* Call log_set_print_filename2() with LOG_FILENAME_PATH or LOG_FILENAME_NONE, *as well as* call
* log_set_print_category_hex() with the argument passed to this function. This is to mirror legacy
@@ -831,12 +967,66 @@ void log_set_category_filter(struct log_target *target, int category,
}
#if (!EMBEDDED)
-static void _file_output(struct log_target *target, unsigned int level,
+/* write-queue tells us we should write another msgb (log line) to the output fd */
+static int _file_wq_write_cb(struct osmo_fd *ofd, struct msgb *msg)
+{
+ int rc;
+
+ rc = write(ofd->fd, msgb_data(msg), msgb_length(msg));
+ if (rc < 0)
+ return rc;
+ if (rc != msgb_length(msg)) {
+ /* pull the number of bytes we have already written */
+ msgb_pull(msg, rc);
+ /* ask write_queue to re-insert the msgb at the head of the queue */
+ return -EAGAIN;
+ }
+ return 0;
+}
+
+/* output via buffered, blocking stdio streams */
+static void _file_output_stream(struct log_target *target, unsigned int level,
const char *log)
{
- fprintf(target->tgt_file.out, "%s", log);
+ OSMO_ASSERT(target->tgt_file.out);
+ fputs(log, target->tgt_file.out);
fflush(target->tgt_file.out);
}
+
+/* output via non-blocking write_queue, doing internal buffering */
+static void _file_raw_output(struct log_target *target, int subsys, unsigned int level, const char *file,
+ int line, int cont, const char *format, va_list ap)
+{
+ struct msgb *msg;
+ int rc;
+
+ OSMO_ASSERT(target->tgt_file.wqueue);
+ msg = msgb_alloc_c(target->tgt_file.wqueue, MAX_LOG_SIZE, "log_file_msg");
+ if (!msg)
+ return;
+
+ /* we simply enqueue the log message to a write queue here, to avoid any blocking
+ * writes on the output file. The write queue will tell us once the file is writable
+ * and call _file_wq_write_cb() */
+ rc = _output_buf((char *)msgb_data(msg), msgb_tailroom(msg), target, subsys, level, file, line, cont, format, ap);
+ msgb_put(msg, rc);
+
+ /* attempt a synchronous, non-blocking write, if the write queue is empty */
+ if (target->tgt_file.wqueue->current_length == 0) {
+ rc = _file_wq_write_cb(&target->tgt_file.wqueue->bfd, msg);
+ if (rc == 0) {
+ /* the write was complete, we can exit early */
+ msgb_free(msg);
+ return;
+ }
+ }
+ /* if we reach here, either we already had elements in the write_queue, or the synchronous write
+ * failed: enqueue the message to the write_queue (backlog) */
+ if (osmo_wqueue_enqueue_quiet(target->tgt_file.wqueue, msg) < 0) {
+ msgb_free(msg);
+ /* TODO: increment some counter so we can see that messages were dropped */
+ }
+}
#endif
/*! Create a new log target skeleton
@@ -876,6 +1066,7 @@ struct log_target *log_target_create(void)
/* global settings */
target->use_color = 1;
target->print_timestamp = 0;
+ target->print_tid = 0;
target->print_filename2 = LOG_FILENAME_PATH;
target->print_category_hex = true;
@@ -898,7 +1089,7 @@ struct log_target *log_target_create_stderr(void)
target->type = LOG_TGT_TYPE_STDERR;
target->tgt_file.out = stderr;
- target->output = _file_output;
+ target->output = _file_output_stream;
return target;
#else
return NULL;
@@ -906,11 +1097,11 @@ struct log_target *log_target_create_stderr(void)
}
#if (!EMBEDDED)
-/*! Create a new file-based log target
+/*! Create a new file-based log target using buffered, blocking stream output
* \param[in] fname File name of the new log file
* \returns Log target in case of success, NULL otherwise
*/
-struct log_target *log_target_create_file(const char *fname)
+struct log_target *log_target_create_file_stream(const char *fname)
{
struct log_target *target;
@@ -924,9 +1115,164 @@ struct log_target *log_target_create_file(const char *fname)
log_target_destroy(target);
return NULL;
}
+ target->output = _file_output_stream;
+ target->tgt_file.fname = talloc_strdup(target, fname);
+
+ return target;
+}
+
+/*! switch from non-blocking/write-queue to blocking + buffered stream output
+ * \param[in] target log target which we should switch
+ * \return 0 on success; 1 if already switched before; negative on error
+ * Must be called with mutex osmo_log_tgt_mutex held, see log_tgt_mutex_lock.
+ */
+int log_target_file_switch_to_stream(struct log_target *target)
+{
+ struct osmo_wqueue *wq;
+
+ if (!target)
+ return -ENODEV;
+
+ if (target->tgt_file.out) {
+ /* target has already been switched over */
+ return 1;
+ }
+
+ wq = target->tgt_file.wqueue;
+ OSMO_ASSERT(wq);
+
+ /* re-open output as stream */
+ if (target->type == LOG_TGT_TYPE_STDERR)
+ target->tgt_file.out = stderr;
+ else
+ target->tgt_file.out = fopen(target->tgt_file.fname, "a");
+ if (!target->tgt_file.out) {
+ return -EIO;
+ }
+
+ /* synchronously write anything left in the queue */
+ while (!llist_empty(&wq->msg_queue)) {
+ struct msgb *msg = msgb_dequeue(&wq->msg_queue);
+ fwrite(msgb_data(msg), msgb_length(msg), 1, target->tgt_file.out);
+ msgb_free(msg);
+ }
+
+ /* now that everything succeeded, we can finally close the old output fd */
+ if (target->type == LOG_TGT_TYPE_FILE) {
+ osmo_fd_unregister(&wq->bfd);
+ close(wq->bfd.fd);
+ }
+
+ /* release the queue itself */
+ talloc_free(wq);
+ target->tgt_file.wqueue = NULL;
+ target->output = _file_output_stream;
+ target->raw_output = NULL;
+
+ return 0;
+}
+
+/*! switch from blocking + buffered file output to non-blocking write-queue based output.
+ * \param[in] target log target which we should switch
+ * \return 0 on success; 1 if already switched before; negative on error
+ * Must be called with mutex osmo_log_tgt_mutex held, see log_tgt_mutex_lock.
+ */
+int log_target_file_switch_to_wqueue(struct log_target *target)
+{
+ struct osmo_wqueue *wq;
+ int rc;
+
+ if (!target)
+ return -ENODEV;
+
+ if (!target->tgt_file.out) {
+ /* target has already been switched over */
+ return 1;
+ }
+
+ /* we create a ~640kB sized talloc pool within the write-queue to ensure individual
+ * log lines (stored as msgbs) will not put result in malloc() calls, and also to
+ * reduce the OOM probability within logging, as the pool is already allocated */
+ wq = talloc_pooled_object(target, struct osmo_wqueue, LOG_WQUEUE_LEN,
+ LOG_WQUEUE_LEN*(sizeof(struct msgb)+MAX_LOG_SIZE));
+ if (!wq)
+ return -ENOMEM;
+ osmo_wqueue_init(wq, LOG_WQUEUE_LEN);
+
+ fflush(target->tgt_file.out);
+ if (target->type == LOG_TGT_TYPE_FILE) {
+ rc = open(target->tgt_file.fname, O_WRONLY|O_APPEND|O_CREAT|O_NONBLOCK, 0660);
+ if (rc < 0) {
+ talloc_free(wq);
+ return -errno;
+ }
+ } else {
+ rc = STDERR_FILENO;
+ }
+ wq->bfd.fd = rc;
+ wq->bfd.when = OSMO_FD_WRITE;
+ wq->write_cb = _file_wq_write_cb;
+
+ rc = osmo_fd_register(&wq->bfd);
+ if (rc < 0) {
+ talloc_free(wq);
+ return -EIO;
+ }
+ target->tgt_file.wqueue = wq;
+ target->raw_output = _file_raw_output;
+ target->output = NULL;
+
+ /* now that everything succeeded, we can finally close the old output stream */
+ if (target->type == LOG_TGT_TYPE_FILE)
+ fclose(target->tgt_file.out);
+ target->tgt_file.out = NULL;
+
+ return 0;
+}
+
+/*! Create a new file-based log target using non-blocking write_queue
+ * \param[in] fname File name of the new log file
+ * \returns Log target in case of success, NULL otherwise
+ */
+struct log_target *log_target_create_file(const char *fname)
+{
+ struct log_target *target;
+ struct osmo_wqueue *wq;
+ int rc;
+
+ target = log_target_create();
+ if (!target)
+ return NULL;
+
+ target->type = LOG_TGT_TYPE_FILE;
+ /* we create a ~640kB sized talloc pool within the write-queue to ensure individual
+ * log lines (stored as msgbs) will not put result in malloc() calls, and also to
+ * reduce the OOM probability within logging, as the pool is already allocated */
+ wq = talloc_pooled_object(target, struct osmo_wqueue, LOG_WQUEUE_LEN,
+ LOG_WQUEUE_LEN*(sizeof(struct msgb)+MAX_LOG_SIZE));
+ if (!wq) {
+ log_target_destroy(target);
+ return NULL;
+ }
+ osmo_wqueue_init(wq, LOG_WQUEUE_LEN);
+ wq->bfd.fd = open(fname, O_WRONLY|O_APPEND|O_CREAT|O_NONBLOCK, 0660);
+ if (wq->bfd.fd < 0) {
+ talloc_free(wq);
+ log_target_destroy(target);
+ return NULL;
+ }
+ wq->bfd.when = OSMO_FD_WRITE;
+ wq->write_cb = _file_wq_write_cb;
- target->output = _file_output;
+ rc = osmo_fd_register(&wq->bfd);
+ if (rc < 0) {
+ talloc_free(wq);
+ log_target_destroy(target);
+ return NULL;
+ }
+ target->tgt_file.wqueue = wq;
+ target->raw_output = _file_raw_output;
target->tgt_file.fname = talloc_strdup(target, fname);
return target;
@@ -939,7 +1285,7 @@ struct log_target *log_target_create_file(const char *fname)
* \returns Log target (if found), NULL otherwise
* Must be called with mutex osmo_log_tgt_mutex held, see log_tgt_mutex_lock.
*/
-struct log_target *log_target_find(int type, const char *fname)
+struct log_target *log_target_find(enum log_target_type type, const char *fname)
{
struct log_target *tgt;
@@ -966,17 +1312,36 @@ struct log_target *log_target_find(int type, const char *fname)
* \param[in] target log target to unregister, close and delete */
void log_target_destroy(struct log_target *target)
{
-
/* just in case, to make sure we don't have any references */
log_del_target(target);
#if (!EMBEDDED)
+ struct osmo_wqueue *wq;
switch (target->type) {
case LOG_TGT_TYPE_FILE:
- if (target->tgt_file.out == NULL)
- break;
- fclose(target->tgt_file.out);
- target->tgt_file.out = NULL;
+ case LOG_TGT_TYPE_STDERR:
+ if (target->tgt_file.out) {
+ if (target->type == LOG_TGT_TYPE_FILE)
+ fclose(target->tgt_file.out);
+ target->tgt_file.out = NULL;
+ }
+ wq = target->tgt_file.wqueue;
+ if (wq) {
+ if (wq->bfd.fd >= 0) {
+ 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;
+ }
+ talloc_free((void *)target->tgt_file.fname);
+ target->tgt_file.fname = NULL;
+ break;
+ case LOG_TGT_TYPE_GSMTAP:
+ gsmtap_source_free(target->tgt_gsmtap.gsmtap_inst);
break;
#ifdef HAVE_SYSLOG_H
case LOG_TGT_TYPE_SYSLOG:
@@ -997,13 +1362,33 @@ void log_target_destroy(struct log_target *target)
* \returns 0 in case of success; negative otherwise */
int log_target_file_reopen(struct log_target *target)
{
- fclose(target->tgt_file.out);
+ struct osmo_wqueue *wq;
+ int rc;
- target->tgt_file.out = fopen(target->tgt_file.fname, "a");
- if (!target->tgt_file.out)
- return -errno;
+ OSMO_ASSERT(target->type == LOG_TGT_TYPE_FILE || target->type == LOG_TGT_TYPE_STDERR);
+ OSMO_ASSERT(target->tgt_file.out || target->tgt_file.wqueue);
- /* we assume target->output already to be set */
+ if (target->tgt_file.out) {
+ fclose(target->tgt_file.out);
+ target->tgt_file.out = fopen(target->tgt_file.fname, "a");
+ if (!target->tgt_file.out)
+ return -errno;
+ } else {
+ wq = target->tgt_file.wqueue;
+ osmo_fd_unregister(&wq->bfd);
+ if (wq->bfd.fd >= 0) {
+ close(wq->bfd.fd);
+ wq->bfd.fd = -1;
+ }
+
+ rc = open(target->tgt_file.fname, O_WRONLY|O_APPEND|O_CREAT|O_NONBLOCK, 0660);
+ if (rc < 0)
+ return -errno;
+ wq->bfd.fd = rc;
+ rc = osmo_fd_register(&wq->bfd);
+ if (rc < 0)
+ return rc;
+ }
return 0;
}
diff --git a/src/logging_gsmtap.c b/src/core/logging_gsmtap.c
index bd642715..dfd059b7 100644
--- a/src/logging_gsmtap.c
+++ b/src/core/logging_gsmtap.c
@@ -21,23 +21,20 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
/*! \addtogroup logging
* @{
* \file logging_gsmtap.c */
-#include "../config.h"
+#include "config.h"
#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
+#include <unistd.h>
#ifdef HAVE_STRINGS_H
#include <strings.h>
@@ -50,9 +47,12 @@
#include <osmocom/core/logging.h>
#include <osmocom/core/timer.h>
#include <osmocom/core/byteswap.h>
+#include <osmocom/core/thread.h>
#define GSMTAP_LOG_MAX_SIZE 4096
+static __thread uint32_t logging_gsmtap_tid;
+
static void _gsmtap_raw_output(struct log_target *target, int subsys,
unsigned int level, const char *file,
int line, int cont, const char *format,
@@ -82,6 +82,9 @@ static void _gsmtap_raw_output(struct log_target *target, int subsys,
/* Logging header */
golh = (struct gsmtap_osmocore_log_hdr *) msgb_put(msg, sizeof(*golh));
OSMO_STRLCPY_ARRAY(golh->proc_name, target->tgt_gsmtap.ident);
+ if (logging_gsmtap_tid == 0)
+ osmo_store32be((uint32_t)osmo_gettid(), &logging_gsmtap_tid);
+ golh->pid = logging_gsmtap_tid;
if (subsys_name)
OSMO_STRLCPY_ARRAY(golh->subsys, subsys_name + 1);
else
diff --git a/src/logging_syslog.c b/src/core/logging_syslog.c
index f980689d..1153bdf4 100644
--- a/src/logging_syslog.c
+++ b/src/core/logging_syslog.c
@@ -16,17 +16,13 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
/*! \addtogroup logging
* @{
* \file logging_syslog.c */
-#include "../config.h"
+#include "config.h"
#ifdef HAVE_SYSLOG_H
diff --git a/src/core/logging_systemd.c b/src/core/logging_systemd.c
new file mode 100644
index 00000000..2e86feb6
--- /dev/null
+++ b/src/core/logging_systemd.c
@@ -0,0 +1,117 @@
+/*
+ * (C) 2020 by Vadim Yanitskiy <axilirator@gmail.com>
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+/*! \addtogroup logging
+ * @{
+ * \file logging_systemd.c */
+
+#include <stdio.h>
+#include <syslog.h>
+
+/* Do not use this file as location in sd_journal_print() */
+#define SD_JOURNAL_SUPPRESS_LOCATION
+
+#include <systemd/sd-journal.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/logging.h>
+
+/* FIXME: copy-pasted from logging_syslog.c */
+static int logp2syslog_level(unsigned int level)
+{
+ if (level >= LOGL_FATAL)
+ return LOG_CRIT;
+ else if (level >= LOGL_ERROR)
+ return LOG_ERR;
+ else if (level >= LOGL_NOTICE)
+ return LOG_NOTICE;
+ else if (level >= LOGL_INFO)
+ return LOG_INFO;
+ else
+ return LOG_DEBUG;
+}
+
+static void _systemd_output(struct log_target *target,
+ unsigned int level, const char *log)
+{
+ /* systemd accepts the same level constants as syslog */
+ sd_journal_print(logp2syslog_level(level), "%s", log);
+}
+
+static void _systemd_raw_output(struct log_target *target, int subsys,
+ unsigned int level, const char *file,
+ int line, int cont, const char *format,
+ va_list ap)
+{
+ char buf[4096];
+ int rc;
+
+ rc = vsnprintf(buf, sizeof(buf), format, ap);
+ if (rc < 0) {
+ sd_journal_print(LOG_ERR, "vsnprintf() failed to render a message "
+ "originated from %s:%d (rc=%d)\n",
+ file, line, rc);
+ return;
+ }
+
+ sd_journal_send("CODE_FILE=%s, CODE_LINE=%d", file, line,
+ "PRIORITY=%d", logp2syslog_level(level),
+ "OSMO_SUBSYS=%s", log_category_name(subsys),
+ "OSMO_SUBSYS_HEX=%4.4x", subsys,
+ "MESSAGE=%s", buf,
+ NULL);
+}
+
+/*! Create a new logging target for systemd journal logging.
+ * \param[in] raw whether to offload rendering of the meta information
+ * (location, category) to systemd-journal.
+ * \returns Log target in case of success, NULL in case of error.
+ */
+struct log_target *log_target_create_systemd(bool raw)
+{
+ struct log_target *target;
+
+ target = log_target_create();
+ if (!target)
+ return NULL;
+
+ target->type = LOG_TGT_TYPE_SYSTEMD;
+ log_target_systemd_set_raw(target, raw);
+
+ return target;
+}
+
+/*! Change meta information handling of an existing logging target.
+ * \param[in] target logging target to be modified.
+ * \param[in] raw whether to offload rendering of the meta information
+ * (location, category) to systemd-journal.
+ */
+void log_target_systemd_set_raw(struct log_target *target, bool raw)
+{
+ target->sd_journal.raw = raw;
+ if (raw) {
+ target->raw_output = _systemd_raw_output;
+ target->output = NULL;
+ } else {
+ target->output = _systemd_output;
+ target->raw_output = NULL;
+ }
+}
+
+/* @} */
diff --git a/src/loggingrb.c b/src/core/loggingrb.c
index 4a80cc8a..2bf7b665 100644
--- a/src/loggingrb.c
+++ b/src/core/loggingrb.c
@@ -16,10 +16,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
/*! \addtogroup loggingrb
diff --git a/src/macaddr.c b/src/core/macaddr.c
index 56fdf86e..3b231fb8 100644
--- a/src/macaddr.c
+++ b/src/core/macaddr.c
@@ -18,10 +18,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
/*! \addtogroup utils
diff --git a/src/core/mnl.c b/src/core/mnl.c
new file mode 100644
index 00000000..d148e1b3
--- /dev/null
+++ b/src/core/mnl.c
@@ -0,0 +1,111 @@
+/*! \file mnl.c
+ *
+ * This code integrates libmnl (minimal netlink library) into the osmocom select
+ * loop abstraction. It allows other osmocom libraries or application code to
+ * create netlink sockets and subscribe to netlink events via libmnl. The completion
+ * handler / callbacks are dispatched via libosmocore select loop handling.
+ */
+
+/*
+ * (C) 2020 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved.
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <osmocom/core/select.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/mnl.h>
+
+#include <libmnl/libmnl.h>
+
+#include <errno.h>
+#include <string.h>
+
+/* osmo_fd call-back for when RTNL socket is readable */
+static int osmo_mnl_fd_cb(struct osmo_fd *ofd, unsigned int what)
+{
+ uint8_t buf[MNL_SOCKET_BUFFER_SIZE];
+ struct osmo_mnl *omnl = ofd->data;
+ int rc;
+
+ if (!(what & OSMO_FD_READ))
+ return 0;
+
+ rc = mnl_socket_recvfrom(omnl->mnls, buf, sizeof(buf));
+ if (rc <= 0) {
+ LOGP(DLGLOBAL, LOGL_ERROR, "Error in mnl_socket_recvfrom(): %s\n",
+ strerror(errno));
+ return -EIO;
+ }
+
+ return mnl_cb_run(buf, rc, 0, 0, omnl->mnl_cb, omnl);
+}
+
+/*! create an osmocom-wrapped limnl netlink socket.
+ * \param[in] ctx talloc context from which to allocate
+ * \param[in] bus netlink socket bus ID (see NETLINK_* constants)
+ * \param[in] groups groups of messages to bind/subscribe to
+ * \param[in] mnl_cb callback function called for each incoming message
+ * \param[in] priv opaque private user data
+ * \returns newly-allocated osmo_mnl or NULL in case of error. */
+struct osmo_mnl *osmo_mnl_init(void *ctx, int bus, unsigned int groups, mnl_cb_t mnl_cb, void *priv)
+{
+ struct osmo_mnl *olm = talloc_zero(ctx, struct osmo_mnl);
+
+ if (!olm)
+ return NULL;
+
+ olm->priv = priv;
+ olm->mnl_cb = mnl_cb;
+ olm->mnls = mnl_socket_open(bus);
+ if (!olm->mnls) {
+ LOGP(DLGLOBAL, LOGL_ERROR, "Error creating netlink socket for bus %d: %s\n",
+ bus, strerror(errno));
+ goto out_free;
+ }
+
+ if (mnl_socket_bind(olm->mnls, groups, MNL_SOCKET_AUTOPID) < 0) {
+ LOGP(DLGLOBAL, LOGL_ERROR, "Error binding netlink socket for bus %d to groups 0x%x: %s\n",
+ bus, groups, strerror(errno));
+ goto out_close;
+ }
+
+ osmo_fd_setup(&olm->ofd, mnl_socket_get_fd(olm->mnls), OSMO_FD_READ, osmo_mnl_fd_cb, olm, 0);
+
+ if (osmo_fd_register(&olm->ofd)) {
+ LOGP(DLGLOBAL, LOGL_ERROR, "Error registering netlinks socket\n");
+ goto out_close;
+ }
+
+ return olm;
+
+out_close:
+ mnl_socket_close(olm->mnls);
+out_free:
+ talloc_free(olm);
+ return NULL;
+}
+
+/*! destroy an existing osmocom-wrapped mnl netlink socket: Unregister + close + free.
+ * \param[in] omnl osmo_mnl socket previously returned by osmo_mnl_init() */
+void osmo_mnl_destroy(struct osmo_mnl *omnl)
+{
+ if (!omnl)
+ return;
+
+ osmo_fd_unregister(&omnl->ofd);
+ mnl_socket_close(omnl->mnls);
+ talloc_free(omnl);
+}
diff --git a/src/msgb.c b/src/core/msgb.c
index 4edbdf34..713510c6 100644
--- a/src/msgb.c
+++ b/src/core/msgb.c
@@ -14,10 +14,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
/*! \addtogroup msgb
@@ -64,7 +60,7 @@
#include <osmocom/core/talloc.h>
#include <osmocom/core/logging.h>
-/*! Allocate a new message buffer from given talloc cotext
+/*! Allocate a new message buffer from given talloc context
* \param[in] ctx talloc context from which to allocate
* \param[in] size Length in octets, including headroom
* \param[in] name Human-readable name to be associated with msgb
@@ -318,24 +314,29 @@ void *msgb_talloc_ctx_init(void *root_ctx, unsigned int pool_size)
return tall_msgb_ctx;
}
-/*! Copy an msgb.
+/*! Copy an msgb with memory reallocation.
*
- * This function allocates a new msgb, copies the data buffer of msg,
- * and adjusts the pointers (incl l1h-l4h) accordingly. The cb part
- * is not copied.
+ * This function allocates a new msgb with new_len size, copies the data buffer of msg,
+ * and adjusts the pointers (incl l1h-l4h) accordingly. The cb part is not copied.
+ * \param[in] ctx talloc context on which allocation happens
* \param[in] msg The old msgb object
- * \param[in] name Human-readable name to be associated with msgb
+ * \param[in] new_len The length of new msgb object
+ * \param[in] name Human-readable name to be associated with new msgb
*/
-struct msgb *msgb_copy_c(const void *ctx, const struct msgb *msg, const char *name)
+struct msgb *msgb_copy_resize_c(const void *ctx, const struct msgb *msg, uint16_t new_len, const char *name)
{
struct msgb *new_msg;
- new_msg = msgb_alloc_c(ctx, msg->data_len, name);
- if (!new_msg)
+ if (new_len < msgb_length(msg)) {
+ LOGP(DLGLOBAL, LOGL_ERROR,
+ "Data from old msgb (%u bytes) won't fit into new msgb (%u bytes) after reallocation\n",
+ msgb_length(msg), new_len);
return NULL;
+ }
- /* copy data */
- memcpy(new_msg->_data, msg->_data, new_msg->data_len);
+ new_msg = msgb_alloc_c(ctx, new_len, name);
+ if (!new_msg)
+ return NULL;
/* copy header */
new_msg->len = msg->len;
@@ -343,6 +344,9 @@ struct msgb *msgb_copy_c(const void *ctx, const struct msgb *msg, const char *na
new_msg->head += msg->head - msg->_data;
new_msg->tail += msg->tail - msg->_data;
+ /* copy data */
+ memcpy(new_msg->data, msg->data, msgb_length(msg));
+
if (msg->l1h)
new_msg->l1h = new_msg->_data + (msg->l1h - msg->_data);
if (msg->l2h)
@@ -355,6 +359,32 @@ struct msgb *msgb_copy_c(const void *ctx, const struct msgb *msg, const char *na
return new_msg;
}
+/*! Copy an msgb with memory reallocation.
+ *
+ * This function allocates a new msgb with new_len size, copies the data buffer of msg,
+ * and adjusts the pointers (incl l1h-l4h) accordingly. The cb part is not copied.
+ * \param[in] msg The old msgb object
+ * \param[in] name Human-readable name to be associated with new msgb
+ */
+struct msgb *msgb_copy_resize(const struct msgb *msg, uint16_t new_len, const char *name)
+{
+ return msgb_copy_resize_c(tall_msgb_ctx, msg, new_len, name);
+}
+
+/*! Copy an msgb.
+ *
+ * This function allocates a new msgb, copies the data buffer of msg,
+ * and adjusts the pointers (incl l1h-l4h) accordingly. The cb part
+ * is not copied.
+ * \param[in] ctx talloc context on which allocation happens
+ * \param[in] msg The old msgb object
+ * \param[in] name Human-readable name to be associated with msgb
+ */
+struct msgb *msgb_copy_c(const void *ctx, const struct msgb *msg, const char *name)
+{
+ return msgb_copy_resize_c(ctx, msg, msg->data_len, name);
+}
+
/*! Copy an msgb.
*
* This function allocates a new msgb, copies the data buffer of msg,
@@ -430,11 +460,11 @@ int msgb_resize_area(struct msgb *msg, uint8_t *area,
*/
char *msgb_hexdump_buf(char *buf, size_t buf_len, const struct msgb *msg)
{
- int buf_offs = 0;
+ unsigned int buf_offs = 0;
int nchars;
const unsigned char *start = msg->data;
const unsigned char *lxhs[4];
- int i;
+ unsigned int i;
lxhs[0] = msg->l1h;
lxhs[1] = msg->l2h;
diff --git a/src/msgfile.c b/src/core/msgfile.c
index 1f11aa60..abb4e7cf 100644
--- a/src/msgfile.c
+++ b/src/core/msgfile.c
@@ -17,10 +17,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#define _WITH_GETLINE
diff --git a/src/core/netdev.c b/src/core/netdev.c
new file mode 100644
index 00000000..e244ed18
--- /dev/null
+++ b/src/core/netdev.c
@@ -0,0 +1,962 @@
+
+/* network device (interface) functions.
+ * (C) 2023 by sysmocom - s.m.f.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..56b0c3ae
--- /dev/null
+++ b/src/core/netns.c
@@ -0,0 +1,208 @@
+
+/* Network namespace convenience functions
+ * (C) 2023 by sysmocom - s.m.f.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/panic.c b/src/core/panic.c
index 072f458b..bbf6d081 100644
--- a/src/panic.c
+++ b/src/core/panic.c
@@ -17,10 +17,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
/*! \addtogroup utils
@@ -31,7 +27,7 @@
#include <osmocom/core/panic.h>
#include <osmocom/core/backtrace.h>
-#include "../config.h"
+#include "config.h"
static osmo_panic_handler_t osmo_panic_handler = (void*)0;
diff --git a/src/plugin.c b/src/core/plugin.c
index 40de4f8c..687ad406 100644
--- a/src/plugin.c
+++ b/src/core/plugin.c
@@ -17,17 +17,13 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
/*! \addtogroup utils
* @{
* \file plugin.c */
-#include "../config.h"
+#include "config.h"
#if HAVE_DLFCN_H
diff --git a/src/prbs.c b/src/core/prbs.c
index 8fa04bb7..8fa04bb7 100644
--- a/src/prbs.c
+++ b/src/core/prbs.c
diff --git a/src/prim.c b/src/core/prim.c
index 3c8a7f12..3c8a7f12 100644
--- a/src/prim.c
+++ b/src/core/prim.c
diff --git a/src/core/probes.d b/src/core/probes.d
new file mode 100644
index 00000000..e4150f0c
--- /dev/null
+++ b/src/core/probes.d
@@ -0,0 +1,6 @@
+provider libosmocore {
+ probe log_start();
+ probe log_done();
+ probe stats_start();
+ probe stats_done();
+};
diff --git a/src/rate_ctr.c b/src/core/rate_ctr.c
index 9043a2c6..44e26585 100644
--- a/src/rate_ctr.c
+++ b/src/core/rate_ctr.c
@@ -14,10 +14,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
/*! \addtogroup rate_ctr
@@ -57,14 +53,17 @@
*
* \file rate_ctr.c */
+#include <errno.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
+#include <unistd.h>
+#include <inttypes.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/linuxlist.h>
#include <osmocom/core/talloc.h>
-#include <osmocom/core/timer.h>
+#include <osmocom/core/select.h>
#include <osmocom/core/rate_ctr.h>
#include <osmocom/core/logging.h>
@@ -263,6 +262,26 @@ void rate_ctr_group_free(struct rate_ctr_group *grp)
talloc_free(grp);
}
+/*! Get rate counter from group, identified by index idx
+ * \param[in] grp Rate counter group
+ * \param[in] idx Index of the counter to retrieve
+ * \returns rate counter requested
+ */
+struct rate_ctr *rate_ctr_group_get_ctr(struct rate_ctr_group *grp, unsigned int idx)
+{
+ return &grp->ctr[idx];
+}
+
+/*! Set a name for the group of counters be used instead of index value
+ at report time.
+ * \param[in] grp Rate counter group
+ * \param[in] name Name identifier to assign to the rate counter group
+ */
+void rate_ctr_group_set_name(struct rate_ctr_group *grp, const char *name)
+{
+ osmo_talloc_replace_string(grp, &grp->name, name);
+}
+
/*! Add a number to the counter */
void rate_ctr_add(struct rate_ctr *ctr, int inc)
{
@@ -287,14 +306,9 @@ static void interval_expired(struct rate_ctr *ctr, enum rate_ctr_intv intv)
ctr->intv[intv].rate = ctr->current - ctr->intv[intv].last;
/* save current counter for next interval */
ctr->intv[intv].last = ctr->current;
-
- /* update the rate of the next bigger interval. This will
- * be overwritten when that next larger interval expires */
- if (intv + 1 < ARRAY_SIZE(ctr->intv))
- ctr->intv[intv+1].rate += ctr->intv[intv].rate;
}
-static struct osmo_timer_list rate_ctr_timer;
+static struct osmo_fd rate_ctr_timer = { .fd = -1 };
static uint64_t timer_ticks;
/* The one-second interval has expired */
@@ -315,18 +329,35 @@ static void rate_ctr_group_intv(struct rate_ctr_group *grp)
}
}
-static void rate_ctr_timer_cb(void *data)
+static int rate_ctr_timer_cb(struct osmo_fd *ofd, unsigned int what)
{
struct rate_ctr_group *ctrg;
+ uint64_t expire_count;
+ int rc;
- /* Increment number of ticks before we calculate intervals,
- * as a counter value of 0 would already wrap all counters */
- timer_ticks++;
+ /* check that the timer has actually expired */
+ if (!(what & OSMO_FD_READ))
+ return 0;
- llist_for_each_entry(ctrg, &rate_ctr_groups, list)
- rate_ctr_group_intv(ctrg);
+ /* read from timerfd: number of expirations of periodic timer */
+ rc = read(ofd->fd, (void *) &expire_count, sizeof(expire_count));
+ if (rc < 0 && errno == EAGAIN)
+ return 0;
- osmo_timer_schedule(&rate_ctr_timer, 1, 0);
+ OSMO_ASSERT(rc == sizeof(expire_count));
+
+ if (expire_count > 1)
+ LOGP(DLGLOBAL, LOGL_NOTICE, "Stats timer expire_count=%" PRIu64 ": We missed %" PRIu64 " timers\n",
+ expire_count, expire_count - 1);
+
+ do { /* Increment number of ticks before we calculate intervals,
+ * as a counter value of 0 would already wrap all counters */
+ timer_ticks++;
+ llist_for_each_entry(ctrg, &rate_ctr_groups, list)
+ rate_ctr_group_intv(ctrg);
+ } while (--expire_count);
+
+ return 0;
}
/*! Initialize the counter module. Call this once from your application.
@@ -334,9 +365,27 @@ static void rate_ctr_timer_cb(void *data)
* \returns 0 on success; negative on error */
int rate_ctr_init(void *tall_ctx)
{
+ struct timespec ts_interval = { .tv_sec = 1, .tv_nsec = 0 };
+ int rc;
+
+ /* ignore repeated initialization */
+ if (osmo_fd_is_registered(&rate_ctr_timer))
+ return 0;
+
tall_rate_ctr_ctx = tall_ctx;
- osmo_timer_setup(&rate_ctr_timer, rate_ctr_timer_cb, NULL);
- osmo_timer_schedule(&rate_ctr_timer, 1, 0);
+
+ rc = osmo_timerfd_setup(&rate_ctr_timer, rate_ctr_timer_cb, NULL);
+ if (rc < 0) {
+ LOGP(DLGLOBAL, LOGL_ERROR, "Failed to setup the timer with error code %d (fd=%d)\n",
+ rc, rate_ctr_timer.fd);
+ return rc;
+ }
+
+ rc = osmo_timerfd_schedule(&rate_ctr_timer, NULL, &ts_interval);
+ if (rc < 0) {
+ LOGP(DLGLOBAL, LOGL_ERROR, "Failed to schedule the timer with error code %d (fd=%d)\n",
+ rc, rate_ctr_timer.fd);
+ }
return 0;
}
diff --git a/src/rbtree.c b/src/core/rbtree.c
index 211978f1..f4dc219b 100644
--- a/src/rbtree.c
+++ b/src/core/rbtree.c
@@ -15,11 +15,6 @@
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
- MA 02110-1301, USA
-
linux/lib/rbtree.c
*/
diff --git a/src/select.c b/src/core/select.c
index 496beea9..72f794f3 100644
--- a/src/select.c
+++ b/src/core/select.c
@@ -4,8 +4,8 @@
* userspace logging daemon for the iptables ULOG target
* of the linux 2.4 netfilter subsystem. */
/*
- * (C) 2000-2009 by Harald Welte <laforge@gnumonks.org>
- * All Rights Reserverd.
+ * (C) 2000-2020 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved.
*
* SPDX-License-Identifier: GPL-2.0+
*
@@ -18,11 +18,6 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
- * MA 02110-1301, USA.
*/
#include <fcntl.h>
@@ -38,11 +33,14 @@
#include <osmocom/core/logging.h>
#include <osmocom/core/talloc.h>
#include <osmocom/core/utils.h>
+#include <osmocom/core/stat_item.h>
+#include <osmocom/core/stats_tcp.h>
-#include "../config.h"
+#include "config.h"
-#ifdef HAVE_SYS_SELECT_H
+#if defined(HAVE_SYS_SELECT_H) && defined(HAVE_POLL_H)
#include <sys/select.h>
+#include <poll.h>
/*! \addtogroup select
* @{
@@ -56,6 +54,23 @@ static __thread int maxfd = 0;
static __thread struct llist_head osmo_fds; /* TLS cannot use LLIST_HEAD() */
static __thread int unregistered_count;
+#ifndef FORCE_IO_SELECT
+struct poll_state {
+ /* array of pollfd */
+ struct pollfd *poll;
+ /* number of entries in pollfd allocated */
+ unsigned int poll_size;
+ /* number of osmo_fd registered */
+ unsigned int num_registered;
+};
+static __thread struct poll_state g_poll;
+#endif /* FORCE_IO_SELECT */
+
+/*! See osmo_select_shutdown_request() */
+static int _osmo_select_shutdown_requested = 0;
+/*! See osmo_select_shutdown_request() */
+static bool _osmo_select_shutdown_done = false;
+
/*! Set up an osmo-fd. Will not register it.
* \param[inout] ofd Osmo FD to be set-up
* \param[in] fd OS-level file descriptor number
@@ -75,6 +90,15 @@ void osmo_fd_setup(struct osmo_fd *ofd, int fd, unsigned int when,
ofd->priv_nr = priv_nr;
}
+/*! Update the 'when' field of osmo_fd. "ofd->when = (ofd->when & when_mask) | when".
+ * Use this function instead of directly modifying ofd->when, as the latter will be
+ * removed soon. */
+void osmo_fd_update_when(struct osmo_fd *ofd, unsigned int when_mask, unsigned int when)
+{
+ ofd->when &= when_mask;
+ ofd->when |= when;
+}
+
/*! Check if a file descriptor is already registered
* \param[in] fd osmocom file descriptor to be checked
* \returns true if registered; otherwise false
@@ -127,6 +151,19 @@ int osmo_fd_register(struct osmo_fd *fd)
return 0;
}
#endif
+#ifndef FORCE_IO_SELECT
+ if (g_poll.num_registered + 1 > g_poll.poll_size) {
+ struct pollfd *p;
+ unsigned int new_size = g_poll.poll_size ? g_poll.poll_size * 2 : 1024;
+ p = talloc_realloc(OTC_GLOBAL, g_poll.poll, struct pollfd, new_size);
+ if (!p)
+ return -ENOMEM;
+ memset(p + g_poll.poll_size, 0, new_size - g_poll.poll_size);
+ g_poll.poll = p;
+ g_poll.poll_size = new_size;
+ }
+ g_poll.num_registered++;
+#endif /* FORCE_IO_SELECT */
llist_add_tail(&fd->list, &osmo_fds);
@@ -143,6 +180,12 @@ void osmo_fd_unregister(struct osmo_fd *fd)
* osmo_fd_is_registered() */
unregistered_count++;
llist_del(&fd->list);
+#ifndef FORCE_IO_SELECT
+ g_poll.num_registered--;
+#endif /* FORCE_IO_SELECT */
+
+ /* If existent, free any statistical data */
+ osmo_stats_tcp_osmo_fd_unregister(fd);
}
/*! Close a file descriptor, mark it as closed + unregister from select loop abstraction
@@ -237,6 +280,126 @@ restart:
return work;
}
+
+#ifndef FORCE_IO_SELECT
+/* fill g_poll.poll and return the number of entries filled */
+static unsigned int poll_fill_fds(void)
+{
+ struct osmo_fd *ufd;
+ unsigned int i = 0;
+
+ llist_for_each_entry(ufd, &osmo_fds, list) {
+ struct pollfd *p;
+
+ if (!ufd->when)
+ continue;
+
+ p = &g_poll.poll[i++];
+
+ p->fd = ufd->fd;
+ p->events = 0;
+ p->revents = 0;
+
+ /* use the same mapping as the Linux kernel does in fs/select.c */
+ if (ufd->when & OSMO_FD_READ)
+ p->events |= POLLIN | POLLHUP | POLLERR;
+
+ if (ufd->when & OSMO_FD_WRITE)
+ p->events |= POLLOUT | POLLERR;
+
+ if (ufd->when & OSMO_FD_EXCEPT)
+ p->events |= POLLPRI;
+
+ }
+
+ return i;
+}
+
+/* iterate over first n_fd entries of g_poll.poll + dispatch */
+static int poll_disp_fds(unsigned int n_fd)
+{
+ struct osmo_fd *ufd;
+ unsigned int i;
+ int work = 0;
+ int shutdown_pending_writes = 0;
+
+ for (i = 0; i < n_fd; i++) {
+ struct pollfd *p = &g_poll.poll[i];
+ int flags = 0;
+
+ if (!p->revents)
+ continue;
+
+ ufd = osmo_fd_get_by_fd(p->fd);
+ if (!ufd) {
+ /* FD might have been unregistered meanwhile */
+ continue;
+ }
+ /* use the same mapping as the Linux kernel does in fs/select.c */
+ if (p->revents & (POLLIN | POLLHUP | POLLERR))
+ flags |= OSMO_FD_READ;
+ if (p->revents & (POLLOUT | POLLERR))
+ flags |= OSMO_FD_WRITE;
+ if (p->revents & POLLPRI)
+ flags |= OSMO_FD_EXCEPT;
+
+ /* make sure we never report more than the user requested */
+ flags &= ufd->when;
+
+ if (_osmo_select_shutdown_requested > 0) {
+ if (ufd->when & OSMO_FD_WRITE)
+ shutdown_pending_writes++;
+ }
+
+ if (flags) {
+ work = 1;
+ /* make sure to clear any log context before processing the next incoming message
+ * as part of some file descriptor callback. This effectively prevents "context
+ * leaking" from processing of one message into processing of the next message as part
+ * of one iteration through the list of file descriptors here. See OS#3813 */
+ log_reset_context();
+ ufd->cb(ufd, flags);
+ }
+ }
+
+ if (_osmo_select_shutdown_requested > 0 && !shutdown_pending_writes)
+ _osmo_select_shutdown_done = true;
+
+ return work;
+}
+
+static int _osmo_select_main(int polling)
+{
+ unsigned int n_poll;
+ int rc;
+ int timeout = 0;
+
+ /* prepare read and write fdsets */
+ n_poll = poll_fill_fds();
+
+ if (!polling) {
+ osmo_timers_prepare();
+ timeout = osmo_timers_nearest_ms();
+
+ if (_osmo_select_shutdown_requested && timeout == -1)
+ timeout = 0;
+ }
+
+ rc = poll(g_poll.poll, n_poll, timeout);
+ if (rc < 0)
+ return 0;
+
+ /* fire timers */
+ if (!_osmo_select_shutdown_requested)
+ osmo_timers_update();
+
+ OSMO_ASSERT(osmo_ctx->select);
+
+ /* call registered callback functions */
+ return poll_disp_fds(n_poll);
+}
+#else /* FORCE_IO_SELECT */
+/* the old implementation based on select, used 2008-2020 */
static int _osmo_select_main(int polling)
{
fd_set readset, writeset, exceptset;
@@ -264,6 +427,7 @@ static int _osmo_select_main(int polling)
/* call registered callback functions */
return osmo_fd_disp_fds(&readset, &writeset, &exceptset);
}
+#endif /* FORCE_IO_SELECT */
/*! select main loop integration
* \param[in] polling should we pollonly (1) or block on select (0)
@@ -453,6 +617,59 @@ osmo_signalfd_setup(void *ctx, sigset_t set, osmo_signalfd_cb *cb, void *data)
#endif /* HAVE_SYS_SIGNALFD_H */
+/*! Request osmo_select_* to only service pending OSMO_FD_WRITE requests. Once all writes are done,
+ * osmo_select_shutdown_done() returns true. This allows for example to send all outbound packets before terminating the
+ * process.
+ *
+ * Usage example:
+ *
+ * static void signal_handler(int signum)
+ * {
+ * fprintf(stdout, "signal %u received\n", signum);
+ *
+ * switch (signum) {
+ * case SIGINT:
+ * case SIGTERM:
+ * // If the user hits Ctrl-C the third time, just terminate immediately.
+ * if (osmo_select_shutdown_requested() >= 2)
+ * exit(-1);
+ * // Request write-only mode in osmo_select_main_ctx()
+ * osmo_select_shutdown_request();
+ * break;
+ * [...]
+ * }
+ *
+ * main()
+ * {
+ * signal(SIGINT, &signal_handler);
+ * signal(SIGTERM, &signal_handler);
+ *
+ * [...]
+ *
+ * // After the signal_handler issued osmo_select_shutdown_request(), osmo_select_shutdown_done() returns true
+ * // as soon as all write queues are empty.
+ * while (!osmo_select_shutdown_done()) {
+ * osmo_select_main_ctx(0);
+ * }
+ * }
+ */
+void osmo_select_shutdown_request(void)
+{
+ _osmo_select_shutdown_requested++;
+};
+
+/*! Return the number of times osmo_select_shutdown_request() was called before. */
+int osmo_select_shutdown_requested(void)
+{
+ return _osmo_select_shutdown_requested;
+};
+
+/*! Return true after osmo_select_shutdown_requested() was called, and after an osmo_select poll loop found no more
+ * pending OSMO_FD_WRITE on any registered socket. */
+bool osmo_select_shutdown_done(void) {
+ return _osmo_select_shutdown_done;
+};
+
/*! @} */
#endif /* _HAVE_SYS_SELECT_H */
diff --git a/src/sercomm.c b/src/core/sercomm.c
index 2639bf8a..1798acec 100644
--- a/src/sercomm.c
+++ b/src/core/sercomm.c
@@ -14,10 +14,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
/*! \addtogroup sercomm
diff --git a/src/serial.c b/src/core/serial.c
index 31cb81d1..117c049b 100644
--- a/src/serial.c
+++ b/src/core/serial.c
@@ -16,10 +16,6 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
/*! \addtogroup serial
@@ -67,7 +63,7 @@ osmo_serial_init(const char *dev, speed_t baudrate)
return -errno;
}
- /* now put it into blcoking mode */
+ /* now put it into blocking mode */
flags = fcntl(fd, F_GETFL, 0);
if (flags < 0) {
dbg_perror("fcntl get flags");
@@ -91,8 +87,10 @@ osmo_serial_init(const char *dev, speed_t baudrate)
goto error;
}
- cfsetispeed(&tio, baudrate);
- cfsetospeed(&tio, baudrate);
+ if (cfsetispeed(&tio, baudrate) < 0)
+ dbg_perror("cfsetispeed()");
+ if (cfsetospeed(&tio, baudrate) < 0)
+ dbg_perror("cfsetospeed()");
tio.c_cflag &= ~(PARENB | CSTOPB | CSIZE | CRTSCTS);
tio.c_cflag |= (CREAD | CLOCAL | CS8);
@@ -136,12 +134,15 @@ _osmo_serial_set_baudrate(int fd, speed_t baudrate)
dbg_perror("tcgetattr()");
return -errno;
}
- cfsetispeed(&tio, baudrate);
- cfsetospeed(&tio, baudrate);
+
+ if (cfsetispeed(&tio, baudrate) < 0)
+ dbg_perror("cfsetispeed()");
+ if (cfsetospeed(&tio, baudrate) < 0)
+ dbg_perror("cfsetospeed()");
rc = tcsetattr(fd, TCSANOW, &tio);
if (rc < 0) {
- dbg_perror("tcgetattr()");
+ dbg_perror("tcsetattr()");
return -errno;
}
@@ -240,4 +241,39 @@ osmo_serial_clear_custom_baudrate(int fd)
return 0;
}
+/*! Convert unsigned integer value to speed_t
+ * \param[in] baudrate integer value containing the desired standard baudrate
+ * \param[out] speed the standrd baudrate requested in speed_t format
+ * \returns 0 for success or negative errno.
+ */
+int
+osmo_serial_speed_t(unsigned int baudrate, speed_t *speed)
+{
+ switch(baudrate) {
+ case 0: *speed = B0; break;
+ case 50: *speed = B50; break;
+ case 75: *speed = B75; break;
+ case 110: *speed = B110; break;
+ case 134: *speed = B134; break;
+ case 150: *speed = B150; break;
+ case 200: *speed = B200; break;
+ case 300: *speed = B300; break;
+ case 600: *speed = B600; break;
+ case 1200: *speed = B1200; break;
+ case 1800: *speed = B1800; break;
+ case 2400: *speed = B2400; break;
+ case 4800: *speed = B4800; break;
+ case 9600: *speed = B9600; break;
+ case 19200: *speed = B19200; break;
+ case 38400: *speed = B38400; break;
+ case 57600: *speed = B57600; break;
+ case 115200: *speed = B115200; break;
+ case 230400: *speed = B230400; break;
+ default:
+ *speed = B0;
+ return -EINVAL;
+ }
+ return 0;
+}
+
/*! @} */
diff --git a/src/signal.c b/src/core/signal.c
index be3b7778..ba1555ae 100644
--- a/src/signal.c
+++ b/src/core/signal.c
@@ -16,10 +16,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <osmocom/core/signal.h>
diff --git a/src/sockaddr_str.c b/src/core/sockaddr_str.c
index f5508a03..9f1e8972 100644
--- a/src/sockaddr_str.c
+++ b/src/core/sockaddr_str.c
@@ -20,10 +20,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include "config.h"
diff --git a/src/socket.c b/src/core/socket.c
index f078242c..9e0f84e3 100644
--- a/src/socket.c
+++ b/src/core/socket.c
@@ -15,13 +15,9 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
-#include "../config.h"
+#include "config.h"
/*! \addtogroup socket
* @{
@@ -118,7 +114,7 @@ static struct addrinfo *addrinfo_helper(uint16_t family, uint16_t type, uint8_t
static int addrinfo_helper_multi(struct addrinfo **addrinfo, uint16_t family, uint16_t type, uint8_t proto,
const char **hosts, size_t host_cnt, uint16_t port, bool passive)
{
- int i, j;
+ unsigned int i, j;
for (i = 0; i < host_cnt; i++) {
addrinfo[i] = addrinfo_helper(family, type, proto, hosts[i], port, passive);
@@ -132,31 +128,64 @@ static int addrinfo_helper_multi(struct addrinfo **addrinfo, uint16_t family, ui
}
#endif /* HAVE_LIBSCTP*/
-static int socket_helper(const struct addrinfo *rp, unsigned int flags)
+static int socket_helper_tail(int sfd, unsigned int flags)
{
- int sfd, on = 1;
+ int rc, on = 1;
+ uint8_t dscp = GET_OSMO_SOCK_F_DSCP(flags);
+ uint8_t prio = GET_OSMO_SOCK_F_PRIO(flags);
- sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
- if (sfd == -1) {
- LOGP(DLGLOBAL, LOGL_ERROR,
- "unable to create socket: %s\n", strerror(errno));
- return sfd;
- }
if (flags & OSMO_SOCK_F_NONBLOCK) {
if (ioctl(sfd, FIONBIO, (unsigned char *)&on) < 0) {
LOGP(DLGLOBAL, LOGL_ERROR,
"cannot set this socket unblocking: %s\n",
strerror(errno));
close(sfd);
- sfd = -EINVAL;
+ return -EINVAL;
+ }
+ }
+
+ if (dscp) {
+ rc = osmo_sock_set_dscp(sfd, dscp);
+ if (rc) {
+ LOGP(DLGLOBAL, LOGL_ERROR, "cannot set IP DSCP of socket to %u: %s\n",
+ dscp, strerror(errno));
+ /* we consider this a non-fatal error */
+ }
+ }
+
+ if (prio) {
+ rc = osmo_sock_set_priority(sfd, prio);
+ if (rc) {
+ LOGP(DLGLOBAL, LOGL_ERROR, "cannot set priority of socket to %u: %s\n",
+ prio, strerror(errno));
+ /* we consider this a non-fatal error */
}
}
+
+ return 0;
+}
+
+static int socket_helper(const struct addrinfo *rp, unsigned int flags)
+{
+ int sfd, rc;
+
+ sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
+ if (sfd == -1) {
+ LOGP(DLGLOBAL, LOGL_ERROR,
+ "unable to create socket: %s\n", strerror(errno));
+ return sfd;
+ }
+
+ rc = socket_helper_tail(sfd, flags);
+ if (rc < 0)
+ return rc;
+
return sfd;
}
static int socket_helper_osa(const struct osmo_sockaddr *addr, uint16_t type, uint8_t proto, unsigned int flags)
{
- int sfd, on = 1;
+ int sfd, rc;
sfd = socket(addr->u.sa.sa_family, type, proto);
if (sfd == -1) {
@@ -164,15 +193,11 @@ static int socket_helper_osa(const struct osmo_sockaddr *addr, uint16_t type, ui
"unable to create socket: %s\n", strerror(errno));
return sfd;
}
- if (flags & OSMO_SOCK_F_NONBLOCK) {
- if (ioctl(sfd, FIONBIO, (unsigned char *)&on) < 0) {
- LOGP(DLGLOBAL, LOGL_ERROR,
- "cannot set this socket unblocking: %s\n",
- strerror(errno));
- close(sfd);
- sfd = -EINVAL;
- }
- }
+
+ rc = socket_helper_tail(sfd, flags);
+ if (rc < 0)
+ return rc;
+
return sfd;
}
@@ -185,7 +210,8 @@ static int socket_helper_osa(const struct osmo_sockaddr *addr, uint16_t type, ui
static int multiaddr_snprintf(char* buf, size_t buf_len, const char **hosts, size_t host_cnt)
{
int len = 0, offset = 0, rem = buf_len;
- int ret, i;
+ size_t i;
+ int ret;
char *after;
if (buf_len < 3)
@@ -223,7 +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;
}
@@ -458,8 +484,8 @@ int osmo_sock_init2(uint16_t family, uint16_t type, uint8_t proto,
}
#define _SOCKADDR_TO_STR(dest, sockaddr) do { \
- if (osmo_sockaddr_str_from_sockaddr(&dest, &sockaddr->u.sas)) \
- osmo_strlcpy(dest.ip, "Invalid IP", 11); \
+ if (osmo_sockaddr_str_from_sockaddr(dest, &sockaddr->u.sas)) \
+ osmo_strlcpy((dest)->ip, "Invalid IP", 11); \
} while (0)
/*! Initialize a socket (including bind and/or connect)
@@ -494,7 +520,8 @@ int osmo_sock_init_osa(uint16_t type, uint8_t proto,
unsigned int flags)
{
int sfd = -1, rc, on = 1;
- struct osmo_sockaddr_str sastr = {};
+ struct osmo_sockaddr_str _sastr = {};
+ struct osmo_sockaddr_str *sastr = &_sastr;
if ((flags & (OSMO_SOCK_F_BIND | OSMO_SOCK_F_CONNECT)) == 0) {
LOGP(DLGLOBAL, LOGL_ERROR, "invalid: you have to specify either "
@@ -525,8 +552,8 @@ int osmo_sock_init_osa(uint16_t type, uint8_t proto,
sfd = socket_helper_osa(local, type, proto, flags);
if (sfd < 0) {
_SOCKADDR_TO_STR(sastr, local);
- LOGP(DLGLOBAL, LOGL_ERROR, "no suitable local addr found for: %s:%u\n",
- sastr.ip, sastr.port);
+ LOGP(DLGLOBAL, LOGL_ERROR, "no suitable local addr found for: " OSMO_SOCKADDR_STR_FMT "\n",
+ OSMO_SOCKADDR_STR_FMT_ARGS(sastr));
return -ENODEV;
}
@@ -536,10 +563,8 @@ int osmo_sock_init_osa(uint16_t type, uint8_t proto,
if (rc < 0) {
_SOCKADDR_TO_STR(sastr, local);
LOGP(DLGLOBAL, LOGL_ERROR,
- "cannot setsockopt socket:"
- " %s:%u: %s\n",
- sastr.ip, sastr.port,
- strerror(errno));
+ "cannot setsockopt socket: " OSMO_SOCKADDR_STR_FMT ": %s\n",
+ OSMO_SOCKADDR_STR_FMT_ARGS(sastr), strerror(errno));
close(sfd);
return rc;
}
@@ -547,8 +572,8 @@ int osmo_sock_init_osa(uint16_t type, uint8_t proto,
if (bind(sfd, &local->u.sa, sizeof(struct osmo_sockaddr)) == -1) {
_SOCKADDR_TO_STR(sastr, local);
- LOGP(DLGLOBAL, LOGL_ERROR, "unable to bind socket: %s:%u: %s\n",
- sastr.ip, sastr.port, strerror(errno));
+ LOGP(DLGLOBAL, LOGL_ERROR, "unable to bind socket: " OSMO_SOCKADDR_STR_FMT ": %s\n",
+ OSMO_SOCKADDR_STR_FMT_ARGS(sastr), strerror(errno));
close(sfd);
return -1;
}
@@ -570,8 +595,8 @@ 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) {
_SOCKADDR_TO_STR(sastr, remote);
- LOGP(DLGLOBAL, LOGL_ERROR, "unable to connect socket: %s:%u: %s\n",
- sastr.ip, sastr.port, strerror(errno));
+ LOGP(DLGLOBAL, LOGL_ERROR, "unable to connect socket: " OSMO_SOCKADDR_STR_FMT ": %s\n",
+ OSMO_SOCKADDR_STR_FMT_ARGS(sastr), strerror(errno));
close(sfd);
return rc;
}
@@ -621,7 +646,7 @@ static bool addrinfo_has_in6addr_any(const struct addrinfo **result, size_t resu
static int socket_helper_multiaddr(uint16_t family, uint16_t type, uint8_t proto, unsigned int flags)
{
- int sfd, on = 1;
+ int sfd, rc;
sfd = socket(family, type, proto);
if (sfd == -1) {
@@ -629,22 +654,18 @@ static int socket_helper_multiaddr(uint16_t family, uint16_t type, uint8_t proto
"Unable to create socket: %s\n", strerror(errno));
return sfd;
}
- if (flags & OSMO_SOCK_F_NONBLOCK) {
- if (ioctl(sfd, FIONBIO, (unsigned char *)&on) < 0) {
- LOGP(DLGLOBAL, LOGL_ERROR,
- "Cannot set this socket unblocking: %s\n",
- strerror(errno));
- close(sfd);
- sfd = -EINVAL;
- }
- }
+
+ rc = socket_helper_tail(sfd, flags);
+ if (rc < 0)
+ return rc;
+
return sfd;
}
/* Build array of addresses taking first addrinfo result of the requested family
* for each host in addrs_buf. */
static int addrinfo_to_sockaddr(uint16_t family, const struct addrinfo **result,
- const char **hosts, int host_cont,
+ const char **hosts, unsigned int host_cont,
uint8_t *addrs_buf, size_t addrs_buf_len) {
size_t host_idx, offset = 0;
const struct addrinfo *rp;
@@ -700,7 +721,7 @@ int osmo_sock_init2_multiaddr(uint16_t family, uint16_t type, uint8_t proto,
{
struct addrinfo *res_loc[OSMO_SOCK_MAX_ADDRS], *res_rem[OSMO_SOCK_MAX_ADDRS];
int sfd = -1, rc, on = 1;
- int i;
+ unsigned int i;
bool loc_has_v4addr, rem_has_v4addr;
bool loc_has_v6addr, rem_has_v6addr;
struct sockaddr_in6 addrs_buf[OSMO_SOCK_MAX_ADDRS];
@@ -869,7 +890,9 @@ int osmo_sock_init(uint16_t family, uint16_t type, uint8_t proto,
const char *host, uint16_t port, unsigned int flags)
{
struct addrinfo *result, *rp;
- int sfd, rc, on = 1;
+ int sfd = -1; /* initialize to avoid uninitialized false warnings on some gcc versions (11.1.0) */
+ int on = 1;
+ int rc;
if ((flags & (OSMO_SOCK_F_BIND | OSMO_SOCK_F_CONNECT)) ==
(OSMO_SOCK_F_BIND | OSMO_SOCK_F_CONNECT)) {
@@ -947,11 +970,12 @@ int osmo_sock_init(uint16_t family, uint16_t type, uint8_t proto,
/*! fill \ref osmo_fd for a give sfd
* \param[out] ofd file descriptor (will be filled in)
* \param[in] sfd socket file descriptor
+ * \param[in] flags flags like \ref OSMO_SOCK_F_CONNECT
* \returns socket fd on success; negative on error
*
* This function fills the \a ofd structure.
*/
-static inline int osmo_fd_init_ofd(struct osmo_fd *ofd, int sfd)
+static inline int osmo_fd_init_ofd(struct osmo_fd *ofd, int sfd, unsigned int flags)
{
int rc;
@@ -961,6 +985,14 @@ static inline int osmo_fd_init_ofd(struct osmo_fd *ofd, int sfd)
ofd->fd = sfd;
ofd->when = OSMO_FD_READ;
+ /* if we're doing a non-blocking connect, the completion will be signaled
+ * by marking the fd as WRITE-able. So in this exceptional case, we're
+ * also interested in when the socket becomes write-able */
+ if ((flags & (OSMO_SOCK_F_CONNECT|OSMO_SOCK_F_NONBLOCK)) ==
+ (OSMO_SOCK_F_CONNECT|OSMO_SOCK_F_NONBLOCK)) {
+ ofd->when |= OSMO_FD_WRITE;
+ }
+
rc = osmo_fd_register(ofd);
if (rc < 0) {
close(sfd);
@@ -986,7 +1018,7 @@ static inline int osmo_fd_init_ofd(struct osmo_fd *ofd, int sfd)
int osmo_sock_init_ofd(struct osmo_fd *ofd, int family, int type, int proto,
const char *host, uint16_t port, unsigned int flags)
{
- return osmo_fd_init_ofd(ofd, osmo_sock_init(family, type, proto, host, port, flags));
+ return osmo_fd_init_ofd(ofd, osmo_sock_init(family, type, proto, host, port, flags), flags);
}
/*! Initialize a socket and fill \ref osmo_fd
@@ -1009,14 +1041,14 @@ int osmo_sock_init2_ofd(struct osmo_fd *ofd, int family, int type, int proto,
const char *remote_host, uint16_t remote_port, unsigned int flags)
{
return osmo_fd_init_ofd(ofd, osmo_sock_init2(family, type, proto, local_host,
- local_port, remote_host, remote_port, flags));
+ local_port, remote_host, remote_port, flags), flags);
}
int osmo_sock_init_osa_ofd(struct osmo_fd *ofd, int type, int proto,
const struct osmo_sockaddr *local,
const struct osmo_sockaddr *remote, unsigned int flags)
{
- return osmo_fd_init_ofd(ofd, osmo_sock_init_osa(type, proto, local, remote, flags));
+ return osmo_fd_init_ofd(ofd, osmo_sock_init_osa(type, proto, local, remote, flags), flags);
}
/*! Initialize a socket and fill \ref sockaddr
@@ -1093,6 +1125,25 @@ static int sockaddr_equal(const struct sockaddr *a,
return 0;
}
+/* linux has a default route:
+local 127.0.0.0/8 dev lo proto kernel scope host src 127.0.0.1
+*/
+static int sockaddr_is_local_routed(const struct sockaddr *a)
+{
+#if __linux__
+ if (a->sa_family != AF_INET)
+ return 0;
+
+ uint32_t address = ((struct sockaddr_in *)a)->sin_addr.s_addr; /* already BE */
+ uint32_t eightmask = htonl(0xff000000); /* /8 mask */
+ uint32_t local_prefix_127 = htonl(0x7f000000); /* 127.0.0.0 */
+
+ if ((address & eightmask) == local_prefix_127)
+ return 1;
+#endif
+ return 0;
+}
+
/*! Determine if the given address is a local address
* \param[in] addr Socket Address
* \param[in] addrlen Length of socket address in bytes
@@ -1102,6 +1153,9 @@ int osmo_sockaddr_is_local(struct sockaddr *addr, unsigned int addrlen)
{
struct ifaddrs *ifaddr, *ifa;
+ if (sockaddr_is_local_routed(addr))
+ return 1;
+
if (getifaddrs(&ifaddr) == -1) {
LOGP(DLGLOBAL, LOGL_ERROR, "getifaddrs:"
" %s\n", strerror(errno));
@@ -1121,6 +1175,26 @@ int osmo_sockaddr_is_local(struct sockaddr *addr, unsigned int addrlen)
return 0;
}
+/*! Determine if the given address is an ANY address ("0.0.0.0", "::"). Port is not checked.
+ * \param[in] addr Socket Address
+ * \param[in] addrlen Length of socket address in bytes
+ * \returns 1 if address is ANY, 0 otherwise. -1 is address family not supported/detected.
+ */
+int osmo_sockaddr_is_any(const struct osmo_sockaddr *addr)
+{
+ switch (addr->u.sa.sa_family) {
+ case AF_INET6: {
+ struct in6_addr ip6_any = IN6ADDR_ANY_INIT;
+ return memcmp(&addr->u.sin6.sin6_addr,
+ &ip6_any, sizeof(ip6_any)) == 0;
+ }
+ case AF_INET:
+ return addr->u.sin.sin_addr.s_addr == INADDR_ANY;
+ default:
+ return -1;
+ }
+}
+
/*! Convert sockaddr_in to IP address as char string and port as uint16_t.
* \param[out] addr String buffer to write IP address to, or NULL.
* \param[out] addr_len Size of \a addr.
@@ -1200,6 +1274,80 @@ uint16_t osmo_sockaddr_port(const struct sockaddr *sa)
return 0;
}
+/*! Set sockaddr port content (to network byte order).
+ * \param[out] sa sockaddr to set the port of.
+ * \param[in] port port nr to set.
+ */
+void osmo_sockaddr_set_port(struct sockaddr *sa, uint16_t port)
+{
+ struct osmo_sockaddr *osa = (struct osmo_sockaddr *)sa;
+ switch (osa->u.sa.sa_family) {
+ case AF_INET6:
+ osa->u.sin6.sin6_port = htons(port);
+ return;
+ case AF_INET:
+ osa->u.sin.sin_port = htons(port);
+ return;
+ }
+}
+
+static unsigned int in6_addr_netmask_to_prefixlen(const struct in6_addr *netmask)
+{
+ #if defined(__linux__)
+ #define ADDRFIELD(i) s6_addr32[i]
+ #else
+ #define ADDRFIELD(i) __u6_addr.__u6_addr32[i]
+ #endif
+
+ unsigned int i, j, prefix = 0;
+
+ for (j = 0; j < 4; j++) {
+ uint32_t bits = netmask->ADDRFIELD(j);
+ uint8_t *b = (uint8_t *)&bits;
+ for (i = 0; i < 4; i++) {
+ while (b[i] & 0x80) {
+ prefix++;
+ b[i] = b[i] << 1;
+ }
+ }
+ }
+
+ #undef ADDRFIELD
+
+ return prefix;
+}
+
+static unsigned int in_addr_netmask_to_prefixlen(const struct in_addr *netmask)
+{
+ uint32_t bits = netmask->s_addr;
+ uint8_t *b = (uint8_t *)&bits;
+ unsigned int i, prefix = 0;
+
+ for (i = 0; i < 4; i++) {
+ while (b[i] & 0x80) {
+ prefix++;
+ b[i] = b[i] << 1;
+ }
+ }
+ return prefix;
+}
+
+/*! Convert netmask to prefix length representation
+ * \param[in] netmask sockaddr containing a netmask (consecutive list of 1-bit followed by consecutive list of 0-bit)
+ * \returns prefix length representation of the netmask (count of 1-bit from the start of the netmask), negative on error.
+ */
+int osmo_sockaddr_netmask_to_prefixlen(const struct osmo_sockaddr *netmask)
+{
+ switch (netmask->u.sa.sa_family) {
+ case AF_INET6:
+ return in6_addr_netmask_to_prefixlen(&netmask->u.sin6.sin6_addr);
+ case AF_INET:
+ return in_addr_netmask_to_prefixlen(&netmask->u.sin.sin_addr);
+ default:
+ return -ENOTSUP;
+ }
+}
+
/*! Initialize a unix domain socket (including bind/connect)
* \param[in] type Socket type like SOCK_DGRAM, SOCK_STREAM
* \param[in] proto Protocol like IPPROTO_TCP, IPPROTO_UDP
@@ -1218,7 +1366,7 @@ int osmo_sock_unix_init(uint16_t type, uint8_t proto,
const char *socket_path, unsigned int flags)
{
struct sockaddr_un local;
- int sfd, rc, on = 1;
+ int sfd, rc;
unsigned int namelen;
if ((flags & (OSMO_SOCK_F_BIND | OSMO_SOCK_F_CONNECT)) ==
@@ -1245,7 +1393,7 @@ int osmo_sock_unix_init(uint16_t type, uint8_t proto,
sfd = socket(AF_UNIX, type, proto);
if (sfd < 0)
- return -1;
+ return -errno;
if (flags & OSMO_SOCK_F_CONNECT) {
rc = connect(sfd, (struct sockaddr *)&local, namelen);
@@ -1258,26 +1406,20 @@ int osmo_sock_unix_init(uint16_t type, uint8_t proto,
goto err;
}
- if (flags & OSMO_SOCK_F_NONBLOCK) {
- if (ioctl(sfd, FIONBIO, (unsigned char *)&on) < 0) {
- LOGP(DLGLOBAL, LOGL_ERROR,
- "cannot set this socket unblocking: %s\n",
- strerror(errno));
- close(sfd);
- return -EINVAL;
- }
- }
+ rc = socket_helper_tail(sfd, flags);
+ if (rc < 0)
+ return rc;
rc = osmo_sock_init_tail(sfd, type, flags);
if (rc < 0) {
close(sfd);
- sfd = -1;
+ sfd = rc;
}
return sfd;
err:
close(sfd);
- return -1;
+ return -errno;
}
/*! Initialize a unix domain socket and fill \ref osmo_fd
@@ -1294,7 +1436,7 @@ err:
int osmo_sock_unix_init_ofd(struct osmo_fd *ofd, uint16_t type, uint8_t proto,
const char *socket_path, unsigned int flags)
{
- return osmo_fd_init_ofd(ofd, osmo_sock_unix_init(type, proto, socket_path, flags));
+ return osmo_fd_init_ofd(ofd, osmo_sock_unix_init(type, proto, socket_path, flags), flags);
}
/*! Get the IP and/or port number on socket in separate string buffers.
@@ -1685,12 +1827,72 @@ int osmo_sockaddr_local_ip(struct osmo_sockaddr *local_ip, const struct osmo_soc
return rc;
}
+/*! Copy the addr part, the IP address octets in network byte order, to a buffer.
+ * Useful for encoding network protocols.
+ * \param[out] dst Write octets to this buffer.
+ * \param[in] dst_maxlen Space available in buffer.
+ * \param[in] os Sockaddr to copy IP of.
+ * \return nr of octets written on success, negative on error.
+ */
+int osmo_sockaddr_to_octets(uint8_t *dst, size_t dst_maxlen, const struct osmo_sockaddr *os)
+{
+ const void *addr;
+ size_t len;
+ switch (os->u.sa.sa_family) {
+ case AF_INET:
+ addr = &os->u.sin.sin_addr;
+ len = sizeof(os->u.sin.sin_addr);
+ break;
+ case AF_INET6:
+ addr = &os->u.sin6.sin6_addr;
+ len = sizeof(os->u.sin6.sin6_addr);
+ break;
+ default:
+ return -ENOTSUP;
+ }
+ if (dst_maxlen < len)
+ return -ENOSPC;
+ memcpy(dst, addr, len);
+ return len;
+}
+
+/*! Copy the addr part, the IP address octets in network byte order, from a buffer.
+ * Useful for decoding network protocols.
+ * \param[out] os Write IP address to this sockaddr.
+ * \param[in] src Source buffer to read IP address octets from.
+ * \param[in] src_len Number of octets to copy.
+ * \return number of octets read on success, negative on error.
+ */
+int osmo_sockaddr_from_octets(struct osmo_sockaddr *os, const void *src, size_t src_len)
+{
+ void *addr;
+ size_t len;
+ *os = (struct osmo_sockaddr){0};
+ switch (src_len) {
+ case sizeof(os->u.sin.sin_addr):
+ os->u.sa.sa_family = AF_INET;
+ addr = &os->u.sin.sin_addr;
+ len = sizeof(os->u.sin.sin_addr);
+ break;
+ case sizeof(os->u.sin6.sin6_addr):
+ os->u.sin6.sin6_family = AF_INET6;
+ addr = &os->u.sin6.sin6_addr;
+ len = sizeof(os->u.sin6.sin6_addr);
+ break;
+ default:
+ return -ENOTSUP;
+ }
+ memcpy(addr, src, len);
+ return len;
+}
+
/*! Compare two osmo_sockaddr.
* \param[in] a
* \param[in] b
* \return 0 if a and b are equal. Otherwise it follows memcmp()
*/
-int osmo_sockaddr_cmp(struct osmo_sockaddr *a, struct osmo_sockaddr *b)
+int osmo_sockaddr_cmp(const struct osmo_sockaddr *a,
+ const struct osmo_sockaddr *b)
{
if (a == b)
return 0;
@@ -1714,6 +1916,147 @@ int osmo_sockaddr_cmp(struct osmo_sockaddr *a, struct osmo_sockaddr *b)
}
}
+/*! string-format a given osmo_sockaddr address
+ * \param[in] sockaddr the osmo_sockaddr to print
+ * \return pointer to the string on success; NULL on error
+ */
+const char *osmo_sockaddr_to_str(const struct osmo_sockaddr *sockaddr)
+{
+ /* INET6_ADDRSTRLEN contains already a null termination,
+ * adding '[' ']' ':' '16 bit port' */
+ static __thread char buf[INET6_ADDRSTRLEN + 8];
+ return osmo_sockaddr_to_str_buf(buf, sizeof(buf), sockaddr);
+}
+
+/*! string-format a given osmo_sockaddr address into a user-supplied buffer.
+ * Same as osmo_sockaddr_to_str_buf() but returns a would-be length in snprintf() style.
+ * \param[in] buf user-supplied output buffer
+ * \param[in] buf_len size of the user-supplied output buffer in bytes
+ * \param[in] sockaddr the osmo_sockaddr to print
+ * \return number of characters that would be written if the buffer is large enough, like snprintf().
+ */
+int osmo_sockaddr_to_str_buf2(char *buf, size_t buf_len, const struct osmo_sockaddr *sockaddr)
+{
+ struct osmo_strbuf sb = { .buf = buf, .len = buf_len };
+ uint16_t port = 0;
+
+ if (!sockaddr) {
+ OSMO_STRBUF_PRINTF(sb, "NULL");
+ return sb.chars_needed;
+ }
+
+ switch (sockaddr->u.sa.sa_family) {
+ case AF_INET:
+ OSMO_STRBUF_APPEND(sb, osmo_sockaddr_to_str_and_uint, &port, &sockaddr->u.sa);
+ if (port)
+ OSMO_STRBUF_PRINTF(sb, ":%u", port);
+ break;
+ case AF_INET6:
+ OSMO_STRBUF_PRINTF(sb, "[");
+ OSMO_STRBUF_APPEND(sb, osmo_sockaddr_to_str_and_uint, &port, &sockaddr->u.sa);
+ OSMO_STRBUF_PRINTF(sb, "]");
+ if (port)
+ OSMO_STRBUF_PRINTF(sb, ":%u", port);
+ break;
+ default:
+ OSMO_STRBUF_PRINTF(sb, "unsupported family %d", sockaddr->u.sa.sa_family);
+ break;
+ }
+
+ return sb.chars_needed;
+}
+
+/*! string-format a given osmo_sockaddr address into a talloc allocated buffer.
+ * Like osmo_sockaddr_to_str_buf2() but returns a talloc allocated string.
+ * \param[in] ctx talloc context to allocate from, e.g. OTC_SELECT.
+ * \param[in] sockaddr the osmo_sockaddr to print.
+ * \return human readable string.
+ */
+char *osmo_sockaddr_to_str_c(void *ctx, const struct osmo_sockaddr *sockaddr)
+{
+ OSMO_NAME_C_IMPL(ctx, 64, "ERROR", osmo_sockaddr_to_str_buf2, sockaddr)
+}
+
+/*! string-format a given osmo_sockaddr address into a user-supplied buffer.
+ * Like osmo_sockaddr_to_str_buf2() but returns buf, or NULL if too short.
+ * \param[in] buf user-supplied output buffer
+ * \param[in] buf_len size of the user-supplied output buffer in bytes
+ * \param[in] sockaddr the osmo_sockaddr to print
+ * \return pointer to the string on success; NULL on error
+ */
+char *osmo_sockaddr_to_str_buf(char *buf, size_t buf_len,
+ const struct osmo_sockaddr *sockaddr)
+{
+ int chars_needed = osmo_sockaddr_to_str_buf2(buf, buf_len, sockaddr);
+ if (chars_needed >= buf_len)
+ return NULL;
+ return buf;
+}
+
+/*! Set the DSCP (differentiated services code point) of a socket.
+ * \param[in] dscp DSCP value in range 0..63
+ * \returns 0 on success; negative on error. */
+int osmo_sock_set_dscp(int fd, uint8_t dscp)
+{
+ struct sockaddr_storage local_addr;
+ socklen_t local_addr_len = sizeof(local_addr);
+ uint8_t tos;
+ socklen_t tos_len = sizeof(tos);
+ int tclass;
+ socklen_t tclass_len = sizeof(tclass);
+ int rc;
+
+ /* DSCP is a 6-bit value stored in the upper 6 bits of the 8-bit TOS */
+ if (dscp > 63)
+ return -EINVAL;
+
+ rc = getsockname(fd, (struct sockaddr *)&local_addr, &local_addr_len);
+ if (rc < 0)
+ return rc;
+
+ switch (local_addr.ss_family) {
+ case AF_INET:
+ /* read the original value */
+ rc = getsockopt(fd, IPPROTO_IP, IP_TOS, &tos, &tos_len);
+ if (rc < 0)
+ return rc;
+ /* mask-in the DSCP into the upper 6 bits */
+ tos &= 0x03;
+ tos |= dscp << 2;
+ /* and write it back to the kernel */
+ rc = setsockopt(fd, IPPROTO_IP, IP_TOS, &tos, sizeof(tos));
+ break;
+ case AF_INET6:
+ /* read the original value */
+ rc = getsockopt(fd, IPPROTO_IPV6, IPV6_TCLASS, &tclass, &tclass_len);
+ if (rc < 0)
+ return rc;
+ /* mask-in the DSCP into the upper 6 bits */
+ tclass &= 0x03;
+ tclass |= dscp << 2;
+ /* and write it back to the kernel */
+ rc = setsockopt(fd, IPPROTO_IPV6, IPV6_TCLASS, &tclass, sizeof(tclass));
+ break;
+ case AF_UNSPEC:
+ default:
+ LOGP(DLGLOBAL, LOGL_ERROR, "No DSCP support for socket family %u\n",
+ local_addr.ss_family);
+ rc = -1;
+ break;
+ }
+
+ return rc;
+}
+
+/*! Set the priority value of a socket.
+ * \param[in] prio priority value. Values outside 0..6 require CAP_NET_ADMIN.
+ * \returns 0 on success; negative on error. */
+int osmo_sock_set_priority(int fd, int prio)
+{
+ /* and write it back to the kernel */
+ return setsockopt(fd, SOL_SOCKET, SO_PRIORITY, &prio, sizeof(prio));
+}
+
#endif /* HAVE_SYS_SOCKET_H */
/*! @} */
diff --git a/src/core/stat_item.c b/src/core/stat_item.c
new file mode 100644
index 00000000..804972bf
--- /dev/null
+++ b/src/core/stat_item.c
@@ -0,0 +1,463 @@
+/*! \file stat_item.c
+ * utility routines for keeping statistical values */
+/*
+ * (C) 2009-2010 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2015 by sysmocom - s.f.m.c. GmbH
+ *
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+/*! \addtogroup osmo_stat_item
+ * @{
+ *
+ * This osmo_stat_item module adds instrumentation capabilities to
+ * gather measurement and statistical values in a similar fashion to
+ * what we have as \ref osmo_counter_group.
+ *
+ * As opposed to counters, osmo_stat_item do not increment but consist
+ * of a configurable-sized FIFO, which can store not only the current
+ * (most recent) value, but also historic values.
+ *
+ * The only supported value type is an int32_t.
+ *
+ * Getting values from osmo_stat_item is usually done at a high level
+ * through the stats API (stats.c). It uses item->stats_next_id to
+ * store what has been sent to all enabled reporters. It is also
+ * possible to read from osmo_stat_item directly, without modifying
+ * its state, by storing next_id outside of osmo_stat_item.
+ *
+ * Each value stored in the FIFO of an osmo_stat_item has an associated
+ * value_id. The value_id is increased with each value, so (until the
+ * counter wraps) more recent values will have higher values.
+ *
+ * When a new value is set, the oldest value in the FIFO gets silently
+ * overwritten. Lost values are skipped when getting values from the
+ * item.
+ *
+ */
+
+/* Struct overview:
+ *
+ * Group and item descriptions:
+ * Each group description exists once as osmo_stat_item_group_desc,
+ * each such group description lists N osmo_stat_item_desc, i.e. describes N stat items.
+ *
+ * Actual stats:
+ * The global osmo_stat_item_groups llist contains all group instances, each points at a group description.
+ * This list mixes all types of groups in a single llist, where each instance points at its group desc and has an index.
+ * There are one or more instances of each group, each storing stats for a distinct object (for example, one description
+ * for a BTS group, and any number of BTS instances with independent stats). A group is identified by a group index nr
+ * and possibly also a given name for that particular index (e.g. in osmo-mgw, a group instance is named
+ * "virtual-trunk-0" and can be looked up by that name instead of its more or less arbitrary group index number).
+ *
+ * Each group instance contains one osmo_stat_item instance per global stat item description.
+ * Each osmo_stat_item keeps track of the values for the current reporting period (min, last, max, sum, n),
+ * and also stores the set of values reported at the end of the previous reporting period.
+ *
+ * const osmo_stat_item_group_desc foo
+ * +-- group_name_prefix = "foo"
+ * +-- item_desc[] (array of osmo_stat_item_desc)
+ * +-- osmo_stat_item_desc bar
+ * | +-- name = "bar"
+ * | +-- description
+ * | +-- unit
+ * | +-- default_value
+ * |
+ * +-- osmo_stat_item_desc: baz
+ * +-- ...
+ *
+ * const osmo_stat_item_group_desc moo
+ * +-- group_name_prefix = "moo"
+ * +-- item_desc[]
+ * +-- osmo_stat_item_desc goo
+ * | +-- name = "goo"
+ * | +-- description
+ * | +-- unit
+ * | +-- default_value
+ * |
+ * +-- osmo_stat_item_desc: loo
+ * +-- ...
+ *
+ * osmo_stat_item_groups (llist of osmo_stat_item_group)
+ * |
+ * +-- group: foo[0]
+ * | +-- desc --> osmo_stat_item_group_desc foo
+ * | +-- idx = 0
+ * | +-- name = NULL (no given name for this group instance)
+ * | +-- items[]
+ * | |
+ * | [0] --> osmo_stat_item instance for "bar"
+ * | | +-- desc --> osmo_stat_item_desc bar (see above)
+ * | | +-- value.{min, last, max, n, sum}
+ * | | +-- reported.{min, last, max, n, sum}
+ * | |
+ * | [1] --> osmo_stat_item instance for "baz"
+ * | | +-- desc --> osmo_stat_item_desc baz
+ * | | +-- value.{min, last, max, n, sum}
+ * | | +-- reported.{min, last, max, n, sum}
+ * | .
+ * | :
+ * |
+ * +-- group: foo[1]
+ * | +-- desc --> osmo_stat_item_group_desc foo
+ * | +-- idx = 1
+ * | +-- name = "special-foo" (instance can be looked up by this index-name)
+ * | +-- items[]
+ * | |
+ * | [0] --> osmo_stat_item instance for "bar"
+ * | | +-- desc --> osmo_stat_item_desc bar
+ * | | +-- value.{min, last, max, n, sum}
+ * | | +-- reported.{min, last, max, n, sum}
+ * | |
+ * | [1] --> osmo_stat_item instance for "baz"
+ * | | +-- desc --> osmo_stat_item_desc baz
+ * | | +-- value.{min, last, max, n, sum}
+ * | | +-- reported.{min, last, max, n, sum}
+ * | .
+ * | :
+ * |
+ * +-- group: moo[0]
+ * | +-- desc --> osmo_stat_item_group_desc moo
+ * | +-- idx = 0
+ * | +-- name = NULL
+ * | +-- items[]
+ * | |
+ * | [0] --> osmo_stat_item instance for "goo"
+ * | | +-- desc --> osmo_stat_item_desc goo
+ * | | +-- value.{min, last, max, n, sum}
+ * | | +-- reported.{min, last, max, n, sum}
+ * | |
+ * | [1] --> osmo_stat_item instance for "loo"
+ * | | +-- desc --> osmo_stat_item_desc loo
+ * | | +-- value.{min, last, max, n, sum}
+ * | | +-- reported.{min, last, max, n, sum}
+ * | .
+ * | :
+ * .
+ * :
+ *
+ */
+
+#include <stdint.h>
+#include <string.h>
+
+#include <osmocom/core/utils.h>
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/core/stat_item.h>
+
+#include <stat_item_internal.h>
+
+/*! global list of stat_item groups */
+static LLIST_HEAD(osmo_stat_item_groups);
+
+/*! talloc context from which we allocate */
+static void *tall_stat_item_ctx;
+
+/*! Allocate a new group of counters according to description.
+ * Allocate a group of stat items described in \a desc from talloc context \a ctx,
+ * giving the new group the index \a idx.
+ * \param[in] ctx \ref talloc context
+ * \param[in] desc Statistics item group description
+ * \param[in] idx Index of new stat item group
+ */
+struct osmo_stat_item_group *osmo_stat_item_group_alloc(void *ctx,
+ const struct osmo_stat_item_group_desc *group_desc,
+ unsigned int idx)
+{
+ unsigned int group_size;
+ unsigned int item_idx;
+ struct osmo_stat_item *items;
+
+ struct osmo_stat_item_group *group;
+
+ group_size = sizeof(struct osmo_stat_item_group) +
+ group_desc->num_items * sizeof(struct osmo_stat_item *);
+
+ if (!ctx)
+ ctx = tall_stat_item_ctx;
+
+ group = talloc_zero_size(ctx, group_size);
+ if (!group)
+ return NULL;
+
+ group->desc = group_desc;
+ group->idx = idx;
+
+ items = talloc_array(group, struct osmo_stat_item, group_desc->num_items);
+ OSMO_ASSERT(items);
+ for (item_idx = 0; item_idx < group_desc->num_items; item_idx++) {
+ struct osmo_stat_item *item = &items[item_idx];
+ const struct osmo_stat_item_desc *item_desc = &group_desc->item_desc[item_idx];
+ group->items[item_idx] = item;
+ *item = (struct osmo_stat_item){
+ .desc = item_desc,
+ .value = {
+ .n = 0,
+ .last = item_desc->default_value,
+ .min = item_desc->default_value,
+ .max = item_desc->default_value,
+ .sum = 0,
+ },
+ };
+ }
+
+ llist_add(&group->list, &osmo_stat_item_groups);
+ return group;
+}
+
+/*! Free the memory for the specified group of stat items */
+void osmo_stat_item_group_free(struct osmo_stat_item_group *grp)
+{
+ if (!grp)
+ return;
+
+ llist_del(&grp->list);
+ talloc_free(grp);
+}
+
+/*! Get statistics item from group, identified by index idx
+ * \param[in] grp Rate counter group
+ * \param[in] idx Index of the counter to retrieve
+ * \returns rate counter requested
+ */
+struct osmo_stat_item *osmo_stat_item_group_get_item(struct osmo_stat_item_group *grp, unsigned int idx)
+{
+ return grp->items[idx];
+}
+
+/*! Set a name for the statistics item group to be used instead of index value
+ at report time.
+ * \param[in] statg Statistics item group
+ * \param[in] name Name identifier to assign to the statistics item group
+ */
+void osmo_stat_item_group_set_name(struct osmo_stat_item_group *statg, const char *name)
+{
+ osmo_talloc_replace_string(statg, &statg->name, name);
+}
+
+/*! Increase the stat_item to the given value.
+ * This function adds a new value for the given stat_item at the end of
+ * the FIFO.
+ * \param[in] item The stat_item whose \a value we want to set
+ * \param[in] value The numeric value we want to store at end of FIFO
+ */
+void osmo_stat_item_inc(struct osmo_stat_item *item, int32_t value)
+{
+ osmo_stat_item_set(item, item->value.last + value);
+}
+
+/*! Descrease the stat_item to the given value.
+ * This function adds a new value for the given stat_item at the end of
+ * the FIFO.
+ * \param[in] item The stat_item whose \a value we want to set
+ * \param[in] value The numeric value we want to store at end of FIFO
+ */
+void osmo_stat_item_dec(struct osmo_stat_item *item, int32_t value)
+{
+ osmo_stat_item_set(item, item->value.last - value);
+}
+
+/*! Set the a given stat_item to the given value.
+ * This function adds a new value for the given stat_item at the end of
+ * the FIFO.
+ * \param[in] item The stat_item whose \a value we want to set
+ * \param[in] value The numeric value we want to store at end of FIFO
+ */
+void osmo_stat_item_set(struct osmo_stat_item *item, int32_t value)
+{
+ item->value.last = value;
+ if (item->value.n == 0) {
+ /* No values recorded yet, clamp min and max to this first value. */
+ item->value.min = item->value.max = value;
+ /* Overwrite any cruft remaining in value.sum */
+ item->value.sum = value;
+ item->value.n = 1;
+ } else {
+ item->value.min = OSMO_MIN(item->value.min, value);
+ item->value.max = OSMO_MAX(item->value.max, value);
+ item->value.sum += value;
+ item->value.n++;
+ }
+}
+
+/*! Indicate that a reporting period has elapsed, and prepare the stat item for a new period of collecting min/max/avg.
+ * \param item Stat item to flush.
+ */
+void osmo_stat_item_flush(struct osmo_stat_item *item)
+{
+ item->reported = item->value;
+
+ /* Indicate a new reporting period: no values have been received, but the previous value.last remains unchanged
+ * for the case that an entire period elapses without a new value appearing. */
+ item->value.n = 0;
+ item->value.sum = 0;
+
+ /* Also for the case that an entire period elapses without any osmo_stat_item_set(), put the min and max to the
+ * last value. As soon as one osmo_stat_item_set() occurs, these are both set to the new value (when n is still
+ * zero from above). */
+ item->value.min = item->value.max = item->value.last;
+}
+
+/*! Initialize the stat item module. Call this once from your program.
+ * \param[in] tall_ctx Talloc context from which this module allocates */
+int osmo_stat_item_init(void *tall_ctx)
+{
+ tall_stat_item_ctx = tall_ctx;
+
+ return 0;
+}
+
+/*! Search for item group based on group name and index
+ * \param[in] name Name of stats_item_group we want to find
+ * \param[in] idx Index of the group we want to find
+ * \returns pointer to group, if found; NULL otherwise */
+struct osmo_stat_item_group *osmo_stat_item_get_group_by_name_idx(
+ const char *name, const unsigned int idx)
+{
+ struct osmo_stat_item_group *statg;
+
+ llist_for_each_entry(statg, &osmo_stat_item_groups, list) {
+ if (!statg->desc)
+ continue;
+
+ if (!strcmp(statg->desc->group_name_prefix, name) &&
+ statg->idx == idx)
+ return statg;
+ }
+ return NULL;
+}
+
+/*! Search for item group based on group name and index's name.
+ * \param[in] name Name of stats_item_group we want to find.
+ * \param[in] idx_name Index of the group we want to find, by the index's name (osmo_stat_item_group->name).
+ * \returns pointer to group, if found; NULL otherwise. */
+struct osmo_stat_item_group *osmo_stat_item_get_group_by_name_idxname(const char *group_name, const char *idx_name)
+{
+ struct osmo_stat_item_group *statg;
+
+ llist_for_each_entry(statg, &osmo_stat_item_groups, list) {
+ if (!statg->desc || !statg->name)
+ continue;
+ if (strcmp(statg->desc->group_name_prefix, group_name))
+ continue;
+ if (strcmp(statg->name, idx_name))
+ continue;
+ return statg;
+ }
+ return NULL;
+}
+
+/*! Search for item based on group + item name
+ * \param[in] statg group in which to search for the item
+ * \param[in] name name of item to search within \a statg
+ * \returns pointer to item, if found; NULL otherwise */
+const struct osmo_stat_item *osmo_stat_item_get_by_name(
+ const struct osmo_stat_item_group *statg, const char *name)
+{
+ int i;
+ const struct osmo_stat_item_desc *item_desc;
+
+ if (!statg->desc)
+ return NULL;
+
+ for (i = 0; i < statg->desc->num_items; i++) {
+ item_desc = &statg->desc->item_desc[i];
+
+ if (!strcmp(item_desc->name, name)) {
+ return statg->items[i];
+ }
+ }
+ return NULL;
+}
+
+/*! Iterate over all items in group, call user-supplied function on each
+ * \param[in] statg stat_item group over whose items to iterate
+ * \param[in] handle_item Call-back function, aborts if rc < 0
+ * \param[in] data Private data handed through to \a handle_item
+ */
+int osmo_stat_item_for_each_item(struct osmo_stat_item_group *statg,
+ osmo_stat_item_handler_t handle_item, void *data)
+{
+ int rc = 0;
+ int i;
+
+ for (i = 0; i < statg->desc->num_items; i++) {
+ struct osmo_stat_item *item = statg->items[i];
+ rc = handle_item(statg, item, data);
+ if (rc < 0)
+ return rc;
+ }
+
+ return rc;
+}
+
+/*! Iterate over all stat_item groups in system, call user-supplied function on each
+ * \param[in] handle_group Call-back function, aborts if rc < 0
+ * \param[in] data Private data handed through to \a handle_group
+ */
+int osmo_stat_item_for_each_group(osmo_stat_item_group_handler_t handle_group, void *data)
+{
+ struct osmo_stat_item_group *statg;
+ int rc = 0;
+
+ llist_for_each_entry(statg, &osmo_stat_item_groups, list) {
+ rc = handle_group(statg, data);
+ if (rc < 0)
+ return rc;
+ }
+
+ return rc;
+}
+
+/*! Get the last (freshest) value. */
+int32_t osmo_stat_item_get_last(const struct osmo_stat_item *item)
+{
+ return item->value.last;
+}
+
+/*! Remove all values of a stat item
+ * \param[in] item stat item to reset
+ */
+void osmo_stat_item_reset(struct osmo_stat_item *item)
+{
+ item->value.sum = 0;
+ item->value.n = 0;
+ item->value.last = item->value.min = item->value.max = item->desc->default_value;
+}
+
+/*! Reset all osmo stat items in a group
+ * \param[in] statg stat item group to reset
+ */
+void osmo_stat_item_group_reset(struct osmo_stat_item_group *statg)
+{
+ int i;
+
+ for (i = 0; i < statg->desc->num_items; i++) {
+ struct osmo_stat_item *item = statg->items[i];
+ osmo_stat_item_reset(item);
+ }
+}
+
+/*! Return the description for an osmo_stat_item. */
+const struct osmo_stat_item_desc *osmo_stat_item_get_desc(struct osmo_stat_item *item)
+{
+ return item->desc;
+}
+
+/*! @} */
diff --git a/src/core/stat_item_internal.h b/src/core/stat_item_internal.h
new file mode 100644
index 00000000..9ede8c41
--- /dev/null
+++ b/src/core/stat_item_internal.h
@@ -0,0 +1,35 @@
+/*! \file stat_item_internal.h
+ * internal definitions for the osmo_stat_item API */
+#pragma once
+
+/*! \addtogroup osmo_stat_item
+ * @{
+ */
+
+struct osmo_stat_item_period {
+ /*! Number of osmo_stat_item_set() that occurred during the reporting period, zero if none. */
+ uint32_t n;
+ /*! Smallest value seen in a reporting period. */
+ int32_t min;
+ /*! Most recent value passed to osmo_stat_item_set(), or the item->desc->default_value if none. */
+ int32_t last;
+ /*! Largest value seen in a reporting period. */
+ int32_t max;
+ /*! Sum of all values passed to osmo_stat_item_set() in the reporting period. */
+ int64_t sum;
+};
+
+/*! data we keep for each actual item */
+struct osmo_stat_item {
+ /*! back-reference to the item description */
+ const struct osmo_stat_item_desc *desc;
+
+ /*! Current reporting period / current value. */
+ struct osmo_stat_item_period value;
+
+ /*! The results of the previous reporting period. According to these, the stats reporter decides whether to
+ * re-send values or omit an unchanged value from a report. */
+ struct osmo_stat_item_period reported;
+};
+
+/*! @} */
diff --git a/src/stats.c b/src/core/stats.c
index c91a9780..16e6f620 100644
--- a/src/stats.c
+++ b/src/core/stats.c
@@ -16,10 +16,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
/*! \addtogroup stats
@@ -89,14 +85,27 @@
#include <osmocom/core/select.h>
#include <osmocom/core/counter.h>
#include <osmocom/core/msgb.h>
+#include <osmocom/core/stats_tcp.h>
+
+#ifdef HAVE_SYSTEMTAP
+/* include the generated probes header and put markers in code */
+#include "probes.h"
+#define TRACE(probe) probe
+#define TRACE_ENABLED(probe) probe ## _ENABLED()
+#else
+/* Wrap the probe to allow it to be removed when no systemtap available */
+#define TRACE(probe)
+#define TRACE_ENABLED(probe) (0)
+#endif /* HAVE_SYSTEMTAP */
+
+#include <stat_item_internal.h>
#define STATS_DEFAULT_INTERVAL 5 /* secs */
#define STATS_DEFAULT_BUFLEN 256
-static LLIST_HEAD(osmo_stats_reporter_list);
+LLIST_HEAD(osmo_stats_reporter_list);
static void *osmo_stats_ctx = NULL;
static int is_initialised = 0;
-static int32_t current_stat_item_index = 0;
static struct osmo_stats_config s_stats_config = {
.interval = STATS_DEFAULT_INTERVAL,
@@ -166,7 +175,7 @@ static int osmo_stats_timer_cb(struct osmo_fd *ofd, unsigned int what)
return 0;
}
-static int start_timer()
+static int start_timer(void)
{
int rc;
int interval = osmo_stats_config->interval;
@@ -205,13 +214,15 @@ struct osmo_stats_reporter *osmo_stats_reporter_alloc(enum osmo_stats_reporter_t
{
struct osmo_stats_reporter *srep;
srep = talloc_zero(osmo_stats_ctx, struct osmo_stats_reporter);
- OSMO_ASSERT(srep);
+ if (!srep)
+ return NULL;
+
srep->type = type;
if (name)
srep->name = talloc_strdup(srep, name);
srep->fd = -1;
- llist_add(&srep->list, &osmo_stats_reporter_list);
+ llist_add_tail(&srep->list, &osmo_stats_reporter_list);
return srep;
}
@@ -225,15 +236,17 @@ void osmo_stats_reporter_free(struct osmo_stats_reporter *srep)
talloc_free(srep);
}
-/*! Initilize the stats reporting module; call this once in your program
+/*! Initialize the stats reporting module; call this once in your program.
* \param[in] ctx Talloc context from which stats related memory is allocated */
void osmo_stats_init(void *ctx)
{
osmo_stats_ctx = ctx;
- osmo_stat_item_discard_all(&current_stat_item_index);
-
is_initialised = 1;
start_timer();
+
+ /* Make sure that the tcp-stats interval timer also runs at its
+ * preconfigured rate. The vty might change this setting later. */
+ osmo_stats_tcp_set_interval(osmo_tcp_stats_config->interval);
}
/*! Find a stats_reporter of given \a type and \a name.
@@ -401,7 +414,7 @@ int osmo_stats_reporter_set_flush_period(struct osmo_stats_reporter *srep, unsig
/*! Set the name prefix of a given stats_reporter.
* \param[in] srep stats_reporter whose name prefix is to be set
- * \param[in] prefix NAme perfix to pre-pend for any reported value
+ * \param[in] prefix Name prefix to pre-pend for any reported value
* \returns 0 on success; negative on error */
int osmo_stats_reporter_set_name_prefix(struct osmo_stats_reporter *srep, const char *prefix)
{
@@ -475,6 +488,8 @@ int osmo_stats_reporter_udp_open(struct osmo_stats_reporter *srep)
}
srep->buffer = msgb_alloc(buffer_size, "stats buffer");
+ if (!srep->buffer)
+ goto failed;
return 0;
@@ -558,6 +573,8 @@ struct osmo_stats_reporter *osmo_stats_reporter_create_log(const char *name)
{
struct osmo_stats_reporter *srep;
srep = osmo_stats_reporter_alloc(OSMO_STATS_REPORTER_LOG, name);
+ if (!srep)
+ return NULL;
srep->have_net_config = 0;
@@ -683,36 +700,28 @@ static int osmo_stat_item_handler(
struct osmo_stat_item_group *statg, struct osmo_stat_item *item, void *sctx_)
{
struct osmo_stats_reporter *srep;
- int32_t idx = current_stat_item_index;
- int32_t value;
- int have_value;
-
- have_value = osmo_stat_item_get_next(item, &idx, &value) > 0;
- if (!have_value)
- /* Send the last value in case a flush is requested */
- value = osmo_stat_item_get_last(item);
-
- do {
- llist_for_each_entry(srep, &osmo_stats_reporter_list, list) {
- if (!srep->running)
- continue;
+ int32_t prev_reported_value = item->reported.max;
+ int32_t new_value = item->value.max;
- if (!have_value && !srep->force_single_flush)
- continue;
+ llist_for_each_entry(srep, &osmo_stats_reporter_list, list) {
+ if (!srep->running)
+ continue;
- if (!osmo_stats_reporter_check_config(srep,
- statg->idx, statg->desc->class_id))
- continue;
+ /* If the previously reported value is the same as the current value, skip resending the value.
+ * However, if the stats reporter is set to resend all values, do resend the current value regardless of
+ * repetitions.
+ */
+ if (new_value == prev_reported_value && !srep->force_single_flush)
+ continue;
- osmo_stats_reporter_send_item(srep, statg,
- item->desc, value);
- }
+ if (!osmo_stats_reporter_check_config(srep,
+ statg->idx, statg->desc->class_id))
+ continue;
- if (!have_value)
- break;
+ osmo_stats_reporter_send_item(srep, statg, item->desc, new_value);
+ }
- have_value = osmo_stat_item_get_next(item, &idx, &value) > 0;
- } while (have_value);
+ osmo_stat_item_flush(item);
return 0;
}
@@ -755,7 +764,7 @@ static int handle_counter(struct osmo_counter *counter, void *sctx_)
/*** main reporting function ***/
-static void flush_all_reporters()
+static void flush_all_reporters(void)
{
struct osmo_stats_reporter *srep;
@@ -778,16 +787,17 @@ static void flush_all_reporters()
}
}
-int osmo_stats_report()
+int osmo_stats_report(void)
{
/* per group actions */
+ TRACE(LIBOSMOCORE_STATS_START());
osmo_counters_for_each(handle_counter, NULL);
rate_ctr_for_each_group(rate_ctr_group_handler, NULL);
osmo_stat_item_for_each_group(osmo_stat_item_group_handler, NULL);
/* global actions */
- osmo_stat_item_discard_all(&current_stat_item_index);
flush_all_reporters();
+ TRACE(LIBOSMOCORE_STATS_DONE());
return 0;
}
diff --git a/src/stats_statsd.c b/src/core/stats_statsd.c
index d4496677..b27baff8 100644
--- a/src/stats_statsd.c
+++ b/src/core/stats_statsd.c
@@ -15,10 +15,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
/*! \addtogroup stats
@@ -32,6 +28,7 @@
#include <string.h>
#include <stdint.h>
+#include <inttypes.h>
#include <errno.h>
#include <osmocom/core/utils.h>
@@ -57,6 +54,8 @@ struct osmo_stats_reporter *osmo_stats_reporter_create_statsd(const char *name)
{
struct osmo_stats_reporter *srep;
srep = osmo_stats_reporter_alloc(OSMO_STATS_REPORTER_STATSD, name);
+ if (!srep)
+ return NULL;
srep->have_net_config = 1;
@@ -88,7 +87,7 @@ static void osmo_stats_reporter_sanitize_name(char *buf)
}
static int osmo_stats_reporter_statsd_send(struct osmo_stats_reporter *srep,
- const char *name1, unsigned int index1, const char *name2, int64_t value,
+ const char *name1, const char *index1, const char *name2, int64_t value,
const char *unit)
{
char *buf;
@@ -100,15 +99,15 @@ static int osmo_stats_reporter_statsd_send(struct osmo_stats_reporter *srep,
if (prefix) {
if (name1)
- fmt = "%1$s.%2$s.%6$u.%3$s:%4$d|%5$s";
+ fmt = "%1$s.%2$s.%6$s.%3$s:%4$" PRId64 "|%5$s";
else
- fmt = "%1$s.%2$0.0s%3$s:%4$d|%5$s";
+ fmt = "%1$s.%2$0.0s%3$s:%4$" PRId64 "|%5$s";
} else {
prefix = "";
if (name1)
- fmt = "%1$s%2$s.%6$u.%3$s:%4$d|%5$s";
+ fmt = "%1$s%2$s.%6$s.%3$s:%4$" PRId64 "|%5$s";
else
- fmt = "%1$s%2$0.0s%3$s:%4$d|%5$s";
+ fmt = "%1$s%2$0.0s%3$s:%4$" PRId64 "|%5$s";
}
if (srep->agg_enabled) {
@@ -161,32 +160,42 @@ static int osmo_stats_reporter_statsd_send_counter(struct osmo_stats_reporter *s
const struct rate_ctr_desc *desc,
int64_t value, int64_t delta)
{
- if (ctrg)
- return osmo_stats_reporter_statsd_send(srep,
- ctrg->desc->group_name_prefix,
- ctrg->idx,
- desc->name, delta, "c");
- else
- return osmo_stats_reporter_statsd_send(srep,
- NULL, 0,
- desc->name, delta, "c");
+ char buf_idx[64];
+ const char *idx_name = buf_idx;
+ const char *prefix;
+
+ if (ctrg) {
+ prefix = ctrg->desc->group_name_prefix;
+ if (ctrg->name)
+ idx_name = ctrg->name;
+ else
+ snprintf(buf_idx, sizeof(buf_idx), "%u", ctrg->idx);
+ } else {
+ prefix = NULL;
+ buf_idx[0] = '0';
+ buf_idx[1] = '\n';
+ }
+ return osmo_stats_reporter_statsd_send(srep, prefix, idx_name, desc->name, delta, "c");
}
static int osmo_stats_reporter_statsd_send_item(struct osmo_stats_reporter *srep,
const struct osmo_stat_item_group *statg,
const struct osmo_stat_item_desc *desc, int64_t value)
{
- if (value < 0) {
- return osmo_stats_reporter_statsd_send(srep,
- statg->desc->group_name_prefix,
- statg->idx,
- desc->name, 0, "g");
- } else {
- return osmo_stats_reporter_statsd_send(srep,
- statg->desc->group_name_prefix,
- statg->idx,
- desc->name, value, "g");
+ char buf_idx[64];
+ char *idx_name;
+ if (statg->name)
+ idx_name = statg->name;
+ else {
+ snprintf(buf_idx, sizeof(buf_idx), "%u", statg->idx);
+ idx_name = buf_idx;
}
+
+ if (value < 0)
+ value = 0;
+
+ return osmo_stats_reporter_statsd_send(srep, statg->desc->group_name_prefix,
+ idx_name, desc->name, value, "g");
}
#endif /* !EMBEDDED */
diff --git a/src/core/stats_tcp.c b/src/core/stats_tcp.c
new file mode 100644
index 00000000..ebb380e8
--- /dev/null
+++ b/src/core/stats_tcp.c
@@ -0,0 +1,325 @@
+/*
+ * (C) 2021 by sysmocom - s.f.m.c. GmbH
+ * Author: Philipp Maier <pmaier@sysmocom.de>
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+/*! \addtogroup stats
+ * @{
+ * \file stats_tcp.c */
+
+#include "config.h"
+#if !defined(EMBEDDED)
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <linux/tcp.h>
+#include <errno.h>
+#include <pthread.h>
+
+#include <osmocom/core/select.h>
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/core/stat_item.h>
+#include <osmocom/core/stats.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/core/stats_tcp.h>
+
+static struct osmo_tcp_stats_config s_tcp_stats_config = {
+ .interval = TCP_STATS_DEFAULT_INTERVAL,
+};
+
+struct osmo_tcp_stats_config *osmo_tcp_stats_config = &s_tcp_stats_config;
+
+static struct osmo_timer_list stats_tcp_poll_timer;
+
+static LLIST_HEAD(stats_tcp);
+static struct stats_tcp_entry *stats_tcp_entry_cur;
+pthread_mutex_t stats_tcp_lock;
+
+struct stats_tcp_entry {
+ struct llist_head entry;
+ const struct osmo_fd *fd;
+ struct osmo_stat_item_group *stats_tcp;
+ const char *name;
+};
+
+enum {
+ STATS_TCP_UNACKED,
+ STATS_TCP_LOST,
+ STATS_TCP_RETRANS,
+ STATS_TCP_RTT,
+ STATS_TCP_RCV_RTT,
+ STATS_TCP_NOTSENT_BYTES,
+ STATS_TCP_RWND_LIMITED,
+ STATS_TCP_SNDBUF_LIMITED,
+ STATS_TCP_REORD_SEEN,
+};
+
+static struct osmo_stat_item_desc stats_tcp_item_desc[] = {
+ [STATS_TCP_UNACKED] = { "tcp:unacked", "unacknowledged packets", "", 60, 0 },
+ [STATS_TCP_LOST] = { "tcp:lost", "lost packets", "", 60, 0 },
+ [STATS_TCP_RETRANS] = { "tcp:retrans", "retransmitted packets", "", 60, 0 },
+ [STATS_TCP_RTT] = { "tcp:rtt", "roundtrip-time", "", 60, 0 },
+ [STATS_TCP_RCV_RTT] = { "tcp:rcv_rtt", "roundtrip-time (receive)", "", 60, 0 },
+ [STATS_TCP_NOTSENT_BYTES] = { "tcp:notsent_bytes", "bytes not yet sent", "", 60, 0 },
+ [STATS_TCP_RWND_LIMITED] = { "tcp:rwnd_limited", "time (usec) limited by receive window", "", 60, 0 },
+ [STATS_TCP_SNDBUF_LIMITED] = { "tcp:sndbuf_limited", "Time (usec) limited by send buffer", "", 60, 0 },
+ [STATS_TCP_REORD_SEEN] = { "tcp:reord_seen", "reordering events seen", "", 60, 0 },
+};
+
+static struct osmo_stat_item_group_desc stats_tcp_desc = {
+ .group_name_prefix = "tcp",
+ .group_description = "stats tcp",
+ .class_id = OSMO_STATS_CLASS_GLOBAL,
+ .num_items = ARRAY_SIZE(stats_tcp_item_desc),
+ .item_desc = stats_tcp_item_desc,
+};
+
+static void fill_stats(struct stats_tcp_entry *stats_tcp_entry)
+{
+ int rc;
+ struct tcp_info tcp_info;
+ socklen_t tcp_info_len = sizeof(tcp_info);
+ char stat_name[256];
+
+ /* Do not fill in anything before the socket is connected to a remote end */
+ if (osmo_sock_get_ip_and_port(stats_tcp_entry->fd->fd, NULL, 0, NULL, 0, false) != 0)
+ return;
+
+ /* Gather TCP statistics and update the stats items */
+ rc = getsockopt(stats_tcp_entry->fd->fd, IPPROTO_TCP, TCP_INFO, &tcp_info, &tcp_info_len);
+ if (rc < 0)
+ return;
+
+ /* Create stats items if they do not exist yet */
+ if (!stats_tcp_entry->stats_tcp) {
+ stats_tcp_entry->stats_tcp =
+ osmo_stat_item_group_alloc(stats_tcp_entry, &stats_tcp_desc, stats_tcp_entry->fd->fd);
+ OSMO_ASSERT(stats_tcp_entry->stats_tcp);
+ }
+
+ /* Update statistics */
+ if (stats_tcp_entry->name)
+ snprintf(stat_name, sizeof(stat_name), "%s", stats_tcp_entry->name);
+ else
+ snprintf(stat_name, sizeof(stat_name), "%s", osmo_sock_get_name2(stats_tcp_entry->fd->fd));
+ osmo_stat_item_group_set_name(stats_tcp_entry->stats_tcp, stat_name);
+
+ osmo_stat_item_set(osmo_stat_item_group_get_item(stats_tcp_entry->stats_tcp, STATS_TCP_UNACKED),
+ tcp_info.tcpi_unacked);
+ osmo_stat_item_set(osmo_stat_item_group_get_item(stats_tcp_entry->stats_tcp, STATS_TCP_LOST),
+ tcp_info.tcpi_lost);
+ osmo_stat_item_set(osmo_stat_item_group_get_item(stats_tcp_entry->stats_tcp, STATS_TCP_RETRANS),
+ tcp_info.tcpi_retrans);
+ osmo_stat_item_set(osmo_stat_item_group_get_item(stats_tcp_entry->stats_tcp, STATS_TCP_RTT), tcp_info.tcpi_rtt);
+ osmo_stat_item_set(osmo_stat_item_group_get_item(stats_tcp_entry->stats_tcp, STATS_TCP_RCV_RTT),
+ tcp_info.tcpi_rcv_rtt);
+#if HAVE_TCP_INFO_TCPI_NOTSENT_BYTES == 1
+ osmo_stat_item_set(osmo_stat_item_group_get_item(stats_tcp_entry->stats_tcp, STATS_TCP_NOTSENT_BYTES),
+ tcp_info.tcpi_notsent_bytes);
+#else
+ osmo_stat_item_set(osmo_stat_item_group_get_item(stats_tcp_entry->stats_tcp, STATS_TCP_NOTSENT_BYTES), -1);
+#endif
+
+#if HAVE_TCP_INFO_TCPI_RWND_LIMITED == 1
+ osmo_stat_item_set(osmo_stat_item_group_get_item(stats_tcp_entry->stats_tcp, STATS_TCP_RWND_LIMITED),
+ tcp_info.tcpi_rwnd_limited);
+#else
+ osmo_stat_item_set(osmo_stat_item_group_get_item(stats_tcp_entry->stats_tcp, STATS_TCP_RWND_LIMITED), -1);
+#endif
+
+#if STATS_TCP_SNDBUF_LIMITED == 1
+ osmo_stat_item_set(osmo_stat_item_group_get_item(stats_tcp_entry->stats_tcp, STATS_TCP_REORD_SEEN),
+ tcp_info.tcpi_sndbuf_limited);
+#else
+ osmo_stat_item_set(osmo_stat_item_group_get_item(stats_tcp_entry->stats_tcp, STATS_TCP_REORD_SEEN), -1);
+#endif
+
+#if HAVE_TCP_INFO_TCPI_REORD_SEEN == 1
+ osmo_stat_item_set(osmo_stat_item_group_get_item(stats_tcp_entry->stats_tcp, STATS_TCP_REORD_SEEN),
+ tcp_info.tcpi_reord_seen);
+#else
+ osmo_stat_item_set(osmo_stat_item_group_get_item(stats_tcp_entry->stats_tcp, STATS_TCP_REORD_SEEN), -1);
+#endif
+
+}
+
+static bool is_tcp(const struct osmo_fd *fd)
+{
+ int rc;
+ struct stat fd_stat;
+ int so_protocol = 0;
+ socklen_t so_protocol_len = sizeof(so_protocol);
+
+ /* Is this a socket? */
+ rc = fstat(fd->fd, &fd_stat);
+ if (rc < 0)
+ return false;
+ if (!S_ISSOCK(fd_stat.st_mode))
+ return false;
+
+ /* Is it a TCP socket? */
+ rc = getsockopt(fd->fd, SOL_SOCKET, SO_PROTOCOL, &so_protocol, &so_protocol_len);
+ if (rc < 0)
+ return false;
+ if (so_protocol == IPPROTO_TCP)
+ return true;
+
+ return false;
+}
+
+/*! Register an osmo_fd for TCP stats monitoring.
+ * \param[in] fd osmocom file descriptor to be registered.
+ * \param[in] human readbla name that is used as prefix for the related stats item.
+ * \returns 0 on success; negative in case of error. */
+int osmo_stats_tcp_osmo_fd_register(const struct osmo_fd *fd, const char *name)
+{
+ struct stats_tcp_entry *stats_tcp_entry;
+
+ /* Only TCP sockets can be registered for monitoring, anything else will fall through. */
+ if (!is_tcp(fd))
+ return -EINVAL;
+
+ /* When the osmo_fd is registered and unregistered properly there shouldn't be any leftovers from already closed
+ * osmo_fds in the stats_tcp list. But lets proactively make sure that any leftovers are cleaned up. */
+ osmo_stats_tcp_osmo_fd_unregister(fd);
+
+ /* Make a new list object, attach the osmo_fd... */
+ stats_tcp_entry = talloc_zero(OTC_GLOBAL, struct stats_tcp_entry);
+ OSMO_ASSERT(stats_tcp_entry);
+ stats_tcp_entry->fd = fd;
+ stats_tcp_entry->name = talloc_strdup(stats_tcp_entry, name);
+
+ pthread_mutex_lock(&stats_tcp_lock);
+ llist_add_tail(&stats_tcp_entry->entry, &stats_tcp);
+ pthread_mutex_unlock(&stats_tcp_lock);
+
+ return 0;
+}
+
+static void next_stats_tcp_entry(void)
+{
+ struct stats_tcp_entry *last;
+
+ if (llist_empty(&stats_tcp)) {
+ stats_tcp_entry_cur = NULL;
+ return;
+ }
+
+ last = (struct stats_tcp_entry *)llist_last_entry(&stats_tcp, struct stats_tcp_entry, entry);
+
+ if (!stats_tcp_entry_cur || stats_tcp_entry_cur == last)
+ stats_tcp_entry_cur =
+ (struct stats_tcp_entry *)llist_first_entry(&stats_tcp, struct stats_tcp_entry, entry);
+ else
+ stats_tcp_entry_cur =
+ (struct stats_tcp_entry *)llist_entry(stats_tcp_entry_cur->entry.next, struct stats_tcp_entry,
+ entry);
+}
+
+/*! Register an osmo_fd for TCP stats monitoring.
+ * \param[in] fd osmocom file descriptor to be unregistered.
+ * \returns 0 on success; negative in case of error. */
+int osmo_stats_tcp_osmo_fd_unregister(const struct osmo_fd *fd)
+{
+ struct stats_tcp_entry *stats_tcp_entry;
+ int rc = -EINVAL;
+
+ pthread_mutex_lock(&stats_tcp_lock);
+ llist_for_each_entry(stats_tcp_entry, &stats_tcp, entry) {
+ if (fd->fd == stats_tcp_entry->fd->fd) {
+ /* In case we want to remove exactly that item which is also selected as the current itemy, we
+ * must designate either a different item or invalidate the current item. */
+ if (stats_tcp_entry == stats_tcp_entry_cur) {
+ if (llist_count(&stats_tcp) > 2)
+ next_stats_tcp_entry();
+ else
+ stats_tcp_entry_cur = NULL;
+ }
+
+ /* Date item from list */
+ llist_del(&stats_tcp_entry->entry);
+ osmo_stat_item_group_free(stats_tcp_entry->stats_tcp);
+ talloc_free(stats_tcp_entry);
+ rc = 0;
+ break;
+ }
+ }
+ pthread_mutex_unlock(&stats_tcp_lock);
+
+ return rc;
+}
+
+static void stats_tcp_poll_timer_cb(void *data)
+{
+ int i;
+ int batch_size;
+ int llist_size;
+
+ pthread_mutex_lock(&stats_tcp_lock);
+
+ /* Make sure we do not run over the same sockets multiple times if the
+ * configured llist_size is larger then the actual list */
+ batch_size = osmo_tcp_stats_config->batch_size;
+ llist_size = llist_count(&stats_tcp);
+ if (llist_size < batch_size)
+ batch_size = llist_size;
+
+ /* Process a batch of sockets */
+ for (i = 0; i < batch_size; i++) {
+ next_stats_tcp_entry();
+ if (stats_tcp_entry_cur)
+ fill_stats(stats_tcp_entry_cur);
+ }
+
+ pthread_mutex_unlock(&stats_tcp_lock);
+
+ if (osmo_tcp_stats_config->interval > 0)
+ osmo_timer_schedule(&stats_tcp_poll_timer, osmo_tcp_stats_config->interval, 0);
+}
+
+/*! Set the polling interval (common for all sockets)
+ * \param[in] interval Poll interval in seconds
+ * \returns 0 on success; negative on error */
+int osmo_stats_tcp_set_interval(int interval)
+{
+ osmo_tcp_stats_config->interval = interval;
+ if (osmo_tcp_stats_config->interval > 0)
+ osmo_timer_schedule(&stats_tcp_poll_timer, osmo_tcp_stats_config->interval, 0);
+ return 0;
+}
+
+static __attribute__((constructor))
+void on_dso_load_stats_tcp(void)
+{
+ stats_tcp_entry_cur = NULL;
+ pthread_mutex_init(&stats_tcp_lock, NULL);
+
+ osmo_tcp_stats_config->interval = TCP_STATS_DEFAULT_INTERVAL;
+ osmo_tcp_stats_config->batch_size = TCP_STATS_DEFAULT_BATCH_SIZE;
+
+ osmo_timer_setup(&stats_tcp_poll_timer, stats_tcp_poll_timer_cb, NULL);
+}
+
+#endif /* !EMBEDDED */
+
+/* @} */
diff --git a/src/strrb.c b/src/core/strrb.c
index 461fdecc..df7edb31 100644
--- a/src/strrb.c
+++ b/src/core/strrb.c
@@ -31,10 +31,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
/*! \addtogroup utils
diff --git a/src/tdef.c b/src/core/tdef.c
index 71a33158..abbe581a 100644
--- a/src/tdef.c
+++ b/src/core/tdef.c
@@ -52,18 +52,18 @@
* By keeping separate osmo_tdef arrays, several groups of timers can be kept
* separately. The VTY tests in tests/tdef/ showcase different schemes:
*
- * - \ref tests/vty/tdef_vty_test_config_root.c:
+ * - \ref tests/tdef/tdef_vty_config_root_test.c:
* Keep several timer definitions in separately named groups: showcase the
* osmo_tdef_vty_groups*() API. Each timer group exists exactly once.
*
- * - \ref tests/vty/tdef_vty_test_config_subnode.c:
+ * - \ref tests/tdef/tdef_vty_config_subnode_test.c:
* Keep a single list of timers without separate grouping.
* Put this list on a specific subnode below the CONFIG_NODE.
* There could be several separate subnodes with timers like this, i.e.
* continuing from this example, sets of timers could be separated by placing
* timers in specific config subnodes instead of using the global group name.
*
- * - \ref tests/vty/tdef_vty_test_dynamic.c:
+ * - \ref tests/tdef/tdef_vty_dynamic_test.c:
* Dynamically allocate timer definitions per each new created object.
* Thus there can be an arbitrary number of independent timer definitions, one
* per allocated object.
@@ -93,6 +93,17 @@ static unsigned long osmo_tdef_factor(enum osmo_tdef_unit a, enum osmo_tdef_unit
return 1;
switch (b) {
+ case OSMO_TDEF_US:
+ switch (a) {
+ case OSMO_TDEF_MS:
+ return 1000;
+ case OSMO_TDEF_S:
+ return 1000*1000;
+ case OSMO_TDEF_M:
+ return 60*1000*1000;
+ default:
+ return 0;
+ }
case OSMO_TDEF_MS:
switch (a) {
case OSMO_TDEF_S:
@@ -189,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)
{
@@ -320,7 +333,7 @@ const struct osmo_tdef_state_timeout *osmo_tdef_get_state_timeout(uint32_t state
*/
int _osmo_tdef_fsm_inst_state_chg(struct osmo_fsm_inst *fi, uint32_t state,
const struct osmo_tdef_state_timeout *timeouts_array,
- const struct osmo_tdef *tdefs, unsigned long default_timeout,
+ const struct osmo_tdef *tdefs, long default_timeout,
const char *file, int line)
{
const struct osmo_tdef_state_timeout *t = osmo_tdef_get_state_timeout(state, timeouts_array);
@@ -351,6 +364,7 @@ const struct value_string osmo_tdef_unit_names[] = {
{ OSMO_TDEF_MS, "ms" },
{ OSMO_TDEF_M, "m" },
{ OSMO_TDEF_CUSTOM, "custom-unit" },
+ { OSMO_TDEF_US, "us" },
{}
};
diff --git a/src/core/thread.c b/src/core/thread.c
new file mode 100644
index 00000000..d9a98422
--- /dev/null
+++ b/src/core/thread.c
@@ -0,0 +1,56 @@
+/*
+ * (C) 2021 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Pau Espin Pedrol <pespin@sysmocom.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+/*! \addtogroup thread
+ * @{
+ * \file thread.c
+ */
+
+/*! \file thread.c
+ */
+
+#include "config.h"
+
+/* If HAVE_GETTID, then "_GNU_SOURCE" may need to be defined to use gettid() */
+#if HAVE_GETTID
+#define _GNU_SOURCE
+#endif
+#include <unistd.h>
+#include <sys/types.h>
+
+#include <osmocom/core/thread.h>
+
+/*! Wrapper around Linux's gettid() to make it easily accessible on different system versions.
+ * If the gettid() API cannot be found, it will use the syscall directly if
+ * available. If no syscall is found available, then getpid() is called as
+ * fallback. See 'man 2 gettid' for further and details information.
+ * \returns This call is always successful and returns returns the thread ID of
+ * the calling thread (or the process ID of the current process if
+ * gettid() or its syscall are unavailable in the system).
+ */
+pid_t osmo_gettid(void)
+{
+#if HAVE_GETTID
+ return gettid();
+#elif defined(LINUX) && defined(__NR_gettid)
+ return (pid_t) syscall(__NR_gettid);
+#else
+ #pragma message ("use pid as tid")
+ return getpid();
+#endif
+}
diff --git a/src/core/time_cc.c b/src/core/time_cc.c
new file mode 100644
index 00000000..0e6879e5
--- /dev/null
+++ b/src/core/time_cc.c
@@ -0,0 +1,228 @@
+/*! \file foo.c
+ * Report the cumulative counter of time for which a flag is true as rate counter.
+ */
+/* Copyright (C) 2021 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * Author: Neels Hofmeyr <nhofmeyr@sysmocom.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+/*! \addtogroup time_cc
+ *
+ * Report the cumulative counter of time for which a flag is true as rate counter.
+ *
+ * Useful for reporting cumulative time counters as defined in 3GPP TS 52.402, for example allAvailableSDCCHAllocated,
+ * allAvailableTCHAllocated, availablePDCHAllocatedTime.
+ *
+ * For a usage example, see the description of struct osmo_time_cc.
+ *
+ * @{
+ * \file time_cc.c
+ */
+#include "config.h"
+#ifdef HAVE_CLOCK_GETTIME
+
+#include <limits.h>
+#include <time.h>
+
+#include <osmocom/core/tdef.h>
+#include <osmocom/core/rate_ctr.h>
+#include <osmocom/core/time_cc.h>
+
+#define GRAN_USEC(TIME_CC) ((TIME_CC)->cfg.gran_usec ? : 1000000)
+#define ROUND_THRESHOLD_USEC(TIME_CC) ((TIME_CC)->cfg.round_threshold_usec ? \
+ OSMO_MIN((TIME_CC)->cfg.round_threshold_usec, GRAN_USEC(TIME_CC)) \
+ : (GRAN_USEC(TIME_CC) / 2))
+
+static uint64_t time_now_usec(void)
+{
+ struct timespec tp;
+ if (osmo_clock_gettime(CLOCK_MONOTONIC, &tp))
+ return 0;
+ return (uint64_t)tp.tv_sec * 1000000 + tp.tv_nsec / 1000;
+}
+
+static void osmo_time_cc_forget_sum(struct osmo_time_cc *tc, uint64_t now);
+
+static void osmo_time_cc_update_from_tdef(struct osmo_time_cc *tc, uint64_t now)
+{
+ bool do_forget_sum = false;
+ if (!tc->cfg.T_defs)
+ return;
+ if (tc->cfg.T_gran) {
+ uint64_t was = GRAN_USEC(tc);
+ tc->cfg.gran_usec = osmo_tdef_get(tc->cfg.T_defs, tc->cfg.T_gran, OSMO_TDEF_US, -1);
+ if (was != GRAN_USEC(tc))
+ do_forget_sum = true;
+ }
+ if (tc->cfg.T_round_threshold)
+ tc->cfg.round_threshold_usec = osmo_tdef_get(tc->cfg.T_defs, tc->cfg.T_round_threshold,
+ OSMO_TDEF_US, -1);
+ if (tc->cfg.T_forget_sum) {
+ uint64_t was = tc->cfg.forget_sum_usec;
+ tc->cfg.forget_sum_usec = osmo_tdef_get(tc->cfg.T_defs, tc->cfg.T_forget_sum, OSMO_TDEF_US, -1);
+ if (tc->cfg.forget_sum_usec && was != tc->cfg.forget_sum_usec)
+ do_forget_sum = true;
+ }
+
+ if (do_forget_sum && tc->sum)
+ osmo_time_cc_forget_sum(tc, now);
+}
+
+static void osmo_time_cc_schedule_timer(struct osmo_time_cc *tc, uint64_t now);
+
+/*! Clear out osmo_timer and internal counting state of struct osmo_time_cc. The .cfg remains unaffected. After calling,
+ * the osmo_time_cc instance can be used again to accumulate state as if it had just been initialized. */
+void osmo_time_cc_cleanup(struct osmo_time_cc *tc)
+{
+ osmo_timer_del(&tc->timer);
+ *tc = (struct osmo_time_cc){
+ .cfg = tc->cfg,
+ };
+}
+
+static void osmo_time_cc_start(struct osmo_time_cc *tc, uint64_t now)
+{
+ osmo_time_cc_cleanup(tc);
+ tc->start_time = now;
+ tc->last_counted_time = now;
+ osmo_time_cc_update_from_tdef(tc, now);
+ osmo_time_cc_schedule_timer(tc, now);
+}
+
+static void osmo_time_cc_count_time(struct osmo_time_cc *tc, uint64_t now)
+{
+ uint64_t time_delta = now - tc->last_counted_time;
+ tc->last_counted_time = now;
+ if (!tc->flag_state)
+ return;
+ /* Flag is currently true, cumulate the elapsed time */
+ tc->total_sum += time_delta;
+ tc->sum += time_delta;
+}
+
+static void osmo_time_cc_report(struct osmo_time_cc *tc, uint64_t now)
+{
+ uint64_t delta;
+ uint64_t n;
+ /* We report a sum "rounded up", ahead of time. If the granularity period has not yet elapsed after the last
+ * reporting, do not report again yet. */
+ if (tc->reported_sum > tc->sum)
+ return;
+ delta = tc->sum - tc->reported_sum;
+ /* elapsed full periods */
+ n = delta / GRAN_USEC(tc);
+ /* If the delta has passed round_threshold (normally half of gran_usec), increment. */
+ delta -= n * GRAN_USEC(tc);
+ if (delta >= ROUND_THRESHOLD_USEC(tc))
+ n++;
+ if (!n)
+ return;
+
+ /* integer sanity, since rate_ctr_add() takes an int argument. */
+ if (n > INT_MAX)
+ n = INT_MAX;
+ if (tc->cfg.rate_ctr)
+ rate_ctr_add(tc->cfg.rate_ctr, n);
+ /* Store the increments of gran_usec that were counted. */
+ tc->reported_sum += n * GRAN_USEC(tc);
+}
+
+static void osmo_time_cc_forget_sum(struct osmo_time_cc *tc, uint64_t now)
+{
+ tc->reported_sum = 0;
+ tc->sum = 0;
+
+ if (tc->last_counted_time < now)
+ tc->last_counted_time = now;
+}
+
+/*! Initialize struct osmo_time_cc. Call this once before use, and before setting up the .cfg items. */
+void osmo_time_cc_init(struct osmo_time_cc *tc)
+{
+ *tc = (struct osmo_time_cc){0};
+}
+
+/*! Report state to be recorded by osmo_time_cc instance. Setting an unchanged state repeatedly has no effect. */
+void osmo_time_cc_set_flag(struct osmo_time_cc *tc, bool flag)
+{
+ uint64_t now = time_now_usec();
+ if (!tc->start_time)
+ osmo_time_cc_start(tc, now);
+ /* No flag change == no effect */
+ if (flag == tc->flag_state)
+ return;
+ /* Sum up elapsed time, report increments for that. */
+ osmo_time_cc_count_time(tc, now);
+ osmo_time_cc_report(tc, now);
+ tc->flag_state = flag;
+ osmo_time_cc_schedule_timer(tc, now);
+}
+
+static void osmo_time_cc_timer_cb(void *data)
+{
+ struct osmo_time_cc *tc = data;
+ uint64_t now = time_now_usec();
+
+ osmo_time_cc_update_from_tdef(tc, now);
+
+ if (tc->flag_state) {
+ osmo_time_cc_count_time(tc, now);
+ osmo_time_cc_report(tc, now);
+ } else if (tc->cfg.forget_sum_usec && tc->sum
+ && (now >= tc->last_counted_time + tc->cfg.forget_sum_usec)) {
+ osmo_time_cc_forget_sum(tc, now);
+ }
+ osmo_time_cc_schedule_timer(tc, now);
+}
+
+/*! Figure out the next time we should do anything, if the flag state remains unchanged. */
+static void osmo_time_cc_schedule_timer(struct osmo_time_cc *tc, uint64_t now)
+{
+ uint64_t next_event = UINT64_MAX;
+
+ osmo_time_cc_update_from_tdef(tc, now);
+
+ /* If it is required, when will the next forget_sum happen? */
+ if (tc->cfg.forget_sum_usec && !tc->flag_state && tc->sum > 0) {
+ uint64_t next_forget_time = tc->last_counted_time + tc->cfg.forget_sum_usec;
+ next_event = OSMO_MIN(next_event, next_forget_time);
+ }
+ /* Next rate_ctr increment? */
+ if (tc->flag_state) {
+ uint64_t next_inc = now + (tc->reported_sum - tc->sum) + ROUND_THRESHOLD_USEC(tc);
+ next_event = OSMO_MIN(next_event, next_inc);
+ }
+
+ /* No event coming up? */
+ if (next_event == UINT64_MAX)
+ return;
+
+ if (next_event <= now)
+ next_event = 0;
+ else
+ next_event -= now;
+
+ osmo_timer_setup(&tc->timer, osmo_time_cc_timer_cb, tc);
+ osmo_timer_del(&tc->timer);
+ osmo_timer_schedule(&tc->timer, next_event / 1000000, next_event % 1000000);
+}
+
+#endif /* HAVE_CLOCK_GETTIME */
+
+/*! @} */
diff --git a/src/timer.c b/src/core/timer.c
index d3129a73..20d87a05 100644
--- a/src/timer.c
+++ b/src/core/timer.c
@@ -19,10 +19,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
@@ -135,7 +131,7 @@ void osmo_timer_del(struct osmo_timer_list *timer)
* This function can be used to determine whether a given timer
* has alredy expired (returns 0) or is still pending (returns 1)
*/
-int osmo_timer_pending(struct osmo_timer_list *timer)
+int osmo_timer_pending(const struct osmo_timer_list *timer)
{
return timer->active;
}
@@ -184,6 +180,22 @@ struct timeval *osmo_timers_nearest(void)
return nearest_p;
}
+/*! Determine time between now and the nearest timer in milliseconds
+ * \returns number of milliseconds until nearest timer expires; -1 if no timers pending
+ */
+int osmo_timers_nearest_ms(void)
+{
+ int nearest_ms;
+
+ if (!nearest_p)
+ return -1;
+
+ nearest_ms = nearest_p->tv_sec * 1000;
+ nearest_ms += nearest_p->tv_usec / 1000;
+
+ return nearest_ms;
+}
+
static void update_nearest(struct timeval *cand, struct timeval *current)
{
if (cand->tv_sec != LONG_MAX) {
diff --git a/src/timer_clockgettime.c b/src/core/timer_clockgettime.c
index 7b17fd11..6112b8a5 100644
--- a/src/timer_clockgettime.c
+++ b/src/core/timer_clockgettime.c
@@ -14,10 +14,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
/*! \addtogroup timer
diff --git a/src/timer_gettimeofday.c b/src/core/timer_gettimeofday.c
index 34949465..e0212b5a 100644
--- a/src/timer_gettimeofday.c
+++ b/src/core/timer_gettimeofday.c
@@ -15,10 +15,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
/*! \addtogroup timer
diff --git a/src/core/tun.c b/src/core/tun.c
new file mode 100644
index 00000000..4c9dec1c
--- /dev/null
+++ b/src/core/tun.c
@@ -0,0 +1,572 @@
+
+/* TUN interface functions.
+ * (C) 2023 by sysmocom - s.m.f.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;
+
+ osmo_fd_register(&tundev->wqueue.bfd);
+ tundev->opened = true;
+ return 0;
+
+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 738cc5da..9714403d 100644
--- a/src/use_count.c
+++ b/src/core/use_count.c
@@ -19,10 +19,6 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <errno.h>
@@ -127,6 +123,9 @@ int osmo_use_count_to_str_buf(char *buf, size_t buf_len, const struct osmo_use_c
OSMO_STRBUF_PRINTF(sb, "%" PRId32 " (", count);
+ if (!uc->use_counts.next)
+ goto uninitialized;
+
first = true;
llist_for_each_entry(e, &uc->use_counts, entry) {
if (!e->count)
@@ -140,6 +139,8 @@ int osmo_use_count_to_str_buf(char *buf, size_t buf_len, const struct osmo_use_c
}
if (first)
OSMO_STRBUF_PRINTF(sb, "-");
+
+uninitialized:
OSMO_STRBUF_PRINTF(sb, ")");
return sb.chars_needed;
}
@@ -234,6 +235,8 @@ int _osmo_use_count_get_put(struct osmo_use_count *uc, const char *use, int32_t
{
struct osmo_use_count_entry *e;
int32_t old_use_count;
+ if (!uc)
+ return -EINVAL;
if (!change)
return 0;
diff --git a/src/utils.c b/src/core/utils.c
index 3c4a8c9f..231b65c9 100644
--- a/src/utils.c
+++ b/src/core/utils.c
@@ -17,10 +17,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
@@ -30,6 +26,7 @@
#include <errno.h>
#include <stdio.h>
#include <inttypes.h>
+#include <limits.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/bit64gen.h>
@@ -205,7 +202,7 @@ int osmo_str2bcd(uint8_t *dst, size_t dst_size, const char *digits, int start_ni
if (end_nibble & 1)
end_nibble++;
}
- if ((end_nibble / 2) > dst_size)
+ if ((unsigned int) (end_nibble / 2) > dst_size)
return -ENOMEM;
for (nibble_i = start_nibble; nibble_i < end_nibble; nibble_i++) {
@@ -240,7 +237,7 @@ int osmo_str2bcd(uint8_t *dst, size_t dst_size, const char *digits, int start_ni
* \param[in] max_len maximum space in output buffer
* \returns number of parsed octets, or -1 on error
*/
-int osmo_hexparse(const char *str, uint8_t *b, int max_len)
+int osmo_hexparse(const char *str, uint8_t *b, unsigned int max_len)
{
char c;
@@ -315,7 +312,7 @@ const char *osmo_hexdump_buf(char *out_buf, size_t out_buf_size, const unsigned
for (i = 0; i < len; i++) {
const char *delimp = delim;
int len_remain = out_buf_size - (cur - out_buf) - 1;
- if (len_remain < (2 + delim_len)
+ if (len_remain < (int) (2 + delim_len)
&& !(!delim_after_last && i == (len - 1) && len_remain >= 2))
break;
@@ -343,7 +340,7 @@ const char *osmo_hexdump_buf(char *out_buf, size_t out_buf_size, const unsigned
*/
char *osmo_ubit_dump_buf(char *buf, size_t buf_len, const uint8_t *bits, unsigned int len)
{
- int i;
+ unsigned int i;
if (len > buf_len-1)
len = buf_len-1;
@@ -406,9 +403,6 @@ char *osmo_hexdump(const unsigned char *buf, int len)
*
* This function will print a sequence of bytes as hexadecimal numbers,
* adding one space character between each byte (e.g. "1a ef d9")
- *
- * The maximum size of the output buffer is 4096 bytes, i.e. the maximum
- * number of input bytes that can be printed in one call is 1365!
*/
char *osmo_hexdump_c(const void *ctx, const unsigned char *buf, int len)
{
@@ -445,9 +439,6 @@ char *osmo_hexdump_nospc(const unsigned char *buf, int len)
*
* This function will print a sequence of bytes as hexadecimal numbers,
* without any space character between each byte (e.g. "1aefd9")
- *
- * The maximum size of the output buffer is 4096 bytes, i.e. the maximum
- * number of input bytes that can be printed in one call is 2048!
*/
char *osmo_hexdump_nospc_c(const void *ctx, const unsigned char *buf, int len)
{
@@ -468,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
@@ -545,7 +536,7 @@ uint64_t osmo_decode_big_endian(const uint8_t *data, size_t data_len)
/*! Generic big-endian encoding of big endian number up to 64bit
* \param[in] value unsigned integer value to be stored
- * \param[in] data_len number of octets
+ * \param[in] data_len number of octets
* \returns static buffer containing big-endian stored value
*
* This is like osmo_store64be_ext, except that this returns a static buffer of
@@ -576,8 +567,8 @@ size_t osmo_strlcpy(char *dst, const char *src, size_t siz)
size_t ret = src ? strlen(src) : 0;
if (siz) {
- size_t len = (ret >= siz) ? siz - 1 : ret;
- if (src)
+ size_t len = OSMO_MIN(siz - 1, ret);
+ if (len)
memcpy(dst, src, len);
dst[len] = '\0';
}
@@ -686,7 +677,7 @@ bool osmo_identifier_valid(const char *str)
* To guarantee passing osmo_separated_identifiers_valid(), replace_with must not itself be an illegal character. If in
* doubt, use '-'.
* \param[inout] str Identifier to sanitize, must be nul terminated and in a writable buffer.
- * \param[in] sep_chars Additional characters that are allowed besides osmo_identifier_illegal_chars.
+ * \param[in] sep_chars Additional characters that are to be replaced besides osmo_identifier_illegal_chars.
* \param[in] replace_with Replace any illegal characters with this character.
*/
void osmo_identifier_sanitize_buf(char *str, const char *sep_chars, char replace_with)
@@ -763,7 +754,7 @@ int osmo_print_n(char *buf, size_t bufsize, const char *str, size_t n)
* the non-legacy format also escapes those as "\xNN" sequences.
* \return Number of characters that would be written if bufsize were large enough excluding '\0' (like snprintf()).
*/
-static size_t _osmo_escape_str_buf(char *buf, size_t bufsize, const char *str, int in_len, bool legacy_format)
+static int _osmo_escape_str_buf(char *buf, size_t bufsize, const char *str, int in_len, bool legacy_format)
{
struct osmo_strbuf sb = { .buf = buf, .len = bufsize };
int in_pos = 0;
@@ -835,6 +826,18 @@ done:
* \param[in] bufsize sizeof(buf).
* \param[in] str A string that may contain any characters.
* \param[in] in_len Pass -1 to print until nul char, or >= 0 to force a length (also past nul chars).
+ * \return Number of characters that would be written if bufsize were large enough excluding '\0' (like snprintf()).
+ */
+int osmo_escape_str_buf3(char *buf, size_t bufsize, const char *str, int in_len)
+{
+ return _osmo_escape_str_buf(buf, bufsize, str, in_len, false);
+}
+
+/*! Return the string with all non-printable characters escaped.
+ * \param[out] buf string buffer to write escaped characters to.
+ * \param[in] bufsize sizeof(buf).
+ * \param[in] str A string that may contain any characters.
+ * \param[in] in_len Pass -1 to print until nul char, or >= 0 to force a length (also past nul chars).
* \return The output buffer (buf).
*/
char *osmo_escape_str_buf2(char *buf, size_t bufsize, const char *str, int in_len)
@@ -892,6 +895,20 @@ static size_t _osmo_quote_str_buf(char *buf, size_t bufsize, const char *str, in
return sb.chars_needed;
}
+/*! Like osmo_escape_str_buf3(), but returns double-quotes around a string, or "NULL" for a NULL string.
+ * This allows passing any char* value and get its C representation as string.
+ * The function signature is suitable for OSMO_STRBUF_APPEND_NOLEN().
+ * \param[out] buf string buffer to write escaped characters to.
+ * \param[in] bufsize sizeof(buf).
+ * \param[in] str A string that may contain any characters.
+ * \param[in] in_len Pass -1 to print until nul char, or >= 0 to force a length.
+ * \return Number of characters that would be written if bufsize were large enough excluding '\0' (like snprintf()).
+ */
+int osmo_quote_str_buf3(char *buf, size_t bufsize, const char *str, int in_len)
+{
+ return _osmo_quote_str_buf(buf, bufsize, str, in_len, false);
+}
+
/*! Like osmo_escape_str_buf2(), but returns double-quotes around a string, or "NULL" for a NULL string.
* This allows passing any char* value and get its C representation as string.
* The function signature is suitable for OSMO_STRBUF_APPEND_NOLEN().
@@ -1208,4 +1225,315 @@ bool osmo_str_startswith(const char *str, const char *startswith_str)
return strncmp(str, startswith_str, strlen(startswith_str)) == 0;
}
+/*! Convert a string of a floating point number to a signed int, with a decimal factor (fixed-point precision).
+ * For example, with precision=3, convert "-1.23" to -1230. In other words, the float value is multiplied by
+ * 10 to-the-power-of precision to obtain the returned integer.
+ * The usable range of digits is -INT64_MAX .. INT64_MAX -- note, not INT64_MIN! The value of INT64_MIN is excluded to
+ * reduce implementation complexity. See also utils_test.c.
+ * The advantage over using sscanf("%f") is guaranteed precision: float or double types may apply rounding in the
+ * conversion result. osmo_float_str_to_int() and osmo_int_to_float_str_buf() guarantee true results when converting
+ * back and forth between string and int.
+ * \param[out] val Returned integer value.
+ * \param[in] str String of a float, like '-12.345'.
+ * \param[in] precision Fixed-point precision, or * \returns 0 on success, negative on error.
+ */
+int osmo_float_str_to_int(int64_t *val, const char *str, unsigned int precision)
+{
+ const char *point;
+ char *endptr;
+ const char *p;
+ int64_t sign = 1;
+ int64_t integer = 0;
+ int64_t decimal = 0;
+ int64_t precision_factor;
+ int64_t integer_max;
+ int64_t decimal_max;
+ unsigned int i;
+
+ OSMO_ASSERT(val);
+ *val = 0;
+
+ if (!str)
+ return -EINVAL;
+ if (str[0] == '-') {
+ str = str + 1;
+ sign = -1;
+ } else if (str[0] == '+') {
+ str = str + 1;
+ }
+ if (!str[0])
+ return -EINVAL;
+
+ /* Validate entire string as purely digits and at most one decimal dot. If not doing this here in advance,
+ * parsing digits might stop early because of precision cut-off and miss validation of input data. */
+ point = NULL;
+ for (p = str; *p; p++) {
+ if (*p == '.') {
+ if (point)
+ return -EINVAL;
+ point = p;
+ } else if (!isdigit((unsigned char)*p))
+ return -EINVAL;
+ }
+
+ /* Parse integer part if there is one. If the string starts with a point, there's nothing to parse for the
+ * integer part. */
+ if (!point || point > str) {
+ errno = 0;
+ integer = strtoll(str, &endptr, 10);
+ if ((errno == ERANGE && (integer == LLONG_MAX || integer == LLONG_MIN))
+ || (errno != 0 && integer == 0))
+ return -ERANGE;
+
+ if ((point && endptr != point)
+ || (!point && *endptr))
+ return -EINVAL;
+ }
+
+ /* Parse the fractional part if there is any, and if the precision is nonzero (if we even care about fractional
+ * digits) */
+ if (precision && point && point[1] != '\0') {
+ /* limit the number of digits parsed to 'precision'.
+ * If 'precision' is larger than the 19 digits representable in int64_t, skip some, to pick up lower
+ * magnitude digits. */
+ unsigned int skip_digits = (precision < 20) ? 0 : precision - 20;
+ char decimal_str[precision + 1];
+ osmo_strlcpy(decimal_str, point+1, precision+1);
+
+ /* fill with zeros to make exactly 'precision' digits */
+ for (i = strlen(decimal_str); i < precision; i++)
+ decimal_str[i] = '0';
+ decimal_str[precision] = '\0';
+
+ for (i = 0; i < skip_digits; i++) {
+ /* When skipping digits because precision > nr-of-digits-in-int64_t, they must be zero;
+ * if there is a nonzero digit above the precision, it's -ERANGE. */
+ if (decimal_str[i] != '0')
+ return -ERANGE;
+ }
+ errno = 0;
+ decimal = strtoll(decimal_str + skip_digits, &endptr, 10);
+ if ((errno == ERANGE && (decimal == LLONG_MAX || decimal == LLONG_MIN))
+ || (errno != 0 && decimal == 0))
+ return -ERANGE;
+
+ if (*endptr)
+ return -EINVAL;
+ }
+
+ if (precision > 18) {
+ /* Special case of returning more digits than fit in int64_t range, e.g.
+ * osmo_float_str_to_int("0.0000000012345678901234567", precision=25) -> 12345678901234567. */
+ precision_factor = 0;
+ integer_max = 0;
+ decimal_max = INT64_MAX;
+ } else {
+ /* Do not surpass the resulting int64_t range. Depending on the amount of precision, the integer part
+ * and decimal part have specific ranges they must comply to. */
+ precision_factor = 1;
+ for (i = 0; i < precision; i++)
+ precision_factor *= 10;
+ integer_max = INT64_MAX / precision_factor;
+ if (integer == integer_max)
+ decimal_max = INT64_MAX % precision_factor;
+ else
+ decimal_max = INT64_MAX;
+ }
+
+ if (integer > integer_max)
+ return -ERANGE;
+ if (decimal > decimal_max)
+ return -ERANGE;
+
+ *val = sign * (integer * precision_factor + decimal);
+ return 0;
+}
+
+/*! Convert an integer to a floating point string using a decimal quotient (fixed-point precision).
+ * For example, with precision = 3, convert -1230 to "-1.23".
+ * The usable range of digits is -INT64_MAX .. INT64_MAX -- note, not INT64_MIN! The value of INT64_MIN is excluded to
+ * reduce implementation complexity. See also utils_test.c.
+ * The advantage over using printf("%.6g") is guaranteed precision: float or double types may apply rounding in the
+ * conversion result. osmo_float_str_to_int() and osmo_int_to_float_str_buf() guarantee true results when converting
+ * back and forth between string and int.
+ * The resulting string omits trailing zeros in the fractional part (like "%g" would) but never applies rounding.
+ * \param[out] buf Buffer to write string to.
+ * \param[in] buflen sizeof(buf).
+ * \param[in] val Value to convert to float.
+ * \returns number of chars that would be written, like snprintf().
+ */
+int osmo_int_to_float_str_buf(char *buf, size_t buflen, int64_t val, unsigned int precision)
+{
+ struct osmo_strbuf sb = { .buf = buf, .len = buflen };
+ unsigned int i;
+ unsigned int w;
+ int64_t precision_factor;
+ if (val < 0) {
+ OSMO_STRBUF_PRINTF(sb, "-");
+ if (val == INT64_MIN) {
+ OSMO_STRBUF_PRINTF(sb, "ERR");
+ return sb.chars_needed;
+ }
+ val = -val;
+ }
+
+ if (precision > 18) {
+ /* Special case of returning more digits than fit in int64_t range, e.g.
+ * osmo_int_to_float_str(12345678901234567, precision=25) -> "0.0000000012345678901234567". */
+ if (!val) {
+ OSMO_STRBUF_PRINTF(sb, "0");
+ return sb.chars_needed;
+ }
+ OSMO_STRBUF_PRINTF(sb, "0.");
+ for (i = 19; i < precision; i++)
+ OSMO_STRBUF_PRINTF(sb, "0");
+ precision = 19;
+ } else {
+ precision_factor = 1;
+ for (i = 0; i < precision; i++)
+ precision_factor *= 10;
+
+ OSMO_STRBUF_PRINTF(sb, "%" PRId64, val / precision_factor);
+ val %= precision_factor;
+ if (!val)
+ return sb.chars_needed;
+ OSMO_STRBUF_PRINTF(sb, ".");
+ }
+
+ /* print fractional part, skip trailing zeros */
+ w = precision;
+ while (!(val % 10)) {
+ val /= 10;
+ w--;
+ }
+ OSMO_STRBUF_PRINTF(sb, "%0*" PRId64, w, val);
+ return sb.chars_needed;
+}
+
+/*! Convert an integer with a factor of a million to a floating point string.
+ * For example, convert -1230000 to "-1.23".
+ * \param[in] ctx Talloc ctx to allocate string buffer from.
+ * \param[in] val Value to convert to float.
+ * \returns resulting string, dynamically allocated.
+ */
+char *osmo_int_to_float_str_c(void *ctx, int64_t val, unsigned int precision)
+{
+ OSMO_NAME_C_IMPL(ctx, 16, "ERROR", osmo_int_to_float_str_buf, val, precision)
+}
+
+/*! Convert a string of a number to int64_t, including all common strtoll() validity checks.
+ * It's not so trivial to call strtoll() and properly verify that the input string was indeed a valid number string.
+ * \param[out] result Buffer for the resulting integer number, or NULL if the caller is only interested in the
+ * validation result (returned rc).
+ * \param[in] str The string to convert.
+ * \param[in] base The integer base, i.e. 10 for decimal numbers or 16 for hexadecimal, as in strtoll().
+ * \param[in] min_val The smallest valid number expected in the string.
+ * \param[in] max_val The largest valid number expected in the string.
+ * \return 0 on success, -EOVERFLOW if the number in the string exceeds int64_t, -ENOTSUPP if the base is not supported,
+ * -ERANGE if the converted number exceeds the range [min_val..max_val] but is still within int64_t range, -E2BIG if
+ * surplus characters follow after the number, -EINVAL if the string does not contain a number. In case of -ERANGE and
+ * -E2BIG, the converted number is still accurately returned in result. In case of -EOVERFLOW, the returned value is
+ * clamped to INT64_MIN..INT64_MAX.
+ */
+int osmo_str_to_int64(int64_t *result, const char *str, int base, int64_t min_val, int64_t max_val)
+{
+ long long int val;
+ char *endptr;
+ if (result)
+ *result = 0;
+ if (!str || !*str)
+ return -EINVAL;
+ errno = 0;
+ val = strtoll(str, &endptr, base);
+ /* In case the number string exceeds long long int range, strtoll() clamps the returned value to LLONG_MIN or
+ * LLONG_MAX. Make sure of the same here with respect to int64_t. */
+ if (val < INT64_MIN) {
+ if (result)
+ *result = INT64_MIN;
+ return -ERANGE;
+ }
+ if (val > INT64_MAX) {
+ if (result)
+ *result = INT64_MAX;
+ return -ERANGE;
+ }
+ if (result)
+ *result = (int64_t)val;
+ switch (errno) {
+ case 0:
+ break;
+ case ERANGE:
+ return -EOVERFLOW;
+ default:
+ case EINVAL:
+ return -ENOTSUP;
+ }
+ if (!endptr || *endptr) {
+ /* No chars were converted */
+ if (endptr == str)
+ return -EINVAL;
+ /* Or there are surplus chars after the converted number */
+ return -E2BIG;
+ }
+ if (val < min_val || val > max_val)
+ return -ERANGE;
+ return 0;
+}
+
+/*! Convert a string of a number to int, including all common strtoll() validity checks.
+ * Same as osmo_str_to_int64() but using the plain int data type.
+ * \param[out] result Buffer for the resulting integer number, or NULL if the caller is only interested in the
+ * validation result (returned rc).
+ * \param[in] str The string to convert.
+ * \param[in] base The integer base, i.e. 10 for decimal numbers or 16 for hexadecimal, as in strtoll().
+ * \param[in] min_val The smallest valid number expected in the string.
+ * \param[in] max_val The largest valid number expected in the string.
+ * \return 0 on success, -EOVERFLOW if the number in the string exceeds int range, -ENOTSUPP if the base is not supported,
+ * -ERANGE if the converted number exceeds the range [min_val..max_val] but is still within int range, -E2BIG if
+ * surplus characters follow after the number, -EINVAL if the string does not contain a number. In case of -ERANGE and
+ * -E2BIG, the converted number is still accurately returned in result. In case of -EOVERFLOW, the returned value is
+ * clamped to INT_MIN..INT_MAX.
+ */
+int osmo_str_to_int(int *result, const char *str, int base, int min_val, int max_val)
+{
+ int64_t val;
+ int rc = osmo_str_to_int64(&val, str, base, min_val, max_val);
+ /* In case the number string exceeds long long int range, strtoll() clamps the returned value to LLONG_MIN or
+ * LLONG_MAX. Make sure of the same here with respect to int. */
+ if (val < INT_MIN) {
+ if (result)
+ *result = INT_MIN;
+ return -EOVERFLOW;
+ }
+ if (val > INT_MAX) {
+ if (result)
+ *result = INT_MAX;
+ return -EOVERFLOW;
+ }
+ if (result)
+ *result = (int)val;
+ return rc;
+}
+
+/*! Replace a string using talloc and release its prior content (if any).
+ * This is a format string capable equivalent of osmo_talloc_replace_string().
+ * \param[in] ctx Talloc context to use for allocation.
+ * \param[out] dst Pointer to string, will be updated with ptr to new string.
+ * \param[in] fmt Format string that will be copied to newly allocated string. */
+void osmo_talloc_replace_string_fmt(void *ctx, char **dst, const char *fmt, ...)
+{
+ char *name = NULL;
+
+ if (fmt != NULL) {
+ va_list ap;
+
+ va_start(ap, fmt);
+ name = talloc_vasprintf(ctx, fmt, ap);
+ va_end(ap);
+ }
+
+ talloc_free(*dst);
+ *dst = name;
+}
+
/*! @} */
diff --git a/src/write_queue.c b/src/core/write_queue.c
index 3399b0f1..884cebdd 100644
--- a/src/write_queue.c
+++ b/src/core/write_queue.c
@@ -16,10 +16,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <errno.h>
@@ -64,16 +60,19 @@ int osmo_wqueue_bfd_cb(struct osmo_fd *fd, unsigned int what)
fd->when &= ~OSMO_FD_WRITE;
+ msg = msgb_dequeue_count(&queue->msg_queue, &queue->current_length);
/* the queue might have been emptied */
- if (!llist_empty(&queue->msg_queue)) {
- --queue->current_length;
-
- msg = msgb_dequeue(&queue->msg_queue);
+ if (msg) {
rc = queue->write_cb(fd, msg);
- msgb_free(msg);
-
- if (rc == -EBADF)
+ if (rc == -EBADF) {
+ msgb_free(msg);
goto err_badfd;
+ } else if (rc == -EAGAIN) {
+ /* re-enqueue the msgb to the head of the queue */
+ llist_add(&msg->list, &queue->msg_queue);
+ queue->current_length++;
+ } else
+ msgb_free(msg);
if (!llist_empty(&queue->msg_queue))
fd->when |= OSMO_FD_WRITE;
@@ -100,10 +99,26 @@ void osmo_wqueue_init(struct osmo_wqueue *queue, int max_length)
INIT_LLIST_HEAD(&queue->msg_queue);
}
+/*! Enqueue a new \ref msgb into a write queue (without logging full queue events)
+ * \param[in] queue Write queue to be used
+ * \param[in] data to-be-enqueued message buffer
+ * \returns 0 on success; negative on error (MESSAGE NOT FREED IN CASE OF ERROR).
+ */
+int osmo_wqueue_enqueue_quiet(struct osmo_wqueue *queue, struct msgb *data)
+{
+ if (queue->current_length >= queue->max_length)
+ return -ENOSPC;
+
+ msgb_enqueue_count(&queue->msg_queue, data, &queue->current_length);
+ queue->bfd.when |= OSMO_FD_WRITE;
+
+ return 0;
+}
+
/*! Enqueue a new \ref msgb into a write queue
* \param[in] queue Write queue to be used
* \param[in] data to-be-enqueued message buffer
- * \returns 0 on success; negative on error
+ * \returns 0 on success; negative on error (MESSAGE NOT FREED IN CASE OF ERROR).
*/
int osmo_wqueue_enqueue(struct osmo_wqueue *queue, struct msgb *data)
{
@@ -113,11 +128,7 @@ int osmo_wqueue_enqueue(struct osmo_wqueue *queue, struct msgb *data)
return -ENOSPC;
}
- ++queue->current_length;
- msgb_enqueue(&queue->msg_queue, data);
- queue->bfd.when |= OSMO_FD_WRITE;
-
- return 0;
+ return osmo_wqueue_enqueue_quiet(queue, data);
}
/*! Clear a \ref osmo_wqueue
diff --git a/src/ctrl/Makefile.am b/src/ctrl/Makefile.am
index ca642869..29f867e6 100644
--- a/src/ctrl/Makefile.am
+++ b/src/ctrl/Makefile.am
@@ -1,9 +1,10 @@
# This is _NOT_ the library release version, it's an API version.
# Please read chapter "Library interface versions" of the libtool documentation
# before making any modifications: https://www.gnu.org/software/libtool/manual/html_node/Versioning.html
-LIBVERSION=4:0:4
+LIBVERSION=8:0:8
-AM_CFLAGS = -Wall $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)/include $(TALLOC_CFLAGS)
+AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_builddir)/include -I$(top_builddir)
+AM_CFLAGS = -Wall $(TALLOC_CFLAGS)
if ENABLE_CTRL
lib_LTLIBRARIES = libosmoctrl.la
@@ -12,7 +13,7 @@ libosmoctrl_la_SOURCES = control_cmd.c control_if.c fsm_ctrl_commands.c
libosmoctrl_la_LDFLAGS = $(LTLDFLAGS_OSMOCTRL) -version-info $(LIBVERSION) -no-undefined
libosmoctrl_la_LIBADD = $(TALLOC_LIBS) \
- $(top_builddir)/src/libosmocore.la \
+ $(top_builddir)/src/core/libosmocore.la \
$(top_builddir)/src/gsm/libosmogsm.la \
$(top_builddir)/src/vty/libosmovty.la
@@ -21,5 +22,6 @@ libosmoctrl_la_SOURCES += control_vty.c
endif
EXTRA_DIST = libosmoctrl.map
+EXTRA_libosmoctrl_la_DEPENDENCIES = libosmoctrl.map
endif
diff --git a/src/ctrl/control_cmd.c b/src/ctrl/control_cmd.c
index 33496bd8..dec19b9b 100644
--- a/src/ctrl/control_cmd.c
+++ b/src/ctrl/control_cmd.c
@@ -20,10 +20,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <ctype.h>
@@ -207,13 +203,23 @@ failure:
}
/*! Install a given command definition at a given CTRL node.
- * \param[in] node CTRL node at whihc \a cmd is to be installed
+ * \param[in] node CTRL node at which \a cmd is to be installed
* \param[in] cmd command definition to be installed
* \returns 0 on success; negative on error */
int ctrl_cmd_install(enum ctrl_node_type node, struct ctrl_cmd_element *cmd)
{
vector cmds_vec;
+ /* If this assert triggers, it means the program forgot to initialize
+ * the CTRL interface first by calling ctrl_handle_alloc(2)() directly
+ * or indirectly through ctrl_interface_setup_dynip(2)()
+ */
+ if (!ctrl_node_vec) {
+ LOGP(DLCTRL, LOGL_ERROR,
+ "ctrl_handle must be initialized prior to installing cmds.\n");
+ return -ENODEV;
+ }
+
cmds_vec = vector_lookup_ensure(ctrl_node_vec, node);
if (!cmds_vec) {
@@ -516,92 +522,61 @@ err:
* \returns callee-allocated message buffer containing the encoded \a cmd; NULL on error */
struct msgb *ctrl_cmd_make(struct ctrl_cmd *cmd)
{
- struct msgb *msg;
+ struct msgb *msg = NULL;
+ char *strbuf;
+ size_t len;
const char *type;
- char *tmp;
if (!cmd->id)
return NULL;
- msg = msgb_alloc_headroom(4096, 128, "ctrl command make");
- if (!msg)
- return NULL;
-
type = get_value_string(ctrl_type_vals, cmd->type);
switch (cmd->type) {
case CTRL_TYPE_GET:
if (!cmd->variable)
- goto err;
-
- tmp = talloc_asprintf(cmd, "%s %s %s", type, cmd->id, cmd->variable);
- if (!tmp) {
- LOGP(DLCTRL, LOGL_ERROR, "Failed to allocate cmd.\n");
- goto err;
- }
-
- msg->l2h = msgb_put(msg, strlen(tmp));
- memcpy(msg->l2h, tmp, strlen(tmp));
- talloc_free(tmp);
+ return NULL;
+ strbuf = talloc_asprintf(cmd, "%s %s %s", type, cmd->id, cmd->variable);
break;
case CTRL_TYPE_SET:
if (!cmd->variable || !cmd->value)
- goto err;
-
- tmp = talloc_asprintf(cmd, "%s %s %s %s", type, cmd->id, cmd->variable,
- cmd->value);
- if (!tmp) {
- LOGP(DLCTRL, LOGL_ERROR, "Failed to allocate cmd.\n");
- goto err;
- }
-
- msg->l2h = msgb_put(msg, strlen(tmp));
- memcpy(msg->l2h, tmp, strlen(tmp));
- talloc_free(tmp);
+ return NULL;
+ strbuf = talloc_asprintf(cmd, "%s %s %s %s", type, cmd->id,
+ cmd->variable, cmd->value);
break;
case CTRL_TYPE_GET_REPLY:
case CTRL_TYPE_SET_REPLY:
case CTRL_TYPE_TRAP:
if (!cmd->variable || !cmd->reply)
- goto err;
-
- tmp = talloc_asprintf(cmd, "%s %s %s %s", type, cmd->id, cmd->variable,
- cmd->reply);
- if (!tmp) {
- LOGP(DLCTRL, LOGL_ERROR, "Failed to allocate cmd.\n");
- goto err;
- }
-
- msg->l2h = msgb_put(msg, strlen(tmp));
- memcpy(msg->l2h, tmp, strlen(tmp));
- talloc_free(tmp);
+ return NULL;
+ strbuf = talloc_asprintf(cmd, "%s %s %s %s", type, cmd->id,
+ cmd->variable, cmd->reply);
break;
case CTRL_TYPE_ERROR:
if (!cmd->reply)
- goto err;
-
- tmp = talloc_asprintf(cmd, "%s %s %s", type, cmd->id,
- cmd->reply);
- if (!tmp) {
- LOGP(DLCTRL, LOGL_ERROR, "Failed to allocate cmd.\n");
- goto err;
- }
-
- msg->l2h = msgb_put(msg, strlen(tmp));
- memcpy(msg->l2h, tmp, strlen(tmp));
- talloc_free(tmp);
+ return NULL;
+ strbuf = talloc_asprintf(cmd, "%s %s %s", type, cmd->id, cmd->reply);
break;
default:
LOGP(DLCTRL, LOGL_NOTICE, "Unknown command type %i\n", cmd->type);
- goto err;
- break;
+ return NULL;
}
- return msg;
+ if (!strbuf) {
+ LOGP(DLCTRL, LOGL_ERROR, "Failed to allocate cmd.\n");
+ goto ret;
+ }
+ len = strlen(strbuf);
-err:
- msgb_free(msg);
- return NULL;
+ msg = msgb_alloc_headroom(len + 128, 128, "ctrl ERROR command make");
+ if (!msg)
+ goto ret;
+ msg->l2h = msgb_put(msg, len);
+ memcpy(msg->l2h, strbuf, len);
+
+ret:
+ talloc_free(strbuf);
+ return msg;
}
/*! Build a deferred control command state and keep it the per-connection list of deferred commands.
diff --git a/src/ctrl/control_if.c b/src/ctrl/control_if.c
index ce2e3676..94b8975e 100644
--- a/src/ctrl/control_if.c
+++ b/src/ctrl/control_if.c
@@ -20,10 +20,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include "config.h"
@@ -35,6 +31,7 @@
#include <string.h>
#include <time.h>
#include <unistd.h>
+#include <limits.h>
#include <arpa/inet.h>
@@ -50,9 +47,11 @@
#include <osmocom/ctrl/control_cmd.h>
#include <osmocom/ctrl/control_if.h>
+#include <osmocom/ctrl/control_vty.h>
#include <osmocom/core/msgb.h>
#include <osmocom/core/rate_ctr.h>
+#include <osmocom/core/stat_item.h>
#include <osmocom/core/select.h>
#include <osmocom/core/counter.h>
#include <osmocom/core/talloc.h>
@@ -82,26 +81,22 @@ static LLIST_HEAD(ctrl_lookup_helpers);
* \returns 1 on success; 0 in case of error */
int ctrl_parse_get_num(vector vline, int i, long *num)
{
- char *token, *tmp;
+ char *token;
+ int64_t val;
if (i >= vector_active(vline))
return 0;
token = vector_slot(vline, i);
- errno = 0;
- if (token[0] == '\0')
- return 0;
-
- *num = strtol(token, &tmp, 10);
- if (tmp[0] != '\0' || errno != 0)
+ if (osmo_str_to_int64(&val, token, 10, LONG_MIN, LONG_MAX))
return 0;
-
+ *num = (long)val;
return 1;
}
/*! Send a CTRL command to all connections.
* \param[in] ctrl global control handle
- * \param[in] cmd command to send to all connections in \ctrl
+ * \param[in] cmd command to send to all connections in ctrl
* \returns number of times the command has been sent */
int ctrl_cmd_send_to_all(struct ctrl_handle *ctrl, struct ctrl_cmd *cmd)
{
@@ -221,6 +216,10 @@ int ctrl_cmd_handle(struct ctrl_handle *ctrl, struct ctrl_cmd *cmd,
if (cmd->type == CTRL_TYPE_SET_REPLY ||
cmd->type == CTRL_TYPE_GET_REPLY) {
+ if (ctrl->reply_cb) {
+ ctrl->reply_cb(ctrl, cmd, data);
+ return CTRL_CMD_HANDLED;
+ }
if (strncmp(cmd->reply, "OK", 2) == 0) {
LOGP(DLCTRL, LOGL_DEBUG, "%s <%s> for %s is OK\n",
get_value_string(ctrl_type_vals, cmd->type),
@@ -485,8 +484,14 @@ static int control_write_cb(struct osmo_fd *bfd, struct msgb *msg)
control_close_conn(ccon);
return -EBADF;
}
- if (rc != msg->len)
+ if (rc < 0) {
LOGP(DLCTRL, LOGL_ERROR, "Failed to write message to the CTRL connection.\n");
+ return 0;
+ }
+ if (rc < msg->len) {
+ msgb_pull(msg, rc);
+ return -EAGAIN;
+ }
return 0;
}
@@ -510,6 +515,7 @@ struct ctrl_connection *osmo_ctrl_conn_alloc(void *ctx, void *data)
INIT_LLIST_HEAD(&ccon->def_cmds);
ccon->write_queue.bfd.data = data;
+ ccon->write_queue.bfd.fd = -1;
ccon->write_queue.write_cb = control_write_cb;
ccon->write_queue.read_cb = handle_control_read;
@@ -584,12 +590,12 @@ static uint64_t get_rate_ctr_value(const struct rate_ctr *ctr, int intv, const c
}
}
-static int get_rate_ctr_group_idx(const struct rate_ctr_group *ctrg, int intv, struct ctrl_cmd *cmd)
+static int get_rate_ctr_group_idx(struct rate_ctr_group *ctrg, int intv, struct ctrl_cmd *cmd)
{
unsigned int i;
for (i = 0; i < ctrg->desc->num_ctr; i++) {
ctrl_cmd_reply_printf(cmd, "%s %"PRIu64";", ctrg->desc->ctr_desc[i].name,
- get_rate_ctr_value(&ctrg->ctr[i], intv, ctrg->desc->group_name_prefix));
+ get_rate_ctr_value(rate_ctr_group_get_ctr(ctrg, i), intv, ctrg->desc->group_name_prefix));
if (!cmd->reply) {
cmd->reply = "OOM";
return CTRL_CMD_ERROR;
@@ -718,6 +724,100 @@ static int verify_rate_ctr(struct ctrl_cmd *cmd, const char *value, void *data)
return 0;
}
+/* Expose all stat_item groups on CTRL, as read-only variables of the form:
+ * stat_item.(last|...).group_name.N.item_name
+ * stat_item.(last|...).group_name.by_name.group_idx_name.item_name
+ */
+CTRL_CMD_DEFINE_RO(stat_item, "stat_item *");
+static int get_stat_item(struct ctrl_cmd *cmd, void *data)
+{
+ char *dup;
+ char *saveptr;
+ char *value_type;
+ char *group_name;
+ char *group_idx_str;
+ char *item_name;
+ char *tmp;
+ int32_t val;
+ struct osmo_stat_item_group *statg;
+ const struct osmo_stat_item *stat_item;
+
+ /* cmd will be freed in control_if.c after handling here, so no need to free the dup string. */
+ dup = talloc_strdup(cmd, cmd->variable);
+ if (!dup) {
+ cmd->reply = "OOM";
+ return CTRL_CMD_ERROR;
+ }
+
+ /* Split off the first "stat_item." part */
+ tmp = strtok_r(dup, ".", &saveptr);
+ if (!tmp || strcmp(tmp, "stat_item") != 0)
+ goto format_error;
+
+ /* Split off the "last." part (validated further below) */
+ value_type = strtok_r(NULL, ".", &saveptr);
+ if (!value_type)
+ goto format_error;
+
+ /* Split off the "group_name." part */
+ group_name = strtok_r(NULL, ".", &saveptr);
+ if (!group_name)
+ goto format_error;
+
+ /* Split off the "N." part */
+ group_idx_str = strtok_r(NULL, ".", &saveptr);
+ if (!group_idx_str)
+ goto format_error;
+ if (strcmp(group_idx_str, "by_name") == 0) {
+ /* The index is not given by "N" but by "by_name.foo". Get the "foo" idx-name */
+ group_idx_str = strtok_r(NULL, ".", &saveptr);
+ statg = osmo_stat_item_get_group_by_name_idxname(group_name, group_idx_str);
+ } else {
+ int idx;
+ if (osmo_str_to_int(&idx, group_idx_str, 10, 0, INT_MAX))
+ goto format_error;
+ statg = osmo_stat_item_get_group_by_name_idx(group_name, idx);
+ }
+ if (!statg) {
+ cmd->reply = "Stat group with given name and index not found";
+ return CTRL_CMD_ERROR;
+ }
+
+ /* Split off the "item_name" part */
+ item_name = strtok_r(NULL, ".", &saveptr);
+ if (!item_name)
+ goto format_error;
+ stat_item = osmo_stat_item_get_by_name(statg, item_name);
+ if (!stat_item) {
+ cmd->reply = "No such stat item found";
+ return CTRL_CMD_ERROR;
+ }
+
+ tmp = strtok_r(NULL, "", &saveptr);
+ if (tmp) {
+ cmd->reply = "Garbage after stat item name";
+ return CTRL_CMD_ERROR;
+ }
+
+ if (!strcmp(value_type, "last"))
+ val = osmo_stat_item_get_last(stat_item);
+ else
+ goto format_error;
+
+ cmd->reply = talloc_asprintf(cmd, "%"PRIu32, val);
+ if (!cmd->reply) {
+ cmd->reply = "OOM";
+ return CTRL_CMD_ERROR;
+ }
+
+ return CTRL_CMD_REPLY;
+
+format_error:
+ cmd->reply = "Stat item must be of form stat_item.type.group_name.N.item_name,"
+ " e.g. 'stat_item.last.bsc.0.msc_num:connected'";
+ return CTRL_CMD_ERROR;
+}
+
/* counter */
CTRL_CMD_DEFINE(counter, "counter *");
static int get_counter(struct ctrl_cmd *cmd, void *data)
@@ -779,7 +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;
@@ -805,6 +905,9 @@ static int ctrl_init(unsigned int node_count)
ret = ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_rate_ctr);
if (ret)
goto err_vec;
+ ret = ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_stat_item);
+ if (ret)
+ goto err_vec;
ret = ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_counter);
if (ret)
goto err_vec;
@@ -928,12 +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 ef988892..a7ebddc2 100644
--- a/src/ctrl/control_vty.c
+++ b/src/ctrl/control_vty.c
@@ -17,10 +17,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <stdlib.h>
@@ -30,16 +26,20 @@
static void *ctrl_vty_ctx = NULL;
static const char *ctrl_vty_bind_addr = NULL;
+/* Port the CTRL should bind to: -1 means not configured */
+static int ctrl_bind_port = -1;
DEFUN(cfg_ctrl_bind_addr,
cfg_ctrl_bind_addr_cmd,
- "bind A.B.C.D",
+ "bind A.B.C.D [<0-65535>]",
"Set bind address to listen for Control connections\n"
- "Local IP address (default 127.0.0.1)\n")
+ "Local IP address (default 127.0.0.1)\n"
+ "Local TCP port number\n")
{
talloc_free((char*)ctrl_vty_bind_addr);
ctrl_vty_bind_addr = NULL;
ctrl_vty_bind_addr = talloc_strdup(ctrl_vty_ctx, argv[0]);
+ ctrl_bind_port = argc > 1 ? atoi(argv[1]) : -1;
return CMD_SUCCESS;
}
@@ -50,6 +50,11 @@ const char *ctrl_vty_get_bind_addr(void)
return ctrl_vty_bind_addr;
}
+uint16_t ctrl_vty_get_bind_port(uint16_t default_port)
+{
+ return ctrl_bind_port >= 0 ? ctrl_bind_port : default_port;
+}
+
static struct cmd_node ctrl_node = {
L_CTRL_NODE,
"%s(config-ctrl)# ",
@@ -82,10 +87,10 @@ static int config_write_ctrl(struct vty *vty)
int ctrl_vty_init(void *ctx)
{
ctrl_vty_ctx = ctx;
- install_element(CONFIG_NODE, &cfg_ctrl_cmd);
+ install_lib_element(CONFIG_NODE, &cfg_ctrl_cmd);
install_node(&ctrl_node, config_write_ctrl);
- install_element(L_CTRL_NODE, &cfg_ctrl_bind_addr_cmd);
+ install_lib_element(L_CTRL_NODE, &cfg_ctrl_bind_addr_cmd);
return 0;
}
diff --git a/src/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 65c35529..3a64a717 100644
--- a/src/gb/Makefile.am
+++ b/src/gb/Makefile.am
@@ -1,10 +1,12 @@
# This is _NOT_ the library release version, it's an API version.
# Please read chapter "Library interface versions" of the libtool documentation
# before making any modifications: https://www.gnu.org/software/libtool/manual/html_node/Versioning.html
-LIBVERSION=11:0:0
+LIBVERSION=15:0:1
-AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_builddir)/include
-AM_CFLAGS = -Wall ${GCC_FVISIBILITY_HIDDEN} -fno-strict-aliasing $(TALLOC_CFLAGS)
+AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_builddir)/include -I$(top_builddir)
+AM_CFLAGS = -Wall -fno-strict-aliasing \
+ $(TALLOC_CFLAGS) \
+ $(NULL)
# FIXME: this should eventually go into a milenage/Makefile.am
noinst_HEADERS = common_vty.h gb_internal.h gprs_bssgp_internal.h gprs_ns2_internal.h
@@ -12,18 +14,30 @@ noinst_HEADERS = common_vty.h gb_internal.h gprs_bssgp_internal.h gprs_ns2_inter
if ENABLE_GB
lib_LTLIBRARIES = libosmogb.la
-libosmogb_la_LDFLAGS = $(LTLDFLAGS_OSMOGB) -version-info $(LIBVERSION)
+libosmogb_la_LDFLAGS = \
+ $(LTLDFLAGS_OSMOGB) \
+ -version-info $(LIBVERSION) \
+ -no-undefined \
+ $(NULL)
libosmogb_la_LIBADD = $(TALLOC_LIBS) \
- $(top_builddir)/src/libosmocore.la \
+ $(top_builddir)/src/core/libosmocore.la \
$(top_builddir)/src/vty/libosmovty.la \
$(top_builddir)/src/gsm/libosmogsm.la
libosmogb_la_SOURCES = gprs_ns.c gprs_ns_frgre.c gprs_ns_vty.c gprs_ns_sns.c \
- gprs_bssgp.c gprs_bssgp_util.c gprs_bssgp_vty.c \
+ gprs_bssgp.c gprs_bssgp_util.c gprs_bssgp_vty.c gprs_bssgp_rim.c \
gprs_bssgp_bss.c \
- gprs_ns2.c gprs_ns2_udp.c gprs_ns2_frgre.c gprs_ns2_vc_fsm.c gprs_ns2_sns.c \
+ gprs_ns2.c gprs_ns2_udp.c gprs_ns2_frgre.c gprs_ns2_fr.c gprs_ns2_vc_fsm.c gprs_ns2_sns.c \
gprs_ns2_message.c gprs_ns2_vty.c \
- common_vty.c
+ gprs_bssgp2.c bssgp_bvc_fsm.c \
+ common_vty.c frame_relay.c
+
+# convenience library for testing with access to all non-static symbols
+noinst_LTLIBRARIES = libosmogb-test.la
+libosmogb_test_la_LIBADD = $(libosmogb_la_LIBADD)
+libosmogb_test_la_SOURCES= $(libosmogb_la_SOURCES)
+
endif
EXTRA_DIST = libosmogb.map
+EXTRA_libosmogb_la_DEPENDENCIES = libosmogb.map
diff --git a/src/gb/bssgp_bvc_fsm.c b/src/gb/bssgp_bvc_fsm.c
new file mode 100644
index 00000000..3a36c7dc
--- /dev/null
+++ b/src/gb/bssgp_bvc_fsm.c
@@ -0,0 +1,857 @@
+/* BSSGP per-BVC Finite State Machine */
+
+/* (C) 2020 Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <string.h>
+#include <stdio.h>
+
+#include <osmocom/core/fsm.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/tdef.h>
+
+#include <osmocom/gsm/gsm48.h>
+#include <osmocom/gsm/tlv.h>
+
+#include <osmocom/gprs/gprs_msgb.h>
+#include <osmocom/gprs/gprs_bssgp.h>
+#include <osmocom/gprs/gprs_bssgp2.h>
+#include <osmocom/gprs/bssgp_bvc_fsm.h>
+
+#include "common_vty.h"
+
+#define S(x) (1 << (x))
+
+/* TODO: Those are not made cofnigurable via a VTY yet */
+struct osmo_tdef bssgp_bvc_fsm_tdefs[] = {
+ {
+ .T = 1,
+ .default_val = 5,
+ .min_val = 1,
+ .max_val = 30,
+ .unit = OSMO_TDEF_S,
+ .desc = "Guards the BSSGP BVC (un)blocking procedure",
+ }, {
+ .T = 2,
+ .default_val = 10,
+ .min_val = 1,
+ .max_val = 120,
+ .unit = OSMO_TDEF_S,
+ .desc = "Guards the BSSGP BVC reset procedure",
+ }, {
+ .T = 3,
+ .default_val = 500,
+ .min_val = 100,
+ .max_val = 10000,
+ .unit = OSMO_TDEF_MS,
+ .desc = "Guards the BSSGP SUSPEND procedure",
+ }, {
+ .T = 4,
+ .default_val = 500,
+ .min_val = 100,
+ .max_val = 10000,
+ .unit = OSMO_TDEF_MS,
+ .desc = "Guards the BSSGP RESUME procedure",
+ }, {
+ .T = 5,
+ .default_val = 15,
+ .min_val = 1,
+ .max_val = 30,
+ .unit = OSMO_TDEF_S,
+ .desc = "Guards the BSSGP Radio Access Capability Update procedure",
+ },
+ {}
+};
+
+#define T1 1
+#define T2 2
+
+/* We cannot use osmo_tdef_fsm_* as it makes hard-coded assumptions that
+ * each new/target state will always use the same timer and timeout - or
+ * a timeout at all */
+#define T1_SECS osmo_tdef_get(bssgp_bvc_fsm_tdefs, 1, OSMO_TDEF_S, 5)
+#define T2_SECS osmo_tdef_get(bssgp_bvc_fsm_tdefs, 2, OSMO_TDEF_S, 10)
+
+/* forward declaration */
+static struct osmo_fsm bssgp_bvc_fsm;
+
+static const struct value_string ptp_bvc_event_names[] = {
+ { BSSGP_BVCFSM_E_RX_BLOCK, "RX-BVC-BLOCK" },
+ { BSSGP_BVCFSM_E_RX_BLOCK_ACK, "RX-BVC-BLOCK-ACK" },
+ { BSSGP_BVCFSM_E_RX_UNBLOCK, "RX-BVC-UNBLOCK" },
+ { BSSGP_BVCFSM_E_RX_UNBLOCK_ACK, "RX-BVC-UNBLOCK-ACK" },
+ { BSSGP_BVCFSM_E_RX_RESET, "RX-BVC-RESET" },
+ { BSSGP_BVCFSM_E_RX_RESET_ACK, "RX-BVC-RESET-ACK" },
+ { BSSGP_BVCFSM_E_RX_FC_BVC, "RX-FLOW-CONTROL-BVC" },
+ { BSSGP_BVCFSM_E_RX_FC_BVC_ACK, "RX-FLOW-CONTROL-BVC-ACK" },
+ { BSSGP_BVCFSM_E_REQ_BLOCK, "REQ-BLOCK" },
+ { BSSGP_BVCFSM_E_REQ_UNBLOCK, "REQ-UNBLOCK" },
+ { BSSGP_BVCFSM_E_REQ_RESET, "REQ-RESET" },
+ { BSSGP_BVCFSM_E_REQ_FC_BVC, "REQ-FLOW-CONTROL-BVC" },
+ { 0, NULL }
+};
+
+struct bvc_fsm_priv {
+ /* NS-instance; defining the scope for NSEI below */
+ struct gprs_ns2_inst *nsi;
+
+ /* NSEI of the underlying NS Entity */
+ uint16_t nsei;
+ /* Maximum size of the BSSGP PDU */
+ uint16_t max_pdu_len;
+
+ /* BVCI of this BVC */
+ uint16_t bvci;
+
+ /* are we the SGSN (true) or the BSS (false) */
+ bool role_sgsn;
+
+ /* BSS side: are we locally marked blocked? */
+ bool locally_blocked;
+ uint8_t block_cause;
+
+ /* cause value of the last outbound BVC-RESET (for re-transmissions) */
+ uint8_t last_reset_cause;
+
+ struct {
+ /* Bit 0..7: Features; Bit 8..15: Extended Features */
+ uint32_t advertised;
+ uint32_t received;
+ uint32_t negotiated;
+ /* only used if BSSGP_XFEAT_GBIT is negotiated */
+ enum bssgp_fc_granularity fc_granularity;
+ } features;
+
+ /* Cell Identification used by BSS when
+ * transmitting BVC-RESET / BVC-RESET-ACK, or those received
+ * from BSS in SGSN role */
+ struct gprs_ra_id ra_id;
+ uint16_t cell_id;
+
+ /* call-backs provided by the user */
+ const struct bssgp_bvc_fsm_ops *ops;
+ /* private data pointer passed to each call-back invocation */
+ void *ops_priv;
+};
+
+static int fi_tx_ptp(struct osmo_fsm_inst *fi, struct msgb *msg)
+{
+ struct bssgp_normal_hdr *bgph = (struct bssgp_normal_hdr *) msgb_bssgph(msg);
+ struct bvc_fsm_priv *bfp = fi->priv;
+
+ OSMO_ASSERT(fi->fsm == &bssgp_bvc_fsm);
+
+ LOGPFSM(fi, "Tx BSSGP %s\n", osmo_tlv_prot_msg_name(&osmo_pdef_bssgp, bgph->pdu_type));
+
+ return bssgp2_nsi_tx_ptp(bfp->nsi, bfp->nsei, bfp->bvci, msg, 0);
+}
+
+static int fi_tx_sig(struct osmo_fsm_inst *fi, struct msgb *msg)
+{
+ struct bssgp_normal_hdr *bgph = (struct bssgp_normal_hdr *) msgb_bssgph(msg);
+ struct bvc_fsm_priv *bfp = fi->priv;
+
+ OSMO_ASSERT(fi->fsm == &bssgp_bvc_fsm);
+
+ LOGPFSM(fi, "Tx BSSGP %s\n", osmo_tlv_prot_msg_name(&osmo_pdef_bssgp, bgph->pdu_type));
+
+ return bssgp2_nsi_tx_sig(bfp->nsi, bfp->nsei, msg, 0);
+}
+
+/* helper function to transmit BVC-RESET with right combination of conditional/optional IEs */
+static void _tx_bvc_reset(struct osmo_fsm_inst *fi, uint8_t cause)
+{
+ struct bvc_fsm_priv *bfp = fi->priv;
+ const uint8_t *features = NULL;
+ const uint8_t *features_ext = NULL;
+ uint8_t _features[2] = {
+ (bfp->features.advertised >> 0) & 0xff,
+ (bfp->features.advertised >> 8) & 0xff,
+ };
+ struct msgb *tx;
+
+ OSMO_ASSERT(fi->fsm == &bssgp_bvc_fsm);
+
+ /* transmit BVC-RESET to peer; RA-ID only present for PTP from BSS */
+ if (bfp->bvci == 0) {
+ features = &_features[0];
+ features_ext = &_features[1];
+ }
+ tx = bssgp2_enc_bvc_reset(bfp->bvci, cause,
+ bfp->bvci && !bfp->role_sgsn ? &bfp->ra_id : NULL,
+ bfp->cell_id, features, features_ext);
+ fi_tx_sig(fi, tx);
+}
+
+/* helper function to transmit BVC-RESET-ACK with right combination of conditional/optional IEs */
+static void _tx_bvc_reset_ack(struct osmo_fsm_inst *fi)
+{
+ struct bvc_fsm_priv *bfp = fi->priv;
+ const uint8_t *features = NULL;
+ const uint8_t *features_ext = NULL;
+ uint8_t _features[2] = {
+ (bfp->features.advertised >> 0) & 0xff,
+ (bfp->features.advertised >> 8) & 0xff,
+ };
+ struct msgb *tx;
+
+ OSMO_ASSERT(fi->fsm == &bssgp_bvc_fsm);
+
+ /* transmit BVC-RESET-ACK to peer; RA-ID only present for PTP from BSS -> SGSN */
+ if (bfp->bvci == 0) {
+ features = &_features[0];
+ features_ext = &_features[1];
+ }
+ tx = bssgp2_enc_bvc_reset_ack(bfp->bvci, bfp->bvci && !bfp->role_sgsn ? &bfp->ra_id : NULL,
+ bfp->cell_id, features, features_ext);
+ fi_tx_sig(fi, tx);
+}
+
+/* helper function to transmit BVC-STATUS with right combination of conditional/optional IEs */
+static void _tx_status(struct osmo_fsm_inst *fi, enum gprs_bssgp_cause cause, const struct msgb *rx)
+{
+ struct bvc_fsm_priv *bfp = fi->priv;
+ struct msgb *tx;
+ uint16_t *bvci = NULL;
+
+ /* GSM 08.18, 10.4.14.1: The BVCI must be included if (and only if) the
+ * cause is either "BVCI blocked" or "BVCI unknown" */
+ if (cause == BSSGP_CAUSE_UNKNOWN_BVCI || cause == BSSGP_CAUSE_BVCI_BLOCKED)
+ bvci = &bfp->bvci;
+
+ tx = bssgp2_enc_status(cause, bvci, rx, bfp->max_pdu_len);
+
+ if (msgb_bvci(rx) == 0)
+ fi_tx_sig(fi, tx);
+ else
+ fi_tx_ptp(fi, tx);
+}
+
+/* Update the features by bit-wise AND of advertised + received features */
+static void update_negotiated_features(struct osmo_fsm_inst *fi, const struct tlv_parsed *tp)
+{
+ struct bvc_fsm_priv *bfp = fi->priv;
+
+ OSMO_ASSERT(fi->fsm == &bssgp_bvc_fsm);
+
+ bfp->features.received = 0;
+
+ if (TLVP_PRES_LEN(tp, BSSGP_IE_FEATURE_BITMAP, 1))
+ bfp->features.received |= *TLVP_VAL(tp, BSSGP_IE_FEATURE_BITMAP);
+
+ if (TLVP_PRES_LEN(tp, BSSGP_IE_EXT_FEATURE_BITMAP, 1))
+ bfp->features.received |= (*TLVP_VAL(tp, BSSGP_IE_EXT_FEATURE_BITMAP) << 8);
+
+ bfp->features.negotiated = bfp->features.advertised & bfp->features.received;
+
+ LOGPFSML(fi, LOGL_NOTICE, "Updating features: Advertised 0x%04x, Received 0x%04x, Negotiated 0x%04x\n",
+ bfp->features.advertised, bfp->features.received, bfp->features.negotiated);
+}
+
+/* "tail" of each onenter() handler: Calling the state change notification call-back */
+static void _onenter_tail(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct bvc_fsm_priv *bfp = fi->priv;
+
+ if (prev_state == fi->state)
+ return;
+
+ if (bfp->ops && bfp->ops->state_chg_notification)
+ bfp->ops->state_chg_notification(bfp->nsei, bfp->bvci, prev_state, fi->state, bfp->ops_priv);
+}
+
+static void bssgp_bvc_fsm_null(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ /* we don't really expect anything in this state; all handled via allstate */
+ OSMO_ASSERT(0);
+}
+
+static void bssgp_bvc_fsm_blocked_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct bvc_fsm_priv *bfp = fi->priv;
+ /* signaling BVC can never be blocked */
+ OSMO_ASSERT(bfp->bvci != 0);
+ _onenter_tail(fi, prev_state);
+}
+
+static void bssgp_bvc_fsm_blocked(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct bvc_fsm_priv *bfp = fi->priv;
+ struct msgb *rx = NULL, *tx;
+ const struct tlv_parsed *tp = NULL;
+ uint8_t cause;
+
+ switch (event) {
+ case BSSGP_BVCFSM_E_RX_BLOCK_ACK:
+ rx = data;
+ tp = (const struct tlv_parsed *) msgb_bcid(rx);
+ /* If a BVC-BLOCK-ACK PDU is received by a BSS for the signalling BVC, the PDU is ignored. */
+ if (bfp->bvci == 0) {
+ LOGPFSML(fi, LOGL_ERROR, "Rx BVC-BLOCK-ACK on BVCI=0 is illegal\n");
+ if (!bfp->role_sgsn)
+ break;
+ _tx_status(fi, BSSGP_CAUSE_SEM_INCORR_PDU, rx);
+ break;
+ }
+ /* stop T1 timer */
+ osmo_fsm_inst_state_chg(fi, BSSGP_BVCFSM_S_BLOCKED, 0, 0);
+ break;
+ case BSSGP_BVCFSM_E_RX_BLOCK:
+ rx = data;
+ tp = (const struct tlv_parsed *) msgb_bcid(rx);
+ cause = *TLVP_VAL(tp, BSSGP_IE_CAUSE);
+ LOGPFSML(fi, LOGL_NOTICE, "Rx BVC-BLOCK (cause=%s)\n", bssgp_cause_str(cause));
+ /* If a BVC-BLOCK PDU is received by an SGSN for a blocked BVC, a BVC-BLOCK-ACK
+ * PDU shall be returned. */
+ if (bfp->role_sgsn) {
+ /* If a BVC-BLOCK PDU is received by an SGSN for
+ * the signalling BVC, the PDU is ignored */
+ if (bfp->bvci == 0)
+ break;
+ tx = bssgp2_enc_bvc_block_ack(bfp->bvci);
+ fi_tx_sig(fi, tx);
+ }
+ break;
+ case BSSGP_BVCFSM_E_RX_UNBLOCK:
+ rx = data;
+ tp = (const struct tlv_parsed *) msgb_bcid(rx);
+ LOGPFSML(fi, LOGL_NOTICE, "Rx BVC-UNBLOCK\n");
+ if (bfp->bvci == 0) {
+ LOGPFSML(fi, LOGL_ERROR, "Rx BVC-UNBLOCK on BVCI=0 is illegal\n");
+ /* If BVC-UNBLOCK PDU is received by an SGSN for the signalling BVC, the PDU is ignored.*/
+ if (bfp->role_sgsn)
+ break;
+ _tx_status(fi, BSSGP_CAUSE_SEM_INCORR_PDU, rx);
+ break;
+ }
+ if (!bfp->role_sgsn) {
+ LOGPFSML(fi, LOGL_ERROR, "Rx BVC-UNBLOCK on BSS is illegal\n");
+ _tx_status(fi, BSSGP_CAUSE_SEM_INCORR_PDU, rx);
+ break;
+ }
+ tx = bssgp2_enc_bvc_unblock_ack(bfp->bvci);
+ fi_tx_sig(fi, tx);
+ osmo_fsm_inst_state_chg(fi, BSSGP_BVCFSM_S_UNBLOCKED, T1_SECS, T1);
+ break;
+ case BSSGP_BVCFSM_E_REQ_UNBLOCK:
+ if (bfp->role_sgsn) {
+ LOGPFSML(fi, LOGL_ERROR, "SGSN side cannot initiate BVC unblock\n");
+ break;
+ }
+ if (bfp->bvci == 0) {
+ LOGPFSML(fi, LOGL_ERROR, "BVCI 0 cannot be unblocked\n");
+ break;
+ }
+ bfp->locally_blocked = false;
+ tx = bssgp2_enc_bvc_unblock(bfp->bvci);
+ fi_tx_sig(fi, tx);
+ osmo_fsm_inst_state_chg(fi, BSSGP_BVCFSM_S_UNBLOCKED, 0, 0);
+ break;
+ }
+}
+
+/* Waiting for RESET-ACK: Receive PDUs but don't transmit */
+static void bssgp_bvc_fsm_wait_reset_ack(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct bvc_fsm_priv *bfp = fi->priv;
+ const struct tlv_parsed *tp = NULL;
+ struct msgb *rx = NULL, *tx;
+ uint8_t cause;
+
+ switch (event) {
+ case BSSGP_BVCFSM_E_RX_RESET:
+ /* 48.018 Section 8.4.3: If the BSS (or SGSN) has sent a BVC-RESET PDU for a BVCI to
+ * the SGSN (or BSS) and is awaiting a BVC-RESET-ACK PDU in response, but instead
+ * receives a BVC-RESET PDU indicating the same BVCI, then this shall be interpreted
+ * as a BVC-RESET ACK PDU and the T2 timer shall be stopped. */
+ /* fall-through */
+ case BSSGP_BVCFSM_E_RX_RESET_ACK:
+ rx = data;
+ cause = bfp->last_reset_cause;
+ tp = (const struct tlv_parsed *) msgb_bcid(rx);
+ if (bfp->bvci == 0)
+ update_negotiated_features(fi, tp);
+ if (bfp->role_sgsn && bfp->bvci != 0)
+ bfp->cell_id = bssgp_parse_cell_id(&bfp->ra_id, TLVP_VAL(tp, BSSGP_IE_CELL_ID));
+ if (!bfp->role_sgsn && bfp->bvci != 0 && bfp->locally_blocked) {
+ /* initiate the blocking procedure */
+ /* transmit BVC-BLOCK, transition to BLOCKED state and start re-transmit timer */
+ tx = bssgp2_enc_bvc_block(bfp->bvci, bfp->block_cause);
+ fi_tx_sig(fi, tx);
+ osmo_fsm_inst_state_chg(fi, BSSGP_BVCFSM_S_BLOCKED, T1_SECS, T1);
+ } else
+ osmo_fsm_inst_state_chg(fi, BSSGP_BVCFSM_S_UNBLOCKED, 0, 0);
+ if (bfp->ops && bfp->ops->reset_ack_notification)
+ bfp->ops->reset_ack_notification(bfp->nsei, bfp->bvci, &bfp->ra_id, bfp->cell_id, cause, bfp->ops_priv);
+ break;
+ }
+}
+
+static void bssgp_bvc_fsm_unblocked(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct bssgp2_flow_ctrl rx_fc, *tx_fc;
+ struct bvc_fsm_priv *bfp = fi->priv;
+ const struct tlv_parsed *tp = NULL;
+ struct msgb *rx = NULL, *tx;
+ int rc;
+
+ switch (event) {
+ case BSSGP_BVCFSM_E_RX_UNBLOCK_ACK:
+ rx = data;
+ tp = (const struct tlv_parsed *) msgb_bcid(rx);
+ /* If BVC-UNBLOCK-ACK PDU is received by an BSS for the signalling BVC, the PDU is ignored. */
+ if (bfp->bvci == 0) {
+ LOGPFSML(fi, LOGL_ERROR, "Rx BVC-UNBLOCK-ACK on BVCI=0 is illegal\n");
+ if (!bfp->role_sgsn)
+ break;
+ _tx_status(fi, BSSGP_CAUSE_SEM_INCORR_PDU, rx);
+ break;
+ }
+ /* stop T1 timer */
+ osmo_fsm_inst_state_chg(fi, BSSGP_BVCFSM_S_UNBLOCKED, 0, 0);
+ break;
+ case BSSGP_BVCFSM_E_RX_UNBLOCK:
+ rx = data;
+ tp = (const struct tlv_parsed *) msgb_bcid(rx);
+ /* If a BVC-UNBLOCK PDU is received by an SGSN for a blocked BVC, a BVC-UNBLOCK-ACK
+ * PDU shall be returned. */
+ if (bfp->role_sgsn) {
+ /* If a BVC-UNBLOCK PDU is received by an SGSN for
+ * the signalling BVC, the PDU is ignored */
+ if (bfp->bvci == 0)
+ break;
+ bssgp_tx_simple_bvci(BSSGP_PDUT_BVC_UNBLOCK_ACK, bfp->nsei, bfp->bvci, 0);
+ }
+ break;
+ case BSSGP_BVCFSM_E_RX_BLOCK:
+ rx = data;
+ tp = (const struct tlv_parsed *) msgb_bcid(rx);
+ LOGPFSML(fi, LOGL_NOTICE, "Rx BVC-BLOCK (cause=%s)\n",
+ bssgp_cause_str(*TLVP_VAL(tp, BSSGP_IE_CAUSE)));
+ /* If a BVC-BLOCK PDU is received by an SGSN for the signalling BVC, the PDU is ignored */
+ if (bfp->bvci == 0) {
+ LOGPFSML(fi, LOGL_ERROR, "Rx BVC-BLOCK on BVCI=0 is illegal\n");
+ if (bfp->role_sgsn)
+ break;
+ _tx_status(fi, BSSGP_CAUSE_SEM_INCORR_PDU, rx);
+ break;
+ }
+ if (!bfp->role_sgsn) {
+ LOGPFSML(fi, LOGL_ERROR, "Rx BVC-BLOCK on BSS is illegal\n");
+ _tx_status(fi, BSSGP_CAUSE_SEM_INCORR_PDU, rx);
+ break;
+ }
+ /* transmit BVC-BLOCK-ACK, transition to BLOCKED state */
+ tx = bssgp2_enc_bvc_block_ack(bfp->bvci);
+ fi_tx_sig(fi, tx);
+ osmo_fsm_inst_state_chg(fi, BSSGP_BVCFSM_S_BLOCKED, 0, 0);
+ break;
+ case BSSGP_BVCFSM_E_REQ_BLOCK:
+ if (bfp->role_sgsn) {
+ LOGPFSML(fi, LOGL_ERROR, "SGSN may not initiate BVC-BLOCK\n");
+ break;
+ }
+ if (bfp->bvci == 0) {
+ LOGPFSML(fi, LOGL_ERROR, "BVCI 0 cannot be blocked\n");
+ break;
+ }
+ bfp->locally_blocked = true;
+ bfp->block_cause = *(uint8_t *)data;
+ /* transmit BVC-BLOCK, transition to BLOCKED state and start re-transmit timer */
+ tx = bssgp2_enc_bvc_block(bfp->bvci, bfp->block_cause);
+ fi_tx_sig(fi, tx);
+ osmo_fsm_inst_state_chg(fi, BSSGP_BVCFSM_S_BLOCKED, T1_SECS, T1);
+ break;
+ case BSSGP_BVCFSM_E_RX_FC_BVC:
+ rx = data;
+ tp = (const struct tlv_parsed *) msgb_bcid(rx);
+ /* we assume osmo_tlv_prot_* has been used before calling here to ensure this */
+ OSMO_ASSERT(bfp->role_sgsn);
+ rc = bssgp2_dec_fc_bvc(&rx_fc, tp);
+ if (rc < 0) {
+ _tx_status(fi, BSSGP_CAUSE_SEM_INCORR_PDU, rx);
+ break;
+ }
+ if (bfp->ops->rx_fc_bvc)
+ bfp->ops->rx_fc_bvc(bfp->nsei, bfp->bvci, &rx_fc, bfp->ops_priv);
+ tx = bssgp2_enc_fc_bvc_ack(rx_fc.tag);
+ fi_tx_ptp(fi, tx);
+ break;
+ case BSSGP_BVCFSM_E_RX_FC_BVC_ACK:
+ rx = data;
+ tp = (const struct tlv_parsed *) msgb_bcid(rx);
+ /* we assume osmo_tlv_prot_* has been used before calling here to ensure this */
+ OSMO_ASSERT(!bfp->role_sgsn);
+ break;
+ case BSSGP_BVCFSM_E_REQ_FC_BVC:
+ tx_fc = data;
+ tx = bssgp2_enc_fc_bvc(tx_fc, bfp->features.negotiated & (BSSGP_XFEAT_GBIT << 8) ?
+ &bfp->features.fc_granularity : NULL);
+ fi_tx_ptp(fi, tx);
+ break;
+ }
+}
+
+static void bssgp_bvc_fsm_allstate(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct bvc_fsm_priv *bfp = fi->priv;
+ uint8_t cause;
+ const struct tlv_parsed *tp = NULL;
+ struct msgb *rx = NULL;
+
+ switch (event) {
+ case BSSGP_BVCFSM_E_REQ_RESET:
+ bfp->locally_blocked = false;
+ cause = bfp->last_reset_cause = *(uint8_t *) data;
+ _tx_bvc_reset(fi, cause);
+ osmo_fsm_inst_state_chg(fi, BSSGP_BVCFSM_S_WAIT_RESET_ACK, T2_SECS, T2);
+#if 0 /* not sure if we really should notify the application if itself has requested the reset? */
+ if (bfp->ops && bfp->ops->reset_notification)
+ bfp->ops->reset_notification(bfp->nsei, bfp->bvci, NULL, 0, cause, bfp->ops_priv);
+#endif
+ break;
+ case BSSGP_BVCFSM_E_RX_RESET:
+ rx = data;
+ tp = (const struct tlv_parsed *) msgb_bcid(rx);
+ cause = *TLVP_VAL(tp, BSSGP_IE_CAUSE);
+ if (bfp->role_sgsn && bfp->bvci != 0)
+ bfp->cell_id = bssgp_parse_cell_id(&bfp->ra_id, TLVP_VAL(tp, BSSGP_IE_CELL_ID));
+ LOGPFSML(fi, LOGL_NOTICE, "Rx BVC-RESET (cause=%s)\n", bssgp_cause_str(cause));
+ if (bfp->bvci == 0)
+ update_negotiated_features(fi, tp);
+ _tx_bvc_reset_ack(fi);
+ osmo_fsm_inst_state_chg(fi, BSSGP_BVCFSM_S_UNBLOCKED, 0, 0);
+ if (bfp->ops && bfp->ops->reset_notification) {
+ bfp->ops->reset_notification(bfp->nsei, bfp->bvci, &bfp->ra_id, bfp->cell_id,
+ cause, bfp->ops_priv);
+ }
+ break;
+ }
+}
+
+static int bssgp_bvc_fsm_timer_cb(struct osmo_fsm_inst *fi)
+{
+ struct bvc_fsm_priv *bfp = fi->priv;
+ struct msgb *tx;
+
+ switch (fi->T) {
+ case T1:
+ switch (fi->state) {
+ case BSSGP_BVCFSM_S_BLOCKED:
+ /* re-transmit BVC-BLOCK */
+ tx = bssgp2_enc_bvc_block(bfp->bvci, bfp->block_cause);
+ fi_tx_sig(fi, tx);
+ osmo_fsm_inst_state_chg(fi, BSSGP_BVCFSM_S_BLOCKED, T1_SECS, T1);
+ break;
+ case BSSGP_BVCFSM_S_UNBLOCKED:
+ /* re-transmit BVC-UNBLOCK */
+ tx = bssgp2_enc_bvc_unblock(bfp->bvci);
+ fi_tx_sig(fi, tx);
+ osmo_fsm_inst_state_chg(fi, BSSGP_BVCFSM_S_UNBLOCKED, T1_SECS, T1);
+ break;
+ }
+ break;
+ case T2:
+ switch (fi->state) {
+ case BSSGP_BVCFSM_S_WAIT_RESET_ACK:
+ /* re-transmit BVC-RESET */
+ _tx_bvc_reset(fi, bfp->last_reset_cause);
+ osmo_fsm_inst_state_chg(fi, BSSGP_BVCFSM_S_WAIT_RESET_ACK, T2_SECS, T2);
+ break;
+ case BSSGP_BVCFSM_S_UNBLOCKED:
+ /* re-transmit BVC-RESET-ACK */
+ _tx_bvc_reset_ack(fi);
+ osmo_fsm_inst_state_chg(fi, BSSGP_BVCFSM_S_UNBLOCKED, T2_SECS, T2);
+ break;
+ }
+ break;
+ default:
+ OSMO_ASSERT(0);
+ break;
+ }
+ return 0;
+}
+
+
+
+static const struct osmo_fsm_state bssgp_bvc_fsm_states[] = {
+ [BSSGP_BVCFSM_S_NULL] = {
+ /* initial state from which we must do a RESET */
+ .name = "NULL",
+ .in_event_mask = 0,
+ .out_state_mask = S(BSSGP_BVCFSM_S_WAIT_RESET_ACK) |
+ S(BSSGP_BVCFSM_S_UNBLOCKED),
+ .action = bssgp_bvc_fsm_null,
+ },
+ [BSSGP_BVCFSM_S_BLOCKED] = {
+ .name = "BLOCKED",
+ .in_event_mask = S(BSSGP_BVCFSM_E_RX_UNBLOCK) |
+ S(BSSGP_BVCFSM_E_RX_BLOCK) |
+ S(BSSGP_BVCFSM_E_RX_BLOCK_ACK) |
+ S(BSSGP_BVCFSM_E_REQ_UNBLOCK),
+ .out_state_mask = S(BSSGP_BVCFSM_S_WAIT_RESET_ACK) |
+ S(BSSGP_BVCFSM_S_UNBLOCKED) |
+ S(BSSGP_BVCFSM_S_BLOCKED),
+ .action = bssgp_bvc_fsm_blocked,
+ .onenter = bssgp_bvc_fsm_blocked_onenter,
+ },
+ [BSSGP_BVCFSM_S_WAIT_RESET_ACK]= {
+ .name = "WAIT_RESET_ACK",
+ .in_event_mask = S(BSSGP_BVCFSM_E_RX_RESET_ACK) |
+ S(BSSGP_BVCFSM_E_RX_RESET),
+ .out_state_mask = S(BSSGP_BVCFSM_S_UNBLOCKED) |
+ S(BSSGP_BVCFSM_S_BLOCKED) |
+ S(BSSGP_BVCFSM_S_WAIT_RESET_ACK),
+ .action = bssgp_bvc_fsm_wait_reset_ack,
+ .onenter = _onenter_tail,
+ },
+
+ [BSSGP_BVCFSM_S_UNBLOCKED] = {
+ .name = "UNBLOCKED",
+ .in_event_mask = S(BSSGP_BVCFSM_E_RX_BLOCK) |
+ S(BSSGP_BVCFSM_E_RX_UNBLOCK) |
+ S(BSSGP_BVCFSM_E_RX_UNBLOCK_ACK) |
+ S(BSSGP_BVCFSM_E_REQ_BLOCK) |
+ S(BSSGP_BVCFSM_E_RX_FC_BVC) |
+ S(BSSGP_BVCFSM_E_RX_FC_BVC_ACK) |
+ S(BSSGP_BVCFSM_E_REQ_FC_BVC),
+ .out_state_mask = S(BSSGP_BVCFSM_S_BLOCKED) |
+ S(BSSGP_BVCFSM_S_WAIT_RESET_ACK) |
+ S(BSSGP_BVCFSM_S_UNBLOCKED),
+ .action = bssgp_bvc_fsm_unblocked,
+ .onenter = _onenter_tail,
+ },
+};
+
+static struct osmo_fsm bssgp_bvc_fsm = {
+ .name = "BSSGP-BVC",
+ .states = bssgp_bvc_fsm_states,
+ .num_states = ARRAY_SIZE(bssgp_bvc_fsm_states),
+ .allstate_event_mask = S(BSSGP_BVCFSM_E_REQ_RESET) |
+ S(BSSGP_BVCFSM_E_RX_RESET),
+ .allstate_action = bssgp_bvc_fsm_allstate,
+ .timer_cb = bssgp_bvc_fsm_timer_cb,
+ .log_subsys = DLBSSGP,
+ .event_names = ptp_bvc_event_names,
+};
+
+static struct osmo_fsm_inst *
+_bvc_fsm_alloc(void *ctx, struct gprs_ns2_inst *nsi, bool role_sgsn, uint16_t nsei, uint16_t bvci)
+{
+ struct osmo_fsm_inst *fi;
+ struct bvc_fsm_priv *bfp;
+ char idbuf[64];
+
+ /* TODO: encode our role in the id string? */
+ snprintf(idbuf, sizeof(idbuf), "NSE%05u-BVC%05u", nsei, bvci);
+
+ fi = osmo_fsm_inst_alloc(&bssgp_bvc_fsm, ctx, NULL, LOGL_INFO, idbuf);
+ if (!fi)
+ return NULL;
+
+ bfp = talloc_zero(fi, struct bvc_fsm_priv);
+ if (!bfp) {
+ osmo_fsm_inst_free(fi);
+ return NULL;
+ }
+ fi->priv = bfp;
+
+ bfp->nsi = nsi;
+ bfp->role_sgsn = role_sgsn;
+ bfp->nsei = nsei;
+ bfp->bvci = bvci;
+ bfp->max_pdu_len = UINT16_MAX;
+
+ return fi;
+}
+
+/*! Allocate a SIGNALING-BVC FSM for the BSS role (facing a remote SGSN).
+ * \param[in] ctx talloc context from which to allocate
+ * \param[in] nsi NS Instance on which this BVC operates
+ * \param[in] nsei NS Entity Identifier on which this BVC operates
+ * \param[in] features Feature [byte 0] and Extended Feature [byte 1] bitmap
+ * \returns newly-allocated FSM Instance; NULL in case of error */
+struct osmo_fsm_inst *
+bssgp_bvc_fsm_alloc_sig_bss(void *ctx, struct gprs_ns2_inst *nsi, uint16_t nsei, uint32_t features)
+{
+ struct osmo_fsm_inst *fi = _bvc_fsm_alloc(ctx, nsi, false, nsei, 0);
+ struct bvc_fsm_priv *bfp;
+
+ if (!fi)
+ return NULL;
+
+ bfp = fi->priv;
+ bfp->features.advertised = features;
+
+ return fi;
+}
+
+/*! Allocate a PTP-BVC FSM for the BSS role (facing a remote SGSN).
+ * \param[in] ctx talloc context from which to allocate
+ * \param[in] nsi NS Instance on which this BVC operates
+ * \param[in] nsei NS Entity Identifier on which this BVC operates
+ * \param[in] bvci BVCI of this FSM
+ * \param[in] ra_id Routing Area Identity of the cell (reported to SGSN)
+ * \param[in] cell_id Cell Identifier of the cell (reported to SGSN)
+ * \returns newly-allocated FSM Instance; NULL in case of error */
+struct osmo_fsm_inst *
+bssgp_bvc_fsm_alloc_ptp_bss(void *ctx, struct gprs_ns2_inst *nsi, uint16_t nsei,
+ uint16_t bvci, const struct gprs_ra_id *ra_id, uint16_t cell_id)
+{
+ struct osmo_fsm_inst *fi;
+ struct bvc_fsm_priv *bfp;
+
+ OSMO_ASSERT(bvci >= 2);
+ OSMO_ASSERT(ra_id);
+
+ fi = _bvc_fsm_alloc(ctx, nsi, false, nsei, bvci);
+ if (!fi)
+ return NULL;
+
+ bfp = fi->priv;
+ bfp->ra_id = *ra_id;
+ bfp->cell_id = cell_id;
+
+ return fi;
+}
+
+/*! Allocate a SIGNALING-BVC FSM for the SGSN role (facing a remote BSS).
+ * \param[in] ctx talloc context from which to allocate
+ * \param[in] nsi NS Instance on which this BVC operates
+ * \param[in] nsei NS Entity Identifier on which this BVC operates
+ * \param[in] features Feature [byte 0] and Extended Feature [byte 1] bitmap
+ * \returns newly-allocated FSM Instance; NULL in case of error */
+struct osmo_fsm_inst *
+bssgp_bvc_fsm_alloc_sig_sgsn(void *ctx, struct gprs_ns2_inst *nsi, uint16_t nsei, uint32_t features)
+{
+ struct osmo_fsm_inst *fi = _bvc_fsm_alloc(ctx, nsi, true, nsei, 0);
+ struct bvc_fsm_priv *bfp;
+
+ if (!fi)
+ return NULL;
+
+ bfp = fi->priv;
+ bfp->features.advertised = features;
+
+ return fi;
+}
+
+/*! Allocate a PTP-BVC FSM for the SGSN role (facing a remote BSS).
+ * \param[in] ctx talloc context from which to allocate
+ * \param[in] nsi NS Instance on which this BVC operates
+ * \param[in] nsei NS Entity Identifier on which this BVC operates
+ * \param[in] bvci BVCI of this FSM
+ * \returns newly-allocated FSM Instance; NULL in case of error */
+struct osmo_fsm_inst *
+bssgp_bvc_fsm_alloc_ptp_sgsn(void *ctx, struct gprs_ns2_inst *nsi, uint16_t nsei, uint16_t bvci)
+{
+ struct osmo_fsm_inst *fi;
+
+ OSMO_ASSERT(bvci >= 2);
+
+ fi = _bvc_fsm_alloc(ctx, nsi, true, nsei, bvci);
+ if (!fi)
+ return NULL;
+
+ return fi;
+}
+
+/*! Set the 'operations' callbacks + private data.
+ * \param[in] fi FSM instance for which the data shall be set
+ * \param[in] ops BSSGP BVC FSM operations (call-back functions) to register
+ * \param[in] ops_priv opaque/private data pointer passed through to call-backs */
+void bssgp_bvc_fsm_set_ops(struct osmo_fsm_inst *fi, const struct bssgp_bvc_fsm_ops *ops, void *ops_priv)
+{
+ struct bvc_fsm_priv *bfp = fi->priv;
+
+ OSMO_ASSERT(fi->fsm == &bssgp_bvc_fsm);
+
+ bfp->ops = ops;
+ bfp->ops_priv = ops_priv;
+}
+
+/*! Return if the given BVC FSM is in UNBLOCKED state. */
+bool bssgp_bvc_fsm_is_unblocked(struct osmo_fsm_inst *fi)
+{
+ return fi->state == BSSGP_BVCFSM_S_UNBLOCKED;
+}
+
+/*! Determine the cause value why given BVC FSM is blocked. */
+uint8_t bssgp_bvc_fsm_get_block_cause(struct osmo_fsm_inst *fi)
+{
+ struct bvc_fsm_priv *bfp = fi->priv;
+
+ OSMO_ASSERT(fi->fsm == &bssgp_bvc_fsm);
+ return bfp->block_cause;
+}
+
+/*! Return the advertised features / extended features. */
+uint32_t bssgp_bvc_fsm_get_features_advertised(struct osmo_fsm_inst *fi)
+{
+ struct bvc_fsm_priv *bfp = fi->priv;
+
+ OSMO_ASSERT(fi->fsm == &bssgp_bvc_fsm);
+ return bfp->features.advertised;
+}
+
+/*! Return the received features / extended features. */
+uint32_t bssgp_bvc_fsm_get_features_received(struct osmo_fsm_inst *fi)
+{
+ struct bvc_fsm_priv *bfp = fi->priv;
+
+ OSMO_ASSERT(fi->fsm == &bssgp_bvc_fsm);
+ return bfp->features.received;
+}
+
+/*! Return the negotiated features / extended features. */
+uint32_t bssgp_bvc_fsm_get_features_negotiated(struct osmo_fsm_inst *fi)
+{
+ struct bvc_fsm_priv *bfp = fi->priv;
+
+ OSMO_ASSERT(fi->fsm == &bssgp_bvc_fsm);
+ return bfp->features.negotiated;
+}
+
+/*! Set the maximum size of a BSSGP PDU.
+ *! On the NS layer this corresponds to the size of an NS SDU in NS-UNITDATA (3GPP TS 48.016 Ch. 9.2.10) */
+void bssgp_bvc_fsm_set_max_pdu_len(struct osmo_fsm_inst *fi, uint16_t max_pdu_len) {
+ struct bvc_fsm_priv *bfp = fi->priv;
+
+ OSMO_ASSERT(fi->fsm == &bssgp_bvc_fsm);
+ bfp->max_pdu_len = max_pdu_len;
+}
+
+/*! Return the maximum size of a BSSGP PDU
+ *! On the NS layer this corresponds to the size of an NS SDU in NS-UNITDATA (3GPP TS 48.016 Ch. 9.2.10) */
+uint16_t bssgp_bvc_fsm_get_max_pdu_len(const struct osmo_fsm_inst *fi)
+{
+ const struct bvc_fsm_priv *bfp = fi->priv;
+
+ OSMO_ASSERT(fi->fsm == &bssgp_bvc_fsm);
+ return bfp->max_pdu_len;
+}
+
+
+static __attribute__((constructor)) void on_dso_load_bvc_fsm(void)
+{
+ OSMO_ASSERT(osmo_fsm_register(&bssgp_bvc_fsm) == 0);
+}
diff --git a/src/gb/common_vty.c b/src/gb/common_vty.c
index eb665d52..ad3dea23 100644
--- a/src/gb/common_vty.c
+++ b/src/gb/common_vty.c
@@ -40,15 +40,21 @@
int gprs_log_filter_fn(const struct log_context *ctx,
struct log_target *tar)
{
+ const void *nse = ctx->ctx[LOG_CTX_GB_NSE];
const void *nsvc = ctx->ctx[LOG_CTX_GB_NSVC];
const void *bvc = ctx->ctx[LOG_CTX_GB_BVC];
+ /* Filter on the NS Entity */
+ if ((tar->filter_map & (1 << LOG_FLT_GB_NSE)) != 0
+ && nse && (nse == tar->filter_data[LOG_FLT_GB_NSE]))
+ return 1;
+
/* Filter on the NS Virtual Connection */
if ((tar->filter_map & (1 << LOG_FLT_GB_NSVC)) != 0
&& nsvc && (nsvc == tar->filter_data[LOG_FLT_GB_NSVC]))
return 1;
- /* Filter on the NS Virtual Connection */
+ /* Filter on the BSSGP Virtual Connection */
if ((tar->filter_map & (1 << LOG_FLT_GB_BVC)) != 0
&& bvc && (bvc == tar->filter_data[LOG_FLT_GB_BVC]))
return 1;
@@ -57,4 +63,4 @@ int gprs_log_filter_fn(const struct log_context *ctx,
}
-int DNS, DBSSGP;
+int DNS;
diff --git a/src/gb/common_vty.h b/src/gb/common_vty.h
index 801d2dab..8e883319 100644
--- a/src/gb/common_vty.h
+++ b/src/gb/common_vty.h
@@ -3,5 +3,5 @@
#include <osmocom/vty/command.h>
#include <osmocom/core/logging.h>
-extern int DNS, DBSSGP;
+extern int DNS;
diff --git a/src/gb/frame_relay.c b/src/gb/frame_relay.c
new file mode 100644
index 00000000..e973b915
--- /dev/null
+++ b/src/gb/frame_relay.c
@@ -0,0 +1,1051 @@
+/*! \file frame_relay.c
+ * Implement frame relay/PVC by Q.933
+ */
+/* (C) 2020 Harald Welte <laforge@gnumonks.org>
+ * (C) 2020 sysmocom - s.f.m.c. GmbH
+ * Author: Alexander Couzens <lynxis@fe80.eu>
+ *
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include <osmocom/gprs/frame_relay.h>
+#include <osmocom/core/endian.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/tdef.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/core/msgb.h>
+
+#include <osmocom/gsm/tlv.h>
+
+#define LOGPFRL(frl, lvl, fmt, args ...) \
+ LOGP(DFR, lvl, "%s: " fmt, (frl)->name, ## args)
+
+#define DFR DLNS
+
+/* Table 4-2/Q.931 */
+enum q931_msgtype {
+ /* Call establishment message */
+ Q931_MSGT_ALERTING = 0x01,
+ Q931_MSGT_CALL_PROCEEDING = 0x02,
+ Q931_MSGT_CONNECT = 0x07,
+ Q931_MSGT_CONNECT_ACK = 0x0f,
+ Q931_MSGT_PROGRESS = 0x03,
+ Q931_MSGT_SETUP = 0x05,
+ Q931_MSGT_SETUP_ACK = 0x0d,
+ /* Call information phase message */
+ Q931_MSGT_RESUME = 0x26,
+ Q931_MSGT_RESUME_ACK = 0x2e,
+ Q931_MSGT_RESUME_REJ = 0x22,
+ Q931_MSGT_SUSPEND = 0x25,
+ Q931_MSGT_SUSPEND_ACK = 0x2d,
+ Q931_MSGT_USER_INFO = 0x20,
+ /* Call clearing message */
+ Q931_MSGT_DISCONNECT = 0x45,
+ Q931_MSGT_RELEASE = 0x4d,
+ Q931_MSGT_RELEASE_COMPLETE = 0x5a,
+ Q931_MSGT_RESTART = 0x46,
+ Q931_MSGT_RESTART_ACK = 0x4e,
+ /* Miscellaneous messages */
+ Q931_MSGT_SEGMENT = 0x60,
+ Q931_MSGT_CONGESTION_CONTROL = 0x79,
+ Q931_MSGT_IFORMATION = 0x7b,
+ Q931_MSGT_NOTIFY = 0x6e,
+ Q931_MSGT_STATUS = 0x7d,
+ Q931_MSGT_STATUS_ENQUIRY = 0x75,
+};
+
+
+/* Figure A.1/Q.933 Report type information element */
+enum q933_type_of_report {
+ Q933_REPT_FULL_STATUS = 0x00,
+ Q933_REPT_LINK_INTEGRITY_VERIF = 0x01,
+ Q933_REPT_SINGLE_PVC_ASYNC_STS = 0x02,
+};
+
+/* Q.933 Section A.3 */
+enum q933_iei {
+ Q933_IEI_REPORT_TYPE = 0x51,
+ Q933_IEI_LINK_INT_VERIF = 0x53,
+ Q933_IEI_PVC_STATUS = 0x57,
+};
+
+/* Q.933 Section A.3.3 */
+enum q933_pvc_status {
+ Q933_PVC_STATUS_DLC_ACTIVE = 0x02,
+ Q933_PVC_STATUS_DLC_DELETE = 0x04,
+ Q933_PVC_STATUS_DLC_NEW = 0x08,
+};
+
+
+
+#define LAPF_UI 0x03 /* UI control word */
+#define Q931_PDISC_CC 0x08 /* protocol discriminator */
+#define LMI_Q933A_CALLREF 0x00 /* NULL call-ref */
+
+/* LMI DLCI values */
+#define LMI_Q933A_DLCI 0 /* Q.933A DLCI */
+#define LMI_CISCO_DLCI 1023 /* Cisco DLCI */
+
+/* maximum of supported */
+#define MAX_SUPPORTED_PVC 10
+
+/* TODO: add counters since good connection */
+
+/* Message header of the L3 payload of a Q.933 Annex A message */
+struct q933_a_hdr {
+ uint8_t prot_disc;
+ uint8_t call_ref;
+ uint8_t msg_type;
+} __attribute__((packed));
+
+/* Value part of the Q.933 Annex A.3.3 IE */
+struct q933_a_pvc_sts {
+#if OSMO_IS_LITTLE_ENDIAN
+ uint8_t dlci_msb:6,
+ spare:1,
+ ext0:1;
+ uint8_t space1:3,
+ dlci_lsb:4,
+ ext1:1;
+ uint8_t reserved:1,
+ active:1,
+ delete:1,
+ new:1,
+ spare2:3,
+ ext2:1;
+
+#elif OSMO_IS_BIG_ENDIAN
+/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianness.py) */
+ uint8_t ext0:1, spare:1, dlci_msb:6;
+ uint8_t ext1:1, dlci_lsb:4, space1:3;
+ uint8_t ext2:1, spare2:3, new:1, delete:1, active:1, reserved:1;
+#endif
+} __attribute__((packed));
+
+/* RX Message: 14 [ 00 01 03 08 00 75 95 01 01 00 03 02 01 00 ] */
+/* RX Message: 13 [ 00 01 03 08 00 75 51 01 00 53 02 01 00 ] */
+
+const struct value_string osmo_fr_role_names[] = {
+ { FR_ROLE_USER_EQUIPMENT, "USER" },
+ { FR_ROLE_NETWORK_EQUIPMENT, "NETWORK" },
+ { 0, NULL }
+};
+
+/* Table A.4/Q.933 */
+struct osmo_tdef fr_tdefs[] = {
+ {
+ .T=391,
+ .default_val = 10,
+ .min_val = 5,
+ .max_val = 30,
+ .desc = "Link integrity verification polling timer",
+ .unit = OSMO_TDEF_S,
+ }, {
+ .T=392,
+ .default_val = 15,
+ .min_val = 5,
+ .max_val = 30,
+ .desc = "Polling verification timer",
+ .unit = OSMO_TDEF_S,
+ },
+ {}
+};
+
+static const struct tlv_definition q933_att_tlvdef = {
+ .def = {
+ [Q933_IEI_REPORT_TYPE] = { TLV_TYPE_TLV },
+ [Q933_IEI_LINK_INT_VERIF] = { TLV_TYPE_TLV },
+ [Q933_IEI_PVC_STATUS] = { TLV_TYPE_TLV },
+ },
+};
+
+static void check_link_state(struct osmo_fr_link *link, bool valid);
+
+static inline uint16_t q922_to_dlci(const uint8_t *hdr)
+{
+ return ((hdr[0] & 0xFC) << 2) | ((hdr[1] & 0xF0) >> 4);
+}
+
+
+static inline void dlci_to_q922(uint8_t *hdr, uint16_t dlci)
+{
+ hdr[0] = (dlci >> 2) & 0xFC;
+ hdr[1] = ((dlci << 4) & 0xF0) | 0x01;
+}
+
+static void dlc_set_active(struct osmo_fr_dlc *dlc, bool active)
+{
+ if (active == dlc->active)
+ return;
+
+ dlc->active = active;
+
+ LOGPFRL(dlc->link, LOGL_NOTICE, "DLCI %u became %s\n", dlc->dlci, active ? "active" : "inactive");
+ if (dlc->status_cb)
+ dlc->status_cb(dlc, dlc->cb_data, active);
+}
+
+/* allocate a message buffer and put Q.933 Annex A headers (L2 + L3) */
+static struct msgb *q933_msgb_alloc(uint16_t dlci, uint8_t prot_disc, uint8_t msg_type)
+{
+ struct msgb *msg = msgb_alloc_headroom(1600+64, 64, "FR Q.933 Tx");
+ struct q933_a_hdr *qh;
+
+ if (!msg)
+ return NULL;
+
+ msg->l1h = msgb_put(msg, 2);
+ dlci_to_q922(msg->l1h, dlci);
+
+ /* LAPF UI control */
+ msg->l2h = msgb_put(msg, 1);
+ *msg->l2h = LAPF_UI;
+
+ msg->l3h = msgb_put(msg, sizeof(*qh));
+ qh = (struct q933_a_hdr *) msg->l3h;
+ qh->prot_disc = prot_disc;
+ qh->call_ref = LMI_Q933A_CALLREF;
+ qh->msg_type = msg_type;
+
+ return msg;
+}
+
+/* obtain the [next] transmit sequence number */
+static uint8_t link_get_tx_seq(struct osmo_fr_link *link)
+{
+ /* The {user equipment, network} increments the send sequence
+ * counter using modulo 256. The value zero is skipped. */
+ link->last_tx_seq++;
+ if (link->last_tx_seq == 0)
+ link->last_tx_seq++;
+
+ return link->last_tx_seq;
+}
+
+/* Append PVC Status IE according to Q.933 A.3.2 */
+static void msgb_put_link_int_verif(struct msgb *msg, struct osmo_fr_link *link)
+{
+ uint8_t link_int_tx[2];
+ link_int_tx[0] = link_get_tx_seq(link);
+ link_int_tx[1] = link->last_rx_seq;
+ msgb_tlv_put(msg, Q933_IEI_LINK_INT_VERIF, 2, link_int_tx);
+}
+
+static void dlc_destroy(struct osmo_fr_dlc *dlc)
+{
+ llist_del(&dlc->list);
+ talloc_free(dlc);
+}
+
+/* Append PVC Status IE according to Q.933 A.3.3 */
+static void msgb_put_pvc_status(struct msgb *msg, struct osmo_fr_dlc *dlc)
+{
+ uint8_t ie[3];
+
+ ie[0] = (dlc->dlci >> 4) & 0x3f;
+ /* extension bits */
+ ie[1] = 0x80 | ((dlc->dlci & 0xf) << 3);
+ /* extension bits */
+ ie[2] = 0x80;
+
+ /* FIXME: validate: this status should be added as long it's not yet acked by the remote */
+ if (dlc->active)
+ ie[2] |= Q933_PVC_STATUS_DLC_ACTIVE;
+
+ if (dlc->add) {
+ ie[2] |= Q933_PVC_STATUS_DLC_NEW;
+ /* we've reported it as new once, reset the status */
+ }
+
+ if (dlc->del) {
+ ie[2] |= Q933_PVC_STATUS_DLC_DELETE;
+ /* we've reported it as deleted once, destroy it */
+ dlc_destroy(dlc);
+ }
+
+ msgb_tlv_put(msg, Q933_IEI_PVC_STATUS, 3, ie);
+}
+
+/* Send a Q.933 STATUS ENQUIRY given type over given link */
+static int tx_lmi_q933_status_enq(struct osmo_fr_link *link, uint8_t rep_type)
+{
+ struct msgb *resp;
+
+ resp = q933_msgb_alloc(0, Q931_PDISC_CC, Q931_MSGT_STATUS_ENQUIRY);
+ if (!resp)
+ return -1;
+ resp->dst = link;
+ link->expected_rep = rep_type;
+
+ /* Table A.2/Q.933 */
+ msgb_tlv_put(resp, Q933_IEI_REPORT_TYPE, 1, &rep_type);
+ msgb_put_link_int_verif(resp, link);
+
+ return link->tx_cb(link->cb_data, resp);
+}
+
+/* Send a Q.933 STATUS of given type over given link */
+static int tx_lmi_q933_status(struct osmo_fr_link *link, uint8_t rep_type)
+{
+ struct osmo_fr_dlc *dlc;
+ struct msgb *resp;
+
+ resp = q933_msgb_alloc(0, Q931_PDISC_CC, Q931_MSGT_STATUS);
+ if (!resp)
+ return -1;
+
+ resp->dst = link;
+
+ /* Table A.1/Q.933 */
+ msgb_tlv_put(resp, Q933_IEI_REPORT_TYPE, 1, &rep_type);
+ switch (rep_type) {
+ case Q933_REPT_FULL_STATUS:
+ msgb_put_link_int_verif(resp, link);
+ llist_for_each_entry(dlc, &link->dlc_list, list) {
+ if (dlc->add || dlc->del)
+ dlc->state_send = true;
+
+ msgb_put_pvc_status(resp, dlc);
+ }
+ break;
+ case Q933_REPT_LINK_INTEGRITY_VERIF:
+ msgb_put_link_int_verif(resp, link);
+ llist_for_each_entry(dlc, &link->dlc_list, list) {
+ if (dlc->add || dlc->del) {
+ msgb_put_pvc_status(resp, dlc);
+ dlc->state_send = true;
+ }
+ }
+ break;
+ case Q933_REPT_SINGLE_PVC_ASYNC_STS:
+ llist_for_each_entry(dlc, &link->dlc_list, list)
+ msgb_put_pvc_status(resp, dlc);
+ break;
+ }
+
+ return link->tx_cb(link->cb_data, resp);
+}
+
+
+static void link_set_failed(struct osmo_fr_link *link)
+{
+ struct osmo_fr_dlc *dlc;
+
+ LOGPFRL(link, LOGL_NOTICE, "Link failed\n");
+ link->state = false;
+ if (link->status_cb)
+ link->status_cb(link, link->cb_data, link->state);
+
+ llist_for_each_entry(dlc, &link->dlc_list, list) {
+ dlc_set_active(dlc, false);
+ }
+}
+
+/* Q.933 */
+static int rx_lmi_q933_status_enq(struct msgb *msg, struct tlv_parsed *tp)
+{
+ struct osmo_fr_link *link = msg->dst;
+ struct osmo_fr_dlc *dlc;
+ const uint8_t *link_int_rx;
+ uint8_t rep_type;
+
+ OSMO_ASSERT(link);
+
+ if (link->role == FR_ROLE_USER_EQUIPMENT) {
+ LOGPFRL(link, LOGL_ERROR, "STATUS-ENQ aren't supported in role user\n");
+ return -1;
+ }
+
+ /* check for mandatory IEs */
+ if (!TLVP_PRES_LEN(tp, Q933_IEI_REPORT_TYPE, 1) ||
+ !TLVP_PRES_LEN(tp, Q933_IEI_LINK_INT_VERIF, 2))
+ return -1;
+
+ rep_type = *TLVP_VAL(tp, Q933_IEI_REPORT_TYPE);
+
+ link_int_rx = TLVP_VAL(tp, Q933_IEI_LINK_INT_VERIF);
+ link->last_rx_seq = link_int_rx[0];
+
+ /* this is a bit of a hack. Q.933 explicitly forbids either side from ever
+ * sending a sequence number of '0'. Values start from '1' and are modulo 256,
+ * but '0' is always skipped. So if the peer is sending us a "last received
+ * sequence number of '0' it means it has not yet received any packets from us,
+ * which in turn can only mean that it has just been restarted. Let's treat
+ * this as "service affecting condition" and notify upper layers. This helps
+ * particularly in recovering from rapidly re-starting peers, where the Q.933
+ * nor NS have time to actually detect the connection was lost. Se OS#4974 */
+ if (link_int_rx[1] == 0) {
+ link_set_failed(link);
+ /* the network checks the receive sequence number received from
+ * the user equipment against its send sequence counter */
+ } else if (link_int_rx[1] != link->last_tx_seq) {
+ check_link_state(link, false);
+ link->err_count++;
+ } else {
+ check_link_state(link, true);
+ /* confirm DLC state changes */
+ llist_for_each_entry(dlc, &link->dlc_list, list) {
+ if (!dlc->state_send)
+ continue;
+
+ if (dlc->add) {
+ dlc_set_active(dlc, link->state);
+ dlc->add = false;
+ }
+
+ if (dlc->del) {
+ dlc->del = false;
+ }
+
+ dlc->state_send = false;
+ }
+ }
+
+
+ /* The network responds to each STATUS ENQUIRY message with a
+ * STATUS message and resets the T392 timer */
+ osmo_timer_schedule(&link->t392, osmo_tdef_get(link->net->T_defs, 392, OSMO_TDEF_S, 15), 0);
+
+ return tx_lmi_q933_status(link, rep_type);
+}
+
+/* check if the link become active.
+ * The link becomes active when enough times a STATUS/STATUS ENQUIRY arrives without any loss.
+ * Look at the last N393 STATUS/STATUS ENQUIRY PDUs. The link is valid if at least N392
+ * got received.
+ * param[in] valid contains the status of the last packet */
+static void check_link_state(struct osmo_fr_link *link, bool valid)
+{
+ unsigned int last, i;
+ unsigned int carry = 0;
+ struct osmo_fr_dlc *dlc;
+
+ link->succeed <<= 1;
+ if (valid)
+ link->succeed |= 1;
+
+ /* count the bits */
+ last = link->succeed & ((1 << link->net->n393) - 1);
+ for (i = 0; i < link->net->n393; i++)
+ if (last & (1 << i))
+ carry++;
+
+ if (link->net->n393 - carry >= link->net->n392) {
+ /* failing link */
+ if (!link->state)
+ return;
+
+ link_set_failed(link);
+ } else {
+ /* good link */
+ if (link->state)
+ return;
+
+ LOGPFRL(link, LOGL_NOTICE, "Link recovered\n");
+ link->state = true;
+ if (link->status_cb)
+ link->status_cb(link, link->cb_data, link->state);
+
+ if (link->role == FR_ROLE_USER_EQUIPMENT) {
+ /* make sure the next STATUS ENQUIRY is for a full
+ * status report to get the configred DLCs ASAP */
+ link->polling_count = 0;
+ /* we must not proceed further below if we're in user role,
+ * as otherwise link recovery would set all DLCs as active */
+ return;
+ }
+
+ llist_for_each_entry(dlc, &link->dlc_list, list) {
+ if (!dlc->add && !dlc->del)
+ dlc_set_active(dlc, true);
+ }
+ }
+}
+
+static int validate_pvc_status(struct tlv_parsed *tp, size_t tp_len)
+{
+ size_t i;
+ uint16_t len = 0;
+
+ for (i = 0; i < tp_len; i++) {
+ if (!TLVP_PRESENT(&tp[i], Q933_IEI_PVC_STATUS))
+ continue;
+
+ /* PVC status can be 2 or 3 bytes. If the PVC is bigger
+ * ignore this to be compatible to future extensions. */
+ len = TLVP_LEN(&tp[i], Q933_IEI_PVC_STATUS);
+ if (len <= 1) {
+ return -EINVAL;
+ }
+ /* FIXME: validate correct flags: are some flags invalid at the same time? */
+ }
+
+ return 0;
+}
+
+static int parse_full_pvc_status(struct osmo_fr_link *link, struct tlv_parsed *tp, size_t tp_len)
+{
+ size_t i;
+ int err = 0;
+ struct osmo_fr_dlc *dlc, *tmp;
+ struct q933_a_pvc_sts *pvc;
+ uint16_t dlci = 0;
+ uint16_t *dlcis = talloc_zero_array(link, uint16_t, tp_len);
+ if (!dlcis)
+ return -ENOMEM;
+
+ /* first run validate all PVCs */
+ err = validate_pvc_status(tp, tp_len);
+ if (err < 0)
+ goto out;
+
+ for (i = 0; i < tp_len; i++) {
+ if (!TLVP_PRESENT(&tp[i], Q933_IEI_PVC_STATUS))
+ continue;
+
+ /* parse only 3 byte PVCs */
+ pvc = (struct q933_a_pvc_sts *) TLVP_VAL_MINLEN(
+ &tp[i],
+ Q933_IEI_PVC_STATUS,
+ sizeof(struct q933_a_pvc_sts));
+ if (!pvc)
+ continue;
+
+ dlci = ((pvc->dlci_msb & 0x3f) << 4) | (pvc->dlci_lsb & 0xf);
+ dlcis[i] = dlci;
+ dlc = osmo_fr_dlc_by_dlci(link, dlci);
+ if (!dlc) {
+ dlc = osmo_fr_dlc_alloc(link, dlci);
+ if (!dlc) {
+ LOGPFRL(link, LOGL_ERROR, "Could not create DLC %d\n", dlci);
+ continue;
+ }
+ }
+
+ /* Figure A.3/Q.933: The delete bit is only applicable for timely notification
+ * using the optional single PVC asynchronous status report.
+ * Ignoring the delete. */
+ dlc->add = pvc->new;
+ dlc_set_active(dlc, pvc->active);
+ dlc->del = 0;
+ }
+
+ /* check if all dlc are present in PVC Status */
+ llist_for_each_entry_safe(dlc, tmp, &link->dlc_list, list) {
+ bool found = false;
+ for (i = 0; i < tp_len; i++) {
+ if (dlcis[i] == dlc->dlci) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ dlc_set_active(dlc, false);
+ dlc->del = true;
+ }
+ }
+
+ return 0;
+out:
+ talloc_free(dlcis);
+ return err;
+}
+
+static int parse_link_pvc_status(struct osmo_fr_link *link, struct tlv_parsed *tp, size_t tp_len)
+{
+ int err;
+ size_t i;
+ struct q933_a_pvc_sts *pvc;
+ struct osmo_fr_dlc *dlc;
+ uint16_t dlci = 0;
+
+ err = validate_pvc_status(tp, tp_len);
+ if (err < 0)
+ return err;
+
+ for (i = 0; i < tp_len; i++) {
+ if (!TLVP_PRESENT(&tp[i], Q933_IEI_PVC_STATUS))
+ continue;
+
+ /* parse only 3 byte PVCs */
+ pvc = (struct q933_a_pvc_sts *) TLVP_VAL_MINLEN(
+ &tp[i],
+ Q933_IEI_PVC_STATUS,
+ sizeof(struct q933_a_pvc_sts));
+ if (!pvc)
+ continue;
+
+ dlci = ((pvc->dlci_msb & 0x3f) << 4) | (pvc->dlci_lsb & 0xf);
+ dlc = osmo_fr_dlc_by_dlci(link, dlci);
+ if (!dlc) {
+ /* don't create dlc's for the ones which are about to be deleted. */
+ if (pvc->delete)
+ continue;
+
+ dlc = osmo_fr_dlc_alloc(link, dlci);
+ if (!dlc) {
+ LOGPFRL(link, LOGL_ERROR, "Rx STATUS: Could not create DLC %d\n", dlci);
+ continue;
+ }
+ }
+
+ if (pvc->delete) {
+ dlc->del = 1;
+ } else {
+ dlc->add = pvc->new;
+ dlc_set_active(dlc, pvc->active);
+ dlc->del = 0;
+ }
+ }
+
+ return 0;
+}
+
+static size_t count_pvc_status(struct tlv_parsed *tp, size_t tp_len)
+{
+ size_t i, count = 0;
+ for (i = 0; i < tp_len; i++) {
+ if (!TLVP_PRESENT(&tp[i], Q933_IEI_PVC_STATUS))
+ continue;
+ count++;
+ }
+
+ return count;
+}
+
+static int rx_lmi_q933_status(struct msgb *msg, struct tlv_parsed *tp)
+{
+ struct osmo_fr_link *link = msg->dst;
+ const uint8_t *link_int_rx;
+ uint8_t rep_type;
+
+ OSMO_ASSERT(link);
+
+ if (link->role == FR_ROLE_NETWORK_EQUIPMENT) {
+ LOGPFRL(link, LOGL_ERROR, "Rx STATUS: STATUS aren't supported in role network\n");
+ return -1;
+ }
+
+ /* check for mandatory IEs */
+ if (!TLVP_PRES_LEN(tp, Q933_IEI_REPORT_TYPE, 1)) {
+ LOGPFRL(link, LOGL_NOTICE, "Rx STATUS: Missing TLV Q933 Report Type\n");
+ return -1;
+ }
+
+ rep_type = *TLVP_VAL(tp, Q933_IEI_REPORT_TYPE);
+
+ switch (rep_type) {
+ case Q933_REPT_FULL_STATUS:
+ case Q933_REPT_LINK_INTEGRITY_VERIF:
+ if (rep_type != link->expected_rep) {
+ LOGPFRL(link, LOGL_NOTICE, "Rx STATUS: Unexpected Q933 report type (got 0x%x != exp 0x%x)\n",
+ rep_type, link->expected_rep);
+ return -1;
+ }
+
+ if (!TLVP_PRES_LEN(tp, Q933_IEI_LINK_INT_VERIF, 2)) {
+ LOGPFRL(link, LOGL_NOTICE, "Rx STATUS: Missing TLV Q933 Link Integrety Verification\n");
+ return -1;
+ }
+ link_int_rx = TLVP_VAL(tp, Q933_IEI_LINK_INT_VERIF);
+ link->last_rx_seq = link_int_rx[0];
+ /* The received receive sequence number is not valid if
+ * it is not equal to the last transmitted send sequence
+ * number. Ignore messages containing this error. As a
+ * result, timer T391 expires and the user then
+ * increments the error count. */
+ if (link_int_rx[1] != link->last_tx_seq)
+ return 0;
+ break;
+ case Q933_REPT_SINGLE_PVC_ASYNC_STS:
+ default:
+ return -1;
+ }
+
+ check_link_state(link, true);
+ if (count_pvc_status(tp, MAX_SUPPORTED_PVC + 1) > MAX_SUPPORTED_PVC) {
+ LOGPFRL(link, LOGL_ERROR, "Rx STATUS: Too many PVC! Only %d are supported!\n", MAX_SUPPORTED_PVC);
+ }
+
+ switch (rep_type) {
+ case Q933_REPT_FULL_STATUS:
+ parse_full_pvc_status(link, tp, MAX_SUPPORTED_PVC);
+ break;
+ case Q933_REPT_LINK_INTEGRITY_VERIF:
+ parse_link_pvc_status(link, tp, MAX_SUPPORTED_PVC);
+ break;
+ default:
+ break;
+ }
+
+ /* The network responds to each STATUS ENQUIRY message with a
+ * STATUS message and resets the T392 timer */
+ osmo_timer_schedule(&link->t392, osmo_tdef_get(link->net->T_defs, 392, OSMO_TDEF_S, 15), 0);
+
+ return 0;
+}
+
+static int rx_lmi_q922(struct msgb *msg)
+{
+ struct osmo_fr_link *link = msg->dst;
+ struct q933_a_hdr *qh;
+ /* the + 1 is used to detect more than MAX_SUPPORTED_PVC */
+ struct tlv_parsed tp[MAX_SUPPORTED_PVC + 1];
+ uint8_t *lapf;
+ int rc;
+
+ OSMO_ASSERT(link);
+
+ if (msgb_l2len(msg) < 1)
+ return -1;
+ lapf = msgb_l2(msg);
+
+ /* we only support LAPF UI frames */
+ if (lapf[0] != LAPF_UI)
+ return -1;
+
+ msg->l3h = msg->l2h + 1;
+ if (msgb_l3len(msg) < 3)
+ return -1;
+
+ qh = (struct q933_a_hdr *) msgb_l3(msg);
+ if (qh->prot_disc != Q931_PDISC_CC) {
+ LOGPFRL(link, LOGL_NOTICE,
+ "Rx unsupported LMI protocol discriminator %u\n", qh->prot_disc);
+ return -1;
+ }
+
+ rc = tlv_parse2(tp, MAX_SUPPORTED_PVC + 1, &q933_att_tlvdef,
+ msgb_l3(msg) + sizeof(*qh),
+ msgb_l3len(msg) - sizeof(*qh), 0, 0);
+ if (rc < 0) {
+ LOGPFRL(link, LOGL_NOTICE,
+ "Failed to parse TLVs in LMI message type %u\n", qh->msg_type);
+ return rc;
+ }
+
+ switch (qh->msg_type) {
+ case Q931_MSGT_STATUS_ENQUIRY:
+ rc = rx_lmi_q933_status_enq(msg, tp);
+ break;
+ case Q931_MSGT_STATUS:
+ rc = rx_lmi_q933_status(msg, tp);
+ break;
+ default:
+ LOGPFRL(link, LOGL_NOTICE,
+ "Rx unsupported LMI message type %u\n", qh->msg_type);
+ rc = -1;
+ break;
+ }
+ msgb_free(msg);
+
+ return rc;
+}
+
+int osmo_fr_rx(struct msgb *msg)
+{
+ int rc = 0;
+ uint8_t *frh;
+ uint16_t dlci;
+ struct osmo_fr_dlc *dlc;
+ struct osmo_fr_link *link = msg->dst;
+
+ OSMO_ASSERT(link);
+
+ if (msgb_length(msg) < 2) {
+ LOGPFRL(link, LOGL_ERROR, "Rx short FR header: %u bytes\n", msgb_length(msg));
+ rc = -1;
+ goto out;
+ }
+
+ frh = msg->l1h = msgb_data(msg);
+ if (frh[0] & 0x01) {
+ LOGPFRL(link, LOGL_NOTICE, "Rx Unsupported single-byte FR address\n");
+ rc = -1;
+ goto out;
+ }
+ if ((frh[1] & 0x0f) != 0x01) {
+ LOGPFRL(link, LOGL_NOTICE, "Rx Unknown second FR octet 0x%02x\n", frh[1]);
+ rc = -1;
+ goto out;
+ }
+ dlci = q922_to_dlci(frh);
+ msg->l2h = frh + 2;
+
+ switch (dlci) {
+ case LMI_Q933A_DLCI:
+ return rx_lmi_q922(msg);
+ case LMI_CISCO_DLCI:
+ LOGPFRL(link, LOGL_ERROR, "Rx Unsupported FR DLCI %u\n", dlci);
+ goto out;
+ }
+
+ if (!link->state) {
+ LOGPFRL(link, LOGL_NOTICE, "Link is not reliable. Discarding Rx PDU on DLCI %d\n", dlci);
+ goto out;
+ }
+
+ dlc = osmo_fr_dlc_by_dlci(link, dlci);
+ if (dlc) {
+ if (dlc->active) {
+ /* dispatch to handler of respective DLC */
+ msg->dst = dlc;
+ return dlc->rx_cb(dlc->cb_data, msg);
+ } else {
+ LOGPFRL(link, LOGL_NOTICE, "DLCI %u not yet active. Discarding Rx PDU\n", dlci);
+ }
+ } else {
+ if (link->unknown_dlc_rx_cb)
+ return link->unknown_dlc_rx_cb(link->unknown_dlc_rx_cb_data, msg);
+ else
+ LOGPFRL(link, LOGL_NOTICE, "DLCI %u doesn't exist. Discarding Rx PDU\n", dlci);
+ }
+
+out:
+ msgb_free(msg);
+
+ return rc;
+}
+
+int osmo_fr_tx_dlc(struct msgb *msg)
+{
+ uint8_t *frh;
+ struct osmo_fr_dlc *dlc = msg->dst;
+ struct osmo_fr_link *link = dlc->link;
+
+ OSMO_ASSERT(dlc);
+ OSMO_ASSERT(link);
+
+ if (!link->state) {
+ LOGPFRL(link, LOGL_NOTICE, "Link is not reliable (yet), discarding Tx\n");
+ msgb_free(msg);
+ return -1;
+ }
+ if (!dlc->active) {
+ LOGPFRL(link, LOGL_NOTICE, "DLCI %u is not active (yet), discarding Tx\n", dlc->dlci);
+ msgb_free(msg);
+ return -1;
+ }
+ LOGPFRL(link, LOGL_DEBUG, "DLCI %u is active, sending message\n", dlc->dlci);
+
+ if (msgb_headroom(msg) < 2) {
+ msgb_free(msg);
+ return -ENOSPC;
+ }
+
+ frh = msgb_push(msg, 2);
+ dlci_to_q922(frh, dlc->dlci);
+
+ msg->dst = link;
+ return link->tx_cb(link->cb_data, msg);
+}
+
+/* Every T391 seconds, the user equipment sends a STATUS ENQUIRY
+ * message to the network and resets its polling timer (T391). */
+static void fr_t391_cb(void *data)
+{
+ struct osmo_fr_link *link = data;
+
+ OSMO_ASSERT(link);
+
+ if (link->polling_count % link->net->n391 == 0)
+ tx_lmi_q933_status_enq(link, Q933_REPT_FULL_STATUS);
+ else
+ tx_lmi_q933_status_enq(link, Q933_REPT_LINK_INTEGRITY_VERIF);
+ link->polling_count++;
+ osmo_timer_schedule(&link->t391, osmo_tdef_get(link->net->T_defs, 391, OSMO_TDEF_S, 10), 0);
+}
+
+static void fr_t392_cb(void *data)
+{
+ struct osmo_fr_link *link = data;
+
+ OSMO_ASSERT(link);
+
+ /* A.5 The network increments the error count .. Non-receipt of
+ * a STATUS ENQUIRY within T392, which results in restarting
+ * T392 */
+ link->err_count++;
+ check_link_state(link, false);
+ osmo_timer_schedule(&link->t392, osmo_tdef_get(link->net->T_defs, 392, OSMO_TDEF_S, 15), 0);
+}
+
+/* allocate a frame relay network */
+struct osmo_fr_network *osmo_fr_network_alloc(void *ctx)
+{
+ struct osmo_fr_network *net = talloc_zero(ctx, struct osmo_fr_network);
+ if (!net)
+ return NULL;
+
+ INIT_LLIST_HEAD(&net->links);
+ net->T_defs = fr_tdefs;
+ osmo_tdefs_reset(net->T_defs);
+ net->n391 = 6;
+ net->n392 = 3;
+ net->n393 = 4;
+
+ return net;
+}
+
+void osmo_fr_network_free(struct osmo_fr_network *net)
+{
+ struct osmo_fr_link *link, *tmp;
+
+ if (!net)
+ return;
+
+ llist_for_each_entry_safe(link, tmp, &net->links, list) {
+ osmo_fr_link_free(link);
+ }
+}
+
+/* allocate a frame relay link in a given network */
+struct osmo_fr_link *osmo_fr_link_alloc(struct osmo_fr_network *net, enum osmo_fr_role role, const char *name)
+{
+ struct osmo_fr_link *link = talloc_zero(net, struct osmo_fr_link);
+ if (!link)
+ return NULL;
+ link->role = role;
+ link->net = net;
+ link->name = talloc_strdup(link, name);
+ INIT_LLIST_HEAD(&link->dlc_list);
+ llist_add_tail(&link->list, &net->links);
+
+ osmo_timer_setup(&link->t391, fr_t391_cb, link);
+ osmo_timer_setup(&link->t392, fr_t392_cb, link);
+
+ switch (role) {
+ case FR_ROLE_USER_EQUIPMENT:
+ osmo_timer_schedule(&link->t391, osmo_tdef_get(link->net->T_defs, 391, OSMO_TDEF_S, 15), 0);
+ break;
+ case FR_ROLE_NETWORK_EQUIPMENT:
+ osmo_timer_schedule(&link->t392, osmo_tdef_get(link->net->T_defs, 392, OSMO_TDEF_S, 15), 0);
+ break;
+ }
+
+ LOGPFRL(link, LOGL_INFO, "Creating frame relay link with role %s\n", osmo_fr_role_str(role));
+
+ return link;
+}
+
+void osmo_fr_link_free(struct osmo_fr_link *link)
+{
+ struct osmo_fr_dlc *dlc, *tmp;
+
+ if (!link)
+ return;
+
+ osmo_timer_del(&link->t391);
+ osmo_timer_del(&link->t392);
+
+ llist_for_each_entry_safe(dlc, tmp, &link->dlc_list, list) {
+ osmo_fr_dlc_free(dlc);
+ }
+
+ llist_del(&link->list);
+ talloc_free(link);
+}
+
+/* allocate a data link connectoin on a given framerelay link */
+struct osmo_fr_dlc *osmo_fr_dlc_alloc(struct osmo_fr_link *link, uint16_t dlci)
+{
+ struct osmo_fr_dlc *dlc = talloc_zero(link, struct osmo_fr_dlc);
+ if (!dlc)
+ return NULL;
+
+ dlc->link = link;
+ dlc->dlci = dlci;
+ dlc->active = false;
+
+ llist_add_tail(&dlc->list, &link->dlc_list);
+
+ dlc->add = true;
+ tx_lmi_q933_status(link, Q933_REPT_SINGLE_PVC_ASYNC_STS);
+
+ return dlc;
+}
+
+void osmo_fr_dlc_free(struct osmo_fr_dlc *dlc)
+{
+ llist_del(&dlc->list);
+ talloc_free(dlc);
+}
+
+/* TODO: rework osmo_fr_dlc_alloc/free with handling it's own memory.
+ * For network role: The dlc have to created by the application (e.g. vty).
+ * The dlc shouldn't free'd directly. It should be communicated to the
+ * other side and wait until it's confirmed OR the link go off and free it afterwards.
+ * For user equpment role: The dlc can be created by the application or the dlc will be created
+ * by the frame relay because the network is configuring the dlc.
+ * The dlc shouldn't be free'd. Only the handler should be set to NULL.
+ */
+
+struct osmo_fr_dlc *osmo_fr_dlc_by_dlci(struct osmo_fr_link *link, uint16_t dlci)
+{
+ struct osmo_fr_dlc *dlc;
+
+ llist_for_each_entry(dlc, &link->dlc_list, list) {
+ if (dlc->dlci == dlci)
+ return dlc;
+ }
+ return NULL;
+}
+
+
+#include <osmocom/vty/vty.h>
+#include <osmocom/vty/tdef_vty.h>
+
+static void fr_dlc_dump_vty(struct vty *vty, const struct osmo_fr_dlc *dlc)
+{
+ vty_out(vty, " FR DLC %05u: %s%s%s%s", dlc->dlci,
+ dlc->active ? "ACTIVE" : "INACTIVE",
+ dlc->add ? " ADDED" : "", dlc->del ? " DELETED" : "", VTY_NEWLINE);
+}
+
+static void fr_link_dump_vty(struct vty *vty, const struct osmo_fr_link *link)
+{
+ const struct osmo_fr_dlc *dlc;
+
+ vty_out(vty, "FR Link '%s': Role %s, LastRxSeq %u, LastTxSeq %u%s",
+ link->name, link->role == FR_ROLE_USER_EQUIPMENT ? "USER" : "NETWORK",
+ link->last_rx_seq, link->last_tx_seq, VTY_NEWLINE);
+ llist_for_each_entry(dlc, &link->dlc_list, list) {
+ fr_dlc_dump_vty(vty, dlc);
+ }
+}
+
+void osmo_fr_network_dump_vty(struct vty *vty, const struct osmo_fr_network *net)
+{
+ struct osmo_fr_link *link;
+
+ vty_out(vty, "FR Network: N391 %u, N392 %u, N393 %u%s",
+ net->n391, net->n392, net->n393, VTY_NEWLINE);
+ osmo_tdef_vty_out_all(vty, net->T_defs, " ");
+ llist_for_each_entry(link, &net->links, list) {
+ fr_link_dump_vty(vty, link);
+ }
+}
diff --git a/src/gb/gprs_bssgp.c b/src/gb/gprs_bssgp.c
index 9c119fbc..7abef804 100644
--- a/src/gb/gprs_bssgp.c
+++ b/src/gb/gprs_bssgp.c
@@ -40,7 +40,8 @@
#include <osmocom/gprs/gprs_bssgp_bss.h>
#include <osmocom/gprs/gprs_ns.h>
-#include "common_vty.h"
+#include "osmocom/gsm/gsm48.h"
+#include "gprs_bssgp_internal.h"
void *bssgp_tall_ctx = NULL;
@@ -76,6 +77,7 @@ static int _bssgp_tx_dl_ud(struct bssgp_flow_control *fc, struct msgb *msg,
/* callback to be backward compatible with old users which do not set the bssgp_ns_send function */
static int _gprs_ns_sendmsg(void *ctx, struct msgb *msg)
{
+ OSMO_ASSERT(bssgp_nsi);
return gprs_ns_sendmsg(bssgp_nsi, msg);
}
@@ -92,6 +94,75 @@ struct bssgp_bvc_ctx *btsctx_by_raid_cid(const struct gprs_ra_id *raid, uint16_t
return NULL;
}
+/* Transmit a BVC-RESET or BVC-RESET-ACK with a given nsei and bvci (Chapter 10.4.12)
+ * \param[in] pdu Either BSSGP_PDUT_BVC_RESET or BSSGP_PDUT_BVC_RESET_ACK
+ * \param[in] nsei The NSEI to transmit over
+ * \param[in] bvci BVCI of the BVC to reset
+ * \param[in] cause The cause of the reset only valid for BSSGP_PDUT_BVC_RESET.
+ * \param[in] ra_id Pointer to the ra_id to include. If NULL no cell information will be included
+ * \param[in] cell_id The cell_id to include (if ra_id is not NULL)
+ * returns >= 0 on success, on error < 0.
+ */
+static int tx_bvc_reset_nsei_bvci(enum bssgp_pdu_type pdu, uint16_t nsei, uint16_t bvci,
+ enum gprs_bssgp_cause cause, const struct gprs_ra_id *ra_id, uint16_t cell_id)
+{
+ struct msgb *msg = bssgp_msgb_alloc();
+ struct bssgp_normal_hdr *bgph =
+ (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
+ uint16_t _bvci = osmo_htons(bvci);
+
+ OSMO_ASSERT(pdu == BSSGP_PDUT_BVC_RESET || pdu == BSSGP_PDUT_BVC_RESET_ACK);
+
+ msgb_nsei(msg) = nsei;
+ msgb_bvci(msg) = BVCI_SIGNALLING;
+ bgph->pdu_type = pdu;
+
+ msgb_tvlv_put(msg, BSSGP_IE_BVCI, 2, (uint8_t *) &_bvci);
+
+ if (pdu == BSSGP_PDUT_BVC_RESET) {
+ msgb_tvlv_put(msg, BSSGP_IE_CAUSE, 1, (uint8_t *) &cause);
+ LOGP(DLBSSGP, LOGL_NOTICE, "BSSGP (BVCI=%u) Tx BVC-RESET "
+ "CAUSE=%s\n", bvci, bssgp_cause_str(cause));
+ } else {
+ LOGP(DLBSSGP, LOGL_NOTICE, "BSSGP (BVCI=%u) Tx BVC-RESET-ACK\n", bvci);
+ }
+
+ if (ra_id) {
+ uint8_t bssgp_cid[8];
+ bssgp_create_cell_id(bssgp_cid, ra_id, cell_id);
+ msgb_tvlv_put(msg, BSSGP_IE_CELL_ID, sizeof(bssgp_cid), bssgp_cid);
+ }
+
+ /* Optional: Feature Bitmap */
+
+ return bssgp_ns_send(bssgp_ns_send_data, msg);
+}
+
+/*! Transmit a BVC-RESET message with a given nsei and bvci (Chapter 10.4.12)
+ * \param[in] nsei The NSEI to transmit over
+ * \param[in] bvci BVCI of the BVC to reset
+ * \param[in] cause The cause of the reset
+ * \param[in] ra_id Pointer to the ra_id to include. If NULL no cell information will be included
+ * \param[in] cell_id The cell_id to include (if ra_id is not NULL)
+ * returns >= 0 on success, on error < 0.
+ */
+int bssgp_tx_bvc_reset_nsei_bvci(uint16_t nsei, uint16_t bvci, enum gprs_bssgp_cause cause, const struct gprs_ra_id *ra_id, uint16_t cell_id)
+{
+ return tx_bvc_reset_nsei_bvci(BSSGP_PDUT_BVC_RESET, nsei, bvci, cause, ra_id, cell_id);
+}
+
+/*! Transmit a BVC-RESET-ACK message with a given nsei and bvci (Chapter 10.4.12)
+ * \param[in] nsei The NSEI to transmit over
+ * \param[in] bvci BVCI of the BVC to reset
+ * \param[in] ra_id Pointer to the ra_id to include. If NULL no cell information will be included
+ * \param[in] cell_id The cell_id to include (if ra_id is not NULL)
+ * returns >= 0 on success, on error < 0.
+ */
+int bssgp_tx_bvc_reset_ack_nsei_bvci(uint16_t nsei, uint16_t bvci, const struct gprs_ra_id *ra_id, uint16_t cell_id)
+{
+ return tx_bvc_reset_nsei_bvci(BSSGP_PDUT_BVC_RESET_ACK, nsei, bvci, 0, ra_id, cell_id);
+}
+
/*! Initiate reset procedure for all PTP BVC on a given NSEI.
*
* This function initiates reset procedure for all PTP BVC with a given cause.
@@ -106,7 +177,7 @@ int bssgp_tx_bvc_ptp_reset(uint16_t nsei, enum gprs_bssgp_cause cause)
llist_for_each_entry(bctx, &bssgp_bvc_ctxts, list) {
if (bctx->nsei == nsei && bctx->bvci != BVCI_SIGNALLING) {
- LOGP(DBSSGP, LOGL_DEBUG, "NSEI=%u/BVCI=%u RESET due to %s\n",
+ LOGP(DLBSSGP, LOGL_DEBUG, "NSEI=%u/BVCI=%u RESET due to %s\n",
nsei, bctx->bvci, bssgp_cause_str(cause));
rc = bssgp_tx_bvc_reset(bctx, bctx->bvci, cause);
if (rc < 0)
@@ -144,25 +215,36 @@ struct bssgp_bvc_ctx *btsctx_alloc(uint16_t bvci, uint16_t nsei)
return NULL;
ctx->bvci = bvci;
ctx->nsei = nsei;
+ ctx->is_sgsn = true;
/* FIXME: BVCI is not unique, only BVCI+NSEI ?!? */
ctx->ctrg = rate_ctr_group_alloc(ctx, &bssgp_ctrg_desc, bvci);
- if (!ctx->ctrg) {
- talloc_free(ctx);
- return NULL;
- }
+ if (!ctx->ctrg)
+ goto err_ctrg;
+
ctx->fc = talloc_zero(ctx, struct bssgp_flow_control);
+ if (!ctx->fc)
+ goto err_fc;
+
/* cofigure for 2Mbit, 30 packets in queue */
bssgp_fc_init(ctx->fc, 100000, 2*1024*1024/8, 30, &_bssgp_tx_dl_ud);
llist_add(&ctx->list, &bssgp_bvc_ctxts);
return ctx;
+
+err_fc:
+ rate_ctr_group_free(ctx->ctrg);
+err_ctrg:
+ talloc_free(ctx);
+ return NULL;
}
void bssgp_bvc_ctx_free(struct bssgp_bvc_ctx *ctx)
{
if (!ctx)
return;
+
+ osmo_timer_del(&ctx->fc->timer);
rate_ctr_group_free(ctx->ctrg);
llist_del(&ctx->list);
talloc_free(ctx);
@@ -293,7 +375,7 @@ static int bssgp_rx_bvc_reset(struct msgb *msg, struct tlv_parsed *tp,
uint16_t bvci;
bvci = tlvp_val16be(tp, BSSGP_IE_BVCI);
- DEBUGP(DBSSGP, "BSSGP BVCI=%u Rx RESET cause=%s\n", bvci,
+ DEBUGP(DLBSSGP, "BSSGP BVCI=%u Rx RESET cause=%s\n", bvci,
bssgp_cause_str(*TLVP_VAL(tp, BSSGP_IE_CAUSE)));
/* look-up or create the BTS context for this BVC */
@@ -306,19 +388,26 @@ static int bssgp_rx_bvc_reset(struct msgb *msg, struct tlv_parsed *tp,
/* When we receive a BVC-RESET PDU (at least of a PTP BVCI), the BSS
* informs us about its RAC + Cell ID, so we can create a mapping */
- if (bvci != 0 && bvci != 1) {
- if (!TLVP_PRESENT(tp, BSSGP_IE_CELL_ID)) {
- LOGP(DBSSGP, LOGL_ERROR, "BSSGP BVCI=%u Rx RESET "
+ if (bctx->is_sgsn && bvci != BVCI_SIGNALLING && bvci != BVCI_PTM) {
+ if (!TLVP_PRES_LEN(tp, BSSGP_IE_CELL_ID, 8)) {
+ LOGP(DLBSSGP, LOGL_ERROR, "BSSGP BVCI=%u Rx RESET "
"missing mandatory IE\n", bvci);
return -EINVAL;
}
/* actually extract RAC / CID */
bctx->cell_id = bssgp_parse_cell_id(&bctx->ra_id,
TLVP_VAL(tp, BSSGP_IE_CELL_ID));
- LOGP(DBSSGP, LOGL_NOTICE, "Cell %s CI %u on BVCI %u\n",
+ LOGP(DLBSSGP, LOGL_NOTICE, "Cell %s CI %u on BVCI %u\n",
osmo_rai_name(&bctx->ra_id), bctx->cell_id, bvci);
}
+ /* Acknowledge the RESET to the BTS */
+ if (bvci == BVCI_SIGNALLING || bvci == BVCI_PTM || bctx->is_sgsn)
+ bssgp_tx_simple_bvci(BSSGP_PDUT_BVC_RESET_ACK,
+ nsei, bvci, ns_bvci);
+ else
+ bssgp_tx_bvc_reset_ack_nsei_bvci(nsei, bvci, &bctx->ra_id, bctx->cell_id);
+
/* Send NM_BVC_RESET.ind to NM */
memset(&nmp, 0, sizeof(nmp));
nmp.nsei = nsei;
@@ -328,10 +417,6 @@ static int bssgp_rx_bvc_reset(struct msgb *msg, struct tlv_parsed *tp,
osmo_prim_init(&nmp.oph, SAP_BSSGP_NM, PRIM_NM_BVC_RESET,
PRIM_OP_INDICATION, msg);
bssgp_prim_cb(&nmp.oph, NULL);
-
- /* Acknowledge the RESET to the BTS */
- bssgp_tx_simple_bvci(BSSGP_PDUT_BVC_RESET_ACK,
- nsei, bvci, ns_bvci);
return 0;
}
@@ -344,20 +429,20 @@ static int bssgp_rx_bvc_block(struct msgb *msg, struct tlv_parsed *tp)
bvci = tlvp_val16be(tp, BSSGP_IE_BVCI);
if (bvci == BVCI_SIGNALLING) {
/* 8.3.2: Signalling BVC shall never be blocked */
- LOGP(DBSSGP, LOGL_ERROR, "NSEI=%u/BVCI=%u "
+ LOGP(DLBSSGP, LOGL_ERROR, "NSEI=%u/BVCI=%u "
"received block for signalling BVC!?!\n",
nsei, msgb_bvci(msg));
return 0;
}
- LOGP(DBSSGP, LOGL_INFO, "BSSGP Rx BVCI=%u BVC-BLOCK\n", bvci);
+ LOGP(DLBSSGP, LOGL_INFO, "BSSGP Rx BVCI=%u BVC-BLOCK\n", bvci);
ptp_ctx = btsctx_by_bvci_nsei(bvci, nsei);
if (!ptp_ctx)
return bssgp_tx_status(BSSGP_CAUSE_UNKNOWN_BVCI, &bvci, msg);
ptp_ctx->state |= BVC_S_BLOCKED;
- rate_ctr_inc(&ptp_ctx->ctrg->ctr[BSSGP_CTR_BLOCKED]);
+ rate_ctr_inc(rate_ctr_group_get_ctr(ptp_ctx->ctrg, BSSGP_CTR_BLOCKED));
/* Send NM_BVC_BLOCK.ind to NM */
memset(&nmp, 0, sizeof(nmp));
@@ -382,13 +467,13 @@ static int bssgp_rx_bvc_unblock(struct msgb *msg, struct tlv_parsed *tp)
bvci = tlvp_val16be(tp, BSSGP_IE_BVCI);
if (bvci == BVCI_SIGNALLING) {
/* 8.3.2: Signalling BVC shall never be blocked */
- LOGP(DBSSGP, LOGL_ERROR, "NSEI=%u/BVCI=%u "
+ LOGP(DLBSSGP, LOGL_ERROR, "NSEI=%u/BVCI=%u "
"received unblock for signalling BVC!?!\n",
nsei, msgb_bvci(msg));
return 0;
}
- DEBUGP(DBSSGP, "BSSGP BVCI=%u Rx BVC-UNBLOCK\n", bvci);
+ DEBUGP(DLBSSGP, "BSSGP BVCI=%u Rx BVC-UNBLOCK\n", bvci);
ptp_ctx = btsctx_by_bvci_nsei(bvci, nsei);
if (!ptp_ctx)
@@ -420,12 +505,12 @@ static int bssgp_rx_ul_ud(struct msgb *msg, struct tlv_parsed *tp,
/* extract TLLI and parse TLV IEs */
msgb_tlli(msg) = osmo_ntohl(budh->tlli);
- DEBUGP(DBSSGP, "BSSGP TLLI=0x%08x Rx UPLINK-UNITDATA\n", msgb_tlli(msg));
+ DEBUGP(DLBSSGP, "BSSGP TLLI=0x%08x Rx UPLINK-UNITDATA\n", msgb_tlli(msg));
/* Cell ID and LLC_PDU are the only mandatory IE */
- if (!TLVP_PRESENT(tp, BSSGP_IE_CELL_ID) ||
+ if (!TLVP_PRES_LEN(tp, BSSGP_IE_CELL_ID, 8) ||
!TLVP_PRESENT(tp, BSSGP_IE_LLC_PDU)) {
- LOGP(DBSSGP, LOGL_ERROR, "BSSGP TLLI=0x%08x Rx UL-UD "
+ LOGP(DLBSSGP, LOGL_ERROR, "BSSGP TLLI=0x%08x Rx UL-UD "
"missing mandatory IE\n", msgb_tlli(msg));
return bssgp_tx_status(BSSGP_CAUSE_MISSING_MAND_IE, NULL, msg);
}
@@ -453,16 +538,16 @@ static int bssgp_rx_suspend(struct msgb *msg, struct tlv_parsed *tp)
uint16_t ns_bvci = msgb_bvci(msg), nsei = msgb_nsei(msg);
int rc;
- if (!TLVP_PRESENT(tp, BSSGP_IE_TLLI) ||
- !TLVP_PRESENT(tp, BSSGP_IE_ROUTEING_AREA)) {
- LOGP(DBSSGP, LOGL_ERROR, "BSSGP BVCI=%u Rx SUSPEND "
+ if (!TLVP_PRES_LEN(tp, BSSGP_IE_TLLI, 4) ||
+ !TLVP_PRES_LEN(tp, BSSGP_IE_ROUTEING_AREA, 6)) {
+ LOGP(DLBSSGP, LOGL_ERROR, "BSSGP BVCI=%u Rx SUSPEND "
"missing mandatory IE\n", ns_bvci);
return bssgp_tx_status(BSSGP_CAUSE_MISSING_MAND_IE, NULL, msg);
}
tlli = tlvp_val32be(tp, BSSGP_IE_TLLI);
- DEBUGP(DBSSGP, "BSSGP BVCI=%u TLLI=0x%08x Rx SUSPEND\n",
+ DEBUGP(DLBSSGP, "BSSGP BVCI=%u TLLI=0x%08x Rx SUSPEND\n",
ns_bvci, tlli);
gsm48_parse_ra(&raid, TLVP_VAL(tp, BSSGP_IE_ROUTEING_AREA));
@@ -494,10 +579,10 @@ static int bssgp_rx_resume(struct msgb *msg, struct tlv_parsed *tp)
uint16_t ns_bvci = msgb_bvci(msg), nsei = msgb_nsei(msg);
int rc;
- if (!TLVP_PRESENT(tp, BSSGP_IE_TLLI) ||
- !TLVP_PRESENT(tp, BSSGP_IE_ROUTEING_AREA) ||
- !TLVP_PRESENT(tp, BSSGP_IE_SUSPEND_REF_NR)) {
- LOGP(DBSSGP, LOGL_ERROR, "BSSGP BVCI=%u Rx RESUME "
+ if (!TLVP_PRES_LEN(tp, BSSGP_IE_TLLI, 4 ) ||
+ !TLVP_PRES_LEN(tp, BSSGP_IE_ROUTEING_AREA, 6) ||
+ !TLVP_PRES_LEN(tp, BSSGP_IE_SUSPEND_REF_NR, 1)) {
+ LOGP(DLBSSGP, LOGL_ERROR, "BSSGP BVCI=%u Rx RESUME "
"missing mandatory IE\n", ns_bvci);
return bssgp_tx_status(BSSGP_CAUSE_MISSING_MAND_IE, NULL, msg);
}
@@ -505,7 +590,7 @@ static int bssgp_rx_resume(struct msgb *msg, struct tlv_parsed *tp)
tlli = tlvp_val32be(tp, BSSGP_IE_TLLI);
suspend_ref = *TLVP_VAL(tp, BSSGP_IE_SUSPEND_REF_NR);
- DEBUGP(DBSSGP, "BSSGP BVCI=%u TLLI=0x%08x Rx RESUME\n", ns_bvci, tlli);
+ DEBUGP(DLBSSGP, "BSSGP BVCI=%u TLLI=0x%08x Rx RESUME\n", ns_bvci, tlli);
gsm48_parse_ra(&raid, TLVP_VAL(tp, BSSGP_IE_ROUTEING_AREA));
@@ -536,21 +621,21 @@ static int bssgp_rx_llc_disc(struct msgb *msg, struct tlv_parsed *tp,
uint32_t tlli = 0;
uint16_t nsei = msgb_nsei(msg);
- if (!TLVP_PRESENT(tp, BSSGP_IE_TLLI) ||
- !TLVP_PRESENT(tp, BSSGP_IE_LLC_FRAMES_DISCARDED) ||
- !TLVP_PRESENT(tp, BSSGP_IE_BVCI) ||
- !TLVP_PRESENT(tp, BSSGP_IE_NUM_OCT_AFF)) {
- LOGP(DBSSGP, LOGL_ERROR, "BSSGP BVCI=%u Rx LLC DISCARDED "
+ if (!TLVP_PRES_LEN(tp, BSSGP_IE_TLLI, 4) ||
+ !TLVP_PRES_LEN(tp, BSSGP_IE_LLC_FRAMES_DISCARDED, 1) ||
+ !TLVP_PRES_LEN(tp, BSSGP_IE_BVCI, 2) ||
+ !TLVP_PRES_LEN(tp, BSSGP_IE_NUM_OCT_AFF, 3)) {
+ LOGP(DLBSSGP, LOGL_ERROR, "BSSGP BVCI=%u Rx LLC DISCARDED "
"missing mandatory IE\n", ctx->bvci);
+ return bssgp_tx_status(BSSGP_CAUSE_MISSING_MAND_IE, NULL, msg);
}
- if (TLVP_PRESENT(tp, BSSGP_IE_TLLI))
- tlli = tlvp_val32be(tp, BSSGP_IE_TLLI);
+ tlli = tlvp_val32be(tp, BSSGP_IE_TLLI);
- DEBUGP(DBSSGP, "BSSGP BVCI=%u TLLI=%08x Rx LLC DISCARDED\n",
+ DEBUGP(DLBSSGP, "BSSGP BVCI=%u TLLI=%08x Rx LLC DISCARDED\n",
ctx->bvci, tlli);
- rate_ctr_inc(&ctx->ctrg->ctr[BSSGP_CTR_DISCARDED]);
+ rate_ctr_inc(rate_ctr_group_get_ctr(ctx->ctrg, BSSGP_CTR_DISCARDED));
/* send NM_LLC_DISCARDED to NM */
memset(&nmp, 0, sizeof(nmp));
@@ -571,27 +656,27 @@ int bssgp_rx_status(struct msgb *msg, struct tlv_parsed *tp,
struct osmo_bssgp_prim nmp;
enum gprs_bssgp_cause cause;
- if (!TLVP_PRESENT(tp, BSSGP_IE_CAUSE)) {
- LOGP(DBSSGP, LOGL_ERROR, "BSSGP BVCI=%u Rx STATUS "
+ if (!TLVP_PRES_LEN(tp, BSSGP_IE_CAUSE, 1)) {
+ LOGP(DLBSSGP, LOGL_ERROR, "BSSGP BVCI=%u Rx STATUS "
"missing mandatory IE\n", bvci);
cause = BSSGP_CAUSE_PROTO_ERR_UNSPEC;
} else {
cause = *TLVP_VAL(tp, BSSGP_IE_CAUSE);
}
- LOGP(DBSSGP, LOGL_NOTICE, "BSSGP BVCI=%u Rx BVC STATUS, cause=%s\n",
+ LOGP(DLBSSGP, LOGL_NOTICE, "BSSGP BVCI=%u Rx BVC STATUS, cause=%s\n",
bvci, bssgp_cause_str(cause));
if (cause == BSSGP_CAUSE_BVCI_BLOCKED || cause == BSSGP_CAUSE_UNKNOWN_BVCI) {
- if (!TLVP_PRESENT(tp, BSSGP_IE_BVCI))
- LOGP(DBSSGP, LOGL_ERROR,
+ if (!TLVP_PRES_LEN(tp, BSSGP_IE_BVCI, 2))
+ LOGP(DLBSSGP, LOGL_ERROR,
"BSSGP BVCI=%u Rx STATUS cause=%s "
"missing conditional BVCI IE\n",
bvci, bssgp_cause_str(cause));
}
if (bctx)
- rate_ctr_inc(&bctx->ctrg->ctr[BSSGP_CTR_STATUS]);
+ rate_ctr_inc(rate_ctr_group_get_ctr(bctx->ctrg, BSSGP_CTR_STATUS));
/* send NM_STATUS to NM */
memset(&nmp, 0, sizeof(nmp));
@@ -604,7 +689,6 @@ int bssgp_rx_status(struct msgb *msg, struct tlv_parsed *tp,
return bssgp_prim_cb(&nmp.oph, NULL);
}
-
/* One element (msgb) in a BSSGP Flow Control queue */
struct bssgp_fc_queue_element {
/* linked list of queue elements */
@@ -636,7 +720,7 @@ static void fc_timer_cb(void *data)
list);
if (bssgp_fc_needs_queueing(fc, fcqe->llc_pdu_len)) {
- LOGP(DBSSGP, LOGL_NOTICE, "BSSGP-FC: fc_timer_cb() but still "
+ LOGP(DLBSSGP, LOGL_NOTICE, "BSSGP-FC: fc_timer_cb() but still "
"not able to send PDU of %u bytes\n", fcqe->llc_pdu_len);
/* make sure we re-start the timer */
fc_queue_timer_cfg(fc);
@@ -774,7 +858,7 @@ int bssgp_fc_in(struct bssgp_flow_control *fc, struct msgb *msg,
struct timeval time_now;
if (llc_pdu_len > fc->bucket_size_max) {
- LOGP(DBSSGP, LOGL_NOTICE, "Single PDU (size=%u) is larger "
+ LOGP(DLBSSGP, LOGL_NOTICE, "Single PDU (size=%u) is larger "
"than maximum bucket size (%u)!\n", llc_pdu_len,
fc->bucket_size_max);
msgb_free(msg);
@@ -835,15 +919,15 @@ static int bssgp_rx_fc_bvc(struct msgb *msg, struct tlv_parsed *tp,
uint32_t old_leak_rate = bctx->fc->bucket_leak_rate;
uint32_t old_r_def_ms = bctx->r_default_ms;
- DEBUGP(DBSSGP, "BSSGP BVCI=%u Rx Flow Control BVC\n",
+ DEBUGP(DLBSSGP, "BSSGP BVCI=%u Rx Flow Control BVC\n",
bctx->bvci);
- if (!TLVP_PRESENT(tp, BSSGP_IE_TAG) ||
- !TLVP_PRESENT(tp, BSSGP_IE_BVC_BUCKET_SIZE) ||
- !TLVP_PRESENT(tp, BSSGP_IE_BUCKET_LEAK_RATE) ||
- !TLVP_PRESENT(tp, BSSGP_IE_BMAX_DEFAULT_MS) ||
- !TLVP_PRESENT(tp, BSSGP_IE_R_DEFAULT_MS)) {
- LOGP(DBSSGP, LOGL_ERROR, "BSSGP BVCI=%u Rx FC BVC "
+ if (!TLVP_PRES_LEN(tp, BSSGP_IE_TAG, 1) ||
+ !TLVP_PRES_LEN(tp, BSSGP_IE_BVC_BUCKET_SIZE, 2) ||
+ !TLVP_PRES_LEN(tp, BSSGP_IE_BUCKET_LEAK_RATE, 2) ||
+ !TLVP_PRES_LEN(tp, BSSGP_IE_BMAX_DEFAULT_MS, 2) ||
+ !TLVP_PRES_LEN(tp, BSSGP_IE_R_DEFAULT_MS,2)) {
+ LOGP(DLBSSGP, LOGL_ERROR, "BSSGP BVCI=%u Rx FC BVC "
"missing mandatory IE\n", bctx->bvci);
return bssgp_tx_status(BSSGP_CAUSE_MISSING_MAND_IE, NULL, msg);
}
@@ -858,17 +942,17 @@ static int bssgp_rx_fc_bvc(struct msgb *msg, struct tlv_parsed *tp,
bctx->r_default_ms = 100 * tlvp_val16be(tp, BSSGP_IE_R_DEFAULT_MS) / 8;
if (old_leak_rate != 0 && bctx->fc->bucket_leak_rate == 0)
- LOGP(DBSSGP, LOGL_NOTICE, "BSS instructs us to bucket leak "
+ LOGP(DLBSSGP, LOGL_NOTICE, "BSS instructs us to bucket leak "
"rate of 0, stopping all DL GPRS!\n");
else if (old_leak_rate == 0 && bctx->fc->bucket_leak_rate != 0)
- LOGP(DBSSGP, LOGL_NOTICE, "BSS instructs us to bucket leak "
+ LOGP(DLBSSGP, LOGL_NOTICE, "BSS instructs us to bucket leak "
"rate of != 0, restarting all DL GPRS!\n");
if (old_r_def_ms != 0 && bctx->r_default_ms == 0)
- LOGP(DBSSGP, LOGL_NOTICE, "BSS instructs us to MS default "
+ LOGP(DLBSSGP, LOGL_NOTICE, "BSS instructs us to MS default "
"bucket leak rate of 0, stopping DL GPRS!\n");
else if (old_r_def_ms == 0 && bctx->r_default_ms != 0)
- LOGP(DBSSGP, LOGL_NOTICE, "BSS instructs us to MS default "
+ LOGP(DLBSSGP, LOGL_NOTICE, "BSS instructs us to MS default "
"bucket leak rate != 0, restarting DL GPRS!\n");
/* reconfigure the timer for flow control based on new values */
@@ -905,13 +989,13 @@ static int bssgp_rx_ptp(struct msgb *msg, struct tlv_parsed *tp,
break;
case BSSGP_PDUT_RA_CAPABILITY:
/* BSS requests RA capability or IMSI */
- DEBUGP(DBSSGP, "BSSGP BVCI=%u Rx RA CAPABILITY UPDATE\n",
+ DEBUGP(DLBSSGP, "BSSGP BVCI=%u Rx RA CAPABILITY UPDATE\n",
bctx->bvci);
/* FIXME: send GMM_RA_CAPABILITY_UPDATE.ind to GMM */
/* FIXME: send RA_CAPA_UPDATE_ACK */
break;
case BSSGP_PDUT_RADIO_STATUS:
- DEBUGP(DBSSGP, "BSSGP BVCI=%u Rx RADIO STATUS\n", bctx->bvci);
+ DEBUGP(DLBSSGP, "BSSGP BVCI=%u Rx RADIO STATUS\n", bctx->bvci);
/* BSS informs us of some exception */
/* FIXME: send GMM_RADIO_STATUS.ind to GMM */
break;
@@ -921,7 +1005,7 @@ static int bssgp_rx_ptp(struct msgb *msg, struct tlv_parsed *tp,
break;
case BSSGP_PDUT_FLOW_CONTROL_MS:
/* BSS informs us of available bandwidth to one MS */
- DEBUGP(DBSSGP, "BSSGP BVCI=%u Rx Flow Control MS\n",
+ DEBUGP(DLBSSGP, "BSSGP BVCI=%u Rx Flow Control MS\n",
bctx->bvci);
/* FIXME: actually implement flow control */
/* FIXME: Send FLOW_CONTROL_MS_ACK */
@@ -929,12 +1013,15 @@ static int bssgp_rx_ptp(struct msgb *msg, struct tlv_parsed *tp,
case BSSGP_PDUT_STATUS:
/* This is already handled in bssgp_rcvmsg() */
break;
+ case BSSGP_PDUT_BVC_RESET:
+ rc = bssgp_rx_bvc_reset(msg, tp, bctx->bvci);
+ break;
case BSSGP_PDUT_DOWNLOAD_BSS_PFC:
case BSSGP_PDUT_CREATE_BSS_PFC_ACK:
case BSSGP_PDUT_CREATE_BSS_PFC_NACK:
case BSSGP_PDUT_MODIFY_BSS_PFC:
case BSSGP_PDUT_DELETE_BSS_PFC_ACK:
- DEBUGP(DBSSGP, "BSSGP BVCI=%u Rx PDU type %s not [yet] "
+ DEBUGP(DLBSSGP, "BSSGP BVCI=%u Rx PDU type %s not [yet] "
"implemented\n", bctx->bvci, bssgp_pdu_str(pdu_type));
rc = bssgp_tx_status(BSSGP_CAUSE_PDU_INCOMP_FEAT, NULL, msg);
break;
@@ -945,13 +1032,13 @@ static int bssgp_rx_ptp(struct msgb *msg, struct tlv_parsed *tp,
case BSSGP_PDUT_RA_CAPA_UPDATE_ACK:
case BSSGP_PDUT_FLOW_CONTROL_BVC_ACK:
case BSSGP_PDUT_FLOW_CONTROL_MS_ACK:
- DEBUGP(DBSSGP, "BSSGP BVCI=%u PDU type %s only exists in DL\n",
+ DEBUGP(DLBSSGP, "BSSGP BVCI=%u PDU type %s only exists in DL\n",
bctx->bvci, bssgp_pdu_str(pdu_type));
bssgp_tx_status(BSSGP_CAUSE_PROTO_ERR_UNSPEC, NULL, msg);
rc = -EINVAL;
break;
default:
- DEBUGP(DBSSGP, "BSSGP BVCI=%u PDU type %s unknown\n",
+ DEBUGP(DLBSSGP, "BSSGP BVCI=%u PDU type %s unknown\n",
bctx->bvci, bssgp_pdu_str(pdu_type));
rc = bssgp_tx_status(BSSGP_CAUSE_PROTO_ERR_UNSPEC, NULL, msg);
break;
@@ -982,13 +1069,13 @@ static int bssgp_rx_sign(struct msgb *msg, struct tlv_parsed *tp,
break;
case BSSGP_PDUT_FLUSH_LL_ACK:
/* BSS informs us it has performed LL FLUSH */
- DEBUGP(DBSSGP, "BSSGP Rx BVCI=%u FLUSH LL ACK\n", bvci);
+ DEBUGP(DLBSSGP, "BSSGP Rx BVCI=%u FLUSH LL ACK\n", bvci);
/* FIXME: send NM_FLUSH_LL.res to NM */
break;
case BSSGP_PDUT_LLC_DISCARD:
/* BSS informs that some LLC PDU's have been discarded */
if (!bctx) {
- LOGP(DBSSGP, LOGL_ERROR,
+ LOGP(DLBSSGP, LOGL_ERROR,
"BSSGP Rx LLC-DISCARD missing mandatory BVCI\n");
goto err_mand_ie;
}
@@ -996,9 +1083,9 @@ static int bssgp_rx_sign(struct msgb *msg, struct tlv_parsed *tp,
break;
case BSSGP_PDUT_BVC_BLOCK:
/* BSS tells us that BVC shall be blocked */
- if (!TLVP_PRESENT(tp, BSSGP_IE_BVCI) ||
- !TLVP_PRESENT(tp, BSSGP_IE_CAUSE)) {
- LOGP(DBSSGP, LOGL_ERROR, "BSSGP Rx BVC-BLOCK "
+ if (!TLVP_PRES_LEN(tp, BSSGP_IE_BVCI, 2) ||
+ !TLVP_PRES_LEN(tp, BSSGP_IE_CAUSE, 1)) {
+ LOGP(DLBSSGP, LOGL_ERROR, "BSSGP Rx BVC-BLOCK "
"missing mandatory IE\n");
goto err_mand_ie;
}
@@ -1006,21 +1093,21 @@ static int bssgp_rx_sign(struct msgb *msg, struct tlv_parsed *tp,
break;
case BSSGP_PDUT_BVC_UNBLOCK:
/* BSS tells us that BVC shall be unblocked */
- if (!TLVP_PRESENT(tp, BSSGP_IE_BVCI)) {
- LOGP(DBSSGP, LOGL_ERROR, "BSSGP Rx BVC-UNBLOCK "
+ if (!TLVP_PRES_LEN(tp, BSSGP_IE_BVCI, 2)) {
+ LOGP(DLBSSGP, LOGL_ERROR, "BSSGP Rx BVC-UNBLOCK "
"missing mandatory IE\n");
goto err_mand_ie;
}
rc = bssgp_rx_bvc_unblock(msg, tp);
break;
case BSSGP_PDUT_BVC_RESET_ACK:
- LOGP(DBSSGP, LOGL_ERROR, "BSSGP BVCI=%u Rx BVC-RESET-ACK\n", bvci);
+ LOGP(DLBSSGP, LOGL_ERROR, "BSSGP BVCI=%u Rx BVC-RESET-ACK\n", bvci);
break;
case BSSGP_PDUT_BVC_RESET:
- /* BSS tells us that BVC init is required */
- if (!TLVP_PRESENT(tp, BSSGP_IE_BVCI) ||
- !TLVP_PRESENT(tp, BSSGP_IE_CAUSE)) {
- LOGP(DBSSGP, LOGL_ERROR, "BSSGP Rx BVC-RESET "
+ /* SGSN or BSS tells us that BVC init is required */
+ if (!TLVP_PRES_LEN(tp, BSSGP_IE_BVCI, 2) ||
+ !TLVP_PRES_LEN(tp, BSSGP_IE_CAUSE, 1)) {
+ LOGP(DLBSSGP, LOGL_ERROR, "BSSGP Rx BVC-RESET "
"missing mandatory IE\n");
goto err_mand_ie;
}
@@ -1029,6 +1116,15 @@ static int bssgp_rx_sign(struct msgb *msg, struct tlv_parsed *tp,
case BSSGP_PDUT_STATUS:
/* This is already handled in bssgp_rcvmsg() */
break;
+
+ case BSSGP_PDUT_RAN_INFO:
+ case BSSGP_PDUT_RAN_INFO_REQ:
+ case BSSGP_PDUT_RAN_INFO_ACK:
+ case BSSGP_PDUT_RAN_INFO_ERROR:
+ case BSSGP_PDUT_RAN_INFO_APP_ERROR:
+ rc = bssgp_rx_rim(msg, tp, bvci);
+ break;
+
/* those only exist in the SGSN -> BSS direction */
case BSSGP_PDUT_PAGING_PS:
case BSSGP_PDUT_PAGING_CS:
@@ -1040,13 +1136,13 @@ static int bssgp_rx_sign(struct msgb *msg, struct tlv_parsed *tp,
case BSSGP_PDUT_BVC_BLOCK_ACK:
case BSSGP_PDUT_BVC_UNBLOCK_ACK:
case BSSGP_PDUT_SGSN_INVOKE_TRACE:
- DEBUGP(DBSSGP, "BSSGP BVCI=%u Rx PDU type %s only exists in DL\n",
+ DEBUGP(DLBSSGP, "BSSGP BVCI=%u Rx PDU type %s only exists in DL\n",
bvci, bssgp_pdu_str(pdu_type));
bssgp_tx_status(BSSGP_CAUSE_PROTO_ERR_UNSPEC, NULL, msg);
rc = -EINVAL;
break;
default:
- DEBUGP(DBSSGP, "BSSGP BVCI=%u Rx PDU type %s unknown\n",
+ DEBUGP(DLBSSGP, "BSSGP BVCI=%u Rx PDU type %s unknown\n",
bvci, bssgp_pdu_str(pdu_type));
rc = bssgp_tx_status(BSSGP_CAUSE_PROTO_ERR_UNSPEC, NULL, msg);
break;
@@ -1084,14 +1180,14 @@ int bssgp_rcvmsg(struct msgb *msg)
rc = bssgp_tlv_parse(&tp, budh->data, data_len);
}
if (rc < 0) {
- LOGP(DBSSGP, LOGL_ERROR, "Failed to parse BSSGP %s message. Invalid message was: %s\n",
+ LOGP(DLBSSGP, LOGL_ERROR, "Failed to parse BSSGP %s message. Invalid message was: %s\n",
bssgp_pdu_str(pdu_type), msgb_hexdump(msg));
if (pdu_type != BSSGP_PDUT_STATUS)
return bssgp_tx_status(BSSGP_CAUSE_INV_MAND_INF, NULL, msg);
return rc;
}
- if (bvci == BVCI_SIGNALLING && TLVP_PRESENT(&tp, BSSGP_IE_BVCI))
+ if (bvci == BVCI_SIGNALLING && TLVP_PRES_LEN(&tp, BSSGP_IE_BVCI, 2))
bvci = tlvp_val16be(&tp, BSSGP_IE_BVCI);
/* look-up or create the BTS context for this BVC */
@@ -1099,8 +1195,8 @@ int bssgp_rcvmsg(struct msgb *msg)
if (bctx) {
log_set_context(LOG_CTX_GB_BVC, bctx);
- rate_ctr_inc(&bctx->ctrg->ctr[BSSGP_CTR_PKTS_IN]);
- rate_ctr_add(&bctx->ctrg->ctr[BSSGP_CTR_BYTES_IN],
+ rate_ctr_inc(rate_ctr_group_get_ctr(bctx->ctrg, BSSGP_CTR_PKTS_IN));
+ rate_ctr_add(rate_ctr_group_get_ctr(bctx->ctrg, BSSGP_CTR_BYTES_IN),
msgb_bssgp_len(msg));
}
@@ -1114,7 +1210,7 @@ int bssgp_rcvmsg(struct msgb *msg)
* registered if a BVCI is given. */
if (!bctx && bvci != BVCI_SIGNALLING &&
pdu_type != BSSGP_PDUT_BVC_RESET) {
- LOGP(DBSSGP, LOGL_NOTICE, "NSEI=%u/BVCI=%u Rejecting PDU type %s for unknown BVCI\n", nsei, bvci,
+ LOGP(DLBSSGP, LOGL_NOTICE, "NSEI=%u/BVCI=%u Rejecting PDU type %s for unknown BVCI\n", nsei, bvci,
bssgp_pdu_str(pdu_type));
return bssgp_tx_status(BSSGP_CAUSE_UNKNOWN_BVCI, &bvci, msg);
}
@@ -1126,7 +1222,7 @@ int bssgp_rcvmsg(struct msgb *msg)
else if (bctx)
rc = bssgp_rx_ptp(msg, &tp, bctx);
else
- LOGP(DBSSGP, LOGL_NOTICE,
+ LOGP(DLBSSGP, LOGL_NOTICE,
"NSEI=%u/BVCI=%u Cannot handle PDU type %s for unknown BVCI, NS BVCI %u\n", nsei, bvci,
bssgp_pdu_str(pdu_type), ns_bvci);
@@ -1150,7 +1246,7 @@ int bssgp_tx_dl_ud(struct msgb *msg, uint16_t pdu_lifetime,
/* Identifiers from UP: TLLI, BVCI, NSEI (all in msgb->cb) */
if (bvci <= BVCI_PTM ) {
- LOGP(DBSSGP, LOGL_ERROR, "Cannot send DL-UD to BVCI %u\n",
+ LOGP(DLBSSGP, LOGL_ERROR, "Cannot send DL-UD to BVCI %u\n",
bvci);
msgb_free(msg);
return -EINVAL;
@@ -1158,7 +1254,7 @@ int bssgp_tx_dl_ud(struct msgb *msg, uint16_t pdu_lifetime,
bctx = btsctx_by_bvci_nsei(bvci, nsei);
if (!bctx) {
- LOGP(DBSSGP, LOGL_ERROR, "Cannot send DL-UD to unknown BVCI %u\n",
+ LOGP(DLBSSGP, LOGL_ERROR, "Cannot send DL-UD to unknown BVCI %u\n",
bvci);
msgb_free(msg);
return -ENODEV;
@@ -1224,8 +1320,8 @@ int bssgp_tx_dl_ud(struct msgb *msg, uint16_t pdu_lifetime,
budh->tlli = osmo_htonl(msgb_tlli(msg));
budh->pdu_type = BSSGP_PDUT_DL_UNITDATA;
- rate_ctr_inc(&bctx->ctrg->ctr[BSSGP_CTR_PKTS_OUT]);
- rate_ctr_add(&bctx->ctrg->ctr[BSSGP_CTR_BYTES_OUT], msg->len);
+ rate_ctr_inc(rate_ctr_group_get_ctr(bctx->ctrg, BSSGP_CTR_PKTS_OUT));
+ rate_ctr_add(rate_ctr_group_get_ctr(bctx->ctrg, BSSGP_CTR_BYTES_OUT), msg->len);
/* Identifiers down: BVCI, NSEI (in msgb->cb) */
@@ -1309,7 +1405,9 @@ int bssgp_tx_paging(uint16_t nsei, uint16_t ns_bvci,
void bssgp_set_log_ss(int ss)
{
- DBSSGP = ss;
+ /* BSSGP has moved from DGPRS to DLGPRS, please update your code if it's
+ * still calling this function
+ */
}
/*!
@@ -1330,7 +1428,7 @@ void bssgp_fc_flush_queue(struct bssgp_flow_control *fc)
/*!
* \brief Flush the queues of all BSSGP contexts.
*/
-void bssgp_flush_all_queues()
+void bssgp_flush_all_queues(void)
{
struct bssgp_bvc_ctx *bctx;
diff --git a/src/gb/gprs_bssgp2.c b/src/gb/gprs_bssgp2.c
new file mode 100644
index 00000000..104fe08e
--- /dev/null
+++ b/src/gb/gprs_bssgp2.c
@@ -0,0 +1,486 @@
+/* BSSGP2 - second generation of BSSGP library */
+
+/* (C) 2020 Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <osmocom/core/utils.h>
+#include <osmocom/core/byteswap.h>
+#include <osmocom/core/msgb.h>
+
+#include <osmocom/gsm/gsm48.h>
+#include <osmocom/gsm/tlv.h>
+
+#include <osmocom/gprs/gprs_ns2.h>
+#include <osmocom/gprs/gprs_bssgp.h>
+#include <osmocom/gprs/gprs_bssgp2.h>
+
+
+/*! transmit BSSGP PDU over NS (PTP BVC)
+ * \param[in] nsi NS Instance through which to transmit
+ * \param[in] nsei NSEI of NSE through which to transmit
+ * \param[in] bvci BVCI through which to transmit
+ * \param[in] msg BSSGP PDU to transmit
+ * \returns 0 on success; negative on error */
+int bssgp2_nsi_tx_ptp(struct gprs_ns2_inst *nsi, uint16_t nsei, uint16_t bvci,
+ struct msgb *msg, uint32_t lsp)
+{
+ struct osmo_gprs_ns2_prim nsp = {};
+ int rc;
+
+ if (!msg)
+ return 0;
+
+ nsp.bvci = bvci;
+ nsp.nsei = nsei;
+ nsp.u.unitdata.link_selector = lsp;
+
+ osmo_prim_init(&nsp.oph, SAP_NS, GPRS_NS2_PRIM_UNIT_DATA, PRIM_OP_REQUEST, msg);
+ rc = gprs_ns2_recv_prim(nsi, &nsp.oph);
+
+ return rc;
+}
+
+/*! transmit BSSGP PDU over NS (SIGNALING BVC)
+ * \param[in] nsi NS Instance through which to transmit
+ * \param[in] nsei NSEI of NSE through which to transmit
+ * \param[in] msg BSSGP PDU to transmit
+ * \returns 0 on success; negative on error */
+int bssgp2_nsi_tx_sig(struct gprs_ns2_inst *nsi, uint16_t nsei, struct msgb *msg, uint32_t lsp)
+{
+ return bssgp2_nsi_tx_ptp(nsi, nsei, 0, msg, lsp);
+}
+
+/*! Encode BSSGP BVC-BLOCK PDU as per TS 48.018 Section 10.4.8. */
+struct msgb *bssgp2_enc_bvc_block(uint16_t bvci, enum gprs_bssgp_cause cause)
+{
+ struct msgb *msg = bssgp_msgb_alloc();
+ struct bssgp_normal_hdr *bgph;
+ uint16_t _bvci = osmo_htons(bvci);
+
+ if (!msg)
+ return NULL;
+
+ bgph = (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
+ bgph->pdu_type = BSSGP_PDUT_BVC_BLOCK;
+
+ msgb_tvlv_put(msg, BSSGP_IE_BVCI, 2, (uint8_t *) &_bvci);
+ msgb_tvlv_put(msg, BSSGP_IE_CAUSE, 1, (uint8_t *) &cause);
+
+ return msg;
+}
+
+/*! Encode BSSGP BVC-BLOCK-ACK PDU as per TS 48.018 Section 10.4.9. */
+struct msgb *bssgp2_enc_bvc_block_ack(uint16_t bvci)
+{
+ struct msgb *msg = bssgp_msgb_alloc();
+ struct bssgp_normal_hdr *bgph;
+ uint16_t _bvci = osmo_htons(bvci);
+
+ if (!msg)
+ return NULL;
+
+ bgph = (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
+ bgph->pdu_type = BSSGP_PDUT_BVC_BLOCK_ACK;
+
+ msgb_tvlv_put(msg, BSSGP_IE_BVCI, 2, (uint8_t *) &_bvci);
+
+ return msg;
+}
+
+/*! Encode BSSGP BVC-UNBLOCK PDU as per TS 48.018 Section 10.4.10. */
+struct msgb *bssgp2_enc_bvc_unblock(uint16_t bvci)
+{
+ struct msgb *msg = bssgp_msgb_alloc();
+ struct bssgp_normal_hdr *bgph;
+ uint16_t _bvci = osmo_htons(bvci);
+
+ if (!msg)
+ return NULL;
+
+ bgph = (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
+ bgph->pdu_type = BSSGP_PDUT_BVC_UNBLOCK;
+
+ msgb_tvlv_put(msg, BSSGP_IE_BVCI, 2, (uint8_t *) &_bvci);
+
+ return msg;
+}
+
+/*! Encode BSSGP BVC-UNBLOCK-ACK PDU as per TS 48.018 Section 10.4.11. */
+struct msgb *bssgp2_enc_bvc_unblock_ack(uint16_t bvci)
+{
+ struct msgb *msg = bssgp_msgb_alloc();
+ struct bssgp_normal_hdr *bgph;
+ uint16_t _bvci = osmo_htons(bvci);
+
+ if (!msg)
+ return NULL;
+
+ bgph = (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
+ bgph->pdu_type = BSSGP_PDUT_BVC_UNBLOCK_ACK;
+
+ msgb_tvlv_put(msg, BSSGP_IE_BVCI, 2, (uint8_t *) &_bvci);
+
+ return msg;
+}
+
+/*! Encode BSSGP BVC-RESET PDU as per TS 48.018 Section 10.4.12.
+ * \param[in] bvci PTP BVCI to encode into the BVCI IE
+ * \param[in] cause BSSGP Cause value (reason for reset)
+ * \param[in] ra_id Routing Area ID to be encoded to CELL_ID IE (optional)
+ * \param[in] cell_id Cell ID to be encoded to CELL_ID IE (only if ra_id is non-NULL)
+ * \param[in] feat_bm Feature Bitmap (optional)
+ * \param[in] ext_feat_bm Extended Feature Bitmap (optional) */
+struct msgb *bssgp2_enc_bvc_reset(uint16_t bvci, enum gprs_bssgp_cause cause,
+ const struct gprs_ra_id *ra_id, uint16_t cell_id,
+ const uint8_t *feat_bm, const uint8_t *ext_feat_bm)
+{
+ struct msgb *msg = bssgp_msgb_alloc();
+ struct bssgp_normal_hdr *bgph;
+ uint16_t _bvci = osmo_htons(bvci);
+
+ if (!msg)
+ return NULL;
+
+ bgph = (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
+ bgph->pdu_type = BSSGP_PDUT_BVC_RESET;
+
+ msgb_tvlv_put(msg, BSSGP_IE_BVCI, 2, (uint8_t *) &_bvci);
+ msgb_tvlv_put(msg, BSSGP_IE_CAUSE, 1, (uint8_t *) &cause);
+ if (ra_id) {
+ uint8_t bssgp_cid[8];
+ bssgp_create_cell_id(bssgp_cid, ra_id, cell_id);
+ msgb_tvlv_put(msg, BSSGP_IE_CELL_ID, sizeof(bssgp_cid), bssgp_cid);
+ }
+
+ if (feat_bm)
+ msgb_tvlv_put(msg, BSSGP_IE_FEATURE_BITMAP, 1, feat_bm);
+
+ if (ext_feat_bm)
+ msgb_tvlv_put(msg, BSSGP_IE_EXT_FEATURE_BITMAP, 1, feat_bm);
+
+ return msg;
+}
+
+/*! Encode BSSGP BVC-RESET-ACK PDU as per TS 48.018 Section 10.4.13.
+ * \param[in] bvci PTP BVCI to encode into the BVCI IE
+ * \param[in] ra_id Routing Area ID to be encoded to CELL_ID IE (optional)
+ * \param[in] cell_id Cell ID to be encoded to CELL_ID IE (only if ra_id is non-NULL)
+ * \param[in] feat_bm Feature Bitmap (optional)
+ * \param[in] ext_feat_bm Extended Feature Bitmap (optional) */
+struct msgb *bssgp2_enc_bvc_reset_ack(uint16_t bvci, const struct gprs_ra_id *ra_id, uint16_t cell_id,
+ const uint8_t *feat_bm, const uint8_t *ext_feat_bm)
+{
+ struct msgb *msg = bssgp_msgb_alloc();
+ struct bssgp_normal_hdr *bgph;
+ uint16_t _bvci = osmo_htons(bvci);
+
+ if (!msg)
+ return NULL;
+
+ bgph = (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
+ bgph->pdu_type = BSSGP_PDUT_BVC_RESET_ACK;
+
+ msgb_tvlv_put(msg, BSSGP_IE_BVCI, 2, (uint8_t *) &_bvci);
+ if (ra_id) {
+ uint8_t bssgp_cid[8];
+ bssgp_create_cell_id(bssgp_cid, ra_id, cell_id);
+ msgb_tvlv_put(msg, BSSGP_IE_CELL_ID, sizeof(bssgp_cid), bssgp_cid);
+ }
+
+ if (feat_bm)
+ msgb_tvlv_put(msg, BSSGP_IE_FEATURE_BITMAP, 1, feat_bm);
+
+ if (ext_feat_bm)
+ msgb_tvlv_put(msg, BSSGP_IE_EXT_FEATURE_BITMAP, 1, feat_bm);
+
+ return msg;
+}
+
+/*! Encode BSSGP STATUS PDU as per TS 48.018 Section 10.4.14.
+ * \param[in] cause BSSGP Cause value
+ * \param[in] bvci optional BVCI - only encoded if non-NULL
+ * \param[in] msg optional message buffer containing PDU in error - only encoded if non-NULL
+ * \param[in] max_pdu_len Maximum BSSGP PDU size the NS layer accepts */
+struct msgb *bssgp2_enc_status(uint8_t cause, const uint16_t *bvci, const struct msgb *orig_msg, uint16_t max_pdu_len)
+{
+ struct msgb *msg = bssgp_msgb_alloc();
+ struct bssgp_normal_hdr *bgph;
+
+ if (!msg)
+ return NULL;
+
+ bgph = (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
+ bgph->pdu_type = BSSGP_PDUT_STATUS;
+ msgb_tvlv_put(msg, BSSGP_IE_CAUSE, 1, &cause);
+ /* FIXME: Require/encode BVCI only if cause is BVCI unknown/blocked
+ * See 3GPP TS 48.018 Ch. 10.4.14 */
+ if (bvci) {
+ uint16_t _bvci = osmo_htons(*bvci);
+ msgb_tvlv_put(msg, BSSGP_IE_BVCI, 2, (uint8_t *) &_bvci);
+ }
+ if (orig_msg) {
+ uint32_t orig_len, max_orig_len;
+ /* Calculate how big the reply would be: the BSSGP msg so far + size of the PDU IN ERROR including tvl */
+ orig_len = msgb_bssgp_len(orig_msg);
+ max_orig_len = msgb_length(msg) + TVLV_GROSS_LEN(orig_len);
+ /* Truncate the difference between max_orig_len and mtu */
+ if (max_orig_len > max_pdu_len)
+ orig_len -= max_orig_len - max_pdu_len;
+ msgb_tvlv_put(msg, BSSGP_IE_PDU_IN_ERROR, orig_len, msgb_bssgph(orig_msg));
+ }
+
+ return msg;
+}
+
+static const unsigned int bssgp_fc_gran_tbl[] = {
+ [BSSGP_FC_GRAN_100] = 100,
+ [BSSGP_FC_GRAN_1000] = 1000,
+ [BSSGP_FC_GRAN_10000] = 10000,
+ [BSSGP_FC_GRAN_100000] = 100000,
+};
+
+/*! Decode a FLOW-CONTROL-BVC PDU as per TS 48.018 Section 10.4.4.
+ * \param[out] fc caller-allocated memory for parsed output
+ * \param[in] tp pre-parsed TLVs; caller must ensure mandatory IE presence/length
+ * \returns 0 on success; negative in case of error */
+int bssgp2_dec_fc_bvc(struct bssgp2_flow_ctrl *fc, const struct tlv_parsed *tp)
+{
+ unsigned int granularity = 100;
+
+ /* optional "Flow Control Granularity IE" (11.3.102); applies to
+ * bucket_size_max, bucket_leak_rate and PFC FC params IE */
+ if (TLVP_PRESENT(tp, BSSGP_IE_FLOW_CTRL_GRANULARITY)) {
+ uint8_t gran = *TLVP_VAL(tp, BSSGP_IE_FLOW_CTRL_GRANULARITY);
+ granularity = bssgp_fc_gran_tbl[gran & 3];
+ }
+
+ /* mandatory IEs */
+ fc->tag = *TLVP_VAL(tp, BSSGP_IE_TAG);
+ fc->bucket_size_max = granularity * tlvp_val16be(tp, BSSGP_IE_BVC_BUCKET_SIZE);
+ fc->bucket_leak_rate = (granularity * tlvp_val16be(tp, BSSGP_IE_BUCKET_LEAK_RATE)) / 8;
+ fc->u.bvc.bmax_default_ms = granularity * tlvp_val16be(tp, BSSGP_IE_BMAX_DEFAULT_MS);
+ fc->u.bvc.r_default_ms = (granularity * tlvp_val16be(tp, BSSGP_IE_R_DEFAULT_MS)) / 8;
+
+ /* optional / conditional */
+ if (TLVP_PRESENT(tp, BSSGP_IE_BUCKET_FULL_RATIO)) {
+ fc->bucket_full_ratio_present = true;
+ fc->bucket_full_ratio = *TLVP_VAL(tp, BSSGP_IE_BUCKET_FULL_RATIO);
+ } else {
+ fc->bucket_full_ratio_present = false;
+ }
+
+ if (TLVP_PRESENT(tp, BSSGP_IE_BVC_MEASUREMENT)) {
+ uint16_t val = tlvp_val16be(tp, BSSGP_IE_BVC_MEASUREMENT);
+ fc->u.bvc.measurement_present = true;
+ /* convert from centi-seconds to milli-seconds */
+ if (val == 0xffff)
+ fc->u.bvc.measurement = 0xffffffff;
+ else
+ fc->u.bvc.measurement = val * 10;
+ } else {
+ fc->u.bvc.measurement_present = false;
+ }
+
+ return 0;
+
+}
+
+/*! Encode a FLOW-CONTROL-BVC PDU as per TS 48.018 Section 10.4.4.
+ * \param[in] fc structure describing to-be-encoded FC parameters
+ * \param[in] gran if non-NULL: Encode using specified unit granularity
+ * \returns encoded PDU or NULL in case of error */
+struct msgb *bssgp2_enc_fc_bvc(const struct bssgp2_flow_ctrl *fc, enum bssgp_fc_granularity *gran)
+{
+ struct msgb *msg = bssgp_msgb_alloc();
+ struct bssgp_normal_hdr *bgph;
+ unsigned int granularity = 100;
+
+ if (gran)
+ granularity = bssgp_fc_gran_tbl[*gran & 3];
+
+ if (!msg)
+ return NULL;
+
+ bgph = (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
+ bgph->pdu_type = BSSGP_PDUT_FLOW_CONTROL_BVC;
+
+ msgb_tvlv_put(msg, BSSGP_IE_TAG, 1, &fc->tag);
+ msgb_tvlv_put_16be(msg, BSSGP_IE_BVC_BUCKET_SIZE, fc->bucket_size_max / granularity);
+ msgb_tvlv_put_16be(msg, BSSGP_IE_BUCKET_LEAK_RATE, fc->bucket_leak_rate * 8 / granularity);
+ msgb_tvlv_put_16be(msg, BSSGP_IE_BMAX_DEFAULT_MS, fc->u.bvc.bmax_default_ms / granularity);
+ msgb_tvlv_put_16be(msg, BSSGP_IE_R_DEFAULT_MS, fc->u.bvc.r_default_ms * 8 / granularity);
+
+ if (fc->bucket_full_ratio_present)
+ msgb_tvlv_put(msg, BSSGP_IE_BUCKET_FULL_RATIO, 1, &fc->bucket_full_ratio);
+
+ if (fc->u.bvc.measurement_present) {
+ uint16_t val;
+ /* convert from ms to cs */
+ if (fc->u.bvc.measurement == 0xffffffff)
+ val = 0xffff;
+ else
+ val = fc->u.bvc.measurement / 10;
+ msgb_tvlv_put_16be(msg, BSSGP_IE_BVC_MEASUREMENT, val);
+ }
+
+ if (gran) {
+ uint8_t val = *gran & 3;
+ msgb_tvlv_put(msg, BSSGP_IE_FLOW_CTRL_GRANULARITY, 1, &val);
+ }
+
+ return msg;
+}
+
+/*! Encode BSSGP FLUSH-LL PDU as per TS 48.018 Section 10.4.1.
+ * \param[in] tlli - the TLLI of the MS
+ * \param[in] old_bvci BVCI
+ * \param[in] new_bvci2 optional BVCI - only encoded if non-NULL
+ * \param[in] nsei optional - only encoded if non-NULL
+ * \returns encoded PDU or NULL in case of error */
+struct msgb *bssgp2_enc_flush_ll(uint32_t tlli, uint16_t old_bvci,
+ const uint16_t *new_bvci, const uint16_t *nsei)
+{
+ struct msgb *msg = bssgp_msgb_alloc();
+ struct bssgp_normal_hdr *bgph;
+
+ if (!msg)
+ return NULL;
+
+ bgph = (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
+ bgph->pdu_type = BSSGP_PDUT_FLUSH_LL;
+
+ msgb_tvlv_put_32be(msg, BSSGP_IE_TLLI, tlli);
+ msgb_tvlv_put_16be(msg, BSSGP_IE_BVCI, old_bvci);
+ if (new_bvci)
+ msgb_tvlv_put_16be(msg, BSSGP_IE_BVCI, *new_bvci);
+
+ if (nsei)
+ msgb_tvlv_put_16be(msg, BSSGP_IE_BVCI, *nsei);
+
+ return msg;
+}
+
+/*! Encode a FLOW-CONTROL-BVC-ACK PDU as per TS 48.018 Section 10.4.4.
+ * \param[in] tag the tag IE value to encode
+ * \returns encoded PDU or NULL in case of error */
+struct msgb *bssgp2_enc_fc_bvc_ack(uint8_t tag)
+{
+ struct msgb *msg = bssgp_msgb_alloc();
+ struct bssgp_normal_hdr *bgph;
+
+ if (!msg)
+ return NULL;
+
+ bgph = (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
+ bgph->pdu_type = BSSGP_PDUT_FLOW_CONTROL_BVC_ACK;
+
+ msgb_tvlv_put(msg, BSSGP_IE_TAG, 1, &tag);
+
+ return msg;
+}
+
+/*! Decode a FLOW-CONTROL-MS PDU as per TS 48.018 Section 10.4.6.
+ * \param[out] fc caller-allocated memory for parsed output
+ * \param[in] tp pre-parsed TLVs; caller must ensure mandatory IE presence/length
+ * \returns 0 on success; negative in case of error */
+int bssgp2_dec_fc_ms(struct bssgp2_flow_ctrl *fc, struct tlv_parsed *tp)
+{
+ unsigned int granularity = 100;
+
+ /* optional "Flow Control Granularity IE" (11.3.102); applies to
+ * bucket_size_max, bucket_leak_rate and PFC FC params IE */
+ if (TLVP_PRESENT(tp, BSSGP_IE_FLOW_CTRL_GRANULARITY)) {
+ uint8_t gran = *TLVP_VAL(tp, BSSGP_IE_FLOW_CTRL_GRANULARITY);
+ granularity = bssgp_fc_gran_tbl[gran & 3];
+ }
+
+ /* mandatory IEs */
+ fc->u.ms.tlli = tlvp_val32be(tp, BSSGP_IE_TLLI);
+ fc->tag = *TLVP_VAL(tp, BSSGP_IE_TAG);
+ fc->bucket_size_max = granularity * tlvp_val16be(tp, BSSGP_IE_MS_BUCKET_SIZE);
+ fc->bucket_leak_rate = (granularity * tlvp_val16be(tp, BSSGP_IE_BUCKET_LEAK_RATE)) / 8;
+
+ /* optional / conditional */
+ if (TLVP_PRESENT(tp, BSSGP_IE_BUCKET_FULL_RATIO)) {
+ fc->bucket_full_ratio_present = true;
+ fc->bucket_full_ratio = *TLVP_VAL(tp, BSSGP_IE_BUCKET_FULL_RATIO);
+ } else {
+ fc->bucket_full_ratio_present = false;
+ }
+
+ return 0;
+}
+
+/*! Encode a FLOW-CONTROL-MS PDU as per TS 48.018 Section 10.4.6.
+ * \param[in] fc structure describing to-be-encoded FC parameters
+ * \param[in] gran if non-NULL: Encode using specified unit granularity
+ * \returns encoded PDU or NULL in case of error */
+struct msgb *bssgp2_enc_fc_ms(const struct bssgp2_flow_ctrl *fc, enum bssgp_fc_granularity *gran)
+{
+ struct msgb *msg = bssgp_msgb_alloc();
+ struct bssgp_normal_hdr *bgph;
+ unsigned int granularity = 100;
+
+ if (gran)
+ granularity = bssgp_fc_gran_tbl[*gran & 3];
+
+ if (!msg)
+ return NULL;
+
+ bgph = (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
+ bgph->pdu_type = BSSGP_PDUT_FLOW_CONTROL_MS;
+
+ msgb_tvlv_put_32be(msg, BSSGP_IE_TLLI, fc->u.ms.tlli);
+ msgb_tvlv_put(msg, BSSGP_IE_TAG, 1, &fc->tag);
+ msgb_tvlv_put_16be(msg, BSSGP_IE_MS_BUCKET_SIZE, fc->bucket_size_max / granularity);
+ msgb_tvlv_put_16be(msg, BSSGP_IE_BUCKET_LEAK_RATE, fc->bucket_leak_rate * 8 / granularity);
+
+ if (fc->bucket_full_ratio_present)
+ msgb_tvlv_put(msg, BSSGP_IE_BUCKET_FULL_RATIO, 1, &fc->bucket_full_ratio);
+
+ if (gran) {
+ uint8_t val = *gran & 3;
+ msgb_tvlv_put(msg, BSSGP_IE_FLOW_CTRL_GRANULARITY, 1, &val);
+ }
+
+ return msg;
+}
+
+/*! Encode a FLOW-CONTROL-BVC-ACK PDU as per TS 48.018 Section 10.4.7.
+ * \param[in] tlli the TLLI IE value to encode
+ * \param[in] tag the tag IE value to encode
+ * \returns encoded PDU or NULL in case of error */
+struct msgb *bssgp2_enc_fc_ms_ack(uint32_t tlli, uint8_t tag)
+{
+ struct msgb *msg = bssgp_msgb_alloc();
+ struct bssgp_normal_hdr *bgph;
+
+ if (!msg)
+ return NULL;
+
+ bgph = (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
+ bgph->pdu_type = BSSGP_PDUT_FLOW_CONTROL_MS_ACK;
+
+ msgb_tvlv_put_32be(msg, BSSGP_IE_TLLI, tlli);
+ msgb_tvlv_put(msg, BSSGP_IE_TAG, 1, &tag);
+
+ return msg;
+}
diff --git a/src/gb/gprs_bssgp_bss.c b/src/gb/gprs_bssgp_bss.c
index 94b18126..8230d871 100644
--- a/src/gb/gprs_bssgp_bss.c
+++ b/src/gb/gprs_bssgp_bss.c
@@ -35,7 +35,6 @@
#include <osmocom/gprs/gprs_ns.h>
#include "gprs_bssgp_internal.h"
-#include "common_vty.h"
#define GSM_IMSI_LENGTH 17
@@ -61,7 +60,7 @@ int bssgp_tx_suspend(uint16_t nsei, uint32_t tlli,
struct bssgp_normal_hdr *bgph =
(struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
- LOGP(DBSSGP, LOGL_NOTICE, "BSSGP (BVCI=0) Tx SUSPEND (TLLI=0x%04x)\n",
+ LOGP(DLBSSGP, LOGL_NOTICE, "BSSGP (BVCI=0) Tx SUSPEND (TLLI=0x%04x)\n",
tlli);
msgb_nsei(msg) = nsei;
msgb_bvci(msg) = 0; /* Signalling */
@@ -81,7 +80,7 @@ int bssgp_tx_resume(uint16_t nsei, uint32_t tlli,
struct bssgp_normal_hdr *bgph =
(struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
- LOGP(DBSSGP, LOGL_NOTICE, "BSSGP (BVCI=0) Tx RESUME (TLLI=0x%04x)\n",
+ LOGP(DLBSSGP, LOGL_NOTICE, "BSSGP (BVCI=0) Tx RESUME (TLLI=0x%04x)\n",
tlli);
msgb_nsei(msg) = nsei;
msgb_bvci(msg) = 0; /* Signalling */
@@ -102,7 +101,7 @@ int bssgp_tx_ra_capa_upd(struct bssgp_bvc_ctx *bctx, uint32_t tlli, uint8_t tag)
struct bssgp_normal_hdr *bgph =
(struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
- LOGP(DBSSGP, LOGL_NOTICE, "BSSGP (BVCI=%u) Tx RA-CAPA-UPD (TLLI=0x%04x)\n",
+ LOGP(DLBSSGP, LOGL_NOTICE, "BSSGP (BVCI=%u) Tx RA-CAPA-UPD (TLLI=0x%04x)\n",
bctx->bvci, tlli);
/* set NSEI and BVCI in msgb cb */
@@ -124,7 +123,7 @@ static struct msgb *common_tx_radio_status(struct bssgp_bvc_ctx *bctx)
struct bssgp_normal_hdr *bgph =
(struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
- LOGP(DBSSGP, LOGL_NOTICE, "BSSGP (BVCI=%u) Tx RADIO-STATUS ",
+ LOGP(DLBSSGP, LOGL_NOTICE, "BSSGP (BVCI=%u) Tx RADIO-STATUS ",
bctx->bvci);
/* set NSEI and BVCI in msgb cb */
@@ -140,7 +139,7 @@ static struct msgb *common_tx_radio_status(struct bssgp_bvc_ctx *bctx)
static int common_tx_radio_status2(struct msgb *msg, uint8_t cause)
{
msgb_tvlv_put(msg, BSSGP_IE_CAUSE, 1, &cause);
- LOGPC(DBSSGP, LOGL_NOTICE, "CAUSE=%s\n", bssgp_cause_str(cause));
+ LOGPC(DLBSSGP, LOGL_NOTICE, "CAUSE=%s\n", bssgp_cause_str(cause));
return bssgp_ns_send(bssgp_ns_send_data, msg);
}
@@ -154,7 +153,7 @@ int bssgp_tx_radio_status_tlli(struct bssgp_bvc_ctx *bctx, uint8_t cause,
if (!msg)
return -ENOMEM;
bssgp_msgb_tlli_put(msg, tlli);
- LOGPC(DBSSGP, LOGL_NOTICE, "TLLI=0x%08x ", tlli);
+ LOGPC(DLBSSGP, LOGL_NOTICE, "TLLI=0x%08x ", tlli);
return common_tx_radio_status2(msg, cause);
}
@@ -169,7 +168,7 @@ int bssgp_tx_radio_status_tmsi(struct bssgp_bvc_ctx *bctx, uint8_t cause,
if (!msg)
return -ENOMEM;
msgb_tvlv_put(msg, BSSGP_IE_TMSI, 4, (uint8_t *)&_tmsi);
- LOGPC(DBSSGP, LOGL_NOTICE, "TMSI=0x%08x ", tmsi);
+ LOGPC(DLBSSGP, LOGL_NOTICE, "TMSI=0x%08x ", tmsi);
return common_tx_radio_status2(msg, cause);
}
@@ -195,7 +194,7 @@ int bssgp_tx_radio_status_imsi(struct bssgp_bvc_ctx *bctx, uint8_t cause,
if (imsi_len > 2)
msgb_tvlv_put(msg, BSSGP_IE_IMSI, imsi_len-2, mi+2);
#pragma GCC diagnostic pop
- LOGPC(DBSSGP, LOGL_NOTICE, "IMSI=%s ", imsi);
+ LOGPC(DLBSSGP, LOGL_NOTICE, "IMSI=%s ", imsi);
return common_tx_radio_status2(msg, cause);
}
@@ -234,7 +233,7 @@ int bssgp_tx_llc_discarded(struct bssgp_bvc_ctx *bctx, uint32_t tlli,
uint16_t _bvci = osmo_htons(bctx->bvci);
uint32_t _oct_aff = osmo_htonl(num_octets & 0xFFFFFF);
- LOGP(DBSSGP, LOGL_NOTICE, "BSSGP (BVCI=%u) Tx LLC-DISCARDED "
+ LOGP(DLBSSGP, LOGL_NOTICE, "BSSGP (BVCI=%u) Tx LLC-DISCARDED "
"TLLI=0x%04x, FRAMES=%u, OCTETS=%u\n", bctx->bvci, tlli,
num_frames, num_octets);
msgb_nsei(msg) = bctx->nsei;
@@ -258,7 +257,7 @@ int bssgp_tx_bvc_block(struct bssgp_bvc_ctx *bctx, uint8_t cause)
(struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
uint16_t _bvci = osmo_htons(bctx->bvci);
- LOGP(DBSSGP, LOGL_NOTICE, "BSSGP (BVCI=%u) Tx BVC-BLOCK "
+ LOGP(DLBSSGP, LOGL_NOTICE, "BSSGP (BVCI=%u) Tx BVC-BLOCK "
"CAUSE=%s\n", bctx->bvci, bssgp_cause_str(cause));
msgb_nsei(msg) = bctx->nsei;
@@ -279,7 +278,7 @@ int bssgp_tx_bvc_unblock(struct bssgp_bvc_ctx *bctx)
(struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
uint16_t _bvci = osmo_htons(bctx->bvci);
- LOGP(DBSSGP, LOGL_NOTICE, "BSSGP (BVCI=%u) Tx BVC-UNBLOCK\n", bctx->bvci);
+ LOGP(DLBSSGP, LOGL_NOTICE, "BSSGP (BVCI=%u) Tx BVC-UNBLOCK\n", bctx->bvci);
msgb_nsei(msg) = bctx->nsei;
msgb_bvci(msg) = 0; /* Signalling */
@@ -293,32 +292,23 @@ int bssgp_tx_bvc_unblock(struct bssgp_bvc_ctx *bctx)
/*! Transmit a BVC-RESET message (Chapter 10.4.12) */
int bssgp_tx_bvc_reset2(struct bssgp_bvc_ctx *bctx, uint16_t bvci, uint8_t cause, bool add_cell_id)
{
- struct msgb *msg = bssgp_msgb_alloc();
- struct bssgp_normal_hdr *bgph =
- (struct bssgp_normal_hdr *) msgb_put(msg, sizeof(*bgph));
- uint16_t _bvci = osmo_htons(bvci);
-
- LOGP(DBSSGP, LOGL_NOTICE, "BSSGP (BVCI=%u) Tx BVC-RESET "
- "CAUSE=%s\n", bvci, bssgp_cause_str(cause));
-
- msgb_nsei(msg) = bctx->nsei;
- msgb_bvci(msg) = 0; /* Signalling */
- bgph->pdu_type = BSSGP_PDUT_BVC_RESET;
-
- msgb_tvlv_put(msg, BSSGP_IE_BVCI, 2, (uint8_t *) &_bvci);
- msgb_tvlv_put(msg, BSSGP_IE_CAUSE, 1, &cause);
- if (add_cell_id) {
- uint8_t bssgp_cid[8];
- bssgp_create_cell_id(bssgp_cid, &bctx->ra_id, bctx->cell_id);
- msgb_tvlv_put(msg, BSSGP_IE_CELL_ID, sizeof(bssgp_cid), bssgp_cid);
- }
- /* Optional: Feature Bitmap */
-
- return bssgp_ns_send(bssgp_ns_send_data, msg);
+ if (add_cell_id)
+ return bssgp_tx_bvc_reset_nsei_bvci(bctx->nsei, bvci, cause, &bctx->ra_id, bctx->cell_id);
+ else
+ return bssgp_tx_bvc_reset_nsei_bvci(bctx->nsei, bvci, cause, NULL, 0);
}
int bssgp_tx_bvc_reset(struct bssgp_bvc_ctx *bctx, uint16_t bvci, uint8_t cause)
{
- return bssgp_tx_bvc_reset2(bctx, bvci, cause, bvci != BVCI_PTM);
+ /* The Cell Identifier IE is mandatory in the BVC-RESET PDU sent from BSS to SGSN in order to reset a
+ * BVC corresponding to a PTP functional entity. The Cell Identifier IE shall not be used in any other
+ * BVC-RESET PDU. */
+ switch (bvci) {
+ case BVCI_SIGNALLING:
+ case BVCI_PTM:
+ return bssgp_tx_bvc_reset2(bctx, bvci, cause, false);
+ default:
+ return bssgp_tx_bvc_reset2(bctx, bvci, cause, true);
+ }
}
/*! Transmit a FLOW_CONTROL-BVC (Chapter 10.4.4)
@@ -476,8 +466,8 @@ int bssgp_tx_ul_ud(struct bssgp_bvc_ctx *bctx, uint32_t tlli,
msgb_nsei(msg) = bctx->nsei;
msgb_bvci(msg) = bctx->bvci;
- rate_ctr_inc(&bctx->ctrg->ctr[BSSGP_CTR_PKTS_OUT]);
- rate_ctr_add(&bctx->ctrg->ctr[BSSGP_CTR_BYTES_OUT], msg->len);
+ rate_ctr_inc(rate_ctr_group_get_ctr(bctx->ctrg, BSSGP_CTR_PKTS_OUT));
+ rate_ctr_add(rate_ctr_group_get_ctr(bctx->ctrg, BSSGP_CTR_BYTES_OUT), msg->len);
return bssgp_ns_send(bssgp_ns_send_data, msg);
}
@@ -520,24 +510,24 @@ int bssgp_rx_paging(struct bssgp_paging_info *pinfo,
TLVP_LEN(&tp, BSSGP_IE_IMSI));
/* DRX Parameters */
- if (!TLVP_PRESENT(&tp, BSSGP_IE_DRX_PARAMS))
+ if (!TLVP_PRES_LEN(&tp, BSSGP_IE_DRX_PARAMS, 2))
goto err_mand_ie;
pinfo->drx_params = tlvp_val16be(&tp, BSSGP_IE_DRX_PARAMS);
/* Scope */
- if (TLVP_PRESENT(&tp, BSSGP_IE_BSS_AREA_ID)) {
+ if (TLVP_PRES_LEN(&tp, BSSGP_IE_BSS_AREA_ID, 1)) {
pinfo->scope = BSSGP_PAGING_BSS_AREA;
- } else if (TLVP_PRESENT(&tp, BSSGP_IE_LOCATION_AREA)) {
+ } else if (TLVP_PRES_LEN(&tp, BSSGP_IE_LOCATION_AREA, 5)) {
pinfo->scope = BSSGP_PAGING_LOCATION_AREA;
memcpy(ra, TLVP_VAL(&tp, BSSGP_IE_LOCATION_AREA),
TLVP_LEN(&tp, BSSGP_IE_LOCATION_AREA));
gsm48_parse_ra(&pinfo->raid, ra);
- } else if (TLVP_PRESENT(&tp, BSSGP_IE_ROUTEING_AREA)) {
+ } else if (TLVP_PRES_LEN(&tp, BSSGP_IE_ROUTEING_AREA, 6)) {
pinfo->scope = BSSGP_PAGING_ROUTEING_AREA;
memcpy(ra, TLVP_VAL(&tp, BSSGP_IE_ROUTEING_AREA),
TLVP_LEN(&tp, BSSGP_IE_ROUTEING_AREA));
gsm48_parse_ra(&pinfo->raid, ra);
- } else if (TLVP_PRESENT(&tp, BSSGP_IE_BVCI)) {
+ } else if (TLVP_PRES_LEN(&tp, BSSGP_IE_BVCI, 2)) {
pinfo->scope = BSSGP_PAGING_BVCI;
pinfo->bvci = tlvp_val16be(&tp, BSSGP_IE_BVCI);
} else
@@ -555,8 +545,7 @@ int bssgp_rx_paging(struct bssgp_paging_info *pinfo,
}
/* Optional (P-)TMSI */
- if (TLVP_PRESENT(&tp, BSSGP_IE_TMSI) &&
- TLVP_LEN(&tp, BSSGP_IE_TMSI) >= 4) {
+ if (TLVP_PRES_LEN(&tp, BSSGP_IE_TMSI, 4)) {
if (!pinfo->ptmsi)
pinfo->ptmsi = talloc_zero_size(pinfo, sizeof(uint32_t));
*(pinfo->ptmsi) = osmo_load32be(TLVP_VAL(&tp, BSSGP_IE_TMSI));
diff --git a/src/gb/gprs_bssgp_internal.h b/src/gb/gprs_bssgp_internal.h
index 2ada027c..5022d32d 100644
--- a/src/gb/gprs_bssgp_internal.h
+++ b/src/gb/gprs_bssgp_internal.h
@@ -5,3 +5,5 @@
extern bssgp_bvc_send bssgp_ns_send;
extern void *bssgp_ns_send_data;
+
+int bssgp_rx_rim(struct msgb *msg, struct tlv_parsed *tp, uint16_t bvci);
diff --git a/src/gb/gprs_bssgp_rim.c b/src/gb/gprs_bssgp_rim.c
new file mode 100644
index 00000000..71f7ea84
--- /dev/null
+++ b/src/gb/gprs_bssgp_rim.c
@@ -0,0 +1,1221 @@
+/*! \file gprs_bssgp.c
+ * GPRS BSSGP RIM protocol implementation as per 3GPP TS 48.018. */
+/*
+ * (C) 2020-2021 by sysmocom - s.f.m.c. GmbH
+ * Author: Philipp Maier <pmaier@sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <errno.h>
+#include <osmocom/gprs/gprs_bssgp.h>
+#include <osmocom/gprs/gprs_bssgp_rim.h>
+#include <osmocom/gsm/gsm0808_utils.h>
+#include "gprs_bssgp_internal.h"
+
+/* TVLV IEs use a variable length field. To be sure we will do all buffer
+ * length checks with the maximum possible header length, which is
+ * 1 octet tag + 2 octets length = 3 */
+#define TVLV_HDR_MAXLEN 3
+
+/* Usually RIM application containers and their surrounding RIM containers
+ * are not likely to exceed 128 octets, so the usual header length will be 2 */
+#define TVLV_HDR_LEN 2
+
+/* The reporting cell identifier is encoded as a cell identifier IE
+ * (3GPP TS 48.018, sub-clause 11.3.9) but without IE and length octets. */
+#define REP_CELL_ID_LEN 8
+
+const struct value_string bssgp_rim_routing_info_discr_strs[] = {
+ { BSSGP_RIM_ROUTING_INFO_GERAN, "GERAN-cell" },
+ { BSSGP_RIM_ROUTING_INFO_UTRAN, "UTRAN-RNC" },
+ { BSSGP_RIM_ROUTING_INFO_EUTRAN, "E-UTRAN-eNodeB/HeNB" },
+ { 0, NULL }
+};
+
+/*! Parse a RIM Routing 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)
+{
+ struct gprs_ra_id raid_temp;
+
+ memset(ri, 0, sizeof(*ri));
+ if (len < 2)
+ return -EINVAL;
+
+ ri->discr = buf[0] & 0x0f;
+ buf++;
+
+ switch (ri->discr) {
+ case BSSGP_RIM_ROUTING_INFO_GERAN:
+ if (len < 9)
+ return -EINVAL;
+ ri->geran.cid = bssgp_parse_cell_id(&ri->geran.raid, buf);
+ return 9;
+ case BSSGP_RIM_ROUTING_INFO_UTRAN:
+ if (len < 9)
+ return -EINVAL;
+ gsm48_parse_ra(&ri->utran.raid, buf);
+ ri->utran.rncid = osmo_load16be(buf + 6);
+ return 9;
+ case BSSGP_RIM_ROUTING_INFO_EUTRAN:
+ if (len < 7 || len > 14)
+ return -EINVAL;
+ /* Note: 3GPP TS 24.301 Figure 9.9.3.32.1 and 3GPP TS 24.008
+ * Figure 10.5.130 specify MCC/MNC encoding in the same way,
+ * so we can re-use gsm48_parse_ra() for that. */
+ gsm48_parse_ra(&raid_temp, buf);
+ ri->eutran.tai.mcc = raid_temp.mcc;
+ ri->eutran.tai.mnc = raid_temp.mnc;
+ ri->eutran.tai.mnc_3_digits = raid_temp.mnc_3_digits;
+ ri->eutran.tai.tac = osmo_load16be(buf + 3);
+ memcpy(ri->eutran.global_enb_id, buf + 5, len - 6);
+ ri->eutran.global_enb_id_len = len - 6;
+ return len;
+ default:
+ return -EINVAL;
+ }
+}
+
+/*! Encode a RIM Routing information IE (3GPP TS 48.018, chapter 11.3.70).
+ * \param[out] buf user provided memory (at least 14 byte) for the generated value part of the IE.
+ * \param[in] ri user provided input data struct.
+ * \returns length of encoded octets, -EINVAL on error. */
+int bssgp_create_rim_ri(uint8_t *buf, const struct bssgp_rim_routing_info *ri)
+{
+ int rc;
+ struct gprs_ra_id raid_temp;
+ int len;
+
+ buf[0] = ri->discr & 0x0f;
+ buf++;
+
+ switch (ri->discr) {
+ case BSSGP_RIM_ROUTING_INFO_GERAN:
+ rc = bssgp_create_cell_id(buf, &ri->geran.raid, ri->geran.cid);
+ if (rc < 0)
+ return -EINVAL;
+ len = rc + 1;
+ break;
+ case BSSGP_RIM_ROUTING_INFO_UTRAN:
+ gsm48_encode_ra((struct gsm48_ra_id *)buf, &ri->utran.raid);
+ osmo_store16be(ri->utran.rncid, buf + 6);
+ len = 9;
+ break;
+ case BSSGP_RIM_ROUTING_INFO_EUTRAN:
+ /* Note: 3GPP TS 24.301 Figure 9.9.3.32.1 and 3GPP TS 24.008
+ * Figure 10.5.130 specify MCC/MNC encoding in the same way,
+ * so we can re-use gsm48_encode_ra() for that. */
+ raid_temp = (struct gprs_ra_id) {
+ .mcc = ri->eutran.tai.mcc,
+ .mnc = ri->eutran.tai.mnc,
+ .mnc_3_digits = ri->eutran.tai.mnc_3_digits,
+ };
+
+ gsm48_encode_ra((struct gsm48_ra_id *)buf, &raid_temp);
+ osmo_store16be(ri->eutran.tai.tac, buf + 3);
+ OSMO_ASSERT(ri->eutran.global_enb_id_len <=
+ sizeof(ri->eutran.global_enb_id));
+ memcpy(buf + 5, ri->eutran.global_enb_id,
+ ri->eutran.global_enb_id_len);
+ len = ri->eutran.global_enb_id_len + 6;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ OSMO_ASSERT(len <= BSSGP_RIM_ROUTING_INFO_MAXLEN);
+ return len;
+}
+
+/*! Encode a RIM Routing information into a human readable string.
+ * \param[buf] user provided string buffer to store the resulting string.
+ * \param[buf_len] maximum length of string buffer.
+ * \param[in] ri user provided input data struct.
+ * \returns pointer to the beginning of the resulting string stored in string buffer. */
+char *bssgp_rim_ri_name_buf(char *buf, size_t buf_len, const struct bssgp_rim_routing_info *ri)
+{
+ char plmn_str[16];
+ char enb_id_str[16];
+ char g_id_ps_str[32];
+ struct osmo_plmn_id plmn;
+ struct osmo_cell_global_id_ps g_id_ps;
+
+ if (!ri)
+ return NULL;
+
+ switch (ri->discr) {
+ case BSSGP_RIM_ROUTING_INFO_GERAN:
+ g_id_ps.rai.rac = ri->geran.raid.rac;
+ g_id_ps.rai.lac.lac = ri->geran.raid.lac;
+ g_id_ps.rai.lac.plmn.mcc = ri->geran.raid.mcc;
+ g_id_ps.rai.lac.plmn.mnc_3_digits = ri->geran.raid.mnc_3_digits;
+ g_id_ps.rai.lac.plmn.mnc = ri->geran.raid.mnc;
+ g_id_ps.cell_identity = ri->geran.cid;
+ snprintf(buf, buf_len, "%s-%s", bssgp_rim_routing_info_discr_str(ri->discr),
+ osmo_cgi_ps_name_buf(g_id_ps_str, sizeof(g_id_ps_str), &g_id_ps));
+ break;
+ case BSSGP_RIM_ROUTING_INFO_UTRAN:
+ g_id_ps.rai.rac = ri->utran.raid.rac;
+ g_id_ps.rai.lac.lac = ri->utran.raid.lac;
+ g_id_ps.rai.lac.plmn.mcc = ri->utran.raid.mcc;
+ g_id_ps.rai.lac.plmn.mnc_3_digits = ri->utran.raid.mnc_3_digits;
+ g_id_ps.rai.lac.plmn.mnc = ri->utran.raid.mnc;
+ g_id_ps.cell_identity = ri->utran.rncid;
+ snprintf(buf, buf_len, "%s-%s", bssgp_rim_routing_info_discr_str(ri->discr),
+ osmo_cgi_ps_name_buf(g_id_ps_str, sizeof(g_id_ps_str), &g_id_ps));
+ break;
+ case BSSGP_RIM_ROUTING_INFO_EUTRAN:
+ plmn.mcc = ri->eutran.tai.mcc;
+ plmn.mnc = ri->eutran.tai.mnc;
+ plmn.mnc_3_digits = ri->eutran.tai.mnc_3_digits;
+ snprintf(buf, buf_len, "%s-%s-%u-%s", bssgp_rim_routing_info_discr_str(ri->discr),
+ osmo_plmn_name_buf(plmn_str, sizeof(plmn_str), &plmn), ri->eutran.tai.tac,
+ osmo_hexdump_buf(enb_id_str, sizeof(enb_id_str), ri->eutran.global_enb_id,
+ ri->eutran.global_enb_id_len, "", false));
+ break;
+ default:
+ snprintf(buf, buf_len, "invalid");
+ }
+
+ return buf;
+}
+
+/*! Encode a RIM Routing information into a human readable string.
+ * \param[in] ri user provided input data struct.
+ * \returns pointer to the resulting string. */
+const char *bssgp_rim_ri_name(const struct bssgp_rim_routing_info *ri)
+{
+ static __thread char rim_ri_buf[64];
+ return bssgp_rim_ri_name_buf(rim_ri_buf, sizeof(rim_ri_buf), ri);
+}
+
+/*! Decode a RAN Information Request Application Container for NACC (3GPP TS 48.018, section 11.3.63.1.1).
+ * \param[out] user provided memory for decoded data struct.
+ * \param[in] buf user provided memory with the encoded value data of the IE.
+ * \returns 0 on success, -EINVAL on error. */
+int bssgp_dec_ran_inf_req_app_cont_nacc(struct bssgp_ran_inf_req_app_cont_nacc *cont, const uint8_t *buf, size_t len)
+{
+ int rc;
+
+ if (len < REP_CELL_ID_LEN)
+ return -EINVAL;
+
+ rc = gsm0808_decode_cell_id_u((union gsm0808_cell_id_u*)&cont->reprt_cell,
+ CELL_IDENT_WHOLE_GLOBAL_PS, buf, len);
+ if (rc < 0)
+ return -EINVAL;
+
+ return 0;
+}
+
+/*! Encode a RAN Information Request Application Container for NACC (3GPP TS 48.018, section 11.3.63.1.1).
+ * \param[out] buf user provided memory for the generated value part of the IE.
+ * \param[in] cont user provided input data struct.
+ * \returns length of encoded octets, -EINVAL on error. */
+int bssgp_enc_ran_inf_req_app_cont_nacc(uint8_t *buf, size_t len, const struct bssgp_ran_inf_req_app_cont_nacc *cont)
+{
+ int rc;
+ struct gprs_ra_id *raid;
+
+ if (len < REP_CELL_ID_LEN)
+ return -EINVAL;
+
+ raid = (struct gprs_ra_id *)&cont->reprt_cell.rai;
+ rc = bssgp_create_cell_id(buf, raid, cont->reprt_cell.cell_identity);
+ if (rc < 0)
+ return -EINVAL;
+ return rc;
+}
+
+/*! Decode a RAN Information Application Container (3GPP TS 48.018, section 11.3.63.2.1).
+ * \param[out] user provided memory for decoded data struct.
+ * \param[in] buf user provided memory with the encoded value data of the IE.
+ * \returns 0 on success, -EINVAL on error. */
+int bssgp_dec_ran_inf_app_cont_nacc(struct bssgp_ran_inf_app_cont_nacc *cont, const uint8_t *buf, size_t len)
+{
+ unsigned int i;
+ int remaining_buf_len;
+ int rc;
+
+ /* The given buffer must at least contain a reporting cell identifer
+ * plus one octet that defines number/type of attached sysinfo messages. */
+ if (len < REP_CELL_ID_LEN + 1)
+ return -EINVAL;
+
+ rc = gsm0808_decode_cell_id_u((union gsm0808_cell_id_u*)&cont->reprt_cell,
+ CELL_IDENT_WHOLE_GLOBAL_PS, buf, len);
+ if (rc < 0)
+ return -EINVAL;
+
+ buf += REP_CELL_ID_LEN;
+
+ cont->type_psi = buf[0] & 1;
+ cont->num_si = buf[0] >> 1;
+ buf++;
+
+ /* The number of sysinfo messages may be zero */
+ if (cont->num_si == 0)
+ return 0;
+
+ /* Check if the prospected system information messages fit in the
+ * remaining buffer space */
+ remaining_buf_len = len - REP_CELL_ID_LEN - 1;
+ if (remaining_buf_len <= 0)
+ return -EINVAL;
+ if (cont->type_psi && remaining_buf_len / BSSGP_RIM_PSI_LEN < cont->num_si)
+ return -EINVAL;
+ else if (remaining_buf_len / BSSGP_RIM_SI_LEN < cont->num_si)
+ return -EINVAL;
+
+ for (i = 0; i < cont->num_si; i++) {
+ cont->si[i] = buf;
+ if (cont->type_psi)
+ buf += BSSGP_RIM_PSI_LEN;
+ else
+ buf += BSSGP_RIM_SI_LEN;
+ }
+
+ return 0;
+}
+
+/*! Encode a RAN Information Application Container (3GPP TS 48.018, section 11.3.63.2.1).
+ * \param[out] buf user provided memory for the generated value part of the IE.
+ * \param[in] cont user provided input data struct.
+ * \returns length of encoded octets, -EINVAL on error. */
+int bssgp_enc_ran_inf_app_cont_nacc(uint8_t *buf, size_t len, const struct bssgp_ran_inf_app_cont_nacc *cont)
+{
+ uint8_t *buf_ptr = buf;
+ int rc;
+ unsigned int silen;
+ unsigned int i;
+ struct gprs_ra_id *raid;
+
+ if (cont->type_psi)
+ silen = BSSGP_RIM_PSI_LEN;
+ else
+ silen = BSSGP_RIM_SI_LEN;
+
+ /* The buffer must accept the reporting cell id, plus 1 byte to define
+ * the type and number of sysinfo messages. */
+ if (len < REP_CELL_ID_LEN + 1 + silen * cont->num_si)
+ return -EINVAL;
+
+ raid = (struct gprs_ra_id *)&cont->reprt_cell.rai;
+ rc = bssgp_create_cell_id(buf_ptr, raid, cont->reprt_cell.cell_identity);
+ if (rc < 0)
+ return -EINVAL;
+ buf_ptr += rc;
+
+ buf_ptr[0] = 0x00;
+ if (cont->type_psi)
+ buf_ptr[0] |= 0x01;
+ buf_ptr[0] |= (cont->num_si << 1);
+ buf_ptr++;
+
+ for (i = 0; i < cont->num_si; i++) {
+ memcpy(buf_ptr, cont->si[i], silen);
+ buf_ptr += silen;
+ }
+
+ return (int)(buf_ptr - buf);
+}
+
+/* 3GPP TS 48.018, table 11.3.64.1.b, NACC Cause coding */
+const struct value_string bssgp_nacc_cause_strs[] = {
+ { BSSGP_NACC_CAUSE_UNSPEC, "unspecified error" },
+ { BSSGP_NACC_CAUSE_SYNTAX_ERR, "syntax error in app container" },
+ { BSSGP_NACC_CAUSE_RPRT_CELL_MISSMTCH, "reporting cell id mismatch" },
+ { BSSGP_NACC_CAUSE_SIPSI_TYPE_ERR, "SI/PSI type error" },
+ { BSSGP_NACC_CAUSE_SIPSI_LEN_ERR, "SI/PSI inconsistent length" },
+ { BSSGP_NACC_CAUSE_SIPSI_SET_ERR, "inconsistent set of msg" },
+ { 0, NULL }
+};
+
+/*! Decode a Application Error Container for NACC (3GPP TS 48.018, section 11.3.64.1).
+ * \param[out] user provided memory for decoded data struct.
+ * \param[in] buf user provided memory with the encoded value data of the IE.
+ * \returns 0 on success, -EINVAL on error. */
+int bssgp_dec_app_err_cont_nacc(struct bssgp_app_err_cont_nacc *cont, const uint8_t *buf, size_t len)
+{
+ /* The buffer must at least contain the NACC cause code, it should also
+ * contain the application container, but we won't error if it is missing. */
+ if (len < 1)
+ return -EINVAL;
+
+ cont->nacc_cause = buf[0];
+
+ if (len > 1) {
+ cont->err_app_cont = buf + 1;
+ cont->err_app_cont_len = len - 1;
+ } else {
+ cont->err_app_cont = NULL;
+ cont->err_app_cont_len = 0;
+ }
+
+ return 0;
+}
+
+/*! Encode Application Error Container for NACC (3GPP TS 48.018, section 11.3.64.1).
+ * \param[out] buf user provided memory for the generated value part of the IE.
+ * \param[in] cont user provided input data struct.
+ * \returns length of encoded octets, -EINVAL on error. */
+int bssgp_enc_app_err_cont_nacc(uint8_t *buf, size_t len, const struct bssgp_app_err_cont_nacc *cont)
+{
+ uint8_t *buf_ptr = buf;
+
+ /* The buffer must accept the length of the application container and the NACC
+ * cause code, which is one octet in length. */
+ if (len < cont->err_app_cont_len + 1)
+ return -EINVAL;
+
+ buf_ptr[0] = cont->nacc_cause;
+ buf_ptr++;
+
+ memcpy(buf_ptr, cont->err_app_cont, cont->err_app_cont_len);
+ buf_ptr += cont->err_app_cont_len;
+
+ return (int)(buf_ptr - buf);
+}
+
+/* The structs bssgp_ran_inf_req_rim_cont, bssgp_ran_inf_rim_cont and bssgp_ran_inf_app_err_rim_cont *cont
+ * share four common fields at the beginning, we use the following struct as parameter type for the common
+ * encoder/decoder functions. (See also 3GPP TS 48.018 table 11.3.62a.1.b, table 11.3.62a.2.b, and
+ * table 11.3.62a.5.b) */
+struct bssgp_ran_inf_x_cont {
+ enum bssgp_ran_inf_app_id app_id;
+ uint32_t seq_num;
+ struct bssgp_rim_pdu_ind pdu_ind;
+ uint8_t prot_ver;
+};
+
+static int dec_rim_cont_common(struct bssgp_ran_inf_x_cont *cont, struct tlv_parsed *tp)
+{
+ if (TLVP_PRES_LEN(tp, BSSGP_IE_RIM_APP_IDENTITY, sizeof(uint8_t)))
+ cont->app_id = TLVP_VAL(tp, BSSGP_IE_RIM_APP_IDENTITY)[0];
+ else
+ return -EINVAL;
+
+ if (TLVP_PRES_LEN(tp, BSSGP_IE_RIM_SEQ_NR, sizeof(cont->seq_num)))
+ cont->seq_num = tlvp_val32be(tp, BSSGP_IE_RIM_SEQ_NR);
+ else
+ return -EINVAL;
+
+ if (TLVP_PRES_LEN(tp, BSSGP_IE_RIM_PDU_INDICATIONS, sizeof(cont->pdu_ind)))
+ memcpy(&cont->pdu_ind, TLVP_VAL(tp, BSSGP_IE_RIM_PDU_INDICATIONS), sizeof(cont->pdu_ind));
+ else
+ return -EINVAL;
+
+ if (TLVP_PRES_LEN(tp, BSSGP_IE_RIM_PROTOCOL_VERSION, sizeof(cont->prot_ver)))
+ cont->prot_ver = TLVP_VAL(tp, BSSGP_IE_RIM_PROTOCOL_VERSION)[0];
+ else
+ cont->prot_ver = 1;
+
+ return 0;
+}
+
+static uint8_t *enc_rim_cont_common(uint8_t *buf, size_t len, const struct bssgp_ran_inf_x_cont *cont)
+{
+
+ uint32_t seq_num = osmo_htonl(cont->seq_num);
+ uint8_t app_id_temp;
+ uint8_t *buf_ptr = buf;
+
+ if (len <
+ TVLV_HDR_MAXLEN * 4 + sizeof(app_id_temp) + sizeof(seq_num) + sizeof(cont->pdu_ind) +
+ sizeof(cont->prot_ver))
+ return NULL;
+
+ app_id_temp = cont->app_id;
+ buf_ptr = tvlv_put(buf_ptr, BSSGP_IE_RIM_APP_IDENTITY, sizeof(app_id_temp), &app_id_temp);
+ buf_ptr = tvlv_put(buf_ptr, BSSGP_IE_RIM_SEQ_NR, sizeof(seq_num), (uint8_t *) & seq_num);
+ buf_ptr = tvlv_put(buf_ptr, BSSGP_IE_RIM_PDU_INDICATIONS, sizeof(cont->pdu_ind), (uint8_t *) & cont->pdu_ind);
+ if (cont->prot_ver > 0)
+ buf_ptr = tvlv_put(buf_ptr, BSSGP_IE_RIM_PROTOCOL_VERSION, sizeof(cont->prot_ver), &cont->prot_ver);
+
+ return buf_ptr;
+}
+
+/* 3GPP TS 48.018, table 11.3.61.b: RIM Application Identity coding */
+const struct value_string bssgp_ran_inf_app_id_strs[] = {
+ { BSSGP_RAN_INF_APP_ID_NACC, "Network Assisted Cell Change (NACC)" },
+ { BSSGP_RAN_INF_APP_ID_SI3, "System Information 3 (SI3)" },
+ { BSSGP_RAN_INF_APP_ID_MBMS, "MBMS data channel" },
+ { BSSGP_RAN_INF_APP_ID_SON, "SON Transfer" },
+ { BSSGP_RAN_INF_APP_ID_UTRA_SI, "UTRA System Information (UTRA SI)" },
+ { 0, NULL }
+};
+
+/*! Decode a RAN Information Request RIM Container (3GPP TS 48.018, table 11.3.62a.1.b).
+ * \param[out] user provided memory for decoded data struct.
+ * \param[in] buf user provided memory with the encoded value data of the IE.
+ * \returns 0 on success, -EINVAL on error. */
+int bssgp_dec_ran_inf_req_rim_cont(struct bssgp_ran_inf_req_rim_cont *cont, const uint8_t *buf, size_t len)
+{
+ int rc;
+ struct tlv_parsed tp;
+
+ memset(cont, 0, sizeof(*cont));
+
+ rc = tlv_parse(&tp, &tvlv_att_def, buf, len, 0, 0);
+ if (rc < 0)
+ return -EINVAL;
+
+ rc = dec_rim_cont_common((struct bssgp_ran_inf_x_cont *)cont, &tp);
+ if (rc < 0)
+ return -EINVAL;
+
+ if (TLVP_PRESENT(&tp, BSSGP_IE_RIM_REQ_APP_CONTAINER)) {
+ switch (cont->app_id) {
+ case BSSGP_RAN_INF_APP_ID_NACC:
+ rc = bssgp_dec_ran_inf_req_app_cont_nacc(&cont->u.app_cont_nacc,
+ TLVP_VAL(&tp, BSSGP_IE_RIM_REQ_APP_CONTAINER),
+ TLVP_LEN(&tp, BSSGP_IE_RIM_REQ_APP_CONTAINER));
+ break;
+ case BSSGP_RAN_INF_APP_ID_SI3:
+ case BSSGP_RAN_INF_APP_ID_MBMS:
+ case BSSGP_RAN_INF_APP_ID_SON:
+ case BSSGP_RAN_INF_APP_ID_UTRA_SI:
+ /* TODO: add parsers for Si3, MBMS, SON, UTRA-SI app containers */
+ return -EOPNOTSUPP;
+ default:
+ return -EINVAL;
+ }
+
+ if (rc < 0)
+ return rc;
+ }
+
+ if (TLVP_PRES_LEN(&tp, BSSGP_IE_SON_TRANSFER_APP_ID, 1)) {
+ cont->son_trans_app_id = TLVP_VAL(&tp, BSSGP_IE_SON_TRANSFER_APP_ID);
+ cont->son_trans_app_id_len = TLVP_LEN(&tp, BSSGP_IE_SON_TRANSFER_APP_ID);
+ }
+
+ return 0;
+}
+
+/* Dub a TLVP header into a given buffer. The value part of the IE must start
+ * at the 2nd octet. Should the length field make a 3 octet TLVP header
+ * necessary (unlikely, but possible) the value part is moved ahead by one
+ * octet. The function returns a pointer to the end of value part. */
+static uint8_t *dub_tlvp_header(uint8_t *buf, uint8_t iei, uint16_t len)
+{
+ uint8_t *buf_ptr = buf;
+
+ buf_ptr[0] = iei;
+ if (len <= TVLV_MAX_ONEBYTE) {
+ buf_ptr[1] = (uint8_t) len;
+ buf_ptr[1] |= 0x80;
+ buf_ptr += TVLV_HDR_LEN;
+ } else {
+ memmove(buf_ptr + 1, buf_ptr, len);
+ buf_ptr[1] = len >> 8;
+ buf_ptr[2] = len & 0xff;
+ buf_ptr += TVLV_HDR_MAXLEN;
+ }
+ buf_ptr += len;
+
+ return buf_ptr;
+}
+
+/*! Encode a RAN Information Request RIM Container (3GPP TS 48.018, table 11.3.62a.1.b).
+ * \param[out] buf user provided memory for the generated value part of the IE.
+ * \param[in] cont user provided input data struct.
+ * \returns length of encoded octets, -EINVAL on error. */
+int bssgp_enc_ran_inf_req_rim_cont(uint8_t *buf, size_t len, const struct bssgp_ran_inf_req_rim_cont *cont)
+{
+ uint8_t *buf_ptr = buf;
+ int app_cont_len = 0;
+ int remaining_buf_len;
+
+ buf_ptr = enc_rim_cont_common(buf_ptr, len, (struct bssgp_ran_inf_x_cont *)cont);
+ if (!buf_ptr)
+ return -EINVAL;
+
+ remaining_buf_len = len - (int)(buf_ptr - buf);
+ if (remaining_buf_len <= 0)
+ return -EINVAL;
+
+ switch (cont->app_id) {
+ case BSSGP_RAN_INF_APP_ID_NACC:
+ app_cont_len =
+ bssgp_enc_ran_inf_req_app_cont_nacc(buf_ptr + TVLV_HDR_LEN, remaining_buf_len - TVLV_HDR_MAXLEN,
+ &cont->u.app_cont_nacc);
+ break;
+ case BSSGP_RAN_INF_APP_ID_SI3:
+ case BSSGP_RAN_INF_APP_ID_MBMS:
+ case BSSGP_RAN_INF_APP_ID_SON:
+ case BSSGP_RAN_INF_APP_ID_UTRA_SI:
+ /* TODO: add encoders for Si3, MBMS, SON, UTRA-SI app containers */
+ return -EOPNOTSUPP;
+ default:
+ return -EINVAL;
+ }
+
+ if (app_cont_len < 0)
+ return -EINVAL;
+ buf_ptr = dub_tlvp_header(buf_ptr, BSSGP_IE_RIM_REQ_APP_CONTAINER, app_cont_len);
+
+ remaining_buf_len = len - (int)(buf_ptr - buf);
+ if (remaining_buf_len < 0)
+ return -EINVAL;
+
+ if (cont->son_trans_app_id && cont->son_trans_app_id_len > 0) {
+ if (remaining_buf_len < cont->son_trans_app_id_len + TVLV_HDR_MAXLEN)
+ return -EINVAL;
+ buf_ptr =
+ tvlv_put(buf_ptr, BSSGP_IE_SON_TRANSFER_APP_ID, cont->son_trans_app_id_len, cont->son_trans_app_id);
+ }
+ return (int)(buf_ptr - buf);
+}
+
+/*! Decode a RAN Information RIM Container (3GPP TS 48.018, table 11.3.62a.2.b).
+ * \param[out] user provided memory for decoded data struct.
+ * \param[in] buf user provided memory with the encoded value data of the IE.
+ * \returns 0 on success, -EINVAL on error. */
+int bssgp_dec_ran_inf_rim_cont(struct bssgp_ran_inf_rim_cont *cont, const uint8_t *buf, size_t len)
+{
+ int rc;
+ struct tlv_parsed tp;
+
+ memset(cont, 0, sizeof(*cont));
+
+ rc = tlv_parse(&tp, &tvlv_att_def, buf, len, 0, 0);
+ if (rc < 0)
+ return -EINVAL;
+
+ rc = dec_rim_cont_common((struct bssgp_ran_inf_x_cont *)cont, &tp);
+ if (rc < 0)
+ return -EINVAL;
+
+ if (TLVP_PRESENT(&tp, BSSGP_IE_RAN_INFO_APP_CONTAINER)) {
+ switch (cont->app_id) {
+ case BSSGP_RAN_INF_APP_ID_NACC:
+ rc = bssgp_dec_ran_inf_app_cont_nacc(&cont->u.app_cont_nacc,
+ TLVP_VAL(&tp, BSSGP_IE_RAN_INFO_APP_CONTAINER),
+ TLVP_LEN(&tp, BSSGP_IE_RAN_INFO_APP_CONTAINER));
+ break;
+ case BSSGP_RAN_INF_APP_ID_SI3:
+ case BSSGP_RAN_INF_APP_ID_MBMS:
+ case BSSGP_RAN_INF_APP_ID_SON:
+ case BSSGP_RAN_INF_APP_ID_UTRA_SI:
+ /* TODO: add parsers for Si3, MBMS, SON, UTRA-SI app containers */
+ return -EOPNOTSUPP;
+ default:
+ return -EINVAL;
+ }
+
+ if (rc < 0)
+ return rc;
+ } else if (TLVP_PRESENT(&tp, BSSGP_IE_APP_ERROR_CONTAINER)) {
+ switch (cont->app_id) {
+ case BSSGP_RAN_INF_APP_ID_NACC:
+ rc = bssgp_dec_app_err_cont_nacc(&cont->u.app_err_cont_nacc,
+ TLVP_VAL(&tp, BSSGP_IE_APP_ERROR_CONTAINER), TLVP_LEN(&tp,
+ BSSGP_IE_APP_ERROR_CONTAINER));
+ break;
+ case BSSGP_RAN_INF_APP_ID_SI3:
+ case BSSGP_RAN_INF_APP_ID_MBMS:
+ case BSSGP_RAN_INF_APP_ID_SON:
+ case BSSGP_RAN_INF_APP_ID_UTRA_SI:
+ /* TODO: add parsers for Si3, MBMS, SON, UTRA-SI app containers */
+ return -EOPNOTSUPP;
+ default:
+ return -EINVAL;
+ }
+ if (rc < 0)
+ return rc;
+ cont->app_err = true;
+ }
+
+ if (TLVP_PRES_LEN(&tp, BSSGP_IE_SON_TRANSFER_APP_ID, 1)) {
+ cont->son_trans_app_id = TLVP_VAL(&tp, BSSGP_IE_SON_TRANSFER_APP_ID);
+ cont->son_trans_app_id_len = TLVP_LEN(&tp, BSSGP_IE_SON_TRANSFER_APP_ID);
+ }
+
+ return 0;
+}
+
+/*! Encode a RAN Information RIM Container (3GPP TS 48.018, table 11.3.62a.2.b).
+ * \param[out] buf user provided memory for the generated value part of the IE.
+ * \param[in] cont user provided input data struct.
+ * \returns length of encoded octets, -EINVAL on error. */
+int bssgp_enc_ran_inf_rim_cont(uint8_t *buf, size_t len, const struct bssgp_ran_inf_rim_cont *cont)
+{
+ uint8_t *buf_ptr = buf;
+ int app_cont_len = 0;
+ int remaining_buf_len;
+
+ buf_ptr = enc_rim_cont_common(buf_ptr, len, (struct bssgp_ran_inf_x_cont *)cont);
+ if (!buf_ptr)
+ return -EINVAL;
+
+ remaining_buf_len = len - (int)(buf_ptr - buf);
+ if (remaining_buf_len <= 0)
+ return -EINVAL;
+
+ if (cont->app_err) {
+ switch (cont->app_id) {
+ case BSSGP_RAN_INF_APP_ID_NACC:
+ app_cont_len =
+ bssgp_enc_app_err_cont_nacc(buf_ptr + TVLV_HDR_LEN, remaining_buf_len - TVLV_HDR_MAXLEN,
+ &cont->u.app_err_cont_nacc);
+ break;
+ case BSSGP_RAN_INF_APP_ID_SI3:
+ case BSSGP_RAN_INF_APP_ID_MBMS:
+ case BSSGP_RAN_INF_APP_ID_SON:
+ case BSSGP_RAN_INF_APP_ID_UTRA_SI:
+ /* TODO: add encoders for Si3, MBMS, SON, UTRA-SI app containers */
+ return -EOPNOTSUPP;
+ default:
+ return -EINVAL;
+ }
+ if (app_cont_len < 0)
+ return -EINVAL;
+ buf_ptr = dub_tlvp_header(buf_ptr, BSSGP_IE_APP_ERROR_CONTAINER, app_cont_len);
+ } else {
+ switch (cont->app_id) {
+ case BSSGP_RAN_INF_APP_ID_NACC:
+ app_cont_len =
+ bssgp_enc_ran_inf_app_cont_nacc(buf_ptr + TVLV_HDR_LEN, remaining_buf_len - TVLV_HDR_MAXLEN,
+ &cont->u.app_cont_nacc);
+ break;
+ case BSSGP_RAN_INF_APP_ID_SI3:
+ case BSSGP_RAN_INF_APP_ID_MBMS:
+ case BSSGP_RAN_INF_APP_ID_SON:
+ case BSSGP_RAN_INF_APP_ID_UTRA_SI:
+ /* TODO: add encoders for Si3, MBMS, SON, UTRA-SI app containers */
+ return -EOPNOTSUPP;
+ default:
+ return -EINVAL;
+ }
+ if (app_cont_len < 0)
+ return -EINVAL;
+ buf_ptr = dub_tlvp_header(buf_ptr, BSSGP_IE_RAN_INFO_APP_CONTAINER, app_cont_len);
+ }
+
+ remaining_buf_len = len - (int)(buf_ptr - buf);
+ if (remaining_buf_len < 0)
+ return -EINVAL;
+
+ if (cont->son_trans_app_id && cont->son_trans_app_id_len > 0) {
+ if (remaining_buf_len < cont->son_trans_app_id_len + TVLV_HDR_MAXLEN)
+ return -EINVAL;
+ buf_ptr =
+ tvlv_put(buf_ptr, BSSGP_IE_SON_TRANSFER_APP_ID, cont->son_trans_app_id_len, cont->son_trans_app_id);
+ }
+ return (int)(buf_ptr - buf);
+}
+
+/*! Decode a RAN Information ACK RIM Container (3GPP TS 48.018, table 11.3.62a.3.b).
+ * \param[out] user provided memory for decoded data struct.
+ * \param[in] buf user provided memory with the encoded value data of the IE.
+ * \returns 0 on success, -EINVAL on error. */
+int bssgp_dec_ran_inf_ack_rim_cont(struct bssgp_ran_inf_ack_rim_cont *cont, const uint8_t *buf, size_t len)
+{
+ int rc;
+ struct tlv_parsed tp;
+
+ memset(cont, 0, sizeof(*cont));
+
+ rc = tlv_parse(&tp, &tvlv_att_def, buf, len, 0, 0);
+ if (rc < 0)
+ return -EINVAL;
+
+ if (TLVP_PRES_LEN(&tp, BSSGP_IE_RIM_APP_IDENTITY, sizeof(uint8_t)))
+ cont->app_id = TLVP_VAL(&tp, BSSGP_IE_RIM_APP_IDENTITY)[0];
+ else
+ return -EINVAL;
+
+ if (TLVP_PRES_LEN(&tp, BSSGP_IE_RIM_SEQ_NR, sizeof(cont->seq_num)))
+ cont->seq_num = tlvp_val32be(&tp, BSSGP_IE_RIM_SEQ_NR);
+ else
+ return -EINVAL;
+
+ if (TLVP_PRES_LEN(&tp, BSSGP_IE_RIM_PROTOCOL_VERSION, sizeof(cont->prot_ver)))
+ cont->prot_ver = TLVP_VAL(&tp, BSSGP_IE_RIM_PROTOCOL_VERSION)[0];
+ else
+ cont->prot_ver = 1;
+
+ if (TLVP_PRES_LEN(&tp, BSSGP_IE_SON_TRANSFER_APP_ID, 1)) {
+ cont->son_trans_app_id = TLVP_VAL(&tp, BSSGP_IE_SON_TRANSFER_APP_ID);
+ cont->son_trans_app_id_len = TLVP_LEN(&tp, BSSGP_IE_SON_TRANSFER_APP_ID);
+ }
+
+ return 0;
+}
+
+/*! Encode a RAN Information ACK RIM Container (3GPP TS 48.018, table 11.3.62a.3.b).
+ * \param[out] buf user provided memory for the generated value part of the IE.
+ * \param[in] cont user provided input data struct.
+ * \returns length of encoded octets, -EINVAL on error. */
+int bssgp_enc_ran_inf_ack_rim_cont(uint8_t *buf, size_t len, const struct bssgp_ran_inf_ack_rim_cont *cont)
+{
+ uint8_t *buf_ptr = buf;
+ uint32_t seq_num = osmo_htonl(cont->seq_num);
+ uint8_t app_id_temp;
+
+ if (len <
+ 4 * TVLV_HDR_MAXLEN + sizeof(app_id_temp) + sizeof(seq_num) + sizeof(cont->prot_ver) +
+ cont->son_trans_app_id_len)
+ return -EINVAL;
+
+ app_id_temp = cont->app_id;
+ buf_ptr = tvlv_put(buf_ptr, BSSGP_IE_RIM_APP_IDENTITY, sizeof(app_id_temp), &app_id_temp);
+ buf_ptr = tvlv_put(buf_ptr, BSSGP_IE_RIM_SEQ_NR, sizeof(seq_num), (uint8_t *) & seq_num);
+
+ if (cont->prot_ver > 0)
+ buf_ptr = tvlv_put(buf_ptr, BSSGP_IE_RIM_PROTOCOL_VERSION, sizeof(cont->prot_ver), &cont->prot_ver);
+
+ if (cont->son_trans_app_id && cont->son_trans_app_id_len > 0)
+ buf_ptr =
+ tvlv_put(buf_ptr, BSSGP_IE_SON_TRANSFER_APP_ID, cont->son_trans_app_id_len, cont->son_trans_app_id);
+
+ return (int)(buf_ptr - buf);
+}
+
+/*! Decode a RAN Information Error RIM Container (3GPP TS 48.018, table 11.3.62a.4.b).
+ * \param[out] user provided memory for decoded data struct.
+ * \param[in] buf user provided memory with the encoded value data of the IE.
+ * \returns 0 on success, -EINVAL on error. */
+int bssgp_dec_ran_inf_err_rim_cont(struct bssgp_ran_inf_err_rim_cont *cont, const uint8_t *buf, size_t len)
+{
+ int rc;
+ struct tlv_parsed tp;
+
+ memset(cont, 0, sizeof(*cont));
+
+ rc = tlv_parse(&tp, &tvlv_att_def, buf, len, 0, 0);
+ if (rc < 0)
+ return -EINVAL;
+
+ if (TLVP_PRES_LEN(&tp, BSSGP_IE_RIM_APP_IDENTITY, sizeof(uint8_t)))
+ cont->app_id = TLVP_VAL(&tp, BSSGP_IE_RIM_APP_IDENTITY)[0];
+ else
+ return -EINVAL;
+
+ if (TLVP_PRES_LEN(&tp, BSSGP_IE_CAUSE, sizeof(cont->cause)))
+ cont->cause = TLVP_VAL(&tp, BSSGP_IE_CAUSE)[0];
+ else
+ return -EINVAL;
+
+ if (TLVP_PRES_LEN(&tp, BSSGP_IE_RIM_PROTOCOL_VERSION, sizeof(cont->prot_ver)))
+ cont->prot_ver = TLVP_VAL(&tp, BSSGP_IE_RIM_PROTOCOL_VERSION)[0];
+ else
+ cont->prot_ver = 1;
+
+ if (TLVP_PRESENT(&tp, BSSGP_IE_PDU_IN_ERROR)) {
+ cont->err_pdu = TLVP_VAL(&tp, BSSGP_IE_PDU_IN_ERROR);
+ cont->err_pdu_len = TLVP_LEN(&tp, BSSGP_IE_PDU_IN_ERROR);
+ } else {
+ return -EINVAL;
+ }
+
+ if (TLVP_PRES_LEN(&tp, BSSGP_IE_SON_TRANSFER_APP_ID, 1)) {
+ cont->son_trans_app_id = TLVP_VAL(&tp, BSSGP_IE_SON_TRANSFER_APP_ID);
+ cont->son_trans_app_id_len = TLVP_LEN(&tp, BSSGP_IE_SON_TRANSFER_APP_ID);
+ }
+
+ return 0;
+}
+
+/*! Encode a RAN Information Error RIM Container (3GPP TS 48.018, table 11.3.62a.4.b).
+ * \param[out] buf user provided memory for the generated value part of the IE.
+ * \param[in] cont user provided input data struct.
+ * \returns length of encoded octets, -EINVAL on error. */
+int bssgp_enc_ran_inf_err_rim_cont(uint8_t *buf, size_t len, const struct bssgp_ran_inf_err_rim_cont *cont)
+{
+ uint8_t *buf_ptr = buf;
+ uint8_t app_id_temp;
+
+ if (len <
+ TVLV_HDR_MAXLEN * 5 + sizeof(app_id_temp) + sizeof(cont->cause) + sizeof(cont->prot_ver) +
+ cont->err_pdu_len + cont->son_trans_app_id_len)
+ return -EINVAL;
+
+ app_id_temp = cont->app_id;
+ buf_ptr = tvlv_put(buf_ptr, BSSGP_IE_RIM_APP_IDENTITY, sizeof(app_id_temp), &app_id_temp);
+ buf_ptr = tvlv_put(buf_ptr, BSSGP_IE_CAUSE, sizeof(cont->cause), &cont->cause);
+
+ if (cont->prot_ver > 0)
+ buf_ptr = tvlv_put(buf_ptr, BSSGP_IE_RIM_PROTOCOL_VERSION, sizeof(cont->prot_ver), &cont->prot_ver);
+
+ if (cont->err_pdu && cont->err_pdu_len > 0)
+ buf_ptr = tvlv_put(buf_ptr, BSSGP_IE_PDU_IN_ERROR, cont->err_pdu_len, cont->err_pdu);
+ else
+ return -EINVAL;
+
+ if (cont->son_trans_app_id && cont->son_trans_app_id_len > 0)
+ buf_ptr =
+ tvlv_put(buf_ptr, BSSGP_IE_SON_TRANSFER_APP_ID, cont->son_trans_app_id_len, cont->son_trans_app_id);
+
+ return (int)(buf_ptr - buf);
+}
+
+/*! Decode a RAN Information Application Error RIM Container (3GPP TS 48.018, table 11.3.62a.5.b).
+ * \param[out] user provided memory for decoded data struct.
+ * \param[in] buf user provided memory with the encoded value data of the IE.
+ * \returns 0 on success, -EINVAL on error. */
+int bssgp_dec_ran_inf_app_err_rim_cont(struct bssgp_ran_inf_app_err_rim_cont *cont, const uint8_t *buf, size_t len)
+{
+ int rc;
+ struct tlv_parsed tp;
+
+ memset(cont, 0, sizeof(*cont));
+
+ rc = tlv_parse(&tp, &tvlv_att_def, buf, len, 0, 0);
+ if (rc < 0)
+ return -EINVAL;
+
+ rc = dec_rim_cont_common((struct bssgp_ran_inf_x_cont *)cont, &tp);
+ if (rc < 0)
+ return -EINVAL;
+
+ switch (cont->app_id) {
+ case BSSGP_RAN_INF_APP_ID_NACC:
+ rc = bssgp_dec_app_err_cont_nacc(&cont->u.app_err_cont_nacc,
+ TLVP_VAL(&tp, BSSGP_IE_APP_ERROR_CONTAINER), TLVP_LEN(&tp,
+ BSSGP_IE_APP_ERROR_CONTAINER));
+ break;
+ case BSSGP_RAN_INF_APP_ID_SI3:
+ case BSSGP_RAN_INF_APP_ID_MBMS:
+ case BSSGP_RAN_INF_APP_ID_SON:
+ case BSSGP_RAN_INF_APP_ID_UTRA_SI:
+ /* TODO: add parsers for Si3, MBMS, SON, UTRA-SI app containers */
+ return -EOPNOTSUPP;
+ default:
+ return -EINVAL;
+ }
+ if (rc < 0)
+ return rc;
+
+ return 0;
+}
+
+/*! Encode a RAN Information Application Error RIM Container (3GPP TS 48.018, table 11.3.62a.5.b).
+ * \param[out] buf user provided memory for the generated value part of the IE.
+ * \param[in] cont user provided input data struct.
+ * \returns length of encoded octets, -EINVAL on error. */
+int bssgp_enc_ran_inf_app_err_rim_cont(uint8_t *buf, size_t len, const struct bssgp_ran_inf_app_err_rim_cont *cont)
+{
+ uint8_t *buf_ptr = buf;
+ int app_cont_len = 0;
+ int remaining_buf_len;
+
+ buf_ptr = enc_rim_cont_common(buf_ptr, len, (struct bssgp_ran_inf_x_cont *)cont);
+ if (!buf_ptr)
+ return -EINVAL;
+
+ remaining_buf_len = len - (int)(buf_ptr - buf);
+ if (remaining_buf_len <= 0)
+ return -EINVAL;
+
+ switch (cont->app_id) {
+ case BSSGP_RAN_INF_APP_ID_NACC:
+ app_cont_len =
+ bssgp_enc_app_err_cont_nacc(buf_ptr + TVLV_HDR_LEN, remaining_buf_len - TVLV_HDR_MAXLEN,
+ &cont->u.app_err_cont_nacc);
+ break;
+ case BSSGP_RAN_INF_APP_ID_SI3:
+ case BSSGP_RAN_INF_APP_ID_MBMS:
+ case BSSGP_RAN_INF_APP_ID_SON:
+ case BSSGP_RAN_INF_APP_ID_UTRA_SI:
+ /* TODO: add encoders for Si3, MBMS, SON, UTRA-SI app containers */
+ return -EOPNOTSUPP;
+ default:
+ return -EINVAL;
+ }
+ if (app_cont_len < 0)
+ return -EINVAL;
+ buf_ptr = dub_tlvp_header(buf_ptr, BSSGP_IE_APP_ERROR_CONTAINER, app_cont_len);
+
+ return (int)(buf_ptr - buf);
+}
+
+/*! Parse a given message buffer into a rim-pdu struct.
+ * \param[out] pdu user provided memory for the resulting RAN INFORMATION PDU.
+ * \param[in] msg BSSGP message buffer that contains the encoded RAN INFORMATION PDU.
+ * \returns 0 on sccess, -EINVAL on error. */
+int bssgp_parse_rim_pdu(struct bssgp_ran_information_pdu *pdu, const struct msgb *msg)
+{
+ struct tlv_parsed tp[2];
+ struct bssgp_normal_hdr *bgph = (struct bssgp_normal_hdr *)msgb_bssgph(msg);
+ int data_len;
+ int rc;
+ uint16_t nsei = msgb_nsei(msg);
+
+ memset(pdu, 0, sizeof(*pdu));
+
+ data_len = msgb_bssgp_len(msg) - sizeof(*bgph);
+ if (data_len < 0)
+ return -EINVAL;
+
+ rc = osmo_tlv_prot_parse(&osmo_pdef_bssgp, tp, ARRAY_SIZE(tp), bgph->pdu_type, bgph->data, data_len, 0, 0,
+ DLBSSGP, __func__);
+ if (rc < 0)
+ return -EINVAL;
+
+ if (TLVP_PRESENT(&tp[0], BSSGP_IE_RIM_ROUTING_INFO)) {
+ rc = bssgp_parse_rim_ri(&pdu->routing_info_dest, TLVP_VAL(&tp[0], BSSGP_IE_RIM_ROUTING_INFO),
+ TLVP_LEN(&tp[0], BSSGP_IE_RIM_ROUTING_INFO));
+ if (rc < 0) {
+ LOGP(DLBSSGP, LOGL_ERROR, "BSSGP RIM (NSEI=%u) invalid Destination Cell Identifier IE\n", nsei);
+ return -EINVAL;
+ }
+ } else {
+ LOGP(DLBSSGP, LOGL_ERROR, "BSSGP RIM (NSEI=%u) missing Destination Cell Identifier IE\n", nsei);
+ return -EINVAL;
+ }
+
+ if (TLVP_PRESENT(&tp[1], BSSGP_IE_RIM_ROUTING_INFO)) {
+ rc = bssgp_parse_rim_ri(&pdu->routing_info_src, TLVP_VAL(&tp[1], BSSGP_IE_RIM_ROUTING_INFO),
+ TLVP_LEN(&tp[1], BSSGP_IE_RIM_ROUTING_INFO));
+ if (rc < 0) {
+ LOGP(DLBSSGP, LOGL_ERROR, "BSSGP RIM (NSEI=%u) invalid Destination Cell Identifier IE\n", nsei);
+ return -EINVAL;
+ }
+ } else {
+ LOGP(DLBSSGP, LOGL_ERROR, "BSSGP RIM (NSEI=%u) missing Source Cell Identifier IE\n", nsei);
+ return -EINVAL;
+ }
+
+ if (TLVP_PRESENT(&tp[0], BSSGP_IE_RI_REQ_RIM_CONTAINER))
+ pdu->rim_cont_iei = BSSGP_IE_RI_REQ_RIM_CONTAINER;
+ else if (TLVP_PRESENT(&tp[0], BSSGP_IE_RI_RIM_CONTAINER))
+ pdu->rim_cont_iei = BSSGP_IE_RI_RIM_CONTAINER;
+ else if (TLVP_PRESENT(&tp[0], BSSGP_IE_RI_APP_ERROR_RIM_CONT))
+ pdu->rim_cont_iei = BSSGP_IE_RI_APP_ERROR_RIM_CONT;
+ else if (TLVP_PRESENT(&tp[0], BSSGP_IE_RI_ACK_RIM_CONTAINER))
+ pdu->rim_cont_iei = BSSGP_IE_RI_ACK_RIM_CONTAINER;
+ else if (TLVP_PRESENT(&tp[0], BSSGP_IE_RI_ERROR_RIM_COINTAINER))
+ pdu->rim_cont_iei = BSSGP_IE_RI_ERROR_RIM_COINTAINER;
+ else {
+ LOGP(DLBSSGP, LOGL_ERROR, "BSSGP RIM (NSEI=%u) missing or wrong RIM Container IE\n", nsei);
+ return -EINVAL;
+ }
+
+ pdu->rim_cont = TLVP_VAL(&tp[0], pdu->rim_cont_iei);
+ pdu->rim_cont_len = TLVP_LEN(&tp[0], pdu->rim_cont_iei);
+
+ /* Make sure the rim container field is not empty */
+ if (pdu->rim_cont_len < 1)
+ return -EINVAL;
+ if (!pdu->rim_cont)
+ return -EINVAL;
+
+ /* Note: It is not an error if we fail to parse the RIM container,
+ * since there are applications where parsing the RIM container
+ * is not necessary (routing). It is up to the API user to check
+ * the results. */
+ switch (pdu->rim_cont_iei) {
+ case BSSGP_IE_RI_REQ_RIM_CONTAINER:
+ rc = bssgp_dec_ran_inf_req_rim_cont(&pdu->decoded.req_rim_cont, pdu->rim_cont, pdu->rim_cont_len);
+ break;
+ case BSSGP_IE_RI_RIM_CONTAINER:
+ rc = bssgp_dec_ran_inf_rim_cont(&pdu->decoded.rim_cont, pdu->rim_cont, pdu->rim_cont_len);
+ break;
+ case BSSGP_IE_RI_APP_ERROR_RIM_CONT:
+ rc = bssgp_dec_ran_inf_app_err_rim_cont(&pdu->decoded.app_err_rim_cont, pdu->rim_cont,
+ pdu->rim_cont_len);
+ break;
+ case BSSGP_IE_RI_ACK_RIM_CONTAINER:
+ rc = bssgp_dec_ran_inf_ack_rim_cont(&pdu->decoded.ack_rim_cont, pdu->rim_cont, pdu->rim_cont_len);
+ break;
+ case BSSGP_IE_RI_ERROR_RIM_COINTAINER:
+ rc = bssgp_dec_ran_inf_err_rim_cont(&pdu->decoded.err_rim_cont, pdu->rim_cont, pdu->rim_cont_len);
+ break;
+ default:
+ LOGP(DLBSSGP, LOGL_DEBUG, "BSSGP RIM (NSEI=%u) cannot parse unknown RIM container.\n", nsei);
+ return 0;
+ }
+ if (rc < 0) {
+ LOGP(DLBSSGP, LOGL_DEBUG, "BSSGP RIM (NSEI=%u) unable to parse RIM container.\n", nsei);
+ return 0;
+ }
+ pdu->decoded_present = true;
+
+ return 0;
+}
+
+/*! Encode a given rim-pdu struct into a message buffer.
+ * \param[out] pdu user provided memory that contains the RAN INFORMATION PDU to encode.
+ * \returns BSSGP message buffer on sccess, NULL on error. */
+struct msgb *bssgp_encode_rim_pdu(const struct bssgp_ran_information_pdu *pdu)
+{
+ struct msgb *msg = bssgp_msgb_alloc();
+ struct bssgp_normal_hdr *bgph;
+ uint8_t rim_ri_buf[BSSGP_RIM_ROUTING_INFO_MAXLEN];
+ int rc;
+
+ if (!msg)
+ return NULL;
+ bgph = (struct bssgp_normal_hdr *)msgb_put(msg, sizeof(*bgph));
+
+ /* Set PDU type based on RIM container type */
+ switch (pdu->rim_cont_iei) {
+ case BSSGP_IE_RI_REQ_RIM_CONTAINER:
+ bgph->pdu_type = BSSGP_PDUT_RAN_INFO_REQ;
+ break;
+ case BSSGP_IE_RI_RIM_CONTAINER:
+ bgph->pdu_type = BSSGP_PDUT_RAN_INFO;
+ break;
+ case BSSGP_IE_RI_APP_ERROR_RIM_CONT:
+ bgph->pdu_type = BSSGP_PDUT_RAN_INFO_APP_ERROR;
+ break;
+ case BSSGP_IE_RI_ACK_RIM_CONTAINER:
+ bgph->pdu_type = BSSGP_PDUT_RAN_INFO_ACK;
+ break;
+ case BSSGP_IE_RI_ERROR_RIM_COINTAINER:
+ bgph->pdu_type = BSSGP_PDUT_RAN_INFO_ERROR;
+ break;
+ default:
+ /* The caller must correctly specify the container type! */
+ OSMO_ASSERT(false);
+ }
+
+ /* Put RIM routing information */
+ rc = bssgp_create_rim_ri(rim_ri_buf, &pdu->routing_info_dest);
+ if (rc < 0 || rc > BSSGP_RIM_ROUTING_INFO_MAXLEN)
+ goto error;
+ msgb_tvlv_put(msg, BSSGP_IE_RIM_ROUTING_INFO, rc, rim_ri_buf);
+ rc = bssgp_create_rim_ri(rim_ri_buf, &pdu->routing_info_src);
+ if (rc < 0 || rc > BSSGP_RIM_ROUTING_INFO_MAXLEN)
+ goto error;
+ msgb_tvlv_put(msg, BSSGP_IE_RIM_ROUTING_INFO, rc, rim_ri_buf);
+
+ /* Put RIM container */
+ if (pdu->decoded_present) {
+ uint8_t *rim_cont_buf = talloc_zero_size(msg, msg->data_len);
+ if (!rim_cont_buf)
+ goto error;
+
+ switch (pdu->rim_cont_iei) {
+ case BSSGP_IE_RI_REQ_RIM_CONTAINER:
+ rc = bssgp_enc_ran_inf_req_rim_cont(rim_cont_buf, msg->data_len, &pdu->decoded.req_rim_cont);
+ break;
+ case BSSGP_IE_RI_RIM_CONTAINER:
+ rc = bssgp_enc_ran_inf_rim_cont(rim_cont_buf, msg->data_len, &pdu->decoded.rim_cont);
+ break;
+ case BSSGP_IE_RI_APP_ERROR_RIM_CONT:
+ rc = bssgp_enc_ran_inf_app_err_rim_cont(rim_cont_buf, msg->data_len,
+ &pdu->decoded.app_err_rim_cont);
+ break;
+ case BSSGP_IE_RI_ACK_RIM_CONTAINER:
+ rc = bssgp_enc_ran_inf_ack_rim_cont(rim_cont_buf, msg->data_len, &pdu->decoded.ack_rim_cont);
+ break;
+ case BSSGP_IE_RI_ERROR_RIM_COINTAINER:
+ rc = bssgp_enc_ran_inf_err_rim_cont(rim_cont_buf, msg->data_len, &pdu->decoded.err_rim_cont);
+ break;
+ default:
+ /* The API user must set the iei properly! */
+ OSMO_ASSERT(false);
+ }
+ if (rc < 0) {
+ talloc_free(rim_cont_buf);
+ goto error;
+ }
+
+ msgb_tvlv_put(msg, pdu->rim_cont_iei, rc, rim_cont_buf);
+ talloc_free(rim_cont_buf);
+ } else {
+ /* Make sure the RIM container is actually present. */
+ OSMO_ASSERT(pdu->rim_cont_iei != 0 && pdu->rim_cont_len > 0 && pdu->rim_cont);
+ msgb_tvlv_put(msg, pdu->rim_cont_iei, pdu->rim_cont_len, pdu->rim_cont);
+ }
+
+ return msg;
+error:
+ msgb_free(msg);
+ return 0;
+}
+
+/*! Send RIM RAN INFORMATION REQUEST via BSSGP (3GPP TS 48.018, section 10.6.1).
+ * \param[in] pdu user provided memory for the RAN INFORMATION PDU to be sent.
+ * \param[in] nsei BSSGP network service entity identifier (NSEI).
+ * \returns 0 on sccess, -EINVAL on error. */
+int bssgp_tx_rim(const struct bssgp_ran_information_pdu *pdu, uint16_t nsei)
+{
+ struct msgb *msg;
+ struct bssgp_normal_hdr *bgph;
+ char ri_src_str[64];
+ char ri_dest_str[64];
+
+ /* Encode RIM PDU into mesage buffer */
+ msg = bssgp_encode_rim_pdu(pdu);
+ if (!msg) {
+ LOGP(DLBSSGP, LOGL_ERROR,
+ "BSSGP RIM (NSEI=%u) unable to encode BSSGP RIM PDU\n", nsei);
+ return -EINVAL;
+ }
+
+ msgb_nsei(msg) = nsei;
+ msgb_bvci(msg) = 0; /* Signalling */
+
+ bgph = (struct bssgp_normal_hdr *)msgb_bssgph(msg);
+ DEBUGP(DLBSSGP, "BSSGP BVCI=0 Tx RIM-PDU:%s, src=%s, dest=%s\n",
+ 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);
+}
+
+/* For internal use only (called from gprs_bssgp.c) */
+int bssgp_rx_rim(struct msgb *msg, struct tlv_parsed *tp, uint16_t bvci)
+{
+ struct osmo_bssgp_prim nmp;
+ uint16_t nsei = msgb_nsei(msg);
+ struct bssgp_normal_hdr *bgph = (struct bssgp_normal_hdr *)msgb_bssgph(msg);
+ enum bssgp_prim prim;
+ char ri_src_str[64];
+ char ri_dest_str[64];
+
+ /* Specify PRIM type based on the RIM PDU */
+ switch (bgph->pdu_type) {
+ case BSSGP_PDUT_RAN_INFO:
+ case BSSGP_PDUT_RAN_INFO_REQ:
+ case BSSGP_PDUT_RAN_INFO_ACK:
+ case BSSGP_PDUT_RAN_INFO_ERROR:
+ case BSSGP_PDUT_RAN_INFO_APP_ERROR:
+ prim = PRIM_BSSGP_RIM_PDU_TRANSFER;
+ break;
+ default:
+ /* Caller already makes sure that this can't happen. */
+ OSMO_ASSERT(false);
+ }
+
+ /* Send BSSGP RIM indication to NM */
+ memset(&nmp, 0, sizeof(nmp));
+ nmp.nsei = nsei;
+ nmp.bvci = bvci;
+ nmp.tp = tp;
+ if (bssgp_parse_rim_pdu(&nmp.u.rim_pdu, msg) < 0)
+ return bssgp_tx_status(BSSGP_CAUSE_MISSING_MAND_IE, NULL, msg);
+ DEBUGP(DLBSSGP, "BSSGP BVCI=%u Rx RIM-PDU:%s, src=%s, dest=%s\n",
+ bvci, bssgp_pdu_str(bgph->pdu_type),
+ bssgp_rim_ri_name_buf(ri_src_str, sizeof(ri_src_str), &nmp.u.rim_pdu.routing_info_src),
+ bssgp_rim_ri_name_buf(ri_dest_str, sizeof(ri_dest_str), &nmp.u.rim_pdu.routing_info_dest));
+ osmo_prim_init(&nmp.oph, SAP_BSSGP_RIM, prim, PRIM_OP_INDICATION, msg);
+ bssgp_prim_cb(&nmp.oph, NULL);
+
+ return 0;
+}
diff --git a/src/gb/gprs_bssgp_util.c b/src/gb/gprs_bssgp_util.c
index 917f1f32..92896c1f 100644
--- a/src/gb/gprs_bssgp_util.c
+++ b/src/gb/gprs_bssgp_util.c
@@ -33,7 +33,6 @@
#include <osmocom/gprs/gprs_ns.h>
#include "gprs_bssgp_internal.h"
-#include "common_vty.h"
struct gprs_ns_inst *bssgp_nsi;
@@ -107,6 +106,8 @@ static const struct value_string bssgp_pdu_strings[] = {
{ BSSGP_PDUT_UL_UNITDATA, "UL-UNITDATA" },
{ BSSGP_PDUT_RA_CAPABILITY, "RA-CAPABILITY" },
{ BSSGP_PDUT_PTM_UNITDATA, "PTM-UNITDATA" },
+ { BSSGP_PDUT_DL_MMBS_UNITDATA, "DL-MBMS-UNITDATA" },
+ { BSSGP_PDUT_UL_MMBS_UNITDATA, "UL-MBMS-UNITDATA" },
{ BSSGP_PDUT_PAGING_PS, "PAGING-PS" },
{ BSSGP_PDUT_PAGING_CS, "PAGING-CS" },
{ BSSGP_PDUT_RA_CAPA_UDPATE, "RA-CAPABILITY-UPDATE" },
@@ -118,6 +119,10 @@ static const struct value_string bssgp_pdu_strings[] = {
{ BSSGP_PDUT_RESUME, "RESUME" },
{ BSSGP_PDUT_RESUME_ACK, "RESUME-ACK" },
{ BSSGP_PDUT_RESUME_NACK, "RESUME-NACK" },
+ { BSSGP_PDUT_DUMMY_PAGING_PS, "DUMMY-PAGING-PS" },
+ { BSSGP_PDUT_DUMMY_PAGING_PS_RESP, "DUMMY-PAGING-PS-RESP" },
+ { BSSGP_PDUT_MS_REGISTR_ENQ, "MS-REGISTRATION-ENQ" },
+ { BSSGP_PDUT_MS_REGISTR_ENQ_RESP, "MS-REGISTRATION-ENQ-RESP" },
{ BSSGP_PDUT_BVC_BLOCK, "BVC-BLOCK" },
{ BSSGP_PDUT_BVC_BLOCK_ACK, "BVC-BLOCK-ACK" },
{ BSSGP_PDUT_BVC_RESET, "BVC-RESET" },
@@ -131,8 +136,11 @@ static const struct value_string bssgp_pdu_strings[] = {
{ BSSGP_PDUT_FLUSH_LL, "FLUSH-LL" },
{ BSSGP_PDUT_FLUSH_LL_ACK, "FLUSH-LL-ACK" },
{ BSSGP_PDUT_LLC_DISCARD, "LLC DISCARDED" },
+ { BSSGP_PDUT_FLOW_CONTROL_PFC, "FLOW-CONTROL-PFC" },
+ { BSSGP_PDUT_FLOW_CONTROL_PFC_ACK, "FLOW-CONTROL-PFC-ACK" },
{ BSSGP_PDUT_SGSN_INVOKE_TRACE, "SGSN-INVOKE-TRACE" },
{ BSSGP_PDUT_STATUS, "STATUS" },
+ { BSSGP_PDUT_OVERLOAD, "OVERLOAD" },
{ BSSGP_PDUT_DOWNLOAD_BSS_PFC, "DOWNLOAD-BSS-PFC" },
{ BSSGP_PDUT_CREATE_BSS_PFC, "CREATE-BSS-PFC" },
{ BSSGP_PDUT_CREATE_BSS_PFC_ACK, "CREATE-BSS-PFC-ACK" },
@@ -141,9 +149,338 @@ static const struct value_string bssgp_pdu_strings[] = {
{ BSSGP_PDUT_MODIFY_BSS_PFC_ACK, "MODIFY-BSS-PFC-ACK" },
{ BSSGP_PDUT_DELETE_BSS_PFC, "DELETE-BSS-PFC" },
{ BSSGP_PDUT_DELETE_BSS_PFC_ACK, "DELETE-BSS-PFC-ACK" },
+ { BSSGP_PDUT_DELETE_BSS_PFC_REQ, "DELETE-BSS-PFC-REQ" },
+ { BSSGP_PDUT_PS_HO_REQUIRED, "PS-HO-REQUIRED" },
+ { BSSGP_PDUT_PS_HO_REQUIRED_ACK, "PS-HO-REQUIRED-ACK" },
+ { BSSGP_PDUT_PS_HO_REQUIRED_NACK, "PS-HO-REQUIRED-NACK" },
+ { BSSGP_PDUT_PS_HO_REQUEST, "PS-HO-REQUEST" },
+ { BSSGP_PDUT_PS_HO_REQUEST_ACK, "PS-HO-REQUEST-ACK" },
+ { BSSGP_PDUT_PS_HO_REQUEST_NACK, "PS-HO-REQUEST-NACK" },
+ { BSSGP_PDUT_PS_HO_COMPLETE, "PS-HO-COMPLETE" },
+ { BSSGP_PDUT_PS_HO_CANCEL, "PS-HO-CANCEL" },
+ { BSSGP_PDUT_PS_HO_COMPLETE_ACK, "PS-HO-COMPLETE-ACK" },
+ { BSSGP_PDUT_PERFORM_LOC_REQ, "PERFORM-LOC-REQ" },
+ { BSSGP_PDUT_PERFORM_LOC_RESP, "PERFORM-LOC-RESP" },
+ { BSSGP_PDUT_PERFORM_LOC_ABORT, "PERFORM-LOC-ABORT" },
+ { BSSGP_PDUT_POSITION_COMMAND, "POSITION-COMMAND" },
+ { BSSGP_PDUT_POSITION_RESPONSE, "POSITION-RESPONSE" },
+ { BSSGP_PDUT_RAN_INFO, "RAN-INFO" },
+ { BSSGP_PDUT_RAN_INFO_REQ, "RAN-INFO-REQ" },
+ { BSSGP_PDUT_RAN_INFO_ACK, "RAN-INFO-ACK" },
+ { BSSGP_PDUT_RAN_INFO_ERROR, "RAN-INFO-ERROR" },
+ { BSSGP_PDUT_RAN_INFO_APP_ERROR, "RAN-INFO-APP-ERROR" },
+ { BSSGP_PDUT_MBMS_START_REQ, "MBMS-START-REQ" },
+ { BSSGP_PDUT_MBMS_START_RESP, "MBMS-START-RESP" },
+ { BSSGP_PDUT_MBMS_STOP_REQ, "MBMS-STOP-REQ" },
+ { BSSGP_PDUT_MBMS_STOP_RESP, "MBMS-STOP-RESP" },
+ { BSSGP_PDUT_MBMS_UPDATE_REQ, "MBMS-UPDATE-REQ" },
+ { BSSGP_PDUT_MBMS_UPDATE_RESP, "MBMS-UPDATE-RESP" },
{ 0, NULL },
};
+static const uint8_t dl_ud_ies[] = { BSSGP_IE_PDU_LIFETIME };
+static const uint8_t ul_ud_ies[] = { BSSGP_IE_CELL_ID };
+static const uint8_t ra_cap_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_MS_RADIO_ACCESS_CAP };
+static const uint8_t dl_mb_ud_ies[] = { BSSGP_IE_PDU_LIFETIME, BSSGP_IE_TMGI, BSSGP_IE_LLC_PDU };
+static const uint8_t ul_mb_ud_ies[] = { BSSGP_IE_PDU_LIFETIME, BSSGP_IE_TMGI, BSSGP_IE_LLC_PDU };
+static const uint8_t pag_ps_ies[] = { BSSGP_IE_IMSI, BSSGP_IE_QOS_PROFILE };
+static const uint8_t pag_cs_ies[] = { BSSGP_IE_IMSI, BSSGP_IE_DRX_PARAMS };
+static const uint8_t ra_cap_upd_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_TAG };
+static const uint8_t ra_cap_upd_ack_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_TAG, BSSGP_IE_RA_CAP_UPD_CAUSE };
+static const uint8_t rad_sts_ies[] = { BSSGP_IE_RADIO_CAUSE };
+static const uint8_t suspend_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_ROUTEING_AREA };
+static const uint8_t suspend_ack_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_ROUTEING_AREA, BSSGP_IE_SUSPEND_REF_NR };
+static const uint8_t suspend_nack_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_ROUTEING_AREA };
+static const uint8_t resume_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_ROUTEING_AREA, BSSGP_IE_SUSPEND_REF_NR };
+static const uint8_t resume_ack_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_ROUTEING_AREA };
+static const uint8_t resume_nack_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_ROUTEING_AREA };
+static const uint8_t d_pag_ps_ies[] = { BSSGP_IE_IMSI };
+static const uint8_t d_pag_ps_resp_ies[] = { BSSGP_IE_IMSI, BSSGP_IE_T_UNTIL_NEXT_PAGING };
+static const uint8_t d_pag_ps_rej_ies[] = { BSSGP_IE_IMSI, BSSGP_IE_T_UNTIL_NEXT_PAGING };
+static const uint8_t ms_reg_enq_ies[] = { BSSGP_IE_IMSI };
+static const uint8_t ms_reg_enq_res_ies[] = { BSSGP_IE_IMSI };
+static const uint8_t flush_ll_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_BVCI };
+static const uint8_t flush_ll_ack_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_FLUSH_ACTION };
+static const uint8_t llc_disc_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_LLC_FRAMES_DISCARDED, BSSGP_IE_BVCI,
+ BSSGP_IE_NUM_OCT_AFF };
+static const uint8_t fc_bvc_ies[] = { BSSGP_IE_TAG, BSSGP_IE_BVC_BUCKET_SIZE, BSSGP_IE_BUCKET_LEAK_RATE,
+ BSSGP_IE_BMAX_DEFAULT_MS, BSSGP_IE_R_DEFAULT_MS };
+static const uint8_t fc_bvc_ack_ies[] = { BSSGP_IE_TAG };
+static const uint8_t fc_ms_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_TAG, BSSGP_IE_MS_BUCKET_SIZE,
+ BSSGP_IE_BUCKET_LEAK_RATE };
+static const uint8_t fc_ms_ack_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_TAG };
+static const uint8_t block_ies[] = { BSSGP_IE_BVCI, BSSGP_IE_CAUSE };
+static const uint8_t block_ack_ies[] = { BSSGP_IE_BVCI };
+static const uint8_t unblock_ies[] = { BSSGP_IE_BVCI };
+static const uint8_t unblock_ack_ies[] = { BSSGP_IE_BVCI };
+static const uint8_t reset_ies[] = { BSSGP_IE_BVCI, BSSGP_IE_CAUSE };
+static const uint8_t reset_ack_ies[] = { BSSGP_IE_BVCI };
+static const uint8_t status_ies[] = { BSSGP_IE_CAUSE };
+static const uint8_t inv_trc_ies[] = { BSSGP_IE_TRACE_TYPE, BSSGP_IE_TRACE_REFERENC };
+static const uint8_t dl_bss_pfc_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_PACKET_FLOW_ID };
+static const uint8_t crt_bss_pfc_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_PACKET_FLOW_ID,
+ BSSGP_IE_PACKET_FLOW_TIMER, BSSGP_IE_AGG_BSS_QOS_PROFILE };
+static const uint8_t crt_bss_pfc_ack_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_PACKET_FLOW_ID,
+ BSSGP_IE_AGG_BSS_QOS_PROFILE };
+static const uint8_t crt_bss_pfc_nack_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_PACKET_FLOW_ID, BSSGP_IE_CAUSE };
+static const uint8_t mod_bss_pfc_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_PACKET_FLOW_ID,
+ BSSGP_IE_AGG_BSS_QOS_PROFILE };
+static const uint8_t mod_bss_pfc_ack_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_PACKET_FLOW_ID,
+ BSSGP_IE_PACKET_FLOW_TIMER, BSSGP_IE_AGG_BSS_QOS_PROFILE };
+static const uint8_t del_bss_pfc_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_PACKET_FLOW_ID };
+static const uint8_t del_bss_pfc_ack_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_PACKET_FLOW_ID };
+static const uint8_t fc_pfc_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_TAG, BSSGP_IE_PFC_FLOW_CTRL_PARAMS };
+static const uint8_t fc_pfc_ack_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_TAG };
+static const uint8_t del_bss_pfc_req_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_PACKET_FLOW_ID, BSSGP_IE_CAUSE };
+static const uint8_t ps_ho_required_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_CAUSE, BSSGP_IE_CELL_ID,
+ BSSGP_IE_ACTIVE_PFC_LIST };
+static const uint8_t ps_ho_required_ack_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_LIST_OF_SETUP_PFC };
+static const uint8_t ps_ho_required_nack_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_CAUSE };
+static const uint8_t ps_ho_request_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_IMSI, BSSGP_IE_CAUSE,
+ BSSGP_IE_CELL_ID, BSSGP_IE_SBSS_TO_TBSS_TR_CONT,
+ BSSGP_IE_PFC_TO_BE_SETUP_LIST };
+static const uint8_t ps_ho_request_ack_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_LIST_OF_SETUP_PFC,
+ BSSGP_IE_TBSS_TO_SBSS_TR_CONT };
+static const uint8_t ps_ho_request_nack_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_CAUSE };
+static const uint8_t ps_ho_compl_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_IMSI };
+static const uint8_t ps_ho_cancel_ies[] = { BSSGP_IE_TLLI, BSSGP_IE_CAUSE, BSSGP_IE_CELL_ID };
+static const uint8_t ps_ho_compl_ack_ies[] = { BSSGP_IE_TLLI };
+static const uint8_t overload_ies[] = { BSSGP_IE_PRIO_CLASS_IND };
+static const uint8_t rinfo_ies[] = { BSSGP_IE_RIM_ROUTING_INFO, BSSGP_IE_RI_RIM_CONTAINER };
+static const uint8_t rinfo_req_ies[] = { BSSGP_IE_RIM_ROUTING_INFO, BSSGP_IE_RI_REQ_RIM_CONTAINER };
+static const uint8_t rinfo_ack_ies[] = { BSSGP_IE_RIM_ROUTING_INFO, BSSGP_IE_RI_ACK_RIM_CONTAINER };
+static const uint8_t rinfo_err_ies[] = { BSSGP_IE_RIM_ROUTING_INFO, BSSGP_IE_RI_ERROR_RIM_COINTAINER };
+static const uint8_t rinfo_aerr_ies[] = { BSSGP_IE_RIM_ROUTING_INFO, BSSGP_IE_RI_APP_ERROR_RIM_CONT };
+
+#define DL BSSGP_PDUF_DL
+#define UL BSSGP_PDUF_UL
+#define SIG BSSGP_PDUF_SIG
+#define PTP BSSGP_PDUF_PTP
+#define PTM BSSGP_PDUF_PTM
+
+const struct osmo_tlv_prot_def osmo_pdef_bssgp = {
+ .name = "BSSGP",
+ .tlv_def = &tvlv_att_def,
+ .msg_def = {
+ [BSSGP_PDUT_DL_UNITDATA] = MSG_DEF("DL-UNITDATA", dl_ud_ies, DL|PTP),
+ [BSSGP_PDUT_UL_UNITDATA] = MSG_DEF("UL-UNITDATA", ul_ud_ies, UL|PTP),
+ [BSSGP_PDUT_RA_CAPABILITY] = MSG_DEF("RA-CAPABILITY", ra_cap_ies, DL|PTP),
+ [BSSGP_PDUT_DL_MMBS_UNITDATA] = MSG_DEF("DL-MBMS-UNITDATA", dl_mb_ud_ies, DL|PTM),
+ [BSSGP_PDUT_UL_MMBS_UNITDATA] = MSG_DEF("UL-MBMS-UNITDATA", ul_mb_ud_ies, UL|PTM),
+ [BSSGP_PDUT_PAGING_PS] = MSG_DEF("PAGING-PS", pag_ps_ies, DL|PTP|SIG),
+ [BSSGP_PDUT_PAGING_CS] = MSG_DEF("PAGING-CS", pag_cs_ies, DL|PTP|SIG),
+ [BSSGP_PDUT_RA_CAPA_UDPATE] = MSG_DEF("RA-CAPABILITY-UPDATE", ra_cap_upd_ies, UL|PTP),
+ [BSSGP_PDUT_RA_CAPA_UPDATE_ACK] = MSG_DEF("RA-CAPABILITY-UPDATE-ACK", ra_cap_upd_ack_ies, DL|PTP),
+ [BSSGP_PDUT_RADIO_STATUS] = MSG_DEF("RADIO-STATUS", rad_sts_ies, UL|PTP),
+ [BSSGP_PDUT_SUSPEND] = MSG_DEF("SUSPEND", suspend_ies, UL|SIG),
+ [BSSGP_PDUT_SUSPEND_ACK] = MSG_DEF("SUSPEND-ACK", suspend_ack_ies, DL|SIG),
+ [BSSGP_PDUT_SUSPEND_NACK] = MSG_DEF("SUSPEND-NACK", suspend_nack_ies, DL|SIG),
+ [BSSGP_PDUT_RESUME] = MSG_DEF("RESUME", resume_ies, UL|SIG),
+ [BSSGP_PDUT_RESUME_ACK] = MSG_DEF("RESUME-ACK", resume_ack_ies, DL|SIG),
+ [BSSGP_PDUT_RESUME_NACK] = MSG_DEF("RESUME-NACK", resume_nack_ies, DL|SIG),
+ [BSSGP_PDUT_DUMMY_PAGING_PS] = MSG_DEF("DUMMY-PAGING-PS", d_pag_ps_ies, DL|SIG|PTP),
+ [BSSGP_PDUT_DUMMY_PAGING_PS_RESP] = MSG_DEF("DUMMY-PAGING-PS-RESP", d_pag_ps_resp_ies, UL|SIG|PTP),
+ [BSSGP_PDUT_PAGING_PS_REJECT] = MSG_DEF("PAGING-PS-REJ", d_pag_ps_rej_ies, UL|SIG|PTP),
+ [BSSGP_PDUT_MS_REGISTR_ENQ] = MSG_DEF("MS-REGISRATION-ENQ", ms_reg_enq_ies, UL|SIG),
+ [BSSGP_PDUT_MS_REGISTR_ENQ_RESP] = MSG_DEF("MS-REGISRATION-ENQ-RESP", ms_reg_enq_res_ies, DL|SIG),
+ [BSSGP_PDUT_FLUSH_LL] = MSG_DEF("FLUSH-LL", flush_ll_ies, DL|SIG),
+ [BSSGP_PDUT_FLUSH_LL_ACK] = MSG_DEF("FLUSH-LL-ACK", flush_ll_ack_ies, UL|SIG),
+ [BSSGP_PDUT_LLC_DISCARD] = MSG_DEF("LLC-DISCARDED", llc_disc_ies, UL|SIG),
+ [BSSGP_PDUT_FLOW_CONTROL_BVC] = MSG_DEF("FC-BVC", fc_bvc_ies, UL|PTP),
+ [BSSGP_PDUT_FLOW_CONTROL_BVC_ACK] = MSG_DEF("FC-BVC-ACK", fc_bvc_ack_ies, DL|PTP),
+ [BSSGP_PDUT_FLOW_CONTROL_MS] = MSG_DEF("FC-MS", fc_ms_ies, UL|PTP),
+ [BSSGP_PDUT_FLOW_CONTROL_MS_ACK] = MSG_DEF("FC-MS-ACK", fc_ms_ack_ies, DL|PTP),
+ [BSSGP_PDUT_BVC_BLOCK] = MSG_DEF("BVC-BLOCK", block_ies, UL|SIG),
+ [BSSGP_PDUT_BVC_BLOCK_ACK] = MSG_DEF("BVC-BLOCK-ACK", block_ack_ies, DL|SIG),
+ [BSSGP_PDUT_BVC_UNBLOCK] = MSG_DEF("BVC-UNBLOCK", unblock_ies, UL|SIG),
+ [BSSGP_PDUT_BVC_UNBLOCK_ACK] = MSG_DEF("BVC-UNBLOCK-ACK", unblock_ack_ies, DL|SIG),
+ [BSSGP_PDUT_BVC_RESET] = MSG_DEF("BVC-RESET", reset_ies, UL|DL|SIG|PTP),
+ [BSSGP_PDUT_BVC_RESET_ACK] = MSG_DEF("BVC-RESET-ACK", reset_ack_ies, UL|DL|SIG|PTP),
+ [BSSGP_PDUT_STATUS] = MSG_DEF("STATUS", status_ies, UL|DL|PTP|SIG|PTM),
+ [BSSGP_PDUT_SGSN_INVOKE_TRACE] = MSG_DEF("SGSN-INVOKE-TRACE", inv_trc_ies, DL|SIG),
+ [BSSGP_PDUT_DOWNLOAD_BSS_PFC] = MSG_DEF("DOWNLOAD-BSS-PFC", dl_bss_pfc_ies, UL|PTP),
+ [BSSGP_PDUT_CREATE_BSS_PFC] = MSG_DEF("CREATE-BSS-PFC", crt_bss_pfc_ies, DL|PTP),
+ [BSSGP_PDUT_CREATE_BSS_PFC_ACK] = MSG_DEF("CREATE-BSS-PFC-ACK", crt_bss_pfc_ack_ies, UL|PTP),
+ [BSSGP_PDUT_CREATE_BSS_PFC_NACK] = MSG_DEF("CREATE-BSS-PFC-NACK", crt_bss_pfc_nack_ies, UL|PTP),
+ [BSSGP_PDUT_MODIFY_BSS_PFC] = MSG_DEF("MODIFY-BSS-PFC", mod_bss_pfc_ies, DL|PTP),
+ [BSSGP_PDUT_MODIFY_BSS_PFC_ACK] = MSG_DEF("MODIFY-BSS-PFC-ACK", mod_bss_pfc_ack_ies, UL|PTP),
+ [BSSGP_PDUT_DELETE_BSS_PFC] = MSG_DEF("DELETE-BSS-PFC", del_bss_pfc_ies, DL|PTP),
+ [BSSGP_PDUT_DELETE_BSS_PFC_ACK] = MSG_DEF("DELETE-BSS-PFC-ACK", del_bss_pfc_ack_ies, UL|PTP),
+ [BSSGP_PDUT_FLOW_CONTROL_PFC] = MSG_DEF("FC-PFC", fc_pfc_ies, UL|PTP),
+ [BSSGP_PDUT_FLOW_CONTROL_PFC_ACK] = MSG_DEF("FC-PFC-ACK", fc_pfc_ack_ies, DL|PTP),
+ [BSSGP_PDUT_DELETE_BSS_PFC_REQ] = MSG_DEF("DELETE-BSS-PFC-REQ", del_bss_pfc_req_ies, UL|PTP),
+ [BSSGP_PDUT_PS_HO_REQUIRED] = MSG_DEF("PS-HO-REQUIRED", ps_ho_required_ies, UL|PTP),
+ [BSSGP_PDUT_PS_HO_REQUIRED_ACK] = MSG_DEF("PS-HO-REQUIRED-ACK", ps_ho_required_ack_ies, DL|PTP),
+ [BSSGP_PDUT_PS_HO_REQUIRED_NACK] = MSG_DEF("PS-HO-REQUIRED-NACK", ps_ho_required_nack_ies, DL|PTP),
+ [BSSGP_PDUT_PS_HO_REQUEST] = MSG_DEF("PS-HO-REQUEST", ps_ho_request_ies, DL|PTP),
+ [BSSGP_PDUT_PS_HO_REQUEST_ACK] = MSG_DEF("PS-HO-REQUEST-ACK", ps_ho_request_ack_ies, UL|PTP),
+ [BSSGP_PDUT_PS_HO_REQUEST_NACK] = MSG_DEF("PS-HO-REQUEST-NACK", ps_ho_request_nack_ies, UL|PTP),
+ [BSSGP_PDUT_PS_HO_COMPLETE] = MSG_DEF("PS-HO-COMPLETE", ps_ho_compl_ies, UL|PTP),
+ [BSSGP_PDUT_PS_HO_CANCEL] = MSG_DEF("PS-HO-CANCEL", ps_ho_cancel_ies, UL|PTP),
+ [BSSGP_PDUT_PS_HO_COMPLETE_ACK] = MSG_DEF("PS-HO-COMPLETE-ACK", ps_ho_compl_ack_ies, DL|PTP),
+ [BSSGP_PDUT_OVERLOAD] = MSG_DEF("OVERLOAD", overload_ies, DL|SIG),
+ /* TODO: Messages on LCS SAP */
+ /* Messages on RIM SAP */
+ [BSSGP_PDUT_RAN_INFO] = MSG_DEF("RAN-INFORMATION", rinfo_ies, DL|UL|SIG),
+ [BSSGP_PDUT_RAN_INFO_REQ] = MSG_DEF("RAN-INFORMATION-REQUEST", rinfo_req_ies, DL|UL|SIG),
+ [BSSGP_PDUT_RAN_INFO_ACK] = MSG_DEF("RAN-INFORMATION-ACK", rinfo_ack_ies, DL|UL|SIG),
+ [BSSGP_PDUT_RAN_INFO_ERROR] = MSG_DEF("RAN-INFORMATION-ERROR", rinfo_err_ies, DL|UL|SIG),
+ [BSSGP_PDUT_RAN_INFO_APP_ERROR] = MSG_DEF("RAN-INFORMATION-APP-ERROR", rinfo_aerr_ies, DL|UL|SIG),
+ /* TODO: Messages on MBMS SAP */
+ },
+ .ie_def = {
+ [BSSGP_IE_ALIGNMENT] = { 0, "Alignment Octets" },
+ [BSSGP_IE_BMAX_DEFAULT_MS] = { 2, "Bmax default MS" },
+ [BSSGP_IE_BSS_AREA_ID] = { 1, "BSS Area Indication" },
+ [BSSGP_IE_BUCKET_LEAK_RATE] = { 2, "Bucket Leak Rate (R)" },
+ [BSSGP_IE_BVC_BUCKET_SIZE] = { 2, "BVC Bucket Size" },
+ [BSSGP_IE_BVCI] = { 2, "BVCI" },
+ [BSSGP_IE_BVC_MEASUREMENT] = {2, "BVC Measurement" },
+ [BSSGP_IE_CAUSE] = { 1, "Cause" },
+ [BSSGP_IE_CELL_ID] = { 8, "Cell Identifier" },
+ [BSSGP_IE_CHAN_NEEDED] = { 1, "Channel Needed" },
+ [BSSGP_IE_DRX_PARAMS] = { 2, "DRX Parameters" },
+ [BSSGP_IE_EMLPP_PRIO] = { 3, "eMLPP Priority" },
+ [BSSGP_IE_FLUSH_ACTION] = { 1, "Flush Action" },
+ [BSSGP_IE_IMSI] = { 1, "Mobile Identity" },
+ [BSSGP_IE_LLC_PDU] = { 0, "LLC-PDU" },
+ [BSSGP_IE_LLC_FRAMES_DISCARDED] = { 1, "LLC Frames Discarded" },
+ [BSSGP_IE_LOCATION_AREA] = { 5, "Location Area" },
+ [BSSGP_IE_LSA_ID_LIST] = { 3, "LSA Identifier List" },
+ [BSSGP_IE_LSA_INFORMATION] = { 5, "LSA Information" },
+ [BSSGP_IE_MOBILE_ID] = { 1, "Mobile Identity" },
+ [BSSGP_IE_MS_BUCKET_SIZE] = { 2, "MS Bucket Size" },
+ [BSSGP_IE_MS_RADIO_ACCESS_CAP] = { 1, "MS Radio Access Capability" },
+ [BSSGP_IE_OMC_ID] = { 1, "OMC Id" },
+ [BSSGP_IE_PDU_IN_ERROR] = { 0, "PDU In Error" },
+ [BSSGP_IE_PDU_LIFETIME] = { 2, "PDU Lifetime" },
+ [BSSGP_IE_PRIORITY] = { 1, "Priority" },
+ [BSSGP_IE_QOS_PROFILE] = { 3, "QoS Profile" },
+ [BSSGP_IE_RADIO_CAUSE] = { 1, "Radio Cause" },
+ [BSSGP_IE_RA_CAP_UPD_CAUSE] = { 1, "RA-Cap-UPD-Cause" },
+ [BSSGP_IE_ROUTEING_AREA] = { 6, "Routeing Area" },
+ [BSSGP_IE_R_DEFAULT_MS] = { 2, "R_default_MS" },
+ [BSSGP_IE_SUSPEND_REF_NR] = { 1, "Suspend Reference Number" },
+ [BSSGP_IE_TAG] = { 1, "Tag" },
+ [BSSGP_IE_TLLI] = { 4, "TLLI" },
+ [BSSGP_IE_TMSI] = { 4, "TMSI" },
+ [BSSGP_IE_TRACE_REFERENC] = { 2, "Trace Reference" },
+ [BSSGP_IE_TRACE_TYPE] = { 1, "Trace Type" },
+ [BSSGP_IE_TRANSACTION_ID] = { 2, "Transaction Id" },
+ [BSSGP_IE_TRIGGER_ID] = { 1, "Trigger Id" },
+ [BSSGP_IE_NUM_OCT_AFF] = { 3, "Number of octets affected" },
+ [BSSGP_IE_PACKET_FLOW_ID] = { 1, "Packet Flow Identifier (PFI)" },
+ [BSSGP_IE_AGG_BSS_QOS_PROFILE] = { 14, "Aggregate BSS QoS Profile" },
+ [BSSGP_IE_PACKET_FLOW_TIMER] = { 1, "GPRS Timer" },
+ [BSSGP_IE_FEATURE_BITMAP] = { 1, "Feature Bitmap" },
+ [BSSGP_IE_BUCKET_FULL_RATIO] = { 1, "Bucket Full Ratio" },
+ [BSSGP_IE_SERVICE_UTRAN_CCO] = { 1, "Service UTRAN COO" },
+ [BSSGP_IE_NSEI] = { 2, "NSEI" },
+ [BSSGP_IE_RRLP_APDU] = { 1, "RLLP APDU" },
+ [BSSGP_IE_LCS_QOS] = { 4, "LCS QoS" },
+ [BSSGP_IE_LCS_CLIENT_TYPE] = { 1, "LCS Client Type" },
+ [BSSGP_IE_REQUESTED_GPS_AST_DATA] = { 4, "Requested GPS Assistance Data" },
+ [BSSGP_IE_LOCATION_TYPE] = { 2, "Location Type" },
+ [BSSGP_IE_LOCATION_ESTIMATE] = { 1, "Location Estimate" },
+ [BSSGP_IE_POSITIONING_DATA] = { 1, "Positioning Data" },
+ [BSSGP_IE_DECIPHERING_KEYS] = { 15, "Deciphering Keys" },
+ [BSSGP_IE_LCS_PRIORITY] = { 1, "LCS Priority" },
+ [BSSGP_IE_LCS_CAUSE] = { 1, "LCS Cause" },
+ [BSSGP_IE_LCS_CAPABILITY] = { 1, "LCS Capability" },
+ [BSSGP_IE_RRLP_FLAGS] = { 1, "RRLP Flags" },
+ [BSSGP_IE_RIM_APP_IDENTITY] = { 1, "RIM Application Identity" },
+ [BSSGP_IE_RIM_SEQ_NR] = { 4, "RIM Sequence Number" },
+ [BSSGP_IE_RIM_REQ_APP_CONTAINER] = { 12, "RIM-REQUEST RIM Container" },
+ [BSSGP_IE_RAN_INFO_APP_CONTAINER] = { 12, "RAN-INFORMATION RIM Container" },
+ [BSSGP_IE_RI_ACK_RIM_CONTAINER] = { 9, "RAN-INFORMATION-ACK RIM Container" },
+ [BSSGP_IE_RI_ERROR_RIM_COINTAINER] = { 9, "RAN-INFOIRMATION-ERROR RIM Container" },
+ [BSSGP_IE_RI_APP_ERROR_RIM_CONT] = { 14, "RAN-INFORMATION-APP-ERROR RIM Container" },
+ [BSSGP_IE_RIM_PDU_INDICATIONS] = { 1, "RIM PDU Indications" },
+ [BSSGP_IE_RIM_PROTOCOL_VERSION] = { 1, "RIM Protocol Version Number" },
+ [BSSGP_IE_PFC_FLOW_CTRL_PARAMS] = { 7, "PFC FLow Control Parameters" },
+ [BSSGP_IE_GLOBAL_CN_ID] = { 5, "Global CN-Id" },
+ [BSSGP_IE_RIM_ROUTING_INFO] = { 1, "RIM Routing Information" },
+ [BSSGP_IE_MBMS_SESSION_ID] = { 0, "MBMS Session Identity" },
+ [BSSGP_IE_MBMS_SESSION_DURATION] = { 0, "MBMS Session Duration" },
+ [BSSGP_IE_MBMS_SA_ID_LIST] = { 3, "MBMS Service Area Identity List" },
+ [BSSGP_IE_MBMS_RESPONSE] = { 1, "MBMS Response" },
+ [BSSGP_IE_MBMS_RA_LIST] = { 9, "MBMS Routing Area List" },
+ [BSSGP_IE_MBMS_SESSION_INFO] = { 1, "MBMS Session Information" },
+ [BSSGP_IE_TMGI] = { 6, "TMGI" },
+ [BSSGP_IE_MBMS_STOP_CAUSE] = { 1, "MBM Stop Cause" },
+ [BSSGP_IE_SBSS_TO_TBSS_TR_CONT] = { 7, "Source BSS to Target BSS Transparent Container" },
+ [BSSGP_IE_TBSS_TO_SBSS_TR_CONT] = { 0, "Target BSS to Source BSS Transparent Container" },
+ [BSSGP_IE_NAS_CONT_FOR_PS_HO] = { 0, "NAS container for PS Handover" },
+ [BSSGP_IE_PFC_TO_BE_SETUP_LIST] = { 9, "PFCs to be set-up list" },
+ [BSSGP_IE_LIST_OF_SETUP_PFC] = { 1, "List of set-up PFCs" },
+ [BSSGP_IE_EXT_FEATURE_BITMAP] = { 1, "Extended Feature Bitmap" },
+ [BSSGP_IE_SRC_TO_TGT_TR_CONT] = { 0, "Source to Target Transparent Container" },
+ [BSSGP_IE_TGT_TO_SRC_TR_CONT] = { 0, "Target to Source Transparent Container" },
+ [BSSGP_IE_NC_ID] = { 8, "RNC Identifier" },
+ [BSSGP_IE_PAGE_MODE] = { 1, "Page Mode" },
+ [BSSGP_IE_CONTAINER_ID] = { 1, "Container ID" },
+ [BSSGP_IE_GLOBAL_TFI] = { 1, "Global TFI" },
+ [BSSGP_IE_IMEI] = { 1, "IMEI" },
+ [BSSGP_IE_TIME_TO_MBMS_DATA_XFR] = { 1, "Time to MBMS Data Transfer" },
+ [BSSGP_IE_MBMS_SESSION_REP_NR] = { 1, "MBMS Session Repetition Number" },
+ [BSSGP_IE_INTER_RAT_HO_INFO] = { 0, "Inter RAT Handover Info" },
+ [BSSGP_IE_PS_HO_COMMAND] = { 0, "PS Handover Command" },
+ [BSSGP_IE_PS_HO_INDICATIONS] = { 1, "PS Handover Indications" },
+ [BSSGP_IE_SI_PSI_CONTAINER] = { 1, "SI/PSI Container" },
+ [BSSGP_IE_ACTIVE_PFC_LIST] = { 2, "Active PFCs List" },
+ [BSSGP_IE_VELOCITY_DATA] = { 0, "Velocity Data" },
+ [BSSGP_IE_DTM_HO_COMMAND] = { 0, "DTM Handover Command" },
+ [BSSGP_IE_CS_INDICATION] = { 1, "CS Indication" },
+ [BSSGP_IE_RQD_GANNS_AST_DATA] = { 0, "Requested GANSS Assistance Data" },
+ [BSSGP_IE_GANSS_LOCATION_TYPE] = { 1, "GANSS Location Type" },
+ [BSSGP_IE_GANSS_POSITIONING_DATA] = { 0, "GANSS Positioning Data" },
+ [BSSGP_IE_FLOW_CTRL_GRANULARITY] = { 1, "Flow Control Granularity" },
+ [BSSGP_IE_ENB_ID] = { 6, "eNB Identifier" },
+ [BSSGP_IE_EUTRAN_IRAT_HO_INFO] = { 0, "E-UTRAN Inter RAT Handover Info" },
+ [BSSGP_IE_SUB_PID4RAT_FREQ_PRIO] = { 1, "Subscriber Profile ID for RAT/Frequency priority" },
+ [BSSGP_IE_REQ4IRAT_HO_INFO] = { 1, "Request for Inter-RAT Handover Info" },
+ [BSSGP_IE_RELIABLE_IRAT_HO_INFO] = { 1, "Reliable Inter-RAT Handover Info" },
+ [BSSGP_IE_SON_TRANSFER_APP_ID] = { 0, "SON Transfer Application Identity" },
+ [BSSGP_IE_CSG_ID] = { 5, "CSG Identifier" },
+ [BSSGP_IE_TAC] = { 3, "Tracking Area Code" },
+ [BSSGP_IE_REDIRECT_ATTEMPT_FLAG] = { 1, "Redirect Attempt Flag" },
+ [BSSGP_IE_REDIRECTION_INDICATION] = { 1, "Redirection Indication" },
+ [BSSGP_IE_REDIRECTION_COMPLETED] = { 1, "Redirection Completed" },
+ [BSSGP_IE_UNCONF_SEND_STATE_VAR] = { 2, "Unconfirmed send state variable" },
+ [BSSGP_IE_IRAT_MEASUREMENT_CONF] = { 10, "IRAT Measurement Configuration" },
+ [BSSGP_IE_SCI] = { 1, "SCI" },
+ [BSSGP_IE_GGSN_PGW_LOCATION] = { 1, "GGSN/P-GW Location" },
+ [BSSGP_IE_SELECTED_PLMN_ID] = { 3, "Selected PLMN ID" },
+ [BSSGP_IE_PRIO_CLASS_IND] = { 1, "Priority Class Indication" },
+ [BSSGP_IE_SOURCE_CELL_ID] = { 6, "Source Cell ID" },
+ [BSSGP_IE_IRAT_MEAS_CFG_E_EARFCN] = { 10, "IRAT Measurement Configuration (extended E-ARFCNs)" },
+ [BSSGP_IE_EDRX_PARAMETERS] = { 1, "eDRX Parameters" },
+ [BSSGP_IE_T_UNTIL_NEXT_PAGING] = { 2, "Time Until Next Paging Occasion" },
+ [BSSGP_IE_COVERAGE_CLASS] = { 1, "Coverage Class" },
+ [BSSGP_IE_PAGING_ATTEMPT_INFO] = { 1, "Paging Attempt Information" },
+ [BSSGP_IE_EXCEPTION_REPORT_FLAG] = { 1, "Exception Report Flag" },
+ [BSSGP_IE_OLD_RA_ID] = { 6, "Old Routing Area Identification" },
+ [BSSGP_IE_ATTACH_IND] = { 1, "Attach Indicator" },
+ [BSSGP_IE_PLMN_ID] = { 3, "PLMN Identity" },
+ [BSSGP_IE_MME_QUERY] = { 1, "MME Query" },
+ [BSSGP_IE_SGSN_GROUP_ID] = { 3, "SGSN Group Identity" },
+ [BSSGP_IE_ADDITIONAL_PTMSI] = { 4, "Additional P-TMSI" },
+ [BSSGP_IE_UE_USAGE_TYPE] = { 1, "UE Usage Type" },
+ [BSSGP_IE_MLAT_TIMER] = { 1, "Multilateration Timer" },
+ [BSSGP_IE_MLAT_TA] = { 2, "Multilateration Timing Advance" },
+ [BSSGP_IE_MS_SYNC_ACCURACY] = { 1, "MS Sync Accuracy" },
+ [BSSGP_IE_BTS_RX_ACCURACY_LVL] = { 1, "BTS Reception Accuracy Level" },
+ [BSSGP_IE_TA_REQ] = { 1, "Timing Advance Request (TAR)" },
+ },
+};
+
+#undef DL
+#undef UL
+#undef SIG
+#undef PTP
+#undef PTM
+
+
const char *bssgp_cause_str(enum gprs_bssgp_cause cause)
{
return get_value_string(bssgp_cause_strings, cause);
@@ -225,17 +562,17 @@ int bssgp_tx_status(uint8_t cause, uint16_t *bvci, struct msgb *orig_msg)
cause is either "BVCI blocked" or "BVCI unknown" */
if (cause == BSSGP_CAUSE_UNKNOWN_BVCI || cause == BSSGP_CAUSE_BVCI_BLOCKED) {
if (bvci == NULL)
- LOGP(DBSSGP, LOGL_ERROR, "BSSGP Tx STATUS, cause=%s: "
+ LOGP(DLBSSGP, LOGL_ERROR, "BSSGP Tx STATUS, cause=%s: "
"missing conditional BVCI\n",
bssgp_cause_str(cause));
} else {
if (bvci != NULL)
- LOGP(DBSSGP, LOGL_ERROR, "BSSGP Tx STATUS, cause=%s: "
+ LOGP(DLBSSGP, LOGL_ERROR, "BSSGP Tx STATUS, cause=%s: "
"unexpected conditional BVCI\n",
bssgp_cause_str(cause));
}
- LOGP(DBSSGP, LOGL_NOTICE, "BSSGP BVCI=%u Tx STATUS, cause=%s\n",
+ LOGP(DLBSSGP, LOGL_NOTICE, "BSSGP BVCI=%u Tx STATUS, cause=%s\n",
bvci ? *bvci : 0, bssgp_cause_str(cause));
msgb_nsei(msg) = msgb_nsei(orig_msg);
msgb_bvci(msg) = 0;
diff --git a/src/gb/gprs_bssgp_vty.c b/src/gb/gprs_bssgp_vty.c
index 5dab94e7..d04641d9 100644
--- a/src/gb/gprs_bssgp_vty.c
+++ b/src/gb/gprs_bssgp_vty.c
@@ -44,8 +44,6 @@
#include <osmocom/vty/telnet_interface.h>
#include <osmocom/vty/misc.h>
-#include "common_vty.h"
-
static void log_set_bvc_filter(struct log_target *target,
struct bssgp_bvc_ctx *bctx)
{
@@ -207,15 +205,15 @@ DEFUN(logging_fltr_bvc,
int bssgp_vty_init(void)
{
- install_element_ve(&show_bssgp_cmd);
- install_element_ve(&show_bssgp_stats_cmd);
- install_element_ve(&show_bvc_cmd);
- install_element_ve(&logging_fltr_bvc_cmd);
- install_element_ve(&bvc_reset_cmd);
+ install_lib_element_ve(&show_bssgp_cmd);
+ install_lib_element_ve(&show_bssgp_stats_cmd);
+ install_lib_element_ve(&show_bvc_cmd);
+ install_lib_element_ve(&logging_fltr_bvc_cmd);
+ install_lib_element_ve(&bvc_reset_cmd);
- install_element(CFG_LOG_NODE, &logging_fltr_bvc_cmd);
+ install_lib_element(CFG_LOG_NODE, &logging_fltr_bvc_cmd);
- install_element(CONFIG_NODE, &cfg_bssgp_cmd);
+ install_lib_element(CONFIG_NODE, &cfg_bssgp_cmd);
install_node(&bssgp_node, config_write_bssgp);
return 0;
diff --git a/src/gb/gprs_ns.c b/src/gb/gprs_ns.c
index a6ef6d47..304fe7c1 100644
--- a/src/gb/gprs_ns.c
+++ b/src/gb/gprs_ns.c
@@ -29,7 +29,7 @@
* @{
*
* GPRS Networks Service (NS) messages on the Gb interface
- * 3GPP TS 08.16 version 8.0.1 Release 1999 / ETSI TS 101 299 V8.0.1 (2002-05)
+ * 3GPP TS 08.16 version 8.0.1 Release 1999 / ETSI TS 101 299 V8.0.1 (2002-05)
*
* Some introduction into NS: NS is used typically on top of frame relay,
* but in the ip.access world it is encapsulated in UDP packets. It serves
@@ -352,8 +352,7 @@ struct gprs_nsvc *gprs_nsvc_create2(struct gprs_ns_inst *nsi, uint16_t nsvci,
*/
void gprs_nsvc_delete(struct gprs_nsvc *nsvc)
{
- if (osmo_timer_pending(&nsvc->timer))
- osmo_timer_del(&nsvc->timer);
+ osmo_timer_del(&nsvc->timer);
llist_del(&nsvc->list);
rate_ctr_group_free(nsvc->ctrg);
osmo_stat_item_group_free(nsvc->statg);
@@ -486,8 +485,8 @@ static int gprs_ns_tx(struct gprs_nsvc *nsvc, struct msgb *msg)
}
/* Increment number of Uplink bytes */
- rate_ctr_inc(&nsvc->ctrg->ctr[NS_CTR_PKTS_OUT]);
- rate_ctr_add(&nsvc->ctrg->ctr[NS_CTR_BYTES_OUT], msgb_l2len(msg));
+ rate_ctr_inc(rate_ctr_group_get_ctr(nsvc->ctrg, NS_CTR_PKTS_OUT));
+ rate_ctr_add(rate_ctr_group_get_ctr(nsvc->ctrg, NS_CTR_BYTES_OUT), msgb_l2len(msg));
switch (nsvc->ll) {
case GPRS_NS_LL_UDP:
@@ -533,7 +532,7 @@ static int gprs_ns_tx_simple(struct gprs_nsvc *nsvc, uint8_t pdu_type)
/*! Transmit a NS-RESET on a given NSVC
* \param[in] nsvc NS-VC used for transmission
- * \paam[in] cause Numeric NS cause value
+ * \param[in] cause Numeric NS cause value
*/
int gprs_ns_tx_reset(struct gprs_nsvc *nsvc, uint8_t cause)
{
@@ -643,7 +642,7 @@ int gprs_ns_tx_block(struct gprs_nsvc *nsvc, uint8_t cause)
/* be conservative and mark it as blocked even now! */
ns_mark_blocked(nsvc);
- rate_ctr_inc(&nsvc->ctrg->ctr[NS_CTR_BLOCKED]);
+ rate_ctr_inc(rate_ctr_group_get_ctr(nsvc->ctrg, NS_CTR_BLOCKED));
msg->l2h = msgb_put(msg, sizeof(*nsh));
nsh = (struct gprs_ns_hdr *) msg->l2h;
@@ -750,8 +749,7 @@ static void nsvc_start_timer(struct gprs_nsvc *nsvc, enum nsvc_timer_mode mode)
nsvc->nsei, get_value_string(timer_mode_strs, mode),
seconds);
- if (osmo_timer_pending(&nsvc->timer))
- osmo_timer_del(&nsvc->timer);
+ osmo_timer_del(&nsvc->timer);
osmo_gettimeofday(&nsvc->timer_started, NULL);
nsvc->timer_mode = mode;
@@ -781,15 +779,15 @@ static void gprs_ns_timer_cb(void *data)
switch (nsvc->timer_mode) {
case NSVC_TIMER_TNS_ALIVE:
/* Tns-alive case: we expired without response ! */
- rate_ctr_inc(&nsvc->ctrg->ctr[NS_CTR_LOST_ALIVE]);
+ rate_ctr_inc(rate_ctr_group_get_ctr(nsvc->ctrg, NS_CTR_LOST_ALIVE));
nsvc->alive_retries++;
if (nsvc->alive_retries >
nsvc->nsi->timeout[NS_TOUT_TNS_ALIVE_RETRIES]) {
/* mark as dead (and blocked unless IP-SNS) */
- rate_ctr_inc(&nsvc->ctrg->ctr[NS_CTR_DEAD]);
+ rate_ctr_inc(rate_ctr_group_get_ctr(nsvc->ctrg, NS_CTR_DEAD));
if (!nsvc->nsi->bss_sns_fi && nsvc->nsi->nsip.use_reset_block_unblock) {
ns_set_state(nsvc, NSE_S_BLOCKED);
- rate_ctr_inc(&nsvc->ctrg->ctr[NS_CTR_BLOCKED]);
+ rate_ctr_inc(rate_ctr_group_get_ctr(nsvc->ctrg, NS_CTR_BLOCKED));
} else
ns_set_state(nsvc, 0);
LOGP(DNS, LOGL_NOTICE,
@@ -816,7 +814,7 @@ static void gprs_ns_timer_cb(void *data)
nsvc_start_timer(nsvc, NSVC_TIMER_TNS_ALIVE);
break;
case NSVC_TIMER_TNS_RESET:
- rate_ctr_inc(&nsvc->ctrg->ctr[NS_CTR_LOST_RESET]);
+ rate_ctr_inc(rate_ctr_group_get_ctr(nsvc->ctrg, NS_CTR_LOST_RESET));
if (!(nsvc->state & NSE_S_RESET))
LOGP(DNS, LOGL_NOTICE,
"NSEI=%u Reset timed out but RESET flag is not set\n",
@@ -1076,7 +1074,7 @@ int gprs_ns_tx_sns_size_ack(struct gprs_nsvc *nsvc, uint8_t *cause)
* if the NS-VC is ALIVE and not BLOCKED. After that, it adds a NS
* header for the NS-UNITDATA message type and sends it off.
*
- * Section 9.2.10: transmit side / NS-UNITDATA-REQUEST primitive
+ * Section 9.2.10: transmit side / NS-UNITDATA-REQUEST primitive
*/
int gprs_ns_sendmsg(struct gprs_ns_inst *nsi, struct msgb *msg)
{
@@ -1251,7 +1249,7 @@ static int gprs_ns_rx_reset(struct gprs_nsvc **nsvc, struct msgb *msg)
ns_osmo_signal_dispatch_mismatch(*nsvc, msg,
NS_PDUT_RESET,
NS_IE_VCI);
- rate_ctr_inc(&(*nsvc)->ctrg->ctr[NS_CTR_INV_VCI]);
+ rate_ctr_inc(rate_ctr_group_get_ctr((*nsvc)->ctrg, NS_CTR_INV_VCI));
gprs_ns_tx_reset_ack(*nsvc);
return 0;
}
@@ -1277,14 +1275,14 @@ static int gprs_ns_rx_reset(struct gprs_nsvc **nsvc, struct msgb *msg)
ns_osmo_signal_dispatch_mismatch(*nsvc, msg,
NS_PDUT_RESET,
NS_IE_NSEI);
- rate_ctr_inc(&(*nsvc)->ctrg->ctr[NS_CTR_INV_NSEI]);
+ rate_ctr_inc(rate_ctr_group_get_ctr((*nsvc)->ctrg, NS_CTR_INV_NSEI));
rc = gprs_ns_tx_reset_ack(*nsvc);
CHECK_TX_RC(rc, *nsvc);
return 0;
}
/* NSEI has changed */
- rate_ctr_inc(&(*nsvc)->ctrg->ctr[NS_CTR_NSEI_CHG]);
+ rate_ctr_inc(rate_ctr_group_get_ctr((*nsvc)->ctrg, NS_CTR_NSEI_CHG));
(*nsvc)->nsei = nsei;
}
@@ -1292,7 +1290,7 @@ static int gprs_ns_rx_reset(struct gprs_nsvc **nsvc, struct msgb *msg)
ns_set_state(*nsvc, NSE_S_BLOCKED | NSE_S_ALIVE);
if (orig_nsvc) {
- rate_ctr_inc(&(*nsvc)->ctrg->ctr[NS_CTR_REPLACED]);
+ rate_ctr_inc(rate_ctr_group_get_ctr((*nsvc)->ctrg, NS_CTR_REPLACED));
ns_osmo_signal_dispatch_replaced(*nsvc, orig_nsvc);
/* Update the ll info fields */
@@ -1390,7 +1388,7 @@ static int gprs_ns_rx_reset_ack(struct gprs_nsvc **nsvc, struct msgb *msg)
ns_osmo_signal_dispatch_mismatch(*nsvc, msg,
NS_PDUT_RESET_ACK,
NS_IE_VCI);
- rate_ctr_inc(&(*nsvc)->ctrg->ctr[NS_CTR_INV_VCI]);
+ rate_ctr_inc(rate_ctr_group_get_ctr((*nsvc)->ctrg, NS_CTR_INV_VCI));
LOGP(DNS, LOGL_ERROR,
"NS RESET ACK Unknown NS-VCI %d (%s NSEI=%d) "
"from %s\n",
@@ -1401,7 +1399,7 @@ static int gprs_ns_rx_reset_ack(struct gprs_nsvc **nsvc, struct msgb *msg)
}
/* Notify others */
- rate_ctr_inc(&(*nsvc)->ctrg->ctr[NS_CTR_REPLACED]);
+ rate_ctr_inc(rate_ctr_group_get_ctr((*nsvc)->ctrg, NS_CTR_REPLACED));
ns_osmo_signal_dispatch_replaced(*nsvc, orig_nsvc);
/* Update the ll info fields */
@@ -1415,7 +1413,7 @@ static int gprs_ns_rx_reset_ack(struct gprs_nsvc **nsvc, struct msgb *msg)
ns_osmo_signal_dispatch_mismatch(*nsvc, msg,
NS_PDUT_RESET_ACK,
NS_IE_NSEI);
- rate_ctr_inc(&(*nsvc)->ctrg->ctr[NS_CTR_INV_NSEI]);
+ rate_ctr_inc(rate_ctr_group_get_ctr((*nsvc)->ctrg, NS_CTR_INV_NSEI));
LOGP(DNS, LOGL_ERROR,
"NS RESET ACK Unknown NSEI %d (NS-VCI=%u) from %s\n",
nsei, nsvci, gprs_ns_ll_str(*nsvc));
@@ -1423,14 +1421,14 @@ static int gprs_ns_rx_reset_ack(struct gprs_nsvc **nsvc, struct msgb *msg)
}
/* NSEI has changed */
- rate_ctr_inc(&(*nsvc)->ctrg->ctr[NS_CTR_NSEI_CHG]);
+ rate_ctr_inc(rate_ctr_group_get_ctr((*nsvc)->ctrg, NS_CTR_NSEI_CHG));
(*nsvc)->nsei = nsei;
}
/* Mark NS-VC as blocked and alive */
ns_set_state(*nsvc, NSE_S_BLOCKED | NSE_S_ALIVE);
ns_set_remote_state(*nsvc, NSE_S_BLOCKED | NSE_S_ALIVE);
- rate_ctr_inc(&(*nsvc)->ctrg->ctr[NS_CTR_BLOCKED]);
+ rate_ctr_inc(rate_ctr_group_get_ctr((*nsvc)->ctrg, NS_CTR_BLOCKED));
if ((*nsvc)->persistent || (*nsvc)->remote_end_is_sgsn) {
/* stop RESET timer */
osmo_timer_del(&(*nsvc)->timer);
@@ -1471,7 +1469,7 @@ static int gprs_ns_rx_block(struct gprs_nsvc *nsvc, struct msgb *msg)
//nsvci = (uint16_t *) TLVP_VAL(&tp, NS_IE_VCI);
ns_osmo_signal_dispatch(nsvc, S_NS_BLOCK, *cause);
- rate_ctr_inc(&nsvc->ctrg->ctr[NS_CTR_BLOCKED]);
+ rate_ctr_inc(rate_ctr_group_get_ctr(nsvc->ctrg, NS_CTR_BLOCKED));
return gprs_ns_tx_block_ack(nsvc);
}
@@ -1705,7 +1703,7 @@ int gprs_ns_vc_create(struct gprs_ns_inst *nsi, struct msgb *msg,
existing_nsvc->nsei = nsei;
/* Do statistics */
- rate_ctr_inc(&existing_nsvc->ctrg->ctr[NS_CTR_NSEI_CHG]);
+ rate_ctr_inc(rate_ctr_group_get_ctr(existing_nsvc->ctrg, NS_CTR_NSEI_CHG));
}
*new_nsvc = existing_nsvc;
@@ -1734,8 +1732,8 @@ int gprs_ns_process_msg(struct gprs_ns_inst *nsi, struct msgb *msg,
log_set_context(LOG_CTX_GB_NSVC, *nsvc);
/* Increment number of Incoming bytes */
- rate_ctr_inc(&(*nsvc)->ctrg->ctr[NS_CTR_PKTS_IN]);
- rate_ctr_add(&(*nsvc)->ctrg->ctr[NS_CTR_BYTES_IN], msgb_l2len(msg));
+ rate_ctr_inc(rate_ctr_group_get_ctr((*nsvc)->ctrg, NS_CTR_PKTS_IN));
+ rate_ctr_add(rate_ctr_group_get_ctr((*nsvc)->ctrg, NS_CTR_BYTES_IN), msgb_l2len(msg));
if (nsvc_is_not_used(*nsvc) && !ns_is_sns(nsh->pdu_type) && nsh->pdu_type != NS_PDUT_STATUS) {
LOGP(DNS, LOGL_NOTICE, "NSEI=%u Rx %s on unused/pre-configured endpoint, discarding\n",
@@ -1762,7 +1760,7 @@ int gprs_ns_process_msg(struct gprs_ns_inst *nsi, struct msgb *msg,
case NS_PDUT_ALIVE_ACK:
ns_mark_alive(*nsvc);
if ((*nsvc)->timer_mode == NSVC_TIMER_TNS_ALIVE)
- osmo_stat_item_set((*nsvc)->statg->items[NS_STAT_ALIVE_DELAY],
+ osmo_stat_item_set(osmo_stat_item_group_get_item((*nsvc)->statg, NS_STAT_ALIVE_DELAY),
nsvc_timer_elapsed_ms(*nsvc));
/* stop Tns-alive and start Tns-test */
nsvc_start_timer(*nsvc, NSVC_TIMER_TNS_TEST);
@@ -2068,7 +2066,8 @@ int gprs_ns_nsip_listen(struct gprs_ns_inst *nsi)
osmo_sock_init2_ofd(&nsi->nsip.fd, AF_INET, SOCK_DGRAM,
IPPROTO_UDP, inet_ntoa(in),
nsi->nsip.local_port, remote_str,
- nsi->nsip.remote_port, OSMO_SOCK_F_BIND | OSMO_SOCK_F_CONNECT);
+ nsi->nsip.remote_port, OSMO_SOCK_F_BIND | OSMO_SOCK_F_CONNECT |
+ OSMO_SOCK_F_BIND | OSMO_SOCK_F_DSCP(nsi->nsip.dscp));
LOGP(DNS, LOGL_NOTICE,
"Listening for nsip packets from %s:%u on %s:%u\n",
@@ -2076,7 +2075,8 @@ int gprs_ns_nsip_listen(struct gprs_ns_inst *nsi)
} else {
/* Accept UDP packets from any source IP/Port */
ret = osmo_sock_init_ofd(&nsi->nsip.fd, AF_INET, SOCK_DGRAM,
- IPPROTO_UDP, inet_ntoa(in), nsi->nsip.local_port, OSMO_SOCK_F_BIND);
+ IPPROTO_UDP, inet_ntoa(in), nsi->nsip.local_port,
+ OSMO_SOCK_F_BIND | OSMO_SOCK_F_DSCP(nsi->nsip.dscp));
LOGP(DNS, LOGL_NOTICE, "Listening for nsip packets on %s:%u\n", inet_ntoa(in), nsi->nsip.local_port);
}
@@ -2087,13 +2087,6 @@ int gprs_ns_nsip_listen(struct gprs_ns_inst *nsi)
return ret;
}
- ret = setsockopt(nsi->nsip.fd.fd, IPPROTO_IP, IP_TOS,
- &nsi->nsip.dscp, sizeof(nsi->nsip.dscp));
- if (ret < 0)
- LOGP(DNS, LOGL_ERROR,
- "Failed to set the DSCP to %d with ret(%d) errno(%d)\n",
- nsi->nsip.dscp, ret, errno);
-
LOGP(DNS, LOGL_NOTICE, "NS UDP socket at %s:%d\n", inet_ntoa(in), nsi->nsip.local_port);
return ret;
diff --git a/src/gb/gprs_ns2.c b/src/gb/gprs_ns2.c
index 57a08d1d..02d2266a 100644
--- a/src/gb/gprs_ns2.c
+++ b/src/gb/gprs_ns2.c
@@ -57,9 +57,7 @@
* Those mappings are administratively configured.
*
* This implementation has the following limitations:
- * - Only one NS-VC for each NSE: No load-sharing function
* - NSVCI 65535 and 65534 are reserved for internal use
- * - Only UDP is supported as of now, no frame relay support
* - There are no BLOCK and UNBLOCK timers (yet?)
*
* \file gprs_ns2.c */
@@ -74,6 +72,7 @@
#include <arpa/inet.h>
#include <osmocom/core/fsm.h>
+#include <osmocom/core/logging.h>
#include <osmocom/core/msgb.h>
#include <osmocom/core/rate_ctr.h>
#include <osmocom/core/socket.h>
@@ -138,7 +137,7 @@ static const struct tlv_definition ns_att_tlvdef2 = {
/* Section 10.3.2, Table 13 */
-static const struct value_string ns2_cause_str[] = {
+const struct value_string gprs_ns2_cause_strs[] = {
{ NS_CAUSE_TRANSIT_FAIL, "Transit network failure" },
{ NS_CAUSE_OM_INTERVENTION, "O&M intervention" },
{ NS_CAUSE_EQUIP_FAIL, "Equipment failure" },
@@ -160,39 +159,43 @@ static const struct value_string ns2_cause_str[] = {
{ 0, NULL }
};
-/*! Obtain a human-readable string for NS cause value */
-const char *gprs_ns2_cause_str(int cause)
-{
- enum ns_cause _cause = cause;
- return get_value_string(ns2_cause_str, _cause);
-}
+static const struct rate_ctr_desc ns_ctr_description[] = {
+ [NS_CTR_PKTS_IN] = { "packets:in", "Packets at NS Level ( In)" },
+ [NS_CTR_PKTS_OUT] = { "packets:out", "Packets at NS Level (Out)" },
+ [NS_CTR_PKTS_OUT_DROP] = { "packets:out:drop", "Dropped Packets (Out)" },
+ [NS_CTR_BYTES_IN] = { "bytes:in", "Bytes at NS Level ( In)" },
+ [NS_CTR_BYTES_OUT] = { "bytes:out", "Bytes at NS Level (Out)" },
+ [NS_CTR_BYTES_OUT_DROP] = { "bytes:out:drop", "Dropped Bytes (Out)" },
+ [NS_CTR_BLOCKED] = { "blocked", "NS-VC Block count " },
+ [NS_CTR_UNBLOCKED] = { "unblocked", "NS-VC Unblock count " },
+ [NS_CTR_DEAD] = { "dead", "NS-VC gone dead count " },
+ [NS_CTR_REPLACED] = { "replaced", "NS-VC replaced other count" },
+ [NS_CTR_NSEI_CHG] = { "nsei-chg", "NS-VC changed NSEI count " },
+ [NS_CTR_INV_VCI] = { "inv-nsvci", "NS-VCI was invalid count " },
+ [NS_CTR_INV_NSEI] = { "inv-nsei", "NSEI was invalid count " },
+ [NS_CTR_LOST_ALIVE] = { "lost:alive", "ALIVE ACK missing count " },
+ [NS_CTR_LOST_RESET] = { "lost:reset", "RESET ACK missing count " },
+};
-static const struct rate_ctr_desc nsvc_ctr_description[] = {
- { "packets:in", "Packets at NS Level ( In)" },
- { "packets:out","Packets at NS Level (Out)" },
- { "bytes:in", "Bytes at NS Level ( In)" },
- { "bytes:out", "Bytes at NS Level (Out)" },
- { "blocked", "NS-VC Block count " },
- { "dead", "NS-VC gone dead count " },
- { "replaced", "NS-VC replaced other count" },
- { "nsei-chg", "NS-VC changed NSEI count " },
- { "inv-nsvci", "NS-VCI was invalid count " },
- { "inv-nsei", "NSEI was invalid count " },
- { "lost:alive", "ALIVE ACK missing count " },
- { "lost:reset", "RESET ACK missing count " },
+static const struct rate_ctr_group_desc nse_ctrg_desc = {
+ .group_name_prefix = "ns:nse",
+ .group_description = "NSE Peer Statistics",
+ .num_ctr = ARRAY_SIZE(ns_ctr_description),
+ .ctr_desc = ns_ctr_description,
+ .class_id = OSMO_STATS_CLASS_PEER,
};
static const struct rate_ctr_group_desc nsvc_ctrg_desc = {
.group_name_prefix = "ns:nsvc",
.group_description = "NSVC Peer Statistics",
- .num_ctr = ARRAY_SIZE(nsvc_ctr_description),
- .ctr_desc = nsvc_ctr_description,
+ .num_ctr = ARRAY_SIZE(ns_ctr_description),
+ .ctr_desc = ns_ctr_description,
.class_id = OSMO_STATS_CLASS_PEER,
};
static const struct osmo_stat_item_desc nsvc_stat_description[] = {
- { "alive.delay", "ALIVE response time ", "ms", 16, 0 },
+ [NS_STAT_ALIVE_DELAY] = { "alive.delay", "ALIVE response time ", "ms", 16, 0 },
};
static const struct osmo_stat_item_group_desc nsvc_statg_desc = {
@@ -203,6 +206,44 @@ static const struct osmo_stat_item_group_desc nsvc_statg_desc = {
.class_id = OSMO_STATS_CLASS_PEER,
};
+const struct osmo_stat_item_desc nsbind_stat_description[] = {
+ [NS2_BIND_STAT_BACKLOG_LEN] = { "tx_backlog_length", "Transmit backlog length", "packets", 16, 0 },
+};
+
+static const struct osmo_stat_item_group_desc nsbind_statg_desc = {
+ .group_name_prefix = "ns.bind",
+ .group_description = "NS Bind Statistics",
+ .num_items = ARRAY_SIZE(nsbind_stat_description),
+ .item_desc = nsbind_stat_description,
+ .class_id = OSMO_STATS_CLASS_PEER,
+};
+
+const struct value_string gprs_ns2_aff_cause_prim_strs[] = {
+ { GPRS_NS2_AFF_CAUSE_VC_FAILURE, "NSVC failure" },
+ { GPRS_NS2_AFF_CAUSE_VC_RECOVERY, "NSVC recovery" },
+ { GPRS_NS2_AFF_CAUSE_FAILURE, "NSE failure" },
+ { GPRS_NS2_AFF_CAUSE_RECOVERY, "NSE recovery" },
+ { GPRS_NS2_AFF_CAUSE_SNS_CONFIGURED, "NSE SNS configured" },
+ { GPRS_NS2_AFF_CAUSE_SNS_FAILURE, "NSE SNS failure" },
+ { GPRS_NS2_AFF_CAUSE_SNS_NO_ENDPOINTS, "NSE SNS no endpoints"},
+ { GPRS_NS2_AFF_CAUSE_MTU_CHANGE, "NSE MTU changed" },
+ { 0, NULL }
+};
+
+const struct value_string gprs_ns2_prim_strs[] = {
+ { GPRS_NS2_PRIM_UNIT_DATA, "UNIT DATA" },
+ { GPRS_NS2_PRIM_CONGESTION, "CONGESTION" },
+ { GPRS_NS2_PRIM_STATUS, "STATUS" },
+ { 0, NULL }
+};
+
+const struct value_string gprs_ns2_lltype_strs[] = {
+ { GPRS_NS2_LL_UDP, "UDP" },
+ { GPRS_NS2_LL_FR_GRE, "FR_GRE" },
+ { GPRS_NS2_LL_FR, "FR" },
+ { 0, NULL }
+};
+
/*! string-format a given NS-VC into a user-supplied buffer.
* \param[in] buf user-allocated output buffer
* \param[in] buf_len size of user-allocated output buffer in bytes
@@ -210,23 +251,23 @@ static const struct osmo_stat_item_group_desc nsvc_statg_desc = {
* \return pointer to buf on success; NULL on error */
char *gprs_ns2_ll_str_buf(char *buf, size_t buf_len, struct gprs_ns2_vc *nsvc)
{
- struct osmo_sockaddr *local;
- struct osmo_sockaddr *remote;
+ const struct osmo_sockaddr *local;
+ const struct osmo_sockaddr *remote;
struct osmo_sockaddr_str local_str;
struct osmo_sockaddr_str remote_str;
if (!buf_len)
return NULL;
- switch (nsvc->ll) {
- case GPRS_NS_LL_UDP:
+ switch (nsvc->nse->ll) {
+ case GPRS_NS2_LL_UDP:
if (!gprs_ns2_is_ip_bind(nsvc->bind)) {
buf[0] = '\0';
return buf;
}
local = gprs_ns2_ip_bind_sockaddr(nsvc->bind);
- remote = gprs_ns2_ip_vc_sockaddr(nsvc);
+ remote = gprs_ns2_ip_vc_remote(nsvc);
if (osmo_sockaddr_str_from_sockaddr(&local_str, &local->u.sas))
strcpy(local_str.ip, "invalid");
if (osmo_sockaddr_str_from_sockaddr(&remote_str, &remote->u.sas))
@@ -242,14 +283,15 @@ char *gprs_ns2_ll_str_buf(char *buf, size_t buf_len, struct gprs_ns2_vc *nsvc)
local_str.ip, local_str.port,
remote_str.ip, remote_str.port);
break;
- case GPRS_NS_LL_FR_GRE:
+ case GPRS_NS2_LL_FR_GRE:
snprintf(buf, buf_len, "frgre)");
break;
- case GPRS_NS_LL_E1:
- snprintf(buf, buf_len, "e1)");
+ case GPRS_NS2_LL_FR:
+ snprintf(buf, buf_len, "fr)netif: %s dlci: %u", gprs_ns2_fr_bind_netif(nsvc->bind),
+ gprs_ns2_fr_nsvc_dlci(nsvc));
break;
default:
- buf[0] = '\0';
+ snprintf(buf, buf_len, "unknown)");
break;
}
@@ -282,61 +324,223 @@ char *gprs_ns2_ll_str_c(const void *ctx, struct gprs_ns2_vc *nsvc)
return gprs_ns2_ll_str_buf(buf, NS2_LL_MAX_STR, nsvc);
}
+/*! Return the current state name of a given NS-VC to a thread-local static buffer.
+ * \param[in] nsvc NS-VC to return the state of
+ * \return pointer to the string on success; NULL on error */
+const char *gprs_ns2_nsvc_state_name(struct gprs_ns2_vc *nsvc)
+{
+ return osmo_fsm_inst_state_name(nsvc->fi);
+}
+
+/* select a signalling NSVC and respect sig_counter
+ * param[out] reset_counter - all counter has to be resetted to their signal weight
+ * return the chosen nsvc or NULL
+ */
+static struct gprs_ns2_vc *ns2_load_sharing_signal(struct gprs_ns2_nse *nse)
+{
+ struct gprs_ns2_vc *nsvc = NULL, *last = NULL, *tmp;
+
+ llist_for_each_entry(tmp, &nse->nsvc, list) {
+ if (tmp->sig_weight == 0)
+ continue;
+ if (!ns2_vc_is_unblocked(tmp))
+ continue;
+ if (tmp->sig_counter == 0) {
+ last = tmp;
+ continue;
+ }
+
+ tmp->sig_counter--;
+ nsvc = tmp;
+ break;
+ }
+
+ /* all counter were zero, but there are valid nsvc */
+ if (!nsvc && last) {
+ llist_for_each_entry(tmp, &nse->nsvc, list) {
+ tmp->sig_counter = tmp->sig_weight;
+ }
+
+ last->sig_counter--;
+ return last;
+ } else {
+ return nsvc;
+ }
+}
+
+/* 4.4.1 Load Sharing function for the Frame Relay Sub-Network */
+static struct gprs_ns2_vc *ns2_load_sharing_modulo(
+ struct gprs_ns2_nse *nse,
+ uint16_t bvci,
+ uint32_t load_selector)
+{
+ struct gprs_ns2_vc *tmp;
+ uint32_t mod;
+ uint32_t i = 0;
+
+ if (nse->nsvc_count == 0)
+ return NULL;
+
+ mod = (bvci + load_selector) % nse->nsvc_count;
+ llist_for_each_entry(tmp, &nse->nsvc, list) {
+ if (!ns2_vc_is_unblocked(tmp))
+ continue;
+ if (i == mod)
+ return tmp;
+ i++;
+ }
+
+ return NULL;
+}
+
+/* 4.4.2 Load Sharing function for the IP Sub-Network
+ *
+ * Implement a simple approach for UDP load sharing of data weight based on the modulo of the lsp.
+ *
+ * E.g. 3 NSVC: 1st weight 5, 2nd weight 3, 3rd weight 1, lsp = 3.
+ * sum all weights = 9
+ * target_weight = lsp % sum = 3
+ *
+ * 1st NSVC will be the target for 0-4
+ * 2nd NSVC will be the target for 5-7
+ * 3rd NSVC will be the target for 8
+ *
+ * The 1st NSVC will be used.
+ * E.g. lsp = 7. The 2nd NSVC will used.
+ */
+static struct gprs_ns2_vc *ns2_load_sharing_weight_modulo(
+ struct gprs_ns2_nse *nse,
+ uint16_t bvci,
+ uint32_t load_selector)
+{
+ struct gprs_ns2_vc *tmp;
+ uint32_t mod;
+ uint32_t i = 0;
+
+ if (nse->nsvc_count == 0)
+ return NULL;
+
+ mod = (bvci + load_selector) % nse->sum_data_weight;
+ llist_for_each_entry(tmp, &nse->nsvc, list) {
+ if (tmp->data_weight == 0)
+ continue;
+ if (!ns2_vc_is_unblocked(tmp))
+ continue;
+ if (i == mod || mod < i + tmp->data_weight)
+ return tmp;
+ i += tmp->data_weight;
+ }
+
+ return NULL;
+}
+
+/* pick the first available data NSVC - no load sharing */
+struct gprs_ns2_vc *ns2_load_sharing_first(struct gprs_ns2_nse *nse)
+{
+ struct gprs_ns2_vc *nsvc = NULL, *tmp;
+
+ llist_for_each_entry(tmp, &nse->nsvc, list) {
+ if (!ns2_vc_is_unblocked(tmp))
+ continue;
+ if (tmp->data_weight == 0)
+ continue;
+
+ nsvc = tmp;
+ break;
+ }
+
+ return nsvc;
+}
+
+
+static struct gprs_ns2_vc *ns2_load_sharing(
+ struct gprs_ns2_nse *nse,
+ uint16_t bvci,
+ uint32_t link_selector)
+{
+ struct gprs_ns2_vc *nsvc = NULL;
+
+ switch (nse->ll) {
+ case GPRS_NS2_LL_FR:
+ nsvc = ns2_load_sharing_modulo(nse, bvci, link_selector);
+ break;
+ case GPRS_NS2_LL_UDP:
+ default:
+ if (bvci == 0) {
+ /* signalling */
+ nsvc = ns2_load_sharing_signal(nse);
+ } else {
+ /* data with load sharing parameter */
+ nsvc = ns2_load_sharing_weight_modulo(nse, bvci, link_selector);
+ }
+ break;
+ }
+
+ return nsvc;
+}
+
/*! Receive a primitive from the NS User (Gb).
* \param[in] nsi NS instance to which the primitive is issued
* \param[in] oph The primitive
* \return 0 on success; negative on error */
int gprs_ns2_recv_prim(struct gprs_ns2_inst *nsi, struct osmo_prim_hdr *oph)
{
- /* TODO: implement load distribution function */
/* TODO: implement resource distribution */
/* TODO: check for empty PDUs which can be sent to Request/Confirm
* the IP endpoint */
struct osmo_gprs_ns2_prim *nsp;
struct gprs_ns2_nse *nse = NULL;
- struct gprs_ns2_vc *nsvc = NULL, *tmp;
+ struct gprs_ns2_vc *nsvc = NULL;
uint16_t bvci, nsei;
uint8_t sducontrol = 0;
+ int rc = 0;
- if (oph->sap != SAP_NS)
- return -EINVAL;
+ if (oph->sap != SAP_NS) {
+ rc = -EINVAL;
+ goto out;
+ }
nsp = container_of(oph, struct osmo_gprs_ns2_prim, oph);
- if (oph->operation != PRIM_OP_REQUEST || oph->primitive != PRIM_NS_UNIT_DATA)
- return -EINVAL;
+ if (oph->operation != PRIM_OP_REQUEST || oph->primitive != GPRS_NS2_PRIM_UNIT_DATA) {
+ rc = -EINVAL;
+ goto out;
+ }
- if (!oph->msg)
- return -EINVAL;
+ if (!oph->msg) {
+ rc = -EINVAL;
+ goto out;
+ }
bvci = nsp->bvci;
nsei = nsp->nsei;
nse = gprs_ns2_nse_by_nsei(nsi, nsei);
- if (!nse)
- return -EINVAL;
-
- llist_for_each_entry(tmp, &nse->nsvc, list) {
- if (!gprs_ns2_vc_is_unblocked(tmp))
- continue;
- if (bvci == 0 && tmp->sig_weight == 0)
- continue;
- if (bvci != 0 && tmp->data_weight == 0)
- continue;
+ if (!nse) {
+ rc = -EINVAL;
+ goto out;
+ }
- nsvc = tmp;
+ if (!nse->alive) {
+ goto out;
}
+ nsvc = ns2_load_sharing(nse, bvci, nsp->u.unitdata.link_selector);
+
/* TODO: send a status primitive back */
if (!nsvc)
- return 0;
+ goto out;
- if (nsp->u.unitdata.change == NS_ENDPOINT_REQUEST_CHANGE)
+ if (nsp->u.unitdata.change == GPRS_NS2_ENDPOINT_REQUEST_CHANGE)
sducontrol = 1;
- else if (nsp->u.unitdata.change == NS_ENDPOINT_CONFIRM_CHANGE)
+ else if (nsp->u.unitdata.change == GPRS_NS2_ENDPOINT_CONFIRM_CHANGE)
sducontrol = 2;
return ns2_tx_unit_data(nsvc, bvci, sducontrol, oph->msg);
+
+out:
+ msgb_free(oph->msg);
+ return rc;
}
/*! Send a STATUS.ind primitive to the specified NS instance user.
@@ -344,27 +548,51 @@ int gprs_ns2_recv_prim(struct gprs_ns2_inst *nsi, struct osmo_prim_hdr *oph)
* \param[in] nsei NSEI to which the statue relates
* \param[in] bvci BVCI to which the status relates
* \param[in] cause The cause of the status */
-void ns2_prim_status_ind(struct gprs_ns2_inst *nsi,
- uint16_t nsei, uint16_t bvci,
+void ns2_prim_status_ind(struct gprs_ns2_nse *nse,
+ struct gprs_ns2_vc *nsvc,
+ uint16_t bvci,
enum gprs_ns2_affecting_cause cause)
{
+ char nsvc_str[NS2_LL_MAX_STR];
struct osmo_gprs_ns2_prim nsp = {};
- nsp.nsei = nsei;
+ nsp.nsei = nse->nsei;
nsp.bvci = bvci;
nsp.u.status.cause = cause;
- nsp.u.status.transfer = -1;
- osmo_prim_init(&nsp.oph, SAP_NS, PRIM_NS_STATUS,
- PRIM_OP_INDICATION, NULL);
- nsi->cb(&nsp.oph, nsi->cb_data);
+ nsp.u.status.transfer = ns2_count_transfer_cap(nse, bvci);
+ nsp.u.status.first = nse->first;
+ nsp.u.status.persistent = nse->persistent;
+ if (nse->mtu < 4)
+ nsp.u.status.mtu = 0;
+ else
+ nsp.u.status.mtu = nse->mtu - 4; /* 1 Byte NS PDU type, 1 Byte NS SDU control, 2 Byte BVCI */
+
+ if (nsvc) {
+ nsp.u.status.nsvc = gprs_ns2_ll_str_buf(nsvc_str, sizeof(nsvc_str), nsvc);
+ LOGNSVC(nsvc, LOGL_NOTICE, "NS-STATUS.ind(bvci=%05u): cause=%s, transfer=%d, first=%d, mtu=%d\n",
+ nsp.bvci, gprs_ns2_aff_cause_prim_str(nsp.u.status.cause),
+ nsp.u.status.transfer, nsp.u.status.first, nsp.u.status.mtu);
+ } else {
+ LOGNSE(nse, LOGL_NOTICE, "NS-STATUS.ind(bvci=%05u): cause=%s, transfer=%d, first=%d, mtu=%d\n",
+ nsp.bvci, gprs_ns2_aff_cause_prim_str(nsp.u.status.cause),
+ nsp.u.status.transfer, nsp.u.status.first, nsp.u.status.mtu);
+ }
+
+ osmo_prim_init(&nsp.oph, SAP_NS, GPRS_NS2_PRIM_STATUS, PRIM_OP_INDICATION, NULL);
+ nse->nsi->cb(&nsp.oph, nse->nsi->cb_data);
}
/*! Allocate a NS-VC within the given bind + NSE.
* \param[in] bind The 'bind' on which we operate
* \param[in] nse The NS Entity on which we operate
* \param[in] initiater - if this is an incoming remote (!initiater) or a local outgoing connection (initater)
+ * \param[in] id - human-readable identifier
* \return newly allocated NS-VC on success; NULL on error */
-struct gprs_ns2_vc *ns2_vc_alloc(struct gprs_ns2_vc_bind *bind, struct gprs_ns2_nse *nse, bool initiater)
+struct gprs_ns2_vc *ns2_vc_alloc(struct gprs_ns2_vc_bind *bind, struct gprs_ns2_nse *nse, bool initiater,
+ enum gprs_ns2_vc_mode vc_mode, const char *id)
{
+ /* Sanity check */
+ OSMO_ASSERT(bind->ll == nse->ll);
+
struct gprs_ns2_vc *nsvc = talloc_zero(bind, struct gprs_ns2_vc);
if (!nsvc)
@@ -372,24 +600,29 @@ struct gprs_ns2_vc *ns2_vc_alloc(struct gprs_ns2_vc_bind *bind, struct gprs_ns2_
nsvc->bind = bind;
nsvc->nse = nse;
- nsvc->mode = bind->vc_mode;
+ nsvc->mode = vc_mode;
nsvc->sig_weight = 1;
nsvc->data_weight = 1;
- nsvc->ctrg = rate_ctr_group_alloc(nsvc, &nsvc_ctrg_desc, bind->nsi->rate_ctr_idx);
+ nsvc->ctrg = rate_ctr_group_alloc(nsvc, &nsvc_ctrg_desc, bind->nsi->nsvc_rate_ctr_idx);
if (!nsvc->ctrg) {
goto err;
}
- nsvc->statg = osmo_stat_item_group_alloc(nsvc, &nsvc_statg_desc, bind->nsi->rate_ctr_idx);
+ nsvc->statg = osmo_stat_item_group_alloc(nsvc, &nsvc_statg_desc, bind->nsi->nsvc_rate_ctr_idx);
if (!nsvc->statg)
goto err_group;
- if (!gprs_ns2_vc_fsm_alloc(nsvc, NULL, initiater))
+ if (!ns2_vc_fsm_alloc(nsvc, id, initiater))
goto err_statg;
- bind->nsi->rate_ctr_idx++;
+ bind->nsi->nsvc_rate_ctr_idx++;
- llist_add(&nsvc->list, &nse->nsvc);
- llist_add(&nsvc->blist, &bind->nsvc);
+ rate_ctr_group_set_name(nsvc->ctrg, id);
+ osmo_stat_item_group_set_name(nsvc->statg, id);
+
+ llist_add_tail(&nsvc->list, &nse->nsvc);
+ llist_add_tail(&nsvc->blist, &bind->nsvc);
+ osmo_clock_gettime(CLOCK_MONOTONIC, &nsvc->ts_alive_change);
+ ns2_nse_update_mtu(nse);
return nsvc;
@@ -407,11 +640,10 @@ err:
* \param[in] nsvc NS-VC to destroy */
void gprs_ns2_free_nsvc(struct gprs_ns2_vc *nsvc)
{
- if (!nsvc)
+ if (!nsvc || nsvc->freed)
return;
-
- ns2_prim_status_ind(nsvc->nse->nsi, nsvc->nse->nsei,
- 0, NS_AFF_CAUSE_VC_FAILURE);
+ nsvc->freed = true;
+ ns2_prim_status_ind(nsvc->nse, nsvc, 0, GPRS_NS2_AFF_CAUSE_VC_FAILURE);
llist_del(&nsvc->list);
llist_del(&nsvc->blist);
@@ -420,7 +652,7 @@ void gprs_ns2_free_nsvc(struct gprs_ns2_vc *nsvc)
ns2_nse_notify_unblocked(nsvc, false);
/* check if sns is using this VC */
- ns2_sns_free_nsvc(nsvc);
+ ns2_sns_replace_nsvc(nsvc);
osmo_fsm_inst_term(nsvc->fi, OSMO_FSM_TERM_REQUEST, NULL);
/* let the driver/bind clean up it's internal state */
@@ -433,8 +665,34 @@ void gprs_ns2_free_nsvc(struct gprs_ns2_vc *nsvc)
talloc_free(nsvc);
}
+void ns2_free_nsvcs(struct gprs_ns2_nse *nse)
+{
+ struct gprs_ns2_vc *nsvc;
+
+ /* prevent recursive free() when the user reacts on a down event and free() a second time */
+ while (!llist_empty(&nse->nsvc)) {
+ nsvc = llist_first_entry(&nse->nsvc, struct gprs_ns2_vc, list);
+ gprs_ns2_free_nsvc(nsvc);
+ }
+}
+
+/*! Destroy/release all NS-VC of given NSE
+ * \param[in] nse NSE
+ */
+void gprs_ns2_free_nsvcs(struct gprs_ns2_nse *nse)
+{
+ if (!nse || nse->freed)
+ return;
+
+ if (nse->bss_sns_fi) {
+ osmo_fsm_inst_dispatch(nse->bss_sns_fi, NS2_SNS_EV_REQ_FREE_NSVCS, NULL);
+ } else {
+ ns2_free_nsvcs(nse);
+ }
+}
+
/*! Allocate a message buffer for use with the NS2 stack. */
-struct msgb *gprs_ns2_msgb_alloc(void)
+struct msgb *ns2_msgb_alloc(void)
{
struct msgb *msg = msgb_alloc_headroom(NS_ALLOC_SIZE, NS_ALLOC_HEADROOM,
"GPRS/NS");
@@ -453,7 +711,7 @@ struct msgb *gprs_ns2_msgb_alloc(void)
* \return 0 on success */
static int reject_status_msg(struct msgb *orig_msg, struct tlv_parsed *tp, struct msgb **reject, enum ns_cause cause)
{
- struct msgb *msg = gprs_ns2_msgb_alloc();
+ struct msgb *msg = ns2_msgb_alloc();
struct gprs_ns_hdr *nsh;
bool have_vci = false;
uint8_t _cause = cause;
@@ -462,7 +720,7 @@ static int reject_status_msg(struct msgb *orig_msg, struct tlv_parsed *tp, struc
if (!msg)
return -ENOMEM;
- if (TLVP_PRESENT(tp, NS_IE_NSEI)) {
+ if (TLVP_PRES_LEN(tp, NS_IE_NSEI, 2)) {
nsei = tlvp_val16be(tp, NS_IE_NSEI);
LOGP(DLNS, LOGL_NOTICE, "NSEI=%u Rejecting message without NSVCI. Tx NS STATUS (cause=%s)\n",
@@ -474,7 +732,7 @@ static int reject_status_msg(struct msgb *orig_msg, struct tlv_parsed *tp, struc
nsh->pdu_type = NS_PDUT_STATUS;
msgb_tvlv_put(msg, NS_IE_CAUSE, 1, &_cause);
- have_vci = TLVP_PRESENT(tp, NS_IE_VCI);
+ have_vci = TLVP_PRES_LEN(tp, NS_IE_VCI, 2);
/* Section 9.2.7.1: Static conditions for NS-VCI */
if (cause == NS_CAUSE_NSVC_BLOCKED ||
@@ -543,51 +801,137 @@ struct gprs_ns2_vc *gprs_ns2_nsvc_by_nsvci(struct gprs_ns2_inst *nsi, uint16_t n
/*! Create a NS Entity within given NS instance.
* \param[in] nsi NS instance in which to create NS Entity
* \param[in] nsei NS Entity Identifier of to-be-created NSE
+ * \param[in] ip_sns_role_sgsn Does local side implement SGSN role?
* \returns newly-allocated NS-E in successful case; NULL on error */
-struct gprs_ns2_nse *gprs_ns2_create_nse(struct gprs_ns2_inst *nsi, uint16_t nsei)
+struct gprs_ns2_nse *gprs_ns2_create_nse2(struct gprs_ns2_inst *nsi, uint16_t nsei,
+ enum gprs_ns2_ll linklayer, enum gprs_ns2_dialect dialect,
+ bool ip_sns_role_sgsn)
{
struct gprs_ns2_nse *nse;
nse = gprs_ns2_nse_by_nsei(nsi, nsei);
if (nse) {
- LOGP(DLNS, LOGL_ERROR, "NSEI:%u Can not create a NSE with already taken NSEI\n", nsei);
+ LOGNSE(nse, LOGL_ERROR, "Can not create a NSE with already taken NSEI\n");
return nse;
}
nse = talloc_zero(nsi, struct gprs_ns2_nse);
if (!nse)
return NULL;
+ nse->dialect = GPRS_NS2_DIALECT_UNDEF;
+ nse->ip_sns_role_sgsn = ip_sns_role_sgsn;
+
+ if (ns2_nse_set_dialect(nse, dialect) < 0) {
+ talloc_free(nse);
+ return NULL;
+ }
+
+ nse->ctrg = rate_ctr_group_alloc(nse, &nse_ctrg_desc, nsei);
+ if (!nse->ctrg) {
+ talloc_free(nse);
+ return NULL;
+ }
+ nse->ll = linklayer;
nse->nsei = nsei;
nse->nsi = nsi;
- llist_add(&nse->list, &nsi->nse);
+ nse->first = true;
+ nse->mtu = 0;
+ llist_add_tail(&nse->list, &nsi->nse);
INIT_LLIST_HEAD(&nse->nsvc);
+ osmo_clock_gettime(CLOCK_MONOTONIC, &nse->ts_alive_change);
return nse;
}
+int ns2_nse_set_dialect(struct gprs_ns2_nse *nse, enum gprs_ns2_dialect dialect)
+{
+ char sns[16];
+
+ if (nse->dialect == dialect)
+ return 0;
+
+ switch (nse->dialect) {
+ case GPRS_NS2_DIALECT_UNDEF:
+ if (dialect == GPRS_NS2_DIALECT_SNS) {
+ snprintf(sns, sizeof(sns), "NSE%05u-SNS", nse->nsei);
+ if (nse->ip_sns_role_sgsn)
+ nse->bss_sns_fi = ns2_sns_sgsn_fsm_alloc(nse, sns);
+ else
+ nse->bss_sns_fi = ns2_sns_bss_fsm_alloc(nse, sns);
+ if (!nse->bss_sns_fi)
+ return -1;
+ }
+ nse->dialect = dialect;
+ break;
+ default:
+ if (dialect == GPRS_NS2_DIALECT_UNDEF) {
+ if (nse->bss_sns_fi)
+ osmo_fsm_inst_term(nse->bss_sns_fi, OSMO_FSM_TERM_REQUEST, NULL);
+ nse->bss_sns_fi = NULL;
+ nse->dialect = GPRS_NS2_DIALECT_UNDEF;
+ } else {
+ /* we don't support arbitrary changes without going through UNDEF first */
+ return -EPERM;
+ }
+ }
+
+ return 0;
+}
+
+/*! Create a NS Entity within given NS instance.
+ * \param[in] nsi NS instance in which to create NS Entity
+ * \param[in] nsei NS Entity Identifier of to-be-created NSE
+ * \returns newly-allocated NS-E in successful case; NULL on error */
+struct gprs_ns2_nse *gprs_ns2_create_nse(struct gprs_ns2_inst *nsi, uint16_t nsei,
+ enum gprs_ns2_ll linklayer, enum gprs_ns2_dialect dialect)
+{
+ return gprs_ns2_create_nse2(nsi, nsei, linklayer, dialect, false);
+}
+
+/*! Return the NSEI
+ * \param[in] nse NS Entity
+ * \return the nsei.
+ */
+uint16_t gprs_ns2_nse_nsei(struct gprs_ns2_nse *nse)
+{
+ return nse->nsei;
+}
+
/*! Destroy given NS Entity.
* \param[in] nse NS Entity to destroy */
void gprs_ns2_free_nse(struct gprs_ns2_nse *nse)
{
- struct gprs_ns2_vc *nsvc, *tmp;
-
- if (!nse)
+ if (!nse || nse->freed)
return;
- llist_for_each_entry_safe(nsvc, tmp, &nse->nsvc, list) {
- gprs_ns2_free_nsvc(nsvc);
+ nse->freed = true;
+ nse->alive = false;
+ if (nse->bss_sns_fi) {
+ osmo_fsm_inst_term(nse->bss_sns_fi, OSMO_FSM_TERM_REQUEST, NULL);
+ nse->bss_sns_fi = NULL;
}
- ns2_prim_status_ind(nse->nsi, nse->nsei,
- 0, NS_AFF_CAUSE_FAILURE);
+ gprs_ns2_free_nsvcs(nse);
+ ns2_prim_status_ind(nse, NULL, 0, GPRS_NS2_AFF_CAUSE_FAILURE);
+ rate_ctr_group_free(nse->ctrg);
+ ns2_free_nsvcs(nse);
llist_del(&nse->list);
- if (nse->bss_sns_fi)
- osmo_fsm_inst_term(nse->bss_sns_fi, OSMO_FSM_TERM_REQUEST, NULL);
talloc_free(nse);
}
+void gprs_ns2_free_nses(struct gprs_ns2_inst *nsi)
+{
+ struct gprs_ns2_nse *nse;
+
+ /* prevent recursive free() when the user reacts on a down event and free() a second time */
+ while (!llist_empty(&nsi->nse)) {
+ nse = llist_first_entry(&nsi->nse, struct gprs_ns2_nse, list);
+ gprs_ns2_free_nse(nse);
+ }
+}
+
static inline int ns2_tlv_parse(struct tlv_parsed *dec,
const uint8_t *buf, int buf_len, uint8_t lv_tag,
uint8_t lv_tag2)
@@ -600,125 +944,229 @@ static inline int ns2_tlv_parse(struct tlv_parsed *dec,
return rc;
}
+static enum ns2_cs ns2_create_vc_sns(struct gprs_ns2_vc_bind *bind,
+ const struct osmo_sockaddr *remote,
+ struct gprs_ns2_vc **success, uint16_t nsei)
+{
+ struct gprs_ns2_vc *nsvc;
+ struct gprs_ns2_nse *nse;
+
+ nsvc = gprs_ns2_nsvc_by_sockaddr_bind(bind, remote);
+ /* ns2_create_vc() is only called if no NS-VC could be found */
+ OSMO_ASSERT(!nsvc);
+
+ nse = gprs_ns2_nse_by_nsei(bind->nsi, nsei);
+ if (!nse) {
+ if (!bind->accept_sns) {
+ struct osmo_sockaddr_str remote_str;
+ osmo_sockaddr_str_from_sockaddr(&remote_str, &remote->u.sas);
+ /* no dynamic creation of IP-SNS NSE permitted */
+ LOGP(DLNS, LOGL_ERROR, "[%s]:%u: Dynamic creation of NSE(%05u) via IP-SNS not "
+ "permitted. Check your config.\n", remote_str.ip, remote_str.port, nsei);
+ return NS2_CS_ERROR;
+ }
+ nse = gprs_ns2_create_nse2(bind->nsi, nsei, bind->ll, GPRS_NS2_DIALECT_SNS, true);
+ if (!nse) {
+ LOGP(DLNS, LOGL_ERROR, "Failed to create NSE(%05u)\n", nsei);
+ return NS2_CS_ERROR;
+ }
+ /* add configured list of default binds; if that fails, use only current bind */
+ if (!ns2_sns_add_sns_default_binds(nse))
+ gprs_ns2_sns_add_bind(nse, bind);
+ } else {
+ /* nsei already known */
+ if (nse->ll != bind->ll) {
+ LOGNSE(nse, LOGL_ERROR, "Received NS-RESET with wrong linklayer(%s)"
+ " for already known NSE(%s)\n", gprs_ns2_lltype_str(bind->ll),
+ gprs_ns2_lltype_str(nse->ll));
+ return NS2_CS_SKIPPED;
+ }
+ }
+
+ nsvc = ns2_ip_bind_connect(bind, nse, remote);
+ if (!nsvc)
+ return NS2_CS_SKIPPED;
+
+ nsvc->nsvci_is_valid = false;
+
+ *success = nsvc;
+
+ return NS2_CS_CREATED;
+}
/*! Create a new NS-VC based on a [received] message. Depending on the bind it might create a NSE.
* \param[in] bind the bind through which msg was received
* \param[in] msg the actual received message
+ * \param[in] remote address of remote peer sending message
* \param[in] logname A name to describe the VC. E.g. ip address pair
* \param[out] reject A message filled to be sent back. Only used in failure cases.
* \param[out] success A pointer which will be set to the new VC on success
* \return enum value indicating the status, e.g. GPRS_NS2_CS_CREATED */
-enum gprs_ns2_cs ns2_create_vc(struct gprs_ns2_vc_bind *bind,
- struct msgb *msg,
- const char *logname,
- struct msgb **reject,
- struct gprs_ns2_vc **success)
+enum ns2_cs ns2_create_vc(struct gprs_ns2_vc_bind *bind,
+ struct msgb *msg,
+ const struct osmo_sockaddr *remote,
+ const char *logname,
+ struct msgb **reject,
+ struct gprs_ns2_vc **success)
{
struct gprs_ns_hdr *nsh = (struct gprs_ns_hdr *)msg->l2h;
struct tlv_parsed tp;
struct gprs_ns2_vc *nsvc;
struct gprs_ns2_nse *nse;
+ enum gprs_ns2_dialect dialect;
+ enum gprs_ns2_vc_mode vc_mode;
uint16_t nsvci;
uint16_t nsei;
+ const struct osmo_sockaddr *local;
+ char idbuf[256], tmp[INET6_ADDRSTRLEN + 8];
- int rc;
+ int rc, tlv;
if (msg->len < sizeof(struct gprs_ns_hdr))
- return GPRS_NS2_CS_ERROR;
+ return NS2_CS_ERROR;
+
+ /* parse the tlv early to allow reject status msg to
+ * work with valid tp.
+ * Ignore the return code until the pdu type is parsed because
+ * an unknown pdu type should be ignored */
+ tlv = ns2_tlv_parse(&tp, nsh->data,
+ msgb_l2len(msg) - sizeof(*nsh), 0, 0);
+
+ if (bind->ll == GPRS_NS2_LL_UDP && nsh->pdu_type == SNS_PDUT_SIZE && tlv >= 0) {
+ uint16_t nsei;
+
+ if (!TLVP_PRES_LEN(&tp, NS_IE_NSEI, 2)) {
+ rc = reject_status_msg(msg, &tp, reject, NS_CAUSE_MISSING_ESSENT_IE);
+ if (rc < 0)
+ LOGP(DLNS, LOGL_ERROR, "Failed to generate reject message (%d)\n", rc);
+ return NS2_CS_REJECTED;
+ }
+ nsei = tlvp_val16be(&tp, NS_IE_NSEI);
+ /* Create NS-VC, and if required, even NSE dynamically */
+ return ns2_create_vc_sns(bind, remote, success, nsei);
+ }
- if (nsh->pdu_type == NS_PDUT_STATUS) {
+ switch (nsh->pdu_type) {
+ case NS_PDUT_STATUS:
/* Do not respond, see 3GPP TS 08.16, 7.5.1 */
LOGP(DLNS, LOGL_INFO, "Ignoring NS STATUS from %s "
"for non-existing NS-VC\n",
logname);
- return GPRS_NS2_CS_SKIPPED;
- }
-
- if (nsh->pdu_type == NS_PDUT_ALIVE_ACK) {
+ return NS2_CS_SKIPPED;
+ case NS_PDUT_ALIVE_ACK:
/* Ignore this, see 3GPP TS 08.16, 7.4.1 */
LOGP(DLNS, LOGL_INFO, "Ignoring NS ALIVE ACK from %s "
"for non-existing NS-VC\n",
logname);
- return GPRS_NS2_CS_SKIPPED;
- }
-
- if (nsh->pdu_type == NS_PDUT_RESET_ACK) {
+ return NS2_CS_SKIPPED;
+ case NS_PDUT_RESET_ACK:
/* Ignore this, see 3GPP TS 08.16, 7.3.1 */
LOGP(DLNS, LOGL_INFO, "Ignoring NS RESET ACK from %s "
"for non-existing NS-VC\n",
logname);
- return GPRS_NS2_CS_SKIPPED;
- }
-
- if (bind->vc_mode == NS2_VC_MODE_BLOCKRESET) {
- /* Only the RESET procedure creates a new NSVC */
- if (nsh->pdu_type != NS_PDUT_RESET) {
- rc = reject_status_msg(msg, &tp, reject, NS_CAUSE_PDU_INCOMP_PSTATE);
-
- if (rc < 0) {
- LOGP(DLNS, LOGL_ERROR, "Failed to generate reject message (%d)\n", rc);
- return rc;
- }
- return GPRS_NS2_CS_REJECTED;
+ return NS2_CS_SKIPPED;
+ case NS_PDUT_RESET:
+ /* accept PDU RESET when vc_mode matches */
+ if (bind->accept_ipaccess) {
+ dialect = GPRS_NS2_DIALECT_IPACCESS;
+ break;
}
- } else { /* NS2_VC_MODE_ALIVE */
- /* Only the ALIVE procedure creates a new NSVC */
- if (nsh->pdu_type != NS_PDUT_ALIVE) {
- rc = reject_status_msg(msg, &tp, reject, NS_CAUSE_PDU_INCOMP_PSTATE);
- if (rc < 0) {
- LOGP(DLNS, LOGL_ERROR, "Failed to generate reject message (%d)\n", rc);
- return rc;
- }
- return GPRS_NS2_CS_REJECTED;
- }
+ rc = reject_status_msg(msg, &tp, reject, NS_CAUSE_PDU_INCOMP_PSTATE);
+ if (rc < 0)
+ LOGP(DLNS, LOGL_ERROR, "Failed to generate reject message (%d)\n", rc);
+ return NS2_CS_REJECTED;
+ default:
+ rc = reject_status_msg(msg, &tp, reject, NS_CAUSE_PDU_INCOMP_PSTATE);
+ if (rc < 0)
+ LOGP(DLNS, LOGL_ERROR, "Failed to generate reject message (%d)\n", rc);
+ return NS2_CS_REJECTED;
}
- rc = ns2_tlv_parse(&tp, nsh->data,
- msgb_l2len(msg) - sizeof(*nsh), 0, 0);
- if (rc < 0) {
+ if (tlv < 0) {
+ /* TODO: correct behaviour would checking what's wrong.
+ * If it's an essential TLV for the PDU return NS_CAUSE_INVAL_ESSENT_IE.
+ * Otherwise ignore the non-essential TLV. */
LOGP(DLNS, LOGL_ERROR, "Rx NS RESET Error %d during "
- "TLV Parse\n", rc);
- /* TODO: send invalid message back */
- return GPRS_NS2_CS_REJECTED;
+ "TLV Parse\n", tlv);
+ rc = reject_status_msg(msg, &tp, reject, NS_CAUSE_PROTO_ERR_UNSPEC);
+ if (rc < 0)
+ LOGP(DLNS, LOGL_ERROR, "Failed to generate reject message (%d)\n", rc);
+ return NS2_CS_REJECTED;
}
- if (bind->vc_mode == NS2_VC_MODE_BLOCKRESET) {
- if (!TLVP_PRESENT(&tp, NS_IE_CAUSE) ||
- !TLVP_PRESENT(&tp, NS_IE_VCI) || !TLVP_PRESENT(&tp, NS_IE_NSEI)) {
- LOGP(DLNS, LOGL_ERROR, "NS RESET Missing mandatory IE\n");
- rc = reject_status_msg(msg, &tp, reject, NS_CAUSE_MISSING_ESSENT_IE);
- return GPRS_NS2_CS_REJECTED;
- }
+ if (!TLVP_PRES_LEN(&tp, NS_IE_CAUSE, 1) ||
+ !TLVP_PRES_LEN(&tp, NS_IE_VCI, 2) || !TLVP_PRES_LEN(&tp, NS_IE_NSEI, 2)) {
+ LOGP(DLNS, LOGL_ERROR, "NS RESET Missing mandatory IE\n");
+ rc = reject_status_msg(msg, &tp, reject, NS_CAUSE_MISSING_ESSENT_IE);
+ if (rc < 0)
+ LOGP(DLNS, LOGL_ERROR, "Failed to generate reject message (%d)\n", rc);
+ return NS2_CS_REJECTED;
}
- /* find or create NSE */
nsei = tlvp_val16be(&tp, NS_IE_NSEI);
+ nsvci = tlvp_val16be(&tp, NS_IE_VCI);
+
+ /* find or create NSE */
nse = gprs_ns2_nse_by_nsei(bind->nsi, nsei);
if (!nse) {
- if (!bind->nsi->create_nse) {
- return GPRS_NS2_CS_SKIPPED;
- }
+ /* only create nse for udp & ipaccess */
+ if (bind->ll != GPRS_NS2_LL_UDP || dialect != GPRS_NS2_DIALECT_IPACCESS)
+ return NS2_CS_SKIPPED;
+
+ if (!bind->accept_ipaccess)
+ return NS2_CS_SKIPPED;
- nse = gprs_ns2_create_nse(bind->nsi, nsei);
+ nse = gprs_ns2_create_nse(bind->nsi, nsei, bind->ll, dialect);
if (!nse) {
- return GPRS_NS2_CS_ERROR;
+ LOGP(DLNS, LOGL_ERROR, "Failed to create NSE(%05u)\n", nsei);
+ return NS2_CS_ERROR;
+ }
+ } else {
+ /* nsei already known */
+ if (nse->ll != bind->ll) {
+ LOGNSE(nse, LOGL_ERROR, "Received NS-RESET NS-VCI(%05u) with wrong linklayer(%s)"
+ " for already known NSE(%s)\n", nsvci, gprs_ns2_lltype_str(bind->ll),
+ gprs_ns2_lltype_str(nse->ll));
+ return NS2_CS_SKIPPED;
}
}
- nsvc = ns2_vc_alloc(bind, nse, false);
- if (!nsvc)
- return GPRS_NS2_CS_SKIPPED;
+ nsvc = gprs_ns2_nsvc_by_nsvci(bind->nsi, nsvci);
+ if (nsvc) {
+ if (nsvc->persistent) {
+ LOGNSVC(nsvc, LOGL_ERROR, "Received NS-RESET for a persistent NSE over wrong connection.\n");
+ return NS2_CS_SKIPPED;
+ }
+ /* destroy old dynamic nsvc */
+ gprs_ns2_free_nsvc(nsvc);
+ }
- nsvc->ll = GPRS_NS_LL_UDP;
+ /* do nse persistent check late to be more precise on the error message */
+ if (nse->persistent) {
+ LOGNSE(nse, LOGL_ERROR, "Received NS-RESET for a persistent NSE but the unknown "
+ "NS-VCI(%05u)\n", nsvci);
+ return NS2_CS_SKIPPED;
+ }
nsvci = tlvp_val16be(&tp, NS_IE_VCI);
+ vc_mode = ns2_dialect_to_vc_mode(dialect);
+
+ local = gprs_ns2_ip_bind_sockaddr(bind);
+ osmo_sockaddr_to_str_buf(tmp, sizeof(tmp), local);
+ snprintf(idbuf, sizeof(idbuf), "%s-NSE%05u-NSVC%05u-%s-%s", gprs_ns2_lltype_str(nse->ll),
+ nse->nsei, nsvci, tmp, osmo_sockaddr_to_str(remote));
+ osmo_identifier_sanitize_buf(idbuf, NULL, '_');
+ nsvc = ns2_vc_alloc(bind, nse, false, vc_mode, idbuf);
+ if (!nsvc)
+ return NS2_CS_SKIPPED;
+
nsvc->nsvci = nsvci;
nsvc->nsvci_is_valid = true;
*success = nsvc;
- return GPRS_NS2_CS_CREATED;
+ return NS2_CS_CREATED;
}
/*! Create, and connect an inactive, new IP-based NS-VC
@@ -728,17 +1176,17 @@ enum gprs_ns2_cs ns2_create_vc(struct gprs_ns2_vc_bind *bind,
* \param[in] nsvci is only required when bind->vc_mode == NS2_VC_MODE_BLOCKRESET
* \return pointer to newly-allocated, connected and inactive NS-VC; NULL on error */
struct gprs_ns2_vc *gprs_ns2_ip_connect_inactive(struct gprs_ns2_vc_bind *bind,
- struct osmo_sockaddr *remote,
+ const struct osmo_sockaddr *remote,
struct gprs_ns2_nse *nse,
uint16_t nsvci)
{
struct gprs_ns2_vc *nsvc;
- nsvc = gprs_ns2_ip_bind_connect(bind, nse, remote);
+ nsvc = ns2_ip_bind_connect(bind, nse, remote);
if (!nsvc)
return NULL;
- if (nsvc->mode == NS2_VC_MODE_BLOCKRESET) {
+ if (nsvc->mode == GPRS_NS2_VC_MODE_BLOCKRESET) {
nsvc->nsvci = nsvci;
nsvc->nsvci_is_valid = true;
}
@@ -753,7 +1201,7 @@ struct gprs_ns2_vc *gprs_ns2_ip_connect_inactive(struct gprs_ns2_vc_bind *bind,
* \param[in] nsvci is only required when bind->vc_mode == NS2_VC_MODE_BLOCKRESET
* \return pointer to newly-allocated, connected and activated NS-VC; NULL on error */
struct gprs_ns2_vc *gprs_ns2_ip_connect(struct gprs_ns2_vc_bind *bind,
- struct osmo_sockaddr *remote,
+ const struct osmo_sockaddr *remote,
struct gprs_ns2_nse *nse,
uint16_t nsvci)
{
@@ -762,7 +1210,7 @@ struct gprs_ns2_vc *gprs_ns2_ip_connect(struct gprs_ns2_vc_bind *bind,
if (!nsvc)
return NULL;
- gprs_ns2_vc_fsm_start(nsvc);
+ ns2_vc_fsm_start(nsvc);
return nsvc;
}
@@ -774,14 +1222,15 @@ struct gprs_ns2_vc *gprs_ns2_ip_connect(struct gprs_ns2_vc_bind *bind,
* \param[in] nsvci is only required when bind->vc_mode == NS2_VC_MODE_BLOCKRESET
* \return pointer to newly-allocated, connected and activated NS-VC; NULL on error */
struct gprs_ns2_vc *gprs_ns2_ip_connect2(struct gprs_ns2_vc_bind *bind,
- struct osmo_sockaddr *remote,
+ const struct osmo_sockaddr *remote,
uint16_t nsei,
- uint16_t nsvci)
+ uint16_t nsvci,
+ enum gprs_ns2_dialect dialect)
{
struct gprs_ns2_nse *nse = gprs_ns2_nse_by_nsei(bind->nsi, nsei);
if (!nse) {
- nse = gprs_ns2_create_nse(bind->nsi, nsei);
+ nse = gprs_ns2_create_nse(bind->nsi, nsei, GPRS_NS2_LL_UDP, dialect);
if (!nse)
return NULL;
}
@@ -789,52 +1238,21 @@ struct gprs_ns2_vc *gprs_ns2_ip_connect2(struct gprs_ns2_vc_bind *bind,
return gprs_ns2_ip_connect(bind, remote, nse, nsvci);
}
-/*! Create, connect and activate a new IP-SNS NSE.
- * \param[in] bind bind in which the new NS-VC is to be created
- * \param[in] remote remote address to which to connect
- * \param[in] nsei NSEI of the NS Entity in which the NS-VC is to be created
- * \return 0 on success; negative on error */
-int gprs_ns2_ip_connect_sns(struct gprs_ns2_vc_bind *bind,
- struct osmo_sockaddr *remote,
- uint16_t nsei)
-{
- struct gprs_ns2_nse *nse = gprs_ns2_nse_by_nsei(bind->nsi, nsei);
- struct gprs_ns2_vc *nsvc;
-
- if (!nse) {
- nse = gprs_ns2_create_nse(bind->nsi, nsei);
- if (!nse)
- return -1;
- }
-
- nsvc = gprs_ns2_ip_bind_connect(bind, nse, remote);
- if (!nsvc)
- return -1;
-
- if (!nse->bss_sns_fi)
- nse->bss_sns_fi = ns2_sns_bss_fsm_alloc(nse, NULL);
-
- if (!nse->bss_sns_fi)
- return -1;
-
- return ns2_sns_bss_fsm_start(nse, nsvc, remote);
-}
-
/*! Find NS-VC for given socket address.
* \param[in] nse NS Entity in which to search
* \param[in] sockaddr socket address to search for
* \return NS-VC matching sockaddr; NULL if none found */
-struct gprs_ns2_vc *gprs_ns2_nsvc_by_sockaddr(struct gprs_ns2_nse *nse,
- struct osmo_sockaddr *sockaddr)
+struct gprs_ns2_vc *gprs_ns2_nsvc_by_sockaddr_nse(struct gprs_ns2_nse *nse,
+ const struct osmo_sockaddr *sockaddr)
{
struct gprs_ns2_vc *nsvc;
- struct osmo_sockaddr *remote;
+ const struct osmo_sockaddr *remote;
OSMO_ASSERT(nse);
OSMO_ASSERT(sockaddr);
llist_for_each_entry(nsvc, &nse->nsvc, list) {
- remote = gprs_ns2_ip_vc_sockaddr(nsvc);
+ remote = gprs_ns2_ip_vc_remote(nsvc);
if (!osmo_sockaddr_cmp(sockaddr, remote))
return nsvc;
}
@@ -842,22 +1260,55 @@ struct gprs_ns2_vc *gprs_ns2_nsvc_by_sockaddr(struct gprs_ns2_nse *nse,
return NULL;
}
+/*!
+ * Iterate over all nsvc of a NS Entity and call the callback.
+ * If the callback returns < 0 it aborts the loop and returns the callback return code.
+ * \param[in] nse NS Entity to iterate over all nsvcs
+ * \param[in] cb the callback to call
+ * \param[inout] cb_data the private data of the callback
+ * \return 0 if the loop completes. If a callback returns < 0 it will returns this value.
+ */
+int gprs_ns2_nse_foreach_nsvc(struct gprs_ns2_nse *nse, gprs_ns2_foreach_nsvc_cb cb, void *cb_data)
+{
+ struct gprs_ns2_vc *nsvc, *tmp;
+ int rc = 0;
+ llist_for_each_entry_safe(nsvc, tmp, &nse->nsvc, list) {
+ rc = cb(nsvc, cb_data);
+ if (rc < 0)
+ return rc;
+ }
+
+ return 0;
+}
+
+
/*! Bottom-side entry-point for received NS PDU from the driver/bind
- * \param[in] nsi NS instance
* \param[in] nsvc NS-VC for which the message was received
- * \param msg the received message. Ownership is trasnferred, caller must not free it!
+ * \param msg the received message. Ownership is transferred, caller must not free it!
* \return 0 on success; negative on error */
-int ns2_recv_vc(struct gprs_ns2_inst *nsi,
- struct gprs_ns2_vc *nsvc,
+int ns2_recv_vc(struct gprs_ns2_vc *nsvc,
struct msgb *msg)
{
struct gprs_ns_hdr *nsh = (struct gprs_ns_hdr *) msg->l2h;
- struct tlv_parsed tp;
+ struct tlv_parsed tp = { };
int rc = 0;
- if (msg->len < sizeof(struct gprs_ns_hdr))
- return -EINVAL;
+ log_set_context(LOG_CTX_GB_NSE, nsvc->nse);
+ log_set_context(LOG_CTX_GB_NSVC, nsvc);
+
+ RATE_CTR_INC_NS(nsvc, NS_CTR_PKTS_IN);
+ RATE_CTR_ADD_NS(nsvc, NS_CTR_BYTES_IN, msg->len);
+
+ if (msg->len < sizeof(struct gprs_ns_hdr)) {
+ rc = -EINVAL;
+ goto freemsg;
+ }
+
+ if (nsh->pdu_type != NS_PDUT_UNITDATA)
+ LOG_NS_RX_SIGNAL(nsvc, nsh->pdu_type);
+ else
+ LOG_NS_DATA(nsvc, "Rx", nsh->pdu_type, LOGL_INFO, "\n");
switch (nsh->pdu_type) {
case SNS_PDUT_CONFIG:
@@ -865,100 +1316,114 @@ int ns2_recv_vc(struct gprs_ns2_inst *nsi,
rc = ns2_tlv_parse(&tp, nsh->data+1,
msgb_l2len(msg) - sizeof(*nsh)-1, 0, 0);
if (rc < 0) {
- LOGPC(DLNS, LOGL_NOTICE, "Error during TLV Parse in %s\n", msgb_hexdump(msg));
- return rc;
+ LOGP(DLNS, LOGL_NOTICE, "Error during TLV Parse in %s\n", msgb_hexdump(msg));
+ goto freemsg;
}
/* All sub-network service related message types */
- rc = gprs_ns2_sns_rx(nsvc, msg, &tp);
- break;
+ return ns2_sns_rx(nsvc, msg, &tp);
case SNS_PDUT_ACK:
case SNS_PDUT_ADD:
case SNS_PDUT_CHANGE_WEIGHT:
case SNS_PDUT_DELETE:
/* weird layout: NSEI TLV, then value-only transaction IE, then TLV again */
- rc = ns2_tlv_parse(&tp, nsh->data+1,
- msgb_l2len(msg) - sizeof(*nsh)-1, 0, 0);
+ rc = ns2_tlv_parse(&tp, nsh->data+5,
+ msgb_l2len(msg) - sizeof(*nsh)-5, 0, 0);
if (rc < 0) {
- LOGPC(DLNS, LOGL_NOTICE, "Error during TLV Parse in %s\n", msgb_hexdump(msg));
- return rc;
+ LOGP(DLNS, LOGL_NOTICE, "Error during TLV Parse in %s\n", msgb_hexdump(msg));
+ goto freemsg;
}
tp.lv[NS_IE_NSEI].val = nsh->data+2;
tp.lv[NS_IE_NSEI].len = 2;
tp.lv[NS_IE_TRANS_ID].val = nsh->data+4;
tp.lv[NS_IE_TRANS_ID].len = 1;
- rc = gprs_ns2_sns_rx(nsvc, msg, &tp);
- break;
+ return ns2_sns_rx(nsvc, msg, &tp);
case SNS_PDUT_CONFIG_ACK:
case SNS_PDUT_SIZE:
case SNS_PDUT_SIZE_ACK:
rc = ns2_tlv_parse(&tp, nsh->data,
msgb_l2len(msg) - sizeof(*nsh), 0, 0);
if (rc < 0) {
- LOGPC(DLNS, LOGL_NOTICE, "Error during TLV Parse in %s\n", msgb_hexdump(msg));
- return rc;
+ LOGP(DLNS, LOGL_NOTICE, "Error during TLV Parse in %s\n", msgb_hexdump(msg));
+ goto freemsg;
}
/* All sub-network service related message types */
- rc = gprs_ns2_sns_rx(nsvc, msg, &tp);
- break;
-
+ return ns2_sns_rx(nsvc, msg, &tp);
case NS_PDUT_UNITDATA:
- rc = gprs_ns2_vc_rx(nsvc, msg, NULL);
- break;
+ return ns2_vc_rx(nsvc, msg, &tp);
default:
rc = ns2_tlv_parse(&tp, nsh->data,
msgb_l2len(msg) - sizeof(*nsh), 0, 0);
if (rc < 0) {
- LOGPC(DLNS, LOGL_NOTICE, "Error during TLV Parse\n");
+ LOGP(DLNS, LOGL_NOTICE, "Error during TLV Parse\n");
if (nsh->pdu_type != NS_PDUT_STATUS)
- ns2_tx_status(nsvc, NS_CAUSE_PROTO_ERR_UNSPEC, 0, msg);
+ ns2_tx_status(nsvc, NS_CAUSE_PROTO_ERR_UNSPEC, 0, msg, NULL);
return rc;
}
- rc = gprs_ns2_vc_rx(nsvc, msg, &tp);
- break;
+ return ns2_vc_rx(nsvc, msg, &tp);
}
+freemsg:
+ msgb_free(msg);
return rc;
}
+/* summarize all active data nsvcs */
+void ns2_nse_data_sum(struct gprs_ns2_nse *nse)
+{
+ struct gprs_ns2_vc *nsvc;
+
+ nse->nsvc_count = 0;
+ nse->sum_data_weight = 0;
+ nse->sum_sig_weight = 0;
+
+ llist_for_each_entry(nsvc, &nse->nsvc, list) {
+ if (!ns2_vc_is_unblocked(nsvc))
+ continue;
+
+ nse->nsvc_count++;
+ nse->sum_data_weight += nsvc->data_weight;
+ nse->sum_sig_weight += nsvc->sig_weight;
+ }
+}
+
/*! Notify a nse about the change of a NS-VC.
* \param[in] nsvc NS-VC which has detected the change (and shall not be notified).
* \param[in] unblocked whether the NSE should be marked as unblocked (true) or blocked (false) */
void ns2_nse_notify_unblocked(struct gprs_ns2_vc *nsvc, bool unblocked)
{
struct gprs_ns2_nse *nse = nsvc->nse;
- struct gprs_ns2_vc *tmp;
+ struct gprs_ns2_inst *nsi = nse->nsi;
+ uint16_t nsei = nse->nsei;
+
+ ns2_nse_data_sum(nse);
+ ns2_sns_notify_alive(nse, nsvc, unblocked);
+
+ /* NSE could have been freed, try to get it again */
+ nse = gprs_ns2_nse_by_nsei(nsi, nsei);
- if (unblocked == nse->alive)
+ if (!nse || unblocked == nse->alive)
return;
- if (unblocked) {
- /* this is the first unblocked NSVC on an unavailable NSE */
+ /* wait until both data_weight and sig_weight are != 0 before declaring NSE as alive */
+ if (unblocked && nse->sum_data_weight && nse->sum_sig_weight) {
nse->alive = true;
- ns2_prim_status_ind(nse->nsi, nse->nsei,
- 0, NS_AFF_CAUSE_RECOVERY);
+ osmo_clock_gettime(CLOCK_MONOTONIC, &nse->ts_alive_change);
+ ns2_prim_status_ind(nse, NULL, 0, GPRS_NS2_AFF_CAUSE_RECOVERY);
+ nse->first = false;
return;
}
- /* check if there are any remaining alive vcs */
- llist_for_each_entry(tmp, &nse->nsvc, list) {
- if (tmp == nsvc)
- continue;
-
- if (gprs_ns2_vc_is_unblocked(tmp)) {
- /* there is at least one remaining alive NSVC */
- return;
- }
+ if (nse->alive && (nse->sum_data_weight == 0 || nse->sum_sig_weight == 0)) {
+ /* nse became unavailable */
+ nse->alive = false;
+ osmo_clock_gettime(CLOCK_MONOTONIC, &nse->ts_alive_change);
+ ns2_prim_status_ind(nse, NULL, 0, GPRS_NS2_AFF_CAUSE_FAILURE);
}
-
- /* nse became unavailable */
- nse->alive = false;
- ns2_prim_status_ind(nse->nsi, nse->nsei,
- 0, NS_AFF_CAUSE_FAILURE);
}
/*! Create a new GPRS NS instance
* \param[in] ctx a talloc context to allocate NS instance from
- * \param[in] cb Call-back function for dispatching primitives to the user
+ * \param[in] cb Call-back function for dispatching primitives to the user. The Call-back must free all msgb* given in the primitive.
* \param[in] cb_data transparent user data passed to Call-back
* \returns dynamically allocated gprs_ns_inst; NULL on error */
struct gprs_ns2_inst *gprs_ns2_instantiate(void *ctx, osmo_prim_cb cb, void *cb_data)
@@ -982,6 +1447,9 @@ struct gprs_ns2_inst *gprs_ns2_instantiate(void *ctx, osmo_prim_cb cb, void *cb_
nsi->timeout[NS_TOUT_TNS_ALIVE] = 3;
nsi->timeout[NS_TOUT_TNS_ALIVE_RETRIES] = 10;
nsi->timeout[NS_TOUT_TSNS_PROV] = 3; /* 1..10 */
+ nsi->timeout[NS_TOUT_TSNS_SIZE_RETRIES] = 3;
+ nsi->timeout[NS_TOUT_TSNS_CONFIG_RETRIES] = 3;
+ nsi->timeout[NS_TOUT_TSNS_PROCEDURES_RETRIES] = 3;
return nsi;
}
@@ -990,31 +1458,13 @@ struct gprs_ns2_inst *gprs_ns2_instantiate(void *ctx, osmo_prim_cb cb, void *cb_
* \param[in] nsi NS instance to destroy */
void gprs_ns2_free(struct gprs_ns2_inst *nsi)
{
- struct gprs_ns2_vc_bind *bind, *tbind;
- struct gprs_ns2_nse *nse, *ntmp;
-
if (!nsi)
return;
- llist_for_each_entry_safe(nse, ntmp, &nsi->nse, list) {
- gprs_ns2_free_nse(nse);
- }
-
- llist_for_each_entry_safe(bind, tbind, &nsi->binding, list) {
- gprs_ns2_free_bind(bind);
- }
-}
+ gprs_ns2_free_nses(nsi);
+ gprs_ns2_free_binds(nsi);
-/*! Configure whether a NS Instance should dynamically create NSEs based on incoming traffic.
- * \param nsi the instance to modify
- * \param create_nse if NSE can be created on receiving package. SGSN set this.
- * \return 0 on success; negative on error
- */
-int gprs_ns2_dynamic_create_nse(struct gprs_ns2_inst *nsi, bool create_nse)
-{
- nsi->create_nse = create_nse;
-
- return 0;
+ talloc_free(nsi);
}
/*! Start the NS-ALIVE FSM in all NS-VCs of given NSE.
@@ -1025,30 +1475,35 @@ void gprs_ns2_start_alive_all_nsvcs(struct gprs_ns2_nse *nse)
OSMO_ASSERT(nse);
llist_for_each_entry(nsvc, &nse->nsvc, list) {
+ /* A pre-configured endpoint shall not be used for NSE data or signalling traffic
+ * (with the exception of Size and Configuration procedures) unless it is
+ * configured by the SGSN using the auto-configuration procedures */
if (nsvc->sns_only)
continue;
- gprs_ns2_vc_fsm_start(nsvc);
+ ns2_vc_fsm_start(nsvc);
}
}
-/*! Set the mode of given bind.
- * \param[in] bind the bind we want to set the mode of
- * \param[in] modde mode to set bind to */
-void gprs_ns2_bind_set_mode(struct gprs_ns2_vc_bind *bind, enum gprs_ns2_vc_mode mode)
-{
- bind->vc_mode = mode;
-}
-
/*! Destroy a given bind.
* \param[in] bind the bind we want to destroy */
void gprs_ns2_free_bind(struct gprs_ns2_vc_bind *bind)
{
- struct gprs_ns2_vc *nsvc, *tmp;
- if (!bind)
+ struct gprs_ns2_vc *nsvc;
+ struct gprs_ns2_nse *nse;
+ if (!bind || bind->freed)
return;
+ bind->freed = true;
- llist_for_each_entry_safe(nsvc, tmp, &bind->nsvc, blist) {
+ if (gprs_ns2_is_ip_bind(bind)) {
+ llist_for_each_entry(nse, &bind->nsi->nse, list) {
+ gprs_ns2_sns_del_bind(nse, bind);
+ }
+ }
+
+ /* prevent recursive free() when the user reacts on a down event and free() a second time */
+ while (!llist_empty(&bind->nsvc)) {
+ nsvc = llist_first_entry(&bind->nsvc, struct gprs_ns2_vc, blist);
gprs_ns2_free_nsvc(nsvc);
}
@@ -1056,6 +1511,183 @@ void gprs_ns2_free_bind(struct gprs_ns2_vc_bind *bind)
bind->driver->free_bind(bind);
llist_del(&bind->list);
+ osmo_stat_item_group_free(bind->statg);
+ talloc_free((char *)bind->name);
talloc_free(bind);
}
+
+void gprs_ns2_free_binds(struct gprs_ns2_inst *nsi)
+{
+ struct gprs_ns2_vc_bind *bind;
+
+ /* prevent recursive free() when the user reacts on a down event and free() a second time */
+ while (!llist_empty(&nsi->binding)) {
+ bind = llist_first_entry(&nsi->binding, struct gprs_ns2_vc_bind, list);
+ gprs_ns2_free_bind(bind);
+ }
+}
+
+/*! Search for a bind with a unique name
+ * \param[in] nsi NS instance on which we operate
+ * \param[in] name The unique bind name to search for
+ * \return the bind or NULL if not found
+ */
+struct gprs_ns2_vc_bind *gprs_ns2_bind_by_name(struct gprs_ns2_inst *nsi, const char *name)
+{
+ struct gprs_ns2_vc_bind *bind;
+
+ llist_for_each_entry(bind, &nsi->binding, list) {
+ if (!strcmp(bind->name, name))
+ return bind;
+ }
+
+ return NULL;
+}
+
+enum gprs_ns2_vc_mode ns2_dialect_to_vc_mode(enum gprs_ns2_dialect dialect)
+{
+ switch (dialect) {
+ case GPRS_NS2_DIALECT_SNS:
+ case GPRS_NS2_DIALECT_STATIC_ALIVE:
+ return GPRS_NS2_VC_MODE_ALIVE;
+ case GPRS_NS2_DIALECT_STATIC_RESETBLOCK:
+ case GPRS_NS2_DIALECT_IPACCESS:
+ return GPRS_NS2_VC_MODE_BLOCKRESET;
+ default:
+ return -1;
+ }
+}
+
+static void add_bind_array(struct gprs_ns2_vc_bind **array,
+ struct gprs_ns2_vc_bind *bind, int size)
+{
+ int i;
+ for (i=0; i < size; i++) {
+ if (array[i] == bind)
+ return;
+ if (!array[i])
+ break;
+ }
+
+ if (i == size)
+ return;
+
+ array[i] = bind;
+}
+
+void ns2_nse_update_mtu(struct gprs_ns2_nse *nse)
+{
+ struct gprs_ns2_vc *nsvc;
+ int mtu = 0;
+
+ if (llist_empty(&nse->nsvc)) {
+ nse->mtu = 0;
+ return;
+ }
+
+ llist_for_each_entry(nsvc, &nse->nsvc, list) {
+ if (mtu == 0)
+ mtu = nsvc->bind->mtu;
+ else if (mtu > nsvc->bind->mtu)
+ mtu = nsvc->bind->mtu;
+ }
+
+ if (nse->mtu == mtu)
+ return;
+
+ nse->mtu = mtu;
+ if (nse->alive)
+ ns2_prim_status_ind(nse, NULL, 0, GPRS_NS2_AFF_CAUSE_MTU_CHANGE);
+}
+
+/*! calculate the transfer capabilities for a nse
+ * \param nse the nse to count the transfer capability
+ * \param bvci a bvci - unused
+ * \return the transfer capability in mbit. On error < 0.
+ */
+int ns2_count_transfer_cap(struct gprs_ns2_nse *nse,
+ uint16_t bvci)
+{
+ struct gprs_ns2_vc *nsvc;
+ struct gprs_ns2_vc_bind **active_binds;
+ int i, active_nsvcs = 0, transfer_cap = 0;
+
+ /* calculate the transfer capabilities based on the binds.
+ * A bind has a transfer capability which is shared across all NSVCs.
+ * Take care the bind cap is not counted twice within a NSE.
+ * This should be accurate for FR and UDP but not for FR/GRE. */
+
+ if (!nse->alive)
+ return 0;
+
+ llist_for_each_entry(nsvc, &nse->nsvc, list) {
+ if (ns2_vc_is_unblocked(nsvc))
+ active_nsvcs++;
+ }
+
+ if (!active_nsvcs)
+ return 0;
+
+ active_binds = talloc_zero_array(nse, struct gprs_ns2_vc_bind*, active_nsvcs);
+ if (!active_binds)
+ return -ENOMEM;
+
+ llist_for_each_entry(nsvc, &nse->nsvc, list) {
+ if (!ns2_vc_is_unblocked(nsvc))
+ continue;
+ add_bind_array(active_binds, nsvc->bind, active_nsvcs);
+ }
+
+ /* TODO: change calcuation for FR/GRE */
+ for (i = 0; i < active_nsvcs; i++) {
+ if (active_binds[i])
+ transfer_cap += active_binds[i]->transfer_capability;
+ }
+
+ talloc_free(active_binds);
+ return transfer_cap;
+}
+
+/*! common allocation + low-level initialization of a bind. Called by vc-drivers */
+int ns2_bind_alloc(struct gprs_ns2_inst *nsi, const char *name,
+ struct gprs_ns2_vc_bind **result)
+{
+ struct gprs_ns2_vc_bind *bind;
+
+ if (!name)
+ return -EINVAL;
+
+ if (gprs_ns2_bind_by_name(nsi, name))
+ return -EALREADY;
+
+ bind = talloc_zero(nsi, struct gprs_ns2_vc_bind);
+ if (!bind)
+ return -ENOMEM;
+
+ bind->name = talloc_strdup(bind, name);
+ if (!bind->name) {
+ talloc_free(bind);
+ return -ENOMEM;
+ }
+
+ bind->statg = osmo_stat_item_group_alloc(bind, &nsbind_statg_desc, nsi->bind_rate_ctr_idx);
+ if (!bind->statg) {
+ talloc_free(bind);
+ return -ENOMEM;
+ }
+
+ bind->sns_sig_weight = 1;
+ bind->sns_data_weight = 1;
+ bind->nsi = nsi;
+ INIT_LLIST_HEAD(&bind->nsvc);
+ llist_add_tail(&bind->list, &nsi->binding);
+
+ nsi->bind_rate_ctr_idx++;
+
+ if (result)
+ *result = bind;
+
+ return 0;
+}
+
/*! @} */
diff --git a/src/gb/gprs_ns2_fr.c b/src/gb/gprs_ns2_fr.c
new file mode 100644
index 00000000..d6516012
--- /dev/null
+++ b/src/gb/gprs_ns2_fr.c
@@ -0,0 +1,978 @@
+/*! \file gprs_ns2_fr.c
+ * NS-over-FR-over-GRE implementation.
+ * GPRS Networks Service (NS) messages on the Gb interface.
+ * 3GPP TS 08.16 version 8.0.1 Release 1999 / ETSI TS 101 299 V8.0.1 (2002-05)
+ * as well as its successor 3GPP TS 48.016 */
+
+/* (C) 2009-2021 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2020 sysmocom - s.f.m.c. GmbH
+ * Author: Alexander Couzens <lynxis@fe80.eu>
+ *
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <netinet/ip6.h>
+#include <arpa/inet.h>
+#include <linux/if.h>
+
+#include <sys/ioctl.h>
+#include <netpacket/packet.h>
+#include <linux/if_ether.h>
+#include <linux/hdlc.h>
+#include <linux/hdlc/ioctl.h>
+#include <linux/sockios.h>
+
+#include <osmocom/gprs/frame_relay.h>
+#include <osmocom/core/byteswap.h>
+#include <osmocom/core/stat_item.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/gprs/gprs_ns2.h>
+#include <osmocom/core/netdev.h>
+#include <osmocom/gprs/protocol/gsm_08_16.h>
+#include <osmocom/gprs/protocol/gsm_08_18.h>
+
+#include "config.h"
+#include "common_vty.h"
+#include "gprs_ns2_internal.h"
+
+#define GRE_PTYPE_FR 0x6559
+#define GRE_PTYPE_IPv4 0x0800
+#define GRE_PTYPE_IPv6 0x86dd
+#define GRE_PTYPE_KAR 0x0000 /* keepalive response */
+
+#ifndef IPPROTO_GRE
+# define IPPROTO_GRE 47
+#endif
+
+#define E1_LINERATE 2048000
+#define E1_SLOTS_TOTAL 32
+#define E1_SLOTS_USED 31
+/* usable bitrate of the E1 superchannel with 31 of 32 timeslots */
+#define SUPERCHANNEL_LINERATE (E1_LINERATE*E1_SLOTS_USED)/E1_SLOTS_TOTAL
+/* nanoseconds per bit (504) */
+#define BIT_DURATION_NS (1000000000 / SUPERCHANNEL_LINERATE)
+
+static void free_bind(struct gprs_ns2_vc_bind *bind);
+static int fr_dlci_rx_cb(void *cb_data, struct msgb *msg);
+
+struct gprs_ns2_vc_driver vc_driver_fr = {
+ .name = "GB frame relay",
+ .free_bind = free_bind,
+};
+
+struct priv_bind {
+ struct osmo_netdev *netdev;
+ char netif[IFNAMSIZ];
+ struct osmo_fr_link *link;
+ int ifindex;
+ bool if_running;
+ /* backlog queue for AF_PACKET / ENOBUFS handling (see OS#4995) */
+ struct {
+ /* file-descriptor for AF_PACKET socket */
+ struct osmo_fd ofd;
+ /* LMI bucket (we only store the last LMI message, no need to queue */
+ struct msgb *lmi_msg;
+ /* list of NS msgb (backlog) */
+ struct llist_head list;
+ /* timer to trigger next attempt of AF_PACKET write */
+ struct osmo_timer_list timer;
+ /* re-try after that many micro-seconds */
+ uint32_t retry_us;
+ } backlog;
+};
+
+struct priv_vc {
+ struct osmo_sockaddr remote;
+ uint16_t dlci;
+ struct osmo_fr_dlc *dlc;
+};
+
+static void free_vc(struct gprs_ns2_vc *nsvc)
+{
+ if (!nsvc)
+ return;
+
+ if (!nsvc->priv)
+ return;
+
+ OSMO_ASSERT(gprs_ns2_is_fr_bind(nsvc->bind));
+ talloc_free(nsvc->priv);
+ nsvc->priv = NULL;
+}
+
+static void dump_vty(const struct gprs_ns2_vc_bind *bind, struct vty *vty, bool stats)
+{
+ struct priv_bind *priv;
+ struct gprs_ns2_vc *nsvc;
+ struct osmo_fr_link *fr_link;
+
+ if (!bind)
+ return;
+
+ priv = bind->priv;
+ fr_link = priv->link;
+
+ vty_out(vty, "FR bind: %s, role: %s, link: %s%s", priv->netif,
+ osmo_fr_role_str(fr_link->role), priv->if_running ? "UP" : "DOWN", VTY_NEWLINE);
+
+ llist_for_each_entry(nsvc, &bind->nsvc, blist) {
+ ns2_vty_dump_nsvc(vty, nsvc, stats);
+ }
+
+ priv = bind->priv;
+}
+
+/*! clean up all private driver state. Should be only called by gprs_ns2_free_bind() */
+static void free_bind(struct gprs_ns2_vc_bind *bind)
+{
+ struct priv_bind *priv;
+ struct msgb *msg, *msg2;
+
+ if (!bind)
+ return;
+
+ OSMO_ASSERT(gprs_ns2_is_fr_bind(bind));
+ priv = bind->priv;
+
+ OSMO_ASSERT(llist_empty(&bind->nsvc));
+
+ osmo_timer_del(&priv->backlog.timer);
+ llist_for_each_entry_safe(msg, msg2, &priv->backlog.list, list) {
+ msgb_free(msg);
+ }
+ msgb_free(priv->backlog.lmi_msg);
+
+ osmo_netdev_free(priv->netdev);
+ osmo_fr_link_free(priv->link);
+ osmo_fd_close(&priv->backlog.ofd);
+ talloc_free(priv);
+}
+
+static void fr_dlci_status_cb(struct osmo_fr_dlc *dlc, void *cb_data, bool active)
+{
+ struct gprs_ns2_vc *nsvc = cb_data;
+
+ if (active) {
+ ns2_vc_fsm_start(nsvc);
+ } else {
+ ns2_vc_force_unconfigured(nsvc);
+ }
+}
+
+static struct priv_vc *fr_alloc_vc(struct gprs_ns2_vc_bind *bind,
+ struct gprs_ns2_vc *nsvc,
+ uint16_t dlci)
+{
+ struct priv_bind *privb = bind->priv;
+ struct priv_vc *priv = talloc_zero(bind, struct priv_vc);
+ if (!priv)
+ return NULL;
+
+ OSMO_ASSERT(gprs_ns2_is_fr_bind(bind));
+ nsvc->priv = priv;
+ priv->dlci = dlci;
+ priv->dlc = osmo_fr_dlc_alloc(privb->link, dlci);
+ if (!priv->dlc) {
+ nsvc->priv = NULL;
+ talloc_free(priv);
+ return NULL;
+ }
+
+ priv->dlc->cb_data = nsvc;
+ priv->dlc->rx_cb = fr_dlci_rx_cb;
+ priv->dlc->status_cb = fr_dlci_status_cb;
+
+ return priv;
+}
+
+int gprs_ns2_find_vc_by_dlci(struct gprs_ns2_vc_bind *bind,
+ uint16_t dlci,
+ struct gprs_ns2_vc **result)
+{
+ struct gprs_ns2_vc *nsvc;
+ struct priv_vc *vcpriv;
+
+ OSMO_ASSERT(gprs_ns2_is_fr_bind(bind));
+ if (!result)
+ return -EINVAL;
+
+ llist_for_each_entry(nsvc, &bind->nsvc, blist) {
+ vcpriv = nsvc->priv;
+ if (vcpriv->dlci != dlci) {
+ *result = nsvc;
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+/* PDU from the network interface towards the fr layer (upwards) */
+static int fr_netif_ofd_cb(struct osmo_fd *bfd, uint32_t what)
+{
+ struct gprs_ns2_vc_bind *bind = bfd->data;
+ struct priv_bind *priv = bind->priv;
+ struct msgb *msg;
+ struct sockaddr_ll sll;
+ socklen_t sll_len = sizeof(sll);
+ int rc = 0;
+
+ /* we only handle read here. write to AF_PACKET sockets cannot be triggered
+ * by select or poll, see OS#4995 */
+ if (!(what & OSMO_FD_READ))
+ return 0;
+
+ msg = msgb_alloc(NS_ALLOC_SIZE, "Gb/NS/FR Rx");
+ if (!msg)
+ return -ENOMEM;
+
+ rc = recvfrom(bfd->fd, msg->data, NS_ALLOC_SIZE, 0, (struct sockaddr *)&sll, &sll_len);
+ if (rc < 0) {
+ LOGBIND(bind, LOGL_ERROR, "recv error %s during NS-FR recv\n", strerror(errno));
+ goto out_err;
+ } else if (rc == 0) {
+ goto out_err;
+ }
+
+ /* ignore any packets that we might have received for a different interface, between
+ * the socket() and the bind() call */
+ if (sll.sll_ifindex != priv->ifindex)
+ goto out_err;
+
+ msgb_put(msg, rc);
+ msg->dst = priv->link;
+ return osmo_fr_rx(msg);
+
+out_err:
+ msgb_free(msg);
+ return rc;
+}
+
+/* PDU from the frame relay towards the NS-VC (upwards) */
+static int fr_dlci_rx_cb(void *cb_data, struct msgb *msg)
+{
+ int rc;
+ struct gprs_ns2_vc *nsvc = cb_data;
+
+ rc = ns2_recv_vc(nsvc, msg);
+
+ return rc;
+}
+
+static int fr_netif_write_one(struct gprs_ns2_vc_bind *bind, struct msgb *msg)
+{
+ struct priv_bind *priv = bind->priv;
+ unsigned int len = msgb_length(msg);
+ int rc;
+
+ /* estimate the retry time based on the data rate it takes to transmit */
+ priv->backlog.retry_us = (BIT_DURATION_NS * 8 * len) / 1000;
+
+ rc = write(priv->backlog.ofd.fd, msgb_data(msg), len);
+ if (rc == len) {
+ msgb_free(msg);
+ return 0;
+ } else if (rc < 0) {
+ /* don't free, the caller might want to re-transmit */
+ switch (errno) {
+ case EAGAIN:
+ case ENOBUFS:
+ /* not a real error, but more a normal event on AF_PACKET */
+ /* don't free the message and let the caller re-enqueue */
+ return -errno;
+ default:
+ /* an actual error, like -ENETDOWN, -EMSGSIZE */
+ LOGBIND(bind, LOGL_ERROR, "error during write to AF_PACKET: %s\n", strerror(errno));
+ msgb_free(msg);
+ return 0;
+ }
+ } else {
+ /* short write */
+ LOGBIND(bind, LOGL_ERROR, "short write on AF_PACKET: %d < %d\n", rc, len);
+ msgb_free(msg);
+ return 0;
+ }
+}
+
+/*! determine if given bind is for FR-GRE encapsulation. */
+int gprs_ns2_is_fr_bind(struct gprs_ns2_vc_bind *bind)
+{
+ return (bind->driver == &vc_driver_fr);
+}
+
+/* PDU from the NS-VC towards the frame relay layer (downwards) */
+static int fr_vc_sendmsg(struct gprs_ns2_vc *nsvc, struct msgb *msg)
+{
+ struct priv_vc *vcpriv = nsvc->priv;
+
+ msg->dst = vcpriv->dlc;
+ return osmo_fr_tx_dlc(msg);
+}
+
+static void enqueue_at_head(struct gprs_ns2_vc_bind *bind, struct msgb *msg)
+{
+ struct priv_bind *priv = bind->priv;
+ llist_add(&msg->list, &priv->backlog.list);
+ osmo_stat_item_inc(osmo_stat_item_group_get_item(bind->statg, NS2_BIND_STAT_BACKLOG_LEN), 1);
+ osmo_timer_schedule(&priv->backlog.timer, 0, priv->backlog.retry_us);
+}
+
+static void enqueue_at_tail(struct gprs_ns2_vc_bind *bind, struct msgb *msg)
+{
+ struct priv_bind *priv = bind->priv;
+ llist_add_tail(&msg->list, &priv->backlog.list);
+ osmo_stat_item_inc(osmo_stat_item_group_get_item(bind->statg, NS2_BIND_STAT_BACKLOG_LEN), 1);
+ osmo_timer_schedule(&priv->backlog.timer, 0, priv->backlog.retry_us);
+}
+
+#define LMI_Q933A_DLCI 0
+
+/* enqueue to backlog (LMI, signaling) or drop (userdata msg) */
+static int backlog_enqueue_or_free(struct gprs_ns2_vc_bind *bind, struct msgb *msg)
+{
+ struct priv_bind *priv = bind->priv;
+ uint8_t dlci = msg->data[0];
+ uint8_t ns_pdu_type;
+ uint16_t bvci;
+
+ if (msgb_length(msg) < 1)
+ goto out_free;
+
+ /* we want to enqueue only Q.933 LMI traffic or NS signaling; NOT user traffic */
+ switch (dlci) {
+ case LMI_Q933A_DLCI:
+ /* always store only the last LMI message in the lmi_msg bucket */
+ msgb_free(priv->backlog.lmi_msg);
+ priv->backlog.lmi_msg = msg;
+ return 0;
+ default:
+ /* there's no point in trying to enqueue messages if the interface is down */
+ if (!priv->if_running)
+ break;
+
+ if (msgb_length(msg) < 3)
+ break;
+ ns_pdu_type = msg->data[2];
+ switch (ns_pdu_type) {
+ case NS_PDUT_UNITDATA:
+ if (msgb_length(msg) < 6)
+ break;
+ bvci = osmo_load16be(msg->data + 4);
+ /* enqueue BVCI=0 traffic at tail of queue */
+ if (bvci == BVCI_SIGNALLING) {
+ enqueue_at_tail(bind, msg);
+ return 0;
+ }
+ break;
+ default:
+ /* enqueue NS signaling traffic at head of queue */
+ enqueue_at_head(bind, msg);
+ return 0;
+ }
+ break;
+ }
+
+out_free:
+ /* drop everything that is not LMI, NS-signaling or BVCI-0 */
+ msgb_free(msg);
+ return -1;
+}
+
+static void fr_backlog_timer_cb(void *data)
+{
+ struct gprs_ns2_vc_bind *bind = data;
+ struct priv_bind *priv = bind->priv;
+ int i, rc;
+
+ /* first try to get rid of the LMI message, if any */
+ if (priv->backlog.lmi_msg) {
+ rc = fr_netif_write_one(bind, priv->backlog.lmi_msg);
+ if (rc < 0)
+ goto restart_timer;
+ /* fr_netif_write_one() has just free'd it */
+ priv->backlog.lmi_msg = NULL;
+ }
+
+ /* attempt to send up to 10 messages in every timer */
+ for (i = 0; i < 10; i++) {
+ struct msgb *msg = msgb_dequeue(&priv->backlog.list);
+ if (!msg)
+ break;
+
+ rc = fr_netif_write_one(bind, msg);
+ if (rc < 0) {
+ /* re-add at head of list */
+ llist_add(&msg->list, &priv->backlog.list);
+ break;
+ }
+ osmo_stat_item_dec(osmo_stat_item_group_get_item(bind->statg, NS2_BIND_STAT_BACKLOG_LEN), 1);
+ }
+
+restart_timer:
+ /* re-start timer if we still have data in the queue */
+ if (!llist_empty(&priv->backlog.list))
+ osmo_timer_schedule(&priv->backlog.timer, 0, priv->backlog.retry_us);
+}
+
+/* PDU from the frame relay layer towards the network interface (downwards) */
+int fr_tx_cb(void *data, struct msgb *msg)
+{
+ struct gprs_ns2_vc_bind *bind = data;
+ struct priv_bind *priv = bind->priv;
+ int rc;
+
+ if (llist_empty(&priv->backlog.list)) {
+ /* attempt to transmit right now */
+ rc = fr_netif_write_one(bind, msg);
+ if (rc < 0) {
+ /* enqueue to backlog in case it fails */
+ return backlog_enqueue_or_free(bind, msg);
+ }
+ } else {
+ /* enqueue to backlog */
+ return backlog_enqueue_or_free(bind, msg);
+ }
+
+ return 0;
+}
+
+static int devname2ifindex(const char *ifname)
+{
+ struct ifreq ifr;
+ int sk, rc;
+
+ sk = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
+ if (sk < 0)
+ return sk;
+
+
+ memset(&ifr, 0, sizeof(ifr));
+ OSMO_STRLCPY_ARRAY(ifr.ifr_name, ifname);
+
+ rc = ioctl(sk, SIOCGIFINDEX, &ifr);
+ close(sk);
+ if (rc < 0)
+ return rc;
+
+ return ifr.ifr_ifindex;
+}
+
+static int open_socket(int ifindex, const struct gprs_ns2_vc_bind *nsbind)
+{
+ struct sockaddr_ll addr;
+ int fd, rc;
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sll_family = AF_PACKET;
+ addr.sll_protocol = htons(ETH_P_ALL);
+ addr.sll_ifindex = ifindex;
+
+ fd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_HDLC));
+ if (fd < 0) {
+ LOGBIND(nsbind, LOGL_ERROR, "Can not create AF_PACKET socket. Are you root or have CAP_NET_RAW?\n");
+ return fd;
+ }
+
+ /* there's a race condition between the above syscall and the bind() call below,
+ * causing other packets to be received in between */
+
+ rc = bind(fd, (struct sockaddr *)&addr, sizeof(addr));
+ if (rc < 0) {
+ LOGBIND(nsbind, LOGL_ERROR, "Can not bind AF_PACKET socket to ifindex %d\n", ifindex);
+ close(fd);
+ return rc;
+ }
+
+ return fd;
+}
+
+static int gprs_n2_fr_ifupdown_ind_cb(struct osmo_netdev *netdev, bool if_running)
+{
+ struct gprs_ns2_vc_bind *bind = osmo_netdev_get_priv_data(netdev);
+ struct priv_bind *bpriv = bind->priv;
+ struct msgb *msg, *msg2;
+
+ if (bpriv->if_running == if_running)
+ return 0;
+
+ LOGBIND(bind, LOGL_NOTICE, "FR net-device '%s': Physical link state changed: %s\n",
+ bpriv->netif, if_running ? "UP" : "DOWN");
+
+ /* free any backlog, both on IFUP and IFDOWN. Keep the LMI, as it makes
+ * sense to get one out of the door ASAP. */
+ llist_for_each_entry_safe(msg, msg2, &bpriv->backlog.list, list) {
+ msgb_free(msg);
+ }
+
+ if (if_running) {
+ /* interface just came up */
+ if (bpriv->backlog.lmi_msg)
+ osmo_timer_schedule(&bpriv->backlog.timer, 0, bpriv->backlog.retry_us);
+ } else {
+ /* interface just went down; no need to retransmit */
+ osmo_timer_del(&bpriv->backlog.timer);
+ }
+
+ bpriv->if_running = if_running;
+ return 0;
+}
+
+static int gprs_n2_fr_mtu_chg_cb(struct osmo_netdev *netdev, uint32_t new_mtu)
+{
+ struct gprs_ns2_vc_bind *bind = osmo_netdev_get_priv_data(netdev);
+ struct priv_bind *bpriv = bind->priv;
+ struct gprs_ns2_nse *nse;
+
+ /* 2 byte DLCI header */
+ if (new_mtu <= 2)
+ return 0;
+ new_mtu -= 2;
+
+ if (new_mtu == bind->mtu)
+ return 0;
+
+ LOGBIND(bind, LOGL_INFO, "MTU changed from %d to %d.\n",
+ bind->mtu + 2, new_mtu + 2);
+
+ bind->mtu = new_mtu;
+ if (!bpriv->if_running)
+ return 0;
+
+ llist_for_each_entry(nse, &bind->nsi->nse, list) {
+ ns2_nse_update_mtu(nse);
+ }
+ return 0;
+}
+
+static int set_ifupdown(const char *netif, bool up)
+{
+ int sock, rc;
+ struct ifreq req;
+
+ sock = socket(AF_INET, SOCK_DGRAM, 0);
+ if (sock < 0)
+ return sock;
+
+ memset(&req, 0, sizeof req);
+ OSMO_STRLCPY_ARRAY(req.ifr_name, netif);
+
+ rc = ioctl(sock, SIOCGIFFLAGS, &req);
+ if (rc < 0) {
+ close(sock);
+ return rc;
+ }
+
+ if ((req.ifr_flags & IFF_UP) == up) {
+ close(sock);
+ return 0;
+ }
+
+ if (up)
+ req.ifr_flags |= IFF_UP;
+
+ rc = ioctl(sock, SIOCSIFFLAGS, &req);
+ close(sock);
+ return rc;
+}
+
+static int setup_device(const char *netif, const struct gprs_ns2_vc_bind *bind)
+{
+ int sock, rc;
+ char buffer[128];
+ fr_proto *fr = (void*)buffer;
+ struct ifreq req;
+
+ sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP);
+ if (sock < 0) {
+ LOGBIND(bind, LOGL_ERROR, "%s: Unable to create socket: %s\n",
+ netif, strerror(errno));
+ return sock;
+ }
+
+ memset(&req, 0, sizeof(struct ifreq));
+ memset(&buffer, 0, sizeof(buffer));
+ OSMO_STRLCPY_ARRAY(req.ifr_name, netif);
+ req.ifr_settings.ifs_ifsu.sync = (void*)buffer;
+ req.ifr_settings.size = sizeof(buffer);
+ req.ifr_settings.type = IF_GET_PROTO;
+
+ /* EINVAL is returned when no protocol has been set */
+ rc = ioctl(sock, SIOCWANDEV, &req);
+ if (rc < 0 && errno != EINVAL) {
+ LOGBIND(bind, LOGL_ERROR, "%s: Unable to get FR protocol information: %s\n",
+ netif, strerror(errno));
+ goto err;
+ }
+
+ /* check if the device is good */
+ if (rc == 0 && req.ifr_settings.type == IF_PROTO_FR && fr->lmi == LMI_NONE) {
+ LOGBIND(bind, LOGL_NOTICE, "%s: has correct frame relay mode and lmi\n", netif);
+ goto ifup;
+ }
+
+ /* modify the device to match */
+ rc = set_ifupdown(netif, false);
+ if (rc) {
+ LOGBIND(bind, LOGL_ERROR, "Unable to bring down the device %s: %s\n",
+ netif, strerror(errno));
+ goto err;
+ }
+
+ memset(&req, 0, sizeof(struct ifreq));
+ memset(fr, 0, sizeof(fr_proto));
+ OSMO_STRLCPY_ARRAY(req.ifr_name, netif);
+ req.ifr_settings.type = IF_PROTO_FR;
+ req.ifr_settings.size = sizeof(fr_proto);
+ req.ifr_settings.ifs_ifsu.fr = fr;
+ fr->lmi = LMI_NONE;
+ /* even those settings aren't used, they must be in the range */
+ /* polling verification timer*/
+ fr->t391 = 10;
+ /* link integrity verification polling timer */
+ fr->t392 = 15;
+ /* full status polling counter*/
+ fr->n391 = 6;
+ /* error threshold */
+ fr->n392 = 3;
+ /* monitored events count */
+ fr->n393 = 4;
+
+ LOGBIND(bind, LOGL_INFO, "%s: Setting frame relay related parameters\n", netif);
+ rc = ioctl(sock, SIOCWANDEV, &req);
+ if (rc) {
+ LOGBIND(bind, LOGL_ERROR, "%s: Unable to set FR protocol on information: %s\n",
+ netif, strerror(errno));
+ goto err;
+ }
+
+ifup:
+ rc = set_ifupdown(netif, true);
+ if (rc)
+ LOGBIND(bind, LOGL_ERROR, "Unable to bring up the device %s: %s\n",
+ netif, strerror(errno));
+err:
+ close(sock);
+ return rc;
+}
+
+/*! Create a new bind for NS over FR.
+ * \param[in] nsi NS instance in which to create the bind
+ * \param[in] netif Network interface to bind to
+ * \param[in] fr_network
+ * \param[in] fr_role
+ * \param[out] result pointer to the created bind or if a bind with the name exists return the bind.
+ * \return 0 on success; negative on error. -EALREADY returned in case a bind with the name exists */
+int gprs_ns2_fr_bind(struct gprs_ns2_inst *nsi,
+ const char *name,
+ const char *netif,
+ struct osmo_fr_network *fr_network,
+ enum osmo_fr_role fr_role,
+ struct gprs_ns2_vc_bind **result)
+{
+ struct gprs_ns2_vc_bind *bind;
+ struct priv_bind *priv;
+ struct osmo_fr_link *fr_link;
+ int rc = 0;
+
+ if (strlen(netif) > IFNAMSIZ)
+ return -EINVAL;
+
+ bind = gprs_ns2_bind_by_name(nsi, name);
+ if (bind) {
+ if (result)
+ *result = bind;
+ return -EALREADY;
+ }
+
+ rc = ns2_bind_alloc(nsi, name, &bind);
+ if (rc < 0)
+ return rc;
+
+ bind->driver = &vc_driver_fr;
+ bind->ll = GPRS_NS2_LL_FR;
+ /* 2 mbit */
+ bind->transfer_capability = 2;
+ bind->send_vc = fr_vc_sendmsg;
+ bind->free_vc = free_vc;
+ bind->dump_vty = dump_vty;
+ bind->mtu = FRAME_RELAY_SDU;
+ priv = bind->priv = talloc_zero(bind, struct priv_bind);
+ if (!priv) {
+ rc = -ENOMEM;
+ goto err_bind;
+ }
+
+ INIT_LLIST_HEAD(&priv->backlog.list);
+ OSMO_STRLCPY_ARRAY(priv->netif, netif);
+
+ /* FIXME: move fd handling into socket.c */
+ fr_link = osmo_fr_link_alloc(fr_network, fr_role, netif);
+ if (!fr_link) {
+ rc = -EINVAL;
+ goto err_bind;
+ }
+
+ fr_link->tx_cb = fr_tx_cb;
+ fr_link->cb_data = bind;
+ priv->link = fr_link;
+
+ priv->ifindex = rc = devname2ifindex(netif);
+ if (rc < 0) {
+ LOGBIND(bind, LOGL_ERROR, "Can not get interface index for interface %s\n", netif);
+ goto err_fr;
+ }
+
+ priv->netdev = osmo_netdev_alloc(bind, name);
+ if (!priv->netdev) {
+ rc = -ENOENT;
+ goto err_fr;
+ }
+ osmo_netdev_set_priv_data(priv->netdev, bind);
+ osmo_netdev_set_ifupdown_ind_cb(priv->netdev, gprs_n2_fr_ifupdown_ind_cb);
+ osmo_netdev_set_mtu_chg_cb(priv->netdev, gprs_n2_fr_mtu_chg_cb);
+ rc = osmo_netdev_set_ifindex(priv->netdev, priv->ifindex);
+ if (rc < 0)
+ goto err_free_netdev;
+ rc = osmo_netdev_register(priv->netdev);
+ if (rc < 0)
+ goto err_free_netdev;
+
+ /* set protocol frame relay and lmi */
+ rc = setup_device(priv->netif, bind);
+ if(rc < 0) {
+ LOGBIND(bind, LOGL_ERROR, "Failed to setup the interface %s for frame relay and lmi\n", netif);
+ goto err_free_netdev;
+ }
+
+ rc = open_socket(priv->ifindex, bind);
+ if (rc < 0)
+ goto err_free_netdev;
+ priv->backlog.retry_us = 2500; /* start with some non-zero value; this corrsponds to 496 bytes */
+ osmo_timer_setup(&priv->backlog.timer, fr_backlog_timer_cb, bind);
+ osmo_fd_setup(&priv->backlog.ofd, rc, OSMO_FD_READ, fr_netif_ofd_cb, bind, 0);
+ rc = osmo_fd_register(&priv->backlog.ofd);
+ if (rc < 0)
+ goto err_fd;
+
+ if (result)
+ *result = bind;
+
+ return rc;
+
+err_fd:
+ close(priv->backlog.ofd.fd);
+err_free_netdev:
+ osmo_netdev_free(priv->netdev);
+ priv->netdev = NULL;
+err_fr:
+ osmo_fr_link_free(fr_link);
+ priv->link = NULL;
+err_bind:
+ gprs_ns2_free_bind(bind);
+
+ return rc;
+}
+
+/*! Return the frame relay role of a bind
+ * \param[in] bind The bind
+ * \return the frame relay role or -EINVAL if bind is not frame relay
+ */
+enum osmo_fr_role gprs_ns2_fr_bind_role(struct gprs_ns2_vc_bind *bind)
+{
+ struct priv_bind *priv;
+
+ if (bind->driver != &vc_driver_fr)
+ return -EINVAL;
+
+ priv = bind->priv;
+ return priv->link->role;
+}
+
+/*! Return the network interface of the bind
+ * \param[in] bind The bind
+ * \return the network interface
+ */
+const char *gprs_ns2_fr_bind_netif(struct gprs_ns2_vc_bind *bind)
+{
+ struct priv_bind *priv;
+
+ if (bind->driver != &vc_driver_fr)
+ return NULL;
+
+ priv = bind->priv;
+ return priv->netif;
+}
+
+/*! Find NS bind for a given network interface
+ * \param[in] nsi NS instance
+ * \param[in] netif the network interface to search for
+ * \return the bind or NULL if not found
+ */
+struct gprs_ns2_vc_bind *gprs_ns2_fr_bind_by_netif(
+ struct gprs_ns2_inst *nsi,
+ const char *netif)
+{
+ struct gprs_ns2_vc_bind *bind;
+ const char *_netif;
+
+ OSMO_ASSERT(nsi);
+ OSMO_ASSERT(netif);
+
+ llist_for_each_entry(bind, &nsi->binding, list) {
+ if (!gprs_ns2_is_fr_bind(bind))
+ continue;
+
+ _netif = gprs_ns2_fr_bind_netif(bind);
+ if (!strncmp(_netif, netif, IFNAMSIZ))
+ return bind;
+ }
+
+ return NULL;
+}
+
+/*! Create, connect and activate a new FR-based NS-VC
+ * \param[in] bind bind in which the new NS-VC is to be created
+ * \param[in] nsei NSEI of the NS Entity in which the NS-VC is to be created
+ * \param[in] dlci Data Link connection identifier
+ * \return pointer to newly-allocated, connected and activated NS-VC; NULL on error */
+struct gprs_ns2_vc *gprs_ns2_fr_connect(struct gprs_ns2_vc_bind *bind,
+ struct gprs_ns2_nse *nse,
+ uint16_t nsvci,
+ uint16_t dlci)
+{
+ struct gprs_ns2_vc *nsvc = NULL;
+ struct priv_vc *priv = NULL;
+ struct priv_bind *bpriv = bind->priv;
+ char idbuf[64];
+
+ OSMO_ASSERT(gprs_ns2_is_fr_bind(bind));
+ nsvc = gprs_ns2_fr_nsvc_by_dlci(bind, dlci);
+ if (nsvc) {
+ goto err;
+ }
+
+ snprintf(idbuf, sizeof(idbuf), "NSE%05u-NSVC%05u-%s-%s-DLCI%u", nse->nsei, nsvci,
+ gprs_ns2_lltype_str(nse->ll), bpriv->netif, dlci);
+ osmo_identifier_sanitize_buf(idbuf, NULL, '_');
+ nsvc = ns2_vc_alloc(bind, nse, true, GPRS_NS2_VC_MODE_BLOCKRESET, idbuf);
+ if (!nsvc)
+ goto err;
+
+ nsvc->priv = priv = fr_alloc_vc(bind, nsvc, dlci);
+ if (!priv)
+ goto err;
+
+ nsvc->nsvci = nsvci;
+ nsvc->nsvci_is_valid = true;
+
+ return nsvc;
+
+err:
+ gprs_ns2_free_nsvc(nsvc);
+ return NULL;
+}
+
+
+/*! Create, connect and activate a new FR-based NS-VC
+ * \param[in] bind bind in which the new NS-VC is to be created
+ * \param[in] nsei NSEI of the NS Entity in which the NS-VC is to be created
+ * \param[in] dlci Data Link connection identifier
+ * \return pointer to newly-allocated, connected and activated NS-VC; NULL on error */
+struct gprs_ns2_vc *gprs_ns2_fr_connect2(struct gprs_ns2_vc_bind *bind,
+ uint16_t nsei,
+ uint16_t nsvci,
+ uint16_t dlci)
+{
+ bool created_nse = false;
+ struct gprs_ns2_vc *nsvc = NULL;
+ struct gprs_ns2_nse *nse;
+
+ OSMO_ASSERT(gprs_ns2_is_fr_bind(bind));
+ nse = gprs_ns2_nse_by_nsei(bind->nsi, nsei);
+ if (!nse) {
+ nse = gprs_ns2_create_nse(bind->nsi, nsei, GPRS_NS2_LL_FR, GPRS_NS2_DIALECT_STATIC_RESETBLOCK);
+ if (!nse)
+ return NULL;
+ created_nse = true;
+ }
+
+ nsvc = gprs_ns2_fr_connect(bind, nse, nsvci, dlci);
+ if (!nsvc)
+ goto err_nse;
+
+ return nsvc;
+
+err_nse:
+ if (created_nse)
+ gprs_ns2_free_nse(nse);
+
+ return NULL;
+}
+
+/*! Return the nsvc by dlci.
+ * \param[in] bind
+ * \param[in] dlci Data Link connection identifier
+ * \return the nsvc or NULL if not found
+ */
+struct gprs_ns2_vc *gprs_ns2_fr_nsvc_by_dlci(struct gprs_ns2_vc_bind *bind,
+ uint16_t dlci)
+{
+ struct gprs_ns2_vc *nsvc;
+ struct priv_vc *vcpriv;
+
+ OSMO_ASSERT(gprs_ns2_is_fr_bind(bind));
+ llist_for_each_entry(nsvc, &bind->nsvc, blist) {
+ vcpriv = nsvc->priv;
+
+ if (dlci == vcpriv->dlci)
+ return nsvc;
+ }
+
+ return NULL;
+}
+
+/*! Return the dlci of the nsvc
+ * \param[in] nsvc
+ * \return the dlci or 0 on error. 0 is not a valid dlci.
+ */
+uint16_t gprs_ns2_fr_nsvc_dlci(const struct gprs_ns2_vc *nsvc)
+{
+ struct priv_vc *vcpriv;
+
+ if (!nsvc->bind)
+ return 0;
+
+ if (nsvc->bind->driver != &vc_driver_fr)
+ return 0;
+
+ vcpriv = nsvc->priv;
+ return vcpriv->dlci;
+}
diff --git a/src/gb/gprs_ns2_frgre.c b/src/gb/gprs_ns2_frgre.c
index 715c41a6..93913431 100644
--- a/src/gb/gprs_ns2_frgre.c
+++ b/src/gb/gprs_ns2_frgre.c
@@ -180,26 +180,25 @@ static int handle_rx_gre_ipv6(struct osmo_fd *bfd, struct msgb *msg,
inner_ip6h = (struct ip6_hdr *) ((uint8_t *)greh + sizeof(*greh));
if (gre_payload_len < sizeof(*ip6hdr) + sizeof(*inner_greh)) {
- LOGP(DLNS, LOGL_ERROR, "GRE keepalive too short\n");
+ LOGBIND(bind, LOGL_ERROR, "GRE keepalive too short\n");
return -EIO;
}
if (!memcmp(&inner_ip6h->ip6_src, &ip6hdr->ip6_src, sizeof(struct in6_addr)) ||
!memcmp(&inner_ip6h->ip6_dst, &ip6hdr->ip6_dst, sizeof(struct in6_addr))) {
- LOGP(DLNS, LOGL_ERROR,
- "GRE keepalive with wrong tunnel addresses\n");
+ LOGBIND(bind, LOGL_ERROR, "GRE keepalive with wrong tunnel addresses\n");
return -EIO;
}
/* Are IPv6 extensions header are allowed in the *inner*? In the outer they are */
if (inner_ip6h->ip6_ctlun.ip6_un1.ip6_un1_nxt != IPPROTO_GRE) {
- LOGP(DLNS, LOGL_ERROR, "GRE keepalive with wrong protocol\n");
+ LOGBIND(bind, LOGL_ERROR, "GRE keepalive with wrong protocol\n");
return -EIO;
}
inner_greh = (struct gre_hdr *) ((uint8_t *)inner_ip6h + sizeof(struct ip6_hdr));
if (inner_greh->ptype != osmo_htons(GRE_PTYPE_KAR)) {
- LOGP(DLNS, LOGL_ERROR, "GRE keepalive inner GRE type != 0\n");
+ LOGBIND(bind, LOGL_ERROR, "GRE keepalive inner GRE type != 0\n");
return -EIO;
}
@@ -212,7 +211,7 @@ static int handle_rx_gre_ipv6(struct osmo_fd *bfd, struct msgb *msg,
ia6 = ip6hdr->ip6_src;
char ip6str[INET6_ADDRSTRLEN] = {};
inet_ntop(AF_INET6, &ia6, ip6str, INET6_ADDRSTRLEN);
- LOGP(DLNS, LOGL_DEBUG, "GRE keepalive from %s, responding\n", ip6str);
+ LOGBIND(bind, LOGL_DEBUG, "GRE keepalive from %s, responding\n", ip6str);
/* why does it reduce the gre_payload_len by the ipv6 header?
* make it similiar to ipv4 even this seems to be wrong */
@@ -238,25 +237,24 @@ static int handle_rx_gre_ipv4(struct osmo_fd *bfd, struct msgb *msg,
inner_iph = (struct iphdr *) ((uint8_t *)greh + sizeof(*greh));
if (gre_payload_len < inner_iph->ihl*4 + sizeof(*inner_greh)) {
- LOGP(DLNS, LOGL_ERROR, "GRE keepalive too short\n");
+ LOGBIND(bind, LOGL_ERROR, "GRE keepalive too short\n");
return -EIO;
}
if (inner_iph->saddr != iph->daddr ||
inner_iph->daddr != iph->saddr) {
- LOGP(DLNS, LOGL_ERROR,
- "GRE keepalive with wrong tunnel addresses\n");
+ LOGBIND(bind, LOGL_ERROR, "GRE keepalive with wrong tunnel addresses\n");
return -EIO;
}
if (inner_iph->protocol != IPPROTO_GRE) {
- LOGP(DLNS, LOGL_ERROR, "GRE keepalive with wrong protocol\n");
+ LOGBIND(bind, LOGL_ERROR, "GRE keepalive with wrong protocol\n");
return -EIO;
}
inner_greh = (struct gre_hdr *) ((uint8_t *)inner_iph + iph->ihl*4);
if (inner_greh->ptype != osmo_htons(GRE_PTYPE_KAR)) {
- LOGP(DLNS, LOGL_ERROR, "GRE keepalive inner GRE type != 0\n");
+ LOGBIND(bind, LOGL_ERROR, "GRE keepalive inner GRE type != 0\n");
return -EIO;
}
@@ -267,8 +265,7 @@ static int handle_rx_gre_ipv4(struct osmo_fd *bfd, struct msgb *msg,
daddr.sin_port = IPPROTO_GRE;
ia.s_addr = iph->saddr;
- LOGP(DLNS, LOGL_DEBUG, "GRE keepalive from %s, responding\n",
- inet_ntoa(ia));
+ LOGBIND(bind, LOGL_DEBUG, "GRE keepalive from %s, responding\n", inet_ntoa(ia));
/* why does it reduce the gre_payload_len by the ipv4 header? */
return sendto(priv->fd.fd, inner_greh,
@@ -277,7 +274,8 @@ static int handle_rx_gre_ipv4(struct osmo_fd *bfd, struct msgb *msg,
}
static struct msgb *read_nsfrgre_msg(struct osmo_fd *bfd, int *error,
- struct osmo_sockaddr *saddr, uint16_t *dlci)
+ struct osmo_sockaddr *saddr, uint16_t *dlci,
+ const struct gprs_ns2_vc_bind *bind)
{
struct msgb *msg = msgb_alloc(NS_ALLOC_SIZE, "Gb/NS/FR/GRE Rx");
int ret = 0;
@@ -296,8 +294,7 @@ static struct msgb *read_nsfrgre_msg(struct osmo_fd *bfd, int *error,
ret = recvfrom(bfd->fd, msg->data, NS_ALLOC_SIZE, 0,
&saddr->u.sa, &saddr_len);
if (ret < 0) {
- LOGP(DLNS, LOGL_ERROR, "recv error %s during NS-FR-GRE recv\n",
- strerror(errno));
+ LOGBIND(bind, LOGL_ERROR, "recv error %s during NS-FR-GRE recv\n", strerror(errno));
*error = ret;
goto out_err;
} else if (ret == 0) {
@@ -314,6 +311,7 @@ static struct msgb *read_nsfrgre_msg(struct osmo_fd *bfd, int *error,
break;
case AF_INET6:
ip46hdr = sizeof(struct ip6_hdr);
+ break;
default:
*error = -EIO;
goto out_err;
@@ -322,7 +320,7 @@ static struct msgb *read_nsfrgre_msg(struct osmo_fd *bfd, int *error,
/* TODO: add support for the extension headers */
if (msg->len < ip46hdr + sizeof(*greh) + 2) {
- LOGP(DLNS, LOGL_ERROR, "Short IP packet: %u bytes\n", msg->len);
+ LOGBIND(bind, LOGL_ERROR, "Short IP packet: %u bytes\n", msg->len);
*error = -EIO;
goto out_err;
}
@@ -331,7 +329,7 @@ static struct msgb *read_nsfrgre_msg(struct osmo_fd *bfd, int *error,
case AF_INET:
iph = (struct iphdr *) msg->data;
if (msg->len < (iph->ihl*4 + sizeof(*greh) + 2)) {
- LOGP(DLNS, LOGL_ERROR, "Short IP packet: %u bytes\n", msg->len);
+ LOGBIND(bind, LOGL_ERROR, "Short IP packet: %u bytes\n", msg->len);
*error = -EIO;
goto out_err;
}
@@ -341,49 +339,56 @@ static struct msgb *read_nsfrgre_msg(struct osmo_fd *bfd, int *error,
break;
}
- greh = (struct gre_hdr *) (msg->data + iph->ihl*4);
+ if (iph)
+ greh = (struct gre_hdr *) (msg->data + iph->ihl*4);
+ else
+ greh = (struct gre_hdr *) (msg->data + sizeof(struct ip6_hdr));
+
if (greh->flags) {
- LOGP(DLNS, LOGL_NOTICE, "Unknown GRE flags 0x%04x\n",
- osmo_ntohs(greh->flags));
+ LOGBIND(bind, LOGL_NOTICE, "Unknown GRE flags 0x%04x\n", osmo_ntohs(greh->flags));
}
switch (osmo_ntohs(greh->ptype)) {
case GRE_PTYPE_IPv4:
/* IPv4 messages might be GRE keepalives */
- *error = handle_rx_gre_ipv4(bfd, msg, iph, greh);
+ if (iph)
+ *error = handle_rx_gre_ipv4(bfd, msg, iph, greh);
+ else
+ *error = -EIO;
goto out_err;
break;
case GRE_PTYPE_IPv6:
- *error = handle_rx_gre_ipv6(bfd, msg, ip6h, greh);
+ if (ip6h)
+ *error = handle_rx_gre_ipv6(bfd, msg, ip6h, greh);
+ else
+ *error = -EIO;
goto out_err;
break;
case GRE_PTYPE_FR:
/* continue as usual */
break;
default:
- LOGP(DLNS, LOGL_NOTICE, "Unknown GRE protocol 0x%04x != FR\n",
- osmo_ntohs(greh->ptype));
+ LOGBIND(bind, LOGL_NOTICE, "Unknown GRE protocol 0x%04x != FR\n", osmo_ntohs(greh->ptype));
*error = -EIO;
goto out_err;
break;
}
if (msg->len < sizeof(*greh) + 2) {
- LOGP(DLNS, LOGL_ERROR, "Short FR header: %u bytes\n", msg->len);
+ LOGBIND(bind, LOGL_ERROR, "Short FR header: %u bytes\n", msg->len);
*error = -EIO;
goto out_err;
}
frh = (uint8_t *)greh + sizeof(*greh);
if (frh[0] & 0x01) {
- LOGP(DLNS, LOGL_NOTICE, "Unsupported single-byte FR address\n");
+ LOGBIND(bind, LOGL_NOTICE, "Unsupported single-byte FR address\n");
*error = -EIO;
goto out_err;
}
*dlci = ((frh[0] & 0xfc) << 2);
if ((frh[1] & 0x0f) != 0x01) {
- LOGP(DLNS, LOGL_NOTICE, "Unknown second FR octet 0x%02x\n",
- frh[1]);
+ LOGBIND(bind, LOGL_NOTICE, "Unknown second FR octet 0x%02x\n", frh[1]);
*error = -EIO;
goto out_err;
}
@@ -398,9 +403,9 @@ out_err:
return NULL;
}
-static int gprs_ns2_find_vc_by_dlci(struct gprs_ns2_vc_bind *bind,
- uint16_t dlci,
- struct gprs_ns2_vc **result)
+static int ns2_find_vc_by_dlci(struct gprs_ns2_vc_bind *bind,
+ uint16_t dlci,
+ struct gprs_ns2_vc **result)
{
struct gprs_ns2_vc *nsvc;
struct priv_vc *vcpriv;
@@ -429,43 +434,39 @@ static int handle_nsfrgre_read(struct osmo_fd *bfd)
struct msgb *reject;
uint16_t dlci;
- msg = read_nsfrgre_msg(bfd, &rc, &saddr, &dlci);
+ msg = read_nsfrgre_msg(bfd, &rc, &saddr, &dlci, bind);
if (!msg)
return rc;
if (dlci == 0 || dlci == 1023) {
- LOGP(DLNS, LOGL_INFO, "Received FR on LMI DLCI %u - ignoring\n",
- dlci);
+ LOGBIND(bind, LOGL_INFO, "Received FR on LMI DLCI %u - ignoring\n", dlci);
rc = 0;
goto out;
}
- rc = gprs_ns2_find_vc_by_dlci(bind, dlci, &nsvc);
+ rc = ns2_find_vc_by_dlci(bind, dlci, &nsvc);
if (rc) {
/* VC not found */
- rc = ns2_create_vc(bind, msg, "newconnection", &reject, &nsvc);
+ rc = ns2_create_vc(bind, msg, &saddr, "newconnection", &reject, &nsvc);
switch (rc) {
- case GPRS_NS2_CS_FOUND:
- rc = ns2_recv_vc(bind->nsi, nsvc, msg);
+ case NS2_CS_FOUND:
break;
- case GPRS_NS2_CS_ERROR:
- case GPRS_NS2_CS_SKIPPED:
+ case NS2_CS_ERROR:
+ case NS2_CS_SKIPPED:
rc = 0;
- break;
- case GPRS_NS2_CS_REJECTED:
+ goto out;
+ case NS2_CS_REJECTED:
/* nsip_sendmsg will free reject */
- frgre_sendmsg(bind, reject, &saddr);
- return 0;
- case GPRS_NS2_CS_CREATED:
+ rc = frgre_sendmsg(bind, reject, &saddr);
+ goto out;
+ case NS2_CS_CREATED:
frgre_alloc_vc(bind, nsvc, &saddr, dlci);
- gprs_ns2_vc_fsm_start(nsvc);
- rc = ns2_recv_vc(bind->nsi, nsvc, msg);
+ ns2_vc_fsm_start(nsvc);
break;
}
- } else {
- /* VC found */
- rc = ns2_recv_vc(bind->nsi, nsvc, msg);
}
+
+ rc = ns2_recv_vc(nsvc, msg);
out:
msgb_free(msg);
@@ -538,64 +539,66 @@ int gprs_ns2_is_frgre_bind(struct gprs_ns2_vc_bind *bind)
* \param[in] nsi NS instance in which to create the bind
* \param[in] local local address on which to bind
* \param[in] dscp DSCP/TOS bits to use for transmitted data on this bind
- * \param[out] result pointer to created bind
- * \return 0 on success; negative on error */
+ * \param[out] result pointer to the created bind or if a bind with the name exists return the bind.
+ * \return 0 on success; negative on error. -EALREADY returned in case a bind with the name exists */
int gprs_ns2_frgre_bind(struct gprs_ns2_inst *nsi,
- struct osmo_sockaddr *local,
+ const char *name,
+ const struct osmo_sockaddr *local,
int dscp,
struct gprs_ns2_vc_bind **result)
{
- struct gprs_ns2_vc_bind *bind = talloc_zero(nsi, struct gprs_ns2_vc_bind);
+ struct gprs_ns2_vc_bind *bind;
struct priv_bind *priv;
int rc;
- if (!bind)
- return -ENOSPC;
+ if (local->u.sa.sa_family != AF_INET && local->u.sa.sa_family != AF_INET6)
+ return -EINVAL;
- if (local->u.sa.sa_family != AF_INET && local->u.sa.sa_family != AF_INET6) {
- talloc_free(bind);
+ if (dscp < 0 || dscp > 63)
return -EINVAL;
+
+ bind = gprs_ns2_bind_by_name(nsi, name);
+ if (bind) {
+ if (result)
+ *result = bind;
+ return -EALREADY;
}
+ rc = ns2_bind_alloc(nsi, name, &bind);
+ if (rc < 0)
+ return rc;
+
bind->driver = &vc_driver_frgre;
+ bind->ll = GPRS_NS2_LL_FR_GRE;
+ /* 2 mbit transfer capability. Counting should be done different for this. */
+ bind->transfer_capability = 2;
bind->send_vc = frgre_vc_sendmsg;
bind->free_vc = free_vc;
bind->nsi = nsi;
+ /* TODO: allow to set the MTU via vty. It can not be automatic detected because it goes over an
+ * ethernet device and the MTU here must match the FR side of the FR-to-GRE gateway.
+ */
+ bind->mtu = FRAME_RELAY_SDU;
priv = bind->priv = talloc_zero(bind, struct priv_bind);
if (!priv) {
- talloc_free(bind);
- return -ENOSPC;
+ gprs_ns2_free_bind(bind);
+ return -ENOMEM;
}
priv->fd.cb = frgre_fd_cb;
priv->fd.data = bind;
priv->addr = *local;
INIT_LLIST_HEAD(&bind->nsvc);
-
- llist_add(&bind->list, &nsi->binding);
+ priv->dscp = dscp;
rc = osmo_sock_init_osa_ofd(&priv->fd, SOCK_RAW, IPPROTO_GRE,
local, NULL,
- OSMO_SOCK_F_BIND);
+ OSMO_SOCK_F_BIND | OSMO_SOCK_F_DSCP(priv->dscp));
if (rc < 0) {
- talloc_free(priv);
- talloc_free(bind);
+ gprs_ns2_free_bind(bind);
return rc;
}
- if (dscp > 0) {
- priv->dscp = dscp;
-
- rc = setsockopt(priv->fd.fd, IPPROTO_IP, IP_TOS,
- &dscp, sizeof(dscp));
- if (rc < 0)
- LOGP(DLNS, LOGL_ERROR,
- "Failed to set the DSCP to %d with ret(%d) errno(%d)\n",
- dscp, rc, errno);
- }
-
- ns2_vty_bind_apply(bind);
-
if (result)
*result = bind;
diff --git a/src/gb/gprs_ns2_internal.h b/src/gb/gprs_ns2_internal.h
index 708eb59c..4f332219 100644
--- a/src/gb/gprs_ns2_internal.h
+++ b/src/gb/gprs_ns2_internal.h
@@ -5,9 +5,54 @@
#include <stdbool.h>
#include <stdint.h>
+#include <osmocom/core/logging.h>
#include <osmocom/gprs/protocol/gsm_08_16.h>
#include <osmocom/gprs/gprs_ns2.h>
+#define LOGNSE(nse, lvl, fmt, args ...) \
+ LOGP(DLNS, lvl, "NSE(%05u) " fmt, (nse)->nsei, ## args)
+
+#define LOGBIND(bind, lvl, fmt, args ...) \
+ LOGP(DLNS, lvl, "BIND(%s) " fmt, (bind)->name, ## args)
+
+#define LOGNSVC_SS(ss, nsvc, lvl, fmt, args ...) \
+ do { \
+ if ((nsvc)->nsvci_is_valid) { \
+ LOGP(ss, lvl, "NSE(%05u)-NSVC(%05u) " fmt, \
+ (nsvc)->nse->nsei, (nsvc)->nsvci, ## args); \
+ } else { \
+ LOGP(ss, lvl, "NSE(%05u)-NSVC(none) " fmt, \
+ (nsvc)->nse->nsei, ## args); \
+ } \
+ } while (0)
+
+#define LOGNSVC(nsvc, lvl, fmt, args ...) \
+ LOGNSVC_SS(DLNS, nsvc, lvl, fmt, ## args)
+
+#define LOG_NS_SIGNAL(nsvc, direction, pdu_type, lvl, fmt, args ...) \
+ LOGNSVC_SS(DLNSSIGNAL, nsvc, lvl, "%s %s" fmt, direction, get_value_string(gprs_ns_pdu_strings, pdu_type), ## args)
+
+#define LOG_NS_DATA(nsvc, direction, pdu_type, lvl, fmt, args ...) \
+ LOGNSVC_SS(DLNSDATA, nsvc, lvl, "%s %s" fmt, direction, get_value_string(gprs_ns_pdu_strings, pdu_type), ## args)
+
+#define LOG_NS_RX_SIGNAL(nsvc, pdu_type) LOG_NS_SIGNAL(nsvc, "Rx", pdu_type, LOGL_INFO, "\n")
+#define LOG_NS_TX_SIGNAL(nsvc, pdu_type) LOG_NS_SIGNAL(nsvc, "Tx", pdu_type, LOGL_INFO, "\n")
+
+#define RATE_CTR_INC_NS(nsvc, ctr) \
+ do { \
+ struct gprs_ns2_vc *_nsvc = (nsvc); \
+ rate_ctr_inc(rate_ctr_group_get_ctr(_nsvc->ctrg, ctr)); \
+ rate_ctr_inc(rate_ctr_group_get_ctr(_nsvc->nse->ctrg, ctr)); \
+ } while (0)
+
+#define RATE_CTR_ADD_NS(nsvc, ctr, val) \
+ do { \
+ struct gprs_ns2_vc *_nsvc = (nsvc); \
+ rate_ctr_add(rate_ctr_group_get_ctr(_nsvc->ctrg, ctr), val); \
+ rate_ctr_add(rate_ctr_group_get_ctr(_nsvc->nse->ctrg, ctr), val); \
+ } while (0)
+
+
struct osmo_fsm_inst;
struct tlv_parsed;
struct vty;
@@ -15,10 +60,8 @@ struct vty;
struct gprs_ns2_vc_driver;
struct gprs_ns2_vc_bind;
-
-
-#define NS_TIMERS_COUNT 8
-#define NS_TIMERS "(tns-block|tns-block-retries|tns-reset|tns-reset-retries|tns-test|tns-alive|tns-alive-retries|tsns-prov)"
+#define NS_TIMERS_COUNT 11
+#define NS_TIMERS "(tns-block|tns-block-retries|tns-reset|tns-reset-retries|tns-test|tns-alive|tns-alive-retries|tsns-prov|tsns-size-retries|tsns-config-retries|tsns-procedures-retries)"
#define NS_TIMERS_HELP \
"(un)blocking Timer (Tns-block) timeout\n" \
"(un)blocking Timer (Tns-block) number of retries\n" \
@@ -27,7 +70,10 @@ struct gprs_ns2_vc_bind;
"Test Timer (Tns-test) timeout\n" \
"Alive Timer (Tns-alive) timeout\n" \
"Alive Timer (Tns-alive) number of retries\n" \
- "SNS Provision Timer (Tsns-prov) timeout\n"
+ "SNS Provision Timer (Tsns-prov) timeout\n" \
+ "SNS Size number of retries\n" \
+ "SNS Config number of retries\n" \
+ "SNS Procedures number of retries\n" \
/* Educated guess - LLC user payload is 1500 bytes plus possible headers */
#define NS_ALLOC_SIZE 3072
@@ -42,6 +88,9 @@ enum ns2_timeout {
NS_TOUT_TNS_ALIVE,
NS_TOUT_TNS_ALIVE_RETRIES,
NS_TOUT_TSNS_PROV,
+ NS_TOUT_TSNS_SIZE_RETRIES,
+ NS_TOUT_TSNS_CONFIG_RETRIES,
+ NS_TOUT_TSNS_PROCEDURES_RETRIES,
};
enum nsvc_timer_mode {
@@ -52,26 +101,40 @@ enum nsvc_timer_mode {
_NSVC_TIMER_NR,
};
-enum ns_stat {
+enum ns2_vc_stat {
NS_STAT_ALIVE_DELAY,
};
-/*! Osmocom NS link layer types */
-enum gprs_ns_ll {
- GPRS_NS_LL_UDP, /*!< NS/UDP/IP */
- GPRS_NS_LL_E1, /*!< NS/E1 */
- GPRS_NS_LL_FR_GRE, /*!< NS/FR/GRE/IP */
+enum ns2_bind_stat {
+ NS2_BIND_STAT_BACKLOG_LEN,
};
/*! Osmocom NS2 VC create status */
-enum gprs_ns2_cs {
- GPRS_NS2_CS_CREATED, /*!< A NSVC object has been created */
- GPRS_NS2_CS_FOUND, /*!< A NSVC object has been found */
- GPRS_NS2_CS_REJECTED, /*!< Rejected and answered message */
- GPRS_NS2_CS_SKIPPED, /*!< Skipped message */
- GPRS_NS2_CS_ERROR, /*!< Failed to process message */
+enum ns2_cs {
+ NS2_CS_CREATED, /*!< A NSVC object has been created */
+ NS2_CS_FOUND, /*!< A NSVC object has been found */
+ NS2_CS_REJECTED, /*!< Rejected and answered message */
+ NS2_CS_SKIPPED, /*!< Skipped message */
+ NS2_CS_ERROR, /*!< Failed to process message */
};
+enum ns_ctr {
+ NS_CTR_PKTS_IN,
+ NS_CTR_PKTS_OUT,
+ NS_CTR_PKTS_OUT_DROP,
+ NS_CTR_BYTES_IN,
+ NS_CTR_BYTES_OUT,
+ NS_CTR_BYTES_OUT_DROP,
+ NS_CTR_BLOCKED,
+ NS_CTR_UNBLOCKED,
+ NS_CTR_DEAD,
+ NS_CTR_REPLACED,
+ NS_CTR_NSEI_CHG,
+ NS_CTR_INV_VCI,
+ NS_CTR_INV_NSEI,
+ NS_CTR_LOST_ALIVE,
+ NS_CTR_LOST_RESET,
+};
#define NSE_S_BLOCKED 0x0001
#define NSE_S_ALIVE 0x0002
@@ -95,15 +158,14 @@ struct gprs_ns2_inst {
/*! linked lists of all NSVC in this instance */
struct llist_head nse;
- /*! create dynamic NSE on receiving packages */
- bool create_nse;
-
uint16_t timeout[NS_TIMERS_COUNT];
/*! workaround for rate counter until rate counter accepts char str as index */
- uint32_t rate_ctr_idx;
+ uint32_t nsvc_rate_ctr_idx;
+ uint32_t bind_rate_ctr_idx;
};
+
/*! Structure repesenting a NSE. The BSS/PCU will only have a single NSE, while SGSN has one for each BSS/PCU */
struct gprs_ns2_nse {
uint16_t nsei;
@@ -117,13 +179,47 @@ struct gprs_ns2_nse {
/*! llist head to hold all nsvc */
struct llist_head nsvc;
+ /*! count all active NSVCs */
+ int nsvc_count;
+
/*! true if this NSE was created by VTY or pcu socket) */
bool persistent;
+ /*! true if this NSE wasn't yet alive at all.
+ * Will be true after the first status ind with NS_AFF_CAUSE_RECOVERY */
+ bool first;
+
/*! true if this NSE has at least one alive VC */
bool alive;
+ /*! which link-layer are we based on? */
+ enum gprs_ns2_ll ll;
+
+ /*! which dialect does this NSE speaks? */
+ enum gprs_ns2_dialect dialect;
+
struct osmo_fsm_inst *bss_sns_fi;
+
+ /*! sum of all the data weight of _alive_ NS-VCs */
+ uint32_t sum_data_weight;
+
+ /*! sum of all the signalling weight of _alive_ NS-VCs */
+ uint32_t sum_sig_weight;
+
+ /*! MTU of a NS PDU. This is the lowest MTU of all NSVCs */
+ uint16_t mtu;
+
+ /*! are we implementing the SGSN role? */
+ bool ip_sns_role_sgsn;
+
+ /*! NSE-wide statistics */
+ struct rate_ctr_group *ctrg;
+
+ /*! recursive anchor */
+ bool freed;
+
+ /*! when the NSE became alive or dead */
+ struct timespec ts_alive_change;
};
/*! Structure representing a single NS-VC */
@@ -149,27 +245,40 @@ struct gprs_ns2_vc {
/*! signalling weight. 0 = don't use for signalling (BVCI == 0)*/
uint8_t sig_weight;
- /*! signaling weight. 0 = don't use for user data (BVCI != 0) */
+ /*! signalling packet counter for the load sharing function */
+ uint8_t sig_counter;
+
+ /*! data weight. 0 = don't use for user data (BVCI != 0) */
uint8_t data_weight;
/*! can be used by the bind/driver of the virtual circuit. e.g. ipv4/ipv6/frgre/e1 */
void *priv;
bool nsvci_is_valid;
+ /*! should this NS-VC only be used for SNS-SIZE and SNS-CONFIG? */
bool sns_only;
struct rate_ctr_group *ctrg;
struct osmo_stat_item_group *statg;
- /*! which link-layer are we based on? */
- enum gprs_ns_ll ll;
enum gprs_ns2_vc_mode mode;
struct osmo_fsm_inst *fi;
+
+ /*! recursive anchor */
+ bool freed;
+
+ /*! if blocked by O&M/vty */
+ bool om_blocked;
+
+ /*! when the NSVC became alive or dead */
+ struct timespec ts_alive_change;
};
/*! Structure repesenting a bind instance. E.g. IPv4 listen port. */
struct gprs_ns2_vc_bind {
+ /*! unique name */
+ const char *name;
/*! list entry in nsi */
struct llist_head list;
/*! list of all VC */
@@ -180,16 +289,38 @@ struct gprs_ns2_vc_bind {
struct gprs_ns2_inst *nsi;
struct gprs_ns2_vc_driver *driver;
- /*! if VCs use reset/block/unblock method. IP shall not use this */
- enum gprs_ns2_vc_mode vc_mode;
+ bool accept_ipaccess;
+ bool accept_sns;
+
+ /*! transfer capability in mbit */
+ int transfer_capability;
+
+ /*! MTU of a NS PDU on this bind. */
+ uint16_t mtu;
+
+ /*! which link-layer are we based on? */
+ enum gprs_ns2_ll ll;
/*! send a msg over a VC */
int (*send_vc)(struct gprs_ns2_vc *nsvc, struct msgb *msg);
/*! free the vc priv data */
void (*free_vc)(struct gprs_ns2_vc *nsvc);
-};
+ /*! allow to show information for the vty */
+ void (*dump_vty)(const struct gprs_ns2_vc_bind *bind,
+ struct vty *vty, bool stats);
+
+ /*! the IP-SNS signalling weight when doing dynamic configuration */
+ uint8_t sns_sig_weight;
+ /*! the IP-SNS data weight when doing dynamic configuration */
+ uint8_t sns_data_weight;
+
+ struct osmo_stat_item_group *statg;
+
+ /*! recursive anchor */
+ bool freed;
+};
struct gprs_ns2_vc_driver {
const char *name;
@@ -197,34 +328,63 @@ struct gprs_ns2_vc_driver {
void (*free_bind)(struct gprs_ns2_vc_bind *driver);
};
-enum gprs_ns2_cs ns2_create_vc(struct gprs_ns2_vc_bind *bind,
+enum ns2_sns_event {
+ NS2_SNS_EV_REQ_SELECT_ENDPOINT, /*!< Select a SNS endpoint from the list */
+ NS2_SNS_EV_RX_SIZE,
+ NS2_SNS_EV_RX_SIZE_ACK,
+ NS2_SNS_EV_RX_CONFIG,
+ NS2_SNS_EV_RX_CONFIG_END, /*!< SNS-CONFIG with end flag received */
+ NS2_SNS_EV_RX_CONFIG_ACK,
+ NS2_SNS_EV_RX_ADD,
+ NS2_SNS_EV_RX_DELETE,
+ NS2_SNS_EV_RX_CHANGE_WEIGHT,
+ NS2_SNS_EV_RX_ACK, /*!< Rx of SNS-ACK (response to ADD/DELETE/CHG_WEIGHT */
+ NS2_SNS_EV_REQ_NO_NSVC, /*!< no more NS-VC remaining (all dead) */
+ NS2_SNS_EV_REQ_FREE_NSVCS, /*!< free all NS-VCs */
+ NS2_SNS_EV_REQ_NSVC_ALIVE, /*!< a NS-VC became alive */
+ NS2_SNS_EV_REQ_ADD_BIND, /*!< add a new local bind to this NSE */
+ NS2_SNS_EV_REQ_DELETE_BIND, /*!< remove a local bind from this NSE */
+ NS2_SNS_EV_REQ_CHANGE_WEIGHT, /*!< a bind changed its weight */
+};
+
+enum ns2_cs ns2_create_vc(struct gprs_ns2_vc_bind *bind,
struct msgb *msg,
+ const struct osmo_sockaddr *remote,
const char *logname,
struct msgb **reject,
struct gprs_ns2_vc **success);
-int ns2_recv_vc(struct gprs_ns2_inst *nsi,
- struct gprs_ns2_vc *nsvc,
+int ns2_recv_vc(struct gprs_ns2_vc *nsvc,
struct msgb *msg);
struct gprs_ns2_vc *ns2_vc_alloc(struct gprs_ns2_vc_bind *bind,
struct gprs_ns2_nse *nse,
- bool initiater);
+ bool initiater,
+ enum gprs_ns2_vc_mode vc_mode,
+ const char *id);
+
+void ns2_free_nsvcs(struct gprs_ns2_nse *nse);
+int ns2_bind_alloc(struct gprs_ns2_inst *nsi, const char *name,
+ struct gprs_ns2_vc_bind **result);
-struct msgb *gprs_ns2_msgb_alloc(void);
+struct msgb *ns2_msgb_alloc(void);
-void gprs_ns2_sns_dump_vty(struct vty *vty, const struct gprs_ns2_nse *nse, bool stats);
-void ns2_prim_status_ind(struct gprs_ns2_inst *nsi,
- uint16_t nsei, uint16_t bvci,
+void ns2_sns_write_vty(struct vty *vty, const struct gprs_ns2_nse *nse);
+void ns2_sns_dump_vty(struct vty *vty, const char *prefix, const struct gprs_ns2_nse *nse, bool stats);
+void ns2_prim_status_ind(struct gprs_ns2_nse *nse,
+ struct gprs_ns2_vc *nsvc,
+ uint16_t bvci,
enum gprs_ns2_affecting_cause cause);
void ns2_nse_notify_alive(struct gprs_ns2_vc *nsvc, bool alive);
+void ns2_nse_update_mtu(struct gprs_ns2_nse *nse);
+int ns2_nse_set_dialect(struct gprs_ns2_nse *nse, enum gprs_ns2_dialect dialect);
/* message */
-int gprs_ns2_validate(struct gprs_ns2_vc *nsvc,
- uint8_t pdu_type,
- struct msgb *msg,
- struct tlv_parsed *tp,
- uint8_t *cause);
+int ns2_validate(struct gprs_ns2_vc *nsvc,
+ uint8_t pdu_type,
+ struct msgb *msg,
+ struct tlv_parsed *tp,
+ uint8_t *cause);
/* SNS messages */
int ns2_tx_sns_ack(struct gprs_ns2_vc *nsvc, uint8_t trans_id, uint8_t *cause,
@@ -242,9 +402,28 @@ int ns2_tx_sns_size(struct gprs_ns2_vc *nsvc, bool reset_flag, uint16_t max_nr_n
int ip4_ep_nr, int ip6_ep_nr);
int ns2_tx_sns_size_ack(struct gprs_ns2_vc *nsvc, uint8_t *cause);
+int ns2_tx_sns_add(struct gprs_ns2_vc *nsvc,
+ uint8_t trans_id,
+ const struct gprs_ns_ie_ip4_elem *ip4_elems,
+ unsigned int num_ip4_elems,
+ const struct gprs_ns_ie_ip6_elem *ip6_elems,
+ unsigned int num_ip6_elems);
+int ns2_tx_sns_change_weight(struct gprs_ns2_vc *nsvc,
+ uint8_t trans_id,
+ const struct gprs_ns_ie_ip4_elem *ip4_elems,
+ unsigned int num_ip4_elems,
+ const struct gprs_ns_ie_ip6_elem *ip6_elems,
+ unsigned int num_ip6_elems);
+int ns2_tx_sns_del(struct gprs_ns2_vc *nsvc,
+ uint8_t trans_id,
+ const struct gprs_ns_ie_ip4_elem *ip4_elems,
+ unsigned int num_ip4_elems,
+ const struct gprs_ns_ie_ip6_elem *ip6_elems,
+ unsigned int num_ip6_elems);
+
/* transmit message over a VC */
-int ns2_tx_block(struct gprs_ns2_vc *nsvc, uint8_t cause);
-int ns2_tx_block_ack(struct gprs_ns2_vc *nsvc);
+int ns2_tx_block(struct gprs_ns2_vc *nsvc, uint8_t cause, uint16_t *nsvci);
+int ns2_tx_block_ack(struct gprs_ns2_vc *nsvc, uint16_t *nsvci);
int ns2_tx_reset(struct gprs_ns2_vc *nsvc, uint8_t cause);
int ns2_tx_reset_ack(struct gprs_ns2_vc *nsvc);
@@ -260,31 +439,44 @@ int ns2_tx_unit_data(struct gprs_ns2_vc *nsvc,
struct msgb *msg);
int ns2_tx_status(struct gprs_ns2_vc *nsvc, uint8_t cause,
- uint16_t bvci, struct msgb *orig_msg);
+ uint16_t bvci, struct msgb *orig_msg, uint16_t *nsvci);
/* driver */
-struct gprs_ns2_vc *gprs_ns2_ip_bind_connect(struct gprs_ns2_vc_bind *bind,
- struct gprs_ns2_nse *nse,
- struct osmo_sockaddr *remote);
+struct gprs_ns2_vc *ns2_ip_bind_connect(struct gprs_ns2_vc_bind *bind,
+ struct gprs_ns2_nse *nse,
+ const struct osmo_sockaddr *remote);
+int ns2_ip_count_bind(struct gprs_ns2_inst *nsi, struct osmo_sockaddr *remote);
+struct gprs_ns2_vc_bind *ns2_ip_get_bind_by_index(struct gprs_ns2_inst *nsi,
+ struct osmo_sockaddr *remote,
+ int index);
/* sns */
-int gprs_ns2_sns_rx(struct gprs_ns2_vc *nsvc, struct msgb *msg, struct tlv_parsed *tp);
+int ns2_sns_rx(struct gprs_ns2_vc *nsvc, struct msgb *msg, struct tlv_parsed *tp);
struct osmo_fsm_inst *ns2_sns_bss_fsm_alloc(struct gprs_ns2_nse *nse,
const char *id);
-int ns2_sns_bss_fsm_start(struct gprs_ns2_nse *nse, struct gprs_ns2_vc *nsvc,
- struct osmo_sockaddr *remote);
-void ns2_sns_free_nsvc(struct gprs_ns2_vc *nsvc);
+struct osmo_fsm_inst *ns2_sns_sgsn_fsm_alloc(struct gprs_ns2_nse *nse, const char *id);
+void ns2_sns_replace_nsvc(struct gprs_ns2_vc *nsvc);
+void ns2_sns_notify_alive(struct gprs_ns2_nse *nse, struct gprs_ns2_vc *nsvc, bool alive);
+void ns2_sns_update_weights(struct gprs_ns2_vc_bind *bind);
/* vc */
-struct osmo_fsm_inst *gprs_ns2_vc_fsm_alloc(struct gprs_ns2_vc *nsvc,
+struct osmo_fsm_inst *ns2_vc_fsm_alloc(struct gprs_ns2_vc *nsvc,
const char *id, bool initiate);
-int gprs_ns2_vc_fsm_start(struct gprs_ns2_vc *nsvc);
-int gprs_ns2_vc_rx(struct gprs_ns2_vc *nsvc, struct msgb *msg, struct tlv_parsed *tp);
-int gprs_ns2_vc_is_alive(struct gprs_ns2_vc *nsvc);
-int gprs_ns2_vc_is_unblocked(struct gprs_ns2_vc *nsvc);
-
-/* vty.c */
-void ns2_vty_bind_apply(struct gprs_ns2_vc_bind *bind);
+int ns2_vc_fsm_start(struct gprs_ns2_vc *nsvc);
+int ns2_vc_force_unconfigured(struct gprs_ns2_vc *nsvc);
+int ns2_vc_rx(struct gprs_ns2_vc *nsvc, struct msgb *msg, struct tlv_parsed *tp);
+int ns2_vc_is_alive(struct gprs_ns2_vc *nsvc);
+int ns2_vc_is_unblocked(struct gprs_ns2_vc *nsvc);
+int ns2_vc_block(struct gprs_ns2_vc *nsvc);
+int ns2_vc_reset(struct gprs_ns2_vc *nsvc);
+int ns2_vc_unblock(struct gprs_ns2_vc *nsvc);
+void ns2_vty_dump_nsvc(struct vty *vty, struct gprs_ns2_vc *nsvc, bool stats);
/* nse */
void ns2_nse_notify_unblocked(struct gprs_ns2_vc *nsvc, bool unblocked);
+enum gprs_ns2_vc_mode ns2_dialect_to_vc_mode(enum gprs_ns2_dialect dialect);
+int ns2_count_transfer_cap(struct gprs_ns2_nse *nse,
+ uint16_t bvci);
+
+/* vty */
+int ns2_sns_add_sns_default_binds(struct gprs_ns2_nse *nse);
diff --git a/src/gb/gprs_ns2_message.c b/src/gb/gprs_ns2_message.c
index fac6108c..1b986feb 100644
--- a/src/gb/gprs_ns2_message.c
+++ b/src/gb/gprs_ns2_message.c
@@ -43,30 +43,13 @@
do { \
if (!nsvc->nse->bss_sns_fi) \
break; \
- LOGP(DLNS, LOGL_DEBUG, "NSEI=%u Rx invalid packet %s with SNS\n", \
- nsvc->nse->nsei, reason); \
+ LOGNSVC(nsvc, LOGL_DEBUG, "invalid packet %s with SNS\n", reason); \
} while (0)
-enum ns_ctr {
- NS_CTR_PKTS_IN,
- NS_CTR_PKTS_OUT,
- NS_CTR_BYTES_IN,
- NS_CTR_BYTES_OUT,
- NS_CTR_BLOCKED,
- NS_CTR_DEAD,
- NS_CTR_REPLACED,
- NS_CTR_NSEI_CHG,
- NS_CTR_INV_VCI,
- NS_CTR_INV_NSEI,
- NS_CTR_LOST_ALIVE,
- NS_CTR_LOST_RESET,
-};
-
-
-
-static int gprs_ns2_validate_reset(struct gprs_ns2_vc *nsvc, struct msgb *msg, struct tlv_parsed *tp, uint8_t *cause)
+static int ns2_validate_reset(struct gprs_ns2_vc *nsvc, struct msgb *msg, struct tlv_parsed *tp, uint8_t *cause)
{
- if (!TLVP_PRESENT(tp, NS_IE_CAUSE) || !TLVP_PRESENT(tp, NS_IE_VCI) || !TLVP_PRESENT(tp, NS_IE_NSEI)) {
+ if (!TLVP_PRES_LEN(tp, NS_IE_CAUSE, 1) ||
+ !TLVP_PRES_LEN(tp, NS_IE_VCI, 2) || !TLVP_PRES_LEN(tp, NS_IE_NSEI, 2)) {
*cause = NS_CAUSE_MISSING_ESSENT_IE;
return -1;
}
@@ -74,9 +57,9 @@ static int gprs_ns2_validate_reset(struct gprs_ns2_vc *nsvc, struct msgb *msg, s
return 0;
}
-static int gprs_ns2_validate_reset_ack(struct gprs_ns2_vc *nsvc, struct msgb *msg, struct tlv_parsed *tp, uint8_t *cause)
+static int ns2_validate_reset_ack(struct gprs_ns2_vc *nsvc, struct msgb *msg, struct tlv_parsed *tp, uint8_t *cause)
{
- if (!TLVP_PRESENT(tp, NS_IE_VCI) || !TLVP_PRESENT(tp, NS_IE_NSEI)) {
+ if (!TLVP_PRES_LEN(tp, NS_IE_VCI, 2) || !TLVP_PRES_LEN(tp, NS_IE_NSEI, 2)) {
*cause = NS_CAUSE_MISSING_ESSENT_IE;
return -1;
}
@@ -84,9 +67,9 @@ static int gprs_ns2_validate_reset_ack(struct gprs_ns2_vc *nsvc, struct msgb *ms
return 0;
}
-static int gprs_ns2_validate_block(struct gprs_ns2_vc *nsvc, struct msgb *msg, struct tlv_parsed *tp, uint8_t *cause)
+static int ns2_validate_block(struct gprs_ns2_vc *nsvc, struct msgb *msg, struct tlv_parsed *tp, uint8_t *cause)
{
- if (!TLVP_PRESENT(tp, NS_IE_VCI) || !TLVP_PRESENT(tp, NS_IE_CAUSE)) {
+ if (!TLVP_PRES_LEN(tp, NS_IE_VCI, 2) || !TLVP_PRES_LEN(tp, NS_IE_CAUSE, 1)) {
*cause = NS_CAUSE_MISSING_ESSENT_IE;
return -1;
}
@@ -94,9 +77,9 @@ static int gprs_ns2_validate_block(struct gprs_ns2_vc *nsvc, struct msgb *msg, s
return 0;
}
-static int gprs_ns2_validate_block_ack(struct gprs_ns2_vc *nsvc, struct msgb *msg, struct tlv_parsed *tp, uint8_t *cause)
+static int ns2_validate_block_ack(struct gprs_ns2_vc *nsvc, struct msgb *msg, struct tlv_parsed *tp, uint8_t *cause)
{
- if (!TLVP_PRESENT(tp, NS_IE_VCI)) {
+ if (!TLVP_PRES_LEN(tp, NS_IE_VCI, 2)) {
*cause = NS_CAUSE_MISSING_ESSENT_IE;
return -1;
}
@@ -104,42 +87,46 @@ static int gprs_ns2_validate_block_ack(struct gprs_ns2_vc *nsvc, struct msgb *ms
return 0;
}
-static int gprs_ns2_validate_status(struct gprs_ns2_vc *nsvc, struct msgb *msg, struct tlv_parsed *tp, uint8_t *cause)
+static int ns2_validate_status(struct gprs_ns2_vc *nsvc, struct msgb *msg, struct tlv_parsed *tp, uint8_t *cause)
{
- if (!TLVP_PRESENT(tp, NS_IE_CAUSE)) {
+ if (!TLVP_PRES_LEN(tp, NS_IE_CAUSE, 1)) {
*cause = NS_CAUSE_MISSING_ESSENT_IE;
return -1;
}
- uint8_t _cause = tlvp_val8(tp, NS_IE_VCI, 0);
-
+ uint8_t _cause = tlvp_val8(tp, NS_IE_CAUSE, 0);
switch (_cause) {
case NS_CAUSE_NSVC_BLOCKED:
case NS_CAUSE_NSVC_UNKNOWN:
- if (!TLVP_PRESENT(tp, NS_IE_CAUSE)) {
+ if (!TLVP_PRES_LEN(tp, NS_IE_VCI, 1)) {
*cause = NS_CAUSE_MISSING_ESSENT_IE;
return -1;
}
+
+ if (nsvc->mode != GPRS_NS2_VC_MODE_BLOCKRESET) {
+ *cause = NS_CAUSE_PDU_INCOMP_PSTATE;
+ return -1;
+ }
break;
case NS_CAUSE_SEM_INCORR_PDU:
case NS_CAUSE_PDU_INCOMP_PSTATE:
case NS_CAUSE_PROTO_ERR_UNSPEC:
case NS_CAUSE_INVAL_ESSENT_IE:
case NS_CAUSE_MISSING_ESSENT_IE:
- if (!TLVP_PRESENT(tp, NS_IE_CAUSE)) {
+ if (!TLVP_PRES_LEN(tp, NS_IE_PDU, 1)) {
*cause = NS_CAUSE_MISSING_ESSENT_IE;
return -1;
}
break;
case NS_CAUSE_BVCI_UNKNOWN:
- if (!TLVP_PRESENT(tp, NS_IE_BVCI)) {
+ if (!TLVP_PRES_LEN(tp, NS_IE_BVCI, 2)) {
*cause = NS_CAUSE_MISSING_ESSENT_IE;
return -1;
}
break;
case NS_CAUSE_UNKN_IP_TEST_FAILED:
- if (!TLVP_PRESENT (tp, NS_IE_IPv4_LIST) && !TLVP_PRESENT(tp, NS_IE_IPv6_LIST)) {
+ if (!TLVP_PRESENT(tp, NS_IE_IPv4_LIST) && !TLVP_PRESENT(tp, NS_IE_IPv6_LIST)) {
*cause = NS_CAUSE_MISSING_ESSENT_IE;
return -1;
}
@@ -149,23 +136,23 @@ static int gprs_ns2_validate_status(struct gprs_ns2_vc *nsvc, struct msgb *msg,
return 0;
}
-int gprs_ns2_validate(struct gprs_ns2_vc *nsvc,
- uint8_t pdu_type,
- struct msgb *msg,
- struct tlv_parsed *tp,
- uint8_t *cause)
+int ns2_validate(struct gprs_ns2_vc *nsvc,
+ uint8_t pdu_type,
+ struct msgb *msg,
+ struct tlv_parsed *tp,
+ uint8_t *cause)
{
switch (pdu_type) {
case NS_PDUT_RESET:
- return gprs_ns2_validate_reset(nsvc, msg, tp, cause);
+ return ns2_validate_reset(nsvc, msg, tp, cause);
case NS_PDUT_RESET_ACK:
- return gprs_ns2_validate_reset_ack(nsvc, msg, tp, cause);
+ return ns2_validate_reset_ack(nsvc, msg, tp, cause);
case NS_PDUT_BLOCK:
- return gprs_ns2_validate_block(nsvc, msg, tp, cause);
+ return ns2_validate_block(nsvc, msg, tp, cause);
case NS_PDUT_BLOCK_ACK:
- return gprs_ns2_validate_block_ack(nsvc, msg, tp, cause);
+ return ns2_validate_block_ack(nsvc, msg, tp, cause);
case NS_PDUT_STATUS:
- return gprs_ns2_validate_status(nsvc, msg, tp, cause);
+ return ns2_validate_status(nsvc, msg, tp, cause);
/* following PDUs doesn't have any payloads */
case NS_PDUT_ALIVE:
@@ -183,12 +170,31 @@ int gprs_ns2_validate(struct gprs_ns2_vc *nsvc,
}
+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;
+}
+
/* transmit functions */
static int ns2_tx_simple(struct gprs_ns2_vc *nsvc, uint8_t pdu_type)
{
- struct msgb *msg = gprs_ns2_msgb_alloc();
+ struct msgb *msg = ns2_msgb_alloc();
struct gprs_ns_hdr *nsh;
+ log_set_context(LOG_CTX_GB_NSE, nsvc->nse);
log_set_context(LOG_CTX_GB_NSVC, nsvc);
if (!msg)
@@ -196,77 +202,87 @@ static int ns2_tx_simple(struct gprs_ns2_vc *nsvc, uint8_t pdu_type)
msg->l2h = msgb_put(msg, sizeof(*nsh));
nsh = (struct gprs_ns_hdr *) msg->l2h;
-
nsh->pdu_type = pdu_type;
- return nsvc->bind->send_vc(nsvc, msg);
+ LOG_NS_TX_SIGNAL(nsvc, nsh->pdu_type);
+ return ns_vc_tx(nsvc, msg);
}
/*! Transmit a NS-BLOCK on a given NS-VC.
* \param[in] vc NS-VC on which the NS-BLOCK is to be transmitted
* \param[in] cause Numeric NS Cause value
+ * \param[in] nsvci if given this NSVCI will be encoded. If NULL the nsvc->nsvci will be used.
* \returns 0 in case of success */
-int ns2_tx_block(struct gprs_ns2_vc *nsvc, uint8_t cause)
+int ns2_tx_block(struct gprs_ns2_vc *nsvc, uint8_t cause, uint16_t *nsvci)
{
struct msgb *msg;
struct gprs_ns_hdr *nsh;
- uint16_t nsvci = osmo_htons(nsvc->nsvci);
+ uint16_t encoded_nsvci;
+ if (nsvci)
+ encoded_nsvci = osmo_htons(*nsvci);
+ else
+ encoded_nsvci = osmo_htons(nsvc->nsvci);
+
+ log_set_context(LOG_CTX_GB_NSE, nsvc->nse);
log_set_context(LOG_CTX_GB_NSVC, nsvc);
ERR_IF_NSVC_USES_SNS(nsvc, "transmit NS BLOCK");
- msg = gprs_ns2_msgb_alloc();
+ msg = ns2_msgb_alloc();
if (!msg)
return -ENOMEM;
- LOGP(DLNS, LOGL_INFO, "NSEI=%u Tx NS BLOCK (NSVCI=%u, cause=%s)\n",
- nsvc->nse->nsei, nsvc->nsvci, gprs_ns2_cause_str(cause));
-
- rate_ctr_inc(&nsvc->ctrg->ctr[NS_CTR_BLOCKED]);
+ rate_ctr_inc(rate_ctr_group_get_ctr(nsvc->ctrg, NS_CTR_BLOCKED));
msg->l2h = msgb_put(msg, sizeof(*nsh));
nsh = (struct gprs_ns_hdr *) msg->l2h;
nsh->pdu_type = NS_PDUT_BLOCK;
msgb_tvlv_put(msg, NS_IE_CAUSE, 1, &cause);
- msgb_tvlv_put(msg, NS_IE_VCI, 2, (uint8_t *) &nsvci);
+ msgb_tvlv_put(msg, NS_IE_VCI, 2, (uint8_t *) &encoded_nsvci);
- return nsvc->bind->send_vc(nsvc, msg);
+ LOG_NS_SIGNAL(nsvc, "Tx", nsh->pdu_type, LOGL_INFO, " cause=%s\n", gprs_ns2_cause_str(cause));
+ return ns_vc_tx(nsvc, msg);
}
/*! Transmit a NS-BLOCK-ACK on a given NS-VC.
* \param[in] nsvc NS-VC on which the NS-BLOCK is to be transmitted
+ * \param[in] nsvci if given this NSVCI will be encoded. If NULL the nsvc->nsvci will be used.
* \returns 0 in case of success */
-int ns2_tx_block_ack(struct gprs_ns2_vc *nsvc)
+int ns2_tx_block_ack(struct gprs_ns2_vc *nsvc, uint16_t *nsvci)
{
struct msgb *msg;
struct gprs_ns_hdr *nsh;
- uint16_t nsvci = osmo_htons(nsvc->nsvci);
+ uint16_t encoded_nsvci;
+
+ if (nsvci)
+ encoded_nsvci = osmo_htons(*nsvci);
+ else
+ encoded_nsvci = osmo_htons(nsvc->nsvci);
+ log_set_context(LOG_CTX_GB_NSE, nsvc->nse);
log_set_context(LOG_CTX_GB_NSVC, nsvc);
ERR_IF_NSVC_USES_SNS(nsvc, "transmit NS BLOCK ACK");
- msg = gprs_ns2_msgb_alloc();
+ msg = ns2_msgb_alloc();
if (!msg)
return -ENOMEM;
- LOGP(DLNS, LOGL_INFO, "NSEI=%u Tx NS BLOCK ACK (NSVCI=%u)\n", nsvc->nse->nsei, nsvc->nsvci);
-
- /* be conservative and mark it as blocked even now! */
msg->l2h = msgb_put(msg, sizeof(*nsh));
nsh = (struct gprs_ns_hdr *) msg->l2h;
nsh->pdu_type = NS_PDUT_BLOCK_ACK;
- msgb_tvlv_put(msg, NS_IE_VCI, 2, (uint8_t *) &nsvci);
+ msgb_tvlv_put(msg, NS_IE_VCI, 2, (uint8_t *) &encoded_nsvci);
- return nsvc->bind->send_vc(nsvc, msg);
+ LOG_NS_TX_SIGNAL(nsvc, nsh->pdu_type);
+ return ns_vc_tx(nsvc, msg);
}
/*! Transmit a NS-RESET on a given NS-VC.
* \param[in] nsvc NS-VC used for transmission
- * \paam[in] cause Numeric NS cause value
+ * \param[in] cause Numeric NS cause value
* \returns 0 in case of success */
int ns2_tx_reset(struct gprs_ns2_vc *nsvc, uint8_t cause)
{
@@ -275,17 +291,15 @@ int ns2_tx_reset(struct gprs_ns2_vc *nsvc, uint8_t cause)
uint16_t nsvci = osmo_htons(nsvc->nsvci);
uint16_t nsei = osmo_htons(nsvc->nse->nsei);
+ log_set_context(LOG_CTX_GB_NSE, nsvc->nse);
log_set_context(LOG_CTX_GB_NSVC, nsvc);
ERR_IF_NSVC_USES_SNS(nsvc, "transmit NS RESET");
- msg = gprs_ns2_msgb_alloc();
+ msg = ns2_msgb_alloc();
if (!msg)
return -ENOMEM;
- LOGP(DLNS, LOGL_INFO, "NSEI=%u Tx NS RESET (NSVCI=%u, cause=%s)\n",
- nsvc->nse->nsei, nsvc->nsvci, gprs_ns2_cause_str(cause));
-
msg->l2h = msgb_put(msg, sizeof(*nsh));
nsh = (struct gprs_ns_hdr *) msg->l2h;
nsh->pdu_type = NS_PDUT_RESET;
@@ -294,7 +308,8 @@ int ns2_tx_reset(struct gprs_ns2_vc *nsvc, uint8_t cause)
msgb_tvlv_put(msg, NS_IE_VCI, 2, (uint8_t *) &nsvci);
msgb_tvlv_put(msg, NS_IE_NSEI, 2, (uint8_t *) &nsei);
- return nsvc->bind->send_vc(nsvc, msg);
+ LOG_NS_SIGNAL(nsvc, "Tx", nsh->pdu_type, LOGL_INFO, " cause=%s\n", gprs_ns2_cause_str(cause));
+ return ns_vc_tx(nsvc, msg);
}
/*! Transmit a NS-RESET-ACK on a given NS-VC.
@@ -307,11 +322,12 @@ int ns2_tx_reset_ack(struct gprs_ns2_vc *nsvc)
uint16_t nsvci, nsei;
/* Section 9.2.6 */
+ log_set_context(LOG_CTX_GB_NSE, nsvc->nse);
log_set_context(LOG_CTX_GB_NSVC, nsvc);
ERR_IF_NSVC_USES_SNS(nsvc, "transmit NS RESET ACK");
- msg = gprs_ns2_msgb_alloc();
+ msg = ns2_msgb_alloc();
if (!msg)
return -ENOMEM;
@@ -323,13 +339,11 @@ int ns2_tx_reset_ack(struct gprs_ns2_vc *nsvc)
nsh->pdu_type = NS_PDUT_RESET_ACK;
- LOGP(DLNS, LOGL_INFO, "NSEI=%u Tx NS RESET ACK (NSVCI=%u)\n",
- nsvc->nse->nsei, nsvc->nsvci);
-
msgb_tvlv_put(msg, NS_IE_VCI, 2, (uint8_t *)&nsvci);
msgb_tvlv_put(msg, NS_IE_NSEI, 2, (uint8_t *)&nsei);
- return nsvc->bind->send_vc(nsvc, msg);
+ LOG_NS_TX_SIGNAL(nsvc, nsh->pdu_type);
+ return ns_vc_tx(nsvc, msg);
}
/*! Transmit a NS-UNBLOCK on a given NS-VC.
@@ -337,13 +351,11 @@ int ns2_tx_reset_ack(struct gprs_ns2_vc *nsvc)
* \returns 0 in case of success */
int ns2_tx_unblock(struct gprs_ns2_vc *nsvc)
{
+ log_set_context(LOG_CTX_GB_NSE, nsvc->nse);
log_set_context(LOG_CTX_GB_NSVC, nsvc);
ERR_IF_NSVC_USES_SNS(nsvc, "transmit NS UNBLOCK");
- LOGP(DLNS, LOGL_INFO, "NSEI=%u Tx NS UNBLOCK (NSVCI=%u)\n",
- nsvc->nse->nsei, nsvc->nsvci);
-
return ns2_tx_simple(nsvc, NS_PDUT_UNBLOCK);
}
@@ -353,13 +365,11 @@ int ns2_tx_unblock(struct gprs_ns2_vc *nsvc)
* \returns 0 in case of success */
int ns2_tx_unblock_ack(struct gprs_ns2_vc *nsvc)
{
+ log_set_context(LOG_CTX_GB_NSE, nsvc->nse);
log_set_context(LOG_CTX_GB_NSVC, nsvc);
ERR_IF_NSVC_USES_SNS(nsvc, "transmit NS UNBLOCK ACK");
- LOGP(DLNS, LOGL_INFO, "NSEI=%u Tx NS UNBLOCK (NSVCI=%u)\n",
- nsvc->nse->nsei, nsvc->nsvci);
-
return ns2_tx_simple(nsvc, NS_PDUT_UNBLOCK_ACK);
}
@@ -368,9 +378,8 @@ int ns2_tx_unblock_ack(struct gprs_ns2_vc *nsvc)
* \returns 0 in case of success */
int ns2_tx_alive(struct gprs_ns2_vc *nsvc)
{
+ log_set_context(LOG_CTX_GB_NSE, nsvc->nse);
log_set_context(LOG_CTX_GB_NSVC, nsvc);
- LOGP(DLNS, LOGL_DEBUG, "NSEI=%u Tx NS ALIVE (NSVCI=%u)\n",
- nsvc->nse->nsei, nsvc->nsvci);
return ns2_tx_simple(nsvc, NS_PDUT_ALIVE);
}
@@ -380,9 +389,8 @@ int ns2_tx_alive(struct gprs_ns2_vc *nsvc)
* \returns 0 in case of success */
int ns2_tx_alive_ack(struct gprs_ns2_vc *nsvc)
{
+ log_set_context(LOG_CTX_GB_NSE, nsvc->nse);
log_set_context(LOG_CTX_GB_NSVC, nsvc);
- LOGP(DLNS, LOGL_DEBUG, "NSEI=%u Tx NS ALIVE_ACK (NSVCI=%u)\n",
- nsvc->nse->nsei, nsvc->nsvci);
return ns2_tx_simple(nsvc, NS_PDUT_ALIVE_ACK);
}
@@ -399,12 +407,13 @@ int ns2_tx_unit_data(struct gprs_ns2_vc *nsvc,
{
struct gprs_ns_hdr *nsh;
+ log_set_context(LOG_CTX_GB_NSE, nsvc->nse);
log_set_context(LOG_CTX_GB_NSVC, nsvc);
msg->l2h = msgb_push(msg, sizeof(*nsh) + 3);
nsh = (struct gprs_ns_hdr *) msg->l2h;
if (!nsh) {
- LOGP(DLNS, LOGL_ERROR, "Not enough headroom for NS header\n");
+ LOGNSVC(nsvc, LOGL_ERROR, "Not enough headroom for NS header\n");
msgb_free(msg);
return -EIO;
}
@@ -414,7 +423,8 @@ int ns2_tx_unit_data(struct gprs_ns2_vc *nsvc,
nsh->data[1] = bvci >> 8;
nsh->data[2] = bvci & 0xff;
- return nsvc->bind->send_vc(nsvc, msg);
+ LOG_NS_DATA(nsvc, "Tx", nsh->pdu_type, LOGL_INFO, "\n");
+ return ns_vc_tx(nsvc, msg);
}
/*! Transmit a NS-STATUS on a given NS-VC.
@@ -422,14 +432,17 @@ int ns2_tx_unit_data(struct gprs_ns2_vc *nsvc,
* \param[in] cause Numeric NS cause value
* \param[in] bvci BVCI to be reset within NSVC
* \param[in] orig_msg message causing the STATUS
+ * \param[in] nsvci if given this NSVCI will be encoded. If NULL the nsvc->nsvci will be used.
* \returns 0 in case of success */
int ns2_tx_status(struct gprs_ns2_vc *nsvc, uint8_t cause,
- uint16_t bvci, struct msgb *orig_msg)
+ uint16_t bvci, struct msgb *orig_msg, uint16_t *nsvci)
{
- struct msgb *msg = gprs_ns2_msgb_alloc();
+ struct msgb *msg = ns2_msgb_alloc();
struct gprs_ns_hdr *nsh;
- uint16_t nsvci = osmo_htons(nsvc->nsvci);
+ uint16_t encoded_nsvci;
+ unsigned int orig_len, max_orig_len;
+ log_set_context(LOG_CTX_GB_NSE, nsvc->nse);
log_set_context(LOG_CTX_GB_NSVC, nsvc);
bvci = osmo_htons(bvci);
@@ -437,39 +450,162 @@ int ns2_tx_status(struct gprs_ns2_vc *nsvc, uint8_t cause,
if (!msg)
return -ENOMEM;
- LOGP(DLNS, LOGL_NOTICE, "NSEI=%u Tx NS STATUS (NSVCI=%u, cause=%s)\n",
- nsvc->nse->nsei, nsvc->nsvci, gprs_ns2_cause_str(cause));
-
msg->l2h = msgb_put(msg, sizeof(*nsh));
nsh = (struct gprs_ns_hdr *) msg->l2h;
nsh->pdu_type = NS_PDUT_STATUS;
msgb_tvlv_put(msg, NS_IE_CAUSE, 1, &cause);
- /* Section 9.2.7.1: Static conditions for NS-VCI */
- if (cause == NS_CAUSE_NSVC_BLOCKED ||
- cause == NS_CAUSE_NSVC_UNKNOWN)
- msgb_tvlv_put(msg, NS_IE_VCI, 2, (uint8_t *)&nsvci);
-
- /* Section 9.2.7.2: Static conditions for NS PDU */
switch (cause) {
+ case NS_CAUSE_NSVC_BLOCKED:
+ case NS_CAUSE_NSVC_UNKNOWN:
+ /* Section 9.2.7.1: Static conditions for NS-VCI */
+ if (nsvci)
+ encoded_nsvci = osmo_htons(*nsvci);
+ else
+ encoded_nsvci = osmo_htons(nsvc->nsvci);
+ msgb_tvlv_put(msg, NS_IE_VCI, 2, (uint8_t *)&encoded_nsvci);
+ break;
case NS_CAUSE_SEM_INCORR_PDU:
case NS_CAUSE_PDU_INCOMP_PSTATE:
case NS_CAUSE_PROTO_ERR_UNSPEC:
case NS_CAUSE_INVAL_ESSENT_IE:
case NS_CAUSE_MISSING_ESSENT_IE:
- msgb_tvlv_put(msg, NS_IE_PDU, msgb_l2len(orig_msg),
- orig_msg->l2h);
+ /* Section 9.2.7.2: Static conditions for NS PDU */
+ /* ensure the PDU doesn't exceed the MTU */
+ orig_len = msgb_l2len(orig_msg);
+ max_orig_len = msgb_length(msg) + TVLV_GROSS_LEN(orig_len);
+ if (max_orig_len > nsvc->bind->mtu)
+ orig_len -= max_orig_len - nsvc->bind->mtu;
+ msgb_tvlv_put(msg, NS_IE_PDU, orig_len, orig_msg->l2h);
break;
+ case NS_CAUSE_BVCI_UNKNOWN:
+ /* Section 9.2.7.3: Static conditions for BVCI */
+ msgb_tvlv_put(msg, NS_IE_VCI, 2, (uint8_t *)&bvci);
+ break;
+
default:
break;
}
- /* Section 9.2.7.3: Static conditions for BVCI */
- if (cause == NS_CAUSE_BVCI_UNKNOWN)
- msgb_tvlv_put(msg, NS_IE_VCI, 2, (uint8_t *)&bvci);
+ LOG_NS_SIGNAL(nsvc, "Tx", nsh->pdu_type, LOGL_INFO, " cause=%s\n", gprs_ns2_cause_str(cause));
+ return ns_vc_tx(nsvc, msg);
+}
+
+/*! Encode + Transmit a SNS-ADD/SNS-CHANGE-WEIGHT as per Section 9.3.2/9.3.3.
+ * \param[in] nsvc NS-VC through which to transmit the SNS-CONFIG
+ * \param[in] pdu The PDU type to send out
+ * \param[in] trans_id The transaction id
+ * \param[in] ip4_elems Array of IPv4 Elements
+ * \param[in] num_ip4_elems number of ip4_elems
+ * \param[in] ip6_elems Array of IPv6 Elements
+ * \param[in] num_ip6_elems number of ip6_elems
+ * \returns 0 on success; negative in case of error */
+static int ns2_tx_sns_procedure(struct gprs_ns2_vc *nsvc,
+ enum ns_pdu_type pdu,
+ uint8_t trans_id,
+ const struct gprs_ns_ie_ip4_elem *ip4_elems,
+ unsigned int num_ip4_elems,
+ const struct gprs_ns_ie_ip6_elem *ip6_elems,
+ unsigned int num_ip6_elems)
+{
+ struct msgb *msg;
+ struct gprs_ns_hdr *nsh;
+ uint16_t nsei;
+
+ if (!nsvc)
+ return -EINVAL;
+
+ if (!ip4_elems && !ip6_elems)
+ return -EINVAL;
+
+ msg = ns2_msgb_alloc();
- return nsvc->bind->send_vc(nsvc, msg);
+ log_set_context(LOG_CTX_GB_NSE, nsvc->nse);
+ log_set_context(LOG_CTX_GB_NSVC, nsvc);
+ if (!msg)
+ return -ENOMEM;
+
+ if (!nsvc->nse->bss_sns_fi) {
+ LOGNSVC(nsvc, LOGL_ERROR, "Cannot transmit SNS on NSVC without SNS active\n");
+ msgb_free(msg);
+ return -EIO;
+ }
+
+ nsei = osmo_htons(nsvc->nse->nsei);
+
+ msg->l2h = msgb_put(msg, sizeof(*nsh));
+ nsh = (struct gprs_ns_hdr *) msg->l2h;
+ nsh->pdu_type = pdu;
+ msgb_tvlv_put(msg, NS_IE_NSEI, 2, (uint8_t *)&nsei);
+ msgb_v_put(msg, trans_id);
+
+ /* List of IP4 Elements 10.3.2c */
+ if (ip4_elems) {
+ msgb_tvlv_put(msg, NS_IE_IPv4_LIST, num_ip4_elems*sizeof(struct gprs_ns_ie_ip4_elem),
+ (const uint8_t *)ip4_elems);
+ } else if (ip6_elems) {
+ /* List of IP6 elements 10.3.2d */
+ msgb_tvlv_put(msg, NS_IE_IPv6_LIST, num_ip6_elems*sizeof(struct gprs_ns_ie_ip6_elem),
+ (const uint8_t *)ip6_elems);
+ }
+
+ return ns_vc_tx(nsvc, msg);
+}
+
+/*! Encode + Transmit a SNS-ADD as per Section 9.3.2.
+ * \param[in] nsvc NS-VC through which to transmit the SNS-CONFIG
+ * \param[in] trans_id The transaction id
+ * \param[in] ip4_elems Array of IPv4 Elements
+ * \param[in] num_ip4_elems number of ip4_elems
+ * \param[in] ip6_elems Array of IPv6 Elements
+ * \param[in] num_ip6_elems number of ip6_elems
+ * \returns 0 on success; negative in case of error */
+int ns2_tx_sns_add(struct gprs_ns2_vc *nsvc,
+ uint8_t trans_id,
+ const struct gprs_ns_ie_ip4_elem *ip4_elems,
+ unsigned int num_ip4_elems,
+ const struct gprs_ns_ie_ip6_elem *ip6_elems,
+ unsigned int num_ip6_elems)
+{
+ return ns2_tx_sns_procedure(nsvc, SNS_PDUT_ADD, trans_id, ip4_elems, num_ip4_elems, ip6_elems, num_ip6_elems);
+}
+
+/*! Encode + Transmit a SNS-CHANGE-WEIGHT as per Section 9.3.3.
+ * \param[in] nsvc NS-VC through which to transmit the SNS-CONFIG
+ * \param[in] trans_id The transaction id
+ * \param[in] ip4_elems Array of IPv4 Elements
+ * \param[in] num_ip4_elems number of ip4_elems
+ * \param[in] ip6_elems Array of IPv6 Elements
+ * \param[in] num_ip6_elems number of ip6_elems
+ * \returns 0 on success; negative in case of error */
+int ns2_tx_sns_change_weight(struct gprs_ns2_vc *nsvc,
+ uint8_t trans_id,
+ const struct gprs_ns_ie_ip4_elem *ip4_elems,
+ unsigned int num_ip4_elems,
+ const struct gprs_ns_ie_ip6_elem *ip6_elems,
+ unsigned int num_ip6_elems)
+{
+ return ns2_tx_sns_procedure(nsvc, SNS_PDUT_CHANGE_WEIGHT, trans_id, ip4_elems, num_ip4_elems, ip6_elems, num_ip6_elems);
+}
+
+/*! Encode + Transmit a SNS-DEL as per Section 9.3.6.
+ * \param[in] nsvc NS-VC through which to transmit the SNS-CONFIG
+ * \param[in] trans_id The transaction id
+ * \param[in] ip4_elems Array of IPv4 Elements
+ * \param[in] num_ip4_elems number of ip4_elems
+ * \param[in] ip6_elems Array of IPv6 Elements
+ * \param[in] num_ip6_elems number of ip6_elems
+ * \returns 0 on success; negative in case of error */
+int ns2_tx_sns_del(struct gprs_ns2_vc *nsvc,
+ uint8_t trans_id,
+ const struct gprs_ns_ie_ip4_elem *ip4_elems,
+ unsigned int num_ip4_elems,
+ const struct gprs_ns_ie_ip6_elem *ip6_elems,
+ unsigned int num_ip6_elems)
+{
+ /* TODO: IP Address field */
+ return ns2_tx_sns_procedure(nsvc, SNS_PDUT_DELETE, trans_id, ip4_elems, num_ip4_elems, ip6_elems, num_ip6_elems);
}
@@ -486,26 +622,27 @@ int ns2_tx_sns_ack(struct gprs_ns2_vc *nsvc, uint8_t trans_id, uint8_t *cause,
const struct gprs_ns_ie_ip6_elem *ip6_elems,
unsigned int num_ip6_elems)
{
- struct msgb *msg = gprs_ns2_msgb_alloc();
+ struct msgb *msg;
struct gprs_ns_hdr *nsh;
uint16_t nsei;
if (!nsvc)
return -1;
- msg = gprs_ns2_msgb_alloc();
+ msg = ns2_msgb_alloc();
+ log_set_context(LOG_CTX_GB_NSE, nsvc->nse);
log_set_context(LOG_CTX_GB_NSVC, nsvc);
if (!msg)
return -ENOMEM;
if (!nsvc->nse->bss_sns_fi) {
- LOGP(DLNS, LOGL_ERROR, "NSEI=%u Cannot transmit SNS on NSVC without SNS active\n",
- nsvc->nse->nsei);
+ LOGNSVC(nsvc, LOGL_ERROR, "Cannot transmit SNS on NSVC without SNS active\n");
msgb_free(msg);
return -EIO;
}
+
nsei = osmo_htons(nsvc->nse->nsei);
msg->l2h = msgb_put(msg, sizeof(*nsh));
@@ -529,7 +666,10 @@ int ns2_tx_sns_ack(struct gprs_ns2_vc *nsvc, uint8_t trans_id, uint8_t *cause,
(const uint8_t *)ip6_elems);
}
- return nsvc->bind->send_vc(nsvc, msg);
+ LOG_NS_SIGNAL(nsvc, "Tx", nsh->pdu_type, LOGL_INFO,
+ " (trans_id=%u, cause=%s, num_ip4=%u, num_ip6=%u)\n",
+ trans_id, cause ? gprs_ns2_cause_str(*cause) : "NULL", num_ip4_elems, num_ip6_elems);
+ return ns_vc_tx(nsvc, msg);
}
/*! Encode + Transmit a SNS-CONFIG as per Section 9.3.4.
@@ -551,15 +691,15 @@ int ns2_tx_sns_config(struct gprs_ns2_vc *nsvc, bool end_flag,
if (!nsvc)
return -1;
- msg = gprs_ns2_msgb_alloc();
+ msg = ns2_msgb_alloc();
+ log_set_context(LOG_CTX_GB_NSE, nsvc->nse);
log_set_context(LOG_CTX_GB_NSVC, nsvc);
if (!msg)
return -ENOMEM;
if (!nsvc->nse->bss_sns_fi) {
- LOGP(DLNS, LOGL_ERROR, "NSEI=%u Cannot transmit SNS on NSVC without SNS active\n",
- nsvc->nse->nsei);
+ LOGNSVC(nsvc, LOGL_ERROR, "Cannot transmit SNS on NSVC without SNS active\n");
msgb_free(msg);
return -EIO;
}
@@ -584,7 +724,10 @@ int ns2_tx_sns_config(struct gprs_ns2_vc *nsvc, bool end_flag,
(const uint8_t *)ip6_elems);
}
- return nsvc->bind->send_vc(nsvc, msg);
+ LOG_NS_SIGNAL(nsvc, "Tx", nsh->pdu_type, LOGL_INFO,
+ " (end_flag=%u, num_ip4=%u, num_ip6=%u)\n",
+ end_flag, num_ip4_elems, num_ip6_elems);
+ return ns_vc_tx(nsvc, msg);
}
/*! Encode + Transmit a SNS-CONFIG-ACK as per Section 9.3.5.
@@ -600,14 +743,14 @@ int ns2_tx_sns_config_ack(struct gprs_ns2_vc *nsvc, uint8_t *cause)
if (!nsvc)
return -1;
- msg = gprs_ns2_msgb_alloc();
+ msg = ns2_msgb_alloc();
+ log_set_context(LOG_CTX_GB_NSE, nsvc->nse);
log_set_context(LOG_CTX_GB_NSVC, nsvc);
if (!msg)
return -ENOMEM;
if (!nsvc->nse->bss_sns_fi) {
- LOGP(DLNS, LOGL_ERROR, "NSEI=%u Cannot transmit SNS on NSVC without SNS active\n",
- nsvc->nse->nsei);
+ LOGNSVC(nsvc, LOGL_ERROR, "Cannot transmit SNS on NSVC without SNS active\n");
msgb_free(msg);
return -EIO;
}
@@ -623,7 +766,10 @@ int ns2_tx_sns_config_ack(struct gprs_ns2_vc *nsvc, uint8_t *cause)
if (cause)
msgb_tvlv_put(msg, NS_IE_CAUSE, 1, cause);
- return nsvc->bind->send_vc(nsvc, msg);
+ LOGNSVC(nsvc, LOGL_INFO, "Tx SNS-CONFIG-ACK (cause=%s)\n",
+ cause ? gprs_ns2_cause_str(*cause) : "NULL");
+ LOG_NS_TX_SIGNAL(nsvc, nsh->pdu_type);
+ return ns_vc_tx(nsvc, msg);
}
@@ -637,22 +783,22 @@ int ns2_tx_sns_config_ack(struct gprs_ns2_vc *nsvc, uint8_t *cause)
int ns2_tx_sns_size(struct gprs_ns2_vc *nsvc, bool reset_flag, uint16_t max_nr_nsvc,
int ip4_ep_nr, int ip6_ep_nr)
{
- struct msgb *msg = gprs_ns2_msgb_alloc();
+ struct msgb *msg;
struct gprs_ns_hdr *nsh;
uint16_t nsei;
if (!nsvc)
return -1;
- msg = gprs_ns2_msgb_alloc();
+ msg = ns2_msgb_alloc();
+ log_set_context(LOG_CTX_GB_NSE, nsvc->nse);
log_set_context(LOG_CTX_GB_NSVC, nsvc);
if (!msg)
return -ENOMEM;
if (!nsvc->nse->bss_sns_fi) {
- LOGP(DLNS, LOGL_ERROR, "NSEI=%u Cannot transmit SNS on NSVC without SNS active\n",
- nsvc->nse->nsei);
+ LOGNSVC(nsvc, LOGL_ERROR, "Cannot transmit SNS on NSVC without SNS active\n");
msgb_free(msg);
return -EIO;
}
@@ -672,7 +818,10 @@ int ns2_tx_sns_size(struct gprs_ns2_vc *nsvc, bool reset_flag, uint16_t max_nr_n
if (ip6_ep_nr >= 0)
msgb_tv16_put(msg, NS_IE_IPv6_EP_NR, ip6_ep_nr);
- return nsvc->bind->send_vc(nsvc, msg);
+ LOG_NS_SIGNAL(nsvc, "Tx", nsh->pdu_type, LOGL_INFO,
+ " (reset=%u, max_nr_nsvc=%u, num_ip4=%d, num_ip6=%d)\n",
+ reset_flag, max_nr_nsvc, ip4_ep_nr, ip6_ep_nr);
+ return ns_vc_tx(nsvc, msg);
}
/*! Encode + Transmit a SNS-SIZE-ACK as per Section 9.3.8.
@@ -681,17 +830,17 @@ int ns2_tx_sns_size(struct gprs_ns2_vc *nsvc, bool reset_flag, uint16_t max_nr_n
* \returns 0 on success; negative in case of error */
int ns2_tx_sns_size_ack(struct gprs_ns2_vc *nsvc, uint8_t *cause)
{
- struct msgb *msg = gprs_ns2_msgb_alloc();
+ struct msgb *msg = ns2_msgb_alloc();
struct gprs_ns_hdr *nsh;
uint16_t nsei;
+ log_set_context(LOG_CTX_GB_NSE, nsvc->nse);
log_set_context(LOG_CTX_GB_NSVC, nsvc);
if (!msg)
return -ENOMEM;
if (!nsvc->nse->bss_sns_fi) {
- LOGP(DLNS, LOGL_ERROR, "NSEI=%u Cannot transmit SNS on NSVC without SNS active\n",
- nsvc->nse->nsei);
+ LOGNSVC(nsvc, LOGL_ERROR, "Cannot transmit SNS on NSVC without SNS active\n");
msgb_free(msg);
return -EIO;
}
@@ -707,7 +856,7 @@ int ns2_tx_sns_size_ack(struct gprs_ns2_vc *nsvc, uint8_t *cause)
if (cause)
msgb_tvlv_put(msg, NS_IE_CAUSE, 1, cause);
- return nsvc->bind->send_vc(nsvc, msg);
+ LOG_NS_SIGNAL(nsvc, "Tx", nsh->pdu_type, LOGL_INFO, " cause=%s\n",
+ cause ? gprs_ns2_cause_str(*cause) : "NULL");
+ return ns_vc_tx(nsvc, msg);
}
-
-
diff --git a/src/gb/gprs_ns2_sns.c b/src/gb/gprs_ns2_sns.c
index 812200dc..0afc06ed 100644
--- a/src/gb/gprs_ns2_sns.c
+++ b/src/gb/gprs_ns2_sns.c
@@ -3,7 +3,7 @@
* 3GPP TS 08.16 version 8.0.1 Release 1999 / ETSI TS 101 299 V8.0.1 (2002-05)
* as well as its successor 3GPP TS 48.016 */
-/* (C) 2018 by Harald Welte <laforge@gnumonks.org>
+/* (C) 2018-2021 by Harald Welte <laforge@gnumonks.org>
* (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* Author: Alexander Couzens <lynxis@fe80.eu>
*
@@ -46,6 +46,7 @@
#include <osmocom/core/fsm.h>
#include <osmocom/core/msgb.h>
#include <osmocom/core/socket.h>
+#include <osmocom/core/sockaddr_str.h>
#include <osmocom/gsm/tlv.h>
#include <osmocom/gprs/gprs_msgb.h>
#include <osmocom/gprs/gprs_ns2.h>
@@ -55,63 +56,123 @@
#define S(x) (1 << (x))
-enum ns2_sns_type {
- IPv4,
- IPv6,
+enum ns2_sns_role {
+ GPRS_SNS_ROLE_BSS,
+ GPRS_SNS_ROLE_SGSN,
};
+/* BSS-side-only states _ST_BSS_; SGSN-side only states _ST_SGSN_; others shared */
enum gprs_sns_bss_state {
GPRS_SNS_ST_UNCONFIGURED,
- GPRS_SNS_ST_SIZE, /*!< SNS-SIZE procedure ongoing */
- GPRS_SNS_ST_CONFIG_BSS, /*!< SNS-CONFIG procedure (BSS->SGSN) ongoing */
- GPRS_SNS_ST_CONFIG_SGSN, /*!< SNS-CONFIG procedure (SGSN->BSS) ongoing */
+ GPRS_SNS_ST_BSS_SIZE, /*!< SNS-SIZE procedure ongoing */
+ GPRS_SNS_ST_BSS_CONFIG_BSS, /*!< SNS-CONFIG procedure (BSS->SGSN) ongoing */
+ GPRS_SNS_ST_BSS_CONFIG_SGSN, /*!< SNS-CONFIG procedure (SGSN->BSS) ongoing */
GPRS_SNS_ST_CONFIGURED,
-};
-
-enum gprs_sns_event {
- GPRS_SNS_EV_START,
- GPRS_SNS_EV_SIZE,
- GPRS_SNS_EV_SIZE_ACK,
- GPRS_SNS_EV_CONFIG,
- GPRS_SNS_EV_CONFIG_END, /*!< SNS-CONFIG with end flag received */
- GPRS_SNS_EV_CONFIG_ACK,
- GPRS_SNS_EV_ADD,
- GPRS_SNS_EV_DELETE,
- GPRS_SNS_EV_CHANGE_WEIGHT,
- GPRS_SNS_EV_NO_NSVC,
+ GPRS_SNS_ST_SGSN_WAIT_CONFIG, /* !< SGSN role: Wait for CONFIG from BSS */
+ GPRS_SNS_ST_SGSN_WAIT_CONFIG_ACK, /* !< SGSN role: Wait for CONFIG-ACK from BSS */
+ GPRS_SNS_ST_LOCAL_PROCEDURE, /*!< in process of a ADD/DEL/CHANGE procedure towards SGSN (BSS->SGSN) */
};
static const struct value_string gprs_sns_event_names[] = {
- { GPRS_SNS_EV_START, "START" },
- { GPRS_SNS_EV_SIZE, "SIZE" },
- { GPRS_SNS_EV_SIZE_ACK, "SIZE_ACK" },
- { GPRS_SNS_EV_CONFIG, "CONFIG" },
- { GPRS_SNS_EV_CONFIG_END, "CONFIG_END" },
- { GPRS_SNS_EV_CONFIG_ACK, "CONFIG_ACK" },
- { GPRS_SNS_EV_ADD, "ADD" },
- { GPRS_SNS_EV_DELETE, "DELETE" },
- { GPRS_SNS_EV_CHANGE_WEIGHT, "CHANGE_WEIGHT" },
+ { NS2_SNS_EV_REQ_SELECT_ENDPOINT, "REQ_SELECT_ENDPOINT" },
+ { NS2_SNS_EV_RX_SIZE, "RX_SIZE" },
+ { NS2_SNS_EV_RX_SIZE_ACK, "RX_SIZE_ACK" },
+ { NS2_SNS_EV_RX_CONFIG, "RX_CONFIG" },
+ { NS2_SNS_EV_RX_CONFIG_END, "RX_CONFIG_END" },
+ { NS2_SNS_EV_RX_CONFIG_ACK, "RX_CONFIG_ACK" },
+ { NS2_SNS_EV_RX_ADD, "RX_ADD" },
+ { NS2_SNS_EV_RX_DELETE, "RX_DELETE" },
+ { NS2_SNS_EV_RX_ACK, "RX_ACK" },
+ { NS2_SNS_EV_RX_CHANGE_WEIGHT, "RX_CHANGE_WEIGHT" },
+ { NS2_SNS_EV_REQ_NO_NSVC, "REQ_NO_NSVC" },
+ { NS2_SNS_EV_REQ_FREE_NSVCS, "REQ_FREE_NSVCS" },
+ { NS2_SNS_EV_REQ_NSVC_ALIVE, "REQ_NSVC_ALIVE"},
+ { NS2_SNS_EV_REQ_ADD_BIND, "REQ_ADD_BIND"},
+ { NS2_SNS_EV_REQ_DELETE_BIND, "REQ_DELETE_BIND"},
+ { NS2_SNS_EV_REQ_CHANGE_WEIGHT, "REQ_CHANGE_WEIGHT"},
{ 0, NULL }
};
+#define GPRS_SNS_FLAG_KEEP_SELECT_ENDPOINT_ORDER (void *) 1
+
+enum sns_procedure {
+ SNS_PROC_NONE, /*!< used as invalid/idle value */
+ SNS_PROC_ADD,
+ SNS_PROC_DEL,
+ SNS_PROC_CHANGE_WEIGHT,
+};
+
+struct sns_endpoint {
+ struct llist_head list;
+ struct osmo_sockaddr saddr;
+};
+
+struct ns2_sns_bind {
+ struct llist_head list;
+ struct gprs_ns2_vc_bind *bind;
+ uint8_t change_weight_state;
+};
+
+struct ns2_sns_procedure {
+ struct llist_head list;
+ struct ns2_sns_bind *sbind;
+ uint16_t sig_weight;
+ uint16_t data_weight;
+ /* copy entry to protect against changes of gss->local */
+ struct gprs_ns_ie_ip4_elem ip4;
+ struct gprs_ns_ie_ip6_elem ip6;
+ enum sns_procedure procedure;
+ uint8_t trans_id;
+ /* is the procedure in process */
+ bool running;
+};
+
+struct ns2_sns_elems {
+ struct gprs_ns_ie_ip4_elem *ip4;
+ unsigned int num_ip4;
+ struct gprs_ns_ie_ip6_elem *ip6;
+ unsigned int num_ip6;
+};
+
struct ns2_sns_state {
struct gprs_ns2_nse *nse;
- enum ns2_sns_type ip;
-
- /* initial connection. the initial connection will be terminated
- * in configured state or moved into NSE if valid */
- struct osmo_sockaddr initial;
+ /* containing the address family AF_* */
+ int family;
+ enum ns2_sns_role role; /* local role: BSS or SGSN */
+
+ /* holds the list of initial SNS endpoints */
+ struct llist_head sns_endpoints;
+ /* list of used struct ns2_sns_bind */
+ struct llist_head binds;
+ /* pointer to the bind which was used to initiate the SNS connection */
+ struct ns2_sns_bind *initial_bind;
+ /* prevent recursive reselection */
+ bool reselection_running;
+
+ /* protection against recursive free() */
+ bool block_no_nsvc_events;
+
+ /* The current initial SNS endpoints.
+ * The initial connection will be moved into the NSE
+ * if configured via SNS. Otherwise it will be removed
+ * in configured state. */
+ struct sns_endpoint *initial;
/* all SNS PDU will be sent over this nsvc */
struct gprs_ns2_vc *sns_nsvc;
+ /* timer N */
+ int N;
+ /* true if at least one nsvc is alive */
+ bool alive;
/* local configuration to send to the remote end */
- struct gprs_ns_ie_ip4_elem *ip4_local;
- size_t num_ip4_local;
+ struct ns2_sns_elems local;
- /* local configuration to send to the remote end */
- struct gprs_ns_ie_ip6_elem *ip6_local;
- size_t num_ip6_local;
+ /* local configuration after all local procedures applied */
+ struct ns2_sns_elems local_procedure;
+
+ /* remote configuration as received */
+ struct ns2_sns_elems remote;
/* local configuration about our capabilities in terms of connections to
* remote (SGSN) side */
@@ -119,13 +180,9 @@ struct ns2_sns_state {
size_t num_max_ip4_remote;
size_t num_max_ip6_remote;
- /* remote configuration as received */
- struct gprs_ns_ie_ip4_elem *ip4_remote;
- unsigned int num_ip4_remote;
-
- /* remote configuration as received */
- struct gprs_ns_ie_ip6_elem *ip6_remote;
- unsigned int num_ip6_remote;
+ struct llist_head procedures;
+ struct ns2_sns_procedure *current_procedure;
+ uint8_t trans_id;
};
static inline struct gprs_ns2_nse *nse_inst_from_fi(struct osmo_fsm_inst *fi)
@@ -134,41 +191,68 @@ static inline struct gprs_ns2_nse *nse_inst_from_fi(struct osmo_fsm_inst *fi)
return gss->nse;
}
+/* The SNS has failed. Etither restart the SNS (BSS) or remove the SNS (SGSN) */
+#define sns_failed(fi, reason) \
+ _sns_failed(fi, reason, __FILE__, __LINE__)
+static void _sns_failed(struct osmo_fsm_inst *fi, const char *reason, const char *file, int line)
+{
+ struct ns2_sns_state *gss = fi->priv;
+
+ if (reason)
+ LOGPFSMLSRC(fi, LOGL_ERROR, file, line, "NSE %d: SNS failed: %s\n", gss->nse->nsei, reason);
+
+ gss->alive = false;
+ if (gss->role == GPRS_SNS_ROLE_SGSN) {
+ if (!gss->nse->persistent)
+ gprs_ns2_free_nse(gss->nse);
+ else
+ _osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_UNCONFIGURED, 0, 0, file, line);
+ } else {
+ _osmo_fsm_inst_dispatch(fi, NS2_SNS_EV_REQ_SELECT_ENDPOINT, NULL, file, line);
+ }
+}
+
/* helper function to compute the sum of all (data or signaling) weights */
-static int ip4_weight_sum(const struct gprs_ns_ie_ip4_elem *ip4, unsigned int num,
- bool data_weight)
+static int ip4_weight_sum(const struct ns2_sns_elems *elems, bool data_weight)
{
unsigned int i;
int weight_sum = 0;
- for (i = 0; i < num; i++) {
+ for (i = 0; i < elems->num_ip4; i++) {
if (data_weight)
- weight_sum += ip4[i].data_weight;
+ weight_sum += elems->ip4[i].data_weight;
else
- weight_sum += ip4[i].sig_weight;
+ weight_sum += elems->ip4[i].sig_weight;
}
return weight_sum;
}
-#define ip4_weight_sum_data(x,y) ip4_weight_sum(x, y, true)
-#define ip4_weight_sum_sig(x,y) ip4_weight_sum(x, y, false)
+#define ip4_weight_sum_data(elems) ip4_weight_sum(elems, true)
+#define ip4_weight_sum_sig(elems) ip4_weight_sum(elems, false)
/* helper function to compute the sum of all (data or signaling) weights */
-static int ip6_weight_sum(const struct gprs_ns_ie_ip6_elem *ip6, unsigned int num,
- bool data_weight)
+static int ip6_weight_sum(const struct ns2_sns_elems *elems, bool data_weight)
{
unsigned int i;
int weight_sum = 0;
- for (i = 0; i < num; i++) {
+ for (i = 0; i < elems->num_ip6; i++) {
if (data_weight)
- weight_sum += ip6[i].data_weight;
+ weight_sum += elems->ip6[i].data_weight;
else
- weight_sum += ip6[i].sig_weight;
+ weight_sum += elems->ip6[i].sig_weight;
}
return weight_sum;
}
-#define ip6_weight_sum_data(x,y) ip6_weight_sum(x, y, true)
-#define ip6_weight_sum_sig(x,y) ip6_weight_sum(x, y, false)
+#define ip6_weight_sum_data(elems) ip6_weight_sum(elems, true)
+#define ip6_weight_sum_sig(elems) ip6_weight_sum(elems, false)
+
+static int ip46_weight_sum(const struct ns2_sns_elems *elems, bool data_weight)
+{
+ return ip4_weight_sum(elems, data_weight) +
+ ip6_weight_sum(elems, data_weight);
+}
+#define ip46_weight_sum_data(elems) ip46_weight_sum(elems, true)
+#define ip46_weight_sum_sig(elems) ip46_weight_sum(elems, false)
static struct gprs_ns2_vc *nsvc_by_ip4_elem(struct gprs_ns2_nse *nse,
const struct gprs_ns_ie_ip4_elem *ip4)
@@ -179,7 +263,7 @@ static struct gprs_ns2_vc *nsvc_by_ip4_elem(struct gprs_ns2_nse *nse,
sa.u.sin.sin_port = ip4->udp_port;
sa.u.sin.sin_family = AF_INET;
- return gprs_ns2_nsvc_by_sockaddr(nse, &sa);
+ return gprs_ns2_nsvc_by_sockaddr_nse(nse, &sa);
}
static struct gprs_ns2_vc *nsvc_by_ip6_elem(struct gprs_ns2_nse *nse,
@@ -191,16 +275,31 @@ static struct gprs_ns2_vc *nsvc_by_ip6_elem(struct gprs_ns2_nse *nse,
sa.u.sin6.sin6_port = ip6->udp_port;
sa.u.sin6.sin6_family = AF_INET;
- return gprs_ns2_nsvc_by_sockaddr(nse, &sa);
+ return gprs_ns2_nsvc_by_sockaddr_nse(nse, &sa);
}
-/*! called when a nsvc is beeing freed */
-void ns2_sns_free_nsvc(struct gprs_ns2_vc *nsvc)
+/*! Return the initial SNS remote socket address
+ * \param nse NS Entity
+ * \return address of the initial SNS connection; NULL in case of error
+ */
+const struct osmo_sockaddr *gprs_ns2_nse_sns_remote(struct gprs_ns2_nse *nse)
{
- struct gprs_ns2_nse *nse;
+ struct ns2_sns_state *gss;
+
+ if (!nse->bss_sns_fi)
+ return NULL;
+
+ gss = (struct ns2_sns_state *) nse->bss_sns_fi->priv;
+ return &gss->initial->saddr;
+}
+
+/*! called when a nsvc is beeing freed or the nsvc became dead */
+void ns2_sns_replace_nsvc(struct gprs_ns2_vc *nsvc)
+{
+ struct gprs_ns2_nse *nse = nsvc->nse;
struct gprs_ns2_vc *tmp;
+ struct osmo_fsm_inst *fi = nse->bss_sns_fi;
struct ns2_sns_state *gss;
- struct osmo_fsm_inst *fi = nsvc->nse->bss_sns_fi;
if (!fi)
return;
@@ -209,109 +308,142 @@ void ns2_sns_free_nsvc(struct gprs_ns2_vc *nsvc)
if (nsvc != gss->sns_nsvc)
return;
- nse = nsvc->nse;
- if (nse->alive) {
- /* choose a different sns nsvc */
+ gss->sns_nsvc = NULL;
+ if (gss->alive) {
llist_for_each_entry(tmp, &nse->nsvc, list) {
- if (gprs_ns2_vc_is_unblocked(tmp))
+ if (ns2_vc_is_unblocked(tmp)) {
gss->sns_nsvc = tmp;
+ return;
+ }
}
} else {
- LOGPFSML(fi, LOGL_ERROR, "NSE %d: no remaining NSVC. Reseting SNS FSM.", nse->nsei);
- gss->sns_nsvc = NULL;
- osmo_fsm_inst_dispatch(fi, GPRS_SNS_EV_NO_NSVC, NULL);
+ /* the SNS is waiting for its first NS-VC to come up
+ * choose any other nsvc */
+ llist_for_each_entry(tmp, &nse->nsvc, list) {
+ if (nsvc != tmp) {
+ gss->sns_nsvc = tmp;
+ return;
+ }
+ }
}
+
+ if (gss->block_no_nsvc_events)
+ osmo_fsm_inst_dispatch(fi, NS2_SNS_EV_REQ_NO_NSVC, NULL);
}
-static void ns2_nsvc_create_ip4(struct osmo_fsm_inst *fi,
- struct gprs_ns2_nse *nse,
- const struct gprs_ns_ie_ip4_elem *ip4)
+static void ns2_clear_elems(struct ns2_sns_elems *elems)
+{
+ TALLOC_FREE(elems->ip4);
+ TALLOC_FREE(elems->ip6);
+
+ elems->num_ip4 = 0;
+ elems->num_ip6 = 0;
+}
+
+static void ns2_clear_procedures(struct ns2_sns_state *gss)
+{
+ struct ns2_sns_procedure *procedure, *tmp;
+ gss->current_procedure = NULL;
+ llist_for_each_entry_safe(procedure, tmp, &gss->procedures, list) {
+ llist_del(&procedure->list);
+ talloc_free(procedure);
+ }
+}
+
+static void ns2_vc_create_ip(struct osmo_fsm_inst *fi, struct gprs_ns2_nse *nse, const struct osmo_sockaddr *remote,
+ uint8_t sig_weight, uint8_t data_weight)
{
struct gprs_ns2_inst *nsi = nse->nsi;
struct gprs_ns2_vc *nsvc;
struct gprs_ns2_vc_bind *bind;
- struct osmo_sockaddr remote;
- /* copy over. Both data structures use network byte order */
- remote.u.sin.sin_family = AF_INET;
- remote.u.sin.sin_addr.s_addr = ip4->ip_addr;
- remote.u.sin.sin_port = ip4->udp_port;
/* for every bind, create a connection if bind type == IP */
llist_for_each_entry(bind, &nsi->binding, list) {
+ if (bind->ll != GPRS_NS2_LL_UDP)
+ continue;
/* ignore failed connection */
nsvc = gprs_ns2_ip_connect_inactive(bind,
- &remote,
+ remote,
nse, 0);
if (!nsvc) {
LOGPFSML(fi, LOGL_ERROR, "SNS-CONFIG: Failed to create NSVC\n");
continue;
}
- nsvc->sig_weight = ip4->sig_weight;
- nsvc->data_weight = ip4->data_weight;
+ nsvc->sig_weight = sig_weight;
+ nsvc->data_weight = data_weight;
}
}
+static void ns2_nsvc_create_ip4(struct osmo_fsm_inst *fi,
+ struct gprs_ns2_nse *nse,
+ const struct gprs_ns_ie_ip4_elem *ip4)
+{
+ struct osmo_sockaddr remote = { };
+ /* copy over. Both data structures use network byte order */
+ remote.u.sin.sin_family = AF_INET;
+ remote.u.sin.sin_addr.s_addr = ip4->ip_addr;
+ remote.u.sin.sin_port = ip4->udp_port;
+
+ ns2_vc_create_ip(fi, nse, &remote, ip4->sig_weight, ip4->data_weight);
+}
+
static void ns2_nsvc_create_ip6(struct osmo_fsm_inst *fi,
struct gprs_ns2_nse *nse,
const struct gprs_ns_ie_ip6_elem *ip6)
{
- struct gprs_ns2_inst *nsi = nse->nsi;
- struct gprs_ns2_vc *nsvc;
- struct gprs_ns2_vc_bind *bind;
struct osmo_sockaddr remote = {};
/* copy over. Both data structures use network byte order */
remote.u.sin6.sin6_family = AF_INET6;
remote.u.sin6.sin6_addr = ip6->ip_addr;
remote.u.sin6.sin6_port = ip6->udp_port;
- /* for every bind, create a connection if bind type == IP */
- llist_for_each_entry(bind, &nsi->binding, list) {
- /* ignore failed connection */
- nsvc = gprs_ns2_ip_connect_inactive(bind,
- &remote,
- nse, 0);
- if (!nsvc) {
- LOGPFSML(fi, LOGL_ERROR, "SNS-CONFIG: Failed to create NSVC\n");
+ ns2_vc_create_ip(fi, nse, &remote, ip6->sig_weight, ip6->data_weight);
+}
+
+static struct gprs_ns2_vc *nsvc_for_bind_and_remote(struct gprs_ns2_nse *nse,
+ struct gprs_ns2_vc_bind *bind,
+ const struct osmo_sockaddr *remote)
+{
+ struct gprs_ns2_vc *nsvc;
+
+ llist_for_each_entry(nsvc, &nse->nsvc, list) {
+ if (nsvc->bind != bind)
continue;
- }
- nsvc->sig_weight = ip6->sig_weight;
- nsvc->data_weight = ip6->data_weight;
+ if (!osmo_sockaddr_cmp(remote, gprs_ns2_ip_vc_remote(nsvc)))
+ return nsvc;
}
+ return NULL;
}
-
static int create_missing_nsvcs(struct osmo_fsm_inst *fi)
{
struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
struct gprs_ns2_nse *nse = nse_inst_from_fi(fi);
struct gprs_ns2_vc *nsvc;
- struct gprs_ns2_vc_bind *bind;
+ struct ns2_sns_bind *sbind;
struct osmo_sockaddr remote = { };
unsigned int i;
- for (i = 0; i < gss->num_ip4_remote; i++) {
- const struct gprs_ns_ie_ip4_elem *ip4 = &gss->ip4_remote[i];
+ /* iterate over all remote IPv4 endpoints */
+ for (i = 0; i < gss->remote.num_ip4; i++) {
+ const struct gprs_ns_ie_ip4_elem *ip4 = &gss->remote.ip4[i];
remote.u.sin.sin_family = AF_INET;
remote.u.sin.sin_addr.s_addr = ip4->ip_addr;
remote.u.sin.sin_port = ip4->udp_port;
- llist_for_each_entry(bind, &nse->nsi->binding, list) {
- bool found = false;
+ /* iterate over all local binds within this SNS */
+ llist_for_each_entry(sbind, &gss->binds, list) {
+ struct gprs_ns2_vc_bind *bind = sbind->bind;
- llist_for_each_entry(nsvc, &nse->nsvc, list) {
- if (nsvc->bind != bind)
- continue;
-
- if (!osmo_sockaddr_cmp(&remote, gprs_ns2_ip_vc_sockaddr(nsvc))) {
- found = true;
- break;
- }
- }
+ /* we only care about UDP binds */
+ if (bind->ll != GPRS_NS2_LL_UDP)
+ continue;
- if (!found) {
+ nsvc = nsvc_for_bind_and_remote(nse, bind, &remote);
+ if (!nsvc) {
nsvc = gprs_ns2_ip_connect_inactive(bind, &remote, nse, 0);
if (!nsvc) {
/* TODO: add to a list to send back a NS-STATUS */
@@ -326,27 +458,24 @@ static int create_missing_nsvcs(struct osmo_fsm_inst *fi)
}
}
- for (i = 0; i < gss->num_ip6_remote; i++) {
- const struct gprs_ns_ie_ip6_elem *ip6 = &gss->ip6_remote[i];
+ /* iterate over all remote IPv4 endpoints */
+ for (i = 0; i < gss->remote.num_ip6; i++) {
+ const struct gprs_ns_ie_ip6_elem *ip6 = &gss->remote.ip6[i];
remote.u.sin6.sin6_family = AF_INET6;
remote.u.sin6.sin6_addr = ip6->ip_addr;
remote.u.sin6.sin6_port = ip6->udp_port;
- llist_for_each_entry(bind, &nse->nsi->binding, list) {
- bool found = false;
-
- llist_for_each_entry(nsvc, &nse->nsvc, list) {
- if (nsvc->bind != bind)
- continue;
+ /* iterate over all local binds within this SNS */
+ llist_for_each_entry(sbind, &gss->binds, list) {
+ struct gprs_ns2_vc_bind *bind = sbind->bind;
- if (!osmo_sockaddr_cmp(&remote, gprs_ns2_ip_vc_sockaddr(nsvc))) {
- found = true;
- break;
- }
- }
+ if (bind->ll != GPRS_NS2_LL_UDP)
+ continue;
- if (!found) {
+ /* we only care about UDP binds */
+ nsvc = nsvc_for_bind_and_remote(nse, bind, &remote);
+ if (!nsvc) {
nsvc = gprs_ns2_ip_connect_inactive(bind, &remote, nse, 0);
if (!nsvc) {
/* TODO: add to a list to send back a NS-STATUS */
@@ -366,114 +495,144 @@ static int create_missing_nsvcs(struct osmo_fsm_inst *fi)
}
/* Add a given remote IPv4 element to gprs_sns_state */
-static int add_remote_ip4_elem(struct ns2_sns_state *gss, const struct gprs_ns_ie_ip4_elem *ip4)
+static int add_ip4_elem(struct ns2_sns_state *gss, struct ns2_sns_elems *elems,
+ const struct gprs_ns_ie_ip4_elem *ip4)
{
- unsigned int i;
-
- if (gss->num_ip4_remote >= gss->num_max_ip4_remote)
- return -NS_CAUSE_INVAL_NR_NS_VC;
-
/* check for duplicates */
- for (i = 0; i < gss->num_ip4_remote; i++) {
- if (memcmp(&gss->ip4_remote[i], ip4, sizeof(*ip4)))
+ for (unsigned int i = 0; i < elems->num_ip4; i++) {
+ if (memcmp(&elems->ip4[i], ip4, sizeof(*ip4)))
continue;
- /* TODO: log message duplicate */
- /* TODO: check if this is the correct cause code */
- return -NS_CAUSE_PROTO_ERR_UNSPEC;
+ return -1;
}
- gss->ip4_remote = talloc_realloc(gss, gss->ip4_remote, struct gprs_ns_ie_ip4_elem,
- gss->num_ip4_remote+1);
- gss->ip4_remote[gss->num_ip4_remote] = *ip4;
- gss->num_ip4_remote += 1;
+ elems->ip4 = talloc_realloc(gss, elems->ip4, struct gprs_ns_ie_ip4_elem,
+ elems->num_ip4+1);
+ elems->ip4[elems->num_ip4] = *ip4;
+ elems->num_ip4 += 1;
return 0;
}
/* Remove a given remote IPv4 element from gprs_sns_state */
-static int remove_remote_ip4_elem(struct ns2_sns_state *gss, const struct gprs_ns_ie_ip4_elem *ip4)
+static int remove_ip4_elem(struct ns2_sns_state *gss, struct ns2_sns_elems *elems,
+ const struct gprs_ns_ie_ip4_elem *ip4)
{
unsigned int i;
- for (i = 0; i < gss->num_ip4_remote; i++) {
- if (memcmp(&gss->ip4_remote[i], ip4, sizeof(*ip4)))
+ for (i = 0; i < elems->num_ip4; i++) {
+ if (memcmp(&elems->ip4[i], ip4, sizeof(*ip4)))
continue;
/* all array elements < i remain as they are; all > i are shifted left by one */
- memmove(&gss->ip4_remote[i], &gss->ip4_remote[i+1], gss->num_ip4_remote-i-1);
- gss->num_ip4_remote -= 1;
+ memmove(&elems->ip4[i], &elems->ip4[i+1], elems->num_ip4-i-1);
+ elems->num_ip4 -= 1;
return 0;
}
return -1;
}
/* update the weights for specified remote IPv4 */
-static int update_remote_ip4_elem(struct ns2_sns_state *gss, const struct gprs_ns_ie_ip4_elem *ip4)
+static int update_ip4_elem(struct ns2_sns_state *gss, struct ns2_sns_elems *elems,
+ const struct gprs_ns_ie_ip4_elem *ip4)
{
unsigned int i;
- for (i = 0; i < gss->num_ip4_remote; i++) {
- if (gss->ip4_remote[i].ip_addr != ip4->ip_addr ||
- gss->ip4_remote[i].udp_port != ip4->udp_port)
+ for (i = 0; i < elems->num_ip4; i++) {
+ if (elems->ip4[i].ip_addr != ip4->ip_addr ||
+ elems->ip4[i].udp_port != ip4->udp_port)
continue;
- gss->ip4_remote[i].sig_weight = ip4->sig_weight;
- gss->ip4_remote[i].data_weight = ip4->data_weight;
+ elems->ip4[i].sig_weight = ip4->sig_weight;
+ elems->ip4[i].data_weight = ip4->data_weight;
return 0;
}
return -1;
}
/* Add a given remote IPv6 element to gprs_sns_state */
-static int add_remote_ip6_elem(struct ns2_sns_state *gss, const struct gprs_ns_ie_ip6_elem *ip6)
+static int add_ip6_elem(struct ns2_sns_state *gss, struct ns2_sns_elems *elems,
+ const struct gprs_ns_ie_ip6_elem *ip6)
{
- if (gss->num_ip6_remote >= gss->num_max_ip6_remote)
- return -NS_CAUSE_INVAL_NR_NS_VC;
+ /* check for duplicates */
+ for (unsigned int i = 0; i < elems->num_ip6; i++) {
+ if (memcmp(&elems->ip6[i].ip_addr, &ip6->ip_addr, sizeof(ip6->ip_addr)) ||
+ elems->ip6[i].udp_port != ip6->udp_port)
+ continue;
+ return -1;
+ }
- gss->ip6_remote = talloc_realloc(gss, gss->ip6_remote, struct gprs_ns_ie_ip6_elem,
- gss->num_ip6_remote+1);
- gss->ip6_remote[gss->num_ip6_remote] = *ip6;
- gss->num_ip6_remote += 1;
+ elems->ip6 = talloc_realloc(gss, elems->ip6, struct gprs_ns_ie_ip6_elem,
+ elems->num_ip6+1);
+ elems->ip6[elems->num_ip6] = *ip6;
+ elems->num_ip6 += 1;
return 0;
}
/* Remove a given remote IPv6 element from gprs_sns_state */
-static int remove_remote_ip6_elem(struct ns2_sns_state *gss, const struct gprs_ns_ie_ip6_elem *ip6)
+static int remove_ip6_elem(struct ns2_sns_state *gss, struct ns2_sns_elems *elems,
+ const struct gprs_ns_ie_ip6_elem *ip6)
{
unsigned int i;
- for (i = 0; i < gss->num_ip6_remote; i++) {
- if (memcmp(&gss->ip6_remote[i], ip6, sizeof(*ip6)))
+ for (i = 0; i < elems->num_ip6; i++) {
+ if (memcmp(&elems->ip6[i], ip6, sizeof(*ip6)))
continue;
/* all array elements < i remain as they are; all > i are shifted left by one */
- memmove(&gss->ip6_remote[i], &gss->ip6_remote[i+1], gss->num_ip6_remote-i-1);
- gss->num_ip6_remote -= 1;
+ memmove(&elems->ip6[i], &elems->ip6[i+1], elems->num_ip6-i-1);
+ elems->num_ip6 -= 1;
return 0;
}
return -1;
}
/* update the weights for specified remote IPv6 */
-static int update_remote_ip6_elem(struct ns2_sns_state *gss, const struct gprs_ns_ie_ip6_elem *ip6)
+static int update_ip6_elem(struct ns2_sns_state *gss, struct ns2_sns_elems *elems,
+ const struct gprs_ns_ie_ip6_elem *ip6)
{
unsigned int i;
- for (i = 0; i < gss->num_ip6_remote; i++) {
- if (memcmp(&gss->ip6_remote[i].ip_addr, &ip6->ip_addr, sizeof(ip6->ip_addr)) ||
- gss->ip6_remote[i].udp_port != ip6->udp_port)
+ for (i = 0; i < elems->num_ip6; i++) {
+ if (memcmp(&elems->ip6[i].ip_addr, &ip6->ip_addr, sizeof(ip6->ip_addr)) ||
+ elems->ip6[i].udp_port != ip6->udp_port)
continue;
- gss->ip6_remote[i].sig_weight = ip6->sig_weight;
- gss->ip6_remote[i].data_weight = ip6->data_weight;
+ elems->ip6[i].sig_weight = ip6->sig_weight;
+ elems->ip6[i].data_weight = ip6->data_weight;
return 0;
}
return -1;
}
+static int remove_bind_elem(struct ns2_sns_state *gss, struct ns2_sns_elems *elems, struct ns2_sns_bind *sbind)
+{
+ struct gprs_ns_ie_ip4_elem ip4;
+ struct gprs_ns_ie_ip6_elem ip6;
+ const struct osmo_sockaddr *saddr = gprs_ns2_ip_bind_sockaddr(sbind->bind);
+
+ switch (saddr->u.sa.sa_family) {
+ case AF_INET:
+ ip4.ip_addr = saddr->u.sin.sin_addr.s_addr;
+ ip4.udp_port = saddr->u.sin.sin_port;
+ ip4.sig_weight = sbind->bind->sns_sig_weight;
+ ip4.data_weight = sbind->bind->sns_data_weight;
+ return remove_ip4_elem(gss, elems, &ip4);
+ case AF_INET6:
+ memcpy(&ip6.ip_addr, &saddr->u.sin6.sin6_addr, sizeof(struct in6_addr));
+ ip6.udp_port = saddr->u.sin.sin_port;
+ ip6.sig_weight = sbind->bind->sns_sig_weight;
+ ip6.data_weight = sbind->bind->sns_data_weight;
+ return remove_ip6_elem(gss, elems, &ip6);
+ default:
+ return -1;
+ }
+
+ return -1;
+}
+
static int do_sns_change_weight(struct osmo_fsm_inst *fi, const struct gprs_ns_ie_ip4_elem *ip4, const struct gprs_ns_ie_ip6_elem *ip6)
{
struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
struct gprs_ns2_nse *nse = nse_inst_from_fi(fi);
struct gprs_ns2_vc *nsvc;
struct osmo_sockaddr sa = {};
- struct osmo_sockaddr *remote;
+ const struct osmo_sockaddr *remote;
uint8_t new_signal;
uint8_t new_data;
@@ -484,7 +643,7 @@ static int do_sns_change_weight(struct osmo_fsm_inst *fi, const struct gprs_ns_i
* SNS-ACK PDU with a cause code of "Invalid weights". */
if (ip4) {
- if (update_remote_ip4_elem(gss, ip4))
+ if (update_ip4_elem(gss, &gss->remote, ip4))
return -NS_CAUSE_UNKN_IP_EP;
/* copy over. Both data structures use network byte order */
@@ -494,7 +653,7 @@ static int do_sns_change_weight(struct osmo_fsm_inst *fi, const struct gprs_ns_i
new_signal = ip4->sig_weight;
new_data = ip4->data_weight;
} else if (ip6) {
- if (update_remote_ip6_elem(gss, ip6))
+ if (update_ip6_elem(gss, &gss->remote, ip6))
return -NS_CAUSE_UNKN_IP_EP;
/* copy over. Both data structures use network byte order */
@@ -508,7 +667,7 @@ static int do_sns_change_weight(struct osmo_fsm_inst *fi, const struct gprs_ns_i
}
llist_for_each_entry(nsvc, &nse->nsvc, list) {
- remote = gprs_ns2_ip_vc_sockaddr(nsvc);
+ remote = gprs_ns2_ip_vc_remote(nsvc);
/* all nsvc in NSE should be IP/UDP nsvc */
OSMO_ASSERT(remote);
@@ -533,18 +692,18 @@ static int do_sns_delete(struct osmo_fsm_inst *fi,
struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
struct gprs_ns2_nse *nse = nse_inst_from_fi(fi);
struct gprs_ns2_vc *nsvc, *tmp;
- struct osmo_sockaddr *remote;
+ const struct osmo_sockaddr *remote;
struct osmo_sockaddr sa = {};
if (ip4) {
- if (remove_remote_ip4_elem(gss, ip4) < 0)
+ if (remove_ip4_elem(gss, &gss->remote, ip4) < 0)
return -NS_CAUSE_UNKN_IP_EP;
/* copy over. Both data structures use network byte order */
sa.u.sin.sin_addr.s_addr = ip4->ip_addr;
sa.u.sin.sin_port = ip4->udp_port;
sa.u.sin.sin_family = AF_INET;
} else if (ip6) {
- if (remove_remote_ip6_elem(gss, ip6))
+ if (remove_ip6_elem(gss, &gss->remote, ip6))
return -NS_CAUSE_UNKN_IP_EP;
/* copy over. Both data structures use network byte order */
@@ -556,7 +715,7 @@ static int do_sns_delete(struct osmo_fsm_inst *fi,
}
llist_for_each_entry_safe(nsvc, tmp, &nse->nsvc, list) {
- remote = gprs_ns2_ip_vc_sockaddr(nsvc);
+ remote = gprs_ns2_ip_vc_remote(nsvc);
/* all nsvc in NSE should be IP/UDP nsvc */
OSMO_ASSERT(remote);
if (osmo_sockaddr_cmp(&sa, remote))
@@ -581,12 +740,18 @@ static int do_sns_add(struct osmo_fsm_inst *fi,
/* Upon receiving an SNS-ADD PDU, if the consequent number of IPv4 endpoints
* exceeds the number of IPv4 endpoints supported by the NSE, the NSE shall send
* an SNS-ACK PDU with a cause code set to "Invalid number of IP4 Endpoints". */
- switch (gss->ip) {
- case IPv4:
- rc = add_remote_ip4_elem(gss, ip4);
+ switch (gss->family) {
+ case AF_INET:
+ if (gss->remote.num_ip4 >= gss->num_max_ip4_remote)
+ return -NS_CAUSE_INVAL_NR_NS_VC;
+ /* TODO: log message duplicate */
+ rc = add_ip4_elem(gss, &gss->remote, ip4);
break;
- case IPv6:
- rc = add_remote_ip6_elem(gss, ip6);
+ case AF_INET6:
+ if (gss->remote.num_ip6 >= gss->num_max_ip6_remote)
+ return -NS_CAUSE_INVAL_NR_NS_VC;
+ /* TODO: log message duplicate */
+ rc = add_ip6_elem(gss, &gss->remote, ip6);
break;
default:
/* the gss->ip is initialized with the bss */
@@ -594,13 +759,13 @@ static int do_sns_add(struct osmo_fsm_inst *fi,
}
if (rc)
- return rc;
+ return -NS_CAUSE_PROTO_ERR_UNSPEC;
/* Upon receiving an SNS-ADD PDU containing an already configured IP endpoint the
* NSE shall send an SNS-ACK PDU with the cause code "Protocol error -
* unspecified" */
- switch (gss->ip) {
- case IPv4:
+ switch (gss->family) {
+ case AF_INET:
nsvc = nsvc_by_ip4_elem(nse, ip4);
if (nsvc) {
/* the nsvc should be already in sync with the ip4 / ip6 elements */
@@ -610,7 +775,7 @@ static int do_sns_add(struct osmo_fsm_inst *fi,
/* TODO: failure case */
ns2_nsvc_create_ip4(fi, nse, ip4);
break;
- case IPv6:
+ case AF_INET6:
nsvc = nsvc_by_ip6_elem(nse, ip6);
if (nsvc) {
/* the nsvc should be already in sync with the ip4 / ip6 elements */
@@ -628,35 +793,31 @@ static int do_sns_add(struct osmo_fsm_inst *fi,
}
-static void ns2_sns_st_unconfigured(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+static void ns2_sns_st_bss_unconfigured(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
- struct gprs_ns2_nse *nse = nse_inst_from_fi(fi);
- struct gprs_ns2_inst *nsi = nse->nsi;
-
- switch (event) {
- case GPRS_SNS_EV_START:
- osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_SIZE, nsi->timeout[NS_TOUT_TSNS_PROV], 1);
- break;
- default:
- OSMO_ASSERT(0);
- }
+ struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
+ OSMO_ASSERT(gss->role == GPRS_SNS_ROLE_BSS);
+ /* empty state - SNS Select will start by ns2_sns_st_all_action() */
}
-static void ns2_sns_st_size(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+static void ns2_sns_st_bss_size(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
+ struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
struct gprs_ns2_nse *nse = nse_inst_from_fi(fi);
struct gprs_ns2_inst *nsi = nse->nsi;
struct tlv_parsed *tp = NULL;
+ OSMO_ASSERT(gss->role == GPRS_SNS_ROLE_BSS);
+
switch (event) {
- case GPRS_SNS_EV_SIZE_ACK:
+ case NS2_SNS_EV_RX_SIZE_ACK:
tp = data;
if (TLVP_VAL_MINLEN(tp, NS_IE_CAUSE, 1)) {
LOGPFSML(fi, LOGL_ERROR, "SNS-SIZE-ACK with cause %s\n",
gprs_ns2_cause_str(*TLVP_VAL(tp, NS_IE_CAUSE)));
/* TODO: What to do? */
} else {
- osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_CONFIG_BSS,
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_BSS_CONFIG_BSS,
nsi->timeout[NS_TOUT_TSNS_PROV], 2);
}
break;
@@ -665,33 +826,230 @@ static void ns2_sns_st_size(struct osmo_fsm_inst *fi, uint32_t event, void *data
}
}
-static void ns2_sns_st_size_onenter(struct osmo_fsm_inst *fi, uint32_t old_state)
+static int ns2_sns_count_num_local_ep(struct osmo_fsm_inst *fi, int ip_proto)
+{
+ struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
+ struct ns2_sns_bind *sbind;
+ int count = 0;
+
+ llist_for_each_entry(sbind, &gss->binds, list) {
+ const struct osmo_sockaddr *sa = gprs_ns2_ip_bind_sockaddr(sbind->bind);
+ if (!sa)
+ continue;
+
+ switch (ip_proto) {
+ case AF_INET:
+ if (sa->u.sas.ss_family == AF_INET)
+ count++;
+ break;
+ case AF_INET6:
+ if (sa->u.sas.ss_family == AF_INET6)
+ count++;
+ break;
+ }
+ }
+ return count;
+}
+
+static int ns2_sns_copy_local_endpoints(struct ns2_sns_state *gss)
+{
+ switch (gss->family) {
+ case AF_INET:
+ gss->local_procedure.ip4 = talloc_realloc(gss, gss->local_procedure.ip4, struct gprs_ns_ie_ip4_elem,
+ gss->local.num_ip4);
+ if (!gss->local_procedure.ip4)
+ return -ENOMEM;
+
+ gss->local_procedure.num_ip4 = gss->local.num_ip4;
+ memcpy(gss->local_procedure.ip4, gss->local.ip4,
+ sizeof(struct gprs_ns_ie_ip4_elem) * gss->local.num_ip4);
+ break;
+ case AF_INET6:
+ gss->local_procedure.ip6 = talloc_realloc(gss, gss->local_procedure.ip6, struct gprs_ns_ie_ip6_elem,
+ gss->local.num_ip6);
+ if (!gss->local_procedure.ip6)
+ return -ENOMEM;
+
+ gss->local_procedure.num_ip6 = gss->local.num_ip6;
+ memcpy(gss->local_procedure.ip6, gss->local.ip6,
+ sizeof(struct gprs_ns_ie_ip6_elem) * gss->local.num_ip6);
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+
+ return 0;
+}
+
+static void ns2_sns_compute_local_ep_from_binds(struct osmo_fsm_inst *fi)
+{
+ struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
+ struct gprs_ns_ie_ip4_elem *ip4_elems;
+ struct gprs_ns_ie_ip6_elem *ip6_elems;
+ struct gprs_ns2_vc_bind *bind;
+ struct ns2_sns_bind *sbind;
+ const struct osmo_sockaddr *remote;
+ const struct osmo_sockaddr *sa;
+ struct osmo_sockaddr local;
+ int count;
+
+ ns2_clear_elems(&gss->local);
+
+ /* no initial available */
+ if (gss->role == GPRS_SNS_ROLE_BSS) {
+ if (!gss->initial)
+ return;
+ remote = &gss->initial->saddr;
+ } else
+ remote = gprs_ns2_ip_vc_remote(gss->sns_nsvc);
+
+ /* count how many bindings are available (only UDP binds) */
+ count = llist_count(&gss->binds);
+ if (count == 0) {
+ LOGPFSML(fi, LOGL_ERROR, "No local binds for this NSE -> cannot determine IP endpoints\n");
+ return;
+ }
+
+ switch (gss->family) {
+ case AF_INET:
+ ip4_elems = talloc_realloc(fi, gss->local.ip4, struct gprs_ns_ie_ip4_elem, count);
+ if (!ip4_elems)
+ return;
+
+ gss->local.ip4 = ip4_elems;
+ llist_for_each_entry(sbind, &gss->binds, list) {
+ bind = sbind->bind;
+ sa = gprs_ns2_ip_bind_sockaddr(bind);
+ if (!sa)
+ continue;
+
+ if (sa->u.sas.ss_family != AF_INET)
+ continue;
+
+ /* check if this is an specific bind */
+ if (sa->u.sin.sin_addr.s_addr == 0) {
+ if (osmo_sockaddr_local_ip(&local, remote))
+ continue;
+
+ ip4_elems->ip_addr = local.u.sin.sin_addr.s_addr;
+ } else {
+ ip4_elems->ip_addr = sa->u.sin.sin_addr.s_addr;
+ }
+
+ ip4_elems->udp_port = sa->u.sin.sin_port;
+ ip4_elems->sig_weight = bind->sns_sig_weight;
+ ip4_elems->data_weight = bind->sns_data_weight;
+ ip4_elems++;
+ }
+
+ gss->local.num_ip4 = count;
+ gss->num_max_nsvcs = OSMO_MAX(gss->num_max_ip4_remote * gss->local.num_ip4, 8);
+ break;
+ case AF_INET6:
+ /* IPv6 */
+ ip6_elems = talloc_realloc(fi, gss->local.ip6, struct gprs_ns_ie_ip6_elem, count);
+ if (!ip6_elems)
+ return;
+
+ gss->local.ip6 = ip6_elems;
+
+ llist_for_each_entry(sbind, &gss->binds, list) {
+ bind = sbind->bind;
+ sa = gprs_ns2_ip_bind_sockaddr(bind);
+ if (!sa)
+ continue;
+
+ if (sa->u.sas.ss_family != AF_INET6)
+ continue;
+
+ /* check if this is an specific bind */
+ if (IN6_IS_ADDR_UNSPECIFIED(&sa->u.sin6.sin6_addr)) {
+ if (osmo_sockaddr_local_ip(&local, remote))
+ continue;
+
+ ip6_elems->ip_addr = local.u.sin6.sin6_addr;
+ } else {
+ ip6_elems->ip_addr = sa->u.sin6.sin6_addr;
+ }
+
+ ip6_elems->udp_port = sa->u.sin.sin_port;
+ ip6_elems->sig_weight = bind->sns_sig_weight;
+ ip6_elems->data_weight = bind->sns_data_weight;
+
+ ip6_elems++;
+ }
+ gss->local.num_ip6 = count;
+ gss->num_max_nsvcs = OSMO_MAX(gss->num_max_ip6_remote * gss->local.num_ip6, 8);
+ break;
+ }
+
+ ns2_sns_copy_local_endpoints(gss);
+}
+
+static void ns2_sns_choose_next_bind(struct ns2_sns_state *gss)
+{
+ /* take the first bind or take the next bind */
+ if (!gss->initial_bind || gss->initial_bind->list.next == &gss->binds)
+ gss->initial_bind = llist_first_entry_or_null(&gss->binds, struct ns2_sns_bind, list);
+ else
+ gss->initial_bind = llist_entry(gss->initial_bind->list.next, struct ns2_sns_bind, list);
+}
+
+/* setup all dynamic SNS settings, create a new nsvc and send the SIZE */
+static void ns2_sns_st_bss_size_onenter(struct osmo_fsm_inst *fi, uint32_t old_state)
{
struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
+ OSMO_ASSERT(gss->role == GPRS_SNS_ROLE_BSS);
+
+ /* on a generic failure, the timer callback will recover */
if (old_state != GPRS_SNS_ST_UNCONFIGURED)
- ns2_prim_status_ind(gss->nse->nsi, gss->nse->nsei, 0, NS_AFF_CAUSE_SNS_FAILURE);
+ ns2_prim_status_ind(gss->nse, NULL, 0, GPRS_NS2_AFF_CAUSE_SNS_FAILURE);
+ if (old_state != GPRS_SNS_ST_BSS_SIZE)
+ gss->N = 0;
+
+ ns2_clear_procedures(gss);
+ gss->alive = false;
+
+ ns2_sns_compute_local_ep_from_binds(fi);
+ ns2_sns_choose_next_bind(gss);
+
+ /* setup the NSVC */
+ if (!gss->sns_nsvc) {
+ struct gprs_ns2_vc_bind *bind = gss->initial_bind->bind;
+ struct osmo_sockaddr *remote = &gss->initial->saddr;
+ gss->sns_nsvc = ns2_ip_bind_connect(bind, gss->nse, remote);
+ if (!gss->sns_nsvc)
+ return;
+ /* A pre-configured endpoint shall not be used for NSE data or signalling traffic
+ * (with the exception of Size and Configuration procedures) unless it is configured
+ * by the SGSN using the auto-configuration procedures */
+ gss->sns_nsvc->sns_only = true;
+ }
if (gss->num_max_ip4_remote > 0)
- ns2_tx_sns_size(gss->sns_nsvc, true, gss->num_max_nsvcs, gss->num_max_ip4_remote, -1);
+ ns2_tx_sns_size(gss->sns_nsvc, true, gss->num_max_nsvcs, gss->local.num_ip4, -1);
else
- ns2_tx_sns_size(gss->sns_nsvc, true, gss->num_max_nsvcs, -1, gss->num_max_ip6_remote);
-
+ ns2_tx_sns_size(gss->sns_nsvc, true, gss->num_max_nsvcs, -1, gss->local.num_ip6);
}
-static void ns2_sns_st_config_bss(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+static void ns2_sns_st_bss_config_bss(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
+ struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
+ struct gprs_ns2_nse *nse = nse_inst_from_fi(fi);
struct tlv_parsed *tp = NULL;
+ OSMO_ASSERT(gss->role == GPRS_SNS_ROLE_BSS);
+
switch (event) {
- case GPRS_SNS_EV_CONFIG_ACK:
+ case NS2_SNS_EV_RX_CONFIG_ACK:
tp = (struct tlv_parsed *) data;
if (TLVP_VAL_MINLEN(tp, NS_IE_CAUSE, 1)) {
LOGPFSML(fi, LOGL_ERROR, "SNS-CONFIG-ACK with cause %s\n",
gprs_ns2_cause_str(*TLVP_VAL(tp, NS_IE_CAUSE)));
/* TODO: What to do? */
} else {
- osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_CONFIG_SGSN, 0, 0);
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_BSS_CONFIG_SGSN, nse->nsi->timeout[NS_TOUT_TSNS_PROV], 3);
}
break;
default:
@@ -699,152 +1057,139 @@ static void ns2_sns_st_config_bss(struct osmo_fsm_inst *fi, uint32_t event, void
}
}
-static void ns2_sns_st_config_bss_onenter(struct osmo_fsm_inst *fi, uint32_t old_state)
+static void ns2_sns_st_bss_config_bss_onenter(struct osmo_fsm_inst *fi, uint32_t old_state)
{
struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
+
+ OSMO_ASSERT(gss->role == GPRS_SNS_ROLE_BSS);
+
+ if (old_state != GPRS_SNS_ST_BSS_CONFIG_BSS)
+ gss->N = 0;
+
/* Transmit SNS-CONFIG */
- /* TODO: ipv6 */
- switch (gss->ip) {
- case IPv4:
+ switch (gss->family) {
+ case AF_INET:
ns2_tx_sns_config(gss->sns_nsvc, true,
- gss->ip4_local, gss->num_ip4_local,
- NULL, 0);
+ gss->local.ip4, gss->local.num_ip4,
+ NULL, 0);
break;
- case IPv6:
+ case AF_INET6:
ns2_tx_sns_config(gss->sns_nsvc, true,
- NULL, 0,
- gss->ip6_local, gss->num_ip6_local);
+ NULL, 0,
+ gss->local.ip6, gss->local.num_ip6);
break;
}
}
+/* calculate the timeout of the configured state. the configured
+ * state will fail if not at least one NS-VC is alive within X second.
+ */
+static inline int ns_sns_configured_timeout(struct osmo_fsm_inst *fi)
+{
+ int secs;
+ struct gprs_ns2_inst *nsi = nse_inst_from_fi(fi)->nsi;
+ secs = nsi->timeout[NS_TOUT_TNS_ALIVE] * nsi->timeout[NS_TOUT_TNS_ALIVE_RETRIES];
+ secs += nsi->timeout[NS_TOUT_TNS_TEST];
+
+ return secs;
+}
-static void ns_sns_st_config_sgsn_ip4(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+/* append the remote endpoints from the parsed TLV array to the ns2_sns_state */
+static int ns_sns_append_remote_eps(struct osmo_fsm_inst *fi, const struct tlv_parsed *tp)
{
struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
- struct gprs_ns2_nse *nse = nse_inst_from_fi(fi);
- const struct gprs_ns_ie_ip4_elem *v4_list;
- unsigned int num_v4;
- struct tlv_parsed *tp = NULL;
- uint8_t cause;
+ if (TLVP_PRESENT(tp, NS_IE_IPv4_LIST)) {
+ const struct gprs_ns_ie_ip4_elem *v4_list;
+ unsigned int num_v4;
+ v4_list = (const struct gprs_ns_ie_ip4_elem *) TLVP_VAL(tp, NS_IE_IPv4_LIST);
+ num_v4 = TLVP_LEN(tp, NS_IE_IPv4_LIST) / sizeof(*v4_list);
- tp = (struct tlv_parsed *) data;
+ if (num_v4 && gss->remote.ip6)
+ return -NS_CAUSE_INVAL_NR_IPv4_EP;
- if (!TLVP_PRESENT(tp, NS_IE_IPv4_LIST)) {
- cause = NS_CAUSE_INVAL_NR_IPv4_EP;
- ns2_tx_sns_config_ack(gss->sns_nsvc, &cause);
- osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_UNCONFIGURED, 0, 0);
- return;
+ /* realloc to the new size */
+ gss->remote.ip4 = talloc_realloc(gss, gss->remote.ip4,
+ struct gprs_ns_ie_ip4_elem,
+ gss->remote.num_ip4 + num_v4);
+ /* append the new entries to the end of the list */
+ memcpy(&gss->remote.ip4[gss->remote.num_ip4], v4_list, num_v4*sizeof(*v4_list));
+ gss->remote.num_ip4 += num_v4;
+
+ LOGPFSML(fi, LOGL_INFO, "Rx SNS-CONFIG: Remote IPv4 list now %u entries\n",
+ gss->remote.num_ip4);
}
- v4_list = (const struct gprs_ns_ie_ip4_elem *) TLVP_VAL(tp, NS_IE_IPv4_LIST);
- num_v4 = TLVP_LEN(tp, NS_IE_IPv4_LIST) / sizeof(*v4_list);
- /* realloc to the new size */
- gss->ip4_remote = talloc_realloc(gss, gss->ip4_remote,
- struct gprs_ns_ie_ip4_elem,
- gss->num_ip4_remote+num_v4);
- /* append the new entries to the end of the list */
- memcpy(&gss->ip4_remote[gss->num_ip4_remote], v4_list, num_v4*sizeof(*v4_list));
- gss->num_ip4_remote += num_v4;
-
- LOGPFSML(fi, LOGL_INFO, "Rx SNS-CONFIG: Remote IPv4 list now %u entries\n",
- gss->num_ip4_remote);
- if (event == GPRS_SNS_EV_CONFIG_END) {
- /* check if sum of data / sig weights == 0 */
- if (ip4_weight_sum_data(gss->ip4_remote, gss->num_ip4_remote) == 0 ||
- ip4_weight_sum_sig(gss->ip4_remote, gss->num_ip4_remote) == 0) {
- cause = NS_CAUSE_INVAL_WEIGH;
- ns2_tx_sns_config_ack(gss->sns_nsvc, &cause);
- osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_UNCONFIGURED, 0, 0);
- return;
- }
- create_missing_nsvcs(fi);
- ns2_tx_sns_config_ack(gss->sns_nsvc, NULL);
- /* start the test procedure on ALL NSVCs! */
- gprs_ns2_start_alive_all_nsvcs(nse);
- osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_CONFIGURED, 0, 0);
- } else {
- /* just send CONFIG-ACK */
- ns2_tx_sns_config_ack(gss->sns_nsvc, NULL);
+
+ if (TLVP_PRESENT(tp, NS_IE_IPv6_LIST)) {
+ const struct gprs_ns_ie_ip6_elem *v6_list;
+ unsigned int num_v6;
+ v6_list = (const struct gprs_ns_ie_ip6_elem *) TLVP_VAL(tp, NS_IE_IPv6_LIST);
+ num_v6 = TLVP_LEN(tp, NS_IE_IPv6_LIST) / sizeof(*v6_list);
+
+ if (num_v6 && gss->remote.ip4)
+ return -NS_CAUSE_INVAL_NR_IPv6_EP;
+
+ /* realloc to the new size */
+ gss->remote.ip6 = talloc_realloc(gss, gss->remote.ip6,
+ struct gprs_ns_ie_ip6_elem,
+ gss->remote.num_ip6 + num_v6);
+ /* append the new entries to the end of the list */
+ memcpy(&gss->remote.ip6[gss->remote.num_ip6], v6_list, num_v6*sizeof(*v6_list));
+ gss->remote.num_ip6 += num_v6;
+
+ LOGPFSML(fi, LOGL_INFO, "Rx SNS-CONFIG: Remote IPv6 list now %d entries\n",
+ gss->remote.num_ip6);
}
+
+ return 0;
}
-static void ns_sns_st_config_sgsn_ip6(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+static void ns2_sns_st_bss_config_sgsn_onenter(struct osmo_fsm_inst *fi, uint32_t old_state)
{
struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
- struct gprs_ns2_nse *nse = nse_inst_from_fi(fi);
- const struct gprs_ns_ie_ip6_elem *v6_list;
- unsigned int num_v6;
- struct tlv_parsed *tp = NULL;
- uint8_t cause;
-
- tp = (struct tlv_parsed *) data;
+ OSMO_ASSERT(gss->role == GPRS_SNS_ROLE_BSS);
- if (!TLVP_PRESENT(tp, NS_IE_IPv6_LIST)) {
- cause = NS_CAUSE_INVAL_NR_IPv6_EP;
- ns2_tx_sns_config_ack(gss->sns_nsvc, &cause);
- osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_UNCONFIGURED, 0, 0);
- return;
- }
- v6_list = (const struct gprs_ns_ie_ip6_elem *) TLVP_VAL(tp, NS_IE_IPv6_LIST);
- num_v6 = TLVP_LEN(tp, NS_IE_IPv6_LIST) / sizeof(*v6_list);
- /* realloc to the new size */
- gss->ip6_remote = talloc_realloc(gss, gss->ip6_remote,
- struct gprs_ns_ie_ip6_elem,
- gss->num_ip6_remote+num_v6);
- /* append the new entries to the end of the list */
- memcpy(&gss->ip6_remote[gss->num_ip6_remote], v6_list, num_v6*sizeof(*v6_list));
- gss->num_ip6_remote += num_v6;
-
- LOGPFSML(fi, LOGL_INFO, "Rx SNS-CONFIG: Remote IPv6 list now %u entries\n",
- gss->num_ip6_remote);
- if (event == GPRS_SNS_EV_CONFIG_END) {
- /* check if sum of data / sig weights == 0 */
- if (ip6_weight_sum_data(gss->ip6_remote, gss->num_ip6_remote) == 0 ||
- ip6_weight_sum_sig(gss->ip6_remote, gss->num_ip6_remote) == 0) {
- cause = NS_CAUSE_INVAL_WEIGH;
- ns2_tx_sns_config_ack(gss->sns_nsvc, &cause);
- osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_UNCONFIGURED, 0, 0);
- return;
- }
- create_missing_nsvcs(fi);
- ns2_tx_sns_config_ack(gss->sns_nsvc, NULL);
- /* start the test procedure on ALL NSVCs! */
- gprs_ns2_start_alive_all_nsvcs(nse);
- osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_CONFIGURED, 0, 0);
- } else {
- /* just send CONFIG-ACK */
- ns2_tx_sns_config_ack(gss->sns_nsvc, NULL);
- }
+ if (old_state != GPRS_SNS_ST_BSS_CONFIG_SGSN)
+ gss->N = 0;
}
-static void ns2_sns_st_config_sgsn(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+static void ns2_sns_st_bss_config_sgsn(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
+ struct gprs_ns2_nse *nse = nse_inst_from_fi(fi);
+ uint8_t cause;
+ int rc;
+
+ OSMO_ASSERT(gss->role == GPRS_SNS_ROLE_BSS);
switch (event) {
- case GPRS_SNS_EV_CONFIG_END:
- case GPRS_SNS_EV_CONFIG:
-
-#if 0 /* part of incoming SNS-SIZE (doesn't happen on BSS side */
- if (TLVP_PRESENT(tp, NS_IE_RESET_FLAG)) {
- /* reset all existing config */
- if (gss->ip4_remote)
- talloc_free(gss->ip4_remote);
- gss->num_ip4_remote = 0;
+ case NS2_SNS_EV_RX_CONFIG_END:
+ case NS2_SNS_EV_RX_CONFIG:
+ rc = ns_sns_append_remote_eps(fi, data);
+ if (rc < 0) {
+ cause = -rc;
+ ns2_tx_sns_config_ack(gss->sns_nsvc, &cause);
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_UNCONFIGURED, 0, 0);
+ return;
}
-#endif
- /* TODO: reject IPv6 elements on IPv4 mode and vice versa */
- switch (gss->ip) {
- case IPv4:
- ns_sns_st_config_sgsn_ip4(fi, event, data);
- break;
- case IPv6:
- ns_sns_st_config_sgsn_ip6(fi, event, data);
- break;
- default:
- OSMO_ASSERT(0);
+ if (event == NS2_SNS_EV_RX_CONFIG_END) {
+ /* check if sum of data / sig weights == 0 */
+ if (ip46_weight_sum_data(&gss->remote) == 0 || ip46_weight_sum_sig(&gss->remote) == 0) {
+ cause = NS_CAUSE_INVAL_WEIGH;
+ ns2_tx_sns_config_ack(gss->sns_nsvc, &cause);
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_UNCONFIGURED, 0, 0);
+ return;
+ }
+ create_missing_nsvcs(fi);
+ ns2_tx_sns_config_ack(gss->sns_nsvc, NULL);
+ /* start the test procedure on ALL NSVCs! */
+ gprs_ns2_start_alive_all_nsvcs(nse);
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_CONFIGURED, 0, 0);
+ } else {
+ /* just send CONFIG-ACK */
+ ns2_tx_sns_config_ack(gss->sns_nsvc, NULL);
+ osmo_timer_schedule(&fi->timer, nse->nsi->timeout[NS_TOUT_TSNS_PROV], 0);
}
break;
default:
@@ -852,7 +1197,7 @@ static void ns2_sns_st_config_sgsn(struct osmo_fsm_inst *fi, uint32_t event, voi
}
}
-/* called when receiving GPRS_SNS_EV_ADD in state configure */
+/* called when receiving NS2_SNS_EV_RX_ADD in state configure */
static void ns2_sns_st_configured_add(struct osmo_fsm_inst *fi,
struct ns2_sns_state *gss,
struct tlv_parsed *tp)
@@ -871,7 +1216,7 @@ static void ns2_sns_st_configured_add(struct osmo_fsm_inst *fi,
*/
trans_id = *TLVP_VAL(tp, NS_IE_TRANS_ID);
- if (gss->ip == IPv4) {
+ if (gss->family == AF_INET) {
if (!TLVP_PRESENT(tp, NS_IE_IPv4_LIST)) {
cause = NS_CAUSE_INVAL_NR_IPv4_EP;
ns2_tx_sns_ack(gss->sns_nsvc, trans_id, &cause, NULL, 0, NULL, 0);
@@ -935,7 +1280,7 @@ static void ns2_sns_st_configured_delete(struct osmo_fsm_inst *fi,
* TODO: check if IPv4_LIST/IPv6_LIST and IP_ADDR is present at the same time
*/
trans_id = *TLVP_VAL(tp, NS_IE_TRANS_ID);
- if (gss->ip == IPv4) {
+ if (gss->family == AF_INET) {
if (TLVP_PRESENT(tp, NS_IE_IPv4_LIST)) {
v4_list = (const struct gprs_ns_ie_ip4_elem *) TLVP_VAL(tp, NS_IE_IPv4_LIST);
num_v4 = TLVP_LEN(tp, NS_IE_IPv4_LIST) / sizeof(*v4_list);
@@ -963,9 +1308,9 @@ static void ns2_sns_st_configured_delete(struct osmo_fsm_inst *fi,
return;
}
/* make a copy as do_sns_delete() will change the array underneath us */
- ip4_remote = talloc_memdup(fi, gss->ip4_remote,
- gss->num_ip4_remote * sizeof(*v4_list));
- for (i = 0; i < gss->num_ip4_remote; i++) {
+ ip4_remote = talloc_memdup(fi, gss->remote.ip4,
+ gss->remote.num_ip4 * sizeof(*v4_list));
+ for (i = 0; i < gss->remote.num_ip4; i++) {
if (ip4_remote[i].ip_addr == ip_addr) {
rc = do_sns_delete(fi, &ip4_remote[i], NULL);
if (rc < 0) {
@@ -1014,9 +1359,9 @@ static void ns2_sns_st_configured_delete(struct osmo_fsm_inst *fi,
}
memcpy(&ip6_addr, (ie+1), sizeof(struct in6_addr));
/* make a copy as do_sns_delete() will change the array underneath us */
- ip6_remote = talloc_memdup(fi, gss->ip6_remote,
- gss->num_ip6_remote * sizeof(*v4_list));
- for (i = 0; i < gss->num_ip6_remote; i++) {
+ ip6_remote = talloc_memdup(fi, gss->remote.ip6,
+ gss->remote.num_ip6 * sizeof(*v4_list));
+ for (i = 0; i < gss->remote.num_ip6; i++) {
if (!memcmp(&ip6_remote[i].ip_addr, &ip6_addr, sizeof(struct in6_addr))) {
rc = do_sns_delete(fi, NULL, &ip6_remote[i]);
if (rc < 0) {
@@ -1095,347 +1440,963 @@ static void ns2_sns_st_configured(struct osmo_fsm_inst *fi, uint32_t event, void
struct tlv_parsed *tp = data;
switch (event) {
- case GPRS_SNS_EV_ADD:
+ case NS2_SNS_EV_RX_ADD:
ns2_sns_st_configured_add(fi, gss, tp);
break;
- case GPRS_SNS_EV_DELETE:
+ case NS2_SNS_EV_RX_DELETE:
ns2_sns_st_configured_delete(fi, gss, tp);
break;
- case GPRS_SNS_EV_CHANGE_WEIGHT:
+ case NS2_SNS_EV_RX_CHANGE_WEIGHT:
ns2_sns_st_configured_change(fi, gss, tp);
break;
+ case NS2_SNS_EV_REQ_NSVC_ALIVE:
+ osmo_timer_del(&fi->timer);
+ break;
}
}
static void ns2_sns_st_configured_onenter(struct osmo_fsm_inst *fi, uint32_t old_state)
{
+ struct gprs_ns2_vc *nsvc;
+ struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
+ struct gprs_ns2_nse *nse = nse_inst_from_fi(fi);
+ /* NS-VC status updates are only parsed in ST_CONFIGURED.
+ * Do an initial check if there are any nsvc alive atm */
+ llist_for_each_entry(nsvc, &nse->nsvc, list) {
+ if (ns2_vc_is_unblocked(nsvc)) {
+ gss->alive = true;
+ osmo_timer_del(&fi->timer);
+ break;
+ }
+ }
+
+ /* remove the initial NSVC if the NSVC isn't part of the configuration */
+ if (gss->sns_nsvc->sns_only)
+ gprs_ns2_free_nsvc(gss->sns_nsvc);
+
+ if (old_state != GPRS_SNS_ST_LOCAL_PROCEDURE)
+ ns2_prim_status_ind(nse, NULL, 0, GPRS_NS2_AFF_CAUSE_SNS_CONFIGURED);
+
+ if (!llist_empty(&gss->procedures)) {
+ osmo_fsm_inst_state_chg(gss->nse->bss_sns_fi, GPRS_SNS_ST_LOCAL_PROCEDURE,
+ gss->nse->nsi->timeout[NS_TOUT_TSNS_PROV], 5);
+ }
+}
+
+static void ns2_sns_st_local_procedure_onenter(struct osmo_fsm_inst *fi, uint32_t old_state)
+{
+ struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
+
+ /* check if resend or not */
+ if (!gss->current_procedure) {
+ /* take next procedure */
+ gss->current_procedure = llist_first_entry_or_null(&gss->procedures,
+ struct ns2_sns_procedure, list);
+ if (!gss->current_procedure) {
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_CONFIGURED, 0, 0);
+ return;
+ }
+ gss->N = 0;
+ gss->current_procedure->running = true;
+ gss->current_procedure->trans_id = ++gss->trans_id;
+ if (gss->trans_id == 0)
+ gss->trans_id = gss->current_procedure->trans_id = 1;
+
+ }
+
+ /* also takes care of retransmitting */
+ switch (gss->current_procedure->procedure) {
+ case SNS_PROC_ADD:
+ if (gss->family == AF_INET)
+ ns2_tx_sns_add(gss->sns_nsvc, gss->current_procedure->trans_id, &gss->current_procedure->ip4, 1, NULL, 0);
+ else
+ ns2_tx_sns_add(gss->sns_nsvc, gss->current_procedure->trans_id, NULL, 0, &gss->current_procedure->ip6, 1);
+ break;
+ case SNS_PROC_CHANGE_WEIGHT:
+ if (gss->family == AF_INET)
+ ns2_tx_sns_change_weight(gss->sns_nsvc, gss->current_procedure->trans_id, &gss->current_procedure->ip4, 1, NULL, 0);
+ else
+ ns2_tx_sns_change_weight(gss->sns_nsvc, gss->current_procedure->trans_id, NULL, 0, &gss->current_procedure->ip6, 1);
+ break;
+ case SNS_PROC_DEL:
+ if (gss->family == AF_INET)
+ ns2_tx_sns_del(gss->sns_nsvc, gss->current_procedure->trans_id, &gss->current_procedure->ip4, 1, NULL, 0);
+ else
+ ns2_tx_sns_del(gss->sns_nsvc, gss->current_procedure->trans_id, NULL, 0, &gss->current_procedure->ip6, 1);
+ break;
+ default:
+ break;
+ }
+}
+
+static void create_nsvc_for_new_sbind(struct ns2_sns_state *gss, struct ns2_sns_bind *sbind)
+{
+ struct gprs_ns2_nse *nse = gss->nse;
+ struct gprs_ns2_vc_bind *bind = sbind->bind;
+ struct gprs_ns2_vc *nsvc;
+ struct osmo_sockaddr remote = { };
+ unsigned int i;
+
+ /* iterate over all remote IPv4 endpoints */
+ for (i = 0; i < gss->remote.num_ip4; i++) {
+ const struct gprs_ns_ie_ip4_elem *ip4 = &gss->remote.ip4[i];
+
+ remote.u.sin.sin_family = AF_INET;
+ remote.u.sin.sin_addr.s_addr = ip4->ip_addr;
+ remote.u.sin.sin_port = ip4->udp_port;
+ /* we only care about UDP binds */
+ if (bind->ll != GPRS_NS2_LL_UDP)
+ continue;
+
+ nsvc = nsvc_for_bind_and_remote(nse, bind, &remote);
+ if (!nsvc) {
+ nsvc = gprs_ns2_ip_connect_inactive(bind, &remote, nse, 0);
+ if (!nsvc) {
+ /* TODO: add to a list to send back a NS-STATUS */
+ continue;
+ }
+ }
+
+ /* update data / signalling weight */
+ nsvc->data_weight = ip4->data_weight;
+ nsvc->sig_weight = ip4->sig_weight;
+ nsvc->sns_only = false;
+ }
+
+ /* iterate over all remote IPv4 endpoints */
+ for (i = 0; i < gss->remote.num_ip6; i++) {
+ const struct gprs_ns_ie_ip6_elem *ip6 = &gss->remote.ip6[i];
+
+ remote.u.sin6.sin6_family = AF_INET6;
+ remote.u.sin6.sin6_addr = ip6->ip_addr;
+ remote.u.sin6.sin6_port = ip6->udp_port;
+
+ /* we only care about UDP binds */
+ nsvc = nsvc_for_bind_and_remote(nse, bind, &remote);
+ if (!nsvc) {
+ nsvc = gprs_ns2_ip_connect_inactive(bind, &remote, nse, 0);
+ if (!nsvc) {
+ /* TODO: add to a list to send back a NS-STATUS */
+ continue;
+ }
+ }
+
+ /* update data / signalling weight */
+ nsvc->data_weight = ip6->data_weight;
+ nsvc->sig_weight = ip6->sig_weight;
+ nsvc->sns_only = false;
+ }
+}
+
+static void ns2_sns_st_local_procedure(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
struct gprs_ns2_nse *nse = nse_inst_from_fi(fi);
- ns2_prim_status_ind(nse->nsi, nse->nsei, 0, NS_AFF_CAUSE_SNS_CONFIGURED);
+ struct gprs_ns_ie_ip4_elem *ip4, *proc4;
+ struct gprs_ns_ie_ip6_elem *ip6, *proc6;
+ struct tlv_parsed *tp = data;
+ uint8_t trans_id;
+ uint8_t cause;
+
+ switch (event) {
+ case NS2_SNS_EV_RX_ADD:
+ ns2_sns_st_configured_add(fi, gss, tp);
+ break;
+ case NS2_SNS_EV_RX_DELETE:
+ ns2_sns_st_configured_delete(fi, gss, tp);
+ break;
+ case NS2_SNS_EV_RX_CHANGE_WEIGHT:
+ ns2_sns_st_configured_change(fi, gss, tp);
+ break;
+ case NS2_SNS_EV_RX_ACK:
+ /* presence of trans_id is already checked here */
+ trans_id = tlvp_val8(tp, NS_IE_TRANS_ID, 0);
+ if (trans_id != gss->current_procedure->trans_id) {
+ LOGPFSML(fi, LOGL_INFO, "NSEI=%u Rx SNS ACK with invalid transaction id %d. Valid %d\n",
+ nse->nsei, trans_id, gss->current_procedure->trans_id);
+ break;
+ }
+
+ if (TLVP_PRESENT(tp, NS_IE_CAUSE)) {
+ /* what happend on error cause? return to size? */
+ cause = tlvp_val8(tp, NS_IE_CAUSE, 0);
+ LOGPFSML(fi, LOGL_ERROR, "NSEI=%u Rx SNS ACK trans %d with cause code %d.\n",
+ nse->nsei, trans_id, cause);
+ sns_failed(fi, NULL);
+ break;
+ }
+
+ switch (gss->current_procedure->procedure) {
+ case SNS_PROC_ADD:
+ switch (gss->family) {
+ case AF_INET:
+ add_ip4_elem(gss, &gss->local, &gss->current_procedure->ip4);
+ break;
+ case AF_INET6:
+ add_ip6_elem(gss, &gss->local, &gss->current_procedure->ip6);
+ break;
+ }
+ /* the sbind can be NULL if the bind has been released by del_bind */
+ if (gss->current_procedure->sbind) {
+ create_nsvc_for_new_sbind(gss, gss->current_procedure->sbind);
+ gprs_ns2_start_alive_all_nsvcs(nse);
+ }
+ break;
+ case SNS_PROC_CHANGE_WEIGHT:
+ switch (gss->family) {
+ case AF_INET:
+ proc4 = &gss->current_procedure->ip4;
+ for (unsigned int i=0; i<gss->local.num_ip4; i++) {
+ ip4 = &gss->local.ip4[i];
+ if (ip4->ip_addr != proc4->ip_addr ||
+ ip4->udp_port != proc4->udp_port)
+ continue;
+ ip4->sig_weight = proc4->sig_weight;
+ ip4->data_weight = proc4->data_weight;
+ break;
+ }
+ break;
+ case AF_INET6:
+ proc6 = &gss->current_procedure->ip6;
+ for (unsigned int i=0; i<gss->local.num_ip6; i++) {
+ ip6 = &gss->local.ip6[i];
+ if (memcmp(&ip6->ip_addr, &proc6->ip_addr, sizeof(proc6->ip_addr)) ||
+ ip6->udp_port != proc6->udp_port) {
+ continue;
+ }
+ ip6->sig_weight = proc6->sig_weight;
+ ip6->data_weight = proc6->data_weight;
+ break;
+ }
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+ break;
+ case SNS_PROC_DEL:
+ switch (gss->family) {
+ case AF_INET:
+ remove_ip4_elem(gss, &gss->local, &gss->current_procedure->ip4);
+ break;
+ case AF_INET6:
+ remove_ip6_elem(gss, &gss->local, &gss->current_procedure->ip6);
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+
+ llist_del(&gss->current_procedure->list);
+ talloc_free(gss->current_procedure);
+ gss->current_procedure = NULL;
+
+ if (llist_empty(&gss->procedures))
+ osmo_fsm_inst_state_chg(gss->nse->bss_sns_fi, GPRS_SNS_ST_CONFIGURED,
+ 0, 0);
+ else
+ osmo_fsm_inst_state_chg(gss->nse->bss_sns_fi, GPRS_SNS_ST_LOCAL_PROCEDURE,
+ gss->nse->nsi->timeout[NS_TOUT_TSNS_PROV], 5);
+ break;
+ }
}
static const struct osmo_fsm_state ns2_sns_bss_states[] = {
[GPRS_SNS_ST_UNCONFIGURED] = {
- .in_event_mask = S(GPRS_SNS_EV_START),
- .out_state_mask = S(GPRS_SNS_ST_SIZE),
+ .in_event_mask = 0, /* handled by all_state_action */
+ .out_state_mask = S(GPRS_SNS_ST_UNCONFIGURED) |
+ S(GPRS_SNS_ST_BSS_SIZE),
.name = "UNCONFIGURED",
- .action = ns2_sns_st_unconfigured,
+ .action = ns2_sns_st_bss_unconfigured,
},
- [GPRS_SNS_ST_SIZE] = {
- .in_event_mask = S(GPRS_SNS_EV_SIZE_ACK),
+ [GPRS_SNS_ST_BSS_SIZE] = {
+ .in_event_mask = S(NS2_SNS_EV_RX_SIZE_ACK),
.out_state_mask = S(GPRS_SNS_ST_UNCONFIGURED) |
- S(GPRS_SNS_ST_SIZE) |
- S(GPRS_SNS_ST_CONFIG_BSS),
- .name = "SIZE",
- .action = ns2_sns_st_size,
- .onenter = ns2_sns_st_size_onenter,
+ S(GPRS_SNS_ST_BSS_SIZE) |
+ S(GPRS_SNS_ST_BSS_CONFIG_BSS),
+ .name = "BSS_SIZE",
+ .action = ns2_sns_st_bss_size,
+ .onenter = ns2_sns_st_bss_size_onenter,
},
- [GPRS_SNS_ST_CONFIG_BSS] = {
- .in_event_mask = S(GPRS_SNS_EV_CONFIG_ACK),
+ [GPRS_SNS_ST_BSS_CONFIG_BSS] = {
+ .in_event_mask = S(NS2_SNS_EV_RX_CONFIG_ACK),
.out_state_mask = S(GPRS_SNS_ST_UNCONFIGURED) |
- S(GPRS_SNS_ST_CONFIG_BSS) |
- S(GPRS_SNS_ST_CONFIG_SGSN) |
- S(GPRS_SNS_ST_SIZE),
- .name = "CONFIG_BSS",
- .action = ns2_sns_st_config_bss,
- .onenter = ns2_sns_st_config_bss_onenter,
+ S(GPRS_SNS_ST_BSS_CONFIG_BSS) |
+ S(GPRS_SNS_ST_BSS_CONFIG_SGSN) |
+ S(GPRS_SNS_ST_BSS_SIZE),
+ .name = "BSS_CONFIG_BSS",
+ .action = ns2_sns_st_bss_config_bss,
+ .onenter = ns2_sns_st_bss_config_bss_onenter,
},
- [GPRS_SNS_ST_CONFIG_SGSN] = {
- .in_event_mask = S(GPRS_SNS_EV_CONFIG) |
- S(GPRS_SNS_EV_CONFIG_END),
+ [GPRS_SNS_ST_BSS_CONFIG_SGSN] = {
+ .in_event_mask = S(NS2_SNS_EV_RX_CONFIG) |
+ S(NS2_SNS_EV_RX_CONFIG_END),
.out_state_mask = S(GPRS_SNS_ST_UNCONFIGURED) |
- S(GPRS_SNS_ST_CONFIG_SGSN) |
+ S(GPRS_SNS_ST_BSS_CONFIG_SGSN) |
S(GPRS_SNS_ST_CONFIGURED) |
- S(GPRS_SNS_ST_SIZE),
- .name = "CONFIG_SGSN",
- .action = ns2_sns_st_config_sgsn,
+ S(GPRS_SNS_ST_BSS_SIZE),
+ .name = "BSS_CONFIG_SGSN",
+ .action = ns2_sns_st_bss_config_sgsn,
+ .onenter = ns2_sns_st_bss_config_sgsn_onenter,
},
[GPRS_SNS_ST_CONFIGURED] = {
- .in_event_mask = S(GPRS_SNS_EV_ADD) |
- S(GPRS_SNS_EV_DELETE) |
- S(GPRS_SNS_EV_CHANGE_WEIGHT),
- .out_state_mask = S(GPRS_SNS_ST_UNCONFIGURED),
+ .in_event_mask = S(NS2_SNS_EV_RX_ADD) |
+ S(NS2_SNS_EV_RX_DELETE) |
+ S(NS2_SNS_EV_RX_CHANGE_WEIGHT) |
+ S(NS2_SNS_EV_REQ_NSVC_ALIVE),
+ .out_state_mask = S(GPRS_SNS_ST_UNCONFIGURED) |
+ S(GPRS_SNS_ST_BSS_SIZE) |
+ S(GPRS_SNS_ST_LOCAL_PROCEDURE),
.name = "CONFIGURED",
.action = ns2_sns_st_configured,
.onenter = ns2_sns_st_configured_onenter,
},
+ [GPRS_SNS_ST_LOCAL_PROCEDURE] = {
+ .in_event_mask = S(NS2_SNS_EV_RX_ADD) |
+ S(NS2_SNS_EV_RX_DELETE) |
+ S(NS2_SNS_EV_RX_CHANGE_WEIGHT) |
+ S(NS2_SNS_EV_RX_ACK) |
+ S(NS2_SNS_EV_REQ_NSVC_ALIVE),
+ .out_state_mask = S(GPRS_SNS_ST_UNCONFIGURED) |
+ S(GPRS_SNS_ST_BSS_SIZE) |
+ S(GPRS_SNS_ST_CONFIGURED) |
+ S(GPRS_SNS_ST_LOCAL_PROCEDURE),
+ .name = "LOCAL_PROCEDURE",
+ .action = ns2_sns_st_local_procedure,
+ .onenter = ns2_sns_st_local_procedure_onenter,
+ },
+
};
static int ns2_sns_fsm_bss_timer_cb(struct osmo_fsm_inst *fi)
{
+ struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
struct gprs_ns2_nse *nse = nse_inst_from_fi(fi);
struct gprs_ns2_inst *nsi = nse->nsi;
+ gss->N++;
switch (fi->T) {
case 1:
- osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_SIZE, nsi->timeout[NS_TOUT_TSNS_PROV], 1);
+ if (gss->N >= nsi->timeout[NS_TOUT_TSNS_SIZE_RETRIES]) {
+ sns_failed(fi, "Size retries failed. Selecting next IP-SNS endpoint.");
+ } else {
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_BSS_SIZE, nsi->timeout[NS_TOUT_TSNS_PROV], 1);
+ }
break;
case 2:
- osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_CONFIG_BSS, nsi->timeout[NS_TOUT_TSNS_PROV], 2);
+ if (gss->N >= nsi->timeout[NS_TOUT_TSNS_CONFIG_RETRIES]) {
+ sns_failed(fi, "BSS Config retries failed. Selecting next IP-SNS endpoint");
+ } else {
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_BSS_CONFIG_BSS, nsi->timeout[NS_TOUT_TSNS_PROV], 2);
+ }
+ break;
+ case 3:
+ if (gss->N >= nsi->timeout[NS_TOUT_TSNS_CONFIG_RETRIES]) {
+ sns_failed(fi, "SGSN Config retries failed. Selecting next IP-SNS endpoint.");
+ } else {
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_BSS_CONFIG_SGSN, nsi->timeout[NS_TOUT_TSNS_PROV], 3);
+ }
+ break;
+ case 4:
+ sns_failed(fi, "Config succeeded but no NS-VC came online. Selecting next IP-SNS endpoint.");
+ break;
+ case 5:
+ if (gss->N >= nsi->timeout[NS_TOUT_TSNS_CONFIG_RETRIES]) {
+ sns_failed(fi, "SNS Procedure retries failed.");
+ } else {
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_LOCAL_PROCEDURE, nsi->timeout[NS_TOUT_TSNS_PROV], 5);
+ }
break;
}
return 0;
}
-static void ns2_sns_st_all_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+static struct gprs_ns_ie_ip4_elem *ns2_get_sbind_ip4_entry(struct ns2_sns_state *gss,
+ struct ns2_sns_bind *sbind,
+ struct ns2_sns_elems *endpoints)
{
- struct gprs_ns2_nse *nse = nse_inst_from_fi(fi);
+ const struct osmo_sockaddr *addr;
+ struct gprs_ns_ie_ip4_elem *ip4;
- /* reset when receiving GPRS_SNS_EV_NO_NSVC */
- osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_SIZE, nse->nsi->timeout[NS_TOUT_TSNS_PROV], 3);
-}
+ if (gss->family != AF_INET)
+ return NULL;
-static struct osmo_fsm gprs_ns2_sns_bss_fsm = {
- .name = "GPRS-NS2-SNS-BSS",
- .states = ns2_sns_bss_states,
- .num_states = ARRAY_SIZE(ns2_sns_bss_states),
- .allstate_event_mask = GPRS_SNS_EV_NO_NSVC,
- .allstate_action = ns2_sns_st_all_action,
- .cleanup = NULL,
- .timer_cb = ns2_sns_fsm_bss_timer_cb,
- /* .log_subsys = DNS, "is not constant" */
- .event_names = gprs_sns_event_names,
- .pre_term = NULL,
- .log_subsys = DLNS,
-};
+ addr = gprs_ns2_ip_bind_sockaddr(sbind->bind);
+ if (addr->u.sa.sa_family != AF_INET)
+ return NULL;
-/*! Allocate an IP-SNS FSM for the BSS side.
- * \param[in] nse NS Entity in which the FSM runs
- * \param[in] id string identifier
- * \retruns FSM instance on success; NULL on error */
-struct osmo_fsm_inst *ns2_sns_bss_fsm_alloc(struct gprs_ns2_nse *nse,
- const char *id)
+ for (unsigned int i=0; i<endpoints->num_ip4; i++) {
+ ip4 = &endpoints->ip4[i];
+ if (ip4->ip_addr == addr->u.sin.sin_addr.s_addr &&
+ ip4->udp_port == addr->u.sin.sin_port)
+ return ip4;
+ }
+
+ return NULL;
+}
+
+static struct gprs_ns_ie_ip6_elem *ns2_get_sbind_ip6_entry(struct ns2_sns_state *gss,
+ struct ns2_sns_bind *sbind,
+ struct ns2_sns_elems *endpoints)
{
- struct osmo_fsm_inst *fi;
- struct ns2_sns_state *gss;
+ const struct osmo_sockaddr *addr;
+ struct gprs_ns_ie_ip6_elem *ip6;
- fi = osmo_fsm_inst_alloc(&gprs_ns2_sns_bss_fsm, nse, NULL, LOGL_DEBUG, id);
- if (!fi)
- return fi;
+ if (gss->family != AF_INET6)
+ return NULL;
- gss = talloc_zero(fi, struct ns2_sns_state);
- if (!gss)
- goto err;
+ addr = gprs_ns2_ip_bind_sockaddr(sbind->bind);
+ if (addr->u.sa.sa_family != AF_INET6)
+ return NULL;
- fi->priv = gss;
- gss->nse = nse;
+ for (unsigned int i=0; i<endpoints->num_ip6; i++) {
+ ip6 = &endpoints->ip6[i];
+ if (memcmp(&ip6->ip_addr, &addr->u.sin6.sin6_addr, sizeof(ip6->ip_addr)) ||
+ ip6->udp_port != addr->u.sin6.sin6_port)
+ return ip6;
+ }
- return fi;
-err:
- osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL);
return NULL;
}
-/*! Start an IP-SNS FSM.
- * \param[in] nse NS Entity whose IP-SNS FSM shall be started
- * \param[in] nsvc Initial NS-VC
- * \param[in] remote remote (SGSN) address
- * \returns 0 on success; negative on error */
-int ns2_sns_bss_fsm_start(struct gprs_ns2_nse *nse, struct gprs_ns2_vc *nsvc, struct osmo_sockaddr *remote)
+/* return != 0 if the resulting weight is invalid. return 1 if sbind doesn't have an entry */
+static int ns2_update_weight_entry(struct ns2_sns_state *gss, struct ns2_sns_bind *sbind,
+ struct ns2_sns_elems *endpoints)
{
- struct osmo_fsm_inst *fi = nse->bss_sns_fi;
- struct ns2_sns_state *gss = (struct ns2_sns_state *) nse->bss_sns_fi->priv;
- struct gprs_ns_ie_ip4_elem *ip4_elems;
- struct gprs_ns_ie_ip6_elem *ip6_elems;
- struct gprs_ns2_vc_bind *bind;
- struct gprs_ns2_inst *nsi = nse->nsi;
- struct osmo_sockaddr *sa, local;
- gss->ip = remote->u.sa.sa_family == AF_INET ? IPv4 : IPv6;
+ struct gprs_ns_ie_ip4_elem *ip4;
+ struct gprs_ns_ie_ip6_elem *ip6;
+
+ switch (gss->family) {
+ case AF_INET:
+ ip4 = ns2_get_sbind_ip4_entry(gss, sbind, endpoints);
+ if (!ip4)
+ return 1;
+ ip4->sig_weight = sbind->bind->sns_sig_weight;
+ ip4->data_weight = sbind->bind->sns_data_weight;
+ return (ip4_weight_sum_sig(endpoints) != 0 && ip4_weight_sum_data(endpoints) != 0);
+ break;
+ case AF_INET6:
+ ip6 = ns2_get_sbind_ip6_entry(gss, sbind, endpoints);
+ if (!ip6)
+ return 1;
+ ip6->sig_weight = sbind->bind->sns_sig_weight;
+ ip6->data_weight = sbind->bind->sns_data_weight;
+ return (ip6_weight_sum_sig(endpoints) != 0 && ip6_weight_sum_data(endpoints) != 0);
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+static void ns2_add_procedure(struct ns2_sns_state *gss, struct ns2_sns_bind *sbind,
+ enum sns_procedure procedure_type)
+{
+ struct ns2_sns_procedure *procedure = NULL;
+ const struct osmo_sockaddr *saddr;
+ saddr = gprs_ns2_ip_bind_sockaddr(sbind->bind);
- gss->initial = *remote;
- gss->sns_nsvc = nsvc;
- nsvc->sns_only = true;
+ OSMO_ASSERT(saddr->u.sa.sa_family == gss->family);
- int count = 0;
- llist_for_each_entry(bind, &nsi->binding, list) {
- if (!gprs_ns2_is_ip_bind(bind))
- continue;
+ switch (procedure_type) {
+ case SNS_PROC_ADD:
+ break;
+ case SNS_PROC_DEL:
+ break;
+ case SNS_PROC_CHANGE_WEIGHT:
+ llist_for_each_entry(procedure, &gss->procedures, list) {
+ if (procedure->sbind == sbind && procedure->procedure == procedure_type &&
+ !procedure->running) {
+ switch(gss->family) {
+ case AF_INET:
+ /* merge it with a previous procedure */
+ procedure->ip4.ip_addr = sbind->bind->sns_sig_weight;
+ procedure->ip4.data_weight = sbind->bind->sns_data_weight;
+ break;
+ case AF_INET6:
+ /* merge it with a previous procedure */
+ procedure->ip6.sig_weight = sbind->bind->sns_sig_weight;
+ procedure->ip6.data_weight = sbind->bind->sns_data_weight;
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+ return;
+ }
+ }
+ break;
+ default:
+ return;
+ }
- sa = gprs_ns2_ip_bind_sockaddr(bind);
- if (!sa)
- continue;
+ procedure = talloc_zero(gss, struct ns2_sns_procedure);
+ if (!procedure)
+ return;
- if (sa->u.sa.sa_family == remote->u.sa.sa_family)
- count++;
+ switch (procedure_type) {
+ case SNS_PROC_ADD:
+ case SNS_PROC_CHANGE_WEIGHT:
+ procedure->sbind = sbind;
+ break;
+ default:
+ break;
}
- if (count == 0) {
- /* TODO: logging */
- goto err;
+ llist_add_tail(&procedure->list, &gss->procedures);
+ procedure->procedure = procedure_type;
+ procedure->sig_weight = sbind->bind->sns_sig_weight;
+ procedure->data_weight = sbind->bind->sns_data_weight;
+
+ switch(gss->family) {
+ case AF_INET:
+ procedure->ip4.ip_addr = saddr->u.sin.sin_addr.s_addr;
+ procedure->ip4.udp_port = saddr->u.sin.sin_port;
+ procedure->ip4.sig_weight = sbind->bind->sns_sig_weight;
+ procedure->ip4.data_weight = sbind->bind->sns_data_weight;
+ break;
+ case AF_INET6:
+ memcpy(&procedure->ip6.ip_addr, &saddr->u.sin6.sin6_addr, sizeof(struct in6_addr));
+ procedure->ip6.udp_port = saddr->u.sin.sin_port;
+ procedure->ip6.sig_weight = sbind->bind->sns_sig_weight;
+ procedure->ip6.data_weight = sbind->bind->sns_data_weight;
+ break;
+ default:
+ OSMO_ASSERT(0);
}
- switch (gss->ip) {
- case IPv4:
- ip4_elems = talloc_zero_size(fi, sizeof(struct gprs_ns_ie_ip4_elem) * count);
- if (!ip4_elems)
- goto err;
-
- gss->ip4_local = ip4_elems;
-
- llist_for_each_entry(bind, &nsi->binding, list) {
- if (!gprs_ns2_is_ip_bind(bind))
- continue;
+ if (gss->nse->bss_sns_fi->state == GPRS_SNS_ST_CONFIGURED) {
+ osmo_fsm_inst_state_chg(gss->nse->bss_sns_fi, GPRS_SNS_ST_LOCAL_PROCEDURE,
+ gss->nse->nsi->timeout[NS_TOUT_TSNS_PROV], 5);
+ }
+}
- sa = gprs_ns2_ip_bind_sockaddr(bind);
- if (!sa)
- continue;
+/* add an entrypoint to sns_endpoints */
+static int ns2_sns_add_elements(struct ns2_sns_state *gss, struct ns2_sns_bind *sbind,
+ struct ns2_sns_elems *elems)
+{
+ const struct osmo_sockaddr *saddr;
+ struct gprs_ns_ie_ip4_elem ip4;
+ struct gprs_ns_ie_ip6_elem ip6;
+ int rc = -1;
+
+ saddr = gprs_ns2_ip_bind_sockaddr(sbind->bind);
+ OSMO_ASSERT(saddr->u.sa.sa_family == gss->family);
+
+ switch (gss->family) {
+ case AF_INET:
+ ip4.ip_addr = saddr->u.sin.sin_addr.s_addr;
+ ip4.udp_port= saddr->u.sin.sin_port;
+ ip4.sig_weight = sbind->bind->sns_sig_weight;
+ ip4.data_weight = sbind->bind->sns_data_weight;
+ rc = add_ip4_elem(gss, elems, &ip4);
+ break;
+ case AF_INET6:
+ memcpy(&ip6.ip_addr, &saddr->u.sin6.sin6_addr, sizeof(struct in6_addr));
+ ip6.udp_port= saddr->u.sin.sin_port;
+ ip6.sig_weight = sbind->bind->sns_sig_weight;
+ ip6.data_weight = sbind->bind->sns_data_weight;
+ rc = add_ip6_elem(gss, elems, &ip6);
+ break;
+ }
- if (sa->u.sas.ss_family != AF_INET)
- continue;
+ return rc;
+}
- /* check if this is an specific bind */
- if (sa->u.sin.sin_addr.s_addr == 0) {
- if (osmo_sockaddr_local_ip(&local, remote))
- continue;
+/* common allstate-action for both roles */
+static void ns2_sns_st_all_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct gprs_ns2_nse *nse = nse_inst_from_fi(fi);
+ struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
+ struct ns2_sns_bind *sbind;
+ struct gprs_ns2_vc *nsvc, *nsvc2;
+ struct ns2_sns_procedure *procedure;
- ip4_elems->ip_addr = local.u.sin.sin_addr.s_addr;
- } else {
- ip4_elems->ip_addr = sa->u.sin.sin_addr.s_addr;
+ switch (event) {
+ case NS2_SNS_EV_REQ_ADD_BIND:
+ sbind = data;
+ switch (fi->state) {
+ case GPRS_SNS_ST_UNCONFIGURED:
+ if (gss->role == GPRS_SNS_ROLE_BSS)
+ osmo_fsm_inst_dispatch(nse->bss_sns_fi, NS2_SNS_EV_REQ_SELECT_ENDPOINT, NULL);
+ break;
+ case GPRS_SNS_ST_BSS_SIZE:
+ switch (gss->family) {
+ case AF_INET:
+ if (gss->num_max_ip4_remote <= gss->local.num_ip4 ||
+ gss->num_max_ip4_remote * (gss->local.num_ip4 + 1) > gss->num_max_nsvcs) {
+ osmo_fsm_inst_dispatch(nse->bss_sns_fi, NS2_SNS_EV_REQ_SELECT_ENDPOINT, GPRS_SNS_FLAG_KEEP_SELECT_ENDPOINT_ORDER);
+ return;
+ }
+ break;
+ case AF_INET6:
+ if (gss->num_max_ip6_remote <= gss->local.num_ip6 ||
+ gss->num_max_ip6_remote * (gss->local.num_ip6 + 1) > gss->num_max_nsvcs) {
+ osmo_fsm_inst_dispatch(nse->bss_sns_fi, NS2_SNS_EV_REQ_SELECT_ENDPOINT, GPRS_SNS_FLAG_KEEP_SELECT_ENDPOINT_ORDER);
+ return;
+ }
+ break;
+ }
+ ns2_sns_add_elements(gss, sbind, &gss->local);
+ break;
+ case GPRS_SNS_ST_BSS_CONFIG_BSS:
+ case GPRS_SNS_ST_BSS_CONFIG_SGSN:
+ case GPRS_SNS_ST_CONFIGURED:
+ switch (gss->family) {
+ case AF_INET:
+ if (gss->num_max_ip4_remote <= gss->local.num_ip4) {
+ LOGPFSML(fi, LOGL_ERROR,
+ "NSE %d: ignoring bind %s because there are too many endpoints for the SNS.\n",
+ nse->nsei, sbind->bind->name);
+ return;
+ }
+ if (gss->remote.num_ip4 * (gss->local.num_ip4 + 1) > gss->num_max_nsvcs) {
+ LOGPFSML(fi, LOGL_ERROR,
+ "NSE %d: ignoring bind %s because there are too many endpoints for the SNS.\n",
+ nse->nsei, sbind->bind->name);
+ return;
+ }
+ break;
+ case AF_INET6:
+ if (gss->num_max_ip6_remote <= gss->local.num_ip6) {
+ LOGPFSML(fi, LOGL_ERROR,
+ "NSE %d: ignoring bind %s because there are too many endpoints for the SNS.\n",
+ nse->nsei, sbind->bind->name);
+ return;
+ }
+ if (gss->remote.num_ip6 * (gss->local.num_ip6 + 1) > gss->num_max_nsvcs) {
+ LOGPFSML(fi, LOGL_ERROR,
+ "NSE %d: ignoring bind %s because there are too many endpoints for the SNS.\n",
+ nse->nsei, sbind->bind->name);
+ return;
+ }
+ break;
+ }
+ ns2_sns_add_elements(gss, sbind, &gss->local_procedure);
+ ns2_add_procedure(gss, sbind, SNS_PROC_ADD);
+ break;
+ }
+ break;
+ case NS2_SNS_EV_REQ_DELETE_BIND:
+ sbind = data;
+ switch (fi->state) {
+ case GPRS_SNS_ST_UNCONFIGURED:
+ break;
+ case GPRS_SNS_ST_BSS_SIZE:
+ llist_for_each_entry_safe(nsvc, nsvc2, &nse->nsvc, list) {
+ if (nsvc->bind == sbind->bind) {
+ gprs_ns2_free_nsvc(nsvc);
+ }
+ }
+ osmo_fsm_inst_dispatch(fi, NS2_SNS_EV_REQ_SELECT_ENDPOINT, NULL);
+ break;
+ case GPRS_SNS_ST_BSS_CONFIG_BSS:
+ case GPRS_SNS_ST_BSS_CONFIG_SGSN:
+ case GPRS_SNS_ST_CONFIGURED:
+ case GPRS_SNS_ST_LOCAL_PROCEDURE:
+ remove_bind_elem(gss, &gss->local_procedure, sbind);
+ if (ip46_weight_sum(&gss->local_procedure, true) == 0 ||
+ ip46_weight_sum(&gss->local_procedure, false) == 0) {
+ LOGPFSML(fi, LOGL_ERROR, "NSE %d: weight has become invalid because of removing bind %s. Resetting the configuration\n",
+ nse->nsei, sbind->bind->name);
+ sns_failed(fi, NULL);
+ break;
+ }
+ gss->block_no_nsvc_events = true;
+ llist_for_each_entry_safe(nsvc, nsvc2, &nse->nsvc, list) {
+ if (nsvc->bind == sbind->bind) {
+ gprs_ns2_free_nsvc(nsvc);
+ }
+ }
+ gss->block_no_nsvc_events = false;
+ if (nse->sum_sig_weight == 0 || !nse->alive || !gss->alive) {
+ sns_failed(fi, "While deleting a bind the current state became invalid (no signalling weight)");
+ break;
}
- ip4_elems->udp_port = sa->u.sin.sin_port;
- ip4_elems->sig_weight = 2;
- ip4_elems->data_weight = 1;
- ip4_elems++;
+ /* ensure other procedures doesn't use the sbind */
+ llist_for_each_entry(procedure, &gss->procedures, list) {
+ if (procedure->sbind == sbind)
+ procedure->sbind = NULL;
+ }
+ ns2_add_procedure(gss, sbind, SNS_PROC_DEL);
+ break;
}
- gss->num_ip4_local = count;
- gss->num_max_ip4_remote = 4;
+ /* if this is the last bind, the free_nsvc() will trigger a reselection */
+ talloc_free(sbind);
break;
- case IPv6:
- /* IPv6 */
- ip6_elems = talloc_zero_size(fi, sizeof(struct gprs_ns_ie_ip6_elem) * count);
- if (!ip6_elems)
- goto err;
+ case NS2_SNS_EV_REQ_CHANGE_WEIGHT:
+ sbind = data;
+ switch (fi->state) {
+ case GPRS_SNS_ST_UNCONFIGURED:
+ /* select_endpoint will check if this is a valid configuration */
+ if (gss->role == GPRS_SNS_ROLE_BSS)
+ osmo_fsm_inst_dispatch(fi, NS2_SNS_EV_REQ_SELECT_ENDPOINT, NULL);
+ break;
+ case GPRS_SNS_ST_BSS_SIZE:
+ /* invalid weight? */
+ if (!ns2_update_weight_entry(gss, sbind, &gss->local))
+ sns_failed(fi, "updating weights results in an invalid configuration.");
+ break;
+ default:
+ if (!ns2_update_weight_entry(gss, sbind, &gss->local_procedure)) {
+ sns_failed(fi, "updating weights results in an invalid configuration.");
+ break;
+ }
+ ns2_add_procedure(gss, sbind, SNS_PROC_CHANGE_WEIGHT);
+ break;
+ }
+ }
+}
- gss->ip6_local = ip6_elems;
+/* validate the bss configuration (sns endpoint and binds)
+ * - no endpoints -> invalid
+ * - no binds -> invalid
+ * - only v4 sns endpoints, only v6 binds -> invalid
+ * - only v4 sns endpoints, but v4 sig weights == 0 -> invalid ...
+ */
+static int ns2_sns_bss_valid_configuration(struct ns2_sns_state *gss)
+{
+ struct ns2_sns_bind *sbind;
+ struct sns_endpoint *endpoint;
+ const struct osmo_sockaddr *addr;
+ int v4_sig = 0, v4_data = 0, v6_sig = 0, v6_data = 0;
+ bool v4_endpoints = false;
+ bool v6_endpoints = false;
+
+ if (llist_empty(&gss->sns_endpoints) || llist_empty(&gss->binds))
+ return 0;
- llist_for_each_entry(bind, &nsi->binding, list) {
- if (!gprs_ns2_is_ip_bind(bind))
- continue;
+ llist_for_each_entry(sbind, &gss->binds, list) {
+ addr = gprs_ns2_ip_bind_sockaddr(sbind->bind);
+ if (!addr)
+ continue;
+ switch (addr->u.sa.sa_family) {
+ case AF_INET:
+ v4_sig += sbind->bind->sns_sig_weight;
+ v4_data += sbind->bind->sns_data_weight;
+ break;
+ case AF_INET6:
+ v6_sig += sbind->bind->sns_sig_weight;
+ v6_data += sbind->bind->sns_data_weight;
+ break;
+ }
+ }
- sa = gprs_ns2_ip_bind_sockaddr(bind);
- if (!sa)
- continue;
+ llist_for_each_entry(endpoint, &gss->sns_endpoints, list) {
+ switch (endpoint->saddr.u.sa.sa_family) {
+ case AF_INET:
+ v4_endpoints = true;
+ break;
+ case AF_INET6:
+ v6_endpoints = true;
+ break;
+ }
+ }
- if (sa->u.sas.ss_family != AF_INET6)
- continue;
+ return (v4_endpoints && v4_sig && v4_data) || (v6_endpoints && v6_sig && v6_data);
+}
- /* check if this is an specific bind */
- if (IN6_IS_ADDR_UNSPECIFIED(&sa->u.sin6.sin6_addr)) {
- if (osmo_sockaddr_local_ip(&local, remote))
- continue;
+/* allstate-action for BSS role */
+static void ns2_sns_st_all_action_bss(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
+ struct gprs_ns2_nse *nse = nse_inst_from_fi(fi);
- ip6_elems->ip_addr = local.u.sin6.sin6_addr;
- } else {
- ip6_elems->ip_addr = sa->u.sin6.sin6_addr;
- }
+ /* reset when receiving NS2_SNS_EV_REQ_NO_NSVC */
+ switch (event) {
+ case NS2_SNS_EV_REQ_NO_NSVC:
+ /* ignore reselection running */
+ if (gss->reselection_running || gss->block_no_nsvc_events)
+ break;
- ip6_elems->udp_port = sa->u.sin.sin_port;
- ip6_elems->sig_weight = 2;
- ip6_elems->data_weight = 1;
+ sns_failed(fi, "no remaining NSVC, resetting SNS FSM");
+ break;
+ case NS2_SNS_EV_REQ_FREE_NSVCS:
+ case NS2_SNS_EV_REQ_SELECT_ENDPOINT:
+ /* TODO: keep the order of binds when data == GPRS_SNS_FLAG_KEEP_SELECT_ENDPOINT_ORDER */
+ /* tear down previous state
+ * gprs_ns2_free_nsvcs() will trigger NO_NSVC, prevent this from triggering a reselection */
+ if (gss->reselection_running || gss->block_no_nsvc_events)
+ break;
- ip6_elems++;
+ gss->reselection_running = true;
+ ns2_free_nsvcs(nse);
+ ns2_clear_elems(&gss->local);
+ ns2_clear_elems(&gss->remote);
+
+ /* Choose the next sns endpoint. */
+ if (!ns2_sns_bss_valid_configuration(gss)) {
+ gss->initial = NULL;
+ ns2_prim_status_ind(gss->nse, NULL, 0, GPRS_NS2_AFF_CAUSE_SNS_NO_ENDPOINTS);
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_UNCONFIGURED, 0, 3);
+ gss->reselection_running = false;
+ return;
+ } else if (!gss->initial) {
+ gss->initial = llist_first_entry(&gss->sns_endpoints, struct sns_endpoint, list);
+ } else if (gss->initial->list.next == &gss->sns_endpoints) {
+ /* last entry, continue with first */
+ gss->initial = llist_first_entry(&gss->sns_endpoints, struct sns_endpoint, list);
+ } else {
+ /* next element is an entry */
+ gss->initial = llist_entry(gss->initial->list.next, struct sns_endpoint, list);
}
- gss->num_ip6_local = count;
- gss->num_max_ip6_remote = 4;
+
+ gss->family = gss->initial->saddr.u.sa.sa_family;
+ gss->reselection_running = false;
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_BSS_SIZE, nse->nsi->timeout[NS_TOUT_TSNS_PROV], 1);
+ break;
+ default:
+ ns2_sns_st_all_action(fi, event, data);
break;
}
+}
- gss->num_max_nsvcs = 8;
+static struct osmo_fsm gprs_ns2_sns_bss_fsm = {
+ .name = "GPRS-NS2-SNS-BSS",
+ .states = ns2_sns_bss_states,
+ .num_states = ARRAY_SIZE(ns2_sns_bss_states),
+ .allstate_event_mask = S(NS2_SNS_EV_REQ_NO_NSVC) |
+ S(NS2_SNS_EV_REQ_FREE_NSVCS) |
+ S(NS2_SNS_EV_REQ_SELECT_ENDPOINT) |
+ S(NS2_SNS_EV_REQ_ADD_BIND) |
+ S(NS2_SNS_EV_REQ_CHANGE_WEIGHT) |
+ S(NS2_SNS_EV_REQ_DELETE_BIND),
+ .allstate_action = ns2_sns_st_all_action_bss,
+ .cleanup = NULL,
+ .timer_cb = ns2_sns_fsm_bss_timer_cb,
+ .event_names = gprs_sns_event_names,
+ .pre_term = NULL,
+ .log_subsys = DLNS,
+};
- return osmo_fsm_inst_dispatch(nse->bss_sns_fi, GPRS_SNS_EV_START, NULL);
+/*! Allocate an IP-SNS FSM for the BSS side.
+ * \param[in] nse NS Entity in which the FSM runs
+ * \param[in] id string identifier
+ * \returns FSM instance on success; NULL on error */
+struct osmo_fsm_inst *ns2_sns_bss_fsm_alloc(struct gprs_ns2_nse *nse,
+ const char *id)
+{
+ struct osmo_fsm_inst *fi;
+ struct ns2_sns_state *gss;
+ fi = osmo_fsm_inst_alloc(&gprs_ns2_sns_bss_fsm, nse, NULL, LOGL_DEBUG, id);
+ if (!fi)
+ return fi;
+
+ gss = talloc_zero(fi, struct ns2_sns_state);
+ if (!gss)
+ goto err;
+
+ fi->priv = gss;
+ gss->nse = nse;
+ gss->role = GPRS_SNS_ROLE_BSS;
+ /* The SGSN doesn't tell the BSS, so we assume there's always sufficient */
+ gss->num_max_ip4_remote = 8192;
+ gss->num_max_ip6_remote = 8192;
+ INIT_LLIST_HEAD(&gss->sns_endpoints);
+ INIT_LLIST_HEAD(&gss->binds);
+ INIT_LLIST_HEAD(&gss->procedures);
+
+ return fi;
err:
- return -1;
+ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL);
+ return NULL;
}
/*! main entry point for receiving SNS messages from the network.
* \param[in] nsvc NS-VC on which the message was received
* \param[in] msg message buffer of the IP-SNS message
* \param[in] tp parsed TLV structure of message
- * \retruns 0 on success; negative on error */
-int gprs_ns2_sns_rx(struct gprs_ns2_vc *nsvc, struct msgb *msg, struct tlv_parsed *tp)
+ * \returns 0 on success; negative on error */
+int ns2_sns_rx(struct gprs_ns2_vc *nsvc, struct msgb *msg, struct tlv_parsed *tp)
{
struct gprs_ns2_nse *nse = nsvc->nse;
struct gprs_ns_hdr *nsh = (struct gprs_ns_hdr *) msg->l2h;
uint16_t nsei = nsvc->nse->nsei;
+ struct ns2_sns_state *gss;
struct osmo_fsm_inst *fi;
+ int rc = 0;
if (!nse->bss_sns_fi) {
- LOGP(DLNS, LOGL_NOTICE, "NSEI=%u Rx %s for NS Instance that has no SNS!\n",
- nsvc->nse->nsei, get_value_string(gprs_ns_pdu_strings, nsh->pdu_type));
- return -EINVAL;
+ LOGNSVC(nsvc, LOGL_NOTICE, "Rx %s for NS Instance that has no SNS!\n",
+ get_value_string(gprs_ns_pdu_strings, nsh->pdu_type));
+ rc = -EINVAL;
+ goto out;
}
- LOGP(DLNS, LOGL_DEBUG, "NSEI=%u Rx SNS PDU type %s\n", nsei,
- get_value_string(gprs_ns_pdu_strings, nsh->pdu_type));
-
/* FIXME: how to resolve SNS FSM Instance by NSEI (SGSN)? */
fi = nse->bss_sns_fi;
+ gss = (struct ns2_sns_state *) fi->priv;
+ gss->sns_nsvc = nsvc;
+
+ LOGPFSML(fi, LOGL_DEBUG, "NSEI=%u Rx SNS PDU type %s\n", nsei,
+ get_value_string(gprs_ns_pdu_strings, nsh->pdu_type));
switch (nsh->pdu_type) {
case SNS_PDUT_SIZE:
- osmo_fsm_inst_dispatch(fi, GPRS_SNS_EV_SIZE, tp);
+ osmo_fsm_inst_dispatch(fi, NS2_SNS_EV_RX_SIZE, tp);
break;
case SNS_PDUT_SIZE_ACK:
- osmo_fsm_inst_dispatch(fi, GPRS_SNS_EV_SIZE_ACK, tp);
+ osmo_fsm_inst_dispatch(fi, NS2_SNS_EV_RX_SIZE_ACK, tp);
break;
case SNS_PDUT_CONFIG:
if (nsh->data[0] & 0x01)
- osmo_fsm_inst_dispatch(fi, GPRS_SNS_EV_CONFIG_END, tp);
+ osmo_fsm_inst_dispatch(fi, NS2_SNS_EV_RX_CONFIG_END, tp);
else
- osmo_fsm_inst_dispatch(fi, GPRS_SNS_EV_CONFIG, tp);
+ osmo_fsm_inst_dispatch(fi, NS2_SNS_EV_RX_CONFIG, tp);
break;
case SNS_PDUT_CONFIG_ACK:
- osmo_fsm_inst_dispatch(fi, GPRS_SNS_EV_CONFIG_ACK, tp);
+ osmo_fsm_inst_dispatch(fi, NS2_SNS_EV_RX_CONFIG_ACK, tp);
break;
case SNS_PDUT_ADD:
- osmo_fsm_inst_dispatch(fi, GPRS_SNS_EV_ADD, tp);
+ osmo_fsm_inst_dispatch(fi, NS2_SNS_EV_RX_ADD, tp);
break;
case SNS_PDUT_DELETE:
- osmo_fsm_inst_dispatch(fi, GPRS_SNS_EV_DELETE, tp);
+ osmo_fsm_inst_dispatch(fi, NS2_SNS_EV_RX_DELETE, tp);
break;
case SNS_PDUT_CHANGE_WEIGHT:
- osmo_fsm_inst_dispatch(fi, GPRS_SNS_EV_CHANGE_WEIGHT, tp);
+ osmo_fsm_inst_dispatch(fi, NS2_SNS_EV_RX_CHANGE_WEIGHT, tp);
break;
case SNS_PDUT_ACK:
- LOGP(DLNS, LOGL_NOTICE, "NSEI=%u Rx unsupported SNS PDU type %s\n", nsei,
- get_value_string(gprs_ns_pdu_strings, nsh->pdu_type));
+ osmo_fsm_inst_dispatch(fi, NS2_SNS_EV_RX_ACK, tp);
break;
default:
- LOGP(DLNS, LOGL_ERROR, "NSEI=%u Rx unknown SNS PDU type %s\n", nsei,
- get_value_string(gprs_ns_pdu_strings, nsh->pdu_type));
- return -EINVAL;
+ LOGPFSML(fi, LOGL_ERROR, "NSEI=%u Rx unknown SNS PDU type %s\n", nsei,
+ get_value_string(gprs_ns_pdu_strings, nsh->pdu_type));
+ rc = -EINVAL;
}
- return 0;
+out:
+ msgb_free(msg);
+
+ return rc;
}
#include <osmocom/vty/vty.h>
#include <osmocom/vty/misc.h>
-static void vty_dump_sns_ip4(struct vty *vty, const struct gprs_ns_ie_ip4_elem *ip4)
+static void vty_dump_sns_ip4(struct vty *vty, const char *prefix, const struct gprs_ns_ie_ip4_elem *ip4)
{
struct in_addr in = { .s_addr = ip4->ip_addr };
- vty_out(vty, " %s:%u, Signalling Weight: %u, Data Weight: %u%s",
+ vty_out(vty, "%s %s:%u, Signalling Weight: %u, Data Weight: %u%s", prefix,
inet_ntoa(in), ntohs(ip4->udp_port), ip4->sig_weight, ip4->data_weight, VTY_NEWLINE);
}
-static void vty_dump_sns_ip6(struct vty *vty, const struct gprs_ns_ie_ip6_elem *ip6)
+static void vty_dump_sns_ip6(struct vty *vty, const char *prefix, const struct gprs_ns_ie_ip6_elem *ip6)
{
char ip_addr[INET6_ADDRSTRLEN] = {};
if (!inet_ntop(AF_INET6, &ip6->ip_addr, ip_addr, (INET6_ADDRSTRLEN)))
strcpy(ip_addr, "Invalid IPv6");
- vty_out(vty, " %s:%u, Signalling Weight: %u, Data Weight: %u%s",
+ vty_out(vty, "%s %s:%u, Signalling Weight: %u, Data Weight: %u%s", prefix,
ip_addr, ntohs(ip6->udp_port), ip6->sig_weight, ip6->data_weight, VTY_NEWLINE);
}
/*! Dump the IP-SNS state to a vty.
* \param[in] vty VTY to which the state shall be printed
+ * \param[in] prefix prefix to print at start of each line (typically indenting)
* \param[in] nse NS Entity whose IP-SNS state shall be printed
* \param[in] stats Whether or not statistics shall also be printed */
-void gprs_ns2_sns_dump_vty(struct vty *vty, const struct gprs_ns2_nse *nse, bool stats)
+void ns2_sns_dump_vty(struct vty *vty, const char *prefix, const struct gprs_ns2_nse *nse, bool stats)
{
struct ns2_sns_state *gss;
unsigned int i;
@@ -1443,35 +2404,703 @@ void gprs_ns2_sns_dump_vty(struct vty *vty, const struct gprs_ns2_nse *nse, bool
if (!nse->bss_sns_fi)
return;
- vty_out_fsm_inst(vty, nse->bss_sns_fi);
+ vty_out_fsm_inst2(vty, prefix, nse->bss_sns_fi);
+ gss = (struct ns2_sns_state *) nse->bss_sns_fi->priv;
+
+ vty_out(vty, "%sMaximum number of remote NS-VCs: %zu, IPv4 Endpoints: %zu, IPv6 Endpoints: %zu%s",
+ prefix, gss->num_max_nsvcs, gss->num_max_ip4_remote, gss->num_max_ip6_remote, VTY_NEWLINE);
+
+ if (gss->local.num_ip4 && gss->remote.num_ip4) {
+ vty_out(vty, "%sLocal IPv4 Endpoints:%s", prefix, VTY_NEWLINE);
+ for (i = 0; i < gss->local.num_ip4; i++)
+ vty_dump_sns_ip4(vty, prefix, &gss->local.ip4[i]);
+
+ vty_out(vty, "%sRemote IPv4 Endpoints:%s", prefix, VTY_NEWLINE);
+ for (i = 0; i < gss->remote.num_ip4; i++)
+ vty_dump_sns_ip4(vty, prefix, &gss->remote.ip4[i]);
+ }
+
+ if (gss->local.num_ip6 && gss->remote.num_ip6) {
+ vty_out(vty, "%sLocal IPv6 Endpoints:%s", prefix, VTY_NEWLINE);
+ for (i = 0; i < gss->local.num_ip6; i++)
+ vty_dump_sns_ip6(vty, prefix, &gss->local.ip6[i]);
+
+ vty_out(vty, "%sRemote IPv6 Endpoints:%s", prefix, VTY_NEWLINE);
+ for (i = 0; i < gss->remote.num_ip6; i++)
+ vty_dump_sns_ip6(vty, prefix, &gss->remote.ip6[i]);
+ }
+}
+
+/*! write IP-SNS to a vty
+ * \param[in] vty VTY to which the state shall be printed
+ * \param[in] nse NS Entity whose IP-SNS state shall be printed */
+void ns2_sns_write_vty(struct vty *vty, const struct gprs_ns2_nse *nse)
+{
+ struct ns2_sns_state *gss;
+ struct osmo_sockaddr_str addr_str;
+ struct sns_endpoint *endpoint;
+
+ if (!nse->bss_sns_fi)
+ return;
+
gss = (struct ns2_sns_state *) nse->bss_sns_fi->priv;
+ llist_for_each_entry(endpoint, &gss->sns_endpoints, list) {
+ /* It's unlikely that an error happens, but let's better be safe. */
+ if (osmo_sockaddr_str_from_sockaddr(&addr_str, &endpoint->saddr.u.sas) != 0)
+ addr_str = (struct osmo_sockaddr_str) { .ip = "<INVALID>" };
+ vty_out(vty, " ip-sns-remote %s %u%s", addr_str.ip, addr_str.port, VTY_NEWLINE);
+ }
+}
- vty_out(vty, "Maximum number of remote NS-VCs: %zu, IPv4 Endpoints: %zu, IPv6 Endpoints: %zu%s",
- gss->num_max_nsvcs, gss->num_max_ip4_remote, gss->num_max_ip6_remote, VTY_NEWLINE);
+static struct sns_endpoint *ns2_get_sns_endpoint(struct ns2_sns_state *state,
+ const struct osmo_sockaddr *saddr)
+{
+ struct sns_endpoint *endpoint;
- if (gss->num_ip4_local && gss->num_ip4_remote) {
- vty_out(vty, "Local IPv4 Endpoints:%s", VTY_NEWLINE);
- for (i = 0; i < gss->num_ip4_local; i++)
- vty_dump_sns_ip4(vty, &gss->ip4_local[i]);
+ llist_for_each_entry(endpoint, &state->sns_endpoints, list) {
+ if (!osmo_sockaddr_cmp(saddr, &endpoint->saddr))
+ return endpoint;
+ }
+
+ return NULL;
+}
+
+/*! gprs_ns2_sns_add_endpoint
+ * \param[in] nse
+ * \param[in] sockaddr
+ * \return
+ */
+int gprs_ns2_sns_add_endpoint(struct gprs_ns2_nse *nse,
+ const struct osmo_sockaddr *saddr)
+{
+ struct ns2_sns_state *gss;
+ struct sns_endpoint *endpoint;
+ bool do_selection = false;
- vty_out(vty, "Remote IPv4 Endpoints:%s", VTY_NEWLINE);
- for (i = 0; i < gss->num_ip4_remote; i++)
- vty_dump_sns_ip4(vty, &gss->ip4_remote[i]);
+ if (nse->ll != GPRS_NS2_LL_UDP) {
+ return -EINVAL;
+ }
+
+ if (nse->dialect != GPRS_NS2_DIALECT_SNS) {
+ return -EINVAL;
}
- if (gss->num_ip6_local && gss->num_ip6_remote) {
- vty_out(vty, "Local IPv6 Endpoints:%s", VTY_NEWLINE);
- for (i = 0; i < gss->num_ip6_local; i++)
- vty_dump_sns_ip6(vty, &gss->ip6_local[i]);
+ gss = nse->bss_sns_fi->priv;
+
+ if (ns2_get_sns_endpoint(gss, saddr))
+ return -EADDRINUSE;
+
+ endpoint = talloc_zero(nse->bss_sns_fi->priv, struct sns_endpoint);
+ if (!endpoint)
+ return -ENOMEM;
+
+ endpoint->saddr = *saddr;
+ if (llist_empty(&gss->sns_endpoints))
+ do_selection = true;
- vty_out(vty, "Remote IPv6 Endpoints:%s", VTY_NEWLINE);
- for (i = 0; i < gss->num_ip6_remote; i++)
- vty_dump_sns_ip6(vty, &gss->ip6_remote[i]);
+ llist_add_tail(&endpoint->list, &gss->sns_endpoints);
+ if (do_selection)
+ osmo_fsm_inst_dispatch(nse->bss_sns_fi, NS2_SNS_EV_REQ_SELECT_ENDPOINT, NULL);
+
+ return 0;
+}
+
+/*! gprs_ns2_sns_del_endpoint
+ * \param[in] nse
+ * \param[in] sockaddr
+ * \return 0 on success, otherwise < 0
+ */
+int gprs_ns2_sns_del_endpoint(struct gprs_ns2_nse *nse,
+ const struct osmo_sockaddr *saddr)
+{
+ struct ns2_sns_state *gss;
+ struct sns_endpoint *endpoint;
+
+ if (nse->ll != GPRS_NS2_LL_UDP) {
+ return -EINVAL;
+ }
+
+ if (nse->dialect != GPRS_NS2_DIALECT_SNS) {
+ return -EINVAL;
+ }
+
+ gss = nse->bss_sns_fi->priv;
+ endpoint = ns2_get_sns_endpoint(gss, saddr);
+ if (!endpoint)
+ return -ENOENT;
+
+ /* if this is an unused SNS endpoint it's done */
+ if (gss->initial != endpoint) {
+ llist_del(&endpoint->list);
+ talloc_free(endpoint);
+ return 0;
}
+
+ /* gprs_ns2_free_nsvcs() will trigger NS2_SNS_EV_REQ_NO_NSVC on the last NS-VC
+ * and restart SNS SIZE procedure which selects a new initial */
+ LOGNSE(nse, LOGL_INFO, "Current in-use SNS endpoint is being removed."
+ "Closing all NS-VC and restart SNS-SIZE procedure"
+ "with a remaining SNS endpoint.\n");
+
+ /* Continue with the next endpoint in the list.
+ * Special case if the endpoint is at the start or end of the list */
+ if (endpoint->list.prev == &gss->sns_endpoints ||
+ endpoint->list.next == &gss->sns_endpoints)
+ gss->initial = NULL;
+ else
+ gss->initial = llist_entry(endpoint->list.next->prev,
+ struct sns_endpoint,
+ list);
+
+ llist_del(&endpoint->list);
+ gprs_ns2_free_nsvcs(nse);
+ talloc_free(endpoint);
+
+ return 0;
}
+/*! gprs_ns2_sns_count
+ * \param[in] nse NS Entity whose IP-SNS endpoints shall be printed
+ * \return the count of endpoints or < 0 if NSE doesn't contain sns.
+ */
+int gprs_ns2_sns_count(struct gprs_ns2_nse *nse)
+{
+ struct ns2_sns_state *gss;
+ struct sns_endpoint *endpoint;
+ int count = 0;
+
+ if (nse->ll != GPRS_NS2_LL_UDP) {
+ return -EINVAL;
+ }
+
+ if (nse->dialect != GPRS_NS2_DIALECT_SNS) {
+ return -EINVAL;
+ }
+
+ gss = nse->bss_sns_fi->priv;
+ llist_for_each_entry(endpoint, &gss->sns_endpoints, list)
+ count++;
+
+ return count;
+}
+
+void ns2_sns_notify_alive(struct gprs_ns2_nse *nse, struct gprs_ns2_vc *nsvc, bool alive)
+{
+ struct ns2_sns_state *gss;
+ struct gprs_ns2_vc *tmp;
+
+ if (!nse->bss_sns_fi)
+ return;
+
+ gss = nse->bss_sns_fi->priv;
+ if (nse->bss_sns_fi->state != GPRS_SNS_ST_CONFIGURED && nse->bss_sns_fi->state != GPRS_SNS_ST_LOCAL_PROCEDURE)
+ return;
+
+ if (gss->block_no_nsvc_events)
+ return;
+
+ if (gss->alive && nse->sum_sig_weight == 0) {
+ sns_failed(nse->bss_sns_fi, "No signalling NSVC available");
+ return;
+ }
+
+ /* check if this is the current SNS NS-VC */
+ if (nsvc == gss->sns_nsvc && !alive) {
+ /* only replace the SNS NS-VC if there are other alive NS-VC.
+ * There aren't any other alive NS-VC when the SNS fsm just reached CONFIGURED
+ * and couldn't confirm yet if the NS-VC comes up */
+ llist_for_each_entry(tmp, &nse->nsvc, list) {
+ if (nsvc == tmp)
+ continue;
+ if (ns2_vc_is_unblocked(nsvc)) {
+ ns2_sns_replace_nsvc(nsvc);
+ break;
+ }
+ }
+ }
+
+ if (alive == gss->alive)
+ return;
+
+ if (alive) {
+ /* we need at least a signalling NSVC before become alive */
+ if (nse->sum_sig_weight == 0)
+ return;
+ gss->alive = true;
+ osmo_fsm_inst_dispatch(nse->bss_sns_fi, NS2_SNS_EV_REQ_NSVC_ALIVE, NULL);
+ } else {
+ /* is there at least another alive nsvc? */
+ llist_for_each_entry(tmp, &nse->nsvc, list) {
+ if (ns2_vc_is_unblocked(tmp))
+ return;
+ }
+
+ /* all NS-VC have failed */
+ gss->alive = false;
+ osmo_fsm_inst_dispatch(nse->bss_sns_fi, NS2_SNS_EV_REQ_NO_NSVC, NULL);
+ }
+}
+
+int gprs_ns2_sns_add_bind(struct gprs_ns2_nse *nse,
+ struct gprs_ns2_vc_bind *bind)
+{
+ struct ns2_sns_state *gss;
+ struct ns2_sns_bind *tmp;
+
+ OSMO_ASSERT(nse->bss_sns_fi);
+ gss = nse->bss_sns_fi->priv;
+
+ if (!gprs_ns2_is_ip_bind(bind)) {
+ return -EINVAL;
+ }
+
+ if (!llist_empty(&gss->binds)) {
+ llist_for_each_entry(tmp, &gss->binds, list) {
+ if (tmp->bind == bind)
+ return -EALREADY;
+ }
+ }
+
+ tmp = talloc_zero(gss, struct ns2_sns_bind);
+ if (!tmp)
+ return -ENOMEM;
+ tmp->bind = bind;
+ llist_add_tail(&tmp->list, &gss->binds);
+
+ osmo_fsm_inst_dispatch(nse->bss_sns_fi, NS2_SNS_EV_REQ_ADD_BIND, tmp);
+ return 0;
+}
+
+/* Remove a bind from the SNS. All assosiated NSVC must be removed. */
+int gprs_ns2_sns_del_bind(struct gprs_ns2_nse *nse,
+ struct gprs_ns2_vc_bind *bind)
+{
+ struct ns2_sns_state *gss;
+ struct ns2_sns_bind *tmp, *tmp2;
+ bool found = false;
+
+ if (!nse->bss_sns_fi)
+ return -EINVAL;
+
+ gss = nse->bss_sns_fi->priv;
+ if (gss->initial_bind && gss->initial_bind->bind == bind) {
+ if (gss->initial_bind->list.prev == &gss->binds)
+ gss->initial_bind = NULL;
+ else
+ gss->initial_bind = llist_entry(gss->initial_bind->list.prev, struct ns2_sns_bind, list);
+ }
+
+ llist_for_each_entry_safe(tmp, tmp2, &gss->binds, list) {
+ if (tmp->bind == bind) {
+ llist_del(&tmp->list);
+ found = true;
+ break;
+ }
+ }
+
+ if (!found)
+ return -ENOENT;
+
+ osmo_fsm_inst_dispatch(nse->bss_sns_fi, NS2_SNS_EV_REQ_DELETE_BIND, tmp);
+ return 0;
+}
+
+/* Update SNS weights for a bind (local endpoint).
+ * \param[in] bind the bind which has been updated
+ */
+void ns2_sns_update_weights(struct gprs_ns2_vc_bind *bind)
+{
+ struct ns2_sns_bind *sbind;
+ struct gprs_ns2_nse *nse;
+ struct ns2_sns_state *gss;
+ const struct osmo_sockaddr *addr = gprs_ns2_ip_bind_sockaddr(bind);
+
+ llist_for_each_entry(nse, &bind->nsi->nse, list) {
+ if (!nse->bss_sns_fi)
+ continue;
+
+ gss = nse->bss_sns_fi->priv;
+ if (addr->u.sa.sa_family != gss->family)
+ return;
+
+ llist_for_each_entry(sbind, &gss->binds, list) {
+ if (sbind->bind == bind) {
+ osmo_fsm_inst_dispatch(gss->nse->bss_sns_fi, NS2_SNS_EV_REQ_CHANGE_WEIGHT, sbind);
+ break;
+ }
+ }
+ }
+}
+
+
+
+
+/***********************************************************************
+ * SGSN role
+ ***********************************************************************/
+
+/* cleanup all state. If nsvc is given, don't remove this nsvc. (nsvc is given when a SIZE PDU received) */
+static void ns2_clear_sgsn(struct ns2_sns_state *gss, struct gprs_ns2_vc *size_nsvc)
+{
+ struct gprs_ns2_vc *nsvc, *nsvc2;
+
+ ns2_clear_procedures(gss);
+ ns2_clear_elems(&gss->local);
+ ns2_clear_elems(&gss->remote);
+ gss->block_no_nsvc_events = true;
+ llist_for_each_entry_safe(nsvc, nsvc2, &gss->nse->nsvc, list) {
+ /* Ignore the NSVC over which the SIZE PDU got received */
+ if (size_nsvc && size_nsvc == nsvc)
+ continue;
+
+ gprs_ns2_free_nsvc(nsvc);
+ }
+ gss->block_no_nsvc_events = false;
+}
+
+static void ns2_sns_st_sgsn_unconfigured_onenter(struct osmo_fsm_inst *fi, uint32_t old_state)
+{
+ struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
+
+ ns2_clear_sgsn(gss, NULL);
+}
+
+static void ns2_sns_st_sgsn_unconfigured(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
+ OSMO_ASSERT(gss->role == GPRS_SNS_ROLE_SGSN);
+ /* do nothing; Rx SNS-SIZE handled in ns2_sns_st_all_action_sgsn() */
+}
+
+/* We're waiting for inbound SNS-CONFIG from the BSS */
+static void ns2_sns_st_sgsn_wait_config(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
+ struct gprs_ns2_nse *nse = nse_inst_from_fi(fi);
+ struct gprs_ns2_inst *nsi = nse->nsi;
+ uint8_t cause;
+ int rc;
+
+ OSMO_ASSERT(gss->role == GPRS_SNS_ROLE_SGSN);
+
+ switch (event) {
+ case NS2_SNS_EV_RX_CONFIG:
+ case NS2_SNS_EV_RX_CONFIG_END:
+ rc = ns_sns_append_remote_eps(fi, data);
+ if (rc < 0) {
+ cause = -rc;
+ ns2_tx_sns_config_ack(gss->sns_nsvc, &cause);
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_UNCONFIGURED, 0, 0);
+ return;
+ }
+ /* only change state if last CONFIG was received */
+ if (event == NS2_SNS_EV_RX_CONFIG_END) {
+ /* ensure sum of data weight / sig weights is > 0 */
+ if (ip46_weight_sum_data(&gss->remote) == 0 || ip46_weight_sum_sig(&gss->remote) == 0) {
+ cause = NS_CAUSE_INVAL_WEIGH;
+ ns2_tx_sns_config_ack(gss->sns_nsvc, &cause);
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_UNCONFIGURED, 0, 0);
+ break;
+ }
+ ns2_tx_sns_config_ack(gss->sns_nsvc, NULL);
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_SGSN_WAIT_CONFIG_ACK, nsi->timeout[NS_TOUT_TSNS_PROV], 3);
+ } else {
+ /* just send CONFIG-ACK */
+ ns2_tx_sns_config_ack(gss->sns_nsvc, NULL);
+ osmo_timer_schedule(&fi->timer, nse->nsi->timeout[NS_TOUT_TSNS_PROV], 0);
+ }
+ break;
+ }
+}
+
+static void ns2_sns_st_sgsn_wait_config_ack_onenter(struct osmo_fsm_inst *fi, uint32_t old_state)
+{
+ struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
+ OSMO_ASSERT(gss->role == GPRS_SNS_ROLE_SGSN);
+
+ /* transmit SGSN-oriented SNS-CONFIG */
+ ns2_tx_sns_config(gss->sns_nsvc, true, gss->local.ip4, gss->local.num_ip4,
+ gss->local.ip6, gss->local.num_ip6);
+}
+
+/* We're waiting for SNS-CONFIG-ACK from the BSS (in response to our outbound SNS-CONFIG) */
+static void ns2_sns_st_sgsn_wait_config_ack(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
+ struct gprs_ns2_nse *nse = nse_inst_from_fi(fi);
+ struct tlv_parsed *tp = NULL;
+
+ OSMO_ASSERT(gss->role == GPRS_SNS_ROLE_SGSN);
+
+ switch (event) {
+ case NS2_SNS_EV_RX_CONFIG_ACK:
+ tp = data;
+ if (TLVP_VAL_MINLEN(tp, NS_IE_CAUSE, 1)) {
+ LOGPFSML(fi, LOGL_ERROR, "Rx SNS-CONFIG-ACK with cause %s\n",
+ gprs_ns2_cause_str(*TLVP_VAL(tp, NS_IE_CAUSE)));
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_UNCONFIGURED, 0, 0);
+ break;
+ }
+ /* we currently only send one SNS-CONFIG with END FLAG */
+ if (true) {
+ create_missing_nsvcs(fi);
+ /* start the test procedure on ALL NSVCs! */
+ gprs_ns2_start_alive_all_nsvcs(nse);
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_CONFIGURED, ns_sns_configured_timeout(fi), 4);
+ }
+ break;
+ }
+}
+
+/* SGSN-side SNS state machine */
+static const struct osmo_fsm_state ns2_sns_sgsn_states[] = {
+ [GPRS_SNS_ST_UNCONFIGURED] = {
+ .in_event_mask = 0, /* handled by all_state_action */
+ .out_state_mask = S(GPRS_SNS_ST_UNCONFIGURED) |
+ S(GPRS_SNS_ST_SGSN_WAIT_CONFIG),
+ .name = "UNCONFIGURED",
+ .action = ns2_sns_st_sgsn_unconfigured,
+ .onenter = ns2_sns_st_sgsn_unconfigured_onenter,
+ },
+ [GPRS_SNS_ST_SGSN_WAIT_CONFIG] = {
+ .in_event_mask = S(NS2_SNS_EV_RX_CONFIG) |
+ S(NS2_SNS_EV_RX_CONFIG_END),
+ .out_state_mask = S(GPRS_SNS_ST_UNCONFIGURED) |
+ S(GPRS_SNS_ST_SGSN_WAIT_CONFIG) |
+ S(GPRS_SNS_ST_SGSN_WAIT_CONFIG_ACK),
+ .name = "SGSN_WAIT_CONFIG",
+ .action = ns2_sns_st_sgsn_wait_config,
+ },
+ [GPRS_SNS_ST_SGSN_WAIT_CONFIG_ACK] = {
+ .in_event_mask = S(NS2_SNS_EV_RX_CONFIG_ACK),
+ .out_state_mask = S(GPRS_SNS_ST_UNCONFIGURED) |
+ S(GPRS_SNS_ST_SGSN_WAIT_CONFIG) |
+ S(GPRS_SNS_ST_SGSN_WAIT_CONFIG_ACK) |
+ S(GPRS_SNS_ST_CONFIGURED),
+ .name = "SGSN_WAIT_CONFIG_ACK",
+ .action = ns2_sns_st_sgsn_wait_config_ack,
+ .onenter = ns2_sns_st_sgsn_wait_config_ack_onenter,
+ },
+ [GPRS_SNS_ST_CONFIGURED] = {
+ .in_event_mask = S(NS2_SNS_EV_RX_ADD) |
+ S(NS2_SNS_EV_RX_DELETE) |
+ S(NS2_SNS_EV_RX_CHANGE_WEIGHT) |
+ S(NS2_SNS_EV_REQ_NSVC_ALIVE),
+ .out_state_mask = S(GPRS_SNS_ST_UNCONFIGURED) |
+ S(GPRS_SNS_ST_SGSN_WAIT_CONFIG) |
+ S(GPRS_SNS_ST_LOCAL_PROCEDURE),
+ .name = "CONFIGURED",
+ /* shared with BSS side; once configured there's no difference */
+ .action = ns2_sns_st_configured,
+ .onenter = ns2_sns_st_configured_onenter,
+ },
+ [GPRS_SNS_ST_LOCAL_PROCEDURE] = {
+ .in_event_mask = S(NS2_SNS_EV_RX_ADD) |
+ S(NS2_SNS_EV_RX_DELETE) |
+ S(NS2_SNS_EV_RX_CHANGE_WEIGHT) |
+ S(NS2_SNS_EV_RX_ACK) |
+ S(NS2_SNS_EV_REQ_CHANGE_WEIGHT) |
+ S(NS2_SNS_EV_REQ_NSVC_ALIVE),
+ .out_state_mask = S(GPRS_SNS_ST_UNCONFIGURED) |
+ S(GPRS_SNS_ST_CONFIGURED) |
+ S(GPRS_SNS_ST_LOCAL_PROCEDURE),
+ .name = "LOCAL_PROCEDURE",
+ /* shared with BSS side; once configured there's no difference */
+ .action = ns2_sns_st_local_procedure,
+ .onenter = ns2_sns_st_local_procedure_onenter,
+ },
+};
+
+static int ns2_sns_fsm_sgsn_timer_cb(struct osmo_fsm_inst *fi)
+{
+ struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
+ struct gprs_ns2_nse *nse = nse_inst_from_fi(fi);
+ struct gprs_ns2_inst *nsi = nse->nsi;
+
+ gss->N++;
+ switch (fi->T) {
+ case 3:
+ if (gss->N >= nsi->timeout[NS_TOUT_TSNS_CONFIG_RETRIES]) {
+ LOGPFSML(fi, LOGL_ERROR, "NSE %d: SGSN Config retries failed. Giving up.\n", nse->nsei);
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_UNCONFIGURED, nsi->timeout[NS_TOUT_TSNS_PROV], 3);
+ } else {
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_SGSN_WAIT_CONFIG_ACK, nsi->timeout[NS_TOUT_TSNS_PROV], 3);
+ }
+ break;
+ case 4:
+ LOGPFSML(fi, LOGL_ERROR, "NSE %d: Config succeeded but no NS-VC came online.\n", nse->nsei);
+ break;
+ case 5:
+ if (gss->N >= nsi->timeout[NS_TOUT_TSNS_PROCEDURES_RETRIES]) {
+ sns_failed(fi, "SNS Procedure retries failed.");
+ } else {
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_LOCAL_PROCEDURE, nsi->timeout[NS_TOUT_TSNS_PROV],
+ fi->T);
+ }
+ break;
+ }
+
+ return 0;
+}
+
+/* allstate-action for SGSN role */
+static void ns2_sns_st_all_action_sgsn(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct ns2_sns_state *gss = (struct ns2_sns_state *) fi->priv;
+ struct tlv_parsed *tp = NULL;
+ size_t num_local_eps, num_remote_eps;
+ uint8_t flag;
+ uint8_t cause;
+
+ OSMO_ASSERT(gss->role == GPRS_SNS_ROLE_SGSN);
+
+ switch (event) {
+ case NS2_SNS_EV_RX_SIZE:
+ tp = (struct tlv_parsed *) data;
+ /* check for mandatory / conditional IEs */
+ if (!TLVP_PRES_LEN(tp, NS_IE_RESET_FLAG, 1) ||
+ !TLVP_PRES_LEN(tp, NS_IE_MAX_NR_NSVC, 2)) {
+ cause = NS_CAUSE_MISSING_ESSENT_IE;
+ ns2_tx_sns_size_ack(gss->sns_nsvc, &cause);
+ if (fi->state == GPRS_SNS_ST_UNCONFIGURED)
+ sns_failed(fi, "Rx Size: Missing essential IE");
+ break;
+ }
+ if (!TLVP_PRES_LEN(tp, NS_IE_IPv4_EP_NR, 2) &&
+ !TLVP_PRES_LEN(tp, NS_IE_IPv6_EP_NR, 2)) {
+ cause = NS_CAUSE_MISSING_ESSENT_IE;
+ ns2_tx_sns_size_ack(gss->sns_nsvc, &cause);
+ if (fi->state == GPRS_SNS_ST_UNCONFIGURED)
+ sns_failed(fi, "Rx Size: Missing essential IE");
+ break;
+ }
+ if (TLVP_PRES_LEN(tp, NS_IE_IPv4_EP_NR, 2))
+ gss->num_max_ip4_remote = tlvp_val16be(tp, NS_IE_IPv4_EP_NR);
+ if (TLVP_PRES_LEN(tp, NS_IE_IPv6_EP_NR, 2))
+ gss->num_max_ip6_remote = tlvp_val16be(tp, NS_IE_IPv6_EP_NR);
+ /* decide if we go for IPv4 or IPv6 */
+ if (gss->num_max_ip6_remote && ns2_sns_count_num_local_ep(fi, AF_INET6)) {
+ gss->family = AF_INET6;
+ ns2_sns_compute_local_ep_from_binds(fi);
+ num_local_eps = gss->local.num_ip6;
+ num_remote_eps = gss->num_max_ip6_remote;
+ } else if (gss->num_max_ip4_remote && ns2_sns_count_num_local_ep(fi, AF_INET)) {
+ gss->family = AF_INET;
+ ns2_sns_compute_local_ep_from_binds(fi);
+ num_local_eps = gss->local.num_ip4;
+ num_remote_eps = gss->num_max_ip4_remote;
+ } else {
+ if (gss->local.num_ip4 && !gss->num_max_ip4_remote)
+ cause = NS_CAUSE_INVAL_NR_IPv4_EP;
+ else
+ cause = NS_CAUSE_INVAL_NR_IPv6_EP;
+ ns2_tx_sns_size_ack(gss->sns_nsvc, &cause);
+ if (fi->state == GPRS_SNS_ST_UNCONFIGURED)
+ sns_failed(fi, "Rx Size: Invalid Nr of IPv4/IPv6 EPs");
+ break;
+ }
+ /* ensure number of NS-VCs is sufficient for full mesh */
+ gss->num_max_nsvcs = tlvp_val16be(tp, NS_IE_MAX_NR_NSVC);
+ if (gss->num_max_nsvcs < num_remote_eps * num_local_eps) {
+ LOGPFSML(fi, LOGL_ERROR, "%zu local and %zu remote EPs, requires %zu NS-VC, "
+ "but BSS supports only %zu maximum NS-VCs\n", num_local_eps,
+ num_remote_eps, num_local_eps * num_remote_eps, gss->num_max_nsvcs);
+ cause = NS_CAUSE_INVAL_NR_NS_VC;
+ ns2_tx_sns_size_ack(gss->sns_nsvc, &cause);
+ if (fi->state == GPRS_SNS_ST_UNCONFIGURED)
+ sns_failed(fi, NULL);
+ break;
+ }
+ /* perform state reset, if requested */
+ flag = *TLVP_VAL(tp, NS_IE_RESET_FLAG);
+ if (flag & 1) {
+ /* clear all state */
+ /* TODO: ensure gss->sns_nsvc is always the NSVC on which we received the SIZE PDU */
+ gss->N = 0;
+ ns2_clear_sgsn(gss, gss->sns_nsvc);
+ /* keep the NSVC we need for SNS, but unconfigure it */
+ gss->sns_nsvc->sig_weight = 0;
+ gss->sns_nsvc->data_weight = 0;
+ gss->block_no_nsvc_events = true;
+ ns2_vc_force_unconfigured(gss->sns_nsvc);
+ gss->block_no_nsvc_events = false;
+ ns2_sns_compute_local_ep_from_binds(fi);
+ }
+
+ if (fi->state == GPRS_SNS_ST_UNCONFIGURED && !(flag & 1)) {
+ sns_failed(fi, "Rx Size without Reset flag, but NSE is unknown");
+ break;
+ }
+
+ /* send SIZE_ACK */
+ ns2_tx_sns_size_ack(gss->sns_nsvc, NULL);
+ /* only wait for SNS-CONFIG in case of Reset flag */
+ if (flag & 1)
+ osmo_fsm_inst_state_chg(fi, GPRS_SNS_ST_SGSN_WAIT_CONFIG, 0, 0);
+ break;
+ case NS2_SNS_EV_REQ_FREE_NSVCS:
+ sns_failed(fi, "On user request to free all NSVCs");
+ break;
+ default:
+ ns2_sns_st_all_action(fi, event, data);
+ break;
+ }
+}
+
+static struct osmo_fsm gprs_ns2_sns_sgsn_fsm = {
+ .name = "GPRS-NS2-SNS-SGSN",
+ .states = ns2_sns_sgsn_states,
+ .num_states = ARRAY_SIZE(ns2_sns_sgsn_states),
+ .allstate_event_mask = S(NS2_SNS_EV_RX_SIZE) |
+ S(NS2_SNS_EV_REQ_NO_NSVC) |
+ S(NS2_SNS_EV_REQ_FREE_NSVCS) |
+ S(NS2_SNS_EV_REQ_ADD_BIND) |
+ S(NS2_SNS_EV_REQ_CHANGE_WEIGHT) |
+ S(NS2_SNS_EV_REQ_DELETE_BIND),
+ .allstate_action = ns2_sns_st_all_action_sgsn,
+ .cleanup = NULL,
+ .timer_cb = ns2_sns_fsm_sgsn_timer_cb,
+ .event_names = gprs_sns_event_names,
+ .pre_term = NULL,
+ .log_subsys = DLNS,
+};
+
+/*! Allocate an IP-SNS FSM for the SGSN side.
+ * \param[in] nse NS Entity in which the FSM runs
+ * \param[in] id string identifier
+ * \returns FSM instance on success; NULL on error */
+struct osmo_fsm_inst *ns2_sns_sgsn_fsm_alloc(struct gprs_ns2_nse *nse, const char *id)
+{
+ struct osmo_fsm_inst *fi;
+ struct ns2_sns_state *gss;
+
+ fi = osmo_fsm_inst_alloc(&gprs_ns2_sns_sgsn_fsm, nse, NULL, LOGL_DEBUG, id);
+ if (!fi)
+ return fi;
+
+ gss = talloc_zero(fi, struct ns2_sns_state);
+ if (!gss)
+ goto err;
+
+ fi->priv = gss;
+ gss->nse = nse;
+ gss->role = GPRS_SNS_ROLE_SGSN;
+ INIT_LLIST_HEAD(&gss->sns_endpoints);
+ INIT_LLIST_HEAD(&gss->binds);
+ INIT_LLIST_HEAD(&gss->procedures);
+
+ return fi;
+err:
+ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL);
+ return NULL;
+}
+
+
+
+
/* initialize osmo_ctx on main tread */
static __attribute__((constructor)) void on_dso_load_ctx(void)
{
OSMO_ASSERT(osmo_fsm_register(&gprs_ns2_sns_bss_fsm) == 0);
+ OSMO_ASSERT(osmo_fsm_register(&gprs_ns2_sns_sgsn_fsm) == 0);
}
diff --git a/src/gb/gprs_ns2_udp.c b/src/gb/gprs_ns2_udp.c
index bb421a75..48160218 100644
--- a/src/gb/gprs_ns2_udp.c
+++ b/src/gb/gprs_ns2_udp.c
@@ -49,6 +49,7 @@ struct priv_bind {
struct osmo_fd fd;
struct osmo_sockaddr addr;
int dscp;
+ uint8_t priority;
};
struct priv_vc {
@@ -63,6 +64,8 @@ static void free_bind(struct gprs_ns2_vc_bind *bind)
if (!bind)
return;
+ OSMO_ASSERT(gprs_ns2_is_ip_bind(bind));
+
priv = bind->priv;
osmo_fd_close(&priv->fd);
@@ -71,25 +74,59 @@ static void free_bind(struct gprs_ns2_vc_bind *bind)
static void free_vc(struct gprs_ns2_vc *nsvc)
{
+ if (!nsvc)
+ return;
+
if (!nsvc->priv)
return;
+ OSMO_ASSERT(gprs_ns2_is_ip_bind(nsvc->bind));
talloc_free(nsvc->priv);
nsvc->priv = NULL;
}
+static void dump_vty(const struct gprs_ns2_vc_bind *bind,
+ struct vty *vty, bool stats)
+{
+ struct priv_bind *priv;
+ struct gprs_ns2_vc *nsvc;
+ struct osmo_sockaddr_str sockstr = {};
+ unsigned long nsvcs = 0;
+
+ if (!bind)
+ return;
+
+ priv = bind->priv;
+ if (osmo_sockaddr_str_from_sockaddr(&sockstr, &priv->addr.u.sas))
+ strcpy(sockstr.ip, "invalid");
+
+ llist_for_each_entry(nsvc, &bind->nsvc, blist) {
+ nsvcs++;
+ }
+
+ vty_out(vty, "UDP bind: %s:%d DSCP: %d Priority: %u%s", sockstr.ip, sockstr.port,
+ priv->dscp, priv->priority, VTY_NEWLINE);
+ vty_out(vty, " IP-SNS signalling weight: %u data weight: %u%s",
+ bind->sns_sig_weight, bind->sns_data_weight, VTY_NEWLINE);
+ vty_out(vty, " %lu NS-VC:%s", nsvcs, VTY_NEWLINE);
+
+ llist_for_each_entry(nsvc, &bind->nsvc, blist) {
+ ns2_vty_dump_nsvc(vty, nsvc, stats);
+ }
+}
+
+
/*! Find a NS-VC by its remote socket address.
* \param[in] bind in which to search
* \param[in] saddr remote peer socket adddress to search
- * \param[out] result pointer to result
- * \returns 0 on success; 1 if no NS-VC was found; negative on error */
-int gprs_ns2_find_vc_by_sockaddr(struct gprs_ns2_vc_bind *bind, struct osmo_sockaddr *saddr, struct gprs_ns2_vc **result)
+ * \returns NS-VC matching sockaddr; NULL if none found */
+struct gprs_ns2_vc *gprs_ns2_nsvc_by_sockaddr_bind(struct gprs_ns2_vc_bind *bind,
+ const struct osmo_sockaddr *saddr)
{
struct gprs_ns2_vc *nsvc;
struct priv_vc *vcpriv;
- if (!result)
- return -EINVAL;
+ OSMO_ASSERT(gprs_ns2_is_ip_bind(bind));
llist_for_each_entry(nsvc, &bind->nsvc, blist) {
vcpriv = nsvc->priv;
@@ -98,11 +135,10 @@ int gprs_ns2_find_vc_by_sockaddr(struct gprs_ns2_vc_bind *bind, struct osmo_sock
if (osmo_sockaddr_cmp(&vcpriv->remote, saddr))
continue;
- *result = nsvc;
- return 0;
+ return nsvc;
}
- return 1;
+ return NULL;
}
static inline int nsip_sendmsg(struct gprs_ns2_vc_bind *bind,
@@ -136,10 +172,10 @@ static int nsip_vc_sendmsg(struct gprs_ns2_vc *nsvc, struct msgb *msg)
}
/* Read a single NS-over-IP message */
-static struct msgb *read_nsip_msg(struct osmo_fd *bfd, int *error,
- struct osmo_sockaddr *saddr)
+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 = gprs_ns2_msgb_alloc();
+ struct msgb *msg = ns2_msgb_alloc();
int ret = 0;
socklen_t saddr_len = sizeof(*saddr);
@@ -151,8 +187,8 @@ static struct msgb *read_nsip_msg(struct osmo_fd *bfd, int *error,
ret = recvfrom(bfd->fd, msg->data, NS_ALLOC_SIZE - NS_ALLOC_HEADROOM, 0,
&saddr->u.sa, &saddr_len);
if (ret < 0) {
- LOGP(DLNS, LOGL_ERROR, "recv error %s during NSIP recvfrom %s\n",
- strerror(errno), osmo_sock_get_name2(bfd->fd));
+ 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;
@@ -182,47 +218,46 @@ static struct priv_vc *ns2_driver_alloc_vc(struct gprs_ns2_vc_bind *bind, struct
static int handle_nsip_read(struct osmo_fd *bfd)
{
- int rc;
+ int rc = 0;
int error = 0;
struct gprs_ns2_vc_bind *bind = bfd->data;
struct osmo_sockaddr saddr;
struct gprs_ns2_vc *nsvc;
- struct msgb *msg = read_nsip_msg(bfd, &error, &saddr);
+ struct msgb *msg = read_nsip_msg(bfd, &error, &saddr, bind);
struct msgb *reject;
if (!msg)
return -EINVAL;
/* check if a vc is available */
- rc = gprs_ns2_find_vc_by_sockaddr(bind, &saddr, &nsvc);
- if (rc) {
+ nsvc = gprs_ns2_nsvc_by_sockaddr_bind(bind, &saddr);
+ if (!nsvc) {
/* VC not found */
- rc = ns2_create_vc(bind, msg, "newconnection", &reject, &nsvc);
+ rc = ns2_create_vc(bind, msg, &saddr, "newconnection", &reject, &nsvc);
switch (rc) {
- case GPRS_NS2_CS_FOUND:
- rc = ns2_recv_vc(bind->nsi, nsvc, msg);
+ case NS2_CS_FOUND:
break;
- case GPRS_NS2_CS_ERROR:
- case GPRS_NS2_CS_SKIPPED:
+ case NS2_CS_ERROR:
+ case NS2_CS_SKIPPED:
rc = 0;
- break;
- case GPRS_NS2_CS_REJECTED:
+ goto out;
+ case NS2_CS_REJECTED:
/* nsip_sendmsg will free reject */
- nsip_sendmsg(bind, reject, &saddr);
- return 0;
- case GPRS_NS2_CS_CREATED:
+ rc = nsip_sendmsg(bind, reject, &saddr);
+ goto out;
+ case NS2_CS_CREATED:
ns2_driver_alloc_vc(bind, nsvc, &saddr);
- gprs_ns2_vc_fsm_start(nsvc);
- rc = ns2_recv_vc(bind->nsi, nsvc, msg);
+ /* only start the fsm for non SNS. SNS will take care of its own */
+ if (nsvc->nse->dialect != GPRS_NS2_DIALECT_SNS)
+ ns2_vc_fsm_start(nsvc);
break;
}
- } else {
- /* VC found */
- rc = ns2_recv_vc(bind->nsi, nsvc, msg);
}
- msgb_free(msg);
+ return ns2_recv_vc(nsvc, msg);
+out:
+ msgb_free(msg);
return rc;
}
@@ -244,68 +279,97 @@ static int nsip_fd_cb(struct osmo_fd *bfd, unsigned int what)
return rc;
}
+/*! Find NS bind for a given socket address
+ * \param[in] nsi NS instance
+ * \param[in] sockaddr socket address to search for
+ * \return
+ */
+struct gprs_ns2_vc_bind *gprs_ns2_ip_bind_by_sockaddr(struct gprs_ns2_inst *nsi,
+ const struct osmo_sockaddr *sockaddr)
+{
+ struct gprs_ns2_vc_bind *bind;
+ const struct osmo_sockaddr *local;
+
+ OSMO_ASSERT(nsi);
+ OSMO_ASSERT(sockaddr);
+
+ llist_for_each_entry(bind, &nsi->binding, list) {
+ if (!gprs_ns2_is_ip_bind(bind))
+ continue;
+
+ local = gprs_ns2_ip_bind_sockaddr(bind);
+ if (!osmo_sockaddr_cmp(sockaddr, local))
+ return bind;
+ }
+
+ return NULL;
+}
+
/*! Bind to an IPv4/IPv6 address
* \param[in] nsi NS Instance in which to create the NSVC
* \param[in] local the local address to bind to
* \param[in] dscp the DSCP/TOS bits used for transmitted data
- * \param[out] result if set, returns the bind object
- * \return 0 on success; negative in case of error */
+ * \param[out] result pointer to the created bind or if a bind with the name exists return the bind.
+ * \return 0 on success; negative on error. -EALREADY returned in case a bind with the name exists */
int gprs_ns2_ip_bind(struct gprs_ns2_inst *nsi,
- struct osmo_sockaddr *local,
+ const char *name,
+ const struct osmo_sockaddr *local,
int dscp,
struct gprs_ns2_vc_bind **result)
{
- struct gprs_ns2_vc_bind *bind = talloc_zero(nsi, struct gprs_ns2_vc_bind);
+ struct gprs_ns2_vc_bind *bind;
struct priv_bind *priv;
int rc;
- if (!bind)
- return -ENOSPC;
+ if (local->u.sa.sa_family != AF_INET && local->u.sa.sa_family != AF_INET6)
+ return -EINVAL;
- if (local->u.sa.sa_family != AF_INET && local->u.sa.sa_family != AF_INET6) {
- talloc_free(bind);
+ if (dscp < 0 || dscp > 63)
return -EINVAL;
+
+ bind = gprs_ns2_ip_bind_by_sockaddr(nsi, local);
+ if (bind) {
+ if (result)
+ *result = bind;
+ return -EBUSY;
}
+ rc = ns2_bind_alloc(nsi, name, &bind);
+ if (rc < 0)
+ return rc;
+
bind->driver = &vc_driver_ip;
+ bind->ll = GPRS_NS2_LL_UDP;
+ /* expect 100 mbit at least.
+ * TODO: ask the network layer about the speed. But would require
+ * notification on change. */
+ bind->transfer_capability = 100;
bind->send_vc = nsip_vc_sendmsg;
bind->free_vc = free_vc;
- bind->nsi = nsi;
+ bind->dump_vty = dump_vty;
priv = bind->priv = talloc_zero(bind, struct priv_bind);
if (!priv) {
- talloc_free(bind);
- return -ENOSPC;
+ gprs_ns2_free_bind(bind);
+ return -ENOMEM;
}
priv->fd.cb = nsip_fd_cb;
priv->fd.data = bind;
priv->addr = *local;
- INIT_LLIST_HEAD(&bind->nsvc);
-
- llist_add(&bind->list, &nsi->binding);
+ priv->dscp = dscp;
rc = osmo_sock_init_osa_ofd(&priv->fd, SOCK_DGRAM, IPPROTO_UDP,
local, NULL,
- OSMO_SOCK_F_BIND);
+ OSMO_SOCK_F_BIND | OSMO_SOCK_F_DSCP(priv->dscp));
if (rc < 0) {
- talloc_free(priv);
- talloc_free(bind);
+ gprs_ns2_free_bind(bind);
return rc;
}
- if (dscp > 0) {
- priv->dscp = dscp;
-
- rc = setsockopt(priv->fd.fd, IPPROTO_IP, IP_TOS,
- &dscp, sizeof(dscp));
- if (rc < 0)
- LOGP(DLNS, LOGL_ERROR,
- "Failed to set the DSCP to %d with ret(%d) errno(%d)\n",
- dscp, rc, errno);
- }
-
- ns2_vty_bind_apply(bind);
-
+ /* IPv4: max fragmented payload can be (13 bit) * 8 byte => 65535.
+ * IPv6: max payload can be 65535 (RFC 2460).
+ * UDP header = 8 byte */
+ bind->mtu = 65535 - 8;
if (result)
*result = bind;
@@ -317,14 +381,39 @@ int gprs_ns2_ip_bind(struct gprs_ns2_inst *nsi,
* \param[in] nse NS entity to be used for the new NS-VC
* \param[in] remote remote address to connect to
* \return pointer to newly-allocated and connected NS-VC; NULL on error */
-struct gprs_ns2_vc *gprs_ns2_ip_bind_connect(struct gprs_ns2_vc_bind *bind,
- struct gprs_ns2_nse *nse,
- struct osmo_sockaddr *remote)
+struct gprs_ns2_vc *ns2_ip_bind_connect(struct gprs_ns2_vc_bind *bind,
+ struct gprs_ns2_nse *nse,
+ const struct osmo_sockaddr *remote)
{
struct gprs_ns2_vc *nsvc;
+ const struct osmo_sockaddr *local;
struct priv_vc *priv;
+ enum gprs_ns2_vc_mode vc_mode;
+ char idbuf[256], tmp[INET6_ADDRSTRLEN + 8];
+
+ OSMO_ASSERT(gprs_ns2_is_ip_bind(bind));
+
+ vc_mode = ns2_dialect_to_vc_mode(nse->dialect);
+ if ((int) vc_mode == -1) {
+ LOGNSE(nse, LOGL_ERROR, "Can not derive vc mode from dialect %d. Maybe libosmocore is too old.\n",
+ nse->dialect);
+ return NULL;
+ }
+
+ /* duplicate */
+ if (gprs_ns2_nsvc_by_sockaddr_bind(bind, remote))
+ return NULL;
+
+ local = gprs_ns2_ip_bind_sockaddr(bind);
+ osmo_sockaddr_to_str_buf(tmp, sizeof(tmp), local);
+ snprintf(idbuf, sizeof(idbuf), "NSE%05u-NSVC-%s-%s-%s", nse->nsei, gprs_ns2_lltype_str(nse->ll),
+ tmp, osmo_sockaddr_to_str(remote));
+ osmo_identifier_sanitize_buf(idbuf, NULL, '_');
+
+ nsvc = ns2_vc_alloc(bind, nse, true, vc_mode, idbuf);
+ if (!nsvc)
+ return NULL;
- nsvc = ns2_vc_alloc(bind, nse, true);
nsvc->priv = talloc_zero(bind, struct priv_vc);
if (!nsvc->priv) {
gprs_ns2_free_nsvc(nsvc);
@@ -334,31 +423,78 @@ struct gprs_ns2_vc *gprs_ns2_ip_bind_connect(struct gprs_ns2_vc_bind *bind,
priv = nsvc->priv;
priv->remote = *remote;
- nsvc->ll = GPRS_NS_LL_UDP;
-
return nsvc;
}
+/*! Return the socket address of the local peer of a NS-VC.
+ * \param[in] nsvc NS-VC whose local peer we want to know
+ * \return address of the local peer; NULL in case of error */
+const struct osmo_sockaddr *gprs_ns2_ip_vc_local(const struct gprs_ns2_vc *nsvc)
+{
+ struct priv_bind *priv;
+
+ if (nsvc->bind->driver != &vc_driver_ip)
+ return NULL;
+
+ priv = nsvc->bind->priv;
+ return &priv->addr;
+}
+
/*! Return the socket address of the remote peer of a NS-VC.
* \param[in] nsvc NS-VC whose remote peer we want to know
* \return address of the remote peer; NULL in case of error */
-struct osmo_sockaddr *gprs_ns2_ip_vc_sockaddr(struct gprs_ns2_vc *nsvc)
+const struct osmo_sockaddr *gprs_ns2_ip_vc_remote(const struct gprs_ns2_vc *nsvc)
{
struct priv_vc *priv;
- if (nsvc->ll != GPRS_NS_LL_UDP)
+ if (nsvc->bind->driver != &vc_driver_ip)
return NULL;
priv = nsvc->priv;
return &priv->remote;
}
+/*! Compare the NS-VC with the given parameter
+ * \param[in] nsvc NS-VC to compare with
+ * \param[in] local The local address
+ * \param[in] remote The remote address
+ * \param[in] nsvci NS-VCI will only be used if the NS-VC in BLOCKRESET mode otherwise NS-VCI isn't applicable.
+ * \return true if the NS-VC has the same properties as given
+ */
+bool gprs_ns2_ip_vc_equal(const struct gprs_ns2_vc *nsvc,
+ const struct osmo_sockaddr *local,
+ const struct osmo_sockaddr *remote,
+ uint16_t nsvci)
+{
+ struct priv_vc *vpriv;
+ struct priv_bind *bpriv;
+
+ if (nsvc->bind->driver != &vc_driver_ip)
+ return false;
+
+ vpriv = nsvc->priv;
+ bpriv = nsvc->bind->priv;
+
+ if (osmo_sockaddr_cmp(local, &bpriv->addr))
+ return false;
+
+ if (osmo_sockaddr_cmp(remote, &vpriv->remote))
+ return false;
+
+ if (nsvc->mode == GPRS_NS2_VC_MODE_BLOCKRESET)
+ if (nsvc->nsvci != nsvci)
+ return false;
+
+ return true;
+}
+
/*! Return the locally bound socket address of the bind.
* \param[in] bind The bind whose local address we want to know
* \return address of the local bind */
-struct osmo_sockaddr *gprs_ns2_ip_bind_sockaddr(struct gprs_ns2_vc_bind *bind)
+const struct osmo_sockaddr *gprs_ns2_ip_bind_sockaddr(struct gprs_ns2_vc_bind *bind)
{
struct priv_bind *priv;
+ OSMO_ASSERT(gprs_ns2_is_ip_bind(bind));
priv = bind->priv;
return &priv->addr;
@@ -376,18 +512,106 @@ int gprs_ns2_ip_bind_set_dscp(struct gprs_ns2_vc_bind *bind, int dscp)
struct priv_bind *priv;
int rc = 0;
+ if (dscp < 0 || dscp > 63)
+ return -EINVAL;
+
+ OSMO_ASSERT(gprs_ns2_is_ip_bind(bind));
priv = bind->priv;
if (dscp != priv->dscp) {
priv->dscp = dscp;
- rc = setsockopt(priv->fd.fd, IPPROTO_IP, IP_TOS,
- &dscp, sizeof(dscp));
- if (rc < 0)
- LOGP(DLNS, LOGL_ERROR,
- "Failed to set the DSCP to %d with ret(%d) errno(%d)\n",
- dscp, rc, errno);
+ rc = osmo_sock_set_dscp(priv->fd.fd, dscp);
+ if (rc < 0) {
+ LOGBIND(bind, LOGL_ERROR, "Failed to set the DSCP to %u with ret(%d) errno(%d)\n",
+ dscp, rc, errno);
+ }
+ }
+
+ return rc;
+}
+
+/*! Set the socket priority of the given bind. */
+int gprs_ns2_ip_bind_set_priority(struct gprs_ns2_vc_bind *bind, uint8_t priority)
+{
+ struct priv_bind *priv;
+ int rc = 0;
+
+ OSMO_ASSERT(gprs_ns2_is_ip_bind(bind));
+ priv = bind->priv;
+
+ if (priority != priv->priority) {
+ priv->priority = priority;
+
+ rc = osmo_sock_set_priority(priv->fd.fd, priority);
+ if (rc < 0) {
+ LOGBIND(bind, LOGL_ERROR, "Failed to set the priority to %u with ret(%d) errno(%d)\n",
+ priority, rc, errno);
+ }
}
return rc;
}
+
+
+/*! Count UDP binds compatible with remote */
+int ns2_ip_count_bind(struct gprs_ns2_inst *nsi, struct osmo_sockaddr *remote)
+{
+ struct gprs_ns2_vc_bind *bind;
+ const struct osmo_sockaddr *sa;
+ int count = 0;
+
+ llist_for_each_entry(bind, &nsi->binding, list) {
+ if (!gprs_ns2_is_ip_bind(bind))
+ continue;
+
+ sa = gprs_ns2_ip_bind_sockaddr(bind);
+ if (!sa)
+ continue;
+
+ if (sa->u.sa.sa_family == remote->u.sa.sa_family)
+ count++;
+ }
+
+ return count;
+}
+
+/* return the matching bind by index */
+struct gprs_ns2_vc_bind *ns2_ip_get_bind_by_index(struct gprs_ns2_inst *nsi,
+ struct osmo_sockaddr *remote,
+ int index)
+{
+ struct gprs_ns2_vc_bind *bind;
+ const struct osmo_sockaddr *sa;
+ int i = 0;
+
+ llist_for_each_entry(bind, &nsi->binding, list) {
+ if (!gprs_ns2_is_ip_bind(bind))
+ continue;
+
+ sa = gprs_ns2_ip_bind_sockaddr(bind);
+ if (!sa)
+ continue;
+
+ if (sa->u.sa.sa_family == remote->u.sa.sa_family) {
+ if (index == i)
+ return bind;
+ i++;
+ }
+ }
+
+ return NULL;
+}
+
+/*! set the signalling and data weight for this bind
+ * \param[in] bind
+ * \param[in] signalling the signalling weight
+ * \param[in] data the data weight
+ */
+void gprs_ns2_ip_bind_set_sns_weight(struct gprs_ns2_vc_bind *bind, uint8_t signalling, uint8_t data)
+{
+ OSMO_ASSERT(gprs_ns2_is_ip_bind(bind));
+ bind->sns_sig_weight = signalling;
+ bind->sns_data_weight = data;
+ ns2_sns_update_weights(bind);
+}
diff --git a/src/gb/gprs_ns2_vc_fsm.c b/src/gb/gprs_ns2_vc_fsm.c
index 33a0328b..9cd83c4f 100644
--- a/src/gb/gprs_ns2_vc_fsm.c
+++ b/src/gb/gprs_ns2_vc_fsm.c
@@ -49,22 +49,24 @@
#define S(x) (1 << (x))
-#define DNS 10
-
struct gprs_ns2_vc_priv {
struct gprs_ns2_vc *nsvc;
/* how often the timer was triggered */
int N;
- /* The initiater is responsible to UNBLOCK the VC. The BSS is usually the initiater.
- * It can change while runtime. The side which blocks an unblocked side.*/
- bool initiater;
+ /* The initiator is responsible to UNBLOCK the VC. The BSS is usually the initiator.
+ * It can change during runtime. The side which blocks an unblocked side.*/
+ bool initiator;
+ bool initiate_block;
+ bool initiate_reset;
+ /* if unitdata is forwarded to the user */
+ bool accept_unitdata;
/* the alive counter is present in all states */
struct {
struct osmo_timer_list timer;
enum ns2_timeout mode;
int N;
- struct timeval timer_started;
+ struct timespec timer_started;
} alive;
};
@@ -75,13 +77,13 @@ struct gprs_ns2_vc_priv {
* - UNCONFIGURED -> RESET -> BLOCK -> UNBLOCKED
*
* Without RESET/BLOCK, the state should follow:
- * - UNCONFIGURED -> ALIVE -> UNBLOCKED
+ * - UNCONFIGURED -> RECOVERY -> UNBLOCKED
*
* The UNBLOCKED and TEST states are used to send ALIVE PDU using the timeout Tns-test and Tns-alive.
* UNBLOCKED -> TEST: on expire of Tns-Test, send Alive PDU.
* TEST -> UNBLOCKED: on receive of Alive_Ack PDU, go into UNBLOCKED.
*
- * The ALIVE state is used as intermediate, because a VC is only valid if it received an Alive ACK when
+ * The RECOVERY state is used as intermediate, because a VC is only valid if it received an Alive ACK when
* not using RESET/BLOCK procedure.
*/
@@ -91,38 +93,49 @@ enum gprs_ns2_vc_state {
GPRS_NS2_ST_BLOCKED,
GPRS_NS2_ST_UNBLOCKED, /* allows sending NS_UNITDATA */
- GPRS_NS2_ST_ALIVE, /* only used when not using RESET/BLOCK procedure */
+ GPRS_NS2_ST_RECOVERING, /* only used when not using RESET/BLOCK procedure */
};
enum gprs_ns2_vc_event {
- GPRS_NS2_EV_START,
+ GPRS_NS2_EV_REQ_START,
/* received messages */
- GPRS_NS2_EV_RESET,
- GPRS_NS2_EV_RESET_ACK,
- GPRS_NS2_EV_UNBLOCK,
- GPRS_NS2_EV_UNBLOCK_ACK,
- GPRS_NS2_EV_BLOCK,
- GPRS_NS2_EV_BLOCK_ACK,
- GPRS_NS2_EV_ALIVE,
- GPRS_NS2_EV_ALIVE_ACK,
- GPRS_NS2_EV_STATUS,
-
- GPRS_NS2_EV_UNITDATA,
+ GPRS_NS2_EV_RX_RESET,
+ GPRS_NS2_EV_RX_RESET_ACK,
+ GPRS_NS2_EV_RX_UNBLOCK,
+ GPRS_NS2_EV_RX_UNBLOCK_ACK,
+ GPRS_NS2_EV_RX_BLOCK,
+ GPRS_NS2_EV_RX_BLOCK_ACK,
+ GPRS_NS2_EV_RX_ALIVE,
+ GPRS_NS2_EV_RX_ALIVE_ACK,
+ GPRS_NS2_EV_RX_STATUS,
+
+ GPRS_NS2_EV_RX_UNITDATA,
+
+ GPRS_NS2_EV_REQ_FORCE_UNCONFIGURED, /* called via vty for tests */
+ GPRS_NS2_EV_REQ_OM_RESET, /* vty cmd: reset */
+ GPRS_NS2_EV_REQ_OM_BLOCK, /* vty cmd: block */
+ GPRS_NS2_EV_REQ_OM_UNBLOCK, /* vty cmd: unblock*/
+ GPRS_NS2_EV_RX_BLOCK_FOREIGN, /* received a BLOCK over another NSVC */
};
-static const struct value_string gprs_ns2_vc_event_names[] = {
- { GPRS_NS2_EV_START, "START" },
- { GPRS_NS2_EV_RESET, "RESET" },
- { GPRS_NS2_EV_RESET_ACK, "RESET_ACK" },
- { GPRS_NS2_EV_UNBLOCK, "UNBLOCK" },
- { GPRS_NS2_EV_UNBLOCK_ACK, "UNBLOCK_ACK" },
- { GPRS_NS2_EV_BLOCK, "BLOCK" },
- { GPRS_NS2_EV_BLOCK_ACK, "BLOCK_ACK" },
- { GPRS_NS2_EV_ALIVE, "ALIVE" },
- { GPRS_NS2_EV_ALIVE_ACK, "ALIVE_ACK" },
- { GPRS_NS2_EV_STATUS, "STATUS" },
- { GPRS_NS2_EV_UNITDATA, "UNITDATA" },
+static const struct value_string ns2_vc_event_names[] = {
+ { GPRS_NS2_EV_REQ_START, "REQ-START" },
+ { GPRS_NS2_EV_RX_RESET, "RX-RESET" },
+ { GPRS_NS2_EV_RX_RESET_ACK, "RX-RESET_ACK" },
+ { GPRS_NS2_EV_RX_UNBLOCK, "RX-UNBLOCK" },
+ { GPRS_NS2_EV_RX_UNBLOCK_ACK, "RX-UNBLOCK_ACK" },
+ { GPRS_NS2_EV_RX_BLOCK, "RX-BLOCK" },
+ { GPRS_NS2_EV_RX_BLOCK_FOREIGN, "RX-BLOCK_FOREIGN" },
+ { GPRS_NS2_EV_RX_BLOCK_ACK, "RX-BLOCK_ACK" },
+ { GPRS_NS2_EV_RX_ALIVE, "RX-ALIVE" },
+ { GPRS_NS2_EV_RX_ALIVE_ACK, "RX-ALIVE_ACK" },
+ { GPRS_NS2_EV_RX_STATUS, "RX-STATUS" },
+ { GPRS_NS2_EV_RX_UNITDATA, "RX-UNITDATA" },
+ { GPRS_NS2_EV_REQ_FORCE_UNCONFIGURED, "REQ-FORCE_UNCONFIGURED" },
+ { GPRS_NS2_EV_REQ_OM_RESET, "REQ-O&M-RESET"},
+ { GPRS_NS2_EV_REQ_OM_BLOCK, "REQ-O&M-BLOCK"},
+ { GPRS_NS2_EV_REQ_OM_UNBLOCK, "REQ-O&M-UNBLOCK"},
{ 0, NULL }
};
@@ -132,35 +145,59 @@ static inline struct gprs_ns2_inst *ns_inst_from_fi(struct osmo_fsm_inst *fi)
return priv->nsvc->nse->nsi;
}
-static void start_test_procedure(struct gprs_ns2_vc_priv *priv)
+/* Start the NS-TEST procedure, either with transmitting a tx_alive,
+ * (start_tx_alive==true) or with starting tns-test */
+static void start_test_procedure(struct osmo_fsm_inst *fi, bool start_tx_alive)
{
+ struct gprs_ns2_vc_priv *priv = fi->priv;
struct gprs_ns2_inst *nsi = priv->nsvc->nse->nsi;
+ unsigned int tout_idx;
- if (osmo_timer_pending(&priv->alive.timer))
- return;
+ if (osmo_timer_pending(&priv->alive.timer)) {
+ if (start_tx_alive) {
+ if (priv->alive.mode == NS_TOUT_TNS_ALIVE)
+ return;
+ } else {
+ if (priv->alive.mode == NS_TOUT_TNS_TEST)
+ return;
+ }
+ }
- priv->alive.mode = NS_TOUT_TNS_ALIVE;
priv->alive.N = 0;
- osmo_gettimeofday(&priv->alive.timer_started, NULL);
- ns2_tx_alive(priv->nsvc);
- osmo_timer_schedule(&priv->alive.timer, nsi->timeout[NS_TOUT_TNS_ALIVE], 0);
+ if (start_tx_alive) {
+ priv->alive.mode = NS_TOUT_TNS_ALIVE;
+ osmo_clock_gettime(CLOCK_MONOTONIC, &priv->alive.timer_started);
+ ns2_tx_alive(priv->nsvc);
+ tout_idx = NS_TOUT_TNS_ALIVE;
+ } else {
+ priv->alive.mode = NS_TOUT_TNS_TEST;
+ tout_idx = NS_TOUT_TNS_TEST;
+ }
+ LOGPFSML(fi, LOGL_DEBUG, "Starting Tns-%s of %u seconds\n",
+ tout_idx == NS_TOUT_TNS_ALIVE ? "alive" : "test", nsi->timeout[tout_idx]);
+ osmo_timer_schedule(&priv->alive.timer, nsi->timeout[tout_idx], 0);
}
static void stop_test_procedure(struct gprs_ns2_vc_priv *priv)
{
+ osmo_stat_item_set(osmo_stat_item_group_get_item(priv->nsvc->statg, NS_STAT_ALIVE_DELAY), 0);
osmo_timer_del(&priv->alive.timer);
}
+/* how many milliseconds have expired since the last alive timer start? */
static int alive_timer_elapsed_ms(struct gprs_ns2_vc_priv *priv)
{
- struct timeval now, elapsed;
- osmo_gettimeofday(&now, NULL);
- timersub(&now, &priv->alive.timer_started, &elapsed);
+ struct timespec now, elapsed;
- return 1000 * elapsed.tv_sec + elapsed.tv_usec / 1000;
+ if (osmo_clock_gettime(CLOCK_MONOTONIC, &now) != 0)
+ return 0;
+
+ timespecsub(&now, &priv->alive.timer_started, &elapsed);
+ return elapsed.tv_sec * 1000 + (elapsed.tv_nsec / 1000000);
}
+/* we just received a NS-ALIVE-ACK; re-schedule after Tns-test */
static void recv_test_procedure(struct osmo_fsm_inst *fi)
{
struct gprs_ns2_vc_priv *priv = fi->priv;
@@ -173,7 +210,7 @@ static void recv_test_procedure(struct osmo_fsm_inst *fi)
priv->alive.mode = NS_TOUT_TNS_TEST;
osmo_timer_schedule(&priv->alive.timer, nsi->timeout[NS_TOUT_TNS_TEST], 0);
- osmo_stat_item_set(nsvc->statg->items[NS_STAT_ALIVE_DELAY],
+ osmo_stat_item_set(osmo_stat_item_group_get_item(nsvc->statg, NS_STAT_ALIVE_DELAY),
alive_timer_elapsed_ms(priv));
}
@@ -187,10 +224,13 @@ static void alive_timeout_handler(void *data)
switch (priv->alive.mode) {
case NS_TOUT_TNS_TEST:
priv->alive.mode = NS_TOUT_TNS_ALIVE;
+ priv->alive.N = 0;
+ osmo_clock_gettime(CLOCK_MONOTONIC, &priv->alive.timer_started);
ns2_tx_alive(priv->nsvc);
osmo_timer_schedule(&priv->alive.timer, nsi->timeout[NS_TOUT_TNS_ALIVE], 0);
break;
case NS_TOUT_TNS_ALIVE:
+ RATE_CTR_INC_NS(priv->nsvc, NS_CTR_LOST_ALIVE);
priv->alive.N++;
if (priv->alive.N <= nsi->timeout[NS_TOUT_TNS_ALIVE_RETRIES]) {
@@ -199,10 +239,10 @@ static void alive_timeout_handler(void *data)
osmo_timer_schedule(&priv->alive.timer, nsi->timeout[NS_TOUT_TNS_ALIVE], 0);
} else {
/* lost connection */
- if (priv->nsvc->mode == NS2_VC_MODE_BLOCKRESET) {
+ if (priv->nsvc->mode == GPRS_NS2_VC_MODE_BLOCKRESET) {
osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_RESET, nsi->timeout[NS_TOUT_TNS_RESET], 0);
} else {
- osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_ALIVE, nsi->timeout[NS_TOUT_TNS_ALIVE], 0);
+ osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_RECOVERING, nsi->timeout[NS_TOUT_TNS_ALIVE], 0);
}
}
break;
@@ -211,22 +251,39 @@ static void alive_timeout_handler(void *data)
}
}
-static void gprs_ns2_st_unconfigured(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+
+static void ns2_st_unconfigured_onenter(struct osmo_fsm_inst *fi, uint32_t old_state)
+{
+ struct gprs_ns2_vc_priv *priv = fi->priv;
+
+ stop_test_procedure(fi->priv);
+ ns2_nse_notify_unblocked(priv->nsvc, false);
+}
+
+static void ns2_st_unconfigured(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct gprs_ns2_vc_priv *priv = fi->priv;
struct gprs_ns2_inst *nsi = priv->nsvc->nse->nsi;
+ priv->initiate_reset = priv->initiate_block = priv->initiator;
+ priv->nsvc->om_blocked = false;
+
switch (event) {
- case GPRS_NS2_EV_START:
+ case GPRS_NS2_EV_REQ_START:
switch (priv->nsvc->mode) {
- case NS2_VC_MODE_ALIVE:
- osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_ALIVE, nsi->timeout[NS_TOUT_TNS_ALIVE], NS_TOUT_TNS_ALIVE);
+ case GPRS_NS2_VC_MODE_ALIVE:
+ if (priv->nsvc->nse->dialect == GPRS_NS2_DIALECT_SNS) {
+ /* In IP-SNS, the NS-VC are assumed initially alive, until the alive
+ * procedure should fail at some future point */
+ osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_UNBLOCKED, 0, 0);
+ } else {
+ osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_RECOVERING, nsi->timeout[NS_TOUT_TNS_ALIVE], NS_TOUT_TNS_ALIVE);
+ }
break;
- case NS2_VC_MODE_BLOCKRESET:
+ case GPRS_NS2_VC_MODE_BLOCKRESET:
osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_RESET, nsi->timeout[NS_TOUT_TNS_RESET], NS_TOUT_TNS_RESET);
break;
}
-
break;
default:
OSMO_ASSERT(0);
@@ -234,28 +291,32 @@ static void gprs_ns2_st_unconfigured(struct osmo_fsm_inst *fi, uint32_t event, v
}
-static void gprs_ns2_st_reset_onenter(struct osmo_fsm_inst *fi, uint32_t old_state)
+static void ns2_st_reset_onenter(struct osmo_fsm_inst *fi, uint32_t old_state)
{
struct gprs_ns2_vc_priv *priv = fi->priv;
if (old_state != GPRS_NS2_ST_RESET)
priv->N = 0;
- if (priv->initiater)
+ priv->accept_unitdata = false;
+ if (priv->initiate_reset)
ns2_tx_reset(priv->nsvc, NS_CAUSE_OM_INTERVENTION);
stop_test_procedure(priv);
ns2_nse_notify_unblocked(priv->nsvc, false);
}
-static void gprs_ns2_st_reset(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+static void ns2_st_reset(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct gprs_ns2_inst *nsi = ns_inst_from_fi(fi);
struct gprs_ns2_vc_priv *priv = fi->priv;
- if (priv->initiater) {
+ if (priv->initiate_reset) {
switch (event) {
- case GPRS_NS2_EV_RESET_ACK:
+ case GPRS_NS2_EV_RX_RESET:
+ ns2_tx_reset_ack(priv->nsvc);
+ /* fall-through */
+ case GPRS_NS2_EV_RX_RESET_ACK:
osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_BLOCKED,
nsi->timeout[NS_TOUT_TNS_BLOCK], NS_TOUT_TNS_BLOCK);
break;
@@ -263,7 +324,7 @@ static void gprs_ns2_st_reset(struct osmo_fsm_inst *fi, uint32_t event, void *da
} else {
/* we are on the receiving end */
switch (event) {
- case GPRS_NS2_EV_RESET:
+ case GPRS_NS2_EV_RX_RESET:
ns2_tx_reset_ack(priv->nsvc);
osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_BLOCKED,
0, 0);
@@ -272,33 +333,69 @@ static void gprs_ns2_st_reset(struct osmo_fsm_inst *fi, uint32_t event, void *da
}
}
-static void gprs_ns2_st_blocked_onenter(struct osmo_fsm_inst *fi, uint32_t old_state)
+static void ns2_st_blocked_onenter(struct osmo_fsm_inst *fi, uint32_t old_state)
{
struct gprs_ns2_vc_priv *priv = fi->priv;
- if (old_state != GPRS_NS2_ST_BLOCKED)
+ if (old_state != GPRS_NS2_ST_BLOCKED) {
priv->N = 0;
+ RATE_CTR_INC_NS(priv->nsvc, NS_CTR_BLOCKED);
+ }
- if (priv->initiater)
+ ns2_nse_notify_unblocked(priv->nsvc, false);
+ if (priv->nsvc->om_blocked) {
+ /* we are already blocked after a RESET */
+ if (old_state == GPRS_NS2_ST_RESET) {
+ osmo_timer_del(&fi->timer);
+ } else {
+ ns2_tx_block(priv->nsvc, NS_CAUSE_OM_INTERVENTION, NULL);
+ }
+ } else if (priv->initiate_block) {
ns2_tx_unblock(priv->nsvc);
+ }
- start_test_procedure(priv);
+ start_test_procedure(fi, true);
}
-static void gprs_ns2_st_blocked(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+static void ns2_st_blocked(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct gprs_ns2_vc_priv *priv = fi->priv;
- if (priv->initiater) {
+ if (priv->nsvc->om_blocked) {
+ switch (event) {
+ case GPRS_NS2_EV_RX_BLOCK_ACK:
+ priv->accept_unitdata = false;
+ osmo_timer_del(&fi->timer);
+ break;
+ case GPRS_NS2_EV_RX_BLOCK:
+ ns2_tx_block_ack(priv->nsvc, NULL);
+ /* fall through */
+ case GPRS_NS2_EV_RX_BLOCK_FOREIGN:
+ /* the BLOCK ACK for foreign BLOCK PDUs (rx over another nsvc) will be sent
+ * from the receiving nsvc */
+ priv->accept_unitdata = false;
+ osmo_timer_del(&fi->timer);
+ break;
+ case GPRS_NS2_EV_RX_UNBLOCK:
+ priv->accept_unitdata = false;
+ ns2_tx_block(priv->nsvc, NS_CAUSE_OM_INTERVENTION, NULL);
+ osmo_timer_add(&fi->timer);
+ break;
+ }
+ } else if (priv->initiate_block) {
switch (event) {
- case GPRS_NS2_EV_BLOCK:
+ case GPRS_NS2_EV_RX_BLOCK_FOREIGN:
+ /* the block ack will be sent by the rx NSVC */
+ break;
+ case GPRS_NS2_EV_RX_BLOCK:
/* TODO: BLOCK is a UNBLOCK_NACK */
- ns2_tx_block_ack(priv->nsvc);
+ ns2_tx_block_ack(priv->nsvc, NULL);
break;
- case GPRS_NS2_EV_UNBLOCK:
+ case GPRS_NS2_EV_RX_UNBLOCK:
ns2_tx_unblock_ack(priv->nsvc);
/* fall through */
- case GPRS_NS2_EV_UNBLOCK_ACK:
+ case GPRS_NS2_EV_RX_UNBLOCK_ACK:
+ priv->accept_unitdata = true;
osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_UNBLOCKED,
0, NS_TOUT_TNS_TEST);
break;
@@ -306,7 +403,13 @@ static void gprs_ns2_st_blocked(struct osmo_fsm_inst *fi, uint32_t event, void *
} else {
/* we are on the receiving end. The initiator who sent RESET is responsible to UNBLOCK! */
switch (event) {
- case GPRS_NS2_EV_UNBLOCK:
+ case GPRS_NS2_EV_RX_BLOCK_FOREIGN:
+ /* the block ack will be sent by the rx NSVC */
+ break;
+ case GPRS_NS2_EV_RX_BLOCK:
+ ns2_tx_block_ack(priv->nsvc, NULL);
+ break;
+ case GPRS_NS2_EV_RX_UNBLOCK:
ns2_tx_unblock_ack(priv->nsvc);
osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_UNBLOCKED,
0, 0);
@@ -315,37 +418,60 @@ static void gprs_ns2_st_blocked(struct osmo_fsm_inst *fi, uint32_t event, void *
}
}
-static void gprs_ns2_st_unblocked_on_enter(struct osmo_fsm_inst *fi, uint32_t old_state)
+static void ns2_st_unblocked_on_enter(struct osmo_fsm_inst *fi, uint32_t old_state)
{
struct gprs_ns2_vc_priv *priv = fi->priv;
+ struct gprs_ns2_vc *nsvc = priv->nsvc;
+ struct gprs_ns2_nse *nse = nsvc->nse;
+
+ if (old_state != GPRS_NS2_ST_UNBLOCKED) {
+ RATE_CTR_INC_NS(nsvc, NS_CTR_UNBLOCKED);
+ osmo_clock_gettime(CLOCK_MONOTONIC, &nsvc->ts_alive_change);
+ }
- ns2_nse_notify_unblocked(priv->nsvc, true);
+ priv->accept_unitdata = true;
+ ns2_nse_notify_unblocked(nsvc, true);
+ ns2_prim_status_ind(nse, nsvc, 0, GPRS_NS2_AFF_CAUSE_VC_RECOVERY);
+
+ /* the closest interpretation of the spec would start Tns-test here first,
+ * and only send a NS-ALIVE after Tns-test has expired (i.e. setting the
+ * second argument to 'false'. However, being quick in detecting unavailability
+ * of a NS-VC seems like a good idea */
+ start_test_procedure(fi, true);
}
-static void gprs_ns2_st_unblocked(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+static void ns2_st_unblocked(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
struct gprs_ns2_vc_priv *priv = fi->priv;
switch (event) {
- case GPRS_NS2_EV_BLOCK:
- priv->initiater = false;
- ns2_tx_block_ack(priv->nsvc);
+ case GPRS_NS2_EV_RX_UNBLOCK:
+ ns2_tx_unblock_ack(priv->nsvc);
+ break;
+ case GPRS_NS2_EV_RX_BLOCK:
+ ns2_tx_block_ack(priv->nsvc, NULL);
+ /* fall through */
+ case GPRS_NS2_EV_RX_BLOCK_FOREIGN:
+ /* the BLOCK ACK for foreign BLOCK PDUs (rx over another nsvc) will be sent
+ * from the receiving nsvc */
+ priv->initiate_block = false;
+ priv->accept_unitdata = false;
osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_BLOCKED,
0, 2);
break;
}
}
-static void gprs_ns2_st_alive(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+static void ns2_st_alive(struct osmo_fsm_inst *fi, uint32_t event, void *data)
{
switch (event) {
- case GPRS_NS2_EV_ALIVE_ACK:
+ case GPRS_NS2_EV_RX_ALIVE_ACK:
osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_UNBLOCKED, 0, 0);
break;
}
}
-static void gprs_ns2_st_alive_onenter(struct osmo_fsm_inst *fi, uint32_t old_state)
+static void ns2_st_alive_onenter(struct osmo_fsm_inst *fi, uint32_t old_state)
{
struct gprs_ns2_vc_priv *priv = fi->priv;
struct gprs_ns2_inst *nsi = ns_inst_from_fi(fi);
@@ -353,73 +479,76 @@ static void gprs_ns2_st_alive_onenter(struct osmo_fsm_inst *fi, uint32_t old_sta
priv->alive.mode = NS_TOUT_TNS_TEST;
osmo_timer_schedule(&priv->alive.timer, nsi->timeout[NS_TOUT_TNS_TEST], 0);
- if (old_state != GPRS_NS2_ST_ALIVE)
+ if (old_state != GPRS_NS2_ST_RECOVERING)
priv->N = 0;
- ns2_tx_alive(priv->nsvc);
+ start_test_procedure(fi, true);
ns2_nse_notify_unblocked(priv->nsvc, false);
}
-static void gprs_ns2_st_alive_onleave(struct osmo_fsm_inst *fi, uint32_t next_state)
-{
- start_test_procedure(fi->priv);
-}
-
-static const struct osmo_fsm_state gprs_ns2_vc_states[] = {
+static const struct osmo_fsm_state ns2_vc_states[] = {
[GPRS_NS2_ST_UNCONFIGURED] = {
- .in_event_mask = S(GPRS_NS2_EV_START),
- .out_state_mask = S(GPRS_NS2_ST_RESET) | S(GPRS_NS2_ST_ALIVE),
+ .in_event_mask = S(GPRS_NS2_EV_REQ_START),
+ .out_state_mask = S(GPRS_NS2_ST_RESET) |
+ S(GPRS_NS2_ST_RECOVERING) |
+ S(GPRS_NS2_ST_UNBLOCKED),
.name = "UNCONFIGURED",
- .action = gprs_ns2_st_unconfigured,
+ .action = ns2_st_unconfigured,
+ .onenter = ns2_st_unconfigured_onenter,
},
[GPRS_NS2_ST_RESET] = {
- .in_event_mask = S(GPRS_NS2_EV_RESET_ACK) | S(GPRS_NS2_EV_RESET),
+ .in_event_mask = S(GPRS_NS2_EV_RX_RESET_ACK) | S(GPRS_NS2_EV_RX_RESET),
.out_state_mask = S(GPRS_NS2_ST_RESET) |
- S(GPRS_NS2_ST_BLOCKED),
+ S(GPRS_NS2_ST_BLOCKED) |
+ S(GPRS_NS2_ST_UNCONFIGURED),
.name = "RESET",
- .action = gprs_ns2_st_reset,
- .onenter = gprs_ns2_st_reset_onenter,
+ .action = ns2_st_reset,
+ .onenter = ns2_st_reset_onenter,
},
[GPRS_NS2_ST_BLOCKED] = {
- .in_event_mask = S(GPRS_NS2_EV_BLOCK) | S(GPRS_NS2_EV_BLOCK_ACK) |
- S(GPRS_NS2_EV_UNBLOCK) | S(GPRS_NS2_EV_UNBLOCK_ACK),
+ .in_event_mask = S(GPRS_NS2_EV_RX_BLOCK) | S(GPRS_NS2_EV_RX_BLOCK_ACK) |
+ S(GPRS_NS2_EV_RX_UNBLOCK) | S(GPRS_NS2_EV_RX_UNBLOCK_ACK) |
+ S(GPRS_NS2_EV_RX_BLOCK_FOREIGN),
.out_state_mask = S(GPRS_NS2_ST_RESET) |
S(GPRS_NS2_ST_UNBLOCKED) |
- S(GPRS_NS2_ST_BLOCKED),
+ S(GPRS_NS2_ST_BLOCKED) |
+ S(GPRS_NS2_ST_UNCONFIGURED),
.name = "BLOCKED",
- .action = gprs_ns2_st_blocked,
- .onenter = gprs_ns2_st_blocked_onenter,
+ .action = ns2_st_blocked,
+ .onenter = ns2_st_blocked_onenter,
},
[GPRS_NS2_ST_UNBLOCKED] = {
- .in_event_mask = S(GPRS_NS2_EV_BLOCK),
- .out_state_mask = S(GPRS_NS2_ST_RESET) | S(GPRS_NS2_ST_ALIVE) |
- S(GPRS_NS2_ST_BLOCKED),
+ .in_event_mask = S(GPRS_NS2_EV_RX_BLOCK) | S(GPRS_NS2_EV_RX_UNBLOCK_ACK) |
+ S(GPRS_NS2_EV_RX_UNBLOCK) | S(GPRS_NS2_EV_RX_BLOCK_FOREIGN),
+ .out_state_mask = S(GPRS_NS2_ST_RESET) | S(GPRS_NS2_ST_RECOVERING) |
+ S(GPRS_NS2_ST_BLOCKED) |
+ S(GPRS_NS2_ST_UNCONFIGURED),
.name = "UNBLOCKED",
- .action = gprs_ns2_st_unblocked,
- .onenter = gprs_ns2_st_unblocked_on_enter,
+ .action = ns2_st_unblocked,
+ .onenter = ns2_st_unblocked_on_enter,
},
- /* ST_ALIVE is only used on VC without RESET/BLOCK */
- [GPRS_NS2_ST_ALIVE] = {
- .in_event_mask = S(GPRS_NS2_EV_ALIVE_ACK),
- .out_state_mask = S(GPRS_NS2_ST_RESET) |
- S(GPRS_NS2_ST_UNBLOCKED),
- .name = "ALIVE",
- .action = gprs_ns2_st_alive,
- .onenter = gprs_ns2_st_alive_onenter,
- .onleave = gprs_ns2_st_alive_onleave,
+ /* ST_RECOVERING is only used on VC without RESET/BLOCK */
+ [GPRS_NS2_ST_RECOVERING] = {
+ .in_event_mask = S(GPRS_NS2_EV_RX_ALIVE_ACK),
+ .out_state_mask = S(GPRS_NS2_ST_RECOVERING) |
+ S(GPRS_NS2_ST_UNBLOCKED) |
+ S(GPRS_NS2_ST_UNCONFIGURED),
+ .name = "RECOVERING",
+ .action = ns2_st_alive,
+ .onenter = ns2_st_alive_onenter,
},
};
-static int gprs_ns2_vc_fsm_timer_cb(struct osmo_fsm_inst *fi)
+static int ns2_vc_fsm_timer_cb(struct osmo_fsm_inst *fi)
{
struct gprs_ns2_inst *nsi = ns_inst_from_fi(fi);
struct gprs_ns2_vc_priv *priv = fi->priv;
- if (priv->initiater) {
- /* PCU timeouts */
- switch (fi->state) {
- case GPRS_NS2_ST_RESET:
+ switch (fi->state) {
+ case GPRS_NS2_ST_RESET:
+ if (priv->initiate_reset) {
+ RATE_CTR_INC_NS(priv->nsvc, NS_CTR_LOST_RESET);
priv->N++;
if (priv->N <= nsi->timeout[NS_TOUT_TNS_RESET_RETRIES]) {
osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_RESET, nsi->timeout[NS_TOUT_TNS_RESET], 0);
@@ -427,30 +556,44 @@ static int gprs_ns2_vc_fsm_timer_cb(struct osmo_fsm_inst *fi)
priv->N = 0;
osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_RESET, nsi->timeout[NS_TOUT_TNS_RESET], 0);
}
- break;
- case GPRS_NS2_ST_BLOCKED:
+ }
+ break;
+ case GPRS_NS2_ST_BLOCKED:
+ if (priv->initiate_block) {
priv->N++;
- if (priv->N <= nsi->timeout[NS_TOUT_TNS_BLOCK_RETRIES]) {
- osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_BLOCKED, nsi->timeout[NS_TOUT_TNS_BLOCK], 0);
+ if (priv->nsvc->om_blocked) {
+ if (priv->N <= nsi->timeout[NS_TOUT_TNS_BLOCK_RETRIES]) {
+ osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_BLOCKED, nsi->timeout[NS_TOUT_TNS_BLOCK], 0);
+ } else {
+ /* 7.2 stop accepting data when BLOCK PDU not responded */
+ priv->accept_unitdata = false;
+ }
} else {
- osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_RESET, nsi->timeout[NS_TOUT_TNS_RESET], 0);
+ if (priv->N <= nsi->timeout[NS_TOUT_TNS_BLOCK_RETRIES]) {
+ osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_BLOCKED, nsi->timeout[NS_TOUT_TNS_BLOCK], 0);
+ } else {
+ osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_RESET, nsi->timeout[NS_TOUT_TNS_RESET], 0);
+ }
}
- break;
- case GPRS_NS2_ST_ALIVE:
+ }
+ break;
+ case GPRS_NS2_ST_RECOVERING:
+ if (priv->initiate_reset) {
priv->N++;
if (priv->N <= nsi->timeout[NS_TOUT_TNS_ALIVE_RETRIES]) {
- osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_ALIVE, 0, 0);
+ osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_RECOVERING, 0, 0);
} else {
priv->N = 0;
- osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_ALIVE, 0, 0);
+ osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_RECOVERING, 0, 0);
}
break;
}
+ break;
}
return 0;
}
-static void gprs_ns2_recv_unitdata(struct osmo_fsm_inst *fi,
+static void ns2_recv_unitdata(struct osmo_fsm_inst *fi,
struct msgb *msg)
{
struct gprs_ns2_vc_priv *priv = fi->priv;
@@ -459,8 +602,10 @@ static void gprs_ns2_recv_unitdata(struct osmo_fsm_inst *fi,
struct osmo_gprs_ns2_prim nsp = {};
uint16_t bvci;
- if (msgb_l2len(msg) < sizeof(*nsh) + 3)
+ if (msgb_l2len(msg) < sizeof(*nsh) + 3) {
+ msgb_free(msg);
return;
+ }
/* TODO: 7.1: For an IP sub-network, an NS-UNITDATA PDU
* for a PTP BVC may indicate a request to change the IP endpoint
@@ -469,39 +614,52 @@ static void gprs_ns2_recv_unitdata(struct osmo_fsm_inst *fi,
/* TODO: nsh->data[0] -> C/R only valid in IP SNS */
bvci = nsh->data[1] << 8 | nsh->data[2];
- msgb_bssgph(msg) = &nsh->data[3];
- msgb_bvci(msg) = nsp.bvci = bvci;
- msgb_nsei(msg) = nsp.nsei = priv->nsvc->nse->nsei;
+ msg->l3h = &nsh->data[3];
+ nsp.bvci = bvci;
+ nsp.nsei = priv->nsvc->nse->nsei;
- if (nsh->data[0])
- nsp.u.unitdata.change = NS_ENDPOINT_REQUEST_CHANGE;
+ /* 10.3.9 NS SDU Control Bits */
+ if (nsh->data[0] & 0x1)
+ nsp.u.unitdata.change = GPRS_NS2_ENDPOINT_REQUEST_CHANGE;
- osmo_prim_init(&nsp.oph, SAP_NS, PRIM_NS_UNIT_DATA,
+ osmo_prim_init(&nsp.oph, SAP_NS, GPRS_NS2_PRIM_UNIT_DATA,
PRIM_OP_INDICATION, msg);
nsi->cb(&nsp.oph, nsi->cb_data);
}
-static void gprs_ns2_vc_fsm_allstate_action(struct osmo_fsm_inst *fi,
+static void ns2_vc_fsm_allstate_action(struct osmo_fsm_inst *fi,
uint32_t event,
void *data)
{
struct gprs_ns2_vc_priv *priv = fi->priv;
struct gprs_ns2_inst *nsi = ns_inst_from_fi(fi);
+ struct tlv_parsed *tp;
+ struct msgb *msg = data;
+ uint8_t cause;
switch (event) {
- case GPRS_NS2_EV_RESET:
- if (priv->nsvc->mode != NS2_VC_MODE_BLOCKRESET)
+ case GPRS_NS2_EV_REQ_OM_RESET:
+ if (priv->nsvc->mode != GPRS_NS2_VC_MODE_BLOCKRESET)
+ break;
+ /* move the FSM into reset */
+ if (fi->state != GPRS_NS2_ST_RESET) {
+ priv->initiate_reset = true;
+ osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_RESET, nsi->timeout[NS_TOUT_TNS_RESET], NS_TOUT_TNS_RESET);
+ }
+ break;
+ case GPRS_NS2_EV_RX_RESET:
+ if (priv->nsvc->mode != GPRS_NS2_VC_MODE_BLOCKRESET)
break;
/* move the FSM into reset */
if (fi->state != GPRS_NS2_ST_RESET) {
- priv->initiater = false;
+ priv->initiate_reset = false;
osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_RESET, nsi->timeout[NS_TOUT_TNS_RESET], NS_TOUT_TNS_RESET);
}
/* pass the event down into FSM action */
- gprs_ns2_st_reset(fi, event, data);
+ ns2_st_reset(fi, event, data);
break;
- case GPRS_NS2_EV_ALIVE:
+ case GPRS_NS2_EV_RX_ALIVE:
switch (fi->state) {
case GPRS_NS2_ST_UNCONFIGURED:
case GPRS_NS2_ST_RESET:
@@ -511,47 +669,112 @@ static void gprs_ns2_vc_fsm_allstate_action(struct osmo_fsm_inst *fi,
ns2_tx_alive_ack(priv->nsvc);
}
break;
- case GPRS_NS2_EV_ALIVE_ACK:
+ case GPRS_NS2_EV_RX_ALIVE_ACK:
/* for VCs without RESET/BLOCK/UNBLOCK, the connections comes after ALIVE_ACK unblocked */
- if (fi->state == GPRS_NS2_ST_ALIVE)
- gprs_ns2_st_alive(fi, event, data);
+ if (fi->state == GPRS_NS2_ST_RECOVERING)
+ ns2_st_alive(fi, event, data);
else
recv_test_procedure(fi);
break;
- case GPRS_NS2_EV_UNITDATA:
+ case GPRS_NS2_EV_RX_UNITDATA:
+ /* UNITDATA has to handle the release of msg.
+ * If send upwards (gprs_ns2_recv_unitdata) it must NOT free
+ * the msg, the upper layer has to do it.
+ * Otherwise the msg must be freed.
+ */
+
+ LOG_NS_DATA(priv->nsvc, "Rx", NS_PDUT_UNITDATA, LOGL_INFO, "\n");
switch (fi->state) {
case GPRS_NS2_ST_BLOCKED:
/* 7.2.1: the BLOCKED_ACK might be lost */
- if (priv->initiater)
- gprs_ns2_recv_unitdata(fi, data);
- else
- ns2_tx_status(priv->nsvc,
- NS_CAUSE_NSVC_BLOCKED,
- 0, data);
+ if (priv->accept_unitdata) {
+ ns2_recv_unitdata(fi, msg);
+ return;
+ }
+
+ ns2_tx_status(priv->nsvc,
+ NS_CAUSE_NSVC_BLOCKED,
+ 0, msg, NULL);
break;
/* ALIVE can receive UNITDATA if the ALIVE_ACK is lost */
- case GPRS_NS2_ST_ALIVE:
+ case GPRS_NS2_ST_RECOVERING:
case GPRS_NS2_ST_UNBLOCKED:
- gprs_ns2_recv_unitdata(fi, data);
+ ns2_recv_unitdata(fi, msg);
+ return;
+ }
+
+ msgb_free(msg);
+ break;
+ case GPRS_NS2_EV_REQ_FORCE_UNCONFIGURED:
+ if (fi->state != GPRS_NS2_ST_UNCONFIGURED) {
+ /* Force the NSVC back to its initial state */
+ osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_UNCONFIGURED, 0, 0);
+ return;
+ }
+ break;
+ case GPRS_NS2_EV_REQ_OM_BLOCK:
+ /* vty cmd: block */
+ priv->initiate_block = true;
+ priv->nsvc->om_blocked = true;
+ osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_BLOCKED, nsi->timeout[NS_TOUT_TNS_BLOCK], 0);
+ break;
+ case GPRS_NS2_EV_REQ_OM_UNBLOCK:
+ /* vty cmd: unblock*/
+ if (!priv->nsvc->om_blocked)
+ return;
+ priv->nsvc->om_blocked = false;
+ if (fi->state == GPRS_NS2_ST_BLOCKED)
+ osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_BLOCKED, nsi->timeout[NS_TOUT_TNS_BLOCK], 0);
+ break;
+ case GPRS_NS2_EV_RX_STATUS:
+ tp = data;
+ cause = tlvp_val8(tp, NS_IE_CAUSE, 0);
+ switch (cause) {
+ case NS_CAUSE_NSVC_BLOCKED:
+ if (fi->state != GPRS_NS2_ST_BLOCKED) {
+ LOG_NS_SIGNAL(priv->nsvc, "Rx", NS_PDUT_STATUS, LOGL_ERROR, ": remote side reported blocked state.\n");
+ priv->initiate_block = false;
+ priv->accept_unitdata = false;
+ osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_BLOCKED, nsi->timeout[NS_TOUT_TNS_BLOCK], 0);
+ }
+ break;
+ case NS_CAUSE_NSVC_UNKNOWN:
+ if (fi->state != GPRS_NS2_ST_RESET && fi->state != GPRS_NS2_ST_UNCONFIGURED) {
+ LOG_NS_SIGNAL(priv->nsvc, "Rx", NS_PDUT_STATUS, LOGL_ERROR, ": remote side reported unknown nsvc.\n");
+ osmo_fsm_inst_state_chg(fi, GPRS_NS2_ST_RESET, nsi->timeout[NS_TOUT_TNS_RESET], 0);
+ }
break;
}
+
break;
}
}
-static struct osmo_fsm gprs_ns2_vc_fsm = {
+static void ns2_vc_fsm_clean(struct osmo_fsm_inst *fi,
+ enum osmo_fsm_term_cause cause)
+{
+ struct gprs_ns2_vc_priv *priv = fi->priv;
+
+ osmo_timer_del(&priv->alive.timer);
+}
+
+static struct osmo_fsm ns2_vc_fsm = {
.name = "GPRS-NS2-VC",
- .states = gprs_ns2_vc_states,
- .num_states = ARRAY_SIZE(gprs_ns2_vc_states),
- .allstate_event_mask = S(GPRS_NS2_EV_UNITDATA) |
- S(GPRS_NS2_EV_RESET) |
- S(GPRS_NS2_EV_ALIVE) |
- S(GPRS_NS2_EV_ALIVE_ACK),
- .allstate_action = gprs_ns2_vc_fsm_allstate_action,
- .cleanup = NULL,
- .timer_cb = gprs_ns2_vc_fsm_timer_cb,
- /* .log_subsys = DNS, "is not constant" */
- .event_names = gprs_ns2_vc_event_names,
+ .states = ns2_vc_states,
+ .num_states = ARRAY_SIZE(ns2_vc_states),
+ .allstate_event_mask = S(GPRS_NS2_EV_RX_UNITDATA) |
+ S(GPRS_NS2_EV_RX_RESET) |
+ S(GPRS_NS2_EV_RX_ALIVE) |
+ S(GPRS_NS2_EV_RX_ALIVE_ACK) |
+ S(GPRS_NS2_EV_RX_STATUS) |
+ S(GPRS_NS2_EV_REQ_FORCE_UNCONFIGURED) |
+ S(GPRS_NS2_EV_REQ_OM_RESET) |
+ S(GPRS_NS2_EV_REQ_OM_BLOCK) |
+ S(GPRS_NS2_EV_REQ_OM_UNBLOCK),
+ .allstate_action = ns2_vc_fsm_allstate_action,
+ .cleanup = ns2_vc_fsm_clean,
+ .timer_cb = ns2_vc_fsm_timer_cb,
+ .event_names = ns2_vc_event_names,
.pre_term = NULL,
.log_subsys = DLNS,
};
@@ -561,23 +784,23 @@ static struct osmo_fsm gprs_ns2_vc_fsm = {
* \param ctx
* \param vc
* \param id a char representation of the virtual curcuit
- * \param initiater initiater is the site which starts the connection. Usually the BSS.
+ * \param initiator initiator is the site which starts the connection. Usually the BSS.
* \return NULL on error, otherwise the fsm
*/
-struct osmo_fsm_inst *gprs_ns2_vc_fsm_alloc(struct gprs_ns2_vc *nsvc,
- const char *id, bool initiater)
+struct osmo_fsm_inst *ns2_vc_fsm_alloc(struct gprs_ns2_vc *nsvc,
+ const char *id, bool initiator)
{
struct osmo_fsm_inst *fi;
struct gprs_ns2_vc_priv *priv;
- fi = osmo_fsm_inst_alloc(&gprs_ns2_vc_fsm, nsvc, NULL, LOGL_DEBUG, id);
+ fi = osmo_fsm_inst_alloc(&ns2_vc_fsm, nsvc, NULL, LOGL_DEBUG, id);
if (!fi)
return fi;
nsvc->fi = fi;
priv = fi->priv = talloc_zero(fi, struct gprs_ns2_vc_priv);
priv->nsvc = nsvc;
- priv->initiater = initiater;
+ priv->initiator = initiator;
osmo_timer_setup(&priv->alive.timer, alive_timeout_handler, fi);
@@ -587,75 +810,177 @@ struct osmo_fsm_inst *gprs_ns2_vc_fsm_alloc(struct gprs_ns2_vc *nsvc,
/*! Start a NS-VC FSM.
* \param nsvc the virtual circuit
* \return 0 on success; negative on error */
-int gprs_ns2_vc_fsm_start(struct gprs_ns2_vc *nsvc)
+int ns2_vc_fsm_start(struct gprs_ns2_vc *nsvc)
{
/* allows to call this function even for started nsvc by gprs_ns2_start_alive_all_nsvcs */
if (nsvc->fi->state == GPRS_NS2_ST_UNCONFIGURED)
- return osmo_fsm_inst_dispatch(nsvc->fi, GPRS_NS2_EV_START, NULL);
+ return osmo_fsm_inst_dispatch(nsvc->fi, GPRS_NS2_EV_REQ_START, NULL);
return 0;
}
+/*! Reset a NS-VC FSM.
+ * \param nsvc the virtual circuit
+ * \return 0 on success; negative on error */
+int ns2_vc_force_unconfigured(struct gprs_ns2_vc *nsvc)
+{
+ return osmo_fsm_inst_dispatch(nsvc->fi, GPRS_NS2_EV_REQ_FORCE_UNCONFIGURED, NULL);
+}
+
+/*! Block a NS-VC.
+ * \param nsvc the virtual circuit
+ * \return 0 on success; negative on error */
+int ns2_vc_block(struct gprs_ns2_vc *nsvc)
+{
+ struct gprs_ns2_vc_priv *priv = nsvc->fi->priv;
+ if (priv->nsvc->om_blocked)
+ return -EALREADY;
+
+ return osmo_fsm_inst_dispatch(nsvc->fi, GPRS_NS2_EV_REQ_OM_BLOCK, NULL);
+}
+
+/*! Unblock a NS-VC.
+ * \param nsvc the virtual circuit
+ * \return 0 on success; negative on error */
+int ns2_vc_unblock(struct gprs_ns2_vc *nsvc)
+{
+ struct gprs_ns2_vc_priv *priv = nsvc->fi->priv;
+ if (!priv->nsvc->om_blocked)
+ return -EALREADY;
+
+ return osmo_fsm_inst_dispatch(nsvc->fi, GPRS_NS2_EV_REQ_OM_UNBLOCK, NULL);
+}
+
+/*! Reset a NS-VC.
+ * \param nsvc the virtual circuit
+ * \return 0 on success; negative on error */
+int ns2_vc_reset(struct gprs_ns2_vc *nsvc)
+{
+ return osmo_fsm_inst_dispatch(nsvc->fi, GPRS_NS2_EV_REQ_OM_RESET, NULL);
+}
+
/*! entry point for messages from the driver/VL
* \param nsvc virtual circuit on which the message was received
* \param msg message that was received
* \param tp parsed TLVs of the received message
* \return 0 on success; negative on error */
-int gprs_ns2_vc_rx(struct gprs_ns2_vc *nsvc, struct msgb *msg, struct tlv_parsed *tp)
+int ns2_vc_rx(struct gprs_ns2_vc *nsvc, struct msgb *msg, struct tlv_parsed *tp)
{
struct gprs_ns_hdr *nsh = (struct gprs_ns_hdr *) msg->l2h;
+ struct gprs_ns2_vc *target_nsvc = nsvc;
struct osmo_fsm_inst *fi = nsvc->fi;
+ int rc = 0;
uint8_t cause;
+ uint16_t nsei, nsvci;
/* TODO: 7.2: on UNBLOCK/BLOCK: check if NS-VCI is correct,
* if not answer STATUS with "NS-VC unknown" */
- /* TODO: handle RESET with different VCI */
/* TODO: handle BLOCK/UNBLOCK/ALIVE with different VCI */
- if (gprs_ns2_validate(nsvc, nsh->pdu_type, msg, tp, &cause)) {
+ if (ns2_validate(nsvc, nsh->pdu_type, msg, tp, &cause)) {
+ /* don't answer on a STATUS with a STATUS */
if (nsh->pdu_type != NS_PDUT_STATUS) {
- return ns2_tx_status(nsvc, cause, 0, msg);
+ rc = ns2_tx_status(nsvc, cause, 0, msg, NULL);
+ goto out;
+ }
+ }
+
+ if (TLVP_PRESENT(tp, NS_IE_NSEI)) {
+ nsei = tlvp_val16be(tp, NS_IE_NSEI);
+ if (nsei != nsvc->nse->nsei) {
+ /* 48.016 § 7.3.1 send, RESET_ACK to wrong NSVCI + ignore */
+ if (nsh->pdu_type == NS_PDUT_RESET)
+ ns2_tx_reset_ack(nsvc);
+
+ LOG_NS_SIGNAL(nsvc, "Rx", nsh->pdu_type, LOGL_ERROR, " with wrong NSEI (exp: %05u, got %05u). Ignoring PDU.\n", nsvc->nse->nsei, nsei);
+ goto out;
+ }
+ }
+
+ if (nsvc->nsvci_is_valid && TLVP_PRESENT(tp, NS_IE_VCI)) {
+ nsvci = tlvp_val16be(tp, NS_IE_VCI);
+ if (nsvci != nsvc->nsvci) {
+ /* 48.016 § 7.3.1 send RESET_ACK to wrong NSVCI + ignore */
+ if (nsh->pdu_type == NS_PDUT_RESET) {
+ ns2_tx_reset_ack(nsvc);
+ LOG_NS_SIGNAL(nsvc, "Rx", nsh->pdu_type, LOGL_ERROR, " with wrong NSVCI (exp: %05u, got %05u). Ignoring PDU.\n", nsvc->nsvci, nsvci);
+ goto out;
+ } else if (nsh->pdu_type == NS_PDUT_BLOCK || nsh->pdu_type == NS_PDUT_STATUS) {
+ /* this is a PDU received over a NSVC and reports a status/block for another NSVC */
+ target_nsvc = gprs_ns2_nsvc_by_nsvci(nsvc->nse->nsi, nsvci);
+ if (!target_nsvc) {
+ LOGPFSML(fi, LOGL_ERROR, "Received a %s PDU for unknown NSVC (NSVCI %d)\n",
+ get_value_string(gprs_ns_pdu_strings, nsh->pdu_type), nsvci);
+ if (nsh->pdu_type == NS_PDUT_BLOCK)
+ ns2_tx_status(nsvc, NS_CAUSE_NSVC_UNKNOWN, 0, msg, &nsvci);
+ goto out;
+ }
+
+ if (target_nsvc->nse != nsvc->nse) {
+ LOGPFSML(fi, LOGL_ERROR, "Received a %s PDU for a NSVC (NSVCI %d) but it belongs to a different NSE!\n",
+ get_value_string(gprs_ns_pdu_strings, nsh->pdu_type), nsvci);
+ goto out;
+ }
+
+ /* the status/block will be passed to the nsvc/target nsvc in the switch */
+ } else {
+ LOG_NS_SIGNAL(nsvc, "Rx", nsh->pdu_type, LOGL_ERROR, " with wrong NSVCI=%05u. Ignoring PDU.\n", nsvci);
+ goto out;
+ }
}
}
switch (nsh->pdu_type) {
case NS_PDUT_RESET:
- osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_RESET, tp);
+ osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_RX_RESET, tp);
break;
case NS_PDUT_RESET_ACK:
- osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_RESET_ACK, tp);
+ osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_RX_RESET_ACK, tp);
break;
case NS_PDUT_BLOCK:
- osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_BLOCK, tp);
+ if (target_nsvc != nsvc) {
+ osmo_fsm_inst_dispatch(target_nsvc->fi, GPRS_NS2_EV_RX_BLOCK_FOREIGN, tp);
+ ns2_tx_block_ack(nsvc, &nsvci);
+ } else {
+ osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_RX_BLOCK, tp);
+ }
break;
case NS_PDUT_BLOCK_ACK:
- osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_BLOCK_ACK, tp);
+ osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_RX_BLOCK_ACK, tp);
break;
case NS_PDUT_UNBLOCK:
- osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_UNBLOCK, tp);
+ osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_RX_UNBLOCK, tp);
break;
case NS_PDUT_UNBLOCK_ACK:
- osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_UNBLOCK_ACK, tp);
+ osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_RX_UNBLOCK_ACK, tp);
break;
case NS_PDUT_ALIVE:
- osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_ALIVE, tp);
+ osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_RX_ALIVE, tp);
break;
case NS_PDUT_ALIVE_ACK:
- osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_ALIVE_ACK, tp);
+ osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_RX_ALIVE_ACK, tp);
break;
case NS_PDUT_UNITDATA:
- osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_UNITDATA, msg);
+ /* UNITDATA have to free msg because it might send the msg layer upwards */
+ osmo_fsm_inst_dispatch(fi, GPRS_NS2_EV_RX_UNITDATA, msg);
+ return 0;
+ case NS_PDUT_STATUS:
+ osmo_fsm_inst_dispatch(target_nsvc->fi, GPRS_NS2_EV_RX_STATUS, tp);
break;
default:
- LOGP(DLNS, LOGL_ERROR, "NSEI=%u Rx unknown NS PDU type %s\n", nsvc->nse->nsei,
- get_value_string(gprs_ns_pdu_strings, nsh->pdu_type));
- return -EINVAL;
+ LOGPFSML(fi, LOGL_ERROR, "NSEI=%u Rx unknown NS PDU type %s\n", nsvc->nse->nsei,
+ get_value_string(gprs_ns_pdu_strings, nsh->pdu_type));
+ rc = -EINVAL;
+ break;
}
- return 0;
+out:
+ msgb_free(msg);
+
+ return rc;
}
/*! is the given NS-VC unblocked? */
-int gprs_ns2_vc_is_unblocked(struct gprs_ns2_vc *nsvc)
+int ns2_vc_is_unblocked(struct gprs_ns2_vc *nsvc)
{
return (nsvc->fi->state == GPRS_NS2_ST_UNBLOCKED);
}
@@ -663,5 +988,5 @@ int gprs_ns2_vc_is_unblocked(struct gprs_ns2_vc *nsvc)
/* initialize osmo_ctx on main tread */
static __attribute__((constructor)) void on_dso_load_ctx(void)
{
- OSMO_ASSERT(osmo_fsm_register(&gprs_ns2_vc_fsm) == 0);
+ OSMO_ASSERT(osmo_fsm_register(&ns2_vc_fsm) == 0);
}
diff --git a/src/gb/gprs_ns2_vty.c b/src/gb/gprs_ns2_vty.c
index 81f88b62..3046fffd 100644
--- a/src/gb/gprs_ns2_vty.c
+++ b/src/gb/gprs_ns2_vty.c
@@ -1,10 +1,9 @@
/*! \file gprs_ns2_vty.c
* VTY interface for our GPRS Networks Service (NS) implementation. */
-/* (C) 2009-2014 by Harald Welte <laforge@gnumonks.org>
- * (C) 2016-2017 by sysmocom - s.f.m.c. GmbH
- * (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+/* (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
* Author: Alexander Couzens <lynxis@fe80.eu>
+ * (C) 2021 by Harald Welte <laforge@osmocom.org>
*
* All Rights Reserved
*
@@ -31,61 +30,66 @@
#include <stdint.h>
#include <arpa/inet.h>
+#include <net/if.h>
-#include <osmocom/core/msgb.h>
#include <osmocom/core/byteswap.h>
-#include <osmocom/core/talloc.h>
-#include <osmocom/core/select.h>
+#include <osmocom/core/fsm.h>
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/msgb.h>
#include <osmocom/core/rate_ctr.h>
-#include <osmocom/core/socket.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/talloc.h>
#include <osmocom/core/sockaddr_str.h>
-#include <osmocom/core/linuxlist.h>
#include <osmocom/core/socket.h>
+#include <osmocom/gprs/frame_relay.h>
#include <osmocom/gprs/gprs_ns2.h>
#include <osmocom/gsm/tlv.h>
-#include <osmocom/vty/vty.h>
#include <osmocom/vty/command.h>
#include <osmocom/vty/logging.h>
-#include <osmocom/vty/telnet_interface.h>
#include <osmocom/vty/misc.h>
+#include <osmocom/vty/telnet_interface.h>
+#include <osmocom/vty/vty.h>
#include "gprs_ns2_internal.h"
-struct ns2_vty_priv {
- /* global listen */
- struct osmo_sockaddr_str udp;
- struct osmo_sockaddr_str frgreaddr;
+#define SHOW_NS_STR "Display information about the NS protocol\n"
+#define NSVCI_STR "NS Virtual Connection ID (NS-VCI)\n"
+#define DLCI_STR "Data Link connection identifier\n"
+
+static struct gprs_ns2_inst *vty_nsi = NULL;
+static struct osmo_fr_network *vty_fr_network = NULL;
+static struct llist_head binds;
+static struct llist_head nses;
+static struct llist_head ip_sns_default_binds;
+
+struct vty_bind {
+ struct llist_head list;
+ const char *name;
+ enum gprs_ns2_ll ll;
int dscp;
- enum gprs_ns2_vc_mode vc_mode;
- /* force vc mode if another configuration forces
- * the vc mode. E.g. SNS configuration */
- bool force_vc_mode;
- char *force_vc_mode_reason;
- bool frgre;
-
- struct llist_head vtyvc;
+ uint8_t priority;
+ bool accept_ipaccess;
+ bool accept_sns;
+ uint8_t ip_sns_sig_weight;
+ uint8_t ip_sns_data_weight;
};
-struct ns2_vty_vc {
+struct vty_nse {
struct llist_head list;
-
- struct osmo_sockaddr_str remote;
- enum gprs_ns_ll ll;
-
- /* old vty code doesnt support multiple NSVCI per NSEI */
uint16_t nsei;
- uint16_t nsvci;
- uint16_t frdlci;
-
- bool remote_end_is_sgsn;
- bool configured;
+ /* list of binds which are valid for this nse. Only IP-SNS uses this
+ * to allow `no listen ..` in the bind context. So "half" created binds are valid for
+ * IP-SNS. This allows changing the bind ip without modifying all NSEs afterwards */
+ struct llist_head binds;
};
-static struct gprs_ns2_inst *vty_nsi = NULL;
-static struct ns2_vty_priv priv;
+/* used by IP-SNS to connect multiple vty_nse_bind to a vty_nse */
+struct vty_nse_bind {
+ struct llist_head list;
+ struct vty_bind *vbind;
+};
-/* FIXME: this should go to some common file as it is copied
- * in vty_interface.c of the BSC */
+/* TODO: this should into osmo timer */
static const struct value_string gprs_ns_timer_strs[] = {
{ 0, "tns-block" },
{ 1, "tns-block-retries" },
@@ -95,19 +99,149 @@ static const struct value_string gprs_ns_timer_strs[] = {
{ 5, "tns-alive" },
{ 6, "tns-alive-retries" },
{ 7, "tsns-prov" },
+ { 8, "tsns-size-retries" },
+ { 9, "tsns-config-retries" },
+ {10, "tsns-procedures-retries" },
{ 0, NULL }
};
-static void log_set_nsvc_filter(struct log_target *target,
- struct gprs_ns2_vc *nsvc)
+const struct value_string vty_fr_role_names[] = {
+ { FR_ROLE_USER_EQUIPMENT, "fr" },
+ { FR_ROLE_NETWORK_EQUIPMENT, "frnet" },
+ { 0, NULL }
+};
+
+const struct value_string vty_ll_names[] = {
+ { GPRS_NS2_LL_FR, "fr" },
+ { GPRS_NS2_LL_FR_GRE, "frgre" },
+ { GPRS_NS2_LL_UDP, "udp" },
+ { 0, NULL }
+};
+
+static struct vty_bind *vty_bind_by_name(const char *name)
{
- if (nsvc) {
- target->filter_map |= (1 << LOG_FLT_GB_NSVC);
- target->filter_data[LOG_FLT_GB_NSVC] = nsvc;
- } else if (target->filter_data[LOG_FLT_GB_NSVC]) {
- target->filter_map = ~(1 << LOG_FLT_GB_NSVC);
- target->filter_data[LOG_FLT_GB_NSVC] = NULL;
+ struct vty_bind *vbind;
+ llist_for_each_entry(vbind, &binds, list) {
+ if (!strcmp(vbind->name, name))
+ return vbind;
+ }
+ return NULL;
+}
+
+static struct vty_bind *vty_bind_alloc(const char *name)
+{
+ struct vty_bind *vbind = talloc_zero(vty_nsi, struct vty_bind);
+ if (!vbind)
+ return NULL;
+
+ vbind->name = talloc_strdup(vty_nsi, name);
+ if (!vbind->name) {
+ talloc_free(vbind);
+ return NULL;
+ }
+
+ vbind->ip_sns_sig_weight = 1;
+ vbind->ip_sns_data_weight = 1;
+ llist_add_tail(&vbind->list, &binds);
+ return vbind;
+}
+
+static void vty_bind_free(struct vty_bind *vbind)
+{
+ if (!vbind)
+ return;
+
+ llist_del(&vbind->list);
+ talloc_free(vbind);
+}
+
+static struct vty_nse *vty_nse_by_nsei(uint16_t nsei)
+{
+ struct vty_nse *vnse;
+ llist_for_each_entry(vnse, &nses, list) {
+ if (vnse->nsei == nsei)
+ return vnse;
+ }
+ return NULL;
+}
+
+static struct vty_nse *vty_nse_alloc(uint16_t nsei)
+{
+ struct vty_nse *vnse = talloc_zero(vty_nsi, struct vty_nse);
+ if (!vnse)
+ return NULL;
+
+ vnse->nsei = nsei;
+ INIT_LLIST_HEAD(&vnse->binds);
+ llist_add_tail(&vnse->list, &nses);
+ return vnse;
+}
+
+static void vty_nse_free(struct vty_nse *vnse)
+{
+ if (!vnse)
+ return;
+
+ llist_del(&vnse->list);
+ /* all vbind of the nse will be freed by talloc */
+ talloc_free(vnse);
+}
+
+static int vty_nse_add_vbind(struct vty_nse *vnse, struct vty_bind *vbind)
+{
+ struct vty_nse_bind *vnse_bind;
+
+ if (vbind->ll != GPRS_NS2_LL_UDP)
+ return -EINVAL;
+
+ llist_for_each_entry(vnse_bind, &vnse->binds, list) {
+ if (vnse_bind->vbind == vbind)
+ return -EALREADY;
}
+
+ vnse_bind = talloc(vnse, struct vty_nse_bind);
+ if (!vnse_bind)
+ return -ENOMEM;
+ vnse_bind->vbind = vbind;
+
+ llist_add_tail(&vnse_bind->list, &vnse->binds);
+ return 0;
+}
+
+static int vty_nse_remove_vbind(struct vty_nse *vnse, struct vty_bind *vbind)
+{
+ struct vty_nse_bind *vnse_bind, *tmp;
+ if (vbind->ll != GPRS_NS2_LL_UDP)
+ return -EINVAL;
+
+ llist_for_each_entry_safe(vnse_bind, tmp, &vnse->binds, list) {
+ if (vnse_bind->vbind == vbind) {
+ llist_del(&vnse_bind->list);
+ talloc_free(vnse_bind);
+ return 0;
+ }
+ }
+
+ return -ENOENT;
+}
+
+/* check if the NSE still has SNS configuration */
+static bool vty_nse_check_sns(struct gprs_ns2_nse *nse) {
+ struct vty_nse *vnse = vty_nse_by_nsei(nse->nsei);
+
+ int count = gprs_ns2_sns_count(nse);
+ if (count > 0) {
+ /* there are other sns endpoints */
+ return true;
+ }
+
+ if (!vnse)
+ return false;
+
+ if (llist_empty(&vnse->binds))
+ return false;
+
+ return true;
}
static struct cmd_node ns_node = {
@@ -116,545 +250,1962 @@ static struct cmd_node ns_node = {
1,
};
-static struct ns2_vty_vc *vtyvc_alloc(uint16_t nsei) {
- struct ns2_vty_vc *vtyvc = talloc_zero(vty_nsi, struct ns2_vty_vc);
- if (!vtyvc)
- return vtyvc;
-
- vtyvc->nsei = nsei;
+DEFUN(cfg_ns, cfg_ns_cmd,
+ "ns",
+ "Configure the GPRS Network Service")
+{
+ vty->node = L_NS_NODE;
+ return CMD_SUCCESS;
+}
- llist_add(&vtyvc->list, &priv.vtyvc);
+DEFUN(cfg_ns_timer, cfg_ns_timer_cmd,
+ "timer " NS_TIMERS " <0-65535>",
+ "Network Service Timer\n"
+ NS_TIMERS_HELP "Timer Value\n")
+{
+ int idx = get_string_value(gprs_ns_timer_strs, argv[0]);
+ int val = atoi(argv[1]);
- return vtyvc;
-}
+ if (idx < 0 || idx >= ARRAY_SIZE(vty_nsi->timeout))
+ return CMD_WARNING;
-static void ns2_vc_free(struct ns2_vty_vc *vtyvc) {
- if (!vtyvc)
- return;
+ vty_nsi->timeout[idx] = val;
- llist_del(&vtyvc->list);
- talloc_free(vtyvc);
+ return CMD_SUCCESS;
}
-static struct ns2_vty_vc *vtyvc_by_nsei(uint16_t nsei, bool alloc_missing) {
- struct ns2_vty_vc *vtyvc;
- llist_for_each_entry(vtyvc, &priv.vtyvc, list) {
- if (vtyvc->nsei == nsei)
- return vtyvc;
+DEFUN(cfg_ns_nsei, cfg_ns_nsei_cmd,
+ "nse <0-65535> [ip-sns-role-sgsn]",
+ "Persistent NS Entity\n"
+ "NS Entity ID (NSEI)\n"
+ "Create NSE in SGSN role (default: BSS)\n"
+ )
+{
+ struct gprs_ns2_nse *nse;
+ struct vty_nse *vnse;
+ uint16_t nsei = atoi(argv[0]);
+ bool sgsn_role = false;
+ bool free_vnse = false;
+ if (argc > 1 && !strcmp(argv[1], "ip-sns-role-sgsn"))
+ sgsn_role = true;
+
+ vnse = vty_nse_by_nsei(nsei);
+ if (!vnse) {
+ vnse = vty_nse_alloc(nsei);
+ if (!vnse) {
+ vty_out(vty, "Failed to create vty NSE!%s", VTY_NEWLINE);
+ return CMD_ERR_INCOMPLETE;
+ }
+ free_vnse = true;
}
- if (alloc_missing) {
- vtyvc = vtyvc_alloc(nsei);
- if (!vtyvc)
- return vtyvc;
+ nse = gprs_ns2_nse_by_nsei(vty_nsi, nsei);
+ if (!nse) {
+ nse = gprs_ns2_create_nse2(vty_nsi, nsei, GPRS_NS2_LL_UNDEF, GPRS_NS2_DIALECT_UNDEF,
+ sgsn_role);
+ if (!nse) {
+ vty_out(vty, "Failed to create NSE!%s", VTY_NEWLINE);
+ goto err;
+ }
+ nse->persistent = true;
+ }
- vtyvc->nsei = nsei;
+ if (!nse->persistent) {
+ /* TODO: should the dynamic NSE removed? */
+ vty_out(vty, "A dynamic NSE with the specified NSEI already exists%s", VTY_NEWLINE);
+ goto err;
}
- return NULL;
+ vty->node = L_NS_NSE_NODE;
+ vty->index = nse;
+
+ return CMD_SUCCESS;
+
+err:
+ if (free_vnse)
+ talloc_free(vnse);
+
+ return CMD_ERR_INCOMPLETE;
}
-static int config_write_ns(struct vty *vty)
+DEFUN(cfg_no_ns_nsei, cfg_no_ns_nsei_cmd,
+ "no nse <0-65535>",
+ NO_STR
+ "Delete a Persistent NS Entity\n"
+ "NS Entity ID (NSEI)\n"
+ )
{
- struct ns2_vty_vc *vtyvc;
- unsigned int i;
- struct osmo_sockaddr_str sockstr;
+ struct gprs_ns2_nse *nse;
+ struct vty_nse *vnse;
+ uint16_t nsei = atoi(argv[0]);
- vty_out(vty, "ns%s", VTY_NEWLINE);
+ nse = gprs_ns2_nse_by_nsei(vty_nsi, nsei);
+ if (!nse) {
+ vty_out(vty, "Can not find NS Entity %s%s", argv[0], VTY_NEWLINE);
+ return CMD_ERR_NOTHING_TODO;
+ }
+
+ if (!nse->persistent) {
+ vty_out(vty, "Ignoring non-persistent NS Entity%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
- /* global configuration must be written first, as some of it may be
- * relevant when creating the NSE/NSVC later below */
+ vty_out(vty, "Deleting NS Entity %u%s", nse->nsei, VTY_NEWLINE);
+ gprs_ns2_free_nse(nse);
- vty_out(vty, " encapsulation framerelay-gre enabled %u%s",
- priv.frgre ? 1 : 0, VTY_NEWLINE);
+ vnse = vty_nse_by_nsei(nsei);
+ vty_nse_free(vnse);
+
+ return CMD_SUCCESS;
+}
+
+/* TODO: add fr/gre */
+DEFUN(cfg_ns_bind, cfg_ns_bind_cmd,
+ "bind (fr|udp) ID",
+ "Configure local Bind\n"
+ "Frame Relay\n" "UDP/IP\n"
+ "Unique identifier for this bind (to reference from NS-VCs, NSEs, ...)\n"
+ )
+{
+ const char *nstype = argv[0];
+ const char *name = argv[1];
+ struct vty_bind *vbind;
+ enum gprs_ns2_ll ll;
+ int rc;
+
+ rc = get_string_value(vty_ll_names, nstype);
+ if (rc < 0)
+ return CMD_WARNING;
+ ll = (enum gprs_ns2_ll) rc;
+
+ if (!osmo_identifier_valid(name)) {
+ vty_out(vty, "Invalid ID. The ID should be only alphanumeric.%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
- if (priv.frgre) {
- if (strlen(priv.frgreaddr.ip)) {
- vty_out(vty, " encapsulation framerelay-gre local-ip %s%s",
- sockstr.ip, VTY_NEWLINE);
+ vbind = vty_bind_by_name(name);
+ if (vbind) {
+ if (vbind->ll != ll) {
+ vty_out(vty, "A bind with the specified ID already exists with a different type (fr|frgre|udp)!%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
}
} else {
- if (strlen(priv.udp.ip)) {
- vty_out(vty, " encapsulation udp local-ip %s%s",
- priv.udp.ip, VTY_NEWLINE);
+ vbind = vty_bind_alloc(name);
+ if (!vbind) {
+ vty_out(vty, "Can not create bind - out of memory%s", VTY_NEWLINE);
+ return CMD_WARNING;
}
+ vbind->ll = ll;
+ }
+
+ vty->index = vbind;
+ vty->node = L_NS_BIND_NODE;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_no_ns_bind, cfg_no_ns_bind_cmd,
+ "no bind ID",
+ NO_STR
+ "Delete a bind\n"
+ "Unique identifier for this bind\n"
+ )
+{
+ struct vty_bind *vbind;
+ struct gprs_ns2_vc_bind *bind;
+ const char *name = argv[0];
- if (priv.udp.port)
- vty_out(vty, " encapsulation udp local-port %u%s",
- priv.udp.port, VTY_NEWLINE);
+ vbind = vty_bind_by_name(name);
+ if (!vbind) {
+ vty_out(vty, "bind %s does not exist!%s", name, VTY_NEWLINE);
+ return CMD_WARNING;
}
+ vty_bind_free(vbind);
+ bind = gprs_ns2_bind_by_name(vty_nsi, name);
+ if (bind)
+ gprs_ns2_free_bind(bind);
+ return CMD_SUCCESS;
+}
- if (priv.dscp)
- vty_out(vty, " encapsulation udp dscp %d%s",
- priv.dscp, VTY_NEWLINE);
- vty_out(vty, " encapsulation udp use-reset-block-unblock %s%s",
- priv.vc_mode == NS2_VC_MODE_BLOCKRESET ? "enabled" : "disabled", VTY_NEWLINE);
+static void config_write_vbind(struct vty *vty, struct vty_bind *vbind)
+{
+ struct gprs_ns2_vc_bind *bind;
+ const struct osmo_sockaddr *addr;
+ struct osmo_sockaddr_str addr_str;
+ const char *netif, *frrole_str, *llstr;
+ enum osmo_fr_role frrole;
- llist_for_each_entry(vtyvc, &priv.vtyvc, list) {
- vty_out(vty, " nse %u nsvci %u%s",
- vtyvc->nsei, vtyvc->nsvci, VTY_NEWLINE);
+ llstr = get_value_string_or_null(vty_ll_names, vbind->ll);
+ if (!llstr)
+ return;
+ vty_out(vty, " bind %s %s%s", llstr, vbind->name, VTY_NEWLINE);
- vty_out(vty, " nse %u remote-role %s%s",
- vtyvc->nsei, vtyvc->remote_end_is_sgsn ? "sgsn" : "bss",
- VTY_NEWLINE);
+ bind = gprs_ns2_bind_by_name(vty_nsi, vbind->name);
+ switch (vbind->ll) {
+ case GPRS_NS2_LL_FR:
+ if (bind) {
+ netif = gprs_ns2_fr_bind_netif(bind);
+ if (!netif)
+ return;
+ frrole = gprs_ns2_fr_bind_role(bind);
+ if ((int) frrole == -1)
+ return;
+ frrole_str = get_value_string_or_null(vty_fr_role_names, frrole);
+ if (netif && frrole_str)
+ vty_out(vty, " fr %s %s%s", netif, frrole_str, VTY_NEWLINE);
+ }
+ break;
+ case GPRS_NS2_LL_UDP:
+ if (bind) {
+ addr = gprs_ns2_ip_bind_sockaddr(bind);
+ if (!osmo_sockaddr_str_from_sockaddr(&addr_str, &addr->u.sas)) {
+ vty_out(vty, " listen %s %u%s", addr_str.ip, addr_str.port,
+ VTY_NEWLINE);
+ }
+ }
+ if (vbind->accept_ipaccess)
+ vty_out(vty, " accept-ipaccess%s", VTY_NEWLINE);
+ if (vbind->accept_sns)
+ vty_out(vty, " accept-dynamic-ip-sns%s", VTY_NEWLINE);
+ if (vbind->dscp)
+ vty_out(vty, " dscp %u%s", vbind->dscp, VTY_NEWLINE);
+ if (vbind->priority)
+ vty_out(vty, " socket-priority %u%s", vbind->priority, VTY_NEWLINE);
+ vty_out(vty, " ip-sns signalling-weight %u data-weight %u%s",
+ vbind->ip_sns_sig_weight, vbind->ip_sns_data_weight, VTY_NEWLINE);
+ break;
+ default:
+ return;
+ }
+}
- switch (vtyvc->ll) {
- case GPRS_NS_LL_UDP:
- vty_out(vty, " nse %u encapsulation udp%s", vtyvc->nsei, VTY_NEWLINE);
- vty_out(vty, " nse %u remote-ip %s%s",
- vtyvc->nsei,
- vtyvc->remote.ip,
- VTY_NEWLINE);
- vty_out(vty, " nse %u remote-port %u%s",
- vtyvc->nsei, vtyvc->remote.port,
- VTY_NEWLINE);
+static void config_write_nsvc(struct vty *vty, const struct gprs_ns2_vc *nsvc)
+{
+ const char *netif;
+ uint16_t dlci;
+ const struct osmo_sockaddr *addr;
+ struct osmo_sockaddr_str addr_str;
+
+ switch (nsvc->nse->ll) {
+ case GPRS_NS2_LL_UNDEF:
+ break;
+ case GPRS_NS2_LL_UDP:
+ switch (nsvc->nse->dialect) {
+ case GPRS_NS2_DIALECT_IPACCESS:
+ addr = gprs_ns2_ip_vc_remote(nsvc);
+ if (!addr)
+ break;
+ if (osmo_sockaddr_str_from_sockaddr(&addr_str, &addr->u.sas))
+ break;
+ vty_out(vty, " nsvc ipa %s %s %u nsvci %u%s",
+ nsvc->bind->name, addr_str.ip, addr_str.port,
+ nsvc->nsvci, VTY_NEWLINE);
break;
- case GPRS_NS_LL_FR_GRE:
- vty_out(vty, " nse %u encapsulation framerelay-gre%s",
- vtyvc->nsei, VTY_NEWLINE);
- vty_out(vty, " nse %u remote-ip %s%s",
- vtyvc->nsei,
- vtyvc->remote.ip,
- VTY_NEWLINE);
- vty_out(vty, " nse %u fr-dlci %u%s",
- vtyvc->nsei, vtyvc->frdlci,
- VTY_NEWLINE);
+ case GPRS_NS2_DIALECT_STATIC_ALIVE:
+ addr = gprs_ns2_ip_vc_remote(nsvc);
+ if (!addr)
+ break;
+ if (osmo_sockaddr_str_from_sockaddr(&addr_str, &addr->u.sas))
+ break;
+ vty_out(vty, " nsvc udp %s %s %u%s",
+ nsvc->bind->name, addr_str.ip, addr_str.port, VTY_NEWLINE);
break;
default:
break;
}
+ break;
+ case GPRS_NS2_LL_FR:
+ netif = gprs_ns2_fr_bind_netif(nsvc->bind);
+ if (!netif)
+ break;
+ dlci = gprs_ns2_fr_nsvc_dlci(nsvc);
+ if (!dlci)
+ break;
+ OSMO_ASSERT(nsvc->nsvci_is_valid);
+ vty_out(vty, " nsvc fr %s dlci %u nsvci %u%s",
+ netif, dlci, nsvc->nsvci, VTY_NEWLINE);
+ break;
+ case GPRS_NS2_LL_FR_GRE:
+ break;
+ }
+}
+
+static void _config_write_ns_nse(struct vty *vty, struct gprs_ns2_nse *nse)
+{
+ struct gprs_ns2_vc *nsvc;
+ struct vty_nse *vnse = vty_nse_by_nsei(nse->nsei);
+ struct vty_nse_bind *vbind;
+
+ OSMO_ASSERT(vnse);
+
+ vty_out(vty, " nse %u%s%s", nse->nsei,
+ nse->ip_sns_role_sgsn ? " ip-sns-role-sgsn" : "", VTY_NEWLINE);
+ switch (nse->dialect) {
+ case GPRS_NS2_DIALECT_SNS:
+ ns2_sns_write_vty(vty, nse);
+ llist_for_each_entry(vbind, &vnse->binds, list) {
+ vty_out(vty, " ip-sns-bind %s%s", vbind->vbind->name, VTY_NEWLINE);
+ }
+ break;
+ default:
+ llist_for_each_entry(nsvc, &nse->nsvc, list) {
+ config_write_nsvc(vty, nsvc);
+ }
+ break;
+ }
+}
+
+static int config_write_ns_nse(struct vty *vty)
+{
+ struct gprs_ns2_nse *nse;
+
+ llist_for_each_entry(nse, &vty_nsi->nse, list) {
+ if (!nse->persistent)
+ continue;
+
+ _config_write_ns_nse(vty, nse);
+ }
+
+ return 0;
+}
+
+static int config_write_ns_bind(struct vty *vty)
+{
+ struct vty_bind *vbind;
+
+ llist_for_each_entry(vbind, &binds, list) {
+ config_write_vbind(vty, vbind);
}
+ return 0;
+}
+
+static int config_write_ns(struct vty *vty)
+{
+ struct vty_nse_bind *vbind;
+ unsigned int i;
+ int ret;
+
+ vty_out(vty, "ns%s", VTY_NEWLINE);
+
for (i = 0; i < ARRAY_SIZE(vty_nsi->timeout); i++)
vty_out(vty, " timer %s %u%s",
get_value_string(gprs_ns_timer_strs, i),
vty_nsi->timeout[i], VTY_NEWLINE);
+ ret = config_write_ns_bind(vty);
+ if (ret)
+ return ret;
+
+ llist_for_each_entry(vbind, &ip_sns_default_binds, list) {
+ vty_out(vty, " ip-sns-default bind %s%s", vbind->vbind->name, VTY_NEWLINE);
+ }
+
+ ret = config_write_ns_nse(vty);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+
+static struct cmd_node ns_bind_node = {
+ L_NS_BIND_NODE,
+ "%s(config-ns-bind)# ",
+ 1,
+};
+
+DEFUN(cfg_ns_bind_listen, cfg_ns_bind_listen_cmd,
+ "listen " VTY_IPV46_CMD " <1-65535>",
+ "Configure local IP + Port of this bind\n"
+ "Local IPv4 Address\n" "Local IPv6 Address\n"
+ "Local UDP Port\n"
+ )
+{
+ struct vty_bind *vbind = vty->index;
+ struct gprs_ns2_vc_bind *bind;
+ int rc;
+ const char *addr_str = argv[0];
+ unsigned int port = atoi(argv[1]);
+ struct osmo_sockaddr_str sockaddr_str;
+ struct osmo_sockaddr sockaddr;
+
+ if (vbind->ll != GPRS_NS2_LL_UDP) {
+ vty_out(vty, "listen can be only used with UDP bind%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (osmo_sockaddr_str_from_str(&sockaddr_str, addr_str, port)) {
+ vty_out(vty, "Can not parse the Address %s %s%s", argv[0], argv[1], VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ osmo_sockaddr_str_to_sockaddr(&sockaddr_str, &sockaddr.u.sas);
+ if (gprs_ns2_ip_bind_by_sockaddr(vty_nsi, &sockaddr)) {
+ vty_out(vty, "A bind with the specified address already exists!%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ rc = gprs_ns2_ip_bind(vty_nsi, vbind->name, &sockaddr, vbind->dscp, &bind);
+ if (rc != 0) {
+ vty_out(vty, "Failed to create the bind (rc %d)!%s", rc, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ bind->accept_ipaccess = vbind->accept_ipaccess;
+ bind->accept_sns = vbind->accept_sns;
+
return CMD_SUCCESS;
}
-DEFUN(cfg_ns, cfg_ns_cmd,
- "ns",
- "Configure the GPRS Network Service")
+DEFUN(cfg_no_ns_bind_listen, cfg_no_ns_bind_listen_cmd,
+ "no listen",
+ NO_STR
+ "Delete a IP/Port assignment\n"
+ )
{
- vty->node = L_NS_NODE;
+ struct vty_bind *vbind = vty->index;
+ struct gprs_ns2_vc_bind *bind;
+
+ if (vbind->ll != GPRS_NS2_LL_UDP) {
+ vty_out(vty, "no listen can be only used with UDP bind%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ bind = gprs_ns2_bind_by_name(vty_nsi, vbind->name);
+ if (!bind)
+ return CMD_ERR_NOTHING_TODO;
+
+ OSMO_ASSERT(bind->ll == GPRS_NS2_LL_UDP);
+ gprs_ns2_free_bind(bind);
return CMD_SUCCESS;
}
-static void dump_nsvc(struct vty *vty, struct gprs_ns2_vc *nsvc, bool stats)
+DEFUN(cfg_ns_bind_dscp, cfg_ns_bind_dscp_cmd,
+ "dscp <0-63>",
+ "Set DSCP/TOS on the UDP socket\n" "DSCP Value\n")
{
- struct osmo_sockaddr_str remote;
- struct osmo_sockaddr_str local;
- struct osmo_sockaddr *sockaddr;
+ struct vty_bind *vbind = vty->index;
+ struct gprs_ns2_vc_bind *bind;
+ uint16_t dscp = atoi(argv[0]);
- switch (nsvc->ll) {
- case GPRS_NS_LL_UDP: {
- sockaddr = gprs_ns2_ip_vc_sockaddr(nsvc);
- if (!sockaddr) {
- vty_out(vty, "unknown");
- break;
- }
+ if (vbind->ll != GPRS_NS2_LL_UDP) {
+ vty_out(vty, "dscp can be only used with UDP bind%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
- if (osmo_sockaddr_str_from_sockaddr(
- &remote,
- &sockaddr->u.sas)) {
- vty_out(vty, "unknown");
- break;
- }
+ vbind->dscp = dscp;
+ bind = gprs_ns2_bind_by_name(vty_nsi, vbind->name);
+ if (bind)
+ gprs_ns2_ip_bind_set_dscp(bind, dscp);
- vty_out(vty, "%s:%u <> %s:%u", local.ip, local.port, remote.ip, remote.port);
- break;
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_no_ns_bind_dscp, cfg_no_ns_bind_dscp_cmd,
+ "no dscp",
+ "Set DSCP/TOS on the UDP socket\n" "DSCP Value\n")
+{
+ struct vty_bind *vbind = vty->index;
+ struct gprs_ns2_vc_bind *bind;
+ uint16_t dscp = 0;
+
+ if (vbind->ll != GPRS_NS2_LL_UDP) {
+ vty_out(vty, "dscp can be only used with UDP bind%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
}
- case GPRS_NS_LL_FR_GRE:
- /* TODO: implement dump_nse for FR GRE */
- case GPRS_NS_LL_E1:
- /* TODO: implement dump_nse for E1 */
- break;
+
+ vbind->dscp = dscp;
+ bind = gprs_ns2_bind_by_name(vty_nsi, vbind->name);
+ if (bind)
+ gprs_ns2_ip_bind_set_dscp(bind, dscp);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_ns_bind_priority, cfg_ns_bind_priority_cmd,
+ "socket-priority <0-255>",
+ "Set socket priority on the UDP socket\n" "Priority Value (>6 requires CAP_NET_ADMIN)\n")
+{
+ struct vty_bind *vbind = vty->index;
+ struct gprs_ns2_vc_bind *bind;
+ uint8_t prio = atoi(argv[0]);
+
+ if (vbind->ll != GPRS_NS2_LL_UDP) {
+ vty_out(vty, "dscp can be only used with UDP bind%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
}
- vty_out(vty, "Remote: %s ",
- gprs_ns2_ll_str(nsvc));
+ vbind->priority = prio;
+ bind = gprs_ns2_bind_by_name(vty_nsi, vbind->name);
+ if (bind)
+ gprs_ns2_ip_bind_set_priority(bind, prio);
- vty_out(vty, "%s%s", nsvc->ll == GPRS_NS_LL_UDP ? "UDP" : "FR-GRE", VTY_NEWLINE);
+ return CMD_SUCCESS;
+}
- if (stats) {
- vty_out_rate_ctr_group(vty, " ", nsvc->ctrg);
- vty_out_stat_item_group(vty, " ", nsvc->statg);
+DEFUN(cfg_ns_bind_ipaccess, cfg_ns_bind_ipaccess_cmd,
+ "accept-ipaccess",
+ "Allow to create dynamic NS Entity by NS Reset PDU on UDP (ip.access style)\n"
+ )
+{
+ struct vty_bind *vbind = vty->index;
+ struct gprs_ns2_vc_bind *bind;
+
+ if (vbind->ll != GPRS_NS2_LL_UDP) {
+ vty_out(vty, "accept-ipaccess can be only used with UDP bind%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
}
+
+ vbind->accept_ipaccess = true;
+ bind = gprs_ns2_bind_by_name(vty_nsi, vbind->name);
+ if (bind)
+ bind->accept_ipaccess = true;
+
+ return CMD_SUCCESS;
}
-static void dump_nse(struct vty *vty, const struct gprs_ns2_nse *nse, bool stats, bool persistent_only)
+DEFUN(cfg_no_ns_bind_ipaccess, cfg_no_ns_bind_ipaccess_cmd,
+ "no accept-ipaccess",
+ NO_STR
+ "Reject NS Reset PDU on UDP (ip.access style)\n"
+ )
{
- struct gprs_ns2_vc *nsvc;
+ struct vty_bind *vbind = vty->index;
+ struct gprs_ns2_vc_bind *bind;
- vty_out(vty, "NSEI %5u%s",
- nse->nsei, VTY_NEWLINE);
+ if (vbind->ll != GPRS_NS2_LL_UDP) {
+ vty_out(vty, "no accept-ipaccess can be only used with UDP bind%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
- gprs_ns2_sns_dump_vty(vty, nse, stats);
- llist_for_each_entry(nsvc, &nse->nsvc, list) {
- if (persistent_only) {
- if (nsvc->persistent)
- dump_nsvc(vty, nsvc, stats);
- } else {
- dump_nsvc(vty, nsvc, stats);
- }
+ vbind->accept_ipaccess = false;
+ bind = gprs_ns2_bind_by_name(vty_nsi, vbind->name);
+ if (bind)
+ bind->accept_ipaccess = false;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_ns_bind_accept_sns, cfg_ns_bind_accept_sns_cmd,
+ "accept-dynamic-ip-sns",
+ "Allow to create dynamic NS Entities by IP-SNS PDUs\n"
+ )
+{
+ struct vty_bind *vbind = vty->index;
+ struct gprs_ns2_vc_bind *bind;
+
+ if (vbind->ll != GPRS_NS2_LL_UDP) {
+ vty_out(vty, "accept-dynamic-ip-sns can be only used with UDP bind%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
}
+
+ vbind->accept_sns = true;
+ bind = gprs_ns2_bind_by_name(vty_nsi, vbind->name);
+ if (bind)
+ bind->accept_sns = true;
+
+ return CMD_SUCCESS;
}
-static void dump_ns(struct vty *vty, const struct gprs_ns2_inst *nsi, bool stats, bool persistent_only)
+DEFUN(cfg_no_ns_bind_accept_sns, cfg_no_ns_bind_accept_sns_cmd,
+ "no accept-dynamic-ip-sns",
+ NO_STR
+ "Disable dynamic creation of NS Entities by IP-SNS PDUs\n"
+ )
{
- struct gprs_ns2_nse *nse;
+ struct vty_bind *vbind = vty->index;
+ struct gprs_ns2_vc_bind *bind;
- llist_for_each_entry(nse, &nsi->nse, list) {
- dump_nse(vty, nse, stats, persistent_only);
- break;
+ if (vbind->ll != GPRS_NS2_LL_UDP) {
+ vty_out(vty, "no accept-dynamic-ip-sns can be only used with UDP bind%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
}
+ vbind->accept_sns = false;
+ bind = gprs_ns2_bind_by_name(vty_nsi, vbind->name);
+ if (bind)
+ bind->accept_sns = false;
+
+ return CMD_SUCCESS;
}
-DEFUN(show_ns, show_ns_cmd, "show ns",
- SHOW_STR "Display information about the NS protocol")
+DEFUN(cfg_ns_bind_ip_sns_weight, cfg_ns_bind_ip_sns_weight_cmd,
+ "ip-sns signalling-weight <0-254> data-weight <0-254>",
+ "IP SNS\n"
+ "signalling weight used by IP-SNS dynamic configuration\n"
+ "signalling weight used by IP-SNS dynamic configuration\n"
+ "data weight used by IP-SNS dynamic configuration\n"
+ "data weight used by IP-SNS dynamic configuration\n")
{
- dump_ns(vty, vty_nsi, false, false);
+ struct vty_bind *vbind = vty->index;
+ struct gprs_ns2_vc_bind *bind;
+
+ int signalling = atoi(argv[0]);
+ int data = atoi(argv[1]);
+
+ if (vbind->ll != GPRS_NS2_LL_UDP) {
+ vty_out(vty, "ip-sns signalling-weight <0-254> data-weight <0-254> can be only used with UDP bind%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ vbind->ip_sns_data_weight = data;
+ vbind->ip_sns_sig_weight = signalling;
+ bind = gprs_ns2_bind_by_name(vty_nsi, vbind->name);
+ if (bind)
+ gprs_ns2_ip_bind_set_sns_weight(bind, signalling, data);
+
return CMD_SUCCESS;
}
-DEFUN(show_ns_stats, show_ns_stats_cmd, "show ns stats",
- SHOW_STR
- "Display information about the NS protocol\n"
- "Include statistics\n")
+DEFUN(cfg_ns_bind_fr, cfg_ns_bind_fr_cmd,
+ "fr NETIF (fr|frnet)",
+ "frame relay\n"
+ IFNAME_STR
+ "fr (user) is used by BSS or SGSN attached to UNI of a FR network\n"
+ "frnet (network) is used by SGSN if BSS is directly attached\n"
+ )
{
- dump_ns(vty, vty_nsi, true, false);
+ struct vty_bind *vbind = vty->index;
+ struct gprs_ns2_vc_bind *bind;
+ const char *netif = argv[0];
+ const char *role = argv[1];
+
+ int rc = 0;
+ enum osmo_fr_role frrole;
+
+ if (vbind->ll != GPRS_NS2_LL_FR) {
+ vty_out(vty, "fr can be only used with frame relay bind%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (!strcmp(role, "fr"))
+ frrole = FR_ROLE_USER_EQUIPMENT;
+ else if (!strcmp(role, "frnet"))
+ frrole = FR_ROLE_NETWORK_EQUIPMENT;
+ else
+ return CMD_WARNING;
+
+ bind = gprs_ns2_fr_bind_by_netif(vty_nsi, netif);
+ if (bind) {
+ vty_out(vty, "Interface %s already used.%s", netif, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ rc = gprs_ns2_fr_bind(vty_nsi, vbind->name, netif, vty_fr_network, frrole, &bind);
+ if (rc < 0) {
+ LOGP(DLNS, LOGL_ERROR, "Failed to bind interface %s on fr. Err: %d\n", netif, rc);
+ return CMD_WARNING;
+ }
+
return CMD_SUCCESS;
}
-DEFUN(show_ns_pers, show_ns_pers_cmd, "show ns persistent",
- SHOW_STR
- "Display information about the NS protocol\n"
- "Show only persistent NS\n")
+DEFUN(cfg_no_ns_bind_fr, cfg_no_ns_bind_fr_cmd,
+ "no fr NETIF",
+ NO_STR
+ "Delete a frame relay link\n"
+ "Delete a frame relay link\n"
+ IFNAME_STR
+ )
{
- dump_ns(vty, vty_nsi, true, true);
+ struct vty_bind *vbind = vty->index;
+ struct gprs_ns2_vc_bind *bind;
+ const char *netif = argv[0];
+
+ if (vbind->ll != GPRS_NS2_LL_FR) {
+ vty_out(vty, "fr can be only used with frame relay bind%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ bind = gprs_ns2_fr_bind_by_netif(vty_nsi, netif);
+ if (!bind) {
+ vty_out(vty, "Interface not found.%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (strcmp(bind->name, vbind->name)) {
+ vty_out(vty, "The specified interface is not bound to this bind.%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ gprs_ns2_free_bind(bind);
return CMD_SUCCESS;
}
-DEFUN(show_nse, show_nse_cmd, "show ns (nsei|nsvc) <0-65535> [stats]",
- SHOW_STR "Display information about the NS protocol\n"
- "Select one NSE by its NSE Identifier\n"
- "Select one NSE by its NS-VC Identifier\n"
- "The Identifier of selected type\n"
- "Include Statistics\n")
+
+static struct cmd_node ns_nse_node = {
+ L_NS_NSE_NODE,
+ "%s(config-ns-nse)# ",
+ 1,
+};
+
+DEFUN(cfg_ns_nse_nsvc_fr, cfg_ns_nse_nsvc_fr_cmd,
+ "nsvc fr NETIF dlci <16-1007> nsvci <0-65535>",
+ "NS Virtual Connection\n"
+ "frame relay\n"
+ "frame relay interface. Must be registered via fr vty\n"
+ NSVCI_STR
+ NSVCI_STR
+ DLCI_STR
+ DLCI_STR
+ )
{
- struct gprs_ns2_inst *nsi = vty_nsi;
- struct gprs_ns2_nse *nse;
+ struct gprs_ns2_vc_bind *bind;
struct gprs_ns2_vc *nsvc;
- uint16_t id = atoi(argv[1]);
- bool show_stats = false;
+ struct gprs_ns2_nse *nse = vty->index;
+ const char *netif = argv[0];
+ uint16_t dlci = atoi(argv[1]);
+ uint16_t nsvci = atoi(argv[2]);
+ bool dialect_modified = false;
+ bool ll_modified = false;
- if (argc >= 3)
- show_stats = true;
+ if (nse->ll != GPRS_NS2_LL_FR && nse->ll != GPRS_NS2_LL_UNDEF) {
+ vty_out(vty, "Can not mix NS-VC with different link layer%s", VTY_NEWLINE);
+ goto err;
+ }
- if (!strcmp(argv[0], "nsei")) {
- nse = gprs_ns2_nse_by_nsei(nsi, id);
- if (!nse) {
- return CMD_WARNING;
- }
+ if (nse->dialect != GPRS_NS2_DIALECT_STATIC_RESETBLOCK && nse->dialect != GPRS_NS2_DIALECT_UNDEF) {
+ vty_out(vty, "Can not mix NS-VC with different dialects%s", VTY_NEWLINE);
+ goto err;
+ }
- dump_nse(vty, nse, show_stats, false);
- } else {
- nsvc = gprs_ns2_nsvc_by_nsvci(nsi, id);
+ if (nse->ll == GPRS_NS2_LL_UNDEF) {
+ nse->ll = GPRS_NS2_LL_FR;
+ ll_modified = true;
+ }
- if (!nsvc) {
- vty_out(vty, "No such NS Entity%s", VTY_NEWLINE);
- return CMD_WARNING;
- }
+ if (nse->dialect == GPRS_NS2_DIALECT_UNDEF) {
+ ns2_nse_set_dialect(nse, GPRS_NS2_DIALECT_STATIC_RESETBLOCK);
+ dialect_modified = true;
+ }
- dump_nsvc(vty, nsvc, show_stats);
+
+ bind = gprs_ns2_fr_bind_by_netif(vty_nsi, netif);
+ if (!bind) {
+ vty_out(vty, "Can not find fr interface \"%s\". Please configure it via fr vty.%s",
+ netif, VTY_NEWLINE);
+ goto err;
+ }
+
+ if (gprs_ns2_fr_nsvc_by_dlci(bind, dlci)) {
+ vty_out(vty, "A NS-VC with the specified DLCI already exist!%s", VTY_NEWLINE);
+ goto err;
}
+ if (gprs_ns2_nsvc_by_nsvci(vty_nsi, nsvci)) {
+ vty_out(vty, "A NS-VC with the specified NS-VCI already exist!%s", VTY_NEWLINE);
+ goto err;
+ }
+
+ nsvc = gprs_ns2_fr_connect(bind, nse, nsvci, dlci);
+ if (!nsvc) {
+ /* Could not create NS-VC, connect failed */
+ vty_out(vty, "Failed to create the NS-VC%s", VTY_NEWLINE);
+ goto err;
+ }
+ nsvc->persistent = true;
return CMD_SUCCESS;
-}
-#define NSE_CMD_STR "Persistent NS Entity\n" "NS Entity ID (NSEI)\n"
+err:
+ if (ll_modified)
+ nse->ll = GPRS_NS2_LL_UNDEF;
+ if (dialect_modified)
+ ns2_nse_set_dialect(nse, GPRS_NS2_DIALECT_UNDEF);
-DEFUN(cfg_nse_nsvc, cfg_nse_nsvci_cmd,
- "nse <0-65535> nsvci <0-65535>",
- NSE_CMD_STR
- "NS Virtual Connection\n"
- "NS Virtual Connection ID (NSVCI)\n"
- )
+ return CMD_WARNING;
+}
+
+DEFUN(cfg_no_ns_nse_nsvc_fr_dlci, cfg_no_ns_nse_nsvc_fr_dlci_cmd,
+ "no nsvc fr NETIF dlci <16-1007>",
+ NO_STR
+ "Delete frame relay NS-VC\n"
+ "frame relay\n"
+ "frame relay interface. Must be registered via fr vty\n"
+ DLCI_STR
+ DLCI_STR
+ )
{
- struct ns2_vty_vc *vtyvc;
+ struct gprs_ns2_vc_bind *bind;
+ struct gprs_ns2_vc *nsvc;
+ struct gprs_ns2_nse *nse = vty->index;
+ const char *netif = argv[0];
+ uint16_t dlci = atoi(argv[1]);
- uint16_t nsei = atoi(argv[0]);
- uint16_t nsvci = atoi(argv[1]);
+ if (nse->ll != GPRS_NS2_LL_FR) {
+ vty_out(vty, "This NSE doesn't support frame relay.%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ bind = gprs_ns2_fr_bind_by_netif(vty_nsi, netif);
+ if (!bind) {
+ vty_out(vty, "Can not find fr interface \"%s\"%s",
+ netif, VTY_NEWLINE);
+ return CMD_ERR_NOTHING_TODO;
+ }
- vtyvc = vtyvc_by_nsei(nsei, true);
- if (!vtyvc) {
- vty_out(vty, "Can not allocate space %s", VTY_NEWLINE);
+ nsvc = gprs_ns2_fr_nsvc_by_dlci(bind, dlci);
+ if (!nsvc) {
+ vty_out(vty, "Can not find a NS-VC on fr interface %s with dlci %u%s",
+ netif, dlci, VTY_NEWLINE);
return CMD_WARNING;
}
- vtyvc->nsvci = nsvci;
+ if (nse != nsvc->nse) {
+ vty_out(vty, "The specified NS-VC is not a part of the NSE %u!%s"
+ "To remove this NS-VC go to the vty node 'nse %u'%s",
+ nse->nsei, VTY_NEWLINE,
+ nsvc->nse->nsei, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ gprs_ns2_free_nsvc(nsvc);
+ if (llist_empty(&nse->nsvc)) {
+ nse->ll = GPRS_NS2_LL_UNDEF;
+ ns2_nse_set_dialect(nse, GPRS_NS2_DIALECT_UNDEF);
+ }
return CMD_SUCCESS;
}
-DEFUN(cfg_nse_remoteip, cfg_nse_remoteip_cmd,
- "nse <0-65535> remote-ip " VTY_IPV46_CMD,
- NSE_CMD_STR
- "Remote IP Address\n"
- "Remote IP Address\n")
+DEFUN(cfg_no_ns_nse_nsvci, cfg_no_ns_nse_nsvci_cmd,
+ "no nsvc nsvci <0-65535>",
+ NO_STR
+ "Delete NSVC\n"
+ NSVCI_STR
+ NSVCI_STR
+ )
{
- uint16_t nsei = atoi(argv[0]);
- struct ns2_vty_vc *vtyvc;
+ struct gprs_ns2_vc *nsvc;
+ struct gprs_ns2_nse *nse = vty->index;
+ uint16_t nsvci = atoi(argv[0]);
- vtyvc = vtyvc_by_nsei(nsei, true);
- if (!vtyvc) {
- vty_out(vty, "Can not allocate space %s", VTY_NEWLINE);
+ switch (nse->dialect) {
+ case GPRS_NS2_DIALECT_SNS:
+ case GPRS_NS2_DIALECT_STATIC_ALIVE:
+ vty_out(vty, "NSE doesn't support NSVCI.%s", VTY_NEWLINE);
return CMD_WARNING;
+ case GPRS_NS2_DIALECT_UNDEF:
+ vty_out(vty, "No NSVCs configured%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ case GPRS_NS2_DIALECT_IPACCESS:
+ case GPRS_NS2_DIALECT_STATIC_RESETBLOCK:
+ break;
}
- osmo_sockaddr_str_from_str2(&vtyvc->remote, argv[1]);
+ nsvc = gprs_ns2_nsvc_by_nsvci(vty_nsi, nsvci);
+ if (!nsvc) {
+ vty_out(vty, "Can not find NS-VC with NS-VCI %u%s", nsvci, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (nse != nsvc->nse) {
+ vty_out(vty, "NS-VC with NS-VCI %u is not part of this NSE!%s",
+ nsvci, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ gprs_ns2_free_nsvc(nsvc);
+ if (llist_empty(&nse->nsvc)) {
+ nse->ll = GPRS_NS2_LL_UNDEF;
+ ns2_nse_set_dialect(nse, GPRS_NS2_DIALECT_UNDEF);
+ }
return CMD_SUCCESS;
}
-DEFUN(cfg_nse_remoteport, cfg_nse_remoteport_cmd,
- "nse <0-65535> remote-port <0-65535>",
- NSE_CMD_STR
- "Remote UDP Port\n"
- "Remote UDP Port Number\n")
+static int ns_nse_nsvc_udp_cmds(struct vty *vty, const char *bind_name, const char *remote_char, uint16_t port,
+ uint16_t sig_weight, uint16_t data_weight)
{
- uint16_t nsei = atoi(argv[0]);
- uint16_t port = atoi(argv[1]);
- struct ns2_vty_vc *vtyvc;
+ struct gprs_ns2_vc_bind *bind;
+ struct gprs_ns2_vc *nsvc;
+ struct gprs_ns2_nse *nse = vty->index;
+ bool dialect_modified = false;
+ bool ll_modified = false;
- vtyvc = vtyvc_by_nsei(nsei, true);
- if (!vtyvc) {
- vty_out(vty, "Can not allocate space %s", VTY_NEWLINE);
- return CMD_WARNING;
+ struct osmo_sockaddr_str remote_str;
+ struct osmo_sockaddr remote;
+
+ if (nse->ll == GPRS_NS2_LL_UNDEF) {
+ nse->ll = GPRS_NS2_LL_UDP;
+ ll_modified = true;
+ }
+
+ if (nse->dialect == GPRS_NS2_DIALECT_UNDEF) {
+ ns2_nse_set_dialect(nse, GPRS_NS2_DIALECT_STATIC_ALIVE);
+ dialect_modified = true;
}
- vtyvc->remote.port = port;
+ if (nse->ll != GPRS_NS2_LL_UDP) {
+ vty_out(vty, "Can not mix NS-VC with different link layer%s", VTY_NEWLINE);
+ goto err;
+ }
+
+ if (nse->dialect != GPRS_NS2_DIALECT_STATIC_ALIVE) {
+ vty_out(vty, "Can not mix NS-VC with different dialects%s", VTY_NEWLINE);
+ goto err;
+ }
+
+ if (osmo_sockaddr_str_from_str(&remote_str, remote_char, port)) {
+ vty_out(vty, "Can not parse IPv4/IPv6 or port.%s", VTY_NEWLINE);
+ goto err;
+ }
+
+ if (osmo_sockaddr_str_to_sockaddr(&remote_str, &remote.u.sas)) {
+ vty_out(vty, "Can not parse IPv4/IPv6 or port.%s", VTY_NEWLINE);
+ goto err;
+ }
+
+ bind = gprs_ns2_bind_by_name(vty_nsi, bind_name);
+ if (!bind) {
+ vty_out(vty, "Can not find bind with name %s%s",
+ bind_name, VTY_NEWLINE);
+ goto err;
+ }
+
+ if (bind->ll != GPRS_NS2_LL_UDP) {
+ vty_out(vty, "Bind %s is not an UDP bind.%s",
+ bind_name, VTY_NEWLINE);
+ goto err;
+ }
+
+ nsvc = gprs_ns2_nsvc_by_sockaddr_bind(bind, &remote);
+ if (nsvc) {
+ if (nsvc->nse == nse)
+ vty_out(vty, "Specified NSVC is already present in this NSE.%s", VTY_NEWLINE);
+ else
+ vty_out(vty, "Specified NSVC is already present in another NSE%05u.%s", nsvc->nse->nsei, VTY_NEWLINE);
+ goto err;
+ }
+
+ nsvc = gprs_ns2_ip_connect(bind, &remote, nse, 0);
+ if (!nsvc) {
+ vty_out(vty, "Can not create NS-VC.%s", VTY_NEWLINE);
+ goto err;
+ }
+ nsvc->sig_weight = sig_weight;
+ nsvc->data_weight = data_weight;
+ nsvc->persistent = true;
return CMD_SUCCESS;
+
+err:
+ if (ll_modified)
+ nse->ll = GPRS_NS2_LL_UNDEF;
+ if (dialect_modified)
+ ns2_nse_set_dialect(nse, GPRS_NS2_DIALECT_UNDEF);
+ return CMD_WARNING;
}
-DEFUN(cfg_nse_fr_dlci, cfg_nse_fr_dlci_cmd,
- "nse <0-65535> fr-dlci <16-1007>",
- NSE_CMD_STR
- "Frame Relay DLCI\n"
- "Frame Relay DLCI Number\n")
+DEFUN(cfg_ns_nse_nsvc_udp, cfg_ns_nse_nsvc_udp_cmd,
+ "nsvc udp BIND " VTY_IPV46_CMD " <1-65535>",
+ "NS Virtual Connection\n"
+ "NS over UDP\n"
+ "A unique bind identifier created by ns bind\n"
+ "Remote IPv4 Address\n" "Remote IPv6 Address\n"
+ "Remote UDP Port\n")
{
- uint16_t nsei = atoi(argv[0]);
- uint16_t dlci = atoi(argv[1]);
- struct ns2_vty_vc *vtyvc;
+ const char *bind_name = argv[0];
+ const char *remote = argv[1];
+ uint16_t port = atoi(argv[2]);
+ uint16_t sig_weight = 1;
+ uint16_t data_weight = 1;
+
+ return ns_nse_nsvc_udp_cmds(vty, bind_name, remote, port, sig_weight, data_weight);
+}
+
+DEFUN(cfg_ns_nse_nsvc_udp_weights, cfg_ns_nse_nsvc_udp_weights_cmd,
+ "nsvc udp BIND " VTY_IPV46_CMD " <1-65535> signalling-weight <0-254> data-weight <0-254>",
+ "NS Virtual Connection\n"
+ "NS over UDP\n"
+ "A unique bind identifier created by ns bind\n"
+ "Remote IPv4 Address\n" "Remote IPv6 Address\n"
+ "Remote UDP Port\n"
+ "Signalling weight of the NSVC (default = 1)\n"
+ "Signalling weight of the NSVC (default = 1)\n"
+ "Data weight of the NSVC (default = 1)\n"
+ "Data weight of the NSVC (default = 1)\n"
+ )
+{
+ const char *bind_name = argv[0];
+ const char *remote = argv[1];
+ uint16_t port = atoi(argv[2]);
+ uint16_t sig_weight = atoi(argv[3]);
+ uint16_t data_weight = atoi(argv[4]);
+
+ return ns_nse_nsvc_udp_cmds(vty, bind_name, remote, port, sig_weight, data_weight);
+}
+
+DEFUN(cfg_no_ns_nse_nsvc_udp, cfg_no_ns_nse_nsvc_udp_cmd,
+ "no nsvc udp BIND " VTY_IPV46_CMD " <1-65535>",
+ NO_STR
+ "Delete a NS Virtual Connection\n"
+ "NS over UDP\n"
+ "A unique bind identifier created by ns bind\n"
+ "Remote IPv4 Address\n" "Remote IPv6 Address\n"
+ "Remote UDP Port\n"
+ )
+{
+ struct gprs_ns2_vc_bind *bind;
+ struct gprs_ns2_vc *nsvc;
+ struct gprs_ns2_nse *nse = vty->index;
+ const char *bind_name = argv[0];
+ struct osmo_sockaddr_str remote_str;
+ struct osmo_sockaddr remote;
+ uint16_t port = atoi(argv[2]);
+
+ if (nse->ll != GPRS_NS2_LL_UDP) {
+ vty_out(vty, "This NSE doesn't support UDP.%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
- vtyvc = vtyvc_by_nsei(nsei, true);
- if (!vtyvc) {
- vty_out(vty, "Can not allocate space %s", VTY_NEWLINE);
+ if (nse->dialect != GPRS_NS2_DIALECT_STATIC_ALIVE) {
+ vty_out(vty, "This NSE doesn't support UDP with dialect static alive.%s", VTY_NEWLINE);
return CMD_WARNING;
}
- if (vtyvc->ll != GPRS_NS_LL_FR_GRE) {
- vty_out(vty, "Warning: seting FR DLCI on non-FR NSE%s",
- VTY_NEWLINE);
+ bind = gprs_ns2_bind_by_name(vty_nsi, bind_name);
+ if (!bind) {
+ vty_out(vty, "Can not find bind with name %s%s",
+ bind_name, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (bind->ll != GPRS_NS2_LL_UDP) {
+ vty_out(vty, "Bind %s is not an UDP bind.%s",
+ bind_name, VTY_NEWLINE);
+ return CMD_WARNING;
}
- vtyvc->frdlci = dlci;
+ if (osmo_sockaddr_str_from_str(&remote_str, argv[1], port)) {
+ vty_out(vty, "Can not parse IPv4/IPv6 or port.%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (osmo_sockaddr_str_to_sockaddr(&remote_str, &remote.u.sas)) {
+ vty_out(vty, "Can not parse IPv4/IPv6 or port.%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ nsvc = gprs_ns2_nsvc_by_sockaddr_bind(bind, &remote);
+ if (!nsvc) {
+ vty_out(vty, "Can not find NS-VC with remote %s:%u%s",
+ remote_str.ip, remote_str.port, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (!nsvc->persistent) {
+ vty_out(vty, "NS-VC with remote %s:%u is a dynamic NS-VC. Not configured by vty.%s",
+ remote_str.ip, remote_str.port, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (nsvc->nse != nse) {
+ vty_out(vty, "NS-VC is not part of this NSE!%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ gprs_ns2_free_nsvc(nsvc);
+ if (llist_empty(&nse->nsvc)) {
+ nse->ll = GPRS_NS2_LL_UNDEF;
+ ns2_nse_set_dialect(nse, GPRS_NS2_DIALECT_UNDEF);
+ }
return CMD_SUCCESS;
}
-DEFUN(cfg_nse_encaps, cfg_nse_encaps_cmd,
- "nse <0-65535> encapsulation (udp|framerelay-gre)",
- NSE_CMD_STR
- "Encapsulation for NS\n"
- "UDP/IP Encapsulation\n" "Frame-Relay/GRE/IP Encapsulation\n")
+DEFUN(cfg_ns_nse_nsvc_ipa, cfg_ns_nse_nsvc_ipa_cmd,
+ "nsvc ipa BIND " VTY_IPV46_CMD " <1-65535> nsvci <0-65535>" ,
+ "NS Virtual Connection\n"
+ "NS over UDP ip.access style (uses RESET/BLOCK)\n"
+ "A unique bind identifier created by ns bind\n"
+ "Remote IPv4 Address\n" "Remote IPv6 Address\n"
+ "Remote UDP Port\n"
+ NSVCI_STR
+ NSVCI_STR
+ )
{
- uint16_t nsei = atoi(argv[0]);
- struct ns2_vty_vc *vtyvc;
+ struct gprs_ns2_vc_bind *bind;
+ struct gprs_ns2_vc *nsvc;
+ struct gprs_ns2_nse *nse = vty->index;
+ bool dialect_modified = false;
+ bool ll_modified = false;
+
+ const char *bind_name = argv[0];
+ struct osmo_sockaddr_str remote_str;
+ struct osmo_sockaddr remote;
+ uint16_t port = atoi(argv[2]);
+ uint16_t nsvci = atoi(argv[3]);
+
+ if (nse->ll == GPRS_NS2_LL_UNDEF) {
+ nse->ll = GPRS_NS2_LL_UDP;
+ ll_modified = true;
+ }
- vtyvc = vtyvc_by_nsei(nsei, true);
- if (!vtyvc) {
- vty_out(vty, "Can not allocate space %s", VTY_NEWLINE);
- return CMD_WARNING;
+ if (nse->dialect == GPRS_NS2_DIALECT_UNDEF) {
+ ns2_nse_set_dialect(nse, GPRS_NS2_DIALECT_IPACCESS);
+ dialect_modified = true;
}
- if (!strcmp(argv[1], "udp"))
- vtyvc->ll = GPRS_NS_LL_UDP;
- else
- vtyvc->ll = GPRS_NS_LL_FR_GRE;
+ if (nse->ll != GPRS_NS2_LL_UDP) {
+ vty_out(vty, "Can not mix NS-VC with different link layer%s", VTY_NEWLINE);
+ goto err;
+ }
+
+ if (nse->dialect != GPRS_NS2_DIALECT_IPACCESS) {
+ vty_out(vty, "Can not mix NS-VC with different dialects%s", VTY_NEWLINE);
+ goto err;
+ }
+
+ if (osmo_sockaddr_str_from_str(&remote_str, argv[1], port)) {
+ vty_out(vty, "Can not parse IPv4/IPv6 or port.%s", VTY_NEWLINE);
+ goto err;
+ }
+
+ if (osmo_sockaddr_str_to_sockaddr(&remote_str, &remote.u.sas)) {
+ vty_out(vty, "Can not parse IPv4/IPv6 or port.%s", VTY_NEWLINE);
+ goto err;
+ }
+
+ bind = gprs_ns2_bind_by_name(vty_nsi, bind_name);
+ if (!bind) {
+ vty_out(vty, "Can not find bind with name %s%s",
+ bind_name, VTY_NEWLINE);
+ goto err;
+ }
+
+ if (bind->ll != GPRS_NS2_LL_UDP) {
+ vty_out(vty, "Bind %s is not an UDP bind.%s",
+ bind_name, VTY_NEWLINE);
+ goto err;
+ }
+
+ nsvc = gprs_ns2_ip_connect(bind, &remote, nse, nsvci);
+ if (!nsvc) {
+ vty_out(vty, "Can not create NS-VC.%s", VTY_NEWLINE);
+ goto err;
+ }
+ nsvc->persistent = true;
return CMD_SUCCESS;
+
+err:
+ if (ll_modified)
+ nse->ll = GPRS_NS2_LL_UNDEF;
+ if (dialect_modified)
+ ns2_nse_set_dialect(nse, GPRS_NS2_DIALECT_UNDEF);
+ return CMD_WARNING;
}
-DEFUN(cfg_nse_remoterole, cfg_nse_remoterole_cmd,
- "nse <0-65535> remote-role (sgsn|bss)",
- NSE_CMD_STR
- "Remote NSE Role\n"
- "Remote Peer is SGSN\n"
- "Remote Peer is BSS\n")
+DEFUN(cfg_no_ns_nse_nsvc_ipa, cfg_no_ns_nse_nsvc_ipa_cmd,
+ "no nsvc ipa BIND " VTY_IPV46_CMD " <1-65535> nsvci <0-65535>",
+ NO_STR
+ "Delete a NS Virtual Connection\n"
+ "NS over UDP\n"
+ "A unique bind identifier created by ns bind\n"
+ "Remote IPv4 Address\n" "Remote IPv6 Address\n"
+ "Remote UDP Port\n"
+ NSVCI_STR
+ NSVCI_STR
+ )
{
- uint16_t nsei = atoi(argv[0]);
- struct ns2_vty_vc *vtyvc;
+ struct gprs_ns2_vc_bind *bind;
+ struct gprs_ns2_vc *nsvc;
+ struct gprs_ns2_nse *nse = vty->index;
+ const char *bind_name = argv[0];
+ struct osmo_sockaddr_str remote_str;
+ struct osmo_sockaddr remote;
+ uint16_t port = atoi(argv[2]);
+ uint16_t nsvci = atoi(argv[3]);
+
+ if (nse->ll != GPRS_NS2_LL_UDP) {
+ vty_out(vty, "This NSE doesn't support UDP.%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
- vtyvc = vtyvc_by_nsei(nsei, true);
- if (!vtyvc) {
- vty_out(vty, "Can not allocate space %s", VTY_NEWLINE);
+ if (nse->dialect != GPRS_NS2_DIALECT_IPACCESS) {
+ vty_out(vty, "This NSE doesn't support UDP with dialect ipaccess.%s", VTY_NEWLINE);
return CMD_WARNING;
}
- if (!strcmp(argv[1], "sgsn"))
- vtyvc->remote_end_is_sgsn = 1;
- else
- vtyvc->remote_end_is_sgsn = 0;
+ bind = gprs_ns2_bind_by_name(vty_nsi, bind_name);
+ if (!bind) {
+ vty_out(vty, "Can not find bind with name %s%s",
+ bind_name, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (bind->ll != GPRS_NS2_LL_UDP) {
+ vty_out(vty, "Bind %s is not an UDP bind.%s",
+ bind_name, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (osmo_sockaddr_str_from_str(&remote_str, argv[1], port)) {
+ vty_out(vty, "Can not parse IPv4/IPv6 or port.%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (osmo_sockaddr_str_to_sockaddr(&remote_str, &remote.u.sas)) {
+ vty_out(vty, "Can not parse IPv4/IPv6 or port.%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ nsvc = gprs_ns2_nsvc_by_sockaddr_bind(bind, &remote);
+ if (!nsvc) {
+ vty_out(vty, "Can not find NS-VC with remote %s:%u%s",
+ remote_str.ip, remote_str.port, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (!nsvc->persistent) {
+ vty_out(vty, "NS-VC with remote %s:%u is a dynamic NS-VC. Not configured by vty.%s",
+ remote_str.ip, remote_str.port, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (nsvc->nse != nse) {
+ vty_out(vty, "NS-VC is not part of this NSE!%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (!nsvc->nsvci_is_valid) {
+ vty_out(vty, "NS-VC doesn't have a nsvci!%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (nsvc->nsvci != nsvci) {
+ vty_out(vty, "NS-VC has a different nsvci (%u)!%s",
+ nsvc->nsvci, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ gprs_ns2_free_nsvc(nsvc);
+ if (llist_empty(&nse->nsvc)) {
+ nse->ll = GPRS_NS2_LL_UNDEF;
+ ns2_nse_set_dialect(nse, GPRS_NS2_DIALECT_UNDEF);
+ }
return CMD_SUCCESS;
}
-DEFUN(cfg_no_nse, cfg_no_nse_cmd,
- "no nse <0-65535>",
- "Delete Persistent NS Entity\n"
- "Delete " NSE_CMD_STR)
+DEFUN(cfg_ns_nse_ip_sns_remote, cfg_ns_nse_ip_sns_remote_cmd,
+ "ip-sns-remote " VTY_IPV46_CMD " <1-65535>",
+ "SNS Initial Endpoint\n"
+ "SGSN IPv4 Address\n" "SGSN IPv6 Address\n"
+ "SGSN UDP Port\n"
+ )
{
- uint16_t nsei = atoi(argv[0]);
- struct ns2_vty_vc *vtyvc;
+ struct gprs_ns2_nse *nse = vty->index;
+ bool dialect_modified = false;
+ bool ll_modified = false;
+ int rc;
+
+ /* argv[0] */
+ struct osmo_sockaddr_str remote_str;
+ struct osmo_sockaddr remote;
+ uint16_t port = atoi(argv[1]);
+
+ if (nse->ll == GPRS_NS2_LL_UNDEF) {
+ nse->ll = GPRS_NS2_LL_UDP;
+ ll_modified = true;
+ }
+
+ if (nse->dialect == GPRS_NS2_DIALECT_UNDEF) {
+ if (ns2_nse_set_dialect(nse, GPRS_NS2_DIALECT_SNS) < 0)
+ goto err;
+ dialect_modified = true;
+ }
+
+ if (nse->ll != GPRS_NS2_LL_UDP) {
+ vty_out(vty, "Can not mix NS-VC with different link layer%s", VTY_NEWLINE);
+ goto err;
+ }
+
+ if (nse->dialect != GPRS_NS2_DIALECT_SNS) {
+ vty_out(vty, "Can not mix NS-VC with different dialects%s", VTY_NEWLINE);
+ goto err;
+ }
+
+ if (osmo_sockaddr_str_from_str(&remote_str, argv[0], port)) {
+ vty_out(vty, "Can not parse IPv4/IPv6 or port.%s", VTY_NEWLINE);
+ goto err;
+ }
+
+ if (osmo_sockaddr_str_to_sockaddr(&remote_str, &remote.u.sas)) {
+ vty_out(vty, "Can not parse IPv4/IPv6 or port.%s", VTY_NEWLINE);
+ goto err;
+ }
+
+ rc = gprs_ns2_sns_add_endpoint(nse, &remote);
+ switch (rc) {
+ case 0:
+ return CMD_SUCCESS;
+ case -EADDRINUSE:
+ vty_out(vty, "Specified SNS endpoint already part of the NSE.%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ default:
+ vty_out(vty, "Can not add specified SNS endpoint.%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+err:
+ if (ll_modified)
+ nse->ll = GPRS_NS2_LL_UNDEF;
+ if (dialect_modified)
+ ns2_nse_set_dialect(nse, GPRS_NS2_DIALECT_UNDEF);
+ return CMD_WARNING;
+}
+
+DEFUN(cfg_no_ns_nse_ip_sns_remote, cfg_no_ns_nse_ip_sns_remote_cmd,
+ "no ip-sns-remote " VTY_IPV46_CMD " <1-65535>",
+ NO_STR
+ "Delete a SNS Initial Endpoint\n"
+ "SGSN IPv4 Address\n" "SGSN IPv6 Address\n"
+ "SGSN UDP Port\n"
+ )
+{
+ struct gprs_ns2_nse *nse = vty->index;
+ struct osmo_sockaddr_str remote_str; /* argv[0] */
+ struct osmo_sockaddr remote;
+ uint16_t port = atoi(argv[1]);
+
+ if (nse->ll != GPRS_NS2_LL_UDP) {
+ vty_out(vty, "This NSE doesn't support UDP.%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
- vtyvc = vtyvc_by_nsei(nsei, false);
- if (!vtyvc) {
- vty_out(vty, "The NSE %d does not exists.%s", nsei, VTY_NEWLINE);
+ if (nse->dialect != GPRS_NS2_DIALECT_SNS) {
+ vty_out(vty, "This NSE doesn't support UDP with dialect ip-sns.%s", VTY_NEWLINE);
return CMD_WARNING;
}
- ns2_vc_free(vtyvc);
+ if (osmo_sockaddr_str_from_str(&remote_str, argv[0], port)) {
+ vty_out(vty, "Can not parse IPv4/IPv6 or port.%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (osmo_sockaddr_str_to_sockaddr(&remote_str, &remote.u.sas)) {
+ vty_out(vty, "Can not parse IPv4/IPv6 or port.%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (gprs_ns2_sns_del_endpoint(nse, &remote)) {
+ vty_out(vty, "Can not remove specified SNS endpoint.%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (vty_nse_check_sns(nse)) {
+ /* there is still sns configuration valid */
+ return CMD_SUCCESS;
+ } else {
+ /* clean up nse to allow other nsvc commands */
+ ns2_nse_set_dialect(nse, GPRS_NS2_DIALECT_UNDEF);
+ nse->ll = GPRS_NS2_LL_UNDEF;
+ }
return CMD_SUCCESS;
}
-DEFUN(cfg_ns_timer, cfg_ns_timer_cmd,
- "timer " NS_TIMERS " <0-65535>",
- "Network Service Timer\n"
- NS_TIMERS_HELP "Timer Value\n")
+/* add all IP-SNS default binds to the given NSE */
+int ns2_sns_add_sns_default_binds(struct gprs_ns2_nse *nse)
{
- int idx = get_string_value(gprs_ns_timer_strs, argv[0]);
- int val = atoi(argv[1]);
+ struct vty_nse_bind *vnse_bind;
+ int count = 0;
- if (idx < 0 || idx >= ARRAY_SIZE(vty_nsi->timeout))
+ OSMO_ASSERT(nse->ll == GPRS_NS2_LL_UDP);
+ OSMO_ASSERT(nse->dialect == GPRS_NS2_DIALECT_SNS);
+
+ llist_for_each_entry(vnse_bind, &ip_sns_default_binds, list) {
+ struct gprs_ns2_vc_bind *bind = gprs_ns2_bind_by_name(vty_nsi, vnse_bind->vbind->name);
+ /* the bind might not yet created because "listen" is missing. */
+ if (!bind)
+ continue;
+ gprs_ns2_sns_add_bind(nse, bind);
+ count++;
+ }
+ return count;
+}
+
+DEFUN(cfg_ns_ip_sns_default_bind, cfg_ns_ip_sns_default_bind_cmd,
+ "ip-sns-default bind ID",
+ "Defaults for dynamically created NSEs created by IP-SNS in SGSN role\n"
+ "IP SNS binds\n"
+ "Name of NS udp bind whose IP endpoint will be used as IP-SNS local endpoint. Can be given multiple times.\n")
+{
+ struct vty_bind *vbind;
+ struct vty_nse_bind *vnse_bind;
+ const char *name = argv[0];
+
+ vbind = vty_bind_by_name(name);
+ if (!vbind) {
+ vty_out(vty, "Can not find the given bind '%s'%s", name, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (vbind->ll != GPRS_NS2_LL_UDP) {
+ vty_out(vty, "ip-sns-default bind can only be used with UDP bind%s", VTY_NEWLINE);
return CMD_WARNING;
+ }
- vty_nsi->timeout[idx] = val;
+ llist_for_each_entry(vnse_bind, &ip_sns_default_binds, list) {
+ if (vnse_bind->vbind == vbind)
+ return CMD_SUCCESS;
+ }
+
+ vnse_bind = talloc(vty_nsi, struct vty_nse_bind);
+ if (!vnse_bind)
+ return CMD_WARNING;
+ vnse_bind->vbind = vbind;
+
+ llist_add_tail(&vnse_bind->list, &ip_sns_default_binds);
return CMD_SUCCESS;
}
-#define ENCAPS_STR "NS encapsulation options\n"
+DEFUN(cfg_no_ns_ip_sns_default_bind, cfg_no_ns_ip_sns_default_bind_cmd,
+ "no ip-sns-default bind ID",
+ NO_STR "Defaults for dynamically created NSEs created by IP-SNS in SGSN role\n"
+ "IP SNS binds\n"
+ "Name of NS udp bind whose IP endpoint will be removed as IP-SNS local endpoint.\n")
+{
+ struct vty_bind *vbind;
+ struct vty_nse_bind *vnse_bind;
+ const char *name = argv[0];
-DEFUN(cfg_nsip_local_ip, cfg_nsip_local_ip_cmd,
- "encapsulation udp local-ip " VTY_IPV46_CMD,
- ENCAPS_STR "NS over UDP Encapsulation\n"
- "Set the IP address on which we listen for NS/UDP\n"
- "IP Address\n")
+ vbind = vty_bind_by_name(name);
+ if (!vbind) {
+ vty_out(vty, "Can not find the given bind '%s'%s", name, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (vbind->ll != GPRS_NS2_LL_UDP) {
+ vty_out(vty, "ip-sns-default bind can only be used with UDP bind%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ llist_for_each_entry(vnse_bind, &ip_sns_default_binds, list) {
+ if (vnse_bind->vbind == vbind) {
+ llist_del(&vnse_bind->list);
+ talloc_free(vnse_bind);
+ return CMD_SUCCESS;
+ }
+ }
+
+ vty_out(vty, "Bind '%s' was not an ip-sns-default bind%s", name, VTY_NEWLINE);
+ return CMD_WARNING;
+}
+
+DEFUN(cfg_ns_nse_ip_sns_bind, cfg_ns_nse_ip_sns_bind_cmd,
+ "ip-sns-bind BINDID",
+ "IP SNS binds\n"
+ "Name of NS udp bind whose IP endpoint will be used as IP-SNS local endpoint. Can be given multiple times.\n")
{
- osmo_sockaddr_str_from_str2(&priv.udp, argv[0]);
+ struct gprs_ns2_nse *nse = vty->index;
+ struct gprs_ns2_vc_bind *bind;
+ struct vty_bind *vbind;
+ struct vty_nse *vnse;
+ const char *name = argv[0];
+ bool ll_modified = false;
+ bool dialect_modified = false;
+ int rc;
+
+ if (nse->ll == GPRS_NS2_LL_UNDEF) {
+ nse->ll = GPRS_NS2_LL_UDP;
+ ll_modified = true;
+ }
+
+ if (nse->dialect == GPRS_NS2_DIALECT_UNDEF) {
+ if (ns2_nse_set_dialect(nse, GPRS_NS2_DIALECT_SNS) < 0)
+ goto err;
+ dialect_modified = true;
+ }
+
+ if (nse->ll != GPRS_NS2_LL_UDP) {
+ vty_out(vty, "Can not mix NS-VC with different link layer%s", VTY_NEWLINE);
+ goto err;
+ }
+
+ if (nse->dialect != GPRS_NS2_DIALECT_SNS) {
+ vty_out(vty, "Can not mix NS-VC with different dialects%s", VTY_NEWLINE);
+ goto err;
+ }
+
+ vbind = vty_bind_by_name(name);
+ if (!vbind) {
+ vty_out(vty, "Can not find the given bind '%s'%s", name, VTY_NEWLINE);
+ goto err;
+ }
+
+ if (vbind->ll != GPRS_NS2_LL_UDP) {
+ vty_out(vty, "ip-sns-bind can only be used with UDP bind%s",
+ VTY_NEWLINE);
+ goto err;
+ }
+
+ /* the vnse has been created together when creating the nse node. The parent node should check this already! */
+ vnse = vty_nse_by_nsei(nse->nsei);
+ OSMO_ASSERT(vnse);
+
+ rc = vty_nse_add_vbind(vnse, vbind);
+ switch (rc) {
+ case 0:
+ break;
+ case -EALREADY:
+ vty_out(vty, "Failed to add ip-sns-bind %s already present%s", name, VTY_NEWLINE);
+ goto err;
+ case -ENOMEM:
+ vty_out(vty, "Failed to add ip-sns-bind %s out of memory%s", name, VTY_NEWLINE);
+ goto err;
+ default:
+ vty_out(vty, "Failed to add ip-sns-bind %s! %d%s", name, rc, VTY_NEWLINE);
+ goto err;
+ }
+
+ /* the bind might not yet created because "listen" is missing. */
+ bind = gprs_ns2_bind_by_name(vty_nsi, name);
+ if (!bind)
+ return CMD_SUCCESS;
+
+ rc = gprs_ns2_sns_add_bind(nse, bind);
+ switch (rc) {
+ case 0:
+ break;
+ case -EALREADY:
+ vty_out(vty, "Failed to add ip-sns-bind %s already present%s", name, VTY_NEWLINE);
+ goto err;
+ case -ENOMEM:
+ vty_out(vty, "Failed to add ip-sns-bind %s out of memory%s", name, VTY_NEWLINE);
+ goto err;
+ default:
+ vty_out(vty, "Failed to add ip-sns-bind %s! %d%s", name, rc, VTY_NEWLINE);
+ goto err;
+ }
return CMD_SUCCESS;
+err:
+ if (ll_modified)
+ nse->ll = GPRS_NS2_LL_UNDEF;
+ if (dialect_modified)
+ ns2_nse_set_dialect(nse, GPRS_NS2_DIALECT_UNDEF);
+
+ return CMD_WARNING;
}
-DEFUN(cfg_nsip_local_port, cfg_nsip_local_port_cmd,
- "encapsulation udp local-port <0-65535>",
- ENCAPS_STR "NS over UDP Encapsulation\n"
- "Set the UDP port on which we listen for NS/UDP\n"
- "UDP port number\n")
+DEFUN(cfg_no_ns_nse_ip_sns_bind, cfg_no_ns_nse_ip_sns_bind_cmd,
+ "no ip-sns-bind BINDID",
+ NO_STR
+ "IP SNS binds\n"
+ "Name of NS udp bind whose IP endpoint will not be used as IP-SNS local endpoint\n")
{
- unsigned int port = atoi(argv[0]);
+ struct gprs_ns2_nse *nse = vty->index;
+ struct gprs_ns2_vc_bind *bind;
+ struct vty_bind *vbind;
+ struct vty_nse *vnse;
+ const char *name = argv[0];
+ int rc;
- priv.udp.port = port;
+ if (nse->ll != GPRS_NS2_LL_UDP) {
+ vty_out(vty, "This NSE doesn't support UDP.%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (nse->dialect != GPRS_NS2_DIALECT_SNS) {
+ vty_out(vty, "This NSE doesn't support UDP with dialect ip-sns.%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ vbind = vty_bind_by_name(name);
+ if (!vbind) {
+ vty_out(vty, "Can not find the given bind '%s'%s", name, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (vbind->ll != GPRS_NS2_LL_UDP) {
+ vty_out(vty, "no ip-sns-bind can only be used with UDP bind%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ /* the vnse has been created together when creating the nse node. The parent node should check this already! */
+ vnse = vty_nse_by_nsei(nse->nsei);
+ OSMO_ASSERT(vnse);
+
+ rc = vty_nse_remove_vbind(vnse, vbind);
+ switch(rc) {
+ case 0:
+ break;
+ case -ENOENT:
+ vty_out(vty, "Bind %s is not part of this NSE%s", name, VTY_NEWLINE);
+ return CMD_WARNING;
+ case -EINVAL:
+ vty_out(vty, "no ip-sns-bind can only be used with UDP bind%s",
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ default:
+ return CMD_WARNING;
+ }
+
+ /* the bind might not exists yet */
+ bind = gprs_ns2_bind_by_name(vty_nsi, name);
+ if (bind)
+ gprs_ns2_sns_del_bind(nse, bind);
+
+ if (!vty_nse_check_sns(nse)) {
+ /* clean up nse to allow other nsvc commands */
+ ns2_nse_set_dialect(nse, GPRS_NS2_DIALECT_UNDEF);
+ nse->ll = GPRS_NS2_LL_UNDEF;
+ }
return CMD_SUCCESS;
}
-DEFUN(cfg_nsip_dscp, cfg_nsip_dscp_cmd,
- "encapsulation udp dscp <0-255>",
- ENCAPS_STR "NS over UDP Encapsulation\n"
- "Set DSCP/TOS on the UDP socket\n" "DSCP Value\n")
+/* non-config commands */
+void ns2_vty_dump_nsvc(struct vty *vty, struct gprs_ns2_vc *nsvc, bool stats)
+{
+ if (nsvc->nsvci_is_valid)
+ vty_out(vty, " NSVCI %05u: %s %s %s %s %ssince ", nsvc->nsvci,
+ osmo_fsm_inst_state_name(nsvc->fi),
+ nsvc->persistent ? "PERSIST" : "DYNAMIC",
+ gprs_ns2_ll_str(nsvc),
+ ns2_vc_is_unblocked(nsvc) ? "ALIVE" : "DEAD",
+ nsvc->om_blocked ? "(blocked by O&M/vty) " :
+ !ns2_vc_is_unblocked(nsvc) ? "(cause: remote) " : "");
+ else
+ vty_out(vty, " %s %s sig_weight=%u data_weight=%u %s %s %ssince ",
+ osmo_fsm_inst_state_name(nsvc->fi),
+ nsvc->persistent ? "PERSIST" : "DYNAMIC",
+ nsvc->sig_weight, nsvc->data_weight,
+ gprs_ns2_ll_str(nsvc),
+ ns2_vc_is_unblocked(nsvc) ? "ALIVE" : "DEAD",
+ !ns2_vc_is_unblocked(nsvc) ? "(cause: remote) " : "");
+
+ vty_out_uptime(vty, &nsvc->ts_alive_change);
+ vty_out_newline(vty);
+
+ if (stats) {
+ vty_out_rate_ctr_group(vty, " ", nsvc->ctrg);
+ vty_out_stat_item_group(vty, " ", nsvc->statg);
+ }
+}
+
+static void dump_nse(struct vty *vty, const struct gprs_ns2_nse *nse, bool stats, bool persistent_only)
+{
+ struct gprs_ns2_vc *nsvc;
+ unsigned int nsvcs = 0;
+
+ if (persistent_only && !nse->persistent)
+ return;
+
+ vty_out(vty, "NSEI %05u: %s, %s since ", nse->nsei, gprs_ns2_lltype_str(nse->ll),
+ nse->alive ? "ALIVE" : "DEAD");
+ vty_out_uptime(vty, &nse->ts_alive_change);
+ vty_out_newline(vty);
+
+ ns2_sns_dump_vty(vty, " ", nse, stats);
+ llist_for_each_entry(nsvc, &nse->nsvc, list) {
+ nsvcs++;
+ }
+ vty_out(vty, " %u NS-VC:%s", nsvcs, VTY_NEWLINE);
+ llist_for_each_entry(nsvc, &nse->nsvc, list)
+ ns2_vty_dump_nsvc(vty, nsvc, stats);
+}
+
+static void dump_bind(struct vty *vty, const struct gprs_ns2_vc_bind *bind, bool stats)
+{
+ if (bind->dump_vty)
+ bind->dump_vty(bind, vty, stats);
+
+ if (stats) {
+ vty_out_stat_item_group(vty, " ", bind->statg);
+ }
+}
+
+static void dump_ns_bind(struct vty *vty, const struct gprs_ns2_inst *nsi, bool stats)
{
- int dscp = atoi(argv[0]);
struct gprs_ns2_vc_bind *bind;
- priv.dscp = dscp;
+ llist_for_each_entry(bind, &nsi->binding, list) {
+ dump_bind(vty, bind, stats);
+ }
+}
+
- llist_for_each_entry(bind, &vty_nsi->binding, list) {
- if (gprs_ns2_is_ip_bind(bind))
- gprs_ns2_ip_bind_set_dscp(bind, dscp);
+static void dump_ns_entities(struct vty *vty, const struct gprs_ns2_inst *nsi, bool stats, bool persistent_only)
+{
+ struct gprs_ns2_nse *nse;
+
+ llist_for_each_entry(nse, &nsi->nse, list) {
+ dump_nse(vty, nse, stats, persistent_only);
}
+}
+/* Backwards compatibility, among other things for the TestVTYGbproxy which expects
+ * 'show ns' to output something about binds */
+DEFUN_HIDDEN(show_ns, show_ns_cmd, "show ns",
+ SHOW_STR SHOW_NS_STR)
+{
+ dump_ns_entities(vty, vty_nsi, false, false);
+ dump_ns_bind(vty, vty_nsi, false);
+ if (vty_fr_network && llist_count(&vty_fr_network->links))
+ osmo_fr_network_dump_vty(vty, vty_fr_network);
return CMD_SUCCESS;
}
-DEFUN(cfg_nsip_res_block_unblock, cfg_nsip_res_block_unblock_cmd,
- "encapsulation udp use-reset-block-unblock (enabled|disabled)",
- ENCAPS_STR "NS over UDP Encapsulation\n"
- "Use NS-{RESET,BLOCK,UNBLOCK} procedures in violation of 3GPP TS 48.016\n"
- "Enable NS-{RESET,BLOCK,UNBLOCK}\n"
- "Disable NS-{RESET,BLOCK,UNBLOCK}\n")
+
+DEFUN(show_ns_binds, show_ns_binds_cmd, "show ns binds [stats]",
+ SHOW_STR SHOW_NS_STR
+ "Display information about the NS protocol binds\n"
+ "Include statistic\n")
{
- enum gprs_ns2_vc_mode vc_mode;
- struct gprs_ns2_vc_bind *bind;
+ bool stats = false;
+ if (argc > 0)
+ stats = true;
- if (!strcmp(argv[0], "enabled"))
- vc_mode = NS2_VC_MODE_BLOCKRESET;
- else
- vc_mode = NS2_VC_MODE_ALIVE;
+ dump_ns_bind(vty, vty_nsi, stats);
+ return CMD_SUCCESS;
+}
+
+DEFUN(show_ns_entities, show_ns_entities_cmd, "show ns entities [stats]",
+ SHOW_STR SHOW_NS_STR
+ "Display information about the NS protocol entities (NSEs)\n"
+ "Include statistics\n")
+{
+ bool stats = false;
+ if (argc > 0)
+ stats = true;
+
+ dump_ns_entities(vty, vty_nsi, stats, false);
+ return CMD_SUCCESS;
+}
+
+DEFUN(show_ns_pers, show_ns_pers_cmd, "show ns persistent",
+ SHOW_STR SHOW_NS_STR
+ "Show only persistent NS\n")
+{
+ dump_ns_entities(vty, vty_nsi, true, true);
+ return CMD_SUCCESS;
+}
+
+DEFUN(show_nse, show_nse_cmd, "show ns (nsei|nsvc) <0-65535> [stats]",
+ SHOW_STR SHOW_NS_STR
+ "Select one NSE by its NSE Identifier\n"
+ "Select one NSE by its NS-VC Identifier\n"
+ "The Identifier of selected type\n"
+ "Include Statistics\n")
+{
+ struct gprs_ns2_inst *nsi = vty_nsi;
+ struct gprs_ns2_nse *nse;
+ struct gprs_ns2_vc *nsvc;
+ uint16_t id = atoi(argv[1]);
+ bool show_stats = false;
+
+ if (argc >= 3)
+ show_stats = true;
+
+ if (!strcmp(argv[0], "nsei")) {
+ nse = gprs_ns2_nse_by_nsei(nsi, id);
+ if (!nse) {
+ return CMD_WARNING;
+ }
+
+ dump_nse(vty, nse, show_stats, false);
+ } else {
+ nsvc = gprs_ns2_nsvc_by_nsvci(nsi, id);
- if (priv.force_vc_mode) {
- if (priv.vc_mode != vc_mode)
- {
- vty_out(vty, "Ignoring use-reset-block because it's already set by %s.%s",
- priv.force_vc_mode_reason, VTY_NEWLINE);
+ if (!nsvc) {
+ vty_out(vty, "No such NS Entity%s", VTY_NEWLINE);
return CMD_WARNING;
}
- return CMD_SUCCESS;
+ ns2_vty_dump_nsvc(vty, nsvc, show_stats);
}
- priv.vc_mode = vc_mode;
+ return CMD_SUCCESS;
+}
- llist_for_each_entry(bind, &vty_nsi->binding, list) {
- gprs_ns2_bind_set_mode(bind, priv.vc_mode);
+static int nsvc_force_unconf_cb(struct gprs_ns2_vc *nsvc, void *ctx)
+{
+ ns2_vc_force_unconfigured(nsvc);
+ ns2_vc_fsm_start(nsvc);
+ return 0;
+}
+
+DEFUN_HIDDEN(nsvc_force_unconf, nsvc_force_unconf_cmd,
+ "nsvc nsei <0-65535> force-unconfigured",
+ "NS Virtual Connection\n"
+ "The NSEI\n"
+ "Reset the NSVCs back to initial state\n"
+ )
+{
+ struct gprs_ns2_inst *nsi = vty_nsi;
+ struct gprs_ns2_nse *nse;
+
+ uint16_t id = atoi(argv[0]);
+
+ nse = gprs_ns2_nse_by_nsei(nsi, id);
+ if (!nse) {
+ vty_out(vty, "Could not find NSE for NSEI %u%s", id, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (!nse->persistent) {
+ gprs_ns2_free_nse(nse);
+ } else if (nse->dialect == GPRS_NS2_DIALECT_SNS) {
+ gprs_ns2_free_nsvcs(nse);
+ } else {
+ /* Perform the operation for all nsvc */
+ gprs_ns2_nse_foreach_nsvc(nse, nsvc_force_unconf_cb, NULL);
}
return CMD_SUCCESS;
}
-DEFUN(cfg_frgre_local_ip, cfg_frgre_local_ip_cmd,
- "encapsulation framerelay-gre local-ip " VTY_IPV46_CMD,
- ENCAPS_STR "NS over Frame Relay over GRE Encapsulation\n"
- "Set the IP address on which we listen for NS/FR/GRE\n"
- "IP Address\n")
+DEFUN(nse_restart_sns, nse_restart_sns_cmd,
+ "nse <0-65535> restart-sns",
+ "NSE specific commands\n"
+ "NS Entity ID (NSEI)\n"
+ "Restart SNS procedure\n")
{
- osmo_sockaddr_str_from_str2(&priv.frgreaddr, argv[0]);
+ struct gprs_ns2_inst *nsi = vty_nsi;
+ struct gprs_ns2_nse *nse;
+
+ uint16_t id = atoi(argv[0]);
+ nse = gprs_ns2_nse_by_nsei(nsi, id);
+ if (!nse) {
+ vty_out(vty, "Could not find NSE for NSEI %u%s", id, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ if (nse->dialect != GPRS_NS2_DIALECT_SNS) {
+ vty_out(vty, "Given NSEI %u doesn't use IP-SNS%s", id, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ gprs_ns2_free_nsvcs(nse);
return CMD_SUCCESS;
}
-DEFUN(cfg_frgre_enable, cfg_frgre_enable_cmd,
- "encapsulation framerelay-gre enabled (1|0)",
- ENCAPS_STR "NS over Frame Relay over GRE Encapsulation\n"
- "Enable or disable Frame Relay over GRE\n"
- "Enable\n" "Disable\n")
+DEFUN(nsvc_block, nsvc_block_cmd,
+ "nsvc <0-65535> (block|unblock|reset)",
+ "NS Virtual Connection\n"
+ NSVCI_STR
+ "Block a NSVC. As cause code O&M intervention will be used.\n"
+ "Unblock a NSVC. As cause code O&M intervention will be used.\n"
+ "Reset a NSVC. As cause code O&M intervention will be used.\n")
{
- int enabled = atoi(argv[0]);
+ struct gprs_ns2_inst *nsi = vty_nsi;
+ struct gprs_ns2_vc *nsvc;
+ int rc;
- priv.frgre = enabled;
+ uint16_t id = atoi(argv[0]);
+
+ nsvc = gprs_ns2_nsvc_by_nsvci(nsi, id);
+ if (!nsvc) {
+ vty_out(vty, "Could not find NSVCI %05u%s", id, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (!strcmp(argv[1], "block")) {
+ rc = ns2_vc_block(nsvc);
+ switch (rc) {
+ case 0:
+ vty_out(vty, "The NS-VC %05u will be blocked.%s", id, VTY_NEWLINE);
+ return CMD_SUCCESS;
+ case -EALREADY:
+ vty_out(vty, "The NS-VC %05u is already blocked.%s", id, VTY_NEWLINE);
+ return CMD_ERR_NOTHING_TODO;
+ default:
+ vty_out(vty, "An unknown error %d happend on NS-VC %05u.%s", rc, id, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ } else if (!strcmp(argv[1], "unblock")) {
+ rc = ns2_vc_unblock(nsvc);
+ switch (rc) {
+ case 0:
+ vty_out(vty, "The NS-VC %05u will be unblocked.%s", id, VTY_NEWLINE);
+ return CMD_SUCCESS;
+ case -EALREADY:
+ vty_out(vty, "The NS-VC %05u is already unblocked.%s", id, VTY_NEWLINE);
+ return CMD_ERR_NOTHING_TODO;
+ default:
+ vty_out(vty, "An unknown error %d happend on NS-VC %05u.%s", rc, id, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ } else {
+ ns2_vc_reset(nsvc);
+ vty_out(vty, "The NS-VC %05u has been resetted.%s", id, VTY_NEWLINE);
+ }
return CMD_SUCCESS;
}
-/* TODO: allow vty to reset/block/unblock nsvc/nsei */
+static void log_set_nse_filter(struct log_target *target,
+ struct gprs_ns2_nse *nse)
+{
+ if (nse) {
+ target->filter_map |= (1 << LOG_FLT_GB_NSE);
+ target->filter_data[LOG_FLT_GB_NSE] = nse;
+ } else if (target->filter_data[LOG_FLT_GB_NSE]) {
+ target->filter_map = ~(1 << LOG_FLT_GB_NSE);
+ target->filter_data[LOG_FLT_GB_NSE] = NULL;
+ }
+}
+
+static void log_set_nsvc_filter(struct log_target *target,
+ struct gprs_ns2_vc *nsvc)
+{
+ if (nsvc) {
+ target->filter_map |= (1 << LOG_FLT_GB_NSVC);
+ target->filter_data[LOG_FLT_GB_NSVC] = nsvc;
+ } else if (target->filter_data[LOG_FLT_GB_NSVC]) {
+ target->filter_map = ~(1 << LOG_FLT_GB_NSVC);
+ target->filter_data[LOG_FLT_GB_NSVC] = NULL;
+ }
+}
+
+DEFUN(logging_fltr_nse,
+ logging_fltr_nse_cmd,
+ "logging filter nse nsei <0-65535>",
+ LOGGING_STR FILTER_STR
+ "Filter based on NS Entity\n"
+ "Identify NSE by NSEI\n"
+ "Numeric identifier\n")
+{
+ struct log_target *tgt;
+ struct gprs_ns2_nse *nse;
+ uint16_t id = atoi(argv[0]);
+
+ log_tgt_mutex_lock();
+ tgt = osmo_log_vty2tgt(vty);
+ if (!tgt) {
+ log_tgt_mutex_unlock();
+ return CMD_WARNING;
+ }
+
+ nse = gprs_ns2_nse_by_nsei(vty_nsi, id);
+ if (!nse) {
+ vty_out(vty, "No NSE by that identifier%s", VTY_NEWLINE);
+ log_tgt_mutex_unlock();
+ return CMD_WARNING;
+ }
+
+ log_set_nse_filter(tgt, nse);
+ log_tgt_mutex_unlock();
+ return CMD_SUCCESS;
+}
-/* TODO: add filter for NSEI as ns1 code does */
/* TODO: add filter for single connection by description */
DEFUN(logging_fltr_nsvc,
logging_fltr_nsvc_cmd,
@@ -666,7 +2217,7 @@ DEFUN(logging_fltr_nsvc,
{
struct log_target *tgt;
struct gprs_ns2_vc *nsvc;
- uint16_t id = atoi(argv[1]);
+ uint16_t id = atoi(argv[0]);
log_tgt_mutex_lock();
tgt = osmo_log_vty2tgt(vty);
@@ -687,147 +2238,87 @@ DEFUN(logging_fltr_nsvc,
return CMD_SUCCESS;
}
-int gprs_ns2_vty_init(struct gprs_ns2_inst *nsi)
+/*! initialized a reduced vty interface which excludes the configuration nodes besides timeouts.
+ * This can be used by the PCU which can be only configured by the BTS/BSC and not by the vty.
+ * \param[in] nsi NS instance on which we operate
+ * \return 0 on success.
+ */
+int gprs_ns2_vty_init_reduced(struct gprs_ns2_inst *nsi)
{
- static bool vty_elements_installed = false;
-
vty_nsi = nsi;
- memset(&priv, 0, sizeof(struct ns2_vty_priv));
- INIT_LLIST_HEAD(&priv.vtyvc);
- priv.vc_mode = NS2_VC_MODE_BLOCKRESET;
+ INIT_LLIST_HEAD(&binds);
+ INIT_LLIST_HEAD(&nses);
+ INIT_LLIST_HEAD(&ip_sns_default_binds);
- /* Regression test code may call this function repeatedly, so make sure
- * that VTY elements are not duplicated, which would assert. */
- if (vty_elements_installed)
- return 0;
- vty_elements_installed = true;
+ vty_fr_network = osmo_fr_network_alloc(nsi);
+ if (!vty_fr_network)
+ return -ENOMEM;
- install_element_ve(&show_ns_cmd);
- install_element_ve(&show_ns_stats_cmd);
- install_element_ve(&show_ns_pers_cmd);
- install_element_ve(&show_nse_cmd);
- install_element_ve(&logging_fltr_nsvc_cmd);
+ install_lib_element_ve(&show_ns_cmd);
+ install_lib_element_ve(&show_ns_binds_cmd);
+ install_lib_element_ve(&show_ns_entities_cmd);
+ install_lib_element_ve(&show_ns_pers_cmd);
+ install_lib_element_ve(&show_nse_cmd);
+ install_lib_element_ve(&logging_fltr_nse_cmd);
+ install_lib_element_ve(&logging_fltr_nsvc_cmd);
- install_element(CFG_LOG_NODE, &logging_fltr_nsvc_cmd);
+ install_lib_element(ENABLE_NODE, &nsvc_force_unconf_cmd);
+ install_lib_element(ENABLE_NODE, &nsvc_block_cmd);
+ install_lib_element(ENABLE_NODE, &nse_restart_sns_cmd);
- install_element(CONFIG_NODE, &cfg_ns_cmd);
- install_node(&ns_node, config_write_ns);
- install_element(L_NS_NODE, &cfg_nse_nsvci_cmd);
- install_element(L_NS_NODE, &cfg_nse_remoteip_cmd);
- install_element(L_NS_NODE, &cfg_nse_remoteport_cmd);
- install_element(L_NS_NODE, &cfg_nse_fr_dlci_cmd);
- install_element(L_NS_NODE, &cfg_nse_encaps_cmd);
- install_element(L_NS_NODE, &cfg_nse_remoterole_cmd);
- install_element(L_NS_NODE, &cfg_no_nse_cmd);
- install_element(L_NS_NODE, &cfg_ns_timer_cmd);
- install_element(L_NS_NODE, &cfg_nsip_local_ip_cmd);
- install_element(L_NS_NODE, &cfg_nsip_local_port_cmd);
- install_element(L_NS_NODE, &cfg_nsip_dscp_cmd);
- install_element(L_NS_NODE, &cfg_nsip_res_block_unblock_cmd);
- install_element(L_NS_NODE, &cfg_frgre_enable_cmd);
- install_element(L_NS_NODE, &cfg_frgre_local_ip_cmd);
-
- /* TODO: nsvc/nsei command to reset states or reset/block/unblock nsei/nsvcs */
+ install_lib_element(CFG_LOG_NODE, &logging_fltr_nse_cmd);
+ install_lib_element(CFG_LOG_NODE, &logging_fltr_nsvc_cmd);
- return 0;
-}
-
-/*!
- * \brief gprs_ns2_vty_create parse the vty tree into ns nodes
- * It has to be in different steps to ensure the bind is created before creating VCs.
- * \return 0 on success
- */
-int gprs_ns2_vty_create() {
- struct ns2_vty_vc *vtyvc;
- struct gprs_ns2_vc_bind *bind;
- struct gprs_ns2_nse *nse;
- struct gprs_ns2_vc *nsvc;
- struct osmo_sockaddr sockaddr;
-
- if (!vty_nsi)
- return -1;
-
- /* create binds, only support a single bind. either FR or UDP */
- if (priv.frgre) {
- /* TODO not yet supported !*/
- return -1;
- } else {
- /* UDP */
- osmo_sockaddr_str_to_sockaddr(&priv.udp, &sockaddr.u.sas);
- gprs_ns2_ip_bind(vty_nsi, &sockaddr, priv.dscp, &bind);
- if (!bind) {
- /* TODO: could not bind on the specific address */
- return -1;
- }
- gprs_ns2_bind_set_mode(bind, priv.vc_mode);
- }
-
- /* create vcs */
- llist_for_each_entry(vtyvc, &priv.vtyvc, list) {
- if (strlen(vtyvc->remote.ip) == 0) {
- /* Invalid IP for VC */
- continue;
- }
-
- if (!vtyvc->remote.port) {
- /* Invalid port for VC */
- continue;
- }
-
- if (osmo_sockaddr_str_to_sockaddr(&vtyvc->remote, &sockaddr.u.sas)) {
- /* Invalid sockaddr for VC */
- continue;
- }
-
- nse = gprs_ns2_nse_by_nsei(vty_nsi, vtyvc->nsei);
- if (!nse) {
- nse = gprs_ns2_create_nse(vty_nsi, vtyvc->nsei);
- if (!nse) {
- /* Could not create NSE for VTY */
- continue;
- }
- }
- nse->persistent = true;
-
- if (bind) {
- nsvc = gprs_ns2_ip_connect(bind,
- &sockaddr,
- nse,
- vtyvc->nsvci);
- if (!nsvc) {
- /* Could not create NSVC, connect failed */
- continue;
- }
- nsvc->persistent = true;
- }
- }
+ install_lib_element(CONFIG_NODE, &cfg_ns_cmd);
+ install_node(&ns_node, config_write_ns);
+ /* TODO: convert into osmo timer */
+ install_lib_element(L_NS_NODE, &cfg_ns_timer_cmd);
return 0;
}
-/*!
- * \brief ns2_vty_bind_apply will be called when a new bind is created to apply vty settings
- * \param bind
- * \return
- */
-void ns2_vty_bind_apply(struct gprs_ns2_vc_bind *bind)
-{
- gprs_ns2_bind_set_mode(bind, priv.vc_mode);
-}
-
-/*!
- * \brief ns2_vty_force_vc_mode force a mode and prevents the vty from overwriting it.
- * \param force if true mode and reason will be set. false to allow modification via vty.
- * \param mode
- * \param reason A description shown to the user when a vty command wants to change the mode.
- */
-void gprs_ns2_vty_force_vc_mode(bool force, enum gprs_ns2_vc_mode mode, char *reason)
+int gprs_ns2_vty_init(struct gprs_ns2_inst *nsi)
{
- priv.force_vc_mode = force;
+ int rc = gprs_ns2_vty_init_reduced(nsi);
+ if (rc)
+ return rc;
+
+ install_lib_element(L_NS_NODE, &cfg_ns_nsei_cmd);
+ install_lib_element(L_NS_NODE, &cfg_no_ns_nsei_cmd);
+ install_lib_element(L_NS_NODE, &cfg_ns_bind_cmd);
+ install_lib_element(L_NS_NODE, &cfg_no_ns_bind_cmd);
+
+ install_lib_element(L_NS_NODE, &cfg_ns_ip_sns_default_bind_cmd);
+ install_lib_element(L_NS_NODE, &cfg_no_ns_ip_sns_default_bind_cmd);
+
+ install_node(&ns_bind_node, NULL);
+ install_lib_element(L_NS_BIND_NODE, &cfg_ns_bind_listen_cmd);
+ install_lib_element(L_NS_BIND_NODE, &cfg_no_ns_bind_listen_cmd);
+ install_lib_element(L_NS_BIND_NODE, &cfg_ns_bind_dscp_cmd);
+ install_lib_element(L_NS_BIND_NODE, &cfg_no_ns_bind_dscp_cmd);
+ install_lib_element(L_NS_BIND_NODE, &cfg_ns_bind_priority_cmd);
+ install_lib_element(L_NS_BIND_NODE, &cfg_ns_bind_ip_sns_weight_cmd);
+ install_lib_element(L_NS_BIND_NODE, &cfg_ns_bind_ipaccess_cmd);
+ install_lib_element(L_NS_BIND_NODE, &cfg_no_ns_bind_ipaccess_cmd);
+ install_lib_element(L_NS_BIND_NODE, &cfg_ns_bind_fr_cmd);
+ install_lib_element(L_NS_BIND_NODE, &cfg_no_ns_bind_fr_cmd);
+ install_lib_element(L_NS_BIND_NODE, &cfg_ns_bind_accept_sns_cmd);
+ install_lib_element(L_NS_BIND_NODE, &cfg_no_ns_bind_accept_sns_cmd);
+
+ install_node(&ns_nse_node, NULL);
+ install_lib_element(L_NS_NSE_NODE, &cfg_ns_nse_nsvc_fr_cmd);
+ install_lib_element(L_NS_NSE_NODE, &cfg_no_ns_nse_nsvci_cmd);
+ install_lib_element(L_NS_NSE_NODE, &cfg_no_ns_nse_nsvc_fr_dlci_cmd);
+ install_lib_element(L_NS_NSE_NODE, &cfg_ns_nse_nsvc_udp_cmd);
+ install_lib_element(L_NS_NSE_NODE, &cfg_ns_nse_nsvc_udp_weights_cmd);
+ install_lib_element(L_NS_NSE_NODE, &cfg_no_ns_nse_nsvc_udp_cmd);
+ install_lib_element(L_NS_NSE_NODE, &cfg_ns_nse_nsvc_ipa_cmd);
+ install_lib_element(L_NS_NSE_NODE, &cfg_no_ns_nse_nsvc_ipa_cmd);
+ install_lib_element(L_NS_NSE_NODE, &cfg_ns_nse_ip_sns_remote_cmd);
+ install_lib_element(L_NS_NSE_NODE, &cfg_no_ns_nse_ip_sns_remote_cmd);
+ install_lib_element(L_NS_NSE_NODE, &cfg_ns_nse_ip_sns_bind_cmd);
+ install_lib_element(L_NS_NSE_NODE, &cfg_no_ns_nse_ip_sns_bind_cmd);
- if (force) {
- priv.vc_mode = mode;
- priv.force_vc_mode_reason = reason;
- }
+ return 0;
}
diff --git a/src/gb/gprs_ns_vty.c b/src/gb/gprs_ns_vty.c
index a39e1395..f27bf6a8 100644
--- a/src/gb/gprs_ns_vty.c
+++ b/src/gb/gprs_ns_vty.c
@@ -644,32 +644,32 @@ int gprs_ns_vty_init(struct gprs_ns_inst *nsi)
return 0;
vty_elements_installed = true;
- install_element_ve(&show_ns_cmd);
- install_element_ve(&show_ns_stats_cmd);
- install_element_ve(&show_ns_pers_cmd);
- install_element_ve(&show_nse_cmd);
- install_element_ve(&logging_fltr_nsvc_cmd);
+ install_lib_element_ve(&show_ns_cmd);
+ install_lib_element_ve(&show_ns_stats_cmd);
+ install_lib_element_ve(&show_ns_pers_cmd);
+ install_lib_element_ve(&show_nse_cmd);
+ install_lib_element_ve(&logging_fltr_nsvc_cmd);
- install_element(CFG_LOG_NODE, &logging_fltr_nsvc_cmd);
+ install_lib_element(CFG_LOG_NODE, &logging_fltr_nsvc_cmd);
- install_element(CONFIG_NODE, &cfg_ns_cmd);
+ install_lib_element(CONFIG_NODE, &cfg_ns_cmd);
install_node(&ns_node, config_write_ns);
- install_element(L_NS_NODE, &cfg_nse_nsvci_cmd);
- install_element(L_NS_NODE, &cfg_nse_remoteip_cmd);
- install_element(L_NS_NODE, &cfg_nse_remoteport_cmd);
- install_element(L_NS_NODE, &cfg_nse_fr_dlci_cmd);
- install_element(L_NS_NODE, &cfg_nse_encaps_cmd);
- install_element(L_NS_NODE, &cfg_nse_remoterole_cmd);
- install_element(L_NS_NODE, &cfg_no_nse_cmd);
- install_element(L_NS_NODE, &cfg_ns_timer_cmd);
- install_element(L_NS_NODE, &cfg_nsip_local_ip_cmd);
- install_element(L_NS_NODE, &cfg_nsip_local_port_cmd);
- install_element(L_NS_NODE, &cfg_nsip_dscp_cmd);
- install_element(L_NS_NODE, &cfg_nsip_res_block_unblock_cmd);
- install_element(L_NS_NODE, &cfg_frgre_enable_cmd);
- install_element(L_NS_NODE, &cfg_frgre_local_ip_cmd);
-
- install_element(ENABLE_NODE, &nsvc_nsei_cmd);
+ install_lib_element(L_NS_NODE, &cfg_nse_nsvci_cmd);
+ install_lib_element(L_NS_NODE, &cfg_nse_remoteip_cmd);
+ install_lib_element(L_NS_NODE, &cfg_nse_remoteport_cmd);
+ install_lib_element(L_NS_NODE, &cfg_nse_fr_dlci_cmd);
+ install_lib_element(L_NS_NODE, &cfg_nse_encaps_cmd);
+ install_lib_element(L_NS_NODE, &cfg_nse_remoterole_cmd);
+ install_lib_element(L_NS_NODE, &cfg_no_nse_cmd);
+ install_lib_element(L_NS_NODE, &cfg_ns_timer_cmd);
+ install_lib_element(L_NS_NODE, &cfg_nsip_local_ip_cmd);
+ install_lib_element(L_NS_NODE, &cfg_nsip_local_port_cmd);
+ install_lib_element(L_NS_NODE, &cfg_nsip_dscp_cmd);
+ install_lib_element(L_NS_NODE, &cfg_nsip_res_block_unblock_cmd);
+ install_lib_element(L_NS_NODE, &cfg_frgre_enable_cmd);
+ install_lib_element(L_NS_NODE, &cfg_frgre_local_ip_cmd);
+
+ install_lib_element(ENABLE_NODE, &nsvc_nsei_cmd);
return 0;
}
diff --git a/src/gb/libosmogb.map b/src/gb/libosmogb.map
index d08e85e9..e02273d9 100644
--- a/src/gb/libosmogb.map
+++ b/src/gb/libosmogb.map
@@ -2,7 +2,25 @@ LIBOSMOGB_1.0 {
global:
bssgp_cause_str;
bssgp_create_cell_id;
+bssgp_create_rim_ri;
+bssgp_dec_app_err_cont_nacc;
+bssgp_dec_ran_inf_ack_rim_cont;
+bssgp_dec_ran_inf_err_rim_cont;
+bssgp_dec_ran_inf_req_app_cont_nacc;
+bssgp_dec_ran_inf_req_rim_cont;
+bssgp_dec_ran_inf_app_cont_nacc;
+bssgp_dec_ran_inf_app_err_rim_cont;
+bssgp_dec_ran_inf_rim_cont;
bssgp_pdu_str;
+bssgp_enc_app_err_cont_nacc;
+bssgp_enc_ran_inf_ack_rim_cont;
+bssgp_enc_ran_inf_err_rim_cont;
+bssgp_enc_ran_inf_req_app_cont_nacc;
+bssgp_enc_ran_inf_req_rim_cont;
+bssgp_enc_ran_inf_app_cont_nacc;
+bssgp_enc_ran_inf_app_err_rim_cont;
+bssgp_enc_ran_inf_rim_cont;
+bssgp_encode_rim_pdu;
bssgp_fc_in;
bssgp_fc_init;
bssgp_fc_ms_init;
@@ -12,11 +30,19 @@ bssgp_msgb_alloc;
bssgp_msgb_copy;
bssgp_msgb_tlli_put;
bssgp_msgb_ra_put;
+bssgp_nacc_cause_strs;
bssgp_parse_cell_id;
+bssgp_parse_rim_pdu;
+bssgp_parse_rim_ri;
+bssgp_ran_inf_app_id_strs;
+bssgp_rim_routing_info_discr_strs;
+bssgp_rim_ri_name_buf;
+bssgp_rim_ri_name;
bssgp_set_bssgp_callback;
bssgp_tx_bvc_block;
bssgp_tx_bvc_reset;
bssgp_tx_bvc_reset2;
+bssgp_tx_bvc_reset_nsei_bvci;
bssgp_tx_bvc_unblock;
bssgp_tx_fc_bvc;
bssgp_tx_fc_ms;
@@ -29,6 +55,7 @@ bssgp_tx_radio_status_tmsi;
bssgp_tx_resume;
bssgp_tx_resume_ack;
bssgp_tx_resume_nack;
+bssgp_tx_rim;
bssgp_tx_simple_bvci;
bssgp_tx_status;
bssgp_tx_suspend;
@@ -44,6 +71,45 @@ bssgp_tx_paging;
bssgp_vty_init;
bssgp_nsi;
+bssgp2_nsi_tx_ptp;
+bssgp2_nsi_tx_sig;
+bssgp2_dec_fc_bvc;
+bssgp2_dec_fc_ms;
+bssgp2_enc_bvc_block;
+bssgp2_enc_bvc_block_ack;
+bssgp2_enc_bvc_unblock;
+bssgp2_enc_bvc_unblock_ack;
+bssgp2_enc_bvc_reset;
+bssgp2_enc_bvc_reset_ack;
+bssgp2_enc_fc_bvc;
+bssgp2_enc_fc_bvc_ack;
+bssgp2_enc_fc_ms;
+bssgp2_enc_fc_ms_ack;
+bssgp2_enc_flush_ll;
+bssgp2_enc_status;
+
+bssgp_bvc_fsm_alloc_sig_bss;
+bssgp_bvc_fsm_alloc_ptp_bss;
+bssgp_bvc_fsm_alloc_sig_sgsn;
+bssgp_bvc_fsm_alloc_ptp_sgsn;
+bssgp_bvc_fsm_set_ops;
+bssgp_bvc_fsm_is_unblocked;
+bssgp_bvc_fsm_get_block_cause;
+bssgp_bvc_fsm_get_features_advertised;
+bssgp_bvc_fsm_get_features_received;
+bssgp_bvc_fsm_get_features_negotiated;
+bssgp_bvc_fsm_set_max_pdu_len;
+bssgp_bvc_fsm_get_max_pdu_len;
+
+osmo_fr_network_alloc;
+osmo_fr_network_free;
+osmo_fr_link_alloc;
+osmo_fr_link_free;
+osmo_fr_dlc_alloc;
+osmo_fr_rx;
+osmo_fr_tx_dlc;
+osmo_fr_role_names;
+
gprs_ns_signal_ns_names;
gprs_ns_pdu_strings;
gprs_ns_cause_str;
@@ -72,39 +138,64 @@ gprs_ns_ll_copy;
gprs_ns_ll_clear;
gprs_ns_msgb_alloc;
-gprs_ns2_bind_set_mode;
-gprs_ns2_cause_str;
+gprs_ns2_aff_cause_prim_strs;
+gprs_ns2_bind_by_name;
+gprs_ns2_cause_strs;
gprs_ns2_create_nse;
-gprs_ns2_dynamic_create_nse;
+gprs_ns2_create_nse2;
gprs_ns2_find_vc_by_sockaddr;
gprs_ns2_free;
gprs_ns2_free_bind;
+gprs_ns2_free_binds;
gprs_ns2_free_nse;
+gprs_ns2_free_nses;
gprs_ns2_free_nsvc;
+gprs_ns2_free_nsvcs;
gprs_ns2_frgre_bind;
+gprs_ns2_fr_bind;
+gprs_ns2_fr_bind_netif;
+gprs_ns2_fr_bind_by_netif;
+gprs_ns2_fr_connect;
+gprs_ns2_fr_nsvc_by_dlci;
+gprs_ns2_fr_nsvc_dlci;
+gprs_ns2_is_fr_bind;
+gprs_ns2_find_vc_by_dlci;
gprs_ns2_instantiate;
gprs_ns2_ip_bind;
+gprs_ns2_ip_bind_by_sockaddr;
gprs_ns2_ip_bind_set_dscp;
+gprs_ns2_ip_bind_set_priority;
+gprs_ns2_ip_bind_set_sns_weight;
gprs_ns2_ip_bind_sockaddr;
gprs_ns2_ip_connect;
gprs_ns2_ip_connect2;
gprs_ns2_ip_connect_inactive;
-gprs_ns2_ip_connect_sns;
-gprs_ns2_ip_vc_sockaddr;
+gprs_ns2_ip_vc_local;
+gprs_ns2_ip_vc_remote;
+gprs_ns2_ip_vc_equal;
gprs_ns2_is_frgre_bind;
gprs_ns2_is_ip_bind;
gprs_ns2_ll_str;
gprs_ns2_ll_str_buf;
gprs_ns2_ll_str_c;
+gprs_ns2_lltype_strs;
gprs_ns2_nse_by_nsei;
+gprs_ns2_nse_foreach_nsvc;
+gprs_ns2_nse_nsei;
+gprs_ns2_nse_sns_remote;
gprs_ns2_nsvc_by_nsvci;
gprs_ns2_nsvc_by_sockaddr;
+gprs_ns2_nsvc_state_name;
+gprs_ns2_prim_strs;
gprs_ns2_recv_prim;
gprs_ns2_reset_persistent_nsvcs;
gprs_ns2_start_alive_all_nsvcs;
-gprs_ns2_vty_create;
-gprs_ns2_vty_force_vc_mode;
+gprs_ns2_sns_add_bind;
+gprs_ns2_sns_add_endpoint;
+gprs_ns2_sns_del_bind;
+gprs_ns2_sns_del_endpoint;
gprs_ns2_vty_init;
+gprs_ns2_vty_init_reduced;
gprs_nsvc_create2;
gprs_nsvc_delete;
@@ -120,5 +211,7 @@ bssgp_bvc_ctx_free;
btsctx_by_bvci_nsei;
btsctx_by_raid_cid;
+osmo_pdef_bssgp;
+
local: *;
};
diff --git a/src/gsm/Makefile.am b/src/gsm/Makefile.am
index c5232abb..ce0b1f4f 100644
--- a/src/gsm/Makefile.am
+++ b/src/gsm/Makefile.am
@@ -1,10 +1,10 @@
# This is _NOT_ the library release version, it's an API version.
# Please read chapter "Library interface versions" of the libtool documentation
# before making any modifications: https://www.gnu.org/software/libtool/manual/html_node/Versioning.html
-LIBVERSION=15:0:0
+LIBVERSION=19:0:1
-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
@@ -25,27 +25,34 @@ libgsmint_la_SOURCES = a5.c rxlev_stat.c tlv_parser.c comp128.c comp128v23.c \
gsm48_ie.c gsm0808.c sysinfo.c \
gprs_cipher_core.c gprs_rlc.c gsm0480.c abis_nm.c gsm0502.c \
gsm0411_utils.c gsm0411_smc.c gsm0411_smr.c gsm0414.c \
- lapd_core.c lapdm.c kasumi.c gsm29205.c gsm_04_08_gprs.c \
- auth_core.c auth_comp128v1.c auth_comp128v23.c auth_xor.c \
+ lapdm.c kasumi.c gsm29205.c gsm_04_08_gprs.c \
+ auth_core.c auth_comp128v1.c auth_comp128v23.c auth_xor.c auth_xor_2g.c \
auth_milenage.c milenage/aes-encblock.c gea.c \
milenage/aes-internal.c milenage/aes-internal-enc.c \
milenage/milenage.c gan.c ipa.c gsm0341.c apn.c \
gsup.c gsup_sms.c gprs_gea.c gsm0503_conv.c oap.c gsm0808_utils.c \
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
+ gsm29118.c gsm48_rest_octets.c cbsp.c gsm48049.c \
+ gad.c bsslap.c bssmap_le.c kdf.c iuup.c gsm44021.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
+libgsmint_la_SOURCES += kdf/sha256.c kdf/sha256-internal.c \
+ kdf/sha1.c kdf/sha1-internal.c
endif
EXTRA_DIST = libosmogsm.map
+EXTRA_libosmogsm_la_DEPENDENCIES = libosmogsm.map
# Convolutional codes generation
gsm0503_conv.c: $(top_srcdir)/utils/conv_gen.py $(top_srcdir)/utils/conv_codes_gsm.py
diff --git a/src/gsm/a5.c b/src/gsm/a5.c
index 223d3ad8..d223cb76 100644
--- a/src/gsm/a5.c
+++ b/src/gsm/a5.c
@@ -14,10 +14,6 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
/*! \addtogroup a5
diff --git a/src/gsm/abis_nm.c b/src/gsm/abis_nm.c
index 13837d2b..38cd1948 100644
--- a/src/gsm/abis_nm.c
+++ b/src/gsm/abis_nm.c
@@ -610,6 +610,10 @@ const struct value_string abis_nm_obj_class_names[] = {
{ NM_OC_RADIO_CARRIER, "RADIO-CARRIER" },
{ NM_OC_BASEB_TRANSC, "BASEBAND-TRANSCEIVER" },
{ NM_OC_CHANNEL, "CHANNEL" },
+ { NM_OC_IPAC_E1_TRUNK, "IPAC-E1-TRUNK" },
+ { NM_OC_IPAC_E1_PORT, "IPAC-E1-PORT" },
+ { NM_OC_IPAC_E1_CHAN, "IPAC-E1-CHAN" },
+ { NM_OC_IPAC_CLK_MODULE,"IPAC-CLK-MODULE" },
{ NM_OC_BS11_ADJC, "ADJC" },
{ NM_OC_BS11_HANDOVER, "HANDOVER" },
{ NM_OC_BS11_PWR_CTRL, "POWER-CONTROL" },
@@ -699,7 +703,7 @@ static const enum abis_nm_chan_comb chcomb4pchan[] = {
[GSM_PCHAN_UNKNOWN] = 0xff,
[GSM_PCHAN_CCCH_SDCCH4_CBCH] = NM_CHANC_BCCH_CBCH,
[GSM_PCHAN_SDCCH8_SACCH8C_CBCH] = NM_CHANC_SDCCH_CBCH,
- [GSM_PCHAN_TCH_F_TCH_H_PDCH] = NM_CHANC_OSMO_TCHFull_TCHHalf_PDCH,
+ [GSM_PCHAN_OSMO_DYN] = NM_CHANC_OSMO_DYN,
/* FIXME: bounds check */
};
diff --git a/src/gsm/apn.c b/src/gsm/apn.c
index a7074eab..4800702c 100644
--- a/src/gsm/apn.c
+++ b/src/gsm/apn.c
@@ -24,8 +24,8 @@
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
-#include <talloc.h>
+#include <osmocom/core/talloc.h>
#include <osmocom/gsm/apn.h>
#define APN_OI_GPRS_FMT "mnc%03u.mcc%03u.gprs"
diff --git a/src/gsm/auth_comp128v1.c b/src/gsm/auth_comp128v1.c
index 493ebfd0..ba3cf601 100644
--- a/src/gsm/auth_comp128v1.c
+++ b/src/gsm/auth_comp128v1.c
@@ -17,10 +17,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <osmocom/crypt/auth.h>
diff --git a/src/gsm/auth_comp128v23.c b/src/gsm/auth_comp128v23.c
index 279d2b74..91fb2597 100644
--- a/src/gsm/auth_comp128v23.c
+++ b/src/gsm/auth_comp128v23.c
@@ -19,10 +19,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <osmocom/crypt/auth.h>
diff --git a/src/gsm/auth_core.c b/src/gsm/auth_core.c
index 9e750a01..ce6ba7df 100644
--- a/src/gsm/auth_core.c
+++ b/src/gsm/auth_core.c
@@ -14,10 +14,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include "config.h"
@@ -212,8 +208,9 @@ 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" },
{ 0, NULL }
};
diff --git a/src/gsm/auth_milenage.c b/src/gsm/auth_milenage.c
index 95891000..4a430b3c 100644
--- a/src/gsm/auth_milenage.c
+++ b/src/gsm/auth_milenage.c
@@ -17,10 +17,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <osmocom/crypt/auth.h>
diff --git a/src/gsm/auth_xor.c b/src/gsm/auth_xor.c
index 36e006e6..81076bda 100644
--- a/src/gsm/auth_xor.c
+++ b/src/gsm/auth_xor.c
@@ -20,10 +20,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <string.h>
@@ -172,8 +168,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..9efe5cc2
--- /dev/null
+++ b/src/gsm/auth_xor_2g.c
@@ -0,0 +1,79 @@
+/*! \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_data *aud,
+ const uint8_t *_rand)
+{
+ uint8_t res1[16];
+
+ 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
new file mode 100644
index 00000000..70ded13f
--- /dev/null
+++ b/src/gsm/bsslap.c
@@ -0,0 +1,325 @@
+/* 3GPP TS 48.071 BSSLAP protocol definitions */
+/*
+ * (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Neels Hofmeyr <neels@hofmeyr.de>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <errno.h>
+
+#include <osmocom/core/msgb.h>
+#include <osmocom/gsm/bsslap.h>
+#include <osmocom/gsm/tlv.h>
+
+#include <osmocom/core/logging.h>
+
+/*! \addtogroup bsslap
+ * @{
+ * \file bsslap.c
+ * Message encoding and decoding for 3GPP TS 48.071 BSSLAP protocol.
+ */
+
+static const struct tlv_definition osmo_bsslap_tlvdef = {
+ .def = {
+ [BSSLAP_IEI_TA] = { TLV_TYPE_TV },
+ [BSSLAP_IEI_CELL_ID] = { TLV_TYPE_FIXED, 2 },
+ [BSSLAP_IEI_CHAN_DESC] = { TLV_TYPE_FIXED, 3 },
+ [BSSLAP_IEI_MEAS_REP] = { TLV_TYPE_TLV },
+ [BSSLAP_IEI_CAUSE] = { TLV_TYPE_TV },
+ [BSSLAP_IEI_RRLP_FLAG] = { TLV_TYPE_TV },
+ [BSSLAP_IEI_RRLP] = { TLV_TYPE_TLV },
+ [BSSLAP_IEI_CELL_ID_LIST] = { TLV_TYPE_TLV },
+ [BSSLAP_IEI_ENH_MEAS_REP] = { TLV_TYPE_TLV },
+ [BSSLAP_IEI_LAC] = { TLV_TYPE_TLV },
+ [BSSLAP_IEI_FREQ_LIST] = { TLV_TYPE_TLV },
+ [BSSLAP_IEI_MS_POWER] = { TLV_TYPE_TV },
+ [BSSLAP_IEI_DELTA_TIMER] = { TLV_TYPE_TV },
+ [BSSLAP_IEI_SERVING_CELL_ID] = { TLV_TYPE_TLV },
+ [BSSLAP_IEI_ENCR_KEY] = { TLV_TYPE_FIXED, 8 },
+ [BSSLAP_IEI_CIPH_MODE_SET] = { TLV_TYPE_TV },
+ [BSSLAP_IEI_CHAN_MODE] = { TLV_TYPE_TV, 2 },
+ [BSSLAP_IEI_MR_CONFIG] = { TLV_TYPE_TLV },
+ [BSSLAP_IEI_POLLING_REPETITION] = { TLV_TYPE_TV },
+ [BSSLAP_IEI_PACKET_CHAN_DESC] = { TLV_TYPE_FIXED, 4 },
+ [BSSLAP_IEI_TLLI] = { TLV_TYPE_FIXED, 4 },
+ [BSSLAP_IEI_TFI] = { TLV_TYPE_TLV },
+ [BSSLAP_IEI_TBF_START_TIME] = { TLV_TYPE_FIXED, 2 },
+ [BSSLAP_IEI_PWRUP_START_TIME] = { TLV_TYPE_TLV },
+ [BSSLAP_IEI_LONG_ENCR_KEY] = { TLV_TYPE_FIXED, 16 },
+ [BSSLAP_IEI_CONCUR_POS_PROC_F] = { TLV_TYPE_TV },
+ },
+};
+
+#define DEC_ERR(RC, MSG_TYPE, IEI, CAUSE, fmt, args...) do { \
+ if (err && !*err) { \
+ *err = talloc_zero(err_ctx, struct osmo_bsslap_err); \
+ **err = (struct osmo_bsslap_err){ \
+ .rc = (RC), \
+ .msg_type = (MSG_TYPE), \
+ .iei = (IEI), \
+ .cause = (CAUSE), \
+ .logmsg = talloc_asprintf(*err, "Error decoding BSSLAP%s%s%s%s%s: " fmt, \
+ (MSG_TYPE) >= 0 ? " " : "", \
+ (MSG_TYPE) >= 0 ? osmo_bsslap_msgt_name(MSG_TYPE) : "", \
+ (IEI) >= 0 ? ": " : "", \
+ (IEI) >= 0 ? osmo_bsslap_iei_name(IEI) : "", \
+ (IEI) >= 0 ? " IE" : "", \
+##args), \
+ }; \
+ } \
+ return RC; \
+ } while(0)
+
+static void osmo_bsslap_ie_enc_cell_id(struct msgb *msg, uint16_t cell_id)
+{
+ msgb_put_u8(msg, BSSLAP_IEI_CELL_ID);
+ msgb_put_u16(msg, cell_id);
+}
+
+static int osmo_bsslap_ie_dec_cell_id(uint16_t *cell_id,
+ enum bsslap_msgt msgt, enum bsslap_iei iei,
+ struct osmo_bsslap_err **err, void *err_ctx,
+ const uint8_t *data, size_t len)
+{
+ if (len != 2)
+ DEC_ERR(-EINVAL, msgt, iei, LCS_CAUSE_UNSPECIFIED, "Expected 2 bytes, got %zu", len);
+ *cell_id = osmo_load16be(data);
+ return 0;
+}
+
+static void osmo_bsslap_ie_enc_ta(struct msgb *msg, uint8_t ta)
+{
+ msgb_put_u8(msg, BSSLAP_IEI_TA);
+ msgb_put_u8(msg, ta);
+}
+
+static int osmo_bsslap_ie_dec_ta(uint8_t *ta,
+ enum bsslap_msgt msgt, enum bsslap_iei iei,
+ struct osmo_bsslap_err **err, void *err_ctx,
+ const uint8_t *data, size_t len)
+{
+ if (len != 1)
+ DEC_ERR(-EINVAL, msgt, iei, LCS_CAUSE_UNSPECIFIED, "Expected 1 byte, got %zu", len);
+ *ta = data[0];
+ return 0;
+}
+
+static void osmo_bsslap_ie_enc_cause(struct msgb *msg, enum bsslap_cause cause)
+{
+ msgb_put_u8(msg, BSSLAP_IEI_CAUSE);
+ msgb_put_u8(msg, cause);
+}
+
+static int osmo_bsslap_ie_dec_cause(enum bsslap_cause *cause,
+ enum bsslap_msgt msgt, enum bsslap_iei iei,
+ struct osmo_bsslap_err **err, void *err_ctx,
+ const uint8_t *data, size_t len)
+{
+ if (len != 1)
+ DEC_ERR(-EINVAL, msgt, iei, LCS_CAUSE_UNSPECIFIED, "Expected 1 byte, got %zu", len);
+ *cause = data[0];
+ return 0;
+}
+
+static void osmo_bsslap_ie_enc_chan_desc(struct msgb *msg, const struct gsm48_chan_desc *chan_desc)
+{
+ struct gsm48_chan_desc *put_chan_desc;
+ msgb_put_u8(msg, BSSLAP_IEI_CHAN_DESC);
+ put_chan_desc = (void*)msgb_put(msg, sizeof(*chan_desc));
+ *put_chan_desc = *chan_desc;
+}
+
+static int osmo_bsslap_ie_dec_chan_desc(struct gsm48_chan_desc *chan_desc,
+ enum bsslap_msgt msgt, enum bsslap_iei iei,
+ struct osmo_bsslap_err **err, void *err_ctx,
+ const uint8_t *data, size_t len)
+{
+ if (len != sizeof(*chan_desc))
+ DEC_ERR(-EINVAL, msgt, iei, LCS_CAUSE_UNSPECIFIED, "Expected %zu bytes, got %zu",
+ sizeof(*chan_desc), len);
+ *chan_desc = *(struct gsm48_chan_desc*)data;
+ return 0;
+}
+
+/*! Encode BSSLAP PDU and append to msgb (3GPP TS 48.071).
+ * \param[out] msg msgb to append to.
+ * \param[in] pdu PDU data to encode.
+ * \return number of bytes written, negative on error.
+ */
+int osmo_bsslap_enc(struct msgb *msg, const struct bsslap_pdu *pdu)
+{
+ uint8_t *old_tail = msg->tail;
+
+ msgb_put_u8(msg, pdu->msg_type);
+
+ switch (pdu->msg_type) {
+ case BSSLAP_MSGT_TA_REQUEST:
+ /* The TA Request message contains only the message type. */
+ break;
+
+ case BSSLAP_MSGT_TA_RESPONSE:
+ osmo_bsslap_ie_enc_cell_id(msg, pdu->ta_response.cell_id);
+ osmo_bsslap_ie_enc_ta(msg, pdu->ta_response.ta);
+ break;
+
+ case BSSLAP_MSGT_REJECT:
+ osmo_bsslap_ie_enc_cause(msg, pdu->reject);
+ break;
+
+ case BSSLAP_MSGT_RESET:
+ osmo_bsslap_ie_enc_cell_id(msg, pdu->reset.cell_id);
+ osmo_bsslap_ie_enc_ta(msg, pdu->reset.ta);
+ osmo_bsslap_ie_enc_chan_desc(msg, &pdu->reset.chan_desc);
+ osmo_bsslap_ie_enc_cause(msg, pdu->reset.cause);
+ break;
+
+ case BSSLAP_MSGT_ABORT:
+ osmo_bsslap_ie_enc_cause(msg, pdu->abort);
+ break;
+
+ case BSSLAP_MSGT_TA_LAYER3:
+ osmo_bsslap_ie_enc_ta(msg, pdu->ta_layer3.ta);
+ break;
+
+ default:
+ return -ENOTSUP;
+ }
+ return (msg->tail - old_tail);
+}
+
+/*! Decode BSSLAP PDU (3GPP TS 48.071).
+ * \param[out] pdu Write decoded values here.
+ * \param[out] err Returned pointer to error info, dynamically allocated; NULL to not return any.
+ * \param[in] err_ctx Talloc context to allocate err from, if required.
+ * \param[in] data Pointer to BSSLAP PDU raw data.
+ * \param[in] len Data length to decode.
+ * \return 0 on success, negative on error.
+ */
+int osmo_bsslap_dec(struct bsslap_pdu *pdu,
+ struct osmo_bsslap_err **err, void *err_ctx,
+ const uint8_t *data, size_t len)
+{
+ const uint8_t *ies_start;
+ int ies_len;
+ struct tlv_parsed tp;
+
+ memset(pdu, 0x00, sizeof(*pdu));
+ if (err)
+ *err = NULL;
+
+#define DEC_IE_MANDATORY(IEI, DEC_FUN, DEC_FUN_ARG) do { \
+ const struct tlv_p_entry *e; \
+ int rc; \
+ if (!(e = TLVP_GET(&tp, IEI))) \
+ DEC_ERR(-EINVAL, pdu->msg_type, IEI, LCS_CAUSE_DATA_MISSING_IN_REQ, "missing mandatory IE"); \
+ rc = DEC_FUN(DEC_FUN_ARG, pdu->msg_type, IEI, err, err_ctx, e->val, e->len); \
+ if (rc) \
+ DEC_ERR(rc, pdu->msg_type, IEI, LCS_CAUSE_UNSPECIFIED, "cannot parse IE"); \
+ } while (0)
+
+ if (len < 1)
+ DEC_ERR(-EINVAL, -1, -1, LCS_CAUSE_UNSPECIFIED, "PDU too short: %zu b", len);
+
+ pdu->msg_type = data[0];
+
+ if (pdu->msg_type == BSSLAP_MSGT_TA_REQUEST) {
+ /* The TA Request message contains only the message type. */
+ return 0;
+ }
+
+ ies_start = &data[1];
+ ies_len = len - 1;
+
+ if (tlv_parse2(&tp, 1, &osmo_bsslap_tlvdef, ies_start, ies_len, 0, 0) <= 0)
+ DEC_ERR(-EINVAL, pdu->msg_type, -1, LCS_CAUSE_UNSPECIFIED, "failed to parse TLV structure");
+
+ switch (pdu->msg_type) {
+
+ case BSSLAP_MSGT_TA_RESPONSE:
+ DEC_IE_MANDATORY(BSSLAP_IEI_CELL_ID, osmo_bsslap_ie_dec_cell_id, &pdu->ta_response.cell_id);
+ DEC_IE_MANDATORY(BSSLAP_IEI_TA, osmo_bsslap_ie_dec_ta, &pdu->ta_response.ta);
+ return 0;
+
+ case BSSLAP_MSGT_REJECT:
+ DEC_IE_MANDATORY(BSSLAP_IEI_CAUSE, osmo_bsslap_ie_dec_cause, &pdu->reject);
+ return 0;
+
+ case BSSLAP_MSGT_RESET:
+ DEC_IE_MANDATORY(BSSLAP_IEI_CELL_ID, osmo_bsslap_ie_dec_cell_id, &pdu->reset.cell_id);
+ DEC_IE_MANDATORY(BSSLAP_IEI_TA, osmo_bsslap_ie_dec_ta, &pdu->reset.ta);
+ DEC_IE_MANDATORY(BSSLAP_IEI_CHAN_DESC, osmo_bsslap_ie_dec_chan_desc, &pdu->reset.chan_desc);
+ DEC_IE_MANDATORY(BSSLAP_IEI_CAUSE, osmo_bsslap_ie_dec_cause, &pdu->reset.cause);
+ return 0;
+
+ case BSSLAP_MSGT_ABORT:
+ DEC_IE_MANDATORY(BSSLAP_IEI_CAUSE, osmo_bsslap_ie_dec_cause, &pdu->abort);
+ return 0;
+
+ case BSSLAP_MSGT_TA_LAYER3:
+ DEC_IE_MANDATORY(BSSLAP_IEI_TA, osmo_bsslap_ie_dec_ta, &pdu->ta_layer3.ta);
+ return 0;
+
+ default:
+ DEC_ERR(-EINVAL, pdu->msg_type, -1, LCS_CAUSE_UNSPECIFIED, "Unsupported message type");
+ }
+}
+
+const struct value_string osmo_bsslap_msgt_names[] = {
+ { BSSLAP_MSGT_TA_REQUEST, "TA Request" },
+ { BSSLAP_MSGT_TA_RESPONSE, "TA Response" },
+ { BSSLAP_MSGT_REJECT, "Reject" },
+ { BSSLAP_MSGT_RESET, "Reset" },
+ { BSSLAP_MSGT_ABORT, "Abort" },
+ { BSSLAP_MSGT_TA_LAYER3, "TA Layer3" },
+ { BSSLAP_MSGT_MS_POS_CMD, "MS Position Command" },
+ { BSSLAP_MSGT_MS_POS_RESP, "MS Position Response" },
+ { BSSLAP_MSGT_UTDOA_REQ, "U-TDOA Request" },
+ { BSSLAP_MSGT_UTDOA_RESP, "U-TDOA Response" },
+ {}
+};
+
+const struct value_string osmo_bsslap_iei_names[] = {
+ { BSSLAP_IEI_TA, "Timing Advance" },
+ { BSSLAP_IEI_CELL_ID, "Cell Identity" },
+ { BSSLAP_IEI_CHAN_DESC, "Channel Description" },
+ { BSSLAP_IEI_MEAS_REP, "Measurement Report" },
+ { BSSLAP_IEI_CAUSE, "Cause" },
+ { BSSLAP_IEI_RRLP_FLAG, "RRLP Flag" },
+ { BSSLAP_IEI_RRLP, "RRLP" },
+ { BSSLAP_IEI_CELL_ID_LIST, "Cell Identity List" },
+ { BSSLAP_IEI_ENH_MEAS_REP, "Enhanced Measurement Report" },
+ { BSSLAP_IEI_LAC, "Location Area Code" },
+ { BSSLAP_IEI_FREQ_LIST, "Frequency List" },
+ { BSSLAP_IEI_MS_POWER, "MS Power" },
+ { BSSLAP_IEI_DELTA_TIMER, "Delta Timer" },
+ { BSSLAP_IEI_SERVING_CELL_ID, "Serving Cell Identifier" },
+ { BSSLAP_IEI_ENCR_KEY, "Encryption Key" },
+ { BSSLAP_IEI_CIPH_MODE_SET, "Cipher Mode Setting" },
+ { BSSLAP_IEI_CHAN_MODE, "Channel Mode" },
+ { BSSLAP_IEI_MR_CONFIG, "MultiRate Configuration" },
+ { BSSLAP_IEI_POLLING_REPETITION, "Polling Repetition" },
+ { BSSLAP_IEI_PACKET_CHAN_DESC, "Packet Channel Description" },
+ { BSSLAP_IEI_TLLI, "TLLI" },
+ { BSSLAP_IEI_TFI, "TFI" },
+ { BSSLAP_IEI_TBF_START_TIME, "TBF Starting Time" },
+ { BSSLAP_IEI_PWRUP_START_TIME, "Powerup Starting Time" },
+ { BSSLAP_IEI_LONG_ENCR_KEY, "Long Encryption Key" },
+ { BSSLAP_IEI_CONCUR_POS_PROC_F, "Concurrent Positioning Flag" },
+ {}
+};
+
+/*! @} */
diff --git a/src/gsm/bssmap_le.c b/src/gsm/bssmap_le.c
new file mode 100644
index 00000000..1ee45517
--- /dev/null
+++ b/src/gsm/bssmap_le.c
@@ -0,0 +1,937 @@
+/* 3GPP TS 49.031 BSSMAP-LE protocol definitions */
+/*
+ * (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Neels Hofmeyr <neels@hofmeyr.de>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <string.h>
+
+#include <osmocom/core/byteswap.h>
+#include <osmocom/core/endian.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/gsm/bssmap_le.h>
+#include <osmocom/gsm/bsslap.h>
+#include <osmocom/gsm/gad.h>
+#include <osmocom/gsm/gsm48.h>
+#include <osmocom/gsm/gsm0808.h>
+
+/*! \addtogroup bssmap_le
+ * @{
+ * \file bssmap_le.c
+ * Message encoding and decoding for 3GPP TS 49.031 BSSMAP-LE.
+ */
+
+#define BSSAP_LE_MSG_SIZE BSSMAP_MSG_SIZE
+#define BSSAP_LE_MSG_HEADROOM BSSMAP_MSG_HEADROOM
+
+static const struct tlv_definition osmo_bssmap_le_tlvdef = {
+ .def = {
+ [BSSMAP_LE_IEI_LCS_QoS] = { TLV_TYPE_TLV },
+ [BSSMAP_LE_IEI_LCS_PRIORITY] = { TLV_TYPE_TLV },
+ [BSSMAP_LE_IEI_LOCATION_TYPE] = { TLV_TYPE_TLV },
+ [BSSMAP_LE_IEI_GANSS_LOCATION_TYPE] = { TLV_TYPE_TLV },
+ [BSSMAP_LE_IEI_GEO_LOCATION] = { TLV_TYPE_TLV },
+ [BSSMAP_LE_IEI_POSITIONING_DATA] = { TLV_TYPE_TLV },
+ [BSSMAP_LE_IEI_GANSS_POS_DATA] = { TLV_TYPE_TLV },
+ [BSSMAP_LE_IEI_VELOCITY_DATA] = { TLV_TYPE_TLV },
+ [BSSMAP_LE_IEI_LCS_CAUSE] = { TLV_TYPE_TLV },
+ [BSSMAP_LE_IEI_LCS_CLIENT_TYPE] = { TLV_TYPE_TLV },
+ [BSSMAP_LE_IEI_APDU] = { TLV_TYPE_TL16V },
+ [BSSMAP_LE_IEI_NET_ELEM_ID] = { TLV_TYPE_TLV },
+ [BSSMAP_LE_IEI_REQ_GPS_ASS_D] = { TLV_TYPE_TLV },
+ [BSSMAP_LE_IEI_REQ_GANSS_ASS_D] = { TLV_TYPE_TLV },
+ [BSSMAP_LE_IEI_DECIPH_KEYS] = { TLV_TYPE_TLV },
+ [BSSMAP_LE_IEI_RET_ERR_REQ] = { TLV_TYPE_TLV },
+ [BSSMAP_LE_IEI_RET_ERR_CAUSE] = { TLV_TYPE_TLV },
+ [BSSMAP_LE_IEI_SEGMENTATION] = { TLV_TYPE_TLV },
+ [BSSMAP_LE_IEI_CLASSMARK3_INFO] = { TLV_TYPE_TLV },
+ [BSSMAP_LE_IEI_CAUSE] = { TLV_TYPE_TLV },
+ [BSSMAP_LE_IEI_CELL_ID] = { TLV_TYPE_TLV },
+ [BSSMAP_LE_IEI_CHOSEN_CHAN] = { TLV_TYPE_TLV },
+ [BSSMAP_LE_IEI_IMSI] = { TLV_TYPE_TLV },
+ [BSSMAP_LE_IEI_LCS_CAPABILITY] = { TLV_TYPE_TLV },
+ [BSSMAP_LE_IEI_PKT_MEAS_REP] = { TLV_TYPE_TLV },
+ [BSSMAP_LE_IEI_CELL_ID_LIST] = { TLV_TYPE_TLV },
+ [BSSMAP_LE_IEI_IMEI] = { TLV_TYPE_TLV },
+ [BSSMAP_LE_IEI_BSS_MLAT_CAP] = { TLV_TYPE_TLV },
+ [BSSMAP_LE_IEI_CELL_INFO_LIST] = { TLV_TYPE_TLV },
+ [BSSMAP_LE_IEI_BTS_RX_ACC_LVL] = { TLV_TYPE_TLV },
+ [BSSMAP_LE_IEI_MLAT_METHOD] = { TLV_TYPE_TLV },
+ [BSSMAP_LE_IEI_MLAT_TA] = { TLV_TYPE_TLV },
+ [BSSMAP_LE_IEI_MS_SYNC_ACC] = { TLV_TYPE_TLV },
+ [BSSMAP_LE_IEI_SHORT_ID_SET] = { TLV_TYPE_TLV },
+ [BSSMAP_LE_IEI_RANDOM_ID_SET] = { TLV_TYPE_TLV },
+ [BSSMAP_LE_IEI_SHORT_BSS_ID] = { TLV_TYPE_TLV },
+ [BSSMAP_LE_IEI_RANDOM_ID] = { TLV_TYPE_TLV },
+ [BSSMAP_LE_IEI_SHORT_ID] = { TLV_TYPE_TLV },
+ [BSSMAP_LE_IEI_COVERAGE_CLASS] = { TLV_TYPE_TLV },
+ [BSSMAP_LE_IEI_MTA_ACC_SEC_RQD] = { TLV_TYPE_TLV },
+ },
+};
+
+#define DEC_ERR_NO_RETURN(RC, MSG_TYPE, IEI, CAUSE, fmt, args...) do { \
+ if (err && !*err) { \
+ *err = talloc_zero(err_ctx, struct osmo_bssmap_le_err); \
+ **err = (struct osmo_bssmap_le_err){ \
+ .rc = (RC), \
+ .msg_type = (MSG_TYPE), \
+ .iei = (IEI), \
+ .cause = (CAUSE), \
+ }; \
+ (*err)->logmsg = talloc_asprintf(*err, "Error decoding BSSMAP-LE%s%s%s%s%s: " fmt, \
+ (MSG_TYPE) >= 0 ? " " : "", \
+ (MSG_TYPE) >= 0 ? osmo_bssmap_le_msgt_name(MSG_TYPE) : "", \
+ (IEI) >= 0 ? ": " : "", \
+ (IEI) >= 0 ? osmo_bssmap_le_iei_name(IEI) : "", \
+ (IEI) >= 0 ? " IE" : "", \
+ ##args); \
+ } \
+ } while(0)
+
+#define DEC_ERR(RC, MSG_TYPE, IEI, CAUSE, fmt, args...) do { \
+ DEC_ERR_NO_RETURN(RC, MSG_TYPE, IEI, CAUSE, fmt, ##args); \
+ return RC; \
+ } while(0)
+
+#define DEC_IE_MANDATORY(MSG_TYPE, IEI, DEC_FUN, DEC_FUN_ARG) do { \
+ const struct tlv_p_entry *e; \
+ int rc; \
+ if (!(e = TLVP_GET(tp, IEI))) \
+ DEC_ERR(-EINVAL, MSG_TYPE, IEI, LCS_CAUSE_DATA_MISSING_IN_REQ, "missing mandatory IE"); \
+ rc = DEC_FUN(DEC_FUN_ARG, MSG_TYPE, IEI, err, err_ctx, e->val, e->len); \
+ if (rc) \
+ DEC_ERR(rc, MSG_TYPE, IEI, LCS_CAUSE_UNSPECIFIED, "cannot parse IE"); \
+ } while (0)
+
+#define DEC_IE_OPTIONAL_FLAG(MSG_TYPE, IEI, DEC_FUN, DEC_FUN_ARG, PRESENCE_FLAG) do { \
+ const struct tlv_p_entry *e; \
+ int rc; \
+ if ((e = TLVP_GET(tp, IEI))) {\
+ rc = DEC_FUN(DEC_FUN_ARG, MSG_TYPE, IEI, err, err_ctx, e->val, e->len); \
+ if (rc) \
+ DEC_ERR(rc, MSG_TYPE, IEI, LCS_CAUSE_UNSPECIFIED, "cannot parse IE"); \
+ PRESENCE_FLAG = true; \
+ } \
+ } while (0)
+
+#define DEC_IE_OPTIONAL(MSG_TYPE, IEI, DEC_FUN, DEC_FUN_ARG) do { \
+ const struct tlv_p_entry *e; \
+ int rc; \
+ if ((e = TLVP_GET(tp, IEI))) {\
+ rc = DEC_FUN(DEC_FUN_ARG, MSG_TYPE, IEI, err, err_ctx, e->val, e->len); \
+ if (rc) \
+ DEC_ERR(rc, MSG_TYPE, IEI, LCS_CAUSE_UNSPECIFIED, "cannot parse IE"); \
+ } \
+ } while (0)
+
+/*! Encode full BSSMAP-LE Location Type IE, including IEI tag and length.
+ * \param[inout] msg Message buffer to append to.
+ * \param[in] location_type Values to enconde.
+ * \returns length of bytes written to the msgb.
+ */
+uint8_t osmo_bssmap_le_ie_enc_location_type(struct msgb *msg,
+ const struct bssmap_le_location_type *location_type)
+{
+ uint8_t *old_tail;
+ uint8_t *tlv_len;
+ OSMO_ASSERT(msg);
+ msgb_put_u8(msg, BSSMAP_LE_IEI_LOCATION_TYPE);
+ tlv_len = msgb_put(msg, 1);
+ old_tail = msg->tail;
+ msgb_put_u8(msg, location_type->location_information);
+
+ switch (location_type->location_information) {
+ case BSSMAP_LE_LOC_INFO_ASSIST_TARGET_MS:
+ case BSSMAP_LE_LOC_INFO_BC_DECIPHER_KEYS:
+ msgb_put_u8(msg, location_type->positioning_method);
+ break;
+ default:
+ break;
+ }
+
+ *tlv_len = (uint8_t) (msg->tail - old_tail);
+ return *tlv_len + 2;
+}
+
+/*! Decode BSSMAP-LE Location Type IE value part.
+ * \param[out] lt Buffer to write decoded values to.
+ * \param[in] elem Pointer to the value part, the V of a TLV.
+ * \param[in] len Length, the L of a TLV.
+ * \returns 0 on success, negative on error; lt is always overwritten: cleared on error, populated with values on
+ * success.
+ */
+int osmo_bssmap_le_ie_dec_location_type(struct bssmap_le_location_type *lt,
+ enum bssmap_le_msgt msgt, enum bssmap_le_iei iei,
+ struct osmo_bssmap_le_err **err, void *err_ctx,
+ const uint8_t *elem, uint8_t len)
+{
+ memset(lt, 0x00, sizeof(*lt));
+
+ if (!elem || len < 1)
+ DEC_ERR(-EINVAL, msgt, iei, LCS_CAUSE_UNSPECIFIED, "zero length");
+
+ lt->location_information = elem[0];
+ switch (lt->location_information) {
+
+ case BSSMAP_LE_LOC_INFO_CURRENT_GEOGRAPHIC:
+ if (len != 1)
+ DEC_ERR(-EINVAL, msgt, iei, LCS_CAUSE_UNSPECIFIED,
+ "location info type 'Current Geographic': length should be 1 byte, got %u", len);
+ lt->positioning_method = BSSMAP_LE_POS_METHOD_OMITTED;
+ return 0;
+
+ case BSSMAP_LE_LOC_INFO_ASSIST_TARGET_MS:
+ case BSSMAP_LE_LOC_INFO_BC_DECIPHER_KEYS:
+ if (len != 2)
+ DEC_ERR(-EINVAL, msgt, iei, LCS_CAUSE_UNSPECIFIED,
+ "location info type %d: length should be 2 bytes, got %u",
+ lt->location_information, len);
+ lt->positioning_method = elem[1];
+ switch (lt->positioning_method) {
+ case BSSMAP_LE_POS_METHOD_MOBILE_ASSISTED_E_OTD:
+ case BSSMAP_LE_POS_METHOD_MOBILE_BASED_E_OTD:
+ case BSSMAP_LE_POS_METHOD_ASSISTED_GPS:
+ return 0;
+ default:
+ DEC_ERR(-EINVAL, msgt, iei, LCS_CAUSE_UNSPECIFIED,
+ "location info type %d: unknown Positioning Method: %d",
+ lt->location_information, lt->positioning_method);
+ }
+
+ default:
+ DEC_ERR(-EINVAL, msgt, iei, LCS_CAUSE_UNSPECIFIED, "unknown location info type %d",
+ lt->location_information);
+ }
+}
+
+/*! Encode full BSSMAP-LE LCS Client Type IE, including IEI tag and length.
+ * \param[inout] msg Message buffer to append to.
+ * \param[in] client_type Value to enconde.
+ * \returns length of bytes written to the msgb.
+ */
+static uint8_t osmo_bssmap_le_ie_enc_lcs_client_type(struct msgb *msg, enum bssmap_le_lcs_client_type client_type)
+{
+ OSMO_ASSERT(msg);
+ msgb_put_u8(msg, BSSMAP_LE_IEI_LCS_CLIENT_TYPE);
+ /* length */
+ msgb_put_u8(msg, 1);
+ msgb_put_u8(msg, client_type);
+ return 3;
+}
+
+static int osmo_bssmap_le_ie_dec_lcs_client_type(enum bssmap_le_lcs_client_type *client_type,
+ enum bssmap_le_msgt msgt, enum bssmap_le_iei iei,
+ struct osmo_bssmap_le_err **err, void *err_ctx,
+ const uint8_t *elem, uint8_t len)
+{
+ *client_type = 0;
+
+ if (!elem || len < 1)
+ DEC_ERR(-EINVAL, msgt, iei, LCS_CAUSE_UNSPECIFIED, "zero length");
+
+ *client_type = elem[0];
+
+ switch (*client_type) {
+ case BSSMAP_LE_LCS_CTYPE_VALUE_ADDED_UNSPECIFIED:
+ case BSSMAP_LE_LCS_CTYPE_PLMN_OPER_UNSPECIFIED:
+ case BSSMAP_LE_LCS_CTYPE_PLMN_OPER_BCAST_SERVICE:
+ case BSSMAP_LE_LCS_CTYPE_PLMN_OPER_OAM:
+ case BSSMAP_LE_LCS_CTYPE_PLMN_OPER_ANON_STATS:
+ case BSSMAP_LE_LCS_CTYPE_PLMN_OPER_TGT_MS_SVC:
+ case BSSMAP_LE_LCS_CTYPE_EMERG_SVC_UNSPECIFIED:
+ case BSSMAP_LE_LCS_CTYPE_LI_UNSPECIFIED:
+ return 0;
+ default:
+ DEC_ERR(-EINVAL, msgt, iei, LCS_CAUSE_UNSPECIFIED, "unknown LCS Client Type: %d", *client_type);
+ }
+}
+
+/*! Encode full BSSMAP-LE LCS Priority IE, including IEI tag and length.
+ * \param[inout] msg Message buffer to append to.
+ * \param[in] priority Value to enconde.
+ * \returns length of bytes written to the msgb.
+ */
+static uint8_t osmo_bssmap_le_ie_enc_lcs_priority(struct msgb *msg, uint8_t priority)
+{
+ OSMO_ASSERT(msg);
+ msgb_put_u8(msg, BSSMAP_LE_IEI_LCS_PRIORITY);
+ /* length */
+ msgb_put_u8(msg, 1);
+ msgb_put_u8(msg, priority);
+ return 3;
+}
+
+static int osmo_bssmap_le_ie_dec_lcs_priority(uint8_t *priority,
+ enum bssmap_le_msgt msgt, enum bssmap_le_iei iei,
+ struct osmo_bssmap_le_err **err, void *err_ctx,
+ const uint8_t *elem, uint8_t len)
+{
+ if (!elem || len != 1)
+ DEC_ERR(-EINVAL, msgt, iei, LCS_CAUSE_UNSPECIFIED, "unexpected length");
+
+ *priority = elem[0];
+ return 0;
+}
+
+/*! Encode full BSSMAP-LE LCS QoS IE, including IEI tag and length.
+ * \param[inout] msg Message buffer to append to.
+ * \param[in] priority Value to enconde.
+ * \returns length of bytes written to the msgb.
+ */
+static uint8_t osmo_bssmap_le_ie_enc_lcs_qos(struct msgb *msg, const struct osmo_bssmap_le_lcs_qos *qos)
+{
+ OSMO_ASSERT(msg);
+ msgb_tlv_put(msg, BSSMAP_LE_IEI_LCS_QoS, sizeof(*qos), (const uint8_t *)qos);
+ return 2 + sizeof(*qos);
+}
+
+static int osmo_bssmap_le_ie_dec_lcs_qos(struct osmo_bssmap_le_lcs_qos *qos,
+ enum bssmap_le_msgt msgt, enum bssmap_le_iei iei,
+ struct osmo_bssmap_le_err **err, void *err_ctx,
+ const uint8_t *elem, uint8_t len)
+{
+ if (!elem || len != sizeof(*qos))
+ DEC_ERR(-EINVAL, msgt, iei, LCS_CAUSE_UNSPECIFIED, "unexpected length");
+
+ memcpy(qos, elem, len);
+ return 0;
+}
+
+/*! Encode the value part of 3GPP TS 49.031 10.13 LCS Cause, without IEI and len.
+ * Identically used in 3GPP TS 48.008 3.2.2.66. Usage example:
+ *
+ * uint8_t *l = msgb_tl_put(msg, BSSMAP_LE_IEI_LCS_CAUSE);
+ * int rc = osmo_lcs_cause_enc(msg, &lcs_cause);
+ * if (rc < 0)
+ * goto error;
+ * *l = rc;
+ *
+ * \param[inout] msg Message buffer to append the LCS Cause values to.
+ * \param[in] lcs_cause LCS Cause values to enconde.
+ * \returns length of bytes written to the msgb.
+ */
+int osmo_lcs_cause_enc(struct msgb *msg, const struct lcs_cause_ie *lcs_cause)
+{
+ msgb_put_u8(msg, lcs_cause->cause_val);
+ if (lcs_cause->cause_val == LCS_CAUSE_POS_METH_FAILURE && lcs_cause->diag_val_present) {
+ msgb_put_u8(msg, lcs_cause->diag_val);
+ return 2;
+ }
+ return 1;
+}
+
+/*! Decode the value part of 3GPP TS 49.031 10.13 LCS Cause, without IEI and len.
+ * Identically used in 3GPP TS 48.008 3.2.2.66.
+ *
+ * \param[out] lcs_cause Write decoded LCS Cause values here.
+ * \param[in] data Encoded cause bytes.
+ * \param[in] len Length of data in bytes.
+ * \returns 0 on success, negative on error.
+ */
+int osmo_lcs_cause_dec(struct lcs_cause_ie *lcs_cause,
+ enum bssmap_le_msgt msgt, enum bssmap_le_iei iei,
+ struct osmo_bssmap_le_err **err, void *err_ctx,
+ const uint8_t *data, uint8_t len)
+{
+ memset(lcs_cause, 0x00, sizeof(*lcs_cause));
+
+ if (!data || len < 1)
+ DEC_ERR(-EINVAL, msgt, iei, LCS_CAUSE_UNSPECIFIED, "zero length");
+
+ lcs_cause->present = true;
+ lcs_cause->cause_val = data[0];
+ if (len > 1) {
+ lcs_cause->diag_val_present = true;
+ lcs_cause->diag_val = data[1];
+ }
+ if (len > 2)
+ DEC_ERR(-EINVAL, msgt, iei, LCS_CAUSE_UNSPECIFIED, "expected length <= 2, got %u", len);
+
+ return 0;
+}
+
+static int osmo_bssmap_le_ie_enc_apdu(struct msgb *msg, const struct bsslap_pdu *bsslap)
+{
+ uint8_t *old_tail;
+ void *l;
+ msgb_put_u8(msg, BSSMAP_LE_IEI_APDU);
+ l = msgb_put(msg, 2);
+ old_tail = msg->tail;
+ msgb_put_u8(msg, BSSMAP_LE_APDU_PROT_BSSLAP);
+ int rc = osmo_bsslap_enc(msg, bsslap);
+ if (rc <= 0)
+ return -EINVAL;
+ osmo_store16be(msg->tail - old_tail, l);
+ return 0;
+}
+
+static int osmo_bssmap_le_ie_dec_apdu(struct bsslap_pdu *bsslap,
+ enum bssmap_le_msgt msgt, enum bssmap_le_iei iei,
+ struct osmo_bssmap_le_err **err, void *err_ctx,
+ const uint8_t *data, size_t len)
+{
+ enum bssmap_le_apdu_proto proto;
+ struct osmo_bsslap_err *bsslap_err;
+
+ if (!data || len < 1)
+ DEC_ERR(-EINVAL, msgt, iei, LCS_CAUSE_UNSPECIFIED, "zero length");
+
+ proto = data[0];
+
+ switch (proto) {
+ case BSSMAP_LE_APDU_PROT_BSSLAP:
+ if (osmo_bsslap_dec(bsslap, &bsslap_err, err_ctx, data + 1, len - 1)) {
+ DEC_ERR_NO_RETURN(bsslap_err ? bsslap_err->rc : -EINVAL,
+ msgt, iei, LCS_CAUSE_UNSPECIFIED,
+ "Error decoding BSSLAP%s%s",
+ bsslap_err && bsslap_err->logmsg ? ": " : "",
+ bsslap_err && bsslap_err->logmsg ? bsslap_err->logmsg : "");
+ (*err)->bsslap_err = bsslap_err;
+ return (*err)->rc;
+ }
+ return 0;
+ case BSSMAP_LE_APDU_PROT_LLP:
+ case BSSMAP_LE_APDU_PROT_SMLCPP:
+ DEC_ERR(-EINVAL, msgt, iei, LCS_CAUSE_UNSPECIFIED, "Unimplemented APDU type: %d", proto);
+ default:
+ DEC_ERR(-EINVAL, msgt, iei, LCS_CAUSE_UNSPECIFIED, "Invalid APDU type: %d", proto);
+ }
+}
+
+static int osmo_bssmap_le_ie_dec_cell_id(struct gsm0808_cell_id *cell_id,
+ enum bssmap_le_msgt msgt, enum bssmap_le_iei iei,
+ struct osmo_bssmap_le_err **err, void *err_ctx,
+ const uint8_t *elem, uint8_t len)
+{
+ int rc;
+ rc = gsm0808_dec_cell_id(cell_id, elem, len);
+ if (rc <= 0)
+ DEC_ERR(rc, msgt, iei, LCS_CAUSE_UNSPECIFIED, "Error decoding Cell Identifier %s",
+ osmo_hexdump_c(err_ctx, elem, len));
+ return 0;
+}
+
+static int osmo_bssmap_le_ie_dec_imsi(struct osmo_mobile_identity *imsi,
+ enum bssmap_le_msgt msgt, enum bssmap_le_iei iei,
+ struct osmo_bssmap_le_err **err, void *err_ctx,
+ const uint8_t *elem, uint8_t len)
+{
+ int rc;
+ rc = osmo_mobile_identity_decode(imsi, elem, len, false);
+ if (rc || imsi->type != GSM_MI_TYPE_IMSI)
+ DEC_ERR(-EINVAL, msgt, iei, LCS_CAUSE_UNSPECIFIED,
+ "cannot parse IMSI identity %s", osmo_hexdump_c(err_ctx, elem, len));
+ return 0;
+}
+
+static int osmo_bssmap_le_ie_dec_imei(struct osmo_mobile_identity *imei,
+ enum bssmap_le_msgt msgt, enum bssmap_le_iei iei,
+ struct osmo_bssmap_le_err **err, void *err_ctx,
+ const uint8_t *elem, uint8_t len)
+{
+ int rc;
+ rc = osmo_mobile_identity_decode(imei, elem, len, false);
+ if (rc || imei->type != GSM_MI_TYPE_IMEI)
+ DEC_ERR(-EINVAL, msgt, iei, LCS_CAUSE_UNSPECIFIED,
+ "cannot parse IMEI identity %s", osmo_hexdump_c(err_ctx, elem, len));
+ return 0;
+}
+
+static int osmo_bssmap_le_ie_dec_gad(union gad_raw *gad,
+ enum bssmap_le_msgt msgt, enum bssmap_le_iei iei,
+ struct osmo_bssmap_le_err **err, void *err_ctx,
+ const uint8_t *elem, uint8_t len)
+{
+ struct osmo_gad_err *gad_err;
+ if (osmo_gad_raw_read(gad, &gad_err, err_ctx, elem, len)) {
+ DEC_ERR_NO_RETURN(gad_err ? gad_err->rc : -EINVAL,
+ msgt, BSSMAP_LE_IEI_GEO_LOCATION, LCS_CAUSE_UNSPECIFIED,
+ "Error decoding GAD%s%s",
+ gad_err && gad_err->logmsg ? ": " : "",
+ gad_err && gad_err->logmsg ? gad_err->logmsg : "");
+ (*err)->gad_err = gad_err;
+ return (*err)->rc;
+ }
+ return 0;
+}
+
+struct osmo_bssap_le_header {
+ uint8_t type;
+ uint8_t length;
+ uint8_t data[0];
+} __attribute__((packed));
+
+/*! Return the BSSMAP-LE msg_type from a BSSAP-LE PDU, e.g. from a msgb_l3().
+ * \param[in] data BSSAP-LE PDU data, starting with BSSAP-LE discriminator.
+ * \param[in] len Length of data in bytes.
+ * \returns bssmap_le_msgt or negative on error or non-BSSMAP-LE discriminator. */
+enum bssmap_le_msgt osmo_bssmap_le_msgt(const uint8_t *data, uint8_t len)
+{
+ const struct osmo_bssap_le_header *h = (void*)data;
+ if (!data || len < sizeof(struct osmo_bssap_le_header) + 1)
+ return -1;
+ if (h->type != BSSAP_LE_MSG_DISCR_BSSMAP_LE)
+ return -1;
+ return h->data[0];
+}
+
+static int osmo_bssmap_le_enc_reset(struct msgb *msg, enum gsm0808_cause cause)
+{
+ /* The BSSMAP-LE Reset Cause is defined as identical to the 3GPP TS 48.008 Cause. */
+ gsm0808_enc_cause(msg, cause);
+ return 0;
+}
+
+static int osmo_bssmap_le_dec_reset(enum gsm0808_cause *cause,
+ enum bssmap_le_msgt msgt,
+ struct osmo_bssmap_le_err **err, void *err_ctx,
+ const struct tlv_parsed *tp)
+{
+ const struct tlv_p_entry *e;
+
+ if (!(e = TLVP_GET(tp, BSSMAP_LE_IEI_CAUSE)))
+ DEC_ERR(-EINVAL, msgt, BSSMAP_LE_IEI_CAUSE, LCS_CAUSE_DATA_MISSING_IN_REQ, "missing mandatory IE");
+
+ *cause = gsm0808_get_cause(tp);
+ if (*cause < 0)
+ DEC_ERR(-EINVAL, msgt, BSSMAP_LE_IEI_CAUSE, LCS_CAUSE_UNSPECIFIED, "cannot parse IE");
+
+ return 0;
+}
+
+static int osmo_bssmap_le_enc_perform_loc_req(struct msgb *msg, const struct bssmap_le_perform_loc_req *params)
+{
+ osmo_bssmap_le_ie_enc_location_type(msg, &params->location_type);
+
+ gsm0808_enc_cell_id(msg, &params->cell_id);
+
+ if (params->lcs_client_type_present)
+ osmo_bssmap_le_ie_enc_lcs_client_type(msg, params->lcs_client_type);
+
+ if (params->more_items && params->lcs_priority_present)
+ osmo_bssmap_le_ie_enc_lcs_priority(msg, params->lcs_priority);
+
+ if (params->more_items && params->lcs_qos_present)
+ osmo_bssmap_le_ie_enc_lcs_qos(msg, &params->lcs_qos);
+
+ if (params->apdu_present) {
+ int rc = osmo_bssmap_le_ie_enc_apdu(msg, &params->apdu);
+ if (rc < 0)
+ return rc;
+ }
+
+ if (params->imsi.type == GSM_MI_TYPE_IMSI) {
+ uint8_t *l = msgb_tl_put(msg, BSSMAP_LE_IEI_IMSI);
+ int rc = osmo_mobile_identity_encode_msgb(msg, &params->imsi, false);
+ if (rc < 0)
+ return rc;
+ *l = rc;
+ }
+
+ if (params->imei.type == GSM_MI_TYPE_IMEI) {
+ uint8_t *l = msgb_tl_put(msg, BSSMAP_LE_IEI_IMEI);
+ int rc = osmo_mobile_identity_encode_msgb(msg, &params->imei, false);
+ if (rc < 0)
+ return rc;
+ *l = rc;
+ }
+ return 0;
+}
+
+static int osmo_bssmap_le_dec_perform_loc_req(struct bssmap_le_perform_loc_req *params,
+ enum bssmap_le_msgt msgt,
+ struct osmo_bssmap_le_err **err, void *err_ctx,
+ const struct tlv_parsed *tp)
+{
+ memset(params, 0x00, sizeof(*params));
+
+ DEC_IE_MANDATORY(msgt, BSSMAP_LE_IEI_LOCATION_TYPE, osmo_bssmap_le_ie_dec_location_type,
+ &params->location_type);
+ DEC_IE_MANDATORY(msgt, BSSMAP_LE_IEI_CELL_ID, osmo_bssmap_le_ie_dec_cell_id,
+ &params->cell_id);
+ DEC_IE_OPTIONAL_FLAG(msgt, BSSMAP_LE_IEI_LCS_CLIENT_TYPE, osmo_bssmap_le_ie_dec_lcs_client_type,
+ &params->lcs_client_type, params->lcs_client_type_present);
+ DEC_IE_OPTIONAL_FLAG(msgt, BSSMAP_LE_IEI_LCS_PRIORITY, osmo_bssmap_le_ie_dec_lcs_priority,
+ &params->lcs_priority, params->lcs_priority_present);
+ DEC_IE_OPTIONAL_FLAG(msgt, BSSMAP_LE_IEI_LCS_QoS, osmo_bssmap_le_ie_dec_lcs_qos,
+ &params->lcs_qos, params->lcs_qos_present);
+ DEC_IE_OPTIONAL_FLAG(msgt, BSSMAP_LE_IEI_APDU, osmo_bssmap_le_ie_dec_apdu, &params->apdu,
+ params->apdu_present);
+ DEC_IE_OPTIONAL(msgt, BSSMAP_LE_IEI_IMSI, osmo_bssmap_le_ie_dec_imsi, &params->imsi);
+ DEC_IE_OPTIONAL(msgt, BSSMAP_LE_IEI_IMEI, osmo_bssmap_le_ie_dec_imei, &params->imei);
+
+ if (params->lcs_priority_present || params->lcs_qos_present)
+ params->more_items = true;
+
+ return 0;
+}
+
+static int osmo_bssmap_le_enc_perform_loc_resp(struct msgb *msg, const struct bssmap_le_perform_loc_resp *params)
+{
+ if (params->location_estimate_present) {
+ uint8_t *l = msgb_tl_put(msg, BSSMAP_LE_IEI_GEO_LOCATION);
+ int rc = osmo_gad_raw_write(msg, &params->location_estimate);
+ if (rc < 0)
+ return rc;
+ *l = rc;
+ }
+
+ if (params->lcs_cause.present) {
+ uint8_t *l = msgb_tl_put(msg, BSSMAP_LE_IEI_LCS_CAUSE);
+ int rc = osmo_lcs_cause_enc(msg, &params->lcs_cause);
+ if (rc < 0)
+ return rc;
+ *l = rc;
+ }
+ return 0;
+}
+
+static int osmo_bssmap_le_dec_perform_loc_resp(struct bssmap_le_perform_loc_resp *params,
+ enum bssmap_le_msgt msgt,
+ struct osmo_bssmap_le_err **err, void *err_ctx,
+ const struct tlv_parsed *tp)
+{
+ 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);
+ DEC_IE_OPTIONAL(msgt, BSSMAP_LE_IEI_LCS_CAUSE, osmo_lcs_cause_dec, &params->lcs_cause);
+
+ return 0;
+}
+
+static int osmo_bssmap_le_enc_perform_loc_abort(struct msgb *msg, const struct lcs_cause_ie *params)
+{
+ uint8_t *l = msgb_tl_put(msg, BSSMAP_LE_IEI_LCS_CAUSE);
+ int rc = osmo_lcs_cause_enc(msg, params);
+ if (rc < 0)
+ return rc;
+ *l = rc;
+ return 0;
+}
+
+static int osmo_bssmap_le_dec_perform_loc_abort(struct lcs_cause_ie *params,
+ enum bssmap_le_msgt msgt,
+ struct osmo_bssmap_le_err **err, void *err_ctx,
+ const struct tlv_parsed *tp)
+{
+ memset(params, 0x00, sizeof(*params));
+
+ DEC_IE_MANDATORY(msgt, BSSMAP_LE_IEI_LCS_CAUSE, osmo_lcs_cause_dec, params);
+ return 0;
+}
+
+static int osmo_bssmap_le_enc_conn_oriented_info(struct msgb *msg,
+ const struct bssmap_le_conn_oriented_info *params)
+{
+ return osmo_bssmap_le_ie_enc_apdu(msg, &params->apdu);
+}
+
+static int osmo_bssmap_le_dec_conn_oriented_info(struct bssmap_le_conn_oriented_info *params,
+ enum bssmap_le_msgt msgt,
+ struct osmo_bssmap_le_err **err, void *err_ctx,
+ const struct tlv_parsed *tp)
+{
+ memset(params, 0x00, sizeof(*params));
+
+ DEC_IE_MANDATORY(msgt, BSSMAP_LE_IEI_APDU, osmo_bssmap_le_ie_dec_apdu, &params->apdu);
+ return 0;
+}
+
+/*! Encode BSSMAP-LE PDU and add to msgb (3GPP TS 49.031).
+ * See also osmo_bssap_le_enc().
+ * \param[out] msg msgb to append to.
+ * \param[in] pdu PDU data to encode.
+ * \return number of bytes written, negative on error.
+ */
+static int osmo_bssmap_le_enc(struct msgb *msg, const struct bssmap_le_pdu *pdu)
+{
+ int rc;
+ uint8_t *old_tail;
+ old_tail = msg->tail;
+
+ msgb_v_put(msg, pdu->msg_type);
+
+ switch (pdu->msg_type) {
+ case BSSMAP_LE_MSGT_RESET:
+ rc = osmo_bssmap_le_enc_reset(msg, pdu->reset);
+ break;
+ case BSSMAP_LE_MSGT_RESET_ACK:
+ /* Consists only of the message type. */
+ rc = 0;
+ break;
+ case BSSMAP_LE_MSGT_PERFORM_LOC_REQ:
+ rc = osmo_bssmap_le_enc_perform_loc_req(msg, &pdu->perform_loc_req);
+ break;
+ case BSSMAP_LE_MSGT_PERFORM_LOC_RESP:
+ rc = osmo_bssmap_le_enc_perform_loc_resp(msg, &pdu->perform_loc_resp);
+ break;
+ case BSSMAP_LE_MSGT_PERFORM_LOC_ABORT:
+ rc = osmo_bssmap_le_enc_perform_loc_abort(msg, &pdu->perform_loc_abort);
+ break;
+ case BSSMAP_LE_MSGT_CONN_ORIENTED_INFO:
+ rc = osmo_bssmap_le_enc_conn_oriented_info(msg, &pdu->conn_oriented_info);
+ break;
+ default:
+ rc = -ENOTSUP;
+ }
+
+ if (rc < 0)
+ return rc;
+
+ return (msg->tail - old_tail);
+}
+
+/*! Decode BSSMAP-LE PDU (3GPP TS 49.031).
+ * See also osmo_bssap_le_dec().
+ * \param[out] pdu Write decoded values here.
+ * \param[in] data Pointer to BSSMAP-LE PDU raw data.
+ * \param[in] len Data length to decode.
+ * \return NULL upon success, a human readable error message on failure.
+ */
+static int osmo_bssmap_le_dec(struct bssmap_le_pdu *pdu,
+ struct osmo_bssmap_le_err **err, void *err_ctx,
+ const uint8_t *data, size_t len)
+{
+ const uint8_t *ies_start;
+ int ies_len;
+ struct tlv_parsed tp;
+
+ memset(pdu, 0x00, sizeof(*pdu));
+
+ if (len < 1)
+ DEC_ERR(-EINVAL, -1, -1, LCS_CAUSE_UNSPECIFIED, "zero length");
+ pdu->msg_type = data[0];
+
+ /* BSSMAP-LE IEs */
+ ies_start = &data[1];
+ ies_len = len - 1;
+
+ if (tlv_parse(&tp, &osmo_bssmap_le_tlvdef, ies_start, ies_len, 0, 0) < 0)
+ DEC_ERR(-EINVAL, pdu->msg_type, -1, LCS_CAUSE_UNSPECIFIED, "failed to parse TLV structure");
+
+ switch (pdu->msg_type) {
+ case BSSMAP_LE_MSGT_RESET:
+ return osmo_bssmap_le_dec_reset(&pdu->reset, pdu->msg_type, err, err_ctx, &tp);
+ case BSSMAP_LE_MSGT_RESET_ACK:
+ /* Consists only of the message type. */
+ return 0;
+ case BSSMAP_LE_MSGT_PERFORM_LOC_REQ:
+ return osmo_bssmap_le_dec_perform_loc_req(&pdu->perform_loc_req, pdu->msg_type, err, err_ctx, &tp);
+ case BSSMAP_LE_MSGT_PERFORM_LOC_RESP:
+ return osmo_bssmap_le_dec_perform_loc_resp(&pdu->perform_loc_resp, pdu->msg_type, err, err_ctx, &tp);
+ case BSSMAP_LE_MSGT_PERFORM_LOC_ABORT:
+ return osmo_bssmap_le_dec_perform_loc_abort(&pdu->perform_loc_abort, pdu->msg_type, err, err_ctx, &tp);
+ case BSSMAP_LE_MSGT_CONN_ORIENTED_INFO:
+ return osmo_bssmap_le_dec_conn_oriented_info(&pdu->conn_oriented_info, pdu->msg_type, err, err_ctx,
+ &tp);
+ default:
+ DEC_ERR(-EINVAL, pdu->msg_type, -1, LCS_CAUSE_UNSPECIFIED, "Unsupported BSSMAP-LE message type");
+ }
+}
+
+/*! Encode BSSAP-LE PDU returned in new msgb (3GPP TS 49.031).
+ * By spec, BSSAP-LE contains either BSSMAP-LE or DTAP.
+ * \param[in] pdu PDU data to encode.
+ * \return msgb with encoded data and l2h set to the start.
+ */
+struct msgb *osmo_bssap_le_enc(const struct bssap_le_pdu *pdu)
+{
+ struct msgb *msg;
+ int rc;
+
+ if (pdu->discr != BSSAP_LE_MSG_DISCR_BSSMAP_LE)
+ return NULL;
+
+ msg = msgb_alloc_headroom(BSSAP_LE_MSG_SIZE, BSSAP_LE_MSG_HEADROOM,
+ osmo_bssmap_le_msgt_name(pdu->bssmap_le.msg_type));
+ if (!msg)
+ return NULL;
+
+ rc = osmo_bssmap_le_enc(msg, &pdu->bssmap_le);
+ if (rc <= 0) {
+ msgb_free(msg);
+ return NULL;
+ }
+
+ /* prepend header with final length */
+ msg->l2h = msgb_tv_push(msg, pdu->discr, msgb_length(msg));
+
+ return msg;
+}
+
+/*! Decode BSSAP-LE PDU (3GPP TS 49.031).
+ * \param[out] pdu Write decoded values here.
+ * \param[in] data Pointer to BSSMAP-LE PDU raw data.
+ * \param[in] len Data length to decode.
+ * \return NULL upon success, a human readable error message on failure.
+ */
+int osmo_bssap_le_dec(struct bssap_le_pdu *pdu, struct osmo_bssap_le_err **err, void *err_ctx, struct msgb *msg)
+{
+ struct osmo_bssap_le_header *h;
+ unsigned int check_len;
+ struct osmo_bssmap_le_err *bssmap_le_err = NULL;
+ int rc;
+
+#define BSSAP_LE_DEC_ERR(RC, fmt, args...) do { \
+ if (err && !*err) { \
+ *err = talloc_zero(err_ctx, struct osmo_bssap_le_err); \
+ **err = (struct osmo_bssap_le_err){ \
+ .rc = (RC), \
+ .logmsg = talloc_asprintf(*err, "Error decoding BSSAP-LE: " fmt, ##args), \
+ }; \
+ } \
+ return RC; \
+ } while(0)
+
+ memset(pdu, 0x00, sizeof(*pdu));
+
+ h = msgb_l2(msg);
+ if (!h)
+ BSSAP_LE_DEC_ERR(-EINVAL, "missing msgb_l2() pointer");
+ if (msgb_l2len(msg) < sizeof(*h))
+ BSSAP_LE_DEC_ERR(-EINVAL, "message too short for header");
+ check_len = msgb_l2len(msg) - sizeof(*h);
+ if (h->length < check_len)
+ BSSAP_LE_DEC_ERR(-EINVAL, "message truncated, header length (%u) longer than message (%u)",
+ h->length, check_len);
+
+ switch (h->type) {
+ case BSSAP_LE_MSG_DISCR_BSSMAP_LE:
+ break;
+ default:
+ BSSAP_LE_DEC_ERR(-EINVAL, "unsupported discr %u, only BSSMAP-LE is implemented", h->type);
+ }
+
+ rc = osmo_bssmap_le_dec(&pdu->bssmap_le, err ? &bssmap_le_err : NULL, err_ctx,
+ h->data, h->length);
+ if (rc)
+ BSSAP_LE_DEC_ERR(rc, "%s",
+ (bssmap_le_err && bssmap_le_err->logmsg) ?
+ bssmap_le_err->logmsg : "unknown error in BSSMAP-LE part");
+ return 0;
+}
+
+const struct value_string osmo_bssmap_le_msgt_names[] = {
+ { BSSMAP_LE_MSGT_PERFORM_LOC_REQ, "PERFORM LOCATION REQUEST" },
+ { BSSMAP_LE_MSGT_PERFORM_LOC_RESP, "PERFORM LOCATION RESPONSE" },
+ { BSSMAP_LE_MSGT_PERFORM_LOC_ABORT, "PERFORM LOCATION ABORT" },
+ { BSSMAP_LE_MSGT_PERFORM_LOC_INFO, "PERFORM LOCATION INFO" },
+ { BSSMAP_LE_MSGT_ASSIST_INFO_REQ, "ASSISTANCE INFORMATION REQUEST" },
+ { BSSMAP_LE_MSGT_ASSIST_INFO_RESP, "ASSISTANCE INFORMATION RESPONSE" },
+ { BSSMAP_LE_MSGT_CONN_ORIENTED_INFO, "CONNECTION ORIENTED INFORMATON" },
+ { BSSMAP_LE_MSGT_CONN_LESS_INFO, "CONNECTIONLESS INFORMATION" },
+ { BSSMAP_LE_MSGT_RESET, "RESET" },
+ { BSSMAP_LE_MSGT_RESET_ACK, "RESET ACKNOWLEDGE" },
+ {}
+};
+
+const struct value_string osmo_bssmap_le_iei_names[] = {
+ { BSSMAP_LE_IEI_LCS_QoS, "LCS_QoS" },
+ { BSSMAP_LE_IEI_LCS_PRIORITY, "LCS_PRIORITY" },
+ { BSSMAP_LE_IEI_LOCATION_TYPE, "LOCATION_TYPE" },
+ { BSSMAP_LE_IEI_GANSS_LOCATION_TYPE, "GANSS_LOCATION_TYPE" },
+ { BSSMAP_LE_IEI_GEO_LOCATION, "GEO_LOCATION" },
+ { BSSMAP_LE_IEI_POSITIONING_DATA, "POSITIONING_DATA" },
+ { BSSMAP_LE_IEI_GANSS_POS_DATA, "GANSS_POS_DATA" },
+ { BSSMAP_LE_IEI_VELOCITY_DATA, "VELOCITY_DATA" },
+ { BSSMAP_LE_IEI_LCS_CAUSE, "LCS_CAUSE" },
+ { BSSMAP_LE_IEI_LCS_CLIENT_TYPE, "LCS_CLIENT_TYPE" },
+ { BSSMAP_LE_IEI_APDU, "APDU" },
+ { BSSMAP_LE_IEI_NET_ELEM_ID, "NET_ELEM_ID" },
+ { BSSMAP_LE_IEI_REQ_GPS_ASS_D, "REQ_GPS_ASS_D" },
+ { BSSMAP_LE_IEI_REQ_GANSS_ASS_D, "REQ_GANSS_ASS_D" },
+ { BSSMAP_LE_IEI_DECIPH_KEYS, "DECIPH_KEYS" },
+ { BSSMAP_LE_IEI_RET_ERR_REQ, "RET_ERR_REQ" },
+ { BSSMAP_LE_IEI_RET_ERR_CAUSE, "RET_ERR_CAUSE" },
+ { BSSMAP_LE_IEI_SEGMENTATION, "SEGMENTATION" },
+ { BSSMAP_LE_IEI_CLASSMARK3_INFO, "CLASSMARK3_INFO" },
+ { BSSMAP_LE_IEI_CAUSE, "CAUSE" },
+ { BSSMAP_LE_IEI_CELL_ID, "CELL_ID" },
+ { BSSMAP_LE_IEI_CHOSEN_CHAN, "CHOSEN_CHAN" },
+ { BSSMAP_LE_IEI_IMSI, "IMSI" },
+ { BSSMAP_LE_IEI_LCS_CAPABILITY, "LCS_CAPABILITY" },
+ { BSSMAP_LE_IEI_PKT_MEAS_REP, "PKT_MEAS_REP" },
+ { BSSMAP_LE_IEI_CELL_ID_LIST, "CELL_ID_LIST" },
+ { BSSMAP_LE_IEI_IMEI, "IMEI" },
+ { BSSMAP_LE_IEI_BSS_MLAT_CAP, "BSS_MLAT_CAP" },
+ { BSSMAP_LE_IEI_CELL_INFO_LIST, "CELL_INFO_LIST" },
+ { BSSMAP_LE_IEI_BTS_RX_ACC_LVL, "BTS_RX_ACC_LVL" },
+ { BSSMAP_LE_IEI_MLAT_METHOD, "MLAT_METHOD" },
+ { BSSMAP_LE_IEI_MLAT_TA, "MLAT_TA" },
+ { BSSMAP_LE_IEI_MS_SYNC_ACC, "MS_SYNC_ACC" },
+ { BSSMAP_LE_IEI_SHORT_ID_SET, "SHORT_ID_SET" },
+ { BSSMAP_LE_IEI_RANDOM_ID_SET, "RANDOM_ID_SET" },
+ { BSSMAP_LE_IEI_SHORT_BSS_ID, "SHORT_BSS_ID" },
+ { BSSMAP_LE_IEI_RANDOM_ID, "RANDOM_ID" },
+ { BSSMAP_LE_IEI_SHORT_ID, "SHORT_ID" },
+ { BSSMAP_LE_IEI_COVERAGE_CLASS, "COVERAGE_CLASS" },
+ { BSSMAP_LE_IEI_MTA_ACC_SEC_RQD, "MTA_ACC_SEC_RQD" },
+ {}
+};
+
+/*! Return a human readable string describing a BSSAP-LE PDU.
+ * \param[out] buf String buffer to write to.
+ * \param[in] buflen sizeof(buf).
+ * \param[in] bssap_le Decoded BSSAP-LE PDU data.
+ * \returns number of chars that would be written, like snprintf().
+ */
+int osmo_bssap_le_pdu_to_str_buf(char *buf, size_t buflen, const struct bssap_le_pdu *bssap_le)
+{
+ struct osmo_strbuf sb = { .buf = buf, .len = buflen };
+ const struct bssmap_le_pdu *bssmap_le;
+
+ switch (bssap_le->discr) {
+ case BSSAP_LE_MSG_DISCR_BSSMAP_LE:
+ bssmap_le = &bssap_le->bssmap_le;
+ OSMO_STRBUF_PRINTF(sb, "BSSMAP-LE %s", osmo_bssmap_le_msgt_name(bssmap_le->msg_type));
+ switch (bssmap_le->msg_type) {
+ case BSSMAP_LE_MSGT_PERFORM_LOC_REQ:
+ if (bssmap_le->perform_loc_req.apdu_present)
+ OSMO_STRBUF_PRINTF(sb, " with BSSLAP %s",
+ osmo_bsslap_msgt_name(bssmap_le->perform_loc_req.apdu.msg_type));
+ break;
+
+ case BSSMAP_LE_MSGT_CONN_ORIENTED_INFO:
+ OSMO_STRBUF_PRINTF(sb, " with BSSLAP %s",
+ osmo_bsslap_msgt_name(bssmap_le->conn_oriented_info.apdu.msg_type));
+ break;
+
+ default:
+ break;
+ }
+ break;
+ default:
+ OSMO_STRBUF_PRINTF(sb, "BSSAP-LE discr %d not implemented", bssap_le->discr);
+ break;
+ }
+
+ return sb.chars_needed;
+}
+
+/*! Return a human readable string describing a BSSAP-LE PDU.
+ * \param[in] ctx Talloc context to allocate string buffer from.
+ * \param[in] bssap_le Decoded BSSAP-LE PDU data.
+ * \returns string.
+ */
+char *osmo_bssap_le_pdu_to_str_c(void *ctx, const struct bssap_le_pdu *bssap_le)
+{
+ OSMO_NAME_C_IMPL(ctx, 32, "ERROR", osmo_bssap_le_pdu_to_str_buf, bssap_le)
+}
+
+/*! @} */
diff --git a/src/gsm/bts_features.c b/src/gsm/bts_features.c
index 9a5188b2..b6cd82ec 100644
--- a/src/gsm/bts_features.c
+++ b/src/gsm/bts_features.c
@@ -14,13 +14,9 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
- * MA 02110-1301, USA.
*/
+#include <osmocom/core/utils.h>
#include <osmocom/gsm/bts_features.h>
const struct value_string osmo_bts_features_descs[] = {
@@ -41,11 +37,59 @@ const struct value_string osmo_bts_features_descs[] = {
{ BTS_FEAT_ETWS_PN, "ETWS Primary Notification via PCH" },
{ BTS_FEAT_PAGING_COORDINATION, "BSS Paging Coordination" },
{ BTS_FEAT_IPV6_NSVC, "NSVC IPv6" },
+ { BTS_FEAT_ACCH_REP, "FACCH/SACCH Repetition" },
+ { BTS_FEAT_CCN, "Cell Change Notification (CCN)" },
+ { BTS_FEAT_VAMOS, "VAMOS (Voice services over Adaptive Multi-user channels on One Slot)" },
+ { BTS_FEAT_ABIS_OSMO_PCU, "OsmoPCU over OML link IPA multiplex" },
+ { BTS_FEAT_BCCH_POWER_RED, "BCCH carrier power reduction mode" },
+ { BTS_FEAT_DYN_TS_SDCCH8, "Dynamic Timeslot configuration as SDCCH8" },
+ { BTS_FEAT_ACCH_TEMP_OVP, "FACCH/SACCH Temporary overpower" },
+ { BTS_FEAT_OSMUX, "Osmux (Osmocom RTP multiplexing)" },
+ { BTS_FEAT_VBS, "Voice Broadcast Service" },
+ { BTS_FEAT_VGCS, "Voice Group Call Service" },
{ 0, NULL }
};
-/*! return string representation of a BTS feature */
+/* Ensure that all BTS_FEAT_* entries are present in osmo_bts_features_descs[] */
+osmo_static_assert(ARRAY_SIZE(osmo_bts_features_descs) == _NUM_BTS_FEAT + 1, _bts_features_descs);
+
+/*! return description string of a BTS feature (osmo_bts_features_descs).
+ * To get the plain feature name, use osmo_bts_features_name() instead. */
const char *osmo_bts_feature_name(enum osmo_bts_features feature)
{
return get_value_string(osmo_bts_features_descs, feature);
}
+
+const struct value_string osmo_bts_features_names[] = {
+ { BTS_FEAT_HSCSD, "HSCSD" },
+ { BTS_FEAT_GPRS, "GPRS" },
+ { BTS_FEAT_EGPRS, "EGPRS" },
+ { BTS_FEAT_ECSD, "ECSD" },
+ { BTS_FEAT_HOPPING, "HOPPING" },
+ { BTS_FEAT_MULTI_TSC, "MULTI_TSC" },
+ { BTS_FEAT_OML_ALERTS, "OML_ALERTS" },
+ { BTS_FEAT_AGCH_PCH_PROP, "AGCH_PCH_PROP" },
+ { BTS_FEAT_CBCH, "CBCH" },
+ { BTS_FEAT_SPEECH_F_V1, "SPEECH_F_V1" },
+ { BTS_FEAT_SPEECH_H_V1, "SPEECH_H_V1" },
+ { BTS_FEAT_SPEECH_F_EFR, "SPEECH_F_EFR" },
+ { BTS_FEAT_SPEECH_F_AMR, "SPEECH_F_AMR" },
+ { BTS_FEAT_SPEECH_H_AMR, "SPEECH_H_AMR" },
+ { BTS_FEAT_ETWS_PN, "ETWS_PN" },
+ { BTS_FEAT_PAGING_COORDINATION, "PAGING_COORDINATION" },
+ { BTS_FEAT_IPV6_NSVC, "IPV6_NSVC" },
+ { BTS_FEAT_ACCH_REP, "ACCH_REP" },
+ { BTS_FEAT_CCN, "CCN" },
+ { BTS_FEAT_VAMOS, "VAMOS" },
+ { BTS_FEAT_ABIS_OSMO_PCU, "ABIS_OSMO_PCU" },
+ { BTS_FEAT_BCCH_POWER_RED, "BCCH_PWR_RED" },
+ { BTS_FEAT_DYN_TS_SDCCH8, "DYN_TS_SDCCH8" },
+ { BTS_FEAT_ACCH_TEMP_OVP, "ACCH_TEMP_OVP" },
+ { BTS_FEAT_OSMUX, "OSMUX" },
+ { BTS_FEAT_VBS, "VBS" },
+ { BTS_FEAT_VGCS, "VGCS" },
+ {}
+};
+
+/* Ensure that all BTS_FEAT_* entries are present in osmo_bts_features_names[] */
+osmo_static_assert(ARRAY_SIZE(osmo_bts_features_names) == _NUM_BTS_FEAT + 1, _bts_features_names);
diff --git a/src/gsm/cbsp.c b/src/gsm/cbsp.c
index b89358d4..28852f6f 100644
--- a/src/gsm/cbsp.c
+++ b/src/gsm/cbsp.c
@@ -15,10 +15,6 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "config.h"
@@ -34,7 +30,7 @@
#include <osmocom/gsm/cbsp.h>
#include <osmocom/gsm/gsm0808_utils.h>
-const __thread char *osmo_cbsp_errstr;
+__thread const char *osmo_cbsp_errstr;
struct msgb *osmo_cbsp_msgb_alloc(void *ctx, const char *name)
{
@@ -130,13 +126,13 @@ static int encode_wperiod(uint32_t secs)
if (secs <= 10)
return secs;
if (secs <= 30)
- return (secs-10)/2;
+ return 10 + (secs-10)/2;
if (secs <= 120)
- return (secs-30)/5;
+ return 30 + (secs-30)/5;
if (secs <= 600)
- return (secs-120)/10;
+ return 120 + (secs-120)/10;
if (secs <= 60*60)
- return (secs-600)/30;
+ return 600 + (secs-600)/30;
osmo_cbsp_errstr = "warning period out of range";
return -1;
}
@@ -177,12 +173,15 @@ static int cbsp_enc_write_repl(struct msgb *msg, const struct osmo_cbsp_write_re
}
} else {
int wperiod = encode_wperiod(in->u.emergency.warning_period);
+ uint8_t *cur;
if (wperiod < 0)
return -EINVAL;
msgb_tv_put(msg, CBSP_IEI_EMERG_IND, in->u.emergency.indicator);
msgb_tv16_put(msg, CBSP_IEI_WARN_TYPE, in->u.emergency.warning_type);
- msgb_tlv_put(msg, CBSP_IEI_WARN_SEC_INFO, sizeof(in->u.emergency.warning_sec_info),
- in->u.emergency.warning_sec_info);
+ /* Tag + fixed length value! */
+ msgb_put_u8(msg, CBSP_IEI_WARN_SEC_INFO);
+ cur = msgb_put(msg, sizeof(in->u.emergency.warning_sec_info));
+ memcpy(cur, in->u.emergency.warning_sec_info, sizeof(in->u.emergency.warning_sec_info));
msgb_tv_put(msg, CBSP_IEI_WARNING_PERIOD, wperiod);
}
return 0;
@@ -349,7 +348,12 @@ static int cbsp_enc_reset_fail(struct msgb *msg, const struct osmo_cbsp_reset_fa
/* 8.1.3.18a KEEP ALIVE */
static int cbsp_enc_keep_alive(struct msgb *msg, const struct osmo_cbsp_keep_alive *in)
{
- msgb_tv_put(msg, CBSP_IEI_KEEP_ALIVE_REP_PERIOD, in->repetition_period);
+ int rperiod = encode_wperiod(in->repetition_period);
+ if (in->repetition_period > 120)
+ return -EINVAL;
+ if (rperiod < 0)
+ return -EINVAL;
+ msgb_tv_put(msg, CBSP_IEI_KEEP_ALIVE_REP_PERIOD, rperiod);
return 0;
}
@@ -533,8 +537,8 @@ static int cbsp_decode_fail_list(struct llist_head *fl, void *ctx,
struct osmo_cbsp_fail_ent *ent = talloc_zero(ctx, struct osmo_cbsp_fail_ent);
unsigned int len_remain = len - (cur - buf);
OSMO_ASSERT(ent);
- ent->id_discr = cur[0];
- rc = gsm0808_decode_cell_id_u(&ent->cell_id, ent->id_discr, cur+1, len_remain-1);
+ ent->id_discr = *cur++;
+ rc = gsm0808_decode_cell_id_u(&ent->cell_id, ent->id_discr, cur, len_remain-1);
if (rc < 0) {
osmo_cbsp_errstr = "fail list: error decoding cell_id_union";
return rc;
@@ -635,6 +639,7 @@ static int cbsp_dec_write_repl(struct osmo_cbsp_write_replace *out, const struct
struct msgb *in, void *ctx)
{
unsigned int i;
+ int rc;
/* check for mandatory IEs */
if (!TLVP_PRESENT(tp, CBSP_IEI_MSG_ID) ||
@@ -652,8 +657,10 @@ static int cbsp_dec_write_repl(struct osmo_cbsp_write_replace *out, const struct
}
INIT_LLIST_HEAD(&out->cell_list.list);
- cbsp_decode_cell_list(&out->cell_list, ctx, TLVP_VAL(tp, CBSP_IEI_CELL_LIST),
- TLVP_LEN(tp, CBSP_IEI_CELL_LIST));
+ rc = cbsp_decode_cell_list(&out->cell_list, ctx, TLVP_VAL(tp, CBSP_IEI_CELL_LIST),
+ TLVP_LEN(tp, CBSP_IEI_CELL_LIST));
+ if (rc < 0)
+ return rc;
if (TLVP_PRESENT(tp, CBSP_IEI_CHANNEL_IND)) {
uint8_t num_of_pages;
@@ -675,6 +682,7 @@ static int cbsp_dec_write_repl(struct osmo_cbsp_write_replace *out, const struct
out->u.cbs.category = *TLVP_VAL(tp, CBSP_IEI_CATEGORY);
out->u.cbs.rep_period = tlvp_val16be(tp, CBSP_IEI_REP_PERIOD);
out->u.cbs.num_bcast_req = tlvp_val16be(tp, CBSP_IEI_NUM_BCAST_REQ);
+ out->u.cbs.dcs = *TLVP_VAL(tp, CBSP_IEI_DCS);
num_of_pages = *TLVP_VAL(tp, CBSP_IEI_NUM_OF_PAGES);
if (num_of_pages < 1)
return -EINVAL;
@@ -713,6 +721,8 @@ static int cbsp_dec_write_repl(struct osmo_cbsp_write_replace *out, const struct
static int cbsp_dec_write_repl_compl(struct osmo_cbsp_write_replace_complete *out,
const struct tlv_parsed *tp, struct msgb *in, void *ctx)
{
+ int rc;
+
if (!TLVP_PRES_LEN(tp, CBSP_IEI_MSG_ID, 2) ||
!TLVP_PRES_LEN(tp, CBSP_IEI_NEW_SERIAL_NR, 2)) {
osmo_cbsp_errstr = "missing/short mandatory IE";
@@ -728,14 +738,18 @@ static int cbsp_dec_write_repl_compl(struct osmo_cbsp_write_replace_complete *ou
INIT_LLIST_HEAD(&out->num_compl_list.list);
if (TLVP_PRES_LEN(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST, 7)) {
- cbsp_decode_num_compl_list(&out->num_compl_list, ctx,
- TLVP_VAL(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST),
- TLVP_LEN(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST));
+ rc = cbsp_decode_num_compl_list(&out->num_compl_list, ctx,
+ TLVP_VAL(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST),
+ TLVP_LEN(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST));
+ if (rc < 0)
+ return rc;
}
INIT_LLIST_HEAD(&out->cell_list.list);
- cbsp_decode_cell_list(&out->cell_list, ctx, TLVP_VAL(tp, CBSP_IEI_CELL_LIST),
- TLVP_LEN(tp, CBSP_IEI_CELL_LIST));
+ rc = cbsp_decode_cell_list(&out->cell_list, ctx, TLVP_VAL(tp, CBSP_IEI_CELL_LIST),
+ TLVP_LEN(tp, CBSP_IEI_CELL_LIST));
+ if (rc < 0)
+ return rc;
if (TLVP_PRES_LEN(tp, CBSP_IEI_CHANNEL_IND, 1)) {
out->channel_ind = talloc(ctx, enum cbsp_channel_ind);
@@ -748,6 +762,8 @@ static int cbsp_dec_write_repl_compl(struct osmo_cbsp_write_replace_complete *ou
static int cbsp_dec_write_repl_fail(struct osmo_cbsp_write_replace_failure *out,
const struct tlv_parsed *tp, struct msgb *in, void *ctx)
{
+ int rc;
+
if (!TLVP_PRES_LEN(tp, CBSP_IEI_MSG_ID, 2) ||
!TLVP_PRES_LEN(tp, CBSP_IEI_NEW_SERIAL_NR, 2) ||
!TLVP_PRES_LEN(tp, CBSP_IEI_FAILURE_LIST, 5)) {
@@ -763,21 +779,27 @@ static int cbsp_dec_write_repl_fail(struct osmo_cbsp_write_replace_failure *out,
}
INIT_LLIST_HEAD(&out->fail_list);
- cbsp_decode_fail_list(&out->fail_list, ctx,
- TLVP_VAL(tp, CBSP_IEI_FAILURE_LIST),
- TLVP_LEN(tp, CBSP_IEI_FAILURE_LIST));
+ rc = cbsp_decode_fail_list(&out->fail_list, ctx,
+ TLVP_VAL(tp, CBSP_IEI_FAILURE_LIST),
+ TLVP_LEN(tp, CBSP_IEI_FAILURE_LIST));
+ if (rc < 0)
+ return rc;
INIT_LLIST_HEAD(&out->num_compl_list.list);
if (TLVP_PRES_LEN(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST, 7)) {
- cbsp_decode_num_compl_list(&out->num_compl_list, ctx,
- TLVP_VAL(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST),
- TLVP_LEN(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST));
+ rc = cbsp_decode_num_compl_list(&out->num_compl_list, ctx,
+ TLVP_VAL(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST),
+ TLVP_LEN(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST));
+ if (rc < 0)
+ return rc;
}
INIT_LLIST_HEAD(&out->cell_list.list);
if (TLVP_PRES_LEN(tp, CBSP_IEI_CELL_LIST, 1)) {
- cbsp_decode_cell_list(&out->cell_list, ctx, TLVP_VAL(tp, CBSP_IEI_CELL_LIST),
- TLVP_LEN(tp, CBSP_IEI_CELL_LIST));
+ rc = cbsp_decode_cell_list(&out->cell_list, ctx, TLVP_VAL(tp, CBSP_IEI_CELL_LIST),
+ TLVP_LEN(tp, CBSP_IEI_CELL_LIST));
+ if (rc < 0)
+ return rc;
}
if (TLVP_PRES_LEN(tp, CBSP_IEI_CHANNEL_IND, 1)) {
@@ -791,6 +813,8 @@ static int cbsp_dec_write_repl_fail(struct osmo_cbsp_write_replace_failure *out,
static int cbsp_dec_kill(struct osmo_cbsp_kill *out, const struct tlv_parsed *tp,
struct msgb *in, void *ctx)
{
+ int rc;
+
if (!TLVP_PRES_LEN(tp, CBSP_IEI_MSG_ID, 2) ||
!TLVP_PRES_LEN(tp, CBSP_IEI_OLD_SERIAL_NR, 2) ||
!TLVP_PRES_LEN(tp, CBSP_IEI_CELL_LIST, 1)) {
@@ -802,8 +826,10 @@ static int cbsp_dec_kill(struct osmo_cbsp_kill *out, const struct tlv_parsed *tp
out->old_serial_nr = tlvp_val16be(tp, CBSP_IEI_OLD_SERIAL_NR);
INIT_LLIST_HEAD(&out->cell_list.list);
- cbsp_decode_cell_list(&out->cell_list, ctx, TLVP_VAL(tp, CBSP_IEI_CELL_LIST),
- TLVP_LEN(tp, CBSP_IEI_CELL_LIST));
+ rc = cbsp_decode_cell_list(&out->cell_list, ctx, TLVP_VAL(tp, CBSP_IEI_CELL_LIST),
+ TLVP_LEN(tp, CBSP_IEI_CELL_LIST));
+ if (rc < 0)
+ return rc;
if (TLVP_PRES_LEN(tp, CBSP_IEI_CHANNEL_IND, 1)) {
out->channel_ind = talloc(ctx, enum cbsp_channel_ind);
@@ -816,6 +842,8 @@ static int cbsp_dec_kill(struct osmo_cbsp_kill *out, const struct tlv_parsed *tp
static int cbsp_dec_kill_compl(struct osmo_cbsp_kill_complete *out, const struct tlv_parsed *tp,
struct msgb *in, void *ctx)
{
+ int rc;
+
if (!TLVP_PRES_LEN(tp, CBSP_IEI_MSG_ID, 2) ||
!TLVP_PRES_LEN(tp, CBSP_IEI_OLD_SERIAL_NR, 2) ||
!TLVP_PRES_LEN(tp, CBSP_IEI_CELL_LIST, 1)) {
@@ -828,14 +856,18 @@ static int cbsp_dec_kill_compl(struct osmo_cbsp_kill_complete *out, const struct
INIT_LLIST_HEAD(&out->num_compl_list.list);
if (TLVP_PRES_LEN(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST, 7)) {
- cbsp_decode_num_compl_list(&out->num_compl_list, ctx,
- TLVP_VAL(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST),
- TLVP_LEN(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST));
+ rc = cbsp_decode_num_compl_list(&out->num_compl_list, ctx,
+ TLVP_VAL(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST),
+ TLVP_LEN(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST));
+ if (rc < 0)
+ return rc;
}
INIT_LLIST_HEAD(&out->cell_list.list);
- cbsp_decode_cell_list(&out->cell_list, ctx, TLVP_VAL(tp, CBSP_IEI_CELL_LIST),
- TLVP_LEN(tp, CBSP_IEI_CELL_LIST));
+ rc = cbsp_decode_cell_list(&out->cell_list, ctx, TLVP_VAL(tp, CBSP_IEI_CELL_LIST),
+ TLVP_LEN(tp, CBSP_IEI_CELL_LIST));
+ if (rc < 0)
+ return rc;
if (TLVP_PRES_LEN(tp, CBSP_IEI_CHANNEL_IND, 1)) {
out->channel_ind = talloc(ctx, enum cbsp_channel_ind);
@@ -848,6 +880,8 @@ static int cbsp_dec_kill_compl(struct osmo_cbsp_kill_complete *out, const struct
static int cbsp_dec_kill_fail(struct osmo_cbsp_kill_failure *out, const struct tlv_parsed *tp,
struct msgb *in, void *ctx)
{
+ int rc;
+
if (!TLVP_PRES_LEN(tp, CBSP_IEI_MSG_ID, 2) ||
!TLVP_PRES_LEN(tp, CBSP_IEI_OLD_SERIAL_NR, 2) ||
!TLVP_PRES_LEN(tp, CBSP_IEI_FAILURE_LIST, 5)) {
@@ -859,21 +893,27 @@ static int cbsp_dec_kill_fail(struct osmo_cbsp_kill_failure *out, const struct t
out->old_serial_nr = tlvp_val16be(tp, CBSP_IEI_OLD_SERIAL_NR);
INIT_LLIST_HEAD(&out->fail_list);
- cbsp_decode_fail_list(&out->fail_list, ctx,
- TLVP_VAL(tp, CBSP_IEI_FAILURE_LIST),
- TLVP_LEN(tp, CBSP_IEI_FAILURE_LIST));
+ rc = cbsp_decode_fail_list(&out->fail_list, ctx,
+ TLVP_VAL(tp, CBSP_IEI_FAILURE_LIST),
+ TLVP_LEN(tp, CBSP_IEI_FAILURE_LIST));
+ if (rc < 0)
+ return rc;
INIT_LLIST_HEAD(&out->num_compl_list.list);
if (TLVP_PRES_LEN(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST, 7)) {
- cbsp_decode_num_compl_list(&out->num_compl_list, ctx,
- TLVP_VAL(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST),
- TLVP_LEN(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST));
+ rc = cbsp_decode_num_compl_list(&out->num_compl_list, ctx,
+ TLVP_VAL(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST),
+ TLVP_LEN(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST));
+ if (rc < 0)
+ return rc;
}
INIT_LLIST_HEAD(&out->cell_list.list);
if (TLVP_PRES_LEN(tp, CBSP_IEI_CELL_LIST, 1)) {
- cbsp_decode_cell_list(&out->cell_list, ctx, TLVP_VAL(tp, CBSP_IEI_CELL_LIST),
- TLVP_LEN(tp, CBSP_IEI_CELL_LIST));
+ rc = cbsp_decode_cell_list(&out->cell_list, ctx, TLVP_VAL(tp, CBSP_IEI_CELL_LIST),
+ TLVP_LEN(tp, CBSP_IEI_CELL_LIST));
+ if (rc < 0)
+ return rc;
}
if (TLVP_PRES_LEN(tp, CBSP_IEI_CHANNEL_IND, 1)) {
@@ -887,6 +927,8 @@ static int cbsp_dec_kill_fail(struct osmo_cbsp_kill_failure *out, const struct t
static int cbsp_dec_load_query(struct osmo_cbsp_load_query *out, const struct tlv_parsed *tp,
struct msgb *in, void *ctx)
{
+ int rc;
+
if (!TLVP_PRES_LEN(tp, CBSP_IEI_CELL_LIST, 1) ||
!TLVP_PRES_LEN(tp, CBSP_IEI_CHANNEL_IND, 1)) {
osmo_cbsp_errstr = "missing/short mandatory IE";
@@ -894,8 +936,10 @@ static int cbsp_dec_load_query(struct osmo_cbsp_load_query *out, const struct tl
}
INIT_LLIST_HEAD(&out->cell_list.list);
- cbsp_decode_cell_list(&out->cell_list, ctx, TLVP_VAL(tp, CBSP_IEI_CELL_LIST),
- TLVP_LEN(tp, CBSP_IEI_CELL_LIST));
+ rc = cbsp_decode_cell_list(&out->cell_list, ctx, TLVP_VAL(tp, CBSP_IEI_CELL_LIST),
+ TLVP_LEN(tp, CBSP_IEI_CELL_LIST));
+ if (rc < 0)
+ return rc;
out->channel_ind = *TLVP_VAL(tp, CBSP_IEI_CHANNEL_IND);
return 0;
@@ -905,6 +949,8 @@ static int cbsp_dec_load_query(struct osmo_cbsp_load_query *out, const struct tl
static int cbsp_dec_load_query_compl(struct osmo_cbsp_load_query_complete *out,
const struct tlv_parsed *tp, struct msgb *in, void *ctx)
{
+ int rc;
+
if (!TLVP_PRES_LEN(tp, CBSP_IEI_RR_LOADING_LIST, 6) ||
!TLVP_PRES_LEN(tp, CBSP_IEI_CHANNEL_IND, 1)) {
osmo_cbsp_errstr = "missing/short mandatory IE";
@@ -912,9 +958,11 @@ static int cbsp_dec_load_query_compl(struct osmo_cbsp_load_query_complete *out,
}
INIT_LLIST_HEAD(&out->loading_list.list);
- cbsp_decode_loading_list(&out->loading_list, ctx,
- TLVP_VAL(tp, CBSP_IEI_RR_LOADING_LIST),
- TLVP_LEN(tp, CBSP_IEI_RR_LOADING_LIST));
+ rc = cbsp_decode_loading_list(&out->loading_list, ctx,
+ TLVP_VAL(tp, CBSP_IEI_RR_LOADING_LIST),
+ TLVP_LEN(tp, CBSP_IEI_RR_LOADING_LIST));
+ if (rc < 0)
+ return rc;
out->channel_ind = *TLVP_VAL(tp, CBSP_IEI_CHANNEL_IND);
return 0;
@@ -924,6 +972,8 @@ static int cbsp_dec_load_query_compl(struct osmo_cbsp_load_query_complete *out,
static int cbsp_dec_load_query_fail(struct osmo_cbsp_load_query_failure *out,
const struct tlv_parsed *tp, struct msgb *in, void *ctx)
{
+ int rc;
+
if (!TLVP_PRES_LEN(tp, CBSP_IEI_FAILURE_LIST, 5) ||
!TLVP_PRES_LEN(tp, CBSP_IEI_CHANNEL_IND, 1)) {
osmo_cbsp_errstr = "missing/short mandatory IE";
@@ -931,25 +981,29 @@ static int cbsp_dec_load_query_fail(struct osmo_cbsp_load_query_failure *out,
}
INIT_LLIST_HEAD(&out->fail_list);
- cbsp_decode_fail_list(&out->fail_list, ctx,
- TLVP_VAL(tp, CBSP_IEI_FAILURE_LIST),
- TLVP_LEN(tp, CBSP_IEI_FAILURE_LIST));
+ rc = cbsp_decode_fail_list(&out->fail_list, ctx,
+ TLVP_VAL(tp, CBSP_IEI_FAILURE_LIST),
+ TLVP_LEN(tp, CBSP_IEI_FAILURE_LIST));
+ if (rc < 0)
+ return rc;
out->channel_ind = *TLVP_VAL(tp, CBSP_IEI_CHANNEL_IND);
INIT_LLIST_HEAD(&out->loading_list.list);
if (TLVP_PRES_LEN(tp, CBSP_IEI_RR_LOADING_LIST, 6)) {
- cbsp_decode_loading_list(&out->loading_list, ctx,
- TLVP_VAL(tp, CBSP_IEI_RR_LOADING_LIST),
- TLVP_LEN(tp, CBSP_IEI_RR_LOADING_LIST));
+ rc = cbsp_decode_loading_list(&out->loading_list, ctx,
+ TLVP_VAL(tp, CBSP_IEI_RR_LOADING_LIST),
+ TLVP_LEN(tp, CBSP_IEI_RR_LOADING_LIST));
}
- return 0;
+ return rc;
}
/* 8.1.3.10 STATUS QUERY */
static int cbsp_dec_msg_status_query(struct osmo_cbsp_msg_status_query *out,
const struct tlv_parsed *tp, struct msgb *in, void *ctx)
{
+ int rc;
+
if (!TLVP_PRES_LEN(tp, CBSP_IEI_MSG_ID, 2) ||
!TLVP_PRES_LEN(tp, CBSP_IEI_OLD_SERIAL_NR, 2) ||
!TLVP_PRES_LEN(tp, CBSP_IEI_CELL_LIST, 1) ||
@@ -962,8 +1016,10 @@ static int cbsp_dec_msg_status_query(struct osmo_cbsp_msg_status_query *out,
out->old_serial_nr = tlvp_val16be(tp, CBSP_IEI_OLD_SERIAL_NR);
INIT_LLIST_HEAD(&out->cell_list.list);
- cbsp_decode_cell_list(&out->cell_list, ctx, TLVP_VAL(tp, CBSP_IEI_CELL_LIST),
- TLVP_LEN(tp, CBSP_IEI_CELL_LIST));
+ rc = cbsp_decode_cell_list(&out->cell_list, ctx, TLVP_VAL(tp, CBSP_IEI_CELL_LIST),
+ TLVP_LEN(tp, CBSP_IEI_CELL_LIST));
+ if (rc < 0)
+ return rc;
out->channel_ind = *TLVP_VAL(tp, CBSP_IEI_CHANNEL_IND);
return 0;
@@ -973,6 +1029,8 @@ static int cbsp_dec_msg_status_query(struct osmo_cbsp_msg_status_query *out,
static int cbsp_dec_msg_status_query_compl(struct osmo_cbsp_msg_status_query_complete *out,
const struct tlv_parsed *tp, struct msgb *in, void *ctx)
{
+ int rc;
+
if (!TLVP_PRES_LEN(tp, CBSP_IEI_MSG_ID, 2) ||
!TLVP_PRES_LEN(tp, CBSP_IEI_OLD_SERIAL_NR, 2) ||
!TLVP_PRES_LEN(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST, 7) ||
@@ -985,9 +1043,11 @@ static int cbsp_dec_msg_status_query_compl(struct osmo_cbsp_msg_status_query_com
out->old_serial_nr = tlvp_val16be(tp, CBSP_IEI_OLD_SERIAL_NR);
INIT_LLIST_HEAD(&out->num_compl_list.list);
- cbsp_decode_num_compl_list(&out->num_compl_list, ctx,
- TLVP_VAL(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST),
- TLVP_LEN(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST));
+ rc = cbsp_decode_num_compl_list(&out->num_compl_list, ctx,
+ TLVP_VAL(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST),
+ TLVP_LEN(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST));
+ if (rc < 0)
+ return rc;
out->channel_ind = *TLVP_VAL(tp, CBSP_IEI_CHANNEL_IND);
return 0;
}
@@ -996,6 +1056,8 @@ static int cbsp_dec_msg_status_query_compl(struct osmo_cbsp_msg_status_query_com
static int cbsp_dec_msg_status_query_fail(struct osmo_cbsp_msg_status_query_failure *out,
const struct tlv_parsed *tp, struct msgb *in, void *ctx)
{
+ int rc;
+
if (!TLVP_PRES_LEN(tp, CBSP_IEI_MSG_ID, 2) ||
!TLVP_PRES_LEN(tp, CBSP_IEI_OLD_SERIAL_NR, 2) ||
!TLVP_PRES_LEN(tp, CBSP_IEI_FAILURE_LIST, 5) ||
@@ -1008,17 +1070,21 @@ static int cbsp_dec_msg_status_query_fail(struct osmo_cbsp_msg_status_query_fail
out->old_serial_nr = tlvp_val16be(tp, CBSP_IEI_OLD_SERIAL_NR);
INIT_LLIST_HEAD(&out->fail_list);
- cbsp_decode_fail_list(&out->fail_list, ctx,
- TLVP_VAL(tp, CBSP_IEI_FAILURE_LIST),
- TLVP_LEN(tp, CBSP_IEI_FAILURE_LIST));
+ rc = cbsp_decode_fail_list(&out->fail_list, ctx,
+ TLVP_VAL(tp, CBSP_IEI_FAILURE_LIST),
+ TLVP_LEN(tp, CBSP_IEI_FAILURE_LIST));
+ if (rc < 0)
+ return rc;
out->channel_ind = *TLVP_VAL(tp, CBSP_IEI_CHANNEL_IND);
INIT_LLIST_HEAD(&out->num_compl_list.list);
if (TLVP_PRES_LEN(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST, 7)) {
- cbsp_decode_num_compl_list(&out->num_compl_list, ctx,
- TLVP_VAL(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST),
- TLVP_LEN(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST));
+ rc = cbsp_decode_num_compl_list(&out->num_compl_list, ctx,
+ TLVP_VAL(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST),
+ TLVP_LEN(tp, CBSP_IEI_NUM_BCAST_COMPL_LIST));
+ if (rc < 0)
+ return rc;
}
return 0;
}
@@ -1027,14 +1093,19 @@ static int cbsp_dec_msg_status_query_fail(struct osmo_cbsp_msg_status_query_fail
static int cbsp_dec_reset(struct osmo_cbsp_reset *out, const struct tlv_parsed *tp,
struct msgb *in, void *ctx)
{
+ int rc;
+
if (!TLVP_PRES_LEN(tp, CBSP_IEI_CELL_LIST, 1)) {
osmo_cbsp_errstr = "missing/short mandatory IE";
return -EINVAL;
}
INIT_LLIST_HEAD(&out->cell_list.list);
- cbsp_decode_cell_list(&out->cell_list, ctx, TLVP_VAL(tp, CBSP_IEI_CELL_LIST),
- TLVP_LEN(tp, CBSP_IEI_CELL_LIST));
+ rc = cbsp_decode_cell_list(&out->cell_list, ctx, TLVP_VAL(tp, CBSP_IEI_CELL_LIST),
+ TLVP_LEN(tp, CBSP_IEI_CELL_LIST));
+ if (rc < 0)
+ return rc;
+
return 0;
}
@@ -1042,14 +1113,19 @@ static int cbsp_dec_reset(struct osmo_cbsp_reset *out, const struct tlv_parsed *
static int cbsp_dec_reset_compl(struct osmo_cbsp_reset_complete *out, const struct tlv_parsed *tp,
struct msgb *in, void *ctx)
{
+ int rc;
+
if (!TLVP_PRES_LEN(tp, CBSP_IEI_CELL_LIST, 1)) {
osmo_cbsp_errstr = "missing/short mandatory IE";
return -EINVAL;
}
INIT_LLIST_HEAD(&out->cell_list.list);
- cbsp_decode_cell_list(&out->cell_list, ctx, TLVP_VAL(tp, CBSP_IEI_CELL_LIST),
- TLVP_LEN(tp, CBSP_IEI_CELL_LIST));
+ rc = cbsp_decode_cell_list(&out->cell_list, ctx, TLVP_VAL(tp, CBSP_IEI_CELL_LIST),
+ TLVP_LEN(tp, CBSP_IEI_CELL_LIST));
+ if (rc < 0)
+ return rc;
+
return 0;
}
@@ -1057,20 +1133,26 @@ static int cbsp_dec_reset_compl(struct osmo_cbsp_reset_complete *out, const stru
static int cbsp_dec_reset_fail(struct osmo_cbsp_reset_failure *out, const struct tlv_parsed *tp,
struct msgb *in, void *ctx)
{
+ int rc;
+
if (!TLVP_PRES_LEN(tp, CBSP_IEI_FAILURE_LIST, 5)) {
osmo_cbsp_errstr = "missing/short mandatory IE";
return -EINVAL;
}
INIT_LLIST_HEAD(&out->fail_list);
- cbsp_decode_fail_list(&out->fail_list, ctx,
- TLVP_VAL(tp, CBSP_IEI_FAILURE_LIST),
- TLVP_LEN(tp, CBSP_IEI_FAILURE_LIST));
+ rc = cbsp_decode_fail_list(&out->fail_list, ctx,
+ TLVP_VAL(tp, CBSP_IEI_FAILURE_LIST),
+ TLVP_LEN(tp, CBSP_IEI_FAILURE_LIST));
+ if (rc < 0)
+ return rc;
INIT_LLIST_HEAD(&out->cell_list.list);
if (TLVP_PRES_LEN(tp, CBSP_IEI_CELL_LIST, 1)) {
- cbsp_decode_cell_list(&out->cell_list, ctx, TLVP_VAL(tp, CBSP_IEI_CELL_LIST),
- TLVP_LEN(tp, CBSP_IEI_CELL_LIST));
+ rc = cbsp_decode_cell_list(&out->cell_list, ctx, TLVP_VAL(tp, CBSP_IEI_CELL_LIST),
+ TLVP_LEN(tp, CBSP_IEI_CELL_LIST));
+ if (rc < 0)
+ return rc;
}
return 0;
}
@@ -1079,12 +1161,14 @@ static int cbsp_dec_reset_fail(struct osmo_cbsp_reset_failure *out, const struct
static int cbsp_dec_keep_alive(struct osmo_cbsp_keep_alive *out, const struct tlv_parsed *tp,
struct msgb *in, void *ctx)
{
+ uint8_t rperiod;
if (!TLVP_PRES_LEN(tp, CBSP_IEI_KEEP_ALIVE_REP_PERIOD, 1)) {
osmo_cbsp_errstr = "missing/short mandatory IE";
return -EINVAL;
}
- out->repetition_period = *TLVP_VAL(tp, CBSP_IEI_KEEP_ALIVE_REP_PERIOD);
+ rperiod = *TLVP_VAL(tp, CBSP_IEI_KEEP_ALIVE_REP_PERIOD);
+ out->repetition_period = decode_wperiod(rperiod);
return 0;
}
@@ -1099,6 +1183,8 @@ static int cbsp_dec_keep_alive_compl(struct osmo_cbsp_keep_alive_complete *out,
static int cbsp_dec_restart(struct osmo_cbsp_restart *out, const struct tlv_parsed *tp,
struct msgb *in, void *ctx)
{
+ int rc;
+
if (!TLVP_PRES_LEN(tp, CBSP_IEI_CELL_LIST, 1) ||
!TLVP_PRES_LEN(tp, CBSP_IEI_BCAST_MSG_TYPE, 1) ||
!TLVP_PRES_LEN(tp, CBSP_IEI_RECOVERY_IND, 1)) {
@@ -1107,8 +1193,10 @@ static int cbsp_dec_restart(struct osmo_cbsp_restart *out, const struct tlv_pars
}
INIT_LLIST_HEAD(&out->cell_list.list);
- cbsp_decode_cell_list(&out->cell_list, ctx, TLVP_VAL(tp, CBSP_IEI_CELL_LIST),
- TLVP_LEN(tp, CBSP_IEI_CELL_LIST));
+ rc = cbsp_decode_cell_list(&out->cell_list, ctx, TLVP_VAL(tp, CBSP_IEI_CELL_LIST),
+ TLVP_LEN(tp, CBSP_IEI_CELL_LIST));
+ if (rc < 0)
+ return rc;
out->bcast_msg_type = *TLVP_VAL(tp, CBSP_IEI_BCAST_MSG_TYPE);
out->recovery_ind = *TLVP_VAL(tp, CBSP_IEI_RECOVERY_IND);
@@ -1119,6 +1207,8 @@ static int cbsp_dec_restart(struct osmo_cbsp_restart *out, const struct tlv_pars
static int cbsp_dec_failure(struct osmo_cbsp_failure *out, const struct tlv_parsed *tp,
struct msgb *in, void *ctx)
{
+ int rc;
+
if (!TLVP_PRES_LEN(tp, CBSP_IEI_FAILURE_LIST, 5) ||
!TLVP_PRES_LEN(tp, CBSP_IEI_BCAST_MSG_TYPE, 1)) {
osmo_cbsp_errstr = "missing/short mandatory IE";
@@ -1126,9 +1216,11 @@ static int cbsp_dec_failure(struct osmo_cbsp_failure *out, const struct tlv_pars
}
INIT_LLIST_HEAD(&out->fail_list);
- cbsp_decode_fail_list(&out->fail_list, ctx,
- TLVP_VAL(tp, CBSP_IEI_FAILURE_LIST),
- TLVP_LEN(tp, CBSP_IEI_FAILURE_LIST));
+ rc = cbsp_decode_fail_list(&out->fail_list, ctx,
+ TLVP_VAL(tp, CBSP_IEI_FAILURE_LIST),
+ TLVP_LEN(tp, CBSP_IEI_FAILURE_LIST));
+ if (rc < 0)
+ return rc;
out->bcast_msg_type = *TLVP_VAL(tp, CBSP_IEI_BCAST_MSG_TYPE);
return 0;
@@ -1169,6 +1261,7 @@ static int cbsp_dec_error_ind(struct osmo_cbsp_error_ind *out, const struct tlv_
* \returns callee-allocated decoded representation of CBSP message; NULL on error */
struct osmo_cbsp_decoded *osmo_cbsp_decode(void *ctx, struct msgb *in)
{
+ OSMO_ASSERT(in->l1h != NULL && in->l2h != NULL);
struct osmo_cbsp_decoded *out = talloc_zero(ctx, struct osmo_cbsp_decoded);
const struct cbsp_header *h = msgb_l1(in);
struct tlv_parsed tp[16]; /* max. number of pages in a given CBS message */
@@ -1434,6 +1527,10 @@ int osmo_cbsp_recv_buffered(void *ctx, int fd, struct msgb **rmsg, struct msgb *
needed = len - msgb_l2len(msg);
if (needed > 0) {
+ if (needed > msgb_tailroom(msg)) {
+ rc = -ENOMEM;
+ goto discard_msg;
+ }
rc = recv(fd, msg->tail, needed, 0);
if (rc == 0)
goto discard_msg;
@@ -1457,12 +1554,7 @@ int osmo_cbsp_recv_buffered(void *ctx, int fd, struct msgb **rmsg, struct msgb *
}
}
/* else: complete message received */
- rc = msgb_l2len(msg);
- if (rc == 0) {
- /* drop empty message */
- rc = -EAGAIN;
- goto discard_msg;
- }
+ rc = msgb_length(msg);
if (tmp_msg)
*tmp_msg = NULL;
*rmsg = msg;
@@ -1475,4 +1567,25 @@ discard_msg:
return rc;
}
+/*! value_string[] for enum osmo_cbsp_cause. */
+const struct value_string osmo_cbsp_cause_names[] = {
+ { OSMO_CBSP_CAUSE_PARAM_NOT_RECOGNISED, "Parameter-not-recognised" },
+ { OSMO_CBSP_CAUSE_PARAM_VALUE_INVALID, "Parameter-value-invalid" },
+ { OSMO_CBSP_CAUSE_MSG_REF_NOT_IDENTIFIED, "Message-reference-not-identified" },
+ { OSMO_CBSP_CAUSE_CELL_ID_NOT_VALID, "Cell-identity-not-valid" },
+ { OSMO_CBSP_CAUSE_UNRECOGNISED_MESSAGE, "Unrecognised-message" },
+ { OSMO_CBSP_CAUSE_MISSING_MANDATORY_ELEMENT, "Missing-mandatory-element" },
+ { OSMO_CBSP_CAUSE_BSC_CAPACITY_EXCEEDED, "BSC-capacity-exceeded" },
+ { OSMO_CBSP_CAUSE_CELL_MEMORY_EXCEEDED, "Cell-memory-exceeded" },
+ { OSMO_CBSP_CAUSE_BSC_MEMORY_EXCEEDED, "BSC-memory-exceeded" },
+ { OSMO_CBSP_CAUSE_CELL_BROADCAST_NOT_SUPPORTED, "Cell-broadcast-not-supported" },
+ { OSMO_CBSP_CAUSE_CELL_BROADCAST_NOT_OPERATIONAL, "Cell-broadcast-not-operational" },
+ { OSMO_CBSP_CAUSE_INCOMPATIBLE_DRX_PARAM, "Incompatible-DRX-parameter:"},
+ { OSMO_CBSP_CAUSE_EXT_CHAN_NOT_SUPPORTED, "Extended-channel-not-supported"},
+ { OSMO_CBSP_CAUSE_MSG_REF_ALREADY_USED, "Message-reference-already-used"},
+ { OSMO_CBSP_CAUSE_UNSPECIFIED_ERROR, "Unspecified-error"},
+ { OSMO_CBSP_CAUSE_LAI_OR_LAC_NOT_VALID, "LAI-or-LAC-not-valid"},
+ { 0, NULL }
+};
+
#endif /* HAVE_SYS_SOCKET_H */
diff --git a/src/gsm/comp128.c b/src/gsm/comp128.c
index b28a8437..e252b04c 100644
--- a/src/gsm/comp128.c
+++ b/src/gsm/comp128.c
@@ -58,10 +58,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <string.h>
diff --git a/src/gsm/comp128v23.c b/src/gsm/comp128v23.c
index 550f6a49..0947017c 100644
--- a/src/gsm/comp128v23.c
+++ b/src/gsm/comp128v23.c
@@ -23,10 +23,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <stdint.h>
diff --git a/src/gsm/gad.c b/src/gsm/gad.c
new file mode 100644
index 00000000..23b907ac
--- /dev/null
+++ b/src/gsm/gad.c
@@ -0,0 +1,491 @@
+/* 3GPP TS 23.032 GAD: Universal Geographical Area Description */
+/*
+ * (C) 2020 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * Author: Neels Hofmeyr <neels@hofmeyr.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <errno.h>
+#include <inttypes.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/gsm/gad.h>
+
+/*! \addtogroup gad
+ * @{
+ * \file gad.c
+ * Message encoding and decoding for 3GPP TS 23.032 GAD: Universal Geographical Area Description.
+ */
+
+const struct value_string osmo_gad_type_names[] = {
+ { GAD_TYPE_ELL_POINT, "Ellipsoid-point" },
+ { GAD_TYPE_ELL_POINT_UNC_CIRCLE, "Ellipsoid-point-with-uncertainty-circle" },
+ { GAD_TYPE_ELL_POINT_UNC_ELLIPSE, "Ellipsoid-point-with-uncertainty-ellipse" },
+ { GAD_TYPE_POLYGON, "Polygon" },
+ { GAD_TYPE_ELL_POINT_ALT, "Ellipsoid-point-with-altitude" },
+ { GAD_TYPE_ELL_POINT_ALT_UNC_ELL, "Ellipsoid-point-with-altitude-and-uncertainty-ellipsoid" },
+ { GAD_TYPE_ELL_ARC, "Ellipsoid-arc" },
+ { GAD_TYPE_HA_ELL_POINT_UNC_ELLIPSE, "High-accuracy-ellipsoid-point-with-uncertainty-ellipse" },
+ { GAD_TYPE_HA_ELL_POINT_ALT_UNC_ELL, "High-accuracy-ellipsoid-point-with-altitude-and-uncertainty-ellipsoid" },
+ {}
+};
+
+/*! Encode a latitude value according to 3GPP TS 23.032.
+ * Useful to clamp a latitude to an actually encodable accuracy:
+ * set_lat = osmo_gad_dec_lat(osmo_gad_enc_lat(orig_lat));
+ * \param[in] deg_1e6 Latitude in micro degrees (degrees * 1e6), -90'000'000 (S) .. 90'000'000 (N).
+ * \returns encoded latitude in host-byte-order (24bit).
+ */
+uint32_t osmo_gad_enc_lat(int32_t deg_1e6)
+{
+ /* N <= ((2**23)/90)*X < N+1
+ * N: encoded latitude
+ * X: latitude in degrees
+ */
+ int32_t sign = 0;
+ int64_t x;
+ deg_1e6 = OSMO_MAX(-90000000, OSMO_MIN(90000000, deg_1e6));
+ if (deg_1e6 < 0) {
+ sign = 1 << 23;
+ deg_1e6 = -deg_1e6;
+ }
+ x = deg_1e6;
+ x <<= 23;
+ x += (1 << 23) - 1;
+ x /= 90 * 1000000;
+ return sign | (x & 0x7fffff);
+}
+
+/*! Decode a latitude value according to 3GPP TS 23.032.
+ * Useful to clamp a latitude to an actually encodable accuracy:
+ * set_lat = osmo_gad_dec_lat(osmo_gad_enc_lat(orig_lat));
+ * \param[in] lat encoded latitude in host-byte-order (24bit).
+ * \returns decoded latitude in micro degrees (degrees * 1e6), -90'000'000 (S) .. 90'000'000 (N).
+ */
+int32_t osmo_gad_dec_lat(uint32_t lat)
+{
+ int64_t sign = 1;
+ int64_t x;
+ if (lat & 0x800000) {
+ sign = -1;
+ lat &= 0x7fffff;
+ }
+ x = lat;
+ x *= 90 * 1000000;
+ x >>= 23;
+ x *= sign;
+ return x;
+}
+
+/*! Encode a longitude value according to 3GPP TS 23.032.
+ * Useful to clamp a longitude to an actually encodable accuracy:
+ * set_lon = osmo_gad_dec_lon(osmo_gad_enc_lon(orig_lon));
+ * \param[in] deg_1e6 Longitude in micro degrees (degrees * 1e6), -180'000'000 (W) .. 180'000'000 (E).
+ * \returns encoded longitude in host-byte-order (24bit).
+ */
+uint32_t osmo_gad_enc_lon(int32_t deg_1e6)
+{
+ /* -180 .. 180 degrees mapped to a signed 24 bit integer.
+ * N <= ((2**24)/360) * X < N+1
+ * N: encoded longitude
+ * X: longitude in degrees
+ */
+ int64_t x;
+ deg_1e6 = OSMO_MAX(-180000000, OSMO_MIN(180000000, deg_1e6));
+ x = deg_1e6;
+ x *= (1 << 24);
+ if (deg_1e6 >= 0)
+ x += (1 << 24) - 1;
+ else
+ x -= (1 << 24) - 1;
+ x /= 360 * 1000000;
+ return (uint32_t)(x & 0xffffff);
+}
+
+/*! Decode a longitude value according to 3GPP TS 23.032.
+ * Normally, encoding and decoding is done via osmo_gad_enc() and osmo_gad_dec() for entire PDUs. But calling this
+ * directly can be useful to clamp a longitude to an actually encodable accuracy:
+ * int32_t set_lon = osmo_gad_dec_lon(osmo_gad_enc_lon(orig_lon));
+ * \param[in] lon Encoded longitude.
+ * \returns Longitude in micro degrees (degrees * 1e6), -180'000'000 (W) .. 180'000'000 (E).
+ */
+int32_t osmo_gad_dec_lon(uint32_t lon)
+{
+ /* -180 .. 180 degrees mapped to a signed 24 bit integer.
+ * N <= ((2**24)/360) * X < N+1
+ * N: encoded longitude
+ * X: longitude in degrees
+ */
+ int32_t slon;
+ int64_t x;
+ if (lon & 0x800000) {
+ /* make the 24bit negative number to a 32bit negative number */
+ slon = lon | 0xff000000;
+ } else {
+ slon = lon;
+ }
+ x = slon;
+ x *= 360 * 1000000;
+ x /= (1 << 24);
+ return x;
+}
+
+/*
+ * r = C((1+x)**K - 1)
+ * C = 10, x = 0.1
+ *
+ * def r(k):
+ * return 10.*(((1+0.1)**k) -1 )
+ * for k in range(128):
+ * print('%d,' % (r(k) * 1000.))
+ */
+static uint32_t table_uncertainty_1e3[128] = {
+ 0, 1000, 2100, 3310, 4641, 6105, 7715, 9487, 11435, 13579, 15937, 18531, 21384, 24522, 27974, 31772, 35949,
+ 40544, 45599, 51159, 57274, 64002, 71402, 79543, 88497, 98347, 109181, 121099, 134209, 148630, 164494, 181943,
+ 201137, 222251, 245476, 271024, 299126, 330039, 364043, 401447, 442592, 487851, 537636, 592400, 652640, 718904,
+ 791795, 871974, 960172, 1057189, 1163908, 1281299, 1410429, 1552472, 1708719, 1880591, 2069650, 2277615,
+ 2506377, 2758014, 3034816, 3339298, 3674227, 4042650, 4447915, 4893707, 5384077, 5923485, 6516834, 7169517,
+ 7887469, 8677216, 9545938, 10501531, 11552685, 12708953, 13980849, 15379933, 16918927, 18611820, 20474002,
+ 22522402, 24775642, 27254206, 29980627, 32979690, 36278659, 39907525, 43899277, 48290205, 53120226, 58433248,
+ 64277573, 70706330, 77777964, 85556760, 94113436, 103525780, 113879358, 125268293, 137796123, 151576735,
+ 166735409, 183409950, 201751945, 221928139, 244121953, 268535149, 295389664, 324929630, 357423593, 393166952,
+ 432484648, 475734112, 523308524, 575640376, 633205414, 696526955, 766180651, 842799716, 927080688, 1019789756,
+ 1121769732, 1233947705, 1357343476, 1493078824, 1642387706, 1806627477,
+};
+
+/*! Decode an uncertainty circle value according to 3GPP TS 23.032.
+ * Useful to clamp a value to an actually encodable accuracy:
+ * set_unc = osmo_gad_dec_unc(osmo_gad_enc_unc(orig_unc));
+ * \param[in] unc Encoded uncertainty value.
+ * \returns Uncertainty value in millimeters.
+ */
+uint32_t osmo_gad_dec_unc(uint8_t unc)
+{
+ return table_uncertainty_1e3[unc & 0x7f];
+}
+
+/*! Encode an uncertainty circle value according to 3GPP TS 23.032.
+ * Normally, encoding and decoding is done via osmo_gad_enc() and osmo_gad_dec() for entire PDUs. But calling this
+ * directly can be useful to clamp a value to an actually encodable accuracy:
+ * uint32_t set_unc = osmo_gad_dec_unc(osmo_gad_enc_unc(orig_unc));
+ * \param[in] mm Uncertainty value in millimeters.
+ * \returns Encoded uncertainty value.
+ */
+uint8_t osmo_gad_enc_unc(uint32_t mm)
+{
+ uint8_t unc;
+ for (unc = 0; unc < ARRAY_SIZE(table_uncertainty_1e3); unc++) {
+ if (table_uncertainty_1e3[unc] > mm)
+ return unc - 1;
+ }
+ return 127;
+}
+
+/* So far we don't encode a high-accuracy uncertainty anywhere, so these static items would flag as compiler warnings
+ * for unused items. As soon as any HA items get used, remove this ifdef. */
+#ifdef GAD_FUTURE
+
+/*
+ * r = C((1+x)**K - 1)
+ * C = 0.3, x = 0.02
+ *
+ * def r(k):
+ * return 0.3*(((1+0.02)**k) -1 )
+ * for k in range(256):
+ * print('%d,' % (r(k) * 1000.))
+ */
+static uint32_t table_ha_uncertainty_1e3[256] = {
+ 0, 6, 12, 18, 24, 31, 37, 44, 51, 58, 65, 73, 80, 88, 95, 103, 111, 120, 128, 137, 145, 154, 163, 173, 182, 192,
+ 202, 212, 222, 232, 243, 254, 265, 276, 288, 299, 311, 324, 336, 349, 362, 375, 389, 402, 417, 431, 445, 460,
+ 476, 491, 507, 523, 540, 556, 574, 591, 609, 627, 646, 665, 684, 703, 724, 744, 765, 786, 808, 830, 853, 876,
+ 899, 923, 948, 973, 998, 1024, 1051, 1078, 1105, 1133, 1162, 1191, 1221, 1252, 1283, 1314, 1347, 1380, 1413,
+ 1447, 1482, 1518, 1554, 1592, 1629, 1668, 1707, 1748, 1788, 1830, 1873, 1916, 1961, 2006, 2052, 2099, 2147,
+ 2196, 2246, 2297, 2349, 2402, 2456, 2511, 2567, 2625, 2683, 2743, 2804, 2866, 2929, 2994, 3060, 3127, 3195,
+ 3265, 3336, 3409, 3483, 3559, 3636, 3715, 3795, 3877, 3961, 4046, 4133, 4222, 4312, 4404, 4498, 4594, 4692,
+ 4792, 4894, 4998, 5104, 5212, 5322, 5435, 5549, 5666, 5786, 5907, 6032, 6158, 6287, 6419, 6554, 6691, 6830,
+ 6973, 7119, 7267, 7418, 7573, 7730, 7891, 8055, 8222, 8392, 8566, 8743, 8924, 9109, 9297, 9489, 9685, 9884,
+ 10088, 10296, 10508, 10724, 10944, 11169, 11399, 11633, 11871, 12115, 12363, 12616, 12875, 13138, 13407, 13681,
+ 13961, 14246, 14537, 14834, 15136, 15445, 15760, 16081, 16409, 16743, 17084, 17431, 17786, 18148, 18517, 18893,
+ 19277, 19669, 20068, 20475, 20891, 21315, 21747, 22188, 22638, 23096, 23564, 24042, 24529, 25025, 25532, 26048,
+ 26575, 27113, 27661, 28220, 28791, 29372, 29966, 30571, 31189, 31818, 32461, 33116, 33784, 34466, 35161, 35871,
+ 36594, 37332, 38085, 38852, 39635, 40434, 41249, 42080, 42927, 43792, 44674, 45573, 46491,
+};
+
+static uint32_t osmo_gad_dec_ha_unc(uint8_t unc)
+{
+ return table_uncertainty_1e3[unc];
+}
+
+static uint8_t osmo_gad_enc_ha_unc(uint32_t mm)
+{
+ uint8_t unc;
+ for (unc = 0; unc < ARRAY_SIZE(table_ha_uncertainty_1e3); unc++) {
+ if (table_uncertainty_1e3[unc] > mm)
+ return unc - 1;
+ }
+ return 255;
+}
+
+#endif /* GAD_FUTURE */
+
+/* Return error code, and, if required, allocate and populate struct osmo_gad_err. */
+#define DEC_ERR(RC, TYPE, fmt, args...) do { \
+ if (err) { \
+ *err = talloc_zero(err_ctx, struct osmo_gad_err); \
+ **err = (struct osmo_gad_err){ \
+ .rc = (RC), \
+ .type = (TYPE), \
+ .logmsg = talloc_asprintf(*err, "Error decoding GAD%s%s: " fmt, \
+ ((int)(TYPE)) >= 0 ? " " : "", \
+ ((int)(TYPE)) >= 0 ? osmo_gad_type_name(TYPE) : "", ##args), \
+ }; \
+ } \
+ return RC; \
+ } while(0)
+
+static int osmo_gad_enc_ell_point_unc_circle(struct gad_raw_ell_point_unc_circle *raw, const struct osmo_gad_ell_point_unc_circle *v)
+{
+ if (v->lat < -90000000 || v->lat > 90000000)
+ return -EINVAL;
+ if (v->lon < -180000000 || v->lon > 180000000)
+ return -EINVAL;
+ *raw = (struct gad_raw_ell_point_unc_circle){
+ .h = { .type = GAD_TYPE_ELL_POINT_UNC_CIRCLE },
+ .unc = osmo_gad_enc_unc(v->unc),
+ };
+ osmo_store32be_ext(osmo_gad_enc_lat(v->lat), raw->lat, 3);
+ osmo_store32be_ext(osmo_gad_enc_lon(v->lon), raw->lon, 3);
+ return sizeof(raw);
+}
+
+static int osmo_gad_dec_ell_point_unc_circle(struct osmo_gad_ell_point_unc_circle *v,
+ struct osmo_gad_err **err, void *err_ctx,
+ const struct gad_raw_ell_point_unc_circle *raw)
+{
+ /* Load 24bit big endian */
+ v->lat = osmo_gad_dec_lat(osmo_load32be_ext_2(raw->lat, 3));
+ v->lon = osmo_gad_dec_lon(osmo_load32be_ext_2(raw->lon, 3));
+
+ if (raw->spare2)
+ DEC_ERR(-EINVAL, raw->h.type, "Bit 8 of Uncertainty code should be zero");
+
+ v->unc = osmo_gad_dec_unc(raw->unc);
+ return 0;
+}
+
+static int osmo_gad_raw_len(const union gad_raw *gad_raw)
+{
+ switch (gad_raw->h.type) {
+ case GAD_TYPE_ELL_POINT:
+ return sizeof(gad_raw->ell_point);
+ case GAD_TYPE_ELL_POINT_UNC_CIRCLE:
+ return sizeof(gad_raw->ell_point_unc_circle);
+ case GAD_TYPE_ELL_POINT_UNC_ELLIPSE:
+ return sizeof(gad_raw->ell_point_unc_ellipse);
+ case GAD_TYPE_POLYGON:
+ if (gad_raw->polygon.h.num_points < 3)
+ return -EINVAL;
+ return sizeof(gad_raw->polygon.h)
+ + gad_raw->polygon.h.num_points * sizeof(gad_raw->polygon.point[0]);
+ case GAD_TYPE_ELL_POINT_ALT:
+ return sizeof(gad_raw->ell_point_alt);
+ case GAD_TYPE_ELL_POINT_ALT_UNC_ELL:
+ return sizeof(gad_raw->ell_point_alt_unc_ell);
+ case GAD_TYPE_ELL_ARC:
+ return sizeof(gad_raw->ell_arc);
+ case GAD_TYPE_HA_ELL_POINT_UNC_ELLIPSE:
+ return sizeof(gad_raw->ha_ell_point_unc_ell);
+ case GAD_TYPE_HA_ELL_POINT_ALT_UNC_ELL:
+ return sizeof(gad_raw->ha_ell_point_alt_unc_ell);
+ default:
+ return -ENOTSUP;
+ }
+}
+
+/*! Append a GAD PDU to the msgb.
+ * Write the correct number of bytes depending on the GAD type and possibly on variable length attributes.
+ * \param[out] msg Append to this msgb.
+ * \param[in] gad_raw GAD data to write.
+ * \returns number of bytes appended to msgb, or negative on failure.
+ */
+int osmo_gad_raw_write(struct msgb *msg, const union gad_raw *gad_raw)
+{
+ int len;
+ uint8_t *dst;
+ len = osmo_gad_raw_len(gad_raw);
+ if (len < 0)
+ return len;
+ dst = msgb_put(msg, len);
+ memcpy(dst, (void*)gad_raw, len);
+ return len;
+}
+
+/*! Read a GAD PDU and validate structure.
+ * Memcpy from data to gad_raw struct, and validate correct length depending on the GAD type and possibly on variable
+ * length attributes.
+ * \param[out] gad_raw Copy GAD PDU here.
+ * \param[out] err Returned pointer to error info, dynamically allocated; NULL to not return any.
+ * \param[in] err_ctx Talloc context to allocate err from, if required.
+ * \param[in] data Encoded GAD bytes buffer.
+ * \param[in] len Length of data in bytes.
+ * \returns 0 on success, negative on error. If returning negative and err was non-NULL, *err is guaranteed to point to
+ * an allocated struct osmo_gad_err.
+ */
+int osmo_gad_raw_read(union gad_raw *gad_raw, struct osmo_gad_err **err, void *err_ctx, const uint8_t *data, uint8_t len)
+{
+ int gad_len;
+ const union gad_raw *src;
+ if (err)
+ *err = NULL;
+ if (len < sizeof(src->h))
+ DEC_ERR(-EINVAL, -1, "GAD data too short for header (%u bytes)", len);
+
+ src = (void*)data;
+ gad_len = osmo_gad_raw_len(src);
+ if (gad_len < 0)
+ DEC_ERR(-EINVAL, src->h.type, "GAD data invalid (rc=%d)", gad_len);
+ if (gad_len != len)
+ DEC_ERR(-EINVAL, src->h.type, "GAD data with unexpected length: expected %d bytes, got %u",
+ gad_len, len);
+
+ memcpy((void*)gad_raw, data, gad_len);
+ return 0;
+}
+
+/*! Write GAD values with consistent units to raw GAD PDU representation.
+ * \param[out] gad_raw Write to this buffer.
+ * \param[in] gad GAD values to encode.
+ * \returns number of bytes written, or negative on failure.
+ */
+int osmo_gad_enc(union gad_raw *gad_raw, const struct osmo_gad *gad)
+{
+ switch (gad->type) {
+ case GAD_TYPE_ELL_POINT_UNC_CIRCLE:
+ return osmo_gad_enc_ell_point_unc_circle(&gad_raw->ell_point_unc_circle, &gad->ell_point_unc_circle);
+ default:
+ return -ENOTSUP;
+ }
+}
+
+/*! Decode GAD raw PDU to values with consistent units.
+ * \param[out] gad Decoded GAD values are written here.
+ * \param[out] err Returned pointer to error info, dynamically allocated; NULL to not return any.
+ * \param[in] err_ctx Talloc context to allocate err from, if required.
+ * \param[in] raw Raw GAD data in network-byte-order.
+ * \returns 0 on success, negative on error. If returning negative and err was non-NULL, *err is guaranteed to point to
+ * an allocated struct osmo_gad_err.
+ */
+int osmo_gad_dec(struct osmo_gad *gad, struct osmo_gad_err **err, void *err_ctx, const union gad_raw *raw)
+{
+ *gad = (struct osmo_gad){
+ .type = raw->h.type,
+ };
+ switch (raw->h.type) {
+ case GAD_TYPE_ELL_POINT_UNC_CIRCLE:
+ return osmo_gad_dec_ell_point_unc_circle(&gad->ell_point_unc_circle, err, err_ctx,
+ &raw->ell_point_unc_circle);
+ default:
+ DEC_ERR(-ENOTSUP, raw->h.type, "unsupported GAD type");
+ }
+}
+
+/*! Return a human readable representation of a raw GAD PDU.
+ * Convert to GAD values and feed the result to osmo_gad_to_str_buf().
+ * \param[out] buf Buffer to write string to.
+ * \param[in] buflen sizeof(buf).
+ * \param[in] gad Location data.
+ * \returns number of chars that would be written, like snprintf().
+ */
+int osmo_gad_raw_to_str_buf(char *buf, size_t buflen, const union gad_raw *raw)
+{
+ struct osmo_gad gad;
+ if (osmo_gad_dec(&gad, NULL, NULL, raw)) {
+ struct osmo_strbuf sb = { .buf = buf, .len = buflen };
+ OSMO_STRBUF_PRINTF(sb, "invalid");
+ return sb.chars_needed;
+ }
+ return osmo_gad_to_str_buf(buf, buflen, &gad);
+}
+
+/*! Return a human readable representation of a raw GAD PDU.
+ * Convert to GAD values and feed the result to osmo_gad_to_str_buf().
+ * \param[in] ctx Talloc ctx to allocate string buffer from.
+ * \param[in] raw GAD data in network-byte-order.
+ * \returns resulting string, dynamically allocated.
+ */
+char *osmo_gad_raw_to_str_c(void *ctx, const union gad_raw *raw)
+{
+ OSMO_NAME_C_IMPL(ctx, 128, "ERROR", osmo_gad_raw_to_str_buf, raw)
+}
+
+/*! Return a human readable representation of GAD (location estimate) values.
+ * \param[out] buf Buffer to write string to.
+ * \param[in] buflen sizeof(buf).
+ * \param[in] gad Location data.
+ * \returns number of chars that would be written, like snprintf().
+ */
+int osmo_gad_to_str_buf(char *buf, size_t buflen, const struct osmo_gad *gad)
+{
+ struct osmo_strbuf sb = { .buf = buf, .len = buflen };
+
+ if (!gad) {
+ OSMO_STRBUF_PRINTF(sb, "null");
+ return sb.chars_needed;
+ }
+
+ OSMO_STRBUF_PRINTF(sb, "%s{", osmo_gad_type_name(gad->type));
+
+ switch (gad->type) {
+ case GAD_TYPE_ELL_POINT:
+ OSMO_STRBUF_PRINTF(sb, "lat=");
+ OSMO_STRBUF_APPEND(sb, osmo_int_to_float_str_buf, gad->ell_point.lat, 6);
+ OSMO_STRBUF_PRINTF(sb, ",lon=");
+ OSMO_STRBUF_APPEND(sb, osmo_int_to_float_str_buf, gad->ell_point.lon, 6);
+ break;
+
+ case GAD_TYPE_ELL_POINT_UNC_CIRCLE:
+ OSMO_STRBUF_PRINTF(sb, "lat=");
+ OSMO_STRBUF_APPEND(sb, osmo_int_to_float_str_buf, gad->ell_point_unc_circle.lat, 6);
+ OSMO_STRBUF_PRINTF(sb, ",lon=");
+ OSMO_STRBUF_APPEND(sb, osmo_int_to_float_str_buf, gad->ell_point_unc_circle.lon, 6);
+ OSMO_STRBUF_PRINTF(sb, ",unc=");
+ OSMO_STRBUF_APPEND(sb, osmo_int_to_float_str_buf, gad->ell_point_unc_circle.unc, 3);
+ OSMO_STRBUF_PRINTF(sb, "m");
+ break;
+
+ default:
+ OSMO_STRBUF_PRINTF(sb, "to-str-not-implemented");
+ break;
+ }
+
+ OSMO_STRBUF_PRINTF(sb, "}");
+ return sb.chars_needed;
+}
+
+/*! Return a human readable representation of GAD (location estimate) values.
+ * \param[in] ctx Talloc ctx to allocate string buffer from.
+ * \param[in] val Value to convert to float.
+ * \returns resulting string, dynamically allocated.
+ */
+char *osmo_gad_to_str_c(void *ctx, const struct osmo_gad *gad)
+{
+ OSMO_NAME_C_IMPL(ctx, 128, "ERROR", osmo_gad_to_str_buf, gad)
+}
+
+/*! @} */
diff --git a/src/gsm/gea.c b/src/gsm/gea.c
index 5756bb08..aedc898e 100644
--- a/src/gsm/gea.c
+++ b/src/gsm/gea.c
@@ -12,10 +12,6 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <osmocom/core/bits.h>
diff --git a/src/gsm/gprs_cipher_core.c b/src/gsm/gprs_cipher_core.c
index 7f2b1a50..97e581db 100644
--- a/src/gsm/gprs_cipher_core.c
+++ b/src/gsm/gprs_cipher_core.c
@@ -17,10 +17,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include "config.h"
diff --git a/src/gsm/gprs_gea.c b/src/gsm/gprs_gea.c
index 73147886..eae1cf12 100644
--- a/src/gsm/gprs_gea.c
+++ b/src/gsm/gprs_gea.c
@@ -16,10 +16,6 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <osmocom/crypt/gprs_cipher.h>
diff --git a/src/gsm/gprs_rlc.c b/src/gsm/gprs_rlc.c
index bdfc8eac..4f02a7a9 100644
--- a/src/gsm/gprs_rlc.c
+++ b/src/gsm/gprs_rlc.c
@@ -13,9 +13,8 @@
#include <string.h>
#include <osmocom/core/utils.h>
-#include <osmocom/gprs/gprs_rlc.h>
#include <osmocom/coding/gsm0503_coding.h>
-#include <osmocom/gprs/protocol/gsm_04_60.h>
+#include <osmocom/gsm/protocol/gsm_44_060.h>
#define EGPRS_CPS_TYPE1_TBL_SZ 29
#define EGPRS_CPS_TYPE2_TBL_SZ 8
diff --git a/src/gsm/gsm0341.c b/src/gsm/gsm0341.c
index 89f5de3f..a6238b36 100644
--- a/src/gsm/gsm0341.c
+++ b/src/gsm/gsm0341.c
@@ -14,10 +14,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
diff --git a/src/gsm/gsm0411_smr.c b/src/gsm/gsm0411_smr.c
index 02b41995..03cf0051 100644
--- a/src/gsm/gsm0411_smr.c
+++ b/src/gsm/gsm0411_smr.c
@@ -131,7 +131,7 @@ const struct value_string gsm411_rp_cause_strs[] = {
{ GSM411_RP_CAUSE_INV_TRANS_REF, "Invalid Transaction Reference" },
{ GSM411_RP_CAUSE_SEMANT_INC_MSG, "Semantically Incorrect Message" },
{ GSM411_RP_CAUSE_INV_MAND_INF, "Invalid Mandatory Information" },
- { GSM411_RP_CAUSE_MSGTYPE_NOTEXIST, "Message Type non-existant" },
+ { GSM411_RP_CAUSE_MSGTYPE_NOTEXIST, "Message Type non-existent" },
{ GSM411_RP_CAUSE_MSG_INCOMP_STATE, "Message incompatible with protocol state" },
{ GSM411_RP_CAUSE_IE_NOTEXIST, "Information Element not existing" },
{ GSM411_RP_CAUSE_PROTOCOL_ERR, "Protocol Error" },
diff --git a/src/gsm/gsm0411_utils.c b/src/gsm/gsm0411_utils.c
index ccefe546..470b017f 100644
--- a/src/gsm/gsm0411_utils.c
+++ b/src/gsm/gsm0411_utils.c
@@ -27,7 +27,7 @@
*
*/
-#include "../../config.h"
+#include "config.h"
#include <time.h>
#include <string.h>
@@ -293,7 +293,7 @@ enum sms_alphabet gsm338_get_sms_alphabet(uint8_t dcs)
* \param[in] type GSM340_TYPE_*
* \param[in] plan Numbering Plan
* \param[in] number string containing number
- * \reurns number of bytes of \a oa that have been used */
+ * \returns number of bytes of \a oa that have been used */
int gsm340_gen_oa(uint8_t *oa, unsigned int oa_len, uint8_t type,
uint8_t plan, const char *number)
{
@@ -348,7 +348,7 @@ int gsm411_push_rp_header(struct msgb *msg, uint8_t rp_msg_type,
* \param[in] proto Protocol
* \param[in] trans Transaction
* \param[in] msg_type Message Type
- * \retrns 0 */
+ * \returns 0 */
int gsm411_push_cp_header(struct msgb *msg, uint8_t proto, uint8_t trans,
uint8_t msg_type)
{
diff --git a/src/gsm/gsm0480.c b/src/gsm/gsm0480.c
index 1d5cde95..7a7f71f0 100644
--- a/src/gsm/gsm0480.c
+++ b/src/gsm/gsm0480.c
@@ -19,10 +19,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <osmocom/gsm/gsm48.h>
diff --git a/src/gsm/gsm0502.c b/src/gsm/gsm0502.c
index e34d3f57..b1474061 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. */
diff --git a/src/gsm/gsm0808.c b/src/gsm/gsm0808.c
index 53220617..e1768c1a 100644
--- a/src/gsm/gsm0808.c
+++ b/src/gsm/gsm0808.c
@@ -15,10 +15,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <string.h>
@@ -26,9 +22,11 @@
#include <osmocom/core/byteswap.h>
#include <osmocom/core/endian.h>
#include <osmocom/gsm/gsm0808.h>
+#include <osmocom/gsm/gsm0808_lcs.h>
#include <osmocom/gsm/gsm0808_utils.h>
#include <osmocom/gsm/protocol/gsm_08_08.h>
#include <osmocom/gsm/gsm48.h>
+#include <osmocom/gsm/gad.h>
/*! \addtogroup gsm0808
* @{
@@ -102,13 +100,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.
@@ -226,22 +230,32 @@ struct msgb *gsm0808_create_clear_command2(uint8_t cause, bool csfb_ind)
return msg;
}
-/*! Create BSSMAP Cipher Mode Command message
+/*! Superseded by gsm0808_create_cipher2() to include Kc128.
+ * Create BSSMAP Cipher Mode Command message (without Kc128).
* \param[in] ei Mandatory Encryption Information
+ * \param[in] kc128 optional kc128 key for A5/4
* \param[in] cipher_response_mode optional 1-byte Cipher Response Mode
* \returns callee-allocated msgb with BSSMAP Cipher Mode Command message */
struct msgb *gsm0808_create_cipher(const struct gsm0808_encrypt_info *ei,
const uint8_t *cipher_response_mode)
{
+ struct gsm0808_cipher_mode_command cmc = {
+ .ei = *ei,
+ .cipher_response_mode_present = (cipher_response_mode != NULL),
+ .cipher_response_mode = (cipher_response_mode ? *cipher_response_mode : 0),
+ };
+ return gsm0808_create_cipher2(&cmc);
+}
+
+/*! Create BSSMAP Cipher Mode Command message.
+ * \param[in] cmc Information to encode.
+ * \returns callee-allocated msgb with BSSMAP Cipher Mode Command message */
+struct msgb *gsm0808_create_cipher2(const struct gsm0808_cipher_mode_command *cmc)
+{
/* See also: 3GPP TS 48.008 3.2.1.30 CIPHER MODE COMMAND */
struct msgb *msg;
- /* Mandatory emelent! */
- OSMO_ASSERT(ei);
-
- msg =
- msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM,
- "cipher-mode-command");
+ msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM, "cipher-mode-command");
if (!msg)
return NULL;
@@ -249,12 +263,16 @@ struct msgb *gsm0808_create_cipher(const struct gsm0808_encrypt_info *ei,
msgb_v_put(msg, BSS_MAP_MSG_CIPHER_MODE_CMD);
/* Encryption Information 3.2.2.10 */
- gsm0808_enc_encrypt_info(msg, ei);
+ gsm0808_enc_encrypt_info(msg, &cmc->ei);
/* Cipher Response Mode 3.2.2.34 */
- if (cipher_response_mode)
+ if (cmc->cipher_response_mode_present)
msgb_tv_put(msg, GSM0808_IE_CIPHER_RESPONSE_MODE,
- *cipher_response_mode);
+ cmc->cipher_response_mode);
+
+ /* Kc128 3.2.2.109 */
+ if (cmc->kc128_present)
+ gsm0808_enc_kc128(msg, cmc->kc128);
/* pre-pend the header */
msg->l3h =
@@ -361,7 +379,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;
@@ -406,7 +424,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");
@@ -492,8 +510,6 @@ struct msgb *gsm0808_create_ass2(const struct gsm0808_channel_type *ct,
{
/* See also: 3GPP TS 48.008 3.2.1.1 ASSIGNMENT REQUEST */
struct msgb *msg;
- uint16_t cic_sw;
- uint32_t ci_sw;
/* Mandatory emelent! */
OSMO_ASSERT(ct);
@@ -511,11 +527,8 @@ struct msgb *gsm0808_create_ass2(const struct gsm0808_channel_type *ct,
gsm0808_enc_channel_type(msg, ct);
/* Circuit Identity Code 3.2.2.2 */
- if (cic) {
- cic_sw = osmo_htons(*cic);
- msgb_tv_fixed_put(msg, GSM0808_IE_CIRCUIT_IDENTITY_CODE,
- sizeof(cic_sw), (uint8_t *) & cic_sw);
- }
+ if (cic)
+ msgb_tv16_put(msg, GSM0808_IE_CIRCUIT_IDENTITY_CODE, *cic);
/* AoIP: AoIP Transport Layer Address (MGW) 3.2.2.102 */
if (ss) {
@@ -523,23 +536,17 @@ struct msgb *gsm0808_create_ass2(const struct gsm0808_channel_type *ct,
}
/* AoIP: Codec List (MSC Preferred) 3.2.2.103 */
- if (scl)
- gsm0808_enc_speech_codec_list(msg, scl);
+ if (scl) {
+ if (gsm0808_enc_speech_codec_list2(msg, scl) < 0)
+ goto exit_free;
+ }
/* AoIP: Call Identifier 3.2.2.105 */
if (ci) {
/* NOTE: 3GPP TS 48.008, section 3.2.2.105 specifies that
- the least significant byte should be transmitted first.
- On x86, this would mean that the endieness is already
- correct, however a platform independed implementation
- is required: */
-#ifndef OSMO_IS_LITTLE_ENDIAN
- ci_sw = osmo_swab32(*ci);
-#else
- ci_sw = *ci;
-#endif
- msgb_tv_fixed_put(msg, GSM0808_IE_CALL_ID, sizeof(ci_sw),
- (uint8_t *) & ci_sw);
+ * the least significant byte shall be transmitted first. */
+ msgb_v_put(msg, GSM0808_IE_CALL_ID);
+ osmo_store32le(*ci, msgb_put(msg, sizeof(*ci)));
}
if (kc)
@@ -553,6 +560,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.
@@ -617,12 +628,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 */
@@ -633,6 +648,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
@@ -694,13 +713,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
@@ -802,7 +827,7 @@ struct msgb *gsm0808_create_paging(const char *imsi, const uint32_t *tmsi,
{
struct gsm0808_cell_id_list2 cil2 = {};
- /* Mandatory emelents! */
+ /* Mandatory elements! */
OSMO_ASSERT(cil);
if (cil->id_list_len > GSM0808_CELL_ID_LIST2_MAXLEN)
@@ -844,6 +869,11 @@ static uint8_t put_old_bss_to_new_bss_information(struct msgb *msg,
msgb_tlv_put(msg, GSM0808_FE_IE_CURRENT_CHANNEL_TYPE_2, 2, val);
}
+ if (i->last_eutran_plmn_id_present) {
+ msgb_put_u8(msg, GSM0808_FE_IE_LAST_USED_EUTRAN_PLMN_ID);
+ osmo_plmn_to_bcd(msgb_put(msg, 3), &i->last_eutran_plmn_id);
+ }
+
*tlv_len = (uint8_t) (msg->tail - old_tail);
return *tlv_len + 2;
}
@@ -988,8 +1018,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];
@@ -997,6 +1029,9 @@ struct msgb *gsm0808_create_handover_request(const struct gsm0808_handover_reque
msgb_tv_fixed_put(msg, GSM0808_IE_CALL_ID, 4, val);
}
+ if (params->more_items && params->kc128_present)
+ gsm0808_enc_kc128(msg, params->kc128);
+
if (params->global_call_reference && params->global_call_reference_len) {
msgb_tlv_put(msg, GSM0808_IE_GLOBAL_CALL_REF,
params->global_call_reference_len, params->global_call_reference);
@@ -1006,6 +1041,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.
@@ -1038,14 +1077,28 @@ struct msgb *gsm0808_create_handover_request_ack2(const struct gsm0808_handover_
if (params->aoip_transport_layer)
gsm0808_enc_aoip_trasp_addr(msg, params->aoip_transport_layer);
+ /* AoIP: add Codec List (BSS Supported) 3.2.2.103.
+ * (codec_list_bss_supported was added to struct gsm0808_handover_request_ack later than speech_codec_chosen
+ * below, but it needs to come before it in the message coding). */
+ if (params->more_items && params->codec_list_bss_supported.len) {
+ if (gsm0808_enc_speech_codec_list2(msg, &params->codec_list_bss_supported) < 0)
+ goto exit_free;
+ }
+
/* AoIP: Speech Codec (Chosen) 3.2.2.104 */
- if (params->speech_codec_chosen_present)
- gsm0808_enc_speech_codec(msg, &params->speech_codec_chosen);
+ if (params->speech_codec_chosen_present) {
+ if (gsm0808_enc_speech_codec2(msg, &params->speech_codec_chosen) < 0)
+ goto exit_free;
+ }
/* prepend header with final length */
msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg));
return msg;
+
+exit_free:
+ msgb_free(msg);
+ return NULL;
}
/*! Same as gsm0808_create_handover_request_ack2() but with less parameters.
@@ -1097,7 +1150,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;
@@ -1116,7 +1169,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;
@@ -1151,12 +1204,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)
@@ -1170,6 +1227,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.
@@ -1193,13 +1254,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.
@@ -1235,8 +1302,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)
@@ -1246,6 +1315,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.
@@ -1341,6 +1414,106 @@ struct msgb *gsm0808_create_dtap(struct msgb *msg_l3, uint8_t link_id)
return msg;
}
+struct msgb *gsm0808_create_perform_location_request(const struct gsm0808_perform_location_request *params)
+{
+ struct msgb *msg;
+ uint8_t *out;
+ int rc;
+
+ msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM, "BSSMAP-PERFORM-LOCATION-REQUEST");
+ if (!msg)
+ return NULL;
+
+ /* Message Type, 3.2.2.1 */
+ msgb_v_put(msg, BSS_MAP_MSG_PERFORM_LOCATION_RQST);
+
+ /* Location Type 3.2.2.63 */
+ osmo_bssmap_le_ie_enc_location_type(msg, &params->location_type);
+
+ if (params->imsi.type == GSM_MI_TYPE_IMSI) {
+ /* IMSI 3.2.2.6 */
+ out = msgb_tl_put(msg, GSM0808_IE_IMSI);
+ rc = osmo_mobile_identity_encode_msgb(msg, &params->imsi, false);
+ if (rc < 0) {
+ msgb_free(msg);
+ return NULL;
+ }
+ /* write the MI value length */
+ *out = rc;
+ }
+
+ /* prepend header with final length */
+ msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg));
+
+ return msg;
+}
+
+struct msgb *gsm0808_create_perform_location_response(const struct gsm0808_perform_location_response *params)
+{
+ struct msgb *msg;
+
+ msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM, "BSSMAP-PERFORM-LOCATION-RESPONSE");
+ if (!msg)
+ return NULL;
+
+ /* Message Type, 3.2.2.1 */
+ msgb_v_put(msg, BSS_MAP_MSG_PERFORM_LOCATION_RESPONSE);
+
+ if (params->location_estimate_present) {
+ uint8_t *l = msgb_tl_put(msg, GSM0808_IE_LOCATION_ESTIMATE);
+ int rc = osmo_gad_raw_write(msg, &params->location_estimate);
+ if (rc < 0) {
+ msgb_free(msg);
+ return NULL;
+ }
+ *l = rc;
+ }
+
+ if (params->lcs_cause.present) {
+ uint8_t *l = msgb_tl_put(msg, GSM0808_IE_LCS_CAUSE);
+ int rc = osmo_lcs_cause_enc(msg, &params->lcs_cause);
+ if (rc < 0) {
+ msgb_free(msg);
+ return NULL;
+ }
+ *l = rc;
+ }
+
+ /* prepend header with final length */
+ msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg));
+
+ return msg;
+}
+
+int gsm0808_enc_lcs_cause(struct msgb *msg, const struct lcs_cause_ie *lcs_cause)
+{
+ uint8_t *l = msgb_tl_put(msg, GSM0808_IE_LCS_CAUSE);
+ int rc = osmo_lcs_cause_enc(msg, lcs_cause);
+ if (rc <= 0)
+ return rc;
+ *l = rc;
+ return rc + 2;
+}
+
+struct msgb *gsm0808_create_perform_location_abort(const struct lcs_cause_ie *lcs_cause)
+{
+ struct msgb *msg;
+
+ msg = msgb_alloc_headroom(BSSMAP_MSG_SIZE, BSSMAP_MSG_HEADROOM, "BSSMAP-PERFORM-LOCATION-ABORT");
+ if (!msg)
+ return NULL;
+
+ /* Message Type, 3.2.2.1 */
+ msgb_v_put(msg, BSS_MAP_MSG_PERFORM_LOCATION_ABORT);
+
+ gsm0808_enc_lcs_cause(msg, lcs_cause);
+
+ /* prepend header with final length */
+ msg->l3h = msgb_tv_push(msg, BSSAP_MSG_BSS_MANAGEMENT, msgb_length(msg));
+
+ return msg;
+}
+
/* As per 3GPP TS 48.008 version 11.7.0 Release 11 */
static const struct tlv_definition bss_att_tlvdef = {
.def = {
@@ -1412,6 +1585,7 @@ static const struct tlv_definition bss_att_tlvdef = {
[GSM0808_IE_LOCATION_ESTIMATE] = { TLV_TYPE_TLV },
[GSM0808_IE_POSITIONING_DATA] = { TLV_TYPE_TLV },
[GSM0808_IE_LCS_CAUSE] = { TLV_TYPE_TLV },
+ [GSM0808_IE_LCS_CLIENT_TYPE] = { TLV_TYPE_TLV },
[GSM0808_IE_APDU] = { TLV_TYPE_TLV },
[GSM0808_IE_NETWORK_ELEMENT_IDENTITY] = { TLV_TYPE_TLV },
[GSM0808_IE_GPS_ASSISTANCE_DATA] = { TLV_TYPE_TLV },
@@ -1428,6 +1602,7 @@ static const struct tlv_definition bss_att_tlvdef = {
[GSM0800_IE_INTER_SYSTEM_INFO] = { TLV_TYPE_TLV },
[GSM0808_IE_SNA_ACCESS_INFO] = { TLV_TYPE_TLV },
[GSM0808_IE_VSTK_RAND_INFO] = { TLV_TYPE_TLV },
+ [GSM0808_IE_VSTK_INFO] = { TLV_TYPE_TLV },
[GSM0808_IE_PAGING_INFO] = { TLV_TYPE_TV },
[GSM0808_IE_IMEI] = { TLV_TYPE_TLV },
[GSM0808_IE_VELOCITY_ESTIMATE] = { TLV_TYPE_TLV },
@@ -1470,6 +1645,11 @@ static const struct tlv_definition bss_att_tlvdef = {
[GSM0808_IE_CN_TO_MS_TRANSP_INFO] = { TLV_TYPE_TLV },
[GSM0808_IE_SELECTED_PLMN_ID] = { TLV_TYPE_FIXED, 3 },
[GSM0808_IE_LAST_USED_EUTRAN_PLMN_ID] = { TLV_TYPE_FIXED, 3 },
+ [GSM0808_IE_OLD_LAI] = { TLV_TYPE_FIXED, 5 },
+ [GSM0808_IE_ATTACH_INDICATOR] = { TLV_TYPE_T },
+ [GSM0808_IE_SELECTED_OPERATOR] = { TLV_TYPE_FIXED, 3 },
+ [GSM0808_IE_PS_REGISTERED_OPERATOR] = { TLV_TYPE_FIXED, 3 },
+ [GSM0808_IE_CS_REGISTERED_OPERATOR] = { TLV_TYPE_FIXED, 3 },
/* Osmocom extensions */
[GSM0808_IE_OSMO_OSMUX_SUPPORT] = { TLV_TYPE_T },
@@ -1482,6 +1662,32 @@ const struct tlv_definition *gsm0808_att_tlvdef(void)
return &bss_att_tlvdef;
}
+/* As per 3GPP TS 48.008 version 16.0.0 Release 16 § 3.2.2.58 Old BSS to New BSS Information */
+const struct tlv_definition gsm0808_old_bss_to_new_bss_info_att_tlvdef = {
+ .def = {
+ [GSM0808_FE_IE_EXTRA_INFORMATION] = { TLV_TYPE_TLV },
+ [GSM0808_FE_IE_CURRENT_CHANNEL_TYPE_2] = { TLV_TYPE_TLV },
+ [GSM0808_FE_IE_TARGET_CELL_RADIO_INFORMATION] = { TLV_TYPE_TLV },
+ [GSM0808_FE_IE_GPRS_SUSPEND_INFORMATION] = { TLV_TYPE_TLV },
+ [GSM0808_FE_IE_MULTIRATE_CONFIGURATION_INFORMATION] = { TLV_TYPE_TLV },
+ [GSM0808_FE_IE_DUAL_TRANSFER_MODE_INFORMATION] = { TLV_TYPE_TLV },
+ [GSM0808_FE_IE_INTER_RAT_HANDOVER_INFO] = { TLV_TYPE_TLV },
+ [GSM0808_FE_IE_CDMA2000_CAPABILITY_INFORMATION] = { TLV_TYPE_TLV },
+ [GSM0808_FE_IE_DOWNLINK_CELL_LOAD_INFORMATION] = { TLV_TYPE_TLV },
+ [GSM0808_FE_IE_UPLINK_CELL_LOAD_INFORMATION] = { TLV_TYPE_TLV },
+ [GSM0808_FE_IE_CELL_LOAD_INFORMATION_GROUP] = { TLV_TYPE_TLV },
+ [GSM0808_FE_IE_CELL_LOAD_INFORMATION] = { TLV_TYPE_TLV },
+ [GSM0808_FE_IE_PS_INDICATION] = { TLV_TYPE_TLV },
+ [GSM0808_FE_IE_DTM_HANDOVER_COMMAND_INDICATION] = { TLV_TYPE_TLV },
+ [GSM0808_FE_IE_D_RNTI] = { TLV_TYPE_TLV },
+ [GSM0808_FE_IE_IRAT_MEASUREMENT_CONFIGURATION] = { TLV_TYPE_TLV },
+ [GSM0808_FE_IE_SOURCE_CELL_ID] = { TLV_TYPE_TLV },
+ [GSM0808_FE_IE_IRAT_MEASUREMENT_CONFIGURATION_EXTENDED_E_ARFCNS] = { TLV_TYPE_TLV },
+ [GSM0808_FE_IE_VGCS_TALKER_MODE] = { TLV_TYPE_TLV },
+ [GSM0808_FE_IE_LAST_USED_EUTRAN_PLMN_ID] = { TLV_TYPE_FIXED, 3 },
+ },
+};
+
const struct value_string gsm0406_dlci_sapi_names[] = {
{ DLCI_SAPI_RR_MM_CC, "RR/MM/CC" },
{ DLCI_SAPI_SMS, "SMS" },
@@ -1582,7 +1788,7 @@ static const struct value_string gsm0808_msgt_names[] = {
{ 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 }
@@ -1822,4 +2028,101 @@ const struct value_string gsm0808_lcls_status_names[] = {
{ 0, NULL }
};
+/* Convert one S0-S15 bit to its set of AMR modes, for HR AMR and FR AMR.
+ * This is 3GPP TS 28.062 Table 7.11.3.1.3-2: "Preferred Configurations", with some configurations removed as specified
+ * in 3GPP TS 48.008 3.2.2.103:
+ *
+ * FR_AMR is coded ‘0011’.
+ * S11, S13 and S15 are reserved and coded with zeroes.
+ *
+ * HR_AMR is coded ‘0100’.
+ * S6 - S7 and S11 – S15 are reserved and coded with zeroes.
+ *
+ * Meaning: for FR, exclude all Optimisation Mode configurations.
+ * For HR, exclude all that are not supported by HR AMR -- drop all that include at least one of
+ * 10.2 or 12.2.
+ *
+ * Also, for HR, drop 12.2k from S1.
+ *
+ * The first array dimension is 0 for half rate and 1 for full rate.
+ * The second array dimension is the configuration number (0..15) aka Sn.
+ * The values are bitmask combinations of (1 << GSM0808_AMR_MODE_nnnn).
+ *
+ * For example, accumulate all modes that are possible in a given my_s15_s0:
+ *
+ * uint8_t modes = 0;
+ * for (s_bit = 0; s_bit < 15; s_bit++)
+ * if (my_s15_s0 & (1 << s_bit))
+ * modes |= gsm0808_amr_modes_from_cfg[full_rate ? 1 : 0][s_bit];
+ * for (i = 0; i < 8; i++)
+ * if (modes & (1 << i))
+ * printf(" %s", gsm0808_amr_mode_name(i));
+ */
+const uint8_t gsm0808_amr_modes_from_cfg[2][16] = {
+#define B(X) (1 << (GSM0808_AMR_MODE_##X))
+ /* HR */
+ {
+ /* Sn = modes */
+ [0] = B(4_75),
+ [1] = B(4_75) | B(5_90) | B(7_40),
+ [2] = B(5_90),
+ [3] = B(6_70),
+ [4] = B(7_40),
+ [5] = B(7_95),
+ [6] = 0,
+ [7] = 0,
+
+ [8] = B(4_75) | B(5_90),
+ [9] = B(4_75) | B(5_90) | B(6_70),
+ [10] = B(4_75) | B(5_90) | B(6_70) | B(7_40),
+ [11] = 0,
+ [12] = 0,
+ [13] = 0,
+ [14] = 0,
+ [15] = 0,
+ },
+ /* FR */
+ {
+ /* Sn = modes */
+ [0] = B(4_75),
+ [1] = B(4_75) | B(5_90) | B(7_40) | B(12_2),
+ [2] = B(5_90),
+ [3] = B(6_70),
+ [4] = B(7_40),
+ [5] = B(7_95),
+ [6] = B(10_2),
+ [7] = B(12_2),
+
+ [8] = B(4_75) | B(5_90),
+ [9] = B(4_75) | B(5_90) | B(6_70),
+ [10] = B(4_75) | B(5_90) | B(6_70) | B(7_40),
+ [11] = 0,
+ [12] = B(4_75) | B(5_90) | B(6_70) | B(10_2),
+ [13] = 0,
+ [14] = B(4_75) | B(5_90) | B(7_95) | B(12_2),
+ [15] = 0,
+ }
+};
+
+/* AMR mode names from GSM0808_AMR_MODE_*, for use with gsm0808_amr_modes_from_cfg.
+ *
+ * For example:
+ * printf("S9: ");
+ * uint8_t s9_modes = gsm0808_amr_modes_from_cfg[full_rate ? 1 : 0][9];
+ * for (bit = 0; bit < 8; bit++)
+ * if (s9_modes & (1 << bit))
+ * printf("%s,", gsm0808_amr_mode_name(bit));
+ */
+const struct value_string gsm0808_amr_mode_names[] = {
+ { GSM0808_AMR_MODE_4_75, "4.75" },
+ { GSM0808_AMR_MODE_5_15, "5.15" },
+ { GSM0808_AMR_MODE_5_90, "5.90" },
+ { GSM0808_AMR_MODE_6_70, "6.70" },
+ { GSM0808_AMR_MODE_7_40, "7.40" },
+ { GSM0808_AMR_MODE_7_95, "7.95" },
+ { GSM0808_AMR_MODE_10_2, "10.2" },
+ { GSM0808_AMR_MODE_12_2, "12.2" },
+ {}
+};
+
/*! @} */
diff --git a/src/gsm/gsm0808_utils.c b/src/gsm/gsm0808_utils.c
index 6bf771f8..efa9305f 100644
--- a/src/gsm/gsm0808_utils.c
+++ b/src/gsm/gsm0808_utils.c
@@ -78,7 +78,7 @@ uint8_t gsm0808_enc_cause(struct msgb *msg, uint16_t cause)
/*! Encode TS 08.08 AoIP transport address IE
* \param[out] msg Message Buffer to which to append IE
* \param[in] ss Socket Address to be used in IE
- * \returns number of bytes added to \a msg */
+ * \returns number of bytes added to \a msg; 0 if msg is too small */
uint8_t gsm0808_enc_aoip_trasp_addr(struct msgb *msg,
const struct sockaddr_storage *ss)
{
@@ -87,16 +87,25 @@ uint8_t gsm0808_enc_aoip_trasp_addr(struct msgb *msg,
struct sockaddr_in6 *sin6;
uint16_t port = 0;
uint8_t *ptr;
- uint8_t *old_tail;
- uint8_t *tlv_len;
+ const uint8_t len_tl = 2;
+ uint8_t len_v = sizeof(port);
- OSMO_ASSERT(msg);
- OSMO_ASSERT(ss);
OSMO_ASSERT(ss->ss_family == AF_INET || ss->ss_family == AF_INET6);
+ switch (ss->ss_family) {
+ case AF_INET:
+ len_v += IP_V4_ADDR_LEN;
+ break;
+ case AF_INET6:
+ len_v += IP_V6_ADDR_LEN;
+ break;
+ }
+
+ if (msgb_tailroom(msg) < len_tl + len_v)
+ return 0;
+
msgb_put_u8(msg, GSM0808_IE_AOIP_TRASP_ADDR);
- tlv_len = msgb_put(msg,1);
- old_tail = msg->tail;
+ msgb_put_u8(msg, len_v);
switch (ss->ss_family) {
case AF_INET:
@@ -114,9 +123,7 @@ uint8_t gsm0808_enc_aoip_trasp_addr(struct msgb *msg,
}
msgb_put_u16(msg, port);
-
- *tlv_len = (uint8_t) (msg->tail - old_tail);
- return *tlv_len + 2;
+ return len_tl + len_v;
}
/*! Decode TS 08.08 AoIP transport address IE
@@ -132,7 +139,6 @@ int gsm0808_dec_aoip_trasp_addr(struct sockaddr_storage *ss,
struct sockaddr_in6 sin6;
const uint8_t *old_elem = elem;
- OSMO_ASSERT(ss);
if (!elem)
return -EINVAL;
if (len == 0)
@@ -180,7 +186,6 @@ int gsm0808_dec_aoip_trasp_addr(struct sockaddr_storage *ss,
* \returns number of bytes parsed */
int gsm0808_dec_osmux_cid(uint8_t *cid, const uint8_t *elem, uint8_t len)
{
- OSMO_ASSERT(cid);
if (!elem)
return -EINVAL;
if (len != 1)
@@ -204,21 +209,21 @@ static void decode_lai(const uint8_t *data, struct osmo_location_area_id *decode
gsm48_decode_lai2(&lai, decoded);
}
-/* Helper function for gsm0808_enc_speech_codec()
- * and gsm0808_enc_speech_codec_list() */
-static uint8_t enc_speech_codec(struct msgb *msg,
- const struct gsm0808_speech_codec *sc)
+/* Helper function for gsm0808_enc_speech_codec[_list]().
+ * Returns number of bytes appended; negative on error. */
+static int enc_speech_codec(struct msgb *msg,
+ const struct gsm0808_speech_codec *sc)
{
/* See also 3GPP TS 48.008 3.2.2.103 Speech Codec List */
uint8_t header = 0;
uint8_t *old_tail;
- bool type_extended = false;
+ bool type_extended;
/* Note: Extended codec types are codec types that require 8 instead
* of 4 bit to fully specify the selected codec. In the following,
* we check if we work with an extended type or not. We also check
* if the codec type is valid at all. */
- switch(sc->type) {
+ switch (sc->type) {
case GSM0808_SCT_FR1:
case GSM0808_SCT_FR2:
case GSM0808_SCT_FR3:
@@ -235,8 +240,7 @@ static uint8_t enc_speech_codec(struct msgb *msg,
break;
default:
/* Invalid codec type specified */
- OSMO_ASSERT(false);
- break;
+ return -EINVAL;
}
old_tail = msg->tail;
@@ -255,7 +259,6 @@ static uint8_t enc_speech_codec(struct msgb *msg,
msgb_put_u8(msg, header);
msgb_put_u8(msg, sc->type);
} else {
- OSMO_ASSERT(sc->type < 0x0f);
header |= sc->type;
msgb_put_u8(msg, header);
}
@@ -273,11 +276,13 @@ static uint8_t enc_speech_codec(struct msgb *msg,
case GSM0808_SCT_FR5:
case GSM0808_SCT_HR4:
case GSM0808_SCT_CSD:
- OSMO_ASSERT((sc->cfg & 0xff00) == 0);
+ if (sc->cfg >> 8)
+ return -EINVAL;
msgb_put_u8(msg, (uint8_t) sc->cfg & 0xff);
break;
default:
- OSMO_ASSERT(sc->cfg == 0);
+ if (sc->cfg != 0)
+ return -EINVAL;
break;
}
@@ -287,27 +292,38 @@ static uint8_t enc_speech_codec(struct msgb *msg,
/*! Encode TS 08.08 Speech Codec IE
* \param[out] msg Message Buffer to which IE will be appended
* \param[in] sc Speech Codec to be encoded into IE
- * \returns number of bytes appended to \a msg */
-uint8_t gsm0808_enc_speech_codec(struct msgb *msg,
- const struct gsm0808_speech_codec *sc)
+ * \returns number of bytes appended to \a msg; negative on error */
+int gsm0808_enc_speech_codec2(struct msgb *msg,
+ const struct gsm0808_speech_codec *sc)
{
/*! See also 3GPP TS 48.008 3.2.2.103 Speech Codec List */
- uint8_t *old_tail;
uint8_t *tlv_len;
-
- OSMO_ASSERT(msg);
- OSMO_ASSERT(sc);
+ int rc;
msgb_put_u8(msg, GSM0808_IE_SPEECH_CODEC);
tlv_len = msgb_put(msg, 1);
- old_tail = msg->tail;
- enc_speech_codec(msg, sc);
+ rc = enc_speech_codec(msg, sc);
+ if (rc < 0)
+ return rc;
- *tlv_len = (uint8_t) (msg->tail - old_tail);
+ *tlv_len = rc;
return *tlv_len + 2;
}
+/*! Deprecated: gsm0808_enc_speech_codec2() wrapper for backwards compatibility.
+ * Mimics the old behavior: crash on missing and/or invalid input. */
+uint8_t gsm0808_enc_speech_codec(struct msgb *msg,
+ const struct gsm0808_speech_codec *sc)
+{
+ int rc;
+
+ rc = gsm0808_enc_speech_codec2(msg, sc);
+ OSMO_ASSERT(rc > 0);
+
+ return rc;
+}
+
/*! Decode TS 08.08 Speech Codec IE
* \param[out] sc Caller-allocated memory for Speech Codec
* \param[in] elem IE value to be decoded
@@ -320,7 +336,6 @@ int gsm0808_dec_speech_codec(struct gsm0808_speech_codec *sc,
uint8_t header;
const uint8_t *old_elem = elem;
- OSMO_ASSERT(sc);
if (!elem)
return -EINVAL;
if (len == 0)
@@ -392,35 +407,46 @@ int gsm0808_dec_speech_codec(struct gsm0808_speech_codec *sc,
/*! Encode TS 08.08 Speech Codec list
* \param[out] msg Message Buffer to which IE is to be appended
* \param[in] scl Speech Codec List to be encoded into IE
- * \returns number of bytes added to \a msg */
-uint8_t gsm0808_enc_speech_codec_list(struct msgb *msg,
- const struct gsm0808_speech_codec_list *scl)
+ * \returns number of bytes added to \a msg; negative on error */
+int gsm0808_enc_speech_codec_list2(struct msgb *msg,
+ const struct gsm0808_speech_codec_list *scl)
{
/*! See also 3GPP TS 48.008 3.2.2.103 Speech Codec List */
uint8_t *old_tail;
uint8_t *tlv_len;
unsigned int i;
- uint8_t rc;
unsigned int bytes_used = 0;
- OSMO_ASSERT(msg);
- OSMO_ASSERT(scl);
-
msgb_put_u8(msg, GSM0808_IE_SPEECH_CODEC_LIST);
tlv_len = msgb_put(msg, 1);
old_tail = msg->tail;
for (i = 0; i < scl->len; i++) {
- rc = enc_speech_codec(msg, &scl->codec[i]);
- OSMO_ASSERT(rc >= 1);
+ int rc = enc_speech_codec(msg, &scl->codec[i]);
+ if (rc < 1)
+ return rc;
bytes_used += rc;
- OSMO_ASSERT(bytes_used <= 255);
+ if (bytes_used > 0xff)
+ return -EOVERFLOW;
}
*tlv_len = (uint8_t) (msg->tail - old_tail);
return *tlv_len + 2;
}
+/*! Deprecated: gsm0808_enc_speech_codec_list2() wrapper for backwards compatibility.
+ * Mimics the old behavior: crash on missing and/or invalid input. */
+uint8_t gsm0808_enc_speech_codec_list(struct msgb *msg,
+ const struct gsm0808_speech_codec_list *scl)
+{
+ int rc;
+
+ rc = gsm0808_enc_speech_codec_list2(msg, scl);
+ OSMO_ASSERT(rc > 0);
+
+ return rc;
+}
+
/*! Decode TS 08.08 Speech Codec list IE
* \param[out] scl Caller-provided memory to store codec list
* \param[in] elem IE value to be decoded
@@ -435,7 +461,6 @@ int gsm0808_dec_speech_codec_list(struct gsm0808_speech_codec_list *scl,
int rc;
uint8_t decoded = 0;
- OSMO_ASSERT(scl);
if (!elem)
return -EINVAL;
@@ -472,16 +497,8 @@ uint8_t gsm0808_enc_channel_type(struct msgb *msg,
uint8_t *old_tail;
uint8_t *tlv_len;
- OSMO_ASSERT(msg);
- OSMO_ASSERT(ct);
OSMO_ASSERT(ct->perm_spch_len <= CHANNEL_TYPE_ELEMENT_MAXLEN - 2);
- /* FIXME: Implement encoding support for Data
- * and Speech + CTM Text Telephony */
- if ((ct->ch_indctr & 0x0f) != GSM0808_CHAN_SPEECH
- && (ct->ch_indctr & 0x0f) != GSM0808_CHAN_SIGN)
- OSMO_ASSERT(false);
-
msgb_put_u8(msg, GSM0808_IE_CHANNEL_TYPE);
tlv_len = msgb_put(msg, 1);
old_tail = msg->tail;
@@ -489,12 +506,45 @@ uint8_t gsm0808_enc_channel_type(struct msgb *msg,
msgb_put_u8(msg, ct->ch_indctr & 0x0f);
msgb_put_u8(msg, ct->ch_rate_type);
- for (i = 0; i < ct->perm_spch_len; i++) {
- byte = ct->perm_spch[i];
+ switch (ct->ch_indctr) {
+ case GSM0808_CHAN_DATA:
+ byte = ct->data_rate;
- if (i < ct->perm_spch_len - 1)
- byte |= 0x80;
+ 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);
+
+ /* Set asymmetry indication, rest is spare */
+ byte = ct->data_asym_pref << 5;
+ }
+ }
msgb_put_u8(msg, byte);
+ break;
+ case GSM0808_CHAN_SPEECH:
+ case GSM0808_CHAN_SPEECH_CTM_TEXT_TELEPHONY:
+ for (i = 0; i < ct->perm_spch_len; i++) {
+ byte = ct->perm_spch[i];
+
+ if (i < ct->perm_spch_len - 1)
+ byte |= 0x80;
+ msgb_put_u8(msg, byte);
+ }
+ break;
+ case GSM0808_CHAN_SIGN:
+ /* Octet 5 is spare */
+ msgb_put_u8(msg, 0);
+ break;
+ default:
+ OSMO_ASSERT(false);
}
*tlv_len = (uint8_t) (msg->tail - old_tail);
@@ -514,7 +564,6 @@ int gsm0808_dec_channel_type(struct gsm0808_channel_type *ct,
uint8_t byte;
const uint8_t *old_elem = elem;
- OSMO_ASSERT(ct);
if (!elem)
return -EINVAL;
if (len < 3 || len > 11)
@@ -524,17 +573,60 @@ int gsm0808_dec_channel_type(struct gsm0808_channel_type *ct,
ct->ch_indctr = (*elem) & 0x0f;
elem++;
- ct->ch_rate_type = (*elem) & 0x0f;
+ ct->ch_rate_type = *elem;
elem++;
- for (i = 0; i < ARRAY_SIZE(ct->perm_spch); i++) {
+ switch (ct->ch_indctr) {
+ case GSM0808_CHAN_DATA:
byte = *elem;
elem++;
- ct->perm_spch[i] = byte & 0x7f;
- if ((byte & 0x80) == 0x00)
- break;
+ ct->data_transparent = !(byte & 0x40); /* T/NT */
+ ct->data_rate = byte & 0x3f;
+
+ /* Optional extension for non-transparent service */
+ if (byte & 0x80) {
+ if (ct->data_transparent)
+ return -EINVAL;
+ if (elem - old_elem >= len)
+ return -EOVERFLOW;
+ byte = *elem;
+ elem++;
+
+ ct->data_rate_allowed_is_set = true;
+ ct->data_rate_allowed = byte & 0x7f;
+
+ /* Optional extension */
+ if (byte & 0x80) {
+ if (elem - old_elem >= len)
+ return -EOVERFLOW;
+ byte = *elem;
+ elem++;
+
+ ct->data_asym_pref_is_set = true;
+ ct->data_asym_pref = byte & 0x60 >> 5;
+ }
+ }
+ break;
+ case GSM0808_CHAN_SPEECH:
+ case GSM0808_CHAN_SPEECH_CTM_TEXT_TELEPHONY:
+ for (i = 0; i < ARRAY_SIZE(ct->perm_spch); i++) {
+ if (elem - old_elem >= len)
+ return -EOVERFLOW;
+
+ byte = *elem;
+ elem++;
+ ct->perm_spch[i] = byte & 0x7f;
+ if ((byte & 0x80) == 0x00)
+ break;
+ }
+ ct->perm_spch_len = i + 1;
+ break;
+ case GSM0808_CHAN_SIGN:
+ /* Octet 5 is spare */
+ break;
+ default:
+ return -ENOTSUP;
}
- ct->perm_spch_len = i + 1;
return (int)(elem - old_elem);
}
@@ -703,8 +795,6 @@ uint8_t gsm0808_enc_encrypt_info(struct msgb *msg,
uint8_t *old_tail;
uint8_t *tlv_len;
- OSMO_ASSERT(msg);
- OSMO_ASSERT(ei);
OSMO_ASSERT(ei->key_len <= ARRAY_SIZE(ei->key));
OSMO_ASSERT(ei->perm_algo_len <= ENCRY_INFO_PERM_ALGO_MAXLEN);
@@ -721,6 +811,8 @@ uint8_t gsm0808_enc_encrypt_info(struct msgb *msg,
}
msgb_put_u8(msg, perm_algo);
+ /* FIXME: 48.008 3.2.2.10 Encryption Information says:
+ * "When present, the key shall be 8 octets long." */
ptr = msgb_put(msg, ei->key_len);
memcpy(ptr, ei->key, ei->key_len);
@@ -741,7 +833,6 @@ int gsm0808_dec_encrypt_info(struct gsm0808_encrypt_info *ei,
unsigned int perm_algo_len = 0;
const uint8_t *old_elem = elem;
- OSMO_ASSERT(ei);
if (!elem)
return -EINVAL;
if (len == 0)
@@ -760,6 +851,8 @@ int gsm0808_dec_encrypt_info(struct gsm0808_encrypt_info *ei,
}
ei->perm_algo_len = perm_algo_len;
+ /* FIXME: 48.008 3.2.2.10 Encryption Information says:
+ * "When present, the key shall be 8 octets long." */
ei->key_len = len - 1;
memcpy(ei->key, elem, ei->key_len);
elem+=ei->key_len;
@@ -767,6 +860,80 @@ int gsm0808_dec_encrypt_info(struct gsm0808_encrypt_info *ei,
return (int)(elem - old_elem);
}
+/*! Encode TS 48.008 Kc128 IE.
+ * \param[out] msg Message Buffer to which IE is to be appended.
+ * \param[in] kc128 Pointer to 16 bytes of Kc128 key data.
+ * \returns number of bytes appended to msg */
+int gsm0808_enc_kc128(struct msgb *msg, const uint8_t *kc128)
+{
+ uint8_t *start = msg->tail;
+ msgb_tv_fixed_put(msg, GSM0808_IE_KC_128, 16, kc128);
+ return msg->tail - start;
+}
+
+/*! Decode TS 48.008 Kc128 IE.
+ * \param[out] kc128 Target buffer for received Kc128 key, 16 bytes long.
+ * \param[in] elem IE value to be decoded (without IE discriminator).
+ * \param[in] len Length of elem in bytes.
+ * \returns number of bytes parsed; negative on error */
+int gsm0808_dec_kc128(uint8_t *kc128, const uint8_t *elem, uint8_t len)
+{
+ if (len != 16)
+ return -EINVAL;
+ memcpy(kc128, elem, 16);
+ return len;
+}
+
+/* Store individual Cell Identifier information in a CGI, without clearing the remaining ones.
+ * This is useful to supplement one CGI with information from more than one Cell Identifier,
+ * which in turn is useful to match Cell Identifiers of differing kinds to each other.
+ * Before first invocation, clear the *dst struct externally, this function does only write those members
+ * that are present in parameter u.
+ */
+static void cell_id_to_cgi(struct osmo_cell_global_id *dst,
+ enum CELL_IDENT discr, const union gsm0808_cell_id_u *u)
+{
+ switch (discr) {
+ case CELL_IDENT_WHOLE_GLOBAL:
+ *dst = u->global;
+ return;
+
+ case CELL_IDENT_WHOLE_GLOBAL_PS:
+ dst->lai = u->global_ps.rai.lac;
+ dst->cell_identity = u->global_ps.cell_identity;
+ return;
+
+ case CELL_IDENT_LAC_AND_CI:
+ dst->lai.lac = u->lac_and_ci.lac;
+ dst->cell_identity = u->lac_and_ci.ci;
+ return;
+
+ case CELL_IDENT_CI:
+ dst->cell_identity = u->ci;
+ return;
+
+ case CELL_IDENT_LAI_AND_LAC:
+ dst->lai = u->lai_and_lac;
+ return;
+
+ case CELL_IDENT_LAC:
+ dst->lai.lac = u->lac;
+ return;
+
+ case CELL_IDENT_SAI:
+ dst->lai = u->sai.lai;
+ return;
+
+ case CELL_IDENT_NO_CELL:
+ case CELL_IDENT_BSS:
+ case CELL_IDENT_UTRAN_PLMN_LAC_RNC:
+ case CELL_IDENT_UTRAN_RNC:
+ case CELL_IDENT_UTRAN_LAC_RNC:
+ /* No values to set. */
+ return;
+ }
+}
+
/* Return the size of the value part of a cell identifier of given type */
int gsm0808_cell_id_size(enum CELL_IDENT discr)
{
@@ -784,6 +951,10 @@ int gsm0808_cell_id_size(enum CELL_IDENT discr)
case CELL_IDENT_BSS:
case CELL_IDENT_NO_CELL:
return 0;
+ case CELL_IDENT_SAI:
+ return 7;
+ case CELL_IDENT_WHOLE_GLOBAL_PS:
+ return 8;
default:
return -EINVAL;
}
@@ -828,6 +999,20 @@ int gsm0808_decode_cell_id_u(union gsm0808_cell_id_u *out, enum CELL_IDENT discr
case CELL_IDENT_NO_CELL:
/* Does not have any list items */
break;
+ case CELL_IDENT_SAI:
+ if (len < 7)
+ return -EINVAL;
+ decode_lai(buf, &out->sai.lai);
+ out->sai.sac = osmo_load16be(buf + sizeof(struct gsm48_loc_area_id));
+ break;
+ case CELL_IDENT_WHOLE_GLOBAL_PS:
+ /* 3GPP TS 48.018 sec 11.3.9 "Cell Identifier" */
+ if (len < 8)
+ return -EINVAL;
+ decode_lai(buf, (struct osmo_location_area_id *)&out->global_ps.rai); /* rai contains lai + non-decoded rac */
+ out->global_ps.rai.rac = *(buf + sizeof(struct gsm48_loc_area_id));
+ out->global_ps.cell_identity = osmo_load16be(buf + sizeof(struct gsm48_loc_area_id) + 1);
+ break;
default:
/* Remaining cell identification types are not implemented. */
return -EINVAL;
@@ -869,6 +1054,26 @@ void gsm0808_msgb_put_cell_id_u(struct msgb *msg, enum CELL_IDENT id_discr, cons
case CELL_IDENT_NO_CELL:
/* Does not have any list items */
break;
+
+ case CELL_IDENT_SAI: {
+ const struct osmo_service_area_id *id = &u->sai;
+ struct gsm48_loc_area_id lai;
+ gsm48_generate_lai2(&lai, &id->lai);
+ memcpy(msgb_put(msg, sizeof(lai)), &lai, sizeof(lai));
+ msgb_put_u16(msg, id->sac);
+ break;
+ }
+
+ case CELL_IDENT_WHOLE_GLOBAL_PS: {
+ /* 3GPP TS 48.018 sec 11.3.9 "Cell Identifier" */
+ const struct osmo_cell_global_id_ps *id = &u->global_ps;
+ struct gsm48_loc_area_id lai;
+ gsm48_generate_lai2(&lai, &id->rai.lac);
+ memcpy(msgb_put(msg, sizeof(lai)), &lai, sizeof(lai));
+ memcpy(msgb_put(msg, 1), &id->rai.rac, 1);
+ msgb_put_u16(msg, id->cell_identity);
+ break;
+ }
default:
/* Support for other identifier list types is not implemented. */
OSMO_ASSERT(false);
@@ -885,19 +1090,31 @@ uint8_t gsm0808_enc_cell_id_list2(struct msgb *msg,
uint8_t *old_tail;
uint8_t *tlv_len;
unsigned int i;
-
- OSMO_ASSERT(msg);
- OSMO_ASSERT(cil);
+ uint8_t id_discr;
msgb_put_u8(msg, GSM0808_IE_CELL_IDENTIFIER_LIST);
tlv_len = msgb_put(msg, 1);
old_tail = msg->tail;
- msgb_put_u8(msg, cil->id_discr & 0x0f);
+ /* CGI-PS is an osmocom-specific type. In here we don't care about the
+ * PS part, only the CS one. */
+ if (cil->id_discr == CELL_IDENT_WHOLE_GLOBAL_PS)
+ id_discr = CELL_IDENT_WHOLE_GLOBAL;
+ else
+ id_discr = cil->id_discr & 0x0f;
+
+ msgb_put_u8(msg, id_discr);
OSMO_ASSERT(cil->id_list_len <= GSM0808_CELL_ID_LIST2_MAXLEN);
- for (i = 0; i < cil->id_list_len; i++)
- gsm0808_msgb_put_cell_id_u(msg, cil->id_discr, &cil->id_list[i]);
+ for (i = 0; i < cil->id_list_len; i++) {
+ if (cil->id_discr == CELL_IDENT_WHOLE_GLOBAL_PS) {
+ union gsm0808_cell_id_u u;
+ cell_id_to_cgi(&u.global, cil->id_discr, &cil->id_list[i]);
+ gsm0808_msgb_put_cell_id_u(msg, CELL_IDENT_WHOLE_GLOBAL, &u);
+ } else {
+ gsm0808_msgb_put_cell_id_u(msg, cil->id_discr, &cil->id_list[i]);
+ }
+ }
*tlv_len = (uint8_t) (msg->tail - old_tail);
return *tlv_len + 2;
@@ -916,9 +1133,6 @@ uint8_t gsm0808_enc_cell_id_list(struct msgb *msg,
uint8_t *tlv_len;
unsigned int i;
- OSMO_ASSERT(msg);
- OSMO_ASSERT(cil);
-
msgb_put_u8(msg, GSM0808_IE_CELL_IDENTIFIER_LIST);
tlv_len = msgb_put(msg, 1);
old_tail = msg->tail;
@@ -1057,6 +1271,31 @@ static int parse_cell_id_lac_list(struct gsm0808_cell_id_list2 *cil, const uint8
return i;
}
+static int parse_cell_id_sai_list(struct gsm0808_cell_id_list2 *cil, const uint8_t *data, size_t remain, size_t *consumed)
+{
+ struct osmo_service_area_id *id;
+ uint16_t *sac_be;
+ size_t lai_offset;
+ int i = 0;
+ const size_t elemlen = sizeof(struct gsm48_loc_area_id) + sizeof(*sac_be);
+
+ *consumed = 0;
+ while (remain >= elemlen) {
+ if (i >= GSM0808_CELL_ID_LIST2_MAXLEN)
+ return -ENOSPC;
+ id = &cil->id_list[i].sai;
+ lai_offset = i * elemlen;
+ decode_lai(&data[lai_offset], &id->lai);
+ sac_be = (uint16_t *)(&data[lai_offset + sizeof(struct gsm48_loc_area_id)]);
+ id->sac = osmo_load16be(sac_be);
+ *consumed += elemlen;
+ remain -= elemlen;
+ i++;
+ }
+
+ return i;
+}
+
/*! Decode Cell Identifier List IE
* \param[out] cil Caller-provided memory to store Cell ID list
* \param[in] elem IE value to be decoded
@@ -1069,7 +1308,6 @@ int gsm0808_dec_cell_id_list2(struct gsm0808_cell_id_list2 *cil,
size_t bytes_elem = 0;
int list_len = 0;
- OSMO_ASSERT(cil);
if (!elem)
return -EINVAL;
if (len == 0)
@@ -1101,6 +1339,12 @@ int gsm0808_dec_cell_id_list2(struct gsm0808_cell_id_list2 *cil,
case CELL_IDENT_NO_CELL:
/* Does not have any list items */
break;
+ case CELL_IDENT_SAI:
+ list_len = parse_cell_id_sai_list(cil, elem, len, &bytes_elem);
+ break;
+ case CELL_IDENT_UTRAN_PLMN_LAC_RNC:
+ case CELL_IDENT_UTRAN_RNC:
+ case CELL_IDENT_UTRAN_LAC_RNC:
default:
/* Remaining cell identification types are not implemented. */
return -EINVAL;
@@ -1131,7 +1375,6 @@ int gsm0808_dec_cell_id_list(struct gsm0808_cell_id_list *cil,
const uint8_t *old_elem = elem;
unsigned int item_count = 0;
- OSMO_ASSERT(cil);
if (!elem)
return -EINVAL;
if (len == 0)
@@ -1289,9 +1532,6 @@ uint8_t gsm0808_enc_cell_id(struct msgb *msg, const struct gsm0808_cell_id *ci)
.id_list_len = 1,
};
- OSMO_ASSERT(msg);
- OSMO_ASSERT(ci);
-
ie_tag = msg->tail;
rc = gsm0808_enc_cell_id_list2(msg, &cil);
@@ -1498,7 +1738,7 @@ int gsm48_mr_cfg_from_gsm0808_sc_cfg(struct gsm48_multi_rate_conf *cfg,
/* Rate 5,15k can never be selected (see table) */
cfg->m5_15 = 0;
- if (s15_s0 & GSM0808_SC_CFG_AMR_4_75_5_90_7_40_12_20 & 0xff) {
+ if (s15_s0 & GSM0808_SC_CFG_AMR_4_75_5_90_7_40_12_20) {
/* Table Table 7.11.3.1.3-2 lists one mode that selects 4
* rates at once (Config-NB-Code = 1). The rates selected
* are known to be compatible between GERAN and UTRAN, since
@@ -1592,6 +1832,10 @@ int gsm0808_cell_id_u_name(char *buf, size_t buflen,
return snprintf(buf, buflen, "%s", osmo_lai_name(&u->lai_and_lac));
case CELL_IDENT_WHOLE_GLOBAL:
return snprintf(buf, buflen, "%s", osmo_cgi_name(&u->global));
+ case CELL_IDENT_WHOLE_GLOBAL_PS:
+ return snprintf(buf, buflen, "%s", osmo_cgi_ps_name(&u->global_ps));
+ case CELL_IDENT_SAI:
+ return snprintf(buf, buflen, "%s", osmo_sai_name(&u->sai));
default:
/* For CELL_IDENT_BSS and CELL_IDENT_NO_CELL, just print the discriminator.
* Same for kinds we have no string representation of yet. */
@@ -1599,47 +1843,6 @@ int gsm0808_cell_id_u_name(char *buf, size_t buflen,
}
}
-/* Store individual Cell Identifier information in a CGI, without clearing the remaining ones.
- * This is useful to supplement one CGI with information from more than one Cell Identifier,
- * which in turn is useful to match Cell Identifiers of differing kinds to each other.
- * Before first invocation, clear the *dst struct externally, this function does only write those members
- * that are present in parameter u.
- */
-static void cell_id_to_cgi(struct osmo_cell_global_id *dst,
- enum CELL_IDENT discr, const union gsm0808_cell_id_u *u)
-{
- switch (discr) {
- case CELL_IDENT_WHOLE_GLOBAL:
- *dst = u->global;
- return;
-
- case CELL_IDENT_LAC_AND_CI:
- dst->lai.lac = u->lac_and_ci.lac;
- dst->cell_identity = u->lac_and_ci.ci;
- return;
-
- case CELL_IDENT_CI:
- dst->cell_identity = u->ci;
- return;
-
- case CELL_IDENT_LAI_AND_LAC:
- dst->lai = u->lai_and_lac;
- return;
-
- case CELL_IDENT_LAC:
- dst->lai.lac = u->lac;
- return;
-
- case CELL_IDENT_NO_CELL:
- case CELL_IDENT_BSS:
- case CELL_IDENT_UTRAN_PLMN_LAC_RNC:
- case CELL_IDENT_UTRAN_RNC:
- case CELL_IDENT_UTRAN_LAC_RNC:
- /* No values to set. */
- return;
- }
-}
-
/*! Return true if the common information between the two Cell Identifiers match.
* For example, if a LAC+CI is compared to LAC, return true if the LAC are the same.
* Note that CELL_IDENT_NO_CELL will always return false.
@@ -1762,6 +1965,14 @@ void gsm0808_cell_id_from_cgi(struct gsm0808_cell_id *cid, enum CELL_IDENT id_di
cid->id.global = *cgi;
return;
+ case CELL_IDENT_WHOLE_GLOBAL_PS:
+ cid->id.global_ps = (struct osmo_cell_global_id_ps){
+ .rai = {
+ .lac = cgi->lai,
+ },
+ .cell_identity = cgi->cell_identity,
+ };
+ return;
case CELL_IDENT_LAC_AND_CI:
cid->id.lac_and_ci = (struct osmo_lac_and_ci_id){
.lac = cgi->lai.lac,
@@ -1781,6 +1992,12 @@ void gsm0808_cell_id_from_cgi(struct gsm0808_cell_id *cid, enum CELL_IDENT id_di
cid->id.lac = cgi->lai.lac;
return;
+ case CELL_IDENT_SAI:
+ cid->id.sai = (struct osmo_service_area_id){
+ .lai = cgi->lai,
+ };
+ return;
+
case CELL_IDENT_NO_CELL:
case CELL_IDENT_BSS:
case CELL_IDENT_UTRAN_PLMN_LAC_RNC:
@@ -1805,6 +2022,11 @@ int gsm0808_cell_id_to_cgi(struct osmo_cell_global_id *cgi, const struct gsm0808
*cgi = cid->id.global;
return OSMO_CGI_PART_PLMN | OSMO_CGI_PART_LAC | OSMO_CGI_PART_CI;
+ case CELL_IDENT_WHOLE_GLOBAL_PS:
+ cgi->lai = cid->id.global_ps.rai.lac;
+ cgi->cell_identity = cid->id.global_ps.cell_identity;
+ return OSMO_CGI_PART_PLMN | OSMO_CGI_PART_LAC | OSMO_CGI_PART_CI;
+
case CELL_IDENT_LAC_AND_CI:
cgi->lai.lac = cid->id.lac_and_ci.lac;
cgi->cell_identity = cid->id.lac_and_ci.ci;
@@ -1822,6 +2044,10 @@ int gsm0808_cell_id_to_cgi(struct osmo_cell_global_id *cgi, const struct gsm0808
cgi->lai.lac = cid->id.lac;
return OSMO_CGI_PART_LAC;
+ case CELL_IDENT_SAI:
+ cgi->lai = cid->id.sai.lai;
+ return OSMO_CGI_PART_PLMN | OSMO_CGI_PART_LAC;
+
case CELL_IDENT_NO_CELL:
case CELL_IDENT_BSS:
case CELL_IDENT_UTRAN_PLMN_LAC_RNC:
@@ -1844,6 +2070,8 @@ const struct value_string gsm0808_cell_id_discr_names[] = {
{ CELL_IDENT_UTRAN_PLMN_LAC_RNC, "UTRAN-PLMN-LAC-RNC" },
{ CELL_IDENT_UTRAN_RNC, "UTRAN-RNC" },
{ CELL_IDENT_UTRAN_LAC_RNC, "UTRAN-LAC-RNC" },
+ { CELL_IDENT_SAI, "SAI" },
+ { CELL_IDENT_WHOLE_GLOBAL_PS, "CGI-PS"},
{ 0, NULL }
};
diff --git a/src/gsm/gsm23003.c b/src/gsm/gsm23003.c
index e20afcbc..1eed41fe 100644
--- a/src/gsm/gsm23003.c
+++ b/src/gsm/gsm23003.c
@@ -17,10 +17,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <ctype.h>
@@ -247,6 +243,43 @@ char *osmo_lai_name_c(const void *ctx, const struct osmo_location_area_id *lai)
return osmo_lai_name_buf(buf, 32, lai);
}
+/*! Return MCC-MNC-LAC-RAC as string, in caller-provided output buffer.
+ * \param[out] buf caller-allocated output buffer
+ * \param[in] buf_len size of buf in bytes
+ * \param[in] rai RAI to encode, the rac member is ignored.
+ * \returns buf
+ */
+char *osmo_rai_name2_buf(char *buf, size_t buf_len, const struct osmo_routing_area_id *rai)
+{
+ char plmn[16];
+ snprintf(buf, buf_len, "%s-%u-%u", osmo_plmn_name_buf(plmn, sizeof(plmn),
+ &rai->lac.plmn), rai->lac.lac, rai->rac);
+ return buf;
+}
+
+/*! Return MCC-MNC-LAC-RAC as string, in a static buffer.
+ * \param[in] rai RAI to encode, the rac member is ignored.
+ * \returns Static string buffer.
+ */
+const char *osmo_rai_name2(const struct osmo_routing_area_id *rai)
+{
+ static __thread char buf[32];
+ return osmo_rai_name2_buf(buf, sizeof(buf), rai);
+}
+
+/*! Return MCC-MNC-LAC-RAC as string, in a talloc-allocated output buffer.
+ * \param[in] ctx talloc context from which to allocate output buffer
+ * \param[in] rai RAI to encode, the rac member is ignored.
+ * \returns string representation of lai in dynamically allocated buffer.
+ */
+char *osmo_rai_name2_c(const void *ctx, const struct osmo_routing_area_id *rai)
+{
+ char *buf = talloc_size(ctx, 32);
+ if (!buf)
+ return NULL;
+ return osmo_rai_name2_buf(buf, 32, rai);
+}
+
/*! Return MCC-MNC-LAC-CI as string, in caller-provided output buffer.
* \param[out] buf caller-allocated output buffer
* \param[in] buf_len size of buf in bytes
@@ -291,6 +324,95 @@ char *osmo_cgi_name_c(const void *ctx, const struct osmo_cell_global_id *cgi)
return osmo_cgi_name_buf(buf, 32, cgi);
}
+/*! Return MCC-MNC-LAC-RAC-CI as string, in caller-provided output buffer.
+ * \param[out] buf caller-allocated output buffer
+ * \param[in] buf_len size of buf in bytes
+ * \param[in] cgi_ps CGI-PS to encode.
+ * \returns buf
+ */
+char *osmo_cgi_ps_name_buf(char *buf, size_t buf_len, const struct osmo_cell_global_id_ps *cgi_ps)
+{
+ snprintf(buf, buf_len, "%s-%u", osmo_rai_name2(&cgi_ps->rai), cgi_ps->cell_identity);
+ return buf;
+}
+
+/*! Return MCC-MNC-LAC-RAC-CI as string, in a static buffer.
+ * \param[in] cgi_ps CGI-PS to encode.
+ * \returns Static string buffer.
+ */
+const char *osmo_cgi_ps_name(const struct osmo_cell_global_id_ps *cgi_ps)
+{
+ static __thread char buf[32];
+ return osmo_cgi_ps_name_buf(buf, sizeof(buf), cgi_ps);
+}
+
+/*! Same as osmo_cgi_ps_name(), but uses a different static buffer.
+ * Useful for printing two distinct CGI-PSs in the same printf format.
+ * \param[in] cgi CGI-PS to encode.
+ * \returns Static string buffer.
+ */
+const char *osmo_cgi_ps_name2(const struct osmo_cell_global_id_ps *cgi_ps)
+{
+ static __thread char buf[32];
+ return osmo_cgi_ps_name_buf(buf, sizeof(buf), cgi_ps);
+}
+
+/*! Return MCC-MNC-LAC-RAC-CI as string, in a talloc-allocated output buffer.
+ * \param[in] ctx talloc context from which to allocate output buffer
+ * \param[in] cgi_ps CGI-PS to encode.
+ * \returns string representation of CGI in dynamically-allocated buffer.
+ */
+char *osmo_cgi_ps_name_c(const void *ctx, const struct osmo_cell_global_id_ps *cgi_ps)
+{
+ char *buf = talloc_size(ctx, 32);
+ return osmo_cgi_ps_name_buf(buf, 32, cgi_ps);
+}
+
+/*! Return MCC-MNC-LAC-SAC as string, in caller-provided output buffer.
+ * \param[out] buf caller-allocated output buffer
+ * \param[in] buf_len size of buf in bytes
+ * \param[in] sai SAI to encode.
+ * \returns buf
+ */
+char *osmo_sai_name_buf(char *buf, size_t buf_len, const struct osmo_service_area_id *sai)
+{
+ snprintf(buf, buf_len, "%s-%u", osmo_lai_name(&sai->lai), sai->sac);
+ return buf;
+}
+
+/*! Return MCC-MNC-LAC-SAC as string, in a static buffer.
+ * \param[in] sai SAI to encode.
+ * \returns Static string buffer.
+ */
+const char *osmo_sai_name(const struct osmo_service_area_id *sai)
+{
+ static __thread char buf[32];
+ return osmo_sai_name_buf(buf, sizeof(buf), sai);
+}
+
+/*! Same as osmo_cgi_name(), but uses a different static buffer.
+ * Useful for printing two distinct CGIs in the same printf format.
+ * \param[in] sai SAI to encode.
+ * \returns Static string buffer.
+ */
+const char *osmo_sai_name2(const struct osmo_service_area_id *sai)
+{
+ static __thread char buf[32];
+ return osmo_sai_name_buf(buf, sizeof(buf), sai);
+}
+
+/*! Return MCC-MNC-LAC-SAC as string, in a talloc-allocated output buffer.
+ * \param[in] ctx talloc context from which to allocate output buffer
+ * \param[in] sai SAI to encode.
+ * \returns string representation of CGI in dynamically-allocated buffer.
+ */
+char *osmo_sai_name_c(const void *ctx, const struct osmo_service_area_id *sai)
+{
+ char *buf = talloc_size(ctx, 32);
+ return osmo_sai_name_buf(buf, 32, sai);
+}
+
+
static void to_bcd(uint8_t *bcd, uint16_t val)
{
bcd[2] = val % 10;
@@ -368,14 +490,12 @@ void osmo_plmn_to_bcd(uint8_t *bcd_dst, const struct osmo_plmn_id *plmn)
}
}
-/* Convert given 3-byte BCD buffer to integers and write results to *mcc and
- * *mnc. The first three BCD digits result in the MCC and the remaining ones in
- * the MNC. Return mnc_3_digits as false if the MNC's most significant digit is encoded as 0xF, true
- * otherwise; i.e. true if MNC > 99 or if it is represented with leading zeros instead of 0xF.
+/* Convert given 3-byte BCD buffer to integers and write results to plmn->mcc and plmn->mnc. The first three BCD digits
+ * result in the MCC and the remaining ones in the MNC. Set plmn->mnc_3_digits as false if the MNC's most significant
+ * digit is encoded as 0xF, true otherwise; i.e. true if MNC > 99 or if it is represented with leading zeros instead of
+ * 0xF.
* \param[in] bcd_src 3-byte BCD buffer containing MCC+MNC representations.
- * \param[out] mcc MCC result buffer, or NULL.
- * \param[out] mnc MNC result buffer, or NULL.
- * \param[out] mnc_3_digits Result buffer for 3-digit flag, or NULL.
+ * \param[out] plmn user provided memory to store the result.
*/
void osmo_plmn_from_bcd(const uint8_t *bcd_src, struct osmo_plmn_id *plmn)
{
@@ -406,22 +526,23 @@ void osmo_plmn_from_bcd(const uint8_t *bcd_src, struct osmo_plmn_id *plmn)
*/
int osmo_mnc_from_str(const char *mnc_str, uint16_t *mnc, bool *mnc_3_digits)
{
- long int _mnc = 0;
+ int _mnc = 0;
bool _mnc_3_digits = false;
- char *endptr;
int rc = 0;
if (!mnc_str || !isdigit((unsigned char)mnc_str[0]) || strlen(mnc_str) > 3)
return -EINVAL;
- errno = 0;
- _mnc = strtol(mnc_str, &endptr, 10);
- if (errno)
- rc = -errno;
- else if (*endptr)
+ rc = osmo_str_to_int(&_mnc, mnc_str, 10, 0, 999);
+ /* Heed the API definition to return -EINVAL in case of surplus chars */
+ if (rc == -E2BIG)
return -EINVAL;
- if (_mnc < 0 || _mnc > 999)
- return -ERANGE;
+ /* Heed the API definition to always return negative errno */
+ if (rc > 0)
+ return -rc;
+ if (rc < 0)
+ return rc;
+
_mnc_3_digits = strlen(mnc_str) > 2;
if (mnc)
@@ -484,6 +605,23 @@ int osmo_lai_cmp(const struct osmo_location_area_id *a, const struct osmo_locati
return 0;
}
+/* Compare two RAI.
+ * The order of comparison is MCC, MNC, LAC, RAC. See also osmo_lai_cmp().
+ * \param a[in] "Left" side RAI.
+ * \param b[in] "Right" side RAI.
+ * \returns 0 if the RAI are equal, -1 if a < b, 1 if a > b. */
+int osmo_rai_cmp(const struct osmo_routing_area_id *a, const struct osmo_routing_area_id *b)
+{
+ int rc = osmo_lai_cmp(&a->lac, &b->lac);
+ if (rc)
+ return rc;
+ if (a->rac < b->rac)
+ return -1;
+ if (a->rac > b->rac)
+ return 1;
+ return 0;
+}
+
/* Compare two CGI.
* The order of comparison is MCC, MNC, LAC, CI. See also osmo_lai_cmp().
* \param a[in] "Left" side CGI.
@@ -501,6 +639,23 @@ int osmo_cgi_cmp(const struct osmo_cell_global_id *a, const struct osmo_cell_glo
return 0;
}
+/* Compare two CGI-PS.
+ * The order of comparison is MCC, MNC, LAC, RAC, CI. See also osmo_rai_cmp().
+ * \param a[in] "Left" side CGI-PS.
+ * \param b[in] "Right" side CGI-PS.
+ * \returns 0 if the CGI are equal, -1 if a < b, 1 if a > b. */
+int osmo_cgi_ps_cmp(const struct osmo_cell_global_id_ps *a, const struct osmo_cell_global_id_ps *b)
+{
+ int rc = osmo_rai_cmp(&a->rai, &b->rai);
+ if (rc)
+ return rc;
+ if (a->cell_identity < b->cell_identity)
+ return -1;
+ if (a->cell_identity > b->cell_identity)
+ return 1;
+ return 0;
+}
+
/*! Generate TS 23.003 Section 19.2 Home Network Realm/Domain (text form)
* \param out[out] caller-provided output buffer, at least 33 bytes long
* \param plmn[in] Osmocom representation of PLMN ID (MCC + MNC)
diff --git a/src/gsm/gsm23236.c b/src/gsm/gsm23236.c
index 01d0eb36..4a83ec82 100644
--- a/src/gsm/gsm23236.c
+++ b/src/gsm/gsm23236.c
@@ -17,10 +17,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <errno.h>
@@ -436,27 +432,13 @@ char *osmo_nri_ranges_to_str_c(void *ctx, const struct osmo_nri_ranges *nri_rang
*/
static int osmo_nri_parse(int16_t *dst, const char *str)
{
- char *endp;
- int64_t val;
+ int val;
int base = 10;
-
- if (osmo_str_startswith(str, "0x")) {
- str += 2;
+ if (osmo_str_startswith(str, "0x"))
base = 16;
- }
-
- if (!str || !str[0])
+ if (osmo_str_to_int(&val, str, base, 0, INT16_MAX))
return -1;
-
- errno = 0;
- val = strtoull(str, &endp, base);
- if (errno || *endp != '\0')
- return -1;
-
- if (val < 0 || val > INT16_MAX)
- return -1;
-
- *dst = val;
+ *dst = (int16_t)val;
return 0;
}
diff --git a/src/gsm/gsm29118.c b/src/gsm/gsm29118.c
index 2c02b2fd..0ca5b52e 100644
--- a/src/gsm/gsm29118.c
+++ b/src/gsm/gsm29118.c
@@ -14,10 +14,6 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <osmocom/core/utils.h>
diff --git a/src/gsm/gsm29205.c b/src/gsm/gsm29205.c
index 6ceb8ee4..8fed0206 100644
--- a/src/gsm/gsm29205.c
+++ b/src/gsm/gsm29205.c
@@ -14,10 +14,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include "config.h"
diff --git a/src/gsm/gsm44021.c b/src/gsm/gsm44021.c
new file mode 100644
index 00000000..5e9d5ab8
--- /dev/null
+++ b/src/gsm/gsm44021.c
@@ -0,0 +1,303 @@
+/*************************************************************************
+ * GSM CSD modified V.110 frame decoding/encoding (ubits <-> struct with D/S/X/E bits)
+ *************************************************************************/
+
+/* (C) 2022 by Harald Welte <laforge@osmocom.org>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <errno.h>
+#include <osmocom/core/bits.h>
+#include <osmocom/isdn/v110.h>
+
+/*! Decode a 60-bit GSM 12kbit/s CSD frame present as 60 ubits into a struct osmo_v110_decoded_frame.
+ * \param[out] caller-allocated output data structure, filled by this function
+ * \param[in] ra_bits One V.110 frame as 60 unpacked bits. */
+int osmo_csd_12k_6k_decode_frame(struct osmo_v110_decoded_frame *fr, const ubit_t *ra_bits, size_t n_bits)
+{
+ /* 3GPP TS 44.021 Section 8.1.2 / 8.1.3
+ D1 D2 D3 D4 D5 D6 S1
+ D7 D8 D9 D10 D11 D12 X
+ D13 D14 D15 D16 D17 D18 S3
+ D19 D20 D21 D22 D23 D24 S4
+ E4 E5 E6 E7 D25 D26 D27
+ D28 D29 D30 S6 D31 D32 D33
+ D34 D35 D36 X D37 D38 D39
+ D40 D41 D42 S8 D43 D44 D45
+ D46 D47 D48 S9 */
+
+ if (n_bits < 60)
+ return -EINVAL;
+
+ /* X1 .. X2 */
+ fr->x_bits[0] = ra_bits[1 * 7 + 6];
+ fr->x_bits[1] = ra_bits[6 * 7 + 3];
+
+ /* S1, S3, S4, S6, S8, S9 */
+ fr->s_bits[0] = ra_bits[0 * 7 + 6];
+ fr->s_bits[2] = ra_bits[2 * 7 + 6];
+ fr->s_bits[3] = ra_bits[3 * 7 + 6];
+ fr->s_bits[5] = ra_bits[5 * 7 + 3];
+ fr->s_bits[7] = ra_bits[7 * 7 + 3];
+ fr->s_bits[8] = ra_bits[8 * 7 + 3];
+
+ /* E1 .. E3 must be set by out-of-band knowledge! */
+
+ /* E4 .. E7 */
+ memcpy(fr->e_bits+3, ra_bits + 4 * 7 + 0, 4);
+
+ /* D-bits */
+ memcpy(fr->d_bits + 0 * 6 + 0, ra_bits + 0 * 7 + 0, 6);
+ memcpy(fr->d_bits + 1 * 6 + 0, ra_bits + 1 * 7 + 0, 6);
+ memcpy(fr->d_bits + 2 * 6 + 0, ra_bits + 2 * 7 + 0, 6);
+ memcpy(fr->d_bits + 3 * 6 + 0, ra_bits + 3 * 7 + 0, 6);
+ memcpy(fr->d_bits + 4 * 6 + 0, ra_bits + 4 * 7 + 4, 3);
+ memcpy(fr->d_bits + 4 * 6 + 3, ra_bits + 5 * 7 + 0, 3);
+ memcpy(fr->d_bits + 5 * 6 + 0, ra_bits + 5 * 7 + 4, 3);
+ memcpy(fr->d_bits + 5 * 6 + 3, ra_bits + 6 * 7 + 0, 3);
+ memcpy(fr->d_bits + 6 * 6 + 0, ra_bits + 6 * 7 + 4, 3);
+ memcpy(fr->d_bits + 6 * 6 + 3, ra_bits + 7 * 7 + 0, 3);
+ memcpy(fr->d_bits + 7 * 6 + 0, ra_bits + 7 * 7 + 4, 3);
+ memcpy(fr->d_bits + 7 * 6 + 3, ra_bits + 8 * 7 + 0, 3);
+
+ return 0;
+}
+
+int osmo_csd_12k_6k_encode_frame(ubit_t *ra_bits, size_t ra_bits_size, const struct osmo_v110_decoded_frame *fr)
+{
+ if (ra_bits_size < 60)
+ return -EINVAL;
+
+ /* X1 .. X2 */
+ ra_bits[1 * 7 + 6] = fr->x_bits[0];
+ ra_bits[6 * 7 + 3] = fr->x_bits[1];
+
+ /* S1, S3, S4, S6, S8, S9 */
+ ra_bits[0 * 7 + 6] = fr->s_bits[0];
+ ra_bits[2 * 7 + 6] = fr->s_bits[2];
+ ra_bits[3 * 7 + 6] = fr->s_bits[3];
+ ra_bits[5 * 7 + 3] = fr->s_bits[5];
+ ra_bits[7 * 7 + 3] = fr->s_bits[7];
+ ra_bits[8 * 7 + 3] = fr->s_bits[8];
+
+ /* E1 .. E3 are dropped */
+
+ /* E4 .. E7 */
+ memcpy(ra_bits + 4 * 7 + 0, fr->e_bits+3, 4);
+
+ /* D-bits */
+ memcpy(ra_bits + 0 * 7 + 0, fr->d_bits + 0 * 6 + 0, 6);
+ memcpy(ra_bits + 1 * 7 + 0, fr->d_bits + 1 * 6 + 0, 6);
+ memcpy(ra_bits + 2 * 7 + 0, fr->d_bits + 2 * 6 + 0, 6);
+ memcpy(ra_bits + 3 * 7 + 0, fr->d_bits + 3 * 6 + 0, 6);
+ memcpy(ra_bits + 4 * 7 + 4, fr->d_bits + 4 * 6 + 0, 3);
+ memcpy(ra_bits + 5 * 7 + 0, fr->d_bits + 4 * 6 + 3, 3);
+ memcpy(ra_bits + 5 * 7 + 4, fr->d_bits + 5 * 6 + 0, 3);
+ memcpy(ra_bits + 6 * 7 + 0, fr->d_bits + 5 * 6 + 3, 3);
+ memcpy(ra_bits + 6 * 7 + 4, fr->d_bits + 6 * 6 + 0, 3);
+ memcpy(ra_bits + 7 * 7 + 0, fr->d_bits + 6 * 6 + 3, 3);
+ memcpy(ra_bits + 7 * 7 + 4, fr->d_bits + 7 * 6 + 0, 3);
+ memcpy(ra_bits + 8 * 7 + 0, fr->d_bits + 7 * 6 + 3, 3);
+
+ return 60;
+}
+
+/*! Decode a 36-bit GSM 3k6kbit/s CSD frame present as 36 ubits into a struct osmo_v110_decoded_frame.
+ * \param[out] caller-allocated output data structure, filled by this function
+ * \param[in] ra_bits One V.110 frame as 36 unpacked bits. */
+int osmo_csd_3k6_decode_frame(struct osmo_v110_decoded_frame *fr, const ubit_t *ra_bits, size_t n_bits)
+{
+
+ /* 3GPP TS 44.021 Section 8.1.4
+ D1 D2 D3 S1 D4 D5 D6 X
+ D7 D8 D9 S3 D10 D11 D12 S4
+ E4 E5 E6 E7 D13 D14 D15 S6
+ D16 D17 D18 X D19 D20 D21 S8
+ D22 D23 D24 S9
+ */
+
+ if (n_bits < 36)
+ return -EINVAL;
+
+ /* X1 .. X2 */
+ fr->x_bits[0] = ra_bits[0 * 8 + 7];
+ fr->x_bits[1] = ra_bits[3 * 8 + 3];
+
+ /* S1, S3, S4, S6, S8, S9 */
+ fr->s_bits[0] = ra_bits[0 * 8 + 3];
+ fr->s_bits[2] = ra_bits[1 * 8 + 3];
+ fr->s_bits[3] = ra_bits[1 * 8 + 7];
+ fr->s_bits[5] = ra_bits[2 * 8 + 7];
+ fr->s_bits[7] = ra_bits[3 * 8 + 7];
+ fr->s_bits[8] = ra_bits[4 * 8 + 3];
+
+ /* E1 .. E3 must be set by out-of-band knowledge! */
+
+ /* E4 .. E7 */
+ memcpy(fr->e_bits+3, ra_bits + 2 * 8 + 0, 4);
+
+ /* D-bits */
+ unsigned int d_idx = 0;
+ fr->d_bits[d_idx++] = ra_bits[0 * 8 + 0]; /* D1 */
+ fr->d_bits[d_idx++] = ra_bits[0 * 8 + 0]; /* D1 */
+ fr->d_bits[d_idx++] = ra_bits[0 * 8 + 1]; /* D2 */
+ fr->d_bits[d_idx++] = ra_bits[0 * 8 + 1]; /* D2 */
+ fr->d_bits[d_idx++] = ra_bits[0 * 8 + 2]; /* D3 */
+ fr->d_bits[d_idx++] = ra_bits[0 * 8 + 2]; /* D3 */
+ fr->d_bits[d_idx++] = ra_bits[0 * 8 + 4]; /* D4 */
+ fr->d_bits[d_idx++] = ra_bits[0 * 8 + 4]; /* D4 */
+ fr->d_bits[d_idx++] = ra_bits[0 * 8 + 5]; /* D5 */
+ fr->d_bits[d_idx++] = ra_bits[0 * 8 + 5]; /* D5 */
+ fr->d_bits[d_idx++] = ra_bits[0 * 8 + 6]; /* D6 */
+ fr->d_bits[d_idx++] = ra_bits[0 * 8 + 6]; /* D6 */
+
+ fr->d_bits[d_idx++] = ra_bits[1 * 8 + 0]; /* D7 */
+ fr->d_bits[d_idx++] = ra_bits[1 * 8 + 0]; /* D7 */
+ fr->d_bits[d_idx++] = ra_bits[1 * 8 + 1]; /* D8 */
+ fr->d_bits[d_idx++] = ra_bits[1 * 8 + 1]; /* D8 */
+ fr->d_bits[d_idx++] = ra_bits[1 * 8 + 2]; /* D9 */
+ fr->d_bits[d_idx++] = ra_bits[1 * 8 + 2]; /* D9 */
+ fr->d_bits[d_idx++] = ra_bits[1 * 8 + 4]; /* D10 */
+ fr->d_bits[d_idx++] = ra_bits[1 * 8 + 4]; /* D10 */
+ fr->d_bits[d_idx++] = ra_bits[1 * 8 + 5]; /* D11 */
+ fr->d_bits[d_idx++] = ra_bits[1 * 8 + 5]; /* D11 */
+ fr->d_bits[d_idx++] = ra_bits[1 * 8 + 6]; /* D12 */
+ fr->d_bits[d_idx++] = ra_bits[1 * 8 + 6]; /* D12 */
+
+ fr->d_bits[d_idx++] = ra_bits[2 * 8 + 4]; /* D13 */
+ fr->d_bits[d_idx++] = ra_bits[2 * 8 + 4]; /* D13 */
+ fr->d_bits[d_idx++] = ra_bits[2 * 8 + 5]; /* D14 */
+ fr->d_bits[d_idx++] = ra_bits[2 * 8 + 5]; /* D14 */
+ fr->d_bits[d_idx++] = ra_bits[2 * 8 + 6]; /* D15 */
+ fr->d_bits[d_idx++] = ra_bits[2 * 8 + 6]; /* D15 */
+
+ fr->d_bits[d_idx++] = ra_bits[3 * 8 + 0]; /* D16 */
+ fr->d_bits[d_idx++] = ra_bits[3 * 8 + 0]; /* D16 */
+ fr->d_bits[d_idx++] = ra_bits[3 * 8 + 1]; /* D17 */
+ fr->d_bits[d_idx++] = ra_bits[3 * 8 + 1]; /* D17 */
+ fr->d_bits[d_idx++] = ra_bits[3 * 8 + 2]; /* D18 */
+ fr->d_bits[d_idx++] = ra_bits[3 * 8 + 2]; /* D18 */
+ fr->d_bits[d_idx++] = ra_bits[3 * 8 + 4]; /* D19 */
+ fr->d_bits[d_idx++] = ra_bits[3 * 8 + 4]; /* D19 */
+ fr->d_bits[d_idx++] = ra_bits[3 * 8 + 5]; /* D20 */
+ fr->d_bits[d_idx++] = ra_bits[3 * 8 + 5]; /* D20 */
+ fr->d_bits[d_idx++] = ra_bits[3 * 8 + 6]; /* D21 */
+ fr->d_bits[d_idx++] = ra_bits[3 * 8 + 6]; /* D21 */
+
+ fr->d_bits[d_idx++] = ra_bits[4 * 8 + 0]; /* D22 */
+ fr->d_bits[d_idx++] = ra_bits[4 * 8 + 0]; /* D22 */
+ fr->d_bits[d_idx++] = ra_bits[4 * 8 + 1]; /* D23 */
+ fr->d_bits[d_idx++] = ra_bits[4 * 8 + 1]; /* D23 */
+ fr->d_bits[d_idx++] = ra_bits[4 * 8 + 2]; /* D24 */
+ fr->d_bits[d_idx++] = ra_bits[4 * 8 + 2]; /* D24 */
+
+ OSMO_ASSERT(d_idx == 48);
+
+ return 0;
+}
+
+int osmo_csd_3k6_encode_frame(ubit_t *ra_bits, size_t ra_bits_size, const struct osmo_v110_decoded_frame *fr)
+{
+ if (ra_bits_size < 36)
+ return -EINVAL;
+
+ /* X1 .. X2 */
+ ra_bits[0 * 8 + 7] = fr->x_bits[0];
+ ra_bits[3 * 8 + 3] = fr->x_bits[1];
+
+ /* S1, S3, S4, S6, S8, S9 */
+ ra_bits[0 * 8 + 3] = fr->s_bits[0];
+ ra_bits[1 * 8 + 3] = fr->s_bits[2];
+ ra_bits[1 * 8 + 7] = fr->s_bits[3];
+ ra_bits[2 * 8 + 7] = fr->s_bits[5];
+ ra_bits[3 * 8 + 7] = fr->s_bits[7];
+ ra_bits[4 * 8 + 3] = fr->s_bits[8];
+
+ /* E1 .. E3 are ignored */
+
+ /* E4 .. E7 */
+ memcpy(ra_bits + 2 * 8 + 0, fr->e_bits+3, 4);
+
+ /* D-bits */
+ unsigned int d_idx = 0;
+ ra_bits[0 * 8 + 0] = fr->d_bits[d_idx]; d_idx += 2;
+ ra_bits[0 * 8 + 1] = fr->d_bits[d_idx]; d_idx += 2;
+ ra_bits[0 * 8 + 2] = fr->d_bits[d_idx]; d_idx += 2;
+ ra_bits[0 * 8 + 4] = fr->d_bits[d_idx]; d_idx += 2;
+ ra_bits[0 * 8 + 5] = fr->d_bits[d_idx]; d_idx += 2;
+ ra_bits[0 * 8 + 6] = fr->d_bits[d_idx]; d_idx += 2;
+
+ ra_bits[1 * 8 + 0] = fr->d_bits[d_idx]; d_idx += 2;
+ ra_bits[1 * 8 + 1] = fr->d_bits[d_idx]; d_idx += 2;
+ ra_bits[1 * 8 + 2] = fr->d_bits[d_idx]; d_idx += 2;
+ ra_bits[1 * 8 + 4] = fr->d_bits[d_idx]; d_idx += 2;
+ ra_bits[1 * 8 + 5] = fr->d_bits[d_idx]; d_idx += 2;
+ ra_bits[1 * 8 + 6] = fr->d_bits[d_idx]; d_idx += 2;
+
+ ra_bits[2 * 8 + 4] = fr->d_bits[d_idx]; d_idx += 2;
+ ra_bits[2 * 8 + 5] = fr->d_bits[d_idx]; d_idx += 2;
+ ra_bits[2 * 8 + 6] = fr->d_bits[d_idx]; d_idx += 2;
+
+ ra_bits[3 * 8 + 0] = fr->d_bits[d_idx]; d_idx += 2;
+ ra_bits[3 * 8 + 1] = fr->d_bits[d_idx]; d_idx += 2;
+ ra_bits[3 * 8 + 2] = fr->d_bits[d_idx]; d_idx += 2;
+ ra_bits[3 * 8 + 4] = fr->d_bits[d_idx]; d_idx += 2;
+ ra_bits[3 * 8 + 5] = fr->d_bits[d_idx]; d_idx += 2;
+ ra_bits[3 * 8 + 6] = fr->d_bits[d_idx]; d_idx += 2;
+
+ ra_bits[4 * 8 + 0] = fr->d_bits[d_idx]; d_idx += 2;
+ ra_bits[4 * 8 + 1] = fr->d_bits[d_idx]; d_idx += 2;
+ ra_bits[4 * 8 + 2] = fr->d_bits[d_idx]; d_idx += 2;
+
+ OSMO_ASSERT(d_idx == 48);
+
+ return 36;
+}
+
+/*! Print a encoded "CSD modififed V.110" frame in the same table-like structure as the spec.
+ * \param outf output FILE stream to which to dump
+ * \param[in] fr unpacked bits to dump
+ * \param[in] in_len length of unpacked bits available at fr. */
+void osmo_csd_ubit_dump(FILE *outf, const ubit_t *fr, size_t in_len)
+{
+ switch (in_len) {
+ case 60:
+ for (unsigned int septet = 0; septet < 9; septet++) {
+ if (septet < 8) {
+ fprintf(outf, "%d\t%d\t%d\t%d\t%d\t%d\t%d\n", fr[septet * 7 + 0],
+ fr[septet * 7 + 1], fr[septet * 7 + 2], fr[septet * 7 + 3],
+ fr[septet * 7 + 4], fr[septet * 7 + 5], fr[septet*7 + 6]);
+ } else {
+ fprintf(outf, "%d\t%d\t%d\t%d\n", fr[septet * 7 + 0],
+ fr[septet * 7 + 1], fr[septet * 7 + 2], fr[septet * 7 + 3]);
+ }
+ }
+ break;
+ case 36:
+ for (unsigned int octet = 0; octet < 5; octet++) {
+ if (octet < 4) {
+ fprintf(outf, "%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\n",
+ fr[octet * 8 + 0], fr[octet * 8 + 1], fr[octet * 8 + 2],
+ fr[octet * 8 + 3], fr[octet * 8 + 4], fr[octet * 8 + 5],
+ fr[octet * 8 + 6], fr[octet * 8 + 7]);
+ } else {
+ fprintf(outf, "%d\t%d\t%d\t%d\n", fr[octet * 8 + 0],
+ fr[octet * 8 + 1], fr[octet * 8 + 2], fr[octet * 8 + 3]);
+ }
+ }
+ break;
+ default:
+ fprintf(outf, "invalid input data length: %zu\n", in_len);
+ }
+}
diff --git a/src/gsm/gsm48.c b/src/gsm/gsm48.c
index eb1d055b..919500bc 100644
--- a/src/gsm/gsm48.c
+++ b/src/gsm/gsm48.c
@@ -19,10 +19,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <stdint.h>
@@ -124,6 +120,7 @@ const struct tlv_definition gsm48_rr_att_tlvdef = {
[GSM48_IE_CHDES_2_AFTER] = { TLV_TYPE_FIXED, 3 },
[GSM48_IE_MODE_SEC_CH] = { TLV_TYPE_TV },
[GSM48_IE_F_CH_SEQ_AFTER] = { TLV_TYPE_FIXED, 9 },
+ [GSM48_IE_EXTENDED_TSC_SET] = { TLV_TYPE_TV },
[GSM48_IE_MA_AFTER] = { TLV_TYPE_TLV },
[GSM48_IE_BA_RANGE] = { TLV_TYPE_TLV },
[GSM48_IE_GROUP_CHDES] = { TLV_TYPE_TLV },
@@ -164,13 +161,16 @@ static const struct value_string rr_cause_names[] = {
{ GSM48_RR_CAUSE_ABNORMAL_TIMER, "Abnormal release, timer expired" },
{ GSM48_RR_CAUSE_ABNORMAL_NOACT, "Abnormal release, no activity on radio path" },
{ GSM48_RR_CAUSE_PREMPTIVE_REL, "Preemptive release" },
+ { GSM48_RR_CAUSE_UTRAN_CFG_UNK, "UTRAN configuration unknown" },
{ GSM48_RR_CAUSE_HNDOVER_IMP, "Handover impossible, timing advance out of range" },
{ GSM48_RR_CAUSE_CHAN_MODE_UNACCT, "Channel mode unacceptable" },
{ GSM48_RR_CAUSE_FREQ_NOT_IMPL, "Frequency not implemented" },
+ { GSM48_RR_CAUSE_LEAVE_GROUP_CA, "Originator or talker leaving group call area" },
+ { GSM48_RR_CAUSE_LOW_LEVEL_FAIL, "Lower layer failure" },
{ GSM48_RR_CAUSE_CALL_CLEARED, "Call already cleared" },
{ GSM48_RR_CAUSE_SEMANT_INCORR, "Semantically incorrect message" },
{ GSM48_RR_CAUSE_INVALID_MAND_INF, "Invalid mandatory information" },
- { GSM48_RR_CAUSE_MSG_TYPE_N, "Message type non-existant or not implemented" },
+ { GSM48_RR_CAUSE_MSG_TYPE_N, "Message type non-existent or not implemented" },
{ GSM48_RR_CAUSE_MSG_TYPE_N_COMPAT, "Message type not compatible with protocol state" },
{ GSM48_RR_CAUSE_COND_IE_ERROR, "Conditional IE error" },
{ GSM48_RR_CAUSE_NO_CELL_ALLOC_A, "No cell allocation available" },
@@ -419,6 +419,29 @@ 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" },
@@ -429,9 +452,52 @@ const struct value_string gsm48_chan_mode_names[] = {
{ GSM48_CMODE_DATA_12k0, "DATA_12k0" },
{ GSM48_CMODE_DATA_6k0, "DATA_6k0" },
{ GSM48_CMODE_DATA_3k6, "DATA_3k6" },
+ { GSM48_CMODE_SPEECH_V1_VAMOS, "SPEECH_V1_VAMOS" },
+ { GSM48_CMODE_SPEECH_V2_VAMOS, "SPEECH_V2_VAMOS" },
+ { GSM48_CMODE_SPEECH_V3_VAMOS, "SPEECH_V3_VAMOS" },
+ { GSM48_CMODE_SPEECH_V5_VAMOS, "SPEECH_V5_VAMOS" },
{ 0, NULL },
};
+/*! Translate GSM48_CMODE_SPEECH_* to its corresponding GSM48_CMODE_SPEECH_*_VAMOS mode.
+ * If the mode has no equivalent VAMOS mode, return a negative value.
+ */
+enum gsm48_chan_mode gsm48_chan_mode_to_vamos(enum gsm48_chan_mode mode)
+{
+ switch (mode) {
+ case GSM48_CMODE_SPEECH_V1:
+ case GSM48_CMODE_SPEECH_V1_VAMOS:
+ return GSM48_CMODE_SPEECH_V1_VAMOS;
+ case GSM48_CMODE_SPEECH_EFR:
+ case GSM48_CMODE_SPEECH_V2_VAMOS:
+ return GSM48_CMODE_SPEECH_V2_VAMOS;
+ case GSM48_CMODE_SPEECH_AMR:
+ case GSM48_CMODE_SPEECH_V3_VAMOS:
+ return GSM48_CMODE_SPEECH_V3_VAMOS;
+ case GSM48_CMODE_SPEECH_V5_VAMOS:
+ return GSM48_CMODE_SPEECH_V5_VAMOS;
+ default:
+ return -1;
+ }
+}
+
+/*! Translate GSM48_CMODE_SPEECH_*_VAMOS to its corresponding GSM48_CMODE_SPEECH_* non-vamos mode.
+ * If the mode is not a VAMOS mode, return the unchanged mode.
+ */
+enum gsm48_chan_mode gsm48_chan_mode_to_non_vamos(enum gsm48_chan_mode mode)
+{
+ switch (mode) {
+ case GSM48_CMODE_SPEECH_V1_VAMOS:
+ return GSM48_CMODE_SPEECH_V1;
+ case GSM48_CMODE_SPEECH_V2_VAMOS:
+ return GSM48_CMODE_SPEECH_EFR;
+ case GSM48_CMODE_SPEECH_V3_VAMOS:
+ return GSM48_CMODE_SPEECH_AMR;
+ default:
+ return mode;
+ }
+}
+
const struct value_string gsm_chan_t_names[] = {
{ GSM_LCHAN_NONE, "NONE" },
{ GSM_LCHAN_SDCCH, "SDCCH" },
@@ -554,8 +620,8 @@ int osmo_mobile_identity_decode(struct osmo_mobile_identity *mi, const uint8_t *
{
int rc;
int nibbles_len;
- char *str;
- size_t str_size;
+ char *str = NULL; /* initialize to avoid uninitialized false warnings on some gcc versions (11.1.0) */
+ size_t str_size = 0; /* initialize to avoid uninitialized false warnings on some gcc versions (11.1.0) */
if (!mi_data || mi_len < 1)
return -EBADMSG;
@@ -630,8 +696,12 @@ int osmo_mobile_identity_decode(struct osmo_mobile_identity *mi, const uint8_t *
goto return_error;
}
rc = osmo_bcd2str(str, str_size, mi_data, 1, 1 + nibbles_len, allow_hex);
- /* rc checked below */
- break;
+ /* check mi->str printing rc */
+ if (rc < 1 || rc >= str_size) {
+ rc = -EBADMSG;
+ goto return_error;
+ }
+ return 0;
default:
/* Already handled above, but as future bug paranoia: */
@@ -639,13 +709,6 @@ int osmo_mobile_identity_decode(struct osmo_mobile_identity *mi, const uint8_t *
goto return_error;
}
- /* check mi->str printing rc */
- if (rc < 1 || rc >= str_size) {
- rc = -EBADMSG;
- goto return_error;
- }
- return 0;
-
return_error:
*mi = (struct osmo_mobile_identity){
.type = GSM_MI_TYPE_NONE,
@@ -1295,6 +1358,25 @@ int gsm48_construct_ra(uint8_t *buf, const struct gprs_ra_id *raid)
return 6;
}
+/*! Compare a TS 04.08 Routing Area Identifier
+ * \param[in] raid1 first Routing Area ID to compare.
+ * \param[in] raid2 second Routing Area ID to compare.
+ * \returns true if raid1 and raid2 match, false otherwise. */
+bool gsm48_ra_equal(const struct gprs_ra_id *raid1, const struct gprs_ra_id *raid2)
+{
+ if (raid1->mcc != raid2->mcc)
+ return false;
+ if (raid1->mnc != raid2->mnc)
+ return false;
+ if (raid1->mnc_3_digits != raid2->mnc_3_digits)
+ return false;
+ if (raid1->lac != raid2->lac)
+ return false;
+ if (raid1->rac != raid2->rac)
+ return false;
+ return true;
+}
+
/*! Determine number of paging sub-channels
* \param[in] chan_desc Control Channel Description
* \returns number of paging sub-channels
@@ -1302,7 +1384,7 @@ int gsm48_construct_ra(uint8_t *buf, const struct gprs_ra_id *raid)
* Uses From Table 10.5.33 of GSM 04.08 to determine the number of
* paging sub-channels in the given control channel configuration
*/
-int gsm48_number_of_paging_subchannels(struct gsm48_control_channel_descr *chan_desc)
+int gsm48_number_of_paging_subchannels(const struct gsm48_control_channel_descr *chan_desc)
{
unsigned int n_pag_blocks = gsm0502_get_n_pag_blocks(chan_desc);
diff --git a/src/gsm/gsm48_arfcn_range_encode.c b/src/gsm/gsm48_arfcn_range_encode.c
index 6423a9a8..afe552d8 100644
--- a/src/gsm/gsm48_arfcn_range_encode.c
+++ b/src/gsm/gsm48_arfcn_range_encode.c
@@ -166,6 +166,10 @@ int osmo_gsm48_range_enc_determine_range(const int *arfcns, const int size, int
{
int max = 0;
+ /* don't dereference arfcns[] array if size is 0 */
+ if (size == 0)
+ return OSMO_GSM48_ARFCN_RANGE_128;
+
/*
* Go for the easiest. And pick arfcns[0] == f0.
*/
diff --git a/src/gsm/gsm48_ie.c b/src/gsm/gsm48_ie.c
index 31028ba4..e431e4f8 100644
--- a/src/gsm/gsm48_ie.c
+++ b/src/gsm/gsm48_ie.c
@@ -19,10 +19,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
@@ -34,6 +30,7 @@
#include <osmocom/core/msgb.h>
#include <osmocom/gsm/tlv.h>
#include <osmocom/gsm/mncc.h>
+#include <osmocom/core/bitvec.h>
#include <osmocom/gsm/protocol/gsm_04_08.h>
#include <osmocom/gsm/gsm48_ie.h>
@@ -206,7 +203,24 @@ int gsm48_decode_bearer_cap(struct gsm_mncc_bearer_cap *bcap,
case GSM_MNCC_BCAP_SPEECH:
i = 1;
s = 0;
- while(!(lv[i] & 0x80)) {
+ if ((lv[1] & 0x80) != 0) { /* octet 3a is absent */
+ switch (bcap->radio) {
+ case GSM48_BCAP_RRQ_FR_ONLY:
+ bcap->speech_ver[s++] = GSM48_BCAP_SV_FR;
+ break;
+ case GSM48_BCAP_RRQ_DUAL_HR:
+ bcap->speech_ver[s++] = GSM48_BCAP_SV_HR;
+ bcap->speech_ver[s++] = GSM48_BCAP_SV_FR;
+ break;
+ case GSM48_BCAP_RRQ_DUAL_FR:
+ bcap->speech_ver[s++] = GSM48_BCAP_SV_FR;
+ bcap->speech_ver[s++] = GSM48_BCAP_SV_HR;
+ break;
+ }
+ bcap->speech_ver[s] = -1; /* end of list */
+ return 0;
+ }
+ while (!(lv[i] & 0x80)) {
i++; /* octet 3a etc */
if (in_len < i)
return 0;
@@ -221,7 +235,7 @@ int gsm48_decode_bearer_cap(struct gsm_mncc_bearer_cap *bcap,
case GSM_MNCC_BCAP_UNR_DIG:
case GSM_MNCC_BCAP_FAX_G3:
i = 1;
- while(!(lv[i] & 0x80)) {
+ while (!(lv[i] & 0x80)) {
i++; /* octet 3a etc */
if (in_len < i)
return 0;
@@ -235,7 +249,7 @@ int gsm48_decode_bearer_cap(struct gsm_mncc_bearer_cap *bcap,
return 0;
bcap->data.rate_adaption = (lv[i] >> 3) & 3;
bcap->data.sig_access = lv[i] & 7;
- while(!(lv[i] & 0x80)) {
+ while (!(lv[i] & 0x80)) {
i++; /* octet 5a etc */
if (in_len < i)
return 0;
@@ -355,7 +369,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];
@@ -811,7 +825,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);
@@ -866,8 +880,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;
@@ -1299,4 +1314,249 @@ int gsm48_decode_freq_list(struct gsm_sysinfo_freq *f, uint8_t *cd,
return 0;
}
+
+/*! Decode 3GPP TS 24.008 Mobile Station Classmark 3 (10.5.1.7).
+ * \param[out] classmark3_out user provided memory to store decoded classmark3.
+ * \param[in] classmark3 pointer to memory that contains the raw classmark bits.
+ * \param[in] classmark3_len length in bytes of the memory where classmark3 points to.
+ * \returns 0 on success; negative on error. */
+int gsm48_decode_classmark3(struct gsm48_classmark3 *classmark3_out,
+ const uint8_t *classmark3, size_t classmark3_len)
+{
+ struct bitvec bv;
+ uint8_t data[255];
+ struct gsm48_classmark3 *cm3 = classmark3_out;
+
+ /* if cm3 gets extended by spec, it will be truncated, but 255 bytes
+ * should be more than enough. */
+ if (classmark3_len > sizeof(data))
+ classmark3_len = sizeof(data);
+
+ memset(&bv, 0, sizeof(bv));
+ memset(data, 0, sizeof(data));
+ memset(classmark3_out, 0, sizeof(*classmark3_out));
+
+ memcpy(data, classmark3, classmark3_len);
+ bv.data = (uint8_t*) data;
+ bv.data_len = sizeof(data);
+
+ /* Parse bit vector, see also: 3GPP TS 24.008, section 10.5.1.7 */
+ bitvec_get_uint(&bv, 1);
+ cm3->mult_band_supp = bitvec_get_uint(&bv, 3);
+ switch (cm3->mult_band_supp) {
+ case 0x00:
+ cm3->a5_bits = bitvec_get_uint(&bv, 4);
+ break;
+ case 0x05:
+ case 0x06:
+ cm3->a5_bits = bitvec_get_uint(&bv, 4);
+ cm3->assoc_radio_cap_2 = bitvec_get_uint(&bv, 4);
+ cm3->assoc_radio_cap_1 = bitvec_get_uint(&bv, 4);
+ break;
+ case 0x01:
+ case 0x02:
+ case 0x04:
+ cm3->a5_bits = bitvec_get_uint(&bv, 4);
+ bitvec_get_uint(&bv, 4);
+ cm3->assoc_radio_cap_1 = bitvec_get_uint(&bv, 4);
+ break;
+ default:
+ return -1;
+ }
+
+ cm3->r_support.present = bitvec_get_uint(&bv, 1);
+ if (cm3->r_support.present)
+ cm3->r_support.r_gsm_assoc_radio_cap = bitvec_get_uint(&bv, 3);
+
+ cm3->hscsd_mult_slot_cap.present = bitvec_get_uint(&bv, 1);
+ if (cm3->hscsd_mult_slot_cap.present)
+ cm3->hscsd_mult_slot_cap.mslot_class = bitvec_get_uint(&bv, 5);
+
+ cm3->ucs2_treatment = bitvec_get_uint(&bv, 1);
+ cm3->extended_meas_cap = bitvec_get_uint(&bv, 1);
+
+ cm3->ms_meas_cap.present = bitvec_get_uint(&bv, 1);
+ if (cm3->ms_meas_cap.present) {
+ cm3->ms_meas_cap.sms_value = bitvec_get_uint(&bv, 4);
+ cm3->ms_meas_cap.sm_value = bitvec_get_uint(&bv, 4);
+ }
+
+ cm3->ms_pos_method_cap.present = bitvec_get_uint(&bv, 1);
+ if (cm3->ms_pos_method_cap.present)
+ cm3->ms_pos_method_cap.method = bitvec_get_uint(&bv, 5);
+
+ cm3->ecsd_multislot_cap.present = bitvec_get_uint(&bv, 1);
+ if (cm3->ecsd_multislot_cap.present)
+ cm3->ecsd_multislot_cap.mslot_class = bitvec_get_uint(&bv, 5);
+
+ cm3->psk8_struct.present = bitvec_get_uint(&bv, 1);
+ if (cm3->psk8_struct.present) {
+ cm3->psk8_struct.mod_cap = bitvec_get_uint(&bv, 1);
+
+ cm3->psk8_struct.rf_pwr_cap_1.present = bitvec_get_uint(&bv, 1);
+ if (cm3->psk8_struct.rf_pwr_cap_1.present) {
+ cm3->psk8_struct.rf_pwr_cap_1.value =
+ bitvec_get_uint(&bv, 2);
+ }
+
+ cm3->psk8_struct.rf_pwr_cap_2.present = bitvec_get_uint(&bv, 1);
+ if (cm3->psk8_struct.rf_pwr_cap_2.present) {
+ cm3->psk8_struct.rf_pwr_cap_2.value =
+ bitvec_get_uint(&bv, 2);
+ }
+ }
+
+ cm3->gsm_400_bands_supp.present = bitvec_get_uint(&bv, 1);
+ if (cm3->gsm_400_bands_supp.present) {
+ cm3->gsm_400_bands_supp.value = bitvec_get_uint(&bv, 2);
+ if (cm3->gsm_400_bands_supp.value == 0x00)
+ return -1;
+ cm3->gsm_400_bands_supp.assoc_radio_cap =
+ bitvec_get_uint(&bv, 4);
+ }
+
+ cm3->gsm_850_assoc_radio_cap.present = bitvec_get_uint(&bv, 1);
+ if (cm3->gsm_850_assoc_radio_cap.present)
+ cm3->gsm_850_assoc_radio_cap.value = bitvec_get_uint(&bv, 4);
+
+ cm3->gsm_1900_assoc_radio_cap.present = bitvec_get_uint(&bv, 1);
+ if (cm3->gsm_1900_assoc_radio_cap.present)
+ cm3->gsm_1900_assoc_radio_cap.value = bitvec_get_uint(&bv, 4);
+
+ cm3->umts_fdd_rat_cap = bitvec_get_uint(&bv, 1);
+ cm3->umts_tdd_rat_cap = bitvec_get_uint(&bv, 1);
+ cm3->cdma200_rat_cap = bitvec_get_uint(&bv, 1);
+
+ cm3->dtm_gprs_multislot_cap.present = bitvec_get_uint(&bv, 1);
+ if (cm3->dtm_gprs_multislot_cap.present) {
+ cm3->dtm_gprs_multislot_cap.mslot_class = bitvec_get_uint(&bv, 2);
+ cm3->dtm_gprs_multislot_cap.single_slot_dtm =
+ bitvec_get_uint(&bv, 1);
+ cm3->dtm_gprs_multislot_cap.dtm_egprs_multislot_cap.present =
+ bitvec_get_uint(&bv, 1);
+ if (cm3->dtm_gprs_multislot_cap.dtm_egprs_multislot_cap.present)
+ cm3->dtm_gprs_multislot_cap.dtm_egprs_multislot_cap.
+ mslot_class = bitvec_get_uint(&bv, 2);
+ }
+
+ /* Release 4 starts here. */
+ cm3->single_band_supp.present = bitvec_get_uint(&bv, 1);
+ if (cm3->single_band_supp.present)
+ cm3->single_band_supp.value = bitvec_get_uint(&bv, 4);
+
+ cm3->gsm_750_assoc_radio_cap.present = bitvec_get_uint(&bv, 1);
+ if (cm3->gsm_750_assoc_radio_cap.present)
+ cm3->gsm_750_assoc_radio_cap.value = bitvec_get_uint(&bv, 4);
+
+ cm3->umts_1_28_mcps_tdd_rat_cap = bitvec_get_uint(&bv, 1);
+ cm3->geran_feature_package = bitvec_get_uint(&bv, 1);
+
+ cm3->extended_dtm_gprs_multislot_cap.present = bitvec_get_uint(&bv, 1);
+ if (cm3->extended_dtm_gprs_multislot_cap.present) {
+ cm3->extended_dtm_gprs_multislot_cap.mslot_class =
+ bitvec_get_uint(&bv, 2);
+ cm3->extended_dtm_gprs_multislot_cap.
+ extended_dtm_egprs_multislot_cap.present =
+ bitvec_get_uint(&bv, 1);
+ if (cm3->extended_dtm_gprs_multislot_cap.
+ extended_dtm_egprs_multislot_cap.present)
+ cm3->extended_dtm_gprs_multislot_cap.
+ extended_dtm_egprs_multislot_cap.mslot_class =
+ bitvec_get_uint(&bv, 2);
+ }
+
+ /* Release 5 starts here */
+ cm3->high_multislot_cap.present = bitvec_get_uint(&bv, 1);
+ if (cm3->high_multislot_cap.present)
+ cm3->high_multislot_cap.value = bitvec_get_uint(&bv, 2);
+
+ /* This used to be the GERAN Iu mode support bit, but the newer spec
+ * releases say that it should not be used (always zero), however
+ * we will just ignore tha state of this bit. */
+ bitvec_get_uint(&bv, 1);
+
+ cm3->geran_feature_package_2 = bitvec_get_uint(&bv, 1);
+ cm3->gmsk_multislot_power_prof = bitvec_get_uint(&bv, 2);
+ cm3->psk8_multislot_power_prof = bitvec_get_uint(&bv, 2);
+
+ /* Release 6 starts here */
+ cm3->t_gsm_400_bands_supp.present = bitvec_get_uint(&bv, 1);
+ if (cm3->t_gsm_400_bands_supp.present) {
+ cm3->t_gsm_400_bands_supp.value = bitvec_get_uint(&bv, 2);
+ cm3->t_gsm_400_bands_supp.assoc_radio_cap =
+ bitvec_get_uint(&bv, 4);
+ }
+
+ /* This used to be T-GSM 900 associated radio capability, but the
+ * newer spec releases say that this bit should not be used, but if
+ * it is used by some MS anyway we must assume that there is data
+ * we have to override. */
+ if (bitvec_get_uint(&bv, 1))
+ bitvec_get_uint(&bv, 4);
+
+ cm3->dl_advanced_rx_perf = bitvec_get_uint(&bv, 2);
+ cm3->dtm_enhancements_cap = bitvec_get_uint(&bv, 1);
+
+ cm3->dtm_gprs_high_multislot_cap.present = bitvec_get_uint(&bv, 1);
+ if (cm3->dtm_gprs_high_multislot_cap.present) {
+ cm3->dtm_gprs_high_multislot_cap.mslot_class =
+ bitvec_get_uint(&bv, 3);
+ cm3->dtm_gprs_high_multislot_cap.offset_required =
+ bitvec_get_uint(&bv, 1);
+ cm3->dtm_gprs_high_multislot_cap.dtm_egprs_high_multislot_cap.
+ present = bitvec_get_uint(&bv, 1);
+ if (cm3->dtm_gprs_high_multislot_cap.
+ dtm_egprs_high_multislot_cap.present)
+ cm3->dtm_gprs_high_multislot_cap.
+ dtm_egprs_high_multislot_cap.mslot_class =
+ bitvec_get_uint(&bv, 3);
+ }
+
+ cm3->repeated_acch_capability = bitvec_get_uint(&bv, 1);
+
+ /* Release 7 starts here */
+ cm3->gsm_710_assoc_radio_cap.present = bitvec_get_uint(&bv, 1);
+ if (cm3->gsm_710_assoc_radio_cap.present)
+ cm3->gsm_710_assoc_radio_cap.value = bitvec_get_uint(&bv, 4);
+
+ cm3->t_gsm_810_assoc_radio_cap.present = bitvec_get_uint(&bv, 1);
+ if (cm3->t_gsm_810_assoc_radio_cap.present)
+ cm3->t_gsm_810_assoc_radio_cap.value = bitvec_get_uint(&bv, 4);
+
+ cm3->ciphering_mode_setting_cap = bitvec_get_uint(&bv, 1);
+ cm3->add_pos_cap = bitvec_get_uint(&bv, 1);
+
+ /* Release 8 starts here */
+ cm3->e_utra_fdd_supp = bitvec_get_uint(&bv, 1);
+ cm3->e_utra_tdd_supp = bitvec_get_uint(&bv, 1);
+ cm3->e_utra_meas_rep_supp = bitvec_get_uint(&bv, 1);
+ cm3->prio_resel_supp = bitvec_get_uint(&bv, 1);
+
+ /* Release 9 starts here */
+ cm3->utra_csg_cells_rep = bitvec_get_uint(&bv, 1);
+
+ cm3->vamos_level = bitvec_get_uint(&bv, 2);
+
+ /* Release 10 starts here */
+ cm3->tighter_capability = bitvec_get_uint(&bv, 2);
+ cm3->sel_ciph_dl_sacch = bitvec_get_uint(&bv, 1);
+
+ /* Release 11 starts here */
+ cm3->cs_ps_srvcc_geran_utra = bitvec_get_uint(&bv, 2);
+ cm3->cs_ps_srvcc_geran_eutra = bitvec_get_uint(&bv, 2);
+
+ cm3->geran_net_sharing = bitvec_get_uint(&bv, 1);
+ cm3->e_utra_wb_rsrq_meas_supp = bitvec_get_uint(&bv, 1);
+
+ /* Release 12 starts here */
+ cm3->er_band_support = bitvec_get_uint(&bv, 1);
+ cm3->utra_mult_band_ind_supp = bitvec_get_uint(&bv, 1);
+ cm3->e_utra_mult_band_ind_supp = bitvec_get_uint(&bv, 1);
+ cm3->extended_tsc_set_cap_supp = bitvec_get_uint(&bv, 1);
+
+ /* Late addition of a release 11 feature */
+ cm3->extended_earfcn_val_range = bitvec_get_uint(&bv, 1);
+
+ return 0;
+}
/*! @} */
diff --git a/src/gsm/gsm48_rest_octets.c b/src/gsm/gsm48_rest_octets.c
index 3c1b22af..77fd349b 100644
--- a/src/gsm/gsm48_rest_octets.c
+++ b/src/gsm/gsm48_rest_octets.c
@@ -46,8 +46,9 @@ int osmo_gsm48_rest_octets_si1_encode(uint8_t *data, uint8_t *nch_pos, int is180
if (nch_pos) {
bitvec_set_bit(&bv, H);
bitvec_set_uint(&bv, *nch_pos, 5);
- } else
+ } else {
bitvec_set_bit(&bv, L);
+ }
if (is1800_net)
bitvec_set_bit(&bv, L);
@@ -63,6 +64,7 @@ static inline bool append_eutran_neib_cell(struct bitvec *bv, const struct osmo_
uint8_t budget)
{
unsigned i, skip = 0;
+ size_t offset = *e_offset;
int16_t rem = budget - 6; /* account for mandatory stop bit and THRESH_E-UTRAN_high */
uint8_t earfcn_budget;
@@ -93,7 +95,7 @@ static inline bool append_eutran_neib_cell(struct bitvec *bv, const struct osmo_
/* now we can proceed with actually adding EARFCNs within adjusted budget limit */
for (i = 0; i < e->length; i++) {
if (e->arfcn[i] != OSMO_EARFCN_INVALID) {
- if (skip < *e_offset) {
+ if (skip < offset) {
skip++; /* ignore EARFCNs added on previous calls */
} else {
earfcn_budget = 17; /* compute budget per-EARFCN */
@@ -134,8 +136,9 @@ static inline bool append_eutran_neib_cell(struct bitvec *bv, const struct osmo_
/* E-UTRAN_PRIORITY: 3GPP TS 45.008*/
bitvec_set_bit(bv, 1);
bitvec_set_uint(bv, e->prio, 3);
- } else
+ } else {
bitvec_set_bit(bv, 0);
+ }
/* THRESH_E-UTRAN_high */
bitvec_set_uint(bv, e->thresh_hi, 5);
@@ -144,15 +147,17 @@ static inline bool append_eutran_neib_cell(struct bitvec *bv, const struct osmo_
/* THRESH_E-UTRAN_low: */
bitvec_set_bit(bv, 1);
bitvec_set_uint(bv, e->thresh_lo, 5);
- } else
+ } else {
bitvec_set_bit(bv, 0);
+ }
if (e->qrxlm_valid) {
/* E-UTRAN_QRXLEVMIN: */
bitvec_set_bit(bv, 1);
bitvec_set_uint(bv, e->qrxlm, 5);
- } else
+ } else {
bitvec_set_bit(bv, 0);
+ }
return true;
}
@@ -161,7 +166,7 @@ static inline void append_earfcn(struct bitvec *bv, const struct osmo_earfcn_si2
{
bool appended;
unsigned int old = bv->cur_bit; /* save current position to make rollback possible */
- int rem = budget - 25;
+ int rem = ((int)budget) - 40;
if (rem <= 0)
return;
@@ -189,8 +194,29 @@ static inline void append_earfcn(struct bitvec *bv, const struct osmo_earfcn_si2
/* Priority and E-UTRAN Parameters Description */
bitvec_set_bit(bv, 1);
- /* No Serving Cell Priority Parameters Descr. */
- bitvec_set_bit(bv, 0);
+ /* budget: 10 bits used above */
+
+ /* Serving Cell Priority Parameters Descr. is Present,
+ * see also: 3GPP TS 44.018, Table 10.5.2.33b.1 */
+ bitvec_set_bit(bv, 1);
+
+ /* GERAN_PRIORITY */
+ bitvec_set_uint(bv, 0, 3);
+
+ /* THRESH_Priority_Search */
+ bitvec_set_uint(bv, 0, 4);
+
+ /* THRESH_GSM_low */
+ bitvec_set_uint(bv, 0, 4);
+
+ /* H_PRIO */
+ bitvec_set_uint(bv, 0, 2);
+
+ /* T_Reselection */
+ bitvec_set_uint(bv, 0, 2);
+
+ /* budget: 26 bits used above */
+
/* No 3G Priority Parameters Description */
bitvec_set_bit(bv, 0);
/* E-UTRAN Parameters Description */
@@ -214,12 +240,16 @@ static inline void append_earfcn(struct bitvec *bv, const struct osmo_earfcn_si2
/* Repeated E-UTRAN Neighbour Cells */
bitvec_set_bit(bv, 1);
+ /* budget: 34 bits used above */
+
appended = append_eutran_neib_cell(bv, e, e_offset, rem);
if (!appended) { /* appending is impossible within current budget: rollback */
bv->cur_bit = old;
return;
}
+ /* budget: further 6 bits used below, totalling 40 bits */
+
/* stop bit - end of Repeated E-UTRAN Neighbour Cells sequence: */
bitvec_set_bit(bv, 0);
@@ -404,8 +434,9 @@ static inline void append_uarfcns(struct bitvec *bv, const uint16_t *uarfcn_list
if (i < uarfcn_length) {
cu = uarfcn_list[i];
st = i;
- } else
+ } else {
break;
+ }
}
/* stop bit - end of Repeated UTRAN FDD Neighbour Cells */
@@ -508,8 +539,9 @@ static void append_selection_params(struct bitvec *bv,
bitvec_set_uint(bv, sp->cell_resel_off, 6);
bitvec_set_uint(bv, sp->temp_offs, 3);
bitvec_set_uint(bv, sp->penalty_time, 5);
- } else
+ } else {
bitvec_set_bit(bv, L);
+ }
}
/* Append power offset to bitvec */
@@ -519,8 +551,9 @@ static void append_power_offset(struct bitvec *bv,
if (po->present) {
bitvec_set_bit(bv, H);
bitvec_set_uint(bv, po->power_offset, 2);
- } else
+ } else {
bitvec_set_bit(bv, L);
+ }
}
/* Append GPRS indicator to bitvec */
@@ -532,8 +565,9 @@ static void append_gprs_ind(struct bitvec *bv,
bitvec_set_uint(bv, gi->ra_colour, 3);
/* 0 == SI13 in BCCH Norm, 1 == SI13 sent on BCCH Ext */
bitvec_set_bit(bv, gi->si13_position);
- } else
+ } else {
bitvec_set_bit(bv, L);
+ }
}
/* Generate SI3 Rest Octests (Chapter 10.5.2.34 / Table 10.4.72) */
@@ -567,8 +601,9 @@ int osmo_gsm48_rest_octets_si3_encode(uint8_t *data, const struct osmo_gsm48_si_
if (si3->scheduling.present) {
bitvec_set_bit(&bv, H);
bitvec_set_uint(&bv, si3->scheduling.where, 3);
- } else
+ } else {
bitvec_set_bit(&bv, L);
+ }
/* GPRS Indicator */
append_gprs_ind(&bv, &si3->gprs_ind);
@@ -576,9 +611,9 @@ int osmo_gsm48_rest_octets_si3_encode(uint8_t *data, const struct osmo_gsm48_si_
/* 3G Early Classmark Sending Restriction. If H, then controlled by
* early_cm_ctrl above */
if (si3->early_cm_restrict_3g)
- bitvec_set_bit(&bv, H);
- else
bitvec_set_bit(&bv, L);
+ else
+ bitvec_set_bit(&bv, H);
if (si3->si2quater_indicator) {
bitvec_set_bit(&bv, H); /* indicator struct present */
@@ -618,22 +653,25 @@ int osmo_gsm48_rest_octets_si4_encode(uint8_t *data, const struct osmo_gsm48_si_
if (si4->lsa_params.present) {
bitvec_set_bit(&bv, H);
append_lsa_params(&bv, &si4->lsa_params);
- } else
+ } else {
bitvec_set_bit(&bv, L);
+ }
/* Cell Identity */
if (1) {
bitvec_set_bit(&bv, H);
bitvec_set_uint(&bv, si4->cell_id, 16);
- } else
+ } else {
bitvec_set_bit(&bv, L);
+ }
/* LSA ID Information */
if (0) {
bitvec_set_bit(&bv, H);
/* FIXME */
- } else
+ } else {
bitvec_set_bit(&bv, L);
+ }
} else {
/* L and break indicator */
bitvec_set_bit(&bv, L);
@@ -671,25 +709,29 @@ int osmo_gsm48_rest_octets_si6_encode(uint8_t *data, const struct osmo_gsm48_si6
if (in->pch_nch_info.call_priority_present) {
bitvec_set_bit(&bv, 1);
bitvec_set_uint(&bv, in->pch_nch_info.call_priority, 3);
- } else
+ } else {
bitvec_set_bit(&bv, 0);
+ }
bitvec_set_bit(&bv, !!in->pch_nch_info.nln_status_sacch);
- } else
+ } else {
bitvec_set_bit(&bv, L);
+ }
if (in->vbs_vgcs_options.present) {
bitvec_set_bit(&bv, H);
bitvec_set_bit(&bv, !!in->vbs_vgcs_options.inband_notifications);
bitvec_set_bit(&bv, !!in->vbs_vgcs_options.inband_pagings);
- } else
+ } else {
bitvec_set_bit(&bv, L);
+ }
if (in->dtm_support.present) {
bitvec_set_bit(&bv, H);
bitvec_set_uint(&bv, in->dtm_support.rac, 8);
bitvec_set_uint(&bv, in->dtm_support.max_lapdm, 3);
- } else
+ } else {
bitvec_set_bit(&bv, L);
+ }
if (in->band_indicator_1900)
bitvec_set_bit(&bv, H);
@@ -699,13 +741,127 @@ int osmo_gsm48_rest_octets_si6_encode(uint8_t *data, const struct osmo_gsm48_si6
if (in->gprs_ms_txpwr_max_ccch.present) {
bitvec_set_bit(&bv, H);
bitvec_set_uint(&bv, in->gprs_ms_txpwr_max_ccch.max_txpwr, 5);
- } else
+ } else {
bitvec_set_bit(&bv, L);
+ }
bitvec_spare_padding(&bv, (bv.data_len * 8) - 1);
return bv.data_len;
}
+
+static unsigned int decode_t3192(unsigned int t3192)
+{
+ /* See also 3GPP TS 44.060
+ Table 12.24.2: GPRS Cell Options information element details */
+ static const unsigned int decode_t3192_tbl[8] = {500, 1000, 1500, 0, 80, 120, 160, 200};
+ OSMO_ASSERT(t3192 <= 7);
+ return decode_t3192_tbl[t3192];
+}
+
+static unsigned int decode_drx_timer(unsigned int drx)
+{
+ static const unsigned int decode_drx_timer_tbl[8] = {0, 1, 2, 4, 8, 16, 32, 64};
+ OSMO_ASSERT(drx <= 7);
+ return decode_drx_timer_tbl[drx];
+}
+
+static int decode_gprs_cell_opt(struct osmo_gprs_cell_options *gco, struct bitvec *bv)
+{
+ gco->nmo = bitvec_get_uint(bv, 2);
+ gco->t3168 = (bitvec_get_uint(bv, 3) + 1) * 500;
+ gco->t3192 = decode_t3192(bitvec_get_uint(bv, 3));
+ gco->drx_timer_max = decode_drx_timer(bitvec_get_uint(bv, 3));
+
+ /* ACCESS_BURST_TYPE: */
+ bitvec_get_uint(bv, 1);
+ /* CONTROL_ACK_TYPE: */
+ gco->ctrl_ack_type_use_block = bitvec_get_uint(bv, 1);
+ gco->bs_cv_max = bitvec_get_uint(bv, 4);
+
+ if (bitvec_get_uint(bv, 1)) {
+ bitvec_get_uint(bv, 3); /* DEC */
+ bitvec_get_uint(bv, 3); /* INC */
+ bitvec_get_uint(bv, 3); /* MAX */
+ }
+
+ if (bitvec_get_uint(bv, 1)) {
+ int ext_len = bitvec_get_uint(bv, 6);
+ if (ext_len < 0)
+ return ext_len;
+ unsigned int cur_bit = bv->cur_bit;
+ /* Extension Information */
+ /* R99 extension: */
+ gco->ext_info.egprs_supported = bitvec_get_uint(bv, 1);
+ if (gco->ext_info.egprs_supported) {
+ gco->ext_info.use_egprs_p_ch_req = !bitvec_get_uint(bv, 1);
+ gco->ext_info.bep_period = bitvec_get_uint(bv, 4);
+ }
+ gco->ext_info.pfc_supported = bitvec_get_uint(bv, 1);
+ gco->ext_info.dtm_supported = bitvec_get_uint(bv, 1);
+ gco->ext_info.bss_paging_coordination = bitvec_get_uint(bv, 1);
+ /* REL-4 extension: */
+ gco->ext_info.ccn_active = bitvec_get_uint(bv, 1);
+ bitvec_get_uint(bv, 1); /* NW_EXT_UTBF */
+ bv->cur_bit = cur_bit + ext_len + 1;
+ }
+ return 0;
+}
+
+static void decode_gprs_pwr_ctrl_pars(struct osmo_gprs_power_ctrl_pars *pcp, struct bitvec *bv)
+{
+ pcp->alpha = bitvec_get_uint(bv, 4);
+ pcp->t_avg_w = bitvec_get_uint(bv,5);
+ pcp->t_avg_t = bitvec_get_uint(bv, 5);
+ pcp->pc_meas_chan = bitvec_get_uint(bv, 1);
+ pcp->n_avg_i = bitvec_get_uint(bv, 4);
+}
+
+/*! Decode SI13 Rest Octests (04.08 Chapter 10.5.2.37b).
+ * \param[out] si13 decoded SI13 rest octets
+ * \param[in] encoded SI13 rest octets
+ * \returns parsed bits on success, negative on error */
+int osmo_gsm48_rest_octets_si13_decode(struct osmo_gsm48_si13_info *si13, const uint8_t *data)
+{
+ struct osmo_gprs_cell_options *co = &si13->cell_opts;
+ struct osmo_gprs_power_ctrl_pars *pcp = &si13->pwr_ctrl_pars;
+ struct bitvec bv;
+ int rc;
+
+ memset(&bv, 0, sizeof(bv));
+ bv.data = (uint8_t *) data;
+ bv.data_len = 20;
+
+ memset(si13, 0, sizeof(*si13));
+
+
+ if (bitvec_get_bit_high(&bv) == H) {
+ si13->bcch_change_mark = bitvec_get_uint(&bv, 3);
+ si13->si_change_field = bitvec_get_uint(&bv, 4);
+ if (bitvec_get_uint(&bv, 1)) {
+ si13->bcch_change_mark = bitvec_get_uint(&bv, 2);
+ /* FIXME: implement parsing GPRS Mobile Allocation IE */
+ return -ENOTSUP;
+ }
+ if (bitvec_get_uint(&bv, 1)) {
+ /* PBCCH present in cell */
+ /* FIXME: parse not implemented */
+ return -ENOTSUP;
+ } else {
+ /* PBCCH not present in cell */
+ si13->rac = bitvec_get_uint(&bv, 8);
+ si13->spgc_ccch_sup = bitvec_get_uint(&bv, 1);
+ si13->prio_acc_thr = bitvec_get_uint(&bv, 3);
+ si13->net_ctrl_ord = bitvec_get_uint(&bv, 2);
+ if ((rc = decode_gprs_cell_opt(co, &bv)) < 0)
+ return rc;
+
+ decode_gprs_pwr_ctrl_pars(pcp, &bv);
+ }
+ }
+ return bv.cur_bit;
+}
+
/* GPRS Mobile Allocation as per TS 04.60 Chapter 12.10a:
< GPRS Mobile Allocation IE > ::=
< HSN : bit (6) >
@@ -730,8 +886,9 @@ static int append_gprs_mobile_alloc(struct bitvec *bv)
/* We want to use a RFL number list */
bitvec_set_bit(bv, 1);
/* FIXME: RFL number list */
- } else
+ } else {
bitvec_set_bit(bv, 0);
+ }
if (0) {
/* We want to use a MA_BITMAP */
@@ -743,8 +900,9 @@ static int append_gprs_mobile_alloc(struct bitvec *bv)
/* We want to provide an ARFCN index list */
bitvec_set_bit(bv, 1);
/* FIXME */
- } else
+ } else {
bitvec_set_bit(bv, 0);
+ }
}
return 0;
}
@@ -862,24 +1020,20 @@ static int append_gprs_cell_opt(struct bitvec *bv,
} else {
/* extension information */
bitvec_set_bit(bv, 1);
+ /* R99 extension: */
if (!gco->ext_info.egprs_supported) {
/* 6bit length of extension */
- bitvec_set_uint(bv, (1 + 3)-1, 6);
+ bitvec_set_uint(bv, (1 + 5)-1, 6);
/* EGPRS supported in the cell */
bitvec_set_bit(bv, 0);
} else {
/* 6bit length of extension */
- bitvec_set_uint(bv, (1 + 5 + 3)-1, 6);
+ bitvec_set_uint(bv, (1 + 5 + 5)-1, 6);
/* EGPRS supported in the cell */
bitvec_set_bit(bv, 1);
- /* 1bit EGPRS PACKET CHANNEL REQUEST */
- if (gco->supports_egprs_11bit_rach == 0) {
- bitvec_set_bit(bv,
- gco->ext_info.use_egprs_p_ch_req);
- } else {
- bitvec_set_bit(bv, 0);
- }
+ /* 1bit EGPRS PACKET CHANNEL REQUEST (inverted logic) */
+ bitvec_set_bit(bv, !gco->ext_info.use_egprs_p_ch_req);
/* 4bit BEP PERIOD */
bitvec_set_uint(bv, gco->ext_info.bep_period, 4);
@@ -887,6 +1041,10 @@ static int append_gprs_cell_opt(struct bitvec *bv,
bitvec_set_bit(bv, gco->ext_info.pfc_supported);
bitvec_set_bit(bv, gco->ext_info.dtm_supported);
bitvec_set_bit(bv, gco->ext_info.bss_paging_coordination);
+
+ /* REL-4 extension: */
+ bitvec_set_bit(bv, gco->ext_info.ccn_active);
+ bitvec_set_bit(bv, 0); /* NW_EXT_UTBF disabled */
}
return 0;
@@ -973,15 +1131,17 @@ void osmo_gsm48_rest_octets_si3_decode(struct osmo_gsm48_si_ro_info *si3, const
sp->cell_resel_off = bitvec_get_uint(&bv, 6);
sp->temp_offs = bitvec_get_uint(&bv, 3);
sp->penalty_time = bitvec_get_uint(&bv, 5);
- } else
+ } else {
sp->present = 0;
+ }
/* Optional Power Offset */
if (bitvec_get_bit_high(&bv) == H) {
po->present = 1;
po->power_offset = bitvec_get_uint(&bv, 2);
- } else
+ } else {
po->present = 0;
+ }
/* System Information 2ter Indicator */
if (bitvec_get_bit_high(&bv) == H)
@@ -999,26 +1159,71 @@ void osmo_gsm48_rest_octets_si3_decode(struct osmo_gsm48_si_ro_info *si3, const
if (bitvec_get_bit_high(&bv) == H) {
si3->scheduling.present = 1;
si3->scheduling.where = bitvec_get_uint(&bv, 3);
- } else
+ } else {
si3->scheduling.present = 0;
+ }
/* GPRS Indicator */
if (bitvec_get_bit_high(&bv) == H) {
gi->present = 1;
gi->ra_colour = bitvec_get_uint(&bv, 3);
gi->si13_position = bitvec_get_uint(&bv, 1);
- } else
+ } else {
gi->present = 0;
+ }
/* 3G Early Classmark Sending Restriction. If H, then controlled by
* early_cm_ctrl above */
if (bitvec_get_bit_high(&bv) == H)
- si3->early_cm_restrict_3g = 1;
- else
si3->early_cm_restrict_3g = 0;
+ else
+ si3->early_cm_restrict_3g = 1;
if (bitvec_get_bit_high(&bv) == H)
si3->si2quater_indicator = 1;
else
si3->si2quater_indicator = 0;
}
+
+
+void osmo_gsm48_rest_octets_si4_decode(struct osmo_gsm48_si_ro_info *si4, const uint8_t *data, int len)
+{
+ struct osmo_gsm48_si_selection_params *sp = &si4->selection_params;
+ struct osmo_gsm48_si_power_offset *po = &si4->power_offset;
+ struct osmo_gsm48_si3_gprs_ind *gi = &si4->gprs_ind;
+ struct bitvec bv;
+
+ memset(&bv, 0, sizeof(bv));
+ bv.data = (uint8_t *) data;
+ bv.data_len = len;
+
+ memset(si4, 0, sizeof(*si4));
+
+ /* Optional Selection Parameters */
+ if (bitvec_get_bit_high(&bv) == H) {
+ sp->present = 1;
+ sp->cbq = bitvec_get_uint(&bv, 1);
+ sp->cell_resel_off = bitvec_get_uint(&bv, 6);
+ sp->temp_offs = bitvec_get_uint(&bv, 3);
+ sp->penalty_time = bitvec_get_uint(&bv, 5);
+ } else {
+ sp->present = 0;
+ }
+
+ /* Optional Power Offset */
+ if (bitvec_get_bit_high(&bv) == H) {
+ po->present = 1;
+ po->power_offset = bitvec_get_uint(&bv, 2);
+ } else {
+ po->present = 0;
+ }
+
+ /* GPRS Indicator */
+ if (bitvec_get_bit_high(&bv) == H) {
+ gi->present = 1;
+ gi->ra_colour = bitvec_get_uint(&bv, 3);
+ gi->si13_position = bitvec_get_uint(&bv, 1);
+ } else {
+ gi->present = 0;
+ }
+}
diff --git a/src/gsm/gsm_04_08_gprs.c b/src/gsm/gsm_04_08_gprs.c
index 608fa8c1..80325939 100644
--- a/src/gsm/gsm_04_08_gprs.c
+++ b/src/gsm/gsm_04_08_gprs.c
@@ -65,7 +65,7 @@ const struct value_string gsm48_gmm_cause_names_[] = {
{ GMM_CAUSE_SEM_INCORR_MSG, "Semantically incorrect message" },
{ GMM_CAUSE_INV_MAND_INFO, "Invalid mandatory information" },
{ GMM_CAUSE_MSGT_NOTEXIST_NOTIMPL,
- "Message type non-existant or not implemented" },
+ "Message type non-existent or not implemented" },
{ GMM_CAUSE_MSGT_INCOMP_P_STATE,
"Message type not compatible with protocol state" },
{ GMM_CAUSE_IE_NOTEXIST_NOTIMPL,
@@ -105,7 +105,7 @@ const struct value_string gsm48_gsm_cause_names_[] = {
{ GSM_CAUSE_SEM_INCORR_MSG, "Semantically incorrect message" },
{ GSM_CAUSE_INV_MAND_INFO, "Invalid mandatory information" },
{ GSM_CAUSE_MSGT_NOTEXIST_NOTIMPL,
- "Message type non-existant or not implemented" },
+ "Message type non-existent or not implemented" },
{ GSM_CAUSE_MSGT_INCOMP_P_STATE,
"Message type not compatible with protocol state" },
{ GSM_CAUSE_IE_NOTEXIST_NOTIMPL,
diff --git a/src/gsm/gsm_utils.c b/src/gsm/gsm_utils.c
index ae77a9dc..06b24068 100644
--- a/src/gsm/gsm_utils.c
+++ b/src/gsm/gsm_utils.c
@@ -19,10 +19,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
/*! \mainpage libosmogsm Documentation
@@ -96,7 +92,7 @@
#include <time.h>
#include <unistd.h>
-#include "../../config.h"
+#include "config.h"
#if (!EMBEDDED)
/* FIXME: this can be removed once we bump glibc requirements to 2.25: */
@@ -324,17 +320,18 @@ int gsm_septet_encode(uint8_t *result, const char *data)
* \param[in] septet_len Length of \a rdata
* \param[in] padding padding bits at start
* \returns number of bytes used in \a result */
-int gsm_septets2octets(uint8_t *result, const uint8_t *rdata, uint8_t septet_len, uint8_t padding)
+int gsm_septet_pack(uint8_t *result, const uint8_t *rdata, size_t septet_len, uint8_t padding)
{
int i = 0, z = 0;
uint8_t cb, nb;
int shift = 0;
- uint8_t *data = calloc(septet_len + 1, sizeof(uint8_t));
+ uint8_t *data = malloc(septet_len + 1);
if (padding) {
shift = 7 - padding;
/* the first zero is needed for padding */
memcpy(data + 1, rdata, septet_len);
+ data[0] = 0x00;
septet_len++;
} else
memcpy(data, rdata, septet_len);
@@ -369,6 +366,12 @@ int gsm_septets2octets(uint8_t *result, const uint8_t *rdata, uint8_t septet_len
return z;
}
+/*! Backwards compatibility wrapper for gsm_septets_pack(), deprecated. */
+int gsm_septets2octets(uint8_t *result, const uint8_t *rdata, uint8_t septet_len, uint8_t padding)
+{
+ return gsm_septet_pack(result, rdata, septet_len, padding);
+}
+
/*! GSM 7-bit alphabet TS 03.38 6.2.1 Character packing
* \param[out] result Caller-provided output buffer
* \param[in] n Maximum length of \a result in bytes
@@ -382,7 +385,7 @@ int gsm_7bit_encode_n(uint8_t *result, size_t n, const char *data, int *octets)
size_t max_septets = n * 8 / 7;
/* prepare for the worst case, every character expanding to two bytes */
- uint8_t *rdata = calloc(strlen(data) * 2, sizeof(uint8_t));
+ uint8_t *rdata = malloc(strlen(data) * 2);
y = gsm_septet_encode(rdata, data);
if (y > max_septets) {
@@ -393,7 +396,7 @@ int gsm_7bit_encode_n(uint8_t *result, size_t n, const char *data, int *octets)
y = max_septets;
}
- o = gsm_septets2octets(result, rdata, y, 0);
+ o = gsm_septet_pack(result, rdata, y, 0);
if (octets)
*octets = o;
@@ -487,7 +490,7 @@ int osmo_get_rand_id(uint8_t *out, size_t len)
* \param[out] buf Pre-allocated bufer for storing IE
* \returns Number of bytes filled in buf
*/
-size_t gsm0858_rsl_ul_meas_enc(struct gsm_meas_rep_unidir *mru, bool dtxd_used,
+size_t gsm0858_rsl_ul_meas_enc(const struct gsm_meas_rep_unidir *mru, bool dtxd_used,
uint8_t *buf)
{
buf[0] = dtxd_used ? (1 << 6) : 0;
@@ -882,10 +885,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)
diff --git a/src/gsm/ipa.c b/src/gsm/ipa.c
index 7a26ba45..447e8e3d 100644
--- a/src/gsm/ipa.c
+++ b/src/gsm/ipa.c
@@ -388,7 +388,7 @@ struct msgb *ipa_ccm_make_id_resp(const struct ipaccess_unit *dev,
tag = msgb_put(msg, 3 + strlen(str) + 1);
tag[0] = 0x00;
tag[1] = 1 + strlen(str) + 1;
- tag[2] = ies_req[1];
+ tag[2] = ies_req[i];
memcpy(tag + 3, str, strlen(str) + 1);
}
ipa_prepend_header(msg, IPAC_PROTO_IPACCESS);
@@ -412,10 +412,14 @@ struct msgb *ipa_ccm_make_id_resp_from_req(const struct ipaccess_unit *dev,
/* build a array of the IEIs */
while (len >= 2) {
uint8_t t_len, t_tag;
- len -= 2;
+ len -= 2; /* subtract the length of the two bytes read below */
t_len = *cur++;
t_tag = *cur++;
+ /* as the 'tag' is included in the length of t_len, this cannot happen */
+ if (t_len == 0)
+ break;
+
if (t_len > len + 1) {
LOGP(DLINP, LOGL_ERROR, "IPA CCM tag 0x%02x does not fit\n", t_tag);
break;
@@ -423,13 +427,14 @@ struct msgb *ipa_ccm_make_id_resp_from_req(const struct ipaccess_unit *dev,
ies[num_ies++] = t_tag;
- cur += t_len;
+ /* we need to subtract one from t_len to account for the tag */
+ cur += t_len - 1;
/* prevent any unsigned integer underflow due to somebody sending us
* messages with wrong length values */
if (len <= t_len)
- len -= t_len;
- else
len = 0;
+ else
+ len -= t_len - 1;
}
return ipa_ccm_make_id_resp(dev, ies, num_ies);
}
diff --git a/src/gsm/iuup.c b/src/gsm/iuup.c
new file mode 100644
index 00000000..16a6f5e0
--- /dev/null
+++ b/src/gsm/iuup.c
@@ -0,0 +1,1064 @@
+/*! \file iu_up.c
+ * IuUP (Iu User Plane) according to 3GPP TS 25.415 */
+/*
+ * (C) 2017 by Harald Welte <laforge@gnumonks.org>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <errno.h>
+#include <inttypes.h>
+
+#include <osmocom/core/crc8gen.h>
+#include <osmocom/core/crc16gen.h>
+#include <osmocom/core/fsm.h>
+#include <osmocom/core/prim.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/core/logging.h>
+
+#include <osmocom/gsm/prim.h>
+#include <osmocom/gsm/protocol/gsm_25_415.h>
+#include <osmocom/gsm/iuup.h>
+
+/***********************************************************************
+ * CRC Calculation
+ ***********************************************************************/
+
+/* Section 6.6.3.8 Header CRC */
+const struct osmo_crc8gen_code iuup_hdr_crc_code = {
+ .bits = 6,
+ .poly = 47,
+ .init = 0,
+ .remainder = 0,
+};
+
+/* Section 6.6.3.9 Payload CRC */
+const struct osmo_crc16gen_code iuup_data_crc_code = {
+ .bits = 10,
+ .poly = 563,
+ .init = 0,
+ .remainder = 0,
+};
+
+static int iuup_get_payload_offset(const uint8_t *iuup_pdu)
+{
+ uint8_t pdu_type = iuup_pdu[0] >> 4;
+ switch (pdu_type) {
+ case 0:
+ case 14:
+ return 4;
+ case 1:
+ return 3;
+ default:
+ return -1;
+ }
+}
+
+int osmo_iuup_compute_payload_crc(const uint8_t *iuup_pdu, unsigned int pdu_len)
+{
+ ubit_t buf[1024*8];
+ uint8_t pdu_type;
+ int offset, payload_len_bytes;
+
+ if (pdu_len < 1)
+ return -1;
+
+ pdu_type = iuup_pdu[0] >> 4;
+
+ /* Type 1 has no CRC */
+ if (pdu_type == 1)
+ return 0;
+
+ offset = iuup_get_payload_offset(iuup_pdu);
+ if (offset < 0)
+ return offset;
+
+ if (pdu_len < offset)
+ return -1;
+
+ payload_len_bytes = pdu_len - offset;
+ osmo_pbit2ubit(buf, iuup_pdu+offset, payload_len_bytes*8);
+ return osmo_crc16gen_compute_bits(&iuup_data_crc_code, buf, payload_len_bytes*8);
+}
+
+int osmo_iuup_compute_header_crc(const uint8_t *iuup_pdu, unsigned int pdu_len)
+{
+ ubit_t buf[2*8];
+
+ if (pdu_len < 2)
+ return -1;
+
+ osmo_pbit2ubit(buf, iuup_pdu, 2*8);
+ return osmo_crc8gen_compute_bits(&iuup_hdr_crc_code, buf, 2*8);
+}
+
+/***********************************************************************
+ * Internal State / FSM (Annex B)
+ ***********************************************************************/
+
+#define S(x) (1 << (x))
+
+#define IUUP_TIMER_INIT 1
+#define IUUP_TIMER_TA 2
+#define IUUP_TIMER_RC 3
+
+struct osmo_timer_nt {
+ uint32_t n; /* number of repetitions */
+ struct osmo_iuup_tnl_prim *retrans_itp;
+ struct osmo_timer_list timer;
+};
+
+struct osmo_iuup_instance {
+ struct osmo_iuup_rnl_config config;
+ struct osmo_fsm_inst *fi;
+
+ uint8_t mode_version;
+
+ struct {
+ struct osmo_timer_nt init;
+ struct osmo_timer_nt ta;
+ struct osmo_timer_nt rc;
+ } timer;
+ /* call-back function to pass primitives up to the user */
+ osmo_prim_cb user_prim_cb;
+ void *user_prim_priv;
+ osmo_prim_cb transport_prim_cb;
+ void *transport_prim_priv;
+ uint8_t type14_fn; /* 2 bits */
+};
+
+enum iuup_fsm_state {
+ IUUP_FSM_ST_NULL,
+ IUUP_FSM_ST_INIT,
+ IUUP_FSM_ST_TrM_DATA_XFER_READY,
+ IUUP_FSM_ST_SMpSDU_DATA_XFER_READY,
+};
+
+enum iuup_fsm_event {
+ IUUP_FSM_EVT_IUUP_CONFIG_REQ,
+ IUUP_FSM_EVT_IUUP_DATA_REQ,
+ IUUP_FSM_EVT_IUUP_DATA_IND,
+ IUUP_FSM_EVT_IUUP_STATUS_REQ,
+ IUUP_FSM_EVT_IUUP_STATUS_IND,
+ IUUP_FSM_EVT_SSASAR_UNITDATA_REQ,
+ IUUP_FSM_EVT_SSASAR_UNITDATA_IND,
+ IUUP_FSM_EVT_IUUP_UNITDATA_REQ,
+ IUUP_FSM_EVT_IUUP_UNITDATA_IND,
+ IUUP_FSM_EVT_INIT,
+ IUUP_FSM_EVT_LAST_INIT_ACK,
+ IUUP_FSM_EVT_INIT_NACK,
+};
+
+static const struct value_string iuup_fsm_event_names[] = {
+ { IUUP_FSM_EVT_IUUP_CONFIG_REQ, "IuUP-CONFIG-req" },
+ { IUUP_FSM_EVT_IUUP_DATA_REQ, "IuUP-DATA-req" },
+ { IUUP_FSM_EVT_IUUP_DATA_IND, "IuUP-DATA-ind" },
+ { IUUP_FSM_EVT_IUUP_STATUS_REQ, "IuUP-STATUS-req" },
+ { IUUP_FSM_EVT_IUUP_STATUS_IND, "IuUP-STATUS-ind" },
+ { IUUP_FSM_EVT_SSASAR_UNITDATA_REQ, "SSSAR-UNITDATA-req" },
+ { IUUP_FSM_EVT_SSASAR_UNITDATA_IND, "SSSAR-UNITDATA-ind" },
+ { IUUP_FSM_EVT_IUUP_UNITDATA_REQ, "IuUP-UNITDATA-req" },
+ { IUUP_FSM_EVT_IUUP_UNITDATA_IND, "IuUP-UNITDATA-ind" },
+ { IUUP_FSM_EVT_INIT, "INIT" },
+ { IUUP_FSM_EVT_LAST_INIT_ACK, "LAST_INIT_ACK" },
+ { IUUP_FSM_EVT_INIT_NACK, "INIT_NACK" },
+ { 0, NULL }
+};
+
+static inline uint8_t iuup_get_pdu_type(const uint8_t *data)
+{
+ return data[0] >> 4;
+}
+
+static inline uint8_t iuup_get_hdr_crc(const uint8_t *data)
+{
+ return data[2] >> 2;
+}
+
+/* Helper functions to store non-packed structs in msgb so that pointers are properly aligned: */
+#define IUUP_MSGB_SIZE 4096
+#define PTR_ALIGNMENT_BYTES 8
+#define IUUP_MSGB_HEADROOM_MIN_REQUIRED (OSMO_MAX(sizeof(struct osmo_iuup_tnl_prim), sizeof(struct osmo_iuup_rnl_prim)) + (PTR_ALIGNMENT_BYTES - 1))
+static inline struct msgb *osmo_iuup_msgb_alloc_c(void *ctx, size_t size)
+{
+ OSMO_ASSERT(size > IUUP_MSGB_HEADROOM_MIN_REQUIRED);
+ return msgb_alloc_headroom_c(ctx, size, IUUP_MSGB_HEADROOM_MIN_REQUIRED, "iuup-msgb");
+}
+
+/* push data so that the resulting pointer to write to is aligned to 8 byte */
+static inline __attribute__((assume_aligned(PTR_ALIGNMENT_BYTES)))
+unsigned char *aligned_msgb_push(struct msgb *msg, unsigned int len)
+{
+ uint8_t *ptr = (msgb_data(msg) - len);
+ size_t extra_size = ((uintptr_t)ptr & (PTR_ALIGNMENT_BYTES - 1));
+
+ return msgb_push(msg, len + extra_size);
+}
+
+struct osmo_iuup_rnl_prim *osmo_iuup_rnl_prim_alloc(void *ctx, unsigned int primitive, unsigned int operation, unsigned int size)
+{
+ struct msgb *msg;
+ struct osmo_iuup_rnl_prim *irp;
+
+ msg = osmo_iuup_msgb_alloc_c(ctx, size);
+ irp = (struct osmo_iuup_rnl_prim *)aligned_msgb_push(msg, sizeof(*irp));
+ osmo_prim_init(&irp->oph, SAP_IUUP_RNL, primitive, operation, msg);
+ return irp;
+}
+
+struct osmo_iuup_tnl_prim *osmo_iuup_tnl_prim_alloc(void *ctx, unsigned int primitive, unsigned int operation, unsigned int size)
+{
+ struct msgb *msg;
+ struct osmo_iuup_tnl_prim *itp;
+
+ msg = osmo_iuup_msgb_alloc_c(ctx, size);
+ itp = (struct osmo_iuup_tnl_prim *) aligned_msgb_push(msg, sizeof(*itp));
+ osmo_prim_init(&itp->oph, SAP_IUUP_TNL, primitive, operation, msg);
+ return itp;
+}
+
+/* 6.6.2.3.2 */
+static struct osmo_iuup_tnl_prim *itp_ctrl_ack_alloc(struct osmo_iuup_instance *iui, enum iuup_procedure proc_ind, uint8_t fn)
+{
+ struct osmo_iuup_tnl_prim *itp;
+ struct iuup_ctrl_ack *ack;
+ itp = osmo_iuup_tnl_prim_alloc(iui, OSMO_IUUP_TNL_UNITDATA, PRIM_OP_REQUEST, IUUP_MSGB_SIZE);
+ itp->oph.msg->l2h = msgb_put(itp->oph.msg, sizeof(struct iuup_ctrl_ack));
+ ack = (struct iuup_ctrl_ack *) msgb_l2(itp->oph.msg);
+ *ack = (struct iuup_ctrl_ack){
+ .hdr = {
+ .frame_nr = fn,
+ .ack_nack = IUUP_AN_ACK,
+ .pdu_type = IUUP_PDU_T_CONTROL,
+ .proc_ind = proc_ind,
+ .mode_version = iui->mode_version,
+ .payload_crc_hi = 0,
+ .header_crc = 0,
+ .payload_crc_lo = 0,
+ },
+ };
+ ack->hdr.header_crc = osmo_iuup_compute_header_crc(msgb_l2(itp->oph.msg), msgb_l2len(itp->oph.msg));
+ return itp;
+}
+
+/* 6.6.2.3.3 */
+static struct osmo_iuup_tnl_prim *tnp_ctrl_nack_alloc(struct osmo_iuup_instance *iui, enum iuup_procedure proc_ind, enum iuup_error_cause error_cause, uint8_t fn)
+{
+ struct osmo_iuup_tnl_prim *itp;
+ struct iuup_ctrl_nack *nack;
+ itp = osmo_iuup_tnl_prim_alloc(iui, OSMO_IUUP_TNL_UNITDATA, PRIM_OP_REQUEST, IUUP_MSGB_SIZE);
+ itp->oph.msg->l2h = msgb_put(itp->oph.msg, sizeof(struct iuup_ctrl_nack));
+ nack = (struct iuup_ctrl_nack *) msgb_l2(itp->oph.msg);
+ *nack = (struct iuup_ctrl_nack){
+ .hdr = {
+ .frame_nr = fn,
+ .ack_nack = IUUP_AN_NACK,
+ .pdu_type = IUUP_PDU_T_CONTROL,
+ .proc_ind = proc_ind,
+ .mode_version = iui->mode_version,
+ .payload_crc_hi = 0,
+ .header_crc = 0,
+ .payload_crc_lo = 0,
+ },
+ .spare = 0,
+ .error_cause = error_cause,
+ };
+ nack->hdr.header_crc = osmo_iuup_compute_header_crc(msgb_l2(itp->oph.msg), msgb_l2len(itp->oph.msg));
+ return itp;
+}
+
+/* 6.6.2.3.4.1 */
+static struct osmo_iuup_tnl_prim *tnp_ctrl_init_alloc(struct osmo_iuup_instance *iui)
+{
+ struct osmo_iuup_tnl_prim *itp;
+ struct iuup_pdutype14_hdr *hdr;
+ struct iuup_ctrl_init_hdr *ihdr;
+ struct iuup_ctrl_init_rfci_hdr *ihdr_rfci;
+ struct iuup_ctrl_init_tail *itail;
+ unsigned int i, j;
+ uint16_t payload_crc;
+ uint8_t rfci_cnt;
+ struct msgb *msg;
+
+ itp = osmo_iuup_tnl_prim_alloc(iui, OSMO_IUUP_TNL_UNITDATA, PRIM_OP_REQUEST, IUUP_MSGB_SIZE);
+ msg = itp->oph.msg;
+
+ msg->l2h = msgb_put(msg, sizeof(*hdr));
+ hdr = (struct iuup_pdutype14_hdr *)msgb_l2(msg);
+ hdr->frame_nr = iui->type14_fn++;
+ hdr->ack_nack = IUUP_AN_PROCEDURE;
+ hdr->pdu_type = IUUP_PDU_T_CONTROL;
+ hdr->proc_ind = IUUP_PROC_INIT;
+ hdr->mode_version = 0; /* Use here the minimum version required to negotiate */
+ hdr->header_crc = osmo_iuup_compute_header_crc(msgb_l2(msg), msgb_l2len(msg));
+
+ ihdr = (struct iuup_ctrl_init_hdr *)msgb_put(msg, sizeof(*ihdr));
+ ihdr->chain_ind = 0; /* this frame is the last frame for the procedure. TODO: support several */
+ ihdr->num_subflows_per_rfci = iui->config.num_subflows;
+ ihdr->ti = iui->config.IPTIs_present ? 1 : 0;
+ ihdr->spare = 0;
+
+ /* RFCI + subflow size part: */
+ rfci_cnt = 0;
+ for (i = 0; i < ARRAY_SIZE(iui->config.rfci); i++) {
+ bool last;
+ uint8_t len_size;
+ struct osmo_iuup_rfci *rfci = &iui->config.rfci[i];
+ if (!rfci->used)
+ continue;
+ rfci_cnt++;
+ last = (rfci_cnt == iui->config.num_rfci);
+
+ len_size = 1;
+ for (j = 0; j < iui->config.num_subflows; j++) {
+ if (rfci->subflow_sizes[j] > UINT8_MAX)
+ len_size = 2;
+ }
+
+ ihdr_rfci = (struct iuup_ctrl_init_rfci_hdr *)msgb_put(msg, sizeof(*ihdr_rfci) + len_size * iui->config.num_subflows);
+ ihdr_rfci->rfci = rfci->id;
+ ihdr_rfci->li = len_size - 1;
+ ihdr_rfci->lri = last;
+ if (len_size == 2) {
+ uint16_t *buf = (uint16_t *)&ihdr_rfci->subflow_length[0];
+ for (j = 0; j < iui->config.num_subflows; j++)
+ osmo_store16be(rfci->subflow_sizes[j], buf++);
+ } else {
+ for (j = 0; j < iui->config.num_subflows; j++)
+ ihdr_rfci->subflow_length[j] = rfci->subflow_sizes[j];
+ }
+ /* early loop termination: */
+ if (last)
+ break;
+ }
+ /* Sanity check: */
+ if (rfci_cnt != iui->config.num_rfci) {
+ LOGP(DLIUUP, LOGL_ERROR, "rfci_cnt %u != num_rfci %u\n",
+ rfci_cnt, iui->config.num_rfci);
+ msgb_free(msg);
+ return NULL;
+ }
+
+ if (iui->config.IPTIs_present) {
+ uint8_t num_bytes = (iui->config.num_rfci + 1) / 2;
+ uint8_t *buf = msgb_put(msg, num_bytes);
+ rfci_cnt = 0;
+ for (i = 0; i < ARRAY_SIZE(iui->config.rfci); i++) {
+ struct osmo_iuup_rfci *rfci = &iui->config.rfci[i];
+ if (!rfci->used)
+ continue;
+ if (!(rfci_cnt & 0x01)) /* is even: */
+ buf[rfci_cnt / 2] = (((uint8_t)rfci->IPTI) << 4);
+ else
+ buf[rfci_cnt / 2] |= (rfci->IPTI & 0x0F);
+ rfci_cnt++;
+ /* early loop termination: */
+ if (rfci_cnt == iui->config.num_rfci)
+ break;
+ }
+ }
+
+ itail = (struct iuup_ctrl_init_tail *)msgb_put(msg, sizeof(*itail));
+ osmo_store16be(iui->config.supported_versions_mask, &itail->versions_supported);
+ itail->spare = 0;
+ itail->data_pdu_type = iui->config.data_pdu_type;
+
+ payload_crc = osmo_iuup_compute_payload_crc(msgb_l2(msg), msgb_l2len(msg));
+ hdr->payload_crc_hi = (payload_crc >> 8) & 0x03;
+ hdr->payload_crc_lo = payload_crc & 0xff;
+
+
+ return itp;
+}
+
+static struct osmo_iuup_rnl_prim *irp_init_ind_alloc(struct osmo_iuup_instance *iui)
+{
+ struct osmo_iuup_rnl_prim *irp;
+
+ irp = osmo_iuup_rnl_prim_alloc(iui, OSMO_IUUP_RNL_STATUS, PRIM_OP_INDICATION, IUUP_MSGB_SIZE);
+ irp->u.status.procedure = IUUP_PROC_INIT;
+ irp->u.status.u.initialization.mode_version = iui->mode_version;
+ irp->u.status.u.initialization.data_pdu_type = iui->config.data_pdu_type;
+ irp->u.status.u.initialization.num_subflows = iui->config.num_subflows;
+ irp->u.status.u.initialization.num_rfci = iui->config.num_rfci;
+ irp->u.status.u.initialization.IPTIs_present = iui->config.IPTIs_present;
+ memcpy(irp->u.status.u.initialization.rfci, iui->config.rfci, sizeof(iui->config.rfci));
+ return irp;
+}
+
+/* transform a RNL data primitive into a TNL data primitive (down the stack) */
+static struct osmo_iuup_tnl_prim *rnl_to_tnl_data(struct osmo_iuup_instance *iui,
+ struct osmo_iuup_rnl_prim *irp)
+{
+ struct osmo_iuup_tnl_prim *itp;
+ struct osmo_iuup_rnl_data dt;
+ struct msgb *msg;
+ uint16_t payload_crc;
+ struct iuup_pdutype0_hdr *h0;
+ struct iuup_pdutype1_hdr *h1;
+
+ OSMO_ASSERT(OSMO_PRIM_HDR(&irp->oph) == OSMO_PRIM(OSMO_IUUP_RNL_DATA, PRIM_OP_REQUEST));
+
+ msg = irp->oph.msg;
+ dt = irp->u.data;
+
+ /* pull up to the IuUP payload and push a new primitive header in front */
+ msgb_pull_to_l3(msg);
+
+ /* push the PDU TYPE 0 / 1 header in front of the payload */
+ switch (iui->config.data_pdu_type) {
+ case 0:
+ msg->l2h = msgb_push(msg, sizeof(*h0));
+ h0 = (struct iuup_pdutype0_hdr *)msg->l2h;
+ h0->frame_nr = dt.frame_nr;
+ h0->pdu_type = IUUP_PDU_T_DATA_CRC;
+ h0->rfci = dt.rfci;
+ h0->fqc = dt.fqc;
+ h0->header_crc = osmo_iuup_compute_header_crc(msgb_l2(msg), msgb_l2len(msg));
+ payload_crc = osmo_iuup_compute_payload_crc(msgb_l2(msg), msgb_l2len(msg));
+ h0->payload_crc_hi = (payload_crc >> 8) & 0x03;
+ h0->payload_crc_lo = payload_crc & 0xff;
+ break;
+ case 1:
+ msg->l2h = msgb_push(msg, sizeof(*h1));
+ h1 = (struct iuup_pdutype1_hdr *)msg->l2h;
+ h1->frame_nr = dt.frame_nr;
+ h1->pdu_type = IUUP_PDU_T_DATA_NOCRC;
+ h1->rfci = dt.rfci;
+ h1->fqc = dt.fqc;
+ h1->header_crc = osmo_iuup_compute_header_crc(msgb_l2(msg), msgb_l2len(msg));
+ h1->spare = 0;
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+
+ /* Avoid allocating irp out of 8byte-aligned address, Asan is not happy with it */
+ itp = (struct osmo_iuup_tnl_prim *) aligned_msgb_push(msg, sizeof(*itp));
+ osmo_prim_init(&itp->oph, SAP_IUUP_TNL, OSMO_IUUP_TNL_UNITDATA, PRIM_OP_REQUEST, msg);
+
+ return itp;
+}
+
+/* transform a TNL primitive into a RNL primitive (up the stack) */
+static struct osmo_iuup_rnl_prim *tnl_to_rnl_data(struct osmo_iuup_tnl_prim *itp)
+{
+ struct msgb *msg;
+ struct iuup_pdutype0_hdr *h0;
+ struct iuup_pdutype1_hdr *h1;
+ struct osmo_iuup_rnl_data dt;
+ struct osmo_iuup_rnl_prim *irp;
+
+ msg = itp->oph.msg;
+
+ OSMO_ASSERT(OSMO_PRIM_HDR(&itp->oph) == OSMO_PRIM(OSMO_IUUP_TNL_UNITDATA, PRIM_OP_INDICATION));
+
+ switch (iuup_get_pdu_type(msgb_l2(msg))) {
+ case IUUP_PDU_T_DATA_CRC:
+ h0 = (struct iuup_pdutype0_hdr *) msgb_l2(msg);
+ dt.rfci = h0->rfci;
+ dt.frame_nr = h0->frame_nr;
+ dt.fqc = h0->fqc;
+ break;
+ case IUUP_PDU_T_DATA_NOCRC:
+ h1 = (struct iuup_pdutype1_hdr *) msgb_l2(msg);
+ dt.rfci = h1->rfci;
+ dt.frame_nr = h1->frame_nr;
+ dt.fqc = h1->fqc;
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+
+ /* pull up to the IuUP payload and push a new primitive header in front */
+ msgb_pull_to_l3(msg);
+
+ /* Avoid allocating irp out of 8byte-aligned address, Asan is not happy with it */
+ irp = (struct osmo_iuup_rnl_prim *) aligned_msgb_push(msg, sizeof(*irp));
+ osmo_prim_init(&irp->oph, SAP_IUUP_RNL, OSMO_IUUP_RNL_DATA, PRIM_OP_INDICATION, msg);
+ irp->u.data = dt;
+
+ return irp;
+}
+
+static struct osmo_iuup_rnl_prim *irp_error_event_alloc_c(void *ctx, enum iuup_error_cause cause, enum iuup_error_distance distance)
+{
+ struct osmo_iuup_rnl_prim *irp;
+ struct msgb *msg;
+ msg = msgb_alloc_c(ctx, sizeof(*irp), "iuup-tx");
+ irp = (struct osmo_iuup_rnl_prim *) msgb_put(msg, sizeof(*irp));
+ osmo_prim_init(&irp->oph, SAP_IUUP_RNL, OSMO_IUUP_RNL_STATUS, PRIM_OP_INDICATION, msg);
+ irp->u.status.procedure = IUUP_PROC_ERR_EVENT;
+ irp->u.status.u.error_event.cause = cause;
+ irp->u.status.u.error_event.distance = distance;
+ return irp;
+}
+
+static struct osmo_iuup_tnl_prim *itp_copy_c(void *ctx, const struct osmo_iuup_tnl_prim *src_itp)
+{
+ struct msgb *msg;
+ struct osmo_iuup_tnl_prim *dst_itp;
+
+ msg = msgb_copy_c(ctx, src_itp->oph.msg, "iuup-tx-retrans");
+ dst_itp = (struct osmo_iuup_tnl_prim *)msgb_data(msg);
+ dst_itp->oph.msg = msg;
+ return dst_itp;
+}
+
+static void retransmit_initialization(struct osmo_iuup_instance *iui)
+{
+ struct osmo_iuup_tnl_prim *itp;
+ iui->fi->T = IUUP_TIMER_INIT;
+ osmo_timer_schedule(&iui->fi->timer, iui->config.t_init.t_ms / 1000, (iui->config.t_init.t_ms % 1000) * 1000);
+ itp = itp_copy_c(iui, iui->timer.init.retrans_itp);
+ iui->transport_prim_cb(&itp->oph, iui->transport_prim_priv);
+}
+
+/* return: whether the last Init was Acked correctly and hence can transition to next state */
+static bool iuup_rx_initialization(struct osmo_iuup_instance *iui, struct osmo_iuup_tnl_prim *itp)
+{
+ struct iuup_pdutype14_hdr *hdr;
+ struct iuup_ctrl_init_hdr *ihdr;
+ struct iuup_ctrl_init_rfci_hdr *ihdr_rfci;
+ struct iuup_ctrl_init_tail *itail;
+ enum iuup_error_cause err_cause;
+ uint8_t num_rfci = 0;
+ int i;
+ bool is_last;
+ uint16_t remote_mask, match_mask;
+ struct osmo_iuup_rnl_prim *irp;
+ struct osmo_iuup_tnl_prim *resp;
+
+ /* TODO: whenever we check message boundaries, length, etc. and we fail, send NACK */
+
+ hdr = (struct iuup_pdutype14_hdr *)msgb_l2(itp->oph.msg);
+ ihdr = (struct iuup_ctrl_init_hdr *)hdr->payload;
+ if (ihdr->num_subflows_per_rfci == 0) {
+ LOGPFSML(iui->fi, LOGL_NOTICE, "Initialization: Unexpected num_subflows=0 received\n");
+ err_cause = IUUP_ERR_CAUSE_UNEXPECTED_VALUE;
+ goto send_nack;
+ }
+ ihdr_rfci = (struct iuup_ctrl_init_rfci_hdr *)ihdr->rfci_data;
+
+ do {
+ struct osmo_iuup_rfci *rfci = &iui->config.rfci[num_rfci];
+ uint8_t l_size_bytes = ihdr_rfci->li + 1;
+ is_last = ihdr_rfci->lri;
+ if (num_rfci >= IUUP_MAX_RFCIS) {
+ LOGPFSML(iui->fi, LOGL_NOTICE, "Initialization: Too many RFCIs received (%u)\n",
+ num_rfci);
+ err_cause = IUUP_ERR_CAUSE_UNEXPECTED_RFCI;
+ goto send_nack;
+ }
+ rfci->used = 1;
+ rfci->id = ihdr_rfci->rfci;
+ if (l_size_bytes == 2) {
+ uint16_t *subflow_size = (uint16_t *)ihdr_rfci->subflow_length;
+ for (i = 0; i < ihdr->num_subflows_per_rfci; i++) {
+ rfci->subflow_sizes[i] = osmo_load16be(subflow_size);
+ subflow_size++;
+ }
+ } else {
+ uint8_t *subflow_size = ihdr_rfci->subflow_length;
+ for (i = 0; i < ihdr->num_subflows_per_rfci; i++) {
+ rfci->subflow_sizes[i] = *subflow_size;
+ subflow_size++;
+ }
+ }
+ num_rfci++;
+ ihdr_rfci++;
+ ihdr_rfci = (struct iuup_ctrl_init_rfci_hdr *)(((uint8_t *)ihdr_rfci) + ihdr->num_subflows_per_rfci * l_size_bytes);
+ } while (!is_last);
+
+ if (ihdr->ti) { /* Timing information present */
+ uint8_t *buf = (uint8_t *)ihdr_rfci;
+ uint8_t num_bytes = (num_rfci + 1) / 2;
+ iui->config.IPTIs_present = true;
+ for (i = 0; i < num_bytes - 1; i++) {
+ iui->config.rfci[i*2].IPTI = *buf >> 4;
+ iui->config.rfci[i*2 + 1].IPTI = *buf & 0x0f;
+ buf++;
+ }
+ iui->config.rfci[i*2].IPTI = *buf >> 4;
+ if (!(num_rfci & 0x01)) /* is even: */
+ iui->config.rfci[i*2 + 1].IPTI = *buf & 0x0f;
+ buf++;
+ itail = (struct iuup_ctrl_init_tail *)buf;
+ } else {
+ iui->config.IPTIs_present = false;
+ itail = (struct iuup_ctrl_init_tail *)ihdr_rfci;
+ }
+ if (itail->data_pdu_type > 1) {
+ LOGPFSML(iui->fi, LOGL_NOTICE, "Initialization: Unexpected Data PDU Type %u received\n", itail->data_pdu_type);
+ err_cause = IUUP_ERR_CAUSE_UNEXPECTED_VALUE;
+ goto send_nack;
+ }
+
+ remote_mask = osmo_load16be(&itail->versions_supported);
+ match_mask = (remote_mask & iui->config.supported_versions_mask);
+ if (match_mask == 0x0000) {
+ LOGPFSML(iui->fi, LOGL_NOTICE,
+ "Initialization: No match in supported versions local=0x%04x vs remote=0x%04x\n",
+ iui->config.supported_versions_mask, remote_mask);
+ err_cause = IUUP_ERR_CAUSE_UNEXPECTED_VALUE;
+ goto send_nack;
+ }
+ for (i = 15; i >= 0; i--) {
+ if (match_mask & (1<<i)) {
+ iui->mode_version = i;
+ break;
+ }
+ }
+
+ iui->config.num_rfci = num_rfci;
+ iui->config.num_subflows = ihdr->num_subflows_per_rfci;
+ iui->config.data_pdu_type = itail->data_pdu_type;
+
+ irp = irp_init_ind_alloc(iui);
+ iui->user_prim_cb(&irp->oph, iui->user_prim_priv);
+
+ LOGPFSML(iui->fi, LOGL_DEBUG, "Tx Initialization ACK\n");
+ resp = itp_ctrl_ack_alloc(iui, IUUP_PROC_INIT, hdr->frame_nr);
+ iui->transport_prim_cb(&resp->oph, iui->transport_prim_priv);
+ return ihdr->chain_ind == 0;
+send_nack:
+ LOGPFSML(iui->fi, LOGL_NOTICE, "Tx Initialization NACK cause=%u orig_message=%s\n",
+ err_cause, osmo_hexdump((const unsigned char *) msgb_l2(itp->oph.msg), msgb_l2len(itp->oph.msg)));
+ resp = tnp_ctrl_nack_alloc(iui, IUUP_PROC_INIT, err_cause, hdr->frame_nr);
+ iui->transport_prim_cb(&resp->oph, iui->transport_prim_priv);
+ return false;
+}
+
+/**********************
+ * FSM STATE FUNCTIONS
+ **********************/
+static void iuup_fsm_null(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct osmo_iuup_instance *iui = fi->priv;
+ struct osmo_iuup_rnl_prim *user_prim = NULL;
+
+ switch (event) {
+ case IUUP_FSM_EVT_IUUP_CONFIG_REQ:
+ user_prim = data;
+ iui->config = user_prim->u.config;
+ iui->config.supported_versions_mask &= 0x0003; /* We only support versions 1 and 2 ourselves */
+ //TODO: if supported_versions_mask == 0x0000,no supported versions, send error to upper layers
+
+ if (iui->config.transparent)
+ osmo_fsm_inst_state_chg(fi, IUUP_FSM_ST_TrM_DATA_XFER_READY, 0, 0);
+ else {
+ osmo_fsm_inst_state_chg(fi, IUUP_FSM_ST_INIT, 0, 0);
+ }
+ break;
+ }
+}
+
+/* transparent mode data transfer */
+static void iuup_fsm_trm_data(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ //struct osmo_iuup_instance *iui = fi->priv;
+
+ switch (event) {
+ case IUUP_FSM_EVT_IUUP_CONFIG_REQ:
+ osmo_fsm_inst_state_chg(fi, IUUP_FSM_ST_NULL, 0, 0);
+ break;
+ case IUUP_FSM_EVT_IUUP_DATA_REQ:
+ /* Data coming down from RNL (user) towards TNL (transport) */
+ break;
+ case IUUP_FSM_EVT_IUUP_DATA_IND:
+ /* Data coming up from TNL (transport) towards RNL (user) */
+ break;
+ case IUUP_FSM_EVT_IUUP_UNITDATA_REQ:
+ case IUUP_FSM_EVT_IUUP_UNITDATA_IND:
+ case IUUP_FSM_EVT_SSASAR_UNITDATA_REQ:
+ case IUUP_FSM_EVT_SSASAR_UNITDATA_IND:
+ /* no state change */
+ break;
+ }
+}
+
+static void iuup_fsm_init_on_enter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct osmo_iuup_instance *iui = fi->priv;
+
+ iui->type14_fn = 0;
+ if (iui->config.active) {
+ iui->timer.init.n = 0;
+ iui->timer.init.retrans_itp = tnp_ctrl_init_alloc(iui);
+ retransmit_initialization(iui);
+ }
+}
+
+static void iuup_fsm_init(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct osmo_iuup_instance *iui = fi->priv;
+ struct osmo_iuup_rnl_prim *irp;
+ struct osmo_iuup_tnl_prim *itp;
+
+ switch (event) {
+ case IUUP_FSM_EVT_IUUP_CONFIG_REQ:
+ /* the only permitted 'config req' type is the request to release the instance */
+ osmo_fsm_inst_state_chg(fi, IUUP_FSM_ST_NULL, 0, 0);
+ break;
+ case IUUP_FSM_EVT_INIT:
+ itp = data;
+ if (iuup_rx_initialization(iui, itp))
+ osmo_fsm_inst_state_chg(fi, IUUP_FSM_ST_SMpSDU_DATA_XFER_READY, 0, 0);
+ break;
+ case IUUP_FSM_EVT_LAST_INIT_ACK:
+ /* last INIT ACK was received, transition to DATA_XFER_READY state */
+ osmo_fsm_inst_state_chg(fi, IUUP_FSM_ST_SMpSDU_DATA_XFER_READY, 0, 0);
+ break;
+ case IUUP_FSM_EVT_INIT_NACK:
+ LOGPFSML(fi, LOGL_NOTICE, "Rx Initialization NACK N=%" PRIu32 "/%" PRIu32 "\n",
+ iui->timer.init.n, iui->config.t_init.n_max);
+ osmo_timer_del(&fi->timer);
+ if (iui->timer.init.n == iui->config.t_init.n_max) {
+ irp = irp_error_event_alloc_c(iui, IUUP_ERR_CAUSE_INIT_FAILURE_REP_NACK, IUUP_ERR_DIST_SECOND_FWD);
+ iui->user_prim_cb(&irp->oph, iui->user_prim_priv);
+ return;
+ }
+ iui->timer.init.n++;
+ retransmit_initialization(iui);
+ break;
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+/* 3GPP TS 25.415 B.2.3 "Support Mode Data Transfer Ready State" */
+static void iuup_fsm_smpsdu_data(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ struct osmo_iuup_instance *iui = fi->priv;
+ struct osmo_iuup_rnl_prim *irp = NULL;
+ struct osmo_iuup_tnl_prim *itp = NULL;
+
+ switch (event) {
+ case IUUP_FSM_EVT_IUUP_CONFIG_REQ:
+ irp = data;
+ osmo_fsm_inst_state_chg(fi, IUUP_FSM_ST_NULL, 0, 0);
+ break;
+ case IUUP_FSM_EVT_INIT:
+ /* "In case of handover or relocation, Initialisation procedures
+ * may have to be performed and Iu UP instance may have to enter
+ * the initialisation state." */
+ itp = data;
+ if (!iuup_rx_initialization(iui, itp))
+ osmo_fsm_inst_state_chg(fi, IUUP_FSM_ST_INIT, 0, 0);
+ break;
+ case IUUP_FSM_EVT_IUUP_DATA_REQ:
+ /* Data coming down from RNL (user) towards TNL (transport) */
+ irp = data;
+ itp = rnl_to_tnl_data(iui, irp);
+ iui->transport_prim_cb(&itp->oph, iui->transport_prim_priv);
+ break;
+ case IUUP_FSM_EVT_IUUP_DATA_IND:
+ /* Data coming up from TNL (transport) towards RNL (user) */
+ itp = data;
+ irp = tnl_to_rnl_data(itp);
+ iui->user_prim_cb(&irp->oph, iui->user_prim_priv);
+ break;
+ }
+}
+
+static int iuup_fsm_timer_cb(struct osmo_fsm_inst *fi)
+{
+ struct osmo_iuup_instance *iui = fi->priv;
+ struct osmo_iuup_rnl_prim *irp;
+
+ switch (fi->T) {
+ case IUUP_TIMER_INIT:
+ OSMO_ASSERT(fi->state == IUUP_FSM_ST_INIT);
+ if (iui->timer.init.n == iui->config.t_init.n_max) {
+ irp = irp_error_event_alloc_c(iui, IUUP_ERR_CAUSE_INIT_FAILURE_NET_TMR, IUUP_ERR_DIST_LOCAL);
+ iui->user_prim_cb(&irp->oph, iui->user_prim_priv);
+ return 0;
+ }
+ iui->timer.init.n++;
+ retransmit_initialization(iui);
+ break;
+ case IUUP_TIMER_TA:
+ break;
+ case IUUP_TIMER_RC:
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+ return 0;
+}
+
+
+static const struct osmo_fsm_state iuup_fsm_states[] = {
+ [IUUP_FSM_ST_NULL] = {
+ .in_event_mask = S(IUUP_FSM_EVT_IUUP_CONFIG_REQ),
+ .out_state_mask = S(IUUP_FSM_ST_INIT) |
+ S(IUUP_FSM_ST_TrM_DATA_XFER_READY),
+ .name = "NULL",
+ .action = iuup_fsm_null,
+ },
+ [IUUP_FSM_ST_TrM_DATA_XFER_READY] = {
+ .in_event_mask = S(IUUP_FSM_EVT_IUUP_CONFIG_REQ) |
+ S(IUUP_FSM_EVT_IUUP_STATUS_REQ) |
+ S(IUUP_FSM_EVT_IUUP_DATA_REQ) |
+ S(IUUP_FSM_EVT_IUUP_DATA_IND) |
+ S(IUUP_FSM_EVT_IUUP_UNITDATA_REQ) |
+ S(IUUP_FSM_EVT_IUUP_UNITDATA_IND) |
+ S(IUUP_FSM_EVT_SSASAR_UNITDATA_REQ) |
+ S(IUUP_FSM_EVT_SSASAR_UNITDATA_IND),
+ .out_state_mask = S(IUUP_FSM_ST_NULL),
+ .name = "TrM_Data_Transfer_Ready",
+ .action = iuup_fsm_trm_data,
+ },
+ [IUUP_FSM_ST_INIT] = {
+ .in_event_mask = S(IUUP_FSM_EVT_IUUP_CONFIG_REQ) |
+ S(IUUP_FSM_EVT_INIT) |
+ S(IUUP_FSM_EVT_LAST_INIT_ACK) |
+ S(IUUP_FSM_EVT_INIT_NACK),
+ .out_state_mask = S(IUUP_FSM_ST_NULL) |
+ S(IUUP_FSM_ST_SMpSDU_DATA_XFER_READY),
+ .name = "Initialisation",
+ .onenter = iuup_fsm_init_on_enter,
+ .action = iuup_fsm_init,
+ },
+ [IUUP_FSM_ST_SMpSDU_DATA_XFER_READY] = {
+ .in_event_mask = S(IUUP_FSM_EVT_IUUP_CONFIG_REQ) |
+ S(IUUP_FSM_EVT_INIT) |
+ S(IUUP_FSM_EVT_IUUP_DATA_REQ) |
+ S(IUUP_FSM_EVT_IUUP_DATA_IND),
+ .out_state_mask = S(IUUP_FSM_ST_NULL) |
+ S(IUUP_FSM_ST_INIT),
+ .name = "SMpSDU_Data_Transfer_Ready",
+ .action = iuup_fsm_smpsdu_data,
+ },
+};
+
+static struct osmo_fsm iuup_fsm = {
+ .name = "IuUP",
+ .states = iuup_fsm_states,
+ .num_states = ARRAY_SIZE(iuup_fsm_states),
+ .timer_cb = iuup_fsm_timer_cb,
+ .log_subsys = DLIUUP,
+ .event_names = iuup_fsm_event_names,
+};
+
+static int iuup_verify_pdu(const uint8_t *data, unsigned int len)
+{
+ int header_crc_computed, payload_crc_computed;
+ uint16_t payload_crc;
+ uint8_t pdu_type = iuup_get_pdu_type(data);
+ struct iuup_pdutype0_hdr *t0h;
+ struct iuup_pdutype14_hdr *t14h;
+
+ if (len < 3)
+ return -EINVAL;
+
+ header_crc_computed = osmo_iuup_compute_header_crc(data, len);
+ if (iuup_get_hdr_crc(data) != header_crc_computed) {
+ LOGP(DLIUUP, LOGL_NOTICE, "Header Checksum error: rx 0x%02x vs exp 0x%02x\n",
+ iuup_get_hdr_crc(data), header_crc_computed);
+ return -EIO;
+ }
+ switch (pdu_type) {
+ case IUUP_PDU_T_DATA_NOCRC:
+ if (len < 4)
+ return -EINVAL;
+ break;
+ case IUUP_PDU_T_DATA_CRC:
+ t0h = (struct iuup_pdutype0_hdr *) data;
+ payload_crc = ((uint16_t)t0h->payload_crc_hi << 8) | t0h->payload_crc_lo;
+ payload_crc_computed = osmo_iuup_compute_payload_crc(data, len);
+ if (payload_crc != payload_crc_computed)
+ goto payload_crc_err;
+ break;
+ case IUUP_PDU_T_CONTROL:
+ t14h = (struct iuup_pdutype14_hdr *) data;
+ if (t14h->ack_nack == IUUP_AN_PROCEDURE) {
+ payload_crc = ((uint16_t)t14h->payload_crc_hi << 8) | t14h->payload_crc_lo;
+ payload_crc_computed = osmo_iuup_compute_payload_crc(data, len);
+ if (payload_crc != payload_crc_computed)
+ goto payload_crc_err;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+
+payload_crc_err:
+ LOGP(DLIUUP, LOGL_NOTICE, "Payload Checksum error (pdu type %u): rx 0x%02x vs exp 0x%02x\n",
+ pdu_type, payload_crc, payload_crc_computed);
+ return -EIO;
+}
+
+/* A IuUP TNL SAP primitive from transport (lower layer) */
+int osmo_iuup_tnl_prim_up(struct osmo_iuup_instance *inst, struct osmo_iuup_tnl_prim *itp)
+{
+ struct osmo_prim_hdr *oph = &itp->oph;
+ struct iuup_pdutype14_hdr *t14h;
+ int rc = 0;
+
+ OSMO_ASSERT(oph->sap == SAP_IUUP_TNL);
+
+ switch (OSMO_PRIM_HDR(oph)) {
+ case OSMO_PRIM(OSMO_IUUP_TNL_UNITDATA, PRIM_OP_INDICATION):
+ if (iuup_verify_pdu(msgb_l2(oph->msg), msgb_l2len(oph->msg)) < 0) {
+ LOGPFSML(inst->fi, LOGL_NOTICE, "Discarding invalid IuUP PDU: %s\n",
+ osmo_hexdump((const unsigned char *) msgb_l2(oph->msg), msgb_l2len(oph->msg)));
+ /* don't return error as the caller is not responsible for the PDU which
+ * was transmitted from some remote peer */
+ return 0;
+ }
+ switch (iuup_get_pdu_type(msgb_l2(oph->msg))) {
+ case IUUP_PDU_T_DATA_CRC:
+ oph->msg->l3h = msgb_l2(oph->msg) + sizeof(struct iuup_pdutype0_hdr);
+ rc = osmo_fsm_inst_dispatch(inst->fi, IUUP_FSM_EVT_IUUP_DATA_IND, itp);
+ break;
+ case IUUP_PDU_T_DATA_NOCRC:
+ oph->msg->l3h = msgb_l2(oph->msg) + sizeof(struct iuup_pdutype1_hdr);
+ rc = osmo_fsm_inst_dispatch(inst->fi, IUUP_FSM_EVT_IUUP_DATA_IND, itp);
+ break;
+ case IUUP_PDU_T_CONTROL:
+ t14h = (struct iuup_pdutype14_hdr *) msgb_l2(oph->msg);
+ switch (t14h->ack_nack) {
+ case IUUP_AN_PROCEDURE:
+ switch (t14h->proc_ind) {
+ case IUUP_PROC_INIT:
+ rc = osmo_fsm_inst_dispatch(inst->fi, IUUP_FSM_EVT_INIT, itp);
+ break;
+ case IUUP_PROC_RATE_CTRL:
+ case IUUP_PROC_TIME_ALIGN:
+ case IUUP_PROC_ERR_EVENT:
+ LOGPFSML(inst->fi, LOGL_NOTICE, "Received Request for "
+ "unsupported IuUP procedure %u\n", t14h->proc_ind);
+ break;
+ default:
+ LOGPFSML(inst->fi, LOGL_NOTICE, "Received Request for "
+ "unknown IuUP procedure %u\n", t14h->proc_ind);
+ break;
+ }
+ break;
+ case IUUP_AN_ACK:
+ switch (t14h->proc_ind) {
+ case IUUP_PROC_INIT:
+ rc = osmo_fsm_inst_dispatch(inst->fi,
+ IUUP_FSM_EVT_LAST_INIT_ACK, itp);
+ break;
+ default:
+ LOGPFSML(inst->fi, LOGL_ERROR, "Received ACK for "
+ "unknown IuUP procedure %u\n", t14h->proc_ind);
+ break;
+ }
+ break;
+ case IUUP_AN_NACK:
+ switch (t14h->proc_ind) {
+ case IUUP_PROC_INIT:
+ rc = osmo_fsm_inst_dispatch(inst->fi,
+ IUUP_FSM_EVT_INIT_NACK, itp);
+ break;
+ default:
+ LOGPFSML(inst->fi, LOGL_ERROR, "Received NACK for "
+ "unknown IuUP procedure %u\n", t14h->proc_ind);
+ break;
+ }
+ break;
+ default:
+ LOGPFSML(inst->fi, LOGL_ERROR, "Received unknown IuUP ACK/NACK\n");
+ break;
+ }
+ break;
+ default:
+ LOGPFSML(inst->fi, LOGL_NOTICE, "Received unknown IuUP PDU type %u\n",
+ iuup_get_pdu_type(msgb_l2(oph->msg)));
+ break;
+ }
+ break;
+ default:
+ /* exception: return an error code due to a wrong primitive */
+ return -EINVAL;
+ }
+
+ return rc;
+}
+
+/* A IuUP RNL SAP primitive from user (higher layer) */
+int osmo_iuup_rnl_prim_down(struct osmo_iuup_instance *inst, struct osmo_iuup_rnl_prim *irp)
+{
+ struct osmo_prim_hdr *oph = &irp->oph;
+ int rc;
+
+ OSMO_ASSERT(oph->sap == SAP_IUUP_RNL);
+
+ switch (OSMO_PRIM_HDR(oph)) {
+ case OSMO_PRIM(OSMO_IUUP_RNL_CONFIG, PRIM_OP_REQUEST):
+ rc = osmo_fsm_inst_dispatch(inst->fi, IUUP_FSM_EVT_IUUP_CONFIG_REQ, irp);
+ msgb_free(irp->oph.msg);
+ break;
+ case OSMO_PRIM(OSMO_IUUP_RNL_DATA, PRIM_OP_REQUEST):
+ rc = osmo_fsm_inst_dispatch(inst->fi, IUUP_FSM_EVT_IUUP_DATA_REQ, irp);
+ if (rc != 0)
+ msgb_free(irp->oph.msg);
+ break;
+ case OSMO_PRIM(OSMO_IUUP_RNL_STATUS, PRIM_OP_REQUEST):
+ rc = osmo_fsm_inst_dispatch(inst->fi, IUUP_FSM_EVT_IUUP_STATUS_REQ, irp);
+ msgb_free(irp->oph.msg);
+ break;
+ default:
+ rc = -EINVAL;
+ msgb_free(irp->oph.msg);
+ }
+ return rc;
+}
+
+struct osmo_iuup_instance *osmo_iuup_instance_alloc(void *ctx, const char *id)
+{
+ struct osmo_iuup_instance *iui;
+ iui = talloc_zero(ctx, struct osmo_iuup_instance);
+ if (!iui)
+ return NULL;
+
+ iui->fi = osmo_fsm_inst_alloc(&iuup_fsm, NULL, iui, LOGL_DEBUG, id);
+ if (!iui->fi)
+ goto free_ret;
+
+ return iui;
+free_ret:
+ talloc_free(iui);
+ return NULL;
+}
+
+void osmo_iuup_instance_free(struct osmo_iuup_instance *iui)
+{
+ if (!iui)
+ return;
+
+ if (iui->fi)
+ osmo_fsm_inst_free(iui->fi);
+ iui->fi = NULL;
+ talloc_free(iui);
+}
+
+void osmo_iuup_instance_set_user_prim_cb(struct osmo_iuup_instance *iui, osmo_prim_cb func, void *priv)
+{
+ iui->user_prim_cb = func;
+ iui->user_prim_priv = priv;
+}
+void osmo_iuup_instance_set_transport_prim_cb(struct osmo_iuup_instance *iui, osmo_prim_cb func, void *priv)
+{
+ iui->transport_prim_cb = func;
+ iui->transport_prim_priv = priv;
+}
+
+static __attribute__((constructor)) void on_dso_load_iuup_fsm(void)
+{
+ OSMO_ASSERT(osmo_fsm_register(&iuup_fsm) == 0);
+}
diff --git a/src/gsm/kasumi.c b/src/gsm/kasumi.c
index f93c002b..78885bdd 100644
--- a/src/gsm/kasumi.c
+++ b/src/gsm/kasumi.c
@@ -16,10 +16,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <stdint.h>
diff --git a/src/gsm/kdf.c b/src/gsm/kdf.c
new file mode 100644
index 00000000..4113aada
--- /dev/null
+++ b/src/gsm/kdf.c
@@ -0,0 +1,163 @@
+/*
+ * (C) 2021 by sysmocom s.f.m.c. GmbH
+ *
+ * Author: Eric Wild <ewild@sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <stdint.h>
+#include <string.h>
+
+#include "config.h"
+#if (USE_GNUTLS)
+#include <gnutls/gnutls.h>
+#include <gnutls/crypto.h>
+#define HMAC_FUNC(k,lk,s,sl,out) gnutls_hmac_fast(GNUTLS_MAC_SHA256,k,lk,s,sl,out)
+#else
+#include <osmocom/crypt/kdf.h>
+#define HMAC_FUNC(k,lk,s,sl,out) hmac_sha256(k,lk,s,sl,out)
+#endif
+
+#include <osmocom/core/bit32gen.h>
+#include <osmocom/crypt/kdf.h>
+
+#include "kdf/common.h"
+#include "kdf/sha256.h"
+
+
+#if (USE_GNUTLS)
+/* gnutls < 3.3.0 requires global init.
+ * gnutls >= 3.3.0 does it automatic.
+ * It doesn't hurt calling it twice,
+ * as long it's not done at the same time (threads).
+ */
+__attribute__((constructor))
+static void on_dso_load_gnutls(void)
+{
+ if (!gnutls_check_version("3.3.0"))
+ gnutls_global_init();
+}
+
+__attribute__((destructor))
+static void on_dso_unload_gnutls(void)
+{
+ if (!gnutls_check_version("3.3.0"))
+ gnutls_global_deinit();
+}
+#endif
+
+/*
+ * This file uses the generic key derivation function defined in 3GPP TS 33.220 Annex B
+ *
+ * The S parameter always consists of concatenated values FC | P0 | L0 | Pi | Li | ...
+ * with Pi = Parameter number i and Li = Length of Pi (two octets)
+ *
+ * FC is either a single octet or two octets 0xff | FC
+ * FC values ranges depend on the specification parts that use the KDF,
+ * they are defined in 3GPP TS 33.220 Annex B.2.2
+ *
+ */
+
+/*! \addtogroup kdf
+ * @{
+ * key derivation functions
+ *
+ * \file kdf.c */
+
+/* 3GPP TS 33.102 B.5 */
+void osmo_kdf_kc128(const uint8_t* ck, const uint8_t* ik, uint8_t* kc128) {
+ uint8_t k[16*2];
+ uint8_t s[1];
+ uint8_t out_tmp256[32];
+ memcpy (&k[0], ck, 16);
+ memcpy (&k[16], ik, 16);
+
+ s[0] = 0x32; // yeah, really just one FC byte..
+
+ HMAC_FUNC(k, 32, s, 1, out_tmp256);
+ memcpy(kc128, out_tmp256, 16);
+}
+
+/* 3GPP TS 33.401 A.2 */
+void osmo_kdf_kasme(const uint8_t *ck, const uint8_t *ik, const uint8_t* plmn_id,
+ const uint8_t *sqn, const uint8_t *ak, uint8_t *kasme)
+{
+ uint8_t s[14];
+ uint8_t k[16*2];
+ int i;
+
+ memcpy(&k[0], ck, 16);
+ memcpy(&k[16], ik, 16);
+
+ s[0] = 0x10;
+ memcpy(&s[1], plmn_id, 3);
+ s[4] = 0x00;
+ s[5] = 0x03;
+
+ for (i = 0; i < 6; i++)
+ s[6+i] = sqn[i] ^ ak[i];
+ s[12] = 0x00;
+ s[13] = 0x06;
+
+ HMAC_FUNC(k, 32, s, 14, kasme);
+}
+
+/* 3GPP TS 33.401 A.3 */
+void osmo_kdf_enb(const uint8_t *kasme, uint32_t ul_count, uint8_t *kenb)
+{
+ uint8_t s[7];
+
+ s[0] = 0x11;
+ osmo_store32be(ul_count, &s[1]);
+ s[5] = 0x00;
+ s[6] = 0x04;
+
+ HMAC_FUNC(kasme, 32, s, 7, kenb);
+}
+
+/* 3GPP TS 33.401 A.4 */
+void osmo_kdf_nh(const uint8_t *kasme, const uint8_t *sync_input, uint8_t *nh)
+{
+ uint8_t s[35];
+
+ s[0] = 0x12;
+ memcpy(s+1, sync_input, 32);
+ s[33] = 0x00;
+ s[34] = 0x20;
+
+ HMAC_FUNC(kasme, 32, s, 35, nh);
+}
+
+/* 3GPP TS 33.401 A.7 */
+void osmo_kdf_nas(uint8_t algo_type, uint8_t algo_id, const uint8_t *kasme, uint8_t *knas)
+{
+ uint8_t s[7];
+ uint8_t out[32];
+
+ s[0] = 0x15;
+ s[1] = algo_type;
+ s[2] = 0x00;
+ s[3] = 0x01;
+ s[4] = algo_id;
+ s[5] = 0x00;
+ s[6] = 0x01;
+
+ HMAC_FUNC(kasme, 32, s, 7, out);
+ memcpy(knas, out+16, 16);
+}
+
+/*! @} */
diff --git a/src/gsm/kdf/common.h b/src/gsm/kdf/common.h
new file mode 100644
index 00000000..c1ef17ef
--- /dev/null
+++ b/src/gsm/kdf/common.h
@@ -0,0 +1,101 @@
+#pragma once
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define CONFIG_CRYPTO_INTERNAL
+#define TEST_FAIL() 0
+
+#define MSG_DEBUG
+#define wpa_hexdump(x, args...)
+#define wpa_hexdump_key(x, args...)
+#define wpa_printf(x, args...)
+
+#define os_memcpy(x, y, z) memcpy(x, y, z)
+#define os_memcmp(x, y, z) memcmp(x, y, z)
+#define os_memset(x, y, z) memset(x, y, z)
+#define os_malloc(x) malloc(x)
+#define os_free(x) free(x)
+#define os_strlen(x) strlen(x)
+
+#define forced_memzero(ptr, len) memset(ptr, 0, len);
+
+typedef uint64_t u64;
+typedef uint32_t u32;
+typedef uint16_t u16;
+typedef uint8_t u8;
+typedef int64_t s64;
+typedef int32_t s32;
+typedef int16_t s16;
+typedef int8_t s8;
+
+
+/* Macros for handling unaligned memory accesses */
+
+#define WPA_GET_BE16(a) ((u16) (((a)[0] << 8) | (a)[1]))
+#define WPA_PUT_BE16(a, val) \
+ do { \
+ (a)[0] = ((u16) (val)) >> 8; \
+ (a)[1] = ((u16) (val)) & 0xff; \
+ } while (0)
+
+#define WPA_GET_LE16(a) ((u16) (((a)[1] << 8) | (a)[0]))
+#define WPA_PUT_LE16(a, val) \
+ do { \
+ (a)[1] = ((u16) (val)) >> 8; \
+ (a)[0] = ((u16) (val)) & 0xff; \
+ } while (0)
+
+#define WPA_GET_BE24(a) ((((u32) (a)[0]) << 16) | (((u32) (a)[1]) << 8) | \
+ ((u32) (a)[2]))
+#define WPA_PUT_BE24(a, val) \
+ do { \
+ (a)[0] = (u8) ((((u32) (val)) >> 16) & 0xff); \
+ (a)[1] = (u8) ((((u32) (val)) >> 8) & 0xff); \
+ (a)[2] = (u8) (((u32) (val)) & 0xff); \
+ } while (0)
+
+#define WPA_GET_BE32(a) ((((u32) (a)[0]) << 24) | (((u32) (a)[1]) << 16) | \
+ (((u32) (a)[2]) << 8) | ((u32) (a)[3]))
+#define WPA_PUT_BE32(a, val) \
+ do { \
+ (a)[0] = (u8) ((((u32) (val)) >> 24) & 0xff); \
+ (a)[1] = (u8) ((((u32) (val)) >> 16) & 0xff); \
+ (a)[2] = (u8) ((((u32) (val)) >> 8) & 0xff); \
+ (a)[3] = (u8) (((u32) (val)) & 0xff); \
+ } while (0)
+
+#define WPA_GET_LE32(a) ((((u32) (a)[3]) << 24) | (((u32) (a)[2]) << 16) | \
+ (((u32) (a)[1]) << 8) | ((u32) (a)[0]))
+#define WPA_PUT_LE32(a, val) \
+ do { \
+ (a)[3] = (u8) ((((u32) (val)) >> 24) & 0xff); \
+ (a)[2] = (u8) ((((u32) (val)) >> 16) & 0xff); \
+ (a)[1] = (u8) ((((u32) (val)) >> 8) & 0xff); \
+ (a)[0] = (u8) (((u32) (val)) & 0xff); \
+ } while (0)
+
+#define WPA_GET_BE64(a) ((((u64) (a)[0]) << 56) | (((u64) (a)[1]) << 48) | \
+ (((u64) (a)[2]) << 40) | (((u64) (a)[3]) << 32) | \
+ (((u64) (a)[4]) << 24) | (((u64) (a)[5]) << 16) | \
+ (((u64) (a)[6]) << 8) | ((u64) (a)[7]))
+#define WPA_PUT_BE64(a, val) \
+ do { \
+ (a)[0] = (u8) (((u64) (val)) >> 56); \
+ (a)[1] = (u8) (((u64) (val)) >> 48); \
+ (a)[2] = (u8) (((u64) (val)) >> 40); \
+ (a)[3] = (u8) (((u64) (val)) >> 32); \
+ (a)[4] = (u8) (((u64) (val)) >> 24); \
+ (a)[5] = (u8) (((u64) (val)) >> 16); \
+ (a)[6] = (u8) (((u64) (val)) >> 8); \
+ (a)[7] = (u8) (((u64) (val)) & 0xff); \
+ } while (0)
+
+#define WPA_GET_LE64(a) ((((u64) (a)[7]) << 56) | (((u64) (a)[6]) << 48) | \
+ (((u64) (a)[5]) << 40) | (((u64) (a)[4]) << 32) | \
+ (((u64) (a)[3]) << 24) | (((u64) (a)[2]) << 16) | \
+ (((u64) (a)[1]) << 8) | ((u64) (a)[0]))
+
+
+#define __must_check
diff --git a/src/gsm/kdf/crypto.h b/src/gsm/kdf/crypto.h
new file mode 100644
index 00000000..6dca191c
--- /dev/null
+++ b/src/gsm/kdf/crypto.h
@@ -0,0 +1,470 @@
+/*
+ * WPA Supplicant / wrapper functions for crypto libraries
+ * Copyright (c) 2004-2009, Jouni Malinen <j@w1.fi>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Alternatively, this software may be distributed under the terms of BSD
+ * license.
+ *
+ * See README and COPYING for more details.
+ *
+ * This file defines the cryptographic functions that need to be implemented
+ * for wpa_supplicant and hostapd. When TLS is not used, internal
+ * implementation of MD5, SHA1, and AES is used and no external libraries are
+ * required. When TLS is enabled (e.g., by enabling EAP-TLS or EAP-PEAP), the
+ * crypto library used by the TLS implementation is expected to be used for
+ * non-TLS needs, too, in order to save space by not implementing these
+ * functions twice.
+ *
+ * Wrapper code for using each crypto library is in its own file (crypto*.c)
+ * and one of these files is build and linked in to provide the functions
+ * defined here.
+ */
+
+#ifndef CRYPTO_H
+#define CRYPTO_H
+
+/**
+ * md4_vector - MD4 hash for data vector
+ * @num_elem: Number of elements in the data vector
+ * @addr: Pointers to the data areas
+ * @len: Lengths of the data blocks
+ * @mac: Buffer for the hash
+ * Returns: 0 on success, -1 on failure
+ */
+int md4_vector(size_t num_elem, const u8 *addr[], const size_t *len, u8 *mac);
+
+/**
+ * md5_vector - MD5 hash for data vector
+ * @num_elem: Number of elements in the data vector
+ * @addr: Pointers to the data areas
+ * @len: Lengths of the data blocks
+ * @mac: Buffer for the hash
+ * Returns: 0 on success, -1 on failure
+ */
+int md5_vector(size_t num_elem, const u8 *addr[], const size_t *len, u8 *mac);
+
+#ifdef CONFIG_FIPS
+/**
+ * md5_vector_non_fips_allow - MD5 hash for data vector (non-FIPS use allowed)
+ * @num_elem: Number of elements in the data vector
+ * @addr: Pointers to the data areas
+ * @len: Lengths of the data blocks
+ * @mac: Buffer for the hash
+ * Returns: 0 on success, -1 on failure
+ */
+int md5_vector_non_fips_allow(size_t num_elem, const u8 *addr[],
+ const size_t *len, u8 *mac);
+#else /* CONFIG_FIPS */
+#define md5_vector_non_fips_allow md5_vector
+#endif /* CONFIG_FIPS */
+
+
+/**
+ * sha1_vector - SHA-1 hash for data vector
+ * @num_elem: Number of elements in the data vector
+ * @addr: Pointers to the data areas
+ * @len: Lengths of the data blocks
+ * @mac: Buffer for the hash
+ * Returns: 0 on success, -1 on failure
+ */
+int sha1_vector(size_t num_elem, const u8 *addr[], const size_t *len,
+ u8 *mac);
+
+/**
+ * fips186_2-prf - NIST FIPS Publication 186-2 change notice 1 PRF
+ * @seed: Seed/key for the PRF
+ * @seed_len: Seed length in bytes
+ * @x: Buffer for PRF output
+ * @xlen: Output length in bytes
+ * Returns: 0 on success, -1 on failure
+ *
+ * This function implements random number generation specified in NIST FIPS
+ * Publication 186-2 for EAP-SIM. This PRF uses a function that is similar to
+ * SHA-1, but has different message padding.
+ */
+int __must_check fips186_2_prf(const u8 *seed, size_t seed_len, u8 *x,
+ size_t xlen);
+
+/**
+ * sha256_vector - SHA256 hash for data vector
+ * @num_elem: Number of elements in the data vector
+ * @addr: Pointers to the data areas
+ * @len: Lengths of the data blocks
+ * @mac: Buffer for the hash
+ * Returns: 0 on success, -1 on failure
+ */
+int sha256_vector(size_t num_elem, const u8 *addr[], const size_t *len,
+ u8 *mac);
+
+/**
+ * des_encrypt - Encrypt one block with DES
+ * @clear: 8 octets (in)
+ * @key: 7 octets (in) (no parity bits included)
+ * @cypher: 8 octets (out)
+ */
+void des_encrypt(const u8 *clear, const u8 *key, u8 *cypher);
+
+/**
+ * aes_encrypt_init - Initialize AES for encryption
+ * @key: Encryption key
+ * @len: Key length in bytes (usually 16, i.e., 128 bits)
+ * Returns: Pointer to context data or %NULL on failure
+ */
+void * aes_encrypt_init(const u8 *key, size_t len);
+
+/**
+ * aes_encrypt - Encrypt one AES block
+ * @ctx: Context pointer from aes_encrypt_init()
+ * @plain: Plaintext data to be encrypted (16 bytes)
+ * @crypt: Buffer for the encrypted data (16 bytes)
+ */
+void aes_encrypt(void *ctx, const u8 *plain, u8 *crypt);
+
+/**
+ * aes_encrypt_deinit - Deinitialize AES encryption
+ * @ctx: Context pointer from aes_encrypt_init()
+ */
+void aes_encrypt_deinit(void *ctx);
+
+/**
+ * aes_decrypt_init - Initialize AES for decryption
+ * @key: Decryption key
+ * @len: Key length in bytes (usually 16, i.e., 128 bits)
+ * Returns: Pointer to context data or %NULL on failure
+ */
+void * aes_decrypt_init(const u8 *key, size_t len);
+
+/**
+ * aes_decrypt - Decrypt one AES block
+ * @ctx: Context pointer from aes_encrypt_init()
+ * @crypt: Encrypted data (16 bytes)
+ * @plain: Buffer for the decrypted data (16 bytes)
+ */
+void aes_decrypt(void *ctx, const u8 *crypt, u8 *plain);
+
+/**
+ * aes_decrypt_deinit - Deinitialize AES decryption
+ * @ctx: Context pointer from aes_encrypt_init()
+ */
+void aes_decrypt_deinit(void *ctx);
+
+
+enum crypto_hash_alg {
+ CRYPTO_HASH_ALG_MD5, CRYPTO_HASH_ALG_SHA1,
+ CRYPTO_HASH_ALG_HMAC_MD5, CRYPTO_HASH_ALG_HMAC_SHA1,
+ CRYPTO_HASH_ALG_SHA256, CRYPTO_HASH_ALG_HMAC_SHA256
+};
+
+struct crypto_hash;
+
+/**
+ * crypto_hash_init - Initialize hash/HMAC function
+ * @alg: Hash algorithm
+ * @key: Key for keyed hash (e.g., HMAC) or %NULL if not needed
+ * @key_len: Length of the key in bytes
+ * Returns: Pointer to hash context to use with other hash functions or %NULL
+ * on failure
+ *
+ * This function is only used with internal TLSv1 implementation
+ * (CONFIG_TLS=internal). If that is not used, the crypto wrapper does not need
+ * to implement this.
+ */
+struct crypto_hash * crypto_hash_init(enum crypto_hash_alg alg, const u8 *key,
+ size_t key_len);
+
+/**
+ * crypto_hash_update - Add data to hash calculation
+ * @ctx: Context pointer from crypto_hash_init()
+ * @data: Data buffer to add
+ * @len: Length of the buffer
+ *
+ * This function is only used with internal TLSv1 implementation
+ * (CONFIG_TLS=internal). If that is not used, the crypto wrapper does not need
+ * to implement this.
+ */
+void crypto_hash_update(struct crypto_hash *ctx, const u8 *data, size_t len);
+
+/**
+ * crypto_hash_finish - Complete hash calculation
+ * @ctx: Context pointer from crypto_hash_init()
+ * @hash: Buffer for hash value or %NULL if caller is just freeing the hash
+ * context
+ * @len: Pointer to length of the buffer or %NULL if caller is just freeing the
+ * hash context; on return, this is set to the actual length of the hash value
+ * Returns: 0 on success, -1 if buffer is too small (len set to needed length),
+ * or -2 on other failures (including failed crypto_hash_update() operations)
+ *
+ * This function calculates the hash value and frees the context buffer that
+ * was used for hash calculation.
+ *
+ * This function is only used with internal TLSv1 implementation
+ * (CONFIG_TLS=internal). If that is not used, the crypto wrapper does not need
+ * to implement this.
+ */
+int crypto_hash_finish(struct crypto_hash *ctx, u8 *hash, size_t *len);
+
+
+enum crypto_cipher_alg {
+ CRYPTO_CIPHER_NULL = 0, CRYPTO_CIPHER_ALG_AES, CRYPTO_CIPHER_ALG_3DES,
+ CRYPTO_CIPHER_ALG_DES, CRYPTO_CIPHER_ALG_RC2, CRYPTO_CIPHER_ALG_RC4
+};
+
+struct crypto_cipher;
+
+/**
+ * crypto_cipher_init - Initialize block/stream cipher function
+ * @alg: Cipher algorithm
+ * @iv: Initialization vector for block ciphers or %NULL for stream ciphers
+ * @key: Cipher key
+ * @key_len: Length of key in bytes
+ * Returns: Pointer to cipher context to use with other cipher functions or
+ * %NULL on failure
+ *
+ * This function is only used with internal TLSv1 implementation
+ * (CONFIG_TLS=internal). If that is not used, the crypto wrapper does not need
+ * to implement this.
+ */
+struct crypto_cipher * crypto_cipher_init(enum crypto_cipher_alg alg,
+ const u8 *iv, const u8 *key,
+ size_t key_len);
+
+/**
+ * crypto_cipher_encrypt - Cipher encrypt
+ * @ctx: Context pointer from crypto_cipher_init()
+ * @plain: Plaintext to cipher
+ * @crypt: Resulting ciphertext
+ * @len: Length of the plaintext
+ * Returns: 0 on success, -1 on failure
+ *
+ * This function is only used with internal TLSv1 implementation
+ * (CONFIG_TLS=internal). If that is not used, the crypto wrapper does not need
+ * to implement this.
+ */
+int __must_check crypto_cipher_encrypt(struct crypto_cipher *ctx,
+ const u8 *plain, u8 *crypt, size_t len);
+
+/**
+ * crypto_cipher_decrypt - Cipher decrypt
+ * @ctx: Context pointer from crypto_cipher_init()
+ * @crypt: Ciphertext to decrypt
+ * @plain: Resulting plaintext
+ * @len: Length of the cipher text
+ * Returns: 0 on success, -1 on failure
+ *
+ * This function is only used with internal TLSv1 implementation
+ * (CONFIG_TLS=internal). If that is not used, the crypto wrapper does not need
+ * to implement this.
+ */
+int __must_check crypto_cipher_decrypt(struct crypto_cipher *ctx,
+ const u8 *crypt, u8 *plain, size_t len);
+
+/**
+ * crypto_cipher_decrypt - Free cipher context
+ * @ctx: Context pointer from crypto_cipher_init()
+ *
+ * This function is only used with internal TLSv1 implementation
+ * (CONFIG_TLS=internal). If that is not used, the crypto wrapper does not need
+ * to implement this.
+ */
+void crypto_cipher_deinit(struct crypto_cipher *ctx);
+
+
+struct crypto_public_key;
+struct crypto_private_key;
+
+/**
+ * crypto_public_key_import - Import an RSA public key
+ * @key: Key buffer (DER encoded RSA public key)
+ * @len: Key buffer length in bytes
+ * Returns: Pointer to the public key or %NULL on failure
+ *
+ * This function can just return %NULL if the crypto library supports X.509
+ * parsing. In that case, crypto_public_key_from_cert() is used to import the
+ * public key from a certificate.
+ *
+ * This function is only used with internal TLSv1 implementation
+ * (CONFIG_TLS=internal). If that is not used, the crypto wrapper does not need
+ * to implement this.
+ */
+struct crypto_public_key * crypto_public_key_import(const u8 *key, size_t len);
+
+/**
+ * crypto_private_key_import - Import an RSA private key
+ * @key: Key buffer (DER encoded RSA private key)
+ * @len: Key buffer length in bytes
+ * @passwd: Key encryption password or %NULL if key is not encrypted
+ * Returns: Pointer to the private key or %NULL on failure
+ *
+ * This function is only used with internal TLSv1 implementation
+ * (CONFIG_TLS=internal). If that is not used, the crypto wrapper does not need
+ * to implement this.
+ */
+struct crypto_private_key * crypto_private_key_import(const u8 *key,
+ size_t len,
+ const char *passwd);
+
+/**
+ * crypto_public_key_from_cert - Import an RSA public key from a certificate
+ * @buf: DER encoded X.509 certificate
+ * @len: Certificate buffer length in bytes
+ * Returns: Pointer to public key or %NULL on failure
+ *
+ * This function can just return %NULL if the crypto library does not support
+ * X.509 parsing. In that case, internal code will be used to parse the
+ * certificate and public key is imported using crypto_public_key_import().
+ *
+ * This function is only used with internal TLSv1 implementation
+ * (CONFIG_TLS=internal). If that is not used, the crypto wrapper does not need
+ * to implement this.
+ */
+struct crypto_public_key * crypto_public_key_from_cert(const u8 *buf,
+ size_t len);
+
+/**
+ * crypto_public_key_encrypt_pkcs1_v15 - Public key encryption (PKCS #1 v1.5)
+ * @key: Public key
+ * @in: Plaintext buffer
+ * @inlen: Length of plaintext buffer in bytes
+ * @out: Output buffer for encrypted data
+ * @outlen: Length of output buffer in bytes; set to used length on success
+ * Returns: 0 on success, -1 on failure
+ *
+ * This function is only used with internal TLSv1 implementation
+ * (CONFIG_TLS=internal). If that is not used, the crypto wrapper does not need
+ * to implement this.
+ */
+int __must_check crypto_public_key_encrypt_pkcs1_v15(
+ struct crypto_public_key *key, const u8 *in, size_t inlen,
+ u8 *out, size_t *outlen);
+
+/**
+ * crypto_private_key_decrypt_pkcs1_v15 - Private key decryption (PKCS #1 v1.5)
+ * @key: Private key
+ * @in: Encrypted buffer
+ * @inlen: Length of encrypted buffer in bytes
+ * @out: Output buffer for encrypted data
+ * @outlen: Length of output buffer in bytes; set to used length on success
+ * Returns: 0 on success, -1 on failure
+ *
+ * This function is only used with internal TLSv1 implementation
+ * (CONFIG_TLS=internal). If that is not used, the crypto wrapper does not need
+ * to implement this.
+ */
+int __must_check crypto_private_key_decrypt_pkcs1_v15(
+ struct crypto_private_key *key, const u8 *in, size_t inlen,
+ u8 *out, size_t *outlen);
+
+/**
+ * crypto_private_key_sign_pkcs1 - Sign with private key (PKCS #1)
+ * @key: Private key from crypto_private_key_import()
+ * @in: Plaintext buffer
+ * @inlen: Length of plaintext buffer in bytes
+ * @out: Output buffer for encrypted (signed) data
+ * @outlen: Length of output buffer in bytes; set to used length on success
+ * Returns: 0 on success, -1 on failure
+ *
+ * This function is only used with internal TLSv1 implementation
+ * (CONFIG_TLS=internal). If that is not used, the crypto wrapper does not need
+ * to implement this.
+ */
+int __must_check crypto_private_key_sign_pkcs1(struct crypto_private_key *key,
+ const u8 *in, size_t inlen,
+ u8 *out, size_t *outlen);
+
+/**
+ * crypto_public_key_free - Free public key
+ * @key: Public key
+ *
+ * This function is only used with internal TLSv1 implementation
+ * (CONFIG_TLS=internal). If that is not used, the crypto wrapper does not need
+ * to implement this.
+ */
+void crypto_public_key_free(struct crypto_public_key *key);
+
+/**
+ * crypto_private_key_free - Free private key
+ * @key: Private key from crypto_private_key_import()
+ *
+ * This function is only used with internal TLSv1 implementation
+ * (CONFIG_TLS=internal). If that is not used, the crypto wrapper does not need
+ * to implement this.
+ */
+void crypto_private_key_free(struct crypto_private_key *key);
+
+/**
+ * crypto_public_key_decrypt_pkcs1 - Decrypt PKCS #1 signature
+ * @key: Public key
+ * @crypt: Encrypted signature data (using the private key)
+ * @crypt_len: Encrypted signature data length
+ * @plain: Buffer for plaintext (at least crypt_len bytes)
+ * @plain_len: Plaintext length (max buffer size on input, real len on output);
+ * Returns: 0 on success, -1 on failure
+ */
+int __must_check crypto_public_key_decrypt_pkcs1(
+ struct crypto_public_key *key, const u8 *crypt, size_t crypt_len,
+ u8 *plain, size_t *plain_len);
+
+/**
+ * crypto_global_init - Initialize crypto wrapper
+ *
+ * This function is only used with internal TLSv1 implementation
+ * (CONFIG_TLS=internal). If that is not used, the crypto wrapper does not need
+ * to implement this.
+ */
+int __must_check crypto_global_init(void);
+
+/**
+ * crypto_global_deinit - Deinitialize crypto wrapper
+ *
+ * This function is only used with internal TLSv1 implementation
+ * (CONFIG_TLS=internal). If that is not used, the crypto wrapper does not need
+ * to implement this.
+ */
+void crypto_global_deinit(void);
+
+/**
+ * crypto_mod_exp - Modular exponentiation of large integers
+ * @base: Base integer (big endian byte array)
+ * @base_len: Length of base integer in bytes
+ * @power: Power integer (big endian byte array)
+ * @power_len: Length of power integer in bytes
+ * @modulus: Modulus integer (big endian byte array)
+ * @modulus_len: Length of modulus integer in bytes
+ * @result: Buffer for the result
+ * @result_len: Result length (max buffer size on input, real len on output)
+ * Returns: 0 on success, -1 on failure
+ *
+ * This function calculates result = base ^ power mod modulus. modules_len is
+ * used as the maximum size of modulus buffer. It is set to the used size on
+ * success.
+ *
+ * This function is only used with internal TLSv1 implementation
+ * (CONFIG_TLS=internal). If that is not used, the crypto wrapper does not need
+ * to implement this.
+ */
+int __must_check crypto_mod_exp(const u8 *base, size_t base_len,
+ const u8 *power, size_t power_len,
+ const u8 *modulus, size_t modulus_len,
+ u8 *result, size_t *result_len);
+
+/**
+ * rc4_skip - XOR RC4 stream to given data with skip-stream-start
+ * @key: RC4 key
+ * @keylen: RC4 key length
+ * @skip: number of bytes to skip from the beginning of the RC4 stream
+ * @data: data to be XOR'ed with RC4 stream
+ * @data_len: buf length
+ * Returns: 0 on success, -1 on failure
+ *
+ * Generate RC4 pseudo random stream for the given key, skip beginning of the
+ * stream, and XOR the end result with the data buffer to perform RC4
+ * encryption/decryption.
+ */
+int rc4_skip(const u8 *key, size_t keylen, size_t skip,
+ u8 *data, size_t data_len);
+
+#endif /* CRYPTO_H */
diff --git a/src/gsm/kdf/sha1-internal.c b/src/gsm/kdf/sha1-internal.c
new file mode 100644
index 00000000..2216b439
--- /dev/null
+++ b/src/gsm/kdf/sha1-internal.c
@@ -0,0 +1,307 @@
+/*
+ * SHA1 hash implementation and interface functions
+ * Copyright (c) 2003-2005, Jouni Malinen <j@w1.fi>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Alternatively, this software may be distributed under the terms of BSD
+ * license.
+ *
+ * See README and COPYING for more details.
+ */
+
+
+#include "common.h"
+#include "sha1.h"
+#include "sha1_i.h"
+//#include "md5.h"
+#include "crypto.h"
+
+typedef struct SHA1Context SHA1_CTX;
+
+void SHA1Transform(u32 state[5], const unsigned char buffer[64]);
+
+
+/**
+ * sha1_vector - SHA-1 hash for data vector
+ * @num_elem: Number of elements in the data vector
+ * @addr: Pointers to the data areas
+ * @len: Lengths of the data blocks
+ * @mac: Buffer for the hash
+ * Returns: 0 on success, -1 of failure
+ */
+int sha1_vector(size_t num_elem, const u8 *addr[], const size_t *len, u8 *mac)
+{
+ SHA1_CTX ctx;
+ size_t i;
+
+ SHA1Init(&ctx);
+ for (i = 0; i < num_elem; i++)
+ SHA1Update(&ctx, addr[i], len[i]);
+ SHA1Final(mac, &ctx);
+ return 0;
+}
+
+
+/* ===== start - public domain SHA1 implementation ===== */
+
+/*
+SHA-1 in C
+By Steve Reid <sreid@sea-to-sky.net>
+100% Public Domain
+
+-----------------
+Modified 7/98
+By James H. Brown <jbrown@burgoyne.com>
+Still 100% Public Domain
+
+Corrected a problem which generated improper hash values on 16 bit machines
+Routine SHA1Update changed from
+ void SHA1Update(SHA1_CTX* context, unsigned char* data, unsigned int
+len)
+to
+ void SHA1Update(SHA1_CTX* context, unsigned char* data, unsigned
+long len)
+
+The 'len' parameter was declared an int which works fine on 32 bit machines.
+However, on 16 bit machines an int is too small for the shifts being done
+against
+it. This caused the hash function to generate incorrect values if len was
+greater than 8191 (8K - 1) due to the 'len << 3' on line 3 of SHA1Update().
+
+Since the file IO in main() reads 16K at a time, any file 8K or larger would
+be guaranteed to generate the wrong hash (e.g. Test Vector #3, a million
+"a"s).
+
+I also changed the declaration of variables i & j in SHA1Update to
+unsigned long from unsigned int for the same reason.
+
+These changes should make no difference to any 32 bit implementations since
+an
+int and a long are the same size in those environments.
+
+--
+I also corrected a few compiler warnings generated by Borland C.
+1. Added #include <process.h> for exit() prototype
+2. Removed unused variable 'j' in SHA1Final
+3. Changed exit(0) to return(0) at end of main.
+
+ALL changes I made can be located by searching for comments containing 'JHB'
+-----------------
+Modified 8/98
+By Steve Reid <sreid@sea-to-sky.net>
+Still 100% public domain
+
+1- Removed #include <process.h> and used return() instead of exit()
+2- Fixed overwriting of finalcount in SHA1Final() (discovered by Chris Hall)
+3- Changed email address from steve@edmweb.com to sreid@sea-to-sky.net
+
+-----------------
+Modified 4/01
+By Saul Kravitz <Saul.Kravitz@celera.com>
+Still 100% PD
+Modified to run on Compaq Alpha hardware.
+
+-----------------
+Modified 4/01
+By Jouni Malinen <j@w1.fi>
+Minor changes to match the coding style used in Dynamics.
+
+Modified September 24, 2004
+By Jouni Malinen <j@w1.fi>
+Fixed alignment issue in SHA1Transform when SHA1HANDSOFF is defined.
+
+*/
+
+/*
+Test Vectors (from FIPS PUB 180-1)
+"abc"
+ A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D
+"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"
+ 84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1
+A million repetitions of "a"
+ 34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F
+*/
+
+#define SHA1HANDSOFF
+
+#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits))))
+
+/* blk0() and blk() perform the initial expand. */
+/* I got the idea of expanding during the round function from SSLeay */
+#ifndef WORDS_BIGENDIAN
+#define blk0(i) (block->l[i] = (rol(block->l[i], 24) & 0xFF00FF00) | \
+ (rol(block->l[i], 8) & 0x00FF00FF))
+#else
+#define blk0(i) block->l[i]
+#endif
+#define blk(i) (block->l[i & 15] = rol(block->l[(i + 13) & 15] ^ \
+ block->l[(i + 8) & 15] ^ block->l[(i + 2) & 15] ^ block->l[i & 15], 1))
+
+/* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */
+#define R0(v,w,x,y,z,i) \
+ z += ((w & (x ^ y)) ^ y) + blk0(i) + 0x5A827999 + rol(v, 5); \
+ w = rol(w, 30);
+#define R1(v,w,x,y,z,i) \
+ z += ((w & (x ^ y)) ^ y) + blk(i) + 0x5A827999 + rol(v, 5); \
+ w = rol(w, 30);
+#define R2(v,w,x,y,z,i) \
+ z += (w ^ x ^ y) + blk(i) + 0x6ED9EBA1 + rol(v, 5); w = rol(w, 30);
+#define R3(v,w,x,y,z,i) \
+ z += (((w | x) & y) | (w & x)) + blk(i) + 0x8F1BBCDC + rol(v, 5); \
+ w = rol(w, 30);
+#define R4(v,w,x,y,z,i) \
+ z += (w ^ x ^ y) + blk(i) + 0xCA62C1D6 + rol(v, 5); \
+ w=rol(w, 30);
+
+
+#ifdef VERBOSE /* SAK */
+void SHAPrintContext(SHA1_CTX *context, char *msg)
+{
+ printf("%s (%d,%d) %x %x %x %x %x\n",
+ msg,
+ context->count[0], context->count[1],
+ context->state[0],
+ context->state[1],
+ context->state[2],
+ context->state[3],
+ context->state[4]);
+}
+#endif
+
+/* Hash a single 512-bit block. This is the core of the algorithm. */
+
+void SHA1Transform(u32 state[5], const unsigned char buffer[64])
+{
+ u32 a, b, c, d, e;
+ typedef union {
+ unsigned char c[64];
+ u32 l[16];
+ } CHAR64LONG16;
+ CHAR64LONG16* block;
+#ifdef SHA1HANDSOFF
+ CHAR64LONG16 workspace;
+ block = &workspace;
+ os_memcpy(block, buffer, 64);
+#else
+ block = (CHAR64LONG16 *) buffer;
+#endif
+ /* Copy context->state[] to working vars */
+ a = state[0];
+ b = state[1];
+ c = state[2];
+ d = state[3];
+ e = state[4];
+ /* 4 rounds of 20 operations each. Loop unrolled. */
+ R0(a,b,c,d,e, 0); R0(e,a,b,c,d, 1); R0(d,e,a,b,c, 2); R0(c,d,e,a,b, 3);
+ R0(b,c,d,e,a, 4); R0(a,b,c,d,e, 5); R0(e,a,b,c,d, 6); R0(d,e,a,b,c, 7);
+ R0(c,d,e,a,b, 8); R0(b,c,d,e,a, 9); R0(a,b,c,d,e,10); R0(e,a,b,c,d,11);
+ R0(d,e,a,b,c,12); R0(c,d,e,a,b,13); R0(b,c,d,e,a,14); R0(a,b,c,d,e,15);
+ R1(e,a,b,c,d,16); R1(d,e,a,b,c,17); R1(c,d,e,a,b,18); R1(b,c,d,e,a,19);
+ R2(a,b,c,d,e,20); R2(e,a,b,c,d,21); R2(d,e,a,b,c,22); R2(c,d,e,a,b,23);
+ R2(b,c,d,e,a,24); R2(a,b,c,d,e,25); R2(e,a,b,c,d,26); R2(d,e,a,b,c,27);
+ R2(c,d,e,a,b,28); R2(b,c,d,e,a,29); R2(a,b,c,d,e,30); R2(e,a,b,c,d,31);
+ R2(d,e,a,b,c,32); R2(c,d,e,a,b,33); R2(b,c,d,e,a,34); R2(a,b,c,d,e,35);
+ R2(e,a,b,c,d,36); R2(d,e,a,b,c,37); R2(c,d,e,a,b,38); R2(b,c,d,e,a,39);
+ R3(a,b,c,d,e,40); R3(e,a,b,c,d,41); R3(d,e,a,b,c,42); R3(c,d,e,a,b,43);
+ R3(b,c,d,e,a,44); R3(a,b,c,d,e,45); R3(e,a,b,c,d,46); R3(d,e,a,b,c,47);
+ R3(c,d,e,a,b,48); R3(b,c,d,e,a,49); R3(a,b,c,d,e,50); R3(e,a,b,c,d,51);
+ R3(d,e,a,b,c,52); R3(c,d,e,a,b,53); R3(b,c,d,e,a,54); R3(a,b,c,d,e,55);
+ R3(e,a,b,c,d,56); R3(d,e,a,b,c,57); R3(c,d,e,a,b,58); R3(b,c,d,e,a,59);
+ R4(a,b,c,d,e,60); R4(e,a,b,c,d,61); R4(d,e,a,b,c,62); R4(c,d,e,a,b,63);
+ R4(b,c,d,e,a,64); R4(a,b,c,d,e,65); R4(e,a,b,c,d,66); R4(d,e,a,b,c,67);
+ R4(c,d,e,a,b,68); R4(b,c,d,e,a,69); R4(a,b,c,d,e,70); R4(e,a,b,c,d,71);
+ R4(d,e,a,b,c,72); R4(c,d,e,a,b,73); R4(b,c,d,e,a,74); R4(a,b,c,d,e,75);
+ R4(e,a,b,c,d,76); R4(d,e,a,b,c,77); R4(c,d,e,a,b,78); R4(b,c,d,e,a,79);
+ /* Add the working vars back into context.state[] */
+ state[0] += a;
+ state[1] += b;
+ state[2] += c;
+ state[3] += d;
+ state[4] += e;
+ /* Wipe variables */
+ a = b = c = d = e = 0;
+#ifdef SHA1HANDSOFF
+ os_memset(block, 0, 64);
+#endif
+}
+
+
+/* SHA1Init - Initialize new context */
+
+void SHA1Init(SHA1_CTX* context)
+{
+ /* SHA1 initialization constants */
+ context->state[0] = 0x67452301;
+ context->state[1] = 0xEFCDAB89;
+ context->state[2] = 0x98BADCFE;
+ context->state[3] = 0x10325476;
+ context->state[4] = 0xC3D2E1F0;
+ context->count[0] = context->count[1] = 0;
+}
+
+
+/* Run your data through this. */
+
+void SHA1Update(SHA1_CTX* context, const void *_data, u32 len)
+{
+ u32 i, j;
+ const unsigned char *data = _data;
+
+#ifdef VERBOSE
+ SHAPrintContext(context, "before");
+#endif
+ j = (context->count[0] >> 3) & 63;
+ if ((context->count[0] += len << 3) < (len << 3))
+ context->count[1]++;
+ context->count[1] += (len >> 29);
+ if ((j + len) > 63) {
+ os_memcpy(&context->buffer[j], data, (i = 64-j));
+ SHA1Transform(context->state, context->buffer);
+ for ( ; i + 63 < len; i += 64) {
+ SHA1Transform(context->state, &data[i]);
+ }
+ j = 0;
+ }
+ else i = 0;
+ os_memcpy(&context->buffer[j], &data[i], len - i);
+#ifdef VERBOSE
+ SHAPrintContext(context, "after ");
+#endif
+}
+
+
+/* Add padding and return the message digest. */
+
+void SHA1Final(unsigned char digest[20], SHA1_CTX* context)
+{
+ u32 i;
+ unsigned char finalcount[8];
+
+ for (i = 0; i < 8; i++) {
+ finalcount[i] = (unsigned char)
+ ((context->count[(i >= 4 ? 0 : 1)] >>
+ ((3-(i & 3)) * 8) ) & 255); /* Endian independent */
+ }
+ SHA1Update(context, (unsigned char *) "\200", 1);
+ while ((context->count[0] & 504) != 448) {
+ SHA1Update(context, (unsigned char *) "\0", 1);
+ }
+ SHA1Update(context, finalcount, 8); /* Should cause a SHA1Transform()
+ */
+ for (i = 0; i < 20; i++) {
+ digest[i] = (unsigned char)
+ ((context->state[i >> 2] >> ((3 - (i & 3)) * 8)) &
+ 255);
+ }
+ /* Wipe variables */
+ i = 0;
+ os_memset(context->buffer, 0, 64);
+ os_memset(context->state, 0, 20);
+ os_memset(context->count, 0, 8);
+ os_memset(finalcount, 0, 8);
+}
+
+/* ===== end - public domain SHA1 implementation ===== */
diff --git a/src/gsm/kdf/sha1.c b/src/gsm/kdf/sha1.c
new file mode 100644
index 00000000..0d39f77b
--- /dev/null
+++ b/src/gsm/kdf/sha1.c
@@ -0,0 +1,162 @@
+/*
+ * SHA1 hash implementation and interface functions
+ * Copyright (c) 2003-2005, Jouni Malinen <j@w1.fi>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Alternatively, this software may be distributed under the terms of BSD
+ * license.
+ *
+ * See README and COPYING for more details.
+ */
+
+
+#include "common.h"
+#include "sha1.h"
+#include "crypto.h"
+
+
+/**
+ * hmac_sha1_vector - HMAC-SHA1 over data vector (RFC 2104)
+ * @key: Key for HMAC operations
+ * @key_len: Length of the key in bytes
+ * @num_elem: Number of elements in the data vector
+ * @addr: Pointers to the data areas
+ * @len: Lengths of the data blocks
+ * @mac: Buffer for the hash (20 bytes)
+ * Returns: 0 on success, -1 on failure
+ */
+int hmac_sha1_vector(const u8 *key, size_t key_len, size_t num_elem,
+ const u8 *addr[], const size_t *len, u8 *mac)
+{
+ unsigned char k_pad[64]; /* padding - key XORd with ipad/opad */
+ unsigned char tk[20];
+ const u8 *_addr[6];
+ size_t _len[6], i;
+
+ if (num_elem > 5) {
+ /*
+ * Fixed limit on the number of fragments to avoid having to
+ * allocate memory (which could fail).
+ */
+ return -1;
+ }
+
+ /* if key is longer than 64 bytes reset it to key = SHA1(key) */
+ if (key_len > 64) {
+ if (sha1_vector(1, &key, &key_len, tk))
+ return -1;
+ key = tk;
+ key_len = 20;
+ }
+
+ /* the HMAC_SHA1 transform looks like:
+ *
+ * SHA1(K XOR opad, SHA1(K XOR ipad, text))
+ *
+ * where K is an n byte key
+ * ipad is the byte 0x36 repeated 64 times
+ * opad is the byte 0x5c repeated 64 times
+ * and text is the data being protected */
+
+ /* start out by storing key in ipad */
+ os_memset(k_pad, 0, sizeof(k_pad));
+ os_memcpy(k_pad, key, key_len);
+ /* XOR key with ipad values */
+ for (i = 0; i < 64; i++)
+ k_pad[i] ^= 0x36;
+
+ /* perform inner SHA1 */
+ _addr[0] = k_pad;
+ _len[0] = 64;
+ for (i = 0; i < num_elem; i++) {
+ _addr[i + 1] = addr[i];
+ _len[i + 1] = len[i];
+ }
+ if (sha1_vector(1 + num_elem, _addr, _len, mac))
+ return -1;
+
+ os_memset(k_pad, 0, sizeof(k_pad));
+ os_memcpy(k_pad, key, key_len);
+ /* XOR key with opad values */
+ for (i = 0; i < 64; i++)
+ k_pad[i] ^= 0x5c;
+
+ /* perform outer SHA1 */
+ _addr[0] = k_pad;
+ _len[0] = 64;
+ _addr[1] = mac;
+ _len[1] = SHA1_MAC_LEN;
+ return sha1_vector(2, _addr, _len, mac);
+}
+
+
+/**
+ * hmac_sha1 - HMAC-SHA1 over data buffer (RFC 2104)
+ * @key: Key for HMAC operations
+ * @key_len: Length of the key in bytes
+ * @data: Pointers to the data area
+ * @data_len: Length of the data area
+ * @mac: Buffer for the hash (20 bytes)
+ * Returns: 0 on success, -1 of failure
+ */
+int hmac_sha1(const u8 *key, size_t key_len, const u8 *data, size_t data_len,
+ u8 *mac)
+{
+ return hmac_sha1_vector(key, key_len, 1, &data, &data_len, mac);
+}
+
+
+/**
+ * sha1_prf - SHA1-based Pseudo-Random Function (PRF) (IEEE 802.11i, 8.5.1.1)
+ * @key: Key for PRF
+ * @key_len: Length of the key in bytes
+ * @label: A unique label for each purpose of the PRF
+ * @data: Extra data to bind into the key
+ * @data_len: Length of the data
+ * @buf: Buffer for the generated pseudo-random key
+ * @buf_len: Number of bytes of key to generate
+ * Returns: 0 on success, -1 of failure
+ *
+ * This function is used to derive new, cryptographically separate keys from a
+ * given key (e.g., PMK in IEEE 802.11i).
+ */
+int sha1_prf(const u8 *key, size_t key_len, const char *label,
+ const u8 *data, size_t data_len, u8 *buf, size_t buf_len)
+{
+ u8 counter = 0;
+ size_t pos, plen;
+ u8 hash[SHA1_MAC_LEN];
+ size_t label_len = os_strlen(label) + 1;
+ const unsigned char *addr[3];
+ size_t len[3];
+
+ addr[0] = (u8 *) label;
+ len[0] = label_len;
+ addr[1] = data;
+ len[1] = data_len;
+ addr[2] = &counter;
+ len[2] = 1;
+
+ pos = 0;
+ while (pos < buf_len) {
+ plen = buf_len - pos;
+ if (plen >= SHA1_MAC_LEN) {
+ if (hmac_sha1_vector(key, key_len, 3, addr, len,
+ &buf[pos]))
+ return -1;
+ pos += SHA1_MAC_LEN;
+ } else {
+ if (hmac_sha1_vector(key, key_len, 3, addr, len,
+ hash))
+ return -1;
+ os_memcpy(&buf[pos], hash, plen);
+ break;
+ }
+ counter++;
+ }
+
+ return 0;
+}
diff --git a/src/gsm/kdf/sha1.h b/src/gsm/kdf/sha1.h
new file mode 100644
index 00000000..f0c1a5f9
--- /dev/null
+++ b/src/gsm/kdf/sha1.h
@@ -0,0 +1,33 @@
+/*
+ * SHA1 hash implementation and interface functions
+ * Copyright (c) 2003-2009, Jouni Malinen <j@w1.fi>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Alternatively, this software may be distributed under the terms of BSD
+ * license.
+ *
+ * See README and COPYING for more details.
+ */
+
+#ifndef SHA1_H
+#define SHA1_H
+
+#define SHA1_MAC_LEN 20
+
+int hmac_sha1_vector(const u8 *key, size_t key_len, size_t num_elem,
+ const u8 *addr[], const size_t *len, u8 *mac);
+int hmac_sha1(const u8 *key, size_t key_len, const u8 *data, size_t data_len,
+ u8 *mac);
+int sha1_prf(const u8 *key, size_t key_len, const char *label,
+ const u8 *data, size_t data_len, u8 *buf, size_t buf_len);
+int sha1_t_prf(const u8 *key, size_t key_len, const char *label,
+ const u8 *seed, size_t seed_len, u8 *buf, size_t buf_len);
+int __must_check tls_prf_sha1_md5(const u8 *secret, size_t secret_len,
+ const char *label, const u8 *seed,
+ size_t seed_len, u8 *out, size_t outlen);
+int pbkdf2_sha1(const char *passphrase, const char *ssid, size_t ssid_len,
+ int iterations, u8 *buf, size_t buflen);
+#endif /* SHA1_H */
diff --git a/src/gsm/kdf/sha1_i.h b/src/gsm/kdf/sha1_i.h
new file mode 100644
index 00000000..ec2f82f7
--- /dev/null
+++ b/src/gsm/kdf/sha1_i.h
@@ -0,0 +1,29 @@
+/*
+ * SHA1 internal definitions
+ * Copyright (c) 2003-2005, Jouni Malinen <j@w1.fi>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Alternatively, this software may be distributed under the terms of BSD
+ * license.
+ *
+ * See README and COPYING for more details.
+ */
+
+#ifndef SHA1_I_H
+#define SHA1_I_H
+
+struct SHA1Context {
+ u32 state[5];
+ u32 count[2];
+ unsigned char buffer[64];
+};
+
+void SHA1Init(struct SHA1Context *context);
+void SHA1Update(struct SHA1Context *context, const void *data, u32 len);
+void SHA1Final(unsigned char digest[20], struct SHA1Context *context);
+void SHA1Transform(u32 state[5], const unsigned char buffer[64]);
+
+#endif /* SHA1_I_H */
diff --git a/src/gsm/kdf/sha256-internal.c b/src/gsm/kdf/sha256-internal.c
new file mode 100644
index 00000000..18cc8f80
--- /dev/null
+++ b/src/gsm/kdf/sha256-internal.c
@@ -0,0 +1,231 @@
+/*
+ * SHA-256 hash implementation and interface functions
+ * Copyright (c) 2003-2011, Jouni Malinen <j@w1.fi>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Alternatively, this software may be distributed under the terms of BSD
+ * license.
+ *
+ * See README and COPYING for more details.
+ */
+
+
+#include "common.h"
+#include "sha256.h"
+#include "sha256_i.h"
+#include "crypto.h"
+
+
+/**
+ * sha256_vector - SHA256 hash for data vector
+ * @num_elem: Number of elements in the data vector
+ * @addr: Pointers to the data areas
+ * @len: Lengths of the data blocks
+ * @mac: Buffer for the hash
+ * Returns: 0 on success, -1 of failure
+ */
+int sha256_vector(size_t num_elem, const u8 *addr[], const size_t *len,
+ u8 *mac)
+{
+ struct sha256_state ctx;
+ size_t i;
+
+ sha256_init(&ctx);
+ for (i = 0; i < num_elem; i++)
+ if (sha256_process(&ctx, addr[i], len[i]))
+ return -1;
+ if (sha256_done(&ctx, mac))
+ return -1;
+ return 0;
+}
+
+
+/* ===== start - public domain SHA256 implementation ===== */
+
+/* This is based on SHA256 implementation in LibTomCrypt that was released into
+ * public domain by Tom St Denis. */
+
+/* the K array */
+static const unsigned long K[64] = {
+ 0x428a2f98UL, 0x71374491UL, 0xb5c0fbcfUL, 0xe9b5dba5UL, 0x3956c25bUL,
+ 0x59f111f1UL, 0x923f82a4UL, 0xab1c5ed5UL, 0xd807aa98UL, 0x12835b01UL,
+ 0x243185beUL, 0x550c7dc3UL, 0x72be5d74UL, 0x80deb1feUL, 0x9bdc06a7UL,
+ 0xc19bf174UL, 0xe49b69c1UL, 0xefbe4786UL, 0x0fc19dc6UL, 0x240ca1ccUL,
+ 0x2de92c6fUL, 0x4a7484aaUL, 0x5cb0a9dcUL, 0x76f988daUL, 0x983e5152UL,
+ 0xa831c66dUL, 0xb00327c8UL, 0xbf597fc7UL, 0xc6e00bf3UL, 0xd5a79147UL,
+ 0x06ca6351UL, 0x14292967UL, 0x27b70a85UL, 0x2e1b2138UL, 0x4d2c6dfcUL,
+ 0x53380d13UL, 0x650a7354UL, 0x766a0abbUL, 0x81c2c92eUL, 0x92722c85UL,
+ 0xa2bfe8a1UL, 0xa81a664bUL, 0xc24b8b70UL, 0xc76c51a3UL, 0xd192e819UL,
+ 0xd6990624UL, 0xf40e3585UL, 0x106aa070UL, 0x19a4c116UL, 0x1e376c08UL,
+ 0x2748774cUL, 0x34b0bcb5UL, 0x391c0cb3UL, 0x4ed8aa4aUL, 0x5b9cca4fUL,
+ 0x682e6ff3UL, 0x748f82eeUL, 0x78a5636fUL, 0x84c87814UL, 0x8cc70208UL,
+ 0x90befffaUL, 0xa4506cebUL, 0xbef9a3f7UL, 0xc67178f2UL
+};
+
+
+/* Various logical functions */
+#define RORc(x, y) \
+( ((((unsigned long) (x) & 0xFFFFFFFFUL) >> (unsigned long) ((y) & 31)) | \
+ ((unsigned long) (x) << (unsigned long) (32 - ((y) & 31)))) & 0xFFFFFFFFUL)
+#define Ch(x,y,z) (z ^ (x & (y ^ z)))
+#define Maj(x,y,z) (((x | y) & z) | (x & y))
+#define S(x, n) RORc((x), (n))
+#define R(x, n) (((x)&0xFFFFFFFFUL)>>(n))
+#define Sigma0(x) (S(x, 2) ^ S(x, 13) ^ S(x, 22))
+#define Sigma1(x) (S(x, 6) ^ S(x, 11) ^ S(x, 25))
+#define Gamma0(x) (S(x, 7) ^ S(x, 18) ^ R(x, 3))
+#define Gamma1(x) (S(x, 17) ^ S(x, 19) ^ R(x, 10))
+#ifndef MIN
+#define MIN(x, y) (((x) < (y)) ? (x) : (y))
+#endif
+
+/* compress 512-bits */
+static int sha256_compress(struct sha256_state *md, unsigned char *buf)
+{
+ u32 S[8], W[64], t0, t1;
+ u32 t;
+ int i;
+
+ /* copy state into S */
+ for (i = 0; i < 8; i++) {
+ S[i] = md->state[i];
+ }
+
+ /* copy the state into 512-bits into W[0..15] */
+ for (i = 0; i < 16; i++)
+ W[i] = WPA_GET_BE32(buf + (4 * i));
+
+ /* fill W[16..63] */
+ for (i = 16; i < 64; i++) {
+ W[i] = Gamma1(W[i - 2]) + W[i - 7] + Gamma0(W[i - 15]) +
+ W[i - 16];
+ }
+
+ /* Compress */
+#define RND(a,b,c,d,e,f,g,h,i) \
+ t0 = h + Sigma1(e) + Ch(e, f, g) + K[i] + W[i]; \
+ t1 = Sigma0(a) + Maj(a, b, c); \
+ d += t0; \
+ h = t0 + t1;
+
+ for (i = 0; i < 64; ++i) {
+ RND(S[0], S[1], S[2], S[3], S[4], S[5], S[6], S[7], i);
+ t = S[7]; S[7] = S[6]; S[6] = S[5]; S[5] = S[4];
+ S[4] = S[3]; S[3] = S[2]; S[2] = S[1]; S[1] = S[0]; S[0] = t;
+ }
+
+ /* feedback */
+ for (i = 0; i < 8; i++) {
+ md->state[i] = md->state[i] + S[i];
+ }
+ return 0;
+}
+
+
+/* Initialize the hash state */
+void sha256_init(struct sha256_state *md)
+{
+ md->curlen = 0;
+ md->length = 0;
+ md->state[0] = 0x6A09E667UL;
+ md->state[1] = 0xBB67AE85UL;
+ md->state[2] = 0x3C6EF372UL;
+ md->state[3] = 0xA54FF53AUL;
+ md->state[4] = 0x510E527FUL;
+ md->state[5] = 0x9B05688CUL;
+ md->state[6] = 0x1F83D9ABUL;
+ md->state[7] = 0x5BE0CD19UL;
+}
+
+/**
+ Process a block of memory though the hash
+ @param md The hash state
+ @param in The data to hash
+ @param inlen The length of the data (octets)
+ @return CRYPT_OK if successful
+*/
+int sha256_process(struct sha256_state *md, const unsigned char *in,
+ unsigned long inlen)
+{
+ unsigned long n;
+
+ if (md->curlen >= sizeof(md->buf))
+ return -1;
+
+ while (inlen > 0) {
+ if (md->curlen == 0 && inlen >= SHA256_BLOCK_SIZE) {
+ if (sha256_compress(md, (unsigned char *) in) < 0)
+ return -1;
+ md->length += SHA256_BLOCK_SIZE * 8;
+ in += SHA256_BLOCK_SIZE;
+ inlen -= SHA256_BLOCK_SIZE;
+ } else {
+ n = MIN(inlen, (SHA256_BLOCK_SIZE - md->curlen));
+ os_memcpy(md->buf + md->curlen, in, n);
+ md->curlen += n;
+ in += n;
+ inlen -= n;
+ if (md->curlen == SHA256_BLOCK_SIZE) {
+ if (sha256_compress(md, md->buf) < 0)
+ return -1;
+ md->length += 8 * SHA256_BLOCK_SIZE;
+ md->curlen = 0;
+ }
+ }
+ }
+
+ return 0;
+}
+
+
+/**
+ Terminate the hash to get the digest
+ @param md The hash state
+ @param out [out] The destination of the hash (32 bytes)
+ @return CRYPT_OK if successful
+*/
+int sha256_done(struct sha256_state *md, unsigned char *out)
+{
+ int i;
+
+ if (md->curlen >= sizeof(md->buf))
+ return -1;
+
+ /* increase the length of the message */
+ md->length += md->curlen * 8;
+
+ /* append the '1' bit */
+ md->buf[md->curlen++] = (unsigned char) 0x80;
+
+ /* if the length is currently above 56 bytes we append zeros
+ * then compress. Then we can fall back to padding zeros and length
+ * encoding like normal.
+ */
+ if (md->curlen > 56) {
+ while (md->curlen < SHA256_BLOCK_SIZE) {
+ md->buf[md->curlen++] = (unsigned char) 0;
+ }
+ sha256_compress(md, md->buf);
+ md->curlen = 0;
+ }
+
+ /* pad up to 56 bytes of zeroes */
+ while (md->curlen < 56) {
+ md->buf[md->curlen++] = (unsigned char) 0;
+ }
+
+ /* store length */
+ WPA_PUT_BE64(md->buf + 56, md->length);
+ sha256_compress(md, md->buf);
+
+ /* copy output */
+ for (i = 0; i < 8; i++)
+ WPA_PUT_BE32(out + (4 * i), md->state[i]);
+
+ return 0;
+}
+
+/* ===== end - public domain SHA256 implementation ===== */
diff --git a/src/gsm/kdf/sha256.c b/src/gsm/kdf/sha256.c
new file mode 100644
index 00000000..4f8b6cc5
--- /dev/null
+++ b/src/gsm/kdf/sha256.c
@@ -0,0 +1,156 @@
+/*
+ * SHA-256 hash implementation and interface functions
+ * Copyright (c) 2003-2007, Jouni Malinen <j@w1.fi>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Alternatively, this software may be distributed under the terms of BSD
+ * license.
+ *
+ * See README and COPYING for more details.
+ */
+
+
+#include "common.h"
+#include "sha256.h"
+#include "crypto.h"
+
+
+/**
+ * hmac_sha256_vector - HMAC-SHA256 over data vector (RFC 2104)
+ * @key: Key for HMAC operations
+ * @key_len: Length of the key in bytes
+ * @num_elem: Number of elements in the data vector
+ * @addr: Pointers to the data areas
+ * @len: Lengths of the data blocks
+ * @mac: Buffer for the hash (32 bytes)
+ */
+void hmac_sha256_vector(const u8 *key, size_t key_len, size_t num_elem,
+ const u8 *addr[], const size_t *len, u8 *mac)
+{
+ unsigned char k_pad[64]; /* padding - key XORd with ipad/opad */
+ unsigned char tk[32];
+ const u8 *_addr[6];
+ size_t _len[6], i;
+
+ if (num_elem > 5) {
+ /*
+ * Fixed limit on the number of fragments to avoid having to
+ * allocate memory (which could fail).
+ */
+ return;
+ }
+
+ /* if key is longer than 64 bytes reset it to key = SHA256(key) */
+ if (key_len > 64) {
+ sha256_vector(1, &key, &key_len, tk);
+ key = tk;
+ key_len = 32;
+ }
+
+ /* the HMAC_SHA256 transform looks like:
+ *
+ * SHA256(K XOR opad, SHA256(K XOR ipad, text))
+ *
+ * where K is an n byte key
+ * ipad is the byte 0x36 repeated 64 times
+ * opad is the byte 0x5c repeated 64 times
+ * and text is the data being protected */
+
+ /* start out by storing key in ipad */
+ os_memset(k_pad, 0, sizeof(k_pad));
+ os_memcpy(k_pad, key, key_len);
+ /* XOR key with ipad values */
+ for (i = 0; i < 64; i++)
+ k_pad[i] ^= 0x36;
+
+ /* perform inner SHA256 */
+ _addr[0] = k_pad;
+ _len[0] = 64;
+ for (i = 0; i < num_elem; i++) {
+ _addr[i + 1] = addr[i];
+ _len[i + 1] = len[i];
+ }
+ sha256_vector(1 + num_elem, _addr, _len, mac);
+
+ os_memset(k_pad, 0, sizeof(k_pad));
+ os_memcpy(k_pad, key, key_len);
+ /* XOR key with opad values */
+ for (i = 0; i < 64; i++)
+ k_pad[i] ^= 0x5c;
+
+ /* perform outer SHA256 */
+ _addr[0] = k_pad;
+ _len[0] = 64;
+ _addr[1] = mac;
+ _len[1] = SHA256_MAC_LEN;
+ sha256_vector(2, _addr, _len, mac);
+}
+
+
+/**
+ * hmac_sha256 - HMAC-SHA256 over data buffer (RFC 2104)
+ * @key: Key for HMAC operations
+ * @key_len: Length of the key in bytes
+ * @data: Pointers to the data area
+ * @data_len: Length of the data area
+ * @mac: Buffer for the hash (20 bytes)
+ */
+void hmac_sha256(const u8 *key, size_t key_len, const u8 *data,
+ size_t data_len, u8 *mac)
+{
+ hmac_sha256_vector(key, key_len, 1, &data, &data_len, mac);
+}
+
+
+/**
+ * sha256_prf - SHA256-based Pseudo-Random Function (IEEE 802.11r, 8.5.1.5.2)
+ * @key: Key for PRF
+ * @key_len: Length of the key in bytes
+ * @label: A unique label for each purpose of the PRF
+ * @data: Extra data to bind into the key
+ * @data_len: Length of the data
+ * @buf: Buffer for the generated pseudo-random key
+ * @buf_len: Number of bytes of key to generate
+ *
+ * This function is used to derive new, cryptographically separate keys from a
+ * given key.
+ */
+void sha256_prf(const u8 *key, size_t key_len, const char *label,
+ const u8 *data, size_t data_len, u8 *buf, size_t buf_len)
+{
+ u16 counter = 1;
+ size_t pos, plen;
+ u8 hash[SHA256_MAC_LEN];
+ const u8 *addr[4];
+ size_t len[4];
+ u8 counter_le[2], length_le[2];
+
+ addr[0] = counter_le;
+ len[0] = 2;
+ addr[1] = (u8 *) label;
+ len[1] = os_strlen(label);
+ addr[2] = data;
+ len[2] = data_len;
+ addr[3] = length_le;
+ len[3] = sizeof(length_le);
+
+ WPA_PUT_LE16(length_le, buf_len * 8);
+ pos = 0;
+ while (pos < buf_len) {
+ plen = buf_len - pos;
+ WPA_PUT_LE16(counter_le, counter);
+ if (plen >= SHA256_MAC_LEN) {
+ hmac_sha256_vector(key, key_len, 4, addr, len,
+ &buf[pos]);
+ pos += SHA256_MAC_LEN;
+ } else {
+ hmac_sha256_vector(key, key_len, 4, addr, len, hash);
+ os_memcpy(&buf[pos], hash, plen);
+ break;
+ }
+ counter++;
+ }
+}
diff --git a/src/gsm/kdf/sha256.h b/src/gsm/kdf/sha256.h
new file mode 100644
index 00000000..b1ce6afe
--- /dev/null
+++ b/src/gsm/kdf/sha256.h
@@ -0,0 +1,30 @@
+/*
+ * SHA256 hash implementation and interface functions
+ * Copyright (c) 2003-2011, Jouni Malinen <j@w1.fi>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Alternatively, this software may be distributed under the terms of BSD
+ * license.
+ *
+ * See README and COPYING for more details.
+ */
+
+#ifndef SHA256_H
+#define SHA256_H
+
+#define SHA256_MAC_LEN 32
+
+void hmac_sha256_vector(const u8 *key, size_t key_len, size_t num_elem,
+ const u8 *addr[], const size_t *len, u8 *mac);
+void hmac_sha256(const u8 *key, size_t key_len, const u8 *data,
+ size_t data_len, u8 *mac);
+void sha256_prf(const u8 *key, size_t key_len, const char *label,
+ const u8 *data, size_t data_len, u8 *buf, size_t buf_len);
+void tls_prf_sha256(const u8 *secret, size_t secret_len,
+ const char *label, const u8 *seed, size_t seed_len,
+ u8 *out, size_t outlen);
+
+#endif /* SHA256_H */
diff --git a/src/gsm/kdf/sha256_i.h b/src/gsm/kdf/sha256_i.h
new file mode 100644
index 00000000..20ae4889
--- /dev/null
+++ b/src/gsm/kdf/sha256_i.h
@@ -0,0 +1,31 @@
+/*
+ * SHA-256 internal definitions
+ * Copyright (c) 2003-2011, Jouni Malinen <j@w1.fi>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * Alternatively, this software may be distributed under the terms of BSD
+ * license.
+ *
+ * See README and COPYING for more details.
+ */
+
+#ifndef SHA256_I_H
+#define SHA256_I_H
+
+#define SHA256_BLOCK_SIZE 64
+
+struct sha256_state {
+ u64 length;
+ u32 state[8], curlen;
+ u8 buf[SHA256_BLOCK_SIZE];
+};
+
+void sha256_init(struct sha256_state *md);
+int sha256_process(struct sha256_state *md, const unsigned char *in,
+ unsigned long inlen);
+int sha256_done(struct sha256_state *md, unsigned char *out);
+
+#endif /* SHA256_I_H */
diff --git a/src/gsm/lapdm.c b/src/gsm/lapdm.c
index bfd6d263..b7f57694 100644
--- a/src/gsm/lapdm.c
+++ b/src/gsm/lapdm.c
@@ -19,10 +19,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
/*! \addtogroup lapdm
@@ -257,7 +253,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,
@@ -735,11 +731,9 @@ static int l2_ph_data_ind(struct msgb *msg, struct lapdm_entity *le,
&& LAPDm_CTRL_U_BITS(msg->l2h[3]) == 0) {
mctx.lapdm_fmt = LAPDm_FMT_B4;
n201 = N201_B4;
- LOGP(DLLAPD, LOGL_INFO, "fmt=B4\n");
} else {
mctx.lapdm_fmt = LAPDm_FMT_B;
n201 = N201_AB_SACCH;
- LOGP(DLLAPD, LOGL_INFO, "fmt=B\n");
}
/* SACCH frames have a two-byte L1 header that
* OsmocomBB L1 doesn't strip */
@@ -750,7 +744,6 @@ static int l2_ph_data_ind(struct msgb *msg, struct lapdm_entity *le,
sapi = (msg->l2h[0] >> 2) & 7;
} else {
mctx.lapdm_fmt = LAPDm_FMT_B;
- LOGP(DLLAPD, LOGL_INFO, "fmt=B\n");
n201 = N201_AB_SDCCH;
sapi = (msg->l2h[0] >> 2) & 7;
}
@@ -850,7 +843,6 @@ static int l2_ph_data_ind(struct msgb *msg, struct lapdm_entity *le,
break;
case LAPDm_FMT_Bbis:
/* directly pass up to layer3 */
- LOGP(DLLAPD, LOGL_INFO, "fmt=Bbis UI\n");
msg->l3h = msg->l2h;
msgb_pull_to_l3(msg);
rc = send_rslms_rll_l3(RSL_MT_UNIT_DATA_IND, &mctx, msg);
diff --git a/src/gsm/libosmogsm.map b/src/gsm/libosmogsm.map
index 4ece1079..6795c578 100644
--- a/src/gsm/libosmogsm.map
+++ b/src/gsm/libosmogsm.map
@@ -121,6 +121,10 @@ gsm0503_cs3;
gsm0503_cs2_np;
gsm0503_cs3_np;
gsm0503_tch_fr;
+gsm0503_tch_f24;
+gsm0503_tch_f48;
+gsm0503_tch_f96;
+gsm0503_tch_f144;
gsm0503_tch_hr;
gsm0503_tch_afs_12_2;
gsm0503_tch_afs_10_2;
@@ -154,6 +158,7 @@ gsm0503_mcs8;
gsm0503_mcs9;
gsm0808_att_tlvdef;
+gsm0808_old_bss_to_new_bss_info_att_tlvdef;
gsm0808_bssap_name;
gsm0808_bssmap_name;
gsm0808_cause_name;
@@ -169,6 +174,7 @@ gsm0808_create_ass_compl2;
gsm0808_create_assignment_failure;
gsm0808_create_ass_fail;
gsm0808_create_cipher;
+gsm0808_create_cipher2;
gsm0808_create_cipher_complete;
gsm0808_create_cipher_reject;
gsm0808_create_cipher_reject_ext;
@@ -210,13 +216,17 @@ gsm0808_enc_aoip_trasp_addr;
gsm0808_dec_aoip_trasp_addr;
gsm0808_dec_osmux_cid;
gsm0808_enc_speech_codec;
+gsm0808_enc_speech_codec2;
gsm0808_dec_speech_codec;
gsm0808_enc_speech_codec_list;
+gsm0808_enc_speech_codec_list2;
gsm0808_dec_speech_codec_list;
gsm0808_enc_channel_type;
gsm0808_dec_channel_type;
gsm0808_enc_encrypt_info;
gsm0808_dec_encrypt_info;
+gsm0808_enc_kc128;
+gsm0808_dec_kc128;
gsm0808_enc_cell_id_list;
gsm0808_enc_cell_id_list2;
gsm0808_dec_cell_id_list;
@@ -242,6 +252,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;
@@ -255,6 +267,9 @@ gsm0808_enc_lcls;
gsm0808_dec_lcls;
gsm0808_msgb_put_cell_id_u;
gsm0808_decode_cell_id_u;
+gsm0808_create_perform_location_request;
+gsm0808_create_perform_location_response;
+gsm0808_create_perform_location_abort;
gsm29118_msgb_alloc;
gsm29118_create_alert_req;
@@ -317,11 +332,15 @@ osmo_gsm48_rest_octets_si2quater_encode;
osmo_gsm48_rest_octets_si6_encode;
osmo_gsm48_rest_octets_si3_encode;
osmo_gsm48_rest_octets_si4_encode;
+osmo_gsm48_rest_octets_si13_decode;
osmo_gsm48_rest_octets_si13_encode;
osmo_gsm48_rest_octets_si3_decode;
+osmo_gsm48_rest_octets_si4_decode;
+gsm48_rr_short_pd_msg_name;
gsm48_rr_msg_name;
gsm48_cc_state_name;
gsm48_construct_ra;
+gsm48_ra_equal;
gsm48_encode_ra;
gsm48_hdr_gmm_cipherable;
gsm48_decode_bcd_number;
@@ -335,6 +354,7 @@ gsm48_decode_cccap;
gsm48_decode_connected;
gsm48_decode_facility;
gsm48_decode_freq_list;
+gsm48_decode_classmark3;
gsm48_decode_keypad;
gsm48_decode_lai;
gsm48_decode_notify;
@@ -365,6 +385,8 @@ gsm48_generate_mid;
gsm48_generate_mid_from_imsi;
gsm48_generate_mid_from_tmsi;
gsm48_mi_to_string;
+gsm48_chan_mode_to_vamos;
+gsm48_chan_mode_to_non_vamos;
osmo_mobile_identity_to_str_buf;
osmo_mobile_identity_to_str_c;
osmo_mobile_identity_cmp;
@@ -389,6 +411,7 @@ gsm48_generate_lai2;
gsm48_decode_lai2;
osmo_bts_features_descs;
osmo_bts_feature_name;
+osmo_bts_features_names;
osmo_plmn_to_bcd;
osmo_plmn_from_bcd;
osmo_mcc_name;
@@ -401,16 +424,27 @@ osmo_plmn_name;
osmo_plmn_name_buf;
osmo_plmn_name_c;
osmo_plmn_name2;
+osmo_lai_cmp;
osmo_lai_name;
osmo_lai_name_buf;
osmo_lai_name_c;
+osmo_rai_cmp;
osmo_rai_name;
osmo_rai_name_buf;
osmo_rai_name_c;
+osmo_rai_name2;
+osmo_rai_name2_buf;
+osmo_rai_name2_c;
+osmo_cgi_cmp;
osmo_cgi_name;
osmo_cgi_name_buf;
osmo_cgi_name_c;
osmo_cgi_name2;
+osmo_cgi_ps_cmp;
+osmo_cgi_ps_name;
+osmo_cgi_ps_name_buf;
+osmo_cgi_ps_name_c;
+osmo_cgi_ps_name2;
osmo_gummei_name;
osmo_gummei_name_buf;
osmo_gummei_name_c;
@@ -462,6 +496,7 @@ osmo_dump_gsmtime_c;
gsm_milenage;
gsm_septet_encode;
+gsm_septet_pack;
gsm_septets2octets;
lapd_dl_exit;
@@ -524,6 +559,12 @@ osmo_auth_supported;
osmo_auth_c3;
osmo_sub_auth_type_names;
+osmo_kdf_kc128;
+osmo_kdf_kasme;
+osmo_kdf_enb;
+osmo_kdf_nh;
+osmo_kdf_nas;
+
osmo_rsl2sitype;
osmo_sitype2rsl;
@@ -575,6 +616,11 @@ osmo_shift_tlv;
osmo_match_shift_tlv;
osmo_shift_lv;
+osmo_tlv_prot_msg_name;
+osmo_tlv_prot_ie_name;
+osmo_tlv_prot_validate_tp;
+osmo_tlv_prot_parse;
+
gan_msgt_vals;
gan_pdisc_vals;
@@ -705,5 +751,51 @@ osmo_nri_ranges_vty_del;
osmo_nri_ranges_to_str_buf;
osmo_nri_ranges_to_str_c;
+osmo_bssmap_le_msgt_names;
+osmo_bssap_le_enc;
+osmo_bssap_le_dec;
+osmo_lcs_cause_enc;
+osmo_lcs_cause_dec;
+osmo_bssmap_le_ie_enc_location_type;
+osmo_bssmap_le_ie_dec_location_type;
+osmo_bssmap_le_msgt;
+osmo_bssap_le_pdu_to_str_buf;
+osmo_bssap_le_pdu_to_str_c;
+
+osmo_bsslap_enc;
+osmo_bsslap_dec;
+osmo_bsslap_msgt_names;
+
+osmo_gad_enc;
+osmo_gad_dec;
+osmo_gad_to_str_buf;
+osmo_gad_to_str_c;
+osmo_gad_enc_lat;
+osmo_gad_dec_lat;
+osmo_gad_enc_lon;
+osmo_gad_dec_lon;
+osmo_gad_enc_unc;
+osmo_gad_dec_unc;
+osmo_gad_raw_read;
+osmo_gad_raw_write;
+osmo_gad_type_names;
+
+osmo_iuup_compute_header_crc;
+osmo_iuup_compute_payload_crc;
+osmo_iuup_instance_alloc;
+osmo_iuup_instance_free;
+osmo_iuup_instance_set_user_prim_cb;
+osmo_iuup_instance_set_transport_prim_cb;
+osmo_iuup_tnl_prim_up;
+osmo_iuup_rnl_prim_down;
+osmo_iuup_rnl_prim_alloc;
+osmo_iuup_tnl_prim_alloc;
+
+osmo_csd_12k_6k_decode_frame;
+osmo_csd_12k_6k_encode_frame;
+osmo_csd_3k6_decode_frame;
+osmo_csd_3k6_encode_frame;
+osmo_csd_ubit_dump;
+
local: *;
};
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 cdbdf913..3668ceac 100644
--- a/src/gsm/rsl.c
+++ b/src/gsm/rsl.c
@@ -17,10 +17,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <stdint.h>
@@ -126,6 +122,11 @@ const struct tlv_definition rsl_att_tlvdef = {
[RSL_IE_TFO_STATUS] = { TLV_TYPE_TV },
[RSL_IE_LLP_APDU] = { TLV_TYPE_TLV },
[RSL_IE_SIEMENS_MRPCI] = { TLV_TYPE_TV },
+ [RSL_IE_OSMO_REP_ACCH_CAP] = { TLV_TYPE_TLV },
+ [RSL_IE_OSMO_TRAINING_SEQUENCE] = { TLV_TYPE_TLV },
+ [RSL_IE_OSMO_TEMP_OVP_ACCH_CAP] = { TLV_TYPE_TLV },
+ [RSL_IE_OSMO_OSMUX_CID] = { TLV_TYPE_TLV },
+ [RSL_IE_IPAC_SRTP_CONFIG] = { TLV_TYPE_TLV },
[RSL_IE_IPAC_PROXY_UDP] = { TLV_TYPE_FIXED, 2 },
[RSL_IE_IPAC_BSCMPL_TOUT] = { TLV_TYPE_TV },
[RSL_IE_IPAC_REMOTE_IP] = { TLV_TYPE_FIXED, 4 },
@@ -134,6 +135,8 @@ const struct tlv_definition rsl_att_tlvdef = {
[RSL_IE_IPAC_LOCAL_PORT] = { TLV_TYPE_FIXED, 2 },
[RSL_IE_IPAC_SPEECH_MODE] = { TLV_TYPE_TV },
[RSL_IE_IPAC_LOCAL_IP] = { TLV_TYPE_FIXED, 4 },
+ [RSL_IE_IPAC_CONN_STAT] = { TLV_TYPE_TLV, 28 },
+ [RSL_IE_IPAC_HO_C_PARMS] = { TLV_TYPE_TLV },
[RSL_IE_IPAC_CONN_ID] = { TLV_TYPE_FIXED, 2 },
[RSL_IE_IPAC_RTP_CSD_FMT] = { TLV_TYPE_TV },
[RSL_IE_IPAC_RTP_JIT_BUF] = { TLV_TYPE_FIXED, 2 },
@@ -157,6 +160,7 @@ uint8_t rsl_enc_chan_nr(uint8_t type, uint8_t subch, uint8_t timeslot)
switch (type) {
case RSL_CHAN_Lm_ACCHs:
+ case RSL_CHAN_OSMO_VAMOS_Lm_ACCHs:
subch &= 0x01;
break;
case RSL_CHAN_SDCCH4_ACCH:
@@ -185,38 +189,34 @@ int rsl_dec_chan_nr(uint8_t chan_nr, uint8_t *type, uint8_t *subch, uint8_t *tim
{
*timeslot = chan_nr & 0x7;
- if ((chan_nr & 0xf8) == RSL_CHAN_Bm_ACCHs) {
- *type = RSL_CHAN_Bm_ACCHs;
- *subch = 0;
- } else if ((chan_nr & 0xf0) == RSL_CHAN_Lm_ACCHs) {
- *type = RSL_CHAN_Lm_ACCHs;
- *subch = (chan_nr >> 3) & 0x1;
- } else if ((chan_nr & 0xe0) == RSL_CHAN_SDCCH4_ACCH) {
- *type = RSL_CHAN_SDCCH4_ACCH;
- *subch = (chan_nr >> 3) & 0x3;
- } else if ((chan_nr & 0xc0) == RSL_CHAN_SDCCH8_ACCH) {
- *type = RSL_CHAN_SDCCH8_ACCH;
- *subch = (chan_nr >> 3) & 0x7;
- } else if ((chan_nr & 0xf8) == RSL_CHAN_BCCH) {
- *type = RSL_CHAN_BCCH;
- *subch = 0;
- } else if ((chan_nr & 0xf8) == RSL_CHAN_RACH) {
- *type = RSL_CHAN_RACH;
- *subch = 0;
- } else if ((chan_nr & 0xf8) == RSL_CHAN_PCH_AGCH) {
- *type = RSL_CHAN_PCH_AGCH;
- *subch = 0;
- } else if ((chan_nr & 0xf8) == RSL_CHAN_OSMO_PDCH) {
- *type = RSL_CHAN_OSMO_PDCH;
+ switch (chan_nr & RSL_CHAN_NR_MASK) {
+ case RSL_CHAN_Bm_ACCHs:
+ case RSL_CHAN_BCCH:
+ case RSL_CHAN_RACH:
+ case RSL_CHAN_PCH_AGCH:
+ case RSL_CHAN_OSMO_PDCH:
+ case RSL_CHAN_OSMO_CBCH4:
+ case RSL_CHAN_OSMO_CBCH8:
+ case RSL_CHAN_OSMO_VAMOS_Bm_ACCHs:
+ *type = chan_nr & RSL_CHAN_NR_MASK;
*subch = 0;
- } else if ((chan_nr & 0xf8) == RSL_CHAN_OSMO_CBCH4) {
- *type = RSL_CHAN_OSMO_CBCH4;
- *subch = 0;
- } else if ((chan_nr & 0xf8) == RSL_CHAN_OSMO_CBCH8) {
- *type = RSL_CHAN_OSMO_CBCH8;
- *subch = 0;
- } else
- return -EINVAL;
+ break;
+ default:
+ if ((chan_nr & 0xf0) == RSL_CHAN_Lm_ACCHs) {
+ *type = RSL_CHAN_Lm_ACCHs;
+ *subch = (chan_nr >> 3) & 0x1;
+ } else if ((chan_nr & 0xe0) == RSL_CHAN_SDCCH4_ACCH) {
+ *type = RSL_CHAN_SDCCH4_ACCH;
+ *subch = (chan_nr >> 3) & 0x3;
+ } else if ((chan_nr & 0xc0) == RSL_CHAN_SDCCH8_ACCH) {
+ *type = RSL_CHAN_SDCCH8_ACCH;
+ *subch = (chan_nr >> 3) & 0x7;
+ } else if ((chan_nr & 0xf0) == RSL_CHAN_OSMO_VAMOS_Lm_ACCHs) {
+ *type = RSL_CHAN_OSMO_VAMOS_Lm_ACCHs;
+ *subch = (chan_nr >> 3) & 0x1;
+ } else
+ return -EINVAL;
+ }
return 0;
}
@@ -232,26 +232,30 @@ char *rsl_chan_nr_str_buf(char *buf, size_t buf_len, uint8_t chan_nr)
int ts = chan_nr & 7;
uint8_t cbits = chan_nr >> 3;
- if (cbits == 0x01)
+ if (cbits == ABIS_RSL_CHAN_NR_CBITS_Bm_ACCHs)
snprintf(buf, buf_len, "TCH/F on TS%d", ts);
- else if ((cbits & 0x1e) == 0x02)
+ else if ((cbits & 0x1e) == ABIS_RSL_CHAN_NR_CBITS_Lm_ACCHs(0))
snprintf(buf, buf_len, "TCH/H(%u) on TS%d", cbits & 0x01, ts);
- else if ((cbits & 0x1c) == 0x04)
+ else if ((cbits & 0x1c) == ABIS_RSL_CHAN_NR_CBITS_SDCCH4_ACCH(0))
snprintf(buf, buf_len, "SDCCH/4(%u) on TS%d", cbits & 0x03, ts);
- else if ((cbits & 0x18) == 0x08)
+ else if ((cbits & 0x18) == ABIS_RSL_CHAN_NR_CBITS_SDCCH8_ACCH(0))
snprintf(buf, buf_len, "SDCCH/8(%u) on TS%d", cbits & 0x07, ts);
- else if (cbits == 0x10)
+ else if (cbits == ABIS_RSL_CHAN_NR_CBITS_BCCH)
snprintf(buf, buf_len, "BCCH on TS%d", ts);
- else if (cbits == 0x11)
+ else if (cbits == ABIS_RSL_CHAN_NR_CBITS_RACH)
snprintf(buf, buf_len, "RACH on TS%d", ts);
- else if (cbits == 0x12)
+ else if (cbits == ABIS_RSL_CHAN_NR_CBITS_PCH_AGCH)
snprintf(buf, buf_len, "PCH/AGCH on TS%d", ts);
- else if (cbits == 0x18)
+ else if (cbits == ABIS_RSL_CHAN_NR_CBITS_OSMO_PDCH)
snprintf(buf, buf_len, "PDCH on TS%d", ts);
- else if (cbits == 0x19)
+ else if (cbits == ABIS_RSL_CHAN_NR_CBITS_OSMO_CBCH4)
snprintf(buf, buf_len, "CBCH(SDCCH/4) on TS%d", ts);
- else if (cbits == 0x1a)
+ else if (cbits == ABIS_RSL_CHAN_NR_CBITS_OSMO_CBCH8)
snprintf(buf, buf_len, "CBCH(SDCCH/8) on TS%d", ts);
+ else if (cbits == ABIS_RSL_CHAN_NR_CBITS_OSMO_VAMOS_Bm_ACCHs)
+ snprintf(buf, buf_len, "VAMOS TCH/F on TS%d", ts);
+ else if ((cbits & 0x1e) == ABIS_RSL_CHAN_NR_CBITS_OSMO_VAMOS_Lm_ACCHs(0))
+ snprintf(buf, buf_len, "VAMOS TCH/H(%u) on TS%d", cbits & 0x01, ts);
else
snprintf(buf, buf_len, "UNKNOWN on TS%d", ts);
@@ -264,7 +268,7 @@ char *rsl_chan_nr_str_buf(char *buf, size_t buf_len, uint8_t chan_nr)
*/
const char *rsl_chan_nr_str(uint8_t chan_nr)
{
- static __thread char str[20];
+ static __thread char str[32];
return rsl_chan_nr_str_buf(str, sizeof(str), chan_nr);
}
@@ -275,10 +279,10 @@ const char *rsl_chan_nr_str(uint8_t chan_nr)
*/
char *rsl_chan_nr_str_c(const void *ctx, uint8_t chan_nr)
{
- char *str = talloc_size(ctx, 20);
+ char *str = talloc_size(ctx, 32);
if (!str)
return NULL;
- return rsl_chan_nr_str_buf(str, 20, chan_nr);
+ return rsl_chan_nr_str_buf(str, 32, chan_nr);
}
static const struct value_string rsl_err_vals[] = {
@@ -617,6 +621,10 @@ const struct tlv_definition rsl_ipac_eie_tlvdef = {
[RSL_IPAC_EIE_3G_NCELL_LIST] = { TLV_TYPE_TLV },
[RSL_IPAC_EIE_SDCCH_CTL_PARAM] = { TLV_TYPE_TV },
[RSL_IPAC_EIE_AMR_CONV_THRESH] = { TLV_TYPE_FIXED, 9 },
+ /* Osmocom extensions: */
+ [RSL_IPAC_EIE_OSMO_MEAS_AVG_CFG]= { TLV_TYPE_TLV },
+ [RSL_IPAC_EIE_OSMO_MS_PWR_CTL] = { TLV_TYPE_TLV },
+ [RSL_IPAC_EIE_OSMO_PC_THRESH_COMP]= { TLV_TYPE_TLV },
},
};
diff --git a/src/gsm/rxlev_stat.c b/src/gsm/rxlev_stat.c
index 9c650cc1..f1b77f4c 100644
--- a/src/gsm/rxlev_stat.c
+++ b/src/gsm/rxlev_stat.c
@@ -17,10 +17,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <unistd.h>
diff --git a/src/gsm/sysinfo.c b/src/gsm/sysinfo.c
index b615871f..40e26524 100644
--- a/src/gsm/sysinfo.c
+++ b/src/gsm/sysinfo.c
@@ -46,7 +46,7 @@ osmo_static_assert(sizeof(struct gsm48_system_information_type_4) == 13, _si4_si
/* bs11 forgot the l2 len, 0-6 rest octets */
osmo_static_assert(sizeof(struct gsm48_system_information_type_5) == 18, _si5_size);
osmo_static_assert(sizeof(struct gsm48_system_information_type_6) == 11, _si6_size);
-
+osmo_static_assert(sizeof(struct gsm48_system_information_type_10) == 1, _si10_size);
osmo_static_assert(sizeof(struct gsm48_system_information_type_13) == 3, _si13_size);
static const uint8_t sitype2rsl[_MAX_SYSINFO_TYPE] = {
diff --git a/src/gsm/tlv_parser.c b/src/gsm/tlv_parser.c
index 159b42bd..de766881 100644
--- a/src/gsm/tlv_parser.c
+++ b/src/gsm/tlv_parser.c
@@ -1,4 +1,4 @@
-/* (C) 2008-2017 by Harald Welte <laforge@gnumonks.org>
+/* (C) 2008-2020 by Harald Welte <laforge@gnumonks.org>
* (C) 2016-2017 by sysmocom - s.f.m.c. GmbH
*
* All Rights Reserved
@@ -24,6 +24,7 @@
#include <stdint.h>
#include <errno.h>
#include <osmocom/core/utils.h>
+#include <osmocom/core/logging.h>
#include <osmocom/gsm/tlv.h>
/*! \addtogroup tlv
@@ -125,7 +126,7 @@ int osmo_tlvp_merge(struct tlv_parsed *dst, const struct tlv_parsed *src)
* \param[inout] msg Caller-allocated message buffer with sufficient tailroom
* \param[in] type TLV type/format to use during encode
* \param[in] tag Tag of TLV to be encoded
- * \parma[in] len Length of TLV to be encoded
+ * \param[in] len Length of TLV to be encoded
* \param[in] val Value part of TLV to be encoded
* \returns 0 on success; negative in case of error */
int tlv_encode_one(struct msgb *msg, enum tlv_type type, uint8_t tag,
@@ -231,7 +232,10 @@ int tlv_parse_one(uint8_t *o_tag, uint16_t *o_len, const uint8_t **o_val,
const uint8_t *buf, int buf_len)
{
uint8_t tag;
- int len;
+ int len; /* number of bytes consumed by TLV entry */
+
+ if (buf_len < 1)
+ return OSMO_TLVP_ERR_OFS_BEYOND_BUFFER;
tag = *buf;
*o_tag = tag;
@@ -264,56 +268,54 @@ int tlv_parse_one(uint8_t *o_tag, uint16_t *o_len, const uint8_t **o_val,
break;
case TLV_TYPE_TLV:
tlv: /* GSM TS 04.07 11.2.4: Type 4 TLV */
- if (buf + 1 > buf + buf_len)
- return -1;
+ if (buf_len < 2)
+ return OSMO_TLVP_ERR_OFS_BEYOND_BUFFER;
*o_val = buf+2;
*o_len = *(buf+1);
len = *o_len + 2;
- if (len > buf_len)
- return -2;
break;
case TLV_TYPE_vTvLV_GAN: /* 44.318 / 11.1.4 */
/* FIXME: variable-length TAG! */
+ if (buf_len < 2)
+ return OSMO_TLVP_ERR_OFS_BEYOND_BUFFER;
if (*(buf+1) & 0x80) {
+ if (buf_len < 3)
+ return OSMO_TLVP_ERR_OFS_BEYOND_BUFFER;
/* like TL16Vbut without highest bit of len */
- if (2 > buf_len)
- return -1;
*o_val = buf+3;
*o_len = (*(buf+1) & 0x7F) << 8 | *(buf+2);
len = *o_len + 3;
- if (len > buf_len)
- return -2;
} else {
/* like TLV */
goto tlv;
}
break;
case TLV_TYPE_TvLV:
+ if (buf_len < 2)
+ return OSMO_TLVP_ERR_OFS_BEYOND_BUFFER;
if (*(buf+1) & 0x80) {
/* like TLV, but without highest bit of len */
- if (buf + 1 > buf + buf_len)
- return -1;
*o_val = buf+2;
*o_len = *(buf+1) & 0x7f;
len = *o_len + 2;
- if (len > buf_len)
- return -2;
break;
}
/* like TL16V, fallthrough */
case TLV_TYPE_TL16V:
- if (2 > buf_len)
- return -1;
+ if (buf_len < 3)
+ return OSMO_TLVP_ERR_OFS_BEYOND_BUFFER;
*o_val = buf+3;
*o_len = *(buf+1) << 8 | *(buf+2);
len = *o_len + 3;
- if (len > buf_len)
- return -2;
break;
default:
- return -3;
+ return OSMO_TLVP_ERR_UNKNOWN_TLV_TYPE;
}
+ if (buf_len < len) {
+ *o_val = NULL;
+ return OSMO_TLVP_ERR_OFS_LEN_BEYOND_BUFFER;
+ }
return len;
}
@@ -369,12 +371,12 @@ int tlv_parse2(struct tlv_parsed *dec, int dec_multiples,
const uint8_t *val;
uint16_t parsed_len;
if (ofs > buf_len)
- return -1;
+ return OSMO_TLVP_ERR_OFS_BEYOND_BUFFER;
val = &buf[ofs+1];
len = buf[ofs];
parsed_len = len + 1;
if (ofs + parsed_len > buf_len)
- return -2;
+ return OSMO_TLVP_ERR_OFS_LEN_BEYOND_BUFFER;
num_parsed++;
ofs += parsed_len;
/* store the resulting val and len */
@@ -390,12 +392,12 @@ int tlv_parse2(struct tlv_parsed *dec, int dec_multiples,
const uint8_t *val;
uint16_t parsed_len;
if (ofs > buf_len)
- return -1;
+ return OSMO_TLVP_ERR_OFS_BEYOND_BUFFER;
val = &buf[ofs+1];
len = buf[ofs];
parsed_len = len + 1;
if (ofs + parsed_len > buf_len)
- return -2;
+ return OSMO_TLVP_ERR_OFS_LEN_BEYOND_BUFFER;
num_parsed++;
ofs += parsed_len;
/* store the resulting val and len */
@@ -431,7 +433,7 @@ int tlv_parse2(struct tlv_parsed *dec, int dec_multiples,
return num_parsed;
}
-/*! take a master (src) tlvdev and fill up all empty slots in 'dst'
+/*! take a master (src) tlv_definition and fill up all empty slots in 'dst'
* \param dst TLV parser definition that is to be patched
* \param[in] src TLV parser definition whose content is patched into \a dst */
void tlv_def_patch(struct tlv_definition *dst, const struct tlv_definition *src)
@@ -627,4 +629,108 @@ fail:
return -1;
}
+static __thread char ienamebuf[32];
+static __thread char msgnamebuf[32];
+
+/*! get the message name for given msg_type in protocol pdef */
+const char *osmo_tlv_prot_msg_name(const struct osmo_tlv_prot_def *pdef, uint8_t msg_type)
+{
+ if (pdef->msg_def[msg_type].name) {
+ return pdef->msg_def[msg_type].name;
+ } else if (pdef->msgt_names) {
+ return get_value_string(pdef->msgt_names, msg_type);
+ } else {
+ snprintf(msgnamebuf, sizeof(msgnamebuf), "Unknown msg_type 0x%02x", msg_type);
+ return msgnamebuf;
+ }
+}
+
+/*! get the IE name for given IEI in protocol pdef */
+const char *osmo_tlv_prot_ie_name(const struct osmo_tlv_prot_def *pdef, uint8_t iei)
+{
+ if (pdef->ie_def[iei].name) {
+ return pdef->ie_def[iei].name;
+ } else {
+ snprintf(ienamebuf, sizeof(ienamebuf), "Unknown IEI 0x%02x", iei);
+ return ienamebuf;
+ }
+}
+
+/*! Validate an already TLV-decoded message against the protocol definition.
+ * \param[in] pdef protocol definition of given protocol
+ * \param[in] msg_type message type of the parsed message
+ * \param[in] tp TLV parser result
+ * \param[in] log_subsys logging sub-system for log messages
+ * \param[in] log_pfx prefix for log messages
+ * \returns 0 in case of success; negative osmo_tlv_parser_error in case of error
+ */
+int osmo_tlv_prot_validate_tp(const struct osmo_tlv_prot_def *pdef, uint8_t msg_type,
+ const struct tlv_parsed *tp, int log_subsys, const char *log_pfx)
+{
+ const struct osmo_tlv_prot_msg_def *msg_def= &pdef->msg_def[msg_type];
+ unsigned int err = 0;
+ unsigned int i;
+
+ if (msg_def->mand_ies) {
+ for (i = 0; i < msg_def->mand_count; i++) {
+ uint8_t iei = msg_def->mand_ies[i];
+ if (!TLVP_PRESENT(tp, iei)) {
+ LOGP(log_subsys, LOGL_ERROR, "%s %s %s: Missing Mandatory IE: %s\n",
+ log_pfx, pdef->name, osmo_tlv_prot_msg_name(pdef, msg_type),
+ osmo_tlv_prot_ie_name(pdef, iei));
+ if (!err)
+ err = OSMO_TLVP_ERR_MAND_IE_MISSING;
+ }
+ }
+ }
+
+ for (i = 0; i < ARRAY_SIZE(tp->lv); i++) {
+ uint16_t min_len;
+
+ if (!TLVP_PRESENT(tp, i))
+ continue;
+
+ min_len = pdef->ie_def[i].min_len;
+ if (TLVP_LEN(tp, i) < min_len) {
+ LOGP(log_subsys, LOGL_ERROR, "%s %s %s: Short IE %s: %u < %u\n", log_pfx,
+ pdef->name, osmo_tlv_prot_msg_name(pdef, msg_type),
+ osmo_tlv_prot_ie_name(pdef, i), TLVP_LEN(tp, i), min_len);
+ if (!err)
+ err = OSMO_TLVP_ERR_IE_TOO_SHORT;
+ }
+ }
+
+ return err;
+}
+
+/*! Parse + Validate a TLV-encoded message against the protocol definition.
+ * \param[in] pdef protocol definition of given protocol
+ * \param[out] dec caller-allocated pointer to \ref tlv_parsed
+ * \param[in] dec_multiples length of the tlv_parsed[] in \a dec.
+ * \param[in] msg_type message type of the parsed message
+ * \param[in] buf the input data buffer to be parsed
+ * \param[in] buf_len length of the input data buffer
+ * \param[in] lv_tag an initial LV tag at the start of the buffer
+ * \param[in] lv_tag2 a second initial LV tag following the \a lv_tag
+ * \param[in] log_subsys logging sub-system for log messages
+ * \param[in] log_pfx prefix for log messages
+ * \returns 0 in case of success; negative osmo_tlv_parser_error in case of error
+ */
+int osmo_tlv_prot_parse(const struct osmo_tlv_prot_def *pdef,
+ struct tlv_parsed *dec, unsigned int dec_multiples, uint8_t msg_type,
+ const uint8_t *buf, unsigned int buf_len, uint8_t lv_tag, uint8_t lv_tag2,
+ int log_subsys, const char *log_pfx)
+{
+ int rc;
+
+ rc = tlv_parse2(dec, dec_multiples, pdef->tlv_def, buf, buf_len, lv_tag, lv_tag2);
+ if (rc < 0) {
+ LOGP(log_subsys, LOGL_ERROR, "%s %s %s: TLV parser error %d\n", log_pfx,
+ pdef->name, osmo_tlv_prot_msg_name(pdef, msg_type), rc);
+ return rc;
+ }
+
+ return osmo_tlv_prot_validate_tp(pdef, msg_type, dec, log_subsys, log_pfx);
+}
+
/*! @} */
diff --git a/src/isdn/Makefile.am b/src/isdn/Makefile.am
new file mode 100644
index 00000000..3bb7b047
--- /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=0:0:0
+
+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 91ab2a19..0e848166 100644
--- a/src/gsm/i460_mux.c
+++ b/src/isdn/i460_mux.c
@@ -14,11 +14,6 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
- * MA 02110-1301, USA.
*/
#include <errno.h>
@@ -26,10 +21,12 @@
#include <osmocom/core/bits.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/msgb.h>
-#include <osmocom/gsm/i460_mux.h>
+#include <osmocom/isdn/i460_mux.h>
-/* count the number of sub-channels in this I460 slot */
-static int osmo_i460_subchan_count(struct osmo_i460_timeslot *ts)
+/*! count the number of sub-channels in this I.460 slot.
+ * \param[in] ts timeslot that holds the I.460 subchannels.
+ * \return number of subchannels. */
+int osmo_i460_subchan_count(struct osmo_i460_timeslot *ts)
{
int i, num_used = 0;
@@ -124,10 +121,10 @@ static void demux_subchan_extract_bits(struct osmo_i460_subchan *schan, const ui
}
}
-/*! Data from E1 timeslot into de-multiplexer
- * \param[in] ts timeslot state
- * \param[in] data input data bytes as received from E1/T1
- * \param[in] data_len length of data in bytes */
+/*! Feed multiplexed data (from an E1 timeslot) into de-multiplexer.
+ * \param[in] ts timeslot state.
+ * \param[in] data input data bytes as received from E1/T1.
+ * \param[in] data_len length of data in bytes. */
void osmo_i460_demux_in(struct osmo_i460_timeslot *ts, const uint8_t *data, size_t data_len)
{
struct osmo_i460_subchan *schan;
@@ -200,7 +197,7 @@ static ubit_t mux_schan_provide_bit(struct osmo_i460_subchan *schan)
/*! provide one byte with the subchan-specific bits of given sub-channel.
* \param[in] schan sub-channel that is to provide bits
- * \parma[out] mask bitmask of those bits filled in
+ * \param[out] mask bitmask of those bits filled in
* \returns bits of given sub-channel */
static uint8_t mux_subchan_provide_bits(struct osmo_i460_subchan *schan, uint8_t *mask)
{
@@ -267,11 +264,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 6a56572b..6475cb0f 100644
--- a/src/gsm/lapd_core.c
+++ b/src/isdn/lapd_core.c
@@ -18,10 +18,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
/*! \addtogroup lapd
@@ -73,6 +69,7 @@
//#define TEST_CONTENT_RESOLUTION_NETWORK
#include <stdio.h>
+#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include <errno.h>
@@ -82,7 +79,7 @@
#include <osmocom/core/msgb.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/talloc.h>
-#include <osmocom/gsm/lapd_core.h>
+#include <osmocom/isdn/lapd_core.h>
#include <osmocom/gsm/rsl.h>
/* TS 04.06 Table 4 / Section 3.8.1 */
@@ -624,8 +621,6 @@ static void lapd_t200_cb(void *data)
if (dl->retrans_ctr >= dl->n200_est_rel + 1) {
/* send MDL ERROR INIDCATION to L3 */
mdl_error(MDL_CAUSE_T200_EXPIRED, &dl->lctx);
- /* send RELEASE INDICATION to L3 */
- send_dl_simple(PRIM_DL_REL, PRIM_OP_CONFIRM, &dl->lctx);
/* flush tx and send buffers */
lapd_dl_flush_tx(dl);
lapd_dl_flush_send(dl);
@@ -634,6 +629,8 @@ static void lapd_t200_cb(void *data)
/* NOTE: we must not change any other states or buffers
* and queues, since we may reconnect after handover
* failure. the buffered messages is replaced there */
+ /* send RELEASE INDICATION to L3 */
+ send_dl_simple(PRIM_DL_REL, PRIM_OP_CONFIRM, &dl->lctx);
break;
}
/* retransmit DISC command */
@@ -746,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 ? */
@@ -771,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. */
}
@@ -781,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);
}
@@ -814,420 +810,485 @@ static void lapd_acknowledge(struct lapd_msg_ctx *lctx)
/* L1 -> L2 */
-/* Receive a LAPD U (Unnumbered) message from L1 */
-static int lapd_rx_u(struct msgb *msg, struct lapd_msg_ctx *lctx)
+/* Receive a LAPD U SABM(E) message from L1 */
+static int lapd_rx_u_sabm(struct msgb *msg, struct lapd_msg_ctx *lctx)
{
struct lapd_datalink *dl = lctx->dl;
int length = lctx->length;
int rc = 0;
uint8_t prim, op;
- switch (lctx->s_u) {
- case LAPD_U_SABM:
- case LAPD_U_SABME:
- prim = PRIM_DL_EST;
- op = PRIM_OP_INDICATION;
-
- LOGDL(dl, LOGL_INFO, "SABM(E) received in state %s\n", lapd_state_name(dl->state));
- /* 5.7.1 */
- dl->seq_err_cond = 0;
- /* G.2.2 Wrong value of the C/R bit */
- if (lctx->cr == dl->cr.rem2loc.resp) {
- LOGDL(dl, LOGL_ERROR, "SABM response error\n");
- msgb_free(msg);
- mdl_error(MDL_CAUSE_FRM_UNIMPL, lctx);
- return -EINVAL;
- }
+ prim = PRIM_DL_EST;
+ op = PRIM_OP_INDICATION;
- /* G.4.5 If SABM is received with L>N201 or with M bit
- * set, AN MDL-ERROR-INDICATION is sent to MM.
- */
- if (lctx->more || length > lctx->n201) {
- LOGDL(dl, LOGL_ERROR, "SABM too large error\n");
- msgb_free(msg);
- mdl_error(MDL_CAUSE_UFRM_INC_PARAM, lctx);
- return -EIO;
- }
+ LOGDL(dl, LOGL_INFO, "SABM(E) received in state %s\n", lapd_state_name(dl->state));
+ /* 5.7.1 */
+ dl->seq_err_cond = 0;
+ /* G.2.2 Wrong value of the C/R bit */
+ if (lctx->cr == dl->cr.rem2loc.resp) {
+ LOGDL(dl, LOGL_ERROR, "SABM response error\n");
+ msgb_free(msg);
+ mdl_error(MDL_CAUSE_FRM_UNIMPL, lctx);
+ return -EINVAL;
+ }
- switch (dl->state) {
- case LAPD_STATE_IDLE:
- break;
- case LAPD_STATE_MF_EST:
- LOGDL(dl, LOGL_INFO, "SABM command, multiple frame established state\n");
- /* If link is lost on the remote side, we start over
- * and send DL-ESTABLISH indication again. */
- /* Additionally, continue in case of content resoltion
- * (GSM network). This happens, if the mobile has not
- * yet received UA or another mobile (collision) tries
- * to establish connection. The mobile must receive
- * UA again. */
- /* 5.4.2.1 */
- if (!length) {
- /* If no content resolution, this is a
- * re-establishment. */
- LOGDL(dl, LOGL_INFO, "Remote reestablish\n");
- break;
- }
- if (!dl->cont_res) {
- LOGDL(dl, LOGL_INFO, "SABM command not allowed in state %s\n",
- lapd_state_name(dl->state));
- mdl_error(MDL_CAUSE_SABM_MF, lctx);
- msgb_free(msg);
- return 0;
- }
- /* Ignore SABM if content differs from first SABM. */
- if (dl->mode == LAPD_MODE_NETWORK && length) {
-#ifdef TEST_CONTENT_RESOLUTION_NETWORK
- dl->cont_res->data[0] ^= 0x01;
-#endif
- if (memcmp(dl->cont_res->data, msg->data,
- length)) {
- LOGDL(dl, LOGL_INFO, "Another SABM with different content - "
- "ignoring!\n");
- msgb_free(msg);
- return 0;
- }
- }
- /* send UA again */
- lapd_send_ua(lctx, length, msg->l3h);
- msgb_free(msg);
- return 0;
- case LAPD_STATE_DISC_SENT:
- /* 5.4.6.2 send DM with F=P */
- lapd_send_dm(lctx);
- /* stop Timer T200 */
- lapd_stop_t200(dl);
- msgb_free(msg);
- return send_dl_simple(prim, op, lctx);
- default:
- /* collision: Send UA, but still wait for rx UA, then
- * change to MF_EST state.
- */
+ /* G.4.5 If SABM is received with L>N201 or with M bit
+ * set, AN MDL-ERROR-INDICATION is sent to MM.
+ */
+ if (lctx->more || length > lctx->n201) {
+ LOGDL(dl, LOGL_ERROR, "SABM too large error\n");
+ msgb_free(msg);
+ mdl_error(MDL_CAUSE_UFRM_INC_PARAM, lctx);
+ return -EIO;
+ }
+
+ switch (dl->state) {
+ case LAPD_STATE_IDLE:
+ break;
+ case LAPD_STATE_TIMER_RECOV:
+ LOGDL(dl, LOGL_INFO, "SABM command, timer recovery state\n");
+ /* If link is lost on the remote side, we start over
+ * and send DL-ESTABLISH indication again. */
+ /* 3GPP TS 44.006 8.6.3 "Procedures for re-establishment" */
+ if (length) {
/* check for contention resoultion */
- if (dl->tx_hist[0].msg && dl->tx_hist[0].msg->len) {
- LOGDL(dl, LOGL_NOTICE, "SABM not allowed during contention "
- "resolution (state=%s)\n", lapd_state_name(dl->state));
- mdl_error(MDL_CAUSE_SABM_INFO_NOTALL, lctx);
- }
- lapd_send_ua(lctx, length, msg->l3h);
+ LOGDL(dl, LOGL_ERROR, "SABM L>0 not expected in timer "
+ "recovery state\n");
+ mdl_error(MDL_CAUSE_SABM_INFO_NOTALL, lctx);
+ lapd_send_dm(lctx);
msgb_free(msg);
return 0;
}
- /* save message context for further use */
- memcpy(&dl->lctx, lctx, sizeof(dl->lctx));
-#ifndef TEST_CONTENT_RESOLUTION_NETWORK
- /* send UA response */
- lapd_send_ua(lctx, length, msg->l3h);
-#endif
- /* set Vs, Vr and Va to 0 */
- dl->v_send = dl->v_recv = dl->v_ack = 0;
- /* clear tx_hist */
- lapd_dl_flush_hist(dl);
- /* enter multiple-frame-established state */
- lapd_dl_newstate(dl, LAPD_STATE_MF_EST);
- /* store content resolution data on network side
- * Note: cont_res will be removed when changing state again,
- * so it must be allocated AFTER lapd_dl_newstate(). */
- if (dl->mode == LAPD_MODE_NETWORK && length) {
- dl->cont_res = lapd_msgb_alloc(length, "CONT RES");
- memcpy(msgb_put(dl->cont_res, length), msg->l3h,
- length);
- LOGDL(dl, LOGL_NOTICE, "Store content res.\n");
- }
- /* send notification to L3 */
- if (length == 0) {
- /* 5.4.1.2 Normal establishment procedures */
- rc = send_dl_simple(prim, op, lctx);
- msgb_free(msg);
- } else {
- /* 5.4.1.4 Contention resolution establishment */
- msgb_trim(msg, length);
- rc = send_dl_l3(prim, op, lctx, msg);
- }
+ /* re-establishment, continue below */
+ lapd_stop_t200(dl);
break;
- case LAPD_U_DM:
- LOGDL(dl, LOGL_INFO, "DM received in state %s\n", lapd_state_name(dl->state));
- /* G.2.2 Wrong value of the C/R bit */
- if (lctx->cr == dl->cr.rem2loc.cmd) {
- LOGDL(dl, LOGL_ERROR, "DM command error\n");
- msgb_free(msg);
- mdl_error(MDL_CAUSE_FRM_UNIMPL, lctx);
- return -EINVAL;
+ case LAPD_STATE_MF_EST:
+ LOGDL(dl, LOGL_INFO, "SABM command, multiple frame established state\n");
+ /* If link is lost on the remote side, we start over
+ * and send DL-ESTABLISH indication again. */
+ /* Additionally, continue in case of content resoltion
+ * (GSM network). This happens, if the mobile has not
+ * yet received UA or another mobile (collision) tries
+ * to establish connection. The mobile must receive
+ * UA again. */
+ /* 5.4.2.1 */
+ if (!length) {
+ /* If no content resolution, this is a
+ * re-establishment. */
+ LOGDL(dl, LOGL_INFO, "Remote reestablish\n");
+ break;
}
- if (!lctx->p_f) {
- /* 5.4.1.2 DM responses with the F bit set to "0"
- * shall be ignored.
- */
+ if (!dl->cont_res) {
+ LOGDL(dl, LOGL_INFO, "SABM command not allowed in state %s\n",
+ lapd_state_name(dl->state));
+ mdl_error(MDL_CAUSE_SABM_MF, lctx);
msgb_free(msg);
return 0;
}
- switch (dl->state) {
- case LAPD_STATE_SABM_SENT:
- break;
- case LAPD_STATE_MF_EST:
- if (lctx->p_f) {
- LOGDL(dl, LOGL_INFO, "unsolicited DM response\n");
- mdl_error(MDL_CAUSE_UNSOL_DM_RESP, lctx);
- } else {
- LOGDL(dl, LOGL_INFO, "unsolicited DM response, "
- "multiple frame established state\n");
- mdl_error(MDL_CAUSE_UNSOL_DM_RESP_MF, lctx);
- /* reestablish */
- if (!dl->reestablish) {
- msgb_free(msg);
- return 0;
- }
- LOGDL(dl, LOGL_NOTICE, "Performing reestablishment\n");
- lapd_reestablish(dl);
- }
- msgb_free(msg);
- return 0;
- case LAPD_STATE_TIMER_RECOV:
- /* FP = 0 (DM is normal in case PF = 1) */
- if (!lctx->p_f) {
- LOGDL(dl, LOGL_INFO, "unsolicited DM response, multiple frame "
- "established state\n");
- mdl_error(MDL_CAUSE_UNSOL_DM_RESP_MF, lctx);
+ /* Ignore SABM if content differs from first SABM. */
+ if (dl->mode == LAPD_MODE_NETWORK && length) {
+#ifdef TEST_CONTENT_RESOLUTION_NETWORK
+ dl->cont_res->data[0] ^= 0x01;
+#endif
+ if (memcmp(dl->cont_res->data, msg->data,
+ length)) {
+ LOGDL(dl, LOGL_INFO, "Another SABM with different content - "
+ "ignoring!\n");
msgb_free(msg);
- /* reestablish */
- if (!dl->reestablish)
- return 0;
- LOGDL(dl, LOGL_NOTICE, "Performing reestablishment\n");
- return lapd_reestablish(dl);
+ return 0;
}
- break;
- case LAPD_STATE_DISC_SENT:
- /* stop Timer T200 */
- lapd_stop_t200(dl);
- /* go to idle state */
- lapd_dl_flush_tx(dl);
- lapd_dl_flush_send(dl);
- lapd_dl_newstate(dl, LAPD_STATE_IDLE);
- rc = send_dl_simple(PRIM_DL_REL, PRIM_OP_CONFIRM, lctx);
- msgb_free(msg);
- return 0;
- case LAPD_STATE_IDLE:
- /* 5.4.5 all other frame types shall be discarded */
- default:
- LOGDL(dl, LOGL_INFO, "unsolicited DM response! (discarding)\n");
- msgb_free(msg);
- return 0;
}
- /* stop timer T200 */
+ /* send UA again */
+ lapd_send_ua(lctx, length, msg->l3h);
+ msgb_free(msg);
+ return 0;
+ case LAPD_STATE_DISC_SENT:
+ /* 5.4.6.2 send DM with F=P */
+ lapd_send_dm(lctx);
+ /* stop Timer T200 */
lapd_stop_t200(dl);
- /* go to idle state */
- lapd_dl_newstate(dl, LAPD_STATE_IDLE);
- rc = send_dl_simple(PRIM_DL_REL, PRIM_OP_INDICATION, lctx);
msgb_free(msg);
- break;
- case LAPD_U_UI:
- LOGDL(dl, LOGL_INFO, "UI received\n");
- /* G.2.2 Wrong value of the C/R bit */
- if (lctx->cr == dl->cr.rem2loc.resp) {
- LOGDL(dl, LOGL_ERROR, "UI indicates response error\n");
- msgb_free(msg);
- mdl_error(MDL_CAUSE_FRM_UNIMPL, lctx);
- return -EINVAL;
+ return send_dl_simple(prim, op, lctx);
+ default:
+ /* collision: Send UA, but still wait for rx UA, then
+ * change to MF_EST state.
+ */
+ /* check for contention resoultion */
+ if (dl->tx_hist[0].msg && dl->tx_hist[0].msg->len) {
+ LOGDL(dl, LOGL_NOTICE, "SABM not allowed during contention "
+ "resolution (state=%s)\n", lapd_state_name(dl->state));
+ mdl_error(MDL_CAUSE_SABM_INFO_NOTALL, lctx);
}
+ lapd_send_ua(lctx, length, msg->l3h);
+ msgb_free(msg);
+ return 0;
+ }
+ /* save message context for further use */
+ memcpy(&dl->lctx, lctx, sizeof(dl->lctx));
+#ifndef TEST_CONTENT_RESOLUTION_NETWORK
+ /* send UA response */
+ lapd_send_ua(lctx, length, msg->l3h);
+#endif
+ /* set Vs, Vr and Va to 0 */
+ dl->v_send = dl->v_recv = dl->v_ack = 0;
+ /* clear tx_hist */
+ lapd_dl_flush_hist(dl);
+ /* enter multiple-frame-established state */
+ lapd_dl_newstate(dl, LAPD_STATE_MF_EST);
+ /* store content resolution data on network side
+ * Note: cont_res will be removed when changing state again,
+ * so it must be allocated AFTER lapd_dl_newstate(). */
+ if (dl->mode == LAPD_MODE_NETWORK && length) {
+ dl->cont_res = lapd_msgb_alloc(length, "CONT RES");
+ memcpy(msgb_put(dl->cont_res, length), msg->l3h,
+ length);
+ LOGDL(dl, LOGL_INFO, "Store content res.\n");
+ }
+ /* send notification to L3 */
+ if (length == 0) {
+ /* 5.4.1.2 Normal establishment procedures */
+ rc = send_dl_simple(prim, op, lctx);
+ msgb_free(msg);
+ } else {
+ /* 5.4.1.4 Contention resolution establishment */
+ msgb_trim(msg, length);
+ rc = send_dl_l3(prim, op, lctx, msg);
+ }
+ return rc;
+}
+
+/* Receive a LAPD U DM message from L1 */
+static int lapd_rx_u_dm(struct msgb *msg, struct lapd_msg_ctx *lctx)
+{
+ struct lapd_datalink *dl = lctx->dl;
+ int rc = 0;
- /* G.4.5 If UI is received with L>N201 or with M bit
- * set, AN MDL-ERROR-INDICATION is sent to MM.
+ LOGDL(dl, LOGL_INFO, "DM received in state %s\n", lapd_state_name(dl->state));
+ /* G.2.2 Wrong value of the C/R bit */
+ if (lctx->cr == dl->cr.rem2loc.cmd) {
+ LOGDL(dl, LOGL_ERROR, "DM command error\n");
+ msgb_free(msg);
+ mdl_error(MDL_CAUSE_FRM_UNIMPL, lctx);
+ return -EINVAL;
+ }
+ if (!lctx->p_f) {
+ /* 5.4.1.2 DM responses with the F bit set to "0"
+ * shall be ignored.
*/
- if (length > lctx->n201 || lctx->more) {
- LOGDL(dl, LOGL_ERROR, "UI too large error (%d > N201(%d) or M=%d)\n",
- length, lctx->n201, lctx->more);
- msgb_free(msg);
- mdl_error(MDL_CAUSE_UFRM_INC_PARAM, lctx);
- return -EIO;
+ msgb_free(msg);
+ return 0;
+ }
+ switch (dl->state) {
+ case LAPD_STATE_SABM_SENT:
+ break;
+ case LAPD_STATE_MF_EST:
+ if (lctx->p_f) {
+ LOGDL(dl, LOGL_INFO, "unsolicited DM response\n");
+ mdl_error(MDL_CAUSE_UNSOL_DM_RESP, lctx);
+ } else {
+ LOGDL(dl, LOGL_INFO, "unsolicited DM response, "
+ "multiple frame established state\n");
+ mdl_error(MDL_CAUSE_UNSOL_DM_RESP_MF, lctx);
+ /* reestablish */
+ if (!dl->reestablish) {
+ msgb_free(msg);
+ return 0;
+ }
+ LOGDL(dl, LOGL_NOTICE, "Performing reestablishment\n");
+ lapd_reestablish(dl);
}
-
- /* do some length checks */
- if (length == 0) {
- /* 5.3.3 UI frames received with the length indicator
- * set to "0" shall be ignored
- */
- LOGDL(dl, LOGL_INFO, "length=0 (discarding)\n");
+ msgb_free(msg);
+ return 0;
+ case LAPD_STATE_TIMER_RECOV:
+ /* FP = 0 (DM is normal in case PF = 1) */
+ if (!lctx->p_f) {
+ LOGDL(dl, LOGL_INFO, "unsolicited DM response, multiple frame "
+ "established state\n");
+ mdl_error(MDL_CAUSE_UNSOL_DM_RESP_MF, lctx);
msgb_free(msg);
- return 0;
+ /* reestablish */
+ if (!dl->reestablish)
+ return 0;
+ LOGDL(dl, LOGL_NOTICE, "Performing reestablishment\n");
+ return lapd_reestablish(dl);
}
- msgb_trim(msg, length);
- rc = send_dl_l3(PRIM_DL_UNIT_DATA, PRIM_OP_INDICATION, lctx,
- msg);
break;
- case LAPD_U_DISC:
- prim = PRIM_DL_REL;
- op = PRIM_OP_INDICATION;
-
- LOGDL(dl, LOGL_INFO, "DISC received in state %s\n", lapd_state_name(dl->state));
- /* flush tx and send buffers */
+ case LAPD_STATE_DISC_SENT:
+ /* stop Timer T200 */
+ lapd_stop_t200(dl);
+ /* go to idle state */
lapd_dl_flush_tx(dl);
lapd_dl_flush_send(dl);
- /* 5.7.1 */
- dl->seq_err_cond = 0;
- /* G.2.2 Wrong value of the C/R bit */
- if (lctx->cr == dl->cr.rem2loc.resp) {
- LOGDL(dl, LOGL_ERROR, "DISC response error\n");
- msgb_free(msg);
- mdl_error(MDL_CAUSE_FRM_UNIMPL, lctx);
- return -EINVAL;
- }
- if (length > 0 || lctx->more) {
- /* G.4.4 If a DISC or DM frame is received with L>0 or
- * with the M bit set to "1", an MDL-ERROR-INDICATION
- * primitive with cause "U frame with incorrect
- * parameters" is sent to the mobile management entity.
- */
- LOGDL(dl, LOGL_ERROR, "U frame iwth incorrect parameters\n");
- msgb_free(msg);
- mdl_error(MDL_CAUSE_UFRM_INC_PARAM, lctx);
- return -EIO;
- }
- switch (dl->state) {
- case LAPD_STATE_IDLE:
- LOGDL(dl, LOGL_INFO, "DISC in idle state\n");
- /* send DM with F=P */
- msgb_free(msg);
- return lapd_send_dm(lctx);
- case LAPD_STATE_SABM_SENT:
- LOGDL(dl, LOGL_INFO, "DISC in SABM state\n");
- /* 5.4.6.2 send DM with F=P */
- lapd_send_dm(lctx);
- /* stop Timer T200 */
- lapd_stop_t200(dl);
- /* go to idle state */
- lapd_dl_newstate(dl, LAPD_STATE_IDLE);
- msgb_free(msg);
- return send_dl_simple(PRIM_DL_REL, PRIM_OP_INDICATION,
- lctx);
- case LAPD_STATE_MF_EST:
- case LAPD_STATE_TIMER_RECOV:
- LOGDL(dl, LOGL_INFO, "DISC in est state\n");
- break;
- case LAPD_STATE_DISC_SENT:
- LOGDL(dl, LOGL_INFO, "DISC in disc state\n");
- prim = PRIM_DL_REL;
- op = PRIM_OP_CONFIRM;
- break;
- default:
- lapd_send_ua(lctx, length, msg->l3h);
- msgb_free(msg);
- return 0;
- }
- /* send UA response */
- lapd_send_ua(lctx, length, msg->l3h);
+ lapd_dl_newstate(dl, LAPD_STATE_IDLE);
+ rc = send_dl_simple(PRIM_DL_REL, PRIM_OP_CONFIRM, lctx);
+ msgb_free(msg);
+ return 0;
+ case LAPD_STATE_IDLE:
+ /* 5.4.5 all other frame types shall be discarded */
+ default:
+ LOGDL(dl, LOGL_INFO, "unsolicited DM response! (discarding)\n");
+ msgb_free(msg);
+ return 0;
+ }
+ /* stop timer T200 */
+ lapd_stop_t200(dl);
+ /* go to idle state */
+ lapd_dl_newstate(dl, LAPD_STATE_IDLE);
+ rc = send_dl_simple(PRIM_DL_REL, PRIM_OP_INDICATION, lctx);
+ msgb_free(msg);
+ return rc;
+}
+
+/* Receive a LAPD U UI message from L1 */
+static int lapd_rx_u_ui(struct msgb *msg, struct lapd_msg_ctx *lctx)
+{
+ struct lapd_datalink *dl = lctx->dl;
+ int length = lctx->length;
+
+ LOGDL(dl, LOGL_INFO, "UI received\n");
+ /* G.2.2 Wrong value of the C/R bit */
+ if (lctx->cr == dl->cr.rem2loc.resp) {
+ LOGDL(dl, LOGL_ERROR, "UI indicates response error\n");
+ msgb_free(msg);
+ mdl_error(MDL_CAUSE_FRM_UNIMPL, lctx);
+ return -EINVAL;
+ }
+
+ /* G.4.5 If UI is received with L>N201 or with M bit
+ * set, AN MDL-ERROR-INDICATION is sent to MM.
+ */
+ if (length > lctx->n201 || lctx->more) {
+ LOGDL(dl, LOGL_ERROR, "UI too large error (%d > N201(%d) or M=%d)\n",
+ length, lctx->n201, lctx->more);
+ msgb_free(msg);
+ mdl_error(MDL_CAUSE_UFRM_INC_PARAM, lctx);
+ return -EIO;
+ }
+
+ /* do some length checks */
+ if (length == 0) {
+ /* 5.3.3 UI frames received with the length indicator
+ * set to "0" shall be ignored
+ */
+ LOGDL(dl, LOGL_INFO, "length=0 (discarding)\n");
+ msgb_free(msg);
+ return 0;
+ }
+ msgb_trim(msg, length);
+ return send_dl_l3(PRIM_DL_UNIT_DATA, PRIM_OP_INDICATION, lctx, msg);
+}
+
+/* Receive a LAPD U DISC message from L1 */
+static int lapd_rx_u_disc(struct msgb *msg, struct lapd_msg_ctx *lctx)
+{
+ struct lapd_datalink *dl = lctx->dl;
+ int length = lctx->length;
+ int rc = 0;
+ uint8_t prim, op;
+
+ prim = PRIM_DL_REL;
+ op = PRIM_OP_INDICATION;
+
+ LOGDL(dl, LOGL_INFO, "DISC received in state %s\n", lapd_state_name(dl->state));
+ /* flush tx and send buffers */
+ lapd_dl_flush_tx(dl);
+ lapd_dl_flush_send(dl);
+ /* 5.7.1 */
+ dl->seq_err_cond = 0;
+ /* G.2.2 Wrong value of the C/R bit */
+ if (lctx->cr == dl->cr.rem2loc.resp) {
+ LOGDL(dl, LOGL_ERROR, "DISC response error\n");
+ msgb_free(msg);
+ mdl_error(MDL_CAUSE_FRM_UNIMPL, lctx);
+ return -EINVAL;
+ }
+ if (length > 0 || lctx->more) {
+ /* G.4.4 If a DISC or DM frame is received with L>0 or
+ * with the M bit set to "1", an MDL-ERROR-INDICATION
+ * primitive with cause "U frame with incorrect
+ * parameters" is sent to the mobile management entity.
+ */
+ LOGDL(dl, LOGL_ERROR, "U frame iwth incorrect parameters\n");
+ msgb_free(msg);
+ mdl_error(MDL_CAUSE_UFRM_INC_PARAM, lctx);
+ return -EIO;
+ }
+ switch (dl->state) {
+ case LAPD_STATE_IDLE:
+ LOGDL(dl, LOGL_INFO, "DISC in idle state\n");
+ /* send DM with F=P */
+ msgb_free(msg);
+ return lapd_send_dm(lctx);
+ case LAPD_STATE_SABM_SENT:
+ LOGDL(dl, LOGL_INFO, "DISC in SABM state\n");
+ /* 5.4.6.2 send DM with F=P */
+ lapd_send_dm(lctx);
/* stop Timer T200 */
lapd_stop_t200(dl);
- /* enter idle state, keep tx-buffer with UA response */
+ /* go to idle state */
lapd_dl_newstate(dl, LAPD_STATE_IDLE);
- /* send notification to L3 */
- rc = send_dl_simple(prim, op, lctx);
msgb_free(msg);
+ return send_dl_simple(PRIM_DL_REL, PRIM_OP_INDICATION,
+ lctx);
+ case LAPD_STATE_MF_EST:
+ case LAPD_STATE_TIMER_RECOV:
+ LOGDL(dl, LOGL_INFO, "DISC in est state\n");
break;
- case LAPD_U_UA:
- LOGDL(dl, LOGL_INFO, "UA received in state %s\n", lapd_state_name(dl->state));
- /* G.2.2 Wrong value of the C/R bit */
- if (lctx->cr == dl->cr.rem2loc.cmd) {
- LOGDL(dl, LOGL_ERROR, "UA indicates command error\n");
- msgb_free(msg);
- mdl_error(MDL_CAUSE_FRM_UNIMPL, lctx);
- return -EINVAL;
- }
+ case LAPD_STATE_DISC_SENT:
+ LOGDL(dl, LOGL_INFO, "DISC in disc state\n");
+ prim = PRIM_DL_REL;
+ op = PRIM_OP_CONFIRM;
+ break;
+ default:
+ lapd_send_ua(lctx, length, msg->l3h);
+ msgb_free(msg);
+ return 0;
+ }
+ /* send UA response */
+ lapd_send_ua(lctx, length, msg->l3h);
+ /* stop Timer T200 */
+ lapd_stop_t200(dl);
+ /* enter idle state, keep tx-buffer with UA response */
+ lapd_dl_newstate(dl, LAPD_STATE_IDLE);
+ /* send notification to L3 */
+ rc = send_dl_simple(prim, op, lctx);
+ msgb_free(msg);
+ return rc;
+}
- /* G.4.5 If UA is received with L>N201 or with M bit
- * set, AN MDL-ERROR-INDICATION is sent to MM.
- */
- if (lctx->more || length > lctx->n201) {
- LOGDL(dl, LOGL_ERROR, "UA too large error\n");
- msgb_free(msg);
- mdl_error(MDL_CAUSE_UFRM_INC_PARAM, lctx);
- return -EIO;
- }
+/* Receive a LAPD U UA message from L1 */
+static int lapd_rx_u_ua(struct msgb *msg, struct lapd_msg_ctx *lctx)
+{
+ struct lapd_datalink *dl = lctx->dl;
+ int length = lctx->length;
+ int rc = 0;
- if (!lctx->p_f) {
- /* 5.4.1.2 A UA response with the F bit set to "0"
- * shall be ignored.
- */
- LOGDL(dl, LOGL_INFO, "F=0 (discarding)\n");
- msgb_free(msg);
- return 0;
- }
- switch (dl->state) {
- case LAPD_STATE_SABM_SENT:
- break;
- case LAPD_STATE_MF_EST:
- case LAPD_STATE_TIMER_RECOV:
- LOGDL(dl, LOGL_INFO, "unsolicited UA response! (discarding)\n");
- mdl_error(MDL_CAUSE_UNSOL_UA_RESP, lctx);
- msgb_free(msg);
- return 0;
- case LAPD_STATE_DISC_SENT:
- LOGDL(dl, LOGL_INFO, "UA in disconnect state\n");
- /* stop Timer T200 */
- lapd_stop_t200(dl);
+ LOGDL(dl, LOGL_INFO, "UA received in state %s\n", lapd_state_name(dl->state));
+ /* G.2.2 Wrong value of the C/R bit */
+ if (lctx->cr == dl->cr.rem2loc.cmd) {
+ LOGDL(dl, LOGL_ERROR, "UA indicates command error\n");
+ msgb_free(msg);
+ mdl_error(MDL_CAUSE_FRM_UNIMPL, lctx);
+ return -EINVAL;
+ }
+
+ /* G.4.5 If UA is received with L>N201 or with M bit
+ * set, AN MDL-ERROR-INDICATION is sent to MM.
+ */
+ if (lctx->more || length > lctx->n201) {
+ LOGDL(dl, LOGL_ERROR, "UA too large error\n");
+ msgb_free(msg);
+ mdl_error(MDL_CAUSE_UFRM_INC_PARAM, lctx);
+ return -EIO;
+ }
+
+ if (!lctx->p_f) {
+ /* 5.4.1.2 A UA response with the F bit set to "0"
+ * shall be ignored.
+ */
+ LOGDL(dl, LOGL_INFO, "F=0 (discarding)\n");
+ msgb_free(msg);
+ return 0;
+ }
+ switch (dl->state) {
+ case LAPD_STATE_SABM_SENT:
+ break;
+ case LAPD_STATE_MF_EST:
+ case LAPD_STATE_TIMER_RECOV:
+ LOGDL(dl, LOGL_INFO, "unsolicited UA response! (discarding)\n");
+ mdl_error(MDL_CAUSE_UNSOL_UA_RESP, lctx);
+ msgb_free(msg);
+ return 0;
+ case LAPD_STATE_DISC_SENT:
+ LOGDL(dl, LOGL_INFO, "UA in disconnect state\n");
+ /* stop Timer T200 */
+ lapd_stop_t200(dl);
+ /* go to idle state */
+ lapd_dl_flush_tx(dl);
+ lapd_dl_flush_send(dl);
+ lapd_dl_newstate(dl, LAPD_STATE_IDLE);
+ rc = send_dl_simple(PRIM_DL_REL, PRIM_OP_CONFIRM, lctx);
+ msgb_free(msg);
+ return 0;
+ case LAPD_STATE_IDLE:
+ /* 5.4.5 all other frame types shall be discarded */
+ default:
+ LOGDL(dl, LOGL_INFO, "unsolicited UA response! (discarding)\n");
+ msgb_free(msg);
+ return 0;
+ }
+ LOGDL(dl, LOGL_INFO, "UA in SABM state\n");
+ /* stop Timer T200 */
+ lapd_stop_t200(dl);
+ /* compare UA with SABME if contention resolution is applied */
+ if (dl->tx_hist[0].msg->len) {
+ if (length != (dl->tx_hist[0].msg->len)
+ || !!memcmp(dl->tx_hist[0].msg->data, msg->l3h,
+ length)) {
+ LOGDL(dl, LOGL_INFO, "**** UA response mismatches ****\n");
/* go to idle state */
lapd_dl_flush_tx(dl);
lapd_dl_flush_send(dl);
lapd_dl_newstate(dl, LAPD_STATE_IDLE);
- rc = send_dl_simple(PRIM_DL_REL, PRIM_OP_CONFIRM, lctx);
- msgb_free(msg);
- return 0;
- case LAPD_STATE_IDLE:
- /* 5.4.5 all other frame types shall be discarded */
- default:
- LOGDL(dl, LOGL_INFO, "unsolicited UA response! (discarding)\n");
+ rc = send_dl_simple(PRIM_DL_REL, PRIM_OP_INDICATION, lctx);
msgb_free(msg);
return 0;
}
- LOGDL(dl, LOGL_INFO, "UA in SABM state\n");
- /* stop Timer T200 */
- lapd_stop_t200(dl);
- /* compare UA with SABME if contention resolution is applied */
- if (dl->tx_hist[0].msg->len) {
- if (length != (dl->tx_hist[0].msg->len)
- || !!memcmp(dl->tx_hist[0].msg->data, msg->l3h,
- length)) {
- LOGDL(dl, LOGL_INFO, "**** UA response mismatches ****\n");
- rc = send_dl_simple(PRIM_DL_REL,
- PRIM_OP_INDICATION, lctx);
- msgb_free(msg);
- /* go to idle state */
- lapd_dl_flush_tx(dl);
- lapd_dl_flush_send(dl);
- lapd_dl_newstate(dl, LAPD_STATE_IDLE);
- return 0;
- }
- }
- /* set Vs, Vr and Va to 0 */
- dl->v_send = dl->v_recv = dl->v_ack = 0;
- /* clear tx_hist */
- lapd_dl_flush_hist(dl);
- /* enter multiple-frame-established state */
- lapd_dl_newstate(dl, LAPD_STATE_MF_EST);
- /* send outstanding frames, if any (resume / reconnect) */
- lapd_send_i(lctx, __LINE__);
- /* send notification to L3 */
- rc = send_dl_simple(PRIM_DL_EST, PRIM_OP_CONFIRM, lctx);
- msgb_free(msg);
- break;
+ }
+ /* set Vs, Vr and Va to 0 */
+ dl->v_send = dl->v_recv = dl->v_ack = 0;
+ /* clear tx_hist */
+ lapd_dl_flush_hist(dl);
+ /* enter multiple-frame-established state */
+ lapd_dl_newstate(dl, LAPD_STATE_MF_EST);
+ /* send outstanding frames, if any (resume / reconnect) */
+ lapd_send_i(lctx, __LINE__);
+ /* send notification to L3 */
+ rc = send_dl_simple(PRIM_DL_EST, PRIM_OP_CONFIRM, lctx);
+ msgb_free(msg);
+ return rc;
+}
+
+/* Receive a LAPD U FRMR message from L1 */
+static int lapd_rx_u_frmr(struct msgb *msg, struct lapd_msg_ctx *lctx)
+{
+ struct lapd_datalink *dl = lctx->dl;
+
+ LOGDL(dl, LOGL_NOTICE, "Frame reject received\n");
+ /* send MDL ERROR INIDCATION to L3 */
+ mdl_error(MDL_CAUSE_FRMR, lctx);
+ msgb_free(msg);
+ /* reestablish */
+ if (!dl->reestablish)
+ return 0;
+ LOGDL(dl, LOGL_NOTICE, "Performing reestablishment\n");
+ return lapd_reestablish(dl);
+}
+
+/* Receive a LAPD U (Unnumbered) message from L1 */
+static int lapd_rx_u(struct msgb *msg, struct lapd_msg_ctx *lctx)
+{
+ switch (lctx->s_u) {
+ case LAPD_U_SABM:
+ case LAPD_U_SABME:
+ return lapd_rx_u_sabm(msg, lctx);
+ case LAPD_U_DM:
+ return lapd_rx_u_dm(msg, lctx);
+ case LAPD_U_UI:
+ return lapd_rx_u_ui(msg, lctx);
+ case LAPD_U_DISC:
+ return lapd_rx_u_disc(msg, lctx);
+ case LAPD_U_UA:
+ return lapd_rx_u_ua(msg, lctx);
case LAPD_U_FRMR:
- LOGDL(dl, LOGL_NOTICE, "Frame reject received\n");
- /* send MDL ERROR INIDCATION to L3 */
- mdl_error(MDL_CAUSE_FRMR, lctx);
- msgb_free(msg);
- /* reestablish */
- if (!dl->reestablish)
- break;
- LOGDL(dl, LOGL_NOTICE, "Performing reestablishment\n");
- rc = lapd_reestablish(dl);
- break;
+ return lapd_rx_u_frmr(msg, lctx);
default:
/* G.3.1 */
- LOGDL(dl, LOGL_NOTICE, "Unnumbered frame not allowed\n");
+ LOGDL(lctx->dl, LOGL_NOTICE, "Unnumbered frame not allowed\n");
msgb_free(msg);
mdl_error(MDL_CAUSE_FRM_UNIMPL, lctx);
return -EINVAL;
}
- return rc;
}
/* Receive a LAPD S (Supervisory) message from L1 */
@@ -1647,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;
}
@@ -1677,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)
{
@@ -1715,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;
@@ -1852,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 {
@@ -1977,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;
@@ -2041,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..4d135478
--- /dev/null
+++ b/src/isdn/v110.c
@@ -0,0 +1,582 @@
+/* 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;
+
+ if (fr->e_bits[1] != 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 4e2348bd..0f6be576 100644
--- a/src/sim/Makefile.am
+++ b/src/sim/Makefile.am
@@ -1,9 +1,9 @@
# This is _NOT_ the library release version, it's an API version.
# Please read chapter "Library interface versions" of the libtool documentation
# before making any modifications: https://www.gnu.org/software/libtool/manual/html_node/Versioning.html
-LIBVERSION=2:0:0
+LIBVERSION=3:2:1
-AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)/include
+AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_builddir)/include -I$(top_builddir)
AM_CFLAGS = -fPIC -Wall $(TALLOC_CFLAGS)
AM_LDFLAGS = $(COVERAGE_LDFLAGS)
@@ -15,9 +15,12 @@ lib_LTLIBRARIES = libosmosim.la
libosmosim_la_SOURCES = core.c reader.c class_tables.c \
card_fs_sim.c card_fs_usim.c card_fs_uicc.c \
card_fs_isim.c card_fs_hpsim.c card_fs_tetra.c
-libosmosim_la_LDFLAGS = -version-info $(LIBVERSION)
+libosmosim_la_LDFLAGS = \
+ -version-info $(LIBVERSION) \
+ -no-undefined \
+ $(NULL)
libosmosim_la_LIBADD = \
- $(top_builddir)/src/libosmocore.la \
+ $(top_builddir)/src/core/libosmocore.la \
$(top_builddir)/src/gsm/libosmogsm.la \
$(TALLOC_LIBS)
if ENABLE_PCSC
diff --git a/src/sim/card_fs_hpsim.c b/src/sim/card_fs_hpsim.c
index 4a5f7d9a..2c115b75 100644
--- a/src/sim/card_fs_hpsim.c
+++ b/src/sim/card_fs_hpsim.c
@@ -17,10 +17,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
diff --git a/src/sim/card_fs_isim.c b/src/sim/card_fs_isim.c
index f11c0294..1a38da2f 100644
--- a/src/sim/card_fs_isim.c
+++ b/src/sim/card_fs_isim.c
@@ -17,10 +17,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
diff --git a/src/sim/card_fs_sim.c b/src/sim/card_fs_sim.c
index 55ce9af7..f07a2370 100644
--- a/src/sim/card_fs_sim.c
+++ b/src/sim/card_fs_sim.c
@@ -17,10 +17,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <errno.h>
diff --git a/src/sim/card_fs_tetra.c b/src/sim/card_fs_tetra.c
index 12853a52..597e38b7 100644
--- a/src/sim/card_fs_tetra.c
+++ b/src/sim/card_fs_tetra.c
@@ -17,10 +17,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <errno.h>
diff --git a/src/sim/card_fs_uicc.c b/src/sim/card_fs_uicc.c
index 5dcaaa12..87def0e4 100644
--- a/src/sim/card_fs_uicc.c
+++ b/src/sim/card_fs_uicc.c
@@ -17,10 +17,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
diff --git a/src/sim/card_fs_usim.c b/src/sim/card_fs_usim.c
index 4c8f79c4..8cff3fc3 100644
--- a/src/sim/card_fs_usim.c
+++ b/src/sim/card_fs_usim.c
@@ -15,10 +15,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
@@ -348,6 +344,68 @@ static const struct osim_file_desc usim_ef_in_df_hnb[] = {
"Oprator Home NodeB Name"),
};
+/* 31.102 Chapter 4.4.8 */
+static const struct osim_file_desc usim_ef_in_df_prose[] = {
+ EF_LIN_FIX_N(0x4F01, 0x01, "EF.PROSE_MON", F_OPTIONAL, 1, 64,
+ "ProSe Monitoring Parameters"),
+ EF_LIN_FIX_N(0x4F02, 0x02, "EF.PROSE_ANN", F_OPTIONAL, 1, 64,
+ "ProSe Announcing Parameters"),
+ EF_LIN_FIX_N(0x4F03, 0x03, "EF.PROSEFUNC", F_OPTIONAL, 1, 64,
+ "HPLMN ProSe Function"),
+ EF_TRANSP_N(0x4F04, 0x04, "EF.PROSE_RADIO_COM", F_OPTIONAL, 1, 128,
+ "ProSe Direct Communication Radio Parameters"),
+ EF_TRANSP_N(0x4F05, 0x05, "EF.PROSE_RADIO_MON", F_OPTIONAL, 1, 128,
+ "ProSe Direct Discovery Monitoring Radio Parameters"),
+ EF_TRANSP_N(0x4F06, 0x06, "EF.PROSE_RADIO_ANN", F_OPTIONAL, 1, 128,
+ "ProSe Direct Discovery Announcing Radio Parameters"),
+ EF_LIN_FIX_N(0x4F07, 0x07, "EF.PROSE_POLICY", F_OPTIONAL, 1, 64,
+ "ProSe Direct Discovery Announcing Radio Parameters"),
+ EF_LIN_FIX_N(0x4F08, 0x08, "EF.PROSE_PLMN", F_OPTIONAL, 1, 64,
+ "ProSe PLMN Parametes"),
+ EF_TRANSP_N(0x4F09, 0x09, "EF.PROSE_GC", F_OPTIONAL, 8, 64,
+ "ProSe Direct Discovery Announcing Radio Parameters"),
+ EF_TRANSP_N(0x4F10, 0x10, "EF.PST", F_OPTIONAL, 1, 2,
+ "ProSe Service Table"),
+ EF_TRANSP_N(0x4F11, 0x11, "EF.PROSE_UIRC", F_OPTIONAL, 1, 128,
+ "ProSe UsageInformationReportingConfiguration"),
+ EF_LIN_FIX_N(0x4F12, 0x12, "EF.PROSE_GM_DISCOVERY", F_OPTIONAL, 1, 64,
+ "ProSe Group Member Discovery Parameters"),
+ EF_LIN_FIX_N(0x4F13, 0x13, "EF.PROSE_RELAY", F_OPTIONAL, 1, 64,
+ "ProSe Relay Parameters"),
+ EF_TRANSP_N(0x4F14, 0x14, "EF.PROSE_RELAY_DISCOVERY", F_OPTIONAL, 5, 64,
+ "ProSe Relay Discovery Parameters"),
+};
+
+/* 31.102 Chapter 4.4.9 */
+static const struct osim_file_desc usim_ef_in_df_acdc[] = {
+ EF_TRANSP_N(0x4F01, 0x01, "EF.ACDC_LIST", F_OPTIONAL, 1, 128,
+ "ACDC List"),
+};
+
+/* 31.102 Chapter 4.4.11 */
+static const struct osim_file_desc usim_ef_in_df_5gs[] = {
+ EF_TRANSP_N(0x4F01, 0x01, "EF.5GS3GPPLOCI", F_OPTIONAL, 20, 20,
+ "5GS 3GPP location information"),
+ EF_TRANSP_N(0x4F02, 0x02, "EF.5GSN3GPPLOCI", F_OPTIONAL, 20, 20,
+ "5GS non-3GPP location information"),
+ EF_LIN_FIX_N(0x4F03, 0x03, "EF.5GS3GPPNSC", F_OPTIONAL, 57, 57,
+ "5GS 3GPP Access NAS Security Context"),
+ EF_LIN_FIX_N(0x4F04, 0x04, "EF.5GSN3GPPNSC", F_OPTIONAL, 57, 57,
+ "5GS non-3GPP Access NAS Security Context"),
+ EF_TRANSP_N(0x4F05, 0x05, "EF.5GAUTHKEYS", F_OPTIONAL, 68, 68,
+ "5GS authentication keys"),
+ EF_TRANSP_N(0x4F06, 0x06, "EF.UAC_AIC", F_OPTIONAL, 4, 4,
+ "UAC Access Identities Configuration"),
+ EF_TRANSP_N(0x4F07, 0x07, "EF.SUCI_Calc_Info", F_OPTIONAL, 2, 64,
+ "Subscription Concealed Identifier Calculation Information"),
+ EF_LIN_FIX_N(0x4F08, 0x08, "EF.OPL5G", F_OPTIONAL, 10, 10,
+ "5GS Operator PLMN List"),
+ EF_TRANSP_N(0x4F09, 0x09, "EF.NSI", F_OPTIONAL, 1, 64,
+ "Network Specific Identifier"),
+ EF_TRANSP_N(0x4F0A, 0x0A, "EF.Routing_Indicator", F_OPTIONAL, 4, 4,
+ "Routing Indicator"),
+};
+
/* Annex E - TS 101 220 */
static const uint8_t adf_usim_aid[] = { 0xA0, 0x00, 0x00, 0x00, 0x87, 0x10, 0x02 };
@@ -375,6 +433,8 @@ struct osim_card_app_profile *osim_aprof_usim(void *ctx)
ARRAY_SIZE(usim_ef_in_df_mexe));
add_df_with_ef(uadf, 0x5F40, "DF.WLAN", usim_ef_in_df_wlan,
ARRAY_SIZE(usim_ef_in_df_wlan));
+ add_df_with_ef(uadf, 0x5FC0, "DF.5GS", usim_ef_in_df_5gs,
+ ARRAY_SIZE(usim_ef_in_df_5gs));
/* Home-NodeB (femtocell) */
add_df_with_ef(uadf, 0x5F50, "DF.HNB", usim_ef_in_df_hnb,
ARRAY_SIZE(usim_ef_in_df_hnb));
@@ -383,6 +443,10 @@ struct osim_card_app_profile *osim_aprof_usim(void *ctx)
ARRAY_SIZE(usim_ef_in_solsa));
/* OMA BCAST Smart Card Profile */
add_df_with_ef(uadf, 0x5F80, "DF.BCAST", NULL, 0);
+ add_df_with_ef(uadf, 0x5F90, "DF.ProSe", usim_ef_in_df_prose,
+ ARRAY_SIZE(usim_ef_in_df_prose));
+ add_df_with_ef(uadf, 0x5FA0, "DF.ACDC", usim_ef_in_df_acdc,
+ ARRAY_SIZE(usim_ef_in_df_acdc));
return aprof;
}
diff --git a/src/sim/class_tables.c b/src/sim/class_tables.c
index 6f541ee2..9c513879 100644
--- a/src/sim/class_tables.c
+++ b/src/sim/class_tables.c
@@ -13,10 +13,6 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <stdint.h>
@@ -117,7 +113,7 @@ static const uint8_t uicc_ins_tbl_046[256] = {
[0xD6] = 3, /* UPDATE BINARY */
[0xB2] = 2, /* READ RECORD */
[0xDC] = 3, /* UPDATE RECORD */
- [0xA2] = 4, /* SEEK */
+ [0xA2] = 4, /* SEARCH RECORD */
[0x20] = 3, /* VERIFY PIN */
[0x24] = 3, /* CHANGE PIN */
[0x26] = 3, /* DISABLE PIN */
@@ -177,6 +173,24 @@ static int uicc046_cla_ins_helper(const struct osim_cla_ins_case *cic,
return 0;
}
+static int gp_cla_ins_helper(const struct osim_cla_ins_case *cic,
+ const uint8_t *hdr)
+{
+ uint8_t ins = hdr[1];
+ uint8_t p1 = hdr[2];
+
+ switch (ins) {
+ case 0xE2: /* STORE DATA */
+ switch (p1 & 0x01) {
+ case 1:
+ return 4;
+ default:
+ return 3;
+ }
+ }
+ return 0;
+}
+
/* ETSI TS 102 221, Table 10.5, CLA = 0x8x, 0xCx or 0xEx */
static const uint8_t uicc_ins_tbl_8ce[256] = {
[0xF2] = 2, /* STATUS */
@@ -184,6 +198,7 @@ static const uint8_t uicc_ins_tbl_8ce[256] = {
[0xCB] = 4, /* RETRIEVE DATA */
[0xDB] = 3, /* SET DATA */
[0xAA] = 3, /* TERMINAL CAPABILITY */
+ [0x78] = 4, /* GET IDENTITY */
};
/* ETSI TS 102 221, Table 10.5, CLA = 0x80 */
@@ -192,6 +207,21 @@ static const uint8_t uicc_ins_tbl_80[256] = {
[0xC2] = 4, /* ENVELOPE */
[0x12] = 2, /* FETCH */
[0x14] = 3, /* TERMINAL RESPONSE */
+ [0x76] = 4, /* SUSPEND UICC */
+ [0x7A] = 4, /* EXCHANGE CAPABILITIES */
+};
+
+/* Card Specification v2.3.1*/
+static const uint8_t gp_ins_tbl_8ce[256] = {
+ [0xE4] = 4, /* DELETE */
+ [0xE2] = 0x80, /* STORE DATA */
+ [0xCA] = 4, /* GET DATA */
+ [0xCB] = 4, /* GET DATA */
+ [0xF2] = 4, /* GET STATUS */
+ [0xE6] = 4, /* INSTALL */
+ [0xE8] = 4, /* LOAD */
+ [0xD8] = 4, /* PUT KEY */
+ [0xF0] = 3, /* SET STATUS */
};
static const struct osim_cla_ins_case uicc_ins_case[] = {
@@ -226,6 +256,21 @@ static const struct osim_cla_ins_case uicc_ins_case[] = {
.cla = 0xE0,
.cla_mask = 0xF0,
.ins_tbl = uicc_ins_tbl_8ce,
+ }, {
+ .cla = 0x80,
+ .cla_mask = 0xF0,
+ .helper = gp_cla_ins_helper,
+ .ins_tbl = gp_ins_tbl_8ce,
+ }, {
+ .cla = 0xC0,
+ .cla_mask = 0xF0,
+ .helper = gp_cla_ins_helper,
+ .ins_tbl = gp_ins_tbl_8ce,
+ }, {
+ .cla = 0xE0,
+ .cla_mask = 0xF0,
+ .helper = gp_cla_ins_helper,
+ .ins_tbl = gp_ins_tbl_8ce,
},
};
@@ -273,7 +318,23 @@ static const struct osim_cla_ins_case uicc_sim_ins_case[] = {
.cla = 0xE0,
.cla_mask = 0xF0,
.ins_tbl = uicc_ins_tbl_8ce,
+ }, {
+ .cla = 0x80,
+ .cla_mask = 0xF0,
+ .helper = gp_cla_ins_helper,
+ .ins_tbl = gp_ins_tbl_8ce,
+ }, {
+ .cla = 0xC0,
+ .cla_mask = 0xF0,
+ .helper = gp_cla_ins_helper,
+ .ins_tbl = gp_ins_tbl_8ce,
+ }, {
+ .cla = 0xE0,
+ .cla_mask = 0xF0,
+ .helper = gp_cla_ins_helper,
+ .ins_tbl = gp_ins_tbl_8ce,
},
+
};
const struct osim_cla_ins_card_profile osim_uicc_sim_cic_profile = {
@@ -305,7 +366,7 @@ int osim_determine_apdu_case(const struct osim_cla_ins_card_profile *prof,
case 0x80:
return cic->helper(cic, hdr);
case 0x00:
- /* continue with fruther cic, rather than abort
+ /* continue with further cic, rather than abort
* now */
continue;
default:
diff --git a/src/sim/core.c b/src/sim/core.c
index d11c2d83..fa17e12d 100644
--- a/src/sim/core.c
+++ b/src/sim/core.c
@@ -17,10 +17,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
diff --git a/src/sim/reader.c b/src/sim/reader.c
index d5292baa..982b2eef 100644
--- a/src/sim/reader.c
+++ b/src/sim/reader.c
@@ -17,10 +17,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
@@ -44,7 +40,7 @@ static int get_sw(struct msgb *resp)
{
int ret;
- if (!msgb_apdu_de(resp) || msgb_apdu_le(resp) < 2)
+ if (!resp->l2h || msgb_apdu_le(resp) < 2)
return -EIO;
ret = msgb_get_u16(resp);
@@ -123,7 +119,6 @@ transceive_again:
/* save SW */
sw = msgb_apdu_sw(tmsg);
- printf("sw = 0x%04x\n", sw);
msgb_apdu_sw(amsg) = sw;
switch (msgb_apdu_case(amsg)) {
@@ -278,3 +273,24 @@ struct osim_card_hdl *osim_card_open(struct osim_reader_hdl *rh, enum osim_proto
return ch;
}
+
+int osim_card_reset(struct osim_card_hdl *card, bool cold_reset)
+{
+ struct osim_reader_hdl *rh = card->reader;
+
+ return rh->ops->card_reset(card, cold_reset);
+}
+
+int osim_card_close(struct osim_card_hdl *card)
+{
+ struct osim_reader_hdl *rh = card->reader;
+ int rc;
+
+ rc = rh->ops->card_close(card);
+
+ card->reader = NULL;
+ talloc_free(card);
+ rh->card = NULL;
+
+ return rc;
+}
diff --git a/src/sim/reader_pcsc.c b/src/sim/reader_pcsc.c
index c37380a3..c37072ee 100644
--- a/src/sim/reader_pcsc.c
+++ b/src/sim/reader_pcsc.c
@@ -17,10 +17,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
@@ -41,12 +37,9 @@
if (rv != SCARD_S_SUCCESS) { \
fprintf(stderr, text ": %s (0x%lX)\n", pcsc_stringify_error(rv), rv); \
goto end; \
-} else { \
- printf(text ": OK\n\n"); \
}
-
struct pcsc_reader_state {
SCARDCONTEXT hContext;
SCARDHANDLE hCard;
@@ -56,6 +49,27 @@ struct pcsc_reader_state {
char *name;
};
+static int pcsc_get_atr(struct osim_card_hdl *card)
+{
+ struct osim_reader_hdl *rh = card->reader;
+ struct pcsc_reader_state *st = rh->priv;
+ char pbReader[MAX_READERNAME];
+ DWORD dwReaderLen = sizeof(pbReader);
+ DWORD dwAtrLen = sizeof(card->atr);
+ DWORD dwState, dwProt;
+ long rc;
+
+ rc = SCardStatus(st->hCard, pbReader, &dwReaderLen, &dwState, &dwProt,
+ card->atr, &dwAtrLen);
+ PCSC_ERROR(rc, "SCardStatus");
+ card->atr_len = dwAtrLen;
+
+ return 0;
+
+end:
+ return -EIO;
+}
+
static struct osim_reader_hdl *pcsc_reader_open(int num, const char *id, void *ctx)
{
struct osim_reader_hdl *rh;
@@ -130,12 +144,42 @@ static struct osim_card_hdl *pcsc_card_open(struct osim_reader_hdl *rh,
chan->card = card;
llist_add(&chan->list, &card->channels);
+ pcsc_get_atr(card);
+
return card;
end:
return NULL;
}
+static int pcsc_card_reset(struct osim_card_hdl *card, bool cold_reset)
+{
+ struct pcsc_reader_state *st = card->reader->priv;
+ LONG rc;
+
+ rc = SCardReconnect(st->hCard, SCARD_SHARE_SHARED, SCARD_PROTOCOL_T0,
+ cold_reset ? SCARD_UNPOWER_CARD : SCARD_RESET_CARD,
+ &st->dwActiveProtocol);
+ PCSC_ERROR(rc, "SCardReconnect");
+
+ return 0;
+end:
+ return -EIO;
+}
+
+static int pcsc_card_close(struct osim_card_hdl *card)
+{
+ struct pcsc_reader_state *st = card->reader->priv;
+ LONG rc;
+
+ rc = SCardDisconnect(st->hCard, SCARD_UNPOWER_CARD);
+ PCSC_ERROR(rc, "SCardDisconnect");
+
+ return 0;
+end:
+ return -EIO;
+}
+
static int pcsc_transceive(struct osim_reader_hdl *rh, struct msgb *msg)
{
@@ -143,13 +187,10 @@ static int pcsc_transceive(struct osim_reader_hdl *rh, struct msgb *msg)
DWORD rlen = msgb_tailroom(msg);
LONG rc;
- printf("TX: %s\n", osmo_hexdump(msg->data, msg->len));
-
rc = SCardTransmit(st->hCard, st->pioSendPci, msg->data, msgb_length(msg),
&st->pioRecvPci, msg->tail, &rlen);
PCSC_ERROR(rc, "SCardEndTransaction");
- printf("RX: %s\n", osmo_hexdump(msg->tail, rlen));
msgb_put(msg, rlen);
msgb_apdu_le(msg) = rlen;
@@ -162,6 +203,8 @@ const struct osim_reader_ops pcsc_reader_ops = {
.name = "PC/SC",
.reader_open = pcsc_reader_open,
.card_open = pcsc_card_open,
+ .card_reset = pcsc_card_reset,
+ .card_close = pcsc_card_close,
.transceive = pcsc_transceive,
};
diff --git a/src/stat_item.c b/src/stat_item.c
deleted file mode 100644
index ba364640..00000000
--- a/src/stat_item.c
+++ /dev/null
@@ -1,388 +0,0 @@
-/*! \file stat_item.c
- * utility routines for keeping statistical values */
-/*
- * (C) 2009-2010 by Harald Welte <laforge@gnumonks.org>
- * (C) 2015 by sysmocom - s.f.m.c. GmbH
- *
- * All Rights Reserved
- *
- * SPDX-License-Identifier: GPL-2.0+
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- */
-
-/*! \addtogroup osmo_stat_item
- * @{
- *
- * This osmo_stat_item module adds instrumentation capabilities to
- * gather measurement and statistical values in a similar fashion to
- * what we have as \ref osmo_counter_group.
- *
- * As opposed to counters, osmo_stat_item do not increment but consist
- * of a configurable-sized FIFO, which can store not only the current
- * (most recent) value, but also historic values.
- *
- * The only supported value type is an int32_t.
- *
- * Getting values from the osmo_stat_item does not modify its state to
- * allow for multiple independent back-ends retrieving values (e.g. VTY
- * and statd).
- *
- * Each value stored in the FIFO of an osmo_stat_item has an associated
- * value_id. The value_id is derived from an application-wide globally
- * incrementing counter, so (until the counter wraps) more recent
- * values will have higher values.
- *
- * When a new value is set, the oldest value in the FIFO gets silently
- * overwritten. Lost values are skipped when getting values from the
- * item.
- *
- */
-
-#include <stdint.h>
-#include <string.h>
-
-#include <osmocom/core/utils.h>
-#include <osmocom/core/linuxlist.h>
-#include <osmocom/core/talloc.h>
-#include <osmocom/core/timer.h>
-#include <osmocom/core/stat_item.h>
-
-/*! global list of stat_item groups */
-static LLIST_HEAD(osmo_stat_item_groups);
-/*! counter for assigning globally unique value identifiers */
-static int32_t global_value_id = 0;
-
-/*! talloc context from which we allocate */
-static void *tall_stat_item_ctx;
-
-/*! Allocate a new group of counters according to description.
- * Allocate a group of stat items described in \a desc from talloc context \a ctx,
- * giving the new group the index \a idx.
- * \param[in] ctx \ref talloc context
- * \param[in] desc Statistics item group description
- * \param[in] idx Index of new stat item group
- */
-struct osmo_stat_item_group *osmo_stat_item_group_alloc(void *ctx,
- const struct osmo_stat_item_group_desc *desc,
- unsigned int idx)
-{
- unsigned int group_size;
- unsigned long items_size = 0;
- unsigned int item_idx;
- void *items;
-
- struct osmo_stat_item_group *group;
-
- group_size = sizeof(struct osmo_stat_item_group) +
- desc->num_items * sizeof(struct osmo_stat_item *);
-
- if (!ctx)
- ctx = tall_stat_item_ctx;
-
- group = talloc_zero_size(ctx, group_size);
- if (!group)
- return NULL;
-
- group->desc = desc;
- group->idx = idx;
-
- /* Get combined size of all items */
- for (item_idx = 0; item_idx < desc->num_items; item_idx++) {
- unsigned int size;
- size = sizeof(struct osmo_stat_item) +
- sizeof(struct osmo_stat_item_value) *
- desc->item_desc[item_idx].num_values;
- /* Align to pointer size */
- size = (size + sizeof(void *) - 1) & ~(sizeof(void *) - 1);
-
- /* Store offsets into the item array */
- group->items[item_idx] = (void *)items_size;
-
- items_size += size;
- }
-
- items = talloc_zero_size(group, items_size);
- if (!items) {
- talloc_free(group);
- return NULL;
- }
-
- /* Update item pointers */
- for (item_idx = 0; item_idx < desc->num_items; item_idx++) {
- struct osmo_stat_item *item = (struct osmo_stat_item *)
- ((uint8_t *)items + (unsigned long)group->items[item_idx]);
- unsigned int i;
-
- group->items[item_idx] = item;
- item->last_offs = desc->item_desc[item_idx].num_values - 1;
- item->last_value_index = -1;
- item->desc = &desc->item_desc[item_idx];
-
- for (i = 0; i <= item->last_offs; i++) {
- item->values[i].value = desc->item_desc[item_idx].default_value;
- item->values[i].id = OSMO_STAT_ITEM_NOVALUE_ID;
- }
- }
-
- llist_add(&group->list, &osmo_stat_item_groups);
-
- return group;
-}
-
-/*! Free the memory for the specified group of stat items */
-void osmo_stat_item_group_free(struct osmo_stat_item_group *grp)
-{
- llist_del(&grp->list);
- talloc_free(grp);
-}
-
-/*! Increase the stat_item to the given value.
- * This function adds a new value for the given stat_item at the end of
- * the FIFO.
- * \param[in] item The stat_item whose \a value we want to set
- * \param[in] value The numeric value we want to store at end of FIFO
- */
-void osmo_stat_item_inc(struct osmo_stat_item *item, int32_t value)
-{
- int32_t oldvalue = item->values[item->last_offs].value;
- osmo_stat_item_set(item, oldvalue + value);
-}
-
-/*! Descrease the stat_item to the given value.
- * This function adds a new value for the given stat_item at the end of
- * the FIFO.
- * \param[in] item The stat_item whose \a value we want to set
- * \param[in] value The numeric value we want to store at end of FIFO
- */
-void osmo_stat_item_dec(struct osmo_stat_item *item, int32_t value)
-{
- int32_t oldvalue = item->values[item->last_offs].value;
- osmo_stat_item_set(item, oldvalue - value);
-}
-
-/*! Set the a given stat_item to the given value.
- * This function adds a new value for the given stat_item at the end of
- * the FIFO.
- * \param[in] item The stat_item whose \a value we want to set
- * \param[in] value The numeric value we want to store at end of FIFO
- */
-void osmo_stat_item_set(struct osmo_stat_item *item, int32_t value)
-{
- item->last_offs += 1;
- if (item->last_offs >= item->desc->num_values)
- item->last_offs = 0;
-
- global_value_id += 1;
- if (global_value_id == OSMO_STAT_ITEM_NOVALUE_ID)
- global_value_id += 1;
-
- item->values[item->last_offs].value = value;
- item->values[item->last_offs].id = global_value_id;
-}
-
-/*! Retrieve the next value from the osmo_stat_item object.
- * If a new value has been set, it is returned. The idx is used to decide
- * which value to return.
- * On success, *idx is updated to refer to the next unread value. If
- * values have been missed due to FIFO overflow, *idx is incremented by
- * (1 + num_lost).
- * This way, the osmo_stat_item object can be kept stateless from the reader's
- * perspective and therefore be used by several backends simultaneously.
- *
- * \param val the osmo_stat_item object
- * \param idx identifies the next value to be read
- * \param value a pointer to store the value
- * \returns the increment of the index (0: no value has been read,
- * 1: one value has been taken,
- * (1+n): n values have been skipped, one has been taken)
- */
-int osmo_stat_item_get_next(const struct osmo_stat_item *item, int32_t *next_idx,
- int32_t *value)
-{
- const struct osmo_stat_item_value *next_value;
- const struct osmo_stat_item_value *item_value = NULL;
- int idx_delta;
- int next_offs;
-
- next_offs = item->last_offs;
- next_value = &item->values[next_offs];
-
- while (next_value->id - *next_idx >= 0 &&
- next_value->id != OSMO_STAT_ITEM_NOVALUE_ID)
- {
- item_value = next_value;
-
- next_offs -= 1;
- if (next_offs < 0)
- next_offs = item->desc->num_values - 1;
- if (next_offs == item->last_offs)
- break;
- next_value = &item->values[next_offs];
- }
-
- if (!item_value)
- /* All items have been read */
- return 0;
-
- *value = item_value->value;
-
- idx_delta = item_value->id + 1 - *next_idx;
-
- *next_idx = item_value->id + 1;
-
- return idx_delta;
-}
-
-/*! Skip/discard all values of this item and update \a idx accordingly */
-int osmo_stat_item_discard(const struct osmo_stat_item *item, int32_t *idx)
-{
- int discarded = item->values[item->last_offs].id + 1 - *idx;
- *idx = item->values[item->last_offs].id + 1;
-
- return discarded;
-}
-
-/*! Skip all values of all items and update \a idx accordingly */
-int osmo_stat_item_discard_all(int32_t *idx)
-{
- int discarded = global_value_id + 1 - *idx;
- *idx = global_value_id + 1;
-
- return discarded;
-}
-
-/*! Initialize the stat item module. Call this once from your program.
- * \param[in] tall_ctx Talloc context from which this module allocates */
-int osmo_stat_item_init(void *tall_ctx)
-{
- tall_stat_item_ctx = tall_ctx;
-
- return 0;
-}
-
-/*! Search for item group based on group name and index
- * \param[in] name Name of stats_item_group we want to find
- * \param[in] idx Index of the group we want to find
- * \returns pointer to group, if found; NULL otherwise */
-struct osmo_stat_item_group *osmo_stat_item_get_group_by_name_idx(
- const char *name, const unsigned int idx)
-{
- struct osmo_stat_item_group *statg;
-
- llist_for_each_entry(statg, &osmo_stat_item_groups, list) {
- if (!statg->desc)
- continue;
-
- if (!strcmp(statg->desc->group_name_prefix, name) &&
- statg->idx == idx)
- return statg;
- }
- return NULL;
-}
-
-/*! Search for item based on group + item name
- * \param[in] statg group in which to search for the item
- * \param[in] name name of item to search within \a statg
- * \returns pointer to item, if found; NULL otherwise */
-const struct osmo_stat_item *osmo_stat_item_get_by_name(
- const struct osmo_stat_item_group *statg, const char *name)
-{
- int i;
- const struct osmo_stat_item_desc *item_desc;
-
- if (!statg->desc)
- return NULL;
-
- for (i = 0; i < statg->desc->num_items; i++) {
- item_desc = &statg->desc->item_desc[i];
-
- if (!strcmp(item_desc->name, name)) {
- return statg->items[i];
- }
- }
- return NULL;
-}
-
-/*! Iterate over all items in group, call user-supplied function on each
- * \param[in] statg stat_item group over whose items to iterate
- * \param[in] handle_item Call-back function, aborts if rc < 0
- * \param[in] data Private data handed through to \a handle_item
- */
-int osmo_stat_item_for_each_item(struct osmo_stat_item_group *statg,
- osmo_stat_item_handler_t handle_item, void *data)
-{
- int rc = 0;
- int i;
-
- for (i = 0; i < statg->desc->num_items; i++) {
- struct osmo_stat_item *item = statg->items[i];
- rc = handle_item(statg, item, data);
- if (rc < 0)
- return rc;
- }
-
- return rc;
-}
-
-/*! Iterate over all stat_item groups in system, call user-supplied function on each
- * \param[in] handle_group Call-back function, aborts if rc < 0
- * \param[in] data Private data handed through to \a handle_group
- */
-int osmo_stat_item_for_each_group(osmo_stat_item_group_handler_t handle_group, void *data)
-{
- struct osmo_stat_item_group *statg;
- int rc = 0;
-
- llist_for_each_entry(statg, &osmo_stat_item_groups, list) {
- rc = handle_group(statg, data);
- if (rc < 0)
- return rc;
- }
-
- return rc;
-}
-
-
-/*! Remove all values of a stat item
- * \param[in] item stat item to reset
- */
-void osmo_stat_item_reset(struct osmo_stat_item *item)
-{
- unsigned int i;
-
- item->last_offs = item->desc->num_values - 1;
- item->last_value_index = -1;
-
- for (i = 0; i <= item->last_offs; i++) {
- item->values[i].value = item->desc->default_value;
- item->values[i].id = OSMO_STAT_ITEM_NOVALUE_ID;
- }
-}
-
-/*! Reset all osmo stat items in a group
- * \param[in] statg stat item group to reset
- */
-void osmo_stat_item_group_reset(struct osmo_stat_item_group *statg)
-{
- int i;
-
- for (i = 0; i < statg->desc->num_items; i++) {
- struct osmo_stat_item *item = statg->items[i];
- osmo_stat_item_reset(item);
- }
-}
-/*! @} */
diff --git a/src/usb/Makefile.am b/src/usb/Makefile.am
index 2dee434b..c7d7a2a2 100644
--- a/src/usb/Makefile.am
+++ b/src/usb/Makefile.am
@@ -1,9 +1,9 @@
# This is _NOT_ the library release version, it's an API version.
# Please read chapter "Library interface versions" of the libtool documentation
# before making any modifications: https://www.gnu.org/software/libtool/manual/html_node/Versioning.html
-LIBVERSION=0:0:0
+LIBVERSION=0:1:0
-AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include -I$(top_builddir)/include
+AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_builddir)/include -I$(top_builddir)
AM_CFLAGS = -fPIC -Wall $(LIBUSB_CFLAGS) $(TALLOC_CFLAGS)
AM_LDFLAGS = $(COVERAGE_LDFLAGS)
@@ -12,9 +12,12 @@ if ENABLE_LIBUSB
lib_LTLIBRARIES = libosmousb.la
libosmousb_la_SOURCES = osmo_libusb.c
-libosmousb_la_LDFLAGS = -version-info $(LIBVERSION)
+libosmousb_la_LDFLAGS = \
+ -version-info $(LIBVERSION) \
+ -no-undefined \
+ $(NULL)
libosmousb_la_LIBADD = \
- $(top_builddir)/src/libosmocore.la \
+ $(top_builddir)/src/core/libosmocore.la \
$(TALLOC_LIBS) \
$(LIBUSB_LIBS)
diff --git a/src/usb/osmo_libusb.c b/src/usb/osmo_libusb.c
index bb862067..a249d100 100644
--- a/src/usb/osmo_libusb.c
+++ b/src/usb/osmo_libusb.c
@@ -14,10 +14,6 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <errno.h>
#include <unistd.h>
@@ -103,7 +99,7 @@ static void osmo_usb_added_cb(int fd, short events, void *user_data)
static void osmo_usb_removed_cb(int fd, void *user_data)
{
struct osmo_fd *ofd = osmo_fd_get_by_fd(fd);
- if (!fd)
+ if (!ofd)
return;
osmo_fd_unregister(ofd);
talloc_free(ofd);
@@ -541,7 +537,8 @@ libusb_device_handle *osmo_libusb_open_claim_interface(void *ctx, libusb_context
addr = libusb_get_device_address(*dev);
path = osmo_libusb_dev_get_path_buf(pathbuf, sizeof(pathbuf), *dev);
if ((ifm->addr && addr == ifm->addr) ||
- (strlen(ifm->path) && !strcmp(path, ifm->path))) {
+ (strlen(ifm->path) && !strcmp(path, ifm->path)) ||
+ (!ifm->addr && !strlen(ifm->path) && !list[1] /* only one device */)) {
rc = libusb_open(*dev, &usb_devh);
if (rc < 0) {
fprintf(stderr, "Cannot open device: %s\n", libusb_error_name(rc));
@@ -739,11 +736,15 @@ int osmo_libusb_get_ep_addrs(libusb_device_handle *devh, unsigned int if_num,
int osmo_libusb_init(libusb_context **pluctx)
{
libusb_context *luctx = NULL;
+ const struct libusb_pollfd **pfds;
+
int rc;
rc = libusb_init(pluctx);
- if (rc != 0)
+ if (rc != 0) {
+ LOGP(DLUSB, LOGL_ERROR, "Error initializing libusb: %s\n", libusb_strerror(rc));
return rc;
+ }
if (pluctx)
luctx = *pluctx;
@@ -754,6 +755,17 @@ int osmo_libusb_init(libusb_context **pluctx)
libusb_set_pollfd_notifiers(luctx, osmo_usb_added_cb, osmo_usb_removed_cb, luctx);
+ /* get the initial file descriptors which were created even before during libusb_init() */
+ pfds = libusb_get_pollfds(luctx);
+ if (pfds) {
+ const struct libusb_pollfd **pfds2 = pfds;
+ const struct libusb_pollfd *pfd;
+ /* synthesize 'add' call-backs. not sure why libusb doesn't do that by itself? */
+ for (pfd = *pfds2; pfd; pfd = *++pfds2)
+ osmo_usb_added_cb(pfd->fd, pfd->events, luctx);
+ libusb_free_pollfds(pfds);
+ }
+
return 0;
}
diff --git a/src/vty/Makefile.am b/src/vty/Makefile.am
index 81ff1045..252123da 100644
--- a/src/vty/Makefile.am
+++ b/src/vty/Makefile.am
@@ -1,10 +1,10 @@
# This is _NOT_ the library release version, it's an API version.
# Please read chapter "Library interface versions" of the libtool documentation
# before making any modifications: https://www.gnu.org/software/libtool/manual/html_node/Versioning.html
-LIBVERSION=8:1:4
+LIBVERSION=12:0:3
-AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_builddir)/include
-AM_CFLAGS = -Wall $(TALLOC_CFLAGS)
+AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_builddir)/include -I$(top_builddir)
+AM_CFLAGS = -Wall $(TALLOC_CFLAGS) $(PTHREAD_CFLAGS)
if ENABLE_VTY
lib_LTLIBRARIES = libosmovty.la
@@ -14,5 +14,5 @@ libosmovty_la_SOURCES = buffer.c command.c vty.c vector.c utils.c \
fsm_vty.c talloc_ctx_vty.c \
cpu_sched_vty.c tdef_vty.c
libosmovty_la_LDFLAGS = -version-info $(LIBVERSION) -no-undefined
-libosmovty_la_LIBADD = $(top_builddir)/src/libosmocore.la $(TALLOC_LIBS)
+libosmovty_la_LIBADD = $(top_builddir)/src/core/libosmocore.la $(TALLOC_LIBS) $(PTHREAD_LIBS)
endif
diff --git a/src/vty/command.c b/src/vty/command.c
index bad26888..1e76474f 100644
--- a/src/vty/command.c
+++ b/src/vty/command.c
@@ -38,14 +38,18 @@ Boston, MA 02110-1301, USA. */
#include <ctype.h>
#include <time.h>
#include <limits.h>
+#include <signal.h>
#include <sys/time.h>
#include <sys/stat.h>
+#include <sys/types.h>
#include <osmocom/vty/vector.h>
#include <osmocom/vty/vty.h>
#include <osmocom/vty/command.h>
+#include <osmocom/core/logging.h>
#include <osmocom/core/talloc.h>
+#include <osmocom/core/timer.h>
#include <osmocom/core/utils.h>
#ifndef MAXPATHLEN
@@ -63,6 +67,9 @@ Boston, MA 02110-1301, USA. */
void *tall_vty_cmd_ctx;
+/* Set by on_dso_load_starttime() for "show uptime". */
+static struct timespec starttime;
+
/* Command vector which includes some level of command lists. Normally
each daemon maintains each own cmdvec. */
vector cmdvec;
@@ -622,33 +629,119 @@ static char *xml_escape(const char *inp)
typedef int (*print_func_t)(void *data, const char *fmt, ...);
+static const struct value_string cmd_attr_desc[] = {
+ { CMD_ATTR_DEPRECATED, "This command is deprecated" },
+ { CMD_ATTR_HIDDEN, "This command is hidden (check expert mode)" },
+ { CMD_ATTR_IMMEDIATE, "This command applies immediately" },
+ { CMD_ATTR_NODE_EXIT, "This command applies on VTY node exit" },
+ /* CMD_ATTR_LIB_COMMAND is intentionally skipped */
+ { 0, NULL }
+};
+
+/* Public attributes (to be printed in the VTY / XML reference) */
+#define CMD_ATTR_PUBLIC_MASK \
+ (CMD_ATTR_HIDDEN | CMD_ATTR_IMMEDIATE | CMD_ATTR_NODE_EXIT)
+
+/* Get a flag character for a global VTY command attribute */
+static char cmd_attr_get_flag(unsigned int attr)
+{
+ switch (attr) {
+ case CMD_ATTR_HIDDEN:
+ return '^';
+ case CMD_ATTR_IMMEDIATE:
+ return '!';
+ case CMD_ATTR_NODE_EXIT:
+ return '@';
+ default:
+ return '.';
+ }
+}
+
+/* Description of attributes shared between the lib commands */
+static const char * const cmd_lib_attr_desc[32] = {
+ /* [OSMO_LIBNAME_LIB_ATTR_ATTRNAME] = \
+ * "Brief but meaningful description", */
+ [OSMO_SCCP_LIB_ATTR_RSTRT_ASP] = \
+ "This command applies on ASP restart",
+ [OSMO_ABIS_LIB_ATTR_IPA_NEW_LNK] = \
+ "This command applies on IPA link establishment",
+ [OSMO_ABIS_LIB_ATTR_LINE_UPD] = \
+ "This command applies on E1 line update",
+};
+
+/* Flag letters of attributes shared between the lib commands.
+ * NOTE: uppercase letters only, the rest is reserved for applications. */
+static const char cmd_lib_attr_letters[32] = {
+ /* [OSMO_LIBNAME_LIB_ATTR_ATTRNAME] = 'X', */
+ [OSMO_SCCP_LIB_ATTR_RSTRT_ASP] = 'A',
+ [OSMO_ABIS_LIB_ATTR_IPA_NEW_LNK] = 'I',
+ [OSMO_ABIS_LIB_ATTR_LINE_UPD] = 'L',
+};
+
/*
* Write one cmd_element as XML via a print_func_t.
*/
-static int vty_dump_element(struct cmd_element *cmd, print_func_t print_func, void *data, const char *newline)
+static int vty_dump_element(const struct cmd_element *cmd, print_func_t print_func,
+ void *data, const char *newline)
{
char *xml_string = xml_escape(cmd->string);
unsigned int i;
print_func(data, " <command id='%s'>%s", xml_string, newline);
+ /* Print global attributes and their description */
+ if (cmd->attr & CMD_ATTR_PUBLIC_MASK) { /* ... only public ones */
+ print_func(data, " <attributes scope='global'>%s", newline);
+
+ for (i = 0; i < ARRAY_SIZE(cmd_attr_desc) - 1; i++) {
+ char *xml_att_desc;
+ char flag;
+
+ if (~cmd->attr & cmd_attr_desc[i].value)
+ continue;
+
+ xml_att_desc = xml_escape(cmd_attr_desc[i].str);
+ print_func(data, " <attribute doc='%s'",
+ xml_att_desc, newline);
+ talloc_free(xml_att_desc);
+
+ flag = cmd_attr_get_flag(cmd_attr_desc[i].value);
+ if (flag != '.')
+ print_func(data, " flag='%c'", flag);
+ print_func(data, " />%s", newline);
+ }
+
+ print_func(data, " </attributes>%s", newline);
+ }
+
/* Print application specific attributes and their description */
if (cmd->usrattr != 0x00) { /* ... if at least one flag is set */
- print_func(data, " <attributes scope='application'>%s", newline);
+ const char * const *desc;
+ const char *letters;
+
+ if (cmd->attr & CMD_ATTR_LIB_COMMAND) {
+ print_func(data, " <attributes scope='library'>%s", newline);
+ letters = &cmd_lib_attr_letters[0];
+ desc = &cmd_lib_attr_desc[0];
+ } else {
+ print_func(data, " <attributes scope='application'>%s", newline);
+ letters = &host.app_info->usr_attr_letters[0];
+ desc = &host.app_info->usr_attr_desc[0];
+ }
for (i = 0; i < ARRAY_SIZE(host.app_info->usr_attr_desc); i++) {
char *xml_att_desc;
char flag;
/* Skip attribute if *not* set */
- if (~cmd->usrattr & (1 << i))
+ if (~cmd->usrattr & ((unsigned)1 << i))
continue;
- xml_att_desc = xml_escape(host.app_info->usr_attr_desc[i]);
+ xml_att_desc = xml_escape(desc[i]);
print_func(data, " <attribute doc='%s'", xml_att_desc);
talloc_free(xml_att_desc);
- if ((flag = host.app_info->usr_attr_letters[i]) != '\0')
+ if ((flag = letters[i]) != '\0')
print_func(data, " flag='%c'", flag);
print_func(data, " />%s", newline);
}
@@ -683,12 +776,27 @@ static int vty_dump_element(struct cmd_element *cmd, print_func_t print_func, vo
return 0;
}
-static bool vty_command_is_common(struct cmd_element *cmd);
+static bool vty_command_is_common(const struct cmd_element *cmd);
/*
* Dump all nodes and commands associated with a given node as XML via a print_func_t.
+ *
+ * (gflag_mask, match = false) - print only those commands with non-matching flags.
+ * (gflag_mask, match = true) - print only those commands with matching flags.
+ *
+ * Some examples:
+ *
+ * Print all commands except deprecated and hidden (default mode):
+ * (CMD_ATTR_DEPRECATED | CMD_ATTR_HIDDEN, false)
+ * Print only deprecated and hidden commands:
+ * (CMD_ATTR_DEPRECATED | CMD_ATTR_HIDDEN, true)
+ * Print all commands except deprecated (expert mode):
+ * (CMD_ATTR_DEPRECATED, false)
+ * Print only hidden commands:
+ * (CMD_ATTR_HIDDEN, true)
*/
-static int vty_dump_nodes(print_func_t print_func, void *data, const char *newline)
+static int vty_dump_nodes(print_func_t print_func, void *data, const char *newline,
+ unsigned char gflag_mask, bool match)
{
int i, j;
int same_name_count;
@@ -701,27 +809,27 @@ static int vty_dump_nodes(print_func_t print_func, void *data, const char *newli
print_func(data, " <description>These commands are available on all VTY nodes. They are listed"
" here only once, to unclutter the VTY reference.</description>%s", newline);
for (i = 0; i < vector_active(cmdvec); ++i) {
- struct cmd_node *cnode;
- cnode = vector_slot(cmdvec, i);
+ const struct cmd_node *cnode = vector_slot(cmdvec, i);
if (!cnode)
continue;
if (cnode->node != CONFIG_NODE)
continue;
for (j = 0; j < vector_active(cnode->cmd_vector); ++j) {
- struct cmd_element *elem;
- elem = vector_slot(cnode->cmd_vector, j);
+ const struct cmd_element *elem = vector_slot(cnode->cmd_vector, j);
if (!vty_command_is_common(elem))
continue;
- if (!(elem->attr & (CMD_ATTR_DEPRECATED | CMD_ATTR_HIDDEN)))
- vty_dump_element(elem, print_func, data, newline);
+ if (!match && (elem->attr & gflag_mask) != 0x00)
+ continue;
+ if (match && (elem->attr & gflag_mask) == 0x00)
+ continue;
+ vty_dump_element(elem, print_func, data, newline);
}
}
print_func(data, " </node>%s", newline);
for (i = 0; i < vector_active(cmdvec); ++i) {
- struct cmd_node *cnode;
- cnode = vector_slot(cmdvec, i);
+ const struct cmd_node *cnode = vector_slot(cmdvec, i);
if (!cnode)
continue;
if (vector_active(cnode->cmd_vector) < 1)
@@ -732,8 +840,7 @@ static int vty_dump_nodes(print_func_t print_func, void *data, const char *newli
* 'name', the second becomes 'name_2', then 'name_3', ... */
same_name_count = 1;
for (j = 0; j < i; ++j) {
- struct cmd_node *cnode2;
- cnode2 = vector_slot(cmdvec, j);
+ const struct cmd_node *cnode2 = vector_slot(cmdvec, j);
if (!cnode2)
continue;
if (strcmp(cnode->name, cnode2->name) == 0)
@@ -747,12 +854,14 @@ static int vty_dump_nodes(print_func_t print_func, void *data, const char *newli
print_func(data, " <name>%s</name>%s", cnode->name, newline);
for (j = 0; j < vector_active(cnode->cmd_vector); ++j) {
- struct cmd_element *elem;
- elem = vector_slot(cnode->cmd_vector, j);
+ const struct cmd_element *elem = vector_slot(cnode->cmd_vector, j);
if (vty_command_is_common(elem))
continue;
- if (!(elem->attr & (CMD_ATTR_DEPRECATED | CMD_ATTR_HIDDEN)))
- vty_dump_element(elem, print_func, data, newline);
+ if (!match && (elem->attr & gflag_mask) != 0x00)
+ continue;
+ if (match && (elem->attr & gflag_mask) == 0x00)
+ continue;
+ vty_dump_element(elem, print_func, data, newline);
}
print_func(data, " </node>%s", newline);
@@ -776,7 +885,10 @@ static int print_func_vty(void *data, const char *format, ...)
static int vty_dump_xml_ref_to_vty(struct vty *vty)
{
- return vty_dump_nodes(print_func_vty, vty, VTY_NEWLINE);
+ unsigned char gflag_mask = CMD_ATTR_DEPRECATED;
+ if (!vty->expert_mode)
+ gflag_mask |= CMD_ATTR_HIDDEN;
+ return vty_dump_nodes(print_func_vty, vty, VTY_NEWLINE, gflag_mask, false);
}
static int print_func_stream(void *data, const char *format, ...)
@@ -789,11 +901,61 @@ static int print_func_stream(void *data, const char *format, ...)
return rc;
}
+const struct value_string vty_ref_gen_mode_names[] = {
+ { VTY_REF_GEN_MODE_DEFAULT, "default" },
+ { VTY_REF_GEN_MODE_EXPERT, "expert" },
+ { VTY_REF_GEN_MODE_HIDDEN, "hidden" },
+ { 0, NULL }
+};
+
+const struct value_string vty_ref_gen_mode_desc[] = {
+ { VTY_REF_GEN_MODE_DEFAULT, "all commands except deprecated and hidden" },
+ { VTY_REF_GEN_MODE_EXPERT, "all commands including hidden, excluding deprecated" },
+ { VTY_REF_GEN_MODE_HIDDEN, "hidden commands only" },
+ { 0, NULL }
+};
+
/*! Print the XML reference of all VTY nodes to the given stream.
+ * \param[out] stream Output stream to print the XML reference to.
+ * \param[in] mode The XML reference generation mode.
+ * \returns always 0 for now, no errors possible.
+ */
+int vty_dump_xml_ref_mode(FILE *stream, enum vty_ref_gen_mode mode)
+{
+ unsigned char gflag_mask;
+ bool match = false;
+
+ switch (mode) {
+ case VTY_REF_GEN_MODE_EXPERT:
+ /* All commands except deprecated */
+ gflag_mask = CMD_ATTR_DEPRECATED;
+ break;
+ case VTY_REF_GEN_MODE_HIDDEN:
+ /* Only hidden commands */
+ gflag_mask = CMD_ATTR_HIDDEN;
+ match = true;
+ break;
+ case VTY_REF_GEN_MODE_DEFAULT:
+ default:
+ /* All commands except deprecated and hidden */
+ gflag_mask = CMD_ATTR_DEPRECATED | CMD_ATTR_HIDDEN;
+ break;
+ }
+
+ return vty_dump_nodes(print_func_stream, stream, "\n", gflag_mask, match);
+}
+
+/*! Print the XML reference of all VTY nodes to the given stream.
+ * \param[out] stream Output stream to print the XML reference to.
+ * \returns always 0 for now, no errors possible.
+ *
+ * NOTE: this function is deprecated because it does not allow to
+ * specify the XML reference generation mode (default mode
+ * is hard-coded). Use vty_dump_xml_ref_mode() instead.
*/
int vty_dump_xml_ref(FILE *stream)
{
- return vty_dump_nodes(print_func_stream, stream, "\n");
+ return vty_dump_xml_ref_mode(stream, VTY_REF_GEN_MODE_DEFAULT);
}
/* Check if a command with given string exists at given node */
@@ -833,6 +995,16 @@ void install_element(int ntype, struct cmd_element *cmd)
cmd->cmdsize = cmd_cmdsize(cmd->strvec);
}
+/*! Install a library command into a node
+ * \param[in] ntype Node Type
+ * \param[in] cmd element to be installed
+ */
+void install_lib_element(int ntype, struct cmd_element *cmd)
+{
+ cmd->attr |= CMD_ATTR_LIB_COMMAND;
+ install_element(ntype, cmd);
+}
+
/* Install a command into VIEW and ENABLE node */
void install_element_ve(struct cmd_element *cmd)
{
@@ -840,6 +1012,13 @@ void install_element_ve(struct cmd_element *cmd)
install_element(ENABLE_NODE, cmd);
}
+/* Install a library command into VIEW and ENABLE node */
+void install_lib_element_ve(struct cmd_element *cmd)
+{
+ cmd->attr |= CMD_ATTR_LIB_COMMAND;
+ install_element_ve(cmd);
+}
+
#ifdef VTY_CRYPT_PW
static unsigned char itoa64[] =
"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
@@ -913,6 +1092,7 @@ static int config_write_host(struct vty *vty)
static vector cmd_node_vector(vector v, enum node_type ntype)
{
struct cmd_node *cnode = vector_slot(v, ntype);
+ OSMO_ASSERT(cnode != NULL);
return cnode->cmd_vector;
}
@@ -1170,7 +1350,6 @@ static enum match_type cmd_ipv6_prefix_match(const char *str)
int colons = 0, nums = 0, double_colon = 0;
int mask;
const char *sp = NULL;
- char *endptr = NULL;
if (str == NULL)
return PARTLY_MATCH;
@@ -1268,11 +1447,7 @@ static enum match_type cmd_ipv6_prefix_match(const char *str)
if (state < STATE_MASK)
return PARTLY_MATCH;
- mask = strtol(str, &endptr, 10);
- if (*endptr != '\0')
- return NO_MATCH;
-
- if (mask < 0 || mask > 128)
+ if (osmo_str_to_int(&mask, str, 10, 0, 128))
return NO_MATCH;
/* I don't know why mask < 13 makes command match partly.
@@ -1305,19 +1480,52 @@ static enum match_type cmd_ipv6_prefix_match(const char *str)
#error "LONG_MAX not defined!"
#endif
-static int cmd_range_match(const char *range, const char *str)
+/* This function is aimed at quickly guessing & filtering the numeric base a
+ * string can contain, by no means validates the entire value.
+ * Returns 16 if string follows pattern "({+,-}0x[digits])"
+ * Returns 10 if string follows pattern "({+,-}[digits])"
+ * Returns a negative value if something other is detected (error)
+*/
+static int check_base(const char *str)
+{
+ const char *ptr = str;
+ /* Skip any space */
+ while (isspace(*ptr)) ptr++;
+
+ /* Skip optional sign: */
+ if (*ptr == '+' || *ptr == '-')
+ ptr++;
+ if (*ptr == '0') {
+ ptr++;
+ if (*ptr == '\0' || isdigit(*ptr))
+ return 10;
+ if (!(*ptr == 'x' || *ptr == 'X'))
+ return -1;
+ ptr++;
+ if (isxdigit(*ptr))
+ return 16;
+ return -1;
+ }
+ return 10;
+}
+
+int vty_cmd_range_match(const char *range, const char *str)
{
char *p;
char buf[DECIMAL_STRLEN_MAX_UNSIGNED + 1];
char *endptr = NULL;
+ int min_base, max_base, val_base;
if (str == NULL)
return 1;
+ if ((val_base = check_base(str)) < 0)
+ return 0;
+
if (range[1] == '-') {
signed long min = 0, max = 0, val;
- val = strtol(str, &endptr, 10);
+ val = strtol(str, &endptr, val_base);
if (*endptr != '\0')
return 0;
@@ -1329,7 +1537,9 @@ static int cmd_range_match(const char *range, const char *str)
return 0;
strncpy(buf, range, p - range);
buf[p - range] = '\0';
- min = -strtol(buf, &endptr, 10);
+ if ((min_base = check_base(buf)) < 0)
+ return 0;
+ min = -strtol(buf, &endptr, min_base);
if (*endptr != '\0')
return 0;
@@ -1341,7 +1551,9 @@ static int cmd_range_match(const char *range, const char *str)
return 0;
strncpy(buf, range, p - range);
buf[p - range] = '\0';
- max = strtol(buf, &endptr, 10);
+ if ((max_base = check_base(buf)) < 0)
+ return 0;
+ max = strtol(buf, &endptr, max_base);
if (*endptr != '\0')
return 0;
@@ -1353,7 +1565,7 @@ static int cmd_range_match(const char *range, const char *str)
if (str[0] == '-')
return 0;
- val = strtoul(str, &endptr, 10);
+ val = strtoul(str, &endptr, val_base);
if (*endptr != '\0')
return 0;
@@ -1365,7 +1577,9 @@ static int cmd_range_match(const char *range, const char *str)
return 0;
strncpy(buf, range, p - range);
buf[p - range] = '\0';
- min = strtoul(buf, &endptr, 10);
+ if ((min_base = check_base(buf)) < 0)
+ return 0;
+ min = strtoul(buf, &endptr, min_base);
if (*endptr != '\0')
return 0;
@@ -1377,7 +1591,9 @@ static int cmd_range_match(const char *range, const char *str)
return 0;
strncpy(buf, range, p - range);
buf[p - range] = '\0';
- max = strtoul(buf, &endptr, 10);
+ if ((max_base = check_base(buf)) < 0)
+ return 0;
+ max = strtoul(buf, &endptr, max_base);
if (*endptr != '\0')
return 0;
@@ -1385,6 +1601,14 @@ static int cmd_range_match(const char *range, const char *str)
return 0;
}
+ /* Don't allow ranges specified by min and max with different bases */
+ if (min_base != max_base)
+ return 0;
+ /* arg value passed must match the base of the range */
+ if (min_base != val_base)
+ return 0;
+
+ /* Everything's fine */
return 1;
}
@@ -1428,7 +1652,7 @@ cmd_match(const char *str, const char *command,
return VARARG_MATCH;
else if (CMD_RANGE(str))
{
- if (cmd_range_match(str, command))
+ if (vty_cmd_range_match(str, command))
return RANGE_MATCH;
}
#ifdef HAVE_IPV6
@@ -1625,7 +1849,7 @@ is_cmd_ambiguous(char *command, vector v, int index, enum match_type type)
}
break;
case RANGE_MATCH:
- if (cmd_range_match
+ if (vty_cmd_range_match
(str, command)) {
if (matched
&& strcmp(matched,
@@ -1718,7 +1942,7 @@ static const char *cmd_entry_function_desc(const char *src, const char *dst)
return dst;
if (CMD_RANGE(dst)) {
- if (cmd_range_match(dst, src))
+ if (vty_cmd_range_match(dst, src))
return dst;
else
return NULL;
@@ -1907,7 +2131,9 @@ cmd_describe_command_real(vector vline, struct vty *vty, int *status)
if (!cmd_element)
continue;
- if (cmd_element->attr & (CMD_ATTR_DEPRECATED|CMD_ATTR_HIDDEN))
+ if (cmd_element->attr & CMD_ATTR_DEPRECATED)
+ continue;
+ if (!vty->expert_mode && (cmd_element->attr & CMD_ATTR_HIDDEN))
continue;
strvec = cmd_element->strvec;
@@ -2767,7 +2993,10 @@ DEFUN(config_terminal,
}
/* Enable command */
-DEFUN(enable, config_enable_cmd, "enable", "Turn on privileged mode command\n")
+DEFUN(enable, config_enable_cmd,
+ "enable [expert-mode]",
+ "Turn on privileged mode command\n"
+ "Enable the expert mode (show hidden commands)\n")
{
/* If enable password is NULL, change to ENABLE_NODE */
if ((host.enable == NULL && host.enable_encrypt == NULL) ||
@@ -2776,6 +3005,8 @@ DEFUN(enable, config_enable_cmd, "enable", "Turn on privileged mode command\n")
else
vty->node = AUTH_ENABLE_NODE;
+ vty->expert_mode = argc > 0;
+
return CMD_SUCCESS;
}
@@ -2785,6 +3016,9 @@ DEFUN(disable,
{
if (vty->node == ENABLE_NODE)
vty->node = VIEW_NODE;
+
+ vty->expert_mode = false;
+
return CMD_SUCCESS;
}
@@ -2836,6 +3070,18 @@ gDEFUN(config_exit,
return CMD_SUCCESS;
}
+DEFUN(shutdown,
+ shutdown_cmd, "shutdown", "Request a shutdown of the program\n")
+{
+ LOGP(DLGLOBAL, LOGL_INFO, "Shutdown requested from telnet\n");
+ vty_out(vty, "%s is shutting down. Bye!%s", host.app_info->name, VTY_NEWLINE);
+
+ /* Same exit path as if it was killed by the service manager */
+ kill(getpid(), SIGTERM);
+
+ return CMD_SUCCESS;
+}
+
/* Show version. */
DEFUN(show_version,
show_version_cmd, "show version", SHOW_STR "Displays program version\n")
@@ -2855,6 +3101,23 @@ DEFUN(show_online_help,
return CMD_SUCCESS;
}
+DEFUN(show_pid,
+ show_pid_cmd, "show pid", SHOW_STR "Displays the process ID\n")
+{
+ vty_out(vty, "%d%s", getpid(), VTY_NEWLINE);
+ return CMD_SUCCESS;
+}
+
+DEFUN(show_uptime,
+ show_uptime_cmd, "show uptime", SHOW_STR "Displays how long the program has been running\n")
+{
+ vty_out(vty, "%s has been running for ", host.app_info->name);
+ vty_out_uptime(vty, &starttime);
+ vty_out_newline(vty);
+
+ return CMD_SUCCESS;
+}
+
/* Help display function for all node. */
gDEFUN(config_help,
config_help_cmd, "help", "Description of the interactive help system\n")
@@ -2877,19 +3140,215 @@ gDEFUN(config_help,
return CMD_SUCCESS;
}
+enum {
+ ATTR_TYPE_GLOBAL = (1 << 0),
+ ATTR_TYPE_LIB = (1 << 1),
+ ATTR_TYPE_APP = (1 << 2),
+};
+
+static void print_attr_list(struct vty *vty, unsigned int attr_mask)
+{
+ const char *desc;
+ unsigned int i;
+ bool found;
+ char flag;
+
+ if (attr_mask & ATTR_TYPE_GLOBAL) {
+ vty_out(vty, " Global attributes:%s", VTY_NEWLINE);
+
+ for (i = 0; i < ARRAY_SIZE(cmd_attr_desc) - 1; i++) {
+ flag = cmd_attr_get_flag(cmd_attr_desc[i].value);
+ desc = cmd_attr_desc[i].str;
+
+ /* Skip attributes without flags */
+ if (flag != '.')
+ vty_out(vty, " %c %s%s", flag, desc, VTY_NEWLINE);
+ }
+ }
+
+ if (attr_mask & ATTR_TYPE_LIB) {
+ vty_out(vty, " Library specific attributes:%s", VTY_NEWLINE);
+
+ for (i = 0, found = false; i < _OSMO_CORE_LIB_ATTR_COUNT; i++) {
+ if ((desc = cmd_lib_attr_desc[i]) == NULL)
+ continue;
+ found = true;
+
+ flag = cmd_lib_attr_letters[i];
+ if (flag == '\0')
+ flag = '.';
+
+ vty_out(vty, " %c %s%s", flag, desc, VTY_NEWLINE);
+ }
+
+ if (!found)
+ vty_out(vty, " (no attributes)%s", VTY_NEWLINE);
+ }
+
+ if (attr_mask & ATTR_TYPE_APP) {
+ vty_out(vty, " Application specific attributes:%s", VTY_NEWLINE);
+
+ for (i = 0, found = false; i < VTY_CMD_USR_ATTR_NUM; i++) {
+ if ((desc = host.app_info->usr_attr_desc[i]) == NULL)
+ continue;
+ found = true;
+
+ flag = host.app_info->usr_attr_letters[i];
+ if (flag == '\0')
+ flag = '.';
+
+ vty_out(vty, " %c %s%s", flag, desc, VTY_NEWLINE);
+ }
+
+ if (!found)
+ vty_out(vty, " (no attributes)%s", VTY_NEWLINE);
+ }
+}
+
+gDEFUN(show_vty_attr_all, show_vty_attr_all_cmd,
+ "show vty-attributes",
+ SHOW_STR "List of VTY attributes\n")
+{
+ print_attr_list(vty, 0xff);
+ return CMD_SUCCESS;
+}
+
+gDEFUN(show_vty_attr, show_vty_attr_cmd,
+ "show vty-attributes (application|library|global)",
+ SHOW_STR "List of VTY attributes\n"
+ "Application specific attributes only\n"
+ "Library specific attributes only\n"
+ "Global attributes only\n")
+{
+ unsigned int attr_mask = 0;
+
+ if (argv[0][0] == 'g') /* global */
+ attr_mask |= ATTR_TYPE_GLOBAL;
+ else if (argv[0][0] == 'l') /* library */
+ attr_mask |= ATTR_TYPE_LIB;
+ else if (argv[0][0] == 'a') /* application */
+ attr_mask |= ATTR_TYPE_APP;
+
+ print_attr_list(vty, attr_mask);
+ return CMD_SUCCESS;
+}
+
+/* Compose flag bit-mask for all commands within the given node */
+static unsigned int node_flag_mask(const struct cmd_node *cnode, bool expert_mode)
+{
+ unsigned int flag_mask = 0x00;
+ unsigned int f, i;
+
+ for (f = 0; f < VTY_CMD_USR_ATTR_NUM; f++) {
+ for (i = 0; i < vector_active(cnode->cmd_vector); i++) {
+ const struct cmd_element *cmd;
+ char flag_letter;
+
+ if ((cmd = vector_slot(cnode->cmd_vector, i)) == NULL)
+ continue;
+ if (cmd->attr & CMD_ATTR_DEPRECATED)
+ continue;
+ if (!expert_mode && (cmd->attr & CMD_ATTR_HIDDEN))
+ continue;
+ if (~cmd->usrattr & ((unsigned)1 << f))
+ continue;
+
+ if (cmd->attr & CMD_ATTR_LIB_COMMAND)
+ flag_letter = cmd_lib_attr_letters[f];
+ else
+ flag_letter = host.app_info->usr_attr_letters[f];
+
+ if (flag_letter == '\0')
+ continue;
+
+ flag_mask |= (1 << f);
+ break;
+ }
+ }
+
+ return flag_mask;
+}
+
+/* Compose global flag char-mask for the given command (e.g. "!" or "@") */
+static const char *cmd_gflag_mask(const struct cmd_element *cmd)
+{
+ static char char_mask[8 + 1];
+ char *ptr = &char_mask[0];
+
+ /* Mutually exclusive global attributes */
+ if (cmd->attr & CMD_ATTR_HIDDEN)
+ *(ptr++) = cmd_attr_get_flag(CMD_ATTR_HIDDEN);
+ else if (cmd->attr & CMD_ATTR_IMMEDIATE)
+ *(ptr++) = cmd_attr_get_flag(CMD_ATTR_IMMEDIATE);
+ else if (cmd->attr & CMD_ATTR_NODE_EXIT)
+ *(ptr++) = cmd_attr_get_flag(CMD_ATTR_NODE_EXIT);
+ else
+ *(ptr++) = '.';
+
+ *ptr = '\0';
+
+ return char_mask;
+}
+
+/* Compose app / lib flag char-mask for the given command (e.g. ".F.OB..") */
+static const char *cmd_flag_mask(const struct cmd_element *cmd,
+ unsigned int flag_mask)
+{
+ static char char_mask[VTY_CMD_USR_ATTR_NUM + 1];
+ char *ptr = &char_mask[0];
+ char flag_letter;
+ unsigned int f;
+
+ for (f = 0; f < VTY_CMD_USR_ATTR_NUM; f++) {
+ if (~flag_mask & ((unsigned)1 << f))
+ continue;
+ if (~cmd->usrattr & ((unsigned)1 << f)) {
+ *(ptr++) = '.';
+ continue;
+ }
+
+ if (cmd->attr & CMD_ATTR_LIB_COMMAND)
+ flag_letter = cmd_lib_attr_letters[f];
+ else
+ flag_letter = host.app_info->usr_attr_letters[f];
+
+ *(ptr++) = flag_letter ? flag_letter : '.';
+ }
+
+ *ptr = '\0';
+
+ return char_mask;
+}
+
/* Help display function for all node. */
-gDEFUN(config_list, config_list_cmd, "list", "Print command list\n")
+gDEFUN(config_list, config_list_cmd,
+ "list [with-flags]",
+ "Print command list\n"
+ "Also print the VTY attribute flags\n")
{
unsigned int i;
struct cmd_node *cnode = vector_slot(cmdvec, vty->node);
+ unsigned int flag_mask = 0x00;
struct cmd_element *cmd;
+ if (argc > 0)
+ flag_mask = node_flag_mask(cnode, vty->expert_mode);
+
for (i = 0; i < vector_active(cnode->cmd_vector); i++) {
if ((cmd = vector_slot(cnode->cmd_vector, i)) == NULL)
continue;
- if (cmd->attr & (CMD_ATTR_DEPRECATED | CMD_ATTR_HIDDEN))
+ if (cmd->attr & CMD_ATTR_DEPRECATED)
continue;
- vty_out(vty, " %s%s", cmd->string, VTY_NEWLINE);
+ if (!vty->expert_mode && (cmd->attr & CMD_ATTR_HIDDEN))
+ continue;
+ if (!argc)
+ vty_out(vty, " %s%s", cmd->string, VTY_NEWLINE);
+ else {
+ vty_out(vty, " %s %s %s%s",
+ cmd_gflag_mask(cmd),
+ cmd_flag_mask(cmd, flag_mask),
+ cmd->string, VTY_NEWLINE);
+ }
}
return CMD_SUCCESS;
@@ -3380,16 +3839,7 @@ DEFUN(config_terminal_length, config_terminal_length_cmd,
"Set number of lines on a screen\n"
"Number of lines on screen (0 for no pausing)\n")
{
- int lines;
- char *endptr = NULL;
-
- lines = strtol(argv[0], &endptr, 10);
- if (lines < 0 || lines > 512 || *endptr != '\0') {
- vty_out(vty, "length is malformed%s", VTY_NEWLINE);
- return CMD_WARNING;
- }
- vty->lines = lines;
-
+ vty->lines = atoi(argv[0]);
return CMD_SUCCESS;
}
@@ -3408,16 +3858,7 @@ DEFUN(service_terminal_length, service_terminal_length_cmd,
"System wide terminal length configuration\n"
"Number of lines of VTY (0 means no line control)\n")
{
- int lines;
- char *endptr = NULL;
-
- lines = strtol(argv[0], &endptr, 10);
- if (lines < 0 || lines > 512 || *endptr != '\0') {
- vty_out(vty, "length is malformed%s", VTY_NEWLINE);
- return CMD_WARNING;
- }
- host.lines = lines;
-
+ host.lines = atoi(argv[0]);
return CMD_SUCCESS;
}
@@ -3841,6 +4282,11 @@ void host_config_set(const char *filename)
host.config = talloc_strdup(tall_vty_cmd_ctx, filename);
}
+const char *host_config_file(void)
+{
+ return host.config;
+}
+
/*! Deprecated, now happens implicitly when calling install_node().
* Users of the API may still attempt to call this function, hence
* leave it here as a no-op. */
@@ -3858,29 +4304,34 @@ void vty_install_default(int node)
/*! Install common commands like 'exit' and 'list'. */
static void install_basic_node_commands(int node)
{
- install_element(node, &config_help_cmd);
- install_element(node, &config_list_cmd);
+ install_lib_element(node, &config_help_cmd);
+ install_lib_element(node, &config_list_cmd);
- install_element(node, &config_write_terminal_cmd);
- install_element(node, &config_write_file_cmd);
- install_element(node, &config_write_memory_cmd);
- install_element(node, &config_write_cmd);
- install_element(node, &show_running_config_cmd);
+ install_lib_element(node, &show_vty_attr_all_cmd);
+ install_lib_element(node, &show_vty_attr_cmd);
- install_element(node, &config_exit_cmd);
+ install_lib_element(node, &config_write_terminal_cmd);
+ install_lib_element(node, &config_write_file_cmd);
+ install_lib_element(node, &config_write_memory_cmd);
+ install_lib_element(node, &config_write_cmd);
+ install_lib_element(node, &show_running_config_cmd);
+
+ install_lib_element(node, &config_exit_cmd);
if (node >= CONFIG_NODE) {
/* It's not a top node. */
- install_element(node, &config_end_cmd);
+ install_lib_element(node, &config_end_cmd);
}
}
/*! Return true if a node is installed by install_basic_node_commands(), so
* that we can avoid repeating them for each and every node during 'show
* running-config' */
-static bool vty_command_is_common(struct cmd_element *cmd)
+static bool vty_command_is_common(const struct cmd_element *cmd)
{
if (cmd == &config_help_cmd
+ || cmd == &show_vty_attr_all_cmd
+ || cmd == &show_vty_attr_cmd
|| cmd == &config_list_cmd
|| cmd == &config_write_terminal_cmd
|| cmd == &config_write_file_cmd
@@ -3958,55 +4409,92 @@ void cmd_init(int terminal)
install_node(&config_node, config_write_host);
/* Each node's basic commands. */
- install_element(VIEW_NODE, &show_version_cmd);
- install_element(VIEW_NODE, &show_online_help_cmd);
+ install_lib_element(VIEW_NODE, &show_pid_cmd);
+ install_lib_element(VIEW_NODE, &show_uptime_cmd);
+ install_lib_element(VIEW_NODE, &show_version_cmd);
+ install_lib_element(VIEW_NODE, &show_online_help_cmd);
if (terminal) {
- install_element(VIEW_NODE, &config_list_cmd);
- install_element(VIEW_NODE, &config_exit_cmd);
- install_element(VIEW_NODE, &config_help_cmd);
- install_element(VIEW_NODE, &config_enable_cmd);
- install_element(VIEW_NODE, &config_terminal_length_cmd);
- install_element(VIEW_NODE, &config_terminal_no_length_cmd);
- install_element(VIEW_NODE, &echo_cmd);
+ install_lib_element(VIEW_NODE, &config_list_cmd);
+ install_lib_element(VIEW_NODE, &config_exit_cmd);
+ install_lib_element(VIEW_NODE, &config_help_cmd);
+ install_lib_element(VIEW_NODE, &show_vty_attr_all_cmd);
+ install_lib_element(VIEW_NODE, &show_vty_attr_cmd);
+ install_lib_element(VIEW_NODE, &config_enable_cmd);
+ install_lib_element(VIEW_NODE, &config_terminal_length_cmd);
+ install_lib_element(VIEW_NODE, &config_terminal_no_length_cmd);
+ install_lib_element(VIEW_NODE, &echo_cmd);
}
if (terminal) {
- install_element(ENABLE_NODE, &config_disable_cmd);
- install_element(ENABLE_NODE, &config_terminal_cmd);
- install_element (ENABLE_NODE, &copy_runningconfig_startupconfig_cmd);
+ install_lib_element(ENABLE_NODE, &config_disable_cmd);
+ install_lib_element(ENABLE_NODE, &config_terminal_cmd);
+ install_lib_element(ENABLE_NODE, &copy_runningconfig_startupconfig_cmd);
+ install_lib_element(ENABLE_NODE, &shutdown_cmd);
}
- install_element (ENABLE_NODE, &show_startup_config_cmd);
- install_element(ENABLE_NODE, &show_version_cmd);
- install_element(ENABLE_NODE, &show_online_help_cmd);
+ install_lib_element(ENABLE_NODE, &show_startup_config_cmd);
+ install_lib_element(ENABLE_NODE, &show_version_cmd);
+ install_lib_element(ENABLE_NODE, &show_online_help_cmd);
if (terminal) {
- install_element(ENABLE_NODE, &config_terminal_length_cmd);
- install_element(ENABLE_NODE, &config_terminal_no_length_cmd);
- install_element(ENABLE_NODE, &echo_cmd);
+ install_lib_element(ENABLE_NODE, &config_terminal_length_cmd);
+ install_lib_element(ENABLE_NODE, &config_terminal_no_length_cmd);
+ install_lib_element(ENABLE_NODE, &echo_cmd);
}
- install_element(CONFIG_NODE, &hostname_cmd);
- install_element(CONFIG_NODE, &no_hostname_cmd);
+ install_lib_element(CONFIG_NODE, &hostname_cmd);
+ install_lib_element(CONFIG_NODE, &no_hostname_cmd);
if (terminal) {
- install_element(CONFIG_NODE, &password_cmd);
- install_element(CONFIG_NODE, &password_text_cmd);
- install_element(CONFIG_NODE, &enable_password_cmd);
- install_element(CONFIG_NODE, &enable_password_text_cmd);
- install_element(CONFIG_NODE, &no_enable_password_cmd);
+ install_lib_element(CONFIG_NODE, &password_cmd);
+ install_lib_element(CONFIG_NODE, &password_text_cmd);
+ install_lib_element(CONFIG_NODE, &enable_password_cmd);
+ install_lib_element(CONFIG_NODE, &enable_password_text_cmd);
+ install_lib_element(CONFIG_NODE, &no_enable_password_cmd);
#ifdef VTY_CRYPT_PW
- install_element(CONFIG_NODE, &service_password_encrypt_cmd);
- install_element(CONFIG_NODE, &no_service_password_encrypt_cmd);
+ install_lib_element(CONFIG_NODE, &service_password_encrypt_cmd);
+ install_lib_element(CONFIG_NODE, &no_service_password_encrypt_cmd);
#endif
- install_element(CONFIG_NODE, &banner_motd_default_cmd);
- install_element(CONFIG_NODE, &banner_motd_file_cmd);
- install_element(CONFIG_NODE, &no_banner_motd_cmd);
- install_element(CONFIG_NODE, &service_terminal_length_cmd);
- install_element(CONFIG_NODE, &no_service_terminal_length_cmd);
+ install_lib_element(CONFIG_NODE, &banner_motd_default_cmd);
+ install_lib_element(CONFIG_NODE, &banner_motd_file_cmd);
+ install_lib_element(CONFIG_NODE, &no_banner_motd_cmd);
+ install_lib_element(CONFIG_NODE, &service_terminal_length_cmd);
+ install_lib_element(CONFIG_NODE, &no_service_terminal_length_cmd);
}
srand(time(NULL));
}
+static __attribute__((constructor)) void on_dso_load_starttime(void)
+{
+ osmo_clock_gettime(CLOCK_MONOTONIC, &starttime);
+}
+
+/* FIXME: execute this section in the unit test instead */
+static __attribute__((constructor)) void on_dso_load(void)
+{
+ unsigned int i, j;
+
+ /* Check total number of the library specific attributes */
+ OSMO_ASSERT(_OSMO_CORE_LIB_ATTR_COUNT < 32);
+
+ /* Check for duplicates in the list of library specific flags */
+ for (i = 0; i < _OSMO_CORE_LIB_ATTR_COUNT; i++) {
+ if (cmd_lib_attr_letters[i] == '\0')
+ continue;
+
+ /* Some flag characters are reserved for global attributes */
+ const char rafc[] = VTY_CMD_ATTR_FLAGS_RESERVED;
+ for (j = 0; j < ARRAY_SIZE(rafc); j++)
+ OSMO_ASSERT(cmd_lib_attr_letters[i] != rafc[j]);
+
+ /* Only upper case flag letters are allowed for libraries */
+ OSMO_ASSERT(cmd_lib_attr_letters[i] >= 'A');
+ OSMO_ASSERT(cmd_lib_attr_letters[i] <= 'Z');
+
+ for (j = i + 1; j < _OSMO_CORE_LIB_ATTR_COUNT; j++)
+ OSMO_ASSERT(cmd_lib_attr_letters[i] != cmd_lib_attr_letters[j]);
+ }
+}
+
/*! @} */
diff --git a/src/vty/cpu_sched_vty.c b/src/vty/cpu_sched_vty.c
index 35671a91..7198f747 100644
--- a/src/vty/cpu_sched_vty.c
+++ b/src/vty/cpu_sched_vty.c
@@ -25,6 +25,8 @@
#define _GNU_SOURCE
+#include "config.h"
+
#include <string.h>
#include <stdlib.h>
#include <errno.h>
@@ -87,7 +89,7 @@ static struct cmd_node sched_node = {
};
/* returns number of configured CPUs in the system, or negative otherwise */
-static int get_num_cpus() {
+static int get_num_cpus(void) {
static unsigned int num_cpus = 0;
long ln;
@@ -276,7 +278,6 @@ static bool proc_name_exists(const char *name, pid_t *res_pid)
static enum sched_vty_thread_id procname2pid(pid_t *res_pid, const char *str, bool applynow)
{
size_t i, len;
- char *end;
bool is_pid = true;
if (strcmp(str, "all") == 0) {
@@ -297,12 +298,12 @@ static enum sched_vty_thread_id procname2pid(pid_t *res_pid, const char *str, bo
}
}
if (is_pid) {
- errno = 0;
- *res_pid = strtoul(str, &end, 0);
- if ((errno == ERANGE && *res_pid == ULONG_MAX) || (errno && !*res_pid) ||
- str == end) {
+ int64_t val;
+ if (osmo_str_to_int64(&val, str, 0, 0, INT64_MAX))
+ return SCHED_VTY_THREAD_UNKNOWN;
+ *res_pid = (pid_t)val;
+ if (*res_pid != val)
return SCHED_VTY_THREAD_UNKNOWN;
- }
if (!applynow || proc_tid_exists(*res_pid))
return SCHED_VTY_THREAD_ID;
else
@@ -371,7 +372,7 @@ static int my_sched_setaffinity(enum sched_vty_thread_id tid_type, pid_t pid,
}
-DEFUN(cfg_sched_cpu_affinity, cfg_sched_cpu_affinity_cmd,
+DEFUN_ATTR(cfg_sched_cpu_affinity, cfg_sched_cpu_affinity_cmd,
"cpu-affinity (self|all|<0-4294967295>|THREADNAME) CPUHEXMASK [delay]",
"Set CPU affinity mask on a (group of) thread(s)\n"
"Set CPU affinity mask on thread running the VTY\n"
@@ -379,7 +380,8 @@ DEFUN(cfg_sched_cpu_affinity, cfg_sched_cpu_affinity_cmd,
"Set CPU affinity mask on a thread with specified PID\n"
"Set CPU affinity mask on a thread with specified thread name\n"
"CPU affinity mask\n"
- "If set, delay applying the affinity mask now and let the app handle it at a later point\n")
+ "If set, delay applying the affinity mask now and let the app handle it at a later point\n",
+ CMD_ATTR_IMMEDIATE)
{
const char* str_who = argv[0];
const char *str_mask = argv[1];
@@ -476,11 +478,12 @@ static int set_sched_rr(unsigned int prio)
return 0;
}
-DEFUN(cfg_sched_policy, cfg_sched_policy_cmd,
+DEFUN_ATTR(cfg_sched_policy, cfg_sched_policy_cmd,
"policy rr <1-32>",
"Set the scheduling policy to use for the process\n"
"Use the SCHED_RR real-time scheduling algorithm\n"
- "Set the SCHED_RR real-time priority\n")
+ "Set the SCHED_RR real-time priority\n",
+ CMD_ATTR_IMMEDIATE)
{
sched_vty_opts->sched_rr_prio = atoi(argv[0]);
@@ -604,13 +607,13 @@ int osmo_cpu_sched_vty_init(void *tall_ctx)
INIT_LLIST_HEAD(&sched_vty_opts->cpu_affinity_li);
pthread_mutex_init(&sched_vty_opts->cpu_affinity_li_mutex, NULL);
- install_element(CONFIG_NODE, &cfg_sched_cmd);
+ install_lib_element(CONFIG_NODE, &cfg_sched_cmd);
install_node(&sched_node, config_write_sched);
- install_element(L_CPU_SCHED_NODE, &cfg_sched_policy_cmd);
- install_element(L_CPU_SCHED_NODE, &cfg_sched_cpu_affinity_cmd);
+ install_lib_element(L_CPU_SCHED_NODE, &cfg_sched_policy_cmd);
+ install_lib_element(L_CPU_SCHED_NODE, &cfg_sched_cpu_affinity_cmd);
- install_element_ve(&show_sched_threads_cmd);
+ install_lib_element_ve(&show_sched_threads_cmd);
/* Initialize amount of cpus now */
if (get_num_cpus() < 0)
@@ -636,8 +639,10 @@ int osmo_cpu_sched_vty_apply_localthread(void)
return 0;
}
+#ifdef HAVE_PTHREAD_GETNAME_NP
if (pthread_getname_np(pthread_self(), name, sizeof(name)) == 0)
has_name = true;
+#endif
/* Get latest matching mask for the thread */
pthread_mutex_lock(&sched_vty_opts->cpu_affinity_li_mutex);
diff --git a/src/vty/fsm_vty.c b/src/vty/fsm_vty.c
index 9bde241c..da6038fa 100644
--- a/src/vty/fsm_vty.c
+++ b/src/vty/fsm_vty.c
@@ -14,16 +14,12 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <stdlib.h>
#include <string.h>
-#include "../../config.h"
+#include "config.h"
#include <osmocom/vty/command.h>
#include <osmocom/vty/buffer.h>
@@ -51,62 +47,82 @@ extern struct llist_head osmo_g_fsms;
/*! Print information about a FSM [class] to the given VTY
* \param vty The VTY to which to print
+ * \param[in] prefix prefix to print at start of each line (typically indenting)
* \param[in] fsm The FSM class to print
*/
-void vty_out_fsm(struct vty *vty, struct osmo_fsm *fsm)
+void vty_out_fsm2(struct vty *vty, const char *prefix, struct osmo_fsm *fsm)
{
unsigned int i;
const struct value_string *evt_name;
- vty_out(vty, "FSM Name: '%s', Log Subsys: '%s'%s", fsm->name,
+ vty_out(vty, "%sFSM Name: '%s', Log Subsys: '%s'%s", prefix, fsm->name,
log_category_name(fsm->log_subsys), VTY_NEWLINE);
/* list the events */
if (fsm->event_names) {
for (evt_name = fsm->event_names; evt_name->str != NULL; evt_name++) {
- vty_out(vty, " Event %02u (0x%08x): '%s'%s", evt_name->value,
- (1 << evt_name->value), evt_name->str, VTY_NEWLINE);
+ vty_out(vty, "%s Event %02u (0x%08x): '%s'%s", prefix, evt_name->value,
+ (1U << evt_name->value), evt_name->str, VTY_NEWLINE);
}
} else
- vty_out(vty, " No event names are defined for this FSM! Please fix!%s", VTY_NEWLINE);
+ vty_out(vty, "%s No event names are defined for this FSM! Please fix!%s", prefix, VTY_NEWLINE);
/* list the states */
- vty_out(vty, " Number of States: %u%s", fsm->num_states, VTY_NEWLINE);
+ vty_out(vty, "%s Number of States: %u%s", prefix, fsm->num_states, VTY_NEWLINE);
for (i = 0; i < fsm->num_states; i++) {
const struct osmo_fsm_state *state = &fsm->states[i];
- vty_out(vty, " State %-20s InEvtMask: 0x%08x, OutStateMask: 0x%08x%s",
+ vty_out(vty, "%s State %-20s InEvtMask: 0x%08x, OutStateMask: 0x%08x%s", prefix,
state->name, state->in_event_mask, state->out_state_mask,
VTY_NEWLINE);
}
}
+/*! Print information about a FSM [class] to the given VTY
+ * \param vty The VTY to which to print
+ * \param[in] fsm The FSM class to print
+ */
+void vty_out_fsm(struct vty *vty, struct osmo_fsm *fsm)
+{
+ vty_out_fsm2(vty, "", fsm);
+}
+
/*! Print a FSM instance to the given VTY
* \param vty The VTY to which to print
+ * \param[in] prefix prefix to print at start of each line (typically indenting)
* \param[in] fsmi The FSM instance to print
*/
-void vty_out_fsm_inst(struct vty *vty, struct osmo_fsm_inst *fsmi)
+void vty_out_fsm_inst2(struct vty *vty, const char *prefix, struct osmo_fsm_inst *fsmi)
{
struct osmo_fsm_inst *child;
- vty_out(vty, "FSM Instance Name: '%s', ID: '%s'%s",
+ vty_out(vty, "%sFSM Instance Name: '%s', ID: '%s'%s", prefix,
fsmi->name, fsmi->id, VTY_NEWLINE);
- vty_out(vty, " Log-Level: '%s', State: '%s'%s",
+ vty_out(vty, "%s Log-Level: '%s', State: '%s'%s", prefix,
log_level_str(fsmi->log_level),
osmo_fsm_state_name(fsmi->fsm, fsmi->state),
VTY_NEWLINE);
if (fsmi->T)
- vty_out(vty, " Timer: %u%s", fsmi->T, VTY_NEWLINE);
+ vty_out(vty, "%s Timer: %u%s", prefix, fsmi->T, VTY_NEWLINE);
if (fsmi->proc.parent) {
- vty_out(vty, " Parent: '%s', Term-Event: '%s'%s",
+ vty_out(vty, "%s Parent: '%s', Term-Event: '%s'%s", prefix,
fsmi->proc.parent->name,
osmo_fsm_event_name(fsmi->proc.parent->fsm,
fsmi->proc.parent_term_event),
VTY_NEWLINE);
}
llist_for_each_entry(child, &fsmi->proc.children, proc.child) {
- vty_out(vty, " Child: '%s'%s", child->name, VTY_NEWLINE);
+ vty_out(vty, "%s Child: '%s'%s", prefix, child->name, VTY_NEWLINE);
}
}
+/*! Print a FSM instance to the given VTY
+ * \param vty The VTY to which to print
+ * \param[in] fsmi The FSM instance to print
+ */
+void vty_out_fsm_inst(struct vty *vty, struct osmo_fsm_inst *fsmi)
+{
+ vty_out_fsm_inst2(vty, "", fsmi);
+}
+
#define SH_FSM_STR SHOW_STR "Show information about finite state machines\n"
#define SH_FSMI_STR SHOW_STR "Show information about finite state machine instances\n"
@@ -196,9 +212,9 @@ void osmo_fsm_vty_add_cmds(void)
if (osmo_fsm_vty_cmds_installed)
return;
- install_element_ve(&show_fsm_cmd);
- install_element_ve(&show_fsms_cmd);
- install_element_ve(&show_fsm_inst_cmd);
- install_element_ve(&show_fsm_insts_cmd);
+ install_lib_element_ve(&show_fsm_cmd);
+ install_lib_element_ve(&show_fsms_cmd);
+ install_lib_element_ve(&show_fsm_inst_cmd);
+ install_lib_element_ve(&show_fsm_insts_cmd);
osmo_fsm_vty_cmds_installed = true;
}
diff --git a/src/vty/logging_vty.c b/src/vty/logging_vty.c
index 0e1782a4..2a074222 100644
--- a/src/vty/logging_vty.c
+++ b/src/vty/logging_vty.c
@@ -15,16 +15,12 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <stdlib.h>
#include <string.h>
-#include "../../config.h"
+#include "config.h"
#include <osmocom/core/talloc.h>
#include <osmocom/core/logging.h>
@@ -33,6 +29,7 @@
#include <osmocom/core/strrb.h>
#include <osmocom/core/loggingrb.h>
#include <osmocom/core/gsmtap.h>
+#include <osmocom/core/application.h>
#include <osmocom/vty/command.h>
#include <osmocom/vty/buffer.h>
@@ -130,7 +127,7 @@ DEFUN(enable_logging,
conn = (struct telnet_connection *) vty->priv;
if (conn->dbg) {
- vty_out(vty, "Logging already enabled.%s", VTY_NEWLINE);
+ vty_out(vty, "%% Logging already enabled.%s", VTY_NEWLINE);
return CMD_WARNING;
}
@@ -157,7 +154,7 @@ struct log_target *osmo_log_vty2tgt(struct vty *vty)
conn = (struct telnet_connection *) vty->priv;
if (!conn->dbg)
- vty_out(vty, "Logging was not enabled.%s", VTY_NEWLINE);
+ vty_out(vty, "%% Logging was not enabled.%s", VTY_NEWLINE);
return conn->dbg;
}
@@ -224,6 +221,22 @@ DEFUN(logging_prnt_ext_timestamp,
RET_WITH_UNLOCK(CMD_SUCCESS);
}
+DEFUN(logging_prnt_tid,
+ logging_prnt_tid_cmd,
+ "logging print thread-id (0|1)",
+ LOGGING_STR "Log output settings\n"
+ "Configure log message logging Thread ID\n"
+ "Don't prefix each log message\n"
+ "Prefix each log message with current Thread ID\n")
+{
+ struct log_target *tgt;
+
+ ACQUIRE_VTY_LOG_TGT_WITH_LOCK(vty, tgt);
+
+ log_set_print_tid(tgt, atoi(argv[0]));
+ RET_WITH_UNLOCK(CMD_SUCCESS);
+}
+
DEFUN(logging_prnt_cat,
logging_prnt_cat_cmd,
"logging print category (0|1)",
@@ -355,12 +368,12 @@ DEFUN(logging_level,
int level = log_parse_level(argv[1]);
if (level < 0) {
- vty_out(vty, "Invalid level `%s'%s", argv[1], VTY_NEWLINE);
+ vty_out(vty, "%% Invalid level '%s'%s", argv[1], VTY_NEWLINE);
return CMD_WARNING;
}
if (category < 0) {
- vty_out(vty, "Invalid category `%s'%s", argv[0], VTY_NEWLINE);
+ vty_out(vty, "%% Invalid category '%s'%s", argv[0], VTY_NEWLINE);
return CMD_WARNING;
}
@@ -578,7 +591,7 @@ gDEFUN(cfg_description, cfg_description_cmd,
char **dptr = vty->index_sub;
if (!dptr) {
- vty_out(vty, "vty->index_sub == NULL%s", VTY_NEWLINE);
+ vty_out(vty, "%% vty->index_sub == NULL%s", VTY_NEWLINE);
return CMD_WARNING;
}
@@ -599,7 +612,7 @@ gDEFUN(cfg_no_description, cfg_no_description_cmd,
char **dptr = vty->index_sub;
if (!dptr) {
- vty_out(vty, "vty->index_sub == NULL%s", VTY_NEWLINE);
+ vty_out(vty, "%% vty->index_sub == NULL%s", VTY_NEWLINE);
return CMD_WARNING;
}
@@ -731,6 +744,64 @@ DEFUN(cfg_no_log_syslog, cfg_no_log_syslog_cmd,
}
#endif /* HAVE_SYSLOG_H */
+DEFUN(cfg_log_systemd_journal, cfg_log_systemd_journal_cmd,
+ "log systemd-journal [raw]",
+ LOG_STR "Logging to systemd-journal\n"
+ "Offload rendering of the meta information (location, category) to systemd\n")
+{
+#ifdef ENABLE_SYSTEMD_LOGGING
+ struct log_target *tgt;
+ bool raw = argc > 0;
+
+ log_tgt_mutex_lock();
+ tgt = log_target_find(LOG_TGT_TYPE_SYSTEMD, NULL);
+ if (tgt == NULL) {
+ tgt = log_target_create_systemd(raw);
+ if (tgt == NULL) {
+ vty_out(vty, "%% Unable to create systemd-journal "
+ "log target%s", VTY_NEWLINE);
+ RET_WITH_UNLOCK(CMD_WARNING);
+ }
+ log_add_target(tgt);
+ } else if (tgt->sd_journal.raw != raw) {
+ log_target_systemd_set_raw(tgt, raw);
+ }
+
+ vty->index = tgt;
+ vty->node = CFG_LOG_NODE;
+
+ RET_WITH_UNLOCK(CMD_SUCCESS);
+#else
+ vty_out(vty, "%% systemd-journal logging is not available "
+ "in this build of libosmocore%s", VTY_NEWLINE);
+ return CMD_WARNING;
+#endif /* ENABLE_SYSTEMD_LOGGING */
+}
+
+DEFUN(cfg_no_log_systemd_journal, cfg_no_log_systemd_journal_cmd,
+ "no log systemd-journal",
+ NO_STR LOG_STR "Logging to systemd-journal\n")
+{
+#ifdef ENABLE_SYSTEMD_LOGGING
+ struct log_target *tgt;
+
+ log_tgt_mutex_lock();
+ tgt = log_target_find(LOG_TGT_TYPE_SYSTEMD, NULL);
+ if (!tgt) {
+ vty_out(vty, "%% No systemd-journal logging active%s", VTY_NEWLINE);
+ RET_WITH_UNLOCK(CMD_WARNING);
+ }
+
+ log_target_destroy(tgt);
+
+ RET_WITH_UNLOCK(CMD_SUCCESS);
+#else
+ vty_out(vty, "%% systemd-journal logging is not available "
+ "in this build of libosmocore%s", VTY_NEWLINE);
+ return CMD_WARNING;
+#endif /* ENABLE_SYSTEMD_LOGGING */
+}
+
DEFUN(cfg_log_gsmtap, cfg_log_gsmtap_cmd,
"log gsmtap [HOSTNAME]",
LOG_STR "Logging via GSMTAP\n"
@@ -759,9 +830,31 @@ DEFUN(cfg_log_gsmtap, cfg_log_gsmtap_cmd,
RET_WITH_UNLOCK(CMD_SUCCESS);
}
+DEFUN(cfg_no_log_gsmtap, cfg_no_log_gsmtap_cmd,
+ "no log gsmtap [HOSTNAME]",
+ NO_STR LOG_STR "Logging via GSMTAP\n"
+ "Host name to send the GSMTAP logging to (UDP port 4729)\n")
+{
+ const char *hostname = argc ? argv[0] : "127.0.0.1";
+ struct log_target *tgt;
+
+ log_tgt_mutex_lock();
+ tgt = log_target_find(LOG_TGT_TYPE_GSMTAP, hostname);
+ if (tgt == NULL) {
+ vty_out(vty, "%% Unable to find GSMTAP log target for %s%s",
+ hostname, VTY_NEWLINE);
+ RET_WITH_UNLOCK(CMD_WARNING);
+ }
+
+ log_target_destroy(tgt);
+
+ RET_WITH_UNLOCK(CMD_SUCCESS);
+}
+
DEFUN(cfg_log_stderr, cfg_log_stderr_cmd,
- "log stderr",
- LOG_STR "Logging via STDERR of the process\n")
+ "log stderr [blocking-io]",
+ LOG_STR "Logging via STDERR of the process\n"
+ "Use blocking, synchronous I/O\n")
{
struct log_target *tgt;
@@ -777,6 +870,11 @@ DEFUN(cfg_log_stderr, cfg_log_stderr_cmd,
log_add_target(tgt);
}
+ if (argc > 0 && !strcmp(argv[0], "blocking-io"))
+ log_target_file_switch_to_stream(tgt);
+ else
+ log_target_file_switch_to_wqueue(tgt);
+
vty->index = tgt;
vty->node = CFG_LOG_NODE;
@@ -797,13 +895,15 @@ DEFUN(cfg_no_log_stderr, cfg_no_log_stderr_cmd,
}
log_target_destroy(tgt);
+ osmo_stderr_target = NULL;
RET_WITH_UNLOCK(CMD_SUCCESS);
}
DEFUN(cfg_log_file, cfg_log_file_cmd,
- "log file .FILENAME",
- LOG_STR "Logging to text file\n" "Filename\n")
+ "log file FILENAME [blocking-io]",
+ LOG_STR "Logging to text file\n" "Filename\n"
+ "Use blocking, synchronous I/O\n")
{
const char *fname = argv[0];
struct log_target *tgt;
@@ -813,13 +913,18 @@ DEFUN(cfg_log_file, cfg_log_file_cmd,
if (!tgt) {
tgt = log_target_create_file(fname);
if (!tgt) {
- vty_out(vty, "%% Unable to create file `%s'%s",
+ vty_out(vty, "%% Unable to create file '%s'%s",
fname, VTY_NEWLINE);
RET_WITH_UNLOCK(CMD_WARNING);
}
log_add_target(tgt);
}
+ if (argc > 1 && !strcmp(argv[1], "blocking-io"))
+ log_target_file_switch_to_stream(tgt);
+ else
+ log_target_file_switch_to_wqueue(tgt);
+
vty->index = tgt;
vty->node = CFG_LOG_NODE;
@@ -828,7 +933,7 @@ DEFUN(cfg_log_file, cfg_log_file_cmd,
DEFUN(cfg_no_log_file, cfg_no_log_file_cmd,
- "no log file .FILENAME",
+ "no log file FILENAME",
NO_STR LOG_STR "Logging to text file\n" "Filename\n")
{
const char *fname = argv[0];
@@ -837,7 +942,7 @@ DEFUN(cfg_no_log_file, cfg_no_log_file_cmd,
log_tgt_mutex_lock();
tgt = log_target_find(LOG_TGT_TYPE_FILE, fname);
if (!tgt) {
- vty_out(vty, "%% No such log file `%s'%s",
+ vty_out(vty, "%% No such log file '%s'%s",
fname, VTY_NEWLINE);
RET_WITH_UNLOCK(CMD_WARNING);
}
@@ -903,7 +1008,10 @@ static int config_write_log_single(struct vty *vty, struct log_target *tgt)
return 1;
break;
case LOG_TGT_TYPE_STDERR:
- vty_out(vty, "log stderr%s", VTY_NEWLINE);
+ if (tgt->tgt_file.wqueue)
+ vty_out(vty, "log stderr%s", VTY_NEWLINE);
+ else
+ vty_out(vty, "log stderr blocking-io%s", VTY_NEWLINE);
break;
case LOG_TGT_TYPE_SYSLOG:
#ifdef HAVE_SYSLOG_H
@@ -914,7 +1022,10 @@ static int config_write_log_single(struct vty *vty, struct log_target *tgt)
#endif
break;
case LOG_TGT_TYPE_FILE:
- vty_out(vty, "log file %s%s", tgt->tgt_file.fname, VTY_NEWLINE);
+ if (tgt->tgt_file.wqueue)
+ vty_out(vty, "log file %s%s", tgt->tgt_file.fname, VTY_NEWLINE);
+ else
+ vty_out(vty, "log file %s blocking-io%s", tgt->tgt_file.fname, VTY_NEWLINE);
break;
case LOG_TGT_TYPE_STRRB:
vty_out(vty, "log alarms %zu%s",
@@ -924,6 +1035,11 @@ static int config_write_log_single(struct vty *vty, struct log_target *tgt)
vty_out(vty, "log gsmtap %s%s",
tgt->tgt_gsmtap.hostname, VTY_NEWLINE);
break;
+ case LOG_TGT_TYPE_SYSTEMD:
+ vty_out(vty, "log systemd-journal%s%s",
+ tgt->sd_journal.raw ? " raw" : "",
+ VTY_NEWLINE);
+ break;
}
vty_out(vty, " logging filter all %u%s",
@@ -938,6 +1054,8 @@ static int config_write_log_single(struct vty *vty, struct log_target *tgt)
tgt->print_category_hex ? 1 : 0, VTY_NEWLINE);
vty_out(vty, " logging print category %d%s",
tgt->print_category ? 1 : 0, VTY_NEWLINE);
+ vty_out(vty, " logging print thread-id %d%s",
+ tgt->print_tid ? 1 : 0, VTY_NEWLINE);
if (tgt->print_ext_timestamp)
vty_out(vty, " logging print extended-timestamp 1%s", VTY_NEWLINE);
else
@@ -945,8 +1063,9 @@ static int config_write_log_single(struct vty *vty, struct log_target *tgt)
tgt->print_timestamp ? 1 : 0, VTY_NEWLINE);
if (tgt->print_level)
vty_out(vty, " logging print level 1%s", VTY_NEWLINE);
- vty_out(vty, " logging print file %s%s",
+ vty_out(vty, " logging print file %s%s%s",
get_value_string(logging_print_file_args, tgt->print_filename2),
+ tgt->print_filename_pos == LOG_FILENAME_POS_LINE_END ? " last" : "",
VTY_NEWLINE);
if (tgt->loglevel) {
@@ -1014,7 +1133,7 @@ void logging_vty_add_deprecated_subsys(void *ctx, const char *name)
"Deprecated Category\n";
cmd->attr = CMD_ATTR_DEPRECATED;
- install_element(CFG_LOG_NODE, cmd);
+ install_lib_element(CFG_LOG_NODE, cmd);
}
/* logp (<categories>) (debug|...|fatal) .LOGMESSAGE*/
@@ -1026,6 +1145,23 @@ DEFUN(vty_logp,
int category = log_parse_category(argv[0]);
int level = log_parse_level(argv[1]);
char *str = argv_concat(argv, argc, 2);
+
+ if (level < 0) {
+ vty_out(vty, "%% Invalid level '%s'%s", argv[1], VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (category < 0) {
+ vty_out(vty, "%% Invalid category '%s'%s", argv[0], VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ /* Properly handle library specific sub-systems */
+ if ((unsigned int) category >= osmo_log_info->num_cat_user) {
+ category -= osmo_log_info->num_cat_user - 1;
+ category *= -1;
+ }
+
LOGP(category, level, "%s\n", str);
return CMD_SUCCESS;
}
@@ -1060,20 +1196,21 @@ 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_element_ve(&enable_logging_cmd);
- install_element_ve(&disable_logging_cmd);
- install_element_ve(&logging_fltr_all_cmd);
- install_element_ve(&logging_use_clr_cmd);
- install_element_ve(&logging_prnt_timestamp_cmd);
- install_element_ve(&logging_prnt_ext_timestamp_cmd);
- install_element_ve(&logging_prnt_cat_cmd);
- install_element_ve(&logging_prnt_cat_hex_cmd);
- install_element_ve(&logging_prnt_level_cmd);
- install_element_ve(&logging_prnt_file_cmd);
- install_element_ve(&logging_set_category_mask_cmd);
- install_element_ve(&logging_set_category_mask_old_cmd);
+ install_lib_element_ve(&enable_logging_cmd);
+ install_lib_element_ve(&disable_logging_cmd);
+ install_lib_element_ve(&logging_fltr_all_cmd);
+ install_lib_element_ve(&logging_use_clr_cmd);
+ install_lib_element_ve(&logging_prnt_timestamp_cmd);
+ install_lib_element_ve(&logging_prnt_ext_timestamp_cmd);
+ install_lib_element_ve(&logging_prnt_tid_cmd);
+ install_lib_element_ve(&logging_prnt_cat_cmd);
+ install_lib_element_ve(&logging_prnt_cat_hex_cmd);
+ install_lib_element_ve(&logging_prnt_level_cmd);
+ install_lib_element_ve(&logging_prnt_file_cmd);
+ install_lib_element_ve(&logging_set_category_mask_cmd);
+ install_lib_element_ve(&logging_set_category_mask_old_cmd);
/* logging level (<categories>) (debug|...|fatal) */
gen_logging_level_cmd_strs(&logging_level_cmd,
@@ -1083,47 +1220,51 @@ void logging_vty_add_cmds()
gen_logging_level_cmd_strs(&deprecated_logging_level_everything_cmd,
"everything", EVERYTHING_STR);
- install_element_ve(&logging_level_cmd);
- install_element_ve(&logging_level_set_all_cmd);
- install_element_ve(&logging_level_force_all_cmd);
- install_element_ve(&no_logging_level_force_all_cmd);
- install_element_ve(&deprecated_logging_level_everything_cmd);
- install_element_ve(&deprecated_logging_level_all_cmd);
- install_element_ve(&deprecated_logging_level_all_everything_cmd);
+ install_lib_element_ve(&logging_level_cmd);
+ install_lib_element_ve(&logging_level_set_all_cmd);
+ install_lib_element_ve(&logging_level_force_all_cmd);
+ install_lib_element_ve(&no_logging_level_force_all_cmd);
+ install_lib_element_ve(&deprecated_logging_level_everything_cmd);
+ install_lib_element_ve(&deprecated_logging_level_all_cmd);
+ install_lib_element_ve(&deprecated_logging_level_all_everything_cmd);
gen_vty_logp_cmd_strs(&vty_logp_cmd);
- install_element_ve(&vty_logp_cmd);
+ install_lib_element_ve(&vty_logp_cmd);
- install_element_ve(&show_logging_vty_cmd);
- install_element_ve(&show_alarms_cmd);
+ install_lib_element_ve(&show_logging_vty_cmd);
+ install_lib_element_ve(&show_alarms_cmd);
install_node(&cfg_log_node, config_write_log);
- install_element(CFG_LOG_NODE, &logging_fltr_all_cmd);
- install_element(CFG_LOG_NODE, &logging_use_clr_cmd);
- install_element(CFG_LOG_NODE, &logging_prnt_timestamp_cmd);
- install_element(CFG_LOG_NODE, &logging_prnt_ext_timestamp_cmd);
- install_element(CFG_LOG_NODE, &logging_prnt_cat_cmd);
- install_element(CFG_LOG_NODE, &logging_prnt_cat_hex_cmd);
- install_element(CFG_LOG_NODE, &logging_prnt_level_cmd);
- install_element(CFG_LOG_NODE, &logging_prnt_file_cmd);
- install_element(CFG_LOG_NODE, &logging_level_cmd);
- install_element(CFG_LOG_NODE, &logging_level_set_all_cmd);
- install_element(CFG_LOG_NODE, &logging_level_force_all_cmd);
- install_element(CFG_LOG_NODE, &no_logging_level_force_all_cmd);
- install_element(CFG_LOG_NODE, &deprecated_logging_level_everything_cmd);
- install_element(CFG_LOG_NODE, &deprecated_logging_level_all_cmd);
- install_element(CFG_LOG_NODE, &deprecated_logging_level_all_everything_cmd);
-
- install_element(CONFIG_NODE, &cfg_log_stderr_cmd);
- install_element(CONFIG_NODE, &cfg_no_log_stderr_cmd);
- install_element(CONFIG_NODE, &cfg_log_file_cmd);
- install_element(CONFIG_NODE, &cfg_no_log_file_cmd);
- install_element(CONFIG_NODE, &cfg_log_alarms_cmd);
- install_element(CONFIG_NODE, &cfg_no_log_alarms_cmd);
+ install_lib_element(CFG_LOG_NODE, &logging_fltr_all_cmd);
+ install_lib_element(CFG_LOG_NODE, &logging_use_clr_cmd);
+ install_lib_element(CFG_LOG_NODE, &logging_prnt_timestamp_cmd);
+ install_lib_element(CFG_LOG_NODE, &logging_prnt_ext_timestamp_cmd);
+ install_lib_element(CFG_LOG_NODE, &logging_prnt_tid_cmd);
+ install_lib_element(CFG_LOG_NODE, &logging_prnt_cat_cmd);
+ install_lib_element(CFG_LOG_NODE, &logging_prnt_cat_hex_cmd);
+ install_lib_element(CFG_LOG_NODE, &logging_prnt_level_cmd);
+ install_lib_element(CFG_LOG_NODE, &logging_prnt_file_cmd);
+ install_lib_element(CFG_LOG_NODE, &logging_level_cmd);
+ install_lib_element(CFG_LOG_NODE, &logging_level_set_all_cmd);
+ install_lib_element(CFG_LOG_NODE, &logging_level_force_all_cmd);
+ install_lib_element(CFG_LOG_NODE, &no_logging_level_force_all_cmd);
+ install_lib_element(CFG_LOG_NODE, &deprecated_logging_level_everything_cmd);
+ install_lib_element(CFG_LOG_NODE, &deprecated_logging_level_all_cmd);
+ install_lib_element(CFG_LOG_NODE, &deprecated_logging_level_all_everything_cmd);
+
+ install_lib_element(CONFIG_NODE, &cfg_log_stderr_cmd);
+ install_lib_element(CONFIG_NODE, &cfg_no_log_stderr_cmd);
+ install_lib_element(CONFIG_NODE, &cfg_log_file_cmd);
+ install_lib_element(CONFIG_NODE, &cfg_no_log_file_cmd);
+ install_lib_element(CONFIG_NODE, &cfg_log_alarms_cmd);
+ install_lib_element(CONFIG_NODE, &cfg_no_log_alarms_cmd);
#ifdef HAVE_SYSLOG_H
- install_element(CONFIG_NODE, &cfg_log_syslog_cmd);
- install_element(CONFIG_NODE, &cfg_log_syslog_local_cmd);
- install_element(CONFIG_NODE, &cfg_no_log_syslog_cmd);
+ install_lib_element(CONFIG_NODE, &cfg_log_syslog_cmd);
+ install_lib_element(CONFIG_NODE, &cfg_log_syslog_local_cmd);
+ install_lib_element(CONFIG_NODE, &cfg_no_log_syslog_cmd);
#endif
- install_element(CONFIG_NODE, &cfg_log_gsmtap_cmd);
+ install_lib_element(CONFIG_NODE, &cfg_log_systemd_journal_cmd);
+ install_lib_element(CONFIG_NODE, &cfg_no_log_systemd_journal_cmd);
+ install_lib_element(CONFIG_NODE, &cfg_log_gsmtap_cmd);
+ install_lib_element(CONFIG_NODE, &cfg_no_log_gsmtap_cmd);
}
diff --git a/src/vty/stats_vty.c b/src/vty/stats_vty.c
index 1483eaa5..f940018f 100644
--- a/src/vty/stats_vty.c
+++ b/src/vty/stats_vty.c
@@ -1,5 +1,5 @@
/*
- * (C) 2009-2010 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2009-2022 by Harald Welte <laforge@gnumonks.org>
* (C) 2009-2014 by Holger Hans Peter Freyther
* (C) 2015 by sysmocom - s.f.m.c. GmbH
* All Rights Reserved
@@ -16,16 +16,12 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <stdlib.h>
#include <string.h>
-#include "../../config.h"
+#include "config.h"
#include <osmocom/vty/command.h>
#include <osmocom/vty/buffer.h>
@@ -37,11 +33,13 @@
#include <osmocom/core/stats.h>
#include <osmocom/core/counter.h>
#include <osmocom/core/rate_ctr.h>
+#include <osmocom/core/stats_tcp.h>
#define CFG_STATS_STR "Configure stats sub-system\n"
#define CFG_REPORTER_STR "Configure a stats reporter\n"
#define SHOW_STATS_STR "Show statistical values\n"
+#define SKIP_ZERO_STR "Skip items with total count zero\n"
#define STATS_STR "Stats related commands\n"
@@ -269,14 +267,20 @@ DEFUN(cfg_stats_reporter_flush_period, cfg_stats_reporter_flush_period_cmd,
}
DEFUN(cfg_stats_reporter_statsd, cfg_stats_reporter_statsd_cmd,
- "stats reporter statsd",
- CFG_STATS_STR CFG_REPORTER_STR "Report to a STATSD server\n")
+ "stats reporter statsd [NAME]",
+ CFG_STATS_STR CFG_REPORTER_STR
+ "Report to a STATSD server\n"
+ "Name of the reporter\n")
{
struct osmo_stats_reporter *srep;
+ const char *name = NULL;
+
+ if (argc > 0)
+ name = argv[0];
- srep = osmo_stats_reporter_find(OSMO_STATS_REPORTER_STATSD, NULL);
+ srep = osmo_stats_reporter_find(OSMO_STATS_REPORTER_STATSD, name);
if (!srep) {
- srep = osmo_stats_reporter_create_statsd(NULL);
+ srep = osmo_stats_reporter_create_statsd(name);
if (!srep) {
vty_out(vty, "%% Unable to create statsd reporter%s",
VTY_NEWLINE);
@@ -293,15 +297,21 @@ DEFUN(cfg_stats_reporter_statsd, cfg_stats_reporter_statsd_cmd,
}
DEFUN(cfg_no_stats_reporter_statsd, cfg_no_stats_reporter_statsd_cmd,
- "no stats reporter statsd",
- NO_STR CFG_STATS_STR CFG_REPORTER_STR "Report to a STATSD server\n")
+ "no stats reporter statsd [NAME]",
+ NO_STR CFG_STATS_STR CFG_REPORTER_STR
+ "Report to a STATSD server\n"
+ "Name of the reporter\n")
{
struct osmo_stats_reporter *srep;
+ const char *name = NULL;
- srep = osmo_stats_reporter_find(OSMO_STATS_REPORTER_STATSD, NULL);
+ if (argc > 0)
+ name = argv[0];
+
+ srep = osmo_stats_reporter_find(OSMO_STATS_REPORTER_STATSD, name);
if (!srep) {
- vty_out(vty, "%% No statsd logging active%s",
- VTY_NEWLINE);
+ vty_out(vty, "%% There is no such statsd reporter with name '%s'%s",
+ name ? name : "", VTY_NEWLINE);
return CMD_WARNING;
}
@@ -311,14 +321,20 @@ DEFUN(cfg_no_stats_reporter_statsd, cfg_no_stats_reporter_statsd_cmd,
}
DEFUN(cfg_stats_reporter_log, cfg_stats_reporter_log_cmd,
- "stats reporter log",
- CFG_STATS_STR CFG_REPORTER_STR "Report to the logger\n")
+ "stats reporter log [NAME]",
+ CFG_STATS_STR CFG_REPORTER_STR
+ "Report to the logger\n"
+ "Name of the reporter\n")
{
struct osmo_stats_reporter *srep;
+ const char *name = NULL;
- srep = osmo_stats_reporter_find(OSMO_STATS_REPORTER_LOG, NULL);
+ if (argc > 0)
+ name = argv[0];
+
+ srep = osmo_stats_reporter_find(OSMO_STATS_REPORTER_LOG, name);
if (!srep) {
- srep = osmo_stats_reporter_create_log(NULL);
+ srep = osmo_stats_reporter_create_log(name);
if (!srep) {
vty_out(vty, "%% Unable to create log reporter%s",
VTY_NEWLINE);
@@ -335,15 +351,21 @@ DEFUN(cfg_stats_reporter_log, cfg_stats_reporter_log_cmd,
}
DEFUN(cfg_no_stats_reporter_log, cfg_no_stats_reporter_log_cmd,
- "no stats reporter log",
- NO_STR CFG_STATS_STR CFG_REPORTER_STR "Report to the logger\n")
+ "no stats reporter log [NAME]",
+ NO_STR CFG_STATS_STR CFG_REPORTER_STR
+ "Report to the logger\n"
+ "Name of the reporter\n")
{
struct osmo_stats_reporter *srep;
+ const char *name = NULL;
+
+ if (argc > 0)
+ name = argv[0];
- srep = osmo_stats_reporter_find(OSMO_STATS_REPORTER_LOG, NULL);
+ srep = osmo_stats_reporter_find(OSMO_STATS_REPORTER_LOG, name);
if (!srep) {
- vty_out(vty, "%% No log reporting active%s",
- VTY_NEWLINE);
+ vty_out(vty, "%% There is no such log reporter with name '%s'%s",
+ name ? name : "", VTY_NEWLINE);
return CMD_WARNING;
}
@@ -369,27 +391,60 @@ DEFUN(cfg_stats_interval, cfg_stats_interval_cmd,
return CMD_SUCCESS;
}
+DEFUN(cfg_tcp_stats_interval, cfg_tcp_stats_interval_cmd,
+ "stats-tcp interval <0-65535>",
+ CFG_STATS_STR "Set the tcp socket stats polling interval\n"
+ "Interval in seconds (0 disables the polling interval)\n")
+{
+ int rc;
+ int interval = atoi(argv[0]);
+ rc = osmo_stats_tcp_set_interval(interval);
+ if (rc < 0) {
+ vty_out(vty, "%% Unable to set interval: %s%s",
+ strerror(-rc), VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_tcp_stats_batch_size, cfg_tcp_stats_batch_size_cmd,
+ "stats-tcp batch-size <1-65535>",
+ CFG_STATS_STR "Set the number of tcp sockets that are processed per stats polling interval\n"
+ "Number of sockets per interval\n")
+{
+ osmo_tcp_stats_config->batch_size = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
DEFUN(show_stats,
show_stats_cmd,
- "show stats",
- SHOW_STR SHOW_STATS_STR)
+ "show stats [skip-zero]",
+ SHOW_STR SHOW_STATS_STR SKIP_ZERO_STR)
{
- vty_out_statistics_full(vty, "");
+ bool skip_zero = false;
+ if (argc > 0)
+ skip_zero = true;
+
+ vty_out_statistics_full2(vty, "", skip_zero);
return CMD_SUCCESS;
}
DEFUN(show_stats_level,
show_stats_level_cmd,
- "show stats level (global|peer|subscriber)",
+ "show stats level (global|peer|subscriber) [skip-zero]",
SHOW_STR SHOW_STATS_STR
"Set the maximum group level\n"
"Show global groups only\n"
"Show global and network peer related groups\n"
- "Show global, peer, and subscriber groups\n")
+ "Show global, peer, and subscriber groups\n" SKIP_ZERO_STR)
{
int level = get_string_value(stats_class_strs, argv[0]);
- vty_out_statistics_partial(vty, "", level);
+ bool skip_zero = false;
+ if (argc > 1)
+ skip_zero = true;
+ vty_out_statistics_partial2(vty, "", level, skip_zero);
return CMD_SUCCESS;
}
@@ -415,13 +470,6 @@ static int asciidoc_handle_counter(struct osmo_counter *counter, void *sctx_)
static void asciidoc_counter_generate(struct vty *vty)
{
- if (osmo_counters_count() == 0)
- {
- vty_out(vty, "// there are no ungrouped osmo_counters%s",
- VTY_NEWLINE);
- return;
- }
-
vty_out(vty, "// ungrouped osmo_counters%s", VTY_NEWLINE);
vty_out(vty, ".ungrouped osmo counters%s", VTY_NEWLINE);
vty_out(vty, "[options=\"header\"]%s", VTY_NEWLINE);
@@ -482,10 +530,11 @@ static int asciidoc_osmo_stat_item_handler(
{
struct vty *vty = sctx_;
- char *name = osmo_asciidoc_escape(item->desc->name);
- char *description = osmo_asciidoc_escape(item->desc->description);
+ const struct osmo_stat_item_desc *desc = osmo_stat_item_get_desc(item);
+ char *name = osmo_asciidoc_escape(desc->name);
+ char *description = osmo_asciidoc_escape(desc->description);
char *group_name_prefix = osmo_asciidoc_escape(statg->desc->group_name_prefix);
- char *unit = osmo_asciidoc_escape(item->desc->unit);
+ char *unit = osmo_asciidoc_escape(desc->unit);
/* | Name | Reference | Description | Unit | */
vty_out(vty, "| %s | <<%s_%s>> | %s | %s%s",
@@ -542,30 +591,48 @@ DEFUN(show_stats_asciidoc_table,
vty_out(vty, "// generating tables for rate_ctr_group%s", VTY_NEWLINE);
rate_ctr_for_each_group(asciidoc_rate_ctr_group_handler, vty);
- vty_out(vty, "== Osmo Stat Items%s%s", VTY_NEWLINE, VTY_NEWLINE);
+ vty_out(vty, "=== Osmo Stat Items%s%s", VTY_NEWLINE, VTY_NEWLINE);
vty_out(vty, "// generating tables for osmo_stat_items%s", VTY_NEWLINE);
osmo_stat_item_for_each_group(asciidoc_osmo_stat_item_group_handler, vty);
- vty_out(vty, "== Osmo Counters%s%s", VTY_NEWLINE, VTY_NEWLINE);
- vty_out(vty, "// generating tables for osmo_counters%s", VTY_NEWLINE);
- asciidoc_counter_generate(vty);
+ if (osmo_counters_count() == 0)
+ {
+ vty_out(vty, "// there are no ungrouped osmo_counters%s",
+ VTY_NEWLINE);
+ } else {
+ vty_out(vty, "=== Osmo Counters%s%s", VTY_NEWLINE, VTY_NEWLINE);
+ vty_out(vty, "// generating tables for osmo_counters%s", VTY_NEWLINE);
+ asciidoc_counter_generate(vty);
+ }
return CMD_SUCCESS;
}
+struct rctr_vty_ctx {
+ struct vty *vty;
+ bool skip_zero;
+};
+
static int rate_ctr_group_handler(struct rate_ctr_group *ctrg, void *sctx_)
{
- struct vty *vty = sctx_;
- vty_out(vty, "%s %u:%s", ctrg->desc->group_description, ctrg->idx, VTY_NEWLINE);
- vty_out_rate_ctr_group_fmt(vty, "%25n: %10c (%S/s %M/m %H/h %D/d) %d", ctrg);
+ struct rctr_vty_ctx *sctx = sctx_;
+ struct vty *vty = sctx->vty;
+ vty_out(vty, "%s %u", ctrg->desc->group_description, ctrg->idx);
+ if (ctrg->name != NULL)
+ vty_out(vty, " (%s)", ctrg->name);
+ vty_out(vty, ":%s", VTY_NEWLINE);
+ vty_out_rate_ctr_group_fmt2(vty, "%25n: %10c (%S/s %M/m %H/h %D/d) %d", ctrg, sctx->skip_zero);
return 0;
}
DEFUN(show_rate_counters,
show_rate_counters_cmd,
- "show rate-counters",
- SHOW_STR "Show all rate counters\n")
+ "show rate-counters [skip-zero]",
+ SHOW_STR "Show all rate counters\n" SKIP_ZERO_STR)
{
- rate_ctr_for_each_group(rate_ctr_group_handler, vty);
+ struct rctr_vty_ctx rctx = { .vty = vty, .skip_zero = false };
+ if (argc > 0)
+ rctx.skip_zero = true;
+ rate_ctr_for_each_group(rate_ctr_group_handler, &rctx);
return CMD_SUCCESS;
}
@@ -584,37 +651,37 @@ static int reset_rate_ctr_group_handler(struct rate_ctr_group *ctrg, void *sctx_
return 0;
}
-static int reset_osmo_stat_item_group_handler(struct osmo_stat_item_group *statg, void *sctx_)
-{
- osmo_stat_item_group_reset(statg);
- return 0;
-}
-
DEFUN(stats_reset,
stats_reset_cmd,
"stats reset",
- STATS_STR "Reset all stats\n")
+ STATS_STR "Reset all rate counter stats\n")
{
rate_ctr_for_each_group(reset_rate_ctr_group_handler, NULL);
- osmo_stat_item_for_each_group(reset_osmo_stat_item_group_handler, NULL);
return CMD_SUCCESS;
}
static int config_write_stats_reporter(struct vty *vty, struct osmo_stats_reporter *srep)
{
- if (srep == NULL)
- return 0;
+ const char *type = NULL;
switch (srep->type) {
case OSMO_STATS_REPORTER_STATSD:
- vty_out(vty, "stats reporter statsd%s", VTY_NEWLINE);
+ type = "statsd";
break;
case OSMO_STATS_REPORTER_LOG:
- vty_out(vty, "stats reporter log%s", VTY_NEWLINE);
+ type = "log";
break;
+ default:
+ /* don't try to save unknown stats reporters to the VTY. Imagine some
+ * application registering a new application specific stats reporter that
+ * this VTY code knows nothing about! */
+ return 0;
}
- vty_out(vty, " disable%s", VTY_NEWLINE);
+ vty_out(vty, "stats reporter %s", type);
+ if (srep->name != NULL)
+ vty_out(vty, " %s", srep->name);
+ vty_out(vty, "%s", VTY_NEWLINE);
if (srep->have_net_config) {
if (srep->dest_addr_str)
@@ -648,6 +715,8 @@ static int config_write_stats_reporter(struct vty *vty, struct osmo_stats_report
if (srep->enabled)
vty_out(vty, " enable%s", VTY_NEWLINE);
+ else
+ vty_out(vty, " disable%s", VTY_NEWLINE);
return 1;
}
@@ -656,13 +725,15 @@ static int config_write_stats(struct vty *vty)
{
struct osmo_stats_reporter *srep;
- /* TODO: loop through all reporters */
- srep = osmo_stats_reporter_find(OSMO_STATS_REPORTER_STATSD, NULL);
- config_write_stats_reporter(vty, srep);
- srep = osmo_stats_reporter_find(OSMO_STATS_REPORTER_LOG, NULL);
- config_write_stats_reporter(vty, srep);
-
vty_out(vty, "stats interval %d%s", osmo_stats_config->interval, VTY_NEWLINE);
+ if (osmo_tcp_stats_config->interval != TCP_STATS_DEFAULT_INTERVAL)
+ vty_out(vty, "stats-tcp interval %d%s", osmo_tcp_stats_config->interval, VTY_NEWLINE);
+ if (osmo_tcp_stats_config->batch_size != TCP_STATS_DEFAULT_BATCH_SIZE)
+ vty_out(vty, "stats-tcp batch-size %d%s", osmo_tcp_stats_config->batch_size, VTY_NEWLINE);
+
+ /* Loop through all reporters */
+ llist_for_each_entry(srep, &osmo_stats_reporter_list, list)
+ config_write_stats_reporter(vty, srep);
return 1;
}
@@ -671,35 +742,37 @@ 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_element_ve(&show_stats_cmd);
- install_element_ve(&show_stats_level_cmd);
+ install_lib_element_ve(&show_stats_cmd);
+ install_lib_element_ve(&show_stats_level_cmd);
- install_element(CONFIG_NODE, &cfg_stats_reporter_statsd_cmd);
- install_element(CONFIG_NODE, &cfg_no_stats_reporter_statsd_cmd);
- install_element(CONFIG_NODE, &cfg_stats_reporter_log_cmd);
- install_element(CONFIG_NODE, &cfg_no_stats_reporter_log_cmd);
- install_element(CONFIG_NODE, &cfg_stats_interval_cmd);
+ install_lib_element(CONFIG_NODE, &cfg_stats_reporter_statsd_cmd);
+ install_lib_element(CONFIG_NODE, &cfg_no_stats_reporter_statsd_cmd);
+ install_lib_element(CONFIG_NODE, &cfg_stats_reporter_log_cmd);
+ install_lib_element(CONFIG_NODE, &cfg_no_stats_reporter_log_cmd);
+ install_lib_element(CONFIG_NODE, &cfg_stats_interval_cmd);
+ install_lib_element(CONFIG_NODE, &cfg_tcp_stats_interval_cmd);
+ install_lib_element(CONFIG_NODE, &cfg_tcp_stats_batch_size_cmd);
install_node(&cfg_stats_node, config_write_stats);
- install_element(CFG_STATS_NODE, &cfg_stats_reporter_local_ip_cmd);
- install_element(CFG_STATS_NODE, &cfg_no_stats_reporter_local_ip_cmd);
- install_element(CFG_STATS_NODE, &cfg_stats_reporter_remote_ip_cmd);
- install_element(CFG_STATS_NODE, &cfg_stats_reporter_remote_port_cmd);
- install_element(CFG_STATS_NODE, &cfg_stats_reporter_mtu_cmd);
- install_element(CFG_STATS_NODE, &cfg_no_stats_reporter_mtu_cmd);
- install_element(CFG_STATS_NODE, &cfg_stats_reporter_prefix_cmd);
- install_element(CFG_STATS_NODE, &cfg_no_stats_reporter_prefix_cmd);
- install_element(CFG_STATS_NODE, &cfg_stats_reporter_level_cmd);
- install_element(CFG_STATS_NODE, &cfg_stats_reporter_enable_cmd);
- install_element(CFG_STATS_NODE, &cfg_stats_reporter_disable_cmd);
- install_element(CFG_STATS_NODE, &cfg_stats_reporter_flush_period_cmd);
-
- install_element_ve(&show_stats_asciidoc_table_cmd);
- install_element_ve(&show_rate_counters_cmd);
-
- install_element(ENABLE_NODE, &stats_report_cmd);
- install_element(ENABLE_NODE, &stats_reset_cmd);
+ install_lib_element(CFG_STATS_NODE, &cfg_stats_reporter_local_ip_cmd);
+ install_lib_element(CFG_STATS_NODE, &cfg_no_stats_reporter_local_ip_cmd);
+ install_lib_element(CFG_STATS_NODE, &cfg_stats_reporter_remote_ip_cmd);
+ install_lib_element(CFG_STATS_NODE, &cfg_stats_reporter_remote_port_cmd);
+ install_lib_element(CFG_STATS_NODE, &cfg_stats_reporter_mtu_cmd);
+ install_lib_element(CFG_STATS_NODE, &cfg_no_stats_reporter_mtu_cmd);
+ install_lib_element(CFG_STATS_NODE, &cfg_stats_reporter_prefix_cmd);
+ install_lib_element(CFG_STATS_NODE, &cfg_no_stats_reporter_prefix_cmd);
+ install_lib_element(CFG_STATS_NODE, &cfg_stats_reporter_level_cmd);
+ install_lib_element(CFG_STATS_NODE, &cfg_stats_reporter_enable_cmd);
+ install_lib_element(CFG_STATS_NODE, &cfg_stats_reporter_disable_cmd);
+ install_lib_element(CFG_STATS_NODE, &cfg_stats_reporter_flush_period_cmd);
+
+ install_lib_element_ve(&show_stats_asciidoc_table_cmd);
+ install_lib_element_ve(&show_rate_counters_cmd);
+
+ install_lib_element(ENABLE_NODE, &stats_report_cmd);
+ install_lib_element(ENABLE_NODE, &stats_reset_cmd);
}
diff --git a/src/vty/talloc_ctx_vty.c b/src/vty/talloc_ctx_vty.c
index 16cb7635..ea8ebe70 100644
--- a/src/vty/talloc_ctx_vty.c
+++ b/src/vty/talloc_ctx_vty.c
@@ -17,17 +17,13 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <stdio.h>
#include <regex.h>
#include <string.h>
-#include <talloc.h>
+#include <osmocom/core/talloc.h>
#include <osmocom/vty/command.h>
#include <osmocom/vty/vty.h>
@@ -151,6 +147,8 @@ static void talloc_ctx_walk(const char *ctx, const char *depth,
/* Determine a context for report */
if (!strncmp(ctx, "app", 3))
talloc_ctx = host.app_info->tall_ctx;
+ else if (!strcmp(ctx, "global"))
+ talloc_ctx = OTC_GLOBAL;
else if (!strncmp(ctx, "all", 3))
talloc_ctx = NULL;
@@ -167,11 +165,12 @@ static void talloc_ctx_walk(const char *ctx, const char *depth,
}
#define BASE_CMD_STR \
- "show talloc-context (application|all) (full|brief|DEPTH)"
+ "show talloc-context (application|global|all) (full|brief|DEPTH)"
#define BASE_CMD_DESCR \
SHOW_STR "Show talloc memory hierarchy\n" \
"Application's context\n" \
+ "Global context (OTC_GLOBAL)\n" \
"All contexts, if NULL-context tracking is enabled\n" \
"Display a full talloc memory hierarchy\n" \
"Display a brief talloc memory hierarchy\n" \
@@ -247,7 +246,7 @@ DEFUN(show_talloc_ctx_tree, show_talloc_ctx_tree_cmd,
*/
void osmo_talloc_vty_add_cmds(void)
{
- install_element_ve(&show_talloc_ctx_cmd);
- install_element_ve(&show_talloc_ctx_tree_cmd);
- install_element_ve(&show_talloc_ctx_filter_cmd);
+ install_lib_element_ve(&show_talloc_ctx_cmd);
+ install_lib_element_ve(&show_talloc_ctx_tree_cmd);
+ install_lib_element_ve(&show_talloc_ctx_filter_cmd);
}
diff --git a/src/vty/tdef_vty.c b/src/vty/tdef_vty.c
index fe6d48ba..bd209ae0 100644
--- a/src/vty/tdef_vty.c
+++ b/src/vty/tdef_vty.c
@@ -50,10 +50,9 @@
*/
struct osmo_tdef *osmo_tdef_vty_parse_T_arg(struct vty *vty, struct osmo_tdef *tdefs, const char *T_str)
{
- long l;
+ int l;
int T;
struct osmo_tdef *t;
- char *endptr;
const char *T_nr_str;
int sign = 1;
@@ -77,9 +76,7 @@ struct osmo_tdef *osmo_tdef_vty_parse_T_arg(struct vty *vty, struct osmo_tdef *t
return NULL;
}
- errno = 0;
- l = strtol(T_nr_str, &endptr, 10);
- if (errno || *endptr || l > INT_MAX || l < 0) {
+ if (osmo_str_to_int(&l, T_nr_str, 10, 0, INT_MAX)) {
vty_out(vty, "%% Invalid T timer argument (should be 'T1234' or 'X1234'): '%s'%s", T_str, VTY_NEWLINE);
return NULL;
}
@@ -245,7 +242,7 @@ void osmo_tdef_vty_out_all(struct vty *vty, struct osmo_tdef *tdefs, const char
/*! Write current timer configuration arguments to the vty. Skip all entries that reflect their default value.
* The passed prefix string must contain both necessary indent and the VTY command the specific implementation is using.
- * See tdef_vty_test_config_subnode.c and tdef_vty_test_dynamic.c for examples.
+ * See tdef_vty_config_subnode_test.c and tdef_vty_dynamic_test.c for examples.
* \param[in] vty VTY context.
* \param[in] tdefs Array of timers to print, ended with a fully zero-initialized entry.
* \param[in] prefix_fmt Arbitrary string to start each line with, with variable printf like arguments.
@@ -379,8 +376,8 @@ void osmo_tdef_vty_groups_init(unsigned int parent_cfg_node, struct osmo_tdef_gr
cfg_timer_cmd.string = timer_command_string("timer", OSMO_TDEF_VTY_ARG_SET_OPTIONAL);
cfg_timer_cmd.doc = timer_doc_string("Configure or show timers\n", OSMO_TDEF_VTY_DOC_SET);
- install_element_ve(&show_timer_cmd);
- install_element(parent_cfg_node, &cfg_timer_cmd);
+ install_lib_element_ve(&show_timer_cmd);
+ install_lib_element(parent_cfg_node, &cfg_timer_cmd);
}
/*! Write the global osmo_tdef_group configuration to VTY, as previously passed to osmo_tdef_vty_groups_init().
diff --git a/src/vty/telnet_interface.c b/src/vty/telnet_interface.c
index 9aa36fe4..8fa5dbff 100644
--- a/src/vty/telnet_interface.c
+++ b/src/vty/telnet_interface.c
@@ -14,10 +14,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <sys/socket.h>
@@ -46,7 +42,7 @@
* process in order to enable interactive command-line introspection,
* interaction and configuration.
*
- * You typically call \ref telnet_init or \ref telnet_init_dynif once
+ * You typically call telnet_init_default once
* from your application code to enable this.
*/
@@ -64,26 +60,14 @@ static struct osmo_fd server_socket = {
.priv_nr = 0,
};
-/*! Initialize telnet based VTY interface listening to 127.0.0.1
- * \param[in] tall_ctx \ref talloc context
- * \param[in] priv private data to be passed to callback
- * \param[in] port TCP port number to bind to
- */
-int telnet_init(void *tall_ctx, void *priv, int port)
-{
- return telnet_init_dynif(tall_ctx, priv, "127.0.0.1", port);
-}
-
-/*! Initialize telnet based VTY interface
- * \param[in] tall_ctx \ref talloc context
- * \param[in] priv private data to be passed to callback
- * \param[in] ip IP to listen to ('::1' for localhost, '::0' for all, ...)
- * \param[in] port TCP port number to bind to
- */
-int telnet_init_dynif(void *tall_ctx, void *priv, const char *ip, int port)
+/* Helper for deprecating telnet_init_dynif(), which previously held this code */
+static int _telnet_init_dynif(void *tall_ctx, void *priv, const char *ip, int port)
{
int rc;
+ if (port < 0)
+ return -EINVAL;
+
tall_telnet_ctx = talloc_named_const(tall_ctx, 1,
"telnet_connection");
@@ -98,22 +82,45 @@ int telnet_init_dynif(void *tall_ctx, void *priv, const char *ip, int port)
if (rc < 0) {
LOGP(DLGLOBAL, LOGL_ERROR, "Cannot bind telnet at %s %d\n",
ip, port);
- return -1;
+ return rc;
}
LOGP(DLGLOBAL, LOGL_NOTICE, "Available via telnet %s %d\n", ip, port);
return 0;
}
+/*! Initialize telnet based VTY interface listening to 127.0.0.1
+ * \param[in] tall_ctx \ref talloc context
+ * \param[in] priv private data to be passed to callback
+ * \param[in] port TCP port number to bind to
+ * \deprecated use telnet_init_default() instead
+ */
+int telnet_init(void *tall_ctx, void *priv, int port)
+{
+ return _telnet_init_dynif(tall_ctx, priv, "127.0.0.1", port);
+}
+
+/*! Initialize telnet based VTY interface
+ * \param[in] tall_ctx \ref talloc context
+ * \param[in] priv private data to be passed to callback
+ * \param[in] ip IP to listen to ('::1' for localhost, '::0' for all, ...)
+ * \param[in] port TCP port number to bind to
+ * \deprecated use telnet_init_default() instead
+ */
+int telnet_init_dynif(void *tall_ctx, void *priv, const char *ip, int port)
+{
+ return _telnet_init_dynif(tall_ctx, priv, ip, port);
+}
+
/*! Initializes telnet based VTY interface using the configured bind addr/port.
* \param[in] tall_ctx \ref talloc context
* \param[in] priv private data to be passed to callback
- * \param[in] default_port TCP port number to bind to if not explicitely configured
+ * \param[in] default_port TCP port number to bind to if not explicitly configured
*/
int telnet_init_default(void *tall_ctx, void *priv, int default_port)
{
- return telnet_init_dynif(tall_ctx, priv, vty_get_bind_addr(),
- vty_get_bind_port(default_port));
+ return _telnet_init_dynif(tall_ctx, priv, vty_get_bind_addr(),
+ vty_get_bind_port(default_port));
}
@@ -256,7 +263,7 @@ void vty_event(enum event event, int sock, struct vty *vty)
}
/*! Close all telnet connections and release the telnet socket */
-void telnet_exit(void)
+void telnet_exit(void)
{
struct telnet_connection *tc, *tc2;
diff --git a/src/vty/utils.c b/src/vty/utils.c
index 0358d9bd..a6515157 100644
--- a/src/vty/utils.c
+++ b/src/vty/utils.c
@@ -18,13 +18,10 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <stdint.h>
+#include <stdbool.h>
#include <inttypes.h>
#include <string.h>
#include <ctype.h>
@@ -48,6 +45,7 @@ struct vty_out_context {
struct vty *vty;
const char *prefix;
int max_level;
+ bool skip_zero;
};
static int rate_ctr_handler(
@@ -57,6 +55,9 @@ static int rate_ctr_handler(
struct vty_out_context *vctx = vctx_;
struct vty *vty = vctx->vty;
+ if (vctx->skip_zero && ctr->current == 0)
+ return 0;
+
vty_out(vty, " %s%s: %8" PRIu64 " "
"(%" PRIu64 "/s %" PRIu64 "/m %" PRIu64 "/h %" PRIu64 "/d)%s",
vctx->prefix, desc->description, ctr->current,
@@ -73,17 +74,24 @@ static int rate_ctr_handler(
* \param[in] vty The VTY to which it should be printed
* \param[in] prefix Any additional log prefix ahead of each line
* \param[in] ctrg Rate counter group to be printed
+ * \param[in] skip_zero Skip all zero-valued counters
*/
-void vty_out_rate_ctr_group(struct vty *vty, const char *prefix,
- struct rate_ctr_group *ctrg)
+void vty_out_rate_ctr_group2(struct vty *vty, const char *prefix,
+ struct rate_ctr_group *ctrg, bool skip_zero)
{
- struct vty_out_context vctx = {vty, prefix};
+ struct vty_out_context vctx = {vty, prefix, 0, skip_zero};
vty_out(vty, "%s%s:%s", prefix, ctrg->desc->group_description, VTY_NEWLINE);
rate_ctr_for_each_counter(ctrg, rate_ctr_handler, &vctx);
}
+void vty_out_rate_ctr_group(struct vty *vty, const char *prefix,
+ struct rate_ctr_group *ctrg)
+{
+ vty_out_rate_ctr_group2(vty, prefix, ctrg, false);
+}
+
static char *
pad_append_str(char *s, const char *a, int minwidth)
{
@@ -107,7 +115,12 @@ static int rate_ctr_handler_fmt(
struct vty_out_context *vctx = vctx_;
struct vty *vty = vctx->vty;
const char *fmt = vctx->prefix;
- char *s = talloc_strdup(vty, "");
+ char *s;
+
+ if (vctx->skip_zero && ctr->current == 0)
+ return 0;
+
+ s = talloc_strdup(vty, "");
OSMO_ASSERT(s);
while (*fmt) {
@@ -209,14 +222,20 @@ static int rate_ctr_handler_fmt(
* \param[in] vty The VTY to which it should be printed
* \param[in] ctrg Rate counter group to be printed
* \param[in] fmt A format which may contain the above directives.
+ * \param[in] skip_zero Skip all zero-valued counters
*/
-void vty_out_rate_ctr_group_fmt(struct vty *vty, const char *fmt,
- struct rate_ctr_group *ctrg)
+void vty_out_rate_ctr_group_fmt2(struct vty *vty, const char *fmt,
+ struct rate_ctr_group *ctrg, bool skip_zero)
{
- struct vty_out_context vctx = {vty, fmt};
+ struct vty_out_context vctx = {vty, fmt, 0, skip_zero};
rate_ctr_for_each_counter(ctrg, rate_ctr_handler_fmt, &vctx);
}
+void vty_out_rate_ctr_group_fmt(struct vty *vty, const char *fmt,
+ struct rate_ctr_group *ctrg)
+{
+ vty_out_rate_ctr_group_fmt2(vty, fmt, ctrg, false);
+}
static int rate_ctr_group_handler(struct rate_ctr_group *ctrg, void *vctx_)
{
struct vty_out_context *vctx = vctx_;
@@ -225,12 +244,10 @@ static int rate_ctr_group_handler(struct rate_ctr_group *ctrg, void *vctx_)
if (ctrg->desc->class_id > vctx->max_level)
return 0;
- if (ctrg->idx)
- vty_out(vty, "%s%s (%d):%s", vctx->prefix,
- ctrg->desc->group_description, ctrg->idx, VTY_NEWLINE);
- else
- vty_out(vty, "%s%s:%s", vctx->prefix,
- ctrg->desc->group_description, VTY_NEWLINE);
+ vty_out(vty, "%s%s (%d)", vctx->prefix, ctrg->desc->group_description, ctrg->idx);
+ if (ctrg->name)
+ vty_out(vty, "('%s')", ctrg->name);
+ vty_out(vty, ":%s", VTY_NEWLINE);
rate_ctr_for_each_counter(ctrg, rate_ctr_handler, vctx);
@@ -249,14 +266,15 @@ static int osmo_stat_item_handler(
{
struct vty_out_context *vctx = vctx_;
struct vty *vty = vctx->vty;
- const char *unit =
- item->desc->unit != OSMO_STAT_ITEM_NO_UNIT ?
- item->desc->unit : "";
+ const struct osmo_stat_item_desc *desc = osmo_stat_item_get_desc(item);
+ int32_t value = osmo_stat_item_get_last(item);
+ const char *unit = (desc->unit != OSMO_STAT_ITEM_NO_UNIT) ? desc->unit : "";
+
+ if (vctx->skip_zero && value == 0)
+ return 0;
vty_out(vty, " %s%s: %8" PRIi32 " %s%s",
- vctx->prefix, item->desc->description,
- osmo_stat_item_get_last(item),
- unit, VTY_NEWLINE);
+ vctx->prefix, desc->description, value, unit, VTY_NEWLINE);
return 0;
}
@@ -265,17 +283,24 @@ static int osmo_stat_item_handler(
* \param[in] vty The VTY to which it should be printed
* \param[in] prefix Any additional log prefix ahead of each line
* \param[in] statg Stat item group to be printed
+ * \param[in] skip_zero Skip all zero-valued counters
*/
-void vty_out_stat_item_group(struct vty *vty, const char *prefix,
- struct osmo_stat_item_group *statg)
+void vty_out_stat_item_group2(struct vty *vty, const char *prefix,
+ struct osmo_stat_item_group *statg, bool skip_zero)
{
- struct vty_out_context vctx = {vty, prefix};
+ struct vty_out_context vctx = {vty, prefix, 0, skip_zero};
vty_out(vty, "%s%s:%s", prefix, statg->desc->group_description,
VTY_NEWLINE);
osmo_stat_item_for_each_item(statg, osmo_stat_item_handler, &vctx);
}
+void vty_out_stat_item_group(struct vty *vty, const char *prefix,
+ struct osmo_stat_item_group *statg)
+{
+ return vty_out_stat_item_group2(vty, prefix, statg, false);
+}
+
static int osmo_stat_item_group_handler(struct osmo_stat_item_group *statg, void *vctx_)
{
struct vty_out_context *vctx = vctx_;
@@ -284,13 +309,10 @@ static int osmo_stat_item_group_handler(struct osmo_stat_item_group *statg, void
if (statg->desc->class_id > vctx->max_level)
return 0;
- if (statg->idx)
- vty_out(vty, "%s%s (%d):%s", vctx->prefix,
- statg->desc->group_description, statg->idx,
- VTY_NEWLINE);
- else
- vty_out(vty, "%s%s:%s", vctx->prefix,
- statg->desc->group_description, VTY_NEWLINE);
+ vty_out(vty, "%s%s (%d)", vctx->prefix, statg->desc->group_description, statg->idx);
+ if (statg->name)
+ vty_out(vty, "('%s')", statg->name);
+ vty_out(vty, ":%s", VTY_NEWLINE);
osmo_stat_item_for_each_item(statg, osmo_stat_item_handler, vctx);
@@ -308,21 +330,22 @@ static int handle_counter(struct osmo_counter *counter, void *vctx_)
struct vty_out_context *vctx = vctx_;
struct vty *vty = vctx->vty;
const char *description = counter->description;
+ unsigned long value = osmo_counter_get(counter);
+
+ if (vctx->skip_zero && value == 0)
+ return 0;
if (!counter->description)
description = counter->name;
- vty_out(vty, " %s%s: %8lu%s",
- vctx->prefix, description,
- osmo_counter_get(counter), VTY_NEWLINE);
+ vty_out(vty, " %s%s: %8lu%s", vctx->prefix, description, value, VTY_NEWLINE);
return 0;
}
-void vty_out_statistics_partial(struct vty *vty, const char *prefix,
- int max_level)
+void vty_out_statistics_partial2(struct vty *vty, const char *prefix, int max_level, bool skip_zero)
{
- struct vty_out_context vctx = {vty, prefix, max_level};
+ struct vty_out_context vctx = {vty, prefix, max_level, skip_zero};
vty_out(vty, "%sUngrouped counters:%s", prefix, VTY_NEWLINE);
osmo_counters_for_each(handle_counter, &vctx);
@@ -330,9 +353,19 @@ void vty_out_statistics_partial(struct vty *vty, const char *prefix,
osmo_stat_item_for_each_group(osmo_stat_item_group_handler, &vctx);
}
+void vty_out_statistics_partial(struct vty *vty, const char *prefix, int max_level)
+{
+ return vty_out_statistics_partial2(vty, prefix, max_level, false);
+}
+
+void vty_out_statistics_full2(struct vty *vty, const char *prefix, bool skip_zero)
+{
+ vty_out_statistics_partial2(vty, prefix, INT_MAX, skip_zero);
+}
+
void vty_out_statistics_full(struct vty *vty, const char *prefix)
{
- vty_out_statistics_partial(vty, prefix, INT_MAX);
+ vty_out_statistics_full2(vty, prefix, false);
}
/*! Generate a VTY command string from value_string */
diff --git a/src/vty/vector.c b/src/vty/vector.c
index f9e5ec3e..34b161d8 100644
--- a/src/vty/vector.c
+++ b/src/vty/vector.c
@@ -16,11 +16,6 @@
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with GNU Zebra; see the file COPYING. If not, write to the Free
- * Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
*/
#include <stdlib.h>
diff --git a/src/vty/vty.c b/src/vty/vty.c
index ebdf9fc9..3a549b43 100644
--- a/src/vty/vty.c
+++ b/src/vty/vty.c
@@ -66,6 +66,8 @@
#include <osmocom/vty/command.h>
#include <osmocom/vty/buffer.h>
#include <osmocom/core/talloc.h>
+#include <osmocom/core/timer.h>
+#include <osmocom/core/utils.h>
#ifndef MAXPATHLEN
#define MAXPATHLEN 4096
@@ -206,6 +208,12 @@ static void vty_auth(struct vty *vty)
}
}
+void vty_flush(struct vty *vty)
+{
+ if (vty->obuf)
+ buffer_flush_all(vty->obuf, vty->fd);
+}
+
/*! Close a given vty interface. */
void vty_close(struct vty *vty)
{
@@ -331,6 +339,25 @@ int vty_out_newline(struct vty *vty)
return 0;
}
+/*! calculates the time difference of a give timespec to the current time
+ * and prints in a human readable format (days, hours, minutes, seconds).
+ */
+int vty_out_uptime(struct vty *vty, const struct timespec *starttime)
+{
+ struct timespec now;
+ struct timespec uptime;
+
+ osmo_clock_gettime(CLOCK_MONOTONIC, &now);
+ timespecsub(&now, starttime, &uptime);
+
+ int d = uptime.tv_sec / (3600 * 24);
+ int h = uptime.tv_sec / 3600 % 24;
+ int m = uptime.tv_sec / 60 % 60;
+ int s = uptime.tv_sec % 60;
+
+ return vty_out(vty, "%dd %dh %dm %ds", d, h, m, s);
+}
+
/*! return the current index of a given VTY */
void *vty_current_index(struct vty *vty)
{
@@ -458,6 +485,7 @@ static int vty_command(struct vty *vty)
static const char telnet_backward_char = 0x08;
static const char telnet_space_char = ' ';
+static const char telnet_escape_char = 0x1B;
/* Basic function to write buffer to vty. */
static void vty_write(struct vty *vty, const char *buf, size_t nbytes)
@@ -857,6 +885,19 @@ static void vty_down_level(struct vty *vty)
vty->cp = 0;
}
+/* When '^L' is typed, clear all lines above the current one. */
+static void vty_clear_screen(struct vty *vty)
+{
+ vty_out(vty, "%c%s%c%s",
+ telnet_escape_char,
+ "[2J", /* Erase Screen */
+ telnet_escape_char,
+ "[H" /* Cursor Home */
+ );
+ vty_prompt(vty);
+ vty_redraw_line(vty);
+}
+
/* When '^Z' is received from vty, move down to the enable mode. */
static void vty_end_config(struct vty *vty)
{
@@ -1119,7 +1160,7 @@ static void vty_describe_command(struct vty *vty)
int ret;
vector vline;
vector describe;
- unsigned int i, width, desc_width;
+ unsigned int i, cmd_width, desc_width;
struct desc *desc, *desc_cr = NULL;
vline = cmd_make_strvec(vty->buf);
@@ -1143,19 +1184,17 @@ static void vty_describe_command(struct vty *vty)
vty_prompt(vty);
vty_redraw_line(vty);
return;
- break;
case CMD_ERR_NO_MATCH:
cmd_free_strvec(vline);
vty_out(vty, "%% There is no matched command.%s", VTY_NEWLINE);
vty_prompt(vty);
vty_redraw_line(vty);
return;
- break;
}
/* Get width of command string. */
- width = 0;
- for (i = 0; i < vector_active(describe); i++)
+ cmd_width = 0;
+ for (i = 0; i < vector_active(describe); i++) {
if ((desc = vector_slot(describe, i)) != NULL) {
unsigned int len;
@@ -1166,15 +1205,16 @@ static void vty_describe_command(struct vty *vty)
if (desc->cmd[0] == '.')
len--;
- if (width < len)
- width = len;
+ if (cmd_width < len)
+ cmd_width = len;
}
+ }
/* Get width of description string. */
- desc_width = vty->width - (width + 6);
+ desc_width = vty->width - (cmd_width + 6);
/* Print out description. */
- for (i = 0; i < vector_active(describe); i++)
+ for (i = 0; i < vector_active(describe); i++) {
if ((desc = vector_slot(describe, i)) != NULL) {
if (desc->cmd[0] == '\0')
continue;
@@ -1190,19 +1230,20 @@ static void vty_describe_command(struct vty *vty)
'.' ? desc->cmd + 1 : desc->cmd,
VTY_NEWLINE);
else if (desc_width >= strlen(desc->str))
- vty_out(vty, " %-*s %s%s", width,
+ vty_out(vty, " %-*s %s%s", cmd_width,
desc->cmd[0] ==
'.' ? desc->cmd + 1 : desc->cmd,
desc->str, VTY_NEWLINE);
else
- vty_describe_fold(vty, width, desc_width, desc);
+ vty_describe_fold(vty, cmd_width, desc_width, desc);
#if 0
- vty_out(vty, " %-*s %s%s", width
+ vty_out(vty, " %-*s %s%s", cmd_width
desc->cmd[0] == '.' ? desc->cmd + 1 : desc->cmd,
desc->str ? desc->str : "", VTY_NEWLINE);
#endif /* 0 */
}
+ }
if ((desc = desc_cr)) {
if (!desc->str)
@@ -1210,11 +1251,11 @@ static void vty_describe_command(struct vty *vty)
desc->cmd[0] == '.' ? desc->cmd + 1 : desc->cmd,
VTY_NEWLINE);
else if (desc_width >= strlen(desc->str))
- vty_out(vty, " %-*s %s%s", width,
+ vty_out(vty, " %-*s %s%s", cmd_width,
desc->cmd[0] == '.' ? desc->cmd + 1 : desc->cmd,
desc->str, VTY_NEWLINE);
else
- vty_describe_fold(vty, width, desc_width, desc);
+ vty_describe_fold(vty, cmd_width, desc_width, desc);
}
cmd_free_strvec(vline);
@@ -1401,6 +1442,9 @@ int vty_read(struct vty *vty)
case CONTROL('K'):
vty_kill_line(vty);
break;
+ case CONTROL('L'):
+ vty_clear_screen(vty);
+ break;
case CONTROL('N'):
vty_next_line(vty);
break;
@@ -1461,9 +1505,13 @@ int vty_read(struct vty *vty)
return 0;
}
-/* Read up configuration file */
-static int
-vty_read_file(FILE *confp, void *priv)
+/* Read up configuration from a file stream */
+/*! Read up VTY configuration from a file stream
+ * \param[in] confp file pointer of the stream for the configuration file
+ * \param[in] priv private data to be passed to \ref vty_read_file
+ * \returns Zero on success, non-zero on error
+ */
+int vty_read_config_filep(FILE *confp, void *priv)
{
int ret;
struct vty *vty;
@@ -1794,6 +1842,8 @@ void vty_init_vtysh(void)
/* Install vty's own commands like `who' command. */
void vty_init(struct vty_app_info *app_info)
{
+ unsigned int i, j;
+
tall_vty_ctx = talloc_named_const(NULL, 0, "vty");
tall_vty_vec_ctx = talloc_named_const(tall_vty_ctx, 0, "vty_vector");
tall_vty_cmd_ctx = talloc_named_const(tall_vty_ctx, 0, "vty_command");
@@ -1802,6 +1852,36 @@ void vty_init(struct vty_app_info *app_info)
host.app_info = app_info;
+ /* Check for duplicate flags in application specific attributes (if any) */
+ for (i = 0; i < ARRAY_SIZE(app_info->usr_attr_letters); i++) {
+ if (app_info->usr_attr_letters[i] == '\0')
+ continue;
+
+ /* Some flag characters are reserved for global attributes */
+ const char rafc[] = VTY_CMD_ATTR_FLAGS_RESERVED;
+ for (j = 0; j < ARRAY_SIZE(rafc); j++) {
+ if (app_info->usr_attr_letters[i] != rafc[j])
+ continue;
+ fprintf(stderr, "Attribute flag character '%c' is reserved "
+ "for globals! Please fix.\n", app_info->usr_attr_letters[i]);
+ }
+
+ /* Upper case flag letters are reserved for libraries */
+ if (app_info->usr_attr_letters[i] >= 'A' &&
+ app_info->usr_attr_letters[i] <= 'Z') {
+ fprintf(stderr, "Attribute flag letter '%c' is reserved "
+ "for libraries! Please fix.\n", app_info->usr_attr_letters[i]);
+ }
+
+ for (j = i + 1; j < ARRAY_SIZE(app_info->usr_attr_letters); j++) {
+ if (app_info->usr_attr_letters[j] != app_info->usr_attr_letters[i])
+ continue;
+ fprintf(stderr, "Found duplicate flag letter '%c' in application "
+ "specific attributes (index %u vs %u)! Please fix.\n",
+ app_info->usr_attr_letters[i], i, j);
+ }
+ }
+
/* For further configuration read, preserve current directory. */
vty_save_cwd();
@@ -1810,18 +1890,18 @@ void vty_init(struct vty_app_info *app_info)
/* Install bgp top node. */
install_node(&vty_node, vty_config_write);
- install_element_ve(&config_who_cmd);
- install_element_ve(&show_history_cmd);
- install_element(CONFIG_NODE, &line_vty_cmd);
- install_element(CONFIG_NODE, &service_advanced_vty_cmd);
- install_element(CONFIG_NODE, &no_service_advanced_vty_cmd);
- install_element(CONFIG_NODE, &show_history_cmd);
- install_element(ENABLE_NODE, &terminal_monitor_cmd);
- install_element(ENABLE_NODE, &terminal_no_monitor_cmd);
+ install_lib_element_ve(&config_who_cmd);
+ install_lib_element_ve(&show_history_cmd);
+ install_lib_element(CONFIG_NODE, &line_vty_cmd);
+ install_lib_element(CONFIG_NODE, &service_advanced_vty_cmd);
+ install_lib_element(CONFIG_NODE, &no_service_advanced_vty_cmd);
+ install_lib_element(CONFIG_NODE, &show_history_cmd);
+ install_lib_element(ENABLE_NODE, &terminal_monitor_cmd);
+ install_lib_element(ENABLE_NODE, &terminal_no_monitor_cmd);
- install_element(VTY_NODE, &vty_login_cmd);
- install_element(VTY_NODE, &no_vty_login_cmd);
- install_element(VTY_NODE, &vty_bind_cmd);
+ install_lib_element(VTY_NODE, &vty_login_cmd);
+ install_lib_element(VTY_NODE, &no_vty_login_cmd);
+ install_lib_element(VTY_NODE, &vty_bind_cmd);
}
/*! Read the configuration file using the VTY code
@@ -1837,7 +1917,7 @@ int vty_read_config_file(const char *file_name, void *priv)
if (!cfile)
return -ENOENT;
- rc = vty_read_file(cfile, priv);
+ rc = vty_read_config_filep(cfile, priv);
fclose(cfile);
host_config_set(file_name);
diff --git a/tapset/Makefile.am b/tapset/Makefile.am
new file mode 100644
index 00000000..a07a3b1d
--- /dev/null
+++ b/tapset/Makefile.am
@@ -0,0 +1,22 @@
+.PHONY: clean-local install-data-hook uninstall-local
+
+EXTRA_DIST = libosmocore.stp
+TAPSET_FILES = $(EXTRA_DIST)
+TAPSET_INSTALL_DIR = $(DESTDIR)@ABS_TAPSET_DIR@
+
+if ENABLE_SYSTEMTAP
+all-local: $(TAPSET_FILES)
+
+clean-local:
+
+install-data-hook:
+ $(MKDIR_P) $(TAPSET_INSTALL_DIR)
+ $(INSTALL_DATA) $(TAPSET_FILES) $(TAPSET_INSTALL_DIR)
+
+uninstall-local:
+ @list='$(TAPSET_FILES)'; for p in $$list; do \
+ echo " rm -f '$(TAPSET_INSTALL_DIR)/$$p'"; \
+ rm -f "$(TAPSET_INSTALL_DIR)/$$p"; \
+ done
+
+endif
diff --git a/tapset/libosmocore.stp b/tapset/libosmocore.stp
new file mode 100644
index 00000000..a3e8f211
--- /dev/null
+++ b/tapset/libosmocore.stp
@@ -0,0 +1,29 @@
+/* libosmocore tapset
+ *
+ * This file is part of libosmocore.
+ *
+ * Each probe defines the probe name and a full probestr which consist of the probe name and between
+ * brackets all argument names and values.
+ */
+
+probe libosmocore_log_start = process("libosmocore").mark("log_start")
+{
+ count = $arg1;
+ probestr = sprintf("%s(count=%d), $$name, count);
+}
+
+probe libosmocore_log_done = process("libosmocore").mark("log_done")
+{
+ probestr = sprintf("%s", $$name);
+}
+
+probe libosmocore_stats_start = process("libosmocore").mark("statsd_start")
+{
+ count = $arg1;
+ probestr = sprintf("%s(count=%d), $$name, count);
+}
+
+probe libosmocore_stats_done = process("libosmocore").mark("statsd_done")
+{
+ probestr = sprintf("%s", $$name);
+}
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 7a0b4b1f..275cf5f1 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
@@ -9,13 +9,13 @@ LDADD += $(top_builddir)/tests/libsercomstub.a
endif
check_PROGRAMS = timer/timer_test sms/sms_test ussd/ussd_test \
- smscb/smscb_test bits/bitrev_test a5/a5_test \
+ bits/bitrev_test a5/a5_test \
conv/conv_test auth/milenage_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 \
loggingrb/loggingrb_test strrb/strrb_test \
- comp128/comp128_test smscb/gsm0341_test \
+ comp128/comp128_test \
bitvec/bitvec_test msgb/msgb_test bits/bitcomp_test \
bits/bitfield_test \
tlv/tlv_test gsup/gsup_test oap/oap_test \
@@ -28,9 +28,9 @@ check_PROGRAMS = timer/timer_test sms/sms_test ussd/ussd_test \
oap/oap_client_test gsm29205/gsm29205_test \
logging/logging_vty_test \
vty/vty_transcript_test \
- tdef/tdef_test tdef/tdef_vty_test_config_root \
- tdef/tdef_vty_test_config_subnode \
- tdef/tdef_vty_test_dynamic \
+ tdef/tdef_test tdef/tdef_vty_config_root_test \
+ tdef/tdef_vty_config_subnode_test \
+ tdef/tdef_vty_dynamic_test \
sockaddr_str/sockaddr_str_test \
use_count/use_count_test \
context/context_test \
@@ -38,6 +38,21 @@ check_PROGRAMS = timer/timer_test sms/sms_test ussd/ussd_test \
dtx/dtx_gsm0503_test \
i460_mux/i460_mux_test \
bitgen/bitgen_test \
+ gad/gad_test \
+ bsslap/bsslap_test \
+ bssmap_le/bssmap_le_test \
+ it_q/it_q_test \
+ time_cc/time_cc_test \
+ gsm48/rest_octets_test \
+ base64/base64_test \
+ iuup/iuup_test \
+ smscb/smscb_test \
+ smscb/gsm0341_test \
+ smscb/cbsp_test \
+ auth/xor2g_test \
+ v110/test_frame \
+ v110/test_ra1 \
+ gsm44021/test_frame_csd \
$(NULL)
if ENABLE_MSGFILE
@@ -67,18 +82,25 @@ endif
if !EMBEDDED
check_PROGRAMS += \
stats/stats_test \
+ stats/stats_vty_test \
exec/exec_test
endif
if ENABLE_GB
-check_PROGRAMS += gb/bssgp_fc_test gb/gprs_bssgp_test gb/gprs_ns_test fr/fr_test
+check_PROGRAMS += gb/bssgp_fc_test gb/gprs_bssgp_test gb/gprs_bssgp_rim_test gb/gprs_ns_test gb/gprs_ns2_test fr/fr_test
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
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/core
+
+stats_stats_vty_test_SOURCES = stats/stats_vty_test.c
+stats_stats_vty_test_LDADD = $(LDADD) $(top_builddir)/src/vty/libosmovty.la
a5_a5_test_SOURCES = a5/a5_test.c
a5_a5_test_LDADD = $(LDADD) $(top_builddir)/src/gsm/libgsmint.la
@@ -92,6 +114,9 @@ comp128_comp128_test_LDADD = $(LDADD) $(top_builddir)/src/gsm/libosmogsm.la
auth_milenage_test_SOURCES = auth/milenage_test.c
auth_milenage_test_LDADD = $(LDADD) $(top_builddir)/src/gsm/libosmogsm.la
+auth_xor2g_test_SOURCES = auth/xor2g_test.c
+auth_xor2g_test_LDADD = $(LDADD) $(top_builddir)/src/gsm/libosmogsm.la
+
abis_abis_test_SOURCES = abis/abis_test.c
abis_abis_test_LDADD = $(LDADD) $(top_builddir)/src/gsm/libosmogsm.la
@@ -135,11 +160,14 @@ gsm29205_gsm29205_test_LDADD = $(LDADD) $(top_builddir)/src/gsm/libosmogsm.la
gsm0408_gsm0408_test_SOURCES = gsm0408/gsm0408_test.c
gsm0408_gsm0408_test_LDADD = $(LDADD) $(top_builddir)/src/gsm/libosmogsm.la
+gsm48_rest_octets_test_SOURCES = gsm48/rest_octets_test.c
+gsm48_rest_octets_test_LDADD = $(LDADD) $(top_builddir)/src/gsm/libosmogsm.la
+
gprs_gprs_test_SOURCES = gprs/gprs_test.c
gprs_gprs_test_LDADD = $(LDADD) $(top_builddir)/src/gsm/libosmogsm.la
lapd_lapd_test_SOURCES = lapd/lapd_test.c
-lapd_lapd_test_LDADD = $(LDADD) $(top_builddir)/src/gsm/libosmogsm.la
+lapd_lapd_test_LDADD = $(LDADD) $(top_builddir)/src/gsm/libosmogsm.la $(top_builddir)/src/isdn/libosmoisdn.la
msgb_msgb_test_SOURCES = msgb/msgb_test.c
@@ -151,6 +179,9 @@ smscb_smscb_test_LDADD = $(LDADD) $(top_builddir)/src/gsm/libosmogsm.la
smscb_gsm0341_test_SOURCES = smscb/gsm0341_test.c
smscb_gsm0341_test_LDADD = $(LDADD) $(top_builddir)/src/gsm/libosmogsm.la
+smscb_cbsp_test_SOURCES = smscb/cbsp_test.c
+smscb_cbsp_test_LDADD = $(LDADD) $(top_builddir)/src/gsm/libosmogsm.la
+
sms_sms_test_SOURCES = sms/sms_test.c
sms_sms_test_LDADD = $(LDADD) $(top_builddir)/src/gsm/libosmogsm.la
@@ -169,13 +200,25 @@ gb_bssgp_fc_test_LDADD = $(LDADD) $(top_builddir)/src/gb/libosmogb.la \
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 \
- $(top_builddir)/src/gsm/libosmogsm.la
+ $(top_builddir)/src/gsm/libosmogsm.la \
+ $(top_builddir)/src/gb/libosmogb.la
+
+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_ns_test_SOURCES = gb/gprs_ns_test.c
gb_gprs_ns_test_LDADD = $(LDADD) $(top_builddir)/src/gb/libosmogb.la $(LIBRARY_DLSYM) \
$(top_builddir)/src/vty/libosmovty.la \
$(top_builddir)/src/gsm/libosmogsm.la
+gb_gprs_ns2_test_SOURCES = gb/gprs_ns2_test.c
+gb_gprs_ns2_test_LDADD = $(LDADD) $(LIBRARY_DLSYM) \
+ $(top_builddir)/src/vty/libosmovty.la \
+ $(top_builddir)/src/gsm/libosmogsm.la \
+ $(top_builddir)/src/core/libosmocore.la \
+ $(top_builddir)/src/gb/libosmogb-test.la
+
logging_logging_test_SOURCES = logging/logging_test.c
logging_logging_vty_test_SOURCES = logging/logging_vty_test.c
@@ -254,14 +297,14 @@ gsm23236_gsm23236_test_LDADD = $(LDADD) $(top_builddir)/src/gsm/libosmogsm.la
tdef_tdef_test_SOURCES = tdef/tdef_test.c
tdef_tdef_test_LDADD = $(LDADD)
-tdef_tdef_vty_test_config_root_SOURCES = tdef/tdef_vty_test_config_root.c
-tdef_tdef_vty_test_config_root_LDADD = $(LDADD) $(top_builddir)/src/vty/libosmovty.la
+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_test_config_subnode_SOURCES = tdef/tdef_vty_test_config_subnode.c
-tdef_tdef_vty_test_config_subnode_LDADD = $(LDADD) $(top_builddir)/src/vty/libosmovty.la
+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_test_dynamic_SOURCES = tdef/tdef_vty_test_dynamic.c
-tdef_tdef_vty_test_dynamic_LDADD = $(LDADD) $(top_builddir)/src/vty/libosmovty.la
+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
sockaddr_str_sockaddr_str_test_SOURCES = sockaddr_str/sockaddr_str_test.c
sockaddr_str_sockaddr_str_test_LDADD = $(LDADD)
@@ -276,11 +319,40 @@ 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 = $(LDADD) $(top_builddir)/src/isdn/libosmoisdn.la
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
+
+bsslap_bsslap_test_SOURCES = bsslap/bsslap_test.c
+bsslap_bsslap_test_LDADD = $(LDADD) $(top_builddir)/src/gsm/libosmogsm.la
+
+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
+
+it_q_it_q_test_SOURCES = it_q/it_q_test.c
+it_q_it_q_test_LDADD = $(LDADD)
+
+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
+
+v110_test_frame_SOURCES = v110/test_frame.c
+v110_test_frame_LDADD = $(LDADD) $(top_builddir)/src/isdn/libosmoisdn.la
+
+v110_test_ra1_SOURCES = v110/test_ra1.c
+v110_test_ra1_LDADD = $(LDADD) $(top_builddir)/src/isdn/libosmoisdn.la
+
+gsm44021_test_frame_csd_SOURCES = gsm44021/test_frame_csd.c
+gsm44021_test_frame_csd_LDADD = $(LDADD) $(top_builddir)/src/isdn/libosmoisdn.la \
+ $(top_builddir)/src/gsm/libosmogsm.la
+
+
# The `:;' works around a Bash 3.2 bug when the output is not writeable.
$(srcdir)/package.m4: $(top_srcdir)/configure.ac
:;{ \
@@ -301,12 +373,17 @@ $(srcdir)/package.m4: $(top_srcdir)/configure.ac
EXTRA_DIST = testsuite.at $(srcdir)/package.m4 $(TESTSUITE) \
timer/timer_test.ok sms/sms_test.ok ussd/ussd_test.ok \
- smscb/smscb_test.ok bits/bitrev_test.ok a5/a5_test.ok \
+ bits/bitrev_test.ok a5/a5_test.ok \
conv/conv_test.ok auth/milenage_test.ok ctrl/ctrl_test.ok \
- lapd/lapd_test.ok gsm0408/gsm0408_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 \
gb/bssgp_fc_tests.ok gb/bssgp_fc_tests.sh \
gb/gprs_bssgp_test.ok gb/gprs_ns_test.ok gea/gea_test.ok \
+ gb/gprs_bssgp_rim_test.ok \
+ gb/gprs_ns2_vty.vty gb/osmoappdesc.py gb/osmo-ns-dummy.cfg \
+ gb/gprs_ns2_test.ok \
gprs/gprs_test.ok kasumi/kasumi_test.ok \
msgfile/msgfile_test.ok msgfile/msgconfig.cfg \
logging/logging_test.ok logging/logging_test.err \
@@ -315,7 +392,7 @@ EXTRA_DIST = testsuite.at $(srcdir)/package.m4 $(TESTSUITE) \
loggingrb/logging_test.err strrb/strrb_test.ok \
codec/codec_test.ok \
codec/codec_ecu_fr_test.ok \
- vty/vty_test.ok \
+ vty/vty_test.ok vty/vty_test.err \
vty/fail_not_de-indented.cfg \
vty/fail_tabs_and_spaces.cfg \
vty/fail_too_much_indent.cfg \
@@ -330,7 +407,9 @@ EXTRA_DIST = testsuite.at $(srcdir)/package.m4 $(TESTSUITE) \
vty/ok_tabs.cfg \
vty/ok_deprecated_logging.cfg \
comp128/comp128_test.ok bits/bitfield_test.ok \
- utils/utils_test.ok utils/utils_test.err stats/stats_test.ok \
+ utils/utils_test.ok utils/utils_test.err \
+ stats/stats_test.ok stats/stats_test.err \
+ stats/stats_vty_test.vty \
bitvec/bitvec_test.ok msgb/msgb_test.ok bits/bitcomp_test.ok \
sim/sim_test.ok tlv/tlv_test.ok abis/abis_test.ok \
gsup/gsup_test.ok gsup/gsup_test.err \
@@ -350,9 +429,9 @@ EXTRA_DIST = testsuite.at $(srcdir)/package.m4 $(TESTSUITE) \
vty/vty_transcript_test.vty \
tdef/tdef_test.ok \
tdef/tdef_test_range_64bit.ok \
- tdef/tdef_vty_test_config_root.vty \
- tdef/tdef_vty_test_config_subnode.vty \
- tdef/tdef_vty_test_dynamic.vty \
+ tdef/tdef_vty_config_root_test.vty \
+ tdef/tdef_vty_config_subnode_test.vty \
+ tdef/tdef_vty_dynamic_test.vty \
sockaddr_str/sockaddr_str_test.ok \
use_count/use_count_test.ok use_count/use_count_test.err \
context/context_test.ok \
@@ -361,6 +440,20 @@ EXTRA_DIST = testsuite.at $(srcdir)/package.m4 $(TESTSUITE) \
exec/exec_test.ok exec/exec_test.err \
i460_mux/i460_mux_test.ok \
bitgen/bitgen_test.ok \
+ gad/gad_test.ok \
+ bsslap/bsslap_test.ok \
+ bssmap_le/bssmap_le_test.ok \
+ it_q/it_q_test.ok \
+ time_cc/time_cc_test.ok \
+ gsm48/rest_octets_test.ok \
+ base64/base64_test.ok \
+ iuup/iuup_test.ok \
+ smscb/smscb_test.ok \
+ smscb/gsm0341_test.ok \
+ smscb/cbsp_test.ok \
+ v110/test_frame.ok \
+ v110/test_ra1.ok \
+ gsm44021/test_frame_csd.ok \
$(NULL)
if ENABLE_LIBSCTP
@@ -377,6 +470,200 @@ noinst_HEADERS = conv/conv.h
TESTSUITE = $(srcdir)/testsuite
+update_exp: $(check_PROGRAMS)
+ a5/a5_test \
+ >$(srcdir)/a5/a5_test.ok
+ abis/abis_test \
+ >$(srcdir)/abis/abis_test.ok
+if ENABLE_CTRL
+ ctrl/ctrl_test \
+ >$(srcdir)/ctrl/ctrl_test.ok
+endif
+ kasumi/kasumi_test \
+ >$(srcdir)/kasumi/kasumi_test.ok
+ bits/bitrev_test \
+ >$(srcdir)/bits/bitrev_test.ok
+ bitvec/bitvec_test \
+ >$(srcdir)/bitvec/bitvec_test.ok
+ bits/bitcomp_test \
+ >$(srcdir)/bits/bitcomp_test.ok
+ bits/bitfield_test \
+ >$(srcdir)/bits/bitfield_test.ok
+ conv/conv_test \
+ >$(srcdir)/conv/conv_test.ok
+ conv/conv_gsm0503_test \
+ >$(srcdir)/conv/conv_gsm0503_test.ok
+ coding/coding_test \
+ >$(srcdir)/coding/coding_test.ok
+ msgb/msgb_test \
+ >$(srcdir)/msgb/msgb_test.ok
+ gea/gea_test \
+ >$(srcdir)/gea/gea_test.ok
+if ENABLE_MSGFILE
+ cp $(srcdir)/msgfile/msgconfig.cfg .
+ msgfile/msgfile_test \
+ >$(srcdir)/msgfile/msgfile_test.ok
+endif
+ sms/sms_test \
+ >$(srcdir)/sms/sms_test.ok
+ smscb/smscb_test \
+ >$(srcdir)/smscb/smscb_test.ok
+ smscb/gsm0341_test \
+ >$(srcdir)/smscb/gsm0341_test.ok
+ smscb/cbsp_test \
+ >$(srcdir)/smscb/cbsp_test.ok
+ ussd/ussd_test \
+ >$(srcdir)/ussd/ussd_test.ok
+ auth/milenage_test \
+ >$(srcdir)/auth/milenage_test.ok
+ comp128/comp128_test \
+ >$(srcdir)/comp128/comp128_test.ok
+ lapd/lapd_test \
+ >$(srcdir)/lapd/lapd_test.ok
+ gsm0502/gsm0502_test \
+ >$(srcdir)/gsm0502/gsm0502_test.ok
+ dtx/dtx_gsm0503_test \
+ >$(srcdir)/dtx/dtx_gsm0503_test.ok
+ gsm0808/gsm0808_test \
+ >$(srcdir)/gsm0808/gsm0808_test.ok
+ gsm29205/gsm29205_test \
+ >$(srcdir)/gsm29205/gsm29205_test.ok
+ gsm0408/gsm0408_test \
+ 2>$(srcdir)/gsm0408/gsm0408_test.err \
+ 1>$(srcdir)/gsm0408/gsm0408_test.ok
+ gsm48/rest_octets_test \
+ >$(srcdir)/gsm48/rest_octets_test.ok
+ gprs/gprs_test \
+ >$(srcdir)/gprs/gprs_test.ok
+ logging/logging_test \
+ >$(srcdir)/logging/logging_test.ok \
+ 2>$(srcdir)/logging/logging_test.err
+ codec/codec_test \
+ >$(srcdir)/codec/codec_test.ok
+ codec/codec_ecu_fr_test \
+ >$(srcdir)/codec/codec_ecu_fr_test.ok
+if ENABLE_GB
+ fr/fr_test \
+ >$(srcdir)/fr/fr_test.ok
+endif
+ loggingrb/loggingrb_test \
+ >$(srcdir)/loggingrb/logging_test.ok \
+ 2>$(srcdir)/loggingrb/logging_test.err
+ strrb/strrb_test \
+ >$(srcdir)/strrb/strrb_test.ok
+if ENABLE_VTY
+ cp $(srcdir)/vty/*.cfg .
+ vty/vty_test \
+ >$(srcdir)/vty/vty_test.ok \
+ 2>$(srcdir)/vty/vty_test.err
+endif
+if ENABLE_GB
+ gb/gprs_bssgp_test \
+ >$(srcdir)/gb/gprs_bssgp_test.ok
+ gb/gprs_bssgp_rim_test \
+ >$(srcdir)/gb/gprs_bssgp_rim_test.ok
+ gb/gprs_ns_test \
+ >$(srcdir)/gb/gprs_ns_test.ok
+ gb/gprs_ns2_test \
+ >$(srcdir)/gb/gprs_ns2_test.ok
+endif
+if ENABLE_UTILITIES
+ utils/utils_test \
+ >$(srcdir)/utils/utils_test.ok
+endif
+if !EMBEDDED
+ stats/stats_test \
+ >$(srcdir)/stats/stats_test.ok \
+ 2>$(srcdir)/stats/stats_test.err
+endif
+ write_queue/wqueue_test \
+ >$(srcdir)/write_queue/wqueue_test.ok
+if ENABLE_GB
+ $(srcdir)/gb/bssgp_fc_tests.sh gb \
+ >$(srcdir)/gb/bssgp_fc_tests.ok \
+ 2>$(srcdir)/gb/bssgp_fc_tests.err
+endif
+if ENABLE_PCSC
+ sim/sim_test \
+ >$(srcdir)/sim/sim_test.ok
+endif
+ timer/timer_test \
+ >$(srcdir)/timer/timer_test.ok
+ timer/clk_override_test \
+ >$(srcdir)/timer/clk_override_test.ok
+ tlv/tlv_test \
+ >$(srcdir)/tlv/tlv_test.ok
+ gsup/gsup_test \
+ >$(srcdir)/gsup/gsup_test.ok \
+ 2>$(srcdir)/gsup/gsup_test.err
+if ENABLE_CTRL
+ fsm/fsm_test \
+ >$(srcdir)/fsm/fsm_test.ok \
+ 2>$(srcdir)/fsm/fsm_test.err
+ fsm/fsm_dealloc_test \
+ 2>$(srcdir)/fsm/fsm_dealloc_test.err
+endif
+ oap/oap_test \
+ >$(srcdir)/oap/oap_test.ok
+ oap/oap_client_test \
+ >$(srcdir)/oap/oap_client_test.ok \
+ 2>$(srcdir)/oap/oap_client_test.err
+ socket/socket_test \
+ >$(srcdir)/socket/socket_test.ok \
+ 2>$(srcdir)/socket/socket_test.err
+ socket/socket_sctp_test \
+ >$(srcdir)/socket/socket_sctp_test.ok \
+ 2>$(srcdir)/socket/socket_sctp_test.err
+ $(srcdir)/osmo-auc-gen/osmo-auc-gen_test.sh ../utils/osmo-auc-gen \
+ >$(srcdir)/osmo-auc-gen/osmo-auc-gen_test.ok \
+ 2>$(srcdir)/osmo-auc-gen/osmo-auc-gen_test.err
+ endian/endian_test \
+ >$(srcdir)/endian/endian_test.ok
+ sercomm/sercomm_test \
+ >$(srcdir)/sercomm/sercomm_test.ok
+ prbs/prbs_test \
+ >$(srcdir)/prbs/prbs_test.ok
+ gsm23003/gsm23003_test \
+ >$(srcdir)/gsm23003/gsm23003_test.ok
+ gsm23236/gsm23236_test \
+ >$(srcdir)/gsm23236/gsm23236_test.ok
+ tdef/tdef_test \
+ >$(srcdir)/tdef/tdef_test.ok
+ sockaddr_str/sockaddr_str_test \
+ >$(srcdir)/sockaddr_str/sockaddr_str_test.ok
+ use_count/use_count_test \
+ >$(srcdir)/use_count/use_count_test.ok \
+ 2>$(srcdir)/use_count/use_count_test.err
+ context/context_test \
+ >$(srcdir)/context/context_test.ok
+if !EMBEDDED
+ exec/exec_test \
+ >$(srcdir)/exec/exec_test.ok \
+ 2>$(srcdir)/exec/exec_test.err
+endif
+ i460_mux/i460_mux_test \
+ >$(srcdir)/i460_mux/i460_mux_test.ok
+ bitgen/bitgen_test \
+ >$(srcdir)/bitgen/bitgen_test.ok
+ gad/gad_test \
+ >$(srcdir)/gad/gad_test.ok
+ bsslap/bsslap_test \
+ >$(srcdir)/bsslap/bsslap_test.ok
+ bssmap_le/bssmap_le_test \
+ >$(srcdir)/bssmap_le/bssmap_le_test.ok
+ it_q/it_q_test \
+ >$(srcdir)/it_q/it_q_test.ok
+ time_cc/time_cc_test \
+ >$(srcdir)/time_cc/time_cc_test.ok
+ iuup/iuup_test \
+ >$(srcdir)/iuup/iuup_test.ok
+ v110/test_frame \
+ >$(srcdir)/v110/test_frame.ok
+ v110/test_ra1 \
+ >$(srcdir)/v110/test_ra1.ok
+ gsm44021/test_frame_csd \
+ >$(srcdir)/gsm44021/test_frame_csd.ok
+
check-local: atconfig $(TESTSUITE)
[ -e /proc/cpuinfo ] && cat /proc/cpuinfo
$(SHELL) '$(TESTSUITE)' $(TESTSUITEFLAGS)
@@ -414,6 +701,15 @@ endif
# pass -u to osmo_verify_transcript_vty.py by doing:
# make vty-test U=-u
+vty-test-ns2:
+ $(MAKE) -C $(top_builddir)/utils osmo-ns-dummy
+ osmo_verify_transcript_vty.py -v \
+ -p 42042 \
+ -r "$(top_builddir)/utils/osmo-ns-dummy -p 42042" \
+ $(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
+
vty-test-logging:
osmo_verify_transcript_vty.py -v \
-p 42042 \
@@ -429,22 +725,30 @@ vty-test-vty:
vty-test-tdef:
osmo_verify_transcript_vty.py -v \
-p 42042 \
- -r "$(top_builddir)/tests/tdef/tdef_vty_test_config_root" \
- $(U) $(srcdir)/tdef/tdef_vty_test_config_root.vty
+ -r "$(top_builddir)/tests/tdef/tdef_vty_config_root_test" \
+ $(U) $(srcdir)/tdef/tdef_vty_config_root_test.vty
osmo_verify_transcript_vty.py -v \
-p 42042 \
- -r "$(top_builddir)/tests/tdef/tdef_vty_test_config_subnode" \
- $(U) $(srcdir)/tdef/tdef_vty_test_config_subnode.vty
+ -r "$(top_builddir)/tests/tdef/tdef_vty_config_subnode_test" \
+ $(U) $(srcdir)/tdef/tdef_vty_config_subnode_test.vty
+ osmo_verify_transcript_vty.py -v \
+ -p 42042 \
+ -r "$(top_builddir)/tests/tdef/tdef_vty_dynamic_test" \
+ $(U) $(srcdir)/tdef/tdef_vty_dynamic_test.vty
+
+vty-test-stats:
osmo_verify_transcript_vty.py -v \
-p 42042 \
- -r "$(top_builddir)/tests/tdef/tdef_vty_test_dynamic" \
- $(U) $(srcdir)/tdef/tdef_vty_test_dynamic.vty
+ -r "$(top_builddir)/tests/stats/stats_vty_test" \
+ $(U) $(srcdir)/stats/*.vty
# don't run vty tests concurrently so that the ports don't conflict
vty-test:
$(MAKE) vty-test-logging
$(MAKE) vty-test-vty
$(MAKE) vty-test-tdef
+ $(MAKE) vty-test-ns2
+ $(MAKE) vty-test-stats
ctrl-test:
echo "No CTRL tests exist currently"
diff --git a/tests/abis/abis_test.c b/tests/abis/abis_test.c
index ca6daed8..563363d3 100644
--- a/tests/abis/abis_test.c
+++ b/tests/abis/abis_test.c
@@ -13,10 +13,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <osmocom/core/application.h>
@@ -171,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,
@@ -197,6 +193,52 @@ static void test_sw_descr()
msgb_free(msg);
}
+/* Test decode IPAC_DLCX_IND obtained from SYS#5915 */
+static void test_dec_ipac_dlc_indx(void)
+{
+/* Radio Signalling Link (RSL)
+ 0111 111. = Message discriminator: ip.access Vendor Specific messages (63)
+ .... ...0 = T bit: Not considered transparent by BTS
+ .111 0110 = Message type: ip.access DLCX INDication (0x76)
+ Channel number IE
+ Element identifier: Channel Number (0x01)
+ 0000 1... = C-bits: Bm + ACCH (1)
+ .... .110 = Time slot number (TN): 6
+ Element identifier: Connection Identifier (0xf8)
+ ip.access Connection ID: 0
+ Element identifier: Connection Statistics (0xf6)
+ [1 byte length here, val = 28 (0x1c)]
+ Packets Sent: 1202
+ Octets Sent: 45052
+ Packets Received: 556
+ Octets Received: 24580
+ Packets Lost: 0
+ Inter-arrival Jitter: 0
+ Average Tx Delay: 0
+ Cause IE
+ Element identifier: Cause (0x1a)
+ Length: 1
+ 0... .... = Extension: No Extension
+ .000 .... = Class: Normal event (0)
+ .000 1111 = Cause Value: normal event, unspecified (15)
+*/
+ const uint8_t hex[] = {
+ 0x7e, 0x76, 0x01, 0x0e, 0xf8, 0x00, 0x00, 0xf6, 0x1c, 0x00, 0x00, 0x04, 0xb2, 0x00, 0x00, 0xaf,
+ 0xfc, 0x00, 0x00, 0x02, 0x2c, 0x00, 0x00, 0x60, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x1a, 0x01, 0x0f
+ };
+ struct abis_rsl_dchan_hdr *dh = (struct abis_rsl_dchan_hdr *)&hex[0];
+
+ struct tlv_parsed tp;
+ int rc;
+
+ printf("Testing decoding IPAC_DLCX_IND\n");
+
+ rc = rsl_tlv_parse(&tp, dh->data, sizeof(hex) - sizeof(*dh));
+
+ OSMO_ASSERT(rc == 3);
+}
+
int main(int argc, char **argv)
{
void *ctx = talloc_named_const(NULL, 0, "abis_test");
@@ -206,6 +248,7 @@ int main(int argc, char **argv)
test_simple_sw_config();
test_simple_sw_short();
test_dual_sw_config();
+ test_dec_ipac_dlc_indx();
printf("OK.\n");
diff --git a/tests/abis/abis_test.ok b/tests/abis/abis_test.ok
index e6b626be..2ee647bb 100644
--- a/tests/abis/abis_test.ok
+++ b/tests/abis/abis_test.ok
@@ -38,4 +38,5 @@ len: 13
file_id: 09 07 05
file_ver: 06 07 08
test_dual_sw_config(): OK
+Testing decoding IPAC_DLCX_IND
OK.
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/base64/base64_test.c b/tests/base64/base64_test.c
new file mode 100644
index 00000000..79ec212a
--- /dev/null
+++ b/tests/base64/base64_test.c
@@ -0,0 +1,55 @@
+#include <osmocom/core/base64.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+static const unsigned char base64_test_dec[64] = {
+ 0x24, 0x48, 0x6E, 0x56, 0x87, 0x62, 0x5A, 0xBD,
+ 0xBF, 0x17, 0xD9, 0xA2, 0xC4, 0x17, 0x1A, 0x01,
+ 0x94, 0xED, 0x8F, 0x1E, 0x11, 0xB3, 0xD7, 0x09,
+ 0x0C, 0xB6, 0xE9, 0x10, 0x6F, 0x22, 0xEE, 0x13,
+ 0xCA, 0xB3, 0x07, 0x05, 0x76, 0xC9, 0xFA, 0x31,
+ 0x6C, 0x08, 0x34, 0xFF, 0x8D, 0xC2, 0x6C, 0x38,
+ 0x00, 0x43, 0xE9, 0x54, 0x97, 0xAF, 0x50, 0x4B,
+ 0xD1, 0x41, 0xBA, 0x95, 0x31, 0x5A, 0x0B, 0x97
+};
+
+static const unsigned char base64_test_enc[] =
+ "JEhuVodiWr2/F9mixBcaAZTtjx4Rs9cJDLbpEG8i7hPK"
+ "swcFdsn6MWwINP+Nwmw4AEPpVJevUEvRQbqVMVoLlw==";
+
+/*
+ * Checkup routine
+ */
+int main(int argc, char **argv)
+{
+ size_t len;
+ const unsigned char *src;
+ unsigned char buffer[128];
+
+ printf(" Base64 encoding test: ");
+
+ src = base64_test_dec;
+
+ if (osmo_base64_encode(buffer, sizeof(buffer), &len, src, 64) != 0 ||
+ memcmp(base64_test_enc, buffer, 88) != 0) {
+ printf("failed\n");
+
+ exit(1);
+ }
+
+ printf("passed\n Base64 decoding test: ");
+
+ src = base64_test_enc;
+
+ if (osmo_base64_decode(buffer, sizeof(buffer), &len, src, 88) != 0 ||
+ memcmp(base64_test_dec, buffer, 64) != 0) {
+ printf("failed\n");
+
+ exit(1);
+ }
+
+ printf("passed\n\n");
+
+ exit(0);
+}
diff --git a/tests/base64/base64_test.ok b/tests/base64/base64_test.ok
new file mode 100644
index 00000000..ff187d9c
--- /dev/null
+++ b/tests/base64/base64_test.ok
@@ -0,0 +1,3 @@
+ Base64 encoding test: passed
+ Base64 decoding test: passed
+
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 b4764e68..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];
@@ -237,7 +237,8 @@ static void test_bitvec_read_field(void)
#define _bitvec_read_field(idx, len) \
readIndex = idx; \
field = bitvec_read_field(&bv, &readIndex, len); \
- printf("bitvec_read_field(idx=%u, len=%u) => %" PRIx64 "\n", idx, len, field);
+ printf("bitvec_read_field(idx=%u, len=%u) => %" PRIx64 " (%s)\n", \
+ idx, len, field, errno == 0 ? "success" : "error");
_bitvec_read_field(0, 64);
_bitvec_read_field(0, 32);
diff --git a/tests/bitvec/bitvec_test.ok b/tests/bitvec/bitvec_test.ok
index b118502e..d87ac7e0 100644
--- a/tests/bitvec/bitvec_test.ok
+++ b/tests/bitvec/bitvec_test.ok
@@ -171,21 +171,21 @@ bitvec_runlength....
bitvec bytes used.
test bitvec_read_field():
-bitvec_read_field(idx=0, len=64) => deadbeeffeebdaed
-bitvec_read_field(idx=0, len=32) => deadbeef
-bitvec_read_field(idx=0, len=16) => dead
-bitvec_read_field(idx=0, len=8) => de
-bitvec_read_field(idx=0, len=0) => 0
-bitvec_read_field(idx=8, len=8) => ad
-bitvec_read_field(idx=8, len=4) => a
-bitvec_read_field(idx=8, len=0) => 0
-bitvec_read_field(idx=10, len=9) => 16d
-bitvec_read_field(idx=10, len=7) => 5b
-bitvec_read_field(idx=10, len=5) => 16
-bitvec_read_field(idx=10, len=3) => 5
-bitvec_read_field(idx=10, len=1) => 1
-bitvec_read_field(idx=512, len=16) => ffffffffffffffea
-bitvec_read_field(idx=0, len=65) => ffffffffffffffea
-bitvec_read_field(idx=64, len=16) => ffffffffffffffea
+bitvec_read_field(idx=0, len=64) => deadbeeffeebdaed (success)
+bitvec_read_field(idx=0, len=32) => deadbeef (success)
+bitvec_read_field(idx=0, len=16) => dead (success)
+bitvec_read_field(idx=0, len=8) => de (success)
+bitvec_read_field(idx=0, len=0) => 0 (success)
+bitvec_read_field(idx=8, len=8) => ad (success)
+bitvec_read_field(idx=8, len=4) => a (success)
+bitvec_read_field(idx=8, len=0) => 0 (success)
+bitvec_read_field(idx=10, len=9) => 16d (success)
+bitvec_read_field(idx=10, len=7) => 5b (success)
+bitvec_read_field(idx=10, len=5) => 16 (success)
+bitvec_read_field(idx=10, len=3) => 5 (success)
+bitvec_read_field(idx=10, len=1) => 1 (success)
+bitvec_read_field(idx=512, len=16) => 0 (error)
+bitvec_read_field(idx=0, len=65) => 0 (error)
+bitvec_read_field(idx=64, len=16) => 0 (error)
bitvec ok.
diff --git a/tests/bsslap/bsslap_test.c b/tests/bsslap/bsslap_test.c
new file mode 100644
index 00000000..fc5ce754
--- /dev/null
+++ b/tests/bsslap/bsslap_test.c
@@ -0,0 +1,103 @@
+#include <stdio.h>
+
+#include <osmocom/core/utils.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/gsm/bsslap.h>
+
+struct bsslap_pdu bsslap_test_pdus[] = {
+ {
+ .msg_type = BSSLAP_MSGT_TA_REQUEST,
+ },
+ {
+ .msg_type = BSSLAP_MSGT_TA_RESPONSE,
+ .ta_response = {
+ .cell_id = 23,
+ .ta = 42,
+ },
+ },
+ {
+ .msg_type = BSSLAP_MSGT_REJECT,
+ .reject = BSSLAP_CAUSE_OTHER_RADIO_EVT_FAIL,
+ },
+ {
+ .msg_type = BSSLAP_MSGT_RESET,
+ .reset = {
+ .cell_id = 23,
+ .ta = 42,
+ .chan_desc = {
+ .chan_nr = 23,
+ .h0 = {
+ .tsc = 5,
+ .h = 1,
+ .arfcn_high = 2,
+ .arfcn_low = 3,
+ },
+ },
+ .cause = BSSLAP_CAUSE_INTRA_BSS_HO,
+ },
+ },
+ {
+ .msg_type = BSSLAP_MSGT_ABORT,
+ .abort = BSSLAP_CAUSE_LOSS_SIG_CONN_MS,
+ },
+ {
+ .msg_type = BSSLAP_MSGT_TA_LAYER3,
+ .ta_layer3 = {
+ .ta = 23,
+ },
+ },
+};
+
+void test_bsslap_enc_dec(void)
+{
+ struct bsslap_pdu *pdu;
+ printf("--- %s\n", __func__);
+
+ for (pdu = bsslap_test_pdus; (pdu - bsslap_test_pdus) < ARRAY_SIZE(bsslap_test_pdus); pdu++) {
+ struct msgb *msg = msgb_alloc(1024, __func__);
+ struct bsslap_pdu dec_pdu;
+ struct osmo_bsslap_err *err;
+ int rc;
+ void *loop_ctx = msg;
+ rc = osmo_bsslap_enc(msg, pdu);
+ if (rc <= 0) {
+ printf("[%td] %s: ERROR: failed to encode pdu\n", (pdu - bsslap_test_pdus),
+ osmo_bsslap_msgt_name(pdu->msg_type));
+ goto loop_end;
+ }
+ if (rc != msg->len) {
+ printf("[%td] %s: ERROR: osmo_bsslap_enc() returned length %d but msgb has %d bytes\n",
+ (pdu - bsslap_test_pdus), osmo_bsslap_msgt_name(pdu->msg_type),
+ rc, msg->len);
+ goto loop_end;
+ }
+
+ memset(&dec_pdu, 0xff, sizeof(dec_pdu));
+ rc = osmo_bsslap_dec(&dec_pdu, &err, loop_ctx, msg->data, msg->len);
+ if (rc) {
+ printf("[%td] %s: ERROR: failed to decode pdu: %s\n", (pdu - bsslap_test_pdus),
+ osmo_bsslap_msgt_name(pdu->msg_type), err->logmsg);
+ printf(" encoded data: %s\n", osmo_hexdump(msg->data, msg->len));
+ goto loop_end;
+ }
+
+ if (memcmp(pdu, &dec_pdu, sizeof(dec_pdu))) {
+ printf("[%td] %s: ERROR: decoded PDU != encoded PDU\n", (pdu - bsslap_test_pdus),
+ osmo_bsslap_msgt_name(pdu->msg_type));
+ printf(" original struct: %s\n", osmo_hexdump((void*)pdu, sizeof(*pdu)));
+ printf(" decoded struct: %s\n", osmo_hexdump((void*)&dec_pdu, sizeof(dec_pdu)));
+ goto loop_end;
+ }
+
+ printf("[%td] %s: ok\n", (pdu - bsslap_test_pdus), osmo_bsslap_msgt_name(pdu->msg_type));
+
+loop_end:
+ msgb_free(msg);
+ }
+}
+
+int main(int argc, char **argv)
+{
+ test_bsslap_enc_dec();
+ return 0;
+}
diff --git a/tests/bsslap/bsslap_test.ok b/tests/bsslap/bsslap_test.ok
new file mode 100644
index 00000000..f3199e11
--- /dev/null
+++ b/tests/bsslap/bsslap_test.ok
@@ -0,0 +1,7 @@
+--- test_bsslap_enc_dec
+[0] TA Request: ok
+[1] TA Response: ok
+[2] Reject: ok
+[3] Reset: ok
+[4] Abort: ok
+[5] TA Layer3: ok
diff --git a/tests/bssmap_le/bssmap_le_test.c b/tests/bssmap_le/bssmap_le_test.c
new file mode 100644
index 00000000..a14c38a3
--- /dev/null
+++ b/tests/bssmap_le/bssmap_le_test.c
@@ -0,0 +1,207 @@
+#include <stdio.h>
+
+#include <osmocom/core/utils.h>
+#include <osmocom/gsm/bssmap_le.h>
+
+struct bssmap_le_pdu bssmap_le_test_pdus[] = {
+ {
+ .msg_type = BSSMAP_LE_MSGT_RESET,
+ .reset = GSM0808_CAUSE_EQUIPMENT_FAILURE,
+ },
+ {
+ .msg_type = BSSMAP_LE_MSGT_RESET_ACK,
+ },
+ {
+ .msg_type = BSSMAP_LE_MSGT_PERFORM_LOC_REQ,
+ .perform_loc_req = {
+ .location_type = {
+ .location_information = BSSMAP_LE_LOC_INFO_CURRENT_GEOGRAPHIC,
+ },
+
+ .cell_id = {
+ .id_discr = CELL_IDENT_LAC_AND_CI,
+ .id.lac_and_ci = {
+ .lac = 23,
+ .ci = 42,
+ },
+ },
+
+ .lcs_client_type_present = true,
+ .lcs_client_type = BSSMAP_LE_LCS_CTYPE_VALUE_ADDED_UNSPECIFIED,
+
+ .imsi = {
+ .type = GSM_MI_TYPE_IMSI,
+ .imsi = "1234567890",
+ },
+
+ .imei = {
+ .type = GSM_MI_TYPE_IMEI,
+ .imei = "123456789012345",
+ },
+
+ .apdu_present = true,
+ .apdu = {
+ .msg_type = BSSLAP_MSGT_TA_LAYER3,
+ .ta_layer3 = {
+ .ta = 23,
+ },
+ },
+ },
+ },
+ {
+ .msg_type = BSSMAP_LE_MSGT_PERFORM_LOC_RESP,
+ .perform_loc_resp = {
+ .location_estimate_present = true,
+ .location_estimate = {
+ .ell_point_unc_circle = {
+ .h = { .type = GAD_TYPE_ELL_POINT_UNC_CIRCLE },
+ .lat = { 1, 2, 3 },
+ .lon = { 4, 5, 6 },
+ .unc = 123,
+ },
+ },
+ },
+ },
+ {
+ .msg_type = BSSMAP_LE_MSGT_PERFORM_LOC_RESP,
+ .perform_loc_resp = {
+ .lcs_cause = {
+ .present = true,
+ .cause_val = LCS_CAUSE_REQUEST_ABORTED,
+ },
+ },
+ },
+ {
+ .msg_type = BSSMAP_LE_MSGT_PERFORM_LOC_RESP,
+ .perform_loc_resp = {
+ .lcs_cause = {
+ .present = true,
+ .cause_val = LCS_CAUSE_POS_METH_FAILURE,
+ .diag_val_present = true,
+ .diag_val = 23,
+ },
+ },
+ },
+ {
+ .msg_type = BSSMAP_LE_MSGT_PERFORM_LOC_ABORT,
+ .perform_loc_abort = {
+ .present = true,
+ .cause_val = LCS_CAUSE_REQUEST_ABORTED,
+ },
+ },
+ {
+ .msg_type = BSSMAP_LE_MSGT_CONN_ORIENTED_INFO,
+ .conn_oriented_info = {
+ .apdu = {
+ .msg_type = BSSLAP_MSGT_TA_REQUEST,
+ },
+ },
+ },
+ {
+ .msg_type = BSSMAP_LE_MSGT_CONN_ORIENTED_INFO,
+ .conn_oriented_info = {
+ .apdu = {
+ .msg_type = BSSLAP_MSGT_TA_RESPONSE,
+ .ta_response = {
+ .cell_id = 23,
+ .ta = 42,
+ },
+ },
+ },
+ },
+ {
+ .msg_type = BSSMAP_LE_MSGT_CONN_ORIENTED_INFO,
+ .conn_oriented_info = {
+ .apdu = {
+ .msg_type = BSSLAP_MSGT_REJECT,
+ .reject = BSSLAP_CAUSE_CONGESTION,
+ },
+ },
+ },
+ {
+ .msg_type = BSSMAP_LE_MSGT_PERFORM_LOC_REQ,
+ .perform_loc_req = {
+ .location_type = {
+ .location_information = BSSMAP_LE_LOC_INFO_CURRENT_GEOGRAPHIC,
+ },
+
+ .cell_id = {
+ .id_discr = CELL_IDENT_LAC_AND_CI,
+ .id.lac_and_ci = {
+ .lac = 23,
+ .ci = 42,
+ },
+ },
+
+ .lcs_client_type_present = true,
+ .lcs_client_type = BSSMAP_LE_LCS_CTYPE_EMERG_SVC_UNSPECIFIED,
+
+ .more_items = true,
+
+ .lcs_priority_present = true,
+ .lcs_priority = 0x00, /* highest */
+
+ .lcs_qos_present = true,
+ .lcs_qos = {
+ .ha_ind = 1,
+ .ha_val = 0x12,
+ },
+ },
+ },
+};
+
+void test_bssmap_le_enc_dec(void)
+{
+ struct bssmap_le_pdu *pdu;
+ printf("--- %s\n", __func__);
+
+ for (pdu = bssmap_le_test_pdus; (pdu - bssmap_le_test_pdus) < ARRAY_SIZE(bssmap_le_test_pdus); pdu++) {
+ struct msgb *msg;
+ struct bssap_le_pdu enc_pdu = {
+ .discr = BSSAP_LE_MSG_DISCR_BSSMAP_LE,
+ .bssmap_le = *pdu,
+ };
+ struct bssap_le_pdu dec_pdu;
+ struct osmo_bssap_le_err *err = NULL;
+ void *loop_ctx;
+ int rc;
+
+ msg = osmo_bssap_le_enc(&enc_pdu);
+ if (!msg) {
+ printf("[%td] %s: ERROR: failed to encode pdu\n", (pdu - bssmap_le_test_pdus),
+ osmo_bssmap_le_msgt_name(pdu->msg_type));
+ goto loop_end;
+ }
+ loop_ctx = msg;
+
+ memset(&dec_pdu, 0xff, sizeof(dec_pdu));
+ rc = osmo_bssap_le_dec(&dec_pdu, &err, loop_ctx, msg);
+ if (rc) {
+ printf("[%td] %s: ERROR: failed to decode pdu: %s\n", (pdu - bssmap_le_test_pdus),
+ osmo_bssmap_le_msgt_name(pdu->msg_type), err->logmsg);
+ printf(" encoded data: %s\n", osmo_hexdump(msg->data, msg->len));
+ goto loop_end;
+ }
+
+ if (memcmp(&enc_pdu, &dec_pdu, sizeof(dec_pdu))) {
+ printf("[%td] %s: ERROR: decoded PDU != encoded PDU\n", (pdu - bssmap_le_test_pdus),
+ osmo_bssmap_le_msgt_name(pdu->msg_type));
+ printf(" original struct: %s\n", osmo_hexdump((void*)&enc_pdu, sizeof(enc_pdu)));
+ printf(" decoded struct: %s\n", osmo_hexdump((void*)&dec_pdu, sizeof(dec_pdu)));
+ printf(" encoded data: %s\n", osmo_hexdump(msg->data, msg->len));
+ goto loop_end;
+ }
+
+ printf("[%td] %s: ok (encoded len = %d)\n", (pdu - bssmap_le_test_pdus),
+ osmo_bssmap_le_msgt_name(pdu->msg_type), msg->len);
+
+loop_end:
+ msgb_free(msg);
+ }
+}
+
+int main(int argc, char **argv)
+{
+ test_bssmap_le_enc_dec();
+ return 0;
+}
diff --git a/tests/bssmap_le/bssmap_le_test.ok b/tests/bssmap_le/bssmap_le_test.ok
new file mode 100644
index 00000000..8cc77039
--- /dev/null
+++ b/tests/bssmap_le/bssmap_le_test.ok
@@ -0,0 +1,12 @@
+--- test_bssmap_le_enc_dec
+[0] RESET: ok (encoded len = 6)
+[1] RESET ACKNOWLEDGE: ok (encoded len = 3)
+[2] PERFORM LOCATION REQUEST: ok (encoded len = 41)
+[3] PERFORM LOCATION RESPONSE: ok (encoded len = 13)
+[4] PERFORM LOCATION RESPONSE: ok (encoded len = 6)
+[5] PERFORM LOCATION RESPONSE: ok (encoded len = 7)
+[6] PERFORM LOCATION ABORT: ok (encoded len = 6)
+[7] CONNECTION ORIENTED INFORMATON: ok (encoded len = 8)
+[8] CONNECTION ORIENTED INFORMATON: ok (encoded len = 13)
+[9] CONNECTION ORIENTED INFORMATON: ok (encoded len = 10)
+[10] PERFORM LOCATION REQUEST: ok (encoded len = 25)
diff --git a/tests/codec/codec_ecu_fr_test.c b/tests/codec/codec_ecu_fr_test.c
index 4040ce94..a9dae896 100644
--- a/tests/codec/codec_ecu_fr_test.c
+++ b/tests/codec/codec_ecu_fr_test.c
@@ -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/coding/coding_test.c b/tests/coding/coding_test.c
index bdfe3002..5a6f6e06 100644
--- a/tests/coding/coding_test.c
+++ b/tests/coding/coding_test.c
@@ -13,10 +13,6 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <stdio.h>
diff --git a/tests/conv/conv_gsm0503_test.ok b/tests/conv/conv_gsm0503_test.ok
index 764bd436..bfefcd0d 100644
--- a/tests/conv/conv_gsm0503_test.ok
+++ b/tests/conv/conv_gsm0503_test.ok
@@ -6,6 +6,38 @@
[..] 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_f48
+[.] Input length : ret = 152 exp = 152 -> OK
+[.] Output length : ret = 468 exp = 468 -> 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 b46e9ac5..1d4d4d74 100644
--- a/tests/ctrl/ctrl_test.c
+++ b/tests/ctrl/ctrl_test.c
@@ -117,11 +117,14 @@ static void assert_test(struct ctrl_handle *ctrl, struct ctrl_connection *ccon,
} else {
struct msgb *sent_msg = msgb_dequeue(&ccon->write_queue.msg_queue);
OSMO_ASSERT(sent_msg);
- msgb_put_u8(sent_msg, 0);
- printf("replied: '%s'\n", osmo_escape_str((char*)msgb_l2(sent_msg), -1));
+ char *strbuf = talloc_size(sent_msg, msgb_l2len(sent_msg) + 1);
+ memcpy(strbuf, msgb_l2(sent_msg), msgb_l2len(sent_msg));
+ strbuf[msgb_l2len(sent_msg)] = '\0';
+
+ printf("replied: '%s'\n", osmo_escape_str(strbuf, -1));
OSMO_ASSERT(t->reply_str);
- OSMO_ASSERT(!strcmp(t->reply_str, (char*)msgb_l2(sent_msg)));
+ OSMO_ASSERT(!strcmp(t->reply_str, strbuf));
msgb_free(sent_msg);
}
osmo_wqueue_clear(&ccon->write_queue);
@@ -331,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;
@@ -387,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/dtx/dtx_gsm0503_test.c b/tests/dtx/dtx_gsm0503_test.c
index f3003133..21d1751e 100644
--- a/tests/dtx/dtx_gsm0503_test.c
+++ b/tests/dtx/dtx_gsm0503_test.c
@@ -15,18 +15,18 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <string.h>
#include <stdio.h>
#include <stdint.h>
#include <osmocom/core/utils.h>
+#include <osmocom/coding/gsm0503_coding.h>
#include <osmocom/coding/gsm0503_amr_dtx.h>
+/* Length of payload bits in a Normal Burst */
+#define BURST_PLEN (57 * 2 + 2)
+
char sample_afs_sid_frame[] =
{
"111111110000000011001100101010100100010011111111001000100111011110011001001100111100110010011001111011100100010011111111001000100111011110011001001100111100110010011001111011100100010011111111001000100111011110011001001100111100110010011001111011100100010011111111001000100111011110011001001100111100110010011001111011100100010011111111001000100111011110011001001100111100110010011001111011100100010011111111001000100111011110011001001100111100110010011001"
@@ -72,7 +72,7 @@ char sample_sid_update_inh_frame[] =
"xBxBxBxBxBxBxBxBxBxBxBxBxBxBxBxBx0x1x0x0x1x1x1x1x0x0x1x0x0x1x1x1x1x0x0x1x0x0x1x1x1x1x0x0x1x0x0x1x1x1x1x0x0x1x0x0x1x1x1x1x0x0x1x0x0x1x1x1x1x0x0x1x0x0x1x1x1x1x0x0x1x0x0x1x1x1x1x0x0x1x0x0x1x1x1x1x0x0x1x0x0x1x1x1x1x0x0x1x0x0x1x1x1x1x0x0x1x0x0x1x1x1x1x0x0x1x0x0x1x1x1x1x0x0x1x0x0x1x1x1x1x0x0x1x0x0x1x1x1x1x0x0x1x0x0x1x1x1x1x0x0x1x0x0x1x1x1x1x0x0x1x0x0x1x1x1x1x0x0x1x0x0x1x1x1x1x0x0x1x0x0x1x1x1x1x0x0x1x0x0x1x1x1x1x0x0x1x0x0x1x1x1x1x0x0x1x0x0x1x1x1x1x0x0x1x0x0x1"
};
-unsigned int string_to_ubit(ubit_t * ubits, char *string)
+unsigned int string_to_sbit(sbit_t *sbits, char *string)
{
unsigned int len;
unsigned int i;
@@ -80,7 +80,7 @@ unsigned int string_to_ubit(ubit_t * ubits, char *string)
len = strlen(string);
for (i = 0; i < len; i++) {
- ubits[i] = string[i] & 1;
+ sbits[i] = string[i] == '1' ? -127 : 127;
}
return len;
@@ -88,28 +88,161 @@ unsigned int string_to_ubit(ubit_t * ubits, char *string)
void test_gsm0503_detect_afs_dtx_frame(char *string)
{
- ubit_t ubits[512];
+ sbit_t sbits[512];
uint8_t dtx_frame_type;
int n_errors;
int n_bits_total;
+ int mode_id = -1;
- string_to_ubit(ubits, string);
- dtx_frame_type = gsm0503_detect_afs_dtx_frame(&n_errors, &n_bits_total, ubits);
- printf(" ==> %s, n_errors=%i, n_bits_total=%i\n", gsm0503_amr_dtx_frame_name(dtx_frame_type),
- n_errors, n_bits_total);
+ string_to_sbit(sbits, string);
+ dtx_frame_type = gsm0503_detect_afs_dtx_frame2(&n_errors, &n_bits_total, &mode_id, sbits);
+ printf(" ==> %s, n_errors=%d, n_bits_total=%d, mode_id=%d\n",
+ gsm0503_amr_dtx_frame_name(dtx_frame_type),
+ n_errors, n_bits_total, mode_id);
}
void test_gsm0503_detect_ahs_dtx_frame(char *string)
{
- ubit_t ubits[512];
+ sbit_t sbits[512];
uint8_t dtx_frame_type;
int n_errors;
int n_bits_total;
+ int mode_id = -1;
+
+ string_to_sbit(sbits, string);
+ dtx_frame_type = gsm0503_detect_ahs_dtx_frame2(&n_errors, &n_bits_total, &mode_id, sbits);
+ printf(" ==> %s, n_errors=%d, n_bits_total=%d, mode_id=%d\n",
+ gsm0503_amr_dtx_frame_name(dtx_frame_type),
+ n_errors, n_bits_total, mode_id);
+}
+
+static void test_gsm0503_tch_afhs_decode_dtx(const sbit_t *bursts, size_t offset,
+ enum gsm0503_amr_dtx_frames *amr_last_dtx,
+ bool full_rate, const char *test_desc)
+{
+ uint8_t tch_data[128]; /* just to be safe */
+ int n_errors = 0, n_bits_total = 0;
+ int rc;
+
+ printf("Running %s(at offset=%zu): testing %s\n", __func__, offset, test_desc);
+
+ /* Dummy (not really important) values */
+ uint8_t codec[4] = { 0, 1, 2, 3 };
+ int codecs = ARRAY_SIZE(codec);
+ uint8_t ul_cmr = 0;
+ uint8_t ul_ft = 0;
+
+ if (full_rate) {
+ rc = gsm0503_tch_afs_decode_dtx(&tch_data[0], &bursts[offset], false,
+ codec, codecs, &ul_ft, &ul_cmr,
+ &n_errors, &n_bits_total,
+ (uint8_t *)amr_last_dtx);
+ } else {
+ rc = gsm0503_tch_ahs_decode_dtx(&tch_data[0], &bursts[offset], false, false,
+ codec, codecs, &ul_ft, &ul_cmr,
+ &n_errors, &n_bits_total,
+ (uint8_t *)amr_last_dtx);
+ }
+ printf(" ==> gsm0503_tch_a%cs_decode_dtx() yields '%s' (rc=%d, BER %d/%d)\n",
+ full_rate ? 'f' : 'h', gsm0503_amr_dtx_frame_name(*amr_last_dtx),
+ rc, n_errors, n_bits_total);
+ if (rc > 0)
+ printf(" ====> tch_data[] = { %s }\n", osmo_hexdump_nospc(tch_data, rc));
+}
+
+static void test_gsm0503_tch_afhs_decode_dtx_sid_update(void)
+{
+ enum gsm0503_amr_dtx_frames amr_last_dtx = AMR_OTHER;
+ sbit_t bursts[BURST_PLEN * 12]; /* 12 bursts */
+ int rc;
+
+ /* 456 soft-bits containing an AFS_SID_UPDATE frame (captured on the air) */
+ const char *afs_sid_update = \
+ "94 81 83 76 7b 81 6b 7f 76 8c 81 81 81 86 71 7f 75 81 6d 7a 81 6b 7f 78 8a 87 70 75 8e"
+ "81 8d 7f 81 70 72 81 7f 85 86 7f 93 81 8a 74 7f 71 89 8a 75 7f 7f 78 8c 81 8b 7f 81 7f"
+ "7f 7f 70 8a 8b 7f 90 81 81 81 8a 77 7f 7f 70 81 70 71 86 8e 7f 81 7f 81 75 72 87 8c 76"
+ "7f 72 8e 81 81 81 81 92 7f 8c 81 92 7f 8c 89 7f 81 7f 8f 8b 77 76 86 8c 78 73 88 81 8b"
+ "81 7f 8c 85 77 7b 8d 81 81 81 8b 7f 81 7f 8e 81 8e 7f 8a 8a 7f 93 85 6b 7f 7f 72 81 6f"
+ "76 89 81 81 81 8a 73 7f 72 88 87 73 7f 73 81 7f 81 7f 92 87 73 78 81 6f 7f 71 81 76 77"
+ "6f 81 7f 81 71 7f 6e 81 75 77 83 81 81 90 7f 8b 88 76 76 8a 8d 76 74 81 7f 92 81 81 8b"
+ "78 72 81 77 76 81 6c 7c 8b 81 81 8d 7f 8b 81 8e 74 7f 7f 72 81 7f 81 74 7f 71 81 75 7f"
+ "8e 81 81 8c 72 79 85 8c 78 75 8c 8a 7f 90 81 8e 77 77 81 70 7f 7f 71 81 7f 81 7f 8e 89"
+ "7f 8f 81 8f 7f 8c 8d 7f 81 7f 81 6f 7f 71 8a 87 7f 81 6f 77 81 7f 8d 88 73 79 8a 8a 7f"
+ "7f 7f 7f 7f 76 8b 81 8c 77 7c 8a 81 91 7f 81 76 79 81 71 7f 7f 6f 84 8e 78 7f 7f 7f 74"
+ "88 86 7b 77 81 6f 7f 7f 7f 7f 7f 75 81 70 7f 76 89 81 81 81 8d 78 74 84 81 8e 7f 8d 8a"
+ "7f 79 8c 87 7f 81 7f 81 6f 7f 75 8d 8a 7f 81 7f 92 81 81 85 76 7f 6f 8c 88 6c 7f 73 91"
+ "81 8d 71 7f 7f 73 8d 88 7f 81 7f 91 86 6f 7f 73 8e 81 8d 79 78 81 72 74 8c 86 72 7f 77"
+ "6e 81 7f 81 77 76 81 72 74 81 6f 7f 6f 8d 81 91 7f 81 6d 7f 6d 81 6c 7f 6c 81 7f 81 7f"
+ "8c 8b 7f 8e 89 74 74 8c 81 81 81 81 81 92 7f 8e 8b 7f 93 81 8f 7f 90 81 8d 74 7b 8b 89";
- string_to_ubit(ubits, string);
- dtx_frame_type = gsm0503_detect_ahs_dtx_frame(&n_errors, &n_bits_total, ubits);
- printf(" ==> %s, n_errors=%i, n_bits_total=%i\n", gsm0503_amr_dtx_frame_name(dtx_frame_type),
- n_errors, n_bits_total);
+ memset(&bursts[0], 0, sizeof(bursts));
+ rc = osmo_hexparse(afs_sid_update, (uint8_t *)&bursts[BURST_PLEN * 4], BURST_PLEN * 8);
+ OSMO_ASSERT(rc == BURST_PLEN * 4);
+
+ /* Test detection of AFS_SID_UPDATE (marker) */
+ test_gsm0503_tch_afhs_decode_dtx(&bursts[0], BURST_PLEN * 0, &amr_last_dtx, true /* AFS */,
+ "detection of AFS_SID_UPDATE");
+
+ /* Test decoding of AFS_SID_UPDATE_CN (actual SID) */
+ test_gsm0503_tch_afhs_decode_dtx(&bursts[0], BURST_PLEN * 4, &amr_last_dtx, true /* AFS */,
+ "decoding of AFS_SID_UPDATE");
+
+ /* 456 soft-bits containing an AHS_SID_UPDATE frame (captured on the air) */
+ const char *ahs_sid_update = \
+ "81 67 7f 7f 7f 71 8f 88 6f 73 81 7e 81 6b 7f 7e 7d 6f 8f 8a 72 76 92 81 82 81 8f 6d 6f"
+ "81 7f 92 8c 7f 97 81 8e 6f 7f 7c 7f 6e 81 7e 81 6e 73 81 7f 93 8d 6f 7f 6c 81 6b 7f 72"
+ "7c 7c 7d 7f 6f 8f 81 94 7f 92 8d 6e 7d 7d 7f 6c 8b 8e 73 71 81 7f 92 90 7f 81 6e 6e 81"
+ "7f 94 8e 70 7f 6e 8c 8d 77 7f 6a 81 7f 81 70 6d 81 6c 71 8c 91 7f 90 8e 73 6e 81 6d 7f"
+ "81 8b 71 6e 81 7f 82 7c 81 7f 81 6d 73 81 6c 6d 81 6d 7f 6e 81 7e 81 6b 7f 7f 7f 6b 81"
+ "6e 6f 81 68 7f 71 91 81 82 81 8e 70 7f 7c 7d 7f 70 81 7f 91 8f 7f 81 6c 7f 71 81 6d 74"
+ "6f 8f 81 92 7f 82 7f 91 8b 7f 81 6b 7f 6d 81 6b 6f 81 6f 6e 90 81 81 92 7f 94 81 95 7f"
+ "96 81 96 70 7f 72 8f 81 95 7f 81 6f 70 81 7f 90 92 7f 81 6c 70 81 6b 7f 6f 8d 8d 7f 81"
+ "77 81 6a 7e 7e 73 92 8c 7f 81 6a 7f 6c 8e 8e 6e 7f 71 8e 8d 7e 81 6d 7f 6c 81 6d 6c 81"
+ "7f 94 81 92 7f 97 81 92 6e 7f 70 8c 8b 73 73 91 81 93 7f 81 70 72 81 7d 81 71 70 81 7f"
+ "7d 7f 6d 90 8d 73 76 92 81 92 6f 7d 7d 70 91 81 8f 73 75 8c 90 7f 94 81 91 70 7f 7d 7e"
+ "70 8d 8d 73 7f 7c 7e 6a 81 7e 81 6d 7f 6a 81 6f 7f 7f 71 8e 81 82 81 81 81 96 72 7e 7d"
+ "81 8d 7f 81 68 7f 7e 7c 7b 7f 6c 81 6a 7f 7f 71 8f 8d 7f 81 6c 72 8e 88 70 70 81 6d 70"
+ "8d 90 7f 81 7e 95 81 94 7f 92 8b 6e 7f 7f 70 8c 8c 73 75 91 81 91 6d 7d 7e 7b 7c 7d 71"
+ "6c 89 91 7f 81 7f 95 81 93 7f 95 90 7f 81 6d 70 81 6f 75 8c 8e 75 71 81 6e 70 8d 8d 7f"
+ "91 92 7f 81 7f 94 8d 70 71 81 6e 6d 81 6e 75 8e 81 93 70 7f 70 8f 8c 7f 81 6d 6f 81 6a";
+
+ memset(&bursts[0], 0, sizeof(bursts));
+ rc = osmo_hexparse(ahs_sid_update, (uint8_t *)&bursts[BURST_PLEN * 2], BURST_PLEN * 10);
+ OSMO_ASSERT(rc == BURST_PLEN * 4);
+
+ /* Test detection and decoding of AHS_SID_UPDATE */
+ test_gsm0503_tch_afhs_decode_dtx(&bursts[0], BURST_PLEN * 0, &amr_last_dtx, false /* AHS */,
+ "detection/decoding of AHS_SID_UPDATE");
+ test_gsm0503_tch_afhs_decode_dtx(&bursts[0], BURST_PLEN * 2, &amr_last_dtx, false /* AHS */,
+ "detection/decoding of AHS_SID_UPDATE");
+ test_gsm0503_tch_afhs_decode_dtx(&bursts[0], BURST_PLEN * 4, &amr_last_dtx, false /* AHS */,
+ "detection/decoding of AHS_SID_UPDATE");
+}
+
+static void test_gsm0503_tch_afhs_decode_dtx_facch(void)
+{
+ enum gsm0503_amr_dtx_frames amr_last_dtx;
+ sbit_t bursts[BURST_PLEN * 8]; /* 8 bursts */
+ unsigned int i;
+
+ /* Set stealing bits to provoke FACCH/[FH] detection */
+ for (i = 0; i < 8; i++) {
+ sbit_t *burst = &bursts[BURST_PLEN * i];
+ memset(&burst[0], 0, BURST_PLEN);
+ burst[i >> 2 ? 57 : 58] = -127;
+ }
+
+ amr_last_dtx = AFS_SID_UPDATE;
+ test_gsm0503_tch_afhs_decode_dtx(&bursts[0], BURST_PLEN * 0,
+ &amr_last_dtx, true /* AFS */,
+ "tagging of FACCH/F");
+ OSMO_ASSERT(amr_last_dtx == AMR_OTHER);
+
+ amr_last_dtx = AHS_SID_UPDATE;
+ test_gsm0503_tch_afhs_decode_dtx(&bursts[0], BURST_PLEN * 0,
+ &amr_last_dtx, false /* AHS */,
+ "tagging of FACCH/H");
+ OSMO_ASSERT(amr_last_dtx == AMR_OTHER);
}
int main(int argc, char **argv)
@@ -126,5 +259,8 @@ int main(int argc, char **argv)
test_gsm0503_detect_ahs_dtx_frame(sample_sid_first_inh_frame);
test_gsm0503_detect_ahs_dtx_frame(sample_sid_update_inh_frame);
+ test_gsm0503_tch_afhs_decode_dtx_sid_update();
+ test_gsm0503_tch_afhs_decode_dtx_facch();
+
return EXIT_SUCCESS;
}
diff --git a/tests/dtx/dtx_gsm0503_test.ok b/tests/dtx/dtx_gsm0503_test.ok
index 77a49360..477d4312 100644
--- a/tests/dtx/dtx_gsm0503_test.ok
+++ b/tests/dtx/dtx_gsm0503_test.ok
@@ -1,11 +1,27 @@
FR AMR DTX FRAMES:
- ==> AFS_SID_FIRST, n_errors=0, n_bits_total=212
- ==> AFS_SID_UPDATE (marker), n_errors=0, n_bits_total=212
- ==> AFS_ONSET, n_errors=0, n_bits_total=228
+ ==> AFS_SID_FIRST, n_errors=0, n_bits_total=212, mode_id=-1
+ ==> AFS_SID_UPDATE (marker), n_errors=0, n_bits_total=212, mode_id=-1
+ ==> AFS_ONSET, n_errors=0, n_bits_total=228, mode_id=0
HR AMR DTX FRAMES:
- ==> AHS_SID_UPDATE (marker), n_errors=0, n_bits_total=212
- ==> AHS_SID_FIRST_P1, n_errors=0, n_bits_total=212
- ==> AHS_SID_FIRST_P2, n_errors=0, n_bits_total=114
- ==> AHS_ONSET, n_errors=0, n_bits_total=114
- ==> AHS_SID_FIRST_INH, n_errors=0, n_bits_total=212
- ==> AHS_SID_UPDATE_INH, n_errors=0, n_bits_total=212
+ ==> AHS_SID_UPDATE (marker), n_errors=0, n_bits_total=212, mode_id=-1
+ ==> AHS_SID_FIRST_P1, n_errors=0, n_bits_total=212, mode_id=-1
+ ==> AHS_SID_FIRST_P2, n_errors=0, n_bits_total=114, mode_id=0
+ ==> AHS_ONSET, n_errors=0, n_bits_total=114, mode_id=0
+ ==> AHS_SID_FIRST_INH, n_errors=0, n_bits_total=212, mode_id=-1
+ ==> AHS_SID_UPDATE_INH, n_errors=0, n_bits_total=212, mode_id=-1
+Running test_gsm0503_tch_afhs_decode_dtx(at offset=0): testing detection of AFS_SID_UPDATE
+ ==> gsm0503_tch_afs_decode_dtx() yields 'AFS_SID_UPDATE (marker)' (rc=0, BER 0/212)
+Running test_gsm0503_tch_afhs_decode_dtx(at offset=464): testing decoding of AFS_SID_UPDATE
+ ==> gsm0503_tch_afs_decode_dtx() yields 'AFS_SID_UPDATE_CN (audio)' (rc=5, BER 0/212)
+ ====> tch_data[] = { 26e9b1d2b0 }
+Running test_gsm0503_tch_afhs_decode_dtx(at offset=0): testing detection/decoding of AHS_SID_UPDATE
+ ==> gsm0503_tch_ahs_decode_dtx() yields 'AMR_OTHER (audio)' (rc=-1, BER 109/212)
+Running test_gsm0503_tch_afhs_decode_dtx(at offset=232): testing detection/decoding of AHS_SID_UPDATE
+ ==> gsm0503_tch_ahs_decode_dtx() yields 'AHS_SID_UPDATE_CN (audio)' (rc=5, BER 0/424)
+ ====> tch_data[] = { 6003ccb270 }
+Running test_gsm0503_tch_afhs_decode_dtx(at offset=464): testing detection/decoding of AHS_SID_UPDATE
+ ==> gsm0503_tch_ahs_decode_dtx() yields 'AMR_OTHER (audio)' (rc=-1, BER 111/212)
+Running test_gsm0503_tch_afhs_decode_dtx(at offset=0): testing tagging of FACCH/F
+ ==> gsm0503_tch_afs_decode_dtx() yields 'AMR_OTHER (audio)' (rc=-1, BER 456/456)
+Running test_gsm0503_tch_afhs_decode_dtx(at offset=0): testing tagging of FACCH/H
+ ==> gsm0503_tch_ahs_decode_dtx() yields 'AMR_OTHER (audio)' (rc=-1, BER 456/456)
diff --git a/tests/fr/fr_test.c b/tests/fr/fr_test.c
index 4d472b55..cdcdb434 100644
--- a/tests/fr/fr_test.c
+++ b/tests/fr/fr_test.c
@@ -12,10 +12,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#define _GNU_SOURCE
@@ -56,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 48f80e57..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");
@@ -443,7 +443,7 @@ int main(void)
ctx = talloc_named_const(NULL, 0, "main");
osmo_init_logging2(ctx, NULL);
- log_set_print_filename(osmo_stderr_target, 0);
+ log_set_print_filename2(osmo_stderr_target, LOG_FILENAME_NONE);
log_set_print_level(osmo_stderr_target, 1);
log_set_print_category(osmo_stderr_target, 1);
log_set_print_category_hex(osmo_stderr_target, 0);
diff --git a/tests/fsm/fsm_test.c b/tests/fsm/fsm_test.c
index fe48b2ba..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;
@@ -386,6 +386,74 @@ static void test_state_chg_T()
fprintf(stderr, "--- %s() done\n", __func__);
}
+/* Test setting a state timeout with second granularity */
+static void test_state_chg_Ts(void)
+{
+ struct osmo_fsm_inst *fi;
+
+ fprintf(stderr, "\n--- %s()\n", __func__);
+
+ fsm.timer_cb = &timer_cb;
+ timeout_fired = -1;
+ fake_time_start();
+
+ fi = osmo_fsm_inst_alloc(&fsm, g_ctx, NULL, LOGL_DEBUG, NULL);
+ OSMO_ASSERT(fi);
+
+ osmo_fsm_inst_state_chg(fi, ST_ONE, 8, 4242);
+ OSMO_ASSERT(timeout_fired == -1);
+
+ fake_time_passes(3, 0); /* +3s */
+ OSMO_ASSERT(timeout_fired == -1);
+
+ fake_time_passes(2, 500000); /* +2.5s */
+ OSMO_ASSERT(timeout_fired == -1);
+
+ fake_time_passes(2, 500000); /* +2.5s */
+ OSMO_ASSERT(timeout_fired == 4242);
+
+ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REQUEST, NULL);
+
+ fprintf(stderr, "--- %s() done\n", __func__);
+}
+
+/* Test setting a state timeout with millisecond granularity */
+static void test_state_chg_Tms(void)
+{
+ struct osmo_fsm_inst *fi;
+
+ fprintf(stderr, "\n--- %s()\n", __func__);
+
+ fsm.timer_cb = &timer_cb;
+ timeout_fired = -1;
+ fake_time_start();
+
+ fi = osmo_fsm_inst_alloc(&fsm, g_ctx, NULL, LOGL_DEBUG, NULL);
+ OSMO_ASSERT(fi);
+
+ osmo_fsm_inst_state_chg_ms(fi, ST_ONE, 1337, 4242); /* 1s 337ms */
+ OSMO_ASSERT(timeout_fired == -1);
+
+ fake_time_passes(0, 500000); /* +500ms, 500ms total */
+ OSMO_ASSERT(timeout_fired == -1);
+
+ fake_time_passes(0, 250000); /* +250ms, 750ms total */
+ OSMO_ASSERT(timeout_fired == -1);
+
+ fake_time_passes(0, 350000); /* +350ms, 1s 100ms total */
+ OSMO_ASSERT(timeout_fired == -1);
+
+ fake_time_passes(0, 200000); /* +200ms, 1s 300ms total */
+ OSMO_ASSERT(timeout_fired == -1);
+
+ fake_time_passes(0, 37000); /* +37ms, 1s 337ms total */
+ OSMO_ASSERT(timeout_fired == 4242);
+
+ osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REQUEST, NULL);
+
+ fprintf(stderr, "--- %s() done\n", __func__);
+}
+
static const struct log_info_cat default_categories[] = {
[DMAIN] = {
.name = "DMAIN",
@@ -412,8 +480,10 @@ int main(int argc, char **argv)
log_init(&log_info, NULL);
stderr_target = log_target_create_stderr();
log_add_target(stderr_target);
- log_set_print_filename(stderr_target, 0);
+ log_set_print_filename2(stderr_target, LOG_FILENAME_NONE);
log_set_use_color(stderr_target, 0);
+ log_set_print_category(stderr_target, 0);
+ log_set_print_category_hex(stderr_target, 0);
g_ctrl = ctrl_handle_alloc(NULL, NULL, NULL);
g_ctx = NULL;
@@ -432,6 +502,8 @@ int main(int argc, char **argv)
test_id_api();
test_state_chg_keep_timer();
test_state_chg_T();
+ test_state_chg_Ts();
+ test_state_chg_Tms();
osmo_fsm_unregister(&fsm);
exit(0);
diff --git a/tests/fsm/fsm_test.err b/tests/fsm/fsm_test.err
index 4cc5ca40..51bf5da3 100644
--- a/tests/fsm/fsm_test.err
+++ b/tests/fsm/fsm_test.err
@@ -116,3 +116,31 @@ Test_FSM{TWO}: Terminating (cause = OSMO_FSM_TERM_REQUEST)
Test_FSM{TWO}: Freeing instance
Test_FSM{TWO}: Deallocated
--- test_state_chg_T() done
+
+--- test_state_chg_Ts()
+Total time passed: 0.000000 s
+Test_FSM{NULL}: Allocated
+Test_FSM{NULL}: State change to ONE (T4242, 8s)
+Total time passed: 3.000000 s
+Total time passed: 5.500000 s
+Total time passed: 8.000000 s
+Test_FSM{ONE}: Timeout of T4242
+Test_FSM{ONE}: Terminating (cause = OSMO_FSM_TERM_REQUEST)
+Test_FSM{ONE}: Freeing instance
+Test_FSM{ONE}: Deallocated
+--- test_state_chg_Ts() done
+
+--- test_state_chg_Tms()
+Total time passed: 0.000000 s
+Test_FSM{NULL}: Allocated
+Test_FSM{NULL}: State change to ONE (T4242, 1337ms)
+Total time passed: 0.500000 s
+Total time passed: 0.750000 s
+Total time passed: 1.100000 s
+Total time passed: 1.300000 s
+Total time passed: 1.337000 s
+Test_FSM{ONE}: Timeout of T4242
+Test_FSM{ONE}: Terminating (cause = OSMO_FSM_TERM_REQUEST)
+Test_FSM{ONE}: Freeing instance
+Test_FSM{ONE}: Deallocated
+--- test_state_chg_Tms() done
diff --git a/tests/gad/gad_test.c b/tests/gad/gad_test.c
new file mode 100644
index 00000000..19be6a61
--- /dev/null
+++ b/tests/gad/gad_test.c
@@ -0,0 +1,143 @@
+#include <stdio.h>
+
+#include <osmocom/core/utils.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/gsm/gad.h>
+
+void test_gad_lat_lon_dec_enc_stability(void)
+{
+ uint32_t lat_enc;
+ uint32_t lon_enc;
+ printf("--- %s\n", __func__);
+ for (lat_enc = 0x0; lat_enc <= 0xffffff; lat_enc++) {
+ int32_t lat_dec = osmo_gad_dec_lat(lat_enc);
+ uint32_t enc2 = osmo_gad_enc_lat(lat_dec);
+ uint32_t want_enc = lat_enc;
+ /* "-0" == 0, because the highest bit is defined as a sign bit. */
+ if (lat_enc == 0x800000)
+ want_enc = 0;
+ if (enc2 != want_enc) {
+ printf("ERR: lat=%u --> %d --> %u\n", lat_enc, lat_dec, enc2);
+ printf("%d -> %u\n", lat_dec + 1, osmo_gad_enc_lat(lat_dec + 1));
+ OSMO_ASSERT(false);
+ }
+ }
+ printf("osmo_gad_dec_lat() -> osmo_gad_enc_lat() of %u values successful\n", lat_enc);
+ for (lon_enc = 0; lon_enc <= 0xffffff; lon_enc++) {
+ int32_t lon_dec = osmo_gad_dec_lon(lon_enc);
+ uint32_t enc2 = osmo_gad_enc_lon(lon_dec);
+ uint32_t want_enc = lon_enc;
+ if (enc2 != want_enc) {
+ printf("ERR: lon=%u 0x%x --> %d --> %u\n", lon_enc, lon_enc, lon_dec, enc2);
+ printf("%d -> %u\n", lon_dec + 1, osmo_gad_enc_lon(lon_dec + 1));
+ printf("%d -> %u\n", lon_dec - 1, osmo_gad_enc_lon(lon_dec - 1));
+ OSMO_ASSERT(false);
+ }
+ }
+ printf("osmo_gad_dec_lon() -> osmo_gad_enc_lon() of %u values successful\n", lon_enc);
+}
+
+struct osmo_gad gad_test_values[] = {
+ {
+ .type = GAD_TYPE_ELL_POINT_UNC_CIRCLE,
+ .ell_point_unc_circle = {
+ /* Values rounded to the nearest encodable value, for test result matching */
+ .lat = 23000006,
+ .lon = 42000002,
+ .unc = 442592,
+ },
+ },
+};
+
+void test_gad_enc_dec(void)
+{
+ int i;
+ printf("--- %s\n", __func__);
+
+ for (i = 0; i < ARRAY_SIZE(gad_test_values); i++) {
+ struct osmo_gad *t = &gad_test_values[i];
+ struct msgb *msg = msgb_alloc(1024, __func__);
+ union gad_raw raw_write;
+ union gad_raw raw_read;
+ struct osmo_gad dec_pdu;
+ int rc;
+ struct osmo_gad_err *err;
+ void *loop_ctx = msg;
+ rc = osmo_gad_enc(&raw_write, t);
+ if (rc <= 0) {
+ printf("[%d] %s: ERROR: osmo_gad_enc() failed\n", i, osmo_gad_type_name(t->type));
+ goto loop_end;
+ }
+ rc = osmo_gad_raw_write(msg, &raw_write);
+ if (rc <= 0) {
+ printf("[%d] %s: ERROR: osmo_gad_raw_write() failed\n", i, osmo_gad_type_name(t->type));
+ goto loop_end;
+ }
+ if (rc != msg->len) {
+ printf("[%d] %s: ERROR: osmo_gad_raw_write() returned length %d but msgb has %d bytes\n",
+ i, osmo_gad_type_name(t->type),
+ rc, msg->len);
+ goto loop_end;
+ }
+
+ memset(&raw_read, 0xff, sizeof(raw_read));
+ rc = osmo_gad_raw_read(&raw_read, &err, loop_ctx, msg->data, msg->len);
+ if (rc) {
+ printf("[%d] ERROR: osmo_gad_raw_read() failed: %s\n", i, err->logmsg);
+ printf(" encoded data: %s\n", osmo_hexdump(msg->data, msg->len));
+ goto loop_end;
+ }
+
+ memset(&dec_pdu, 0xff, sizeof(dec_pdu));
+ rc = osmo_gad_dec(&dec_pdu, &err, loop_ctx, &raw_read);
+ if (rc) {
+ printf("[%d] ERROR: failed to decode pdu: %s\n", i, err->logmsg);
+ printf(" encoded data: %s\n", osmo_hexdump(msg->data, msg->len));
+ goto loop_end;
+ }
+
+ if (memcmp(t, &dec_pdu, sizeof(dec_pdu))) {
+ char strbuf[128];
+ printf("[%d] %s: ERROR: decoded PDU != encoded PDU\n", i,
+ osmo_gad_type_name(t->type));
+ osmo_gad_to_str_buf(strbuf, sizeof(strbuf), t);
+ printf(" original struct: %s\n", strbuf);
+ osmo_gad_to_str_buf(strbuf, sizeof(strbuf), &dec_pdu);
+ printf(" decoded struct: %s\n", strbuf);
+ goto loop_end;
+ }
+
+ printf("[%d] %s: ok\n", i, osmo_gad_type_name(t->type));
+ printf(" encoded data: %s\n", msgb_hexdump(msg));
+
+loop_end:
+ msgb_free(msg);
+ }
+}
+
+void test_gad_to_str(void)
+{
+ int i;
+ printf("--- %s\n", __func__);
+
+ for (i = 0; i < ARRAY_SIZE(gad_test_values); i++) {
+ struct osmo_gad *t = &gad_test_values[i];
+ char buf[1024];
+ int rc;
+ rc = osmo_gad_to_str_buf(buf, sizeof(buf), t);
+
+ printf("[%d] ", i);
+ if (rc <= 0)
+ printf("%s: ERROR: osmo_gad_to_str_buf() failed\n", osmo_gad_type_name(t->type));
+ else
+ printf("%s\n", buf);
+ }
+}
+
+int main(int argc, char **argv)
+{
+ test_gad_lat_lon_dec_enc_stability();
+ test_gad_enc_dec();
+ test_gad_to_str();
+ return 0;
+}
diff --git a/tests/gad/gad_test.ok b/tests/gad/gad_test.ok
new file mode 100644
index 00000000..ec5432a8
--- /dev/null
+++ b/tests/gad/gad_test.ok
@@ -0,0 +1,8 @@
+--- test_gad_lat_lon_dec_enc_stability
+osmo_gad_dec_lat() -> osmo_gad_enc_lat() of 16777216 values successful
+osmo_gad_dec_lon() -> osmo_gad_enc_lon() of 16777216 values successful
+--- test_gad_enc_dec
+[0] Ellipsoid-point-with-uncertainty-circle: ok
+ encoded data: 10 20 b6 0c 1d dd de 28
+--- test_gad_to_str
+[0] Ellipsoid-point-with-uncertainty-circle{lat=23.000006,lon=42.000002,unc=442.592m}
diff --git a/tests/gb/bssgp_fc_test.c b/tests/gb/bssgp_fc_test.c
index cc387771..63e8308a 100644
--- a/tests/gb/bssgp_fc_test.c
+++ b/tests/gb/bssgp_fc_test.c
@@ -148,7 +148,9 @@ int main(int argc, char **argv)
osmo_init_logging2(ctx, &info);
log_set_use_color(osmo_stderr_target, 0);
- log_set_print_filename(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);
tall_msgb_ctx = msgb_talloc_ctx_init(ctx, 0);
diff --git a/tests/gb/gprs_bssgp_rim_test.c b/tests/gb/gprs_bssgp_rim_test.c
new file mode 100644
index 00000000..bb533f6d
--- /dev/null
+++ b/tests/gb/gprs_bssgp_rim_test.c
@@ -0,0 +1,799 @@
+/* Test routines for the BSSGP implementation in libosmogb
+ *
+ * (C) 2020 by sysmocom - s.f.m.c. GmbH
+ * Author: Philipp Maier <pmaier@sysmocom.de>
+ *
+ * Skeleton based on bssgp_fc_test.c
+ * (C) 2012 by Harald Welte <laforge@gnumonks.org>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+#undef _GNU_SOURCE
+#define _GNU_SOURCE
+
+#include <osmocom/core/application.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/gprs/gprs_bssgp.h>
+#include <osmocom/gprs/gprs_ns.h>
+#include <osmocom/gprs/gprs_bssgp_rim.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+
+void dump_rim_ri(struct bssgp_rim_routing_info *ri)
+{
+ switch (ri->discr) {
+ case BSSGP_RIM_ROUTING_INFO_GERAN:
+ printf("GERAN cell identifier\n");
+ printf(" * mcc: %u\n", ri->geran.raid.mcc);
+ printf(" mnc: %u\n", ri->geran.raid.mnc);
+ printf(" mnc 3 digits: %u\n", ri->geran.raid.mnc_3_digits);
+ printf(" lac: %u\n", ri->geran.raid.lac);
+ printf(" rac: %u\n", ri->geran.raid.rac);
+ printf(" * cell id: %04x\n", ri->geran.cid);
+ break;
+ case BSSGP_RIM_ROUTING_INFO_UTRAN:
+ printf("UTRAN RNC identifier\n");
+ printf(" * mcc: %u\n", ri->utran.raid.mcc);
+ printf(" mnc: %u\n", ri->utran.raid.mnc);
+ printf(" mnc 3 digits: %u\n", ri->utran.raid.mnc_3_digits);
+ printf(" lac: %u\n", ri->utran.raid.lac);
+ printf(" rac: %u\n", ri->utran.raid.rac);
+ printf(" * rnc id: %04x\n", ri->utran.rncid);
+ break;
+ case BSSGP_RIM_ROUTING_INFO_EUTRAN:
+ printf("EUTRAN eNB identifier\n");
+ printf(" * mcc: %u\n", ri->eutran.tai.mcc);
+ printf(" mnc: %u\n", ri->eutran.tai.mnc);
+ printf(" mnc 3 digits: %u\n", ri->eutran.tai.mnc_3_digits);
+ printf(" tac: %u\n", ri->eutran.tai.tac);
+ printf(" * global_enb_id: %s\n",
+ osmo_hexdump_nospc(ri->eutran.global_enb_id,
+ ri->eutran.global_enb_id_len));
+ break;
+ default:
+ OSMO_ASSERT(false);
+ }
+}
+
+static void test_bssgp_parse_rim_ri(void)
+{
+ int rc;
+ struct bssgp_rim_routing_info result;
+ uint8_t testvec_geran[] =
+ { 0x00, 0x62, 0xf2, 0x24, 0x33, 0x90, 0x00, 0x51, 0xe1 };
+ uint8_t testvec_utran[] =
+ { 0x01, 0x62, 0xf2, 0x24, 0x33, 0x90, 0x00, 0x51, 0xe1 };
+ uint8_t testvec_eutran[] =
+ { 0x02, 0x62, 0xf2, 0x24, 0x33, 0x90, 0x00, 0x51, 0xe1 };
+
+ printf("----- %s START\n", __func__);
+
+ rc = bssgp_parse_rim_ri(&result, testvec_geran,
+ sizeof(testvec_geran));
+ printf("rc=%d\n", rc);
+ dump_rim_ri(&result);
+ printf("\n");
+
+ rc = bssgp_parse_rim_ri(&result, testvec_utran,
+ sizeof(testvec_utran));
+ printf("rc=%d\n", rc);
+ dump_rim_ri(&result);
+ printf("\n");
+
+ rc = bssgp_parse_rim_ri(&result, testvec_eutran,
+ sizeof(testvec_eutran));
+ printf("rc=%d\n", rc);
+ dump_rim_ri(&result);
+ printf("\n");
+
+ printf("----- %s END\n", __func__);
+}
+
+static void test_bssgp_create_rim_ri(void)
+{
+ int rc;
+ struct bssgp_rim_routing_info ri;
+ uint8_t result[15];
+
+ printf("----- %s START\n", __func__);
+ memset(&ri, 0, sizeof(ri));
+ memset(result, 0, sizeof(result));
+ ri.discr = BSSGP_RIM_ROUTING_INFO_GERAN;
+
+ ri.geran.raid.mcc = 262;
+ ri.geran.raid.mnc = 42;
+ ri.geran.raid.mnc_3_digits = false;
+ ri.geran.raid.lac = 13200;
+ ri.geran.raid.rac = 0;
+ ri.geran.cid = 0x51e1;
+ dump_rim_ri(&ri);
+ rc = bssgp_create_rim_ri(result, &ri);
+ printf("rc=%d, ", rc);
+ if (rc > 0)
+ printf("result=%s", osmo_hexdump_nospc(result, rc));
+ printf("\n\n");
+
+ memset(&ri, 0, sizeof(ri));
+ memset(result, 0, sizeof(result));
+ ri.discr = BSSGP_RIM_ROUTING_INFO_UTRAN;
+ ri.utran.raid.mcc = 262;
+ ri.utran.raid.mnc = 42;
+ ri.utran.raid.mnc_3_digits = 0;
+ ri.utran.raid.lac = 13200;
+ ri.utran.raid.rac = 0;
+ ri.utran.rncid = 0x51e1;
+ dump_rim_ri(&ri);
+ rc = bssgp_create_rim_ri(result, &ri);
+ printf("rc=%d, ", rc);
+ if (rc > 0)
+ printf("result=%s", osmo_hexdump_nospc(result, rc));
+ printf("\n\n");
+
+ memset(&ri, 0, sizeof(ri));
+ memset(result, 0, sizeof(result));
+ ri.discr = BSSGP_RIM_ROUTING_INFO_EUTRAN;
+ ri.eutran.tai.mcc = 262;
+ ri.eutran.tai.mnc = 42;
+ ri.eutran.tai.mnc_3_digits = 0;
+ ri.eutran.tai.tac = 13200;
+ ri.eutran.global_enb_id[0] = 0x00;
+ ri.eutran.global_enb_id[1] = 0x51;
+ ri.eutran.global_enb_id[2] = 0xe1;
+ ri.eutran.global_enb_id_len = 3;
+ dump_rim_ri(&ri);
+ rc = bssgp_create_rim_ri(result, &ri);
+ printf("rc=%d, ", rc);
+ if (rc > 0)
+ printf("result=%s", osmo_hexdump_nospc(result, rc));
+ printf("\n\n");
+
+ printf("----- %s END\n", __func__);
+}
+
+void dump_bssgp_ran_inf_req_app_cont_nacc(struct bssgp_ran_inf_req_app_cont_nacc *app_cont)
+{
+ printf(" app_cont: bssgp_ran_inf_req_app_cont_nacc:\n");
+ printf(" reprt_cell.rai.lac.plmn.mcc = %u\n", app_cont->reprt_cell.rai.lac.plmn.mcc);
+ printf(" reprt_cell.rai.lac.plmn.mnc = %u\n", app_cont->reprt_cell.rai.lac.plmn.mnc);
+ printf(" reprt_cell.rai.lac.plmn.mnc_3_digits = %u\n", app_cont->reprt_cell.rai.lac.plmn.mnc_3_digits);
+ printf(" reprt_cell.rai.lac.lac = %u\n", app_cont->reprt_cell.rai.lac.lac);
+ printf(" reprt_cell.rai.rac = %u\n", app_cont->reprt_cell.rai.rac);
+ printf(" reprt_cell.cell_identity = %04x\n", app_cont->reprt_cell.cell_identity);
+}
+
+void dump_bssgp_ran_inf_req_rim_cont(struct bssgp_ran_inf_req_rim_cont *rim_cont)
+{
+ printf("bssgp_ran_inf_req_rim_cont:\n");
+ printf(" app_id = %02x\n", rim_cont->app_id);
+ printf(" seq_num = %08x\n", rim_cont->seq_num);
+ printf(" pdu_ind.ack_requested = %u\n", rim_cont->pdu_ind.ack_requested);
+ printf(" pdu_ind.pdu_type_ext = %u\n", rim_cont->pdu_ind.pdu_type_ext);
+ printf(" prot_ver = %u\n", rim_cont->prot_ver);
+ switch (rim_cont->app_id) {
+ case BSSGP_RAN_INF_APP_ID_NACC:
+ dump_bssgp_ran_inf_req_app_cont_nacc(&rim_cont->u.app_cont_nacc);
+ break;
+ case BSSGP_RAN_INF_APP_ID_SI3:
+ case BSSGP_RAN_INF_APP_ID_MBMS:
+ case BSSGP_RAN_INF_APP_ID_SON:
+ case BSSGP_RAN_INF_APP_ID_UTRA_SI:
+ printf(" app_cont: (not implemented yet)\n");
+ break;
+ default:
+ printf(" app_cont: (illegal application identifier)\n");
+ }
+ if (rim_cont->son_trans_app_id) {
+ printf(" son_trans_app_id: %s\n",
+ osmo_hexdump_nospc(rim_cont->son_trans_app_id, rim_cont->son_trans_app_id_len));
+ printf(" son_trans_app_id_len: %zu\n", rim_cont->son_trans_app_id_len);
+ }
+}
+
+static void test_bssgp_dec_ran_inf_req_rim_cont_nacc(void)
+{
+ int rc;
+ struct bssgp_ran_inf_req_rim_cont rim_cont_dec;
+ uint8_t testvec[] =
+ { 0x4b, 0x81, 0x01, 0x4c, 0x84, 0x00, 0x00, 0x00, 0x01, 0x4f, 0x81, 0x02, 0x55, 0x81, 0x01, 0x4d, 0x88,
+ 0x62, 0xf2, 0x24, 0x33, 0x90, 0x00, 0x51, 0xe1 };
+
+ printf("----- %s START\n", __func__);
+
+ rc = bssgp_dec_ran_inf_req_rim_cont(&rim_cont_dec, testvec, sizeof(testvec));
+ printf("rc=%d, ", rc);
+ if (rc == 0)
+ dump_bssgp_ran_inf_req_rim_cont(&rim_cont_dec);
+
+ printf("----- %s END\n", __func__);
+}
+
+static void test_bssgp_enc_ran_inf_req_rim_cont_nacc(void)
+{
+ int rc;
+ struct bssgp_ran_inf_req_rim_cont rim_cont = { };
+ uint8_t result[256];
+ printf("----- %s START\n", __func__);
+
+ rim_cont.app_id = BSSGP_RAN_INF_APP_ID_NACC;
+ rim_cont.seq_num = 1;
+ rim_cont.pdu_ind.ack_requested = 0;
+ rim_cont.pdu_ind.pdu_type_ext = 1;
+ rim_cont.prot_ver = 1;
+ rim_cont.son_trans_app_id = NULL;
+ rim_cont.son_trans_app_id_len = 0;
+ rim_cont.u.app_cont_nacc.reprt_cell.rai.lac.plmn.mcc = 262;
+ rim_cont.u.app_cont_nacc.reprt_cell.rai.lac.plmn.mnc = 42;
+ rim_cont.u.app_cont_nacc.reprt_cell.rai.lac.plmn.mnc_3_digits = 0;
+ rim_cont.u.app_cont_nacc.reprt_cell.rai.lac.lac = 13200;
+ rim_cont.u.app_cont_nacc.reprt_cell.rai.rac = 0;
+ rim_cont.u.app_cont_nacc.reprt_cell.cell_identity = 0x51e1;
+
+ dump_bssgp_ran_inf_req_rim_cont(&rim_cont);
+
+ rc = bssgp_enc_ran_inf_req_rim_cont(result, sizeof(result), &rim_cont);
+ printf("rc=%d, ", rc);
+ if (rc > 0)
+ printf("result=%s", osmo_hexdump_nospc(result, rc));
+ printf("\n");
+ printf("----- %s END\n", __func__);
+}
+
+static void dump_bssgp_ran_inf_app_cont_nacc(struct bssgp_ran_inf_app_cont_nacc *app_cont)
+{
+ unsigned int i;
+ unsigned int silen;
+ printf(" app_cont: bssgp_ran_inf_app_cont_nacc:\n");
+ printf(" reprt_cell.rai.lac.plmn.mcc = %u\n", app_cont->reprt_cell.rai.lac.plmn.mcc);
+ printf(" reprt_cell.rai.lac.plmn.mnc = %u\n", app_cont->reprt_cell.rai.lac.plmn.mnc);
+ printf(" reprt_cell.rai.lac.plmn.mnc_3_digits = %u\n", app_cont->reprt_cell.rai.lac.plmn.mnc_3_digits);
+ printf(" reprt_cell.rai.lac.lac = %u\n", app_cont->reprt_cell.rai.lac.lac);
+ printf(" reprt_cell.rai.rac = %u\n", app_cont->reprt_cell.rai.rac);
+ printf(" reprt_cell.cell_identity = %04x\n", app_cont->reprt_cell.cell_identity);
+ printf(" type_psi = %u\n", app_cont->type_psi);
+ printf(" num_si = %u\n", app_cont->num_si);
+
+ if (app_cont->type_psi)
+ silen = 22;
+ else
+ silen = 21;
+
+ for (i = 0; i < app_cont->num_si; i++)
+ printf(" si[%u] = %s\n", i, osmo_hexdump_nospc(app_cont->si[i], silen));
+}
+
+static void dump_bssgp_app_err_cont_nacc(struct bssgp_app_err_cont_nacc *app_cont)
+{
+ printf(" app_err_cont: bssgp_app_err_cont_nacc:\n");
+ printf(" macc_cause = %02x\n", app_cont->nacc_cause);
+ if (app_cont->err_app_cont) {
+ printf(" err_app_cont: %s\n", osmo_hexdump_nospc(app_cont->err_app_cont, app_cont->err_app_cont_len));
+ printf(" err_app_cont_len: %zu\n", app_cont->err_app_cont_len);
+ }
+}
+
+static void dump_bssgp_ran_inf_rim_cont(struct bssgp_ran_inf_rim_cont *rim_cont)
+{
+ printf("bssgp_ran_inf_rim_cont:\n");
+ printf(" app_id = %02x\n", rim_cont->app_id);
+ printf(" seq_num = %08x\n", rim_cont->seq_num);
+ printf(" pdu_ind.ack_requested = %u\n", rim_cont->pdu_ind.ack_requested);
+ printf(" pdu_ind.pdu_type_ext = %u\n", rim_cont->pdu_ind.pdu_type_ext);
+ printf(" prot_ver = %u\n", rim_cont->prot_ver);
+ printf(" app_err = %u\n", rim_cont->app_err);
+ if (rim_cont->app_err) {
+ switch (rim_cont->app_id) {
+ case BSSGP_RAN_INF_APP_ID_NACC:
+ dump_bssgp_app_err_cont_nacc(&rim_cont->u.app_err_cont_nacc);
+ break;
+ case BSSGP_RAN_INF_APP_ID_SI3:
+ case BSSGP_RAN_INF_APP_ID_MBMS:
+ case BSSGP_RAN_INF_APP_ID_SON:
+ case BSSGP_RAN_INF_APP_ID_UTRA_SI:
+ printf(" app_err_cont: (not implemented yet)\n");
+ break;
+ default:
+ printf(" app_err_cont: (illegal application identifier)\n");
+ }
+ } else {
+ switch (rim_cont->app_id) {
+ case BSSGP_RAN_INF_APP_ID_NACC:
+ dump_bssgp_ran_inf_app_cont_nacc(&rim_cont->u.app_cont_nacc);
+ break;
+ case BSSGP_RAN_INF_APP_ID_SI3:
+ case BSSGP_RAN_INF_APP_ID_MBMS:
+ case BSSGP_RAN_INF_APP_ID_SON:
+ case BSSGP_RAN_INF_APP_ID_UTRA_SI:
+ printf(" app_cont: (not implemented yet)\n");
+ break;
+ default:
+ printf(" app_cont: (illegal application identifier)\n");
+ }
+ }
+ if (rim_cont->son_trans_app_id) {
+ printf(" son_trans_app_id: %s\n",
+ osmo_hexdump_nospc(rim_cont->son_trans_app_id, rim_cont->son_trans_app_id_len));
+ printf(" son_trans_app_id_len: %zu\n", rim_cont->son_trans_app_id_len);
+ }
+}
+
+static void test_bssgp_dec_ran_inf_rim_cont_nacc(void)
+{
+ int rc;
+ struct bssgp_ran_inf_rim_cont rim_cont_dec;
+ uint8_t testvec[] =
+ { 0x4b, 0x81, 0x01, 0x4c, 0x84, 0x00, 0x00, 0x00, 0x02, 0x4f, 0x81, 0x02, 0x55, 0x81, 0x01, 0x4e, 0xc8,
+ 0x62, 0xf2, 0x24, 0x33, 0x4f, 0x00, 0x51, 0xe0, 0x06, 0x19, 0x8f, 0xb1, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x79, 0x00, 0x00, 0x2b, 0x1b, 0x75, 0x30, 0x00, 0xf1, 0x10,
+ 0x23, 0x6e,
+ 0xc9, 0x03, 0x3c, 0x27, 0x47, 0x40, 0x79, 0x00, 0x00, 0x3c, 0x0b, 0x2b, 0x2b, 0x00, 0x90, 0x00, 0x18,
+ 0x5a, 0x6f,
+ 0xc9, 0xe0, 0x84, 0x10, 0xab, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b
+ };
+
+ printf("----- %s START\n", __func__);
+
+ rc = bssgp_dec_ran_inf_rim_cont(&rim_cont_dec, testvec, sizeof(testvec));
+ printf("rc=%d, ", rc);
+ if (rc == 0)
+ dump_bssgp_ran_inf_rim_cont(&rim_cont_dec);
+
+ printf("----- %s END\n", __func__);
+}
+
+static void test_bssgp_dec_ran_inf_rim_cont_err_nacc(void)
+{
+ int rc;
+ struct bssgp_ran_inf_rim_cont rim_cont_dec;
+ uint8_t testvec[] =
+ { 0x4b, 0x81, 0x01, 0x4c, 0x84, 0x00, 0x00, 0x00, 0x01, 0x4f, 0x81, 0x02, 0x55, 0x81, 0x01, 0x56, 0x86,
+ 0x01, 0xaa, 0xbb, 0xcc, 0xdd, 0xee };
+
+ printf("----- %s START\n", __func__);
+
+ rc = bssgp_dec_ran_inf_rim_cont(&rim_cont_dec, testvec, sizeof(testvec));
+ printf("rc=%d, ", rc);
+ if (rc == 0)
+ dump_bssgp_ran_inf_rim_cont(&rim_cont_dec);
+
+ printf("----- %s END\n", __func__);
+}
+
+static void test_bssgp_enc_ran_inf_rim_cont_nacc(void)
+{
+ int rc;
+ struct bssgp_ran_inf_rim_cont rim_cont = { };
+
+ uint8_t si1[] =
+ { 0x19, 0x8f, 0xb1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x79, 0x00, 0x00, 0x2b
+ };
+ uint8_t si3[] =
+ { 0x1b, 0x75, 0x30, 0x00, 0xf1, 0x10, 0x23, 0x6e, 0xc9, 0x03, 0x3c, 0x27, 0x47, 0x40, 0x79, 0x00, 0x00,
+ 0x3c, 0x0b, 0x2b, 0x2b
+ };
+ uint8_t si13[] =
+ { 0x00, 0x90, 0x00, 0x18, 0x5a, 0x6f, 0xc9, 0xe0, 0x84, 0x10, 0xab, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b,
+ 0x2b, 0x2b, 0x2b, 0x2b
+ };
+
+ uint8_t result[256];
+ printf("----- %s START\n", __func__);
+
+ rim_cont.app_id = BSSGP_RAN_INF_APP_ID_NACC;
+ rim_cont.seq_num = 1;
+ rim_cont.pdu_ind.ack_requested = 0;
+ rim_cont.pdu_ind.pdu_type_ext = 1;
+ rim_cont.prot_ver = 1;
+ rim_cont.son_trans_app_id = NULL;
+ rim_cont.son_trans_app_id_len = 0;
+ rim_cont.app_err = false;
+ rim_cont.u.app_cont_nacc.reprt_cell.rai.lac.plmn.mcc = 262;
+ rim_cont.u.app_cont_nacc.reprt_cell.rai.lac.plmn.mnc = 42;
+ rim_cont.u.app_cont_nacc.reprt_cell.rai.lac.plmn.mnc_3_digits = 0;
+ rim_cont.u.app_cont_nacc.reprt_cell.rai.lac.lac = 13135;
+ rim_cont.u.app_cont_nacc.reprt_cell.rai.rac = 0;
+ rim_cont.u.app_cont_nacc.reprt_cell.cell_identity = 0x51e0;
+ rim_cont.u.app_cont_nacc.type_psi = 0;
+ rim_cont.u.app_cont_nacc.num_si = 3;
+ rim_cont.u.app_cont_nacc.si[0] = si1;
+ rim_cont.u.app_cont_nacc.si[1] = si3;
+ rim_cont.u.app_cont_nacc.si[2] = si13;
+
+ dump_bssgp_ran_inf_rim_cont(&rim_cont);
+
+ rc = bssgp_enc_ran_inf_rim_cont(result, sizeof(result), &rim_cont);
+ printf("rc=%d, ", rc);
+ if (rc > 0)
+ printf("result=%s", osmo_hexdump_nospc(result, rc));
+ printf("\n");
+ printf("----- %s END\n", __func__);
+}
+
+static void test_bssgp_enc_ran_inf_rim_cont_err_nacc(void)
+{
+ int rc;
+ struct bssgp_ran_inf_rim_cont rim_cont = { };
+ uint8_t err_app_cont[] = { 0xaa, 0xbb, 0xcc, 0xdd, 0xee };
+
+ uint8_t result[256];
+ printf("----- %s START\n", __func__);
+
+ rim_cont.app_id = BSSGP_RAN_INF_APP_ID_NACC;
+ rim_cont.seq_num = 1;
+ rim_cont.pdu_ind.ack_requested = 0;
+ rim_cont.pdu_ind.pdu_type_ext = 1;
+ rim_cont.prot_ver = 1;
+ rim_cont.son_trans_app_id = NULL;
+ rim_cont.son_trans_app_id_len = 0;
+ rim_cont.app_err = true;
+ rim_cont.u.app_err_cont_nacc.nacc_cause = BSSGP_NACC_CAUSE_SYNTAX_ERR;
+ rim_cont.u.app_err_cont_nacc.err_app_cont = err_app_cont;
+ rim_cont.u.app_err_cont_nacc.err_app_cont_len = sizeof(err_app_cont);
+ dump_bssgp_ran_inf_rim_cont(&rim_cont);
+
+ rc = bssgp_enc_ran_inf_rim_cont(result, sizeof(result), &rim_cont);
+ printf("rc=%d, ", rc);
+ if (rc > 0)
+ printf("result=%s", osmo_hexdump_nospc(result, rc));
+ printf("\n");
+ printf("----- %s END\n", __func__);
+}
+
+static void dump_bssgp_ran_inf_ack_rim_cont(struct bssgp_ran_inf_ack_rim_cont *rim_cont)
+{
+ printf("bssgp_ran_inf_ack_rim_cont:\n");
+ printf(" app_id = %02x\n", rim_cont->app_id);
+ printf(" seq_num = %08x\n", rim_cont->seq_num);
+ printf(" prot_ver = %u\n", rim_cont->prot_ver);
+ if (rim_cont->son_trans_app_id) {
+ printf(" son_trans_app_id: %s\n",
+ osmo_hexdump_nospc(rim_cont->son_trans_app_id, rim_cont->son_trans_app_id_len));
+ printf(" son_trans_app_id_len: %zu\n", rim_cont->son_trans_app_id_len);
+ }
+}
+
+static void test_bssgp_dec_ran_inf_ack_rim_cont(void)
+{
+ int rc;
+ struct bssgp_ran_inf_ack_rim_cont rim_cont_dec;
+ uint8_t testvec[] = { 0x4b, 0x81, 0x01, 0x4c, 0x84, 0x00, 0x00, 0x00, 0x01, 0x55, 0x81, 0x01 };
+
+ printf("----- %s START\n", __func__);
+
+ rc = bssgp_dec_ran_inf_ack_rim_cont(&rim_cont_dec, testvec, sizeof(testvec));
+ printf("rc=%d, ", rc);
+ if (rc == 0)
+ dump_bssgp_ran_inf_ack_rim_cont(&rim_cont_dec);
+
+ printf("----- %s END\n", __func__);
+}
+
+static void test_bssgp_enc_ran_inf_ack_rim_cont(void)
+{
+ int rc;
+ struct bssgp_ran_inf_ack_rim_cont rim_cont = { };
+ uint8_t result[256];
+ printf("----- %s START\n", __func__);
+
+ rim_cont.app_id = BSSGP_RAN_INF_APP_ID_NACC;
+ rim_cont.seq_num = 1;
+ rim_cont.prot_ver = 1;
+ rim_cont.son_trans_app_id = NULL;
+ rim_cont.son_trans_app_id_len = 0;
+ dump_bssgp_ran_inf_ack_rim_cont(&rim_cont);
+
+ rc = bssgp_enc_ran_inf_ack_rim_cont(result, sizeof(result), &rim_cont);
+ printf("rc=%d, ", rc);
+ if (rc > 0)
+ printf("result=%s", osmo_hexdump_nospc(result, rc));
+ printf("\n");
+ printf("----- %s END\n", __func__);
+}
+
+void dump_bssgp_ran_inf_err_rim_cont(struct bssgp_ran_inf_err_rim_cont *rim_cont)
+{
+ printf("bssgp_ran_inf_err_rim_cont:\n");
+ printf(" app_id = %02x\n", rim_cont->app_id);
+ printf(" cause = %02x\n", rim_cont->cause);
+ printf(" prot_ver = %u\n", rim_cont->prot_ver);
+ if (rim_cont->err_pdu) {
+ printf(" err_pdu: %s\n", osmo_hexdump_nospc(rim_cont->err_pdu, rim_cont->err_pdu_len));
+ printf(" err_pdu_len: %zu\n", rim_cont->err_pdu_len);
+ }
+ if (rim_cont->son_trans_app_id) {
+ printf(" son_trans_app_id: %s\n",
+ osmo_hexdump_nospc(rim_cont->son_trans_app_id, rim_cont->son_trans_app_id_len));
+ printf(" son_trans_app_id_len: %zu\n", rim_cont->son_trans_app_id_len);
+ }
+}
+
+static void test_bssgp_dec_ran_inf_err_rim_cont(void)
+{
+ int rc;
+ struct bssgp_ran_inf_err_rim_cont rim_cont_dec;
+ uint8_t testvec[] =
+ { 0x4b, 0x81, 0x17, 0x07, 0x81, 0x2b, 0x55, 0x81, 0x01, 0x15, 0x85, 0xaa, 0xbb, 0xcc, 0xdd, 0xee };
+
+ printf("----- %s START\n", __func__);
+
+ rc = bssgp_dec_ran_inf_err_rim_cont(&rim_cont_dec, testvec, sizeof(testvec));
+ printf("rc=%d, ", rc);
+ if (rc == 0)
+ dump_bssgp_ran_inf_err_rim_cont(&rim_cont_dec);
+
+ printf("----- %s END\n", __func__);
+}
+
+static void test_bssgp_enc_ran_inf_err_rim_cont(void)
+{
+ int rc;
+ struct bssgp_ran_inf_err_rim_cont rim_cont = { };
+ uint8_t err_pdu[] = { 0xaa, 0xbb, 0xcc, 0xdd, 0xee };
+ uint8_t result[256];
+ printf("----- %s START\n", __func__);
+
+ rim_cont.app_id = 23;
+ rim_cont.cause = 0x2b;
+ rim_cont.prot_ver = 1;
+ rim_cont.err_pdu = err_pdu;
+ rim_cont.err_pdu_len = sizeof(err_pdu);
+ rim_cont.son_trans_app_id = NULL;
+ rim_cont.son_trans_app_id_len = 0;
+ dump_bssgp_ran_inf_err_rim_cont(&rim_cont);
+
+ rc = bssgp_enc_ran_inf_err_rim_cont(result, sizeof(result), &rim_cont);
+ printf("rc=%d, ", rc);
+ if (rc > 0)
+ printf("result=%s", osmo_hexdump_nospc(result, rc));
+ printf("\n");
+ printf("----- %s END\n", __func__);
+}
+
+void dump_bssgp_ran_inf_app_err_rim_cont(struct bssgp_ran_inf_app_err_rim_cont *rim_cont)
+{
+ printf("bssgp_ran_inf_app_err_rim_cont:\n");
+ printf(" app_id = %02x\n", rim_cont->app_id);
+ printf(" seq_num = %08x\n", rim_cont->seq_num);
+ printf(" pdu_ind.ack_requested = %u\n", rim_cont->pdu_ind.ack_requested);
+ printf(" pdu_ind.pdu_type_ext = %u\n", rim_cont->pdu_ind.pdu_type_ext);
+ printf(" prot_ver = %u\n", rim_cont->prot_ver);
+ switch (rim_cont->app_id) {
+ case BSSGP_RAN_INF_APP_ID_NACC:
+ dump_bssgp_app_err_cont_nacc(&rim_cont->u.app_err_cont_nacc);
+ break;
+ case BSSGP_RAN_INF_APP_ID_SI3:
+ case BSSGP_RAN_INF_APP_ID_MBMS:
+ case BSSGP_RAN_INF_APP_ID_SON:
+ case BSSGP_RAN_INF_APP_ID_UTRA_SI:
+ printf(" app_err_cont: (not implemented yet)\n");
+ break;
+ default:
+ printf(" app_err_cont: (illegal application identifier)\n");
+ }
+}
+
+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;
+ uint8_t testvec[] =
+ { 0x4b, 0x81, 0x01, 0x4c, 0x84, 0x00, 0x00, 0x00, 0x01, 0x4f, 0x81, 0x02, 0x55, 0x81, 0x01, 0x56, 0x85,
+ 0xaa, 0xbb, 0xcc, 0xdd, 0xee
+ };
+
+ printf("----- %s START\n", __func__);
+
+ rc = bssgp_dec_ran_inf_app_err_rim_cont(&rim_cont_dec, testvec, sizeof(testvec));
+ printf("rc=%d, ", rc);
+ if (rc == 0)
+ dump_bssgp_ran_inf_app_err_rim_cont(&rim_cont_dec);
+
+ printf("----- %s END\n", __func__);
+}
+
+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 = { };
+ uint8_t err_app_cont[] = { 0xaa, 0xbb, 0xcc, 0xdd, 0xee };
+ uint8_t result[256];
+
+ printf("----- %s START\n", __func__);
+ rim_cont.app_id = BSSGP_RAN_INF_APP_ID_NACC;
+ rim_cont.seq_num = 1;
+ rim_cont.pdu_ind.ack_requested = 0;
+ rim_cont.pdu_ind.pdu_type_ext = 1;
+ rim_cont.prot_ver = 1;
+ rim_cont.u.app_err_cont_nacc.nacc_cause = BSSGP_NACC_CAUSE_SYNTAX_ERR;
+ rim_cont.u.app_err_cont_nacc.err_app_cont = err_app_cont;
+ rim_cont.u.app_err_cont_nacc.err_app_cont_len = sizeof(err_app_cont);
+ dump_bssgp_ran_inf_app_err_rim_cont(&rim_cont);
+
+ rc = bssgp_enc_ran_inf_app_err_rim_cont(result, sizeof(result), &rim_cont);
+ printf("rc=%d, ", rc);
+ if (rc > 0)
+ printf("result=%s", osmo_hexdump_nospc(result, rc));
+ printf("\n");
+ printf("----- %s END\n", __func__);
+}
+
+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;
+ uint8_t testvec[] = { 0x62, 0xf2, 0x24, 0x33, 0x90, 0x00, 0x51, 0xe1 };
+
+ printf("----- %s START\n", __func__);
+
+ rc = bssgp_dec_ran_inf_req_app_cont_nacc(&app_cont_dec, testvec, sizeof(testvec));
+ printf("rc=%d, ", rc);
+ if (rc == 0)
+ dump_bssgp_ran_inf_req_app_cont_nacc(&app_cont_dec);
+
+ printf("----- %s END\n", __func__);
+}
+
+static void test_bssgp_enc_ran_inf_req_app_cont_nacc(void)
+{
+ int rc;
+ struct bssgp_ran_inf_req_app_cont_nacc app_cont = { };
+ uint8_t result[256];
+ printf("----- %s START\n", __func__);
+
+ app_cont.reprt_cell.rai.lac.plmn.mcc = 262;
+ app_cont.reprt_cell.rai.lac.plmn.mnc = 42;
+ app_cont.reprt_cell.rai.lac.plmn.mnc_3_digits = 0;
+ app_cont.reprt_cell.rai.lac.lac = 13200;
+ app_cont.reprt_cell.rai.rac = 0;
+ app_cont.reprt_cell.cell_identity = 0x51e1;
+ dump_bssgp_ran_inf_req_app_cont_nacc(&app_cont);
+
+ rc = bssgp_enc_ran_inf_req_app_cont_nacc(result, sizeof(result), &app_cont);
+ printf("rc=%d, ", rc);
+ if (rc > 0)
+ printf("result=%s", osmo_hexdump_nospc(result, rc));
+ printf("\n");
+ printf("----- %s END\n", __func__);
+}
+
+static void test_bssgp_dec_ran_inf_app_cont_nacc(void)
+{
+ int rc;
+ struct bssgp_ran_inf_app_cont_nacc app_cont_dec;
+ uint8_t testvec[] =
+ { 0x62, 0xf2, 0x24, 0x33, 0x4f, 0x00, 0x51, 0xe0, 0x06, 0x19, 0x8f, 0xb1, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x79, 0x00, 0x00, 0x2b, 0x1b, 0x75, 0x30, 0x00, 0xf1, 0x10,
+ 0x23, 0x6e, 0xc9, 0x03, 0x3c, 0x27, 0x47, 0x40, 0x79, 0x00, 0x00, 0x3c, 0x0b, 0x2b, 0x2b, 0x00, 0x90, 0x00, 0x18,
+ 0x5a, 0x6f, 0xc9, 0xe0, 0x84, 0x10, 0xab, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b };
+
+ printf("----- %s START\n", __func__);
+
+ rc = bssgp_dec_ran_inf_app_cont_nacc(&app_cont_dec, testvec, sizeof(testvec));
+ printf("rc=%d, ", rc);
+ if (rc == 0)
+ dump_bssgp_ran_inf_app_cont_nacc(&app_cont_dec);
+
+ printf("----- %s END\n", __func__);
+}
+
+static void test_bssgp_enc_ran_inf_app_cont_nacc(void)
+{
+ int rc;
+ struct bssgp_ran_inf_app_cont_nacc app_cont = { };
+
+ uint8_t si1[] =
+ { 0x19, 0x8f, 0xb1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x79, 0x00, 0x00, 0x2b };
+ uint8_t si3[] =
+ { 0x1b, 0x75, 0x30, 0x00, 0xf1, 0x10, 0x23, 0x6e, 0xc9, 0x03, 0x3c, 0x27, 0x47, 0x40, 0x79, 0x00, 0x00,
+ 0x3c, 0x0b, 0x2b, 0x2b };
+ uint8_t si13[] =
+ { 0x00, 0x90, 0x00, 0x18, 0x5a, 0x6f, 0xc9, 0xe0, 0x84, 0x10, 0xab, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b,
+ 0x2b, 0x2b, 0x2b, 0x2b };
+
+ uint8_t result[256];
+ printf("----- %s START\n", __func__);
+
+ app_cont.reprt_cell.rai.lac.plmn.mcc = 262;
+ app_cont.reprt_cell.rai.lac.plmn.mnc = 42;
+ app_cont.reprt_cell.rai.lac.plmn.mnc_3_digits = 0;
+ app_cont.reprt_cell.rai.lac.lac = 13135;
+ app_cont.reprt_cell.rai.rac = 0;
+ app_cont.reprt_cell.cell_identity = 0x51e1;
+ app_cont.type_psi = false;
+ app_cont.num_si = 3;
+ app_cont.si[0] = si1;
+ app_cont.si[1] = si3;
+ app_cont.si[2] = si13;
+ dump_bssgp_ran_inf_app_cont_nacc(&app_cont);
+
+ rc = bssgp_enc_ran_inf_app_cont_nacc(result, sizeof(result), &app_cont);
+ printf("rc=%d, ", rc);
+ if (rc > 0)
+ printf("result=%s", osmo_hexdump_nospc(result, rc));
+ printf("\n");
+ printf("----- %s END\n", __func__);
+}
+
+static void test_bssgp_dec_app_err_cont_nacc(void)
+{
+ int rc;
+ struct bssgp_app_err_cont_nacc app_cont_dec;
+ uint8_t testvec[] = { 0x01, 0xaa, 0xbb, 0xcc, 0xdd, 0xee };
+
+ printf("----- %s START\n", __func__);
+
+ rc = bssgp_dec_app_err_cont_nacc(&app_cont_dec, testvec, sizeof(testvec));
+ printf("rc=%d, ", rc);
+ if (rc == 0)
+ dump_bssgp_app_err_cont_nacc(&app_cont_dec);
+
+ printf("----- %s END\n", __func__);
+}
+
+static void test_bssgp_enc_app_err_cont_nacc(void)
+{
+ int rc;
+ struct bssgp_app_err_cont_nacc app_cont = { };
+ uint8_t err_app_cont[] = { 0xaa, 0xbb, 0xcc, 0xdd, 0xee };
+ uint8_t result[256];
+ printf("----- %s START\n", __func__);
+
+ app_cont.nacc_cause = BSSGP_NACC_CAUSE_SYNTAX_ERR;
+ app_cont.err_app_cont = err_app_cont;
+ app_cont.err_app_cont_len = sizeof(err_app_cont);
+ dump_bssgp_app_err_cont_nacc(&app_cont);
+
+ rc = bssgp_enc_app_err_cont_nacc(result, sizeof(result), &app_cont);
+ printf("rc=%d, ", rc);
+ if (rc > 0)
+ printf("result=%s", osmo_hexdump_nospc(result, rc));
+ printf("\n");
+ printf("----- %s END\n", __func__);
+}
+
+int bssgp_prim_cb(struct osmo_prim_hdr *oph, void *ctx)
+{
+ return 0;
+}
+
+int main(int argc, char **argv)
+{
+ printf("===== BSSGP RIM test START\n");
+
+ /* RIM routing information */
+ test_bssgp_parse_rim_ri();
+ test_bssgp_create_rim_ri();
+
+ /* RIM containers */
+ test_bssgp_dec_ran_inf_req_rim_cont_nacc();
+ test_bssgp_enc_ran_inf_req_rim_cont_nacc();
+ test_bssgp_dec_ran_inf_rim_cont_nacc();
+ test_bssgp_dec_ran_inf_rim_cont_err_nacc();
+ test_bssgp_enc_ran_inf_rim_cont_nacc();
+ test_bssgp_enc_ran_inf_rim_cont_err_nacc();
+ test_bssgp_dec_ran_inf_ack_rim_cont();
+ test_bssgp_enc_ran_inf_ack_rim_cont();
+ test_bssgp_dec_ran_inf_err_rim_cont();
+ test_bssgp_enc_ran_inf_err_rim_cont();
+ test_bssgp_dec_ran_inf_app_err_rim_cont_nacc();
+ test_bssgp_enc_ran_inf_app_err_rim_cont_nacc();
+
+ /* Application containers */
+ test_bssgp_dec_ran_inf_req_app_cont_nacc();
+ test_bssgp_enc_ran_inf_req_app_cont_nacc();
+ test_bssgp_dec_ran_inf_app_cont_nacc();
+ test_bssgp_enc_ran_inf_app_cont_nacc();
+ test_bssgp_dec_app_err_cont_nacc();
+ test_bssgp_enc_app_err_cont_nacc();
+
+ printf("===== BSSGP RIM test END\n\n");
+
+ exit(EXIT_SUCCESS);
+}
diff --git a/tests/gb/gprs_bssgp_rim_test.ok b/tests/gb/gprs_bssgp_rim_test.ok
new file mode 100644
index 00000000..df5a41df
--- /dev/null
+++ b/tests/gb/gprs_bssgp_rim_test.ok
@@ -0,0 +1,276 @@
+===== BSSGP RIM test START
+----- test_bssgp_parse_rim_ri START
+rc=9
+GERAN cell identifier
+ * mcc: 262
+ mnc: 42
+ mnc 3 digits: 0
+ lac: 13200
+ rac: 0
+ * cell id: 51e1
+
+rc=9
+UTRAN RNC identifier
+ * mcc: 262
+ mnc: 42
+ mnc 3 digits: 0
+ lac: 13200
+ rac: 0
+ * rnc id: 51e1
+
+rc=9
+EUTRAN eNB identifier
+ * mcc: 262
+ mnc: 42
+ mnc 3 digits: 0
+ tac: 13200
+ * global_enb_id: 0051e1
+
+----- test_bssgp_parse_rim_ri END
+----- test_bssgp_create_rim_ri START
+GERAN cell identifier
+ * mcc: 262
+ mnc: 42
+ mnc 3 digits: 0
+ lac: 13200
+ rac: 0
+ * cell id: 51e1
+rc=9, result=0062f22433900051e1
+
+UTRAN RNC identifier
+ * mcc: 262
+ mnc: 42
+ mnc 3 digits: 0
+ lac: 13200
+ rac: 0
+ * rnc id: 51e1
+rc=9, result=0162f22433900051e1
+
+EUTRAN eNB identifier
+ * mcc: 262
+ mnc: 42
+ mnc 3 digits: 0
+ tac: 13200
+ * global_enb_id: 0051e1
+rc=9, result=0262f22433900051e1
+
+----- test_bssgp_create_rim_ri END
+----- test_bssgp_dec_ran_inf_req_rim_cont_nacc START
+rc=0, bssgp_ran_inf_req_rim_cont:
+ app_id = 01
+ seq_num = 00000001
+ pdu_ind.ack_requested = 0
+ pdu_ind.pdu_type_ext = 1
+ prot_ver = 1
+ app_cont: bssgp_ran_inf_req_app_cont_nacc:
+ reprt_cell.rai.lac.plmn.mcc = 262
+ reprt_cell.rai.lac.plmn.mnc = 42
+ reprt_cell.rai.lac.plmn.mnc_3_digits = 0
+ reprt_cell.rai.lac.lac = 13200
+ reprt_cell.rai.rac = 0
+ reprt_cell.cell_identity = 51e1
+----- test_bssgp_dec_ran_inf_req_rim_cont_nacc END
+----- test_bssgp_enc_ran_inf_req_rim_cont_nacc START
+bssgp_ran_inf_req_rim_cont:
+ app_id = 01
+ seq_num = 00000001
+ pdu_ind.ack_requested = 0
+ pdu_ind.pdu_type_ext = 1
+ prot_ver = 1
+ app_cont: bssgp_ran_inf_req_app_cont_nacc:
+ reprt_cell.rai.lac.plmn.mcc = 262
+ reprt_cell.rai.lac.plmn.mnc = 42
+ reprt_cell.rai.lac.plmn.mnc_3_digits = 0
+ reprt_cell.rai.lac.lac = 13200
+ reprt_cell.rai.rac = 0
+ reprt_cell.cell_identity = 51e1
+rc=25, result=4b81014c84000000014f81025581014d8862f22433900051e1
+----- test_bssgp_enc_ran_inf_req_rim_cont_nacc END
+----- test_bssgp_dec_ran_inf_rim_cont_nacc START
+rc=0, bssgp_ran_inf_rim_cont:
+ app_id = 01
+ seq_num = 00000002
+ pdu_ind.ack_requested = 0
+ pdu_ind.pdu_type_ext = 1
+ prot_ver = 1
+ app_err = 0
+ app_cont: bssgp_ran_inf_app_cont_nacc:
+ reprt_cell.rai.lac.plmn.mcc = 262
+ reprt_cell.rai.lac.plmn.mnc = 42
+ reprt_cell.rai.lac.plmn.mnc_3_digits = 0
+ reprt_cell.rai.lac.lac = 13135
+ reprt_cell.rai.rac = 0
+ reprt_cell.cell_identity = 51e0
+ type_psi = 0
+ num_si = 3
+ si[0] = 198fb100000000000000000000000000007900002b
+ si[1] = 1b753000f110236ec9033c2747407900003c0b2b2b
+ si[2] = 009000185a6fc9e08410ab2b2b2b2b2b2b2b2b2b2b
+----- test_bssgp_dec_ran_inf_rim_cont_nacc END
+----- test_bssgp_dec_ran_inf_rim_cont_err_nacc START
+rc=0, bssgp_ran_inf_rim_cont:
+ app_id = 01
+ seq_num = 00000001
+ pdu_ind.ack_requested = 0
+ pdu_ind.pdu_type_ext = 1
+ prot_ver = 1
+ app_err = 1
+ app_err_cont: bssgp_app_err_cont_nacc:
+ macc_cause = 01
+ err_app_cont: aabbccddee
+ err_app_cont_len: 5
+----- test_bssgp_dec_ran_inf_rim_cont_err_nacc END
+----- test_bssgp_enc_ran_inf_rim_cont_nacc START
+bssgp_ran_inf_rim_cont:
+ app_id = 01
+ seq_num = 00000001
+ pdu_ind.ack_requested = 0
+ pdu_ind.pdu_type_ext = 1
+ prot_ver = 1
+ app_err = 0
+ app_cont: bssgp_ran_inf_app_cont_nacc:
+ reprt_cell.rai.lac.plmn.mcc = 262
+ reprt_cell.rai.lac.plmn.mnc = 42
+ reprt_cell.rai.lac.plmn.mnc_3_digits = 0
+ reprt_cell.rai.lac.lac = 13135
+ reprt_cell.rai.rac = 0
+ reprt_cell.cell_identity = 51e0
+ type_psi = 0
+ num_si = 3
+ si[0] = 198fb100000000000000000000000000007900002b
+ si[1] = 1b753000f110236ec9033c2747407900003c0b2b2b
+ si[2] = 009000185a6fc9e08410ab2b2b2b2b2b2b2b2b2b2b
+rc=89, result=4b81014c84000000014f81025581014ec862f224334f0051e006198fb100000000000000000000000000007900002b1b753000f110236ec9033c2747407900003c0b2b2b009000185a6fc9e08410ab2b2b2b2b2b2b2b2b2b2b
+----- test_bssgp_enc_ran_inf_rim_cont_nacc END
+----- test_bssgp_enc_ran_inf_rim_cont_err_nacc START
+bssgp_ran_inf_rim_cont:
+ app_id = 01
+ seq_num = 00000001
+ pdu_ind.ack_requested = 0
+ pdu_ind.pdu_type_ext = 1
+ prot_ver = 1
+ app_err = 1
+ app_err_cont: bssgp_app_err_cont_nacc:
+ macc_cause = 01
+ err_app_cont: aabbccddee
+ err_app_cont_len: 5
+rc=23, result=4b81014c84000000014f8102558101568601aabbccddee
+----- test_bssgp_enc_ran_inf_rim_cont_err_nacc END
+----- test_bssgp_dec_ran_inf_ack_rim_cont START
+rc=0, bssgp_ran_inf_ack_rim_cont:
+ app_id = 01
+ seq_num = 00000001
+ prot_ver = 1
+----- test_bssgp_dec_ran_inf_ack_rim_cont END
+----- test_bssgp_enc_ran_inf_ack_rim_cont START
+bssgp_ran_inf_ack_rim_cont:
+ app_id = 01
+ seq_num = 00000001
+ prot_ver = 1
+rc=12, result=4b81014c8400000001558101
+----- test_bssgp_enc_ran_inf_ack_rim_cont END
+----- test_bssgp_dec_ran_inf_err_rim_cont START
+rc=0, bssgp_ran_inf_err_rim_cont:
+ app_id = 17
+ cause = 2b
+ prot_ver = 1
+ err_pdu: aabbccddee
+ err_pdu_len: 5
+----- test_bssgp_dec_ran_inf_err_rim_cont END
+----- test_bssgp_enc_ran_inf_err_rim_cont START
+bssgp_ran_inf_err_rim_cont:
+ app_id = 17
+ cause = 2b
+ prot_ver = 1
+ err_pdu: aabbccddee
+ err_pdu_len: 5
+rc=16, result=4b811707812b5581011585aabbccddee
+----- test_bssgp_enc_ran_inf_err_rim_cont END
+----- test_bssgp_dec_ran_inf_app_err_rim_cont_nacc START
+rc=0, bssgp_ran_inf_app_err_rim_cont:
+ app_id = 01
+ seq_num = 00000001
+ pdu_ind.ack_requested = 0
+ pdu_ind.pdu_type_ext = 1
+ prot_ver = 1
+ app_err_cont: bssgp_app_err_cont_nacc:
+ macc_cause = aa
+ err_app_cont: bbccddee
+ err_app_cont_len: 4
+----- test_bssgp_dec_ran_inf_app_err_rim_cont_nacc END
+----- test_bssgp_enc_ran_inf_app_err_rim_cont_nacc START
+bssgp_ran_inf_app_err_rim_cont:
+ app_id = 01
+ seq_num = 00000001
+ pdu_ind.ack_requested = 0
+ pdu_ind.pdu_type_ext = 1
+ prot_ver = 1
+ app_err_cont: bssgp_app_err_cont_nacc:
+ macc_cause = 01
+ err_app_cont: aabbccddee
+ err_app_cont_len: 5
+rc=23, result=4b81014c84000000014f8102558101568601aabbccddee
+----- test_bssgp_enc_ran_inf_app_err_rim_cont_nacc END
+----- test_bssgp_dec_ran_inf_req_app_cont_nacc START
+rc=0, app_cont: bssgp_ran_inf_req_app_cont_nacc:
+ reprt_cell.rai.lac.plmn.mcc = 262
+ reprt_cell.rai.lac.plmn.mnc = 42
+ reprt_cell.rai.lac.plmn.mnc_3_digits = 0
+ reprt_cell.rai.lac.lac = 13200
+ reprt_cell.rai.rac = 0
+ reprt_cell.cell_identity = 51e1
+----- test_bssgp_dec_ran_inf_req_app_cont_nacc END
+----- test_bssgp_enc_ran_inf_req_app_cont_nacc START
+ app_cont: bssgp_ran_inf_req_app_cont_nacc:
+ reprt_cell.rai.lac.plmn.mcc = 262
+ reprt_cell.rai.lac.plmn.mnc = 42
+ reprt_cell.rai.lac.plmn.mnc_3_digits = 0
+ reprt_cell.rai.lac.lac = 13200
+ reprt_cell.rai.rac = 0
+ reprt_cell.cell_identity = 51e1
+rc=8, result=62f22433900051e1
+----- test_bssgp_enc_ran_inf_req_app_cont_nacc END
+----- test_bssgp_dec_ran_inf_app_cont_nacc START
+rc=0, app_cont: bssgp_ran_inf_app_cont_nacc:
+ reprt_cell.rai.lac.plmn.mcc = 262
+ reprt_cell.rai.lac.plmn.mnc = 42
+ reprt_cell.rai.lac.plmn.mnc_3_digits = 0
+ reprt_cell.rai.lac.lac = 13135
+ reprt_cell.rai.rac = 0
+ reprt_cell.cell_identity = 51e0
+ type_psi = 0
+ num_si = 3
+ si[0] = 198fb100000000000000000000000000007900002b
+ si[1] = 1b753000f110236ec9033c2747407900003c0b2b2b
+ si[2] = 009000185a6fc9e08410ab2b2b2b2b2b2b2b2b2b2b
+----- test_bssgp_dec_ran_inf_app_cont_nacc END
+----- test_bssgp_enc_ran_inf_app_cont_nacc START
+ app_cont: bssgp_ran_inf_app_cont_nacc:
+ reprt_cell.rai.lac.plmn.mcc = 262
+ reprt_cell.rai.lac.plmn.mnc = 42
+ reprt_cell.rai.lac.plmn.mnc_3_digits = 0
+ reprt_cell.rai.lac.lac = 13135
+ reprt_cell.rai.rac = 0
+ reprt_cell.cell_identity = 51e1
+ type_psi = 0
+ num_si = 3
+ si[0] = 198fb100000000000000000000000000007900002b
+ si[1] = 1b753000f110236ec9033c2747407900003c0b2b2b
+ si[2] = 009000185a6fc9e08410ab2b2b2b2b2b2b2b2b2b2b
+rc=72, result=62f224334f0051e106198fb100000000000000000000000000007900002b1b753000f110236ec9033c2747407900003c0b2b2b009000185a6fc9e08410ab2b2b2b2b2b2b2b2b2b2b
+----- test_bssgp_enc_ran_inf_app_cont_nacc END
+----- test_bssgp_dec_app_err_cont_nacc START
+rc=0, app_err_cont: bssgp_app_err_cont_nacc:
+ macc_cause = 01
+ err_app_cont: aabbccddee
+ err_app_cont_len: 5
+----- test_bssgp_dec_app_err_cont_nacc END
+----- test_bssgp_enc_app_err_cont_nacc START
+ app_err_cont: bssgp_app_err_cont_nacc:
+ macc_cause = 01
+ err_app_cont: aabbccddee
+ err_app_cont_len: 5
+rc=6, result=01aabbccddee
+----- test_bssgp_enc_app_err_cont_nacc END
+===== BSSGP RIM test END
+
diff --git a/tests/gb/gprs_bssgp_test.c b/tests/gb/gprs_bssgp_test.c
index 52e986e8..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);
@@ -298,7 +298,7 @@ int main(int argc, char **argv)
osmo_init_logging2(ctx, &info);
log_set_use_color(osmo_stderr_target, 0);
- log_set_print_filename(osmo_stderr_target, 0);
+ log_set_print_filename2(osmo_stderr_target, LOG_FILENAME_NONE);
msgb_talloc_ctx_init(ctx, 0);
diff --git a/tests/gb/gprs_ns2_test.c b/tests/gb/gprs_ns2_test.c
new file mode 100644
index 00000000..0221a8d6
--- /dev/null
+++ b/tests/gb/gprs_ns2_test.c
@@ -0,0 +1,670 @@
+/* test routines for NS connection handling
+ * (C) 2020 sysmocom - s.f.m.c. GmbH
+ * Author: Alexander Couzens <lynxis@fe80.eu>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+#undef _GNU_SOURCE
+#define _GNU_SOURCE
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <string.h>
+#include <getopt.h>
+#include <dlfcn.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <osmocom/core/fsm.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/application.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/write_queue.h>
+#include <osmocom/gprs/gprs_msgb.h>
+#include <osmocom/gprs/gprs_ns2.h>
+#include <osmocom/gprs/gprs_bssgp.h>
+
+#include "../../src/gb/gprs_ns2_internal.h"
+
+int bssgp_prim_cb(struct osmo_prim_hdr *oph, void *ctx)
+{
+ return -1;
+}
+
+static struct log_info info = {};
+static struct osmo_wqueue *unitdata = NULL;
+static struct osmo_gprs_ns2_prim last_nse_recovery = {};
+static struct osmo_gprs_ns2_prim last_nse_mtu_change = {};
+
+static int ns_prim_cb(struct osmo_prim_hdr *oph, void *ctx)
+{
+ struct osmo_gprs_ns2_prim *nsp;
+ OSMO_ASSERT(oph->sap == SAP_NS);
+ nsp = container_of(oph, struct osmo_gprs_ns2_prim, oph);
+ if (oph->msg) {
+ if (oph->primitive == GPRS_NS2_PRIM_UNIT_DATA) {
+ osmo_wqueue_enqueue(unitdata, oph->msg);
+ } else {
+ msgb_free(oph->msg);
+ }
+ }
+ if (oph->primitive == GPRS_NS2_PRIM_STATUS) {
+ if (nsp->u.status.cause == GPRS_NS2_AFF_CAUSE_RECOVERY) {
+ last_nse_recovery = *nsp;
+ } else if (nsp->u.status.cause == GPRS_NS2_AFF_CAUSE_MTU_CHANGE) {
+ last_nse_mtu_change = *nsp;
+ }
+ }
+ return 0;
+}
+
+static int gp_send_to_ns(struct gprs_ns2_inst *nsi, struct msgb *msg, uint16_t nsei, uint16_t bvci, uint32_t lsp)
+{
+ struct osmo_gprs_ns2_prim nsp = {};
+ nsp.nsei = nsei;
+ nsp.bvci = bvci;
+ nsp.u.unitdata.link_selector = lsp;
+ osmo_prim_init(&nsp.oph, SAP_NS, GPRS_NS2_PRIM_UNIT_DATA,
+ PRIM_OP_REQUEST, msg);
+ return gprs_ns2_recv_prim(nsi, &nsp.oph);
+}
+
+
+static struct msgb *get_pdu(struct gprs_ns2_vc_bind *bind, enum ns_pdu_type pdu_type)
+{
+ struct gprs_ns_hdr *nsh;
+ struct osmo_wqueue *queue = bind->priv;
+
+ while (!llist_empty(&queue->msg_queue)) {
+ struct msgb *msg = msgb_dequeue(&queue->msg_queue);
+ nsh = (struct gprs_ns_hdr *) msg->l2h;
+ if (nsh->pdu_type == pdu_type)
+ return msg;
+ msgb_free(msg);
+ }
+
+ return NULL;
+}
+
+static bool find_pdu(struct gprs_ns2_vc_bind *bind, enum ns_pdu_type pdu_type)
+{
+ struct msgb *msg;
+ msg = get_pdu(bind, pdu_type);
+ if (msg) {
+ msgb_free(msg);
+ return true;
+ }
+
+ return false;
+}
+
+static unsigned int count_pdus(struct gprs_ns2_vc_bind *bind)
+{
+ struct osmo_wqueue *queue = bind->priv;
+ return llist_count(&queue->msg_queue);
+}
+
+static void clear_pdus(struct gprs_ns2_vc_bind *bind)
+{
+ struct osmo_wqueue *queue = bind->priv;
+ osmo_wqueue_clear(queue);
+}
+
+struct gprs_ns2_vc_driver vc_driver_dummy = {
+ .name = "GB UDP dummy",
+ .free_bind = clear_pdus,
+};
+
+static int vc_sendmsg(struct gprs_ns2_vc *nsvc, struct msgb *msg)
+{
+ struct gprs_ns2_vc_bind *bind = nsvc->bind;
+ struct osmo_wqueue *queue = bind->priv;
+
+ osmo_wqueue_enqueue(queue, msg);
+ return 0;
+}
+
+static struct gprs_ns2_vc_bind *dummy_bind(struct gprs_ns2_inst *nsi, const char *name)
+{
+ struct gprs_ns2_vc_bind *bind = NULL;
+ OSMO_ASSERT(ns2_bind_alloc(nsi, name, &bind) == 0);
+ OSMO_ASSERT(bind);
+
+ bind->driver = &vc_driver_dummy;
+ bind->ll = GPRS_NS2_LL_UDP;
+ bind->transfer_capability = 42;
+ bind->send_vc = vc_sendmsg;
+ bind->priv = talloc_zero(bind, struct osmo_wqueue);
+ bind->mtu = 123;
+ struct osmo_wqueue *queue = bind->priv;
+
+ osmo_wqueue_init(queue, 100);
+
+ return bind;
+}
+
+static void free_loopback(struct gprs_ns2_vc_bind *bind) {}
+
+struct gprs_ns2_vc_driver vc_driver_loopback = {
+ .name = "loopback dummy",
+ .free_bind = free_loopback,
+};
+
+/* loopback the msg */
+static int loopback_sendmsg(struct gprs_ns2_vc *nsvc, struct msgb *msg)
+{
+ struct gprs_ns2_vc *target = nsvc->priv;
+ return ns2_recv_vc(target, msg);
+}
+
+/* create a loopback nsvc object which can be used with ns2_tx_* functions. it's not fully registered etc. */
+static struct gprs_ns2_vc *loopback_nsvc(struct gprs_ns2_vc_bind *bind, struct gprs_ns2_vc *target)
+{
+ struct gprs_ns2_vc *nsvc = talloc_zero(bind, struct gprs_ns2_vc);
+ memcpy(nsvc, target, sizeof(struct gprs_ns2_vc));
+ nsvc->bind = bind;
+ nsvc->priv = target;
+ return nsvc;
+}
+
+/* a loop back bind to use the tx_ functions from gprs_ns2_message.c */
+static struct gprs_ns2_vc_bind *loopback_bind(struct gprs_ns2_inst *nsi, const char *name)
+{
+ struct gprs_ns2_vc_bind *bind = NULL;
+ OSMO_ASSERT(ns2_bind_alloc(nsi, name, &bind) == 0)
+ OSMO_ASSERT(bind);
+ bind->driver = &vc_driver_loopback;
+ bind->ll = GPRS_NS2_LL_UDP;
+ bind->transfer_capability = 99;
+ bind->send_vc = loopback_sendmsg;
+ bind->mtu = 123;
+ return bind;
+}
+
+void test_nse_transfer_cap(void *ctx)
+{
+ struct gprs_ns2_inst *nsi;
+ struct gprs_ns2_vc_bind *bind[2];
+ struct gprs_ns2_nse *nse;
+ struct gprs_ns2_vc *nsvc[3];
+
+ /* create a UDP dummy bind[0] with transfer cap 42.
+ * create nse (nsei 1001)
+ * create 2x nsvc with the same bind.
+ * nsvc[0] or nsvc[1] is alive (or both) cap == 42
+ *
+ * create a second bind with transfer cap == 23
+ * create 3rd nsvc with bind[1]
+ * transfer cap should be 42 + 23
+ */
+
+ printf("--- Testing NSE transfer cap\n");
+
+ printf("---- Create NSE + Binds\n");
+ nsi = gprs_ns2_instantiate(ctx, ns_prim_cb, NULL);
+ bind[0] = dummy_bind(nsi, "transfercap1");
+ bind[1] = dummy_bind(nsi, "transfercap2");
+ bind[1]->transfer_capability = 23;
+ nse = gprs_ns2_create_nse(nsi, 1001, GPRS_NS2_LL_UDP, GPRS_NS2_DIALECT_STATIC_ALIVE);
+ OSMO_ASSERT(nse);
+
+ printf("---- Test with NSVC[0]\n");
+ nsvc[0] = ns2_vc_alloc(bind[0], nse, false, GPRS_NS2_VC_MODE_ALIVE, NULL);
+ OSMO_ASSERT(nsvc[0]);
+ OSMO_ASSERT(ns2_count_transfer_cap(nse, 0) == 0);
+ nsvc[0]->fi->state = 3; /* HACK: 3 = GPRS_NS2_ST_UNBLOCKED */
+ ns2_nse_notify_unblocked(nsvc[0], true);
+ OSMO_ASSERT(ns2_count_transfer_cap(nse, 0) == 42);
+
+ printf("---- Test with NSVC[1]\n");
+ nsvc[1] = ns2_vc_alloc(bind[0], nse, false, GPRS_NS2_VC_MODE_ALIVE, NULL);
+ OSMO_ASSERT(nsvc[1]);
+ OSMO_ASSERT(ns2_count_transfer_cap(nse, 0) == 42);
+ nsvc[1]->fi->state = 3; /* HACK: 3 = GPRS_NS2_ST_UNBLOCKED */
+ ns2_nse_notify_unblocked(nsvc[1], true);
+ OSMO_ASSERT(ns2_count_transfer_cap(nse, 0) == 42);
+
+ printf("---- Test with NSVC[2]\n");
+ nsvc[2] = ns2_vc_alloc(bind[1], nse, false, GPRS_NS2_VC_MODE_ALIVE, NULL);
+ OSMO_ASSERT(nsvc[2]);
+ OSMO_ASSERT(ns2_count_transfer_cap(nse, 0) == 42);
+ nsvc[2]->fi->state = 3; /* HACK: 3 = GPRS_NS2_ST_UNBLOCKED */
+ ns2_nse_notify_unblocked(nsvc[2], true);
+ OSMO_ASSERT(ns2_count_transfer_cap(nse, 0) == 42 + 23);
+
+ printf("---- Test with NSVC[1] removed\n");
+ /* reset nsvc[1] to be unconfigured - shouldn't change anything */
+ nsvc[1]->fi->state = 0; /* HACK: 0 = GPRS_NS2_ST_UNCONFIGURED */
+ ns2_nse_notify_unblocked(nsvc[1], false);
+ OSMO_ASSERT(ns2_count_transfer_cap(nse, 0) == 42 + 23);
+
+ gprs_ns2_free(nsi);
+ printf("--- Finish NSE transfer cap\n");
+
+}
+
+/* setup NSE with 2x NSVCs.
+ * block 1x NSVC
+ * unblock 1x NSVC*/
+void test_block_unblock_nsvc(void *ctx)
+{
+ struct gprs_ns2_inst *nsi;
+ struct gprs_ns2_vc_bind *bind[2];
+ struct gprs_ns2_nse *nse;
+ struct gprs_ns2_vc *nsvc[2];
+ struct gprs_ns_hdr *nsh;
+ struct msgb *msg;
+ char idbuf[32];
+ int i;
+
+ printf("--- Testing NSE block unblock nsvc\n");
+ printf("---- Create NSE + Binds\n");
+ nsi = gprs_ns2_instantiate(ctx, ns_prim_cb, NULL);
+ bind[0] = dummy_bind(nsi, "bblock1");
+ bind[1] = dummy_bind(nsi, "bblock2");
+ nse = gprs_ns2_create_nse(nsi, 1001, GPRS_NS2_LL_UDP, GPRS_NS2_DIALECT_STATIC_RESETBLOCK);
+ OSMO_ASSERT(nse);
+
+ for (i=0; i<2; i++) {
+ printf("---- Create NSVC[i]\n");
+ snprintf(idbuf, sizeof(idbuf), "NSE%05u-dummy-%i", nse->nsei, i);
+ nsvc[i] = ns2_vc_alloc(bind[i], nse, false, GPRS_NS2_VC_MODE_BLOCKRESET, idbuf);
+ OSMO_ASSERT(nsvc[i]);
+ nsvc[i]->fi->state = 3; /* HACK: 3 = GPRS_NS2_ST_UNBLOCKED */
+ /* ensure the fi->state works correct */
+ OSMO_ASSERT(ns2_vc_is_unblocked(nsvc[i]));
+ ns2_nse_notify_unblocked(nsvc[i], true);
+ }
+
+ /* both nsvcs are unblocked and alive. Let's block it. */
+ OSMO_ASSERT(!find_pdu(bind[0], NS_PDUT_BLOCK));
+ clear_pdus(bind[0]);
+ ns2_vc_block(nsvc[0]);
+ OSMO_ASSERT(find_pdu(bind[0], NS_PDUT_BLOCK));
+ /* state == BLOCKED */
+ clear_pdus(bind[0]);
+
+ /* now unblocking it */
+ ns2_vc_unblock(nsvc[0]);
+ OSMO_ASSERT(find_pdu(bind[0], NS_PDUT_UNBLOCK));
+ clear_pdus(bind[0]);
+
+ msg = msgb_alloc_headroom(NS_ALLOC_SIZE, NS_ALLOC_HEADROOM, "test_unblock");
+ msg->l2h = msgb_put(msg, sizeof(*nsh));
+ nsh = (struct gprs_ns_hdr *) msg->l2h;
+ nsh->pdu_type = NS_PDUT_UNBLOCK_ACK;
+ ns2_recv_vc(nsvc[0], msg);
+
+ OSMO_ASSERT(ns2_vc_is_unblocked(nsvc[0]));
+ gprs_ns2_free(nsi);
+ printf("--- Finish NSE block unblock nsvc\n");
+}
+
+/* setup NSE with 2x NSVCs.
+ * block 1st NSVC
+ * block 2nd NSVC
+ * unblock 1st NSVC */
+void test_block_unblock_nsvc2(void *ctx)
+{
+ struct gprs_ns2_inst *nsi;
+ struct gprs_ns2_vc_bind *bind[2];
+ struct gprs_ns2_nse *nse;
+ struct gprs_ns2_vc *nsvc[2];
+ struct gprs_ns_hdr *nsh;
+ struct msgb *msg;
+ char idbuf[32];
+ int i;
+
+ printf("--- Testing NSE block unblock nsvc2\n");
+ printf("---- Create NSE + Binds\n");
+ nsi = gprs_ns2_instantiate(ctx, ns_prim_cb, NULL);
+ bind[0] = dummy_bind(nsi, "bblock1");
+ bind[1] = dummy_bind(nsi, "bblock2");
+ nse = gprs_ns2_create_nse(nsi, 1001, GPRS_NS2_LL_UDP, GPRS_NS2_DIALECT_STATIC_RESETBLOCK);
+ OSMO_ASSERT(nse);
+
+ for (i=0; i<2; i++) {
+ printf("---- Create NSVC[i]\n");
+ snprintf(idbuf, sizeof(idbuf), "NSE%05u-dummy-%i", nse->nsei, i);
+ nsvc[i] = ns2_vc_alloc(bind[i], nse, false, GPRS_NS2_VC_MODE_BLOCKRESET, idbuf);
+ OSMO_ASSERT(nsvc[i]);
+ nsvc[i]->fi->state = 3; /* HACK: 3 = GPRS_NS2_ST_UNBLOCKED */
+ /* ensure the fi->state works correct */
+ OSMO_ASSERT(ns2_vc_is_unblocked(nsvc[i]));
+ ns2_nse_notify_unblocked(nsvc[i], true);
+ }
+
+ OSMO_ASSERT(nse->alive);
+ /* both nsvcs are unblocked and alive. Let's block them. */
+ OSMO_ASSERT(!find_pdu(bind[0], NS_PDUT_BLOCK));
+ clear_pdus(bind[0]);
+ ns2_vc_block(nsvc[0]);
+ OSMO_ASSERT(find_pdu(bind[0], NS_PDUT_BLOCK));
+ clear_pdus(bind[0]);
+ OSMO_ASSERT(nse->alive);
+
+ OSMO_ASSERT(!find_pdu(bind[1], NS_PDUT_BLOCK));
+ clear_pdus(bind[1]);
+ ns2_vc_block(nsvc[1]);
+ OSMO_ASSERT(find_pdu(bind[1], NS_PDUT_BLOCK));
+ clear_pdus(bind[1]);
+ OSMO_ASSERT(!nse->alive);
+
+ /* now unblocking the 1st NSVC */
+ ns2_vc_unblock(nsvc[0]);
+ OSMO_ASSERT(find_pdu(bind[0], NS_PDUT_UNBLOCK));
+ clear_pdus(bind[0]);
+ msg = msgb_alloc_headroom(NS_ALLOC_SIZE, NS_ALLOC_HEADROOM, "test_unblock");
+ msg->l2h = msgb_put(msg, sizeof(*nsh));
+ nsh = (struct gprs_ns_hdr *) msg->l2h;
+ nsh->pdu_type = NS_PDUT_UNBLOCK_ACK;
+ ns2_recv_vc(nsvc[0], msg);
+ OSMO_ASSERT(nse->alive);
+
+ OSMO_ASSERT(ns2_vc_is_unblocked(nsvc[0]));
+ gprs_ns2_free(nsi);
+ printf("--- Finish NSE block unblock nsvc2\n");
+}
+
+static struct msgb *generate_unitdata(const char *msgname)
+{
+ struct gprs_ns_hdr *nsh;
+ struct msgb *msg = msgb_alloc_headroom(NS_ALLOC_SIZE, NS_ALLOC_HEADROOM, msgname);
+ OSMO_ASSERT(msg);
+
+ msg->l2h = msgb_put(msg, sizeof(*nsh) + 6);
+ nsh = (struct gprs_ns_hdr *) msg->l2h;
+ nsh->pdu_type = NS_PDUT_UNITDATA;
+ nsh->data[0] = 0; /* sdu control */
+ nsh->data[1] = 0; /* msb bvci */
+ nsh->data[2] = 12; /* lsb bvci */
+ nsh->data[3] = 0xab; /* first data byte */
+ nsh->data[4] = 0xcd;
+ nsh->data[5] = 0xef;
+
+ return msg;
+}
+
+void test_unitdata(void *ctx)
+{
+ struct gprs_ns2_inst *nsi;
+ struct gprs_ns2_vc_bind *bind[2];
+ struct gprs_ns2_vc_bind *loopbind;
+ struct gprs_ns2_nse *nse;
+ struct gprs_ns2_vc *nsvc[2];
+ struct gprs_ns2_vc *loop[2];
+
+ struct msgb *msg, *other;
+ char idbuf[32];
+ int i;
+
+ printf("--- Testing unitdata test\n");
+ osmo_wqueue_clear(unitdata);
+ printf("---- Create NSE + Binds\n");
+ nsi = gprs_ns2_instantiate(ctx, ns_prim_cb, NULL);
+ bind[0] = dummy_bind(nsi, "bblock1");
+ bind[1] = dummy_bind(nsi, "bblock2");
+ loopbind = loopback_bind(nsi, "loopback");
+ nse = gprs_ns2_create_nse(nsi, 1004, GPRS_NS2_LL_UDP, GPRS_NS2_DIALECT_STATIC_RESETBLOCK);
+ OSMO_ASSERT(nse);
+
+ for (i=0; i<2; i++) {
+ printf("---- Create NSVC[%d]\n", i);
+ snprintf(idbuf, sizeof(idbuf), "NSE%05u-dummy-%i", nse->nsei, i);
+ nsvc[i] = ns2_vc_alloc(bind[i], nse, false, GPRS_NS2_VC_MODE_BLOCKRESET, idbuf);
+ loop[i] = loopback_nsvc(loopbind, nsvc[i]);
+ OSMO_ASSERT(nsvc[i]);
+ ns2_vc_fsm_start(nsvc[i]);
+ OSMO_ASSERT(!ns2_vc_is_unblocked(nsvc[i]));
+ ns2_tx_reset(loop[i], NS_CAUSE_OM_INTERVENTION);
+ ns2_tx_unblock(loop[i]);
+ OSMO_ASSERT(ns2_vc_is_unblocked(nsvc[i]));
+ }
+
+ /* both nsvcs are unblocked and alive */
+ printf("---- Send UNITDATA to NSVC[0]\n");
+ msg = generate_unitdata("test_unitdata");
+ ns2_recv_vc(nsvc[0], msg);
+ other = msgb_dequeue(&unitdata->msg_queue);
+ OSMO_ASSERT(msg == other);
+ other = msgb_dequeue(&unitdata->msg_queue);
+ OSMO_ASSERT(NULL == other);
+
+ printf("---- Send Block NSVC[0]\n");
+ ns2_vc_block(nsvc[0]);
+ ns2_tx_block_ack(loop[0], NULL);
+
+ /* try to receive a unitdata - this should be dropped & freed by NS */
+ printf("---- Try to receive over blocked NSVC[0]\n");
+ ns2_recv_vc(nsvc[0], msg);
+ other = msgb_dequeue(&unitdata->msg_queue);
+ OSMO_ASSERT(NULL == other);
+
+ /* nsvc[1] should be still good */
+ printf("---- Receive over NSVC[1]\n");
+ msg = generate_unitdata("test_unitdata2");
+ ns2_recv_vc(nsvc[1], msg);
+ other = msgb_dequeue(&unitdata->msg_queue);
+ OSMO_ASSERT(msg == other);
+ msgb_free(msg);
+
+ gprs_ns2_free(nsi);
+ printf("--- Finish unitdata test\n");
+}
+
+void test_unitdata_weights(void *ctx)
+{
+ struct gprs_ns2_inst *nsi;
+ struct gprs_ns2_vc_bind *bind[3];
+ struct gprs_ns2_vc_bind *loopbind;
+ struct gprs_ns2_nse *nse;
+ struct gprs_ns2_vc *nsvc[3];
+ struct gprs_ns2_vc *loop[3];
+
+ struct msgb *msg, *other;
+ char idbuf[32];
+ int i;
+
+ printf("--- Testing unitdata weight test\n");
+ osmo_wqueue_clear(unitdata);
+ printf("---- Create NSE + Binds\n");
+ nsi = gprs_ns2_instantiate(ctx, ns_prim_cb, NULL);
+ bind[0] = dummy_bind(nsi, "bblock1");
+ bind[1] = dummy_bind(nsi, "bblock2");
+ bind[2] = dummy_bind(nsi, "bblock3");
+ loopbind = loopback_bind(nsi, "loopback");
+ nse = gprs_ns2_create_nse(nsi, 1004, GPRS_NS2_LL_UDP, GPRS_NS2_DIALECT_STATIC_ALIVE);
+ OSMO_ASSERT(nse);
+
+ /* data weights are
+ * nsvc[0] = 1
+ * nsvc[1] = 2
+ * nsvc[2] = 3
+ */
+ for (i = 0; i < 3; i++) {
+ printf("---- Create NSVC[%d]\n", i);
+ snprintf(idbuf, sizeof(idbuf), "NSE%05u-dummy-%i", nse->nsei, i);
+ nsvc[i] = ns2_vc_alloc(bind[i], nse, false, GPRS_NS2_VC_MODE_ALIVE, idbuf);
+ loop[i] = loopback_nsvc(loopbind, nsvc[i]);
+ OSMO_ASSERT(nsvc[i]);
+ nsvc[i]->data_weight = i + 1;
+ ns2_vc_fsm_start(nsvc[i]);
+ OSMO_ASSERT(!ns2_vc_is_unblocked(nsvc[i]));
+ ns2_tx_alive_ack(loop[i]);
+ OSMO_ASSERT(ns2_vc_is_unblocked(nsvc[i]));
+ }
+
+ /* all nsvcs are alive */
+ printf("---- Send UNITDATA to all NSVCs\n");
+ for (i = 0; i < 3; i++) {
+ msg = generate_unitdata("test_unitdata_weight");
+ ns2_recv_vc(nsvc[i], msg);
+ other = msgb_dequeue(&unitdata->msg_queue);
+ OSMO_ASSERT(msg == other);
+ other = msgb_dequeue(&unitdata->msg_queue);
+ OSMO_ASSERT(NULL == other);
+ msgb_free(msg);
+ }
+
+ /* nsvc[1] should be still good */
+ printf("---- Send BSSGP data to the NSE to test unitdata over NSVC[1]\n");
+ for (i = 0; i < 3; i++)
+ clear_pdus(bind[i]);
+
+ for (i = 0; i < 12; i++) {
+ msg = generate_unitdata("test_unitdata_weight2");
+ gp_send_to_ns(nsi, msg, 1004, 1, i + 1);
+ }
+
+ for (i = 0; i < 3; i++)
+ fprintf(stderr, "count_pdus(bind[%d]) = %d\n", i, count_pdus(bind[i]));
+
+ for (i = 0; i < 3; i++) {
+ OSMO_ASSERT(count_pdus(bind[i]) == nsvc[i]->data_weight * 2);
+ }
+
+ gprs_ns2_free(nsi);
+ printf("--- Finish unitdata weight test\n");
+}
+
+void test_mtu(void *ctx)
+{
+ struct gprs_ns2_inst *nsi;
+ struct gprs_ns2_vc_bind *bind[2];
+ struct gprs_ns2_vc_bind *loopbind;
+ struct gprs_ns2_nse *nse;
+ struct gprs_ns2_vc *nsvc[2];
+ struct gprs_ns2_vc *loop[2];
+
+ struct msgb *msg, *other;
+ char idbuf[32];
+ int i;
+
+ printf("--- Testing mtu test\n");
+ osmo_wqueue_clear(unitdata);
+ printf("---- Create NSE + Binds\n");
+ nsi = gprs_ns2_instantiate(ctx, ns_prim_cb, NULL);
+ bind[0] = dummy_bind(nsi, "bblock1");
+ bind[1] = dummy_bind(nsi, "bblock2");
+ loopbind = loopback_bind(nsi, "loopback");
+ nse = gprs_ns2_create_nse(nsi, 1004, GPRS_NS2_LL_UDP, GPRS_NS2_DIALECT_STATIC_RESETBLOCK);
+ OSMO_ASSERT(nse);
+
+ for (i=0; i<2; i++) {
+ printf("---- Create NSVC[%d]\n", i);
+ snprintf(idbuf, sizeof(idbuf), "NSE%05u-dummy-%i", nse->nsei, i);
+ nsvc[i] = ns2_vc_alloc(bind[i], nse, false, GPRS_NS2_VC_MODE_BLOCKRESET, idbuf);
+ loop[i] = loopback_nsvc(loopbind, nsvc[i]);
+ OSMO_ASSERT(nsvc[i]);
+ ns2_vc_fsm_start(nsvc[i]);
+ OSMO_ASSERT(!ns2_vc_is_unblocked(nsvc[i]));
+ ns2_tx_reset(loop[i], NS_CAUSE_OM_INTERVENTION);
+ ns2_tx_unblock(loop[i]);
+ OSMO_ASSERT(ns2_vc_is_unblocked(nsvc[i]));
+ }
+
+ /* both nsvcs are unblocked and alive */
+ printf("---- Send a small UNITDATA to NSVC[0]\n");
+ msg = generate_unitdata("test_unitdata");
+ ns2_recv_vc(nsvc[0], msg);
+ other = msgb_dequeue(&unitdata->msg_queue);
+ OSMO_ASSERT(msg == other);
+ other = msgb_dequeue(&unitdata->msg_queue);
+ OSMO_ASSERT(NULL == other);
+ msgb_free(msg);
+
+ printf("---- Check if got mtu reported\n");
+ /* 1b NS PDU type, 1b NS SDU control, 2b BVCI */
+ OSMO_ASSERT(last_nse_recovery.u.status.mtu == 123 - 4);
+
+ bind[0]->mtu = 100;
+ ns2_nse_update_mtu(nse);
+ OSMO_ASSERT(last_nse_mtu_change.u.status.mtu == 100 - 4);
+
+ gprs_ns2_free(nsi);
+ printf("--- Finish unitdata test\n");
+}
+
+void test_unconfigured(void *ctx)
+{
+ struct gprs_ns2_inst *nsi;
+ struct gprs_ns2_vc_bind *bind[2];
+ struct gprs_ns2_vc_bind *loopbind;
+ struct gprs_ns2_nse *nse;
+ struct gprs_ns2_vc *nsvc[2];
+ struct gprs_ns2_vc *loop[2];
+
+ char idbuf[32];
+ int i;
+
+ printf("--- Testing force unconfigured\n");
+ osmo_wqueue_clear(unitdata);
+ printf("---- Create NSE + Binds\n");
+ nsi = gprs_ns2_instantiate(ctx, ns_prim_cb, NULL);
+ bind[0] = dummy_bind(nsi, "bblock1");
+ bind[1] = dummy_bind(nsi, "bblock2");
+ loopbind = loopback_bind(nsi, "loopback");
+ nse = gprs_ns2_create_nse(nsi, 1004, GPRS_NS2_LL_UDP, GPRS_NS2_DIALECT_STATIC_RESETBLOCK);
+ OSMO_ASSERT(nse);
+
+ for (i=0; i<2; i++) {
+ printf("---- Create NSVC[%d]\n", i);
+ snprintf(idbuf, sizeof(idbuf), "NSE%05u-dummy-%i", nse->nsei, i);
+ nsvc[i] = ns2_vc_alloc(bind[i], nse, false, GPRS_NS2_VC_MODE_BLOCKRESET, idbuf);
+ loop[i] = loopback_nsvc(loopbind, nsvc[i]);
+ OSMO_ASSERT(nsvc[i]);
+ ns2_vc_fsm_start(nsvc[i]);
+ OSMO_ASSERT(!ns2_vc_is_unblocked(nsvc[i]));
+ ns2_tx_reset(loop[i], NS_CAUSE_OM_INTERVENTION);
+ ns2_tx_unblock(loop[i]);
+ OSMO_ASSERT(ns2_vc_is_unblocked(nsvc[i]));
+ }
+
+ /* both nsvcs are unblocked and alive */
+ printf("---- Check if NSE is alive\n");
+ OSMO_ASSERT(nse->alive);
+
+ ns2_vc_force_unconfigured(nsvc[0]);
+ OSMO_ASSERT(nse->alive);
+
+ ns2_vc_force_unconfigured(nsvc[1]);
+ printf("---- Check if NSE is dead\n");
+ OSMO_ASSERT(!nse->alive);
+
+ gprs_ns2_free(nsi);
+ printf("--- Finish force unconfigured test\n");
+}
+
+int main(int argc, char **argv)
+{
+ void *ctx = talloc_named_const(NULL, 0, "gprs_ns2_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);
+ log_set_log_level(osmo_stderr_target, LOGL_INFO);
+ unitdata = talloc_zero(ctx, struct osmo_wqueue);
+ osmo_wqueue_init(unitdata, 100);
+ setlinebuf(stdout);
+
+ printf("===== NS2 protocol test START\n");
+ test_nse_transfer_cap(ctx);
+ test_block_unblock_nsvc(ctx);
+ test_block_unblock_nsvc2(ctx);
+ test_unitdata(ctx);
+ test_unitdata_weights(ctx);
+ test_unconfigured(ctx);
+ test_mtu(ctx);
+ printf("===== NS2 protocol test END\n\n");
+
+ talloc_free(ctx);
+ exit(EXIT_SUCCESS);
+}
diff --git a/tests/gb/gprs_ns2_test.ok b/tests/gb/gprs_ns2_test.ok
new file mode 100644
index 00000000..16e66bb8
--- /dev/null
+++ b/tests/gb/gprs_ns2_test.ok
@@ -0,0 +1,51 @@
+===== NS2 protocol test START
+--- Testing NSE transfer cap
+---- Create NSE + Binds
+---- Test with NSVC[0]
+---- Test with NSVC[1]
+---- Test with NSVC[2]
+---- Test with NSVC[1] removed
+--- Finish NSE transfer cap
+--- Testing NSE block unblock nsvc
+---- Create NSE + Binds
+---- Create NSVC[i]
+---- Create NSVC[i]
+--- Finish NSE block unblock nsvc
+--- Testing NSE block unblock nsvc2
+---- Create NSE + Binds
+---- Create NSVC[i]
+---- Create NSVC[i]
+--- Finish NSE block unblock nsvc2
+--- Testing unitdata test
+---- Create NSE + Binds
+---- Create NSVC[0]
+---- Create NSVC[1]
+---- Send UNITDATA to NSVC[0]
+---- Send Block NSVC[0]
+---- Try to receive over blocked NSVC[0]
+---- Receive over NSVC[1]
+--- Finish unitdata test
+--- Testing unitdata weight test
+---- Create NSE + Binds
+---- Create NSVC[0]
+---- Create NSVC[1]
+---- Create NSVC[2]
+---- Send UNITDATA to all NSVCs
+---- Send BSSGP data to the NSE to test unitdata over NSVC[1]
+--- Finish unitdata weight test
+--- Testing force unconfigured
+---- Create NSE + Binds
+---- Create NSVC[0]
+---- Create NSVC[1]
+---- Check if NSE is alive
+---- Check if NSE is dead
+--- Finish force unconfigured test
+--- Testing mtu test
+---- Create NSE + Binds
+---- Create NSVC[0]
+---- Create NSVC[1]
+---- Send a small UNITDATA to NSVC[0]
+---- Check if got mtu reported
+--- Finish unitdata test
+===== NS2 protocol test END
+
diff --git a/tests/gb/gprs_ns2_vty.vty b/tests/gb/gprs_ns2_vty.vty
new file mode 100644
index 00000000..8db5fb50
--- /dev/null
+++ b/tests/gb/gprs_ns2_vty.vty
@@ -0,0 +1,89 @@
+OsmoNSdummy> list
+...
+ show ns binds [stats]
+ show ns entities [stats]
+ show ns persistent
+ show ns (nsei|nsvc) <0-65535> [stats]
+...
+ logging filter nse nsei <0-65535>
+ logging filter nsvc nsvci <0-65535>
+...
+OsmoNSdummy> enable
+OsmoNSdummy# configure terminal
+OsmoNSdummy(config)# list
+...
+ ns
+...
+OsmoNSdummy(config)# ns
+OsmoNSdummy(config-ns)# list
+...
+ timer (tns-block|tns-block-retries|tns-reset|tns-reset-retries|tns-test|tns-alive|tns-alive-retries|tsns-prov|tsns-size-retries|tsns-config-retries|tsns-procedures-retries) <0-65535>
+ nse <0-65535> [ip-sns-role-sgsn]
+ no nse <0-65535>
+ bind (fr|udp) ID
+ no bind ID
+...
+OsmoNSdummy(config-ns)# bind udp abc
+OsmoNSdummy(config-ns-bind)# fr eta0 frnet
+fr can be only used with frame relay bind
+OsmoNSdummy(config-ns-bind)# listen 127.0.0.14 42999
+OsmoNSdummy(config-ns-bind)# end
+OsmoNSdummy# show ns
+UDP bind: 127.0.0.14:42999 DSCP: 0 Priority: 0
+ IP-SNS signalling weight: 1 data weight: 1
+ 0 NS-VC:
+OsmoNSdummy# configure terminal
+OsmoNSdummy(config)# ns
+OsmoNSdummy(config-ns)# nse 1234
+OsmoNSdummy(config-ns-nse)# nsvc udp abc 127.0.0.15 9496
+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 (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 (cause: remote) since 0d 0h 0m 0s
+OsmoNSdummy# configure terminal
+OsmoNSdummy(config)# ns
+OsmoNSdummy(config-ns)# nse 1234
+OsmoNSdummy(config-ns-nse)# nsvc udp abc 127.0.0.16 9496 signalling-weight 0 data-weight 9
+OsmoNSdummy(config-ns-nse)# nsvc udp abc 127.0.0.17 9496 signalling-weight 0 data-weight 0
+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 (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 (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
+OsmoNSdummy(config-ns-nse)# nsvc udp abc 127.0.0.16 9496 signalling-weight 2 data-weight 2
+Specified NSVC is already present in this NSE.
+OsmoNSdummy(config-ns-nse)# exit
+OsmoNSdummy(config-ns)# nse 1235
+OsmoNSdummy(config-ns-nse)# nsvc udp abc 127.0.0.16 9496 signalling-weight 2 data-weight 2
+Specified NSVC is already present in another NSE01234.
+OsmoNSdummy(config-ns-nse)# exit
+OsmoNSdummy(config-ns)# nse 2342
+OsmoNSdummy(config-ns-nse)# ip-sns-bind abc
+OsmoNSdummy(config-ns-nse)# ip-sns-bind abc
+Failed to add ip-sns-bind abc already present
+OsmoNSdummy(config-ns-nse)# ip-sns-bind abc2
+Can not find the given bind 'abc2'
+OsmoNSdummy(config-ns-nse)# ip-sns-remote 127.0.0.1 22222
+OsmoNSdummy(config-ns-nse)# ip-sns-remote 127.0.0.1 22222
+Specified SNS endpoint already part of the NSE.
+OsmoNSdummy(config-ns-nse)# exit
+OsmoNSdummy(config-ns)# no bind abc
+OsmoNSdummy(config-ns)# no bind abc77
+bind abc77 does not exist!
+OsmoNSdummy(config-ns)# exit
diff --git a/tests/gb/gprs_ns_test.c b/tests/gb/gprs_ns_test.c
index f70e4937..fc1ec605 100644
--- a/tests/gb/gprs_ns_test.c
+++ b/tests/gb/gprs_ns_test.c
@@ -262,7 +262,7 @@ static void dump_rate_ctr_group(FILE *stream, const char *prefix,
unsigned int i;
for (i = 0; i < ctrg->desc->num_ctr; i++) {
- struct rate_ctr *ctr = &ctrg->ctr[i];
+ struct rate_ctr *ctr = rate_ctr_group_get_ctr(ctrg, i);
if (ctr->current && !strchr(ctrg->desc->ctr_desc[i].name, ':'))
fprintf(stream, " %s%s: %llu%s",
prefix, ctrg->desc->ctr_desc[i].description,
@@ -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};
@@ -904,11 +904,11 @@ int main(int argc, char **argv)
void *ctx = talloc_named_const(NULL, 0, "gprs_ns_test");
osmo_init_logging2(ctx, &info);
log_set_use_color(osmo_stderr_target, 0);
- log_set_print_filename(osmo_stderr_target, 0);
- osmo_signal_register_handler(SS_L_NS, &test_signal, NULL);
-
- log_set_print_filename(osmo_stderr_target, 0);
+ log_set_print_category(osmo_stderr_target, 0);
+ log_set_print_category_hex(osmo_stderr_target, 0);
+ log_set_print_filename2(osmo_stderr_target, LOG_FILENAME_NONE);
log_set_log_level(osmo_stderr_target, LOGL_INFO);
+ osmo_signal_register_handler(SS_L_NS, &test_signal, NULL);
setlinebuf(stdout);
diff --git a/tests/gb/osmo-ns-dummy.cfg b/tests/gb/osmo-ns-dummy.cfg
new file mode 100644
index 00000000..72696189
--- /dev/null
+++ b/tests/gb/osmo-ns-dummy.cfg
@@ -0,0 +1,25 @@
+!
+! OsmoNSdummy (1.4.0.495-64db) configuration saved from vty
+!!
+ns
+ timer tns-block 3
+ timer tns-block-retries 3
+ timer tns-reset 3
+ timer tns-reset-retries 3
+ timer tns-test 30
+ timer tns-alive 3
+ timer tns-alive-retries 10
+ timer tsns-prov 3
+ timer tsns-size-retries 3
+ timer tsns-config-retries 3
+ bind udp local
+ listen 127.0.0.1 2158
+ accept-ipaccess
+ ip-sns signalling-weight 1 data-weight 1
+ nse 1236
+ nsvc ipa local 127.0.0.4 23000 nsvci 101
+ nse 1235
+ nsvc udp local 127.0.0.3 23000
+ nse 1234
+ ip-sns-bind local
+ ip-sns-remote 127.0.0.2 2158
diff --git a/tests/gb/osmoappdesc.py b/tests/gb/osmoappdesc.py
new file mode 100644
index 00000000..323b94ef
--- /dev/null
+++ b/tests/gb/osmoappdesc.py
@@ -0,0 +1,27 @@
+#!/usr/bin/env python3
+
+# (C) 2021 by sysmocom - s.f.m.c. GmbH
+# Author: Alexander Couzens <lynxis@fe80.eu>
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>
+
+app_configs = {
+ "osmo-ns-dummy": ["osmo-ns-dummy.cfg"],
+}
+
+apps = [(45999, "../../utils/osmo-ns-dummy -p 45999", "OsmoNSdummy", "osmo-ns-dummy")
+ ]
+
+vty_command = ["../../utils/osmo-ns-dummy", "-p", "45999", "-c", "osmo-ns-dummy.cfg"]
+
+vty_app = apps[0]
diff --git a/tests/gsm0408/gsm0408_test.c b/tests/gsm0408/gsm0408_test.c
index d2ae6f6c..4436d517 100644
--- a/tests/gsm0408/gsm0408_test.c
+++ b/tests/gsm0408/gsm0408_test.c
@@ -12,10 +12,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
@@ -69,6 +65,18 @@ static const struct gsm_mncc_bearer_cap bcap_speech_all = {
},
};
+static const uint8_t speech_no3a_lv[] = { 0x01, 0xa0 };
+
+static const struct gsm_mncc_bearer_cap bcap_speech_no3a = {
+ .transfer = GSM48_BCAP_ITCAP_SPEECH,
+ .mode = GSM48_BCAP_TMOD_CIRCUIT,
+ .coding = GSM48_BCAP_CODING_GSM_STD,
+ .radio = GSM48_BCAP_RRQ_FR_ONLY,
+ .speech_ver = {
+ 0, -1,
+ },
+};
+
struct bcap_test {
const uint8_t *lv;
@@ -79,15 +87,17 @@ struct bcap_test {
static const struct bcap_test bcap_tests[] = {
{ csd_9600_v110_lv, &bcap_csd_9600_v110, "CSD 9600/V.110/transparent" },
{ speech_all_lv, &bcap_speech_all, "Speech, all codecs" },
+ { 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;
for (i = 0; i < ARRAY_SIZE(bcap_tests); i++) {
struct msgb *msg = msgb_alloc(100, "test");
+ bool pass = false;
int lv_len;
memset(&bc, 0, sizeof(bc));
@@ -97,7 +107,7 @@ static int test_bearer_cap()
if (rc < 0) {
fprintf(stderr, "Error decoding %s\n",
bcap_tests[i].name);
- return rc;
+ goto verdict;
}
if (memcmp(&bc, bcap_tests[i].bc, sizeof(bc))) {
fprintf(stderr, "Incorrect decoded result of %s:\n",
@@ -106,7 +116,7 @@ static int test_bearer_cap()
osmo_hexdump((uint8_t *) bcap_tests[i].bc, sizeof(bc)));
fprintf(stderr, " is: %s\n",
osmo_hexdump((uint8_t *) &bc, sizeof(bc)));
- return -1;
+ goto verdict;
}
/* also test re-encode? */
@@ -114,7 +124,7 @@ static int test_bearer_cap()
if (rc < 0) {
fprintf(stderr, "Error encoding %s\n",
bcap_tests[i].name);
- return rc;
+ goto verdict;
}
lv_len = bcap_tests[i].lv[0]+1;
if (memcmp(msg->data, bcap_tests[i].lv, lv_len)) {
@@ -124,10 +134,14 @@ static int test_bearer_cap()
osmo_hexdump(bcap_tests[i].lv, lv_len));
fprintf(stderr, " is: %s\n",
osmo_hexdump(msg->data, msg->len));
- return -1;
+ goto verdict;
}
- printf("Test `%s' passed\n", bcap_tests[i].name);
+ /* all checks passed */
+ pass = true;
+
+verdict:
+ printf("Test `%s' %sed\n", bcap_tests[i].name, pass ? "pass" : "fail");
msgb_free(msg);
}
@@ -327,6 +341,203 @@ static void test_lai_encode_decode(void)
}
}
+static void dump_cm3(struct gsm48_classmark3 *cm3)
+{
+ printf("mult_band_supp=%02x\n", cm3->mult_band_supp);
+ printf("a5_bits=%02x\n", cm3->a5_bits);
+ printf("assoc_radio_cap_1=%02x\n", cm3->assoc_radio_cap_1);
+ printf("assoc_radio_cap_2=%02x\n", cm3->assoc_radio_cap_2);
+ printf("\n");
+ printf("r_support.present=%u\n", cm3->r_support.present);
+ printf("r_support.r_gsm_assoc_radio_cap=%02x\n",
+ cm3->r_support.r_gsm_assoc_radio_cap);
+ printf("\n");
+ printf("hscsd_mult_slot_cap.present=%u\n",
+ cm3->hscsd_mult_slot_cap.present);
+ printf("hscsd_mult_slot_cap.mslot_class=%02x\n",
+ cm3->hscsd_mult_slot_cap.mslot_class);
+ printf("\n");
+ printf("ucs2_treatment=%u\n", cm3->ucs2_treatment);
+ printf("extended_meas_cap=%u\n", cm3->extended_meas_cap);
+ printf("\n");
+ printf("ms_meas_cap.present=%u\n", cm3->ms_meas_cap.present);
+ printf("ms_meas_cap.sms_value=%02x\n", cm3->ms_meas_cap.sms_value);
+ printf("ms_meas_cap.sm_value=%02x\n", cm3->ms_meas_cap.sm_value);
+ printf("\n");
+ printf("ms_pos_method_cap.present=%u\n",
+ cm3->ms_pos_method_cap.present);
+ printf("ms_pos_method_cap.method=%02x\n",
+ cm3->ms_pos_method_cap.method);
+ printf("\n");
+ printf("ecsd_multislot_cap.present=%u\n",
+ cm3->ecsd_multislot_cap.present);
+ printf("ecsd_multislot_cap.mslot_class=%02x\n",
+ cm3->ecsd_multislot_cap.mslot_class);
+ printf("\n");
+ printf("psk8_struct.present=%u\n", cm3->psk8_struct.present);
+ printf("psk8_struct.mod_cap=%u\n", cm3->psk8_struct.mod_cap);
+ printf("psk8_struct.rf_pwr_cap_1.present=%u\n",
+ cm3->psk8_struct.rf_pwr_cap_1.present);
+ printf("psk8_struct.rf_pwr_cap_1.value=%02x\n",
+ cm3->psk8_struct.rf_pwr_cap_1.value);
+ printf("psk8_struct.rf_pwr_cap_2.present=%u\n",
+ cm3->psk8_struct.rf_pwr_cap_2.present);
+ printf("psk8_struct.rf_pwr_cap_2.value=%02x\n",
+ cm3->psk8_struct.rf_pwr_cap_2.value);
+ printf("\n");
+ printf("gsm_400_bands_supp.present=%u\n",
+ cm3->gsm_400_bands_supp.present);
+ printf("gsm_400_bands_supp.value=%02x\n",
+ cm3->gsm_400_bands_supp.value);
+ printf("gsm_400_bands_supp.assoc_radio_cap=%02x\n",
+ cm3->gsm_400_bands_supp.assoc_radio_cap);
+ printf("\n");
+ printf("gsm_850_assoc_radio_cap.present=%u\n",
+ cm3->gsm_850_assoc_radio_cap.present);
+ printf("gsm_850_assoc_radio_cap.value=%02x\n",
+ cm3->gsm_850_assoc_radio_cap.value);
+ printf("\n");
+ printf("gsm_1900_assoc_radio_cap.present=%u\n",
+ cm3->gsm_1900_assoc_radio_cap.present);
+ printf("gsm_1900_assoc_radio_cap.value=%02x\n",
+ cm3->gsm_1900_assoc_radio_cap.value);
+ printf("\n");
+ printf("umts_fdd_rat_cap=%u\n", cm3->umts_fdd_rat_cap);
+ printf("umts_tdd_rat_cap=%u\n", cm3->umts_tdd_rat_cap);
+ printf("cdma200_rat_cap=%u\n", cm3->cdma200_rat_cap);
+ printf("\n");
+ printf("dtm_gprs_multislot_cap.present=%u\n",
+ cm3->dtm_gprs_multislot_cap.present);
+ printf("dtm_gprs_multislot_cap.mslot_class=%02x\n",
+ cm3->dtm_gprs_multislot_cap.mslot_class);
+ printf("dtm_gprs_multislot_cap.single_slot_dtm=%u\n",
+ cm3->dtm_gprs_multislot_cap.single_slot_dtm);
+ printf("dtm_gprs_multislot_cap.dtm_egprs_multislot_cap.present=%u\n",
+ cm3->dtm_gprs_multislot_cap.dtm_egprs_multislot_cap.present);
+ printf("dtm_gprs_multislot_cap.dtm_egprs_multislot_cap.mslot_class=%02x\n",
+ cm3->dtm_gprs_multislot_cap.dtm_egprs_multislot_cap.mslot_class);
+ printf("\n");
+ printf("single_band_supp.present=%u\n", cm3->single_band_supp.present);
+ printf("single_band_supp.value=%u\n", cm3->single_band_supp.value);
+ printf("\n");
+ printf("gsm_750_assoc_radio_cap.present=%u\n",
+ cm3->gsm_750_assoc_radio_cap.present);
+ printf("gsm_750_assoc_radio_cap.value=%02x\n",
+ cm3->gsm_750_assoc_radio_cap.value);
+ printf("\n");
+ printf("umts_1_28_mcps_tdd_rat_cap=%u\n",
+ cm3->umts_1_28_mcps_tdd_rat_cap);
+ printf("geran_feature_package=%u\n", cm3->geran_feature_package);
+ printf("\n");
+ printf("extended_dtm_gprs_multislot_cap.present=%u\n",
+ cm3->extended_dtm_gprs_multislot_cap.present);
+ printf("extended_dtm_gprs_multislot_cap.mslot_class=%02x\n",
+ cm3->extended_dtm_gprs_multislot_cap.mslot_class);
+ printf
+ ("extended_dtm_gprs_multislot_cap.dtm_egprs_multislot_cap.present=%u\n",
+ cm3->extended_dtm_gprs_multislot_cap.
+ extended_dtm_egprs_multislot_cap.present);
+ printf
+ ("extended_dtm_gprs_multislot_cap.dtm_egprs_multislot_cap.mslot_class=%02x\n",
+ cm3->extended_dtm_gprs_multislot_cap.
+ extended_dtm_egprs_multislot_cap.mslot_class);
+ printf("\n");
+ printf("high_multislot_cap.present=%u\n",
+ cm3->high_multislot_cap.present);
+ printf("high_multislot_cap.value=%02x\n",
+ cm3->high_multislot_cap.value);
+ printf("\n");
+ printf("geran_feature_package_2=%u\n", cm3->geran_feature_package_2);
+ printf("gmsk_multislot_power_prof=%02x\n",
+ cm3->gmsk_multislot_power_prof);
+ printf("psk8_multislot_power_prof=%02x\n",
+ cm3->psk8_multislot_power_prof);
+ printf("\n");
+ printf("t_gsm_400_bands_supp.present=%u\n",
+ cm3->t_gsm_400_bands_supp.present);
+ printf("t_gsm_400_bands_supp.value=%02x\n",
+ cm3->t_gsm_400_bands_supp.value);
+ printf("t_gsm_400_bands_supp.assoc_radio_cap=%02x\n",
+ cm3->t_gsm_400_bands_supp.assoc_radio_cap);
+ printf("\n");
+ printf("dl_advanced_rx_perf=%02x\n", cm3->dl_advanced_rx_perf);
+ printf("dtm_enhancements_cap=%u\n", cm3->dtm_enhancements_cap);
+ printf("\n");
+ printf("dtm_gprs_high_multislot_cap.present=%u\n",
+ cm3->dtm_gprs_high_multislot_cap.present);
+ printf("dtm_gprs_high_multislot_cap.mslot_class=%02x\n",
+ cm3->dtm_gprs_high_multislot_cap.mslot_class);
+ printf("dtm_gprs_high_multislot_cap.offset_required=%u\n",
+ cm3->dtm_gprs_high_multislot_cap.offset_required);
+ printf
+ ("dtm_gprs_high_multislot_cap.dtm_egprs_high_multislot_cap.present=%u\n",
+ cm3->dtm_gprs_high_multislot_cap.dtm_egprs_high_multislot_cap.
+ present);
+ printf
+ ("dtm_gprs_high_multislot_cap.dtm_egprs_high_multislot_cap.mslot_class=%02x\n",
+ cm3->dtm_gprs_high_multislot_cap.dtm_egprs_high_multislot_cap.
+ mslot_class);
+ printf("\n");
+ printf("repeated_acch_capability=%u\n", cm3->repeated_acch_capability);
+ printf("\n");
+ printf("gsm_710_assoc_radio_cap.present=%u\n",
+ cm3->gsm_710_assoc_radio_cap.present);
+ printf("gsm_710_assoc_radio_cap.value=%02x\n",
+ cm3->gsm_710_assoc_radio_cap.value);
+ printf("\n");
+ printf("t_gsm_810_assoc_radio_cap.present=%u\n",
+ cm3->t_gsm_810_assoc_radio_cap.present);
+ printf("t_gsm_810_assoc_radio_cap.value=%02x\n",
+ cm3->t_gsm_810_assoc_radio_cap.value);
+ printf("\n");
+ printf("ciphering_mode_setting_cap=%u\n",
+ cm3->ciphering_mode_setting_cap);
+ printf("add_pos_cap=%u\n", cm3->add_pos_cap);
+ printf("e_utra_fdd_supp=%u\n", cm3->e_utra_fdd_supp);
+ printf("e_utra_tdd_supp=%u\n", cm3->e_utra_tdd_supp);
+ printf("e_utra_meas_rep_supp=%u\n", cm3->e_utra_meas_rep_supp);
+ printf("prio_resel_supp=%u\n", cm3->prio_resel_supp);
+ printf("utra_csg_cells_rep=%u\n", cm3->utra_csg_cells_rep);
+ printf("vamos_level=%02x\n", cm3->vamos_level);
+ printf("tighter_capability=%02x\n", cm3->tighter_capability);
+ printf("sel_ciph_dl_sacch=%u\n", cm3->sel_ciph_dl_sacch);
+ printf("cs_ps_srvcc_geran_utra=%02x\n", cm3->cs_ps_srvcc_geran_utra);
+ printf("cs_ps_srvcc_geran_eutra=%02x\n", cm3->cs_ps_srvcc_geran_eutra);
+ printf("geran_net_sharing=%u\n", cm3->geran_net_sharing);
+ printf("e_utra_wb_rsrq_meas_supp=%u\n", cm3->e_utra_wb_rsrq_meas_supp);
+ printf("er_band_support=%u\n", cm3->er_band_support);
+ printf("utra_mult_band_ind_supp=%u\n", cm3->utra_mult_band_ind_supp);
+ printf("e_utra_mult_band_ind_supp=%u\n",
+ cm3->e_utra_mult_band_ind_supp);
+ printf("extended_tsc_set_cap_supp=%u\n",
+ cm3->extended_tsc_set_cap_supp);
+ printf("extended_earfcn_val_range=%u\n",
+ cm3->extended_earfcn_val_range);
+}
+
+static void test_decode_classmark3(void)
+{
+ struct gsm48_classmark3 cm3;
+ const uint8_t cm3_1[] = { 0x60, 0x14, 0x04, 0x2f, 0x65, 0x00, 0x20, 0x03, 0x40, 0x4a };
+ const uint8_t cm3_2[] = { 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55};
+ const uint8_t cm3_3[] = { 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa};
+
+ printf("=====cm3_1=====\n");
+ gsm48_decode_classmark3(&cm3, cm3_1, sizeof(cm3_1));
+ dump_cm3(&cm3);
+ printf("\n");
+
+ printf("=====cm3_2=====\n");
+ gsm48_decode_classmark3(&cm3, cm3_2, sizeof(cm3_2));
+ dump_cm3(&cm3);
+ printf("\n");
+
+ printf("=====cm3_3=====\n");
+ gsm48_decode_classmark3(&cm3, cm3_3, sizeof(cm3_3));
+ dump_cm3(&cm3);
+ printf("\n");
+}
+
static void test_mid_from_tmsi(void)
{
static const uint8_t res[] = { 0x17, 0x05, 0xf4, 0xaa, 0xbb, 0xcc, 0xdd };
@@ -943,7 +1154,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__);
@@ -957,7 +1168,7 @@ void test_struct_mobile_identity()
rc = osmo_mobile_identity_decode_from_l3(&mi, msg, false);
msgb_free(msg);
- printf("%s: rc = %d", t->label, rc);
+ printf("%s: %s", t->label, rc ? "rc != 0" : "rc == 0");
if (!rc) {
printf(", mi = %s", osmo_mobile_identity_to_str_c(OTC_SELECT, &mi));
}
@@ -966,7 +1177,7 @@ void test_struct_mobile_identity()
&& ((rc != 0) || !osmo_mobile_identity_cmp(&mi, &t->expect_mi))) {
printf(" ok");
} else {
- printf(" ERROR: Expected rc = %d", t->expect_rc);
+ printf(" ERROR: Got rc = %d, expected rc = %d", rc, t->expect_rc);
if (!t->expect_rc)
printf(", mi = %s", osmo_mobile_identity_to_str_c(OTC_SELECT, &t->expect_mi));
}
@@ -1105,7 +1316,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 };
@@ -1339,7 +1550,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;
@@ -1400,7 +1611,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)
@@ -1433,7 +1644,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];
@@ -1458,7 +1669,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;
@@ -1497,7 +1708,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;
@@ -1539,6 +1750,37 @@ static void test_power_ctrl()
VERIFY(rc, <, 0);
}
+static void test_rach_tx_integer_raw2val(void)
+{
+ unsigned int raw;
+ for (raw = 0; raw <= 0x0f; raw++) {
+ unsigned int val = rach_tx_integer_raw2val(raw);
+ printf("rach_tx_integer_raw2val(0x0%x): %u slots used to spread transmission\n",
+ raw, val);
+ }
+}
+
+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);
+ }
+ }
+}
+
int main(int argc, char **argv)
{
test_bearer_cap();
@@ -1550,12 +1792,15 @@ int main(int argc, char **argv)
test_bcd_number_encode_decode();
test_ra_cap();
test_lai_encode_decode();
+ test_decode_classmark3();
test_si_range_helpers();
test_arfcn_filter();
test_print_encoding();
test_range_encoding();
test_power_ctrl();
+ test_rach_tx_integer_raw2val();
+ test_gsm_gsmtime2fn();
return EXIT_SUCCESS;
}
diff --git a/tests/gsm0408/gsm0408_test.err b/tests/gsm0408/gsm0408_test.err
new file mode 100644
index 00000000..8aeda3ed
--- /dev/null
+++ b/tests/gsm0408/gsm0408_test.err
@@ -0,0 +1,3 @@
+Incorrect encoded result of Speech, without octet 3a:
+ should: 01 a0
+ is: 02 20 80
diff --git a/tests/gsm0408/gsm0408_test.ok b/tests/gsm0408/gsm0408_test.ok
index 3e6ae1f4..dc48f84a 100644
--- a/tests/gsm0408/gsm0408_test.ok
+++ b/tests/gsm0408/gsm0408_test.ok
@@ -1,5 +1,6 @@
Test `CSD 9600/V.110/transparent' passed
Test `Speech, all codecs' passed
+Test `Speech, without octet 3a' failed
Simple TMSI encoding test....passed
Simple IMSI encoding test....passed: [10] 17 08 99 10 07 00 00 00 64 02
@@ -140,55 +141,55 @@ Decoding zero length Mobile Identities
returned empty string
test_struct_mobile_identity()
-LU with IMSI 901700000004620: rc = 0, mi = IMSI-901700000004620 ok
-LU with TMSI 0x0980ad8a: rc = 0, mi = TMSI-0x0980AD8A ok
-LU with invalid MI type: rc = -22 ok
-LU with truncated IMSI MI: rc = -74 ok
-LU with too short IMSI MI (12345): rc = -74 ok
-LU with just long enough IMSI MI 123456: rc = 0, mi = IMSI-123456 ok
-LU with max length IMSI MI 123456789012345: rc = 0, mi = IMSI-123456789012345 ok
-LU with just too long IMSI MI 1234567890123456: rc = -74 ok
-LU with truncated TMSI MI: rc = -74 ok
-LU with odd length TMSI: rc = -74 ok
-LU with too long TMSI MI: rc = -74 ok
-LU with too short TMSI: rc = -74 ok
-CM Service Request with IMSI 123456: rc = 0, mi = IMSI-123456 ok
-CM Service Request with TMSI 0x5a42e404: rc = 0, mi = TMSI-0x5A42E404 ok
-CM Service Request with shorter CM2, with IMSI 123456: rc = 0, mi = IMSI-123456 ok
-CM Service Request with longer CM2, with IMSI 123456: rc = 0, mi = IMSI-123456 ok
-CM Service Request with shorter CM2, with TMSI 0x00000000: rc = 0, mi = TMSI-0x00000000 ok
-CM Service Request with invalid MI type: rc = -22 ok
-CM Service Request with truncated IMSI MI: rc = -74 ok
-CM Service Request with truncated TMSI MI: rc = -74 ok
-CM Service Request with odd length TMSI: rc = -74 ok
-CM Service Request with too long TMSI MI: rc = -74 ok
-CM Service Request with too short TMSI: rc = -74 ok
-CM Service Reestablish Request with TMSI 0x5a42e404: rc = 0, mi = TMSI-0x5A42E404 ok
-Paging Response with IMSI 1234567: rc = 0, mi = IMSI-1234567 ok
-Paging Response with TMSI 0xb48883de: rc = 0, mi = TMSI-0xB48883DE ok
-Paging Response with TMSI, with unused nibble not 0xf: rc = -74 ok
-Paging Response with too short IMEI (1234567): rc = -74 ok
-Paging Response with IMEI 123456789012345: rc = 0, mi = IMEI-123456789012345 ok
-Paging Response with IMEI 12345678901234 (no Luhn checksum): rc = 0, mi = IMEI-12345678901234 ok
-Paging Response with IMEISV 1234567890123456: rc = 0, mi = IMEI-SV-1234567890123456 ok
-Paging Response with too short IMEISV 123456789012345: rc = -74 ok
-Paging Response with too long IMEISV 12345678901234567: rc = -74 ok
-Paging Response with IMSI 123456789012345 and flipped ODD bit: rc = -74 ok
-IMSI-Detach with IMSI 901700000004620: rc = 0, mi = IMSI-901700000004620 ok
-IMSI-Detach with TMSI 0x0980ad8a: rc = 0, mi = TMSI-0x0980AD8A ok
-IMSI-Detach with invalid MI type: rc = -22 ok
-IMSI-Detach with truncated IMSI MI: rc = -74 ok
-IMSI-Detach with too short IMSI MI (12345): rc = -74 ok
-IMSI-Detach with just long enough IMSI MI 123456: rc = 0, mi = IMSI-123456 ok
-IMSI-Detach with max length IMSI MI 123456789012345: rc = 0, mi = IMSI-123456789012345 ok
-IMSI-Detach with just too long IMSI MI 1234567890123456: rc = -74 ok
-IMSI-Detach with truncated TMSI MI: rc = -74 ok
-IMSI-Detach with odd length TMSI: rc = -74 ok
-IMSI-Detach with too long TMSI MI: rc = -74 ok
-IMSI-Detach with too short TMSI: rc = -74 ok
-Identity Response with IMSI 901700000004620: rc = 0, mi = IMSI-901700000004620 ok
-Identity Response with IMEI 123456789012345: rc = 0, mi = IMEI-123456789012345 ok
-Identity Response with IMEISV 9876543210987654: rc = 0, mi = IMEI-SV-9876543210987654 ok
+LU with IMSI 901700000004620: rc == 0, mi = IMSI-901700000004620 ok
+LU with TMSI 0x0980ad8a: rc == 0, mi = TMSI-0x0980AD8A ok
+LU with invalid MI type: rc != 0 ok
+LU with truncated IMSI MI: rc != 0 ok
+LU with too short IMSI MI (12345): rc != 0 ok
+LU with just long enough IMSI MI 123456: rc == 0, mi = IMSI-123456 ok
+LU with max length IMSI MI 123456789012345: rc == 0, mi = IMSI-123456789012345 ok
+LU with just too long IMSI MI 1234567890123456: rc != 0 ok
+LU with truncated TMSI MI: rc != 0 ok
+LU with odd length TMSI: rc != 0 ok
+LU with too long TMSI MI: rc != 0 ok
+LU with too short TMSI: rc != 0 ok
+CM Service Request with IMSI 123456: rc == 0, mi = IMSI-123456 ok
+CM Service Request with TMSI 0x5a42e404: rc == 0, mi = TMSI-0x5A42E404 ok
+CM Service Request with shorter CM2, with IMSI 123456: rc == 0, mi = IMSI-123456 ok
+CM Service Request with longer CM2, with IMSI 123456: rc == 0, mi = IMSI-123456 ok
+CM Service Request with shorter CM2, with TMSI 0x00000000: rc == 0, mi = TMSI-0x00000000 ok
+CM Service Request with invalid MI type: rc != 0 ok
+CM Service Request with truncated IMSI MI: rc != 0 ok
+CM Service Request with truncated TMSI MI: rc != 0 ok
+CM Service Request with odd length TMSI: rc != 0 ok
+CM Service Request with too long TMSI MI: rc != 0 ok
+CM Service Request with too short TMSI: rc != 0 ok
+CM Service Reestablish Request with TMSI 0x5a42e404: rc == 0, mi = TMSI-0x5A42E404 ok
+Paging Response with IMSI 1234567: rc == 0, mi = IMSI-1234567 ok
+Paging Response with TMSI 0xb48883de: rc == 0, mi = TMSI-0xB48883DE ok
+Paging Response with TMSI, with unused nibble not 0xf: rc != 0 ok
+Paging Response with too short IMEI (1234567): rc != 0 ok
+Paging Response with IMEI 123456789012345: rc == 0, mi = IMEI-123456789012345 ok
+Paging Response with IMEI 12345678901234 (no Luhn checksum): rc == 0, mi = IMEI-12345678901234 ok
+Paging Response with IMEISV 1234567890123456: rc == 0, mi = IMEI-SV-1234567890123456 ok
+Paging Response with too short IMEISV 123456789012345: rc != 0 ok
+Paging Response with too long IMEISV 12345678901234567: rc != 0 ok
+Paging Response with IMSI 123456789012345 and flipped ODD bit: rc != 0 ok
+IMSI-Detach with IMSI 901700000004620: rc == 0, mi = IMSI-901700000004620 ok
+IMSI-Detach with TMSI 0x0980ad8a: rc == 0, mi = TMSI-0x0980AD8A ok
+IMSI-Detach with invalid MI type: rc != 0 ok
+IMSI-Detach with truncated IMSI MI: rc != 0 ok
+IMSI-Detach with too short IMSI MI (12345): rc != 0 ok
+IMSI-Detach with just long enough IMSI MI 123456: rc == 0, mi = IMSI-123456 ok
+IMSI-Detach with max length IMSI MI 123456789012345: rc == 0, mi = IMSI-123456789012345 ok
+IMSI-Detach with just too long IMSI MI 1234567890123456: rc != 0 ok
+IMSI-Detach with truncated TMSI MI: rc != 0 ok
+IMSI-Detach with odd length TMSI: rc != 0 ok
+IMSI-Detach with too long TMSI MI: rc != 0 ok
+IMSI-Detach with too short TMSI: rc != 0 ok
+Identity Response with IMSI 901700000004620: rc == 0, mi = IMSI-901700000004620 ok
+Identity Response with IMEI 123456789012345: rc == 0, mi = IMEI-123456789012345 ok
+Identity Response with IMEISV 9876543210987654: rc == 0, mi = IMEI-SV-9876543210987654 ok
BSD number encoding / decoding test
- Running test: regular 9-digit MSISDN
@@ -385,6 +386,348 @@ RA test...passed
Encoded 21 63 54 00 17
gsm48_decode_lai2() gives 123-456-23 (3-digit MNC)
passed
+=====cm3_1=====
+mult_band_supp=06
+a5_bits=00
+assoc_radio_cap_1=04
+assoc_radio_cap_2=01
+
+r_support.present=0
+r_support.r_gsm_assoc_radio_cap=00
+
+hscsd_mult_slot_cap.present=0
+hscsd_mult_slot_cap.mslot_class=00
+
+ucs2_treatment=0
+extended_meas_cap=0
+
+ms_meas_cap.present=0
+ms_meas_cap.sms_value=00
+ms_meas_cap.sm_value=00
+
+ms_pos_method_cap.present=1
+ms_pos_method_cap.method=01
+
+ecsd_multislot_cap.present=0
+ecsd_multislot_cap.mslot_class=00
+
+psk8_struct.present=1
+psk8_struct.mod_cap=1
+psk8_struct.rf_pwr_cap_1.present=1
+psk8_struct.rf_pwr_cap_1.value=02
+psk8_struct.rf_pwr_cap_2.present=1
+psk8_struct.rf_pwr_cap_2.value=02
+
+gsm_400_bands_supp.present=0
+gsm_400_bands_supp.value=00
+gsm_400_bands_supp.assoc_radio_cap=00
+
+gsm_850_assoc_radio_cap.present=1
+gsm_850_assoc_radio_cap.value=04
+
+gsm_1900_assoc_radio_cap.present=0
+gsm_1900_assoc_radio_cap.value=00
+
+umts_fdd_rat_cap=0
+umts_tdd_rat_cap=0
+cdma200_rat_cap=0
+
+dtm_gprs_multislot_cap.present=0
+dtm_gprs_multislot_cap.mslot_class=00
+dtm_gprs_multislot_cap.single_slot_dtm=0
+dtm_gprs_multislot_cap.dtm_egprs_multislot_cap.present=0
+dtm_gprs_multislot_cap.dtm_egprs_multislot_cap.mslot_class=00
+
+single_band_supp.present=0
+single_band_supp.value=0
+
+gsm_750_assoc_radio_cap.present=0
+gsm_750_assoc_radio_cap.value=00
+
+umts_1_28_mcps_tdd_rat_cap=0
+geran_feature_package=1
+
+extended_dtm_gprs_multislot_cap.present=0
+extended_dtm_gprs_multislot_cap.mslot_class=00
+extended_dtm_gprs_multislot_cap.dtm_egprs_multislot_cap.present=0
+extended_dtm_gprs_multislot_cap.dtm_egprs_multislot_cap.mslot_class=00
+
+high_multislot_cap.present=0
+high_multislot_cap.value=00
+
+geran_feature_package_2=0
+gmsk_multislot_power_prof=00
+psk8_multislot_power_prof=00
+
+t_gsm_400_bands_supp.present=0
+t_gsm_400_bands_supp.value=00
+t_gsm_400_bands_supp.assoc_radio_cap=00
+
+dl_advanced_rx_perf=01
+dtm_enhancements_cap=1
+
+dtm_gprs_high_multislot_cap.present=0
+dtm_gprs_high_multislot_cap.mslot_class=00
+dtm_gprs_high_multislot_cap.offset_required=0
+dtm_gprs_high_multislot_cap.dtm_egprs_high_multislot_cap.present=0
+dtm_gprs_high_multislot_cap.dtm_egprs_high_multislot_cap.mslot_class=00
+
+repeated_acch_capability=1
+
+gsm_710_assoc_radio_cap.present=0
+gsm_710_assoc_radio_cap.value=00
+
+t_gsm_810_assoc_radio_cap.present=0
+t_gsm_810_assoc_radio_cap.value=00
+
+ciphering_mode_setting_cap=0
+add_pos_cap=0
+e_utra_fdd_supp=0
+e_utra_tdd_supp=0
+e_utra_meas_rep_supp=0
+prio_resel_supp=1
+utra_csg_cells_rep=0
+vamos_level=01
+tighter_capability=01
+sel_ciph_dl_sacch=0
+cs_ps_srvcc_geran_utra=00
+cs_ps_srvcc_geran_eutra=00
+geran_net_sharing=0
+e_utra_wb_rsrq_meas_supp=0
+er_band_support=0
+utra_mult_band_ind_supp=0
+e_utra_mult_band_ind_supp=0
+extended_tsc_set_cap_supp=0
+extended_earfcn_val_range=0
+
+=====cm3_2=====
+mult_band_supp=05
+a5_bits=05
+assoc_radio_cap_1=05
+assoc_radio_cap_2=05
+
+r_support.present=0
+r_support.r_gsm_assoc_radio_cap=00
+
+hscsd_mult_slot_cap.present=1
+hscsd_mult_slot_cap.mslot_class=0a
+
+ucs2_treatment=1
+extended_meas_cap=0
+
+ms_meas_cap.present=1
+ms_meas_cap.sms_value=05
+ms_meas_cap.sm_value=05
+
+ms_pos_method_cap.present=0
+ms_pos_method_cap.method=00
+
+ecsd_multislot_cap.present=1
+ecsd_multislot_cap.mslot_class=0a
+
+psk8_struct.present=1
+psk8_struct.mod_cap=0
+psk8_struct.rf_pwr_cap_1.present=1
+psk8_struct.rf_pwr_cap_1.value=01
+psk8_struct.rf_pwr_cap_2.present=0
+psk8_struct.rf_pwr_cap_2.value=00
+
+gsm_400_bands_supp.present=1
+gsm_400_bands_supp.value=01
+gsm_400_bands_supp.assoc_radio_cap=05
+
+gsm_850_assoc_radio_cap.present=0
+gsm_850_assoc_radio_cap.value=00
+
+gsm_1900_assoc_radio_cap.present=1
+gsm_1900_assoc_radio_cap.value=05
+
+umts_fdd_rat_cap=0
+umts_tdd_rat_cap=1
+cdma200_rat_cap=0
+
+dtm_gprs_multislot_cap.present=1
+dtm_gprs_multislot_cap.mslot_class=01
+dtm_gprs_multislot_cap.single_slot_dtm=0
+dtm_gprs_multislot_cap.dtm_egprs_multislot_cap.present=1
+dtm_gprs_multislot_cap.dtm_egprs_multislot_cap.mslot_class=01
+
+single_band_supp.present=0
+single_band_supp.value=0
+
+gsm_750_assoc_radio_cap.present=1
+gsm_750_assoc_radio_cap.value=05
+
+umts_1_28_mcps_tdd_rat_cap=0
+geran_feature_package=1
+
+extended_dtm_gprs_multislot_cap.present=0
+extended_dtm_gprs_multislot_cap.mslot_class=00
+extended_dtm_gprs_multislot_cap.dtm_egprs_multislot_cap.present=0
+extended_dtm_gprs_multislot_cap.dtm_egprs_multislot_cap.mslot_class=00
+
+high_multislot_cap.present=1
+high_multislot_cap.value=01
+
+geran_feature_package_2=1
+gmsk_multislot_power_prof=01
+psk8_multislot_power_prof=01
+
+t_gsm_400_bands_supp.present=0
+t_gsm_400_bands_supp.value=00
+t_gsm_400_bands_supp.assoc_radio_cap=00
+
+dl_advanced_rx_perf=01
+dtm_enhancements_cap=0
+
+dtm_gprs_high_multislot_cap.present=1
+dtm_gprs_high_multislot_cap.mslot_class=02
+dtm_gprs_high_multislot_cap.offset_required=1
+dtm_gprs_high_multislot_cap.dtm_egprs_high_multislot_cap.present=0
+dtm_gprs_high_multislot_cap.dtm_egprs_high_multislot_cap.mslot_class=00
+
+repeated_acch_capability=1
+
+gsm_710_assoc_radio_cap.present=0
+gsm_710_assoc_radio_cap.value=00
+
+t_gsm_810_assoc_radio_cap.present=1
+t_gsm_810_assoc_radio_cap.value=05
+
+ciphering_mode_setting_cap=0
+add_pos_cap=1
+e_utra_fdd_supp=0
+e_utra_tdd_supp=0
+e_utra_meas_rep_supp=0
+prio_resel_supp=0
+utra_csg_cells_rep=0
+vamos_level=00
+tighter_capability=00
+sel_ciph_dl_sacch=0
+cs_ps_srvcc_geran_utra=00
+cs_ps_srvcc_geran_eutra=00
+geran_net_sharing=0
+e_utra_wb_rsrq_meas_supp=0
+er_band_support=0
+utra_mult_band_ind_supp=0
+e_utra_mult_band_ind_supp=0
+extended_tsc_set_cap_supp=0
+extended_earfcn_val_range=0
+
+=====cm3_3=====
+mult_band_supp=02
+a5_bits=0a
+assoc_radio_cap_1=0a
+assoc_radio_cap_2=00
+
+r_support.present=1
+r_support.r_gsm_assoc_radio_cap=02
+
+hscsd_mult_slot_cap.present=1
+hscsd_mult_slot_cap.mslot_class=0a
+
+ucs2_treatment=1
+extended_meas_cap=0
+
+ms_meas_cap.present=1
+ms_meas_cap.sms_value=05
+ms_meas_cap.sm_value=05
+
+ms_pos_method_cap.present=0
+ms_pos_method_cap.method=00
+
+ecsd_multislot_cap.present=1
+ecsd_multislot_cap.mslot_class=0a
+
+psk8_struct.present=1
+psk8_struct.mod_cap=0
+psk8_struct.rf_pwr_cap_1.present=1
+psk8_struct.rf_pwr_cap_1.value=01
+psk8_struct.rf_pwr_cap_2.present=0
+psk8_struct.rf_pwr_cap_2.value=00
+
+gsm_400_bands_supp.present=1
+gsm_400_bands_supp.value=01
+gsm_400_bands_supp.assoc_radio_cap=05
+
+gsm_850_assoc_radio_cap.present=0
+gsm_850_assoc_radio_cap.value=00
+
+gsm_1900_assoc_radio_cap.present=1
+gsm_1900_assoc_radio_cap.value=05
+
+umts_fdd_rat_cap=0
+umts_tdd_rat_cap=1
+cdma200_rat_cap=0
+
+dtm_gprs_multislot_cap.present=1
+dtm_gprs_multislot_cap.mslot_class=01
+dtm_gprs_multislot_cap.single_slot_dtm=0
+dtm_gprs_multislot_cap.dtm_egprs_multislot_cap.present=1
+dtm_gprs_multislot_cap.dtm_egprs_multislot_cap.mslot_class=01
+
+single_band_supp.present=0
+single_band_supp.value=0
+
+gsm_750_assoc_radio_cap.present=1
+gsm_750_assoc_radio_cap.value=05
+
+umts_1_28_mcps_tdd_rat_cap=0
+geran_feature_package=1
+
+extended_dtm_gprs_multislot_cap.present=0
+extended_dtm_gprs_multislot_cap.mslot_class=00
+extended_dtm_gprs_multislot_cap.dtm_egprs_multislot_cap.present=0
+extended_dtm_gprs_multislot_cap.dtm_egprs_multislot_cap.mslot_class=00
+
+high_multislot_cap.present=1
+high_multislot_cap.value=01
+
+geran_feature_package_2=1
+gmsk_multislot_power_prof=01
+psk8_multislot_power_prof=01
+
+t_gsm_400_bands_supp.present=0
+t_gsm_400_bands_supp.value=00
+t_gsm_400_bands_supp.assoc_radio_cap=00
+
+dl_advanced_rx_perf=01
+dtm_enhancements_cap=0
+
+dtm_gprs_high_multislot_cap.present=1
+dtm_gprs_high_multislot_cap.mslot_class=02
+dtm_gprs_high_multislot_cap.offset_required=1
+dtm_gprs_high_multislot_cap.dtm_egprs_high_multislot_cap.present=0
+dtm_gprs_high_multislot_cap.dtm_egprs_high_multislot_cap.mslot_class=00
+
+repeated_acch_capability=1
+
+gsm_710_assoc_radio_cap.present=0
+gsm_710_assoc_radio_cap.value=00
+
+t_gsm_810_assoc_radio_cap.present=1
+t_gsm_810_assoc_radio_cap.value=04
+
+ciphering_mode_setting_cap=0
+add_pos_cap=0
+e_utra_fdd_supp=0
+e_utra_tdd_supp=0
+e_utra_meas_rep_supp=0
+prio_resel_supp=0
+utra_csg_cells_rep=0
+vamos_level=00
+tighter_capability=00
+sel_ciph_dl_sacch=0
+cs_ps_srvcc_geran_utra=00
+cs_ps_srvcc_geran_eutra=00
+geran_net_sharing=0
+e_utra_wb_rsrq_meas_supp=0
+er_band_support=0
+utra_mult_band_ind_supp=0
+e_utra_mult_band_ind_supp=0
+extended_tsc_set_cap_supp=0
+extended_earfcn_val_range=0
+
Element is: 2 => freqs[i] = 121
Element is: 2 => freqs[i] = 1
Element is: 0 => freqs[i] = 68
@@ -449,3 +792,19 @@ Random range test: range 127, max num ARFCNs 29
Random range test: range 255, max num ARFCNs 22
Random range test: range 511, max num ARFCNs 18
Random range test: range 1023, max num ARFCNs 16
+rach_tx_integer_raw2val(0x00): 3 slots used to spread transmission
+rach_tx_integer_raw2val(0x01): 4 slots used to spread transmission
+rach_tx_integer_raw2val(0x02): 5 slots used to spread transmission
+rach_tx_integer_raw2val(0x03): 6 slots used to spread transmission
+rach_tx_integer_raw2val(0x04): 7 slots used to spread transmission
+rach_tx_integer_raw2val(0x05): 8 slots used to spread transmission
+rach_tx_integer_raw2val(0x06): 9 slots used to spread transmission
+rach_tx_integer_raw2val(0x07): 10 slots used to spread transmission
+rach_tx_integer_raw2val(0x08): 11 slots used to spread transmission
+rach_tx_integer_raw2val(0x09): 12 slots used to spread transmission
+rach_tx_integer_raw2val(0x0a): 14 slots used to spread transmission
+rach_tx_integer_raw2val(0x0b): 16 slots used to spread transmission
+rach_tx_integer_raw2val(0x0c): 20 slots used to spread transmission
+rach_tx_integer_raw2val(0x0d): 25 slots used to spread transmission
+rach_tx_integer_raw2val(0x0e): 32 slots used to spread transmission
+rach_tx_integer_raw2val(0x0f): 50 slots used to spread transmission
diff --git a/tests/gsm0502/gsm0502_test.c b/tests/gsm0502/gsm0502_test.c
index a950c6c1..e9deaa9f 100644
--- a/tests/gsm0502/gsm0502_test.c
+++ b/tests/gsm0502/gsm0502_test.c
@@ -15,10 +15,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <string.h>
@@ -90,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;
diff --git a/tests/gsm0808/gsm0808_test.c b/tests/gsm0808/gsm0808_test.c
index dd2ffbe9..aa086a23 100644
--- a/tests/gsm0808/gsm0808_test.c
+++ b/tests/gsm0808/gsm0808_test.c
@@ -12,10 +12,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <osmocom/gsm/gsm0808.h>
@@ -115,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,
@@ -152,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;
@@ -163,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;
@@ -175,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;
@@ -186,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;
@@ -197,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;
@@ -208,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;
@@ -219,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,
@@ -259,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 };
@@ -316,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;
@@ -331,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;
@@ -346,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 };
@@ -368,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;
@@ -379,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 };
@@ -428,7 +424,60 @@ static void test_dec_confusion()
osmo_hexdump(diag->msg, diag_len-2));
}
-static void test_create_ass()
+/* Test Perform Location Report SYS#5891 */
+static void test_dec_perform_location_report_sys5891(void)
+{
+/* Message Type Perform Location Request
+ Location Type
+ Element ID: 0x44
+ Length: 1
+ Location Information: current geographic location (0x00)
+ Cell Identifier/CI (25911)
+ Element ID: 0x05
+ Length: 8
+ 0000 .... = Spare bit(s): 0x00
+ .... 0000 = Cell identification discriminator: The whole Cell Global Identification, CGI, is used to identify the cells. (0)
+ Mobile Country Code (MCC): (removed))
+ Mobile Network Code (MNC): (removed))
+ Cell LAC: 0x001e (30)
+ Cell CI: 0x6537 (25911)
+ LCS Client Type
+ Element ID: 0x48
+ Length: 1
+ 0011 .... = Client Category: Emergency Services (0x03)
+ .... 0000 = Client Subtype: unspecified (0x00)
+ LCS Priority
+ Element ID: 0x43
+ Length: 1
+ Periodicity: highest (0)
+ LCS QoS
+ Element ID: 0x3e
+ Length: 4
+ 0000 00.. = Spare: 0x00
+ .... ..0. = Velocity Requested: do not report velocity (0x00)
+ .... ...0 = Vertical Coordinate Indicator: vertical coordinate not requested (0x00)
+ 1... .... = Horizontal Accuracy Indicator: horizontal accuracy is specified (0x01)
+ .001 0010 = Horizontal Accuracy: 0x12
+ 0... .... = Vertical Accuracy Indicator: vertical accuracy is not specified (0x00)
+ .000 0000 = Spare: 0x00
+ 00.. .... = Response Time Category: Response Time is not specified (0x00)
+*/
+ const uint8_t hex[] = {
+ 0x2b, 0x44, 0x01, 0x00, 0x05, 0x08, 0x00, 0xab, 0xbc, 0xcd, 0x00, 0x1e,
+ 0x65, 0x37, 0x48, 0x01, 0x30, 0x43, 0x01, 0x00, 0x3e, 0x04, 0x00, 0x92,
+ 0x00, 0x00
+ };
+
+ struct tlv_parsed tp;
+ int rc;
+
+ printf("Testing decoding Perform Location Report SYS#5891\n");
+
+ rc = tlv_parse(&tp, gsm0808_att_tlvdef(), hex+1, sizeof(hex)-1, 0, 0);
+ OSMO_ASSERT(rc == 5);
+}
+
+static void test_create_ass(void)
{
static const uint8_t res1[] =
{ 0x00, 0x0a, 0x01, 0x0b, 0x04, 0x01, 0x0b, 0xa1, 0x25, 0x01, 0x00,
@@ -478,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,
@@ -499,7 +548,7 @@ static void test_create_ass2()
GSM0808_SCT_CSD | 0x90,
0xc0,
GSM0808_IE_CALL_ID,
- 0xce, 0xfa, 0xad, 0xde, /* CallID */
+ 0xce, 0xfa, 0xad, 0xde, /* CallID */
0x83, 0x45, 0x45, 0x45, 0x45, 0x45, 0x45, 0x45, 0x45, 0x45, 0x45, 0x45, 0x45, 0x45, 0x45, 0x45, 0x45, /* Kc */
GSM0808_IE_GLOBAL_CALL_REF, 0x0d, /* GCR, length */
0x03, 0x44, 0x44, 0x44, /* GCR, Net ID */
@@ -555,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,
@@ -574,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;
@@ -611,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[] = {
@@ -629,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,
@@ -655,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;
@@ -666,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,
@@ -706,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;
@@ -723,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;
@@ -740,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,
@@ -818,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;
@@ -846,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;
@@ -875,7 +924,29 @@ static void test_enc_dec_aoip_trasp_addr_v6()
msgb_free(msg);
}
-static void test_gsm0808_enc_dec_speech_codec()
+static void test_enc_aoip_trasp_addr_msg_too_small(void)
+{
+ struct msgb *msg;
+ struct sockaddr_storage enc_addr;
+ struct sockaddr_in enc_addr_in;
+ uint8_t rc_enc;
+
+ memset(&enc_addr_in, 0, sizeof(enc_addr_in));
+ enc_addr_in.sin_family = AF_INET;
+ enc_addr_in.sin_port = htons(1234);
+ inet_aton("255.0.255.255", &enc_addr_in.sin_addr);
+
+ memset(&enc_addr, 0, sizeof(enc_addr));
+ memcpy(&enc_addr, &enc_addr_in, sizeof(enc_addr_in));
+
+ msg = msgb_alloc(7, "output buffer");
+ rc_enc = gsm0808_enc_aoip_trasp_addr(msg, &enc_addr);
+ OSMO_ASSERT(rc_enc == 0);
+
+ msgb_free(msg);
+}
+
+static void test_gsm0808_enc_dec_speech_codec(void)
{
struct gsm0808_speech_codec enc_sc = {
.pi = true,
@@ -888,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);
@@ -900,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,
@@ -914,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);
@@ -925,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,
@@ -939,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);
@@ -950,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 = {
@@ -982,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);
@@ -993,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,
@@ -1004,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);
@@ -1015,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,
@@ -1046,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 },
@@ -1076,7 +1287,20 @@ static void test_gsm0808_enc_dec_encrypt_info()
msgb_free(msg);
}
-static void test_gsm0808_enc_dec_cell_id_list_lac()
+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};
+ struct gsm0808_cell_id_list2 dec_cil;
+ int rc;
+
+ rc = gsm0808_dec_cell_id_list2(&dec_cil, enc_cil, sizeof(enc_cil));
+ OSMO_ASSERT(rc == sizeof(enc_cil));
+ OSMO_ASSERT(dec_cil.id_discr = CELL_IDENT_SAI);
+ OSMO_ASSERT(dec_cil.id_list_len = 1);
+}
+
+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;
@@ -1103,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;
@@ -1132,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;
@@ -1168,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;
@@ -1191,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;
@@ -1247,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;
@@ -1281,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;
@@ -1322,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;
@@ -1395,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 = {
@@ -1577,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,
@@ -1603,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,
@@ -1625,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,
@@ -1647,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,
@@ -1678,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,
@@ -1701,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,
@@ -1728,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,
@@ -1760,29 +1984,123 @@ static void test_gsm0808_enc_dec_cell_id_global()
msgb_free(msg);
}
+static void test_gsm0808_enc_dec_cell_id_global_ps(void)
+{
+ struct gsm0808_cell_id enc_cgi = {
+ .id_discr = CELL_IDENT_WHOLE_GLOBAL,
+ .id.global = {
+ .lai = {
+ .plmn = { .mcc = 123, .mnc = 456 },
+ .lac = 0x2342
+ },
+ .cell_identity = 0x423,
+ }
+ };
+ struct gsm0808_cell_id enc_cgi_ps = {
+ .id_discr = CELL_IDENT_WHOLE_GLOBAL_PS,
+ .id.global_ps = {
+ .rai = {
+ .lac = {
+ .plmn = { .mcc = 123, .mnc = 456 },
+ .lac = 0x2342
+ },
+ .rac = 0xcc,
+ },
+ .cell_identity = 0x423,
+ }
+ };
+ struct msgb *msg_cgi, *msg_cgi_ps;
+ uint8_t rc_enc;
+
+ msg_cgi = msgb_alloc(1024, "output buffer (CGI)");
+ rc_enc = gsm0808_enc_cell_id(msg_cgi, &enc_cgi);
+ OSMO_ASSERT(rc_enc > 0);
+
+ msg_cgi_ps = msgb_alloc(1024, "output buffer (CGI-PS)");
+ rc_enc = gsm0808_enc_cell_id(msg_cgi_ps, &enc_cgi_ps);
+ OSMO_ASSERT(rc_enc > 0);
+
+ OSMO_ASSERT(msgb_eq(msg_cgi, msg_cgi_ps));
+
+ msgb_free(msg_cgi);
+ 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");
}
@@ -1974,7 +2292,7 @@ static void test_gsm0808_sc_cfg_from_gsm48_mr_cfg(void)
cfg.m10_2 = 0;
cfg.m12_2 = 0;
test_gsm0808_sc_cfg_from_gsm48_mr_cfg_single(&cfg);
-
+
}
static void test_gsm48_mr_cfg_from_gsm0808_sc_cfg_single(uint16_t s15_s0)
@@ -1983,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");
@@ -2004,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");
@@ -2189,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;
@@ -2352,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;
@@ -2436,12 +2746,17 @@ int main(int argc, char **argv)
test_enc_dec_aoip_trasp_addr_v4();
test_enc_dec_aoip_trasp_addr_v6();
+ test_enc_aoip_trasp_addr_msg_too_small();
test_gsm0808_enc_dec_speech_codec();
test_gsm0808_enc_dec_speech_codec_ext_with_cfg();
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();
@@ -2452,6 +2767,7 @@ int main(int argc, char **argv)
test_gsm0808_enc_dec_cell_id_list_multi_ci();
test_gsm0808_enc_dec_cell_id_list_multi_lac_and_ci();
test_gsm0808_enc_dec_cell_id_list_multi_global();
+ test_gsm0808_dec_cell_id_list_srvcc();
test_cell_id_list_add();
@@ -2462,6 +2778,7 @@ int main(int argc, char **argv)
test_gsm0808_enc_dec_cell_id_ci();
test_gsm0808_enc_dec_cell_id_lac_and_ci();
test_gsm0808_enc_dec_cell_id_global();
+ test_gsm0808_enc_dec_cell_id_global_ps();
test_gsm0808_sc_cfg_from_gsm48_mr_cfg();
test_gsm48_mr_cfg_from_gsm0808_sc_cfg();
@@ -2472,6 +2789,7 @@ int main(int argc, char **argv)
test_gsm0808_cell_id_to_from_cgi();
test_dec_confusion();
+ test_dec_perform_location_report_sys5891();
printf("Done\n");
return EXIT_SUCCESS;
diff --git a/tests/gsm0808/gsm0808_test.ok b/tests/gsm0808/gsm0808_test.ok
index eaae7a69..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!
@@ -916,4 +916,5 @@ Testing decoding CONFUSION
Diagnostics error octet location 0 (Error location not determined)
Diagnostics error bit location 15 (Reserved value)
Diagnostics message that provoked the error: 00 03 25 03 25
+Testing decoding Perform Location Report SYS#5891
Done
diff --git a/tests/gsm23003/gsm23003_test.c b/tests/gsm23003/gsm23003_test.c
index eac5a115..77d3173e 100644
--- a/tests/gsm23003/gsm23003_test.c
+++ b/tests/gsm23003/gsm23003_test.c
@@ -15,10 +15,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <stdio.h>
@@ -58,7 +54,7 @@ static struct {
{ NULL, false },
};
-bool test_valid_imsi()
+bool test_valid_imsi(void)
{
int i;
bool pass = true;
@@ -101,7 +97,7 @@ static struct {
{ NULL, false },
};
-bool test_valid_msisdn()
+bool test_valid_msisdn(void)
{
int i;
bool pass = true;
@@ -142,7 +138,7 @@ static struct {
{ NULL, false, false },
};
-bool test_valid_imei()
+bool test_valid_imei(void)
{
int i;
bool pass = true;
@@ -189,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;
@@ -213,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 },
@@ -230,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 },
@@ -256,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 14c5ce38..6cbdbeb2 100644
--- a/tests/gsm23236/gsm23236_test.c
+++ b/tests/gsm23236/gsm23236_test.c
@@ -15,10 +15,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <stdio.h>
@@ -142,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;
@@ -226,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__);
@@ -331,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__);
@@ -361,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__);
@@ -549,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 };
@@ -598,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 44c3453c..6598f894 100644
--- a/tests/gsm29205/gsm29205_test.c
+++ b/tests/gsm29205/gsm29205_test.c
@@ -12,10 +12,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <osmocom/gsm/gsm29205.h>
@@ -32,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/test_frame_csd.c b/tests/gsm44021/test_frame_csd.c
new file mode 100644
index 00000000..98efdacb
--- /dev/null
+++ b/tests/gsm44021/test_frame_csd.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/test_frame_csd.ok b/tests/gsm44021/test_frame_csd.ok
new file mode 100644
index 00000000..1a5d3f25
--- /dev/null
+++ b/tests/gsm44021/test_frame_csd.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
new file mode 100644
index 00000000..bf53212e
--- /dev/null
+++ b/tests/gsm48/rest_octets_test.c
@@ -0,0 +1,116 @@
+/*
+ * (C) 2021 by sysmocom - s.m.f.c. GmbH <info@sysmocom.de>
+ * Author: Pau Espin Pedrol <pespin@sysmocom.de>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <osmocom/gsm/protocol/gsm_04_08.h>
+#include <osmocom/gsm/gsm48_rest_octets.h>
+
+struct si13_test {
+ const char *name;
+ const struct osmo_gsm48_si13_info si;
+ int enc_rc;
+ int dec_rc;
+ void (*post_dec_cb)(const struct si13_test *test, const struct osmo_gsm48_si13_info* dec);
+};
+
+void post_dec_cb_test_alpha(const struct si13_test *test, const struct osmo_gsm48_si13_info* dec)
+{
+ OSMO_ASSERT(test->si.pwr_ctrl_pars.alpha == dec->pwr_ctrl_pars.alpha);
+}
+
+static const struct si13_test test_si13_arr[] = {
+ {
+ .name = "test alpha",
+ .si = {
+ .cell_opts = {
+ .nmo = GPRS_NMO_II,
+ .t3168 = 2000,
+ .t3192 = 1500,
+ .drx_timer_max = 3,
+ .bs_cv_max = 15,
+ .ctrl_ack_type_use_block = true,
+ .ext_info_present = 0,
+ .ext_info = {
+ .egprs_supported = 1,
+ .use_egprs_p_ch_req = 1,
+ .bep_period = 5,
+ .pfc_supported = 0,
+ .dtm_supported = 0,
+ .bss_paging_coordination = 0,
+ .ccn_active = true,
+ },
+ },
+ .pwr_ctrl_pars = {
+ .alpha = 5,
+ .t_avg_w = 16,
+ .t_avg_t = 16,
+ .pc_meas_chan = 0,
+ .n_avg_i = 8,
+ },
+ .bcch_change_mark = 1,
+ .si_change_field = 0,
+ .rac = 0x03,
+ .spgc_ccch_sup = 0,
+ .net_ctrl_ord = 0,
+ .prio_acc_thr = 6,
+ },
+ .enc_rc = 20,
+ .dec_rc = 71,
+ .post_dec_cb = post_dec_cb_test_alpha,
+ },
+};
+
+static void test_si13(void)
+{
+ int i, rc;
+ uint8_t data[GSM_MACBLOCK_LEN];
+ struct osmo_gsm48_si13_info si13;
+
+ for (i = 0; i < ARRAY_SIZE(test_si13_arr); i++) {
+ memset(data, 0, sizeof(data));
+ rc = osmo_gsm48_rest_octets_si13_encode(data, &test_si13_arr[i].si);
+ if (rc >= 0) {
+ printf("si13_encode (%d): %s\n", rc, osmo_hexdump(data, rc));
+ } else {
+ printf("si13_encode failed (%d)\n", rc);
+ }
+ OSMO_ASSERT(rc == test_si13_arr[i].enc_rc);
+ if (rc <= 0)
+ continue;
+ memset(&si13, 0 , sizeof(si13));
+ rc = osmo_gsm48_rest_octets_si13_decode(&si13, data);
+ if (rc >= 0) {
+ printf("si13_decode (%d)\n", rc);
+ } else {
+ printf("si13_decode failed (%d)\n", rc);
+ }
+ OSMO_ASSERT(rc == test_si13_arr[i].dec_rc);
+ if (test_si13_arr[i].post_dec_cb) {
+ test_si13_arr[i].post_dec_cb(&test_si13_arr[i], &si13);
+ }
+ }
+}
+
+int main(int argc, char **argv)
+{
+ test_si13();
+
+ return EXIT_SUCCESS;
+}
diff --git a/tests/gsm48/rest_octets_test.ok b/tests/gsm48/rest_octets_test.ok
new file mode 100644
index 00000000..54204f24
--- /dev/null
+++ b/tests/gsm48/rest_octets_test.ok
@@ -0,0 +1,2 @@
+si13_encode (20): 90 00 d8 5a 6f c9 e5 84 10 ab 2b 2b 2b 2b 2b 2b 2b 2b 2b 2b
+si13_decode (71)
diff --git a/tests/gsup/gsup_test.c b/tests/gsup/gsup_test.c
index f34ac7a6..3d7ea401 100644
--- a/tests/gsup/gsup_test.c
+++ b/tests/gsup/gsup_test.c
@@ -749,9 +749,10 @@ int main(int argc, char **argv)
{
void *ctx = talloc_named_const(NULL, 0, "gsup_test");
osmo_init_logging2(ctx, &info);
- log_set_print_filename(osmo_stderr_target, 0);
+ log_set_print_filename2(osmo_stderr_target, LOG_FILENAME_NONE);
log_set_print_timestamp(osmo_stderr_target, 0);
log_set_use_color(osmo_stderr_target, 0);
+ log_set_print_category_hex(osmo_stderr_target, 0);
log_set_print_category(osmo_stderr_target, 1);
test_gsup_messages_dec_enc();
diff --git a/tests/i460_mux/i460_mux_test.c b/tests/i460_mux/i460_mux_test.c
index 9d5fcf78..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)
@@ -395,4 +395,5 @@ int main(int argc, char **argv)
test_16k_subchan();
test_8k_subchan();
test_unused_subchan();
+ return 0;
}
diff --git a/tests/it_q/it_q_test.c b/tests/it_q/it_q_test.c
new file mode 100644
index 00000000..28e32d89
--- /dev/null
+++ b/tests/it_q/it_q_test.c
@@ -0,0 +1,120 @@
+#include <stdio.h>
+#include <errno.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/it_q.h>
+
+struct it_q_test1 {
+ struct llist_head list;
+ int *foo;
+};
+
+struct it_q_test2 {
+ int foo;
+ struct llist_head list;
+};
+
+#define ENTER_TC printf("\n== Entering test case %s\n", __func__)
+
+static void tc_alloc(void)
+{
+ struct osmo_it_q *q1, *q2;
+
+ ENTER_TC;
+
+ printf("allocating q1\n");
+ q1 = osmo_it_q_alloc(OTC_GLOBAL, "q1", 3, NULL, NULL);
+ OSMO_ASSERT(q1);
+
+ /* ensure that no duplicate allocation for the */
+ printf("attempting duplicate allocation of qa\n");
+ q2 = osmo_it_q_alloc(OTC_GLOBAL, "q1", 3, NULL, NULL);
+ OSMO_ASSERT(!q2);
+
+ /* ensure that same name can be re-created after destroying old one */
+ osmo_it_q_destroy(q1);
+ printf("re-allocating q1\n");
+ q1 = osmo_it_q_alloc(OTC_GLOBAL, "q1", 3, NULL, NULL);
+ OSMO_ASSERT(q1);
+
+ osmo_it_q_destroy(q1);
+}
+
+static void tc_queue_length(void)
+{
+ struct osmo_it_q *q1;
+ unsigned int qlen = 3;
+ struct it_q_test1 *item;
+ int i, rc;
+
+ ENTER_TC;
+
+ printf("allocating q1\n");
+ q1 = osmo_it_q_alloc(OTC_GLOBAL, "q1", qlen, NULL, NULL);
+ OSMO_ASSERT(q1);
+
+ printf("adding queue entries up to the limit\n");
+ for (i = 0; i < qlen; i++) {
+ item = talloc_zero(OTC_GLOBAL, struct it_q_test1);
+ rc = osmo_it_q_enqueue(q1, item, list);
+ OSMO_ASSERT(rc == 0);
+ }
+ printf("attempting to add more than the limit\n");
+ item = talloc_zero(OTC_GLOBAL, struct it_q_test1);
+ rc = osmo_it_q_enqueue(q1, item, list);
+ OSMO_ASSERT(rc == -ENOSPC);
+
+ osmo_it_q_destroy(q1);
+}
+
+static int g_read_cb_count;
+
+static void q_read_cb(struct osmo_it_q *q, struct llist_head *item)
+{
+ struct it_q_test1 *it = container_of(item, struct it_q_test1, list);
+ *it->foo += 1;
+ talloc_free(item);
+}
+
+static void tc_eventfd(void)
+{
+ struct osmo_it_q *q1;
+ unsigned int qlen = 30;
+ struct it_q_test1 *item;
+ int i, rc;
+
+ ENTER_TC;
+
+ printf("allocating q1\n");
+ q1 = osmo_it_q_alloc(OTC_GLOBAL, "q1", qlen, q_read_cb, NULL);
+ OSMO_ASSERT(q1);
+ osmo_fd_register(&q1->event_ofd);
+
+ /* ensure read-cb isn't called unless we enqueue something */
+ osmo_select_main(1);
+ OSMO_ASSERT(g_read_cb_count == 0);
+
+ /* ensure read-cb is called for each enqueued msg once */
+ printf("adding %u queue entries up to the limit\n", qlen);
+ for (i = 0; i < qlen; i++) {
+ item = talloc_zero(OTC_GLOBAL, struct it_q_test1);
+ item->foo = &g_read_cb_count;
+ rc = osmo_it_q_enqueue(q1, item, list);
+ OSMO_ASSERT(rc == 0);
+ }
+
+ osmo_select_main(1);
+ printf("%u entries were dequeued\n", qlen);
+ OSMO_ASSERT(g_read_cb_count == qlen);
+
+ osmo_it_q_destroy(q1);
+}
+
+int main(int argc, char **argv)
+{
+ tc_alloc();
+ tc_queue_length();
+ tc_eventfd();
+ return 0;
+}
diff --git a/tests/it_q/it_q_test.ok b/tests/it_q/it_q_test.ok
new file mode 100644
index 00000000..7f102c61
--- /dev/null
+++ b/tests/it_q/it_q_test.ok
@@ -0,0 +1,15 @@
+
+== Entering test case tc_alloc
+allocating q1
+attempting duplicate allocation of qa
+re-allocating q1
+
+== Entering test case tc_queue_length
+allocating q1
+adding queue entries up to the limit
+attempting to add more than the limit
+
+== Entering test case tc_eventfd
+allocating q1
+adding 30 queue entries up to the limit
+30 entries were dequeued
diff --git a/tests/iuup/iuup_test.c b/tests/iuup/iuup_test.c
new file mode 100644
index 00000000..e40b9e77
--- /dev/null
+++ b/tests/iuup/iuup_test.c
@@ -0,0 +1,764 @@
+#include <stdint.h>
+#include <stdio.h>
+
+#include <osmocom/core/application.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/fsm.h>
+#include <osmocom/core/select.h>
+
+#include <osmocom/gsm/prim.h>
+#include <osmocom/gsm/iuup.h>
+
+static void *iuup_test_ctx;
+
+static struct osmo_iuup_rnl_config def_configure_req = {
+ .transparent = false,
+ .active = true,
+ .supported_versions_mask = 0x0001,
+ .num_rfci = 3,
+ .num_subflows = 3,
+ .IPTIs_present = true,
+ .rfci = {
+ {.used = 1, .id = 0, .IPTI = 1, .subflow_sizes = {81, 103, 60} },
+ {.used = 1, .id = 1, .IPTI = 7, .subflow_sizes = {39, 0, 0} },
+ {.used = 1, .id = 2, .IPTI = 1, .subflow_sizes = {0, 0, 0} },
+ },
+ /* .delivery_err_sdu = All set to 0 (YES) by default, */
+ .IPTIs_present = true,
+ .t_init = { .t_ms = IUUP_TIMER_INIT_T_DEFAULT, .n_max = IUUP_TIMER_INIT_N_DEFAULT },
+ .t_ta = { .t_ms = IUUP_TIMER_TA_T_DEFAULT, .n_max = IUUP_TIMER_TA_N_DEFAULT },
+ .t_rc = { .t_ms = IUUP_TIMER_RC_T_DEFAULT, .n_max = IUUP_TIMER_RC_N_DEFAULT },
+};
+
+/* Frame 33, "Initialization", OS#4744 3g_call_23112021.pcapng
+IuUP
+ 1110 .... = PDU Type: Control Procedure (14)
+ .... 00.. = Ack/Nack: Procedure (0)
+ .... ..00 = Frame Number: 0
+ 0000 .... = Mode Version: 0x0
+ .... 0000 = Procedure: Initialization (0)
+ 1101 11.. = Header CRC: 0x37 [correct]
+ .... ..11 1001 1001 = Payload CRC: 0x399
+ 000. .... = Spare: 0x0
+ ...1 .... = TI: IPTIs present in frame (1)
+ .... 011. = Subflows: 3
+ .... ...0 = Chain Indicator: this frame is the last frame for the procedure (0)
+ RFCI 0 Initialization
+ 0... .... = RFCI 0 LRI: Not last RFCI (0x0)
+ .0.. .... = RFCI 0 LI: one octet used (0x0)
+ ..00 0000 = RFCI 0: 0
+ RFCI 0 Flow 0 Len: 81
+ RFCI 0 Flow 1 Len: 103
+ RFCI 0 Flow 2 Len: 60
+ RFCI 1 Initialization
+ 0... .... = RFCI 1 LRI: Not last RFCI (0x0)
+ .0.. .... = RFCI 1 LI: one octet used (0x0)
+ ..00 0001 = RFCI 1: 1
+ RFCI 1 Flow 0 Len: 39
+ RFCI 1 Flow 1 Len: 0
+ RFCI 1 Flow 2 Len: 0
+ RFCI 2 Initialization
+ 1... .... = RFCI 2 LRI: Last RFCI in current frame (0x1)
+ .0.. .... = RFCI 2 LI: one octet used (0x0)
+ ..00 0010 = RFCI 2: 2
+ RFCI 2 Flow 0 Len: 0
+ RFCI 2 Flow 1 Len: 0
+ RFCI 2 Flow 2 Len: 0
+ IPTIs
+ 0001 .... = RFCI 0 IPTI: 0x1
+ .... 0111 = RFCI 1 IPTI: 0x7
+ 0001 .... = RFCI 2 IPTI: 0x1
+ Iu UP Mode Versions Supported: 0x0001
+ 0... .... .... .... = Version 16: not supported (0x0)
+ .0.. .... .... .... = Version 15: not supported (0x0)
+ ..0. .... .... .... = Version 14: not supported (0x0)
+ ...0 .... .... .... = Version 13: not supported (0x0)
+ .... 0... .... .... = Version 12: not supported (0x0)
+ .... .0.. .... .... = Version 11: not supported (0x0)
+ .... ..0. .... .... = Version 10: not supported (0x0)
+ .... ...0 .... .... = Version 9: not supported (0x0)
+ .... .... 0... .... = Version 8: not supported (0x0)
+ .... .... .0.. .... = Version 7: not supported (0x0)
+ .... .... ..0. .... = Version 6: not supported (0x0)
+ .... .... ...0 .... = Version 5: not supported (0x0)
+ .... .... .... 0... = Version 4: not supported (0x0)
+ .... .... .... .0.. = Version 3: not supported (0x0)
+ .... .... .... ..0. = Version 2: not supported (0x0)
+ .... .... .... ...1 = Version 1: supported (0x1)
+ 0000 .... = RFCI Data Pdu Type: PDU type 0 (0x0)
+*/
+static const uint8_t iuup_initialization[] = {
+ 0xe0, 0x00, 0xdf, 0x99, 0x16, 0x00, 0x51, 0x67, 0x3c, 0x01, 0x27, 0x00,
+ 0x00, 0x82, 0x00, 0x00, 0x00, 0x17, 0x10, 0x00, 0x01, 0x00
+};
+
+/* Frame 87, "Data RFCI=0 FN = 1", OS#4744 3g_call_23112021.pcapng
+IuUP
+ 0000 .... = PDU Type: Data with CRC (0)
+ .... 0001 = Frame Number: 1
+ 00.. .... = FQC: Frame Good (0)
+ ..00 0000 = RFCI: 0x00
+ 1110 00.. = Header CRC: 0x38 [correct]
+ .... ..11 1111 1111 = Payload CRC: 0x3ff
+ Payload Data: 08556d944c71a1a081e7ead204244480000ecd82b81118000097c4794e7740
+*/
+static const uint8_t iuup_data[] = {
+ 0x01, 0x00, 0xe3, 0xff, /*payload starts here: */ 0x08, 0x55, 0x6d, 0x94, 0x4c, 0x71, 0xa1, 0xa0,
+ 0x81, 0xe7, 0xea, 0xd2, 0x04, 0x24, 0x44, 0x80, 0x00, 0x0e, 0xcd, 0x82,
+ 0xb8, 0x11, 0x18, 0x00, 0x00, 0x97, 0xc4, 0x79, 0x4e, 0x77, 0x40
+};
+
+#define IUUP_MSGB_SIZE 4096
+
+static struct osmo_iuup_tnl_prim *itp_ctrl_nack_alloc(enum iuup_procedure proc_ind, enum iuup_error_cause error_cause, uint8_t fn)
+{
+ struct osmo_iuup_tnl_prim *tnp;
+ struct iuup_ctrl_nack *nack;
+ tnp = osmo_iuup_tnl_prim_alloc(iuup_test_ctx, OSMO_IUUP_TNL_UNITDATA, PRIM_OP_INDICATION, IUUP_MSGB_SIZE);
+ tnp->oph.msg->l2h = msgb_put(tnp->oph.msg, sizeof(struct iuup_ctrl_nack));
+ nack = (struct iuup_ctrl_nack *) msgb_l2(tnp->oph.msg);
+ *nack = (struct iuup_ctrl_nack){
+ .hdr = {
+ .frame_nr = fn,
+ .ack_nack = IUUP_AN_NACK,
+ .pdu_type = IUUP_PDU_T_CONTROL,
+ .proc_ind = proc_ind,
+ .mode_version = 0,
+ .payload_crc_hi = 0,
+ .header_crc = 0,
+ .payload_crc_lo = 0,
+ },
+ .spare = 0,
+ .error_cause = error_cause,
+ };
+ nack->hdr.header_crc = osmo_iuup_compute_header_crc(msgb_l2(tnp->oph.msg), msgb_l2len(tnp->oph.msg));
+ return tnp;
+}
+
+static struct osmo_iuup_tnl_prim *itp_ctrl_ack_alloc(enum iuup_procedure proc_ind, uint8_t fn)
+{
+ struct osmo_iuup_tnl_prim *tnp;
+ struct iuup_ctrl_ack *ack;
+ tnp = osmo_iuup_tnl_prim_alloc(iuup_test_ctx, OSMO_IUUP_TNL_UNITDATA, PRIM_OP_INDICATION, IUUP_MSGB_SIZE);
+ tnp->oph.msg->l2h = msgb_put(tnp->oph.msg, sizeof(struct iuup_ctrl_ack));
+ ack = (struct iuup_ctrl_ack *) msgb_l2(tnp->oph.msg);
+ *ack = (struct iuup_ctrl_ack){
+ .hdr = {
+ .frame_nr = fn,
+ .ack_nack = IUUP_AN_ACK,
+ .pdu_type = IUUP_PDU_T_CONTROL,
+ .proc_ind = proc_ind,
+ .mode_version = 0,
+ .payload_crc_hi = 0,
+ .header_crc = 0,
+ .payload_crc_lo = 0,
+ },
+ };
+ ack->hdr.header_crc = osmo_iuup_compute_header_crc(msgb_l2(tnp->oph.msg), msgb_l2len(tnp->oph.msg));
+ return tnp;
+}
+
+static void clock_override_set(long sec, long usec)
+{
+ osmo_gettimeofday_override_time.tv_sec = sec + usec / (1000*1000);
+ osmo_gettimeofday_override_time.tv_usec = usec % (1000*1000);
+ printf("sys={%lu.%06lu}, %s\n", osmo_gettimeofday_override_time.tv_sec,
+ osmo_gettimeofday_override_time.tv_usec, __func__);
+}
+
+void test_crc(void)
+{
+ int rc;
+
+ /* Frame 34, "Initialization ACK", OS#4744 3g_call_23112021.pcapng */
+ static const uint8_t iuup_initialization_ack[] = {
+ 0xe4, 0x00, 0xdf, 0x99, 0x16, 0x00, 0x51, 0x67, 0x3c, 0x01, 0x27, 0x00,
+ 0x00, 0x82, 0x00, 0x00, 0x00, 0x17, 0x10, 0x00, 0x01, 0x00
+ };
+
+ printf("=== start: %s ===\n", __func__);
+
+ rc = osmo_iuup_compute_header_crc(iuup_initialization, sizeof(iuup_initialization));
+ printf("iuup_initialization: Header CRC = 0x%02x\n", rc);
+ rc = osmo_iuup_compute_payload_crc(iuup_initialization, sizeof(iuup_initialization));
+ printf("iuup_initialization: Payload CRC = 0x%03x\n", rc);
+
+ rc = osmo_iuup_compute_header_crc(iuup_initialization_ack, sizeof(iuup_initialization_ack));
+ printf("iuup_initialization_ack: Header CRC = 0x%02x\n", rc);
+ rc = osmo_iuup_compute_payload_crc(iuup_initialization_ack, sizeof(iuup_initialization_ack));
+ printf("iuup_initialization_ack: Payload CRC = 0x%03x\n", rc);
+
+ printf("=== end: %s ===\n", __func__);
+}
+
+
+/****************************
+ * test_tinit_timeout_retrans
+ ****************************/
+static unsigned int _tinit_timeout_retrans_user_rx_prim = 0;
+static int _tinit_timeout_retrans_user_prim_cb(struct osmo_prim_hdr *oph, void *ctx)
+{
+ struct osmo_iuup_rnl_prim *irp = (struct osmo_iuup_rnl_prim *)oph;
+ printf("%s()\n", __func__);
+
+ OSMO_ASSERT(OSMO_PRIM_HDR(&irp->oph) == OSMO_PRIM(OSMO_IUUP_RNL_STATUS, PRIM_OP_INDICATION));
+
+ OSMO_ASSERT(irp->u.status.procedure == IUUP_PROC_ERR_EVENT);
+ OSMO_ASSERT(irp->u.status.u.error_event.cause == IUUP_ERR_CAUSE_INIT_FAILURE_NET_TMR);
+ OSMO_ASSERT(irp->u.status.u.error_event.distance == IUUP_ERR_DIST_LOCAL);
+ _tinit_timeout_retrans_user_rx_prim++;
+ msgb_free(oph->msg);
+ return 0;
+}
+static unsigned int _tinit_timeout_retrans_transport_rx_prim = 0;
+static int _tinit_timeout_retrans_transport_prim_cb(struct osmo_prim_hdr *oph, void *ctx)
+{
+ struct osmo_iuup_tnl_prim *itp = (struct osmo_iuup_tnl_prim *)oph;
+ struct msgb *msg = oph->msg;
+
+ printf("%s()\n", __func__);
+ OSMO_ASSERT(OSMO_PRIM_HDR(&itp->oph) == OSMO_PRIM(OSMO_IUUP_TNL_UNITDATA, PRIM_OP_REQUEST));
+ printf("Transport: DL len=%u: %s\n", msgb_l2len(msg),
+ osmo_hexdump((const unsigned char *) msgb_l2(msg), msgb_l2len(msg)));
+ _tinit_timeout_retrans_transport_rx_prim++;
+
+ msgb_free(msg);
+ return 0;
+}
+void test_tinit_timeout_retrans(void)
+{
+ struct osmo_iuup_instance *iui;
+ struct osmo_iuup_rnl_prim *rnp;
+ int rc, i;
+
+ iui = osmo_iuup_instance_alloc(iuup_test_ctx, __func__);
+ OSMO_ASSERT(iui);
+ osmo_iuup_instance_set_user_prim_cb(iui, _tinit_timeout_retrans_user_prim_cb, NULL);
+ osmo_iuup_instance_set_transport_prim_cb(iui, _tinit_timeout_retrans_transport_prim_cb, NULL);
+
+ clock_override_set(0, 0);
+
+ /* Tx CONFIG.req */
+ rnp = osmo_iuup_rnl_prim_alloc(iuup_test_ctx, OSMO_IUUP_RNL_CONFIG, PRIM_OP_REQUEST, IUUP_MSGB_SIZE);
+ rnp->u.config = def_configure_req;
+ OSMO_ASSERT((rc = osmo_iuup_rnl_prim_down(iui, rnp)) == 0);
+ /* STATUS-INIT.req is transmitted automatically: */
+ OSMO_ASSERT(_tinit_timeout_retrans_transport_rx_prim == 1);
+
+ /* After one sec, INITIALIZATION msg is retransmitted */
+ for (i = 1; i < IUUP_TIMER_INIT_N_DEFAULT + 1; i++) {
+ clock_override_set(0, IUUP_TIMER_INIT_T_DEFAULT*1000 * i);
+ osmo_select_main(0);
+ OSMO_ASSERT(_tinit_timeout_retrans_transport_rx_prim == i + 1);
+ }
+ /* Last one should send an error event: */
+ OSMO_ASSERT(_tinit_timeout_retrans_user_rx_prim == 0);
+ clock_override_set(0, IUUP_TIMER_INIT_T_DEFAULT*1000 * i);
+ osmo_select_main(0);
+ OSMO_ASSERT(_tinit_timeout_retrans_transport_rx_prim == i);
+ OSMO_ASSERT(_tinit_timeout_retrans_user_rx_prim == 1);
+
+ /* Nothing else is received afterwards. osmo_select_main() will block forever. */
+ /*clock_override_set(i + 1, 0);
+ osmo_select_main(0);
+ OSMO_ASSERT(_tinit_timeout_retrans_transport_rx_prim == i);
+ OSMO_ASSERT(_tinit_timeout_retrans_user_rx_prim == 1);*/
+
+ osmo_iuup_instance_free(iui);
+}
+
+/****************************
+ * test_tinit_nack
+ ****************************/
+static unsigned int _init_nack_retrans_user_rx_prim = 0;
+static int _init_nack_retrans_user_prim_cb(struct osmo_prim_hdr *oph, void *ctx)
+{
+ struct osmo_iuup_rnl_prim *irp = (struct osmo_iuup_rnl_prim *)oph;
+
+ printf("%s()\n", __func__);
+
+ OSMO_ASSERT(OSMO_PRIM_HDR(&irp->oph) == OSMO_PRIM(OSMO_IUUP_RNL_STATUS, PRIM_OP_INDICATION));
+
+ OSMO_ASSERT(irp->u.status.procedure == IUUP_PROC_ERR_EVENT);
+ OSMO_ASSERT(irp->u.status.u.error_event.cause == IUUP_ERR_CAUSE_INIT_FAILURE_REP_NACK);
+ OSMO_ASSERT(irp->u.status.u.error_event.distance == IUUP_ERR_DIST_SECOND_FWD);
+ _init_nack_retrans_user_rx_prim++;
+ msgb_free(oph->msg);
+ return 0;
+}
+static int _init_nack_retrans_transport_rx_prim = 0;
+static int _init_nack_retrans_transport_prim_cb(struct osmo_prim_hdr *oph, void *ctx)
+{
+ struct osmo_iuup_tnl_prim *itp = (struct osmo_iuup_tnl_prim *)oph;
+ struct msgb *msg = oph->msg;
+
+ printf("%s()\n", __func__);
+ OSMO_ASSERT(OSMO_PRIM_HDR(&itp->oph) == OSMO_PRIM(OSMO_IUUP_TNL_UNITDATA, PRIM_OP_REQUEST));
+ printf("Transport: DL len=%u: %s\n", msgb_l2len(msg),
+ osmo_hexdump((const unsigned char *) msgb_l2(msg), msgb_l2len(msg)));
+ _init_nack_retrans_transport_rx_prim++;
+
+ msgb_free(msg);
+ return 0;
+}
+void test_init_nack_retrans(void)
+{
+ struct osmo_iuup_instance *iui;
+ struct osmo_iuup_rnl_prim *rnp;
+ struct osmo_iuup_tnl_prim *tnp;
+ int rc, i;
+
+ iui = osmo_iuup_instance_alloc(iuup_test_ctx, __func__);
+ OSMO_ASSERT(iui);
+ osmo_iuup_instance_set_user_prim_cb(iui, _init_nack_retrans_user_prim_cb, NULL);
+ osmo_iuup_instance_set_transport_prim_cb(iui, _init_nack_retrans_transport_prim_cb, NULL);
+
+ clock_override_set(0, 0);
+
+ /* Tx CONFIG.req */
+ rnp = osmo_iuup_rnl_prim_alloc(iuup_test_ctx, OSMO_IUUP_RNL_CONFIG, PRIM_OP_REQUEST, IUUP_MSGB_SIZE);
+ rnp->u.config = def_configure_req;
+ OSMO_ASSERT((rc = osmo_iuup_rnl_prim_down(iui, rnp)) == 0);
+ /* STATUS-INIT.req is transmitted automatically: */
+ OSMO_ASSERT(_init_nack_retrans_transport_rx_prim == 1);
+
+ /* After one sec, INITIALIZATION msg is retransmitted */
+ for (i = 1; i < IUUP_TIMER_INIT_N_DEFAULT + 1; i++) {
+ /* Send NACK: */
+ tnp = itp_ctrl_nack_alloc(IUUP_PROC_INIT, IUUP_ERR_CAUSE_MODE_VERSION_NOT_SUPPORTED, 0);
+ OSMO_ASSERT((rc = osmo_iuup_tnl_prim_up(iui, tnp)) == 0);
+ /* A new INIT is retransmitted: */
+ OSMO_ASSERT(_init_nack_retrans_transport_rx_prim == i + 1);
+ }
+ /* Last one should send an error event: */
+ OSMO_ASSERT(_init_nack_retrans_user_rx_prim == 0);
+ tnp = itp_ctrl_nack_alloc(IUUP_PROC_INIT, IUUP_ERR_CAUSE_MODE_VERSION_NOT_SUPPORTED, 0);
+ OSMO_ASSERT((rc = osmo_iuup_tnl_prim_up(iui, tnp)) == 0);
+ OSMO_ASSERT(_init_nack_retrans_transport_rx_prim == i);
+ OSMO_ASSERT(_init_nack_retrans_user_rx_prim == 1);
+
+ /* Nothing else is received afterwards. osmo_select_main() will block forever. */
+
+ osmo_iuup_instance_free(iui);
+}
+
+
+/****************************
+ * test_init_ack
+ ****************************/
+static unsigned int _init_ack_user_rx_prim = 0;
+static int _init_ack_user_prim_cb(struct osmo_prim_hdr *oph, void *ctx)
+{
+ struct osmo_iuup_rnl_prim *irp = (struct osmo_iuup_rnl_prim *)oph;
+ struct msgb *msg = oph->msg;
+
+ printf("%s()\n", __func__);
+
+ OSMO_ASSERT(OSMO_PRIM_HDR(&irp->oph) == OSMO_PRIM(OSMO_IUUP_RNL_DATA, PRIM_OP_INDICATION));
+ printf("User: UL len=%u: %s\n", msgb_l3len(msg),
+ osmo_hexdump((const unsigned char *) msgb_l3(msg), msgb_l3len(msg)));
+
+ _init_ack_user_rx_prim++;
+ msgb_free(oph->msg);
+ return 0;
+}
+static int _init_ack_transport_rx_prim = 0;
+static int _init_ack_transport_prim_cb(struct osmo_prim_hdr *oph, void *ctx)
+{
+ struct osmo_iuup_tnl_prim *itp = (struct osmo_iuup_tnl_prim *)oph;
+ struct msgb *msg = oph->msg;
+
+ printf("%s()\n", __func__);
+ OSMO_ASSERT(OSMO_PRIM_HDR(&itp->oph) == OSMO_PRIM(OSMO_IUUP_TNL_UNITDATA, PRIM_OP_REQUEST));
+ printf("Transport: DL len=%u: %s\n", msgb_l2len(msg),
+ osmo_hexdump((const unsigned char *) msgb_l2(msg), msgb_l2len(msg)));
+ _init_ack_transport_rx_prim++;
+
+ msgb_free(msg);
+ return 0;
+}
+void test_init_ack(void)
+{
+ struct osmo_iuup_instance *iui;
+ struct osmo_iuup_rnl_prim *rnp;
+ struct osmo_iuup_tnl_prim *tnp;
+ struct iuup_pdutype0_hdr *hdr0;
+ int rc;
+
+ iui = osmo_iuup_instance_alloc(iuup_test_ctx, __func__);
+ OSMO_ASSERT(iui);
+ osmo_iuup_instance_set_user_prim_cb(iui, _init_ack_user_prim_cb, NULL);
+ osmo_iuup_instance_set_transport_prim_cb(iui, _init_ack_transport_prim_cb, NULL);
+
+ clock_override_set(0, 0);
+
+ /* Tx CONFIG.req */
+ rnp = osmo_iuup_rnl_prim_alloc(iuup_test_ctx, OSMO_IUUP_RNL_CONFIG, PRIM_OP_REQUEST, IUUP_MSGB_SIZE);
+ rnp->u.config = def_configure_req;
+ OSMO_ASSERT((rc = osmo_iuup_rnl_prim_down(iui, rnp)) == 0);
+ /* STATUS-INIT.req is transmitted automatically: */
+ OSMO_ASSERT(_init_ack_transport_rx_prim == 1);
+
+ /* Send ACK: */
+ tnp = itp_ctrl_ack_alloc(IUUP_PROC_INIT, 0);
+ OSMO_ASSERT((rc = osmo_iuup_tnl_prim_up(iui, tnp)) == 0);
+ OSMO_ASSERT(_init_ack_transport_rx_prim == 1); /* Make sure there's no retrans */
+ OSMO_ASSERT(_init_ack_user_rx_prim == 0); /* Make sure there's no error event */
+
+ /* Send IuUP incoming data to the implementation: */
+ tnp = osmo_iuup_tnl_prim_alloc(iuup_test_ctx, OSMO_IUUP_TNL_UNITDATA, PRIM_OP_INDICATION, IUUP_MSGB_SIZE);
+ tnp->oph.msg->l2h = msgb_put(tnp->oph.msg, sizeof(iuup_data));
+ hdr0 = (struct iuup_pdutype0_hdr *)msgb_l2(tnp->oph.msg);
+ memcpy(hdr0, iuup_data, sizeof(iuup_data));
+ OSMO_ASSERT((rc = osmo_iuup_tnl_prim_up(iui, tnp)) == 0);
+ /* We receive it in RNL: */
+ OSMO_ASSERT(_init_ack_user_rx_prim == 1);
+
+ /* Now in opposite direction, RNL->[IuuP]->TNL: */
+ rnp = osmo_iuup_rnl_prim_alloc(iuup_test_ctx, OSMO_IUUP_RNL_DATA, PRIM_OP_REQUEST, IUUP_MSGB_SIZE);
+ rnp->u.data.rfci = 0;
+ rnp->u.data.frame_nr = 1;
+ rnp->u.data.fqc = IUUP_FQC_FRAME_GOOD;
+ rnp->oph.msg->l3h = msgb_put(rnp->oph.msg, sizeof(iuup_data) - 4);
+ memcpy(rnp->oph.msg->l3h, iuup_data + 4, sizeof(iuup_data) - 4);
+ OSMO_ASSERT((rc = osmo_iuup_rnl_prim_down(iui, rnp)) == 0);
+ OSMO_ASSERT(_init_ack_transport_rx_prim == 2); /* We receive data in TNL */
+
+ osmo_iuup_instance_free(iui);
+}
+
+/****************************
+ * test_passive_init
+ ****************************/
+static unsigned int _passive_init_user_rx_prim = 0;
+static int _passive_init_user_prim_cb(struct osmo_prim_hdr *oph, void *ctx)
+{
+ struct osmo_iuup_rnl_prim *irp = (struct osmo_iuup_rnl_prim *)oph;
+ struct msgb *msg = oph->msg;
+
+ printf("%s()\n", __func__);
+
+ switch (_passive_init_user_rx_prim) {
+ case 0:
+ OSMO_ASSERT(OSMO_PRIM_HDR(&irp->oph) == OSMO_PRIM(OSMO_IUUP_RNL_STATUS, PRIM_OP_INDICATION));
+ OSMO_ASSERT(irp->u.status.procedure == IUUP_PROC_INIT);
+ break;
+ case 1:
+ default:
+ OSMO_ASSERT(OSMO_PRIM_HDR(&irp->oph) == OSMO_PRIM(OSMO_IUUP_RNL_DATA, PRIM_OP_INDICATION));
+ printf("User: UL len=%u: %s\n", msgb_l3len(msg),
+ osmo_hexdump((const unsigned char *) msgb_l3(msg), msgb_l3len(msg)));
+ }
+
+ _passive_init_user_rx_prim++;
+ msgb_free(oph->msg);
+ return 0;
+}
+static int _passive_init_transport_rx_prim = 0;
+static int _passive_init_transport_prim_cb(struct osmo_prim_hdr *oph, void *ctx)
+{
+ struct osmo_iuup_tnl_prim *itp = (struct osmo_iuup_tnl_prim *)oph;
+ struct msgb *msg;
+
+ printf("%s()\n", __func__);
+ msg = oph->msg;
+ OSMO_ASSERT(OSMO_PRIM_HDR(&itp->oph) == OSMO_PRIM(OSMO_IUUP_TNL_UNITDATA, PRIM_OP_REQUEST));
+ printf("Transport: DL len=%u: %s\n", msgb_l2len(msg),
+ osmo_hexdump((const unsigned char *) msgb_l2(msg), msgb_l2len(msg)));
+ _passive_init_transport_rx_prim++;
+
+ msgb_free(msg);
+ return 0;
+}
+void test_passive_init(void)
+{
+ /* Here we check the passive INIT code path, aka receiving INIT and returning INIT_ACK/NACK */
+ struct osmo_iuup_instance *iui;
+ struct osmo_iuup_rnl_prim *rnp;
+ struct osmo_iuup_tnl_prim *tnp;
+ struct iuup_pdutype14_hdr *hdr14;
+ struct iuup_pdutype0_hdr *hdr0;
+ int rc;
+
+ iui = osmo_iuup_instance_alloc(iuup_test_ctx, __func__);
+ OSMO_ASSERT(iui);
+ osmo_iuup_instance_set_user_prim_cb(iui, _passive_init_user_prim_cb, NULL);
+ osmo_iuup_instance_set_transport_prim_cb(iui, _passive_init_transport_prim_cb, NULL);
+
+ clock_override_set(0, 0);
+
+ /* Tx CONFIG.req */
+ rnp = osmo_iuup_rnl_prim_alloc(iuup_test_ctx, OSMO_IUUP_RNL_CONFIG, PRIM_OP_REQUEST, IUUP_MSGB_SIZE);
+ rnp->u.config = def_configure_req;
+ rnp->u.config.active = false;
+ OSMO_ASSERT((rc = osmo_iuup_rnl_prim_down(iui, rnp)) == 0);
+ /* STATUS-INIT.req is NOT transmitted automatically: */
+ OSMO_ASSERT(_passive_init_transport_rx_prim == 0);
+
+ /* Send Init: */
+ tnp = osmo_iuup_tnl_prim_alloc(iuup_test_ctx, OSMO_IUUP_TNL_UNITDATA, PRIM_OP_INDICATION, IUUP_MSGB_SIZE);
+ tnp->oph.msg->l2h = msgb_put(tnp->oph.msg, sizeof(iuup_initialization));
+ hdr14 = (struct iuup_pdutype14_hdr *)msgb_l2(tnp->oph.msg);
+ memcpy(hdr14, iuup_initialization, sizeof(iuup_initialization));
+ OSMO_ASSERT((rc = osmo_iuup_tnl_prim_up(iui, tnp)) == 0);
+ OSMO_ASSERT(_passive_init_transport_rx_prim == 1); /* We receive an Init ACK */
+ OSMO_ASSERT(_passive_init_user_rx_prim == 1); /* We receive the Status-Init.ind */
+
+ /* Send IuUP incoming data to the implementation: */
+ tnp = osmo_iuup_tnl_prim_alloc(iuup_test_ctx, OSMO_IUUP_TNL_UNITDATA, PRIM_OP_INDICATION, IUUP_MSGB_SIZE);
+ tnp->oph.msg->l2h = msgb_put(tnp->oph.msg, sizeof(iuup_data));
+ hdr0 = (struct iuup_pdutype0_hdr *)msgb_l2(tnp->oph.msg);
+ memcpy(hdr0, iuup_data, sizeof(iuup_data));
+ OSMO_ASSERT((rc = osmo_iuup_tnl_prim_up(iui, tnp)) == 0);
+ /* We receive it in RNL: */
+ OSMO_ASSERT(_passive_init_user_rx_prim == 2);
+
+ /* Now in opposite direction, RNL->[IuuP]->TNL: */
+ rnp = osmo_iuup_rnl_prim_alloc(iuup_test_ctx, OSMO_IUUP_RNL_DATA, PRIM_OP_REQUEST, IUUP_MSGB_SIZE);
+ rnp->u.data.rfci = 0;
+ rnp->u.data.frame_nr = 1;
+ rnp->u.data.fqc = IUUP_FQC_FRAME_GOOD;
+ rnp->oph.msg->l3h = msgb_put(rnp->oph.msg, sizeof(iuup_data) - 4);
+ memcpy(rnp->oph.msg->l3h, iuup_data + 4, sizeof(iuup_data) - 4);
+ OSMO_ASSERT((rc = osmo_iuup_rnl_prim_down(iui, rnp)) == 0);
+ OSMO_ASSERT(_passive_init_transport_rx_prim == 2); /* We receive data in TNL */
+
+ osmo_iuup_instance_free(iui);
+}
+
+/****************************
+ * test_passive_init_retrans
+ ****************************/
+static unsigned int _passive_init_retrans_user_rx_prim = 0;
+static int _passive_init_retrans_user_prim_cb(struct osmo_prim_hdr *oph, void *ctx)
+{
+ struct osmo_iuup_rnl_prim *irp = (struct osmo_iuup_rnl_prim *)oph;
+ struct msgb *msg = oph->msg;
+
+ printf("%s()\n", __func__);
+
+ switch (_passive_init_retrans_user_rx_prim) {
+ case 0:
+ case 1:
+ OSMO_ASSERT(OSMO_PRIM_HDR(&irp->oph) == OSMO_PRIM(OSMO_IUUP_RNL_STATUS, PRIM_OP_INDICATION));
+ OSMO_ASSERT(irp->u.status.procedure == IUUP_PROC_INIT);
+ break;
+ case 2:
+ default:
+ OSMO_ASSERT(OSMO_PRIM_HDR(&irp->oph) == OSMO_PRIM(OSMO_IUUP_RNL_DATA, PRIM_OP_INDICATION));
+ printf("User: UL len=%u: %s\n", msgb_l3len(msg),
+ osmo_hexdump((const unsigned char *) msgb_l3(msg), msgb_l3len(msg)));
+ }
+
+ _passive_init_retrans_user_rx_prim++;
+ msgb_free(oph->msg);
+ return 0;
+}
+void test_passive_init_retrans(void)
+{
+ /* Here we check the passive INIT code path, aka receiving INIT and
+ * returning INIT_ACK/NACK. We emulate the peer not receiving the INIT
+ * ACK and hence retransmitting the INIT. The IuUP stack should then
+ * push the new INIT info up the stack and ACK it. */
+ struct osmo_iuup_instance *iui;
+ struct osmo_iuup_rnl_prim *rnp;
+ struct osmo_iuup_tnl_prim *tnp;
+ struct iuup_pdutype14_hdr *hdr14;
+ struct iuup_pdutype0_hdr *hdr0;
+ int rc;
+
+ /* reset global var, we reuse it together wth callback from test_passive_init(): */
+ _passive_init_transport_rx_prim = 0;
+
+ iui = osmo_iuup_instance_alloc(iuup_test_ctx, __func__);
+ OSMO_ASSERT(iui);
+ osmo_iuup_instance_set_user_prim_cb(iui, _passive_init_retrans_user_prim_cb, NULL);
+ osmo_iuup_instance_set_transport_prim_cb(iui, _passive_init_transport_prim_cb, NULL);
+
+ clock_override_set(0, 0);
+
+ /* Tx CONFIG.req */
+ rnp = osmo_iuup_rnl_prim_alloc(iuup_test_ctx, OSMO_IUUP_RNL_CONFIG, PRIM_OP_REQUEST, IUUP_MSGB_SIZE);
+ rnp->u.config = def_configure_req;
+ rnp->u.config.active = false;
+ OSMO_ASSERT((rc = osmo_iuup_rnl_prim_down(iui, rnp)) == 0);
+ /* STATUS-INIT.req is NOT transmitted automatically: */
+ OSMO_ASSERT(_passive_init_transport_rx_prim == 0);
+
+ /* Send Init: */
+ tnp = osmo_iuup_tnl_prim_alloc(iuup_test_ctx, OSMO_IUUP_TNL_UNITDATA, PRIM_OP_INDICATION, IUUP_MSGB_SIZE);
+ tnp->oph.msg->l2h = msgb_put(tnp->oph.msg, sizeof(iuup_initialization));
+ hdr14 = (struct iuup_pdutype14_hdr *)msgb_l2(tnp->oph.msg);
+ memcpy(hdr14, iuup_initialization, sizeof(iuup_initialization));
+ OSMO_ASSERT((rc = osmo_iuup_tnl_prim_up(iui, tnp)) == 0);
+ OSMO_ASSERT(_passive_init_transport_rx_prim == 1); /* We receive an Init ACK */
+ OSMO_ASSERT(_passive_init_retrans_user_rx_prim == 1); /* We receive the Status-Init.ind */
+
+ /* Send Init (retrans): */
+ tnp = osmo_iuup_tnl_prim_alloc(iuup_test_ctx, OSMO_IUUP_TNL_UNITDATA, PRIM_OP_INDICATION, IUUP_MSGB_SIZE);
+ tnp->oph.msg->l2h = msgb_put(tnp->oph.msg, sizeof(iuup_initialization));
+ hdr14 = (struct iuup_pdutype14_hdr *)msgb_l2(tnp->oph.msg);
+ memcpy(hdr14, iuup_initialization, sizeof(iuup_initialization));
+ OSMO_ASSERT((rc = osmo_iuup_tnl_prim_up(iui, tnp)) == 0);
+ OSMO_ASSERT(_passive_init_transport_rx_prim == 2); /* We receive another Init ACK */
+ OSMO_ASSERT(_passive_init_retrans_user_rx_prim == 2); /* We receive another Status-Init.ind */
+
+ /* Send IuUP incoming data to the implementation: */
+ tnp = osmo_iuup_tnl_prim_alloc(iuup_test_ctx, OSMO_IUUP_TNL_UNITDATA, PRIM_OP_INDICATION, IUUP_MSGB_SIZE);
+ tnp->oph.msg->l2h = msgb_put(tnp->oph.msg, sizeof(iuup_data));
+ hdr0 = (struct iuup_pdutype0_hdr *)msgb_l2(tnp->oph.msg);
+ memcpy(hdr0, iuup_data, sizeof(iuup_data));
+ OSMO_ASSERT((rc = osmo_iuup_tnl_prim_up(iui, tnp)) == 0);
+ /* We receive it in RNL: */
+ OSMO_ASSERT(_passive_init_retrans_user_rx_prim == 3);
+
+ /* Now in opposite direction, RNL->[IuuP]->TNL: */
+ rnp = osmo_iuup_rnl_prim_alloc(iuup_test_ctx, OSMO_IUUP_RNL_DATA, PRIM_OP_REQUEST, IUUP_MSGB_SIZE);
+ rnp->u.data.rfci = 0;
+ rnp->u.data.frame_nr = 1;
+ rnp->u.data.fqc = IUUP_FQC_FRAME_GOOD;
+ rnp->oph.msg->l3h = msgb_put(rnp->oph.msg, sizeof(iuup_data) - 4);
+ memcpy(rnp->oph.msg->l3h, iuup_data + 4, sizeof(iuup_data) - 4);
+ OSMO_ASSERT((rc = osmo_iuup_rnl_prim_down(iui, rnp)) == 0);
+ OSMO_ASSERT(_passive_init_transport_rx_prim == 3); /* We receive data in TNL */
+
+ osmo_iuup_instance_free(iui);
+}
+
+static int _decode_passive_init_2_rfci_no_iptis_user_prim_cb(struct osmo_prim_hdr *oph, void *ctx)
+{
+ struct osmo_iuup_rnl_prim *irp = (struct osmo_iuup_rnl_prim *)oph;
+ printf("%s(): Initialization decoded fine!\n", __func__);
+ OSMO_ASSERT(OSMO_PRIM_HDR(&irp->oph) == OSMO_PRIM(OSMO_IUUP_RNL_STATUS, PRIM_OP_INDICATION));
+ OSMO_ASSERT(irp->u.status.procedure == IUUP_PROC_INIT);
+ OSMO_ASSERT(irp->u.status.u.initialization.num_rfci == 2);
+ OSMO_ASSERT(irp->u.status.u.initialization.num_subflows == 3);
+ OSMO_ASSERT(irp->u.status.u.initialization.data_pdu_type == 0);
+ OSMO_ASSERT(irp->u.status.u.initialization.IPTIs_present == false);
+ msgb_free(oph->msg);
+ return 0;
+}
+static int _decode_passive_init_2_rfci_no_iptis_transport_prim_cb(struct osmo_prim_hdr *oph, void *ctx)
+{
+ struct osmo_iuup_tnl_prim *itp = (struct osmo_iuup_tnl_prim *)oph;
+ struct msgb *msg;
+ struct iuup_pdutype14_hdr *hdr;
+
+ printf("%s()\n", __func__);
+ msg = oph->msg;
+ OSMO_ASSERT(OSMO_PRIM_HDR(&itp->oph) == OSMO_PRIM(OSMO_IUUP_TNL_UNITDATA, PRIM_OP_REQUEST));
+ printf("Transport: DL len=%u: %s\n", msgb_l2len(msg),
+ osmo_hexdump((const unsigned char *) msgb_l2(msg), msgb_l2len(msg)));
+ hdr = msgb_l2(msg);
+ OSMO_ASSERT(hdr->pdu_type == IUUP_PDU_T_CONTROL);
+ OSMO_ASSERT(hdr->ack_nack == IUUP_AN_ACK);
+ msgb_free(msg);
+ return 0;
+}
+void test_decode_passive_init_2_rfci_no_iptis(void)
+{
+ /* Here we check the passive INIT code path, aka receiving INIT and returning INIT_ACK/NACK */
+ struct osmo_iuup_instance *iui;
+ struct osmo_iuup_rnl_prim *rnp;
+ struct osmo_iuup_tnl_prim *tnp;
+ struct iuup_pdutype14_hdr *hdr14;
+ int rc;
+
+ /* Frame 46, "Initialization", SYS#5969 call4_Iu_Iuh.pcap
+ 1110 .... = PDU Type: Control Procedure (14)
+ .... 00.. = Ack/Nack: Procedure (0)
+ .... ..00 = Frame Number: 0
+ 0000 .... = Mode Version: 0x0
+ .... 0000 = Procedure: Initialization (0)
+ 1101 11.. = Header CRC: 0x37 [correct]
+ .... ..01 1011 0100 = Payload CRC: 0x1b4
+ 000. .... = Spare: 0x0
+ ...0 .... = TI: IPTIs not present (0)
+ .... 011. = Subflows: 3
+ .... ...0 = Chain Indicator: this frame is the last frame for the procedure (0)
+ RFCI 1 Initialization
+ 0... .... = RFCI 0 LRI: Not last RFCI (0x0)
+ .0.. .... = RFCI 0 LI: one octet used (0x0)
+ ..00 0001 = RFCI 0: 1
+ RFCI 0 Flow 0 Len: 81
+ RFCI 0 Flow 1 Len: 103
+ RFCI 0 Flow 2 Len: 60
+ RFCI 6 Initialization
+ 1... .... = RFCI 1 LRI: Last RFCI in current frame (0x1)
+ .0.. .... = RFCI 1 LI: one octet used (0x0)
+ ..00 0110 = RFCI 1: 6
+ RFCI 1 Flow 0 Len: 39
+ RFCI 1 Flow 1 Len: 0
+ RFCI 1 Flow 2 Len: 0
+ Iu UP Mode Versions Supported: 0x0001
+ 0... .... .... .... = Version 16: not supported (0x0)
+ .0.. .... .... .... = Version 15: not supported (0x0)
+ ..0. .... .... .... = Version 14: not supported (0x0)
+ ...0 .... .... .... = Version 13: not supported (0x0)
+ .... 0... .... .... = Version 12: not supported (0x0)
+ .... .0.. .... .... = Version 11: not supported (0x0)
+ .... ..0. .... .... = Version 10: not supported (0x0)
+ .... ...0 .... .... = Version 9: not supported (0x0)
+ .... .... 0... .... = Version 8: not supported (0x0)
+ .... .... .0.. .... = Version 7: not supported (0x0)
+ .... .... ..0. .... = Version 6: not supported (0x0)
+ .... .... ...0 .... = Version 5: not supported (0x0)
+ .... .... .... 0... = Version 4: not supported (0x0)
+ .... .... .... .0.. = Version 3: not supported (0x0)
+ .... .... .... ..0. = Version 2: not supported (0x0)
+ .... .... .... ...1 = Version 1: supported (0x1)
+ 0000 .... = RFCI Data Pdu Type: PDU type 0 (0x0)
+ */
+ const uint8_t iuup_init[] = {
+ 0xe0, 0x00, 0xdd, 0xb4, 0x06, 0x01, 0x51, 0x67, 0x3c, 0x86, 0x27,
+ 0x00, 0x00, 0x00, 0x01, 0x00
+ };
+
+ iui = osmo_iuup_instance_alloc(iuup_test_ctx, __func__);
+ OSMO_ASSERT(iui);
+ osmo_iuup_instance_set_user_prim_cb(iui, _decode_passive_init_2_rfci_no_iptis_user_prim_cb, NULL);
+ osmo_iuup_instance_set_transport_prim_cb(iui, _decode_passive_init_2_rfci_no_iptis_transport_prim_cb, NULL);
+
+ clock_override_set(0, 0);
+
+ /* Tx CONFIG.req */
+ rnp = osmo_iuup_rnl_prim_alloc(iuup_test_ctx, OSMO_IUUP_RNL_CONFIG, PRIM_OP_REQUEST, IUUP_MSGB_SIZE);
+ rnp->u.config = def_configure_req;
+ rnp->u.config.active = false;
+ OSMO_ASSERT((rc = osmo_iuup_rnl_prim_down(iui, rnp)) == 0);
+
+ /* Send Init: */
+ tnp = osmo_iuup_tnl_prim_alloc(iuup_test_ctx, OSMO_IUUP_TNL_UNITDATA, PRIM_OP_INDICATION, IUUP_MSGB_SIZE);
+ tnp->oph.msg->l2h = msgb_put(tnp->oph.msg, sizeof(iuup_init));
+ hdr14 = (struct iuup_pdutype14_hdr *)msgb_l2(tnp->oph.msg);
+ memcpy(hdr14, iuup_init, sizeof(iuup_init));
+ OSMO_ASSERT((rc = osmo_iuup_tnl_prim_up(iui, tnp)) == 0);
+
+ osmo_iuup_instance_free(iui);
+}
+
+int main(int argc, char **argv)
+{
+ iuup_test_ctx = talloc_named_const(NULL, 0, "iuup_test");
+ osmo_init_logging2(iuup_test_ctx, NULL);
+ log_set_print_filename2(osmo_stderr_target, LOG_FILENAME_NONE);
+ log_set_print_category(osmo_stderr_target, 1);
+ log_set_print_category_hex(osmo_stderr_target, 0);
+ log_set_use_color(osmo_stderr_target, 0);
+ log_set_category_filter(osmo_stderr_target, DLIUUP, 1, LOGL_DEBUG);
+ osmo_fsm_log_addr(false);
+
+ osmo_gettimeofday_override = true;
+
+ test_crc();
+ test_tinit_timeout_retrans();
+ test_init_nack_retrans();
+ test_init_ack();
+ test_passive_init();
+ test_passive_init_retrans();
+ test_decode_passive_init_2_rfci_no_iptis();
+
+ printf("OK.\n");
+}
diff --git a/tests/iuup/iuup_test.ok b/tests/iuup/iuup_test.ok
new file mode 100644
index 00000000..57baba9c
--- /dev/null
+++ b/tests/iuup/iuup_test.ok
@@ -0,0 +1,61 @@
+=== start: test_crc ===
+iuup_initialization: Header CRC = 0x37
+iuup_initialization: Payload CRC = 0x399
+iuup_initialization_ack: Header CRC = 0x09
+iuup_initialization_ack: Payload CRC = 0x399
+=== end: test_crc ===
+sys={0.000000}, clock_override_set
+_tinit_timeout_retrans_transport_prim_cb()
+Transport: DL len=22: e0 00 df 99 16 00 51 67 3c 01 27 00 00 82 00 00 00 17 10 00 01 00
+sys={1.000000}, clock_override_set
+_tinit_timeout_retrans_transport_prim_cb()
+Transport: DL len=22: e0 00 df 99 16 00 51 67 3c 01 27 00 00 82 00 00 00 17 10 00 01 00
+sys={2.000000}, clock_override_set
+_tinit_timeout_retrans_transport_prim_cb()
+Transport: DL len=22: e0 00 df 99 16 00 51 67 3c 01 27 00 00 82 00 00 00 17 10 00 01 00
+sys={3.000000}, clock_override_set
+_tinit_timeout_retrans_transport_prim_cb()
+Transport: DL len=22: e0 00 df 99 16 00 51 67 3c 01 27 00 00 82 00 00 00 17 10 00 01 00
+sys={4.000000}, clock_override_set
+_tinit_timeout_retrans_user_prim_cb()
+sys={0.000000}, clock_override_set
+_init_nack_retrans_transport_prim_cb()
+Transport: DL len=22: e0 00 df 99 16 00 51 67 3c 01 27 00 00 82 00 00 00 17 10 00 01 00
+_init_nack_retrans_transport_prim_cb()
+Transport: DL len=22: e0 00 df 99 16 00 51 67 3c 01 27 00 00 82 00 00 00 17 10 00 01 00
+_init_nack_retrans_transport_prim_cb()
+Transport: DL len=22: e0 00 df 99 16 00 51 67 3c 01 27 00 00 82 00 00 00 17 10 00 01 00
+_init_nack_retrans_transport_prim_cb()
+Transport: DL len=22: e0 00 df 99 16 00 51 67 3c 01 27 00 00 82 00 00 00 17 10 00 01 00
+_init_nack_retrans_user_prim_cb()
+sys={0.000000}, clock_override_set
+_init_ack_transport_prim_cb()
+Transport: DL len=22: e0 00 df 99 16 00 51 67 3c 01 27 00 00 82 00 00 00 17 10 00 01 00
+_init_ack_user_prim_cb()
+User: UL len=31: 08 55 6d 94 4c 71 a1 a0 81 e7 ea d2 04 24 44 80 00 0e cd 82 b8 11 18 00 00 97 c4 79 4e 77 40
+_init_ack_transport_prim_cb()
+Transport: DL len=35: 01 00 e3 ff 08 55 6d 94 4c 71 a1 a0 81 e7 ea d2 04 24 44 80 00 0e cd 82 b8 11 18 00 00 97 c4 79 4e 77 40
+sys={0.000000}, clock_override_set
+_passive_init_user_prim_cb()
+_passive_init_transport_prim_cb()
+Transport: DL len=4: e4 00 24 00
+_passive_init_user_prim_cb()
+User: UL len=31: 08 55 6d 94 4c 71 a1 a0 81 e7 ea d2 04 24 44 80 00 0e cd 82 b8 11 18 00 00 97 c4 79 4e 77 40
+_passive_init_transport_prim_cb()
+Transport: DL len=35: 01 00 e3 ff 08 55 6d 94 4c 71 a1 a0 81 e7 ea d2 04 24 44 80 00 0e cd 82 b8 11 18 00 00 97 c4 79 4e 77 40
+sys={0.000000}, clock_override_set
+_passive_init_retrans_user_prim_cb()
+_passive_init_transport_prim_cb()
+Transport: DL len=4: e4 00 24 00
+_passive_init_retrans_user_prim_cb()
+_passive_init_transport_prim_cb()
+Transport: DL len=4: e4 00 24 00
+_passive_init_retrans_user_prim_cb()
+User: UL len=31: 08 55 6d 94 4c 71 a1 a0 81 e7 ea d2 04 24 44 80 00 0e cd 82 b8 11 18 00 00 97 c4 79 4e 77 40
+_passive_init_transport_prim_cb()
+Transport: DL len=35: 01 00 e3 ff 08 55 6d 94 4c 71 a1 a0 81 e7 ea d2 04 24 44 80 00 0e cd 82 b8 11 18 00 00 97 c4 79 4e 77 40
+sys={0.000000}, clock_override_set
+_decode_passive_init_2_rfci_no_iptis_user_prim_cb(): Initialization decoded fine!
+_decode_passive_init_2_rfci_no_iptis_transport_prim_cb()
+Transport: DL len=4: e4 00 24 00
+OK.
diff --git a/tests/lapd/lapd_test.c b/tests/lapd/lapd_test.c
index 48851f47..739f9b7d 100644
--- a/tests/lapd/lapd_test.c
+++ b/tests/lapd/lapd_test.c
@@ -16,10 +16,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <osmocom/core/application.h>
@@ -380,7 +376,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");
@@ -474,7 +470,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");
@@ -527,7 +523,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");
@@ -607,7 +603,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");
@@ -681,7 +677,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_test.c b/tests/logging/logging_test.c
index b9cb57fb..3548b1dd 100644
--- a/tests/logging/logging_test.c
+++ b/tests/logging/logging_test.c
@@ -13,10 +13,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <osmocom/core/logging.h>
@@ -77,10 +73,16 @@ int main(int argc, char **argv)
stderr_target = log_target_create_stderr();
log_add_target(stderr_target);
log_set_all_filter(stderr_target, 1);
- log_set_print_filename(stderr_target, 0);
+ log_set_print_filename2(stderr_target, LOG_FILENAME_NONE);
+ log_set_print_category_hex(stderr_target, 0);
log_set_print_category(stderr_target, 1);
log_set_use_color(stderr_target, 0);
+ if (argc > 1 && !strcmp(argv[1], "wqueue"))
+ log_target_file_switch_to_wqueue(stderr_target);
+ else
+ log_target_file_switch_to_stream(stderr_target);
+
log_parse_category_mask(stderr_target, "DRLL:DCC");
log_parse_category_mask(stderr_target, "DRLL");
@@ -128,5 +130,13 @@ int main(int argc, char **argv)
log_set_category_filter(stderr_target, DLGLOBAL, 1, LOGL_DEBUG);
DEBUGP(DLGLOBAL, "You should see this (DLGLOBAL on DEBUG)\n");
+ /* Test printing of the filename */
+ log_set_print_filename2(stderr_target, LOG_FILENAME_BASENAME);
+
+ log_set_print_filename_pos(stderr_target, LOG_FILENAME_POS_HEADER_END);
+ DEBUGP(DLGLOBAL, "A message with source info printed first\n");
+ log_set_print_filename_pos(stderr_target, LOG_FILENAME_POS_LINE_END);
+ DEBUGP(DLGLOBAL, "A message with source info printed last\n");
+
return 0;
}
diff --git a/tests/logging/logging_test.err b/tests/logging/logging_test.err
index 17b3cad0..01ab8782 100644
--- a/tests/logging/logging_test.err
+++ b/tests/logging/logging_test.err
@@ -7,3 +7,5 @@ DLGLOBAL You should see this on DLGLOBAL (c)
DLGLOBAL You should see this on DLGLOBAL (d)
DLGLOBAL You should see this on DLGLOBAL (e)
DLGLOBAL You should see this (DLGLOBAL on DEBUG)
+DLGLOBAL logging_test.c:137 A message with source info printed first
+DLGLOBAL A message with source info printed last (logging_test.c:139)
diff --git a/tests/logging/logging_vty_test.c b/tests/logging/logging_vty_test.c
index e7019f61..15f7fc2b 100644
--- a/tests/logging/logging_vty_test.c
+++ b/tests/logging/logging_vty_test.c
@@ -15,10 +15,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#define _GNU_SOURCE
@@ -82,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);
}
@@ -125,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"
@@ -254,6 +250,7 @@ int main(int argc, char **argv)
log_set_print_category_hex(osmo_stderr_target, 0);
log_set_print_level(osmo_stderr_target, 1);
log_set_print_filename2(osmo_stderr_target, LOG_FILENAME_NONE);
+ log_target_file_switch_to_wqueue(osmo_stderr_target);
if (cmdline_config.config_file) {
rc = vty_read_config_file(cmdline_config.config_file, NULL);
@@ -264,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;
diff --git a/tests/logging/logging_vty_test.vty b/tests/logging/logging_vty_test.vty
index 5a5d7e65..2cff62c1 100644
--- a/tests/logging/logging_vty_test.vty
+++ b/tests/logging/logging_vty_test.vty
@@ -29,7 +29,7 @@ logging_vty_test(config)# no log stderr
logging_vty_test(config)# exit
logging_vty_test# logging level force-all notice
-Logging was not enabled.
+% Logging was not enabled.
logging_vty_test# logging enable
logging_vty_test# logging filter all 1
@@ -48,12 +48,13 @@ logging_vty_test# list
logging color (0|1)
logging timestamp (0|1)
logging print extended-timestamp (0|1)
+ logging print thread-id (0|1)
logging print category (0|1)
logging print category-hex (0|1)
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) (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) (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
@@ -471,38 +472,44 @@ 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) (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) (debug|info|notice|error|fatal) .LOGMESSAGE
...
logging_vty_test# logp?
logp Print a message on all log outputs; useful for placing markers in test logs
logging_vty_test# logp ?
- aa Antropomorphic Armadillos (AA)
- bb Bidirectional Breadspread (BB)
- ccc Chaos Communication Congress (CCC)
- dddd Dehydrated Dribbling Duck Dunkers (DDDD)
- eee Exhaustive Entropy Extraction (EEE)
- lglobal Library-internal global log family
- llapd LAPD in libosmogsm
- linp A-bis Intput Subsystem
- lmux A-bis B-Subchannel TRAU Frame Multiplex
- lmi A-bis Input Driver for Signalling
- lmib A-bis Input Driver for B-Channels (voice)
- lsms Layer3 Short Message Service (SMS)
- lctrl Control Interface
- lgtp GPRS GTP library
- lstats Statistics messages and logging
- lgsup Generic Subscriber Update Protocol
- loap Osmocom Authentication Protocol
- lss7 libosmo-sigtran Signalling System 7
- lsccp libosmo-sigtran SCCP Implementation
- lsua libosmo-sigtran SCCP User Adaptation
- lm3ua libosmo-sigtran MTP3 User Adaptation
- lmgcp libosmo-mgcp Media Gateway Control Protocol
- ljibuf libosmo-netif Jitter Buffer
- lrspro Remote SIM protocol
- lns GPRS NS layer
+ aa Antropomorphic Armadillos (AA)
+ bb Bidirectional Breadspread (BB)
+ ccc Chaos Communication Congress (CCC)
+ dddd Dehydrated Dribbling Duck Dunkers (DDDD)
+ eee Exhaustive Entropy Extraction (EEE)
+ lglobal Library-internal global log family
+ llapd LAPD in libosmogsm
+ linp A-bis Intput Subsystem
+ lmux A-bis B-Subchannel TRAU Frame Multiplex
+ lmi A-bis Input Driver for Signalling
+ lmib A-bis Input Driver for B-Channels (voice)
+ lsms Layer3 Short Message Service (SMS)
+ lctrl Control Interface
+ lgtp GPRS GTP library
+ lstats Statistics messages and logging
+ lgsup Generic Subscriber Update Protocol
+ loap Osmocom Authentication Protocol
+ lss7 libosmo-sigtran Signalling System 7
+ lsccp libosmo-sigtran SCCP Implementation
+ lsua libosmo-sigtran SCCP User Adaptation
+ lm3ua libosmo-sigtran MTP3 User Adaptation
+ lmgcp libosmo-mgcp Media Gateway Control Protocol
+ ljibuf libosmo-netif Jitter Buffer
+ lrspro Remote SIM protocol
+ lns GPRS NS layer
+ lbssgp GPRS BSSGP layer
+ lnsdata GPRS NS layer data PDU
+ lnssignal GPRS NS layer signal PDU
+ liuup Iu UP layer
+ lpfcp libosmo-pfcp Packet Forwarding Control Protocol
+ lcsn1 libosmo-csn1 Concrete Syntax Notation 1 codec
logging_vty_test# logp lglobal ?
debug Log debug messages and higher levels
@@ -522,3 +529,6 @@ DAA ERROR This is the log message
logging_vty_test# logp lglobal debug This log message is not echoed
logging_vty_test# logp lglobal notice This log message is echoed
DLGLOBAL NOTICE This log message is echoed
+
+logging_vty_test# logp lctrl notice This is a CTRL specific message
+DLCTRL NOTICE This is a CTRL specific message
diff --git a/tests/loggingrb/loggingrb_test.c b/tests/loggingrb/loggingrb_test.c
index 0b2ae5b5..fd11896a 100644
--- a/tests/loggingrb/loggingrb_test.c
+++ b/tests/loggingrb/loggingrb_test.c
@@ -14,10 +14,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <osmocom/core/logging.h>
@@ -64,7 +60,9 @@ int main(int argc, char **argv)
ringbuf_target = log_target_create_rb(0x1000);
log_add_target(ringbuf_target);
log_set_all_filter(ringbuf_target, 1);
- log_set_print_filename(ringbuf_target, 0);
+ log_set_print_filename2(ringbuf_target, LOG_FILENAME_NONE);
+ log_set_print_category(ringbuf_target, 0);
+ log_set_print_category_hex(ringbuf_target, 0);
log_parse_category_mask(ringbuf_target, "DRLL:DCC");
log_parse_category_mask(ringbuf_target, "DRLL");
diff --git a/tests/msgb/msgb_test.c b/tests/msgb/msgb_test.c
index ffaa1557..7966103b 100644
--- a/tests/msgb/msgb_test.c
+++ b/tests/msgb/msgb_test.c
@@ -12,10 +12,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <stdlib.h>
@@ -65,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;
@@ -121,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;
@@ -142,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;
@@ -165,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));
+
+ if (!msgb_eq_data_print(msg2, msg->data, msgb_length(msg)))
+ printf("copy test failed!\n");
- for (i = 0; i < msgb_length(msg2); i++)
- OSMO_ASSERT(msg2->data[i] == (uint8_t)i);
+ 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;
@@ -277,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/msgfile/msgfile_test.c b/tests/msgfile/msgfile_test.c
index 2684b6a4..3fe173d6 100644
--- a/tests/msgfile/msgfile_test.c
+++ b/tests/msgfile/msgfile_test.c
@@ -13,10 +13,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <osmocom/core/msgfile.h>
diff --git a/tests/oap/oap_client_test.c b/tests/oap/oap_client_test.c
index dadf2ee7..622912f9 100644
--- a/tests/oap/oap_client_test.c
+++ b/tests/oap/oap_client_test.c
@@ -260,7 +260,8 @@ int main(int argc, char **argv)
OSMO_ASSERT(osmo_stderr_target);
log_set_use_color(osmo_stderr_target, 0);
log_set_print_timestamp(osmo_stderr_target, 0);
- log_set_print_filename(osmo_stderr_target, 0);
+ log_set_print_filename2(osmo_stderr_target, LOG_FILENAME_NONE);
+ log_set_print_category_hex(osmo_stderr_target, 0);
log_set_print_category(osmo_stderr_target, 1);
log_parse_category_mask(osmo_stderr_target, "DLOAP,1");
diff --git a/tests/oap/oap_test.c b/tests/oap/oap_test.c
index 32676ca3..bebd6031 100644
--- a/tests/oap/oap_test.c
+++ b/tests/oap/oap_test.c
@@ -15,10 +15,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <osmocom/core/application.h>
diff --git a/tests/osmo-auc-gen/osmo-auc-gen_test.ok b/tests/osmo-auc-gen/osmo-auc-gen_test.ok
index 2840783a..3c41f41b 100644
--- a/tests/osmo-auc-gen/osmo-auc-gen_test.ok
+++ b/tests/osmo-auc-gen/osmo-auc-gen_test.ok
@@ -9,6 +9,8 @@ AUTN: 790c5d80c47b0000716ce00883bc39e1
IK: 6cf555588bb61ab2ff23cd333c05ed09
CK: f0b29f50a7d873f30336473bdc35d04f
RES: f511d3a7f06e6a30
+IMS nonce: amEFB2XKoyyQNxNw5dbcLXkMXYDEewAAcWzgCIO8OeE=
+IMS res: 9RHTp/BuajA=
SRES: 057fb997
Kc: 60524000cc5e5407
SQN: 0
@@ -24,6 +26,8 @@ AUTN: 790c5d80c47a000058508ab3864e26a0
IK: 6cf555588bb61ab2ff23cd333c05ed09
CK: f0b29f50a7d873f30336473bdc35d04f
RES: f511d3a7f06e6a30
+IMS nonce: amEFB2XKoyyQNxNw5dbcLXkMXYDEegAAWFCKs4ZOJqA=
+IMS res: 9RHTp/BuajA=
SRES: 057fb997
Kc: 60524000cc5e5407
SQN: 1
@@ -39,6 +43,8 @@ AUTN: 790c5d80c46c0000e74d796ec095dbee
IK: 6cf555588bb61ab2ff23cd333c05ed09
CK: f0b29f50a7d873f30336473bdc35d04f
RES: f511d3a7f06e6a30
+IMS nonce: amEFB2XKoyyQNxNw5dbcLXkMXYDEbAAA5015bsCV2+4=
+IMS res: 9RHTp/BuajA=
SRES: 057fb997
Kc: 60524000cc5e5407
SQN: 23
@@ -54,6 +60,8 @@ AUTN: 434a46a71aeb0000fedc563f27a0916c
IK: d7213dd74860ccb8c14e54c0c4abc91c
CK: c350653d72f7a5bac3a27422e5186019
RES: 912cdfaadd7b0154
+IMS nonce: HcT5dDJczmEeVPUW3B/sVkNKRqca6wAA/txWPyegkWw=
+IMS res: kSzfqt17AVQ=
SRES: 4c57defe
Kc: 169d78081b24c007
SQN: 42
@@ -69,6 +77,8 @@ AUTN: bfbf3332c91e0000d6199cad31d15f26
IK: 191a93c4396113bff6939d4f98e169a6
CK: 9c38d9089265ed5ea164e190a65c200d
RES: fd40205be2c9c7b2
+IMS nonce: KkgWL/PtykrfC3teUn1sFr+/MzLJHgAA1hmcrTHRXyY=
+IMS res: /UAgW+LJx7I=
SRES: 1f89e7e9
Kc: d2d5361395b9b74a
SQN: 99
@@ -84,6 +94,8 @@ AUTN: afb993e4f4b8000069cdeebb4a4b5b58
IK: c348c2fe2f3e1fb37a7ae1638163bd98
CK: e740c156278705a14e1a99ba6d31334f
RES: 7c04e86a67967fcd
+IMS nonce: amEFB2XKoyyQNxNw5dbcLa+5k+T0uAAAac3uu0pLW1g=
+IMS res: fAToameWf80=
SRES: 1b9297a7
Kc: 10687b71e4eb94c5
SQN: 281474976710655
@@ -99,6 +111,8 @@ AUTN: 8704f5ba55d30000541dde77ea5b1d8c
IK: 27497388b6cb044648f396aa155b95ef
CK: f64735036e5871319c679f4742a75ea1
RES: e229c19e791f2e41
+IMS nonce: OfovTj1SPYYZpztPZcPhTYcE9bpV0wAAVB3ed+pbHYw=
+IMS res: 4inBnnkfLkE=
SRES: 9b36efdf
Kc: 059a4f668f6fbe39
SQN: 32
@@ -115,6 +129,8 @@ AUTN: 8704f5ba55d6000079267a4b347ad890
IK: 27497388b6cb044648f396aa155b95ef
CK: f64735036e5871319c679f4742a75ea1
RES: e229c19e791f2e41
+IMS nonce: OfovTj1SPYYZpztPZcPhTYcE9bpV1gAAeSZ6SzR62JA=
+IMS res: 4inBnnkfLkE=
SRES: 9b36efdf
Kc: 059a4f668f6fbe39
SQN: 37
@@ -131,6 +147,8 @@ AUTN: 8704f5ba55c40000129ddaa4f5016e25
IK: 27497388b6cb044648f396aa155b95ef
CK: f64735036e5871319c679f4742a75ea1
RES: e229c19e791f2e41
+IMS nonce: OfovTj1SPYYZpztPZcPhTYcE9bpVxAAAEp3apPUBbiU=
+IMS res: 4inBnnkfLkE=
SRES: 9b36efdf
Kc: 059a4f668f6fbe39
SQN: 55
@@ -147,6 +165,8 @@ AUTN: 8704f5ba55cc00009d169f5ff89f6087
IK: 27497388b6cb044648f396aa155b95ef
CK: f64735036e5871319c679f4742a75ea1
RES: e229c19e791f2e41
+IMS nonce: OfovTj1SPYYZpztPZcPhTYcE9bpVzAAAnRafX/ifYIc=
+IMS res: 4inBnnkfLkE=
SRES: 9b36efdf
Kc: 059a4f668f6fbe39
SQN: 63
@@ -163,6 +183,8 @@ AUTN: 8704f5ba55eb0000d7fc4f7f19cfc180
IK: 27497388b6cb044648f396aa155b95ef
CK: f64735036e5871319c679f4742a75ea1
RES: e229c19e791f2e41
+IMS nonce: OfovTj1SPYYZpztPZcPhTYcE9bpV6wAA1/xPfxnPwYA=
+IMS res: 4inBnnkfLkE=
SRES: 9b36efdf
Kc: 059a4f668f6fbe39
SQN: 24
@@ -179,6 +201,8 @@ AUTN: 8704f5ba55eb0000d7fc4f7f19cfc180
IK: 27497388b6cb044648f396aa155b95ef
CK: f64735036e5871319c679f4742a75ea1
RES: e229c19e791f2e41
+IMS nonce: OfovTj1SPYYZpztPZcPhTYcE9bpV6wAA1/xPfxnPwYA=
+IMS res: 4inBnnkfLkE=
SRES: 9b36efdf
Kc: 059a4f668f6fbe39
SQN: 24
@@ -195,6 +219,8 @@ AUTN: 8704f5ba55ea0000aab06de3fd6c01af
IK: 27497388b6cb044648f396aa155b95ef
CK: f64735036e5871319c679f4742a75ea1
RES: e229c19e791f2e41
+IMS nonce: OfovTj1SPYYZpztPZcPhTYcE9bpV6gAAqrBt4/1sAa8=
+IMS res: 4inBnnkfLkE=
SRES: 9b36efdf
Kc: 059a4f668f6fbe39
SQN: 25
@@ -211,6 +237,8 @@ AUTN: 8704f5ba54f30000cbba2fbba3c5e242
IK: 27497388b6cb044648f396aa155b95ef
CK: f64735036e5871319c679f4742a75ea1
RES: e229c19e791f2e41
+IMS nonce: OfovTj1SPYYZpztPZcPhTYcE9bpU8wAAy7ovu6PF4kI=
+IMS res: 4inBnnkfLkE=
SRES: 9b36efdf
Kc: 059a4f668f6fbe39
SQN: 256
@@ -227,6 +255,8 @@ AUTN: 8704f5ba54f200008f8e14579da5ecbb
IK: 27497388b6cb044648f396aa155b95ef
CK: f64735036e5871319c679f4742a75ea1
RES: e229c19e791f2e41
+IMS nonce: OfovTj1SPYYZpztPZcPhTYcE9bpU8gAAj44UV52l7Ls=
+IMS res: 4inBnnkfLkE=
SRES: 9b36efdf
Kc: 059a4f668f6fbe39
SQN: 257
diff --git a/tests/sercomm/sercomm_test.c b/tests/sercomm/sercomm_test.c
index 058c9eb4..9bffc0d8 100644
--- a/tests/sercomm/sercomm_test.c
+++ b/tests/sercomm/sercomm_test.c
@@ -13,10 +13,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <stdio.h>
diff --git a/tests/sim/sim_test.c b/tests/sim/sim_test.c
index 425ce11d..2e2eec58 100644
--- a/tests/sim/sim_test.c
+++ b/tests/sim/sim_test.c
@@ -12,10 +12,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <stdio.h>
diff --git a/tests/sms/sms_test.c b/tests/sms/sms_test.c
index 06153964..912c0829 100644
--- a/tests/sms/sms_test.c
+++ b/tests/sms/sms_test.c
@@ -13,10 +13,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <stdio.h>
@@ -219,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;
@@ -268,6 +264,54 @@ static void test_gen_oa(void)
printf("Result: len(%d) data(%s)\n", len, osmo_hexdump(oa, len));
}
+static void test_enc_large_msg(void)
+{
+ uint8_t enc_buf[2048 * 7 / 8];
+ char large_msg[2048 + 1];
+ int i, j, nsep, noct = 0;
+
+ printf("\nRunning %s\n", __func__);
+
+ /* Expected chunks (repeated) in the output buffer */
+ const uint8_t exp_chunk[] = { 0xc1, 0x60, 0x30, 0x18, 0x0c, 0x06, 0x83 };
+
+ /* Length variants to be tested */
+ static const size_t nlen[] = { 2048, 1024, 555, 512, 260, 255, 250 };
+
+ memset(&large_msg[0], (int) 'A', sizeof(large_msg) - 1);
+
+ for (i = 0; i < ARRAY_SIZE(nlen); i++) {
+ /* Clear the output buffer first */
+ memset(&enc_buf[0], 0x00, sizeof(enc_buf));
+ /* Limit length of the input string */
+ large_msg[nlen[i]] = '\0';
+
+ /* How many octets we expect to be used? */
+ int noct_exp = nlen[i] * 7 / 8;
+ if (nlen[i] % 8 != 0)
+ noct_exp++;
+
+ /* Encode a sequence of 'A' repeated nlen[i] times */
+ nsep = gsm_7bit_encode_n(&enc_buf[0], sizeof(enc_buf), large_msg, &noct);
+ printf("gsm_7bit_encode_n(len=%zu) processed %d septets (expected %zu): %s\n",
+ nlen[i], nsep, nlen[i], nsep == nlen[i] ? "OK" : "FAIL");
+ printf("gsm_7bit_encode_n(len=%zu) used %d octets in the buffer (expected %d): %s\n",
+ nlen[i], noct, noct_exp, noct == noct_exp ? "OK" : "FAIL");
+
+ /* The encoding result is expected to consist of repeated chunks */
+ for (j = 0; j < noct_exp; j += sizeof(exp_chunk)) {
+ size_t len = OSMO_MIN(noct_exp - j, sizeof(exp_chunk));
+ if (nlen[i] % 8 != 0) /* skip incomplete octets */
+ len--;
+ if (memcmp(&enc_buf[j], exp_chunk, len) != 0) {
+ printf("\tUnexpected chunk at enc_buf[%d:%zu]: %s\n",
+ j, len, osmo_hexdump(&enc_buf[j], len));
+ break; /* No need to show them all */
+ }
+ }
+ }
+}
+
int main(int argc, char** argv)
{
printf("SMS testing\n");
@@ -336,7 +380,7 @@ int main(int argc, char** argv)
memcpy(tmp, septet_data, concatenated_part1_septet_length);
/* In our case: test_multiple_decode[0].ud_hdr_ind equals number of padding bits*/
- octet_length = gsm_septets2octets(coded, tmp, concatenated_part1_septet_length, test_multiple_encode[0].ud_hdr_ind);
+ octet_length = gsm_septet_pack(coded, tmp, concatenated_part1_septet_length, test_multiple_encode[0].ud_hdr_ind);
/* copy header */
memset(tmp, 0x42, sizeof(tmp));
@@ -354,7 +398,7 @@ int main(int argc, char** argv)
memcpy(tmp, septet_data + concatenated_part1_septet_length, concatenated_part2_septet_length);
/* In our case: test_multiple_decode[1].ud_hdr_ind equals number of padding bits*/
- octet_length = gsm_septets2octets(coded, tmp, concatenated_part2_septet_length, test_multiple_encode[1].ud_hdr_ind);
+ octet_length = gsm_septet_pack(coded, tmp, concatenated_part2_septet_length, test_multiple_encode[1].ud_hdr_ind);
/* copy header */
memset(tmp, 0x42, sizeof(tmp));
@@ -396,6 +440,7 @@ int main(int argc, char** argv)
test_octet_return();
test_gen_oa();
+ test_enc_large_msg();
printf("OK\n");
return 0;
diff --git a/tests/sms/sms_test.ok b/tests/sms/sms_test.ok
index a71567de..de1fce3a 100644
--- a/tests/sms/sms_test.ok
+++ b/tests/sms/sms_test.ok
@@ -18,4 +18,20 @@ Result: len(12) data(14 a1 21 43 65 87 09 21 43 65 87 19 )
Result: len(2) data(00 91 )
Result: len(9) data(0e d0 4f 78 d9 2d 9c 0e 01 )
Result: len(12) data(14 d0 4f 78 d9 2d 9c 0e c3 e2 31 19 )
+
+Running test_enc_large_msg
+gsm_7bit_encode_n(len=2048) processed 2048 septets (expected 2048): OK
+gsm_7bit_encode_n(len=2048) used 1792 octets in the buffer (expected 1792): OK
+gsm_7bit_encode_n(len=1024) processed 1024 septets (expected 1024): OK
+gsm_7bit_encode_n(len=1024) used 896 octets in the buffer (expected 896): OK
+gsm_7bit_encode_n(len=555) processed 555 septets (expected 555): OK
+gsm_7bit_encode_n(len=555) used 486 octets in the buffer (expected 486): OK
+gsm_7bit_encode_n(len=512) processed 512 septets (expected 512): OK
+gsm_7bit_encode_n(len=512) used 448 octets in the buffer (expected 448): OK
+gsm_7bit_encode_n(len=260) processed 260 septets (expected 260): OK
+gsm_7bit_encode_n(len=260) used 228 octets in the buffer (expected 228): OK
+gsm_7bit_encode_n(len=255) processed 255 septets (expected 255): OK
+gsm_7bit_encode_n(len=255) used 224 octets in the buffer (expected 224): OK
+gsm_7bit_encode_n(len=250) processed 250 septets (expected 250): OK
+gsm_7bit_encode_n(len=250) used 219 octets in the buffer (expected 219): OK
OK
diff --git a/tests/smscb/cbsp_test.c b/tests/smscb/cbsp_test.c
new file mode 100644
index 00000000..2dbdded7
--- /dev/null
+++ b/tests/smscb/cbsp_test.c
@@ -0,0 +1,108 @@
+/*
+ * (C) 2022 by sysmocom - s.f.m.c. GmbH
+ * All Rights Reserved
+ *
+ * Author: Pau Espin Pedrol <pespin@sysmocom.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <osmocom/core/utils.h>
+#include <osmocom/core/msgb.h>
+
+#include <osmocom/gsm/protocol/gsm_48_049.h>
+#include <osmocom/gsm/cbsp.h>
+
+/*
+CBSP WRITE-REPLACE FAILURE
+ Message Type: WRITE-REPLACE FAILURE (3)
+ Message Length: 44
+ IE: Message Identifier: 0x0031
+ Information Element Identifier: Message Identifier (14)
+ Message Identifier: 0x0031
+ IE: New Serial Number: 0x4170
+ Information Element Identifier: New Serial Number (3)
+ New Serial Number: 0x4170
+ IE: Failure List: 2 items
+ Information Element Identifier: Failure List (9)
+ Information Element Length: 15
+ Failure List Item: MCC 901 International Mobile, shared code, MNC 70 Clementvale Baltic OÜ, LAC 0x0018, CI 0x0030: Cause Cell-identity-not-valid
+ Cell ID Discriminator: CGI (0)
+ Mobile Country Code (MCC): International Mobile, shared code (901)
+ Mobile Network Code (MNC): Clementvale Baltic OÜ (70)
+ Location Area Code (LAC): 0x0018
+ Cell Identifier (CI): 0x0030
+ Cause: Cell-identity-not-valid (0x03)
+ Failure List Item: LAC 02711, CI 0xc351: Cause LAI-or-LAC-not-valid
+ Cell ID Discriminator: LAC+CI (1)
+ Location Area Code (LAC): 0x2711
+ Cell Identifier (CI): 0xc351
+ Cause: LAI-or-LAC-not-valid (0x0f)
+ IE: Cell List (CGI): 2 items
+ Information Element Identifier: Cell List (4)
+ Information Element Length: 15
+ Cell ID Discriminator: CGI (0)
+ Cell List Item: MCC 901 International Mobile, shared code, MNC 70 Clementvale Baltic OÜ, LAC 0x0017, CI 0x002a
+ Mobile Country Code (MCC): International Mobile, shared code (901)
+ Mobile Network Code (MNC): Clementvale Baltic OÜ (70)
+ Location Area Code (LAC): 0x0017
+ Cell Identifier (CI): 0x002a
+ Cell List Item: MCC 901 International Mobile, shared code, MNC 70 Clementvale Baltic OÜ, LAC 0x0018, CI 0x002a
+ Mobile Country Code (MCC): International Mobile, shared code (901)
+ Mobile Network Code (MNC): Clementvale Baltic OÜ (70)
+ Location Area Code (LAC): 0x0018
+ Cell Identifier (CI): 0x002a
+ IE: Channel Indicator: basic channel
+ Information Element Identifier: Channel Indicator (18)
+ Channel Indicator: basic channel (0x00)
+*/
+static const char write_repl_fail_with_failure_list[] =
+ "0300002c0e003103417009000f0009f1070018003003012711c3510f04000f0009f1070017002a09f1070018002a1200";
+
+static struct msgb *msgb_from_hex(unsigned int size, const char *hex)
+{
+ struct msgb *msg = msgb_alloc(size, "test_cbsp");
+ OSMO_ASSERT(msg);
+ msg->l1h = msgb_put(msg, osmo_hexparse(hex, msg->data, msgb_tailroom(msg)));
+ msg->l2h = msg->l1h + sizeof(struct cbsp_header);
+ return msg;
+}
+
+static void test_decode(void)
+{
+ struct msgb *msg;
+ struct osmo_cbsp_decoded *cbsp_dec;
+
+ printf("=== %s start ===\n", __func__);
+
+ msg = msgb_from_hex(sizeof(write_repl_fail_with_failure_list),
+ write_repl_fail_with_failure_list);
+
+ cbsp_dec = osmo_cbsp_decode(NULL, msg);
+ OSMO_ASSERT(cbsp_dec);
+
+ talloc_free(cbsp_dec);
+ msgb_free(msg);
+
+ printf("=== %s end ===\n", __func__);
+}
+
+int main(int argc, char **argv)
+{
+ test_decode();
+
+ return EXIT_SUCCESS;
+}
diff --git a/tests/smscb/cbsp_test.ok b/tests/smscb/cbsp_test.ok
new file mode 100644
index 00000000..a837de57
--- /dev/null
+++ b/tests/smscb/cbsp_test.ok
@@ -0,0 +1,2 @@
+=== test_decode start ===
+=== test_decode end ===
diff --git a/tests/smscb/gsm0341_test.c b/tests/smscb/gsm0341_test.c
index c400f5c8..966a00fd 100644
--- a/tests/smscb/gsm0341_test.c
+++ b/tests/smscb/gsm0341_test.c
@@ -12,10 +12,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <string.h>
@@ -29,7 +25,7 @@
#include <osmocom/core/utils.h>
#include <osmocom/core/msgb.h>
-struct gsm341_ms_message *gen_msg_from_text(uint16_t msg_id, const char *text)
+struct gsm341_ms_message *gen_msg_from_text(uint16_t msg_id, uint8_t msg_code, const char *text)
{
struct gsm341_ms_message *cbmsg;
int text_len = strlen(text);
@@ -38,11 +34,11 @@ struct gsm341_ms_message *gen_msg_from_text(uint16_t msg_id, const char *text)
uint8_t payload[text_len];
int payload_octets;
- srand(time(NULL));
+ //srand(time(NULL));
gsm_7bit_encode_n(payload, sizeof(payload), text, &payload_octets);
//cbmsg = gsm0341_build_msg(NULL, 0, rand(), 0, msg_id, 0x0f, 1, 1, payload, payload_octets);
- cbmsg = gsm0341_build_msg(NULL, 0, rand(), 0, msg_id, 0x00, 1, 1, payload, payload_octets);
+ cbmsg = gsm0341_build_msg(NULL, 0, msg_code, 0, msg_id, 0x00, 1, 1, payload, payload_octets);
printf("%s\n", osmo_hexdump_nospc((uint8_t *)cbmsg, sizeof(*cbmsg)+payload_octets));
@@ -54,6 +50,7 @@ int main(int argc, char **argv)
uint16_t msg_id = GSM341_MSGID_ETWS_CMAS_MONTHLY_TEST;
char *text = "Mahlzeit!";
char tbuf[GSM341_MAX_CHARS+1];
+ struct gsm341_ms_message *cbmsg;
if (argc > 1)
msg_id = atoi(argv[1]);
@@ -67,7 +64,8 @@ int main(int argc, char **argv)
sizeof(tbuf)-strlen(text));
tbuf[GSM341_MAX_CHARS] = 0;
- gen_msg_from_text(msg_id, tbuf);
+ cbmsg = gen_msg_from_text(msg_id, 1, tbuf);
+ talloc_free(cbmsg);
return EXIT_SUCCESS;
}
diff --git a/tests/smscb/gsm0341_test.ok b/tests/smscb/gsm0341_test.ok
new file mode 100644
index 00000000..600797c3
--- /dev/null
+++ b/tests/smscb/gsm0341_test.ok
@@ -0,0 +1 @@
+0010111c0011cd309aad2fa7e9a146a3d168341a8d46a3d168341a8d46a3d168341a8d46a3d168341a8d46a3d168341a8d46a3d168341a8d46a3d168341a8d46a3d168341a8d46a3d168341a8d46a3d168341a8d46a3d100
diff --git a/tests/smscb/smscb_test.c b/tests/smscb/smscb_test.c
index 5925f69b..3b6b74d3 100644
--- a/tests/smscb/smscb_test.c
+++ b/tests/smscb/smscb_test.c
@@ -12,10 +12,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <osmocom/gsm/protocol/gsm_03_41.h>
diff --git a/tests/sockaddr_str/sockaddr_str_test.c b/tests/sockaddr_str/sockaddr_str_test.c
index 64a61043..541c3a15 100644
--- a/tests/sockaddr_str/sockaddr_str_test.c
+++ b/tests/sockaddr_str/sockaddr_str_test.c
@@ -18,10 +18,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <stdio.h>
@@ -94,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];
@@ -239,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 e70b8be5..e099be92 100644
--- a/tests/socket/socket_sctp_test.c
+++ b/tests/socket/socket_sctp_test.c
@@ -12,10 +12,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <stdio.h>
@@ -35,7 +31,7 @@
#include <osmocom/core/logging.h>
#include <osmocom/core/bits.h>
-#include "../config.h"
+#include "config.h"
void *ctx = NULL;
@@ -222,7 +218,9 @@ int main(int argc, char *argv[])
ctx = talloc_named_const(NULL, 0, "socket_test_sctp");
osmo_init_logging2(ctx, &info);
log_set_use_color(osmo_stderr_target, 0);
- log_set_print_filename(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);
#ifdef HAVE_LIBSCTP
test_sockinit2_multiaddr_simple();
test_sockinit2_multiaddr_several();
diff --git a/tests/socket/socket_test.c b/tests/socket/socket_test.c
index 671177fb..ddb69268 100644
--- a/tests/socket/socket_test.c
+++ b/tests/socket/socket_test.c
@@ -12,10 +12,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <stdio.h>
@@ -36,7 +32,7 @@
#include <osmocom/core/logging.h>
#include <osmocom/core/bits.h>
-#include "../config.h"
+#include "config.h"
void *ctx = NULL;
@@ -154,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] = { };
@@ -313,6 +309,139 @@ static int test_sockinit_osa(void)
return 0;
}
+static void test_osa_str(void)
+{
+ char buf[256];
+ const char *result;
+ struct osmo_sockaddr localhost4 = {};
+ struct osmo_sockaddr localhost6 = {};
+
+ localhost4.u.sin = (struct sockaddr_in){
+ .sin_family = AF_INET,
+ .sin_addr.s_addr = inet_addr("127.0.0.1"),
+ .sin_port = htons(42),
+ };
+
+ localhost6.u.sin6 = (struct sockaddr_in6){
+ .sin6_family = AF_INET6,
+ .sin6_port = htons(42),
+ };
+ inet_pton(AF_INET6, "::1", &localhost6.u.sin6.sin6_addr);
+
+ /* test a too short str */
+ memset(&buf[0], 0, sizeof(buf));
+ result = osmo_sockaddr_to_str_buf(buf, 1, &localhost4);
+ printf("Checking osmo_sockaddr_to_str_buf to small IPv4\n");
+ OSMO_ASSERT(result == NULL);
+
+ memset(&buf[0], 0, sizeof(buf));
+ result = osmo_sockaddr_to_str_buf(buf, sizeof(buf), &localhost4);
+ printf("Checking osmo_sockaddr_to_str_buf IPv4\n");
+ OSMO_ASSERT(!strncmp("127.0.0.1:42", result, sizeof(buf)));
+
+ memset(&buf[0], 0, sizeof(buf));
+ result = osmo_sockaddr_to_str_buf(buf, 256, &localhost6);
+ printf("Checking osmo_sockaddr_to_str_buf IPv6\n");
+ OSMO_ASSERT(!strncmp("[::1]:42", result, sizeof(buf)));
+
+ memset(&buf[0], 0, sizeof(buf));
+ printf("Checking osmo_sockaddr_to_str_buf too short IPv6\n");
+ result = osmo_sockaddr_to_str_buf(buf, 8, &localhost6);
+ OSMO_ASSERT(result == NULL);
+ osmo_sockaddr_to_str_buf2(buf, 8, &localhost6);
+ OSMO_ASSERT(!strncmp("[::1]:4", buf, sizeof(buf)));
+
+ memset(&buf[0], 0, sizeof(buf));
+ result = osmo_sockaddr_to_str_buf(buf, 5, &localhost6);
+ printf("Checking osmo_sockaddr_to_str_buf too short IPv6\n");
+ OSMO_ASSERT(result == NULL);
+
+ localhost6.u.sin6.sin6_port = 0;
+ memset(&buf[0], 0, sizeof(buf));
+ result = osmo_sockaddr_to_str_buf(buf, 5, &localhost6);
+ printf("Checking osmo_sockaddr_to_str_buf only 5 bytes IPv6\n");
+ OSMO_ASSERT(result == NULL);
+
+ inet_pton(AF_INET6, "::", &localhost6.u.sin6.sin6_addr);
+ memset(&buf[0], 0, sizeof(buf));
+ result = osmo_sockaddr_to_str_buf(buf, 5, &localhost6);
+ printf("Checking osmo_sockaddr_to_str_buf only 5 bytes IPv6\n");
+ OSMO_ASSERT(!strncmp("[::]", result, sizeof(buf)));
+
+ inet_pton(AF_INET6, "2003:1234:5678:90ab:cdef:1234:4321:4321", &localhost6.u.sin6.sin6_addr);
+ memset(&buf[0], 0, sizeof(buf));
+ result = osmo_sockaddr_to_str_buf(buf, sizeof(buf), &localhost6);
+ printf("Checking osmo_sockaddr_to_str_buf long IPv6\n");
+ OSMO_ASSERT(!strncmp("[2003:1234:5678:90ab:cdef:1234:4321:4321]", result, sizeof(buf)));
+
+ localhost6.u.sin6.sin6_port = htons(23420);
+ memset(&buf[0], 0, sizeof(buf));
+ result = osmo_sockaddr_to_str_buf(buf, sizeof(buf), &localhost6);
+ printf("Checking osmo_sockaddr_to_str_buf long IPv6 port\n");
+ OSMO_ASSERT(!strncmp("[2003:1234:5678:90ab:cdef:1234:4321:4321]:23420", result, sizeof(buf)));
+
+ result = osmo_sockaddr_to_str(&localhost6);
+ printf("Checking osmo_sockaddr_to_str_buf long IPv6 port static buffer\n");
+ 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[] = {
};
@@ -326,12 +455,16 @@ int main(int argc, char *argv[])
ctx = talloc_named_const(NULL, 0, "socket_test");
osmo_init_logging2(ctx, &info);
log_set_use_color(osmo_stderr_target, 0);
- log_set_print_filename(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_sockinit();
test_sockinit2();
test_get_ip_and_port();
test_sockinit_osa();
+ test_osa_str();
+ test_osa_netmask_prefixlen();
return EXIT_SUCCESS;
}
diff --git a/tests/socket/socket_test.ok b/tests/socket/socket_test.ok
index 9a52d44e..236c0111 100644
--- a/tests/socket/socket_test.ok
+++ b/tests/socket/socket_test.ok
@@ -21,3 +21,13 @@ Checking osmo_sock_init_osa() for combined BIND + CONNECT on IPv6
Checking osmo_sock_init_osa() must fail on mixed IPv4 & IPv6
Checking osmo_sock_init_osa() must fail on mixed IPv6 & IPv4
Checking osmo_sock_init_osa() must fail on invalid osmo_sockaddr
+Checking osmo_sockaddr_to_str_buf to small IPv4
+Checking osmo_sockaddr_to_str_buf IPv4
+Checking osmo_sockaddr_to_str_buf IPv6
+Checking osmo_sockaddr_to_str_buf too short IPv6
+Checking osmo_sockaddr_to_str_buf too short IPv6
+Checking osmo_sockaddr_to_str_buf only 5 bytes IPv6
+Checking osmo_sockaddr_to_str_buf only 5 bytes IPv6
+Checking osmo_sockaddr_to_str_buf long IPv6
+Checking osmo_sockaddr_to_str_buf long IPv6 port
+Checking osmo_sockaddr_to_str_buf long IPv6 port static buffer
diff --git a/tests/stats/stats_test.c b/tests/stats/stats_test.c
index 71f710a9..2796100d 100644
--- a/tests/stats/stats_test.c
+++ b/tests/stats/stats_test.c
@@ -16,18 +16,17 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
+#include <osmocom/core/application.h>
#include <osmocom/core/logging.h>
#include <osmocom/core/utils.h>
#include <osmocom/core/stat_item.h>
#include <osmocom/core/rate_ctr.h>
#include <osmocom/core/stats.h>
+#include <stat_item_internal.h>
+
#include <stdio.h>
#include <inttypes.h>
@@ -87,11 +86,10 @@ static void stat_test(void)
struct osmo_stat_item_group *sgrp2;
const struct osmo_stat_item *sitem1, *sitem2;
- int rc;
int32_t value;
- int32_t rd_a = 0;
- int32_t rd_b = 0;
int i;
+ int64_t sum1;
+ int64_t sum2;
OSMO_ASSERT(statg != NULL);
@@ -109,144 +107,151 @@ static void stat_test(void)
sitem1 = osmo_stat_item_get_by_name(statg, "item.a");
OSMO_ASSERT(sitem1 != NULL);
- OSMO_ASSERT(sitem1 == statg->items[TEST_A_ITEM]);
+ OSMO_ASSERT(sitem1 == osmo_stat_item_group_get_item(statg, TEST_A_ITEM));
sitem2 = osmo_stat_item_get_by_name(statg, "item.b");
OSMO_ASSERT(sitem2 != NULL);
OSMO_ASSERT(sitem2 != sitem1);
- OSMO_ASSERT(sitem2 == statg->items[TEST_B_ITEM]);
+ OSMO_ASSERT(sitem2 == osmo_stat_item_group_get_item(statg, TEST_B_ITEM));
- value = osmo_stat_item_get_last(statg->items[TEST_A_ITEM]);
+ /* No value set yet, expecting default value from osmo_stat_item_desc definition above. */
+ value = osmo_stat_item_get_last(osmo_stat_item_group_get_item(statg, TEST_A_ITEM));
OSMO_ASSERT(value == -1);
- rc = osmo_stat_item_get_next(statg->items[TEST_A_ITEM], &rd_a, &value);
- OSMO_ASSERT(rc == 0);
-
- osmo_stat_item_set(statg->items[TEST_A_ITEM], 1);
-
- value = osmo_stat_item_get_last(statg->items[TEST_A_ITEM]);
- OSMO_ASSERT(value == 1);
-
- rc = osmo_stat_item_get_next(statg->items[TEST_A_ITEM], &rd_a, &value);
- OSMO_ASSERT(rc > 0);
+ /* No value set yet, expecting new value in all fields */
+ osmo_stat_item_set(osmo_stat_item_group_get_item(statg, TEST_A_ITEM), 1);
+ sum1 = 1;
+ value = osmo_stat_item_get_last(osmo_stat_item_group_get_item(statg, TEST_A_ITEM));
OSMO_ASSERT(value == 1);
+ OSMO_ASSERT(sitem1->value.n == 1);
+ OSMO_ASSERT(sitem1->value.min == 1);
+ OSMO_ASSERT(sitem1->value.last == 1);
+ OSMO_ASSERT(sitem1->value.max == 1);
+ OSMO_ASSERT(sitem1->value.sum == 1);
- rc = osmo_stat_item_get_next(statg->items[TEST_A_ITEM], &rd_a, &value);
- OSMO_ASSERT(rc == 0);
-
+ sum2 = 0;
for (i = 2; i <= 32; i++) {
- osmo_stat_item_set(statg->items[TEST_A_ITEM], i);
- osmo_stat_item_set(statg->items[TEST_B_ITEM], 1000 + i);
-
- rc = osmo_stat_item_get_next(statg->items[TEST_A_ITEM], &rd_a, &value);
- OSMO_ASSERT(rc > 0);
- OSMO_ASSERT(value == i);
-
- rc = osmo_stat_item_get_next(statg->items[TEST_B_ITEM], &rd_b, &value);
- OSMO_ASSERT(rc > 0);
- OSMO_ASSERT(value == 1000 + i);
+ osmo_stat_item_set(osmo_stat_item_group_get_item(statg, TEST_A_ITEM), i);
+ sum1 += i;
+ osmo_stat_item_set(osmo_stat_item_group_get_item(statg, TEST_B_ITEM), 1000 + i);
+ sum2 += 1000 + i;
}
+ OSMO_ASSERT(sitem1->value.n == 32);
+ OSMO_ASSERT(sitem1->value.min == 1);
+ OSMO_ASSERT(sitem1->value.last == 32);
+ OSMO_ASSERT(sitem1->value.max == 32);
+ OSMO_ASSERT(sitem1->value.sum == sum1);
+
+ OSMO_ASSERT(sitem2->value.n == 31);
+ OSMO_ASSERT(sitem2->value.min == 1002);
+ OSMO_ASSERT(sitem2->value.last == 1032);
+ OSMO_ASSERT(sitem2->value.max == 1032);
+ OSMO_ASSERT(sitem2->value.sum == sum2);
/* check if dec & inc is working */
- osmo_stat_item_set(statg->items[TEST_A_ITEM], 42);
- rc = osmo_stat_item_get_next(statg->items[TEST_A_ITEM], &rd_a, &value);
- OSMO_ASSERT(rc > 0);
- OSMO_ASSERT(value == 42);
-
- osmo_stat_item_dec(statg->items[TEST_A_ITEM], 21);
- rc = osmo_stat_item_get_next(statg->items[TEST_A_ITEM], &rd_a, &value);
- OSMO_ASSERT(rc > 0);
- OSMO_ASSERT(value == 21);
-
- osmo_stat_item_inc(statg->items[TEST_A_ITEM], 21);
- rc = osmo_stat_item_get_next(statg->items[TEST_A_ITEM], &rd_a, &value);
- OSMO_ASSERT(rc > 0);
- OSMO_ASSERT(value == 42);
-
- /* Keep 2 in FIFO */
- osmo_stat_item_set(statg->items[TEST_A_ITEM], 33);
- osmo_stat_item_set(statg->items[TEST_B_ITEM], 1000 + 33);
-
- for (i = 34; i <= 64; i++) {
- osmo_stat_item_set(statg->items[TEST_A_ITEM], i);
- osmo_stat_item_set(statg->items[TEST_B_ITEM], 1000 + i);
-
- rc = osmo_stat_item_get_next(statg->items[TEST_A_ITEM], &rd_a, &value);
- OSMO_ASSERT(rc > 0);
- OSMO_ASSERT(value == i-1);
-
- rc = osmo_stat_item_get_next(statg->items[TEST_B_ITEM], &rd_b, &value);
- OSMO_ASSERT(rc > 0);
- OSMO_ASSERT(value == 1000 + i-1);
- }
-
- rc = osmo_stat_item_get_next(statg->items[TEST_A_ITEM], &rd_a, &value);
- OSMO_ASSERT(rc > 0);
- OSMO_ASSERT(value == 64);
-
- rc = osmo_stat_item_get_next(statg->items[TEST_B_ITEM], &rd_b, &value);
- OSMO_ASSERT(rc > 0);
- OSMO_ASSERT(value == 1000 + 64);
-
- /* Overrun FIFOs */
- for (i = 65; i <= 96; i++) {
- osmo_stat_item_set(statg->items[TEST_A_ITEM], i);
- osmo_stat_item_set(statg->items[TEST_B_ITEM], 1000 + i);
- }
-
- rc = osmo_stat_item_get_next(statg->items[TEST_A_ITEM], &rd_a, &value);
- OSMO_ASSERT(rc > 0);
- OSMO_ASSERT(value == 93);
-
- for (i = 94; i <= 96; i++) {
- rc = osmo_stat_item_get_next(statg->items[TEST_A_ITEM], &rd_a, &value);
- OSMO_ASSERT(rc > 0);
- OSMO_ASSERT(value == i);
- }
-
- rc = osmo_stat_item_get_next(statg->items[TEST_B_ITEM], &rd_b, &value);
- OSMO_ASSERT(rc > 0);
- OSMO_ASSERT(value == 1000 + 90);
-
- for (i = 91; i <= 96; i++) {
- rc = osmo_stat_item_get_next(statg->items[TEST_B_ITEM], &rd_b, &value);
- OSMO_ASSERT(rc > 0);
- OSMO_ASSERT(value == 1000 + i);
- }
-
- /* Test Discard (single item) */
- osmo_stat_item_set(statg->items[TEST_A_ITEM], 97);
- rc = osmo_stat_item_discard(statg->items[TEST_A_ITEM], &rd_a);
- OSMO_ASSERT(rc > 0);
-
- rc = osmo_stat_item_discard(statg->items[TEST_A_ITEM], &rd_a);
- OSMO_ASSERT(rc == 0);
-
- rc = osmo_stat_item_get_next(statg->items[TEST_A_ITEM], &rd_a, &value);
- OSMO_ASSERT(rc == 0);
-
- osmo_stat_item_set(statg->items[TEST_A_ITEM], 98);
- rc = osmo_stat_item_get_next(statg->items[TEST_A_ITEM], &rd_a, &value);
- OSMO_ASSERT(rc > 0);
- OSMO_ASSERT(value == 98);
-
- rc = osmo_stat_item_get_next(statg->items[TEST_A_ITEM], &rd_a, &value);
- OSMO_ASSERT(rc == 0);
-
- /* Test Discard (all items) */
- osmo_stat_item_set(statg->items[TEST_A_ITEM], 99);
- osmo_stat_item_set(statg->items[TEST_A_ITEM], 100);
- osmo_stat_item_set(statg->items[TEST_A_ITEM], 101);
- osmo_stat_item_set(statg->items[TEST_B_ITEM], 99);
- osmo_stat_item_set(statg->items[TEST_B_ITEM], 100);
-
- rc = osmo_stat_item_discard_all(&rd_a);
- rc = osmo_stat_item_discard_all(&rd_b);
-
- rc = osmo_stat_item_get_next(statg->items[TEST_A_ITEM], &rd_a, &value);
- OSMO_ASSERT(rc == 0);
- rc = osmo_stat_item_get_next(statg->items[TEST_B_ITEM], &rd_b, &value);
- OSMO_ASSERT(rc == 0);
+ osmo_stat_item_set(osmo_stat_item_group_get_item(statg, TEST_A_ITEM), 42);
+ sum1 += 42;
+ OSMO_ASSERT(sitem1->value.n == 33);
+ OSMO_ASSERT(sitem1->value.min == 1);
+ OSMO_ASSERT(sitem1->value.last == 42);
+ OSMO_ASSERT(osmo_stat_item_get_last(osmo_stat_item_group_get_item(statg, TEST_A_ITEM)) == 42);
+ OSMO_ASSERT(sitem1->value.max == 42);
+ OSMO_ASSERT(sitem1->value.sum == sum1);
+
+ osmo_stat_item_dec(osmo_stat_item_group_get_item(statg, TEST_A_ITEM), 21);
+ sum1 += 42 - 21;
+ OSMO_ASSERT(sitem1->value.n == 34);
+ OSMO_ASSERT(sitem1->value.min == 1);
+ OSMO_ASSERT(sitem1->value.last == 21);
+ OSMO_ASSERT(osmo_stat_item_get_last(osmo_stat_item_group_get_item(statg, TEST_A_ITEM)) == 21);
+ OSMO_ASSERT(sitem1->value.max == 42);
+ OSMO_ASSERT(sitem1->value.sum == sum1);
+
+ osmo_stat_item_inc(osmo_stat_item_group_get_item(statg, TEST_A_ITEM), 21);
+ sum1 += 42;
+ OSMO_ASSERT(sitem1->value.n == 35);
+ OSMO_ASSERT(sitem1->value.min == 1);
+ OSMO_ASSERT(sitem1->value.last == 42);
+ OSMO_ASSERT(osmo_stat_item_get_last(osmo_stat_item_group_get_item(statg, TEST_A_ITEM)) == 42);
+ OSMO_ASSERT(sitem1->value.max == 42);
+ OSMO_ASSERT(sitem1->value.sum == sum1);
+
+ /* Test item flush, reporting period elapsing */
+ osmo_stat_item_flush(osmo_stat_item_group_get_item(statg, TEST_A_ITEM));
+ OSMO_ASSERT(sitem1->value.n == 0);
+ OSMO_ASSERT(sitem1->value.min == 42);
+ OSMO_ASSERT(sitem1->value.last == 42);
+ OSMO_ASSERT(osmo_stat_item_get_last(osmo_stat_item_group_get_item(statg, TEST_A_ITEM)) == 42);
+ OSMO_ASSERT(sitem1->value.max == 42);
+ OSMO_ASSERT(sitem1->value.sum == 0);
+
+ /* Still see the previous reporting period in reported.* */
+ OSMO_ASSERT(sitem1->reported.n == 35);
+ OSMO_ASSERT(sitem1->reported.min == 1);
+ OSMO_ASSERT(sitem1->reported.last == 42);
+ OSMO_ASSERT(sitem1->reported.max == 42);
+ OSMO_ASSERT(sitem1->reported.sum == sum1);
+
+ /* After a flush, the first item replaces the last, min and max */
+ osmo_stat_item_set(osmo_stat_item_group_get_item(statg, TEST_A_ITEM), 97);
+ OSMO_ASSERT(sitem1->value.n == 1);
+ OSMO_ASSERT(sitem1->value.min == 97);
+ OSMO_ASSERT(sitem1->value.last == 97);
+ OSMO_ASSERT(osmo_stat_item_get_last(osmo_stat_item_group_get_item(statg, TEST_A_ITEM)) == 97);
+ OSMO_ASSERT(sitem1->value.max == 97);
+ OSMO_ASSERT(sitem1->value.sum == 97);
+
+ /* ...and still see the previous reporting period in reported.* */
+ OSMO_ASSERT(sitem1->reported.n == 35);
+ OSMO_ASSERT(sitem1->reported.min == 1);
+ OSMO_ASSERT(sitem1->reported.last == 42);
+ OSMO_ASSERT(sitem1->reported.max == 42);
+ OSMO_ASSERT(sitem1->reported.sum == sum1);
+
+ /* If an entire reporting period elapses without a new value, the last seen value remains. */
+ osmo_stat_item_flush(osmo_stat_item_group_get_item(statg, TEST_A_ITEM));
+ osmo_stat_item_flush(osmo_stat_item_group_get_item(statg, TEST_A_ITEM));
+ OSMO_ASSERT(sitem1->value.n == 0);
+ OSMO_ASSERT(sitem1->value.min == 97);
+ OSMO_ASSERT(sitem1->value.last == 97);
+ OSMO_ASSERT(osmo_stat_item_get_last(osmo_stat_item_group_get_item(statg, TEST_A_ITEM)) == 97);
+ OSMO_ASSERT(sitem1->value.max == 97);
+ OSMO_ASSERT(sitem1->value.sum == 0);
+
+ /* now the previous reporting period got turned around */
+ OSMO_ASSERT(sitem1->reported.n == 0);
+ OSMO_ASSERT(sitem1->reported.min == 97);
+ OSMO_ASSERT(sitem1->reported.last == 97);
+ OSMO_ASSERT(sitem1->reported.max == 97);
+ OSMO_ASSERT(sitem1->reported.sum == 0);
+
+ /* Another empty reporting period, everything remained the same. */
+ osmo_stat_item_flush(osmo_stat_item_group_get_item(statg, TEST_A_ITEM));
+ OSMO_ASSERT(sitem1->value.n == 0);
+ OSMO_ASSERT(sitem1->value.min == 97);
+ OSMO_ASSERT(sitem1->value.last == 97);
+ OSMO_ASSERT(osmo_stat_item_get_last(osmo_stat_item_group_get_item(statg, TEST_A_ITEM)) == 97);
+ OSMO_ASSERT(sitem1->value.max == 97);
+ OSMO_ASSERT(sitem1->value.sum == 0);
+ OSMO_ASSERT(sitem1->reported.n == 0);
+ OSMO_ASSERT(sitem1->reported.min == 97);
+ OSMO_ASSERT(sitem1->reported.last == 97);
+ OSMO_ASSERT(sitem1->reported.max == 97);
+ OSMO_ASSERT(sitem1->reported.sum == 0);
+
+ /* Test Reset, place back to default value. The previously reported value remains the same. */
+ osmo_stat_item_reset(osmo_stat_item_group_get_item(statg, TEST_A_ITEM));
+ OSMO_ASSERT(sitem1->value.n == 0);
+ OSMO_ASSERT(sitem1->value.min == -1);
+ OSMO_ASSERT(sitem1->value.last == -1);
+ OSMO_ASSERT(osmo_stat_item_get_last(osmo_stat_item_group_get_item(statg, TEST_A_ITEM)) == -1);
+ OSMO_ASSERT(sitem1->value.max == -1);
+ OSMO_ASSERT(sitem1->value.sum == 0);
+ OSMO_ASSERT(sitem1->reported.n == 0);
+ OSMO_ASSERT(sitem1->reported.min == 97);
+ OSMO_ASSERT(sitem1->reported.last == 97);
+ OSMO_ASSERT(sitem1->reported.max == 97);
+ OSMO_ASSERT(sitem1->reported.sum == 0);
osmo_stat_item_group_free(statg);
@@ -258,7 +263,8 @@ static void stat_test(void)
/* define a special stats reporter for testing */
-static int send_count;
+static int sent_counter_vals;
+static int sent_stat_item_vals;
enum {
OSMO_STATS_REPORTER_TEST = OSMO_STATS_REPORTER_LOG + 1,
@@ -271,13 +277,13 @@ static int stats_reporter_test_send_counter(struct osmo_stats_reporter *srep,
{
const char *group_name = ctrg ? ctrg->desc->group_name_prefix : "";
- printf(" %s: counter p=%s g=%s i=%u n=%s v=%lld d=%lld\n",
+ fprintf(stderr, " %s: counter p=%s g=%s i=%u n=%s v=%lld d=%lld\n",
srep->name,
srep->name_prefix ? srep->name_prefix : "",
group_name, ctrg ? ctrg->idx : 0,
desc->name, (long long)value, (long long)delta);
- send_count += 1;
+ sent_counter_vals++;
return 0;
}
@@ -285,25 +291,25 @@ static int stats_reporter_test_send_item(struct osmo_stats_reporter *srep,
const struct osmo_stat_item_group *statg,
const struct osmo_stat_item_desc *desc, int64_t value)
{
- printf(" %s: item p=%s g=%s i=%u n=%s v=%"PRId64" u=%s\n",
+ fprintf(stderr, " %s: item p=%s g=%s i=%u n=%s v=%"PRId64" u=%s\n",
srep->name,
srep->name_prefix ? srep->name_prefix : "",
statg->desc->group_name_prefix, statg->idx,
desc->name, value, desc->unit ? desc->unit : "");
- send_count += 1;
+ sent_stat_item_vals++;
return 0;
}
static int stats_reporter_test_open(struct osmo_stats_reporter *srep)
{
- printf(" %s: open\n", srep->name);
+ fprintf(stderr, " %s: open\n", srep->name);
return 0;
}
static int stats_reporter_test_close(struct osmo_stats_reporter *srep)
{
- printf(" %s: close\n", srep->name);
+ fprintf(stderr, " %s: close\n", srep->name);
return 0;
}
@@ -322,8 +328,19 @@ static struct osmo_stats_reporter *stats_reporter_create_test(const char *name)
return srep;
}
+static void _do_report(int expect_counter_vals, int expect_stat_item_vals, int line)
+{
+ sent_counter_vals = 0;
+ sent_stat_item_vals = 0;
+ osmo_stats_report();
+ fprintf(stderr, "reported: %d counter vals, %d stat item vals\n", sent_counter_vals, sent_stat_item_vals);
+ OSMO_ASSERT(sent_counter_vals == expect_counter_vals);
+ OSMO_ASSERT(sent_stat_item_vals == expect_stat_item_vals);
+}
+
+#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;
@@ -332,7 +349,7 @@ static void test_reporting()
int rc;
- printf("Start test: %s\n", __func__);
+ fprintf(stderr, "Start test: %s\n", __func__);
/* Allocate counters and items */
statg1 = osmo_stat_item_group_alloc(stats_ctx, &statg_desc, 1);
@@ -374,112 +391,100 @@ static void test_reporting()
rc = osmo_stats_reporter_set_max_class(srep2, OSMO_STATS_CLASS_SUBSCRIBER);
OSMO_ASSERT(rc >= 0);
- printf("report (initial):\n");
- send_count = 0;
- osmo_stats_report();
- OSMO_ASSERT(send_count == 20);
+ fprintf(stderr, "report (initial):\n");
+ do_report(12, 8);
- printf("report (srep1 global):\n");
+ fprintf(stderr, "report (srep1 global):\n");
/* force single flush */
osmo_stats_reporter_set_max_class(srep1, OSMO_STATS_CLASS_GLOBAL);
srep1->force_single_flush = 1;
srep2->force_single_flush = 1;
- send_count = 0;
- osmo_stats_report();
- OSMO_ASSERT(send_count == 10);
+ do_report(6, 4);
- printf("report (srep1 peer):\n");
+ fprintf(stderr, "report (srep1 peer):\n");
/* force single flush */
osmo_stats_reporter_set_max_class(srep1, OSMO_STATS_CLASS_PEER);
srep1->force_single_flush = 1;
srep2->force_single_flush = 1;
- send_count = 0;
- osmo_stats_report();
- OSMO_ASSERT(send_count == 14);
+ do_report(6, 8);
- printf("report (srep1 subscriber):\n");
+ fprintf(stderr, "report (srep1 subscriber):\n");
/* force single flush */
osmo_stats_reporter_set_max_class(srep1, OSMO_STATS_CLASS_SUBSCRIBER);
srep1->force_single_flush = 1;
srep2->force_single_flush = 1;
- send_count = 0;
- osmo_stats_report();
- OSMO_ASSERT(send_count == 20);
+ do_report(12, 8);
- printf("report (srep2 disabled):\n");
+ fprintf(stderr, "report (srep2 disabled):\n");
/* force single flush */
srep1->force_single_flush = 1;
srep2->force_single_flush = 1;
rc = osmo_stats_reporter_disable(srep2);
OSMO_ASSERT(rc >= 0);
- send_count = 0;
- osmo_stats_report();
- OSMO_ASSERT(send_count == 10);
+ do_report(6, 4);
- printf("report (srep2 enabled, no flush forced):\n");
+ fprintf(stderr, "report (srep2 enabled, no flush forced):\n");
rc = osmo_stats_reporter_enable(srep2);
OSMO_ASSERT(rc >= 0);
- send_count = 0;
- osmo_stats_report();
- OSMO_ASSERT(send_count == 10);
+ do_report(6, 4);
- printf("report (should be empty):\n");
- send_count = 0;
- osmo_stats_report();
- OSMO_ASSERT(send_count == 0);
+ fprintf(stderr, "report (should be empty):\n");
+ do_report(0, 0);
- printf("report (group 1, counter 1 update):\n");
- rate_ctr_inc(&ctrg1->ctr[TEST_A_CTR]);
- send_count = 0;
- osmo_stats_report();
- OSMO_ASSERT(send_count == 2);
+ fprintf(stderr, "report (group 1, counter 1 update):\n");
+ rate_ctr_inc(rate_ctr_group_get_ctr(ctrg1, TEST_A_CTR));
+ do_report(2, 0);
- printf("report (group 1, item 1 update):\n");
- osmo_stat_item_set(statg1->items[TEST_A_ITEM], 10);
- send_count = 0;
- osmo_stats_report();
- OSMO_ASSERT(send_count == 2);
+ fprintf(stderr, "report (group 1, item 1 update):\n");
+ osmo_stat_item_set(osmo_stat_item_group_get_item(statg1, TEST_A_ITEM), 10);
+ do_report(0, 2);
+
+ fprintf(stderr, "report (group 1, item 1 update twice, with same value):\n");
+ osmo_stat_item_set(osmo_stat_item_group_get_item(statg1, TEST_A_ITEM), 10);
+ osmo_stat_item_set(osmo_stat_item_group_get_item(statg1, TEST_A_ITEM), 10);
+ do_report(0, 0);
+
+ fprintf(stderr, "report (group 1, item 1 update twice, check max):\n");
+ osmo_stat_item_set(osmo_stat_item_group_get_item(statg1, TEST_A_ITEM), 20);
+ osmo_stat_item_set(osmo_stat_item_group_get_item(statg1, TEST_A_ITEM), 10);
+ do_report(0, 2);
+
+ fprintf(stderr, "report (group 1, item 1 no update, send last item (!= last max), OS#5215):\n");
+ do_report(0, 2);
+
+ fprintf(stderr, "report (group 1, item 1 no update, nothing to send):\n");
+ do_report(0, 0);
- printf("report (remove statg1, ctrg1):\n");
+ fprintf(stderr, "report (remove statg1, ctrg1):\n");
/* force single flush */
srep1->force_single_flush = 1;
srep2->force_single_flush = 1;
osmo_stat_item_group_free(statg1);
rate_ctr_group_free(ctrg1);
- send_count = 0;
- osmo_stats_report();
- OSMO_ASSERT(send_count == 12);
+ do_report(8, 4);
- printf("report (remove srep1):\n");
+ fprintf(stderr, "report (remove srep1):\n");
/* force single flush */
srep1->force_single_flush = 1;
srep2->force_single_flush = 1;
osmo_stats_reporter_free(srep1);
- send_count = 0;
- osmo_stats_report();
- OSMO_ASSERT(send_count == 6);
+ do_report(4, 2);
- printf("report (remove statg2):\n");
+ fprintf(stderr, "report (remove statg2):\n");
/* force single flush */
srep2->force_single_flush = 1;
osmo_stat_item_group_free(statg2);
- send_count = 0;
- osmo_stats_report();
- OSMO_ASSERT(send_count == 4);
+ do_report(4, 0);
- printf("report (remove srep2):\n");
+ fprintf(stderr, "report (remove srep2):\n");
/* force single flush */
srep2->force_single_flush = 1;
osmo_stats_reporter_free(srep2);
- send_count = 0;
- osmo_stats_report();
- OSMO_ASSERT(send_count == 0);
+ do_report(0, 0);
- printf("report (remove ctrg2, should be empty):\n");
+ fprintf(stderr, "report (remove ctrg2, should be empty):\n");
rate_ctr_group_free(ctrg2);
- send_count = 0;
- osmo_stats_report();
- OSMO_ASSERT(send_count == 0);
+ do_report(0, 0);
rate_ctr_group_free(ctrg3);
@@ -487,17 +492,24 @@ static void test_reporting()
OSMO_ASSERT(talloc_total_blocks(stats_ctx) == 1);
talloc_free(stats_ctx);
- printf("End test: %s\n", __func__);
+ fprintf(stderr, "End test: %s\n", __func__);
}
int main(int argc, char **argv)
{
- static const struct log_info log_info = {};
- log_init(&log_info, NULL);
+ void *ctx = talloc_named_const(NULL, 0, "main");
+ osmo_init_logging2(ctx, NULL);
+
+ log_set_print_filename2(osmo_stderr_target, LOG_FILENAME_NONE);
+ log_set_print_level(osmo_stderr_target, 1);
+ log_set_print_category(osmo_stderr_target, 1);
+ log_set_print_category_hex(osmo_stderr_target, 0);
+ log_set_use_color(osmo_stderr_target, 0);
osmo_stat_item_init(NULL);
stat_test();
test_reporting();
+ talloc_free(ctx);
return 0;
}
diff --git a/tests/stats/stats_test.err b/tests/stats/stats_test.err
new file mode 100644
index 00000000..4acd35d0
--- /dev/null
+++ b/tests/stats/stats_test.err
@@ -0,0 +1,163 @@
+Start test: test_reporting
+DLGLOBAL ERROR counter group 'ctr-test:one' already exists for index 2, instead using index 3. This is a software bug that needs fixing.
+DLGLOBAL ERROR 'ctr-test.one_dot' is not a valid counter group identifier
+DLGLOBAL NOTICE counter group name mangled: 'ctr-test.one_dot' -> 'ctr-test:one_dot'
+DLGLOBAL NOTICE counter group name mangled: 'ctr.a' -> 'ctr:a'
+DLGLOBAL NOTICE counter group name mangled: 'ctr.b' -> 'ctr:b'
+ test1: open
+ test2: open
+report (initial):
+ test1: counter p= g=ctr-test:one_dot i=3 n=ctr:a v=0 d=0
+ test2: counter p= g=ctr-test:one_dot i=3 n=ctr:a v=0 d=0
+ test1: counter p= g=ctr-test:one_dot i=3 n=ctr:b v=0 d=0
+ test2: counter p= g=ctr-test:one_dot i=3 n=ctr:b v=0 d=0
+ test1: counter p= g=ctr-test:one i=2 n=ctr:a v=0 d=0
+ test2: counter p= g=ctr-test:one i=2 n=ctr:a v=0 d=0
+ test1: counter p= g=ctr-test:one i=2 n=ctr:b v=0 d=0
+ test2: counter p= g=ctr-test:one i=2 n=ctr:b v=0 d=0
+ test1: counter p= g=ctr-test:one i=1 n=ctr:a v=0 d=0
+ test2: counter p= g=ctr-test:one i=1 n=ctr:a v=0 d=0
+ test1: counter p= g=ctr-test:one i=1 n=ctr:b v=0 d=0
+ test2: counter p= g=ctr-test:one i=1 n=ctr:b v=0 d=0
+ test1: item p= g=test.one i=2 n=item.a v=-1 u=ma
+ test2: item p= g=test.one i=2 n=item.a v=-1 u=ma
+ test1: item p= g=test.one i=2 n=item.b v=-1 u=kb
+ test2: item p= g=test.one i=2 n=item.b v=-1 u=kb
+ test1: item p= g=test.one i=1 n=item.a v=-1 u=ma
+ test2: item p= g=test.one i=1 n=item.a v=-1 u=ma
+ test1: item p= g=test.one i=1 n=item.b v=-1 u=kb
+ test2: item p= g=test.one i=1 n=item.b v=-1 u=kb
+reported: 12 counter vals, 8 stat item vals
+report (srep1 global):
+ test2: counter p= g=ctr-test:one_dot i=3 n=ctr:a v=0 d=0
+ test2: counter p= g=ctr-test:one_dot i=3 n=ctr:b v=0 d=0
+ test2: counter p= g=ctr-test:one i=2 n=ctr:a v=0 d=0
+ test2: counter p= g=ctr-test:one i=2 n=ctr:b v=0 d=0
+ test2: counter p= g=ctr-test:one i=1 n=ctr:a v=0 d=0
+ test2: counter p= g=ctr-test:one i=1 n=ctr:b v=0 d=0
+ test2: item p= g=test.one i=2 n=item.a v=-1 u=ma
+ test2: item p= g=test.one i=2 n=item.b v=-1 u=kb
+ test2: item p= g=test.one i=1 n=item.a v=-1 u=ma
+ test2: item p= g=test.one i=1 n=item.b v=-1 u=kb
+reported: 6 counter vals, 4 stat item vals
+report (srep1 peer):
+ test2: counter p= g=ctr-test:one_dot i=3 n=ctr:a v=0 d=0
+ test2: counter p= g=ctr-test:one_dot i=3 n=ctr:b v=0 d=0
+ test2: counter p= g=ctr-test:one i=2 n=ctr:a v=0 d=0
+ test2: counter p= g=ctr-test:one i=2 n=ctr:b v=0 d=0
+ test2: counter p= g=ctr-test:one i=1 n=ctr:a v=0 d=0
+ test2: counter p= g=ctr-test:one i=1 n=ctr:b v=0 d=0
+ test1: item p= g=test.one i=2 n=item.a v=-1 u=ma
+ test2: item p= g=test.one i=2 n=item.a v=-1 u=ma
+ test1: item p= g=test.one i=2 n=item.b v=-1 u=kb
+ test2: item p= g=test.one i=2 n=item.b v=-1 u=kb
+ test1: item p= g=test.one i=1 n=item.a v=-1 u=ma
+ test2: item p= g=test.one i=1 n=item.a v=-1 u=ma
+ test1: item p= g=test.one i=1 n=item.b v=-1 u=kb
+ test2: item p= g=test.one i=1 n=item.b v=-1 u=kb
+reported: 6 counter vals, 8 stat item vals
+report (srep1 subscriber):
+ test1: counter p= g=ctr-test:one_dot i=3 n=ctr:a v=0 d=0
+ test2: counter p= g=ctr-test:one_dot i=3 n=ctr:a v=0 d=0
+ test1: counter p= g=ctr-test:one_dot i=3 n=ctr:b v=0 d=0
+ test2: counter p= g=ctr-test:one_dot i=3 n=ctr:b v=0 d=0
+ test1: counter p= g=ctr-test:one i=2 n=ctr:a v=0 d=0
+ test2: counter p= g=ctr-test:one i=2 n=ctr:a v=0 d=0
+ test1: counter p= g=ctr-test:one i=2 n=ctr:b v=0 d=0
+ test2: counter p= g=ctr-test:one i=2 n=ctr:b v=0 d=0
+ test1: counter p= g=ctr-test:one i=1 n=ctr:a v=0 d=0
+ test2: counter p= g=ctr-test:one i=1 n=ctr:a v=0 d=0
+ test1: counter p= g=ctr-test:one i=1 n=ctr:b v=0 d=0
+ test2: counter p= g=ctr-test:one i=1 n=ctr:b v=0 d=0
+ test1: item p= g=test.one i=2 n=item.a v=-1 u=ma
+ test2: item p= g=test.one i=2 n=item.a v=-1 u=ma
+ test1: item p= g=test.one i=2 n=item.b v=-1 u=kb
+ test2: item p= g=test.one i=2 n=item.b v=-1 u=kb
+ test1: item p= g=test.one i=1 n=item.a v=-1 u=ma
+ test2: item p= g=test.one i=1 n=item.a v=-1 u=ma
+ test1: item p= g=test.one i=1 n=item.b v=-1 u=kb
+ test2: item p= g=test.one i=1 n=item.b v=-1 u=kb
+reported: 12 counter vals, 8 stat item vals
+report (srep2 disabled):
+ test2: close
+ test1: counter p= g=ctr-test:one_dot i=3 n=ctr:a v=0 d=0
+ test1: counter p= g=ctr-test:one_dot i=3 n=ctr:b v=0 d=0
+ test1: counter p= g=ctr-test:one i=2 n=ctr:a v=0 d=0
+ test1: counter p= g=ctr-test:one i=2 n=ctr:b v=0 d=0
+ test1: counter p= g=ctr-test:one i=1 n=ctr:a v=0 d=0
+ test1: counter p= g=ctr-test:one i=1 n=ctr:b v=0 d=0
+ test1: item p= g=test.one i=2 n=item.a v=-1 u=ma
+ test1: item p= g=test.one i=2 n=item.b v=-1 u=kb
+ test1: item p= g=test.one i=1 n=item.a v=-1 u=ma
+ test1: item p= g=test.one i=1 n=item.b v=-1 u=kb
+reported: 6 counter vals, 4 stat item vals
+report (srep2 enabled, no flush forced):
+ test2: open
+ test2: counter p= g=ctr-test:one_dot i=3 n=ctr:a v=0 d=0
+ test2: counter p= g=ctr-test:one_dot i=3 n=ctr:b v=0 d=0
+ test2: counter p= g=ctr-test:one i=2 n=ctr:a v=0 d=0
+ test2: counter p= g=ctr-test:one i=2 n=ctr:b v=0 d=0
+ test2: counter p= g=ctr-test:one i=1 n=ctr:a v=0 d=0
+ test2: counter p= g=ctr-test:one i=1 n=ctr:b v=0 d=0
+ test2: item p= g=test.one i=2 n=item.a v=-1 u=ma
+ test2: item p= g=test.one i=2 n=item.b v=-1 u=kb
+ test2: item p= g=test.one i=1 n=item.a v=-1 u=ma
+ test2: item p= g=test.one i=1 n=item.b v=-1 u=kb
+reported: 6 counter vals, 4 stat item vals
+report (should be empty):
+reported: 0 counter vals, 0 stat item vals
+report (group 1, counter 1 update):
+ test1: counter p= g=ctr-test:one i=1 n=ctr:a v=1 d=1
+ test2: counter p= g=ctr-test:one i=1 n=ctr:a v=1 d=1
+reported: 2 counter vals, 0 stat item vals
+report (group 1, item 1 update):
+ test1: item p= g=test.one i=1 n=item.a v=10 u=ma
+ test2: item p= g=test.one i=1 n=item.a v=10 u=ma
+reported: 0 counter vals, 2 stat item vals
+report (group 1, item 1 update twice, with same value):
+reported: 0 counter vals, 0 stat item vals
+report (group 1, item 1 update twice, check max):
+ test1: item p= g=test.one i=1 n=item.a v=20 u=ma
+ test2: item p= g=test.one i=1 n=item.a v=20 u=ma
+reported: 0 counter vals, 2 stat item vals
+report (group 1, item 1 no update, send last item (!= last max), OS#5215):
+ test1: item p= g=test.one i=1 n=item.a v=10 u=ma
+ test2: item p= g=test.one i=1 n=item.a v=10 u=ma
+reported: 0 counter vals, 2 stat item vals
+report (group 1, item 1 no update, nothing to send):
+reported: 0 counter vals, 0 stat item vals
+report (remove statg1, ctrg1):
+ test1: counter p= g=ctr-test:one_dot i=3 n=ctr:a v=0 d=0
+ test2: counter p= g=ctr-test:one_dot i=3 n=ctr:a v=0 d=0
+ test1: counter p= g=ctr-test:one_dot i=3 n=ctr:b v=0 d=0
+ test2: counter p= g=ctr-test:one_dot i=3 n=ctr:b v=0 d=0
+ test1: counter p= g=ctr-test:one i=2 n=ctr:a v=0 d=0
+ test2: counter p= g=ctr-test:one i=2 n=ctr:a v=0 d=0
+ test1: counter p= g=ctr-test:one i=2 n=ctr:b v=0 d=0
+ test2: counter p= g=ctr-test:one i=2 n=ctr:b v=0 d=0
+ test1: item p= g=test.one i=2 n=item.a v=-1 u=ma
+ test2: item p= g=test.one i=2 n=item.a v=-1 u=ma
+ test1: item p= g=test.one i=2 n=item.b v=-1 u=kb
+ test2: item p= g=test.one i=2 n=item.b v=-1 u=kb
+reported: 8 counter vals, 4 stat item vals
+report (remove srep1):
+ test1: close
+ test2: counter p= g=ctr-test:one_dot i=3 n=ctr:a v=0 d=0
+ test2: counter p= g=ctr-test:one_dot i=3 n=ctr:b v=0 d=0
+ test2: counter p= g=ctr-test:one i=2 n=ctr:a v=0 d=0
+ test2: counter p= g=ctr-test:one i=2 n=ctr:b v=0 d=0
+ test2: item p= g=test.one i=2 n=item.a v=-1 u=ma
+ test2: item p= g=test.one i=2 n=item.b v=-1 u=kb
+reported: 4 counter vals, 2 stat item vals
+report (remove statg2):
+ test2: counter p= g=ctr-test:one_dot i=3 n=ctr:a v=0 d=0
+ test2: counter p= g=ctr-test:one_dot i=3 n=ctr:b v=0 d=0
+ test2: counter p= g=ctr-test:one i=2 n=ctr:a v=0 d=0
+ test2: counter p= g=ctr-test:one i=2 n=ctr:b v=0 d=0
+reported: 4 counter vals, 0 stat item vals
+report (remove srep2):
+ test2: close
+reported: 0 counter vals, 0 stat item vals
+report (remove ctrg2, should be empty):
+reported: 0 counter vals, 0 stat item vals
+End test: test_reporting
diff --git a/tests/stats/stats_test.ok b/tests/stats/stats_test.ok
index 8628adb7..e69de29b 100644
--- a/tests/stats/stats_test.ok
+++ b/tests/stats/stats_test.ok
@@ -1,132 +0,0 @@
-Start test: test_reporting
- test1: open
- test2: open
-report (initial):
- test2: counter p= g=ctr-test:one_dot i=3 n=ctr:a v=0 d=0
- test1: counter p= g=ctr-test:one_dot i=3 n=ctr:a v=0 d=0
- test2: counter p= g=ctr-test:one_dot i=3 n=ctr:b v=0 d=0
- test1: counter p= g=ctr-test:one_dot i=3 n=ctr:b v=0 d=0
- test2: counter p= g=ctr-test:one i=2 n=ctr:a v=0 d=0
- test1: counter p= g=ctr-test:one i=2 n=ctr:a v=0 d=0
- test2: counter p= g=ctr-test:one i=2 n=ctr:b v=0 d=0
- test1: counter p= g=ctr-test:one i=2 n=ctr:b v=0 d=0
- test2: counter p= g=ctr-test:one i=1 n=ctr:a v=0 d=0
- test1: counter p= g=ctr-test:one i=1 n=ctr:a v=0 d=0
- test2: counter p= g=ctr-test:one i=1 n=ctr:b v=0 d=0
- test1: counter p= g=ctr-test:one i=1 n=ctr:b v=0 d=0
- test2: item p= g=test.one i=2 n=item.a v=-1 u=ma
- test1: item p= g=test.one i=2 n=item.a v=-1 u=ma
- test2: item p= g=test.one i=2 n=item.b v=-1 u=kb
- test1: item p= g=test.one i=2 n=item.b v=-1 u=kb
- test2: item p= g=test.one i=1 n=item.a v=-1 u=ma
- test1: item p= g=test.one i=1 n=item.a v=-1 u=ma
- test2: item p= g=test.one i=1 n=item.b v=-1 u=kb
- test1: item p= g=test.one i=1 n=item.b v=-1 u=kb
-report (srep1 global):
- test2: counter p= g=ctr-test:one_dot i=3 n=ctr:a v=0 d=0
- test2: counter p= g=ctr-test:one_dot i=3 n=ctr:b v=0 d=0
- test2: counter p= g=ctr-test:one i=2 n=ctr:a v=0 d=0
- test2: counter p= g=ctr-test:one i=2 n=ctr:b v=0 d=0
- test2: counter p= g=ctr-test:one i=1 n=ctr:a v=0 d=0
- test2: counter p= g=ctr-test:one i=1 n=ctr:b v=0 d=0
- test2: item p= g=test.one i=2 n=item.a v=-1 u=ma
- test2: item p= g=test.one i=2 n=item.b v=-1 u=kb
- test2: item p= g=test.one i=1 n=item.a v=-1 u=ma
- test2: item p= g=test.one i=1 n=item.b v=-1 u=kb
-report (srep1 peer):
- test2: counter p= g=ctr-test:one_dot i=3 n=ctr:a v=0 d=0
- test2: counter p= g=ctr-test:one_dot i=3 n=ctr:b v=0 d=0
- test2: counter p= g=ctr-test:one i=2 n=ctr:a v=0 d=0
- test2: counter p= g=ctr-test:one i=2 n=ctr:b v=0 d=0
- test2: counter p= g=ctr-test:one i=1 n=ctr:a v=0 d=0
- test2: counter p= g=ctr-test:one i=1 n=ctr:b v=0 d=0
- test2: item p= g=test.one i=2 n=item.a v=-1 u=ma
- test1: item p= g=test.one i=2 n=item.a v=-1 u=ma
- test2: item p= g=test.one i=2 n=item.b v=-1 u=kb
- test1: item p= g=test.one i=2 n=item.b v=-1 u=kb
- test2: item p= g=test.one i=1 n=item.a v=-1 u=ma
- test1: item p= g=test.one i=1 n=item.a v=-1 u=ma
- test2: item p= g=test.one i=1 n=item.b v=-1 u=kb
- test1: item p= g=test.one i=1 n=item.b v=-1 u=kb
-report (srep1 subscriber):
- test2: counter p= g=ctr-test:one_dot i=3 n=ctr:a v=0 d=0
- test1: counter p= g=ctr-test:one_dot i=3 n=ctr:a v=0 d=0
- test2: counter p= g=ctr-test:one_dot i=3 n=ctr:b v=0 d=0
- test1: counter p= g=ctr-test:one_dot i=3 n=ctr:b v=0 d=0
- test2: counter p= g=ctr-test:one i=2 n=ctr:a v=0 d=0
- test1: counter p= g=ctr-test:one i=2 n=ctr:a v=0 d=0
- test2: counter p= g=ctr-test:one i=2 n=ctr:b v=0 d=0
- test1: counter p= g=ctr-test:one i=2 n=ctr:b v=0 d=0
- test2: counter p= g=ctr-test:one i=1 n=ctr:a v=0 d=0
- test1: counter p= g=ctr-test:one i=1 n=ctr:a v=0 d=0
- test2: counter p= g=ctr-test:one i=1 n=ctr:b v=0 d=0
- test1: counter p= g=ctr-test:one i=1 n=ctr:b v=0 d=0
- test2: item p= g=test.one i=2 n=item.a v=-1 u=ma
- test1: item p= g=test.one i=2 n=item.a v=-1 u=ma
- test2: item p= g=test.one i=2 n=item.b v=-1 u=kb
- test1: item p= g=test.one i=2 n=item.b v=-1 u=kb
- test2: item p= g=test.one i=1 n=item.a v=-1 u=ma
- test1: item p= g=test.one i=1 n=item.a v=-1 u=ma
- test2: item p= g=test.one i=1 n=item.b v=-1 u=kb
- test1: item p= g=test.one i=1 n=item.b v=-1 u=kb
-report (srep2 disabled):
- test2: close
- test1: counter p= g=ctr-test:one_dot i=3 n=ctr:a v=0 d=0
- test1: counter p= g=ctr-test:one_dot i=3 n=ctr:b v=0 d=0
- test1: counter p= g=ctr-test:one i=2 n=ctr:a v=0 d=0
- test1: counter p= g=ctr-test:one i=2 n=ctr:b v=0 d=0
- test1: counter p= g=ctr-test:one i=1 n=ctr:a v=0 d=0
- test1: counter p= g=ctr-test:one i=1 n=ctr:b v=0 d=0
- test1: item p= g=test.one i=2 n=item.a v=-1 u=ma
- test1: item p= g=test.one i=2 n=item.b v=-1 u=kb
- test1: item p= g=test.one i=1 n=item.a v=-1 u=ma
- test1: item p= g=test.one i=1 n=item.b v=-1 u=kb
-report (srep2 enabled, no flush forced):
- test2: open
- test2: counter p= g=ctr-test:one_dot i=3 n=ctr:a v=0 d=0
- test2: counter p= g=ctr-test:one_dot i=3 n=ctr:b v=0 d=0
- test2: counter p= g=ctr-test:one i=2 n=ctr:a v=0 d=0
- test2: counter p= g=ctr-test:one i=2 n=ctr:b v=0 d=0
- test2: counter p= g=ctr-test:one i=1 n=ctr:a v=0 d=0
- test2: counter p= g=ctr-test:one i=1 n=ctr:b v=0 d=0
- test2: item p= g=test.one i=2 n=item.a v=-1 u=ma
- test2: item p= g=test.one i=2 n=item.b v=-1 u=kb
- test2: item p= g=test.one i=1 n=item.a v=-1 u=ma
- test2: item p= g=test.one i=1 n=item.b v=-1 u=kb
-report (should be empty):
-report (group 1, counter 1 update):
- test2: counter p= g=ctr-test:one i=1 n=ctr:a v=1 d=1
- test1: counter p= g=ctr-test:one i=1 n=ctr:a v=1 d=1
-report (group 1, item 1 update):
- test2: item p= g=test.one i=1 n=item.a v=10 u=ma
- test1: item p= g=test.one i=1 n=item.a v=10 u=ma
-report (remove statg1, ctrg1):
- test2: counter p= g=ctr-test:one_dot i=3 n=ctr:a v=0 d=0
- test1: counter p= g=ctr-test:one_dot i=3 n=ctr:a v=0 d=0
- test2: counter p= g=ctr-test:one_dot i=3 n=ctr:b v=0 d=0
- test1: counter p= g=ctr-test:one_dot i=3 n=ctr:b v=0 d=0
- test2: counter p= g=ctr-test:one i=2 n=ctr:a v=0 d=0
- test1: counter p= g=ctr-test:one i=2 n=ctr:a v=0 d=0
- test2: counter p= g=ctr-test:one i=2 n=ctr:b v=0 d=0
- test1: counter p= g=ctr-test:one i=2 n=ctr:b v=0 d=0
- test2: item p= g=test.one i=2 n=item.a v=-1 u=ma
- test1: item p= g=test.one i=2 n=item.a v=-1 u=ma
- test2: item p= g=test.one i=2 n=item.b v=-1 u=kb
- test1: item p= g=test.one i=2 n=item.b v=-1 u=kb
-report (remove srep1):
- test1: close
- test2: counter p= g=ctr-test:one_dot i=3 n=ctr:a v=0 d=0
- test2: counter p= g=ctr-test:one_dot i=3 n=ctr:b v=0 d=0
- test2: counter p= g=ctr-test:one i=2 n=ctr:a v=0 d=0
- test2: counter p= g=ctr-test:one i=2 n=ctr:b v=0 d=0
- test2: item p= g=test.one i=2 n=item.a v=-1 u=ma
- test2: item p= g=test.one i=2 n=item.b v=-1 u=kb
-report (remove statg2):
- test2: counter p= g=ctr-test:one_dot i=3 n=ctr:a v=0 d=0
- test2: counter p= g=ctr-test:one_dot i=3 n=ctr:b v=0 d=0
- test2: counter p= g=ctr-test:one i=2 n=ctr:a v=0 d=0
- test2: counter p= g=ctr-test:one i=2 n=ctr:b v=0 d=0
-report (remove srep2):
- test2: close
-report (remove ctrg2, should be empty):
-End test: test_reporting
diff --git a/tests/stats/stats_vty_test.c b/tests/stats/stats_vty_test.c
new file mode 100644
index 00000000..29cbf5ec
--- /dev/null
+++ b/tests/stats/stats_vty_test.c
@@ -0,0 +1,88 @@
+/*
+ * (C) 2021 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+#include <signal.h>
+
+#include <osmocom/core/application.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/utils.h>
+
+#include <osmocom/vty/telnet_interface.h>
+#include <osmocom/vty/stats.h>
+#include <osmocom/vty/vty.h>
+
+static void *root_ctx = NULL;
+static int quit = 0;
+
+static void signal_handler(int signal)
+{
+ fprintf(stdout, "signal %u received\n", signal);
+
+ switch (signal) {
+ case SIGINT:
+ case SIGTERM:
+ quit++;
+ break;
+ }
+}
+
+static struct vty_app_info vty_info = {
+ .name = "stats_vty_test",
+};
+
+static const struct log_info_cat default_categories[] = { };
+
+const struct log_info log_info = {
+ .cat = default_categories,
+ .num_cat = ARRAY_SIZE(default_categories),
+};
+
+int main(int argc, char **argv)
+{
+ int rc;
+
+ root_ctx = talloc_named_const(NULL, 0, "stats_vty_test");
+
+ osmo_init_logging2(root_ctx, &log_info);
+
+ vty_info.tall_ctx = root_ctx;
+ vty_init(&vty_info);
+
+ osmo_stats_vty_add_cmds();
+
+ rc = telnet_init_default(root_ctx, NULL, 42042);
+ if (rc < 0)
+ return 2;
+
+ signal(SIGINT, &signal_handler);
+ signal(SIGTERM, &signal_handler);
+ osmo_init_ignore_signals();
+
+ while (!quit)
+ osmo_select_main(0);
+
+ talloc_free(tall_vty_ctx);
+ talloc_free(root_ctx);
+
+ return 0;
+}
diff --git a/tests/stats/stats_vty_test.vty b/tests/stats/stats_vty_test.vty
new file mode 100644
index 00000000..8732d50b
--- /dev/null
+++ b/tests/stats/stats_vty_test.vty
@@ -0,0 +1,217 @@
+stats_vty_test> en
+stats_vty_test# configure terminal
+stats_vty_test(config)# list
+...
+ stats reporter statsd [NAME]
+ no stats reporter statsd [NAME]
+ stats reporter log [NAME]
+ no stats reporter log [NAME]
+ stats interval <0-65535>
+ stats-tcp interval <0-65535>
+...
+
+stats_vty_test(config)# ### No reporters shall be configured by default
+stats_vty_test(config)# show running-config
+... !stats reporter
+
+
+stats_vty_test(config)# ### Create a statsd reporter
+stats_vty_test(config)# stats reporter statsd
+stats_vty_test(config-stats)# list
+...
+ local-ip ADDR
+ no local-ip
+ remote-ip ADDR
+ remote-port <1-65535>
+ mtu <100-65535>
+ no mtu
+ prefix PREFIX
+ no prefix
+ level (global|peer|subscriber)
+ enable
+ disable
+ flush-period <0-65535>
+...
+
+stats_vty_test(config-stats)# show running-config
+...
+stats interval 5
+stats reporter statsd
+ level global
+ no prefix
+ disable
+...
+
+stats_vty_test(config-stats)# level subscriber
+stats_vty_test(config-stats)# prefix statsd-prefix
+stats_vty_test(config-stats)# show running-config
+...
+stats reporter statsd
+ level subscriber
+ prefix statsd-prefix
+...
+
+stats_vty_test(config-stats)# remote-ip 192.168.1.200
+stats_vty_test(config-stats)# remote-port 6969
+stats_vty_test(config-stats)# show running-config
+...
+stats reporter statsd
+ remote-ip 192.168.1.200
+ remote-port 6969
+... !local-ip
+
+stats_vty_test(config-stats)# local-ip 192.168.1.100
+stats_vty_test(config-stats)# show running-config
+...
+stats reporter statsd
+ remote-ip 192.168.1.200
+ remote-port 6969
+ local-ip 192.168.1.100
+...
+
+stats_vty_test(config-stats)# no local-ip
+stats_vty_test(config-stats)# show running-config
+...
+stats reporter statsd
+... !local-ip
+
+stats_vty_test(config-stats)# mtu 1337
+stats_vty_test(config-stats)# show running-config
+...
+stats reporter statsd
+ remote-ip 192.168.1.200
+ remote-port 6969
+ mtu 1337
+...
+
+stats_vty_test(config-stats)# no mtu
+stats_vty_test(config-stats)# show running-config
+...
+stats reporter statsd
+... !mtu
+
+stats_vty_test(config-stats)# flush-period 43556
+stats_vty_test(config-stats)# show running-config
+...
+stats reporter statsd
+ remote-ip 192.168.1.200
+ remote-port 6969
+ level subscriber
+ prefix statsd-prefix
+ flush-period 43556
+...
+
+stats_vty_test(config-stats)# flush-period 0
+stats_vty_test(config-stats)# show running-config
+...
+stats reporter statsd
+... !flush-period
+
+stats_vty_test(config-stats)# enable
+stats_vty_test(config-stats)# exit
+stats_vty_test(config)# show running-config
+...
+stats reporter statsd
+ remote-ip 192.168.1.200
+ remote-port 6969
+ level subscriber
+ prefix statsd-prefix
+ enable
+...
+
+
+stats_vty_test(config)# ### Create a statsd reporter
+stats_vty_test(config)# stats reporter log
+stats_vty_test(config-stats)# level peer
+stats_vty_test(config-stats)# prefix log-prefix
+stats_vty_test(config-stats)# enable
+stats_vty_test(config-stats)# exit
+stats_vty_test(config)# show running-config
+...
+stats reporter statsd
+ remote-ip 192.168.1.200
+ remote-port 6969
+ level subscriber
+ prefix statsd-prefix
+ enable
+stats reporter log
+ level peer
+ prefix log-prefix
+ enable
+...
+
+
+stats_vty_test(config)# ### Create an additional statsd reporter
+stats_vty_test(config)# stats reporter statsd statsd-foo
+stats_vty_test(config-stats)# level global
+stats_vty_test(config-stats)# prefix statsd-one-prefix
+stats_vty_test(config-stats)# remote-ip 192.168.2.200
+stats_vty_test(config-stats)# remote-port 9696
+stats_vty_test(config-stats)# flush-period 1
+stats_vty_test(config-stats)# exit
+
+stats_vty_test(config)# ### Create an additional log reporter
+stats_vty_test(config)# stats reporter log log-bar
+stats_vty_test(config-stats)# level global
+stats_vty_test(config-stats)# prefix log-bar-prefix
+stats_vty_test(config-stats)# flush-period 2
+stats_vty_test(config-stats)# exit
+
+stats_vty_test(config)# ### Create an additional log reporter
+stats_vty_test(config)# stats reporter log log-zoo
+stats_vty_test(config-stats)# level global
+stats_vty_test(config-stats)# prefix log-zoo-prefix
+stats_vty_test(config-stats)# flush-period 3
+stats_vty_test(config-stats)# exit
+
+stats_vty_test(config)# ### We should have 5 reporters now
+stats_vty_test(config)# show running-config
+...
+stats reporter statsd
+ remote-ip 192.168.1.200
+ remote-port 6969
+ level subscriber
+ prefix statsd-prefix
+ enable
+stats reporter log
+ level peer
+ prefix log-prefix
+ enable
+stats reporter statsd statsd-foo
+ remote-ip 192.168.2.200
+ remote-port 9696
+ level global
+ prefix statsd-one-prefix
+ flush-period 1
+ disable
+stats reporter log log-bar
+ level global
+ prefix log-bar-prefix
+ flush-period 2
+ disable
+stats reporter log log-zoo
+ level global
+ prefix log-zoo-prefix
+ flush-period 3
+ disable
+...
+
+
+stats_vty_test(config)# ### Test removing reporters
+stats_vty_test(config)# no stats reporter statsd statsd-foo
+stats_vty_test(config)# no stats reporter log log-bar
+stats_vty_test(config)# no stats reporter log log-zoo
+stats_vty_test(config)# show running-config
+... !(foo|bar|zoo)
+
+stats_vty_test(config)# no stats reporter statsd statsd-foo
+% There is no such statsd reporter with name 'statsd-foo'
+stats_vty_test(config)# no stats reporter log log-zoo
+% There is no such log reporter with name 'log-zoo'
+
+
+stats_vty_test(config)# stats interval 1337
+stats_vty_test(config)# show running-config
+...
+stats interval 1337
+...
diff --git a/tests/strrb/strrb_test.c b/tests/strrb/strrb_test.c
index 4282d1aa..cdeaa346 100644
--- a/tests/strrb/strrb_test.c
+++ b/tests/strrb/strrb_test.c
@@ -13,10 +13,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <stdio.h>
diff --git a/tests/tdef/tdef_test.c b/tests/tdef/tdef_test.c
index 9c0808ea..a0fcc9a9 100644
--- a/tests/tdef/tdef_test.c
+++ b/tests/tdef/tdef_test.c
@@ -17,10 +17,6 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <stdio.h>
@@ -38,7 +34,7 @@ static void *ctx = NULL;
static struct osmo_tdef tdefs[] = {
{ .T=1, .default_val=100, .desc="100s" },
{ .T=2, .default_val=100, .unit=OSMO_TDEF_MS, .desc="100ms" },
- { .T=3, .default_val=100, .unit=OSMO_TDEF_M, .desc="100m" },
+ { .T=3, .default_val=50, .unit=OSMO_TDEF_M, .desc="50m" },
{ .T=4, .default_val=100, .unit=OSMO_TDEF_CUSTOM, .desc="100 potatoes" },
{ .T=7, .default_val=50, .desc="Water Boiling Timeout", .min_val=20, .max_val=800 }, // default is .unit=OSMO_TDEF_S == 0
@@ -54,6 +50,7 @@ static struct osmo_tdef tdefs[] = {
{ .T=1006, .default_val=0, .unit=OSMO_TDEF_S, .desc="zero s" },
{ .T=1007, .default_val=0, .unit=OSMO_TDEF_M, .desc="zero m" },
{ .T=1008, .default_val=0, .unit=OSMO_TDEF_CUSTOM, .desc="zero" },
+ { .T=1009, .default_val=0, .unit=OSMO_TDEF_US, .desc="zero us" },
{ .T=0, .default_val=1, .unit=OSMO_TDEF_CUSTOM, .desc="zero" },
@@ -111,7 +108,7 @@ static void test_tdef_get(bool test_range)
for (i = 0; i < ARRAY_SIZE(tdefs)-1; i++) {
unsigned int T = tdefs[i].T;
print_tdef_info(T);
- for (as_unit = OSMO_TDEF_S; as_unit <= OSMO_TDEF_CUSTOM; as_unit++) {
+ for (as_unit = OSMO_TDEF_S; as_unit <= OSMO_TDEF_US; as_unit++) {
print_tdef_get_short(tdefs, T, as_unit);
}
}
@@ -122,13 +119,13 @@ static void test_tdef_get(bool test_range)
for (i = 0; i < ARRAY_SIZE(tdefs_range)-1; i++) {
unsigned int T = tdefs_range[i].T;
print_tdef_info(T);
- for (as_unit = OSMO_TDEF_S; as_unit <= OSMO_TDEF_CUSTOM; as_unit++) {
+ for (as_unit = OSMO_TDEF_S; as_unit <= OSMO_TDEF_US; as_unit++) {
print_tdef_get_short(tdefs_range, T, as_unit);
}
}
}
-static void test_tdef_get_nonexisting()
+static void test_tdef_get_nonexisting(void)
{
printf("\n%s()\n", __func__);
@@ -136,15 +133,17 @@ static void test_tdef_get_nonexisting()
print_tdef_get(tdefs, 5, OSMO_TDEF_MS);
print_tdef_get(tdefs, 5, OSMO_TDEF_M);
print_tdef_get(tdefs, 5, OSMO_TDEF_CUSTOM);
+ 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__);
printf("setting 7 = 42\n");
t = osmo_tdef_get_entry(tdefs, 7);
+ OSMO_ASSERT(t != NULL);
OSMO_ASSERT(osmo_tdef_val_in_range(t, 42));
t->val = 42;
print_tdef_info(7);
@@ -152,6 +151,7 @@ static void test_tdef_set_and_get()
print_tdef_get_short(tdefs, 7, OSMO_TDEF_S);
print_tdef_get_short(tdefs, 7, OSMO_TDEF_M);
print_tdef_get_short(tdefs, 7, OSMO_TDEF_CUSTOM);
+ print_tdef_get_short(tdefs, 7, OSMO_TDEF_US);
printf("setting 7 = 420\n");
OSMO_ASSERT(osmo_tdef_set(tdefs, 7, 420, OSMO_TDEF_S) == 0);
@@ -160,6 +160,7 @@ static void test_tdef_set_and_get()
print_tdef_get_short(tdefs, 7, OSMO_TDEF_S);
print_tdef_get_short(tdefs, 7, OSMO_TDEF_M);
print_tdef_get_short(tdefs, 7, OSMO_TDEF_CUSTOM);
+ print_tdef_get_short(tdefs, 7, OSMO_TDEF_US);
printf("setting 7 = 10 (ERANGE)\n");
OSMO_ASSERT(!osmo_tdef_val_in_range(t, 10));
@@ -169,6 +170,7 @@ static void test_tdef_set_and_get()
print_tdef_get_short(tdefs, 7, OSMO_TDEF_S);
print_tdef_get_short(tdefs, 7, OSMO_TDEF_M);
print_tdef_get_short(tdefs, 7, OSMO_TDEF_CUSTOM);
+ print_tdef_get_short(tdefs, 7, OSMO_TDEF_US);
printf("setting 7 = 900 (ERANGE)\n");
OSMO_ASSERT(!osmo_tdef_val_in_range(t, 900));
@@ -178,6 +180,7 @@ static void test_tdef_set_and_get()
print_tdef_get_short(tdefs, 7, OSMO_TDEF_S);
print_tdef_get_short(tdefs, 7, OSMO_TDEF_M);
print_tdef_get_short(tdefs, 7, OSMO_TDEF_CUSTOM);
+ print_tdef_get_short(tdefs, 7, OSMO_TDEF_US);
printf("setting 23 = 50 (EEXIST)\n");
OSMO_ASSERT(osmo_tdef_set(tdefs, 23, 50, OSMO_TDEF_S) == -EEXIST);
@@ -314,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;
@@ -471,7 +474,7 @@ int main(int argc, char **argv)
ctx = talloc_named_const(NULL, 0, "tdef_test.c");
osmo_init_logging2(ctx, NULL);
- log_set_print_filename(osmo_stderr_target, 0);
+ log_set_print_filename2(osmo_stderr_target, LOG_FILENAME_NONE);
log_set_print_category(osmo_stderr_target, 1);
log_set_use_color(osmo_stderr_target, 0);
diff --git a/tests/tdef/tdef_test.ok b/tests/tdef/tdef_test.ok
index 3c4a0930..4c97dabb 100644
--- a/tests/tdef/tdef_test.ok
+++ b/tests/tdef/tdef_test.ok
@@ -5,92 +5,116 @@ osmo_tdef_get(1, s) = 100
osmo_tdef_get(1, ms) = 100000
osmo_tdef_get(1, m) = 2
osmo_tdef_get(1, custom-unit) = 100
+osmo_tdef_get(1, us) = 100000000
T2=100ms
osmo_tdef_get(2, s) = 1
osmo_tdef_get(2, ms) = 100
osmo_tdef_get(2, m) = 1
osmo_tdef_get(2, custom-unit) = 100
-T3=100m
-osmo_tdef_get(3, s) = 6000
-osmo_tdef_get(3, ms) = 6000000
-osmo_tdef_get(3, m) = 100
-osmo_tdef_get(3, custom-unit) = 100
+osmo_tdef_get(2, us) = 100000
+T3=50m
+osmo_tdef_get(3, s) = 3000
+osmo_tdef_get(3, ms) = 3000000
+osmo_tdef_get(3, m) = 50
+osmo_tdef_get(3, custom-unit) = 50
+osmo_tdef_get(3, us) = 3000000000
T4=100custom-unit
osmo_tdef_get(4, s) = 100
osmo_tdef_get(4, ms) = 100
osmo_tdef_get(4, m) = 100
osmo_tdef_get(4, custom-unit) = 100
+osmo_tdef_get(4, us) = 100
T7=50s
osmo_tdef_get(7, s) = 50
osmo_tdef_get(7, ms) = 50000
osmo_tdef_get(7, m) = 1
osmo_tdef_get(7, custom-unit) = 50
+osmo_tdef_get(7, us) = 50000000
T8=300s
osmo_tdef_get(8, s) = 300
osmo_tdef_get(8, ms) = 300000
osmo_tdef_get(8, m) = 5
osmo_tdef_get(8, custom-unit) = 300
+osmo_tdef_get(8, us) = 300000000
T9=5m
osmo_tdef_get(9, s) = 300
osmo_tdef_get(9, ms) = 300000
osmo_tdef_get(9, m) = 5
osmo_tdef_get(9, custom-unit) = 5
+osmo_tdef_get(9, us) = 300000000
T10=20m
osmo_tdef_get(10, s) = 1200
osmo_tdef_get(10, ms) = 1200000
osmo_tdef_get(10, m) = 20
osmo_tdef_get(10, custom-unit) = 20
+osmo_tdef_get(10, us) = 1200000000
T1000=2000ms
osmo_tdef_get(1000, s) = 2
osmo_tdef_get(1000, ms) = 2000
osmo_tdef_get(1000, m) = 1
osmo_tdef_get(1000, custom-unit) = 2000
+osmo_tdef_get(1000, us) = 2000000
T1001=60000ms
osmo_tdef_get(1001, s) = 60
osmo_tdef_get(1001, ms) = 60000
osmo_tdef_get(1001, m) = 1
osmo_tdef_get(1001, custom-unit) = 60000
+osmo_tdef_get(1001, us) = 60000000
T1004=1ms
osmo_tdef_get(1004, s) = 1
osmo_tdef_get(1004, ms) = 1
osmo_tdef_get(1004, m) = 1
osmo_tdef_get(1004, custom-unit) = 1
+osmo_tdef_get(1004, us) = 1000
T1005=0ms
osmo_tdef_get(1005, s) = 0
osmo_tdef_get(1005, ms) = 0
osmo_tdef_get(1005, m) = 0
osmo_tdef_get(1005, custom-unit) = 0
+osmo_tdef_get(1005, us) = 0
T1006=0s
osmo_tdef_get(1006, s) = 0
osmo_tdef_get(1006, ms) = 0
osmo_tdef_get(1006, m) = 0
osmo_tdef_get(1006, custom-unit) = 0
+osmo_tdef_get(1006, us) = 0
T1007=0m
osmo_tdef_get(1007, s) = 0
osmo_tdef_get(1007, ms) = 0
osmo_tdef_get(1007, m) = 0
osmo_tdef_get(1007, custom-unit) = 0
+osmo_tdef_get(1007, us) = 0
T1008=0custom-unit
osmo_tdef_get(1008, s) = 0
osmo_tdef_get(1008, ms) = 0
osmo_tdef_get(1008, m) = 0
osmo_tdef_get(1008, custom-unit) = 0
+osmo_tdef_get(1008, us) = 0
+T1009=0us
+osmo_tdef_get(1009, s) = 0
+osmo_tdef_get(1009, ms) = 0
+osmo_tdef_get(1009, m) = 0
+osmo_tdef_get(1009, custom-unit) = 0
+osmo_tdef_get(1009, us) = 0
T0=1custom-unit
osmo_tdef_get(0, s) = 1
osmo_tdef_get(0, ms) = 1
osmo_tdef_get(0, m) = 1
osmo_tdef_get(0, custom-unit) = 1
+osmo_tdef_get(0, us) = 1
T123=1s
osmo_tdef_get(123, s) = 1
osmo_tdef_get(123, ms) = 1000
osmo_tdef_get(123, m) = 1
osmo_tdef_get(123, custom-unit) = 1
+osmo_tdef_get(123, us) = 1000000
test_tdef_get_nonexisting()
osmo_tdef_get(tdefs, 5, s, 999) = 999
osmo_tdef_get(tdefs, 5, ms, 999) = 999
osmo_tdef_get(tdefs, 5, m, 999) = 999
osmo_tdef_get(tdefs, 5, custom-unit, 999) = 999
+osmo_tdef_get(tdefs, 5, us, 999) = 999
test_tdef_set_and_get()
setting 7 = 42
@@ -99,24 +123,28 @@ osmo_tdef_get(7, ms) = 42000
osmo_tdef_get(7, s) = 42
osmo_tdef_get(7, m) = 1
osmo_tdef_get(7, custom-unit) = 42
+osmo_tdef_get(7, us) = 42000000
setting 7 = 420
T7=420s(def=50)
osmo_tdef_get(7, ms) = 420000
osmo_tdef_get(7, s) = 420
osmo_tdef_get(7, m) = 7
osmo_tdef_get(7, custom-unit) = 420
+osmo_tdef_get(7, us) = 420000000
setting 7 = 10 (ERANGE)
T7=420s(def=50)
osmo_tdef_get(7, ms) = 420000
osmo_tdef_get(7, s) = 420
osmo_tdef_get(7, m) = 7
osmo_tdef_get(7, custom-unit) = 420
+osmo_tdef_get(7, us) = 420000000
setting 7 = 900 (ERANGE)
T7=420s(def=50)
osmo_tdef_get(7, ms) = 420000
osmo_tdef_get(7, s) = 420
osmo_tdef_get(7, m) = 7
osmo_tdef_get(7, custom-unit) = 420
+osmo_tdef_get(7, us) = 420000000
setting 23 = 50 (EEXIST)
resetting
T7=50s
@@ -126,7 +154,7 @@ test_tdef_state_timeout()
state=A T=0, no timeout
--> A (configured as T1 100 s) rc=0; state=A T=1, 100.000000 s remaining
--> B (configured as T2 100 ms) rc=0; state=B T=2, 1.000000 s remaining
- --> C (configured as T3 100 m) rc=0; state=C T=3, 6000.000000 s remaining
+ --> C (configured as T3 50 m) rc=0; state=C T=3, 3000.000000 s remaining
--> D (configured as T4 100 custom-unit) rc=0; state=D T=4, 100.000000 s remaining
--> G (configured as T7 50 s) rc=0; state=G T=7, 50.000000 s remaining
--> H (configured as T8 300 s) rc=0; state=H T=8, 300.000000 s remaining
@@ -155,5 +183,5 @@ state=A T=1, 76.954322 s remaining
- test disallowed transition:
--> Z (no timer configured for this state) rc=0; state=Z T=0, no timeout
--> B (configured as T2 100 ms) rc=-1; state=Z T=0, no timeout
- --> C (configured as T3 100 m) rc=-1; state=Z T=0, no timeout
+ --> C (configured as T3 50 m) rc=-1; state=Z T=0, no timeout
--> D (configured as T4 100 custom-unit) rc=-1; state=Z T=0, no timeout
diff --git a/tests/tdef/tdef_vty_test_config_root.c b/tests/tdef/tdef_vty_config_root_test.c
index 92113e87..8c46d958 100644
--- a/tests/tdef/tdef_vty_test_config_root.c
+++ b/tests/tdef/tdef_vty_config_root_test.c
@@ -17,10 +17,6 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#define _GNU_SOURCE
@@ -45,7 +41,7 @@
/* ------------------- HERE IS THE INTERESTING TDEF RELEVANT PART ------------------- */
/* This example keeps several separate timer groups and offers 'timer' VTY commands at the root of the config node. See
- * the tdef_vty_test_config_root.vty transcript test.
+ * the tdef_vty_config_root_test.vty transcript test.
*/
static struct osmo_tdef tdefs_test[] = {
@@ -102,7 +98,7 @@ enum tdef_vty_test_nodes {
/* This example puts 'timer' configuration commands directly at the root of the CONFIG_NODE.
* This TIMER_NODE is merely needed as a hook for the vty_write() command, but becomes an empty node in the VTY docs.
* It is possible to cheat around needing this if you choose to config_write_timer() in another root nodes' write cb.
- * Another example using a 'network' subnode is \ref tdef_vty_test_config_subnode.c */
+ * Another example using a 'network' subnode is \ref tdef_vty_config_subnode_test.c */
static struct cmd_node timer_node = {
TIMER_NODE,
"%s(config-timer)# ",
@@ -115,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);
@@ -127,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"
@@ -264,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_test_config_root.vty b/tests/tdef/tdef_vty_config_root_test.vty
index 6a53b805..6a53b805 100644
--- a/tests/tdef/tdef_vty_test_config_root.vty
+++ b/tests/tdef/tdef_vty_config_root_test.vty
diff --git a/tests/tdef/tdef_vty_test_config_subnode.c b/tests/tdef/tdef_vty_config_subnode_test.c
index ce851f50..e3e165da 100644
--- a/tests/tdef/tdef_vty_test_config_subnode.c
+++ b/tests/tdef/tdef_vty_config_subnode_test.c
@@ -17,10 +17,6 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#define _GNU_SOURCE
@@ -46,7 +42,7 @@
/* This example keeps a single global timer group and offers a custom 'timer' VTY command in a 'network' subnode below
* the CONFIG_NODE.
- * the tdef_vty_test_config_subnode.vty transcript test.
+ * the tdef_vty_config_subnode_test.vty transcript test.
*/
static struct osmo_tdef global_tdefs[] = {
@@ -106,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);
@@ -120,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"
@@ -257,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_test_config_subnode.vty b/tests/tdef/tdef_vty_config_subnode_test.vty
index 2605f71d..2605f71d 100644
--- a/tests/tdef/tdef_vty_test_config_subnode.vty
+++ b/tests/tdef/tdef_vty_config_subnode_test.vty
diff --git a/tests/tdef/tdef_vty_test_dynamic.c b/tests/tdef/tdef_vty_dynamic_test.c
index 20dae535..b646c54e 100644
--- a/tests/tdef/tdef_vty_test_dynamic.c
+++ b/tests/tdef/tdef_vty_dynamic_test.c
@@ -17,10 +17,6 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#define _GNU_SOURCE
@@ -183,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);
@@ -194,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"
@@ -331,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/tdef/tdef_vty_test_dynamic.vty b/tests/tdef/tdef_vty_dynamic_test.vty
index 6aae746b..6aae746b 100644
--- a/tests/tdef/tdef_vty_test_dynamic.vty
+++ b/tests/tdef/tdef_vty_dynamic_test.vty
diff --git a/tests/testsuite.at b/tests/testsuite.at
index 493c16f4..60aa74d3 100644
--- a/tests/testsuite.at
+++ b/tests/testsuite.at
@@ -36,7 +36,7 @@ AT_CLEANUP
AT_SETUP([bitvec])
AT_KEYWORDS([bitvec])
cat $abs_srcdir/bitvec/bitvec_test.ok > expout
-AT_CHECK([$abs_top_builddir/tests/bitvec/bitvec_test], [0], [expout], [ignore])
+AT_CHECK([$abs_top_builddir/tests/bitvec/bitvec_test], [0], [expout])
AT_CLEANUP
AT_SETUP([bitcomp])
@@ -102,6 +102,18 @@ cat $abs_srcdir/smscb/smscb_test.ok > expout
AT_CHECK([$abs_top_builddir/tests/smscb/smscb_test], [0], [expout])
AT_CLEANUP
+AT_SETUP([smscb_gsm0341])
+AT_KEYWORDS([smscb_gsm0341])
+cat $abs_srcdir/smscb/gsm0341_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/smscb/gsm0341_test], [0], [expout])
+AT_CLEANUP
+
+AT_SETUP([smscb_cbsp])
+AT_KEYWORDS([smscb_cbsp])
+cat $abs_srcdir/smscb/cbsp_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/smscb/cbsp_test], [0], [expout])
+AT_CLEANUP
+
AT_SETUP([ussd])
AT_KEYWORDS([ussd])
cat $abs_srcdir/ussd/ussd_test.ok > expout
@@ -114,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
@@ -153,7 +171,14 @@ AT_CLEANUP
AT_SETUP([gsm0408])
AT_KEYWORDS([gsm0408])
cat $abs_srcdir/gsm0408/gsm0408_test.ok > expout
-AT_CHECK([$abs_top_builddir/tests/gsm0408/gsm0408_test], [0], [expout], [ignore])
+cat $abs_srcdir/gsm0408/gsm0408_test.err > experr
+AT_CHECK([$abs_top_builddir/tests/gsm0408/gsm0408_test], [0], [expout], [experr])
+AT_CLEANUP
+
+AT_SETUP([gsm48_rest_octets])
+AT_KEYWORDS([gsm48_rest_octets])
+cat $abs_srcdir/gsm48/rest_octets_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/gsm48/rest_octets_test], [0], [expout], [ignore])
AT_CLEANUP
AT_SETUP([gprs])
@@ -162,11 +187,18 @@ cat $abs_srcdir/gprs/gprs_test.ok > expout
AT_CHECK([$abs_top_builddir/tests/gprs/gprs_test], [0], [expout], [ignore])
AT_CLEANUP
-AT_SETUP([logging])
-AT_KEYWORDS([logging])
+AT_SETUP([logging_stream])
+AT_KEYWORDS([logging_stream])
+cat $abs_srcdir/logging/logging_test.ok > expout
+cat $abs_srcdir/logging/logging_test.err > experr
+AT_CHECK([$abs_top_builddir/tests/logging/logging_test stream], [0], [expout], [experr])
+AT_CLEANUP
+
+AT_SETUP([logging_wqueue])
+AT_KEYWORDS([logging_wqueue])
cat $abs_srcdir/logging/logging_test.ok > expout
cat $abs_srcdir/logging/logging_test.err > experr
-AT_CHECK([$abs_top_builddir/tests/logging/logging_test], [0], [expout], [experr])
+AT_CHECK([$abs_top_builddir/tests/logging/logging_test wqueue], [0], [expout], [experr])
AT_CLEANUP
AT_SETUP([codec])
@@ -204,8 +236,9 @@ AT_CLEANUP
AT_SETUP([vty])
AT_KEYWORDS([vty])
cat $abs_srcdir/vty/vty_test.ok > expout
+cat $abs_srcdir/vty/vty_test.err > experr
cp $abs_srcdir/vty/*.cfg .
-AT_CHECK([$abs_top_builddir/tests/vty/vty_test], [0], [expout], [ignore])
+AT_CHECK([$abs_top_builddir/tests/vty/vty_test], [0], [expout], [experr])
AT_CLEANUP
AT_SETUP([gprs-bssgp])
@@ -214,12 +247,24 @@ cat $abs_srcdir/gb/gprs_bssgp_test.ok > expout
AT_CHECK([$abs_top_builddir/tests/gb/gprs_bssgp_test], [0], [expout], [ignore])
AT_CLEANUP
+AT_SETUP([gprs-bssgp-rim])
+AT_KEYWORDS([gprs-bssgp-rim])
+cat $abs_srcdir/gb/gprs_bssgp_rim_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/gb/gprs_bssgp_rim_test], [0], [expout], [ignore])
+AT_CLEANUP
+
AT_SETUP([gprs-ns])
AT_KEYWORDS([gprs-ns])
cat $abs_srcdir/gb/gprs_ns_test.ok > expout
AT_CHECK([$abs_top_builddir/tests/gb/gprs_ns_test], [0], [expout], [ignore])
AT_CLEANUP
+AT_SETUP([gprs-ns2])
+AT_KEYWORDS([gprs-ns2])
+cat $abs_srcdir/gb/gprs_ns2_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/gb/gprs_ns2_test], [0], [expout], [ignore])
+AT_CLEANUP
+
AT_SETUP([utils])
AT_KEYWORDS([utils])
cat $abs_srcdir/utils/utils_test.ok > expout
@@ -230,7 +275,8 @@ AT_CLEANUP
AT_SETUP([stats])
AT_KEYWORDS([stats])
cat $abs_srcdir/stats/stats_test.ok > expout
-AT_CHECK([$abs_top_builddir/tests/stats/stats_test], [0], [expout], [ignore])
+cat $abs_srcdir/stats/stats_test.err > experr
+AT_CHECK([$abs_top_builddir/tests/stats/stats_test], [0], [expout], [experr])
AT_CLEANUP
AT_SETUP([write_queue])
@@ -402,3 +448,63 @@ AT_KEYWORDS([bitgen])
cat $abs_srcdir/bitgen/bitgen_test.ok > expout
AT_CHECK([$abs_top_builddir/tests/bitgen/bitgen_test], [0], [expout], [ignore])
AT_CLEANUP
+
+AT_SETUP([gad])
+AT_KEYWORDS([gad])
+cat $abs_srcdir/gad/gad_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/gad/gad_test], [0], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([bsslap])
+AT_KEYWORDS([bsslap])
+cat $abs_srcdir/bsslap/bsslap_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/bsslap/bsslap_test], [0], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([bssmap_le])
+AT_KEYWORDS([bssmap_le])
+cat $abs_srcdir/bssmap_le/bssmap_le_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/bssmap_le/bssmap_le_test], [0], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([it_q])
+AT_KEYWORDS([it_q])
+cat $abs_srcdir/it_q/it_q_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/it_q/it_q_test], [0], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([base64])
+AT_KEYWORDS([base64])
+cat $abs_srcdir/base64/base64_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/base64/base64_test], [0], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([time_cc])
+AT_KEYWORDS([time_cc])
+cat $abs_srcdir/time_cc/time_cc_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/time_cc/time_cc_test], [0], [expout], [ignore])
+AT_CLEANUP
+
+AT_SETUP([iuup])
+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_test_frame])
+AT_KEYWORDS([v110_test_frame])
+cat $abs_srcdir/v110/test_frame.ok > expout
+AT_CHECK([$abs_top_builddir/tests/v110/test_frame], [], [expout],[])
+AT_CLEANUP
+
+AT_SETUP([v110_test_ra1])
+AT_KEYWORDS([v110_test_ra1])
+cat $abs_srcdir/v110/test_ra1.ok > expout
+AT_CHECK([$abs_top_builddir/tests/v110/test_ra1], [], [expout],[])
+AT_CLEANUP
+
+AT_SETUP([gsm44021_test_frame_csd])
+AT_KEYWORDS([gsm44021_test_frame_csd])
+cat $abs_srcdir/gsm44021/test_frame_csd.ok > expout
+AT_CHECK([$abs_top_builddir/tests/gsm44021/test_frame_csd], [], [expout],[])
+AT_CLEANUP
diff --git a/tests/time_cc/time_cc_test.c b/tests/time_cc/time_cc_test.c
new file mode 100644
index 00000000..e4a5aaf3
--- /dev/null
+++ b/tests/time_cc/time_cc_test.c
@@ -0,0 +1,768 @@
+/* (C) 2021 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * All Rights Reserved
+ *
+ * Author: Neels Janosch Hofmeyr <nhofmeyr@sysmocom.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <inttypes.h>
+
+#include <osmocom/core/utils.h>
+#include <osmocom/core/rate_ctr.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/application.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/tdef.h>
+#include <osmocom/core/time_cc.h>
+
+enum my_ctrs {
+ CTR_CEIL,
+ CTR_ROUND,
+ CTR_FLOOR,
+};
+
+const struct rate_ctr_desc my_ctr_desc[] = {
+ [CTR_CEIL] = {"ceil", "testing round_threshold_usec = 1"},
+ [CTR_ROUND] = {"round", "testing round_threshold_usec = 0 = gran_usec/2"},
+ [CTR_FLOOR] = {"floor", "testing round_threshold_usec = gran_usec"},
+};
+
+const struct rate_ctr_group_desc my_ctrg_desc = {
+ "time_cc_test",
+ "Counters for osmo_time_cc test",
+ 0,
+ ARRAY_SIZE(my_ctr_desc),
+ my_ctr_desc,
+};
+
+struct rate_ctr_group *my_ctrg;
+
+
+enum my_obj_timers {
+ T_GRAN = -23,
+ T_ROUND_THRESH = -24,
+ T_FORGET_SUM = -25,
+};
+
+struct osmo_tdef g_my_obj_tdefs[] = {
+ { .T = T_GRAN, .default_val = 0, .unit = OSMO_TDEF_MS, .desc = "flag_cc granularity, or zero for 1 second" },
+ { .T = T_ROUND_THRESH, .default_val = 0, .unit = OSMO_TDEF_MS,
+ .desc = "flag_cc rounding threshold, or zero for half a granularity" },
+ { .T = T_FORGET_SUM, .default_val = 0, .unit = OSMO_TDEF_MS,
+ .desc = "flag_cc inactivity forget period, or zero to not forget any timings" },
+ {}
+};
+
+
+struct my_obj {
+ struct osmo_time_cc flag_cc_ceil;
+ struct osmo_time_cc flag_cc_round;
+ struct osmo_time_cc flag_cc_floor;
+};
+
+void my_obj_init(struct my_obj *my_obj)
+{
+ osmo_time_cc_init(&my_obj->flag_cc_ceil);
+ my_obj->flag_cc_ceil.cfg = (struct osmo_time_cc_cfg){
+ .rate_ctr = rate_ctr_group_get_ctr(my_ctrg, CTR_CEIL),
+ .round_threshold_usec = 1,
+ .T_gran = T_GRAN,
+ .T_forget_sum = T_FORGET_SUM,
+ .T_defs = g_my_obj_tdefs,
+ };
+
+ osmo_time_cc_init(&my_obj->flag_cc_round);
+ my_obj->flag_cc_round.cfg = (struct osmo_time_cc_cfg){
+ .rate_ctr = rate_ctr_group_get_ctr(my_ctrg, CTR_ROUND),
+ .T_gran = T_GRAN,
+ .T_round_threshold = T_ROUND_THRESH,
+ .T_forget_sum = T_FORGET_SUM,
+ .T_defs = g_my_obj_tdefs,
+ };
+
+ osmo_time_cc_init(&my_obj->flag_cc_floor);
+ my_obj->flag_cc_floor.cfg = (struct osmo_time_cc_cfg){
+ .rate_ctr = rate_ctr_group_get_ctr(my_ctrg, CTR_FLOOR),
+ .round_threshold_usec = UINT64_MAX, /* always >= gran_usec */
+ .T_gran = T_GRAN,
+ .T_forget_sum = T_FORGET_SUM,
+ .T_defs = g_my_obj_tdefs,
+ };
+}
+
+void my_obj_event(struct my_obj *my_obj, bool flag)
+{
+ osmo_time_cc_set_flag(&my_obj->flag_cc_ceil, flag);
+ osmo_time_cc_set_flag(&my_obj->flag_cc_round, flag);
+ osmo_time_cc_set_flag(&my_obj->flag_cc_floor, flag);
+}
+
+void my_obj_destruct(struct my_obj *my_obj)
+{
+ osmo_time_cc_cleanup(&my_obj->flag_cc_ceil);
+ osmo_time_cc_cleanup(&my_obj->flag_cc_round);
+ osmo_time_cc_cleanup(&my_obj->flag_cc_floor);
+}
+
+static const struct log_info_cat log_categories[] = {
+};
+
+static const struct log_info log_info = {
+ .cat = log_categories,
+ .num_cat = ARRAY_SIZE(log_categories),
+};
+
+int main(int argc, char **argv)
+{
+ void *ctx = talloc_named_const(NULL, 0, "time_cc_test");
+ struct timespec *now;
+ struct my_obj my_obj = {0};
+
+ osmo_init_logging2(ctx, &log_info);
+
+ /* enable override for CLOCK_MONOTONIC */
+ osmo_clock_override_enable(CLOCK_MONOTONIC, true);
+ now = osmo_clock_override_gettimespec(CLOCK_MONOTONIC);
+ now->tv_sec = 23000;
+ now->tv_nsec = 0;
+
+ /* enable override for osmo_gettimeofday(), for osmo_timer_schedule() */
+ osmo_gettimeofday_override = true;
+ osmo_gettimeofday_override_time = (struct timeval){23000, 0};
+
+ my_ctrg = rate_ctr_group_alloc(ctx, &my_ctrg_desc, 0);
+
+#define CHECK_RATE_CTRS(exp_ceil, exp_round, exp_floor) do { \
+ printf("%d CHECK_RATE_CTRS(" #exp_ceil ", " #exp_round ", " #exp_floor ")", \
+ my_obj.flag_cc_round.flag_state); \
+ while (osmo_select_main_ctx(1) > 0); \
+ if (exp_ceil != my_obj.flag_cc_ceil.cfg.rate_ctr->current \
+ || exp_round != my_obj.flag_cc_round.cfg.rate_ctr->current \
+ || exp_floor != my_obj.flag_cc_floor.cfg.rate_ctr->current) \
+ printf("\n ERROR on line %d: ctr_ceil=%"PRIu64" ctr_round=%"PRIu64" ctr_floor=%"PRIu64"\n", \
+ __LINE__, \
+ my_obj.flag_cc_ceil.cfg.rate_ctr->current, \
+ my_obj.flag_cc_round.cfg.rate_ctr->current, \
+ my_obj.flag_cc_floor.cfg.rate_ctr->current); \
+ else \
+ printf(" ok\n"); \
+ } while (0)
+
+#define ADD_MILLISECS_NO_SELECT(ms) do { \
+ osmo_clock_override_add(CLOCK_MONOTONIC, ms / 1000, (uint64_t)(ms % 1000) * 1000000); \
+ osmo_gettimeofday_override_add(ms / 1000, (uint64_t)(ms % 1000) * 1000); \
+ printf("%d ADD_MILLISECS(" #ms ") --> %ld.%03ld", my_obj.flag_cc_round.flag_state, \
+ now->tv_sec, now->tv_nsec/1000000); \
+ printf("\n"); \
+ } while (0)
+
+#define ADD_MILLISECS(ms) do { \
+ ADD_MILLISECS_NO_SELECT(ms); \
+ while (osmo_select_main_ctx(1) > 0); \
+ } while (0)
+
+#define FLAG(VAL) do { \
+ printf(" flag: %s -> %s\n", my_obj.flag_cc_round.flag_state ? "TRUE" : "FALSE", VAL ? "TRUE" : "FALSE"); \
+ my_obj_event(&my_obj, VAL); \
+ } while (0)
+
+ /*
+ * sum ^
+ * | ________
+ * | /
+ * | /
+ * | /
+ * 3*gran --+--------------------------------------+
+ * | /:
+ * | / :
+ * | - - - - - - - - - - - - - - - - - / :
+ * | /. :
+ * | / . :
+ * 2*gran --+--------------------------------+ . :
+ * | /: . :
+ * | / : . :
+ * | - - - - - - - - - -_________/ : . :
+ * | / . : . :
+ * | / . : . :
+ * 1*gran --+-----------------+ . : . :
+ * | /: . : . :
+ * | / : . : . :
+ * | - - - - - - -/ : . : . :
+ * | /. : . : . :
+ * | ....-------' . : . : . :
+ * 0 +----------------------------------------------------------> elapsed time
+ * . : . : . :
+ * _ _ _______ ____________
+ * flag: __| |_| |____| . : |_______|. : . : |__________
+ * f t f t f t . : f t. : . : f
+ * round_threshold_usec : . : . : . :
+ * = 1 usec: 0 1 . :2 . :3 . :4 = "ceil()"
+ * = 0 == gran_usec/2: 0 1 : 2 : 3 : = "round()"
+ * = gran_usec: 0 1 2 3 = "floor()"
+ */
+
+ printf("\n----------- cumulating time, without forget_sum\n\n");
+
+ my_obj_init(&my_obj);
+ CHECK_RATE_CTRS(0, 0, 0);
+
+ ADD_MILLISECS(100);
+ CHECK_RATE_CTRS(0, 0, 0);
+
+ FLAG(true);
+ /* flag has just turned true the first time */
+ CHECK_RATE_CTRS(0, 0, 0);
+ ADD_MILLISECS(1);
+ /* flag has been true for 0.001s */
+ CHECK_RATE_CTRS(1, 0, 0);
+ ADD_MILLISECS(99);
+ /* flag has been true for 0.1s */
+ CHECK_RATE_CTRS(1, 0, 0);
+ FLAG(false);
+ CHECK_RATE_CTRS(1, 0, 0);
+
+ ADD_MILLISECS(100);
+
+ CHECK_RATE_CTRS(1, 0, 0);
+ FLAG(true);
+ CHECK_RATE_CTRS(1, 0, 0);
+ ADD_MILLISECS(100);
+ /* flag has been true for 0.2s */
+ CHECK_RATE_CTRS(1, 0, 0);
+ FLAG(false);
+ CHECK_RATE_CTRS(1, 0, 0);
+
+ ADD_MILLISECS(300);
+
+ CHECK_RATE_CTRS(1, 0, 0);
+ FLAG(true);
+ CHECK_RATE_CTRS(1, 0, 0);
+ ADD_MILLISECS(299);
+ /* flag has been true for 0.499s */
+ CHECK_RATE_CTRS(1, 0, 0);
+ ADD_MILLISECS(1);
+ /* flag has been true for 0.5s */
+ CHECK_RATE_CTRS(1, 1, 0);
+ ADD_MILLISECS(499);
+ /* flag has been true for 0.999s */
+ CHECK_RATE_CTRS(1, 1, 0);
+ ADD_MILLISECS(1);
+ /* flag has been true for 1.0s */
+ CHECK_RATE_CTRS(1, 1, 1);
+ ADD_MILLISECS(1);
+ /* flag has been true for 1.001s */
+ CHECK_RATE_CTRS(2, 1, 1);
+ ADD_MILLISECS(299);
+ /* flag has been true for 1.3s */
+ CHECK_RATE_CTRS(2, 1, 1);
+ FLAG(false);
+ CHECK_RATE_CTRS(2, 1, 1);
+
+ ADD_MILLISECS(400);
+
+ CHECK_RATE_CTRS(2, 1, 1);
+ FLAG(true);
+ CHECK_RATE_CTRS(2, 1, 1);
+ ADD_MILLISECS(199);
+ /* flag has been true for 1.499s */
+ CHECK_RATE_CTRS(2, 1, 1);
+ ADD_MILLISECS(2);
+ /* flag has been true for 1.501s */
+ CHECK_RATE_CTRS(2, 2, 1);
+ ADD_MILLISECS(498);
+ /* flag has been true for 1.999s */
+ CHECK_RATE_CTRS(2, 2, 1);
+ ADD_MILLISECS(2);
+ /* flag has been true for 2.001s */
+ CHECK_RATE_CTRS(3, 2, 2);
+ ADD_MILLISECS(500);
+ /* flag has been true for 2.501s */
+ CHECK_RATE_CTRS(3, 3, 2);
+ ADD_MILLISECS(498);
+ /* flag has been true for 2.999s */
+ CHECK_RATE_CTRS(3, 3, 2);
+ ADD_MILLISECS(3);
+ /* flag has been true for 3.003s */
+ CHECK_RATE_CTRS(4, 3, 3);
+ ADD_MILLISECS(200);
+ /* flag has been true for 3.203s */
+ CHECK_RATE_CTRS(4, 3, 3);
+ FLAG(false);
+ CHECK_RATE_CTRS(4, 3, 3);
+
+ ADD_MILLISECS(4321);
+ CHECK_RATE_CTRS(4, 3, 3);
+
+ FLAG(true);
+ CHECK_RATE_CTRS(4, 3, 3);
+ ADD_MILLISECS(5678);
+ CHECK_RATE_CTRS(9, 9, 8);
+ FLAG(false);
+ CHECK_RATE_CTRS(9, 9, 8);
+
+ my_obj_destruct(&my_obj);
+ rate_ctr_group_reset(my_ctrg);
+
+ printf("\n----------- test forget_sum_usec\n\n");
+ osmo_tdef_set(g_my_obj_tdefs, T_FORGET_SUM, 10, OSMO_TDEF_S);
+
+ now->tv_sec = 23000;
+ now->tv_nsec = 0;
+ osmo_gettimeofday_override_time = (struct timeval){23000, 0};
+
+ my_obj_init(&my_obj);
+
+ CHECK_RATE_CTRS(0, 0, 0);
+
+ FLAG(true);
+ /* flag has just turned true the first time */
+ CHECK_RATE_CTRS(0, 0, 0);
+ ADD_MILLISECS(100);
+ /* flag has been true for 0.1s */
+ CHECK_RATE_CTRS(1, 0, 0);
+ FLAG(false);
+ CHECK_RATE_CTRS(1, 0, 0);
+
+ ADD_MILLISECS(1000);
+ /* 1 s of being false, forget_sum_usec has not yet occurred */
+ CHECK_RATE_CTRS(1, 0, 0);
+
+ ADD_MILLISECS(8999);
+ /* 9.999 s of being false, forget_sum_usec has not yet occurred */
+ CHECK_RATE_CTRS(1, 0, 0);
+
+ ADD_MILLISECS(1);
+ /* 10 s of being false, forget_sum_usec has occurred */
+ CHECK_RATE_CTRS(1, 0, 0);
+
+ FLAG(true);
+ CHECK_RATE_CTRS(1, 0, 0);
+ ADD_MILLISECS(1);
+ /* Since previous sums were forgotton, ceil() triggers again */
+ CHECK_RATE_CTRS(2, 0, 0);
+ /* If the sum had not been forgotten, adding 400 ms to the initial 100 ms would have triggered round(). Verify
+ * that this does not occur, since now full 500 ms are required */
+ ADD_MILLISECS(399);
+ CHECK_RATE_CTRS(2, 0, 0);
+ /* Adding another 100 ms will trigger round() */
+ ADD_MILLISECS(99);
+ CHECK_RATE_CTRS(2, 0, 0);
+ ADD_MILLISECS(1);
+ CHECK_RATE_CTRS(2, 1, 0);
+ /* If the sum had not been forgotten, adding 900 ms to the initial 100 ms would have triggered floor(). Verify
+ * that this does not occur, since now full 1000 ms are required. We already added 500 ms above. */
+ ADD_MILLISECS(400);
+ CHECK_RATE_CTRS(2, 1, 0);
+ /* Adding another 100 ms will trigger floor() */
+ ADD_MILLISECS(99);
+ CHECK_RATE_CTRS(2, 1, 0);
+ ADD_MILLISECS(1);
+ CHECK_RATE_CTRS(2, 1, 1);
+
+ /* Test that durations of false below forget_sum_usec never trigger a forget */
+ ADD_MILLISECS(300);
+ CHECK_RATE_CTRS(3, 1, 1);
+ /* internal counter is now at 0.3s above the last reported rate counter */
+ FLAG(false);
+ ADD_MILLISECS(9999);
+ FLAG(true);
+ ADD_MILLISECS(25);
+ FLAG(false);
+ ADD_MILLISECS(9999);
+ FLAG(true);
+ ADD_MILLISECS(25);
+ FLAG(false);
+ ADD_MILLISECS(9999);
+ FLAG(true);
+ ADD_MILLISECS(25);
+ FLAG(false);
+ ADD_MILLISECS(9999);
+ FLAG(true);
+ ADD_MILLISECS(25);
+ /* internal counter is now at 0.4s above the last reported rate counter */
+ CHECK_RATE_CTRS(3, 1, 1);
+ ADD_MILLISECS(100);
+ CHECK_RATE_CTRS(3, 2, 1);
+ ADD_MILLISECS(500);
+ CHECK_RATE_CTRS(3, 2, 2);
+
+ /* Test that repeated osmo_time_cc_set_flag(false) does not cancel a forget_sum_usec */
+ ADD_MILLISECS(300);
+ /* internal counter is now at 0.3s above the last reported rate counter */
+ CHECK_RATE_CTRS(4, 2, 2);
+ FLAG(false);
+ ADD_MILLISECS(5000);
+ /* Repeat 'false', must not affect forget_sum_usec */
+ FLAG(false);
+ ADD_MILLISECS(5000);
+ CHECK_RATE_CTRS(4, 2, 2);
+ /* 10 s have passed, forget_sum_usec has occurred.
+ * Hence ceil() will trigger again right away: */
+ FLAG(true);
+ ADD_MILLISECS(1);
+ CHECK_RATE_CTRS(5, 2, 2);
+ /* Adding 200 ms to the initial 300 ms would have triggered round(), but no more after forget_sum_usec */
+ ADD_MILLISECS(199);
+ CHECK_RATE_CTRS(5, 2, 2);
+ /* Adding another 300 ms will trigger round() */
+ ADD_MILLISECS(299);
+ CHECK_RATE_CTRS(5, 2, 2);
+ ADD_MILLISECS(1);
+ CHECK_RATE_CTRS(5, 3, 2);
+ /* Adding 700 ms to the initial 300 ms would have triggered ceil(), but no more after forget_sum_usec */
+ ADD_MILLISECS(200);
+ CHECK_RATE_CTRS(5, 3, 2);
+ /* Adding another 300 ms will trigger ceil() */
+ ADD_MILLISECS(299);
+ CHECK_RATE_CTRS(5, 3, 2);
+ ADD_MILLISECS(1);
+ CHECK_RATE_CTRS(5, 3, 3);
+
+ my_obj_destruct(&my_obj);
+ rate_ctr_group_reset(my_ctrg);
+
+
+ /* Verify correctness when select() lags and runs timer callbacks too late */
+ printf("\n----------- cumulating time, without forget_sum, when timer cb are invoked late\n\n");
+ osmo_tdef_set(g_my_obj_tdefs, T_FORGET_SUM, 0, OSMO_TDEF_S);
+ now->tv_sec = 23000;
+ now->tv_nsec = 0;
+ osmo_gettimeofday_override_time = (struct timeval){23000, 0};
+
+ my_obj_init(&my_obj);
+ CHECK_RATE_CTRS(0, 0, 0);
+
+ ADD_MILLISECS_NO_SELECT(100);
+ CHECK_RATE_CTRS(0, 0, 0);
+
+ FLAG(true);
+ /* flag has just turned true the first time */
+ CHECK_RATE_CTRS(0, 0, 0);
+ ADD_MILLISECS_NO_SELECT(100);
+ /* flag has been true for 0.1s */
+ CHECK_RATE_CTRS(1, 0, 0);
+ FLAG(false);
+ CHECK_RATE_CTRS(1, 0, 0);
+
+ ADD_MILLISECS_NO_SELECT(100);
+
+ CHECK_RATE_CTRS(1, 0, 0);
+ FLAG(true);
+ CHECK_RATE_CTRS(1, 0, 0);
+ ADD_MILLISECS_NO_SELECT(100);
+ /* flag has been true for 0.2s */
+ CHECK_RATE_CTRS(1, 0, 0);
+ FLAG(false);
+ CHECK_RATE_CTRS(1, 0, 0);
+
+ ADD_MILLISECS_NO_SELECT(300);
+
+ CHECK_RATE_CTRS(1, 0, 0);
+ FLAG(true);
+ CHECK_RATE_CTRS(1, 0, 0);
+ ADD_MILLISECS_NO_SELECT(799);
+ /* flag has been true for 0.999s */
+ CHECK_RATE_CTRS(1, 1, 0);
+ ADD_MILLISECS_NO_SELECT(1);
+ /* flag has been true for 1.0s */
+ CHECK_RATE_CTRS(1, 1, 1);
+ ADD_MILLISECS_NO_SELECT(300);
+ /* flag has been true for 1.3s */
+ CHECK_RATE_CTRS(2, 1, 1);
+ FLAG(false);
+ CHECK_RATE_CTRS(2, 1, 1);
+
+ ADD_MILLISECS_NO_SELECT(400);
+
+ CHECK_RATE_CTRS(2, 1, 1);
+ FLAG(true);
+ CHECK_RATE_CTRS(2, 1, 1);
+ ADD_MILLISECS_NO_SELECT(699);
+ /* flag has been true for 1.999s */
+ CHECK_RATE_CTRS(2, 2, 1);
+ ADD_MILLISECS_NO_SELECT(1);
+ /* flag has been true for 2.0s */
+ CHECK_RATE_CTRS(2, 2, 2);
+ ADD_MILLISECS_NO_SELECT(1);
+ /* flag has been true for 2.001s */
+ CHECK_RATE_CTRS(3, 2, 2);
+ ADD_MILLISECS_NO_SELECT(499);
+ /* flag has been true for 2.5s */
+ CHECK_RATE_CTRS(3, 3, 2);
+ ADD_MILLISECS_NO_SELECT(499);
+ /* flag has been true for 2.999s */
+ CHECK_RATE_CTRS(3, 3, 2);
+ ADD_MILLISECS_NO_SELECT(1);
+ /* flag has been true for 3.0s */
+ CHECK_RATE_CTRS(3, 3, 3);
+ ADD_MILLISECS_NO_SELECT(200);
+ /* flag has been true for 3.2s */
+ CHECK_RATE_CTRS(4, 3, 3);
+ FLAG(false);
+ CHECK_RATE_CTRS(4, 3, 3);
+
+ ADD_MILLISECS_NO_SELECT(4321);
+ CHECK_RATE_CTRS(4, 3, 3);
+
+ FLAG(true);
+ CHECK_RATE_CTRS(4, 3, 3);
+ ADD_MILLISECS_NO_SELECT(5678);
+ CHECK_RATE_CTRS(9, 9, 8);
+ FLAG(false);
+ CHECK_RATE_CTRS(9, 9, 8);
+
+ my_obj_destruct(&my_obj);
+ rate_ctr_group_reset(my_ctrg);
+
+
+ printf("\n----------- test forget_sum, when timer cb are invoked late\n\n");
+ osmo_tdef_set(g_my_obj_tdefs, T_FORGET_SUM, 10, OSMO_TDEF_S);
+
+ now->tv_sec = 23000;
+ now->tv_nsec = 0;
+ osmo_gettimeofday_override_time = (struct timeval){23000, 0};
+
+ my_obj_init(&my_obj);
+
+ CHECK_RATE_CTRS(0, 0, 0);
+
+ FLAG(true);
+ /* flag has just turned true the first time */
+ CHECK_RATE_CTRS(0, 0, 0);
+ ADD_MILLISECS_NO_SELECT(100);
+ /* flag has been true for 0.1s */
+ CHECK_RATE_CTRS(1, 0, 0);
+ FLAG(false);
+ CHECK_RATE_CTRS(1, 0, 0);
+
+ ADD_MILLISECS_NO_SELECT(1000);
+ /* 1 s of being false, forget_sum_usec has not yet occurred */
+ CHECK_RATE_CTRS(1, 0, 0);
+
+ ADD_MILLISECS_NO_SELECT(8999);
+ /* 9.999 s of being false, forget_sum_usec has not yet occurred */
+ CHECK_RATE_CTRS(1, 0, 0);
+
+ ADD_MILLISECS_NO_SELECT(1);
+ /* 10 s of being false, forget_sum_usec has occurred */
+ CHECK_RATE_CTRS(1, 0, 0);
+
+ FLAG(true);
+ CHECK_RATE_CTRS(1, 0, 0);
+ ADD_MILLISECS_NO_SELECT(1);
+ /* Since previous sums were forgotton, ceil() triggers again */
+ CHECK_RATE_CTRS(2, 0, 0);
+ /* If the sum had not been forgotten, adding 400 ms to the initial 100 ms would have triggered round(). Verify
+ * that this does not occur, since now full 500 ms are required */
+ ADD_MILLISECS_NO_SELECT(399);
+ CHECK_RATE_CTRS(2, 0, 0);
+ /* Adding another 100 ms will trigger round() */
+ ADD_MILLISECS_NO_SELECT(99);
+ CHECK_RATE_CTRS(2, 0, 0);
+ ADD_MILLISECS_NO_SELECT(1);
+ CHECK_RATE_CTRS(2, 1, 0);
+ /* If the sum had not been forgotten, adding 900 ms to the initial 100 ms would have triggered floor(). Verify
+ * that this does not occur, since now full 1000 ms are required. We already added 500 ms above. */
+ ADD_MILLISECS_NO_SELECT(400);
+ CHECK_RATE_CTRS(2, 1, 0);
+ /* Adding another 100 ms will trigger floor() */
+ ADD_MILLISECS_NO_SELECT(99);
+ CHECK_RATE_CTRS(2, 1, 0);
+ ADD_MILLISECS_NO_SELECT(1);
+ CHECK_RATE_CTRS(2, 1, 1);
+
+ /* Test that durations of false below forget_sum_usec never trigger a forget */
+ ADD_MILLISECS_NO_SELECT(300);
+ CHECK_RATE_CTRS(3, 1, 1);
+ /* internal counter is now at 0.3s above the last reported rate counter */
+ FLAG(false);
+ ADD_MILLISECS_NO_SELECT(9999);
+ FLAG(true);
+ ADD_MILLISECS_NO_SELECT(25);
+ FLAG(false);
+ ADD_MILLISECS_NO_SELECT(9999);
+ FLAG(true);
+ ADD_MILLISECS_NO_SELECT(25);
+ FLAG(false);
+ ADD_MILLISECS_NO_SELECT(9999);
+ FLAG(true);
+ ADD_MILLISECS_NO_SELECT(25);
+ FLAG(false);
+ ADD_MILLISECS_NO_SELECT(9999);
+ FLAG(true);
+ ADD_MILLISECS_NO_SELECT(25);
+ /* internal counter is now at 0.4s above the last reported rate counter */
+ CHECK_RATE_CTRS(3, 1, 1);
+ ADD_MILLISECS_NO_SELECT(100);
+ CHECK_RATE_CTRS(3, 2, 1);
+ ADD_MILLISECS_NO_SELECT(500);
+ CHECK_RATE_CTRS(3, 2, 2);
+
+ my_obj_destruct(&my_obj);
+ rate_ctr_group_reset(my_ctrg);
+
+
+#define SET_TDEFS(gran, round_thresh, forget_sum) do { \
+ osmo_tdef_set(g_my_obj_tdefs, T_GRAN, gran, OSMO_TDEF_MS); \
+ osmo_tdef_set(g_my_obj_tdefs, T_ROUND_THRESH, round_thresh, OSMO_TDEF_MS); \
+ osmo_tdef_set(g_my_obj_tdefs, T_FORGET_SUM, forget_sum, OSMO_TDEF_S); \
+ printf("T_defs: T_gran=%luusec T_round_threshold=%luusec T_forget_sum=%luusec\n", \
+ osmo_tdef_get(g_my_obj_tdefs, T_GRAN, OSMO_TDEF_US, -1), \
+ osmo_tdef_get(g_my_obj_tdefs, T_ROUND_THRESH, OSMO_TDEF_US, -1), \
+ osmo_tdef_get(g_my_obj_tdefs, T_FORGET_SUM, OSMO_TDEF_US, -1)); \
+ } while (0)
+
+ printf("\n----------- test T_defs\n\n");
+ now->tv_sec = 23000;
+ now->tv_nsec = 0;
+ osmo_gettimeofday_override_time = (struct timeval){23000, 0};
+
+ SET_TDEFS(100, 10, 0);
+
+ my_obj_init(&my_obj);
+ CHECK_RATE_CTRS(0, 0, 0);
+
+ ADD_MILLISECS(100);
+ CHECK_RATE_CTRS(0, 0, 0);
+
+ FLAG(true);
+ /* flag has just turned true the first time */
+ CHECK_RATE_CTRS(0, 0, 0);
+ ADD_MILLISECS(9);
+ /* flag has been true for 0.009s */
+ CHECK_RATE_CTRS(1, 0, 0);
+ ADD_MILLISECS(1);
+ /* flag has been true for 0.010s */
+ CHECK_RATE_CTRS(1, 1, 0);
+ ADD_MILLISECS(90);
+ /* flag has been true for 0.1s */
+ CHECK_RATE_CTRS(1, 1, 1);
+
+ SET_TDEFS(200, 190, 1);
+ /* gran is changed to 200ms, but still continues until the next scheduled event until the change is picked up.
+ * For ceil(), it is 1 ms ahead.
+ * For round(), it is 10 ms ahead.
+ * For floor(), it is at the next full (previous) gran 100 ms ahead.
+ * When T_defs change, all internal sums are reset to zero without reporting.
+ */
+ CHECK_RATE_CTRS(1, 1, 1);
+ ADD_MILLISECS(1);
+ /* 1ms elapsed: ceil() picks up the T_gran change, starts anew. */
+ /* elapsed: ceil 0 ms */
+ CHECK_RATE_CTRS(1, 1, 1);
+ ADD_MILLISECS(1);
+ /* elapsed: ceil 1 ms */
+ /* ceil() increments because flag has been true for more than 1 us after reset */
+ CHECK_RATE_CTRS(2, 1, 1);
+ ADD_MILLISECS(8);
+ /* 10 ms elapsed: round() picks up the T_gran change, starts anew */
+ /* elapsed: ceil 9 ms, round 0 ms */
+ CHECK_RATE_CTRS(2, 1, 1);
+ ADD_MILLISECS(90);
+ /* 100 ms elapsed: floor() picks up the T_gran change, starts anew */
+ /* elapsed: ceil 99 ms, round 90 ms, floor 0 ms */
+ CHECK_RATE_CTRS(2, 1, 1);
+ ADD_MILLISECS(99);
+ /* elapsed: ceil 198 ms, round 189 ms, floor 99 ms */
+ CHECK_RATE_CTRS(2, 1, 1);
+ ADD_MILLISECS(1);
+ /* elapsed: ceil 199 ms, round 190 ms, floor 100 ms */
+ CHECK_RATE_CTRS(2, 2, 1);
+ ADD_MILLISECS(1);
+ /* elapsed: ceil 200 ms, round 191 ms, floor 101 ms */
+ CHECK_RATE_CTRS(2, 2, 1);
+ ADD_MILLISECS(1);
+ /* elapsed: ceil 201 ms, round 192 ms, floor 102 ms */
+ CHECK_RATE_CTRS(3, 2, 1);
+ ADD_MILLISECS(98);
+ /* elapsed: ceil 299 ms, round 290 ms, floor 200 ms */
+ CHECK_RATE_CTRS(3, 2, 2);
+ ADD_MILLISECS(99);
+ /* elapsed: ceil 398 ms, round 389 ms, floor 299 ms */
+ CHECK_RATE_CTRS(3, 2, 2);
+ ADD_MILLISECS(1);
+ /* elapsed: ceil 399 ms, round 390 ms, floor 300 ms */
+ CHECK_RATE_CTRS(3, 3, 2);
+ ADD_MILLISECS(1);
+ /* elapsed: ceil 400 ms, round 391 ms, floor 301 ms */
+ CHECK_RATE_CTRS(3, 3, 2);
+ ADD_MILLISECS(1);
+ /* elapsed: ceil 401 ms, round 392 ms, floor 302 ms */
+ CHECK_RATE_CTRS(4, 3, 2);
+ ADD_MILLISECS(98);
+ /* elapsed: ceil 499 ms, round 490 ms, floor 400 ms */
+ CHECK_RATE_CTRS(4, 3, 3);
+
+
+ SET_TDEFS(100, 0, 0);
+ /* T_defs change, but they only get picked up upon the next event:
+ * For ceil(), it is 102 ms ahead.
+ * For round(), it is 100 ms ahead (thresh is still 190, currently at 90).
+ * For floor(), it is 200 ms ahead.
+ * When T_defs change, all internal sums are reset to zero without reporting.
+ */
+ CHECK_RATE_CTRS(4, 3, 3);
+ ADD_MILLISECS(100);
+ CHECK_RATE_CTRS(4, 3, 3);
+ /* round() picks up the new T_defs. Internal sum resets, nothing else happens yet.
+ * round() schedules the next event 50 ms ahead. */
+ ADD_MILLISECS(2);
+ CHECK_RATE_CTRS(4, 3, 3);
+ /* ceil() picks up the change, its next event is 1 ms ahead. */
+ ADD_MILLISECS(1);
+ /* ceil: 0.001
+ * round: 0.003
+ * floor: still 97 ms until it picks up the change */
+ CHECK_RATE_CTRS(5, 3, 3);
+ ADD_MILLISECS(46);
+ CHECK_RATE_CTRS(5, 3, 3);
+ ADD_MILLISECS(1);
+ /* round() has first counter trigger after T_defs change. */
+ CHECK_RATE_CTRS(5, 4, 3);
+ /* ceil: 0.048
+ * round: 0.050
+ * floor: still 50 ms until it picks up the change */
+ ADD_MILLISECS(50);
+ /* floor() picks up the change. nothing happens yet. */
+ /* ceil: 0.098
+ * round: 0.100
+ * floor: 0.0 */
+ ADD_MILLISECS(2);
+ /* ceil: 0.100
+ * round: 0.102
+ * floor: 0.002 */
+ CHECK_RATE_CTRS(5, 4, 3);
+ ADD_MILLISECS(1);
+ /* ceil: 0.101
+ * round: 0.103
+ * floor: 0.003 */
+ CHECK_RATE_CTRS(6, 4, 3);
+ ADD_MILLISECS(46);
+ /* ceil: 0.147
+ * round: 0.149
+ * floor: 0.049 */
+ CHECK_RATE_CTRS(6, 4, 3);
+ ADD_MILLISECS(1);
+ /* ceil: 0.148
+ * round: 0.150
+ * floor: 0.050 */
+ CHECK_RATE_CTRS(6, 5, 3);
+
+ my_obj_destruct(&my_obj);
+ rate_ctr_group_reset(my_ctrg);
+
+ return 0;
+}
diff --git a/tests/time_cc/time_cc_test.ok b/tests/time_cc/time_cc_test.ok
new file mode 100644
index 00000000..ccf84d95
--- /dev/null
+++ b/tests/time_cc/time_cc_test.ok
@@ -0,0 +1,328 @@
+
+----------- cumulating time, without forget_sum
+
+0 CHECK_RATE_CTRS(0, 0, 0) ok
+0 ADD_MILLISECS(100) --> 23000.100
+0 CHECK_RATE_CTRS(0, 0, 0) ok
+ flag: FALSE -> TRUE
+1 CHECK_RATE_CTRS(0, 0, 0) ok
+1 ADD_MILLISECS(1) --> 23000.101
+1 CHECK_RATE_CTRS(1, 0, 0) ok
+1 ADD_MILLISECS(99) --> 23000.200
+1 CHECK_RATE_CTRS(1, 0, 0) ok
+ flag: TRUE -> FALSE
+0 CHECK_RATE_CTRS(1, 0, 0) ok
+0 ADD_MILLISECS(100) --> 23000.300
+0 CHECK_RATE_CTRS(1, 0, 0) ok
+ flag: FALSE -> TRUE
+1 CHECK_RATE_CTRS(1, 0, 0) ok
+1 ADD_MILLISECS(100) --> 23000.400
+1 CHECK_RATE_CTRS(1, 0, 0) ok
+ flag: TRUE -> FALSE
+0 CHECK_RATE_CTRS(1, 0, 0) ok
+0 ADD_MILLISECS(300) --> 23000.700
+0 CHECK_RATE_CTRS(1, 0, 0) ok
+ flag: FALSE -> TRUE
+1 CHECK_RATE_CTRS(1, 0, 0) ok
+1 ADD_MILLISECS(299) --> 23000.999
+1 CHECK_RATE_CTRS(1, 0, 0) ok
+1 ADD_MILLISECS(1) --> 23001.000
+1 CHECK_RATE_CTRS(1, 1, 0) ok
+1 ADD_MILLISECS(499) --> 23001.499
+1 CHECK_RATE_CTRS(1, 1, 0) ok
+1 ADD_MILLISECS(1) --> 23001.500
+1 CHECK_RATE_CTRS(1, 1, 1) ok
+1 ADD_MILLISECS(1) --> 23001.501
+1 CHECK_RATE_CTRS(2, 1, 1) ok
+1 ADD_MILLISECS(299) --> 23001.800
+1 CHECK_RATE_CTRS(2, 1, 1) ok
+ flag: TRUE -> FALSE
+0 CHECK_RATE_CTRS(2, 1, 1) ok
+0 ADD_MILLISECS(400) --> 23002.200
+0 CHECK_RATE_CTRS(2, 1, 1) ok
+ flag: FALSE -> TRUE
+1 CHECK_RATE_CTRS(2, 1, 1) ok
+1 ADD_MILLISECS(199) --> 23002.399
+1 CHECK_RATE_CTRS(2, 1, 1) ok
+1 ADD_MILLISECS(2) --> 23002.401
+1 CHECK_RATE_CTRS(2, 2, 1) ok
+1 ADD_MILLISECS(498) --> 23002.899
+1 CHECK_RATE_CTRS(2, 2, 1) ok
+1 ADD_MILLISECS(2) --> 23002.901
+1 CHECK_RATE_CTRS(3, 2, 2) ok
+1 ADD_MILLISECS(500) --> 23003.401
+1 CHECK_RATE_CTRS(3, 3, 2) ok
+1 ADD_MILLISECS(498) --> 23003.899
+1 CHECK_RATE_CTRS(3, 3, 2) ok
+1 ADD_MILLISECS(3) --> 23003.902
+1 CHECK_RATE_CTRS(4, 3, 3) ok
+1 ADD_MILLISECS(200) --> 23004.102
+1 CHECK_RATE_CTRS(4, 3, 3) ok
+ flag: TRUE -> FALSE
+0 CHECK_RATE_CTRS(4, 3, 3) ok
+0 ADD_MILLISECS(4321) --> 23008.423
+0 CHECK_RATE_CTRS(4, 3, 3) ok
+ flag: FALSE -> TRUE
+1 CHECK_RATE_CTRS(4, 3, 3) ok
+1 ADD_MILLISECS(5678) --> 23014.101
+1 CHECK_RATE_CTRS(9, 9, 8) ok
+ flag: TRUE -> FALSE
+0 CHECK_RATE_CTRS(9, 9, 8) ok
+
+----------- test forget_sum_usec
+
+0 CHECK_RATE_CTRS(0, 0, 0) ok
+ flag: FALSE -> TRUE
+1 CHECK_RATE_CTRS(0, 0, 0) ok
+1 ADD_MILLISECS(100) --> 23000.100
+1 CHECK_RATE_CTRS(1, 0, 0) ok
+ flag: TRUE -> FALSE
+0 CHECK_RATE_CTRS(1, 0, 0) ok
+0 ADD_MILLISECS(1000) --> 23001.100
+0 CHECK_RATE_CTRS(1, 0, 0) ok
+0 ADD_MILLISECS(8999) --> 23010.099
+0 CHECK_RATE_CTRS(1, 0, 0) ok
+0 ADD_MILLISECS(1) --> 23010.100
+0 CHECK_RATE_CTRS(1, 0, 0) ok
+ flag: FALSE -> TRUE
+1 CHECK_RATE_CTRS(1, 0, 0) ok
+1 ADD_MILLISECS(1) --> 23010.101
+1 CHECK_RATE_CTRS(2, 0, 0) ok
+1 ADD_MILLISECS(399) --> 23010.500
+1 CHECK_RATE_CTRS(2, 0, 0) ok
+1 ADD_MILLISECS(99) --> 23010.599
+1 CHECK_RATE_CTRS(2, 0, 0) ok
+1 ADD_MILLISECS(1) --> 23010.600
+1 CHECK_RATE_CTRS(2, 1, 0) ok
+1 ADD_MILLISECS(400) --> 23011.000
+1 CHECK_RATE_CTRS(2, 1, 0) ok
+1 ADD_MILLISECS(99) --> 23011.099
+1 CHECK_RATE_CTRS(2, 1, 0) ok
+1 ADD_MILLISECS(1) --> 23011.100
+1 CHECK_RATE_CTRS(2, 1, 1) ok
+1 ADD_MILLISECS(300) --> 23011.400
+1 CHECK_RATE_CTRS(3, 1, 1) ok
+ flag: TRUE -> FALSE
+0 ADD_MILLISECS(9999) --> 23021.399
+ flag: FALSE -> TRUE
+1 ADD_MILLISECS(25) --> 23021.424
+ flag: TRUE -> FALSE
+0 ADD_MILLISECS(9999) --> 23031.423
+ flag: FALSE -> TRUE
+1 ADD_MILLISECS(25) --> 23031.448
+ flag: TRUE -> FALSE
+0 ADD_MILLISECS(9999) --> 23041.447
+ flag: FALSE -> TRUE
+1 ADD_MILLISECS(25) --> 23041.472
+ flag: TRUE -> FALSE
+0 ADD_MILLISECS(9999) --> 23051.471
+ flag: FALSE -> TRUE
+1 ADD_MILLISECS(25) --> 23051.496
+1 CHECK_RATE_CTRS(3, 1, 1) ok
+1 ADD_MILLISECS(100) --> 23051.596
+1 CHECK_RATE_CTRS(3, 2, 1) ok
+1 ADD_MILLISECS(500) --> 23052.096
+1 CHECK_RATE_CTRS(3, 2, 2) ok
+1 ADD_MILLISECS(300) --> 23052.396
+1 CHECK_RATE_CTRS(4, 2, 2) ok
+ flag: TRUE -> FALSE
+0 ADD_MILLISECS(5000) --> 23057.396
+ flag: FALSE -> FALSE
+0 ADD_MILLISECS(5000) --> 23062.396
+0 CHECK_RATE_CTRS(4, 2, 2) ok
+ flag: FALSE -> TRUE
+1 ADD_MILLISECS(1) --> 23062.397
+1 CHECK_RATE_CTRS(5, 2, 2) ok
+1 ADD_MILLISECS(199) --> 23062.596
+1 CHECK_RATE_CTRS(5, 2, 2) ok
+1 ADD_MILLISECS(299) --> 23062.895
+1 CHECK_RATE_CTRS(5, 2, 2) ok
+1 ADD_MILLISECS(1) --> 23062.896
+1 CHECK_RATE_CTRS(5, 3, 2) ok
+1 ADD_MILLISECS(200) --> 23063.096
+1 CHECK_RATE_CTRS(5, 3, 2) ok
+1 ADD_MILLISECS(299) --> 23063.395
+1 CHECK_RATE_CTRS(5, 3, 2) ok
+1 ADD_MILLISECS(1) --> 23063.396
+1 CHECK_RATE_CTRS(5, 3, 3) ok
+
+----------- cumulating time, without forget_sum, when timer cb are invoked late
+
+0 CHECK_RATE_CTRS(0, 0, 0) ok
+0 ADD_MILLISECS(100) --> 23000.100
+0 CHECK_RATE_CTRS(0, 0, 0) ok
+ flag: FALSE -> TRUE
+1 CHECK_RATE_CTRS(0, 0, 0) ok
+1 ADD_MILLISECS(100) --> 23000.200
+1 CHECK_RATE_CTRS(1, 0, 0) ok
+ flag: TRUE -> FALSE
+0 CHECK_RATE_CTRS(1, 0, 0) ok
+0 ADD_MILLISECS(100) --> 23000.300
+0 CHECK_RATE_CTRS(1, 0, 0) ok
+ flag: FALSE -> TRUE
+1 CHECK_RATE_CTRS(1, 0, 0) ok
+1 ADD_MILLISECS(100) --> 23000.400
+1 CHECK_RATE_CTRS(1, 0, 0) ok
+ flag: TRUE -> FALSE
+0 CHECK_RATE_CTRS(1, 0, 0) ok
+0 ADD_MILLISECS(300) --> 23000.700
+0 CHECK_RATE_CTRS(1, 0, 0) ok
+ flag: FALSE -> TRUE
+1 CHECK_RATE_CTRS(1, 0, 0) ok
+1 ADD_MILLISECS(799) --> 23001.499
+1 CHECK_RATE_CTRS(1, 1, 0) ok
+1 ADD_MILLISECS(1) --> 23001.500
+1 CHECK_RATE_CTRS(1, 1, 1) ok
+1 ADD_MILLISECS(300) --> 23001.800
+1 CHECK_RATE_CTRS(2, 1, 1) ok
+ flag: TRUE -> FALSE
+0 CHECK_RATE_CTRS(2, 1, 1) ok
+0 ADD_MILLISECS(400) --> 23002.200
+0 CHECK_RATE_CTRS(2, 1, 1) ok
+ flag: FALSE -> TRUE
+1 CHECK_RATE_CTRS(2, 1, 1) ok
+1 ADD_MILLISECS(699) --> 23002.899
+1 CHECK_RATE_CTRS(2, 2, 1) ok
+1 ADD_MILLISECS(1) --> 23002.900
+1 CHECK_RATE_CTRS(2, 2, 2) ok
+1 ADD_MILLISECS(1) --> 23002.901
+1 CHECK_RATE_CTRS(3, 2, 2) ok
+1 ADD_MILLISECS(499) --> 23003.400
+1 CHECK_RATE_CTRS(3, 3, 2) ok
+1 ADD_MILLISECS(499) --> 23003.899
+1 CHECK_RATE_CTRS(3, 3, 2) ok
+1 ADD_MILLISECS(1) --> 23003.900
+1 CHECK_RATE_CTRS(3, 3, 3) ok
+1 ADD_MILLISECS(200) --> 23004.100
+1 CHECK_RATE_CTRS(4, 3, 3) ok
+ flag: TRUE -> FALSE
+0 CHECK_RATE_CTRS(4, 3, 3) ok
+0 ADD_MILLISECS(4321) --> 23008.421
+0 CHECK_RATE_CTRS(4, 3, 3) ok
+ flag: FALSE -> TRUE
+1 CHECK_RATE_CTRS(4, 3, 3) ok
+1 ADD_MILLISECS(5678) --> 23014.099
+1 CHECK_RATE_CTRS(9, 9, 8) ok
+ flag: TRUE -> FALSE
+0 CHECK_RATE_CTRS(9, 9, 8) ok
+
+----------- test forget_sum, when timer cb are invoked late
+
+0 CHECK_RATE_CTRS(0, 0, 0) ok
+ flag: FALSE -> TRUE
+1 CHECK_RATE_CTRS(0, 0, 0) ok
+1 ADD_MILLISECS(100) --> 23000.100
+1 CHECK_RATE_CTRS(1, 0, 0) ok
+ flag: TRUE -> FALSE
+0 CHECK_RATE_CTRS(1, 0, 0) ok
+0 ADD_MILLISECS(1000) --> 23001.100
+0 CHECK_RATE_CTRS(1, 0, 0) ok
+0 ADD_MILLISECS(8999) --> 23010.099
+0 CHECK_RATE_CTRS(1, 0, 0) ok
+0 ADD_MILLISECS(1) --> 23010.100
+0 CHECK_RATE_CTRS(1, 0, 0) ok
+ flag: FALSE -> TRUE
+1 CHECK_RATE_CTRS(1, 0, 0) ok
+1 ADD_MILLISECS(1) --> 23010.101
+1 CHECK_RATE_CTRS(2, 0, 0) ok
+1 ADD_MILLISECS(399) --> 23010.500
+1 CHECK_RATE_CTRS(2, 0, 0) ok
+1 ADD_MILLISECS(99) --> 23010.599
+1 CHECK_RATE_CTRS(2, 0, 0) ok
+1 ADD_MILLISECS(1) --> 23010.600
+1 CHECK_RATE_CTRS(2, 1, 0) ok
+1 ADD_MILLISECS(400) --> 23011.000
+1 CHECK_RATE_CTRS(2, 1, 0) ok
+1 ADD_MILLISECS(99) --> 23011.099
+1 CHECK_RATE_CTRS(2, 1, 0) ok
+1 ADD_MILLISECS(1) --> 23011.100
+1 CHECK_RATE_CTRS(2, 1, 1) ok
+1 ADD_MILLISECS(300) --> 23011.400
+1 CHECK_RATE_CTRS(3, 1, 1) ok
+ flag: TRUE -> FALSE
+0 ADD_MILLISECS(9999) --> 23021.399
+ flag: FALSE -> TRUE
+1 ADD_MILLISECS(25) --> 23021.424
+ flag: TRUE -> FALSE
+0 ADD_MILLISECS(9999) --> 23031.423
+ flag: FALSE -> TRUE
+1 ADD_MILLISECS(25) --> 23031.448
+ flag: TRUE -> FALSE
+0 ADD_MILLISECS(9999) --> 23041.447
+ flag: FALSE -> TRUE
+1 ADD_MILLISECS(25) --> 23041.472
+ flag: TRUE -> FALSE
+0 ADD_MILLISECS(9999) --> 23051.471
+ flag: FALSE -> TRUE
+1 ADD_MILLISECS(25) --> 23051.496
+1 CHECK_RATE_CTRS(3, 1, 1) ok
+1 ADD_MILLISECS(100) --> 23051.596
+1 CHECK_RATE_CTRS(3, 2, 1) ok
+1 ADD_MILLISECS(500) --> 23052.096
+1 CHECK_RATE_CTRS(3, 2, 2) ok
+
+----------- test T_defs
+
+T_defs: T_gran=100000usec T_round_threshold=10000usec T_forget_sum=0usec
+0 CHECK_RATE_CTRS(0, 0, 0) ok
+0 ADD_MILLISECS(100) --> 23000.100
+0 CHECK_RATE_CTRS(0, 0, 0) ok
+ flag: FALSE -> TRUE
+1 CHECK_RATE_CTRS(0, 0, 0) ok
+1 ADD_MILLISECS(9) --> 23000.109
+1 CHECK_RATE_CTRS(1, 0, 0) ok
+1 ADD_MILLISECS(1) --> 23000.110
+1 CHECK_RATE_CTRS(1, 1, 0) ok
+1 ADD_MILLISECS(90) --> 23000.200
+1 CHECK_RATE_CTRS(1, 1, 1) ok
+T_defs: T_gran=200000usec T_round_threshold=190000usec T_forget_sum=1000000usec
+1 CHECK_RATE_CTRS(1, 1, 1) ok
+1 ADD_MILLISECS(1) --> 23000.201
+1 CHECK_RATE_CTRS(1, 1, 1) ok
+1 ADD_MILLISECS(1) --> 23000.202
+1 CHECK_RATE_CTRS(2, 1, 1) ok
+1 ADD_MILLISECS(8) --> 23000.210
+1 CHECK_RATE_CTRS(2, 1, 1) ok
+1 ADD_MILLISECS(90) --> 23000.300
+1 CHECK_RATE_CTRS(2, 1, 1) ok
+1 ADD_MILLISECS(99) --> 23000.399
+1 CHECK_RATE_CTRS(2, 1, 1) ok
+1 ADD_MILLISECS(1) --> 23000.400
+1 CHECK_RATE_CTRS(2, 2, 1) ok
+1 ADD_MILLISECS(1) --> 23000.401
+1 CHECK_RATE_CTRS(2, 2, 1) ok
+1 ADD_MILLISECS(1) --> 23000.402
+1 CHECK_RATE_CTRS(3, 2, 1) ok
+1 ADD_MILLISECS(98) --> 23000.500
+1 CHECK_RATE_CTRS(3, 2, 2) ok
+1 ADD_MILLISECS(99) --> 23000.599
+1 CHECK_RATE_CTRS(3, 2, 2) ok
+1 ADD_MILLISECS(1) --> 23000.600
+1 CHECK_RATE_CTRS(3, 3, 2) ok
+1 ADD_MILLISECS(1) --> 23000.601
+1 CHECK_RATE_CTRS(3, 3, 2) ok
+1 ADD_MILLISECS(1) --> 23000.602
+1 CHECK_RATE_CTRS(4, 3, 2) ok
+1 ADD_MILLISECS(98) --> 23000.700
+1 CHECK_RATE_CTRS(4, 3, 3) ok
+T_defs: T_gran=100000usec T_round_threshold=0usec T_forget_sum=0usec
+1 CHECK_RATE_CTRS(4, 3, 3) ok
+1 ADD_MILLISECS(100) --> 23000.800
+1 CHECK_RATE_CTRS(4, 3, 3) ok
+1 ADD_MILLISECS(2) --> 23000.802
+1 CHECK_RATE_CTRS(4, 3, 3) ok
+1 ADD_MILLISECS(1) --> 23000.803
+1 CHECK_RATE_CTRS(5, 3, 3) ok
+1 ADD_MILLISECS(46) --> 23000.849
+1 CHECK_RATE_CTRS(5, 3, 3) ok
+1 ADD_MILLISECS(1) --> 23000.850
+1 CHECK_RATE_CTRS(5, 4, 3) ok
+1 ADD_MILLISECS(50) --> 23000.900
+1 ADD_MILLISECS(2) --> 23000.902
+1 CHECK_RATE_CTRS(5, 4, 3) ok
+1 ADD_MILLISECS(1) --> 23000.903
+1 CHECK_RATE_CTRS(6, 4, 3) ok
+1 ADD_MILLISECS(46) --> 23000.949
+1 CHECK_RATE_CTRS(6, 4, 3) ok
+1 ADD_MILLISECS(1) --> 23000.950
+1 CHECK_RATE_CTRS(6, 5, 3) ok
diff --git a/tests/timer/clk_override_test.c b/tests/timer/clk_override_test.c
index 308e8212..e67a6ed6 100644
--- a/tests/timer/clk_override_test.c
+++ b/tests/timer/clk_override_test.c
@@ -16,10 +16,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <stdio.h>
diff --git a/tests/timer/timer_test.c b/tests/timer/timer_test.c
index d2b0204d..9c51ad94 100644
--- a/tests/timer/timer_test.c
+++ b/tests/timer/timer_test.c
@@ -16,10 +16,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <stdio.h>
@@ -33,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 925d7628..f9137ad3 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,6 +332,128 @@ static void test_tlv_encoder()
msgb_free(msg);
}
+static void test_tlv_parser_bounds(void)
+{
+ struct tlv_definition tdef;
+ struct tlv_parsed dec;
+ uint8_t buf[32];
+
+ memset(&tdef, 0, sizeof(tdef));
+
+ printf("Testing TLV_TYPE_T decoder for out-of-bounds\n");
+ tdef.def[0x23].type = TLV_TYPE_T;
+ buf[0] = 0x23;
+ OSMO_ASSERT(tlv_parse(&dec, &tdef, buf, 1, 0, 0) == 1);
+ OSMO_ASSERT(tlv_parse(&dec, &tdef, buf, 0, 0, 0) == 0);
+
+ printf("Testing TLV_TYPE_TV decoder for out-of-bounds\n");
+ tdef.def[0x23].type = TLV_TYPE_TV;
+ buf[0] = 0x23;
+ buf[1] = 0x42;
+ OSMO_ASSERT(tlv_parse(&dec, &tdef, buf, 2, 0, 0) == 1);
+ OSMO_ASSERT(*TLVP_VAL(&dec, 0x23) == buf[1]);
+ OSMO_ASSERT(tlv_parse(&dec, &tdef, buf, 1, 0, 0) == OSMO_TLVP_ERR_OFS_LEN_BEYOND_BUFFER);
+ OSMO_ASSERT(TLVP_VAL(&dec, 0x23) == NULL);
+
+ printf("Testing TLV_TYPE_FIXED decoder for out-of-bounds\n");
+ tdef.def[0x23].type = TLV_TYPE_FIXED;
+ tdef.def[0x23].fixed_len = 2;
+ buf[0] = 0x23;
+ buf[1] = 0x42;
+ buf[2] = 0x55;
+ OSMO_ASSERT(tlv_parse(&dec, &tdef, buf, 3, 0, 0) == 1);
+ OSMO_ASSERT(tlv_parse(&dec, &tdef, buf, 2, 0, 0) == OSMO_TLVP_ERR_OFS_LEN_BEYOND_BUFFER);
+ OSMO_ASSERT(TLVP_VAL(&dec, 0x23) == NULL);
+
+ printf("Testing TLV_TYPE_TLV decoder for out-of-bounds\n");
+ tdef.def[0x23].type = TLV_TYPE_TLV;
+ buf[0] = 0x23;
+ buf[1] = 0x02;
+ buf[2] = 0x55;
+ buf[3] = 0xAA;
+ OSMO_ASSERT(tlv_parse(&dec, &tdef, buf, 4, 0, 0) == 1);
+ OSMO_ASSERT(TLVP_VAL(&dec, 0x23) == &buf[2]);
+ OSMO_ASSERT(tlv_parse(&dec, &tdef, buf, 3, 0, 0) == OSMO_TLVP_ERR_OFS_LEN_BEYOND_BUFFER);
+ OSMO_ASSERT(TLVP_VAL(&dec, 0x23) == NULL);
+ OSMO_ASSERT(tlv_parse(&dec, &tdef, buf, 2, 0, 0) == OSMO_TLVP_ERR_OFS_LEN_BEYOND_BUFFER);
+ OSMO_ASSERT(TLVP_VAL(&dec, 0x23) == NULL);
+ OSMO_ASSERT(tlv_parse(&dec, &tdef, buf, 1, 0, 0) == OSMO_TLVP_ERR_OFS_BEYOND_BUFFER);
+ OSMO_ASSERT(TLVP_VAL(&dec, 0x23) == NULL);
+
+ printf("Testing TLV_TYPE_vTvLV_GAN decoder for out-of-bounds\n");
+ tdef.def[0x23].type = TLV_TYPE_vTvLV_GAN;
+ buf[0] = 0x23;
+ buf[1] = 0x80;
+ buf[2] = 0x01;
+ buf[3] = 0xAA;
+ OSMO_ASSERT(tlv_parse(&dec, &tdef, buf, 4, 0, 0) == 1);
+ OSMO_ASSERT(TLVP_VAL(&dec, 0x23) == &buf[3]);
+ OSMO_ASSERT(tlv_parse(&dec, &tdef, buf, 3, 0, 0) == OSMO_TLVP_ERR_OFS_LEN_BEYOND_BUFFER);
+ OSMO_ASSERT(TLVP_VAL(&dec, 0x23) == NULL);
+ OSMO_ASSERT(tlv_parse(&dec, &tdef, buf, 2, 0, 0) == OSMO_TLVP_ERR_OFS_BEYOND_BUFFER);
+ OSMO_ASSERT(TLVP_VAL(&dec, 0x23) == NULL);
+ OSMO_ASSERT(tlv_parse(&dec, &tdef, buf, 1, 0, 0) == OSMO_TLVP_ERR_OFS_BEYOND_BUFFER);
+ OSMO_ASSERT(TLVP_VAL(&dec, 0x23) == NULL);
+
+ printf("Testing TLV_TYPE_TvLV decoder for out-of-bounds\n");
+ tdef.def[0x23].type = TLV_TYPE_TvLV;
+ buf[0] = 0x23;
+ buf[1] = 0x81;
+ buf[2] = 0xAA;
+ OSMO_ASSERT(tlv_parse(&dec, &tdef, buf, 3, 0, 0) == 1);
+ OSMO_ASSERT(TLVP_VAL(&dec, 0x23) == &buf[2]);
+ OSMO_ASSERT(tlv_parse(&dec, &tdef, buf, 2, 0, 0) == OSMO_TLVP_ERR_OFS_LEN_BEYOND_BUFFER);
+ OSMO_ASSERT(TLVP_VAL(&dec, 0x23) == NULL);
+ OSMO_ASSERT(tlv_parse(&dec, &tdef, buf, 1, 0, 0) == OSMO_TLVP_ERR_OFS_BEYOND_BUFFER);
+ OSMO_ASSERT(TLVP_VAL(&dec, 0x23) == NULL);
+
+ printf("Testing TLV_TYPE_TL16V decoder for out-of-bounds\n");
+ tdef.def[0x23].type = TLV_TYPE_TL16V;
+ buf[0] = 0x23;
+ buf[1] = 0x00;
+ buf[2] = 0x01;
+ buf[3] = 0xAA;
+ OSMO_ASSERT(tlv_parse(&dec, &tdef, buf, 4, 0, 0) == 1);
+ OSMO_ASSERT(TLVP_VAL(&dec, 0x23) == &buf[3]);
+ OSMO_ASSERT(tlv_parse(&dec, &tdef, buf, 3, 0, 0) == OSMO_TLVP_ERR_OFS_LEN_BEYOND_BUFFER);
+ OSMO_ASSERT(TLVP_VAL(&dec, 0x23) == NULL);
+ OSMO_ASSERT(tlv_parse(&dec, &tdef, buf, 2, 0, 0) == OSMO_TLVP_ERR_OFS_BEYOND_BUFFER);
+ OSMO_ASSERT(TLVP_VAL(&dec, 0x23) == NULL);
+ OSMO_ASSERT(tlv_parse(&dec, &tdef, buf, 1, 0, 0) == OSMO_TLVP_ERR_OFS_BEYOND_BUFFER);
+ OSMO_ASSERT(TLVP_VAL(&dec, 0x23) == NULL);
+}
+
+static void test_tlv_lens(void)
+{
+ uint16_t buf_len;
+ uint8_t buf[512];
+ uint8_t val[512] = { 0 };
+ uint16_t x;
+
+
+ for (x = 0; x < 16; x++) {
+ buf_len = lv_put(buf, x, val) - buf;
+ OSMO_ASSERT(buf_len == LV_GROSS_LEN(x));
+ buf_len = tlv_put(buf, 0x23, x, val) - buf;
+ OSMO_ASSERT(buf_len == TLV_GROSS_LEN(x));
+ buf_len = tlv16_put(buf, 0x23, x, (uint16_t *) val) - buf;
+ OSMO_ASSERT(buf_len == TLV16_GROSS_LEN(x));
+ buf_len = tl16v_put(buf, 0x23, x, val) - buf;
+ OSMO_ASSERT(buf_len == TL16V_GROSS_LEN(x));
+ buf_len = t16lv_put(buf, 0x2342, x, val) - buf;
+ OSMO_ASSERT(buf_len == T16LV_GROSS_LEN(x));
+ buf_len = tvlv_put(buf, 0x23, x, val) - buf;
+ OSMO_ASSERT(buf_len == TVLV_GROSS_LEN(x));
+ }
+
+ for (x = 250; x < 300; x++) {
+ buf_len = tl16v_put(buf, 0x23, x, val) - buf;
+ OSMO_ASSERT(buf_len == TL16V_GROSS_LEN(x));
+ buf_len = tvlv_put(buf, 0x23, x, val) - buf;
+ OSMO_ASSERT(buf_len == TVLV_GROSS_LEN(x));
+ }
+}
+
int main(int argc, char **argv)
{
//osmo_init_logging2(ctx, &info);
@@ -339,6 +461,8 @@ int main(int argc, char **argv)
test_tlv_shift_functions();
test_tlv_repeated_ie();
test_tlv_encoder();
+ test_tlv_parser_bounds();
+ test_tlv_lens();
printf("Done.\n");
return EXIT_SUCCESS;
diff --git a/tests/tlv/tlv_test.ok b/tests/tlv/tlv_test.ok
index f3f0fd41..e24b889b 100644
--- a/tests/tlv/tlv_test.ok
+++ b/tests/tlv/tlv_test.ok
@@ -1,4 +1,11 @@
Test shift functions
Testing TLV encoder by decoding + re-encoding binary
Testing TLV encoder with IE ordering
+Testing TLV_TYPE_T decoder for out-of-bounds
+Testing TLV_TYPE_TV decoder for out-of-bounds
+Testing TLV_TYPE_FIXED decoder for out-of-bounds
+Testing TLV_TYPE_TLV decoder for out-of-bounds
+Testing TLV_TYPE_vTvLV_GAN decoder for out-of-bounds
+Testing TLV_TYPE_TvLV decoder for out-of-bounds
+Testing TLV_TYPE_TL16V decoder for out-of-bounds
Done.
diff --git a/tests/use_count/use_count_test.c b/tests/use_count/use_count_test.c
index 95af3082..b784aeb4 100644
--- a/tests/use_count/use_count_test.c
+++ b/tests/use_count/use_count_test.c
@@ -17,10 +17,6 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <stdio.h>
@@ -196,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;
@@ -208,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/use_count/use_count_test.err b/tests/use_count/use_count_test.err
index 97e74a51..29ebfb73 100644
--- a/tests/use_count/use_count_test.err
+++ b/tests/use_count/use_count_test.err
@@ -11,9 +11,9 @@ c: 0 (-)
3 foos
A few gets and puts, logging source file information
-DFOO NOTICE foo(a){IN_USE}: a +1 barring: now used by 1 (barring) (use_count_test.c:223)
-DFOO NOTICE foo(b){IN_USE}: b +1 barring: now used by 1 (barring) (use_count_test.c:225)
-DFOO NOTICE foo(b){IN_USE}: b +1 fighting: now used by 2 (barring,fighting) (use_count_test.c:226)
+DFOO NOTICE foo(a){IN_USE}: a +1 barring: now used by 1 (barring) (use_count_test.c:219)
+DFOO NOTICE foo(b){IN_USE}: b +1 barring: now used by 1 (barring) (use_count_test.c:221)
+DFOO NOTICE foo(b){IN_USE}: b +1 fighting: now used by 2 (barring,fighting) (use_count_test.c:222)
all use counts:
a: 1 (barring)
@@ -22,7 +22,7 @@ c: 0 (-)
3 foos
Attempt to get more than one on limited 'barring' user:
-DFOO ERROR foo(b){IN_USE}: Attempt to get more than one barring (use_count_test.c:231)
+DFOO ERROR foo(b){IN_USE}: Attempt to get more than one barring (use_count_test.c:227)
osmo_use_count_get_put(b, barring, 1) returned error: -34 Numerical result out of range
all use counts:
@@ -32,7 +32,7 @@ c: 0 (-)
3 foos
Put away one user of b
-DFOO NOTICE foo(b){IN_USE}: b -1 barring: now used by 1 (fighting) (use_count_test.c:235)
+DFOO NOTICE foo(b){IN_USE}: b -1 barring: now used by 1 (fighting) (use_count_test.c:231)
all use counts:
a: 1 (barring)
diff --git a/tests/ussd/ussd_test.c b/tests/ussd/ussd_test.c
index 80250711..2b8321d3 100644
--- a/tests/ussd/ussd_test.c
+++ b/tests/ussd/ussd_test.c
@@ -13,10 +13,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <osmocom/core/application.h>
diff --git a/tests/utils/utils_test.c b/tests/utils/utils_test.c
index 9ff023bf..0b7bfe44 100644
--- a/tests/utils/utils_test.c
+++ b/tests/utils/utils_test.c
@@ -14,10 +14,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <osmocom/gsm/ipa.h>
@@ -34,6 +30,7 @@
#include <arpa/inet.h>
#include <errno.h>
#include <limits.h>
+#include <inttypes.h>
static void hexdump_test(void)
{
@@ -345,7 +342,7 @@ static struct {
{ "DeafBeddedBabeAcceededFadedDecaff", 32, 32, false, false },
};
-bool test_is_hexstr()
+bool test_is_hexstr(void)
{
int i;
bool pass = true;
@@ -769,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;
@@ -1026,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];
@@ -1200,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;
@@ -1233,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) };
@@ -1259,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);
@@ -1304,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",
@@ -1443,6 +1499,637 @@ static void osmo_strnchr_test(void)
}
}
+struct float_str_to_int_test {
+ unsigned int precision;
+ const char *str;
+ int64_t expect_val;
+ int expect_err;
+};
+struct float_str_to_int_test float_str_to_int_tests[] = {
+ { 0, "0", 0 },
+ { 0, "1", 1 },
+ { 0, "12.345", 12 },
+ { 0, "+12.345", 12 },
+ { 0, "-12.345", -12 },
+ { 0, "0.345", 0 },
+ { 0, ".345", 0 },
+ { 0, "-0.345", 0 },
+ { 0, "-.345", 0 },
+ { 0, "12.", 12 },
+ { 0, "-180", -180 },
+ { 0, "180", 180 },
+ { 0, "360", 360 },
+ { 0, "123.4567890123", 123 },
+ { 0, "123.4567890123456789012345", 123 },
+ { 0, "9223372036854775807", 9223372036854775807LL },
+ { 0, "-9223372036854775807", -9223372036854775807LL },
+ { 0, "-9223372036854775808", .expect_err = -ERANGE },
+ { 0, "9223372036854775808", .expect_err = -ERANGE },
+ { 0, "-9223372036854775809", .expect_err = -ERANGE },
+ { 0, "100000000000000000000", .expect_err = -ERANGE },
+ { 0, "-100000000000000000000", .expect_err = -ERANGE },
+ { 0, "999999999999999999999999999.99", .expect_err = -ERANGE },
+ { 0, "-999999999999999999999999999.99", .expect_err = -ERANGE },
+ { 0, "1.2.3", .expect_err = -EINVAL },
+ { 0, "foo", .expect_err = -EINVAL },
+ { 0, "1.foo", .expect_err = -EINVAL },
+ { 0, "1.foo", .expect_err = -EINVAL },
+ { 0, "12.-345", .expect_err = -EINVAL },
+ { 0, "-12.-345", .expect_err = -EINVAL },
+ { 0, "12.+345", .expect_err = -EINVAL },
+ { 0, "+12.+345", .expect_err = -EINVAL },
+ { 0, "", .expect_err = -EINVAL },
+ { 0, NULL, .expect_err = -EINVAL },
+
+ { 1, "0", 0 },
+ { 1, "1", 10 },
+ { 1, "12.345", 123 },
+ { 1, "+12.345", 123 },
+ { 1, "-12.345", -123 },
+ { 1, "0.345", 3 },
+ { 1, ".345", 3 },
+ { 1, "-0.345", -3 },
+ { 1, "-.345", -3 },
+ { 1, "12.", 120 },
+ { 1, "-180", -1800 },
+ { 1, "180", 1800 },
+ { 1, "360", 3600 },
+ { 1, "123.4567890123", 1234 },
+ { 1, "123.4567890123456789012345", 1234 },
+ { 1, "922337203685477580.7", 9223372036854775807LL },
+ { 1, "-922337203685477580.7", -9223372036854775807LL },
+ { 1, "-922337203685477580.8", .expect_err = -ERANGE },
+ { 1, "922337203685477580.8", .expect_err = -ERANGE },
+ { 1, "-922337203685477580.9", .expect_err = -ERANGE },
+ { 1, "100000000000000000000", .expect_err = -ERANGE },
+ { 1, "-100000000000000000000", .expect_err = -ERANGE },
+ { 1, "999999999999999999999999999.99", .expect_err = -ERANGE },
+ { 1, "-999999999999999999999999999.99", .expect_err = -ERANGE },
+ { 1, "1.2.3", .expect_err = -EINVAL },
+ { 1, "foo", .expect_err = -EINVAL },
+ { 1, "1.foo", .expect_err = -EINVAL },
+ { 1, "1.foo", .expect_err = -EINVAL },
+ { 1, "12.-345", .expect_err = -EINVAL },
+ { 1, "-12.-345", .expect_err = -EINVAL },
+ { 1, "12.+345", .expect_err = -EINVAL },
+ { 1, "+12.+345", .expect_err = -EINVAL },
+ { 1, "", .expect_err = -EINVAL },
+ { 1, NULL, .expect_err = -EINVAL },
+
+ { 6, "0", 0 },
+ { 6, "1", 1000000 },
+ { 6, "12.345", 12345000 },
+ { 6, "+12.345", 12345000 },
+ { 6, "-12.345", -12345000 },
+ { 6, "0.345", 345000 },
+ { 6, ".345", 345000 },
+ { 6, "-0.345", -345000 },
+ { 6, "-.345", -345000 },
+ { 6, "12.", 12000000 },
+ { 6, "-180", -180000000 },
+ { 6, "180", 180000000 },
+ { 6, "360", 360000000 },
+ { 6, "123.4567890123", 123456789 },
+ { 6, "123.4567890123456789012345", 123456789 },
+ { 6, "9223372036854.775807", 9223372036854775807LL },
+ { 6, "-9223372036854.775807", -9223372036854775807LL },
+ { 6, "-9223372036854.775808", .expect_err = -ERANGE },
+ { 6, "9223372036854.775808", .expect_err = -ERANGE },
+ { 6, "-9223372036854.775809", .expect_err = -ERANGE },
+ { 6, "100000000000000000000", .expect_err = -ERANGE },
+ { 6, "-100000000000000000000", .expect_err = -ERANGE },
+ { 6, "999999999999999999999999999.99", .expect_err = -ERANGE },
+ { 6, "-999999999999999999999999999.99", .expect_err = -ERANGE },
+ { 6, "1.2.3", .expect_err = -EINVAL },
+ { 6, "foo", .expect_err = -EINVAL },
+ { 6, "1.foo", .expect_err = -EINVAL },
+ { 6, "1.foo", .expect_err = -EINVAL },
+ { 6, "12.-345", .expect_err = -EINVAL },
+ { 6, "-12.-345", .expect_err = -EINVAL },
+ { 6, "12.+345", .expect_err = -EINVAL },
+ { 6, "+12.+345", .expect_err = -EINVAL },
+ { 6, "", .expect_err = -EINVAL },
+ { 6, NULL, .expect_err = -EINVAL },
+
+ { 18, "0", 0 },
+ { 18, "1", 1000000000000000000LL },
+ { 18, "1.2345", 1234500000000000000LL },
+ { 18, "+1.2345", 1234500000000000000LL },
+ { 18, "-1.2345", -1234500000000000000LL },
+ { 18, "0.345", 345000000000000000LL },
+ { 18, ".345", 345000000000000000LL },
+ { 18, "-0.345", -345000000000000000LL },
+ { 18, "-.345", -345000000000000000LL },
+ { 18, "2.", 2000000000000000000LL },
+ { 18, "-8", -8000000000000000000LL },
+ { 18, "1.234567890123", 1234567890123000000LL },
+ { 18, "1.234567890123456789012345", 1234567890123456789LL },
+ { 18, "123.4567890123", .expect_err = -ERANGE },
+ { 18, "9.223372036854775807", 9223372036854775807LL },
+ { 18, "-9.223372036854775807", -9223372036854775807LL },
+ { 18, "-9.223372036854775808", .expect_err = -ERANGE },
+ { 18, "9.223372036854775808", .expect_err = -ERANGE },
+ { 18, "-9.223372036854775809", .expect_err = -ERANGE },
+ { 18, "100000000000000000000", .expect_err = -ERANGE },
+ { 18, "-100000000000000000000", .expect_err = -ERANGE },
+ { 18, "999999999999999999999999999.99", .expect_err = -ERANGE },
+ { 18, "-999999999999999999999999999.99", .expect_err = -ERANGE },
+ { 18, "1.2.3", .expect_err = -EINVAL },
+ { 18, "foo", .expect_err = -EINVAL },
+ { 18, "1.foo", .expect_err = -EINVAL },
+ { 18, "1.foo", .expect_err = -EINVAL },
+ { 18, "12.-345", .expect_err = -EINVAL },
+ { 18, "-12.-345", .expect_err = -EINVAL },
+ { 18, "12.+345", .expect_err = -EINVAL },
+ { 18, "+12.+345", .expect_err = -EINVAL },
+ { 18, "", .expect_err = -EINVAL },
+ { 18, NULL, .expect_err = -EINVAL },
+
+ { 19, "0", 0 },
+ { 19, ".1", 1000000000000000000LL },
+ { 19, ".12345", 1234500000000000000LL },
+ { 19, "+.12345", 1234500000000000000LL },
+ { 19, "-.12345", -1234500000000000000LL },
+ { 19, "0.0345", 345000000000000000LL },
+ { 19, ".0345", 345000000000000000LL },
+ { 19, "-0.0345", -345000000000000000LL },
+ { 19, "-.0345", -345000000000000000LL },
+ { 19, ".2", 2000000000000000000LL },
+ { 19, "-.8", -8000000000000000000LL },
+ { 19, ".1234567890123", 1234567890123000000LL },
+ { 19, ".1234567890123456789012345", 1234567890123456789LL },
+ { 19, "123.4567890123", .expect_err = -ERANGE },
+ { 19, ".9223372036854775807", 9223372036854775807LL },
+ { 19, "-.9223372036854775807", -9223372036854775807LL },
+ { 19, "-.9223372036854775808", .expect_err = -ERANGE },
+ { 19, ".9223372036854775808", .expect_err = -ERANGE },
+ { 19, "-.9223372036854775809", .expect_err = -ERANGE },
+ { 19, "100000000000000000000", .expect_err = -ERANGE },
+ { 19, "-100000000000000000000", .expect_err = -ERANGE },
+ { 19, "999999999999999999999999999.99", .expect_err = -ERANGE },
+ { 19, "-999999999999999999999999999.99", .expect_err = -ERANGE },
+ { 19, "1.2.3", .expect_err = -EINVAL },
+ { 19, "foo", .expect_err = -EINVAL },
+ { 19, "1.foo", .expect_err = -EINVAL },
+ { 19, "1.foo", .expect_err = -EINVAL },
+ { 19, "12.-345", .expect_err = -EINVAL },
+ { 19, "-12.-345", .expect_err = -EINVAL },
+ { 19, "12.+345", .expect_err = -EINVAL },
+ { 19, "+12.+345", .expect_err = -EINVAL },
+ { 19, "", .expect_err = -EINVAL },
+ { 19, NULL, .expect_err = -EINVAL },
+
+ { 20, "0", 0 },
+ { 20, ".01", 1000000000000000000LL },
+ { 20, ".012345", 1234500000000000000LL },
+ { 20, "+.012345", 1234500000000000000LL },
+ { 20, "-.012345", -1234500000000000000LL },
+ { 20, "0.00345", 345000000000000000LL },
+ { 20, ".00345", 345000000000000000LL },
+ { 20, "-0.00345", -345000000000000000LL },
+ { 20, "-.00345", -345000000000000000LL },
+ { 20, ".02", 2000000000000000000LL },
+ { 20, "-.08", -8000000000000000000LL },
+ { 20, ".01234567890123", 1234567890123000000LL },
+ { 20, ".01234567890123456789012345", 1234567890123456789LL },
+ { 20, "12.34567890123", .expect_err = -ERANGE },
+ { 20, ".09223372036854775807", 9223372036854775807LL },
+ { 20, "-.09223372036854775807", -9223372036854775807LL },
+ { 20, "-.09223372036854775808", .expect_err = -ERANGE },
+ { 20, ".09223372036854775808", .expect_err = -ERANGE },
+ { 20, "-.09223372036854775809", .expect_err = -ERANGE },
+ { 20, ".1", .expect_err = -ERANGE },
+ { 20, "-.1", .expect_err = -ERANGE },
+ { 20, "999999999999999999999999999.99", .expect_err = -ERANGE },
+ { 20, "-999999999999999999999999999.99", .expect_err = -ERANGE },
+ { 20, "1.2.3", .expect_err = -EINVAL },
+ { 20, "foo", .expect_err = -EINVAL },
+ { 20, "1.foo", .expect_err = -EINVAL },
+ { 20, "1.foo", .expect_err = -EINVAL },
+ { 20, "12.-345", .expect_err = -EINVAL },
+ { 20, "-12.-345", .expect_err = -EINVAL },
+ { 20, "12.+345", .expect_err = -EINVAL },
+ { 20, "+12.+345", .expect_err = -EINVAL },
+ { 20, "", .expect_err = -EINVAL },
+ { 20, NULL, .expect_err = -EINVAL },
+
+ { 25, "0", 0 },
+ { 25, ".0000001", 1000000000000000000LL },
+ { 25, ".00000012345", 1234500000000000000LL },
+ { 25, "+.00000012345", 1234500000000000000LL },
+ { 25, "-.00000012345", -1234500000000000000LL },
+ { 25, "0.0000000345", 345000000000000000LL },
+ { 25, ".0000000345", 345000000000000000LL },
+ { 25, "-0.0000000345", -345000000000000000LL },
+ { 25, "-.0000000345", -345000000000000000LL },
+ { 25, ".0000002", 2000000000000000000LL },
+ { 25, "-.0000008", -8000000000000000000LL },
+ { 25, ".0000001234567890123", 1234567890123000000LL },
+ { 25, ".0000001234567890123456789012345", 1234567890123456789LL },
+ { 25, ".0001234567890123", .expect_err = -ERANGE },
+ { 25, ".0000009223372036854775807", 9223372036854775807LL },
+ { 25, "-.0000009223372036854775807", -9223372036854775807LL },
+ { 25, "-.0000009223372036854775808", .expect_err = -ERANGE },
+ { 25, ".0000009223372036854775808", .expect_err = -ERANGE },
+ { 25, "-.0000009223372036854775809", .expect_err = -ERANGE },
+ { 25, ".000001", .expect_err = -ERANGE },
+ { 25, "-.000001", .expect_err = -ERANGE },
+ { 25, "999999999999999999999999999.99", .expect_err = -ERANGE },
+ { 25, "-999999999999999999999999999.99", .expect_err = -ERANGE },
+ { 25, "1.2.3", .expect_err = -EINVAL },
+ { 25, "foo", .expect_err = -EINVAL },
+ { 25, "1.foo", .expect_err = -EINVAL },
+ { 25, "1.foo", .expect_err = -EINVAL },
+ { 25, "12.-345", .expect_err = -EINVAL },
+ { 25, "-12.-345", .expect_err = -EINVAL },
+ { 25, "12.+345", .expect_err = -EINVAL },
+ { 25, "+12.+345", .expect_err = -EINVAL },
+ { 25, "", .expect_err = -EINVAL },
+ { 25, NULL, .expect_err = -EINVAL },
+};
+const char *errno_str(int rc)
+{
+ switch (rc) {
+ case -EINVAL:
+ return "=-EINVAL";
+ case -ERANGE:
+ return "=-ERANGE";
+ case -E2BIG:
+ return "=-E2BIG";
+ case -EOVERFLOW:
+ return "=-EOVERFLOW";
+ default:
+ return "";
+ }
+}
+void test_float_str_to_int(void)
+{
+ const struct float_str_to_int_test *t;
+ printf("--- %s\n", __func__);
+ for (t = float_str_to_int_tests; (t - float_str_to_int_tests) < ARRAY_SIZE(float_str_to_int_tests); t++) {
+ int rc;
+ int64_t val;
+ rc = osmo_float_str_to_int(&val, t->str, t->precision);
+ printf("osmo_float_str_to_int(%s, %u) -> rc=%d%s val=%" PRId64 "\n",
+ osmo_quote_str(t->str, -1), t->precision, rc, errno_str(rc), val);
+
+ if (rc != t->expect_err)
+ printf(" ERROR: expected rc=%d%s\n", t->expect_err, errno_str(t->expect_err));
+ if (val != t->expect_val)
+ printf(" ERROR: expected val=%" PRId64 "\n", t->expect_val);
+ if (rc != t->expect_err||val != t->expect_val)
+ exit(0);
+ }
+}
+
+struct int_to_float_str_test {
+ unsigned int precision;
+ int64_t val;
+ const char *expect_str;
+};
+struct int_to_float_str_test int_to_float_str_tests[] = {
+ { 0, 0, "0" },
+ { 0, 1, "1" },
+ { 0, 1000000, "1000000" },
+ { 0, -1000000, "-1000000" },
+ { 0, 1000001, "1000001" },
+ { 0, -1000001, "-1000001" },
+ { 0, 1000100, "1000100" },
+ { 0, -1010000, "-1010000" },
+ { 0, 1100000, "1100000" },
+ { 0, 10000000, "10000000" },
+ { 0, -10000000, "-10000000" },
+ { 0, 100000000, "100000000" },
+ { 0, -100000000, "-100000000" },
+ { 0, 9223372036854775807, "9223372036854775807" },
+ { 0, -9223372036854775807, "-9223372036854775807" },
+ { 0, INT64_MIN, "-ERR" },
+
+ { 1, 0, "0" },
+ { 1, 1, "0.1" },
+ { 1, 1000000, "100000" },
+ { 1, -1000000, "-100000" },
+ { 1, 1000001, "100000.1" },
+ { 1, -1000001, "-100000.1" },
+ { 1, 1000100, "100010" },
+ { 1, -1010000, "-101000" },
+ { 1, 1100000, "110000" },
+ { 1, 10000000, "1000000" },
+ { 1, -10000000, "-1000000" },
+ { 1, 100000000, "10000000" },
+ { 1, -100000000, "-10000000" },
+ { 1, 9223372036854775807, "922337203685477580.7" },
+ { 1, -9223372036854775807, "-922337203685477580.7" },
+ { 1, INT64_MIN, "-ERR" },
+
+ { 3, 0, "0" },
+ { 3, 1, "0.001" },
+ { 3, 1000000, "1000" },
+ { 3, -1000000, "-1000" },
+ { 3, 1000001, "1000.001" },
+ { 3, -1000001, "-1000.001" },
+ { 3, 1000100, "1000.1" },
+ { 3, -1010000, "-1010" },
+ { 3, 1100000, "1100" },
+ { 3, 10000000, "10000" },
+ { 3, -10000000, "-10000" },
+ { 3, 100000000, "100000" },
+ { 3, -100000000, "-100000" },
+ { 3, 9223372036854775807, "9223372036854775.807" },
+ { 3, -9223372036854775807, "-9223372036854775.807" },
+ { 3, INT64_MIN, "-ERR" },
+
+ { 6, 0, "0" },
+ { 6, 1, "0.000001" },
+ { 6, 1000000, "1" },
+ { 6, -1000000, "-1" },
+ { 6, 1000001, "1.000001" },
+ { 6, -1000001, "-1.000001" },
+ { 6, 1000100, "1.0001" },
+ { 6, -1010000, "-1.01" },
+ { 6, 1100000, "1.1" },
+ { 6, 10000000, "10" },
+ { 6, -10000000, "-10" },
+ { 6, 100000000, "100" },
+ { 6, -100000000, "-100" },
+ { 6, 9223372036854775807, "9223372036854.775807" },
+ { 6, -9223372036854775807, "-9223372036854.775807" },
+ { 6, INT64_MIN, "-ERR" },
+
+ { 17, 0, "0" },
+ { 17, 1, "0.00000000000000001" },
+ { 17, 1000000, "0.00000000001" },
+ { 17, -1000000, "-0.00000000001" },
+ { 17, 1000001, "0.00000000001000001" },
+ { 17, -1000001, "-0.00000000001000001" },
+ { 17, 1000100, "0.000000000010001" },
+ { 17, -1010000, "-0.0000000000101" },
+ { 17, 1100000, "0.000000000011" },
+ { 17, 10000000, "0.0000000001" },
+ { 17, -10000000, "-0.0000000001" },
+ { 17, 100000000, "0.000000001" },
+ { 17, -100000000, "-0.000000001" },
+ { 17, 9223372036854775807, "92.23372036854775807" },
+ { 17, -9223372036854775807, "-92.23372036854775807" },
+ { 17, INT64_MIN, "-ERR" },
+
+ { 18, 0, "0" },
+ { 18, 1, "0.000000000000000001" },
+ { 18, 1000000, "0.000000000001" },
+ { 18, -1000000, "-0.000000000001" },
+ { 18, 1000001, "0.000000000001000001" },
+ { 18, -1000001, "-0.000000000001000001" },
+ { 18, 1000100, "0.0000000000010001" },
+ { 18, -1010000, "-0.00000000000101" },
+ { 18, 1100000, "0.0000000000011" },
+ { 18, 10000000, "0.00000000001" },
+ { 18, -10000000, "-0.00000000001" },
+ { 18, 100000000, "0.0000000001" },
+ { 18, -100000000, "-0.0000000001" },
+ { 18, 9223372036854775807, "9.223372036854775807" },
+ { 18, -9223372036854775807, "-9.223372036854775807" },
+ { 18, INT64_MIN, "-ERR" },
+
+ { 19, 0, "0" },
+ { 19, 1, "0.0000000000000000001" },
+ { 19, 1000000, "0.0000000000001" },
+ { 19, -1000000, "-0.0000000000001" },
+ { 19, 1000001, "0.0000000000001000001" },
+ { 19, -1000001, "-0.0000000000001000001" },
+ { 19, 1000100, "0.00000000000010001" },
+ { 19, -1010000, "-0.000000000000101" },
+ { 19, 1100000, "0.00000000000011" },
+ { 19, 10000000, "0.000000000001" },
+ { 19, -10000000, "-0.000000000001" },
+ { 19, 100000000, "0.00000000001" },
+ { 19, -100000000, "-0.00000000001" },
+ { 19, 9223372036854775807, "0.9223372036854775807" },
+ { 19, -9223372036854775807, "-0.9223372036854775807" },
+ { 19, INT64_MIN, "-ERR" },
+
+ { 23, 0, "0" },
+ { 23, 1, "0.00000000000000000000001" },
+ { 23, 1000000, "0.00000000000000001" },
+ { 23, -1000000, "-0.00000000000000001" },
+ { 23, 1000001, "0.00000000000000001000001" },
+ { 23, -1000001, "-0.00000000000000001000001" },
+ { 23, 1000100, "0.000000000000000010001" },
+ { 23, -1010000, "-0.0000000000000000101" },
+ { 23, 1100000, "0.000000000000000011" },
+ { 23, 10000000, "0.0000000000000001" },
+ { 23, -10000000, "-0.0000000000000001" },
+ { 23, 100000000, "0.000000000000001" },
+ { 23, -100000000, "-0.000000000000001" },
+ { 23, 9223372036854775807, "0.00009223372036854775807" },
+ { 23, -9223372036854775807, "-0.00009223372036854775807" },
+ { 23, INT64_MIN, "-ERR" },
+};
+void test_int_to_float_str(void)
+{
+ const struct int_to_float_str_test *t;
+ printf("--- %s\n", __func__);
+ for (t = int_to_float_str_tests;
+ (t - int_to_float_str_tests) < ARRAY_SIZE(int_to_float_str_tests);
+ t++) {
+ char buf[128];
+ int rc;
+ rc = osmo_int_to_float_str_buf(buf, sizeof(buf), t->val, t->precision);
+ printf("osmo_int_to_float_str_buf(%" PRId64 ", %u) -> rc=%d str=%s\n", t->val, t->precision, rc,
+ osmo_quote_str(buf, -1));
+
+ if (rc != strlen(buf))
+ printf(" ERROR: expected rc=%zu\n", strlen(buf));
+ if (strcmp(buf, t->expect_str))
+ printf(" ERROR: expected str=%s\n", osmo_quote_str(t->expect_str, -1));
+ if (rc != strlen(buf) || strcmp(buf, t->expect_str))
+ exit(0);
+ }
+}
+
+struct str_to_int_test {
+ const char *str;
+ int base;
+ int min_val;
+ int max_val;
+ int expect_rc;
+ int expect_val;
+};
+/* Avoid using INT_MAX and INT_MIN because that would produce different test output on different architectures */
+struct str_to_int_test str_to_int_tests[] = {
+ { NULL, 10, -1000, 1000, -EINVAL, 0 },
+ { "", 10, -1000, 1000, -EINVAL, 0 },
+ { " ", 10, -1000, 1000, -EINVAL, 0 },
+ { "-", 10, -1000, 1000, -EINVAL, 0 },
+ { "--", 10, -1000, 1000, -EINVAL, 0 },
+ { "+", 10, -1000, 1000, -EINVAL, 0 },
+ { "++", 10, -1000, 1000, -EINVAL, 0 },
+
+ { "0", 10, -1000, 1000, 0, 0 },
+ { "1", 10, -1000, 1000, 0, 1 },
+ { "+1", 10, -1000, 1000, 0, 1 },
+ { "-1", 10, -1000, 1000, 0, -1 },
+ { "1000", 10, -1000, 1000, 0, 1000 },
+ { "+1000", 10, -1000, 1000, 0, 1000 },
+ { "-1000", 10, -1000, 1000, 0, -1000 },
+ { "1001", 10, -1000, 1000, -ERANGE, 1001 },
+ { "+1001", 10, -1000, 1000, -ERANGE, 1001 },
+ { "-1001", 10, -1000, 1000, -ERANGE, -1001 },
+
+ { "0", 16, -1000, 1000, 0, 0 },
+ { "1", 16, -1000, 1000, 0, 1 },
+ { "0x1", 16, -1000, 1000, 0, 1 },
+ { "+1", 16, -1000, 1000, 0, 1 },
+ { "-1", 16, -1000, 1000, 0, -1 },
+ { "+0x1", 16, -1000, 1000, 0, 1 },
+ { "-0x1", 16, -1000, 1000, 0, -1 },
+ { "3e8", 16, -1000, 1000, 0, 1000 },
+ { "3E8", 16, -1000, 1000, 0, 1000 },
+ { "0x3e8", 16, -1000, 1000, 0, 1000 },
+ { "0x3E8", 16, -1000, 1000, 0, 1000 },
+ { "+3e8", 16, -1000, 1000, 0, 1000 },
+ { "+3E8", 16, -1000, 1000, 0, 1000 },
+ { "+0x3e8", 16, -1000, 1000, 0, 1000 },
+ { "+0x3E8", 16, -1000, 1000, 0, 1000 },
+ { "-3e8", 16, -1000, 1000, 0, -1000 },
+ { "-3E8", 16, -1000, 1000, 0, -1000 },
+ { "-0x3e8", 16, -1000, 1000, 0, -1000 },
+ { "-0x3E8", 16, -1000, 1000, 0, -1000 },
+ { "3e9", 16, -1000, 1000, -ERANGE, 1001 },
+ { "3E9", 16, -1000, 1000, -ERANGE, 1001 },
+ { "0x3e9", 16, -1000, 1000, -ERANGE, 1001 },
+ { "0x3E9", 16, -1000, 1000, -ERANGE, 1001 },
+ { "+3e9", 16, -1000, 1000, -ERANGE, 1001 },
+ { "+3E9", 16, -1000, 1000, -ERANGE, 1001 },
+ { "+0x3e9", 16, -1000, 1000, -ERANGE, 1001 },
+ { "+0x3E9", 16, -1000, 1000, -ERANGE, 1001 },
+ { "-3e9", 16, -1000, 1000, -ERANGE, -1001 },
+ { "-3E9", 16, -1000, 1000, -ERANGE, -1001 },
+ { "-0x3e9", 16, -1000, 1000, -ERANGE, -1001 },
+ { "-0x3E9", 16, -1000, 1000, -ERANGE, -1001 },
+
+ { "garble", 10, -1000, 1000, -EINVAL, 0 },
+ { "-garble", 10, -1000, 1000, -EINVAL, 0 },
+ { "0x123", 10, -1000, 1000, -E2BIG, 0 },
+ { "123potatoes", 10, -1000, 1000, -E2BIG, 123 },
+ { "123 potatoes", 10, -1000, 1000, -E2BIG, 123 },
+ { "123 ", 10, -1000, 1000, -E2BIG, 123 },
+ { "123.4", 10, -1000, 1000, -E2BIG, 123 },
+};
+void test_str_to_int(void)
+{
+ const struct str_to_int_test *t;
+ printf("--- %s\n", __func__);
+ for (t = str_to_int_tests; (t - str_to_int_tests) < ARRAY_SIZE(str_to_int_tests); t++) {
+ int rc;
+ int val;
+ rc = osmo_str_to_int(&val, t->str, t->base, t->min_val, t->max_val);
+ printf("osmo_str_to_int(%s, %d, %d, %d) -> rc=%d%s val=%d\n",
+ osmo_quote_str(t->str, -1), t->base, t->min_val, t->max_val, rc, errno_str(rc), val);
+
+ if (rc != t->expect_rc)
+ printf(" ERROR: expected rc=%d%s\n", t->expect_rc, errno_str(t->expect_rc));
+ if (val != t->expect_val)
+ printf(" ERROR: expected val=%d\n", t->expect_val);
+ }
+}
+
+struct str_to_int64_test {
+ const char *str;
+ int base;
+ int64_t min_val;
+ int64_t max_val;
+ int expect_rc;
+ int64_t expect_val;
+};
+struct str_to_int64_test str_to_int64_tests[] = {
+ { NULL, 10, -1000, 1000, -EINVAL, 0 },
+ { "", 10, -1000, 1000, -EINVAL, 0 },
+ { " ", 10, -1000, 1000, -EINVAL, 0 },
+ { "-", 10, -1000, 1000, -EINVAL, 0 },
+ { "--", 10, -1000, 1000, -EINVAL, 0 },
+ { "+", 10, -1000, 1000, -EINVAL, 0 },
+ { "++", 10, -1000, 1000, -EINVAL, 0 },
+
+ { "0", 10, -1000, 1000, 0, 0 },
+ { "1", 10, -1000, 1000, 0, 1 },
+ { "+1", 10, -1000, 1000, 0, 1 },
+ { "-1", 10, -1000, 1000, 0, -1 },
+ { "1000", 10, -1000, 1000, 0, 1000 },
+ { "+1000", 10, -1000, 1000, 0, 1000 },
+ { "-1000", 10, -1000, 1000, 0, -1000 },
+ { "1001", 10, -1000, 1000, -ERANGE, 1001 },
+ { "+1001", 10, -1000, 1000, -ERANGE, 1001 },
+ { "-1001", 10, -1000, 1000, -ERANGE, -1001 },
+
+ { "0", 16, -1000, 1000, 0, 0 },
+ { "1", 16, -1000, 1000, 0, 1 },
+ { "0x1", 16, -1000, 1000, 0, 1 },
+ { "+1", 16, -1000, 1000, 0, 1 },
+ { "-1", 16, -1000, 1000, 0, -1 },
+ { "+0x1", 16, -1000, 1000, 0, 1 },
+ { "-0x1", 16, -1000, 1000, 0, -1 },
+ { "3e8", 16, -1000, 1000, 0, 1000 },
+ { "3E8", 16, -1000, 1000, 0, 1000 },
+ { "0x3e8", 16, -1000, 1000, 0, 1000 },
+ { "0x3E8", 16, -1000, 1000, 0, 1000 },
+ { "+3e8", 16, -1000, 1000, 0, 1000 },
+ { "+3E8", 16, -1000, 1000, 0, 1000 },
+ { "+0x3e8", 16, -1000, 1000, 0, 1000 },
+ { "+0x3E8", 16, -1000, 1000, 0, 1000 },
+ { "-3e8", 16, -1000, 1000, 0, -1000 },
+ { "-3E8", 16, -1000, 1000, 0, -1000 },
+ { "-0x3e8", 16, -1000, 1000, 0, -1000 },
+ { "-0x3E8", 16, -1000, 1000, 0, -1000 },
+ { "3e9", 16, -1000, 1000, -ERANGE, 1001 },
+ { "3E9", 16, -1000, 1000, -ERANGE, 1001 },
+ { "0x3e9", 16, -1000, 1000, -ERANGE, 1001 },
+ { "0x3E9", 16, -1000, 1000, -ERANGE, 1001 },
+ { "+3e9", 16, -1000, 1000, -ERANGE, 1001 },
+ { "+3E9", 16, -1000, 1000, -ERANGE, 1001 },
+ { "+0x3e9", 16, -1000, 1000, -ERANGE, 1001 },
+ { "+0x3E9", 16, -1000, 1000, -ERANGE, 1001 },
+ { "-3e9", 16, -1000, 1000, -ERANGE, -1001 },
+ { "-3E9", 16, -1000, 1000, -ERANGE, -1001 },
+ { "-0x3e9", 16, -1000, 1000, -ERANGE, -1001 },
+ { "-0x3E9", 16, -1000, 1000, -ERANGE, -1001 },
+
+ { "garble", 10, -1000, 1000, -EINVAL, 0 },
+ { "-garble", 10, -1000, 1000, -EINVAL, 0 },
+ { "0x123", 10, -1000, 1000, -E2BIG, 0 },
+ { "123potatoes", 10, -1000, 1000, -E2BIG, 123 },
+ { "123 potatoes", 10, -1000, 1000, -E2BIG, 123 },
+ { "123 ", 10, -1000, 1000, -E2BIG, 123 },
+ { "123.4", 10, -1000, 1000, -E2BIG, 123 },
+
+ { "-9223372036854775808", 10, INT64_MIN, INT64_MAX, 0, INT64_MIN },
+ { "9223372036854775807", 10, INT64_MIN, INT64_MAX, 0, INT64_MAX },
+
+ { "-9223372036854775809", 10, INT64_MIN, INT64_MAX, -EOVERFLOW, INT64_MIN },
+ { "9223372036854775808", 10, INT64_MIN, INT64_MAX, -EOVERFLOW, INT64_MAX },
+
+ { "-9223372036854775808", 10, -1000, 1000, -ERANGE, INT64_MIN },
+ { "9223372036854775807", 10, -1000, 1000, -ERANGE, INT64_MAX },
+ { "-9223372036854775809", 10, -1000, 1000, -EOVERFLOW, INT64_MIN },
+ { "9223372036854775808", 10, -1000, 1000, -EOVERFLOW, INT64_MAX },
+};
+void test_str_to_int64(void)
+{
+ const struct str_to_int64_test *t;
+ printf("--- %s\n", __func__);
+ for (t = str_to_int64_tests; (t - str_to_int64_tests) < ARRAY_SIZE(str_to_int64_tests); t++) {
+ int rc;
+ int64_t val;
+ rc = osmo_str_to_int64(&val, t->str, t->base, t->min_val, t->max_val);
+ printf("osmo_str_to_int64(%s, %d, %"PRId64", %"PRId64") -> rc=%d%s val=%"PRId64"\n",
+ osmo_quote_str(t->str, -1), t->base, t->min_val, t->max_val, rc, errno_str(rc), val);
+
+ if (rc != t->expect_rc)
+ printf(" ERROR: expected rc=%d%s\n", t->expect_rc, errno_str(t->expect_rc));
+ if (val != t->expect_val)
+ printf(" ERROR: expected val=%"PRId64"\n", t->expect_val);
+ }
+}
+
int main(int argc, char **argv)
{
static const struct log_info log_info = {};
@@ -1460,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();
@@ -1468,5 +2156,9 @@ int main(int argc, char **argv)
name_c_impl_test();
osmo_print_n_test();
osmo_strnchr_test();
+ test_float_str_to_int();
+ test_int_to_float_str();
+ test_str_to_int();
+ test_str_to_int64();
return 0;
}
diff --git a/tests/utils/utils_test.ok b/tests/utils/utils_test.ok
index f1b07fa8..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
@@ -505,3 +535,487 @@ osmo_strnchr("foo=bar", 3, '=') -> -1
osmo_strnchr("foo=bar", 0, '=') -> -1
osmo_strnchr("foo", 9, '=') -> -1
osmo_strnchr("foo", 9, '\0') -> 3
+--- test_float_str_to_int
+osmo_float_str_to_int("0", 0) -> rc=0 val=0
+osmo_float_str_to_int("1", 0) -> rc=0 val=1
+osmo_float_str_to_int("12.345", 0) -> rc=0 val=12
+osmo_float_str_to_int("+12.345", 0) -> rc=0 val=12
+osmo_float_str_to_int("-12.345", 0) -> rc=0 val=-12
+osmo_float_str_to_int("0.345", 0) -> rc=0 val=0
+osmo_float_str_to_int(".345", 0) -> rc=0 val=0
+osmo_float_str_to_int("-0.345", 0) -> rc=0 val=0
+osmo_float_str_to_int("-.345", 0) -> rc=0 val=0
+osmo_float_str_to_int("12.", 0) -> rc=0 val=12
+osmo_float_str_to_int("-180", 0) -> rc=0 val=-180
+osmo_float_str_to_int("180", 0) -> rc=0 val=180
+osmo_float_str_to_int("360", 0) -> rc=0 val=360
+osmo_float_str_to_int("123.4567890123", 0) -> rc=0 val=123
+osmo_float_str_to_int("123.4567890123456789012345", 0) -> rc=0 val=123
+osmo_float_str_to_int("9223372036854775807", 0) -> rc=0 val=9223372036854775807
+osmo_float_str_to_int("-9223372036854775807", 0) -> rc=0 val=-9223372036854775807
+osmo_float_str_to_int("-9223372036854775808", 0) -> rc=-34=-ERANGE val=0
+osmo_float_str_to_int("9223372036854775808", 0) -> rc=-34=-ERANGE val=0
+osmo_float_str_to_int("-9223372036854775809", 0) -> rc=-34=-ERANGE val=0
+osmo_float_str_to_int("100000000000000000000", 0) -> rc=-34=-ERANGE val=0
+osmo_float_str_to_int("-100000000000000000000", 0) -> rc=-34=-ERANGE val=0
+osmo_float_str_to_int("999999999999999999999999999.99", 0) -> rc=-34=-ERANGE val=0
+osmo_float_str_to_int("-999999999999999999999999999.99", 0) -> rc=-34=-ERANGE val=0
+osmo_float_str_to_int("1.2.3", 0) -> rc=-22=-EINVAL val=0
+osmo_float_str_to_int("foo", 0) -> rc=-22=-EINVAL val=0
+osmo_float_str_to_int("1.foo", 0) -> rc=-22=-EINVAL val=0
+osmo_float_str_to_int("1.foo", 0) -> rc=-22=-EINVAL val=0
+osmo_float_str_to_int("12.-345", 0) -> rc=-22=-EINVAL val=0
+osmo_float_str_to_int("-12.-345", 0) -> rc=-22=-EINVAL val=0
+osmo_float_str_to_int("12.+345", 0) -> rc=-22=-EINVAL val=0
+osmo_float_str_to_int("+12.+345", 0) -> rc=-22=-EINVAL val=0
+osmo_float_str_to_int("", 0) -> rc=-22=-EINVAL val=0
+osmo_float_str_to_int(NULL, 0) -> rc=-22=-EINVAL val=0
+osmo_float_str_to_int("0", 1) -> rc=0 val=0
+osmo_float_str_to_int("1", 1) -> rc=0 val=10
+osmo_float_str_to_int("12.345", 1) -> rc=0 val=123
+osmo_float_str_to_int("+12.345", 1) -> rc=0 val=123
+osmo_float_str_to_int("-12.345", 1) -> rc=0 val=-123
+osmo_float_str_to_int("0.345", 1) -> rc=0 val=3
+osmo_float_str_to_int(".345", 1) -> rc=0 val=3
+osmo_float_str_to_int("-0.345", 1) -> rc=0 val=-3
+osmo_float_str_to_int("-.345", 1) -> rc=0 val=-3
+osmo_float_str_to_int("12.", 1) -> rc=0 val=120
+osmo_float_str_to_int("-180", 1) -> rc=0 val=-1800
+osmo_float_str_to_int("180", 1) -> rc=0 val=1800
+osmo_float_str_to_int("360", 1) -> rc=0 val=3600
+osmo_float_str_to_int("123.4567890123", 1) -> rc=0 val=1234
+osmo_float_str_to_int("123.4567890123456789012345", 1) -> rc=0 val=1234
+osmo_float_str_to_int("922337203685477580.7", 1) -> rc=0 val=9223372036854775807
+osmo_float_str_to_int("-922337203685477580.7", 1) -> rc=0 val=-9223372036854775807
+osmo_float_str_to_int("-922337203685477580.8", 1) -> rc=-34=-ERANGE val=0
+osmo_float_str_to_int("922337203685477580.8", 1) -> rc=-34=-ERANGE val=0
+osmo_float_str_to_int("-922337203685477580.9", 1) -> rc=-34=-ERANGE val=0
+osmo_float_str_to_int("100000000000000000000", 1) -> rc=-34=-ERANGE val=0
+osmo_float_str_to_int("-100000000000000000000", 1) -> rc=-34=-ERANGE val=0
+osmo_float_str_to_int("999999999999999999999999999.99", 1) -> rc=-34=-ERANGE val=0
+osmo_float_str_to_int("-999999999999999999999999999.99", 1) -> rc=-34=-ERANGE val=0
+osmo_float_str_to_int("1.2.3", 1) -> rc=-22=-EINVAL val=0
+osmo_float_str_to_int("foo", 1) -> rc=-22=-EINVAL val=0
+osmo_float_str_to_int("1.foo", 1) -> rc=-22=-EINVAL val=0
+osmo_float_str_to_int("1.foo", 1) -> rc=-22=-EINVAL val=0
+osmo_float_str_to_int("12.-345", 1) -> rc=-22=-EINVAL val=0
+osmo_float_str_to_int("-12.-345", 1) -> rc=-22=-EINVAL val=0
+osmo_float_str_to_int("12.+345", 1) -> rc=-22=-EINVAL val=0
+osmo_float_str_to_int("+12.+345", 1) -> rc=-22=-EINVAL val=0
+osmo_float_str_to_int("", 1) -> rc=-22=-EINVAL val=0
+osmo_float_str_to_int(NULL, 1) -> rc=-22=-EINVAL val=0
+osmo_float_str_to_int("0", 6) -> rc=0 val=0
+osmo_float_str_to_int("1", 6) -> rc=0 val=1000000
+osmo_float_str_to_int("12.345", 6) -> rc=0 val=12345000
+osmo_float_str_to_int("+12.345", 6) -> rc=0 val=12345000
+osmo_float_str_to_int("-12.345", 6) -> rc=0 val=-12345000
+osmo_float_str_to_int("0.345", 6) -> rc=0 val=345000
+osmo_float_str_to_int(".345", 6) -> rc=0 val=345000
+osmo_float_str_to_int("-0.345", 6) -> rc=0 val=-345000
+osmo_float_str_to_int("-.345", 6) -> rc=0 val=-345000
+osmo_float_str_to_int("12.", 6) -> rc=0 val=12000000
+osmo_float_str_to_int("-180", 6) -> rc=0 val=-180000000
+osmo_float_str_to_int("180", 6) -> rc=0 val=180000000
+osmo_float_str_to_int("360", 6) -> rc=0 val=360000000
+osmo_float_str_to_int("123.4567890123", 6) -> rc=0 val=123456789
+osmo_float_str_to_int("123.4567890123456789012345", 6) -> rc=0 val=123456789
+osmo_float_str_to_int("9223372036854.775807", 6) -> rc=0 val=9223372036854775807
+osmo_float_str_to_int("-9223372036854.775807", 6) -> rc=0 val=-9223372036854775807
+osmo_float_str_to_int("-9223372036854.775808", 6) -> rc=-34=-ERANGE val=0
+osmo_float_str_to_int("9223372036854.775808", 6) -> rc=-34=-ERANGE val=0
+osmo_float_str_to_int("-9223372036854.775809", 6) -> rc=-34=-ERANGE val=0
+osmo_float_str_to_int("100000000000000000000", 6) -> rc=-34=-ERANGE val=0
+osmo_float_str_to_int("-100000000000000000000", 6) -> rc=-34=-ERANGE val=0
+osmo_float_str_to_int("999999999999999999999999999.99", 6) -> rc=-34=-ERANGE val=0
+osmo_float_str_to_int("-999999999999999999999999999.99", 6) -> rc=-34=-ERANGE val=0
+osmo_float_str_to_int("1.2.3", 6) -> rc=-22=-EINVAL val=0
+osmo_float_str_to_int("foo", 6) -> rc=-22=-EINVAL val=0
+osmo_float_str_to_int("1.foo", 6) -> rc=-22=-EINVAL val=0
+osmo_float_str_to_int("1.foo", 6) -> rc=-22=-EINVAL val=0
+osmo_float_str_to_int("12.-345", 6) -> rc=-22=-EINVAL val=0
+osmo_float_str_to_int("-12.-345", 6) -> rc=-22=-EINVAL val=0
+osmo_float_str_to_int("12.+345", 6) -> rc=-22=-EINVAL val=0
+osmo_float_str_to_int("+12.+345", 6) -> rc=-22=-EINVAL val=0
+osmo_float_str_to_int("", 6) -> rc=-22=-EINVAL val=0
+osmo_float_str_to_int(NULL, 6) -> rc=-22=-EINVAL val=0
+osmo_float_str_to_int("0", 18) -> rc=0 val=0
+osmo_float_str_to_int("1", 18) -> rc=0 val=1000000000000000000
+osmo_float_str_to_int("1.2345", 18) -> rc=0 val=1234500000000000000
+osmo_float_str_to_int("+1.2345", 18) -> rc=0 val=1234500000000000000
+osmo_float_str_to_int("-1.2345", 18) -> rc=0 val=-1234500000000000000
+osmo_float_str_to_int("0.345", 18) -> rc=0 val=345000000000000000
+osmo_float_str_to_int(".345", 18) -> rc=0 val=345000000000000000
+osmo_float_str_to_int("-0.345", 18) -> rc=0 val=-345000000000000000
+osmo_float_str_to_int("-.345", 18) -> rc=0 val=-345000000000000000
+osmo_float_str_to_int("2.", 18) -> rc=0 val=2000000000000000000
+osmo_float_str_to_int("-8", 18) -> rc=0 val=-8000000000000000000
+osmo_float_str_to_int("1.234567890123", 18) -> rc=0 val=1234567890123000000
+osmo_float_str_to_int("1.234567890123456789012345", 18) -> rc=0 val=1234567890123456789
+osmo_float_str_to_int("123.4567890123", 18) -> rc=-34=-ERANGE val=0
+osmo_float_str_to_int("9.223372036854775807", 18) -> rc=0 val=9223372036854775807
+osmo_float_str_to_int("-9.223372036854775807", 18) -> rc=0 val=-9223372036854775807
+osmo_float_str_to_int("-9.223372036854775808", 18) -> rc=-34=-ERANGE val=0
+osmo_float_str_to_int("9.223372036854775808", 18) -> rc=-34=-ERANGE val=0
+osmo_float_str_to_int("-9.223372036854775809", 18) -> rc=-34=-ERANGE val=0
+osmo_float_str_to_int("100000000000000000000", 18) -> rc=-34=-ERANGE val=0
+osmo_float_str_to_int("-100000000000000000000", 18) -> rc=-34=-ERANGE val=0
+osmo_float_str_to_int("999999999999999999999999999.99", 18) -> rc=-34=-ERANGE val=0
+osmo_float_str_to_int("-999999999999999999999999999.99", 18) -> rc=-34=-ERANGE val=0
+osmo_float_str_to_int("1.2.3", 18) -> rc=-22=-EINVAL val=0
+osmo_float_str_to_int("foo", 18) -> rc=-22=-EINVAL val=0
+osmo_float_str_to_int("1.foo", 18) -> rc=-22=-EINVAL val=0
+osmo_float_str_to_int("1.foo", 18) -> rc=-22=-EINVAL val=0
+osmo_float_str_to_int("12.-345", 18) -> rc=-22=-EINVAL val=0
+osmo_float_str_to_int("-12.-345", 18) -> rc=-22=-EINVAL val=0
+osmo_float_str_to_int("12.+345", 18) -> rc=-22=-EINVAL val=0
+osmo_float_str_to_int("+12.+345", 18) -> rc=-22=-EINVAL val=0
+osmo_float_str_to_int("", 18) -> rc=-22=-EINVAL val=0
+osmo_float_str_to_int(NULL, 18) -> rc=-22=-EINVAL val=0
+osmo_float_str_to_int("0", 19) -> rc=0 val=0
+osmo_float_str_to_int(".1", 19) -> rc=0 val=1000000000000000000
+osmo_float_str_to_int(".12345", 19) -> rc=0 val=1234500000000000000
+osmo_float_str_to_int("+.12345", 19) -> rc=0 val=1234500000000000000
+osmo_float_str_to_int("-.12345", 19) -> rc=0 val=-1234500000000000000
+osmo_float_str_to_int("0.0345", 19) -> rc=0 val=345000000000000000
+osmo_float_str_to_int(".0345", 19) -> rc=0 val=345000000000000000
+osmo_float_str_to_int("-0.0345", 19) -> rc=0 val=-345000000000000000
+osmo_float_str_to_int("-.0345", 19) -> rc=0 val=-345000000000000000
+osmo_float_str_to_int(".2", 19) -> rc=0 val=2000000000000000000
+osmo_float_str_to_int("-.8", 19) -> rc=0 val=-8000000000000000000
+osmo_float_str_to_int(".1234567890123", 19) -> rc=0 val=1234567890123000000
+osmo_float_str_to_int(".1234567890123456789012345", 19) -> rc=0 val=1234567890123456789
+osmo_float_str_to_int("123.4567890123", 19) -> rc=-34=-ERANGE val=0
+osmo_float_str_to_int(".9223372036854775807", 19) -> rc=0 val=9223372036854775807
+osmo_float_str_to_int("-.9223372036854775807", 19) -> rc=0 val=-9223372036854775807
+osmo_float_str_to_int("-.9223372036854775808", 19) -> rc=-34=-ERANGE val=0
+osmo_float_str_to_int(".9223372036854775808", 19) -> rc=-34=-ERANGE val=0
+osmo_float_str_to_int("-.9223372036854775809", 19) -> rc=-34=-ERANGE val=0
+osmo_float_str_to_int("100000000000000000000", 19) -> rc=-34=-ERANGE val=0
+osmo_float_str_to_int("-100000000000000000000", 19) -> rc=-34=-ERANGE val=0
+osmo_float_str_to_int("999999999999999999999999999.99", 19) -> rc=-34=-ERANGE val=0
+osmo_float_str_to_int("-999999999999999999999999999.99", 19) -> rc=-34=-ERANGE val=0
+osmo_float_str_to_int("1.2.3", 19) -> rc=-22=-EINVAL val=0
+osmo_float_str_to_int("foo", 19) -> rc=-22=-EINVAL val=0
+osmo_float_str_to_int("1.foo", 19) -> rc=-22=-EINVAL val=0
+osmo_float_str_to_int("1.foo", 19) -> rc=-22=-EINVAL val=0
+osmo_float_str_to_int("12.-345", 19) -> rc=-22=-EINVAL val=0
+osmo_float_str_to_int("-12.-345", 19) -> rc=-22=-EINVAL val=0
+osmo_float_str_to_int("12.+345", 19) -> rc=-22=-EINVAL val=0
+osmo_float_str_to_int("+12.+345", 19) -> rc=-22=-EINVAL val=0
+osmo_float_str_to_int("", 19) -> rc=-22=-EINVAL val=0
+osmo_float_str_to_int(NULL, 19) -> rc=-22=-EINVAL val=0
+osmo_float_str_to_int("0", 20) -> rc=0 val=0
+osmo_float_str_to_int(".01", 20) -> rc=0 val=1000000000000000000
+osmo_float_str_to_int(".012345", 20) -> rc=0 val=1234500000000000000
+osmo_float_str_to_int("+.012345", 20) -> rc=0 val=1234500000000000000
+osmo_float_str_to_int("-.012345", 20) -> rc=0 val=-1234500000000000000
+osmo_float_str_to_int("0.00345", 20) -> rc=0 val=345000000000000000
+osmo_float_str_to_int(".00345", 20) -> rc=0 val=345000000000000000
+osmo_float_str_to_int("-0.00345", 20) -> rc=0 val=-345000000000000000
+osmo_float_str_to_int("-.00345", 20) -> rc=0 val=-345000000000000000
+osmo_float_str_to_int(".02", 20) -> rc=0 val=2000000000000000000
+osmo_float_str_to_int("-.08", 20) -> rc=0 val=-8000000000000000000
+osmo_float_str_to_int(".01234567890123", 20) -> rc=0 val=1234567890123000000
+osmo_float_str_to_int(".01234567890123456789012345", 20) -> rc=0 val=1234567890123456789
+osmo_float_str_to_int("12.34567890123", 20) -> rc=-34=-ERANGE val=0
+osmo_float_str_to_int(".09223372036854775807", 20) -> rc=0 val=9223372036854775807
+osmo_float_str_to_int("-.09223372036854775807", 20) -> rc=0 val=-9223372036854775807
+osmo_float_str_to_int("-.09223372036854775808", 20) -> rc=-34=-ERANGE val=0
+osmo_float_str_to_int(".09223372036854775808", 20) -> rc=-34=-ERANGE val=0
+osmo_float_str_to_int("-.09223372036854775809", 20) -> rc=-34=-ERANGE val=0
+osmo_float_str_to_int(".1", 20) -> rc=-34=-ERANGE val=0
+osmo_float_str_to_int("-.1", 20) -> rc=-34=-ERANGE val=0
+osmo_float_str_to_int("999999999999999999999999999.99", 20) -> rc=-34=-ERANGE val=0
+osmo_float_str_to_int("-999999999999999999999999999.99", 20) -> rc=-34=-ERANGE val=0
+osmo_float_str_to_int("1.2.3", 20) -> rc=-22=-EINVAL val=0
+osmo_float_str_to_int("foo", 20) -> rc=-22=-EINVAL val=0
+osmo_float_str_to_int("1.foo", 20) -> rc=-22=-EINVAL val=0
+osmo_float_str_to_int("1.foo", 20) -> rc=-22=-EINVAL val=0
+osmo_float_str_to_int("12.-345", 20) -> rc=-22=-EINVAL val=0
+osmo_float_str_to_int("-12.-345", 20) -> rc=-22=-EINVAL val=0
+osmo_float_str_to_int("12.+345", 20) -> rc=-22=-EINVAL val=0
+osmo_float_str_to_int("+12.+345", 20) -> rc=-22=-EINVAL val=0
+osmo_float_str_to_int("", 20) -> rc=-22=-EINVAL val=0
+osmo_float_str_to_int(NULL, 20) -> rc=-22=-EINVAL val=0
+osmo_float_str_to_int("0", 25) -> rc=0 val=0
+osmo_float_str_to_int(".0000001", 25) -> rc=0 val=1000000000000000000
+osmo_float_str_to_int(".00000012345", 25) -> rc=0 val=1234500000000000000
+osmo_float_str_to_int("+.00000012345", 25) -> rc=0 val=1234500000000000000
+osmo_float_str_to_int("-.00000012345", 25) -> rc=0 val=-1234500000000000000
+osmo_float_str_to_int("0.0000000345", 25) -> rc=0 val=345000000000000000
+osmo_float_str_to_int(".0000000345", 25) -> rc=0 val=345000000000000000
+osmo_float_str_to_int("-0.0000000345", 25) -> rc=0 val=-345000000000000000
+osmo_float_str_to_int("-.0000000345", 25) -> rc=0 val=-345000000000000000
+osmo_float_str_to_int(".0000002", 25) -> rc=0 val=2000000000000000000
+osmo_float_str_to_int("-.0000008", 25) -> rc=0 val=-8000000000000000000
+osmo_float_str_to_int(".0000001234567890123", 25) -> rc=0 val=1234567890123000000
+osmo_float_str_to_int(".0000001234567890123456789012345", 25) -> rc=0 val=1234567890123456789
+osmo_float_str_to_int(".0001234567890123", 25) -> rc=-34=-ERANGE val=0
+osmo_float_str_to_int(".0000009223372036854775807", 25) -> rc=0 val=9223372036854775807
+osmo_float_str_to_int("-.0000009223372036854775807", 25) -> rc=0 val=-9223372036854775807
+osmo_float_str_to_int("-.0000009223372036854775808", 25) -> rc=-34=-ERANGE val=0
+osmo_float_str_to_int(".0000009223372036854775808", 25) -> rc=-34=-ERANGE val=0
+osmo_float_str_to_int("-.0000009223372036854775809", 25) -> rc=-34=-ERANGE val=0
+osmo_float_str_to_int(".000001", 25) -> rc=-34=-ERANGE val=0
+osmo_float_str_to_int("-.000001", 25) -> rc=-34=-ERANGE val=0
+osmo_float_str_to_int("999999999999999999999999999.99", 25) -> rc=-34=-ERANGE val=0
+osmo_float_str_to_int("-999999999999999999999999999.99", 25) -> rc=-34=-ERANGE val=0
+osmo_float_str_to_int("1.2.3", 25) -> rc=-22=-EINVAL val=0
+osmo_float_str_to_int("foo", 25) -> rc=-22=-EINVAL val=0
+osmo_float_str_to_int("1.foo", 25) -> rc=-22=-EINVAL val=0
+osmo_float_str_to_int("1.foo", 25) -> rc=-22=-EINVAL val=0
+osmo_float_str_to_int("12.-345", 25) -> rc=-22=-EINVAL val=0
+osmo_float_str_to_int("-12.-345", 25) -> rc=-22=-EINVAL val=0
+osmo_float_str_to_int("12.+345", 25) -> rc=-22=-EINVAL val=0
+osmo_float_str_to_int("+12.+345", 25) -> rc=-22=-EINVAL val=0
+osmo_float_str_to_int("", 25) -> rc=-22=-EINVAL val=0
+osmo_float_str_to_int(NULL, 25) -> rc=-22=-EINVAL val=0
+--- test_int_to_float_str
+osmo_int_to_float_str_buf(0, 0) -> rc=1 str="0"
+osmo_int_to_float_str_buf(1, 0) -> rc=1 str="1"
+osmo_int_to_float_str_buf(1000000, 0) -> rc=7 str="1000000"
+osmo_int_to_float_str_buf(-1000000, 0) -> rc=8 str="-1000000"
+osmo_int_to_float_str_buf(1000001, 0) -> rc=7 str="1000001"
+osmo_int_to_float_str_buf(-1000001, 0) -> rc=8 str="-1000001"
+osmo_int_to_float_str_buf(1000100, 0) -> rc=7 str="1000100"
+osmo_int_to_float_str_buf(-1010000, 0) -> rc=8 str="-1010000"
+osmo_int_to_float_str_buf(1100000, 0) -> rc=7 str="1100000"
+osmo_int_to_float_str_buf(10000000, 0) -> rc=8 str="10000000"
+osmo_int_to_float_str_buf(-10000000, 0) -> rc=9 str="-10000000"
+osmo_int_to_float_str_buf(100000000, 0) -> rc=9 str="100000000"
+osmo_int_to_float_str_buf(-100000000, 0) -> rc=10 str="-100000000"
+osmo_int_to_float_str_buf(9223372036854775807, 0) -> rc=19 str="9223372036854775807"
+osmo_int_to_float_str_buf(-9223372036854775807, 0) -> rc=20 str="-9223372036854775807"
+osmo_int_to_float_str_buf(-9223372036854775808, 0) -> rc=4 str="-ERR"
+osmo_int_to_float_str_buf(0, 1) -> rc=1 str="0"
+osmo_int_to_float_str_buf(1, 1) -> rc=3 str="0.1"
+osmo_int_to_float_str_buf(1000000, 1) -> rc=6 str="100000"
+osmo_int_to_float_str_buf(-1000000, 1) -> rc=7 str="-100000"
+osmo_int_to_float_str_buf(1000001, 1) -> rc=8 str="100000.1"
+osmo_int_to_float_str_buf(-1000001, 1) -> rc=9 str="-100000.1"
+osmo_int_to_float_str_buf(1000100, 1) -> rc=6 str="100010"
+osmo_int_to_float_str_buf(-1010000, 1) -> rc=7 str="-101000"
+osmo_int_to_float_str_buf(1100000, 1) -> rc=6 str="110000"
+osmo_int_to_float_str_buf(10000000, 1) -> rc=7 str="1000000"
+osmo_int_to_float_str_buf(-10000000, 1) -> rc=8 str="-1000000"
+osmo_int_to_float_str_buf(100000000, 1) -> rc=8 str="10000000"
+osmo_int_to_float_str_buf(-100000000, 1) -> rc=9 str="-10000000"
+osmo_int_to_float_str_buf(9223372036854775807, 1) -> rc=20 str="922337203685477580.7"
+osmo_int_to_float_str_buf(-9223372036854775807, 1) -> rc=21 str="-922337203685477580.7"
+osmo_int_to_float_str_buf(-9223372036854775808, 1) -> rc=4 str="-ERR"
+osmo_int_to_float_str_buf(0, 3) -> rc=1 str="0"
+osmo_int_to_float_str_buf(1, 3) -> rc=5 str="0.001"
+osmo_int_to_float_str_buf(1000000, 3) -> rc=4 str="1000"
+osmo_int_to_float_str_buf(-1000000, 3) -> rc=5 str="-1000"
+osmo_int_to_float_str_buf(1000001, 3) -> rc=8 str="1000.001"
+osmo_int_to_float_str_buf(-1000001, 3) -> rc=9 str="-1000.001"
+osmo_int_to_float_str_buf(1000100, 3) -> rc=6 str="1000.1"
+osmo_int_to_float_str_buf(-1010000, 3) -> rc=5 str="-1010"
+osmo_int_to_float_str_buf(1100000, 3) -> rc=4 str="1100"
+osmo_int_to_float_str_buf(10000000, 3) -> rc=5 str="10000"
+osmo_int_to_float_str_buf(-10000000, 3) -> rc=6 str="-10000"
+osmo_int_to_float_str_buf(100000000, 3) -> rc=6 str="100000"
+osmo_int_to_float_str_buf(-100000000, 3) -> rc=7 str="-100000"
+osmo_int_to_float_str_buf(9223372036854775807, 3) -> rc=20 str="9223372036854775.807"
+osmo_int_to_float_str_buf(-9223372036854775807, 3) -> rc=21 str="-9223372036854775.807"
+osmo_int_to_float_str_buf(-9223372036854775808, 3) -> rc=4 str="-ERR"
+osmo_int_to_float_str_buf(0, 6) -> rc=1 str="0"
+osmo_int_to_float_str_buf(1, 6) -> rc=8 str="0.000001"
+osmo_int_to_float_str_buf(1000000, 6) -> rc=1 str="1"
+osmo_int_to_float_str_buf(-1000000, 6) -> rc=2 str="-1"
+osmo_int_to_float_str_buf(1000001, 6) -> rc=8 str="1.000001"
+osmo_int_to_float_str_buf(-1000001, 6) -> rc=9 str="-1.000001"
+osmo_int_to_float_str_buf(1000100, 6) -> rc=6 str="1.0001"
+osmo_int_to_float_str_buf(-1010000, 6) -> rc=5 str="-1.01"
+osmo_int_to_float_str_buf(1100000, 6) -> rc=3 str="1.1"
+osmo_int_to_float_str_buf(10000000, 6) -> rc=2 str="10"
+osmo_int_to_float_str_buf(-10000000, 6) -> rc=3 str="-10"
+osmo_int_to_float_str_buf(100000000, 6) -> rc=3 str="100"
+osmo_int_to_float_str_buf(-100000000, 6) -> rc=4 str="-100"
+osmo_int_to_float_str_buf(9223372036854775807, 6) -> rc=20 str="9223372036854.775807"
+osmo_int_to_float_str_buf(-9223372036854775807, 6) -> rc=21 str="-9223372036854.775807"
+osmo_int_to_float_str_buf(-9223372036854775808, 6) -> rc=4 str="-ERR"
+osmo_int_to_float_str_buf(0, 17) -> rc=1 str="0"
+osmo_int_to_float_str_buf(1, 17) -> rc=19 str="0.00000000000000001"
+osmo_int_to_float_str_buf(1000000, 17) -> rc=13 str="0.00000000001"
+osmo_int_to_float_str_buf(-1000000, 17) -> rc=14 str="-0.00000000001"
+osmo_int_to_float_str_buf(1000001, 17) -> rc=19 str="0.00000000001000001"
+osmo_int_to_float_str_buf(-1000001, 17) -> rc=20 str="-0.00000000001000001"
+osmo_int_to_float_str_buf(1000100, 17) -> rc=17 str="0.000000000010001"
+osmo_int_to_float_str_buf(-1010000, 17) -> rc=16 str="-0.0000000000101"
+osmo_int_to_float_str_buf(1100000, 17) -> rc=14 str="0.000000000011"
+osmo_int_to_float_str_buf(10000000, 17) -> rc=12 str="0.0000000001"
+osmo_int_to_float_str_buf(-10000000, 17) -> rc=13 str="-0.0000000001"
+osmo_int_to_float_str_buf(100000000, 17) -> rc=11 str="0.000000001"
+osmo_int_to_float_str_buf(-100000000, 17) -> rc=12 str="-0.000000001"
+osmo_int_to_float_str_buf(9223372036854775807, 17) -> rc=20 str="92.23372036854775807"
+osmo_int_to_float_str_buf(-9223372036854775807, 17) -> rc=21 str="-92.23372036854775807"
+osmo_int_to_float_str_buf(-9223372036854775808, 17) -> rc=4 str="-ERR"
+osmo_int_to_float_str_buf(0, 18) -> rc=1 str="0"
+osmo_int_to_float_str_buf(1, 18) -> rc=20 str="0.000000000000000001"
+osmo_int_to_float_str_buf(1000000, 18) -> rc=14 str="0.000000000001"
+osmo_int_to_float_str_buf(-1000000, 18) -> rc=15 str="-0.000000000001"
+osmo_int_to_float_str_buf(1000001, 18) -> rc=20 str="0.000000000001000001"
+osmo_int_to_float_str_buf(-1000001, 18) -> rc=21 str="-0.000000000001000001"
+osmo_int_to_float_str_buf(1000100, 18) -> rc=18 str="0.0000000000010001"
+osmo_int_to_float_str_buf(-1010000, 18) -> rc=17 str="-0.00000000000101"
+osmo_int_to_float_str_buf(1100000, 18) -> rc=15 str="0.0000000000011"
+osmo_int_to_float_str_buf(10000000, 18) -> rc=13 str="0.00000000001"
+osmo_int_to_float_str_buf(-10000000, 18) -> rc=14 str="-0.00000000001"
+osmo_int_to_float_str_buf(100000000, 18) -> rc=12 str="0.0000000001"
+osmo_int_to_float_str_buf(-100000000, 18) -> rc=13 str="-0.0000000001"
+osmo_int_to_float_str_buf(9223372036854775807, 18) -> rc=20 str="9.223372036854775807"
+osmo_int_to_float_str_buf(-9223372036854775807, 18) -> rc=21 str="-9.223372036854775807"
+osmo_int_to_float_str_buf(-9223372036854775808, 18) -> rc=4 str="-ERR"
+osmo_int_to_float_str_buf(0, 19) -> rc=1 str="0"
+osmo_int_to_float_str_buf(1, 19) -> rc=21 str="0.0000000000000000001"
+osmo_int_to_float_str_buf(1000000, 19) -> rc=15 str="0.0000000000001"
+osmo_int_to_float_str_buf(-1000000, 19) -> rc=16 str="-0.0000000000001"
+osmo_int_to_float_str_buf(1000001, 19) -> rc=21 str="0.0000000000001000001"
+osmo_int_to_float_str_buf(-1000001, 19) -> rc=22 str="-0.0000000000001000001"
+osmo_int_to_float_str_buf(1000100, 19) -> rc=19 str="0.00000000000010001"
+osmo_int_to_float_str_buf(-1010000, 19) -> rc=18 str="-0.000000000000101"
+osmo_int_to_float_str_buf(1100000, 19) -> rc=16 str="0.00000000000011"
+osmo_int_to_float_str_buf(10000000, 19) -> rc=14 str="0.000000000001"
+osmo_int_to_float_str_buf(-10000000, 19) -> rc=15 str="-0.000000000001"
+osmo_int_to_float_str_buf(100000000, 19) -> rc=13 str="0.00000000001"
+osmo_int_to_float_str_buf(-100000000, 19) -> rc=14 str="-0.00000000001"
+osmo_int_to_float_str_buf(9223372036854775807, 19) -> rc=21 str="0.9223372036854775807"
+osmo_int_to_float_str_buf(-9223372036854775807, 19) -> rc=22 str="-0.9223372036854775807"
+osmo_int_to_float_str_buf(-9223372036854775808, 19) -> rc=4 str="-ERR"
+osmo_int_to_float_str_buf(0, 23) -> rc=1 str="0"
+osmo_int_to_float_str_buf(1, 23) -> rc=25 str="0.00000000000000000000001"
+osmo_int_to_float_str_buf(1000000, 23) -> rc=19 str="0.00000000000000001"
+osmo_int_to_float_str_buf(-1000000, 23) -> rc=20 str="-0.00000000000000001"
+osmo_int_to_float_str_buf(1000001, 23) -> rc=25 str="0.00000000000000001000001"
+osmo_int_to_float_str_buf(-1000001, 23) -> rc=26 str="-0.00000000000000001000001"
+osmo_int_to_float_str_buf(1000100, 23) -> rc=23 str="0.000000000000000010001"
+osmo_int_to_float_str_buf(-1010000, 23) -> rc=22 str="-0.0000000000000000101"
+osmo_int_to_float_str_buf(1100000, 23) -> rc=20 str="0.000000000000000011"
+osmo_int_to_float_str_buf(10000000, 23) -> rc=18 str="0.0000000000000001"
+osmo_int_to_float_str_buf(-10000000, 23) -> rc=19 str="-0.0000000000000001"
+osmo_int_to_float_str_buf(100000000, 23) -> rc=17 str="0.000000000000001"
+osmo_int_to_float_str_buf(-100000000, 23) -> rc=18 str="-0.000000000000001"
+osmo_int_to_float_str_buf(9223372036854775807, 23) -> rc=25 str="0.00009223372036854775807"
+osmo_int_to_float_str_buf(-9223372036854775807, 23) -> rc=26 str="-0.00009223372036854775807"
+osmo_int_to_float_str_buf(-9223372036854775808, 23) -> rc=4 str="-ERR"
+--- test_str_to_int
+osmo_str_to_int(NULL, 10, -1000, 1000) -> rc=-22=-EINVAL val=0
+osmo_str_to_int("", 10, -1000, 1000) -> rc=-22=-EINVAL val=0
+osmo_str_to_int(" ", 10, -1000, 1000) -> rc=-22=-EINVAL val=0
+osmo_str_to_int("-", 10, -1000, 1000) -> rc=-22=-EINVAL val=0
+osmo_str_to_int("--", 10, -1000, 1000) -> rc=-22=-EINVAL val=0
+osmo_str_to_int("+", 10, -1000, 1000) -> rc=-22=-EINVAL val=0
+osmo_str_to_int("++", 10, -1000, 1000) -> rc=-22=-EINVAL val=0
+osmo_str_to_int("0", 10, -1000, 1000) -> rc=0 val=0
+osmo_str_to_int("1", 10, -1000, 1000) -> rc=0 val=1
+osmo_str_to_int("+1", 10, -1000, 1000) -> rc=0 val=1
+osmo_str_to_int("-1", 10, -1000, 1000) -> rc=0 val=-1
+osmo_str_to_int("1000", 10, -1000, 1000) -> rc=0 val=1000
+osmo_str_to_int("+1000", 10, -1000, 1000) -> rc=0 val=1000
+osmo_str_to_int("-1000", 10, -1000, 1000) -> rc=0 val=-1000
+osmo_str_to_int("1001", 10, -1000, 1000) -> rc=-34=-ERANGE val=1001
+osmo_str_to_int("+1001", 10, -1000, 1000) -> rc=-34=-ERANGE val=1001
+osmo_str_to_int("-1001", 10, -1000, 1000) -> rc=-34=-ERANGE val=-1001
+osmo_str_to_int("0", 16, -1000, 1000) -> rc=0 val=0
+osmo_str_to_int("1", 16, -1000, 1000) -> rc=0 val=1
+osmo_str_to_int("0x1", 16, -1000, 1000) -> rc=0 val=1
+osmo_str_to_int("+1", 16, -1000, 1000) -> rc=0 val=1
+osmo_str_to_int("-1", 16, -1000, 1000) -> rc=0 val=-1
+osmo_str_to_int("+0x1", 16, -1000, 1000) -> rc=0 val=1
+osmo_str_to_int("-0x1", 16, -1000, 1000) -> rc=0 val=-1
+osmo_str_to_int("3e8", 16, -1000, 1000) -> rc=0 val=1000
+osmo_str_to_int("3E8", 16, -1000, 1000) -> rc=0 val=1000
+osmo_str_to_int("0x3e8", 16, -1000, 1000) -> rc=0 val=1000
+osmo_str_to_int("0x3E8", 16, -1000, 1000) -> rc=0 val=1000
+osmo_str_to_int("+3e8", 16, -1000, 1000) -> rc=0 val=1000
+osmo_str_to_int("+3E8", 16, -1000, 1000) -> rc=0 val=1000
+osmo_str_to_int("+0x3e8", 16, -1000, 1000) -> rc=0 val=1000
+osmo_str_to_int("+0x3E8", 16, -1000, 1000) -> rc=0 val=1000
+osmo_str_to_int("-3e8", 16, -1000, 1000) -> rc=0 val=-1000
+osmo_str_to_int("-3E8", 16, -1000, 1000) -> rc=0 val=-1000
+osmo_str_to_int("-0x3e8", 16, -1000, 1000) -> rc=0 val=-1000
+osmo_str_to_int("-0x3E8", 16, -1000, 1000) -> rc=0 val=-1000
+osmo_str_to_int("3e9", 16, -1000, 1000) -> rc=-34=-ERANGE val=1001
+osmo_str_to_int("3E9", 16, -1000, 1000) -> rc=-34=-ERANGE val=1001
+osmo_str_to_int("0x3e9", 16, -1000, 1000) -> rc=-34=-ERANGE val=1001
+osmo_str_to_int("0x3E9", 16, -1000, 1000) -> rc=-34=-ERANGE val=1001
+osmo_str_to_int("+3e9", 16, -1000, 1000) -> rc=-34=-ERANGE val=1001
+osmo_str_to_int("+3E9", 16, -1000, 1000) -> rc=-34=-ERANGE val=1001
+osmo_str_to_int("+0x3e9", 16, -1000, 1000) -> rc=-34=-ERANGE val=1001
+osmo_str_to_int("+0x3E9", 16, -1000, 1000) -> rc=-34=-ERANGE val=1001
+osmo_str_to_int("-3e9", 16, -1000, 1000) -> rc=-34=-ERANGE val=-1001
+osmo_str_to_int("-3E9", 16, -1000, 1000) -> rc=-34=-ERANGE val=-1001
+osmo_str_to_int("-0x3e9", 16, -1000, 1000) -> rc=-34=-ERANGE val=-1001
+osmo_str_to_int("-0x3E9", 16, -1000, 1000) -> rc=-34=-ERANGE val=-1001
+osmo_str_to_int("garble", 10, -1000, 1000) -> rc=-22=-EINVAL val=0
+osmo_str_to_int("-garble", 10, -1000, 1000) -> rc=-22=-EINVAL val=0
+osmo_str_to_int("0x123", 10, -1000, 1000) -> rc=-7=-E2BIG val=0
+osmo_str_to_int("123potatoes", 10, -1000, 1000) -> rc=-7=-E2BIG val=123
+osmo_str_to_int("123 potatoes", 10, -1000, 1000) -> rc=-7=-E2BIG val=123
+osmo_str_to_int("123 ", 10, -1000, 1000) -> rc=-7=-E2BIG val=123
+osmo_str_to_int("123.4", 10, -1000, 1000) -> rc=-7=-E2BIG val=123
+--- test_str_to_int64
+osmo_str_to_int64(NULL, 10, -1000, 1000) -> rc=-22=-EINVAL val=0
+osmo_str_to_int64("", 10, -1000, 1000) -> rc=-22=-EINVAL val=0
+osmo_str_to_int64(" ", 10, -1000, 1000) -> rc=-22=-EINVAL val=0
+osmo_str_to_int64("-", 10, -1000, 1000) -> rc=-22=-EINVAL val=0
+osmo_str_to_int64("--", 10, -1000, 1000) -> rc=-22=-EINVAL val=0
+osmo_str_to_int64("+", 10, -1000, 1000) -> rc=-22=-EINVAL val=0
+osmo_str_to_int64("++", 10, -1000, 1000) -> rc=-22=-EINVAL val=0
+osmo_str_to_int64("0", 10, -1000, 1000) -> rc=0 val=0
+osmo_str_to_int64("1", 10, -1000, 1000) -> rc=0 val=1
+osmo_str_to_int64("+1", 10, -1000, 1000) -> rc=0 val=1
+osmo_str_to_int64("-1", 10, -1000, 1000) -> rc=0 val=-1
+osmo_str_to_int64("1000", 10, -1000, 1000) -> rc=0 val=1000
+osmo_str_to_int64("+1000", 10, -1000, 1000) -> rc=0 val=1000
+osmo_str_to_int64("-1000", 10, -1000, 1000) -> rc=0 val=-1000
+osmo_str_to_int64("1001", 10, -1000, 1000) -> rc=-34=-ERANGE val=1001
+osmo_str_to_int64("+1001", 10, -1000, 1000) -> rc=-34=-ERANGE val=1001
+osmo_str_to_int64("-1001", 10, -1000, 1000) -> rc=-34=-ERANGE val=-1001
+osmo_str_to_int64("0", 16, -1000, 1000) -> rc=0 val=0
+osmo_str_to_int64("1", 16, -1000, 1000) -> rc=0 val=1
+osmo_str_to_int64("0x1", 16, -1000, 1000) -> rc=0 val=1
+osmo_str_to_int64("+1", 16, -1000, 1000) -> rc=0 val=1
+osmo_str_to_int64("-1", 16, -1000, 1000) -> rc=0 val=-1
+osmo_str_to_int64("+0x1", 16, -1000, 1000) -> rc=0 val=1
+osmo_str_to_int64("-0x1", 16, -1000, 1000) -> rc=0 val=-1
+osmo_str_to_int64("3e8", 16, -1000, 1000) -> rc=0 val=1000
+osmo_str_to_int64("3E8", 16, -1000, 1000) -> rc=0 val=1000
+osmo_str_to_int64("0x3e8", 16, -1000, 1000) -> rc=0 val=1000
+osmo_str_to_int64("0x3E8", 16, -1000, 1000) -> rc=0 val=1000
+osmo_str_to_int64("+3e8", 16, -1000, 1000) -> rc=0 val=1000
+osmo_str_to_int64("+3E8", 16, -1000, 1000) -> rc=0 val=1000
+osmo_str_to_int64("+0x3e8", 16, -1000, 1000) -> rc=0 val=1000
+osmo_str_to_int64("+0x3E8", 16, -1000, 1000) -> rc=0 val=1000
+osmo_str_to_int64("-3e8", 16, -1000, 1000) -> rc=0 val=-1000
+osmo_str_to_int64("-3E8", 16, -1000, 1000) -> rc=0 val=-1000
+osmo_str_to_int64("-0x3e8", 16, -1000, 1000) -> rc=0 val=-1000
+osmo_str_to_int64("-0x3E8", 16, -1000, 1000) -> rc=0 val=-1000
+osmo_str_to_int64("3e9", 16, -1000, 1000) -> rc=-34=-ERANGE val=1001
+osmo_str_to_int64("3E9", 16, -1000, 1000) -> rc=-34=-ERANGE val=1001
+osmo_str_to_int64("0x3e9", 16, -1000, 1000) -> rc=-34=-ERANGE val=1001
+osmo_str_to_int64("0x3E9", 16, -1000, 1000) -> rc=-34=-ERANGE val=1001
+osmo_str_to_int64("+3e9", 16, -1000, 1000) -> rc=-34=-ERANGE val=1001
+osmo_str_to_int64("+3E9", 16, -1000, 1000) -> rc=-34=-ERANGE val=1001
+osmo_str_to_int64("+0x3e9", 16, -1000, 1000) -> rc=-34=-ERANGE val=1001
+osmo_str_to_int64("+0x3E9", 16, -1000, 1000) -> rc=-34=-ERANGE val=1001
+osmo_str_to_int64("-3e9", 16, -1000, 1000) -> rc=-34=-ERANGE val=-1001
+osmo_str_to_int64("-3E9", 16, -1000, 1000) -> rc=-34=-ERANGE val=-1001
+osmo_str_to_int64("-0x3e9", 16, -1000, 1000) -> rc=-34=-ERANGE val=-1001
+osmo_str_to_int64("-0x3E9", 16, -1000, 1000) -> rc=-34=-ERANGE val=-1001
+osmo_str_to_int64("garble", 10, -1000, 1000) -> rc=-22=-EINVAL val=0
+osmo_str_to_int64("-garble", 10, -1000, 1000) -> rc=-22=-EINVAL val=0
+osmo_str_to_int64("0x123", 10, -1000, 1000) -> rc=-7=-E2BIG val=0
+osmo_str_to_int64("123potatoes", 10, -1000, 1000) -> rc=-7=-E2BIG val=123
+osmo_str_to_int64("123 potatoes", 10, -1000, 1000) -> rc=-7=-E2BIG val=123
+osmo_str_to_int64("123 ", 10, -1000, 1000) -> rc=-7=-E2BIG val=123
+osmo_str_to_int64("123.4", 10, -1000, 1000) -> rc=-7=-E2BIG val=123
+osmo_str_to_int64("-9223372036854775808", 10, -9223372036854775808, 9223372036854775807) -> rc=0 val=-9223372036854775808
+osmo_str_to_int64("9223372036854775807", 10, -9223372036854775808, 9223372036854775807) -> rc=0 val=9223372036854775807
+osmo_str_to_int64("-9223372036854775809", 10, -9223372036854775808, 9223372036854775807) -> rc=-75=-EOVERFLOW val=-9223372036854775808
+osmo_str_to_int64("9223372036854775808", 10, -9223372036854775808, 9223372036854775807) -> rc=-75=-EOVERFLOW val=9223372036854775807
+osmo_str_to_int64("-9223372036854775808", 10, -1000, 1000) -> rc=-34=-ERANGE val=-9223372036854775808
+osmo_str_to_int64("9223372036854775807", 10, -1000, 1000) -> rc=-34=-ERANGE val=9223372036854775807
+osmo_str_to_int64("-9223372036854775809", 10, -1000, 1000) -> rc=-75=-EOVERFLOW val=-9223372036854775808
+osmo_str_to_int64("9223372036854775808", 10, -1000, 1000) -> rc=-75=-EOVERFLOW val=9223372036854775807
diff --git a/tests/v110/test_frame.c b/tests/v110/test_frame.c
new file mode 100644
index 00000000..ebc617c0
--- /dev/null
+++ b/tests/v110/test_frame.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/test_frame.ok b/tests/v110/test_frame.ok
new file mode 100644
index 00000000..ecefaa87
--- /dev/null
+++ b/tests/v110/test_frame.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/test_ra1.c b/tests/v110/test_ra1.c
new file mode 100644
index 00000000..775ec4b7
--- /dev/null
+++ b/tests/v110/test_ra1.c
@@ -0,0 +1,71 @@
+#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 = osmo_v110_sync_ra1_get_user_data_chunk_bitlen(rate);
+ 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);
+
+ /* 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/test_ra1.ok b/tests/v110/test_ra1.ok
new file mode 100644
index 00000000..8a61fc3f
--- /dev/null
+++ b/tests/v110/test_ra1.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 b2d34ad8..01e323ed 100644
--- a/tests/vty/vty_test.c
+++ b/tests/vty/vty_test.c
@@ -13,10 +13,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <stdio.h>
@@ -452,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);
@@ -477,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;
@@ -498,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;
@@ -513,11 +541,73 @@ 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,
+ VTY_TEST_ATTR_BAR,
+ VTY_TEST_ATTR_ZOO,
+ VTY_TEST_ATTR_FOO_DUP,
+ VTY_TEST_ATTR_ZOO_DUP,
+ VTY_TEST_ATTR_UPPER,
+ VTY_TEST_ATTR_RAFC_DOT,
+ VTY_TEST_ATTR_RAFC_EXCL,
+ VTY_TEST_ATTR_RAFC_AT,
+};
+
int main(int argc, char **argv)
{
struct vty_app_info vty_info = {
.name = "VtyTest",
.version = 0,
+ .usr_attr_letters = {
+ [VTY_TEST_ATTR_FOO] = 'f',
+ [VTY_TEST_ATTR_BAR] = 'b',
+ [VTY_TEST_ATTR_ZOO] = 'z',
+
+ /* Duplicate detection check */
+ [VTY_TEST_ATTR_FOO_DUP] = 'f',
+ [VTY_TEST_ATTR_ZOO_DUP] = 'z',
+ /* Reserved for libraries */
+ [VTY_TEST_ATTR_UPPER] = 'X',
+ /* Reserved for global attribues */
+ [VTY_TEST_ATTR_RAFC_DOT] = '.',
+ [VTY_TEST_ATTR_RAFC_EXCL] = '!',
+ [VTY_TEST_ATTR_RAFC_AT] = '@',
+ },
};
const struct log_info_cat default_categories[] = {};
@@ -567,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
new file mode 100644
index 00000000..b021425d
--- /dev/null
+++ b/tests/vty/vty_test.err
@@ -0,0 +1,77 @@
+Found duplicate flag letter 'f' in application specific attributes (index 0 vs 3)! Please fix.
+Found duplicate flag letter 'z' in application specific attributes (index 2 vs 4)! Please fix.
+Attribute flag letter 'X' is reserved for libraries! Please fix.
+Attribute flag character '.' is reserved for globals! Please fix.
+Attribute flag character '!' is reserved for globals! Please fix.
+Attribute flag character '@' is reserved for globals! Please fix.
+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
+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: 2
+Got VTY event: 3
+There is no such command.
+Error occurred during reading the below line:
+ level1 b
+
+Inconsistent indentation -- leading whitespace must match adjacent lines, and
+indentation must reflect child node levels. A mix of tabs and spaces is
+allowed, but their sequence must not change within a child block.
+Error occurred during reading the below line:
+ level1 b
+
+Inconsistent indentation -- leading whitespace must match adjacent lines, and
+indentation must reflect child node levels. A mix of tabs and spaces is
+allowed, but their sequence must not change within a child block.
+Error occurred during reading the below line:
+ child1 b
+
+Error occurred during reading the below line:
+return-warning
+
+% Ignoring deprecated 'logging level depr (debug|info|notice|error|fatal)'
+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
+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
+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 6651097e..1754b67a 100644
--- a/tests/vty/vty_transcript_test.c
+++ b/tests/vty/vty_transcript_test.c
@@ -17,10 +17,6 @@
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#define _GNU_SOURCE
@@ -41,7 +37,7 @@
void *root_ctx = NULL;
-static void print_help()
+static void print_help(void)
{
printf( "options:\n"
" -h --help this text\n"
@@ -142,9 +138,34 @@ static void signal_handler(int signal)
}
}
+/* Application specific VTY attributes */
+enum {
+ TEST_ATTR_UNBELIEVABLE,
+ TEST_ATTR_MAGNIFICENT,
+ TEST_ATTR_WONDERFUL,
+ TEST_ATTR_UNUSED,
+};
+
static struct vty_app_info vty_info = {
.name = "vty_transcript_test",
.version = PACKAGE_VERSION,
+ .usr_attr_desc = {
+ /* Some random description strings, who cares... */
+ [TEST_ATTR_UNBELIEVABLE] = \
+ "Unbelievable: not able to be believed; unlikely to be true",
+ [TEST_ATTR_MAGNIFICENT] = \
+ "Magnificent: impressively beautiful, elaborate, or extravagant",
+ [TEST_ATTR_WONDERFUL] = \
+ "Wonderful: inspiring delight, pleasure, or admiration",
+ [TEST_ATTR_UNUSED] = \
+ "Intentionally unused attribute, ignore me",
+ },
+ .usr_attr_letters = {
+ [TEST_ATTR_UNBELIEVABLE] = 'u',
+ [TEST_ATTR_MAGNIFICENT] = 'm',
+ [TEST_ATTR_WONDERFUL] = 'w',
+ [TEST_ATTR_UNUSED] = 'n',
+ },
};
static const struct log_info_cat default_categories[] = {};
@@ -186,12 +207,135 @@ DEFUN(multi2, multi2_cmd,
return CMD_SUCCESS;
}
-static void init_vty_cmds()
+#define X(f) (1 << f)
+
+enum {
+ ATTR_TEST_NODE = _LAST_OSMOVTY_NODE + 1,
+};
+
+static struct cmd_node attr_test_node = {
+ ATTR_TEST_NODE,
+ "%s(config-attr-test)# ",
+ 1
+};
+
+DEFUN(cfg_attr_test, cfg_attr_test_cmd,
+ "attribute-test",
+ "Enter attribute test node\n")
+{
+ vty->index = NULL;
+ vty->node = ATTR_TEST_NODE;
+ return CMD_SUCCESS;
+}
+
+DEFUN_DEPRECATED(cfg_attr_deprecated,
+ cfg_attr_deprecated_cmd,
+ "foo-deprecated",
+ "This command is deprecated\n")
+{
+ return CMD_WARNING;
+}
+
+DEFUN_HIDDEN(cfg_attr_hidden,
+ cfg_attr_hidden_cmd,
+ "foo-hidden [expert-mode]",
+ "This command is hidden\n"
+ "But can be seen in the expert mode\n")
+{
+ return CMD_SUCCESS;
+}
+
+DEFUN_ATTR(cfg_attr_immediate, cfg_attr_immediate_cmd,
+ "foo-immediate",
+ "Applies immediately\n",
+ CMD_ATTR_IMMEDIATE)
+{
+ return CMD_SUCCESS;
+}
+
+DEFUN_ATTR(cfg_attr_node_exit, cfg_attr_node_exit_cmd,
+ "foo-node-exit",
+ "Applies on node exit\n",
+ CMD_ATTR_NODE_EXIT)
+{
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_app_attr_unbelievable,
+ cfg_app_attr_unbelievable_cmd,
+ X(TEST_ATTR_UNBELIEVABLE),
+ "app-unbelievable",
+ "Unbelievable help message\n")
+{
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_app_attr_magnificent,
+ cfg_app_attr_magnificent_cmd,
+ X(TEST_ATTR_MAGNIFICENT),
+ "app-magnificent",
+ "Magnificent help message\n")
+{
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_app_attr_wonderful,
+ cfg_app_attr_wonderful_cmd,
+ X(TEST_ATTR_WONDERFUL),
+ "app-wonderful",
+ "Wonderful help message\n")
+{
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_app_attr_unbelievable_magnificent,
+ cfg_app_attr_unbelievable_magnificent_cmd,
+ X(TEST_ATTR_UNBELIEVABLE) | X(TEST_ATTR_MAGNIFICENT),
+ "app-unbelievable-magnificent",
+ "Unbelievable & magnificent help message\n")
+{
+ return CMD_SUCCESS;
+}
+
+DEFUN_USRATTR(cfg_app_attr_unbelievable_wonderful,
+ cfg_app_attr_unbelievable_wonderful_cmd,
+ X(TEST_ATTR_UNBELIEVABLE) | X(TEST_ATTR_WONDERFUL),
+ "app-unbelievable-wonderful",
+ "Unbelievable & wonderful help message\n")
+{
+ return CMD_SUCCESS;
+}
+
+DEFUN_ATTR_USRATTR(cfg_attr_hidden_app_attr_unbelievable,
+ cfg_attr_hidden_app_attr_unbelievable_cmd,
+ CMD_ATTR_HIDDEN, X(TEST_ATTR_UNBELIEVABLE),
+ "app-hidden-unbelievable",
+ "Hidden, but still unbelievable help message\n")
+{
+ return CMD_SUCCESS;
+}
+
+static void init_vty_cmds(void)
{
install_element_ve(&single0_cmd);
install_element_ve(&multi0_cmd);
install_element_ve(&multi1_cmd);
install_element_ve(&multi2_cmd);
+
+ install_element(CONFIG_NODE, &cfg_attr_test_cmd);
+ install_node(&attr_test_node, NULL);
+ install_element(ATTR_TEST_NODE, &cfg_attr_deprecated_cmd);
+ install_element(ATTR_TEST_NODE, &cfg_attr_hidden_cmd);
+ install_element(ATTR_TEST_NODE, &cfg_attr_immediate_cmd);
+ install_element(ATTR_TEST_NODE, &cfg_attr_node_exit_cmd);
+
+ install_element(ATTR_TEST_NODE, &cfg_app_attr_unbelievable_cmd);
+ install_element(ATTR_TEST_NODE, &cfg_app_attr_magnificent_cmd);
+ install_element(ATTR_TEST_NODE, &cfg_app_attr_wonderful_cmd);
+
+ 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);
}
int main(int argc, char **argv)
@@ -217,7 +361,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 db58830e..7b8241eb 100644
--- a/tests/vty/vty_transcript_test.vty
+++ b/tests/vty/vty_transcript_test.vty
@@ -84,3 +84,96 @@ ok argc=1 one
vty_transcript_test> single0
ok argc=0
+
+vty_transcript_test> show vty-attributes
+ Global attributes:
+ ^ This command is hidden (check expert mode)
+ ! This command applies immediately
+ @ This command applies on VTY node exit
+ Library specific attributes:
+ A This command applies on ASP restart
+ I This command applies on IPA link establishment
+ L This command applies on E1 line update
+ Application specific attributes:
+ u Unbelievable: not able to be believed; unlikely to be true
+ m Magnificent: impressively beautiful, elaborate, or extravagant
+ w Wonderful: inspiring delight, pleasure, or admiration
+ n Intentionally unused attribute, ignore me
+
+vty_transcript_test> en
+vty_transcript_test# configure terminal
+vty_transcript_test(config)# attribute-test
+
+vty_transcript_test(config-attr-test)# list
+... !foo-(hidden|deprecated)
+ foo-immediate
+ foo-node-exit
+ app-unbelievable
+ app-magnificent
+ app-wonderful
+ app-unbelievable-magnificent
+ app-unbelievable-wonderful
+... !app-hidden-*
+
+vty_transcript_test(config-attr-test)# list with-flags
+... !foo-(hidden|deprecated)
+ ! ... foo-immediate
+ @ ... foo-node-exit
+ . u.. app-unbelievable
+ . .m. app-magnificent
+ . ..w app-wonderful
+ . um. app-unbelievable-magnificent
+ . u.w app-unbelievable-wonderful
+... !app-hidden-*
+
+vty_transcript_test(config-attr-test)# foo-deprecated?
+% There is no matched command.
+vty_transcript_test(config-attr-test)# foo-hidden?
+% There is no matched command.
+vty_transcript_test(config-attr-test)# app-hidden-unbelievable?
+% There is no matched command.
+
+vty_transcript_test(config-attr-test)# end
+vty_transcript_test# disable
+
+vty_transcript_test> enable?
+ enable Turn on privileged mode command
+vty_transcript_test> enable ?
+ [expert-mode] Enable the expert mode (show hidden commands)
+
+vty_transcript_test> enable expert-mode
+vty_transcript_test# configure terminal
+vty_transcript_test(config)# attribute-test
+
+vty_transcript_test(config-attr-test)# list
+... !foo-deprected
+ foo-hidden [expert-mode]
+ foo-immediate
+ foo-node-exit
+ app-unbelievable
+ app-magnificent
+ app-wonderful
+ app-unbelievable-magnificent
+ app-unbelievable-wonderful
+ app-hidden-unbelievable
+
+vty_transcript_test(config-attr-test)# list with-flags
+... !foo-deprected
+ ^ ... foo-hidden [expert-mode]
+ ! ... foo-immediate
+ @ ... foo-node-exit
+ . u.. app-unbelievable
+ . .m. app-magnificent
+ . ..w app-wonderful
+ . um. app-unbelievable-magnificent
+ . u.w app-unbelievable-wonderful
+ ^ u.. app-hidden-unbelievable
+
+vty_transcript_test(config-attr-test)# foo-deprecated?
+% There is no matched command.
+vty_transcript_test(config-attr-test)# foo-hidden?
+ foo-hidden This command is hidden
+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
diff --git a/tests/write_queue/wqueue_test.c b/tests/write_queue/wqueue_test.c
index 827e4e84..3823ef5b 100644
--- a/tests/write_queue/wqueue_test.c
+++ b/tests/write_queue/wqueue_test.c
@@ -72,7 +72,9 @@ int main(int argc, char **argv)
log_init(&log_info, NULL);
stderr_target = log_target_create_stderr();
log_add_target(stderr_target);
- log_set_print_filename(stderr_target, 0);
+ log_set_print_filename2(stderr_target, LOG_FILENAME_NONE);
+ log_set_print_category_hex(stderr_target, 0);
+ log_set_print_category(stderr_target, 0);
test_wqueue_limit();
diff --git a/utils/Makefile.am b/utils/Makefile.am
index 653b7190..6e11dcd9 100644
--- a/utils/Makefile.am
+++ b/utils/Makefile.am
@@ -1,24 +1,46 @@
-if ENABLE_UTILITIES
-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)
+bin_PROGRAMS =
+noinst_PROGRAMS =
+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
+bin_PROGRAMS += osmo-arfcn osmo-auc-gen osmo-config-merge osmo-aka-verify
osmo_arfcn_SOURCES = osmo-arfcn.c
osmo_auc_gen_SOURCES = osmo-auc-gen.c
+osmo_aka_verify_SOURCES = osmo-aka-verify.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
+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)
endif
endif
+
+if ENABLE_EXT_TESTS
+SUBDIRS = \
+ osmo-stat-dummy \
+ $(NULL)
+
+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/ctrl/libosmoctrl.la \
+ $(top_builddir)/src/gsm/libosmogsm.la
+osmo_ns_dummy_CFLAGS = $(TALLOC_CFLAGS)
+endif
+endif
diff --git a/utils/conv_codes_gsm.py b/utils/conv_codes_gsm.py
index 42f340b9..a6e471e3 100644
--- a/utils/conv_codes_gsm.py
+++ b/utils/conv_codes_gsm.py
@@ -42,6 +42,93 @@ 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/F4.8 definition
+ ConvolutionalCode(
+ 152,
+ [
+ (G1, 1),
+ (G2, 1),
+ (G3, 1),
+ ],
+ name = "tch_f48",
+ description = [
+ "TCH/F4.8 convolutional code:",
+ "152 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/conv_gen.py b/utils/conv_gen.py
index d2eda152..27ffe625 100644
--- a/utils/conv_gen.py
+++ b/utils/conv_gen.py
@@ -16,10 +16,6 @@ mod_license = """
* 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.
*/
"""
diff --git a/utils/osmo-aka-verify.c b/utils/osmo-aka-verify.c
new file mode 100644
index 00000000..f23c349b
--- /dev/null
+++ b/utils/osmo-aka-verify.c
@@ -0,0 +1,249 @@
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <getopt.h>
+
+#include <osmocom/core/utils.h>
+#include <osmocom/core/bit64gen.h>
+
+/* Utility program for implementing the SIM-side procedures of 3GPP Authentication and Key Agreement
+ * as specified by 3GPP TS 33.102 Section 6.3.3
+ *
+ * (C) 2021 by Harald Welte <laforge@gnumonks.org>
+ * Milenage library code used from libosmocore, which inherited it from wpa_supplicant
+ */
+
+/* FIXME: libosmogsm implements those, but doesn't declare them */
+int milenage_f1(const uint8_t *opc, const uint8_t *k, const uint8_t *_rand,
+ const uint8_t *sqn, const uint8_t *amf, uint8_t *mac_a, uint8_t *mac_s);
+int milenage_f2345(const uint8_t *opc, const uint8_t *k, const uint8_t *_rand,
+ uint8_t *res, uint8_t *ck, uint8_t *ik, uint8_t *ak, uint8_t *akstar);
+int milenage_opc_gen(uint8_t *opc, const uint8_t *k, const uint8_t *op);
+
+static int milenage_check(const uint8_t *opc, const uint8_t *k, const uint8_t *sqn, const uint8_t *_rand,
+ const uint8_t *autn, uint8_t *ck, uint8_t *ik, uint8_t *res, size_t *res_len,
+ uint8_t *auts)
+{
+ int i;
+ uint8_t xmac[8], ak[6], rx_sqn_bin[6];
+ unsigned long long rx_sqn;
+ const uint8_t *amf;
+
+ printf("=== Static SIM parameters:\n");
+ printf("Milenage SIM K: %s\n", osmo_hexdump_nospc(k, 16));
+ printf("Milenage SIM OPc: %s\n", osmo_hexdump_nospc(opc, 16));
+ printf("Milenage SIM SQN: %s\n", osmo_hexdump_nospc(sqn, 6));
+ printf("\n");
+
+ printf("=== Authentication Tuple as received from Network:\n");
+ printf("Milenage Input RAND: %s\n", osmo_hexdump_nospc(_rand, 16));
+ printf("Milenage Input AUTN: %s\n", osmo_hexdump_nospc(autn, 16));
+ printf("\tAUTN(+)AK: %s\n", osmo_hexdump_nospc(autn, 6));
+ printf("\tAMF: %s\n", osmo_hexdump_nospc(autn+6, 2));
+ printf("\tMAC: %s\n", osmo_hexdump_nospc(autn+8, 8));
+ printf("\n");
+
+ if (milenage_f2345(opc, k, _rand, res, ck, ik, ak, NULL))
+ return -1;
+
+ *res_len = 8;
+ printf("Milenage f2-Computed RES: %s\n", osmo_hexdump_nospc(res, *res_len));
+ printf("Milenage f3-Computed CK: %s\n", osmo_hexdump_nospc(ck, 16));
+ printf("Milenage f4-Computed IK: %s\n", osmo_hexdump_nospc(ik, 16));
+ printf("Milenage f5-Computed AK: %s\n", osmo_hexdump_nospc(ak, 6));
+
+ /* AUTN = (SQN ^ AK) || AMF || MAC */
+ for (i = 0; i < 6; i++)
+ rx_sqn_bin[i] = autn[i] ^ ak[i];
+ rx_sqn = osmo_load64be_ext(rx_sqn_bin, 6);
+ printf("Milenage Computed SQN: %s (%llu)\n", osmo_hexdump_nospc(rx_sqn_bin, 6), rx_sqn);
+
+ if (memcmp(rx_sqn_bin, sqn, 6) <= 0) {
+ printf("Milenage: RX-SQN differs from SIM SQN: Re-Sync!\n");
+ uint8_t auts_amf[2] = { 0x00, 0x00 }; /* TS 33.102 v7.0.0, 6.3.3 */
+ if (milenage_f2345(opc, k, _rand, NULL, NULL, NULL, NULL, ak))
+ return -1;
+ printf("Milenage Computed AK*: %s", osmo_hexdump_nospc(ak, 6));
+ for (i = 0; i < 6; i++)
+ auts[i] = sqn[i] ^ ak[i];
+ if (milenage_f1(opc, k, _rand, sqn, auts_amf, NULL, auts + 6))
+ return -1;
+ printf("Milenage AUTS: %s\n", osmo_hexdump_nospc(auts, 14));
+ return -2;
+ }
+
+ amf = autn + 6;
+ if (milenage_f1(opc, k, _rand, rx_sqn_bin, amf, xmac, NULL))
+ return -1;
+
+ printf("Milenage f1-Computed XMAC: %s\n", osmo_hexdump_nospc(xmac, 8));
+
+ if (memcmp(xmac, autn + 8, 8) != 0) {
+ fprintf(stderr, "Milenage: MAC mismatch!\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+
+static void help(void)
+{
+ printf( "Static SIM card parameters:\n"
+ "-k --key\tSpecify Ki / K\n"
+ "-o --opc\tSpecify OPC\n"
+ "-O --op\tSpecify OP\n"
+ "-f --amf\tSpecify AMF\n"
+ "-s --sqn\tSpecify SQN\n"
+ "\n"
+ "Authentication Tuple by network:\n"
+ //"-i --ind\tSpecify IND slot for new SQN after AUTS\n"
+ //"-l --ind-len\tSpecify IND bit length (default=5)\n"
+ "-r --rand\tSpecify RAND random value\n"
+ "-A --autn\tSpecify AUTN authentication nonce\n"
+ );
+}
+
+static uint8_t g_k[16];
+static uint8_t g_opc[16];
+static uint8_t g_rand[16];
+static uint8_t g_autn[16];
+static uint8_t g_amf[16];
+static unsigned long long g_sqn;
+
+
+static int handle_options(int argc, char **argv)
+{
+ int rc, option_index;
+ bool rand_is_set = false;
+ bool autn_is_set = false;
+ bool sqn_is_set = false;
+ bool k_is_set = false;
+ bool opc_is_set = false;
+ bool amf_is_set = false;
+ bool opc_is_op = false;
+ int64_t val64;
+
+ while (1) {
+ int c;
+ static struct option long_options[] = {
+ { "key", 1, 0, 'k' },
+ { "opc", 1, 0, 'o' },
+ { "op", 1, 0, 'O' },
+ { "amf", 1, 0, 'f' },
+ { "sqn", 1, 0, 's' },
+ { "rand", 1, 0, 'r' },
+ { "autn", 1, 0, 'A' },
+ { "help", 0, 0, 'h' },
+ { 0, 0, 0, 0 }
+ };
+
+ rc = 0;
+
+ c = getopt_long(argc, argv, "k:o:O:f:s:r:A:h", long_options, &option_index);
+
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'k':
+ rc = osmo_hexparse(optarg, g_k, sizeof(g_k));
+ k_is_set = true;
+ break;
+ case 'o':
+ rc = osmo_hexparse(optarg, g_opc, sizeof(g_opc));
+ opc_is_op = false;
+ opc_is_set = true;
+ break;
+ case 'O':
+ rc = osmo_hexparse(optarg, g_opc, sizeof(g_opc));
+ opc_is_op = true;
+ opc_is_set = true;
+ break;
+ case 'A':
+ rc = osmo_hexparse(optarg, g_autn, sizeof(g_autn));
+ autn_is_set = true;
+ break;
+ case 'f':
+ rc = osmo_hexparse(optarg, g_amf, sizeof(g_amf));
+ amf_is_set = true;
+ break;
+ case 's':
+ rc = osmo_str_to_int64(&val64, optarg, 10, 0, INT64_MAX);
+ g_sqn = (unsigned long long)val64;
+ sqn_is_set = true;
+ break;
+ case 'r':
+ rc = osmo_hexparse(optarg, g_rand, sizeof(g_rand));
+ rand_is_set = true;
+ break;
+ case 'h':
+ help();
+ exit(0);
+ default:
+ help();
+ exit(1);
+ }
+
+ if (rc < 0) {
+ help();
+ fprintf(stderr, "\nError parsing argument of option `%c'\n", c);
+ exit(2);
+ }
+ }
+
+ if (!k_is_set || !opc_is_set || !autn_is_set || !rand_is_set) {
+ fprintf(stderr, "Error: K, OP[c], AUTN and RAND are mandatory arguments\n");
+ fprintf(stderr, "\n");
+ help();
+ exit(2);
+ }
+
+ if (!sqn_is_set)
+ printf("Warning: You may want to specify SQN\n");
+
+ if (!amf_is_set)
+ printf("Warning: You may want to specify AMF\n");
+
+ if (opc_is_op) {
+ uint8_t op[16];
+ memcpy(op, g_opc, 16);
+ rc = milenage_opc_gen(g_opc, g_k, op);
+ OSMO_ASSERT(rc == 0);
+ }
+
+ return 0;
+}
+
+
+
+int main(int argc, char **argv)
+{
+ printf("osmo-aka-check (C) 2021 by Harald Welte\n");
+ printf("This is FREE SOFTWARE with ABSOLUTELY NO WARRANTY\n\n");
+
+ handle_options(argc, argv);
+
+ printf("\n");
+
+ uint8_t ck[16];
+ uint8_t ik[16];
+ uint8_t res[16];
+ size_t res_len;
+ uint8_t auts[14];
+ uint8_t sqn_bin[6];
+ int rc;
+
+ osmo_store64be_ext(g_sqn, sqn_bin, 6);
+
+ rc = milenage_check(g_opc, g_k, sqn_bin, g_rand, g_autn, ck, ik, res, &res_len, auts);
+
+ if (rc < 0) {
+ fprintf(stderr, "Authentication FAILED!\n");
+ exit(1);
+ } else {
+ printf("Authentication SUCCEEDED\n");
+ exit(0);
+ }
+}
diff --git a/utils/osmo-arfcn.c b/utils/osmo-arfcn.c
index 5f138f8c..3d4fb507 100644
--- a/utils/osmo-arfcn.c
+++ b/utils/osmo-arfcn.c
@@ -15,10 +15,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <stdio.h>
@@ -97,7 +93,7 @@ int main(int argc, char **argv)
{
int arfcn, freq, pcs = 0, uplink = -1;
int opt;
- char *param;
+ char *param = NULL;
enum program_mode mode = MODE_NONE;
while ((opt = getopt(argc, argv, "pa:f:udh")) != -1) {
diff --git a/utils/osmo-auc-gen.c b/utils/osmo-auc-gen.c
index 65cfa310..e3e1b431 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-2012 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2010-2021 by Harald Welte <laforge@gnumonks.org>
*
* All Rights Reserved
*
@@ -15,10 +15,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
@@ -34,8 +30,20 @@
#include <osmocom/crypt/auth.h>
#include <osmocom/core/utils.h>
+#include <osmocom/core/base64.h>
#include <osmocom/gsm/gsm_utils.h>
+static void print_base64(const char *fmt, const uint8_t *data, unsigned int len)
+{
+ uint8_t outbuf[256];
+ size_t olen;
+
+ OSMO_ASSERT(osmo_base64_encode(outbuf, sizeof(outbuf), &olen, data, len) == 0);
+ OSMO_ASSERT(sizeof(outbuf) > olen);
+ outbuf[olen] = '\0';
+ printf(fmt, outbuf);
+}
+
static void dump_triplets_dat(struct osmo_auth_vector *vec)
{
if (vec->auth_types & OSMO_AUTH_TYPE_UMTS) {
@@ -53,10 +61,17 @@ static void dump_auth_vec(struct osmo_auth_vector *vec)
printf("RAND:\t%s\n", osmo_hexdump_nospc(vec->rand, sizeof(vec->rand)));
if (vec->auth_types & OSMO_AUTH_TYPE_UMTS) {
+ uint8_t inbuf[sizeof(vec->rand) + sizeof(vec->autn)];
+
printf("AUTN:\t%s\n", osmo_hexdump_nospc(vec->autn, sizeof(vec->autn)));
printf("IK:\t%s\n", osmo_hexdump_nospc(vec->ik, sizeof(vec->ik)));
printf("CK:\t%s\n", osmo_hexdump_nospc(vec->ck, sizeof(vec->ck)));
printf("RES:\t%s\n", osmo_hexdump_nospc(vec->res, vec->res_len));
+
+ memcpy(inbuf, vec->rand, sizeof(vec->rand));
+ memcpy(inbuf + sizeof(vec->rand), vec->autn, sizeof(vec->autn));
+ print_base64("IMS nonce:\t%s\n", inbuf, sizeof(inbuf));
+ print_base64("IMS res:\t%s\n", vec->res, vec->res_len);
}
if (vec->auth_types & OSMO_AUTH_TYPE_GSM) {
@@ -70,7 +85,7 @@ static struct osmo_sub_auth_data test_aud = {
.algo = OSMO_AUTH_ALG_NONE,
};
-static void help()
+static void help(void)
{
int alg;
printf( "-2 --2g\tUse 2G (GSM) authentication\n"
@@ -207,7 +222,7 @@ int main(int argc, char **argv)
fprintf(stderr, "Only UMTS has SQN\n");
exit(2);
}
- sqn = strtoull(optarg, 0, 10);
+ sqn = strtoull(optarg, 0, 0);
sqn_is_set = 1;
break;
case 'i':
diff --git a/utils/osmo-config-merge.c b/utils/osmo-config-merge.c
index 477bb028..ed2039ac 100644
--- a/utils/osmo-config-merge.c
+++ b/utils/osmo-config-merge.c
@@ -15,10 +15,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
/*
diff --git a/utils/osmo-ns-dummy-vty.c b/utils/osmo-ns-dummy-vty.c
new file mode 100644
index 00000000..2e59b11b
--- /dev/null
+++ b/utils/osmo-ns-dummy-vty.c
@@ -0,0 +1,325 @@
+/* VTY for osmo-ns-dummy */
+
+/* (C) 2021 Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <errno.h>
+#include <string.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <inttypes.h>
+
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/application.h>
+#include <osmocom/core/stats.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/logging.h>
+
+#include <osmocom/gsm/prim.h>
+#include <osmocom/gprs/gprs_ns2.h>
+
+#include <osmocom/vty/vty.h>
+#include <osmocom/vty/command.h>
+#include <osmocom/vty/logging.h>
+#include <osmocom/vty/stats.h>
+#include <osmocom/vty/misc.h>
+
+extern struct gprs_ns2_inst *g_nsi;
+static struct llist_head g_ns_traf_gens = LLIST_HEAD_INIT(g_ns_traf_gens);
+int g_mirror_mode;
+
+/* one NS traffic generator instance. You can have as many of these as you want,
+ * just as long as they have unique names */
+struct ns_traf_gen {
+ struct llist_head list;
+ const char *name;
+ struct {
+ uint16_t nsei;
+ uint16_t bvci;
+ /* size of each packet */
+ uint16_t pkt_size;
+ /* interval between packets in us */
+ uint32_t interval_us;
+ /* fixed (false) or random (true) LSP */
+ bool lsp_randomize;
+ /* (fixeD) Link Selector Parameter */
+ uint32_t lsp;
+ } cfg;
+ struct osmo_fd timerfd;
+ bool running;
+};
+
+#define LOGNTG(ntg, lvl, fmt, args ...) \
+ LOGP(DLGLOBAL, lvl, "traf-gen(%s): " fmt, (ntg)->name, ## args)
+
+/* allocate and transmit one NS message */
+static int ntg_tx_one(struct ns_traf_gen *ntg)
+{
+ struct osmo_gprs_ns2_prim nsp = {};
+ struct msgb *msg = msgb_alloc_headroom(3072, 20, "NS traffic gen");
+
+ if (!msg)
+ return -ENOMEM;
+ msgb_put(msg, ntg->cfg.pkt_size);
+ nsp.bvci = ntg->cfg.bvci;
+ nsp.nsei = ntg->cfg.nsei;
+ if (ntg->cfg.lsp_randomize)
+ nsp.u.unitdata.link_selector = rand();
+ else
+ nsp.u.unitdata.link_selector = ntg->cfg.lsp;
+ osmo_prim_init(&nsp.oph, SAP_NS, GPRS_NS2_PRIM_UNIT_DATA, PRIM_OP_REQUEST, msg);
+ return gprs_ns2_recv_prim(g_nsi, &nsp.oph);
+}
+
+/* call-back from transmit timer-fd */
+static int ntg_timerfd_cb(struct osmo_fd *ofd, unsigned int what)
+{
+ uint64_t expire_count;
+ struct ns_traf_gen *ntg = ofd->data;
+ unsigned int i;
+ int rc;
+
+ OSMO_ASSERT(what & OSMO_FD_READ);
+
+ rc = read(ofd->fd, (void *) &expire_count, sizeof(expire_count));
+ if (rc < 0 && errno == EAGAIN)
+ return 0;
+ OSMO_ASSERT(rc == sizeof(expire_count));
+
+ for (i = 0; i < expire_count; i++)
+ ntg_tx_one(ntg);
+
+ return 0;
+}
+
+static struct ns_traf_gen *ns_traf_gen_find(const char *name)
+{
+ struct ns_traf_gen *ntg;
+
+ llist_for_each_entry(ntg, &g_ns_traf_gens, list) {
+ if (!strcmp(ntg->name, name))
+ return ntg;
+ }
+ return NULL;
+}
+
+static struct ns_traf_gen *ns_traf_gen_find_or_alloc(const char *name)
+{
+ struct ns_traf_gen *ntg;
+ int rc;
+
+ ntg = ns_traf_gen_find(name);
+ if (ntg)
+ return ntg;
+
+ ntg = talloc_zero(g_nsi, struct ns_traf_gen);
+ OSMO_ASSERT(ntg);
+ ntg->name = talloc_strdup(ntg, name);
+ ntg->timerfd.fd = -1;
+ rc = osmo_timerfd_setup(&ntg->timerfd, ntg_timerfd_cb, ntg);
+ OSMO_ASSERT(rc >= 0);
+ llist_add_tail(&ntg->list, &g_ns_traf_gens);
+
+ return ntg;
+}
+
+enum nodes {
+ NTG_NODE = _LAST_OSMOVTY_NODE + 1,
+};
+
+static struct cmd_node ntg_node = {
+ NTG_NODE,
+ "%s(config-ns-traf-gen)# ",
+ 1,
+};
+
+static int config_write_ntg(struct vty *vty)
+{
+ struct ns_traf_gen *ntg;
+
+ llist_for_each_entry(ntg, &g_ns_traf_gens, list) {
+ vty_out(vty, "ns-traffic-generator %s%s", ntg->name, VTY_NEWLINE);
+ vty_out(vty, " nsei %u%s", ntg->cfg.nsei, VTY_NEWLINE);
+ vty_out(vty, " bvci %u%s", ntg->cfg.bvci, VTY_NEWLINE);
+ vty_out(vty, " packet-size %u%s", ntg->cfg.pkt_size, VTY_NEWLINE);
+ vty_out(vty, " interval-us %u%s", ntg->cfg.interval_us, VTY_NEWLINE);
+ vty_out(vty, " lsp %u%s", ntg->cfg.lsp, VTY_NEWLINE);
+ vty_out(vty, " lsp-mode %s%s", ntg->cfg.lsp_randomize ? "randomized" : "fixed", VTY_NEWLINE);
+ }
+ vty_out(vty, "mirror-mode %s%s", g_mirror_mode ? "enable" : "disable", VTY_NEWLINE);
+
+ return 0;
+}
+
+DEFUN(ntg_start, ntg_start_stop_cmd,
+ "ns-traffic-generator (start|stop) NAME",
+ "Control named NS traffic generator\n"
+ "Start generating traffic in this traffic generator\n"
+ "Stop generating traffic in this traffic generator\n"
+ "Name of NS traffic generator to start\n")
+{
+ struct ns_traf_gen *ntg = ns_traf_gen_find(argv[1]);
+ if (!ntg) {
+ vty_out(vty, "NS Traffic generator '%s' doesn't exist%s", argv[1], VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (!strcmp(argv[0], "start")) {
+ struct timespec interval;
+ if (ntg->running) {
+ vty_out(vty, "NS Traffic generator was already started%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ interval.tv_sec = ntg->cfg.interval_us / 1000000;
+ interval.tv_nsec = (ntg->cfg.interval_us % 1000000) * 1000;
+ osmo_timerfd_schedule(&ntg->timerfd, NULL, &interval);
+ ntg->running = true;
+ } else {
+ if (!ntg->running) {
+ vty_out(vty, "NS Traffic generator was already stopped%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ osmo_timerfd_disable(&ntg->timerfd);
+ ntg->running = false;
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(ntg_nsei, ntg_nsei_cmd,
+ "nsei <0-65535>",
+ "NSEI to use when generating traffic\n"
+ "NSEI to use when generating traffic\n")
+{
+ struct ns_traf_gen *ntg = vty->index;
+ ntg->cfg.nsei = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(ntg_bvci, ntg_bvci_cmd,
+ "bvci <0-65535>",
+ "BVCI to use when generating traffic\n"
+ "BVCI to use when generating traffic\n")
+{
+ struct ns_traf_gen *ntg = vty->index;
+ ntg->cfg.bvci = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(ntg_pkt_size, ntg_pkt_size_cmd,
+ "packet-size <0-2048>",
+ "Packet size for generated NS-UNITDATA payload\n"
+ "Packet size for generated NS-UNITDATA payload\n")
+{
+ struct ns_traf_gen *ntg = vty->index;
+ ntg->cfg.pkt_size = atoi(argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(ntg_pkt_intv_us, ntg_pkt_intv_us_cmd,
+ "interval-us <0-1000000>",
+ "Interval between packets in microseconds\n"
+ "Interval between packets in microseconds\n")
+{
+ struct ns_traf_gen *ntg = vty->index;
+ ntg->cfg.interval_us = atoi(argv[0]);
+ if (ntg->running) {
+ /* TODO: update timer */
+ }
+ return CMD_SUCCESS;
+}
+
+DEFUN(ntg_lsp, ntg_lsp_cmd,
+ "lsp <0-4294967295>",
+ "Link Selector Parameter (only used in fixed mode)\n"
+ "Link Selector Parameter (only used in fixed mode)\n")
+{
+ struct ns_traf_gen *ntg = vty->index;
+ ntg->cfg.lsp = strtoul(argv[0], NULL, 10);
+ return CMD_SUCCESS;
+}
+
+DEFUN(ntg_lsp_mode, ntg_lsp_mode_cmd,
+ "lsp-mode (fixed|randomized)",
+ "Link Selector Parameter Mode\n"
+ "Fixed / Staic LSP\n"
+ "Randomized LSP\n")
+{
+ struct ns_traf_gen *ntg = vty->index;
+ if (!strcmp(argv[0], "randomized"))
+ ntg->cfg.lsp_randomize = true;
+ else
+ ntg->cfg.lsp_randomize = false;
+ return CMD_SUCCESS;
+}
+
+DEFUN(gen_traffic, gen_traffic_cmd,
+ "ns-traffic-generator NAME",
+ "Configure a given NS traffic generator\n" "Name of NS traffic generator\n")
+{
+ struct ns_traf_gen *ntg = ns_traf_gen_find_or_alloc(argv[0]);
+
+ if (!ntg)
+ return CMD_WARNING;
+
+ vty->index = ntg;
+ vty->node = NTG_NODE;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(mirror_mode, mirror_mode_cmd,
+ "mirror-mode (enable|disable)",
+ "Configure mirroring of incoming NS-UNITDATA\n"
+ "Enable mirroring of incoming NS-UNITDATA\n"
+ "Disable mirroring of incoming NS-UNITDATA\n")
+{
+ if (!strcmp(argv[0], "enable"))
+ g_mirror_mode = true;
+ else
+ g_mirror_mode = false;
+
+ return CMD_SUCCESS;
+}
+
+
+int nsdummy_vty_init(void)
+{
+ /* configuration of traffic generators via CONFIG / NTG node */
+ install_element(CONFIG_NODE, &gen_traffic_cmd);
+ install_element(CONFIG_NODE, &mirror_mode_cmd);
+ install_node(&ntg_node, config_write_ntg);
+ install_element(NTG_NODE, &ntg_nsei_cmd);
+ install_element(NTG_NODE, &ntg_bvci_cmd);
+ install_element(NTG_NODE, &ntg_pkt_size_cmd);
+ install_element(NTG_NODE, &ntg_pkt_intv_us_cmd);
+ install_element(NTG_NODE, &ntg_lsp_cmd);
+ install_element(NTG_NODE, &ntg_lsp_mode_cmd);
+
+ /* starting/stopping the traffic generators is in 'enable' mode, not 'config' */
+ install_element(ENABLE_NODE, &ntg_start_stop_cmd);
+
+ return 0;
+}
diff --git a/utils/osmo-ns-dummy.c b/utils/osmo-ns-dummy.c
new file mode 100644
index 00000000..890444cf
--- /dev/null
+++ b/utils/osmo-ns-dummy.c
@@ -0,0 +1,316 @@
+
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <getopt.h>
+#include <signal.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/gprs/gprs_ns2.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_nsdummy_ctx = NULL;
+static struct log_info log_info = {};
+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;
+
+static const char vty_copyright[] =
+ "Copyright (C) 2020 by by sysmocom - s.f.m.c. GmbH\r\n"
+ "Author: Alexander Couzens <lynxis@fe80.eu>\r\n"
+ "License GNU GPL version 2 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";
+
+static struct vty_app_info vty_info = {
+ .name = "OsmoNSdummy",
+ .version = PACKAGE_VERSION,
+ .copyright = vty_copyright,
+};
+
+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"
+ " -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"
+ );
+}
+
+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 void handle_options(int argc, char **argv)
+{
+ 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' },
+ { "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:r:VD",
+ 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 'p':
+ vty_port = atoi(optarg);
+ if (vty_port < 0 || vty_port > 65535) {
+ 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;
+ case 'V':
+ print_version(1);
+ exit(0);
+ break;
+ case 'D':
+ daemonize = true;
+ break;
+ default:
+ fprintf(stderr, "Unknown option '%c'\n", c);
+ exit(0);
+ break;
+ }
+ }
+
+ if (!config_file)
+ config_file = "osmo-ns-dummy.cfg";
+ if (!vty_port) {
+ fprintf(stderr, "A vty port need to be specified (-p)\n");
+ exit(1);
+ }
+}
+
+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_nsdummy_ctx, stderr);
+ signal(SIGABRT, SIG_DFL);
+ raise(SIGABRT);
+ break;
+ case SIGUSR1:
+ case SIGUSR2:
+ talloc_report_full(tall_nsdummy_ctx, stderr);
+ break;
+ }
+}
+
+extern int g_mirror_mode;
+
+/* called by the ns layer */
+int gprs_ns_prim_cb(struct osmo_prim_hdr *oph, void *ctx)
+{
+ struct osmo_gprs_ns2_prim *nsp = container_of(oph, struct osmo_gprs_ns2_prim, oph);
+
+ switch (oph->primitive) {
+ case GPRS_NS2_PRIM_UNIT_DATA:
+ if (g_mirror_mode) {
+ /* simply switch indication->request and resubmit */
+ oph->operation = PRIM_OP_REQUEST;
+ msgb_pull_to_l3(oph->msg);
+ nsp->u.unitdata.link_selector = rand(); /* ensure random distribution */
+ return gprs_ns2_recv_prim(g_nsi, oph);
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (oph->msg)
+ msgb_free(oph->msg);
+
+ return 0;
+}
+
+int bssgp_prim_cb(struct osmo_prim_hdr *oph, void *ctx)
+{
+ return 0;
+}
+
+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);
+ 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);
+ osmo_stats_init(ctx);
+ rate_ctr_init(ctx);
+
+ 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();
+
+ handle_options(argc, argv);
+
+ g_nsi = gprs_ns2_instantiate(ctx, gprs_ns_prim_cb, NULL);
+ if (!g_nsi) {
+ LOGP(DLNS, LOGL_ERROR, "Failed to create NS instance\n");
+ exit(1);
+ }
+
+ gprs_ns2_vty_init(g_nsi);
+ nsdummy_vty_init();
+ rc = vty_read_config_file(config_file, NULL);
+ if (rc < 0 && config_given) {
+ fprintf(stderr, "Failed to parse the config file: '%s'\n",
+ config_file);
+ exit(1);
+ }
+ if (rc < 0)
+ fprintf(stderr, "No config file: '%s' Using default config.\n",
+ config_file);
+
+ 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);
+ signal(SIGABRT, sighandler);
+ signal(SIGUSR1, sighandler);
+ signal(SIGUSR2, sighandler);
+ osmo_init_ignore_signals();
+
+ if (daemonize) {
+ rc = osmo_daemonize();
+ if (rc < 0) {
+ perror("Error during daemonize");
+ exit(1);
+ }
+ }
+
+ while (!quit) {
+ osmo_select_main(0);
+ }
+
+ telnet_exit();
+ gprs_ns2_free(g_nsi);
+
+ talloc_report_full(tall_nsdummy_ctx, stderr);
+ talloc_free(tall_nsdummy_ctx);
+
+ return 0;
+}
diff --git a/utils/osmo-sim-test.c b/utils/osmo-sim-test.c
index ae55b83e..2c6c7641 100644
--- a/utils/osmo-sim-test.c
+++ b/utils/osmo-sim-test.c
@@ -12,10 +12,6 @@
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- *
*/
#include <stdio.h>
diff --git a/utils/osmo-stat-dummy/Makefile.am b/utils/osmo-stat-dummy/Makefile.am
new file mode 100644
index 00000000..f1faa3d5
--- /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 = $(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) $(LIBOSMOCORE_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMOCTRL_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>