module SABP_Selftest {

/* This is testing the SABP code, specifically re-creating the SABP messages from within
 * the TCP stream using SABP_Adapter.f_APER_getMsgLen() for the various possible APER
 * length determinant cases */

import from Osmocom_Types all;

import from SABP_Adapter all;
import from SABP_Templates all;
import from IPL4asp_Types all;
import from IPL4asp_PortType all;

modulepar {
	charstring mp_bind_ip := "127.0.0.1";
	integer mp_bind_port := 12345;
}

type component test_CT extends SABP_Adapter_CT {
	port IPL4asp_PT TCP;
	var integer g_tcp_conn_id;
	timer Tguard := 10.0;
}

template (value) ASP_Send ts_AS(integer conn_id, octetstring msg) := {
	connId := conn_id,
	proto := omit,
	msg := msg
}

private altstep as_Tguard() runs on test_CT {
	[] Tguard.timeout {
		setverdict(fail, "Tguard timeout");
		mtc.stop;
	}
}

private function f_init() runs on test_CT {
	var IPL4asp_Types.Result res;
	var ASP_Event asp_evt;

	activate(as_Tguard());
	Tguard.start;

	f_bind(mp_bind_ip, mp_bind_port, 0);

	map(self:TCP, system:TCP);
	res := f_IPL4_connect(TCP, mp_bind_ip, mp_bind_port, "", -1, 0, { tcp:={} });
	if (not ispresent(res.connId)) {
		setverdict(fail, "Could not connect to SABP TCP port");
		mtc.stop;
	}
	g_tcp_conn_id := res.connId;

	SABP[0].receive(ASP_Event:{connOpened:=?}) -> value asp_evt {
		g_sabp_conn_id[0] := asp_evt.connOpened.connId;
	}
}

/* send a given input octetstring 'stream' in chunks of 'chunk_size', spaced at 'delay' intervals */
private function tcp_send_segmented(octetstring stream, integer chunk_size := 1, float delay := 0.05)
runs on test_CT {
	var integer i;

	for (i := 0; i < lengthof(stream); i := i+chunk_size) {
		var integer size := chunk_size;
		if (lengthof(stream) - i < chunk_size) {
			size := lengthof(stream) - i;
		}
		TCP.send(ts_AS(g_tcp_conn_id, substr(stream, i, size)));
		f_sleep(delay);
	}
}

/* a simple SABP-RESTART message with single-byte length dteerminant (shorter than 128 byte) */
const octetstring c_restart := '00044010000001000F0009000062F22400230042'O;

/* test whether a message with 1-byte length determinant is parsed */
testcase TC_1byte_len() runs on test_CT {
	f_init();
	TCP.send(ts_AS(g_tcp_conn_id, c_restart));
	f_sabp_exp(tr_SABP_Restart(?));
	setverdict(pass);
}

/* test whether a message with 1-byte length determinant is parsed if each byte is received separately */
testcase TC_1byte_len_bytewise() runs on test_CT {
	f_init();
	tcp_send_segmented(c_restart, 1, 0.05);
	f_sabp_exp(tr_SABP_Restart(?));
	setverdict(pass);
}

/* send three concatenated RESTART in one segment */
testcase TC_1byte_len_chained() runs on test_CT {
	var octetstring chain := c_restart & c_restart & c_restart;
	f_init();
	TCP.send(ts_AS(g_tcp_conn_id, chain));
	f_sabp_exp(tr_SABP_Restart(?));
	f_sabp_exp(tr_SABP_Restart(?));
	f_sabp_exp(tr_SABP_Restart(?));
	setverdict(pass);
}


/* SABP write-replace with length determinant encoded over two bytes ('8093'O) */
const octetstring c_wrepl := '00000080930000080006000211120007000240C0000F0010000113F0030282EC0613F0030282EC070001400100000D0002012A000900020000000400010100000056029F01B4D90D064297D9EC37E8FE96B3C9A0303BDD68341A8D46A3D168341A8D46A3D168341A8D46A3D168341A8D46A3D168341A8D46A3D168341A8D46A3D168341A8D46A3D168341A8D46A3D168341A8D46A3D10012'O;

/* test whether a message with 2-byte length determinant is parsed */
testcase TC_2byte_len() runs on test_CT {
	f_init();
	TCP.send(ts_AS(g_tcp_conn_id, c_wrepl));
	f_sabp_exp(tr_SABP_Write(?, ?));
	f_sabp_send(ts_SABP_Reset({ { pLMNidentity := '09F107'O, lac := '0000'O, sac := '0000'O } }));
	setverdict(pass);
}

/* test whether a message with 2-byte length determinant is parsed if each byte is received separately */
testcase TC_2byte_len_bytewise() runs on test_CT {
	f_init();
	tcp_send_segmented(c_wrepl, 1, 0.05);
	f_sabp_exp(tr_SABP_Write(?, ?));
	setverdict(pass);
}

/* send three concatenated WRITE-REPLACE in one segment */
testcase TC_2byte_len_chained() runs on test_CT {
	var octetstring chain := c_wrepl & c_wrepl & c_wrepl;
	f_init();
	TCP.send(ts_AS(g_tcp_conn_id, chain));
	f_sabp_exp(tr_SABP_Write(?, ?));
	f_sabp_exp(tr_SABP_Write(?, ?));
	f_sabp_exp(tr_SABP_Write(?, ?));
	setverdict(pass);
}

/* send three concatenated WRITE-REPLACE in segments of 10 bytes */
testcase TC_2byte_len_chained_bytewise() runs on test_CT {
	var octetstring chain := c_wrepl & c_wrepl & c_wrepl;
	f_init();
	tcp_send_segmented(chain, 10, 0.05);
	f_sabp_exp(tr_SABP_Write(?, ?));
	f_sabp_exp(tr_SABP_Write(?, ?));
	f_sabp_exp(tr_SABP_Write(?, ?));
	setverdict(pass);
}

/* SABP write-replace with length determinant encoded over multiple chunks ('Cx'O case) */
const octetstring c_chunked := '[encoded data, 35120 characters]'O;

/* test whether a message with chnked length determinant is parsed */
testcase TC_chunked_len() runs on test_CT {
	f_init();
	TCP.send(ts_AS(g_tcp_conn_id, c_chunked));
	f_sabp_exp(tr_SABP_Write(?, ?));
	setverdict(pass);
}

/* test whether a message with chunked length determinant is parsed if each byte is received separately */
testcase TC_chunked_len_bytewise() runs on test_CT {
	f_init();
	tcp_send_segmented(c_chunked, 100, 0.05);
	f_sabp_exp(tr_SABP_Write(?, ?));
	setverdict(pass);
}

control {
	execute( TC_1byte_len() );
	execute( TC_1byte_len_bytewise() );
	execute( TC_1byte_len_chained() );
	execute( TC_2byte_len() );
	execute( TC_2byte_len_bytewise() );
	execute( TC_2byte_len_chained() );
	execute( TC_2byte_len_chained_bytewise() );
	execute( TC_chunked_len() );
	execute( TC_chunked_len_bytewise() );
}


}
