aboutsummaryrefslogtreecommitdiffstats
path: root/tests/handover
diff options
context:
space:
mode:
Diffstat (limited to 'tests/handover')
-rw-r--r--tests/handover/Makefile.am79
-rw-r--r--tests/handover/handover_test.c2597
-rw-r--r--tests/handover/handover_test.ok1
-rw-r--r--tests/handover/handover_tests.ok57
-rwxr-xr-xtests/handover/handover_tests.sh61
-rw-r--r--tests/handover/neighbor_ident_test.c270
-rw-r--r--tests/handover/neighbor_ident_test.err0
-rw-r--r--tests/handover/neighbor_ident_test.ok186
-rw-r--r--tests/handover/test_amr_tch_f_to_h.ho_vty14
-rw-r--r--tests/handover/test_amr_tch_f_to_h_balance_congestion.ho_vty16
-rw-r--r--tests/handover/test_amr_tch_f_to_h_congestion.ho_vty19
-rw-r--r--tests/handover/test_amr_tch_f_to_h_congestion_assignment.ho_vty18
-rw-r--r--tests/handover/test_amr_tch_f_to_h_congestion_assignment_2.ho_vty27
-rw-r--r--tests/handover/test_amr_tch_f_to_h_congestion_assignment_3.ho_vty15
-rw-r--r--tests/handover/test_amr_tch_h_and_afs_bias.ho_vty13
-rw-r--r--tests/handover/test_amr_tch_h_to_f_congestion.ho_vty14
-rw-r--r--tests/handover/test_amr_tch_h_to_f_congestion_two_cells.ho_vty17
-rw-r--r--tests/handover/test_amr_tch_h_to_f_rxlev.ho_vty16
-rw-r--r--tests/handover/test_amr_tch_h_to_f_rxlev_congested.ho_vty63
-rw-r--r--tests/handover/test_amr_tch_h_to_f_rxlev_oscillation.ho_vty20
-rw-r--r--tests/handover/test_amr_tch_h_to_f_rxqual.ho_vty39
-rw-r--r--tests/handover/test_amr_tch_h_to_f_rxqual_congested.ho_vty68
-rw-r--r--tests/handover/test_amr_tch_h_to_f_rxqual_oscillation.ho_vty20
-rw-r--r--tests/handover/test_balance_congestion.ho_vty20
-rw-r--r--tests/handover/test_balance_congestion_2.ho_vty18
-rw-r--r--tests/handover/test_balance_congestion_by_percentage.ho_vty52
-rw-r--r--tests/handover/test_balance_congestion_tchf_tchh.ho_vty53
-rw-r--r--tests/handover/test_bs_power.ho_vty11
-rw-r--r--tests/handover/test_congestion.ho_vty21
-rw-r--r--tests/handover/test_congestion_favor_best_target_rxlev.ho_vty33
-rw-r--r--tests/handover/test_congestion_intra_vs_inter_cell.ho_vty122
-rw-r--r--tests/handover/test_congestion_no_oscillation.ho_vty28
-rw-r--r--tests/handover/test_congestion_no_oscillation2.ho_vty28
-rw-r--r--tests/handover/test_disabled_ho_and_as.ho_vty36
-rw-r--r--tests/handover/test_dyn_ts_amr_tch_f_to_h_congestion_assignment.ho_vty78
-rw-r--r--tests/handover/test_dyn_ts_amr_tch_f_to_h_congestion_assignment_2.ho_vty47
-rw-r--r--tests/handover/test_dyn_ts_amr_tch_h_to_f_congestion_assignment_2.ho_vty27
-rw-r--r--tests/handover/test_dyn_ts_balance_congestion.ho_vty37
-rw-r--r--tests/handover/test_dyn_ts_congestion_tch_f_vs_tch_h.ho_vty74
-rw-r--r--tests/handover/test_dyn_ts_congestion_tch_f_vs_tch_h_2.ho_vty34
-rw-r--r--tests/handover/test_dyn_ts_favor_half_used_tch_h_as_target.ho_vty12
-rw-r--r--tests/handover/test_dyn_ts_favor_moving_half_used_tch_h.ho_vty42
-rw-r--r--tests/handover/test_dyn_ts_favor_static_ts_as_target.ho_vty38
-rw-r--r--tests/handover/test_ho_to_better_cell.ho_vty8
-rw-r--r--tests/handover/test_ho_to_better_cell_2.ho_vty10
-rw-r--r--tests/handover/test_hysteresis.ho_vty13
-rw-r--r--tests/handover/test_insufficient_measurements.ho_vty46
-rw-r--r--tests/handover/test_keep_efr_codec.ho_vty21
-rw-r--r--tests/handover/test_keep_fr_codec.ho_vty21
-rw-r--r--tests/handover/test_keep_hr_codec.ho_vty20
-rw-r--r--tests/handover/test_max_handovers.ho_vty18
-rw-r--r--tests/handover/test_max_ta.ho_vty37
-rw-r--r--tests/handover/test_meas_rep_multi_band.ho_vty47
-rw-r--r--tests/handover/test_min_rxlev_vs_congestion.ho_vty18
-rw-r--r--tests/handover/test_min_rxlev_vs_hysteresis.ho_vty20
-rw-r--r--tests/handover/test_neighbor_congested.ho_vty21
-rw-r--r--tests/handover/test_neighbor_full.ho_vty9
-rw-r--r--tests/handover/test_no_congestion.ho_vty17
-rw-r--r--tests/handover/test_penalty_timer.ho_vty45
-rw-r--r--tests/handover/test_resource_indication.ho_vty67
-rw-r--r--tests/handover/test_rxqual.ho_vty49
-rw-r--r--tests/handover/test_rxqual_vs_congestion.ho_vty19
-rw-r--r--tests/handover/test_stay_in_better_cell.ho_vty6
-rw-r--r--tests/handover/test_stay_in_better_cell_2.ho_vty10
-rw-r--r--tests/handover/test_story.ho_vty72
65 files changed, 3184 insertions, 1851 deletions
diff --git a/tests/handover/Makefile.am b/tests/handover/Makefile.am
index 0f7953cd4..123fd61d6 100644
--- a/tests/handover/Makefile.am
+++ b/tests/handover/Makefile.am
@@ -21,14 +21,13 @@ AM_LDFLAGS = \
$(NULL)
EXTRA_DIST = \
- handover_test.ok \
- neighbor_ident_test.ok \
- neighbor_ident_test.err \
+ handover_tests.sh \
+ handover_tests.ok \
+ $(srcdir)/test*.ho_vty \
$(NULL)
-noinst_PROGRAMS = \
+check_PROGRAMS = \
handover_test \
- neighbor_ident_test \
$(NULL)
handover_test_SOURCES = \
@@ -41,62 +40,7 @@ handover_test_LDFLAGS = \
$(NULL)
handover_test_LDADD = \
- $(top_builddir)/src/osmo-bsc/a_reset.o \
- $(top_builddir)/src/osmo-bsc/abis_nm.o \
- $(top_builddir)/src/osmo-bsc/abis_nm_vty.o \
- $(top_builddir)/src/osmo-bsc/abis_om2000.o \
- $(top_builddir)/src/osmo-bsc/abis_om2000_vty.o \
- $(top_builddir)/src/osmo-bsc/abis_rsl.o \
- $(top_builddir)/src/osmo-bsc/acc.o \
- $(top_builddir)/src/osmo-bsc/arfcn_range_encode.o \
- $(top_builddir)/src/osmo-bsc/assignment_fsm.o \
- $(top_builddir)/src/osmo-bsc/bsc_ctrl_commands.o \
- $(top_builddir)/src/osmo-bsc/bsc_init.o \
- $(top_builddir)/src/osmo-bsc/bsc_rf_ctrl.o \
- $(top_builddir)/src/osmo-bsc/bsc_rll.o \
- $(top_builddir)/src/osmo-bsc/bsc_subscr_conn_fsm.o \
- $(top_builddir)/src/osmo-bsc/bsc_subscriber.o \
- $(top_builddir)/src/osmo-bsc/bsc_vty.o \
- $(top_builddir)/src/osmo-bsc/bts.o \
- $(top_builddir)/src/osmo-bsc/bts_trx.o \
- $(top_builddir)/src/osmo-bsc/bts_ipaccess_nanobts.o \
- $(top_builddir)/src/osmo-bsc/bts_ipaccess_nanobts_omlattr.o \
- $(top_builddir)/src/osmo-bsc/bts_unknown.o \
- $(top_builddir)/src/osmo-bsc/chan_alloc.o \
- $(top_builddir)/src/osmo-bsc/codec_pref.o \
- $(top_builddir)/src/osmo-bsc/gsm_04_08_rr.o \
- $(top_builddir)/src/osmo-bsc/gsm_data.o \
- $(top_builddir)/src/osmo-bsc/handover_cfg.o \
- $(top_builddir)/src/osmo-bsc/handover_decision.o \
- $(top_builddir)/src/osmo-bsc/handover_decision_2.o \
- $(top_builddir)/src/osmo-bsc/handover_fsm.o \
- $(top_builddir)/src/osmo-bsc/handover_logic.o \
- $(top_builddir)/src/osmo-bsc/handover_vty.o \
- $(top_builddir)/src/osmo-bsc/lchan_fsm.o \
- $(top_builddir)/src/osmo-bsc/lchan_rtp_fsm.o \
- $(top_builddir)/src/osmo-bsc/lchan_select.o \
- $(top_builddir)/src/osmo-bsc/meas_feed.o \
- $(top_builddir)/src/osmo-bsc/meas_rep.o \
- $(top_builddir)/src/osmo-bsc/neighbor_ident.o \
- $(top_builddir)/src/osmo-bsc/neighbor_ident_vty.o \
- $(top_builddir)/src/osmo-bsc/net_init.o \
- $(top_builddir)/src/osmo-bsc/osmo_bsc_ctrl.o \
- $(top_builddir)/src/osmo-bsc/osmo_bsc_lcls.o \
- $(top_builddir)/src/osmo-bsc/osmo_bsc_mgcp.o \
- $(top_builddir)/src/osmo-bsc/osmo_bsc_msc.o \
- $(top_builddir)/src/osmo-bsc/paging.o \
- $(top_builddir)/src/osmo-bsc/pcu_sock.o \
- $(top_builddir)/src/osmo-bsc/penalty_timers.o \
- $(top_builddir)/src/osmo-bsc/rest_octets.o \
- $(top_builddir)/src/osmo-bsc/system_information.o \
- $(top_builddir)/src/osmo-bsc/timeslot_fsm.o \
- $(top_builddir)/src/osmo-bsc/smscb.o \
- $(top_builddir)/src/osmo-bsc/cbch_scheduler.o \
- $(top_builddir)/src/osmo-bsc/cbsp_link.o \
- $(top_builddir)/src/osmo-bsc/lcs_loc_req.o \
- $(top_builddir)/src/osmo-bsc/lcs_ta_req.o \
- $(top_builddir)/src/osmo-bsc/lb.o \
- $(top_builddir)/src/osmo-bsc/bsc_sccp.o \
+ $(top_builddir)/src/osmo-bsc/libbsc.la \
$(LIBOSMOCORE_LIBS) \
$(LIBOSMOGSM_LIBS) \
$(LIBOSMOCTRL_LIBS) \
@@ -107,17 +51,6 @@ handover_test_LDADD = \
$(LIBOSMOMGCPCLIENT_LIBS) \
$(NULL)
-neighbor_ident_test_SOURCES = \
- neighbor_ident_test.c \
- $(NULL)
-
-neighbor_ident_test_LDADD = \
- $(top_builddir)/src/osmo-bsc/neighbor_ident.o \
- $(LIBOSMOCORE_LIBS) \
- $(LIBOSMOGSM_LIBS) \
- $(LIBOSMOCTRL_LIBS) \
- $(NULL)
-
.PHONY: update_exp
update_exp:
- $(builddir)/neighbor_ident_test >$(srcdir)/neighbor_ident_test.ok 2>$(srcdir)/neighbor_ident_test.err
+ $(srcdir)/handover_tests.sh $(srcdir) $(builddir) -u
diff --git a/tests/handover/handover_test.c b/tests/handover/handover_test.c
index 84a48af5d..2c54526eb 100644
--- a/tests/handover/handover_test.c
+++ b/tests/handover/handover_test.c
@@ -20,12 +20,14 @@
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
+#include <inttypes.h>
#include <assert.h>
#include <osmocom/core/application.h>
#include <osmocom/core/select.h>
#include <osmocom/core/talloc.h>
+#include <osmocom/vty/vty.h>
#include <osmocom/mgcp_client/mgcp_client_endpoint_fsm.h>
@@ -34,6 +36,7 @@
#include <osmocom/bsc/bsc_subscriber.h>
#include <osmocom/bsc/lchan_select.h>
#include <osmocom/bsc/lchan_fsm.h>
+#include <osmocom/bsc/assignment_fsm.h>
#include <osmocom/bsc/handover_decision.h>
#include <osmocom/bsc/system_information.h>
#include <osmocom/bsc/handover.h>
@@ -49,10 +52,13 @@
#include <osmocom/bsc/bsc_msc_data.h>
#include <osmocom/bsc/bts.h>
#include <osmocom/bsc/paging.h>
+#include <osmocom/bsc/vty.h>
+#include <osmocom/bsc/lchan.h>
+#include <osmocom/mgcp_client/mgcp_client_pool.h>
-void *ctx;
+#include "../../bscconfig.h"
-struct gsm_network *bsc_gsmnet;
+void *ctx;
/* override, requires '-Wl,--wrap=osmo_mgcpc_ep_ci_request'.
* Catch modification of an MGCP connection. */
@@ -77,20 +83,64 @@ void __wrap_osmo_mgcpc_ep_ci_request(struct osmo_mgcpc_ep_ci *ci,
/* measurement report */
uint8_t meas_rep_ba = 0, meas_rep_valid = 1, meas_valid = 1, meas_multi_rep = 0;
-uint8_t meas_dl_rxlev = 0, meas_dl_rxqual = 0;
uint8_t meas_ul_rxlev = 0, meas_ul_rxqual = 0;
-uint8_t meas_tx_power_ms = 0, meas_tx_power_bs = 0, meas_ta_ms = 0;
+uint8_t meas_tx_power_ms = 0;
uint8_t meas_dtx_ms = 0, meas_dtx_bs = 0, meas_nr = 0;
-uint8_t meas_num_nc = 0, meas_rxlev_nc[6], meas_bsic_nc[6], meas_bcch_f_nc[6];
+char *codec_tch_f = NULL;
+char *codec_tch_h = NULL;
+
+struct neighbor_meas {
+ uint8_t rxlev;
+ uint8_t bsic;
+ uint8_t bcch_f;
+};
+
+const struct timeval fake_time_start_time = { 123, 456 };
+
+void fake_time_passes(time_t secs, suseconds_t usecs)
+{
+ struct timeval diff;
+ /* Add time to osmo_fsm timers, using osmo_gettimeofday() */
+ osmo_gettimeofday_override_add(secs, usecs);
+ /* Add time to penalty timers, using osmo_clock_gettime() */
+ osmo_clock_override_add(CLOCK_MONOTONIC, secs, usecs * 1000);
+
+ timersub(&osmo_gettimeofday_override_time, &fake_time_start_time, &diff);
+ fprintf(stderr, "Total time passed: %d.%06d s\n", (int)diff.tv_sec, (int)diff.tv_usec);
+
+ osmo_timers_prepare();
+ osmo_timers_update();
+}
+
+void fake_time_start()
+{
+ struct timespec *clock_override;
+
+ /* osmo_fsm uses osmo_gettimeofday(). To affect FSM timeouts, we need osmo_gettimeofday_override. */
+ osmo_gettimeofday_override_time = fake_time_start_time;
+ osmo_gettimeofday_override = true;
+
+ /* Penalty timers use osmo_clock_gettime(CLOCK_MONOTONIC). To affect these timeouts, we need
+ * osmo_gettimeofday_override. */
+ clock_override = osmo_clock_override_gettimespec(CLOCK_MONOTONIC);
+ OSMO_ASSERT(clock_override);
+ clock_override->tv_sec = fake_time_start_time.tv_sec;
+ clock_override->tv_nsec = fake_time_start_time.tv_usec * 1000;
+ osmo_clock_override_enable(CLOCK_MONOTONIC, true);
+ fake_time_passes(0, 0);
+}
-static void gen_meas_rep(struct gsm_lchan *lchan)
+static void gen_meas_rep(struct gsm_lchan *lchan,
+ uint8_t bs_power_db, uint8_t rxlev, uint8_t rxqual, uint8_t ta,
+ int neighbors_count, struct neighbor_meas *neighbors)
{
struct msgb *msg = msgb_alloc_headroom(256, 64, "RSL");
struct abis_rsl_dchan_hdr *dh;
- uint8_t chan_nr = gsm_lchan2chan_nr(lchan);
uint8_t ulm[3], l1i[2], *buf;
struct gsm48_hdr *gh;
struct gsm48_meas_res *mr;
+ int chan_nr = gsm_lchan2chan_nr(lchan, true);
+ OSMO_ASSERT(chan_nr >= 0);
dh = (struct abis_rsl_dchan_hdr *) msgb_put(msg, sizeof(*dh));
dh->c.msg_discr = ABIS_RSL_MDISC_DED_CHAN;
@@ -105,10 +155,10 @@ static void gen_meas_rep(struct gsm_lchan *lchan)
ulm[2] = (meas_ul_rxqual << 3) | meas_ul_rxqual;
msgb_tlv_put(msg, RSL_IE_UPLINK_MEAS, sizeof(ulm), ulm);
- msgb_tv_put(msg, RSL_IE_BS_POWER, meas_tx_power_bs);
+ msgb_tv_put(msg, RSL_IE_BS_POWER, (bs_power_db / 2) & 0xf);
l1i[0] = 0;
- l1i[1] = meas_ta_ms;
+ l1i[1] = ta;
msgb_tv_fixed_put(msg, RSL_IE_L1_INFO, sizeof(l1i), l1i);
buf = msgb_put(msg, 3);
@@ -123,71 +173,94 @@ static void gen_meas_rep(struct gsm_lchan *lchan)
gh->msg_type = GSM48_MT_RR_MEAS_REP;
/* measurement results */
- mr->rxlev_full = meas_dl_rxlev;
- mr->rxlev_sub = meas_dl_rxlev;
- mr->rxqual_full = meas_dl_rxqual;
- mr->rxqual_sub = meas_dl_rxqual;
+ mr->rxlev_full = rxlev;
+ mr->rxlev_sub = rxlev;
+ mr->rxqual_full = rxqual;
+ mr->rxqual_sub = rxqual;
mr->dtx_used = meas_dtx_ms;
mr->ba_used = meas_rep_ba;
- mr->meas_valid = !meas_valid; /* 0 = valid */
- if (meas_rep_valid) {
- mr->no_nc_n_hi = meas_num_nc >> 2;
- mr->no_nc_n_lo = meas_num_nc & 3;
- } else {
- /* no results for serving cells */
- mr->no_nc_n_hi = 1;
- mr->no_nc_n_lo = 3;
- }
- mr->rxlev_nc1 = meas_rxlev_nc[0];
- mr->rxlev_nc2_hi = meas_rxlev_nc[1] >> 1;
- mr->rxlev_nc2_lo = meas_rxlev_nc[1] & 1;
- mr->rxlev_nc3_hi = meas_rxlev_nc[2] >> 2;
- mr->rxlev_nc3_lo = meas_rxlev_nc[2] & 3;
- mr->rxlev_nc4_hi = meas_rxlev_nc[3] >> 3;
- mr->rxlev_nc4_lo = meas_rxlev_nc[3] & 7;
- mr->rxlev_nc5_hi = meas_rxlev_nc[4] >> 4;
- mr->rxlev_nc5_lo = meas_rxlev_nc[4] & 15;
- mr->rxlev_nc6_hi = meas_rxlev_nc[5] >> 5;
- mr->rxlev_nc6_lo = meas_rxlev_nc[5] & 31;
- mr->bsic_nc1_hi = meas_bsic_nc[0] >> 3;
- mr->bsic_nc1_lo = meas_bsic_nc[0] & 7;
- mr->bsic_nc2_hi = meas_bsic_nc[1] >> 4;
- mr->bsic_nc2_lo = meas_bsic_nc[1] & 15;
- mr->bsic_nc3_hi = meas_bsic_nc[2] >> 5;
- mr->bsic_nc3_lo = meas_bsic_nc[2] & 31;
- mr->bsic_nc4 = meas_bsic_nc[3];
- mr->bsic_nc5 = meas_bsic_nc[4];
- mr->bsic_nc6 = meas_bsic_nc[5];
- mr->bcch_f_nc1 = meas_bcch_f_nc[0];
- mr->bcch_f_nc2 = meas_bcch_f_nc[1];
- mr->bcch_f_nc3 = meas_bcch_f_nc[2];
- mr->bcch_f_nc4 = meas_bcch_f_nc[3];
- mr->bcch_f_nc5_hi = meas_bcch_f_nc[4] >> 1;
- mr->bcch_f_nc5_lo = meas_bcch_f_nc[4] & 1;
- mr->bcch_f_nc6_hi = meas_bcch_f_nc[5] >> 2;
- mr->bcch_f_nc6_lo = meas_bcch_f_nc[5] & 3;
-
- msg->dst = lchan->ts->trx->bts->c0->rsl_link;
+ mr->meas_valid = 0; /* 0 = valid */
+ mr->no_nc_n_hi = neighbors_count >> 2;
+ mr->no_nc_n_lo = neighbors_count & 3;
+
+ mr->rxlev_nc1 = neighbors[0].rxlev;
+ mr->rxlev_nc2_hi = neighbors[1].rxlev >> 1;
+ mr->rxlev_nc2_lo = neighbors[1].rxlev & 1;
+ mr->rxlev_nc3_hi = neighbors[2].rxlev >> 2;
+ mr->rxlev_nc3_lo = neighbors[2].rxlev & 3;
+ mr->rxlev_nc4_hi = neighbors[3].rxlev >> 3;
+ mr->rxlev_nc4_lo = neighbors[3].rxlev & 7;
+ mr->rxlev_nc5_hi = neighbors[4].rxlev >> 4;
+ mr->rxlev_nc5_lo = neighbors[4].rxlev & 15;
+ mr->rxlev_nc6_hi = neighbors[5].rxlev >> 5;
+ mr->rxlev_nc6_lo = neighbors[5].rxlev & 31;
+ mr->bsic_nc1_hi = neighbors[0].bsic >> 3;
+ mr->bsic_nc1_lo = neighbors[0].bsic & 7;
+ mr->bsic_nc2_hi = neighbors[1].bsic >> 4;
+ mr->bsic_nc2_lo = neighbors[1].bsic & 15;
+ mr->bsic_nc3_hi = neighbors[2].bsic >> 5;
+ mr->bsic_nc3_lo = neighbors[2].bsic & 31;
+ mr->bsic_nc4 = neighbors[3].bsic;
+ mr->bsic_nc5 = neighbors[4].bsic;
+ mr->bsic_nc6 = neighbors[5].bsic;
+ mr->bcch_f_nc1 = neighbors[0].bcch_f;
+ mr->bcch_f_nc2 = neighbors[1].bcch_f;
+ mr->bcch_f_nc3 = neighbors[2].bcch_f;
+ mr->bcch_f_nc4 = neighbors[3].bcch_f;
+ mr->bcch_f_nc5_hi = neighbors[4].bcch_f >> 1;
+ mr->bcch_f_nc5_lo = neighbors[4].bcch_f & 1;
+ mr->bcch_f_nc6_hi = neighbors[5].bcch_f >> 2;
+ mr->bcch_f_nc6_lo = neighbors[5].bcch_f & 3;
+
+ msg->dst = rsl_chan_link(lchan);
msg->l2h = (unsigned char *)dh;
msg->l3h = (unsigned char *)gh;
abis_rsl_rcvmsg(msg);
}
-static struct gsm_bts *create_bts(int arfcn)
+enum gsm_phys_chan_config pchan_from_str(const char *str)
{
+ enum gsm_phys_chan_config pchan;
+ if (!strcmp(str, "dyn"))
+ return GSM_PCHAN_OSMO_DYN;
+ if (!strcmp(str, "c+s4"))
+ return GSM_PCHAN_CCCH_SDCCH4;
+ if (!strcmp(str, "-"))
+ return GSM_PCHAN_NONE;
+ pchan = gsm_pchan_parse(str);
+ if (pchan < 0) {
+ fprintf(stderr, "Invalid timeslot pchan type: %s\n", str);
+ exit(1);
+ }
+ return pchan;
+}
+
+const char * const bts_default_ts[] = {
+ "c+s4", "TCH/F", "TCH/F", "TCH/F", "TCH/F", "TCH/H", "TCH/H", "-",
+};
+
+static struct gsm_bts *_create_bts(int num_trx, const char * const *ts_args, int ts_args_count)
+{
+ static int arfcn = 870;
+ static int ci = 0;
struct gsm_bts *bts;
struct e1inp_sign_link *rsl_link;
int i;
+ int trx_i;
+ struct gsm_bts_trx *trx;
+
+ fprintf(stderr, "- Creating BTS %d, %d TRX\n", bsc_gsmnet->num_bts, num_trx);
- bts = bsc_bts_alloc_register(bsc_gsmnet, GSM_BTS_TYPE_UNKNOWN, 0x3f);
+ bts = bsc_bts_alloc_register(bsc_gsmnet, GSM_BTS_TYPE_UNKNOWN, HARDCODED_BSIC);
if (!bts) {
- printf("No resource for bts1\n");
+ fprintf(stderr, "No resource for bts1\n");
return NULL;
}
- bts->location_area_code = 23;
- bts->c0->arfcn = arfcn;
+ bts->location_area_code = 0x0017;
+ bts->cell_identity = ci++;
+ bts->c0->arfcn = arfcn++;
bts->codec.efr = 1;
bts->codec.hr = 1;
@@ -195,31 +268,140 @@ static struct gsm_bts *create_bts(int arfcn)
rsl_link = talloc_zero(ctx, struct e1inp_sign_link);
rsl_link->trx = bts->c0;
- bts->c0->rsl_link = rsl_link;
-
- bts->c0->mo.nm_state.operational = NM_OPSTATE_ENABLED;
- bts->c0->mo.nm_state.availability = NM_AVSTATE_OK;
- bts->c0->mo.nm_state.administrative = NM_STATE_UNLOCKED;
- bts->c0->bb_transc.mo.nm_state.operational = NM_OPSTATE_ENABLED;
- bts->c0->bb_transc.mo.nm_state.availability = NM_AVSTATE_OK;
- bts->c0->bb_transc.mo.nm_state.administrative = NM_STATE_UNLOCKED;
-
- /* 4 full rate and 4 half rate channels */
- for (i = 1; i <= 6; i++) {
- bts->c0->ts[i].pchan_from_config = (i < 5) ? GSM_PCHAN_TCH_F : GSM_PCHAN_TCH_H;
- bts->c0->ts[i].mo.nm_state.operational = NM_OPSTATE_ENABLED;
- bts->c0->ts[i].mo.nm_state.availability = NM_AVSTATE_OK;
- bts->c0->ts[i].mo.nm_state.administrative = NM_STATE_UNLOCKED;
+ bts->c0->rsl_link_primary = rsl_link;
+
+ for (trx_i = 0; trx_i < num_trx; trx_i++) {
+ while (!(trx = gsm_bts_trx_num(bts, trx_i)))
+ gsm_bts_trx_alloc(bts);
+
+ trx->mo.nm_state.operational = NM_OPSTATE_ENABLED;
+ trx->mo.nm_state.availability = NM_AVSTATE_OK;
+ trx->mo.nm_state.administrative = NM_STATE_UNLOCKED;
+ trx->bb_transc.mo.nm_state.operational = NM_OPSTATE_ENABLED;
+ trx->bb_transc.mo.nm_state.availability = NM_AVSTATE_OK;
+ trx->bb_transc.mo.nm_state.administrative = NM_STATE_UNLOCKED;
+
+ /* 4 full rate and 4 half rate channels */
+ for (i = 0; i < 8; i++) {
+ int arg_i = trx_i * 8 + i;
+ const char *ts_arg;
+ if (arg_i >= ts_args_count)
+ ts_arg = bts_default_ts[i];
+ else
+ ts_arg = ts_args[arg_i];
+ fprintf(stderr, "\t%s", ts_arg);
+ trx->ts[i].pchan_from_config = pchan_from_str(ts_arg);
+ if (trx->ts[i].pchan_from_config == GSM_PCHAN_NONE)
+ continue;
+ trx->ts[i].mo.nm_state.operational = NM_OPSTATE_ENABLED;
+ trx->ts[i].mo.nm_state.availability = NM_AVSTATE_OK;
+ trx->ts[i].mo.nm_state.administrative = NM_STATE_UNLOCKED;
+ }
+ fprintf(stderr, "\n");
+
+ for (i = 0; i < ARRAY_SIZE(trx->ts); i++) {
+ /* make sure ts->lchans[] get initialized */
+ osmo_fsm_inst_dispatch(trx->ts[i].fi, TS_EV_RSL_READY, 0);
+ osmo_fsm_inst_dispatch(trx->ts[i].fi, TS_EV_OML_READY, 0);
+
+ /* Unused dyn TS start out as used for PDCH */
+ switch (trx->ts[i].pchan_on_init) {
+ case GSM_PCHAN_OSMO_DYN:
+ case GSM_PCHAN_TCH_F_PDCH:
+ ts_set_pchan_is(&trx->ts[i], GSM_PCHAN_PDCH);
+ break;
+ default:
+ break;
+ }
+ }
}
- for (i = 0; i < ARRAY_SIZE(bts->c0->ts); i++) {
- /* make sure ts->lchans[] get initialized */
- osmo_fsm_inst_dispatch(bts->c0->ts[i].fi, TS_EV_RSL_READY, 0);
- osmo_fsm_inst_dispatch(bts->c0->ts[i].fi, TS_EV_OML_READY, 0);
+ for (i = 0; i < bsc_gsmnet->num_bts; i++) {
+ if (gsm_generate_si(gsm_bts_num(bsc_gsmnet, i), SYSINFO_TYPE_2) <= 0)
+ fprintf(stderr, "Error generating SI2\n");
}
return bts;
}
+char *lchans_use_str(struct gsm_bts_trx_ts *ts, const char *established_prefix, char established_char)
+{
+ char state_chars[8] = { 0 };
+ struct gsm_lchan *lchan;
+ bool any_lchans_established = false;
+ bool any_lchans_in_use = false;
+ ts_for_n_lchans(lchan, ts, ts->max_primary_lchans) {
+ char state_char;
+ if (lchan_state_is(lchan, LCHAN_ST_UNUSED)) {
+ state_char = '-';
+ } else {
+ any_lchans_in_use = true;
+ if (lchan_state_is(lchan, LCHAN_ST_ESTABLISHED)) {
+ any_lchans_established = true;
+ state_char = established_char;
+ } else {
+ state_char = '!';
+ }
+ }
+ state_chars[lchan->nr] = state_char;
+ }
+ if (!any_lchans_in_use)
+ return "-";
+ if (!any_lchans_established)
+ established_prefix = "";
+ return talloc_asprintf(OTC_SELECT, "%s%s", established_prefix, state_chars);
+}
+
+const char *ts_use_str(struct gsm_bts_trx_ts *ts)
+{
+ switch (ts->pchan_is) {
+ case GSM_PCHAN_CCCH_SDCCH4:
+ return "c+s4";
+
+ case GSM_PCHAN_NONE:
+ return "-";
+
+ case GSM_PCHAN_TCH_F:
+ return lchans_use_str(ts, "TCH/", 'F');
+
+ case GSM_PCHAN_TCH_H:
+ return lchans_use_str(ts, "TCH/", 'H');
+
+ default:
+ return gsm_pchan_name(ts->pchan_is);
+ }
+}
+
+bool _expect_ts_use(struct gsm_bts *bts, struct gsm_bts_trx *trx, const char * const *ts_use)
+{
+ int i;
+ int mismatching_ts = -1;
+
+ fprintf(stderr, "bts %d trx %d: expect:", bts->nr, trx->nr);
+ for (i = 0; i < 8; i++)
+ fprintf(stderr, "\t%s", ts_use[i]);
+ fprintf(stderr, "\nbts %d trx %d: got:", bts->nr, trx->nr);
+
+ for (i = 0; i < 8; i++) {
+ struct gsm_bts_trx_ts *ts = &trx->ts[i];
+ const char *use = ts_use_str(ts);
+
+ fprintf(stderr, "\t%s", use);
+
+ if (!strcmp(ts_use[i], "*"))
+ continue;
+ if (strcasecmp(ts_use[i], use) && mismatching_ts < 0)
+ mismatching_ts = i;
+ }
+ fprintf(stderr, "\n");
+
+ if (mismatching_ts >= 0) {
+ fprintf(stderr, "Test failed: mismatching TS use in bts %d trx %d ts %d\n",
+ bts->nr, trx->nr, mismatching_ts);
+ return false;
+ }
+ return true;
+}
+
void create_conn(struct gsm_lchan *lchan)
{
static unsigned int next_imsi = 0;
@@ -246,39 +428,45 @@ void create_conn(struct gsm_lchan *lchan)
snprintf(imsi, sizeof(imsi), "%06u", next_imsi);
lchan->conn->bsub = bsc_subscr_find_or_create_by_imsi(net->bsc_subscribers, imsi, BSUB_USE_CONN);
+ /* Set RTP data that the MSC normally would have sent */
+ OSMO_STRLCPY_ARRAY(conn->user_plane.msc_assigned_rtp_addr, "1.2.3.4");
+ conn->user_plane.msc_assigned_rtp_port = 1234;
+
/* kick the FSM from INIT through to the ACTIVE state */
osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_MO_COMPL_L3, NULL);
osmo_fsm_inst_dispatch(conn->fi, GSCON_EV_A_CONN_CFM, NULL);
}
-/* create lchan */
-struct gsm_lchan *create_lchan(struct gsm_bts *bts, int full_rate, char *codec)
+struct gsm_lchan *lchan_act(struct gsm_lchan *lchan, int full_rate, const char *codec)
{
- struct gsm_lchan *lchan;
-
- lchan = lchan_select_by_type(bts, (full_rate) ? GSM_LCHAN_TCH_F : GSM_LCHAN_TCH_H);
- if (!lchan) {
- printf("No resource for lchan\n");
- exit(EXIT_FAILURE);
- }
-
/* serious hack into osmo_fsm */
lchan->fi->state = LCHAN_ST_ESTABLISHED;
lchan->ts->fi->state = TS_ST_IN_USE;
+ lchan->type = full_rate ? GSM_LCHAN_TCH_F : GSM_LCHAN_TCH_H;
+ /* Fake osmo_mgcpc_ep_ci to indicate that the lchan is used for voice */
+ lchan->mgw_endpoint_ci_bts = (void*)1;
+
+ if (lchan->ts->pchan_on_init == GSM_PCHAN_OSMO_DYN)
+ ts_set_pchan_is(lchan->ts, full_rate ? GSM_PCHAN_TCH_F : GSM_PCHAN_TCH_H);
+ if (lchan->ts->pchan_on_init == GSM_PCHAN_TCH_F_PDCH) {
+ OSMO_ASSERT(full_rate);
+ ts_set_pchan_is(lchan->ts, GSM_PCHAN_TCH_F);
+ }
+
LOG_LCHAN(lchan, LOGL_DEBUG, "activated by handover_test.c\n");
create_conn(lchan);
if (!strcasecmp(codec, "FR") && full_rate)
- lchan->tch_mode = GSM48_CMODE_SPEECH_V1;
+ lchan->current_ch_mode_rate.chan_mode = GSM48_CMODE_SPEECH_V1;
else if (!strcasecmp(codec, "HR") && !full_rate)
- lchan->tch_mode = GSM48_CMODE_SPEECH_V1;
+ lchan->current_ch_mode_rate.chan_mode = GSM48_CMODE_SPEECH_V1;
else if (!strcasecmp(codec, "EFR") && full_rate)
- lchan->tch_mode = GSM48_CMODE_SPEECH_EFR;
+ lchan->current_ch_mode_rate.chan_mode = GSM48_CMODE_SPEECH_EFR;
else if (!strcasecmp(codec, "AMR")) {
- lchan->tch_mode = GSM48_CMODE_SPEECH_AMR;
- lchan->activate.info.s15_s0 = 0x0002;
+ lchan->current_ch_mode_rate.chan_mode = GSM48_CMODE_SPEECH_AMR;
+ lchan->current_ch_mode_rate.s15_s0 = 0x0002;
} else {
- printf("Given codec unknown\n");
+ fprintf(stderr, "Given codec unknown\n");
exit(EXIT_FAILURE);
}
@@ -293,63 +481,128 @@ struct gsm_lchan *create_lchan(struct gsm_bts *bts, int full_rate, char *codec)
.len = 5,
};
+ chan_counts_ts_update(lchan->ts);
+
return lchan;
}
-/* parse channel request */
+struct gsm_lchan *create_lchan(struct gsm_bts *bts, int full_rate, const char *codec)
+{
+ struct gsm_lchan *lchan;
-static int got_chan_req = 0;
-static struct gsm_lchan *chan_req_lchan = NULL;
+ lchan = lchan_select_by_type(bts, (full_rate) ? GSM_LCHAN_TCH_F : GSM_LCHAN_TCH_H,
+ SELECT_FOR_HANDOVER, NULL);
+ if (!lchan) {
+ fprintf(stderr, "No resource for lchan\n");
+ exit(EXIT_FAILURE);
+ }
+
+ return lchan_act(lchan, full_rate, codec);
+}
-static int parse_chan_act(struct gsm_lchan *lchan, uint8_t *data)
+static void lchan_release_ack(struct gsm_lchan *lchan)
{
- chan_req_lchan = lchan;
- return 0;
+ if (!lchan->fi || lchan->fi->state != LCHAN_ST_WAIT_BEFORE_RF_RELEASE)
+ return;
+ /* don't wait before release */
+ osmo_fsm_inst_state_chg(lchan->fi, LCHAN_ST_WAIT_RF_RELEASE_ACK, 0, 0);
+ if (lchan->fi->state == LCHAN_ST_UNUSED)
+ return;
+ /* ack the release */
+ osmo_fsm_inst_dispatch(lchan->fi, LCHAN_EV_RSL_RF_CHAN_REL_ACK, 0);
}
-static int parse_chan_rel(struct gsm_lchan *lchan, uint8_t *data)
+static void lchan_clear(struct gsm_lchan *lchan)
{
- chan_req_lchan = lchan;
- return 0;
+ lchan_release(lchan, true, false, 0, NULL);
+ lchan_release_ack(lchan);
}
-/* parse handover request */
+static void ts_clear(struct gsm_bts_trx_ts *ts)
+{
+ struct gsm_lchan *lchan;
-static int got_ho_req = 0;
-static struct gsm_lchan *ho_req_lchan = NULL;
+ ts_for_n_lchans(lchan, ts, ts->max_lchans_possible) {
+ if (lchan_state_is(lchan, LCHAN_ST_UNUSED))
+ continue;
+ lchan_clear(lchan);
+ }
+ chan_counts_ts_update(ts);
+}
-static int parse_ho_command(struct gsm_lchan *lchan, uint8_t *data, int len)
+bool _set_ts_use(struct gsm_bts *bts, struct gsm_bts_trx *trx, const char * const *ts_use)
{
- struct gsm48_hdr *gh = (struct gsm48_hdr *) data;
- struct gsm48_ho_cmd *ho = (struct gsm48_ho_cmd *) gh->data;
- int arfcn;
- struct gsm_bts *neigh;
-
- switch (gh->msg_type) {
- case GSM48_MT_RR_HANDO_CMD:
- arfcn = (ho->cell_desc.arfcn_hi << 8) | ho->cell_desc.arfcn_lo;
-
- /* look up trx. since every dummy bts uses different arfcn and
- * only one trx, it is simple */
- llist_for_each_entry(neigh, &bsc_gsmnet->bts_list, list) {
- if (neigh->c0->arfcn != arfcn)
- continue;
- ho_req_lchan = lchan;
- return 0;
+ int i;
+
+ fprintf(stderr, "Setting TS use:");
+ for (i = 0; i < 8; i++)
+ fprintf(stderr, "\t%s", ts_use[i]);
+ fprintf(stderr, "\n");
+
+ for (i = 0; i < 8; i++) {
+ struct gsm_bts_trx_ts *ts = &trx->ts[i];
+ const char *want_use = ts_use[i];
+ const char *is_use = ts_use_str(ts);
+
+ if (!strcmp(want_use, "*"))
+ continue;
+
+ /* If it is already as desired, don't change anything */
+ if (!strcasecmp(want_use, is_use))
+ continue;
+
+ if (!strcasecmp(want_use, "tch/f")) {
+ if (!ts_is_capable_of_pchan(ts, GSM_PCHAN_TCH_F)) {
+ fprintf(stderr, "Error: bts %d trx %d ts %d cannot be used as TCH/F\n",
+ bts->nr, trx->nr, i);
+ return false;
+ }
+ ts_clear(ts);
+
+ lchan_act(&ts->lchan[0], true, codec_tch_f ? : "AMR");
+ } else if (!strcasecmp(want_use, "tch/h-")
+ || !strcasecmp(want_use, "tch/hh")
+ || !strcasecmp(want_use, "tch/-h")) {
+ bool act[2];
+ int j;
+
+ if (!ts_is_capable_of_pchan(ts, GSM_PCHAN_TCH_H)) {
+ fprintf(stderr, "Error: bts %d trx %d ts %d cannot be used as TCH/H\n",
+ bts->nr, trx->nr, i);
+ return false;
+ }
+
+ if (ts->pchan_is != GSM_PCHAN_TCH_H)
+ ts_clear(ts);
+
+ act[0] = (want_use[4] == 'h' || want_use[4] == 'H');
+ act[1] = (want_use[5] == 'h' || want_use[5] == 'H');
+
+ for (j = 0; j < 2; j++) {
+ if (lchan_state_is(&ts->lchan[j], LCHAN_ST_UNUSED)) {
+ if (act[j])
+ lchan_act(&ts->lchan[j], false, codec_tch_h ? : "AMR");
+ } else if (!act[j])
+ lchan_clear(&ts->lchan[j]);
+ }
+ } else if (!strcmp(want_use, "-") || !strcasecmp(want_use, "PDCH")) {
+ ts_clear(ts);
}
- break;
- case GSM48_MT_RR_ASS_CMD:
- ho_req_lchan = lchan;
- return 0;
- break;
- default:
- fprintf(stderr, "Error, expecting HO or AS command\n");
- return -EINVAL;
}
-
- return -1;
+ return true;
}
+/* parse channel request */
+
+static struct gsm_lchan *new_chan_req = NULL;
+static struct gsm_lchan *last_chan_req = NULL;
+
+static struct gsm_lchan *new_ho_cmd = NULL;
+static struct gsm_lchan *last_ho_cmd = NULL;
+
+static struct gsm_lchan *new_as_cmd = NULL;
+static struct gsm_lchan *last_as_cmd = NULL;
+
/* send channel activation ack */
static void send_chan_act_ack(struct gsm_lchan *lchan, int act)
{
@@ -360,20 +613,60 @@ static void send_chan_act_ack(struct gsm_lchan *lchan, int act)
dh->c.msg_discr = ABIS_RSL_MDISC_DED_CHAN;
dh->c.msg_type = (act) ? RSL_MT_CHAN_ACTIV_ACK : RSL_MT_RF_CHAN_REL_ACK;
dh->ie_chan = RSL_IE_CHAN_NR;
- dh->chan_nr = gsm_lchan2chan_nr(lchan);
+ dh->chan_nr = gsm_lchan2chan_nr(lchan, true);
- msg->dst = lchan->ts->trx->bts->c0->rsl_link;
+ msg->dst = rsl_chan_link(lchan);
msg->l2h = (unsigned char *)dh;
abis_rsl_rcvmsg(msg);
}
+/* Send RR Assignment Complete for SAPI[0] */
+static void send_assignment_complete(struct gsm_lchan *lchan)
+{
+ struct msgb *msg = msgb_alloc_headroom(256, 64, "RSL");
+ struct abis_rsl_rll_hdr *rh;
+ uint8_t chan_nr = gsm_lchan2chan_nr(lchan, true);
+ uint8_t *buf;
+ struct gsm48_hdr *gh;
+ struct gsm48_ho_cpl *hc;
+
+ fprintf(stderr, "- Send RR Assignment Complete for %s\n", gsm_lchan_name(lchan));
+
+ rh = (struct abis_rsl_rll_hdr *) msgb_put(msg, sizeof(*rh));
+ rh->c.msg_discr = ABIS_RSL_MDISC_RLL;
+ rh->c.msg_type = RSL_MT_DATA_IND;
+ rh->ie_chan = RSL_IE_CHAN_NR;
+ rh->chan_nr = chan_nr;
+ rh->ie_link_id = RSL_IE_LINK_IDENT;
+ rh->link_id = 0x00;
+
+ buf = msgb_put(msg, 3);
+ buf[0] = RSL_IE_L3_INFO;
+ buf[1] = (sizeof(*gh) + sizeof(*hc)) >> 8;
+ buf[2] = (sizeof(*gh) + sizeof(*hc)) & 0xff;
+
+ gh = (struct gsm48_hdr *) msgb_put(msg, sizeof(*gh));
+ hc = (struct gsm48_ho_cpl *) msgb_put(msg, sizeof(*hc));
+
+ gh->proto_discr = GSM48_PDISC_RR;
+ gh->msg_type = GSM48_MT_RR_ASS_COMPL;
+
+ msg->dst = rsl_chan_link(lchan);
+ msg->l2h = (unsigned char *)rh;
+ msg->l3h = (unsigned char *)gh;
+
+ abis_rsl_rcvmsg(msg);
+}
+
/* Send RLL Est Ind for SAPI[0] */
static void send_est_ind(struct gsm_lchan *lchan)
{
struct msgb *msg = msgb_alloc_headroom(256, 64, "RSL");
struct abis_rsl_rll_hdr *rh;
- uint8_t chan_nr = gsm_lchan2chan_nr(lchan);
+ uint8_t chan_nr = gsm_lchan2chan_nr(lchan, true);
+
+ fprintf(stderr, "- Send EST IND for %s\n", gsm_lchan_name(lchan));
rh = (struct abis_rsl_rll_hdr *) msgb_put(msg, sizeof(*rh));
rh->c.msg_discr = ABIS_RSL_MDISC_RLL;
@@ -383,24 +676,51 @@ static void send_est_ind(struct gsm_lchan *lchan)
rh->ie_link_id = RSL_IE_LINK_IDENT;
rh->link_id = 0x00;
- msg->dst = lchan->ts->trx->bts->c0->rsl_link;
+ msg->dst = rsl_chan_link(lchan);
+ msg->l2h = (unsigned char *)rh;
+
+ abis_rsl_rcvmsg(msg);
+}
+
+static void send_ho_detect(struct gsm_lchan *lchan)
+{
+ struct msgb *msg = msgb_alloc_headroom(256, 64, "RSL");
+ struct abis_rsl_rll_hdr *rh;
+ uint8_t chan_nr = gsm_lchan2chan_nr(lchan, true);
+
+ fprintf(stderr, "- Send HO DETECT for %s\n", gsm_lchan_name(lchan));
+
+ rh = (struct abis_rsl_rll_hdr *) msgb_put(msg, sizeof(*rh));
+ rh->c.msg_discr = ABIS_RSL_MDISC_DED_CHAN;
+ rh->c.msg_type = RSL_MT_HANDO_DET;
+ rh->ie_chan = RSL_IE_CHAN_NR;
+ rh->chan_nr = chan_nr;
+ rh->ie_link_id = RSL_IE_LINK_IDENT;
+ rh->link_id = 0x00;
+
+ msg->dst = rsl_chan_link(lchan);
msg->l2h = (unsigned char *)rh;
abis_rsl_rcvmsg(msg);
+
+ send_est_ind(lchan);
+ osmo_fsm_inst_dispatch(lchan->fi, LCHAN_EV_RTP_READY, 0);
+
}
-/* send handover complete */
static void send_ho_complete(struct gsm_lchan *lchan, bool success)
{
struct msgb *msg = msgb_alloc_headroom(256, 64, "RSL");
struct abis_rsl_rll_hdr *rh;
- uint8_t chan_nr = gsm_lchan2chan_nr(lchan);
+ uint8_t chan_nr = gsm_lchan2chan_nr(lchan, true);
uint8_t *buf;
struct gsm48_hdr *gh;
struct gsm48_ho_cpl *hc;
- send_est_ind(lchan);
- osmo_fsm_inst_dispatch(lchan->fi, LCHAN_EV_RTP_READY, 0);
+ if (success)
+ fprintf(stderr, "- Send HO COMPLETE for %s\n", gsm_lchan_name(lchan));
+ else
+ fprintf(stderr, "- Send HO FAIL to %s\n", gsm_lchan_name(lchan));
rh = (struct abis_rsl_rll_hdr *) msgb_put(msg, sizeof(*rh));
rh->c.msg_discr = ABIS_RSL_MDISC_RLL;
@@ -422,7 +742,7 @@ static void send_ho_complete(struct gsm_lchan *lchan, bool success)
gh->msg_type =
success ? GSM48_MT_RR_HANDO_COMPL : GSM48_MT_RR_HANDO_FAIL;
- msg->dst = lchan->ts->trx->bts->c0->rsl_link;
+ msg->dst = rsl_chan_link(lchan);
msg->l2h = (unsigned char *)rh;
msg->l3h = (unsigned char *)gh;
@@ -438,910 +758,797 @@ int __wrap_abis_rsl_sendmsg(struct msgb *msg)
struct e1inp_sign_link *sign_link = msg->dst;
int rc;
struct gsm_lchan *lchan = rsl_lchan_lookup(sign_link->trx, dh->chan_nr, &rc);
+ struct gsm_lchan *other_lchan;
+ struct gsm48_hdr *gh;
if (rc) {
- printf("rsl_lchan_lookup() failed\n");
+ fprintf(stderr, "rsl_lchan_lookup() failed\n");
exit(1);
}
switch (dh->c.msg_type) {
case RSL_MT_CHAN_ACTIV:
- rc = parse_chan_act(lchan, dh->data);
- if (rc == 0)
- got_chan_req = 1;
+ if (new_chan_req) {
+ fprintf(stderr, "Test script is erratic: a channel is requested"
+ " while a previous channel request is still unhandled\n");
+ exit(1);
+ }
+ new_chan_req = lchan;
break;
case RSL_MT_RF_CHAN_REL:
- rc = parse_chan_rel(lchan, dh->data);
- if (rc == 0)
- send_chan_act_ack(chan_req_lchan, 0);
+ send_chan_act_ack(lchan, 0);
+
+ /* send dyn TS back to PDCH if unused */
+ switch (lchan->ts->pchan_on_init) {
+ case GSM_PCHAN_OSMO_DYN:
+ case GSM_PCHAN_TCH_F_PDCH:
+ switch (lchan->ts->pchan_is) {
+ case GSM_PCHAN_TCH_H:
+ other_lchan = &lchan->ts->lchan[
+ (lchan == &lchan->ts->lchan[0])?
+ 1 : 0];
+ if (lchan_state_is(other_lchan, LCHAN_ST_ESTABLISHED))
+ break;
+ /* else fall thru */
+ case GSM_PCHAN_TCH_F:
+ ts_set_pchan_is(lchan->ts, GSM_PCHAN_PDCH);
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+
break;
case RSL_MT_DATA_REQ:
- rc = parse_ho_command(lchan, msg->l3h, msgb_l3len(msg));
- if (rc == 0)
- got_ho_req = 1;
+ gh = (struct gsm48_hdr*)msg->l3h;
+ switch (gh->msg_type) {
+ case GSM48_MT_RR_HANDO_CMD:
+ if (new_ho_cmd || new_as_cmd) {
+ fprintf(stderr, "Test script is erratic: seen a Handover Command"
+ " while a previous Assignment or Handover Command is still unhandled\n");
+ exit(1);
+ }
+ new_ho_cmd = lchan;
+ break;
+ case GSM48_MT_RR_ASS_CMD:
+ if (new_ho_cmd || new_as_cmd) {
+ fprintf(stderr, "Test script is erratic: seen an Assignment Command"
+ " while a previous Assignment or Handover Command is still unhandled\n");
+ exit(1);
+ }
+ new_as_cmd = lchan;
+ break;
+ }
break;
case RSL_MT_IPAC_CRCX:
break;
case RSL_MT_DEACTIVATE_SACCH:
break;
default:
- printf("unknown rsl message=0x%x\n", dh->c.msg_type);
+ fprintf(stderr, "unknown rsl message=0x%x\n", dh->c.msg_type);
}
return 0;
}
-/* test cases */
+struct gsm_bts *bts_by_num_str(const char *num_str)
+{
+ struct gsm_bts *bts = gsm_bts_num(bsc_gsmnet, atoi(num_str));
+ OSMO_ASSERT(bts);
+ return bts;
+}
-static char *test_case_0[] = {
- "2",
+struct gsm_bts_trx *trx_by_num_str(struct gsm_bts *bts, const char *num_str)
+{
+ struct gsm_bts_trx *trx = gsm_bts_trx_num(bts, atoi(num_str));
+ OSMO_ASSERT(trx);
+ return trx;
+}
- "Stay in better cell\n\n"
- "There are many neighbor cells, but only the current cell is the best\n"
- "cell, so no handover is performed\n",
+#define LCHAN_ARGS "lchan <0-255> <0-255> <0-7> <0-7>"
+#define LCHAN_ARGS_DOC "identify an lchan\nBTS nr\nTRX nr\nTimeslot nr\nSubslot nr\n"
- "create-bts", "7",
- "create-ms", "0", "TCH/F", "AMR",
- "meas-rep", "0", "30","0",
- "6","0","20","1","21","2","18","3","20","4","23","5","19",
- "expect-no-chan",
- NULL
-};
+static struct gsm_lchan *parse_lchan_args(const char **argv)
+{
+ struct gsm_bts *bts = bts_by_num_str(argv[0]);
+ struct gsm_bts_trx *trx = trx_by_num_str(bts, argv[1]);
+ struct gsm_bts_trx_ts *ts = &trx->ts[atoi(argv[2])];
+ return &ts->lchan[atoi(argv[3])];
+}
-static char *test_case_1[] = {
- "2",
-
- "Handover to best better cell\n\n"
- "The best neighbor cell is selected\n",
-
- "create-bts", "7",
- "create-ms", "0", "TCH/F", "AMR",
- "meas-rep", "0", "10","0",
- "6","0","20","1","21","2","18","3","20","4","23","5","19",
- "expect-chan", "5", "1",
- "ack-chan",
- "expect-ho", "0", "1",
- "ho-complete",
- NULL
-};
+#define LCHAN_WILDCARD_ARGS "lchan (<0-255>|*) (<0-255>|*) (<0-7>|*) (<0-7>|*)"
+#define LCHAN_WILDCARD_ARGS_DOC "identify an lchan\nBTS nr\nall BTS\nTRX nr\nall BTS\nTimeslot nr\nall TS\nSubslot nr\nall subslots\n"
-static char *test_case_2[] = {
- "2",
-
- "Handover and Assignment must be enabled\n\n"
- "This test will start with disabled assignment and handover. A\n"
- "better neighbor cell (assignment enabled) will not be selected and \n"
- "also no assignment from TCH/H to TCH/F to improve quality. There\n"
- "will be no handover nor assignment. After enabling assignment on the\n"
- "current cell, the MS will assign to TCH/F. After enabling handover\n"
- "in the current cell, but disabling in the neighbor cell, handover\n"
- "will not be performed, until it is enabled in the neighbor cell too.\n",
-
- "create-bts", "2",
- "afs-rxlev-improve", "0", "5",
- "create-ms", "0", "TCH/H", "AMR",
- "as-enable", "0", "0",
- "ho-enable", "0", "0",
- "meas-rep", "0", "0","0", "1","0","30",
- "expect-no-chan",
- "as-enable", "0", "1",
- "meas-rep", "0", "0","0", "1","0","30",
- "expect-chan", "0", "1",
- "ack-chan",
- "expect-ho", "0", "5",
- "ho-complete",
- "ho-enable", "0", "1",
- "ho-enable", "1", "0",
- "meas-rep", "0", "0","0", "1","0","30",
- "expect-no-chan",
- "ho-enable", "1", "1",
- "meas-rep", "0", "0","0", "1","0","30",
- "expect-chan", "1", "1",
- "ack-chan",
- "expect-ho", "0", "1",
- "ho-complete",
- NULL
-};
+static void parse_lchan_wildcard_args(const char **argv, void (*cb)(struct gsm_lchan*, void*), void *cb_data)
+{
+ const char *bts_str = argv[0];
+ const char *trx_str = argv[1];
+ const char *ts_str = argv[2];
+ const char *ss_str = argv[3];
+ int bts_num = (strcmp(bts_str, "*") == 0)? -1 : atoi(bts_str);
+ int trx_num = (strcmp(trx_str, "*") == 0)? -1 : atoi(trx_str);
+ int ts_num = (strcmp(ts_str, "*") == 0)? -1 : atoi(ts_str);
+ int ss_num = (strcmp(ss_str, "*") == 0)? -1 : atoi(ss_str);
+
+ int bts_i;
+ int trx_i;
+ int ts_i;
+ int ss_i;
+
+ for (bts_i = ((bts_num == -1) ? 0 : bts_num);
+ bts_i < ((bts_num == -1) ? bsc_gsmnet->num_bts : bts_num + 1);
+ bts_i++) {
+ struct gsm_bts *bts = gsm_bts_num(bsc_gsmnet, bts_i);
+
+ for (trx_i = ((trx_num == -1) ? 0 : trx_num);
+ trx_i < ((trx_num == -1) ? bts->num_trx : trx_num + 1);
+ trx_i++) {
+ struct gsm_bts_trx *trx = gsm_bts_trx_num(bts, trx_i);
+
+ for (ts_i = ((ts_num == -1) ? 0 : ts_num);
+ ts_i < ((ts_num == -1) ? 8 : ts_num + 1);
+ ts_i++) {
+ struct gsm_bts_trx_ts *ts = &trx->ts[ts_i];
+
+ for (ss_i = ((ss_num == -1) ? 0 : ss_num);
+ ss_i < ((ss_num == -1) ? pchan_subslots(ts->pchan_is) : ss_num + 1);
+ ss_i++) {
+ cb(&ts->lchan[ss_i], cb_data);
+ }
+ }
+ }
+ }
+}
-static char *test_case_3[] = {
- "2",
-
- "Penalty timer must not run\n\n"
- "The MS will try to handover to a better cell, but this will fail.\n"
- "Even though the cell is still better, handover will not be performed\n"
- "due to penalty timer after handover failure\n",
-
- "create-bts", "2",
- "create-ms", "0", "TCH/F", "AMR",
- "meas-rep", "0", "20","0", "1","0","30",
- "expect-chan", "1", "1",
- "ack-chan",
- "expect-ho", "0", "1",
- "ho-failed",
- "meas-rep", "0", "20","0", "1","0","30",
- "expect-no-chan",
- NULL
-};
+static int vty_step = 1;
+
+#define VTY_ECHO() \
+ fprintf(stderr, "\n%d: %s\n", vty_step++, vty->buf)
+
+#define TS_USE " (TCH/F|TCH/H-|TCH/-H|TCH/HH|PDCH" \
+ "|tch/f|tch/h-|tch/-h|tch/hh|pdch" \
+ "|-|*)"
+#define TS_USE_DOC "'TCH/F': one FR call\n" \
+ "'TCH/H-': HR TS with first subslot used as TCH/H, other subslot unused\n" \
+ "'TCH/HH': HR TS with both subslots used as TCH/H\n" \
+ "'TCH/-H': HR TS with only second subslot used as TCH/H\n" \
+ "'PDCH': TS used for PDCH (e.g. unused dynamic TS)\n" \
+ "'tch/f': one FR call\n" \
+ "'tch/h-': HR TS with first subslot used as TCH/H, other subslot unused\n" \
+ "'tch/hh': HR TS with both subslots used as TCH/H\n" \
+ "'tch/-h': HR TS with only second subslot used as TCH/H\n" \
+ "'pdch': TS used for PDCH (e.g. unused dynamic TS)\n" \
+ "'-': TS unused\n" \
+ "'*': TS allowed to be in any state\n"
+
+DEFUN(create_n_bts, create_n_bts_cmd,
+ "create-n-bts <1-255>",
+ "Create a number of BTS with four TCH/F and four TCH/H timeslots\n"
+ "Number of BTS to create\n")
+{
+ int i;
+ int n = atoi(argv[0]);
+ VTY_ECHO();
+ for (i = 0; i < n; i++)
+ _create_bts(1, NULL, 0);
+ return CMD_SUCCESS;
+}
-static char *test_case_4[] = {
- "2",
-
- "TCH/H keeping with HR codec\n\n"
- "The MS is using half rate V1 codec, but the better cell is congested\n"
- "at TCH/H slots. As the congestion is removed, the handover takes\n"
- "place.\n",
-
- "create-bts", "2",
- "set-min-free", "1", "TCH/H", "4",
- "create-ms", "0", "TCH/H", "HR",
- "meas-rep", "0", "20","0", "1","0","30",
- "expect-no-chan",
- "set-min-free", "1", "TCH/H", "3",
- "meas-rep", "0", "20","0", "1","0","30",
- "expect-chan", "1", "5",
- "ack-chan",
- "expect-ho", "0", "5",
- "ho-complete",
- NULL
-};
+DEFUN(create_bts, create_bts_cmd,
+ "create-bts trx-count <1-255> timeslots .TS_CFG",
+ "Create a new BTS with specific timeslot configuration\n"
+ "Create N TRX in the new BTS\n"
+ "TRX count\n"
+ "Timeslot config\n"
+ "Timeslot types for 8 * trx-count, each being one of CCCH+SDCCH4|SDCCH8|TCH/F|TCH/H|TCH/F_TCH/H_SDCCH8_PDCH|...;"
+ " shorthands: cs+4 = CCCH+SDCCH4; dyn = TCH/F_TCH/H_SDCCH8_PDCH\n")
+{
+ int num_trx = atoi(argv[0]);
+ VTY_ECHO();
+ _create_bts(num_trx, argv + 1, argc - 1);
+ return CMD_SUCCESS;
+}
-static char *test_case_5[] = {
- "2",
-
- "TCH/F keeping with FR codec\n\n"
- "The MS is using full rate V1 codec, but the better cell is congested\n"
- "at TCH/F slots. As the congestion is removed, the handover takes\n"
- "place.\n",
-
- "create-bts", "2",
- "set-min-free", "1", "TCH/F", "4",
- "create-ms", "0", "TCH/F", "FR",
- "meas-rep", "0", "20","0", "1","0","30",
- "expect-no-chan",
- "set-min-free", "1", "TCH/F", "3",
- "meas-rep", "0", "20","0", "1","0","30",
- "expect-chan", "1", "1",
- "ack-chan",
- "expect-ho", "0", "1",
- "ho-complete",
- NULL
-};
+DEFUN(create_ms, create_ms_cmd,
+ "create-ms bts <0-999> (TCH/F|TCH/H) (AMR|HR|EFR)",
+ "Create an MS using the next free matching lchan on a given BTS\n"
+ "BTS index to subscribe on\n"
+ "lchan type to select\n"
+ "codec\n")
+{
+ const char *bts_nr_str = argv[0];
+ const char *tch_type = argv[1];
+ const char *codec = argv[2];
+ struct gsm_lchan *lchan;
+ VTY_ECHO();
+ fprintf(stderr, "- Creating mobile at BTS %s on "
+ "%s with %s codec\n", bts_nr_str, tch_type, codec);
+ lchan = create_lchan(bts_by_num_str(bts_nr_str),
+ !strcmp(tch_type, "TCH/F"), codec);
+ if (!lchan) {
+ fprintf(stderr, "Failed to create lchan!\n");
+ return CMD_WARNING;
+ }
+ fprintf(stderr, " * New MS is at %s\n", gsm_lchan_name(lchan));
+ return CMD_SUCCESS;
+}
-static char *test_case_6[] = {
- "2",
-
- "TCH/F keeping with EFR codec\n\n"
- "The MS is using full rate V2 codec, but the better cell is congested\n"
- "at TCH/F slots. As the congestion is removed, the handover takes\n"
- "place.\n",
-
- "create-bts", "2",
- "set-min-free", "1", "TCH/F", "4",
- "create-ms", "0", "TCH/F", "EFR",
- "meas-rep", "0", "20","0", "1","0","30",
- "expect-no-chan",
- "set-min-free", "1", "TCH/F", "3",
- "meas-rep", "0", "20","0", "1","0","30",
- "expect-chan", "1", "1",
- "ack-chan",
- "expect-ho", "0", "1",
- "ho-complete",
- NULL
+struct meas_rep_data {
+ int argc;
+ const char **argv;
+ uint8_t bs_power_db;
};
-static char *test_case_7[] = {
- "2",
-
- "TCH/F to TCH/H changing with AMR codec\n\n"
- "The MS is using AMR V3 codec, the better cell is congested at TCH/F\n"
- "slots. The handover is performed to non-congested TCH/H slots.\n",
-
- "create-bts", "2",
- "set-min-free", "1", "TCH/F", "4",
- "create-ms", "0", "TCH/F", "AMR",
- "meas-rep", "0", "20","0", "1","0","30",
- "expect-chan", "1", "5",
- "ack-chan",
- "expect-ho", "0", "1",
- "ho-complete",
- NULL
-};
+static void _meas_rep_cb(struct gsm_lchan *lc, void *data)
+{
+ struct meas_rep_data *d = data;
+ int argc = d->argc;
+ const char **argv = d->argv;
+ uint8_t rxlev;
+ uint8_t rxqual;
+ uint8_t ta;
+ int i;
+ struct neighbor_meas nm[6] = {};
-static char *test_case_8[] = {
- "2",
-
- "No handover to a cell with no slots available\n\n"
- "If no slot is available, no handover is performed\n",
-
- "create-bts", "2",
- "create-ms", "0", "TCH/F", "AMR",
- "create-ms", "1", "TCH/F", "AMR",
- "create-ms", "1", "TCH/F", "AMR",
- "create-ms", "1", "TCH/F", "AMR",
- "create-ms", "1", "TCH/F", "AMR",
- "create-ms", "1", "TCH/H", "AMR",
- "create-ms", "1", "TCH/H", "AMR",
- "create-ms", "1", "TCH/H", "AMR",
- "create-ms", "1", "TCH/H", "AMR",
- "meas-rep", "0", "0","0", "1","0","30",
- "expect-no-chan",
- NULL
-};
+ if (!lchan_state_is(lc, LCHAN_ST_ESTABLISHED))
+ return;
-static char *test_case_9[] = {
- "2",
-
- "No more parallel handovers, if max_unsync_ho is defined\n\n"
- "There are tree mobiles that want to handover, but only two can do\n"
- "it at a time, because the maximum number is limited to two.\n",
-
- "create-bts", "2",
- "set-max-ho", "1", "2",
- "create-ms", "0", "TCH/F", "AMR",
- "create-ms", "0", "TCH/F", "AMR",
- "create-ms", "0", "TCH/F", "AMR",
- "meas-rep", "0", "0","0", "1","0","30",
- "expect-chan", "1", "1",
- "meas-rep", "1", "0","0", "1","0","30",
- "expect-chan", "1", "2",
- "meas-rep", "2", "0","0", "1","0","30",
- "expect-no-chan",
- NULL
-};
+ rxlev = atoi(argv[0]);
+ rxqual = atoi(argv[1]);
+ ta = atoi(argv[2]);
+ argv += 3;
+ argc -= 3;
-static char *test_case_10[] = {
- "2",
-
- "Hysteresis\n\n"
- "If neighbor cell is better, handover is only performed if the\n"
- "amount of improvement is greater or equal hyteresis\n",
-
- "create-bts", "2",
- "create-ms", "0", "TCH/F", "AMR",
- "meas-rep", "0", "27","0", "1","0","30",
- "expect-no-chan",
- "meas-rep", "0", "26","0", "1","0","30",
- "expect-chan", "1", "1",
- "ack-chan",
- "expect-ho", "0", "1",
- "ho-complete",
- NULL
-};
+ if (!lchan_state_is(lc, LCHAN_ST_ESTABLISHED)) {
+ fprintf(stderr, "Error: sending measurement report for %s which is in state %s\n",
+ gsm_lchan_name(lc), lchan_state_name(lc));
+ exit(1);
+ }
-static char *test_case_11[] = {
- "2",
-
- "No Hysteresis and minimum RX level\n\n"
- "If current cell's RX level is below mimium level, handover must be\n"
- "performed, no matter of the hysteresis. First do not perform\n"
- "handover to better neighbor cell, because the hysteresis is not\n"
- "met. Second do not perform handover because better neighbor cell is\n"
- "below minimum RX level. Third perform handover because current cell\n"
- "is below minimum RX level, even if the better neighbor cell (minimum\n"
- "RX level reached) does not meet the hysteresis.\n",
-
- "create-bts", "2",
- "create-ms", "0", "TCH/F", "AMR",
- "meas-rep", "0", "10","0", "1","0","11",
- "expect-no-chan",
- "meas-rep", "0", "8","0", "1","0","9",
- "expect-no-chan",
- "meas-rep", "0", "9","0", "1","0","10",
- "expect-chan", "1", "1",
- "ack-chan",
- "expect-ho", "0", "1",
- "ho-complete",
- NULL
-};
+ /* skip the optional [neighbors] keyword */
+ if (argc) {
+ argv++;
+ argc--;
+ }
-static char *test_case_12[] = {
- "2",
-
- "No handover to congested cell\n\n"
- "The better neighbor cell is congested, so no handover is performed.\n"
- "After the congestion is over, handover will be performed.\n",
-
- "create-bts", "2",
- "create-ms", "0", "TCH/F", "AMR",
- "set-min-free", "1", "TCH/F", "4",
- "set-min-free", "1", "TCH/H", "4",
- "meas-rep", "0", "20","0", "1","0","30",
- "expect-no-chan",
- "set-min-free", "1", "TCH/F", "3",
- "set-min-free", "1", "TCH/H", "3",
- "meas-rep", "0", "20","0", "1","0","30",
- "expect-chan", "1", "1",
- "ack-chan",
- "expect-ho", "0", "1",
- "ho-complete",
- NULL
-};
+ fprintf(stderr, "- Sending measurement report from %s: rxlev=%u rxqual=%u ta=%u (%d neighbors)\n",
+ gsm_lchan_name(lc), rxlev, rxqual, ta, argc);
+
+ for (i = 0; i < 6; i++) {
+ int neighbor_bts_nr = i;
+ /* since our bts is not in the list of neighbor cells, we need to shift */
+ if (neighbor_bts_nr >= lc->ts->trx->bts->nr)
+ neighbor_bts_nr++;
+ nm[i] = (struct neighbor_meas){
+ .rxlev = argc > i ? atoi(argv[i]) : 0,
+ .bsic = 0x3f,
+ .bcch_f = i,
+ };
+ if (i < argc)
+ fprintf(stderr, " * Neighbor cell #%d, actual BTS %d: rxlev=%d\n", i, neighbor_bts_nr,
+ nm[i].rxlev);
+ }
+ gen_meas_rep(lc, d->bs_power_db, rxlev, rxqual, ta, argc, nm);
+}
-static char *test_case_13[] = {
- "2",
-
- "Handover to balance congestion\n\n"
- "The current and the better cell are congested, so no handover is\n"
- "performed. This is because handover would congest the neighbor cell\n"
- "more. After congestion raises in the current cell, the handover is\n"
- "performed to balance congestion\n",
-
- "create-bts", "2",
- "create-ms", "0", "TCH/F", "AMR",
- "set-min-free", "0", "TCH/F", "4",
- "set-min-free", "0", "TCH/H", "4",
- "set-min-free", "1", "TCH/F", "4",
- "set-min-free", "1", "TCH/H", "4",
- "meas-rep", "0", "20","0", "1","0","30",
- "expect-no-chan",
- "create-ms", "0", "TCH/F", "AMR",
- "meas-rep", "0", "20","0", "1","0","30",
- "expect-chan", "1", "1",
- "ack-chan",
- "expect-ho", "0", "1",
- "ho-complete",
- NULL
-};
+static int _meas_rep(struct vty *vty, uint8_t bs_power_db, int argc, const char **argv)
+{
+ struct meas_rep_data d = {
+ .argc = argc - 4,
+ .argv = argv + 4,
+ .bs_power_db = bs_power_db,
+ };
+ parse_lchan_wildcard_args(argv, _meas_rep_cb, &d);
+ return CMD_SUCCESS;
+}
-static char *test_case_14[] = {
- "2",
-
- "Handover to congested cell, if RX level is below minimum\n\n"
- "The better neighbor cell is congested, so no handover is performed.\n"
- "If the RX level of the current cell drops below minimum acceptable\n"
- "level, the handover is performed.\n",
-
- "create-bts", "2",
- "create-ms", "0", "TCH/F", "AMR",
- "set-min-free", "1", "TCH/F", "4",
- "set-min-free", "1", "TCH/H", "4",
- "meas-rep", "0", "10","0", "1","0","30",
- "expect-no-chan",
- "meas-rep", "0", "9","0", "1","0","30",
- "expect-chan", "1", "1",
- "ack-chan",
- "expect-ho", "0", "1",
- "ho-complete",
- NULL
-};
-static char *test_case_15[] = {
- "2",
-
- "Handover to cell with worse RXLEV, if RXQUAL is below minimum\n\n"
- "The neighbor cell has worse RXLEV, so no handover is performed.\n"
- "If the RXQUAL of the current cell drops below minimum acceptable\n"
- "level, the handover is performed. It is also required that 10\n"
- "reports are received, before RXQUAL is checked.\n",
- /* (See also test 28, which tests for RXQUAL triggering HO to congested cell.) */
- /* TODO: bad RXQUAL may want to prefer assignment within the same cell to avoid interference.
- * See Performance Enhancements in a Frequency Hopping GSM Network (Nielsen Wigard 2002), Chapter
- * 2.1.1, "Interference" in the list of triggers on p.157. */
-
- "create-bts", "2",
- "create-ms", "0", "TCH/F", "AMR",
- "meas-rep", "0", "40","6", "1","0","30",
- "expect-no-chan",
- "meas-rep", "0", "40","6", "1","0","30",
- "expect-no-chan",
- "meas-rep", "0", "40","6", "1","0","30",
- "expect-no-chan",
- "meas-rep", "0", "40","6", "1","0","30",
- "expect-no-chan",
- "meas-rep", "0", "40","6", "1","0","30",
- "expect-no-chan",
- "meas-rep", "0", "40","6", "1","0","30",
- "expect-no-chan",
- "meas-rep", "0", "40","6", "1","0","30",
- "expect-no-chan",
- "meas-rep", "0", "40","6", "1","0","30",
- "expect-no-chan",
- "meas-rep", "0", "40","6", "1","0","30",
- "expect-no-chan",
- "meas-rep", "0", "40","6", "1","0","30",
- "expect-chan", "1", "1",
- "ack-chan",
- "expect-ho", "0", "1",
- "ho-complete",
- NULL
-};
+#define MEAS_REP_ARGS LCHAN_WILDCARD_ARGS " rxlev <0-255> rxqual <0-7> ta <0-255>" \
+ " [neighbors] [<0-255>] [<0-255>] [<0-255>] [<0-255>] [<0-255>] [<0-255>]"
+#define MEAS_REP_DOC "Send measurement report\n"
+#define MEAS_REP_ARGS_DOC \
+ LCHAN_WILDCARD_ARGS_DOC \
+ "rxlev\nrxlev\n" \
+ "rxqual\nrxqual\n" \
+ "timing advance\ntiming advance\n" \
+ "neighbors list of rxlev reported by each neighbor cell\n" \
+ "neighbor 0 rxlev\n" \
+ "neighbor 1 rxlev\n" \
+ "neighbor 2 rxlev\n" \
+ "neighbor 3 rxlev\n" \
+ "neighbor 4 rxlev\n" \
+ "neighbor 5 rxlev\n"
+
+DEFUN(meas_rep, meas_rep_cmd,
+ "meas-rep " MEAS_REP_ARGS,
+ MEAS_REP_DOC MEAS_REP_ARGS_DOC)
+{
+ VTY_ECHO();
+ return _meas_rep(vty, 0, argc, argv);
+}
-static char *test_case_16[] = {
- "2",
-
- "Handover due to maximum TA exceeded\n\n"
- "The MS in the current (best) cell has reached maximum allowed timing\n"
- "advance. No handover is performed until the timing advance exceeds\n"
- "it. The originating cell is still the best, but no handover is\n"
- "performed back to that cell, because the penalty timer (due to\n"
- "maximum allowed timing advance) is running.\n",
-
- "create-bts", "2",
- "create-ms", "0", "TCH/F", "AMR",
- "set-max-ta", "0", "5", /* of cell */
- "set-ta", "0", "5", /* of ms */
- "meas-rep", "0", "30","0", "1","0","20",
- "expect-no-chan",
- "set-ta", "0", "6", /* of ms */
- "meas-rep", "0", "30","0", "1","0","20",
- "expect-chan", "1", "1",
- "ack-chan",
- "expect-ho", "0", "1",
- "ho-complete",
- "meas-rep", "0", "20","0", "1","0","30",
- "expect-no-chan",
- NULL
-};
+DEFUN(meas_rep_repeat, meas_rep_repeat_cmd,
+ "meas-rep repeat <0-999> " MEAS_REP_ARGS,
+ MEAS_REP_DOC
+ "Resend the same measurement report N times\nN\n"
+ MEAS_REP_ARGS_DOC)
+{
+ int count = atoi(argv[0]);
+ VTY_ECHO();
+ argv += 1;
+ argc -= 1;
+
+ while (count--)
+ _meas_rep(vty, 0, argc, argv);
+ return CMD_SUCCESS;
+}
-static char *test_case_17[] = {
- "2",
-
- "Congestion check: No congestion\n\n"
- "Three cells have different number of used slots, but there is no\n"
- "congestion in any of these cells. No handover is performed.\n",
-
- "create-bts", "3",
- "set-min-free", "0", "TCH/F", "2",
- "set-min-free", "0", "TCH/H", "2",
- "set-min-free", "1", "TCH/F", "2",
- "set-min-free", "1", "TCH/H", "2",
- "set-min-free", "2", "TCH/F", "2",
- "set-min-free", "2", "TCH/H", "2",
- "create-ms", "0", "TCH/F", "AMR",
- "create-ms", "0", "TCH/F", "AMR",
- "create-ms", "0", "TCH/H", "AMR",
- "create-ms", "0", "TCH/H", "AMR",
- "create-ms", "1", "TCH/F", "AMR",
- "create-ms", "1", "TCH/H", "AMR",
- "meas-rep", "0", "30","0", "2","0","20","1","20",
- "expect-no-chan",
- "meas-rep", "1", "30","0", "2","0","20","1","20",
- "expect-no-chan",
- "meas-rep", "2", "30","0", "2","0","20","1","20",
- "expect-no-chan",
- "meas-rep", "3", "30","0", "2","0","20","1","20",
- "expect-no-chan",
- "meas-rep", "4", "30","0", "2","0","20","1","20",
- "expect-no-chan",
- "meas-rep", "5", "30","0", "2","0","20","1","20",
- "expect-no-chan",
- "congestion-check",
- "expect-no-chan",
- NULL
-};
+DEFUN(meas_rep_repeat_bspower, meas_rep_repeat_bspower_cmd,
+ "meas-rep repeat <0-999> bspower <0-31> " MEAS_REP_ARGS,
+ MEAS_REP_DOC
+ "Resend the same measurement report N times\nN\n"
+ "Send a nonzero BS Power value in the measurement report (downlink power reduction)\nBS Power reduction in dB\n"
+ MEAS_REP_ARGS_DOC)
+{
+ int count = atoi(argv[0]);
+ uint8_t bs_power_db = atoi(argv[1]);
+ VTY_ECHO();
+ argv += 2;
+ argc -= 2;
+
+ while (count--)
+ _meas_rep(vty, bs_power_db, argc, argv);
+ return CMD_SUCCESS;
+}
-static char *test_case_18[] = {
- "2",
-
- "Congestion check: One out of three cells is congested\n\n"
- "Three cells have different number of used slots, but there is\n"
- "congestion at TCH/F in the first cell. Handover is performed with\n"
- "the best candidate.\n",
-
- "create-bts", "3",
- "set-min-free", "0", "TCH/F", "2",
- "set-min-free", "0", "TCH/H", "2",
- "set-min-free", "1", "TCH/F", "2",
- "set-min-free", "1", "TCH/H", "2",
- "set-min-free", "2", "TCH/F", "2",
- "set-min-free", "2", "TCH/H", "2",
- "create-ms", "0", "TCH/F", "AMR",
- "create-ms", "0", "TCH/F", "AMR",
- "create-ms", "0", "TCH/F", "AMR",
- "create-ms", "0", "TCH/H", "AMR",
- "create-ms", "0", "TCH/H", "AMR",
- "create-ms", "1", "TCH/F", "AMR",
- "create-ms", "1", "TCH/H", "AMR",
- "meas-rep", "0", "30","0", "2","0","20","1","20",
- "expect-no-chan",
- "meas-rep", "1", "30","0", "2","0","20","1","20",
- "expect-no-chan",
- "meas-rep", "2", "30","0", "2","0","21","1","20",
- "expect-no-chan",
- "meas-rep", "3", "30","0", "2","0","20","1","20",
- "expect-no-chan",
- "meas-rep", "4", "30","0", "2","0","20","1","20",
- "expect-no-chan",
- "meas-rep", "5", "30","0", "2","0","20","1","20",
- "expect-no-chan",
- "meas-rep", "6", "30","0", "2","0","20","1","20",
- "expect-no-chan",
- "congestion-check",
- "expect-chan", "1", "2",
- "ack-chan",
- "expect-ho", "0", "3", /* best candidate is MS 2 at BTS 1, TS 3 */
- "ho-complete",
- NULL
-};
+DEFUN(res_ind, res_ind_cmd,
+ "res-ind trx <0-255> <0-255> levels .LEVELS",
+ "Send Resource Indication for a specific TRX, indicating interference levels per lchan\n"
+ "Indicate a BTS and TRX\n" "BTS nr\n" "TRX nr\n"
+ "Indicate interference levels: each level is an index to bts->interf_meas_params.bounds_dbm[],"
+ " i.e. <0-5> or '-' to omit a report for this timeslot/lchan."
+ " Separate timeslots by spaces, for individual subslots directly concatenate values."
+ " If a timeslot has more subslots than provided, the last given value is repeated."
+ " For example: 'res-ind trx 0 0 levels - 1 23 -': on BTS 0 TRX 0, omit ratings for the entire first timeslot,"
+ " send level=1 for timeslot 1, and for timeslot 2 send level=2 for subslot 0 and level=3 for subslot 1.\n")
+{
+ int i;
+ uint8_t level;
+ struct gsm_bts *bts = bts_by_num_str(argv[0]);
+ struct gsm_bts_trx *trx = trx_by_num_str(bts, argv[1]);
+ struct msgb *msg = msgb_alloc_headroom(256, 64, "RES-IND");
+ struct abis_rsl_common_hdr *rslh;
+ uint8_t *res_info_len;
+ VTY_ECHO();
+
+ /* In this test suite, always act as if the interf_meas_params_cfg were already sent to the BTS via OML */
+ bts->interf_meas_params_used = bts->interf_meas_params_cfg;
+
+ argv += 2;
+ argc -= 2;
+
+ rslh = (struct abis_rsl_common_hdr*)msgb_put(msg, sizeof(*rslh));
+ rslh->msg_discr = ABIS_RSL_MDISC_TRX;
+ rslh->msg_type = RSL_MT_RF_RES_IND;
+ msgb_put_u8(msg, RSL_IE_RESOURCE_INFO);
+ res_info_len = msg->tail;
+ msgb_put_u8(msg, 0);
+
+ level = 0xff;
+ for (i = 0; i < ARRAY_SIZE(trx->ts); i++) {
+ const char *ts_str = NULL;
+ struct gsm_lchan *lchan;
+ size_t given_subslots = 0;
+ struct gsm_bts_trx_ts *ts = &trx->ts[i];
+
+ if (i < argc) {
+ ts_str = argv[i];
+ given_subslots = strlen(ts_str);
+ }
-static char *test_case_19[] = {
- "2",
-
- "Congestion check: Balancing over congested cells\n\n"
- "Two cells are congested, but the second cell is less congested.\n"
- "Handover is performed to solve the congestion.\n",
-
- "create-bts", "2",
- "set-min-free", "0", "TCH/F", "4",
- "set-min-free", "1", "TCH/F", "4",
- "create-ms", "0", "TCH/F", "FR",
- "create-ms", "0", "TCH/F", "FR",
- "create-ms", "0", "TCH/F", "FR",
- "create-ms", "1", "TCH/F", "FR",
- "meas-rep", "0", "30","0", "1","0","20",
- "expect-no-chan",
- "meas-rep", "1", "30","0", "1","0","21",
- "expect-no-chan",
- "meas-rep", "2", "30","0", "1","0","20",
- "expect-no-chan",
- "meas-rep", "3", "30","0", "1","0","20",
- "expect-no-chan",
- "congestion-check",
- "expect-chan", "1", "2",
- "ack-chan",
- "expect-ho", "0", "2", /* best candidate is MS 1 at BTS 0, TS 2 */
- "ho-complete",
- NULL
-};
+ ts_for_n_lchans(lchan, ts, ts->max_lchans_possible) {
+ int chan_nr;
+
+ if (lchan->nr < given_subslots && ts_str) {
+ char subslot_val = ts_str[lchan->nr];
+ switch (subslot_val) {
+ case '-':
+ level = INTERF_BAND_UNKNOWN;
+ break;
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ level = subslot_val - '0';
+ break;
+ default:
+ OSMO_ASSERT(false);
+ }
+ }
-static char *test_case_20[] = {
- "2",
-
- "Congestion check: Solving congestion by handover TCH/F -> TCH/H\n\n"
- "Two BTS, one MS in the first congested BTS must handover to\n"
- "non-congested TCH/H of second BTS, in order to solve congestion\n",
- "create-bts", "2",
- "set-min-free", "0", "TCH/F", "4",
- "set-min-free", "0", "TCH/H", "4",
- "set-min-free", "1", "TCH/F", "4",
- "create-ms", "0", "TCH/F", "AMR",
- "meas-rep", "0", "30","0", "1","0","30",
- "expect-no-chan",
- "congestion-check",
- "expect-chan", "1", "5",
- "ack-chan",
- "expect-ho", "0", "1",
- "ho-complete",
- NULL
-};
+ if (level == INTERF_BAND_UNKNOWN)
+ continue;
-static char *test_case_21[] = {
- "2",
-
- "Congestion check: Balancing congestion by handover TCH/F -> TCH/H\n\n"
- "Two BTS, one MS in the first congested BTS must handover to\n"
- "less-congested TCH/H of second BTS, in order to balance congestion\n",
- "create-bts", "2",
- "set-min-free", "0", "TCH/F", "4",
- "set-min-free", "0", "TCH/H", "4",
- "set-min-free", "1", "TCH/F", "4",
- "set-min-free", "1", "TCH/H", "4",
- "create-ms", "0", "TCH/F", "AMR",
- "create-ms", "0", "TCH/F", "AMR",
- "create-ms", "0", "TCH/H", "AMR",
- "meas-rep", "0", "30","0", "1","0","30",
- "expect-no-chan",
- "congestion-check",
- "expect-chan", "1", "1",
- "ack-chan",
- "expect-ho", "0", "1",
- "ho-complete",
- NULL
-};
+ chan_nr = gsm_lchan2chan_nr(lchan, true);
+ if (chan_nr < 0)
+ continue;
-static char *test_case_22[] = {
- "2",
-
- "Congestion check: Upgrading worst candidate from TCH/H -> TCH/F\n\n"
- "There is only one BTS. The TCH/H slots are congested. Since\n"
- "assignment is performed to less-congested TCH/F, the candidate with\n"
- "the worst RX level is chosen.\n",
-
- "create-bts", "1",
- "set-min-free", "0", "TCH/F", "4",
- "set-min-free", "0", "TCH/H", "4",
- "create-ms", "0", "TCH/H", "AMR",
- "create-ms", "0", "TCH/H", "AMR",
- "create-ms", "0", "TCH/H", "AMR",
- "meas-rep", "0", "30","0", "0",
- "meas-rep", "1", "34","0", "0",
- "meas-rep", "2", "20","0", "0",
- "expect-no-chan",
- "congestion-check",
- "expect-chan", "0", "1",
- "ack-chan",
- "expect-ho", "0", "6",
- "ho-complete",
- NULL
-};
+ msgb_put_u8(msg, chan_nr);
+ msgb_put_u8(msg, level << 5);
+ }
+ }
-static char *test_case_23[] = {
- "2",
-
- "Story: 'A neighbor is your friend'\n",
-
- "create-bts", "3",
-
- "print",
- "Andreas is driving along the coast, on a sunny june afternoon.\n"
- "Suddenly he is getting a call from his friend and neighbor Axel.\n"
- "\n"
- "What happens: Two MS are created, #0 for Axel, #1 for Andreas.",
- /* Axel */
- "create-ms", "2", "TCH/F", "AMR",
- /* andreas */
- "create-ms", "0", "TCH/F", "AMR",
- "meas-rep", "1", "40","0", "1","0","30",
- "expect-no-chan",
-
- "print",
- "Axel asks Andreas if he would like to join them for a barbecue.\n"
- "Axel's house is right in the neighborhood and the weather is fine.\n"
- "Andreas agrees, so he drives to a close store to buy some barbecue\n"
- "skewers.\n"
- "\n"
- "What happens: While driving, a different cell (mounted atop the\n"
- "store) becomes better.",
- /* drive to bts 1 */
- "meas-rep", "1", "20","0", "1","0","35",
- "expect-chan", "1", "1",
- "ack-chan",
- "expect-ho", "0", "1",
- "ho-complete",
-
- "print",
- "While Andreas is walking into the store, Axel asks, if he could also\n"
- "bring some beer. Andreas has problems understanding him: \"I have a\n"
- "bad reception here. The cell tower is right atop the store, but poor\n"
- "coverage inside. Can you repeat please?\"\n"
- "\n"
- "What happens: Inside the store the close cell is so bad, that\n"
- "handover back to the previous cell is required.",
- /* bts 1 becomes bad, so bts 0 helps out */
- "meas-rep", "1", "5","0", "1","0","20",
- "expect-chan", "0", "1",
- "ack-chan",
- "expect-ho", "1", "1",
- "ho-complete",
-
- "print",
- "After Andreas bought skewers and beer, he leaves the store.\n"
- "\n"
- "What happens: Outside the store the close cell is better again, so\n"
- "handover back to the that cell is performed.",
- /* bts 1 becomes better again */
- "meas-rep", "1", "20","0", "1","0","35",
- "expect-chan", "1", "1",
- "ack-chan",
- "expect-ho", "0", "1",
- "ho-complete",
-
- "print",
- /* bts 2 becomes better */
- "Andreas drives down to the lake where Axel's house is.\n"
- "\n"
- "What happens: There is a small cell at Axel's house, which becomes\n"
- "better, because the current cell has no good comverage at the lake.",
- "meas-rep", "1", "14","0", "2","0","2","1","63",
- "expect-chan", "2", "2",
- "ack-chan",
- "expect-ho", "1", "1",
- "ho-complete",
-
- "print",
- "Andreas wonders why he still has good radio coverage: \"Last time it\n"
- "was so bad\". Axel says: \"I installed a pico cell in my house,\n"
- "now we can use our mobile phones down here at the lake.\"",
-
- NULL
-};
+ *res_info_len = msg->tail - res_info_len - 1;
-static char *test_case_24[] = {
- "2",
- "No (or not enough) measurements for handover\n\n"
- "Do not solve congestion in cell, because there is no measurement.\n"
- "As soon as enough measurements available (1 in our case), perform\n"
- "handover. Afterwards the old cell becomes congested and the new\n"
- "cell is not. Do not perform handover until new measurements are\n"
- "received.\n",
-
- /* two cells, first in congested, but no handover */
- "create-bts", "2",
- "set-min-free", "0", "TCH/F", "4",
- "set-min-free", "0", "TCH/H", "4",
- "create-ms", "0", "TCH/F", "AMR",
- "congestion-check",
- "expect-no-chan",
-
- /* send measurement and trigger congestion check */
- "meas-rep", "0", "20","0", "1","0","20",
- "expect-no-chan",
- "congestion-check",
- "expect-chan", "1", "1",
- "ack-chan",
- "expect-ho", "0", "1",
- "ho-complete",
-
- /* congest the first cell and remove congestion from second cell */
- "set-min-free", "0", "TCH/F", "0",
- "set-min-free", "0", "TCH/H", "0",
- "set-min-free", "1", "TCH/F", "4",
- "set-min-free", "1", "TCH/H", "4",
-
- /* no handover until measurements applied */
- "congestion-check",
- "expect-no-chan",
- "meas-rep", "0", "20","0", "1","0","20",
- "expect-no-chan",
- "congestion-check",
- "expect-chan", "0", "1",
- "ack-chan",
- "expect-ho", "1", "1",
- "ho-complete",
- NULL
-};
+ msg->dst = trx->rsl_link_primary;
+ msg->l2h = msg->data;
+ abis_rsl_rcvmsg(msg);
-static char *test_case_25[] = {
- "1",
+ return CMD_SUCCESS;
+}
- "Stay in better cell\n\n"
- "There are many neighbor cells, but only the current cell is the best\n"
- "cell, so no handover is performed\n",
+DEFUN(congestion_check, congestion_check_cmd,
+ "congestion-check",
+ "Trigger a congestion check\n")
+{
+ VTY_ECHO();
+ fprintf(stderr, "- Triggering congestion check\n");
+ hodec2_congestion_check(bsc_gsmnet);
+ return CMD_SUCCESS;
+}
- "create-bts", "7",
- "create-ms", "0", "TCH/F", "AMR",
- "meas-rep", "0", "30","0",
- "6","0","20","1","21","2","18","3","20","4","23","5","19",
- "expect-no-chan",
- NULL
-};
+DEFUN(expect_no_chan, expect_no_chan_cmd,
+ "expect-no-chan",
+ "Expect that no channel request was sent from BSC to any cell\n")
+{
+ VTY_ECHO();
+ fprintf(stderr, "- Expecting no channel request\n");
+ if (new_chan_req) {
+ fprintf(stderr, " * Got channel request at %s\n", gsm_lchan_name(new_chan_req));
+ fprintf(stderr, "Test failed, because channel was requested\n");
+ exit(1);
+ }
+ fprintf(stderr, " * Got no channel request\n");
+ return CMD_SUCCESS;
+}
-static char *test_case_26[] = {
- "1",
-
- "Handover to best better cell\n\n"
- "The best neighbor cell is selected\n",
-
- "create-bts", "7",
- "create-ms", "0", "TCH/F", "AMR",
- "meas-rep", "0", "10","0",
- "6","0","20","1","21","2","18","3","20","4","23","5","19",
- "expect-chan", "5", "1",
- "ack-chan",
- "expect-ho", "0", "1",
- "ho-complete",
- NULL
-};
+static void _expect_chan_activ(struct gsm_lchan *lchan)
+{
+ fprintf(stderr, "- Expecting channel request at %s\n",
+ gsm_lchan_name(lchan));
+ if (!new_chan_req) {
+ fprintf(stderr, "Test failed, because no channel was requested\n");
+ exit(1);
+ }
+ last_chan_req = new_chan_req;
+ new_chan_req = NULL;
+ fprintf(stderr, " * Got channel request at %s\n", gsm_lchan_name(last_chan_req));
+ if (lchan != last_chan_req) {
+ fprintf(stderr, "Test failed, because channel was requested on a different lchan than expected\n"
+ "expected: %s got: %s\n",
+ gsm_lchan_name(lchan), gsm_lchan_name(last_chan_req));
+ exit(1);
+ }
+ send_chan_act_ack(lchan, 1);
+}
-static char *test_case_27[] = {
- "2",
-
- "Congestion check: Upgrading worst candidate from TCH/H -> TCH/F\n\n"
- "There is only one BTS. The TCH/H slots are congested. Since\n"
- "assignment is performed to less-congested TCH/F, the candidate with\n"
- "the worst RX level is chosen. (So far like test 22.)\n"
- "After that, trigger more congestion checks to ensure stability.\n",
-
- "create-bts", "1",
- "set-min-free", "0", "TCH/F", "2",
- "set-min-free", "0", "TCH/H", "4",
- "create-ms", "0", "TCH/H", "AMR",
- "create-ms", "0", "TCH/H", "AMR",
- "create-ms", "0", "TCH/H", "AMR",
- "meas-rep", "0", "30","0", "0",
- "meas-rep", "1", "34","0", "0",
- "meas-rep", "2", "20","0", "0",
- "expect-no-chan",
- "congestion-check",
- "expect-chan", "0", "1",
- "ack-chan",
- "expect-ho", "0", "6",
- "ho-complete",
- "congestion-check",
- "expect-chan", "0", "2",
- "ack-chan",
- "expect-ho", "0", "5",
- "ho-complete",
- "congestion-check",
- "expect-no-chan",
- "congestion-check",
- "expect-no-chan",
- NULL
-};
+static void _expect_ho_cmd(struct gsm_lchan *lchan)
+{
+ fprintf(stderr, "- Expecting Handover Command at %s\n",
+ gsm_lchan_name(lchan));
-static char *test_case_28[] = {
- "2",
-
- "Handover to congested cell, if RX quality is below minimum\n\n"
- "The better neighbor cell is congested, so no handover is performed.\n"
- "If the RX quality of the current cell drops below minimum acceptable\n"
- "level, the handover is performed. It is also required that 10\n"
- "resports are received, before RX quality is checked.\n",
-
- "create-bts", "2",
- "create-ms", "0", "TCH/F", "AMR",
- "set-min-free", "1", "TCH/F", "4",
- "set-min-free", "1", "TCH/H", "4",
- "meas-rep", "0", "30","6", "1","0","40",
- "expect-no-chan",
- "meas-rep", "0", "30","6", "1","0","40",
- "expect-no-chan",
- "meas-rep", "0", "30","6", "1","0","40",
- "expect-no-chan",
- "meas-rep", "0", "30","6", "1","0","40",
- "expect-no-chan",
- "meas-rep", "0", "30","6", "1","0","40",
- "expect-no-chan",
- "meas-rep", "0", "30","6", "1","0","40",
- "expect-no-chan",
- "meas-rep", "0", "30","6", "1","0","40",
- "expect-no-chan",
- "meas-rep", "0", "30","6", "1","0","40",
- "expect-no-chan",
- "meas-rep", "0", "30","6", "1","0","40",
- "expect-no-chan",
- "meas-rep", "0", "30","6", "1","0","40",
- "expect-chan", "1", "1",
- "ack-chan",
- "expect-ho", "0", "1",
- "ho-complete",
- NULL
-};
+ if (!new_ho_cmd) {
+ fprintf(stderr, "Test failed, no Handover Command\n");
+ exit(1);
+ }
+ fprintf(stderr, " * Got Handover Command at %s\n", gsm_lchan_name(new_ho_cmd));
+ if (new_ho_cmd != lchan) {
+ fprintf(stderr, "Test failed, Handover Command not on the expected lchan\n");
+ exit(1);
+ }
+ last_ho_cmd = new_ho_cmd;
+ new_ho_cmd = NULL;
+}
-static char *test_case_29[] = {
- "2",
-
- "Congestion check: Balancing congestion by handover TCH/F -> TCH/H\n\n"
- "One BTS, and TCH/F are considered congested, TCH/H are not.\n"
- ,
- "create-bts", "1",
- "set-min-free", "0", "TCH/F", "3",
- "set-min-free", "0", "TCH/H", "0",
- "create-ms", "0", "TCH/F", "AMR",
- "create-ms", "0", "TCH/F", "AMR",
- "create-ms", "0", "TCH/H", "AMR",
- "meas-rep", "0", "30","0", "1","0","30",
- "expect-no-chan",
- "congestion-check",
- "expect-chan", "0", "5",
- "ack-chan",
- "expect-ho", "0", "1",
- "ho-complete",
- NULL
-};
+static void _expect_as_cmd(struct gsm_lchan *lchan)
+{
+ fprintf(stderr, "- Expecting Assignment Command at %s\n",
+ gsm_lchan_name(lchan));
+
+ if (!new_as_cmd) {
+ fprintf(stderr, "Test failed, no Assignment Command\n");
+ exit(1);
+ }
+ fprintf(stderr, " * Got Assignment Command at %s\n", gsm_lchan_name(new_as_cmd));
+ if (new_as_cmd != lchan) {
+ fprintf(stderr, "Test failed, Assignment Command not on the expected lchan\n");
+ exit(1);
+ }
+ last_as_cmd = new_as_cmd;
+ new_as_cmd = NULL;
+}
+DEFUN(expect_chan, expect_chan_cmd,
+ "expect-chan " LCHAN_ARGS,
+ "Expect RSL Channel Activation of a specific lchan\n"
+ LCHAN_ARGS_DOC)
+{
+ VTY_ECHO();
+ _expect_chan_activ(parse_lchan_args(argv));
+ return CMD_SUCCESS;
+}
-static char **test_cases[] = {
- test_case_0,
- test_case_1,
- test_case_2,
- test_case_3,
- test_case_4,
- test_case_5,
- test_case_6,
- test_case_7,
- test_case_8,
- test_case_9,
- test_case_10,
- test_case_11,
- test_case_12,
- test_case_13,
- test_case_14,
- test_case_15,
- test_case_16,
- test_case_17,
- test_case_18,
- test_case_19,
- test_case_20,
- test_case_21,
- test_case_22,
- test_case_23,
- test_case_24,
- test_case_25,
- test_case_26,
- test_case_27,
- test_case_28,
- test_case_29,
-};
+DEFUN(expect_handover_command, expect_handover_command_cmd,
+ "expect-ho-cmd " LCHAN_ARGS,
+ "Expect an RR Handover Command sent to a specific lchan\n"
+ LCHAN_ARGS_DOC)
+{
+ VTY_ECHO();
+ _expect_ho_cmd(parse_lchan_args(argv));
+ return CMD_SUCCESS;
+}
+
+DEFUN(expect_assignment_command, expect_assignment_command_cmd,
+ "expect-as-cmd " LCHAN_ARGS,
+ "Expect Assignment Command for a given lchan\n"
+ LCHAN_ARGS_DOC)
+{
+ VTY_ECHO();
+ _expect_as_cmd(parse_lchan_args(argv));
+ return CMD_SUCCESS;
+}
+
+DEFUN(ho_detection, ho_detection_cmd,
+ "ho-detect",
+ "Send Handover Detection to the most recent HO target lchan\n")
+{
+ VTY_ECHO();
+ if (!last_chan_req) {
+ fprintf(stderr, "Cannot ack handover/assignment, because no chan request\n");
+ exit(1);
+ }
+ send_ho_detect(last_chan_req);
+ return CMD_SUCCESS;
+}
+
+DEFUN(ho_complete, ho_complete_cmd,
+ "ho-complete",
+ "Send Handover Complete for the most recent HO target lchan\n")
+{
+ VTY_ECHO();
+ if (!last_chan_req) {
+ fprintf(stderr, "Cannot ack handover/assignment, because no chan request\n");
+ exit(1);
+ }
+ if (!last_ho_cmd) {
+ fprintf(stderr, "Cannot ack handover/assignment, because no ho request\n");
+ exit(1);
+ }
+ send_ho_complete(last_chan_req, true);
+ lchan_release_ack(last_ho_cmd);
+ return CMD_SUCCESS;
+}
+
+DEFUN(expect_ho, expect_ho_cmd,
+ "expect-ho from " LCHAN_ARGS " to " LCHAN_ARGS,
+ "Expect a handover of a specific lchan to a specific target lchan;"
+ " shorthand for expect-chan, ack-chan, expect-ho, ho-complete.\n"
+ "lchan to handover from\n" LCHAN_ARGS_DOC
+ "lchan to handover to\n" LCHAN_ARGS_DOC)
+{
+ struct gsm_lchan *from = parse_lchan_args(argv);
+ struct gsm_lchan *to = parse_lchan_args(argv+4);
+ VTY_ECHO();
+
+ _expect_chan_activ(to);
+ _expect_ho_cmd(from);
+ send_ho_detect(to);
+ send_ho_complete(to, true);
+
+ lchan_release_ack(from);
+ return CMD_SUCCESS;
+}
+
+DEFUN(expect_as, expect_as_cmd,
+ "expect-as from " LCHAN_ARGS " to " LCHAN_ARGS,
+ "Expect an intra-cell re-assignment of a specific lchan to a specific target lchan;"
+ " shorthand for expect-chan, ack-chan, expect-as, TODO.\n"
+ "lchan to be re-assigned elsewhere\n" LCHAN_ARGS_DOC
+ "new lchan to re-assign to\n" LCHAN_ARGS_DOC)
+{
+ struct gsm_lchan *from = parse_lchan_args(argv);
+ struct gsm_lchan *to = parse_lchan_args(argv+4);
+ VTY_ECHO();
+
+ _expect_chan_activ(to);
+ if (from->ts->trx->bts != to->ts->trx->bts) {
+ vty_out(vty, "%% Error: re-assignment only works within the same BTS%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ _expect_as_cmd(from);
+ send_assignment_complete(to);
+ send_est_ind(to);
+
+ lchan_release_ack(from);
+ return CMD_SUCCESS;
+}
+
+DEFUN(ho_failed, ho_failed_cmd,
+ "ho-failed",
+ "Fail the most recent handover request\n")
+{
+ VTY_ECHO();
+ if (!last_chan_req) {
+ fprintf(stderr, "Cannot fail handover, because no chan request\n");
+ exit(1);
+ }
+ if (!last_ho_cmd) {
+ fprintf(stderr, "Cannot fail handover, because no handover request\n");
+ exit(1);
+ }
+ send_ho_complete(last_ho_cmd, false);
+ lchan_release_ack(last_chan_req);
+ return CMD_SUCCESS;
+}
+
+DEFUN(expect_ts_use, expect_ts_use_cmd,
+ "expect-ts-use trx <0-255> <0-255> states" TS_USE TS_USE TS_USE TS_USE TS_USE TS_USE TS_USE TS_USE,
+ "Expect timeslots of a BTS' TRX to be in a specific state\n"
+ "Indicate a BTS and TRX\n" "BTS nr\n" "TRX nr\n"
+ "List of 8 expected TS states\n"
+ TS_USE_DOC TS_USE_DOC TS_USE_DOC TS_USE_DOC TS_USE_DOC TS_USE_DOC TS_USE_DOC TS_USE_DOC)
+{
+ struct gsm_bts *bts = bts_by_num_str(argv[0]);
+ struct gsm_bts_trx *trx = trx_by_num_str(bts, argv[1]);
+ VTY_ECHO();
+ argv += 2;
+ argc -= 2;
+ if (!_expect_ts_use(bts, trx, argv))
+ exit(1);
+ return CMD_SUCCESS;
+}
+
+DEFUN(codec_f, codec_f_cmd,
+ "codec tch/f (AMR|EFR|FR)",
+ "Define which codec should be used for new TCH/F lchans (for set-ts-use)\n"
+ "Configure the TCH/F codec to use\nAMR\nEFR\nFR\n")
+{
+ VTY_ECHO();
+ osmo_talloc_replace_string(ctx, &codec_tch_f, argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(codec_h, codec_h_cmd,
+ "codec tch/h (AMR|HR)",
+ "Define which codec should be used for new TCH/H lchans (for set-ts-use)\n"
+ "Configure the TCH/H codec to use\nAMR\nHR\n")
+{
+ VTY_ECHO();
+ osmo_talloc_replace_string(ctx, &codec_tch_h, argv[0]);
+ return CMD_SUCCESS;
+}
+
+DEFUN(set_arfcn, set_arfcn_cmd,
+ "set-arfcn trx <0-255> <0-255> <0-1023>",
+ "Set the ARFCN for a BTS' TRX\n"
+ "Indicate a BTS and TRX\n" "BTS nr\n" "TRX nr\n"
+ "Absolute Radio Frequency Channel Number\n")
+{
+ enum gsm_band unused;
+ struct gsm_bts *bts = bts_by_num_str(argv[0]);
+ struct gsm_bts_trx *trx = trx_by_num_str(bts, argv[1]);
+ int arfcn = atoi(argv[2]);
+ VTY_ECHO();
+
+ if (gsm_arfcn2band_rc(arfcn, &unused) < 0) {
+ vty_out(vty, "%% Invalid arfcn %" PRIu16 " detected%s", arfcn, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ trx->arfcn = arfcn;
+
+ if (generate_cell_chan_alloc(trx->bts) != 0) {
+ vty_out(vty, "%% Failed to re-generate Cell Allocation%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ for (int i = 0; i < bsc_gsmnet->num_bts; i++) {
+ if (gsm_generate_si(gsm_bts_num(bsc_gsmnet, i), SYSINFO_TYPE_2) <= 0)
+ fprintf(stderr, "Error generating SI2\n");
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(set_band, set_band_cmd,
+ "set-band bts <0-255> BAND",
+ "Set the frequency band for a BTS\n"
+ "Indicate a BTS\n" "BTS nr\n"
+ "Frequency band\n")
+{
+ struct gsm_bts *bts = bts_by_num_str(argv[0]);
+ int band = gsm_band_parse(argv[1]);
+ VTY_ECHO();
+
+ if (band < 0) {
+ vty_out(vty, "%% BAND %d is not a valid GSM band%s",
+ band, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ bts->band = band;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN(set_ts_use, set_ts_use_cmd,
+ "set-ts-use trx <0-255> <0-255> states" TS_USE TS_USE TS_USE TS_USE TS_USE TS_USE TS_USE TS_USE,
+ "Put timeslots of a BTS' TRX into a specific state\n"
+ "Indicate a BTS and TRX\n" "BTS nr\n" "TRX nr\n"
+ "List of 8 TS states to apply\n"
+ TS_USE_DOC TS_USE_DOC TS_USE_DOC TS_USE_DOC TS_USE_DOC TS_USE_DOC TS_USE_DOC TS_USE_DOC)
+{
+ struct gsm_bts *bts = bts_by_num_str(argv[0]);
+ struct gsm_bts_trx *trx = trx_by_num_str(bts, argv[1]);
+ VTY_ECHO();
+ argv += 2;
+ argc -= 2;
+ if (!_set_ts_use(bts, trx, argv))
+ exit(1);
+ if (!_expect_ts_use(bts, trx, argv))
+ exit(1);
+ return CMD_SUCCESS;
+}
+
+DEFUN(wait, wait_cmd,
+ "wait <0-999999> [<0-999>]",
+ "Let some fake time pass. The test continues instantaneously, but this overrides osmo_gettimeofday() to let"
+ " given amount of time pass virtually.\n"
+ "Seconds to fake-wait\n"
+ "Microseconds to fake-wait, in addition to the seconds waited\n")
+{
+ time_t seconds = atoi(argv[0]);
+ suseconds_t useconds = 0;
+ VTY_ECHO();
+ if (argc > 1)
+ useconds = atoi(argv[1]) * 1000;
+ fake_time_passes(seconds, useconds);
+ return CMD_SUCCESS;
+}
+
+static void ho_test_vty_init()
+{
+ install_element(CONFIG_NODE, &create_n_bts_cmd);
+ install_element(CONFIG_NODE, &create_bts_cmd);
+ install_element(CONFIG_NODE, &create_ms_cmd);
+ install_element(CONFIG_NODE, &meas_rep_cmd);
+ install_element(CONFIG_NODE, &meas_rep_repeat_cmd);
+ install_element(CONFIG_NODE, &meas_rep_repeat_bspower_cmd);
+ install_element(CONFIG_NODE, &res_ind_cmd);
+ install_element(CONFIG_NODE, &congestion_check_cmd);
+ install_element(CONFIG_NODE, &expect_no_chan_cmd);
+ install_element(CONFIG_NODE, &expect_chan_cmd);
+ install_element(CONFIG_NODE, &expect_handover_command_cmd);
+ install_element(CONFIG_NODE, &expect_assignment_command_cmd);
+ install_element(CONFIG_NODE, &ho_detection_cmd);
+ install_element(CONFIG_NODE, &ho_complete_cmd);
+ install_element(CONFIG_NODE, &expect_ho_cmd);
+ install_element(CONFIG_NODE, &expect_as_cmd);
+ install_element(CONFIG_NODE, &ho_failed_cmd);
+ install_element(CONFIG_NODE, &expect_ts_use_cmd);
+ install_element(CONFIG_NODE, &codec_f_cmd);
+ install_element(CONFIG_NODE, &codec_h_cmd);
+ install_element(CONFIG_NODE, &set_arfcn_cmd);
+ install_element(CONFIG_NODE, &set_band_cmd);
+ install_element(CONFIG_NODE, &set_ts_use_cmd);
+ install_element(CONFIG_NODE, &wait_cmd);
+}
static const struct log_info_cat log_categories[] = {
[DHO] = {
@@ -1414,48 +1621,76 @@ const struct log_info log_info = {
.num_cat = ARRAY_SIZE(log_categories),
};
+static struct vty_app_info vty_info = {
+ .name = "ho_test",
+ .copyright =
+ "Copyright (C) 2020 sysmocom - s.f.m.c. GmbH\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,
+ .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",
+ },
+ .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',
+ },
+};
+
int main(int argc, char **argv)
{
- char **test_case;
- struct gsm_bts *bts[256];
- int bts_num = 0;
- struct gsm_lchan *lchan[256];
- int lchan_num = 0;
- int i;
- int algorithm;
- int test_case_i;
- int last_test_i;
+ char *test_file = NULL;
+ int rc;
+
+ if (argc < 2) {
+ fprintf(stderr, "Pass a handover test script as argument\n");
+ exit(1);
+ }
+ test_file = argv[1];
ctx = talloc_named_const(NULL, 0, "handover_test");
msgb_talloc_ctx_init(ctx, 0);
-
- test_case_i = argc > 1? atoi(argv[1]) : -1;
- last_test_i = ARRAY_SIZE(test_cases) - 1;
-
- if (test_case_i < 0 || test_case_i > last_test_i) {
- for (i = 0; i <= last_test_i; i++) {
- printf("Test #%d (algorithm %s):\n%s\n", i,
- test_cases[i][0], test_cases[i][1]);
- }
- printf("\nPlease specify test case number 0..%d\n", last_test_i);
- return EXIT_FAILURE;
- }
+ vty_info.tall_ctx = ctx;
osmo_init_logging2(ctx, &log_info);
+ log_set_print_filename2(osmo_stderr_target, LOG_FILENAME_BASENAME);
+ log_set_print_filename_pos(osmo_stderr_target, LOG_FILENAME_POS_LINE_END);
log_set_print_category(osmo_stderr_target, 1);
log_set_print_category_hex(osmo_stderr_target, 0);
- log_set_print_filename2(osmo_stderr_target, LOG_FILENAME_BASENAME);
+ log_set_print_level(osmo_stderr_target, 1);
+ log_set_print_timestamp(osmo_stderr_target, 0);
osmo_fsm_log_addr(false);
+ /* the 'wait' command above, intended to test penalty timers, adds seconds to the monotonic clock in "fake
+ * time". */
+ fake_time_start();
+
bsc_network_alloc();
if (!bsc_gsmnet)
exit(1);
- ts_fsm_init();
+ /* The MGCP client which is handling the pool (mgcp_client_pool_vty_init) is used from the bsc_vty_init, so
+ * we must allocate an empty mgw pool even though we do not need it for this test. */
+ bsc_gsmnet->mgw.mgw_pool = mgcp_client_pool_alloc(bsc_gsmnet);
+ if (!bsc_gsmnet->mgw.mgw_pool)
+ exit(1);
+
+ vty_init(&vty_info);
+ bsc_vty_init(bsc_gsmnet);
+ ho_test_vty_init();
+
lchan_fsm_init();
bsc_subscr_conn_fsm_init();
handover_fsm_init();
+ assignment_fsm_init();
ho_set_algorithm(bsc_gsmnet->ho, 2);
ho_set_ho_active(bsc_gsmnet->ho, true);
@@ -1478,309 +1713,24 @@ int main(int argc, char **argv)
/* We don't really need any specific model here */
bts_model_unknown_init();
- test_case = test_cases[test_case_i];
-
- fprintf(stderr, "--------------------\n");
- fprintf(stderr, "Performing the following test %d (algorithm %s):\n%s",
- test_case_i, test_case[0], test_case[1]);
- algorithm = atoi(test_case[0]);
- test_case += 2;
- fprintf(stderr, "--------------------\n");
-
/* Disable the congestion check timer, we will trigger manually. */
bsc_gsmnet->hodec2.congestion_check_interval_s = 0;
handover_decision_1_init();
hodec2_init(bsc_gsmnet);
- while (*test_case) {
- if (!strcmp(*test_case, "create-bts")) {
- static int arfcn = 870;
- int n = atoi(test_case[1]);
- fprintf(stderr, "- Creating %d BTS (one TRX each, "
- "TS(1-4) are TCH/F, TS(5-6) are TCH/H)\n", n);
- for (i = 0; i < n; i++)
- bts[bts_num + i] = create_bts(arfcn++);
- for (i = 0; i < n; i++) {
- if (gsm_generate_si(bts[bts_num + i], SYSINFO_TYPE_2) <= 0)
- fprintf(stderr, "Error generating SI2\n");
- }
- bts_num += n;
- test_case += 2;
- } else
- if (!strcmp(*test_case, "as-enable")) {
- fprintf(stderr, "- Set assignment enable state at "
- "BTS %s to %s\n", test_case[1], test_case[2]);
- ho_set_hodec2_as_active(bts[atoi(test_case[1])]->ho, atoi(test_case[2]));
- test_case += 3;
- } else
- if (!strcmp(*test_case, "ho-enable")) {
- fprintf(stderr, "- Set handover enable state at "
- "BTS %s to %s\n", test_case[1], test_case[2]);
- ho_set_ho_active(bts[atoi(test_case[1])]->ho, atoi(test_case[2]));
- test_case += 3;
- } else
- if (!strcmp(*test_case, "afs-rxlev-improve")) {
- fprintf(stderr, "- Set afs RX level improvement at "
- "BTS %s to %s\n", test_case[1], test_case[2]);
- ho_set_hodec2_afs_bias_rxlev(bts[atoi(test_case[1])]->ho, atoi(test_case[2]));
- test_case += 3;
- } else
- if (!strcmp(*test_case, "afs-rxqual-improve")) {
- fprintf(stderr, "- Set afs RX quality improvement at "
- "BTS %s to %s\n", test_case[1], test_case[2]);
- ho_set_hodec2_afs_bias_rxqual(bts[atoi(test_case[1])]->ho, atoi(test_case[2]));
- test_case += 3;
- } else
- if (!strcmp(*test_case, "set-min-free")) {
- fprintf(stderr, "- Setting minimum required free %s "
- "slots at BTS %s to %s\n", test_case[2],
- test_case[1], test_case[3]);
- if (!strcmp(test_case[2], "TCH/F"))
- ho_set_hodec2_tchf_min_slots(bts[atoi(test_case[1])]->ho, atoi(test_case[3]));
- else
- ho_set_hodec2_tchh_min_slots(bts[atoi(test_case[1])]->ho, atoi(test_case[3]));
- test_case += 4;
- } else
- if (!strcmp(*test_case, "set-max-ho")) {
- fprintf(stderr, "- Setting maximum parallel handovers "
- "at BTS %s to %s\n", test_case[1],
- test_case[2]);
- ho_set_hodec2_ho_max( bts[atoi(test_case[1])]->ho, atoi(test_case[2]));
- test_case += 3;
- } else
- if (!strcmp(*test_case, "set-max-ta")) {
- fprintf(stderr, "- Setting maximum timing advance "
- "at BTS %s to %s\n", test_case[1],
- test_case[2]);
- ho_set_hodec2_max_distance(bts[atoi(test_case[1])]->ho, atoi(test_case[2]));
- test_case += 3;
- } else
- if (!strcmp(*test_case, "create-ms")) {
- fprintf(stderr, "- Creating mobile #%d at BTS %s on "
- "%s with %s codec\n", lchan_num, test_case[1],
- test_case[2], test_case[3]);
- lchan[lchan_num] = create_lchan(bts[atoi(test_case[1])],
- !strcmp(test_case[2], "TCH/F"), test_case[3]);
- if (!lchan[lchan_num]) {
- printf("Failed to create lchan!\n");
- return EXIT_FAILURE;
- }
- fprintf(stderr, " * New MS is at BTS %d TS %d\n",
- lchan[lchan_num]->ts->trx->bts->nr,
- lchan[lchan_num]->ts->nr);
- lchan_num++;
- test_case += 4;
- } else
- if (!strcmp(*test_case, "set-ta")) {
- fprintf(stderr, "- Setting maximum timing advance "
- "at MS %s to %s\n", test_case[1],
- test_case[2]);
- meas_ta_ms = atoi(test_case[2]);
- test_case += 3;
- } else
- if (!strcmp(*test_case, "meas-rep")) {
- /* meas-rep <lchan-nr> <rxlev> <rxqual> <nr-of-neighbors> [<cell-idx> <rxlev> [...]] */
- int n = atoi(test_case[4]);
- struct gsm_lchan *lc = lchan[atoi(test_case[1])];
- fprintf(stderr, "- Sending measurement report from "
- "mobile #%s (rxlev=%s, rxqual=%s)\n",
- test_case[1], test_case[2], test_case[3]);
- meas_dl_rxlev = atoi(test_case[2]);
- meas_dl_rxqual = atoi(test_case[3]);
- meas_num_nc = n;
- test_case += 5;
- for (i = 0; i < n; i++) {
- int nr = atoi(test_case[0]);
- /* since our bts is not in the list of neighbor
- * cells, we need to shift */
- if (nr >= lc->ts->trx->bts->nr)
- nr++;
- fprintf(stderr, " * Neighbor cell #%s, actual "
- "BTS %d (rxlev=%s)\n", test_case[0], nr,
- test_case[1]);
- meas_bcch_f_nc[i] = atoi(test_case[0]);
- /* bts number, not counting our own */
- meas_rxlev_nc[i] = atoi(test_case[1]);
- meas_bsic_nc[i] = 0x3f;
- test_case += 2;
- }
- got_chan_req = 0;
- gen_meas_rep(lc);
- } else
- if (!strcmp(*test_case, "congestion-check")) {
- fprintf(stderr, "- Triggering congestion check\n");
- got_chan_req = 0;
- if (algorithm == 2)
- hodec2_congestion_check(bsc_gsmnet);
- test_case += 1;
- } else
- if (!strcmp(*test_case, "expect-chan")) {
- fprintf(stderr, "- Expecting channel request at BTS %s "
- "TS %s\n", test_case[1], test_case[2]);
- if (!got_chan_req) {
- printf("Test failed, because no channel was "
- "requested\n");
- return EXIT_FAILURE;
- }
- fprintf(stderr, " * Got channel request at BTS %d "
- "TS %d\n", chan_req_lchan->ts->trx->bts->nr,
- chan_req_lchan->ts->nr);
- if (chan_req_lchan->ts->trx->bts->nr
- != atoi(test_case[1])) {
- printf("Test failed, because channel was not "
- "requested on expected BTS\n");
- return EXIT_FAILURE;
- }
- if (chan_req_lchan->ts->nr != atoi(test_case[2])) {
- printf("Test failed, because channel was not "
- "requested on expected TS\n");
- return EXIT_FAILURE;
- }
- test_case += 3;
- } else
- if (!strcmp(*test_case, "expect-no-chan")) {
- fprintf(stderr, "- Expecting no channel request\n");
- if (got_chan_req) {
- fprintf(stderr, " * Got channel request at "
- "BTS %d TS %d\n",
- chan_req_lchan->ts->trx->bts->nr,
- chan_req_lchan->ts->nr);
- printf("Test failed, because channel was "
- "requested\n");
- return EXIT_FAILURE;
- }
- fprintf(stderr, " * Got no channel request\n");
- test_case += 1;
- } else
- if (!strcmp(*test_case, "expect-ho")) {
- fprintf(stderr, "- Expecting handover/assignment "
- "request at BTS %s TS %s\n", test_case[1],
- test_case[2]);
- if (!got_ho_req) {
- printf("Test failed, because no handover was "
- "requested\n");
- return EXIT_FAILURE;
- }
- fprintf(stderr, " * Got handover/assignment request at "
- "BTS %d TS %d\n",
- ho_req_lchan->ts->trx->bts->nr,
- ho_req_lchan->ts->nr);
- if (ho_req_lchan->ts->trx->bts->nr
- != atoi(test_case[1])) {
- printf("Test failed, because "
- "handover/assignment was not commanded "
- "at the expected BTS\n");
- return EXIT_FAILURE;
- }
- if (ho_req_lchan->ts->nr != atoi(test_case[2])) {
- printf("Test failed, because "
- "handover/assignment was not commanded "
- "at the expected TS\n");
- return EXIT_FAILURE;
- }
- test_case += 3;
- } else
- if (!strcmp(*test_case, "ack-chan")) {
- fprintf(stderr, "- Acknowledging channel request\n");
- if (!got_chan_req) {
- printf("Cannot ack channel, because no "
- "request\n");
- return EXIT_FAILURE;
- }
- test_case += 1;
- got_ho_req = 0;
- send_chan_act_ack(chan_req_lchan, 1);
- } else
- if (!strcmp(*test_case, "ho-complete")) {
- fprintf(stderr, "- Acknowledging handover/assignment "
- "request\n");
- if (!got_chan_req) {
- printf("Cannot ack handover/assignment, "
- "because no chan request\n");
- return EXIT_FAILURE;
- }
- if (!got_ho_req) {
- printf("Cannot ack handover/assignment, "
- "because no ho request\n");
- return EXIT_FAILURE;
- }
- test_case += 1;
- got_chan_req = 0;
- got_ho_req = 0;
- /* switch lchan */
- for (i = 0; i < lchan_num; i++) {
- if (lchan[i] == ho_req_lchan) {
- fprintf(stderr, " * MS %d changes from "
- "BTS=%d TS=%d to BTS=%d "
- "TS=%d\n", i,
- lchan[i]->ts->trx->bts->nr,
- lchan[i]->ts->nr,
- chan_req_lchan->ts->trx->bts->nr,
- chan_req_lchan->ts->nr);
- lchan[i] = chan_req_lchan;
- }
- }
- send_ho_complete(chan_req_lchan, true);
- } else
- if (!strcmp(*test_case, "ho-failed")) {
- fprintf(stderr, "- Making handover fail\n");
- if (!got_chan_req) {
- printf("Cannot fail handover, because no chan "
- "request\n");
- return EXIT_FAILURE;
- }
- test_case += 1;
- got_chan_req = 0;
- got_ho_req = 0;
- send_ho_complete(ho_req_lchan, false);
- } else
- if (!strcmp(*test_case, "print")) {
- fprintf(stderr, "\n%s\n\n", test_case[1]);
- test_case += 2;
- } else {
- printf("Unknown test command '%s', please fix!\n",
- *test_case);
- return EXIT_FAILURE;
- }
-
- {
- /* Help the lchan out of releasing states */
- struct gsm_bts *bts;
- llist_for_each_entry(bts, &bsc_gsmnet->bts_list, list) {
- struct gsm_bts_trx *trx;
- llist_for_each_entry(trx, &bts->trx_list, list) {
- int ts_nr;
- for (ts_nr = 0; ts_nr < TRX_NR_TS; ts_nr++) {
- struct gsm_lchan *lchan;
- ts_for_each_lchan(lchan, &trx->ts[ts_nr]) {
-
- if (lchan->fi && lchan->fi->state == LCHAN_ST_WAIT_BEFORE_RF_RELEASE) {
- osmo_fsm_inst_state_chg(lchan->fi, LCHAN_ST_WAIT_RF_RELEASE_ACK, 0, 0);
- osmo_fsm_inst_dispatch(lchan->fi, LCHAN_EV_RSL_RF_CHAN_REL_ACK, 0);
- }
- }
- }
- }
- }
- }
- }
-
- for (i = 0; i < lchan_num; i++) {
- struct gsm_subscriber_connection *conn = lchan[i]->conn;
- lchan[i]->conn = NULL;
- conn->lchan = NULL;
- osmo_fsm_inst_term(conn->fi, OSMO_FSM_TERM_REGULAR, NULL);
+ rc = vty_read_config_file(test_file, NULL);
+ if (rc < 0) {
+ fprintf(stderr, "Failed to parse the test file: '%s'\n", test_file);
}
- fprintf(stderr, "--------------------\n");
-
- printf("Test OK\n");
-
- fprintf(stderr, "--------------------\n");
-
talloc_free(ctx);
- return EXIT_SUCCESS;
+ fprintf(stderr,"-------------------\n");
+ if (!rc)
+ fprintf(stderr, "pass\n");
+ else
+ fprintf(stderr, "FAIL\n");
+ return rc;
}
void rtp_socket_free() {}
@@ -1793,30 +1743,35 @@ void trau_mux_unmap() {}
void trau_mux_map_lchan() {}
void trau_recv_lchan() {}
void trau_send_frame() {}
-int osmo_bsc_sigtran_send(struct gsm_subscriber_connection *conn, struct msgb *msg) { return 0; }
+/* Stub */
int osmo_bsc_sigtran_open_conn(struct gsm_subscriber_connection *conn, struct msgb *msg) { return 0; }
void bsc_sapi_n_reject(struct gsm_subscriber_connection *conn, uint8_t dlci, enum gsm0808_cause cause) {}
-void bsc_cipher_mode_compl(struct gsm_subscriber_connection *conn, struct msgb *msg, uint8_t chosen_encr) {}
+void bsc_cipher_mode_compl(struct gsm_subscriber_connection *conn, struct msgb *msg, uint8_t chosen_a5_n) {}
int bsc_compl_l3(struct gsm_lchan *lchan, struct msgb *msg, uint16_t chosen_channel)
{ return 0; }
-int bsc_paging_start(struct bsc_paging_params *params)
-{ return 0; }
void bsc_dtap(struct gsm_subscriber_connection *conn, uint8_t link_id, struct msgb *msg) {}
void bsc_assign_compl(struct gsm_subscriber_connection *conn, uint8_t rr_cause) {}
void bsc_cm_update(struct gsm_subscriber_connection *conn,
const uint8_t *cm2, uint8_t cm2_len,
const uint8_t *cm3, uint8_t cm3_len) {}
-struct gsm0808_handover_required;
-int bsc_tx_bssmap_ho_required(struct gsm_lchan *lchan, const struct gsm0808_cell_id_list2 *target_cells)
-{ return 0; }
-int bsc_tx_bssmap_ho_request_ack(struct gsm_subscriber_connection *conn, struct msgb *rr_ho_command)
-{ return 0; }
-int bsc_tx_bssmap_ho_detect(struct gsm_subscriber_connection *conn) { return 0; }
-enum handover_result bsc_tx_bssmap_ho_complete(struct gsm_subscriber_connection *conn,
- struct gsm_lchan *lchan) { return HO_RESULT_OK; }
-void bsc_tx_bssmap_ho_failure(struct gsm_subscriber_connection *conn) {}
-void osmo_bsc_sigtran_tx_reset(void) {}
-void osmo_bsc_sigtran_tx_reset_ack(void) {}
-void osmo_bsc_sigtran_reset(void) {}
-void bssmap_reset_alloc(void) {}
-void bssmap_reset_is_conn_ready(void) {}
+const char *osmo_mgcpc_ep_name(const struct osmo_mgcpc_ep *ep)
+{
+ return "fake-ep";
+}
+const char *osmo_mgcpc_ep_ci_name(const struct osmo_mgcpc_ep_ci *ci)
+{
+ return "fake-ci";
+}
+const struct mgcp_conn_peer *osmo_mgcpc_ep_ci_get_rtp_info(const struct osmo_mgcpc_ep_ci *ci)
+{
+ static struct mgcp_conn_peer ret = {
+ .addr = "1.2.3.4",
+ .port = 1234,
+ .endpoint = "fake-endpoint",
+ };
+ return &ret;
+}
+struct mgcp_client *osmo_mgcpc_ep_client(const struct osmo_mgcpc_ep *ep)
+{
+ return NULL;
+}
diff --git a/tests/handover/handover_test.ok b/tests/handover/handover_test.ok
deleted file mode 100644
index 678f9a34e..000000000
--- a/tests/handover/handover_test.ok
+++ /dev/null
@@ -1 +0,0 @@
-Test OK
diff --git a/tests/handover/handover_tests.ok b/tests/handover/handover_tests.ok
new file mode 100644
index 000000000..04241811c
--- /dev/null
+++ b/tests/handover/handover_tests.ok
@@ -0,0 +1,57 @@
+pass test_amr_tch_f_to_h.ho_vty
+pass test_amr_tch_f_to_h_balance_congestion.ho_vty
+pass test_amr_tch_f_to_h_congestion.ho_vty
+pass test_amr_tch_f_to_h_congestion_assignment.ho_vty
+pass test_amr_tch_f_to_h_congestion_assignment_2.ho_vty
+pass test_amr_tch_f_to_h_congestion_assignment_3.ho_vty
+pass test_amr_tch_h_and_afs_bias.ho_vty
+pass test_amr_tch_h_to_f_congestion.ho_vty
+pass test_amr_tch_h_to_f_congestion_two_cells.ho_vty
+pass test_amr_tch_h_to_f_rxlev.ho_vty
+pass test_amr_tch_h_to_f_rxlev_congested.ho_vty
+pass test_amr_tch_h_to_f_rxlev_oscillation.ho_vty
+pass test_amr_tch_h_to_f_rxqual.ho_vty
+pass test_amr_tch_h_to_f_rxqual_congested.ho_vty
+pass test_amr_tch_h_to_f_rxqual_oscillation.ho_vty
+pass test_balance_congestion.ho_vty
+pass test_balance_congestion_2.ho_vty
+pass test_balance_congestion_by_percentage.ho_vty
+pass test_balance_congestion_tchf_tchh.ho_vty
+pass test_bs_power.ho_vty
+pass test_congestion.ho_vty
+pass test_congestion_favor_best_target_rxlev.ho_vty
+pass test_congestion_intra_vs_inter_cell.ho_vty
+pass test_congestion_no_oscillation.ho_vty
+pass test_congestion_no_oscillation2.ho_vty
+pass test_disabled_ho_and_as.ho_vty
+pass test_dyn_ts_amr_tch_f_to_h_congestion_assignment.ho_vty
+pass test_dyn_ts_amr_tch_f_to_h_congestion_assignment_2.ho_vty
+pass test_dyn_ts_amr_tch_h_to_f_congestion_assignment_2.ho_vty
+pass test_dyn_ts_balance_congestion.ho_vty
+pass test_dyn_ts_congestion_tch_f_vs_tch_h.ho_vty
+pass test_dyn_ts_congestion_tch_f_vs_tch_h_2.ho_vty
+pass test_dyn_ts_favor_half_used_tch_h_as_target.ho_vty
+pass test_dyn_ts_favor_moving_half_used_tch_h.ho_vty
+pass test_dyn_ts_favor_static_ts_as_target.ho_vty
+pass test_ho_to_better_cell.ho_vty
+pass test_ho_to_better_cell_2.ho_vty
+pass test_hysteresis.ho_vty
+pass test_insufficient_measurements.ho_vty
+pass test_keep_efr_codec.ho_vty
+pass test_keep_fr_codec.ho_vty
+pass test_keep_hr_codec.ho_vty
+pass test_max_handovers.ho_vty
+pass test_max_ta.ho_vty
+pass test_meas_rep_multi_band.ho_vty
+pass test_min_rxlev_vs_congestion.ho_vty
+pass test_min_rxlev_vs_hysteresis.ho_vty
+pass test_neighbor_congested.ho_vty
+pass test_neighbor_full.ho_vty
+pass test_no_congestion.ho_vty
+pass test_penalty_timer.ho_vty
+pass test_resource_indication.ho_vty
+pass test_rxqual.ho_vty
+pass test_rxqual_vs_congestion.ho_vty
+pass test_stay_in_better_cell.ho_vty
+pass test_stay_in_better_cell_2.ho_vty
+pass test_story.ho_vty
diff --git a/tests/handover/handover_tests.sh b/tests/handover/handover_tests.sh
new file mode 100755
index 000000000..4be0c1018
--- /dev/null
+++ b/tests/handover/handover_tests.sh
@@ -0,0 +1,61 @@
+#!/bin/sh
+set -e
+tests_dir="${1:-.}"
+build_dir="${2:-.}"
+update="$3"
+test -d "$tests_dir"
+test -d "$build_dir"
+
+if [ -n "$update" -a "x$update" != "x-u" -a "x$update" != "x-U" ]; then
+ echo "unknown argument: $update"
+ exit 1
+fi
+
+one_test() {
+ test_path="$1"
+ test_name="$(basename "$test_path")"
+ got_out="$(mktemp "tmp.$test_name.stdout.XXXXX")"
+ got_err="$(mktemp "tmp.$test_name.stderr.XXXXX")"
+ set +e
+ "$build_dir"/handover_test "$test_path" > "$got_out" 2> "$got_err"
+ rc=$?
+ expect_out="$test_path.ok"
+ expect_err="$test_path.err"
+ if [ "x$rc" = "x0" -a "x$update" = "x-U" ]; then
+ cp "$got_out" "$expect_out"
+ cp "$got_err" "$expect_err"
+ else
+ if [ -f "$expect_out" ]; then
+ diff -u "$expect_out" "$got_out" >&2
+ fi
+ if [ -f "$expect_err" ]; then
+ diff -u "$expect_err" "$got_err" >&2
+ fi
+ fi
+ rm "$got_out"
+ rm "$got_err"
+ set -e
+ return $rc
+}
+
+results="$(mktemp "tmp.handover_test_results.XXXXX")"
+for test_path in "$tests_dir"/test*.ho_vty ; do
+ test_name="$(basename "$test_path")"
+ if one_test "$test_path"; then
+ echo "pass $test_name" >> "$results"
+ else
+ echo "FAIL $test_name" >> "$results"
+ fi
+done
+set +e
+cat "$results"
+failed="$(grep FAIL "$results")"
+if [ -z "$failed" -a "x$update" != "x" ]; then
+ cp "$results" "$tests_dir"/handover_tests.ok
+fi
+rm "$results"
+if [ -n "$failed" ]; then
+ echo "tests failed"
+ exit 1
+fi
+exit 0
diff --git a/tests/handover/neighbor_ident_test.c b/tests/handover/neighbor_ident_test.c
deleted file mode 100644
index 9acbea035..000000000
--- a/tests/handover/neighbor_ident_test.c
+++ /dev/null
@@ -1,270 +0,0 @@
-/* Test the neighbor_ident.h API */
-/*
- * (C) 2018 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
- * All Rights Reserved
- *
- * Author: Neels Hofmeyr <nhofmeyr@sysmocom.de>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU 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 <talloc.h>
-#include <stdio.h>
-#include <errno.h>
-
-#include <osmocom/gsm/gsm0808.h>
-
-#include <osmocom/bsc/neighbor_ident.h>
-
-static struct neighbor_ident_list *nil;
-
-static const struct neighbor_ident_key *k(int from_bts, uint16_t arfcn, uint8_t bsic)
-{
- static struct neighbor_ident_key key;
- key = (struct neighbor_ident_key){
- .from_bts = from_bts,
- .arfcn = arfcn,
- .bsic = bsic,
- };
- return &key;
-}
-
-static const struct gsm0808_cell_id_list2 cgi1 = {
- .id_discr = CELL_IDENT_WHOLE_GLOBAL,
- .id_list_len = 1,
- .id_list = {
- {
- .global = {
- .lai = {
- .plmn = { .mcc = 1, .mnc = 2, .mnc_3_digits = false },
- .lac = 3,
- },
- .cell_identity = 4,
- }
- },
- },
-};
-
-static const struct gsm0808_cell_id_list2 cgi2 = {
- .id_discr = CELL_IDENT_WHOLE_GLOBAL,
- .id_list_len = 2,
- .id_list = {
- {
- .global = {
- .lai = {
- .plmn = { .mcc = 1, .mnc = 2, .mnc_3_digits = false },
- .lac = 3,
- },
- .cell_identity = 4,
- }
- },
- {
- .global = {
- .lai = {
- .plmn = { .mcc = 5, .mnc = 6, .mnc_3_digits = true },
- .lac = 7,
- },
- .cell_identity = 8,
- }
- },
- },
-};
-
-static const struct gsm0808_cell_id_list2 lac1 = {
- .id_discr = CELL_IDENT_LAC,
- .id_list_len = 1,
- .id_list = {
- {
- .lac = 123
- },
- },
-};
-
-static const struct gsm0808_cell_id_list2 lac2 = {
- .id_discr = CELL_IDENT_LAC,
- .id_list_len = 2,
- .id_list = {
- {
- .lac = 456
- },
- {
- .lac = 789
- },
- },
-};
-
-static void print_cil(const struct gsm0808_cell_id_list2 *cil)
-{
- unsigned int i;
- if (!cil) {
- printf(" cell_id_list == NULL\n");
- return;
- }
- switch (cil->id_discr) {
- case CELL_IDENT_WHOLE_GLOBAL:
- printf(" cell_id_list cgi[%u] = {\n", cil->id_list_len);
- for (i = 0; i < cil->id_list_len; i++)
- printf(" %2d: %s\n", i, osmo_cgi_name(&cil->id_list[i].global));
- printf(" }\n");
- break;
- case CELL_IDENT_LAC:
- printf(" cell_id_list lac[%u] = {\n", cil->id_list_len);
- for (i = 0; i < cil->id_list_len; i++)
- printf(" %2d: %u\n", i, cil->id_list[i].lac);
- printf(" }\n");
- break;
- default:
- printf(" Unimplemented id_disc\n");
- }
-}
-
-static int print_nil_i;
-
-static bool nil_cb(const struct neighbor_ident_key *key, const struct gsm0808_cell_id_list2 *val,
- void *cb_data)
-{
- printf(" %2d: %s\n", print_nil_i++, neighbor_ident_key_name(key));
- print_cil(val);
- return true;
-}
-
-static void print_nil()
-{
- print_nil_i = 0;
- neighbor_ident_iter(nil, nil_cb, NULL);
- if (!print_nil_i)
- printf(" (empty)\n");
-}
-
-#define check_add(key, val, expect_rc) \
- do { \
- int rc; \
- rc = neighbor_ident_add(nil, key, val); \
- printf("neighbor_ident_add(" #key ", " #val ") --> expect rc=" #expect_rc ", got %d\n", rc); \
- if (rc != expect_rc) \
- printf("ERROR\n"); \
- print_nil(); \
- } while(0)
-
-#define check_del(key, expect_rc) \
- do { \
- bool rc; \
- rc = neighbor_ident_del(nil, key); \
- printf("neighbor_ident_del(" #key ") --> %s\n", rc ? "entry deleted" : "nothing deleted"); \
- if (rc != expect_rc) \
- printf("ERROR: expected: %s\n", expect_rc ? "entry deleted" : "nothing deleted"); \
- print_nil(); \
- } while(0)
-
-#define check_get(key, expect_rc) \
- do { \
- const struct gsm0808_cell_id_list2 *rc; \
- rc = neighbor_ident_get(nil, key); \
- printf("neighbor_ident_get(" #key ") --> %s\n", \
- rc ? "entry returned" : "NULL"); \
- if (((bool)expect_rc) != ((bool) rc)) \
- printf("ERROR: expected %s\n", expect_rc ? "an entry" : "NULL"); \
- if (rc) \
- print_cil(rc); \
- } while(0)
-
-int main(void)
-{
- void *ctx = talloc_named_const(NULL, 0, "neighbor_ident_test");
-
- printf("\n--- testing NULL neighbor_ident_list\n");
- nil = NULL;
- check_add(k(0, 1, 2), &cgi1, -ENOMEM);
- check_get(k(0, 1, 2), false);
- check_del(k(0, 1, 2), false);
-
- printf("\n--- adding entries, test that no two identical entries are added\n");
- nil = neighbor_ident_init(ctx);
- check_add(k(0, 1, 2), &cgi1, 1);
- check_get(k(0, 1, 2), true);
- check_add(k(0, 1, 2), &cgi1, 1);
- check_add(k(0, 1, 2), &cgi2, 2);
- check_add(k(0, 1, 2), &cgi2, 2);
- check_del(k(0, 1, 2), true);
-
- printf("\n--- Cannot mix cell identifier types for one entry\n");
- check_add(k(0, 1, 2), &cgi1, 1);
- check_add(k(0, 1, 2), &lac1, -EINVAL);
- check_del(k(0, 1, 2), true);
- neighbor_ident_free(nil);
-
- printf("\n--- BTS matching: specific BTS is stronger\n");
- nil = neighbor_ident_init(ctx);
- check_add(k(NEIGHBOR_IDENT_KEY_ANY_BTS, 1, 2), &lac1, 1);
- check_add(k(3, 1, 2), &lac2, 2);
- check_get(k(2, 1, 2), true);
- check_get(k(3, 1, 2), true);
- check_get(k(4, 1, 2), true);
- check_get(k(NEIGHBOR_IDENT_KEY_ANY_BTS, 1, 2), true);
- neighbor_ident_free(nil);
-
- printf("\n--- BSIC matching: 6bit and 9bit are different realms, and wildcard match is weaker\n");
- nil = neighbor_ident_init(ctx);
- check_add(k(0, 1, BSIC_ANY), &cgi1, 1);
- check_add(k(0, 1, 2), &lac1, 1);
- check_add(k(0, 1, 2), &lac2, 2);
- check_get(k(0, 1, 2), true);
- check_get(k(0, 1, 2), true);
- neighbor_ident_free(nil);
-
- printf("\n--- Value ranges\n");
- nil = neighbor_ident_init(ctx);
- check_add(k(0, 6, 1 << 6), &lac1, -ERANGE);
- check_add(k(0, 6, BSIC_ANY - 1), &lac1, -ERANGE);
- check_add(k(NEIGHBOR_IDENT_KEY_ANY_BTS - 1, 1, BSIC_ANY), &cgi2, -ERANGE);
- check_add(k(256, 1, BSIC_ANY), &cgi2, -ERANGE);
- check_add(k(0, 0, BSIC_ANY), &cgi1, 1);
- check_add(k(255, 65535, BSIC_ANY), &lac1, 1);
- check_add(k(0, 0, 0), &cgi2, 2);
- check_add(k(255, 65535, 0x3f), &lac2, 2);
-
- neighbor_ident_free(nil);
-
- printf("\n--- size limits\n");
- {
- int i;
- struct gsm0808_cell_id_list2 a = { .id_discr = CELL_IDENT_LAC };
- struct gsm0808_cell_id_list2 b = {
- .id_discr = CELL_IDENT_LAC,
- .id_list = {
- { .lac = 423 }
- },
- .id_list_len = 1,
- };
- for (i = 0; i < ARRAY_SIZE(a.id_list); i++) {
- a.id_list[a.id_list_len ++].lac = i;
- }
-
- nil = neighbor_ident_init(ctx);
-
- i = neighbor_ident_add(nil, k(0, 1, 2), &a);
- printf("Added first cell identifier list (added %u) --> rc = %d\n", a.id_list_len, i);
- i = neighbor_ident_add(nil, k(0, 1, 2), &b);
- printf("Added second cell identifier list (tried to add %u) --> rc = %d\n", b.id_list_len, i);
- if (i != -ENOSPC)
- printf("ERROR: expected rc=%d\n", -ENOSPC);
- neighbor_ident_free(nil);
- }
-
- OSMO_ASSERT(talloc_total_blocks(ctx) == 1);
- talloc_free(ctx);
-
- return 0;
-}
diff --git a/tests/handover/neighbor_ident_test.err b/tests/handover/neighbor_ident_test.err
deleted file mode 100644
index e69de29bb..000000000
--- a/tests/handover/neighbor_ident_test.err
+++ /dev/null
diff --git a/tests/handover/neighbor_ident_test.ok b/tests/handover/neighbor_ident_test.ok
deleted file mode 100644
index 961a33cdc..000000000
--- a/tests/handover/neighbor_ident_test.ok
+++ /dev/null
@@ -1,186 +0,0 @@
-
---- testing NULL neighbor_ident_list
-neighbor_ident_add(k(0, 1, 2), &cgi1) --> expect rc=-ENOMEM, got -12
- (empty)
-neighbor_ident_get(k(0, 1, 2)) --> NULL
-neighbor_ident_del(k(0, 1, 2)) --> nothing deleted
- (empty)
-
---- adding entries, test that no two identical entries are added
-neighbor_ident_add(k(0, 1, 2), &cgi1) --> expect rc=1, got 1
- 0: BTS 0 to ARFCN 1 BSIC 2
- cell_id_list cgi[1] = {
- 0: 001-02-3-4
- }
-neighbor_ident_get(k(0, 1, 2)) --> entry returned
- cell_id_list cgi[1] = {
- 0: 001-02-3-4
- }
-neighbor_ident_add(k(0, 1, 2), &cgi1) --> expect rc=1, got 1
- 0: BTS 0 to ARFCN 1 BSIC 2
- cell_id_list cgi[1] = {
- 0: 001-02-3-4
- }
-neighbor_ident_add(k(0, 1, 2), &cgi2) --> expect rc=2, got 2
- 0: BTS 0 to ARFCN 1 BSIC 2
- cell_id_list cgi[2] = {
- 0: 001-02-3-4
- 1: 005-006-7-8
- }
-neighbor_ident_add(k(0, 1, 2), &cgi2) --> expect rc=2, got 2
- 0: BTS 0 to ARFCN 1 BSIC 2
- cell_id_list cgi[2] = {
- 0: 001-02-3-4
- 1: 005-006-7-8
- }
-neighbor_ident_del(k(0, 1, 2)) --> entry deleted
- (empty)
-
---- Cannot mix cell identifier types for one entry
-neighbor_ident_add(k(0, 1, 2), &cgi1) --> expect rc=1, got 1
- 0: BTS 0 to ARFCN 1 BSIC 2
- cell_id_list cgi[1] = {
- 0: 001-02-3-4
- }
-neighbor_ident_add(k(0, 1, 2), &lac1) --> expect rc=-EINVAL, got -22
- 0: BTS 0 to ARFCN 1 BSIC 2
- cell_id_list cgi[1] = {
- 0: 001-02-3-4
- }
-neighbor_ident_del(k(0, 1, 2)) --> entry deleted
- (empty)
-
---- BTS matching: specific BTS is stronger
-neighbor_ident_add(k(NEIGHBOR_IDENT_KEY_ANY_BTS, 1, 2), &lac1) --> expect rc=1, got 1
- 0: BTS * to ARFCN 1 BSIC 2
- cell_id_list lac[1] = {
- 0: 123
- }
-neighbor_ident_add(k(3, 1, 2), &lac2) --> expect rc=2, got 2
- 0: BTS * to ARFCN 1 BSIC 2
- cell_id_list lac[1] = {
- 0: 123
- }
- 1: BTS 3 to ARFCN 1 BSIC 2
- cell_id_list lac[2] = {
- 0: 456
- 1: 789
- }
-neighbor_ident_get(k(2, 1, 2)) --> entry returned
- cell_id_list lac[1] = {
- 0: 123
- }
-neighbor_ident_get(k(3, 1, 2)) --> entry returned
- cell_id_list lac[2] = {
- 0: 456
- 1: 789
- }
-neighbor_ident_get(k(4, 1, 2)) --> entry returned
- cell_id_list lac[1] = {
- 0: 123
- }
-neighbor_ident_get(k(NEIGHBOR_IDENT_KEY_ANY_BTS, 1, 2)) --> entry returned
- cell_id_list lac[1] = {
- 0: 123
- }
-
---- BSIC matching: 6bit and 9bit are different realms, and wildcard match is weaker
-neighbor_ident_add(k(0, 1, BSIC_ANY), &cgi1) --> expect rc=1, got 1
- 0: BTS 0 to ARFCN 1 (any BSIC)
- cell_id_list cgi[1] = {
- 0: 001-02-3-4
- }
-neighbor_ident_add(k(0, 1, 2), &lac1) --> expect rc=1, got 1
- 0: BTS 0 to ARFCN 1 (any BSIC)
- cell_id_list cgi[1] = {
- 0: 001-02-3-4
- }
- 1: BTS 0 to ARFCN 1 BSIC 2
- cell_id_list lac[1] = {
- 0: 123
- }
-neighbor_ident_add(k(0, 1, 2), &lac2) --> expect rc=2, got 3
-ERROR
- 0: BTS 0 to ARFCN 1 (any BSIC)
- cell_id_list cgi[1] = {
- 0: 001-02-3-4
- }
- 1: BTS 0 to ARFCN 1 BSIC 2
- cell_id_list lac[3] = {
- 0: 123
- 1: 456
- 2: 789
- }
-neighbor_ident_get(k(0, 1, 2)) --> entry returned
- cell_id_list lac[3] = {
- 0: 123
- 1: 456
- 2: 789
- }
-neighbor_ident_get(k(0, 1, 2)) --> entry returned
- cell_id_list lac[3] = {
- 0: 123
- 1: 456
- 2: 789
- }
-
---- Value ranges
-neighbor_ident_add(k(0, 6, 1 << 6), &lac1) --> expect rc=-ERANGE, got -34
- (empty)
-neighbor_ident_add(k(0, 6, BSIC_ANY - 1), &lac1) --> expect rc=-ERANGE, got -34
- (empty)
-neighbor_ident_add(k(NEIGHBOR_IDENT_KEY_ANY_BTS - 1, 1, BSIC_ANY), &cgi2) --> expect rc=-ERANGE, got -34
- (empty)
-neighbor_ident_add(k(256, 1, BSIC_ANY), &cgi2) --> expect rc=-ERANGE, got -34
- (empty)
-neighbor_ident_add(k(0, 0, BSIC_ANY), &cgi1) --> expect rc=1, got 1
- 0: BTS 0 to ARFCN 0 (any BSIC)
- cell_id_list cgi[1] = {
- 0: 001-02-3-4
- }
-neighbor_ident_add(k(255, 65535, BSIC_ANY), &lac1) --> expect rc=1, got 1
- 0: BTS 0 to ARFCN 0 (any BSIC)
- cell_id_list cgi[1] = {
- 0: 001-02-3-4
- }
- 1: BTS 255 to ARFCN 65535 (any BSIC)
- cell_id_list lac[1] = {
- 0: 123
- }
-neighbor_ident_add(k(0, 0, 0), &cgi2) --> expect rc=2, got 2
- 0: BTS 0 to ARFCN 0 (any BSIC)
- cell_id_list cgi[1] = {
- 0: 001-02-3-4
- }
- 1: BTS 255 to ARFCN 65535 (any BSIC)
- cell_id_list lac[1] = {
- 0: 123
- }
- 2: BTS 0 to ARFCN 0 BSIC 0
- cell_id_list cgi[2] = {
- 0: 001-02-3-4
- 1: 005-006-7-8
- }
-neighbor_ident_add(k(255, 65535, 0x3f), &lac2) --> expect rc=2, got 2
- 0: BTS 0 to ARFCN 0 (any BSIC)
- cell_id_list cgi[1] = {
- 0: 001-02-3-4
- }
- 1: BTS 255 to ARFCN 65535 (any BSIC)
- cell_id_list lac[1] = {
- 0: 123
- }
- 2: BTS 0 to ARFCN 0 BSIC 0
- cell_id_list cgi[2] = {
- 0: 001-02-3-4
- 1: 005-006-7-8
- }
- 3: BTS 255 to ARFCN 65535 BSIC 63
- cell_id_list lac[2] = {
- 0: 456
- 1: 789
- }
-
---- size limits
-Added first cell identifier list (added 127) --> rc = 127
-Added second cell identifier list (tried to add 1) --> rc = -28
diff --git a/tests/handover/test_amr_tch_f_to_h.ho_vty b/tests/handover/test_amr_tch_f_to_h.ho_vty
new file mode 100644
index 000000000..22c5e8752
--- /dev/null
+++ b/tests/handover/test_amr_tch_f_to_h.ho_vty
@@ -0,0 +1,14 @@
+# TCH/F to TCH/H changing with AMR codec
+# The MS is using AMR V3 codec, the better cell is congested at TCH/F
+# slots. The handover is performed to non-congested TCH/H slots.
+
+create-n-bts 2
+network
+ bts 1
+ handover2 min-free-slots tch/f 4
+set-ts-use trx 0 0 states * TCH/F - - - - - -
+meas-rep lchan 0 0 1 0 rxlev 20 rxqual 0 ta 0 neighbors 30
+expect-ho from lchan 0 0 1 0 to lchan 1 0 5 0
+expect-ts-use trx 0 0 states * - - - - - - -
+expect-ts-use trx 1 0 states * - - - - TCH/H- - -
+
diff --git a/tests/handover/test_amr_tch_f_to_h_balance_congestion.ho_vty b/tests/handover/test_amr_tch_f_to_h_balance_congestion.ho_vty
new file mode 100644
index 000000000..1b8969e1c
--- /dev/null
+++ b/tests/handover/test_amr_tch_f_to_h_balance_congestion.ho_vty
@@ -0,0 +1,16 @@
+# Congestion check: Balancing congestion by handover TCH/F -> TCH/H
+# Two BTS, one MS in the first congested BTS must handover to
+# less-congested TCH/H of second BTS, in order to balance congestion
+
+create-n-bts 2
+network
+ handover2 min-free-slots tch/f 4
+ handover2 min-free-slots tch/h 4
+set-ts-use trx 0 0 states * TCH/F TCH/F - - TCH/H- - -
+meas-rep lchan 0 0 1 0 rxlev 30 rxqual 0 ta 0 neighbors 30
+expect-no-chan
+congestion-check
+expect-ho from lchan 0 0 1 0 to lchan 1 0 1 0
+expect-ts-use trx 0 0 states * - TCH/F - - TCH/H- - -
+expect-ts-use trx 1 0 states * TCH/F - - - - - -
+
diff --git a/tests/handover/test_amr_tch_f_to_h_congestion.ho_vty b/tests/handover/test_amr_tch_f_to_h_congestion.ho_vty
new file mode 100644
index 000000000..5c934941e
--- /dev/null
+++ b/tests/handover/test_amr_tch_f_to_h_congestion.ho_vty
@@ -0,0 +1,19 @@
+# Congestion check: Solving congestion by handover TCH/F -> TCH/H
+# Two BTS, one MS in the first congested BTS must handover to
+# non-congested TCH/H of second BTS, in order to solve congestion
+
+create-n-bts 2
+network
+ bts 0
+ handover2 min-free-slots tch/f 4
+ handover2 min-free-slots tch/h 4
+ bts 1
+ handover2 min-free-slots tch/f 4
+set-ts-use trx 0 0 states * TCH/F - - - - - -
+meas-rep lchan 0 0 1 0 rxlev 30 rxqual 0 ta 0 neighbors 30
+expect-no-chan
+congestion-check
+expect-ho from lchan 0 0 1 0 to lchan 1 0 5 0
+expect-ts-use trx 0 0 states * - - - - - - -
+expect-ts-use trx 1 0 states * - - - - TCH/H- - -
+
diff --git a/tests/handover/test_amr_tch_f_to_h_congestion_assignment.ho_vty b/tests/handover/test_amr_tch_f_to_h_congestion_assignment.ho_vty
new file mode 100644
index 000000000..84f34ff61
--- /dev/null
+++ b/tests/handover/test_amr_tch_f_to_h_congestion_assignment.ho_vty
@@ -0,0 +1,18 @@
+# Congestion check: Upgrading worst candidate from TCH/H -> TCH/F
+# There is only one BTS. The TCH/H slots are congested. Since
+# assignment is performed to less-congested TCH/F, the candidate with
+# the worst RX level is chosen.
+
+create-n-bts 1
+network
+ bts 0
+ handover2 min-free-slots tch/f 4
+ handover2 min-free-slots tch/h 4
+set-ts-use trx 0 0 states * - - - - TCH/HH TCH/H- -
+meas-rep lchan 0 0 5 0 rxlev 30 rxqual 0 ta 0
+meas-rep lchan 0 0 5 1 rxlev 34 rxqual 0 ta 0
+meas-rep lchan 0 0 6 0 rxlev 20 rxqual 0 ta 0
+expect-no-chan
+congestion-check
+expect-as from lchan 0 0 6 0 to lchan 0 0 1 0
+expect-ts-use trx 0 0 states * TCH/F - - - TCH/HH - -
diff --git a/tests/handover/test_amr_tch_f_to_h_congestion_assignment_2.ho_vty b/tests/handover/test_amr_tch_f_to_h_congestion_assignment_2.ho_vty
new file mode 100644
index 000000000..2fa08da17
--- /dev/null
+++ b/tests/handover/test_amr_tch_f_to_h_congestion_assignment_2.ho_vty
@@ -0,0 +1,27 @@
+# Congestion check: Upgrading worst candidate from TCH/H -> TCH/F
+# There is only one BTS. The TCH/H slots are congested. Since
+# assignment is performed to less-congested TCH/F, the candidate with
+# the worst RX level is chosen. (So far like test 22.)
+# After that, trigger more congestion checks to ensure stability.
+
+create-n-bts 1
+network
+ bts 0
+ handover2 min-free-slots tch/f 2
+ handover2 min-free-slots tch/h 4
+set-ts-use trx 0 0 states * - - - - TCH/HH TCH/H- -
+meas-rep lchan 0 0 5 0 rxlev 30 rxqual 0 ta 0
+meas-rep lchan 0 0 5 1 rxlev 34 rxqual 0 ta 0
+meas-rep lchan 0 0 6 0 rxlev 20 rxqual 0 ta 0
+expect-no-chan
+congestion-check
+expect-as from lchan 0 0 6 0 to lchan 0 0 1 0
+expect-ts-use trx 0 0 states * TCH/F - - - TCH/HH - -
+congestion-check
+expect-as from lchan 0 0 5 0 to lchan 0 0 2 0
+expect-ts-use trx 0 0 states * TCH/F TCH/F - - TCH/-H - -
+congestion-check
+expect-no-chan
+congestion-check
+expect-no-chan
+
diff --git a/tests/handover/test_amr_tch_f_to_h_congestion_assignment_3.ho_vty b/tests/handover/test_amr_tch_f_to_h_congestion_assignment_3.ho_vty
new file mode 100644
index 000000000..0dca250d2
--- /dev/null
+++ b/tests/handover/test_amr_tch_f_to_h_congestion_assignment_3.ho_vty
@@ -0,0 +1,15 @@
+# Congestion check: Balancing congestion by handover TCH/F -> TCH/H
+# One BTS, and TCH/F are considered congested, TCH/H are not.
+
+create-n-bts 1
+network
+ bts 0
+ handover2 min-free-slots tch/f 3
+ handover2 min-free-slots tch/h 0
+set-ts-use trx 0 0 states * TCH/F TCH/F - - TCH/H- - -
+meas-rep lchan 0 0 1 0 rxlev 30 rxqual 0 ta 0 neighbors 30
+expect-no-chan
+congestion-check
+expect-as from lchan 0 0 1 0 to lchan 0 0 5 1
+expect-ts-use trx 0 0 states * - TCH/F - - TCH/HH - -
+
diff --git a/tests/handover/test_amr_tch_h_and_afs_bias.ho_vty b/tests/handover/test_amr_tch_h_and_afs_bias.ho_vty
new file mode 100644
index 000000000..462cb0d59
--- /dev/null
+++ b/tests/handover/test_amr_tch_h_and_afs_bias.ho_vty
@@ -0,0 +1,13 @@
+# TCH/H has good RxLev and RxQual, AFS bias should not move it to TCH/F
+
+network
+ handover2 power budget hysteresis 3
+ handover2 min rxlev -90
+ handover2 min rxqual 5
+ handover2 afs-bias rxlev 1
+
+create-bts trx-count 1 timeslots c+s4 TCH/F TCH/F TCH/F TCH/H TCH/H TCH/H PDCH
+set-ts-use trx 0 0 states * - - - TCH/H- - - *
+meas-rep lchan 0 0 4 0 rxlev 50 rxqual 1 ta 0
+# The TCH/H should stay where it is, because its levels are fine.
+expect-no-chan
diff --git a/tests/handover/test_amr_tch_h_to_f_congestion.ho_vty b/tests/handover/test_amr_tch_h_to_f_congestion.ho_vty
new file mode 100644
index 000000000..0252d9f23
--- /dev/null
+++ b/tests/handover/test_amr_tch_h_to_f_congestion.ho_vty
@@ -0,0 +1,14 @@
+# Congestion check: Balancing congestion by handover TCH/H -> TCH/F
+# One BTS, TCH/H are congested and should move to TCH/F.
+
+network
+ handover2 min-free-slots tch/f 0
+ handover2 min-free-slots tch/h 6
+
+create-bts trx-count 1 timeslots c+s4 TCH/F TCH/F TCH/F TCH/H TCH/H TCH/H PDCH
+set-ts-use trx 0 0 states * - - - TCH/H- - - *
+meas-rep lchan 0 0 4 0 rxlev 30 rxqual 0 ta 0
+expect-no-chan
+congestion-check
+expect-as from lchan 0 0 4 0 to lchan 0 0 1 0
+expect-ts-use trx 0 0 states * TCH/F - - - - - *
diff --git a/tests/handover/test_amr_tch_h_to_f_congestion_two_cells.ho_vty b/tests/handover/test_amr_tch_h_to_f_congestion_two_cells.ho_vty
new file mode 100644
index 000000000..fecd06853
--- /dev/null
+++ b/tests/handover/test_amr_tch_h_to_f_congestion_two_cells.ho_vty
@@ -0,0 +1,17 @@
+# Congestion check: Balancing congestion by handover TCH/H -> TCH/F
+# TCH/H are congested and should move to TCH/F
+# There are two cells, and the neighbor has weaker rxlev, so stay in the same cell.
+
+network
+ handover2 min-free-slots tch/f 0
+ handover2 min-free-slots tch/h 6
+
+create-bts trx-count 1 timeslots c+s4 TCH/F TCH/F TCH/F TCH/H TCH/H TCH/H PDCH
+create-bts trx-count 1 timeslots c+s4 TCH/F TCH/F TCH/F TCH/H TCH/H TCH/H PDCH
+set-ts-use trx 1 0 states * - - - TCH/H- - - *
+meas-rep repeat 10 lchan 1 0 4 0 rxlev 30 rxqual 0 ta 0 neighbors 20
+expect-no-chan
+congestion-check
+expect-as from lchan 1 0 4 0 to lchan 1 0 1 0
+expect-ts-use trx 0 0 states * - - - - - - *
+expect-ts-use trx 1 0 states * TCH/F - - - - - *
diff --git a/tests/handover/test_amr_tch_h_to_f_rxlev.ho_vty b/tests/handover/test_amr_tch_h_to_f_rxlev.ho_vty
new file mode 100644
index 000000000..a22ad6d85
--- /dev/null
+++ b/tests/handover/test_amr_tch_h_to_f_rxlev.ho_vty
@@ -0,0 +1,16 @@
+# Low RxLev causes upgrade of TCH/H to TCH/F
+
+network
+ handover2 afs-bias rxlev 0
+ handover2 min rxlev -80
+ handover2 window rxlev averaging 10
+
+create-bts trx-count 1 timeslots c+s4 TCH/F TCH/F TCH/F TCH/H TCH/H TCH/H PDCH
+set-ts-use trx 0 0 states * - - - TCH/H- - - *
+meas-rep repeat 9 lchan 0 0 4 0 rxlev 23 rxqual 1 ta 0
+# not enough values for rxlev averaging
+expect-no-chan
+meas-rep lchan 0 0 4 0 rxlev 23 rxqual 1 ta 0
+# average rxlev is now -110 + 23 = -87 < -80: reassign to TCH/F due to bad rxlev
+expect-as from lchan 0 0 4 0 to lchan 0 0 1 0
+expect-ts-use trx 0 0 states * TCH/F - - - - - *
diff --git a/tests/handover/test_amr_tch_h_to_f_rxlev_congested.ho_vty b/tests/handover/test_amr_tch_h_to_f_rxlev_congested.ho_vty
new file mode 100644
index 000000000..776b09311
--- /dev/null
+++ b/tests/handover/test_amr_tch_h_to_f_rxlev_congested.ho_vty
@@ -0,0 +1,63 @@
+# Low RxLev causes upgrade of TCH/H to TCH/F
+
+network
+ handover2 afs-bias rxlev 0
+ handover2 min rxlev -80
+ handover2 window rxlev averaging 10
+
+create-bts trx-count 1 timeslots c+s4 TCH/F TCH/F TCH/F TCH/H TCH/H PDCH PDCH
+
+
+set-ts-use trx 0 0 states * - - - TCH/HH TCH/HH * *
+meas-rep repeat 9 lchan 0 0 4 0 rxlev 23 rxqual 1 ta 0
+# not enough values for rxlev averaging
+expect-no-chan
+meas-rep lchan 0 0 4 0 rxlev 23 rxqual 1 ta 0
+# average rxlev is now -110 + 23 = -87 < -80: reassign to TCH/F due to bad rxlev
+expect-as from lchan 0 0 4 0 to lchan 0 0 1 0
+expect-ts-use trx 0 0 states * TCH/F - - TCH/-H TCH/HH * *
+
+
+# This situation actually balances congestion
+set-ts-use trx 0 0 states * TCH/F - - TCH/HH TCH/HH * *
+meas-rep repeat 9 lchan 0 0 4 0 rxlev 23 rxqual 1 ta 0
+# not enough values for rxlev averaging
+expect-no-chan
+meas-rep lchan 0 0 4 0 rxlev 23 rxqual 1 ta 0
+# average rxlev is now -110 + 23 = -87 < -80: reassign to TCH/F due to bad rxlev
+expect-as from lchan 0 0 4 0 to lchan 0 0 2 0
+expect-ts-use trx 0 0 states * TCH/F TCH/F - TCH/-H TCH/HH * *
+
+# This situation moves congestion from TCH/H to TCH/F (TCH/H was 100% congested, then makes TCH/F 100% congested)
+# The congestion requirements would normally forbid this, but since this is an "RxQual emergency", we should reassign.
+set-ts-use trx 0 0 states * TCH/F TCH/F - TCH/HH TCH/HH * *
+meas-rep repeat 9 lchan 0 0 4 0 rxlev 23 rxqual 1 ta 0
+# not enough values for rxlev averaging
+expect-no-chan
+meas-rep lchan 0 0 4 0 rxlev 23 rxqual 1 ta 0
+# average rxlev is now -110 + 23 = -87 < -80: reassign to TCH/F due to bad rxlev
+expect-as from lchan 0 0 4 0 to lchan 0 0 3 0
+expect-ts-use trx 0 0 states * TCH/F TCH/F TCH/F TCH/-H TCH/HH * *
+
+# This situation worsens congestion (TCH/H was 50% congested, then makes TCH/F 100% congested)
+# The congestion requirements would normally forbid this, but since this is an "RxQual emergency", we should reassign.
+set-ts-use trx 0 0 states * TCH/F TCH/F - TCH/H- TCH/HH * *
+meas-rep repeat 9 lchan 0 0 4 0 rxlev 23 rxqual 1 ta 0
+# not enough values for rxlev averaging
+expect-no-chan
+meas-rep lchan 0 0 4 0 rxlev 23 rxqual 1 ta 0
+# average rxlev is now -110 + 23 = -87 < -80: reassign to TCH/F due to bad rxlev
+expect-as from lchan 0 0 4 0 to lchan 0 0 3 0
+expect-ts-use trx 0 0 states * TCH/F TCH/F TCH/F - TCH/HH * *
+
+
+# This situation creates congestion (TCH/H was not congested, then makes TCH/F 50% congested)
+# The congestion requirements would normally forbid this, but since this is an "RxQual emergency", we should reassign.
+set-ts-use trx 0 0 states * TCH/F - - TCH/H- - * *
+meas-rep repeat 9 lchan 0 0 4 0 rxlev 23 rxqual 1 ta 0
+# not enough values for rxlev averaging
+expect-no-chan
+meas-rep lchan 0 0 4 0 rxlev 23 rxqual 1 ta 0
+# average rxlev is now -110 + 23 = -87 < -80: reassign to TCH/F due to bad rxlev
+expect-as from lchan 0 0 4 0 to lchan 0 0 2 0
+expect-ts-use trx 0 0 states * TCH/F TCH/F - - - * *
diff --git a/tests/handover/test_amr_tch_h_to_f_rxlev_oscillation.ho_vty b/tests/handover/test_amr_tch_h_to_f_rxlev_oscillation.ho_vty
new file mode 100644
index 000000000..2ef927dde
--- /dev/null
+++ b/tests/handover/test_amr_tch_h_to_f_rxlev_oscillation.ho_vty
@@ -0,0 +1,20 @@
+# Low RxLev causes upgrade of TCH/H to TCH/F.
+# That leads to congestion of TCH/F, but do not handover back to non-congested TCH/H.
+
+network
+ handover2 afs-bias rxlev 0
+ handover2 min rxlev -80
+ handover2 window rxlev averaging 1
+ handover2 min-free-slots tch/f 3
+ handover2 min-free-slots tch/h 4
+
+create-bts trx-count 1 timeslots c+s4 TCH/F TCH/F TCH/F TCH/H TCH/H TCH/H PDCH
+set-ts-use trx 0 0 states * - - - TCH/H- - - *
+meas-rep lchan 0 0 4 0 rxlev 23 rxqual 1 ta 0
+# average rxlev is now -110 + 23 = -87 < -80: reassign to TCH/F due to bad rxlev
+expect-as from lchan 0 0 4 0 to lchan 0 0 1 0
+expect-ts-use trx 0 0 states * TCH/F - - - - - *
+
+meas-rep lchan 0 0 1 0 rxlev 23 rxqual 1 ta 0
+congestion-check
+expect-no-chan
diff --git a/tests/handover/test_amr_tch_h_to_f_rxqual.ho_vty b/tests/handover/test_amr_tch_h_to_f_rxqual.ho_vty
new file mode 100644
index 000000000..f3a2a9086
--- /dev/null
+++ b/tests/handover/test_amr_tch_h_to_f_rxqual.ho_vty
@@ -0,0 +1,39 @@
+# Low RxQual causes upgrade of TCH/H to TCH/F
+
+network
+ handover2 afs-bias rxlev 0
+ handover2 min rxqual 5
+ handover2 window rxqual averaging 2
+
+create-bts trx-count 1 timeslots c+s4 TCH/F TCH/F TCH/F TCH/H TCH/H TCH/H PDCH
+set-ts-use trx 0 0 states * - - - TCH/H- - - *
+meas-rep lchan 0 0 4 0 rxlev 30 rxqual 6 ta 0
+# not enough valus for rxqual averaging
+expect-no-chan
+meas-rep lchan 0 0 4 0 rxlev 30 rxqual 6 ta 0
+# average rxqual now at 6 which is worse than 5, reassign to TCH/F due to bad rxqual.
+expect-as from lchan 0 0 4 0 to lchan 0 0 1 0
+expect-ts-use trx 0 0 states * TCH/F - - - - - *
+meas-rep repeat 2 lchan 0 0 1 0 rxlev 30 rxqual 5 ta 0
+
+# After the upgrade to TCH/F, there should be a penalty timer against re-assgnment within this cell.
+# Configure congestion resolution so that it would normally want to do a re-assignment:
+network
+ handover2 min-free-slots tch/f 3
+ handover2 window rxlev averaging 1
+ handover2 min rxlev -90
+
+# The penalty timer is still active, no re-assignment from congestion of TCH/H
+congestion-check
+expect-no-chan
+expect-ts-use trx 0 0 states * TCH/F - - - - - *
+
+# But handover to another cell is not held off
+create-bts trx-count 1 timeslots c+s4 TCH/F TCH/F TCH/F TCH/H TCH/H TCH/H PDCH
+expect-ts-use trx 0 0 states * TCH/F - - - - - *
+expect-ts-use trx 1 0 states * - - - - - - *
+
+meas-rep lchan 0 0 1 0 rxlev 30 rxqual 5 ta 0 neighbors 35
+expect-ho from lchan 0 0 1 0 to lchan 1 0 4 0
+expect-ts-use trx 0 0 states * - - - - - - *
+expect-ts-use trx 1 0 states * - - - TCH/H- - - *
diff --git a/tests/handover/test_amr_tch_h_to_f_rxqual_congested.ho_vty b/tests/handover/test_amr_tch_h_to_f_rxqual_congested.ho_vty
new file mode 100644
index 000000000..79ff0a72f
--- /dev/null
+++ b/tests/handover/test_amr_tch_h_to_f_rxqual_congested.ho_vty
@@ -0,0 +1,68 @@
+# Low RxQual causes upgrade of TCH/H to TCH/F, also when the cell is congested
+
+network
+ handover2 afs-bias rxlev 0
+ handover2 min rxqual 5
+ handover2 min rxlev -90
+ handover2 window rxqual averaging 2
+ handover2 min-free-slots tch/f 2
+ handover2 min-free-slots tch/h 2
+
+create-bts trx-count 1 timeslots c+s4 TCH/F TCH/F TCH/F TCH/H TCH/H PDCH PDCH
+
+# This situation actually reduces congestion
+set-ts-use trx 0 0 states * - - - TCH/HH TCH/HH * *
+meas-rep lchan 0 0 4 0 rxlev 50 rxqual 6 ta 0
+# not enough values for rxqual averaging
+expect-no-chan
+meas-rep lchan 0 0 4 0 rxlev 50 rxqual 6 ta 0
+# average rxqual now at 6 which is worse than 5, reassign to TCH/F due to bad rxqual.
+expect-as from lchan 0 0 4 0 to lchan 0 0 1 0
+expect-ts-use trx 0 0 states * TCH/F - - TCH/-H TCH/HH * *
+
+
+# This situation actually balances congestion
+set-ts-use trx 0 0 states * TCH/F - - TCH/HH TCH/HH * *
+meas-rep lchan 0 0 4 0 rxlev 50 rxqual 6 ta 0
+# not enough values for rxqual averaging
+expect-no-chan
+meas-rep lchan 0 0 4 0 rxlev 50 rxqual 6 ta 0
+# average rxqual now at 6 which is worse than 5, reassign to TCH/F due to bad rxqual.
+expect-as from lchan 0 0 4 0 to lchan 0 0 2 0
+expect-ts-use trx 0 0 states * TCH/F TCH/F - TCH/-H TCH/HH * *
+
+
+# This situation moves congestion from TCH/H to TCH/F (TCH/H was 100% congested, then makes TCH/F 100% congested)
+# The congestion requirements would normally forbid this, but since this is an "RxQual emergency", we should reassign.
+set-ts-use trx 0 0 states * TCH/F TCH/F - TCH/HH TCH/HH * *
+meas-rep lchan 0 0 4 0 rxlev 50 rxqual 6 ta 0
+# not enough values for rxqual averaging
+expect-no-chan
+meas-rep lchan 0 0 4 0 rxlev 50 rxqual 6 ta 0
+# average rxqual now at 6 which is worse than 5, reassign to TCH/F due to bad rxqual.
+expect-as from lchan 0 0 4 0 to lchan 0 0 3 0
+expect-ts-use trx 0 0 states * TCH/F TCH/F TCH/F TCH/-H TCH/HH * *
+
+
+# This situation worsens congestion (TCH/H was 50% congested, then makes TCH/F 100% congested)
+# The congestion requirements would normally forbid this, but since this is an "RxQual emergency", we should reassign.
+set-ts-use trx 0 0 states * TCH/F TCH/F - TCH/H- TCH/HH * *
+meas-rep lchan 0 0 4 0 rxlev 50 rxqual 6 ta 0
+# not enough values for rxqual averaging
+expect-no-chan
+meas-rep lchan 0 0 4 0 rxlev 50 rxqual 6 ta 0
+# average rxqual now at 6 which is worse than 5, reassign to TCH/F due to bad rxqual.
+expect-as from lchan 0 0 4 0 to lchan 0 0 3 0
+expect-ts-use trx 0 0 states * TCH/F TCH/F TCH/F - TCH/HH * *
+
+
+# This situation creates congestion (TCH/H was not congested, then makes TCH/F 50% congested)
+# The congestion requirements would normally forbid this, but since this is an "RxQual emergency", we should reassign.
+set-ts-use trx 0 0 states * TCH/F - - TCH/H- - * *
+meas-rep lchan 0 0 4 0 rxlev 50 rxqual 6 ta 0
+# not enough values for rxqual averaging
+expect-no-chan
+meas-rep lchan 0 0 4 0 rxlev 50 rxqual 6 ta 0
+# average rxqual now at 6 which is worse than 5, reassign to TCH/F due to bad rxqual.
+expect-as from lchan 0 0 4 0 to lchan 0 0 2 0
+expect-ts-use trx 0 0 states * TCH/F TCH/F - - - * *
diff --git a/tests/handover/test_amr_tch_h_to_f_rxqual_oscillation.ho_vty b/tests/handover/test_amr_tch_h_to_f_rxqual_oscillation.ho_vty
new file mode 100644
index 000000000..e628f032a
--- /dev/null
+++ b/tests/handover/test_amr_tch_h_to_f_rxqual_oscillation.ho_vty
@@ -0,0 +1,20 @@
+# Low RxQual causes upgrade of TCH/H to TCH/F.
+# That leads to congestion of TCH/F, but do not handover back to non-congested TCH/H.
+
+network
+ handover2 afs-bias rxlev 0
+ handover2 min rxqual 5
+ handover2 window rxqual averaging 1
+ handover2 min-free-slots tch/f 3
+ handover2 min-free-slots tch/h 4
+
+create-bts trx-count 1 timeslots c+s4 TCH/F TCH/F TCH/F TCH/H TCH/H TCH/H PDCH
+set-ts-use trx 0 0 states * - - - TCH/H- - - *
+meas-rep lchan 0 0 4 0 rxlev 30 rxqual 6 ta 0
+# average rxlev is now -110 + 23 = -87 < -80: reassign to TCH/F due to bad rxlev
+expect-as from lchan 0 0 4 0 to lchan 0 0 1 0
+expect-ts-use trx 0 0 states * TCH/F - - - - - *
+
+meas-rep lchan 0 0 1 0 rxlev 30 rxqual 6 ta 0
+congestion-check
+expect-no-chan
diff --git a/tests/handover/test_balance_congestion.ho_vty b/tests/handover/test_balance_congestion.ho_vty
new file mode 100644
index 000000000..0988f3bb4
--- /dev/null
+++ b/tests/handover/test_balance_congestion.ho_vty
@@ -0,0 +1,20 @@
+# Handover to balance congestion
+# The current and the better cell are congested, so no handover is
+# performed. This is because handover would congest the neighbor cell
+# more. After congestion rises in the current cell, the handover is
+# performed to balance congestion
+
+create-n-bts 2
+create-ms bts 0 TCH/F AMR
+expect-ts-use trx 0 0 states * TCH/F - - - - - -
+network
+ handover2 min-free-slots tch/f 4
+ handover2 min-free-slots tch/h 4
+meas-rep lchan 0 0 1 0 rxlev 20 rxqual 0 ta 0 neighbors 30
+expect-no-chan
+set-ts-use trx 0 0 states * TCH/F TCH/F - - - - -
+meas-rep lchan 0 0 1 0 rxlev 20 rxqual 0 ta 0 neighbors 30
+expect-ho from lchan 0 0 1 0 to lchan 1 0 1 0
+expect-ts-use trx 0 0 states * - TCH/F - - - - -
+expect-ts-use trx 1 0 states * TCH/F - - - - - -
+
diff --git a/tests/handover/test_balance_congestion_2.ho_vty b/tests/handover/test_balance_congestion_2.ho_vty
new file mode 100644
index 000000000..b157478f9
--- /dev/null
+++ b/tests/handover/test_balance_congestion_2.ho_vty
@@ -0,0 +1,18 @@
+# Congestion check: Balancing over congested cells
+# Two cells are congested, but the second cell is less congested.
+# Handover is performed to solve the congestion.
+
+create-n-bts 2
+network
+ handover2 min-free-slots tch/f 4
+codec tch/f FR
+set-ts-use trx 0 0 states * TCH/F TCH/F TCH/F - - - -
+set-ts-use trx 1 0 states * TCH/F - - - - - -
+meas-rep lchan * * * * rxlev 30 rxqual 0 ta 0 neighbors 20
+meas-rep lchan 0 0 2 0 rxlev 30 rxqual 0 ta 0 neighbors 21
+expect-no-chan
+congestion-check
+expect-ho from lchan 0 0 2 0 to lchan 1 0 2 0
+expect-ts-use trx 0 0 states * TCH/F - TCH/F - - - -
+expect-ts-use trx 1 0 states * TCH/F TCH/F - - - - -
+
diff --git a/tests/handover/test_balance_congestion_by_percentage.ho_vty b/tests/handover/test_balance_congestion_by_percentage.ho_vty
new file mode 100644
index 000000000..aba7d3a5b
--- /dev/null
+++ b/tests/handover/test_balance_congestion_by_percentage.ho_vty
@@ -0,0 +1,52 @@
+# To balance congestion, use the remaining free percentage instead of free lchan counts.
+#
+# Cell A has min-free-slots 2, and has all slots occupied.
+# Cell B has min-free-slots 4, and has 2 slots remaining free.
+#
+# If we count congested lchans: cell A has a congestion count of 2: two more lchans in use than "allowed".
+# If we move one lchan over to cell B, it ends up with a congestion count of 3, which is worse than 2.
+# So when counting lchans, we decide that cell A should remain full.
+#
+# Instead, when comparing percentage of remaining lchans, we would see that cell A is loaded 100% above congestion (2 of
+# 2 remaining lchans in use), but when moving one lchan to cell B, it would only be 75% loaded above its treshold (3 of
+# 4 remaining lchans in use). So a percentage comparison would cause a handover to cell B.
+#
+# This test currently expects the behavior of counting lchans; a patch will change to use percentage, which should
+# reflect in this test.
+
+create-bts trx-count 1 timeslots c+s4 TCH/F TCH/F TCH/F TCH/F TCH/F TCH/F PDCH
+create-bts trx-count 1 timeslots c+s4 TCH/F TCH/F TCH/F TCH/F TCH/F TCH/F PDCH
+
+network
+ bts 0
+ handover2 min-free-slots tch/f 2
+ bts 1
+ handover2 min-free-slots tch/f 4
+
+set-ts-use trx 0 0 states * TCH/F TCH/F TCH/F TCH/F TCH/F TCH/F *
+set-ts-use trx 1 0 states * TCH/F TCH/F TCH/F TCH/F - - *
+
+meas-rep lchan * * * * rxlev 40 rxqual 0 ta 0 neighbors 30
+expect-no-chan
+
+# bts 0 is full, by target_overbooked_after_ho==75% < current_overbooked_before_ho==100%, a congestion balancing to bts
+# 1 is performed.
+congestion-check
+expect-ho from lchan 0 0 1 0 to lchan 1 0 5 0
+
+
+# Make sure that no percentage based handover merely reverses the situation between two cells:
+
+network
+ bts 0
+ handover2 min-free-slots tch/f 4
+ bts 1
+ handover2 min-free-slots tch/f 4
+
+set-ts-use trx 0 0 states * TCH/F TCH/F TCH/F TCH/F - - *
+set-ts-use trx 1 0 states * TCH/F TCH/F TCH/F - - - *
+
+# the condition is false: target_overbooked_after_ho==50% < current_overbooked_before_ho==50%, so no congestion
+# balancing is performed.
+congestion-check
+expect-no-chan
diff --git a/tests/handover/test_balance_congestion_tchf_tchh.ho_vty b/tests/handover/test_balance_congestion_tchf_tchh.ho_vty
new file mode 100644
index 000000000..f151b2a41
--- /dev/null
+++ b/tests/handover/test_balance_congestion_tchf_tchh.ho_vty
@@ -0,0 +1,53 @@
+# Balance congestion across cells and across TCH/F and TCH/H.
+
+network
+ handover2 min-free-slots tch/f 3
+ handover2 min-free-slots tch/h 3
+
+create-bts trx-count 1 timeslots c+s4 TCH/F TCH/F TCH/F TCH/F TCH/H TCH/H TCH/H
+
+# both TCH/H and TCH/F have one lchan = 33% above congestion, nothing happens
+set-ts-use trx 0 0 states * TCH/F TCH/F - - TCH/HH TCH/HH -
+meas-rep lchan * * * * rxlev 10 rxqual 0 ta 0
+congestion-check
+expect-no-chan
+
+# TCH/F = +1 = 33%, TCH/H = +2 = 66% above congestion.
+# Moving a TCH/H to TCH/F would just reverse the situation to F=+2=66%. Nothing happens.
+set-ts-use trx 0 0 states * TCH/F TCH/F - - TCH/HH TCH/HH TCH/H-
+meas-rep lchan * * * * rxlev 10 rxqual 0 ta 0
+congestion-check
+expect-no-chan
+
+# F=+1=33% H=+3=100%. Balance to F=+2=66% (which is < 100%) and H=+2=66%
+set-ts-use trx 0 0 states * TCH/F TCH/F - - TCH/HH TCH/HH TCH/HH
+meas-rep lchan * * * * rxlev 10 rxqual 0 ta 0
+congestion-check
+expect-as from lchan 0 0 5 0 to lchan 0 0 3 0
+
+# Now similar load percentages, just with different min-free-slots settings for tch/f vs tch/h.
+
+network
+ handover2 min-free-slots tch/f 3
+ handover2 min-free-slots tch/h 5
+
+# TCH/F has 1/3 = 33%, TCH/H has 1/5 = 20% overload.
+# Moving one to TCH/H would mean 40% overload on TCH/H, which is above the current TCH/F of 33%.
+# Nothing happens.
+set-ts-use trx 0 0 states * TCH/F TCH/F - - TCH/HH - -
+meas-rep lchan * * * * rxlev 20 rxqual 0 ta 0
+congestion-check
+expect-no-chan
+
+# TCH/F = +1 = 33%, TCH/H = +2 = 40% above congestion. Moving a TCH/H to TCH/F would result
+# in F=+2=66%>40%. Nothing happens.
+set-ts-use trx 0 0 states * TCH/F TCH/F - - TCH/HH TCH/H- -
+meas-rep lchan * * * * rxlev 20 rxqual 0 ta 0
+congestion-check
+expect-no-chan
+
+# F=+1=33% H=+4=80%. Balance to F=+2=66%<80% and H=+3=60%
+set-ts-use trx 0 0 states * TCH/F TCH/F - - TCH/HH TCH/HH TCH/H-
+meas-rep lchan * * * * rxlev 20 rxqual 0 ta 0
+congestion-check
+expect-as from lchan 0 0 5 0 to lchan 0 0 3 0
diff --git a/tests/handover/test_bs_power.ho_vty b/tests/handover/test_bs_power.ho_vty
new file mode 100644
index 000000000..5d8c27846
--- /dev/null
+++ b/tests/handover/test_bs_power.ho_vty
@@ -0,0 +1,11 @@
+# Do not oscillate handover when the BTS applies BS power reduction
+
+create-bts trx-count 1 timeslots c+s4 TCH/F TCH/F TCH/F TCH/F TCH/F TCH/F PDCH
+create-bts trx-count 1 timeslots c+s4 TCH/F TCH/F TCH/F TCH/F TCH/F TCH/F PDCH
+
+set-ts-use trx 0 0 states * TCH/F - - - - - *
+
+meas-rep repeat 10 bspower 20 lchan 0 0 1 0 rxlev 20 rxqual 0 ta 0 neighbors 30
+# there should be no handover, because the bspower reduction of 20 with an rxlev of 20 (= 40) is stronger than the
+# neighbor at 30.
+expect-no-chan
diff --git a/tests/handover/test_congestion.ho_vty b/tests/handover/test_congestion.ho_vty
new file mode 100644
index 000000000..529b8de61
--- /dev/null
+++ b/tests/handover/test_congestion.ho_vty
@@ -0,0 +1,21 @@
+# Congestion check: One out of three cells is congested
+# Three cells have different number of used slots, but there is
+# congestion at TCH/F in the first cell. Handover is performed with
+# the best candidate.
+
+create-n-bts 3
+network
+ handover2 min-free-slots tch/f 2
+ handover2 min-free-slots tch/h 2
+set-ts-use trx 0 0 states * TCH/F TCH/F TCH/F - TCH/HH - -
+set-ts-use trx 1 0 states * TCH/F - - - TCH/H- - -
+meas-rep lchan * * * * rxlev 30 rxqual 0 ta 0 neighbors 20 20
+meas-rep lchan 0 0 3 0 rxlev 30 rxqual 0 ta 0 neighbors 21 20
+expect-no-chan
+expect-ts-use trx 0 0 states * TCH/F TCH/F TCH/F - TCH/HH - -
+expect-ts-use trx 1 0 states * TCH/F - - - TCH/H- - -
+congestion-check
+expect-ho from lchan 0 0 3 0 to lchan 1 0 2 0
+expect-ts-use trx 0 0 states * TCH/F TCH/F - - TCH/HH - -
+expect-ts-use trx 1 0 states * TCH/F TCH/F - - TCH/H- - -
+
diff --git a/tests/handover/test_congestion_favor_best_target_rxlev.ho_vty b/tests/handover/test_congestion_favor_best_target_rxlev.ho_vty
new file mode 100644
index 000000000..e31d98d38
--- /dev/null
+++ b/tests/handover/test_congestion_favor_best_target_rxlev.ho_vty
@@ -0,0 +1,33 @@
+# A handover should mostly favor the best target rxlev:
+# Two candidates for congestion resolution both reduce the RXLEV for the MS,
+# candidate A results in 10 RXLEV loss, candidate B only in 5 RXLEV loss.
+# But candidate A still results in a better RXLEV at the target than candidate B.
+# So tolerate more RXLEV reduction if the resulting RXLEV still remains better.
+create-bts trx-count 1 timeslots c+s4 TCH/F TCH/F TCH/F TCH/F TCH/F TCH/F TCH/F
+create-bts trx-count 1 timeslots c+s4 TCH/F TCH/F TCH/F TCH/F TCH/F TCH/F TCH/F
+
+network
+ bts 0
+ handover2 min-free-slots tch/f 6
+
+set-ts-use trx 0 0 states * TCH/F TCH/F - - - - -
+meas-rep lchan 0 0 1 0 rxlev 40 rxqual 0 ta 0 neighbors 30
+meas-rep lchan 0 0 2 0 rxlev 30 rxqual 0 ta 0 neighbors 25
+
+congestion-check
+expect-ho from lchan 0 0 1 0 to lchan 1 0 1 0
+expect-ts-use trx 0 0 states * - TCH/F - - - - -
+expect-ts-use trx 1 0 states * TCH/F - - - - - -
+
+# clear measurements for next run
+set-ts-use trx 0 0 states * - - - - - - -
+set-ts-use trx 1 0 states * - - - - - - -
+
+set-ts-use trx 0 0 states * TCH/F TCH/F - - - - -
+meas-rep lchan 0 0 1 0 rxlev 30 rxqual 0 ta 0 neighbors 25
+meas-rep lchan 0 0 2 0 rxlev 40 rxqual 0 ta 0 neighbors 30
+
+congestion-check
+expect-ho from lchan 0 0 2 0 to lchan 1 0 1 0
+expect-ts-use trx 0 0 states * TCH/F - - - - - -
+expect-ts-use trx 1 0 states * TCH/F - - - - - -
diff --git a/tests/handover/test_congestion_intra_vs_inter_cell.ho_vty b/tests/handover/test_congestion_intra_vs_inter_cell.ho_vty
new file mode 100644
index 000000000..ddf6802f9
--- /dev/null
+++ b/tests/handover/test_congestion_intra_vs_inter_cell.ho_vty
@@ -0,0 +1,122 @@
+create-bts trx-count 1 timeslots c+s4 TCH/F TCH/F TCH/F TCH/F TCH/H TCH/H PDCH
+create-bts trx-count 1 timeslots c+s4 TCH/F TCH/F TCH/F TCH/F TCH/H TCH/H PDCH
+
+network
+ handover2 min-free-slots tch/h 4
+
+set-ts-use trx 0 0 states * - - - - TCH/H- TCH/H- *
+
+meas-rep lchan 0 0 5 0 rxlev 31 rxqual 0 ta 0 neighbors 20
+expect-no-chan
+meas-rep lchan 0 0 6 0 rxlev 30 rxqual 0 ta 0 neighbors 20
+expect-no-chan
+
+congestion-check
+expect-as from lchan 0 0 6 0 to lchan 0 0 1 0
+expect-ts-use trx 0 0 states * TCH/F - - - TCH/H- - *
+expect-ts-use trx 1 0 states * - - - - - - *
+
+# clear measurements for next run
+set-ts-use trx 0 0 states * - - - - - - *
+set-ts-use trx 1 0 states * - - - - - - *
+
+set-ts-use trx 0 0 states * - - - - TCH/H- TCH/H- *
+meas-rep lchan 0 0 5 0 rxlev 30 rxqual 0 ta 0 neighbors 20
+expect-no-chan
+meas-rep lchan 0 0 6 0 rxlev 31 rxqual 0 ta 0 neighbors 20
+expect-no-chan
+
+congestion-check
+expect-as from lchan 0 0 5 0 to lchan 0 0 1 0
+expect-ts-use trx 0 0 states * TCH/F - - - - TCH/H- *
+expect-ts-use trx 1 0 states * - - - - - - *
+
+# clear measurements for next run
+set-ts-use trx 0 0 states * - - - - - - *
+set-ts-use trx 1 0 states * - - - - - - *
+
+set-ts-use trx 0 0 states * - - - - TCH/H- TCH/H- *
+meas-rep lchan 0 0 5 0 rxlev 30 rxqual 0 ta 0 neighbors 20
+expect-no-chan
+meas-rep lchan 0 0 6 0 rxlev 31 rxqual 0 ta 0 neighbors 21
+expect-no-chan
+
+congestion-check
+expect-as from lchan 0 0 5 0 to lchan 0 0 1 0
+expect-ts-use trx 0 0 states * TCH/F - - - - TCH/H- *
+expect-ts-use trx 1 0 states * - - - - - - *
+
+# clear measurements for next run
+set-ts-use trx 0 0 states * - - - - - - *
+set-ts-use trx 1 0 states * - - - - - - *
+
+set-ts-use trx 0 0 states * - - - - TCH/H- TCH/H- *
+meas-rep lchan 0 0 5 0 rxlev 30 rxqual 0 ta 0 neighbors 21
+expect-no-chan
+meas-rep lchan 0 0 6 0 rxlev 31 rxqual 0 ta 0 neighbors 20
+expect-no-chan
+
+congestion-check
+expect-as from lchan 0 0 5 0 to lchan 0 0 1 0
+expect-ts-use trx 0 0 states * TCH/F - - - - TCH/H- *
+expect-ts-use trx 1 0 states * - - - - - - *
+
+# clear measurements for next run
+set-ts-use trx 0 0 states * - - - - - - *
+set-ts-use trx 1 0 states * - - - - - - *
+
+set-ts-use trx 0 0 states * - - - - TCH/H- TCH/H- *
+meas-rep lchan 0 0 5 0 rxlev 30 rxqual 0 ta 0 neighbors 30
+expect-no-chan
+meas-rep lchan 0 0 6 0 rxlev 31 rxqual 0 ta 0 neighbors 31
+expect-no-chan
+
+congestion-check
+expect-as from lchan 0 0 5 0 to lchan 0 0 1 0
+expect-ts-use trx 0 0 states * TCH/F - - - - TCH/H- *
+expect-ts-use trx 1 0 states * - - - - - - *
+
+# clear measurements for next run
+set-ts-use trx 0 0 states * - - - - - - *
+set-ts-use trx 1 0 states * - - - - - - *
+
+set-ts-use trx 0 0 states * - - - - TCH/H- TCH/H- *
+meas-rep lchan 0 0 5 0 rxlev 31 rxqual 0 ta 0 neighbors 31
+expect-no-chan
+meas-rep lchan 0 0 6 0 rxlev 30 rxqual 0 ta 0 neighbors 30
+expect-no-chan
+
+congestion-check
+expect-as from lchan 0 0 6 0 to lchan 0 0 1 0
+expect-ts-use trx 0 0 states * TCH/F - - - TCH/H- - *
+expect-ts-use trx 1 0 states * - - - - - - *
+
+# clear measurements for next run
+set-ts-use trx 0 0 states * - - - - - - *
+set-ts-use trx 1 0 states * - - - - - - *
+
+set-ts-use trx 0 0 states * - - - - TCH/H- TCH/H- *
+meas-rep lchan 0 0 5 0 rxlev 30 rxqual 0 ta 0 neighbors 31
+expect-no-chan
+meas-rep lchan 0 0 6 0 rxlev 31 rxqual 0 ta 0 neighbors 30
+expect-no-chan
+
+congestion-check
+expect-ho from lchan 0 0 5 0 to lchan 1 0 1 0
+expect-ts-use trx 0 0 states * - - - - - TCH/H- *
+expect-ts-use trx 1 0 states * TCH/F - - - - - *
+
+# clear measurements for next run
+set-ts-use trx 0 0 states * - - - - - - *
+set-ts-use trx 1 0 states * - - - - - - *
+
+set-ts-use trx 0 0 states * - - - - TCH/H- TCH/H- *
+meas-rep lchan 0 0 5 0 rxlev 31 rxqual 0 ta 0 neighbors 30
+expect-no-chan
+meas-rep lchan 0 0 6 0 rxlev 30 rxqual 0 ta 0 neighbors 31
+expect-no-chan
+
+congestion-check
+expect-ho from lchan 0 0 6 0 to lchan 1 0 1 0
+expect-ts-use trx 0 0 states * - - - - TCH/H- - *
+expect-ts-use trx 1 0 states * TCH/F - - - - - *
diff --git a/tests/handover/test_congestion_no_oscillation.ho_vty b/tests/handover/test_congestion_no_oscillation.ho_vty
new file mode 100644
index 000000000..a830cbe77
--- /dev/null
+++ b/tests/handover/test_congestion_no_oscillation.ho_vty
@@ -0,0 +1,28 @@
+# Do not oscillate handover from TCH/F to TCH/H on a neighbor due to congestion,
+# and then back to the original cell due to RXLEV.
+
+create-bts trx-count 1 timeslots c+s4 TCH/F TCH/F TCH/F TCH/F TCH/F TCH/F PDCH
+network
+ bts 0
+ handover2 min-free-slots tch/f 5
+
+create-bts trx-count 1 timeslots c+s4 TCH/F TCH/F TCH/F TCH/F TCH/H TCH/H PDCH
+
+set-ts-use trx 0 0 states * TCH/F TCH/F - - - - *
+set-ts-use trx 1 0 states * TCH/F TCH/F TCH/F TCH/F - - *
+
+meas-rep repeat 10 lchan 0 0 2 0 rxlev 40 rxqual 0 ta 0 neighbors 20
+expect-no-chan
+
+# bts 0 wants to lose one TCH/F. The neighbor's TCH/F are full, but TCH/H are available there.
+congestion-check
+expect-ho from lchan 0 0 2 0 to lchan 1 0 5 0
+
+expect-ts-use trx 0 0 states * TCH/F - - - - - *
+expect-ts-use trx 1 0 states * TCH/F TCH/F TCH/F TCH/F TCH/H- - *
+
+# measurements continue to be the same
+meas-rep lchan 1 0 5 0 rxlev 20 rxqual 0 ta 0 neighbors 40
+
+# despite the better RXLEV, congestion prevents oscillation back to bts 0
+expect-no-chan
diff --git a/tests/handover/test_congestion_no_oscillation2.ho_vty b/tests/handover/test_congestion_no_oscillation2.ho_vty
new file mode 100644
index 000000000..44c4176c2
--- /dev/null
+++ b/tests/handover/test_congestion_no_oscillation2.ho_vty
@@ -0,0 +1,28 @@
+# Almost identical to test_amr_oscillation.ho_vty, this has just two more TCH/H slots in BTS 1, and did not trigger the
+# oscillation bug (which has since been fixed, so that both tests behave identically now).
+
+create-bts trx-count 1 timeslots c+s4 TCH/F TCH/F TCH/F TCH/F TCH/F TCH/F PDCH
+network
+ bts 0
+ handover2 min-free-slots tch/f 5
+
+create-bts trx-count 1 timeslots c+s4 TCH/F TCH/F TCH/F TCH/F TCH/H TCH/H TCH/H
+
+set-ts-use trx 0 0 states * TCH/F TCH/F - - - - *
+set-ts-use trx 1 0 states * TCH/F TCH/F TCH/F TCH/F - - -
+
+meas-rep repeat 10 lchan 0 0 2 0 rxlev 40 rxqual 0 ta 0 neighbors 20
+expect-no-chan
+
+# bts 0 wants to lose one TCH/F. The neighbor's TCH/F are full, but TCH/H are available there.
+congestion-check
+expect-ho from lchan 0 0 2 0 to lchan 1 0 5 0
+
+expect-ts-use trx 0 0 states * TCH/F - - - - - *
+expect-ts-use trx 1 0 states * TCH/F TCH/F TCH/F TCH/F TCH/H- - -
+
+# measurements continue to be the same
+meas-rep lchan 1 0 5 0 rxlev 20 rxqual 0 ta 0 neighbors 40
+
+# despite the better RXLEV, congestion prevents oscillation back to bts 0
+expect-no-chan
diff --git a/tests/handover/test_disabled_ho_and_as.ho_vty b/tests/handover/test_disabled_ho_and_as.ho_vty
new file mode 100644
index 000000000..586c3a70d
--- /dev/null
+++ b/tests/handover/test_disabled_ho_and_as.ho_vty
@@ -0,0 +1,36 @@
+# Handover and Assignment must be enabled
+# This test will start with disabled assignment and handover. A
+# better neighbor cell (assignment enabled) will not be selected and
+# also no assignment from TCH/H to TCH/F to improve quality. There
+# will be no handover nor assignment. After enabling assignment on the
+# current cell, the MS will assign to TCH/F. After enabling handover
+# in the current cell, but disabling in the neighbor cell, handover
+# will not be performed, until it is enabled in the neighbor cell too.
+network
+ handover 0
+ handover2 afs-bias rxlev 5
+ handover2 assignment 0
+
+create-n-bts 2
+set-ts-use trx 0 0 states * - - - - TCH/H- - -
+meas-rep lchan 0 0 5 0 rxlev 0 rxqual 0 ta 0 neighbors 30
+expect-no-chan
+network
+ bts 0
+ handover2 assignment 1
+meas-rep lchan 0 0 5 0 rxlev 0 rxqual 0 ta 0 neighbors 30
+expect-as from lchan 0 0 5 0 to lchan 0 0 1 0
+expect-ts-use trx 0 0 states * TCH/F - - - - - -
+network
+ bts 0
+ handover 1
+meas-rep lchan 0 0 1 0 rxlev 0 rxqual 0 ta 0 neighbors 30
+expect-no-chan
+network
+ bts 1
+ handover 1
+meas-rep lchan 0 0 1 0 rxlev 0 rxqual 0 ta 0 neighbors 30
+expect-ho from lchan 0 0 1 0 to lchan 1 0 1 0
+expect-ts-use trx 0 0 states * - - - - - - -
+expect-ts-use trx 1 0 states * TCH/F - - - - - -
+
diff --git a/tests/handover/test_dyn_ts_amr_tch_f_to_h_congestion_assignment.ho_vty b/tests/handover/test_dyn_ts_amr_tch_f_to_h_congestion_assignment.ho_vty
new file mode 100644
index 000000000..6990132ee
--- /dev/null
+++ b/tests/handover/test_dyn_ts_amr_tch_f_to_h_congestion_assignment.ho_vty
@@ -0,0 +1,78 @@
+# Congestion check: Balancing congestion by handover TCH/F -> TCH/H
+# With dynamic timeslots.
+# As soon as only one TCH/F is left, there should be HO to a dyn TS.
+
+create-bts trx-count 1 timeslots c+s4 TCH/F TCH/F TCH/F dyn dyn dyn PDCH
+
+network
+ bts 0
+ handover2 min-free-slots tch/f 2
+ handover2 min-free-slots tch/h 0
+ handover2 assignment 1
+set-ts-use trx 0 0 states * TCH/F TCH/F TCH/F TCH/F pdch pdch pdch
+
+# (there must be at leas one measurement report on each lchan for congestion check to work)
+meas-rep lchan * * * * rxlev 40 rxqual 0 ta 0 neighbors 30
+
+congestion-check
+expect-no-chan
+
+create-ms bts 0 TCH/F AMR
+meas-rep lchan 0 0 5 0 rxlev 40 rxqual 0 ta 0 neighbors 30
+expect-ts-use trx 0 0 states * TCH/F TCH/F TCH/F TCH/F TCH/F pdch *
+
+congestion-check
+expect-as from lchan 0 0 5 0 to lchan 0 0 6 0
+expect-ts-use trx 0 0 states * TCH/F TCH/F TCH/F TCH/F pdch TCH/H- *
+
+congestion-check
+expect-as from lchan 0 0 4 0 to lchan 0 0 6 1
+expect-ts-use trx 0 0 states * TCH/F TCH/F TCH/F pdch pdch TCH/HH *
+
+congestion-check
+expect-no-chan
+
+create-ms bts 0 TCH/F AMR
+meas-rep lchan 0 0 4 0 rxlev 40 rxqual 0 ta 0 neighbors 30
+expect-ts-use trx 0 0 states * TCH/F TCH/F TCH/F TCH/F pdch TCH/HH *
+
+congestion-check
+expect-as from lchan 0 0 4 0 to lchan 0 0 5 0
+expect-ts-use trx 0 0 states * TCH/F TCH/F TCH/F pdch TCH/H- TCH/HH *
+
+congestion-check
+expect-as from lchan 0 0 1 0 to lchan 0 0 5 1
+expect-ts-use trx 0 0 states * - TCH/F TCH/F pdch TCH/HH TCH/HH *
+
+congestion-check
+expect-no-chan
+
+create-ms bts 0 TCH/F AMR
+meas-rep lchan 0 0 1 0 rxlev 40 rxqual 0 ta 0 neighbors 30
+expect-ts-use trx 0 0 states * TCH/F TCH/F TCH/F pdch TCH/HH TCH/HH *
+
+congestion-check
+expect-as from lchan 0 0 1 0 to lchan 0 0 4 0
+expect-ts-use trx 0 0 states * - TCH/F TCH/F TCH/H- TCH/HH TCH/HH *
+
+congestion-check
+expect-as from lchan 0 0 2 0 to lchan 0 0 4 1
+expect-ts-use trx 0 0 states * - - TCH/F TCH/HH TCH/HH TCH/HH *
+
+congestion-check
+expect-no-chan
+
+create-ms bts 0 TCH/F AMR
+meas-rep lchan 0 0 1 0 rxlev 40 rxqual 0 ta 0 neighbors 30
+expect-ts-use trx 0 0 states * TCH/F - TCH/F TCH/HH TCH/HH TCH/HH *
+
+congestion-check
+expect-no-chan
+
+create-ms bts 0 TCH/F AMR
+meas-rep lchan 0 0 2 0 rxlev 40 rxqual 0 ta 0 neighbors 30
+expect-ts-use trx 0 0 states * TCH/F TCH/F TCH/F TCH/HH TCH/HH TCH/HH *
+
+congestion-check
+expect-no-chan
+
diff --git a/tests/handover/test_dyn_ts_amr_tch_f_to_h_congestion_assignment_2.ho_vty b/tests/handover/test_dyn_ts_amr_tch_f_to_h_congestion_assignment_2.ho_vty
new file mode 100644
index 000000000..08bc151ec
--- /dev/null
+++ b/tests/handover/test_dyn_ts_amr_tch_f_to_h_congestion_assignment_2.ho_vty
@@ -0,0 +1,47 @@
+# If a handover from TCH/F to TCH/H frees a dynamic timeslot,
+# take the freed TCH/H from the soure timeslot into account,
+# both when the target is a dynamic timeslot and when the target is a static timeslot.
+
+create-bts trx-count 1 timeslots c+s4 dyn TCH/F TCH/F TCH/H PDCH PDCH PDCH
+
+network
+ bts 0
+ handover2 min-free-slots tch/f 3
+ handover2 min-free-slots tch/h 2
+ handover2 assignment 1
+
+set-ts-use trx 0 0 states * TCH/F - - - * * *
+# (there must be at least one measurement report on each lchan for congestion check to work)
+meas-rep lchan * * * * rxlev 40 rxqual 0 ta 0 neighbors 30
+congestion-check
+expect-as from lchan 0 0 1 0 to lchan 0 0 4 0
+expect-ts-use trx 0 0 states * pdch - - TCH/H- * * *
+
+# Again with one more TCH/H occupied, there will still be two free TCH/H after HO on the dyn TS
+set-ts-use trx 0 0 states * TCH/F - - TCH/H- * * *
+meas-rep lchan * * * * rxlev 40 rxqual 0 ta 0 neighbors 30
+congestion-check
+expect-as from lchan 0 0 1 0 to lchan 0 0 4 1
+expect-ts-use trx 0 0 states * pdch - - TCH/HH * * *
+
+# Again, with the target being a dyn TS
+create-bts trx-count 1 timeslots c+s4 dyn TCH/F TCH/F dyn PDCH PDCH PDCH
+
+network
+ bts 1
+ handover2 min-free-slots tch/f 3
+ handover2 min-free-slots tch/h 2
+ handover2 assignment 1
+
+set-ts-use trx 1 0 states * TCH/F TCH/F - pdch * * *
+meas-rep lchan 1 * * * rxlev 40 rxqual 0 ta 0 neighbors 30
+congestion-check
+expect-as from lchan 1 0 1 0 to lchan 1 0 4 0
+expect-ts-use trx 1 0 states * pdch TCH/F - TCH/H- * * *
+
+# Again with one more TCH/H occupied, there will still be two free TCH/H after HO on the dyn TS
+set-ts-use trx 1 0 states * TCH/F TCH/F - TCH/H- * * *
+meas-rep lchan 1 * * * rxlev 40 rxqual 0 ta 0 neighbors 30
+congestion-check
+expect-as from lchan 1 0 1 0 to lchan 1 0 4 1
+expect-ts-use trx 1 0 states * pdch TCH/F - TCH/HH * * *
diff --git a/tests/handover/test_dyn_ts_amr_tch_h_to_f_congestion_assignment_2.ho_vty b/tests/handover/test_dyn_ts_amr_tch_h_to_f_congestion_assignment_2.ho_vty
new file mode 100644
index 000000000..bf1edf22b
--- /dev/null
+++ b/tests/handover/test_dyn_ts_amr_tch_h_to_f_congestion_assignment_2.ho_vty
@@ -0,0 +1,27 @@
+# If a handover from TCH/H to TCH/F frees a dynamic timeslot,
+# take the freed TCH/F from the soure timeslot into account,
+# when the target is a static timeslot.
+
+create-bts trx-count 1 timeslots c+s4 dyn TCH/F TCH/F TCH/F PDCH PDCH PDCH
+
+network
+ bts 0
+ handover2 min-free-slots tch/f 2
+ handover2 min-free-slots tch/h 2
+ handover2 assignment 1
+
+set-ts-use trx 0 0 states * TCH/H- - - - * * *
+# (there must be at least one measurement report on each lchan for congestion check to work)
+meas-rep lchan * * * * rxlev 40 rxqual 0 ta 0 neighbors 30
+congestion-check
+expect-as from lchan 0 0 1 0 to lchan 0 0 2 0
+expect-ts-use trx 0 0 states * pdch TCH/F - - * * *
+
+# Again with one more TCH/F occupied, there will still be two free TCH/F after HO on the dyn TS
+set-ts-use trx 0 0 states * TCH/H- - - TCH/F * * *
+meas-rep lchan * * * * rxlev 40 rxqual 0 ta 0 neighbors 30
+congestion-check
+expect-as from lchan 0 0 1 0 to lchan 0 0 2 0
+expect-ts-use trx 0 0 states * pdch TCH/F - TCH/F * * *
+
+# (TCH/H -> TCH/F onto a dyn TS will always make TCH/H congestion worse, so there is no useful test case left here)
diff --git a/tests/handover/test_dyn_ts_balance_congestion.ho_vty b/tests/handover/test_dyn_ts_balance_congestion.ho_vty
new file mode 100644
index 000000000..2fa11b6b0
--- /dev/null
+++ b/tests/handover/test_dyn_ts_balance_congestion.ho_vty
@@ -0,0 +1,37 @@
+# To balance congestion, consider cross effects between TCH/F and TCH/H when occupying a dynamic timeslot in the target:
+# when balancing of TCH/F congestion would take up a dyn TS in the target, reducing TCH/H availability, the handover
+# should not cause worse TCH/H congestion than in the source cell.
+
+create-bts trx-count 1 timeslots c+s4 TCH/F TCH/F TCH/F TCH/F TCH/F TCH/F PDCH
+create-bts trx-count 1 timeslots c+s4 TCH/F TCH/F TCH/F TCH/F dyn dyn PDCH
+
+# for this test, avoid changing a TCH/F to a TCH/H by using a non-AMR codec
+codec tch/f FR
+
+network
+ bts 0
+ handover2 min-free-slots tch/f 2
+ bts 1
+ handover2 min-free-slots tch/f 4
+ handover2 min-free-slots tch/h 4
+
+set-ts-use trx 0 0 states * TCH/F TCH/F TCH/F TCH/F TCH/F TCH/F *
+set-ts-use trx 1 0 states * TCH/F TCH/F TCH/F TCH/F pdch pdch *
+
+meas-rep lchan * * * * rxlev 40 rxqual 0 ta 0 neighbors 30
+expect-no-chan
+
+# bts 0 is full for TCH/F. Looking at TCH/F, by target_overbooked_after_ho==75% < current_overbooked_before_ho==100%, a
+# congestion balancing to bts 1 would be performed. But the TCH/F on the target cell would occupy a dynamic timeslot.
+# That would reduce the TCH/H free slots by two and cause TCH/H being overbooked by 50%. On the source cell, TCH/H is
+# not congested. No handover is performed because 50% in the target is more congestion for TCH/H than 0% in the source
+# cell.
+congestion-check
+expect-no-chan
+
+# If there is no constraint on TCH/H in the target cell, the handover does take place.
+network
+ bts 1
+ handover2 min-free-slots tch/h 2
+congestion-check
+expect-ho from lchan 0 0 1 0 to lchan 1 0 5 0
diff --git a/tests/handover/test_dyn_ts_congestion_tch_f_vs_tch_h.ho_vty b/tests/handover/test_dyn_ts_congestion_tch_f_vs_tch_h.ho_vty
new file mode 100644
index 000000000..c5890a513
--- /dev/null
+++ b/tests/handover/test_dyn_ts_congestion_tch_f_vs_tch_h.ho_vty
@@ -0,0 +1,74 @@
+# If a handover from one TCH kind to the other occupies a dynamic timeslot,
+# also adhere to congestion constraints of the other TCH kind, since taking up
+# a dyn TS may reduce the available slot count for both kinds of TCH.
+
+create-bts trx-count 1 timeslots c+s4 TCH/F TCH/F TCH/F dyn dyn dyn PDCH
+create-bts trx-count 1 timeslots c+s4 TCH/F TCH/F TCH/F dyn dyn dyn PDCH
+
+# A TCH/F has better rxlev at a neighbor, and the neighbor's TCH/F slots would
+# not become congested. But taking up a neighbor's dynamic timeslot for TCH/F
+# would reduce the TCH/H availability to cause congestion on TCH/H. No HO.
+
+network
+ handover2 min-free-slots tch/f 0
+ handover2 min-free-slots tch/h 4
+
+set-ts-use trx 0 0 states * TCH/F - - pdch pdch pdch *
+set-ts-use trx 1 0 states * TCH/F TCH/F TCH/F TCH/HH pdch pdch *
+
+meas-rep lchan * * * * rxlev 40 rxqual 0 ta 0 neighbors 30
+expect-no-chan
+
+meas-rep lchan 0 0 1 0 rxlev 20 rxqual 0 ta 0 neighbors 40
+# no handover because that results in congestion on TCH/H in bts 1
+expect-no-chan
+
+###
+
+set-ts-use trx 0 0 states * - - - pdch pdch pdch *
+set-ts-use trx 1 0 states * TCH/F TCH/F TCH/F TCH/HH TCH/F pdch *
+meas-rep lchan * * * * rxlev 40 rxqual 0 ta 0 neighbors 30
+
+congestion-check
+expect-ho from lchan 1 0 4 1 to lchan 0 0 4 0
+expect-ts-use trx 0 0 states * - - - TCH/H- pdch pdch *
+expect-ts-use trx 1 0 states * TCH/F TCH/F TCH/F TCH/H- TCH/F pdch *
+
+
+###
+
+set-ts-use trx 0 0 states * - - - pdch pdch pdch *
+set-ts-use trx 1 0 states * TCH/F TCH/F TCH/F TCH/H- TCH/F TCH/F *
+
+congestion-check
+# more FAIL: TCH/H moves to worse bts 0 due to congestion
+expect-ho from lchan 1 0 4 0 to lchan 0 0 4 0
+expect-ts-use trx 0 0 states * - - - TCH/H- pdch pdch *
+expect-ts-use trx 1 0 states * TCH/F TCH/F TCH/F pdch TCH/F TCH/F *
+
+
+###
+
+set-ts-use trx 0 0 states * - - - TCH/H- pdch pdch *
+set-ts-use trx 1 0 states * TCH/F TCH/F TCH/F pdch TCH/F TCH/F *
+
+congestion-check
+expect-no-chan
+
+meas-rep lchan 1 * * * rxlev 40 rxqual 0 ta 0 neighbors 30
+expect-no-chan
+
+meas-rep lchan 0 * * * rxlev 30 rxqual 0 ta 0 neighbors 40
+# no HO because the target is congested on TCH/H. Moving to TCH/F would also
+# reduce TCH/H lchans because it would convert another dyn TS.
+expect-no-chan
+
+###
+
+set-ts-use trx 0 0 states * - - - pdch pdch pdch *
+set-ts-use trx 1 0 states * TCH/F TCH/F TCH/F TCH/F TCH/F TCH/F *
+
+congestion-check
+# FAIL: TCH/F occupy dynamic timeslots -- should hand over to bts 0 to free a
+# dyn TS and reduce TCH/H congestion.
+expect-no-chan
diff --git a/tests/handover/test_dyn_ts_congestion_tch_f_vs_tch_h_2.ho_vty b/tests/handover/test_dyn_ts_congestion_tch_f_vs_tch_h_2.ho_vty
new file mode 100644
index 000000000..ef71d3e26
--- /dev/null
+++ b/tests/handover/test_dyn_ts_congestion_tch_f_vs_tch_h_2.ho_vty
@@ -0,0 +1,34 @@
+# If a handover from one TCH kind to the other occupies a dynamic timeslot,
+# also adhere to congestion constraints of the other TCH kind, since taking up
+# a dyn TS may reduce the available slot count for both kinds of TCH.
+
+create-bts trx-count 1 timeslots c+s4 TCH/F TCH/F TCH/F dyn dyn dyn PDCH
+create-bts trx-count 1 timeslots c+s4 TCH/F TCH/F TCH/F dyn dyn dyn PDCH
+
+# A TCH/H has better rxlev at a neighbor, and the neighbor's TCH/H slots would
+# not become congested. But taking up a neighbor's dynamic timeslot for TCH/H
+# would reduce the TCH/F availability to cause congestion on TCH/F. No HO.
+
+network
+ handover2 min-free-slots tch/f 2
+ handover2 min-free-slots tch/h 2
+
+set-ts-use trx 0 0 states * - - - TCH/H- pdch pdch *
+set-ts-use trx 1 0 states * TCH/F TCH/F TCH/F TCH/HH pdch pdch *
+
+meas-rep lchan * * * * rxlev 40 rxqual 0 ta 0 neighbors 30
+expect-no-chan
+
+meas-rep lchan 0 0 4 0 rxlev 20 rxqual 0 ta 0 neighbors 40
+# no handover because that results in congestion on TCH/F in bts 1
+expect-no-chan
+
+
+# Now the same situation, except there already is a half occupied TCH/H, hence an added TCH/H would not change the TCH/F
+# situation. The handover is performed.
+
+set-ts-use trx 0 0 states * - - - TCH/H- pdch pdch *
+set-ts-use trx 1 0 states * TCH/F TCH/F TCH/F TCH/HH TCH/H- pdch *
+
+meas-rep lchan 0 0 4 0 rxlev 20 rxqual 0 ta 0 neighbors 40
+expect-ho from lchan 0 0 4 0 to lchan 1 0 5 1
diff --git a/tests/handover/test_dyn_ts_favor_half_used_tch_h_as_target.ho_vty b/tests/handover/test_dyn_ts_favor_half_used_tch_h_as_target.ho_vty
new file mode 100644
index 000000000..73c236f20
--- /dev/null
+++ b/tests/handover/test_dyn_ts_favor_half_used_tch_h_as_target.ho_vty
@@ -0,0 +1,12 @@
+# assign new MS: re-use half used TCH/H to avoid switching more dyn TS to TCH/H
+
+create-bts trx-count 1 timeslots c+s4 TCH/F TCH/F TCH/F dyn dyn dyn PDCH
+set-ts-use trx 0 0 states * - - - pdch TCH/H- pdch pdch
+create-ms bts 0 TCH/H AMR
+expect-ts-use trx 0 0 states * - - - pdch TCH/HH pdch pdch
+
+# in static timeslots, there is NO preference to fill half-used TCH/H first
+create-bts trx-count 1 timeslots c+s4 TCH/F TCH/F TCH/F TCH/H TCH/H TCH/H PDCH
+set-ts-use trx 1 0 states * - - - - TCH/H- - pdch
+create-ms bts 1 TCH/H AMR
+set-ts-use trx 1 0 states * - - - TCH/H- TCH/H- - pdch
diff --git a/tests/handover/test_dyn_ts_favor_moving_half_used_tch_h.ho_vty b/tests/handover/test_dyn_ts_favor_moving_half_used_tch_h.ho_vty
new file mode 100644
index 000000000..6548360e8
--- /dev/null
+++ b/tests/handover/test_dyn_ts_favor_moving_half_used_tch_h.ho_vty
@@ -0,0 +1,42 @@
+# Congestion check: favor moving a TCH/H that frees a half-used dyn TS completely.
+# The algorithm should notice that this is about moving an lchan within the same cell, so all candidates will remain
+# with unchanged rxlev after a re-assignment; hence the current rxlev for each candidate should not make a difference.
+
+create-bts trx-count 1 timeslots c+s4 TCH/F dyn dyn dyn dyn - -
+network
+ handover2 min-free-slots tch/h 6
+
+# Test with identical rxlev across lchans (trivial and unrealistic)
+set-ts-use trx 0 0 states * - TCH/HH TCH/H- TCH/HH pdch - -
+meas-rep lchan * * * * rxlev 30 rxqual 0 ta 0
+congestion-check
+expect-as from lchan 0 0 3 0 to lchan 0 0 1 0
+expect-ts-use trx 0 0 states * TCH/F TCH/HH pdch TCH/HH pdch - -
+
+# clear measurements for the next run
+set-ts-use trx 0 0 states * - pdch pdch pdch pdch - -
+
+# Check that a weaker rxlev coming up earlier in the congestion checking loop does not override the favored half-used
+# TCH/H
+set-ts-use trx 0 0 states * - TCH/HH TCH/H- TCH/HH pdch - -
+meas-rep lchan 0 0 2 1 rxlev 30 rxqual 0 ta 0
+meas-rep lchan 0 0 3 0 rxlev 31 rxqual 0 ta 0
+meas-rep lchan 0 0 4 0 rxlev 32 rxqual 0 ta 0
+meas-rep lchan 0 0 4 1 rxlev 33 rxqual 0 ta 0
+congestion-check
+expect-as from lchan 0 0 3 0 to lchan 0 0 1 0
+expect-ts-use trx 0 0 states * TCH/F TCH/HH pdch TCH/HH pdch - -
+
+# clear measurements for the next run
+set-ts-use trx 0 0 states * - pdch pdch pdch pdch - -
+
+# Check that a weaker rxlev coming up later in the congestion checking loop does not override the favored half-used
+# TCH/H
+set-ts-use trx 0 0 states * - TCH/HH TCH/H- TCH/HH pdch - -
+meas-rep lchan 0 0 2 1 rxlev 34 rxqual 0 ta 0
+meas-rep lchan 0 0 3 0 rxlev 33 rxqual 0 ta 0
+meas-rep lchan 0 0 4 0 rxlev 32 rxqual 0 ta 0
+meas-rep lchan 0 0 4 1 rxlev 31 rxqual 0 ta 0
+congestion-check
+expect-as from lchan 0 0 3 0 to lchan 0 0 1 0
+expect-ts-use trx 0 0 states * TCH/F TCH/HH pdch TCH/HH pdch - -
diff --git a/tests/handover/test_dyn_ts_favor_static_ts_as_target.ho_vty b/tests/handover/test_dyn_ts_favor_static_ts_as_target.ho_vty
new file mode 100644
index 000000000..0c55a2c97
--- /dev/null
+++ b/tests/handover/test_dyn_ts_favor_static_ts_as_target.ho_vty
@@ -0,0 +1,38 @@
+# If both a static and a dynamic TCH/H (even without pchan switch!) are available, we always prefer static TS.
+create-bts trx-count 1 timeslots c+s4 dyn TCH/H dyn TCH/H dyn TCH/H PDCH
+
+network
+ bts 0
+ channel allocator mode set-all ascending
+
+set-ts-use trx 0 0 states * TCH/-H TCH/-H TCH/-H TCH/-H TCH/-H TCH/-H *
+
+# the dynamic timeslot is already in TCH/H mode, and needs no pchan switch. It appears first in the list, hence it would
+# be used first -- but we prefer using static TS when still available:
+create-ms bts 0 TCH/H AMR
+expect-ts-use trx 0 0 states * TCH/-H TCH/HH TCH/-H TCH/-H TCH/-H TCH/-H *
+# ^
+
+# Interference ratings do NOT influence whether a static or dynamic lchan (even without pchan switch) is going to be
+# assigned.
+network
+ bts 0
+ channel allocator avoid-interference 1
+ interference-meas level-bounds -115 -109 -103 -97 -91 -85
+# 0 1 2 3 4 5
+
+# Here the dyn TS lchan happens to have less interference. But still the choice to prefer static over dynamic weighs
+# stronger. The static TS with least interference is picked.
+# dyn TCH/H dyn TCH/H dyn TCH/H
+expect-ts-use trx 0 0 states * TCH/-H TCH/HH TCH/-H TCH/-H TCH/-H TCH/-H *
+res-ind trx 0 0 levels - 4- -- 1- 4- 3- 2- -
+create-ms bts 0 TCH/H AMR
+expect-ts-use trx 0 0 states * TCH/-H TCH/HH TCH/-H TCH/-H TCH/-H TCH/HH *
+# ^
+create-ms bts 0 TCH/H AMR
+expect-ts-use trx 0 0 states * TCH/-H TCH/HH TCH/-H TCH/HH TCH/-H TCH/HH *
+# ^
+# now only dynamic TS are left. The one dyn lchan with least interference is picked
+create-ms bts 0 TCH/H AMR
+expect-ts-use trx 0 0 states * TCH/-H TCH/HH TCH/HH TCH/HH TCH/-H TCH/HH *
+# ^
diff --git a/tests/handover/test_ho_to_better_cell.ho_vty b/tests/handover/test_ho_to_better_cell.ho_vty
new file mode 100644
index 000000000..afa0a8871
--- /dev/null
+++ b/tests/handover/test_ho_to_better_cell.ho_vty
@@ -0,0 +1,8 @@
+# Handover to best better cell
+# The best neighbor cell is selected
+create-n-bts 7
+set-ts-use trx 0 0 states * TCH/F - - - - - -
+meas-rep lchan 0 0 1 0 rxlev 10 rxqual 0 ta 0 neighbors 20 21 18 20 23 19
+expect-ho from lchan 0 0 1 0 to lchan 5 0 1 0
+expect-ts-use trx 0 0 states * - - - - - - -
+expect-ts-use trx 5 0 states * TCH/F - - - - - -
diff --git a/tests/handover/test_ho_to_better_cell_2.ho_vty b/tests/handover/test_ho_to_better_cell_2.ho_vty
new file mode 100644
index 000000000..c3f554438
--- /dev/null
+++ b/tests/handover/test_ho_to_better_cell_2.ho_vty
@@ -0,0 +1,10 @@
+# Handover to best better cell
+# The best neighbor cell is selected
+
+create-n-bts 7
+set-ts-use trx 0 0 states * TCH/F - - - - - -
+meas-rep lchan 0 0 1 0 rxlev 10 rxqual 0 ta 0 neighbors 20 21 18 20 23 19
+expect-ho from lchan 0 0 1 0 to lchan 5 0 1 0
+expect-ts-use trx 0 0 states * - - - - - - -
+expect-ts-use trx 5 0 states * TCH/F - - - - - -
+
diff --git a/tests/handover/test_hysteresis.ho_vty b/tests/handover/test_hysteresis.ho_vty
new file mode 100644
index 000000000..5c06a360a
--- /dev/null
+++ b/tests/handover/test_hysteresis.ho_vty
@@ -0,0 +1,13 @@
+# Hysteresis
+# If neighbor cell is better, handover is only performed if the
+# amount of improvement is greater or equal hyteresis
+
+create-n-bts 2
+set-ts-use trx 0 0 states * TCH/F - - - - - -
+meas-rep lchan 0 0 1 0 rxlev 27 rxqual 0 ta 0 neighbors 30
+expect-no-chan
+meas-rep lchan 0 0 1 0 rxlev 26 rxqual 0 ta 0 neighbors 30
+expect-ho from lchan 0 0 1 0 to lchan 1 0 1 0
+expect-ts-use trx 0 0 states * - - - - - - -
+expect-ts-use trx 1 0 states * TCH/F - - - - - -
+
diff --git a/tests/handover/test_insufficient_measurements.ho_vty b/tests/handover/test_insufficient_measurements.ho_vty
new file mode 100644
index 000000000..b67a2488e
--- /dev/null
+++ b/tests/handover/test_insufficient_measurements.ho_vty
@@ -0,0 +1,46 @@
+# No (or not enough) measurements for handover
+# Do not solve congestion in cell, because there is no measurement.
+# As soon as enough measurements available (1 in our case), perform
+# handover. Afterwards the old cell becomes congested and the new
+# cell is not. Do not perform handover until new measurements are
+# received.
+#
+# two cells, first in congested, but no handover:
+
+create-n-bts 2
+network
+ bts 0
+ handover2 min-free-slots tch/f 4
+ handover2 min-free-slots tch/h 4
+set-ts-use trx 0 0 states * TCH/F - - - - - -
+congestion-check
+expect-no-chan
+expect-ts-use trx 0 0 states * TCH/F - - - - - -
+
+# send measurement and trigger congestion check:
+meas-rep lchan 0 0 1 0 rxlev 20 rxqual 0 ta 0 neighbors 20
+expect-no-chan
+congestion-check
+expect-ho from lchan 0 0 1 0 to lchan 1 0 1 0
+expect-ts-use trx 0 0 states * - - - - - - -
+expect-ts-use trx 1 0 states * TCH/F - - - - - -
+
+# congest the first cell and remove congestion from second cell:
+network
+ bts 0
+ handover2 min-free-slots tch/f 0
+ handover2 min-free-slots tch/h 0
+ bts 1
+ handover2 min-free-slots tch/f 4
+ handover2 min-free-slots tch/h 4
+
+# no handover until measurements applied:
+congestion-check
+expect-no-chan
+meas-rep lchan 1 0 1 0 rxlev 20 rxqual 0 ta 0 neighbors 20
+expect-no-chan
+congestion-check
+expect-ho from lchan 1 0 1 0 to lchan 0 0 1 0
+expect-ts-use trx 0 0 states * TCH/F - - - - - -
+expect-ts-use trx 1 0 states * - - - - - - -
+
diff --git a/tests/handover/test_keep_efr_codec.ho_vty b/tests/handover/test_keep_efr_codec.ho_vty
new file mode 100644
index 000000000..f1a9b40e6
--- /dev/null
+++ b/tests/handover/test_keep_efr_codec.ho_vty
@@ -0,0 +1,21 @@
+# TCH/F keeping with EFR codec
+# The MS is using full rate V2 codec, but the better cell is congested
+# at TCH/F slots. As the congestion is removed, the handover takes
+# place.
+
+create-n-bts 2
+network
+ bts 1
+ handover2 min-free-slots tch/f 4
+codec tch/f EFR
+set-ts-use trx 0 0 states * TCH/F - - - - - -
+meas-rep lchan 0 0 1 0 rxlev 20 rxqual 0 ta 0 neighbors 30
+expect-no-chan
+network
+ bts 1
+ handover2 min-free-slots tch/f 3
+meas-rep lchan 0 0 1 0 rxlev 20 rxqual 0 ta 0 neighbors 30
+expect-ho from lchan 0 0 1 0 to lchan 1 0 1 0
+expect-ts-use trx 0 0 states * - - - - - - -
+expect-ts-use trx 1 0 states * TCH/F - - - - - -
+
diff --git a/tests/handover/test_keep_fr_codec.ho_vty b/tests/handover/test_keep_fr_codec.ho_vty
new file mode 100644
index 000000000..4729d2514
--- /dev/null
+++ b/tests/handover/test_keep_fr_codec.ho_vty
@@ -0,0 +1,21 @@
+# TCH/F keeping with FR codec
+# The MS is using full rate V1 codec, but the better cell is congested
+# at TCH/F slots. As the congestion is removed, the handover takes
+# place.
+
+create-n-bts 2
+network
+ bts 1
+ handover2 min-free-slots tch/f 4
+codec tch/f FR
+set-ts-use trx 0 0 states * TCH/F - - - - - -
+meas-rep lchan 0 0 1 0 rxlev 20 rxqual 0 ta 0 neighbors 30
+expect-no-chan
+network
+ bts 1
+ handover2 min-free-slots tch/f 3
+meas-rep lchan 0 0 1 0 rxlev 20 rxqual 0 ta 0 neighbors 30
+expect-ho from lchan 0 0 1 0 to lchan 1 0 1 0
+expect-ts-use trx 0 0 states * - - - - - - -
+expect-ts-use trx 1 0 states * TCH/F - - - - - -
+
diff --git a/tests/handover/test_keep_hr_codec.ho_vty b/tests/handover/test_keep_hr_codec.ho_vty
new file mode 100644
index 000000000..2f514bf44
--- /dev/null
+++ b/tests/handover/test_keep_hr_codec.ho_vty
@@ -0,0 +1,20 @@
+# TCH/H keeping with HR codec
+# The MS is using half rate V1 codec, but the better cell is congested
+# at TCH/H slots. As the congestion is removed, the handover takes
+# place.
+
+create-n-bts 2
+network
+ bts 1
+ handover2 min-free-slots tch/h 4
+codec tch/h HR
+set-ts-use trx 0 0 states * - - - - TCH/H- - -
+meas-rep lchan 0 0 5 0 rxlev 20 rxqual 0 ta 0 neighbors 30
+expect-no-chan
+network
+ bts 1
+ handover2 min-free-slots tch/h 3
+meas-rep lchan 0 0 5 0 rxlev 20 rxqual 0 ta 0 neighbors 30
+expect-ho from lchan 0 0 5 0 to lchan 1 0 5 0
+expect-ts-use trx 0 0 states * - - - - - - -
+expect-ts-use trx 1 0 states * - - - - TCH/H- - -
diff --git a/tests/handover/test_max_handovers.ho_vty b/tests/handover/test_max_handovers.ho_vty
new file mode 100644
index 000000000..76a9ea9e7
--- /dev/null
+++ b/tests/handover/test_max_handovers.ho_vty
@@ -0,0 +1,18 @@
+# No more parallel handovers, if max_unsync_ho is defined
+# There are three mobiles that want to handover, but only two can do
+# it at a time, because the maximum number is limited to two.
+
+create-n-bts 2
+network
+ bts 1
+ handover2 max-handovers 2
+set-ts-use trx 0 0 states * TCH/F TCH/F TCH/F - - - -
+meas-rep lchan 0 0 1 0 rxlev 0 rxqual 0 ta 0 neighbors 30
+expect-chan lchan 1 0 1 0
+expect-ho-cmd lchan 0 0 1 0
+meas-rep lchan 0 0 2 0 rxlev 0 rxqual 0 ta 0 neighbors 30
+expect-chan lchan 1 0 2 0
+expect-ho-cmd lchan 0 0 2 0
+meas-rep lchan 0 0 3 0 rxlev 0 rxqual 0 ta 0 neighbors 30
+expect-no-chan
+
diff --git a/tests/handover/test_max_ta.ho_vty b/tests/handover/test_max_ta.ho_vty
new file mode 100644
index 000000000..56dbb19d1
--- /dev/null
+++ b/tests/handover/test_max_ta.ho_vty
@@ -0,0 +1,37 @@
+# Handover due to maximum TA exceeded
+# The MS in the current (best) cell has reached maximum allowed timing
+# advance. No handover is performed until the timing advance exceeds
+# it. The originating cell is still the best, but no handover is
+# performed back to that cell, because the penalty timer (due to
+# maximum allowed timing advance) is running.
+
+create-n-bts 2
+set-ts-use trx 0 0 states * TCH/F - - - - - -
+
+network
+ bts 0
+ handover2 maximum distance 5
+ handover2 penalty-time max-distance 17
+
+meas-rep lchan 0 0 1 0 rxlev 30 rxqual 0 ta 5 neighbors 20
+expect-no-chan
+meas-rep lchan 0 0 1 0 rxlev 30 rxqual 0 ta 6 neighbors 20
+expect-ho from lchan 0 0 1 0 to lchan 1 0 1 0
+expect-ts-use trx 0 0 states * - - - - - - -
+expect-ts-use trx 1 0 states * TCH/F - - - - - -
+
+# Penalty timer after TA was exceeded is running, so no handover back to the better cell:
+meas-rep lchan 1 0 1 0 rxlev 20 rxqual 0 ta 6 neighbors 30
+expect-no-chan
+
+wait 16
+# Penalty timer still running
+meas-rep lchan 1 0 1 0 rxlev 20 rxqual 0 ta 6 neighbors 30
+expect-no-chan
+
+wait 1
+# Now 17 seconds have passed, timeout is done, and a handover is performed again.
+meas-rep lchan 1 0 1 0 rxlev 20 rxqual 0 ta 6 neighbors 30
+expect-ho from lchan 1 0 1 0 to lchan 0 0 1 0
+expect-ts-use trx 0 0 states * TCH/F - - - - - -
+expect-ts-use trx 1 0 states * - - - - - - -
diff --git a/tests/handover/test_meas_rep_multi_band.ho_vty b/tests/handover/test_meas_rep_multi_band.ho_vty
new file mode 100644
index 000000000..d5fa62448
--- /dev/null
+++ b/tests/handover/test_meas_rep_multi_band.ho_vty
@@ -0,0 +1,47 @@
+# Test ARFCN parsing from measurement report in multi-band BSS (OS#5717)
+
+create-n-bts 5
+
+set-band bts 0 1800
+set-arfcn trx 0 0 600
+
+set-band bts 1 900
+set-arfcn trx 1 0 1000
+
+set-band bts 2 850
+set-arfcn trx 2 0 200
+
+set-band bts 3 900
+set-arfcn trx 3 0 0
+
+set-band bts 4 1800
+set-arfcn trx 4 0 800
+
+# Attach MS to BTS 0, BTS 1-4 are neighbors
+create-ms bts 0 TCH/F AMR
+
+expect-ts-use trx 0 0 states * TCH/F - - - - - -
+expect-ts-use trx 1 0 states * - - - - - - -
+expect-ts-use trx 2 0 states * - - - - - - -
+expect-ts-use trx 3 0 states * - - - - - - -
+expect-ts-use trx 4 0 states * - - - - - - -
+
+# Send a measurement report where TRX with ARFCN=800 has the best rxqual. If
+# the BSC resolved the indexes in the measurement report correctly according to
+# 3GPP TS 04.08 ยง 10.5.2.20, then the neighbors are the following:
+# Sub list 1 (band == 1800, same band as the TRX where MS is attached):
+# IDX=0 ARFCN=800 BSIC=63 RXLEV=-75dBm (BTS 4)
+# Sub list 2 (other bands):
+# IDX=1 ARFCN=200 BSIC=63 RXLEV=-110dBm (BTS 2)
+# IDX=2 ARFCN=1000 BSIC=63 RXLEV=-110dBm (BTS 1)
+# IDX=3 ARFCN=0 BSIC=63 RXLEV=-110dBm (BTS 3; at the end because ARFCN=0)
+meas-rep lchan 0 0 1 0 rxlev 20 rxqual 0 ta 0 neighbors 35 0 0 0
+
+# If the BSC parsed the list correctly, it will request a handover to BTS 4.
+expect-ho from lchan 0 0 1 0 to lchan 4 0 1 0
+
+expect-ts-use trx 0 0 states * - - - - - - -
+expect-ts-use trx 1 0 states * - - - - - - -
+expect-ts-use trx 2 0 states * - - - - - - -
+expect-ts-use trx 3 0 states * - - - - - - -
+expect-ts-use trx 4 0 states * TCH/F - - - - - -
diff --git a/tests/handover/test_min_rxlev_vs_congestion.ho_vty b/tests/handover/test_min_rxlev_vs_congestion.ho_vty
new file mode 100644
index 000000000..d43b89cfa
--- /dev/null
+++ b/tests/handover/test_min_rxlev_vs_congestion.ho_vty
@@ -0,0 +1,18 @@
+# Handover to congested cell, if RX level is below minimum
+# The better neighbor cell is congested, so no handover is performed.
+# If the RX level of the current cell drops below minimum acceptable
+# level, the handover is performed.
+
+create-n-bts 2
+set-ts-use trx 0 0 states * TCH/F - - - - - -
+network
+ bts 1
+ handover2 min-free-slots tch/f 4
+ handover2 min-free-slots tch/h 4
+meas-rep lchan 0 0 1 0 rxlev 10 rxqual 0 ta 0 neighbors 30
+expect-no-chan
+meas-rep lchan 0 0 1 0 rxlev 9 rxqual 0 ta 0 neighbors 30
+expect-ho from lchan 0 0 1 0 to lchan 1 0 1 0
+expect-ts-use trx 0 0 states * - - - - - - -
+expect-ts-use trx 1 0 states * TCH/F - - - - - -
+
diff --git a/tests/handover/test_min_rxlev_vs_hysteresis.ho_vty b/tests/handover/test_min_rxlev_vs_hysteresis.ho_vty
new file mode 100644
index 000000000..e034fadaa
--- /dev/null
+++ b/tests/handover/test_min_rxlev_vs_hysteresis.ho_vty
@@ -0,0 +1,20 @@
+# No Hysteresis and minimum RX level
+# If current cell's RX level is below mimium level, handover must be
+# performed, no matter of the hysteresis. First do not perform
+# handover to better neighbor cell, because the hysteresis is not
+# met. Second do not perform handover because better neighbor cell is
+# below minimum RX level. Third perform handover because current cell
+# is below minimum RX level, even if the better neighbor cell (minimum
+# RX level reached) does not meet the hysteresis.
+
+create-n-bts 2
+set-ts-use trx 0 0 states * TCH/F - - - - - -
+meas-rep lchan 0 0 1 0 rxlev 10 rxqual 0 ta 0 neighbors 11
+expect-no-chan
+meas-rep lchan 0 0 1 0 rxlev 8 rxqual 0 ta 0 neighbors 9
+expect-no-chan
+meas-rep lchan 0 0 1 0 rxlev 9 rxqual 0 ta 0 neighbors 10
+expect-ho from lchan 0 0 1 0 to lchan 1 0 1 0
+expect-ts-use trx 0 0 states * - - - - - - -
+expect-ts-use trx 1 0 states * TCH/F - - - - - -
+
diff --git a/tests/handover/test_neighbor_congested.ho_vty b/tests/handover/test_neighbor_congested.ho_vty
new file mode 100644
index 000000000..b2319d40f
--- /dev/null
+++ b/tests/handover/test_neighbor_congested.ho_vty
@@ -0,0 +1,21 @@
+# No handover to congested cell
+# The better neighbor cell is congested, so no handover is performed.
+# After the congestion is over, handover will be performed.
+
+create-n-bts 2
+set-ts-use trx 0 0 states * TCH/F - - - - - -
+network
+ bts 1
+ handover2 min-free-slots tch/f 4
+ handover2 min-free-slots tch/h 4
+meas-rep lchan 0 0 1 0 rxlev 20 rxqual 0 ta 0 neighbors 30
+expect-no-chan
+network
+ bts 1
+ handover2 min-free-slots tch/f 3
+ handover2 min-free-slots tch/h 3
+meas-rep lchan 0 0 1 0 rxlev 20 rxqual 0 ta 0 neighbors 30
+expect-ho from lchan 0 0 1 0 to lchan 1 0 1 0
+expect-ts-use trx 0 0 states * - - - - - - -
+expect-ts-use trx 1 0 states * TCH/F - - - - - -
+
diff --git a/tests/handover/test_neighbor_full.ho_vty b/tests/handover/test_neighbor_full.ho_vty
new file mode 100644
index 000000000..3b06d3dba
--- /dev/null
+++ b/tests/handover/test_neighbor_full.ho_vty
@@ -0,0 +1,9 @@
+# No handover to a cell with no slots available
+# If no slot is available, no handover is performed
+
+create-n-bts 2
+set-ts-use trx 0 0 states * TCH/F - - - - - -
+set-ts-use trx 1 0 states * TCH/F TCH/F TCH/F TCH/F TCH/HH TCH/HH -
+meas-rep lchan 0 0 1 0 rxlev 0 rxqual 0 ta 0 neighbors 30
+expect-no-chan
+
diff --git a/tests/handover/test_no_congestion.ho_vty b/tests/handover/test_no_congestion.ho_vty
new file mode 100644
index 000000000..c8328c6f2
--- /dev/null
+++ b/tests/handover/test_no_congestion.ho_vty
@@ -0,0 +1,17 @@
+# Congestion check: No congestion
+# Three cells have different number of used slots, but there is no
+# congestion in any of these cells. No handover is performed.
+
+create-n-bts 3
+network
+ handover2 min-free-slots tch/f 2
+ handover2 min-free-slots tch/h 2
+set-ts-use trx 0 0 states * TCH/F TCH/F - - TCH/HH - -
+set-ts-use trx 1 0 states * TCH/F - - - TCH/H- - -
+meas-rep lchan * * * * rxlev 30 rxqual 0 ta 0 neighbors 20 1 20
+expect-no-chan
+congestion-check
+expect-no-chan
+expect-ts-use trx 0 0 states * TCH/F TCH/F - - TCH/HH - -
+expect-ts-use trx 1 0 states * TCH/F - - - TCH/H- - -
+
diff --git a/tests/handover/test_penalty_timer.ho_vty b/tests/handover/test_penalty_timer.ho_vty
new file mode 100644
index 000000000..8d864a2d4
--- /dev/null
+++ b/tests/handover/test_penalty_timer.ho_vty
@@ -0,0 +1,45 @@
+# Penalty timer must not run
+# The MS will try to handover to a better cell, but this will fail.
+# Even though the cell is still better, handover will not be performed
+# due to penalty timer after handover failure
+
+network
+ # set the timeout for LCHAN_ST_WAIT_AFTER_ERROR
+ timer X3111 5
+ # set penalty timeout
+ handover2 penalty-time failed-ho 23
+
+create-n-bts 2
+set-ts-use trx 0 0 states * TCH/F - - - - - -
+meas-rep lchan 0 0 1 0 rxlev 20 rxqual 0 ta 0 neighbors 30
+expect-chan lchan 1 0 1 0
+expect-ho-cmd lchan 0 0 1 0
+ho-failed
+# first BTS still services the call:
+expect-ts-use trx 0 0 states * TCH/F - - - - - -
+
+# lchan 1 0 1 0 is in LCHAN_ST_WAIT_AFTER_ERROR because the handover failed:
+expect-ts-use trx 1 0 states * ! - - - - - -
+wait 4
+expect-ts-use trx 1 0 states * ! - - - - - -
+wait 1
+expect-ts-use trx 1 0 states * - - - - - - -
+# back to UNUSED
+
+# No handover because the penalty timer is still running
+meas-rep lchan 0 0 1 0 rxlev 20 rxqual 0 ta 0 neighbors 30
+expect-no-chan
+
+wait 17
+# at this point, the penalty timer has not yet expired. (4+1+17 = 22 < 23)
+meas-rep lchan 0 0 1 0 rxlev 20 rxqual 0 ta 0 neighbors 30
+expect-no-chan
+expect-ts-use trx 0 0 states * TCH/F - - - - - -
+expect-ts-use trx 1 0 states * - - - - - - -
+
+wait 1
+# now the penalty timer of 23 seconds has passed and the handover is attempted again.
+meas-rep lchan 0 0 1 0 rxlev 20 rxqual 0 ta 0 neighbors 30
+expect-ho from lchan 0 0 1 0 to lchan 1 0 1 0
+expect-ts-use trx 0 0 states * - - - - - - -
+expect-ts-use trx 1 0 states * TCH/F - - - - - -
diff --git a/tests/handover/test_resource_indication.ho_vty b/tests/handover/test_resource_indication.ho_vty
new file mode 100644
index 000000000..724372ee9
--- /dev/null
+++ b/tests/handover/test_resource_indication.ho_vty
@@ -0,0 +1,67 @@
+# Test effects of interference levels reported in Resource Indication.
+# Note, this is not actually a handover test.
+
+create-bts trx-count 1 timeslots c+s4 TCH/F TCH/F TCH/F TCH/F TCH/F TCH/F PDCH
+
+# By default, the ordering is most-interference-first
+network
+ bts 0
+ channel allocator avoid-interference 1
+ interference-meas level-bounds -85 -91 -97 -103 -109 -115
+# 0 1 2 3 4 5
+
+res-ind trx 0 0 levels - 1 2 3 4 3 2 -
+create-ms bts 0 TCH/F AMR
+expect-ts-use trx 0 0 states * - - - TCH/F - - *
+
+# The ordering may also be configured reversed, still the lowest dBm value should win
+network
+ bts 0
+ interference-meas level-bounds -115 -109 -103 -97 -91 -85
+# 0 1 2 3 4 5
+
+res-ind trx 0 0 levels - 5 4 2 - 3 4 -
+create-ms bts 0 TCH/F AMR
+expect-ts-use trx 0 0 states * - - TCH/F TCH/F - - *
+
+# Favor lchans that have an indicated interference level
+res-ind trx 0 0 levels - - - - - 4 3 -
+create-ms bts 0 TCH/F AMR
+expect-ts-use trx 0 0 states * - - TCH/F TCH/F - TCH/F *
+
+# For equal levels, pick the first
+res-ind trx 0 0 levels - 2 2 - - 2 - -
+create-ms bts 0 TCH/F AMR
+expect-ts-use trx 0 0 states * TCH/F - TCH/F TCH/F - TCH/F *
+
+# Test clamping of indexes > 5
+res-ind trx 0 0 levels - - 6 - - 4 - -
+create-ms bts 0 TCH/F AMR
+expect-ts-use trx 0 0 states * TCH/F - TCH/F TCH/F TCH/F TCH/F *
+
+# Also test for TCH/H
+create-bts trx-count 1 timeslots c+s4 TCH/H TCH/H TCH/H TCH/H TCH/H TCH/H PDCH
+network
+ bts 1
+ channel allocator avoid-interference 1
+ interference-meas level-bounds -115 -109 -103 -97 -91 -85
+# 0 1 2 3 4 5
+
+res-ind trx 1 0 levels - 54 32 21 23 45 54 -
+create-ms bts 1 TCH/H AMR
+expect-ts-use trx 1 0 states * - - TCH/-H - - - *
+
+# Favor lchans that have an indicated interference level
+res-ind trx 1 0 levels - - - 4- 3- - - -
+create-ms bts 1 TCH/H AMR
+expect-ts-use trx 1 0 states * - - TCH/-H TCH/H- - - *
+
+# For equal levels, pick the first
+res-ind trx 1 0 levels - -2 22 2- -2 22 2- -
+create-ms bts 1 TCH/H AMR
+expect-ts-use trx 1 0 states * TCH/-H - TCH/-H TCH/H- - - *
+
+# Test clamping of indexes > 5
+res-ind trx 1 0 levels - 7- 67 6- -7 54 6- -
+create-ms bts 1 TCH/H AMR
+expect-ts-use trx 1 0 states * TCH/-H - TCH/-H TCH/H- TCH/-H - *
diff --git a/tests/handover/test_rxqual.ho_vty b/tests/handover/test_rxqual.ho_vty
new file mode 100644
index 000000000..6f86cf47f
--- /dev/null
+++ b/tests/handover/test_rxqual.ho_vty
@@ -0,0 +1,49 @@
+# Handover to cell with worse RXLEV, if RXQUAL is below minimum
+# The neighbor cell has worse RXLEV, so no handover is performed.
+# If the RXQUAL of the current cell drops below minimum acceptable
+# level, the handover is performed. It is also required that 10
+# reports are received, before RXQUAL is checked.
+#
+# (See also test 28, which tests for RXQUAL triggering HO to congested cell.)
+#
+# TODO: bad RXQUAL may want to prefer assignment within the same cell to avoid interference.
+# See Performance Enhancements in a Frequency Hopping GSM Network (Nielsen Wigard 2002), Chapter
+# 2.1.1, "Interference" in the list of triggers on p.157.
+
+# first show undesired oscillation when penalty-time low-rxqual-ho is disabled
+network
+ handover2 penalty-time low-rxqual-ho 0
+
+create-n-bts 2
+set-ts-use trx 0 0 states * TCH/F - - - - - -
+meas-rep repeat 9 lchan 0 0 1 0 rxlev 40 rxqual 6 ta 0 neighbors 30
+expect-no-chan
+meas-rep lchan 0 0 1 0 rxlev 40 rxqual 6 ta 0 neighbors 30
+expect-ho from lchan 0 0 1 0 to lchan 1 0 1 0
+expect-ts-use trx 0 0 states * - - - - - - -
+expect-ts-use trx 1 0 states * TCH/F - - - - - -
+
+# Now the channel is on bts 1, which has lower rxlev than bts 0.
+# The result is an undesired ho oscillation, because the penalty timer is zero
+meas-rep lchan 1 0 1 0 rxlev 30 rxqual 0 ta 0 neighbors 40
+expect-ho from lchan 1 0 1 0 to lchan 0 0 1 0
+
+# Set a proper penalty timeout and report bad-rxqual again
+network
+ handover2 penalty-time low-rxqual-ho 10
+meas-rep repeat 10 lchan 0 0 1 0 rxlev 40 rxqual 6 ta 0 neighbors 30
+expect-ho from lchan 0 0 1 0 to lchan 1 0 1 0
+
+# This time the penalty timer prevents oscillation
+meas-rep repeat 10 lchan 1 0 1 0 rxlev 30 rxqual 0 ta 0 neighbors 40
+expect-no-chan
+
+# After the penalty timeout passes, we do go back to the cell with stronger rxlev
+wait 10
+meas-rep lchan 1 0 1 0 rxlev 30 rxqual 0 ta 0 neighbors 40
+expect-ho from lchan 1 0 1 0 to lchan 0 0 1 0
+# If the rxqual is still bad here after the penalty timeout, well, then we quickly snap back to the weaker cell, once
+meas-rep repeat 10 lchan 0 0 1 0 rxlev 40 rxqual 6 ta 0 neighbors 30
+expect-ho from lchan 0 0 1 0 to lchan 1 0 1 0
+meas-rep repeat 10 lchan 1 0 1 0 rxlev 30 rxqual 0 ta 0 neighbors 40
+expect-no-chan
diff --git a/tests/handover/test_rxqual_vs_congestion.ho_vty b/tests/handover/test_rxqual_vs_congestion.ho_vty
new file mode 100644
index 000000000..21c4e7e18
--- /dev/null
+++ b/tests/handover/test_rxqual_vs_congestion.ho_vty
@@ -0,0 +1,19 @@
+# Handover to congested cell, if RX quality is below minimum
+# The better neighbor cell is congested, so no handover is performed.
+# If the RX quality of the current cell drops below minimum acceptable
+# level, the handover is performed. It is also required that 10
+# resports are received, before RX quality is checked.
+
+create-n-bts 2
+set-ts-use trx 0 0 states * TCH/F - - - - - -
+network
+ bts 1
+ handover2 min-free-slots tch/f 4
+ handover2 min-free-slots tch/h 4
+meas-rep repeat 9 lchan 0 0 1 0 rxlev 30 rxqual 6 ta 0 neighbors 40
+expect-no-chan
+meas-rep lchan 0 0 1 0 rxlev 30 rxqual 6 ta 0 neighbors 40
+expect-ho from lchan 0 0 1 0 to lchan 1 0 1 0
+expect-ts-use trx 0 0 states * - - - - - - -
+expect-ts-use trx 1 0 states * TCH/F - - - - - -
+
diff --git a/tests/handover/test_stay_in_better_cell.ho_vty b/tests/handover/test_stay_in_better_cell.ho_vty
new file mode 100644
index 000000000..00e0e1a73
--- /dev/null
+++ b/tests/handover/test_stay_in_better_cell.ho_vty
@@ -0,0 +1,6 @@
+# Stay in better cell
+# There are many neighbor cells, but only the current cell is the best cell, so no handover is performed
+create-n-bts 7
+set-ts-use trx 0 0 states * TCH/F - - - - - -
+meas-rep lchan 0 0 1 0 rxlev 30 rxqual 0 ta 0 neighbors 20 21 18 20 23 19
+expect-no-chan
diff --git a/tests/handover/test_stay_in_better_cell_2.ho_vty b/tests/handover/test_stay_in_better_cell_2.ho_vty
new file mode 100644
index 000000000..b3e76f8fc
--- /dev/null
+++ b/tests/handover/test_stay_in_better_cell_2.ho_vty
@@ -0,0 +1,10 @@
+# Stay in better cell
+# There are many neighbor cells, but only the current cell is the best
+# cell, so no handover is performed
+
+create-n-bts 7
+create-ms bts 0 TCH/F AMR
+expect-ts-use trx 0 0 states * TCH/F - - - - - -
+meas-rep lchan 0 0 1 0 rxlev 30 rxqual 0 ta 0 neighbors 20 21 18 20 23 19
+expect-no-chan
+
diff --git a/tests/handover/test_story.ho_vty b/tests/handover/test_story.ho_vty
new file mode 100644
index 000000000..4e827610e
--- /dev/null
+++ b/tests/handover/test_story.ho_vty
@@ -0,0 +1,72 @@
+# Story: 'A neighbor is your friend'
+
+create-n-bts 3
+
+# Andreas is driving along the coast, on a sunny june afternoon.
+# Suddenly he is getting a call from his friend and neighbor Axel.
+#
+# What happens: Two MS are created, #0 for Axel, #1 for Andreas.
+# Axel:
+create-ms bts 2 TCH/F AMR
+# andreas:
+create-ms bts 0 TCH/F AMR
+expect-ts-use trx 0 0 states * TCH/F - - - - - -
+expect-ts-use trx 1 0 states * - - - - - - -
+expect-ts-use trx 2 0 states * TCH/F - - - - - -
+meas-rep lchan 0 0 1 0 rxlev 40 rxqual 0 ta 0 neighbors 30
+expect-no-chan
+
+# Axel asks Andreas if he would like to join them for a barbecue.
+# Axel's house is right in the neighborhood and the weather is fine.
+# Andreas agrees, so he drives to a close store to buy some barbecue
+# skewers.
+#
+# What happens: While driving, a different cell (mounted atop the
+# store) becomes better.
+# drive to bts 1:
+meas-rep lchan 0 0 1 0 rxlev 20 rxqual 0 ta 0 neighbors 35
+expect-ho from lchan 0 0 1 0 to lchan 1 0 1 0
+expect-ts-use trx 0 0 states * - - - - - - -
+expect-ts-use trx 1 0 states * TCH/F - - - - - -
+expect-ts-use trx 2 0 states * TCH/F - - - - - -
+
+# While Andreas is walking into the store, Axel asks, if he could also
+# bring some beer. Andreas has problems understanding him: "I have a
+# bad reception here. The cell tower is right atop the store, but poor
+# coverage inside. Can you repeat please?"
+#
+# What happens: Inside the store the close cell is so bad, that
+# handover back to the previous cell is required.
+# bts 1 becomes bad, so bts 0 helps out:
+meas-rep lchan 1 0 1 0 rxlev 5 rxqual 0 ta 0 neighbors 20
+expect-ho from lchan 1 0 1 0 to lchan 0 0 1 0
+expect-ts-use trx 0 0 states * TCH/F - - - - - -
+expect-ts-use trx 1 0 states * - - - - - - -
+expect-ts-use trx 2 0 states * TCH/F - - - - - -
+
+# After Andreas bought skewers and beer, he leaves the store.
+#
+# What happens: Outside the store the close cell is better again, so
+# handover back to the that cell is performed.
+# bts 1 becomes better again:
+meas-rep lchan 0 0 1 0 rxlev 20 rxqual 0 ta 0 neighbors 35
+expect-ho from lchan 0 0 1 0 to lchan 1 0 1 0
+expect-ts-use trx 0 0 states * - - - - - - -
+expect-ts-use trx 1 0 states * TCH/F - - - - - -
+expect-ts-use trx 2 0 states * TCH/F - - - - - -
+
+# bts 2 becomes better:
+# Andreas drives down to the lake where Axel's house is.
+#
+# What happens: There is a small cell at Axel's house, which becomes
+# better, because the current cell has no good comverage at the lake.
+meas-rep lchan 1 0 1 0 rxlev 14 rxqual 0 ta 0 neighbors 2 63
+expect-ho from lchan 1 0 1 0 to lchan 2 0 2 0
+expect-ts-use trx 0 0 states * - - - - - - -
+expect-ts-use trx 1 0 states * - - - - - - -
+expect-ts-use trx 2 0 states * TCH/F TCH/F - - - - -
+
+# Andreas wonders why he still has good radio coverage: "Last time it
+# was so bad". Axel says: "I installed a pico cell in my house,
+# now we can use our mobile phones down here at the lake."
+