module REMSIM_Tests {

/* Implementation of RSPRO Client in TTCN-3.
 * (C) 2019 by Harald Welte <laforge@gnumonks.org>
 * 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 IPL4asp_Types all;
import from RSPRO all;
import from RSPRO_Types all;
import from IPA_Types all;
import from IPA_Emulation all;


modulepar {
	charstring mp_bankd_ip := "127.0.0.1";
	integer mp_bankd_port := 9999;

	charstring mp_server_ip := "127.0.0.1";
	integer mp_server_port := 9998;

	integer mp_rsres_port := 9997;
}

const integer NUM_CLIENT := 3;

type record RSPRO_Client {
	IPA_Emulation_CT	vc_IPA,
	IPA_CCM_Parameters	ccm_pars,
	charstring		id,
	ComponentIdentity	rspro_id,

	ClientSlot		rspro_client_slot optional,
	BankId			rspro_bank_id optional,
	SlotNumber		rspro_bank_nslots optional
};

type component rspro_client_CT {
	var RSPRO_Client	rspro[NUM_CLIENT];
	port IPA_RSPRO_PT	RSPRO[NUM_CLIENT];
};

private altstep as_ignore_id_ack(integer i := 0) runs on rspro_client_CT {
	[] RSPRO[i].receive(tr_ASP_IPA_EV(ASP_IPA_EVENT_ID_ACK)) { repeat; }
}

function f_rspro_init(inout RSPRO_Client clnt, charstring dst_host, integer dst_port,
		      ComponentIdentity rspro_id, integer i)
runs on rspro_client_CT
{
	timer T := 4.0;

	clnt.id := "RSPRO" & int2str(i);
	clnt.vc_IPA := IPA_Emulation_CT.create(clnt.id);
	clnt.ccm_pars := c_IPA_default_ccm_pars;
	clnt.ccm_pars.name := "Osmocom TTCN-3 RSPRO client simulator";
	clnt.rspro_id := rspro_id;

	/* leave it up to the caller to set those */
	clnt.rspro_client_slot := omit;
	clnt.rspro_bank_id := omit;
	clnt.rspro_bank_nslots := omit;

	map(clnt.vc_IPA:IPA_PORT, system:IPA_CODEC_PT);
	connect(clnt.vc_IPA:IPA_RSPRO_PORT, self:RSPRO[i]);

	clnt.vc_IPA.start(IPA_Emulation.main_client(dst_host, dst_port, "", 10000+i, clnt.ccm_pars));

	T.start;
	alt {
	[] RSPRO[i].receive(tr_ASP_IPA_EV(ASP_IPA_EVENT_UP)) { }
	[] T.timeout {
		setverdict(fail, "Timeout waiting for ASP_IPA_EVENT_UP");
		mtc.stop;
		}
	}
	T.start;
	alt {
	[] RSPRO[i].receive(tr_ASP_IPA_EV(ASP_IPA_EVENT_ID_ACK)) { }
	[] T.timeout {
		setverdict(fail, "Timeout waiting for ASP_IPA_EVENT_ID_ACK");
		mtc.stop;
		}
	}


	activate(as_ignore_id_ack(i));
}

function f_rspro_fini(inout RSPRO_Client clnt, integer i)
runs on rspro_client_CT {
	clnt.vc_IPA.stop;
	disconnect(clnt.vc_IPA:IPA_RSPRO_PORT, self:RSPRO[i]);
	unmap(clnt.vc_IPA:IPA_PORT, system:IPA_CODEC_PT);
}


function f_rspro_exp(template RsproPDU exp, integer i := 0)
runs on rspro_client_CT return RsproPDU
{
	var RsproPDU pdu;

	timer T := 10.0;
	T.start;
	alt {
	[] RSPRO[i].receive(exp) -> value pdu {
		setverdict(pass);
		}
	[] RSPRO[i].receive(RsproPDU:?) -> value pdu {
		setverdict(fail, "Received unexpected RPSRO", pdu);
		mtc.stop;
		}
	[] RSPRO[i].receive {
		setverdict(fail, "Received unexpected != RPSRO");
		mtc.stop;
		}
	[] T.timeout {
		setverdict(fail, "Timeout waiting for ", exp);
		mtc.stop;
		}
	}
	return pdu;
}

function f_rspro_exp_disconnect(integer i := 0)
runs on rspro_client_CT {
	timer T := 10.0;
	T.start;
	alt {
	[] RSPRO[i].receive(tr_ASP_IPA_EV(ASP_IPA_EVENT_DOWN)) {
		setverdict(pass);
		}
	[] T.timeout {
		setverdict(fail, "Timeout expecting disconnect");
		mtc.stop;
		}
	}
}


