module CCID_Tests {

/* TTCN-3 tests for USB CCID (Chip Card Interface Device)
 *
 * (C) 2018-2019 by Harald Welte <laforge@gnumonks.org>
 * (C) 2026 by Andreas Eversberg <aeversberg@sysmocom.de>
 */

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

import from USB_PortType all;
import from USB_Component all;

import from CCID_Types all;
import from CCID_Templates all;
import from CCID_Emulation 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 := '6141'H } };
	USB_Device_Match mp_cardem_usb_dev_match := { vid_pid := { vid := '1d50'H, pid := '60e3'H } };
	integer mp_use_slot_count := 7;
	integer mp_simtrace_slot := 7;
	boolean mp_test_power_off := true;
	boolean mp_test_set_params := false;
	boolean mp_quirk_resetpar_returns_slotsts := false;
}

/* global test component; manages CCID device */
type component Test_CT {
	var CCID_Emulation_CT vc_CCID;
	port USB_PT USB;
	var CardemSlot_CT vc_SLOT[NR_SLOTS];
	var ST_Emulation_CT vc_cardem_ref[NR_SLOTS];
	private var boolean Cardem_In_Use := false;
};

type component CardemSlot_CT extends Slot_CT {
	var ST_Emulation_CT vc_Cardem := null;
	port ST_USER_PT ST;
	port ST_USER_PT ST_IRQ;
};

/* maximum number of slots we are supporting in the test suite */
private const integer NR_SLOTS := 16;

/***********************************************************************
 * helper infrastructure
 ***********************************************************************/

const octetstring c_UICC_SELECT_MF := '00a40004023f00'O;
const octetstring c_SIM_SELECT_MF := 'a0a40004023f00'O;
const octetstring c_UICC_MANAGE_CHANNEL := '0070800100'O;
const octetstring c_SIM_SELECT_EF_ICCID := '00A4000C022FE2'O;
const octetstring c_SIM_READ_BINARY := '00B000000A'O;
const octetstring c_UICC_GET_RESPONSE_BASE := 'a0c00000'O;
const octetstring c_UICC_TRUNCATED := '00a40004'O;

/* Table 7 of ISO7816-3 */
type enumerated ISO7816_Fi {
	ISO7816_FI_372_4MHz	('0000'B),
	ISO7816_FI_372_5MHz	('0001'B),
	ISO7816_FI_558_6MHz	('0010'B),
	ISO7816_FI_744_8MHz	('0011'B),
	ISO7816_FI_1116_12MHz	('0100'B),
	ISO7816_FI_1488_16MHz	('0101'B),
	ISO7816_FI_1860_20MHz	('0110'B),

	ISO7816_FI_512_5MHz	('1001'B),
	ISO7816_FI_768_7MHz	('1010'B),
	ISO7816_FI_1024_10MHz	('1011'B),
	ISO7816_FI_1536_15MHz	('1100'B),
	ISO7816_FI_2048_20MHz	('1101'B)
};

/* Table 8 of ISO7816-3 */
type enumerated ISO7816_Di {
	ISO7816_DI_1		('0001'B),
	ISO7816_DI_2		('0010'B),
	ISO7816_DI_4		('0011'B),
	ISO7816_DI_8		('0100'B),
	ISO7816_DI_16		('0101'B),
	ISO7816_DI_32		('0110'B),
	ISO7816_DI_64		('0111'B),

	ISO7816_DI_12		('1000'B),
	ISO7816_DI_20		('1001'B)
}

private template (value) CCID_ProtocolData ts_ProtoDataT0(ISO7816_Fi fi, ISO7816_Di di,
							  uint8_t guard_time := 0,
							  uint8_t wait_int := 0) := {
	T0 := {
		Findex := enum2int(fi),
		Dindex := enum2int(di),

		bRFU := '000000'B,
		inv_convention := false,
		bRFU2 := '0'B,

		bGuardTimeT0 := guard_time,
		bWaitingIntegerT0 := wait_int,
		bClockStop := STOPPING_NOT_ALLOWED
	}
};

type function void_fn() runs on CardemSlot_CT;

/* first function inside CardemSlot_CT; wait for CCID_EVENT_UP + call testcase-specific function */
private function f_handler_init(void_fn fn, integer slot_nr, ST_Emulation_CT v_cardem_ref) runs on CardemSlot_CT {
	g_slot_nr := slot_nr;
	vc_Cardem := v_cardem_ref;
	g_Tguard.start;
	activate(as_Tguard());
	CCID.receive(CCID_Emulation_Event:{up_down:=CCID_EVENT_UP});

	fn.apply();
}

/* start a single slot handler */
private function f_start_handler(void_fn fn, integer slot_nr, boolean use_cardem := false) runs on Test_CT
{
	var ST_Emulation_CT vc_Cardem := null;

	vc_SLOT[slot_nr] := CardemSlot_CT.create("Slot" & int2str(slot_nr));
	connect(vc_SLOT[slot_nr]:CCID, vc_CCID:SLOT[slot_nr]);

	if (use_cardem) {
		if (Cardem_In_Use) {
			setverdict(fail, "Only one CCID slot handler can use Cardem.");
			mtc.stop;
		}
		vc_Cardem := ST_Emulation_CT.create("Cardem");
		map(vc_Cardem:USB, system:USB);
		connect(vc_Cardem:INOUT, vc_SLOT[slot_nr]:ST);
		connect(vc_Cardem:IRQ, vc_SLOT[slot_nr]:ST_IRQ);
		Cardem_In_Use := true;
	}

	vc_cardem_ref[slot_nr] := vc_Cardem;

	vc_SLOT[slot_nr].start(f_handler_init(fn, slot_nr, vc_Cardem));

}

