blob: 3cce09124c74b4a577824aa47c7c9ff4f77a84dd [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;
Vadim Yanitskiy741aa572024-06-12 06:16:43 +070043import from SCTP_Templates all;
Harald Welte35498112019-07-02 14:26:39 +080044
45import from NAS_EPS_Types all;
46import from NAS_Templates all;
47
48import from LTE_CryptoFunctions all;
49
50import from General_Types all;
51import from Osmocom_Types all;
52import from IPL4asp_Types all;
53import from DNS_Helpers all;
54
55
56type component S1AP_ConnHdlr {
57 port S1AP_Conn_PT S1AP;
58 /* procedure based port to register for incoming connections */
59 port S1APEM_PROC_PT S1AP_PROC;
60}
61
62/* port between individual per-connection components and this dispatcher */
63type port S1AP_Conn_PT message {
64 inout S1AP_PDU, PDU_NAS_EPS, S1APEM_Config;
65} with { extension "internal" };
66
67type record NAS_Keys {
68 octetstring k_nas_int,
69 octetstring k_nas_enc
70};
Pau Espin Pedrole0163462024-01-09 10:29:39 +010071type record ResetNAScounts {
72/* empty */
73};
Harald Welte35498112019-07-02 14:26:39 +080074type union S1APEM_Config {
Pau Espin Pedrole0163462024-01-09 10:29:39 +010075 NAS_Keys set_nas_keys,
Pau Espin Pedrol35618872024-01-15 15:25:18 +010076 ResetNAScounts reset_nas_counts,
77 NAS_ALG_INT set_nas_alg_int
Harald Welte35498112019-07-02 14:26:39 +080078};
79
80type enumerated S1APEM_EventUpDown {
81 S1APEM_EVENT_DOWN,
82 S1APEM_EVENT_UP
83}
84
85/* an event indicating us whether or not a connection is physically up or down,
86 * and whether we have received an ID_ACK */
87type union S1APEM_Event {
88 S1APEM_EventUpDown up_down
89}
90
91/* global test port e.g. for non-imsi/conn specific messages */
92type port S1AP_PT message {
93 inout S1AP_PDU, S1APEM_Event;
94} with { extension "internal" };
95
96
97/* represents a single S1AP Association */
98type record AssociationData {
99 S1AP_ConnHdlr comp_ref, /* component handling this UE connection */
100 uint24_t enb_ue_s1ap_id optional, /* eNB side S1AP ID */
101 uint32_t mme_ue_s1ap_id optional, /* MME side S1AP ID */
102 EUTRAN_CGI cgi optional,
103 TAI tai optional,
104 NAS_UE_State nus
Harald Welte35498112019-07-02 14:26:39 +0800105};
106
107type component S1AP_Emulation_CT {
108 /* Port facing to the UDP SUT */
109 port S1AP_CODEC_PT S1AP;
110 /* All S1AP_ConnHdlr S1AP ports connect here
111 * S1AP_Emulation_CT.main needs to figure out what messages
112 * to send where with CLIENT.send() to vc_conn */
113 port S1AP_Conn_PT S1AP_CLIENT;
114 /* currently tracked connections */
115 var AssociationData S1apAssociationTable[16];
Philipp Maierf935f0c2023-07-24 17:58:24 +0200116 /* pending expected S1AP Association (UE oriented) */
Harald Welte35498112019-07-02 14:26:39 +0800117 var ExpectData S1apExpectTable[8];
Philipp Maierfc7ada22023-07-24 11:32:12 +0200118 /* pending expected S1AP PDU */
119 var ExpectDataProc S1apExpectTableProc[8];
Harald Welte35498112019-07-02 14:26:39 +0800120 /* procedure based port to register for incoming connections */
121 port S1APEM_PROC_PT S1AP_PROC;
122 /* test port for unit data messages */
123 port S1AP_PT S1AP_UNIT;
124
125 var S1AP_conn_parameters g_pars;
126 var charstring g_s1ap_id;
127 var integer g_s1ap_conn_id := -1;
128}
129
130type function S1APCreateCallback(S1AP_PDU msg, template (omit) MME_UE_S1AP_ID mme_id,
131 template (omit) ENB_UE_S1AP_ID enb_id, charstring id)
132runs on S1AP_Emulation_CT return S1AP_ConnHdlr;
133
134type function S1APUnitdataCallback(S1AP_PDU msg)
135runs on S1AP_Emulation_CT return template S1AP_PDU;
136
137type record S1APOps {
138 S1APCreateCallback create_cb,
139 S1APUnitdataCallback unitdata_cb
140}
141
142type record S1AP_conn_parameters {
143 HostName remote_ip,
144 PortNumber remote_sctp_port,
145 HostName local_ip,
146 PortNumber local_sctp_port,
147 NAS_Role role
148}
149
150function tr_S1AP_RecvFrom_R(template S1AP_PDU msg)
151runs on S1AP_Emulation_CT return template S1AP_RecvFrom {
152 var template S1AP_RecvFrom mrf := {
153 connId := g_s1ap_conn_id,
154 remName := ?,
155 remPort := ?,
156 locName := ?,
157 locPort := ?,
158 msg := msg
159 }
160 return mrf;
161}
162
163private function f_s1ap_ids_known(template (omit) MME_UE_S1AP_ID mme_id,
164 template (omit) ENB_UE_S1AP_ID enb_id)
165runs on S1AP_Emulation_CT return boolean {
166 var integer i;
167 log("f_s1ap_ids_known(",mme_id,", ",enb_id,")");
168 for (i := 0; i < sizeof(S1apAssociationTable); i := i+1) {
169 log("tbl[",i,"]: mme=", S1apAssociationTable[i].mme_ue_s1ap_id,
170 ", enb=", S1apAssociationTable[i].enb_ue_s1ap_id);
171 /* skip empty records */
172 if (S1apAssociationTable[i].mme_ue_s1ap_id == omit and
173 S1apAssociationTable[i].enb_ue_s1ap_id == omit) {
174 log("skipping empty ", i);
175 continue;
176 }
177 if (S1apAssociationTable[i].mme_ue_s1ap_id == omit) {
178 log("entry ", i, " has no MME ID yet (enb=", S1apAssociationTable[i].enb_ue_s1ap_id);
179 /* Table doesn't yet know the MME side ID, let's look-up only
180 * based on the eNB side ID */
181 if (match(S1apAssociationTable[i].enb_ue_s1ap_id, enb_id)) {
182 /* update table with MME side ID */
183 S1apAssociationTable[i].mme_ue_s1ap_id := valueof(mme_id);
184 return true;
185 }
186 } else if (match(S1apAssociationTable[i].enb_ue_s1ap_id, enb_id) and
187 match(S1apAssociationTable[i].mme_ue_s1ap_id, mme_id)) {
188 return true;
189 }
190 }
191 return false;
192}
193
194private function f_comp_known(S1AP_ConnHdlr client)
195runs on S1AP_Emulation_CT return boolean {
196 var integer i;
197 for (i := 0; i < sizeof(S1apAssociationTable); i := i+1) {
198 if (S1apAssociationTable[i].comp_ref == client) {
199 return true;
200 }
201 }
202 return false;
203}
204
205private function f_assoc_id_by_s1ap_ids(template (omit) MME_UE_S1AP_ID mme_id,
206 template (omit) ENB_UE_S1AP_ID enb_id)
207runs on S1AP_Emulation_CT return integer {
208 var integer i;
209 for (i := 0; i < sizeof(S1apAssociationTable); i := i+1) {
Pau Espin Pedrol6cf7fde2023-12-21 19:17:08 +0100210 if (istemplatekind(enb_id, "omit") or
211 match(S1apAssociationTable[i].enb_ue_s1ap_id, enb_id)) {
Harald Welte35498112019-07-02 14:26:39 +0800212 if (istemplatekind(mme_id, "omit")) {
213 return i;
214 } else {
215 if (match(S1apAssociationTable[i].mme_ue_s1ap_id, mme_id)) {
216 return i;
217 }
218 }
219 }
220 }
221 setverdict(fail, "S1AP Association Table not found by ENB-ID=", enb_id, " MME-ID=", mme_id);
222 mtc.stop;
223}
224
225private function f_assoc_id_by_comp(S1AP_ConnHdlr client)
226runs on S1AP_Emulation_CT return integer {
227 var integer i;
228 for (i := 0; i < sizeof(S1apAssociationTable); i := i+1) {
229 if (S1apAssociationTable[i].comp_ref == client) {
230 return i;
231 }
232 }
233 setverdict(fail, "S1AP Association Table not found by component ", client);
234 mtc.stop;
235}
236
237private function f_assoc_by_comp(S1AP_ConnHdlr client)
238runs on S1AP_Emulation_CT return AssociationData {
239 var integer i := f_assoc_id_by_comp(client);
240 return S1apAssociationTable[i];
241}
242
243private function f_s1ap_id_table_add(S1AP_ConnHdlr comp_ref,
244 template (omit) MME_UE_S1AP_ID mme_id, ENB_UE_S1AP_ID enb_id)
245runs on S1AP_Emulation_CT return integer {
246 var integer i;
247 for (i := 0; i < sizeof(S1apAssociationTable); i := i+1) {
248 if (not isvalue(S1apAssociationTable[i].enb_ue_s1ap_id)) {
249 S1apAssociationTable[i].enb_ue_s1ap_id := enb_id;
250 if (istemplatekind(mme_id, "omit")) {
251 S1apAssociationTable[i].mme_ue_s1ap_id := omit;
252 } else {
253 S1apAssociationTable[i].mme_ue_s1ap_id := valueof(mme_id);
254 }
255 S1apAssociationTable[i].comp_ref := comp_ref;
256 return i;
257 }
258 }
259 testcase.stop("S1AP Association Table full!");
260 return -1;
261}
262
263private function f_s1ap_id_table_del(S1AP_ConnHdlr comp_ref, ENB_UE_S1AP_ID enb_id)
264runs on S1AP_Emulation_CT {
265 var integer i;
266 for (i := 0; i < sizeof(S1apAssociationTable); i := i+1) {
267 if (S1apAssociationTable[i].comp_ref == comp_ref and
Philipp Maier0ce67ab2023-07-07 13:02:11 +0200268 S1apAssociationTable[i].enb_ue_s1ap_id == enb_id) {
Harald Welte35498112019-07-02 14:26:39 +0800269 S1apAssociationTable[i].enb_ue_s1ap_id := omit;
270 S1apAssociationTable[i].mme_ue_s1ap_id := omit;
271 S1apAssociationTable[i].comp_ref := null;
272 return;
273 }
274 }
275 setverdict(fail, "S1AP Association Table: Couldn't find to-be-deleted entry!");
276 mtc.stop;
277}
278
279
280private function f_s1ap_id_table_init()
281runs on S1AP_Emulation_CT {
282 for (var integer i := 0; i < sizeof(S1apAssociationTable); i := i+1) {
283 S1apAssociationTable[i].mme_ue_s1ap_id := omit;
284 S1apAssociationTable[i].enb_ue_s1ap_id := omit;
285 S1apAssociationTable[i].cgi := omit;
286 S1apAssociationTable[i].tai := omit;
287 S1apAssociationTable[i].nus := valueof(t_NAS_UE_State(g_pars.role));
Philipp Maiereb930fd2023-07-24 17:59:40 +0200288 S1apAssociationTable[i].comp_ref := null;
Harald Welte35498112019-07-02 14:26:39 +0800289 }
290}
291
Harald Welte35498112019-07-02 14:26:39 +0800292private function f_s1ap_xceive(template (value) S1AP_PDU tx,
293 template S1AP_PDU rx_t := ?)
294runs on S1AP_Emulation_CT return S1AP_PDU {
295 timer T := 10.0;
296 var S1AP_RecvFrom mrf;
297
298 S1AP.send(t_S1AP_Send(g_s1ap_conn_id, tx));
299 alt {
300 [] S1AP.receive(tr_S1AP_RecvFrom_R(rx_t)) -> value mrf { }
301 [] S1AP.receive(tr_SctpAssocChange) { repeat; }
302 [] S1AP.receive(tr_SctpPeerAddrChange) { repeat; }
303 [] T.timeout {
304 setverdict(fail, "Timeout waiting for ", rx_t);
305 mtc.stop;
306 }
307 }
308 return mrf.msg;
309}
310
311/*
312private function f_nas_try_decaps(PDU_NAS_EPS nas) return PDU_NAS_EPS
313{
314 var PDU_NAS_EPS_SecurityProtectedNASMessage secp_nas;
315 if (not match(nas, tr_NAS_EMM_SecurityProtected)) {
316 return nas;
317 }
318 secp_nas := nas.ePS_messages.ePS_MobilityManagement.pDU_NAS_EPS_SecurityProtectedNASMessage;
319 select (secp_nas.securityHeaderType) {
320 case ('0011'B) {
321 var octetstring knas_int := '530ce32318f26264eab26bc116870b86'O;
322 var octetstring data_with_seq := int2oct(secp_nas.sequenceNumber, 1) & secp_nas.nAS_Message;
323 var OCT4 exp_mac := f_snow_3g_f9(knas_int, secp_nas.sequenceNumber, 0,
324 is_downlink:=true, data:=data_with_seq);
325 if (exp_mac != secp_nas.messageAuthenticationCode) {
326 setverdict(fail, "Received NAS MAC ", secp_nas.messageAuthenticationCode,
327 " doesn't match expected MAC ", exp_mac, ": ", nas);
328 mtc.stop;
329 }
330 return dec_PDU_NAS_EPS(secp_nas.nAS_Message);
331 }
332 case else {
333 setverdict(fail, "Implement SecHdrType for ", secp_nas);
334 mtc.stop;
335 }
336 }
337}
338*/
339
Philipp Maierbb8f05d2023-07-07 14:16:08 +0200340function handle_S1AP_UeContextReleaseCmd(template (present) S1AP_PDU rel_cmd) runs on S1AP_Emulation_CT {
341 if (ispresent(rel_cmd.initiatingMessage.value_.uEContextReleaseCommand.protocolIEs[0].value_.uE_S1AP_IDs.uE_S1AP_ID_pair)) {
342 var template MME_UE_S1AP_ID mme_ue_id;
343 var template ENB_UE_S1AP_ID enb_ue_id;
344 var integer assoc_id;
345 var S1AP_ConnHdlr vc_conn
346
347 mme_ue_id := rel_cmd.initiatingMessage.value_.uEContextReleaseCommand.protocolIEs[0].value_.uE_S1AP_IDs.uE_S1AP_ID_pair.mME_UE_S1AP_ID;
348 enb_ue_id := rel_cmd.initiatingMessage.value_.uEContextReleaseCommand.protocolIEs[0].value_.uE_S1AP_IDs.uE_S1AP_ID_pair.eNB_UE_S1AP_ID;
349
350 assoc_id := f_assoc_id_by_s1ap_ids(mme_ue_id, enb_ue_id);
351 vc_conn := S1apAssociationTable[assoc_id].comp_ref;
352
353 f_s1ap_id_table_del(vc_conn, valueof(enb_ue_id));
354 } else {
355 /* TODO: The UE CONTEXT RELEASE COMMAND (see also: 3GPP TS 36.413, section 9.1.4.6), may identify the
356 * context by either an uE_S1AP_ID_pair (MME_UE_S1AP_ID and ENB_UE_S1AP_ID) or an MME_UE_S1AP_ID alone.
357 * The latter case is not implemented here yet. */
358 setverdict(fail, "complete implementation of UeContextReleaseCmd handling");
359 mtc.stop;
360 }
361}
362
Philipp Maierfc7ada22023-07-24 11:32:12 +0200363private function SendToS1apExpectTableProc(S1AP_PDU msg) runs on S1AP_Emulation_CT {
364 var integer procedureCode;
365 var S1AP_ConnHdlr vc_conn;
366
367 if (ispresent(msg.initiatingMessage.procedureCode)) {
368 procedureCode := msg.initiatingMessage.procedureCode;
369 } else if (ispresent(msg.unsuccessfulOutcome.procedureCode)) {
370 procedureCode := msg.unsuccessfulOutcome.procedureCode;
371 } else if (ispresent(msg.successfulOutcome.procedureCode)) {
372 procedureCode := msg.successfulOutcome.procedureCode;
373 } else {
374 return;
375 }
376
377 for (var integer i := 0; i < sizeof(S1apExpectTableProc); i := i+1) {
378 if (S1apExpectTableProc[i].procedureCode == procedureCode) {
379 vc_conn := S1apExpectTableProc[i].vc_conn;
380 if (vc_conn != null) {
381 S1AP_CLIENT.send(msg) to vc_conn;
382 }
383 }
384 }
385
386 return;
387}
388
Harald Welte35498112019-07-02 14:26:39 +0800389function main(S1APOps ops, S1AP_conn_parameters p, charstring id) runs on S1AP_Emulation_CT {
390 var Result res;
391 g_pars := p;
392 g_s1ap_id := id;
393 f_s1ap_id_table_init();
394 f_expect_table_init();
395
396 map(self:S1AP, system:S1AP_CODEC_PT);
397 if (p.remote_sctp_port == -1) {
Vadim Yanitskiy741aa572024-06-12 06:16:43 +0700398 res := S1AP_CodecPort_CtrlFunct.f_IPL4_listen(S1AP, p.local_ip, p.local_sctp_port,
399 { sctp := valueof(ts_SctpTuple(18)) });
Harald Welte35498112019-07-02 14:26:39 +0800400 } else {
401 res := S1AP_CodecPort_CtrlFunct.f_IPL4_connect(S1AP, p.remote_ip, p.remote_sctp_port,
Vadim Yanitskiy741aa572024-06-12 06:16:43 +0700402 p.local_ip, p.local_sctp_port, -1,
403 { sctp := valueof(ts_SctpTuple(18)) });
Harald Welte35498112019-07-02 14:26:39 +0800404 }
405 if (not ispresent(res.connId)) {
406 setverdict(fail, "Could not connect S1AP socket, check your configuration");
407 mtc.stop;
408 }
409 g_s1ap_conn_id := res.connId;
410
411 /* notify user about SCTP establishment */
412 if (p.remote_sctp_port != -1) {
413 S1AP_UNIT.send(S1APEM_Event:{up_down:=S1APEM_EVENT_UP})
414 }
415
416 while (true) {
417 var S1AP_ConnHdlr vc_conn;
418 var PDU_NAS_EPS nas;
Philipp Maier7147c922023-07-07 14:18:32 +0200419 var MME_UE_S1AP_ID mme_id;
420 var ENB_UE_S1AP_ID enb_id;
Philipp Maierfc7ada22023-07-24 11:32:12 +0200421 var integer procedureCode;
Harald Welte35498112019-07-02 14:26:39 +0800422 var S1AP_RecvFrom mrf;
423 var S1AP_PDU msg;
424 var S1APEM_Config s1cfg;
425 var charstring vlr_name, mme_name;
426 var integer ai;
Pau Espin Pedrol3be4d922024-01-15 15:21:57 +0100427 var octetstring kasme;
Harald Welte35498112019-07-02 14:26:39 +0800428
429 alt {
430 /* Configuration primitive from client */
431 [] S1AP_CLIENT.receive(S1APEM_Config:{set_nas_keys:=?}) -> value s1cfg sender vc_conn {
432 var integer assoc_id := f_assoc_id_by_comp(vc_conn);
433 S1apAssociationTable[assoc_id].nus.k_nas_int := s1cfg.set_nas_keys.k_nas_int;
434 S1apAssociationTable[assoc_id].nus.k_nas_enc := s1cfg.set_nas_keys.k_nas_enc;
435 }
Pau Espin Pedrole0163462024-01-09 10:29:39 +0100436 /* Configuration primitive from client */
437 [] S1AP_CLIENT.receive(S1APEM_Config:{reset_nas_counts:=?}) -> value s1cfg sender vc_conn {
438 var integer assoc_id := f_assoc_id_by_comp(vc_conn);
439 S1apAssociationTable[assoc_id].nus.rx_count := 0;
440 S1apAssociationTable[assoc_id].nus.tx_count := 0;
441 }
Pau Espin Pedrol35618872024-01-15 15:25:18 +0100442 /* Configuration primitive from client */
443 [] S1AP_CLIENT.receive(S1APEM_Config:{set_nas_alg_int:=?}) -> value s1cfg sender vc_conn {
444 var integer assoc_id := f_assoc_id_by_comp(vc_conn);
445 S1apAssociationTable[assoc_id].nus.alg_int := s1cfg.set_nas_alg_int;
446 }
Harald Welte35498112019-07-02 14:26:39 +0800447 /* S1AP from client: InitialUE */
448 [] S1AP_CLIENT.receive(tr_S1AP_InitialUE) -> value msg sender vc_conn {
449 /* create a table entry about this connection */
450 ai := f_s1ap_id_table_add(vc_conn, omit, valueof(f_S1AP_get_ENB_UE_S1AP_ID(msg)));
451 /* Store CGI + TAI so we can use it for generating UlNasTransport from NAS */
452 S1apAssociationTable[ai].tai := msg.initiatingMessage.value_.InitialUEMessage.protocolIEs[2].value_.TAI;
453 S1apAssociationTable[ai].cgi := msg.initiatingMessage.value_.InitialUEMessage.protocolIEs[3].value_.EUTRAN_CGI;
454 /* Pass message through */
455 S1AP.send(t_S1AP_Send(g_s1ap_conn_id, msg));
456 }
457 /* NAS from client: Wrap in S1AP Uplink NAS Transport */
458 [] S1AP_CLIENT.receive(PDU_NAS_EPS:?) -> value nas sender vc_conn {
459 var integer assoc_id := f_assoc_id_by_comp(vc_conn);
460 var AssociationData ad := S1apAssociationTable[assoc_id];
461 nas := f_nas_encaps(S1apAssociationTable[assoc_id].nus, nas, new_ctx := false);
462 var octetstring nas_enc := enc_PDU_NAS_EPS(nas);
463 S1AP.send(t_S1AP_Send(g_s1ap_conn_id,
464 ts_S1AP_UlNasTransport(ad.mme_ue_s1ap_id,
465 ad.enb_ue_s1ap_id,
466 nas_enc, ad.cgi, ad.tai)));
467 }
468 /* S1AP from client: pass on transparently */
469 [] S1AP_CLIENT.receive(S1AP_PDU:?) -> value msg sender vc_conn {
470 /* Pass message through */
471 /* FIXME: validate S1AP_IDs ? */
472 S1AP.send(t_S1AP_Send(g_s1ap_conn_id, msg));
473 }
474
475 /* non-UE related S1AP: pass through unmodified/unverified */
476 [] S1AP_UNIT.receive(S1AP_PDU:?) -> value msg sender vc_conn {
477 /* Pass message through */
478 S1AP.send(t_S1AP_Send(g_s1ap_conn_id, msg));
479 }
480
481 /* S1AP received from peer (MME) */
482 [] S1AP.receive(tr_S1AP_RecvFrom_R(?)) -> value mrf {
483 if (match(mrf.msg, tr_S1AP_nonUErelated)) {
484 /* non-UE-related S1AP message */
Philipp Maierfc7ada22023-07-24 11:32:12 +0200485 SendToS1apExpectTableProc(mrf.msg);
Harald Welte35498112019-07-02 14:26:39 +0800486 var template S1AP_PDU resp := ops.unitdata_cb.apply(mrf.msg);
487 if (isvalue(resp)) {
488 S1AP.send(t_S1AP_Send(g_s1ap_conn_id, valueof(resp)));
489 }
Harald Welte35498112019-07-02 14:26:39 +0800490 } else {
491 /* Ue-related S1AP message */
492 /* obtain MME + ENB UE S1AP ID */
493 var template (omit) MME_UE_S1AP_ID mme_ue_id := f_S1AP_get_MME_UE_S1AP_ID(mrf.msg);
494 var template (omit) ENB_UE_S1AP_ID enb_ue_id := f_S1AP_get_ENB_UE_S1AP_ID(mrf.msg);
495 /* check if those IDs are known in our table */
496 if (f_s1ap_ids_known(mme_ue_id, enb_ue_id)) {
497 /* if yes, dispatch to the ConnHdlr for this Ue-Connection */
498 var template (omit) octetstring nas_enc;
Philipp Maier7147c922023-07-07 14:18:32 +0200499 var integer assoc_id := f_assoc_id_by_s1ap_ids(mme_ue_id, enb_ue_id);
Harald Welte35498112019-07-02 14:26:39 +0800500 vc_conn := S1apAssociationTable[assoc_id].comp_ref;
501 nas_enc := f_S1AP_get_NAS_PDU(mrf.msg);
502 if (isvalue(nas_enc)) {
503 nas := dec_PDU_NAS_EPS(valueof(nas_enc));
504 if (match(nas, tr_NAS_EMM_SecurityProtected)) {
505 nas := f_nas_try_decaps(S1apAssociationTable[assoc_id].nus, nas);
506 }
Pau Espin Pedrol2edec462023-12-15 19:16:02 +0100507 /* DL/UlNasTransport are not interesting, don't send them */
508 if (not match(mrf.msg, (tr_S1AP_DlNasTransport, tr_S1AP_UlNasTransport))) {
509 /* send raw S1AP */
510 S1AP_CLIENT.send(mrf.msg) to vc_conn;
511 }
Harald Welte35498112019-07-02 14:26:39 +0800512 /* send decoded NAS */
513 S1AP_CLIENT.send(nas) to vc_conn;
514 } else {
515 /* send raw S1AP */
516 S1AP_CLIENT.send(mrf.msg) to vc_conn;
517 }
518 } else {
519 /* if not, call create_cb so it can create new ConnHdlr */
520 vc_conn := ops.create_cb.apply(mrf.msg, mme_ue_id, enb_ue_id, id);
521 f_s1ap_id_table_add(vc_conn, mme_ue_id, valueof(enb_ue_id));
522 S1AP_CLIENT.send(mrf.msg) to vc_conn;
523 }
Pau Espin Pedrol6cf7fde2023-12-21 19:17:08 +0100524 if (match(mrf.msg, tr_S1AP_UeContextReleaseCmd)) {
525 handle_S1AP_UeContextReleaseCmd(mrf.msg);
526 }
Harald Welte35498112019-07-02 14:26:39 +0800527 }
528 }
529 [] S1AP.receive(tr_SctpAssocChange) { }
530 [] S1AP.receive(tr_SctpPeerAddrChange) { }
Philipp Maier7147c922023-07-07 14:18:32 +0200531 [] S1AP_PROC.getcall(S1APEM_register:{?,?,?}) -> param(mme_id, enb_id, vc_conn) {
532 f_create_expect(mme_id, enb_id, vc_conn);
533 S1AP_PROC.reply(S1APEM_register:{mme_id, enb_id, vc_conn}) to vc_conn;
Harald Welte35498112019-07-02 14:26:39 +0800534 }
Philipp Maierfc7ada22023-07-24 11:32:12 +0200535 [] S1AP_PROC.getcall(S1APEM_register_proc:{?,?}) -> param(procedureCode, vc_conn) {
536 f_create_expect_proc(procedureCode, vc_conn);
537 S1AP_PROC.reply(S1APEM_register_proc:{procedureCode, vc_conn}) to vc_conn;
538 }
Pau Espin Pedrol3be4d922024-01-15 15:21:57 +0100539 [] S1AP_PROC.getcall(S1APEM_derive_nas_token:{?, ?, -}) -> param(kasme, vc_conn) {
540 var integer assoc_id := f_assoc_id_by_comp(vc_conn);
541 var OCT32 nas_token := f_kdf_nas_token(kasme, S1apAssociationTable[assoc_id].nus.tx_count)
542 S1apAssociationTable[assoc_id].nus.tx_count := S1apAssociationTable[assoc_id].nus.tx_count + 1;
543 S1AP_PROC.reply(S1APEM_derive_nas_token:{kasme, vc_conn, nas_token}) to vc_conn;
544 }
Harald Welte35498112019-07-02 14:26:39 +0800545 }
Harald Welte35498112019-07-02 14:26:39 +0800546 }
547}
548
549/* "Expect" Handling */
550
551type record ExpectData {
Philipp Maier7147c922023-07-07 14:18:32 +0200552 MME_UE_S1AP_ID mme_id optional,
553 ENB_UE_S1AP_ID enb_id optional,
Harald Welte35498112019-07-02 14:26:39 +0800554 S1AP_ConnHdlr vc_conn
555}
556
Philipp Maierfc7ada22023-07-24 11:32:12 +0200557/* represents a single S1AP PDU that we expect. When a matching PDU is seen, it is forwarded to the registered
558 * component */
559type record ExpectDataProc {
560 integer procedureCode optional,
561 S1AP_ConnHdlr vc_conn /* component handling this UE connection */
562};
563
Philipp Maier7147c922023-07-07 14:18:32 +0200564signature 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 +0200565signature S1APEM_register_proc(in integer procedureCode, in S1AP_ConnHdlr hdlr);
Pau Espin Pedrol3be4d922024-01-15 15:21:57 +0100566signature S1APEM_derive_nas_token(in octetstring kasme, in S1AP_ConnHdlr hdlr, out OCT32 nas_token);
Harald Welte35498112019-07-02 14:26:39 +0800567
568type port S1APEM_PROC_PT procedure {
569 inout S1APEM_register;
Philipp Maierfc7ada22023-07-24 11:32:12 +0200570 inout S1APEM_register_proc;
Pau Espin Pedrol3be4d922024-01-15 15:21:57 +0100571 inout S1APEM_derive_nas_token;
Harald Welte35498112019-07-02 14:26:39 +0800572} with { extension "internal" };
573
Oliver Smith23e192e2023-02-13 15:00:46 +0100574/* Function that can be used as create_cb and will use the expect table */
Philipp Maier7147c922023-07-07 14:18:32 +0200575function ExpectedCreateCallback(S1AP_PDU msg,
576 template (omit) MME_UE_S1AP_ID mme_id,
577 template (omit) ENB_UE_S1AP_ID enb_id, charstring id)
Harald Welte35498112019-07-02 14:26:39 +0800578runs on S1AP_Emulation_CT return S1AP_ConnHdlr {
579 var S1AP_ConnHdlr ret := null;
580 var integer i;
581
582 for (i := 0; i < sizeof(S1apExpectTable); i := i+1) {
Philipp Maier7147c922023-07-07 14:18:32 +0200583 if (not ispresent(S1apExpectTable[i].mme_id) and not ispresent(S1apExpectTable[i].enb_id)) {
Harald Welte35498112019-07-02 14:26:39 +0800584 continue;
585 }
Philipp Maier7147c922023-07-07 14:18:32 +0200586
587 if (valueof(mme_id) == S1apExpectTable[i].mme_id and valueof(enb_id) == S1apExpectTable[i].enb_id) {
Harald Welte35498112019-07-02 14:26:39 +0800588 ret := S1apExpectTable[i].vc_conn;
589 /* Release this entry */
Philipp Maier7147c922023-07-07 14:18:32 +0200590 S1apExpectTable[i].mme_id := omit;
591 S1apExpectTable[i].enb_id := omit;
Harald Welte35498112019-07-02 14:26:39 +0800592 S1apExpectTable[i].vc_conn := null;
593 log("Found Expect[", i, "] for ", msg, " handled at ", ret);
594 return ret;
595 }
596 }
597 setverdict(fail, "Couldn't find Expect for ", msg);
598 mtc.stop;
599}
600
Philipp Maier7147c922023-07-07 14:18:32 +0200601private function f_create_expect(template (omit) MME_UE_S1AP_ID mme_id,
602 template (omit) ENB_UE_S1AP_ID enb_id,
603 S1AP_ConnHdlr hdlr)
Harald Welte35498112019-07-02 14:26:39 +0800604runs on S1AP_Emulation_CT {
605 var integer i;
606
607 /* Check an entry like this is not already presnt */
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 continue;
611 }
612 if (valueof(mme_id) == S1apExpectTable[i].mme_id and valueof(enb_id) == S1apExpectTable[i].enb_id) {
613 setverdict(fail, "UE MME id / UE ENB id pair already present", mme_id, enb_id);
Harald Welte35498112019-07-02 14:26:39 +0800614 mtc.stop;
615 }
616 }
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 S1apExpectTable[i].mme_id := valueof(mme_id);
620 S1apExpectTable[i].enb_id := valueof(enb_id);
Harald Welte35498112019-07-02 14:26:39 +0800621 S1apExpectTable[i].vc_conn := hdlr;
Philipp Maier7147c922023-07-07 14:18:32 +0200622 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 +0800623 return;
624 }
625 }
626 testcase.stop("No space left in S1apExpectTable")
627}
628
629/* client/conn_hdlr side function to use procedure port to create expect in emulation */
Philipp Maier7147c922023-07-07 14:18:32 +0200630function f_create_s1ap_expect(template (omit) MME_UE_S1AP_ID mme_id,
631 template (omit) ENB_UE_S1AP_ID enb_id) runs on S1AP_ConnHdlr {
632 S1AP_PROC.call(S1APEM_register:{mme_id, enb_id, self}) {
633 [] S1AP_PROC.getreply(S1APEM_register:{?,?,?}) {};
Harald Welte35498112019-07-02 14:26:39 +0800634 }
635}
636
Philipp Maierfc7ada22023-07-24 11:32:12 +0200637private function f_create_expect_proc(integer procedureCode, S1AP_ConnHdlr hdlr) runs on S1AP_Emulation_CT {
638 var integer i;
639
640 /* Check an entry like this is not already presnt */
641 for (i := 0; i < sizeof(S1apExpectTableProc); i := i+1) {
642 if (S1apExpectTableProc[i].vc_conn == null) {
643 continue;
644 }
645 if (S1apExpectTableProc[i].procedureCode == procedureCode) {
646 setverdict(fail, "procedureCode ", procedureCode, " already present");
647 mtc.stop;
648 }
649 }
650 for (i := 0; i < sizeof(S1apExpectTableProc); i := i+1) {
651 if (S1apExpectTableProc[i].vc_conn == null) {
652 S1apExpectTableProc[i].procedureCode := procedureCode;
653 S1apExpectTableProc[i].vc_conn := hdlr;
654 log("Created Expect[", i, "] for PDU:", procedureCode, " to be handled at ", hdlr);
655 return;
656 }
657 }
658 testcase.stop("No space left in S1apExpectTableProc")
659}
660
661/* client/conn_hdlr side function to use procedure port to create expect (PDU) in emulation */
662function f_create_s1ap_expect_proc(integer procedureCode, S1AP_ConnHdlr hdlr) runs on S1AP_ConnHdlr
663{
664 S1AP_PROC.call(S1APEM_register_proc:{procedureCode, self}) {
665 [] S1AP_PROC.getreply(S1APEM_register_proc:{?,?}) {};
666 }
667
668 log(procedureCode);
669}
Harald Welte35498112019-07-02 14:26:39 +0800670
Pau Espin Pedrol3be4d922024-01-15 15:21:57 +0100671/* Derive NAS Token (and post-increment ul_count): */
672function f_s1apem_derive_nas_token(in octetstring kasme) runs on S1AP_ConnHdlr return OCT32
673{
674 var OCT32 nas_token;
675 S1AP_PROC.call(S1APEM_derive_nas_token:{kasme, self, -}) {
676 [] S1AP_PROC.getreply(S1APEM_derive_nas_token:{kasme, self, ?}) -> param(nas_token) {
677 return nas_token;
678 };
679 }
680}
681
Harald Welte35498112019-07-02 14:26:39 +0800682private function f_expect_table_init()
683runs on S1AP_Emulation_CT {
684 var integer i;
685 for (i := 0; i < sizeof(S1apExpectTable); i := i + 1) {
Philipp Maier7147c922023-07-07 14:18:32 +0200686 S1apExpectTable[i].mme_id := omit;
687 S1apExpectTable[i].enb_id := omit;
688 S1apExpectTable[i].vc_conn := null;
Harald Welte35498112019-07-02 14:26:39 +0800689 }
Philipp Maierfc7ada22023-07-24 11:32:12 +0200690
691 for (i := 0; i < sizeof(S1apExpectTableProc); i := i + 1) {
692 S1apExpectTableProc[i].procedureCode := omit;
693 S1apExpectTableProc[i].vc_conn := null;
694 }
Harald Welte35498112019-07-02 14:26:39 +0800695}
696
697function DummyUnitdataCallback(S1AP_PDU msg)
698runs on S1AP_Emulation_CT return template S1AP_PDU {
699 log("Ignoring S1AP ", msg);
700 return omit;
701}
702
703
704function f_S1AP_get_ENB_UE_S1AP_ID(S1AP_PDU s1ap) return template (omit) ENB_UE_S1AP_ID
705{
706 if (ischosen(s1ap.initiatingMessage)) {
707 var InitiatingMessage im := s1ap.initiatingMessage;
708 select (s1ap) {
709 case (tr_S1AP_InitialUE) {
710 return im.value_.InitialUEMessage.protocolIEs[0].value_.ENB_UE_S1AP_ID;
711 }
712 case (tr_S1AP_DlNasTransport) {
713 return im.value_.DownlinkNASTransport.protocolIEs[1].value_.ENB_UE_S1AP_ID;
714 }
715 case (tr_S1AP_UlNasTransport) {
716 return im.value_.UplinkNASTransport.protocolIEs[1].value_.ENB_UE_S1AP_ID;
717 }
718 case (tr_S1AP_IntialCtxSetupReq) {
719 return im.value_.initialContextSetupRequest.protocolIEs[1].value_.ENB_UE_S1AP_ID;
720 }
721 case (tr_S1AP_UeContextReleaseReq) {
722 return im.value_.UEContextReleaseRequest.protocolIEs[1].value_.ENB_UE_S1AP_ID;
723 }
Pau Espin Pedrola5bb8072023-12-21 19:15:31 +0100724 case (tr_S1AP_UeContextReleaseCmd) {
725 if (ispresent(im.value_.uEContextReleaseCommand.protocolIEs[0].value_.uE_S1AP_IDs.uE_S1AP_ID_pair)) {
726 return im.value_.uEContextReleaseCommand.protocolIEs[0].value_.uE_S1AP_IDs.uE_S1AP_ID_pair.eNB_UE_S1AP_ID;
727 } else {
728 return omit;
729 }
730 }
Harald Welte35498112019-07-02 14:26:39 +0800731 case (tr_S1AP_ConnEstInd) {
732 return im.value_.ConnectionEstablishmentIndication.protocolIEs[1].value_.ENB_UE_S1AP_ID;
733 }
734 /* TODO */
735 }
736 } else if (ischosen(s1ap.successfulOutcome)) {
737 var SuccessfulOutcome so := s1ap.successfulOutcome;
738 select (s1ap) {
739 case (tr_S1AP_InitialCtxSetupResp) {
740 return so.value_.initialContextSetupResponse.protocolIEs[1].value_.ENB_UE_S1AP_ID;
741 }
742 case (tr_S1AP_UeContextReleaseCompl) {
743 return so.value_.UEContextReleaseComplete.protocolIEs[1].value_.ENB_UE_S1AP_ID;
744 }
745 /* TODO */
746 }
747 } else if (ischosen(s1ap.unsuccessfulOutcome)) {
748 var UnsuccessfulOutcome uo := s1ap.unsuccessfulOutcome;
749 select (s1ap) {
750 case (tr_S1AP_InitialCtxSetupFail) {
751 return uo.value_.initialContextSetupFailure.protocolIEs[1].value_.ENB_UE_S1AP_ID;
752 }
753 /* TODO */
754 }
755 }
756 return omit;
757}
758
759function f_S1AP_get_MME_UE_S1AP_ID(S1AP_PDU s1ap) return template (omit) MME_UE_S1AP_ID
760{
761 if (ischosen(s1ap.initiatingMessage)) {
762 var InitiatingMessage im := s1ap.initiatingMessage;
763 select (s1ap) {
764 case (tr_S1AP_DlNasTransport) {
765 return im.value_.DownlinkNASTransport.protocolIEs[0].value_.MME_UE_S1AP_ID;
766 }
767 case (tr_S1AP_UlNasTransport) {
768 return im.value_.UplinkNASTransport.protocolIEs[0].value_.MME_UE_S1AP_ID;
769 }
770 case (tr_S1AP_IntialCtxSetupReq) {
771 return im.value_.initialContextSetupRequest.protocolIEs[0].value_.MME_UE_S1AP_ID;
772 }
773 case (tr_S1AP_UeContextReleaseReq) {
774 return im.value_.UEContextReleaseRequest.protocolIEs[0].value_.MME_UE_S1AP_ID;
775 }
Pau Espin Pedrola5bb8072023-12-21 19:15:31 +0100776 case (tr_S1AP_UeContextReleaseCmd) {
777 if (ispresent(im.value_.uEContextReleaseCommand.protocolIEs[0].value_.uE_S1AP_IDs.uE_S1AP_ID_pair)) {
778 return im.value_.uEContextReleaseCommand.protocolIEs[0].value_.uE_S1AP_IDs.uE_S1AP_ID_pair.mME_UE_S1AP_ID;
779 } else {
780 return im.value_.uEContextReleaseCommand.protocolIEs[0].value_.uE_S1AP_IDs.mME_UE_S1AP_ID;
781 }
782 }
Harald Welte35498112019-07-02 14:26:39 +0800783 case (tr_S1AP_ConnEstInd) {
784 return im.value_.ConnectionEstablishmentIndication.protocolIEs[0].value_.MME_UE_S1AP_ID;
785 }
786 /* TODO */
787 }
788 } else if (ischosen(s1ap.successfulOutcome)) {
789 var SuccessfulOutcome so := s1ap.successfulOutcome;
790 select (s1ap) {
791 case (tr_S1AP_InitialCtxSetupResp) {
792 return so.value_.initialContextSetupResponse.protocolIEs[0].value_.MME_UE_S1AP_ID;
793 }
794 case (tr_S1AP_UeContextReleaseCompl) {
795 return so.value_.UEContextReleaseComplete.protocolIEs[0].value_.MME_UE_S1AP_ID;
796 }
797 /* TODO */
798 }
799 } else if (ischosen(s1ap.unsuccessfulOutcome)) {
800 var UnsuccessfulOutcome uo := s1ap.unsuccessfulOutcome;
801 select (s1ap) {
802 case (tr_S1AP_InitialCtxSetupFail) {
803 return uo.value_.initialContextSetupFailure.protocolIEs[0].value_.MME_UE_S1AP_ID;
804 }
805 /* TODO */
806 }
807 }
808 return omit;
809}
810
811function f_S1AP_get_NAS_PDU(S1AP_PDU s1ap) return template (omit) NAS_PDU
812{
Pau Espin Pedrol2edec462023-12-15 19:16:02 +0100813 var integer i, j;
Harald Welte35498112019-07-02 14:26:39 +0800814
815 if (ischosen(s1ap.initiatingMessage)) {
816 var InitiatingMessage im := s1ap.initiatingMessage;
817 select (s1ap) {
818 case (tr_S1AP_DlNasTransport) {
819 var DownlinkNASTransport msg := im.value_.DownlinkNASTransport;
820 for (i := 0; i < lengthof(msg.protocolIEs); i := i+1) {
821 if (msg.protocolIEs[i].id == id_NAS_PDU) {
822 return msg.protocolIEs[i].value_.NAS_PDU;
823 }
824 }
825 }
826 case (tr_S1AP_UlNasTransport) {
827 var UplinkNASTransport msg := im.value_.UplinkNASTransport;
828 for (i := 0; i < lengthof(msg.protocolIEs); i := i+1) {
829 if (msg.protocolIEs[i].id == id_NAS_PDU) {
830 return msg.protocolIEs[i].value_.NAS_PDU;
831 }
832 }
833 }
Pau Espin Pedrol2edec462023-12-15 19:16:02 +0100834 case (tr_S1AP_IntialCtxSetupReq) {
835 var InitialContextSetupRequest msg := im.value_.initialContextSetupRequest;
836 for (i := 0; i < lengthof(msg.protocolIEs); i := i+1) {
837 if (msg.protocolIEs[i].id == id_E_RABToBeSetupListCtxtSUReq) {
838 var E_RABToBeSetupListCtxtSUReq rab_req := msg.protocolIEs[i].value_.E_RABToBeSetupListCtxtSUReq;
839 for (j := 0; j < lengthof(rab_req); j := j+1) {
840 var E_RABToBeSetupItemCtxtSUReq it := rab_req[j].value_.E_RABToBeSetupItemCtxtSUReq;
841 return it.nAS_PDU;
842 }
843 }
844 }
845 return omit;
846 }
Harald Welte35498112019-07-02 14:26:39 +0800847 }
848 }
849 return omit;
850}
851
852
853
854}