function f_rspro_connect_client(integer i, template ResultCode exp_res := ok) runs on rspro_client_CT
{
	select (rspro[i].rspro_id.type_) {
	case (remsimClient) {
		RSPRO[i].send(ts_RSPRO_ConnectClientReq(rspro[i].rspro_id, rspro[i].rspro_client_slot));
		f_rspro_exp(tr_RSPRO_ConnectClientRes(?, exp_res), i);
		}
	case (remsimBankd) {
		var template IpAddress ip := ts_IPv4(mp_bankd_ip);
		RSPRO[i].send(ts_RSPRO_ConnectBankReq(rspro[i].rspro_id, rspro[i].rspro_bank_id,
						      rspro[i].rspro_bank_nslots,
						      ts_IpPort(ip, mp_bankd_port)));
		f_rspro_exp(tr_RSPRO_ConnectBankRes(?, exp_res), i);
		}
	case else {
		setverdict(fail, "Unsupported type ", rspro[i].rspro_id.type_);
		mtc.stop;
		}
	}
}

function f_rspro_connect_clients() runs on rspro_client_CT
{
	var integer i;

	for (i := 0; i < NUM_CLIENT; i := i+1) {
		select (rspro[i].rspro_id.type_) {
		case (remsimClient) {
			RSPRO[i].send(ts_RSPRO_ConnectClientReq(rspro[i].rspro_id,
								rspro[i].rspro_client_slot));
			}
		case (remsimBankd) {
			var template IpAddress ip := ts_IPv4(mp_bankd_ip);
			RSPRO[i].send(ts_RSPRO_ConnectBankReq(rspro[i].rspro_id, rspro[i].rspro_bank_id,
							      rspro[i].rspro_bank_nslots,
							      ts_IpPort(ip, mp_bankd_port)));
			}
		}
	}
	for (i := 0; i < NUM_CLIENT; i := i+1) {
		select (rspro[i].rspro_id.type_) {
		case (remsimClient) {
			f_rspro_exp(tr_RSPRO_ConnectClientRes(?, ResultCode:ok), i);
			}
		case (remsimBankd) {
			f_rspro_exp(tr_RSPRO_ConnectBankRes(?, ResultCode:ok), i);
			}
		}
	}
}

/* transceive a TPDU from modem to card (and back) */
function f_rspro_xceive_mdm2card(integer idx, BankSlot bs, template (value) octetstring data,
				 template (value) TpduFlags flags) runs on rspro_client_CT return octetstring {
	var RsproPDU rx;
	RSPRO[idx].send(ts_RSPRO_TpduModemToCard(rspro[idx].rspro_client_slot, bs, flags, data));
	rx := f_rspro_exp(tr_RSPRO_TpduCardToModem(bs, rspro[idx].rspro_client_slot, ?, ?));
	return rx.msg.tpduCardToModem.data;
}

/* handle an incoming CreateMapping + ACK it */
altstep as_rspro_create_mapping(integer i, template ClientSlot cslot := ?, template BankSlot bslot := ?,
				template ResultCode res := ok)
runs on rspro_client_CT {
	var RsproPDU rx;
	[] RSPRO[i].receive(tr_RSPRO_CreateMappingReq(cslot, bslot)) -> value rx {
		RSPRO[i].send(ts_RSPRO_CreateMappingRes(res));
		}
}

/* handle an incoming RemoveMapping + ACK it */
altstep as_rspro_remove_mapping(integer i, template ClientSlot cslot := ?, template BankSlot bslot := ?,
				template ResultCode res := ok)
runs on rspro_client_CT {
	var RsproPDU rx;
	[] RSPRO[i].receive(tr_RSPRO_RemoveMappingReq(cslot, bslot)) -> value rx {
		RSPRO[i].send(ts_RSPRO_RemoveMappingRes(res));
		}
}

altstep as_rspro_cfg_client_id(integer i, template ClientSlot cslot := ?,
				template (value) ResultCode res := ok)
runs on rspro_client_CT {
	var RsproPDU rx;
	[] RSPRO[i].receive(tr_RSPRO_ConfigClientIdReq(cslot)) -> value rx {
		RSPRO[i].send(ts_RSPRO_ConfigClientIdRes(res));
		}
}

altstep as_rspro_cfg_client_bank(integer i, template BankSlot bslot := ?,
				 template IpPort ip_port := ?,
				template (value) ResultCode res := ok)
runs on rspro_client_CT {
	var RsproPDU rx;
	[] RSPRO[i].receive(tr_RSPRO_ConfigClientBankReq(bslot, ip_port)) -> value rx {
		RSPRO[i].send(ts_RSPRO_ConfigClientBankRes(res));
		}
}



}