blob: 955c690f7f69e1ff02d27b7674b53bc1a889f580 [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,
Pau Espin Pedrol35618872024-01-15 15:25:18 +010075 ResetNAScounts reset_nas_counts,
76 NAS_ALG_INT set_nas_alg_int
Harald Welte35498112019-07-02 14:26:39 +080077};
78
79type enumerated S1APEM_EventUpDown {
80 S1APEM_EVENT_DOWN,
81 S1APEM_EVENT_UP
82}
83
84/* an event indicating us whether or not a connection is physically up or down,
85 * and whether we have received an ID_ACK */
86type union S1APEM_Event {
87 S1APEM_EventUpDown up_down
88}
89
90/* global test port e.g. for non-imsi/conn specific messages */
91type port S1AP_PT message {
92 inout S1AP_PDU, S1APEM_Event;
93} with { extension "internal" };
94
95
96/* represents a single S1AP Association */
97type record AssociationData {
98 S1AP_ConnHdlr comp_ref, /* component handling this UE connection */
99 uint24_t enb_ue_s1ap_id optional, /* eNB side S1AP ID */
100 uint32_t mme_ue_s1ap_id optional, /* MME side S1AP ID */
101 EUTRAN_CGI cgi optional,
102 TAI tai optional,
103 NAS_UE_State nus
Harald Welte35498112019-07-02 14:26:39 +0800104};
105
106type component S1AP_Emulation_CT {
107 /* Port facing to the UDP SUT */
108 port S1AP_CODEC_PT S1AP;
109 /* All S1AP_ConnHdlr S1AP ports connect here
110 * S1AP_Emulation_CT.main needs to figure out what messages
111 * to send where with CLIENT.send() to vc_conn */
112 port S1AP_Conn_PT S1AP_CLIENT;
113 /* currently tracked connections */
114 var AssociationData S1apAssociationTable[16];
Philipp Maierf935f0c2023-07-24 17:58:24 +0200115 /* pending expected S1AP Association (UE oriented) */
Harald Welte35498112019-07-02 14:26:39 +0800116 var ExpectData S1apExpectTable[8];
Philipp Maierfc7ada22023-07-24 11:32:12 +0200117 /* pending expected S1AP PDU */
118 var ExpectDataProc S1apExpectTableProc[8];
Harald Welte35498112019-07-02 14:26:39 +0800119 /* procedure based port to register for incoming connections */
120 port S1APEM_PROC_PT S1AP_PROC;
121 /* test port for unit data messages */
122 port S1AP_PT S1AP_UNIT;
123
124 var S1AP_conn_parameters g_pars;
125 var charstring g_s1ap_id;
126 var integer g_s1ap_conn_id := -1;
127}
128
129type function S1APCreateCallback(S1AP_PDU msg, template (omit) MME_UE_S1AP_ID mme_id,
130 template (omit) ENB_UE_S1AP_ID enb_id, charstring id)
131runs on S1AP_Emulation_CT return S1AP_ConnHdlr;
132
133type function S1APUnitdataCallback(S1AP_PDU msg)
134runs on S1AP_Emulation_CT return template S1AP_PDU;
135
136type record S1APOps {
137 S1APCreateCallback create_cb,
138 S1APUnitdataCallback unitdata_cb
139}
140
141type record S1AP_conn_parameters {
142 HostName remote_ip,
143 PortNumber remote_sctp_port,
144 HostName local_ip,
145 PortNumber local_sctp_port,
146 NAS_Role role
147}
148
149function tr_S1AP_RecvFrom_R(template S1AP_PDU msg)
150runs on S1AP_Emulation_CT return template S1AP_RecvFrom {
151 var template S1AP_RecvFrom mrf := {
152 connId := g_s1ap_conn_id,
153 remName := ?,
154 remPort := ?,
155 locName := ?,
156 locPort := ?,
157 msg := msg
158 }
159 return mrf;
160}
161
162private function f_s1ap_ids_known(template (omit) MME_UE_S1AP_ID mme_id,
163 template (omit) ENB_UE_S1AP_ID enb_id)
164runs on S1AP_Emulation_CT return boolean {
165 var integer i;
166 log("f_s1ap_ids_known(",mme_id,", ",enb_id,")");
167 for (i := 0; i < sizeof(S1apAssociationTable); i := i+1) {
168 log("tbl[",i,"]: mme=", S1apAssociationTable[i].mme_ue_s1ap_id,
169 ", enb=", S1apAssociationTable[i].enb_ue_s1ap_id);
170 /* skip empty records */
171 if (S1apAssociationTable[i].mme_ue_s1ap_id == omit and
172 S1apAssociationTable[i].enb_ue_s1ap_id == omit) {
173 log("skipping empty ", i);
174 continue;
175 }
176 if (S1apAssociationTable[i].mme_ue_s1ap_id == omit) {
177 log("entry ", i, " has no MME ID yet (enb=", S1apAssociationTable[i].enb_ue_s1ap_id);
178 /* Table doesn't yet know the MME side ID, let's look-up only
179 * based on the eNB side ID */
180 if (match(S1apAssociationTable[i].enb_ue_s1ap_id, enb_id)) {
181 /* update table with MME side ID */
182 S1apAssociationTable[i].mme_ue_s1ap_id := valueof(mme_id);
183 return true;
184 }
185 } else if (match(S1apAssociationTable[i].enb_ue_s1ap_id, enb_id) and
186 match(S1apAssociationTable[i].mme_ue_s1ap_id, mme_id)) {
187 return true;
188 }
189 }
190 return false;
191}
192
193private function f_comp_known(S1AP_ConnHdlr client)
194runs on S1AP_Emulation_CT return boolean {
195 var integer i;
196 for (i := 0; i < sizeof(S1apAssociationTable); i := i+1) {
197 if (S1apAssociationTable[i].comp_ref == client) {
198 return true;
199 }
200 }
201 return false;
202}
203
204private function f_assoc_id_by_s1ap_ids(template (omit) MME_UE_S1AP_ID mme_id,
205 template (omit) ENB_UE_S1AP_ID enb_id)
206runs on S1AP_Emulation_CT return integer {
207 var integer i;
208 for (i := 0; i < sizeof(S1apAssociationTable); i := i+1) {
Pau Espin Pedrol6cf7fde2023-12-21 19:17:08 +0100209 if (istemplatekind(enb_id, "omit") or
210 match(S1apAssociationTable[i].enb_ue_s1ap_id, enb_id)) {
Harald Welte35498112019-07-02 14:26:39 +0800211 if (istemplatekind(mme_id, "omit")) {
212 return i;
213 } else {
214 if (match(S1apAssociationTable[i].mme_ue_s1ap_id, mme_id)) {
215 return i;
216 }
217 }
218 }
219 }
220 setverdict(fail, "S1AP Association Table not found by ENB-ID=", enb_id, " MME-ID=", mme_id);
221 mtc.stop;
222}
223
224private function f_assoc_id_by_comp(S1AP_ConnHdlr client)
225runs on S1AP_Emulation_CT return integer {
226 var integer i;
227 for (i := 0; i < sizeof(S1apAssociationTable); i := i+1) {
228 if (S1apAssociationTable[i].comp_ref == client) {
229 return i;
230 }
231 }
232 setverdict(fail, "S1AP Association Table not found by component ", client);
233 mtc.stop;
234}
235
236private function f_assoc_by_comp(S1AP_ConnHdlr client)
237runs on S1AP_Emulation_CT return AssociationData {
238 var integer i := f_assoc_id_by_comp(client);
239 return S1apAssociationTable[i];
240}
241
242private function f_s1ap_id_table_add(S1AP_ConnHdlr comp_ref,
243 template (omit) MME_UE_S1AP_ID mme_id, ENB_UE_S1AP_ID enb_id)
244runs on S1AP_Emulation_CT return integer {
245 var integer i;
246 for (i := 0; i < sizeof(S1apAssociationTable); i := i+1) {
247 if (not isvalue(S1apAssociationTable[i].enb_ue_s1ap_id)) {
248 S1apAssociationTable[i].enb_ue_s1ap_id := enb_id;
249 if (istemplatekind(mme_id, "omit")) {
250 S1apAssociationTable[i].mme_ue_s1ap_id := omit;
251 } else {
252 S1apAssociationTable[i].mme_ue_s1ap_id := valueof(mme_id);
253 }
254 S1apAssociationTable[i].comp_ref := comp_ref;
255 return i;
256 }
257 }
258 testcase.stop("S1AP Association Table full!");
259 return -1;
260}
261
262private function f_s1ap_id_table_del(S1AP_ConnHdlr comp_ref, ENB_UE_S1AP_ID enb_id)
263runs on S1AP_Emulation_CT {
264 var integer i;
265 for (i := 0; i < sizeof(S1apAssociationTable); i := i+1) {
266 if (S1apAssociationTable[i].comp_ref == comp_ref and
Philipp Maier0ce67ab2023-07-07 13:02:11 +0200267 S1apAssociationTable[i].enb_ue_s1ap_id == enb_id) {
Harald Welte35498112019-07-02 14:26:39 +0800268 S1apAssociationTable[i].enb_ue_s1ap_id := omit;
269 S1apAssociationTable[i].mme_ue_s1ap_id := omit;
270 S1apAssociationTable[i].comp_ref := null;
271 return;
272 }
273 }
274 setverdict(fail, "S1AP Association Table: Couldn't find to-be-deleted entry!");
275 mtc.stop;
276}
277
278
279private function f_s1ap_id_table_init()
280runs on S1AP_Emulation_CT {
281 for (var integer i := 0; i < sizeof(S1apAssociationTable); i := i+1) {
282 S1apAssociationTable[i].mme_ue_s1ap_id := omit;
283 S1apAssociationTable[i].enb_ue_s1ap_id := omit;
284 S1apAssociationTable[i].cgi := omit;
285 S1apAssociationTable[i].tai := omit;
286 S1apAssociationTable[i].nus := valueof(t_NAS_UE_State(g_pars.role));
Philipp Maiereb930fd2023-07-24 17:59:40 +0200287 S1apAssociationTable[i].comp_ref := null;
Harald Welte35498112019-07-02 14:26:39 +0800288 }
289}
290
291private template (value) SctpTuple ts_SCTP(template (omit) integer ppid := 18) := {
292 sinfo_stream := omit,
293 sinfo_ppid := ppid,
294 remSocks := omit,
295 assocId := omit
296};
297
298private template PortEvent tr_SctpAssocChange := {
299 sctpEvent := {
300 sctpAssocChange := ?
301 }
302}
303private template PortEvent tr_SctpPeerAddrChange := {
304 sctpEvent := {
305 sctpPeerAddrChange := ?
306 }
307}
308
309private function f_s1ap_xceive(template (value) S1AP_PDU tx,
310 template S1AP_PDU rx_t := ?)
311runs on S1AP_Emulation_CT return S1AP_PDU {
312 timer T := 10.0;
313 var S1AP_RecvFrom mrf;
314
315 S1AP.send(t_S1AP_Send(g_s1ap_conn_id, tx));
316 alt {
317 [] S1AP.receive(tr_S1AP_RecvFrom_R(rx_t)) -> value mrf { }
318 [] S1AP.receive(tr_SctpAssocChange) { repeat; }
319 [] S1AP.receive(tr_SctpPeerAddrChange) { repeat; }
320 [] T.timeout {
321 setverdict(fail, "Timeout waiting for ", rx_t);
322 mtc.stop;
323 }
324 }
325 return mrf.msg;
326}
327
328/*
329private function f_nas_try_decaps(PDU_NAS_EPS nas) return PDU_NAS_EPS
330{
331 var PDU_NAS_EPS_SecurityProtectedNASMessage secp_nas;
332 if (not match(nas, tr_NAS_EMM_SecurityProtected)) {
333 return nas;
334 }
335 secp_nas := nas.ePS_messages.ePS_MobilityManagement.pDU_NAS_EPS_SecurityProtectedNASMessage;
336 select (secp_nas.securityHeaderType) {
337 case ('0011'B) {
338 var octetstring knas_int := '530ce32318f26264eab26bc116870b86'O;
339 var octetstring data_with_seq := int2oct(secp_nas.sequenceNumber, 1) & secp_nas.nAS_Message;
340 var OCT4 exp_mac := f_snow_3g_f9(knas_int, secp_nas.sequenceNumber, 0,
341 is_downlink:=true, data:=data_with_seq);
342 if (exp_mac != secp_nas.messageAuthenticationCode) {
343 setverdict(fail, "Received NAS MAC ", secp_nas.messageAuthenticationCode,
344 " doesn't match expected MAC ", exp_mac, ": ", nas);
345 mtc.stop;
346 }
347 return dec_PDU_NAS_EPS(secp_nas.nAS_Message);
348 }
349 case else {
350 setverdict(fail, "Implement SecHdrType for ", secp_nas);
351 mtc.stop;
352 }
353 }
354}
355*/
356
Philipp Maierbb8f05d2023-07-07 14:16:08 +0200357function handle_S1AP_UeContextReleaseCmd(template (present) S1AP_PDU rel_cmd) runs on S1AP_Emulation_CT {
358 if (ispresent(rel_cmd.initiatingMessage.value_.uEContextReleaseCommand.protocolIEs[0].value_.uE_S1AP_IDs.uE_S1AP_ID_pair)) {
359 var template MME_UE_S1AP_ID mme_ue_id;
360 var template ENB_UE_S1AP_ID enb_ue_id;
361 var integer assoc_id;
362 var S1AP_ConnHdlr vc_conn
363
364 mme_ue_id := rel_cmd.initiatingMessage.value_.uEContextReleaseCommand.protocolIEs[0].value_.uE_S1AP_IDs.uE_S1AP_ID_pair.mME_UE_S1AP_ID;
365 enb_ue_id := rel_cmd.initiatingMessage.value_.uEContextReleaseCommand.protocolIEs[0].value_.uE_S1AP_IDs.uE_S1AP_ID_pair.eNB_UE_S1AP_ID;
366
367 assoc_id := f_assoc_id_by_s1ap_ids(mme_ue_id, enb_ue_id);
368 vc_conn := S1apAssociationTable[assoc_id].comp_ref;
369
370 f_s1ap_id_table_del(vc_conn, valueof(enb_ue_id));
371 } else {
372 /* TODO: The UE CONTEXT RELEASE COMMAND (see also: 3GPP TS 36.413, section 9.1.4.6), may identify the
373 * context by either an uE_S1AP_ID_pair (MME_UE_S1AP_ID and ENB_UE_S1AP_ID) or an MME_UE_S1AP_ID alone.
374 * The latter case is not implemented here yet. */
375 setverdict(fail, "complete implementation of UeContextReleaseCmd handling");
376 mtc.stop;
377 }
378}
379
Philipp Maierfc7ada22023-07-24 11:32:12 +0200380private function SendToS1apExpectTableProc(S1AP_PDU msg) runs on S1AP_Emulation_CT {
381 var integer procedureCode;
382 var S1AP_ConnHdlr vc_conn;
383
384 if (ispresent(msg.initiatingMessage.procedureCode)) {
385 procedureCode := msg.initiatingMessage.procedureCode;
386 } else if (ispresent(msg.unsuccessfulOutcome.procedureCode)) {
387 procedureCode := msg.unsuccessfulOutcome.procedureCode;
388 } else if (ispresent(msg.successfulOutcome.procedureCode)) {
389 procedureCode := msg.successfulOutcome.procedureCode;
390 } else {
391 return;
392 }
393
394 for (var integer i := 0; i < sizeof(S1apExpectTableProc); i := i+1) {
395 if (S1apExpectTableProc[i].procedureCode == procedureCode) {
396 vc_conn := S1apExpectTableProc[i].vc_conn;
397 if (vc_conn != null) {
398 S1AP_CLIENT.send(msg) to vc_conn;
399 }
400 }
401 }
402
403 return;
404}
405
Harald Welte35498112019-07-02 14:26:39 +0800406function main(S1APOps ops, S1AP_conn_parameters p, charstring id) runs on S1AP_Emulation_CT {
407 var Result res;
408 g_pars := p;
409 g_s1ap_id := id;
410 f_s1ap_id_table_init();
411 f_expect_table_init();
412
413 map(self:S1AP, system:S1AP_CODEC_PT);
414 if (p.remote_sctp_port == -1) {
415 res := S1AP_CodecPort_CtrlFunct.f_IPL4_listen(S1AP, p.local_ip, p.local_sctp_port, { sctp := valueof(ts_SCTP) });
416 } else {
417 res := S1AP_CodecPort_CtrlFunct.f_IPL4_connect(S1AP, p.remote_ip, p.remote_sctp_port,
418 p.local_ip, p.local_sctp_port, -1, { sctp := valueof(ts_SCTP) });
419 }
420 if (not ispresent(res.connId)) {
421 setverdict(fail, "Could not connect S1AP socket, check your configuration");
422 mtc.stop;
423 }
424 g_s1ap_conn_id := res.connId;
425
426 /* notify user about SCTP establishment */
427 if (p.remote_sctp_port != -1) {
428 S1AP_UNIT.send(S1APEM_Event:{up_down:=S1APEM_EVENT_UP})
429 }
430
431 while (true) {
432 var S1AP_ConnHdlr vc_conn;
433 var PDU_NAS_EPS nas;
Philipp Maier7147c922023-07-07 14:18:32 +0200434 var MME_UE_S1AP_ID mme_id;
435 var ENB_UE_S1AP_ID enb_id;
Philipp Maierfc7ada22023-07-24 11:32:12 +0200436 var integer procedureCode;
Harald Welte35498112019-07-02 14:26:39 +0800437 var S1AP_RecvFrom mrf;
438 var S1AP_PDU msg;
439 var S1APEM_Config s1cfg;
440 var charstring vlr_name, mme_name;
441 var integer ai;
Pau Espin Pedrol3be4d922024-01-15 15:21:57 +0100442 var octetstring kasme;
Harald Welte35498112019-07-02 14:26:39 +0800443
444 alt {
445 /* Configuration primitive from client */
446 [] S1AP_CLIENT.receive(S1APEM_Config:{set_nas_keys:=?}) -> value s1cfg sender vc_conn {
447 var integer assoc_id := f_assoc_id_by_comp(vc_conn);
448 S1apAssociationTable[assoc_id].nus.k_nas_int := s1cfg.set_nas_keys.k_nas_int;
449 S1apAssociationTable[assoc_id].nus.k_nas_enc := s1cfg.set_nas_keys.k_nas_enc;
450 }
Pau Espin Pedrole0163462024-01-09 10:29:39 +0100451 /* Configuration primitive from client */
452 [] S1AP_CLIENT.receive(S1APEM_Config:{reset_nas_counts:=?}) -> value s1cfg sender vc_conn {
453 var integer assoc_id := f_assoc_id_by_comp(vc_conn);
454 S1apAssociationTable[assoc_id].nus.rx_count := 0;
455 S1apAssociationTable[assoc_id].nus.tx_count := 0;
456 }
Pau Espin Pedrol35618872024-01-15 15:25:18 +0100457 /* Configuration primitive from client */
458 [] S1AP_CLIENT.receive(S1APEM_Config:{set_nas_alg_int:=?}) -> value s1cfg sender vc_conn {
459 var integer assoc_id := f_assoc_id_by_comp(vc_conn);
460 S1apAssociationTable[assoc_id].nus.alg_int := s1cfg.set_nas_alg_int;
461 }
Harald Welte35498112019-07-02 14:26:39 +0800462 /* S1AP from client: InitialUE */
463 [] S1AP_CLIENT.receive(tr_S1AP_InitialUE) -> value msg sender vc_conn {
464 /* create a table entry about this connection */
465 ai := f_s1ap_id_table_add(vc_conn, omit, valueof(f_S1AP_get_ENB_UE_S1AP_ID(msg)));
466 /* Store CGI + TAI so we can use it for generating UlNasTransport from NAS */
467 S1apAssociationTable[ai].tai := msg.initiatingMessage.value_.InitialUEMessage.protocolIEs[2].value_.TAI;
468 S1apAssociationTable[ai].cgi := msg.initiatingMessage.value_.InitialUEMessage.protocolIEs[3].value_.EUTRAN_CGI;
469 /* Pass message through */
470 S1AP.send(t_S1AP_Send(g_s1ap_conn_id, msg));
471 }
472 /* NAS from client: Wrap in S1AP Uplink NAS Transport */
473 [] S1AP_CLIENT.receive(PDU_NAS_EPS:?) -> value nas sender vc_conn {
474 var integer assoc_id := f_assoc_id_by_comp(vc_conn);
475 var AssociationData ad := S1apAssociationTable[assoc_id];
476 nas := f_nas_encaps(S1apAssociationTable[assoc_id].nus, nas, new_ctx := false);
477 var octetstring nas_enc := enc_PDU_NAS_EPS(nas);
478 S1AP.send(t_S1AP_Send(g_s1ap_conn_id,
479 ts_S1AP_UlNasTransport(ad.mme_ue_s1ap_id,
480 ad.enb_ue_s1ap_id,
481 nas_enc, ad.cgi, ad.tai)));
482 }
483 /* S1AP from client: pass on transparently */
484 [] S1AP_CLIENT.receive(S1AP_PDU:?) -> value msg sender vc_conn {
485 /* Pass message through */
486 /* FIXME: validate S1AP_IDs ? */
487 S1AP.send(t_S1AP_Send(g_s1ap_conn_id, msg));
488 }
489
490 /* non-UE related S1AP: pass through unmodified/unverified */
491 [] S1AP_UNIT.receive(S1AP_PDU:?) -> value msg sender vc_conn {
492 /* Pass message through */
493 S1AP.send(t_S1AP_Send(g_s1ap_conn_id, msg));
494 }
495
496 /* S1AP received from peer (MME) */
497 [] S1AP.receive(tr_S1AP_RecvFrom_R(?)) -> value mrf {
498 if (match(mrf.msg, tr_S1AP_nonUErelated)) {
499 /* non-UE-related S1AP message */
Philipp Maierfc7ada22023-07-24 11:32:12 +0200500 SendToS1apExpectTableProc(mrf.msg);
Harald Welte35498112019-07-02 14:26:39 +0800501 var template S1AP_PDU resp := ops.unitdata_cb.apply(mrf.msg);
502 if (isvalue(resp)) {
503 S1AP.send(t_S1AP_Send(g_s1ap_conn_id, valueof(resp)));
504 }
Harald Welte35498112019-07-02 14:26:39 +0800505 } else {
506 /* Ue-related S1AP message */
507 /* obtain MME + ENB UE S1AP ID */
508 var template (omit) MME_UE_S1AP_ID mme_ue_id := f_S1AP_get_MME_UE_S1AP_ID(mrf.msg);
509 var template (omit) ENB_UE_S1AP_ID enb_ue_id := f_S1AP_get_ENB_UE_S1AP_ID(mrf.msg);
510 /* check if those IDs are known in our table */
511 if (f_s1ap_ids_known(mme_ue_id, enb_ue_id)) {
512 /* if yes, dispatch to the ConnHdlr for this Ue-Connection */
513 var template (omit) octetstring nas_enc;
Philipp Maier7147c922023-07-07 14:18:32 +0200514 var integer assoc_id := f_assoc_id_by_s1ap_ids(mme_ue_id, enb_ue_id);
Harald Welte35498112019-07-02 14:26:39 +0800515 vc_conn := S1apAssociationTable[assoc_id].comp_ref;
516 nas_enc := f_S1AP_get_NAS_PDU(mrf.msg);
517 if (isvalue(nas_enc)) {
518 nas := dec_PDU_NAS_EPS(valueof(nas_enc));
519 if (match(nas, tr_NAS_EMM_SecurityProtected)) {
520 nas := f_nas_try_decaps(S1apAssociationTable[assoc_id].nus, nas);
521 }
Pau Espin Pedrol2edec462023-12-15 19:16:02 +0100522 /* DL/UlNasTransport are not interesting, don't send them */
523 if (not match(mrf.msg, (tr_S1AP_DlNasTransport, tr_S1AP_UlNasTransport))) {
524 /* send raw S1AP */
525 S1AP_CLIENT.send(mrf.msg) to vc_conn;
526 }
Harald Welte35498112019-07-02 14:26:39 +0800527 /* send decoded NAS */
528 S1AP_CLIENT.send(nas) to vc_conn;
529 } else {
530 /* send raw S1AP */
531 S1AP_CLIENT.send(mrf.msg) to vc_conn;
532 }
533 } else {
534 /* if not, call create_cb so it can create new ConnHdlr */
535 vc_conn := ops.create_cb.apply(mrf.msg, mme_ue_id, enb_ue_id, id);
536 f_s1ap_id_table_add(vc_conn, mme_ue_id, valueof(enb_ue_id));
537 S1AP_CLIENT.send(mrf.msg) to vc_conn;
538 }
Pau Espin Pedrol6cf7fde2023-12-21 19:17:08 +0100539 if (match(mrf.msg, tr_S1AP_UeContextReleaseCmd)) {
540 handle_S1AP_UeContextReleaseCmd(mrf.msg);
541 }
Harald Welte35498112019-07-02 14:26:39 +0800542 }
543 }
544 [] S1AP.receive(tr_SctpAssocChange) { }
545 [] S1AP.receive(tr_SctpPeerAddrChange) { }
Philipp Maier7147c922023-07-07 14:18:32 +0200546 [] S1AP_PROC.getcall(S1APEM_register:{?,?,?}) -> param(mme_id, enb_id, vc_conn) {
547 f_create_expect(mme_id, enb_id, vc_conn);
548 S1AP_PROC.reply(S1APEM_register:{mme_id, enb_id, vc_conn}) to vc_conn;
Harald Welte35498112019-07-02 14:26:39 +0800549 }
Philipp Maierfc7ada22023-07-24 11:32:12 +0200550 [] S1AP_PROC.getcall(S1APEM_register_proc:{?,?}) -> param(procedureCode, vc_conn) {
551 f_create_expect_proc(procedureCode, vc_conn);
552 S1AP_PROC.reply(S1APEM_register_proc:{procedureCode, vc_conn}) to vc_conn;
553 }
Pau Espin Pedrol3be4d922024-01-15 15:21:57 +0100554 [] S1AP_PROC.getcall(S1APEM_derive_nas_token:{?, ?, -}) -> param(kasme, vc_conn) {
555 var integer assoc_id := f_assoc_id_by_comp(vc_conn);
556 var OCT32 nas_token := f_kdf_nas_token(kasme, S1apAssociationTable[assoc_id].nus.tx_count)
557 S1apAssociationTable[assoc_id].nus.tx_count := S1apAssociationTable[assoc_id].nus.tx_count + 1;
558 S1AP_PROC.reply(S1APEM_derive_nas_token:{kasme, vc_conn, nas_token}) to vc_conn;
559 }
Harald Welte35498112019-07-02 14:26:39 +0800560 }
Harald Welte35498112019-07-02 14:26:39 +0800561 }
562}
563
564/* "Expect" Handling */
565
566type record ExpectData {
Philipp Maier7147c922023-07-07 14:18:32 +0200567 MME_UE_S1AP_ID mme_id optional,
568 ENB_UE_S1AP_ID enb_id optional,
Harald Welte35498112019-07-02 14:26:39 +0800569 S1AP_ConnHdlr vc_conn
570}
571
Philipp Maierfc7ada22023-07-24 11:32:12 +0200572/* represents a single S1AP PDU that we expect. When a matching PDU is seen, it is forwarded to the registered
573 * component */
574type record ExpectDataProc {
575 integer procedureCode optional,
576 S1AP_ConnHdlr vc_conn /* component handling this UE connection */
577};
578
Philipp Maier7147c922023-07-07 14:18:32 +0200579signature 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 +0200580signature S1APEM_register_proc(in integer procedureCode, in S1AP_ConnHdlr hdlr);
Pau Espin Pedrol3be4d922024-01-15 15:21:57 +0100581signature S1APEM_derive_nas_token(in octetstring kasme, in S1AP_ConnHdlr hdlr, out OCT32 nas_token);
Harald Welte35498112019-07-02 14:26:39 +0800582
583type port S1APEM_PROC_PT procedure {
584 inout S1APEM_register;
Philipp Maierfc7ada22023-07-24 11:32:12 +0200585 inout S1APEM_register_proc;
Pau Espin Pedrol3be4d922024-01-15 15:21:57 +0100586 inout S1APEM_derive_nas_token;
Harald Welte35498112019-07-02 14:26:39 +0800587} with { extension "internal" };
588
Oliver Smith23e192e2023-02-13 15:00:46 +0100589/* Function that can be used as create_cb and will use the expect table */
Philipp Maier7147c922023-07-07 14:18:32 +0200590function ExpectedCreateCallback(S1AP_PDU msg,
591 template (omit) MME_UE_S1AP_ID mme_id,
592 template (omit) ENB_UE_S1AP_ID enb_id, charstring id)
Harald Welte35498112019-07-02 14:26:39 +0800593runs on S1AP_Emulation_CT return S1AP_ConnHdlr {
594 var S1AP_ConnHdlr ret := null;
595 var integer i;
596
597 for (i := 0; i < sizeof(S1apExpectTable); i := i+1) {
Philipp Maier7147c922023-07-07 14:18:32 +0200598 if (not ispresent(S1apExpectTable[i].mme_id) and not ispresent(S1apExpectTable[i].enb_id)) {
Harald Welte35498112019-07-02 14:26:39 +0800599 continue;
600 }
Philipp Maier7147c922023-07-07 14:18:32 +0200601
602 if (valueof(mme_id) == S1apExpectTable[i].mme_id and valueof(enb_id) == S1apExpectTable[i].enb_id) {
Harald Welte35498112019-07-02 14:26:39 +0800603 ret := S1apExpectTable[i].vc_conn;
604 /* Release this entry */
Philipp Maier7147c922023-07-07 14:18:32 +0200605 S1apExpectTable[i].mme_id := omit;
606 S1apExpectTable[i].enb_id := omit;
Harald Welte35498112019-07-02 14:26:39 +0800607 S1apExpectTable[i].vc_conn := null;
608 log("Found Expect[", i, "] for ", msg, " handled at ", ret);
609 return ret;
610 }
611 }
612 setverdict(fail, "Couldn't find Expect for ", msg);
613 mtc.stop;
614}
615
Philipp Maier7147c922023-07-07 14:18:32 +0200616private function f_create_expect(template (omit) MME_UE_S1AP_ID mme_id,
617 template (omit) ENB_UE_S1AP_ID enb_id,
618 S1AP_ConnHdlr hdlr)
Harald Welte35498112019-07-02 14:26:39 +0800619runs on S1AP_Emulation_CT {
620 var integer i;
621
622 /* Check an entry like this is not already presnt */
623 for (i := 0; i < sizeof(S1apExpectTable); i := i+1) {
Philipp Maier7147c922023-07-07 14:18:32 +0200624 if (not ispresent(S1apExpectTable[i].mme_id) and not ispresent(S1apExpectTable[i].enb_id)) {
625 continue;
626 }
627 if (valueof(mme_id) == S1apExpectTable[i].mme_id and valueof(enb_id) == S1apExpectTable[i].enb_id) {
628 setverdict(fail, "UE MME id / UE ENB id pair already present", mme_id, enb_id);
Harald Welte35498112019-07-02 14:26:39 +0800629 mtc.stop;
630 }
631 }
632 for (i := 0; i < sizeof(S1apExpectTable); i := i+1) {
Philipp Maier7147c922023-07-07 14:18:32 +0200633 if (not ispresent(S1apExpectTable[i].mme_id) and not ispresent(S1apExpectTable[i].enb_id)) {
634 S1apExpectTable[i].mme_id := valueof(mme_id);
635 S1apExpectTable[i].enb_id := valueof(enb_id);
Harald Welte35498112019-07-02 14:26:39 +0800636 S1apExpectTable[i].vc_conn := hdlr;
Philipp Maier7147c922023-07-07 14:18:32 +0200637 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 +0800638 return;
639 }
640 }
641 testcase.stop("No space left in S1apExpectTable")
642}
643
644/* client/conn_hdlr side function to use procedure port to create expect in emulation */
Philipp Maier7147c922023-07-07 14:18:32 +0200645function f_create_s1ap_expect(template (omit) MME_UE_S1AP_ID mme_id,
646 template (omit) ENB_UE_S1AP_ID enb_id) runs on S1AP_ConnHdlr {
647 S1AP_PROC.call(S1APEM_register:{mme_id, enb_id, self}) {
648 [] S1AP_PROC.getreply(S1APEM_register:{?,?,?}) {};
Harald Welte35498112019-07-02 14:26:39 +0800649 }
650}
651
Philipp Maierfc7ada22023-07-24 11:32:12 +0200652private function f_create_expect_proc(integer procedureCode, S1AP_ConnHdlr hdlr) runs on S1AP_Emulation_CT {
653 var integer i;
654
655 /* Check an entry like this is not already presnt */
656 for (i := 0; i < sizeof(S1apExpectTableProc); i := i+1) {
657 if (S1apExpectTableProc[i].vc_conn == null) {
658 continue;
659 }
660 if (S1apExpectTableProc[i].procedureCode == procedureCode) {
661 setverdict(fail, "procedureCode ", procedureCode, " already present");
662 mtc.stop;
663 }
664 }
665 for (i := 0; i < sizeof(S1apExpectTableProc); i := i+1) {
666 if (S1apExpectTableProc[i].vc_conn == null) {
667 S1apExpectTableProc[i].procedureCode := procedureCode;
668 S1apExpectTableProc[i].vc_conn := hdlr;
669 log("Created Expect[", i, "] for PDU:", procedureCode, " to be handled at ", hdlr);
670 return;
671 }
672 }
673 testcase.stop("No space left in S1apExpectTableProc")
674}
675
676/* client/conn_hdlr side function to use procedure port to create expect (PDU) in emulation */
677function f_create_s1ap_expect_proc(integer procedureCode, S1AP_ConnHdlr hdlr) runs on S1AP_ConnHdlr
678{
679 S1AP_PROC.call(S1APEM_register_proc:{procedureCode, self}) {
680 [] S1AP_PROC.getreply(S1APEM_register_proc:{?,?}) {};
681 }
682
683 log(procedureCode);
684}
Harald Welte35498112019-07-02 14:26:39 +0800685
Pau Espin Pedrol3be4d922024-01-15 15:21:57 +0100686/* Derive NAS Token (and post-increment ul_count): */
687function f_s1apem_derive_nas_token(in octetstring kasme) runs on S1AP_ConnHdlr return OCT32
688{
689 var OCT32 nas_token;
690 S1AP_PROC.call(S1APEM_derive_nas_token:{kasme, self, -}) {
691 [] S1AP_PROC.getreply(S1APEM_derive_nas_token:{kasme, self, ?}) -> param(nas_token) {
692 return nas_token;
693 };
694 }
695}
696
Harald Welte35498112019-07-02 14:26:39 +0800697private function f_expect_table_init()
698runs on S1AP_Emulation_CT {
699 var integer i;
700 for (i := 0; i < sizeof(S1apExpectTable); i := i + 1) {
Philipp Maier7147c922023-07-07 14:18:32 +0200701 S1apExpectTable[i].mme_id := omit;
702 S1apExpectTable[i].enb_id := omit;
703 S1apExpectTable[i].vc_conn := null;
Harald Welte35498112019-07-02 14:26:39 +0800704 }
Philipp Maierfc7ada22023-07-24 11:32:12 +0200705
706 for (i := 0; i < sizeof(S1apExpectTableProc); i := i + 1) {
707 S1apExpectTableProc[i].procedureCode := omit;
708 S1apExpectTableProc[i].vc_conn := null;
709 }
Harald Welte35498112019-07-02 14:26:39 +0800710}
711
712function DummyUnitdataCallback(S1AP_PDU msg)
713runs on S1AP_Emulation_CT return template S1AP_PDU {
714 log("Ignoring S1AP ", msg);
715 return omit;
716}
717
718
719function f_S1AP_get_ENB_UE_S1AP_ID(S1AP_PDU s1ap) return template (omit) ENB_UE_S1AP_ID
720{
721 if (ischosen(s1ap.initiatingMessage)) {
722 var InitiatingMessage im := s1ap.initiatingMessage;
723 select (s1ap) {
724 case (tr_S1AP_InitialUE) {
725 return im.value_.InitialUEMessage.protocolIEs[0].value_.ENB_UE_S1AP_ID;
726 }
727 case (tr_S1AP_DlNasTransport) {
728 return im.value_.DownlinkNASTransport.protocolIEs[1].value_.ENB_UE_S1AP_ID;
729 }
730 case (tr_S1AP_UlNasTransport) {
731 return im.value_.UplinkNASTransport.protocolIEs[1].value_.ENB_UE_S1AP_ID;
732 }
733 case (tr_S1AP_IntialCtxSetupReq) {
734 return im.value_.initialContextSetupRequest.protocolIEs[1].value_.ENB_UE_S1AP_ID;
735 }
736 case (tr_S1AP_UeContextReleaseReq) {
737 return im.value_.UEContextReleaseRequest.protocolIEs[1].value_.ENB_UE_S1AP_ID;
738 }
Pau Espin Pedrola5bb8072023-12-21 19:15:31 +0100739 case (tr_S1AP_UeContextReleaseCmd) {
740 if (ispresent(im.value_.uEContextReleaseCommand.protocolIEs[0].value_.uE_S1AP_IDs.uE_S1AP_ID_pair)) {
741 return im.value_.uEContextReleaseCommand.protocolIEs[0].value_.uE_S1AP_IDs.uE_S1AP_ID_pair.eNB_UE_S1AP_ID;
742 } else {
743 return omit;
744 }
745 }
Harald Welte35498112019-07-02 14:26:39 +0800746 case (tr_S1AP_ConnEstInd) {
747 return im.value_.ConnectionEstablishmentIndication.protocolIEs[1].value_.ENB_UE_S1AP_ID;
748 }
749 /* TODO */
750 }
751 } else if (ischosen(s1ap.successfulOutcome)) {
752 var SuccessfulOutcome so := s1ap.successfulOutcome;
753 select (s1ap) {
754 case (tr_S1AP_InitialCtxSetupResp) {
755 return so.value_.initialContextSetupResponse.protocolIEs[1].value_.ENB_UE_S1AP_ID;
756 }
757 case (tr_S1AP_UeContextReleaseCompl) {
758 return so.value_.UEContextReleaseComplete.protocolIEs[1].value_.ENB_UE_S1AP_ID;
759 }
760 /* TODO */
761 }
762 } else if (ischosen(s1ap.unsuccessfulOutcome)) {
763 var UnsuccessfulOutcome uo := s1ap.unsuccessfulOutcome;
764 select (s1ap) {
765 case (tr_S1AP_InitialCtxSetupFail) {
766 return uo.value_.initialContextSetupFailure.protocolIEs[1].value_.ENB_UE_S1AP_ID;
767 }
768 /* TODO */
769 }
770 }
771 return omit;
772}
773
774function f_S1AP_get_MME_UE_S1AP_ID(S1AP_PDU s1ap) return template (omit) MME_UE_S1AP_ID
775{
776 if (ischosen(s1ap.initiatingMessage)) {
777 var InitiatingMessage im := s1ap.initiatingMessage;
778 select (s1ap) {
779 case (tr_S1AP_DlNasTransport) {
780 return im.value_.DownlinkNASTransport.protocolIEs[0].value_.MME_UE_S1AP_ID;
781 }
782 case (tr_S1AP_UlNasTransport) {
783 return im.value_.UplinkNASTransport.protocolIEs[0].value_.MME_UE_S1AP_ID;
784 }
785 case (tr_S1AP_IntialCtxSetupReq) {
786 return im.value_.initialContextSetupRequest.protocolIEs[0].value_.MME_UE_S1AP_ID;
787 }
788 case (tr_S1AP_UeContextReleaseReq) {
789 return im.value_.UEContextReleaseRequest.protocolIEs[0].value_.MME_UE_S1AP_ID;
790 }
Pau Espin Pedrola5bb8072023-12-21 19:15:31 +0100791 case (tr_S1AP_UeContextReleaseCmd) {
792 if (ispresent(im.value_.uEContextReleaseCommand.protocolIEs[0].value_.uE_S1AP_IDs.uE_S1AP_ID_pair)) {
793 return im.value_.uEContextReleaseCommand.protocolIEs[0].value_.uE_S1AP_IDs.uE_S1AP_ID_pair.mME_UE_S1AP_ID;
794 } else {
795 return im.value_.uEContextReleaseCommand.protocolIEs[0].value_.uE_S1AP_IDs.mME_UE_S1AP_ID;
796 }
797 }
Harald Welte35498112019-07-02 14:26:39 +0800798 case (tr_S1AP_ConnEstInd) {
799 return im.value_.ConnectionEstablishmentIndication.protocolIEs[0].value_.MME_UE_S1AP_ID;
800 }
801 /* TODO */
802 }
803 } else if (ischosen(s1ap.successfulOutcome)) {
804 var SuccessfulOutcome so := s1ap.successfulOutcome;
805 select (s1ap) {
806 case (tr_S1AP_InitialCtxSetupResp) {
807 return so.value_.initialContextSetupResponse.protocolIEs[0].value_.MME_UE_S1AP_ID;
808 }
809 case (tr_S1AP_UeContextReleaseCompl) {
810 return so.value_.UEContextReleaseComplete.protocolIEs[0].value_.MME_UE_S1AP_ID;
811 }
812 /* TODO */
813 }
814 } else if (ischosen(s1ap.unsuccessfulOutcome)) {
815 var UnsuccessfulOutcome uo := s1ap.unsuccessfulOutcome;
816 select (s1ap) {
817 case (tr_S1AP_InitialCtxSetupFail) {
818 return uo.value_.initialContextSetupFailure.protocolIEs[0].value_.MME_UE_S1AP_ID;
819 }
820 /* TODO */
821 }
822 }
823 return omit;
824}
825
826function f_S1AP_get_NAS_PDU(S1AP_PDU s1ap) return template (omit) NAS_PDU
827{
Pau Espin Pedrol2edec462023-12-15 19:16:02 +0100828 var integer i, j;
Harald Welte35498112019-07-02 14:26:39 +0800829
830 if (ischosen(s1ap.initiatingMessage)) {
831 var InitiatingMessage im := s1ap.initiatingMessage;
832 select (s1ap) {
833 case (tr_S1AP_DlNasTransport) {
834 var DownlinkNASTransport msg := im.value_.DownlinkNASTransport;
835 for (i := 0; i < lengthof(msg.protocolIEs); i := i+1) {
836 if (msg.protocolIEs[i].id == id_NAS_PDU) {
837 return msg.protocolIEs[i].value_.NAS_PDU;
838 }
839 }
840 }
841 case (tr_S1AP_UlNasTransport) {
842 var UplinkNASTransport msg := im.value_.UplinkNASTransport;
843 for (i := 0; i < lengthof(msg.protocolIEs); i := i+1) {
844 if (msg.protocolIEs[i].id == id_NAS_PDU) {
845 return msg.protocolIEs[i].value_.NAS_PDU;
846 }
847 }
848 }
Pau Espin Pedrol2edec462023-12-15 19:16:02 +0100849 case (tr_S1AP_IntialCtxSetupReq) {
850 var InitialContextSetupRequest msg := im.value_.initialContextSetupRequest;
851 for (i := 0; i < lengthof(msg.protocolIEs); i := i+1) {
852 if (msg.protocolIEs[i].id == id_E_RABToBeSetupListCtxtSUReq) {
853 var E_RABToBeSetupListCtxtSUReq rab_req := msg.protocolIEs[i].value_.E_RABToBeSetupListCtxtSUReq;
854 for (j := 0; j < lengthof(rab_req); j := j+1) {
855 var E_RABToBeSetupItemCtxtSUReq it := rab_req[j].value_.E_RABToBeSetupItemCtxtSUReq;
856 return it.nAS_PDU;
857 }
858 }
859 }
860 return omit;
861 }
Harald Welte35498112019-07-02 14:26:39 +0800862 }
863 }
864 return omit;
865}
866
867
868
869}