/* (C) 2008-2009 by Harald Welte <laforge@gnumonks.org>
 * (C) 2009-2011 by Holger Hans Peter Freyther <zecke@selfish.org>
 * (C) 2009-2011 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/lienses/>.
 *
 */

#include <osmocom/bsc/bss.h>
#include <osmocom/bsc/debug.h>
#include <osmocom/bsc/gsm_data.h>
#include <osmocom/bsc/osmo_bsc.h>
#include <osmocom/bsc/osmo_bsc_rf.h>
#include <osmocom/bsc/bsc_msc_data.h>
#include <osmocom/bsc/signal.h>
#include <osmocom/bsc/vty.h>
#include <osmocom/bsc/ipaccess.h>
#include <osmocom/bsc/ctrl.h>
#include <osmocom/bsc/osmo_bsc_sigtran.h>
#include <osmocom/bsc/handover_decision.h>
#include <osmocom/bsc/handover_decision_2.h>
#include <osmocom/bsc/timeslot_fsm.h>
#include <osmocom/bsc/lchan_fsm.h>
#include <osmocom/bsc/bsc_subscr_conn_fsm.h>
#include <osmocom/bsc/bsc_subscriber.h>
#include <osmocom/bsc/assignment_fsm.h>
#include <osmocom/bsc/handover_fsm.h>
#include <osmocom/bsc/smscb.h>
#include <osmocom/bsc/lb.h>
#include <osmocom/bsc/meas_feed.h>

#include <osmocom/ctrl/control_cmd.h>
#include <osmocom/ctrl/control_if.h>
#include <osmocom/ctrl/ports.h>
#include <osmocom/ctrl/control_vty.h>

#include <osmocom/core/application.h>
#include <osmocom/core/linuxlist.h>
#include <osmocom/core/talloc.h>
#include <osmocom/core/stats.h>
#include <osmocom/gsm/protocol/gsm_12_21.h>
#include <osmocom/vty/telnet_interface.h>
#include <osmocom/vty/ports.h>
#include <osmocom/vty/logging.h>
#include <osmocom/vty/command.h>
#include <osmocom/vty/cpu_sched_vty.h>

#include <osmocom/mgcp_client/mgcp_client_endpoint_fsm.h>
#include <osmocom/mgcp_client/mgcp_client_pool.h>

#include <osmocom/abis/abis.h>
#include <osmocom/bsc/abis_om2000.h>
#include <osmocom/bsc/abis_nm.h>
#include <osmocom/bsc/abis_rsl.h>
#include <osmocom/bsc/chan_alloc.h>
#include <osmocom/bsc/e1_config.h>
#include <osmocom/bsc/codec_pref.h>
#include <osmocom/bsc/system_information.h>
#include <osmocom/bsc/bts.h>
#include <osmocom/bsc/bsc_stats.h>

#include <osmocom/mgcp_client/mgcp_client.h>
#include <osmocom/mgcp_client/mgcp_client_pool.h>

#include <osmocom/sigtran/xua_msg.h>

#define _GNU_SOURCE
#include <getopt.h>

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>


#include "../../bscconfig.h"

static const char *config_file = "osmo-bsc.cfg";
static const char *rf_ctrl = NULL;
static int daemonize = 0;

static void print_usage(void)
{
	printf("Usage: osmo-bsc\n");
}

static void print_help(void)
{
	printf("Some useful options:\n");
	printf("  -h --help 			This text.\n");
	printf("  -D --daemonize 		Fork the process into a background daemon.\n");
	printf("  -d  --debug option 		--debug=DRLL:DMM:DRR:DRSL:DNM enable debugging.\n");
	printf("  -s --disable-color		Disable coloring log in stderr.\n");
	printf("  -T --timestamp		Print a timestamp in the debug output.\n");
	printf("  -V --version               	Print the version of OsmoBSC.\n");
	printf("  -c --config-file filename	The config file to use.\n");
	printf("  -e --log-level number		Set a global loglevel.\n");
	printf("  -r --rf-ctl NAME		A unix domain socket to listen for cmds.\n");

	printf("\nVTY reference generation:\n");
	printf("     --vty-ref-mode MODE	VTY reference generation mode (e.g. 'expert').\n");
	printf("     --vty-ref-xml		Generate the VTY reference XML output and exit.\n");
}

