From 40fd22e26a09ba37176bc95955634ff19c7cd4d9 Mon Sep 17 00:00:00 2001 From: Leonardo Bonati Date: Fri, 12 Sep 2025 08:24:25 -0400 Subject: [PATCH 01/52] Fix memory allocation issue in KPM RAN function sd parameter handling --- openair2/E2AP/RAN_FUNCTION/O-RAN/ran_func_kpm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openair2/E2AP/RAN_FUNCTION/O-RAN/ran_func_kpm.c b/openair2/E2AP/RAN_FUNCTION/O-RAN/ran_func_kpm.c index fd6578b3b2..aa68ba7d9d 100644 --- a/openair2/E2AP/RAN_FUNCTION/O-RAN/ran_func_kpm.c +++ b/openair2/E2AP/RAN_FUNCTION/O-RAN/ran_func_kpm.c @@ -170,7 +170,7 @@ static void capture_sst_sd(test_cond_value_t* test_cond_value, uint8_t *sst, uin DevAssert(test_cond_value->octet_string_value->len == 4); uint8_t *buf = test_cond_value->octet_string_value->buf; *sst = buf[0]; - *sd = malloc(**sd); + *sd = malloc(sizeof(uint32_t)); **sd = buf[1] << 16 | buf[2] << 8 | buf[3]; } printf("[E2 AGENT][E2SM-KPM] Condition NSSAI (%d, %x).\n", *sst, (*sd) ? **sd : 0xffffff); From 9f97320cbe9a26bff23fa24781715e74c0e74082 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Wed, 20 Aug 2025 19:07:06 +0200 Subject: [PATCH 02/52] HO Preparation: calloc pdcp_config in DRB to Modify Item decoding The malloced struct is initialized to garbage and when the IE is absent, the output "PDCP reestablishment" member (allocated in the stack) is set to a random value. --- openair2/E1AP/lib/e1ap_bearer_context_management.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openair2/E1AP/lib/e1ap_bearer_context_management.c b/openair2/E1AP/lib/e1ap_bearer_context_management.c index 2c4f938a24..f9afa06f69 100644 --- a/openair2/E1AP/lib/e1ap_bearer_context_management.c +++ b/openair2/E1AP/lib/e1ap_bearer_context_management.c @@ -1658,7 +1658,7 @@ static bool e1_decode_pdu_session_to_mod_item(pdu_session_to_mod_t *out, const E drb->id = drb2Mod->dRB_ID; // PDCP Config (O) if (drb2Mod->pDCP_Configuration) { - drb->pdcp_config = malloc_or_fail(sizeof(*drb->pdcp_config)); + drb->pdcp_config = calloc_or_fail(1, sizeof(*drb->pdcp_config)); CHECK_E1AP_DEC(e1_decode_pdcp_config(drb->pdcp_config, drb2Mod->pDCP_Configuration)); } // SDAP Config (O) From 956d30f4e139be730d4cab09d59f69cd82381dda Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Thu, 12 Jun 2025 22:46:08 +0200 Subject: [PATCH 03/52] HO preparation: fix PLMN type in nr_neighbour_cell_t During the review of the N2 HO MR it has been found that the wrong type plmn_identity_t (S1AP) was used in RRC. Now updated to the platform type plmn_id_t. --- openair2/RRC/NR/nr_rrc_defs.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openair2/RRC/NR/nr_rrc_defs.h b/openair2/RRC/NR/nr_rrc_defs.h index f1d9acba7b..f670dc4f86 100644 --- a/openair2/RRC/NR/nr_rrc_defs.h +++ b/openair2/RRC/NR/nr_rrc_defs.h @@ -315,7 +315,7 @@ typedef struct { int absoluteFrequencySSB; int subcarrierSpacing; int band; - plmn_identity_t plmn; + plmn_id_t plmn; uint32_t tac; bool isIntraFrequencyNeighbour; } nr_neighbour_cell_t; From 0a796ebb6cbd55d18dbc57f96860ad43706e67d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BAben=20Soares=20Silva?= Date: Thu, 11 Sep 2025 09:40:46 +0100 Subject: [PATCH 04/52] Fix ERROR.indication unpack function Fix an issue in which the unpacking is erroneously deemed unsuccessful even though it succeed in pulling all the bytes, this was due to not properly handling the return value of pull16 which is either 0 or 2, not 0 or 1. Check the results of push and pull operation with correct boolean operator. --- nfapi/open-nFAPI/fapi/src/nr_fapi_p5.c | 27 +++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/nfapi/open-nFAPI/fapi/src/nr_fapi_p5.c b/nfapi/open-nFAPI/fapi/src/nr_fapi_p5.c index a7cb729f6c..4bcc9b2a44 100644 --- a/nfapi/open-nFAPI/fapi/src/nr_fapi_p5.c +++ b/nfapi/open-nFAPI/fapi/src/nr_fapi_p5.c @@ -1866,22 +1866,23 @@ uint8_t unpack_nr_stop_indication(uint8_t **ppReadPackedMsg, uint8_t *end, void uint8_t pack_nr_error_indication(void *msg, uint8_t **ppWritePackedMsg, uint8_t *end, nfapi_p4_p5_codec_config_t *config) { nfapi_nr_error_indication_scf_t *pNfapiMsg = (nfapi_nr_error_indication_scf_t *)msg; - uint8_t retval = push16(pNfapiMsg->sfn, ppWritePackedMsg, end) != 0; - retval &= push16(pNfapiMsg->slot, ppWritePackedMsg, end) != 0; - retval &= push8(pNfapiMsg->message_id, ppWritePackedMsg, end); - retval &= push8(pNfapiMsg->error_code, ppWritePackedMsg, end); - - retval &= pack_vendor_extension_tlv(pNfapiMsg->vendor_extension, ppWritePackedMsg, end, config); - return retval; + if (push16(pNfapiMsg->sfn, ppWritePackedMsg, end) && push16(pNfapiMsg->slot, ppWritePackedMsg, end) + && push8(pNfapiMsg->message_id, ppWritePackedMsg, end) && push8(pNfapiMsg->error_code, ppWritePackedMsg, end) + && pack_vendor_extension_tlv(pNfapiMsg->vendor_extension, ppWritePackedMsg, end, config)) { + return 1; + } else { + return 0; + } } uint8_t unpack_nr_error_indication(uint8_t **ppReadPackedMsg, uint8_t *end, void *msg, nfapi_p4_p5_codec_config_t *config) { nfapi_nr_error_indication_scf_t *pNfapiMsg = (nfapi_nr_error_indication_scf_t *)msg; - uint8_t retval = pull16(ppReadPackedMsg, &pNfapiMsg->sfn, end); - retval &= pull16(ppReadPackedMsg, &pNfapiMsg->slot, end); - retval &= pull8(ppReadPackedMsg, &pNfapiMsg->message_id, end); - retval &= pull8(ppReadPackedMsg, &pNfapiMsg->error_code, end); - retval &= unpack_nr_tlv_list(NULL, 0, ppReadPackedMsg, end, config, &(pNfapiMsg->vendor_extension)); - return retval; + if (pull16(ppReadPackedMsg, &pNfapiMsg->sfn, end) && pull16(ppReadPackedMsg, &pNfapiMsg->slot, end) + && pull8(ppReadPackedMsg, &pNfapiMsg->message_id, end) && pull8(ppReadPackedMsg, &pNfapiMsg->error_code, end) + && unpack_nr_tlv_list(NULL, 0, ppReadPackedMsg, end, config, &(pNfapiMsg->vendor_extension))) { + return 1; + } else { + return 0; + } } From 6ff098e0bd56653759df060c81e812eb9fa341c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=BAben=20Soares=20Silva?= Date: Wed, 10 Sep 2025 16:20:12 +0100 Subject: [PATCH 05/52] Add Aerial-specific ERROR.indication codes This adds the expectable error codes from Aerial L1, as described in https://docs.nvidia.com/aerial/archive/aerial-sdk/23-3/text/cubb_release_notes/scf_fapi_support.html#additional-aerial-specific-error-codes-reported-in-error-indication-from-l1-to-l2 The errors are added in such a way that they're only visible when compiled with Aerial --- .../nfapi/public_inc/nfapi_nr_interface_scf.h | 43 ++++++++++++++----- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/nfapi/open-nFAPI/nfapi/public_inc/nfapi_nr_interface_scf.h b/nfapi/open-nFAPI/nfapi/public_inc/nfapi_nr_interface_scf.h index f186352b6d..fc45a620d2 100644 --- a/nfapi/open-nFAPI/nfapi/public_inc/nfapi_nr_interface_scf.h +++ b/nfapi/open-nFAPI/nfapi/public_inc/nfapi_nr_interface_scf.h @@ -707,18 +707,41 @@ typedef enum { NFAPI_NR_STOP_MSG_INVALID_STATE } nfapi_nr_stop_errors_e; -//3.3.5 PHY Notifications +// 3.3.5 PHY Notifications + +#define SCF_ERROR_LIST \ + X(NFAPI_NR_PHY_API_MSG_OK, 0x0) \ + X(NFAPI_NR_PHY_API_MSG_INVALID_STATE, 0x1) \ + X(NFAPI_NR_PHY_API_MSG_INVALID_CONFIG, 0x2) \ + X(NFAPI_NR_PHY_API_SFN_OUT_OF_SYNC, 0X3) \ + X(NFAPI_NR_PHY_API_MSG_SLOR_ERR, 0X4) \ + X(NFAPI_NR_PHY_API_MSG_BCH_MISSING, 0X5) \ + X(NFAPI_NR_PHY_API_MSG_INVALID_SFN, 0X6) \ + X(NFAPI_NR_PHY_API_MSG_UL_DCI_ERR, 0X7) \ + X(NFAPI_NR_PHY_API_MSG_TX_ERR, 0X8) + +#ifdef ENABLE_AERIAL +#define AERIAL_ERROR_LIST \ + X(AERIAL_ERROR_CODE_FAPI_END, 0x32) \ + X(AERIAL_ERROR_CODE_L1_PROC_OBJ_UNAVAILABLE_ERR, 0x33) \ + X(AERIAL_ERROR_CODE_MSG_LATE_SLOT_ERR, 0x34) \ + X(AERIAL_ERROR_CODE_PARTIAL_SRS_IND_ERR, 0x35) \ + X(AERIAL_ERROR_CODE_L1_DL_CPLANE_TX_ERROR, 0x36) \ + X(AERIAL_ERROR_CODE_L1_UL_CPLANE_TX_ERROR, 0x37) \ + X(AERIAL_ERROR_CODE_L1_DL_GPU_ERROR, 0x38) \ + X(AERIAL_ERROR_CODE_L1_DL_CPU_TASK_ERROR, 0x39) \ + X(AERIAL_ERROR_CODE_L1_UL_CPU_TASK_ERROR, 0x3A) \ + X(AERIAL_ERROR_CODE_L1_P1_EXIT_ERROR, 0x3B) \ + X(AERIAL_ERROR_CODE_L1_P2_EXIT_ERROR, 0x3C) \ + X(AERIAL_ERROR_CODE_L1_DL_CH_ERROR, 0x3D) \ + X(AERIAL_ERROR_CODE_L1_UL_CH_ERROR, 0x3E) +#else +#define AERIAL_ERROR_LIST +#endif #define NFAPI_PHY_ERROR_LIST \ -X(NFAPI_NR_PHY_API_MSG_OK ,0x0)\ -X(NFAPI_NR_PHY_API_MSG_INVALID_STATE ,0x1)\ -X(NFAPI_NR_PHY_API_MSG_INVALID_CONFIG ,0x2)\ -X(NFAPI_NR_PHY_API_SFN_OUT_OF_SYNC ,0X3)\ -X(NFAPI_NR_PHY_API_MSG_SLOR_ERR ,0X4)\ -X(NFAPI_NR_PHY_API_MSG_BCH_MISSING ,0X5)\ -X(NFAPI_NR_PHY_API_MSG_INVALID_SFN ,0X6)\ -X(NFAPI_NR_PHY_API_MSG_UL_DCI_ERR ,0X7)\ -X(NFAPI_NR_PHY_API_MSG_TX_ERR ,0X8) + SCF_ERROR_LIST \ + AERIAL_ERROR_LIST typedef enum { #define X(name, value) name = value, From 524aa84e966f98f6eb6ed915683a68b5d5a645b8 Mon Sep 17 00:00:00 2001 From: Sakthivel Velumani Date: Mon, 4 Aug 2025 19:26:02 -0400 Subject: [PATCH 06/52] fix: issue 954 - DCI monitoring after RRC setup After the UE is configured with pdcch-Config, it continues to monitor fallback search spaces in pdcch-ConfigCommon for DCI with C-RNTI. The rational is that after sending RRC Setup the network won't know if the UE has received and successfully applied it. Hence the network would still continue to use the fallback DCI till it receives RRC setup complete. --- .../NR_MAC_UE/nr_ue_dci_configuration.c | 41 +++++++++++++++---- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/openair2/LAYER2/NR_MAC_UE/nr_ue_dci_configuration.c b/openair2/LAYER2/NR_MAC_UE/nr_ue_dci_configuration.c index 7a2e9c6ed9..d45cfe8dc1 100644 --- a/openair2/LAYER2/NR_MAC_UE/nr_ue_dci_configuration.c +++ b/openair2/LAYER2/NR_MAC_UE/nr_ue_dci_configuration.c @@ -502,19 +502,42 @@ void ue_dci_configuration(NR_UE_MAC_INST_t *mac, fapi_nr_dl_config_request_t *dl config_dci_pdu(mac, dl_config, rnti_type, slot, ra_SS); } } else if (mac->state == UE_CONNECTED) { + /* + In RRC Connected state, the UE monitors for fallback DCI with C-RNTI in common and UE-specific SS. + First the UE check all UE specific SS. If no fallback DCI is configured, it configures one of following + common SS with the shown priority + 1. RA SS + 2. Other SI SS + 3. Paging SS + 4. SIB1 SS + + There is a possibility that gNB could configure multiple UE-specific SS for fallback DCI or/and common SS, + and in a given slot more than one SS become active. For the moment, we assume such configuration wouldn't happen + and hence don't handle it for the sake of implementation simplicity. + */ + bool configured_fallback_dci = false; for (int i = 0; i < pdcch_config->list_SS.count; i++) { NR_SearchSpace_t *ss = pdcch_config->list_SS.array[i]; - if (is_ss_monitor_occasion(frame, slot, slots_per_frame, ss)) + if (is_ss_monitor_occasion(frame, slot, slots_per_frame, ss)) { config_dci_pdu(mac, dl_config, TYPE_C_RNTI_, slot, ss); + configured_fallback_dci |= (ss->searchSpaceType->present == NR_SearchSpace__searchSpaceType_PR_common); + } } - const NR_SearchSpace_t *ra_SS = get_common_search_space(mac, pdcch_config->ra_SS_id); - if (pdcch_config->list_SS.count == 0 && ra_SS) { - // If the UE has not been provided a Type3-PDCCH CSS set or a USS set and - // the UE has received a C-RNTI and has been provided a Type1-PDCCH CSS set, - // the UE monitors PDCCH candidates for DCI format 0_0 and DCI format 1_0 - // with CRC scrambled by the C-RNTI in the Type1-PDCCH CSS set - if (is_ss_monitor_occasion(frame, slot, slots_per_frame, ra_SS)) - config_dci_pdu(mac, dl_config, TYPE_C_RNTI_, slot, ra_SS); + const NR_SearchSpace_t *css = NULL; + if (!configured_fallback_dci) { +#define CSS_VALID_CUR_SLOT(CSS) \ + ((CSS > -1) && is_ss_monitor_occasion(frame, slot, slots_per_frame, get_common_search_space(mac, CSS))) + if (CSS_VALID_CUR_SLOT(pdcch_config->ra_SS_id)) + css = get_common_search_space(mac, pdcch_config->ra_SS_id); + else if (CSS_VALID_CUR_SLOT(pdcch_config->otherSI_SS_id)) + css = get_common_search_space(mac, pdcch_config->otherSI_SS_id); + else if (CSS_VALID_CUR_SLOT(pdcch_config->paging_SS_id)) + css = get_common_search_space(mac, pdcch_config->paging_SS_id); +#undef CSS_VALID_CUR_SLOT + else + css = mac->search_space_zero; + AssertFatal(css, "Atleast one CSS should be present in connected state\n"); + config_dci_pdu(mac, dl_config, TYPE_C_RNTI_, slot, css); } } } From 93f5f6580640447599145b88b777babc04981731 Mon Sep 17 00:00:00 2001 From: francescomani Date: Thu, 11 Sep 2025 17:57:20 +0200 Subject: [PATCH 07/52] remove --sa command line option from sa_fhi_7.2_metanoia_2x2_gnb docker compose --- .../yaml_files/sa_fhi_7.2_metanoia_2x2_gnb/docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci-scripts/yaml_files/sa_fhi_7.2_metanoia_2x2_gnb/docker-compose.yml b/ci-scripts/yaml_files/sa_fhi_7.2_metanoia_2x2_gnb/docker-compose.yml index 06273747bf..f08859a516 100644 --- a/ci-scripts/yaml_files/sa_fhi_7.2_metanoia_2x2_gnb/docker-compose.yml +++ b/ci-scripts/yaml_files/sa_fhi_7.2_metanoia_2x2_gnb/docker-compose.yml @@ -9,7 +9,7 @@ services: container_name: oai-gnb environment: TZ: Europe/Paris - USE_ADDITIONAL_OPTIONS: --sa --thread-pool 7,8,9,10,11,12 + USE_ADDITIONAL_OPTIONS: --thread-pool 7,8,9,10,11,12 devices: - /dev/vfio:/dev/vfio/ volumes: From b8683dd351d0c87f42b73c1a2e39bb2d47ad9395 Mon Sep 17 00:00:00 2001 From: francescomani Date: Thu, 11 Sep 2025 18:47:25 +0200 Subject: [PATCH 08/52] check command line for unknown options at gNB --- executables/nr-softmodem.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/executables/nr-softmodem.c b/executables/nr-softmodem.c index d8df770cb8..0bd583662b 100644 --- a/executables/nr-softmodem.c +++ b/executables/nr-softmodem.c @@ -713,6 +713,11 @@ int main( int argc, char **argv ) { // wait for end of program printf("TYPE TO TERMINATE\n"); + // Sleep a while before checking all parameters have been used + // Some are used directly in external threads, asynchronously + sleep(2); + config_check_unknown_cmdlineopt(uniqCfg, CONFIG_CHECKALLSECTIONS); + itti_wait_tasks_end(NULL); printf("Returned from ITTI signal handler\n"); From fd791568ffc072927c84fa94611526b57dc15a4c Mon Sep 17 00:00:00 2001 From: francescomani Date: Thu, 11 Sep 2025 18:50:00 +0200 Subject: [PATCH 09/52] change RUNMODEM documentation to reflect --sa not being optional anymore --- doc/RUNMODEM.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/doc/RUNMODEM.md b/doc/RUNMODEM.md index ba2d48be28..6e46a361fe 100644 --- a/doc/RUNMODEM.md +++ b/doc/RUNMODEM.md @@ -85,10 +85,8 @@ The standalone mode is the default mode. Before tag `2024.w45`, the default mode was NSA. Thus, in the past, to run either the gNB or the UE in standalone mode, it was necessary to provide the -`--sa` flag in the command line. This is not necessary anymore; if in doubt, -you can provide `--sa` on the command line. The gNB will ignore this option if -not relevant; the UE might complain that it does not know this option, which -means that standalone is already active. +`--sa` flag in the command line. This is not the case anymore. If provided +the softmodem exits reporting that it does not know this option. The default (SA) mode does the following: - At the gNB: From e99cf956e75e5b56a09696e17ff6ed73130e4eeb Mon Sep 17 00:00:00 2001 From: francescomani Date: Tue, 16 Sep 2025 10:25:04 +0200 Subject: [PATCH 10/52] remove --sa option from newly created handover test --- ci-scripts/yaml_files/5g_sa_f1_b210_ho/docker-compose.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ci-scripts/yaml_files/5g_sa_f1_b210_ho/docker-compose.yml b/ci-scripts/yaml_files/5g_sa_f1_b210_ho/docker-compose.yml index aaf058a9f7..24a3055701 100644 --- a/ci-scripts/yaml_files/5g_sa_f1_b210_ho/docker-compose.yml +++ b/ci-scripts/yaml_files/5g_sa_f1_b210_ho/docker-compose.yml @@ -7,7 +7,7 @@ services: ulimits: core: -1 # for core dumps environment: - USE_ADDITIONAL_OPTIONS: --telnetsrv --telnetsrv.listenport 9090 --telnetsrv.shrmod ci --sa + USE_ADDITIONAL_OPTIONS: --telnetsrv --telnetsrv.listenport 9090 --telnetsrv.shrmod ci --log_config.global_log_options level,nocolor,utc_time,line_num,function volumes: - ../../conf_files/gnb-cu.sa.f1.ho.conf:/opt/oai-gnb/etc/gnb.conf @@ -37,7 +37,7 @@ services: core: -1 # for core dumps environment: USE_B2XX: 'yes' - USE_ADDITIONAL_OPTIONS: --RUs.[0].sdr_addrs serial=30AD30F --sa + USE_ADDITIONAL_OPTIONS: --RUs.[0].sdr_addrs serial=30AD30F --telnetsrv --telnetsrv.listenport 9091 --telnetsrv.shrmod ci --log_config.global_log_options level,nocolor,utc_time,line_num,function devices: @@ -69,7 +69,7 @@ services: core: -1 # for core dumps environment: USE_B2XX: 'yes' - USE_ADDITIONAL_OPTIONS: --RUs.[0].sdr_addrs serial=31F8010 --sa + USE_ADDITIONAL_OPTIONS: --RUs.[0].sdr_addrs serial=31F8010 --telnetsrv --telnetsrv.listenport 9091 --telnetsrv.shrmod ci --log_config.global_log_options level,nocolor,utc_time,line_num,function devices: From a76f7a8c62cc98a1b14059c73f6563a6877ffd0f Mon Sep 17 00:00:00 2001 From: francescomani Date: Tue, 16 Sep 2025 11:21:04 +0200 Subject: [PATCH 11/52] remove rfsim option from CU in F1 docker --- ci-scripts/yaml_files/5g_f1_rfsimulator/docker-compose.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/ci-scripts/yaml_files/5g_f1_rfsimulator/docker-compose.yaml b/ci-scripts/yaml_files/5g_f1_rfsimulator/docker-compose.yaml index 18313d0559..94ef3e7868 100644 --- a/ci-scripts/yaml_files/5g_f1_rfsimulator/docker-compose.yaml +++ b/ci-scripts/yaml_files/5g_f1_rfsimulator/docker-compose.yaml @@ -100,7 +100,6 @@ services: - SYS_NICE environment: USE_ADDITIONAL_OPTIONS: --log_config.global_log_options level,nocolor,time - --rfsimulator.options chanmod --gNBs.[0].remote_s_address 0.0.0.0 --telnetsrv --telnetsrv.listenaddr 192.168.71.150 --telnetsrv.shrmod ci From 8d6bb6bf2007ca795a1992ab27df6e606944ba32 Mon Sep 17 00:00:00 2001 From: rmagueta Date: Fri, 15 Nov 2024 18:25:07 +0000 Subject: [PATCH 12/52] Definition of L1 and implementation of L3 measurements for serving cell --- common/utils/nr/nr_common.h | 13 +++++ .../nfapi/public_inc/fapi_nr_ue_constants.h | 2 +- .../nfapi/public_inc/fapi_nr_ue_interface.h | 15 +++-- .../PHY/NR_UE_ESTIMATION/nr_ue_measurements.c | 19 ++++++ openair1/PHY/NR_UE_TRANSPORT/csi_rx.c | 28 +++++---- openair1/SCHED_NR_UE/phy_procedures_nr_ue.c | 4 +- .../SIMULATION/NR_PHY/nr_unitary_common.c | 9 +++ openair2/COMMON/mac_messages_def.h | 4 ++ openair2/COMMON/mac_messages_types.h | 10 ++++ openair2/LAYER2/NR_MAC_UE/mac_defs.h | 4 +- openair2/LAYER2/NR_MAC_UE/mac_proto.h | 6 +- openair2/LAYER2/NR_MAC_UE/main_ue_nr.c | 2 +- openair2/LAYER2/NR_MAC_UE/nr_ue_procedures.c | 58 +++++++++---------- openair2/NR_UE_PHY_INTERFACE/NR_IF_Module.c | 20 +++---- openair2/RRC/NR_UE/L2_interface_ue.c | 11 ++++ openair2/RRC/NR_UE/L2_interface_ue.h | 6 ++ openair2/RRC/NR_UE/rrc_UE.c | 53 +++++++++++++++++ openair2/RRC/NR_UE/rrc_defs.h | 10 ++++ 18 files changed, 206 insertions(+), 68 deletions(-) diff --git a/common/utils/nr/nr_common.h b/common/utils/nr/nr_common.h index 6ab3f58dab..67f0913dc4 100644 --- a/common/utils/nr/nr_common.h +++ b/common/utils/nr/nr_common.h @@ -88,6 +88,8 @@ static inline const char *rnti_types(nr_rnti_type_t rr) #define NR_NUMBER_OF_SYMBOLS_PER_SLOT_EXTENDED_CP 12 #define NR_MAX_NB_LAYERS 4 // 8 +#define BOUNDED_EVAL(a, b, c) (min(c, max(a, b))) + // Since the IQ samples are represented by SQ15 R+I (see https://en.wikipedia.org/wiki/Q_(number_format)) we need to compensate when // calcualting signal energy. Instead of shifting each sample right by 15, we can normalize the result in dB scale once its // calcualted. Signal energy is calculated using RMS^2, where each sample is squared before taking the average of the sum, therefore @@ -175,6 +177,17 @@ typedef struct { uint32_t step; } NR_timer_t; +typedef struct val_init { + int val; + bool init; +} val_init_t; + +typedef struct meas_s { + uint16_t Nid_cell; + val_init_t ss_rsrp_dBm; + val_init_t csi_rsrp_dBm; +} meas_t; + /** * @brief To start a timer * @param timer Timer to be started diff --git a/nfapi/open-nFAPI/nfapi/public_inc/fapi_nr_ue_constants.h b/nfapi/open-nFAPI/nfapi/public_inc/fapi_nr_ue_constants.h index 37b661afb9..57292ecee5 100644 --- a/nfapi/open-nFAPI/nfapi/public_inc/fapi_nr_ue_constants.h +++ b/nfapi/open-nFAPI/nfapi/public_inc/fapi_nr_ue_constants.h @@ -20,7 +20,7 @@ #define FAPI_NR_RX_PDU_TYPE_DLSCH 0x03 #define FAPI_NR_DCI_IND 0x04 #define FAPI_NR_RX_PDU_TYPE_RAR 0x05 -#define FAPI_NR_CSIRS_IND 0x06 +#define FAPI_NR_MEAS_IND 0x06 #define FAPI_NR_SIBS_MASK_SIB1 0x1 diff --git a/nfapi/open-nFAPI/nfapi/public_inc/fapi_nr_ue_interface.h b/nfapi/open-nFAPI/nfapi/public_inc/fapi_nr_ue_interface.h index 64c8dab07e..a9e83758f6 100644 --- a/nfapi/open-nFAPI/nfapi/public_inc/fapi_nr_ue_interface.h +++ b/nfapi/open-nFAPI/nfapi/public_inc/fapi_nr_ue_interface.h @@ -49,16 +49,23 @@ typedef enum { NFAPI_NR_FORMAT_0_1_AND_1_1, } nfapi_nr_dci_formats_e; +typedef enum { + NFAPI_NR_CSI_MEAS, + NFAPI_NR_SS_MEAS +} nfapi_nr_meas_type_e; typedef struct { - uint32_t rsrp; - int rsrp_dBm; + uint32_t gNB_index; + uint16_t Nid_cell; + nfapi_nr_meas_type_e meas_type; + bool is_neighboring_cell; + uint8_t rsrp_dBm; uint8_t rank_indicator; uint16_t i1; uint8_t i2; uint8_t cqi; rlm_t radiolink_monitoring; -} fapi_nr_csirs_measurements_t; +} fapi_nr_l1_measurements_t; typedef struct { /// frequency_domain_resource; @@ -145,7 +152,7 @@ typedef struct { fapi_nr_pdsch_pdu_t pdsch_pdu; fapi_nr_ssb_pdu_t ssb_pdu; fapi_nr_sib_pdu_t sib_pdu; - fapi_nr_csirs_measurements_t csirs_measurements; + fapi_nr_l1_measurements_t l1_measurements; }; } fapi_nr_rx_indication_body_t; diff --git a/openair1/PHY/NR_UE_ESTIMATION/nr_ue_measurements.c b/openair1/PHY/NR_UE_ESTIMATION/nr_ue_measurements.c index 3c9850bd52..825e025308 100644 --- a/openair1/PHY/NR_UE_ESTIMATION/nr_ue_measurements.c +++ b/openair1/PHY/NR_UE_ESTIMATION/nr_ue_measurements.c @@ -37,6 +37,7 @@ #include "PHY/phy_extern_nr_ue.h" #include "common/utils/LOG/log.h" #include "PHY/sse_intrin.h" +#include "SCHED_NR_UE/defs.h" #define K1 ((long long int) 512) #define K2 ((long long int) (1024-K1)) @@ -254,6 +255,24 @@ void nr_ue_ssb_rsrp_measurements(PHY_VARS_NR_UE *ue, ue->measurements.ssb_rsrp_dBm[ssb_index], rsrp_db_per_re, ue->measurements.ssb_sinr_dB[ssb_index]); + + // Send SS measurements to MAC + if (!ue->if_inst || !ue->if_inst->dl_indication) + return; + + fapi_nr_l1_measurements_t l1_measurements = { + .gNB_index = proc->gNB_id, + .meas_type = NFAPI_NR_SS_MEAS, + .Nid_cell = ue->frame_parms.Nid_cell, + .is_neighboring_cell = false, + }; + int ssb_rsrp_dBm = ue->measurements.ssb_rsrp_dBm[ssb_index]; + l1_measurements.rsrp_dBm = BOUNDED_EVAL(16, ssb_rsrp_dBm + 157, 113); // TS 38.133 - Table 10.1.6.1-1 + nr_downlink_indication_t dl_indication = {0}; + fapi_nr_rx_indication_t rx_ind = {0}; + nr_fill_dl_indication(&dl_indication, NULL, &rx_ind, proc, ue, NULL); + nr_fill_rx_indication(&rx_ind, FAPI_NR_MEAS_IND, ue, NULL, NULL, 1, proc, &l1_measurements, NULL); + ue->if_inst->dl_indication(&dl_indication); } // This function computes the received noise power diff --git a/openair1/PHY/NR_UE_TRANSPORT/csi_rx.c b/openair1/PHY/NR_UE_TRANSPORT/csi_rx.c index 295718e22c..4ab72a13f0 100644 --- a/openair1/PHY/NR_UE_TRANSPORT/csi_rx.c +++ b/openair1/PHY/NR_UE_TRANSPORT/csi_rx.c @@ -979,18 +979,24 @@ void nr_ue_csi_rs_procedures(PHY_VARS_NR_UE *ue, } // Send CSI measurements to MAC - fapi_nr_csirs_measurements_t csirs_measurements; - csirs_measurements.rsrp = rsrp; - csirs_measurements.rsrp_dBm = rsrp_dBm; - csirs_measurements.rank_indicator = rank_indicator; - csirs_measurements.i1 = *i1; - csirs_measurements.i2 = *i2; - csirs_measurements.cqi = cqi; - csirs_measurements.radiolink_monitoring = RLM_no_monitoring; // TODO do be activated in case of RLM based on CSI-RS + if (!ue->if_inst || !ue->if_inst->dl_indication) + return; + + fapi_nr_l1_measurements_t l1_measurements = { + .gNB_index = proc->gNB_id, + .meas_type = NFAPI_NR_CSI_MEAS, + .Nid_cell = frame_parms->Nid_cell, + .is_neighboring_cell = false, + .rsrp_dBm = BOUNDED_EVAL(16, rsrp_dBm + 157, 113), // TS 38.133 - Table 10.1.6.1-1 + .rank_indicator = rank_indicator, + .i1 = *i1, + .i2 = *i2, + .cqi = cqi, + .radiolink_monitoring = RLM_no_monitoring, // TODO do be activated in case of RLM based on CSI-RS + }; nr_downlink_indication_t dl_indication; fapi_nr_rx_indication_t rx_ind = {0}; nr_fill_dl_indication(&dl_indication, NULL, &rx_ind, proc, ue, NULL); - nr_fill_rx_indication(&rx_ind, FAPI_NR_CSIRS_IND, ue, NULL, NULL, 1, proc, (void *)&csirs_measurements, NULL); - if (ue->if_inst && ue->if_inst->dl_indication) - ue->if_inst->dl_indication(&dl_indication); + nr_fill_rx_indication(&rx_ind, FAPI_NR_MEAS_IND, ue, NULL, NULL, 1, proc, (void *)&l1_measurements, NULL); + ue->if_inst->dl_indication(&dl_indication); } diff --git a/openair1/SCHED_NR_UE/phy_procedures_nr_ue.c b/openair1/SCHED_NR_UE/phy_procedures_nr_ue.c index f683a2098d..25447e4bb4 100644 --- a/openair1/SCHED_NR_UE/phy_procedures_nr_ue.c +++ b/openair1/SCHED_NR_UE/phy_procedures_nr_ue.c @@ -186,8 +186,8 @@ void nr_fill_rx_indication(fapi_nr_rx_indication_t *rx_ind, rx->ssb_pdu.decoded_pdu = false; } } break; - case FAPI_NR_CSIRS_IND: - memcpy(&rx->csirs_measurements, typeSpecific, sizeof(fapi_nr_csirs_measurements_t)); + case FAPI_NR_MEAS_IND: + memcpy(&rx->l1_measurements, typeSpecific, sizeof(fapi_nr_l1_measurements_t)); break; default: break; diff --git a/openair1/SIMULATION/NR_PHY/nr_unitary_common.c b/openair1/SIMULATION/NR_PHY/nr_unitary_common.c index 7aca911b7f..585c5388e4 100644 --- a/openair1/SIMULATION/NR_PHY/nr_unitary_common.c +++ b/openair1/SIMULATION/NR_PHY/nr_unitary_common.c @@ -178,3 +178,12 @@ void *rrc_nrue(void *notUsed) { return NULL; } + +void nr_mac_rrc_meas_ind_ue(module_id_t module_id, + uint32_t gNB_index, + uint16_t Nid_cell, + bool csi_meas, + bool is_neighboring_cell, + uint8_t rsrp_dBm) +{ +} diff --git a/openair2/COMMON/mac_messages_def.h b/openair2/COMMON/mac_messages_def.h index f342444c65..11e8ab6f5e 100644 --- a/openair2/COMMON/mac_messages_def.h +++ b/openair2/COMMON/mac_messages_def.h @@ -62,3 +62,7 @@ MESSAGE_DEF(NR_RRC_MAC_SBCCH_DATA_IND, MESSAGE_PRIORITY_MED_PLUS, NRRrcMacSBc // Message to GNB_APP to update SIB19 satellite position information MESSAGE_DEF(GNB_SAT_POSITION_UPDATE, MESSAGE_PRIORITY_MED, gnb_sat_position_update_t, gnb_sat_position_update) + +// nrUE +MESSAGE_DEF(NR_RRC_MAC_MEAS_DATA_IND, MESSAGE_PRIORITY_MED_PLUS, NRRrcMacMeasDataInd, nr_rrc_mac_meas_data_ind) + diff --git a/openair2/COMMON/mac_messages_types.h b/openair2/COMMON/mac_messages_types.h index f5712b115a..677e1e90b1 100644 --- a/openair2/COMMON/mac_messages_types.h +++ b/openair2/COMMON/mac_messages_types.h @@ -44,6 +44,8 @@ #define NR_RRC_MAC_BCCH_DATA_IND(mSGpTR) (mSGpTR)->ittiMsg.nr_rrc_mac_bcch_data_ind #define NR_RRC_MAC_SBCCH_DATA_IND(mSGpTR) (mSGpTR)->ittiMsg.nr_rrc_mac_sbcch_data_ind +#define NR_RRC_MAC_MEAS_DATA_IND(mSGpTR) (mSGpTR)->ittiMsg.nr_rrc_mac_meas_data_ind + #define RRC_MAC_BCCH_MBMS_DATA_REQ(mSGpTR) (mSGpTR)->ittiMsg.rrc_mac_bcch_mbms_data_req #define RRC_MAC_BCCH_MBMS_DATA_IND(mSGpTR) (mSGpTR)->ittiMsg.rrc_mac_bcch_mbms_data_ind @@ -142,6 +144,14 @@ typedef struct NRRrcMacSBcchDataInd_s { uint8_t rsrp; } NRRrcMacSBcchDataInd; +typedef struct NRRrcMacMeasDataInd_s { + uint8_t gnb_index; + uint8_t rsrp_dBm; + uint16_t Nid_cell; + bool is_csi_meas; + bool is_neighboring_cell; +} NRRrcMacMeasDataInd; + typedef struct RrcMacBcchMbmsDataReq_s { uint32_t frame; uint32_t sdu_size; diff --git a/openair2/LAYER2/NR_MAC_UE/mac_defs.h b/openair2/LAYER2/NR_MAC_UE/mac_defs.h index d734870f52..680b5138df 100644 --- a/openair2/LAYER2/NR_MAC_UE/mac_defs.h +++ b/openair2/LAYER2/NR_MAC_UE/mac_defs.h @@ -594,8 +594,8 @@ typedef struct NR_UE_MAC_INST_s { nr_csi_report_t csi_report_template[MAX_CSI_REPORTCONFIG]; - /// measurements from CSI-RS - fapi_nr_csirs_measurements_t csirs_measurements; + /// measurements from SS or CSI-RS + fapi_nr_l1_measurements_t l1_measurements; //// FAPI-like interface message fapi_nr_ul_config_request_t *ul_config_request; diff --git a/openair2/LAYER2/NR_MAC_UE/mac_proto.h b/openair2/LAYER2/NR_MAC_UE/mac_proto.h index 6d4f025a30..035e59c09c 100644 --- a/openair2/LAYER2/NR_MAC_UE/mac_proto.h +++ b/openair2/LAYER2/NR_MAC_UE/mac_proto.h @@ -117,10 +117,8 @@ csi_payload_t nr_ue_aperiodic_csi_reporting(NR_UE_MAC_INST_t *mac, dci_field_t c nr_dci_format_t nr_ue_process_dci_indication_pdu(NR_UE_MAC_INST_t *mac, frame_t frame, int slot, fapi_nr_dci_indication_pdu_t *dci); -int8_t nr_ue_process_csirs_measurements(NR_UE_MAC_INST_t *mac, - frame_t frame, - int slot, - fapi_nr_csirs_measurements_t *csirs_measurements); +void nr_ue_process_l1_measurements(NR_UE_MAC_INST_t *mac, frame_t frame, int slot, fapi_nr_l1_measurements_t *l1_measurements); + void nr_ue_aperiodic_srs_scheduling(NR_UE_MAC_INST_t *mac, long resource_trigger, int frame, int slot); bool trigger_periodic_scheduling_request(NR_UE_MAC_INST_t *mac, diff --git a/openair2/LAYER2/NR_MAC_UE/main_ue_nr.c b/openair2/LAYER2/NR_MAC_UE/main_ue_nr.c index 3348c6b278..78bfb1021c 100644 --- a/openair2/LAYER2/NR_MAC_UE/main_ue_nr.c +++ b/openair2/LAYER2/NR_MAC_UE/main_ue_nr.c @@ -302,7 +302,7 @@ void release_mac_configuration(NR_UE_MAC_INST_t *mac, NR_UE_MAC_reset_cause_t ca release_ul_BWP(mac, i); memset(&mac->ssb_measurements, 0, sizeof(mac->ssb_measurements)); - memset(&mac->csirs_measurements, 0, sizeof(mac->csirs_measurements)); + memset(&mac->l1_measurements, 0, sizeof(mac->l1_measurements)); memset(&mac->ul_time_alignment, 0, sizeof(mac->ul_time_alignment)); for (int i = mac->TAG_list.count; i > 0 ; i--) asn_sequence_del(&mac->TAG_list, i - 1, 1); diff --git a/openair2/LAYER2/NR_MAC_UE/nr_ue_procedures.c b/openair2/LAYER2/NR_MAC_UE/nr_ue_procedures.c index a4362a346e..ab13c9dd07 100644 --- a/openair2/LAYER2/NR_MAC_UE/nr_ue_procedures.c +++ b/openair2/LAYER2/NR_MAC_UE/nr_ue_procedures.c @@ -1504,13 +1504,17 @@ nr_dci_format_t nr_ue_process_dci_indication_pdu(NR_UE_MAC_INST_t *mac, frame_t return format; } -int8_t nr_ue_process_csirs_measurements(NR_UE_MAC_INST_t *mac, - frame_t frame, - int slot, - fapi_nr_csirs_measurements_t *csirs_measurements) { - LOG_D(NR_MAC,"(%d.%d) Received CSI-RS measurements\n", frame, slot); - memcpy(&mac->csirs_measurements, csirs_measurements, sizeof(*csirs_measurements)); - return 0; +void nr_ue_process_l1_measurements(NR_UE_MAC_INST_t *mac, frame_t frame, int slot, fapi_nr_l1_measurements_t *l1_measurements) +{ + LOG_D(NR_MAC, "(%d.%d) Received CSI-RS measurements\n", frame, slot); + memcpy(&mac->l1_measurements, l1_measurements, sizeof(*l1_measurements)); + bool csi_meas = l1_measurements->meas_type == NFAPI_NR_CSI_MEAS; + nr_mac_rrc_meas_ind_ue(mac->ue_id, + l1_measurements->gNB_index, + l1_measurements->Nid_cell, + csi_meas, + l1_measurements->is_neighboring_cell, + l1_measurements->rsrp_dBm); } static void set_harq_status(NR_UE_MAC_INST_t *mac, @@ -2993,9 +2997,9 @@ static csi_payload_t get_csirs_RI_PMI_CQI_payload(NR_UE_MAC_INST_t *mac, AssertFatal(csi_report, "Couldn't find CSI report with ID %ld\n", csi_reportconfig->reportConfigId); int cri_bitlen = csi_report->csi_meas_bitlen.cri_bitlen; int ri_bitlen = csi_report->csi_meas_bitlen.ri_bitlen; - int pmi_x1_bitlen = csi_report->csi_meas_bitlen.pmi_x1_bitlen[mac->csirs_measurements.rank_indicator]; - int pmi_x2_bitlen = csi_report->csi_meas_bitlen.pmi_x2_bitlen[mac->csirs_measurements.rank_indicator]; - int cqi_bitlen = csi_report->csi_meas_bitlen.cqi_bitlen[mac->csirs_measurements.rank_indicator]; + int pmi_x1_bitlen = csi_report->csi_meas_bitlen.pmi_x1_bitlen[mac->l1_measurements.rank_indicator]; + int pmi_x2_bitlen = csi_report->csi_meas_bitlen.pmi_x2_bitlen[mac->l1_measurements.rank_indicator]; + int cqi_bitlen = csi_report->csi_meas_bitlen.cqi_bitlen[mac->l1_measurements.rank_indicator]; if (get_softmodem_params()->emulate_l1) { static const uint8_t mcs_to_cqi[] = {0, 1, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, @@ -3003,9 +3007,9 @@ static csi_payload_t get_csirs_RI_PMI_CQI_payload(NR_UE_MAC_INST_t *mac, CHECK_INDEX(nr_bler_data, NR_NUM_MCS - 1); int mcs = get_mcs_from_sinr(nr_bler_data, (mac->nr_ue_emul_l1.cqi - 640) * 0.1); CHECK_INDEX(mcs_to_cqi, mcs); - mac->csirs_measurements.rank_indicator = mac->nr_ue_emul_l1.ri; - mac->csirs_measurements.i1 = mac->nr_ue_emul_l1.pmi; - mac->csirs_measurements.cqi = mcs_to_cqi[mcs]; + mac->l1_measurements.rank_indicator = mac->nr_ue_emul_l1.ri; + mac->l1_measurements.i1 = mac->nr_ue_emul_l1.pmi; + mac->l1_measurements.cqi = mcs_to_cqi[mcs]; } int padding_bitlen = 0; @@ -3014,19 +3018,19 @@ static csi_payload_t get_csirs_RI_PMI_CQI_payload(NR_UE_MAC_INST_t *mac, p1_bits = cri_bitlen + ri_bitlen + cqi_bitlen; p2_bits = pmi_x1_bitlen + pmi_x2_bitlen; temp_payload_1 = (0/*mac->csi_measurements.cri*/ << (cqi_bitlen + ri_bitlen)) | - (mac->csirs_measurements.rank_indicator << cqi_bitlen) | - (mac->csirs_measurements.cqi); - temp_payload_2 = (mac->csirs_measurements.i1 << pmi_x2_bitlen) | - mac->csirs_measurements.i2; + (mac->l1_measurements.rank_indicator << cqi_bitlen) | + (mac->l1_measurements.cqi); + temp_payload_2 = (mac->l1_measurements.i1 << pmi_x2_bitlen) | + mac->l1_measurements.i2; } else { p1_bits = nr_get_csi_bitlen(csi_report); padding_bitlen = p1_bits - (cri_bitlen + ri_bitlen + pmi_x1_bitlen + pmi_x2_bitlen + cqi_bitlen); temp_payload_1 = (0/*mac->csi_measurements.cri*/ << (cqi_bitlen + pmi_x2_bitlen + pmi_x1_bitlen + padding_bitlen + ri_bitlen)) | - (mac->csirs_measurements.rank_indicator << (cqi_bitlen + pmi_x2_bitlen + pmi_x1_bitlen + padding_bitlen)) | - (mac->csirs_measurements.i1 << (cqi_bitlen + pmi_x2_bitlen)) | - (mac->csirs_measurements.i2 << (cqi_bitlen)) | - (mac->csirs_measurements.cqi); + (mac->l1_measurements.rank_indicator << (cqi_bitlen + pmi_x2_bitlen + pmi_x1_bitlen + padding_bitlen)) | + (mac->l1_measurements.i1 << (cqi_bitlen + pmi_x2_bitlen)) | + (mac->l1_measurements.i2 << (cqi_bitlen)) | + (mac->l1_measurements.cqi); } temp_payload_1 = reverse_bits(temp_payload_1, p1_bits); @@ -3085,17 +3089,7 @@ static csi_payload_t get_csirs_RSRP_payload(NR_UE_MAC_INST_t *mac, } // TODO: Improvements will be needed to cri_ssbri_bitlen>0 - // TS 38.133 - Table 10.1.6.1-1 - int rsrp_dBm = mac->csirs_measurements.rsrp_dBm; - if (rsrp_dBm < -140) { - temp_payload = 16; - } else if (rsrp_dBm > -44) { - temp_payload = 113; - } else { - temp_payload = mac->csirs_measurements.rsrp_dBm + 157; - } - - temp_payload = reverse_bits(temp_payload, n_bits); + temp_payload = reverse_bits(mac->l1_measurements.rsrp_dBm, n_bits); // rsrp_dBm as in TS 38.133 - Table 10.1.6.1-1 LOG_D(NR_MAC, "cri_ssbri_bitlen = %d\n", cri_ssbri_bitlen); LOG_D(NR_MAC, "rsrp_bitlen = %d\n", rsrp_bitlen); diff --git a/openair2/NR_UE_PHY_INTERFACE/NR_IF_Module.c b/openair2/NR_UE_PHY_INTERFACE/NR_IF_Module.c index 21818c68c3..3f765bf6c9 100644 --- a/openair2/NR_UE_PHY_INTERFACE/NR_IF_Module.c +++ b/openair2/NR_UE_PHY_INTERFACE/NR_IF_Module.c @@ -1174,13 +1174,11 @@ static void handle_rlm(rlm_t rlm_result, int frame, NR_UE_MAC_INST_t *mac) nr_mac_rrc_sync_ind(mac->ue_id, frame, is_sync); } -static int8_t handle_csirs_measurements(NR_UE_MAC_INST_t *mac, - frame_t frame, - int slot, - fapi_nr_csirs_measurements_t *csirs_measurements) +static int8_t handle_l1_measurements(NR_UE_MAC_INST_t *mac, frame_t frame, int slot, fapi_nr_l1_measurements_t *l1_measurements) { - handle_rlm(csirs_measurements->radiolink_monitoring, frame, mac); - return nr_ue_process_csirs_measurements(mac, frame, slot, csirs_measurements); + handle_rlm(l1_measurements->radiolink_monitoring, frame, mac); + nr_ue_process_l1_measurements(mac, frame, slot, l1_measurements); + return 0; } void update_harq_status(NR_UE_MAC_INST_t *mac, uint8_t harq_pid, uint8_t ack_nack) @@ -1316,11 +1314,11 @@ static uint32_t nr_ue_dl_processing(NR_UE_MAC_INST_t *mac, nr_downlink_indicatio } ret_mask |= (handle_dlsch(mac, dl_info, i)) << FAPI_NR_RX_PDU_TYPE_RAR; break; - case FAPI_NR_CSIRS_IND: - ret_mask |= (handle_csirs_measurements(mac, - dl_info->frame, - dl_info->slot, - &rx_indication_body.csirs_measurements)) << FAPI_NR_CSIRS_IND; + case FAPI_NR_MEAS_IND: + ret_mask |= (handle_l1_measurements(mac, + dl_info->frame, + dl_info->slot, + &rx_indication_body.l1_measurements)) << FAPI_NR_MEAS_IND; break; default: break; diff --git a/openair2/RRC/NR_UE/L2_interface_ue.c b/openair2/RRC/NR_UE/L2_interface_ue.c index b751f31670..d6bee2ab10 100644 --- a/openair2/RRC/NR_UE/L2_interface_ue.c +++ b/openair2/RRC/NR_UE/L2_interface_ue.c @@ -37,6 +37,17 @@ typedef uint32_t channel_t; +void nr_mac_rrc_meas_ind_ue(module_id_t module_id, uint32_t gNB_index, uint16_t Nid_cell, bool csi_meas, bool is_neighboring_cell, uint8_t rsrp_dBm) +{ + MessageDef *message_p = itti_alloc_new_message(TASK_MAC_UE, 0, NR_RRC_MAC_MEAS_DATA_IND); + NR_RRC_MAC_MEAS_DATA_IND(message_p).gnb_index = gNB_index; + NR_RRC_MAC_MEAS_DATA_IND(message_p).Nid_cell = Nid_cell; + NR_RRC_MAC_MEAS_DATA_IND(message_p).is_csi_meas = csi_meas; + NR_RRC_MAC_MEAS_DATA_IND(message_p).is_neighboring_cell = is_neighboring_cell; + NR_RRC_MAC_MEAS_DATA_IND(message_p).rsrp_dBm = rsrp_dBm; + itti_send_msg_to_task(TASK_RRC_NRUE, GNB_MODULE_ID_TO_INSTANCE(module_id), message_p); +} + void nr_mac_rrc_sync_ind(const module_id_t module_id, const frame_t frame, const bool in_sync) { MessageDef *message_p = itti_alloc_new_message(TASK_MAC_UE, 0, NR_RRC_MAC_SYNC_IND); diff --git a/openair2/RRC/NR_UE/L2_interface_ue.h b/openair2/RRC/NR_UE/L2_interface_ue.h index daaed50e65..857c110d9e 100644 --- a/openair2/RRC/NR_UE/L2_interface_ue.h +++ b/openair2/RRC/NR_UE/L2_interface_ue.h @@ -37,6 +37,12 @@ void nr_mac_rrc_data_ind_ue(const module_id_t module_id, const channel_t channel, const uint8_t* pduP, const sdu_size_t pdu_len); +void nr_mac_rrc_meas_ind_ue(module_id_t module_id, + uint32_t gNB_index, + uint16_t Nid_cell, + bool csi_meas, + bool is_neighboring_cell, + uint8_t rsrp_dBm); void nr_mac_rrc_inactivity_timer_ind(const module_id_t mod_id); void nr_mac_rrc_msg3_ind(const module_id_t mod_id, const int rnti, bool prepare_payload); void nr_ue_rrc_timer_trigger(int instance, int frame, int gnb_id); diff --git a/openair2/RRC/NR_UE/rrc_UE.c b/openair2/RRC/NR_UE/rrc_UE.c index 52d9ecb50a..8683269054 100644 --- a/openair2/RRC/NR_UE/rrc_UE.c +++ b/openair2/RRC/NR_UE/rrc_UE.c @@ -807,6 +807,9 @@ NR_UE_RRC_INST_t* nr_rrc_init_ue(char* uecap_file, int instance_id, int num_ant_ for (int i = 0; i < NB_CNX_UE; i++) { rrcPerNB_t *ptr = &rrc->perNB[i]; ptr->SInfo = (NR_UE_RRC_SI_INFO){0}; + ptr->l3_measurements = (l3_measurements_t){0}; + ptr->l3_measurements.ssb_filter_coeff_rsrp = 1.0f; + ptr->l3_measurements.csi_RS_filter_coeff_rsrp = 1.0f; init_SI_timers(&ptr->SInfo); } @@ -1814,6 +1817,14 @@ static void handle_quantityconfig(rrcPerNB_t *rrc, NR_QuantityConfig_t *quantity if (!rrc->QuantityConfig[i]) rrc->QuantityConfig[i] = calloc(1, sizeof(*rrc->QuantityConfig[i])); rrc->QuantityConfig[i]->quantityConfigCell = quantityNR->quantityConfigCell; + // TODO: It remains to compute ssb_filter_coeff_rsrp and csi_RS_filter_coeff_rsrp for multiple quantityConfig + // TS 38.331 - 5.5.3.2 Layer 3 filtering + NR_QuantityConfigRS_t *qcc = &quantityNR->quantityConfigCell; + l3_measurements_t *l3_measurements = &rrc->l3_measurements; + if (qcc->ssb_FilterConfig.filterCoefficientRSRP) + l3_measurements->ssb_filter_coeff_rsrp = 1. / pow(2, (*qcc->ssb_FilterConfig.filterCoefficientRSRP) / 4); + if (qcc->csi_RS_FilterConfig.filterCoefficientRSRP) + l3_measurements->csi_RS_filter_coeff_rsrp = 1. / pow(2, (*qcc->csi_RS_FilterConfig.filterCoefficientRSRP) / 4); if (quantityNR->quantityConfigRS_Index) UPDATE_IE(rrc->QuantityConfig[i]->quantityConfigRS_Index, quantityNR->quantityConfigRS_Index, struct NR_QuantityConfigRS); } @@ -2238,6 +2249,34 @@ static int nr_rrc_ue_decode_dcch(NR_UE_RRC_INST_t *rrc, return 0; } +static void apply_ema(val_init_t *vi_rsrp_dBm, float filter_coeff_rsrp, int rsrp_dBm) +{ + int *quant = &vi_rsrp_dBm->val; + bool *meas_init = &vi_rsrp_dBm->init; + float coef = *meas_init ? filter_coeff_rsrp : 1.0f; // if not init, first measurement gets full weight + *quant = (1.0f - coef) * (*quant) + coef * rsrp_dBm; + *meas_init = true; +} + +void nr_ue_meas_filtering(rrcPerNB_t *rrc, bool is_neighboring_cell, uint16_t Nid_cell, bool csi_meas, int rsrp_dBm) +{ + if (is_neighboring_cell) + return; + + l3_measurements_t *l3_measurements = &rrc->l3_measurements; + meas_t *meas_cell = &l3_measurements->serving_cell; + if (meas_cell->Nid_cell != Nid_cell) { + meas_cell->ss_rsrp_dBm.init = false; + meas_cell->csi_rsrp_dBm.init = false; + } + meas_cell->Nid_cell = Nid_cell; + + if (csi_meas) + apply_ema(&meas_cell->csi_rsrp_dBm, l3_measurements->csi_RS_filter_coeff_rsrp, rsrp_dBm); + else + apply_ema(&meas_cell->ss_rsrp_dBm, l3_measurements->ssb_filter_coeff_rsrp, rsrp_dBm); +} + void nr_rrc_handle_ra_indication(NR_UE_RRC_INST_t *rrc, bool ra_succeeded) { NR_UE_Timers_Constants_t *timers = &rrc->timers_and_constants; @@ -2354,6 +2393,20 @@ void *rrc_nrue(void *notUsed) nr_rrc_ue_decode_NR_SBCCH_SL_BCH_Message(rrc, sbcch->gnb_index,sbcch->frame, sbcch->slot, sbcch->sdu, sbcch->sdu_size, sbcch->rx_slss_id); + case NR_RRC_MAC_MEAS_DATA_IND: + + LOG_D(NR_RRC, "[%s][Nid_cell %i] Received %s measurements: RSRP = %i (dBm)\n", + NR_RRC_MAC_MEAS_DATA_IND(msg_p).is_neighboring_cell? "Neighboring cell" : "Active cell", + NR_RRC_MAC_MEAS_DATA_IND(msg_p).Nid_cell, + NR_RRC_MAC_MEAS_DATA_IND(msg_p).is_csi_meas ? "CSI meas" : "SSB meas", + NR_RRC_MAC_MEAS_DATA_IND(msg_p).rsrp_dBm - 157); + + rrcPerNB_t *rrcNB = rrc->perNB + NR_RRC_MAC_MEAS_DATA_IND(msg_p).gnb_index; + nr_ue_meas_filtering(rrcNB, + NR_RRC_MAC_MEAS_DATA_IND(msg_p).is_neighboring_cell, + NR_RRC_MAC_MEAS_DATA_IND(msg_p).Nid_cell, + NR_RRC_MAC_MEAS_DATA_IND(msg_p).is_csi_meas, + NR_RRC_MAC_MEAS_DATA_IND(msg_p).rsrp_dBm - 157); break; case NR_RRC_MAC_CCCH_DATA_IND: { diff --git a/openair2/RRC/NR_UE/rrc_defs.h b/openair2/RRC/NR_UE/rrc_defs.h index bc645f7cc1..31e7305b96 100644 --- a/openair2/RRC/NR_UE/rrc_defs.h +++ b/openair2/RRC/NR_UE/rrc_defs.h @@ -192,6 +192,15 @@ typedef enum { typedef enum { RB_NOT_PRESENT, RB_ESTABLISHED, RB_SUSPENDED } NR_RB_status_t; +typedef struct l3_measurements_s { + float ssb_filter_coeff_rsrp; + float csi_RS_filter_coeff_rsrp; + meas_t serving_cell; + long trigger_to_measid; + long trigger_quantity; + long rs_type; +} l3_measurements_t; + typedef struct rrcPerNB { NR_MeasObjectToAddMod_t *MeasObj[MAX_MEAS_OBJ]; NR_ReportConfigToAddMod_t *ReportConfig[MAX_MEAS_CONFIG]; @@ -201,6 +210,7 @@ typedef struct rrcPerNB { NR_MeasGapConfig_t *measGapConfig; NR_UE_RRC_SI_INFO SInfo; NR_RSRP_Range_t s_measure; + l3_measurements_t l3_measurements; } rrcPerNB_t; typedef struct NR_UE_RRC_INST_s { From 3e65d75f833e7abebae37a093162405678fb7bd7 Mon Sep 17 00:00:00 2001 From: rmagueta Date: Mon, 18 Nov 2024 17:29:20 +0000 Subject: [PATCH 13/52] UE sends the measurement report for event A2 --- openair2/RRC/NR/MESSAGES/asn1_msg.c | 57 ++++++++++++ openair2/RRC/NR/MESSAGES/asn1_msg.h | 11 +++ openair2/RRC/NR_UE/rrc_UE.c | 93 +++++++++++++++++++ openair2/RRC/NR_UE/rrc_defs.h | 1 + openair2/RRC/NR_UE/rrc_proto.h | 3 + openair2/RRC/NR_UE/rrc_timers_and_constants.c | 55 +++++++++++ 6 files changed, 220 insertions(+) diff --git a/openair2/RRC/NR/MESSAGES/asn1_msg.c b/openair2/RRC/NR/MESSAGES/asn1_msg.c index 3ff6aecea9..21ccb1e40f 100644 --- a/openair2/RRC/NR/MESSAGES/asn1_msg.c +++ b/openair2/RRC/NR/MESSAGES/asn1_msg.c @@ -785,6 +785,63 @@ int do_RRCSetupComplete(uint8_t *buffer, return((enc_rval.encoded+7)/8); } +// TODO: This function is only implemented for event A2 +int do_nrMeasurementReport_SA(NR_MeasurementReport_t *measurementReport, + long trigger_to_measid, + long trigger_quantity, + long rs_type, + uint16_t Nid_cell, + int rsrp_dBm, + uint8_t *buffer, + size_t buffer_size) +{ + asn_enc_rval_t enc_rval; + NR_UL_DCCH_Message_t ul_dcch_msg; + memset((void *)&ul_dcch_msg, 0, sizeof(NR_UL_DCCH_Message_t)); + + ul_dcch_msg.message.present = NR_UL_DCCH_MessageType_PR_c1; + ul_dcch_msg.message.choice.c1 = CALLOC(1, sizeof(struct NR_UL_DCCH_MessageType__c1)); + ul_dcch_msg.message.choice.c1->present = NR_UL_DCCH_MessageType__c1_PR_measurementReport; + + memset(measurementReport, 0, sizeof(struct NR_MeasurementReport)); + ul_dcch_msg.message.choice.c1->choice.measurementReport = measurementReport; + measurementReport->criticalExtensions.present = NR_MeasurementReport__criticalExtensions_PR_measurementReport; + + NR_MeasurementReport_IEs_t *measurementReport_ie = CALLOC(1, sizeof(struct NR_MeasurementReport_IEs)); + measurementReport->criticalExtensions.choice.measurementReport = measurementReport_ie; + measurementReport_ie->measResults.measId = trigger_to_measid; + + NR_MeasResultServMO_t *measResultServMo = CALLOC(1, sizeof(struct NR_MeasResultServMO)); + + NR_MeasResultNR_t *measResultServingCell = &measResultServMo->measResultServingCell; + measResultServingCell->physCellId = CALLOC(1, sizeof(NR_PhysCellId_t)); + *measResultServingCell->physCellId = Nid_cell; + + struct NR_MeasQuantityResults *active_mq_res = CALLOC(1, sizeof(struct NR_MeasQuantityResults)); + + if (trigger_quantity == NR_MeasTriggerQuantityOffset_PR_rsrp) { + active_mq_res->rsrp = CALLOC(1, sizeof(NR_RSRP_Range_t)); + + *active_mq_res->rsrp = rsrp_dBm + 157; + if (rs_type == NR_NR_RS_Type_ssb) + measResultServingCell->measResult.cellResults.resultsSSB_Cell = active_mq_res; + else + measResultServingCell->measResult.cellResults.resultsCSI_RS_Cell = active_mq_res; + } + + ASN_SEQUENCE_ADD(&measurementReport_ie->measResults.measResultServingMOList.list, measResultServMo); + enc_rval = uper_encode_to_buffer(&asn_DEF_NR_UL_DCCH_Message, NULL, (void *)&ul_dcch_msg, buffer, buffer_size); + AssertFatal(enc_rval.encoded > 0, "ASN1 message encoding failed (%s, %lu)!\n", enc_rval.failed_type->name, enc_rval.encoded); + + if (LOG_DEBUGFLAG(DEBUG_ASN1)) { + xer_fprint(stdout, &asn_DEF_NR_UL_DCCH_Message, (void *)&ul_dcch_msg); + } + + LOG_I(NR_RRC, "MeasurementReport Encoded %zd bits (%zd bytes)\n", enc_rval.encoded, (enc_rval.encoded + 7) / 8); + + return ((enc_rval.encoded + 7) / 8); +} + int do_NR_DLInformationTransfer(uint8_t *buffer, size_t buffer_len, uint8_t transaction_id, diff --git a/openair2/RRC/NR/MESSAGES/asn1_msg.h b/openair2/RRC/NR/MESSAGES/asn1_msg.h index dde62d142f..4f13ca2fad 100644 --- a/openair2/RRC/NR/MESSAGES/asn1_msg.h +++ b/openair2/RRC/NR/MESSAGES/asn1_msg.h @@ -50,11 +50,13 @@ #include "NR_ReestablishmentCause.h" #include "NR_SRB-ToAddModList.h" #include "NR_SecurityConfig.h" +#include "NR_MeasurementReport.h" #include "NR_MeasurementTimingConfiguration.h" #include "ds/seq_arr.h" #include "ds/byte_array.h" #include "rrc_messages_types.h" #include "openair2/LAYER2/nr_pdcp/nr_pdcp_configuration.h" +#include "common/utils/nr/nr_common.h" struct asn_TYPE_descriptor_s; typedef struct { @@ -127,6 +129,15 @@ int do_NR_MeasurementTimingConfiguration(const NR_MeasurementTimingConfiguration int do_RRCSetupRequest(uint8_t *buffer, size_t buffer_size, uint8_t *rv, uint64_t fiveG_S_TMSI_part1); +int do_nrMeasurementReport_SA(NR_MeasurementReport_t *measurementReport, + long trigger_to_measid, + long trigger_quantity, + long rs_type, + uint16_t Nid_cell, + int rsrp_dBm, + uint8_t *buffer, + size_t buffer_size); + int do_NR_RRCReconfigurationComplete_for_nsa(uint8_t *buffer, size_t buffer_size, NR_RRC_TransactionIdentifier_t Transaction_id); int do_NR_RRCReconfigurationComplete(uint8_t *buffer, size_t buffer_size, const uint8_t Transaction_id); diff --git a/openair2/RRC/NR_UE/rrc_UE.c b/openair2/RRC/NR_UE/rrc_UE.c index 8683269054..d0b551f67a 100644 --- a/openair2/RRC/NR_UE/rrc_UE.c +++ b/openair2/RRC/NR_UE/rrc_UE.c @@ -2277,6 +2277,79 @@ void nr_ue_meas_filtering(rrcPerNB_t *rrc, bool is_neighboring_cell, uint16_t Ni apply_ema(&meas_cell->ss_rsrp_dBm, l3_measurements->ssb_filter_coeff_rsrp, rsrp_dBm); } +static void nr_ue_check_meas_report(NR_UE_RRC_INST_t *rrc, const uint8_t gnb_index) +{ + rrcPerNB_t *rrcNB = rrc->perNB + gnb_index; + l3_measurements_t *l3_measurements = &rrcNB->l3_measurements; + + for (int i = 0; i < MAX_MEAS_CONFIG; i++) { + NR_ReportConfigToAddMod_t *report_config = rrcNB->ReportConfig[i]; + if (report_config == NULL) + continue; + + if (report_config->reportConfig.present != NR_ReportConfigToAddMod__reportConfig_PR_reportConfigNR) + continue; + + NR_ReportConfigNR_t *report_config_nr = report_config->reportConfig.choice.reportConfigNR; + if (report_config_nr->reportType.present != NR_ReportConfigNR__reportType_PR_eventTriggered) + continue; + + NR_EventTriggerConfig_t *event_trigger_config = report_config_nr->reportType.choice.eventTriggered; + if (event_trigger_config->eventId.present != NR_EventTriggerConfig__eventId_PR_eventA2) + continue; + + struct NR_EventTriggerConfig__eventId__eventA2 *event_A2 = event_trigger_config->eventId.choice.eventA2; + if (event_A2->a2_Threshold.present != NR_MeasTriggerQuantity_PR_rsrp) + continue; + + meas_t *serving_cell = &l3_measurements->serving_cell; + int serving_cell_rsrp = INT_MAX; + if (serving_cell->ss_rsrp_dBm.init == true) { + serving_cell_rsrp = serving_cell->ss_rsrp_dBm.val; + } else if (serving_cell->csi_rsrp_dBm.init == true) { + serving_cell_rsrp = serving_cell->csi_rsrp_dBm.val; + } else { + LOG_E(NR_RRC, "There are no RSRP measurements taken for the active cell\n"); + } + + // TS 38.133 - Table 10.1.6.1-1: SS-RSRP and CSI-RSRP measurement report mapping + int rsrp_threshold = event_A2->a2_Threshold.choice.rsrp - 157; + int rsrp_hysteresis = event_A2->hysteresis >> 1; + + // TS 38.331 - 5.5.4.3 Event A2 (Serving becomes worse than threshold) + if (serving_cell_rsrp + rsrp_hysteresis < rsrp_threshold) { + if (!nr_timer_is_active(&l3_measurements->TA2)) { + nr_timer_setup(&l3_measurements->TA2, get_A2_event_time_to_trigger(event_A2->timeToTrigger), 10); + nr_timer_start(&l3_measurements->TA2); + int report_config_id = report_config->reportConfigId; + int meas_id = -1; + for (int j = 0; j < MAX_MEAS_ID; j++) { + NR_MeasIdToAddMod_t *meas_id_toAddMod = rrcNB->MeasId[j]; + if (meas_id_toAddMod == NULL) { + continue; + } + if (meas_id_toAddMod->reportConfigId == report_config_id) { + meas_id = meas_id_toAddMod->measId; + } + } + AssertFatal(meas_id > 0, "meas_id did not found for report_config_id %i\n", report_config_id); + + l3_measurements->trigger_to_measid = meas_id; + l3_measurements->trigger_quantity = event_A2->a2_Threshold.present; + l3_measurements->rs_type = event_trigger_config->rsType; + + LOG_W(NR_RRC, + "(active_cell_rsrp) %i + (rsrp_hysteresis) %i < (rsrp_threshold) %i\n", + serving_cell_rsrp, + rsrp_hysteresis, + rsrp_threshold); + } + } else if (nr_timer_is_active(&l3_measurements->TA2) && (serving_cell_rsrp - rsrp_hysteresis > rsrp_threshold)) { + nr_timer_stop(&l3_measurements->TA2); + } + } +} + void nr_rrc_handle_ra_indication(NR_UE_RRC_INST_t *rrc, bool ra_succeeded) { NR_UE_Timers_Constants_t *timers = &rrc->timers_and_constants; @@ -2407,6 +2480,7 @@ void *rrc_nrue(void *notUsed) NR_RRC_MAC_MEAS_DATA_IND(msg_p).Nid_cell, NR_RRC_MAC_MEAS_DATA_IND(msg_p).is_csi_meas, NR_RRC_MAC_MEAS_DATA_IND(msg_p).rsrp_dBm - 157); + nr_ue_check_meas_report(rrc, NR_RRC_MAC_MEAS_DATA_IND(msg_p).gnb_index); break; case NR_RRC_MAC_CCCH_DATA_IND: { @@ -3045,3 +3119,22 @@ void nr_rrc_set_mac_queue(instance_t instance, notifiedFIFO_t *mac_input_nf) NR_UE_RRC_INST_t *rrc = get_NR_UE_rrc_inst(instance); rrc->mac_input_nf = mac_input_nf; } + +void rrc_ue_generate_measurementReport(rrcPerNB_t *rrc, instance_t ue_id) +{ + uint8_t buffer[NR_RRC_BUF_SIZE]; + NR_MeasurementReport_t measurementReport = {0}; + l3_measurements_t *l3m = &rrc->l3_measurements; + int rsrp_dBm = l3m->rs_type == NR_NR_RS_Type_ssb ? l3m->serving_cell.ss_rsrp_dBm.val : l3m->serving_cell.csi_rsrp_dBm.val; + uint8_t size = do_nrMeasurementReport_SA(&measurementReport, + l3m->trigger_to_measid, + l3m->trigger_quantity, + l3m->rs_type, + l3m->serving_cell.Nid_cell, + rsrp_dBm, + buffer, + sizeof(buffer)); + + int srb_id = 1; // possibly TODO in SRB3 in some cases + nr_pdcp_data_req_srb(ue_id, srb_id, 0, size, buffer, deliver_pdu_srb_rlc, NULL); +} diff --git a/openair2/RRC/NR_UE/rrc_defs.h b/openair2/RRC/NR_UE/rrc_defs.h index 31e7305b96..dfcd01a09a 100644 --- a/openair2/RRC/NR_UE/rrc_defs.h +++ b/openair2/RRC/NR_UE/rrc_defs.h @@ -199,6 +199,7 @@ typedef struct l3_measurements_s { long trigger_to_measid; long trigger_quantity; long rs_type; + NR_timer_t TA2; } l3_measurements_t; typedef struct rrcPerNB { diff --git a/openair2/RRC/NR_UE/rrc_proto.h b/openair2/RRC/NR_UE/rrc_proto.h index ba853f546d..d332ca5175 100644 --- a/openair2/RRC/NR_UE/rrc_proto.h +++ b/openair2/RRC/NR_UE/rrc_proto.h @@ -55,6 +55,8 @@ void nr_rrc_going_to_IDLE(NR_UE_RRC_INST_t *rrc, void handle_RRCRelease(NR_UE_RRC_INST_t *rrc); +void rrc_ue_generate_measurementReport(rrcPerNB_t *rrc, instance_t ue_id); + void set_rlf_sib1_timers_and_constants(NR_UE_Timers_Constants_t *tac, NR_UE_TimersAndConstants_t *ue_TimersAndConstants); /**\brief RRC UE task. @@ -83,6 +85,7 @@ void nr_ue_rrc_timer_trigger(int module_id, int frame, int gnb_id); void handle_t300_expiry(NR_UE_RRC_INST_t *rrc); void handle_t430_expiry(NR_UE_RRC_INST_t *rrc); +int get_A2_event_time_to_trigger(long time_to_trigger); void reset_rlf_timers_and_constants(NR_UE_Timers_Constants_t *tac); void set_default_timers_and_constants(NR_UE_Timers_Constants_t *tac); void nr_rrc_set_sib1_timers_and_constants(NR_UE_Timers_Constants_t *tac, NR_SIB1_t *sib1); diff --git a/openair2/RRC/NR_UE/rrc_timers_and_constants.c b/openair2/RRC/NR_UE/rrc_timers_and_constants.c index 57f6be2eb6..b0bc4661bc 100644 --- a/openair2/RRC/NR_UE/rrc_timers_and_constants.c +++ b/openair2/RRC/NR_UE/rrc_timers_and_constants.c @@ -122,6 +122,19 @@ void nr_rrc_SI_timers(NR_UE_RRC_SI_INFO *SInfo) } } +void handle_meas_timers(NR_UE_RRC_INST_t *rrc) +{ + for (int i = 0; i < NB_CNX_UE; i++) { + rrcPerNB_t *nb = &rrc->perNB[i]; + l3_measurements_t *l3_measurements = &nb->l3_measurements; + + bool ta2_expired = nr_timer_tick(&l3_measurements->TA2); + if (ta2_expired && l3_measurements->trigger_quantity > 0) { + rrc_ue_generate_measurementReport(nb, rrc->ue_id); + } + } +} + void nr_rrc_handle_timers(NR_UE_RRC_INST_t *rrc) { NR_UE_Timers_Constants_t *timers = &rrc->timers_and_constants; @@ -185,6 +198,8 @@ void nr_rrc_handle_timers(NR_UE_RRC_INST_t *rrc) // Spec 38.331 Section 5.2.2.6 handle_t430_expiry(rrc); } + + handle_meas_timers(rrc); } int nr_rrc_get_T304(long t304) @@ -630,3 +645,43 @@ void reset_rlf_timers_and_constants(NR_UE_Timers_Constants_t *tac) tac->N310_cnt = 0; tac->N311_cnt = 0; } + +int get_A2_event_time_to_trigger(long time_to_trigger) +{ + switch (time_to_trigger) { + case NR_TimeToTrigger_ms0: + return 0; + case NR_TimeToTrigger_ms40: + return 40; + case NR_TimeToTrigger_ms64: + return 64; + case NR_TimeToTrigger_ms80: + return 80; + case NR_TimeToTrigger_ms100: + return 100; + case NR_TimeToTrigger_ms128: + return 128; + case NR_TimeToTrigger_ms160: + return 160; + case NR_TimeToTrigger_ms256: + return 256; + case NR_TimeToTrigger_ms320: + return 320; + case NR_TimeToTrigger_ms480: + return 480; + case NR_TimeToTrigger_ms512: + return 512; + case NR_TimeToTrigger_ms640: + return 640; + case NR_TimeToTrigger_ms1024: + return 1024; + case NR_TimeToTrigger_ms1280: + return 1280; + case NR_TimeToTrigger_ms2560: + return 2560; + case NR_TimeToTrigger_ms5120: + return 5120; + default: + AssertFatal(false, "Invalid TimeToTrigger %ld\n", time_to_trigger); + } +} From 2c369a8299620f1d2f9371ebdd5d1bfb905777c8 Mon Sep 17 00:00:00 2001 From: alexjiao Date: Tue, 26 Aug 2025 14:57:25 +0100 Subject: [PATCH 14/52] Handle reportAmount and reportInterval in reportConfigNR --- openair2/RRC/NR_UE/rrc_UE.c | 49 ++++++++++++++++++- openair2/RRC/NR_UE/rrc_defs.h | 4 ++ openair2/RRC/NR_UE/rrc_timers_and_constants.c | 17 +++++++ 3 files changed, 69 insertions(+), 1 deletion(-) diff --git a/openair2/RRC/NR_UE/rrc_UE.c b/openair2/RRC/NR_UE/rrc_UE.c index d0b551f67a..7ace2c7a91 100644 --- a/openair2/RRC/NR_UE/rrc_UE.c +++ b/openair2/RRC/NR_UE/rrc_UE.c @@ -1666,6 +1666,14 @@ static void handle_meas_reporting_remove(rrcPerNB_t *rrc, int id, NR_UE_Timers_C // TODO stop the periodical reporting timer or timer T321, whichever is running, // and reset the associated information (e.g. timeToTrigger) for this measId nr_timer_stop(&timers->T321); + + l3_measurements_t *l3_measurements = &rrc->l3_measurements; + nr_timer_stop(&l3_measurements->TA2); + nr_timer_stop(&l3_measurements->periodic_report_timer); + + l3_measurements->reports_sent = 0; + l3_measurements->max_reports = 0; + l3_measurements->report_interval_ms = 0; } static void handle_measobj_remove(rrcPerNB_t *rrc, struct NR_MeasObjectToRemoveList *remove_list, NR_UE_Timers_Constants_t *timers) @@ -2277,6 +2285,38 @@ void nr_ue_meas_filtering(rrcPerNB_t *rrc, bool is_neighboring_cell, uint16_t Ni apply_ema(&meas_cell->ss_rsrp_dBm, l3_measurements->ssb_filter_coeff_rsrp, rsrp_dBm); } +static long get_measurement_report_interval_ms(NR_ReportInterval_t interval) +{ + switch (interval) { + case NR_ReportInterval_ms120: + return 120; + case NR_ReportInterval_ms240: + return 240; + case NR_ReportInterval_ms480: + return 480; + case NR_ReportInterval_ms640: + return 640; + case NR_ReportInterval_ms1024: + return 1024; + case NR_ReportInterval_ms2048: + return 2048; + case NR_ReportInterval_ms5120: + return 5120; + case NR_ReportInterval_ms10240: + return 10240; + case NR_ReportInterval_min1: + return 60000; + case NR_ReportInterval_min6: + return 360000; + case NR_ReportInterval_min12: + return 720000; + case NR_ReportInterval_min30: + return 1800000; + default: + return 1024; + } +} + static void nr_ue_check_meas_report(NR_UE_RRC_INST_t *rrc, const uint8_t gnb_index) { rrcPerNB_t *rrcNB = rrc->perNB + gnb_index; @@ -2318,7 +2358,7 @@ static void nr_ue_check_meas_report(NR_UE_RRC_INST_t *rrc, const uint8_t gnb_ind // TS 38.331 - 5.5.4.3 Event A2 (Serving becomes worse than threshold) if (serving_cell_rsrp + rsrp_hysteresis < rsrp_threshold) { - if (!nr_timer_is_active(&l3_measurements->TA2)) { + if (!nr_timer_is_active(&l3_measurements->TA2) && (l3_measurements->reports_sent == 0)) { nr_timer_setup(&l3_measurements->TA2, get_A2_event_time_to_trigger(event_A2->timeToTrigger), 10); nr_timer_start(&l3_measurements->TA2); int report_config_id = report_config->reportConfigId; @@ -2337,6 +2377,11 @@ static void nr_ue_check_meas_report(NR_UE_RRC_INST_t *rrc, const uint8_t gnb_ind l3_measurements->trigger_to_measid = meas_id; l3_measurements->trigger_quantity = event_A2->a2_Threshold.present; l3_measurements->rs_type = event_trigger_config->rsType; + l3_measurements->reports_sent = 0; + l3_measurements->max_reports = (event_trigger_config->reportAmount == NR_EventTriggerConfig__reportAmount_infinity) + ? INT_MAX + : (1 << event_trigger_config->reportAmount); + l3_measurements->report_interval_ms = get_measurement_report_interval_ms(event_trigger_config->reportInterval); LOG_W(NR_RRC, "(active_cell_rsrp) %i + (rsrp_hysteresis) %i < (rsrp_threshold) %i\n", @@ -2346,6 +2391,8 @@ static void nr_ue_check_meas_report(NR_UE_RRC_INST_t *rrc, const uint8_t gnb_ind } } else if (nr_timer_is_active(&l3_measurements->TA2) && (serving_cell_rsrp - rsrp_hysteresis > rsrp_threshold)) { nr_timer_stop(&l3_measurements->TA2); + nr_timer_stop(&l3_measurements->periodic_report_timer); + l3_measurements->reports_sent = 0; } } } diff --git a/openair2/RRC/NR_UE/rrc_defs.h b/openair2/RRC/NR_UE/rrc_defs.h index dfcd01a09a..d1435526b3 100644 --- a/openair2/RRC/NR_UE/rrc_defs.h +++ b/openair2/RRC/NR_UE/rrc_defs.h @@ -199,7 +199,11 @@ typedef struct l3_measurements_s { long trigger_to_measid; long trigger_quantity; long rs_type; + int reports_sent; + int max_reports; + long report_interval_ms; NR_timer_t TA2; + NR_timer_t periodic_report_timer; } l3_measurements_t; typedef struct rrcPerNB { diff --git a/openair2/RRC/NR_UE/rrc_timers_and_constants.c b/openair2/RRC/NR_UE/rrc_timers_and_constants.c index b0bc4661bc..694656736a 100644 --- a/openair2/RRC/NR_UE/rrc_timers_and_constants.c +++ b/openair2/RRC/NR_UE/rrc_timers_and_constants.c @@ -131,6 +131,23 @@ void handle_meas_timers(NR_UE_RRC_INST_t *rrc) bool ta2_expired = nr_timer_tick(&l3_measurements->TA2); if (ta2_expired && l3_measurements->trigger_quantity > 0) { rrc_ue_generate_measurementReport(nb, rrc->ue_id); + l3_measurements->reports_sent = 1; + + if (l3_measurements->reports_sent < l3_measurements->max_reports) { + nr_timer_setup(&l3_measurements->periodic_report_timer, l3_measurements->report_interval_ms, 10); + nr_timer_start(&l3_measurements->periodic_report_timer); + } + } + + bool periodic_expired = nr_timer_tick(&l3_measurements->periodic_report_timer); + if (periodic_expired && l3_measurements->reports_sent < l3_measurements->max_reports) { + rrc_ue_generate_measurementReport(nb, rrc->ue_id); + l3_measurements->reports_sent++; + + if (l3_measurements->reports_sent < l3_measurements->max_reports) { + nr_timer_setup(&l3_measurements->periodic_report_timer, l3_measurements->report_interval_ms, 10); + nr_timer_start(&l3_measurements->periodic_report_timer); + } } } } From 2aa496bae717cec0a7e0499a528780597b32b72e Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Tue, 16 Sep 2025 15:24:25 +0200 Subject: [PATCH 15/52] Add nr-cuup to run_locally.sh --- ci-scripts/run_locally.sh | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/ci-scripts/run_locally.sh b/ci-scripts/run_locally.sh index 1d4a73e919..6423378e9c 100755 --- a/ci-scripts/run_locally.sh +++ b/ci-scripts/run_locally.sh @@ -15,16 +15,18 @@ fi # The script assumes you've build the following images: # -# docker build . -f docker/Dockerfile.gNB.ubuntu22 -t oai-gnb -# docker build . -f docker/Dockerfile.nrUE.ubuntu22 -t oai-nr-ue +# docker build . -f docker/Dockerfile.gNB.ubuntu -t oai-gnb +# docker build . -f docker/Dockerfile.nr-cuup.ubuntu -t oai-nr-cuup +# docker build . -f docker/Dockerfile.nrUE.ubuntu -t oai-nr-ue # # The images above depend on the following images: # -# docker build . -f docker/Dockerfile.build.ubuntu22 -t ran-build -# dokcer build . -f docker/Dockerfile.base.ubuntu22 -t ran-base +# docker build . -f docker/Dockerfile.build.ubuntu -t ran-build +# docker build . -f docker/Dockerfile.base.ubuntu -t ran-base docker tag oai-nr-ue oai-ci/oai-nr-ue:develop-${SHORT_COMMIT_SHA} docker tag oai-gnb oai-ci/oai-gnb:develop-${SHORT_COMMIT_SHA} +docker tag oai-nr-cuup oai-ci/oai-nr-cuup:develop-${SHORT_COMMIT_SHA} python3 main.py --mode=InitiateHtml --ranRepository=NONE --ranBranch=${CURRENT_BRANCH} \ --ranCommitID=${COMMIT_SHA} --ranAllowMerge=false \ From 1ea90fc9870bfdeabde786c4f23034d0a31bbafd Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Wed, 17 Sep 2025 15:24:10 +0200 Subject: [PATCH 16/52] Rename to native_armv9 image build type For clarity, rename to native_armv9 for clarity. The check inside Python checked for native_arm, and if we introduce native_armv8 (for jetson), it would also match that. --- ci-scripts/cls_containerize.py | 2 +- ci-scripts/xml_files/container_image_build_arm.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ci-scripts/cls_containerize.py b/ci-scripts/cls_containerize.py index 8b5a817ac6..744614e95a 100644 --- a/ci-scripts/cls_containerize.py +++ b/ci-scripts/cls_containerize.py @@ -304,7 +304,7 @@ class Containerize(): result = re.search('build_cross_arm64', self.imageKind) if result is not None: self.dockerfileprefix = '.ubuntu.cross-arm64' - result = re.search('native_arm', self.imageKind) + result = re.search('native_armv9', self.imageKind) if result is not None: imageNames.append(('oai-gnb', 'gNB', 'oai-gnb', '')) imageNames.append(('ran-build-fhi72', 'build.fhi72.native_arm', 'ran-build-fhi72', '')) diff --git a/ci-scripts/xml_files/container_image_build_arm.xml b/ci-scripts/xml_files/container_image_build_arm.xml index 7c731fd3a1..f5daa348aa 100644 --- a/ci-scripts/xml_files/container_image_build_arm.xml +++ b/ci-scripts/xml_files/container_image_build_arm.xml @@ -40,7 +40,7 @@ Build_Image Build all Images gracehopper3-oai - native_arm + native_armv9 From 8045dc71e0643ae547a65b867d549f05af01d485 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Wed, 17 Sep 2025 15:26:34 +0200 Subject: [PATCH 17/52] XML description for build on Jetson (ARMv8) --- ci-scripts/cls_containerize.py | 5 ++ .../container_image_build_jetson.xml | 53 +++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 ci-scripts/xml_files/container_image_build_jetson.xml diff --git a/ci-scripts/cls_containerize.py b/ci-scripts/cls_containerize.py index 744614e95a..965ff6a6db 100644 --- a/ci-scripts/cls_containerize.py +++ b/ci-scripts/cls_containerize.py @@ -311,6 +311,11 @@ class Containerize(): imageNames.append(('oai-nr-cuup', 'nr-cuup', 'oai-nr-cuup', '')) imageNames.append(('oai-nr-ue', 'nrUE', 'oai-nr-ue', '')) imageNames.append(('oai-gnb-aerial', 'gNB.aerial', 'oai-gnb-aerial', '')) + result = re.search('native_armv8', self.imageKind) + if result is not None: + imageNames.append(('oai-gnb', 'gNB', 'oai-gnb', '')) + imageNames.append(('oai-nr-cuup', 'nr-cuup', 'oai-nr-cuup', '')) + imageNames.append(('oai-nr-ue', 'nrUE', 'oai-nr-ue', '')) cmd.cd(lSourcePath) # if asterix, copy the entitlement and subscription manager configurations diff --git a/ci-scripts/xml_files/container_image_build_jetson.xml b/ci-scripts/xml_files/container_image_build_jetson.xml new file mode 100644 index 0000000000..3cdac5efcb --- /dev/null +++ b/ci-scripts/xml_files/container_image_build_jetson.xml @@ -0,0 +1,53 @@ + + + build-tab + Build Container Images for ARM + wrench + + 800813 + 000001 + 000010 + + + + + Create_Workspace + Create new Workspace for server 0 + jetson3-oai + + + Build_Image + Build all Images + jetson3-oai + native_armv8 + + + + Push_Local_Registry + Push Images to Local Registry + jetson3-oai + armv8_ + + + From 44729d57656af72ff7fe10278b0a6357d1759fb6 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Wed, 17 Sep 2025 17:24:41 +0200 Subject: [PATCH 18/52] Add Jetson build pipeline to RAN-Container-Parent --- ci-scripts/Jenkinsfile-GitLab-Container | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/ci-scripts/Jenkinsfile-GitLab-Container b/ci-scripts/Jenkinsfile-GitLab-Container index 57d6b1651e..4bdb20d371 100644 --- a/ci-scripts/Jenkinsfile-GitLab-Container +++ b/ci-scripts/Jenkinsfile-GitLab-Container @@ -175,6 +175,28 @@ pipeline { } } } + stage ("Ubuntu-Jetson-Image-Builder") { + steps { + script { + triggerSlaveJob ('RAN-Ubuntu-Jetson-Image-Builder', 'Ubuntu-Jetson-Image-Builder') + } + } + post { + always { + script { + // Using a unique variable name for each test stage to avoid overwriting on a global variable + // due to parallel-time concurrency + ubuntuJetsonBuildStatus = finalizeSlaveJob('RAN-Ubuntu-Jetson-Image-Builder') + } + } + failure { + script { + currentBuild.result = 'FAILURE' + failingStages += ubuntuJetsonBuildStatus + } + } + } + } stage ("RHEL-Cluster-Image-Builder") { steps { script { From 95dfb67e512e434a93d38e421cf84e760db4fbaa Mon Sep 17 00:00:00 2001 From: francescomani Date: Wed, 17 Sep 2025 16:55:38 +0200 Subject: [PATCH 19/52] fix MSG3 with C-RNTI failure --- openair2/LAYER2/NR_MAC_UE/nr_ra_procedures.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openair2/LAYER2/NR_MAC_UE/nr_ra_procedures.c b/openair2/LAYER2/NR_MAC_UE/nr_ra_procedures.c index 58269f769a..bc29f7a9b9 100644 --- a/openair2/LAYER2/NR_MAC_UE/nr_ra_procedures.c +++ b/openair2/LAYER2/NR_MAC_UE/nr_ra_procedures.c @@ -1193,7 +1193,9 @@ void nr_ra_contention_resolution_failed(NR_UE_MAC_INST_t *mac) ra->t_crnti = 0; // flush MSG3 buffer free_and_zero(ra->Msg3_buffer); - nr_mac_rrc_msg3_ind(mac->ue_id, 0, true); + // MSG3 with C-RNTI is a L2 procedure, we shouldn't send any indication to RRC + if (!mac->msg3_C_RNTI) + nr_mac_rrc_msg3_ind(mac->ue_id, 0, true); NR_PRACH_RESOURCES_t *prach_resources = &ra->prach_resources; prach_resources->preamble_tx_counter++; if (prach_resources->preamble_tx_counter == ra->preambleTransMax + 1) { From a1175f7f613c6d94602ef1d0433cdca2d09272bc Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Wed, 17 Sep 2025 18:38:14 +0200 Subject: [PATCH 20/52] Add documentation for Jetson build pipeline --- doc/TESTBenches.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/doc/TESTBenches.md b/doc/TESTBenches.md index d7881fb993..70a31d7391 100644 --- a/doc/TESTBenches.md +++ b/doc/TESTBenches.md @@ -135,6 +135,14 @@ information on how the images are built. - target image from `Dockerfile.nr-cuup.ubuntu` - target image from `Dockerfile.nrUE.ubuntu` - target image from `Dockerfile.gNB.aerial.ubuntu` +- [RAN-Ubuntu-Jetson-Image-Builder](https://jenkins-oai.eurecom.fr/job/RAN-Ubuntu-Jetson-Image-Builder/) + ~BUILD-ONLY ~4G-LTE ~5G-NR + - jetson3-oai: ARMv8 Ubuntu image build using docker + - base image from `Dockerfile.base.ubuntu` + - build image from `Dockerfile.build.ubuntu`, followed by + - target image from `Dockerfile.gNB.ubuntu` + - target image from `Dockerfile.nr-cuup.ubuntu` + - target image from `Dockerfile.nrUE.ubuntu` #### Image Test pipelines From 8c78ef5b946f53ec6f8c584f7f9710a44ce91c33 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Wed, 17 Sep 2025 18:39:10 +0200 Subject: [PATCH 21/52] Correct Ubuntu version (to generic "Ubuntu") --- doc/TESTBenches.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/TESTBenches.md b/doc/TESTBenches.md index 70a31d7391..e7564b8678 100644 --- a/doc/TESTBenches.md +++ b/doc/TESTBenches.md @@ -113,7 +113,7 @@ information on how the images are built. - [RAN-Ubuntu18-Image-Builder](https://jenkins-oai.eurecom.fr/job/RAN-Ubuntu18-Image-Builder/) ~BUILD-ONLY ~4G-LTE ~5G-NR ~nrUE - run formatting check from `ci-scripts/docker/Dockerfile.formatting.ubuntu` - - obelix: Ubuntu 22 image build using docker (Note: builds U22 images while pipeline is named U18!) + - obelix: Ubuntu image build using docker (Note: builds Ubuntu images of newer version while pipeline is named U18!) - base image from `Dockerfile.base.ubuntu` - build image from `Dockerfile.build.ubuntu`, followed by - target image from `Dockerfile.eNB.ubuntu` @@ -128,7 +128,7 @@ information on how the images are built. - build unit tests from `ci-scripts/docker/Dockerfile.unittest.ubuntu`, and run them - [RAN-Ubuntu-ARM-Image-Builder](https://jenkins-oai.eurecom.fr/job/RAN-Ubuntu-ARM-Image-Builder/) ~BUILD-ONLY ~4G-LTE ~5G-NR - - gracehopper3-oai: ARM Ubuntu 22 image build using docker + - gracehopper3-oai: ARM Ubuntu image build using docker - base image from `Dockerfile.base.ubuntu` - build image from `Dockerfile.build.ubuntu`, followed by - target image from `Dockerfile.gNB.ubuntu` From 98462c20ba80433ec853395e2ccabedf2c4bfc26 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Wed, 17 Sep 2025 14:58:50 +0200 Subject: [PATCH 22/52] Remove interrupt_action Remove this flag, as it is not used as of this commit, and introduces confusion (see next commit: we reused the trans_interrupt timer for the transmission action indicator stop action, where the UE should stay active, while it is *not* when transm_interrupt is active). --- .../LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c | 13 ++++--------- openair2/LAYER2/NR_MAC_gNB/mac_proto.h | 2 +- openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h | 3 --- 3 files changed, 5 insertions(+), 13 deletions(-) diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c index ae5305d77c..e914a71af3 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c @@ -3461,11 +3461,10 @@ void nr_measgap_scheduling(gNB_MAC_INST *nr_mac, frame_t frame, sub_frame_t slot continue; NR_timer_t *t = &UE->UE_sched_ctrl.transm_interrupt; - interrupt_followup_action_t a = nr_timer_is_active(t) ? UE->interrupt_action : FOLLOW_INSYNC; // start a timer to stop scheduling UE during MeasGap, or extend timer for // duration of measGap with existing follow-up action if (!nr_timer_is_active(t) || nr_timer_remaining_time(t) < mgc->mgl_slots) { - nr_mac_interrupt_ue_transmission(nr_mac, UE, a, mgc->mgl_slots); + nr_mac_interrupt_ue_transmission(nr_mac, UE, mgc->mgl_slots); } } } @@ -3549,7 +3548,7 @@ int nr_mac_get_reconfig_delay_slots(NR_SubcarrierSpacing_t scs) return (delay_ms << scs) + sl_ahead; } -int nr_mac_interrupt_ue_transmission(gNB_MAC_INST *mac, NR_UE_info_t *UE, interrupt_followup_action_t action, int slots) +int nr_mac_interrupt_ue_transmission(gNB_MAC_INST *mac, NR_UE_info_t *UE, int slots) { DevAssert(mac != NULL); DevAssert(UE != NULL); @@ -3557,7 +3556,6 @@ int nr_mac_interrupt_ue_transmission(gNB_MAC_INST *mac, NR_UE_info_t *UE, interr nr_timer_setup(&UE->UE_sched_ctrl.transm_interrupt, slots, 1); nr_timer_start(&UE->UE_sched_ctrl.transm_interrupt); - UE->interrupt_action = action; // it might happen that timing advance command should be sent during the UE // inactivity time. To prevent this, set a variable as if we would have just @@ -3565,14 +3563,14 @@ int nr_mac_interrupt_ue_transmission(gNB_MAC_INST *mac, NR_UE_info_t *UE, interr // frames, after the inactivity of the UE. UE->UE_sched_ctrl.ta_frame = (mac->frame - 1 + 1024) % 1024; - LOG_D(NR_MAC, "UE %04x: Interrupt UE transmission (%d slots) action %d\n", UE->rnti, slots, UE->interrupt_action); + LOG_D(NR_MAC, "UE %04x: Interrupt UE transmission (%d slots)\n", UE->rnti, slots); return 0; } int nr_transmission_action_indicator_stop(gNB_MAC_INST *mac, NR_UE_info_t *UE_info) { int delay = nr_mac_get_reconfig_delay_slots(UE_info->current_DL_BWP.scs); - nr_mac_interrupt_ue_transmission(mac, UE_info, FOLLOW_OUTOFSYNC, delay); + nr_mac_interrupt_ue_transmission(mac, UE_info, delay); LOG_I(NR_MAC, "gNB-DU received the TransmissionActionIndicator with Stop value for UE %04x\n", UE_info->rnti); return 0; } @@ -3641,9 +3639,6 @@ void nr_mac_update_timers(module_id_t module_id, frame_t frame, slot_t slot) if (nr_timer_tick(&sched_ctrl->transm_interrupt)) { /* expired */ nr_timer_stop(&sched_ctrl->transm_interrupt); - if (UE->interrupt_action == FOLLOW_OUTOFSYNC) - nr_mac_trigger_ul_failure(sched_ctrl, UE->current_DL_BWP.scs); - /* else: default FOLLOW_INSYNC: nothing to do (UE is now active again) */ } } } diff --git a/openair2/LAYER2/NR_MAC_gNB/mac_proto.h b/openair2/LAYER2/NR_MAC_gNB/mac_proto.h index be5d9d37b9..f04b28b8da 100644 --- a/openair2/LAYER2/NR_MAC_gNB/mac_proto.h +++ b/openair2/LAYER2/NR_MAC_gNB/mac_proto.h @@ -65,7 +65,7 @@ void nr_mac_prepare_ra_ue(gNB_MAC_INST *nrmac, NR_UE_info_t *UE); bool add_new_UE_RA(gNB_MAC_INST *nr_mac, NR_UE_info_t *UE); int nr_mac_get_reconfig_delay_slots(NR_SubcarrierSpacing_t scs); -int nr_mac_interrupt_ue_transmission(gNB_MAC_INST *mac, NR_UE_info_t *UE, interrupt_followup_action_t action, int slots); +int nr_mac_interrupt_ue_transmission(gNB_MAC_INST *mac, NR_UE_info_t *UE, int slots); int nr_transmission_action_indicator_stop(gNB_MAC_INST *mac, NR_UE_info_t *UE_info); void clear_nr_nfapi_information(gNB_MAC_INST *gNB, diff --git a/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h b/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h index af62663de7..3abc547454 100644 --- a/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h +++ b/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h @@ -762,8 +762,6 @@ typedef struct measgap_config { int mgl_slots; } measgap_config_t; -typedef enum interrupt_followup_action { FOLLOW_INSYNC, FOLLOW_OUTOFSYNC } interrupt_followup_action_t; - /*! \brief UE list used by gNB to order UEs/CC for scheduling*/ typedef struct { rnti_t rnti; @@ -780,7 +778,6 @@ typedef struct { /// in case of reestablishment, old spCellConfig to apply after /// reconfiguration NR_SpCellConfig_t *reconfigSpCellConfig; - interrupt_followup_action_t interrupt_action; NR_UE_NR_Capability_t *capability; measgap_config_t measgap_config; // UE selected beam index From 25380e807507e87c58c1cd82289bcf9e43497131 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Wed, 17 Sep 2025 15:09:25 +0200 Subject: [PATCH 23/52] Add transm_timout timer to set UE to UL failure after timeout Introduce a new timer for the use case where a UE should not be scheduled anymore after a certain time. This is the case, for instance, during handover, where the "source DU" forwards the RRCReconfiguration and should stop scheduling this UE after some time. Originally, transm_interrupt was introduced in 5c6d90aaff41 ("Use NR_timer_t for UE transmission interrupt, e.g., RRC processing") to stop scheduling a UE. Commit 004403c84d57 ("Implement F1 UE Ctxt Modif Transmission Action Indicator") reused this timer in an attempt to save code with an "interrupt follow action". In commit 89ebbb419276 ("Do not trigger ul failure in measgap scheduling and standardize the verification if MAC is active in other scheduling functions"), the transm_interrupt timer is used to not schedule a UE through function nr_mac_ue_is_active(). This led to the UE not being forwarded the RRCReconfiguration, effectively breaking handover, as the message never reaches the UE. By using a new timer, the intended functionality is restored. Fixes: 89ebbb419276 ("Do not trigger ul failure in measgap scheduling and standardize the verification if MAC is active in other scheduling functions") --- .../NR_MAC_gNB/gNB_scheduler_primitives.c | 20 ++++++++++++++++++- openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h | 4 ++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c index e914a71af3..7d8367ff6b 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c @@ -3567,10 +3567,23 @@ int nr_mac_interrupt_ue_transmission(gNB_MAC_INST *mac, NR_UE_info_t *UE, int sl return 0; } +static void nr_mac_ue_transmission_timeout(gNB_MAC_INST *mac, NR_UE_info_t *UE, int slots) +{ + DevAssert(mac != NULL); + DevAssert(UE != NULL); + NR_SCHED_ENSURE_LOCKED(&mac->sched_lock); + + nr_timer_setup(&UE->UE_sched_ctrl.transm_timeout, slots, 1); + nr_timer_start(&UE->UE_sched_ctrl.transm_timeout); + + LOG_I(NR_MAC, "UE %04x: Timeout for UE transmission (%d slots)\n", UE->rnti, slots); + return; +} + int nr_transmission_action_indicator_stop(gNB_MAC_INST *mac, NR_UE_info_t *UE_info) { int delay = nr_mac_get_reconfig_delay_slots(UE_info->current_DL_BWP.scs); - nr_mac_interrupt_ue_transmission(mac, UE_info, delay); + nr_mac_ue_transmission_timeout(mac, UE_info, delay); LOG_I(NR_MAC, "gNB-DU received the TransmissionActionIndicator with Stop value for UE %04x\n", UE_info->rnti); return 0; } @@ -3640,6 +3653,11 @@ void nr_mac_update_timers(module_id_t module_id, frame_t frame, slot_t slot) /* expired */ nr_timer_stop(&sched_ctrl->transm_interrupt); } + if (nr_timer_tick(&sched_ctrl->transm_timeout)) { + nr_timer_stop(&sched_ctrl->transm_timeout); + LOG_W(NR_MAC, "UE %04x UL failure after transmission timeout\n", UE->rnti); + nr_mac_trigger_ul_failure(sched_ctrl, UE->current_DL_BWP.scs); + } } } diff --git a/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h b/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h index 3abc547454..50f77b681c 100644 --- a/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h +++ b/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h @@ -688,6 +688,10 @@ typedef struct { /// Timer for RRC processing procedures and transmission activity NR_timer_t transm_interrupt; + /// Timer for timeout before UE is set to UL failure (e.g., + /// "TransmissionActionIndicator" handling + NR_timer_t transm_timeout; + /// sri, ul_ri and tpmi based on SRS nr_srs_feedback_t srs_feedback; From 26698d4f07144f3af729f41498a51e78280a64ff Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Mon, 14 Jul 2025 17:09:21 +0200 Subject: [PATCH 24/52] HO Preparation: do verify_section_notset of neighbour and measurement config for DU During the review of N2 HO MR it has been found that there was no check on the absence of the neighbour and measurement section in the DU config file. --- openair2/GNB_APP/gnb_config.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openair2/GNB_APP/gnb_config.c b/openair2/GNB_APP/gnb_config.c index dcd17efe22..a3f9f0169b 100644 --- a/openair2/GNB_APP/gnb_config.c +++ b/openair2/GNB_APP/gnb_config.c @@ -722,6 +722,8 @@ void RCconfig_verify(configmodule_interface_t *cfg, ngran_node_t node_type) verify_section_notset(cfg, GNB_CONFIG_STRING_GNB_LIST ".[0]", GNB_CONFIG_STRING_AMF_IP_ADDRESS); verify_section_notset(cfg, NULL, CONFIG_STRING_SECURITY); verify_section_notset(cfg, NULL, CONFIG_STRING_NR_PDCP_LIST); + verify_section_notset(cfg, NULL, GNB_CONFIG_STRING_NEIGHBOUR_LIST); + verify_section_notset(cfg, NULL, GNB_CONFIG_STRING_MEASUREMENT_CONFIGURATION); } // else nothing to be checked /* other possible verifications: PNF, VNF, CU-CP, CU-UP, ...? */ From 2f4f1770509a52b59e7e4836b6d173b08d012229 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Wed, 20 Aug 2025 22:21:31 +0200 Subject: [PATCH 25/52] HO Preparation: allocate HO Context in the F1 trigger rather than nr_initiate_handover During the N2 HO review has been found that the HO context was not allocated at trigger time but later in the flow. This might lead to issues in some scenarios (e.g. in N2 HO) and it was fixed in this commit. --- openair2/RRC/NR/rrc_gNB_mobility.c | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/openair2/RRC/NR/rrc_gNB_mobility.c b/openair2/RRC/NR/rrc_gNB_mobility.c index fe3214da2d..7324399d0c 100644 --- a/openair2/RRC/NR/rrc_gNB_mobility.c +++ b/openair2/RRC/NR/rrc_gNB_mobility.c @@ -128,11 +128,8 @@ static void nr_initiate_handover(const gNB_RRC_INST *rrc, DevAssert(target_du != NULL); // source_du might be NULL -> inter-CU handover DevAssert(ho_prep_info->buf != NULL && ho_prep_info->len > 0); + DevAssert(ue->ho_context); - if (ue->ho_context != NULL) { - LOG_E(NR_RRC, "handover for UE %u ongoing, cannot trigger new\n", ue->rrc_ue_id); - return; - } // if any reconfiguration is ongoing, abort handover request for (int i = 0; i < NR_RRC_TRANSACTION_IDENTIFIER_NUMBER; ++i) { if (ue->xids[i] != RRC_ACTION_NONE) { @@ -141,10 +138,7 @@ static void nr_initiate_handover(const gNB_RRC_INST *rrc, } } - // if we know the source DU (F1 handover), allocate the context for it, - // otherwise only for the target DU (N2, Xn) - ho_ctx_type_t ctx_type = source_du != NULL ? HO_CTX_BOTH : HO_CTX_TARGET; - nr_handover_context_t *ho_ctx = alloc_ho_ctx(ctx_type); + nr_handover_context_t *ho_ctx = ue->ho_context; ho_ctx->target->du = target_du; // we will know target->{du_ue_id,new_rnti} once we have UE ctxt setup // response @@ -164,7 +158,7 @@ static void nr_initiate_handover(const gNB_RRC_INST *rrc, int result = asn_copy(&asn_DEF_NR_CellGroupConfig, (void **)&ho_ctx->source->old_cellGroupConfig, ue->masterCellGroup); AssertFatal(result == 0, "error during asn_copy() of CellGroupConfig\n"); } - ue->ho_context = ho_ctx; + LOG_A(NR_RRC, "Handover triggered for UE %u/RNTI %04x towards DU %ld/assoc_id %d/PCI %d\n", ue->rrc_ue_id, @@ -355,6 +349,13 @@ void nr_rrc_trigger_f1_ho(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue, nr_rrc_du_contain uint8_t buf[NR_RRC_BUF_SIZE]; int size = do_NR_HandoverPreparationInformation(ue->ue_cap_buffer.buf, ue->ue_cap_buffer.len, buf, sizeof buf); + // Allocate handover context (both CU and DU) + if (ue->ho_context != NULL) { + LOG_E(NR_RRC, "Ongoing handover for UE %d, cannot trigger new\n", ue->rrc_ue_id); + return; + } + ue->ho_context = alloc_ho_ctx(HO_CTX_BOTH); + // corresponds to a "handover request", 38.300 Sec 9.3.2.3 // see also 38.413 Sec 9.3.1.29 for information on source-CU to target-CU // information (Source NG-RAN Node to Target NG-RAN Node Transparent Container) From ff5b2bc8b27a05acb27a12bf63c3693ca6b22787 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Thu, 21 Aug 2025 00:11:18 +0200 Subject: [PATCH 26/52] HO Preparation: print neighbour cell configuration --- openair2/GNB_APP/gnb_config.c | 38 +++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/openair2/GNB_APP/gnb_config.c b/openair2/GNB_APP/gnb_config.c index a3f9f0169b..55110fc449 100644 --- a/openair2/GNB_APP/gnb_config.c +++ b/openair2/GNB_APP/gnb_config.c @@ -1860,21 +1860,21 @@ static void fill_neighbour_cell_configuration(uint8_t gnb_idx, gNB_RRC_INST *rrc GNBNEIGHBOURCELLPARAMS_DESC, GNB_CONFIG_STRING_NEIGHBOUR_CELL_LIST, neighbourpath); - LOG_D(GNB_APP, "HO LOG: For the Cell: %ld Neighbour Cell ELM NUM: %d\n", cell.nr_cell_id, NeighbourCellParamList.numelt); + LOG_I(GNB_APP, "Cell %ld has %d neighbours\n", cell.nr_cell_id, NeighbourCellParamList.numelt); if (NeighbourCellParamList.numelt < 1) continue; cell.neighbour_cells = malloc_or_fail(sizeof(seq_arr_t)); seq_arr_init(cell.neighbour_cells, sizeof(nr_neighbour_cell_t)); for (int l = 0; l < NeighbourCellParamList.numelt; ++l) { - nr_neighbour_cell_t neighbourCell = {0}; - neighbourCell.gNB_ID = *(NeighbourCellParamList.paramarray[l][GNB_CONFIG_N_CELL_GNB_ID_IDX].uptr); - neighbourCell.nrcell_id = (uint64_t) * (NeighbourCellParamList.paramarray[l][GNB_CONFIG_N_CELL_NR_CELLID_IDX].u64ptr); - neighbourCell.physicalCellId = *NeighbourCellParamList.paramarray[l][GNB_CONFIG_N_CELL_PHYSICAL_ID_IDX].uptr; - neighbourCell.subcarrierSpacing = *NeighbourCellParamList.paramarray[l][GNB_CONFIG_N_CELL_SCS_IDX].uptr; - neighbourCell.band = *NeighbourCellParamList.paramarray[l][GNB_CONFIG_N_CELL_BAND_IDX].uptr; - neighbourCell.absoluteFrequencySSB = *NeighbourCellParamList.paramarray[l][GNB_CONFIG_N_CELL_ABS_FREQ_SSB_IDX].i64ptr; - neighbourCell.tac = *NeighbourCellParamList.paramarray[l][GNB_CONFIG_N_CELL_TAC_IDX].uptr; + nr_neighbour_cell_t n = {0}; + n.gNB_ID = *(NeighbourCellParamList.paramarray[l][GNB_CONFIG_N_CELL_GNB_ID_IDX].uptr); + n.nrcell_id = (uint64_t) * (NeighbourCellParamList.paramarray[l][GNB_CONFIG_N_CELL_NR_CELLID_IDX].u64ptr); + n.physicalCellId = *NeighbourCellParamList.paramarray[l][GNB_CONFIG_N_CELL_PHYSICAL_ID_IDX].uptr; + n.subcarrierSpacing = *NeighbourCellParamList.paramarray[l][GNB_CONFIG_N_CELL_SCS_IDX].uptr; + n.band = *NeighbourCellParamList.paramarray[l][GNB_CONFIG_N_CELL_BAND_IDX].uptr; + n.absoluteFrequencySSB = *NeighbourCellParamList.paramarray[l][GNB_CONFIG_N_CELL_ABS_FREQ_SSB_IDX].i64ptr; + n.tac = *NeighbourCellParamList.paramarray[l][GNB_CONFIG_N_CELL_TAC_IDX].uptr; char neighbour_plmn_path[CONFIG_MAXOPTLENGTH]; snprintf(neighbour_plmn_path, @@ -1886,10 +1886,22 @@ static void fill_neighbour_cell_configuration(uint8_t gnb_idx, gNB_RRC_INST *rrc GNB_CONFIG_STRING_NEIGHBOUR_PLMN); GET_PARAMS(NeighbourPlmn, GNBPLMNPARAMS_DESC, neighbour_plmn_path); - neighbourCell.plmn.mcc = *NeighbourPlmn[GNB_MOBILE_COUNTRY_CODE_IDX].uptr; - neighbourCell.plmn.mnc = *NeighbourPlmn[GNB_MOBILE_NETWORK_CODE_IDX].uptr; - neighbourCell.plmn.mnc_digit_length = *NeighbourPlmn[GNB_MNC_DIGIT_LENGTH].uptr; - seq_arr_push_back(cell.neighbour_cells, &neighbourCell, sizeof(neighbourCell)); + n.plmn.mcc = *NeighbourPlmn[GNB_MOBILE_COUNTRY_CODE_IDX].uptr; + n.plmn.mnc = *NeighbourPlmn[GNB_MOBILE_NETWORK_CODE_IDX].uptr; + n.plmn.mnc_digit_length = *NeighbourPlmn[GNB_MNC_DIGIT_LENGTH].uptr; + seq_arr_push_back(cell.neighbour_cells, &n, sizeof(n)); + LOG_I(GNB_APP, + " [%d] neighbor ID %d cellId %ld PCI %d SCS %d SSB ARFCN %u TAC %u PLMN %03u.%0*u\n", + l, + n.gNB_ID, + n.nrcell_id, + n.physicalCellId, + n.subcarrierSpacing, + n.absoluteFrequencySSB, + n.tac, + n.plmn.mcc, + n.plmn.mnc_digit_length, + n.plmn.mnc); } seq_arr_push_back(rrc->neighbour_cell_configuration, &cell, sizeof(cell)); } From cfb654e77f7f4b8176f0e9f3a0042d39ece53e5a Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Mon, 1 Sep 2025 14:00:35 +0200 Subject: [PATCH 27/52] HO Preparation: add alloc_ho_ctx to rrc_gNB_mobility lib --- openair2/RRC/NR/rrc_gNB_mobility.c | 3 +-- openair2/RRC/NR/rrc_gNB_mobility.h | 3 +++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/openair2/RRC/NR/rrc_gNB_mobility.c b/openair2/RRC/NR/rrc_gNB_mobility.c index 7324399d0c..fbccc637a7 100644 --- a/openair2/RRC/NR/rrc_gNB_mobility.c +++ b/openair2/RRC/NR/rrc_gNB_mobility.c @@ -42,8 +42,7 @@ #include "openair2/E2AP/RAN_FUNCTION/O-RAN/ran_func_rc_extern.h" #endif -typedef enum { HO_CTX_BOTH, HO_CTX_SOURCE, HO_CTX_TARGET } ho_ctx_type_t; -static nr_handover_context_t *alloc_ho_ctx(ho_ctx_type_t type) +nr_handover_context_t *alloc_ho_ctx(ho_ctx_type_t type) { nr_handover_context_t *ho_ctx = calloc_or_fail(1, sizeof(*ho_ctx)); if (type == HO_CTX_SOURCE || type == HO_CTX_BOTH) { diff --git a/openair2/RRC/NR/rrc_gNB_mobility.h b/openair2/RRC/NR/rrc_gNB_mobility.h index 6e9bcbcfe6..a907b3c472 100644 --- a/openair2/RRC/NR/rrc_gNB_mobility.h +++ b/openair2/RRC/NR/rrc_gNB_mobility.h @@ -72,6 +72,9 @@ typedef struct nr_handover_context_s { nr_ho_target_cu_t *target; } nr_handover_context_t; +typedef enum { HO_CTX_BOTH, HO_CTX_SOURCE, HO_CTX_TARGET } ho_ctx_type_t; +nr_handover_context_t *alloc_ho_ctx(ho_ctx_type_t type); + void nr_rrc_trigger_f1_ho(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue, nr_rrc_du_container_t *source_du, nr_rrc_du_container_t *target_du); void nr_rrc_finalize_ho(gNB_RRC_UE_t *ue); From 20e534d483e5dbbb8e2cacc030d8dd74a5104966 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Tue, 25 Mar 2025 10:04:10 +0100 Subject: [PATCH 28/52] Add NGAP Handover Required message NGAP encoder and RRC callback on source NG-RAN This commit introduces the NG Mobility Management Procedure known as Handover Preparation. The outbound message is sent by the source NG-RAN and is known as Handover Required. * introduce NGAP library for Mobility Management * introduce NGAP IEs encoder functions for Handover Required and relevant common IEs * handle RRC trigger in NGAP and Send NGAP Handover Required to the AMF Co-authored-by: batuhan duyuler --- CMakeLists.txt | 1 + openair2/COMMON/ngap_messages_def.h | 1 + openair2/COMMON/ngap_messages_types.h | 87 ++++++++- openair2/RRC/NR/rrc_gNB_NGAP.c | 78 ++++++++ openair2/RRC/NR/rrc_gNB_NGAP.h | 5 + openair3/NGAP/ngap_common.c | 29 +++ openair3/NGAP/ngap_common.h | 3 + openair3/NGAP/ngap_gNB.c | 59 ++++++ openair3/NGAP/ngap_gNB_decoder.c | 10 + openair3/NGAP/ngap_gNB_encoder.c | 3 +- openair3/NGAP/ngap_gNB_mobility_management.c | 193 +++++++++++++++++++ openair3/NGAP/ngap_gNB_mobility_management.h | 31 +++ openair3/NGAP/ngap_msg_includes.h | 8 + 13 files changed, 506 insertions(+), 2 deletions(-) create mode 100644 openair3/NGAP/ngap_gNB_mobility_management.c create mode 100644 openair3/NGAP/ngap_gNB_mobility_management.h diff --git a/CMakeLists.txt b/CMakeLists.txt index fa2d497589..058bfd7a39 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -363,6 +363,7 @@ add_library(ngap ${NGAP_DIR}/ngap_gNB.c ${NGAP_DIR}/ngap_common.c ${NGAP_DIR}/ngap_gNB_context_management_procedures.c + ${NGAP_DIR}/ngap_gNB_mobility_management.c ${NGAP_DIR}/ngap_gNB_decoder.c ${NGAP_DIR}/ngap_gNB_encoder.c ${NGAP_DIR}/ngap_gNB_handlers.c diff --git a/openair2/COMMON/ngap_messages_def.h b/openair2/COMMON/ngap_messages_def.h index 4f05a564c4..eea62d6c00 100644 --- a/openair2/COMMON/ngap_messages_def.h +++ b/openair2/COMMON/ngap_messages_def.h @@ -71,6 +71,7 @@ MESSAGE_DEF(NGAP_UE_CTXT_MODIFICATION_FAIL , MESSAGE_PRIORITY_MED, ngap_ue_ctxt_ MESSAGE_DEF(NGAP_PDUSESSION_SETUP_RESP , MESSAGE_PRIORITY_MED, ngap_pdusession_setup_resp_t , ngap_pdusession_setup_resp) MESSAGE_DEF(NGAP_PDUSESSION_MODIFY_RESP , MESSAGE_PRIORITY_MED, ngap_pdusession_modify_resp_t , ngap_pdusession_modify_resp) MESSAGE_DEF(NGAP_PDUSESSION_RELEASE_RESPONSE , MESSAGE_PRIORITY_MED, ngap_pdusession_release_resp_t , ngap_pdusession_release_resp) +MESSAGE_DEF(NGAP_HANDOVER_REQUIRED, MESSAGE_PRIORITY_MED, ngap_handover_required_t, ngap_handover_required) /* NGAP -> RRC messages */ MESSAGE_DEF(NGAP_DOWNLINK_NAS , MESSAGE_PRIORITY_MED, ngap_downlink_nas_t , ngap_downlink_nas ) diff --git a/openair2/COMMON/ngap_messages_types.h b/openair2/COMMON/ngap_messages_types.h index 3ee858ecca..57ecc51e79 100644 --- a/openair2/COMMON/ngap_messages_types.h +++ b/openair2/COMMON/ngap_messages_types.h @@ -65,7 +65,7 @@ #define NGAP_PDUSESSION_SETUP_REQ(mSGpTR) (mSGpTR)->ittiMsg.ngap_pdusession_setup_req #define NGAP_PDUSESSION_MODIFY_REQ(mSGpTR) (mSGpTR)->ittiMsg.ngap_pdusession_modify_req #define NGAP_PAGING_IND(mSGpTR) (mSGpTR)->ittiMsg.ngap_paging_ind - +#define NGAP_HANDOVER_REQUIRED(mSGpTR) (mSGpTR)->ittiMsg.ngap_handover_required #define NGAP_UE_CONTEXT_RELEASE_REQ(mSGpTR) (mSGpTR)->ittiMsg.ngap_ue_release_req #define NGAP_PDUSESSION_RELEASE_COMMAND(mSGpTR) (mSGpTR)->ittiMsg.ngap_pdusession_release_command #define NGAP_PDUSESSION_RELEASE_RESPONSE(mSGpTR) (mSGpTR)->ittiMsg.ngap_pdusession_release_resp @@ -463,6 +463,91 @@ typedef struct ngap_uplink_nas_s { byte_array_t nas_pdu; } ngap_uplink_nas_t; +typedef struct target_cell_id_s { + plmn_id_t plmn_identity; + uint32_t nrCellIdentity; +} cell_id_t; + +typedef struct target_ran_node_id_s { + uint32_t targetgNBId; + plmn_id_t plmn_identity; + uint32_t tac; +} target_ran_node_id_t; + +/* 3GPP TS 38.413 9.3.1.29 */ +typedef struct { + // QoS Flow Identifier + uint8_t qfi; +} qosflow_info_t; + +/* 3GPP TS 38.413 9.3.1.29 */ +typedef struct { + // PDU Session ID + uint8_t pdusession_id; + // QoS Flow Information List + uint8_t nb_of_qos_flow; + qosflow_info_t qos_flow_info[QOSFLOW_MAX_VALUE]; +} pdusession_resource_info_t; + +/* 3GPP TS 38.413 9.3.1.97 */ +typedef struct { + // Global Cell ID + cell_id_t id; + // Cell type + uint8_t type; + // Time UE Stayed in Cell + long time_in_cell; + // Cause + ngap_cause_t *cause; +} last_visited_ngran_cell_info_t; + +/* 3GPP TS 38.413 9.3.1.29 */ +typedef struct { + // RRC Container: HandoverPreparationInformation message + byte_array_t handoverInfo; + // Target Cell ID + cell_id_t targetCellId; + // PDU Session Resource Information List + uint16_t nb_pdu_session_resource; + pdusession_resource_info_t pdu_session_resource[NGAP_MAX_PDU_SESSION]; + // UE History Information + last_visited_ngran_cell_info_t ue_history_info; +} source_to_target_transparent_container_t; + +typedef enum { + HANDOVER_TYPE_INTRA5GS, // Intra5GS: NG-RAN node to NG-RAN node + HANDOVER_TYPE_5GSTOEPS, // 5GStoEPS: NG-RAN node to eNB + HANDOVER_TYPE_EPSTO5GS, // EPSto5GS: eNB to NG-RAN node + HANDOVER_TYPE_5GSTOUTRA, // 5GStoUTRA: NG-RAN node to UTRA +} ho_type_t; + +/* 3GPP TS 38.413 9.2.3.1 */ +typedef struct { + // PDU Session ID + uint8_t pdusession_id; + // Handover Required Transfer + byte_array_t ho_required_transfer; +} pdusession_resource_t; + +/* 3GPP TS 38.413 9.2.3.1 */ +typedef struct { + // RAN UE NGAP ID + uint32_t gNB_ue_ngap_id; + // AMF UE NGAP ID + uint64_t amf_ue_ngap_id; + // Handover Type + ho_type_t handoverType; + // Cause + ngap_cause_t cause; + // Target ID + target_ran_node_id_t target_gnb_id; + // PDU Session Resource List + uint8_t nb_of_pdusessions; + pdusession_resource_t pdusessions[NGAP_MAX_PDU_SESSION]; + // Source to Target Transparent Container + source_to_target_transparent_container_t *source2target; +} ngap_handover_required_t; + typedef struct ngap_ue_cap_info_ind_s { uint32_t gNB_ue_ngap_id; byte_array_t ue_radio_cap; diff --git a/openair2/RRC/NR/rrc_gNB_NGAP.c b/openair2/RRC/NR/rrc_gNB_NGAP.c index 407a0d03b7..5632e5932b 100644 --- a/openair2/RRC/NR/rrc_gNB_NGAP.c +++ b/openair2/RRC/NR/rrc_gNB_NGAP.c @@ -49,6 +49,7 @@ #include "NGAP_QosFlowSetupRequestItem.h" #include "NGAP_asn_constant.h" #include "NGAP_ProtocolIE-Field.h" +#include "NGAP_CellSize.h" #include "NR_UE-NR-Capability.h" #include "NR_UERadioAccessCapabilityInformation.h" #include "MAC/mac.h" @@ -1297,3 +1298,80 @@ int rrc_gNB_process_PAGING_IND(MessageDef *msg_p, instance_t instance) return 0; } + +/** @brief Callback for NGAP Handover Required message (3GPP TS 38.413 9.2.3.1) + * Direction: source gNB -> AMF */ +void rrc_gNB_send_NGAP_HANDOVER_REQUIRED(gNB_RRC_INST *rrc, + gNB_RRC_UE_t *UE, + const nr_neighbour_cell_t *neighbour, + const byte_array_t hoPrepInfo) +{ + LOG_I(NR_RRC, "Handover Preparation: send Handover Required (target gNB ID=%d, PCI=%d)\n", neighbour->gNB_ID, neighbour->physicalCellId); + + const plmn_id_t plmn = {.mcc = neighbour->plmn.mnc, + .mnc = neighbour->plmn.mnc, + .mnc_digit_length = neighbour->plmn.mnc_digit_length}; + + const target_ran_node_id_t target = { + .plmn_identity = plmn, + .tac = neighbour->tac, + .targetgNBId = neighbour->gNB_ID, + }; + + ngap_handover_required_t msg = { + .amf_ue_ngap_id = UE->amf_ue_ngap_id, + .gNB_ue_ngap_id = UE->rrc_ue_id, + .nb_of_pdusessions = UE->nb_of_pdusessions, + .cause.type = NGAP_CAUSE_RADIO_NETWORK, + .cause.value = NGAP_CAUSE_RADIO_NETWORK_HANDOVER_DESIRABLE_FOR_RADIO_REASON, + .handoverType = HANDOVER_TYPE_INTRA5GS, + .target_gnb_id = target, + }; + + cell_id_t target_cell = { + .nrCellIdentity = neighbour->nrcell_id, + .plmn_identity = plmn, + }; + + // Source to Target Transparent Container (M) + msg.source2target = calloc_or_fail(1, sizeof(*msg.source2target)); + // Target Cell ID (M) + msg.source2target->targetCellId = target_cell; + // RRC Container (M) + msg.source2target->handoverInfo = copy_byte_array(hoPrepInfo); + // UE History Info (M) + msg.source2target->ue_history_info.cause = malloc_or_fail(sizeof(*msg.source2target->ue_history_info.cause)); + msg.source2target->ue_history_info.cause->type = NGAP_CAUSE_RADIO_NETWORK; + msg.source2target->ue_history_info.cause->value = NGAP_CAUSE_RADIO_NETWORK_HANDOVER_DESIRABLE_FOR_RADIO_REASON; + msg.source2target->ue_history_info.type = NGAP_CellSize_small; + msg.source2target->ue_history_info.time_in_cell = 500; // dummy number for now + msg.source2target->ue_history_info.id = target_cell; + + /* Fill both PDU Session Resource List IE (M) in Handover Required + and PDU Session Resource Information List IE (O) in the Source NG-RAN + Node to Target NG-RAN Node Transparent Container */ + for (int i = 0; i < UE->nb_of_pdusessions; ++i) { + if (UE->pduSession[i].status == PDU_SESSION_STATUS_DONE || UE->pduSession[i].status == PDU_SESSION_STATUS_ESTABLISHED) { + rrc_pdu_session_param_t *pduSession = find_pduSession(UE, UE->pduSession[i].param.pdusession_id, false); + if (!pduSession) + continue; + // Handover Required Transfer (M) + uint8_t ho_required_transfer[128] = {0}; + msg.pdusessions[i].ho_required_transfer.buf = ho_required_transfer; + msg.pdusessions[i].ho_required_transfer.len = sizeof(ho_required_transfer); + // PDU Session ID + msg.pdusessions[i].pdusession_id = pduSession->param.pdusession_id; + + // PDU Session Resource Information List (O) + msg.source2target->pdu_session_resource[i].nb_of_qos_flow = pduSession->param.nb_qos; + for (int j = 0; j < pduSession->param.nb_qos; ++j) { + msg.source2target->pdu_session_resource[i].qos_flow_info[j].qfi = pduSession->param.qos[j].qfi; + } + msg.source2target->nb_pdu_session_resource++; + } + } + + MessageDef *msg_p = itti_alloc_new_message(TASK_RRC_GNB, 0, NGAP_HANDOVER_REQUIRED); + NGAP_HANDOVER_REQUIRED(msg_p) = msg; + itti_send_msg_to_task(TASK_NGAP, rrc->module_id, msg_p); +} diff --git a/openair2/RRC/NR/rrc_gNB_NGAP.h b/openair2/RRC/NR/rrc_gNB_NGAP.h index 22fff8705f..fd8cb31ff1 100644 --- a/openair2/RRC/NR/rrc_gNB_NGAP.h +++ b/openair2/RRC/NR/rrc_gNB_NGAP.h @@ -89,4 +89,9 @@ void nr_rrc_pdcp_config_security(gNB_RRC_UE_t *UE, bool enable_ciphering); int rrc_gNB_process_PAGING_IND(MessageDef *msg_p, instance_t instance); +void rrc_gNB_send_NGAP_HANDOVER_REQUIRED(gNB_RRC_INST *rrc, + gNB_RRC_UE_t *UE, + const nr_neighbour_cell_t *neighbourCellConfiguration, + const byte_array_t hoPrepInfo); + #endif diff --git a/openair3/NGAP/ngap_common.c b/openair3/NGAP/ngap_common.c index 82c2ccaf2a..b51c8e80de 100644 --- a/openair3/NGAP/ngap_common.c +++ b/openair3/NGAP/ngap_common.c @@ -112,3 +112,32 @@ ngap_mobility_restriction_t decode_ngap_mobility_restriction(const NGAP_Mobility TBCD_TO_MCC_MNC(&in->servingPLMN, out.serving_plmn.mcc, out.serving_plmn.mnc, out.serving_plmn.mnc_digit_length); return out; } + +void encode_ngap_target_id(NGAP_HandoverRequiredIEs_t *out, const target_ran_node_id_t *in) +{ + out->id = NGAP_ProtocolIE_ID_id_TargetID; + out->criticality = NGAP_Criticality_reject; + out->value.present = NGAP_HandoverRequiredIEs__value_PR_TargetID; + // CHOICE Target ID: NG-RAN (M) + out->value.choice.TargetID.present = NGAP_TargetID_PR_targetRANNodeID; + asn1cCalloc(out->value.choice.TargetID.choice.targetRANNodeID, targetRan); + // Global RAN Node ID (M) + targetRan->globalRANNodeID.present = NGAP_GlobalRANNodeID_PR_globalGNB_ID; + asn1cCalloc(targetRan->globalRANNodeID.choice.globalGNB_ID, globalGnbId); + globalGnbId->gNB_ID.present = NGAP_GNB_ID_PR_gNB_ID; + MACRO_GNB_ID_TO_BIT_STRING(in->targetgNBId, &globalGnbId->gNB_ID.choice.gNB_ID); + MCC_MNC_TO_PLMNID(in->plmn_identity.mcc, in->plmn_identity.mnc, in->plmn_identity.mnc_digit_length, &globalGnbId->pLMNIdentity); + // Selected TAI (M) + INT24_TO_OCTET_STRING(in->tac, &targetRan->selectedTAI.tAC); + MCC_MNC_TO_PLMNID(in->plmn_identity.mcc, + in->plmn_identity.mnc, + in->plmn_identity.mnc_digit_length, + &targetRan->selectedTAI.pLMNIdentity); +} + +void encode_ngap_nr_cgi(NGAP_NR_CGI_t *out, const plmn_id_t *plmn, const uint32_t cell_id) +{ + out->iE_Extensions = NULL; + MCC_MNC_TO_PLMNID(plmn->mcc, plmn->mnc, plmn->mnc_digit_length, &out->pLMNIdentity); + NR_CELL_ID_TO_BIT_STRING(cell_id, &out->nRCellIdentity); +} diff --git a/openair3/NGAP/ngap_common.h b/openair3/NGAP/ngap_common.h index dd7fdebc54..1e53d9dd1a 100644 --- a/openair3/NGAP/ngap_common.h +++ b/openair3/NGAP/ngap_common.h @@ -87,6 +87,9 @@ ngap_ambr_t decode_ngap_UEAggregateMaximumBitRate(const NGAP_UEAggregateMaximumB nssai_t decode_ngap_nssai(const NGAP_S_NSSAI_t *in); ngap_security_capabilities_t decode_ngap_security_capabilities(const NGAP_UESecurityCapabilities_t *in); ngap_mobility_restriction_t decode_ngap_mobility_restriction(const NGAP_MobilityRestrictionList_t *in); +void encode_ngap_target_id(NGAP_HandoverRequiredIEs_t *out, const target_ran_node_id_t *in); +void encode_ngap_nr_cgi(NGAP_NR_CGI_t *out, const plmn_id_t *plmn, const uint32_t cell_id); +pdusession_level_qos_parameter_t fill_qos(uint8_t qfi, const NGAP_QosFlowLevelQosParameters_t *params); /** @}*/ diff --git a/openair3/NGAP/ngap_gNB.c b/openair3/NGAP/ngap_gNB.c index 8edb5c3aa3..4b4fc2f850 100644 --- a/openair3/NGAP/ngap_gNB.c +++ b/openair3/NGAP/ngap_gNB.c @@ -55,6 +55,8 @@ #include "ngap_gNB_management_procedures.h" #include "ngap_gNB_nas_procedures.h" #include "ngap_messages_types.h" +#include "ngap_gNB_mobility_management.h" +#include "ngap_gNB_ue_context.h" #include "oai_asn1.h" #include "openair3/SECU/kdf.h" #include "queue.h" @@ -275,6 +277,57 @@ void ngap_gNB_handle_sctp_data_ind(sctp_data_ind_t *sctp_data_ind) { AssertFatal (result == EXIT_SUCCESS, "Failed to free memory (%d)!\n", result); } + /** @brief UE Mobility Management: callback for Handover Required */ +int ngap_handover_required(instance_t instance, ngap_handover_required_t *msg) +{ + DevAssert(msg != NULL); + NGAP_DEBUG("Triggered Handover Required\n"); + /* Retrieve the NGAP gNB instance associated with Mod_id */ + ngap_gNB_instance_t *ngap_gNB_instance_p = ngap_gNB_get_instance(instance); + DevAssert(ngap_gNB_instance_p != NULL); + + ngap_gNB_ue_context_t *ue_context_p = NULL; + if ((ue_context_p = ngap_get_ue_context(msg->gNB_ue_ngap_id)) == NULL) { + /* The context for this gNB ue ngap id doesn't exist in the map of gNB UEs */ + NGAP_WARN("Failed to find ue context associated with gNB ue ngap id: 0x%08x\n", msg->gNB_ue_ngap_id); + return -1; + } + + if ((ue_context_p->gNB_ue_ngap_id != msg->gNB_ue_ngap_id)) { + NGAP_ERROR("ue_context_p->gNB_ue_ngap_id %d does not match msg->gNB_ue_ngap_id %d\n", + ue_context_p->gNB_ue_ngap_id, + msg->gNB_ue_ngap_id); + return -1; + } + + NGAP_NGAP_PDU_t *pdu = encode_ng_handover_required(msg); + if (!pdu) { + NGAP_ERROR("Failed to encode Handover Required\n"); + ASN_STRUCT_FREE(asn_DEF_NGAP_NGAP_PDU, pdu); + return -1; + } + + if (LOG_DEBUGFLAG(DEBUG_ASN1)) + xer_fprint(stdout, &asn_DEF_NGAP_NGAP_PDU, &pdu); + + byte_array_t out = { .buf = NULL, .len = 0 }; + if (ngap_gNB_encode_pdu(pdu, &out.buf, (uint32_t *)&out.len) < 0) { + ASN_STRUCT_FREE(asn_DEF_NGAP_NGAP_PDU, pdu); + NGAP_ERROR("Failed to encode Handover Required message\n"); + return -1; + } + free(pdu); + + /* UE associated signalling -> use the allocated stream */ + ngap_gNB_itti_send_sctp_data_req(ngap_gNB_instance_p->instance, + ue_context_p->amf_ref->assoc_id, + out.buf, + out.len, + ue_context_p->tx_stream); + + return 0; +} + void ngap_gNB_init(void) { NGAP_DEBUG("Starting NGAP layer\n"); ngap_gNB_prepare_internal_data(); @@ -356,6 +409,12 @@ void *ngap_gNB_process_itti_msg(void *notUsed) { ngap_gNB_pdusession_release_resp(instance, &NGAP_PDUSESSION_RELEASE_RESPONSE(received_msg)); break; + case NGAP_HANDOVER_REQUIRED: + if (ngap_handover_required(instance, &NGAP_HANDOVER_REQUIRED(received_msg)) < 0) { + NGAP_ERROR("Handover Required failure: indication to RRC is not sent!\n"); + } + break; + default: NGAP_ERROR("Received unhandled message: %d:%s\n", ITTI_MSG_ID(received_msg), ITTI_MSG_NAME(received_msg)); break; diff --git a/openair3/NGAP/ngap_gNB_decoder.c b/openair3/NGAP/ngap_gNB_decoder.c index 4b76abecbb..58c70011cf 100644 --- a/openair3/NGAP/ngap_gNB_decoder.c +++ b/openair3/NGAP/ngap_gNB_decoder.c @@ -70,6 +70,12 @@ static int ngap_gNB_decode_initiating_message(NGAP_NGAP_PDU_t *pdu) { NGAP_INFO("PDUSESSIONSetup initiating message\n"); break; + case NGAP_ProcedureCode_id_HandoverPreparation: + res = asn_encode_to_new_buffer(NULL, ATS_CANONICAL_XER, &asn_DEF_NGAP_NGAP_PDU, pdu); + free(res.buffer); + NGAP_INFO("Handover Preparation initiating message\n"); + break; + case NGAP_ProcedureCode_id_PDUSessionResourceModify: res = asn_encode_to_new_buffer(NULL, ATS_CANONICAL_XER, &asn_DEF_NGAP_NGAP_PDU, pdu); free(res.buffer); @@ -119,6 +125,10 @@ static int ngap_gNB_decode_successful_outcome(NGAP_NGAP_PDU_t *pdu) { free(res.buffer); break; + case NGAP_ProcedureCode_id_HandoverPreparation: + res = asn_encode_to_new_buffer(NULL, ATS_CANONICAL_XER, &asn_DEF_NGAP_NGAP_PDU, pdu); + free(res.buffer); + break; default: NGAP_ERROR("Unknown procedure ID (%d) for successfull outcome message\n", diff --git a/openair3/NGAP/ngap_gNB_encoder.c b/openair3/NGAP/ngap_gNB_encoder.c index c3c095541d..a6e91ba9c8 100644 --- a/openair3/NGAP/ngap_gNB_encoder.c +++ b/openair3/NGAP/ngap_gNB_encoder.c @@ -52,7 +52,8 @@ static inline int ngap_gNB_encode_initiating(NGAP_NGAP_PDU_t *pdu, uint8_t **buf NGAP_ProcedureCode_id_NASNonDeliveryIndication, NGAP_ProcedureCode_id_UEContextReleaseRequest, NGAP_ProcedureCode_id_PathSwitchRequest, - NGAP_ProcedureCode_id_PDUSessionResourceModifyIndication}; + NGAP_ProcedureCode_id_PDUSessionResourceModifyIndication, + NGAP_ProcedureCode_id_HandoverPreparation}; int i; for (i = 0; i < sizeofArray(tmp); i++) if (pdu->choice.initiatingMessage->procedureCode == tmp[i]) diff --git a/openair3/NGAP/ngap_gNB_mobility_management.c b/openair3/NGAP/ngap_gNB_mobility_management.c new file mode 100644 index 0000000000..4f116c8f34 --- /dev/null +++ b/openair3/NGAP/ngap_gNB_mobility_management.c @@ -0,0 +1,193 @@ +/* + * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The OpenAirInterface Software Alliance licenses this file to You under + * the OAI Public License, Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.openairinterface.org/?page_id=698 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *------------------------------------------------------------------------------- + * For more information about the OpenAirInterface (OAI) Software Alliance: + * contact@openairinterface.org + */ + +#include +#include +#include +#include "ngap_common.h" +#include "ngap_msg_includes.h" +#include "ngap_gNB_defs.h" +#include "ngap_gNB_ue_context.h" +#include "oai_asn1.h" +#include "ngap_gNB_management_procedures.h" +#include "ngap_gNB_encoder.h" +#include "ngap_gNB_itti_messaging.h" + +/** @brief UE Mobility Management: encode Handover Required + * (9.2.3.1 of 3GPP TS 38.413) NG-RAN node → AMF */ +NGAP_NGAP_PDU_t *encode_ng_handover_required(const ngap_handover_required_t *msg) +{ + NGAP_NGAP_PDU_t *pdu = malloc_or_fail(sizeof(*pdu)); + + /* Prepare the NGAP message to encode */ + pdu->present = NGAP_NGAP_PDU_PR_initiatingMessage; + asn1cCalloc(pdu->choice.initiatingMessage, head); + head->procedureCode = NGAP_ProcedureCode_id_HandoverPreparation; + head->criticality = NGAP_Criticality_reject; + head->value.present = NGAP_InitiatingMessage__value_PR_HandoverRequired; + NGAP_HandoverRequired_t *out = &head->value.choice.HandoverRequired; + + // AMF UE NGAP ID (M) + asn1cSequenceAdd(out->protocolIEs.list, NGAP_HandoverRequiredIEs_t, ie1); + ie1->id = NGAP_ProtocolIE_ID_id_AMF_UE_NGAP_ID; + ie1->criticality = NGAP_Criticality_reject; + ie1->value.present = NGAP_HandoverRequiredIEs__value_PR_AMF_UE_NGAP_ID; + asn_uint642INTEGER(&ie1->value.choice.AMF_UE_NGAP_ID, msg->amf_ue_ngap_id); + + // RAN UE NGAP ID (M) + asn1cSequenceAdd(out->protocolIEs.list, NGAP_HandoverRequiredIEs_t, ie2); + ie2->id = NGAP_ProtocolIE_ID_id_RAN_UE_NGAP_ID; + ie2->criticality = NGAP_Criticality_reject; + ie2->value.present = NGAP_HandoverRequiredIEs__value_PR_RAN_UE_NGAP_ID; + ie2->value.choice.RAN_UE_NGAP_ID = msg->gNB_ue_ngap_id; + + // Handover Type (M) + asn1cSequenceAdd(out->protocolIEs.list, NGAP_HandoverRequiredIEs_t, ie3); + ie3->id = NGAP_ProtocolIE_ID_id_HandoverType; + ie3->criticality = NGAP_Criticality_reject; + ie3->value.present = NGAP_HandoverRequiredIEs__value_PR_HandoverType; + ie3->value.choice.HandoverType = msg->handoverType; + + // Cause (M) + asn1cSequenceAdd(out->protocolIEs.list, NGAP_HandoverRequiredIEs_t, ie4); + ie4->id = NGAP_ProtocolIE_ID_id_Cause; + ie4->criticality = NGAP_Criticality_ignore; + ie4->value.present = NGAP_HandoverRequiredIEs__value_PR_Cause; + encode_ngap_cause(&ie4->value.choice.Cause, &msg->cause); + + // Target ID (M) + asn1cSequenceAdd(out->protocolIEs.list, NGAP_HandoverRequiredIEs_t, ie5); + encode_ngap_target_id(ie5, &msg->target_gnb_id); + + // PDU Session Resource List (M) + asn1cSequenceAdd(out->protocolIEs.list, NGAP_HandoverRequiredIEs_t, ie6); + ie6->id = NGAP_ProtocolIE_ID_id_PDUSessionResourceListHORqd; + ie6->criticality = NGAP_Criticality_reject; + ie6->value.present = NGAP_HandoverRequiredIEs__value_PR_PDUSessionResourceListHORqd; + for (int i = 0; i < msg->nb_of_pdusessions; ++i) { + asn1cSequenceAdd(ie6->value.choice.PDUSessionResourceListHORqd.list, NGAP_PDUSessionResourceItemHORqd_t, hoRequiredPduSession); + // PDU Session ID (M) + hoRequiredPduSession->pDUSessionID = msg->pdusessions[i].pdusession_id; + // Handover Required Transfer (M) + NGAP_HandoverRequiredTransfer_t hoRequiredTransfer = {0}; + uint8_t ho_req_transfer_transparent_container_buffer[128] = {0}; + if (LOG_DEBUGFLAG(DEBUG_ASN1)) + xer_fprint(stdout, &asn_DEF_NGAP_HandoverRequiredTransfer, &hoRequiredTransfer); + asn_enc_rval_t enc_rval = aper_encode_to_buffer(&asn_DEF_NGAP_HandoverRequiredTransfer, + NULL, + &hoRequiredTransfer, + ho_req_transfer_transparent_container_buffer, + 128); + AssertFatal(enc_rval.encoded > 0, "ASN1 message encoding failed (%s, %lu)!\n", enc_rval.failed_type->name, enc_rval.encoded); + hoRequiredPduSession->handoverRequiredTransfer.buf = CALLOC(1, (enc_rval.encoded + 7) / 8); + memcpy(hoRequiredPduSession->handoverRequiredTransfer.buf, + ho_req_transfer_transparent_container_buffer, + (enc_rval.encoded + 7) / 8); + hoRequiredPduSession->handoverRequiredTransfer.size = (enc_rval.encoded + 7) / 8; + + ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_NGAP_HandoverRequiredTransfer, &hoRequiredTransfer); + } + + // Source NG-RAN Node to Target NG-RAN Node Transparent Container (M) + asn1cSequenceAdd(out->protocolIEs.list, NGAP_HandoverRequiredIEs_t, ie); + ie->id = NGAP_ProtocolIE_ID_id_SourceToTarget_TransparentContainer; + ie->criticality = NGAP_Criticality_reject; + ie->value.present = NGAP_HandoverRequiredIEs__value_PR_SourceToTarget_TransparentContainer; + + NGAP_SourceNGRANNode_ToTargetNGRANNode_TransparentContainer_t *source2target = calloc_or_fail(1, sizeof(*source2target)); + + // RRC Container (M) (HandoverPreparationInformation) + source2target->rRCContainer.size = msg->source2target->handoverInfo.len; + source2target->rRCContainer.buf = malloc_or_fail(msg->source2target->handoverInfo.len); + memcpy(source2target->rRCContainer.buf, msg->source2target->handoverInfo.buf, source2target->rRCContainer.size); + + // PDU Session Resource Information List (O) + asn1cCalloc(source2target->pDUSessionResourceInformationList, pduSessionList); + for (uint8_t i = 0; i < msg->nb_of_pdusessions; ++i) { + const pdusession_resource_t *pduSession = &msg->pdusessions[i]; + NGAP_DEBUG("Handover Required: preparing PDU Session Resource Information List for PDU Session ID %d\n", + pduSession->pdusession_id); + asn1cSequenceAdd(pduSessionList->list, NGAP_PDUSessionResourceInformationItem_t, item); + // PDU Session ID (M) + item->pDUSessionID = msg->source2target->pdu_session_resource[i].pdusession_id; + // QoS Flow Information List (M) + for (int q = 0; q < msg->source2target->pdu_session_resource[i].nb_of_qos_flow; ++q) { + asn1cSequenceAdd(item->qosFlowInformationList.list, NGAP_QosFlowInformationItem_t, qosFlowInfo); + qosFlowInfo->qosFlowIdentifier = msg->source2target->pdu_session_resource[i].qos_flow_info[q].qfi; + } + } + + // Target Cell ID (NG-RAN CGI) (M) + source2target->targetCell_ID.present = NGAP_NGRAN_CGI_PR_nR_CGI; + asn1cCalloc(source2target->targetCell_ID.choice.nR_CGI, tNrCGI); + encode_ngap_nr_cgi(tNrCGI, &msg->target_gnb_id.plmn_identity, msg->source2target->targetCellId.nrCellIdentity); + + // UE history Information (M) + asn1cSequenceAdd(source2target->uEHistoryInformation.list, NGAP_LastVisitedCellItem_t, lastVisitedCell); + lastVisitedCell->iE_Extensions = NULL; + // Last Visited Cell Information (M) + lastVisitedCell->lastVisitedCellInformation.present = NGAP_LastVisitedCellInformation_PR_nGRANCell; + // CHOICE (M): NG-RAN Cell + asn1cCalloc(lastVisitedCell->lastVisitedCellInformation.choice.nGRANCell, lastVisitedNR); + // Cell Type (M) + lastVisitedNR->cellType.cellSize = msg->source2target->ue_history_info.type; + // Global Cell ID (M) + lastVisitedNR->globalCellID.present = NGAP_NGRAN_CGI_PR_nR_CGI; + asn1cCalloc(lastVisitedNR->globalCellID.choice.nR_CGI, lastVisitedNrCGI); + cell_id_t *cell = &msg->source2target->ue_history_info.id; + encode_ngap_nr_cgi(lastVisitedNrCGI, &cell->plmn_identity, cell->nrCellIdentity); + // HO Cause Value (O) + if (msg->source2target->ue_history_info.cause) { + asn1cCalloc(lastVisitedNR->hOCauseValue, lastVisitedCause); + encode_ngap_cause(lastVisitedCause, msg->source2target->ue_history_info.cause); + } + // Time UE Stayed in Cell (M) + lastVisitedNR->timeUEStayedInCell = msg->source2target->ue_history_info.time_in_cell; + + if (LOG_DEBUGFLAG(DEBUG_ASN1)) + xer_fprint(stdout, &asn_DEF_NGAP_SourceNGRANNode_ToTargetNGRANNode_TransparentContainer, source2target); + + uint8_t source_to_target_transparent_container_buf[16384] = {0}; + asn_enc_rval_t enc_rval = aper_encode_to_buffer(&asn_DEF_NGAP_SourceNGRANNode_ToTargetNGRANNode_TransparentContainer, + NULL, + (void *)source2target, + (void *)&source_to_target_transparent_container_buf, + 16384); + ASN_STRUCT_FREE(asn_DEF_NGAP_SourceNGRANNode_ToTargetNGRANNode_TransparentContainer, source2target); + if (enc_rval.encoded < 0) { + AssertFatal(enc_rval.encoded > 0, + "HO LOG: Source to Transparent ASN1 message encoding failed (%s, %lu)!\n", + enc_rval.failed_type->name, + enc_rval.encoded); + return NULL; + } + + int total_bytes = (enc_rval.encoded + 7) / 8; + int ret = OCTET_STRING_fromBuf(&ie->value.choice.SourceToTarget_TransparentContainer, + (const char *)&source_to_target_transparent_container_buf, + total_bytes); + if (ret != 0) { + LOG_E(NR_RRC, "HO LOG: Can not perform OCTET_STRING_fromBuf for the SourceToTarget_TransparentContainer"); + return NULL; + } + + return pdu; +} diff --git a/openair3/NGAP/ngap_gNB_mobility_management.h b/openair3/NGAP/ngap_gNB_mobility_management.h new file mode 100644 index 0000000000..b3926877eb --- /dev/null +++ b/openair3/NGAP/ngap_gNB_mobility_management.h @@ -0,0 +1,31 @@ +/* + * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The OpenAirInterface Software Alliance licenses this file to You under + * the OAI Public License, Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.openairinterface.org/?page_id=698 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *------------------------------------------------------------------------------- + * For more information about the OpenAirInterface (OAI) Software Alliance: + * contact@openairinterface.org + */ + +#ifndef NGAP_GNB_MOBILITY_MANAGEMENT_H_ +#define NGAP_GNB_MOBILITY_MANAGEMENT_H_ + +#include +#include "common/platform_types.h" +#include "ngap_messages_types.h" + +NGAP_NGAP_PDU_t *encode_ng_handover_required(const ngap_handover_required_t *msg); + +#endif /* NGAP_GNB_MOBILITY_MANAGEMENT_H_ */ diff --git a/openair3/NGAP/ngap_msg_includes.h b/openair3/NGAP/ngap_msg_includes.h index 1debc2e9f1..cf7d0d9f49 100644 --- a/openair3/NGAP/ngap_msg_includes.h +++ b/openair3/NGAP/ngap_msg_includes.h @@ -78,5 +78,13 @@ #include "NGAP_Dynamic5QIDescriptor.h" #include "NGAP_PDUSessionResourceModifyRequestTransfer.h" #include "NGAP_QosFlowAddOrModifyRequestItem.h" +#include "NGAP_PDUSessionResourceItemHORqd.h" +#include "NGAP_PDUSessionResourceInformationItem.h" +#include "NGAP_QosFlowInformationItem.h" +#include "NGAP_LastVisitedCellItem.h" +#include "NGAP_LastVisitedNGRANCellInformation.h" +#include "NGAP_PDUSessionResourceInformationList.h" +#include "NGAP_HandoverRequiredTransfer.h" +#include "NGAP_SourceNGRANNode-ToTargetNGRANNode-TransparentContainer.h" #endif // NGAP_MSG_INCLUDES_H From 6e4f1b7b31a74505e412bfa243ac9daa992d1250 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Tue, 25 Mar 2025 10:09:49 +0100 Subject: [PATCH 29/52] Add N2 Handover decision and trigger preparation on source CU (1) add N2 ho decision based on measurements (2) add trigger for N2 HO on source CU (3) generate NG Handover Preparation Information message, including RRCReconfiguration, to be passed to the NG Handover Required message (4) call RRC callback for Handover Required message Co-authored-by: batuhan duyuler --- openair2/RRC/NR/MESSAGES/asn1_msg.c | 81 +++++++++++++++++++++++++++++ openair2/RRC/NR/MESSAGES/asn1_msg.h | 3 ++ openair2/RRC/NR/nr_rrc_proto.h | 3 ++ openair2/RRC/NR/rrc_gNB.c | 18 ++++--- openair2/RRC/NR/rrc_gNB_mobility.c | 66 +++++++++++++++++------ openair2/RRC/NR/rrc_gNB_mobility.h | 6 +++ 6 files changed, 155 insertions(+), 22 deletions(-) diff --git a/openair2/RRC/NR/MESSAGES/asn1_msg.c b/openair2/RRC/NR/MESSAGES/asn1_msg.c index 3ff6aecea9..8bd9716107 100644 --- a/openair2/RRC/NR/MESSAGES/asn1_msg.c +++ b/openair2/RRC/NR/MESSAGES/asn1_msg.c @@ -564,6 +564,43 @@ static NR_RRCReconfiguration_IEs_t *build_RRCReconfiguration_IEs(const nr_rrc_re return ie; } +static byte_array_t do_HO_RRCReconfiguration(nr_rrc_reconfig_param_t *params) +{ + NR_RRCReconfiguration_IEs_t *ie = build_RRCReconfiguration_IEs(params); + byte_array_t msg = {.buf = NULL, .len = 0}; + + // Create the standalone RRCReconfiguration message + NR_RRCReconfiguration_t rrcReconf = {0}; + rrcReconf.rrc_TransactionIdentifier = params->transaction_id; + rrcReconf.criticalExtensions.present = NR_RRCReconfiguration__criticalExtensions_PR_rrcReconfiguration; + rrcReconf.criticalExtensions.choice.rrcReconfiguration = ie; + + // Encode the message + int val = uper_encode_to_new_buffer(&asn_DEF_NR_RRCReconfiguration, NULL, &rrcReconf, (void **)&msg.buf); + if (val <= 0) { + LOG_E(NR_RRC, "ASN1 RRCReconfiguration message encoding failed\n"); + ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_NR_RRCReconfiguration, &rrcReconf); + return msg; + } + msg.len = val; + LOG_D(NR_RRC, "RRCReconfiguration: Encoded (%ld bytes)\n", msg.len); + + // don't free what we did not allocate, so set fields with pointers to NULL + // if memory comes from outside + ie->measConfig = NULL; + if (ie->radioBearerConfig) { + ie->radioBearerConfig->srb3_ToRelease = NULL; + ie->radioBearerConfig->drb_ToReleaseList = NULL; + ie->radioBearerConfig->drb_ToAddModList = NULL; + ie->radioBearerConfig->srb_ToAddModList = NULL; + ie->radioBearerConfig->securityConfig = NULL; + } + + ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_NR_RRCReconfiguration, &rrcReconf); + + return msg; +} + byte_array_t do_RRCReconfiguration(const nr_rrc_reconfig_param_t *params) { byte_array_t msg = {.buf = NULL, .len = 0}; @@ -1101,6 +1138,50 @@ NR_MeasConfig_t *get_MeasConfig(const NR_MeasTiming_t *mt, return mc; } +/** @brief HandoverPreparationInformation message (11.2.2 of TS 38.331) + * 1) generate RRCReconfiguration to be transferred + * 2) encodes UE Capabilities from UE Context + * 3) encodes AS context + * 4) generates HO Preparation Info */ +byte_array_t get_HandoverPreparationInformation(nr_rrc_reconfig_param_t *params, int scell_pci) +{ + // Buffer to return + byte_array_t buffer = {.buf = NULL, .len = 0}; + + /* Prepare handoverPreparationInformation IEs */ + NR_HandoverPreparationInformation_t hoPrepInfo = {0}; + hoPrepInfo.criticalExtensions.present = NR_HandoverPreparationInformation__criticalExtensions_PR_c1; + asn1cCalloc(hoPrepInfo.criticalExtensions.choice.c1, c1); + c1->present = NR_HandoverPreparationInformation__criticalExtensions__c1_PR_handoverPreparationInformation; + asn1cCalloc(c1->choice.handoverPreparationInformation, hpi); + + /* Decode stored NR_UE_CapabilityRAT_ContainerList and add to IEs */ + NR_UE_CapabilityRAT_ContainerList_t *clist = NULL; + asn_dec_rval_t dec_rval = + uper_decode(NULL, &asn_DEF_NR_UE_CapabilityRAT_ContainerList, (void **)&clist, params->ue_cap.buf, params->ue_cap.len, 0, 0); + if (dec_rval.code != RC_OK) { + LOG_W(NR_RRC, "Failed to decode UE capability container list for HandoverPreparationInformation, ignoring capabilities\n"); + return buffer; + } + hpi->ue_CapabilityRAT_List = *clist; + free(clist); + + /* sourceConfig: Encode RRCReconfiguration as used in the source cell */ + byte_array_t msg = do_HO_RRCReconfiguration(params); + asn1cCalloc(hpi->sourceConfig, sourceConfig); + OCTET_STRING_fromBuf(&sourceConfig->rrcReconfiguration, (const char *)msg.buf, msg.len); + free_byte_array(msg); + + if (LOG_DEBUGFLAG(DEBUG_ASN1)) + xer_fprint(stdout, &asn_DEF_NR_HandoverPreparationInformation, (void **)&hoPrepInfo); + + /* encode */ + buffer.len = uper_encode_to_new_buffer(&asn_DEF_NR_HandoverPreparationInformation, NULL, (void *)&hoPrepInfo, (void *)&buffer.buf); + AssertFatal(buffer.len > 0, "ASN1 message encoding failed (%ld)!\n", buffer.len); + ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_NR_HandoverPreparationInformation, &hoPrepInfo); + return buffer; +} + void free_MeasConfig(NR_MeasConfig_t *mc) { ASN_STRUCT_FREE(asn_DEF_NR_MeasConfig, mc); diff --git a/openair2/RRC/NR/MESSAGES/asn1_msg.h b/openair2/RRC/NR/MESSAGES/asn1_msg.h index dde62d142f..aab65f6685 100644 --- a/openair2/RRC/NR/MESSAGES/asn1_msg.h +++ b/openair2/RRC/NR/MESSAGES/asn1_msg.h @@ -69,6 +69,7 @@ typedef struct { NR_CellGroupConfig_t *cell_group_config; bool masterKeyUpdate; int nextHopChainingCount; + byte_array_t ue_cap; } nr_rrc_reconfig_param_t; /* @@ -161,4 +162,6 @@ NR_MeasConfig_t *get_MeasConfig(const NR_MeasTiming_t *mt, void free_MeasConfig(NR_MeasConfig_t *mc); int do_NR_Paging(uint8_t Mod_id, uint8_t *buffer, uint32_t tmsi); +byte_array_t get_HandoverPreparationInformation(nr_rrc_reconfig_param_t *params, int scell_pci); + #endif /* __RRC_NR_MESSAGES_ASN1_MSG__H__ */ diff --git a/openair2/RRC/NR/nr_rrc_proto.h b/openair2/RRC/NR/nr_rrc_proto.h index 0d675ff525..a1918bdd34 100644 --- a/openair2/RRC/NR/nr_rrc_proto.h +++ b/openair2/RRC/NR/nr_rrc_proto.h @@ -125,4 +125,7 @@ nr_rrc_reconfig_param_t get_RRCReconfiguration_params(gNB_RRC_INST *rrc, gNB_RRC pdusession_level_qos_parameter_t *get_qos_characteristics(const int qfi, rrc_pdu_session_param_t *pduSession); f1ap_qos_flow_param_t get_qos_char_from_qos_flow_param(const pdusession_level_qos_parameter_t *qos_param); void openair_rrc_gNB_configuration(gNB_RRC_INST *rrc, gNB_RrcConfigurationReq *configuration); +NR_SRB_ToAddModList_t *createSRBlist(gNB_RRC_UE_t *ue, uint8_t reestablish); +NR_DRB_ToAddModList_t *createDRBlist(gNB_RRC_UE_t *ue, bool reestablish); + #endif diff --git a/openair2/RRC/NR/rrc_gNB.c b/openair2/RRC/NR/rrc_gNB.c index 0e5912aafe..ad36b9c10d 100644 --- a/openair2/RRC/NR/rrc_gNB.c +++ b/openair2/RRC/NR/rrc_gNB.c @@ -307,7 +307,7 @@ unsigned int rrc_gNB_get_next_transaction_identifier(module_id_t gnb_mod_idP) * for the radio bearer changes, with some expections for SRB1 (i.e. when resuming * an RRC connection, or at the first reconfiguration after RRC connection * reestablishment in NR, do not re-establish PDCP) */ -static NR_SRB_ToAddModList_t *createSRBlist(gNB_RRC_UE_t *ue, uint8_t reestablish) +NR_SRB_ToAddModList_t *createSRBlist(gNB_RRC_UE_t *ue, uint8_t reestablish) { if (!ue->Srb[SRB1].Active) { LOG_E(NR_RRC, "Call SRB list while SRB1 doesn't exist\n"); @@ -328,7 +328,7 @@ static NR_SRB_ToAddModList_t *createSRBlist(gNB_RRC_UE_t *ue, uint8_t reestablis return list; } -static NR_DRB_ToAddModList_t *createDRBlist(gNB_RRC_UE_t *ue, bool reestablish) +NR_DRB_ToAddModList_t *createDRBlist(gNB_RRC_UE_t *ue, bool reestablish) { NR_DRB_ToAddMod_t *DRB_config = NULL; NR_DRB_ToAddModList_t *DRB_configList = CALLOC(sizeof(*DRB_configList), 1); @@ -349,7 +349,7 @@ static NR_DRB_ToAddModList_t *createDRBlist(gNB_RRC_UE_t *ue, bool reestablish) return DRB_configList; } -static void freeSRBlist(NR_SRB_ToAddModList_t *l) +void freeSRBlist(NR_SRB_ToAddModList_t *l) { ASN_STRUCT_FREE(asn_DEF_NR_SRB_ToAddModList, l); } @@ -1383,6 +1383,7 @@ static void process_Event_Based_Measurement_Report(gNB_RRC_INST *rrc, int servingCellRSRP = 0; int neighbourCellRSRP = 0; int scell_pci = -1; + int best_rsrp = -10000; switch (event_triggered->eventId.present) { case NR_EventTriggerConfig__eventId_PR_eventA2: @@ -1443,14 +1444,20 @@ static void process_Event_Based_Measurement_Report(gNB_RRC_INST *rrc, // No F1 connection but static neighbour configuration is available const nr_a3_event_t *a3_event_configuration = get_a3_configuration(rrc, neighbour->physicalCellId); // Additional check - This part can be modified according to additional cell specific Handover Margin + // a3-Offset: The actual value is field value * 0.5 dB. if (a3_event_configuration - && ((a3_event_configuration->a3_offset + a3_event_configuration->hysteresis) + && ((a3_event_configuration->a3_offset * 0.5 + a3_event_configuration->hysteresis) < (neighbourCellRSRP - servingCellRSRP))) { + if (neighbourCellRSRP > best_rsrp) { + // UE can send multiple neighbour cells A3 event report in 1 Meas Report. So, we need to find the best neighbour + best_rsrp = neighbourCellRSRP; + LOG_I(NR_RRC, "HO LOG: Serving Cell RSRP: %d - Best Neighbor RSRP: %d ! Trigger N2 HO\n", servingCellRSRP, best_rsrp); + nr_rrc_trigger_n2_ho(rrc, ue, scell_pci, neighbour); + } LOG_D(NR_RRC, "HO LOG: Trigger N2 HO for the neighbour gnb: %u cell: %lu\n", neighbour->gNB_ID, neighbour->nrcell_id); } } else if (neigh_cell && neighbour) { /* we know the cell and are connected to the DU! */ - gNB_RRC_INST *rrc = RC.nrrrc[0]; nr_rrc_du_container_t *source_du = get_du_by_cell_id(rrc, serving_cell->nr_cellid); DevAssert(source_du); nr_rrc_du_container_t *target_du = get_du_by_cell_id(rrc, neigh_cell->nr_cellid); @@ -1459,7 +1466,6 @@ static void process_Event_Based_Measurement_Report(gNB_RRC_INST *rrc, LOG_W(NR_RRC, "UE %d: received A3 event for stronger neighbor PCI %d, but no such neighbour in configuration\n", ue->rrc_ue_id, neighbour_pci); } } - } break; default: LOG_D(NR_RRC, "NR_EventTriggerConfig__eventId_PR_NOTHING or Other event report\n"); diff --git a/openair2/RRC/NR/rrc_gNB_mobility.c b/openair2/RRC/NR/rrc_gNB_mobility.c index fbccc637a7..fa2eead5ca 100644 --- a/openair2/RRC/NR/rrc_gNB_mobility.c +++ b/openair2/RRC/NR/rrc_gNB_mobility.c @@ -215,27 +215,12 @@ static void nr_initiate_handover(const gNB_RRC_INST *rrc, free_ue_context_setup_req(&ue_context_setup_req); } -/* -void nr_rrc_trigger_n2_ho(gNB_RRC_INST *rrc, int nr_cgi, uint8_t *ho_prep_info, uint32_t *ho_prep_len, void *pdu_session) -{ - // call from outside (via NGAP message), with the request for handover for a - // (any) UE. This handler should, in order: - // - // 1. look up the target DU =du= via get_du_by_cell_id(nr_cgi) - // (and reject the request if it can't find the DU) - // 2. create a context for a new UE =UE=, based on pdu_session information - // 3. call nr_initiate_handover(rrc, UE, NULL, du, ho_prep_info, ho_prep_len); - // - // after success of the DU, send the result via =success_ptr= to the source - // CU. -} -*/ - typedef struct deliver_ue_ctxt_modification_data_t { gNB_RRC_INST *rrc; f1ap_ue_context_mod_req_t *modification_req; sctp_assoc_t assoc_id; } deliver_ue_ctxt_modification_data_t; + static void rrc_deliver_ue_ctxt_modif_req(void *deliver_pdu_data, ue_id_t ue_id, int srb_id, char *buf, int size, int sdu_id) { DevAssert(deliver_pdu_data != NULL); @@ -399,3 +384,52 @@ void nr_HO_F1_trigger_telnet(gNB_RRC_INST *rrc, uint32_t rrc_ue_id) nr_rrc_trigger_f1_ho(rrc, ue, source_du, target_du); } + +/** @brief Generate the HandoverPreparationInformation to be carried + * in the RRC Container (9.3.1.29 of 3GPP TS 38.413) of the Source + * NG-RAN Node to Target NG-RAN Node Transparent Container IE */ +static byte_array_t rrc_gNB_generate_HandoverPreparationInformation(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue, int serving_pci) +{ + nr_rrc_reconfig_param_t params = get_RRCReconfiguration_params(rrc, ue, 0, false); + params.ue_cap = ue->ue_cap_buffer; + + byte_array_t hoPrepInfo = get_HandoverPreparationInformation(¶ms, serving_pci); + free_RRCReconfiguration_params(params); + + if (hoPrepInfo.len < 0) { + LOG_E(NR_RRC, "HandoverPreparationInformation generation failed for UE %d\n", ue->rrc_ue_id); + return hoPrepInfo; + } + + LOG_D(NR_RRC, "HO LOG: Handover Preparation for UE %lu Encoded (%zd bytes)\n", ue->amf_ue_ngap_id, hoPrepInfo.len); + + return hoPrepInfo; +} + +/** @brief Trigger N2 handover on source gNB: + * 1) Prepare RRC Container with HandoverPreparationInformation message + * 2) send NGAP Handover Required message */ +void nr_rrc_trigger_n2_ho(gNB_RRC_INST *rrc, + gNB_RRC_UE_t *ue, + int serving_pci, + const nr_neighbour_cell_t *neighbour_config) +{ + byte_array_t hoPrepInfo = rrc_gNB_generate_HandoverPreparationInformation(rrc, ue, serving_pci); + if (hoPrepInfo.len < 0) { + free_byte_array(hoPrepInfo); + LOG_E(NR_RRC, "Failed to trigger N2 handover on source gNB for UE %x\n", ue->rrc_ue_id); + return; + } + + // allocate context for source + if (ue->ho_context != NULL) { + LOG_E(NR_RRC, "Ongoing handover for UE %d, cannot trigger new\n", ue->rrc_ue_id); + return; + } + + ue->ho_context = alloc_ho_ctx(HO_CTX_SOURCE); + ue->ho_context->source->du = get_du_for_ue(rrc, ue->rrc_ue_id); + + rrc_gNB_send_NGAP_HANDOVER_REQUIRED(rrc, ue, neighbour_config, hoPrepInfo); + free_byte_array(hoPrepInfo); +} diff --git a/openair2/RRC/NR/rrc_gNB_mobility.h b/openair2/RRC/NR/rrc_gNB_mobility.h index a907b3c472..3169ea23c0 100644 --- a/openair2/RRC/NR/rrc_gNB_mobility.h +++ b/openair2/RRC/NR/rrc_gNB_mobility.h @@ -23,6 +23,7 @@ #include #include "common/utils/ds/byte_array.h" +#include "nr_rrc_defs.h" /* forward declarations */ typedef struct gNB_RRC_INST_s gNB_RRC_INST; @@ -78,4 +79,9 @@ nr_handover_context_t *alloc_ho_ctx(ho_ctx_type_t type); void nr_rrc_trigger_f1_ho(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue, nr_rrc_du_container_t *source_du, nr_rrc_du_container_t *target_du); void nr_rrc_finalize_ho(gNB_RRC_UE_t *ue); +void nr_rrc_trigger_n2_ho(gNB_RRC_INST *rrc, + gNB_RRC_UE_t *ue, + int serving_pci, + const nr_neighbour_cell_t *neighbour_config); + #endif /* RRC_GNB_MOBILITY_H_ */ From 92abefb815a4f7067b9f0e93a8e0445fb750724e Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Fri, 7 Mar 2025 12:15:20 +0100 Subject: [PATCH 30/52] Add NGAP Handover Failure message This message is sent from the Target NG-RAN to the AMF. The commit introduces: * NGAP encoding function * N2 callback for the target gNB * handle the failure in RRC, inform NGAP * send NGAP message to AMF via SCTP Note: UE context in NGAP is fetched from the amf_ue_ngap_id Co-authored-by: batuhan duyuler --- openair2/COMMON/ngap_messages_def.h | 1 + openair2/COMMON/ngap_messages_types.h | 10 +++++ openair2/RRC/NR/rrc_gNB_NGAP.c | 10 ++++- openair2/RRC/NR/rrc_gNB_NGAP.h | 2 + openair2/RRC/NR/rrc_gNB_mobility.c | 13 ++++++ openair2/RRC/NR/rrc_gNB_mobility.h | 2 + openair3/NGAP/ngap_gNB.c | 44 ++++++++++++++++++++ openair3/NGAP/ngap_gNB_mobility_management.c | 33 +++++++++++++++ openair3/NGAP/ngap_gNB_mobility_management.h | 1 + openair3/NGAP/ngap_gNB_ue_context.c | 6 +++ openair3/NGAP/ngap_gNB_ue_context.h | 1 + 11 files changed, 122 insertions(+), 1 deletion(-) diff --git a/openair2/COMMON/ngap_messages_def.h b/openair2/COMMON/ngap_messages_def.h index eea62d6c00..9cf975d34a 100644 --- a/openair2/COMMON/ngap_messages_def.h +++ b/openair2/COMMON/ngap_messages_def.h @@ -72,6 +72,7 @@ MESSAGE_DEF(NGAP_PDUSESSION_SETUP_RESP , MESSAGE_PRIORITY_MED, ngap_pdu MESSAGE_DEF(NGAP_PDUSESSION_MODIFY_RESP , MESSAGE_PRIORITY_MED, ngap_pdusession_modify_resp_t , ngap_pdusession_modify_resp) MESSAGE_DEF(NGAP_PDUSESSION_RELEASE_RESPONSE , MESSAGE_PRIORITY_MED, ngap_pdusession_release_resp_t , ngap_pdusession_release_resp) MESSAGE_DEF(NGAP_HANDOVER_REQUIRED, MESSAGE_PRIORITY_MED, ngap_handover_required_t, ngap_handover_required) +MESSAGE_DEF(NGAP_HANDOVER_FAILURE, MESSAGE_PRIORITY_MED, ngap_handover_failure_t, ngap_handover_failure) /* NGAP -> RRC messages */ MESSAGE_DEF(NGAP_DOWNLINK_NAS , MESSAGE_PRIORITY_MED, ngap_downlink_nas_t , ngap_downlink_nas ) diff --git a/openair2/COMMON/ngap_messages_types.h b/openair2/COMMON/ngap_messages_types.h index 57ecc51e79..9a3e7a9a45 100644 --- a/openair2/COMMON/ngap_messages_types.h +++ b/openair2/COMMON/ngap_messages_types.h @@ -66,6 +66,8 @@ #define NGAP_PDUSESSION_MODIFY_REQ(mSGpTR) (mSGpTR)->ittiMsg.ngap_pdusession_modify_req #define NGAP_PAGING_IND(mSGpTR) (mSGpTR)->ittiMsg.ngap_paging_ind #define NGAP_HANDOVER_REQUIRED(mSGpTR) (mSGpTR)->ittiMsg.ngap_handover_required +#define NGAP_HANDOVER_FAILURE(mSGpTR) (mSGpTR)->ittiMsg.ngap_handover_failure + #define NGAP_UE_CONTEXT_RELEASE_REQ(mSGpTR) (mSGpTR)->ittiMsg.ngap_ue_release_req #define NGAP_PDUSESSION_RELEASE_COMMAND(mSGpTR) (mSGpTR)->ittiMsg.ngap_pdusession_release_command #define NGAP_PDUSESSION_RELEASE_RESPONSE(mSGpTR) (mSGpTR)->ittiMsg.ngap_pdusession_release_resp @@ -548,6 +550,14 @@ typedef struct { source_to_target_transparent_container_t *source2target; } ngap_handover_required_t; +/* 3GPP TS 38.413 9.2.3.6 */ +typedef struct { + // AMF UE NGAP ID (M) + uint64_t amf_ue_ngap_id; + // Cause (M) + ngap_cause_t cause; +} ngap_handover_failure_t; + typedef struct ngap_ue_cap_info_ind_s { uint32_t gNB_ue_ngap_id; byte_array_t ue_radio_cap; diff --git a/openair2/RRC/NR/rrc_gNB_NGAP.c b/openair2/RRC/NR/rrc_gNB_NGAP.c index 5632e5932b..9b4096da4b 100644 --- a/openair2/RRC/NR/rrc_gNB_NGAP.c +++ b/openair2/RRC/NR/rrc_gNB_NGAP.c @@ -1063,7 +1063,15 @@ int rrc_gNB_process_NGAP_UE_CONTEXT_RELEASE_REQ(MessageDef *msg_p, instance_t in } } -//----------------------------------------------------------------------------- +/** @brief Sends the NG Handover Failure from the Target NG-RAN to the AMF */ +void rrc_gNB_send_NGAP_HANDOVER_FAILURE(gNB_RRC_INST *rrc, ngap_handover_failure_t *msg) +{ + LOG_I(NR_RRC, "Send NG Handover Failure message (amf_ue_ngap_id %ld) with cause %d \n ", msg->amf_ue_ngap_id, msg->cause.value); + MessageDef *msg_p = itti_alloc_new_message(TASK_RRC_GNB, 0, NGAP_HANDOVER_FAILURE); + NGAP_HANDOVER_FAILURE(msg_p) = *msg; + itti_send_msg_to_task(TASK_NGAP, rrc->module_id, msg_p); +} + /* * Process the NG command NGAP_UE_CONTEXT_RELEASE_COMMAND, sent by AMF. * The gNB should remove all pdu session, NG context, and other context of the UE. diff --git a/openair2/RRC/NR/rrc_gNB_NGAP.h b/openair2/RRC/NR/rrc_gNB_NGAP.h index fd8cb31ff1..7c52843c0f 100644 --- a/openair2/RRC/NR/rrc_gNB_NGAP.h +++ b/openair2/RRC/NR/rrc_gNB_NGAP.h @@ -94,4 +94,6 @@ void rrc_gNB_send_NGAP_HANDOVER_REQUIRED(gNB_RRC_INST *rrc, const nr_neighbour_cell_t *neighbourCellConfiguration, const byte_array_t hoPrepInfo); +void rrc_gNB_send_NGAP_HANDOVER_FAILURE(gNB_RRC_INST *rrc, ngap_handover_failure_t *msg); + #endif diff --git a/openair2/RRC/NR/rrc_gNB_mobility.c b/openair2/RRC/NR/rrc_gNB_mobility.c index fa2eead5ca..7b1324d6e5 100644 --- a/openair2/RRC/NR/rrc_gNB_mobility.c +++ b/openair2/RRC/NR/rrc_gNB_mobility.c @@ -406,6 +406,19 @@ static byte_array_t rrc_gNB_generate_HandoverPreparationInformation(gNB_RRC_INST return hoPrepInfo; } +/** @brief Callback function to trigger NG Handover Failure on the target gNB, to inform the AMF + * that the preparation of resources has failed (e.g. unsatisfied criteria, gNB is already loaded). + * This message represents an Unsuccessful Outcome of the Handover Resource Allocation */ +void nr_rrc_n2_ho_failure(gNB_RRC_INST *rrc, uint32_t gnb_ue_id, ngap_handover_failure_t *msg) +{ + LOG_I(NR_RRC, "Triggering N2 Handover Failure\n"); + rrc_gNB_send_NGAP_HANDOVER_FAILURE(rrc, msg); + LOG_I(NR_RRC, "Send UE Context Release for gnb_ue_id %d\n", gnb_ue_id); + rrc_gNB_ue_context_t *ue_context_p = rrc_gNB_get_ue_context(rrc, gnb_ue_id); + rrc_gNB_send_NGAP_UE_CONTEXT_RELEASE_REQ(rrc->module_id, ue_context_p, msg->cause); + return; +} + /** @brief Trigger N2 handover on source gNB: * 1) Prepare RRC Container with HandoverPreparationInformation message * 2) send NGAP Handover Required message */ diff --git a/openair2/RRC/NR/rrc_gNB_mobility.h b/openair2/RRC/NR/rrc_gNB_mobility.h index 3169ea23c0..0bb5faf7fa 100644 --- a/openair2/RRC/NR/rrc_gNB_mobility.h +++ b/openair2/RRC/NR/rrc_gNB_mobility.h @@ -53,6 +53,7 @@ typedef struct nr_ho_source_cu { /* acknowledgement of handover request. buf+len is the RRC Reconfiguration */ typedef void (*ho_req_ack_t)(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue); typedef void (*ho_success_t)(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue); + typedef struct nr_ho_target_cu { /// pointer to the (target) DU structure const nr_rrc_du_container_t *du; @@ -78,6 +79,7 @@ nr_handover_context_t *alloc_ho_ctx(ho_ctx_type_t type); void nr_rrc_trigger_f1_ho(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue, nr_rrc_du_container_t *source_du, nr_rrc_du_container_t *target_du); void nr_rrc_finalize_ho(gNB_RRC_UE_t *ue); +void nr_rrc_n2_ho_failure(gNB_RRC_INST *rrc, uint32_t gnb_ue_id, ngap_handover_failure_t *msg); void nr_rrc_trigger_n2_ho(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue, diff --git a/openair3/NGAP/ngap_gNB.c b/openair3/NGAP/ngap_gNB.c index 4b4fc2f850..58511f307c 100644 --- a/openair3/NGAP/ngap_gNB.c +++ b/openair3/NGAP/ngap_gNB.c @@ -334,6 +334,46 @@ void ngap_gNB_init(void) { itti_mark_task_ready(TASK_NGAP); } +static int ngap_gNB_handover_failure(instance_t instance, const ngap_handover_failure_t *msg) +{ + ngap_gNB_ue_context_t *ue_context_p = NULL; + uint8_t *buffer = NULL; + uint32_t length; + + ngap_gNB_instance_t *ngap_gNB_instance_p = ngap_gNB_get_instance(instance); + DevAssert(msg != NULL); + DevAssert(ngap_gNB_instance_p != NULL); + + NGAP_NGAP_PDU_t *pdu = encode_ng_handover_failure(msg); + if (!pdu) { + NGAP_ERROR("Failed to encode NG Handover Failure\n"); + ASN_STRUCT_FREE(asn_DEF_NGAP_NGAP_PDU, pdu); + return -1; + } + + if (ngap_gNB_encode_pdu(pdu, &buffer, &length) < 0) { + NGAP_ERROR("Failed to encode HANDOVER FAILURE MESSAGE\n"); + ASN_STRUCT_FREE(asn_DEF_NGAP_NGAP_PDU, pdu); + return -1; + } + + ngap_gNB_amf_data_t *amf = ngap_gNB_get_AMF_from_instance(ngap_gNB_instance_p); + if ((ue_context_p = ngap_get_ue_context_from_amf_ue_ngap_id(msg->amf_ue_ngap_id)) == NULL) { + //The context for this gNB ue ngap id doesn't exist in the map of gNB UEs + NGAP_WARN("Failed to find UE context associated with amf_ue_ngap_id=%ld\n", msg->amf_ue_ngap_id); + /*In this case there is no ue context since HO REQ might be failed at preprocessing stage itself. + * so sctp message send to AMF based on its assoc_id */ + ngap_gNB_itti_send_sctp_data_req(ngap_gNB_instance_p->instance, amf->assoc_id, buffer, length, 0); + ASN_STRUCT_FREE(asn_DEF_NGAP_NGAP_PDU, pdu); + return -1; + } + + ngap_gNB_itti_send_sctp_data_req(ngap_gNB_instance_p->instance, amf->assoc_id, buffer, length, amf->nextstream); + ASN_STRUCT_FREE(asn_DEF_NGAP_NGAP_PDU, &pdu); + + return 0; +} + void *ngap_gNB_process_itti_msg(void *notUsed) { MessageDef *received_msg = NULL; int result; @@ -415,6 +455,10 @@ void *ngap_gNB_process_itti_msg(void *notUsed) { } break; + case NGAP_HANDOVER_FAILURE: + ngap_gNB_handover_failure(instance, &NGAP_HANDOVER_FAILURE(received_msg)); + break; + default: NGAP_ERROR("Received unhandled message: %d:%s\n", ITTI_MSG_ID(received_msg), ITTI_MSG_NAME(received_msg)); break; diff --git a/openair3/NGAP/ngap_gNB_mobility_management.c b/openair3/NGAP/ngap_gNB_mobility_management.c index 4f116c8f34..9d84fccd09 100644 --- a/openair3/NGAP/ngap_gNB_mobility_management.c +++ b/openair3/NGAP/ngap_gNB_mobility_management.c @@ -191,3 +191,36 @@ NGAP_NGAP_PDU_t *encode_ng_handover_required(const ngap_handover_required_t *msg return pdu; } + +NGAP_NGAP_PDU_t *encode_ng_handover_failure(const ngap_handover_failure_t *msg) +{ + NGAP_NGAP_PDU_t *pdu = malloc_or_fail(sizeof(*pdu)); + + /* Prepare the NGAP message to encode */ + pdu->present = NGAP_NGAP_PDU_PR_unsuccessfulOutcome; + asn1cCalloc(pdu->choice.unsuccessfulOutcome, head); + head->procedureCode = NGAP_ProcedureCode_id_HandoverResourceAllocation; + head->criticality = NGAP_Criticality_reject; + head->value.present = NGAP_UnsuccessfulOutcome__value_PR_HandoverFailure; + NGAP_HandoverFailure_t *out = &head->value.choice.HandoverFailure; + + // AMF_UE_NGAP_ID (M) + { + asn1cSequenceAdd(out->protocolIEs.list, NGAP_HandoverFailureIEs_t, ie); + ie->id = NGAP_ProtocolIE_ID_id_AMF_UE_NGAP_ID; + ie->criticality = NGAP_Criticality_reject; + ie->value.present = NGAP_HandoverFailureIEs__value_PR_AMF_UE_NGAP_ID; + asn_uint642INTEGER(&ie->value.choice.AMF_UE_NGAP_ID, msg->amf_ue_ngap_id); + } + + // Cause (M) + { + asn1cSequenceAdd(out->protocolIEs.list, NGAP_HandoverFailureIEs_t, ie); + ie->id = NGAP_ProtocolIE_ID_id_Cause; + ie->criticality = NGAP_Criticality_ignore; + ie->value.present = NGAP_HandoverFailureIEs__value_PR_Cause; + encode_ngap_cause(&ie->value.choice.Cause, &msg->cause); + } + + return pdu; +} diff --git a/openair3/NGAP/ngap_gNB_mobility_management.h b/openair3/NGAP/ngap_gNB_mobility_management.h index b3926877eb..6f5cdde742 100644 --- a/openair3/NGAP/ngap_gNB_mobility_management.h +++ b/openair3/NGAP/ngap_gNB_mobility_management.h @@ -27,5 +27,6 @@ #include "ngap_messages_types.h" NGAP_NGAP_PDU_t *encode_ng_handover_required(const ngap_handover_required_t *msg); +NGAP_NGAP_PDU_t *encode_ng_handover_failure(const ngap_handover_failure_t *msg); #endif /* NGAP_GNB_MOBILITY_MANAGEMENT_H_ */ diff --git a/openair3/NGAP/ngap_gNB_ue_context.c b/openair3/NGAP/ngap_gNB_ue_context.c index d0949b88c2..994271d822 100644 --- a/openair3/NGAP/ngap_gNB_ue_context.c +++ b/openair3/NGAP/ngap_gNB_ue_context.c @@ -76,6 +76,12 @@ struct ngap_gNB_ue_context_s *ngap_get_ue_context(uint32_t gNB_ue_ngap_id) return RB_FIND(ngap_ue_map, &ngap_ue_head, &temp); } +ngap_gNB_ue_context_t *ngap_get_ue_context_from_amf_ue_ngap_id(uint32_t amf_ue_ngap_id) +{ + ngap_gNB_ue_context_t temp = {.amf_ue_ngap_id = amf_ue_ngap_id}; + return RB_FIND(ngap_ue_map, &ngap_ue_head, &temp); +} + struct ngap_gNB_ue_context_s *ngap_detach_ue_context(uint32_t gNB_ue_ngap_id) { struct ngap_gNB_ue_context_s *tmp = ngap_get_ue_context(gNB_ue_ngap_id); diff --git a/openair3/NGAP/ngap_gNB_ue_context.h b/openair3/NGAP/ngap_gNB_ue_context.h index aaf9e83bbc..1022f2f4c3 100644 --- a/openair3/NGAP/ngap_gNB_ue_context.h +++ b/openair3/NGAP/ngap_gNB_ue_context.h @@ -81,6 +81,7 @@ typedef struct ngap_gNB_ue_context_s { void ngap_store_ue_context(const ngap_gNB_ue_context_t *ue_desc_p); ngap_gNB_ue_context_t *ngap_get_ue_context(uint32_t gNB_ue_ngap_id); +ngap_gNB_ue_context_t *ngap_get_ue_context_from_amf_ue_ngap_id(uint32_t amf_ue_ngap_id); ngap_gNB_ue_context_t *ngap_detach_ue_context(uint32_t gNB_ue_ngap_id); #endif /* NGAP_GNB_UE_CONTEXT_H_ */ From 461cfb7911828ce1db9a716a206d428dd9a96200 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Tue, 11 Mar 2025 15:37:07 +0100 Subject: [PATCH 31/52] Add NGAP Handover Request decoding and RRC handler This commit introduces the NG Mobility Management Procedure known as Handover Resource Allocation. The inbound message is sent by the AMF and is known as Handover Request. Major changes: (0) Add Handover Resource Allocation initiating message decoding * add case for NGAP initiating message and unsuccessful message decoding (1) handle Handover Request on the target NG-RAN (2) decode NG Handover Request message (3) process payload, e.g. create UE context, store IDs, set UP security and trigger bearer setup Co-authored-by: batuhan duyuler --- openair2/COMMON/ngap_messages_def.h | 1 + openair2/COMMON/ngap_messages_types.h | 68 ++++++-- openair2/RRC/NR/nr_rrc_proto.h | 1 + openair2/RRC/NR/rrc_gNB.c | 7 +- openair2/RRC/NR/rrc_gNB_NGAP.c | 101 +++++++++++- openair2/RRC/NR/rrc_gNB_NGAP.h | 3 + openair2/RRC/NR/rrc_gNB_mobility.c | 2 + openair2/RRC/NR/rrc_gNB_mobility.h | 2 + openair3/NGAP/ngap_common.c | 130 +++++++++++++++ openair3/NGAP/ngap_common.h | 3 + openair3/NGAP/ngap_gNB_decoder.c | 6 + openair3/NGAP/ngap_gNB_encoder.c | 16 +- openair3/NGAP/ngap_gNB_handlers.c | 115 ++++---------- openair3/NGAP/ngap_gNB_mobility_management.c | 158 +++++++++++++++++++ openair3/NGAP/ngap_gNB_mobility_management.h | 1 + openair3/NGAP/ngap_msg_includes.h | 15 ++ 16 files changed, 525 insertions(+), 104 deletions(-) diff --git a/openair2/COMMON/ngap_messages_def.h b/openair2/COMMON/ngap_messages_def.h index 9cf975d34a..53b16350e1 100644 --- a/openair2/COMMON/ngap_messages_def.h +++ b/openair2/COMMON/ngap_messages_def.h @@ -83,6 +83,7 @@ MESSAGE_DEF(NGAP_PDUSESSION_SETUP_REQ , MESSAGE_PRIORITY_MED, ngap_pd MESSAGE_DEF(NGAP_PDUSESSION_MODIFY_REQ , MESSAGE_PRIORITY_MED, ngap_pdusession_modify_req_t , ngap_pdusession_modify_req ) MESSAGE_DEF(NGAP_PDUSESSION_RELEASE_COMMAND , MESSAGE_PRIORITY_MED, ngap_pdusession_release_command_t , ngap_pdusession_release_command) MESSAGE_DEF(NGAP_UE_CONTEXT_RELEASE_COMMAND, MESSAGE_PRIORITY_MED, ngap_ue_release_command_t , ngap_ue_release_command) +MESSAGE_DEF(NGAP_HANDOVER_REQUEST, MESSAGE_PRIORITY_MED, ngap_handover_request_t, ngap_handover_request) /* NGAP <-> RRC messages (can be initiated either by MME or gNB) */ MESSAGE_DEF(NGAP_UE_CONTEXT_RELEASE_REQ , MESSAGE_PRIORITY_MED, ngap_ue_release_req_t , ngap_ue_release_req) diff --git a/openair2/COMMON/ngap_messages_types.h b/openair2/COMMON/ngap_messages_types.h index 9a3e7a9a45..49212b257c 100644 --- a/openair2/COMMON/ngap_messages_types.h +++ b/openair2/COMMON/ngap_messages_types.h @@ -67,6 +67,7 @@ #define NGAP_PAGING_IND(mSGpTR) (mSGpTR)->ittiMsg.ngap_paging_ind #define NGAP_HANDOVER_REQUIRED(mSGpTR) (mSGpTR)->ittiMsg.ngap_handover_required #define NGAP_HANDOVER_FAILURE(mSGpTR) (mSGpTR)->ittiMsg.ngap_handover_failure +#define NGAP_HANDOVER_REQUEST(mSGpTR) (mSGpTR)->ittiMsg.ngap_handover_request #define NGAP_UE_CONTEXT_RELEASE_REQ(mSGpTR) (mSGpTR)->ittiMsg.ngap_ue_release_req #define NGAP_PDUSESSION_RELEASE_COMMAND(mSGpTR) (mSGpTR)->ittiMsg.ngap_pdusession_release_command @@ -229,6 +230,15 @@ typedef struct ngap_mobility_restriction_s{ plmn_id_t serving_plmn; }ngap_mobility_restriction_t; +/* PDU Session Resource Setup Request Transfer (9.3.4.1 3GPP TS 38.413) */ +typedef struct { + uint8_t nb_qos; + pdusession_level_qos_parameter_t qos[QOSFLOW_MAX_VALUE]; + pdu_session_type_t pdu_session_type; + // UPF endpoint of the NG-U (N3) transport bearer + gtpu_tunnel_t n3_incoming; +} pdusession_transfer_t; + typedef enum pdusession_qosflow_mapping_ind_e{ QOSFLOW_MAPPING_INDICATION_UL = 0, QOSFLOW_MAPPING_INDICATION_DL = 1, @@ -558,6 +568,55 @@ typedef struct { ngap_cause_t cause; } ngap_handover_failure_t; +typedef struct { + // Next-Hop NH + uint8_t next_hop[SECURITY_KEY_LENGTH]; + // Next Hop Chaining Count + uint8_t next_hop_chain_count; +} ngap_security_context_t; + +/* Handover Request (3GPP TS 38.413 9.2.3.4) + PDU Session Resource Setup Item */ +typedef struct { + // PDU Session ID + uint8_t pdusession_id; + pdu_session_type_t pdu_session_type; + // S-NSSAI + nssai_t nssai; + // Handover Required Transfer + pdusession_transfer_t pdusessionTransfer; +} ho_request_pdusession_t; + +/* 3GPP TS 38.413 9.2.3.4 */ +typedef struct { + // AMF UE NGAP ID + uint64_t amf_ue_ngap_id; + // Handover Type + ho_type_t ho_type; + // Cause + ngap_cause_t cause; + // UE Aggregate Maximum Bit Rate + ngap_ambr_t ue_ambr; + // UE Security Capabilities + ngap_security_capabilities_t security_capabilities; + // Security Context + ngap_security_context_t security_context; + // PDU Session Resource Setup List + uint8_t nb_of_pdusessions; + ho_request_pdusession_t pduSessionResourceSetupList[NGAP_MAX_PDU_SESSION]; + // Allowed NSSAI + uint8_t nb_allowed_nssais; + nssai_t allowed_nssai[8]; + // Source to Target Transparent Container contents + uint64_t nr_cell_id; + byte_array_t ue_ho_prep_info; + byte_array_t ue_cap; + // Mobility Restriction List + ngap_mobility_restriction_t *mobility_restriction; + // GUAMI + nr_guami_t guami; +} ngap_handover_request_t; + typedef struct ngap_ue_cap_info_ind_s { uint32_t gNB_ue_ngap_id; byte_array_t ue_radio_cap; @@ -628,15 +687,6 @@ typedef struct ngap_downlink_nas_s { byte_array_t nas_pdu; } ngap_downlink_nas_t; -/* PDU Session Resource Setup Request Transfer (9.3.4.1 3GPP TS 38.413) */ -typedef struct { - uint8_t nb_qos; - pdusession_level_qos_parameter_t qos[QOSFLOW_MAX_VALUE]; - pdu_session_type_t pdu_session_type; - // UPF endpoint of the NG-U (N3) transport bearer - gtpu_tunnel_t n3_incoming; -} pdusession_transfer_t; - /* PDU Session Resource Setup/Modify Request Item */ typedef struct { int pdusession_id; diff --git a/openair2/RRC/NR/nr_rrc_proto.h b/openair2/RRC/NR/nr_rrc_proto.h index a1918bdd34..bbaaaf5774 100644 --- a/openair2/RRC/NR/nr_rrc_proto.h +++ b/openair2/RRC/NR/nr_rrc_proto.h @@ -127,5 +127,6 @@ f1ap_qos_flow_param_t get_qos_char_from_qos_flow_param(const pdusession_level_qo void openair_rrc_gNB_configuration(gNB_RRC_INST *rrc, gNB_RrcConfigurationReq *configuration); NR_SRB_ToAddModList_t *createSRBlist(gNB_RRC_UE_t *ue, uint8_t reestablish); NR_DRB_ToAddModList_t *createDRBlist(gNB_RRC_UE_t *ue, bool reestablish); +void activate_srb(gNB_RRC_UE_t *UE, int srb_id); #endif diff --git a/openair2/RRC/NR/rrc_gNB.c b/openair2/RRC/NR/rrc_gNB.c index ad36b9c10d..48ea8e760e 100644 --- a/openair2/RRC/NR/rrc_gNB.c +++ b/openair2/RRC/NR/rrc_gNB.c @@ -354,7 +354,7 @@ void freeSRBlist(NR_SRB_ToAddModList_t *l) ASN_STRUCT_FREE(asn_DEF_NR_SRB_ToAddModList, l); } -static void activate_srb(gNB_RRC_UE_t *UE, int srb_id) +void activate_srb(gNB_RRC_UE_t *UE, int srb_id) { AssertFatal(srb_id == 1 || srb_id == 2, "handling only SRB 1 or 2\n"); if (UE->Srb[srb_id].Active == 1) { @@ -2944,6 +2944,11 @@ void *rrc_gnb_task(void *args_p) { rrc_gNB_process_PAGING_IND(msg_p, instance); break; + case NGAP_HANDOVER_REQUEST: + rrc_gNB_process_Handover_Request(RC.nrrrc[instance], instance, &NGAP_HANDOVER_REQUEST(msg_p)); + rrc_gNB_free_Handover_Request(&NGAP_HANDOVER_REQUEST(msg_p)); // Free transfered NG message + break; + default: LOG_E(NR_RRC, "[gNB %ld] Received unexpected message %s\n", instance, msg_name_p); break; diff --git a/openair2/RRC/NR/rrc_gNB_NGAP.c b/openair2/RRC/NR/rrc_gNB_NGAP.c index 9b4096da4b..68aa502f77 100644 --- a/openair2/RRC/NR/rrc_gNB_NGAP.c +++ b/openair2/RRC/NR/rrc_gNB_NGAP.c @@ -83,6 +83,8 @@ #include "rrc_messages_types.h" #include "s1ap_messages_types.h" #include "uper_encoder.h" +#include "rrc_gNB_mobility.h" +#include "rrc_gNB_du.h" #ifdef E2_AGENT #include "openair2/E2AP/RAN_FUNCTION/O-RAN/ran_func_rc_extern.h" @@ -163,18 +165,24 @@ static nr_guami_t get_guami(const uint32_t amf_Id, const plmn_id_t plmn) return guami; } +/** @brief Copy NGAP PDU Session Transfer item to RRC pdusession_t struct */ +static void cp_pdusession_transfer_to_pdusession(pdusession_t *dst, const pdusession_transfer_t *src) +{ + dst->nb_qos = src->nb_qos; + for (uint8_t i = 0; i < src->nb_qos && i < QOSFLOW_MAX_VALUE; ++i) { + dst->qos[i] = src->qos[i]; + } + dst->pdu_session_type = src->pdu_session_type; + dst->n3_incoming = src->n3_incoming; +} + /** @brief Copy NGAP PDU Session Resource item to RRC pdusession_t struct */ static void cp_pdusession_resource_item_to_pdusession(pdusession_t *dst, const pdusession_resource_item_t *src) { dst->pdusession_id = src->pdusession_id; dst->nas_pdu = src->nas_pdu; - dst->nb_qos = src->pdusessionTransfer.nb_qos; - for (uint8_t i = 0; i < src->pdusessionTransfer.nb_qos && i < QOSFLOW_MAX_VALUE; ++i) { - dst->qos[i] = src->pdusessionTransfer.qos[i]; - } - dst->pdu_session_type = src->pdusessionTransfer.pdu_session_type; - dst->n3_incoming = src->pdusessionTransfer.n3_incoming; dst->nssai = src->nssai; + cp_pdusession_transfer_to_pdusession(dst, &src->pdusessionTransfer); } /** @@ -1072,6 +1080,87 @@ void rrc_gNB_send_NGAP_HANDOVER_FAILURE(gNB_RRC_INST *rrc, ngap_handover_failure itti_send_msg_to_task(TASK_NGAP, rrc->module_id, msg_p); } +/** @brief Process NG Handover Request message (8.4.2.2 3GPP TS 38.413) */ +int rrc_gNB_process_Handover_Request(gNB_RRC_INST *rrc, instance_t instance, ngap_handover_request_t *msg) +{ + LOG_I(NR_RRC, "Received Handover Request (on PCI=%lu) \n", msg->nr_cell_id); + struct nr_rrc_du_container_t *du = get_du_by_cell_id(rrc, msg->nr_cell_id); + if (du == NULL) { + /* Cell Not Found! Return HO Request Failure*/ + LOG_E(RRC, "Failed to process Handover Request: no DU found with PCI=%lu \n", msg->nr_cell_id); + ngap_handover_failure_t fail = { + .amf_ue_ngap_id = msg->amf_ue_ngap_id, + .cause.type = NGAP_CAUSE_RADIO_NETWORK, + .cause.value = NGAP_CAUSE_RADIO_NETWORK_RADIO_RESOURCES_NOT_AVAILABLE, + }; + rrc_gNB_send_NGAP_HANDOVER_FAILURE(rrc, &fail); + return -1; + } + + // Create UE context + sctp_assoc_t curr_assoc_id = du->assoc_id; + rrc_gNB_ue_context_t *ue_context_p = rrc_gNB_create_ue_context(curr_assoc_id, UINT16_MAX, rrc, UINT64_MAX, UINT32_MAX); + gNB_RRC_UE_t *UE = &ue_context_p->ue_context; + + // allocate context for target + if (UE->ho_context != NULL) { + LOG_E(NR_RRC, "Ongoing handover for UE %d, cannot trigger new\n", UE->rrc_ue_id); + return -1; + } + UE->ho_context = alloc_ho_ctx(HO_CTX_TARGET); + + // Store IDs in UE context + UE->amf_ue_ngap_id = msg->amf_ue_ngap_id; + UE->ue_guami = msg->guami; + UE->ho_context->target->ue_ho_prep_info = copy_byte_array(msg->ue_ho_prep_info); + // store the received UE Security Capabilities in the UE context + FREE_AND_ZERO_BYTE_ARRAY(UE->ue_cap_buffer); + UE->ue_cap_buffer = copy_byte_array(msg->ue_cap); + + /* store the received Security Context in the UE context + and take it into use as defined in TS 33.501 */ + set_UE_security_algos(rrc, UE, &msg->security_capabilities); + UE->nh_ncc = msg->security_context.next_hop_chain_count; + memcpy(UE->nh, msg->security_context.next_hop, SECURITY_KEY_LENGTH); + // Reset KgNB + memset(UE->kgnb, 0, SECURITY_KEY_LENGTH); + // Derive KgNB* + const f1ap_served_cell_info_t *cell_info = &du->setup_req->cell[0].info; + uint32_t ssb_arfcn = get_ssb_arfcn(du); + nr_derive_key_ng_ran_star(cell_info->nr_pci, ssb_arfcn, UE->nh, UE->kgnb); + UE->as_security_active = true; + // Activate SRBs + activate_srb(UE, SRB1); + activate_srb(UE, SRB2); + nr_rrc_pdcp_config_security(UE, false); + + // Copy PDU Session Resource Setup item to RRC struct and do PDU Session Resource Setup procedure + pdusession_t to_setup = { + .nssai = msg->pduSessionResourceSetupList->nssai, + .pdu_session_type = msg->pduSessionResourceSetupList->pdu_session_type , + .pdusession_id = msg->pduSessionResourceSetupList->pdusession_id, + }; + cp_pdusession_transfer_to_pdusession(&to_setup, &msg->pduSessionResourceSetupList->pdusessionTransfer); + if (!trigger_bearer_setup(rrc, UE, msg->nb_of_pdusessions, &to_setup, msg->ue_ambr.br_dl)) { + LOG_E(NR_RRC, "Failed to establish PDU session: handover failed\n"); + ngap_handover_failure_t fail = { + .amf_ue_ngap_id = msg->amf_ue_ngap_id, + .cause.type = NGAP_CAUSE_RADIO_NETWORK, + .cause.value = NGAP_CAUSE_RADIO_NETWORK_HO_FAILURE_IN_TARGET_5GC_NGRAN_NODE_OR_TARGET_SYSTEM, + }; + rrc_gNB_send_NGAP_HANDOVER_FAILURE(rrc, &fail); + } + + return 0; +} + +void rrc_gNB_free_Handover_Request(ngap_handover_request_t *msg) +{ + free_byte_array(msg->ue_cap); + free_byte_array(msg->ue_ho_prep_info); + free(msg->mobility_restriction); +} + /* * Process the NG command NGAP_UE_CONTEXT_RELEASE_COMMAND, sent by AMF. * The gNB should remove all pdu session, NG context, and other context of the UE. diff --git a/openair2/RRC/NR/rrc_gNB_NGAP.h b/openair2/RRC/NR/rrc_gNB_NGAP.h index 7c52843c0f..53e2b51f8c 100644 --- a/openair2/RRC/NR/rrc_gNB_NGAP.h +++ b/openair2/RRC/NR/rrc_gNB_NGAP.h @@ -96,4 +96,7 @@ void rrc_gNB_send_NGAP_HANDOVER_REQUIRED(gNB_RRC_INST *rrc, void rrc_gNB_send_NGAP_HANDOVER_FAILURE(gNB_RRC_INST *rrc, ngap_handover_failure_t *msg); +int rrc_gNB_process_Handover_Request(gNB_RRC_INST *rrc, instance_t instance, ngap_handover_request_t *msg); +void rrc_gNB_free_Handover_Request(ngap_handover_request_t *msg); + #endif diff --git a/openair2/RRC/NR/rrc_gNB_mobility.c b/openair2/RRC/NR/rrc_gNB_mobility.c index 7b1324d6e5..c9d60422e8 100644 --- a/openair2/RRC/NR/rrc_gNB_mobility.c +++ b/openair2/RRC/NR/rrc_gNB_mobility.c @@ -57,6 +57,8 @@ nr_handover_context_t *alloc_ho_ctx(ho_ctx_type_t type) static void free_ho_ctx(nr_handover_context_t *ho_ctx) { free(ho_ctx->source); + if (ho_ctx->target) + FREE_AND_ZERO_BYTE_ARRAY(ho_ctx->target->ue_ho_prep_info); free(ho_ctx->target); free(ho_ctx); } diff --git a/openair2/RRC/NR/rrc_gNB_mobility.h b/openair2/RRC/NR/rrc_gNB_mobility.h index 0bb5faf7fa..77b90ac3e0 100644 --- a/openair2/RRC/NR/rrc_gNB_mobility.h +++ b/openair2/RRC/NR/rrc_gNB_mobility.h @@ -63,6 +63,8 @@ typedef struct nr_ho_target_cu { uint32_t du_ue_id; /// new (target) RNTI (as for du_ue_id) rnti_t new_rnti; + /// Handover Preparation Buffer + byte_array_t ue_ho_prep_info; /// function pointer to announce handover request acknowledgment ho_req_ack_t ho_req_ack; /// function pointer to announce handover success diff --git a/openair3/NGAP/ngap_common.c b/openair3/NGAP/ngap_common.c index b51c8e80de..9e3e2e9d13 100644 --- a/openair3/NGAP/ngap_common.c +++ b/openair3/NGAP/ngap_common.c @@ -66,6 +66,43 @@ void encode_ngap_cause(NGAP_Cause_t *out, const ngap_cause_t *in) } } +ngap_cause_t decode_ngap_cause(const NGAP_Cause_t *in) +{ + ngap_cause_t out = {0}; + switch (in->present) { + case NGAP_Cause_PR_radioNetwork: + out.type = NGAP_CAUSE_RADIO_NETWORK; + out.value = in->choice.radioNetwork; + break; + + case NGAP_Cause_PR_transport: + out.type = NGAP_CAUSE_TRANSPORT; + out.value = in->choice.transport; + break; + + case NGAP_Cause_PR_nas: + out.type = NGAP_CAUSE_NAS; + out.value = in->choice.nas; + break; + + case NGAP_Cause_PR_protocol: + out.type = NGAP_CAUSE_PROTOCOL; + out.value = in->choice.protocol; + break; + + case NGAP_Cause_PR_misc: + out.type = NGAP_CAUSE_MISC; + out.value = in->choice.misc; + break; + + default: + out.type = NGAP_CAUSE_NOTHING; + NGAP_ERROR("Unknown failure cause %d\n", in->present); + break; + } + return out; +} + nr_guami_t decode_ngap_guami(const NGAP_GUAMI_t *in) { nr_guami_t out = {0}; @@ -141,3 +178,96 @@ void encode_ngap_nr_cgi(NGAP_NR_CGI_t *out, const plmn_id_t *plmn, const uint32_ MCC_MNC_TO_PLMNID(plmn->mcc, plmn->mnc, plmn->mnc_digit_length, &out->pLMNIdentity); NR_CELL_ID_TO_BIT_STRING(cell_id, &out->nRCellIdentity); } + +pdusession_level_qos_parameter_t fill_qos(uint8_t qfi, const NGAP_QosFlowLevelQosParameters_t *params) +{ + pdusession_level_qos_parameter_t out = {0}; + // QFI + out.qfi = qfi; + AssertFatal(params != NULL, "QoS parameters are NULL\n"); + // QosCharacteristics + const NGAP_QosCharacteristics_t *qosChar = ¶ms->qosCharacteristics; + AssertFatal(qosChar != NULL, "QoS characteristics are NULL\n"); + if (qosChar->present == NGAP_QosCharacteristics_PR_nonDynamic5QI) { + AssertFatal(qosChar->choice.nonDynamic5QI != NULL, "nonDynamic5QI is NULL\n"); + out.fiveQI_type = NON_DYNAMIC; + out.fiveQI = qosChar->choice.nonDynamic5QI->fiveQI; + } else if (qosChar->present == NGAP_QosCharacteristics_PR_dynamic5QI) { + AssertFatal(qosChar->choice.dynamic5QI != NULL, "dynamic5QI is NULL\n"); + out.fiveQI_type = DYNAMIC; + out.fiveQI = *qosChar->choice.dynamic5QI->fiveQI; + } else { + AssertFatal(0, "Unsupported QoS Characteristics present value: %d\n", qosChar->present); + } + // Allocation and Retention Priority + const NGAP_AllocationAndRetentionPriority_t *arp = ¶ms->allocationAndRetentionPriority; + out.allocation_retention_priority.priority_level = arp->priorityLevelARP; + out.allocation_retention_priority.pre_emp_capability = arp->pre_emptionCapability; + out.allocation_retention_priority.pre_emp_vulnerability = arp->pre_emptionVulnerability; + + return out; +} + +static gtpu_tunnel_t decode_TNLInformation(const NGAP_GTPTunnel_t *gTPTunnel_p) +{ + gtpu_tunnel_t out = {0}; + // Transport layer address + memcpy(out.addr.buffer, gTPTunnel_p->transportLayerAddress.buf, gTPTunnel_p->transportLayerAddress.size); + out.addr.length = gTPTunnel_p->transportLayerAddress.size - gTPTunnel_p->transportLayerAddress.bits_unused; + // GTP tunnel endpoint ID + OCTET_STRING_TO_INT32(&gTPTunnel_p->gTP_TEID, out.teid); + return out; +} + +void *decode_pdusession_transfer(const asn_TYPE_descriptor_t *td, const OCTET_STRING_t buf) +{ + void *decoded = NULL; + asn_codec_ctx_t ctx = {.max_stack_size = 100 * 1000}; + asn_dec_rval_t rval = aper_decode(&ctx, td, &decoded, buf.buf, buf.size, 0, 0); + if (rval.code != RC_OK) { + NGAP_ERROR("Decoding failed for %s\n", td->name); + return NULL; + } + return decoded; +} + +/** @brief Decode PDU Session Resource Setup Request Transfer (9.3.4.1 3GPP TS 38.413) */ +bool decodePDUSessionResourceSetup(pdusession_transfer_t *out, const OCTET_STRING_t in) +{ + void *decoded = decode_pdusession_transfer(&asn_DEF_NGAP_PDUSessionResourceSetupRequestTransfer, in); + if (!decoded) { + LOG_E(NR_RRC, "Failed to decode PDUSessionResourceSetupRequestTransfer\n"); + return false; + } + + NGAP_PDUSessionResourceSetupRequestTransfer_t *pdusessionTransfer = (NGAP_PDUSessionResourceSetupRequestTransfer_t *)decoded; + for (int i = 0; i < pdusessionTransfer->protocolIEs.list.count; i++) { + NGAP_PDUSessionResourceSetupRequestTransferIEs_t *pdusessionTransfer_ies = pdusessionTransfer->protocolIEs.list.array[i]; + switch (pdusessionTransfer_ies->id) { + // UL NG-U UP TNL Information (Mandatory) + case NGAP_ProtocolIE_ID_id_UL_NGU_UP_TNLInformation: + out->n3_incoming = decode_TNLInformation(pdusessionTransfer_ies->value.choice.UPTransportLayerInformation.choice.gTPTunnel); + break; + + // PDU Session Type (Mandatory) + case NGAP_ProtocolIE_ID_id_PDUSessionType: + out->pdu_session_type = pdusessionTransfer_ies->value.choice.PDUSessionType; + break; + + // QoS Flow Setup Request List (Mandatory) + case NGAP_ProtocolIE_ID_id_QosFlowSetupRequestList: + out->nb_qos = pdusessionTransfer_ies->value.choice.QosFlowSetupRequestList.list.count; + for (int i = 0; i < out->nb_qos; i++) { + NGAP_QosFlowSetupRequestItem_t *item = pdusessionTransfer_ies->value.choice.QosFlowSetupRequestList.list.array[i]; + out->qos[i] = fill_qos(item->qosFlowIdentifier, &item->qosFlowLevelQosParameters); + } + break; + + default: + LOG_D(NR_RRC, "Unhandled optional IE %ld\n", pdusessionTransfer_ies->id); + } + } + ASN_STRUCT_FREE(asn_DEF_NGAP_PDUSessionResourceSetupRequestTransfer, pdusessionTransfer); + + return true; +} diff --git a/openair3/NGAP/ngap_common.h b/openair3/NGAP/ngap_common.h index 1e53d9dd1a..9afaf47c4b 100644 --- a/openair3/NGAP/ngap_common.h +++ b/openair3/NGAP/ngap_common.h @@ -83,6 +83,7 @@ typedef int (*ngap_message_decoded_callback)(sctp_assoc_t assoc_id, uint32_t str void encode_ngap_cause(NGAP_Cause_t *out, const ngap_cause_t *in); nr_guami_t decode_ngap_guami(const NGAP_GUAMI_t *in); +ngap_cause_t decode_ngap_cause(const NGAP_Cause_t *in); ngap_ambr_t decode_ngap_UEAggregateMaximumBitRate(const NGAP_UEAggregateMaximumBitRate_t *in); nssai_t decode_ngap_nssai(const NGAP_S_NSSAI_t *in); ngap_security_capabilities_t decode_ngap_security_capabilities(const NGAP_UESecurityCapabilities_t *in); @@ -90,6 +91,8 @@ ngap_mobility_restriction_t decode_ngap_mobility_restriction(const NGAP_Mobility void encode_ngap_target_id(NGAP_HandoverRequiredIEs_t *out, const target_ran_node_id_t *in); void encode_ngap_nr_cgi(NGAP_NR_CGI_t *out, const plmn_id_t *plmn, const uint32_t cell_id); pdusession_level_qos_parameter_t fill_qos(uint8_t qfi, const NGAP_QosFlowLevelQosParameters_t *params); +void *decode_pdusession_transfer(const asn_TYPE_descriptor_t *td, const OCTET_STRING_t buf); +bool decodePDUSessionResourceSetup(pdusession_transfer_t *out, const OCTET_STRING_t in); /** @}*/ diff --git a/openair3/NGAP/ngap_gNB_decoder.c b/openair3/NGAP/ngap_gNB_decoder.c index 58c70011cf..a56357b55a 100644 --- a/openair3/NGAP/ngap_gNB_decoder.c +++ b/openair3/NGAP/ngap_gNB_decoder.c @@ -94,6 +94,12 @@ static int ngap_gNB_decode_initiating_message(NGAP_NGAP_PDU_t *pdu) { NGAP_INFO("TODO ErrorIndication initiating message\n"); break; + case NGAP_ProcedureCode_id_HandoverResourceAllocation: + res = asn_encode_to_new_buffer(NULL, ATS_CANONICAL_XER, &asn_DEF_NGAP_NGAP_PDU, pdu); + free(res.buffer); + NGAP_INFO("Handover Resource Allocation initiating message\n"); + break; + default: NGAP_ERROR("Unknown procedure ID (%d) for initiating message\n", (int)pdu->choice.initiatingMessage->procedureCode); diff --git a/openair3/NGAP/ngap_gNB_encoder.c b/openair3/NGAP/ngap_gNB_encoder.c index a6e91ba9c8..056aea2fe6 100644 --- a/openair3/NGAP/ngap_gNB_encoder.c +++ b/openair3/NGAP/ngap_gNB_encoder.c @@ -77,7 +77,8 @@ static inline int ngap_gNB_encode_successfull_outcome(NGAP_NGAP_PDU_t *pdu, uint NGAP_ProcedureCode_id_UEContextRelease, NGAP_ProcedureCode_id_PDUSessionResourceSetup, NGAP_ProcedureCode_id_PDUSessionResourceModify, - NGAP_ProcedureCode_id_PDUSessionResourceRelease}; + NGAP_ProcedureCode_id_PDUSessionResourceRelease, + NGAP_ProcedureCode_id_HandoverResourceAllocation}; int i; for (i = 0; i < sizeofArray(tmp); i++) if (pdu->choice.successfulOutcome->procedureCode == tmp[i]) @@ -98,8 +99,17 @@ static inline int ngap_gNB_encode_unsuccessfull_outcome(NGAP_NGAP_PDU_t *pdu, ui { DevAssert(pdu != NULL); - if (pdu->choice.unsuccessfulOutcome->procedureCode != NGAP_ProcedureCode_id_InitialContextSetup) { - NGAP_DEBUG("Unknown procedure ID (%d) for unsuccessfull outcome message\n", (int)pdu->choice.unsuccessfulOutcome->procedureCode); + const NGAP_ProcedureCode_t id[] = {NGAP_ProcedureCode_id_InitialContextSetup, NGAP_ProcedureCode_id_HandoverResourceAllocation}; + + int i; + for (i = 0; i < sizeofArray(id); i++) { + if (pdu->choice.unsuccessfulOutcome->procedureCode == id[i]) { + break; + } + } + + if (i == sizeofArray(id)) { + NGAP_WARN("Unknown procedure ID (%ld) for successful outcome message\n", pdu->choice.successfulOutcome->procedureCode); return -1; } diff --git a/openair3/NGAP/ngap_gNB_handlers.c b/openair3/NGAP/ngap_gNB_handlers.c index 6a92be9b79..485d37236a 100644 --- a/openair3/NGAP/ngap_gNB_handlers.c +++ b/openair3/NGAP/ngap_gNB_handlers.c @@ -44,7 +44,9 @@ #include "ngap_common.h" #include "ngap_gNB_decoder.h" #include "ngap_gNB_defs.h" +#include "ngap_gNB_nnsf.h" #include "ngap_gNB_management_procedures.h" +#include "ngap_gNB_mobility_management.h" #include "ngap_gNB_nas_procedures.h" #include "ngap_gNB_trace.h" #include "ngap_gNB_ue_context.h" @@ -693,97 +695,40 @@ static int ngap_gNB_handle_error_indication(sctp_assoc_t assoc_id, uint32_t stre return 0; } -static void *decode_pdusession_transfer(const asn_TYPE_descriptor_t *td, const OCTET_STRING_t buf) +/** @brief Handler for NGAP Handover Request message (AMF -> target NG-RAN) + * Decode the NGAP message and transfer to RRC */ +static int ngap_gNB_handle_handover_request(sctp_assoc_t assoc_id, uint32_t stream, NGAP_NGAP_PDU_t *pdu) { - void *decoded = NULL; - asn_codec_ctx_t ctx = {.max_stack_size = 100 * 1000}; - asn_dec_rval_t rval = aper_decode(&ctx, td, &decoded, buf.buf, buf.size, 0, 0); - if (rval.code != RC_OK) { - NGAP_ERROR("Decoding failed for %s\n", td->name); - return NULL; - } - return decoded; -} + DevAssert(pdu != NULL); -static pdusession_level_qos_parameter_t fill_qos(uint8_t qfi, const NGAP_QosFlowLevelQosParameters_t *params) -{ - pdusession_level_qos_parameter_t out = {0}; - // QFI - out.qfi = qfi; - AssertFatal(params != NULL, "QoS parameters are NULL\n"); - // QosCharacteristics - const NGAP_QosCharacteristics_t *qosChar = ¶ms->qosCharacteristics; - AssertFatal(qosChar != NULL, "QoS characteristics are NULL\n"); - if (qosChar->present == NGAP_QosCharacteristics_PR_nonDynamic5QI) { - AssertFatal(qosChar->choice.nonDynamic5QI != NULL, "nonDynamic5QI is NULL\n"); - out.fiveQI_type = NON_DYNAMIC; - out.fiveQI = qosChar->choice.nonDynamic5QI->fiveQI; - } else if (qosChar->present == NGAP_QosCharacteristics_PR_dynamic5QI) { - AssertFatal(qosChar->choice.dynamic5QI != NULL, "dynamic5QI is NULL\n"); - out.fiveQI_type = DYNAMIC; - out.fiveQI = *qosChar->choice.dynamic5QI->fiveQI; - } else { - AssertFatal(0, "Unsupported QoS Characteristics present value: %d\n", qosChar->present); - } - // Allocation and Retention Priority - const NGAP_AllocationAndRetentionPriority_t *arp = ¶ms->allocationAndRetentionPriority; - out.allocation_retention_priority.priority_level = arp->priorityLevelARP; - out.allocation_retention_priority.pre_emp_capability = arp->pre_emptionCapability; - out.allocation_retention_priority.pre_emp_vulnerability = arp->pre_emptionVulnerability; - - return out; -} - -static gtpu_tunnel_t decode_TNLInformation(const NGAP_GTPTunnel_t *gTPTunnel_p) -{ - gtpu_tunnel_t out = {0}; - // Transport layer address - memcpy(out.addr.buffer, gTPTunnel_p->transportLayerAddress.buf, gTPTunnel_p->transportLayerAddress.size); - out.addr.length = gTPTunnel_p->transportLayerAddress.size - gTPTunnel_p->transportLayerAddress.bits_unused; - // GTP tunnel endpoint ID - OCTET_STRING_TO_INT32(&gTPTunnel_p->gTP_TEID, out.teid); - return out; -} - -/** @brief Decode PDU Session Resource Setup Request Transfer (9.3.4.1 3GPP TS 38.413) */ -static bool decodePDUSessionResourceSetup(pdusession_transfer_t *out, const OCTET_STRING_t in) -{ - void *decoded = decode_pdusession_transfer(&asn_DEF_NGAP_PDUSessionResourceSetupRequestTransfer, in); - if (!decoded) { - LOG_E(NR_RRC, "Failed to decode PDUSessionResourceSetupRequestTransfer\n"); - return false; + ngap_gNB_amf_data_t *amf_desc_p = ngap_gNB_get_AMF(NULL, assoc_id, 0); + if (amf_desc_p == NULL) { + NGAP_ERROR("Received Handover Request for non existing AMF context\n"); + return -1; } - NGAP_PDUSessionResourceSetupRequestTransfer_t *pdusessionTransfer = (NGAP_PDUSessionResourceSetupRequestTransfer_t *)decoded; - for (int i = 0; i < pdusessionTransfer->protocolIEs.list.count; i++) { - NGAP_PDUSessionResourceSetupRequestTransferIEs_t *pdusessionTransfer_ies = pdusessionTransfer->protocolIEs.list.array[i]; - switch (pdusessionTransfer_ies->id) { - // UL NG-U UP TNL Information (Mandatory) - case NGAP_ProtocolIE_ID_id_UL_NGU_UP_TNLInformation: - out->n3_incoming = decode_TNLInformation(pdusessionTransfer_ies->value.choice.UPTransportLayerInformation.choice.gTPTunnel); - break; + MessageDef *message_p = itti_alloc_new_message(TASK_NGAP, 0, NGAP_HANDOVER_REQUEST); + ngap_handover_request_t *msg = &NGAP_HANDOVER_REQUEST(message_p); + memset(msg, 0, sizeof(*msg)); - // PDU Session Type (Mandatory) - case NGAP_ProtocolIE_ID_id_PDUSessionType: - out->pdu_session_type = pdusessionTransfer_ies->value.choice.PDUSessionType; - break; - - // QoS Flow Setup Request List (Mandatory) - case NGAP_ProtocolIE_ID_id_QosFlowSetupRequestList: - out->nb_qos = pdusessionTransfer_ies->value.choice.QosFlowSetupRequestList.list.count; - for (int i = 0; i < out->nb_qos; i++) { - NGAP_QosFlowSetupRequestItem_t *item = pdusessionTransfer_ies->value.choice.QosFlowSetupRequestList.list.array[i]; - out->qos[i] = fill_qos(item->qosFlowIdentifier, &item->qosFlowLevelQosParameters); - } - break; - - default: - LOG_D(NR_RRC, "Unhandled optional IE %ld\n", pdusessionTransfer_ies->id); - } + if (decode_ng_handover_request(msg, pdu) < 0) { + NGAP_ERROR("Failed to decode NG Handover Request\n"); + ngap_handover_failure_t fail = { + .amf_ue_ngap_id = msg->amf_ue_ngap_id, + .cause.type = NGAP_CAUSE_RADIO_NETWORK, + .cause.value = NGAP_CAUSE_RADIO_NETWORK_HO_FAILURE_IN_TARGET_5GC_NGRAN_NODE_OR_TARGET_SYSTEM, + }; + NGAP_INFO("Send NG Handover Failure message (amf_ue_ngap_id %ld) with cause %d \n ", fail.amf_ue_ngap_id, fail.cause.value); + MessageDef *msg_p = itti_alloc_new_message(TASK_NGAP, 0, NGAP_HANDOVER_FAILURE); + NGAP_HANDOVER_FAILURE(msg_p) = fail; + itti_send_msg_to_task(TASK_NGAP, amf_desc_p->ngap_gNB_instance->instance, msg_p); + return -1; } - ASN_STRUCT_FREE(asn_DEF_NGAP_PDUSessionResourceSetupRequestTransfer, pdusessionTransfer); - return true; + NGAP_INFO("Received NG Handover Request from AMF %s (ID=%lu)\n", amf_desc_p->amf_name, msg->amf_ue_ngap_id); + itti_send_msg_to_task(TASK_RRC_GNB, amf_desc_p->ngap_gNB_instance->instance, message_p); + + return 0; } static int ngap_gNB_handle_initial_context_request(sctp_assoc_t assoc_id, uint32_t stream, NGAP_NGAP_PDU_t *pdu) @@ -1394,7 +1339,7 @@ const ngap_message_decoded_callback ngap_messages_callback[][3] = { {0, 0, 0}, /* HandoverCancel */ {0, 0, 0}, /* HandoverNotification */ {0, 0, 0}, /* HandoverPreparation */ - {0, 0, 0}, /* HandoverResourceAllocation */ + {ngap_gNB_handle_handover_request, 0, 0}, /* HandoverResourceAllocation */ {ngap_gNB_handle_initial_context_request, 0, 0}, /* InitialContextSetup */ {0, 0, 0}, /* InitialUEMessage */ {0, 0, 0}, /* LocationReportingControl */ diff --git a/openair3/NGAP/ngap_gNB_mobility_management.c b/openair3/NGAP/ngap_gNB_mobility_management.c index 9d84fccd09..2aa0056dfe 100644 --- a/openair3/NGAP/ngap_gNB_mobility_management.c +++ b/openair3/NGAP/ngap_gNB_mobility_management.c @@ -30,6 +30,7 @@ #include "ngap_gNB_management_procedures.h" #include "ngap_gNB_encoder.h" #include "ngap_gNB_itti_messaging.h" +#include "conversions.h" /** @brief UE Mobility Management: encode Handover Required * (9.2.3.1 of 3GPP TS 38.413) NG-RAN node → AMF */ @@ -224,3 +225,160 @@ NGAP_NGAP_PDU_t *encode_ng_handover_failure(const ngap_handover_failure_t *msg) return pdu; } + +static void free_ng_handover_request(ngap_handover_request_t *msg) +{ + free_byte_array(msg->ue_ho_prep_info); + free_byte_array(msg->ue_cap); + free(msg->mobility_restriction); +} + +int decode_ng_handover_request(ngap_handover_request_t *out, const NGAP_NGAP_PDU_t *pdu) +{ + DevAssert(pdu != NULL); + NGAP_HandoverRequest_t *container = &pdu->choice.initiatingMessage->value.choice.HandoverRequest; + NGAP_HandoverRequestIEs_t *ie; + + // Handover Type (M) + NGAP_FIND_PROTOCOLIE_BY_ID(NGAP_HandoverRequestIEs_t, ie, container, NGAP_ProtocolIE_ID_id_HandoverType, true); + out->ho_type = ie->value.choice.HandoverType; + if (out->ho_type != HANDOVER_TYPE_INTRA5GS) { + NGAP_ERROR("Only Intra5GS Handover is supported at the moment!\n"); + return -1; + } + + // AMF UE NGAP ID (M) + NGAP_FIND_PROTOCOLIE_BY_ID(NGAP_HandoverRequestIEs_t, ie, container, NGAP_ProtocolIE_ID_id_AMF_UE_NGAP_ID, true); + asn_INTEGER2ulong(&(ie->value.choice.AMF_UE_NGAP_ID), &out->amf_ue_ngap_id); + + // GUAMI (M) + NGAP_FIND_PROTOCOLIE_BY_ID(NGAP_HandoverRequestIEs_t, ie, container, NGAP_ProtocolIE_ID_id_GUAMI, true); + out->guami = decode_ngap_guami(&ie->value.choice.GUAMI); + + // UE Aggregate Maximum Bit Rate (M) + NGAP_FIND_PROTOCOLIE_BY_ID(NGAP_HandoverRequestIEs_t, ie, container, NGAP_ProtocolIE_ID_id_UEAggregateMaximumBitRate, true); + out->ue_ambr = decode_ngap_UEAggregateMaximumBitRate(&ie->value.choice.UEAggregateMaximumBitRate); + + // Allowed NSSAI (M) + NGAP_FIND_PROTOCOLIE_BY_ID(NGAP_HandoverRequestIEs_t, ie, container, NGAP_ProtocolIE_ID_id_AllowedNSSAI, true); + NGAP_DEBUG("AllowedNSSAI.list.count %d\n", ie->value.choice.AllowedNSSAI.list.count); + out->nb_allowed_nssais = ie->value.choice.AllowedNSSAI.list.count; + for (int i = 0; i < out->nb_allowed_nssais; i++) { + out->allowed_nssai[i] = decode_ngap_nssai(&ie->value.choice.AllowedNSSAI.list.array[i]->s_NSSAI); + } + + // UE Security Capabilities (M) + NGAP_FIND_PROTOCOLIE_BY_ID(NGAP_HandoverRequestIEs_t, ie, container, NGAP_ProtocolIE_ID_id_UESecurityCapabilities, true); + out->security_capabilities = decode_ngap_security_capabilities(&ie->value.choice.UESecurityCapabilities); + + // Security Context (M) + NGAP_FIND_PROTOCOLIE_BY_ID(NGAP_HandoverRequestIEs_t, ie, container, NGAP_ProtocolIE_ID_id_SecurityContext, true); + NGAP_SecurityContext_t *sc = &ie->value.choice.SecurityContext; + memcpy(&out->security_context.next_hop, sc->nextHopNH.buf, sc->nextHopNH.size); + out->security_context.next_hop_chain_count = sc->nextHopChainingCount; + + // Mobility Restriction List (O) + NGAP_FIND_PROTOCOLIE_BY_ID(NGAP_HandoverRequestIEs_t, ie, container, NGAP_ProtocolIE_ID_id_MobilityRestrictionList, false); + if (ie != NULL) { + out->mobility_restriction = malloc_or_fail(sizeof(*out->mobility_restriction)); + *out->mobility_restriction = decode_ngap_mobility_restriction(&ie->value.choice.MobilityRestrictionList); + } + + // Cause (M) + NGAP_FIND_PROTOCOLIE_BY_ID(NGAP_HandoverRequestIEs_t, ie, container, NGAP_ProtocolIE_ID_id_Cause, true); + out->cause = decode_ngap_cause(&ie->value.choice.Cause); + + // Source to Target Transparent Container + NGAP_FIND_PROTOCOLIE_BY_ID(NGAP_HandoverRequestIEs_t, + ie, + container, + NGAP_ProtocolIE_ID_id_SourceToTarget_TransparentContainer, + true); + NGAP_SourceToTarget_TransparentContainer_t *choice = &ie->value.choice.SourceToTarget_TransparentContainer; + byte_array_t sourceToTargetTransparentContainer = create_byte_array(choice->size, choice->buf); + NGAP_SourceNGRANNode_ToTargetNGRANNode_TransparentContainer_t *source2target = NULL; + asn_dec_rval_t dec_rval = aper_decode_complete(NULL, + &asn_DEF_NGAP_SourceNGRANNode_ToTargetNGRANNode_TransparentContainer, + (void **)&source2target, + sourceToTargetTransparentContainer.buf, + sourceToTargetTransparentContainer.len); + + free_byte_array(sourceToTargetTransparentContainer); + if (dec_rval.code != RC_OK) { + free_ng_handover_request(out); + NGAP_ERROR("Failed to decode sourceToTargetTransparentContainer\n"); + return -1; + } + + if (LOG_DEBUGFLAG(DEBUG_ASN1)) + xer_fprint(stdout, &asn_DEF_NGAP_SourceNGRANNode_ToTargetNGRANNode_TransparentContainer, source2target); + + // Extract Cell Identity + BIT_STRING_TO_NR_CELL_IDENTITY(&source2target->targetCell_ID.choice.nR_CGI->nRCellIdentity, out->nr_cell_id); + + // Handover Preparation Information: store and decode + out->ue_ho_prep_info = create_byte_array(source2target->rRCContainer.size, source2target->rRCContainer.buf); + NR_HandoverPreparationInformation_t *hoPrepInformation = NULL; + asn_dec_rval_t hoPrep_dec_rval = uper_decode_complete(NULL, + &asn_DEF_NR_HandoverPreparationInformation, + (void **)&hoPrepInformation, + source2target->rRCContainer.buf, + source2target->rRCContainer.size); + AssertFatal(hoPrep_dec_rval.code == RC_OK && hoPrep_dec_rval.consumed > 0, "Handover Prep Info decode error\n"); + if (hoPrep_dec_rval.code != RC_OK && !hoPrep_dec_rval.consumed) { + NGAP_ERROR("Failed to decode HandoverPreparationInformation, abort Handover Request decoding\n"); + free_ng_handover_request(out); + ASN_STRUCT_FREE(asn_DEF_NGAP_SourceNGRANNode_ToTargetNGRANNode_TransparentContainer, source2target); + return -1; + } + + ASN_STRUCT_FREE(asn_DEF_NGAP_SourceNGRANNode_ToTargetNGRANNode_TransparentContainer, source2target); + + if (LOG_DEBUGFLAG(DEBUG_ASN1)) + xer_fprint(stdout, &asn_DEF_NR_HandoverPreparationInformation, hoPrepInformation); + + // Decode UE capabilities and store + NR_HandoverPreparationInformation_IEs_t *hpi = + hoPrepInformation->criticalExtensions.choice.c1->choice.handoverPreparationInformation; + const NR_UE_CapabilityRAT_ContainerList_t *ue_CapabilityRAT_ContainerList = &hpi->ue_CapabilityRAT_List; + out->ue_cap.len = uper_encode_to_new_buffer(&asn_DEF_NR_UE_CapabilityRAT_ContainerList, + NULL, + ue_CapabilityRAT_ContainerList, + (void **)&out->ue_cap.buf); + + ASN_STRUCT_FREE(asn_DEF_NR_HandoverPreparationInformation, hoPrepInformation); + + if (out->ue_cap.len <= 0) { + free_ng_handover_request(out); + NGAP_ERROR("could not encode UE-CapabilityRAT-ContainerList\n"); + return -1; + } + + // PDU Session Resource Setup List (M) + NGAP_FIND_PROTOCOLIE_BY_ID(NGAP_HandoverRequestIEs_t, + ie, + container, + NGAP_ProtocolIE_ID_id_PDUSessionResourceSetupListHOReq, + true); + out->nb_of_pdusessions = ie->value.choice.PDUSessionResourceSetupListHOReq.list.count; + for (int pduSesIdx = 0; pduSesIdx < out->nb_of_pdusessions; ++pduSesIdx) { + NGAP_PDUSessionResourceSetupItemHOReq_t *item_p = ie->value.choice.PDUSessionResourceSetupListHOReq.list.array[pduSesIdx]; + // PDU Session ID (M) + ho_request_pdusession_t *setup = &out->pduSessionResourceSetupList[pduSesIdx]; + setup->pdusession_id = item_p->pDUSessionID; + setup->pdu_session_type = PDUSessionType_ipv4; + // S-NSSAI (M) + setup->nssai = decode_ngap_nssai(&item_p->s_NSSAI); + // Handover Request Transfer (M) + bool ret = decodePDUSessionResourceSetup(&setup->pdusessionTransfer, item_p->handoverRequestTransfer); + if (!ret) { + free_ng_handover_request(out); + NGAP_ERROR("Failed to decode pDUSessionResourceSetupRequestTransfer in NG Initial Context Setup Request\n"); + return -1; + } + } + + + + return 0; +} diff --git a/openair3/NGAP/ngap_gNB_mobility_management.h b/openair3/NGAP/ngap_gNB_mobility_management.h index 6f5cdde742..66e85e644e 100644 --- a/openair3/NGAP/ngap_gNB_mobility_management.h +++ b/openair3/NGAP/ngap_gNB_mobility_management.h @@ -28,5 +28,6 @@ NGAP_NGAP_PDU_t *encode_ng_handover_required(const ngap_handover_required_t *msg); NGAP_NGAP_PDU_t *encode_ng_handover_failure(const ngap_handover_failure_t *msg); +int decode_ng_handover_request(ngap_handover_request_t *out, const NGAP_NGAP_PDU_t *pdu); #endif /* NGAP_GNB_MOBILITY_MANAGEMENT_H_ */ diff --git a/openair3/NGAP/ngap_msg_includes.h b/openair3/NGAP/ngap_msg_includes.h index cf7d0d9f49..a80df29401 100644 --- a/openair3/NGAP/ngap_msg_includes.h +++ b/openair3/NGAP/ngap_msg_includes.h @@ -86,5 +86,20 @@ #include "NGAP_PDUSessionResourceInformationList.h" #include "NGAP_HandoverRequiredTransfer.h" #include "NGAP_SourceNGRANNode-ToTargetNGRANNode-TransparentContainer.h" +#include "NGAP_PDUSessionResourceSetupItemHOReq.h" +#include "NGAP_HandoverCommand.h" +#include "NGAP_HandoverCommandTransfer.h" +#include "NGAP_TargetNGRANNode-ToSourceNGRANNode-TransparentContainer.h" +#include "NGAP_TargetToSource-TransparentContainer.h" +#include "NGAP_UEHistoryInformation.h" +#include "NGAP_Cause.h" +#include "NGAP_PDUSessionResourceHandoverItem.h" +#include "NGAP_SourceToTarget-TransparentContainer.h" +#include "NGAP_HandoverRequest.h" +#include "NGAP_PDUSessionResourceAdmittedList.h" +#include "NGAP_PDUSessionResourceAdmittedItem.h" +#include "NGAP_HandoverRequestAcknowledgeTransfer.h" +#include "NGAP_QosFlowItemWithDataForwarding.h" +#include "NR_HandoverPreparationInformation.h" #endif // NGAP_MSG_INCLUDES_H From fa75484e135c40e60c57805eb3248de522b2f1d4 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Wed, 12 Mar 2025 18:00:19 +0100 Subject: [PATCH 32/52] Add NGAP Handover Request Acknowledge encoder and RRC callback This commit completes the NG Mobility Management Procedure known as Handover Resource Allocation. The outbound message is sent by the target NG-RAN and is known as Handover Request Acknowledge. Major changes: * add RRC callback to trigger the generation of NG Handover Request Acknowledge on the target NG-RAN * add NG Handover Request Acknowledge encoder Co-authored-by: batuhan duyuler --- openair2/COMMON/ngap_messages_def.h | 1 + openair2/COMMON/ngap_messages_types.h | 31 +++++++ openair2/RRC/NR/rrc_gNB_NGAP.c | 36 ++++++++ openair2/RRC/NR/rrc_gNB_NGAP.h | 2 + openair3/NGAP/ngap_gNB.c | 50 ++++++++++ openair3/NGAP/ngap_gNB_mobility_management.c | 96 +++++++++++++++++++- openair3/NGAP/ngap_gNB_mobility_management.h | 2 + 7 files changed, 215 insertions(+), 3 deletions(-) diff --git a/openair2/COMMON/ngap_messages_def.h b/openair2/COMMON/ngap_messages_def.h index 53b16350e1..e9e185e152 100644 --- a/openair2/COMMON/ngap_messages_def.h +++ b/openair2/COMMON/ngap_messages_def.h @@ -73,6 +73,7 @@ MESSAGE_DEF(NGAP_PDUSESSION_MODIFY_RESP , MESSAGE_PRIORITY_MED, ngap_pd MESSAGE_DEF(NGAP_PDUSESSION_RELEASE_RESPONSE , MESSAGE_PRIORITY_MED, ngap_pdusession_release_resp_t , ngap_pdusession_release_resp) MESSAGE_DEF(NGAP_HANDOVER_REQUIRED, MESSAGE_PRIORITY_MED, ngap_handover_required_t, ngap_handover_required) MESSAGE_DEF(NGAP_HANDOVER_FAILURE, MESSAGE_PRIORITY_MED, ngap_handover_failure_t, ngap_handover_failure) +MESSAGE_DEF(NGAP_HANDOVER_REQUEST_ACKNOWLEDGE, MESSAGE_PRIORITY_MED, ngap_handover_request_ack_t, ngap_handover_request_ack) /* NGAP -> RRC messages */ MESSAGE_DEF(NGAP_DOWNLINK_NAS , MESSAGE_PRIORITY_MED, ngap_downlink_nas_t , ngap_downlink_nas ) diff --git a/openair2/COMMON/ngap_messages_types.h b/openair2/COMMON/ngap_messages_types.h index 49212b257c..5a5937500d 100644 --- a/openair2/COMMON/ngap_messages_types.h +++ b/openair2/COMMON/ngap_messages_types.h @@ -68,6 +68,7 @@ #define NGAP_HANDOVER_REQUIRED(mSGpTR) (mSGpTR)->ittiMsg.ngap_handover_required #define NGAP_HANDOVER_FAILURE(mSGpTR) (mSGpTR)->ittiMsg.ngap_handover_failure #define NGAP_HANDOVER_REQUEST(mSGpTR) (mSGpTR)->ittiMsg.ngap_handover_request +#define NGAP_HANDOVER_REQUEST_ACKNOWLEDGE(mSGpTR) (mSGpTR)->ittiMsg.ngap_handover_request_ack #define NGAP_UE_CONTEXT_RELEASE_REQ(mSGpTR) (mSGpTR)->ittiMsg.ngap_ue_release_req #define NGAP_PDUSESSION_RELEASE_COMMAND(mSGpTR) (mSGpTR)->ittiMsg.ngap_pdusession_release_command @@ -617,6 +618,36 @@ typedef struct { nr_guami_t guami; } ngap_handover_request_t; +/* 9.3.4.11 3GPP TS 38.413 */ +typedef struct { + // QoS Flow Setup Response List + uint8_t nb_of_qos_flow; + pdusession_associate_qosflow_t qos_setup_list[QOSFLOW_MAX_VALUE]; + // DL NG-U UP TNL Information + uint32_t gtp_teid; + transport_layer_addr_t gNB_addr; +} ho_request_ack_transfer_t; + +/* 9.2.3.5 3GPP TS 38.413 */ +typedef struct { + // PDU Session ID (M) + uint8_t pdu_session_id; + // Handover Request Acknowledge Transfer (M) + ho_request_ack_transfer_t ack_transfer; +} pdu_session_resource_admitted_t; + +typedef struct { + // AMF UE NGAP ID + uint32_t gNB_ue_ngap_id; + // RAN UE NGAP ID + uint64_t amf_ue_ngap_id; + // PDU Session Resource Admitted List + pdu_session_resource_admitted_t pdusessions[NGAP_MAX_PDU_SESSION]; + uint8_t nb_of_pdusessions; + // Target to Source Transparent Container + byte_array_t target2source; +} ngap_handover_request_ack_t; + typedef struct ngap_ue_cap_info_ind_s { uint32_t gNB_ue_ngap_id; byte_array_t ue_radio_cap; diff --git a/openair2/RRC/NR/rrc_gNB_NGAP.c b/openair2/RRC/NR/rrc_gNB_NGAP.c index 68aa502f77..19cd12704f 100644 --- a/openair2/RRC/NR/rrc_gNB_NGAP.c +++ b/openair2/RRC/NR/rrc_gNB_NGAP.c @@ -1270,6 +1270,42 @@ void rrc_gNB_send_NGAP_UE_CAPABILITIES_IND(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, LOG_I(NR_RRC,"Send message to ngap: NGAP_UE_CAPABILITIES_IND\n"); } +void rrc_gNB_send_NGAP_HANDOVER_REQUEST_ACKNOWLEDGE(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, byte_array_t ho_command) +{ + LOG_D(NR_RRC, "Sending Handover Request Acknowledge\n"); + + MessageDef *msg_p = itti_alloc_new_message(TASK_RRC_GNB, 0, NGAP_HANDOVER_REQUEST_ACKNOWLEDGE); + ngap_handover_request_ack_t *msg = &NGAP_HANDOVER_REQUEST_ACKNOWLEDGE(msg_p); + memset(msg, 0, sizeof(*msg)); + + // RAN UE NGAP ID + msg->gNB_ue_ngap_id = UE->rrc_ue_id; + // AMF UE NGAP ID + msg->amf_ue_ngap_id = UE->amf_ue_ngap_id; + // PDU Session Resource Admitted List + msg->nb_of_pdusessions = UE->nb_of_pdusessions; + for (int i = 0; i < UE->nb_of_pdusessions; i++) { + rrc_pdu_session_param_t *session = &UE->pduSession[i]; + session->status = PDU_SESSION_STATUS_ESTABLISHED; + // PDU Session ID + msg->pdusessions[i].pdu_session_id = session->param.pdusession_id; + // Handover Request Acknowledge Transfer + ho_request_ack_transfer_t *transfer = &msg->pdusessions[i].ack_transfer; + transfer->gtp_teid = session->param.n3_outgoing.teid; + memcpy(transfer->gNB_addr.buffer, session->param.n3_outgoing.addr.buffer, session->param.n3_outgoing.addr.length); + transfer->gNB_addr.length = session->param.n3_outgoing.addr.length; + transfer->nb_of_qos_flow = session->param.nb_qos; + for (int q = 0; q < transfer->nb_of_qos_flow; q++) { + transfer->qos_setup_list[q].qfi = session->param.qos[q].qfi; + transfer->qos_setup_list[q].qos_flow_mapping_ind = QOSFLOW_MAPPING_INDICATION_DL; + } + } + // Target to Source Transparent Container + msg->target2source = copy_byte_array(ho_command); + + itti_send_msg_to_task(TASK_NGAP, rrc->module_id, msg_p); +} + void rrc_gNB_send_NGAP_PDUSESSION_RELEASE_RESPONSE(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, uint8_t xid) { int pdu_sessions_released = 0; diff --git a/openair2/RRC/NR/rrc_gNB_NGAP.h b/openair2/RRC/NR/rrc_gNB_NGAP.h index 53e2b51f8c..9d51db4186 100644 --- a/openair2/RRC/NR/rrc_gNB_NGAP.h +++ b/openair2/RRC/NR/rrc_gNB_NGAP.h @@ -99,4 +99,6 @@ void rrc_gNB_send_NGAP_HANDOVER_FAILURE(gNB_RRC_INST *rrc, ngap_handover_failure int rrc_gNB_process_Handover_Request(gNB_RRC_INST *rrc, instance_t instance, ngap_handover_request_t *msg); void rrc_gNB_free_Handover_Request(ngap_handover_request_t *msg); +void rrc_gNB_send_NGAP_HANDOVER_REQUEST_ACKNOWLEDGE(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, byte_array_t ho_command); + #endif diff --git a/openair3/NGAP/ngap_gNB.c b/openair3/NGAP/ngap_gNB.c index 58511f307c..e80b914871 100644 --- a/openair3/NGAP/ngap_gNB.c +++ b/openair3/NGAP/ngap_gNB.c @@ -374,6 +374,51 @@ static int ngap_gNB_handover_failure(instance_t instance, const ngap_handover_fa return 0; } +static int ngap_gNB_handover_request_acknowledge(instance_t instance, ngap_handover_request_ack_t *msg) +{ + byte_array_t ba = { .buf = NULL, .len = 0 }; + + /* Retrieve the NGAP gNB instance associated with Mod_id */ + ngap_gNB_instance_t *ngap = ngap_gNB_get_instance(instance); + DevAssert(msg != NULL); + DevAssert(ngap != NULL); + + // First, create and store NGAP UE context + ngap_gNB_ue_context_t ue_context_p = { + .amf_ref = ngap_gNB_get_AMF_from_instance(ngap), + .gNB_ue_ngap_id = msg->gNB_ue_ngap_id, + .amf_ue_ngap_id = msg->amf_ue_ngap_id, + .gNB_instance = ngap, + .ue_state = NGAP_UE_CONNECTED, + }; + if (ue_context_p.amf_ref == NULL) { + NGAP_ERROR("Failed to fetch AMF for current NGAP instance\n"); + return -1; + } + ngap_store_ue_context(&ue_context_p); + + NGAP_NGAP_PDU_t *pdu = encode_ng_handover_request_ack(msg); + if (!pdu) { + NGAP_ERROR("Failed to encode NG Handover Request Acknowledge\n"); + return -1; + } + + if (LOG_DEBUGFLAG(DEBUG_ASN1)) + xer_fprint(stdout, &asn_DEF_NGAP_NGAP_PDU, pdu); + + if (ngap_gNB_encode_pdu(pdu, &ba.buf, (uint32_t *)&ba.len) < 0) { + NGAP_ERROR("Failed to encode Handover Request Acknowledge message\n"); + ASN_STRUCT_FREE(asn_DEF_NGAP_NGAP_PDU, pdu); + return -1; + } + + /* UE associated signalling -> use the allocated stream */ + ngap_gNB_itti_send_sctp_data_req(ngap->instance, ue_context_p.amf_ref->assoc_id, ba.buf, ba.len, ue_context_p.tx_stream); + NGAP_INFO("Sent Handover Request Acknowledge to AMF\n"); + + return 0; +} + void *ngap_gNB_process_itti_msg(void *notUsed) { MessageDef *received_msg = NULL; int result; @@ -459,6 +504,11 @@ void *ngap_gNB_process_itti_msg(void *notUsed) { ngap_gNB_handover_failure(instance, &NGAP_HANDOVER_FAILURE(received_msg)); break; + case NGAP_HANDOVER_REQUEST_ACKNOWLEDGE: + ngap_gNB_handover_request_acknowledge(instance, &NGAP_HANDOVER_REQUEST_ACKNOWLEDGE(received_msg)); + free_ng_handover_req_ack(&NGAP_HANDOVER_REQUEST_ACKNOWLEDGE(received_msg)); + break; + default: NGAP_ERROR("Received unhandled message: %d:%s\n", ITTI_MSG_ID(received_msg), ITTI_MSG_NAME(received_msg)); break; diff --git a/openair3/NGAP/ngap_gNB_mobility_management.c b/openair3/NGAP/ngap_gNB_mobility_management.c index 2aa0056dfe..4c64659e01 100644 --- a/openair3/NGAP/ngap_gNB_mobility_management.c +++ b/openair3/NGAP/ngap_gNB_mobility_management.c @@ -22,6 +22,7 @@ #include #include #include +#include "conversions.h" #include "ngap_common.h" #include "ngap_msg_includes.h" #include "ngap_gNB_defs.h" @@ -377,8 +378,97 @@ int decode_ng_handover_request(ngap_handover_request_t *out, const NGAP_NGAP_PDU return -1; } } - - - return 0; } + +NGAP_NGAP_PDU_t *encode_ng_handover_request_ack(ngap_handover_request_ack_t *msg) +{ + NGAP_NGAP_PDU_t *pdu = malloc_or_fail(sizeof(*pdu)); + + pdu->present = NGAP_NGAP_PDU_PR_successfulOutcome; + asn1cCalloc(pdu->choice.successfulOutcome, head); + head->procedureCode = NGAP_ProcedureCode_id_HandoverResourceAllocation; + head->criticality = NGAP_Criticality_reject; + head->value.present = NGAP_SuccessfulOutcome__value_PR_HandoverRequestAcknowledge; + NGAP_HandoverRequestAcknowledge_t *out = &head->value.choice.HandoverRequestAcknowledge; + + // AMF UE NGAP ID (M) + { + asn1cSequenceAdd(out->protocolIEs.list, NGAP_HandoverRequestAcknowledgeIEs_t, ie); + ie->id = NGAP_ProtocolIE_ID_id_AMF_UE_NGAP_ID; + ie->criticality = NGAP_Criticality_ignore; + ie->value.present = NGAP_HandoverRequestAcknowledgeIEs__value_PR_AMF_UE_NGAP_ID; + asn_uint642INTEGER(&ie->value.choice.AMF_UE_NGAP_ID, msg->amf_ue_ngap_id); + } + + // RAN UE NGAP ID (M) + { + asn1cSequenceAdd(out->protocolIEs.list, NGAP_HandoverRequestAcknowledgeIEs_t, ie); + ie->id = NGAP_ProtocolIE_ID_id_RAN_UE_NGAP_ID; + ie->criticality = NGAP_Criticality_ignore; + ie->value.present = NGAP_HandoverRequestAcknowledgeIEs__value_PR_RAN_UE_NGAP_ID; + ie->value.choice.RAN_UE_NGAP_ID = msg->gNB_ue_ngap_id; + } + + // Target to Source Transparent Container (M) + { + asn1cSequenceAdd(out->protocolIEs.list, NGAP_HandoverRequestAcknowledgeIEs_t, ie); + ie->id = NGAP_ProtocolIE_ID_id_TargetToSource_TransparentContainer; + ie->criticality = NGAP_Criticality_reject; + ie->value.present = NGAP_HandoverRequestAcknowledgeIEs__value_PR_TargetToSource_TransparentContainer; + NGAP_TargetNGRANNode_ToSourceNGRANNode_TransparentContainer_t *t2s = calloc_or_fail(1, sizeof(*t2s)); + // RRC Container (M) + { + // Encode Handover Command + t2s->rRCContainer.size = msg->target2source.len; + t2s->rRCContainer.buf = malloc_or_fail(msg->target2source.len); + memcpy(t2s->rRCContainer.buf, msg->target2source.buf, msg->target2source.len); + byte_array_t ba = { .buf = NULL, .len = 0 }; + ba.len = aper_encode_to_new_buffer(&asn_DEF_NGAP_TargetNGRANNode_ToSourceNGRANNode_TransparentContainer, NULL, t2s, (void **)&ba.buf); + OCTET_STRING_fromBuf(&ie->value.choice.TargetToSource_TransparentContainer, (const char *)ba.buf, ba.len); + ASN_STRUCT_FREE(asn_DEF_NGAP_TargetNGRANNode_ToSourceNGRANNode_TransparentContainer, t2s); + free_byte_array(ba); + } + } + + // PDU Session Resource Admitted List (M) + { + asn1cSequenceAdd(out->protocolIEs.list, NGAP_HandoverRequestAcknowledgeIEs_t, ie); + ie->id = NGAP_ProtocolIE_ID_id_PDUSessionResourceAdmittedList; + ie->criticality = NGAP_Criticality_ignore; + ie->value.present = NGAP_HandoverRequestAcknowledgeIEs__value_PR_PDUSessionResourceAdmittedList; + + for (int pduSesIdx = 0; pduSesIdx < msg->nb_of_pdusessions; pduSesIdx++) { + asn1cSequenceAdd(ie->value.choice.PDUSessionResourceAdmittedList.list, NGAP_PDUSessionResourceAdmittedItem_t, item); + item->pDUSessionID = msg->pdusessions[pduSesIdx].pdu_session_id; + /* dLQosFlowPerTNLInformation */ + NGAP_HandoverRequestAcknowledgeTransfer_t transfer = {0}; + transfer.dL_NGU_UP_TNLInformation.present = NGAP_UPTransportLayerInformation_PR_gTPTunnel; + asn1cCalloc(transfer.dL_NGU_UP_TNLInformation.choice.gTPTunnel, tmp); + GTP_TEID_TO_ASN1(msg->pdusessions[pduSesIdx].ack_transfer.gtp_teid, &tmp->gTP_TEID); + tnl_to_bitstring(&tmp->transportLayerAddress, msg->pdusessions[pduSesIdx].ack_transfer.gNB_addr); + + for (int j = 0; j < msg->pdusessions[pduSesIdx].ack_transfer.nb_of_qos_flow; j++) { + asn1cSequenceAdd(transfer.qosFlowSetupResponseList.list, NGAP_QosFlowItemWithDataForwarding_t, qosItem); + qosItem->qosFlowIdentifier = msg->pdusessions[pduSesIdx].ack_transfer.qos_setup_list[j].qfi; + qosItem->dataForwardingAccepted = malloc_or_fail(sizeof(*qosItem->dataForwardingAccepted)); + *qosItem->dataForwardingAccepted = NGAP_DataForwardingAccepted_data_forwarding_accepted; + } + + void *buf; + ssize_t encoded = aper_encode_to_new_buffer(&asn_DEF_NGAP_HandoverRequestAcknowledgeTransfer, NULL, &transfer, &buf); + AssertFatal(encoded > 0, "ASN1 message encoding failed !\n"); + item->handoverRequestAcknowledgeTransfer.buf = buf; + item->handoverRequestAcknowledgeTransfer.size = encoded; + + ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_NGAP_HandoverRequestAcknowledgeTransfer, &transfer); + } + } + + return pdu; +} + +void free_ng_handover_req_ack(ngap_handover_request_ack_t *msg) +{ + free_byte_array(msg->target2source); +} diff --git a/openair3/NGAP/ngap_gNB_mobility_management.h b/openair3/NGAP/ngap_gNB_mobility_management.h index 66e85e644e..dd0061ca35 100644 --- a/openair3/NGAP/ngap_gNB_mobility_management.h +++ b/openair3/NGAP/ngap_gNB_mobility_management.h @@ -29,5 +29,7 @@ NGAP_NGAP_PDU_t *encode_ng_handover_required(const ngap_handover_required_t *msg); NGAP_NGAP_PDU_t *encode_ng_handover_failure(const ngap_handover_failure_t *msg); int decode_ng_handover_request(ngap_handover_request_t *out, const NGAP_NGAP_PDU_t *pdu); +NGAP_NGAP_PDU_t *encode_ng_handover_request_ack(ngap_handover_request_ack_t *msg); +void free_ng_handover_req_ack(ngap_handover_request_ack_t *msg); #endif /* NGAP_GNB_MOBILITY_MANAGEMENT_H_ */ From 6fd02ee7e886b9a3fcfc044291761e173ff54fb2 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Mon, 26 May 2025 10:38:30 +0200 Subject: [PATCH 33/52] Handle N2 handover in UE Context Setup Response on the target NG-RAN * generate Handover Command for the source NG-RAN to be added to the target to source transparent container of the Handover Acknowledge message * add Handover Acknowledge callback for N2 * send NG Handover Acknowledge to AMF Co-authored-by: batuhan duyuler --- openair2/RRC/NR/MESSAGES/asn1_msg.c | 108 ++++++++++++++++++++++++++++ openair2/RRC/NR/MESSAGES/asn1_msg.h | 2 + openair2/RRC/NR/rrc_gNB_mobility.c | 67 +++++++++++++++++ openair2/RRC/NR/rrc_gNB_mobility.h | 1 + 4 files changed, 178 insertions(+) diff --git a/openair2/RRC/NR/MESSAGES/asn1_msg.c b/openair2/RRC/NR/MESSAGES/asn1_msg.c index 8bd9716107..fc3ca002f4 100644 --- a/openair2/RRC/NR/MESSAGES/asn1_msg.c +++ b/openair2/RRC/NR/MESSAGES/asn1_msg.c @@ -82,6 +82,7 @@ #include "NR_UE-CapabilityRequestFilterNR.h" #include "NR_HandoverPreparationInformation.h" #include "NR_HandoverPreparationInformation-IEs.h" +#include "NR_HandoverCommand.h" #include "common/utils/nr/nr_common.h" #if defined(NR_Rel16) #include "NR_SCS-SpecificCarrier.h" @@ -1138,6 +1139,113 @@ NR_MeasConfig_t *get_MeasConfig(const NR_MeasTiming_t *mt, return mc; } +/** @brief Prepares removal of measurement configurations related to the source gNB. + * This function decodes the HandoverPreparationInformation received from the source gNB, + * extracts the source MeasConfig, and marks those entries (measurement objects, report + * configurations, and measurement IDs) for removal by populating the corresponding + * ToRemoveList fields in the UE's current measConfig. + * This ensures that obsolete or source-specific measurement configurations are removed + * from the UE after handover and allows the target gNB to install its own valid configuration. + * @param measConfig Target gNB's current measurement configuration to be updated. + * @param prep_info Encoded HandoverPreparationInformation received from the source gNB.*/ +void fill_removal_lists_from_source_measConfig(NR_MeasConfig_t *currentMC, byte_array_t prep_info) +{ + if (currentMC == NULL) { + LOG_E(NR_RRC, "HO LOG: UE's Measurement Configuration is NULL!\n"); + return; + } + + // Decodes the HandoverPreparationInformation message + NR_HandoverPreparationInformation_t *hpi = NULL; + asn_dec_rval_t hoPrep_dec_rval = uper_decode_complete(NULL, + &asn_DEF_NR_HandoverPreparationInformation, + (void **)&hpi, + (uint8_t *)prep_info.buf, + prep_info.len); + + if (hoPrep_dec_rval.code != RC_OK || hoPrep_dec_rval.consumed < 0) { + LOG_E(NR_RRC, "Handover Prep Info decode error while removing source gnb measurement configuration!\n"); + return; + } + + /* Decodes the RRCReconfiguration message within the HandoverPreparationInformation + and extract the measConfig provided by the source gNB */ + if (!hpi->criticalExtensions.choice.c1->choice.handoverPreparationInformation->sourceConfig) { + LOG_W(NR_RRC, "Missing sourceConfig: in source gNB rrcReconfiguration\n"); + return; + } + NR_RRCReconfiguration_t *rrcReconf = NULL; + NR_AS_Config_t *sourceConfig = hpi->criticalExtensions.choice.c1->choice.handoverPreparationInformation->sourceConfig; + asn_dec_rval_t rrcReconf_dec_rval = uper_decode_complete(NULL, + &asn_DEF_NR_RRCReconfiguration, + (void **)&rrcReconf, + (uint8_t *)sourceConfig->rrcReconfiguration.buf, + sourceConfig->rrcReconfiguration.size); + if (rrcReconf_dec_rval.code != RC_OK || rrcReconf_dec_rval.consumed < 0) { + LOG_E(NR_RRC, "Failed to decode source gNB rrcReconfiguration!\n"); + return; + } + + if (rrcReconf->criticalExtensions.choice.rrcReconfiguration->measConfig == NULL) + return; + + NR_MeasConfig_t *sourceMC = rrcReconf->criticalExtensions.choice.rrcReconfiguration->measConfig; + + /* Add measurement objects, report configurations, and measurement IDs + to the UE's measConfig removal lists, and update UE->measConfig */ + if (sourceMC->measObjectToAddModList->list.count > 0) { + currentMC->measObjectToRemoveList = calloc_or_fail(1, sizeof(*currentMC->measObjectToRemoveList)); + for (int i = 0; i < sourceMC->measObjectToAddModList->list.count; i++) { + NR_MeasObjectId_t *measObjId = calloc_or_fail(1, sizeof(NR_MeasObjectId_t)); + *measObjId = sourceMC->measObjectToAddModList->list.array[i]->measObjectId; + asn1cSeqAdd(¤tMC->measObjectToRemoveList->list, measObjId); + } + } + + if (sourceMC->reportConfigToAddModList->list.count > 0) { + currentMC->reportConfigToRemoveList = calloc_or_fail(1, sizeof(*currentMC->reportConfigToRemoveList)); + for (int i = 0; i < sourceMC->reportConfigToAddModList->list.count; i++) { + NR_ReportConfigId_t *reportConfigId = calloc_or_fail(1, sizeof(NR_ReportConfigId_t)); + *reportConfigId = sourceMC->reportConfigToAddModList->list.array[i]->reportConfigId; + asn1cSeqAdd(¤tMC->reportConfigToRemoveList->list, reportConfigId); + } + } + + if (sourceMC->measIdToAddModList->list.count > 0) { + currentMC->measIdToRemoveList = calloc_or_fail(1, sizeof(*currentMC->measIdToRemoveList)); + for (int i = 0; i < sourceMC->measIdToAddModList->list.count; i++) { + NR_MeasId_t *measId = calloc_or_fail(1, sizeof(NR_MeasId_t)); + *measId = sourceMC->measIdToAddModList->list.array[i]->measId; + asn1cSeqAdd(¤tMC->measIdToRemoveList->list, measId); + } + } +} + +byte_array_t get_HandoverCommandMessage(nr_rrc_reconfig_param_t *params) +{ + // Encode RRCReconfiguration to buffer + byte_array_t msg = do_HO_RRCReconfiguration(params); + + // Add RRCReconfiguration to handoverCommand + NR_HandoverCommand_t HoCommand = {0}; + HoCommand.criticalExtensions.present = NR_HandoverCommand__criticalExtensions_PR_c1; + asn1cCalloc(HoCommand.criticalExtensions.choice.c1, c1); + c1->present = NR_HandoverCommand__criticalExtensions__c1_PR_handoverCommand; + asn1cCalloc(HoCommand.criticalExtensions.choice.c1->choice.handoverCommand, hc); + OCTET_STRING_fromBuf(&hc->handoverCommandMessage, (const char *)msg.buf, msg.len); + free_byte_array(msg); + + if (LOG_DEBUGFLAG(DEBUG_ASN1)) + xer_fprint(stdout, &asn_DEF_NR_HandoverCommand, (void *)&HoCommand); + + // Encode handoverCommand to buffer + byte_array_t buffer = {.len = -1, .buf = NULL}; + buffer.len = uper_encode_to_new_buffer(&asn_DEF_NR_HandoverCommand, NULL, (void *)&HoCommand, (void **)&buffer.buf); + ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_NR_HandoverCommand, &HoCommand); + + return buffer; +} + /** @brief HandoverPreparationInformation message (11.2.2 of TS 38.331) * 1) generate RRCReconfiguration to be transferred * 2) encodes UE Capabilities from UE Context diff --git a/openair2/RRC/NR/MESSAGES/asn1_msg.h b/openair2/RRC/NR/MESSAGES/asn1_msg.h index aab65f6685..668f2f8ade 100644 --- a/openair2/RRC/NR/MESSAGES/asn1_msg.h +++ b/openair2/RRC/NR/MESSAGES/asn1_msg.h @@ -163,5 +163,7 @@ void free_MeasConfig(NR_MeasConfig_t *mc); int do_NR_Paging(uint8_t Mod_id, uint8_t *buffer, uint32_t tmsi); byte_array_t get_HandoverPreparationInformation(nr_rrc_reconfig_param_t *params, int scell_pci); +byte_array_t get_HandoverCommandMessage(nr_rrc_reconfig_param_t *params); +void fill_removal_lists_from_source_measConfig(NR_MeasConfig_t *measConfig, byte_array_t prep_info); #endif /* __RRC_NR_MESSAGES_ASN1_MSG__H__ */ diff --git a/openair2/RRC/NR/rrc_gNB_mobility.c b/openair2/RRC/NR/rrc_gNB_mobility.c index c9d60422e8..bf6ecb5865 100644 --- a/openair2/RRC/NR/rrc_gNB_mobility.c +++ b/openair2/RRC/NR/rrc_gNB_mobility.c @@ -408,6 +408,73 @@ static byte_array_t rrc_gNB_generate_HandoverPreparationInformation(gNB_RRC_INST return hoPrepInfo; } +static byte_array_t rrc_gNB_encode_HandoverCommand(gNB_RRC_UE_t *UE, gNB_RRC_INST *rrc) +{ + DevAssert(UE->ho_context); + DevAssert(UE->ho_context->target); + + NR_SecurityConfig_t *sec = calloc_or_fail(1, sizeof(*sec)); + sec->keyToUse = calloc_or_fail(1, sizeof(*sec->keyToUse)); + *sec->keyToUse = NR_SecurityConfig__keyToUse_master; + sec->securityAlgorithmConfig = calloc_or_fail(1, sizeof(*sec->securityAlgorithmConfig)); + struct NR_SecurityAlgorithmConfig *alg = sec->securityAlgorithmConfig; + alg->cipheringAlgorithm = UE->ciphering_algorithm; + alg->integrityProtAlgorithm = calloc_or_fail(1, sizeof(*alg->integrityProtAlgorithm)); + *alg->integrityProtAlgorithm = UE->integrity_algorithm; + + // Mark source gNB's measurement configuration for removal in UE->measConfig + if (UE->ho_context->target->ue_ho_prep_info.len) { + fill_removal_lists_from_source_measConfig(UE->measConfig, UE->ho_context->target->ue_ho_prep_info); + } + + /* 3GPP TS 38.331 RadioBearerConfig: re-establish PDCP whenever + the security key used for this radio bearer changes, e.g. for SRB2 + when receiving reconfiguration with sync [...]. Similarly, for DRBs + PDCP re-establishment is necessary with derivation of new UP keys */ + nr_rrc_reconfig_param_t params = get_RRCReconfiguration_params(rrc, UE, (1 << 1) | (1 << 2), true); + UE->xids[params.transaction_id] = RRC_DEDICATED_RECONF; + + params.security_config = sec; + params.masterKeyUpdate = true; + params.nextHopChainingCount = UE->nh_ncc; + byte_array_t out = get_HandoverCommandMessage(¶ms); + + // Free remove lists in UE->measConfig + ASN_STRUCT_FREE(asn_DEF_NR_MeasIdToRemoveList, UE->measConfig->measIdToRemoveList); + ASN_STRUCT_FREE(asn_DEF_NR_ReportConfigToRemoveList, UE->measConfig->reportConfigToRemoveList); + ASN_STRUCT_FREE(asn_DEF_NR_MeasObjectToRemoveList, UE->measConfig->measObjectToRemoveList); + + free_RRCReconfiguration_params(params); + return out; +} + +/** @brief This callback is used by the target gNB + * to trigger the Handover Request Acknowledge towards the AMF */ +static void nr_rrc_n2_ho_acknowledge(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE) +{ + AssertFatal(cu_exists_f1_ue_data(UE->rrc_ue_id), "No CU found for rrc_ue_id %d\n", UE->rrc_ue_id); + + f1_ue_data_t previous_data = cu_get_f1_ue_data(UE->rrc_ue_id); + /* this is the callback for N2 handover after F1 UE context setup response in + * the handover case. Check that there was no associated DU, then set it */ + AssertFatal(previous_data.secondary_ue == -1, "there was already a DU present\n"); + previous_data.secondary_ue = UE->ho_context->target->du_ue_id; + bool success = cu_update_f1_ue_data(UE->rrc_ue_id, &previous_data); + DevAssert(success); + LOG_I(NR_RRC, "Updated CU F1AP Context for UE %d, DU Id : %u\n", UE->rrc_ue_id, UE->ho_context->target->du_ue_id); + + byte_array_t hoCommand = rrc_gNB_encode_HandoverCommand(UE, rrc); + if (hoCommand.len < 0) { + LOG_E(NR_RRC, "ASN1 message encoding failed: failed to generate Handover Command Message\n"); + return; + } else { + LOG_D(NR_RRC, "HO LOG: Handover Command for UE %u Encoded (%ld bytes)\n", UE->rrc_ue_id, hoCommand.len); + } + + rrc_gNB_send_NGAP_HANDOVER_REQUEST_ACKNOWLEDGE(rrc, UE, hoCommand); + free_byte_array(hoCommand); +} + /** @brief Callback function to trigger NG Handover Failure on the target gNB, to inform the AMF * that the preparation of resources has failed (e.g. unsatisfied criteria, gNB is already loaded). * This message represents an Unsuccessful Outcome of the Handover Resource Allocation */ diff --git a/openair2/RRC/NR/rrc_gNB_mobility.h b/openair2/RRC/NR/rrc_gNB_mobility.h index 77b90ac3e0..4f31c433ec 100644 --- a/openair2/RRC/NR/rrc_gNB_mobility.h +++ b/openair2/RRC/NR/rrc_gNB_mobility.h @@ -53,6 +53,7 @@ typedef struct nr_ho_source_cu { /* acknowledgement of handover request. buf+len is the RRC Reconfiguration */ typedef void (*ho_req_ack_t)(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue); typedef void (*ho_success_t)(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue); +typedef void (*ho_failure_t)(gNB_RRC_INST *rrc, uint32_t gnb_ue_id, ngap_handover_failure_t *msg); typedef struct nr_ho_target_cu { /// pointer to the (target) DU structure From f66524879db7344aad3bbe3165aa5f8723238264 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Fri, 14 Mar 2025 11:26:16 +0100 Subject: [PATCH 34/52] Add NGAP Handover Command decoder and RRC handler on source gNB * add NG Handover Command message decoder * process in RRC: encode RRCReconfiguration message from the received HandoverCommandMessage * trigger RRCReconfiguration for handover Co-authored-by: batuhan duyuler --- openair2/COMMON/ngap_messages_def.h | 1 + openair2/COMMON/ngap_messages_types.h | 32 +++++++ openair2/RRC/NR/MESSAGES/asn1_msg.c | 39 ++++++++ openair2/RRC/NR/MESSAGES/asn1_msg.h | 1 + openair2/RRC/NR/rrc_gNB.c | 6 ++ openair2/RRC/NR/rrc_gNB_NGAP.c | 28 ++++++ openair2/RRC/NR/rrc_gNB_NGAP.h | 3 + openair2/RRC/NR/rrc_gNB_mobility.c | 3 +- openair2/RRC/NR/rrc_gNB_mobility.h | 2 + openair3/NGAP/ngap_gNB_handlers.c | 56 +++++++++++- .../NGAP/ngap_gNB_management_procedures.c | 8 ++ .../NGAP/ngap_gNB_management_procedures.h | 2 + openair3/NGAP/ngap_gNB_mobility_management.c | 88 +++++++++++++++++++ openair3/NGAP/ngap_gNB_mobility_management.h | 2 + 14 files changed, 269 insertions(+), 2 deletions(-) diff --git a/openair2/COMMON/ngap_messages_def.h b/openair2/COMMON/ngap_messages_def.h index e9e185e152..826c892533 100644 --- a/openair2/COMMON/ngap_messages_def.h +++ b/openair2/COMMON/ngap_messages_def.h @@ -85,6 +85,7 @@ MESSAGE_DEF(NGAP_PDUSESSION_MODIFY_REQ , MESSAGE_PRIORITY_MED, ngap_pd MESSAGE_DEF(NGAP_PDUSESSION_RELEASE_COMMAND , MESSAGE_PRIORITY_MED, ngap_pdusession_release_command_t , ngap_pdusession_release_command) MESSAGE_DEF(NGAP_UE_CONTEXT_RELEASE_COMMAND, MESSAGE_PRIORITY_MED, ngap_ue_release_command_t , ngap_ue_release_command) MESSAGE_DEF(NGAP_HANDOVER_REQUEST, MESSAGE_PRIORITY_MED, ngap_handover_request_t, ngap_handover_request) +MESSAGE_DEF(NGAP_HANDOVER_COMMAND, MESSAGE_PRIORITY_MED, ngap_handover_command_t, ngap_handover_command) /* NGAP <-> RRC messages (can be initiated either by MME or gNB) */ MESSAGE_DEF(NGAP_UE_CONTEXT_RELEASE_REQ , MESSAGE_PRIORITY_MED, ngap_ue_release_req_t , ngap_ue_release_req) diff --git a/openair2/COMMON/ngap_messages_types.h b/openair2/COMMON/ngap_messages_types.h index 5a5937500d..5d53c5c822 100644 --- a/openair2/COMMON/ngap_messages_types.h +++ b/openair2/COMMON/ngap_messages_types.h @@ -69,6 +69,7 @@ #define NGAP_HANDOVER_FAILURE(mSGpTR) (mSGpTR)->ittiMsg.ngap_handover_failure #define NGAP_HANDOVER_REQUEST(mSGpTR) (mSGpTR)->ittiMsg.ngap_handover_request #define NGAP_HANDOVER_REQUEST_ACKNOWLEDGE(mSGpTR) (mSGpTR)->ittiMsg.ngap_handover_request_ack +#define NGAP_HANDOVER_COMMAND(mSGpTR) (mSGpTR)->ittiMsg.ngap_handover_command #define NGAP_UE_CONTEXT_RELEASE_REQ(mSGpTR) (mSGpTR)->ittiMsg.ngap_ue_release_req #define NGAP_PDUSESSION_RELEASE_COMMAND(mSGpTR) (mSGpTR)->ittiMsg.ngap_pdusession_release_command @@ -648,6 +649,37 @@ typedef struct { byte_array_t target2source; } ngap_handover_request_ack_t; +/* 9.3.4.10 3GPP TS 38.413 */ +typedef struct { + // QoS Flow to be Forwarded List + uint8_t nb_of_qos_flow; + pdusession_associate_qosflow_t qos_setup_list[QOSFLOW_MAX_VALUE]; + // UL Forwarding UP TNL Information + uint32_t gtp_teid; + transport_layer_addr_t gNB_addr; +} ho_command_transfer_t; + +typedef struct { + // PDU Session ID (M) + uint8_t pdusession_id; + // Handover Command Transfer (M) + ho_command_transfer_t ho_command_transfer; +} pdusession_resource_handover_t; + +typedef struct { + // AMF UE NGAP ID + uint64_t amf_ue_ngap_id; + // RAN UE NGAP ID + uint32_t gNB_ue_ngap_id; + // Handover Type + ho_type_t handoverType; + // PDU Session Resource Handover List + uint8_t nb_of_pdusessions; + pdusession_resource_handover_t pdu_sessions[NGAP_MAX_PDU_SESSION]; + // Target to Source Transparent Container + byte_array_t handoverCommand; +} ngap_handover_command_t; + typedef struct ngap_ue_cap_info_ind_s { uint32_t gNB_ue_ngap_id; byte_array_t ue_radio_cap; diff --git a/openair2/RRC/NR/MESSAGES/asn1_msg.c b/openair2/RRC/NR/MESSAGES/asn1_msg.c index fc3ca002f4..20bf56390b 100644 --- a/openair2/RRC/NR/MESSAGES/asn1_msg.c +++ b/openair2/RRC/NR/MESSAGES/asn1_msg.c @@ -64,6 +64,10 @@ #include "NR_LogicalChannelConfig.h" #include "NR_PDCP-Config.h" #include "NR_MAC-CellGroupConfig.h" +#include "NR_HandoverPreparationInformation.h" +#include "NR_HandoverCommand.h" +#include "NR_HandoverCommand-IEs.h" +#include "NR_AS-Config.h" #include "NR_SecurityModeCommand.h" #include "NR_CipheringAlgorithm.h" #include "NR_RRCReconfiguration-IEs.h" @@ -1221,6 +1225,41 @@ void fill_removal_lists_from_source_measConfig(NR_MeasConfig_t *currentMC, byte_ } } +int doRRCReconfiguration_from_HandoverCommand(byte_array_t *ba, const byte_array_t handoverCommand) +{ + // Decode Handover Command + NR_HandoverCommand_t *hoCommand = NULL; + asn_dec_rval_t dec_rval = uper_decode_complete(NULL, &asn_DEF_NR_HandoverCommand, (void **)&hoCommand, handoverCommand.buf, handoverCommand.len); + + if (dec_rval.code != RC_OK && dec_rval.consumed == 0) { + LOG_E(NR_RRC, "Can not decode Handover Command!\n"); + return -1; + } + + // Prepare DL DCCH message for RRCReconfiguration + NR_DL_DCCH_Message_t dl_dcch_msg = {0}; + asn_enc_rval_t enc_rval; + dl_dcch_msg.message.present = NR_DL_DCCH_MessageType_PR_c1; + asn1cCalloc(dl_dcch_msg.message.choice.c1, c1); + c1->present = NR_DL_DCCH_MessageType__c1_PR_rrcReconfiguration; + asn1cCalloc(c1->choice.rrcReconfiguration, rrcReconf); + + // Decode RRCReconfiguration from handoverCommandMessage + OCTET_STRING_t *ho = &hoCommand->criticalExtensions.choice.c1->choice.handoverCommand->handoverCommandMessage; + uper_decode_complete(NULL, &asn_DEF_NR_RRCReconfiguration, (void **)&rrcReconf, ho->buf, ho->size); + if (LOG_DEBUGFLAG(DEBUG_ASN1)) + xer_fprint(stdout, &asn_DEF_NR_DL_DCCH_Message, (void *)&dl_dcch_msg); + + // Encode DL DCCH message in buffer + enc_rval = uper_encode_to_buffer(&asn_DEF_NR_DL_DCCH_Message, NULL, &dl_dcch_msg, ba->buf, ba->len); + AssertFatal(enc_rval.encoded > 0, "ASN1 message encoding failed (%s, %lu)!\n", enc_rval.failed_type->name, enc_rval.encoded); + + ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_NR_DL_DCCH_Message, &dl_dcch_msg); + ASN_STRUCT_FREE(asn_DEF_NR_HandoverCommand, hoCommand); + + return ((enc_rval.encoded + 7) / 8); +} + byte_array_t get_HandoverCommandMessage(nr_rrc_reconfig_param_t *params) { // Encode RRCReconfiguration to buffer diff --git a/openair2/RRC/NR/MESSAGES/asn1_msg.h b/openair2/RRC/NR/MESSAGES/asn1_msg.h index 668f2f8ade..665edeef4b 100644 --- a/openair2/RRC/NR/MESSAGES/asn1_msg.h +++ b/openair2/RRC/NR/MESSAGES/asn1_msg.h @@ -165,5 +165,6 @@ int do_NR_Paging(uint8_t Mod_id, uint8_t *buffer, uint32_t tmsi); byte_array_t get_HandoverPreparationInformation(nr_rrc_reconfig_param_t *params, int scell_pci); byte_array_t get_HandoverCommandMessage(nr_rrc_reconfig_param_t *params); void fill_removal_lists_from_source_measConfig(NR_MeasConfig_t *measConfig, byte_array_t prep_info); +int doRRCReconfiguration_from_HandoverCommand(byte_array_t *ba, byte_array_t handoverCommand); #endif /* __RRC_NR_MESSAGES_ASN1_MSG__H__ */ diff --git a/openair2/RRC/NR/rrc_gNB.c b/openair2/RRC/NR/rrc_gNB.c index 48ea8e760e..32021d7f22 100644 --- a/openair2/RRC/NR/rrc_gNB.c +++ b/openair2/RRC/NR/rrc_gNB.c @@ -98,6 +98,7 @@ #include "NR_DL-DCCH-Message.h" #include "ds/byte_array.h" #include "alg/find.h" +#include "NR_HandoverCommand.h" #ifdef E2_AGENT #include "openair2/E2AP/RAN_FUNCTION/O-RAN/ran_func_rc_extern.h" @@ -2949,6 +2950,11 @@ void *rrc_gnb_task(void *args_p) { rrc_gNB_free_Handover_Request(&NGAP_HANDOVER_REQUEST(msg_p)); // Free transfered NG message break; + case NGAP_HANDOVER_COMMAND: + rrc_gNB_process_HandoverCommand(RC.nrrrc[instance], &NGAP_HANDOVER_COMMAND(msg_p)); + rrc_gNB_free_Handover_Command(&NGAP_HANDOVER_COMMAND(msg_p)); // Free transfered NG message + break; + default: LOG_E(NR_RRC, "[gNB %ld] Received unexpected message %s\n", instance, msg_name_p); break; diff --git a/openair2/RRC/NR/rrc_gNB_NGAP.c b/openair2/RRC/NR/rrc_gNB_NGAP.c index 19cd12704f..889cd810b2 100644 --- a/openair2/RRC/NR/rrc_gNB_NGAP.c +++ b/openair2/RRC/NR/rrc_gNB_NGAP.c @@ -39,6 +39,9 @@ #include #include "E1AP_ConfidentialityProtectionIndication.h" #include "E1AP_IntegrityProtectionIndication.h" +#include "NR_UE-CapabilityRAT-ContainerList.h" +#include "NR_HandoverCommand.h" +#include "NR_HandoverCommand-IEs.h" #include "NGAP_CauseRadioNetwork.h" #include "NGAP_Dynamic5QIDescriptor.h" #include "NGAP_GTPTunnel.h" @@ -1161,6 +1164,31 @@ void rrc_gNB_free_Handover_Request(ngap_handover_request_t *msg) free(msg->mobility_restriction); } +/** @brief Process NG Handover Command on Source gNB */ +void rrc_gNB_process_HandoverCommand(gNB_RRC_INST *rrc, const ngap_handover_command_t *msg) +{ + rrc_gNB_ue_context_t *ue_context_p = rrc_gNB_get_ue_context(rrc, msg->gNB_ue_ngap_id); + + if (ue_context_p == NULL) { + LOG_W(NR_RRC, "Unknown UE context associated to gNB_ue_ngap_id (%u)\n", msg->gNB_ue_ngap_id); + return; + } + gNB_RRC_UE_t *UE = &ue_context_p->ue_context; + + uint8_t buffer[NR_RRC_BUF_SIZE]; + byte_array_t ba = {.buf = buffer, .len = sizeof(buffer)}; + int enc = doRRCReconfiguration_from_HandoverCommand(&ba, msg->handoverCommand); + DevAssert(enc > 0); + + rrc_gNB_trigger_reconfiguration_for_handover(rrc, UE, buffer, enc); + LOG_A(NR_RRC, "Send reconfiguration (HO Command) to UE %u/RNTI %04x\n", UE->rrc_ue_id, UE->rnti); +} + +void rrc_gNB_free_Handover_Command(ngap_handover_command_t *msg) +{ + free_byte_array(msg->handoverCommand); +} + /* * Process the NG command NGAP_UE_CONTEXT_RELEASE_COMMAND, sent by AMF. * The gNB should remove all pdu session, NG context, and other context of the UE. diff --git a/openair2/RRC/NR/rrc_gNB_NGAP.h b/openair2/RRC/NR/rrc_gNB_NGAP.h index 9d51db4186..c249711629 100644 --- a/openair2/RRC/NR/rrc_gNB_NGAP.h +++ b/openair2/RRC/NR/rrc_gNB_NGAP.h @@ -101,4 +101,7 @@ void rrc_gNB_free_Handover_Request(ngap_handover_request_t *msg); void rrc_gNB_send_NGAP_HANDOVER_REQUEST_ACKNOWLEDGE(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, byte_array_t ho_command); +void rrc_gNB_process_HandoverCommand(gNB_RRC_INST *rrc, const ngap_handover_command_t *msg); +void rrc_gNB_free_Handover_Command(ngap_handover_command_t *msg); + #endif diff --git a/openair2/RRC/NR/rrc_gNB_mobility.c b/openair2/RRC/NR/rrc_gNB_mobility.c index bf6ecb5865..9e35b976e0 100644 --- a/openair2/RRC/NR/rrc_gNB_mobility.c +++ b/openair2/RRC/NR/rrc_gNB_mobility.c @@ -231,7 +231,8 @@ static void rrc_deliver_ue_ctxt_modif_req(void *deliver_pdu_data, ue_id_t ue_id, data->modification_req->rrc_container = &ba; data->rrc->mac_rrc.ue_context_modification_request(data->assoc_id, data->modification_req); } -static void rrc_gNB_trigger_reconfiguration_for_handover(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue, uint8_t *rrc_reconf, int rrc_reconf_len) + +void rrc_gNB_trigger_reconfiguration_for_handover(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue, uint8_t *rrc_reconf, int rrc_reconf_len) { f1_ue_data_t ue_data = cu_get_f1_ue_data(ue->rrc_ue_id); diff --git a/openair2/RRC/NR/rrc_gNB_mobility.h b/openair2/RRC/NR/rrc_gNB_mobility.h index 4f31c433ec..5a6542132c 100644 --- a/openair2/RRC/NR/rrc_gNB_mobility.h +++ b/openair2/RRC/NR/rrc_gNB_mobility.h @@ -89,4 +89,6 @@ void nr_rrc_trigger_n2_ho(gNB_RRC_INST *rrc, int serving_pci, const nr_neighbour_cell_t *neighbour_config); +void rrc_gNB_trigger_reconfiguration_for_handover(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue, uint8_t *rrc_reconf, int rrc_reconf_len); + #endif /* RRC_GNB_MOBILITY_H_ */ diff --git a/openair3/NGAP/ngap_gNB_handlers.c b/openair3/NGAP/ngap_gNB_handlers.c index 485d37236a..226c8e8fa0 100644 --- a/openair3/NGAP/ngap_gNB_handlers.c +++ b/openair3/NGAP/ngap_gNB_handlers.c @@ -997,6 +997,60 @@ static int ngap_gNB_handle_pdusession_setup_request(sctp_assoc_t assoc_id, uint3 return 0; } +/** @brief Handler for the NG Handover Command + * AMF -> source NG-RAN */ +static int ngap_gNB_handle_handover_command(sctp_assoc_t assoc_id, uint32_t stream, NGAP_NGAP_PDU_t *pdu) +{ + NGAP_INFO("Received NG Handover Command\n"); + ngap_gNB_amf_data_t *amf_desc_p = NULL; + DevAssert(pdu != NULL); + + if ((amf_desc_p = ngap_gNB_get_AMF(NULL, assoc_id, 0)) == NULL) { + NGAP_ERROR( + "[SCTP %u] Received Handover Command for non " + "existing AMF context\n", + assoc_id); + return -1; + } + + MessageDef *message_p = itti_alloc_new_message(TASK_NGAP, 0, NGAP_HANDOVER_COMMAND); + ngap_handover_command_t *msg = &NGAP_HANDOVER_COMMAND(message_p); + memset(msg, 0, sizeof(*msg)); + if (decode_ng_handover_command(msg, pdu) < 0) { + NGAP_ERROR("Failed to decode NG Handover Command"); + free_ng_handover_command(msg); + itti_free(TASK_NGAP, message_p); + return -1; + } + + ngap_gNB_ue_context_t *ue_desc_p = ngap_get_ue_context(msg->gNB_ue_ngap_id); + if (!ue_desc_p) { + NGAP_ERROR( + "[SCTP %u] Received HO Command Msg for non existing UE context (gNB_ue_ngap_id %d) \n", + assoc_id, + msg->gNB_ue_ngap_id); + free_ng_handover_command(msg); + itti_free(TASK_NGAP, message_p); + return -1; + } + + ue_desc_p->rx_stream = stream; + + if (ue_desc_p->amf_ue_ngap_id != msg->amf_ue_ngap_id) { + NGAP_ERROR("UE context amf_ue_ngap_id is different form that of the message (%ld != %ld)", + ue_desc_p->amf_ue_ngap_id, + msg->amf_ue_ngap_id); + free_ng_handover_command(msg); + itti_free(TASK_NGAP, message_p); + return -1; + } + + NGAP_DEBUG("Handover Command is parsed with a size of: %ld. Sending to RRC GNB!\n", msg->handoverCommand.len); + itti_send_msg_to_task(TASK_RRC_GNB, amf_desc_p->ngap_gNB_instance->instance, message_p); + + return 0; +} + static int ngap_gNB_handle_paging(sctp_assoc_t assoc_id, uint32_t stream, NGAP_NGAP_PDU_t *pdu) { ngap_gNB_amf_data_t *amf_desc_p = NULL; @@ -1338,7 +1392,7 @@ const ngap_message_decoded_callback ngap_messages_callback[][3] = { {ngap_gNB_handle_error_indication, 0, 0}, /* ErrorIndication */ {0, 0, 0}, /* HandoverCancel */ {0, 0, 0}, /* HandoverNotification */ - {0, 0, 0}, /* HandoverPreparation */ + {0, ngap_gNB_handle_handover_command, 0}, /* HandoverPreparation */ {ngap_gNB_handle_handover_request, 0, 0}, /* HandoverResourceAllocation */ {ngap_gNB_handle_initial_context_request, 0, 0}, /* InitialContextSetup */ {0, 0, 0}, /* InitialUEMessage */ diff --git a/openair3/NGAP/ngap_gNB_management_procedures.c b/openair3/NGAP/ngap_gNB_management_procedures.c index 6ffce99d9c..9d8748a88b 100644 --- a/openair3/NGAP/ngap_gNB_management_procedures.c +++ b/openair3/NGAP/ngap_gNB_management_procedures.c @@ -200,3 +200,11 @@ void tnl_to_bitstring(BIT_STRING_t *out, const transport_layer_addr_t in) out->bits_unused = 0; } } + +void bitstring_to_tnl(transport_layer_addr_t *out, const BIT_STRING_t in) +{ + if (in.size) { + memcpy(out->buffer, in.buf, in.size); + out->length = in.size; + } +} diff --git a/openair3/NGAP/ngap_gNB_management_procedures.h b/openair3/NGAP/ngap_gNB_management_procedures.h index 365fd0dd9d..28f4b24e7e 100644 --- a/openair3/NGAP/ngap_gNB_management_procedures.h +++ b/openair3/NGAP/ngap_gNB_management_procedures.h @@ -50,4 +50,6 @@ void ngap_gNB_prepare_internal_data(void); void tnl_to_bitstring(BIT_STRING_t *out, const transport_layer_addr_t in); +void bitstring_to_tnl(transport_layer_addr_t *out, const BIT_STRING_t in); + #endif /* NGAP_GNB_MANAGEMENT_PROCEDURES_H_ */ diff --git a/openair3/NGAP/ngap_gNB_mobility_management.c b/openair3/NGAP/ngap_gNB_mobility_management.c index 4c64659e01..68a6823d98 100644 --- a/openair3/NGAP/ngap_gNB_mobility_management.c +++ b/openair3/NGAP/ngap_gNB_mobility_management.c @@ -472,3 +472,91 @@ void free_ng_handover_req_ack(ngap_handover_request_ack_t *msg) { free_byte_array(msg->target2source); } + +/** @brief Decoder for the NG Handover Command */ +int decode_ng_handover_command(ngap_handover_command_t *msg, NGAP_NGAP_PDU_t *pdu) +{ + DevAssert(pdu != NULL); + NGAP_HandoverCommandIEs_t *ie; + NGAP_HandoverCommand_t *container = &pdu->choice.successfulOutcome->value.choice.HandoverCommand; + + // AMF UE NGAP ID (M) + NGAP_FIND_PROTOCOLIE_BY_ID(NGAP_HandoverCommandIEs_t, ie, container, NGAP_ProtocolIE_ID_id_AMF_UE_NGAP_ID, true); + asn_INTEGER2ulong(&(ie->value.choice.AMF_UE_NGAP_ID), &msg->amf_ue_ngap_id); + + // RAN UE NGAP ID (M) + NGAP_FIND_PROTOCOLIE_BY_ID(NGAP_HandoverCommandIEs_t, ie, container, NGAP_ProtocolIE_ID_id_RAN_UE_NGAP_ID, true); + msg->gNB_ue_ngap_id = ie->value.choice.RAN_UE_NGAP_ID; + + // Handover Type (M) + NGAP_FIND_PROTOCOLIE_BY_ID(NGAP_HandoverCommandIEs_t, ie, container, NGAP_ProtocolIE_ID_id_HandoverType, true); + if (ie->value.choice.HandoverType != HANDOVER_TYPE_INTRA5GS) { + NGAP_ERROR("Only Intra 5GS Handover is supported at the moment!\n"); + return -1; + } + + // PDU Session Resource Handover List (O) + NGAP_FIND_PROTOCOLIE_BY_ID(NGAP_HandoverCommandIEs_t, ie, container, NGAP_ProtocolIE_ID_id_HandoverType, false); + if (ie != NULL) { + msg->nb_of_pdusessions = ie->value.choice.PDUSessionResourceHandoverList.list.count; + + for (int i = 0; i < msg->nb_of_pdusessions; ++i) { + NGAP_PDUSessionResourceHandoverItem_t *item = ie->value.choice.PDUSessionResourceHandoverList.list.array[i]; + + // PDU Session ID (M) + msg->pdu_sessions[i].pdusession_id = item->pDUSessionID; + + // Handover Command Transfer (M) + NGAP_HandoverCommandTransfer_t *hoCommandTransfer = NULL; + asn_dec_rval_t dec_rval = uper_decode_complete(NULL, + &asn_DEF_NGAP_HandoverCommandTransfer, + (void **)&hoCommandTransfer, + item->handoverCommandTransfer.buf, + item->handoverCommandTransfer.size); + if (dec_rval.code != RC_OK) { + NGAP_ERROR("Failed to decode handoverCommandTransfer\n"); + continue; + } + + // DL Forwarding UP TNL Information (O) + if (hoCommandTransfer->dLForwardingUP_TNLInformation) { + NGAP_UPTransportLayerInformation_t *up_tnl = hoCommandTransfer->dLForwardingUP_TNLInformation; + if (hoCommandTransfer->dLForwardingUP_TNLInformation->present == NGAP_UPTransportLayerInformation_PR_gTPTunnel) { + OCTET_STRING_TO_INT32(&(up_tnl->choice.gTPTunnel->gTP_TEID), msg->pdu_sessions[i].ho_command_transfer.gtp_teid); + bitstring_to_tnl(&msg->pdu_sessions[i].ho_command_transfer.gNB_addr, up_tnl->choice.gTPTunnel->transportLayerAddress); + } else { + NGAP_WARN("Missing DL Forwarding UP TNL Information\n"); + } + } + ASN_STRUCT_FREE(asn_DEF_NGAP_HandoverCommandTransfer, hoCommandTransfer); + } + } + + // Target to Source Transparent Container (M) + NGAP_FIND_PROTOCOLIE_BY_ID(NGAP_HandoverCommandIEs_t, + ie, + container, + NGAP_ProtocolIE_ID_id_TargetToSource_TransparentContainer, + true); + NGAP_TargetToSource_TransparentContainer_t *target2source = &(ie->value.choice.TargetToSource_TransparentContainer); + NGAP_TargetNGRANNode_ToSourceNGRANNode_TransparentContainer_t *transparentContainer = NULL; + asn_dec_rval_t dec_rval = aper_decode_complete(NULL, + &asn_DEF_NGAP_TargetNGRANNode_ToSourceNGRANNode_TransparentContainer, + (void **)&transparentContainer, + target2source->buf, + target2source->size); + if (dec_rval.code != RC_OK) { + NGAP_ERROR("Failed to decode TargetToSource_TransparentContainer\n"); + return -1; + } + + msg->handoverCommand = create_byte_array(transparentContainer->rRCContainer.size, transparentContainer->rRCContainer.buf); + ASN_STRUCT_FREE(asn_DEF_NGAP_TargetNGRANNode_ToSourceNGRANNode_TransparentContainer, transparentContainer); + + return 0; +} + +void free_ng_handover_command(ngap_handover_command_t *msg) +{ + free_byte_array(msg->handoverCommand); +} diff --git a/openair3/NGAP/ngap_gNB_mobility_management.h b/openair3/NGAP/ngap_gNB_mobility_management.h index dd0061ca35..4ed3229a84 100644 --- a/openair3/NGAP/ngap_gNB_mobility_management.h +++ b/openair3/NGAP/ngap_gNB_mobility_management.h @@ -31,5 +31,7 @@ NGAP_NGAP_PDU_t *encode_ng_handover_failure(const ngap_handover_failure_t *msg); int decode_ng_handover_request(ngap_handover_request_t *out, const NGAP_NGAP_PDU_t *pdu); NGAP_NGAP_PDU_t *encode_ng_handover_request_ack(ngap_handover_request_ack_t *msg); void free_ng_handover_req_ack(ngap_handover_request_ack_t *msg); +int decode_ng_handover_command(ngap_handover_command_t *msg, NGAP_NGAP_PDU_t *pdu); +void free_ng_handover_command(ngap_handover_command_t *msg); #endif /* NGAP_GNB_MOBILITY_MANAGEMENT_H_ */ From e611d092e9927f7b99c0ab5bd15d6f0585732355 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Fri, 14 Mar 2025 12:35:46 +0100 Subject: [PATCH 35/52] Add NGAP Handover Notify encoder and trigger on the target NG-RAN This commit introduces the NG Mobility Management Procedure known as Handover Notification. The outbound message is sent by the target NG-RAN and is known as Handover Notify. The procedure is used to indicate to the AMF that the UE has arrived to the target cell and the NG-based handover has been successfully completed * after RRCReconfiguration complete, in the N2 callback for HO success * add NGAP encoder for the message * RRC callback to trigger Handover Notify message Co-authored-by: batuhan duyuler --- openair2/COMMON/ngap_messages_def.h | 1 + openair2/COMMON/ngap_messages_types.h | 18 +++++++ openair2/RRC/NR/rrc_gNB_NGAP.c | 22 ++++++++ openair2/RRC/NR/rrc_gNB_NGAP.h | 2 + openair2/RRC/NR/rrc_gNB_mobility.c | 7 +++ openair3/NGAP/ngap_gNB.c | 48 +++++++++++++++++ openair3/NGAP/ngap_gNB_encoder.c | 3 +- openair3/NGAP/ngap_gNB_mobility_management.c | 57 ++++++++++++++++++++ openair3/NGAP/ngap_gNB_mobility_management.h | 1 + openair3/NGAP/ngap_msg_includes.h | 1 + 10 files changed, 159 insertions(+), 1 deletion(-) diff --git a/openair2/COMMON/ngap_messages_def.h b/openair2/COMMON/ngap_messages_def.h index 826c892533..fc07cffc6d 100644 --- a/openair2/COMMON/ngap_messages_def.h +++ b/openair2/COMMON/ngap_messages_def.h @@ -74,6 +74,7 @@ MESSAGE_DEF(NGAP_PDUSESSION_RELEASE_RESPONSE , MESSAGE_PRIORITY_MED, ngap_pdu MESSAGE_DEF(NGAP_HANDOVER_REQUIRED, MESSAGE_PRIORITY_MED, ngap_handover_required_t, ngap_handover_required) MESSAGE_DEF(NGAP_HANDOVER_FAILURE, MESSAGE_PRIORITY_MED, ngap_handover_failure_t, ngap_handover_failure) MESSAGE_DEF(NGAP_HANDOVER_REQUEST_ACKNOWLEDGE, MESSAGE_PRIORITY_MED, ngap_handover_request_ack_t, ngap_handover_request_ack) +MESSAGE_DEF(NGAP_HANDOVER_NOTIFY, MESSAGE_PRIORITY_MED, ngap_handover_notify_t, ngap_handover_notify) /* NGAP -> RRC messages */ MESSAGE_DEF(NGAP_DOWNLINK_NAS , MESSAGE_PRIORITY_MED, ngap_downlink_nas_t , ngap_downlink_nas ) diff --git a/openair2/COMMON/ngap_messages_types.h b/openair2/COMMON/ngap_messages_types.h index 5d53c5c822..eb21935e03 100644 --- a/openair2/COMMON/ngap_messages_types.h +++ b/openair2/COMMON/ngap_messages_types.h @@ -70,6 +70,7 @@ #define NGAP_HANDOVER_REQUEST(mSGpTR) (mSGpTR)->ittiMsg.ngap_handover_request #define NGAP_HANDOVER_REQUEST_ACKNOWLEDGE(mSGpTR) (mSGpTR)->ittiMsg.ngap_handover_request_ack #define NGAP_HANDOVER_COMMAND(mSGpTR) (mSGpTR)->ittiMsg.ngap_handover_command +#define NGAP_HANDOVER_NOTIFY(mSGpTR) (mSGpTR)->ittiMsg.ngap_handover_notify #define NGAP_UE_CONTEXT_RELEASE_REQ(mSGpTR) (mSGpTR)->ittiMsg.ngap_ue_release_req #define NGAP_PDUSESSION_RELEASE_COMMAND(mSGpTR) (mSGpTR)->ittiMsg.ngap_pdusession_release_command @@ -680,6 +681,23 @@ typedef struct { byte_array_t handoverCommand; } ngap_handover_command_t; +/* 9.3.1.16 3GPP TS 38.413 */ +typedef struct { + // NR user location information + target_ran_node_id_t target_ng_ran; + uint32_t nrCellIdentity; +} user_location_information_t; + +/* 9.2.3.7 3GPP TS 38.413 */ +typedef struct { + // RAN UE NGAP ID + uint32_t gNB_ue_ngap_id; + // AMF UE NGAP ID + uint64_t amf_ue_ngap_id; + // User Location Information + user_location_information_t user_info; +} ngap_handover_notify_t; + typedef struct ngap_ue_cap_info_ind_s { uint32_t gNB_ue_ngap_id; byte_array_t ue_radio_cap; diff --git a/openair2/RRC/NR/rrc_gNB_NGAP.c b/openair2/RRC/NR/rrc_gNB_NGAP.c index 889cd810b2..e7459b1d49 100644 --- a/openair2/RRC/NR/rrc_gNB_NGAP.c +++ b/openair2/RRC/NR/rrc_gNB_NGAP.c @@ -1334,6 +1334,28 @@ void rrc_gNB_send_NGAP_HANDOVER_REQUEST_ACKNOWLEDGE(gNB_RRC_INST *rrc, gNB_RRC_U itti_send_msg_to_task(TASK_NGAP, rrc->module_id, msg_p); } +/** @brief Prepare NG Handover Notify message and inform NGAP */ +void rrc_gNB_send_NGAP_HANDOVER_NOTIFY(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE) +{ + LOG_I(NR_RRC, "Triggering NGAP Handover Notify\n"); + + MessageDef *msg_p = itti_alloc_new_message(TASK_RRC_GNB, 0, NGAP_HANDOVER_NOTIFY); + ngap_handover_notify_t *ho_notify = &NGAP_HANDOVER_NOTIFY(msg_p); + memset(ho_notify, 0, sizeof(*ho_notify)); + nr_rrc_du_container_t *du = get_du_by_cell_id(rrc, rrc->nr_cellid); + + ho_notify->gNB_ue_ngap_id = UE->rrc_ue_id; + ho_notify->amf_ue_ngap_id = UE->amf_ue_ngap_id; + ho_notify->user_info.nrCellIdentity = rrc->nr_cellid; + ho_notify->user_info.target_ng_ran.tac = *du->setup_req->cell->info.tac; + ho_notify->user_info.target_ng_ran.targetgNBId = rrc->node_id; + ho_notify->user_info.target_ng_ran.plmn_identity.mcc = du->setup_req->cell->info.plmn.mcc; + ho_notify->user_info.target_ng_ran.plmn_identity.mnc = du->setup_req->cell->info.plmn.mnc; + ho_notify->user_info.target_ng_ran.plmn_identity.mnc_digit_length = du->setup_req->cell->info.plmn.mnc_digit_length; + + itti_send_msg_to_task(TASK_NGAP, rrc->module_id, msg_p); +} + void rrc_gNB_send_NGAP_PDUSESSION_RELEASE_RESPONSE(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, uint8_t xid) { int pdu_sessions_released = 0; diff --git a/openair2/RRC/NR/rrc_gNB_NGAP.h b/openair2/RRC/NR/rrc_gNB_NGAP.h index c249711629..4d44b7197c 100644 --- a/openair2/RRC/NR/rrc_gNB_NGAP.h +++ b/openair2/RRC/NR/rrc_gNB_NGAP.h @@ -104,4 +104,6 @@ void rrc_gNB_send_NGAP_HANDOVER_REQUEST_ACKNOWLEDGE(gNB_RRC_INST *rrc, gNB_RRC_U void rrc_gNB_process_HandoverCommand(gNB_RRC_INST *rrc, const ngap_handover_command_t *msg); void rrc_gNB_free_Handover_Command(ngap_handover_command_t *msg); +void rrc_gNB_send_NGAP_HANDOVER_NOTIFY(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE); + #endif diff --git a/openair2/RRC/NR/rrc_gNB_mobility.c b/openair2/RRC/NR/rrc_gNB_mobility.c index 9e35b976e0..9e76217dc9 100644 --- a/openair2/RRC/NR/rrc_gNB_mobility.c +++ b/openair2/RRC/NR/rrc_gNB_mobility.c @@ -476,6 +476,13 @@ static void nr_rrc_n2_ho_acknowledge(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE) free_byte_array(hoCommand); } +/** @brief This callback is used by the target gNB + * to trigger the Handover Notify towards the AMF */ +static void nr_rrc_n2_ho_complete(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE) +{ + rrc_gNB_send_NGAP_HANDOVER_NOTIFY(rrc, UE); +} + /** @brief Callback function to trigger NG Handover Failure on the target gNB, to inform the AMF * that the preparation of resources has failed (e.g. unsatisfied criteria, gNB is already loaded). * This message represents an Unsuccessful Outcome of the Handover Resource Allocation */ diff --git a/openair3/NGAP/ngap_gNB.c b/openair3/NGAP/ngap_gNB.c index e80b914871..272759de4c 100644 --- a/openair3/NGAP/ngap_gNB.c +++ b/openair3/NGAP/ngap_gNB.c @@ -419,6 +419,50 @@ static int ngap_gNB_handover_request_acknowledge(instance_t instance, ngap_hando return 0; } +static int ngap_gNB_handover_notify(instance_t instance, const ngap_handover_notify_t *msg) +{ + LOG_D(NGAP, "Encode NGAP Handover Notify\n"); + ngap_gNB_ue_context_t *ue_context_p = NULL; + byte_array_t ba = { .buf = NULL, .len = 0}; + + /* Retrieve the NGAP gNB instance associated with Mod_id */ + ngap_gNB_instance_t *ngap_gNB_instance_p = ngap_gNB_get_instance(instance); + DevAssert(msg != NULL); + DevAssert(ngap_gNB_instance_p != NULL); + + if ((ue_context_p = ngap_get_ue_context(msg->gNB_ue_ngap_id)) == NULL) { + NGAP_ERROR("Failed to encode Handover Notify: no ue context associated with gNB_ue_ngap_id=%d\n", msg->gNB_ue_ngap_id); + return -1; + } + + if (ue_context_p->gNB_ue_ngap_id != msg->gNB_ue_ngap_id) { + NGAP_ERROR("Failed to encode Handover Notify: unknown gNB_ue_ngap_id=%d\n", msg->gNB_ue_ngap_id); + return -1; + } + + if (ue_context_p->amf_ue_ngap_id != msg->amf_ue_ngap_id) { + NGAP_ERROR("Failed to encode Handover Notify: unknown amf_ue_ngap_id=%ld\n", msg->amf_ue_ngap_id); + return -1; + } + + NGAP_NGAP_PDU_t *pdu = encode_ng_handover_notify(msg); + + if (ngap_gNB_encode_pdu(pdu, &ba.buf, (uint32_t *)&ba.len) < 0) { + NGAP_ERROR("Failed to encode NG Handover Notify message\n"); + ASN_STRUCT_FREE(asn_DEF_NGAP_NGAP_PDU, pdu); + return -1; + } + + /* UE associated signalling -> use the allocated stream */ + ngap_gNB_itti_send_sctp_data_req(ngap_gNB_instance_p->instance, + ue_context_p->amf_ref->assoc_id, + ba.buf, + ba.len, + ue_context_p->tx_stream); + + return 0; +} + void *ngap_gNB_process_itti_msg(void *notUsed) { MessageDef *received_msg = NULL; int result; @@ -509,6 +553,10 @@ void *ngap_gNB_process_itti_msg(void *notUsed) { free_ng_handover_req_ack(&NGAP_HANDOVER_REQUEST_ACKNOWLEDGE(received_msg)); break; + case NGAP_HANDOVER_NOTIFY: + ngap_gNB_handover_notify(instance, &NGAP_HANDOVER_NOTIFY(received_msg)); + break; + default: NGAP_ERROR("Received unhandled message: %d:%s\n", ITTI_MSG_ID(received_msg), ITTI_MSG_NAME(received_msg)); break; diff --git a/openair3/NGAP/ngap_gNB_encoder.c b/openair3/NGAP/ngap_gNB_encoder.c index 056aea2fe6..16954744e2 100644 --- a/openair3/NGAP/ngap_gNB_encoder.c +++ b/openair3/NGAP/ngap_gNB_encoder.c @@ -53,7 +53,8 @@ static inline int ngap_gNB_encode_initiating(NGAP_NGAP_PDU_t *pdu, uint8_t **buf NGAP_ProcedureCode_id_UEContextReleaseRequest, NGAP_ProcedureCode_id_PathSwitchRequest, NGAP_ProcedureCode_id_PDUSessionResourceModifyIndication, - NGAP_ProcedureCode_id_HandoverPreparation}; + NGAP_ProcedureCode_id_HandoverPreparation, + NGAP_ProcedureCode_id_HandoverNotification}; int i; for (i = 0; i < sizeofArray(tmp); i++) if (pdu->choice.initiatingMessage->procedureCode == tmp[i]) diff --git a/openair3/NGAP/ngap_gNB_mobility_management.c b/openair3/NGAP/ngap_gNB_mobility_management.c index 68a6823d98..565be7acd5 100644 --- a/openair3/NGAP/ngap_gNB_mobility_management.c +++ b/openair3/NGAP/ngap_gNB_mobility_management.c @@ -560,3 +560,60 @@ void free_ng_handover_command(ngap_handover_command_t *msg) { free_byte_array(msg->handoverCommand); } + +NGAP_NGAP_PDU_t *encode_ng_handover_notify(const ngap_handover_notify_t *msg) +{ + NGAP_NGAP_PDU_t *pdu = malloc_or_fail(sizeof(*pdu)); + + // Message Type (M) + pdu->present = NGAP_NGAP_PDU_PR_initiatingMessage; + asn1cCalloc(pdu->choice.initiatingMessage, head); + head->procedureCode = NGAP_ProcedureCode_id_HandoverNotification; + head->criticality = NGAP_Criticality_ignore; + head->value.present = NGAP_InitiatingMessage__value_PR_HandoverNotify; + NGAP_HandoverNotify_t *out = &head->value.choice.HandoverNotify; + + // AMF UE NGAP ID (M) + { + asn1cSequenceAdd(out->protocolIEs.list, NGAP_HandoverNotifyIEs_t, ie); + ie->id = NGAP_ProtocolIE_ID_id_AMF_UE_NGAP_ID; + ie->criticality = NGAP_Criticality_reject; + ie->value.present = NGAP_HandoverNotifyIEs__value_PR_AMF_UE_NGAP_ID; + asn_uint642INTEGER(&ie->value.choice.AMF_UE_NGAP_ID, msg->amf_ue_ngap_id); + } + + // RAN UE NGAP ID (M) + { + asn1cSequenceAdd(out->protocolIEs.list, NGAP_HandoverNotifyIEs_t, ie); + ie->id = NGAP_ProtocolIE_ID_id_RAN_UE_NGAP_ID; + ie->criticality = NGAP_Criticality_reject; + ie->value.present = NGAP_HandoverNotifyIEs__value_PR_RAN_UE_NGAP_ID; + ie->value.choice.RAN_UE_NGAP_ID = msg->gNB_ue_ngap_id; + } + + // User Location Information (M) + { + asn1cSequenceAdd(out->protocolIEs.list, NGAP_HandoverNotifyIEs_t, ie); + ie->id = NGAP_ProtocolIE_ID_id_UserLocationInformation; + ie->criticality = NGAP_Criticality_ignore; + ie->value.present = NGAP_HandoverNotifyIEs__value_PR_UserLocationInformation; + ie->value.choice.UserLocationInformation.present = NGAP_UserLocationInformation_PR_userLocationInformationNR; + asn1cCalloc(ie->value.choice.UserLocationInformation.choice.userLocationInformationNR, userinfo_nr_p); + // NR User Location Information + const target_ran_node_id_t *target = &msg->user_info.target_ng_ran; + // CGI (M) + MACRO_GNB_ID_TO_CELL_IDENTITY(target->targetgNBId, msg->user_info.nrCellIdentity, &userinfo_nr_p->nR_CGI.nRCellIdentity); + MCC_MNC_TO_TBCD(target->plmn_identity.mcc, + target->plmn_identity.mnc, + target->plmn_identity.mnc_digit_length, + &userinfo_nr_p->nR_CGI.pLMNIdentity); + // TAI (M) + INT24_TO_OCTET_STRING(target->tac, &userinfo_nr_p->tAI.tAC); + MCC_MNC_TO_PLMNID(target->plmn_identity.mcc, + target->plmn_identity.mnc, + target->plmn_identity.mnc_digit_length, + &userinfo_nr_p->tAI.pLMNIdentity); + } + + return pdu; +} diff --git a/openair3/NGAP/ngap_gNB_mobility_management.h b/openair3/NGAP/ngap_gNB_mobility_management.h index 4ed3229a84..d31bdb0caa 100644 --- a/openair3/NGAP/ngap_gNB_mobility_management.h +++ b/openair3/NGAP/ngap_gNB_mobility_management.h @@ -33,5 +33,6 @@ NGAP_NGAP_PDU_t *encode_ng_handover_request_ack(ngap_handover_request_ack_t *msg void free_ng_handover_req_ack(ngap_handover_request_ack_t *msg); int decode_ng_handover_command(ngap_handover_command_t *msg, NGAP_NGAP_PDU_t *pdu); void free_ng_handover_command(ngap_handover_command_t *msg); +NGAP_NGAP_PDU_t *encode_ng_handover_notify(const ngap_handover_notify_t *msg); #endif /* NGAP_GNB_MOBILITY_MANAGEMENT_H_ */ diff --git a/openair3/NGAP/ngap_msg_includes.h b/openair3/NGAP/ngap_msg_includes.h index a80df29401..4224660974 100644 --- a/openair3/NGAP/ngap_msg_includes.h +++ b/openair3/NGAP/ngap_msg_includes.h @@ -101,5 +101,6 @@ #include "NGAP_HandoverRequestAcknowledgeTransfer.h" #include "NGAP_QosFlowItemWithDataForwarding.h" #include "NR_HandoverPreparationInformation.h" +#include "NGAP_HandoverNotify.h" #endif // NGAP_MSG_INCLUDES_H From 28f926607e8ae1ccc77e48319340325f77943c05 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Fri, 14 Mar 2025 14:51:50 +0100 Subject: [PATCH 36/52] Add N2 HO trigger on Bearer Context Setup Response (target NG-RAN) * add trigger function to (1) add the callbacks and (2) initiate handover on the target NG-RAN * add handover failure callback on target NG-RAN: the Handover Failure is a procedure that belongs to the target NG-RAN and it is different from the Handover Cancel procedure Co-authored-by: batuhan duyuler --- openair2/RRC/NR/MESSAGES/asn1_msg.c | 43 +++++++++++++++++++++++++++++ openair2/RRC/NR/MESSAGES/asn1_msg.h | 4 +++ openair2/RRC/NR/rrc_gNB.c | 26 ++++++++--------- openair2/RRC/NR/rrc_gNB_NGAP.c | 1 + openair2/RRC/NR/rrc_gNB_mobility.c | 26 +++++++++++++++-- openair2/RRC/NR/rrc_gNB_mobility.h | 7 +++++ 6 files changed, 92 insertions(+), 15 deletions(-) diff --git a/openair2/RRC/NR/MESSAGES/asn1_msg.c b/openair2/RRC/NR/MESSAGES/asn1_msg.c index 20bf56390b..ac58b3a278 100644 --- a/openair2/RRC/NR/MESSAGES/asn1_msg.c +++ b/openair2/RRC/NR/MESSAGES/asn1_msg.c @@ -87,6 +87,7 @@ #include "NR_HandoverPreparationInformation.h" #include "NR_HandoverPreparationInformation-IEs.h" #include "NR_HandoverCommand.h" +#include "NR_UE-CapabilityRAT-ContainerList.h" #include "common/utils/nr/nr_common.h" #if defined(NR_Rel16) #include "NR_SCS-SpecificCarrier.h" @@ -202,6 +203,48 @@ int xer_nr_sprint(char *string, size_t string_size, asn_TYPE_descriptor_t *td, v return er.encoded; } +struct NR_UE_NR_Capability *get_ue_nr_capability(int rnti, uint8_t *buf, uint32_t len) +{ + if (!buf || len == 0) return NULL; + + NR_UE_CapabilityRAT_ContainerList_t *clist = NULL; + asn_dec_rval_t rval = uper_decode(NULL, &asn_DEF_NR_UE_CapabilityRAT_ContainerList, + (void **)&clist, buf, len, 0, 0); + + if (rval.code != RC_OK) { + LOG_W(NR_MAC, "UE RNTI %04x: Failed to decode container list\n", rnti); + return NULL; + } + + NR_UE_NR_Capability_t *cap = decode_nr_ue_capability(rnti, clist); + ASN_STRUCT_FREE(asn_DEF_NR_UE_CapabilityRAT_ContainerList, clist); + return cap; +} + +NR_UE_NR_Capability_t *decode_nr_ue_capability(int rnti, const NR_UE_CapabilityRAT_ContainerList_t *clist) +{ + if (!clist) return NULL; + + for (int i = 0; i < clist->list.count; i++) { + const NR_UE_CapabilityRAT_Container_t *c = clist->list.array[i]; + if (c->rat_Type != NR_RAT_Type_nr) + continue; + + NR_UE_NR_Capability_t *cap = NULL; + asn_dec_rval_t rval = uper_decode(NULL, &asn_DEF_NR_UE_NR_Capability, + (void **)&cap, + c->ue_CapabilityRAT_Container.buf, + c->ue_CapabilityRAT_Container.size, + 0, 0); + if (rval.code == RC_OK) + return cap; + + LOG_W(NR_MAC, "UE RNTI %04x: Failed to decode NR capability (%zu bytes)\n", rnti, rval.consumed); + ASN_STRUCT_FREE(asn_DEF_NR_UE_NR_Capability, cap); + } + return NULL; +} + //------------------------------------------------------------------------------ int do_SIB2_NR(uint8_t **msg_SIB2, NR_SSB_MTC_t *ssbmtc) diff --git a/openair2/RRC/NR/MESSAGES/asn1_msg.h b/openair2/RRC/NR/MESSAGES/asn1_msg.h index 665edeef4b..fb43237d2a 100644 --- a/openair2/RRC/NR/MESSAGES/asn1_msg.h +++ b/openair2/RRC/NR/MESSAGES/asn1_msg.h @@ -51,6 +51,7 @@ #include "NR_SRB-ToAddModList.h" #include "NR_SecurityConfig.h" #include "NR_MeasurementTimingConfiguration.h" +#include "NR_UE-CapabilityRAT-ContainerList.h" #include "ds/seq_arr.h" #include "ds/byte_array.h" #include "rrc_messages_types.h" @@ -167,4 +168,7 @@ byte_array_t get_HandoverCommandMessage(nr_rrc_reconfig_param_t *params); void fill_removal_lists_from_source_measConfig(NR_MeasConfig_t *measConfig, byte_array_t prep_info); int doRRCReconfiguration_from_HandoverCommand(byte_array_t *ba, byte_array_t handoverCommand); +struct NR_UE_NR_Capability *get_ue_nr_capability(int rnti, uint8_t *buf, uint32_t len); +NR_UE_NR_Capability_t *decode_nr_ue_capability(int rnti, const NR_UE_CapabilityRAT_ContainerList_t *clist); + #endif /* __RRC_NR_MESSAGES_ASN1_MSG__H__ */ diff --git a/openair2/RRC/NR/rrc_gNB.c b/openair2/RRC/NR/rrc_gNB.c index 32021d7f22..75a3296b8f 100644 --- a/openair2/RRC/NR/rrc_gNB.c +++ b/openair2/RRC/NR/rrc_gNB.c @@ -1607,21 +1607,14 @@ static void handle_ueCapabilityInformation(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, UE->UE_Capability_nr = 0; } - asn_dec_rval_t dec_rval = uper_decode(NULL, - &asn_DEF_NR_UE_NR_Capability, - (void **)&UE->UE_Capability_nr, - ue_cap_container->ue_CapabilityRAT_Container.buf, - ue_cap_container->ue_CapabilityRAT_Container.size, - 0, - 0); - if (LOG_DEBUGFLAG(DEBUG_ASN1)) { - xer_fprint(stdout, &asn_DEF_NR_UE_NR_Capability, UE->UE_Capability_nr); + UE->UE_Capability_nr = decode_nr_ue_capability(UE->rnti, ue_CapabilityRAT_ContainerList); + if (!UE->UE_Capability_nr) { + LOG_E(NR_RRC, "UE %d: NR capability decoding failed\n", UE->rrc_ue_id); + return; } - if ((dec_rval.code != RC_OK) && (dec_rval.consumed == 0)) { - LOG_E(NR_RRC, "UE %d: Failed to decode nr UE capabilities (%zu bytes)\n", UE->rrc_ue_id, dec_rval.consumed); - ASN_STRUCT_FREE(asn_DEF_NR_UE_NR_Capability, UE->UE_Capability_nr); - UE->UE_Capability_nr = 0; + if (LOG_DEBUGFLAG(DEBUG_ASN1)) { + xer_fprint(stdout, &asn_DEF_NR_UE_NR_Capability, UE->UE_Capability_nr); } const NR_RedCapParameters_r17_t *redcap_p = get_redcapparam_r17(UE->UE_Capability_nr); @@ -2557,6 +2550,13 @@ void rrc_gNB_process_e1_bearer_context_setup_resp(e1ap_bearer_setup_resp_t *resp } } + // If HO Preparation Info is stored, N2 handover is ongoing + if (UE->ho_context) { + LOG_I(NR_RRC, "Received Bearer Context Setup Response for UE %d with valid HO Context\n", UE->rrc_ue_id); + UE->ho_context->target->ho_trigger(rrc, UE); + return; + } + if (!UE->f1_ue_context_active) rrc_gNB_generate_UeContextSetupRequest(rrc, ue_context_p, resp); else diff --git a/openair2/RRC/NR/rrc_gNB_NGAP.c b/openair2/RRC/NR/rrc_gNB_NGAP.c index e7459b1d49..961191d171 100644 --- a/openair2/RRC/NR/rrc_gNB_NGAP.c +++ b/openair2/RRC/NR/rrc_gNB_NGAP.c @@ -1111,6 +1111,7 @@ int rrc_gNB_process_Handover_Request(gNB_RRC_INST *rrc, instance_t instance, nga return -1; } UE->ho_context = alloc_ho_ctx(HO_CTX_TARGET); + UE->ho_context->target->ho_trigger = nr_rrc_trigger_n2_ho_target; // Store IDs in UE context UE->amf_ue_ngap_id = msg->amf_ue_ngap_id; diff --git a/openair2/RRC/NR/rrc_gNB_mobility.c b/openair2/RRC/NR/rrc_gNB_mobility.c index 9e76217dc9..a2978e2d56 100644 --- a/openair2/RRC/NR/rrc_gNB_mobility.c +++ b/openair2/RRC/NR/rrc_gNB_mobility.c @@ -122,7 +122,8 @@ static void nr_initiate_handover(const gNB_RRC_INST *rrc, byte_array_t *ho_prep_info, ho_req_ack_t ack, ho_success_t success, - ho_cancel_t cancel) + ho_cancel_t cancel, + ho_failure_t failure) { DevAssert(rrc != NULL); DevAssert(ue != NULL); @@ -145,6 +146,7 @@ static void nr_initiate_handover(const gNB_RRC_INST *rrc, // response ho_ctx->target->ho_req_ack = ack; ho_ctx->target->ho_success = success; + ho_ctx->target->ho_failure = failure; const f1_ue_data_t ue_data = cu_get_f1_ue_data(ue->rrc_ue_id); if (source_du != NULL) { @@ -353,7 +355,7 @@ void nr_rrc_trigger_f1_ho(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue, nr_rrc_du_contain ho_success_t success = nr_rrc_f1_ho_complete; ho_cancel_t cancel = nr_rrc_cancel_f1_ho; byte_array_t hpi = {.buf = buf, .len = size}; - nr_initiate_handover(rrc, ue, source_du, target_du, &hpi, ack, success, cancel); + nr_initiate_handover(rrc, ue, source_du, target_du, &hpi, ack, success, cancel, NULL); } void nr_rrc_finalize_ho(gNB_RRC_UE_t *ue) @@ -496,6 +498,26 @@ void nr_rrc_n2_ho_failure(gNB_RRC_INST *rrc, uint32_t gnb_ue_id, ngap_handover_f return; } +/** @brief Trigger N2 Handover on the target gNB: + * 1) set up callbacks + * 2) initiate N2 handover on target NG-RAN, which triggers the set up + * of HO context and UE Context Setup procedures */ +void nr_rrc_trigger_n2_ho_target(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue) +{ + ho_req_ack_t ack = nr_rrc_n2_ho_acknowledge; + ho_success_t success = nr_rrc_n2_ho_complete; + ho_failure_t failure = nr_rrc_n2_ho_failure; + + const nr_rrc_du_container_t *target_du = get_du_for_ue(rrc, ue->rrc_ue_id); + nr_initiate_handover(rrc, ue, NULL, target_du, &ue->ho_context->target->ue_ho_prep_info, ack, success, NULL, failure); + FREE_AND_ZERO_BYTE_ARRAY(ue->ho_context->target->ue_ho_prep_info); + + NR_UE_NR_Capability_t *ue_cap = get_ue_nr_capability(ue->rnti, ue->ue_cap_buffer.buf, ue->ue_cap_buffer.len); + ue->UE_Capability_nr = ue_cap; + ue->UE_Capability_size = ue->ue_cap_buffer.len; + +} + /** @brief Trigger N2 handover on source gNB: * 1) Prepare RRC Container with HandoverPreparationInformation message * 2) send NGAP Handover Required message */ diff --git a/openair2/RRC/NR/rrc_gNB_mobility.h b/openair2/RRC/NR/rrc_gNB_mobility.h index 5a6542132c..db0645c5e3 100644 --- a/openair2/RRC/NR/rrc_gNB_mobility.h +++ b/openair2/RRC/NR/rrc_gNB_mobility.h @@ -54,6 +54,7 @@ typedef struct nr_ho_source_cu { typedef void (*ho_req_ack_t)(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue); typedef void (*ho_success_t)(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue); typedef void (*ho_failure_t)(gNB_RRC_INST *rrc, uint32_t gnb_ue_id, ngap_handover_failure_t *msg); +typedef void (*ho_trigger_t)(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue); typedef struct nr_ho_target_cu { /// pointer to the (target) DU structure @@ -66,10 +67,14 @@ typedef struct nr_ho_target_cu { rnti_t new_rnti; /// Handover Preparation Buffer byte_array_t ue_ho_prep_info; + /// function pointer to trigger handover on target gNB + ho_trigger_t ho_trigger; /// function pointer to announce handover request acknowledgment ho_req_ack_t ho_req_ack; /// function pointer to announce handover success ho_success_t ho_success; + /// function pointer to announce the handover failure + ho_failure_t ho_failure; } nr_ho_target_cu_t; typedef struct nr_handover_context_s { @@ -91,4 +96,6 @@ void nr_rrc_trigger_n2_ho(gNB_RRC_INST *rrc, void rrc_gNB_trigger_reconfiguration_for_handover(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue, uint8_t *rrc_reconf, int rrc_reconf_len); +void nr_rrc_trigger_n2_ho_target(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue); + #endif /* RRC_GNB_MOBILITY_H_ */ From 984c0bff0f079ce2665edeccb5ec99986801c6c9 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Mon, 1 Sep 2025 15:05:54 +0200 Subject: [PATCH 37/52] Send NG HO failure message upon nr_rrc_n2_ho_acknowledge failure --- openair2/RRC/NR/rrc_gNB_mobility.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openair2/RRC/NR/rrc_gNB_mobility.c b/openair2/RRC/NR/rrc_gNB_mobility.c index a2978e2d56..3c9bf2c17b 100644 --- a/openair2/RRC/NR/rrc_gNB_mobility.c +++ b/openair2/RRC/NR/rrc_gNB_mobility.c @@ -469,6 +469,10 @@ static void nr_rrc_n2_ho_acknowledge(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE) byte_array_t hoCommand = rrc_gNB_encode_HandoverCommand(UE, rrc); if (hoCommand.len < 0) { LOG_E(NR_RRC, "ASN1 message encoding failed: failed to generate Handover Command Message\n"); + ngap_handover_failure_t fail = {.amf_ue_ngap_id = UE->amf_ue_ngap_id, + .cause.type = NGAP_CAUSE_RADIO_NETWORK, + .cause.value = NGAP_CAUSE_RADIO_NETWORK_HO_FAILURE_IN_TARGET_5GC_NGRAN_NODE_OR_TARGET_SYSTEM}; + UE->ho_context->target->ho_failure(rrc, UE->rrc_ue_id, &fail); return; } else { LOG_D(NR_RRC, "HO LOG: Handover Command for UE %u Encoded (%ld bytes)\n", UE->rrc_ue_id, hoCommand.len); From cb68d817f0c77740c3969837cb74eb83087744ed Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Wed, 2 Apr 2025 16:37:33 +0200 Subject: [PATCH 38/52] OAI UE: introduce AS security key update procedure (5.3.5.7 3GPP TS 38.331) * trigger upon reception of MasterKeyUpdate * derive NH, store NH NCC and valid NH key in RRC * masterKeyUpdate: derive enc/int keys after master key update and set PDCP security for SRB1 * call nr_rrc_process_reconfiguration_v1530 before nr_rrc_ue_process_RadioBearerConfig and move dedicatedNAS_MessageList processing at the end of the flow. The sequence shall be (1) keys/security update (2) radioBearerConfig processing, with PDCP entity creation, PDCP reestablishment, SDAP entity creation (3) NAS PDUs processing, which triggers PDU Session Establishment Accept, which sets up the TUN interface. SDAP entity is a pre-requisite, therefore the radioBearerConfig has to be processed (in nr_rrc_ue_process_RadioBearerConfig, add_drb) early enough, or it may cause a race condition in SDAP. --- openair2/RRC/NR_UE/rrc_UE.c | 108 ++++++++++++++++++++++++++++------ openair2/RRC/NR_UE/rrc_defs.h | 4 ++ 2 files changed, 93 insertions(+), 19 deletions(-) diff --git a/openair2/RRC/NR_UE/rrc_UE.c b/openair2/RRC/NR_UE/rrc_UE.c index 52d9ecb50a..b639df31d6 100644 --- a/openair2/RRC/NR_UE/rrc_UE.c +++ b/openair2/RRC/NR_UE/rrc_UE.c @@ -532,19 +532,74 @@ static void nr_rrc_process_sib1(NR_UE_RRC_INST_t *rrc, NR_UE_RRC_SI_INFO *SI_inf nr_rrc_send_msg_to_mac(rrc, &rrc_msg); } -static void nr_rrc_process_reconfiguration_v1530(NR_UE_RRC_INST_t *rrc, NR_RRCReconfiguration_v1530_IEs_t *rec_1530, int gNB_index) +static void nr_rrc_nh_update(NR_UE_RRC_INST_t *rrc, uint8_t *kamf, uint8_t *sync_input) { - if (rec_1530->fullConfig) { - // TODO perform the full configuration procedure as specified in 5.3.5.11 of 331 - LOG_E(NR_RRC, "RRCReconfiguration includes fullConfig but this is not implemented yet\n"); + uint8_t nh[SECURITY_KEY_LEN] = {0}; + nr_derive_nh(kamf, sync_input, nh); + log_hex_buffer("Sync input = stored NH", rrc->nh, SECURITY_KEY_LEN); + memcpy(rrc->nh, nh, SECURITY_KEY_LEN); + rrc->nhcc++; // Increase stored nextHopChainingCount +} + +/** @brief AS security key update procedure (5.3.5.7 3GPP TS 38.331) */ +void as_security_key_update(NR_UE_RRC_INST_t *rrc, NR_MasterKeyUpdate_t *mku) +{ + if (mku->nas_Container) { + LOG_E(NR_RRC, "forward the nas-Container to the upper layers: not implemented yet\n"); } - if (rec_1530->masterCellGroup) - nr_rrc_ue_process_masterCellGroup(rrc, rec_1530->masterCellGroup, rec_1530->fullConfig, gNB_index); - if (rec_1530->masterKeyUpdate) { - // TODO perform AS security key update procedure as specified in 5.3.5.7 - LOG_E(NR_RRC, "RRCReconfiguration includes masterKeyUpdate but this is not implemented yet\n"); + if (mku->keySetChangeIndicator) { + LOG_E(NR_RRC, "derive or update the K gNB key based on the K AMF key, as specified in TS 33.501: not implemented yet\n"); + } else { + /* derive or update the K gNB key based on the current K gNB key or the NH, using the nextHopChainingCount + value indicated in the received masterKeyUpdate, as specified in 6.9.2.3.3 3GPP TS 33.501 */ + if (mku->nextHopChainingCount != rrc->nhcc) { + // - If the UE received an NCC value that was different from the NCC associated with the currently active + // K gNB/K eNB, the UE shall first synchronize the locally kept NH parameter by computing the function defined in + // Annex A.10 iteratively (and increasing the NCC value until it matches the NCC value received from the source + // ng-eNB/gNB via the HO command message. + LOG_A(NR_RRC, "Received masterKeyUpdate (nextHopChainingCount %ld): update security keys\n", mku->nextHopChainingCount); + /** @todo: The KAMF should be obtained from NAS. This exchange over ITTI must be synchronized + * with the rest of the RRCReconfiguration procedure, in particular, the RadioBearerConfig + * processing that triggers bearer modifications. Security configueration of bearers must + * complete using the newly derived keys. As a workaround NAS is directly accessed here. */ + nr_ue_nas_t *nas = get_ue_nas_info(rrc->ue_id); + uint8_t *kamf = nas->security.kamf; + log_hex_buffer("Stored kamf", kamf, SECURITY_KEY_LEN); + if (rrc->nhcc == 0) { // First derivation + derive_kgnb(kamf, 0, rrc->kgnb); + log_hex_buffer("Sync input = derived kgnb", rrc->kgnb, SECURITY_KEY_LEN); + nr_rrc_nh_update(rrc, kamf, rrc->kgnb); + } + for (int i = rrc->nhcc; i < mku->nextHopChainingCount; i++) { // Following derivations + LOG_D(NR_RRC, "Derive keys for ChainingCount = %d\n", i); + nr_rrc_nh_update(rrc, kamf, rrc->nh); + } + nr_derive_key_ng_ran_star(rrc->phyCellID, rrc->arfcn_ssb, rrc->nh, rrc->kgnb); + // When the NCC values match, the UE shall compute the K NG-RAN * + // from the synchronized NH parameter and the target PCI and its frequency ARFCN-DL/EARFCN-DL using the + // function defined in Annex A.11 and A.12. + // The UE shall use the KNG-RAN * as the K gNB when communicating with the target gNB and as the KeNB when + // communicating with the target ng-eNB. + } else { + nr_derive_key_ng_ran_star(rrc->phyCellID, rrc->arfcn_ssb, rrc->kgnb, rrc->kgnb); + } + log_hex_buffer("Derived kgnb", rrc->kgnb, SECURITY_KEY_LEN); } - /* Check if there is dedicated NAS information to forward to NAS */ +} + +static nr_pdcp_entity_security_keys_and_algos_t get_security_rrc_parameters(NR_UE_RRC_INST_t *ue, bool cp) +{ + nr_pdcp_entity_security_keys_and_algos_t out = {0}; + out.ciphering_algorithm = ue->cipheringAlgorithm; + out.integrity_algorithm = ue->integrityProtAlgorithm; + nr_derive_key(cp ? RRC_ENC_ALG : UP_ENC_ALG, ue->cipheringAlgorithm, ue->kgnb, out.ciphering_key); + nr_derive_key(cp ? RRC_INT_ALG : UP_INT_ALG, ue->integrityProtAlgorithm, ue->kgnb, out.integrity_key); + return out; +} + +/** @brief Check if there is dedicated NAS information to forward to NAS */ +static void nr_rrc_process_dedicatedNAS_MessageList(NR_UE_RRC_INST_t *rrc, NR_RRCReconfiguration_v1530_IEs_t *rec_1530) +{ if (rec_1530->dedicatedNAS_MessageList) { struct NR_RRCReconfiguration_v1530_IEs__dedicatedNAS_MessageList *tmp = rec_1530->dedicatedNAS_MessageList; for (int i = 0; i < tmp->list.count; i++) { @@ -557,6 +612,21 @@ static void nr_rrc_process_reconfiguration_v1530(NR_UE_RRC_INST_t *rrc, NR_RRCRe } tmp->list.count = 0; // to prevent the automatic free by ASN1_FREE } +} + +static void nr_rrc_process_reconfiguration_v1530(NR_UE_RRC_INST_t *rrc, NR_RRCReconfiguration_v1530_IEs_t *rec_1530, int gNB_index) +{ + if (rec_1530->fullConfig) { + // TODO perform the full configuration procedure as specified in 5.3.5.11 of 331 + LOG_E(NR_RRC, "RRCReconfiguration includes fullConfig but this is not implemented yet\n"); + } + if (rec_1530->masterCellGroup) + nr_rrc_ue_process_masterCellGroup(rrc, rec_1530->masterCellGroup, rec_1530->fullConfig, gNB_index); + if (rec_1530->masterKeyUpdate) { + as_security_key_update(rrc, rec_1530->masterKeyUpdate); + nr_pdcp_entity_security_keys_and_algos_t sp = get_security_rrc_parameters(rrc, true); + nr_pdcp_config_set_security(rrc->ue_id, 1, true, &sp); + } NR_UE_RRC_SI_INFO *SI_info = &rrc->perNB[gNB_index].SInfo; if (rec_1530->dedicatedSIB1_Delivery) { NR_SIB1_t *sib1 = NULL; @@ -633,6 +703,9 @@ static void nr_rrc_ue_process_rrcReconfiguration(NR_UE_RRC_INST_t *rrc, int gNB_ case NR_RRCReconfiguration__criticalExtensions_PR_rrcReconfiguration: { NR_RRCReconfiguration_IEs_t *ie = reconfiguration->criticalExtensions.choice.rrcReconfiguration; + if (ie->nonCriticalExtension) + nr_rrc_process_reconfiguration_v1530(rrc, ie->nonCriticalExtension, gNB_index); + if (ie->radioBearerConfig) { LOG_I(NR_RRC, "RRCReconfiguration includes radio Bearer Configuration\n"); nr_rrc_ue_process_RadioBearerConfig(rrc, ie->radioBearerConfig); @@ -640,8 +713,11 @@ static void nr_rrc_ue_process_rrcReconfiguration(NR_UE_RRC_INST_t *rrc, int gNB_ xer_fprint(stdout, &asn_DEF_NR_RadioBearerConfig, (const void *)ie->radioBearerConfig); } + /** @note This triggers PDU Session Establishment Accept which sets up the TUN interface. + * SDAP entity is a pre-requisite, therefore the radioBearerConfig has to be processed + * (in nr_rrc_ue_process_RadioBearerConfig, add_drb) early enough, or it may cause a race condition in SDAP. */ if (ie->nonCriticalExtension) - nr_rrc_process_reconfiguration_v1530(rrc, ie->nonCriticalExtension, gNB_index); + nr_rrc_process_dedicatedNAS_MessageList(rrc, ie->nonCriticalExtension); if (ie->secondaryCellGroup) { NR_CellGroupConfig_t *cellGroupConfig = NULL; @@ -1940,14 +2016,8 @@ static void nr_rrc_ue_process_RadioBearerConfig(NR_UE_RRC_INST_t *ue_rrc, ue_rrc->integrityProtAlgorithm = *radioBearerConfig->securityConfig->securityAlgorithmConfig->integrityProtAlgorithm; } } - security_rrc_parameters.ciphering_algorithm = ue_rrc->cipheringAlgorithm; - security_rrc_parameters.integrity_algorithm = ue_rrc->integrityProtAlgorithm; - nr_derive_key(RRC_ENC_ALG, ue_rrc->cipheringAlgorithm, ue_rrc->kgnb, security_rrc_parameters.ciphering_key); - nr_derive_key(RRC_INT_ALG, ue_rrc->integrityProtAlgorithm, ue_rrc->kgnb, security_rrc_parameters.integrity_key); - security_up_parameters.ciphering_algorithm = ue_rrc->cipheringAlgorithm; - security_up_parameters.integrity_algorithm = ue_rrc->integrityProtAlgorithm; - nr_derive_key(UP_ENC_ALG, ue_rrc->cipheringAlgorithm, ue_rrc->kgnb, security_up_parameters.ciphering_key); - nr_derive_key(UP_INT_ALG, ue_rrc->integrityProtAlgorithm, ue_rrc->kgnb, security_up_parameters.integrity_key); + security_rrc_parameters = get_security_rrc_parameters(ue_rrc, true); + security_up_parameters = get_security_rrc_parameters(ue_rrc, false); } if (radioBearerConfig->srb_ToAddModList != NULL) { diff --git a/openair2/RRC/NR_UE/rrc_defs.h b/openair2/RRC/NR_UE/rrc_defs.h index bc645f7cc1..b745b5f1db 100644 --- a/openair2/RRC/NR_UE/rrc_defs.h +++ b/openair2/RRC/NR_UE/rrc_defs.h @@ -233,6 +233,10 @@ typedef struct NR_UE_RRC_INST_s { e_NR_IntegrityProtAlgorithm integrityProtAlgorithm; long keyToUse; bool as_security_activated; + /// Next Hop Chaining Count + uint8_t nh[32]; + uint64_t nhcc; + bool detach_after_release; NR_timer_t release_timer; NR_RRCRelease_t *RRCRelease; From 5227becf89349bf8aa5dedb0c0a9e4c995c4be48 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Thu, 12 Jun 2025 21:58:37 +0200 Subject: [PATCH 39/52] Add telnet trigger for N2 handover * the command gets in input the neighbour PCI and the UE ID * the telnet ci function to retrieve the UE context was extended in the process * add function to trigger handover on the same gNB for testing purposes Co-authored-by: batuhan duyuler --- common/utils/telnetsrv/telnetsrv_ci.c | 49 +++++++++++++++++++++++++++ openair2/RRC/NR/rrc_gNB_mobility.c | 49 +++++++++++++++++++++++++++ 2 files changed, 98 insertions(+) diff --git a/common/utils/telnetsrv/telnetsrv_ci.c b/common/utils/telnetsrv/telnetsrv_ci.c index 24dba02191..291f1f37a2 100644 --- a/common/utils/telnetsrv/telnetsrv_ci.c +++ b/common/utils/telnetsrv/telnetsrv_ci.c @@ -38,6 +38,7 @@ #include "openair2/LAYER2/nr_rlc/nr_rlc_ue_manager.h" #include "openair2/LAYER2/nr_rlc/nr_rlc_entity_am.h" #include "openair2/LAYER2/NR_MAC_gNB/mac_proto.h" +#include "openair2/RRC/NR/rrc_gNB_mobility.h" #define TELNETSERVERCODE #include "telnetsrv.h" @@ -196,6 +197,53 @@ int rrc_gNB_trigger_f1_ho(char *buf, int debug, telnet_printfunc_t prnt) return 0; } +extern void nr_HO_N2_trigger_telnet(gNB_RRC_INST *rrc, uint32_t neighbour_pci, uint32_t rrc_ue_id); + +/** @brief Trigger N2 handover for UE + * @param buf: Neighbour PCI, SCell PCI, RRC UE ID + * @param debug: Debug flag + * @param prnt: Print function + * @return 0 on success, -1 on failure */ +int rrc_gNB_trigger_n2_ho(char *buf, int debug, telnet_printfunc_t prnt) +{ + if (!RC.nrrrc) + ERROR_MSG_RET("no RRC present, cannot list counts\n"); + + if (!buf) { + ERROR_MSG_RET("Please provide neighbour cell id and ue id\n"); + } else { + // Parse neighbour cell PCI + char *token = strtok(buf, ","); + if (!token) { + ERROR_MSG_RET("Invalid input. Expected format: Neighbour PCI, ueId\n"); + } + uint32_t neighbour_pci = strtol(token, NULL, 10); + + // Parse ueId + token = strtok(NULL, ","); + if (!token) { + ERROR_MSG_RET("Missing UE ID\n"); + } + uint32_t ueId = strtol(token, NULL, 10); + + // Retrieve UE context + rrc_gNB_ue_context_t *ue_p = rrc_gNB_get_ue_context(RC.nrrrc[0], ueId); + if (!ue_p) { + ERROR_MSG_RET("UE with id %u not found\n", ueId); + } + gNB_RRC_UE_t *UE = &ue_p->ue_context; + + // Trigger N2 handover + nr_HO_N2_trigger_telnet(RC.nrrrc[0], neighbour_pci, UE->rrc_ue_id); + + // Print success message + prnt("RRC N2 handover triggered for UE %u with neighbour cell id %u\n", + ueId, + neighbour_pci); + } + return 0; +} + int force_ul_failure(char *buf, int debug, telnet_printfunc_t prnt) { if (!RC.nrmac) @@ -245,6 +293,7 @@ static telnetshell_cmddef_t cicmds[] = { {"trigger_f1_ho", "[rrc_ue_id(int,opt)]", rrc_gNB_trigger_f1_ho}, {"fetch_du_by_ue_id", "[rrc_ue_id(int,opt)]", fetch_du_by_ue_id}, {"get_current_bwp", "[rnti(hex,opt)]", get_current_bwp}, + {"trigger_n2_ho", "[neighbour_pci(uint32_t),ueId(uint32_t)]", rrc_gNB_trigger_n2_ho}, {"", "", NULL}, }; diff --git a/openair2/RRC/NR/rrc_gNB_mobility.c b/openair2/RRC/NR/rrc_gNB_mobility.c index 3c9bf2c17b..42214ecc55 100644 --- a/openair2/RRC/NR/rrc_gNB_mobility.c +++ b/openair2/RRC/NR/rrc_gNB_mobility.c @@ -549,3 +549,52 @@ void nr_rrc_trigger_n2_ho(gNB_RRC_INST *rrc, rrc_gNB_send_NGAP_HANDOVER_REQUIRED(rrc, ue, neighbour_config, hoPrepInfo); free_byte_array(hoPrepInfo); } + +extern const nr_neighbour_cell_t *get_neighbour_cell_by_pci(const neighbour_cell_configuration_t *cell, int pci); +extern const neighbour_cell_configuration_t *get_neighbour_cell_config(const gNB_RRC_INST *rrc, int cell_id); + +void nr_HO_N2_trigger_telnet(gNB_RRC_INST *rrc, uint32_t neighbour_pci, uint32_t rrc_ue_id) +{ + rrc_gNB_ue_context_t *ue_context_p = rrc_gNB_get_ue_context(rrc, rrc_ue_id); + if (ue_context_p == NULL) { + LOG_E(NR_RRC, "N2 HO trigger failed for UE %d: cannot find UE context\n", rrc_ue_id); + return; + } + gNB_RRC_UE_t *UE = &ue_context_p->ue_context; + + struct nr_rrc_du_container_t *du = get_du_for_ue(rrc, rrc_ue_id); + if (du == NULL) { + LOG_E(NR_RRC, "N2 HO trigger failed for UE %d: Unknown DU\n", rrc_ue_id); + return; + } + uint16_t scell_pci = du->setup_req->cell[0].info.nr_pci; + + // Simulate handover on the same cell (testing purposes) + if (neighbour_pci == scell_pci) { + LOG_I(NR_RRC, "UE %d: trigger handover on the same cell PCI=%d\n", rrc_ue_id, neighbour_pci); + nr_neighbour_cell_t neighbourConfig = { + .isIntraFrequencyNeighbour = true, + .gNB_ID = du->setup_req->gNB_DU_id, + .nrcell_id = du->setup_req->cell[0].info.nr_cellid, + .physicalCellId = du->setup_req->cell[0].info.nr_pci, + .plmn = du->setup_req->cell[0].info.plmn, + .subcarrierSpacing = du->setup_req->cell[0].info.tdd.tbw.scs, + }; + nr_rrc_trigger_n2_ho(rrc, UE, neighbour_pci, &neighbourConfig); + return; + } + + const f1ap_served_cell_info_t *scell_du = get_cell_information_by_phycellId(scell_pci); + DevAssert(scell_du); + LOG_I(NR_RRC, "UE %d: triggered N2 HO, source PCI=%d to target PCI=%d\n", rrc_ue_id, scell_pci, neighbour_pci); + + const neighbour_cell_configuration_t *cell = get_neighbour_cell_config(rrc, scell_du->nr_cellid); + const nr_neighbour_cell_t *neighbour = get_neighbour_cell_by_pci(cell, neighbour_pci); + + if (neighbour == NULL) { + LOG_E(NR_RRC, "N2 HO trigger failed for UE %d: could not find neighbour cell with PCI=%d\n", rrc_ue_id, neighbour_pci); + return; + } + + nr_rrc_trigger_n2_ho(rrc, UE, scell_du->nr_pci, neighbour); +} From 1015c1f5f0350500173bbe1e9741817544fa8ed7 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Wed, 20 Aug 2025 23:41:34 +0200 Subject: [PATCH 40/52] Add new CI pipeline for N2 Handover The pipeline is using the already existing conf files for the source/target gNB CUs/DUs. For the sake of simplicity, neighbour-config.conf has been rewritten and simplified in this commit, then adopted in this pipeline. The file was not used before. --- .../conf_files/gnb-cu.sa.band78.106prb.conf | 2 +- .../conf_files/gnb-cucp.sa.e1-ho-n2.conf | 82 +++++ ci-scripts/conf_files/neighbour-config.conf | 68 ++-- .../xml_files/container_5g_rfsim_n2_ho.xml | 207 ++++++++++++ .../5g_f1_rfsimulator/docker-compose.yaml | 1 + .../5g_rfsimulator_n2_ho/docker-compose.yaml | 308 ++++++++++++++++++ 6 files changed, 630 insertions(+), 38 deletions(-) create mode 100644 ci-scripts/conf_files/gnb-cucp.sa.e1-ho-n2.conf create mode 100644 ci-scripts/xml_files/container_5g_rfsim_n2_ho.xml create mode 100644 ci-scripts/yaml_files/5g_rfsimulator_n2_ho/docker-compose.yaml diff --git a/ci-scripts/conf_files/gnb-cu.sa.band78.106prb.conf b/ci-scripts/conf_files/gnb-cu.sa.band78.106prb.conf index 051ebd04fc..4294f920ad 100644 --- a/ci-scripts/conf_files/gnb-cu.sa.band78.106prb.conf +++ b/ci-scripts/conf_files/gnb-cu.sa.band78.106prb.conf @@ -15,7 +15,7 @@ gNBs = // Tracking area code, 0x0000 and 0xfffe are reserved values tracking_area_code = 1; plmn_list = ({ mcc = 208; mnc = 99; mnc_length = 2; snssaiList = ({ sst = 1, sd = 0xffffff }) }); - + @include "neighbour-config.conf" nr_cellid = 12345678L; diff --git a/ci-scripts/conf_files/gnb-cucp.sa.e1-ho-n2.conf b/ci-scripts/conf_files/gnb-cucp.sa.e1-ho-n2.conf new file mode 100644 index 0000000000..ad4cca969e --- /dev/null +++ b/ci-scripts/conf_files/gnb-cucp.sa.e1-ho-n2.conf @@ -0,0 +1,82 @@ +Active_gNBs = ( "cu-cp-0"); +# Asn1_verbosity, choice in: none, info, annoying +Asn1_verbosity = "none"; + +gNBs = +( + { + ////////// Identification parameters: + gNB_ID = 0xe00; + +# cell_type = "CELL_MACRO_GNB"; + + gNB_name = "cu-cp-0"; + + // Tracking area code, 0x0000 and 0xfffe are reserved values + tracking_area_code = 1; + plmn_list = ({ mcc = 208; mnc = 99; mnc_length = 2; snssaiList = ({ sst = 1, sd = 0xffffff }) }); + @include "neighbour-config.conf" + + nr_cellid = 12345678L; + + tr_s_preference = "f1"; + + local_s_address = "192.168.71.150"; + remote_s_address = "192.168.71.171"; + local_s_portd = 2153; + remote_s_portd = 2153; + + # ------- SCTP definitions + SCTP : + { + # Number of streams to use in input/output + SCTP_INSTREAMS = 2; + SCTP_OUTSTREAMS = 2; + }; + + + ////////// AMF parameters: + amf_ip_address = ({ ipv4 = "192.168.71.132"; }); + + E1_INTERFACE = + ( + { + type = "cp"; + ipv4_cucp = "192.168.71.150"; + port_cucp = 38462; + ipv4_cuup = "0.0.0.0"; # multiple CU-UPs + port_cuup = 38462; + } + ) + + NETWORK_INTERFACES : + { + GNB_IPV4_ADDRESS_FOR_NG_AMF = "192.168.71.150"; + }; + } +); + +security = { + # preferred ciphering algorithms + # the first one of the list that an UE supports in chosen + # valid values: nea0, nea1, nea2, nea3 + ciphering_algorithms = ( "nea0" ); + + # preferred integrity algorithms + # the first one of the list that an UE supports in chosen + # valid values: nia0, nia1, nia2, nia3 + integrity_algorithms = ( "nia2", "nia0" ); + + # setting 'drb_ciphering' to "no" disables ciphering for DRBs, no matter + # what 'ciphering_algorithms' configures; same thing for 'drb_integrity' + drb_ciphering = "yes"; + drb_integrity = "no"; +}; + +log_config : { + global_log_level = "info"; + pdcp_log_level = "info"; + rrc_log_level = "info"; + f1ap_log_level = "info"; + ngap_log_level = "info"; +}; diff --git a/ci-scripts/conf_files/neighbour-config.conf b/ci-scripts/conf_files/neighbour-config.conf index 875129365e..4d4cd8136c 100644 --- a/ci-scripts/conf_files/neighbour-config.conf +++ b/ci-scripts/conf_files/neighbour-config.conf @@ -1,52 +1,46 @@ neighbour_list = ( + ########################################################## + # Entry USED BY gNB_ID = 0xe00 (nr_cellid = 1) # + ########################################################## { - nr_cellid = 1; + nr_cellid = 0; # Serving cell of gNB 0xe00 neighbour_cell_configuration = ( { - gNB_ID = 0xb00; - nr_cellid = 2; - physical_cellId = 0x002; - absoluteFrequencySSB = 641280 ; - subcarrierSpacing = 1; #30 KHz - plmn = { mcc = 216; mnc = 03; mnc_length = 2}; - tracking_area_code = 2; - }, - { - gNB_ID = 0xa00; - nr_cellid = 3; - physical_cellId = 0x003; - absoluteFrequencySSB = 641280 ; - subcarrierSpacing = 1; #30 KHz - plmn = { mcc = 999; mnc = 99; mnc_length = 2}; - tracking_area_code = 3; - }, - { - gNB_ID = 0xc00; - nr_cellid = 4; - physical_cellId = 0x004; - absoluteFrequencySSB = 641280 ; - subcarrierSpacing = 1; #30 KHz - plmn = { mcc = 999; mnc = 99; mnc_length = 2}; - tracking_area_code = 4; + gNB_ID = 0xb00; + nr_cellid = 1; # Cell served by gNB 0xb00 + physical_cellId = 1; + absoluteFrequencySSB= 621312; + subcarrierSpacing = 1; # 30 kHz + band = 78; + plmn = { mcc = 208; mnc = 99; mnc_length = 2 }; + tracking_area_code = 1; } - ) + ); }, + + ########################################################## + # Entry USED BY gNB_ID = 0xb00 (nr_cellid = 2) # + ########################################################## { - nr_cellid = 2; + nr_cellid = 1; # Serving cell of gNB 0xb00 neighbour_cell_configuration = ( { - gNB_ID = 0xe00; - nr_cellid = 1; - physical_cellId = 0x000; - absoluteFrequencySSB = 641280 ; - subcarrierSpacing = 1; #30 KHz - plmn = { mcc = 001; mnc = 01; mnc_length = 2}; - tracking_area_code = 1; + gNB_ID = 0xe00; + nr_cellid = 0; # Cell served by gNB 0xe00 + physical_cellId = 0; + absoluteFrequencySSB= 641280; + subcarrierSpacing = 1; # 30 kHz + band = 78; + plmn = { mcc = 208; mnc = 99; mnc_length = 2 }; + tracking_area_code = 1; } - ) + ); } - ); +); +############################################################ +# Common NR measurement-event configuration # +############################################################ nr_measurement_configuration = { Periodical = { diff --git a/ci-scripts/xml_files/container_5g_rfsim_n2_ho.xml b/ci-scripts/xml_files/container_5g_rfsim_n2_ho.xml new file mode 100644 index 0000000000..1b2e84046c --- /dev/null +++ b/ci-scripts/xml_files/container_5g_rfsim_n2_ho.xml @@ -0,0 +1,207 @@ + + + rfsim-5gnr-f1-n2-ho + F1 SA TDD N2 Handover + wrench + + 111111 + 800813 + 000001 + 000002 + 000003 + 020001 + 020002 + 030001 + 030002 + 000004 + 040001 + 050001 + 020001 + 020002 + 050002 + 030001 + 030002 + 050003 + 020001 + 020002 + 050004 + 050001 + 020001 + 020002 + 050002 + 100001 + 222222 + + + + + Pull_Local_Registry + Pull Images from Local Registry + 0 + oai-gnb-asan oai-nr-cuup-asan oai-nr-ue-asan + + + + Create_Workspace + Create new Workspace + 0 + 0 + + + + Deploy_Object + Deploy OAI 5G CoreNetwork + ci-scripts/yaml_files/5g_rfsimulator_n2_ho + mysql oai-amf oai-smf oai-upf oai-ext-dn + 0 + 0 + + + + Deploy_Object + Deploy OAI 5G CU-CP/CU-UP/DU 0 + UE RFsim SA + ci-scripts/yaml_files/5g_rfsimulator_n2_ho + oai-cucp-0 oai-cuup-0 oai-du-0 oai-nr-ue + 0 + 0 + + + + Attach_UE + Attach OAI UE (Wait for IP) + rfsim5g_ue + acamas + + + + Deploy_Object + Deploy OAI 5G CU-CP/CU-UP/DU 1 RFsim SA + ci-scripts/yaml_files/5g_rfsimulator_n2_ho + oai-cucp-1 oai-cuup-1 oai-du-1 + 0 + 0 + + + + Ping + Ping NR-UE from ext-dn + rfsim5g_ext_dn + acamas + rfsim5g_ue + acamas + -c 100 -i 0.25 + 5 + + + + Ping + Ping ext-dn from NR-UE + rfsim5g_ue + acamas + rfsim5g_ext_dn + acamas + -c 100 -i 0.25 + 10 + + + + Iperf + Iperf (DL/10Mbps/UDP)(30 sec) + -u -b 3M -t 20 -R + rfsim5g_ue + acamas + rfsim5g_ext_dn + acamas + 5 + 90 + + + + Iperf + Iperf (UL/4Mbps/UDP)(30 sec) + -u -b 1M -t 20 + rfsim5g_ue + acamas + rfsim5g_ext_dn + acamas + 5 + 90 + + + + IdleSleep + Sleep + 5 + + + + Custom_Command + Trigger N2 Handover + acamas + echo ci trigger_n2_ho 1,1 | ncat 192.168.71.150 9090 + yes + + + + Custom_Command + UE (1) connected to DU-PCI 1? + acamas + echo ci fetch_du_by_ue_id 1 | ncat 192.168.71.180 9090 | grep "1234" + + + + Custom_Command + Trigger N2 Handover + acamas + echo ci trigger_n2_ho 0,1 | ncat 192.168.71.180 9090 + yes + + + + Custom_Command + UE (1) connected to DU-PCI 0? + acamas + echo ci fetch_du_by_ue_id 1 | ncat 192.168.71.150 9090 | grep "3584" + + + + Undeploy_Object + true + Undeploy all OAI 5G stack + ci-scripts/yaml_files/5g_rfsimulator_n2_ho + 0 + 0 + 10,100,100,100 + 10,100,100,100 + + + + Clean_Test_Server_Images + true + Clean Test Images on Test Server + 0 + oai-gnb-asan oai-nr-cuup-asan oai-nr-ue-asan + + + diff --git a/ci-scripts/yaml_files/5g_f1_rfsimulator/docker-compose.yaml b/ci-scripts/yaml_files/5g_f1_rfsimulator/docker-compose.yaml index 18313d0559..e5061ff679 100644 --- a/ci-scripts/yaml_files/5g_f1_rfsimulator/docker-compose.yaml +++ b/ci-scripts/yaml_files/5g_f1_rfsimulator/docker-compose.yaml @@ -111,6 +111,7 @@ services: public_net: ipv4_address: 192.168.71.150 volumes: + - ../../conf_files/neighbour-config.conf:/opt/oai-gnb/etc/neighbour-config.conf - ../../conf_files/gnb-cu.sa.band78.106prb.conf:/opt/oai-gnb/etc/gnb.conf healthcheck: test: /bin/bash -c "pgrep nr-softmodem" diff --git a/ci-scripts/yaml_files/5g_rfsimulator_n2_ho/docker-compose.yaml b/ci-scripts/yaml_files/5g_rfsimulator_n2_ho/docker-compose.yaml new file mode 100644 index 0000000000..1149d4f156 --- /dev/null +++ b/ci-scripts/yaml_files/5g_rfsimulator_n2_ho/docker-compose.yaml @@ -0,0 +1,308 @@ +services: + mysql: + container_name: "rfsim5g-mysql" + image: mysql:8.0 + volumes: + - ../5g_rfsimulator/oai_db.sql:/docker-entrypoint-initdb.d/oai_db.sql + - ../5g_rfsimulator/mysql-healthcheck.sh:/tmp/mysql-healthcheck.sh + environment: + - TZ=Europe/Paris + - MYSQL_DATABASE=oai_db + - MYSQL_USER=test + - MYSQL_PASSWORD=test + - MYSQL_ROOT_PASSWORD=linux + healthcheck: + test: /bin/bash -c "/tmp/mysql-healthcheck.sh" + interval: 10s + timeout: 5s + retries: 30 + networks: + public_net: + ipv4_address: 192.168.71.131 + oai-amf: + container_name: "rfsim5g-oai-amf" + image: oaisoftwarealliance/oai-amf:ci-tmp-pr-372-26c14bcd + environment: + - TZ=Europe/paris + volumes: + - ../5g_rfsimulator/mini_nonrf_config.yaml:/openair-amf/etc/config.yaml + depends_on: + - mysql + networks: + public_net: + ipv4_address: 192.168.71.132 + oai-smf: + container_name: "rfsim5g-oai-smf" + image: oaisoftwarealliance/oai-smf:v2.1.9 + environment: + - TZ=Europe/Paris + volumes: + - ../5g_rfsimulator/mini_nonrf_config.yaml:/openair-smf/etc/config.yaml + depends_on: + - oai-amf + networks: + public_net: + ipv4_address: 192.168.71.133 + oai-upf: + container_name: "rfsim5g-oai-upf" + image: oaisoftwarealliance/oai-upf:v2.1.9 + environment: + - TZ=Europe/Paris + volumes: + - ../5g_rfsimulator/mini_nonrf_config.yaml:/openair-upf/etc/config.yaml + depends_on: + - oai-smf + cap_add: + - NET_ADMIN + - SYS_ADMIN + cap_drop: + - ALL + privileged: true + networks: + public_net: + ipv4_address: 192.168.71.134 + interface_name: eth0 + traffic_net: + ipv4_address: 192.168.72.134 + interface_name: eth1 + oai-ext-dn: + privileged: true + container_name: rfsim5g-oai-ext-dn + image: oaisoftwarealliance/trf-gen-cn5g:focal + entrypoint: /bin/bash -c \ + "iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE;"\ + "ip route add 12.1.1.0/24 via 192.168.72.134 dev eth0; sleep infinity" + depends_on: + - oai-upf + networks: + traffic_net: + ipv4_address: 192.168.72.135 + healthcheck: + test: /bin/bash -c "ping -c 2 192.168.72.134" + interval: 10s + timeout: 5s + retries: 5 + + oai-cucp-0: + image: ${REGISTRY:-oaisoftwarealliance}/${GNB_IMG:-oai-gnb}:${TAG:-develop} + container_name: rfsim5g-oai-cucp-0 + cap_drop: + - ALL + environment: + USE_ADDITIONAL_OPTIONS: --rfsim --rfsimulator.serveraddr 192.168.71.190 + --telnetsrv --telnetsrv.shrmod ci + --telnetsrv.listenaddr 192.168.71.150 + --gNBs.[0].nr_cellid 0 + --log_config.global_log_options level,nocolor,time + ASAN_OPTIONS: detect_leaks=0:detect_odr_violation=0 + depends_on: + - oai-amf + networks: + public_net: + ipv4_address: 192.168.71.150 + volumes: + - ../../conf_files/neighbour-config.conf:/opt/oai-gnb/etc/neighbour-config.conf + - ../../conf_files/gnb-cucp.sa.e1-ho-n2.conf:/opt/oai-gnb/etc/gnb.conf + healthcheck: + test: /bin/bash -c "pgrep nr-softmodem" + start_period: 10s + start_interval: 500ms + interval: 10s + timeout: 5s + retries: 5 + + oai-cuup-0: + image: ${REGISTRY:-oaisoftwarealliance}/${NRCUUP_IMG:-oai-nr-cuup}:${TAG:-develop} + container_name: rfsim5g-oai-cuup-0 + cap_drop: + - ALL + environment: + USE_ADDITIONAL_OPTIONS: --log_config.global_log_options level,nocolor,time + --gNBs.[0].E1_INTERFACE.[0].ipv4_cucp 192.168.71.150 + --gNBs.[0].E1_INTERFACE.[0].ipv4_cuup 192.168.71.151 + --gNBs.[0].NETWORK_INTERFACES.GNB_IPV4_ADDRESS_FOR_NGU 192.168.71.151 + --gNBs.[0].local_s_address 192.168.71.151 + ASAN_OPTIONS: detect_leaks=0 + depends_on: + - oai-cucp-0 + networks: + public_net: + ipv4_address: 192.168.71.151 + volumes: + - ../../conf_files/gnb-cuup.sa.f1.conf:/opt/oai-gnb/etc/gnb.conf + healthcheck: + test: /bin/bash -c "pgrep nr-cuup" + start_period: 10s + start_interval: 500ms + interval: 10s + timeout: 5s + retries: 5 + + + oai-du-0: + image: ${REGISTRY:-oaisoftwarealliance}/${GNB_IMG:-oai-gnb}:${TAG:-develop} + container_name: rfsim5g-oai-du-0 + cap_drop: + - ALL + environment: + USE_ADDITIONAL_OPTIONS: --rfsim + --log_config.global_log_options level,nocolor,time + --gNBs.[0].nr_cellid 0 + --telnetsrv --telnetsrv.listenaddr 192.168.71.171 + --telnetsrv.shrmod ci + --rfsimulator.serveraddr 192.168.71.190 + --gNBs.[0].servingCellConfigCommon.[0].ra_ResponseWindow 5 + ASAN_OPTIONS: detect_leaks=0:detect_odr_violation=0 + depends_on: + - oai-cucp-0 + networks: + public_net: + ipv4_address: 192.168.71.171 + volumes: + - ../../conf_files/neighbour-config.conf:/opt/oai-gnb/etc/neighbour-config.conf + - ../../conf_files/gnb-du.sa.band78.106prb.rfsim.conf:/opt/oai-gnb/etc/gnb.conf + healthcheck: + test: /bin/bash -c "pgrep nr-softmodem" + start_period: 10s + start_interval: 500ms + interval: 10s + timeout: 5s + retries: 5 + + oai-nr-ue: + image: ${REGISTRY:-oaisoftwarealliance}/${NRUE_IMG:-oai-nr-ue}:${TAG:-develop} + container_name: rfsim5g-oai-nr-ue + cap_drop: + - ALL + cap_add: + - NET_ADMIN # for interface bringup + - NET_RAW # for ping + environment: + USE_ADDITIONAL_OPTIONS: --rfsim --rfsimulator.serveraddr server + --band 78 -C 3619200000 -r 106 --numerology 1 --ssb 516 + --uicc0.imsi 208990100001100 + --log_config.global_log_options level,nocolor,time + networks: + public_net: + ipv4_address: 192.168.71.190 + devices: + - /dev/net/tun:/dev/net/tun + volumes: + - ../../conf_files/nrue.uicc.conf:/opt/oai-nr-ue/etc/nr-ue.conf + healthcheck: + test: /bin/bash -c "pgrep nr-uesoftmodem" + interval: 10s + timeout: 5s + retries: 5 + + oai-cucp-1: + image: ${REGISTRY:-oaisoftwarealliance}/${GNB_IMG:-oai-gnb}:${TAG:-develop} + container_name: rfsim5g-oai-cucp-1 + cap_drop: + - ALL + environment: + USE_ADDITIONAL_OPTIONS: --rfsim --rfsimulator.serveraddr 192.168.71.190 + --telnetsrv --telnetsrv.shrmod ci + --telnetsrv.listenaddr 192.168.71.180 + --gNBs.[0].E1_INTERFACE.[0].ipv4_cucp 192.168.71.180 + --gNBs.[0].E1_INTERFACE.[0].ipv4_cuup 192.168.71.181 + --gNBs.[0].local_s_address 192.168.71.180 + --gNBs.[0].remote_s_address 192.168.71.181 + --gNBs.[0].gNB_ID 0xb00 + --gNBs.[0].nr_cellid 1 + --gNBs.[0].NETWORK_INTERFACES.GNB_IPV4_ADDRESS_FOR_NG_AMF 192.168.71.180 + --gNBs.[0].NETWORK_INTERFACES.GNB_IPV4_ADDRESS_FOR_NGU 192.168.71.180 + --log_config.global_log_options level,nocolor,time + ASAN_OPTIONS: detect_leaks=0:detect_odr_violation=0 + networks: + public_net: + ipv4_address: 192.168.71.180 + volumes: + - ../../conf_files/neighbour-config.conf:/opt/oai-gnb/etc/neighbour-config.conf + - ../../conf_files/gnb-cucp.sa.e1-ho-n2.conf:/opt/oai-gnb/etc/gnb.conf + healthcheck: + test: /bin/bash -c "pgrep nr-softmodem" + start_period: 10s + start_interval: 500ms + interval: 10s + timeout: 5s + retries: 5 + + oai-cuup-1: + image: ${REGISTRY:-oaisoftwarealliance}/${NRCUUP_IMG:-oai-nr-cuup}:${TAG:-develop} + container_name: rfsim5g-oai-cuup-1 + cap_drop: + - ALL + environment: + USE_ADDITIONAL_OPTIONS: --log_config.global_log_options level,nocolor,time + --gNBs.[0].E1_INTERFACE.[0].ipv4_cucp 192.168.71.180 + --gNBs.[0].E1_INTERFACE.[0].ipv4_cuup 192.168.71.181 + --gNBs.[0].NETWORK_INTERFACES.GNB_IPV4_ADDRESS_FOR_NGU 192.168.71.181 + --gNBs.[0].local_s_address 192.168.71.181 + ASAN_OPTIONS: detect_leaks=0 + depends_on: + - oai-cucp-0 + networks: + public_net: + ipv4_address: 192.168.71.181 + volumes: + - ../../conf_files/gnb-cuup.sa.f1.conf:/opt/oai-gnb/etc/gnb.conf + healthcheck: + test: /bin/bash -c "pgrep nr-cuup" + start_period: 10s + start_interval: 500ms + interval: 10s + timeout: 5s + retries: 5 + + oai-du-1: + image: ${REGISTRY:-oaisoftwarealliance}/${GNB_IMG:-oai-gnb}:${TAG:-develop} + container_name: rfsim5g-oai-du-1 + cap_drop: + - ALL + environment: + USE_ADDITIONAL_OPTIONS: --rfsim + --rfsimulator.serveraddr 192.168.71.190 + --log_config.global_log_options level,nocolor,time + --gNBs.[0].gNB_DU_ID 1234 + --gNBs.[0].gNB_ID 0xb00 + --gNBs.[0].nr_cellid 1 + --gNBs.[0].servingCellConfigCommon.[0].physCellId 1 + --gNBs.[0].servingCellConfigCommon.[0].absoluteFrequencySSB 621312 + --gNBs.[0].servingCellConfigCommon.[0].dl_absoluteFrequencyPointA 620040 + --MACRLCs.[0].local_n_address 192.168.71.182 + --MACRLCs.[0].remote_n_address 192.168.71.180 + ASAN_OPTIONS: detect_leaks=0:detect_odr_violation=0 + depends_on: + - oai-cucp-1 + networks: + public_net: + ipv4_address: 192.168.71.182 + volumes: + - ../../conf_files/neighbour-config.conf:/opt/oai-gnb/etc/neighbour-config.conf + - ../../conf_files/gnb-du.sa.band78.106prb.rfsim.conf:/opt/oai-gnb/etc/gnb.conf + healthcheck: + test: /bin/bash -c "pgrep nr-softmodem" + start_period: 10s + start_interval: 500ms + interval: 10s + timeout: 5s + retries: 5 + +networks: + public_net: + driver: bridge + name: rfsim5g-oai-public-net + ipam: + config: + - subnet: 192.168.71.128/26 + driver_opts: + com.docker.network.bridge.name: "rfsim5g-public" + traffic_net: + driver: bridge + name: rfsim5g-oai-traffic-net + ipam: + config: + - subnet: 192.168.72.128/26 + driver_opts: + com.docker.network.bridge.name: "rfsim5g-traffic" From bc9c59f186c19c9888cdf71790be0151515cde1f Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Wed, 20 Aug 2025 22:56:46 +0200 Subject: [PATCH 41/52] Add NGAP RAN Status Transfer enc/dec and handlers (UL and DL) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit According to 4.9.1.3.3 of 3GPP TS 23.502: > 2a. - 2c. The S-RAN sends the Uplink RAN Status Transfer message to the S-AMF, > as specified in TS 36.300 [46] and TS 38.300 [9]. The S-RAN may omit sending > this message if none of the radio bearers of the UE shall be treated with PDCP status preservation. TS 38.300 says: > For DRBs not configured with DAPS, the source gNB sends the SN STATUS TRANSFER message to the target > gNB to convey the uplink PDCP SN receiver status and the downlink PDCP SN transmitter status of DRBs for > which PDCP status preservation applies (i.e. for RLC AM). The uplink PDCP SN receiver status includes at > least the PDCP SN of the first missing UL PDCP SDU and may include a bit map of the receive status of the out > of sequence UL PDCP SDUs that the UE needs to retransmit in the target cell, if any. The downlink PDCP SN > transmitter status indicates the next PDCP SN that the target gNB shall assign to new PDCP SDUs, not having a > PDCP SN yet. i.e., for DRBs for which PDCP status preservation applies (i.e. for RLC AM), which is is our case. This commit introduces full support for NG-RAN Status Transfer message handling (TS 38.413 §9.2.3.13-14) in CU. It enables correct state transfer of PDCP COUNT values (SN + HFN) during mobility or recovery scenarios. Summary of changes - NGAP: * Added encoding/decoding support for UL/DL RAN Status Transfer message * Defined structures for: ngap_drb_count_value_t, ngap_drb_status_t, ngap_ran_status_container_t, ngap_ran_status_transfer_t --- openair2/COMMON/ngap_messages_def.h | 2 + openair2/COMMON/ngap_messages_types.h | 46 ++++++++++ openair3/NGAP/ngap_gNB.c | 34 +++++++ openair3/NGAP/ngap_gNB_decoder.c | 6 ++ openair3/NGAP/ngap_gNB_encoder.c | 1 + openair3/NGAP/ngap_gNB_handlers.c | 96 +++++++++++++++++++- openair3/NGAP/ngap_gNB_mobility_management.c | 70 ++++++++++++++ openair3/NGAP/ngap_gNB_mobility_management.h | 1 + openair3/NGAP/ngap_msg_includes.h | 7 ++ 9 files changed, 262 insertions(+), 1 deletion(-) diff --git a/openair2/COMMON/ngap_messages_def.h b/openair2/COMMON/ngap_messages_def.h index fc07cffc6d..bdeb2455b4 100644 --- a/openair2/COMMON/ngap_messages_def.h +++ b/openair2/COMMON/ngap_messages_def.h @@ -75,6 +75,8 @@ MESSAGE_DEF(NGAP_HANDOVER_REQUIRED, MESSAGE_PRIORITY_MED, ngap_handover_required MESSAGE_DEF(NGAP_HANDOVER_FAILURE, MESSAGE_PRIORITY_MED, ngap_handover_failure_t, ngap_handover_failure) MESSAGE_DEF(NGAP_HANDOVER_REQUEST_ACKNOWLEDGE, MESSAGE_PRIORITY_MED, ngap_handover_request_ack_t, ngap_handover_request_ack) MESSAGE_DEF(NGAP_HANDOVER_NOTIFY, MESSAGE_PRIORITY_MED, ngap_handover_notify_t, ngap_handover_notify) +MESSAGE_DEF(NGAP_UL_RAN_STATUS_TRANSFER, MESSAGE_PRIORITY_MED, ngap_ran_status_transfer_t, ngap_ul_ran_status_transfer) +MESSAGE_DEF(NGAP_DL_RAN_STATUS_TRANSFER, MESSAGE_PRIORITY_MED, ngap_ran_status_transfer_t, ngap_dl_ran_status_transfer) /* NGAP -> RRC messages */ MESSAGE_DEF(NGAP_DOWNLINK_NAS , MESSAGE_PRIORITY_MED, ngap_downlink_nas_t , ngap_downlink_nas ) diff --git a/openair2/COMMON/ngap_messages_types.h b/openair2/COMMON/ngap_messages_types.h index eb21935e03..a8c2bab58a 100644 --- a/openair2/COMMON/ngap_messages_types.h +++ b/openair2/COMMON/ngap_messages_types.h @@ -76,6 +76,9 @@ #define NGAP_PDUSESSION_RELEASE_COMMAND(mSGpTR) (mSGpTR)->ittiMsg.ngap_pdusession_release_command #define NGAP_PDUSESSION_RELEASE_RESPONSE(mSGpTR) (mSGpTR)->ittiMsg.ngap_pdusession_release_resp +#define NGAP_UL_RAN_STATUS_TRANSFER(mSGpTR) (mSGpTR)->ittiMsg.ngap_ul_ran_status_transfer +#define NGAP_DL_RAN_STATUS_TRANSFER(mSGpTR) (mSGpTR)->ittiMsg.ngap_dl_ran_status_transfer + //-------------------------------------------------------------------------------------------// /* Length of the transport layer address string @@ -963,4 +966,47 @@ typedef struct ngap_pdusession_release_resp_s { } ngap_pdusession_release_resp_t; +/** 9.2.3.14 Uplink RAN Status Transfer (3GPP TS 38.413) + * COUNT value used for both UL and DL PDCP SN + HFN (12-bit or 18-bit SN) */ + +// Indicates PDCP SN length +typedef enum { NGAP_SN_LENGTH_12 = 0, NGAP_SN_LENGTH_18 = 1 } ngap_sn_length_t; + +typedef struct { + // PDCP Sequence Number + uint32_t pdcp_sn; + // Hyper Frame Number + uint32_t hfn; + // SN length + ngap_sn_length_t sn_len; +} ngap_drb_count_value_t; + +// DRBs Subject to Status Transfer Item +typedef struct { + // DRB ID + uint8_t drb_id; + // UL COUNT value + ngap_drb_count_value_t ul_count; + // DL COUNT value + ngap_drb_count_value_t dl_count; +} ngap_drb_status_t; + +// RAN Status Transfer Transparent Container (9.3.1.108) +typedef struct { + // Number of DRBs in the list + uint8_t nb_drb; + // DRB Status List + ngap_drb_status_t drb_status_list[MAX_DRBS_PER_UE]; +} ngap_ran_status_container_t; + +// Uplink RAN Status Transfer message (9.2.3.14) +typedef struct { + // AMF UE NGAP ID (Mandatory) + uint64_t amf_ue_ngap_id; + // RAN UE NGAP ID (Mandatory) + uint32_t gnb_ue_ngap_id; + // RAN Status Transfer Transparent Container (Mandatory) + ngap_ran_status_container_t ran_status; +} ngap_ran_status_transfer_t; + #endif /* NGAP_MESSAGES_TYPES_H_ */ diff --git a/openair3/NGAP/ngap_gNB.c b/openair3/NGAP/ngap_gNB.c index 272759de4c..afa69c4c9d 100644 --- a/openair3/NGAP/ngap_gNB.c +++ b/openair3/NGAP/ngap_gNB.c @@ -463,6 +463,36 @@ static int ngap_gNB_handover_notify(instance_t instance, const ngap_handover_not return 0; } +int ngap_gNB_handle_ul_ran_status_transfer(instance_t instance, const ngap_ran_status_transfer_t *msg) +{ + NGAP_DEBUG("Triggered UL RAN Status Transfer\n"); + + ngap_gNB_instance_t *ngap_gNB_instance_p = ngap_gNB_get_instance(instance); + DevAssert(ngap_gNB_instance_p != NULL); + + ngap_gNB_ue_context_t *ue_context_p = ngap_get_ue_context(msg->gnb_ue_ngap_id); + if (!ue_context_p || ue_context_p->gNB_ue_ngap_id != msg->gnb_ue_ngap_id) { + NGAP_ERROR("Could not find UE context for gNB_ue_ngap_id %d\n", msg->gnb_ue_ngap_id); + return -1; + } + + NGAP_NGAP_PDU_t *pdu = encode_ng_ul_ran_status_transfer(msg); + if (!pdu) { + NGAP_ERROR("Failed to encode UL RAN Status Transfer\n"); + return -1; + } + + byte_array_t out = {.buf = NULL, .len = 0}; + if (ngap_gNB_encode_pdu(pdu, &out.buf, (uint32_t *)&out.len) < 0) { + NGAP_ERROR("UL RAN Status Transfer encoding failure\n"); + ASN_STRUCT_FREE(asn_DEF_NGAP_NGAP_PDU, pdu); + return -1; + } + + ngap_gNB_itti_send_sctp_data_req(instance, ue_context_p->amf_ref->assoc_id, out.buf, out.len, ue_context_p->tx_stream); + return 0; +} + void *ngap_gNB_process_itti_msg(void *notUsed) { MessageDef *received_msg = NULL; int result; @@ -557,6 +587,10 @@ void *ngap_gNB_process_itti_msg(void *notUsed) { ngap_gNB_handover_notify(instance, &NGAP_HANDOVER_NOTIFY(received_msg)); break; + case NGAP_UL_RAN_STATUS_TRANSFER: + ngap_gNB_handle_ul_ran_status_transfer(instance, &NGAP_UL_RAN_STATUS_TRANSFER(received_msg)); + break; + default: NGAP_ERROR("Received unhandled message: %d:%s\n", ITTI_MSG_ID(received_msg), ITTI_MSG_NAME(received_msg)); break; diff --git a/openair3/NGAP/ngap_gNB_decoder.c b/openair3/NGAP/ngap_gNB_decoder.c index a56357b55a..481cccf876 100644 --- a/openair3/NGAP/ngap_gNB_decoder.c +++ b/openair3/NGAP/ngap_gNB_decoder.c @@ -100,6 +100,12 @@ static int ngap_gNB_decode_initiating_message(NGAP_NGAP_PDU_t *pdu) { NGAP_INFO("Handover Resource Allocation initiating message\n"); break; + case NGAP_ProcedureCode_id_DownlinkRANStatusTransfer: + res = asn_encode_to_new_buffer(NULL, ATS_CANONICAL_XER, &asn_DEF_NGAP_NGAP_PDU, pdu); + free(res.buffer); + NGAP_INFO("DL RAN Status Transfer initiating message\n"); + break; + default: NGAP_ERROR("Unknown procedure ID (%d) for initiating message\n", (int)pdu->choice.initiatingMessage->procedureCode); diff --git a/openair3/NGAP/ngap_gNB_encoder.c b/openair3/NGAP/ngap_gNB_encoder.c index 16954744e2..7a7ce758e1 100644 --- a/openair3/NGAP/ngap_gNB_encoder.c +++ b/openair3/NGAP/ngap_gNB_encoder.c @@ -53,6 +53,7 @@ static inline int ngap_gNB_encode_initiating(NGAP_NGAP_PDU_t *pdu, uint8_t **buf NGAP_ProcedureCode_id_UEContextReleaseRequest, NGAP_ProcedureCode_id_PathSwitchRequest, NGAP_ProcedureCode_id_PDUSessionResourceModifyIndication, + NGAP_ProcedureCode_id_UplinkRANStatusTransfer, NGAP_ProcedureCode_id_HandoverPreparation, NGAP_ProcedureCode_id_HandoverNotification}; int i; diff --git a/openair3/NGAP/ngap_gNB_handlers.c b/openair3/NGAP/ngap_gNB_handlers.c index 226c8e8fa0..0257e49698 100644 --- a/openair3/NGAP/ngap_gNB_handlers.c +++ b/openair3/NGAP/ngap_gNB_handlers.c @@ -1378,6 +1378,100 @@ static int ngap_gNB_handle_ng_ENDC_pdusession_modification_confirm(sctp_assoc_t return 0; } +static int ngap_gNB_handle_dl_ran_status_transfer(sctp_assoc_t assoc_id, uint32_t stream, NGAP_NGAP_PDU_t *pdu) +{ + DevAssert(pdu != NULL); + + NGAP_DownlinkRANStatusTransfer_t *container = &pdu->choice.initiatingMessage->value.choice.DownlinkRANStatusTransfer; + NGAP_DownlinkRANStatusTransferIEs_t *ie = NULL; + + ngap_gNB_amf_data_t *amf_desc_p = ngap_gNB_get_AMF(NULL, assoc_id, 0); + if (!amf_desc_p) { + NGAP_ERROR("[SCTP %u] Received DL RAN Status Transfer for unknown AMF\n", assoc_id); + return -1; + } + + // AMF UE NGAP ID + uint64_t amf_ue_ngap_id = 0; + NGAP_FIND_PROTOCOLIE_BY_ID(NGAP_DownlinkRANStatusTransferIEs_t, ie, container, NGAP_ProtocolIE_ID_id_AMF_UE_NGAP_ID, true); + asn_INTEGER2ulong(&ie->value.choice.AMF_UE_NGAP_ID, &amf_ue_ngap_id); + + // RAN UE NGAP ID + NGAP_FIND_PROTOCOLIE_BY_ID(NGAP_DownlinkRANStatusTransferIEs_t, ie, container, NGAP_ProtocolIE_ID_id_RAN_UE_NGAP_ID, true); + NGAP_RAN_UE_NGAP_ID_t gnb_ue_ngap_id = ie->value.choice.RAN_UE_NGAP_ID; + + ngap_gNB_ue_context_t *ue_desc_p = ngap_get_ue_context(gnb_ue_ngap_id); + if (!ue_desc_p) { + NGAP_ERROR("No UE context for gNB_ue_ngap_id %lu\n", gnb_ue_ngap_id); + return -1; + } + + ue_desc_p->rx_stream = stream; + if (ue_desc_p->amf_ue_ngap_id != amf_ue_ngap_id) + NGAP_WARN("UE context mismatch: amf_ue_ngap_id (%lu != %lu)\n", ue_desc_p->amf_ue_ngap_id, amf_ue_ngap_id); + + // Transparent container + NGAP_FIND_PROTOCOLIE_BY_ID(NGAP_DownlinkRANStatusTransferIEs_t, + ie, + container, + NGAP_ProtocolIE_ID_id_RANStatusTransfer_TransparentContainer, + true); + NGAP_RANStatusTransfer_TransparentContainer_t *tcont = &ie->value.choice.RANStatusTransfer_TransparentContainer; + + MessageDef *message_p = itti_alloc_new_message(TASK_NGAP, 0, NGAP_DL_RAN_STATUS_TRANSFER); + ngap_ran_status_transfer_t *msg = &NGAP_DL_RAN_STATUS_TRANSFER(message_p); + memset(msg, 0, sizeof(*msg)); + + msg->amf_ue_ngap_id = amf_ue_ngap_id; + msg->gnb_ue_ngap_id = gnb_ue_ngap_id; + + int nb = tcont->dRBsSubjectToStatusTransferList.list.count; + for (int i = 0; i < nb; ++i) { + NGAP_DRBsSubjectToStatusTransferItem_t *item = tcont->dRBsSubjectToStatusTransferList.list.array[i]; + ngap_drb_status_t *s = &msg->ran_status.drb_status_list[i]; + s->drb_id = item->dRB_ID; + + // UL COUNT + switch (item->dRBStatusUL.present) { + case NGAP_DRBStatusUL_PR_dRBStatusUL18: + s->ul_count.sn_len = NGAP_SN_LENGTH_18; + s->ul_count.pdcp_sn = item->dRBStatusUL.choice.dRBStatusUL18->uL_COUNTValue.pDCP_SN18; + s->ul_count.hfn = item->dRBStatusUL.choice.dRBStatusUL18->uL_COUNTValue.hFN_PDCP_SN18; + break; + case NGAP_DRBStatusUL_PR_dRBStatusUL12: + s->ul_count.sn_len = NGAP_SN_LENGTH_12; + s->ul_count.pdcp_sn = item->dRBStatusUL.choice.dRBStatusUL12->uL_COUNTValue.pDCP_SN12; + s->ul_count.hfn = item->dRBStatusUL.choice.dRBStatusUL12->uL_COUNTValue.hFN_PDCP_SN12; + break; + default: + NGAP_ERROR("Unknown dRBStatusUL.present=%d\n", item->dRBStatusUL.present); + break; + } + + // DL COUNT + switch (item->dRBStatusDL.present) { + case NGAP_DRBStatusDL_PR_dRBStatusDL18: + s->dl_count.sn_len = NGAP_SN_LENGTH_18; + s->dl_count.pdcp_sn = item->dRBStatusDL.choice.dRBStatusDL18->dL_COUNTValue.pDCP_SN18; + s->dl_count.hfn = item->dRBStatusDL.choice.dRBStatusDL18->dL_COUNTValue.hFN_PDCP_SN18; + break; + case NGAP_DRBStatusDL_PR_dRBStatusDL12: + s->dl_count.sn_len = NGAP_SN_LENGTH_12; + s->dl_count.pdcp_sn = item->dRBStatusDL.choice.dRBStatusDL12->dL_COUNTValue.pDCP_SN12; + s->dl_count.hfn = item->dRBStatusDL.choice.dRBStatusDL12->dL_COUNTValue.hFN_PDCP_SN12; + break; + default: + NGAP_ERROR("Unknown dRBStatusDL.present=%d\n", item->dRBStatusDL.present); + break; + } + + msg->ran_status.nb_drb++; + } + + itti_send_msg_to_task(TASK_RRC_GNB, ue_desc_p->gNB_instance->instance, message_p); + return 0; +} + /* Handlers matrix. Only gNB related procedure present here */ const ngap_message_decoded_callback ngap_messages_callback[][3] = { {0, 0, 0}, /* AMFConfigurationUpdate */ @@ -1387,7 +1481,7 @@ const ngap_message_decoded_callback ngap_messages_callback[][3] = { {ngap_gNB_handle_nas_downlink, 0, 0}, /* DownlinkNASTransport */ {0, 0, 0}, /* DownlinkNonUEAssociatedNRPPaTransport */ {0, 0, 0}, /* DownlinkRANConfigurationTransfer */ - {0, 0, 0}, /* DownlinkRANStatusTransfer */ + {ngap_gNB_handle_dl_ran_status_transfer, 0, 0}, /* DownlinkRANStatusTransfer */ {0, 0, 0}, /* DownlinkUEAssociatedNRPPaTransport */ {ngap_gNB_handle_error_indication, 0, 0}, /* ErrorIndication */ {0, 0, 0}, /* HandoverCancel */ diff --git a/openair3/NGAP/ngap_gNB_mobility_management.c b/openair3/NGAP/ngap_gNB_mobility_management.c index 565be7acd5..4f1383e5e0 100644 --- a/openair3/NGAP/ngap_gNB_mobility_management.c +++ b/openair3/NGAP/ngap_gNB_mobility_management.c @@ -617,3 +617,73 @@ NGAP_NGAP_PDU_t *encode_ng_handover_notify(const ngap_handover_notify_t *msg) return pdu; } + +/** @brief Encode NGAP UL RAN Status Transfer (9.2.3.14 of 3GPP TS 38.413) */ +NGAP_NGAP_PDU_t *encode_ng_ul_ran_status_transfer(const ngap_ran_status_transfer_t *msg) +{ + NGAP_NGAP_PDU_t *pdu = malloc_or_fail(sizeof(*pdu)); + pdu->present = NGAP_NGAP_PDU_PR_initiatingMessage; + asn1cCalloc(pdu->choice.initiatingMessage, head); + head->procedureCode = NGAP_ProcedureCode_id_UplinkRANStatusTransfer; + head->criticality = NGAP_Criticality_ignore; + head->value.present = NGAP_InitiatingMessage__value_PR_UplinkRANStatusTransfer; + NGAP_UplinkRANStatusTransfer_t *out = &head->value.choice.UplinkRANStatusTransfer; + + // AMF UE NGAP ID (M) + asn1cSequenceAdd(out->protocolIEs.list, NGAP_UplinkRANStatusTransferIEs_t, ie1); + ie1->id = NGAP_ProtocolIE_ID_id_AMF_UE_NGAP_ID; + ie1->criticality = NGAP_Criticality_reject; + ie1->value.present = NGAP_UplinkRANStatusTransferIEs__value_PR_AMF_UE_NGAP_ID; + asn_uint642INTEGER(&ie1->value.choice.AMF_UE_NGAP_ID, msg->amf_ue_ngap_id); + + // RAN UE NGAP ID (M) + asn1cSequenceAdd(out->protocolIEs.list, NGAP_UplinkRANStatusTransferIEs_t, ie2); + ie2->id = NGAP_ProtocolIE_ID_id_RAN_UE_NGAP_ID; + ie2->criticality = NGAP_Criticality_reject; + ie2->value.present = NGAP_UplinkRANStatusTransferIEs__value_PR_RAN_UE_NGAP_ID; + ie2->value.choice.RAN_UE_NGAP_ID = msg->gnb_ue_ngap_id; + + // RAN Status Transfer Transparent Container (M) + asn1cSequenceAdd(out->protocolIEs.list, NGAP_UplinkRANStatusTransferIEs_t, ie3); + ie3->id = NGAP_ProtocolIE_ID_id_RANStatusTransfer_TransparentContainer; + ie3->criticality = NGAP_Criticality_reject; + ie3->value.present = NGAP_UplinkRANStatusTransferIEs__value_PR_RANStatusTransfer_TransparentContainer; + NGAP_RANStatusTransfer_TransparentContainer_t *container = &ie3->value.choice.RANStatusTransfer_TransparentContainer; + + for (int i = 0; i < msg->ran_status.nb_drb; ++i) { + const ngap_drb_status_t *s = &msg->ran_status.drb_status_list[i]; + asn1cSequenceAdd(container->dRBsSubjectToStatusTransferList.list, NGAP_DRBsSubjectToStatusTransferItem_t, drb_item); + drb_item->dRB_ID = s->drb_id; + + // UL COUNT + if (s->ul_count.sn_len == NGAP_SN_LENGTH_18) { + drb_item->dRBStatusUL.present = NGAP_DRBStatusUL_PR_dRBStatusUL18; + asn1cCalloc(drb_item->dRBStatusUL.choice.dRBStatusUL18, ul18); + ul18->uL_COUNTValue.pDCP_SN18 = s->ul_count.pdcp_sn; + ul18->uL_COUNTValue.hFN_PDCP_SN18 = s->ul_count.hfn; + } else { + drb_item->dRBStatusUL.present = NGAP_DRBStatusUL_PR_dRBStatusUL12; + asn1cCalloc(drb_item->dRBStatusUL.choice.dRBStatusUL12, ul12); + ul12->uL_COUNTValue.pDCP_SN12 = s->ul_count.pdcp_sn; + ul12->uL_COUNTValue.hFN_PDCP_SN12 = s->ul_count.hfn; + } + + // DL COUNT + if (s->dl_count.sn_len == NGAP_SN_LENGTH_18) { + drb_item->dRBStatusDL.present = NGAP_DRBStatusDL_PR_dRBStatusDL18; + asn1cCalloc(drb_item->dRBStatusDL.choice.dRBStatusDL18, dl18); + dl18->dL_COUNTValue.pDCP_SN18 = s->dl_count.pdcp_sn; + dl18->dL_COUNTValue.hFN_PDCP_SN18 = s->dl_count.hfn; + } else { + drb_item->dRBStatusDL.present = NGAP_DRBStatusDL_PR_dRBStatusDL12; + asn1cCalloc(drb_item->dRBStatusDL.choice.dRBStatusDL12, dl12); + dl12->dL_COUNTValue.pDCP_SN12 = s->dl_count.pdcp_sn; + dl12->dL_COUNTValue.hFN_PDCP_SN12 = s->dl_count.hfn; + } + } + + if (LOG_DEBUGFLAG(DEBUG_ASN1)) + xer_fprint(stdout, &asn_DEF_NGAP_RANStatusTransfer_TransparentContainer, &container); + + return pdu; +} diff --git a/openair3/NGAP/ngap_gNB_mobility_management.h b/openair3/NGAP/ngap_gNB_mobility_management.h index d31bdb0caa..b7a6f3547c 100644 --- a/openair3/NGAP/ngap_gNB_mobility_management.h +++ b/openair3/NGAP/ngap_gNB_mobility_management.h @@ -34,5 +34,6 @@ void free_ng_handover_req_ack(ngap_handover_request_ack_t *msg); int decode_ng_handover_command(ngap_handover_command_t *msg, NGAP_NGAP_PDU_t *pdu); void free_ng_handover_command(ngap_handover_command_t *msg); NGAP_NGAP_PDU_t *encode_ng_handover_notify(const ngap_handover_notify_t *msg); +NGAP_NGAP_PDU_t *encode_ng_ul_ran_status_transfer(const ngap_ran_status_transfer_t *msg); #endif /* NGAP_GNB_MOBILITY_MANAGEMENT_H_ */ diff --git a/openair3/NGAP/ngap_msg_includes.h b/openair3/NGAP/ngap_msg_includes.h index 4224660974..816cdfb979 100644 --- a/openair3/NGAP/ngap_msg_includes.h +++ b/openair3/NGAP/ngap_msg_includes.h @@ -102,5 +102,12 @@ #include "NGAP_QosFlowItemWithDataForwarding.h" #include "NR_HandoverPreparationInformation.h" #include "NGAP_HandoverNotify.h" +#include "NGAP_DRBsSubjectToStatusTransferItem.h" +#include "NGAP_DRBStatusUL.h" +#include "NGAP_DRBStatusUL18.h" +#include "NGAP_DRBStatusUL12.h" +#include "NGAP_DRBStatusDL.h" +#include "NGAP_DRBStatusDL18.h" +#include "NGAP_DRBStatusDL12.h" #endif // NGAP_MSG_INCLUDES_H From e527b99de120ff1a8776b0a1f79c353db96655b9 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Wed, 20 Aug 2025 12:45:36 +0200 Subject: [PATCH 42/52] PDCP Status Information: add enc/dec/cp/eq and test to E1AP lib Also: * remove unused oldQosFlows IEs * adopt new enc/dec functions * fix missing return in eq_bearer_context_mod_response --- openair2/COMMON/e1ap_messages_types.h | 23 ++++- .../E1AP/lib/e1ap_bearer_context_management.c | 83 +++++++++++++++++-- openair2/E1AP/tests/e1ap_lib_test.c | 18 ++++ 3 files changed, 116 insertions(+), 8 deletions(-) diff --git a/openair2/COMMON/e1ap_messages_types.h b/openair2/COMMON/e1ap_messages_types.h index f4e9090f66..788236ca05 100644 --- a/openair2/COMMON/e1ap_messages_types.h +++ b/openair2/COMMON/e1ap_messages_types.h @@ -283,6 +283,22 @@ typedef struct bearer_context_pdcp_config_s { bool pDCP_Reestablishment; } bearer_context_pdcp_config_t; +/* 9.3.1.35 3GPP TS 38.463 */ +typedef struct { + // PDCP SN + uint32_t sn; + // HFN + uint32_t hfn; +} e1_pdcp_count_t; + +/* 9.3.1.58 3GPP TS 38.463 */ +typedef struct { + // UL Count Value + e1_pdcp_count_t ul_count; + // DL Count Value + e1_pdcp_count_t dl_count; +} e1_pdcp_status_info_t; + typedef struct drb_to_setup_s { long drbId; bearer_context_pdcp_config_t pdcp_config; @@ -384,6 +400,8 @@ typedef struct DRB_nGRAN_to_modify_s { bearer_context_pdcp_config_t *pdcp_config; // PDCP SN Status Request (O) bool pdcp_sn_status_requested; + // PDCP SN Status Information + e1_pdcp_status_info_t *pdcp_status; // DL UP Transport Layer Information (O) (clause 9.3.1.13, 9.3.2.1) int numDlUpParam; up_params_t DlUpParamList[E1AP_MAX_NUM_UP_PARAM]; @@ -561,9 +579,8 @@ typedef struct DRB_nGRAN_modified_s { // Flow Failed List (O) int numQosFlowFailed; qos_flow_failed_t qosFlowsFailed[E1AP_MAX_NUM_QOS_FLOWS]; - // Old QoS Flow List (O) - int numOldQosFlow; - qos_flow_list_t oldQosFlows[E1AP_MAX_NUM_QOS_FLOWS]; + // PDCP SN Status Information (0) + e1_pdcp_status_info_t *pdcp_status; } DRB_nGRAN_modified_t; typedef struct DRB_nGRAN_failed_s { diff --git a/openair2/E1AP/lib/e1ap_bearer_context_management.c b/openair2/E1AP/lib/e1ap_bearer_context_management.c index f9afa06f69..0f2e5972e2 100644 --- a/openair2/E1AP/lib/e1ap_bearer_context_management.c +++ b/openair2/E1AP/lib/e1ap_bearer_context_management.c @@ -360,6 +360,39 @@ static E1AP_UP_Parameters_t encode_dl_up_parameters(const int numUpParam, const return out; } +/** @brief PDCP SN Status Information (9.3.1.58 3GPP TS 38.463) */ +static E1AP_PDCP_SN_Status_Information_t encode_pdcp_status_info(const e1_pdcp_status_info_t *in) +{ + E1AP_PDCP_SN_Status_Information_t pdcp = {0}; + pdcp.pdcpStatusTransfer_DL.hFN = in->dl_count.hfn; + pdcp.pdcpStatusTransfer_DL.pDCP_SN = in->dl_count.sn; + pdcp.pdcpStatusTransfer_UL.countValue.hFN = in->ul_count.hfn; + pdcp.pdcpStatusTransfer_UL.countValue.pDCP_SN = in->ul_count.sn; + return pdcp; +} + +/** @brief PDCP SN Status Information (9.3.1.58 3GPP TS 38.463) */ +static e1_pdcp_status_info_t decode_pdcp_status_info(const E1AP_PDCP_SN_Status_Information_t* in) +{ + e1_pdcp_status_info_t out = {0}; + const E1AP_PDCP_Count_t *dl = &in->pdcpStatusTransfer_DL; + const E1AP_PDCP_Count_t *ul = &in->pdcpStatusTransfer_UL.countValue; + out.dl_count.hfn = dl->hFN; + out.dl_count.sn = dl->pDCP_SN; + out.ul_count.hfn = ul->hFN; + out.ul_count.sn = ul->pDCP_SN; + return out; +} + +static bool eq_pdcp_info(const e1_pdcp_status_info_t *a, const e1_pdcp_status_info_t *b) +{ + _E1_EQ_CHECK_INT(a->dl_count.hfn, b->dl_count.hfn); + _E1_EQ_CHECK_INT(a->dl_count.sn, b->dl_count.sn); + _E1_EQ_CHECK_INT(a->ul_count.hfn, b->ul_count.hfn); + _E1_EQ_CHECK_INT(a->ul_count.sn, b->ul_count.sn); + return true; +} + /** * @brief Equality check for DRB_nGRAN_to_setup_t */ @@ -1409,6 +1442,11 @@ static E1AP_PDU_Session_Resource_To_Modify_Item_t e1_encode_pdu_session_to_mod_i asn1cCalloc(drb2Mod->pDCP_SN_Status_Request, pDCP_SN_Status_Request); *pDCP_SN_Status_Request = E1AP_PDCP_SN_Status_Request_requested; } + // PDCP Status Information (O) + if (j->pdcp_status) { + asn1cCalloc(drb2Mod->pdcp_SN_Status_Information, pdcp); + *pdcp = encode_pdcp_status_info(j->pdcp_status); + } // DL UP TNL parameters (O) if (j->numDlUpParam > 0) { asn1cCalloc(drb2Mod->dL_UP_Parameters, DL_UP_Param_List); @@ -1667,9 +1705,14 @@ static bool e1_decode_pdu_session_to_mod_item(pdu_session_to_mod_t *out, const E CHECK_E1AP_DEC(e1_decode_sdap_config(drb->sdap_config, drb2Mod->sDAP_Configuration)); } // PDCP SN Status Request (O) - if (drb2Mod->pDCP_SN_Status_Request) { + if (drb2Mod->pDCP_SN_Status_Request && *drb2Mod->pDCP_SN_Status_Request == E1AP_PDCP_SN_Status_Request_requested) { drb->pdcp_sn_status_requested = true; } + // PDCP SN Status Information (O) + if (drb2Mod->pdcp_SN_Status_Information) { + drb->pdcp_status = malloc_or_fail(sizeof(*drb->pdcp_status)); + *drb->pdcp_status = decode_pdcp_status_info(drb2Mod->pdcp_SN_Status_Information); + } // DL UP TNL parameters (O) if (drb2Mod->dL_UP_Parameters) { E1AP_UP_Parameters_t *dl_up_paramList = drb2Mod->dL_UP_Parameters; @@ -1843,6 +1886,7 @@ static DRB_nGRAN_to_mod_t cp_drb_to_mod_item(const DRB_nGRAN_to_mod_t *msg) cp = *msg; _E1_CP_OPTIONAL_IE(&cp, msg, sdap_config); _E1_CP_OPTIONAL_IE(&cp, msg, pdcp_config); + _E1_CP_OPTIONAL_IE(&cp, msg, pdcp_status); return cp; } @@ -1904,12 +1948,15 @@ static bool eq_drb_to_mod(const DRB_nGRAN_to_mod_t *a, const DRB_nGRAN_to_mod_t _E1_EQ_CHECK_INT(a->numDlUpParam, b->numDlUpParam); for (int i = 0; i < a->numDlUpParam; i++) { _E1_EQ_CHECK_INT(a->DlUpParamList[i].cell_group_id, b->DlUpParamList[i].cell_group_id); - eq_up_tl_info(&a->DlUpParamList[i].tl_info, &b->DlUpParamList[i].tl_info); + result &= eq_up_tl_info(&a->DlUpParamList[i].tl_info, &b->DlUpParamList[i].tl_info); } if (a->pdcp_config && b->pdcp_config) result &= eq_pdcp_config(a->pdcp_config, b->pdcp_config); if (a->sdap_config && b->sdap_config) result &= eq_sdap_config(a->sdap_config, b->sdap_config); + _E1_EQ_CHECK_OPTIONAL_PTR(a, b, pdcp_status); + if (a->pdcp_status && b->pdcp_status) + result &= eq_pdcp_info(a->pdcp_status, b->pdcp_status); return result; } @@ -1984,6 +2031,7 @@ static void free_drb_to_mod_item(const DRB_nGRAN_to_mod_t *msg) { free(msg->pdcp_config); free(msg->sdap_config); + free(msg->pdcp_status); } /* Free PDU Session to modify item */ @@ -2111,6 +2159,13 @@ struct E1AP_E1AP_PDU *encode_E1_bearer_context_mod_response(const e1ap_bearer_mo asn1cSequenceAdd(drb_mod->flow_Failed_List->list, E1AP_QoS_Flow_Failed_Item_t, fail); encode_qos_flow_failed_item(fail, &drb->qosFlowsFailed[q]); } + + // PDCP Status Information (O) + if (drb->pdcp_status) { + asn1cCalloc(drb_mod->pDCP_SN_Status_Information, pdcp); + *pdcp = encode_pdcp_status_info(drb->pdcp_status); + } + // UL UP Parameters (O) if (drb->numUpParam) drb_mod->uL_UP_Transport_Parameters = calloc_or_fail(1, sizeof(*drb_mod->uL_UP_Transport_Parameters)); @@ -2310,6 +2365,11 @@ bool decode_E1_bearer_context_mod_response(e1ap_bearer_modif_resp_t *out, const drbMod->qosFlowsFailed[i] = decode_qos_flow_failed_item(drbIE->flow_Failed_List->list.array[i]); } } + // PDCP Status Info (O) + if (drbIE->pDCP_SN_Status_Information) { + drbMod->pdcp_status = malloc_or_fail(sizeof(*drbMod->pdcp_status)); + *drbMod->pdcp_status = decode_pdcp_status_info(drbIE->pDCP_SN_Status_Information); + } } } // DRB Failed to Mod list @@ -2347,8 +2407,12 @@ e1ap_bearer_modif_resp_t cp_bearer_context_mod_response(const e1ap_bearer_modif_ // Shallow copy cp = *msg; // Deep copy PDU sessions modified items - for (int i = 0; i < msg->numPDUSessionsMod; i++) + for (int i = 0; i < msg->numPDUSessionsMod; i++) { cp.pduSessionMod[i] = cp_pdu_session_mod_item(&msg->pduSessionMod[i]); + for (int j = 0; j < msg->pduSessionMod[i].numDRBModified; j++) { + _E1_CP_OPTIONAL_IE(&cp.pduSessionMod[i].DRBnGRanModList[j], &msg->pduSessionMod[i].DRBnGRanModList[j], pdcp_status); + } + } return cp; } @@ -2359,6 +2423,8 @@ bool eq_bearer_context_mod_response(const e1ap_bearer_modif_resp_t *a, const e1a { if (!a || !b) return false; // Null-check both inputs + bool result = true; + // Basic members _E1_EQ_CHECK_INT(a->gNB_cu_cp_ue_id, b->gNB_cu_cp_ue_id); _E1_EQ_CHECK_INT(a->gNB_cu_up_ue_id, b->gNB_cu_up_ue_id); @@ -2375,7 +2441,7 @@ bool eq_bearer_context_mod_response(const e1ap_bearer_modif_resp_t *a, const e1a _E1_EQ_CHECK_OPTIONAL_PTR(pduA, pduB, ng_DL_UP_TL_info); if (pduA->ng_DL_UP_TL_info && pduB->ng_DL_UP_TL_info) - eq_up_tl_info(pduA->ng_DL_UP_TL_info, pduB->ng_DL_UP_TL_info); + result &= eq_up_tl_info(pduA->ng_DL_UP_TL_info, pduB->ng_DL_UP_TL_info); // DRB Modified List _E1_EQ_CHECK_INT(pduA->numDRBModified, pduB->numDRBModified); @@ -2387,6 +2453,10 @@ bool eq_bearer_context_mod_response(const e1ap_bearer_modif_resp_t *a, const e1a for (int j = 0; j < drbModA->numQosFlowSetup; j++) { _E1_EQ_CHECK_LONG(drbModA->qosFlows[j].qfi, drbModB->qosFlows[j].qfi); } + _E1_EQ_CHECK_OPTIONAL_PTR(drbModA, drbModB, pdcp_status); + if (drbModA->pdcp_status && drbModB->pdcp_status) { + result &= eq_pdcp_info(drbModA->pdcp_status, drbModA->pdcp_status); + } } // DRB Failed to Modify List @@ -2420,7 +2490,7 @@ bool eq_bearer_context_mod_response(const e1ap_bearer_modif_resp_t *a, const e1a } - return true; + return result; } /** @@ -2432,6 +2502,9 @@ void free_e1ap_context_mod_response(const e1ap_bearer_modif_resp_t *msg) free(msg->pduSessionMod[i].confidentialityProtectionIndication); free(msg->pduSessionMod[i].integrityProtectionIndication); free(msg->pduSessionMod[i].ng_DL_UP_TL_info); + for (int j = 0; j < msg->pduSessionMod[i].numDRBModified; j++) { + free(msg->pduSessionMod[i].DRBnGRanModList[j].pdcp_status); + } } } diff --git a/openair2/E1AP/tests/e1ap_lib_test.c b/openair2/E1AP/tests/e1ap_lib_test.c index a9fd7aa390..b168ff9e82 100644 --- a/openair2/E1AP/tests/e1ap_lib_test.c +++ b/openair2/E1AP/tests/e1ap_lib_test.c @@ -490,6 +490,13 @@ static void test_bearer_context_modification_request(void) .qos_params.qos_characteristics.non_dynamic.fiveqi = 9, }; + const e1_pdcp_status_info_t dummy_pdcp_status = { + .dl_count.hfn = 12, + .dl_count.sn = 34, + .ul_count.hfn = 56, + .ul_count.sn = 78, + }; + DRB_nGRAN_to_mod_t drb_to_mod = { .numDlUpParam = 1, .DlUpParamList[0].cell_group_id = MCG, @@ -497,10 +504,12 @@ static void test_bearer_context_modification_request(void) .id = 1, .pdcp_config = malloc_or_fail(sizeof(*drb_to_mod.pdcp_config)), .pdcp_sn_status_requested = true, + .pdcp_status = malloc_or_fail(sizeof(*drb_to_mod.pdcp_status)), .numQosFlow2Setup = 1, .qosFlows[0] = dummy_qos_flows, }; *drb_to_mod.pdcp_config = dummy_pdcp_config; + *drb_to_mod.pdcp_status = dummy_pdcp_status; pdu_session_to_mod_t pdusession_mod_item = { .sessionId = 1, @@ -592,12 +601,21 @@ const DRB_nGRAN_failed_t dummy_drb_failed = { */ static void test_bearer_context_modification_response(void) { + const e1_pdcp_status_info_t dummy_pdcp_status = { + .dl_count.hfn = 12, + .dl_count.sn = 2, + .ul_count.hfn = 12, + .ul_count.sn = 3, + }; + // DRB Modified List DRB_nGRAN_modified_t drb_mod = { .id = 1, .numQosFlowSetup = 1, .qosFlows[0].qfi = 1, + .pdcp_status = malloc_or_fail(sizeof(*drb_mod.pdcp_status)), }; + *drb_mod.pdcp_status = dummy_pdcp_status; // DRB Setup List DRB_nGRAN_setup_t setup = { From e96d7f9576225434af6d5e9031e2028423bed57d Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Wed, 20 Aug 2025 23:00:01 +0200 Subject: [PATCH 43/52] Add PDCP API getter and setter for PDCP count values in PDCP entity * Introduced nr_pdcp_count_update() to apply COUNT values to the PDCP entity: - Computes tx_next, rx_next, and rx_deliv from HFN+SN - Handles both 12-bit and 18-bit SN modes with dynamic bitmask * Introduced nr_pdcp_get_drb_count_values() to extract PDCP COUNT values from entity: - Used for building UL RAN Status Transfer on source CU side --- openair2/LAYER2/nr_pdcp/nr_pdcp_entity.c | 94 +++++++++++++++++++++++ openair2/LAYER2/nr_pdcp/nr_pdcp_entity.h | 13 +++- openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.c | 54 +++++++++++++ openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.h | 8 ++ 4 files changed, 168 insertions(+), 1 deletion(-) diff --git a/openair2/LAYER2/nr_pdcp/nr_pdcp_entity.c b/openair2/LAYER2/nr_pdcp/nr_pdcp_entity.c index 41d41cf922..25081891d3 100644 --- a/openair2/LAYER2/nr_pdcp/nr_pdcp_entity.c +++ b/openair2/LAYER2/nr_pdcp/nr_pdcp_entity.c @@ -601,6 +601,94 @@ static void nr_pdcp_entity_get_stats(nr_pdcp_entity_t *entity, *out = entity->stats; } +/** @note: this function applies to the gNB only */ +static nr_pdcp_count_t nr_pdcp_entity_get_count_dl(nr_pdcp_entity_t *entity) +{ + nr_pdcp_count_t count = {0}; + const int sn_bits = entity->sn_size; + const uint32_t sn_mask = entity->sn_max; + count.hfn = entity->rx_deliv >> sn_bits; + count.sn = entity->rx_deliv & sn_mask; + return count; +} + +/** @note: this function applies to the gNB only */ +static nr_pdcp_count_t nr_pdcp_entity_get_count_ul(nr_pdcp_entity_t *entity) +{ + nr_pdcp_count_t count = {0}; + const int sn_bits = entity->sn_size; + const uint32_t sn_mask = entity->sn_max; + count.hfn = entity->tx_next >> sn_bits; + count.sn = entity->tx_next & sn_mask; + return count; +} + +/** @note: this function applies to the gNB only */ +static void nr_pdcp_entity_set_count_dl(nr_pdcp_entity_t *entity, nr_pdcp_count_t pdcp_count, int sn_size) +{ + // SN length in bits + const uint32_t sn_mask = (1 << sn_size) - 1; + + // Compose COUNT values for tx_next (DL) and rx_next (UL) + uint32_t count = (pdcp_count.hfn << sn_size) | (pdcp_count.sn & sn_mask); + + // Apply the COUNT values + entity->rx_next = count + 1; + entity->rx_deliv = count; + + LOG_I(PDCP, + "DRB %d: Applied PDCP Count\n" + " DL COUNT - SN: %d HFN: %d (COUNT=%u)\n" + " SN length: %s\n" + " PDCP entity state after update:\n" + " rx_next = %u\n" + " rx_deliv = %u\n" + " sn_size = %d\n" + " sn_max = %d\n" + " window = %d\n", + entity->rb_id, + pdcp_count.sn, + pdcp_count.hfn, + count, + sn_size == LONG_SN_SIZE ? "18-bit" : "12-bit", + entity->rx_next, + entity->rx_deliv, + entity->sn_size, + entity->sn_max, + entity->window_size); +} + +/** @note: this function applies to the gNB only */ +static void nr_pdcp_entity_set_count_ul(nr_pdcp_entity_t *entity, nr_pdcp_count_t pdcp_count, int sn_size) +{ + // SN length in bits + const uint32_t sn_mask = (1 << sn_size) - 1; + + // Compose COUNT values for tx_next (DL) and rx_next (UL) + uint32_t count = (pdcp_count.hfn << sn_size) | (pdcp_count.sn & sn_mask); + + // Apply the COUNT values + entity->tx_next = count; + + LOG_I(PDCP, + "DRB %d: Applied PDCP Count\n" + " UL COUNT - SN: %d HFN: %d (COUNT=%u)\n" + " SN length: %s\n" + " PDCP entity state after update:\n" + " tx_next = %u\n" + " sn_size = %d\n" + " sn_max = %d\n" + " window = %d\n", + entity->rb_id, + pdcp_count.sn, + pdcp_count.hfn, + count, + sn_size == LONG_SN_SIZE ? "18-bit" : "12-bit", + entity->tx_next, + entity->sn_size, + entity->sn_max, + entity->window_size); +} nr_pdcp_entity_t *new_nr_pdcp_entity( nr_pdcp_entity_type_t type, @@ -675,5 +763,11 @@ nr_pdcp_entity_t *new_nr_pdcp_entity( nr_pdcp_entity_set_security(ret, security_parameters); + /* PDCP Count API */ + ret->get_pdcp_count_dl = nr_pdcp_entity_get_count_dl; + ret->get_pdcp_count_ul = nr_pdcp_entity_get_count_ul; + ret->set_pdcp_count_dl = nr_pdcp_entity_set_count_dl; + ret->set_pdcp_count_ul = nr_pdcp_entity_set_count_ul; + return ret; } diff --git a/openair2/LAYER2/nr_pdcp/nr_pdcp_entity.h b/openair2/LAYER2/nr_pdcp/nr_pdcp_entity.h index 7f1297ac3e..84ecce8997 100644 --- a/openair2/LAYER2/nr_pdcp/nr_pdcp_entity.h +++ b/openair2/LAYER2/nr_pdcp/nr_pdcp_entity.h @@ -55,6 +55,11 @@ typedef struct { uint8_t ciphering_key[NR_K_KEY_SIZE]; } nr_pdcp_entity_security_keys_and_algos_t; +typedef struct { + uint32_t sn; + uint32_t hfn; +} nr_pdcp_count_t; + typedef struct { //nr_pdcp_entity_type_t mode; /* PDU stats */ @@ -87,7 +92,6 @@ typedef struct { uint8_t mode; /* 0: PDCP AM, 1: PDCP UM, 2: PDCP TM */ } nr_pdcp_statistics_t; - typedef struct nr_pdcp_entity_t { nr_pdcp_entity_type_t type; @@ -180,6 +184,13 @@ typedef struct nr_pdcp_entity_t { /* Keep tracks of whether the PDCP entity was suspended or not */ bool entity_suspended; + + /* PDCP Count API */ + nr_pdcp_count_t (*get_pdcp_count_dl)(struct nr_pdcp_entity_t *entity); + nr_pdcp_count_t (*get_pdcp_count_ul)(struct nr_pdcp_entity_t *entity); + void (*set_pdcp_count_dl)(struct nr_pdcp_entity_t *entity, nr_pdcp_count_t count, int sn_size); + void (*set_pdcp_count_ul)(struct nr_pdcp_entity_t *entity, nr_pdcp_count_t count, int sn_size); + } nr_pdcp_entity_t; nr_pdcp_entity_t *new_nr_pdcp_entity( diff --git a/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.c b/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.c index 4d1c0b5ba0..ce885b6b33 100644 --- a/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.c +++ b/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.c @@ -1072,3 +1072,57 @@ int nr_pdcp_get_num_ues(ue_id_t *ue_list, int len) return num_ues; } + +void nr_pdcp_count_update(ue_id_t ue_id, + rb_id_t drb_id, + nr_pdcp_count_t dl_count, + nr_pdcp_count_t ul_count, + int sn_size) +{ + nr_pdcp_manager_lock(nr_pdcp_ue_manager); + + nr_pdcp_ue_t *ue = nr_pdcp_manager_get_ue(nr_pdcp_ue_manager, ue_id); + if (ue == NULL) { + LOG_E(PDCP, "UE %ld not found in PDCP manager\n", ue_id); + nr_pdcp_manager_unlock(nr_pdcp_ue_manager); + return; + } + + nr_pdcp_entity_t *entity = nr_pdcp_get_rb(ue, drb_id, false); + if (entity == NULL) { + LOG_E(PDCP, "No PDCP entity found for UE %ld DRB %ld\n", ue_id, drb_id); + nr_pdcp_manager_unlock(nr_pdcp_ue_manager); + return; + } + + entity->set_pdcp_count_dl(entity, dl_count, sn_size); + entity->set_pdcp_count_ul(entity, ul_count, sn_size); + + nr_pdcp_manager_unlock(nr_pdcp_ue_manager); +} + +/** * @brief Get PDCP COUNT values (PDCP-SN and HFN) for UL and DL from the PDCP entity + * @param[in] ue_id Unique UE ID + * @param[in] rb_id Radio Bearer ID + * @param[out] ul_count UL PDCP count (SN, HFN) + * @param[out] dl_count DL PDCP count (SN, HFN) + */ +void nr_pdcp_get_drb_count_values(ue_id_t ue_id, rb_id_t rb_id, nr_pdcp_count_t *ul_count, nr_pdcp_count_t *dl_count) +{ + nr_pdcp_manager_lock(nr_pdcp_ue_manager); + + nr_pdcp_ue_t *ue = nr_pdcp_manager_get_ue(nr_pdcp_ue_manager, ue_id); + + nr_pdcp_entity_t *entity = nr_pdcp_get_rb(ue, rb_id, false); + + if (!entity) { + LOG_E(PDCP, "No entity found (DRB %ld)\n", rb_id); + nr_pdcp_manager_unlock(nr_pdcp_ue_manager); + return; + } + + *dl_count = entity->get_pdcp_count_dl(entity); + *ul_count = entity->get_pdcp_count_ul(entity); + + nr_pdcp_manager_unlock(nr_pdcp_ue_manager); +} diff --git a/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.h b/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.h index 8d6a65cad6..375bd27458 100644 --- a/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.h +++ b/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.h @@ -125,4 +125,12 @@ int nr_pdcp_get_num_ues(ue_id_t *ue_list, int len); bool nr_pdcp_get_statistics(ue_id_t ue_id, int srb_flag, int rb_id, nr_pdcp_statistics_t *out); +void nr_pdcp_count_update(ue_id_t ue_id, + rb_id_t drb_id, + nr_pdcp_count_t dl_count, + nr_pdcp_count_t ul_count, + int sn_size); + +void nr_pdcp_get_drb_count_values(ue_id_t ue_id, rb_id_t rb_id, nr_pdcp_count_t *ul_count, nr_pdcp_count_t *dl_count); + #endif /* NR_PDCP_OAI_API_H */ From fc5e3ce61d17713523bfc4ee231067fc5596a3ca Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Wed, 20 Aug 2025 15:09:20 +0200 Subject: [PATCH 44/52] source-CU-CP: trigger E1 PDCP Status Request in UE Context Modification Response handler The E1 Bearer Context Modification Request shall send a PDCP Status Request IE during handover, e.g. the HO context is set to source, and it is not target, i.e. only in N2 not in F1. The current function "e1_send_bearer_updates" used to fill the E1 Request has been refactored only for the sake of this commit and to avoid duplicated and redundant code. The function itself deeper analysis and eventually a refactoring that will not be addressed in this commit. --- openair2/RRC/NR/rrc_gNB.c | 43 ++++++++++++++++++++++++++------------- 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/openair2/RRC/NR/rrc_gNB.c b/openair2/RRC/NR/rrc_gNB.c index 75a3296b8f..4bfe0b7747 100644 --- a/openair2/RRC/NR/rrc_gNB.c +++ b/openair2/RRC/NR/rrc_gNB.c @@ -2091,20 +2091,34 @@ static void e1_send_bearer_updates(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, int n, f .gNB_cu_up_ue_id = UE->rrc_ue_id, }; - for (int i = 0; i < n; i++) { - const f1ap_drb_setup_t *drb_f1 = &drbs[i]; - rrc_pdu_session_param_t *pdu_ue = find_pduSession_from_drbId(UE, drb_f1->id); - if (pdu_ue == NULL) { - LOG_E(RRC, "UE %d: UE Context Modif Response: no PDU session for DRB ID %d\n", UE->rrc_ue_id, drb_f1->id); - continue; + bool is_inter_cu_ho = UE->ho_context && UE->ho_context->source && !UE->ho_context->target; + for (int i = 0; i < MAX_DRBS_PER_UE; i++) { + int drb_id = i + 1; + DRB_nGRAN_to_mod_t drb_to_mod = {0}; + if (n > 0) { + /* DRBs to Setup in F1 UE Context Modifcation Response */ + if (i == n) + break; + const f1ap_drb_setup_t *drb_f1 = &drbs[i]; + drb_id = drb_f1->id; + fill_e1_bearer_modif(&drb_to_mod, drb_f1); + } else if (is_inter_cu_ho) { + /* On-going handover: send PDCP Status Request */ + if (UE->established_drbs[i].status == DRB_INACTIVE) + continue; + drb_to_mod.id = drb_id; + // PDCP SN Status Request + drb_to_mod.pdcp_sn_status_requested = true; + LOG_I(NR_RRC, "PDCP Status requested (drb_id=%d)\n", drb_id); } - pdu_session_to_mod_t *pdu_e1 = find_or_next_pdu_session(&req, pdu_ue->param.pdusession_id); - DevAssert(pdu_e1 != NULL); - pdu_e1->sessionId = pdu_ue->param.pdusession_id; - DRB_nGRAN_to_mod_t *drb_e1 = &pdu_e1->DRBnGRanModList[pdu_e1->numDRB2Modify]; - /* Fill E1 bearer context modification */ - fill_e1_bearer_modif(drb_e1, drb_f1); - pdu_e1->numDRB2Modify += 1; + rrc_pdu_session_param_t *pdu = find_pduSession_from_drbId(UE, drb_id); + if (!pdu) + continue; + pdu_session_to_mod_t *to_mod = find_or_next_pdu_session(&req, pdu->param.pdusession_id); + DevAssert(to_mod); + to_mod->sessionId = pdu->param.pdusession_id; + // Fill E1 DRB to Modify item + to_mod->DRBnGRanModList[to_mod->numDRB2Modify++] = drb_to_mod; } DevAssert(req.numPDUSessionsMod > 0); DevAssert(req.numPDUSessions == 0); @@ -2300,7 +2314,8 @@ static void rrc_CU_process_ue_context_modification_response(MessageDef *msg_p, i } gNB_RRC_UE_t *UE = &ue_context_p->ue_context; - if (resp->drbs_len > 0) { + bool is_inter_cu_ho = UE->ho_context && UE->ho_context->source && !UE->ho_context->target; + if (resp->drbs_len > 0 || is_inter_cu_ho) { store_du_f1u_tunnel(resp->drbs, resp->drbs_len, UE); e1_send_bearer_updates(rrc, UE, resp->drbs_len, resp->drbs); } From 44e4ebc4e29c0388e927805b44ce82f2d96cf153 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Wed, 20 Aug 2025 23:03:32 +0200 Subject: [PATCH 45/52] source-CU-UP: fill PDCP Status in response upon reception of E1 Bearer Context Modification Request * Check presence of PDCP Status Requested in Bearer Context Modification Request * Fetch PDCP Status from PDCP entity and fill in the Bearer Context Modification Response --- openair2/LAYER2/nr_pdcp/cucp_cuup_handler.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/openair2/LAYER2/nr_pdcp/cucp_cuup_handler.c b/openair2/LAYER2/nr_pdcp/cucp_cuup_handler.c index 99c427cc17..ae081e4376 100644 --- a/openair2/LAYER2/nr_pdcp/cucp_cuup_handler.c +++ b/openair2/LAYER2/nr_pdcp/cucp_cuup_handler.c @@ -291,6 +291,18 @@ void e1_bearer_context_modif(const e1ap_bearer_mod_req_t *req) &security_parameters); } + // PDCP Status Information requested + if (to_modif->pdcp_sn_status_requested) { + modified->pdcp_status = malloc_or_fail(sizeof(*modified->pdcp_status)); + nr_pdcp_count_t dl_count = {0}; + nr_pdcp_count_t ul_count = {0}; + nr_pdcp_get_drb_count_values(req->gNB_cu_cp_ue_id, to_modif->id, &ul_count, &dl_count); + modified->pdcp_status->dl_count.hfn = dl_count.hfn; + modified->pdcp_status->dl_count.sn = dl_count.sn; + modified->pdcp_status->ul_count.hfn = ul_count.hfn; + modified->pdcp_status->ul_count.sn = ul_count.sn; + } + if (f1inst < 0) // no F1-U? continue; // nothing to do @@ -300,6 +312,7 @@ void e1_bearer_context_modif(const e1ap_bearer_mod_req_t *req) in_addr_t addr = to_modif->DlUpParamList[k].tl_info.tlAddress; GtpuUpdateTunnelOutgoingAddressAndTeid(f1inst, req->gNB_cu_cp_ue_id, to_modif->id, addr, to_modif->DlUpParamList[k].tl_info.teId); } + } } From badb0c561a6cc1f6eec5331d34d7acb3ecad26b5 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Wed, 20 Aug 2025 23:00:49 +0200 Subject: [PATCH 46/52] source-CU-CP: trigger UL NG RAN Status Transfer upon reception of PDCP Status in E1 Bearer Context Modification Response Once fetched the PDCP Status from the PDCP entity over E1, the CU-CP triggers the NG RAN Status Transfer procedure to transfer the PDCP Status to the target CU. This commit is also introducing the RRC callback. --- openair2/RRC/NR/rrc_gNB.c | 15 +++++++++- openair2/RRC/NR/rrc_gNB_NGAP.c | 46 ++++++++++++++++++++++++++++++ openair2/RRC/NR/rrc_gNB_NGAP.h | 2 ++ openair2/RRC/NR/rrc_gNB_mobility.c | 1 + openair2/RRC/NR/rrc_gNB_mobility.h | 4 +++ 5 files changed, 67 insertions(+), 1 deletion(-) diff --git a/openair2/RRC/NR/rrc_gNB.c b/openair2/RRC/NR/rrc_gNB.c index 4bfe0b7747..7e6cd00097 100644 --- a/openair2/RRC/NR/rrc_gNB.c +++ b/openair2/RRC/NR/rrc_gNB.c @@ -2600,10 +2600,23 @@ void rrc_gNB_process_e1_bearer_context_modif_resp(const e1ap_bearer_modif_resp_t return; } - // there is not really anything to do here as of now + int n_drb_mod = 0; + e1_pdcp_status_info_t pdcp_status[MAX_DRBS_PER_UE] = {0}; for (int i = 0; i < resp->numPDUSessionsMod; ++i) { const pdu_session_modif_t *pdu = &resp->pduSessionMod[i]; LOG_I(RRC, "UE %d: PDU session ID %ld modified %d bearers\n", resp->gNB_cu_cp_ue_id, pdu->id, pdu->numDRBModified); + for (int j = 0; j < pdu->numDRBModified; j++) { + // Trigger UL RAN Status Transfer + if (pdu->DRBnGRanModList[j].pdcp_status) { + pdcp_status[n_drb_mod++] = *pdu->DRBnGRanModList[j].pdcp_status; + } + } + } + if (n_drb_mod) { + LOG_I(NR_RRC, "UE %d: received PDU Status Info - send UL RAN Status Transfer\n", resp->gNB_cu_cp_ue_id); + gNB_RRC_UE_t *ue = &ue_context_p->ue_context; + if (ue->ho_context && ue->ho_context->source) + ue->ho_context->source->ho_status_transfer(rrc, ue, n_drb_mod, pdcp_status); } } diff --git a/openair2/RRC/NR/rrc_gNB_NGAP.c b/openair2/RRC/NR/rrc_gNB_NGAP.c index 961191d171..e845369951 100644 --- a/openair2/RRC/NR/rrc_gNB_NGAP.c +++ b/openair2/RRC/NR/rrc_gNB_NGAP.c @@ -1165,6 +1165,52 @@ void rrc_gNB_free_Handover_Request(ngap_handover_request_t *msg) free(msg->mobility_restriction); } +/** @brief Send NG Uplink RAN Status Transfer message (8.4.6 3GPP TS 38.413) + * Direction: source NG-RAN node -> AMF */ +int rrc_gNB_send_NGAP_ul_ran_status_transfer(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, const int n_to_mod, const e1_pdcp_status_info_t *pdcp_status) +{ + AssertFatal(UE != NULL, "UE context is NULL\n"); + + LOG_I(NR_RRC, + "Sending NGAP Uplink RAN Status Transfer (AMF_UE_NGAP_ID=%" PRIu64 ", GNB_UE_NGAP_ID=%u)\n", + UE->amf_ue_ngap_id, + UE->rrc_ue_id); + + ngap_ran_status_transfer_t msg = { + .amf_ue_ngap_id = UE->amf_ue_ngap_id, + .gnb_ue_ngap_id = UE->rrc_ue_id, + }; + + // Loop through DRBs and extract COUNT values + for (int i = 0; i < n_to_mod; ++i) { + drb_t *drb = &UE->established_drbs[i]; + if (!drb->status) + continue; + + bool sn_length_18 = rrc->pdcp_config.drb.sn_size == 18; + + ngap_drb_status_t *item = &msg.ran_status.drb_status_list[msg.ran_status.nb_drb++]; + item->drb_id = drb->drb_id; + + const e1_pdcp_count_t *ul_pdcp = &pdcp_status[i].ul_count; + const e1_pdcp_count_t *dl_pdcp = &pdcp_status[i].dl_count; + + item->ul_count.pdcp_sn = ul_pdcp->sn; + item->ul_count.hfn = ul_pdcp->hfn; + item->ul_count.sn_len = sn_length_18 ? NGAP_SN_LENGTH_18 : NGAP_SN_LENGTH_12; + + item->dl_count.pdcp_sn = dl_pdcp->sn; + item->dl_count.hfn = dl_pdcp->hfn; + item->dl_count.sn_len = sn_length_18 ? NGAP_SN_LENGTH_18 : NGAP_SN_LENGTH_12; + } + + MessageDef *msg_p = itti_alloc_new_message(TASK_RRC_GNB, 0, NGAP_UL_RAN_STATUS_TRANSFER); + NGAP_UL_RAN_STATUS_TRANSFER(msg_p) = msg; + itti_send_msg_to_task(TASK_NGAP, rrc->module_id, msg_p); + + return 0; +} + /** @brief Process NG Handover Command on Source gNB */ void rrc_gNB_process_HandoverCommand(gNB_RRC_INST *rrc, const ngap_handover_command_t *msg) { diff --git a/openair2/RRC/NR/rrc_gNB_NGAP.h b/openair2/RRC/NR/rrc_gNB_NGAP.h index 4d44b7197c..babd82cb01 100644 --- a/openair2/RRC/NR/rrc_gNB_NGAP.h +++ b/openair2/RRC/NR/rrc_gNB_NGAP.h @@ -106,4 +106,6 @@ void rrc_gNB_free_Handover_Command(ngap_handover_command_t *msg); void rrc_gNB_send_NGAP_HANDOVER_NOTIFY(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE); +int rrc_gNB_send_NGAP_ul_ran_status_transfer(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, const int n_to_mod, const e1_pdcp_status_info_t *pdcp_status); + #endif diff --git a/openair2/RRC/NR/rrc_gNB_mobility.c b/openair2/RRC/NR/rrc_gNB_mobility.c index 42214ecc55..614c27e242 100644 --- a/openair2/RRC/NR/rrc_gNB_mobility.c +++ b/openair2/RRC/NR/rrc_gNB_mobility.c @@ -545,6 +545,7 @@ void nr_rrc_trigger_n2_ho(gNB_RRC_INST *rrc, ue->ho_context = alloc_ho_ctx(HO_CTX_SOURCE); ue->ho_context->source->du = get_du_for_ue(rrc, ue->rrc_ue_id); + ue->ho_context->source->ho_status_transfer = rrc_gNB_send_NGAP_ul_ran_status_transfer; rrc_gNB_send_NGAP_HANDOVER_REQUIRED(rrc, ue, neighbour_config, hoPrepInfo); free_byte_array(hoPrepInfo); diff --git a/openair2/RRC/NR/rrc_gNB_mobility.h b/openair2/RRC/NR/rrc_gNB_mobility.h index db0645c5e3..40072abb2d 100644 --- a/openair2/RRC/NR/rrc_gNB_mobility.h +++ b/openair2/RRC/NR/rrc_gNB_mobility.h @@ -33,6 +33,8 @@ typedef struct nr_rrc_du_container_t nr_rrc_du_container_t; typedef struct NR_CellGroupConfig NR_CellGroupConfig_t; typedef void (*ho_cancel_t)(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue); +typedef int (*ho_status_transfer_t)(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, const int n_to_mod, const e1_pdcp_status_info_t *pdcp_status); + typedef struct nr_ho_source_cu { /// pointer to the (source) DU structure const nr_rrc_du_container_t *du; @@ -48,6 +50,8 @@ typedef struct nr_ho_source_cu { /// function pointer to announce the handover cancellation, e.g., /// reestablishment ho_cancel_t ho_cancel; + /// status transfer + ho_status_transfer_t ho_status_transfer; } nr_ho_source_cu_t; /* acknowledgement of handover request. buf+len is the RRC Reconfiguration */ From a82353f42a63b197104c0d7810aeb4b19254ffaf Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Wed, 20 Aug 2025 23:00:38 +0200 Subject: [PATCH 47/52] target-CU-CP: Add RRC handler NG DL RAN Status Transfer --- openair2/RRC/NR/rrc_gNB.c | 4 +++ openair2/RRC/NR/rrc_gNB_NGAP.c | 48 ++++++++++++++++++++++++++++++++++ openair2/RRC/NR/rrc_gNB_NGAP.h | 2 ++ 3 files changed, 54 insertions(+) diff --git a/openair2/RRC/NR/rrc_gNB.c b/openair2/RRC/NR/rrc_gNB.c index 7e6cd00097..de03f306cb 100644 --- a/openair2/RRC/NR/rrc_gNB.c +++ b/openair2/RRC/NR/rrc_gNB.c @@ -2854,6 +2854,10 @@ void *rrc_gnb_task(void *args_p) { rrc_gNB_process_NGAP_PDUSESSION_RELEASE_COMMAND(msg_p, instance); break; + case NGAP_DL_RAN_STATUS_TRANSFER: + rrc_gNB_process_NGAP_DL_RAN_STATUS_TRANSFER(msg_p, instance); + break; + /* Messages from F1AP task */ case F1AP_SETUP_REQ: AssertFatal(!NODE_IS_DU(RC.nrrrc[instance]->node_type), "should not receive F1AP_SETUP_REQUEST in DU!\n"); diff --git a/openair2/RRC/NR/rrc_gNB_NGAP.c b/openair2/RRC/NR/rrc_gNB_NGAP.c index e845369951..bddd0521d2 100644 --- a/openair2/RRC/NR/rrc_gNB_NGAP.c +++ b/openair2/RRC/NR/rrc_gNB_NGAP.c @@ -1605,3 +1605,51 @@ void rrc_gNB_send_NGAP_HANDOVER_REQUIRED(gNB_RRC_INST *rrc, NGAP_HANDOVER_REQUIRED(msg_p) = msg; itti_send_msg_to_task(TASK_NGAP, rrc->module_id, msg_p); } + +int rrc_gNB_process_NGAP_DL_RAN_STATUS_TRANSFER(MessageDef *msg_p, instance_t instance) +{ + const ngap_ran_status_transfer_t *cmd = &NGAP_DL_RAN_STATUS_TRANSFER(msg_p); + gNB_RRC_INST *rrc = RC.nrrrc[instance]; + rrc_gNB_ue_context_t *ue_context_p = rrc_gNB_get_ue_context(rrc, cmd->gnb_ue_ngap_id); + + if (!ue_context_p) { + LOG_E(NR_RRC, "[gNB %ld] No UE context for gNB_ue_ngap_id %u\n", instance, cmd->gnb_ue_ngap_id); + return -1; + } + + gNB_RRC_UE_t *UE = &ue_context_p->ue_context; + LOG_I(NR_RRC, + "[gNB %ld] DL RAN Status Transfer for gNB_ue_ngap_id %u AMF_UE_NGAP_ID %lu\n", + instance, + cmd->gnb_ue_ngap_id, + cmd->amf_ue_ngap_id); + + for (int i = 0; i < cmd->ran_status.nb_drb; ++i) { + const ngap_drb_status_t *s = &cmd->ran_status.drb_status_list[i]; + LOG_I(NR_RRC, + "DL RAN Status Transfer - DRB ID %d:\n" + " UL COUNT: PDCP SN = %u, HFN = %u (%s)\n" + " DL COUNT: PDCP SN = %u, HFN = %u (%s)\n", + s->drb_id, + s->ul_count.pdcp_sn, + s->ul_count.hfn, + s->ul_count.sn_len == NGAP_SN_LENGTH_18 ? "18-bit" : "12-bit", + s->dl_count.pdcp_sn, + s->dl_count.hfn, + s->dl_count.sn_len == NGAP_SN_LENGTH_18 ? "18-bit" : "12-bit"); + + // Send to PDCP layer + for (int j = 0; j < MAX_DRBS_PER_UE; j++) { + if (UE->established_drbs[j].drb_id == s->drb_id) + nr_pdcp_update_ran_status(UE->rrc_ue_id, + s->drb_id, + s->ul_count.pdcp_sn, + s->ul_count.hfn, + s->dl_count.pdcp_sn, + s->dl_count.hfn, + s->dl_count.sn_len == NGAP_SN_LENGTH_18); + } + } + + return 0; +} diff --git a/openair2/RRC/NR/rrc_gNB_NGAP.h b/openair2/RRC/NR/rrc_gNB_NGAP.h index babd82cb01..d1867173a2 100644 --- a/openair2/RRC/NR/rrc_gNB_NGAP.h +++ b/openair2/RRC/NR/rrc_gNB_NGAP.h @@ -108,4 +108,6 @@ void rrc_gNB_send_NGAP_HANDOVER_NOTIFY(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE); int rrc_gNB_send_NGAP_ul_ran_status_transfer(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, const int n_to_mod, const e1_pdcp_status_info_t *pdcp_status); +int rrc_gNB_process_NGAP_DL_RAN_STATUS_TRANSFER(MessageDef *msg_p, instance_t instance); + #endif From 4facb4d63534a5f88c32eae6851dbcd6caed61c4 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Wed, 20 Aug 2025 23:29:23 +0200 Subject: [PATCH 48/52] target-CU-CP: Transfer PDCP Count to target-CU-UP Upon reception of NG RAN Status at the target CU-CP, send PDCP Status via E1 Bearer Context Modification to the target CU-UP and update PDCP Count upon reception of the message. For the sake of simplicity, re-used and extended legacy e1_send_bearer_updates. Further refactoring is not addressed in this commit. --- openair2/LAYER2/nr_pdcp/cucp_cuup_handler.c | 14 +++++++++ openair2/RRC/NR/nr_rrc_proto.h | 1 + openair2/RRC/NR/rrc_gNB.c | 32 +++++++++++++++++---- openair2/RRC/NR/rrc_gNB_NGAP.c | 11 +------ 4 files changed, 43 insertions(+), 15 deletions(-) diff --git a/openair2/LAYER2/nr_pdcp/cucp_cuup_handler.c b/openair2/LAYER2/nr_pdcp/cucp_cuup_handler.c index ae081e4376..b19ccceb22 100644 --- a/openair2/LAYER2/nr_pdcp/cucp_cuup_handler.c +++ b/openair2/LAYER2/nr_pdcp/cucp_cuup_handler.c @@ -303,6 +303,20 @@ void e1_bearer_context_modif(const e1ap_bearer_mod_req_t *req) modified->pdcp_status->ul_count.sn = ul_count.sn; } + // PDCP Status received (from source CU-UP) + if (to_modif->pdcp_status) { + DevAssert(to_modif->pdcp_config); + DevAssert(to_modif->pdcp_config->pDCP_SN_Size_DL == to_modif->pdcp_config->pDCP_SN_Size_UL); + bool is_sn_len_18 = to_modif->pdcp_config->pDCP_SN_Size_DL == NR_PDCP_Config__drb__pdcp_SN_SizeDL_len18bits; + nr_pdcp_count_t dl_count = { .hfn = to_modif->pdcp_status->dl_count.hfn, .sn = to_modif->pdcp_status->dl_count.sn}; + nr_pdcp_count_t ul_count = { .hfn = to_modif->pdcp_status->ul_count.hfn, .sn = to_modif->pdcp_status->ul_count.sn}; + nr_pdcp_count_update(req->gNB_cu_cp_ue_id, + to_modif->id, + dl_count, + ul_count, + is_sn_len_18 ? LONG_SN_SIZE : SHORT_SN_SIZE); + } + if (f1inst < 0) // no F1-U? continue; // nothing to do diff --git a/openair2/RRC/NR/nr_rrc_proto.h b/openair2/RRC/NR/nr_rrc_proto.h index bbaaaf5774..0c545575ba 100644 --- a/openair2/RRC/NR/nr_rrc_proto.h +++ b/openair2/RRC/NR/nr_rrc_proto.h @@ -128,5 +128,6 @@ void openair_rrc_gNB_configuration(gNB_RRC_INST *rrc, gNB_RrcConfigurationReq *c NR_SRB_ToAddModList_t *createSRBlist(gNB_RRC_UE_t *ue, uint8_t reestablish); NR_DRB_ToAddModList_t *createDRBlist(gNB_RRC_UE_t *ue, bool reestablish); void activate_srb(gNB_RRC_UE_t *UE, int srb_id); +void e1_send_bearer_updates(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, int n, f1ap_drb_setup_t *drbs, const ngap_drb_status_t *drb_status); #endif diff --git a/openair2/RRC/NR/rrc_gNB.c b/openair2/RRC/NR/rrc_gNB.c index de03f306cb..797aa19d88 100644 --- a/openair2/RRC/NR/rrc_gNB.c +++ b/openair2/RRC/NR/rrc_gNB.c @@ -2076,9 +2076,28 @@ static void store_du_f1u_tunnel(const f1ap_drb_setup_t *drbs, int n, gNB_RRC_UE_ } } -/* \brief use list of DRBs and send the corresponding bearer update message via - * E1 to the CU of this UE. Also updates TEID info internally */ -static void e1_send_bearer_updates(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, int n, f1ap_drb_setup_t *drbs) +static void fill_e1_bearer_modif_pdcp_status(gNB_RRC_UE_t *UE, + DRB_nGRAN_to_mod_t *drb_to_mod, + const int drb_id, + const bool um_on_default_drb, + const ngap_drb_status_t *drb_status) +{ + DevAssert(drb_status); + drb_to_mod->id = drb_id; + drb_to_mod->pdcp_sn_status_requested = false; + // PDCP SN Status Information + drb_to_mod->pdcp_config = calloc_or_fail(1, sizeof(*drb_to_mod->pdcp_config)); + set_bearer_context_pdcp_config(drb_to_mod->pdcp_config, get_drb(UE, drb_id), um_on_default_drb); + drb_to_mod->pdcp_status = calloc_or_fail(1, sizeof(*drb_to_mod->pdcp_status)); + drb_to_mod->pdcp_status->dl_count.hfn = drb_status->dl_count.hfn; + drb_to_mod->pdcp_status->dl_count.sn = drb_status->dl_count.pdcp_sn; + drb_to_mod->pdcp_status->ul_count.hfn = drb_status->ul_count.hfn; + drb_to_mod->pdcp_status->ul_count.sn = drb_status->ul_count.pdcp_sn; +} + +/** @brief Fill and send Bearer Context Modification Request with: + * (1) from F1 UE Context DRB to setup list, or (2) with PDCP Status Request */ +void e1_send_bearer_updates(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, int n, f1ap_drb_setup_t *drbs, const ngap_drb_status_t *drb_status) { // Quit bearer updates if no CU-UP is associated if (!is_cuup_associated(rrc)) { @@ -2110,6 +2129,9 @@ static void e1_send_bearer_updates(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, int n, f // PDCP SN Status Request drb_to_mod.pdcp_sn_status_requested = true; LOG_I(NR_RRC, "PDCP Status requested (drb_id=%d)\n", drb_id); + } else if (drb_status) { + /* On-going handover: send PDCP Status */ + fill_e1_bearer_modif_pdcp_status(UE, &drb_to_mod, drb_id, rrc->configuration.um_on_default_drb, drb_status); } rrc_pdu_session_param_t *pdu = find_pduSession_from_drbId(UE, drb_id); if (!pdu) @@ -2183,7 +2205,7 @@ static void rrc_CU_process_ue_context_setup_response(MessageDef *msg_p, instance AssertFatal(UE->Srb[1].Active && UE->Srb[2].Active, "SRBs 1 and 2 must be active during DRB Establishment"); store_du_f1u_tunnel(resp->drbs, resp->drbs_len, UE); if (num_drb == 0) - e1_send_bearer_updates(rrc, UE, resp->drbs_len, resp->drbs); + e1_send_bearer_updates(rrc, UE, resp->drbs_len, resp->drbs, NULL); else cuup_notify_reestablishment(rrc, UE); } @@ -2317,7 +2339,7 @@ static void rrc_CU_process_ue_context_modification_response(MessageDef *msg_p, i bool is_inter_cu_ho = UE->ho_context && UE->ho_context->source && !UE->ho_context->target; if (resp->drbs_len > 0 || is_inter_cu_ho) { store_du_f1u_tunnel(resp->drbs, resp->drbs_len, UE); - e1_send_bearer_updates(rrc, UE, resp->drbs_len, resp->drbs); + e1_send_bearer_updates(rrc, UE, resp->drbs_len, resp->drbs, NULL); } if (resp->du_to_cu_rrc_info) { diff --git a/openair2/RRC/NR/rrc_gNB_NGAP.c b/openair2/RRC/NR/rrc_gNB_NGAP.c index bddd0521d2..d4068b46a5 100644 --- a/openair2/RRC/NR/rrc_gNB_NGAP.c +++ b/openair2/RRC/NR/rrc_gNB_NGAP.c @@ -1639,16 +1639,7 @@ int rrc_gNB_process_NGAP_DL_RAN_STATUS_TRANSFER(MessageDef *msg_p, instance_t in s->dl_count.sn_len == NGAP_SN_LENGTH_18 ? "18-bit" : "12-bit"); // Send to PDCP layer - for (int j = 0; j < MAX_DRBS_PER_UE; j++) { - if (UE->established_drbs[j].drb_id == s->drb_id) - nr_pdcp_update_ran_status(UE->rrc_ue_id, - s->drb_id, - s->ul_count.pdcp_sn, - s->ul_count.hfn, - s->dl_count.pdcp_sn, - s->dl_count.hfn, - s->dl_count.sn_len == NGAP_SN_LENGTH_18); - } + e1_send_bearer_updates(rrc, UE, 0, NULL, s); } return 0; From 85f586c76df8a8750191a86b56e801aee5f83312 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Fri, 12 Sep 2025 17:18:38 +0200 Subject: [PATCH 49/52] Add N2 Handover Cancel (8.4.5 of 3GPP TS 38.413) This commit completes the NGAP Mobility Management procedure for N2 handover. Upon failure on source gNB the Handover Cancel message is sent to the AMF which in return responds with an ack. --- openair2/COMMON/ngap_messages_def.h | 2 + openair2/COMMON/ngap_messages_types.h | 20 +++++++ openair2/RRC/NR/rrc_gNB_NGAP.c | 24 ++++++++ openair2/RRC/NR/rrc_gNB_NGAP.h | 1 + openair2/RRC/NR/rrc_gNB_mobility.c | 11 ++++ openair3/NGAP/ngap_gNB.c | 55 ++++++++++++++++++ openair3/NGAP/ngap_gNB_handlers.c | 42 +++++++++++++- openair3/NGAP/ngap_gNB_mobility_management.c | 59 ++++++++++++++++++++ openair3/NGAP/ngap_gNB_mobility_management.h | 2 + 9 files changed, 215 insertions(+), 1 deletion(-) diff --git a/openair2/COMMON/ngap_messages_def.h b/openair2/COMMON/ngap_messages_def.h index bdeb2455b4..98c1f82260 100644 --- a/openair2/COMMON/ngap_messages_def.h +++ b/openair2/COMMON/ngap_messages_def.h @@ -75,6 +75,7 @@ MESSAGE_DEF(NGAP_HANDOVER_REQUIRED, MESSAGE_PRIORITY_MED, ngap_handover_required MESSAGE_DEF(NGAP_HANDOVER_FAILURE, MESSAGE_PRIORITY_MED, ngap_handover_failure_t, ngap_handover_failure) MESSAGE_DEF(NGAP_HANDOVER_REQUEST_ACKNOWLEDGE, MESSAGE_PRIORITY_MED, ngap_handover_request_ack_t, ngap_handover_request_ack) MESSAGE_DEF(NGAP_HANDOVER_NOTIFY, MESSAGE_PRIORITY_MED, ngap_handover_notify_t, ngap_handover_notify) +MESSAGE_DEF(NGAP_HANDOVER_CANCEL, MESSAGE_PRIORITY_MED, ngap_handover_cancel_t, ngap_handover_cancel) MESSAGE_DEF(NGAP_UL_RAN_STATUS_TRANSFER, MESSAGE_PRIORITY_MED, ngap_ran_status_transfer_t, ngap_ul_ran_status_transfer) MESSAGE_DEF(NGAP_DL_RAN_STATUS_TRANSFER, MESSAGE_PRIORITY_MED, ngap_ran_status_transfer_t, ngap_dl_ran_status_transfer) @@ -89,6 +90,7 @@ MESSAGE_DEF(NGAP_PDUSESSION_RELEASE_COMMAND , MESSAGE_PRIORITY_MED, ngap_pdu MESSAGE_DEF(NGAP_UE_CONTEXT_RELEASE_COMMAND, MESSAGE_PRIORITY_MED, ngap_ue_release_command_t , ngap_ue_release_command) MESSAGE_DEF(NGAP_HANDOVER_REQUEST, MESSAGE_PRIORITY_MED, ngap_handover_request_t, ngap_handover_request) MESSAGE_DEF(NGAP_HANDOVER_COMMAND, MESSAGE_PRIORITY_MED, ngap_handover_command_t, ngap_handover_command) +MESSAGE_DEF(NGAP_HANDOVER_CANCEL_ACK, MESSAGE_PRIORITY_MED, ngap_handover_cancel_ack_t, ngap_handover_cancel_ack) /* NGAP <-> RRC messages (can be initiated either by MME or gNB) */ MESSAGE_DEF(NGAP_UE_CONTEXT_RELEASE_REQ , MESSAGE_PRIORITY_MED, ngap_ue_release_req_t , ngap_ue_release_req) diff --git a/openair2/COMMON/ngap_messages_types.h b/openair2/COMMON/ngap_messages_types.h index a8c2bab58a..ef25f6997d 100644 --- a/openair2/COMMON/ngap_messages_types.h +++ b/openair2/COMMON/ngap_messages_types.h @@ -71,6 +71,8 @@ #define NGAP_HANDOVER_REQUEST_ACKNOWLEDGE(mSGpTR) (mSGpTR)->ittiMsg.ngap_handover_request_ack #define NGAP_HANDOVER_COMMAND(mSGpTR) (mSGpTR)->ittiMsg.ngap_handover_command #define NGAP_HANDOVER_NOTIFY(mSGpTR) (mSGpTR)->ittiMsg.ngap_handover_notify +#define NGAP_HANDOVER_CANCEL(mSGpTR) (mSGpTR)->ittiMsg.ngap_handover_cancel +#define NGAP_HANDOVER_CANCEL_ACK(mSGpTR) (mSGpTR)->ittiMsg.ngap_handover_cancel_ack #define NGAP_UE_CONTEXT_RELEASE_REQ(mSGpTR) (mSGpTR)->ittiMsg.ngap_ue_release_req #define NGAP_PDUSESSION_RELEASE_COMMAND(mSGpTR) (mSGpTR)->ittiMsg.ngap_pdusession_release_command @@ -701,6 +703,24 @@ typedef struct { user_location_information_t user_info; } ngap_handover_notify_t; +/* 9.2.3.11 3GPP TS 38.413 */ +typedef struct { + // RAN UE NGAP ID + uint32_t gNB_ue_ngap_id; + // AMF UE NGAP ID + uint64_t amf_ue_ngap_id; + // Cause + ngap_cause_t cause; +} ngap_handover_cancel_t; + +/* 9.2.3.12 3GPP TS 38.413 */ +typedef struct { + // RAN UE NGAP ID + uint32_t gNB_ue_ngap_id; + // AMF UE NGAP ID + uint64_t amf_ue_ngap_id; +} ngap_handover_cancel_ack_t; + typedef struct ngap_ue_cap_info_ind_s { uint32_t gNB_ue_ngap_id; byte_array_t ue_radio_cap; diff --git a/openair2/RRC/NR/rrc_gNB_NGAP.c b/openair2/RRC/NR/rrc_gNB_NGAP.c index d4068b46a5..be7dce5ca1 100644 --- a/openair2/RRC/NR/rrc_gNB_NGAP.c +++ b/openair2/RRC/NR/rrc_gNB_NGAP.c @@ -1403,6 +1403,30 @@ void rrc_gNB_send_NGAP_HANDOVER_NOTIFY(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE) itti_send_msg_to_task(TASK_NGAP, rrc->module_id, msg_p); } +/** @brief Prepare NG Handover Cancel (source NG-RAN) and inform NGAP (N2-based HO only) + * 3GPP TS 38.413 §8.4.5 (Handover Cancellation) - source NG-RAN -> AMF + * 3GPP TS 38.413 §9.2.3.11 (HANDOVER CANCEL) + * @param rrc CU/RRC instance + * @param UE UE context + * @param cause cancellation cause */ +void rrc_gNB_send_NGAP_HANDOVER_CANCEL(int module_id, gNB_RRC_UE_t *UE, ngap_cause_t cause) +{ + DevAssert(UE != NULL); + + LOG_I(NR_RRC, "Triggering NGAP Handover Cancel (gNB_ue_ngap_id=%d, amf_ue_ngap_id=%ld)\n", UE->rrc_ue_id, UE->amf_ue_ngap_id); + + MessageDef *msg_p = itti_alloc_new_message(TASK_RRC_GNB, 0, NGAP_HANDOVER_CANCEL); + ngap_handover_cancel_t *ho_cancel = &NGAP_HANDOVER_CANCEL(msg_p); + memset(ho_cancel, 0, sizeof(*ho_cancel)); + + /* Mandatory IEs (38.413 §9.2.3.11) */ + ho_cancel->gNB_ue_ngap_id = UE->rrc_ue_id; + ho_cancel->amf_ue_ngap_id = UE->amf_ue_ngap_id; + ho_cancel->cause = cause; + + itti_send_msg_to_task(TASK_NGAP, module_id, msg_p); +} + void rrc_gNB_send_NGAP_PDUSESSION_RELEASE_RESPONSE(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, uint8_t xid) { int pdu_sessions_released = 0; diff --git a/openair2/RRC/NR/rrc_gNB_NGAP.h b/openair2/RRC/NR/rrc_gNB_NGAP.h index d1867173a2..b90d9a1493 100644 --- a/openair2/RRC/NR/rrc_gNB_NGAP.h +++ b/openair2/RRC/NR/rrc_gNB_NGAP.h @@ -105,6 +105,7 @@ void rrc_gNB_process_HandoverCommand(gNB_RRC_INST *rrc, const ngap_handover_comm void rrc_gNB_free_Handover_Command(ngap_handover_command_t *msg); void rrc_gNB_send_NGAP_HANDOVER_NOTIFY(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE); +void rrc_gNB_send_NGAP_HANDOVER_CANCEL(int module_id, gNB_RRC_UE_t *UE, ngap_cause_t cause); int rrc_gNB_send_NGAP_ul_ran_status_transfer(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, const int n_to_mod, const e1_pdcp_status_info_t *pdcp_status); diff --git a/openair2/RRC/NR/rrc_gNB_mobility.c b/openair2/RRC/NR/rrc_gNB_mobility.c index 614c27e242..07b742e16c 100644 --- a/openair2/RRC/NR/rrc_gNB_mobility.c +++ b/openair2/RRC/NR/rrc_gNB_mobility.c @@ -489,6 +489,16 @@ static void nr_rrc_n2_ho_complete(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE) rrc_gNB_send_NGAP_HANDOVER_NOTIFY(rrc, UE); } +/** @brief This callback is used by the source gNB to inform the AMF + * about the cancellation of an ongoing handover */ +static void nr_rrc_n2_ho_cancel(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE) +{ + DevAssert(UE->ho_context); + DevAssert(UE->ho_context->source); + ngap_cause_t cause = {.type = NGAP_CAUSE_RADIO_NETWORK, .value = NGAP_CAUSE_RADIO_NETWORK_HANDOVER_CANCELLED}; + rrc_gNB_send_NGAP_HANDOVER_CANCEL(rrc->module_id, UE, cause); +} + /** @brief Callback function to trigger NG Handover Failure on the target gNB, to inform the AMF * that the preparation of resources has failed (e.g. unsatisfied criteria, gNB is already loaded). * This message represents an Unsuccessful Outcome of the Handover Resource Allocation */ @@ -546,6 +556,7 @@ void nr_rrc_trigger_n2_ho(gNB_RRC_INST *rrc, ue->ho_context = alloc_ho_ctx(HO_CTX_SOURCE); ue->ho_context->source->du = get_du_for_ue(rrc, ue->rrc_ue_id); ue->ho_context->source->ho_status_transfer = rrc_gNB_send_NGAP_ul_ran_status_transfer; + ue->ho_context->source->ho_cancel = nr_rrc_n2_ho_cancel; rrc_gNB_send_NGAP_HANDOVER_REQUIRED(rrc, ue, neighbour_config, hoPrepInfo); free_byte_array(hoPrepInfo); diff --git a/openair3/NGAP/ngap_gNB.c b/openair3/NGAP/ngap_gNB.c index afa69c4c9d..8c9ac6a973 100644 --- a/openair3/NGAP/ngap_gNB.c +++ b/openair3/NGAP/ngap_gNB.c @@ -463,6 +463,57 @@ static int ngap_gNB_handover_notify(instance_t instance, const ngap_handover_not return 0; } +/** @brief NGAP: Handover Cancel callback */ +static int ngap_gNB_handover_cancel(instance_t instance, const ngap_handover_cancel_t *msg) +{ + DevAssert(msg != NULL); + byte_array_t ba = {.buf = NULL, .len = 0}; + + // Retrieve NGAP gNB instance + ngap_gNB_instance_t *ngap_gNB_instance_p = ngap_gNB_get_instance(instance); + DevAssert(ngap_gNB_instance_p != NULL); + + // we must have the UE context by RAN UE NGAP ID + ngap_gNB_ue_context_t *ue_context_p = ngap_get_ue_context(msg->gNB_ue_ngap_id); + if (ue_context_p == NULL) { + NGAP_ERROR("Failed to encode Handover Cancel: no UE context for gNB_ue_ngap_id=%d\n", msg->gNB_ue_ngap_id); + return -1; + } + + if (ue_context_p->gNB_ue_ngap_id != msg->gNB_ue_ngap_id) { + NGAP_ERROR("Failed to encode Handover Cancel: mismatched gNB_ue_ngap_id=%d\n", msg->gNB_ue_ngap_id); + return -1; + } + + if (ue_context_p->amf_ue_ngap_id != msg->amf_ue_ngap_id) { + NGAP_ERROR("Failed to encode Handover Cancel: mismatched amf_ue_ngap_id=%ld\n", msg->amf_ue_ngap_id); + return -1; + } + + /* Encode NGAP PDU */ + NGAP_NGAP_PDU_t *pdu = encode_ng_handover_cancel(msg); + if (pdu == NULL) { + NGAP_ERROR("Failed to encode NG Handover Cancel\n"); + return -1; + } + + if (ngap_gNB_encode_pdu(pdu, &ba.buf, (uint32_t *)&ba.len) < 0) { + NGAP_ERROR("Failed to encode NG Handover Cancel message PDU\n"); + ASN_STRUCT_FREE(asn_DEF_NGAP_NGAP_PDU, pdu); + return -1; + } + ASN_STRUCT_FREE(asn_DEF_NGAP_NGAP_PDU, pdu); + + /* UE-associated signalling -> use the UE's allocated SCTP stream */ + ngap_gNB_itti_send_sctp_data_req(ngap_gNB_instance_p->instance, + ue_context_p->amf_ref->assoc_id, + ba.buf, + ba.len, + ue_context_p->tx_stream); + + return 0; +} + int ngap_gNB_handle_ul_ran_status_transfer(instance_t instance, const ngap_ran_status_transfer_t *msg) { NGAP_DEBUG("Triggered UL RAN Status Transfer\n"); @@ -587,6 +638,10 @@ void *ngap_gNB_process_itti_msg(void *notUsed) { ngap_gNB_handover_notify(instance, &NGAP_HANDOVER_NOTIFY(received_msg)); break; + case NGAP_HANDOVER_CANCEL: + ngap_gNB_handover_cancel(instance, &NGAP_HANDOVER_CANCEL(received_msg)); + break; + case NGAP_UL_RAN_STATUS_TRANSFER: ngap_gNB_handle_ul_ran_status_transfer(instance, &NGAP_UL_RAN_STATUS_TRANSFER(received_msg)); break; diff --git a/openair3/NGAP/ngap_gNB_handlers.c b/openair3/NGAP/ngap_gNB_handlers.c index 0257e49698..95c2dd45a7 100644 --- a/openair3/NGAP/ngap_gNB_handlers.c +++ b/openair3/NGAP/ngap_gNB_handlers.c @@ -1051,6 +1051,46 @@ static int ngap_gNB_handle_handover_command(sctp_assoc_t assoc_id, uint32_t stre return 0; } +/** @brief Handler for NG Handover Cancel Acknowledge */ +static int ngap_gNB_handle_handover_cancel_ack(sctp_assoc_t assoc_id, uint32_t stream, NGAP_NGAP_PDU_t *pdu) +{ + NGAP_INFO("Received NG Handover Cancel Acknowledge\n"); + ngap_gNB_amf_data_t *amf_desc_p = NULL; + DevAssert(pdu != NULL); + + amf_desc_p = ngap_gNB_get_AMF(NULL, assoc_id, 0); + if (amf_desc_p == NULL) { + NGAP_ERROR("[SCTP %u] Received Handover Cancel Ack for non-existing AMF context\n", assoc_id); + return -1; + } + + MessageDef *message_p = itti_alloc_new_message(TASK_NGAP, 0, NGAP_HANDOVER_CANCEL_ACK); + ngap_handover_cancel_ack_t *msg = &NGAP_HANDOVER_CANCEL_ACK(message_p); + + if (decode_ng_handover_cancel_ack(msg, pdu) < 0) { + NGAP_ERROR("Failed to decode NG Handover Cancel Acknowledge\n"); + itti_free(TASK_NGAP, message_p); + return -1; + } + + ngap_gNB_ue_context_t *ue_desc_p = ngap_get_ue_context(msg->gNB_ue_ngap_id); + if (!ue_desc_p) { + NGAP_ERROR("[SCTP %u] Handover Cancel Ack for unknown UE context (gNB_ue_ngap_id %d)\n", assoc_id, msg->gNB_ue_ngap_id); + itti_free(TASK_NGAP, message_p); + return -1; + } + + if (ue_desc_p->amf_ue_ngap_id != msg->amf_ue_ngap_id) { + NGAP_WARN("AMF UE NGAP ID mismatch in HO Cancel Ack (ctx %ld != msg %ld)", ue_desc_p->amf_ue_ngap_id, msg->amf_ue_ngap_id); + } + + ue_desc_p->rx_stream = stream; + + // cancel was already handled at RRC level, do not forward + + return 0; +} + static int ngap_gNB_handle_paging(sctp_assoc_t assoc_id, uint32_t stream, NGAP_NGAP_PDU_t *pdu) { ngap_gNB_amf_data_t *amf_desc_p = NULL; @@ -1484,7 +1524,7 @@ const ngap_message_decoded_callback ngap_messages_callback[][3] = { {ngap_gNB_handle_dl_ran_status_transfer, 0, 0}, /* DownlinkRANStatusTransfer */ {0, 0, 0}, /* DownlinkUEAssociatedNRPPaTransport */ {ngap_gNB_handle_error_indication, 0, 0}, /* ErrorIndication */ - {0, 0, 0}, /* HandoverCancel */ + {0, ngap_gNB_handle_handover_cancel_ack, 0}, /* HandoverCancel */ {0, 0, 0}, /* HandoverNotification */ {0, ngap_gNB_handle_handover_command, 0}, /* HandoverPreparation */ {ngap_gNB_handle_handover_request, 0, 0}, /* HandoverResourceAllocation */ diff --git a/openair3/NGAP/ngap_gNB_mobility_management.c b/openair3/NGAP/ngap_gNB_mobility_management.c index 4f1383e5e0..3d281be7c9 100644 --- a/openair3/NGAP/ngap_gNB_mobility_management.c +++ b/openair3/NGAP/ngap_gNB_mobility_management.c @@ -618,6 +618,65 @@ NGAP_NGAP_PDU_t *encode_ng_handover_notify(const ngap_handover_notify_t *msg) return pdu; } +/** @brief NGAP Handover Cancel encoding (9.2.3.11 3GPP TS 38.413 Handover Cancel) + * 8.4.5 Handover Cancellation (source NG-RAN -> AMF) */ +NGAP_NGAP_PDU_t *encode_ng_handover_cancel(const ngap_handover_cancel_t *msg) +{ + DevAssert(msg != NULL); + + NGAP_NGAP_PDU_t *pdu = calloc_or_fail(1, sizeof(*pdu)); + + pdu->present = NGAP_NGAP_PDU_PR_initiatingMessage; + asn1cCalloc(pdu->choice.initiatingMessage, head); + head->procedureCode = NGAP_ProcedureCode_id_HandoverCancel; + head->criticality = NGAP_Criticality_reject; + head->value.present = NGAP_InitiatingMessage__value_PR_HandoverCancel; + NGAP_HandoverCancel_t *out = &head->value.choice.HandoverCancel; + + // AMF UE NGAP ID (M) + asn1cSequenceAdd(out->protocolIEs.list, NGAP_HandoverCancelIEs_t, ie1); + ie1->id = NGAP_ProtocolIE_ID_id_AMF_UE_NGAP_ID; + ie1->criticality = NGAP_Criticality_reject; + ie1->value.present = NGAP_HandoverCancelIEs__value_PR_AMF_UE_NGAP_ID; + asn_uint642INTEGER(&ie1->value.choice.AMF_UE_NGAP_ID, msg->amf_ue_ngap_id); + + // RAN UE NGAP ID (M) + asn1cSequenceAdd(out->protocolIEs.list, NGAP_HandoverCancelIEs_t, ie2); + ie2->id = NGAP_ProtocolIE_ID_id_RAN_UE_NGAP_ID; + ie2->criticality = NGAP_Criticality_reject; + ie2->value.present = NGAP_HandoverCancelIEs__value_PR_RAN_UE_NGAP_ID; + ie2->value.choice.RAN_UE_NGAP_ID = msg->gNB_ue_ngap_id; + + // Cause (M) + asn1cSequenceAdd(out->protocolIEs.list, NGAP_HandoverCancelIEs_t, ie3); + ie3->id = NGAP_ProtocolIE_ID_id_Cause; + ie3->criticality = NGAP_Criticality_reject; + ie3->value.present = NGAP_HandoverCancelIEs__value_PR_Cause; + encode_ngap_cause(&ie3->value.choice.Cause, &msg->cause); + + return pdu; +} + +/** @brief Decode NGAP Handover Cancel Acknowledge (9.2.3.12 3GPP TS 38.413) */ +int decode_ng_handover_cancel_ack(ngap_handover_cancel_ack_t *out, const NGAP_NGAP_PDU_t *pdu) +{ + DevAssert(out != NULL); + DevAssert(pdu != NULL); + + NGAP_HandoverCancelAcknowledge_t *container = &pdu->choice.successfulOutcome->value.choice.HandoverCancelAcknowledge; + NGAP_HandoverCancelAcknowledgeIEs_t *ie; + + /* AMF UE NGAP ID (M) */ + NGAP_FIND_PROTOCOLIE_BY_ID(NGAP_HandoverCancelAcknowledgeIEs_t, ie, container, NGAP_ProtocolIE_ID_id_AMF_UE_NGAP_ID, true); + asn_INTEGER2ulong(&ie->value.choice.AMF_UE_NGAP_ID, &out->amf_ue_ngap_id); + + /* RAN UE NGAP ID (M) */ + NGAP_FIND_PROTOCOLIE_BY_ID(NGAP_HandoverCancelAcknowledgeIEs_t, ie, container, NGAP_ProtocolIE_ID_id_RAN_UE_NGAP_ID, true); + out->gNB_ue_ngap_id = ie->value.choice.RAN_UE_NGAP_ID; + + return 0; +} + /** @brief Encode NGAP UL RAN Status Transfer (9.2.3.14 of 3GPP TS 38.413) */ NGAP_NGAP_PDU_t *encode_ng_ul_ran_status_transfer(const ngap_ran_status_transfer_t *msg) { diff --git a/openair3/NGAP/ngap_gNB_mobility_management.h b/openair3/NGAP/ngap_gNB_mobility_management.h index b7a6f3547c..18aac7c61d 100644 --- a/openair3/NGAP/ngap_gNB_mobility_management.h +++ b/openair3/NGAP/ngap_gNB_mobility_management.h @@ -35,5 +35,7 @@ int decode_ng_handover_command(ngap_handover_command_t *msg, NGAP_NGAP_PDU_t *pd void free_ng_handover_command(ngap_handover_command_t *msg); NGAP_NGAP_PDU_t *encode_ng_handover_notify(const ngap_handover_notify_t *msg); NGAP_NGAP_PDU_t *encode_ng_ul_ran_status_transfer(const ngap_ran_status_transfer_t *msg); +NGAP_NGAP_PDU_t *encode_ng_handover_cancel(const ngap_handover_cancel_t *msg); +int decode_ng_handover_cancel_ack(ngap_handover_cancel_ack_t *out, const NGAP_NGAP_PDU_t *pdu); #endif /* NGAP_GNB_MOBILITY_MANAGEMENT_H_ */ From f9f6e96d6b0f01480b2cc8a287d3abf345456c57 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Wed, 9 Jul 2025 19:16:05 +0200 Subject: [PATCH 50/52] Add N2 handover documentation --- ci-scripts/conf_files/neighbour-config.conf | 5 + doc/RRC/rrc-dev.md | 93 ++++++- doc/RRC/rrc-usage.md | 22 +- doc/handover-tutorial.md | 146 ++++++++++ .../gnb.sa.band78.fr1.106PRB.pci0.rfsim.conf | 251 ++++++++++++++++++ .../gnb.sa.band78.fr1.106PRB.pci1.rfsim.conf | 251 ++++++++++++++++++ .../CONF/neighbour-config-rfsim.conf | 78 ++++++ 7 files changed, 843 insertions(+), 3 deletions(-) create mode 100644 targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.fr1.106PRB.pci0.rfsim.conf create mode 100644 targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.fr1.106PRB.pci1.rfsim.conf create mode 100644 targets/PROJECTS/GENERIC-NR-5GC/CONF/neighbour-config-rfsim.conf diff --git a/ci-scripts/conf_files/neighbour-config.conf b/ci-scripts/conf_files/neighbour-config.conf index 4d4cd8136c..a498dbf157 100644 --- a/ci-scripts/conf_files/neighbour-config.conf +++ b/ci-scripts/conf_files/neighbour-config.conf @@ -1,3 +1,8 @@ +############################################################ +# gNB-to-gNB neighbour list + measurement configuration # +# for the 2-cell rfsim setup (gNB_ID 0xe00 & 0xb00) # +############################################################ + neighbour_list = ( ########################################################## # Entry USED BY gNB_ID = 0xe00 (nr_cellid = 1) # diff --git a/doc/RRC/rrc-dev.md b/doc/RRC/rrc-dev.md index e6eb3f97e0..43f0c7ed93 100644 --- a/doc/RRC/rrc-dev.md +++ b/doc/RRC/rrc-dev.md @@ -210,7 +210,7 @@ sequenceDiagram end ``` -## Handover +## Inter-DU Handover (F1) The basic handover (HO) structure is as follows. In order to support various handover "message passing implementation" (F1AP, NGAP, XnAP), RRC employs @@ -264,6 +264,94 @@ sequenceDiagram sdu->>cucp: F1AP UE Context Release Complete Note over ue,tdu: UE active on target DU ``` +## Inter-gNB Handover (N2) + +This is an inter-NG-RAN procedure. The N2 handover specification is defined in the following documents: + +* 3GPP TS 23.502, 4.9.1.3 Inter NG-RAN node N2 based handover: + - Outlines detailed handover signaling flows for N2-based handovers. + - Covers both intra-system (between 5G gNBs) and inter-system (between 5G and LTE eNBs) handovers. +* 3GPP TS 38.300, section 9 Mobility and State Transitions: + - describes mobility procedures at the NG-RAN level, depending on the RRC state. +* 3GPP TS 38.413 (NGAP), section 8.4 UE Mobility Management Procedures: + - Specifies the signaling procedures over the N2 interface. + - Includes messages like Handover Request, Handover Command, and Handover Preparation. +* 3GPP TS 38.331 (RRC): details the UE-level RRC procedures involved during handovers + +### End-to-end flow + +```mermaid +sequenceDiagram + participant ue as UE + participant sdu as source DU + participant scucp as source CU-CP + participant scuup as source CU-UP + participant tdu as target DU + participant tcucp as target CU-CP + participant tcuup as target CU-UP + participant amf as AMF + + Note over ue,sdu: UE active on source DU + alt HO triggered through A3 event + ue->>sdu: RRC Measurement Report + sdu->>scucp: F1AP UL RRC Msg Transfer (RRC Measurement Report) + Note over scucp: Handover decision (A3 event trigger) + else Manual Trigger + Note over scucp: Handover decision (e.g., telnet) + end + Note over scucp: nr_rrc_trigger_n2_ho() ("on source CU") + scucp->>amf: HANDOVER REQUIRED + amf->>tcucp: HANDOVER REQUEST + Note over tcucp: rrc_gNB_process_Handover_Request + Note over tcucp: trigger_bearer_setup + tcucp->>tcuup: Bearer Context Setup Request + tcuup->>tcucp: Bearer Context Setup Response + Note over tcucp: rrc_gNB_process_e1_bearer_context_setup_resp + Note over tcucp: nr_rrc_trigger_n2_ho_target() ("on target CU") + Note over tcucp: nr_initiate_handover() + tcucp->>tdu: F1AP UE Context Setup Req + Note over tdu: Create UE context + tdu->>tcucp: F1AP UE Context Setup Resp (incl. CellGroupConfig) + Note over tcucp: rrc_CU_process_ue_context_setup_response() ("on target CU") + Note over tcucp: e1_send_bearer_updates() + tcucp->>tcuup: E1AP Bearer Context Modification Req + tcuup->>tcucp: E1AP Bearer Context Modification Resp + tcucp-->>tcucp: callback: ho_req_ack() + Note over tcucp: nr_rrc_n2_ho_acknowledge() ("on target CU") + tcucp->>amf: HANDOVER REQUEST ACKNOWLEDGE (data forwarding info) + amf->>scucp: HANDOVER COMMAND + scucp->>sdu: F1AP UE Context Modification Req (RRC Reconfiguration) + sdu->>ue: RRC Reconfiguration + sdu->>scucp: F1AP UE Context Modification Resp + Note over sdu: Stop scheduling UE + Note over scucp: rrc_CU_process_ue_context_modification_response() + Note over scucp: e1_send_bearer_updates() + scucp->>scuup: E1 Bearer Context Modification Req (PDCP Status Request) + scuup->>scucp: E1 Bearer Context Modification Resp (PDCP Status Info) + scucp->>amf: NG Uplink RAN Status Transfer + amf->>tcucp: NG Downlink RAN Status Transfer + tcucp->>tcuup: E1 Bearer Context Modification Req (PDCP Status Info) + tcuup->>tcucp: E1 Bearer Context Modification Resp + Note over ue: UE attachment to target DU + Note over ue,tdu: RA (Msg1 + Msg2) + ue->>tdu: RRC Reconfiguration Complete + tdu->>tcucp: F1AP UL RRC Msg Transfer (RRC Reconfiguration Complete) + tcucp-->>tcucp: callback: ho_success() + Note over tcucp: nr_rrc_n2_ho_complete() ("on target CU") + Note over tcucp: handle_rrcReconfigurationComplete() ("on target CU") + tcucp->>amf: HANDOVER NOTIFY + amf->>scucp: UE Context Release Command + note over scucp: ngap_gNB_handle_ue_context_release_command + note over scucp: rrc_gNB_process_NGAP_UE_CONTEXT_RELEASE_COMMAND + scucp->>scuup: E1 Bearer Context Release Command + scuup->>scucp: E1 Bearer Context Release Complete + Note over scucp: rrc_gNB_generate_RRCRelease + scucp->>sdu: F1 UE Context Release Command + sdu->>scucp: F1 UE Context Release Complete + note over scucp: rrc_CU_process_ue_context_release_complete + note over scucp: rrc_remove_ue + Note over ue,tdu: UE active on target DU +``` # Structures @@ -308,7 +396,8 @@ corresponding CU. `nr_ho_source_cu_t` contains notably a function pointer `ho_cancel` for handover cancel. `nr_ho_target_cu_t` contains function pointers `ho_req_ack` -for handover request acknowledge and `ho_success` for handover success. As can +for handover request acknowledge, `ho_success` for handover success, +`ho_failure` for handover failure (N2 only). be seen in the sequence diagram above, either the "target CU" or "source CU" needs to do an operation, and a "switch" from target to source CU is done using these function pointers. For instance, in F1, the handover request acknowledge diff --git a/doc/RRC/rrc-usage.md b/doc/RRC/rrc-usage.md index 09c8952889..0eb7bc7195 100644 --- a/doc/RRC/rrc-usage.md +++ b/doc/RRC/rrc-usage.md @@ -99,6 +99,26 @@ the [MAC configuration](../MAC/mac-usage.md) as well for SIB configuration. ### Neighbor-gNB configuration - TBD +#### What is a gNB neighbor? + +Network continuity is a key aspect of 5G. In the 5G architecture, gNB neighbors play a central role in maintaining service continuity through mechanisms such as handover and load balancing. By definition, a gNB neighbor is another gNB that can be measured and linked by the UE. If the current serving gNB is no longer optimal, the UE may connect to a neighbor gNB. + +To support this behavior, the network configuration specifies additional frequencies and cells that the UE should measure. The UE reports these measurements to the network, which then decides whether or not to initiate a handover. + +Neighbor types include: +- **Intra-gNB neighbors** - cells belonging to the same gNB +- **Inter-gNB neighbors** - cells belonging to different gNBs +- **Inter-RAT neighbors** - cells belonging to another RAT (e.g., LTE) + +#### Required configuration parameters + +To define a neighbor cell in the configuration file, the following parameters are typically needed: +- **gNB ID** - unique identifier of the gNB +- **Cell ID** - identifier of the cell within the gNB +- **Physical Cell ID** - identifier of the cell’s synchronization signal (PCI) +- **Absolute Frequency** - frequency used by the SSB (absoluteFrequencySSB) +- **Subcarrier Spacing** - numerology (e.g., 15 kHz, 30 kHz) +- **PLMN configuration** - MCC, MNC, and MNC length +- **Tracking Area Code (TAC)** - identifier of the tracking area Refer to the [handover tutorial](../handover-tutorial.md) for more information. diff --git a/doc/handover-tutorial.md b/doc/handover-tutorial.md index 87a45ed1e5..67bf99b3c2 100644 --- a/doc/handover-tutorial.md +++ b/doc/handover-tutorial.md @@ -207,6 +207,7 @@ neighbour_list = ( physical_cellId = 1; absoluteFrequencySSB = 643296; subcarrierSpacing = 1; #30 KHz + band = 78; plmn = { mcc = 001; mnc = 01; mnc_length = 2}; tracking_area_code = 1; } @@ -221,6 +222,7 @@ neighbour_list = ( physical_cellId = 0; absoluteFrequencySSB = 643296; subcarrierSpacing = 1; #30 KHz + band = 78; plmn = { mcc = 001; mnc = 01; mnc_length = 2}; tracking_area_code = 1; } @@ -340,3 +342,147 @@ while true; do sleep 15 done ``` + +# N2 Handover + +## Run the setup + +An N2 handover involves the transfer of a UE from one gNB to another via the 5G core network. Unlike F1 handover, where the CU handles the process internally between its DUs, N2 handover requires signaling through the AMF, making it a core-network-based handover. + +We assume: + +* Two independent gNBs connected to the same 5GC via N2 interface. +* A UE initially connected to gNB-PCI0, which will be handed over to gNB-PCI1. +* Handover is triggered by either by decision based measurement event (e.g. A3) or telnet command. + +## Steps to run N2 handover with OAI UE + +1. Similarly to F1 handover, UE does not support any measurement reporting and handover is triggered by +telnet command. Therefore, ensure that both gNBs and UE are built with telnet support: + +```sh +./build_oai --ninja --nrUE --gNB --build-lib telnetsrv +``` + +Run the 5G Core Network if not already running. See [OAI CN5G tutorial](./NR_SA_Tutorial_OAI_CN5G.md). + +2. Start the source gNB (gNB-PCI0) e.g. + +```sh +sudo ./nr-softmodem -O ../../../targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.fr1.106PRB.pci0.rfsim.conf --telnetsrv --telnetsrv.shrmod ci --gNBs.[0].min_rxtxtime 6 --rfsim --rfsimulator.serveraddr 127.0.0.1 +``` + +3. Start the UE e.g. + +```sh +sudo ./nr-uesoftmodem -r 106 --numerology 1 --band 78 -C 3619200000 --rfsim --uicc0.imsi 001010000000001 -O ../../../ci-scripts/conf_files/nrue.uicc.conf --rfsimulator.serveraddr server +``` + +Ensure the UE successfully registers with the network. + +4. Start the target gNB (gNB PCI1) e.g. + +```sh +sudo ./nr-softmodem -O ../../../targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.fr1.106PRB.pci1.rfsim.conf --sa --rfsim --telnetsrv --telnetsrv.shrmod ci --gNBs.[0].min_rxtxtime 6 --rfsimulator.serveraddr 127.0.0.1 +``` + +5. Trigger the N2 handover, e.g. + +From gNB-PCI0, trigger handover on target gNB with PCI 1 for UE ID 1: + +```sh +echo ci trigger_n2_ho 1,1 | nc 127.0.0.1 9090 && echo +``` +where the input parameters correspond to the PCI of the neighbor call and the RRC ID of the UE. + +This will initiate the N2 handover on the source gNB. + +## Neighbour list and measurement configuration + +Make sure the configuration file contains a neighbour list and measurement configuration, e.g. [neighbour-config-rfsim.conf](../../ci-scripts/conf_files/neighbour-config.conf). This configuration can also be present in a different file and included in the gNB configuration file with `@include "neighbour-config-rfsim.conf"`. + +For each gNB there is a `neighbour_cell_configuration` linked to its serving cell ID. + +The measurement configuration is based on A2 and A3 measurement events in 5G NR. These events are used by the UE to report radio conditions to the gNB. The A2 Measurement Event indicates that the serving cell’s signal quality has degraded below a defined threshold and the UE shall initiate measurement of neighboring cells. The A3 Measurement Event indicates that a neighboring cell’s signal quality is better than that of the serving cell by a certain offset and the UE shall trigger handover to a stronger neighboring cell + +This is an example with comments on how to use the configuration file: + +``` +############################################################ +# gNB-to-gNB neighbour list + measurement configuration # +# for the 2-cell rfsim setup (gNB_ID 0xe00 & 0xb00) # +############################################################ + +neighbour_list = ( + ########################################################## + # Entry USED BY gNB_ID = 0xe00 (nr_cellid = 12345678L) # + ########################################################## + { + nr_cellid = 12345678L; # Serving cell of gNB 0xe00 + neighbour_cell_configuration = ( + { + gNB_ID = 0xb00; + nr_cellid = 720898; # Cell served by gNB 0xb00 + physical_cellId = 1; + absoluteFrequencySSB= 621312; + subcarrierSpacing = 1; # 30 kHz + band = 78; + plmn = { mcc = 208; mnc = 99; mnc_length = 2 }; + tracking_area_code = 1; + } + ); + }, + + ########################################################## + # Entry USED BY gNB_ID = 0xb00 (nr_cellid = 720898) # + ########################################################## + { + nr_cellid = 720898; # Serving cell of gNB 0xb00 + neighbour_cell_configuration = ( + { + gNB_ID = 0xe00; + nr_cellid = 12345678L; # Cell served by gNB 0xe00 + physical_cellId = 0; + absoluteFrequencySSB= 641280; + subcarrierSpacing = 1; # 30 kHz + band = 78; + plmn = { mcc = 208; mnc = 99; mnc_length = 2 }; + tracking_area_code = 1; + } + ); + } +); + +############################################################ +# Common NR measurement-event configuration # +############################################################ + +nr_measurement_configuration = { + Periodical = { + enable = 1; + includeBeamMeasurements = 1; + maxNrofRS_IndexesToReport = 4; + }; + + A2 = { + enable = 1; + threshold = 60; + timeToTrigger = 1; + }; + + A3 = ( + { + cell_id = 720898; # neighbour of gNB 0xe00 + offset = 10; + hysteresis = 0; + timeToTrigger = 1; + }, + { + cell_id = 12345678; # neighbour of gNB 0xb00 + offset = 5; + hysteresis = 1; + timeToTrigger = 2; + } + ); +}; +``` diff --git a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.fr1.106PRB.pci0.rfsim.conf b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.fr1.106PRB.pci0.rfsim.conf new file mode 100644 index 0000000000..b486b52dec --- /dev/null +++ b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.fr1.106PRB.pci0.rfsim.conf @@ -0,0 +1,251 @@ +Active_gNBs = ( "gNB-OAI"); +# Asn1_verbosity, choice in: none, info, annoying +Asn1_verbosity = "none"; + +gNBs = +( + { + ////////// Identification parameters: + gNB_ID = 0xe00; + gNB_name = "gNB-OAI"; + + // Tracking area code, 0x0000 and 0xfffe are reserved values + tracking_area_code = 1; + plmn_list = ({ mcc = 001; mnc = 01; mnc_length = 2; snssaiList = ({ sst = 1; }) }); + @include "neighbour-config-rfsim.conf" + + nr_cellid = 12345678L; + + ////////// Physical parameters: + + do_CSIRS = 1; + do_SRS = 1; + + #uess_agg_levels = [0,1,2,2,1] + servingCellConfigCommon = ( + { + #spCellConfigCommon + + physCellId = 0; + +# downlinkConfigCommon + #frequencyInfoDL + # this is 3600 MHz + 43 PRBs@30kHz SCS (same as initial BWP) + absoluteFrequencySSB = 641280; + dl_frequencyBand = 78; + # this is 3600 MHz + dl_absoluteFrequencyPointA = 640008; + #scs-SpecificCarrierList + dl_offstToCarrier = 0; +# subcarrierSpacing +# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120 + dl_subcarrierSpacing = 1; + dl_carrierBandwidth = 106; + #initialDownlinkBWP + #genericParameters + # this is RBstart=27,L=48 (275*(L-1))+RBstart + initialDLBWPlocationAndBandwidth = 28875; # 6366 12925 12956 28875 12952 +# subcarrierSpacing +# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120 + initialDLBWPsubcarrierSpacing = 1; + #pdcch-ConfigCommon + initialDLBWPcontrolResourceSetZero = 12; + initialDLBWPsearchSpaceZero = 0; + + #uplinkConfigCommon + #frequencyInfoUL + ul_frequencyBand = 78; + #scs-SpecificCarrierList + ul_offstToCarrier = 0; +# subcarrierSpacing +# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120 + ul_subcarrierSpacing = 1; + ul_carrierBandwidth = 106; + pMax = 20; + #initialUplinkBWP + #genericParameters + initialULBWPlocationAndBandwidth = 28875; +# subcarrierSpacing +# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120 + initialULBWPsubcarrierSpacing = 1; + #rach-ConfigCommon + #rach-ConfigGeneric + prach_ConfigurationIndex = 98; +#prach_msg1_FDM +#0 = one, 1=two, 2=four, 3=eight + prach_msg1_FDM = 0; + prach_msg1_FrequencyStart = 0; + zeroCorrelationZoneConfig = 13; + preambleReceivedTargetPower = -96; +#preamblTransMax (0...10) = (3,4,5,6,7,8,10,20,50,100,200) + preambleTransMax = 6; +#powerRampingStep +# 0=dB0,1=dB2,2=dB4,3=dB6 + powerRampingStep = 1; +#ssb_perRACH_OccasionAndCB_PreamblesPerSSB_PR +#1=oneeighth,2=onefourth,3=half,4=one,5=two,6=four,7=eight,8=sixteen + ssb_perRACH_OccasionAndCB_PreamblesPerSSB_PR = 4; +#one (0..15) 4,8,12,16,...60,64 + ssb_perRACH_OccasionAndCB_PreamblesPerSSB = 14; +#ra_ContentionResolutionTimer +#(0..7) 8,16,24,32,40,48,56,64 + ra_ContentionResolutionTimer = 7; + rsrp_ThresholdSSB = 19; +#prach-RootSequenceIndex_PR +#1 = 839, 2 = 139 + prach_RootSequenceIndex_PR = 2; + prach_RootSequenceIndex = 1; + # SCS for msg1, can only be 15 for 30 kHz < 6 GHz, takes precendence over the one derived from prach-ConfigIndex + # + msg1_SubcarrierSpacing = 1, +# restrictedSetConfig +# 0=unrestricted, 1=restricted type A, 2=restricted type B + restrictedSetConfig = 0, + + msg3_DeltaPreamble = 1; + p0_NominalWithGrant =-90; + +# pucch-ConfigCommon setup : +# pucchGroupHopping +# 0 = neither, 1= group hopping, 2=sequence hopping + pucchGroupHopping = 0; + hoppingId = 40; + p0_nominal = -90; + + ssb_PositionsInBurst_Bitmap = 1; + +# ssb_periodicityServingCell +# 0 = ms5, 1=ms10, 2=ms20, 3=ms40, 4=ms80, 5=ms160, 6=spare2, 7=spare1 + ssb_periodicityServingCell = 2; + +# dmrs_TypeA_position +# 0 = pos2, 1 = pos3 + dmrs_TypeA_Position = 0; + +# subcarrierSpacing +# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120 + subcarrierSpacing = 1; + + + #tdd-UL-DL-ConfigurationCommon +# subcarrierSpacing +# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120 + referenceSubcarrierSpacing = 1; + # pattern1 + # dl_UL_TransmissionPeriodicity + # 0=ms0p5, 1=ms0p625, 2=ms1, 3=ms1p25, 4=ms2, 5=ms2p5, 6=ms5, 7=ms10 + dl_UL_TransmissionPeriodicity = 6; + nrofDownlinkSlots = 7; + nrofDownlinkSymbols = 6; + nrofUplinkSlots = 2; + nrofUplinkSymbols = 4; + + ssPBCH_BlockPower = -25; + } + + ); + + + # ------- SCTP definitions + SCTP : + { + # Number of streams to use in input/output + SCTP_INSTREAMS = 2; + SCTP_OUTSTREAMS = 2; + }; + + + ////////// AMF parameters: + amf_ip_address = ({ ipv4 = "192.168.70.132"; }); + + + NETWORK_INTERFACES : + { + GNB_IPV4_ADDRESS_FOR_NG_AMF = "192.168.70.129/24"; + GNB_IPV4_ADDRESS_FOR_NGU = "192.168.70.129/24"; + GNB_PORT_FOR_S1U = 2152; # Spec 2152 + }; + + } +); + +MACRLCs = ( +{ + num_cc = 1; + tr_s_preference = "local_L1"; + tr_n_preference = "local_RRC"; + pusch_TargetSNRx10 = 150; + pucch_TargetSNRx10 = 200; +} +); + +L1s = ( +{ + num_cc = 1; + tr_n_preference = "local_mac"; + prach_dtx_threshold = 120; + pucch0_dtx_threshold = 100; + ofdm_offset_divisor = 8; #set this to UINT_MAX for offset 0 +} +); + +RUs = ( +{ + local_rf = "yes" + nb_tx = 1 + nb_rx = 1 + att_tx = 12; + att_rx = 12; + bands = [78]; + max_pdschReferenceSignalPower = -27; + max_rxgain = 114; + eNB_instances = [0]; + clock_src = "internal"; +} +); + + +rfsimulator : +{ + serveraddr = "server"; + serverport = 4043; + options = (); #("saviq"); or/and "chanmod" + modelname = "AWGN"; + IQfile = "/tmp/rfsimulator.iqs"; +}; + +security = { + # preferred ciphering algorithms + # the first one of the list that an UE supports in chosen + # valid values: nea0, nea1, nea2, nea3 + ciphering_algorithms = ( "nea0" ); + + # preferred integrity algorithms + # the first one of the list that an UE supports in chosen + # valid values: nia0, nia1, nia2, nia3 + integrity_algorithms = ( "nia2", "nia0" ); + + # setting 'drb_ciphering' to "no" disables ciphering for DRBs, no matter + # what 'ciphering_algorithms' configures; same thing for 'drb_integrity' + drb_ciphering = "yes"; + drb_integrity = "no"; +}; + +log_config : +{ + global_log_level ="info"; + hw_log_level ="info"; + phy_log_level ="info"; + mac_log_level ="info"; + rlc_log_level ="info"; + pdcp_log_level ="info"; + rrc_log_level ="info"; + ngap_log_level ="debug"; + f1ap_log_level ="debug"; +}; + +e2_agent = { + near_ric_ip_addr = "127.0.0.1"; + #sm_dir = "/path/where/the/SMs/are/located/" + sm_dir = "/usr/local/lib/flexric/" +}; diff --git a/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.fr1.106PRB.pci1.rfsim.conf b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.fr1.106PRB.pci1.rfsim.conf new file mode 100644 index 0000000000..c649c8b0f8 --- /dev/null +++ b/targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.fr1.106PRB.pci1.rfsim.conf @@ -0,0 +1,251 @@ +Active_gNBs = ( "gNB-OAI"); +# Asn1_verbosity, choice in: none, info, annoying +Asn1_verbosity = "none"; + +gNBs = +( + { + ////////// Identification parameters: + gNB_ID = 0xb00; + gNB_name = "gNB-OAI"; + + // Tracking area code, 0x0000 and 0xfffe are reserved values + tracking_area_code = 1; + plmn_list = ({ mcc = 001; mnc = 01; mnc_length = 2; snssaiList = ({ sst = 1; }) }); + @include "neighbour-config-rfsim.conf" + + nr_cellid = 720898; + + ////////// Physical parameters: + + do_CSIRS = 1; + do_SRS = 1; + + #uess_agg_levels = [0,1,2,2,1] + servingCellConfigCommon = ( + { + #spCellConfigCommon + + physCellId = 1; + +# downlinkConfigCommon + #frequencyInfoDL + # this is 3600 MHz + 43 PRBs@30kHz SCS (same as initial BWP) + absoluteFrequencySSB = 621312; + dl_frequencyBand = 78; + # this is 3600 MHz + dl_absoluteFrequencyPointA = 620040; + #scs-SpecificCarrierList + dl_offstToCarrier = 0; +# subcarrierSpacing +# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120 + dl_subcarrierSpacing = 1; + dl_carrierBandwidth = 106; + #initialDownlinkBWP + #genericParameters + # this is RBstart=27,L=48 (275*(L-1))+RBstart + initialDLBWPlocationAndBandwidth = 28875; # 6366 12925 12956 28875 12952 +# subcarrierSpacing +# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120 + initialDLBWPsubcarrierSpacing = 1; + #pdcch-ConfigCommon + initialDLBWPcontrolResourceSetZero = 12; + initialDLBWPsearchSpaceZero = 0; + + #uplinkConfigCommon + #frequencyInfoUL + ul_frequencyBand = 78; + #scs-SpecificCarrierList + ul_offstToCarrier = 0; +# subcarrierSpacing +# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120 + ul_subcarrierSpacing = 1; + ul_carrierBandwidth = 106; + pMax = 20; + #initialUplinkBWP + #genericParameters + initialULBWPlocationAndBandwidth = 28875; +# subcarrierSpacing +# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120 + initialULBWPsubcarrierSpacing = 1; + #rach-ConfigCommon + #rach-ConfigGeneric + prach_ConfigurationIndex = 98; +#prach_msg1_FDM +#0 = one, 1=two, 2=four, 3=eight + prach_msg1_FDM = 0; + prach_msg1_FrequencyStart = 0; + zeroCorrelationZoneConfig = 13; + preambleReceivedTargetPower = -96; +#preamblTransMax (0...10) = (3,4,5,6,7,8,10,20,50,100,200) + preambleTransMax = 6; +#powerRampingStep +# 0=dB0,1=dB2,2=dB4,3=dB6 + powerRampingStep = 1; +#ssb_perRACH_OccasionAndCB_PreamblesPerSSB_PR +#1=oneeighth,2=onefourth,3=half,4=one,5=two,6=four,7=eight,8=sixteen + ssb_perRACH_OccasionAndCB_PreamblesPerSSB_PR = 4; +#one (0..15) 4,8,12,16,...60,64 + ssb_perRACH_OccasionAndCB_PreamblesPerSSB = 14; +#ra_ContentionResolutionTimer +#(0..7) 8,16,24,32,40,48,56,64 + ra_ContentionResolutionTimer = 7; + rsrp_ThresholdSSB = 19; +#prach-RootSequenceIndex_PR +#1 = 839, 2 = 139 + prach_RootSequenceIndex_PR = 2; + prach_RootSequenceIndex = 1; + # SCS for msg1, can only be 15 for 30 kHz < 6 GHz, takes precendence over the one derived from prach-ConfigIndex + # + msg1_SubcarrierSpacing = 1, +# restrictedSetConfig +# 0=unrestricted, 1=restricted type A, 2=restricted type B + restrictedSetConfig = 0, + + msg3_DeltaPreamble = 1; + p0_NominalWithGrant =-90; + +# pucch-ConfigCommon setup : +# pucchGroupHopping +# 0 = neither, 1= group hopping, 2=sequence hopping + pucchGroupHopping = 0; + hoppingId = 40; + p0_nominal = -90; + + ssb_PositionsInBurst_Bitmap = 1; + +# ssb_periodicityServingCell +# 0 = ms5, 1=ms10, 2=ms20, 3=ms40, 4=ms80, 5=ms160, 6=spare2, 7=spare1 + ssb_periodicityServingCell = 2; + +# dmrs_TypeA_position +# 0 = pos2, 1 = pos3 + dmrs_TypeA_Position = 0; + +# subcarrierSpacing +# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120 + subcarrierSpacing = 1; + + + #tdd-UL-DL-ConfigurationCommon +# subcarrierSpacing +# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120 + referenceSubcarrierSpacing = 1; + # pattern1 + # dl_UL_TransmissionPeriodicity + # 0=ms0p5, 1=ms0p625, 2=ms1, 3=ms1p25, 4=ms2, 5=ms2p5, 6=ms5, 7=ms10 + dl_UL_TransmissionPeriodicity = 6; + nrofDownlinkSlots = 7; + nrofDownlinkSymbols = 6; + nrofUplinkSlots = 2; + nrofUplinkSymbols = 4; + + ssPBCH_BlockPower = -25; + } + + ); + + + # ------- SCTP definitions + SCTP : + { + # Number of streams to use in input/output + SCTP_INSTREAMS = 2; + SCTP_OUTSTREAMS = 2; + }; + + + ////////// AMF parameters: + amf_ip_address = ({ ipv4 = "192.168.70.132"; }); + + + NETWORK_INTERFACES : + { + GNB_IPV4_ADDRESS_FOR_NG_AMF = "192.168.70.129/24"; + GNB_IPV4_ADDRESS_FOR_NGU = "192.168.70.129/24"; + GNB_PORT_FOR_S1U = 2152; # Spec 2152 + }; + + } +); + +MACRLCs = ( +{ + num_cc = 1; + tr_s_preference = "local_L1"; + tr_n_preference = "local_RRC"; + pusch_TargetSNRx10 = 150; + pucch_TargetSNRx10 = 200; +} +); + +L1s = ( +{ + num_cc = 1; + tr_n_preference = "local_mac"; + prach_dtx_threshold = 120; + pucch0_dtx_threshold = 100; + ofdm_offset_divisor = 8; #set this to UINT_MAX for offset 0 +} +); + +RUs = ( +{ + local_rf = "yes" + nb_tx = 1 + nb_rx = 1 + att_tx = 12; + att_rx = 12; + bands = [78]; + max_pdschReferenceSignalPower = -27; + max_rxgain = 114; + eNB_instances = [0]; + clock_src = "internal"; +} +); + + +rfsimulator : +{ + serveraddr = "server"; + serverport = 4043; + options = (); #("saviq"); or/and "chanmod" + modelname = "AWGN"; + IQfile = "/tmp/rfsimulator.iqs"; +}; + +security = { + # preferred ciphering algorithms + # the first one of the list that an UE supports in chosen + # valid values: nea0, nea1, nea2, nea3 + ciphering_algorithms = ( "nea0" ); + + # preferred integrity algorithms + # the first one of the list that an UE supports in chosen + # valid values: nia0, nia1, nia2, nia3 + integrity_algorithms = ( "nia2", "nia0" ); + + # setting 'drb_ciphering' to "no" disables ciphering for DRBs, no matter + # what 'ciphering_algorithms' configures; same thing for 'drb_integrity' + drb_ciphering = "yes"; + drb_integrity = "no"; +}; + +log_config : +{ + global_log_level ="info"; + hw_log_level ="info"; + phy_log_level ="info"; + mac_log_level ="info"; + rlc_log_level ="info"; + pdcp_log_level ="info"; + rrc_log_level ="info"; + ngap_log_level ="debug"; + f1ap_log_level ="debug"; +}; + +e2_agent = { + near_ric_ip_addr = "127.0.0.1"; + #sm_dir = "/path/where/the/SMs/are/located/" + sm_dir = "/usr/local/lib/flexric/" +}; diff --git a/targets/PROJECTS/GENERIC-NR-5GC/CONF/neighbour-config-rfsim.conf b/targets/PROJECTS/GENERIC-NR-5GC/CONF/neighbour-config-rfsim.conf new file mode 100644 index 0000000000..cff9d2b293 --- /dev/null +++ b/targets/PROJECTS/GENERIC-NR-5GC/CONF/neighbour-config-rfsim.conf @@ -0,0 +1,78 @@ +############################################################ +# gNB-to-gNB neighbour list + measurement configuration # +# for the 2-cell rfsim setup (gNB_ID 0xe00 & 0xb00) # +############################################################ + +neighbour_list = ( + ########################################################## + # Entry USED BY gNB_ID = 0xe00 (nr_cellid = 12345678L) # + ########################################################## + { + nr_cellid = 12345678L; # Serving cell of gNB 0xe00 + neighbour_cell_configuration = ( + { + gNB_ID = 0xb00; + nr_cellid = 720898; # Cell served by gNB 0xb00 + physical_cellId = 1; + absoluteFrequencySSB= 621312; + subcarrierSpacing = 1; # 30 kHz + band = 78; + plmn = { mcc = 001; mnc = 01; mnc_length = 2 }; + tracking_area_code = 1; + } + ); + }, + + ########################################################## + # Entry USED BY gNB_ID = 0xb00 (nr_cellid = 720898) # + ########################################################## + { + nr_cellid = 720898; # Serving cell of gNB 0xb00 + neighbour_cell_configuration = ( + { + gNB_ID = 0xe00; + nr_cellid = 12345678L; # Cell served by gNB 0xe00 + physical_cellId = 0; + absoluteFrequencySSB= 641280; + subcarrierSpacing = 1; # 30 kHz + band = 78; + plmn = { mcc = 001; mnc = 01; mnc_length = 2 }; + tracking_area_code = 1; + } + ); + } +); + +############################################################ +# Common NR measurement-event configuration # +############################################################ + +nr_measurement_configuration = { + Periodical = { + enable = 1; + includeBeamMeasurements = 1; + maxNrofRS_IndexesToReport = 4; + }; + + A2 = { + enable = 1; + threshold = 60; + timeToTrigger = 1; + }; + + A3 = ( + { + cell_id = 720898; # neighbour of gNB 0xe00 + offset = 10; + hysteresis = 0; + timeToTrigger = 1; + }, + { + cell_id = 12345678; # neighbour of gNB 0xb00 + offset = 5; + hysteresis = 1; + timeToTrigger = 2; + } + ); +}; + From 870b25e95ddf41fed739e3af7db6d37bd8e7a05c Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Thu, 18 Sep 2025 14:54:36 +0200 Subject: [PATCH 51/52] Fix: define_property() needs FULL_DOCS define_property() has optional FULL_DOCS in cmake 3.23+, but before it's required. I did not notice when adding this. --- openair1/SIMULATION/tests/CMakeLists.txt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/openair1/SIMULATION/tests/CMakeLists.txt b/openair1/SIMULATION/tests/CMakeLists.txt index 273142a1d7..9602d6d1f4 100644 --- a/openair1/SIMULATION/tests/CMakeLists.txt +++ b/openair1/SIMULATION/tests/CMakeLists.txt @@ -1,4 +1,6 @@ -define_property(TEST PROPERTY TEST_DESCRIPTION BRIEF_DOCS "A human-readable description of this test") +define_property(TEST PROPERTY TEST_DESCRIPTION + BRIEF_DOCS "A human-readable description of this test" + FULL_DOCS "A human-readable description of this test") # Define a macro to add tests for different physical simulators macro(add_physim_test test_name test_description test_exec) @@ -23,7 +25,9 @@ macro(add_physim_test test_name test_description test_exec) add_dependencies(tests ${test_exec}) endmacro() -define_property(TEST PROPERTY CHECK_COUNT BRIEF_DOCS "helper property to enumerate checks in environment") +define_property(TEST PROPERTY CHECK_COUNT + BRIEF_DOCS "helper property to enumerate checks in environment" + FULL_DOCS "the property counts the number of threshold checks, used to enumerate environment variables given to analyze-timing.sh") function(add_timed_physim_test test_name test_description test_exec) # catch all the arguments past the last expected arqument and store them in the options_list if (NOT TARGET ${test_exec}) From 84c51802daab8fd1f775f430d695520537e3622e Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Thu, 18 Sep 2025 15:27:11 +0200 Subject: [PATCH 52/52] CI RFsim test N2: remove RFsim config in CU-CP --- .../yaml_files/5g_rfsimulator_n2_ho/docker-compose.yaml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/ci-scripts/yaml_files/5g_rfsimulator_n2_ho/docker-compose.yaml b/ci-scripts/yaml_files/5g_rfsimulator_n2_ho/docker-compose.yaml index 1149d4f156..6c03a02443 100644 --- a/ci-scripts/yaml_files/5g_rfsimulator_n2_ho/docker-compose.yaml +++ b/ci-scripts/yaml_files/5g_rfsimulator_n2_ho/docker-compose.yaml @@ -89,8 +89,7 @@ services: cap_drop: - ALL environment: - USE_ADDITIONAL_OPTIONS: --rfsim --rfsimulator.serveraddr 192.168.71.190 - --telnetsrv --telnetsrv.shrmod ci + USE_ADDITIONAL_OPTIONS: --telnetsrv --telnetsrv.shrmod ci --telnetsrv.listenaddr 192.168.71.150 --gNBs.[0].nr_cellid 0 --log_config.global_log_options level,nocolor,time @@ -201,8 +200,7 @@ services: cap_drop: - ALL environment: - USE_ADDITIONAL_OPTIONS: --rfsim --rfsimulator.serveraddr 192.168.71.190 - --telnetsrv --telnetsrv.shrmod ci + USE_ADDITIONAL_OPTIONS: --telnetsrv --telnetsrv.shrmod ci --telnetsrv.listenaddr 192.168.71.180 --gNBs.[0].E1_INTERFACE.[0].ipv4_cucp 192.168.71.180 --gNBs.[0].E1_INTERFACE.[0].ipv4_cuup 192.168.71.181