blob: e3e3216c26803498333b85cd0d7c6a16ef3ad35a [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};
Pau Espin Pedrole0163462024-01-09 10:29:39 +010070type record ResetNAScounts {
71/* empty */
72};
Harald Welte35498112019-07-02 14:26:39 +080073type union S1APEM_Config {
Pau Espin Pedrole0163462024-01-09 10:29:39 +010074 NAS_Keys set_nas_keys,
75 ResetNAScounts reset_nas_counts
Harald Welte35498112019-07-02 14:26:39 +080076};
77
78type enumerated S1APEM_EventUpDown {
79 S1APEM_EVENT_DOWN,
80 S1APEM_EVENT_UP
81}
82
83/* an event indicating us whether or not a connection is physically up or down,
84 * and whether we have received an ID_ACK */
85type union S1APEM_Event {
86 S1APEM_EventUpDown up_down
87}
88
89/* global test port e.g. for non-imsi/conn specific messages */
90type port S1AP_PT message {
91 inout S1AP_PDU, S1APEM_Event;
92} with { extension "internal" };
93
94
95/* represents a single S1AP Association */
96type record AssociationData {
97 S1AP_ConnHdlr comp_ref, /* component handling this UE connection */
98 uint24_t enb_ue_s1ap_id optional, /* eNB side S1AP ID */
99 uint32_t mme_ue_s1ap_id optional, /* MME side S1AP ID */
100 EUTRAN_CGI cgi optional,
101 TAI tai optional,
102 NAS_UE_State nus
Harald Welte35498112019-07-02 14:26:39 +0800103};
104
105type component S1AP_Emulation_CT {
106 /* Port facing to the UDP SUT */
107 port S1AP_CODEC_PT S1AP;
108 /* All S1AP_ConnHdlr S1AP ports connect here
109 * S1AP_Emulation_CT.main needs to figure out what messages
110 * to send where with CLIENT.send() to vc_conn */
111 port S1AP_Conn_PT S1AP_CLIENT;
112 /* currently tracked connections */
113 var AssociationData S1apAssociationTable[16];
Philipp Maierf935f0c2023-07-24 17:58:24 +0200114 /* pending expected S1AP Association (UE oriented) */
Harald Welte35498112019-07-02 14:26:39 +0800115 var ExpectData S1apExpectTable[8];
Philipp Maierfc7ada22023-07-24 11:32:12 +0200116 /* pending expected S1AP PDU */
117 var ExpectDataProc S1apExpectTableProc[8];
Harald Welte35498112019-07-02 14:26:39 +0800118 /* procedure based port to register for incoming connections */
119 port S1APEM_PROC_PT S1AP_PROC;
120 /* test port for unit data messages */
121 port S1AP_PT S1AP_UNIT;
122
123 var S1AP_conn_parameters g_pars;
124 var charstring g_s1ap_id;
125 var integer g_s1ap_conn_id := -1;
126}
127
128type function S1APCreateCallback(S1AP_PDU msg, template (omit) MME_UE_S1AP_ID mme_id,
129 template (omit) ENB_UE_S1AP_ID enb_id, charstring id)
130runs on S1AP_Emulation_CT return S1AP_ConnHdlr;
131
132type function S1APUnitdataCallback(S1AP_PDU msg)
133runs on S1AP_Emulation_CT return template S1AP_PDU;
134
135type record S1APOps {
136 S1APCreateCallback create_cb,
137 S1APUnitdataCallback unitdata_cb
138}
139
140type record S1AP_conn_parameters {
141 HostName remote_ip,
142 PortNumber remote_sctp_port,
143 HostName local_ip,
144 PortNumber local_sctp_port,
145 NAS_Role role
146}
147
148function tr_S1AP_RecvFrom_R(template S1AP_PDU msg)
149runs on S1AP_Emulation_CT return template S1AP_RecvFrom {
150 var template S1AP_RecvFrom mrf := {
151 connId := g_s1ap_conn_id,
152 remName := ?,
153 remPort := ?,
154 locName := ?,
155 locPort := ?,
156 msg := msg
157 }
158 return mrf;
159}
160
161private function f_s1ap_ids_known(template (omit) MME_UE_S1AP_ID mme_id,
162 template (omit) ENB_UE_S1AP_ID enb_id)
163runs on S1AP_Emulation_CT return boolean {
164 var integer i;
165 log("f_s1ap_ids_known(",mme_id,", ",enb_id,")");
166 for (i := 0; i < sizeof(S1apAssociationTable); i := i+1) {
167 log("tbl[",i,"]: mme=", S1apAssociationTable[i].mme_ue_s1ap_id,
168 ", enb=", S1apAssociationTable[i].enb_ue_s1ap_id);
169 /* skip empty records */
170 if (S1apAssociationTable[i].mme_ue_s1ap_id == omit and
171 S1apAssociationTable[i].enb_ue_s1ap_id == omit) {
172 log("skipping empty ", i);
173 continue;
174 }
175 if (S1apAssociationTable[i].mme_ue_s1ap_id == omit) {
176 log("entry ", i, " has no MME ID yet (enb=", S1apAssociationTable[i].enb_ue_s1ap_id);
177 /* Table doesn't yet know the MME side ID, let's look-up only
178 * based on the eNB side ID */
179 if (match(S1apAssociationTable[i].enb_ue_s1ap_id, enb_id)) {
180 /* update table with MME side ID */
181 S1apAssociationTable[i].mme_ue_s1ap_id := valueof(mme_id);
182 return true;
183 }
184 } else if (match(S1apAssociationTable[i].enb_ue_s1ap_id, enb_id) and
185 match(S1apAssociationTable[i].mme_ue_s1ap_id, mme_id)) {
186 return true;
187 }
188 }
189 return false;
190}
191
192private function f_comp_known(S1AP_ConnHdlr client)
193runs on S1AP_Emulation_CT return boolean {
194 var integer i;
195 for (i := 0; i < sizeof(S1apAssociationTable); i := i+1) {
196 if (S1apAssociationTable[i].comp_ref == client) {
197 return true;
198 }
199 }
200 return false;
201}
202
203private function f_assoc_id_by_s1ap_ids(template (omit) MME_UE_S1AP_ID mme_id,
204 template (omit) ENB_UE_S1AP_ID enb_id)
205runs on S1AP_Emulation_CT return integer {
206 var integer i;
207 for (i := 0; i < sizeof(S1apAssociationTable); i := i+1) {
Pau Espin Pedrol6cf7fde2023-12-21 19:17:08 +0100208 if (istemplatekind(enb_id, "omit") or
209 match(S1apAssociationTable[i].enb_ue_s1ap_id, enb_id)) {
Harald Welte35498112019-07-02 14:26:39 +0800210 if (istemplatekind(mme_id, "omit")) {
211 return i;
212 } else {
213 if (match(S1apAssociationTable[i].mme_ue_s1ap_id, mme_id)) {
214 return i;
215 }
216 }
217 }
218 }
219 setverdict(fail, "S1AP Association Table not found by ENB-ID=", enb_id, " MME-ID=", mme_id);
220 mtc.stop;
221}
222
223private function f_assoc_id_by_comp(S1AP_ConnHdlr client)
224runs on S1AP_Emulation_CT return integer {
225 var integer i;
226 for (i := 0; i < sizeof(S1apAssociationTable); i := i+1) {
227 if (S1apAssociationTable[i].comp_ref == client) {
228 return i;
229 }
230 }
231 setverdict(fail, "S1AP Association Table not found by component ", client);
232 mtc.stop;
233}
234
235private function f_assoc_by_comp(S1AP_ConnHdlr client)
236runs on S1AP_Emulation_CT return AssociationData {
237 var integer i := f_assoc_id_by_comp(client);
238 return S1apAssociationTable[i];
239}
240
241private function f_s1ap_id_table_add(S1AP_ConnHdlr comp_ref,
242 template (omit) MME_UE_S1AP_ID mme_id, ENB_UE_S1AP_ID enb_id)
243runs on S1AP_Emulation_CT return integer {
244 var integer i;
245 for (i := 0; i < sizeof(S1apAssociationTable); i := i+1) {
246 if (not isvalue(S1apAssociationTable[i].enb_ue_s1ap_id)) {
247 S1apAssociationTable[i].enb_ue_s1ap_id := enb_id;
248 if (istemplatekind(mme_id, "omit")) {
249 S1apAssociationTable[i].mme_ue_s1ap_id := omit;
250 } else {
251 S1apAssociationTable[i].mme_ue_s1ap_id := valueof(mme_id);
252 }
253 S1apAssociationTable[i].comp_ref := comp_ref;
254 return i;
255 }
256 }
257 testcase.stop("S1AP Association Table full!");
258 return -1;
259}
260
261private function f_s1ap_id_table_del(S1AP_ConnHdlr comp_ref, ENB_UE_S1AP_ID enb_id)
262runs on S1AP_Emulation_CT {
263 var integer i;
264 for (i := 0; i < sizeof(S1apAssociationTable); i := i+1) {
265 if (S1apAssociationTable[i].comp_ref == comp_ref and
Philipp Maier0ce67ab2023-07-07 13:02:11 +0200266 S1apAssociationTable[i].enb_ue_s1ap_id == enb_id) {
Harald Welte35498112019-07-02 14:26:39 +0800267 S1apAssociationTable[i].enb_ue_s1ap_id := omit;
268 S1apAssociationTable[i].mme_ue_s1ap_id := omit;
269 S1apAssociationTable[i].comp_ref := null;
270 return;
271 }
272 }
273 setverdict(fail, "S1AP Association Table: Couldn't find to-be-deleted entry!");
274 mtc.stop;
275}
276
277
278private function f_s1ap_id_table_init()
279runs on S1AP_Emulation_CT {
280 for (var integer i := 0; i < sizeof(S1apAssociationTable); i := i+1) {
281 S1apAssociationTable[i].mme_ue_s1ap_id := omit;
282 S1apAssociationTable[i].enb_ue_s1ap_id := omit;
283 S1apAssociationTable[i].cgi := omit;
284 S1apAssociationTable[i].tai := omit;
285 S1apAssociationTable[i].nus := valueof(t_NAS_UE_State(g_pars.role));
Philipp Maiereb930fd2023-07-24 17:59:40 +0200286 S1apAssociationTable[i].comp_ref := null;
Harald Welte35498112019-07-02 14:26:39 +0800287 }
288}
289
290private template (value) SctpTuple ts_SCTP(template (omit) integer ppid := 18) := {
291 sinfo_stream := omit,
292 sinfo_ppid := ppid,
293 remSocks := omit,
294 assocId := omit
295};
296
297private template PortEvent tr_SctpAssocChange := {
298 sctpEvent := {
299 sctpAssocChange := ?
300 }
301}
302private template PortEvent tr_SctpPeerAddrChange := {
303 sctpEvent := {
304 sctpPeerAddrChange := ?
305 }
306}
307
308private function f_s1ap_xceive(template (value) S1AP_PDU tx,
309 template S1AP_PDU rx_t := ?)
310runs on S1AP_Emulation_CT return S1AP_PDU {
311 timer T := 10.0;
312 var S1AP_RecvFrom mrf;
313
314 S1AP.send(t_S1AP_Send(g_s1ap_conn_id, tx));
315 alt {
316 [] S1AP.receive(tr_S1AP_RecvFrom_R(rx_t)) -> value mrf { }
317 [] S1AP.receive(tr_SctpAssocChange) { repeat; }
318 [] S1AP.receive(tr_SctpPeerAddrChange) { repeat; }
319 [] T.timeout {
320 setverdict(fail, "Timeout waiting for ", rx_t);
321 mtc.stop;
322 }
323 }
324 return mrf.msg;
325}
326
327/*
328private function f_nas_try_decaps(PDU_NAS_EPS nas) return PDU_NAS_EPS
329{
330 var PDU_NAS_EPS_SecurityProtectedNASMessage secp_nas;
331 if (not match(nas, tr_NAS_EMM_SecurityProtected)) {
332 return nas;
333 }
334 secp_nas := nas.ePS_messages.ePS_MobilityManagement.pDU_NAS_EPS_SecurityProtectedNASMessage;
335 select (secp_nas.securityHeaderType) {
336 case ('0011'B) {
337 var octetstring knas_int := '530ce32318f26264eab26bc116870b86'O;
338 var octetstring data_with_seq := int2oct(secp_nas.sequenceNumber, 1) & secp_nas.nAS_Message;
339 var OCT4 exp_mac := f_snow_3g_f9(knas_int, secp_nas.sequenceNumber, 0,
340 is_downlink:=true, data:=data_with_seq);
341 if (exp_mac != secp_nas.messageAuthenticationCode) {
342 setverdict(fail, "Received NAS MAC ", secp_nas.messageAuthenticationCode,
343 " doesn't match expected MAC ", exp_mac, ": ", nas);
344 mtc.stop;
345 }
346 return dec_PDU_NAS_EPS(secp_nas.nAS_Message);
347 }
348 case else {
349 setverdict(fail, "Implement SecHdrType for ", secp_nas);
350 mtc.stop;
351 }
352 }
353}
354*/
355
Philipp Maierbb8f05d2023-07-07 14:16:08 +0200356function handle_S1AP_UeContextReleaseCmd(template (present) S1AP_PDU rel_cmd) runs on S1AP_Emulation_CT {
357 if (ispresent(rel_cmd.initiatingMessage.value_.uEContextReleaseCommand.protocolIEs[0].value_.uE_S1AP_IDs.uE_S1AP_ID_pair)) {
358 var template MME_UE_S1AP_ID mme_ue_id;
359 var template ENB_UE_S1AP_ID enb_ue_id;
360 var integer assoc_id;
361 var S1AP_ConnHdlr vc_conn
362
363 mme_ue_id := rel_cmd.initiatingMessage.value_.uEContextReleaseCommand.protocolIEs[0].value_.uE_S1AP_IDs.uE_S1AP_ID_pair.mME_UE_S1AP_ID;
364 enb_ue_id := rel_cmd.initiatingMessage.value_.uEContextReleaseCommand.protocolIEs[0].value_.uE_S1AP_IDs.uE_S1AP_ID_pair.eNB_UE_S1AP_ID;
365
366 assoc_id := f_assoc_id_by_s1ap_ids(mme_ue_id, enb_ue_id);
367 vc_conn := S1apAssociationTable[assoc_id].comp_ref;
368
369 f_s1ap_id_table_del(vc_conn, valueof(enb_ue_id));
370 } else {
371 /* TODO: The UE CONTEXT RELEASE COMMAND (see also: 3GPP TS 36.413, section 9.1.4.6), may identify the
372 * context by either an uE_S1AP_ID_pair (MME_UE_S1AP_ID and ENB_UE_S1AP_ID) or an MME_UE_S1AP_ID alone.
373 * The latter case is not implemented here yet. */
374 setverdict(fail, "complete implementation of UeContextReleaseCmd handling");
375 mtc.stop;
376 }
377}
378
Philipp Maierfc7ada22023-07-24 11:32:12 +0200379private function SendToS1apExpectTableProc(S1AP_PDU msg) runs on S1AP_Emulation_CT {
380 var integer procedureCode;
381 var S1AP_ConnHdlr vc_conn;
382
383 if (ispresent(msg.initiatingMessage.procedureCode)) {
384 procedureCode := msg.initiatingMessage.procedureCode;
385 } else if (ispresent(msg.unsuccessfulOutcome.procedureCode)) {
386 procedureCode := msg.unsuccessfulOutcome.procedureCode;
387 } else if (ispresent(msg.successfulOutcome.procedureCode)) {
388 procedureCode := msg.successfulOutcome.procedureCode;
389 } else {
390 return;
391 }
392
393 for (var integer i := 0; i < sizeof(S1apExpectTableProc); i := i+1) {
394 if (S1apExpectTableProc[i].procedureCode == procedureCode) {
395 vc_conn := S1apExpectTableProc[i].vc_conn;
396 if (vc_conn != null) {
397 S1AP_CLIENT.send(msg) to vc_conn;
398 }
399 }
400 }
401
402 return;
403}
404
Harald Welte35498112019-07-02 14:26:39 +0800405function main(S1APOps ops, S1AP_conn_parameters p, charstring id) runs on S1AP_Emulation_CT {
406 var Result res;
407 g_pars := p;
408 g_s1ap_id := id;
409 f_s1ap_id_table_init();
410 f_expect_table_init();
411
412 map(self:S1AP, system:S1AP_CODEC_PT);
413 if (p.remote_sctp_port == -1) {
414 res := S1AP_CodecPort_CtrlFunct.f_IPL4_listen(S1AP, p.local_ip, p.local_sctp_port, { sctp := valueof(ts_SCTP) });
415 } else {
416 res := S1AP_CodecPort_CtrlFunct.f_IPL4_connect(S1AP, p.remote_ip, p.remote_sctp_port,
417 p.local_ip, p.local_sctp_port, -1, { sctp := valueof(ts_SCTP) });
418 }
419 if (not ispresent(res.connId)) {
420 setverdict(fail, "Could not connect S1AP socket, check your configuration");
421 mtc.stop;
422 }
423 g_s1ap_conn_id := res.connId;
424
425 /* notify user about SCTP establishment */
426 if (p.remote_sctp_port != -1) {
427 S1AP_UNIT.send(S1APEM_Event:{up_down:=S1APEM_EVENT_UP})
428 }
429
430 while (true) {
431 var S1AP_ConnHdlr vc_conn;
432 var PDU_NAS_EPS nas;
Philipp Maier7147c922023-07-07 14:18:32 +0200433 var MME_UE_S1AP_ID mme_id;
434 var ENB_UE_S1AP_ID enb_id;
Philipp Maierfc7ada22023-07-24 11:32:12 +0200435 var integer procedureCode;
Harald Welte35498112019-07-02 14:26:39 +0800436 var S1AP_RecvFrom mrf;
437 var S1AP_PDU msg;
438 var S1APEM_Config s1cfg;
439 var charstring vlr_name, mme_name;
440 var integer ai;
Pau Espin Pedrol3be4d922024-01-15 15:21:57 +0100441 var octetstring kasme;
Harald Welte35498112019-07-02 14:26:39 +0800442
443 alt {
444 /* Configuration primitive from client */
445 [] S1AP_CLIENT.receive(S1APEM_Config:{set_nas_keys:=?}) -> value s1cfg sender vc_conn {
446 var integer assoc_id := f_assoc_id_by_comp(vc_conn);
447 S1apAssociationTable[assoc_id].nus.k_nas_int := s1cfg.set_nas_keys.k_nas_int;
448 S1apAssociationTable[assoc_id].nus.k_nas_enc := s1cfg.set_nas_keys.k_nas_enc;
449 }
Pau Espin Pedrole0163462024-01-09 10:29:39 +0100450 /* Configuration primitive from client */
451 [] S1AP_CLIENT.receive(S1APEM_Config:{reset_nas_counts:=?}) -> value s1cfg sender vc_conn {
452 var integer assoc_id := f_assoc_id_by_comp(vc_conn);
453 S1apAssociationTable[assoc_id].nus.rx_count := 0;
454 S1apAssociationTable[assoc_id].nus.tx_count := 0;
455 }
Harald Welte35498112019-07-02 14:26:39 +0800456 /* S1AP from client: InitialUE */
457 [] S1AP_CLIENT.receive(tr_S1AP_InitialUE) -> value msg sender vc_conn {
458 /* create a table entry about this connection */
459 ai := f_s1ap_id_table_add(vc_conn, omit, valueof(f_S1AP_get_ENB_UE_S1AP_ID(msg)));
460 /* Store CGI + TAI so we can use it for generating UlNasTransport from NAS */
461 S1apAssociationTable[ai].tai := msg.initiatingMessage.value_.InitialUEMessage.protocolIEs[2].value_.TAI;
462 S1apAssociationTable[ai].cgi := msg.initiatingMessage.value_.InitialUEMessage.protocolIEs[3].value_.EUTRAN_CGI;
463 /* Pass message through */
464 S1AP.send(t_S1AP_Send(g_s1ap_conn_id, msg));
465 }
466 /* NAS from client: Wrap in S1AP Uplink NAS Transport */
467 [] S1AP_CLIENT.receive(PDU_NAS_EPS:?) -> value nas sender vc_conn {
468 var integer assoc_id := f_assoc_id_by_comp(vc_conn);
469 var AssociationData ad := S1apAssociationTable[assoc_id];
470 nas := f_nas_encaps(S1apAssociationTable[assoc_id].nus, nas, new_ctx := false);
471 var octetstring nas_enc := enc_PDU_NAS_EPS(nas);
472 S1AP.send(t_S1AP_Send(g_s1ap_conn_id,
473 ts_S1AP_UlNasTransport(ad.mme_ue_s1ap_id,
474 ad.enb_ue_s1ap_id,
475 nas_enc, ad.cgi, ad.tai)));
476 }
477 /* S1AP from client: pass on transparently */
478 [] S1AP_CLIENT.receive(S1AP_PDU:?) -> value msg sender vc_conn {
479 /* Pass message through */
480 /* FIXME: validate S1AP_IDs ? */
481 S1AP.send(t_S1AP_Send(g_s1ap_conn_id, msg));
482 }
483
484 /* non-UE related S1AP: pass through unmodified/unverified */
485 [] S1AP_UNIT.receive(S1AP_PDU:?) -> value msg sender vc_conn {
486 /* Pass message through */
487 S1AP.send(t_S1AP_Send(g_s1ap_conn_id, msg));
488 }
489
490 /* S1AP received from peer (MME) */
491 [] S1AP.receive(tr_S1AP_RecvFrom_R(?)) -> value mrf {
492 if (match(mrf.msg, tr_S1AP_nonUErelated)) {
493 /* non-UE-related S1AP message */
Philipp Maierfc7ada22023-07-24 11:32:12 +0200494 SendToS1apExpectTableProc(mrf.msg);
Harald Welte35498112019-07-02 14:26:39 +0800495 var template S1AP_PDU resp := ops.unitdata_cb.apply(mrf.msg);
496 if (isvalue(resp)) {
497 S1AP.send(t_S1AP_Send(g_s1ap_conn_id, valueof(resp)));
498 }
Harald Welte35498112019-07-02 14:26:39 +0800499 } else {
500 /* Ue-related S1AP message */
501 /* obtain MME + ENB UE S1AP ID */
502 var template (omit) MME_UE_S1AP_ID mme_ue_id := f_S1AP_get_MME_UE_S1AP_ID(mrf.msg);
503 var template (omit) ENB_UE_S1AP_ID enb_ue_id := f_S1AP_get_ENB_UE_S1AP_ID(mrf.msg);
504 /* check if those IDs are known in our table */
505 if (f_s1ap_ids_known(mme_ue_id, enb_ue_id)) {
506 /* if yes, dispatch to the ConnHdlr for this Ue-Connection */
507 var template (omit) octetstring nas_enc;
Philipp Maier7147c922023-07-07 14:18:32 +0200508 var integer assoc_id := f_assoc_id_by_s1ap_ids(mme_ue_id, enb_ue_id);
Harald Welte35498112019-07-02 14:26:39 +0800509 vc_conn := S1apAssociationTable[assoc_id].comp_ref;
510 nas_enc := f_S1AP_get_NAS_PDU(mrf.msg);
511 if (isvalue(nas_enc)) {
512 nas := dec_PDU_NAS_EPS(valueof(nas_enc));
513 if (match(nas, tr_NAS_EMM_SecurityProtected)) {
514 nas := f_nas_try_decaps(S1apAssociationTable[assoc_id].nus, nas);
515 }
Pau Espin Pedrol2edec462023-12-15 19:16:02 +0100516 /* DL/UlNasTransport are not interesting, don't send them */
517 if (not match(mrf.msg, (tr_S1AP_DlNasTransport, tr_S1AP_UlNasTransport))) {
518 /* send raw S1AP */
519 S1AP_CLIENT.send(mrf.msg) to vc_conn;
520 }
Harald Welte35498112019-07-02 14:26:39 +0800521 /* send decoded NAS */
522 S1AP_CLIENT.send(nas) to vc_conn;
523 } else {
524 /* send raw S1AP */
525 S1AP_CLIENT.send(mrf.msg) to vc_conn;
526 }
527 } else {
528 /* if not, call create_cb so it can create new ConnHdlr */
529 vc_conn := ops.create_cb.apply(mrf.msg, mme_ue_id, enb_ue_id, id);
530 f_s1ap_id_table_add(vc_conn, mme_ue_id, valueof(enb_ue_id));
531 S1AP_CLIENT.send(mrf.msg) to vc_conn;
532 }
Pau Espin Pedrol6cf7fde2023-12-21 19:17:08 +0100533 if (match(mrf.msg, tr_S1AP_UeContextReleaseCmd)) {
534 handle_S1AP_UeContextReleaseCmd(mrf.msg);
535 }
Harald Welte35498112019-07-02 14:26:39 +0800536 }
537 }
538 [] S1AP.receive(tr_SctpAssocChange) { }
539 [] S1AP.receive(tr_SctpPeerAddrChange) { }
Philipp Maier7147c922023-07-07 14:18:32 +0200540 [] S1AP_PROC.getcall(S1APEM_register:{?,?,?}) -> param(mme_id, enb_id, vc_conn) {
541 f_create_expect(mme_id, enb_id, vc_conn);
542 S1AP_PROC.reply(S1APEM_register:{mme_id, enb_id, vc_conn}) to vc_conn;
Harald Welte35498112019-07-02 14:26:39 +0800543 }
Philipp Maierfc7ada22023-07-24 11:32:12 +0200544 [] S1AP_PROC.getcall(S1APEM_register_proc:{?,?}) -> param(procedureCode, vc_conn) {
545 f_create_expect_proc(procedureCode, vc_conn);
546 S1AP_PROC.reply(S1APEM_register_proc:{procedureCode, vc_conn}) to vc_conn;
547 }
Pau Espin Pedrol3be4d922024-01-15 15:21:57 +0100548 [] S1AP_PROC.getcall(S1APEM_derive_nas_token:{?, ?, -}) -> param(kasme, vc_conn) {
549 var integer assoc_id := f_assoc_id_by_comp(vc_conn);
550 var OCT32 nas_token := f_kdf_nas_token(kasme, S1apAssociationTable[assoc_id].nus.tx_count)
551 S1apAssociationTable[assoc_id].nus.tx_count := S1apAssociationTable[assoc_id].nus.tx_count + 1;
552 S1AP_PROC.reply(S1APEM_derive_nas_token:{kasme, vc_conn, nas_token}) to vc_conn;
553 }
Harald Welte35498112019-07-02 14:26:39 +0800554 }
Harald Welte35498112019-07-02 14:26:39 +0800555 }
556}
557
558/* "Expect" Handling */
559
560type record ExpectData {
Philipp Maier7147c922023-07-07 14:18:32 +0200561 MME_UE_S1AP_ID mme_id optional,
562 ENB_UE_S1AP_ID enb_id optional,
Harald Welte35498112019-07-02 14:26:39 +0800563 S1AP_ConnHdlr vc_conn
564}
565
Philipp Maierfc7ada22023-07-24 11:32:12 +0200566/* represents a single S1AP PDU that we expect. When a matching PDU is seen, it is forwarded to the registered
567 * component */
568type record ExpectDataProc {
569 integer procedureCode optional,
570 S1AP_ConnHdlr vc_conn /* component handling this UE connection */
571};
572
Philipp Maier7147c922023-07-07 14:18:32 +0200573signature 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 +0200574signature S1APEM_register_proc(in integer procedureCode, in S1AP_ConnHdlr hdlr);
Pau Espin Pedrol3be4d922024-01-15 15:21:57 +0100575signature S1APEM_derive_nas_token(in octetstring kasme, in S1AP_ConnHdlr hdlr, out OCT32 nas_token);
Harald Welte35498112019-07-02 14:26:39 +0800576
577type port S1APEM_PROC_PT procedure {
578 inout S1APEM_register;
Philipp Maierfc7ada22023-07-24 11:32:12 +0200579 inout S1APEM_register_proc;
Pau Espin Pedrol3be4d922024-01-15 15:21:57 +0100580 inout S1APEM_derive_nas_token;
Harald Welte35498112019-07-02 14:26:39 +0800581} with { extension "internal" };
582
Oliver Smith23e192e2023-02-13 15:00:46 +0100583/* Function that can be used as create_cb and will use the expect table */
Philipp Maier7147c922023-07-07 14:18:32 +0200584function ExpectedCreateCallback(S1AP_PDU msg,
585 template (omit) MME_UE_S1AP_ID mme_id,
586 template (omit) ENB_UE_S1AP_ID enb_id, charstring id)
Harald Welte35498112019-07-02 14:26:39 +0800587runs on S1AP_Emulation_CT return S1AP_ConnHdlr {
588 var S1AP_ConnHdlr ret := null;
589 var integer i;
590
591 for (i := 0; i < sizeof(S1apExpectTable); i := i+1) {
Philipp Maier7147c922023-07-07 14:18:32 +0200592 if (not ispresent(S1apExpectTable[i].mme_id) and not ispresent(S1apExpectTable[i].enb_id)) {
Harald Welte35498112019-07-02 14:26:39 +0800593 continue;
594 }
Philipp Maier7147c922023-07-07 14:18:32 +0200595
596 if (valueof(mme_id) == S1apExpectTable[i].mme_id and valueof(enb_id) == S1apExpectTable[i].enb_id) {
Harald Welte35498112019-07-02 14:26:39 +0800597 ret := S1apExpectTable[i].vc_conn;
598 /* Release this entry */
Philipp Maier7147c922023-07-07 14:18:32 +0200599 S1apExpectTable[i].mme_id := omit;
600 S1apExpectTable[i].enb_id := omit;
Harald Welte35498112019-07-02 14:26:39 +0800601 S1apExpectTable[i].vc_conn := null;
602 log("Found Expect[", i, "] for ", msg, " handled at ", ret);
603 return ret;
604 }
605 }
606 setverdict(fail, "Couldn't find Expect for ", msg);
607 mtc.stop;
608}
609
Philipp Maier7147c922023-07-07 14:18:32 +0200610private function f_create_expect(template (omit) MME_UE_S1AP_ID mme_id,
611 template (omit) ENB_UE_S1AP_ID enb_id,
612 S1AP_ConnHdlr hdlr)
Harald Welte35498112019-07-02 14:26:39 +0800613runs on S1AP_Emulation_CT {
614 var integer i;
615
616 /* Check an entry like this is not already presnt */
617 for (i := 0; i < sizeof(S1apExpectTable); i := i+1) {
Philipp Maier7147c922023-07-07 14:18:32 +0200618 if (not ispresent(S1apExpectTable[i].mme_id) and not ispresent(S1apExpectTable[i].enb_id)) {
619 continue;
620 }
621 if (valueof(mme_id) == S1apExpectTable[i].mme_id and valueof(enb_id) == S1apExpectTable[i].enb_id) {
622 setverdict(fail, "UE MME id / UE ENB id pair already present", mme_id, enb_id);
Harald Welte35498112019-07-02 14:26:39 +0800623 mtc.stop;
624 }
625 }
626 for (i := 0; i < sizeof(S1apExpectTable); i := i+1) {
Philipp Maier7147c922023-07-07 14:18:32 +0200627 if (not ispresent(S1apExpectTable[i].mme_id) and not ispresent(S1apExpectTable[i].enb_id)) {
628 S1apExpectTable[i].mme_id := valueof(mme_id);
629 S1apExpectTable[i].enb_id := valueof(enb_id);
Harald Welte35498112019-07-02 14:26:39 +0800630 S1apExpectTable[i].vc_conn := hdlr;
Philipp Maier7147c922023-07-07 14:18:32 +0200631 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 +0800632 return;
633 }
634 }
635 testcase.stop("No space left in S1apExpectTable")
636}
637
638/* client/conn_hdlr side function to use procedure port to create expect in emulation */
Philipp Maier7147c922023-07-07 14:18:32 +0200639function f_create_s1ap_expect(template (omit) MME_UE_S1AP_ID mme_id,
640 template (omit) ENB_UE_S1AP_ID enb_id) runs on S1AP_ConnHdlr {
641 S1AP_PROC.call(S1APEM_register:{mme_id, enb_id, self}) {
642 [] S1AP_PROC.getreply(S1APEM_register:{?,?,?}) {};
Harald Welte35498112019-07-02 14:26:39 +0800643 }
644}
645
Philipp Maierfc7ada22023-07-24 11:32:12 +0200646private function f_create_expect_proc(integer procedureCode, S1AP_ConnHdlr hdlr) runs on S1AP_Emulation_CT {
647 var integer i;
648
649 /* Check an entry like this is not already presnt */
650 for (i := 0; i < sizeof(S1apExpectTableProc); i := i+1) {
651 if (S1apExpectTableProc[i].vc_conn == null) {
652 continue;
653 }
654 if (S1apExpectTableProc[i].procedureCode == procedureCode) {
655 setverdict(fail, "procedureCode ", procedureCode, " already present");
656 mtc.stop;
657 }
658 }
659 for (i := 0; i < sizeof(S1apExpectTableProc); i := i+1) {
660 if (S1apExpectTableProc[i].vc_conn == null) {
661 S1apExpectTableProc[i].procedureCode := procedureCode;
662 S1apExpectTableProc[i].vc_conn := hdlr;
663 log("Created Expect[", i, "] for PDU:", procedureCode, " to be handled at ", hdlr);
664 return;
665 }
666 }
667 testcase.stop("No space left in S1apExpectTableProc")
668}
669
670/* client/conn_hdlr side function to use procedure port to create expect (PDU) in emulation */
671function f_create_s1ap_expect_proc(integer procedureCode, S1AP_ConnHdlr hdlr) runs on S1AP_ConnHdlr
672{
673 S1AP_PROC.call(S1APEM_register_proc:{procedureCode, self}) {
674 [] S1AP_PROC.getreply(S1APEM_register_proc:{?,?}) {};
675 }
676
677 log(procedureCode);
678}
Harald Welte35498112019-07-02 14:26:39 +0800679
Pau Espin Pedrol3be4d922024-01-15 15:21:57 +0100680/* Derive NAS Token (and post-increment ul_count): */
681function f_s1apem_derive_nas_token(in octetstring kasme) runs on S1AP_ConnHdlr return OCT32
682{
683 var OCT32 nas_token;
684 S1AP_PROC.call(S1APEM_derive_nas_token:{kasme, self, -}) {
685 [] S1AP_PROC.getreply(S1APEM_derive_nas_token:{kasme, self, ?}) -> param(nas_token) {
686 return nas_token;
687 };
688 }
689}
690
Harald Welte35498112019-07-02 14:26:39 +0800691private function f_expect_table_init()
692runs on S1AP_Emulation_CT {
693 var integer i;
694 for (i := 0; i < sizeof(S1apExpectTable); i := i + 1) {
Philipp Maier7147c922023-07-07 14:18:32 +0200695 S1apExpectTable[i].mme_id := omit;
696 S1apExpectTable[i].enb_id := omit;
697 S1apExpectTable[i].vc_conn := null;
Harald Welte35498112019-07-02 14:26:39 +0800698 }
Philipp Maierfc7ada22023-07-24 11:32:12 +0200699
700 for (i := 0; i < sizeof(S1apExpectTableProc); i := i + 1) {
701 S1apExpectTableProc[i].procedureCode := omit;
702 S1apExpectTableProc[i].vc_conn := null;
703 }
Harald Welte35498112019-07-02 14:26:39 +0800704}
705
706function DummyUnitdataCallback(S1AP_PDU msg)
707runs on S1AP_Emulation_CT return template S1AP_PDU {
708 log("Ignoring S1AP ", msg);
709 return omit;
710}
711
712
713function f_S1AP_get_ENB_UE_S1AP_ID(S1AP_PDU s1ap) return template (omit) ENB_UE_S1AP_ID
714{
715 if (ischosen(s1ap.initiatingMessage)) {
716 var InitiatingMessage im := s1ap.initiatingMessage;
717 select (s1ap) {
718 case (tr_S1AP_InitialUE) {
719 return im.value_.InitialUEMessage.protocolIEs[0].value_.ENB_UE_S1AP_ID;
720 }
721 case (tr_S1AP_DlNasTransport) {
722 return im.value_.DownlinkNASTransport.protocolIEs[1].value_.ENB_UE_S1AP_ID;
723 }
724 case (tr_S1AP_UlNasTransport) {
725 return im.value_.UplinkNASTransport.protocolIEs[1].value_.ENB_UE_S1AP_ID;
726 }
727 case (tr_S1AP_IntialCtxSetupReq) {
728 return im.value_.initialContextSetupRequest.protocolIEs[1].value_.ENB_UE_S1AP_ID;
729 }
730 case (tr_S1AP_UeContextReleaseReq) {
731 return im.value_.UEContextReleaseRequest.protocolIEs[1].value_.ENB_UE_S1AP_ID;
732 }
Pau Espin Pedrola5bb8072023-12-21 19:15:31 +0100733 case (tr_S1AP_UeContextReleaseCmd) {
734 if (ispresent(im.value_.uEContextReleaseCommand.protocolIEs[0].value_.uE_S1AP_IDs.uE_S1AP_ID_pair)) {
735 return im.value_.uEContextReleaseCommand.protocolIEs[0].value_.uE_S1AP_IDs.uE_S1AP_ID_pair.eNB_UE_S1AP_ID;
736 } else {
737 return omit;
738 }
739 }
Harald Welte35498112019-07-02 14:26:39 +0800740 case (tr_S1AP_ConnEstInd) {
741 return im.value_.ConnectionEstablishmentIndication.protocolIEs[1].value_.ENB_UE_S1AP_ID;
742 }
743 /* TODO */
744 }
745 } else if (ischosen(s1ap.successfulOutcome)) {
746 var SuccessfulOutcome so := s1ap.successfulOutcome;
747 select (s1ap) {
748 case (tr_S1AP_InitialCtxSetupResp) {
749 return so.value_.initialContextSetupResponse.protocolIEs[1].value_.ENB_UE_S1AP_ID;
750 }
751 case (tr_S1AP_UeContextReleaseCompl) {
752 return so.value_.UEContextReleaseComplete.protocolIEs[1].value_.ENB_UE_S1AP_ID;
753 }
754 /* TODO */
755 }
756 } else if (ischosen(s1ap.unsuccessfulOutcome)) {
757 var UnsuccessfulOutcome uo := s1ap.unsuccessfulOutcome;
758 select (s1ap) {
759 case (tr_S1AP_InitialCtxSetupFail) {
760 return uo.value_.initialContextSetupFailure.protocolIEs[1].value_.ENB_UE_S1AP_ID;
761 }
762 /* TODO */
763 }
764 }
765 return omit;
766}
767
768function f_S1AP_get_MME_UE_S1AP_ID(S1AP_PDU s1ap) return template (omit) MME_UE_S1AP_ID
769{
770 if (ischosen(s1ap.initiatingMessage)) {
771 var InitiatingMessage im := s1ap.initiatingMessage;
772 select (s1ap) {
773 case (tr_S1AP_DlNasTransport) {
774 return im.value_.DownlinkNASTransport.protocolIEs[0].value_.MME_UE_S1AP_ID;
775 }
776 case (tr_S1AP_UlNasTransport) {
777 return im.value_.UplinkNASTransport.protocolIEs[0].value_.MME_UE_S1AP_ID;
778 }
779 case (tr_S1AP_IntialCtxSetupReq) {
780 return im.value_.initialContextSetupRequest.protocolIEs[0].value_.MME_UE_S1AP_ID;
781 }
782 case (tr_S1AP_UeContextReleaseReq) {
783 return im.value_.UEContextReleaseRequest.protocolIEs[0].value_.MME_UE_S1AP_ID;
784 }
Pau Espin Pedrola5bb8072023-12-21 19:15:31 +0100785 case (tr_S1AP_UeContextReleaseCmd) {
786 if (ispresent(im.value_.uEContextReleaseCommand.protocolIEs[0].value_.uE_S1AP_IDs.uE_S1AP_ID_pair)) {
787 return im.value_.uEContextReleaseCommand.protocolIEs[0].value_.uE_S1AP_IDs.uE_S1AP_ID_pair.mME_UE_S1AP_ID;
788 } else {
789 return im.value_.uEContextReleaseCommand.protocolIEs[0].value_.uE_S1AP_IDs.mME_UE_S1AP_ID;
790 }
791 }
Harald Welte35498112019-07-02 14:26:39 +0800792 case (tr_S1AP_ConnEstInd) {
793 return im.value_.ConnectionEstablishmentIndication.protocolIEs[0].value_.MME_UE_S1AP_ID;
794 }
795 /* TODO */
796 }
797 } else if (ischosen(s1ap.successfulOutcome)) {
798 var SuccessfulOutcome so := s1ap.successfulOutcome;
799 select (s1ap) {
800 case (tr_S1AP_InitialCtxSetupResp) {
801 return so.value_.initialContextSetupResponse.protocolIEs[0].value_.MME_UE_S1AP_ID;
802 }
803 case (tr_S1AP_UeContextReleaseCompl) {
804 return so.value_.UEContextReleaseComplete.protocolIEs[0].value_.MME_UE_S1AP_ID;
805 }
806 /* TODO */
807 }
808 } else if (ischosen(s1ap.unsuccessfulOutcome)) {
809 var UnsuccessfulOutcome uo := s1ap.unsuccessfulOutcome;
810 select (s1ap) {
811 case (tr_S1AP_InitialCtxSetupFail) {
812 return uo.value_.initialContextSetupFailure.protocolIEs[0].value_.MME_UE_S1AP_ID;
813 }
814 /* TODO */
815 }
816 }
817 return omit;
818}
819
820function f_S1AP_get_NAS_PDU(S1AP_PDU s1ap) return template (omit) NAS_PDU
821{
Pau Espin Pedrol2edec462023-12-15 19:16:02 +0100822 var integer i, j;
Harald Welte35498112019-07-02 14:26:39 +0800823
824 if (ischosen(s1ap.initiatingMessage)) {
825 var InitiatingMessage im := s1ap.initiatingMessage;
826 select (s1ap) {
827 case (tr_S1AP_DlNasTransport) {
828 var DownlinkNASTransport msg := im.value_.DownlinkNASTransport;
829 for (i := 0; i < lengthof(msg.protocolIEs); i := i+1) {
830 if (msg.protocolIEs[i].id == id_NAS_PDU) {
831 return msg.protocolIEs[i].value_.NAS_PDU;
832 }
833 }
834 }
835 case (tr_S1AP_UlNasTransport) {
836 var UplinkNASTransport msg := im.value_.UplinkNASTransport;
837 for (i := 0; i < lengthof(msg.protocolIEs); i := i+1) {
838 if (msg.protocolIEs[i].id == id_NAS_PDU) {
839 return msg.protocolIEs[i].value_.NAS_PDU;
840 }
841 }
842 }
Pau Espin Pedrol2edec462023-12-15 19:16:02 +0100843 case (tr_S1AP_IntialCtxSetupReq) {
844 var InitialContextSetupRequest msg := im.value_.initialContextSetupRequest;
845 for (i := 0; i < lengthof(msg.protocolIEs); i := i+1) {
846 if (msg.protocolIEs[i].id == id_E_RABToBeSetupListCtxtSUReq) {
847 var E_RABToBeSetupListCtxtSUReq rab_req := msg.protocolIEs[i].value_.E_RABToBeSetupListCtxtSUReq;
848 for (j := 0; j < lengthof(rab_req); j := j+1) {
849 var E_RABToBeSetupItemCtxtSUReq it := rab_req[j].value_.E_RABToBeSetupItemCtxtSUReq;
850 return it.nAS_PDU;
851 }
852 }
853 }
854 return omit;
855 }
Harald Welte35498112019-07-02 14:26:39 +0800856 }
857 }
858 return omit;
859}
860
861
862
863}