static void handle_long_options(const char *prog_name, const int long_option)
{
	static int vty_ref_mode = VTY_REF_GEN_MODE_DEFAULT;

	switch (long_option) {
	case 1:
		vty_ref_mode = get_string_value(vty_ref_gen_mode_names, optarg);
		if (vty_ref_mode < 0) {
			fprintf(stderr, "%s: Unknown VTY reference generation "
				"mode '%s'\n", prog_name, optarg);
			exit(2);
		}
		break;
	case 2:
		fprintf(stderr, "Generating the VTY reference in mode '%s' (%s)\n",
			get_value_string(vty_ref_gen_mode_names, vty_ref_mode),
			get_value_string(vty_ref_gen_mode_desc, vty_ref_mode));
		vty_dump_xml_ref_mode(stdout, (enum vty_ref_gen_mode) vty_ref_mode);
		exit(0);
	default:
		fprintf(stderr, "%s: error parsing cmdline options\n", prog_name);
		exit(2);
	}
}

static void handle_options(int argc, char **argv)
{
	while (1) {
		int option_index = 0, c;
		static int long_option = 0;
		static struct option long_options[] = {
			{"help", 0, 0, 'h'},
			{"debug", 1, 0, 'd'},
			{"daemonize", 0, 0, 'D'},
			{"config-file", 1, 0, 'c'},
			{"disable-color", 0, 0, 's'},
			{"timestamp", 0, 0, 'T'},
			{"version", 0, 0, 'V' },
			{"log-level", 1, 0, 'e'},
			{"rf-ctl", 1, 0, 'r'},
			{"vty-ref-mode", 1, &long_option, 1},
			{"vty-ref-xml", 0, &long_option, 2},
			{0, 0, 0, 0}
		};

		c = getopt_long(argc, argv, "hd:DsTVc:e:r:",
				long_options, &option_index);
		if (c == -1)
			break;

		switch (c) {
		case 'h':
			print_usage();
			print_help();
			exit(0);
		case 0:
			handle_long_options(argv[0], long_option);
			break;
		case 's':
			log_set_use_color(osmo_stderr_target, 0);
			break;
		case 'd':
			log_parse_category_mask(osmo_stderr_target, optarg);
			break;
		case 'D':
			daemonize = 1;
			break;
		case 'c':
			config_file = optarg;
			break;
		case 'T':
			log_set_print_timestamp(osmo_stderr_target, 1);
			break;
		case 'V':
			print_version(1);
			exit(0);
			break;
		case 'e':
			log_set_log_level(osmo_stderr_target, atoi(optarg));
			break;
		case 'r':
			rf_ctrl = optarg;
			break;
		default:
			/* catch unknown options *as well as* missing arguments. */
			fprintf(stderr, "Error in command line options. Exiting.\n");
			exit(-1);
		}
	}

	if (argc > optind) {
		fprintf(stderr, "Unsupported positional arguments on command line\n");
		exit(2);
	}
}

/* Callback function for NACK on the OML NM */
static int oml_msg_nack(struct nm_nack_signal_data *nack)
{
	if (nack->mt == NM_MT_GET_ATTR_NACK) {
		LOGP(DNM, LOGL_ERROR, "BTS%u does not support Get Attributes "
		     "OML message.\n", nack->bts->nr);
		return 0;
	}

	if (nack->mt == NM_MT_SET_BTS_ATTR_NACK)
		LOGP(DNM, LOGL_ERROR, "Failed to set BTS attributes. That is fatal. "
		     "Was the bts type and frequency properly specified?\n");
	else
		LOGP(DNM, LOGL_ERROR, "Got %s NACK going to drop the OML links.\n",
		     abis_nm_nack_name(nack->mt));

	if (!nack->bts) {
		LOGP(DNM, LOGL_ERROR, "Unknown bts. Can not drop it.\n");
		return 0;
	}

	if (is_ipa_abisip_bts(nack->bts))
		ipaccess_drop_oml_deferred(nack->bts);

	return 0;
}

/* Callback function to be called every time we receive a signal from NM */
static int nm_sig_cb(unsigned int subsys, unsigned int signal,
		     void *handler_data, void *signal_data)
{
	struct nm_nack_signal_data *nack;

	switch (signal) {
	case S_NM_NACK:
		nack = signal_data;
		return oml_msg_nack(nack);
	default:
		break;
	}
	return 0;
}

