hnb: Introduce HNB_Tests testsuite

A new Iuh CodecPort + Emulation is introduced to (de)mux RANAP and RUA
in the same SCTP socket.
The Iuh_CodecPort.ttcn file has currently a hack to be able to test
HNBAP, since titan seem to be reporting sinfo_ppid=0 when in fact it
received sinfo_ppid=20 (HNBAP).

A couple tests are added to validate HNBAP HNBRegister Request  + Accept
or Reject. In current osmo-hnodeb state, both tests pass if run
separately, but fail if run sequentially since osmo-hnodeb still doesn't
re-connect properly after first test finishes and connection is dropped.

Related: SYS#5516
Change-Id: I7227917148e98a2c777f4b05d8d2eca6e9c121b7
diff --git a/hnodeb/HNB_Tests.ttcn b/hnodeb/HNB_Tests.ttcn
new file mode 100644
index 0000000..7a668dd
--- /dev/null
+++ b/hnodeb/HNB_Tests.ttcn
@@ -0,0 +1,214 @@
+module HNB_Tests {
+
+/* Integration Tests for OsmoHNodeB
+ * (C) 2021 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * All rights reserved.
+ *
+ * Released under the terms of GNU General Public License, Version 2 or
+ * (at your option) any later version.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This test suite tests OsmoHNodB while emulating both multiple UE as
+ * well as the HNBGW. See README for more details.
+ *
+ * There are test cases that run in so-called 'handler mode' and test cases
+ * that run directly on top of the BSSAP and RSL CodecPorts.  The "handler mode"
+ * tests abstract the multiplexing/demultiplexing of multiple SCCP connections
+ * and/or RSL channels and are hence suitable for higher-level test cases, while
+ * the "raw" tests directly on top of the CodecPorts are more suitable for lower-
+ * level testing.
+ */
+
+import from Misc_Helpers all;
+import from General_Types all;
+import from Osmocom_Types all;
+import from IPL4asp_Types all;
+
+import from Osmocom_CTRL_Functions all;
+import from Osmocom_CTRL_Types all;
+import from Osmocom_CTRL_Adapter all;
+
+import from StatsD_Types all;
+import from StatsD_CodecPort all;
+import from StatsD_CodecPort_CtrlFunct all;
+import from StatsD_Checker all;
+
+import from Osmocom_VTY_Functions all;
+import from TELNETasp_PortType all;
+
+import from HNBAP_Templates all;
+
+import from HNBGW_ConnectionHandler all;
+import from Iuh_Emulation all;
+
+modulepar {
+	/* IP address at which the HNodeB can be reached */
+	charstring mp_hnodeb_ip := "127.0.0.1";
+
+	/* IP address at which the test binds */
+	charstring mp_hnbgw_iuh_ip := "127.0.0.1";
+	integer mp_hnbgw_iuh_port := 29169;
+}
+
+type component test_CT extends CTRL_Adapter_CT {
+	port TELNETasp_PT HNBVTY;
+
+	/* global test case guard timer (actual timeout value is set in f_init()) */
+	timer T_guard := 30.0;
+}
+
+/* global altstep for global guard timer; */
+altstep as_Tguard() runs on test_CT {
+	[] T_guard.timeout {
+			setverdict(fail, "Timeout of T_guard");
+			mtc.stop;
+		}
+}
+
+friend function f_logp(TELNETasp_PT pt, charstring log_msg)
+{
+	// log on TTCN3 log output
+	log(log_msg);
+	// log in stderr log
+	f_vty_transceive(pt, "logp lglobal notice TTCN3 f_logp(): " & log_msg);
+}
+
+function f_init_vty(charstring id := "foo") runs on test_CT {
+	if (HNBVTY.checkstate("Mapped")) {
+		/* skip initialization if already executed once */
+		return;
+	}
+	map(self:HNBVTY, system:HNBVTY);
+	f_vty_set_prompts(HNBVTY);
+	f_vty_transceive(HNBVTY, "enable");
+}
+/* global initialization function */
+function f_init(float guard_timeout := 30.0) runs on test_CT {
+	var integer bssap_idx;
+
+	T_guard.start(guard_timeout);
+	activate(as_Tguard());
+
+	f_init_vty("VirtHNBGW");
+
+	/* TODO: Wait for Iuh connection to be established */
+}
+
+friend function f_shutdown_helper() runs on test_CT {
+	all component.stop;
+	setverdict(pass);
+	mtc.stop;
+}
+
+friend function f_gen_test_hdlr_pars() return TestHdlrParams {
+
+	var TestHdlrParams pars := valueof(t_def_TestHdlrPars);
+	pars.hnodeb_addr := mp_hnodeb_ip;
+	pars.hnbgw_addr := mp_hnbgw_iuh_ip;
+	pars.hnbgw_port := mp_hnbgw_iuh_port;
+	return pars;
+}
+
+type function void_fn(charstring id) runs on HNBGW_ConnHdlr;
+
+/* helper function to create and connect a HNBGW_ConnHdlr component */
+private function f_connect_handler(inout HNBGW_ConnHdlr vc_conn, integer bssap_idx := 0) runs on test_CT {
+	/*connect(vc_conn:RAN, g_bssap[bssap_idx].vc_RAN:PROC);
+	connect(vc_conn:MGCP_PROC, vc_MGCP:MGCP_PROC);
+	connect(vc_conn:RSL, bts[0].rsl.vc_RSL:CLIENT_PT);
+	connect(vc_conn:RSL_PROC, bts[0].rsl.vc_RSL:RSL_PROC);
+	if (isvalue(bts[1])) {
+		connect(vc_conn:RSL1, bts[1].rsl.vc_RSL:CLIENT_PT);
+		connect(vc_conn:RSL1_PROC, bts[1].rsl.vc_RSL:RSL_PROC);
+	}
+	if (isvalue(bts[2])) {
+		connect(vc_conn:RSL2, bts[2].rsl.vc_RSL:CLIENT_PT);
+		connect(vc_conn:RSL2_PROC, bts[2].rsl.vc_RSL:RSL_PROC);
+	}
+	connect(vc_conn:BSSAP, g_bssap[bssap_idx].vc_RAN:CLIENT);
+	if (mp_enable_lcs_tests) {
+		connect(vc_conn:BSSAP_LE, g_bssap_le.vc_BSSAP_LE:CLIENT);
+		connect(vc_conn:BSSAP_LE_PROC, g_bssap_le.vc_BSSAP_LE:PROC);
+	}
+	connect(vc_conn:MGCP, vc_MGCP:MGCP_CLIENT);
+	connect(vc_conn:MGCP_MULTI, vc_MGCP:MGCP_CLIENT_MULTI);
+	connect(vc_conn:STATSD_PROC, vc_STATSD:STATSD_PROC);*/
+}
+
+function f_start_handler_create(TestHdlrParams pars)
+runs on test_CT return HNBGW_ConnHdlr {
+	var charstring id := testcasename();
+	var HNBGW_ConnHdlr vc_conn;
+	vc_conn := HNBGW_ConnHdlr.create(id);
+	f_connect_handler(vc_conn);
+	return vc_conn;
+}
+
+function f_start_handler_run(HNBGW_ConnHdlr vc_conn, void_fn fn, TestHdlrParams pars)
+runs on test_CT return HNBGW_ConnHdlr {
+	var charstring id := testcasename();
+	/* Emit a marker to appear in the SUT's own logging output */
+	f_logp(HNBVTY, id & "() start");
+	vc_conn.start(f_handler_init(fn, id, pars));
+	return vc_conn;
+}
+
+function f_start_handler(void_fn fn, template (omit) TestHdlrParams pars_tmpl := omit)
+runs on test_CT return HNBGW_ConnHdlr {
+	var TestHdlrParams pars;
+	if (isvalue(pars)) {
+		pars := valueof(pars_tmpl);
+	} else {
+		pars := valueof(f_gen_test_hdlr_pars());
+	}
+	return f_start_handler_run(f_start_handler_create(pars), fn, pars);
+}
+
+/* first function inside ConnHdlr component; sets g_pars + starts function */
+private function f_handler_init(void_fn fn, charstring id, TestHdlrParams pars)
+runs on HNBGW_ConnHdlr {
+	f_HNBGW_ConnHdlr_init(pars);
+	HNBAP.receive(IUHEM_Event:{up_down:=IUHEM_EVENT_UP}); /* Wait for HNB to connect to us */
+	fn.apply(id);
+}
+
+private function f_tc_hnb_register_request(charstring id) runs on HNBGW_ConnHdlr {
+	f_handle_hnbap_hnb_register_req();
+	f_sleep(1.0);
+}
+testcase TC_hnb_register_request_accept() runs on test_CT {
+	var HNBGW_ConnHdlr vc_conn;
+
+	f_init();
+	vc_conn := f_start_handler(refers(f_tc_hnb_register_request));
+	vc_conn.done;
+	f_shutdown_helper();
+}
+
+private function f_tc_hnb_register_reject(charstring id) runs on HNBGW_ConnHdlr {
+	HNBAP.receive(tr_HNBAP_HNBRegisterRequest(char2oct(g_pars.hNB_Identity_Info),
+						  g_pars.plmnid,
+						  int2bit(g_pars.cell_identity, 28),
+						  int2oct(g_pars.lac, 2),
+						  int2oct(g_pars.rac, 1),
+						  int2oct(g_pars.sac, 2)
+						 ));
+	HNBAP.send(ts_HNBAP_HNBRegisterReject(ts_HnbapCause(overload)));
+	f_sleep(1.0);
+}
+testcase TC_hnb_register_request_reject() runs on test_CT {
+	var HNBGW_ConnHdlr vc_conn;
+
+	f_init();
+	vc_conn := f_start_handler(refers(f_tc_hnb_register_reject));
+	vc_conn.done;
+	f_shutdown_helper();
+}
+
+control {
+	execute( TC_hnb_register_request_accept() );
+	execute( TC_hnb_register_request_reject() );
+}
+
+}