/* start a single Cardem slot handler */
private function f_wait_handlers_complete() runs on Test_CT {
	var integer i;

	for (i := 0; i < NR_SLOTS; i := i+1) {
		if (vc_SLOT[i] != null) {
			vc_SLOT[i].done;
		}
	}
	setverdict(pass);
}

private function f_start_and_wait() runs on Test_CT {
	var integer i;

	/* start CCID_Emulation and Cardem_Emulation last, it will trigger all the per-slot components */
	var CCID_Emulation_Params ccidp := { usb_dev_match := mp_usb_dev_match };
	var USB_IF_Params cardemp := { usb_dev_match := mp_cardem_usb_dev_match, usb_if_nr := 0 };
	vc_CCID.start(CCID_Emulation.main(ccidp));
	for (i := 0; i < NR_SLOTS; i := i+1) {
		if (vc_SLOT[i] != null) {
			if (vc_cardem_ref[i] != null) {
				vc_cardem_ref[i].start(SIMTRACE_Emulation.main(cardemp));
			}
		}
	}
	f_wait_handlers_complete();
}

private function f_init() runs on Test_CT {
	var integer i;
	vc_CCID := CCID_Emulation_CT.create("CCID");
	map(vc_CCID:USB, system:USB);
	for (i := 0; i < NR_SLOTS; i := i+1) {
		vc_SLOT[i] := null;
	}
}

altstep as_cardem_any() runs on CardemSlot_CT {
	var SIMTRACE_PDU pdu;
	[] ST.receive(SIMTRACE_PDU:?) -> value pdu {
		setverdict(fail, "Received unexpected CCID ", pdu);
		self.stop;
		}
	[] ST.receive {
		setverdict(fail, "Received unexpected non-CCID");
		self.stop;
		}
}

/* transceive a Cardem command (expect 'rx' on IN) */
function f_cardem_receive(template (present) SIMTRACE_PDU exp_rx)
runs on CardemSlot_CT return SIMTRACE_PDU {
	var SIMTRACE_PDU pdu;

	alt {
	[] ST.receive(exp_rx) -> value pdu {
		return pdu;
		}
	[] as_cardem_any();
	}
	return pdu;
}

function f_cardem_transmit(template (value) SIMTRACE_PDU tx)
runs on CardemSlot_CT {
	ST.send(tx);
}

altstep as_cardem_manager() runs on CardemSlot_CT {
	var SIMTRACE_PDU pdu;

	[] ST.receive(tr_SIMTRACE_CEMU_CONFIG(tr_FeatureFlags(true))) {
		log("Got Config.");
		repeat;
		}
	[] ST_IRQ.receive(tr_SIMTRACE_MODEM_STATUS()) {
		log("Got Status.");
		repeat;
		}
	[] ST.receive(tr_SIMTRACE_CEMU_PTS(?, ?)) {
		log("Got PTS info.");
		repeat;
		}
}

function f_cardem_manager() runs on CardemSlot_CT {
	if (vc_Cardem == null) {
		setverdict(fail, "Please enable Cardem support first.");
		mtc.stop;
	}

	activate(as_cardem_manager());

	f_cardem_transmit(ts_SIMTRACE_CEMU_CONFIG(ts_FeatureFlags(true)));
	f_cardem_transmit(ts_SIMTRACE_MODEM_SIM_SELECT(SIM_SELECT_REMOTE));
	f_cardem_transmit(ts_SIMTRACE_CEMU_SET_ATR('3b00'O));
	f_cardem_transmit(ts_SIMTRACE_CEMU_CARDINSERT(1));
	f_cardem_transmit(ts_SIMTRACE_MODEM_RESET(MODEM_RESET_PULSE, 300));
	f_cardem_transmit(ts_SIMTRACE_CEMU_SET_ATR('3b9f96801f878031e073fe211b674a4c753034054ba9'O));
	f_sleep(0.2);
}

private function f_ccid_receive(template (present) CCID_PDU pdu_exp) runs on Slot_CT return CCID_PDU {
	var CCID_PDU pdu_rx;
	alt {
	[] CCID.receive(pdu_exp) -> value pdu_rx {
		}
	[] as_ccid_any();
	}

	return pdu_rx;
}

/***********************************************************************
 * Test behavior regarding valid situations
 ***********************************************************************/

/* request 100 times the slot status */
private function f_TC_getstatus() runs on Slot_CT
{
	var integer i;
	for (i := 0; i < 100; i := i+1) {
		CCID.send(ts_CCID_GetSlotStatus(g_slot_nr));
		/* it would be fun to simply send more requests here, but the CCID
		 * spec doesn't permit more than one unresponded command [per slot] */
		alt {
		[] CCID.receive(tr_CCID_SlotStatus(g_slot_nr));
		[] as_ccid_any();
		}
	}
	setverdict(pass);
}
testcase TC_get_status() runs on Test_CT
{
	var integer i;

	f_init();

	for (i := 0; i < mp_use_slot_count; i := i+1) {
		f_start_handler(refers(f_TC_getstatus), i);
	}

	f_start_and_wait();
}


private function f_TC_power_on() runs on Slot_CT
{
	f_ccid_power_on();
}
testcase TC_power_on() runs on Test_CT
{
	var integer i;

	f_init();

	for (i := 0; i < mp_use_slot_count; i := i+1) {
		f_start_handler(refers(f_TC_power_on), i);
	}

	f_start_and_wait();
}

private function f_TC_power_off() runs on Slot_CT
{
	f_ccid_power_on();
	f_ccid_power_off();
}
testcase TC_power_off() runs on Test_CT
{
	var integer i;

	f_init();

	for (i := 0; i < mp_use_slot_count; i := i+1) {
		f_start_handler(refers(f_TC_power_off), i);
	}

	f_start_and_wait();
}