/* Produce a MA as specified in 10.5.2.21 */
static void generate_ma_for_ts(struct gsm_bts_trx_ts *ts)
{
	/* we have three bitvecs: the per-timeslot ARFCNs, the cell chan ARFCNs
	 * and the MA */
	const size_t num_cell_arfcns = ts->trx->bts->si_common.cell_chan_num;
	const struct bitvec *cell_chan = &ts->trx->bts->si_common.cell_alloc;
	const struct bitvec *ts_arfcn = &ts->hopping.arfcns;
	struct bitvec *ma = &ts->hopping.ma;
	int i;

	/* re-set the MA to all-zero */
	ts->hopping.ma_len = 0;
	memset(ma->data, 0, ma->data_len);

	if (!ts->hopping.enabled)
		return;

	/* pad it to octet-aligned number of bits */
	ts->hopping.ma_len = OSMO_BYTES_FOR_BITS(num_cell_arfcns);
	ma->cur_bit = (ts->hopping.ma_len * 8) - 1;

	for (i = 1; i < 1024; i++) {
		if (!bitvec_get_bit_pos(cell_chan, i))
			continue;
		/* set the corresponding bit in the MA */
		if (bitvec_get_bit_pos(ts_arfcn, i))
			bitvec_set_bit_pos(ma, ma->cur_bit, 1);
		else
			bitvec_set_bit_pos(ma, ma->cur_bit, 0);
		ma->cur_bit--;
	}

	/* ARFCN 0 is special: It is coded last in the bitmask */
	if (bitvec_get_bit_pos(cell_chan, 0)) {
		/* set the corresponding bit in the MA */
		if (bitvec_get_bit_pos(ts_arfcn, 0))
			bitvec_set_bit_pos(ma, ma->cur_bit, 1);
		else
			bitvec_set_bit_pos(ma, ma->cur_bit, 0);
	}
}

static void generate_ma_for_bts(struct gsm_bts *bts)
{
	struct gsm_bts_trx *trx;
	unsigned int tn;

	OSMO_ASSERT(bts->si_common.cell_chan_num > 0);
	OSMO_ASSERT(bts->si_common.cell_chan_num <= 64);

	llist_for_each_entry(trx, &bts->trx_list, list) {
		for (tn = 0; tn < ARRAY_SIZE(trx->ts); tn++)
			generate_ma_for_ts(&trx->ts[tn]);
	}
}

static void bootstrap_rsl(struct gsm_bts_trx *trx)
{
	struct gsm_bts *bts = trx->bts;
	unsigned int i;
	int rc;

	LOG_TRX(trx, DRSL, LOGL_NOTICE, "bootstrapping RSL "
		"on ARFCN %u using MCC-MNC %s LAC=%u CID=%u BSIC=%u\n",
		trx->arfcn, osmo_plmn_name(&bsc_gsmnet->plmn),
		bts->location_area_code, bts->cell_identity, bts->bsic);

	if (is_nokia_bts(bts)) {
		rsl_nokia_si_begin(trx);
	}

	/*
	 * Trigger ACC ramping before sending system information to BTS.
	 * This ensures that RACH control in system information is configured correctly.
	 * TRX 0 should be usable and unlocked, otherwise starting ACC ramping is pointless.
	 */
	if (trx_is_usable(trx))
		acc_ramp_trigger(&trx->bts->acc_ramp);

	if (gsm_bts_trx_set_system_infos(trx) != 0) {
		LOG_TRX(trx, DRSL, LOGL_ERROR, "Failed to generate System Information\n");
		return;
	}

	if (is_nokia_bts(bts)) {
		/* channel unspecific, power reduction in 2 dB steps */
		rsl_bs_power_control(trx, 0xFF, trx->max_power_red / 2);
		rsl_nokia_si_end(trx);
	}

	if (bts->model->power_ctrl_send_def_params != NULL) {
		rc = bts->model->power_ctrl_send_def_params(trx);
		if (rc) {
			LOG_TRX(trx, DRSL, LOGL_ERROR, "Failed to send default "
				"MS/BS Power control parameters (rc=%d)\n", rc);
			/* TODO: should we drop RSL connection here? */
		}
	}

	for (i = 0; i < ARRAY_SIZE(trx->ts); i++) {
		struct gsm_bts_trx_ts *ts = &trx->ts[i];
		OSMO_ASSERT(ts->fi);
		osmo_fsm_inst_dispatch(ts->fi, TS_EV_RSL_READY, NULL);
	}

	/* Drop all expired channel requests in the list */
	abis_rsl_chan_rqd_queue_flush(bts);
}

struct osmo_timer_list update_connection_stats_timer;

/* Periodically call bsc_update_connection_stats() to keep stat items updated.
 * It would be nicer to trigger this only when OML or RSL state is seen to flip. I tried hard to find all code paths
 * that should call this and failed to get accurate results; this trivial timer covers all of them. */
static void update_connection_stats_cb(void *data)
{
	bsc_update_connection_stats(bsc_gsmnet);
	osmo_timer_setup(&update_connection_stats_timer, update_connection_stats_cb, NULL);
	osmo_timer_schedule(&update_connection_stats_timer, 1, 0);
}

static bool nch_position_compatible_with_combined_ccch(const struct gsm_bts *bts)
{
	switch (bts->nch.num_blocks) {
	case 0:
		/* no NCH enabled, so we are fine */
		return true;
	case 1:
		if (bts->nch.first_block == 0 || bts->nch.first_block == 1)
			return true;
		break;
	case 2:
		if (bts->nch.first_block == 0)
			return true;
		break;
	default:
		break;
	}

	/* anything else is not permitted */
	return false;
}

