pespin has uploaded this change for review.

View Change

WIP: Introduce udr testsuite

Introduce a new "udr" testsuite which runs against open5gs-udrd.
Start with a couple tests which already contains most of the
infrastructure needed and makes it easy to extend later on.

Related: SYS#7073
Change-Id: I025c7e87e9a8308c084edc7a7da23c080a0dc970
---
M Makefile
M library/General_Types.ttcn
M library/JSON_Generic_Templates.ttcn
M library/TS29505_Subscription_Data_Templates.ttcn
A udr/ConnHdlr.ttcn
A udr/README.md
A udr/UDR_Tests.cfg
A udr/UDR_Tests.default
A udr/UDR_Tests.ttcn
A udr/create_test_subscribers.sh
A udr/expected-results.xml
A udr/gen_links.sh
A udr/mongod.conf
A udr/open5gs-udr.yaml
A udr/regen_makefile.sh
A udr/testenv.cfg
A udr/testsrcdir.cfg
17 files changed, 976 insertions(+), 0 deletions(-)

git pull ssh://gerrit.osmocom.org:29418/osmo-ttcn3-hacks refs/changes/48/43948/1
diff --git a/Makefile b/Makefile
index 87adb6d..ed02e18 100644
--- a/Makefile
+++ b/Makefile
@@ -56,6 +56,7 @@
stp \
sysinfo \
udm \
+ udr \
upf \
$(NULL)

diff --git a/library/General_Types.ttcn b/library/General_Types.ttcn
index 1b9a732..da12111 100644
--- a/library/General_Types.ttcn
+++ b/library/General_Types.ttcn
@@ -173,6 +173,7 @@
type hexstring HEX4n length(4) with { variant "" };
type hexstring HEX6n length(6) with { variant "" };
type hexstring HEX8n length(8) with { variant "" };
+ type hexstring HEX12n length(12) with { variant "" };
type hexstring HEX14n length(14) with { variant "" };
type hexstring HEX15n length(15) with { variant "" };
type hexstring HEX16n length(16) with { variant "" };
diff --git a/library/JSON_Generic_Templates.ttcn b/library/JSON_Generic_Templates.ttcn
index 0ba8ebd..5228550 100644
--- a/library/JSON_Generic_Templates.ttcn
+++ b/library/JSON_Generic_Templates.ttcn
@@ -15,6 +15,31 @@
JSON_integer := val
}