/* repeat IccPowerOn on slot that's already active (next warm reset ATR) */
private function f_TC_power_on_warm() runs on Slot_CT
{
	var integer i;

	/* initial power on */
	f_ccid_power_on();

	/* additional power on */
	for (i := 0; i < 20; i := i+1) {
		f_ccid_power_on();
	}
}
testcase TC_power_on_warm() runs on Test_CT
{
	var integer i;

	f_init();

	for (i := 0; i < mp_use_slot_count; i := i+1) {
		f_start_handler(refers(f_TC_power_on_warm), i);
	}

	f_start_and_wait();
}

/* transfer 1000 APDUs by issuing SELECT MF */
private function f_TC_select_mf() runs on Slot_CT
{
	var integer i;
	f_ccid_power_on();
	f_ccid_set_par(ts_ProtoDataT0(ISO7816_FI_512_5MHz, ISO7816_DI_32));
	for (i := 0; i < 1000; i := i+1) {
		f_ccid_xfr(c_UICC_SELECT_MF, '??'O);
	}
}
testcase TC_select_mf() runs on Test_CT
{
	var integer i;

	f_init();

	for (i := 0; i < mp_use_slot_count; i := i+1) {
		f_start_handler(refers(f_TC_select_mf), i);
	}

	f_start_and_wait();
}

/* GetParametrs: verify contents */
private function f_TC_get_params() runs on Slot_CT
{
	var CCID_PDU par;
	f_ccid_power_on();
	par := f_ccid_get_par();
	log(par);
}
testcase TC_get_params() runs on Test_CT
{
	var integer i;

	f_init();

	for (i := 0; i < mp_use_slot_count; i := i+1) {
		f_start_handler(refers(f_TC_get_params), i);
	}
	f_start_and_wait();
}

/* SetParameters: verify change */
private function f_TC_set_params() runs on Slot_CT
{
	var CCID_PDU par;
	f_ccid_power_on();

	/* get current parameters */
	par := f_ccid_get_par();

	/* modify some of them */
	var CCID_ProtocolData pd := par.u.Parameters.abProtocolData;
	pd.T0.bGuardTimeT0 := 23;
	pd.T0.bWaitingIntegerT0 := 42;
	par := f_ccid_set_par(pd);

	/* check if modifications were applied */
	var template (present) CCID_ProtocolData tr_PD := {
		T0 := {
			Findex := ?,
			Dindex := ?,
			bRFU := ?,
			inv_convention := ?,
			bRFU2 := ?,
			bGuardTimeT0 := 23,
			bWaitingIntegerT0 := 42,
			bClockStop := ?
		}
	};
	if (match(par.u.Parameters.abProtocolData, tr_PD)) {
		setverdict(pass);
	} else {
		setverdict(fail, "SetParameters didn't change GuardTime/WaitingInteger");
	}
}
testcase TC_set_params() runs on Test_CT
{
	var integer i;

	f_init();

	for (i := 0; i < mp_use_slot_count; i := i+1) {
		f_start_handler(refers(f_TC_set_params), i);
	}
	f_start_and_wait();
}

/* ResetParameters: verify change */
private function f_TC_reset_params() runs on Slot_CT
{
	var CCID_PDU par;

	f_TC_set_params();
	par := f_ccid_reset_par();
	if (mp_quirk_resetpar_returns_slotsts) {
		par := f_ccid_get_par();
	}
	if (par.u.Parameters.abProtocolData.T0.bGuardTimeT0 == 23 or
	    par.u.Parameters.abProtocolData.T0.bWaitingIntegerT0 == 42) {
		setverdict(fail, "ResetParameters didn't reset properly");
	}
}
testcase TC_reset_params() runs on Test_CT
{
	var integer i;

	f_init();

	for (i := 0; i < mp_use_slot_count; i := i+1) {
		f_start_handler(refers(f_TC_reset_params), i);
	}
	f_start_and_wait();
}

/* SetParameters: verify acceptance of unchanged setting */
private function f_TC_set_params_nochange() runs on Slot_CT
{
	var CCID_PDU set_par, get_par;
	f_ccid_power_on();

	/* get current parameters */
	set_par := f_ccid_get_par();

	/* send unmodified */
	get_par := f_ccid_set_par(set_par.u.Parameters.abProtocolData);

	/* check if they still match */
	if (match(set_par.u.Parameters.abProtocolData, get_par.u.Parameters.abProtocolData)) {
		setverdict(pass);
	} else {
		setverdict(fail, "SetParameters changed parameters unexpected");
	}
}
testcase TC_set_params_nochange() runs on Test_CT
{
	var integer i;

	f_init();

	for (i := 0; i < mp_use_slot_count; i := i+1) {
		f_start_handler(refers(f_TC_set_params_nochange), i);
	}
	f_start_and_wait();
}

/* manage channel (Case 1) */
private function f_TC_successful_case_1() runs on Slot_CT
{
	var octetstring res;

	f_ccid_power_on();
	res := f_ccid_xfr(c_UICC_MANAGE_CHANNEL, '??'O);
	if (res != '6200'O) {
		setverdict(fail, "Unexpected SW1/SW2");
		mtc.stop;
	}
}
testcase TC_successful_case_1() runs on Test_CT
{
	var integer i;

	f_init();

	for (i := 0; i < mp_use_slot_count; i := i+1) {
		f_start_handler(refers(f_TC_successful_case_1), i);
	}

	f_start_and_wait();
}

/* read binary (Case 3 + 2) */
private function f_TC_successful_case_2_3() runs on Slot_CT
{
	var octetstring res;

	f_ccid_power_on();
	res := f_ccid_xfr(c_SIM_SELECT_EF_ICCID, '??'O);
	if (res != '9000'O) {
		setverdict(fail, "Unexpected SW1/SW2");
		mtc.stop;
	}
	res := f_ccid_xfr(c_SIM_READ_BINARY, ?);
	if (substr(res, 10,2) != '9000'O) {
		setverdict(fail, "Unexpected SW1/SW2");
		mtc.stop;
	}
}
testcase TC_successful_case_2_3() runs on Test_CT
{
	var integer i;

	f_init();

	for (i := 0; i < mp_use_slot_count; i := i+1) {
		f_start_handler(refers(f_TC_successful_case_2_3), i);
	}

	f_start_and_wait();
}