static void bootstrap_bts(struct gsm_bts *bts)
{
	unsigned int n = 0;

	/* Control Channel Description is set from vty/config */

	/* Determine the value of CCCH_CONF. Is TS0/C0 combined? */
	if (bts->c0->ts[0].pchan_from_config != GSM_PCHAN_CCCH) {
		bts->si_common.chan_desc.ccch_conf = RSL_BCCH_CCCH_CONF_1_C;

		/* Limit reserved block to 2 on combined channel according to
		   3GPP TS 44.018 Table 10.5.2.11.1 */
		if (bts->si_common.chan_desc.bs_ag_blks_res > 2) {
			LOGP(DNM, LOGL_NOTICE, "CCCH is combined with SDCCHs, "
			     "reducing BS-AG-BLKS-RES value %d -> 2\n",
			     bts->si_common.chan_desc.bs_ag_blks_res);
			bts->si_common.chan_desc.bs_ag_blks_res = 2;
		}

		if (!nch_position_compatible_with_combined_ccch(bts)) {
			LOG_BTS(bts, DNM, LOGL_ERROR, "CCCH is combined with SDCCHs, but NCH position/size is "
				"incompatible with that. Please fix your config!\n");
		}
	} else { /* Non-combined TS0/C0 configuration */
		/* There can be additional CCCHs on even timeslot numbers */
		n += (bts->c0->ts[2].pchan_from_config == GSM_PCHAN_CCCH);
		n += (bts->c0->ts[4].pchan_from_config == GSM_PCHAN_CCCH);
		n += (bts->c0->ts[6].pchan_from_config == GSM_PCHAN_CCCH);
		bts->si_common.chan_desc.ccch_conf = (n << 1);
	}

	if (bts->nch.first_block + bts->nch.num_blocks > bts->si_common.chan_desc.bs_ag_blks_res) {
		LOG_BTS(bts, DNM, LOGL_ERROR, "Position/Number of NCH blocks (%u..%u) exceeds AGCH (%u)."
			"Please fix your config!\n", bts->nch.first_block,
			bts->nch.first_block + bts->nch.num_blocks - 1,
			bts->si_common.chan_desc.bs_ag_blks_res);
	}

	bts_setup_ramp_init_bts(bts);

	/* ACC ramping is initialized from vty/config */

	/* Initialize the BTS state */
	gsm_bts_sm_mo_reset(bts->site_mgr);

	/* Generate Mobile Allocation bit-masks for all timeslots.
	 * This needs to be done here, because it's used for TS configuration. */
	generate_ma_for_bts(bts);
}

/* Callback function to be called every time we receive a signal from INPUT */
static int inp_sig_cb(unsigned int subsys, unsigned int signal,
		      void *handler_data, void *signal_data)
{
	struct input_signal_data *isd = signal_data;
	struct gsm_bts_trx *trx = isd->trx;
	int rc;

	if (subsys != SS_L_INPUT)
		return -EINVAL;

	LOGP(DLMI, LOGL_DEBUG, "%s(): Input signal '%s' received\n", __func__,
		get_value_string(e1inp_signal_names, signal));
	switch (signal) {
	case S_L_INP_TEI_UP:
		if (isd->link_type == E1INP_SIGN_OML) {
			/* Check parameters and apply vty config dependent parameters */
			rc = gsm_bts_check_cfg(trx->bts);
			if (rc < 0) {
				LOGP(DNM, LOGL_ERROR, "(bts=%u) Error in BTS configuration -- cannot bootstrap BTS\n",
				     trx->bts->nr);
				return rc;
			}
			bootstrap_bts(trx->bts);
		}
		if (isd->link_type == E1INP_SIGN_RSL) {
			rc = gsm_bts_check_cfg(trx->bts);
			if (rc < 0) {
				LOGP(DNM, LOGL_ERROR, "(bts=%u) Error in BTS configuration -- cannot bootstrap RSL\n",
				     trx->bts->nr);
				return rc;
			}
			bootstrap_rsl(trx);
		}
		break;
	case S_L_INP_TEI_DN:
		LOG_TRX(trx, DLMI, LOGL_ERROR, "Lost E1 %s link\n", e1inp_signtype_name(isd->link_type));

		if (isd->link_type == E1INP_SIGN_OML) {
			rate_ctr_inc(rate_ctr_group_get_ctr(trx->bts->bts_ctrs, BTS_CTR_BTS_OML_FAIL));
			/* ip.access BTS models have a single global A-bis/OML link for all
			 * transceivers, so once it's lost we need to notify them all. */
			if (is_ipa_abisip_bts(trx->bts))
				gsm_bts_all_ts_dispatch(trx->bts, TS_EV_OML_DOWN, NULL);
			else /* Other BTS models (e.g. Ericsson) have per-TRX OML links */
				gsm_trx_all_ts_dispatch(trx, TS_EV_OML_DOWN, NULL);
		} else if (isd->link_type == E1INP_SIGN_RSL) {
			rate_ctr_inc(rate_ctr_group_get_ctr(trx->bts->bts_ctrs, BTS_CTR_BTS_RSL_FAIL));
			acc_ramp_abort(&trx->bts->acc_ramp);
			gsm_trx_all_ts_dispatch(trx, TS_EV_RSL_DOWN, NULL);
		}

		gsm_bts_sm_mo_reset(trx->bts->site_mgr);

		abis_nm_clear_queue(trx->bts);
		break;
	default:
		break;
	}

	return 0;
}

