/* GTPv2 Emulation in TTCN-3
 *
 * (C) 2018-2020 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
 */

module GTPv2_Emulation {

import from IPL4asp_Types all;
import from General_Types all;
import from Osmocom_Types all;
import from GTPv2_Types all;
import from GTPv2_Templates all;
import from GTPv2_CodecPort all;
import from GTPv2_CodecPort_CtrlFunct all;

/***********************************************************************
 * Main Emulation Component
 ***********************************************************************/

//modulepar {};

const integer GTP2C_PORT := 2123;

type record Gtp2EmulationCfg {
	HostName gtpc_bind_ip,
	IPL4asp_Types.PortNumber gtpc_bind_port,
	HostName gtpc_remote_ip,
	IPL4asp_Types.PortNumber gtpc_remote_port,
	boolean sgw_role
};

type component GTPv2_Emulation_CT {
	/* Communication with underlying GTP CodecPort */
	port GTPv2C_PT GTP2C;

	/* Communication with Clients */
	port GTP2EM_PT TEIC0;
	port GTP2EM_PT CLIENT;
	port GTP2EM_PROC_PT CLIENT_PROC;

	/* Configuration by the user */
	var Gtp2EmulationCfg g_gtp2_cfg;

	/* State */
	var Gtp2cPeer g_peer;
	var integer g_gtp2c_id;
	var OCT1 g_restart_ctr;
	var uint16_t g_c_seq_nr;
	var TidTableRec TidTable[256];
	var SeqTableRec SeqTable[256];
	var ImsiTableRec ImsiTable[256];
	var UdMsgTableRec UdMsgTable[256];
	var PidTableRec	PidTable[256];
};

/* local TEIC <-> ConnHdlr mapping */
type record TidTableRec {
	OCT4 teic,
	GTP2_ConnHdlr vc_conn
};

/* local SeqNr <-> ConnHdlr mapping (until a response is received */
type record SeqTableRec {
	OCT3 seq,
	GTP2_ConnHdlr vc_conn
};

/* IMSI <-> ConnHdlr mapping */
type record ImsiTableRec {
	hexstring imsi,
	GTP2_ConnHdlr vc_conn
};

/* Unit data message type <-> ConnHdlr mapping */
type record UdMsgTableRec {
	OCT1 messageType,
	GTP2_ConnHdlr vc_conn
};

/* pid <-> ConnHdlr mapping (for UECUPS process termination indication) */
type record PidTableRec {
	/* process ID of the running process */
	integer pid,
	/* component that started it */
	GTP2_ConnHdlr vc_conn
};

private function f_teic_known(OCT4 teic) runs on GTPv2_Emulation_CT return boolean {
	var integer i;
	for (i := 0; i < sizeof(TidTable); i := i+1) {
		if (isbound(TidTable[i].teic) and TidTable[i].teic == teic) {
			return true;
		}
	}
	return false;
}

private function f_comp_by_teic(OCT4 teic) runs on GTPv2_Emulation_CT return GTP2_ConnHdlr {
	var integer i;
	for (i := 0; i < sizeof(TidTable); i := i+1) {
		if (isbound(TidTable[i].teic) and TidTable[i].teic == teic) {
			return TidTable[i].vc_conn;
		}
	}
	setverdict(fail, "No Component for TEIC ", teic);
	mtc.stop;
}

private function f_seq_known(OCT3 seq) runs on GTPv2_Emulation_CT return boolean {
	var integer i;
	for (i := 0; i < sizeof(SeqTable); i := i+1) {
		if (isbound(SeqTable[i].seq) and SeqTable[i].seq == seq) {
			return true;
		}
	}
	return false;
}

private function f_comp_by_seq(OCT3 seq) runs on GTPv2_Emulation_CT return GTP2_ConnHdlr {
	var integer i;
	for (i := 0; i < sizeof(SeqTable); i := i+1) {
		if (isbound(SeqTable[i].seq) and SeqTable[i].seq == seq) {
			return SeqTable[i].vc_conn;
		}
	}
	setverdict(fail, "No Component for SEQ ", seq);
	mtc.stop;
}

private function f_imsi_known(hexstring imsi) runs on GTPv2_Emulation_CT return boolean {
	var integer i;
	for (i := 0; i < sizeof(ImsiTable); i := i+1) {
		if (isbound(ImsiTable[i].imsi) and ImsiTable[i].imsi == imsi) {
			return true;
		}
	}
	return false;
}

private function f_comp_by_imsi(hexstring imsi) runs on GTPv2_Emulation_CT return GTP2_ConnHdlr {
	var integer i;
	for (i := 0; i < sizeof(ImsiTable); i := i+1) {
		if (isbound(ImsiTable[i].imsi) and ImsiTable[i].imsi == imsi) {
			return ImsiTable[i].vc_conn;
		}
	}
	setverdict(fail, "No Component for IMSI ", imsi);
	mtc.stop;
}

private function f_comp_by_pid(integer pid) runs on GTPv2_Emulation_CT return GTP2_ConnHdlr {
	var integer i;
	for (i := 0; i < sizeof(PidTable); i := i+1) {
		if (isbound(PidTable[i].pid) and PidTable[i].pid == pid) {
			/* fixme: remove */
			return PidTable[i].vc_conn;
		}
	}
	setverdict(fail, "No Component for PID ", pid);
	mtc.stop;
}

private function f_tid_tbl_add(OCT4 teic, GTP2_ConnHdlr vc_conn) runs on GTPv2_Emulation_CT {
	var integer i;
	for (i := 0; i < sizeof(TidTable); i := i+1) {
		if (not isbound(TidTable[i].teic)) {
			TidTable[i].teic := teic;
			TidTable[i].vc_conn := vc_conn;
			return;
		}
	}
	testcase.stop("No Space in TidTable for ", teic);
}

private function f_seq_tbl_add(OCT3 seq, GTP2_ConnHdlr vc_conn) runs on GTPv2_Emulation_CT {
	var integer i;
	for (i := 0; i < sizeof(SeqTable); i := i+1) {
		if (not isbound(SeqTable[i].seq)) {
			SeqTable[i].seq := seq;
			SeqTable[i].vc_conn := vc_conn;
			return;
		}
	}
	testcase.stop("No Space in SeqTable for ", seq);
}

private function f_seq_tbl_del(OCT3 seq) runs on GTPv2_Emulation_CT {
	var integer i;
	for (i := 0; i < sizeof(SeqTable); i := i+1) {
		if (isbound(SeqTable[i].seq) and SeqTable[i].seq == seq) {
			SeqTable[i] := {
				seq := -,
				vc_conn := null
			}
		}
	}
}

private function f_imsi_tbl_add(hexstring imsi, GTP2_ConnHdlr vc_conn) runs on GTPv2_Emulation_CT {
	var integer i;
	for (i := 0; i < sizeof(ImsiTable); i := i+1) {
		if (not isbound(ImsiTable[i].imsi)) {
			ImsiTable[i].imsi := imsi;
			ImsiTable[i].vc_conn := vc_conn;
			return;
		}
	}
	testcase.stop("No Space in IMSI Table for ", imsi);
}

private function f_udmsg_tbl_add(OCT1 messageType, GTP2_ConnHdlr vc_conn) runs on GTPv2_Emulation_CT {
	var integer i;
	for (i := 0; i < sizeof(UdMsgTable); i := i+1) {
		if (not isbound(UdMsgTable[i].messageType)) {
			UdMsgTable[i].messageType := messageType;
			UdMsgTable[i].vc_conn := vc_conn;
			return;
		}
	}
	testcase.stop("No Space in UdMsg Table for messateType ", messageType);
}

private function f_pid_tbl_add(integer pid, GTP2_ConnHdlr vc_conn) runs on GTPv2_Emulation_CT {
	var integer i;
	for (i := 0; i < sizeof(PidTable); i := i+1) {
		if (not isbound(PidTable[i].pid)) {
			PidTable[i].pid := pid;
			PidTable[i].vc_conn := vc_conn;
			return;
		}
	}
	testcase.stop("No Space in PID Table for ", pid);
}


/* allocate an unused local teic */
private function f_alloc_teic() runs on GTPv2_Emulation_CT return OCT4 {
	var OCT4 teic;
	var integer i, j;
	for (i := 0; i < 100; i := i+1) {
		teic := f_rnd_octstring(4);
		for (j := 0; j < sizeof(TidTable); j := j+1) {
			if (isbound(TidTable) and TidTable[i].teic == teic) {
				continue;
			}
		}
		/* we iterated over all entries and found no match: great! */
		return teic;
	}
	testcase.stop("Cannot find unused TEIC after ", i, " attempts");
}

/* obtain the IMSI from a GTPv2C PDU, if there is any IMSI contained. The way how the TITAN
 * GTPv2 decoders are structured (explict IE members rather than a list/set of generic IE structures)
 * doesn't make this easy, but requires lots of boilerplate code.  Oh well.. */
function f_gtp2c_extract_imsi(PDU_GTPCv2 gtp) return template (omit) hexstring {
	if (ischosen(gtp.gtpcv2_pdu.createSessionRequest)) {
		if (ispresent(gtp.gtpcv2_pdu.createSessionRequest.iMSI)) {
			return gtp.gtpcv2_pdu.createSessionRequest.iMSI.iMSI_Value;
		}
	} else if (ischosen(gtp.gtpcv2_pdu.downlinkDataNotification)) {
		if (ispresent(gtp.gtpcv2_pdu.downlinkDataNotification.iMSI)) {
			return gtp.gtpcv2_pdu.downlinkDataNotification.iMSI.iMSI_Value;
		}
	} else if (ischosen(gtp.gtpcv2_pdu.downlinkDataNotificationAcknowledgement)) {
		if (ispresent(gtp.gtpcv2_pdu.downlinkDataNotificationAcknowledgement.iMSI)) {
			return gtp.gtpcv2_pdu.downlinkDataNotificationAcknowledgement.iMSI.iMSI_Value;
		}
	} else if (ischosen(gtp.gtpcv2_pdu.downlinkDataNotificationFailureIndication)) {
		if (ispresent(gtp.gtpcv2_pdu.downlinkDataNotificationFailureIndication.iMSI)) {
			return gtp.gtpcv2_pdu.downlinkDataNotificationFailureIndication.iMSI.iMSI_Value;
		}
	} else if (ischosen(gtp.gtpcv2_pdu.createIndirectDataForwardingTunnelRequest)) {
		if (ispresent(gtp.gtpcv2_pdu.createIndirectDataForwardingTunnelRequest.iMSI)) {
			return gtp.gtpcv2_pdu.createIndirectDataForwardingTunnelRequest.iMSI.iMSI_Value;
		}
	} else if (ischosen(gtp.gtpcv2_pdu.stopPagingIndication)) {
		if (ispresent(gtp.gtpcv2_pdu.stopPagingIndication.iMSI)) {
			return gtp.gtpcv2_pdu.stopPagingIndication.iMSI.iMSI_Value;
		}
	} else if (ischosen(gtp.gtpcv2_pdu.forwardRelocationRequest)) {
		if (ispresent(gtp.gtpcv2_pdu.forwardRelocationRequest.iMSI)) {
			return gtp.gtpcv2_pdu.forwardRelocationRequest.iMSI.iMSI_Value;
		}
	} else if (ischosen(gtp.gtpcv2_pdu.contextRequest)) {
		if (ispresent(gtp.gtpcv2_pdu.contextRequest.iMSI)) {
			return gtp.gtpcv2_pdu.contextRequest.iMSI.iMSI_Value;
		}
	} else if (ischosen(gtp.gtpcv2_pdu.identificationResponse)) {
		if (ispresent(gtp.gtpcv2_pdu.identificationResponse.iMSI)) {
			return gtp.gtpcv2_pdu.identificationResponse.iMSI.iMSI_Value;
		}
	} else if (ischosen(gtp.gtpcv2_pdu.changeNotificationRequest)) {
		if (ispresent(gtp.gtpcv2_pdu.changeNotificationRequest)) {
			return gtp.gtpcv2_pdu.changeNotificationRequest.iMSI.iMSI_Value;
		}
	} else if (ischosen(gtp.gtpcv2_pdu.changeNotificationResponse)) {
		if (ispresent(gtp.gtpcv2_pdu.changeNotificationResponse.iMSI)) {
			return gtp.gtpcv2_pdu.changeNotificationResponse.iMSI.iMSI_Value;
		}
	} else if (ischosen(gtp.gtpcv2_pdu.relocationCancelRequest)) {
		if (ispresent(gtp.gtpcv2_pdu.relocationCancelRequest.iMSI)) {
			return gtp.gtpcv2_pdu.relocationCancelRequest.iMSI.iMSI_Value;
		}
	} else if (ischosen(gtp.gtpcv2_pdu.uE_RegistrationQueryRequest)) {
		if (ispresent(gtp.gtpcv2_pdu.uE_RegistrationQueryRequest.iMSI)) {
			return gtp.gtpcv2_pdu.uE_RegistrationQueryRequest.iMSI.iMSI_Value;
		}
	} else if (ischosen(gtp.gtpcv2_pdu.uE_RegistrationQueryResponse)) {
		if (ispresent(gtp.gtpcv2_pdu.uE_RegistrationQueryResponse.iMSI)) {
			return gtp.gtpcv2_pdu.uE_RegistrationQueryResponse.iMSI.iMSI_Value;
		}
	} else if (ischosen(gtp.gtpcv2_pdu.suspendNotification)) {
		if (ispresent(gtp.gtpcv2_pdu.suspendNotification.iMSI)) {
			return gtp.gtpcv2_pdu.suspendNotification.iMSI.iMSI_Value;
		}
	} else if (ischosen(gtp.gtpcv2_pdu.resumeNotification)) {
		if (ispresent(gtp.gtpcv2_pdu.resumeNotification.iMSI)) {
			return gtp.gtpcv2_pdu.resumeNotification.iMSI.iMSI_Value;
		}
	} else if (ischosen(gtp.gtpcv2_pdu.cSPagingIndication)) {
		if (ispresent(gtp.gtpcv2_pdu.cSPagingIndication.iMSI)) {
			return gtp.gtpcv2_pdu.cSPagingIndication.iMSI.iMSI_Value;
		}
	} else if (ischosen(gtp.gtpcv2_pdu.pGW_DownlinkTriggeringNotification)) {
		if (ispresent(gtp.gtpcv2_pdu.pGW_DownlinkTriggeringNotification.iMSI)) {
			return gtp.gtpcv2_pdu.pGW_DownlinkTriggeringNotification.iMSI.iMSI_Value;
		}
	} else if (ischosen(gtp.gtpcv2_pdu.pGW_DownlinkTriggeringAcknowledge)) {
		if (ispresent(gtp.gtpcv2_pdu.pGW_DownlinkTriggeringAcknowledge.iMSI)) {
			return gtp.gtpcv2_pdu.pGW_DownlinkTriggeringAcknowledge.iMSI.iMSI_Value;
		}
	} else if (ischosen(gtp.gtpcv2_pdu.traceSessionActivation)) {
		if (ispresent(gtp.gtpcv2_pdu.traceSessionActivation.iMSI)) {
			return gtp.gtpcv2_pdu.traceSessionActivation.iMSI.iMSI_Value;
		}
	}
	return omit;
}

private function f_gtp2c_is_initial_msg(PDU_GTPCv2 msg) return boolean
{
	if (ischosen(msg.gtpcv2_pdu.echoRequest) or
	    ischosen(msg.gtpcv2_pdu.versionNotSupported) or
	    ischosen(msg.gtpcv2_pdu.createSessionRequest) or
	    ischosen(msg.gtpcv2_pdu.createBearerRequest) or
	    ischosen(msg.gtpcv2_pdu.bearerResourceCommand) or
	    ischosen(msg.gtpcv2_pdu.bearerResourceFailureIndication) or
	    ischosen(msg.gtpcv2_pdu.modifyBearerRequest) or
	    ischosen(msg.gtpcv2_pdu.deleteSessionRequest) or
	    ischosen(msg.gtpcv2_pdu.deleteBearerRequest) or
	    ischosen(msg.gtpcv2_pdu.downlinkDataNotification) or
	    ischosen(msg.gtpcv2_pdu.downlinkDataNotificationAcknowledgement) or
	    ischosen(msg.gtpcv2_pdu.downlinkDataNotificationFailureIndication) or
	    ischosen(msg.gtpcv2_pdu.deleteIndirectDataForwardingTunnelRequest) or
	    ischosen(msg.gtpcv2_pdu.modifyBearerCommand) or
	    ischosen(msg.gtpcv2_pdu.modifyBearerFailureIndication) or
	    ischosen(msg.gtpcv2_pdu.updateBearerRequest) or
	    ischosen(msg.gtpcv2_pdu.deleteBearerCommand) or
	    ischosen(msg.gtpcv2_pdu.createIndirectDataForwardingTunnelRequest) or
	    ischosen(msg.gtpcv2_pdu.releaseAccessBearersRequest) or
	    ischosen(msg.gtpcv2_pdu.stopPagingIndication) or
	    ischosen(msg.gtpcv2_pdu.modifyAccessBearersRequest) or
	    ischosen(msg.gtpcv2_pdu.remoteUEReportNotification) or
	    ischosen(msg.gtpcv2_pdu.remoteUEReportAcknowledge) or
	    ischosen(msg.gtpcv2_pdu.forwardRelocationRequest) or
	    ischosen(msg.gtpcv2_pdu.forwardRelocationCompleteNotification) or
	    ischosen(msg.gtpcv2_pdu.forwardRelocationCompleteAcknowledge) or
	    ischosen(msg.gtpcv2_pdu.contextRequest) or
	    ischosen(msg.gtpcv2_pdu.contextAcknowledge) or
	    ischosen(msg.gtpcv2_pdu.identificationRequest) or
	    ischosen(msg.gtpcv2_pdu.forwardAccessContextNotification) or
	    ischosen(msg.gtpcv2_pdu.forwardAccessContextAcknowledge) or
	    ischosen(msg.gtpcv2_pdu.detachNotification) or
	    ischosen(msg.gtpcv2_pdu.detachAcknowledge) or
	    ischosen(msg.gtpcv2_pdu.changeNotificationRequest) or
	    ischosen(msg.gtpcv2_pdu.relocationCancelRequest) or
	    ischosen(msg.gtpcv2_pdu.configurationTransferTunnel) or
	    ischosen(msg.gtpcv2_pdu.rAN_InformationRelay) or
	    ischosen(msg.gtpcv2_pdu.suspendNotification) or
	    ischosen(msg.gtpcv2_pdu.suspendAcknowledge) or
	    ischosen(msg.gtpcv2_pdu.resumeNotification) or
	    ischosen(msg.gtpcv2_pdu.resumeAcknowledge) or
	    ischosen(msg.gtpcv2_pdu.cSPagingIndication) or
	    ischosen(msg.gtpcv2_pdu.createForwardingTunnelRequest) or
	    ischosen(msg.gtpcv2_pdu.deletePDN_ConnectionSetRequest) or
	    ischosen(msg.gtpcv2_pdu.traceSessionActivation) or
	    ischosen(msg.gtpcv2_pdu.traceSessionDeactivation) or
	    ischosen(msg.gtpcv2_pdu.updatePDN_ConnectionSetRequest) or
	    ischosen(msg.gtpcv2_pdu.pGW_RestartNotification) or
	    ischosen(msg.gtpcv2_pdu.pGW_RestartNotificationAcknowledge) or
	    ischosen(msg.gtpcv2_pdu.pGW_DownlinkTriggeringNotification) or
	    ischosen(msg.gtpcv2_pdu.pGW_DownlinkTriggeringAcknowledge) or
	    ischosen(msg.gtpcv2_pdu.alertMMENotification) or
	    ischosen(msg.gtpcv2_pdu.alertMMEAcknowledge) or
	    ischosen(msg.gtpcv2_pdu.uEActivityNotification) or
	    ischosen(msg.gtpcv2_pdu.uEActivityAcknowledge) or
	    ischosen(msg.gtpcv2_pdu.mBMSSessionStartRequest) or
	    ischosen(msg.gtpcv2_pdu.mBMSSessionUpdateRequest) or
	    ischosen(msg.gtpcv2_pdu.mBMSSessionStopRequest) or
	    ischosen(msg.gtpcv2_pdu.iSR_StatusIndication) or
	    ischosen(msg.gtpcv2_pdu.uE_RegistrationQueryRequest)) {
		return true;
	}
	return false;
}

private function f_init(Gtp2EmulationCfg cfg) runs on GTPv2_Emulation_CT {
	var Result res;

	map(self:GTP2C, system:GTP2C);
	res := GTPv2_CodecPort_CtrlFunct.f_IPL4_listen(GTP2C, cfg.gtpc_bind_ip,
						     cfg.gtpc_bind_port, {udp:={}});
	g_gtp2c_id := res.connId;

	g_restart_ctr := f_rnd_octstring(1);
	g_c_seq_nr := f_rnd_int(65535);
	g_gtp2_cfg := cfg;
	g_peer := {
		connId := g_gtp2c_id,
		remName := g_gtp2_cfg.gtpc_remote_ip,
		remPort := g_gtp2_cfg.gtpc_remote_port
	}
}

private function SendToUdMsgTable(Gtp2cUnitdata g2c_ud) runs on GTPv2_Emulation_CT {
	var GTP2_ConnHdlr vc_conn;

	for (var integer i := 0; i < sizeof(UdMsgTable); i := i + 1) {
		if (isbound(UdMsgTable[i].messageType)) {
			if (UdMsgTable[i].messageType == g2c_ud.gtpc.messageType) {
				vc_conn := UdMsgTable[i].vc_conn;
				CLIENT.send(g2c_ud.gtpc) to vc_conn;
			}
		}
	}

	return;
}

function main(Gtp2EmulationCfg cfg) runs on GTPv2_Emulation_CT {
	var Gtp2cUnitdata g2c_ud;
	var PDU_GTPCv2 g2c;
	var GTP2_ConnHdlr vc_conn;
	var hexstring imsi;
	var OCT1 messageType;
	var OCT4 teic;

	f_init(cfg);

	while (true) {
	alt {
	/* route inbound GTP2-C based on TEIC, SEQ or IMSI */
	[] GTP2C.receive(Gtp2cUnitdata:?) -> value g2c_ud {
		var template hexstring imsi_t := f_gtp2c_extract_imsi(g2c_ud.gtpc);
		/* if this is a response, route by SEQ: */
		if (match(g2c_ud.gtpc, tr_PDU_GTP2C_msgtypes(gtp2_responses))
			  and f_seq_known(g2c_ud.gtpc.sequenceNumber)) {
			vc_conn := f_comp_by_seq(g2c_ud.gtpc.sequenceNumber);
			CLIENT.send(g2c_ud.gtpc) to vc_conn;
		}else if (isvalue(imsi_t) and f_imsi_known(valueof(imsi_t))) {
			vc_conn := f_comp_by_imsi(valueof(imsi_t));
			CLIENT.send(g2c_ud.gtpc) to vc_conn;
		} else if ((ispresent(g2c_ud.gtpc.tEID) and g2c_ud.gtpc.tEID != '00000000'O)
			   and f_teic_known(g2c_ud.gtpc.tEID)) {
			vc_conn := f_comp_by_teic(g2c_ud.gtpc.tEID);
			CLIENT.send(g2c_ud.gtpc) to vc_conn;
		} else if ((not ispresent(g2c_ud.gtpc.tEID) or g2c_ud.gtpc.tEID == '00000000'O)
			   and f_teic_known('00000000'O)) {
			vc_conn := f_comp_by_teic(g2c_ud.gtpc.tEID);
			CLIENT.send(g2c_ud.gtpc) to vc_conn;
		} else {
			SendToUdMsgTable(g2c_ud);
			if (not ispresent(g2c_ud.gtpc.tEID) or g2c_ud.gtpc.tEID == '00000000'O) {
				TEIC0.send(g2c_ud.gtpc);
			}
		}

		/* remove sequence number if response was received */
		if (match(g2c_ud.gtpc, tr_PDU_GTP2C_msgtypes(gtp2_responses))) {
			f_seq_tbl_del(g2c_ud.gtpc.sequenceNumber);
		}

		}

	[] TEIC0.receive(PDU_GTPCv2:?) -> value g2c sender vc_conn {
		/* patch in the next sequence number on outbound Initial message */
		if (f_gtp2c_is_initial_msg(g2c)) {
			g2c.sequenceNumber := int2oct(g_c_seq_nr, 3);
			g_c_seq_nr := g_c_seq_nr + 1;
		}
		/* build Gtp2cUnitdata */
		g2c_ud := { peer := g_peer, gtpc := g2c };
		GTP2C.send(g2c_ud);
		if (match(g2c, tr_PDU_GTP2C_msgtypes(gtp2_requests))) {
			f_seq_tbl_add(g2c.sequenceNumber, vc_conn);
		}
		}

	[] CLIENT.receive(PDU_GTPCv2:?) -> value g2c sender vc_conn {
		/* patch in the next sequence number on outbound Initial message */
		if (f_gtp2c_is_initial_msg(g2c)) {
			g2c.sequenceNumber := int2oct(g_c_seq_nr, 3);
			g_c_seq_nr := g_c_seq_nr + 1;
		}
		/* build Gtp2cUnitdata */
		g2c_ud := { peer := g_peer, gtpc := g2c };
		GTP2C.send(g2c_ud);
		if (match(g2c, tr_PDU_GTP2C_msgtypes(gtp2_requests))) {
			f_seq_tbl_add(g2c.sequenceNumber, vc_conn);
		}
		}

	[] CLIENT_PROC.getcall(GTP2EM_register_imsi:{?}) -> param(imsi) sender vc_conn {
		f_imsi_tbl_add(imsi, vc_conn);
		CLIENT_PROC.reply(GTP2EM_register_imsi:{imsi}) to vc_conn;
		}
	[] CLIENT_PROC.getcall(GTP2EM_register_udmsg:{?}) -> param(messageType) sender vc_conn {
		f_udmsg_tbl_add(messageType, vc_conn);
		CLIENT_PROC.reply(GTP2EM_register_udmsg:{messageType}) to vc_conn;
		}

	[] CLIENT_PROC.getcall(GTP2EM_register_teic:{?}) -> param(teic) sender vc_conn {
		f_tid_tbl_add(teic, vc_conn);
		CLIENT_PROC.reply(GTP2EM_register_teic:{teic}) to vc_conn;
		}
	[] CLIENT_PROC.getcall(GTP2EM_allocate_teic:{}) -> sender vc_conn {
		var OCT4 t := f_alloc_teic();
		f_tid_tbl_add(t, vc_conn);
		CLIENT_PROC.reply(GTP2EM_allocate_teic:{} value t) to vc_conn;
		}
	}
	}
}


/***********************************************************************
 * Interaction between Main and Client Components
 ***********************************************************************/
type port GTP2EM_PT message {
	inout PDU_GTPCv2;
} with { extension "internal" };

signature GTP2EM_register_imsi(hexstring imsi);
signature GTP2EM_register_udmsg(OCT1 messageType);
signature GTP2EM_register_teic(OCT4 teic);
signature GTP2EM_allocate_teic() return OCT4;

type port GTP2EM_PROC_PT procedure {
	inout GTP2EM_register_imsi, GTP2EM_register_udmsg,
	      GTP2EM_register_teic, GTP2EM_allocate_teic;
} with { extension "internal" };

/***********************************************************************
 * Client Component
 ***********************************************************************/

type component GTP2_ConnHdlr {
	port GTP2EM_PT GTP2;
	port GTP2EM_PROC_PT GTP2_PROC;
};

function f_gtp2_register_imsi(hexstring imsi) runs on GTP2_ConnHdlr {
	/* 15-digit IMSIs are len(imsi)=15, but decoded messages are
	 * octet-aligned, hence the hexstring in messages is len(imsi)=16, where
	 * the last hex char is a padding 'F'H */
	imsi := f_pad_bcd_number(imsi);
	GTP2_PROC.call(GTP2EM_register_imsi:{imsi}) {
		[] GTP2_PROC.getreply(GTP2EM_register_imsi:{imsi});
	}
}

function f_gtp2_register_udmsg(OCT1 messageType) runs on GTP2_ConnHdlr {
	GTP2_PROC.call(GTP2EM_register_udmsg:{messageType}) {
		[] GTP2_PROC.getreply(GTP2EM_register_udmsg:{messageType});
	}
}

function f_gtp2_register_teic(OCT4 teic) runs on GTP2_ConnHdlr {
	GTP2_PROC.call(GTP2EM_register_teic:{teic}) {
		[] GTP2_PROC.getreply(GTP2EM_register_teic:{teic});
	}
}

function f_gtp2_allocate_teic() runs on GTP2_ConnHdlr return OCT4 {
	var OCT4 t;
	GTP2_PROC.call(GTP2EM_allocate_teic:{}) {
		[] GTP2_PROC.getreply(GTP2EM_allocate_teic:{}) -> value t {
			return t;
		}
	}
}
}