/* GET RESPONSE (Case 4) */
private function f_TC_successful_case_4() runs on Slot_CT
{
	var octetstring res;
	var integer len;

	f_ccid_power_on();
	res := f_ccid_xfr(c_UICC_SELECT_MF, '??'O);
	if (res[0] != '61'O) {
		setverdict(fail, "Unexpected SW1");
		mtc.stop;
	}
	len := oct2int(res[1]);
	res := f_ccid_xfr(c_UICC_GET_RESPONSE_BASE & int2oct(len, 1), ?)
	if (lengthof(res) != len + 2) {
		setverdict(fail, "Expected ", len + 2, " octets, but got ", res);
		mtc.stop;
	}
}
testcase TC_successful_case_4() runs on Test_CT
{
	var integer i;

	f_init();

	for (i := 0; i < mp_use_slot_count; i := i+1) {
		f_start_handler(refers(f_TC_successful_case_4), i);
	}

	f_start_and_wait();
}

/* A general test to verify a working simtrace. */
private function f_TC_simtrace_test() runs on CardemSlot_CT
{
	var octetstring req := c_UICC_MANAGE_CHANNEL;
	var octetstring res := '6200'O;
	var CCID_PDU ccid_pdu;

	f_cardem_manager();

	f_ccid_power_on(CCID_PWRSEL_3V0);

	/* Send a request towards reader. */
	CCID.send(ts_CCID_XfrBlock(g_slot_nr, req, 0));
	/* Receive the request by SIM. */
	f_cardem_receive(tr_SIMTRACE_CEMU_RX_DATA(?, req));
	/* Transmit the response by SIM. */
	f_cardem_transmit(ts_SIMTRACE_CEMU_TX_DATA(ts_CardEmu_DataFlags(pb_and_tx := true, final := true),
						   res));
	/* Receive the response from reader. */
	ccid_pdu := f_ccid_receive(tr_CCID_DataBlock(g_slot_nr, ?, ?, ?));
	if (ccid_pdu.u.DataBlock.abData != res) {
		setverdict(fail, "Unexpected SW1/SW2");
		mtc.stop;
	}

	/* Stop simtrace emulation, to prevent race condition. */
	vc_Cardem.stop;
	vc_Cardem.done;
}
testcase TC_simtrace_test() runs on Test_CT
{
	f_init();

	f_start_handler(refers(f_TC_simtrace_test), mp_simtrace_slot, true);

	f_start_and_wait();
}

/* The SIM responds with 255 bytes. */
private function f_TC_255_bytes_response() runs on CardemSlot_CT
{
	var octetstring req := c_UICC_GET_RESPONSE_BASE & 'FF'O;
	var octetstring res := '000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e9000'O;
	var CCID_PDU ccid_pdu;

	f_cardem_manager();

	f_ccid_power_on(CCID_PWRSEL_3V0);

	/* Send a request towards reader. */
	CCID.send(ts_CCID_XfrBlock(g_slot_nr, req, 0));
	/* Receive the request by SIM. */
	f_cardem_receive(tr_SIMTRACE_CEMU_RX_DATA(?, req));
	/* Transmit the response by SIM. */
	f_cardem_transmit(ts_SIMTRACE_CEMU_TX_DATA(ts_CardEmu_DataFlags(pb_and_tx := true, final := true),
						   req[1] & res));
	/* Receive the response from reader. */
	ccid_pdu := f_ccid_receive(tr_CCID_DataBlock(g_slot_nr, ?, ?, ?));
	if (ccid_pdu.u.DataBlock.abData != res) {
		setverdict(fail, "Unexpected SW1/SW2");
		mtc.stop;
	}

	/* Stop simtrace emulation, to prevent race condition. */
	vc_Cardem.stop;
	vc_Cardem.done;
}
testcase TC_255_bytes_response() runs on Test_CT
{
	f_init();

	f_start_handler(refers(f_TC_255_bytes_response), mp_simtrace_slot, true);

	f_start_and_wait();
}

/* The SIM responds with 256 bytes. */
private function f_TC_256_bytes_response() runs on CardemSlot_CT
{
	var octetstring req := c_UICC_GET_RESPONSE_BASE & '00'O;
	var octetstring res := '232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323232323429000'O;
	var CCID_PDU ccid_pdu;

	f_cardem_manager();

	f_ccid_power_on(CCID_PWRSEL_3V0);

	/* Send a request towards reader. */
	CCID.send(ts_CCID_XfrBlock(g_slot_nr, req, 0));
	/* Receive the request by SIM. */
	f_cardem_receive(tr_SIMTRACE_CEMU_RX_DATA(?, req));
	/* Transmit the response by SIM. */
	f_cardem_transmit(ts_SIMTRACE_CEMU_TX_DATA(ts_CardEmu_DataFlags(pb_and_tx := true, final := true),
						   req[1] & res));
	/* Receive the response from reader. */
	ccid_pdu := f_ccid_receive(tr_CCID_DataBlock(g_slot_nr, ?, ?, ?));
	if (ccid_pdu.u.DataBlock.abData != res) {
		setverdict(fail, "Unexpected SW1/SW2");
		mtc.stop;
	}

	/* Stop simtrace emulation, to prevent race condition. */
	vc_Cardem.stop;
	vc_Cardem.done;
}
testcase TC_256_bytes_response() runs on Test_CT
{
	f_init();

	f_start_handler(refers(f_TC_256_bytes_response), mp_simtrace_slot, true);

	f_start_and_wait();
}