static int bsc_network_configure(const char *config_file)
{
	struct gsm_bts *bts;
	int rc;

	rc = vty_read_config_file(config_file, NULL);
	if (rc < 0) {
		LOGP(DNM, LOGL_FATAL, "Failed to parse the config file: '%s' (%s)\n", config_file, strerror(-rc));
		return rc;
	}

	/* start telnet after reading config for vty_get_bind_addr() */
	rc = telnet_init_default(tall_bsc_ctx, bsc_gsmnet, OSMO_VTY_PORT_NITB_BSC);
	if (rc < 0)
		return rc;

	osmo_signal_register_handler(SS_NM, nm_sig_cb, NULL);
	osmo_signal_register_handler(SS_L_INPUT, inp_sig_cb, NULL);

	llist_for_each_entry(bts, &bsc_gsmnet->bts_list, list) {
		rc = gsm_bts_check_cfg(bts);
		if (rc < 0) {
			LOGP(DNM, LOGL_FATAL, "(bts=%u) cannot bootstrap BTS, invalid BTS configuration\n", bts->nr);
			return rc;
		}
		bootstrap_bts(bts);
		rc = e1_reconfig_bts(bts);
		if (rc < 0) {
			LOGP(DNM, LOGL_FATAL, "Error enabling E1 input driver\n");
			return rc;
		}
	}

	return 0;
}

static int bsc_vty_go_parent(struct vty *vty)
{
	switch (vty->node) {
	case GSMNET_NODE:
		vty->node = CONFIG_NODE;
		vty->index = NULL;
		break;
	case MGW_NODE:
		vty->node = GSMNET_NODE;
		vty->index = bsc_gsmnet;
		vty->index_sub = 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 POWER_CTRL_NODE:
		vty->node = BTS_NODE;
		{
			const struct gsm_power_ctrl_params *cp = vty->index;
			struct gsm_bts *bts;

			if (cp->dir == GSM_PWR_CTRL_DIR_UL)
				bts = container_of(cp, struct gsm_bts, ms_power_ctrl);
			else
				bts = container_of(cp, struct gsm_bts, bs_power_ctrl);

			vty->index_sub = &bts->description;
			vty->index = bts;
		}
		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;
		}
		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 BSC_NODE:
	case MSC_NODE:
		vty->node = CONFIG_NODE;
		vty->index = NULL;
		break;
	default:
		osmo_ss7_vty_go_parent(vty);
	}

	return vty->node;
}

static struct vty_app_info vty_info = {
	.name 		= "OsmoBSC",
	.copyright	=
	"Copyright (C) 2008-2018 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"
	"Copyright (C) 2013-2022 sysmocom - s.f.m.c. GmbH\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",
	.version	= PACKAGE_VERSION,
	.go_parent_cb	= bsc_vty_go_parent,
	.usr_attr_desc	= {
		[BSC_VTY_ATTR_RESTART_ABIS_OML_LINK] = \
			"This command applies on A-bis OML link (re)establishment",
		[BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK] = \
			"This command applies on A-bis RSL link (re)establishment",
		[BSC_VTY_ATTR_NEW_LCHAN] = \
			"This command applies for newly created lchans",
		[BSC_VTY_ATTR_VENDOR_SPECIFIC] = \
			"This command/parameter is BTS vendor specific",
	},
	.usr_attr_letters = {
		[BSC_VTY_ATTR_RESTART_ABIS_OML_LINK]	= 'o',
		[BSC_VTY_ATTR_RESTART_ABIS_RSL_LINK]	= 'r',
		[BSC_VTY_ATTR_NEW_LCHAN]		= 'l',
		[BSC_VTY_ATTR_VENDOR_SPECIFIC]		= 'v',
	},
};

