module OPCAP_CLIENT_Tests {

import from OPCAP_Adapter all;
import from OPCAP_Types all;
import from OPCAP_Templates all;

import from IPL4asp_Types all;
import from IPL4asp_PortType all;
import from Osmocom_Types all;
import from Osmocom_VTY_Functions all;
import from TELNETasp_PortType all;
import from Socket_API_Definitions all;

type record IpPort {
	charstring ip,
	integer udp_port
};

modulepar {
	/* local IP address listening for OPCAP connections */
	charstring mp_local_opcap_ip := "127.0.0.1";
	/* local TCP base port for inbound OPCAP connections */
	integer mp_local_opcap_port := 5000;

	/* IP + port for simulating user traffic */
	IpPort mp_traffic_a := { "127.0.0.23", 44423 };
	IpPort mp_traffic_b := { "127.0.0.42", 44442 };
};

type component test_CT extends OPCAP_Adapter_CT {
	timer g_Tguard := 30.0;

	port TELNETasp_PT VTY;

	/* port to generate IP traffic that may or may not be captured */
	port IPL4asp_PT IP;
	var integer g_traffic_conn_id;
};

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

/* initialize one of the OPCAP servers, wait for client to connect */
private function f_init_one_srv(integer idx, template (present) uint32_t linktype) runs on test_CT {
	/* start guard timer */
	activate(as_Tguard());
	g_Tguard.start;
	log("Waiting for client-", idx, " connection...");
	/* wait for connection */
	f_bind(mp_local_opcap_ip, mp_local_opcap_port+idx, idx);
	f_wait_client_connect(idx);
	/* wait for file header */
	f_opcap_exp(tr_OPCAP_FILE_HDR(linktype), idx);
};


/* global initialization */
private function f_init() runs on test_CT {
	map(self:VTY, system:VTY);
	f_vty_set_prompts(VTY);
	f_vty_transceive(VTY, "enable");

	map(self:IP, system:IP);
	var IPL4asp_Types.Result res

	/* 0 -> 1 */
	res := f_IPL4_connect(IP, mp_traffic_b.ip, mp_traffic_b.udp_port,
			      mp_traffic_a.ip, mp_traffic_a.udp_port, -1, { udp:={} });
	g_traffic_conn_id := res.connId;
}

/* generate user traffic from A -> B */
function f_trafic_pkt_ab(octetstring payload) runs on test_CT {
	IP.send(ASP_Send:{g_traffic_conn_id, omit, payload})
}

/* expect a specified UDP payload on the OPCAP connection 'idx' */
function f_opcap_exp_udp(octetstring udp_payload, integer idx) runs on test_CT {
	var octetstring rx_tail;
	var integer udp_payload_len, rx_pdu_len;
	var OPCAP_PDU rx_pdu;

	udp_payload_len := lengthof(udp_payload);

	/* sadly I couldn't figure out how to create an octetstring template
	 * for 'match an octetstring ending in 'udp_payload' */
	rx_pdu := f_opcap_exp(tr_OPCAP_PKT(?), idx);
	rx_pdu_len := lengthof(rx_pdu.u.packet.payload);
	rx_tail := substr(rx_pdu.u.packet.payload, rx_pdu_len - udp_payload_len, udp_payload_len);
	if (rx_tail != udp_payload) {
		log("captured UDP payload: ", rx_tail, " but expected: ", udp_payload);
		setverdict(fail);
	} else {
		setverdict(pass);
	}
}

/* create an additional pcap-store-connection via the VTY */
function f_vty_create_addl_connection(integer idx) runs on test_CT
{
	f_vty_config3(VTY, { "client", "pcap-store-connectio second-" & int2str(idx) },
			{ "server ip " & mp_local_opcap_ip,
			  "server port " & int2str(mp_local_opcap_port + idx),
			  "connect" }
			);
}



/* wait for inbound client connection and reception of link header */
testcase TC_connect_rx_hdr() runs on test_CT
{
	f_init();
	f_init_one_srv(0, ?);
	setverdict(pass);
}

/* check if client connection is re-started after a close */
testcase TC_reconnect(integer idx := 0) runs on test_CT
{
	f_init();
	f_init_one_srv(idx, ?);
	f_sleep(2.0);

	log("Disconnecting client-", idx);
	f_disconnect(idx);

	f_wait_client_connect(idx);
	f_opcap_exp(tr_OPCAP_FILE_HDR(?), idx);
	setverdict(pass);
}

/* capture a packet that's within the filter */
testcase TC_capture() runs on test_CT
{
	f_init();
	f_init_one_srv(0, ?);

	for (var integer i := 0; i < 10; i := i + 1) {
		var octetstring udp_payload;

		/* we assume 1400 is low enough to avoid IP fragmentation */
		udp_payload := f_rnd_octstring_rnd_len(1400);
		f_trafic_pkt_ab(udp_payload);

		f_opcap_exp_udp(udp_payload, 0);
	}
}

/* wait for inbound client connections and reception of link header */
testcase TC_multi_connect_rx_hdr() runs on test_CT
{
	f_init();
	f_init_one_srv(0, ?);
	f_vty_create_addl_connection(1);
	f_init_one_srv(1, ?);
	setverdict(pass);
}

/* ensure a packet that's within the filter is sent to secondary clients */
testcase TC_multi_capture() runs on test_CT
{
	f_init();
	f_init_one_srv(0, ?);
	f_vty_create_addl_connection(1);
	f_init_one_srv(1, ?);

	for (var integer i := 0; i < 10; i := i + 1) {
		var octetstring udp_payload;

		/* we assume 1400 is low enough to avoid IP fragmentation */
		udp_payload := f_rnd_octstring_rnd_len(1400);
		f_trafic_pkt_ab(udp_payload);

		/* expect packet to arrive on both simulated servers */
		f_opcap_exp_udp(udp_payload, 0);
		f_opcap_exp_udp(udp_payload, 1);
	}
}

/* TODO: ensure a packet outside the filter is dropped */
/* TODO: capture of truncated packet */
/* TODO: stall the receive window */
/* TODO: different link type (ethernet, not SLL) */


control {
	execute( TC_connect_rx_hdr() );
	execute( TC_reconnect() );
	execute( TC_capture() );
	execute( TC_multi_connect_rx_hdr() );
	execute( TC_multi_capture() );
};


};