/* The SIM responds with many procedure bytes to prevent timeout. */
private function f_TC_null_procedure_byte() runs on CardemSlot_CT
{
	var octetstring req := c_SIM_READ_BINARY;
	var octetstring res := '0102030405060708090a9000'O;
	var CCID_PDU ccid_pdu;
	var integer i;

	f_cardem_manager();

	f_ccid_power_on(CCID_PWRSEL_3V0);

	/* Send a request towards reader. */
	CCID.send(ts_CCID_XfrBlock(g_slot_nr, req, 0));
	/* Receive the request by SIM. */
	f_cardem_receive(tr_SIMTRACE_CEMU_RX_DATA(?, req));
	for (i := 0; i < 40; i := i+1) {
		/* Transmit NULL procedure byte by SIM. */
		f_cardem_transmit(ts_SIMTRACE_CEMU_TX_DATA(ts_CardEmu_DataFlags(pb_and_tx := true, final := false),
							   '60'O));
		/* Eat the possible 'Time Extension status' */
		timer T := 0.2;
		T.start;
		alt {
		[] CCID.receive(tr_CCID_DataBlock(g_slot_nr, ?, tr_CCID_HeaderIN(?, CCID_CMD_STATUS_TIME_EXT, ?), ?)) {
			log("Eating 'Time Extension status'.");
			repeat;
			}
		[] as_ccid_any();
		[] T.timeout { }
		}
	}
	/* Transmit the response by SIM. */
	f_cardem_transmit(ts_SIMTRACE_CEMU_TX_DATA(ts_CardEmu_DataFlags(pb_and_tx := true, final := true),
						   req[1] & res));
	/* Receive the response from reader. */
	ccid_pdu := f_ccid_receive(tr_CCID_DataBlock(g_slot_nr, ?, ?, ?));
	if (ccid_pdu.u.DataBlock.abData != res) {
		setverdict(fail, "Unexpected SW1/SW2");
		mtc.stop;
	}

	/* Stop simtrace emulation, to prevent race condition. */
	vc_Cardem.stop;
	vc_Cardem.done;
}
testcase TC_null_procedure_byte() runs on Test_CT
{
	f_init();

	f_start_handler(refers(f_TC_null_procedure_byte), mp_simtrace_slot, true);

	f_start_and_wait();
}

/* The SIM request with 255 bytes. */
private function f_TC_255_bytes_request() runs on CardemSlot_CT
{
	var octetstring req := '00D60000FF000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e'O
	var octetstring res := '9000'O;
	var CCID_PDU ccid_pdu;

	f_cardem_manager();

	f_ccid_power_on(CCID_PWRSEL_3V0);

	/* Send a request towards reader. */
	CCID.send(ts_CCID_XfrBlock(g_slot_nr, req, 0));
	/* Receive the request by SIM. */
	f_cardem_receive(tr_SIMTRACE_CEMU_RX_DATA(?, substr(req, 0, 5)));
	/* Transmit the procedure byte from SIM. */
	f_cardem_transmit(ts_SIMTRACE_CEMU_TX_DATA(ts_CardEmu_DataFlags(pb_and_rx := true, final := false),
						   'D6'O));
	/* Receive the data by SIM. */
	f_cardem_receive(tr_SIMTRACE_CEMU_RX_DATA(?, substr(req, 5, lengthof(req) - 5)));
	/* Transmit the status bytes from SIM. */
	f_cardem_transmit(ts_SIMTRACE_CEMU_TX_DATA(ts_CardEmu_DataFlags(pb_and_tx := true, final := true),
						   res));
	/* Receive the response from reader. */
	ccid_pdu := f_ccid_receive(tr_CCID_DataBlock(g_slot_nr, ?, ?, ?));
	if (ccid_pdu.u.DataBlock.abData != res) {
		setverdict(fail, "Unexpected SW1/SW2");
		mtc.stop;
	}

	/* Stop simtrace emulation, to prevent race condition. */
	vc_Cardem.stop;
	vc_Cardem.done;
}
testcase TC_255_bytes_request() runs on Test_CT
{
	f_init();

	f_start_handler(refers(f_TC_255_bytes_request), mp_simtrace_slot, true);

	f_start_and_wait();
}

/* The SIM transfer is aborted. */
private function f_TC_abort() runs on CardemSlot_CT
{
	var octetstring req := c_SIM_READ_BINARY;
	var CCID_PDU ccid_pdu;
	var boolean v_slot_status_received := false;

	/* We need to emulate a SIM that does not respond, so we can be sure
	 * that the reader is still waiting while we abort.
	 */
	f_cardem_manager();

	f_ccid_power_on(CCID_PWRSEL_3V0);

	/* Send a request towards reader. */
	CCID.send(ts_CCID_XfrBlock(g_slot_nr, req, 0));
	/* Receive the request by SIM. */
	f_cardem_receive(tr_SIMTRACE_CEMU_RX_DATA(?, req));
	/* Transmit NULL procedure byte by SIM. */
	f_cardem_transmit(ts_SIMTRACE_CEMU_TX_DATA(ts_CardEmu_DataFlags(pb_and_tx := true, final := true),
						   '60'O));
	/* Send abort toward reader. Note that CCID_Emulation.ttcn will detect
	 * the PC_to_RDR_Abort message type and sends another abort message to
	 * the control pipe in advance with the same sequence number. This is
	 * done there, because the sequence number is set at CCID_Emulation.ttcn
	 * upon transmission of the two messages. */
	CCID.send(ts_CCID_Abort(g_slot_nr, 0));
	/* Wait for slot status, but eat the possible 'DataBlock' that will be received after timeout. */
	timer T := 3.0;
	T.start;
	alt {
	[] CCID.receive(tr_CCID_DataBlock(g_slot_nr, ?, ?, ?)) -> value ccid_pdu {
		log("Eating 'DataBlock'.");
		if (ccid_pdu.hdr_in.bStatus.bmCommandStatus != CCID_CMD_STATUS_FAILED) {
			setverdict(fail, "Unexpected Datablock status after abort command.");
			mtc.stop;
		}
		if (ccid_pdu.hdr_in.bError != CCID_ERR_CMD_ABORTED) {
			setverdict(fail, "Unexpected Datablock error code after abort command.");
			mtc.stop;
		}
		repeat;
		}
	[] CCID.receive(tr_CCID_SlotStatus(g_slot_nr, ?, ?, ?)) -> value ccid_pdu {
		v_slot_status_received := true;
		if (ccid_pdu.hdr_in.bStatus.bmCommandStatus != CCID_CMD_STATUS_OK) {
			setverdict(fail, "Unexpected Response to abort command.");
			mtc.stop;
		}
		}
	[] T.timeout { }
	}

	if (not v_slot_status_received) {
		setverdict(fail, "No response to abort command.");
		mtc.stop;
	}

	/* Stop simtrace emulation, to prevent race condition. */
	vc_Cardem.stop;
	vc_Cardem.done;
}
testcase TC_abort() runs on Test_CT
{
	f_init();

	f_start_handler(refers(f_TC_abort), mp_simtrace_slot, true);

	f_start_and_wait();
}