extern int bsc_shutdown_net(struct gsm_network *net);
static void signal_handler(int signum)
{
	fprintf(stdout, "signal %u received\n", signum);

	switch (signum) {
	case SIGINT:
	case SIGTERM:
		/* If SIGTERM was already sent before, just terminate immediately. */
		if (osmo_select_shutdown_requested())
			exit(-1);
		bsc_shutdown_net(bsc_gsmnet);
		osmo_signal_dispatch(SS_L_GLOBAL, S_L_GLOBAL_SHUTDOWN, NULL);
		osmo_select_shutdown_request();
		break;
	case SIGABRT:
		/* in case of abort, we want to obtain a talloc report and
		 * then run default SIGABRT handler, who will generate coredump
		 * and abort the process. abort() should do this for us after we
		 * return, but program wouldn't exit if an external SIGABRT is
		 * received.
		 */
		talloc_report(tall_vty_ctx, stderr);
		talloc_report_full(tall_bsc_ctx, stderr);
		signal(SIGABRT, SIG_DFL);
		raise(SIGABRT);
		break;
	case SIGUSR1:
		talloc_report(tall_vty_ctx, stderr);
		talloc_report_full(tall_bsc_ctx, stderr);
		break;
	default:
		break;
	}
}

static const struct log_info_cat osmo_bsc_categories[] = {
	[DRLL] = {
		.name = "DRLL",
		.description = "A-bis Radio Link Layer (RLL)",
		.color = "\033[1;31m",
		.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 Signalling Link (RSL)",
		.color = "\033[1;35m",
		.enabled = 1, .loglevel = LOGL_NOTICE,
	},
	[DCHAN] = {
		.name = "DCHAN",
		.description = "lchan FSM",
		.color = "\033[1;32m",
		.enabled = 1, .loglevel = LOGL_NOTICE,
	},
	[DTS] = {
		.name = "DTS",
		.description = "timeslot FSM",
		.color = "\033[1;31m",
		.enabled = 1, .loglevel = LOGL_NOTICE,
	},
	[DAS] = {
		.name = "DAS",
		.description = "assignment FSM",
		.color = "\033[1;33m",
		.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_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,
	},
	[DMSC] = {
		.name = "DMSC",
		.description = "Mobile Switching Center",
		.enabled = 1, .loglevel = LOGL_NOTICE,
	},
	[DHO] = {
		.name = "DHO",
		.description = "Hand-Over Process",
		.color = "\033[1;38m",
		.enabled = 1, .loglevel = LOGL_NOTICE,
	},
	[DHODEC] = {
		.name = "DHODEC",
		.description = "Hand-Over Decision",
		.color = "\033[1;38m",
		.enabled = 1, .loglevel = LOGL_NOTICE,
	},
	[DREF] = {
		.name = "DREF",
		.description = "Reference Counting",
		.enabled = 0, .loglevel = LOGL_NOTICE,
	},
	[DCTRL] = {
		.name = "DCTRL",
		.description = "Control interface",
		.enabled = 1, .loglevel = LOGL_NOTICE,
	},
	[DFILTER] = {
		.name = "DFILTER",
		.description = "BSC/NAT IMSI based filtering",
		.enabled = 1, .loglevel = LOGL_NOTICE,
	},
	[DPCU] = {
		.name = "DPCU",
		.description = "PCU Interface",
		.enabled = 1, .loglevel = LOGL_NOTICE,
	},
	[DLCLS] = {
		.name = "DLCLS",
		.description = "Local Call, Local Switch",
		.enabled = 1, .loglevel = LOGL_NOTICE,
	},
	[DCBS] = {
		.name = "DCBS",
		.description = "Cell Broadcast System",
		.enabled = 1, .loglevel = LOGL_NOTICE,
	},
	[DLCS] = {
		.name = "DLCS",
		.description = "Location Services",
		.enabled = 1, .loglevel = LOGL_NOTICE,
	},
	[DASCI] = {
		.name = "DASCI",
		.description = "Advanced Speech Call Items (VGCS/VBS)",
		.color = "\033[1;38m",
		.enabled = 1, .loglevel = LOGL_NOTICE,
	},
	[DRESET] = {
		.name = "DRESET",
		.description = "RESET/ACK on A and Lb interfaces",
		.enabled = 1, .loglevel = LOGL_NOTICE,
	},
	[DLOOP] = {
		.name = "DLOOP",
		.description = "Control loops",
		.color = "\033[0;34m",
		.enabled = 1, .loglevel = LOGL_NOTICE,
	},
};

