module STP_Tests_M3UA_TCP {

/* Osmocom STP test suite in in TTCN-3
 * (C) 2019 Harald Welte <laforge@gnumonks.org>
 * (C) 2024-2025 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
 */

import from General_Types all;
import from Osmocom_Types all;
import from IPL4asp_Types all;
import from Misc_Helpers all;

import from Osmocom_VTY_Functions all;

import from M3UA_Types all;
import from M3UA_Templates all;
import from M3UA_CodecPort all;
import from M3UA_CodecPort_CtrlFunct all;

import from M3UA_Emulation all;
import from MTP3asp_Types all;
import from MTP3asp_PortType all;

import from SCCP_Types all;
import from SCCP_Templates all;
import from SCCPasp_Types all;
import from SCCP_Emulation all;

import from STP_Tests_Common all;
import from STP_Tests_M3UA all;

private altstep as_M3UA_wait_tcp_conn_closed(integer idx) runs on RAW_M3UA_CT {
	var PortEvent pev;
	[] M3UA[idx].receive(PortEvent:{connClosed:=?}) -> value pev {
		log("Peer closed conn: ", pev)
	}
}

private function f_TC_m3ua_tcp(integer idx_a, integer idx_b) runs on RAW_M3UA_CT {
	var M3uaConfig cfg_a := g_m3ua_configs[idx_a];
	var M3uaConfig cfg_b := g_m3ua_configs[idx_b];
	var OCT4 rctx_a := int2oct(cfg_a.routing_ctx, 4);
	var OCT4 rctx_b := int2oct(cfg_b.routing_ctx, 4);
	var OCT4 pc_a := int2oct(cfg_a.point_code, 4);
	var OCT4 pc_b := int2oct(cfg_b.point_code, 4);

	/* establish connection with ASP 'A' */
	if (g_m3ua_configs[idx_a].is_server) {
		f_M3UA_CLNT_asp_up_act(idx_a, rctx := rctx_a);
	} else {
		f_M3UA_asp_up_act(idx_a, rctx := rctx_a);
	}

	/* establish connection with ASP 'B' */
	if (g_m3ua_configs[idx_b].is_server) {
		f_M3UA_CLNT_asp_up_act(idx_b, rctx := rctx_b);
	} else {
		f_M3UA_asp_up_act(idx_b, rctx := rctx_b);
	}

	/* In the case when ASP[idx_b] is configured as the client (i.e. osmo-stp connects to
	 * the testsuite), in f_M3UA_CLNT_asp_up_act() we're expecting to receive ASPACT and
         * then sending ASPACT_ACK to it.  Right after that, we're sending
	 * some random data via ASP[idx_a], which we then expect to receive via ASP[idx_b].
	 *
	 * There is a chance that the random data sent via ASP[idx_a] would reach osmo-stp
	 * earlier than the ASPUP_ACK we sent for ASP[idx_b].  This is happening most of the
	 * times when running TC_m3ua_tcp_cli_srv.  Using f_sleep() helps to avoid this. */
	f_sleep(1.0);

	/* M3UA/A -> M3UA/B */
	f_test_traffic(idx_a, rctx_a, pc_a,
		       idx_b, rctx_b, pc_b);
	/* M3UA/B -> M3UA/A */
	f_test_traffic(idx_b, rctx_b, pc_b,
		       idx_a, rctx_a, pc_a);

	f_clear_m3ua();
}

/* test routing between M3UA/SCTP (client) and M3UA/TCP (client) */
testcase TC_m3ua_tcp_cli() runs on RAW_M3UA_CT {
	var Misc_Helpers.ro_charstring asps := { "asp-sender", "asp-sender-tcp" };
	var M3uaConfigs m3ua_configs := m3ua_build_configs(asps);

	f_init_m3ua(m3ua_configs := m3ua_configs);
	f_TC_m3ua_tcp(0, 1);
}

/* test routing between M3UA/SCTP (client) and M3UA/TCP (server) */
testcase TC_m3ua_tcp_cli_srv() runs on RAW_M3UA_CT {
	var Misc_Helpers.ro_charstring asps := { "asp-sender", "asp-client0-tcp" };
	var M3uaConfigs m3ua_configs := m3ua_build_configs(asps);

	f_init_m3ua(m3ua_configs := m3ua_configs);
	f_init_m3ua_srv();
	f_TC_m3ua_tcp(0, 1);
}

/* test routing between M3UA/SCTP (server) and M3UA/TCP (server) */
testcase TC_m3ua_tcp_srv() runs on RAW_M3UA_CT {
	var Misc_Helpers.ro_charstring asps := { "asp-client0", "asp-client0-tcp" };
	var M3uaConfigs m3ua_configs := m3ua_build_configs(asps);

	f_init_m3ua(m3ua_configs := m3ua_configs);
	f_init_m3ua_srv();
	f_TC_m3ua_tcp(0, 1);
}

/* test routing between M3UA/SCTP (server) and M3UA/TCP (client) */
testcase TC_m3ua_tcp_srv_cli() runs on RAW_M3UA_CT {
	var Misc_Helpers.ro_charstring asps := { "asp-client0", "asp-sender-tcp" };
	var M3uaConfigs m3ua_configs := m3ua_build_configs(asps);

	f_init_m3ua(m3ua_configs := m3ua_configs);
	f_init_m3ua_srv();
	f_TC_m3ua_tcp(0, 1);
}

/* Test administrative state, VTY "[no] shutdown" */
testcase TC_m3ua_tcp_srv_adm_shutdown() runs on RAW_M3UA_CT {
	var Misc_Helpers.ro_charstring asps := { "asp-sender-tcp" };
	var M3uaConfigs m3ua_configs := m3ua_build_configs(asps);
	f_init_m3ua(m3ua_configs := m3ua_configs);

	f_M3UA_asp_up_act(0, c_M3UA_TMT_override, omit);
	f_sleep(1.0);
	f_vty_cs7_asp_cmd(g_m3ua_configs[0], "shutdown");
	log("ASP should now be DOWN")
	as_M3UA_wait_tcp_conn_closed(0);

	f_sleep(1.0);
	/* Make sure SCTP conn will be closed when we connect to it. */
	log("Connecting to ASP (expect closed):");
	f_M3UA_connect_tcp(0);
	timer T := 5.0;
	T.start;
	alt {
	[] as_M3UA_wait_tcp_conn_closed(0);
	[] T.timeout {
		Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "Expected close from ASP SCTP server!");
		}
	}

