blob: 7d91c36270001202445955fd486019ca93a29bd9 [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
Philipp Maierfc7ada22023-07-24 11:32:12 +020018 * MME_UE_S1AP_ID/ENB_UE_S1AP_ID identifiers. It is also possible to register
19 * an expect for a specific procedureCode, in case the expected message is non
20 * UE related (unit-data).
Harald Welte35498112019-07-02 14:26:39 +080021 *
Philipp Maier7147c922023-07-07 14:18:32 +020022 * Inbound non-UE related S1AP messages (such as RESET, SETUP, OVERLOAD) are
23 * dispatched to the S1apOps.unitdata_cb() callback, which is registered with
24 * an argument to the main() function below.
Harald Welte35498112019-07-02 14:26:39 +080025 *
26 * (C) 2019 by Harald Welte <laforge@gnumonks.org>
27 * All rights reserved.
28 *
29 * Released under the terms of GNU General Public License, Version 2 or
30 * (at your option) any later version.
31 *
32 * SPDX-License-Identifier: GPL-2.0-or-later
33 */
34
35import from S1AP_CodecPort all;
36import from S1AP_CodecPort_CtrlFunct all;
37import from S1AP_Types all;
38import from S1AP_Constants all;
39import from S1AP_PDU_Contents all;
40import from S1AP_PDU_Descriptions all;
41import from S1AP_IEs all;
42import from S1AP_Templates all;
43
44import from NAS_EPS_Types all;
45import from NAS_Templates all;
46
47import from LTE_CryptoFunctions all;
48
49import from General_Types all;
50import from Osmocom_Types all;
51import from IPL4asp_Types all;
52import from DNS_Helpers all;
53
54
55type component S1AP_ConnHdlr {
56 port S1AP_Conn_PT S1AP;
57 /* procedure based port to register for incoming connections */
58 port S1APEM_PROC_PT S1AP_PROC;
59}
60
61/* port between individual per-connection components and this dispatcher */
62type port S1AP_Conn_PT message {
63 inout S1AP_PDU, PDU_NAS_EPS, S1APEM_Config;
64} with { extension "internal" };
65
66type record NAS_Keys {
67 octetstring k_nas_int,
68 octetstring k_nas_enc
69};
70type union S1APEM_Config {
71 NAS_Keys set_nas_keys
72};
73
74type enumerated S1APEM_EventUpDown {
75 S1APEM_EVENT_DOWN,
76 S1APEM_EVENT_UP
77}
78
79/* an event indicating us whether or not a connection is physically up or down,
80 * and whether we have received an ID_ACK */
81type union S1APEM_Event {
82 S1APEM_EventUpDown up_down
83}
84
85/* global test port e.g. for non-imsi/conn specific messages */
86type port S1AP_PT message {
87 inout S1AP_PDU, S1APEM_Event;
88} with { extension "internal" };
89
90
91/* represents a single S1AP Association */
92type record AssociationData {
93 S1AP_ConnHdlr comp_ref, /* component handling this UE connection */
94 uint24_t enb_ue_s1ap_id optional, /* eNB side S1AP ID */
95 uint32_t mme_ue_s1ap_id optional, /* MME side S1AP ID */
96 EUTRAN_CGI cgi optional,
97 TAI tai optional,
98 NAS_UE_State nus
Harald Welte35498112019-07-02 14:26:39 +080099};
100
101type component S1AP_Emulation_CT {
102 /* Port facing to the UDP SUT */
103 port S1AP_CODEC_PT S1AP;
104 /* All S1AP_ConnHdlr S1AP ports connect here
105 * S1AP_Emulation_CT.main needs to figure out what messages
106 * to send where with CLIENT.send() to vc_conn */
107 port S1AP_Conn_PT S1AP_CLIENT;
108 /* currently tracked connections */
109 var AssociationData S1apAssociationTable[16];
Philipp Maierf935f0c2023-07-24 17:58:24 +0200110 /* pending expected S1AP Association (UE oriented) */
Harald Welte35498112019-07-02 14:26:39 +0800111 var ExpectData S1apExpectTable[8];
Philipp Maierfc7ada22023-07-24 11:32:12 +0200112 /* pending expected S1AP PDU */
113 var ExpectDataProc S1apExpectTableProc[8];
Harald Welte35498112019-07-02 14:26:39 +0800114 /* procedure based port to register for incoming connections */
115 port S1APEM_PROC_PT S1AP_PROC;
116 /* test port for unit data messages */
117 port S1AP_PT S1AP_UNIT;
118
119 var S1AP_conn_parameters g_pars;
120 var charstring g_s1ap_id;
121 var integer g_s1ap_conn_id := -1;
122}
123
124type function S1APCreateCallback(S1AP_PDU msg, template (omit) MME_UE_S1AP_ID mme_id,
125 template (omit) ENB_UE_S1AP_ID enb_id, charstring id)
126runs on S1AP_Emulation_CT return S1AP_ConnHdlr;
127
128type function S1APUnitdataCallback(S1AP_PDU msg)
129runs on S1AP_Emulation_CT return template S1AP_PDU;
130
131type record S1APOps {
132 S1APCreateCallback create_cb,
133 S1APUnitdataCallback unitdata_cb
134}
135
136type record S1AP_conn_parameters {
137 HostName remote_ip,
138 PortNumber remote_sctp_port,
139 HostName local_ip,
140 PortNumber local_sctp_port,
141 NAS_Role role
142}
143
144function tr_S1AP_RecvFrom_R(template S1AP_PDU msg)
145runs on S1AP_Emulation_CT return template S1AP_RecvFrom {
146 var template S1AP_RecvFrom mrf := {
147 connId := g_s1ap_conn_id,
148 remName := ?,
149 remPort := ?,
150 locName := ?,
151 locPort := ?,
152 msg := msg
153 }
154 return mrf;
155}
156
157private function f_s1ap_ids_known(template (omit) MME_UE_S1AP_ID mme_id,
158 template (omit) ENB_UE_S1AP_ID enb_id)
159runs on S1AP_Emulation_CT return boolean {
160 var integer i;
161 log("f_s1ap_ids_known(",mme_id,", ",enb_id,")");
162 for (i := 0; i < sizeof(S1apAssociationTable); i := i+1) {
163 log("tbl[",i,"]: mme=", S1apAssociationTable[i].mme_ue_s1ap_id,
164 ", enb=", S1apAssociationTable[i].enb_ue_s1ap_id);
165 /* skip empty records */
166 if (S1apAssociationTable[i].mme_ue_s1ap_id == omit and
167 S1apAssociationTable[i].enb_ue_s1ap_id == omit) {
168 log("skipping empty ", i);
169 continue;
170 }
171 if (S1apAssociationTable[i].mme_ue_s1ap_id == omit) {
172 log("entry ", i, " has no MME ID yet (enb=", S1apAssociationTable[i].enb_ue_s1ap_id);
173 /* Table doesn't yet know the MME side ID, let's look-up only
174 * based on the eNB side ID */
175 if (match(S1apAssociationTable[i].enb_ue_s1ap_id, enb_id)) {
176 /* update table with MME side ID */
177 S1apAssociationTable[i].mme_ue_s1ap_id := valueof(mme_id);
178 return true;
179 }
180 } else if (match(S1apAssociationTable[i].enb_ue_s1ap_id, enb_id) and
181 match(S1apAssociationTable[i].mme_ue_s1ap_id, mme_id)) {
182 return true;
183 }
184 }
185 return false;
186}
187
188private function f_comp_known(S1AP_ConnHdlr client)
189runs on S1AP_Emulation_CT return boolean {
190 var integer i;
191 for (i := 0; i < sizeof(S1apAssociationTable); i := i+1) {
192 if (S1apAssociationTable[i].comp_ref == client) {
193 return true;
194 }
195 }
196 return false;
197}
198
199private function f_assoc_id_by_s1ap_ids(template (omit) MME_UE_S1AP_ID mme_id,
200 template (omit) ENB_UE_S1AP_ID enb_id)
201runs on S1AP_Emulation_CT return integer {
202 var integer i;
203 for (i := 0; i < sizeof(S1apAssociationTable); i := i+1) {
Pau Espin Pedrol6cf7fde2023-12-21 19:17:08 +0100204 if (istemplatekind(enb_id, "omit") or
205 match(S1apAssociationTable[i].enb_ue_s1ap_id, enb_id)) {
Harald Welte35498112019-07-02 14:26:39 +0800206 if (istemplatekind(mme_id, "omit")) {
207 return i;
208 } else {
209 if (match(S1apAssociationTable[i].mme_ue_s1ap_id, mme_id)) {
210 return i;
211 }
212 }
213 }
214 }
215 setverdict(fail, "S1AP Association Table not found by ENB-ID=", enb_id, " MME-ID=", mme_id);
216 mtc.stop;
217}
218
219private function f_assoc_id_by_comp(S1AP_ConnHdlr client)
220runs on S1AP_Emulation_CT return integer {
221 var integer i;
222 for (i := 0; i < sizeof(S1apAssociationTable); i := i+1) {
223 if (S1apAssociationTable[i].comp_ref == client) {
224 return i;
225 }
226 }
227 setverdict(fail, "S1AP Association Table not found by component ", client);
228 mtc.stop;
229}
230
231private function f_assoc_by_comp(S1AP_ConnHdlr client)
232runs on S1AP_Emulation_CT return AssociationData {
233 var integer i := f_assoc_id_by_comp(client);
234 return S1apAssociationTable[i];
235}
236
237private function f_s1ap_id_table_add(S1AP_ConnHdlr comp_ref,
238 template (omit) MME_UE_S1AP_ID mme_id, ENB_UE_S1AP_ID enb_id)
239runs on S1AP_Emulation_CT return integer {
240 var integer i;
241 for (i := 0; i < sizeof(S1apAssociationTable); i := i+1) {
242 if (not isvalue(S1apAssociationTable[i].enb_ue_s1ap_id)) {
243 S1apAssociationTable[i].enb_ue_s1ap_id := enb_id;
244 if (istemplatekind(mme_id, "omit")) {
245 S1apAssociationTable[i].mme_ue_s1ap_id := omit;
246 } else {
247 S1apAssociationTable[i].mme_ue_s1ap_id := valueof(mme_id);
248 }
249 S1apAssociationTable[i].comp_ref := comp_ref;
250 return i;
251 }
252 }
253 testcase.stop("S1AP Association Table full!");
254 return -1;
255}
256
257private function f_s1ap_id_table_del(S1AP_ConnHdlr comp_ref, ENB_UE_S1AP_ID enb_id)
258runs on S1AP_Emulation_CT {
259 var integer i;
260 for (i := 0; i < sizeof(S1apAssociationTable); i := i+1) {
261 if (S1apAssociationTable[i].comp_ref == comp_ref and
Philipp Maier0ce67ab2023-07-07 13:02:11 +0200262 S1apAssociationTable[i].enb_ue_s1ap_id == enb_id) {
Harald Welte35498112019-07-02 14:26:39 +0800263 S1apAssociationTable[i].enb_ue_s1ap_id := omit;
264 S1apAssociationTable[i].mme_ue_s1ap_id := omit;
265 S1apAssociationTable[i].comp_ref := null;
266 return;
267 }
268 }
269 setverdict(fail, "S1AP Association Table: Couldn't find to-be-deleted entry!");
270 mtc.stop;
271}
272
273
274private function f_s1ap_id_table_init()
275runs on S1AP_Emulation_CT {
276 for (var integer i := 0; i < sizeof(S1apAssociationTable); i := i+1) {
277 S1apAssociationTable[i].mme_ue_s1ap_id := omit;
278 S1apAssociationTable[i].enb_ue_s1ap_id := omit;
279 S1apAssociationTable[i].cgi := omit;
280 S1apAssociationTable[i].tai := omit;
281 S1apAssociationTable[i].nus := valueof(t_NAS_UE_State(g_pars.role));
Philipp Maiereb930fd2023-07-24 17:59:40 +0200282 S1apAssociationTable[i].comp_ref := null;
Harald Welte35498112019-07-02 14:26:39 +0800283 }
284}
285
286private template (value) SctpTuple ts_SCTP(template (omit) integer ppid := 18) := {
287 sinfo_stream := omit,
288 sinfo_ppid := ppid,
289 remSocks := omit,
290 assocId := omit
291};
292
293private template PortEvent tr_SctpAssocChange := {
294 sctpEvent := {
295 sctpAssocChange := ?
296 }
297}
298private template PortEvent tr_SctpPeerAddrChange := {
299 sctpEvent := {
300 sctpPeerAddrChange := ?
301 }
302}
303
304private function f_s1ap_xceive(template (value) S1AP_PDU tx,
305 template S1AP_PDU rx_t := ?)
306runs on S1AP_Emulation_CT return S1AP_PDU {
307 timer T := 10.0;
308 var S1AP_RecvFrom mrf;
309
310 S1AP.send(t_S1AP_Send(g_s1ap_conn_id, tx));
311 alt {
312 [] S1AP.receive(tr_S1AP_RecvFrom_R(rx_t)) -> value mrf { }
313 [] S1AP.receive(tr_SctpAssocChange) { repeat; }
314 [] S1AP.receive(tr_SctpPeerAddrChange) { repeat; }
315 [] T.timeout {
316 setverdict(fail, "Timeout waiting for ", rx_t);
317 mtc.stop;
318 }
319 }
320 return mrf.msg;
321}
322
323/*
324private function f_nas_try_decaps(PDU_NAS_EPS nas) return PDU_NAS_EPS
325{
326 var PDU_NAS_EPS_SecurityProtectedNASMessage secp_nas;
327 if (not match(nas, tr_NAS_EMM_SecurityProtected)) {
328 return nas;
329 }
330 secp_nas := nas.ePS_messages.ePS_MobilityManagement.pDU_NAS_EPS_SecurityProtectedNASMessage;
331 select (secp_nas.securityHeaderType) {
332 case ('0011'B) {
333 var octetstring knas_int := '530ce32318f26264eab26bc116870b86'O;
334 var octetstring data_with_seq := int2oct(secp_nas.sequenceNumber, 1) & secp_nas.nAS_Message;
335 var OCT4 exp_mac := f_snow_3g_f9(knas_int, secp_nas.sequenceNumber, 0,
336 is_downlink:=true, data:=data_with_seq);
337 if (exp_mac != secp_nas.messageAuthenticationCode) {
338 setverdict(fail, "Received NAS MAC ", secp_nas.messageAuthenticationCode,
339 " doesn't match expected MAC ", exp_mac, ": ", nas);
340 mtc.stop;
341 }
342 return dec_PDU_NAS_EPS(secp_nas.nAS_Message);
343 }
344 case else {
345 setverdict(fail, "Implement SecHdrType for ", secp_nas);
346 mtc.stop;
347 }
348 }
349}
350*/
351
Philipp Maierbb8f05d2023-07-07 14:16:08 +0200352function handle_S1AP_UeContextReleaseCmd(template (present) S1AP_PDU rel_cmd) runs on S1AP_Emulation_CT {
353 if (ispresent(rel_cmd.initiatingMessage.value_.uEContextReleaseCommand.protocolIEs[0].value_.uE_S1AP_IDs.uE_S1AP_ID_pair)) {
354 var template MME_UE_S1AP_ID mme_ue_id;
355 var template ENB_UE_S1AP_ID enb_ue_id;
356 var integer assoc_id;
357 var S1AP_ConnHdlr vc_conn
358
359 mme_ue_id := rel_cmd.initiatingMessage.value_.uEContextReleaseCommand.protocolIEs[0].value_.uE_S1AP_IDs.uE_S1AP_ID_pair.mME_UE_S1AP_ID;
360 enb_ue_id := rel_cmd.initiatingMessage.value_.uEContextReleaseCommand.protocolIEs[0].value_.uE_S1AP_IDs.uE_S1AP_ID_pair.eNB_UE_S1AP_ID;
361
362 assoc_id := f_assoc_id_by_s1ap_ids(mme_ue_id, enb_ue_id);
363 vc_conn := S1apAssociationTable[assoc_id].comp_ref;
364
365 f_s1ap_id_table_del(vc_conn, valueof(enb_ue_id));
366 } else {
367 /* TODO: The UE CONTEXT RELEASE COMMAND (see also: 3GPP TS 36.413, section 9.1.4.6), may identify the
368 * context by either an uE_S1AP_ID_pair (MME_UE_S1AP_ID and ENB_UE_S1AP_ID) or an MME_UE_S1AP_ID alone.
369 * The latter case is not implemented here yet. */
370 setverdict(fail, "complete implementation of UeContextReleaseCmd handling");
371 mtc.stop;
372 }
373}
374
Philipp Maierfc7ada22023-07-24 11:32:12 +0200375private function SendToS1apExpectTableProc(S1AP_PDU msg) runs on S1AP_Emulation_CT {
376 var integer procedureCode;
377 var S1AP_ConnHdlr vc_conn;
378
379 if (ispresent(msg.initiatingMessage.procedureCode)) {
380 procedureCode := msg.initiatingMessage.procedureCode;
381 } else if (ispresent(msg.unsuccessfulOutcome.procedureCode)) {
382 procedureCode := msg.unsuccessfulOutcome.procedureCode;
383 } else if (ispresent(msg.successfulOutcome.procedureCode)) {
384 procedureCode := msg.successfulOutcome.procedureCode;
385 } else {
386 return;
387 }
388
389 for (var integer i := 0; i < sizeof(S1apExpectTableProc); i := i+1) {
390 if (S1apExpectTableProc[i].procedureCode == procedureCode) {
391 vc_conn := S1apExpectTableProc[i].vc_conn;
392 if (vc_conn != null) {
393 S1AP_CLIENT.send(msg) to vc_conn;
394 }
395 }
396 }
397
398 return;
399}
400
Harald Welte35498112019-07-02 14:26:39 +0800401function main(S1APOps ops, S1AP_conn_parameters p, charstring id) runs on S1AP_Emulation_CT {
402 var Result res;
403 g_pars := p;
404 g_s1ap_id := id;
405 f_s1ap_id_table_init();
406 f_expect_table_init();
407
408 map(self:S1AP, system:S1AP_CODEC_PT);
409 if (p.remote_sctp_port == -1) {
410 res := S1AP_CodecPort_CtrlFunct.f_IPL4_listen(S1AP, p.local_ip, p.local_sctp_port, { sctp := valueof(ts_SCTP) });
411 } else {
412 res := S1AP_CodecPort_CtrlFunct.f_IPL4_connect(S1AP, p.remote_ip, p.remote_sctp_port,
413 p.local_ip, p.local_sctp_port, -1, { sctp := valueof(ts_SCTP) });
414 }
415 if (not ispresent(res.connId)) {
416 setverdict(fail, "Could not connect S1AP socket, check your configuration");
417 mtc.stop;
418 }
419 g_s1ap_conn_id := res.connId;
420
421 /* notify user about SCTP establishment */
422 if (p.remote_sctp_port != -1) {
423 S1AP_UNIT.send(S1APEM_Event:{up_down:=S1APEM_EVENT_UP})
424 }
425
426 while (true) {
427 var S1AP_ConnHdlr vc_conn;
428 var PDU_NAS_EPS nas;
Philipp Maier7147c922023-07-07 14:18:32 +0200429 var MME_UE_S1AP_ID mme_id;
430 var ENB_UE_S1AP_ID enb_id;
Philipp Maierfc7ada22023-07-24 11:32:12 +0200431 var integer procedureCode;
Harald Welte35498112019-07-02 14:26:39 +0800432 var S1AP_RecvFrom mrf;
433 var S1AP_PDU msg;
434 var S1APEM_Config s1cfg;
435 var charstring vlr_name, mme_name;
436 var integer ai;
437
438 alt {
439 /* Configuration primitive from client */
440 [] S1AP_CLIENT.receive(S1APEM_Config:{set_nas_keys:=?}) -> value s1cfg sender vc_conn {
441 var integer assoc_id := f_assoc_id_by_comp(vc_conn);
442 S1apAssociationTable[assoc_id].nus.k_nas_int := s1cfg.set_nas_keys.k_nas_int;
443 S1apAssociationTable[assoc_id].nus.k_nas_enc := s1cfg.set_nas_keys.k_nas_enc;
444 }
445 /* S1AP from client: InitialUE */
446 [] S1AP_CLIENT.receive(tr_S1AP_InitialUE) -> value msg sender vc_conn {
447 /* create a table entry about this connection */
448 ai := f_s1ap_id_table_add(vc_conn, omit, valueof(f_S1AP_get_ENB_UE_S1AP_ID(msg)));
449 /* Store CGI + TAI so we can use it for generating UlNasTransport from NAS */
450 S1apAssociationTable[ai].tai := msg.initiatingMessage.value_.InitialUEMessage.protocolIEs[2].value_.TAI;
451 S1apAssociationTable[ai].cgi := msg.initiatingMessage.value_.InitialUEMessage.protocolIEs[3].value_.EUTRAN_CGI;
452 /* Pass message through */
453 S1AP.send(t_S1AP_Send(g_s1ap_conn_id, msg));
454 }
455 /* NAS from client: Wrap in S1AP Uplink NAS Transport */
456 [] S1AP_CLIENT.receive(PDU_NAS_EPS:?) -> value nas sender vc_conn {
457 var integer assoc_id := f_assoc_id_by_comp(vc_conn);
458 var AssociationData ad := S1apAssociationTable[assoc_id];
459 nas := f_nas_encaps(S1apAssociationTable[assoc_id].nus, nas, new_ctx := false);
460 var octetstring nas_enc := enc_PDU_NAS_EPS(nas);
461 S1AP.send(t_S1AP_Send(g_s1ap_conn_id,
462 ts_S1AP_UlNasTransport(ad.mme_ue_s1ap_id,
463 ad.enb_ue_s1ap_id,
464 nas_enc, ad.cgi, ad.tai)));
465 }
466 /* S1AP from client: pass on transparently */
467 [] S1AP_CLIENT.receive(S1AP_PDU:?) -> value msg sender vc_conn {
468 /* Pass message through */
469 /* FIXME: validate S1AP_IDs ? */
470 S1AP.send(t_S1AP_Send(g_s1ap_conn_id, msg));
471 }
472
473 /* non-UE related S1AP: pass through unmodified/unverified */
474 [] S1AP_UNIT.receive(S1AP_PDU:?) -> value msg sender vc_conn {
475 /* Pass message through */
476 S1AP.send(t_S1AP_Send(g_s1ap_conn_id, msg));
477 }
478
479 /* S1AP received from peer (MME) */
480 [] S1AP.receive(tr_S1AP_RecvFrom_R(?)) -> value mrf {
481 if (match(mrf.msg, tr_S1AP_nonUErelated)) {
482 /* non-UE-related S1AP message */
Philipp Maierfc7ada22023-07-24 11:32:12 +0200483 SendToS1apExpectTableProc(mrf.msg);
Harald Welte35498112019-07-02 14:26:39 +0800484 var template S1AP_PDU resp := ops.unitdata_cb.apply(mrf.msg);
485 if (isvalue(resp)) {
486 S1AP.send(t_S1AP_Send(g_s1ap_conn_id, valueof(resp)));
487 }
Harald Welte35498112019-07-02 14:26:39 +0800488 } else {
489 /* Ue-related S1AP message */
490 /* obtain MME + ENB UE S1AP ID */
491 var template (omit) MME_UE_S1AP_ID mme_ue_id := f_S1AP_get_MME_UE_S1AP_ID(mrf.msg);
492 var template (omit) ENB_UE_S1AP_ID enb_ue_id := f_S1AP_get_ENB_UE_S1AP_ID(mrf.msg);
493 /* check if those IDs are known in our table */
494 if (f_s1ap_ids_known(mme_ue_id, enb_ue_id)) {
495 /* if yes, dispatch to the ConnHdlr for this Ue-Connection */
496 var template (omit) octetstring nas_enc;
Philipp Maier7147c922023-07-07 14:18:32 +0200497 var integer assoc_id := f_assoc_id_by_s1ap_ids(mme_ue_id, enb_ue_id);
Harald Welte35498112019-07-02 14:26:39 +0800498 vc_conn := S1apAssociationTable[assoc_id].comp_ref;
499 nas_enc := f_S1AP_get_NAS_PDU(mrf.msg);
500 if (isvalue(nas_enc)) {
501 nas := dec_PDU_NAS_EPS(valueof(nas_enc));
502 if (match(nas, tr_NAS_EMM_SecurityProtected)) {
503 nas := f_nas_try_decaps(S1apAssociationTable[assoc_id].nus, nas);
504 }
Pau Espin Pedrol2edec462023-12-15 19:16:02 +0100505 /* DL/UlNasTransport are not interesting, don't send them */
506 if (not match(mrf.msg, (tr_S1AP_DlNasTransport, tr_S1AP_UlNasTransport))) {
507 /* send raw S1AP */
508 S1AP_CLIENT.send(mrf.msg) to vc_conn;
509 }
Harald Welte35498112019-07-02 14:26:39 +0800510 /* send decoded NAS */
511 S1AP_CLIENT.send(nas) to vc_conn;
512 } else {
513 /* send raw S1AP */
514 S1AP_CLIENT.send(mrf.msg) to vc_conn;
515 }
516 } else {
517 /* if not, call create_cb so it can create new ConnHdlr */
518 vc_conn := ops.create_cb.apply(mrf.msg, mme_ue_id, enb_ue_id, id);
519 f_s1ap_id_table_add(vc_conn, mme_ue_id, valueof(enb_ue_id));
520 S1AP_CLIENT.send(mrf.msg) to vc_conn;
521 }
Pau Espin Pedrol6cf7fde2023-12-21 19:17:08 +0100522 if (match(mrf.msg, tr_S1AP_UeContextReleaseCmd)) {
523 handle_S1AP_UeContextReleaseCmd(mrf.msg);
524 }
Harald Welte35498112019-07-02 14:26:39 +0800525 }
526 }
527 [] S1AP.receive(tr_SctpAssocChange) { }
528 [] S1AP.receive(tr_SctpPeerAddrChange) { }
Philipp Maier7147c922023-07-07 14:18:32 +0200529 [] S1AP_PROC.getcall(S1APEM_register:{?,?,?}) -> param(mme_id, enb_id, vc_conn) {
530 f_create_expect(mme_id, enb_id, vc_conn);
531 S1AP_PROC.reply(S1APEM_register:{mme_id, enb_id, vc_conn}) to vc_conn;
Harald Welte35498112019-07-02 14:26:39 +0800532 }
Philipp Maierfc7ada22023-07-24 11:32:12 +0200533 [] S1AP_PROC.getcall(S1APEM_register_proc:{?,?}) -> param(procedureCode, vc_conn) {
534 f_create_expect_proc(procedureCode, vc_conn);
535 S1AP_PROC.reply(S1APEM_register_proc:{procedureCode, vc_conn}) to vc_conn;
536 }
Harald Welte35498112019-07-02 14:26:39 +0800537 }
538
539 }
540}
541
542/* "Expect" Handling */
543
544type record ExpectData {
Philipp Maier7147c922023-07-07 14:18:32 +0200545 MME_UE_S1AP_ID mme_id optional,
546 ENB_UE_S1AP_ID enb_id optional,
Harald Welte35498112019-07-02 14:26:39 +0800547 S1AP_ConnHdlr vc_conn
548}
549
Philipp Maierfc7ada22023-07-24 11:32:12 +0200550/* represents a single S1AP PDU that we expect. When a matching PDU is seen, it is forwarded to the registered
551 * component */
552type record ExpectDataProc {
553 integer procedureCode optional,
554 S1AP_ConnHdlr vc_conn /* component handling this UE connection */
555};
556
Philipp Maier7147c922023-07-07 14:18:32 +0200557signature S1APEM_register(in MME_UE_S1AP_ID mme_id, in ENB_UE_S1AP_ID enb_id, in S1AP_ConnHdlr hdlr);
Philipp Maierfc7ada22023-07-24 11:32:12 +0200558signature S1APEM_register_proc(in integer procedureCode, in S1AP_ConnHdlr hdlr);
Harald Welte35498112019-07-02 14:26:39 +0800559
560type port S1APEM_PROC_PT procedure {
561 inout S1APEM_register;
Philipp Maierfc7ada22023-07-24 11:32:12 +0200562 inout S1APEM_register_proc;
Harald Welte35498112019-07-02 14:26:39 +0800563} with { extension "internal" };
564
Oliver Smith23e192e2023-02-13 15:00:46 +0100565/* Function that can be used as create_cb and will use the expect table */
Philipp Maier7147c922023-07-07 14:18:32 +0200566function ExpectedCreateCallback(S1AP_PDU msg,
567 template (omit) MME_UE_S1AP_ID mme_id,
568 template (omit) ENB_UE_S1AP_ID enb_id, charstring id)
Harald Welte35498112019-07-02 14:26:39 +0800569runs on S1AP_Emulation_CT return S1AP_ConnHdlr {
570 var S1AP_ConnHdlr ret := null;
571 var integer i;
572
573 for (i := 0; i < sizeof(S1apExpectTable); i := i+1) {
Philipp Maier7147c922023-07-07 14:18:32 +0200574 if (not ispresent(S1apExpectTable[i].mme_id) and not ispresent(S1apExpectTable[i].enb_id)) {
Harald Welte35498112019-07-02 14:26:39 +0800575 continue;
576 }
Philipp Maier7147c922023-07-07 14:18:32 +0200577
578 if (valueof(mme_id) == S1apExpectTable[i].mme_id and valueof(enb_id) == S1apExpectTable[i].enb_id) {
Harald Welte35498112019-07-02 14:26:39 +0800579 ret := S1apExpectTable[i].vc_conn;
580 /* Release this entry */
Philipp Maier7147c922023-07-07 14:18:32 +0200581 S1apExpectTable[i].mme_id := omit;
582 S1apExpectTable[i].enb_id := omit;
Harald Welte35498112019-07-02 14:26:39 +0800583 S1apExpectTable[i].vc_conn := null;
584 log("Found Expect[", i, "] for ", msg, " handled at ", ret);
585 return ret;
586 }
587 }
588 setverdict(fail, "Couldn't find Expect for ", msg);
589 mtc.stop;
590}
591
Philipp Maier7147c922023-07-07 14:18:32 +0200592private function f_create_expect(template (omit) MME_UE_S1AP_ID mme_id,
593 template (omit) ENB_UE_S1AP_ID enb_id,
594 S1AP_ConnHdlr hdlr)
Harald Welte35498112019-07-02 14:26:39 +0800595runs on S1AP_Emulation_CT {
596 var integer i;
597
598 /* Check an entry like this is not already presnt */
599 for (i := 0; i < sizeof(S1apExpectTable); i := i+1) {
Philipp Maier7147c922023-07-07 14:18:32 +0200600 if (not ispresent(S1apExpectTable[i].mme_id) and not ispresent(S1apExpectTable[i].enb_id)) {
601 continue;
602 }
603 if (valueof(mme_id) == S1apExpectTable[i].mme_id and valueof(enb_id) == S1apExpectTable[i].enb_id) {
604 setverdict(fail, "UE MME id / UE ENB id pair already present", mme_id, enb_id);
Harald Welte35498112019-07-02 14:26:39 +0800605 mtc.stop;
606 }
607 }
608 for (i := 0; i < sizeof(S1apExpectTable); i := i+1) {
Philipp Maier7147c922023-07-07 14:18:32 +0200609 if (not ispresent(S1apExpectTable[i].mme_id) and not ispresent(S1apExpectTable[i].enb_id)) {
610 S1apExpectTable[i].mme_id := valueof(mme_id);
611 S1apExpectTable[i].enb_id := valueof(enb_id);
Harald Welte35498112019-07-02 14:26:39 +0800612 S1apExpectTable[i].vc_conn := hdlr;
Philipp Maier7147c922023-07-07 14:18:32 +0200613 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 +0800614 return;
615 }
616 }
617 testcase.stop("No space left in S1apExpectTable")
618}
619
620/* client/conn_hdlr side function to use procedure port to create expect in emulation */
Philipp Maier7147c922023-07-07 14:18:32 +0200621function f_create_s1ap_expect(template (omit) MME_UE_S1AP_ID mme_id,
622 template (omit) ENB_UE_S1AP_ID enb_id) runs on S1AP_ConnHdlr {
623 S1AP_PROC.call(S1APEM_register:{mme_id, enb_id, self}) {
624 [] S1AP_PROC.getreply(S1APEM_register:{?,?,?}) {};
Harald Welte35498112019-07-02 14:26:39 +0800625 }
626}
627
Philipp Maierfc7ada22023-07-24 11:32:12 +0200628private function f_create_expect_proc(integer procedureCode, S1AP_ConnHdlr hdlr) runs on S1AP_Emulation_CT {
629 var integer i;
630
631 /* Check an entry like this is not already presnt */
632 for (i := 0; i < sizeof(S1apExpectTableProc); i := i+1) {
633 if (S1apExpectTableProc[i].vc_conn == null) {
634 continue;
635 }
636 if (S1apExpectTableProc[i].procedureCode == procedureCode) {
637 setverdict(fail, "procedureCode ", procedureCode, " already present");
638 mtc.stop;
639 }
640 }
641 for (i := 0; i < sizeof(S1apExpectTableProc); i := i+1) {
642 if (S1apExpectTableProc[i].vc_conn == null) {
643 S1apExpectTableProc[i].procedureCode := procedureCode;
644 S1apExpectTableProc[i].vc_conn := hdlr;
645 log("Created Expect[", i, "] for PDU:", procedureCode, " to be handled at ", hdlr);
646 return;
647 }
648 }
649 testcase.stop("No space left in S1apExpectTableProc")
650}
651
652/* client/conn_hdlr side function to use procedure port to create expect (PDU) in emulation */
653function f_create_s1ap_expect_proc(integer procedureCode, S1AP_ConnHdlr hdlr) runs on S1AP_ConnHdlr
654{
655 S1AP_PROC.call(S1APEM_register_proc:{procedureCode, self}) {
656 [] S1AP_PROC.getreply(S1APEM_register_proc:{?,?}) {};
657 }
658
659 log(procedureCode);
660}
Harald Welte35498112019-07-02 14:26:39 +0800661
662private function f_expect_table_init()
663runs on S1AP_Emulation_CT {
664 var integer i;
665 for (i := 0; i < sizeof(S1apExpectTable); i := i + 1) {
Philipp Maier7147c922023-07-07 14:18:32 +0200666 S1apExpectTable[i].mme_id := omit;
667 S1apExpectTable[i].enb_id := omit;
668 S1apExpectTable[i].vc_conn := null;
Harald Welte35498112019-07-02 14:26:39 +0800669 }
Philipp Maierfc7ada22023-07-24 11:32:12 +0200670
671 for (i := 0; i < sizeof(S1apExpectTableProc); i := i + 1) {
672 S1apExpectTableProc[i].procedureCode := omit;
673 S1apExpectTableProc[i].vc_conn := null;
674 }
Harald Welte35498112019-07-02 14:26:39 +0800675}
676
677function DummyUnitdataCallback(S1AP_PDU msg)
678runs on S1AP_Emulation_CT return template S1AP_PDU {
679 log("Ignoring S1AP ", msg);
680 return omit;
681}
682
683
684function f_S1AP_get_ENB_UE_S1AP_ID(S1AP_PDU s1ap) return template (omit) ENB_UE_S1AP_ID
685{
686 if (ischosen(s1ap.initiatingMessage)) {
687 var InitiatingMessage im := s1ap.initiatingMessage;
688 select (s1ap) {
689 case (tr_S1AP_InitialUE) {
690 return im.value_.InitialUEMessage.protocolIEs[0].value_.ENB_UE_S1AP_ID;
691 }
692 case (tr_S1AP_DlNasTransport) {
693 return im.value_.DownlinkNASTransport.protocolIEs[1].value_.ENB_UE_S1AP_ID;
694 }
695 case (tr_S1AP_UlNasTransport) {
696 return im.value_.UplinkNASTransport.protocolIEs[1].value_.ENB_UE_S1AP_ID;
697 }
698 case (tr_S1AP_IntialCtxSetupReq) {
699 return im.value_.initialContextSetupRequest.protocolIEs[1].value_.ENB_UE_S1AP_ID;
700 }
701 case (tr_S1AP_UeContextReleaseReq) {
702 return im.value_.UEContextReleaseRequest.protocolIEs[1].value_.ENB_UE_S1AP_ID;
703 }
Pau Espin Pedrola5bb8072023-12-21 19:15:31 +0100704 case (tr_S1AP_UeContextReleaseCmd) {
705 if (ispresent(im.value_.uEContextReleaseCommand.protocolIEs[0].value_.uE_S1AP_IDs.uE_S1AP_ID_pair)) {
706 return im.value_.uEContextReleaseCommand.protocolIEs[0].value_.uE_S1AP_IDs.uE_S1AP_ID_pair.eNB_UE_S1AP_ID;
707 } else {
708 return omit;
709 }
710 }
Harald Welte35498112019-07-02 14:26:39 +0800711 case (tr_S1AP_ConnEstInd) {
712 return im.value_.ConnectionEstablishmentIndication.protocolIEs[1].value_.ENB_UE_S1AP_ID;
713 }
714 /* TODO */
715 }
716 } else if (ischosen(s1ap.successfulOutcome)) {
717 var SuccessfulOutcome so := s1ap.successfulOutcome;
718 select (s1ap) {
719 case (tr_S1AP_InitialCtxSetupResp) {
720 return so.value_.initialContextSetupResponse.protocolIEs[1].value_.ENB_UE_S1AP_ID;
721 }
722 case (tr_S1AP_UeContextReleaseCompl) {
723 return so.value_.UEContextReleaseComplete.protocolIEs[1].value_.ENB_UE_S1AP_ID;
724 }
725 /* TODO */
726 }
727 } else if (ischosen(s1ap.unsuccessfulOutcome)) {
728 var UnsuccessfulOutcome uo := s1ap.unsuccessfulOutcome;
729 select (s1ap) {
730 case (tr_S1AP_InitialCtxSetupFail) {
731 return uo.value_.initialContextSetupFailure.protocolIEs[1].value_.ENB_UE_S1AP_ID;
732 }
733 /* TODO */
734 }
735 }
736 return omit;
737}
738
739function f_S1AP_get_MME_UE_S1AP_ID(S1AP_PDU s1ap) return template (omit) MME_UE_S1AP_ID
740{
741 if (ischosen(s1ap.initiatingMessage)) {
742 var InitiatingMessage im := s1ap.initiatingMessage;
743 select (s1ap) {
744 case (tr_S1AP_DlNasTransport) {
745 return im.value_.DownlinkNASTransport.protocolIEs[0].value_.MME_UE_S1AP_ID;
746 }
747 case (tr_S1AP_UlNasTransport) {
748 return im.value_.UplinkNASTransport.protocolIEs[0].value_.MME_UE_S1AP_ID;
749 }
750 case (tr_S1AP_IntialCtxSetupReq) {
751 return im.value_.initialContextSetupRequest.protocolIEs[0].value_.MME_UE_S1AP_ID;
752 }
753 case (tr_S1AP_UeContextReleaseReq) {
754 return im.value_.UEContextReleaseRequest.protocolIEs[0].value_.MME_UE_S1AP_ID;
755 }
Pau Espin Pedrola5bb8072023-12-21 19:15:31 +0100756 case (tr_S1AP_UeContextReleaseCmd) {
757 if (ispresent(im.value_.uEContextReleaseCommand.protocolIEs[0].value_.uE_S1AP_IDs.uE_S1AP_ID_pair)) {
758 return im.value_.uEContextReleaseCommand.protocolIEs[0].value_.uE_S1AP_IDs.uE_S1AP_ID_pair.mME_UE_S1AP_ID;
759 } else {
760 return im.value_.uEContextReleaseCommand.protocolIEs[0].value_.uE_S1AP_IDs.mME_UE_S1AP_ID;
761 }
762 }
Harald Welte35498112019-07-02 14:26:39 +0800763 case (tr_S1AP_ConnEstInd) {
764 return im.value_.ConnectionEstablishmentIndication.protocolIEs[0].value_.MME_UE_S1AP_ID;
765 }
766 /* TODO */
767 }
768 } else if (ischosen(s1ap.successfulOutcome)) {
769 var SuccessfulOutcome so := s1ap.successfulOutcome;
770 select (s1ap) {
771 case (tr_S1AP_InitialCtxSetupResp) {
772 return so.value_.initialContextSetupResponse.protocolIEs[0].value_.MME_UE_S1AP_ID;
773 }
774 case (tr_S1AP_UeContextReleaseCompl) {
775 return so.value_.UEContextReleaseComplete.protocolIEs[0].value_.MME_UE_S1AP_ID;
776 }
777 /* TODO */
778 }
779 } else if (ischosen(s1ap.unsuccessfulOutcome)) {
780 var UnsuccessfulOutcome uo := s1ap.unsuccessfulOutcome;
781 select (s1ap) {
782 case (tr_S1AP_InitialCtxSetupFail) {
783 return uo.value_.initialContextSetupFailure.protocolIEs[0].value_.MME_UE_S1AP_ID;
784 }
785 /* TODO */
786 }
787 }
788 return omit;
789}
790
791function f_S1AP_get_NAS_PDU(S1AP_PDU s1ap) return template (omit) NAS_PDU
792{
Pau Espin Pedrol2edec462023-12-15 19:16:02 +0100793 var integer i, j;
Harald Welte35498112019-07-02 14:26:39 +0800794
795 if (ischosen(s1ap.initiatingMessage)) {
796 var InitiatingMessage im := s1ap.initiatingMessage;
797 select (s1ap) {
798 case (tr_S1AP_DlNasTransport) {
799 var DownlinkNASTransport msg := im.value_.DownlinkNASTransport;
800 for (i := 0; i < lengthof(msg.protocolIEs); i := i+1) {
801 if (msg.protocolIEs[i].id == id_NAS_PDU) {
802 return msg.protocolIEs[i].value_.NAS_PDU;
803 }
804 }
805 }
806 case (tr_S1AP_UlNasTransport) {
807 var UplinkNASTransport msg := im.value_.UplinkNASTransport;
808 for (i := 0; i < lengthof(msg.protocolIEs); i := i+1) {
809 if (msg.protocolIEs[i].id == id_NAS_PDU) {
810 return msg.protocolIEs[i].value_.NAS_PDU;
811 }
812 }
813 }
Pau Espin Pedrol2edec462023-12-15 19:16:02 +0100814 case (tr_S1AP_IntialCtxSetupReq) {
815 var InitialContextSetupRequest msg := im.value_.initialContextSetupRequest;
816 for (i := 0; i < lengthof(msg.protocolIEs); i := i+1) {
817 if (msg.protocolIEs[i].id == id_E_RABToBeSetupListCtxtSUReq) {
818 var E_RABToBeSetupListCtxtSUReq rab_req := msg.protocolIEs[i].value_.E_RABToBeSetupListCtxtSUReq;
819 for (j := 0; j < lengthof(rab_req); j := j+1) {
820 var E_RABToBeSetupItemCtxtSUReq it := rab_req[j].value_.E_RABToBeSetupItemCtxtSUReq;
821 return it.nAS_PDU;
822 }
823 }
824 }
825 return omit;
826 }
Harald Welte35498112019-07-02 14:26:39 +0800827 }
828 }
829 return omit;
830}
831
832
833
834}