static int filter_fn(const struct log_context *ctx, struct log_target *tar)
{
	const struct bsc_subscr *bsub_ctx = log_get_context(ctx, LOG_CTX_BSC_SUBSCR);
	const struct bsc_subscr *bsub_filter = log_get_filter_data(tar, LOG_FLT_BSC_SUBSCR);

	if (log_get_filter(tar, LOG_FLT_BSC_SUBSCR) &&
	    bsub_ctx && bsub_filter &&
	    strncmp(bsub_ctx->imsi, bsub_filter->imsi, sizeof(bsub_ctx->imsi)) == 0)
		return 1;

	return 0;
}

const struct log_info log_info = {
	.filter_fn = filter_fn,
	.cat = osmo_bsc_categories,
	.num_cat = ARRAY_SIZE(osmo_bsc_categories),
};

extern void *tall_paging_ctx;
extern void *tall_fle_ctx;
extern void *tall_tqe_ctx;

static int bsc_mgw_setup(void)
{
	struct mgcp_client *mgcp_client_single;
	unsigned int pool_members_initalized;

	/* Initialize MGW pool. This initializes and connects all MGCP clients that are currently configured in
	 * the pool. Adding additional MGCP clients to the pool is possible but the user has to configure and
	 * (re)connect them manually from the VTY. */
	if (!mgcp_client_pool_empty(bsc_gsmnet->mgw.mgw_pool)) {
		pool_members_initalized = mgcp_client_pool_connect(bsc_gsmnet->mgw.mgw_pool);
		if (!pool_members_initalized) {
			LOGP(DNM, LOGL_ERROR, "MGW pool failed to initialize any pool members\n");
			return -EINVAL;
		}
		LOGP(DNM, LOGL_NOTICE,
		     "MGW pool with %u pool members configured, (ignoring MGW configuration in VTY node 'msc').\n",
		     pool_members_initalized);
		return 0;
	}

	/* Initialize and connect a single MGCP client. This MGCP client will appear as the one and only pool
	 * member if there is no MGW pool configured. */
	LOGP(DNM, LOGL_NOTICE, "No MGW pool configured, using MGW configuration in VTY node 'msc'\n");
	mgcp_client_single = mgcp_client_init(bsc_gsmnet, bsc_gsmnet->mgw.conf);
	if (!mgcp_client_single) {
		LOGP(DNM, LOGL_ERROR, "MGW (single) client initialization failed\n");
		return -EINVAL;
	}
	if (mgcp_client_connect(mgcp_client_single)) {
		LOGP(DNM, LOGL_ERROR, "MGW (single) connect failed at (%s:%u)\n",
		     bsc_gsmnet->mgw.conf->remote_addr,
		     bsc_gsmnet->mgw.conf->remote_port);
		return -EINVAL;
	}
	mgcp_client_pool_register_single(bsc_gsmnet->mgw.mgw_pool, mgcp_client_single);

	return 0;
}