	/* Now let the ASP be active again, it should reconnect to us: */
	f_vty_cs7_asp_cmd(g_m3ua_configs[0], "no shutdown");
	log("Connecting to ASP (expect success):");
	f_M3UA_connect_tcp(0);
	f_M3UA_asp_up_act(0, c_M3UA_TMT_override, omit, ntfy_after_up := c_M3UA_ST_I_AS_PENDING);

	f_clear_m3ua();
}

/* Test administrative state, VTY "[no] shutdown" */
testcase TC_m3ua_tcp_cli_adm_shutdown() runs on RAW_M3UA_CT {
	var PortEvent pev;
	var Misc_Helpers.ro_charstring asps := { "asp-client0-tcp" };
	var M3uaConfigs m3ua_configs := m3ua_build_configs(asps);

	f_init_m3ua(m3ua_configs := m3ua_configs);
	f_init_m3ua_srv();

	var OCT4 rctx := int2oct(m3ua_configs[0].routing_ctx, 4);
	f_M3UA_CLNT_asp_up_act(0, rctx := rctx);
	f_sleep(1.0);
	f_vty_cs7_asp_cmd(m3ua_configs[0], "shutdown");
	log("ASP should now be DOWN")
	as_M3UA_wait_tcp_conn_closed(0);

	/* Wait for a while to make sure ASP doesn't reconnect to us: */
	timer T := 10.0;
	T.start;
	alt {
	[] M3UA[0].receive(tr_ConnOpened) {
		Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail,
					"Unexpected reconnect from ASP client!");
		}
	[] T.timeout {}
	}

	/* Now let the ASP be active again, it should reconnect to us: */
	f_vty_cs7_asp_cmd(m3ua_configs[0], "no shutdown");
	log("Waiting for client ASP to reconnect to us");
	M3UA[0].receive(tr_ConnOpened) -> value pev {
			g_m3ua_conn_id[0] := pev.connOpened.connId;
	}
	f_M3UA_CLNT_asp_up_act(0, rctx := rctx);

	f_clear_m3ua();
}

/* Test the IUT sends heartbeat procedure when needed. */
testcase TC_m3ua_tcp_beat_timeout() runs on RAW_M3UA_CT {
	var Misc_Helpers.ro_charstring asps := { "asp-sender-tcp" };
	f_TC_beat_timeout(asps);
}

/* Test the IUT sends heartbeat procedure when needed. */
testcase TC_m3ua_tcp_clnt_beat_timeout() runs on RAW_M3UA_CT {
	var Misc_Helpers.ro_charstring asps := { "asp-client0-tcp" };
	f_TC_beat_timeout(asps);
}

control {
	/* M3UA TCP Tests */
	execute( TC_m3ua_tcp_cli() );
	execute( TC_m3ua_tcp_cli_srv() );
	execute( TC_m3ua_tcp_srv() );
	execute( TC_m3ua_tcp_srv_cli() );

	execute( TC_m3ua_tcp_srv_adm_shutdown() );
	execute( TC_m3ua_tcp_cli_adm_shutdown() );

	execute( TC_m3ua_tcp_beat_timeout() );
	execute( TC_m3ua_tcp_clnt_beat_timeout() );
}

}
