aboutsummaryrefslogtreecommitdiffstats
path: root/src/libcommon
diff options
context:
space:
mode:
Diffstat (limited to 'src/libcommon')
-rw-r--r--src/libcommon/Makefile.am47
-rw-r--r--src/libcommon/bsc_version.c30
-rw-r--r--src/libcommon/common_vty.c145
-rw-r--r--src/libcommon/debug.c235
-rw-r--r--src/libcommon/gsm_data.c473
-rw-r--r--src/libcommon/gsm_data_shared.c849
-rw-r--r--src/libcommon/gsm_subscriber_base.c163
-rw-r--r--src/libcommon/gsup_client.c341
-rw-r--r--src/libcommon/gsup_test_client.c298
-rw-r--r--src/libcommon/oap_client.c280
-rw-r--r--src/libcommon/socket.c111
-rw-r--r--src/libcommon/talloc_ctx.c56
12 files changed, 3028 insertions, 0 deletions
diff --git a/src/libcommon/Makefile.am b/src/libcommon/Makefile.am
new file mode 100644
index 000000000..0b258c08a
--- /dev/null
+++ b/src/libcommon/Makefile.am
@@ -0,0 +1,47 @@
+AM_CPPFLAGS = \
+ $(all_includes) \
+ -I$(top_srcdir)/include \
+ -I$(top_builddir) \
+ $(NULL)
+
+AM_CFLAGS = \
+ -Wall \
+ $(LIBOSMOCORE_CFLAGS) \
+ $(LIBOSMOGSM_CFLAGS) \
+ $(LIBOSMOVTY_CFLAGS) \
+ $(LIBOSMOABIS_CFLAGS) \
+ $(COVERAGE_CFLAGS) \
+ $(NULL)
+
+noinst_LIBRARIES = \
+ libcommon.a \
+ $(NULL)
+
+libcommon_a_SOURCES = \
+ bsc_version.c \
+ common_vty.c \
+ debug.c \
+ gsm_data.c \
+ gsm_data_shared.c \
+ gsup_client.c \
+ oap_client.c \
+ socket.c \
+ talloc_ctx.c \
+ gsm_subscriber_base.c \
+ $(NULL)
+
+noinst_PROGRAMS = \
+ gsup_test_client \
+ $(NULL)
+
+gsup_test_client_SOURCES = \
+ gsup_test_client.c \
+ $(NULL)
+gsup_test_client_LDADD = \
+ libcommon.a \
+ $(LIBOSMOCORE_LIBS) \
+ $(LIBOSMOGSM_LIBS) \
+ $(LIBOSMOVTY_LIBS) \
+ $(LIBOSMOABIS_LIBS) \
+ -lrt \
+ $(NULL)
diff --git a/src/libcommon/bsc_version.c b/src/libcommon/bsc_version.c
new file mode 100644
index 000000000..f0369bffe
--- /dev/null
+++ b/src/libcommon/bsc_version.c
@@ -0,0 +1,30 @@
+/* Hold the copyright and version string */
+/* (C) 2010-2016 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU 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 "../../bscconfig.h"
+
+const char *openbsc_copyright =
+ "Copyright (C) 2008-2016 Harald Welte, Holger Freyther\r\n"
+ "Contributions by Daniel Willmann, Jan Lübbe, Stefan Schmidt\r\n"
+ "Dieter Spaar, Andreas Eversberg, Sylvain Munaut, Neels Hofmeyr\r\n\r\n"
+ "License AGPLv3+: GNU AGPL version 3 or later <http://gnu.org/licenses/agpl-3.0.html>\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";
+
+
diff --git a/src/libcommon/common_vty.c b/src/libcommon/common_vty.c
new file mode 100644
index 000000000..6e1c10b00
--- /dev/null
+++ b/src/libcommon/common_vty.c
@@ -0,0 +1,145 @@
+/* OpenBSC VTY common helpers */
+/* (C) 2009-2010 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2009-2010 by Holger Hans Peter Freyther
+ * All Rights Reserved
+ *
+ * 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 <stdlib.h>
+#include <string.h>
+
+#include <osmocom/core/talloc.h>
+
+#include <openbsc/vty.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/debug.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/bsc_nat.h>
+#include <openbsc/abis_om2000.h>
+
+#include <osmocom/vty/telnet_interface.h>
+#include <osmocom/vty/command.h>
+#include <osmocom/vty/buffer.h>
+#include <osmocom/vty/vty.h>
+
+
+int bsc_vty_go_parent(struct vty *vty)
+{
+ switch (vty->node) {
+ case GSMNET_NODE:
+ vty->node = CONFIG_NODE;
+ vty->index = NULL;
+ break;
+ case BTS_NODE:
+ vty->node = GSMNET_NODE;
+ {
+ /* set vty->index correctly ! */
+ struct gsm_bts *bts = vty->index;
+ vty->index = bts->network;
+ vty->index_sub = NULL;
+ }
+ break;
+ case TRX_NODE:
+ vty->node = BTS_NODE;
+ {
+ /* set vty->index correctly ! */
+ struct gsm_bts_trx *trx = vty->index;
+ vty->index = trx->bts;
+ vty->index_sub = &trx->bts->description;
+ }
+ break;
+ case TS_NODE:
+ vty->node = TRX_NODE;
+ {
+ /* set vty->index correctly ! */
+ struct gsm_bts_trx_ts *ts = vty->index;
+ vty->index = ts->trx;
+ vty->index_sub = &ts->trx->description;
+ }
+ break;
+ case OML_NODE:
+ case OM2K_NODE:
+ vty->node = ENABLE_NODE;
+ /* NOTE: this only works because it's not part of the config
+ * tree, where outer commands are searched via vty_go_parent()
+ * and only (!) executed when a matching one is found.
+ */
+ talloc_free(vty->index);
+ vty->index = NULL;
+ break;
+ case OM2K_CON_GROUP_NODE:
+ vty->node = BTS_NODE;
+ {
+ struct con_group *cg = vty->index;
+ struct gsm_bts *bts = cg->bts;
+ vty->index = bts;
+ vty->index_sub = &bts->description;
+ }
+ break;
+ case NAT_BSC_NODE:
+ vty->node = NAT_NODE;
+ {
+ struct bsc_config *bsc_config = vty->index;
+ vty->index = bsc_config->nat;
+ }
+ break;
+ case PGROUP_NODE:
+ vty->node = NAT_NODE;
+ vty->index = NULL;
+ break;
+ case TRUNK_NODE:
+ vty->node = MGCP_NODE;
+ vty->index = NULL;
+ break;
+ case SMPP_ESME_NODE:
+ vty->node = SMPP_NODE;
+ vty->index = NULL;
+ break;
+ case SMPP_NODE:
+ case MGCP_NODE:
+ case GBPROXY_NODE:
+ case SGSN_NODE:
+ case NAT_NODE:
+ case BSC_NODE:
+ case MSC_NODE:
+ case MNCC_INT_NODE:
+ case NITB_NODE:
+ default:
+ if (bsc_vty_is_config_node(vty, vty->node))
+ vty->node = CONFIG_NODE;
+ else
+ vty->node = ENABLE_NODE;
+
+ vty->index = NULL;
+ }
+
+ return vty->node;
+}
+
+int bsc_vty_is_config_node(struct vty *vty, int node)
+{
+ switch (node) {
+ /* add items that are not config */
+ case OML_NODE:
+ case OM2K_NODE:
+ case SUBSCR_NODE:
+ case CONFIG_NODE:
+ return 0;
+
+ default:
+ return 1;
+ }
+}
diff --git a/src/libcommon/debug.c b/src/libcommon/debug.c
new file mode 100644
index 000000000..f29f1683d
--- /dev/null
+++ b/src/libcommon/debug.c
@@ -0,0 +1,235 @@
+/* OpenBSC Debugging/Logging support code */
+
+/* (C) 2008-2010 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2008 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * All Rights Reserved
+ *
+ * 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 <stdarg.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>
+#include <time.h>
+#include <errno.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/gprs/gprs_msgb.h>
+#include <openbsc/gsm_data.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/debug.h>
+
+/* default categories */
+static const struct log_info_cat default_categories[] = {
+ [DRLL] = {
+ .name = "DRLL",
+ .description = "A-bis Radio Link Layer (RLL)",
+ .color = "\033[1;31m",
+ .enabled = 1, .loglevel = LOGL_NOTICE,
+ },
+ [DCC] = {
+ .name = "DCC",
+ .description = "Layer3 Call Control (CC)",
+ .color = "\033[1;32m",
+ .enabled = 1, .loglevel = LOGL_NOTICE,
+ },
+ [DMM] = {
+ .name = "DMM",
+ .description = "Layer3 Mobility Management (MM)",
+ .color = "\033[1;33m",
+ .enabled = 1, .loglevel = LOGL_NOTICE,
+ },
+ [DRR] = {
+ .name = "DRR",
+ .description = "Layer3 Radio Resource (RR)",
+ .color = "\033[1;34m",
+ .enabled = 1, .loglevel = LOGL_NOTICE,
+ },
+ [DRSL] = {
+ .name = "DRSL",
+ .description = "A-bis Radio Siganlling Link (RSL)",
+ .color = "\033[1;35m",
+ .enabled = 1, .loglevel = LOGL_NOTICE,
+ },
+ [DNM] = {
+ .name = "DNM",
+ .description = "A-bis Network Management / O&M (NM/OML)",
+ .color = "\033[1;36m",
+ .enabled = 1, .loglevel = LOGL_INFO,
+ },
+ [DMNCC] = {
+ .name = "DMNCC",
+ .description = "MNCC API for Call Control application",
+ .color = "\033[1;39m",
+ .enabled = 1, .loglevel = LOGL_NOTICE,
+ },
+ [DPAG] = {
+ .name = "DPAG",
+ .description = "Paging Subsystem",
+ .color = "\033[1;38m",
+ .enabled = 1, .loglevel = LOGL_NOTICE,
+ },
+ [DMEAS] = {
+ .name = "DMEAS",
+ .description = "Radio Measurement Processing",
+ .enabled = 0, .loglevel = LOGL_NOTICE,
+ },
+ [DSCCP] = {
+ .name = "DSCCP",
+ .description = "SCCP Protocol",
+ .enabled = 1, .loglevel = LOGL_NOTICE,
+ },
+ [DMSC] = {
+ .name = "DMSC",
+ .description = "Mobile Switching Center",
+ .enabled = 1, .loglevel = LOGL_NOTICE,
+ },
+ [DMGCP] = {
+ .name = "DMGCP",
+ .description = "Media Gateway Control Protocol",
+ .enabled = 1, .loglevel = LOGL_NOTICE,
+ },
+ [DHO] = {
+ .name = "DHO",
+ .description = "Hand-Over",
+ .enabled = 1, .loglevel = LOGL_NOTICE,
+ },
+ [DDB] = {
+ .name = "DDB",
+ .description = "Database Layer",
+ .enabled = 1, .loglevel = LOGL_NOTICE,
+ },
+ [DREF] = {
+ .name = "DREF",
+ .description = "Reference Counting",
+ .enabled = 0, .loglevel = LOGL_NOTICE,
+ },
+ [DGPRS] = {
+ .name = "DGPRS",
+ .description = "GPRS Packet Service",
+ .enabled = 1, .loglevel = LOGL_DEBUG,
+ },
+ [DNS] = {
+ .name = "DNS",
+ .description = "GPRS Network Service (NS)",
+ .enabled = 1, .loglevel = LOGL_INFO,
+ },
+ [DBSSGP] = {
+ .name = "DBSSGP",
+ .description = "GPRS BSS Gateway Protocol (BSSGP)",
+ .enabled = 1, .loglevel = LOGL_DEBUG,
+ },
+ [DLLC] = {
+ .name = "DLLC",
+ .description = "GPRS Logical Link Control Protocol (LLC)",
+ .enabled = 1, .loglevel = LOGL_DEBUG,
+ },
+ [DSNDCP] = {
+ .name = "DSNDCP",
+ .description = "GPRS Sub-Network Dependent Control Protocol (SNDCP)",
+ .enabled = 1, .loglevel = LOGL_DEBUG,
+ },
+ [DNAT] = {
+ .name = "DNAT",
+ .description = "GSM 08.08 NAT/Multiplexer",
+ .enabled = 1, .loglevel = LOGL_NOTICE,
+ },
+ [DCTRL] = {
+ .name = "DCTRL",
+ .description = "Control interface",
+ .enabled = 1, .loglevel = LOGL_NOTICE,
+ },
+ [DSMPP] = {
+ .name = "DSMPP",
+ .description = "SMPP interface for external SMS apps",
+ .enabled = 1, .loglevel = LOGL_DEBUG,
+ },
+ [DFILTER] = {
+ .name = "DFILTER",
+ .description = "BSC/NAT IMSI based filtering",
+ .enabled = 1, .loglevel = LOGL_DEBUG,
+ },
+ [DRANAP] = {
+ .name = "DRANAP",
+ .description = "Radio Access Network Application Part Protocol",
+ .enabled = 1, .loglevel = LOGL_DEBUG,
+ },
+ [DSUA] = {
+ .name = "DSUA",
+ .description = "SCCP User Adaptation Protocol",
+ .enabled = 1, .loglevel = LOGL_DEBUG,
+ },
+ [DPCU] = {
+ .name = "DPCU",
+ .description = "PCU Interface",
+ .enabled = 1, .loglevel = LOGL_DEBUG,
+ },
+};
+
+static int filter_fn(const struct log_context *ctx, struct log_target *tar)
+{
+ const struct gsm_subscriber *subscr = ctx->ctx[LOG_CTX_VLR_SUBSCR];
+ const struct bsc_subscr *bsub = ctx->ctx[LOG_CTX_BSC_SUBSCR];
+ const struct gprs_nsvc *nsvc = ctx->ctx[LOG_CTX_GB_NSVC];
+ const struct gprs_nsvc *bvc = ctx->ctx[LOG_CTX_GB_BVC];
+
+ if ((tar->filter_map & (1 << LOG_FLT_VLR_SUBSCR)) != 0
+ && subscr && subscr == tar->filter_data[LOG_FLT_VLR_SUBSCR])
+ return 1;
+
+ if ((tar->filter_map & (1 << LOG_FLT_BSC_SUBSCR)) != 0
+ && bsub && bsub == tar->filter_data[LOG_FLT_BSC_SUBSCR])
+ 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 */
+ if ((tar->filter_map & (1 << LOG_FLT_GB_BVC)) != 0
+ && bvc && (bvc == tar->filter_data[LOG_FLT_GB_BVC]))
+ return 1;
+
+ return 0;
+}
+
+const struct log_info log_info = {
+ .filter_fn = filter_fn,
+ .cat = default_categories,
+ .num_cat = ARRAY_SIZE(default_categories),
+};
+
+void log_set_filter_vlr_subscr(struct log_target *target,
+ struct gsm_subscriber *vlr_subscr)
+{
+ struct gsm_subscriber **fsub = (void*)&target->filter_data[LOG_FLT_VLR_SUBSCR];
+
+ /* free the old data */
+ if (*fsub) {
+ subscr_put(*fsub);
+ *fsub = NULL;
+ }
+
+ if (vlr_subscr) {
+ target->filter_map |= (1 << LOG_FLT_VLR_SUBSCR);
+ *fsub = subscr_get(vlr_subscr);
+ } else
+ target->filter_map &= ~(1 << LOG_FLT_VLR_SUBSCR);
+}
diff --git a/src/libcommon/gsm_data.c b/src/libcommon/gsm_data.c
new file mode 100644
index 000000000..db7de082d
--- /dev/null
+++ b/src/libcommon/gsm_data.c
@@ -0,0 +1,473 @@
+/* (C) 2008-2010 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU 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 <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include <stdbool.h>
+#include <netinet/in.h>
+
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/gsm/gsm_utils.h>
+#include <osmocom/gsm/abis_nm.h>
+#include <osmocom/core/statistics.h>
+#include <osmocom/gsm/protocol/gsm_04_08.h>
+
+#include <openbsc/gsm_data.h>
+#include <openbsc/bsc_msc_data.h>
+#include <openbsc/abis_nm.h>
+
+void *tall_bsc_ctx;
+
+static LLIST_HEAD(bts_models);
+
+void set_ts_e1link(struct gsm_bts_trx_ts *ts, uint8_t e1_nr,
+ uint8_t e1_ts, uint8_t e1_ts_ss)
+{
+ ts->e1_link.e1_nr = e1_nr;
+ ts->e1_link.e1_ts = e1_ts;
+ ts->e1_link.e1_ts_ss = e1_ts_ss;
+}
+
+static struct gsm_bts_model *bts_model_find(enum gsm_bts_type type)
+{
+ struct gsm_bts_model *model;
+
+ llist_for_each_entry(model, &bts_models, list) {
+ if (model->type == type)
+ return model;
+ }
+
+ return NULL;
+}
+
+int gsm_bts_model_register(struct gsm_bts_model *model)
+{
+ if (bts_model_find(model->type))
+ return -EEXIST;
+
+ tlv_def_patch(&model->nm_att_tlvdef, &abis_nm_att_tlvdef);
+ llist_add_tail(&model->list, &bts_models);
+ return 0;
+}
+
+/* Get reference to a neighbor cell on a given BCCH ARFCN */
+struct gsm_bts *gsm_bts_neighbor(const struct gsm_bts *bts,
+ uint16_t arfcn, uint8_t bsic)
+{
+ struct gsm_bts *neigh;
+ /* FIXME: use some better heuristics here to determine which cell
+ * using this ARFCN really is closest to the target cell. For
+ * now we simply assume that each ARFCN will only be used by one
+ * cell */
+
+ llist_for_each_entry(neigh, &bts->network->bts_list, list) {
+ if (neigh->c0->arfcn == arfcn &&
+ neigh->bsic == bsic)
+ return neigh;
+ }
+
+ return NULL;
+}
+
+const struct value_string bts_type_descs[_NUM_GSM_BTS_TYPE+1] = {
+ { GSM_BTS_TYPE_UNKNOWN, "Unknown BTS Type" },
+ { GSM_BTS_TYPE_BS11, "Siemens BTS (BS-11 or compatible)" },
+ { GSM_BTS_TYPE_NANOBTS, "ip.access nanoBTS or compatible" },
+ { GSM_BTS_TYPE_RBS2000, "Ericsson RBS2000 Series" },
+ { GSM_BTS_TYPE_NOKIA_SITE, "Nokia {Metro,Ultra,In}Site" },
+ { GSM_BTS_TYPE_OSMOBTS, "sysmocom sysmoBTS" },
+ { 0, NULL }
+};
+
+struct gsm_bts_trx *gsm_bts_trx_by_nr(struct gsm_bts *bts, int nr)
+{
+ struct gsm_bts_trx *trx;
+
+ llist_for_each_entry(trx, &bts->trx_list, list) {
+ if (trx->nr == nr)
+ return trx;
+ }
+ return NULL;
+}
+
+/* Search for a BTS in the given Location Area; optionally start searching
+ * with start_bts (for continuing to search after the first result) */
+struct gsm_bts *gsm_bts_by_lac(struct gsm_network *net, unsigned int lac,
+ struct gsm_bts *start_bts)
+{
+ int i;
+ struct gsm_bts *bts;
+ int skip = 0;
+
+ if (start_bts)
+ skip = 1;
+
+ for (i = 0; i < net->num_bts; i++) {
+ bts = gsm_bts_num(net, i);
+
+ if (skip) {
+ if (start_bts == bts)
+ skip = 0;
+ continue;
+ }
+
+ if (lac == GSM_LAC_RESERVED_ALL_BTS || bts->location_area_code == lac)
+ return bts;
+ }
+ return NULL;
+}
+
+static const struct value_string auth_policy_names[] = {
+ { GSM_AUTH_POLICY_CLOSED, "closed" },
+ { GSM_AUTH_POLICY_ACCEPT_ALL, "accept-all" },
+ { GSM_AUTH_POLICY_TOKEN, "token" },
+ { GSM_AUTH_POLICY_REGEXP, "regexp" },
+ { 0, NULL }
+};
+
+enum gsm_auth_policy gsm_auth_policy_parse(const char *arg)
+{
+ return get_string_value(auth_policy_names, arg);
+}
+
+const char *gsm_auth_policy_name(enum gsm_auth_policy policy)
+{
+ return get_value_string(auth_policy_names, policy);
+}
+
+static const struct value_string rrlp_mode_names[] = {
+ { RRLP_MODE_NONE, "none" },
+ { RRLP_MODE_MS_BASED, "ms-based" },
+ { RRLP_MODE_MS_PREF, "ms-preferred" },
+ { RRLP_MODE_ASS_PREF, "ass-preferred" },
+ { 0, NULL }
+};
+
+enum rrlp_mode rrlp_mode_parse(const char *arg)
+{
+ return get_string_value(rrlp_mode_names, arg);
+}
+
+const char *rrlp_mode_name(enum rrlp_mode mode)
+{
+ return get_value_string(rrlp_mode_names, mode);
+}
+
+static const struct value_string bts_gprs_mode_names[] = {
+ { BTS_GPRS_NONE, "none" },
+ { BTS_GPRS_GPRS, "gprs" },
+ { BTS_GPRS_EGPRS, "egprs" },
+ { 0, NULL }
+};
+
+enum bts_gprs_mode bts_gprs_mode_parse(const char *arg, int *valid)
+{
+ int rc;
+
+ rc = get_string_value(bts_gprs_mode_names, arg);
+ if (valid)
+ *valid = rc != -EINVAL;
+ return rc;
+}
+
+const char *bts_gprs_mode_name(enum bts_gprs_mode mode)
+{
+ return get_value_string(bts_gprs_mode_names, mode);
+}
+
+int bts_gprs_mode_is_compat(struct gsm_bts *bts, enum bts_gprs_mode mode)
+{
+ if (mode != BTS_GPRS_NONE &&
+ !gsm_btsmodel_has_feature(bts->model, BTS_FEAT_GPRS)) {
+ return 0;
+ }
+ if (mode == BTS_GPRS_EGPRS &&
+ !gsm_btsmodel_has_feature(bts->model, BTS_FEAT_EGPRS)) {
+ return 0;
+ }
+
+ return 1;
+}
+
+struct gsm_meas_rep *lchan_next_meas_rep(struct gsm_lchan *lchan)
+{
+ struct gsm_meas_rep *meas_rep;
+
+ meas_rep = &lchan->meas_rep[lchan->meas_rep_idx];
+ memset(meas_rep, 0, sizeof(*meas_rep));
+ meas_rep->lchan = lchan;
+ lchan->meas_rep_idx = (lchan->meas_rep_idx + 1)
+ % ARRAY_SIZE(lchan->meas_rep);
+
+ return meas_rep;
+}
+
+int gsm_btsmodel_set_feature(struct gsm_bts_model *model, enum gsm_bts_features feat)
+{
+ OSMO_ASSERT(_NUM_BTS_FEAT < MAX_BTS_FEATURES);
+ return bitvec_set_bit_pos(&model->features, feat, 1);
+}
+
+bool gsm_btsmodel_has_feature(struct gsm_bts_model *model, enum gsm_bts_features feat)
+{
+ OSMO_ASSERT(_NUM_BTS_FEAT < MAX_BTS_FEATURES);
+ return bitvec_get_bit_pos(&model->features, feat);
+}
+
+int gsm_set_bts_type(struct gsm_bts *bts, enum gsm_bts_type type)
+{
+ struct gsm_bts_model *model;
+
+ model = bts_model_find(type);
+ if (!model)
+ return -EINVAL;
+
+ bts->type = type;
+ bts->model = model;
+
+ if (model->start && !model->started) {
+ int ret = model->start(bts->network);
+ if (ret < 0)
+ return ret;
+
+ model->started = true;
+ }
+
+ switch (bts->type) {
+ case GSM_BTS_TYPE_NANOBTS:
+ case GSM_BTS_TYPE_OSMOBTS:
+ /* Set the default OML Stream ID to 0xff */
+ bts->oml_tei = 0xff;
+ bts->c0->nominal_power = 23;
+ break;
+ case GSM_BTS_TYPE_RBS2000:
+ INIT_LLIST_HEAD(&bts->rbs2000.is.conn_groups);
+ INIT_LLIST_HEAD(&bts->rbs2000.con.conn_groups);
+ break;
+ case GSM_BTS_TYPE_BS11:
+ case GSM_BTS_TYPE_UNKNOWN:
+ case GSM_BTS_TYPE_NOKIA_SITE:
+ /* Set default BTS reset timer */
+ bts->nokia.bts_reset_timer_cnf = 15;
+ case _NUM_GSM_BTS_TYPE:
+ break;
+ }
+
+ return 0;
+}
+
+struct gsm_bts *gsm_bts_alloc_register(struct gsm_network *net, enum gsm_bts_type type,
+ uint8_t bsic)
+{
+ struct gsm_bts_model *model = bts_model_find(type);
+ struct gsm_bts *bts;
+
+ if (!model && type != GSM_BTS_TYPE_UNKNOWN)
+ return NULL;
+
+ bts = gsm_bts_alloc(net);
+ if (!bts)
+ return NULL;
+
+ bts->network = net;
+ bts->nr = net->num_bts++;
+ bts->type = type;
+ bts->model = model;
+ bts->bsic = bsic;
+ bts->dtxu = GSM48_DTX_SHALL_NOT_BE_USED;
+ bts->dtxd = false;
+ bts->gprs.ctrl_ack_type_use_block = true; /* use RLC/MAC control block */
+ bts->neigh_list_manual_mode = 0;
+ bts->si_common.cell_sel_par.cell_resel_hyst = 2; /* 4 dB */
+ bts->si_common.cell_sel_par.rxlev_acc_min = 0;
+ bts->si_common.si2quater_neigh_list.arfcn = bts->si_common.data.earfcn_list;
+ bts->si_common.si2quater_neigh_list.meas_bw = bts->si_common.data.meas_bw_list;
+ bts->si_common.si2quater_neigh_list.length = MAX_EARFCN_LIST;
+ bts->si_common.si2quater_neigh_list.thresh_hi = 0;
+ osmo_earfcn_init(&bts->si_common.si2quater_neigh_list);
+ bts->si_common.neigh_list.data = bts->si_common.data.neigh_list;
+ bts->si_common.neigh_list.data_len =
+ sizeof(bts->si_common.data.neigh_list);
+ bts->si_common.si5_neigh_list.data = bts->si_common.data.si5_neigh_list;
+ bts->si_common.si5_neigh_list.data_len =
+ sizeof(bts->si_common.data.si5_neigh_list);
+ bts->si_common.cell_alloc.data = bts->si_common.data.cell_alloc;
+ bts->si_common.cell_alloc.data_len =
+ sizeof(bts->si_common.data.cell_alloc);
+ bts->si_common.rach_control.re = 1; /* no re-establishment */
+ bts->si_common.rach_control.tx_integer = 9; /* 12 slots spread - 217/115 slots delay */
+ bts->si_common.rach_control.max_trans = 3; /* 7 retransmissions */
+ bts->si_common.rach_control.t2 = 4; /* no emergency calls */
+ bts->si_common.chan_desc.att = 1; /* attachment required */
+ bts->si_common.chan_desc.bs_pa_mfrms = RSL_BS_PA_MFRMS_5; /* paging frames */
+ bts->si_common.chan_desc.bs_ag_blks_res = 1; /* reserved AGCH blocks */
+ bts->si_common.chan_desc.t3212 = 5; /* Use 30 min periodic update interval as sane default */
+ gsm_bts_set_radio_link_timeout(bts, 32); /* Use RADIO LINK TIMEOUT of 32 */
+
+ llist_add_tail(&bts->list, &net->bts_list);
+
+ INIT_LLIST_HEAD(&bts->abis_queue);
+
+ INIT_LLIST_HEAD(&bts->loc_list);
+
+ return bts;
+}
+
+void gprs_ra_id_by_bts(struct gprs_ra_id *raid, struct gsm_bts *bts)
+{
+ raid->mcc = bts->network->country_code;
+ raid->mnc = bts->network->network_code;
+ raid->lac = bts->location_area_code;
+ raid->rac = bts->gprs.rac;
+}
+
+int gsm48_ra_id_by_bts(uint8_t *buf, struct gsm_bts *bts)
+{
+ struct gprs_ra_id raid;
+
+ gprs_ra_id_by_bts(&raid, bts);
+
+ return gsm48_construct_ra(buf, &raid);
+}
+
+int gsm_parse_reg(void *ctx, regex_t *reg, char **str, int argc, const char **argv)
+{
+ int ret;
+
+ ret = 0;
+ if (*str) {
+ talloc_free(*str);
+ *str = NULL;
+ }
+ regfree(reg);
+
+ if (argc > 0) {
+ *str = talloc_strdup(ctx, argv[0]);
+ ret = regcomp(reg, argv[0], 0);
+
+ /* handle compilation failures */
+ if (ret != 0) {
+ talloc_free(*str);
+ *str = NULL;
+ }
+ }
+
+ return ret;
+}
+
+/* Assume there are only 256 possible bts */
+osmo_static_assert(sizeof(((struct gsm_bts *) 0)->nr) == 1, _bts_nr_is_256);
+static void depends_calc_index_bit(int bts_nr, int *idx, int *bit)
+{
+ *idx = bts_nr / (8 * 4);
+ *bit = bts_nr % (8 * 4);
+}
+
+void bts_depend_mark(struct gsm_bts *bts, int dep)
+{
+ int idx, bit;
+ depends_calc_index_bit(dep, &idx, &bit);
+
+ bts->depends_on[idx] |= 1 << bit;
+}
+
+void bts_depend_clear(struct gsm_bts *bts, int dep)
+{
+ int idx, bit;
+ depends_calc_index_bit(dep, &idx, &bit);
+
+ bts->depends_on[idx] &= ~(1 << bit);
+}
+
+int bts_depend_is_depedency(struct gsm_bts *base, struct gsm_bts *other)
+{
+ int idx, bit;
+ depends_calc_index_bit(other->nr, &idx, &bit);
+
+ /* Check if there is a depends bit */
+ return (base->depends_on[idx] & (1 << bit)) > 0;
+}
+
+static int bts_is_online(struct gsm_bts *bts)
+{
+ /* TODO: support E1 BTS too */
+ if (!is_ipaccess_bts(bts))
+ return 1;
+
+ if (!bts->oml_link)
+ return 0;
+
+ return bts->mo.nm_state.operational == NM_OPSTATE_ENABLED;
+}
+
+int bts_depend_check(struct gsm_bts *bts)
+{
+ struct gsm_bts *other_bts;
+
+ llist_for_each_entry(other_bts, &bts->network->bts_list, list) {
+ if (!bts_depend_is_depedency(bts, other_bts))
+ continue;
+ if (bts_is_online(other_bts))
+ continue;
+ return 0;
+ }
+ return 1;
+}
+
+/* get the radio link timeout (based on SACCH decode errors, according
+ * to algorithm specified in TS 05.08 section 5.2. A value of -1
+ * indicates we should use an infinitely long timeout, which only works
+ * with OsmoBTS as the BTS implementation */
+int gsm_bts_get_radio_link_timeout(const struct gsm_bts *bts)
+{
+ const struct gsm48_cell_options *cell_options = &bts->si_common.cell_options;
+
+ if (bts->infinite_radio_link_timeout)
+ return -1;
+ else {
+ /* Encoding as per Table 10.5.21 of TS 04.08 */
+ return (cell_options->radio_link_timeout + 1) << 2;
+ }
+}
+
+/* set the radio link timeout (based on SACCH decode errors, according
+ * to algorithm specified in TS 05.08 Section 5.2. A value of -1
+ * indicates we should use an infinitely long timeout, which only works
+ * with OsmoBTS as the BTS implementation */
+void gsm_bts_set_radio_link_timeout(struct gsm_bts *bts, int value)
+{
+ struct gsm48_cell_options *cell_options = &bts->si_common.cell_options;
+
+ if (value < 0)
+ bts->infinite_radio_link_timeout = true;
+ else {
+ bts->infinite_radio_link_timeout = false;
+ /* Encoding as per Table 10.5.21 of TS 04.08 */
+ if (value < 4)
+ value = 4;
+ if (value > 64)
+ value = 64;
+ cell_options->radio_link_timeout = (value >> 2) - 1;
+ }
+}
diff --git a/src/libcommon/gsm_data_shared.c b/src/libcommon/gsm_data_shared.c
new file mode 100644
index 000000000..89926364c
--- /dev/null
+++ b/src/libcommon/gsm_data_shared.c
@@ -0,0 +1,849 @@
+/* (C) 2008-2010 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU 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 <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+
+#include <netinet/in.h>
+
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/gsm/gsm_utils.h>
+#include <osmocom/gsm/abis_nm.h>
+#include <osmocom/core/statistics.h>
+
+#include <openbsc/gsm_data.h>
+
+void gsm_abis_mo_reset(struct gsm_abis_mo *mo)
+{
+ mo->nm_state.operational = NM_OPSTATE_NULL;
+ mo->nm_state.availability = NM_AVSTATE_POWER_OFF;
+}
+
+static void gsm_mo_init(struct gsm_abis_mo *mo, struct gsm_bts *bts,
+ uint8_t obj_class, uint8_t p1, uint8_t p2, uint8_t p3)
+{
+ mo->bts = bts;
+ mo->obj_class = obj_class;
+ mo->obj_inst.bts_nr = p1;
+ mo->obj_inst.trx_nr = p2;
+ mo->obj_inst.ts_nr = p3;
+ gsm_abis_mo_reset(mo);
+}
+
+const struct value_string bts_attribute_names[] = {
+ OSMO_VALUE_STRING(BTS_TYPE_VARIANT),
+ OSMO_VALUE_STRING(BTS_SUB_MODEL),
+ OSMO_VALUE_STRING(TRX_PHY_VERSION),
+ { 0, NULL }
+};
+
+enum bts_attribute str2btsattr(const char *s)
+{
+ return get_string_value(bts_attribute_names, s);
+}
+
+const char *btsatttr2str(enum bts_attribute v)
+{
+ return get_value_string(bts_attribute_names, v);
+}
+
+const struct value_string osmo_bts_variant_names[_NUM_BTS_VARIANT + 1] = {
+ { BTS_UNKNOWN, "unknown" },
+ { BTS_OSMO_LITECELL15, "osmo-bts-lc15" },
+ { BTS_OSMO_OCTPHY, "osmo-bts-octphy" },
+ { BTS_OSMO_SYSMO, "osmo-bts-sysmo" },
+ { BTS_OSMO_TRX, "omso-bts-trx" },
+ { 0, NULL }
+};
+
+enum gsm_bts_type_variant str2btsvariant(const char *arg)
+{
+ return get_string_value(osmo_bts_variant_names, arg);
+}
+
+const char *btsvariant2str(enum gsm_bts_type_variant v)
+{
+ return get_value_string(osmo_bts_variant_names, v);
+}
+
+const struct value_string bts_type_names[_NUM_GSM_BTS_TYPE + 1] = {
+ { GSM_BTS_TYPE_UNKNOWN, "unknown" },
+ { GSM_BTS_TYPE_BS11, "bs11" },
+ { GSM_BTS_TYPE_NANOBTS, "nanobts" },
+ { GSM_BTS_TYPE_RBS2000, "rbs2000" },
+ { GSM_BTS_TYPE_NOKIA_SITE, "nokia_site" },
+ { GSM_BTS_TYPE_OSMOBTS, "sysmobts" },
+ { 0, NULL }
+};
+
+enum gsm_bts_type str2btstype(const char *arg)
+{
+ return get_string_value(bts_type_names, arg);
+}
+
+const char *btstype2str(enum gsm_bts_type type)
+{
+ return get_value_string(bts_type_names, type);
+}
+
+const struct value_string gsm_bts_features_descs[] = {
+ { BTS_FEAT_HSCSD, "HSCSD" },
+ { BTS_FEAT_GPRS, "GPRS" },
+ { BTS_FEAT_EGPRS, "EGPRS" },
+ { BTS_FEAT_ECSD, "ECSD" },
+ { BTS_FEAT_HOPPING, "Frequency Hopping" },
+ { BTS_FEAT_MULTI_TSC, "Multi-TSC" },
+ { BTS_FEAT_OML_ALERTS, "OML Alerts" },
+ { BTS_FEAT_AGCH_PCH_PROP, "AGCH/PCH proportional allocation" },
+ { BTS_FEAT_CBCH, "CBCH" },
+ { 0, NULL }
+};
+
+const struct value_string gsm_chreq_descs[] = {
+ { GSM_CHREQ_REASON_EMERG, "emergency call" },
+ { GSM_CHREQ_REASON_PAG, "answer to paging" },
+ { GSM_CHREQ_REASON_CALL, "call re-establishment" },
+ { GSM_CHREQ_REASON_LOCATION_UPD,"Location updating" },
+ { GSM_CHREQ_REASON_PDCH, "one phase packet access" },
+ { GSM_CHREQ_REASON_OTHER, "other" },
+ { 0, NULL }
+};
+
+const struct value_string gsm_pchant_names[13] = {
+ { GSM_PCHAN_NONE, "NONE" },
+ { GSM_PCHAN_CCCH, "CCCH" },
+ { GSM_PCHAN_CCCH_SDCCH4,"CCCH+SDCCH4" },
+ { GSM_PCHAN_TCH_F, "TCH/F" },
+ { GSM_PCHAN_TCH_H, "TCH/H" },
+ { GSM_PCHAN_SDCCH8_SACCH8C, "SDCCH8" },
+ { GSM_PCHAN_PDCH, "PDCH" },
+ { GSM_PCHAN_TCH_F_PDCH, "TCH/F_PDCH" },
+ { GSM_PCHAN_UNKNOWN, "UNKNOWN" },
+ { GSM_PCHAN_CCCH_SDCCH4_CBCH, "CCCH+SDCCH4+CBCH" },
+ { GSM_PCHAN_SDCCH8_SACCH8C_CBCH, "SDCCH8+CBCH" },
+ { GSM_PCHAN_TCH_F_TCH_H_PDCH, "TCH/F_TCH/H_PDCH" },
+ { 0, NULL }
+};
+
+const struct value_string gsm_pchant_descs[13] = {
+ { GSM_PCHAN_NONE, "Physical Channel not configured" },
+ { GSM_PCHAN_CCCH, "FCCH + SCH + BCCH + CCCH (Comb. IV)" },
+ { GSM_PCHAN_CCCH_SDCCH4,
+ "FCCH + SCH + BCCH + CCCH + 4 SDCCH + 2 SACCH (Comb. V)" },
+ { GSM_PCHAN_TCH_F, "TCH/F + FACCH/F + SACCH (Comb. I)" },
+ { GSM_PCHAN_TCH_H, "2 TCH/H + 2 FACCH/H + 2 SACCH (Comb. II)" },
+ { GSM_PCHAN_SDCCH8_SACCH8C, "8 SDCCH + 4 SACCH (Comb. VII)" },
+ { GSM_PCHAN_PDCH, "Packet Data Channel for GPRS/EDGE" },
+ { GSM_PCHAN_TCH_F_PDCH, "Dynamic TCH/F or GPRS PDCH" },
+ { GSM_PCHAN_UNKNOWN, "Unknown / Unsupported channel combination" },
+ { GSM_PCHAN_CCCH_SDCCH4_CBCH, "FCCH + SCH + BCCH + CCCH + CBCH + 3 SDCCH + 2 SACCH (Comb. V)" },
+ { GSM_PCHAN_SDCCH8_SACCH8C_CBCH, "7 SDCCH + 4 SACCH + CBCH (Comb. VII)" },
+ { GSM_PCHAN_TCH_F_TCH_H_PDCH, "Dynamic TCH/F or TCH/H or GPRS PDCH" },
+ { 0, NULL }
+};
+
+const char *gsm_pchan_name(enum gsm_phys_chan_config c)
+{
+ return get_value_string(gsm_pchant_names, c);
+}
+
+enum gsm_phys_chan_config gsm_pchan_parse(const char *name)
+{
+ return get_string_value(gsm_pchant_names, name);
+}
+
+/* TODO: move to libosmocore, next to gsm_chan_t_names? */
+const char *gsm_lchant_name(enum gsm_chan_t c)
+{
+ return get_value_string(gsm_chan_t_names, c);
+}
+
+static const struct value_string lchan_s_names[] = {
+ { LCHAN_S_NONE, "NONE" },
+ { LCHAN_S_ACT_REQ, "ACTIVATION REQUESTED" },
+ { LCHAN_S_ACTIVE, "ACTIVE" },
+ { LCHAN_S_INACTIVE, "INACTIVE" },
+ { LCHAN_S_REL_REQ, "RELEASE REQUESTED" },
+ { LCHAN_S_REL_ERR, "RELEASE DUE ERROR" },
+ { LCHAN_S_BROKEN, "BROKEN UNUSABLE" },
+ { 0, NULL }
+};
+
+const char *gsm_lchans_name(enum gsm_lchan_state s)
+{
+ return get_value_string(lchan_s_names, s);
+}
+
+static const struct value_string chreq_names[] = {
+ { GSM_CHREQ_REASON_EMERG, "EMERGENCY" },
+ { GSM_CHREQ_REASON_PAG, "PAGING" },
+ { GSM_CHREQ_REASON_CALL, "CALL" },
+ { GSM_CHREQ_REASON_LOCATION_UPD,"LOCATION_UPDATE" },
+ { GSM_CHREQ_REASON_OTHER, "OTHER" },
+ { 0, NULL }
+};
+
+const char *gsm_chreq_name(enum gsm_chreq_reason_t c)
+{
+ return get_value_string(chreq_names, c);
+}
+
+struct gsm_bts *gsm_bts_num(struct gsm_network *net, int num)
+{
+ struct gsm_bts *bts;
+
+ if (num >= net->num_bts)
+ return NULL;
+
+ llist_for_each_entry(bts, &net->bts_list, list) {
+ if (bts->nr == num)
+ return bts;
+ }
+
+ return NULL;
+}
+
+struct gsm_bts_trx *gsm_bts_trx_alloc(struct gsm_bts *bts)
+{
+ struct gsm_bts_trx *trx = talloc_zero(bts, struct gsm_bts_trx);
+ int k;
+
+ if (!trx)
+ return NULL;
+
+ trx->bts = bts;
+ trx->nr = bts->num_trx++;
+ trx->mo.nm_state.administrative = NM_STATE_UNLOCKED;
+
+ gsm_mo_init(&trx->mo, bts, NM_OC_RADIO_CARRIER,
+ bts->nr, trx->nr, 0xff);
+ gsm_mo_init(&trx->bb_transc.mo, bts, NM_OC_BASEB_TRANSC,
+ bts->nr, trx->nr, 0xff);
+
+ for (k = 0; k < TRX_NR_TS; k++) {
+ struct gsm_bts_trx_ts *ts = &trx->ts[k];
+ int l;
+
+ ts->trx = trx;
+ ts->nr = k;
+ ts->pchan = GSM_PCHAN_NONE;
+ ts->dyn.pchan_is = GSM_PCHAN_NONE;
+ ts->dyn.pchan_want = GSM_PCHAN_NONE;
+ ts->tsc = -1;
+
+ gsm_mo_init(&ts->mo, bts, NM_OC_CHANNEL,
+ bts->nr, trx->nr, ts->nr);
+
+ ts->hopping.arfcns.data_len = sizeof(ts->hopping.arfcns_data);
+ ts->hopping.arfcns.data = ts->hopping.arfcns_data;
+ ts->hopping.ma.data_len = sizeof(ts->hopping.ma_data);
+ ts->hopping.ma.data = ts->hopping.ma_data;
+
+ for (l = 0; l < TS_MAX_LCHAN; l++) {
+ struct gsm_lchan *lchan;
+ char *name;
+ lchan = &ts->lchan[l];
+
+ lchan->ts = ts;
+ lchan->nr = l;
+ lchan->type = GSM_LCHAN_NONE;
+
+ name = gsm_lchan_name_compute(lchan);
+ lchan->name = talloc_strdup(trx, name);
+#ifndef ROLE_BSC
+ INIT_LLIST_HEAD(&lchan->sapi_cmds);
+#endif
+ }
+ }
+
+ if (trx->nr != 0)
+ trx->nominal_power = bts->c0->nominal_power;
+
+ llist_add_tail(&trx->list, &bts->trx_list);
+
+ return trx;
+}
+
+
+static const uint8_t bts_nse_timer_default[] = { 3, 3, 3, 3, 30, 3, 10 };
+static const uint8_t bts_cell_timer_default[] =
+ { 3, 3, 3, 3, 3, 10, 3, 10, 3, 10, 3 };
+static const struct gprs_rlc_cfg rlc_cfg_default = {
+ .parameter = {
+ [RLC_T3142] = 20,
+ [RLC_T3169] = 5,
+ [RLC_T3191] = 5,
+ [RLC_T3193] = 160, /* 10ms */
+ [RLC_T3195] = 5,
+ [RLC_N3101] = 10,
+ [RLC_N3103] = 4,
+ [RLC_N3105] = 8,
+ [CV_COUNTDOWN] = 15,
+ [T_DL_TBF_EXT] = 250 * 10, /* ms */
+ [T_UL_TBF_EXT] = 250 * 10, /* ms */
+ },
+ .paging = {
+ .repeat_time = 5 * 50, /* ms */
+ .repeat_count = 3,
+ },
+ .cs_mask = 0x1fff,
+ .initial_cs = 2,
+ .initial_mcs = 6,
+};
+
+struct gsm_bts *gsm_bts_alloc(void *ctx)
+{
+ struct gsm_bts *bts = talloc_zero(ctx, struct gsm_bts);
+ int i;
+
+ if (!bts)
+ return NULL;
+
+ bts->num_trx = 0;
+ INIT_LLIST_HEAD(&bts->trx_list);
+ bts->ms_max_power = 15; /* dBm */
+
+ gsm_mo_init(&bts->mo, bts, NM_OC_BTS,
+ bts->nr, 0xff, 0xff);
+ gsm_mo_init(&bts->site_mgr.mo, bts, NM_OC_SITE_MANAGER,
+ 0xff, 0xff, 0xff);
+
+ for (i = 0; i < ARRAY_SIZE(bts->gprs.nsvc); i++) {
+ bts->gprs.nsvc[i].bts = bts;
+ bts->gprs.nsvc[i].id = i;
+ gsm_mo_init(&bts->gprs.nsvc[i].mo, bts, NM_OC_GPRS_NSVC,
+ bts->nr, i, 0xff);
+ }
+ memcpy(&bts->gprs.nse.timer, bts_nse_timer_default,
+ sizeof(bts->gprs.nse.timer));
+ gsm_mo_init(&bts->gprs.nse.mo, bts, NM_OC_GPRS_NSE,
+ bts->nr, 0xff, 0xff);
+ memcpy(&bts->gprs.cell.timer, bts_cell_timer_default,
+ sizeof(bts->gprs.cell.timer));
+ gsm_mo_init(&bts->gprs.cell.mo, bts, NM_OC_GPRS_CELL,
+ bts->nr, 0xff, 0xff);
+ memcpy(&bts->gprs.cell.rlc_cfg, &rlc_cfg_default,
+ sizeof(bts->gprs.cell.rlc_cfg));
+
+ /* create our primary TRX */
+ bts->c0 = gsm_bts_trx_alloc(bts);
+ if (!bts->c0) {
+ talloc_free(bts);
+ return NULL;
+ }
+ bts->c0->ts[0].pchan = GSM_PCHAN_CCCH_SDCCH4;
+
+ bts->rach_b_thresh = -1;
+ bts->rach_ldavg_slots = -1;
+ bts->paging.free_chans_need = -1;
+ bts->features.data = &bts->_features_data[0];
+ bts->features.data_len = sizeof(bts->_features_data);
+
+ /* si handling */
+ bts->bcch_change_mark = 1;
+
+ return bts;
+}
+
+/* reset the state of all MO in the BTS */
+void gsm_bts_mo_reset(struct gsm_bts *bts)
+{
+ struct gsm_bts_trx *trx;
+ unsigned int i;
+
+ gsm_abis_mo_reset(&bts->mo);
+ gsm_abis_mo_reset(&bts->site_mgr.mo);
+ for (i = 0; i < ARRAY_SIZE(bts->gprs.nsvc); i++)
+ gsm_abis_mo_reset(&bts->gprs.nsvc[i].mo);
+ gsm_abis_mo_reset(&bts->gprs.nse.mo);
+ gsm_abis_mo_reset(&bts->gprs.cell.mo);
+
+ llist_for_each_entry(trx, &bts->trx_list, list) {
+ gsm_abis_mo_reset(&trx->mo);
+ gsm_abis_mo_reset(&trx->bb_transc.mo);
+
+ for (i = 0; i < ARRAY_SIZE(trx->ts); i++) {
+ struct gsm_bts_trx_ts *ts = &trx->ts[i];
+ gsm_abis_mo_reset(&ts->mo);
+ }
+ }
+}
+
+struct gsm_bts_trx *gsm_bts_trx_num(const struct gsm_bts *bts, int num)
+{
+ struct gsm_bts_trx *trx;
+
+ if (num >= bts->num_trx)
+ return NULL;
+
+ llist_for_each_entry(trx, &bts->trx_list, list) {
+ if (trx->nr == num)
+ return trx;
+ }
+
+ return NULL;
+}
+
+static char ts2str[255];
+
+char *gsm_trx_name(const struct gsm_bts_trx *trx)
+{
+ if (!trx)
+ snprintf(ts2str, sizeof(ts2str), "(trx=NULL)");
+ else
+ snprintf(ts2str, sizeof(ts2str), "(bts=%d,trx=%d)",
+ trx->bts->nr, trx->nr);
+
+ return ts2str;
+}
+
+
+char *gsm_ts_name(const struct gsm_bts_trx_ts *ts)
+{
+ snprintf(ts2str, sizeof(ts2str), "(bts=%d,trx=%d,ts=%d)",
+ ts->trx->bts->nr, ts->trx->nr, ts->nr);
+
+ return ts2str;
+}
+
+/*! Log timeslot number with full pchan information */
+char *gsm_ts_and_pchan_name(const struct gsm_bts_trx_ts *ts)
+{
+ switch (ts->pchan) {
+ case GSM_PCHAN_TCH_F_TCH_H_PDCH:
+ if (ts->dyn.pchan_is == ts->dyn.pchan_want)
+ snprintf(ts2str, sizeof(ts2str),
+ "(bts=%d,trx=%d,ts=%d,pchan=%s as %s)",
+ ts->trx->bts->nr, ts->trx->nr, ts->nr,
+ gsm_pchan_name(ts->pchan),
+ gsm_pchan_name(ts->dyn.pchan_is));
+ else
+ snprintf(ts2str, sizeof(ts2str),
+ "(bts=%d,trx=%d,ts=%d,pchan=%s"
+ " switching %s -> %s)",
+ ts->trx->bts->nr, ts->trx->nr, ts->nr,
+ gsm_pchan_name(ts->pchan),
+ gsm_pchan_name(ts->dyn.pchan_is),
+ gsm_pchan_name(ts->dyn.pchan_want));
+ break;
+ case GSM_PCHAN_TCH_F_PDCH:
+ if ((ts->flags & TS_F_PDCH_PENDING_MASK) == 0)
+ snprintf(ts2str, sizeof(ts2str),
+ "(bts=%d,trx=%d,ts=%d,pchan=%s as %s)",
+ ts->trx->bts->nr, ts->trx->nr, ts->nr,
+ gsm_pchan_name(ts->pchan),
+ (ts->flags & TS_F_PDCH_ACTIVE)? "PDCH"
+ : "TCH/F");
+ else
+ snprintf(ts2str, sizeof(ts2str),
+ "(bts=%d,trx=%d,ts=%d,pchan=%s"
+ " switching %s -> %s)",
+ ts->trx->bts->nr, ts->trx->nr, ts->nr,
+ gsm_pchan_name(ts->pchan),
+ (ts->flags & TS_F_PDCH_ACTIVE)? "PDCH"
+ : "TCH/F",
+ (ts->flags & TS_F_PDCH_ACT_PENDING)? "PDCH"
+ : "TCH/F");
+ break;
+ default:
+ snprintf(ts2str, sizeof(ts2str), "(bts=%d,trx=%d,ts=%d,pchan=%s)",
+ ts->trx->bts->nr, ts->trx->nr, ts->nr,
+ gsm_pchan_name(ts->pchan));
+ break;
+ }
+
+ return ts2str;
+}
+
+char *gsm_lchan_name_compute(const struct gsm_lchan *lchan)
+{
+ struct gsm_bts_trx_ts *ts = lchan->ts;
+
+ snprintf(ts2str, sizeof(ts2str), "(bts=%d,trx=%d,ts=%d,ss=%d)",
+ ts->trx->bts->nr, ts->trx->nr, ts->nr, lchan->nr);
+
+ return ts2str;
+}
+
+/* obtain the MO structure for a given object instance */
+struct gsm_abis_mo *
+gsm_objclass2mo(struct gsm_bts *bts, uint8_t obj_class,
+ const struct abis_om_obj_inst *obj_inst)
+{
+ struct gsm_bts_trx *trx;
+ struct gsm_abis_mo *mo = NULL;
+
+ switch (obj_class) {
+ case NM_OC_BTS:
+ mo = &bts->mo;
+ break;
+ case NM_OC_RADIO_CARRIER:
+ if (obj_inst->trx_nr >= bts->num_trx) {
+ return NULL;
+ }
+ trx = gsm_bts_trx_num(bts, obj_inst->trx_nr);
+ mo = &trx->mo;
+ break;
+ case NM_OC_BASEB_TRANSC:
+ if (obj_inst->trx_nr >= bts->num_trx) {
+ return NULL;
+ }
+ trx = gsm_bts_trx_num(bts, obj_inst->trx_nr);
+ mo = &trx->bb_transc.mo;
+ break;
+ case NM_OC_CHANNEL:
+ if (obj_inst->trx_nr >= bts->num_trx) {
+ return NULL;
+ }
+ trx = gsm_bts_trx_num(bts, obj_inst->trx_nr);
+ if (obj_inst->ts_nr >= TRX_NR_TS)
+ return NULL;
+ mo = &trx->ts[obj_inst->ts_nr].mo;
+ break;
+ case NM_OC_SITE_MANAGER:
+ mo = &bts->site_mgr.mo;
+ break;
+ case NM_OC_BS11:
+ switch (obj_inst->bts_nr) {
+ case BS11_OBJ_CCLK:
+ mo = &bts->bs11.cclk.mo;
+ break;
+ case BS11_OBJ_BBSIG:
+ if (obj_inst->ts_nr > bts->num_trx)
+ return NULL;
+ trx = gsm_bts_trx_num(bts, obj_inst->trx_nr);
+ mo = &trx->bs11.bbsig.mo;
+ break;
+ case BS11_OBJ_PA:
+ if (obj_inst->ts_nr > bts->num_trx)
+ return NULL;
+ trx = gsm_bts_trx_num(bts, obj_inst->trx_nr);
+ mo = &trx->bs11.pa.mo;
+ break;
+ default:
+ return NULL;
+ }
+ break;
+ case NM_OC_BS11_RACK:
+ mo = &bts->bs11.rack.mo;
+ break;
+ case NM_OC_BS11_ENVABTSE:
+ if (obj_inst->trx_nr >= ARRAY_SIZE(bts->bs11.envabtse))
+ return NULL;
+ mo = &bts->bs11.envabtse[obj_inst->trx_nr].mo;
+ break;
+ case NM_OC_GPRS_NSE:
+ mo = &bts->gprs.nse.mo;
+ break;
+ case NM_OC_GPRS_CELL:
+ mo = &bts->gprs.cell.mo;
+ break;
+ case NM_OC_GPRS_NSVC:
+ if (obj_inst->trx_nr >= ARRAY_SIZE(bts->gprs.nsvc))
+ return NULL;
+ mo = &bts->gprs.nsvc[obj_inst->trx_nr].mo;
+ break;
+ }
+ return mo;
+}
+
+/* obtain the gsm_nm_state data structure for a given object instance */
+struct gsm_nm_state *
+gsm_objclass2nmstate(struct gsm_bts *bts, uint8_t obj_class,
+ const struct abis_om_obj_inst *obj_inst)
+{
+ struct gsm_abis_mo *mo;
+
+ mo = gsm_objclass2mo(bts, obj_class, obj_inst);
+ if (!mo)
+ return NULL;
+
+ return &mo->nm_state;
+}
+
+/* obtain the in-memory data structure of a given object instance */
+void *
+gsm_objclass2obj(struct gsm_bts *bts, uint8_t obj_class,
+ const struct abis_om_obj_inst *obj_inst)
+{
+ struct gsm_bts_trx *trx;
+ void *obj = NULL;
+
+ switch (obj_class) {
+ case NM_OC_BTS:
+ obj = bts;
+ break;
+ case NM_OC_RADIO_CARRIER:
+ if (obj_inst->trx_nr >= bts->num_trx) {
+ return NULL;
+ }
+ trx = gsm_bts_trx_num(bts, obj_inst->trx_nr);
+ obj = trx;
+ break;
+ case NM_OC_BASEB_TRANSC:
+ if (obj_inst->trx_nr >= bts->num_trx) {
+ return NULL;
+ }
+ trx = gsm_bts_trx_num(bts, obj_inst->trx_nr);
+ obj = &trx->bb_transc;
+ break;
+ case NM_OC_CHANNEL:
+ if (obj_inst->trx_nr >= bts->num_trx) {
+ return NULL;
+ }
+ trx = gsm_bts_trx_num(bts, obj_inst->trx_nr);
+ if (obj_inst->ts_nr >= TRX_NR_TS)
+ return NULL;
+ obj = &trx->ts[obj_inst->ts_nr];
+ break;
+ case NM_OC_SITE_MANAGER:
+ obj = &bts->site_mgr;
+ break;
+ case NM_OC_GPRS_NSE:
+ obj = &bts->gprs.nse;
+ break;
+ case NM_OC_GPRS_CELL:
+ obj = &bts->gprs.cell;
+ break;
+ case NM_OC_GPRS_NSVC:
+ if (obj_inst->trx_nr >= ARRAY_SIZE(bts->gprs.nsvc))
+ return NULL;
+ obj = &bts->gprs.nsvc[obj_inst->trx_nr];
+ break;
+ }
+ return obj;
+}
+
+/* See Table 10.5.25 of GSM04.08 */
+uint8_t gsm_pchan2chan_nr(enum gsm_phys_chan_config pchan,
+ uint8_t ts_nr, uint8_t lchan_nr)
+{
+ uint8_t cbits, chan_nr;
+
+ switch (pchan) {
+ case GSM_PCHAN_TCH_F:
+ case GSM_PCHAN_PDCH:
+ case GSM_PCHAN_TCH_F_PDCH:
+ OSMO_ASSERT(lchan_nr == 0);
+ cbits = 0x01;
+ break;
+ case GSM_PCHAN_TCH_H:
+ OSMO_ASSERT(lchan_nr < 2);
+ cbits = 0x02;
+ cbits += lchan_nr;
+ break;
+ case GSM_PCHAN_CCCH_SDCCH4:
+ case GSM_PCHAN_CCCH_SDCCH4_CBCH:
+ /*
+ * As a special hack for BCCH, lchan_nr == 4 may be passed
+ * here. This should never be sent in an RSL message.
+ * See osmo-bts-xxx/oml.c:opstart_compl().
+ */
+ if (lchan_nr == CCCH_LCHAN)
+ chan_nr = 0;
+ else
+ OSMO_ASSERT(lchan_nr < 4);
+ cbits = 0x04;
+ cbits += lchan_nr;
+ break;
+ case GSM_PCHAN_SDCCH8_SACCH8C:
+ case GSM_PCHAN_SDCCH8_SACCH8C_CBCH:
+ OSMO_ASSERT(lchan_nr < 8);
+ cbits = 0x08;
+ cbits += lchan_nr;
+ break;
+ default:
+ case GSM_PCHAN_CCCH:
+#ifdef ROLE_BSC
+ OSMO_ASSERT(lchan_nr == 0);
+#else
+ /*
+ * FIXME: On octphy and litecell, we hit above assertion (see
+ * Max's comment at https://gerrit.osmocom.org/589 ); disabled
+ * for BTS until this is clarified; remove the #ifdef when it
+ * is fixed.
+ */
+#warning "fix caller that passes lchan_nr != 0"
+#endif
+ cbits = 0x10;
+ break;
+ }
+
+ chan_nr = (cbits << 3) | (ts_nr & 0x7);
+
+ return chan_nr;
+}
+
+uint8_t gsm_lchan2chan_nr(const struct gsm_lchan *lchan)
+{
+ enum gsm_phys_chan_config pchan = lchan->ts->pchan;
+ if (pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH)
+ return gsm_lchan_as_pchan2chan_nr(lchan,
+ lchan->ts->dyn.pchan_is);
+ return gsm_pchan2chan_nr(lchan->ts->pchan, lchan->ts->nr, lchan->nr);
+}
+
+uint8_t gsm_lchan_as_pchan2chan_nr(const struct gsm_lchan *lchan,
+ enum gsm_phys_chan_config as_pchan)
+{
+ if (lchan->ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH
+ && as_pchan == GSM_PCHAN_PDCH)
+ return RSL_CHAN_OSMO_PDCH | (lchan->ts->nr & ~RSL_CHAN_NR_MASK);
+ return gsm_pchan2chan_nr(as_pchan, lchan->ts->nr, lchan->nr);
+}
+
+/* return the gsm_lchan for the CBCH (if it exists at all) */
+struct gsm_lchan *gsm_bts_get_cbch(struct gsm_bts *bts)
+{
+ struct gsm_lchan *lchan = NULL;
+ struct gsm_bts_trx *trx = bts->c0;
+
+ if (trx->ts[0].pchan == GSM_PCHAN_CCCH_SDCCH4_CBCH)
+ lchan = &trx->ts[0].lchan[2];
+ else {
+ int i;
+ for (i = 0; i < 8; i++) {
+ if (trx->ts[i].pchan == GSM_PCHAN_SDCCH8_SACCH8C_CBCH) {
+ lchan = &trx->ts[i].lchan[2];
+ break;
+ }
+ }
+ }
+
+ return lchan;
+}
+
+/* determine logical channel based on TRX and channel number IE */
+struct gsm_lchan *rsl_lchan_lookup(struct gsm_bts_trx *trx, uint8_t chan_nr,
+ int *rc)
+{
+ uint8_t ts_nr = chan_nr & 0x07;
+ uint8_t cbits = chan_nr >> 3;
+ uint8_t lch_idx;
+ struct gsm_bts_trx_ts *ts = &trx->ts[ts_nr];
+ bool ok = true;
+
+ if (rc)
+ *rc = -EINVAL;
+
+ if (cbits == 0x01) {
+ lch_idx = 0; /* TCH/F */
+ if (ts->pchan != GSM_PCHAN_TCH_F &&
+ ts->pchan != GSM_PCHAN_PDCH &&
+ ts->pchan != GSM_PCHAN_TCH_F_PDCH
+ && !(ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH
+ && (ts->dyn.pchan_is == GSM_PCHAN_TCH_F
+ || ts->dyn.pchan_want == GSM_PCHAN_TCH_F)))
+ ok = false;
+ } else if ((cbits & 0x1e) == 0x02) {
+ lch_idx = cbits & 0x1; /* TCH/H */
+ if (ts->pchan != GSM_PCHAN_TCH_H
+ && !(ts->pchan == GSM_PCHAN_TCH_F_TCH_H_PDCH
+ && (ts->dyn.pchan_is == GSM_PCHAN_TCH_H
+ || ts->dyn.pchan_want == GSM_PCHAN_TCH_H)))
+ ok = false;
+ } else if ((cbits & 0x1c) == 0x04) {
+ lch_idx = cbits & 0x3; /* SDCCH/4 */
+ if (ts->pchan != GSM_PCHAN_CCCH_SDCCH4 &&
+ ts->pchan != GSM_PCHAN_CCCH_SDCCH4_CBCH)
+ ok = false;
+ } else if ((cbits & 0x18) == 0x08) {
+ lch_idx = cbits & 0x7; /* SDCCH/8 */
+ if (ts->pchan != GSM_PCHAN_SDCCH8_SACCH8C &&
+ ts->pchan != GSM_PCHAN_SDCCH8_SACCH8C_CBCH)
+ ok = false;
+ } else if (cbits == 0x10 || cbits == 0x11 || cbits == 0x12) {
+ lch_idx = 0;
+ if (ts->pchan != GSM_PCHAN_CCCH &&
+ ts->pchan != GSM_PCHAN_CCCH_SDCCH4 &&
+ ts->pchan != GSM_PCHAN_CCCH_SDCCH4_CBCH)
+ ok = false;
+ /* FIXME: we should not return first sdcch4 !!! */
+ } else if ((chan_nr & RSL_CHAN_NR_MASK) == RSL_CHAN_OSMO_PDCH) {
+ lch_idx = 0;
+ if (ts->pchan != GSM_PCHAN_TCH_F_TCH_H_PDCH)
+ ok = false;
+ } else
+ return NULL;
+
+ if (rc && ok)
+ *rc = 0;
+
+ return &ts->lchan[lch_idx];
+}
+
+static const uint8_t subslots_per_pchan[] = {
+ [GSM_PCHAN_NONE] = 0,
+ [GSM_PCHAN_CCCH] = 0,
+ [GSM_PCHAN_PDCH] = 0,
+ [GSM_PCHAN_CCCH_SDCCH4] = 4,
+ [GSM_PCHAN_TCH_F] = 1,
+ [GSM_PCHAN_TCH_H] = 2,
+ [GSM_PCHAN_SDCCH8_SACCH8C] = 8,
+ [GSM_PCHAN_CCCH_SDCCH4_CBCH] = 4,
+ [GSM_PCHAN_SDCCH8_SACCH8C_CBCH] = 8,
+ /*
+ * GSM_PCHAN_TCH_F_PDCH and GSM_PCHAN_TCH_F_TCH_H_PDCH should not be
+ * part of this, those TS are handled according to their dynamic state.
+ */
+};
+
+/*! Return the actual pchan type, also heeding dynamic TS. */
+enum gsm_phys_chan_config ts_pchan(struct gsm_bts_trx_ts *ts)
+{
+ switch (ts->pchan) {
+ case GSM_PCHAN_TCH_F_TCH_H_PDCH:
+ return ts->dyn.pchan_is;
+ case GSM_PCHAN_TCH_F_PDCH:
+ if (ts->flags & TS_F_PDCH_ACTIVE)
+ return GSM_PCHAN_PDCH;
+ else
+ return GSM_PCHAN_TCH_F;
+ default:
+ return ts->pchan;
+ }
+}
+
+/*! According to ts->pchan and possibly ts->dyn_pchan, return the number of
+ * logical channels available in the timeslot. */
+uint8_t ts_subslots(struct gsm_bts_trx_ts *ts)
+{
+ return subslots_per_pchan[ts_pchan(ts)];
+}
+
+static bool pchan_is_tch(enum gsm_phys_chan_config pchan)
+{
+ switch (pchan) {
+ case GSM_PCHAN_TCH_F:
+ case GSM_PCHAN_TCH_H:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool ts_is_tch(struct gsm_bts_trx_ts *ts)
+{
+ return pchan_is_tch(ts_pchan(ts));
+}
diff --git a/src/libcommon/gsm_subscriber_base.c b/src/libcommon/gsm_subscriber_base.c
new file mode 100644
index 000000000..1ecdee5a5
--- /dev/null
+++ b/src/libcommon/gsm_subscriber_base.c
@@ -0,0 +1,163 @@
+/* The concept of a subscriber as seen by the BSC */
+
+/* (C) 2008 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2009-2010 by Holger Hans Peter Freyther <zecke@selfish.org>
+ * (C) 2010 by On-Waves
+ *
+ * All Rights Reserved
+ *
+ * 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 <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+#include <openbsc/gsm_subscriber.h>
+#include <openbsc/debug.h>
+
+LLIST_HEAD(active_subscribers);
+void *tall_subscr_ctx;
+
+/* for the gsm_subscriber.c */
+struct llist_head *subscr_bsc_active_subscribers(void)
+{
+ return &active_subscribers;
+}
+
+
+char *subscr_name(struct gsm_subscriber *subscr)
+{
+ if (!subscr)
+ return "unknown";
+
+ if (strlen(subscr->name))
+ return subscr->name;
+
+ return subscr->imsi;
+}
+
+struct gsm_subscriber *subscr_alloc(void)
+{
+ struct gsm_subscriber *s;
+
+ s = talloc_zero(tall_subscr_ctx, struct gsm_subscriber);
+ if (!s)
+ return NULL;
+
+ llist_add_tail(&s->entry, &active_subscribers);
+ s->use_count = 1;
+ s->tmsi = GSM_RESERVED_TMSI;
+
+ INIT_LLIST_HEAD(&s->requests);
+
+ return s;
+}
+
+static void subscr_free(struct gsm_subscriber *subscr)
+{
+ llist_del(&subscr->entry);
+ talloc_free(subscr);
+}
+
+void subscr_direct_free(struct gsm_subscriber *subscr)
+{
+ OSMO_ASSERT(subscr->use_count == 1);
+ subscr_free(subscr);
+}
+
+struct gsm_subscriber *subscr_get(struct gsm_subscriber *subscr)
+{
+ subscr->use_count++;
+ DEBUGP(DREF, "subscr %s usage increases usage to: %d\n",
+ subscr->extension, subscr->use_count);
+ return subscr;
+}
+
+struct gsm_subscriber *subscr_put(struct gsm_subscriber *subscr)
+{
+ subscr->use_count--;
+ DEBUGP(DREF, "subscr %s usage decreased usage to: %d\n",
+ subscr->extension, subscr->use_count);
+ if (subscr->use_count <= 0 &&
+ !((subscr->group && subscr->group->keep_subscr) ||
+ subscr->keep_in_ram))
+ subscr_free(subscr);
+ return NULL;
+}
+
+struct gsm_subscriber *subscr_get_or_create(struct gsm_subscriber_group *sgrp,
+ const char *imsi)
+{
+ struct gsm_subscriber *subscr;
+
+ llist_for_each_entry(subscr, subscr_bsc_active_subscribers(), entry) {
+ if (strcmp(subscr->imsi, imsi) == 0 && subscr->group == sgrp)
+ return subscr_get(subscr);
+ }
+
+ subscr = subscr_alloc();
+ if (!subscr)
+ return NULL;
+
+ osmo_strlcpy(subscr->imsi, imsi, sizeof(subscr->imsi));
+ subscr->group = sgrp;
+ return subscr;
+}
+
+struct gsm_subscriber *subscr_active_by_tmsi(struct gsm_subscriber_group *sgrp,
+ uint32_t tmsi)
+{
+ struct gsm_subscriber *subscr;
+
+ llist_for_each_entry(subscr, subscr_bsc_active_subscribers(), entry) {
+ if (subscr->tmsi == tmsi && subscr->group == sgrp)
+ return subscr_get(subscr);
+ }
+
+ return NULL;
+}
+
+struct gsm_subscriber *subscr_active_by_imsi(struct gsm_subscriber_group *sgrp,
+ const char *imsi)
+{
+ struct gsm_subscriber *subscr;
+
+ llist_for_each_entry(subscr, subscr_bsc_active_subscribers(), entry) {
+ if (strcmp(subscr->imsi, imsi) == 0 && subscr->group == sgrp)
+ return subscr_get(subscr);
+ }
+
+ return NULL;
+}
+
+int subscr_purge_inactive(struct gsm_subscriber_group *sgrp)
+{
+ struct gsm_subscriber *subscr, *tmp;
+ int purged = 0;
+
+ llist_for_each_entry_safe(subscr, tmp, subscr_bsc_active_subscribers(), entry) {
+ if (subscr->group == sgrp && subscr->use_count <= 0) {
+ subscr_free(subscr);
+ purged += 1;
+ }
+ }
+
+ return purged;
+}
diff --git a/src/libcommon/gsup_client.c b/src/libcommon/gsup_client.c
new file mode 100644
index 000000000..de00d8d4f
--- /dev/null
+++ b/src/libcommon/gsup_client.c
@@ -0,0 +1,341 @@
+/* Generic Subscriber Update Protocol client */
+
+/* (C) 2014-2016 by Sysmocom s.f.m.c. GmbH
+ * All Rights Reserved
+ *
+ * Author: Jacob Erlbeck
+ * Author: Neels Hofmeyr
+ *
+ * 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 <openbsc/gsup_client.h>
+
+#include <osmocom/abis/ipa.h>
+#include <osmocom/gsm/protocol/ipaccess.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/logging.h>
+
+#include <openbsc/debug.h>
+
+#include <errno.h>
+#include <string.h>
+
+extern void *tall_bsc_ctx;
+
+static void start_test_procedure(struct gsup_client *gsupc);
+
+static void gsup_client_send_ping(struct gsup_client *gsupc)
+{
+ struct msgb *msg = gsup_client_msgb_alloc();
+
+ msg->l2h = msgb_put(msg, 1);
+ msg->l2h[0] = IPAC_MSGT_PING;
+ ipa_msg_push_header(msg, IPAC_PROTO_IPACCESS);
+ ipa_client_conn_send(gsupc->link, msg);
+}
+
+static int gsup_client_connect(struct gsup_client *gsupc)
+{
+ int rc;
+
+ if (gsupc->is_connected)
+ return 0;
+
+ if (osmo_timer_pending(&gsupc->connect_timer)) {
+ LOGP(DLGSUP, LOGL_DEBUG,
+ "GSUP connect: connect timer already running\n");
+ osmo_timer_del(&gsupc->connect_timer);
+ }
+
+ if (osmo_timer_pending(&gsupc->ping_timer)) {
+ LOGP(DLGSUP, LOGL_DEBUG,
+ "GSUP connect: ping timer already running\n");
+ osmo_timer_del(&gsupc->ping_timer);
+ }
+
+ if (ipa_client_conn_clear_queue(gsupc->link) > 0)
+ LOGP(DLGSUP, LOGL_DEBUG, "GSUP connect: discarded stored messages\n");
+
+ rc = ipa_client_conn_open(gsupc->link);
+
+ if (rc >= 0) {
+ LOGP(DLGSUP, LOGL_INFO, "GSUP connecting to %s:%d\n",
+ gsupc->link->addr, gsupc->link->port);
+ return 0;
+ }
+
+ LOGP(DLGSUP, LOGL_INFO, "GSUP failed to connect to %s:%d: %s\n",
+ gsupc->link->addr, gsupc->link->port, strerror(-rc));
+
+ if (rc == -EBADF || rc == -ENOTSOCK || rc == -EAFNOSUPPORT ||
+ rc == -EINVAL)
+ return rc;
+
+ osmo_timer_schedule(&gsupc->connect_timer,
+ GSUP_CLIENT_RECONNECT_INTERVAL, 0);
+
+ LOGP(DLGSUP, LOGL_INFO, "Scheduled timer to retry GSUP connect to %s:%d\n",
+ gsupc->link->addr, gsupc->link->port);
+
+ return 0;
+}
+
+static void connect_timer_cb(void *gsupc_)
+{
+ struct gsup_client *gsupc = gsupc_;
+
+ if (gsupc->is_connected)
+ return;
+
+ gsup_client_connect(gsupc);
+}
+
+static void client_send(struct gsup_client *gsupc, int proto_ext,
+ struct msgb *msg_tx)
+{
+ ipa_prepend_header_ext(msg_tx, proto_ext);
+ ipa_msg_push_header(msg_tx, IPAC_PROTO_OSMO);
+ ipa_client_conn_send(gsupc->link, msg_tx);
+ /* msg_tx is now queued and will be freed. */
+}
+
+static void gsup_client_oap_register(struct gsup_client *gsupc)
+{
+ struct msgb *msg_tx;
+ int rc;
+ rc = oap_client_register(&gsupc->oap_state, &msg_tx);
+
+ if ((rc < 0) || (!msg_tx)) {
+ LOGP(DLGSUP, LOGL_ERROR, "GSUP OAP set up, but cannot register.\n");
+ return;
+ }
+
+ client_send(gsupc, IPAC_PROTO_EXT_OAP, msg_tx);
+}
+
+static void gsup_client_updown_cb(struct ipa_client_conn *link, int up)
+{
+ struct gsup_client *gsupc = link->data;
+
+ LOGP(DLGSUP, LOGL_INFO, "GSUP link to %s:%d %s\n",
+ link->addr, link->port, up ? "UP" : "DOWN");
+
+ gsupc->is_connected = up;
+
+ if (up) {
+ start_test_procedure(gsupc);
+
+ if (gsupc->oap_state.state == OAP_INITIALIZED)
+ gsup_client_oap_register(gsupc);
+
+ osmo_timer_del(&gsupc->connect_timer);
+ } else {
+ osmo_timer_del(&gsupc->ping_timer);
+
+ osmo_timer_schedule(&gsupc->connect_timer,
+ GSUP_CLIENT_RECONNECT_INTERVAL, 0);
+ }
+}
+
+static int gsup_client_oap_handle(struct gsup_client *gsupc, struct msgb *msg_rx)
+{
+ int rc;
+ struct msgb *msg_tx;
+
+ /* If the oap_state is disabled, this will reject the messages. */
+ rc = oap_client_handle(&gsupc->oap_state, msg_rx, &msg_tx);
+ msgb_free(msg_rx);
+ if (rc < 0)
+ return rc;
+
+ if (msg_tx)
+ client_send(gsupc, IPAC_PROTO_EXT_OAP, msg_tx);
+
+ return 0;
+}
+
+static int gsup_client_read_cb(struct ipa_client_conn *link, struct msgb *msg)
+{
+ struct ipaccess_head *hh = (struct ipaccess_head *) msg->data;
+ struct ipaccess_head_ext *he = (struct ipaccess_head_ext *) msgb_l2(msg);
+ struct gsup_client *gsupc = (struct gsup_client *)link->data;
+ int rc;
+ static struct ipaccess_unit ipa_dev = {
+ .unit_name = "SGSN"
+ };
+
+ msg->l2h = &hh->data[0];
+
+ rc = ipaccess_bts_handle_ccm(link, &ipa_dev, msg);
+
+ if (rc < 0) {
+ LOGP(DLGSUP, LOGL_NOTICE,
+ "GSUP received an invalid IPA/CCM message from %s:%d\n",
+ link->addr, link->port);
+ /* Link has been closed */
+ gsupc->is_connected = 0;
+ msgb_free(msg);
+ return -1;
+ }
+
+ if (rc == 1) {
+ uint8_t msg_type = *(msg->l2h);
+ /* CCM message */
+ if (msg_type == IPAC_MSGT_PONG) {
+ LOGP(DLGSUP, LOGL_DEBUG, "GSUP receiving PONG\n");
+ gsupc->got_ipa_pong = 1;
+ }
+
+ msgb_free(msg);
+ return 0;
+ }
+
+ if (hh->proto != IPAC_PROTO_OSMO)
+ goto invalid;
+
+ if (!he || msgb_l2len(msg) < sizeof(*he))
+ goto invalid;
+
+ msg->l2h = &he->data[0];
+
+ if (he->proto == IPAC_PROTO_EXT_GSUP) {
+ OSMO_ASSERT(gsupc->read_cb != NULL);
+ gsupc->read_cb(gsupc, msg);
+ /* expecting read_cb() to free msg */
+ } else if (he->proto == IPAC_PROTO_EXT_OAP) {
+ return gsup_client_oap_handle(gsupc, msg);
+ /* gsup_client_oap_handle frees msg */
+ } else
+ goto invalid;
+
+ return 0;
+
+invalid:
+ LOGP(DLGSUP, LOGL_NOTICE,
+ "GSUP received an invalid IPA message from %s:%d, size = %d\n",
+ link->addr, link->port, msgb_length(msg));
+
+ msgb_free(msg);
+ return -1;
+}
+
+static void ping_timer_cb(void *gsupc_)
+{
+ struct gsup_client *gsupc = gsupc_;
+
+ LOGP(DLGSUP, LOGL_INFO, "GSUP ping callback (%s, %s PONG)\n",
+ gsupc->is_connected ? "connected" : "not connected",
+ gsupc->got_ipa_pong ? "got" : "didn't get");
+
+ if (gsupc->got_ipa_pong) {
+ start_test_procedure(gsupc);
+ return;
+ }
+
+ LOGP(DLGSUP, LOGL_NOTICE, "GSUP ping timed out, reconnecting\n");
+ ipa_client_conn_close(gsupc->link);
+ gsupc->is_connected = 0;
+
+ gsup_client_connect(gsupc);
+}
+
+static void start_test_procedure(struct gsup_client *gsupc)
+{
+ osmo_timer_setup(&gsupc->ping_timer, ping_timer_cb, gsupc);
+
+ gsupc->got_ipa_pong = 0;
+ osmo_timer_schedule(&gsupc->ping_timer, GSUP_CLIENT_PING_INTERVAL, 0);
+ LOGP(DLGSUP, LOGL_DEBUG, "GSUP sending PING\n");
+ gsup_client_send_ping(gsupc);
+}
+
+struct gsup_client *gsup_client_create(const char *ip_addr,
+ unsigned int tcp_port,
+ gsup_client_read_cb_t read_cb,
+ struct oap_client_config *oapc_config)
+{
+ struct gsup_client *gsupc;
+ int rc;
+
+ gsupc = talloc_zero(tall_bsc_ctx, struct gsup_client);
+ OSMO_ASSERT(gsupc);
+
+ /* a NULL oapc_config will mark oap_state disabled. */
+ rc = oap_client_init(oapc_config, &gsupc->oap_state);
+ if (rc != 0)
+ goto failed;
+
+ gsupc->link = ipa_client_conn_create(gsupc,
+ /* no e1inp */ NULL,
+ 0,
+ ip_addr, tcp_port,
+ gsup_client_updown_cb,
+ gsup_client_read_cb,
+ /* default write_cb */ NULL,
+ gsupc);
+ if (!gsupc->link)
+ goto failed;
+
+ osmo_timer_setup(&gsupc->connect_timer, connect_timer_cb, gsupc);
+
+ rc = gsup_client_connect(gsupc);
+
+ if (rc < 0)
+ goto failed;
+
+ gsupc->read_cb = read_cb;
+
+ return gsupc;
+
+failed:
+ gsup_client_destroy(gsupc);
+ return NULL;
+}
+
+void gsup_client_destroy(struct gsup_client *gsupc)
+{
+ osmo_timer_del(&gsupc->connect_timer);
+ osmo_timer_del(&gsupc->ping_timer);
+
+ if (gsupc->link) {
+ ipa_client_conn_close(gsupc->link);
+ ipa_client_conn_destroy(gsupc->link);
+ gsupc->link = NULL;
+ }
+ talloc_free(gsupc);
+}
+
+int gsup_client_send(struct gsup_client *gsupc, struct msgb *msg)
+{
+ if (!gsupc) {
+ msgb_free(msg);
+ return -ENOTCONN;
+ }
+
+ if (!gsupc->is_connected) {
+ msgb_free(msg);
+ return -EAGAIN;
+ }
+
+ client_send(gsupc, IPAC_PROTO_EXT_GSUP, msg);
+
+ return 0;
+}
+
+struct msgb *gsup_client_msgb_alloc(void)
+{
+ return msgb_alloc_headroom(4000, 64, __func__);
+}
diff --git a/src/libcommon/gsup_test_client.c b/src/libcommon/gsup_test_client.c
new file mode 100644
index 000000000..8fc38d60d
--- /dev/null
+++ b/src/libcommon/gsup_test_client.c
@@ -0,0 +1,298 @@
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <signal.h>
+
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/application.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/gsm/gsup.h>
+
+#include <openbsc/gsup_client.h>
+#include <openbsc/debug.h>
+
+static struct gsup_client *g_gc;
+
+
+/***********************************************************************
+ * IMSI Operation
+ ***********************************************************************/
+static LLIST_HEAD(g_imsi_ops);
+
+struct imsi_op_stats {
+ uint32_t num_alloc;
+ uint32_t num_released;
+ uint32_t num_rx_success;
+ uint32_t num_rx_error;
+ uint32_t num_timeout;
+};
+
+enum imsi_op_type {
+ IMSI_OP_SAI,
+ IMSI_OP_LU,
+ IMSI_OP_ISD,
+ _NUM_IMSI_OP
+};
+
+static const struct value_string imsi_op_names[] = {
+ { IMSI_OP_SAI, "SAI" },
+ { IMSI_OP_LU, "LU" },
+ { IMSI_OP_ISD, "ISD" },
+ { 0, NULL }
+};
+
+static struct imsi_op_stats imsi_op_stats[_NUM_IMSI_OP];
+
+struct imsi_op {
+ struct llist_head list;
+ char imsi[17];
+ enum imsi_op_type type;
+ struct osmo_timer_list timer;
+};
+
+static struct imsi_op *imsi_op_find(const char *imsi,
+ enum imsi_op_type type)
+{
+ struct imsi_op *io;
+
+ llist_for_each_entry(io, &g_imsi_ops, list) {
+ if (!strcmp(io->imsi, imsi) && io->type == type)
+ return io;
+ }
+ return NULL;
+}
+
+static void imsi_op_timer_cb(void *data);
+
+static struct imsi_op *imsi_op_alloc(void *ctx, const char *imsi,
+ enum imsi_op_type type)
+{
+ struct imsi_op *io;
+
+ if (imsi_op_find(imsi, type))
+ return NULL;
+
+ io = talloc_zero(ctx, struct imsi_op);
+ osmo_strlcpy(io->imsi, imsi, sizeof(io->imsi));
+ io->type = type;
+ osmo_timer_setup(&io->timer, imsi_op_timer_cb, io);
+ llist_add(&io->list, &g_imsi_ops);
+ imsi_op_stats[type].num_alloc++;
+
+ return io;
+}
+
+static void imsi_op_release(struct imsi_op *io)
+{
+ osmo_timer_del(&io->timer);
+ llist_del(&io->list);
+ imsi_op_stats[io->type].num_released++;
+ talloc_free(io);
+}
+
+static void imsi_op_timer_cb(void *data)
+{
+ struct imsi_op *io = data;
+ printf("%s: Timer expiration\n", io->imsi);
+ imsi_op_stats[io->type].num_timeout++;
+ imsi_op_release(io);
+}
+
+/* allocate + generate + send Send-Auth-Info */
+int req_auth_info(const char *imsi)
+{
+ struct imsi_op *io = imsi_op_alloc(g_gc, imsi, IMSI_OP_SAI);
+ struct osmo_gsup_message gsup = {0};
+ struct msgb *msg = msgb_alloc_headroom(1200, 200, __func__);
+
+ osmo_strlcpy(gsup.imsi, io->imsi, sizeof(gsup.imsi));
+ gsup.message_type = OSMO_GSUP_MSGT_SEND_AUTH_INFO_REQUEST;
+
+ osmo_gsup_encode(msg, &gsup);
+
+ return gsup_client_send(g_gc, msg);
+}
+
+/* allocate + generate + send Send-Auth-Info */
+int req_loc_upd(const char *imsi)
+{
+ struct imsi_op *io = imsi_op_alloc(g_gc, imsi, IMSI_OP_LU);
+ struct osmo_gsup_message gsup = {0};
+ struct msgb *msg = msgb_alloc_headroom(1200, 200, __func__);
+
+ osmo_strlcpy(gsup.imsi, io->imsi, sizeof(gsup.imsi));
+ gsup.message_type = OSMO_GSUP_MSGT_UPDATE_LOCATION_REQUEST;
+
+ osmo_gsup_encode(msg, &gsup);
+
+ return gsup_client_send(g_gc, msg);
+}
+
+int resp_isd(struct imsi_op *io)
+{
+ struct osmo_gsup_message gsup = {0};
+ struct msgb *msg = msgb_alloc_headroom(1200, 200, __func__);
+
+ osmo_strlcpy(gsup.imsi, io->imsi, sizeof(gsup.imsi));
+ gsup.message_type = OSMO_GSUP_MSGT_INSERT_DATA_RESULT;
+
+ osmo_gsup_encode(msg, &gsup);
+
+ imsi_op_release(io);
+
+ return gsup_client_send(g_gc, msg);
+}
+
+/* receive an incoming GSUP message */
+static void imsi_op_rx_gsup(struct imsi_op *io, const struct osmo_gsup_message *gsup)
+{
+ int is_error = 0;
+
+ if (OSMO_GSUP_IS_MSGT_ERROR(gsup->message_type)) {
+ imsi_op_stats[io->type].num_rx_error++;
+ is_error = 1;
+ } else
+ imsi_op_stats[io->type].num_rx_success++;
+
+ switch (io->type) {
+ case IMSI_OP_SAI:
+ printf("%s; SAI Response%s\n", io->imsi, is_error ? ": ERROR" : "");
+ /* now that we have auth tuples, request LU */
+ req_loc_upd(io->imsi);
+ imsi_op_release(io);
+ break;
+ case IMSI_OP_LU:
+ printf("%s; LU Response%s\n", io->imsi, is_error ? ": ERROR" : "");
+ imsi_op_release(io);
+ break;
+ case IMSI_OP_ISD:
+ printf("%s; ISD Request%s\n", io->imsi, is_error ? ": ERROR" : "");
+ resp_isd(io);
+ break;
+ default:
+ printf("%s: Unknown\n", io->imsi);
+ imsi_op_release(io);
+ break;
+ }
+}
+
+static int op_type_by_gsup_msgt(enum osmo_gsup_message_type msg_type)
+{
+ switch (msg_type) {
+ case OSMO_GSUP_MSGT_SEND_AUTH_INFO_RESULT:
+ case OSMO_GSUP_MSGT_SEND_AUTH_INFO_ERROR:
+ return IMSI_OP_SAI;
+ case OSMO_GSUP_MSGT_UPDATE_LOCATION_RESULT:
+ case OSMO_GSUP_MSGT_UPDATE_LOCATION_ERROR:
+ return IMSI_OP_LU;
+ case OSMO_GSUP_MSGT_INSERT_DATA_REQUEST:
+ return IMSI_OP_ISD;
+ default:
+ printf("Unknown GSUP msg_type %u\n", msg_type);
+ return -1;
+ }
+}
+
+static int gsupc_read_cb(struct gsup_client *gsupc, struct msgb *msg)
+{
+ struct osmo_gsup_message gsup_msg = {0};
+ struct imsi_op *io;
+ int rc;
+
+ DEBUGP(DGPRS, "Rx GSUP %s\n", osmo_hexdump(msgb_l2(msg), msgb_l2len(msg)));
+
+ rc = osmo_gsup_decode(msgb_l2(msg), msgb_l2len(msg), &gsup_msg);
+ if (rc < 0)
+ return rc;
+
+ if (!gsup_msg.imsi[0])
+ return -1;
+
+ rc = op_type_by_gsup_msgt(gsup_msg.message_type);
+ if (rc < 0)
+ return rc;
+
+ switch (rc) {
+ case IMSI_OP_SAI:
+ case IMSI_OP_LU:
+ io = imsi_op_find(gsup_msg.imsi, rc);
+ if (!io)
+ return -1;
+ break;
+ case IMSI_OP_ISD:
+ /* ISD is an inbound transaction */
+ io = imsi_op_alloc(g_gc, gsup_msg.imsi, IMSI_OP_ISD);
+ break;
+ }
+
+ imsi_op_rx_gsup(io, &gsup_msg);
+ msgb_free(msg);
+
+ return 0;
+}
+
+static void print_report(void)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(imsi_op_stats); i++) {
+ struct imsi_op_stats *st = &imsi_op_stats[i];
+ const char *name = get_value_string(imsi_op_names, i);
+ printf("%s: %u alloc, %u released, %u success, %u error , %u tout\n",
+ name, st->num_alloc, st->num_released, st->num_rx_success,
+ st->num_rx_error, st->num_timeout);
+ }
+}
+
+static void sig_cb(int sig)
+{
+ switch (sig) {
+ case SIGINT:
+ print_report();
+ exit(0);
+ break;
+ }
+}
+
+void *tall_bsc_ctx = NULL;
+
+/* default categories */
+static struct log_info_cat default_categories[] = {
+};
+
+static const struct log_info gsup_test_client_log_info = {
+ .cat = default_categories,
+ .num_cat = ARRAY_SIZE(default_categories),
+};
+
+int main(int argc, char **argv)
+{
+ unsigned long long i;
+ char *server_host = "127.0.0.1";
+ uint16_t server_port = 2222;
+
+ osmo_init_logging(&gsup_test_client_log_info);
+
+ g_gc = gsup_client_create(server_host, server_port, gsupc_read_cb,
+ NULL);
+
+
+ signal(SIGINT, sig_cb);
+
+ for (i = 0; i < 10000; i++) {
+ unsigned long long imsi = 901790000000000 + i;
+ char imsi_buf[17];
+ snprintf(imsi_buf, sizeof(imsi_buf), "%015llu", imsi);
+ req_auth_info(imsi_buf);
+ osmo_select_main(0);
+ }
+
+ while (1) {
+ osmo_select_main(0);
+ }
+
+ print_report();
+ exit(0);
+}
diff --git a/src/libcommon/oap_client.c b/src/libcommon/oap_client.c
new file mode 100644
index 000000000..5128ac119
--- /dev/null
+++ b/src/libcommon/oap_client.c
@@ -0,0 +1,280 @@
+/* Osmocom Authentication Protocol API */
+
+/* (C) 2015 by Sysmocom s.f.m.c. GmbH
+ * All Rights Reserved
+ *
+ * Author: Neels Hofmeyr
+ *
+ * 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 <string.h>
+#include <errno.h>
+
+#include <osmocom/core/utils.h>
+#include <osmocom/crypt/auth.h>
+#include <osmocom/gsm/oap.h>
+
+#include <openbsc/oap_client.h>
+#include <openbsc/debug.h>
+
+int oap_client_init(struct oap_client_config *config,
+ struct oap_client_state *state)
+{
+ OSMO_ASSERT(state->state == OAP_UNINITIALIZED);
+
+ if (!config)
+ goto disable;
+
+ if (config->client_id == 0)
+ goto disable;
+
+ if (config->secret_k_present == 0) {
+ LOGP(DLOAP, LOGL_NOTICE, "OAP: client ID set, but secret K missing.\n");
+ goto disable;
+ }
+
+ if (config->secret_opc_present == 0) {
+ LOGP(DLOAP, LOGL_NOTICE, "OAP: client ID set, but secret OPC missing.\n");
+ goto disable;
+ }
+
+ state->client_id = config->client_id;
+ memcpy(state->secret_k, config->secret_k, sizeof(state->secret_k));
+ memcpy(state->secret_opc, config->secret_opc, sizeof(state->secret_opc));
+ state->state = OAP_INITIALIZED;
+ return 0;
+
+disable:
+ state->state = OAP_DISABLED;
+ return 0;
+}
+
+/* From the given state and received RAND and AUTN octets, validate the
+ * server's authenticity and formulate the matching milenage reply octets in
+ * *tx_xres. The state is not modified.
+ * On success, and if tx_res is not NULL, exactly 8 octets will be written to
+ * *tx_res. If not NULL, tx_res must point at allocated memory of at least 8
+ * octets. The caller will want to send XRES back to the server in a challenge
+ * response message and update the state.
+ * Return 0 on success; -1 if OAP is disabled; -2 if rx_random and rx_autn fail
+ * the authentication check; -3 for any other errors. */
+static int oap_evaluate_challenge(const struct oap_client_state *state,
+ const uint8_t *rx_random,
+ const uint8_t *rx_autn,
+ uint8_t *tx_xres)
+{
+ struct osmo_auth_vector vec;
+
+ struct osmo_sub_auth_data auth = {
+ .type = OSMO_AUTH_TYPE_UMTS,
+ .algo = OSMO_AUTH_ALG_MILENAGE,
+ };
+
+ osmo_static_assert(sizeof(((struct osmo_sub_auth_data*)0)->u.umts.k)
+ == sizeof(state->secret_k), _secret_k_size_match);
+ osmo_static_assert(sizeof(((struct osmo_sub_auth_data*)0)->u.umts.opc)
+ == sizeof(state->secret_opc), _secret_opc_size_match);
+
+ switch (state->state) {
+ case OAP_UNINITIALIZED:
+ case OAP_DISABLED:
+ return -1;
+ default:
+ break;
+ }
+
+ memcpy(auth.u.umts.k, state->secret_k, sizeof(auth.u.umts.k));
+ memcpy(auth.u.umts.opc, state->secret_opc, sizeof(auth.u.umts.opc));
+ memset(auth.u.umts.amf, '\0', sizeof(auth.u.umts.amf));
+ auth.u.umts.sqn = 41; /* TODO use incrementing sequence nr */
+
+ memset(&vec, 0, sizeof(vec));
+ osmo_auth_gen_vec(&vec, &auth, rx_random);
+
+ if (vec.res_len != 8) {
+ LOGP(DLOAP, LOGL_ERROR, "OAP: Expected XRES to be 8 octets, got %d\n",
+ vec.res_len);
+ return -3;
+ }
+
+ if (osmo_constant_time_cmp(vec.autn, rx_autn, sizeof(vec.autn)) != 0) {
+ LOGP(DLOAP, LOGL_ERROR, "OAP: AUTN mismatch!\n");
+ LOGP(DLOAP, LOGL_INFO, "OAP: AUTN from server: %s\n",
+ osmo_hexdump_nospc(rx_autn, sizeof(vec.autn)));
+ LOGP(DLOAP, LOGL_INFO, "OAP: AUTN expected: %s\n",
+ osmo_hexdump_nospc(vec.autn, sizeof(vec.autn)));
+ return -2;
+ }
+
+ if (tx_xres != NULL)
+ memcpy(tx_xres, vec.res, 8);
+ return 0;
+}
+
+struct msgb *oap_client_encoded(const struct osmo_oap_message *oap_msg)
+{
+ struct msgb *msg = msgb_alloc_headroom(1000, 64, __func__);
+ OSMO_ASSERT(msg);
+ osmo_oap_encode(msg, oap_msg);
+ return msg;
+}
+
+/* Create a new msgb containing an OAP registration message.
+ * On error, return NULL. */
+static struct msgb* oap_msg_register(uint16_t client_id)
+{
+ struct osmo_oap_message oap_msg = {0};
+
+ if (client_id < 1) {
+ LOGP(DLOAP, LOGL_ERROR, "OAP: Invalid client ID: %d\n", client_id);
+ return NULL;
+ }
+
+ oap_msg.message_type = OAP_MSGT_REGISTER_REQUEST;
+ oap_msg.client_id = client_id;
+ return oap_client_encoded(&oap_msg);
+}
+
+int oap_client_register(struct oap_client_state *state, struct msgb **msg_tx)
+{
+ *msg_tx = oap_msg_register(state->client_id);
+ if (!(*msg_tx))
+ return -1;
+
+ state->state = OAP_REQUESTED_CHALLENGE;
+ return 0;
+}
+
+/* Create a new msgb containing an OAP challenge response message.
+ * xres must point at 8 octets to return as challenge response.
+ * On error, return NULL. */
+static struct msgb* oap_msg_challenge_response(uint8_t *xres)
+{
+ struct osmo_oap_message oap_reply = {0};
+
+ oap_reply.message_type = OAP_MSGT_CHALLENGE_RESULT;
+ memcpy(oap_reply.xres, xres, sizeof(oap_reply.xres));
+ oap_reply.xres_present = 1;
+ return oap_client_encoded(&oap_reply);
+}
+
+static int handle_challenge(struct oap_client_state *state,
+ struct osmo_oap_message *oap_rx,
+ struct msgb **msg_tx)
+{
+ int rc;
+ uint8_t xres[8];
+
+ if (!(oap_rx->rand_present && oap_rx->autn_present)) {
+ LOGP(DLOAP, LOGL_ERROR,
+ "OAP challenge incomplete (rand_present: %d, autn_present: %d)\n",
+ oap_rx->rand_present, oap_rx->autn_present);
+ rc = -2;
+ goto failure;
+ }
+
+ rc = oap_evaluate_challenge(state,
+ oap_rx->rand,
+ oap_rx->autn,
+ xres);
+ if (rc < 0)
+ goto failure;
+
+ *msg_tx = oap_msg_challenge_response(xres);
+ if ((*msg_tx) == NULL) {
+ rc = -1;
+ goto failure;
+ }
+
+ state->state = OAP_SENT_CHALLENGE_RESULT;
+ return 0;
+
+failure:
+ OSMO_ASSERT(rc < 0);
+ state->state = OAP_INITIALIZED;
+ return rc;
+}
+
+int oap_client_handle(struct oap_client_state *state,
+ const struct msgb *msg_rx, struct msgb **msg_tx)
+{
+ uint8_t *data = msgb_l2(msg_rx);
+ size_t data_len = msgb_l2len(msg_rx);
+ struct osmo_oap_message oap_msg = {0};
+ int rc = 0;
+
+ *msg_tx = NULL;
+
+ OSMO_ASSERT(data);
+
+ rc = osmo_oap_decode(&oap_msg, data, data_len);
+ if (rc < 0) {
+ LOGP(DLOAP, LOGL_ERROR,
+ "Decoding OAP message failed with error '%s' (%d)\n",
+ get_value_string(gsm48_gmm_cause_names, -rc), -rc);
+ return -10;
+ }
+
+ switch (state->state) {
+ case OAP_UNINITIALIZED:
+ LOGP(DLOAP, LOGL_ERROR,
+ "Received OAP message %d, but the OAP client is"
+ " not initialized\n", oap_msg.message_type);
+ return -ENOTCONN;
+ case OAP_DISABLED:
+ LOGP(DLOAP, LOGL_ERROR,
+ "Received OAP message %d, but the OAP client is"
+ " disabled\n", oap_msg.message_type);
+ return -ENOTCONN;
+ default:
+ break;
+ }
+
+ switch (oap_msg.message_type) {
+ case OAP_MSGT_CHALLENGE_REQUEST:
+ return handle_challenge(state, &oap_msg, msg_tx);
+
+ case OAP_MSGT_REGISTER_RESULT:
+ /* successfully registered */
+ state->state = OAP_REGISTERED;
+ break;
+
+ case OAP_MSGT_REGISTER_ERROR:
+ LOGP(DLOAP, LOGL_ERROR,
+ "OAP registration failed\n");
+ state->state = OAP_INITIALIZED;
+ if (state->registration_failures < 3) {
+ state->registration_failures ++;
+ return oap_client_register(state, msg_tx);
+ }
+ return -11;
+
+ case OAP_MSGT_REGISTER_REQUEST:
+ case OAP_MSGT_CHALLENGE_RESULT:
+ LOGP(DLOAP, LOGL_ERROR,
+ "Received invalid OAP message type for OAP client side: %d\n",
+ (int)oap_msg.message_type);
+ return -12;
+
+ default:
+ LOGP(DLOAP, LOGL_ERROR,
+ "Unknown OAP message type: %d\n",
+ (int)oap_msg.message_type);
+ return -13;
+ }
+
+ return 0;
+}
diff --git a/src/libcommon/socket.c b/src/libcommon/socket.c
new file mode 100644
index 000000000..2a64767f8
--- /dev/null
+++ b/src/libcommon/socket.c
@@ -0,0 +1,111 @@
+/* OpenBSC sokcet code, taken from Abis input driver for ip.access */
+
+/* (C) 2009 by Harald Welte <laforge@gnumonks.org>
+ * (C) 2010 by Holger Hans Peter Freyther
+ * (C) 2010 by On-Waves
+ *
+ * All Rights Reserved
+ *
+ * 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 <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <time.h>
+#include <sys/fcntl.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <arpa/inet.h>
+
+#include <osmocom/core/select.h>
+#include <osmocom/gsm/tlv.h>
+#include <osmocom/core/msgb.h>
+#include <openbsc/debug.h>
+#include <openbsc/gsm_data.h>
+#include <osmocom/core/talloc.h>
+
+int make_sock(struct osmo_fd *bfd, int proto,
+ uint32_t ip, uint16_t port, int priv_nr,
+ int (*cb)(struct osmo_fd *fd, unsigned int what), void *data)
+{
+ struct sockaddr_in addr;
+ int ret, on = 1;
+ int type = SOCK_STREAM;
+
+ switch (proto) {
+ case IPPROTO_TCP:
+ type = SOCK_STREAM;
+ break;
+ case IPPROTO_UDP:
+ type = SOCK_DGRAM;
+ break;
+#ifdef IPPROTO_GRE
+ case IPPROTO_GRE:
+ type = SOCK_RAW;
+ break;
+#endif
+ default:
+ return -EINVAL;
+ }
+
+ bfd->fd = socket(AF_INET, type, proto);
+ bfd->cb = cb;
+ bfd->when = BSC_FD_READ;
+ bfd->data = data;
+ bfd->priv_nr = priv_nr;
+
+ if (bfd->fd < 0) {
+ LOGP(DLINP, LOGL_ERROR, "could not create socket.\n");
+ return -EIO;
+ }
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sin_family = AF_INET;
+ addr.sin_port = htons(port);
+ if (ip != INADDR_ANY)
+ addr.sin_addr.s_addr = htonl(ip);
+ else
+ addr.sin_addr.s_addr = INADDR_ANY;
+
+ setsockopt(bfd->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
+
+ ret = bind(bfd->fd, (struct sockaddr *) &addr, sizeof(addr));
+ if (ret < 0) {
+ LOGP(DLINP, LOGL_ERROR, "could not bind socket %s\n",
+ strerror(errno));
+ close(bfd->fd);
+ return -EIO;
+ }
+
+ if (proto == IPPROTO_TCP) {
+ ret = listen(bfd->fd, 1);
+ if (ret < 0) {
+ perror("listen");
+ close(bfd->fd);
+ return ret;
+ }
+ }
+
+ ret = osmo_fd_register(bfd);
+ if (ret < 0) {
+ perror("register_listen_fd");
+ close(bfd->fd);
+ return ret;
+ }
+ return 0;
+}
diff --git a/src/libcommon/talloc_ctx.c b/src/libcommon/talloc_ctx.c
new file mode 100644
index 000000000..5e3d9aebe
--- /dev/null
+++ b/src/libcommon/talloc_ctx.c
@@ -0,0 +1,56 @@
+/* OpenBSC allocation contexts initialization code */
+/* (C) 2011-2016 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 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 <osmocom/core/talloc.h>
+#include <osmocom/core/msgb.h>
+
+extern void *tall_bsc_ctx;
+extern void *tall_fle_ctx;
+extern void *tall_locop_ctx;
+extern void *tall_authciphop_ctx;
+extern void *tall_gsms_ctx;
+extern void *tall_subscr_ctx;
+extern void *tall_sub_req_ctx;
+extern void *tall_call_ctx;
+extern void *tall_paging_ctx;
+extern void *tall_sigh_ctx;
+extern void *tall_tqe_ctx;
+extern void *tall_trans_ctx;
+extern void *tall_map_ctx;
+extern void *tall_upq_ctx;
+extern void *tall_ctr_ctx;
+
+void talloc_ctx_init(void *ctx_root)
+{
+ msgb_talloc_ctx_init(ctx_root, 0);
+ tall_fle_ctx = talloc_named_const(ctx_root, 0, "bs11_file_list_entry");
+ tall_locop_ctx = talloc_named_const(ctx_root, 0, "loc_updating_oper");
+ tall_authciphop_ctx = talloc_named_const(ctx_root, 0, "auth_ciph_oper");
+ tall_gsms_ctx = talloc_named_const(ctx_root, 0, "sms");
+ tall_subscr_ctx = talloc_named_const(ctx_root, 0, "subscriber");
+ tall_sub_req_ctx = talloc_named_const(ctx_root, 0, "subscr_request");
+ tall_call_ctx = talloc_named_const(ctx_root, 0, "gsm_call");
+ tall_paging_ctx = talloc_named_const(ctx_root, 0, "paging_request");
+ tall_sigh_ctx = talloc_named_const(ctx_root, 0, "signal_handler");
+ tall_tqe_ctx = talloc_named_const(ctx_root, 0, "subch_txq_entry");
+ tall_trans_ctx = talloc_named_const(ctx_root, 0, "transaction");
+ tall_map_ctx = talloc_named_const(ctx_root, 0, "trau_map_entry");
+ tall_upq_ctx = talloc_named_const(ctx_root, 0, "trau_upq_entry");
+ tall_ctr_ctx = talloc_named_const(ctx_root, 0, "counter");
+}