laforge has submitted this change and it was merged. ( https://gerrit.osmocom.org/c/osmo-ttcn3-hacks/+/15196 )
Change subject: library: Add S1AP CodecPort/Emulation ...................................................................... library: Add S1AP CodecPort/Emulation Change-Id: I9bfba3ab2a3830e590b203c44c03b9c9383fff99 --- A library/S1AP_CodecPort.ttcn A library/S1AP_CodecPort_CtrlFunct.ttcn A library/S1AP_CodecPort_CtrlFunctDef.cc A library/S1AP_Emulation.ttcn M mme/gen_links.sh M mme/regen_makefile.sh 6 files changed, 871 insertions(+), 1 deletion(-) Approvals: laforge: Looks good to me, approved pespin: Looks good to me, but someone else must approve Jenkins Builder: Verified diff --git a/library/S1AP_CodecPort.ttcn b/library/S1AP_CodecPort.ttcn new file mode 100644 index 0000000..59cef18 --- /dev/null +++ b/library/S1AP_CodecPort.ttcn @@ -0,0 +1,82 @@ +module S1AP_CodecPort { + +/* Simple S1AP Codec Port, translating between raw SCTP primitives with + * octetstring payload towards the IPL4asp provider, and S1AP primitives + * which carry the decoded S1AP data types as payload. + * + * (C) 2019 by Harald Welte <lafo...@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_PortType all; + import from IPL4asp_Types all; + import from S1AP_PDU_Descriptions all; + import from S1AP_Types all; + + type record S1AP_RecvFrom { + ConnectionId connId, + HostName remName, + PortNumber remPort, + HostName locName, + PortNumber locPort, + S1AP_PDU msg + }; + + template S1AP_RecvFrom t_S1AP_RecvFrom(template S1AP_PDU msg) := { + connId := ?, + remName := ?, + remPort := ?, + locName := ?, + locPort := ?, + msg := msg + } + + type record S1AP_Send { + ConnectionId connId, + S1AP_PDU msg + } + + template S1AP_Send t_S1AP_Send(template ConnectionId connId, template S1AP_PDU msg) := { + connId := connId, + msg := msg + } + + private function IPL4_to_S1AP_RecvFrom(in ASP_RecvFrom pin, out S1AP_RecvFrom pout) { + pout.connId := pin.connId; + pout.remName := pin.remName; + pout.remPort := pin.remPort; + pout.locName := pin.locName; + pout.locPort := pin.locPort; + pout.msg := dec_S1AP_PDU(pin.msg); + } with { extension "prototype(fast)" }; + + private function S1AP_to_IPL4_Send(in S1AP_Send pin, out ASP_Send pout) { + pout.connId := pin.connId; + pout.proto := { + sctp := { + sinfo_stream := omit, + sinfo_ppid := 18, + remSocks := omit, + assocId := omit + } + }; + pout.msg := enc_S1AP_PDU(pin.msg); + } with { extension "prototype(fast)" }; + + type port S1AP_CODEC_PT message { + out S1AP_Send; + in S1AP_RecvFrom, + ASP_ConnId_ReadyToRelease, + ASP_Event; + } with { extension "user IPL4asp_PT + out(S1AP_Send -> ASP_Send:function(S1AP_to_IPL4_Send)) + in(ASP_RecvFrom -> S1AP_RecvFrom: function(IPL4_to_S1AP_RecvFrom); + ASP_ConnId_ReadyToRelease -> ASP_ConnId_ReadyToRelease: simple; + ASP_Event -> ASP_Event: simple)" + } +} diff --git a/library/S1AP_CodecPort_CtrlFunct.ttcn b/library/S1AP_CodecPort_CtrlFunct.ttcn new file mode 100644 index 0000000..0399199 --- /dev/null +++ b/library/S1AP_CodecPort_CtrlFunct.ttcn @@ -0,0 +1,44 @@ +module S1AP_CodecPort_CtrlFunct { + + import from S1AP_CodecPort all; + import from IPL4asp_Types all; + + external function f_IPL4_listen( + inout S1AP_CODEC_PT portRef, + in HostName locName, + in PortNumber locPort, + in ProtoTuple proto, + in OptionList options := {} + ) return Result; + + external function f_IPL4_connect( + inout S1AP_CODEC_PT portRef, + in HostName remName, + in PortNumber remPort, + in HostName locName, + in PortNumber locPort, + in ConnectionId connId, + in ProtoTuple proto, + in OptionList options := {} + ) return Result; + + external function f_IPL4_close( + inout S1AP_CODEC_PT portRef, + in ConnectionId id, + in ProtoTuple proto := { unspecified := {} } + ) return Result; + + external function f_IPL4_setUserData( + inout S1AP_CODEC_PT portRef, + in ConnectionId id, + in UserData userData + ) return Result; + + external function f_IPL4_getUserData( + inout S1AP_CODEC_PT portRef, + in ConnectionId id, + out UserData userData + ) return Result; + +} + diff --git a/library/S1AP_CodecPort_CtrlFunctDef.cc b/library/S1AP_CodecPort_CtrlFunctDef.cc new file mode 100644 index 0000000..dc73046 --- /dev/null +++ b/library/S1AP_CodecPort_CtrlFunctDef.cc @@ -0,0 +1,56 @@ +#include "IPL4asp_PortType.hh" +#include "S1AP_CodecPort.hh" +#include "IPL4asp_PT.hh" + +namespace S1AP__CodecPort__CtrlFunct { + + IPL4asp__Types::Result f__IPL4__listen( + S1AP__CodecPort::S1AP__CODEC__PT& portRef, + const IPL4asp__Types::HostName& locName, + const IPL4asp__Types::PortNumber& locPort, + const IPL4asp__Types::ProtoTuple& proto, + const IPL4asp__Types::OptionList& options) + { + return f__IPL4__PROVIDER__listen(portRef, locName, locPort, proto, options); + } + + IPL4asp__Types::Result f__IPL4__connect( + S1AP__CodecPort::S1AP__CODEC__PT& portRef, + const IPL4asp__Types::HostName& remName, + const IPL4asp__Types::PortNumber& remPort, + const IPL4asp__Types::HostName& locName, + const IPL4asp__Types::PortNumber& locPort, + const IPL4asp__Types::ConnectionId& connId, + const IPL4asp__Types::ProtoTuple& proto, + const IPL4asp__Types::OptionList& options) + { + return f__IPL4__PROVIDER__connect(portRef, remName, remPort, + locName, locPort, connId, proto, options); + } + + IPL4asp__Types::Result f__IPL4__close( + S1AP__CodecPort::S1AP__CODEC__PT& portRef, + const IPL4asp__Types::ConnectionId& connId, + const IPL4asp__Types::ProtoTuple& proto) + { + return f__IPL4__PROVIDER__close(portRef, connId, proto); + } + + IPL4asp__Types::Result f__IPL4__setUserData( + S1AP__CodecPort::S1AP__CODEC__PT& portRef, + const IPL4asp__Types::ConnectionId& connId, + const IPL4asp__Types::UserData& userData) + { + return f__IPL4__PROVIDER__setUserData(portRef, connId, userData); + } + + IPL4asp__Types::Result f__IPL4__getUserData( + S1AP__CodecPort::S1AP__CODEC__PT& portRef, + const IPL4asp__Types::ConnectionId& connId, + IPL4asp__Types::UserData& userData) + { + return f__IPL4__PROVIDER__getUserData(portRef, connId, userData); + } + +} + diff --git a/library/S1AP_Emulation.ttcn b/library/S1AP_Emulation.ttcn new file mode 100644 index 0000000..d09b499 --- /dev/null +++ b/library/S1AP_Emulation.ttcn @@ -0,0 +1,681 @@ +module S1AP_Emulation { + +/* S1AP Emulation, runs on top of S1AP_CodecPort. It multiplexes/demultiplexes + * the individual IMSIs/subscribers, so there can be separate TTCN-3 components handling + * each of them. + * + * The S1AP_Emulation.main() function processes S1AP primitives from the S1AP + * socket via the S1AP_CodecPort, and dispatches them to the per-IMSI components. + * + * For each new IMSI, the S1apOps.create_cb() is called. It can create + * or resolve a TTCN-3 component, and returns a component reference to which that IMSI + * is routed/dispatched. + * + * If a pre-existing component wants to register to handle a future inbound IMSI, it can + * do so by registering an "expect" with the expected IMSI. + * + * Inbound non-UE related S1AP messages (such as RESET, SETUP, OVERLOAD) are dispatched to + * the S1apOps.unitdata_cb() callback, which is registered with an argument to the + * main() function below. + * + * (C) 2019 by Harald Welte <lafo...@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 S1AP_CodecPort all; +import from S1AP_CodecPort_CtrlFunct all; +import from S1AP_Types all; +import from S1AP_Constants all; +import from S1AP_PDU_Contents all; +import from S1AP_PDU_Descriptions all; +import from S1AP_IEs all; +import from S1AP_Templates all; + +import from NAS_EPS_Types all; +import from NAS_Templates all; + +import from LTE_CryptoFunctions all; + +import from General_Types all; +import from Osmocom_Types all; +import from IPL4asp_Types all; +import from DNS_Helpers all; + + +type component S1AP_ConnHdlr { + port S1AP_Conn_PT S1AP; + /* procedure based port to register for incoming connections */ + port S1APEM_PROC_PT S1AP_PROC; +} + +/* port between individual per-connection components and this dispatcher */ +type port S1AP_Conn_PT message { + inout S1AP_PDU, PDU_NAS_EPS, S1APEM_Config; +} with { extension "internal" }; + +type record NAS_Keys { + octetstring k_nas_int, + octetstring k_nas_enc +}; +type union S1APEM_Config { + NAS_Keys set_nas_keys +}; + +type enumerated S1APEM_EventUpDown { + S1APEM_EVENT_DOWN, + S1APEM_EVENT_UP +} + +/* an event indicating us whether or not a connection is physically up or down, + * and whether we have received an ID_ACK */ +type union S1APEM_Event { + S1APEM_EventUpDown up_down +} + +/* global test port e.g. for non-imsi/conn specific messages */ +type port S1AP_PT message { + inout S1AP_PDU, S1APEM_Event; +} with { extension "internal" }; + + +/* represents a single S1AP Association */ +type record AssociationData { + S1AP_ConnHdlr comp_ref, /* component handling this UE connection */ + uint24_t enb_ue_s1ap_id optional, /* eNB side S1AP ID */ + uint32_t mme_ue_s1ap_id optional, /* MME side S1AP ID */ + EUTRAN_CGI cgi optional, + TAI tai optional, + NAS_UE_State nus + + //hexstring imsi optional +}; + +type component S1AP_Emulation_CT { + /* Port facing to the UDP SUT */ + port S1AP_CODEC_PT S1AP; + /* All S1AP_ConnHdlr S1AP ports connect here + * S1AP_Emulation_CT.main needs to figure out what messages + * to send where with CLIENT.send() to vc_conn */ + port S1AP_Conn_PT S1AP_CLIENT; + /* currently tracked connections */ + var AssociationData S1apAssociationTable[16]; + /* pending expected CRCX */ + var ExpectData S1apExpectTable[8]; + /* procedure based port to register for incoming connections */ + port S1APEM_PROC_PT S1AP_PROC; + /* test port for unit data messages */ + port S1AP_PT S1AP_UNIT; + + var S1AP_conn_parameters g_pars; + var charstring g_s1ap_id; + var integer g_s1ap_conn_id := -1; +} + +type function S1APCreateCallback(S1AP_PDU msg, template (omit) MME_UE_S1AP_ID mme_id, + template (omit) ENB_UE_S1AP_ID enb_id, charstring id) +runs on S1AP_Emulation_CT return S1AP_ConnHdlr; + +type function S1APUnitdataCallback(S1AP_PDU msg) +runs on S1AP_Emulation_CT return template S1AP_PDU; + +type record S1APOps { + S1APCreateCallback create_cb, + S1APUnitdataCallback unitdata_cb +} + +type record S1AP_conn_parameters { + HostName remote_ip, + PortNumber remote_sctp_port, + HostName local_ip, + PortNumber local_sctp_port, + NAS_Role role +} + +function tr_S1AP_RecvFrom_R(template S1AP_PDU msg) +runs on S1AP_Emulation_CT return template S1AP_RecvFrom { + var template S1AP_RecvFrom mrf := { + connId := g_s1ap_conn_id, + remName := ?, + remPort := ?, + locName := ?, + locPort := ?, + msg := msg + } + return mrf; +} + +private function f_s1ap_ids_known(template (omit) MME_UE_S1AP_ID mme_id, + template (omit) ENB_UE_S1AP_ID enb_id) +runs on S1AP_Emulation_CT return boolean { + var integer i; + log("f_s1ap_ids_known(",mme_id,", ",enb_id,")"); + for (i := 0; i < sizeof(S1apAssociationTable); i := i+1) { + log("tbl[",i,"]: mme=", S1apAssociationTable[i].mme_ue_s1ap_id, + ", enb=", S1apAssociationTable[i].enb_ue_s1ap_id); + /* skip empty records */ + if (S1apAssociationTable[i].mme_ue_s1ap_id == omit and + S1apAssociationTable[i].enb_ue_s1ap_id == omit) { + log("skipping empty ", i); + continue; + } + if (S1apAssociationTable[i].mme_ue_s1ap_id == omit) { + log("entry ", i, " has no MME ID yet (enb=", S1apAssociationTable[i].enb_ue_s1ap_id); + /* Table doesn't yet know the MME side ID, let's look-up only + * based on the eNB side ID */ + if (match(S1apAssociationTable[i].enb_ue_s1ap_id, enb_id)) { + /* update table with MME side ID */ + S1apAssociationTable[i].mme_ue_s1ap_id := valueof(mme_id); + return true; + } + } else if (match(S1apAssociationTable[i].enb_ue_s1ap_id, enb_id) and + match(S1apAssociationTable[i].mme_ue_s1ap_id, mme_id)) { + return true; + } + } + return false; +} + +private function f_comp_known(S1AP_ConnHdlr client) +runs on S1AP_Emulation_CT return boolean { + var integer i; + for (i := 0; i < sizeof(S1apAssociationTable); i := i+1) { + if (S1apAssociationTable[i].comp_ref == client) { + return true; + } + } + return false; +} + +private function f_assoc_id_by_s1ap_ids(template (omit) MME_UE_S1AP_ID mme_id, + template (omit) ENB_UE_S1AP_ID enb_id) +runs on S1AP_Emulation_CT return integer { + var integer i; + for (i := 0; i < sizeof(S1apAssociationTable); i := i+1) { + if (match(S1apAssociationTable[i].enb_ue_s1ap_id, enb_id)) { + if (istemplatekind(mme_id, "omit")) { + return i; + } else { + if (match(S1apAssociationTable[i].mme_ue_s1ap_id, mme_id)) { + return i; + } + } + } + } + setverdict(fail, "S1AP Association Table not found by ENB-ID=", enb_id, " MME-ID=", mme_id); + mtc.stop; +} + +private function f_assoc_id_by_comp(S1AP_ConnHdlr client) +runs on S1AP_Emulation_CT return integer { + var integer i; + for (i := 0; i < sizeof(S1apAssociationTable); i := i+1) { + if (S1apAssociationTable[i].comp_ref == client) { + return i; + } + } + setverdict(fail, "S1AP Association Table not found by component ", client); + mtc.stop; +} + +private function f_assoc_by_comp(S1AP_ConnHdlr client) +runs on S1AP_Emulation_CT return AssociationData { + var integer i := f_assoc_id_by_comp(client); + return S1apAssociationTable[i]; +} + +private function f_s1ap_id_table_add(S1AP_ConnHdlr comp_ref, + template (omit) MME_UE_S1AP_ID mme_id, ENB_UE_S1AP_ID enb_id) +runs on S1AP_Emulation_CT return integer { + var integer i; + for (i := 0; i < sizeof(S1apAssociationTable); i := i+1) { + if (not isvalue(S1apAssociationTable[i].enb_ue_s1ap_id)) { + S1apAssociationTable[i].enb_ue_s1ap_id := enb_id; + if (istemplatekind(mme_id, "omit")) { + S1apAssociationTable[i].mme_ue_s1ap_id := omit; + } else { + S1apAssociationTable[i].mme_ue_s1ap_id := valueof(mme_id); + } + S1apAssociationTable[i].comp_ref := comp_ref; + return i; + } + } + testcase.stop("S1AP Association Table full!"); + return -1; +} + +private function f_s1ap_id_table_del(S1AP_ConnHdlr comp_ref, ENB_UE_S1AP_ID enb_id) +runs on S1AP_Emulation_CT { + var integer i; + for (i := 0; i < sizeof(S1apAssociationTable); i := i+1) { + if (S1apAssociationTable[i].comp_ref == comp_ref and + S1apAssociationTable[i].mme_ue_s1ap_id == enb_id) { + S1apAssociationTable[i].enb_ue_s1ap_id := omit; + S1apAssociationTable[i].mme_ue_s1ap_id := omit; + S1apAssociationTable[i].comp_ref := null; + return; + } + } + setverdict(fail, "S1AP Association Table: Couldn't find to-be-deleted entry!"); + mtc.stop; +} + + +private function f_s1ap_id_table_init() +runs on S1AP_Emulation_CT { + for (var integer i := 0; i < sizeof(S1apAssociationTable); i := i+1) { + S1apAssociationTable[i].mme_ue_s1ap_id := omit; + S1apAssociationTable[i].enb_ue_s1ap_id := omit; + S1apAssociationTable[i].cgi := omit; + S1apAssociationTable[i].tai := omit; + S1apAssociationTable[i].nus := valueof(t_NAS_UE_State(g_pars.role)); + } +} + +private template (value) SctpTuple ts_SCTP(template (omit) integer ppid := 18) := { + sinfo_stream := omit, + sinfo_ppid := ppid, + remSocks := omit, + assocId := omit +}; + +private template PortEvent tr_SctpAssocChange := { + sctpEvent := { + sctpAssocChange := ? + } +} +private template PortEvent tr_SctpPeerAddrChange := { + sctpEvent := { + sctpPeerAddrChange := ? + } +} + +private function f_s1ap_xceive(template (value) S1AP_PDU tx, + template S1AP_PDU rx_t := ?) +runs on S1AP_Emulation_CT return S1AP_PDU { + timer T := 10.0; + var S1AP_RecvFrom mrf; + + S1AP.send(t_S1AP_Send(g_s1ap_conn_id, tx)); + alt { + [] S1AP.receive(tr_S1AP_RecvFrom_R(rx_t)) -> value mrf { } + [] S1AP.receive(tr_SctpAssocChange) { repeat; } + [] S1AP.receive(tr_SctpPeerAddrChange) { repeat; } + [] T.timeout { + setverdict(fail, "Timeout waiting for ", rx_t); + mtc.stop; + } + } + return mrf.msg; +} + +/* +private function f_nas_try_decaps(PDU_NAS_EPS nas) return PDU_NAS_EPS +{ + var PDU_NAS_EPS_SecurityProtectedNASMessage secp_nas; + if (not match(nas, tr_NAS_EMM_SecurityProtected)) { + return nas; + } + secp_nas := nas.ePS_messages.ePS_MobilityManagement.pDU_NAS_EPS_SecurityProtectedNASMessage; + select (secp_nas.securityHeaderType) { + case ('0011'B) { + var octetstring knas_int := '530ce32318f26264eab26bc116870b86'O; + var octetstring data_with_seq := int2oct(secp_nas.sequenceNumber, 1) & secp_nas.nAS_Message; + var OCT4 exp_mac := f_snow_3g_f9(knas_int, secp_nas.sequenceNumber, 0, + is_downlink:=true, data:=data_with_seq); + if (exp_mac != secp_nas.messageAuthenticationCode) { + setverdict(fail, "Received NAS MAC ", secp_nas.messageAuthenticationCode, + " doesn't match expected MAC ", exp_mac, ": ", nas); + mtc.stop; + } + return dec_PDU_NAS_EPS(secp_nas.nAS_Message); + } + case else { + setverdict(fail, "Implement SecHdrType for ", secp_nas); + mtc.stop; + } + } +} +*/ + +function main(S1APOps ops, S1AP_conn_parameters p, charstring id) runs on S1AP_Emulation_CT { + var Result res; + g_pars := p; + g_s1ap_id := id; + f_s1ap_id_table_init(); + f_expect_table_init(); + + map(self:S1AP, system:S1AP_CODEC_PT); + if (p.remote_sctp_port == -1) { + res := S1AP_CodecPort_CtrlFunct.f_IPL4_listen(S1AP, p.local_ip, p.local_sctp_port, { sctp := valueof(ts_SCTP) }); + } else { + res := S1AP_CodecPort_CtrlFunct.f_IPL4_connect(S1AP, p.remote_ip, p.remote_sctp_port, + p.local_ip, p.local_sctp_port, -1, { sctp := valueof(ts_SCTP) }); + } + if (not ispresent(res.connId)) { + setverdict(fail, "Could not connect S1AP socket, check your configuration"); + mtc.stop; + } + g_s1ap_conn_id := res.connId; + + /* notify user about SCTP establishment */ + if (p.remote_sctp_port != -1) { + S1AP_UNIT.send(S1APEM_Event:{up_down:=S1APEM_EVENT_UP}) + } + + while (true) { + var S1AP_ConnHdlr vc_conn; + var PDU_NAS_EPS nas; + var hexstring imsi; + var S1AP_RecvFrom mrf; + var S1AP_PDU msg; + var S1APEM_Config s1cfg; + var charstring vlr_name, mme_name; + var integer ai; + + alt { + /* Configuration primitive from client */ + [] S1AP_CLIENT.receive(S1APEM_Config:{set_nas_keys:=?}) -> value s1cfg sender vc_conn { + var integer assoc_id := f_assoc_id_by_comp(vc_conn); + S1apAssociationTable[assoc_id].nus.k_nas_int := s1cfg.set_nas_keys.k_nas_int; + S1apAssociationTable[assoc_id].nus.k_nas_enc := s1cfg.set_nas_keys.k_nas_enc; + } + /* S1AP from client: InitialUE */ + [] S1AP_CLIENT.receive(tr_S1AP_InitialUE) -> value msg sender vc_conn { + /* create a table entry about this connection */ + ai := f_s1ap_id_table_add(vc_conn, omit, valueof(f_S1AP_get_ENB_UE_S1AP_ID(msg))); + /* Store CGI + TAI so we can use it for generating UlNasTransport from NAS */ + S1apAssociationTable[ai].tai := msg.initiatingMessage.value_.InitialUEMessage.protocolIEs[2].value_.TAI; + S1apAssociationTable[ai].cgi := msg.initiatingMessage.value_.InitialUEMessage.protocolIEs[3].value_.EUTRAN_CGI; + /* Pass message through */ + S1AP.send(t_S1AP_Send(g_s1ap_conn_id, msg)); + } + /* NAS from client: Wrap in S1AP Uplink NAS Transport */ + [] S1AP_CLIENT.receive(PDU_NAS_EPS:?) -> value nas sender vc_conn { + var integer assoc_id := f_assoc_id_by_comp(vc_conn); + var AssociationData ad := S1apAssociationTable[assoc_id]; + nas := f_nas_encaps(S1apAssociationTable[assoc_id].nus, nas, new_ctx := false); + var octetstring nas_enc := enc_PDU_NAS_EPS(nas); + S1AP.send(t_S1AP_Send(g_s1ap_conn_id, + ts_S1AP_UlNasTransport(ad.mme_ue_s1ap_id, + ad.enb_ue_s1ap_id, + nas_enc, ad.cgi, ad.tai))); + } + /* S1AP from client: pass on transparently */ + [] S1AP_CLIENT.receive(S1AP_PDU:?) -> value msg sender vc_conn { + /* Pass message through */ + /* FIXME: validate S1AP_IDs ? */ + S1AP.send(t_S1AP_Send(g_s1ap_conn_id, msg)); + } + + /* non-UE related S1AP: pass through unmodified/unverified */ + [] S1AP_UNIT.receive(S1AP_PDU:?) -> value msg sender vc_conn { + /* Pass message through */ + S1AP.send(t_S1AP_Send(g_s1ap_conn_id, msg)); + } + + /* S1AP received from peer (MME) */ + [] S1AP.receive(tr_S1AP_RecvFrom_R(?)) -> value mrf { + if (match(mrf.msg, tr_S1AP_nonUErelated)) { + /* non-UE-related S1AP message */ + var template S1AP_PDU resp := ops.unitdata_cb.apply(mrf.msg); + if (isvalue(resp)) { + S1AP.send(t_S1AP_Send(g_s1ap_conn_id, valueof(resp))); + } + } else if (match(mrf.msg, tr_S1AP_UeContextReleaseCmd)) { + /* TODO: special handling, as it contains multiple eNB or MME IDs */ + setverdict(fail, "implement UeContextReleaseCmd handling"); + mtc.stop; + } else { + /* Ue-related S1AP message */ + /* obtain MME + ENB UE S1AP ID */ + var template (omit) MME_UE_S1AP_ID mme_ue_id := f_S1AP_get_MME_UE_S1AP_ID(mrf.msg); + var template (omit) ENB_UE_S1AP_ID enb_ue_id := f_S1AP_get_ENB_UE_S1AP_ID(mrf.msg); + /* check if those IDs are known in our table */ + if (f_s1ap_ids_known(mme_ue_id, enb_ue_id)) { + /* if yes, dispatch to the ConnHdlr for this Ue-Connection */ + var template (omit) octetstring nas_enc; + var integer assoc_id := f_assoc_id_by_s1ap_ids(mme_ue_id, enb_ue_id); + vc_conn := S1apAssociationTable[assoc_id].comp_ref; + nas_enc := f_S1AP_get_NAS_PDU(mrf.msg); + if (isvalue(nas_enc)) { + nas := dec_PDU_NAS_EPS(valueof(nas_enc)); + if (match(nas, tr_NAS_EMM_SecurityProtected)) { + nas := f_nas_try_decaps(S1apAssociationTable[assoc_id].nus, nas); + } + /* send decoded NAS */ + S1AP_CLIENT.send(nas) to vc_conn; + } else { + /* send raw S1AP */ + S1AP_CLIENT.send(mrf.msg) to vc_conn; + } + } else { + /* if not, call create_cb so it can create new ConnHdlr */ + vc_conn := ops.create_cb.apply(mrf.msg, mme_ue_id, enb_ue_id, id); + f_s1ap_id_table_add(vc_conn, mme_ue_id, valueof(enb_ue_id)); + S1AP_CLIENT.send(mrf.msg) to vc_conn; + } + } + } + [] S1AP.receive(tr_SctpAssocChange) { } + [] S1AP.receive(tr_SctpPeerAddrChange) { } + [] S1AP_PROC.getcall(S1APEM_register:{?,?}) -> param(imsi, vc_conn) { + f_create_expect(imsi, vc_conn); + S1AP_PROC.reply(S1APEM_register:{imsi, vc_conn}) to vc_conn; + } + } + + } +} + +/* "Expect" Handling */ + +type record ExpectData { + hexstring imsi optional, + S1AP_ConnHdlr vc_conn +} + +signature S1APEM_register(in hexstring imsi, in S1AP_ConnHdlr hdlr); + +type port S1APEM_PROC_PT procedure { + inout S1APEM_register; +} with { extension "internal" }; + +/* Function that can be used as create_cb and will usse the expect table */ +function ExpectedCreateCallback(S1AP_PDU msg, hexstring imsi, charstring id) +runs on S1AP_Emulation_CT return S1AP_ConnHdlr { + var S1AP_ConnHdlr ret := null; + var integer i; + + for (i := 0; i < sizeof(S1apExpectTable); i := i+1) { + if (not ispresent(S1apExpectTable[i].imsi)) { + continue; + } + if (imsi == S1apExpectTable[i].imsi) { + ret := S1apExpectTable[i].vc_conn; + /* Release this entry */ + S1apExpectTable[i].imsi := omit; + S1apExpectTable[i].vc_conn := null; + log("Found Expect[", i, "] for ", msg, " handled at ", ret); + return ret; + } + } + setverdict(fail, "Couldn't find Expect for ", msg); + mtc.stop; +} + +private function f_create_expect(hexstring imsi, S1AP_ConnHdlr hdlr) +runs on S1AP_Emulation_CT { + var integer i; + + /* Check an entry like this is not already presnt */ + for (i := 0; i < sizeof(S1apExpectTable); i := i+1) { + if (imsi == S1apExpectTable[i].imsi) { + setverdict(fail, "IMSI already present", imsi); + mtc.stop; + } + } + for (i := 0; i < sizeof(S1apExpectTable); i := i+1) { + if (not ispresent(S1apExpectTable[i].imsi)) { + S1apExpectTable[i].imsi := imsi; + S1apExpectTable[i].vc_conn := hdlr; + log("Created Expect[", i, "] for ", imsi, " to be handled at ", hdlr); + return; + } + } + testcase.stop("No space left in S1apExpectTable") +} + +/* client/conn_hdlr side function to use procedure port to create expect in emulation */ +function f_create_s1ap_expect(hexstring imsi) runs on S1AP_ConnHdlr { + S1AP_PROC.call(S1APEM_register:{imsi, self}) { + [] S1AP_PROC.getreply(S1APEM_register:{?,?}) {}; + } +} + + +private function f_expect_table_init() +runs on S1AP_Emulation_CT { + var integer i; + for (i := 0; i < sizeof(S1apExpectTable); i := i + 1) { + S1apExpectTable[i].imsi := omit; + } +} + +function DummyUnitdataCallback(S1AP_PDU msg) +runs on S1AP_Emulation_CT return template S1AP_PDU { + log("Ignoring S1AP ", msg); + return omit; +} + + +function f_S1AP_get_ENB_UE_S1AP_ID(S1AP_PDU s1ap) return template (omit) ENB_UE_S1AP_ID +{ + if (ischosen(s1ap.initiatingMessage)) { + var InitiatingMessage im := s1ap.initiatingMessage; + select (s1ap) { + case (tr_S1AP_InitialUE) { + return im.value_.InitialUEMessage.protocolIEs[0].value_.ENB_UE_S1AP_ID; + } + case (tr_S1AP_DlNasTransport) { + return im.value_.DownlinkNASTransport.protocolIEs[1].value_.ENB_UE_S1AP_ID; + } + case (tr_S1AP_UlNasTransport) { + return im.value_.UplinkNASTransport.protocolIEs[1].value_.ENB_UE_S1AP_ID; + } + case (tr_S1AP_IntialCtxSetupReq) { + return im.value_.initialContextSetupRequest.protocolIEs[1].value_.ENB_UE_S1AP_ID; + } + case (tr_S1AP_UeContextReleaseReq) { + return im.value_.UEContextReleaseRequest.protocolIEs[1].value_.ENB_UE_S1AP_ID; + } + /* UeContextReleaseCmd needs special handling; it can contain any number of MME/UE IDs */ + case (tr_S1AP_ConnEstInd) { + return im.value_.ConnectionEstablishmentIndication.protocolIEs[1].value_.ENB_UE_S1AP_ID; + } + /* TODO */ + } + } else if (ischosen(s1ap.successfulOutcome)) { + var SuccessfulOutcome so := s1ap.successfulOutcome; + select (s1ap) { + case (tr_S1AP_InitialCtxSetupResp) { + return so.value_.initialContextSetupResponse.protocolIEs[1].value_.ENB_UE_S1AP_ID; + } + case (tr_S1AP_UeContextReleaseCompl) { + return so.value_.UEContextReleaseComplete.protocolIEs[1].value_.ENB_UE_S1AP_ID; + } + /* TODO */ + } + } else if (ischosen(s1ap.unsuccessfulOutcome)) { + var UnsuccessfulOutcome uo := s1ap.unsuccessfulOutcome; + select (s1ap) { + case (tr_S1AP_InitialCtxSetupFail) { + return uo.value_.initialContextSetupFailure.protocolIEs[1].value_.ENB_UE_S1AP_ID; + } + /* TODO */ + } + } + return omit; +} + +function f_S1AP_get_MME_UE_S1AP_ID(S1AP_PDU s1ap) return template (omit) MME_UE_S1AP_ID +{ + if (ischosen(s1ap.initiatingMessage)) { + var InitiatingMessage im := s1ap.initiatingMessage; + select (s1ap) { + case (tr_S1AP_DlNasTransport) { + return im.value_.DownlinkNASTransport.protocolIEs[0].value_.MME_UE_S1AP_ID; + } + case (tr_S1AP_UlNasTransport) { + return im.value_.UplinkNASTransport.protocolIEs[0].value_.MME_UE_S1AP_ID; + } + case (tr_S1AP_IntialCtxSetupReq) { + return im.value_.initialContextSetupRequest.protocolIEs[0].value_.MME_UE_S1AP_ID; + } + case (tr_S1AP_UeContextReleaseReq) { + return im.value_.UEContextReleaseRequest.protocolIEs[0].value_.MME_UE_S1AP_ID; + } + /* UeContextReleaseCmd needs special handling; it can contain any number of MME/UE IDs */ + case (tr_S1AP_ConnEstInd) { + return im.value_.ConnectionEstablishmentIndication.protocolIEs[0].value_.MME_UE_S1AP_ID; + } + /* TODO */ + } + } else if (ischosen(s1ap.successfulOutcome)) { + var SuccessfulOutcome so := s1ap.successfulOutcome; + select (s1ap) { + case (tr_S1AP_InitialCtxSetupResp) { + return so.value_.initialContextSetupResponse.protocolIEs[0].value_.MME_UE_S1AP_ID; + } + case (tr_S1AP_UeContextReleaseCompl) { + return so.value_.UEContextReleaseComplete.protocolIEs[0].value_.MME_UE_S1AP_ID; + } + /* TODO */ + } + } else if (ischosen(s1ap.unsuccessfulOutcome)) { + var UnsuccessfulOutcome uo := s1ap.unsuccessfulOutcome; + select (s1ap) { + case (tr_S1AP_InitialCtxSetupFail) { + return uo.value_.initialContextSetupFailure.protocolIEs[0].value_.MME_UE_S1AP_ID; + } + /* TODO */ + } + } + return omit; +} + +function f_S1AP_get_NAS_PDU(S1AP_PDU s1ap) return template (omit) NAS_PDU +{ + var integer i; + + if (ischosen(s1ap.initiatingMessage)) { + var InitiatingMessage im := s1ap.initiatingMessage; + select (s1ap) { + case (tr_S1AP_DlNasTransport) { + var DownlinkNASTransport msg := im.value_.DownlinkNASTransport; + for (i := 0; i < lengthof(msg.protocolIEs); i := i+1) { + if (msg.protocolIEs[i].id == id_NAS_PDU) { + return msg.protocolIEs[i].value_.NAS_PDU; + } + } + } + case (tr_S1AP_UlNasTransport) { + var UplinkNASTransport msg := im.value_.UplinkNASTransport; + for (i := 0; i < lengthof(msg.protocolIEs); i := i+1) { + if (msg.protocolIEs[i].id == id_NAS_PDU) { + return msg.protocolIEs[i].value_.NAS_PDU; + } + } + } + } + } + return omit; +} + + + +} diff --git a/mme/gen_links.sh b/mme/gen_links.sh index 2a98c70..1f2d85b 100755 --- a/mme/gen_links.sh +++ b/mme/gen_links.sh @@ -31,10 +31,17 @@ FILES="SGsAP_Types.ttcn" gen_links $DIR $FILES +DIR=../library/s1ap +FILES="S1AP_CommonDataTypes.asn S1AP_Constants.asn S1AP_Containers.asn S1AP_IEs.asn S1AP_PDU_Contents.asn +S1AP_PDU_Descriptions.asn " +FILES+="S1AP_EncDec.cc S1AP_Types.ttcn " +gen_links $DIR $FILES + DIR=../library FILES="Misc_Helpers.ttcn General_Types.ttcn GSM_Types.ttcn Osmocom_Types.ttcn Native_Functions.ttcn Native_FunctionDefs.cc " FILES+="SGsAP_Templates.ttcn SGsAP_CodecPort.ttcn SGsAP_CodecPort_CtrlFunct.ttcn SGsAP_CodecPort_CtrlFunctDef.cc SGsAP_Emulation.ttcn DNS_Helpers.ttcn " FILES+="L3_Templates.ttcn " +FILES+="S1AP_CodecPort.ttcn " gen_links $DIR $FILES ignore_pp_results diff --git a/mme/regen_makefile.sh b/mme/regen_makefile.sh index 4d6d7c6..ee73658 100755 --- a/mme/regen_makefile.sh +++ b/mme/regen_makefile.sh @@ -1,6 +1,6 @@ #!/bin/sh -FILES="*.ttcn IPL4asp_PT.cc IPL4asp_discovery.cc Native_FunctionDefs.cc SGsAP_CodecPort_CtrlFunctDef.cc TCCConversion.cc TCCEncoding.cc TCCInterface.cc TELNETasp_PT.cc " +FILES="*.ttcn *.asn IPL4asp_PT.cc IPL4asp_discovery.cc Native_FunctionDefs.cc SGsAP_CodecPort_CtrlFunctDef.cc TCCConversion.cc TCCEncoding.cc TCCInterface.cc TELNETasp_PT.cc S1AP_EncDec.cc " export CPPFLAGS_TTCN3="" -- To view, visit https://gerrit.osmocom.org/c/osmo-ttcn3-hacks/+/15196 To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings Gerrit-Project: osmo-ttcn3-hacks Gerrit-Branch: master Gerrit-Change-Id: I9bfba3ab2a3830e590b203c44c03b9c9383fff99 Gerrit-Change-Number: 15196 Gerrit-PatchSet: 3 Gerrit-Owner: laforge <lafo...@gnumonks.org> Gerrit-Reviewer: Jenkins Builder Gerrit-Reviewer: laforge <lafo...@gnumonks.org> Gerrit-Reviewer: pespin <pes...@sysmocom.de> Gerrit-MessageType: merged