module SIMTRACE_Tests {

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

import from USB_PortType all;
import from USB_Types all;
import from USB_Templates all;
import from USB_Component all;
import from USB_PortTypes all;

import from SIMTRACE_Types all;
import from SIMTRACE_Templates all;
import from SIMTRACE_Emulation all;

modulepar {
	//USB_Device_Match mp_usb_dev_match := { vid_pid := { vid := '1d50'H, pid := '60e3'H} };
	USB_Device_Match mp_usb_dev_match := { vid_pid := { vid := '1d50'H, pid := '4004'H} };
	charstring mp_usb_path := "1-2.4.4";
	integer mp_usb_interface := 0;
}

//private const integer NR_IF := 2;

type component Test_CT {
	var ST_Emulation_CT vc_ST;
	port ST_USER_PT ST;
	port ST_USER_PT ST_IRQ;
};

private template (value) USB_IF_Params ts_UsbPars_path(charstring path, uint8_t if_nr) := {
	usb_dev_match := {
		path := {
			path := path
		}
	},
	usb_if_nr := if_nr
}

function f_init(USB_IF_Params pars) runs on Test_CT {
	vc_ST := ST_Emulation_CT.create("ST");
	map(vc_ST:USB, system:USB);
	connect(vc_ST:INOUT, self:ST);
	connect(vc_ST:IRQ, self:ST_IRQ);
	vc_ST.start(SIMTRACE_Emulation.main(pars));
}

function f_drain() runs on Test_CT {
	timer T := 0.1;
	T.start;
	alt {
	[] ST.receive {
		log("Drained msg from INOUT");
		repeat;
		}
	[] ST_IRQ.receive {
		log("Drained msg from IRQ");
		repeat;
		}
	[] T.timeout { }
	}
}


function f_xceive(template (value) SIMTRACE_PDU tx, template (present) SIMTRACE_PDU exp_rx)
runs on Test_CT return SIMTRACE_PDU {
	var SIMTRACE_PDU rx;
	timer T := 5.0;

	ST.send(tx);
	T.start;
	alt {
	[] ST.receive(exp_rx) -> value rx {
		T.stop;
		}
	[] T.timeout {
		setverdict(fail, "Timeout waiting for ", exp_rx);
		mtc.stop;
		}
	}
	return rx;
}

testcase TC_test() runs on Test_CT {

	var USB_IF_Params pars := valueof(ts_UsbPars_path(mp_usb_path, mp_usb_interface));
	f_init(pars);
	f_drain();

	/* Enable the use of the IRQ endpoint to report status updates */
	f_xceive(ts_SIMTRACE_CEMU_CONFIG(ts_FeatureFlags(true)),
		 tr_SIMTRACE_CEMU_CONFIG(tr_FeatureFlags(true)))

	/* Choose "remote" SIM */
	ST.send(ts_SIMTRACE_MODEM_SIM_SELECT(SIM_SELECT_REMOTE));
	/* Trigger modem reset pulse */
	ST.send(ts_SIMTRACE_MODEM_RESET);

	f_drain();

	var SIMTRACE_PDU rx;
	while (true) {
		/* receive TPDU header */
		ST.receive(tr_SIMTRACE_CEMU_RX_DATA(tr_CardEmu_DataFlags(tpdu_hdr:=true), ?)) -> value rx;
		var octetstring apdu_hdr := rx.payload.cardem_do_rxdata.data;
		/* send PB and request further Rx (command bytes) */
		ST.send(ts_SIMTRACE_CEMU_TX_DATA(ts_CardEmu_DataFlags(pb_and_rx:=true), apdu_hdr[1]));
		/* receive remaining data from reader */
		ST.receive(tr_SIMTRACE_CEMU_RX_DATA(tr_CardEmu_DataFlags(final:=true), ?));
		ST.send(ts_SIMTRACE_CEMU_TX_DATA(ts_CardEmu_DataFlags(pb_and_tx:=true), '9000'O));
	}

	f_sleep(100.0);


}

/* Test how firmware reacts on overly-long message (OS#4429, OS#4428) */
testcase TC_long_out() runs on Test_CT {
	var USB_IF_Params pars := valueof(ts_UsbPars_path(mp_usb_path, mp_usb_interface));
	f_init(pars);
	f_drain();

	ST.send(ts_SIMTRACE_CEMU_TX_DATA(ts_CardEmu_DataFlags, f_rnd_octstring(300)));
	f_sleep(5.0);
	/* FIXME: how to verify the device did not reset itself? */
}


/* flood the OUT endpoint with 1000 messages; much more than the firmware can handle */
testcase TC_flood_out() runs on Test_CT {
	var USB_IF_Params pars := valueof(ts_UsbPars_path(mp_usb_path, mp_usb_interface));
	f_init(pars);
	f_drain();

	var integer i;
	for (i := 0; i < 1000; i := i+1) {
		ST.send(ts_SIMTRACE_CEMU_TX_DATA(ts_CardEmu_DataFlags, f_rnd_octstring(10)));
	}
	f_sleep(5.0);
	/* FIXME: how to verify the device is still responsive? */
}


testcase TC_selftest() runs on Test_CT {
	const octetstring c_cemu_sim_rem := '020200000000090001'O;
	const octetstring c_cemu_rx := '010600000000130001000000050000A4000402'O;
	/* 0106000000001300
	   01000000
	   0500
	   00A4000402
	 */
	log(dec_SIMTRACE_PDU(c_cemu_rx));
}






}
