blob: 38c738ba09d42ce5667728d79c9f54ff2dc02230 [file] [log] [blame]
Harald Welte35498112019-07-02 14:26:39 +08001module S1AP_Emulation {
2
Philipp Maier7147c922023-07-07 14:18:32 +02003/* S1AP Emulation, runs on top of S1AP_CodecPort. It multiplexes/demultiplexes
4 * the individual subscribers by their UE association (MME_UE_S1AP_ID/
5 * ENB_UE_S1AP_ID identifiers), so there can be separate TTCN-3 components
6 * handling each of them.
Harald Welte35498112019-07-02 14:26:39 +08007 *
8 * The S1AP_Emulation.main() function processes S1AP primitives from the S1AP
Philipp Maier7147c922023-07-07 14:18:32 +02009 * socket via the S1AP_CodecPort, and dispatches them to the per-subscriber
10 * components.
Harald Welte35498112019-07-02 14:26:39 +080011 *
Philipp Maier7147c922023-07-07 14:18:32 +020012 * For each new subscruber, the S1apOps.create_cb() is called. It can create
13 * or resolve a TTCN-3 component, and returns a component reference to which
14 * that subscriber traffic is routed/dispatched.
Harald Welte35498112019-07-02 14:26:39 +080015 *
Philipp Maier7147c922023-07-07 14:18:32 +020016 * If a pre-existing component wants to register to handle a future inbound UE
17 * association, it can do so by registering an "expect" with the expected
18 * MME_UE_S1AP_ID/ENB_UE_S1AP_ID identifiers.
Harald Welte35498112019-07-02 14:26:39 +080019 *
Philipp Maier7147c922023-07-07 14:18:32 +020020 * Inbound non-UE related S1AP messages (such as RESET, SETUP, OVERLOAD) are
21 * dispatched to the S1apOps.unitdata_cb() callback, which is registered with
22 * an argument to the main() function below.
Harald Welte35498112019-07-02 14:26:39 +080023 *
24 * (C) 2019 by Harald Welte <laforge@gnumonks.org>
25 * All rights reserved.
26 *
27 * Released under the terms of GNU General Public License, Version 2 or
28 * (at your option) any later version.
29 *
30 * SPDX-License-Identifier: GPL-2.0-or-later
31 */
32
33import from S1AP_CodecPort all;
34import from S1AP_CodecPort_CtrlFunct all;
35import from S1AP_Types all;
36import from S1AP_Constants all;
37import from S1AP_PDU_Contents all;
38import from S1AP_PDU_Descriptions all;
39import from S1AP_IEs all;
40import from S1AP_Templates all;
41
42import from NAS_EPS_Types all;
43import from NAS_Templates all;
44
45import from LTE_CryptoFunctions all;
46
47import from General_Types all;
48import from Osmocom_Types all;
49import from IPL4asp_Types all;
50import from DNS_Helpers all;
51
52
53type component S1AP_ConnHdlr {
54 port S1AP_Conn_PT S1AP;
55 /* procedure based port to register for incoming connections */
56 port S1APEM_PROC_PT S1AP_PROC;
57}
58
59/* port between individual per-connection components and this dispatcher */
60type port S1AP_Conn_PT message {
61 inout S1AP_PDU, PDU_NAS_EPS, S1APEM_Config;
62} with { extension "internal" };
63
64type record NAS_Keys {
65 octetstring k_nas_int,
66 octetstring k_nas_enc
67};
68type union S1APEM_Config {
69 NAS_Keys set_nas_keys
70};
71
72type enumerated S1APEM_EventUpDown {
73 S1APEM_EVENT_DOWN,
74 S1APEM_EVENT_UP
75}
76
77/* an event indicating us whether or not a connection is physically up or down,
78 * and whether we have received an ID_ACK */
79type union S1APEM_Event {
80 S1APEM_EventUpDown up_down
81}
82
83/* global test port e.g. for non-imsi/conn specific messages */
84type port S1AP_PT message {
85 inout S1AP_PDU, S1APEM_Event;
86} with { extension "internal" };
87
88
89/* represents a single S1AP Association */
90type record AssociationData {
91 S1AP_ConnHdlr comp_ref, /* component handling this UE connection */
92 uint24_t enb_ue_s1ap_id optional, /* eNB side S1AP ID */
93 uint32_t mme_ue_s1ap_id optional, /* MME side S1AP ID */
94 EUTRAN_CGI cgi optional,
95 TAI tai optional,
96 NAS_UE_State nus
Harald Welte35498112019-07-02 14:26:39 +080097};
98
99type component S1AP_Emulation_CT {
100 /* Port facing to the UDP SUT */
101 port S1AP_CODEC_PT S1AP;
102 /* All S1AP_ConnHdlr S1AP ports connect here
103 * S1AP_Emulation_CT.main needs to figure out what messages
104 * to send where with CLIENT.send() to vc_conn */
105 port S1AP_Conn_PT S1AP_CLIENT;
106 /* currently tracked connections */
107 var AssociationData S1apAssociationTable[16];
Philipp Maierf935f0c2023-07-24 17:58:24 +0200108 /* pending expected S1AP Association (UE oriented) */
Harald Welte35498112019-07-02 14:26:39 +0800109 var ExpectData S1apExpectTable[8];
110 /* procedure based port to register for incoming connections */
111 port S1APEM_PROC_PT S1AP_PROC;
112 /* test port for unit data messages */
113 port S1AP_PT S1AP_UNIT;
114
115 var S1AP_conn_parameters g_pars;
116 var charstring g_s1ap_id;
117 var integer g_s1ap_conn_id := -1;
118}
119
120type function S1APCreateCallback(S1AP_PDU msg, template (omit) MME_UE_S1AP_ID mme_id,
121 template (omit) ENB_UE_S1AP_ID enb_id, charstring id)
122runs on S1AP_Emulation_CT return S1AP_ConnHdlr;
123
124type function S1APUnitdataCallback(S1AP_PDU msg)
125runs on S1AP_Emulation_CT return template S1AP_PDU;
126
127type record S1APOps {
128 S1APCreateCallback create_cb,
129 S1APUnitdataCallback unitdata_cb
130}
131
132type record S1AP_conn_parameters {
133 HostName remote_ip,
134 PortNumber remote_sctp_port,
135 HostName local_ip,
136 PortNumber local_sctp_port,
137 NAS_Role role
138}
139
140function tr_S1AP_RecvFrom_R(template S1AP_PDU msg)
141runs on S1AP_Emulation_CT return template S1AP_RecvFrom {
142 var template S1AP_RecvFrom mrf := {
143 connId := g_s1ap_conn_id,
144 remName := ?,
145 remPort := ?,
146 locName := ?,
147 locPort := ?,
148 msg := msg
149 }
150 return mrf;
151}
152
153private function f_s1ap_ids_known(template (omit) MME_UE_S1AP_ID mme_id,
154 template (omit) ENB_UE_S1AP_ID enb_id)
155runs on S1AP_Emulation_CT return boolean {
156 var integer i;
157 log("f_s1ap_ids_known(",mme_id,", ",enb_id,")");
158 for (i := 0; i < sizeof(S1apAssociationTable); i := i+1) {
159 log("tbl[",i,"]: mme=", S1apAssociationTable[i].mme_ue_s1ap_id,
160 ", enb=", S1apAssociationTable[i].enb_ue_s1ap_id);
161 /* skip empty records */
162 if (S1apAssociationTable[i].mme_ue_s1ap_id == omit and
163 S1apAssociationTable[i].enb_ue_s1ap_id == omit) {
164 log("skipping empty ", i);
165 continue;
166 }
167 if (S1apAssociationTable[i].mme_ue_s1ap_id == omit) {
168 log("entry ", i, " has no MME ID yet (enb=", S1apAssociationTable[i].enb_ue_s1ap_id);
169 /* Table doesn't yet know the MME side ID, let's look-up only
170 * based on the eNB side ID */
171 if (match(S1apAssociationTable[i].enb_ue_s1ap_id, enb_id)) {
172 /* update table with MME side ID */
173 S1apAssociationTable[i].mme_ue_s1ap_id := valueof(mme_id);
174 return true;
175 }
176 } else if (match(S1apAssociationTable[i].enb_ue_s1ap_id, enb_id) and
177 match(S1apAssociationTable[i].mme_ue_s1ap_id, mme_id)) {
178 return true;
179 }
180 }
181 return false;
182}
183
184private function f_comp_known(S1AP_ConnHdlr client)
185runs on S1AP_Emulation_CT return boolean {
186 var integer i;
187 for (i := 0; i < sizeof(S1apAssociationTable); i := i+1) {
188 if (S1apAssociationTable[i].comp_ref == client) {
189 return true;
190 }
191 }
192 return false;
193}
194
195private function f_assoc_id_by_s1ap_ids(template (omit) MME_UE_S1AP_ID mme_id,
196 template (omit) ENB_UE_S1AP_ID enb_id)
197runs on S1AP_Emulation_CT return integer {
198 var integer i;
199 for (i := 0; i < sizeof(S1apAssociationTable); i := i+1) {
200 if (match(S1apAssociationTable[i].enb_ue_s1ap_id, enb_id)) {
201 if (istemplatekind(mme_id, "omit")) {
202 return i;
203 } else {
204 if (match(S1apAssociationTable[i].mme_ue_s1ap_id, mme_id)) {
205 return i;
206 }
207 }
208 }
209 }
210 setverdict(fail, "S1AP Association Table not found by ENB-ID=", enb_id, " MME-ID=", mme_id);
211 mtc.stop;
212}
213
214private function f_assoc_id_by_comp(S1AP_ConnHdlr client)
215runs on S1AP_Emulation_CT return integer {
216 var integer i;
217 for (i := 0; i < sizeof(S1apAssociationTable); i := i+1) {
218 if (S1apAssociationTable[i].comp_ref == client) {
219 return i;
220 }
221 }
222 setverdict(fail, "S1AP Association Table not found by component ", client);
223 mtc.stop;
224}
225
226private function f_assoc_by_comp(S1AP_ConnHdlr client)
227runs on S1AP_Emulation_CT return AssociationData {
228 var integer i := f_assoc_id_by_comp(client);
229 return S1apAssociationTable[i];
230}
231
232private function f_s1ap_id_table_add(S1AP_ConnHdlr comp_ref,
233 template (omit) MME_UE_S1AP_ID mme_id, ENB_UE_S1AP_ID enb_id)
234runs on S1AP_Emulation_CT return integer {
235 var integer i;
236 for (i := 0; i < sizeof(S1apAssociationTable); i := i+1) {
237 if (not isvalue(S1apAssociationTable[i].enb_ue_s1ap_id)) {
238 S1apAssociationTable[i].enb_ue_s1ap_id := enb_id;
239 if (istemplatekind(mme_id, "omit")) {
240 S1apAssociationTable[i].mme_ue_s1ap_id := omit;
241 } else {
242 S1apAssociationTable[i].mme_ue_s1ap_id := valueof(mme_id);
243 }
244 S1apAssociationTable[i].comp_ref := comp_ref;
245 return i;
246 }
247 }
248 testcase.stop("S1AP Association Table full!");
249 return -1;
250}
251
252private function f_s1ap_id_table_del(S1AP_ConnHdlr comp_ref, ENB_UE_S1AP_ID enb_id)
253runs on S1AP_Emulation_CT {
254 var integer i;
255 for (i := 0; i < sizeof(S1apAssociationTable); i := i+1) {
256 if (S1apAssociationTable[i].comp_ref == comp_ref and
Philipp Maier0ce67ab2023-07-07 13:02:11 +0200257 S1apAssociationTable[i].enb_ue_s1ap_id == enb_id) {
Harald Welte35498112019-07-02 14:26:39 +0800258 S1apAssociationTable[i].enb_ue_s1ap_id := omit;
259 S1apAssociationTable[i].mme_ue_s1ap_id := omit;
260 S1apAssociationTable[i].comp_ref := null;
261 return;
262 }
263 }
264 setverdict(fail, "S1AP Association Table: Couldn't find to-be-deleted entry!");
265 mtc.stop;
266}
267
268
269private function f_s1ap_id_table_init()
270runs on S1AP_Emulation_CT {
271 for (var integer i := 0; i < sizeof(S1apAssociationTable); i := i+1) {
272 S1apAssociationTable[i].mme_ue_s1ap_id := omit;
273 S1apAssociationTable[i].enb_ue_s1ap_id := omit;
274 S1apAssociationTable[i].cgi := omit;
275 S1apAssociationTable[i].tai := omit;
276 S1apAssociationTable[i].nus := valueof(t_NAS_UE_State(g_pars.role));
Philipp Maiereb930fd2023-07-24 17:59:40 +0200277 S1apAssociationTable[i].comp_ref := null;
Harald Welte35498112019-07-02 14:26:39 +0800278 }
279}
280
281private template (value) SctpTuple ts_SCTP(template (omit) integer ppid := 18) := {
282 sinfo_stream := omit,
283 sinfo_ppid := ppid,
284 remSocks := omit,
285 assocId := omit
286};
287
288private template PortEvent tr_SctpAssocChange := {
289 sctpEvent := {
290 sctpAssocChange := ?
291 }
292}
293private template PortEvent tr_SctpPeerAddrChange := {
294 sctpEvent := {
295 sctpPeerAddrChange := ?
296 }
297}
298
299private function f_s1ap_xceive(template (value) S1AP_PDU tx,
300 template S1AP_PDU rx_t := ?)
301runs on S1AP_Emulation_CT return S1AP_PDU {
302 timer T := 10.0;
303 var S1AP_RecvFrom mrf;
304
305 S1AP.send(t_S1AP_Send(g_s1ap_conn_id, tx));
306 alt {
307 [] S1AP.receive(tr_S1AP_RecvFrom_R(rx_t)) -> value mrf { }
308 [] S1AP.receive(tr_SctpAssocChange) { repeat; }
309 [] S1AP.receive(tr_SctpPeerAddrChange) { repeat; }
310 [] T.timeout {
311 setverdict(fail, "Timeout waiting for ", rx_t);
312 mtc.stop;
313 }
314 }
315 return mrf.msg;
316}
317
318/*
319private function f_nas_try_decaps(PDU_NAS_EPS nas) return PDU_NAS_EPS
320{
321 var PDU_NAS_EPS_SecurityProtectedNASMessage secp_nas;
322 if (not match(nas, tr_NAS_EMM_SecurityProtected)) {
323 return nas;
324 }
325 secp_nas := nas.ePS_messages.ePS_MobilityManagement.pDU_NAS_EPS_SecurityProtectedNASMessage;
326 select (secp_nas.securityHeaderType) {
327 case ('0011'B) {
328 var octetstring knas_int := '530ce32318f26264eab26bc116870b86'O;
329 var octetstring data_with_seq := int2oct(secp_nas.sequenceNumber, 1) & secp_nas.nAS_Message;
330 var OCT4 exp_mac := f_snow_3g_f9(knas_int, secp_nas.sequenceNumber, 0,
331 is_downlink:=true, data:=data_with_seq);
332 if (exp_mac != secp_nas.messageAuthenticationCode) {
333 setverdict(fail, "Received NAS MAC ", secp_nas.messageAuthenticationCode,
334 " doesn't match expected MAC ", exp_mac, ": ", nas);
335 mtc.stop;
336 }
337 return dec_PDU_NAS_EPS(secp_nas.nAS_Message);
338 }
339 case else {
340 setverdict(fail, "Implement SecHdrType for ", secp_nas);
341 mtc.stop;
342 }
343 }
344}
345*/
346
Philipp Maierbb8f05d2023-07-07 14:16:08 +0200347function handle_S1AP_UeContextReleaseCmd(template (present) S1AP_PDU rel_cmd) runs on S1AP_Emulation_CT {
348 if (ispresent(rel_cmd.initiatingMessage.value_.uEContextReleaseCommand.protocolIEs[0].value_.uE_S1AP_IDs.uE_S1AP_ID_pair)) {
349 var template MME_UE_S1AP_ID mme_ue_id;
350 var template ENB_UE_S1AP_ID enb_ue_id;
351 var integer assoc_id;
352 var S1AP_ConnHdlr vc_conn
353
354 mme_ue_id := rel_cmd.initiatingMessage.value_.uEContextReleaseCommand.protocolIEs[0].value_.uE_S1AP_IDs.uE_S1AP_ID_pair.mME_UE_S1AP_ID;
355 enb_ue_id := rel_cmd.initiatingMessage.value_.uEContextReleaseCommand.protocolIEs[0].value_.uE_S1AP_IDs.uE_S1AP_ID_pair.eNB_UE_S1AP_ID;
356
357 assoc_id := f_assoc_id_by_s1ap_ids(mme_ue_id, enb_ue_id);
358 vc_conn := S1apAssociationTable[assoc_id].comp_ref;
359
360 f_s1ap_id_table_del(vc_conn, valueof(enb_ue_id));
361 } else {
362 /* TODO: The UE CONTEXT RELEASE COMMAND (see also: 3GPP TS 36.413, section 9.1.4.6), may identify the
363 * context by either an uE_S1AP_ID_pair (MME_UE_S1AP_ID and ENB_UE_S1AP_ID) or an MME_UE_S1AP_ID alone.
364 * The latter case is not implemented here yet. */
365 setverdict(fail, "complete implementation of UeContextReleaseCmd handling");
366 mtc.stop;
367 }
368}
369
Harald Welte35498112019-07-02 14:26:39 +0800370function main(S1APOps ops, S1AP_conn_parameters p, charstring id) runs on S1AP_Emulation_CT {
371 var Result res;
372 g_pars := p;
373 g_s1ap_id := id;
374 f_s1ap_id_table_init();
375 f_expect_table_init();
376
377 map(self:S1AP, system:S1AP_CODEC_PT);
378 if (p.remote_sctp_port == -1) {
379 res := S1AP_CodecPort_CtrlFunct.f_IPL4_listen(S1AP, p.local_ip, p.local_sctp_port, { sctp := valueof(ts_SCTP) });
380 } else {
381 res := S1AP_CodecPort_CtrlFunct.f_IPL4_connect(S1AP, p.remote_ip, p.remote_sctp_port,
382 p.local_ip, p.local_sctp_port, -1, { sctp := valueof(ts_SCTP) });
383 }
384 if (not ispresent(res.connId)) {
385 setverdict(fail, "Could not connect S1AP socket, check your configuration");
386 mtc.stop;
387 }
388 g_s1ap_conn_id := res.connId;
389
390 /* notify user about SCTP establishment */
391 if (p.remote_sctp_port != -1) {
392 S1AP_UNIT.send(S1APEM_Event:{up_down:=S1APEM_EVENT_UP})
393 }
394
395 while (true) {
396 var S1AP_ConnHdlr vc_conn;
397 var PDU_NAS_EPS nas;
Philipp Maier7147c922023-07-07 14:18:32 +0200398 var MME_UE_S1AP_ID mme_id;
399 var ENB_UE_S1AP_ID enb_id;
Harald Welte35498112019-07-02 14:26:39 +0800400 var S1AP_RecvFrom mrf;
401 var S1AP_PDU msg;
402 var S1APEM_Config s1cfg;
403 var charstring vlr_name, mme_name;
404 var integer ai;
405
406 alt {
407 /* Configuration primitive from client */
408 [] S1AP_CLIENT.receive(S1APEM_Config:{set_nas_keys:=?}) -> value s1cfg sender vc_conn {
409 var integer assoc_id := f_assoc_id_by_comp(vc_conn);
410 S1apAssociationTable[assoc_id].nus.k_nas_int := s1cfg.set_nas_keys.k_nas_int;
411 S1apAssociationTable[assoc_id].nus.k_nas_enc := s1cfg.set_nas_keys.k_nas_enc;
412 }
413 /* S1AP from client: InitialUE */
414 [] S1AP_CLIENT.receive(tr_S1AP_InitialUE) -> value msg sender vc_conn {
415 /* create a table entry about this connection */
416 ai := f_s1ap_id_table_add(vc_conn, omit, valueof(f_S1AP_get_ENB_UE_S1AP_ID(msg)));
417 /* Store CGI + TAI so we can use it for generating UlNasTransport from NAS */
418 S1apAssociationTable[ai].tai := msg.initiatingMessage.value_.InitialUEMessage.protocolIEs[2].value_.TAI;
419 S1apAssociationTable[ai].cgi := msg.initiatingMessage.value_.InitialUEMessage.protocolIEs[3].value_.EUTRAN_CGI;
420 /* Pass message through */
421 S1AP.send(t_S1AP_Send(g_s1ap_conn_id, msg));
422 }
423 /* NAS from client: Wrap in S1AP Uplink NAS Transport */
424 [] S1AP_CLIENT.receive(PDU_NAS_EPS:?) -> value nas sender vc_conn {
425 var integer assoc_id := f_assoc_id_by_comp(vc_conn);
426 var AssociationData ad := S1apAssociationTable[assoc_id];
427 nas := f_nas_encaps(S1apAssociationTable[assoc_id].nus, nas, new_ctx := false);
428 var octetstring nas_enc := enc_PDU_NAS_EPS(nas);
429 S1AP.send(t_S1AP_Send(g_s1ap_conn_id,
430 ts_S1AP_UlNasTransport(ad.mme_ue_s1ap_id,
431 ad.enb_ue_s1ap_id,
432 nas_enc, ad.cgi, ad.tai)));
433 }
434 /* S1AP from client: pass on transparently */
435 [] S1AP_CLIENT.receive(S1AP_PDU:?) -> value msg sender vc_conn {
436 /* Pass message through */
437 /* FIXME: validate S1AP_IDs ? */
438 S1AP.send(t_S1AP_Send(g_s1ap_conn_id, msg));
439 }
440
441 /* non-UE related S1AP: pass through unmodified/unverified */
442 [] S1AP_UNIT.receive(S1AP_PDU:?) -> value msg sender vc_conn {
443 /* Pass message through */
444 S1AP.send(t_S1AP_Send(g_s1ap_conn_id, msg));
445 }
446
447 /* S1AP received from peer (MME) */
448 [] S1AP.receive(tr_S1AP_RecvFrom_R(?)) -> value mrf {
449 if (match(mrf.msg, tr_S1AP_nonUErelated)) {
450 /* non-UE-related S1AP message */
451 var template S1AP_PDU resp := ops.unitdata_cb.apply(mrf.msg);
452 if (isvalue(resp)) {
453 S1AP.send(t_S1AP_Send(g_s1ap_conn_id, valueof(resp)));
454 }
455 } else if (match(mrf.msg, tr_S1AP_UeContextReleaseCmd)) {
Philipp Maierbb8f05d2023-07-07 14:16:08 +0200456 handle_S1AP_UeContextReleaseCmd(mrf.msg);
Harald Welte35498112019-07-02 14:26:39 +0800457 } else {
458 /* Ue-related S1AP message */
459 /* obtain MME + ENB UE S1AP ID */
460 var template (omit) MME_UE_S1AP_ID mme_ue_id := f_S1AP_get_MME_UE_S1AP_ID(mrf.msg);
461 var template (omit) ENB_UE_S1AP_ID enb_ue_id := f_S1AP_get_ENB_UE_S1AP_ID(mrf.msg);
462 /* check if those IDs are known in our table */
463 if (f_s1ap_ids_known(mme_ue_id, enb_ue_id)) {
464 /* if yes, dispatch to the ConnHdlr for this Ue-Connection */
465 var template (omit) octetstring nas_enc;
Philipp Maier7147c922023-07-07 14:18:32 +0200466 var integer assoc_id := f_assoc_id_by_s1ap_ids(mme_ue_id, enb_ue_id);
Harald Welte35498112019-07-02 14:26:39 +0800467 vc_conn := S1apAssociationTable[assoc_id].comp_ref;
468 nas_enc := f_S1AP_get_NAS_PDU(mrf.msg);
469 if (isvalue(nas_enc)) {
470 nas := dec_PDU_NAS_EPS(valueof(nas_enc));
471 if (match(nas, tr_NAS_EMM_SecurityProtected)) {
472 nas := f_nas_try_decaps(S1apAssociationTable[assoc_id].nus, nas);
473 }
474 /* send decoded NAS */
475 S1AP_CLIENT.send(nas) to vc_conn;
476 } else {
477 /* send raw S1AP */
478 S1AP_CLIENT.send(mrf.msg) to vc_conn;
479 }
480 } else {
481 /* if not, call create_cb so it can create new ConnHdlr */
482 vc_conn := ops.create_cb.apply(mrf.msg, mme_ue_id, enb_ue_id, id);
483 f_s1ap_id_table_add(vc_conn, mme_ue_id, valueof(enb_ue_id));
484 S1AP_CLIENT.send(mrf.msg) to vc_conn;
485 }
486 }
487 }
488 [] S1AP.receive(tr_SctpAssocChange) { }
489 [] S1AP.receive(tr_SctpPeerAddrChange) { }
Philipp Maier7147c922023-07-07 14:18:32 +0200490 [] S1AP_PROC.getcall(S1APEM_register:{?,?,?}) -> param(mme_id, enb_id, vc_conn) {
491 f_create_expect(mme_id, enb_id, vc_conn);
492 S1AP_PROC.reply(S1APEM_register:{mme_id, enb_id, vc_conn}) to vc_conn;
Harald Welte35498112019-07-02 14:26:39 +0800493 }
494 }
495
496 }
497}
498
499/* "Expect" Handling */
500
501type record ExpectData {
Philipp Maier7147c922023-07-07 14:18:32 +0200502 MME_UE_S1AP_ID mme_id optional,
503 ENB_UE_S1AP_ID enb_id optional,
Harald Welte35498112019-07-02 14:26:39 +0800504 S1AP_ConnHdlr vc_conn
505}
506
Philipp Maier7147c922023-07-07 14:18:32 +0200507signature S1APEM_register(in MME_UE_S1AP_ID mme_id, in ENB_UE_S1AP_ID enb_id, in S1AP_ConnHdlr hdlr);
Harald Welte35498112019-07-02 14:26:39 +0800508
509type port S1APEM_PROC_PT procedure {
510 inout S1APEM_register;
511} with { extension "internal" };
512
Oliver Smith23e192e2023-02-13 15:00:46 +0100513/* Function that can be used as create_cb and will use the expect table */
Philipp Maier7147c922023-07-07 14:18:32 +0200514function ExpectedCreateCallback(S1AP_PDU msg,
515 template (omit) MME_UE_S1AP_ID mme_id,
516 template (omit) ENB_UE_S1AP_ID enb_id, charstring id)
Harald Welte35498112019-07-02 14:26:39 +0800517runs on S1AP_Emulation_CT return S1AP_ConnHdlr {
518 var S1AP_ConnHdlr ret := null;
519 var integer i;
520
521 for (i := 0; i < sizeof(S1apExpectTable); i := i+1) {
Philipp Maier7147c922023-07-07 14:18:32 +0200522 if (not ispresent(S1apExpectTable[i].mme_id) and not ispresent(S1apExpectTable[i].enb_id)) {
Harald Welte35498112019-07-02 14:26:39 +0800523 continue;
524 }
Philipp Maier7147c922023-07-07 14:18:32 +0200525
526 if (valueof(mme_id) == S1apExpectTable[i].mme_id and valueof(enb_id) == S1apExpectTable[i].enb_id) {
Harald Welte35498112019-07-02 14:26:39 +0800527 ret := S1apExpectTable[i].vc_conn;
528 /* Release this entry */
Philipp Maier7147c922023-07-07 14:18:32 +0200529 S1apExpectTable[i].mme_id := omit;
530 S1apExpectTable[i].enb_id := omit;
Harald Welte35498112019-07-02 14:26:39 +0800531 S1apExpectTable[i].vc_conn := null;
532 log("Found Expect[", i, "] for ", msg, " handled at ", ret);
533 return ret;
534 }
535 }
536 setverdict(fail, "Couldn't find Expect for ", msg);
537 mtc.stop;
538}
539
Philipp Maier7147c922023-07-07 14:18:32 +0200540private function f_create_expect(template (omit) MME_UE_S1AP_ID mme_id,
541 template (omit) ENB_UE_S1AP_ID enb_id,
542 S1AP_ConnHdlr hdlr)
Harald Welte35498112019-07-02 14:26:39 +0800543runs on S1AP_Emulation_CT {
544 var integer i;
545
546 /* Check an entry like this is not already presnt */
547 for (i := 0; i < sizeof(S1apExpectTable); i := i+1) {
Philipp Maier7147c922023-07-07 14:18:32 +0200548 if (not ispresent(S1apExpectTable[i].mme_id) and not ispresent(S1apExpectTable[i].enb_id)) {
549 continue;
550 }
551 if (valueof(mme_id) == S1apExpectTable[i].mme_id and valueof(enb_id) == S1apExpectTable[i].enb_id) {
552 setverdict(fail, "UE MME id / UE ENB id pair already present", mme_id, enb_id);
Harald Welte35498112019-07-02 14:26:39 +0800553 mtc.stop;
554 }
555 }
556 for (i := 0; i < sizeof(S1apExpectTable); i := i+1) {
Philipp Maier7147c922023-07-07 14:18:32 +0200557 if (not ispresent(S1apExpectTable[i].mme_id) and not ispresent(S1apExpectTable[i].enb_id)) {
558 S1apExpectTable[i].mme_id := valueof(mme_id);
559 S1apExpectTable[i].enb_id := valueof(enb_id);
Harald Welte35498112019-07-02 14:26:39 +0800560 S1apExpectTable[i].vc_conn := hdlr;
Philipp Maier7147c922023-07-07 14:18:32 +0200561 log("Created Expect[", i, "] for UE MME id:", mme_id, ", UE ENB id:", enb_id, " to be handled at ", hdlr);
Harald Welte35498112019-07-02 14:26:39 +0800562 return;
563 }
564 }
565 testcase.stop("No space left in S1apExpectTable")
566}
567
568/* client/conn_hdlr side function to use procedure port to create expect in emulation */
Philipp Maier7147c922023-07-07 14:18:32 +0200569function f_create_s1ap_expect(template (omit) MME_UE_S1AP_ID mme_id,
570 template (omit) ENB_UE_S1AP_ID enb_id) runs on S1AP_ConnHdlr {
571 S1AP_PROC.call(S1APEM_register:{mme_id, enb_id, self}) {
572 [] S1AP_PROC.getreply(S1APEM_register:{?,?,?}) {};
Harald Welte35498112019-07-02 14:26:39 +0800573 }
574}
575
576
577private function f_expect_table_init()
578runs on S1AP_Emulation_CT {
579 var integer i;
580 for (i := 0; i < sizeof(S1apExpectTable); i := i + 1) {
Philipp Maier7147c922023-07-07 14:18:32 +0200581 S1apExpectTable[i].mme_id := omit;
582 S1apExpectTable[i].enb_id := omit;
583 S1apExpectTable[i].vc_conn := null;
Harald Welte35498112019-07-02 14:26:39 +0800584 }
585}
586
587function DummyUnitdataCallback(S1AP_PDU msg)
588runs on S1AP_Emulation_CT return template S1AP_PDU {
589 log("Ignoring S1AP ", msg);
590 return omit;
591}
592
593
594function f_S1AP_get_ENB_UE_S1AP_ID(S1AP_PDU s1ap) return template (omit) ENB_UE_S1AP_ID
595{
596 if (ischosen(s1ap.initiatingMessage)) {
597 var InitiatingMessage im := s1ap.initiatingMessage;
598 select (s1ap) {
599 case (tr_S1AP_InitialUE) {
600 return im.value_.InitialUEMessage.protocolIEs[0].value_.ENB_UE_S1AP_ID;
601 }
602 case (tr_S1AP_DlNasTransport) {
603 return im.value_.DownlinkNASTransport.protocolIEs[1].value_.ENB_UE_S1AP_ID;
604 }
605 case (tr_S1AP_UlNasTransport) {
606 return im.value_.UplinkNASTransport.protocolIEs[1].value_.ENB_UE_S1AP_ID;
607 }
608 case (tr_S1AP_IntialCtxSetupReq) {
609 return im.value_.initialContextSetupRequest.protocolIEs[1].value_.ENB_UE_S1AP_ID;
610 }
611 case (tr_S1AP_UeContextReleaseReq) {
612 return im.value_.UEContextReleaseRequest.protocolIEs[1].value_.ENB_UE_S1AP_ID;
613 }
614 /* UeContextReleaseCmd needs special handling; it can contain any number of MME/UE IDs */
615 case (tr_S1AP_ConnEstInd) {
616 return im.value_.ConnectionEstablishmentIndication.protocolIEs[1].value_.ENB_UE_S1AP_ID;
617 }
618 /* TODO */
619 }
620 } else if (ischosen(s1ap.successfulOutcome)) {
621 var SuccessfulOutcome so := s1ap.successfulOutcome;
622 select (s1ap) {
623 case (tr_S1AP_InitialCtxSetupResp) {
624 return so.value_.initialContextSetupResponse.protocolIEs[1].value_.ENB_UE_S1AP_ID;
625 }
626 case (tr_S1AP_UeContextReleaseCompl) {
627 return so.value_.UEContextReleaseComplete.protocolIEs[1].value_.ENB_UE_S1AP_ID;
628 }
629 /* TODO */
630 }
631 } else if (ischosen(s1ap.unsuccessfulOutcome)) {
632 var UnsuccessfulOutcome uo := s1ap.unsuccessfulOutcome;
633 select (s1ap) {
634 case (tr_S1AP_InitialCtxSetupFail) {
635 return uo.value_.initialContextSetupFailure.protocolIEs[1].value_.ENB_UE_S1AP_ID;
636 }
637 /* TODO */
638 }
639 }
640 return omit;
641}
642
643function f_S1AP_get_MME_UE_S1AP_ID(S1AP_PDU s1ap) return template (omit) MME_UE_S1AP_ID
644{
645 if (ischosen(s1ap.initiatingMessage)) {
646 var InitiatingMessage im := s1ap.initiatingMessage;
647 select (s1ap) {
648 case (tr_S1AP_DlNasTransport) {
649 return im.value_.DownlinkNASTransport.protocolIEs[0].value_.MME_UE_S1AP_ID;
650 }
651 case (tr_S1AP_UlNasTransport) {
652 return im.value_.UplinkNASTransport.protocolIEs[0].value_.MME_UE_S1AP_ID;
653 }
654 case (tr_S1AP_IntialCtxSetupReq) {
655 return im.value_.initialContextSetupRequest.protocolIEs[0].value_.MME_UE_S1AP_ID;
656 }
657 case (tr_S1AP_UeContextReleaseReq) {
658 return im.value_.UEContextReleaseRequest.protocolIEs[0].value_.MME_UE_S1AP_ID;
659 }
660 /* UeContextReleaseCmd needs special handling; it can contain any number of MME/UE IDs */
661 case (tr_S1AP_ConnEstInd) {
662 return im.value_.ConnectionEstablishmentIndication.protocolIEs[0].value_.MME_UE_S1AP_ID;
663 }
664 /* TODO */
665 }
666 } else if (ischosen(s1ap.successfulOutcome)) {
667 var SuccessfulOutcome so := s1ap.successfulOutcome;
668 select (s1ap) {
669 case (tr_S1AP_InitialCtxSetupResp) {
670 return so.value_.initialContextSetupResponse.protocolIEs[0].value_.MME_UE_S1AP_ID;
671 }
672 case (tr_S1AP_UeContextReleaseCompl) {
673 return so.value_.UEContextReleaseComplete.protocolIEs[0].value_.MME_UE_S1AP_ID;
674 }
675 /* TODO */
676 }
677 } else if (ischosen(s1ap.unsuccessfulOutcome)) {
678 var UnsuccessfulOutcome uo := s1ap.unsuccessfulOutcome;
679 select (s1ap) {
680 case (tr_S1AP_InitialCtxSetupFail) {
681 return uo.value_.initialContextSetupFailure.protocolIEs[0].value_.MME_UE_S1AP_ID;
682 }
683 /* TODO */
684 }
685 }
686 return omit;
687}
688
689function f_S1AP_get_NAS_PDU(S1AP_PDU s1ap) return template (omit) NAS_PDU
690{
691 var integer i;
692
693 if (ischosen(s1ap.initiatingMessage)) {
694 var InitiatingMessage im := s1ap.initiatingMessage;
695 select (s1ap) {
696 case (tr_S1AP_DlNasTransport) {
697 var DownlinkNASTransport msg := im.value_.DownlinkNASTransport;
698 for (i := 0; i < lengthof(msg.protocolIEs); i := i+1) {
699 if (msg.protocolIEs[i].id == id_NAS_PDU) {
700 return msg.protocolIEs[i].value_.NAS_PDU;
701 }
702 }
703 }
704 case (tr_S1AP_UlNasTransport) {
705 var UplinkNASTransport msg := im.value_.UplinkNASTransport;
706 for (i := 0; i < lengthof(msg.protocolIEs); i := i+1) {
707 if (msg.protocolIEs[i].id == id_NAS_PDU) {
708 return msg.protocolIEs[i].value_.NAS_PDU;
709 }
710 }
711 }
712 }
713 }
714 return omit;
715}
716
717
718
719}