From a2625163e6b0af38c6f8aab28b0b1012dd03945a Mon Sep 17 00:00:00 2001 From: francescomani Date: Wed, 10 Dec 2025 19:33:01 +0100 Subject: [PATCH 1/6] drop rach_ConfigDedicated once reconfiguration with sync related RA is completed at UE --- openair2/LAYER2/NR_MAC_UE/nr_ra_procedures.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/openair2/LAYER2/NR_MAC_UE/nr_ra_procedures.c b/openair2/LAYER2/NR_MAC_UE/nr_ra_procedures.c index ecf8edb022..aa1073aa98 100644 --- a/openair2/LAYER2/NR_MAC_UE/nr_ra_procedures.c +++ b/openair2/LAYER2/NR_MAC_UE/nr_ra_procedures.c @@ -1114,9 +1114,9 @@ void nr_ra_succeeded(NR_UE_MAC_INST_t *mac, const uint8_t gNB_index, const frame RA_config_t *ra = &mac->ra; if (ra->cfra) { - LOG_I(MAC, "[UE %d][%d.%d][RAPROC] RA procedure succeeded. CFRA: RAR successfully received.\n", mac->ue_id, frame, slot); + LOG_A(NR_MAC, "[UE %d][%d.%d][RAPROC] RA procedure succeeded. CFRA: RAR successfully received.\n", mac->ue_id, frame, slot); } else { - LOG_A(MAC, + LOG_A(NR_MAC, "[UE %d][%d.%d][RAPROC] %d-Step RA procedure succeeded. CBRA: Contention Resolution is successful.\n", mac->ue_id, frame, @@ -1127,6 +1127,10 @@ void nr_ra_succeeded(NR_UE_MAC_INST_t *mac, const uint8_t gNB_index, const frame nr_timer_stop(&ra->contention_resolution_timer); } + // rach-ConfigDedicated is one shot configuration to be used only for a specific instance of reconfiguration with sync + if (ra->rach_ConfigDedicated) + asn1cFreeStruc(asn_DEF_NR_RACH_ConfigDedicated, ra->rach_ConfigDedicated); + ra->RA_active = false; mac->msg3_C_RNTI = false; ra->ra_state = nrRA_SUCCEEDED; From b0d8c1f3b6d5913b23cdb2fc0ad1080c8f35505d Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Fri, 19 Dec 2025 11:26:02 +0100 Subject: [PATCH 2/6] RRC (refactor): extract target context application into dedicated function Refactor handover target context application logic into a reusable function nr_rrc_apply_target_context() that updates both F1AP associations (secondary UE and DU association) and RNTI from the target handover context. And apply to rrc_CU_process_ue_context_modification_response() --- openair2/RRC/NR/rrc_gNB.c | 10 +--------- openair2/RRC/NR/rrc_gNB_mobility.c | 28 ++++++++++++++++++++++++++++ openair2/RRC/NR/rrc_gNB_mobility.h | 2 ++ 3 files changed, 31 insertions(+), 9 deletions(-) diff --git a/openair2/RRC/NR/rrc_gNB.c b/openair2/RRC/NR/rrc_gNB.c index 4d28d66b69..0e07b6a060 100644 --- a/openair2/RRC/NR/rrc_gNB.c +++ b/openair2/RRC/NR/rrc_gNB.c @@ -2579,17 +2579,9 @@ static void rrc_CU_process_ue_context_modification_response(MessageDef *msg_p, i // nothing is to be done, we wait for confirmation to release the UE in the // CU/DU) if (UE->ho_context && UE->ho_context->target && UE->ho_context->source) { - nr_ho_target_cu_t *target_ctx = UE->ho_context->target; - f1_ue_data_t ue_data = cu_get_f1_ue_data(UE->rrc_ue_id); - ue_data.secondary_ue = target_ctx->du_ue_id; - ue_data.du_assoc_id = target_ctx->du->assoc_id; - bool success = cu_update_f1_ue_data(UE->rrc_ue_id, &ue_data); - DevAssert(success); - LOG_I(NR_RRC, "UE %d handover: update RNTI from %04x to %04x\n", UE->rrc_ue_id, UE->rnti, target_ctx->new_rnti); nr_ho_source_cu_t *source_ctx = UE->ho_context->source; DevAssert(source_ctx->old_rnti == UE->rnti); - UE->rnti = target_ctx->new_rnti; - UE->nr_cellid = target_ctx->du->setup_req->cell[0].info.nr_cellid; + nr_rrc_apply_target_context(UE); } } diff --git a/openair2/RRC/NR/rrc_gNB_mobility.c b/openair2/RRC/NR/rrc_gNB_mobility.c index e570682ce8..c1ba11e5ac 100644 --- a/openair2/RRC/NR/rrc_gNB_mobility.c +++ b/openair2/RRC/NR/rrc_gNB_mobility.c @@ -63,6 +63,34 @@ static void free_ho_ctx(nr_handover_context_t *ho_ctx) free(ho_ctx); } +/** @brief Apply target handover context: update F1AP associations and RNTI */ +void nr_rrc_apply_target_context(gNB_RRC_UE_t *UE) +{ + DevAssert(UE->ho_context); + DevAssert(UE->ho_context->target); + nr_ho_target_cu_t *target_ctx = UE->ho_context->target; + f1_ue_data_t ue_data = cu_get_f1_ue_data(UE->rrc_ue_id); + LOG_I(NR_RRC, + "UE %d: update CU F1AP Context DU UE ID %u => %u and RNTI %04x => %04x\n", + UE->rrc_ue_id, + ue_data.secondary_ue, + target_ctx->du_ue_id, + UE->rnti, + target_ctx->new_rnti); + + /* update F1 data: secondary UE association and DU association */ + ue_data.secondary_ue = target_ctx->du_ue_id; + ue_data.du_assoc_id = target_ctx->du->assoc_id; + bool success = cu_update_f1_ue_data(UE->rrc_ue_id, &ue_data); + DevAssert(success); + + /* update UE RNTI */ + UE->rnti = target_ctx->new_rnti; + + /* update UE NR cell ID */ + UE->nr_cellid = target_ctx->du->setup_req->cell[0].info.nr_cellid; +} + /** @brief Fill DRB to Be Setup List in F1 UE Context Setup Request (optional list) * @return 0 if list is empty, list size otherwise */ static int fill_drb_to_be_setup(const gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue, f1ap_drb_to_setup_t drbs[MAX_DRBS_PER_UE]) diff --git a/openair2/RRC/NR/rrc_gNB_mobility.h b/openair2/RRC/NR/rrc_gNB_mobility.h index 88e3548220..54691c2edd 100644 --- a/openair2/RRC/NR/rrc_gNB_mobility.h +++ b/openair2/RRC/NR/rrc_gNB_mobility.h @@ -110,4 +110,6 @@ void nr_rrc_trigger_n2_ho_target(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue); byte_array_t *get_meas_timing_config(const NR_MeasurementTimingConfiguration_t *mtc, const NR_MeasConfig_t *measConfig); +void nr_rrc_apply_target_context(gNB_RRC_UE_t *UE); + #endif /* RRC_GNB_MOBILITY_H_ */ From 4f8feaf513f3a2dd3b09db2456f06d4b24c7791b Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Fri, 19 Dec 2025 11:22:50 +0100 Subject: [PATCH 3/6] RRC: update RNTI after N2 HO acknowledge The RNTI from the target context was not updated after HO acknowledge this led to a failure in the first re-establishment after HO. This commit is introducing the function nr_rrc_apply_target_context in nr_rrc_n2_ho_acknowledge() and also updates the cell ID for consistency. --- openair2/RRC/NR/rrc_gNB_mobility.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/openair2/RRC/NR/rrc_gNB_mobility.c b/openair2/RRC/NR/rrc_gNB_mobility.c index c1ba11e5ac..1fc274db5b 100644 --- a/openair2/RRC/NR/rrc_gNB_mobility.c +++ b/openair2/RRC/NR/rrc_gNB_mobility.c @@ -492,10 +492,7 @@ static void nr_rrc_n2_ho_acknowledge(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE) /* 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); + nr_rrc_apply_target_context(UE); byte_array_t hoCommand = rrc_gNB_encode_HandoverCommand(UE, rrc); if (hoCommand.len < 0) { From eebf79a097b3c26a602a05719daf3d82ac15256f Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Fri, 19 Dec 2025 13:09:55 +0100 Subject: [PATCH 4/6] Add logs to inform about cell updates in MAC-PHY and RRCReestablishment failures These logs are very helpful to identify cell configuration changes in MAC/PHY, for monitoring purposes, and to rapidly signal integrity failures during re-establishment. --- executables/nr-ue.c | 9 +++++++++ openair2/LAYER2/NR_MAC_UE/config_ue.c | 7 +++++++ openair2/RRC/NR_UE/rrc_UE.c | 14 +++++++++++++- 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/executables/nr-ue.c b/executables/nr-ue.c index f57b663f1e..1356e87474 100644 --- a/executables/nr-ue.c +++ b/executables/nr-ue.c @@ -509,6 +509,15 @@ static int handle_sync_req_from_mac(PHY_VARS_NR_UE *UE) uint64_t dl_CarrierFreq = get_carrier_frequency(fp->N_RB_DL, fp->numerology_index, cfg->dl_frequency); uint64_t ul_CarrierFreq = get_carrier_frequency(fp->N_RB_UL, fp->numerology_index, cfg->uplink_frequency); if (dl_CarrierFreq != fp->dl_CarrierFreq || ul_CarrierFreq != fp->ul_CarrierFreq) { + LOG_I(NR_PHY, + "[UE %d] RF frequency change: dl %lu->%lu Hz, ul %lu->%lu Hz (from dl_frequency=%u kHz, target_Nid_cell=%d)\n", + UE->Mod_id, + fp->dl_CarrierFreq, + dl_CarrierFreq, + fp->ul_CarrierFreq, + ul_CarrierFreq, + cfg->dl_frequency, + UE->target_Nid_cell); fp->dl_CarrierFreq = dl_CarrierFreq; fp->ul_CarrierFreq = ul_CarrierFreq; nr_rf_card_config_freq(&UE->openair0_cfg[UE->rf_map.card], ul_CarrierFreq, dl_CarrierFreq, 0); diff --git a/openair2/LAYER2/NR_MAC_UE/config_ue.c b/openair2/LAYER2/NR_MAC_UE/config_ue.c index c92c8225f6..18b331ff46 100644 --- a/openair2/LAYER2/NR_MAC_UE/config_ue.c +++ b/openair2/LAYER2/NR_MAC_UE/config_ue.c @@ -406,6 +406,13 @@ static void config_common_ue(NR_UE_MAC_INST_t *mac, NR_ServingCellConfigCommon_t *scc->ssbSubcarrierSpacing, frequencyInfoDL->absoluteFrequencyPointA) / 1000; // freq in kHz + LOG_I(NR_MAC, + "[UE %d] Set dl_frequency=%u kHz (from absoluteFrequencyPointA=%ld, band=%d, scs=%ld)\n", + mac->ue_id, + cfg->carrier_config.dl_frequency, + frequencyInfoDL->absoluteFrequencyPointA, + mac->nr_band, + *scc->ssbSubcarrierSpacing); for (int i = 0; i < 5; i++) { if (i == frequencyInfoDL->scs_SpecificCarrierList.list.array[0]->subcarrierSpacing) { diff --git a/openair2/RRC/NR_UE/rrc_UE.c b/openair2/RRC/NR_UE/rrc_UE.c index 8487619092..ebf314fc83 100644 --- a/openair2/RRC/NR_UE/rrc_UE.c +++ b/openair2/RRC/NR_UE/rrc_UE.c @@ -833,8 +833,10 @@ static void nr_rrc_process_reconfigurationWithSync(NR_UE_RRC_INST_t *rrc, /* if the frequencyInfoDL is included, consider the target SpCell to be one on the SSB frequency indicated by the frequencyInfoDL */ const NR_DownlinkConfigCommon_t *dcc = reconfigurationWithSync->spCellConfigCommon->downlinkConfigCommon; - if (dcc && dcc->frequencyInfoDL && dcc->frequencyInfoDL->absoluteFrequencySSB) + if (dcc && dcc->frequencyInfoDL && dcc->frequencyInfoDL->absoluteFrequencySSB) { rrc->arfcn_ssb = *dcc->frequencyInfoDL->absoluteFrequencySSB; + LOG_I(NR_RRC, "UE %ld: updated ARFCN_SSB=%ld\n", rrc->ue_id, rrc->arfcn_ssb); + } // consider the target SpCell to be one with a physical cell identity indicated by the physCellId if (!reconfigurationWithSync->spCellConfigCommon->physCellId) @@ -1662,6 +1664,15 @@ static void nr_rrc_ue_decode_NR_BCCH_BCH_Message(NR_UE_RRC_INST_t *rrc, } NR_BCCH_BCH_Message_t *bcch_message = NULL; + if (rrc->phyCellID != phycellid || rrc->arfcn_ssb != ssb_arfcn) { + LOG_I(NR_RRC, + "[UE %ld] BCCH update: phyCellID %d->%u, arfcn_ssb %ld->%ld\n", + rrc->ue_id, + rrc->phyCellID, + phycellid, + rrc->arfcn_ssb, + ssb_arfcn); + } rrc->phyCellID = phycellid; rrc->arfcn_ssb = ssb_arfcn; @@ -2287,6 +2298,7 @@ static void nr_rrc_ue_process_rrcReestablishment(NR_UE_RRC_INST_t *rrc, // perform the actions upon going to RRC_IDLE as specified in 5.3.11 // with release cause 'RRC connection failure', upon which the procedure ends if (!integrity_pass) { + LOG_W(NR_RRC, "Integrity of RRCReestablishment failed, going to IDLE\n"); NR_Release_Cause_t release_cause = RRC_CONNECTION_FAILURE; nr_rrc_going_to_IDLE(rrc, release_cause, NULL); return; From 91a08b1ba735c4b070eb4066c9f046235426ff5a Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Fri, 19 Dec 2025 15:37:36 +0100 Subject: [PATCH 5/6] Fix MAC frequency overwrite during handover causing integrity check failures During N2 handover, the UE receives reconfigurationWithSync which correctly sets the target cell frequency in mac->phy_config.config_req.carrier_config via config_common_ue() call. However, when SIB1 is decoded after reconfiguration, nr_rrc_mac_config_req_sib1() calls config_common_ue_sa() which unconditionally overwrites the frequency with the command-line parameter (downlink_frequency), which corresponds to the source cell frequency. This causes a critical issue during RRC re-establishment: - The UE uses the wrong frequency (source cell) for key derivation (kgnb*) - The gNB uses the correct frequency (target cell) for key derivation - This mismatch results in different kgnb* keys on UE and gNB - Integrity check fails: "Integrity of RRCReestablishment failed, going to IDLE" Root cause: config_common_ue_sa() is used for SIB1 processing (initial cell selection) and uses ServingCellConfigCommonSIB which doesn't contain absoluteFrequencyPointA. It must derive frequency from command-line parameters. However, after handover, the frequency has already been correctly set by config_common_ue() (used for reconfigurationWithSync) which uses ServingCellConfigCommon with absolute frequency information. Solution: Modify config_common_ue_sa() to preserve the existing frequency if it's already initialized (non-zero). Only set frequency from command-line parameters during initial cell selection when dl_frequency is 0. This ensures: - Initial cell selection: frequency derived from command-line parameter, just once - After handover: frequency preserved from reconfigurationWithSync - MAC maintains its own frequency state independently The fix applies to both DL and UL frequencies to maintain consistency. There is probably room for more refactoring to the resync flow, since the functionalities in config_common_ue and config_common_ue_sa are partially overlapping and upon reconfigurationWithSync some other configurations might be applied twice. --- openair2/LAYER2/NR_MAC_UE/config_ue.c | 37 ++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/openair2/LAYER2/NR_MAC_UE/config_ue.c b/openair2/LAYER2/NR_MAC_UE/config_ue.c index 18b331ff46..e97b7fe0c3 100644 --- a/openair2/LAYER2/NR_MAC_UE/config_ue.c +++ b/openair2/LAYER2/NR_MAC_UE/config_ue.c @@ -142,9 +142,21 @@ static void config_common_ue_sa(NR_UE_MAC_INST_t *mac, NR_ServingCellConfigCommo frequencyInfoDL->scs_SpecificCarrierList.list.array[0]->carrierBandwidth); cfg->carrier_config.dl_bandwidth = get_supported_bw_mhz(mac->frequency_range, bw_index); - uint64_t dl_bw_khz = (12 * frequencyInfoDL->scs_SpecificCarrierList.list.array[0]->carrierBandwidth) * - (15 << frequencyInfoDL->scs_SpecificCarrierList.list.array[0]->subcarrierSpacing); - cfg->carrier_config.dl_frequency = (downlink_frequency[cc_idP][0]/1000) - (dl_bw_khz>>1); + /** Only set frequency if not already initialized (e.g., from handover reconfigurationWithSync) + * MAC maintains its own frequency state, don't overwrite it with command-line parameter which + * is related to the initial cell selection. */ + if (cfg->carrier_config.dl_frequency == 0) { + // Initial cell selection: derive from command-line parameter + uint64_t dl_bw_khz = (12 * frequencyInfoDL->scs_SpecificCarrierList.list.array[0]->carrierBandwidth) * + (15 << frequencyInfoDL->scs_SpecificCarrierList.list.array[0]->subcarrierSpacing); + cfg->carrier_config.dl_frequency = (downlink_frequency[cc_idP][0]/1000) - (dl_bw_khz>>1); + LOG_I(NR_MAC, + "[UE %d] Initial cell selection: dl_frequency=%u kHz (from command-line, band=%d, scs=%ld)\n", + mac->ue_id, + cfg->carrier_config.dl_frequency, + mac->nr_band, + frequencyInfoDL->scs_SpecificCarrierList.list.array[0]->subcarrierSpacing); + } for (int i = 0; i < 5; i++) { if (i == frequencyInfoDL->scs_SpecificCarrierList.list.array[0]->subcarrierSpacing) { @@ -165,10 +177,21 @@ static void config_common_ue_sa(NR_UE_MAC_INST_t *mac, NR_ServingCellConfigCommo frequencyInfoUL->scs_SpecificCarrierList.list.array[0]->carrierBandwidth); cfg->carrier_config.uplink_bandwidth = get_supported_bw_mhz(mac->frequency_range, bw_index); - if (frequencyInfoUL->absoluteFrequencyPointA == NULL) - cfg->carrier_config.uplink_frequency = cfg->carrier_config.dl_frequency; - else - cfg->carrier_config.uplink_frequency = cfg->carrier_config.dl_frequency + (uplink_frequency_offset[cc_idP][0] / 1000); + /** Only set UL frequency if not already initialized (e.g., from handover reconfigurationWithSync) + * MAC maintains its own frequency state, don't overwrite it with command-line parameter which + * is related to the initial cell selection. */ + if (cfg->carrier_config.uplink_frequency == 0) { + // Initial cell selection: derive from DL frequency + LOG_I(NR_MAC, + "Initial cell selection: uplink_frequency=%u kHz (from dl_frequency=%u kHz, uplink_frequency_offset=%u kHz)\n", + cfg->carrier_config.uplink_frequency, + cfg->carrier_config.dl_frequency, + uplink_frequency_offset[cc_idP][0]); + if (frequencyInfoUL->absoluteFrequencyPointA == NULL) + cfg->carrier_config.uplink_frequency = cfg->carrier_config.dl_frequency; + else + cfg->carrier_config.uplink_frequency = cfg->carrier_config.dl_frequency + (uplink_frequency_offset[cc_idP][0] / 1000); + } for (int i = 0; i < 5; i++) { if (i == frequencyInfoUL->scs_SpecificCarrierList.list.array[0]->subcarrierSpacing) { From 605b39bd9d65ea1bff77b70038130e21c7f44a3d Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Fri, 19 Dec 2025 16:59:18 +0100 Subject: [PATCH 6/6] Fix RRCReestablishment key derivation to use horizontal derivation Per TS 33.501 6.9.2.3.4 and Fig 6.9.2.1.1-1, when sending RRCReestablishment with nextHopChainingCount, the gNB is staying at the same NCC level (not advancing). Therefore, horizontal derivation from the currently active KgNB must be used, not vertical derivation from NH. Vertical derivation (from NH) is only used when advancing to a new NCC level, which happens during handover or masterKeyUpdate procedures. The previous implementation incorrectly used vertical derivation from NH whenever nh_ncc > 0, which caused key mismatch between gNB and UE during reestablishment after handover, leading to integrity check failures. This fix ensures both gNB and UE use horizontal derivation when nextHopChainingCount matches the current NCC level, aligning with the specification's "virtual NH at NCC=0" model where: - Horizontal derivation = derive KNG-RAN* from currently active KgNB - Vertical derivation = derive KNG-RAN* from NH (only when advancing NCC) --- openair2/RRC/NR/rrc_gNB.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/openair2/RRC/NR/rrc_gNB.c b/openair2/RRC/NR/rrc_gNB.c index 0e07b6a060..285740096a 100644 --- a/openair2/RRC/NR/rrc_gNB.c +++ b/openair2/RRC/NR/rrc_gNB.c @@ -1092,7 +1092,13 @@ static void rrc_gNB_generate_RRCReestablishment(rrc_gNB_ue_context_t *ue_context const f1ap_served_cell_info_t *cell_info = &du->setup_req->cell[0].info; uint32_t ssb_arfcn = get_ssb_arfcn(du); LOG_I(NR_RRC, "Reestablishment update key pci=%d, earfcn_dl=%u\n", cell_info->nr_pci, ssb_arfcn); - nr_derive_key_ng_ran_star(cell_info->nr_pci, ssb_arfcn, ue_p->nh_ncc > 0 ? ue_p->nh : ue_p->kgnb, ue_p->kgnb); + + /* Per TS 33.501 6.9.2.3.4: When sending RRCReestablishment with nextHopChainingCount, + * we're staying at the same NCC level (not advancing), so use horizontal derivation + * from the currently active KgNB. Vertical derivation (from NH) is only used when + * advancing to a new NCC level, which happens during handover or masterKeyUpdate. */ + const uint8_t *base_key = ue_p->kgnb; + nr_derive_key_ng_ran_star(cell_info->nr_pci, ssb_arfcn, base_key, ue_p->kgnb); int size = do_RRCReestablishment(ue_context_pP->ue_context.nh_ncc, buffer, NR_RRC_BUF_SIZE, xid); LOG_A(NR_RRC, "Send RRCReestablishment [%d bytes] to RNTI %04x\n", size, ue_p->rnti);