diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..e01642c
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,59 @@
+*.o
+*.a
+*.lo
+*.la
+.deps
+Makefile
+Makefile.in
+config.h
+config.h.in
+
+#configure
+aclocal.m4
+autom4te.cache/
+compile
+config.log
+config.status
+configure
+configure.lineno
+depcomp
+install-sh
+missing
+stamp-h1
+
+# libtool
+ltmain.sh
+libtool
+.libs
+
+# git-version-gen magic
+.tarball-version
+.version
+
+# apps and app data
+src/osmo-cbc
+
+#tests
+tests/*/*_test
+
+tests/atconfig
+tests/package.m4
+tests/testsuite
+tests/testsuite.log
+
+*.pc
+config.*
+
+tags
+/Doxyfile
+
+# manuals
+doc/manuals/*.html
+doc/manuals/*.svg
+doc/manuals/*.pdf
+doc/manuals/*__*.png
+doc/manuals/*.check
+doc/manuals/generated/
+doc/manuals/osmomsc-usermanual.xml
+doc/manuals/common
+doc/manuals/build
diff --git a/Makefile.am b/Makefile.am
new file mode 100644
index 0000000..11a2b40
--- /dev/null
+++ b/Makefile.am
@@ -0,0 +1,17 @@
+AUTOMAKE_OPTIONS = foreign dist-bzip2 1.6
+
+AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/include
+SUBDIRS = src
+
+EXTRA_DIST = .version git-version-gen osmoappdesc.py doc/examples/osmo-cbc.cfg
+
+AM_DISTCHECK_CONFIGURE_FLAGS = \
+ --with-systemdsystemunitdir=$$dc_install_base/$(systemdsystemunitdir)
+
+@RELMAKE@
+
+BUILT_SOURCES = $(top_srcdir)/.version
+$(top_srcdir)/.version:
+ echo $(VERSION) > $@-t && mv $@-t $@
+dist-hook:
+ echo $(VERSION) > $(distdir)/.tarball-version
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 0000000..3e7ac42
--- /dev/null
+++ b/configure.ac
@@ -0,0 +1,169 @@
+dnl Process this file with autoconf to produce a configure script
+AC_INIT([osmo-cbc],
+ m4_esyscmd([./git-version-gen .tarball-version]),
+ [openbsc@lists.osmocom.org])
+
+dnl *This* is the root dir, even if an install-sh exists in ../ or ../../
+AC_CONFIG_AUX_DIR([.])
+
+AM_INIT_AUTOMAKE([dist-bzip2])
+AC_CONFIG_TESTDIR(tests)
+
+dnl kernel style compile messages
+m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])
+
+dnl include release helper
+RELMAKE='-include osmo-release.mk'
+AC_SUBST([RELMAKE])
+
+dnl checks for programs
+AC_PROG_MAKE_SET
+AC_PROG_CC
+AC_PROG_INSTALL
+LT_INIT
+
+dnl check for pkg-config (explained in detail in libosmocore/configure.ac)
+AC_PATH_PROG(PKG_CONFIG_INSTALLED, pkg-config, no)
+if test "x$PKG_CONFIG_INSTALLED" = "xno"; then
+ AC_MSG_WARN([You need to install pkg-config])
+fi
+PKG_PROG_PKG_CONFIG([0.20])
+
+PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore >= 1.0.0)
+PKG_CHECK_MODULES(LIBOSMOVTY, libosmovty >= 1.0.0)
+PKG_CHECK_MODULES(LIBOSMOGSM, libosmogsm >= 1.0.0)
+PKG_CHECK_MODULES(LIBOSMONETIF, libosmo-netif >= 0.4.0)
+
+AC_ARG_ENABLE(sanitize,
+ [AS_HELP_STRING(
+ [--enable-sanitize],
+ [Compile with address sanitizer enabled],
+ )],
+ [sanitize=$enableval], [sanitize="no"])
+if test x"$sanitize" = x"yes"
+then
+ CFLAGS="$CFLAGS -fsanitize=address -fsanitize=undefined"
+ CPPFLAGS="$CPPFLAGS -fsanitize=address -fsanitize=undefined"
+fi
+
+AC_ARG_ENABLE(werror,
+ [AS_HELP_STRING(
+ [--enable-werror],
+ [Turn all compiler warnings into errors, with exceptions:
+ a) deprecation (allow upstream to mark deprecation without breaking builds);
+ b) "#warning" pragmas (allow to remind ourselves of errors without breaking builds)
+ ]
+ )],
+ [werror=$enableval], [werror="no"])
+if test x"$werror" = x"yes"
+then
+ WERROR_FLAGS="-Werror"
+ WERROR_FLAGS+=" -Wno-error=deprecated -Wno-error=deprecated-declarations"
+ WERROR_FLAGS+=" -Wno-error=cpp" # "#warning"
+ CFLAGS="$CFLAGS $WERROR_FLAGS"
+ CPPFLAGS="$CPPFLAGS $WERROR_FLAGS"
+fi
+
+# The following test is taken from WebKit's webkit.m4
+saved_CFLAGS="$CFLAGS"
+CFLAGS="$CFLAGS -fvisibility=hidden "
+AC_MSG_CHECKING([if ${CC} supports -fvisibility=hidden])
+AC_COMPILE_IFELSE([AC_LANG_SOURCE([char foo;])],
+ [ AC_MSG_RESULT([yes])
+ SYMBOL_VISIBILITY="-fvisibility=hidden"],
+ AC_MSG_RESULT([no]))
+CFLAGS="$saved_CFLAGS"
+AC_SUBST(SYMBOL_VISIBILITY)
+
+CFLAGS="$CFLAGS -Wall"
+CPPFLAGS="$CPPFLAGS -Wall"
+
+AC_ARG_ENABLE(doxygen,
+ [AS_HELP_STRING(
+ [--disable-doxygen],
+ [Disable generation of documentation using doxygen],
+ )],
+ [doxygen=$enableval], [doxygen="yes"])
+AC_PATH_PROG(DOXYGEN,doxygen,false)
+AM_CONDITIONAL(HAVE_DOXYGEN, test $DOXYGEN != false && test "x$doxygen" = "xyes")
+
+# Generate manuals
+AC_ARG_ENABLE(manuals,
+ [AS_HELP_STRING(
+ [--enable-manuals],
+ [Generate manual PDFs [default=no]],
+ )],
+ [osmo_ac_build_manuals=$enableval], [osmo_ac_build_manuals="no"])
+AM_CONDITIONAL([BUILD_MANUALS], [test x"$osmo_ac_build_manuals" = x"yes"])
+AC_ARG_VAR(OSMO_GSM_MANUALS_DIR, [path to common osmo-gsm-manuals files, overriding pkg-config and "../osmo-gsm-manuals"
+ fallback])
+if test x"$osmo_ac_build_manuals" = x"yes"
+then
+ # Find OSMO_GSM_MANUALS_DIR (env, pkg-conf, fallback)
+ if test -n "$OSMO_GSM_MANUALS_DIR"; then
+ echo "checking for OSMO_GSM_MANUALS_DIR... $OSMO_GSM_MANUALS_DIR (from env)"
+ else
+ OSMO_GSM_MANUALS_DIR="$($PKG_CONFIG osmo-gsm-manuals --variable=osmogsmmanualsdir 2>/dev/null)"
+ if test -n "$OSMO_GSM_MANUALS_DIR"; then
+ echo "checking for OSMO_GSM_MANUALS_DIR... $OSMO_GSM_MANUALS_DIR (from pkg-conf)"
+ else
+ OSMO_GSM_MANUALS_DIR="../osmo-gsm-manuals"
+ echo "checking for OSMO_GSM_MANUALS_DIR... $OSMO_GSM_MANUALS_DIR (fallback)"
+ fi
+ fi
+ if ! test -d "$OSMO_GSM_MANUALS_DIR"; then
+ AC_MSG_ERROR("OSMO_GSM_MANUALS_DIR does not exist! Install osmo-gsm-manuals or set OSMO_GSM_MANUALS_DIR.")
+ fi
+
+ # Find and run check-depends
+ CHECK_DEPENDS="$OSMO_GSM_MANUALS_DIR/check-depends.sh"
+ if ! test -x "$CHECK_DEPENDS"; then
+ CHECK_DEPENDS="osmo-gsm-manuals-check-depends"
+ fi
+ if ! $CHECK_DEPENDS; then
+ AC_MSG_ERROR("missing dependencies for --enable-manuals")
+ fi
+
+ # Put in Makefile with absolute path
+ OSMO_GSM_MANUALS_DIR="$(realpath "$OSMO_GSM_MANUALS_DIR")"
+ AC_SUBST([OSMO_GSM_MANUALS_DIR])
+fi
+
+# https://www.freedesktop.org/software/systemd/man/daemon.html
+AC_ARG_WITH([systemdsystemunitdir],
+ [AS_HELP_STRING([--with-systemdsystemunitdir=DIR], [Directory for systemd service files])],,
+ [with_systemdsystemunitdir=auto])
+AS_IF([test "x$with_systemdsystemunitdir" = "xyes" -o "x$with_systemdsystemunitdir" = "xauto"], [
+ def_systemdsystemunitdir=$($PKG_CONFIG --variable=systemdsystemunitdir systemd)
+ AS_IF([test "x$def_systemdsystemunitdir" = "x"],
+ [AS_IF([test "x$with_systemdsystemunitdir" = "xyes"],
+ [AC_MSG_ERROR([systemd support requested but pkg-config unable to query systemd package])])
+ with_systemdsystemunitdir=no],
+ [with_systemdsystemunitdir="$def_systemdsystemunitdir"])])
+AS_IF([test "x$with_systemdsystemunitdir" != "xno"],
+ [AC_SUBST([systemdsystemunitdir], [$with_systemdsystemunitdir])])
+AM_CONDITIONAL([HAVE_SYSTEMD], [test "x$with_systemdsystemunitdir" != "xno"])
+
+AC_ARG_ENABLE([external_tests],
+ AC_HELP_STRING([--enable-external-tests],
+ [Include the VTY/CTRL tests in make check [default=no]]),
+ [enable_ext_tests="$enableval"],[enable_ext_tests="no"])
+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.])
+ fi
+fi
+AC_MSG_CHECKING([whether to enable VTY/CTRL tests])
+AC_MSG_RESULT([$enable_ext_tests])
+AM_CONDITIONAL(ENABLE_EXT_TESTS, test "x$enable_ext_tests" = "xyes")
+
+AC_MSG_RESULT([CFLAGS="$CFLAGS"])
+AC_MSG_RESULT([CPPFLAGS="$CPPFLAGS"])
+
+AC_OUTPUT(
+ src/Makefile
+ contrib/Makefile
+ contrib/systemd/Makefile
+ Makefile)
diff --git a/contrib/Makefile.am b/contrib/Makefile.am
new file mode 100644
index 0000000..3439c97
--- /dev/null
+++ b/contrib/Makefile.am
@@ -0,0 +1 @@
+SUBDIRS = systemd
diff --git a/contrib/jenkins.sh b/contrib/jenkins.sh
new file mode 100755
index 0000000..0f7a5f9
--- /dev/null
+++ b/contrib/jenkins.sh
@@ -0,0 +1,61 @@
+#!/bin/sh
+# jenkins build helper script for osmo-cbc. This is how we build on jenkins.osmocom.org
+#
+# environment variables:
+# * WITH_MANUALS: build manual PDFs if set to "1"
+# * PUBLISH: upload manuals after building if set to "1" (ignored without WITH_MANUALS = "1")
+#
+
+if ! [ -x "$(command -v osmo-build-dep.sh)" ]; then
+ echo "Error: We need to have scripts/osmo-deps.sh from http://git.osmocom.org/osmo-ci/ in PATH !"
+ exit 2
+fi
+
+
+set -ex
+
+base="$PWD"
+deps="$base/deps"
+inst="$deps/install"
+export deps inst
+
+osmo-clean-workspace.sh
+
+mkdir "$deps" || true
+
+verify_value_string_arrays_are_terminated.py $(find . -name "*.[hc]")
+
+export PKG_CONFIG_PATH="$inst/lib/pkgconfig:$PKG_CONFIG_PATH"
+export LD_LIBRARY_PATH="$inst/lib"
+export PATH="$inst/bin:$PATH"
+
+osmo-build-dep.sh libosmocore "" --disable-doxygen
+osmo-build-dep.sh libosmo-netif
+
+# Additional configure options and depends
+CONFIG=""
+if [ "$WITH_MANUALS" = "1" ]; then
+ osmo-build-dep.sh osmo-gsm-manuals
+ CONFIG="--enable-manuals"
+fi
+
+set +x
+echo
+echo
+echo
+echo " =============================== osmo-cbc ==============================="
+echo
+set -x
+
+autoreconf --install --force
+./configure --enable-sanitize --enable-werror --enable-external-tests $CONFIG
+$MAKE $PARALLEL_MAKE
+DISTCHECK_CONFIGURE_FLAGS="--enable-external-tests $CONFIG" \
+ $MAKE distcheck \
+ || cat-testlogs.sh
+
+if [ "$WITH_MANUALS" = "1" ] && [ "$PUBLISH" = "1" ]; then
+ make -C "$base/doc/manuals" publish
+fi
+
+osmo-clean-workspace.sh
diff --git a/contrib/systemd/Makefile.am b/contrib/systemd/Makefile.am
new file mode 100644
index 0000000..de962a4
--- /dev/null
+++ b/contrib/systemd/Makefile.am
@@ -0,0 +1,6 @@
+EXTRA_DIST = osmo-cbc.service
+
+if HAVE_SYSTEMD
+systemdsystemunit_DATA = \
+ osmo-cbc.service
+endif
diff --git a/contrib/systemd/osmo-cbc.service b/contrib/systemd/osmo-cbc.service
new file mode 100644
index 0000000..3446cc4
--- /dev/null
+++ b/contrib/systemd/osmo-cbc.service
@@ -0,0 +1,11 @@
+[Unit]
+Description=Osmocom CBC (Cell Broadcasting Centre)
+
+[Service]
+Type=simple
+Restart=always
+ExecStart=/usr/bin/osmo-cbc -c /etc/osmocom/osmo-cbc.cfg
+RestartSec=2
+
+[Install]
+WantedBy=multi-user.target
diff --git a/git-version-gen b/git-version-gen
new file mode 100755
index 0000000..42cf3d2
--- /dev/null
+++ b/git-version-gen
@@ -0,0 +1,151 @@
+#!/bin/sh
+# Print a version string.
+scriptversion=2010-01-28.01
+
+# Copyright (C) 2007-2010 Free Software Foundation, Inc.
+#
+# 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 .
+
+# This script is derived from GIT-VERSION-GEN from GIT: http://git.or.cz/.
+# It may be run two ways:
+# - from a git repository in which the "git describe" command below
+# produces useful output (thus requiring at least one signed tag)
+# - from a non-git-repo directory containing a .tarball-version file, which
+# presumes this script is invoked like "./git-version-gen .tarball-version".
+
+# In order to use intra-version strings in your project, you will need two
+# separate generated version string files:
+#
+# .tarball-version - present only in a distribution tarball, and not in
+# a checked-out repository. Created with contents that were learned at
+# the last time autoconf was run, and used by git-version-gen. Must not
+# be present in either $(srcdir) or $(builddir) for git-version-gen to
+# give accurate answers during normal development with a checked out tree,
+# but must be present in a tarball when there is no version control system.
+# Therefore, it cannot be used in any dependencies. GNUmakefile has
+# hooks to force a reconfigure at distribution time to get the value
+# correct, without penalizing normal development with extra reconfigures.
+#
+# .version - present in a checked-out repository and in a distribution
+# tarball. Usable in dependencies, particularly for files that don't
+# want to depend on config.h but do want to track version changes.
+# Delete this file prior to any autoconf run where you want to rebuild
+# files to pick up a version string change; and leave it stale to
+# minimize rebuild time after unrelated changes to configure sources.
+#
+# It is probably wise to add these two files to .gitignore, so that you
+# don't accidentally commit either generated file.
+#
+# Use the following line in your configure.ac, so that $(VERSION) will
+# automatically be up-to-date each time configure is run (and note that
+# since configure.ac no longer includes a version string, Makefile rules
+# should not depend on configure.ac for version updates).
+#
+# AC_INIT([GNU project],
+# m4_esyscmd([build-aux/git-version-gen .tarball-version]),
+# [bug-project@example])
+#
+# Then use the following lines in your Makefile.am, so that .version
+# will be present for dependencies, and so that .tarball-version will
+# exist in distribution tarballs.
+#
+# BUILT_SOURCES = $(top_srcdir)/.version
+# $(top_srcdir)/.version:
+# echo $(VERSION) > $@-t && mv $@-t $@
+# dist-hook:
+# echo $(VERSION) > $(distdir)/.tarball-version
+
+case $# in
+ 1) ;;
+ *) echo 1>&2 "Usage: $0 \$srcdir/.tarball-version"; exit 1;;
+esac
+
+tarball_version_file=$1
+nl='
+'
+
+# First see if there is a tarball-only version file.
+# then try "git describe", then default.
+if test -f $tarball_version_file
+then
+ v=`cat $tarball_version_file` || exit 1
+ case $v in
+ *$nl*) v= ;; # reject multi-line output
+ [0-9]*) ;;
+ *) v= ;;
+ esac
+ test -z "$v" \
+ && echo "$0: WARNING: $tarball_version_file seems to be damaged" 1>&2
+fi
+
+if test -n "$v"
+then
+ : # use $v
+elif
+ v=`git describe --abbrev=4 --match='v*' HEAD 2>/dev/null \
+ || git describe --abbrev=4 HEAD 2>/dev/null` \
+ && case $v in
+ [0-9]*) ;;
+ v[0-9]*) ;;
+ *) (exit 1) ;;
+ esac
+then
+ # Is this a new git that lists number of commits since the last
+ # tag or the previous older version that did not?
+ # Newer: v6.10-77-g0f8faeb
+ # Older: v6.10-g0f8faeb
+ case $v in
+ *-*-*) : git describe is okay three part flavor ;;
+ *-*)
+ : git describe is older two part flavor
+ # Recreate the number of commits and rewrite such that the
+ # result is the same as if we were using the newer version
+ # of git describe.
+ vtag=`echo "$v" | sed 's/-.*//'`
+ numcommits=`git rev-list "$vtag"..HEAD | wc -l`
+ v=`echo "$v" | sed "s/\(.*\)-\(.*\)/\1-$numcommits-\2/"`;
+ ;;
+ esac
+
+ # Change the first '-' to a '.', so version-comparing tools work properly.
+ # Remove the "g" in git describe's output string, to save a byte.
+ v=`echo "$v" | sed 's/-/./;s/\(.*\)-g/\1-/'`;
+else
+ v=UNKNOWN
+fi
+
+v=`echo "$v" |sed 's/^v//'`
+
+# Don't declare a version "dirty" merely because a time stamp has changed.
+git status > /dev/null 2>&1
+
+dirty=`sh -c 'git diff-index --name-only HEAD' 2>/dev/null` || dirty=
+case "$dirty" in
+ '') ;;
+ *) # Append the suffix only if there isn't one already.
+ case $v in
+ *-dirty) ;;
+ *) v="$v-dirty" ;;
+ esac ;;
+esac
+
+# Omit the trailing newline, so that m4_esyscmd can use the result directly.
+echo "$v" | tr -d '\012'
+
+# Local variables:
+# eval: (add-hook 'write-file-hooks 'time-stamp)
+# time-stamp-start: "scriptversion="
+# time-stamp-format: "%:y-%02m-%02d.%02H"
+# time-stamp-end: "$"
+# End:
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 0000000..1ae446b
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,9 @@
+AM_CPPFLAGS = $(all_includes) -I$(top_srcdir)/src
+AM_CFLAGS=-Wall -g $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOVTY_CFLAGS) $(LIBOSMONETIF_CFLAGS) $(COVERAGE_CFLAGS)
+AM_LDFLAGS=$(COVERAGE_LDFLAGS)
+
+bin_PROGRAMS = osmo-cbc
+
+osmo_cbc_SOURCES = cbc_main.c cbsp_server.c cbsp_server_fsm.c
+osmo_cbc_LDADD = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOVTY_LIBS) $(LIBOSMONETIF_LIBS)
+
diff --git a/src/cbc_main.c b/src/cbc_main.c
new file mode 100644
index 0000000..a583ade
--- /dev/null
+++ b/src/cbc_main.c
@@ -0,0 +1,200 @@
+/* Osmocom CBC (Cell Broacast Centre) */
+
+/* (C) 2019 by Harald Welte
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: AGPL-3.0+
+ *
+ * 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 .
+ *
+ */
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "internal.h"
+#include "cbsp_server.h"
+
+static void *tall_cbc_ctx;
+
+static const struct log_info_cat log_info_cat[] = {
+ [DCBSP] = {
+ .name = "DCBSP",
+ .description = "Cell Broadcast Service Protocol (CBC-BSC)",
+ .color = "\033[1;31m",
+ .enabled = 1,
+ .loglevel = LOGL_NOTICE,
+ },
+};
+
+static const struct log_info log_info = {
+ .cat = log_info_cat,
+ .num_cat = ARRAY_SIZE(log_info_cat),
+};
+
+static const char cbc_copyright[] =
+ "Copyright (C) 2019 by Harald Welte \r\n"
+ "License AGPLv3+: GNU Affero GPL Version 3 or later \r\n"
+ "This is free software: you are free ot change and redistribute it.\r\n"
+ "There is NO WARRANTY, to the extent permitted by law.\r\n\r\n"
+ "Free Software lives by contribution. If you use this, please contribute!\r\n";
+
+static struct vty_app_info vty_info = {
+ .name = "OsmoCBC",
+ .copyright = cbc_copyright,
+ .version = PACKAGE_VERSION,
+ .go_parent_cb = NULL,
+ .is_config_node = NULL,
+};
+
+static struct {
+ bool daemonize;
+ const char *config_file;
+} cmdline_config = {
+ .daemonize = false,
+ .config_file = "osmo-cbc.cfg",
+};
+
+static void print_help(void)
+{
+ /* FIXME */
+}
+
+static void handle_options(int argc, char **argv)
+{
+ while (1) {
+ int option_index = 0, c;
+ static const struct option long_options[] = {
+ { "help", 0, 0, 'h' },
+ { "daemonize", 0, 0, 'D' },
+ { "config-file", 1, 0, 'c' },
+ { "version", 0, 0, 'V' },
+ { NULL, 0, 0, 0 }
+ };
+
+ c = getopt_long(argc, argv, "hDc:V", long_options, &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'h':
+ print_help();
+ exit(0);
+ break;
+ case 'D':
+ cmdline_config.daemonize = true;
+ break;
+ case 'c':
+ cmdline_config.config_file = optarg;
+ break;
+ case 'V':
+ print_version(1);
+ exit(0);
+ break;
+ default:
+ fprintf(stderr, "Error in command line options. Exiting\n");
+ exit(1);
+ break;
+ }
+ }
+}
+
+static void signal_handler(int signal)
+{
+ fprintf(stdout, "signal %d received\n", signal);
+
+ switch (signal){
+ case SIGUSR1:
+ talloc_report(tall_vty_ctx, stderr);
+ talloc_report_full(tall_cbc_ctx, stderr);
+ break;
+ case SIGUSR2:
+ talloc_report_full(tall_vty_ctx, stderr);
+ break;
+ default:
+ break;
+ }
+}
+
+extern int cbc_client_rx_cb(struct osmo_cbsp_cbc_client *client, struct osmo_cbsp_decoded *dec);
+
+int main(int argc, char **argv)
+{
+ int rc;
+
+ tall_cbc_ctx = talloc_named_const(NULL, 1, "osmo-cbc");
+ msgb_talloc_ctx_init(tall_cbc_ctx, 0);
+ osmo_init_logging2(tall_cbc_ctx, &log_info);
+ osmo_stats_init(tall_cbc_ctx);
+ vty_init(&vty_info);
+
+ handle_options(argc, argv);
+
+ logging_vty_add_cmds();
+ osmo_fsm_vty_add_cmds();
+
+ rc = vty_read_config_file(cmdline_config.config_file, NULL);
+ if (rc < 0) {
+ fprintf(stderr, "Failed ot parse the config file '%s'\n",
+ cmdline_config.config_file);
+ exit(1);
+ }
+
+ rc = telnet_init_dynif(tall_cbc_ctx, NULL, vty_get_bind_addr(), OSMO_VTY_PORT_CBC);
+ if (rc < 0) {
+ perror("Error binding VTY port\n");
+ exit(1);
+ }
+
+ cbsp_cbc_create(tall_cbc_ctx, &cbc_client_rx_cb);
+
+ signal(SIGUSR1, &signal_handler);
+ signal(SIGUSR2, &signal_handler);
+ osmo_init_ignore_signals();
+
+ if (cmdline_config.daemonize) {
+ rc = osmo_daemonize();
+ if (rc < 0) {
+ perror("Error during daemonize");
+ exit(1);
+ }
+ }
+
+ while (1) {
+ rc = osmo_select_main(0);
+ if (rc < 0)
+ exit(3);
+ }
+}
diff --git a/src/cbsp_server.c b/src/cbsp_server.c
index c5ebc19..44bb25c 100644
--- a/src/cbsp_server.c
+++ b/src/cbsp_server.c
@@ -1,34 +1,38 @@
+/* (C) 2019 by Harald Welte
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: AGPL-3.0+
+ *
+ * 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 .
+ *
+ */
+
#include
#include
#include
#include
#include
+#include
#include
+#include
#include
+#include
#include
-/* a CBC server */
-struct osmo_cbsp_cbc {
- /* libosmo-netif stream server */
- struct osmo_stream_srv_link *link;
-
- /* BSCs / clients connected to this CBC */
- struct llist_head clients;
-};
-
-/* a single (remote) client connected to the (local) CBC server */
-struct osmo_cbsp_cbc_client {
- /* entry in osmo_cbsp_cbc.clients */
- struct llist_head list;
- /* stream server connection for this client */
- struct osmo_stream_srv *conn;
- /* partially received CBSP message (rx completion pending) */
- struct msgb *rx_msg;
- /* receive call-back; called for every received message */
- int (*rx_cb)(struct osmo_cbsp_cbc_client *client, struct osmo_cbsp_decoded *dec);
-};
-
+#include "internal.h"
+#include "cbsp_server.h"
#if 0
struct osmo_cbsp_bsc {
@@ -92,8 +96,9 @@ int osmo_cbsp_recv_buffered(int fd, struct msgb **rmsg, struct msgb **tmp_msg)
msg->l2h = msg->tail;
}
+ h = (struct cbsp_header *) msg->data;
/* then read the length as specified in the header */
- len = h->len[0] << 16 | h->len[1] << 8 | h->len[0];
+ len = h->len[0] << 16 | h->len[1] << 8 | h->len[2];
needed = len - msgb_l2len(msg);
if (needed > 0) {
@@ -142,17 +147,25 @@ discard_msg:
-
+const char *cbsp_cbc_client_name(const struct osmo_cbsp_cbc_client *client)
+{
+ struct osmo_fd *ofd = osmo_stream_srv_get_ofd(client->conn);
+ return osmo_sock_get_name2(ofd->fd);
+}
/* data from BSC has arrived at CBC */
static int cbsp_cbc_read_cb(struct osmo_stream_srv *conn)
{
+ struct osmo_stream_srv_link *link = osmo_stream_srv_get_master(conn);
struct osmo_cbsp_cbc_client *client = osmo_stream_srv_get_data(conn);
+ struct osmo_cbsp_cbc *cbc = osmo_stream_srv_link_get_data(link);
struct osmo_fd *ofd = osmo_stream_srv_get_ofd(conn);
struct osmo_cbsp_decoded *decoded;
struct msgb *msg = NULL;
int rc;
+ LOGPCC(client, LOGL_DEBUG, "read_cb rx_msg=%p\n", client->rx_msg);
+
/* message de-segmentation */
rc = osmo_cbsp_recv_buffered(ofd->fd, &msg, &client->rx_msg);
if (rc <= 0) {
@@ -163,16 +176,24 @@ static int cbsp_cbc_read_cb(struct osmo_stream_srv *conn)
/* lost connection with server */
} else if (rc == 0) {
/* connection closed with server */
+
}
/* destroy connection */
+ osmo_stream_srv_destroy(conn);
return -EBADF;
}
OSMO_ASSERT(msg);
+ LOGPCC(client, LOGL_DEBUG, "Received CBSP %s\n", msgb_hexdump(msg));
/* decode + dispatch message */
decoded = osmo_cbsp_decode(client, msg);
+ if (decoded) {
+ LOGPCC(client, LOGL_INFO, "Received CBSP %s\n",
+ get_value_string(cbsp_msg_type_names, decoded->msg_type));
+ cbc->rx_cb(client, decoded);
+ } else {
+ LOGPCC(client, LOGL_ERROR, "Unable to decode %s\n", msgb_hexdump(msg));
+ }
msgb_free(msg);
- if (decoded)
- client->rx_cb(client, decoded);
return 0;
}
@@ -180,6 +201,7 @@ static int cbsp_cbc_read_cb(struct osmo_stream_srv *conn)
static int cbsp_cbc_closed_cb(struct osmo_stream_srv *conn)
{
struct osmo_cbsp_cbc_client *client = osmo_stream_srv_get_data(conn);
+ LOGPCC(client, LOGL_INFO, "connection closed\n");
llist_del(&client->list);
talloc_free(client);
return 0;
@@ -194,9 +216,19 @@ static int cbsp_cbc_accept_cb(struct osmo_stream_srv_link *link, int fd)
client->conn = osmo_stream_srv_create(link, link, fd, cbsp_cbc_read_cb, cbsp_cbc_closed_cb, client);
if (!client->conn) {
+ LOGP(DCBSP, LOGL_ERROR, "Unable to create stream server for %s\n",
+ osmo_sock_get_name2(fd));
talloc_free(client);
return -1;
}
+ client->fi = osmo_fsm_inst_alloc(&cbsp_server_fsm, client, client, LOGL_DEBUG, NULL);
+ if (!client->fi) {
+ LOGPCC(client, LOGL_ERROR, "Unable to allocate FSM\n");
+ osmo_stream_srv_destroy(client->conn);
+ talloc_free(client);
+ return -1;
+ }
+ LOGPCC(client, LOGL_INFO, "New CBSP client connection\n");
llist_add_tail(&client->list, &cbc->clients);
return 0;
@@ -205,29 +237,37 @@ static int cbsp_cbc_accept_cb(struct osmo_stream_srv_link *link, int fd)
void cbsp_cbc_client_tx(struct osmo_cbsp_cbc_client *client, struct osmo_cbsp_decoded *cbsp)
{
struct msgb *msg = osmo_cbsp_encode(cbsp);
- talloc_free(cbsp);
+ LOGPCC(client, LOGL_INFO, "Transmitting %s\n",
+ get_value_string(cbsp_msg_type_names, cbsp->msg_type));
if (!msg) {
- /* FIXME */
+ LOGPCC(client, LOGL_ERROR, "Failed to encode CBSP %s\n",
+ get_value_string(cbsp_msg_type_names, cbsp->msg_type));
+ talloc_free(cbsp);
return;
}
+ talloc_free(cbsp);
osmo_stream_srv_send(client->conn, msg);
}
/* initialize the CBC-side CBSP server */
-struct osmo_cbsp_cbc *cbsp_cbc_create(void *ctx)
+struct osmo_cbsp_cbc *cbsp_cbc_create(void *ctx, int (*rx_cb)(struct osmo_cbsp_cbc_client *client,
+ struct osmo_cbsp_decoded *dec))
{
struct osmo_cbsp_cbc *cbc = talloc_zero(ctx, struct osmo_cbsp_cbc);
int rc;
OSMO_ASSERT(cbc);
+ cbc->rx_cb = rx_cb;
INIT_LLIST_HEAD(&cbc->clients);
cbc->link = osmo_stream_srv_link_create(cbc);
osmo_stream_srv_link_set_data(cbc->link, cbc);
osmo_stream_srv_link_set_nodelay(cbc->link, true);
- osmo_stream_srv_link_set_port(cbc->link, 48049);
+ osmo_stream_srv_link_set_port(cbc->link, CBSP_TCP_PORT);
osmo_stream_srv_link_set_accept_cb(cbc->link, cbsp_cbc_accept_cb);
rc = osmo_stream_srv_link_open(cbc->link);
OSMO_ASSERT(rc == 0);
+ LOGP(DCBSP, LOGL_NOTICE, "Listening for CBSP at %s\n",
+ osmo_stream_srv_link_get_sockname(cbc->link));
return cbc;
}
diff --git a/src/cbsp_server.h b/src/cbsp_server.h
new file mode 100644
index 0000000..88f4f10
--- /dev/null
+++ b/src/cbsp_server.h
@@ -0,0 +1,39 @@
+#pragma once
+#include
+#include
+#include
+
+#define LOGPCC(client, level, fmt, args...) \
+ LOGP(DCBSP, level, "%s: " fmt, cbsp_cbc_client_name(client), ## args)
+
+struct osmo_cbsp_cbc_client;
+struct osmo_fsm_inst;
+
+/* a CBC server */
+struct osmo_cbsp_cbc {
+ /* libosmo-netif stream server */
+ struct osmo_stream_srv_link *link;
+
+ /* BSCs / clients connected to this CBC */
+ struct llist_head clients;
+
+ /* receive call-back; called for every received message */
+ int (*rx_cb)(struct osmo_cbsp_cbc_client *client, struct osmo_cbsp_decoded *dec);
+};
+
+/* a single (remote) client connected to the (local) CBC server */
+struct osmo_cbsp_cbc_client {
+ /* entry in osmo_cbsp_cbc.clients */
+ struct llist_head list;
+ /* stream server connection for this client */
+ struct osmo_stream_srv *conn;
+ /* partially received CBSP message (rx completion pending) */
+ struct msgb *rx_msg;
+
+ struct osmo_fsm_inst *fi;
+};
+
+const char *cbsp_cbc_client_name(const struct osmo_cbsp_cbc_client *client);
+void cbsp_cbc_client_tx(struct osmo_cbsp_cbc_client *client, struct osmo_cbsp_decoded *cbsp);
+struct osmo_cbsp_cbc *cbsp_cbc_create(void *ctx, int (*rx_cb)(struct osmo_cbsp_cbc_client *client,
+ struct osmo_cbsp_decoded *dec));
diff --git a/src/cbsp_server_fsm.c b/src/cbsp_server_fsm.c
new file mode 100644
index 0000000..239e09e
--- /dev/null
+++ b/src/cbsp_server_fsm.c
@@ -0,0 +1,216 @@
+/* (C) 2019 by Harald Welte
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: AGPL-3.0+
+ *
+ * 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 .
+ *
+ */
+
+#include
+
+#include
+
+#include "cbsp_server.h"
+#include "internal.h"
+
+#define S(x) (1 << (x))
+
+#define T_KEEPALIVE 1
+#define T_KEEPALIVE_SECS 30
+
+#define T_WAIT_KEEPALIVE_RESP 2
+#define T_WAIT_KEEPALIVE_RESP_SECS 10
+
+#define T_WAIT_RESET_RESP 3
+#define T_WAIT_RESET_RESP_SECS 5
+
+enum cbsp_server_state {
+ /* initial state after client TCP connection established */
+ CBSP_SRV_S_INIT,
+ /* RESET has been sent to BSC, waiting for response */
+ CBSP_SRV_S_RESET_PENDING,
+ /* Keep-Alive has been sent, waiting for response */
+ CBSP_SRV_S_KEEPALIVE_PENDING,
+ /* normal operation (idle) */
+ CBSP_SRV_S_IDLE,
+};
+
+static const struct value_string cbsp_server_event_names[] = {
+ { CBSP_SRV_E_RX_RST_COMPL, "Rx Reset Complete" },
+ { CBSP_SRV_E_RX_RST_FAIL, "Rx Reset Failure" },
+ { CBSP_SRV_E_RX_KA_COMPL, "Rx Keep-Alive Complete" },
+ { CBSP_SRV_E_RX_RESTART, "Rx Restart" },
+ { CBSP_SRV_E_CMD_RESET, "RESET.cmd" },
+ { 0, NULL }
+};
+
+static void cbsp_server_s_init(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ switch (event) {
+ case CBSP_SRV_E_CMD_RESET:
+ osmo_fsm_inst_state_chg(fi, CBSP_SRV_S_RESET_PENDING, 0, 0);
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static void cbsp_server_s_reset_pending_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+ struct osmo_cbsp_cbc_client *client = (struct osmo_cbsp_cbc_client *) fi->priv;
+ struct osmo_cbsp_decoded *cbspd;
+
+ if (prev_state == CBSP_SRV_S_RESET_PENDING)
+ return;
+
+ cbspd = talloc_zero(fi, struct osmo_cbsp_decoded);
+ OSMO_ASSERT(cbspd);
+ cbspd->msg_type = CBSP_MSGT_RESET;
+ cbspd->u.reset.cell_list.id_discr = CELL_IDENT_BSS;
+ INIT_LLIST_HEAD(&cbspd->u.reset.cell_list.list);
+
+ cbsp_cbc_client_tx(client, cbspd);
+ /* wait for response */
+ osmo_fsm_inst_state_chg(fi, CBSP_SRV_S_RESET_PENDING, T_WAIT_RESET_RESP_SECS,
+ T_WAIT_RESET_RESP);
+}
+
+static void cbsp_server_s_reset_pending(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ switch (event) {
+ case CBSP_SRV_E_RX_RST_COMPL:
+ osmo_fsm_inst_state_chg(fi, CBSP_SRV_S_IDLE, 0, 0);
+ break;
+ case CBSP_SRV_E_RX_RST_FAIL:
+ osmo_fsm_inst_state_chg(fi, CBSP_SRV_S_IDLE, 0, 0);
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static void cbsp_server_s_keepalive_pending(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ switch (event) {
+ case CBSP_SRV_E_RX_KA_COMPL:
+ break;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+/* a bit of a hack to ensure the keep-aliver timer is started every time we enter
+ * the IDLE state, without putting the burden on the caller of
+ * osmo_fsm_inst_state_chg() to specify T_KEEPALIVE + T_KEEPALIVE_SECS */
+static void cbsp_server_s_idle_onenter(struct osmo_fsm_inst *fi, uint32_t old_state)
+{
+ fi->T = T_KEEPALIVE;
+ osmo_timer_schedule(&fi->timer, T_KEEPALIVE_SECS, 0);
+}
+
+static void cbsp_server_s_idle(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ switch (event) {
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static int cbsp_server_fsm_timer_cb(struct osmo_fsm_inst *fi)
+{
+ struct osmo_cbsp_cbc_client *client = (struct osmo_cbsp_cbc_client *) fi->priv;
+ struct osmo_cbsp_decoded *cbspd;
+
+ switch (fi->T) {
+ case T_KEEPALIVE:
+ /* send keepalive to peer */
+ cbspd = talloc_zero(fi, struct osmo_cbsp_decoded);
+ OSMO_ASSERT(cbspd);
+ cbspd->msg_type = CBSP_MSGT_KEEP_ALIVE;
+ cbspd->u.keep_alive.repetition_period = T_KEEPALIVE_SECS;
+ cbsp_cbc_client_tx(client, cbspd);
+ /* wait for response */
+ osmo_fsm_inst_state_chg(fi, CBSP_SRV_S_KEEPALIVE_PENDING, T_WAIT_KEEPALIVE_RESP_SECS,
+ T_WAIT_KEEPALIVE_RESP);
+ return 0;
+ case T_WAIT_KEEPALIVE_RESP:
+ case T_WAIT_RESET_RESP:
+ /* ask core to terminate FSM which will terminate TCP connection */
+ return 1;
+ default:
+ OSMO_ASSERT(0);
+ }
+}
+
+static const struct osmo_fsm_state cbsp_server_fsm_states[] = {
+ [CBSP_SRV_S_INIT] = {
+ .name = "INIT",
+ .in_event_mask = S(CBSP_SRV_E_CMD_RESET),
+ .out_state_mask = S(CBSP_SRV_S_RESET_PENDING),
+ .action = cbsp_server_s_init,
+ },
+ [CBSP_SRV_S_RESET_PENDING] = {
+ .name = "RESET_PENDING",
+ .in_event_mask = S(CBSP_SRV_E_RX_RST_COMPL) |
+ S(CBSP_SRV_E_RX_RST_FAIL),
+ .out_state_mask = S(CBSP_SRV_S_IDLE) |
+ S(CBSP_SRV_S_RESET_PENDING),
+ .action = cbsp_server_s_reset_pending,
+ .onenter = cbsp_server_s_reset_pending_onenter,
+ },
+ [CBSP_SRV_S_KEEPALIVE_PENDING] = {
+ .name = "KEEPALIVE_PENDING",
+ .in_event_mask = S(CBSP_SRV_E_RX_KA_COMPL),
+ .out_state_mask = S(CBSP_SRV_S_IDLE) |
+ S(CBSP_SRV_S_KEEPALIVE_PENDING),
+ .action = cbsp_server_s_keepalive_pending,
+ },
+ [CBSP_SRV_S_IDLE] = {
+ .name = "IDLE",
+ .in_event_mask = 0,
+ .out_state_mask = S(CBSP_SRV_S_KEEPALIVE_PENDING),
+ .action = cbsp_server_s_idle,
+ .onenter = cbsp_server_s_idle_onenter,
+ },
+};
+
+struct osmo_fsm cbsp_server_fsm = {
+ .name = "CBSP-SERVER",
+ .states = cbsp_server_fsm_states,
+ .num_states = ARRAY_SIZE(cbsp_server_fsm_states),
+ .timer_cb = cbsp_server_fsm_timer_cb,
+ .log_subsys = DCBSP,
+ .event_names = cbsp_server_event_names,
+};
+
+/* message was received from remote CBSP peer (BSC) */
+int cbc_client_rx_cb(struct osmo_cbsp_cbc_client *client, struct osmo_cbsp_decoded *dec)
+{
+ switch (dec->msg_type) {
+ case CBSP_MSGT_RESTART:
+ osmo_fsm_inst_dispatch(client->fi, CBSP_SRV_E_RX_RESTART, dec);
+ break;
+ default:
+ LOGPCC(client, LOGL_ERROR, "unknown/unhandled %s\n",
+ get_value_string(cbsp_msg_type_names, dec->msg_type));
+ break;
+ }
+ return 0;
+}
+
+static __attribute__((constructor)) void on_dso_load_cbsp_srv_fsm(void)
+{
+ osmo_fsm_register(&cbsp_server_fsm);
+}
diff --git a/src/internal.h b/src/internal.h
new file mode 100644
index 0000000..cc5e3f5
--- /dev/null
+++ b/src/internal.h
@@ -0,0 +1,18 @@
+#pragma once
+
+#include
+#include
+
+enum {
+ DCBSP,
+};
+
+extern struct osmo_fsm cbsp_server_fsm;
+
+enum cbsp_server_event {
+ CBSP_SRV_E_RX_RST_COMPL,
+ CBSP_SRV_E_RX_RST_FAIL,
+ CBSP_SRV_E_RX_KA_COMPL,
+ CBSP_SRV_E_RX_RESTART,
+ CBSP_SRV_E_CMD_RESET,
+};