pespin has uploaded this change for review. ( https://gerrit.osmocom.org/c/osmo-ttcn3-hacks/+/43948?usp=email )
Change subject: WIP: Introduce udr testsuite ......................................................................
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