/* TODO */
/* IccPowerOn: verify that CCID resets all parameters to default values */
/* IccPowerOn: verify that bPowerSelect has no effect in active state */
/* IccClock: verify clock has stopped/restarted */
/* Abort for command that already terminated */
/* Abort for command that's still processing */


/***********************************************************************
 * Test behavior regarding invalid situations
 ***********************************************************************/

/* message for invalid slot number (more than we have) */
private function f_TC_inval_slot() runs on Slot_CT {
	CCID.send(ts_CCID_GetSlotStatus(g_slot_nr));
	alt {
	[] CCID.receive(tr_CCID_SlotStatus(hdr_in := tr_CCID_HeaderIN_FAIL(CCID_ERR_SLOT_NOT_EXIST))) {
		setverdict(pass);
		}
	[] CCID.receive(tr_CCID_SlotStatus) {
		setverdict(fail, "Unexpected SlotStatus");
		mtc.stop;
		}
	[] as_ccid_any();
	}
}
testcase TC_inval_slot() runs on Test_CT {
	f_init();
	f_start_handler(refers(f_TC_inval_slot), 15);
	f_start_and_wait();
}

/* switch card off and then XfrBlock. Requires reader with IccPowerOff support */
private function f_TC_xfer_off() runs on Slot_CT {
	f_ccid_power_off();
	CCID.send(ts_CCID_XfrBlock(g_slot_nr, c_SIM_SELECT_MF, 0));
	alt {
	[] CCID.receive(tr_CCID_DataBlock(slot:=g_slot_nr, hdr_in:=tr_CCID_HeaderIN_FAIL)) {
		setverdict(pass);
		}
	[] CCID.receive(tr_CCID_DataBlock(slot:=g_slot_nr, hdr_in:=tr_CCID_HeaderIN_OK)) {
		setverdict(fail, "Expected XfrBlock to fail");
		mtc.stop;
		}
	[] as_ccid_any();
	}
}
testcase TC_xfer_off() runs on Test_CT {
	f_init();
	f_start_handler(refers(f_TC_xfer_off), 0);
	f_start_and_wait();
}


/* unsupported Mechanical */
private function f_TC_unsupp_mechanical() runs on Slot_CT {
	CCID.send(ts_CCID_Mechanical(g_slot_nr, CCID_MECH_FN_EJECT_CARD));
	alt {
	[] CCID.receive(tr_CCID_SlotStatus(hdr_in := tr_CCID_HeaderIN_FAIL(CCID_ERR_CMD_NOT_SUPPORTED))) {
		setverdict(pass);
		}
	[] as_ccid_any();
	}
}
testcase TC_unsupp_mechanical() runs on Test_CT {
	f_init();
	f_start_handler(refers(f_TC_unsupp_mechanical), 0);
	f_start_and_wait();
}

/* unsupported Secure */
private function f_TC_unsupp_secure() runs on Slot_CT {
	CCID.send(ts_CCID_Secure(g_slot_nr, 0, 0, ''O));
	alt {
	[] CCID.receive(tr_CCID_DataBlock(hdr_in := tr_CCID_HeaderIN_FAIL(CCID_ERR_CMD_NOT_SUPPORTED))) {
		setverdict(pass);
		}
	[] CCID.receive(tr_CCID_SlotStatus(hdr_in := tr_CCID_HeaderIN_FAIL(CCID_ERR_CMD_NOT_SUPPORTED))) {
		setverdict(pass);
		}
	[] as_ccid_any();
	}
}
testcase TC_unsupp_secure() runs on Test_CT {
	f_init();
	f_start_handler(refers(f_TC_unsupp_secure), 0);
	f_start_and_wait();
}

/* truncated transfer */
private function f_TC_truncated() runs on Slot_CT
{
	var integer i;
	var octetstring req;
	var CCID_PDU pdu;
	const record of octetstring c_status := { '6700'O, '6700'O, '6700'O, '6d00'O, '6881'O };

	f_ccid_power_on();
	for (i := 4; i >= 0; i := i-1) {
		log("Testing trunkted transfer block with ", i, " octet(s).");
		req := substr(c_UICC_TRUNCATED, 0, i);
		pdu := f_ccid_xfr_pdu(req, ?);
		if (pdu.hdr_in.bStatus.bmCommandStatus == CCID_CMD_STATUS_FAILED) {
			log("Command failed as expected.");
			continue;
		}
		if (pdu.u.DataBlock.abData == c_status[4 - i]) {
			log("Got expected status result. (status ", c_status[4 - i], ")");
			continue;
		}
		setverdict(fail, "Unexpected SW1/SW2. (expected ", c_status[4 - i], ", got ",
				 pdu.u.DataBlock.abData, ")");
		mtc.stop;
	}
}
testcase TC_truncated() runs on Test_CT
{
	var integer i;

	f_init();

	for (i := 0; i < mp_use_slot_count; i := i+1) {
		f_start_handler(refers(f_TC_truncated), i);
	}

	f_start_and_wait();
}