int main(int argc, char **argv)
{
	struct bsc_msc_data *msc;
	int rc;

	tall_bsc_ctx = talloc_named_const(NULL, 1, "osmo-bsc");
	msgb_talloc_ctx_init(tall_bsc_ctx, 0);
	osmo_signal_talloc_ctx_init(tall_bsc_ctx);
	osmo_xua_msg_tall_ctx_init(tall_bsc_ctx);
	vty_info.tall_ctx = tall_bsc_ctx;

	tall_paging_ctx = talloc_named_const(tall_bsc_ctx, 0, "paging_request");
	tall_fle_ctx = talloc_named_const(tall_bsc_ctx, 0, "bs11_file_list_entry");
	tall_tqe_ctx = talloc_named_const(tall_bsc_ctx, 0, "subch_txq_entry");

	osmo_init_logging2(tall_bsc_ctx, &log_info);
	osmo_stats_init(tall_bsc_ctx);
	rate_ctr_init(tall_bsc_ctx);

	osmo_fsm_set_dealloc_ctx(OTC_SELECT);
	osmo_fsm_log_timeouts(true);

	/* Allocate global gsm_network struct */
	rc = bsc_network_alloc();
	if (rc) {
		fprintf(stderr, "Allocation failed. exiting.\n");
		exit(1);
	}

	bsc_gsmnet->mgw.conf = mgcp_client_conf_alloc(bsc_gsmnet);
	bsc_gsmnet->mgw.mgw_pool = mgcp_client_pool_alloc(bsc_gsmnet);

	bts_init();
	libosmo_abis_init(tall_bsc_ctx);

	/* enable filters */

	/* This needs to precede handle_options() */
	vty_init(&vty_info);
	bsc_vty_init(bsc_gsmnet);
	ctrl_vty_init(tall_bsc_ctx);
	osmo_cpu_sched_vty_init(tall_bsc_ctx);
	logging_vty_add_deprecated_subsys(tall_bsc_ctx, "cc");
	logging_vty_add_deprecated_subsys(tall_bsc_ctx, "mgcp");
	logging_vty_add_deprecated_subsys(tall_bsc_ctx, "nat");

	/* Initialize SS7 */
	OSMO_ASSERT(osmo_ss7_init() == 0);
	osmo_ss7_vty_init_asp(tall_bsc_ctx);
	osmo_sccp_vty_init();

	/* parse options */
	handle_options(argc, argv);

	/* seed the PRNG */
	srand(time(NULL));

	lb_init();
	acc_ramp_global_init();
	paging_global_init();
	smscb_global_init();
	meas_feed_txqueue_max_length_set(MEAS_FEED_TXQUEUE_MAX_LEN_DEFAULT);

	/* Read the config */
	rc = bsc_network_configure(config_file);
	if (rc < 0) {
		fprintf(stderr, "Bootstrapping the network failed. exiting.\n");
		exit(1);
	}

	if (neighbors_check_cfg()) {
		fprintf(stderr, "Errors in neighbor configuration, check the DHO log. exiting.\n");
		exit(1);
	}

	/* start control interface after reading config for
	 * ctrl_vty_get_bind_addr() */
	bsc_gsmnet->ctrl = bsc_controlif_setup(bsc_gsmnet, OSMO_CTRL_PORT_NITB_BSC);
	if (!bsc_gsmnet->ctrl) {
		fprintf(stderr, "Failed to init the control interface. Exiting.\n");
		exit(1);
	}

	rc = bsc_ctrl_cmds_install(bsc_gsmnet);
	if (rc < 0) {
		fprintf(stderr, "Failed to install control commands. Exiting.\n");
		exit(1);
	}

	if (bsc_gsmnet->neigh_ctrl.addr) {
		bsc_gsmnet->neigh_ctrl.handle = neighbor_controlif_setup(bsc_gsmnet);
		if (!bsc_gsmnet->neigh_ctrl.handle) {
			fprintf(stderr, "Failed to bind Neighbor Resolution Service. Exiting.\n");
			exit(1);
		}
		rc = neighbor_ctrl_cmds_install(bsc_gsmnet);
		if (rc < 0) {
			fprintf(stderr, "Failed to install Neighbor Resolution Service commands. Exiting.\n");
			exit(1);
		}
	}

	if (rf_ctrl)
		osmo_talloc_replace_string(bsc_gsmnet, &bsc_gsmnet->rf_ctrl_name, rf_ctrl);

	bsc_gsmnet->rf_ctrl = osmo_bsc_rf_create(bsc_gsmnet->rf_ctrl_name, bsc_gsmnet);
	if (!bsc_gsmnet->rf_ctrl) {
		fprintf(stderr, "Failed to create the RF service.\n");
		exit(1);
	}

	rc = check_codec_pref(&bsc_gsmnet->mscs);
	if (rc < 0) {
		LOGP(DMSC, LOGL_ERROR, "Configuration contains mutually exclusive codec settings -- check"
				       " configuration!\n");
		if (!bsc_gsmnet->allow_unusable_timeslots) {
			LOGP(DMSC, LOGL_ERROR, "You should really fix that! However, you can prevent OsmoBSC from"
					       " stopping here by setting 'allow-unusable-timeslots' in the 'network'"
					       " section of the config.\n");
			exit(1);
		}
	}

	if (bsc_mgw_setup() != 0)
		exit(1);

	llist_for_each_entry(msc, &bsc_gsmnet->mscs, entry) {
		if (osmo_bsc_msc_init(msc) != 0) {
			LOGP(DMSC, LOGL_ERROR, "Failed to start up. Exiting.\n");
			exit(1);
		}
	}

	if (osmo_bsc_sigtran_init(&bsc_gsmnet->mscs) != 0) {
		LOGP(DNM, LOGL_ERROR, "Failed to initialize sigtran backhaul.\n");
		exit(1);
	}

	handover_decision_1_init();
	hodec2_init(bsc_gsmnet);
	bsc_cbc_link_restart();
	lb_start_or_stop();

	signal(SIGINT, &signal_handler);
	signal(SIGTERM, &signal_handler);
	signal(SIGABRT, &signal_handler);
	signal(SIGUSR1, &signal_handler);
	signal(SIGUSR2, &signal_handler);
	osmo_init_ignore_signals();

	update_connection_stats_cb(NULL);
	chan_counts_sig_init();

	if (daemonize) {
		rc = osmo_daemonize();
		if (rc < 0) {
			perror("Error during daemonize");
			exit(1);
		}
	}

	while (!osmo_select_shutdown_done()) {
		osmo_select_main_ctx(0);
	}

	return 0;
}