+template (value) JSON_generic_val
+ts_JSON_generic_val_integer(template (value) integer val) := {
+ JSON_integer := val
+}
+
+template (present) JSON_generic_val
+tr_JSON_generic_val_string(template (present) charstring val := ?) := {
+ JSON_string := val
+}
+
+template (value) JSON_generic_val
+ts_JSON_generic_val_string(template (value) charstring val) := {
+ JSON_string := val
+}
+
+template (present) JSON_generic_val
+tr_JSON_generic_val_bool(template (present) boolean val := ?) := {
+ JSON_bool := val
+}
+
+template (value) JSON_generic_val
+ts_JSON_generic_val_bool(template (value) boolean val) := {
+ JSON_bool := val
+}
+
function f_JSON_Generic_val_object_get_val(JSON_generic_val json, universal charstring key)
return JSON_generic_val {
if (not ischosen(json.JSON_object)) {
diff --git a/library/TS29505_Subscription_Data_Templates.ttcn b/library/TS29505_Subscription_Data_Templates.ttcn
index 5717941..a523e1a 100644
--- a/library/TS29505_Subscription_Data_Templates.ttcn
+++ b/library/TS29505_Subscription_Data_Templates.ttcn
@@ -1,5 +1,6 @@
module TS29505_Subscription_Data_Templates {

+import from JSON_Generic all
import from TS29505_Subscription_Data all;

template (value) AuthMethod
@@ -7,6 +8,130 @@
enum_val := enum_val
}

+template (value) PatchOperation
+ts_PatchOperation(PatchOperation_enum enum_val) := {
+ enum_val := enum_val
+}
+
+template (value) PlmnId
+ts_PlmnId(template (value) Mcc mcc,
+ template (value) Mnc mnc) := {
+ mcc := mcc,
+ mnc := mnc
+}
+
+template (value) PlmnIdNid
+ts_PlmnIdNid(template (value) Mcc mcc,
+ template (value) Mnc mnc,
+ template (omit) Nid nid := omit) := {
+ mcc := mcc,
+ mnc := mnc,
+ nid := nid
+}
+
+template (value) Guami
+ts_Guami(template (value) PlmnIdNid plmnId,
+ template (value) AmfId amfId) := {
+ plmnId := plmnId,
+ amfId := amfId
+}
+
+template (value) RatType
+ts_RatType(RatType_enum enum_val) := {
+ enum_val := enum_val
+}
+
+template (value) PatchItem
+ts_PatchItem(template (value) PatchOperation op,
+ template (value) charstring path,
+ template (omit) charstring from_ := omit,
+ template (omit) JSON_Generic.JSON_generic_val value_ := omit) := {
+ op := op,
+ path := path,
+ from_ := from_,
+ value_ := value_
+}
+
+
+type TS29505_Subscription_Data.Amf3GppAccessRegistration.backupAmfInfo BackupAmfInfoList;
+
+template (value) Amf3GppAccessRegistration
+ts_Amf3GppAccessRegistration(template (value) NfInstanceId amfInstanceId,
+ template (omit) SupportedFeatures supportedFeatures := omit,
+ template (omit) PurgeFlag purgeFlag := omit,
+ template (omit) Pei pei := omit,
+ template (omit) ImsVoPs imsVoPs := omit,
+ template (value) Uri deregCallbackUri,
+ template (omit) ServiceName amfServiceNameDereg := omit,
+ template (omit) Uri pcscfRestorationCallbackUri := omit,
+ template (omit) ServiceName amfServiceNamePcscfRest := omit,
+ template (omit) boolean initialRegistrationInd := omit,
+ template (omit) boolean emergencyRegistrationInd := omit,
+ template (value) Guami guami,
+ template (omit) BackupAmfInfoList backupAmfInfo := omit,
+ template (omit) DualRegistrationFlag drFlag := omit,
+ template (omit) boolean eps5GsMobilityWoN26 := omit,
+ template (value) RatType ratType,
+ template (omit) boolean urrpIndicator := omit,
+ template (omit) Uri amfEeSubscriptionId := omit,
+ template (omit) EpsInterworkingInfo epsInterworkingInfo := omit,
+ template (omit) boolean ueSrvccCapability := omit,
+ template (omit) DateTime registrationTime := omit,
+ template (omit) VgmlcAddress vgmlcAddress := omit,
+ template (omit) ContextInfo contextInfo := omit,
+ template (omit) boolean noEeSubscriptionInd := omit,
+ template (omit) Supi supi := omit,
+ template (omit) UeReachableInd ueReachableInd := omit,
+ template (omit) boolean reRegistrationRequired := omit,
+ template (omit) boolean adminDeregSubWithdrawn := omit,
+ template (omit) Uri dataRestorationCallbackUri := omit,
+ template (omit) record of charstring resetIds := omit,
+ template (omit) boolean disasterRoamingInd := omit,
+ template (omit) boolean ueMINTCapability := omit,
+ template (omit) boolean ueMintEpsCapability := omit,
+ template (omit) boolean sorSnpnSiSupported := omit,
+ template (omit) boolean udrRestartInd := omit,
+ template (omit) DateTime lastSynchronizationTime := omit,
+ template (omit) boolean ueSnpnSubscriptionInd := omit) := {
+ amfInstanceId := amfInstanceId,
+ supportedFeatures := supportedFeatures,
+ purgeFlag := purgeFlag,
+ pei := pei,
+ imsVoPs := imsVoPs,
+ deregCallbackUri := deregCallbackUri,
+ amfServiceNameDereg := amfServiceNameDereg,
+ pcscfRestorationCallbackUri := pcscfRestorationCallbackUri,
+ amfServiceNamePcscfRest := amfServiceNamePcscfRest,
+ initialRegistrationInd := initialRegistrationInd,
+ emergencyRegistrationInd := emergencyRegistrationInd,
+ guami := guami,
+ backupAmfInfo := backupAmfInfo,
+ drFlag := drFlag,
+ eps5GsMobilityWoN26 := eps5GsMobilityWoN26,
+ ratType := ratType,
+ urrpIndicator := urrpIndicator,
+ amfEeSubscriptionId := amfEeSubscriptionId,
+ epsInterworkingInfo := epsInterworkingInfo,
+ ueSrvccCapability := ueSrvccCapability,
+ registrationTime := registrationTime,
+ vgmlcAddress := vgmlcAddress,
+ contextInfo := contextInfo,
+ noEeSubscriptionInd := noEeSubscriptionInd,
+ supi := supi,
+ ueReachableInd := ueReachableInd,
+ reRegistrationRequired := reRegistrationRequired,
+ adminDeregSubWithdrawn := adminDeregSubWithdrawn,
+ dataRestorationCallbackUri := dataRestorationCallbackUri,
+ resetIds := resetIds,
+ disasterRoamingInd := disasterRoamingInd,
+ ueMINTCapability := ueMINTCapability,
+ ueMintEpsCapability := ueMintEpsCapability,
+ sorSnpnSiSupported := sorSnpnSiSupported,
+ udrRestartInd := udrRestartInd,
+ lastSynchronizationTime := lastSynchronizationTime,
+ ueSnpnSubscriptionInd := ueSnpnSubscriptionInd
+}
+
template (present) AuthenticationSubscription
tr_AuthenticationSubscription(template (present) AuthMethod authenticationMethod := ?,
template charstring encPermanentKey := *,
@@ -82,6 +207,19 @@
}

type TS29505_Subscription_Data.SequenceNumber.lastIndexes SequenceNumberIndexesMap;
+template (present) SequenceNumber
+tr_SequenceNumber(template SqnScheme sqnScheme := *,
+ template charstring sqn := *,
+ template SequenceNumberIndexesMap lastIndexes := *,
+ template integer indLength := *,
+ template Sign difSign := *) := {
+ sqnScheme := sqnScheme,
+ sqn := sqn,
+ lastIndexes := lastIndexes,
+ indLength := indLength,
+ difSign := difSign
+}
+
template (value) SequenceNumber
ts_SequenceNumber(template (omit) SqnScheme sqnScheme := omit,
template (omit) charstring sqn := omit,
diff --git a/udr/ConnHdlr.ttcn b/udr/ConnHdlr.ttcn
new file mode 100644
index 0000000..27014b7
--- /dev/null
+++ b/udr/ConnHdlr.ttcn
@@ -0,0 +1,398 @@
+/* UDR test suite in TTCN-3 (3GPP TS 29.504, TS 29.505), ConnHdlr
+ * (C) 2026 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * All rights reserved.
+ * Author: Pau Espin Pedrol <pespin@sysmocom.de>
+ *
+ * Released under the terms of GNU General Public License, Version 2 or
+ * (at your option) any later version.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+module ConnHdlr {
+
+import from TCCEncoding_Functions all;
+
+import from General_Types all;
+import from Native_Functions all;
+import from IPL4asp_Types all;
+import from Misc_Helpers all;
+
+import from Osmocom_Types all;
+import from GSM_Types all;
+
+import from HTTP2_Types all;
+import from HTTP2_Templates all;
+import from HTTP2_Functions all;
+import from HTTP2_Msg all;
+import from HTTP2_Adapter all;
+import from HTTP2_Server_Emulation all;
+
+import from Prometheus_Checker all;
+
+import from TS29505_Subscription_Data all;
+import from TS29505_Subscription_Data_Templates all;
+
+import from UDR_Tests all;
+
+const integer HTTP2_CONN_SCP := 0;
+
+/* These are a preset of path matches which can be applied through the entire lifecycle of the session: */
+function f_http2_server_path_match()
+runs on ConnHdlr return charstring {
+ var charstring imsi_match := "*imsi-" & g_pars.imsi_str & "*";
+ return imsi_match;
+}
+
+type record ConnHdlrPars {
+ hexstring imsi,
+ charstring imsi_str,
+ charstring serving_network,
+
+ charstring permanent_key,
+ charstring opc,
+ AuthenticationSubscription auth_subscr optional
+}
+
+type component ConnHdlr extends HTTP2_CT, HTTP2_ConnHdlr, Prometheus_Checker_CT {
+ var ConnHdlrPars g_pars;
+}
+
+type function void_fn() runs on ConnHdlr;
+
+function f_ConnHdlrPars(integer idx := 0) return ConnHdlrPars
+{
+ var hexstring imsi := f_concat_pad(lengthof(mp_imsi), substr(mp_imsi, 0, lengthof(mp_imsi) - 6), idx);
+ var charstring imsi_str := f_dec_TBCD(imsi_hex2oct(imsi));
+ var template (value) ConnHdlrPars pars := {
+ imsi := imsi,
+ imsi_str := imsi_str,
+ serving_network := "5G:mnc070.mcc999.3gppnetwork.org", /* TODO: set based on mp_mcc and mp_mnc */
+ permanent_key := "3c6e0b8a9c15224a8228b9a98ca1531d",
+ opc := "762a2206fe0b4151ace403c86a11e479",
+ auth_subscr := omit
+ };
+ return valueof(pars);
+}
+
+function f_ConnHdlr_init(void_fn fn, ConnHdlrPars pars) runs on ConnHdlr {
+ /* make parameters available via component variable */
+ g_pars := pars;
+
+ /* Setup */
+ /* Register SCP HTTP2 sever, where IUT will send all SBI requests: */
+ f_http2_server_emu_register(f_http2_server_path_match(), idx := HTTP2_CONN_SCP);
+ f_http2_adapter_init();
+
+ fn.apply();
+
+ /* Cleanup */
+ f_http2_server_emu_unregister(f_http2_server_path_match(), idx := HTTP2_CONN_SCP);
+}
+
+/********************************************
+ * SCP
+/*******************************************/
+const charstring SCP_uri_hostport := mp_scp_local_ip & ":" & int2str(mp_scp_local_port);
+const charstring SCP_uri_prefix := "http://" & SCP_uri_hostport;
+
+/************************************************************************************
+ * N35 Nudr_DataRepository (SBI): Emulated UDM, 3GPP TS 29.504, 3GPP TS 29.505 A.2
+ ************************************************************************************/
+
+const charstring Nudr_uri_hostport := mp_udr_hostname & ":" & int2str(mp_udr_sbi_port);
+const charstring Nudr_uri_prefix := "http://" & Nudr_uri_hostport;
+const charstring Nudr_DR_path_api_prefix := "/nudr-dr/v1";
+
+/* "{ueId}" */
+template (value) charstring
+ts_sbi_Supi(template (value) charstring imsi) := "imsi-" & valueof(imsi);
+
+template (value) charstring
+ts_Nudr_DR_path_subscription_data(template (value) charstring imsi)
+:= Nudr_DR_path_api_prefix & "/subscription-data/" & valueof(ts_sbi_Supi(imsi));
+
+template (value) charstring
+ts_Nudr_DR_path_authentication_data(template (value) charstring imsi)
+:= valueof(ts_Nudr_DR_path_subscription_data(imsi)) & "/authentication-data";
+
+/* 3GPP TS 29.505 5.2.2.3.1 Resource: AuthenticationSubscription */
+template (value) charstring
+ts_Nudr_DR_path_authentication_subscription(template (value) charstring imsi)
+:= valueof(ts_Nudr_DR_path_authentication_data(imsi)) & "/authentication-subscription";
+
+/* 3GPP TS 29.505 5.2.24.2 */
+template (value) charstring
+ts_Nudr_DR_path_authentication_status(template (value) charstring imsi)
+:= valueof(ts_Nudr_DR_path_authentication_data(imsi)) & "/authentication-status";
+
+template (value) charstring
+ts_Nudr_DR_path_context_data(template (value) charstring imsi)
+:= valueof(ts_Nudr_DR_path_subscription_data(imsi)) & "/context-data";
+
+template (value) charstring
+ts_Nudr_DR_path_amf_3gpp_access(template (value) charstring imsi)
+:= valueof(ts_Nudr_DR_path_context_data(imsi)) & "/amf-3gpp-access";
+
+/* 3GPP TS 29.505 5.4.2.23 */
+function f_Nudr_DR_sqn_int2hex(integer sqn) return HEX12n
+{
+ return int2hex(sqn, 12);
+}
+
+function f_Nudr_DR_sqn_hex2int(HEX12n sqn) return integer
+{
+ return hex2int(sqn);
+}
+
+function f_ts_PlmnIdNid()
+runs on ConnHdlr return TS29505_Subscription_Data.PlmnIdNid {
+ return valueof(TS29505_Subscription_Data_Templates.ts_PlmnIdNid("001", "001"));
+}
+
+function f_ts_Guami()
+runs on ConnHdlr return TS29505_Subscription_Data.Guami {
+ var template (value) TS29505_Subscription_Data.PlmnIdNid plmn_id_nid := f_ts_PlmnIdNid();
+ return valueof(TS29505_Subscription_Data_Templates.ts_Guami(plmnId := plmn_id_nid,
+ amfId := "020040"));
+}
+
+/* 3GPP TS 29.505 5.2.2.3.1 Resource: AuthenticationSubscription (GET) */
+private function f_n35_prepare_get_authentication_subscription()
+runs on ConnHdlr return HTTP2_Msg {
+ var integer http2_idx := f_http2_adapter_find_free_idx();
+ var integer http2_stream_id := f_http2_adapter_next_stream_id(http2_idx);
+ var template (value) HTTP2_Msg http2_tx;
+
+ http2_tx := ts_HTTP2_Msg(http2_idx,
+ http2_stream_id,
+ hb := {
+ pseudo_headers := ts_HTTP2_pseudo_headers_Req(method := "GET",
+ scheme := "http",
+ authority := Nudr_uri_hostport,
+ path := ts_Nudr_DR_path_authentication_subscription(g_pars.imsi_str)),
+ headers := {
+ ts_HTTP2_Hdr_content_type("application/json"),
+ ts_HTTP2_Hdr_accept("application/json"),
+ ts_HTTP2_Hdr_user_agent("ttcn3-udm"),
+ ts_HTTP2_Hdr_accept_encoding("gzip")
+ }
+ });
+ return valueof(http2_tx);
+}
+
+/* 3GPP TS 29.505 5.2.2.3.1 Resource: AuthenticationSubscription (GET) */
+function f_n35_get_authentication_subscription(template integer exp_sqn := ?)
+runs on ConnHdlr {
+ var HTTP2_Msg http2_tx;
+ var HTTP2_Msg http2_rx;
+ var template (present) HTTP2_Msg http2_exp_rx;
+
+ http2_tx := f_n35_prepare_get_authentication_subscription();
+ f_http2_adapter_connect(mp_udr_hostname, mp_udr_sbi_port, mp_udm_local_ip, idx := http2_tx.conn_id);
+ f_http2_adapter_send(http2_tx);
+
+ /* 3GPP TS 29.505 Table 5.2.2.3.1-3 */
+ http2_exp_rx :=
+ tr_HTTP2_Msg(http2_tx.conn_id,
+ http2_tx.stream_id,
+ tr_HTTP2_header_block(pseudo_headers := tr_HTTP2_pseudo_headers_Resp(200)),
+ tr_HTTP2_Data_frame(stream_id := http2_tx.stream_id,
+ end_stream_flag := true,
+ data := decmatch tr_AuthenticationSubscription(authenticationMethod := ts_AuthMethod(FiveG_AKA),
+ encPermanentKey := g_pars.permanent_key,
+ sequenceNumber := tr_SequenceNumber(sqn := ?),
+ encOpcKey := g_pars.opc)));
+ as_http2_adapter_receive(http2_rx, http2_exp_rx, idx := http2_tx.conn_id);
+
+ f_dec_AuthenticationSubscription(http2_rx.body.data, g_pars.auth_subscr); // TODO: validate return code
+ var integer rx_sqn := f_Nudr_DR_sqn_hex2int(str2hex(g_pars.auth_subscr.sequenceNumber.sqn));
+ if (not match(rx_sqn, exp_sqn)) {
+ Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail,
+ log2str("Rx unexpected sqn ", rx_sqn, " vs exp ", exp_sqn));
+ }
+
+ f_http2_adapter_close(idx := http2_tx.conn_id);
+ setverdict(pass);
+}
+
+/* 3GPP TS 29.505 5.2.2.3.1 Resource: AuthenticationSubscription (PATCH) */
+private function f_n35_prepare_patch_authentication_subscription(template (value) PatchItem_list patch_item_li)
+runs on ConnHdlr return HTTP2_Msg {
+ var integer http2_idx := f_http2_adapter_find_free_idx();
+ var integer http2_stream_id := f_http2_adapter_next_stream_id(http2_idx);
+ var template (value) HTTP2_Msg http2_tx;
+ var octetstring patch_item_li_enc;
+
+ patch_item_li_enc := f_enc_PatchItem_list(valueof(patch_item_li));
+
+ http2_tx := ts_HTTP2_Msg(http2_idx,
+ http2_stream_id,
+ hb := {
+ pseudo_headers := ts_HTTP2_pseudo_headers_Req(method := "PATCH",
+ scheme := "http",
+ authority := Nudr_uri_hostport,
+ path := ts_Nudr_DR_path_authentication_subscription(g_pars.imsi_str)),
+ headers := {
+ ts_HTTP2_Hdr_content_type("application/json-patch+json"),
+ ts_HTTP2_Hdr_accept("application/json"),
+ ts_HTTP2_Hdr_user_agent("ttcn3-udm"),
+ ts_HTTP2_Hdr_accept_encoding("gzip")
+ }
+ },
+ body := ts_HTTP2_Data_frame(http2_stream_id,
+ end_stream_flag := true,
+ data := patch_item_li_enc));
+ return valueof(http2_tx);
+}
+
+/* 3GPP TS 29.505 5.2.2.3.2 Resource: AuthenticationSubscription (PATCH) */
+function f_n35_patch_authentication_subscription(template (value) PatchItem_list patch_item_li)
+runs on ConnHdlr {
+ var HTTP2_Msg http2_tx;
+ var HTTP2_Msg http2_rx;
+ var template (present) HTTP2_Msg http2_exp_rx;
+
+ http2_tx := f_n35_prepare_patch_authentication_subscription(patch_item_li);
+ f_http2_adapter_connect(mp_udr_hostname, mp_udr_sbi_port, mp_udm_local_ip, idx := http2_tx.conn_id);
+ f_http2_adapter_send(http2_tx);
+
+ /* 3GPP TS 29.505 Table 5.2.2.3.2-3 */
+ http2_exp_rx :=
+ tr_HTTP2_Msg(http2_tx.conn_id,
+ http2_tx.stream_id,
+ tr_HTTP2_header_block(pseudo_headers := tr_HTTP2_pseudo_headers_Resp(204)),
+ omit);
+ as_http2_adapter_receive(http2_rx, http2_exp_rx, idx := http2_tx.conn_id);
+
+ f_http2_adapter_close(idx := http2_tx.conn_id);
+ setverdict(pass);
+}
+
+
+/* 3GPP TS 29.505 5.2.6.3.1 Resource: Amf3GppAccessRegistration (PUT) */
+private function f_n35_prepare_put_amf_3gpp_access(template (value) Amf3GppAccessRegistration acc_reg)
+runs on ConnHdlr return HTTP2_Msg {
+ var integer http2_idx := f_http2_adapter_find_free_idx();
+ var integer http2_stream_id := f_http2_adapter_next_stream_id(http2_idx);
+ var template (value) HTTP2_Msg http2_tx;
+ var octetstring acc_reg_enc;
+
+ acc_reg_enc := f_enc_Amf3GppAccessRegistration(valueof(acc_reg));
+
+ http2_tx := ts_HTTP2_Msg(http2_idx,
+ http2_stream_id,
+ hb := {
+ pseudo_headers := ts_HTTP2_pseudo_headers_Req(method := "PUT",
+ scheme := "http",
+ authority := Nudr_uri_hostport,
+ path := ts_Nudr_DR_path_amf_3gpp_access(g_pars.imsi_str)),
+ headers := {
+ ts_HTTP2_Hdr_content_type("application/json"),
+ ts_HTTP2_Hdr_accept("application/json,application/problem+json"),
+ ts_HTTP2_Hdr_user_agent("ttcn3-udm"),
+ ts_HTTP2_Hdr_accept_encoding("gzip")
+ }
+ },
+ body := ts_HTTP2_Data_frame(http2_stream_id,
+ end_stream_flag := true,
+ data := acc_reg_enc));
+ return valueof(http2_tx);
+}
+
+/* 3GPP TS 29.505 5.2.6.3.1 Resource: Amf3GppAccessRegistration (PUT) */
+function f_n35_put_amf_3gpp_access()
+runs on ConnHdlr {
+ var HTTP2_Msg http2_tx;
+ var HTTP2_Msg http2_rx;
+ var template (present) HTTP2_Msg http2_exp_rx;
+ var template (value) Amf3GppAccessRegistration acc_reg;
+
+ acc_reg := ts_Amf3GppAccessRegistration(amfInstanceId := "d8073276-cd2a-41f1-aa56-07dfb4f73c6e",
+ deregCallbackUri := ts_Namf_uri_callback_dereg_notify(g_pars.imsi_str),
+ initialRegistrationInd := true,
+ guami := f_ts_Guami(),
+ ratType := ts_RatType(NR));
+
+ http2_tx := f_n35_prepare_put_amf_3gpp_access(acc_reg);
+ f_http2_adapter_connect(mp_udr_hostname, mp_udr_sbi_port, mp_udm_local_ip, idx := http2_tx.conn_id);
+ f_http2_adapter_send(http2_tx);
+
+ /* 3GPP TS 29.505 Table 5.2.6.3.1-3 */
+ http2_exp_rx :=
+ tr_HTTP2_Msg(http2_tx.conn_id,
+ http2_tx.stream_id,
+ tr_HTTP2_header_block(pseudo_headers := tr_HTTP2_pseudo_headers_Resp(204)),
+ omit);
+ as_http2_adapter_receive(http2_rx, http2_exp_rx, idx := http2_tx.conn_id);
+
+ f_http2_adapter_close(idx := http2_tx.conn_id);
+ setverdict(pass);
+}
+
+/* 3GPP TS 29.505 5.2.6.3.2 Resource: Amf3GppAccessRegistration (PATCH) */
+private function f_n35_prepare_patch_amf_3gpp_access(template (value) PatchItem_list patch_item_li)
+runs on ConnHdlr return HTTP2_Msg {
+ var integer http2_idx := f_http2_adapter_find_free_idx();
+ var integer http2_stream_id := f_http2_adapter_next_stream_id(http2_idx);
+ var template (value) HTTP2_Msg http2_tx;
+ var octetstring patch_item_li_enc;
+
+ patch_item_li_enc := f_enc_PatchItem_list(valueof(patch_item_li));
+
+ http2_tx := ts_HTTP2_Msg(http2_idx,
+ http2_stream_id,
+ hb := {
+ pseudo_headers := ts_HTTP2_pseudo_headers_Req(method := "PATCH",
+ scheme := "http",
+ authority := Nudr_uri_hostport,
+ path := ts_Nudr_DR_path_amf_3gpp_access(g_pars.imsi_str)),
+ headers := {
+ ts_HTTP2_Hdr_content_type("application/json-patch+json"),
+ ts_HTTP2_Hdr_accept("application/json"),
+ ts_HTTP2_Hdr_user_agent("ttcn3-udm"),
+ ts_HTTP2_Hdr_accept_encoding("gzip")
+ }
+ },
+ body := ts_HTTP2_Data_frame(http2_stream_id,
+ end_stream_flag := true,
+ data := patch_item_li_enc));
+ return valueof(http2_tx);
+}
+
+/* 3GPP TS 29.505 5.2.6.3.2 Resource: Amf3GppAccessRegistration (PATCH) */
+function f_n35_patch_amf_3gpp_access(template (value) PatchItem_list patch_item_li)
+runs on ConnHdlr {
+ var HTTP2_Msg http2_tx;
+ var HTTP2_Msg http2_rx;
+ var template (present) HTTP2_Msg http2_exp_rx;
+
+ http2_tx := f_n35_prepare_patch_amf_3gpp_access(patch_item_li);
+ f_http2_adapter_connect(mp_udr_hostname, mp_udr_sbi_port, mp_udm_local_ip, idx := http2_tx.conn_id);
+ f_http2_adapter_send(http2_tx);
+
+ /* 3GPP TS 29.505 Table 5.2.6.3.2-3 */
+ http2_exp_rx :=
+ tr_HTTP2_Msg(http2_tx.conn_id,
+ http2_tx.stream_id,
+ tr_HTTP2_header_block(pseudo_headers := tr_HTTP2_pseudo_headers_Resp(204)),
+ omit);
+ as_http2_adapter_receive(http2_rx, http2_exp_rx, idx := http2_tx.conn_id);
+
+ f_http2_adapter_close(idx := http2_tx.conn_id);
+ setverdict(pass);
+}
+
+/**************************************
+ * N11 Namf (SBI): Emulated AMF
+ **************************************/
+
+const charstring Namf_uri_prefix := SCP_uri_prefix;
+
+const charstring Namf_Callback_path_api_prefix := "/namf-callback/v1";
+
+template (value) charstring ts_Namf_uri_callback_dereg_notify(template (value) charstring imsi)
+:= Namf_uri_prefix & Namf_Callback_path_api_prefix & "/imsi-" & valueof(imsi) & "/dereg-notify";
+
+
+}
diff --git a/udr/README.md b/udr/README.md
new file mode 100644
index 0000000..3991017
--- /dev/null
+++ b/udr/README.md
@@ -0,0 +1,16 @@
+# UDR_Tests.ttcn
+
+* external interfaces
+ * N35: SBI (emulates UDM)
+ * N36: SBI (emulates PCF)
+
+{% dot udr_tests.svg
+digraph G {
+ rankdir=LR;
+ ATS [label="ATS\nUDR_Tests.ttcn"];
+ UDR [label="AMF\open5gs-udrd",shape="box"];
+
+ ATS -> UDR [label="N35"];
+ ATS -> UDR [label="N36"];
+}
+%}
diff --git a/udr/UDR_Tests.cfg b/udr/UDR_Tests.cfg
new file mode 100644
index 0000000..433b529
--- /dev/null
+++ b/udr/UDR_Tests.cfg
@@ -0,0 +1,23 @@
+[ORDERED_INCLUDE]
+# Common configuration, shared between test suites
+"../Common.cfg"
+# testsuite specific configuration, not expected to change
+"./UDR_Tests.default"
+
+# Local configuration below
+
+[LOGGING]
+
+[TESTPORT_PARAMETERS]
+
+[MODULE_PARAMETERS]
+UDR_Tests.mp_udr_hostname := "127.0.0.20";
+UDR_Tests.mp_udr_sbi_port := 7777;
+UDR_Tests.mp_scp_local_ip := "127.0.0.200";
+UDR_Tests.mp_scp_local_port := 7777;
+UDR_Tests.mp_udm_local_ip := "127.0.0.6";
+
+[MAIN_CONTROLLER]
+
+[EXECUTE]
+UDR_Tests.control
diff --git a/udr/UDR_Tests.default b/udr/UDR_Tests.default
new file mode 100644
index 0000000..b03f59e
--- /dev/null
+++ b/udr/UDR_Tests.default
@@ -0,0 +1,12 @@
+[LOGGING]
+FileMask := LOG_ALL | TTCN_DEBUG | TTCN_MATCHING | DEBUG_ENCDEC;
+
+[TESTPORT_PARAMETERS]
+*.TCP.noDelay := "yes" // turn off nagle
+*.HTTP.use_notification_ASPs := "yes"
+
+[MODULE_PARAMETERS]
+
+[MAIN_CONTROLLER]
+
+[EXECUTE]
diff --git a/udr/UDR_Tests.ttcn b/udr/UDR_Tests.ttcn
new file mode 100644
index 0000000..2bf6066
--- /dev/null
+++ b/udr/UDR_Tests.ttcn
@@ -0,0 +1,166 @@
+/* UDR test suite in TTCN-3 (3GPP TS 29.503)
+ * (C) 2026 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * All rights reserved.
+ * Author: Pau Espin Pedrol <pespin@sysmocom.de>
+ *
+ * Released under the terms of GNU General Public License, Version 2 or
+ * (at your option) any later version.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+module UDR_Tests {
+
+import from General_Types all;
+import from Native_Functions all;
+import from IPL4asp_Types all;
+import from Misc_Helpers all;
+
+import from Osmocom_Types all;
+import from GSM_Types all;
+
+import from TCCEncoding_Functions all;
+
+import from HTTP2_Adapter all;
+import from HTTP2_Server_Emulation all;
+
+import from JSON_Generic_Templates all;
+import from TS29505_Subscription_Data_Templates all;
+
+import from ConnHdlr all;
+
+modulepar {
+ charstring mp_udr_hostname := "127.0.0.20";
+ integer mp_udr_sbi_port := 7777;
+
+ charstring mp_scp_local_ip := "127.0.0.200";
+ integer mp_scp_local_port := 7777;
+
+ /* IP address at which we listen for HTTP2/SBI to emulate a UDM in ttcn3 */
+ charstring mp_udm_local_ip := "127.0.0.6";
+
+ GsmMcc mp_mcc := '999'H;
+ GsmMnc mp_mnc := '70'H;
+ HEX15n mp_imsi := '999700000000000'H;
+}
+
+type component MTC_CT extends HTTP2_CT, HTTP2_ConnHdlr {
+ /* emulated SCP */
+ var HTTP2_Server_Emulation_CT vc_HTTP2_SRV_SCP;
+
+ timer g_Tguard := 30.0;
+}
+
+private function f_init_http2_srv(charstring id) runs on MTC_CT {
+ id := id & "-PFCP";
+
+ var Http2ServerEmulationCfg http2_srv_cfg := {
+ http2_bind_ip := mp_scp_local_ip,
+ http2_bind_port := mp_scp_local_port,
+ one_req_per_conn := false
+ };
+
+ vc_HTTP2_SRV_SCP := HTTP2_Server_Emulation_CT.create(id) alive;
+ vc_HTTP2_SRV_SCP.start(HTTP2_Server_Emulation.main(http2_srv_cfg));
+
+ connect(self:HTTP2_CONN_PROC[HTTP2_CONN_SCP], vc_HTTP2_SRV_SCP:CONN_PROC);
+}
+
+
+private function f_init(integer imsi_suffix := 0, float t_guard := 30.0) runs on MTC_CT {
+ var charstring id := testcasename();
+
+ g_Tguard.start(t_guard);
+ activate(as_Tguard());
+
+ f_init_http2_srv(id);
+}
+
+private function f_shutdown() runs on MTC_CT {
+ f_http2_server_close_conn_all();
+ setverdict(pass);
+}
+
+private function f_start_handler_create()
+runs on MTC_CT return ConnHdlr
+{
+ var ConnHdlr vc_conn;
+ var charstring id := testcasename();
+
+ vc_conn := ConnHdlr.create(id);
+
+ if (isbound(vc_HTTP2_SRV_SCP)) {
+ connect(vc_conn:HTTP2_CONN[HTTP2_CONN_SCP], vc_HTTP2_SRV_SCP:CONN);
+ connect(vc_conn:HTTP2_CONN_PROC[HTTP2_CONN_SCP], vc_HTTP2_SRV_SCP:CONN_PROC);
+ }
+
+ return vc_conn;
+}
+
+private function f_start_handler_run(ConnHdlr vc_conn, void_fn fn, ConnHdlrPars pars) runs on MTC_CT
+{
+ vc_conn.start(f_ConnHdlr_init(fn, pars));
+}
+
+/* start a connection handler with given parameters */
+private function f_start_handler(void_fn fn, ConnHdlrPars pars)
+runs on MTC_CT return ConnHdlr {
+ var ConnHdlr vc_conn;
+ vc_conn := f_start_handler_create();
+ f_start_handler_run(vc_conn, fn, pars);
+ return vc_conn;
+}
+
+private altstep as_Tguard() runs on MTC_CT {
+ [] g_Tguard.timeout {
+ Misc_Helpers.f_shutdown(__BFILE__, __LINE__, fail, "Tguard timeout");
+ }
+}
+
+private function f_TC_authentication_subscription() runs on ConnHdlr {
+ f_n35_get_authentication_subscription();
+ var integer sqn := str2int(g_pars.auth_subscr.sequenceNumber.sqn);
+ sqn := 96;
+ f_n35_patch_authentication_subscription({ ts_PatchItem(op := ts_PatchOperation(replace_),
+ path := "/sequenceNumber/sqn",
+ value_ := ts_JSON_generic_val_string(hex2str(f_Nudr_DR_sqn_int2hex(sqn)))) });
+ /* Upon patch, open5gs-udrd increments (+32) the sqn: */
+ f_n35_get_authentication_subscription(exp_sqn := sqn + 32);
+ setverdict(pass);
+}
+testcase TC_authentication_subscription() runs on MTC_CT {
+ var ConnHdlr vc_conn;
+ var ConnHdlrPars pars := f_ConnHdlrPars(0);
+ f_init();
+ vc_conn := f_start_handler(refers(f_TC_authentication_subscription), pars);
+ vc_conn.done;
+ f_shutdown();
+
+ setverdict(pass);
+}
+
+/* 5.2.6 Resource: Amf3GppAccessRegistration */
+private function f_TC_amf_3gpp_access_registration() runs on ConnHdlr {
+ f_n35_put_amf_3gpp_access();
+ f_n35_patch_amf_3gpp_access({ ts_PatchItem(op := ts_PatchOperation(replace_),
+ path := "PurgeFlag",
+ value_ := ts_JSON_generic_val_bool(true)) });
+ setverdict(pass);
+}
+testcase TC_amf_3gpp_access_registration() runs on MTC_CT {
+ var ConnHdlr vc_conn;
+ var ConnHdlrPars pars := f_ConnHdlrPars(0);
+ f_init();
+ vc_conn := f_start_handler(refers(f_TC_amf_3gpp_access_registration), pars);
+ vc_conn.done;
+ f_shutdown();
+
+ setverdict(pass);
+}
+
+control {
+ execute( TC_authentication_subscription() );
+ execute( TC_amf_3gpp_access_registration() );
+}
+
+}
diff --git a/udr/create_test_subscribers.sh b/udr/create_test_subscribers.sh
new file mode 100755
index 0000000..715d66d
--- /dev/null
+++ b/udr/create_test_subscribers.sh
@@ -0,0 +1,31 @@
+#!/bin/sh -ex
+DB_URI="mongodb://127.0.0.103/open5gs"
+DBCTL_CMD="open5gs-dbctl --db_uri=$DB_URI"
+
+# mongod needs some time to bootstrap...
+while ! mongosh --quiet $DB_URI</dev/null; do
+ sleep 1
+done
+
+NUM_SUBCRS=1
+get_subscr_imsi() {
+ subscr_idx=$1
+ printf "999700000000%03u" "$subscr_idx"
+}
+
+# Create a test subscriber with IMSI=001010000000000
+DBCTL_PARALLEL_JOBS=20
+idx=0
+while test "$idx" -lt "$NUM_SUBCRS"; do
+ remain="$((NUM_SUBCRS - idx))"
+ if test "$remain" -gt "$DBCTL_PARALLEL_JOBS"; then
+ iterations=$DBCTL_PARALLEL_JOBS
+ else
+ iterations=$remain
+ fi
+ for it in $(seq "$iterations"); do
+ $DBCTL_CMD add "$(get_subscr_imsi $idx)" 3c6e0b8a9c15224a8228b9a98ca1531d 762a2206fe0b4151ace403c86a11e479 &
+ idx=$((idx + 1))
+ done
+ wait $(jobs -p)
+done
diff --git a/udr/expected-results.xml b/udr/expected-results.xml
new file mode 100644
index 0000000..2a269c1
--- /dev/null
+++ b/udr/expected-results.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0"?>
+<testsuite name='Titan' tests='2' failures='0' errors='0' skipped='0' inconc='0' time='MASKED'>
+ <testcase classname='UDR_Tests' name='TC_authentication_subscription' time='MASKED'/>
+ <testcase classname='UDR_Tests' name='TC_amf_3gpp_access_registration' time='MASKED'/>
+</testsuite>
diff --git a/udr/gen_links.sh b/udr/gen_links.sh
new file mode 100755
index 0000000..9be2c01
--- /dev/null
+++ b/udr/gen_links.sh
@@ -0,0 +1,52 @@
+#!/bin/bash -e
+
+BASEDIR=../deps
+
+. ../_buildsystem/gen_links.inc.sh
+
+DIR=$BASEDIR/titan.Libraries.TCCUsefulFunctions/src
+FILES="TCCInterface_Functions.ttcn TCCConversion_Functions.ttcn TCCConversion.cc TCCInterface.cc TCCInterface_ip.h"
+FILES+=" TCCEncoding_Functions.ttcn TCCEncoding.cc " # GSM 7-bit coding
+gen_links $DIR $FILES
+
+DIR=$BASEDIR/titan.TestPorts.Common_Components.Socket-API/src
+FILES="Socket_API_Definitions.ttcn"
+gen_links $DIR $FILES
+
+# Required by MGCP and IPA
+DIR=$BASEDIR/titan.TestPorts.IPL4asp/src
+FILES="IPL4asp_Functions.ttcn IPL4asp_PT.cc IPL4asp_PT.hh IPL4asp_PortType.ttcn IPL4asp_Types.ttcn IPL4asp_discovery.cc IPL4asp_protocol_L234.hh"
+gen_links $DIR $FILES
+
+# Needed by HTTP_Adapter (Prometheus_Checker.ttcn metrics):
+DIR=$BASEDIR/titan.TestPorts.Common_Components.Abstract_Socket/src
+FILES="Abstract_Socket.cc Abstract_Socket.hh "
+gen_links $DIR $FILES
+
+# Needed by HTTP_Adapter (Prometheus_Checker.ttcn metrics):
+DIR=$BASEDIR/titan.TestPorts.HTTPmsg/src
+FILES="HTTPmsg_MessageLen.ttcn HTTPmsg_MessageLen_Function.cc HTTPmsg_PT.cc HTTPmsg_PT.hh HTTPmsg_PortType.ttcn HTTPmsg_Types.ttcn "
+gen_links $DIR $FILES
+
+# Needed by HTTP2_Adapter:
+DIR=$BASEDIR/titan.ProtocolModules.HTTP2/src
+FILES="HTTP2_EncDec.cc HTTP2_Types.ttcn"
+gen_links $DIR $FILES
+
+DIR=$BASEDIR/titan.ProtocolModules.JSON_v07_2006/src
+FILES="JSON_Generic_Null_Def.asn JSON_Generic.ttcn"
+gen_links $DIR $FILES
+
+DIR=$BASEDIR/osmo-ttcn3-openapi-generator/openapi-specs/3GPP_5GC_Rel19/ttcn3
+FILES="TS29571_CommonData.ttcn TS29505_Subscription_Data.ttcn "
+gen_links $DIR $FILES
+
+DIR=../library
+FILES="Misc_Helpers.ttcn General_Types.ttcn GSM_Types.ttcn Osmocom_Types.ttcn Native_Functions.ttcn Native_FunctionDefs.cc IPCP_Types.ttcn IPCP_Templates.ttcn "
+FILES+="SCTP_Templates.ttcn "
+FILES+="HTTP2_CodecPort.ttcn HTTP2_CodecPort_CtrlFunct.ttcn HTTP2_CodecPort_CtrlFunctDef.cc HTTP2_Templates.ttcn HTTP2_Functions.ttcn HTTP2_Msg.ttcn HTTP2_Adapter.ttcn HTTP2_Server_Emulation.ttcn "
+FILES+="HTTP_Adapter.ttcn Prometheus_Checker.ttcn "
+FILES+="JSON_Generic_Templates.ttcn TS29505_Subscription_Data_Templates.ttcn "
+gen_links $DIR $FILES
+
+gen_links_finish
diff --git a/udr/mongod.conf b/udr/mongod.conf
new file mode 100644
index 0000000..ff648f4
--- /dev/null
+++ b/udr/mongod.conf
@@ -0,0 +1,45 @@
+# mongod.conf
+
+# for documentation of all options, see:
+# http://docs.mongodb.org/manual/reference/configuration-options/
+
+# Where and how to store data.
+storage:
+ dbPath: db
+# journal:
+# enabled: true
+# engine:
+# mmapv1:
+# wiredTiger:
+
+# where to write logging data.
+# systemLog:
+# destination: file
+# logAppend: true
+# path: /data/mongod.log
+
+# network interfaces
+net:
+ port: 27017
+ bindIp: 127.0.0.103
+
+
+# how the process runs
+processManagement:
+ timeZoneInfo: /usr/share/zoneinfo
+
+#security:
+
+#operationProfiling:
+
+#replication:
+
+#sharding:
+
+## Enterprise-Only Options:
+
+#auditLog:
+
+#snmp:
+
+
diff --git a/udr/open5gs-udr.yaml b/udr/open5gs-udr.yaml
new file mode 100644
index 0000000..5889006
--- /dev/null
+++ b/udr/open5gs-udr.yaml
@@ -0,0 +1,20 @@
+# See https://github.com/open5gs/open5gs/blob/main/configs/open5gs/udr.yaml.in
+
+db_uri: mongodb://127.0.0.103/open5gs
+logger:
+ level: debug
+
+global:
+ max:
+ ue: 1024
+
+udr:
+ sbi:
+ server:
+ - address: 127.0.0.20
+ port: 7777
+ client:
+# nrf:
+# - uri: http://127.0.0.10:7777
+ scp:
+ - uri: http://127.0.0.200:7777
diff --git a/udr/regen_makefile.sh b/udr/regen_makefile.sh
new file mode 100755
index 0000000..9aa33ba
--- /dev/null
+++ b/udr/regen_makefile.sh
@@ -0,0 +1,23 @@
+#!/bin/sh -e
+
+NAME=UDR_Tests
+
+FILES="
+ *.asn
+ *.ttcn
+ Abstract_Socket.cc
+ HTTPmsg_MessageLen_Function.cc
+ HTTPmsg_PT.cc
+ HTTP2_EncDec.cc
+ HTTP2_CodecPort_CtrlFunctDef.cc
+ IPL4asp_PT.cc
+ IPL4asp_discovery.cc
+ Native_FunctionDefs.cc
+ TCCConversion.cc
+ TCCEncoding.cc
+ TCCInterface.cc
+"
+
+. ../_buildsystem/regen_makefile.inc.sh
+
+sed -i -e 's/^LINUX_LIBS = -lxml2 -lsctp/LINUX_LIBS = -lxml2 -lsctp -lgnutls/' Makefile
diff --git a/udr/testenv.cfg b/udr/testenv.cfg
new file mode 100644
index 0000000..a5036e7
--- /dev/null
+++ b/udr/testenv.cfg
@@ -0,0 +1,18 @@
+[testsuite]
+program=UDR_Tests
+config=UDR_Tests.cfg
+
+[db]
+prepare=mkdir db
+clean=rm -rf db
+program=mongod --config mongod.conf
+setup=create_test_subscribers.sh
+copy=mongod.conf
+make=no
+package=no
+
+[udr]
+program=open5gs-udrd -c open5gs-udr.yaml
+make=open5gs
+package=open5gs-udr
+copy=open5gs-udr.yaml
diff --git a/udr/testsrcdir.cfg b/udr/testsrcdir.cfg
new file mode 100644
index 0000000..7f19289
--- /dev/null
+++ b/udr/testsrcdir.cfg
@@ -0,0 +1,2 @@
+[testsrcdir]
+max_jobs_per_gb_ram=0.3

To view, visit change 43948. To unsubscribe, or for help writing mail filters, visit settings.

Gerrit-MessageType: newchange
Gerrit-Project: osmo-ttcn3-hacks
Gerrit-Branch: master
Gerrit-Change-Id: I025c7e87e9a8308c084edc7a7da23c080a0dc970
Gerrit-Change-Number: 43948
Gerrit-PatchSet: 1
Gerrit-Owner: pespin <pespin@sysmocom.de>