/* The SIM responds with truncated response. */
private function f_TC_truncated_response() runs on CardemSlot_CT
{
	var octetstring req := c_SIM_READ_BINARY;
	var octetstring res := '0102030405060708090a9000'O;
	var CCID_PDU ccid_pdu;

	f_cardem_manager();

	f_ccid_power_on(CCID_PWRSEL_3V0);

	/* Send a request towards reader. */
	CCID.send(ts_CCID_XfrBlock(g_slot_nr, req, 0));
	/* Receive the request by SIM. */
	f_cardem_receive(tr_SIMTRACE_CEMU_RX_DATA(?, req));
	/* Transmit the response by SIM. */
	f_cardem_transmit(ts_SIMTRACE_CEMU_TX_DATA(ts_CardEmu_DataFlags(pb_and_tx := true, final := true),
						   req[1] & substr(res, 0, 5)));
	/* Receive the response from reader. */
	ccid_pdu := f_ccid_receive(tr_CCID_DataBlock(g_slot_nr, ?, ?, ?));
	if (ccid_pdu.hdr_in.bError != CCID_ERR_ICC_MUTE) {
		setverdict(fail, "Unexpected Response or error code");
		mtc.stop;
	}

	/* Stop simtrace emulation, to prevent race condition. */
	vc_Cardem.stop;
	vc_Cardem.done;
}
testcase TC_truncated_response() runs on Test_CT
{
	f_init();

	f_start_handler(refers(f_TC_truncated_response), mp_simtrace_slot, true);

	f_start_and_wait();
}

/* The SIM slot is empty, but closed. There is no ATR received. */
private function f_TC_empty_slot() runs on CardemSlot_CT
{
	var octetstring req := c_SIM_SELECT_EF_ICCID;
	var octetstring res := '9000'O;
	var CCID_PDU ccid_pdu;
	var CCID_PDU par;

	/* Do not start SIM emulation, so there is no ATR. */
	f_cardem_transmit(ts_SIMTRACE_CEMU_SET_ATR(''O));
	f_sleep(0.2);

	f_ccid_power_on(CCID_PWRSEL_3V0, tr_CCID_HeaderIN_FAIL(CCID_ERR_ICC_MUTE, CCID_ICC_STATUS_PRES_INACT));

	/* Send a request towards reader. */
	CCID.send(ts_CCID_XfrBlock(g_slot_nr, req, 0));
	/* Receive the response from reader. */
	ccid_pdu := f_ccid_receive(tr_CCID_DataBlock(g_slot_nr, ?, ?, ?));
	if (ccid_pdu.hdr_in.bError != CCID_ERR_CMD_NOT_SUPPORTED) {
		setverdict(fail, "Unexpected Response or error code");
		mtc.stop;
	}
	/* Send GetParameters on empty slot. */
	par := f_ccid_get_par();
	log(par);
	// FIXME: check result code

	/* Stop simtrace emulation, to prevent race condition. */
	vc_Cardem.stop;
	vc_Cardem.done;
}
testcase TC_empty_slot() runs on Test_CT
{
	f_init();

	f_start_handler(refers(f_TC_empty_slot), mp_simtrace_slot, true);

	f_start_and_wait();
}

/* The SIM does not send the expected status words. */
private function f_TC_status_word_timeout() runs on CardemSlot_CT
{
	var octetstring req := c_UICC_MANAGE_CHANNEL;
	var CCID_PDU ccid_pdu;

	f_cardem_manager();

	f_ccid_power_on(CCID_PWRSEL_3V0);

	/* Send a request towards reader. */
	CCID.send(ts_CCID_XfrBlock(g_slot_nr, req, 0));
	/* Receive the request by SIM. */
	f_cardem_receive(tr_SIMTRACE_CEMU_RX_DATA(?, req));
	/* Receive the response from reader. */
	ccid_pdu := f_ccid_receive(tr_CCID_DataBlock(g_slot_nr, ?, ?, ?));
	if (ccid_pdu.hdr_in.bError != CCID_ERR_ICC_MUTE) {
		setverdict(fail, "Unexpected Response or error code");
		mtc.stop;
	}

	/* Stop simtrace emulation, to prevent race condition. */
	vc_Cardem.stop;
	vc_Cardem.done;
}
testcase TC_status_word_timeout() runs on Test_CT
{
	f_init();

	f_start_handler(refers(f_TC_status_word_timeout), mp_simtrace_slot, true);

	f_start_and_wait();
}

/* The SIM responds with one status word only. */
private function f_TC_status_word_incomplete() runs on CardemSlot_CT
{
	var octetstring req := c_UICC_MANAGE_CHANNEL;
	var octetstring res := '62'O;
	var CCID_PDU ccid_pdu;

	f_cardem_manager();

	f_ccid_power_on(CCID_PWRSEL_3V0);

	/* Send a request towards reader. */
	CCID.send(ts_CCID_XfrBlock(g_slot_nr, req, 0));
	/* Receive the request by SIM. */
	f_cardem_receive(tr_SIMTRACE_CEMU_RX_DATA(?, req));
	/* Transmit the response by SIM. */
	f_cardem_transmit(ts_SIMTRACE_CEMU_TX_DATA(ts_CardEmu_DataFlags(pb_and_tx := true, final := true),
						   res));
	/* Receive the response from reader. */
	ccid_pdu := f_ccid_receive(tr_CCID_DataBlock(g_slot_nr, ?, ?, ?));
	if (ccid_pdu.hdr_in.bError != CCID_ERR_ICC_MUTE) {
		setverdict(fail, "Unexpected Response or error code");
		mtc.stop;
	}

	/* Stop simtrace emulation, to prevent race condition. */
	vc_Cardem.stop;
	vc_Cardem.done;
}
testcase TC_status_word_incomplete() runs on Test_CT
{
	f_init();

	f_start_handler(refers(f_TC_status_word_timeout), mp_simtrace_slot, true);

	f_start_and_wait();
}

/* The SIM sends a wrong procedure byte */
private function f_TC_wrong_procedure_byte() runs on CardemSlot_CT
{
	var octetstring req := c_SIM_READ_BINARY;
	var octetstring res := '0102030405060708090a9000'O;
	var CCID_PDU ccid_pdu;

	f_cardem_manager();

	f_ccid_power_on(CCID_PWRSEL_3V0);

	/* Send a request towards reader. */
	CCID.send(ts_CCID_XfrBlock(g_slot_nr, req, 0));
	/* Receive the request by SIM. */
	f_cardem_receive(tr_SIMTRACE_CEMU_RX_DATA(?, req));
	/* Transmit the response by SIM. */
	f_cardem_transmit(ts_SIMTRACE_CEMU_TX_DATA(ts_CardEmu_DataFlags(pb_and_tx := true, final := true),
						   '42'O & res));
	/* Receive the response from reader. */
	ccid_pdu := f_ccid_receive(tr_CCID_DataBlock(g_slot_nr, ?, ?, ?));
	if (ccid_pdu.hdr_in.bError != CCID_ERR_PROCEDURE_BYTE_CONFLICT) {
		setverdict(fail, "Unexpected Response or error code");
		mtc.stop;
	}

	/* Stop simtrace emulation, to prevent race condition. */
	vc_Cardem.stop;
	vc_Cardem.done;
}
testcase TC_wrong_procedure_byte() runs on Test_CT
{
	f_init();

	f_start_handler(refers(f_TC_wrong_procedure_byte), mp_simtrace_slot, true);

	f_start_and_wait();
}

/* The SIM does not send the expected procedure byte. */
private function f_TC_procedure_byte_timeout() runs on CardemSlot_CT
{
	var octetstring req := c_SIM_READ_BINARY;
	var CCID_PDU ccid_pdu;

	f_cardem_manager();

	f_ccid_power_on(CCID_PWRSEL_3V0);

	/* Send a request towards reader. */
	CCID.send(ts_CCID_XfrBlock(g_slot_nr, req, 0));
	/* Receive the request by SIM. */
	f_cardem_receive(tr_SIMTRACE_CEMU_RX_DATA(?, req));
	/* Transmit procedure byte from SIM. */
	f_cardem_transmit(ts_SIMTRACE_CEMU_TX_DATA(ts_CardEmu_DataFlags(pb_and_tx := true, final := true),
						   '60'O));
	/* Eat the possible 'Time Extension status' */
	timer T := 0.2;
	T.start;
	alt {
	[] CCID.receive(tr_CCID_DataBlock(g_slot_nr, ?, tr_CCID_HeaderIN(?, CCID_CMD_STATUS_TIME_EXT, ?), ?)) {
		log("Eating 'Time Extension status'.");
		repeat;
		}
	[] as_ccid_any();
	[] T.timeout { }
	}
	/* Receive the response from reader. */
	ccid_pdu := f_ccid_receive(tr_CCID_DataBlock(g_slot_nr, ?, ?, ?));
	if (ccid_pdu.hdr_in.bError != CCID_ERR_ICC_MUTE) {
		setverdict(fail, "Unexpected Response or error code");
		mtc.stop;
	}

	/* Stop simtrace emulation, to prevent race condition. */
	vc_Cardem.stop;
	vc_Cardem.done;
}
testcase TC_procedure_byte_timeout() runs on Test_CT
{
	f_init();

	f_start_handler(refers(f_TC_procedure_byte_timeout), mp_simtrace_slot, true);

	f_start_and_wait();
}


/* TODO */
/* IccPowerOn with wrong voltage (> 0x04) */
/* SetParameters for bProtocolNum > 0x01 */
/* SetParameters: invalid parameters */
/* set unsupported frequency */
/* set unsupported clock rate */
/* XfrBlock: bWI in T=0? */
/* XfrBlock: wLevelParameter not matching level? */
/* Abort for command that was not even submitted yet*/
/* dwMaxCCIDMessageLength */


control {
	/* valid transactions */
	execute( TC_get_status() );
	execute( TC_power_on() );
	execute( TC_power_on_warm() );
	if (mp_test_power_off) {
		execute( TC_power_off() );
	}
	execute( TC_select_mf() );
	execute( TC_get_params() );
	if (mp_test_set_params) {
		execute( TC_set_params() );
		execute( TC_reset_params() );
	}
	execute( TC_set_params_nochange() );
	execute( TC_successful_case_4() );
	execute( TC_successful_case_1() );
	execute( TC_successful_case_2_3() );
	execute( TC_simtrace_test() );
	execute( TC_255_bytes_response() );
	execute( TC_256_bytes_response() );
	execute( TC_null_procedure_byte() );
	execute( TC_255_bytes_request() );
	execute( TC_abort() );

	/* error handling */
	execute( TC_inval_slot() );
	if (mp_test_power_off) {
		execute( TC_xfer_off() );
	}
	execute( TC_unsupp_mechanical() );
	execute( TC_unsupp_secure() );
	execute( TC_truncated() );
	execute( TC_truncated_response() );
	execute( TC_empty_slot() );
	execute( TC_status_word_timeout() );
	execute( TC_status_word_incomplete() );
	execute( TC_wrong_procedure_byte() );
	execute( TC_procedure_byte_timeout() );
}




}
