From b8c44aaf2c12c8ddb05b6f8d1305374a44d9a412 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Wed, 29 Oct 2025 17:45:45 +0100 Subject: [PATCH 01/23] OAI UE bugfix: set is_srb flag to false for DRBs --- openair2/RRC/NR_UE/rrc_UE.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openair2/RRC/NR_UE/rrc_UE.c b/openair2/RRC/NR_UE/rrc_UE.c index 9e77dcfa8f..4860e0b63e 100644 --- a/openair2/RRC/NR_UE/rrc_UE.c +++ b/openair2/RRC/NR_UE/rrc_UE.c @@ -2617,7 +2617,7 @@ static int nr_rrc_ue_decode_dcch(NR_UE_RRC_INST_t *rrc, set_DRB_status(rrc, i, RB_ESTABLISHED); nr_mac_rrc_message_t rrc_msg = {0}; rrc_msg.payload_type = NR_MAC_RRC_RESUME_RB; - rrc_msg.payload.resume_rb.is_srb = true; + rrc_msg.payload.resume_rb.is_srb = false; rrc_msg.payload.resume_rb.rb_id = i; nr_rrc_send_msg_to_mac(rrc, &rrc_msg); } From 1c6acfbc201e4ccb457efb9e7f41708c1b832cee Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Wed, 3 Dec 2025 17:05:05 +0100 Subject: [PATCH 02/23] RRC Bugfix: Propagate DU association ID from PDCP to RRC via ITTI message header When processing UL RRC messages from PDCP, the DU association ID (assoc_id) was not being propagated to RRC, resulting in assoc_id being 0 in rrc_gNB_decode_dcch. Fix by setting originInstance in the ITTI message header to the stored du_assoc_id from ue_data when PDCP creates the F1AP_UL_RRC_MESSAGE. --- openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.c | 1 + 1 file changed, 1 insertion(+) diff --git a/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.c b/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.c index 3fd4c4bdd4..f42323ad8e 100644 --- a/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.c +++ b/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.c @@ -506,6 +506,7 @@ srb_found: * RRC, the RLC-PDCP interface does not transport this information */ f1_ue_data_t ue_data = cu_get_f1_ue_data(ue->ue_id); ul_rrc->gNB_DU_ue_id = ue_data.secondary_ue; + message_p->ittiMsgHeader.originInstance = ue_data.du_assoc_id; ul_rrc->srb_id = srb_id; ul_rrc->rrc_container = malloc(size); AssertFatal(ul_rrc->rrc_container != NULL, "OUT OF MEMORY\n"); From 0c879cdf62e9eb4a29c526cbd41f6aa5aebecf68 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Tue, 13 Jan 2026 19:25:02 +0100 Subject: [PATCH 03/23] MAC: clarify reconfigCellGroup warning for handover scenario Document that reconfigCellGroup remaining present is expected during handover (target DU doesn't send RRC Reconfiguration). --- openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c b/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c index c271824e75..a7213cdfd9 100644 --- a/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c +++ b/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c @@ -771,6 +771,10 @@ void ue_context_modification_request(const f1ap_ue_context_mod_req_t *req) } else if (req->reconfig_compl) { LOG_I(NR_MAC, "DU received confirmation of successful RRC Reconfiguration\n"); if (UE->reconfigCellGroup) { + /** During handover, target DU never sends RRC Reconfiguration (source DU does), + * so ack_reconfig is never called on target DU - this warning is expected. + * If RRC Reconfiguration was sent via PDSCH, ack_reconfig should have been called when UE ACKed it. + * If not, this warning would indicate a bug. */ LOG_W(NR_MAC, "reconfigCellGroup still present, did we miss ACK for RRCReconfiguration?\n"); ASN_STRUCT_FREE(asn_DEF_NR_CellGroupConfig, UE->CellGroup); UE->CellGroup = UE->reconfigCellGroup; From 0ea07517fb2e3927a640de8706f532e75d8fb27a Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Tue, 28 Oct 2025 14:48:42 +0100 Subject: [PATCH 04/23] Refactor (RRC/F1): extract common F1 UE context setup code Improve maintainability by centralizing common F1AP UE context setup logic into reusable helper functions, enabling consistent handling and clear serving/target DU entry points for different scenarios. Code duplication prevention is a beneficial side effect, since at least one helper function is adopted in subsequent commits. Major changes: - Move DRB list filler from external file into rrc_gNB.c and rename fill_drb_to_be_setup to rrc_fill_f1_drb_to_setup for naming consistency - Add static helper rrc_fill_f1_ue_context_setup to populate common fields shared between serving DU and target DU scenarios: - Rename rrc_gNB_generate_UeContextSetupRequest to rrc_f1_ue_context_setup_from_e1_response to clarify it prepares F1AP context setup from E1AP bearer setup response for serving DU - Add rrc_f1_ue_context_setup_for_target_du (for target DU in handover/reestablishment scenarios) - Adopt helper rrc_fill_f1_ue_context_setup in both functions --- openair2/RRC/NR/nr_rrc_proto.h | 11 +- openair2/RRC/NR/rrc_gNB.c | 155 ++++++++++++++++++++++------- openair2/RRC/NR/rrc_gNB_mobility.c | 89 +---------------- 3 files changed, 130 insertions(+), 125 deletions(-) diff --git a/openair2/RRC/NR/nr_rrc_proto.h b/openair2/RRC/NR/nr_rrc_proto.h index a36a0778c6..0f8c262e89 100644 --- a/openair2/RRC/NR/nr_rrc_proto.h +++ b/openair2/RRC/NR/nr_rrc_proto.h @@ -100,9 +100,9 @@ int rrc_gNB_generate_pcch_msg(sctp_assoc_t assoc_id, const NR_SIB1_t *sib, uint3 /* UE Management Procedures */ -void rrc_gNB_generate_UeContextSetupRequest(const gNB_RRC_INST *rrc, - rrc_gNB_ue_context_t *const ue_context_pP, - const e1ap_bearer_setup_resp_t *resp); +void rrc_f1_ue_context_setup_from_e1_response(const gNB_RRC_INST *rrc, + rrc_gNB_ue_context_t *const ue_context_pP, + const e1ap_bearer_setup_resp_t *resp); void rrc_gNB_generate_UeContextModificationRequest(const gNB_RRC_INST *rrc, rrc_gNB_ue_context_t *const ue_context_pP, @@ -124,4 +124,9 @@ void activate_srb(gNB_RRC_UE_t *UE, int srb_id); void e1_notify_pdcp_status(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, const ngap_drb_status_t *drb_status); void init_delayed_action(delayed_action_state_t *delayed_action); +void rrc_f1_ue_context_setup_for_target_du(const gNB_RRC_INST *rrc, + gNB_RRC_UE_t *ue, + const nr_rrc_du_container_t *du, + const byte_array_t *ho_prep_info); + #endif diff --git a/openair2/RRC/NR/rrc_gNB.c b/openair2/RRC/NR/rrc_gNB.c index 7a1c96e33b..6f59a1956b 100644 --- a/openair2/RRC/NR/rrc_gNB.c +++ b/openair2/RRC/NR/rrc_gNB.c @@ -529,6 +529,37 @@ void activate_srb(gNB_RRC_UE_t *UE, int srb_id) freeSRBlist(list); } +static f1ap_ue_context_setup_req_t rrc_fill_f1_ue_context_setup(gNB_RRC_UE_t *ue, + const nr_rrc_du_container_t *du, + const uint32_t *gNB_DU_ue_id) +{ + f1ap_ue_context_setup_req_t req = { + .gNB_CU_ue_id = ue->rrc_ue_id, + .servCellIndex = 0, + }; + + req.gnb_du_ue_agg_mbr_ul = malloc_or_fail(sizeof(*req.gnb_du_ue_agg_mbr_ul)); + *req.gnb_du_ue_agg_mbr_ul = 1000000000; + req.cu_to_du_rrc_info.meas_timing_config = get_meas_timing_config(du->mtc, ue->measConfig); + if (ue->ue_cap_buffer.len > 0) { + req.cu_to_du_rrc_info.ue_cap = malloc_or_fail(sizeof(byte_array_t)); + *req.cu_to_du_rrc_info.ue_cap = copy_byte_array(ue->ue_cap_buffer); + } + if (gNB_DU_ue_id) { + req.gNB_DU_ue_id = malloc_or_fail(sizeof(*req.gNB_DU_ue_id)); + *req.gNB_DU_ue_id = *gNB_DU_ue_id; + } + + /* PLMN and cell ID from DU setup request */ + const f1ap_served_cell_info_t *cell_info = &du->setup_req->cell[0].info; + req.plmn.mcc = cell_info->plmn.mcc; + req.plmn.mnc = cell_info->plmn.mnc; + req.plmn.mnc_digit_length = cell_info->plmn.mnc_digit_length; + req.nr_cellid = cell_info->nr_cellid; + + return req; +} + //----------------------------------------------------------------------------- static void rrc_gNB_generate_RRCSetup(instance_t instance, rnti_t rnti, @@ -2815,7 +2846,7 @@ void rrc_gNB_process_e1_bearer_context_setup_resp(e1ap_bearer_setup_resp_t *resp } if (!UE->f1_ue_context_active) - rrc_gNB_generate_UeContextSetupRequest(rrc, ue_context_p, resp); + rrc_f1_ue_context_setup_from_e1_response(rrc, ue_context_p, resp); else rrc_gNB_generate_UeContextModificationRequest(rrc, ue_context_p, resp, 0, NULL); } @@ -3397,25 +3428,96 @@ int rrc_gNB_generate_pcch_msg(sctp_assoc_t assoc_id, const NR_SIB1_t *sib1, uint /* F1AP UE Context Management Procedures */ -//----------------------------------------------------------------------------- -void rrc_gNB_generate_UeContextSetupRequest(const gNB_RRC_INST *rrc, - rrc_gNB_ue_context_t *const ue_context_pP, - const e1ap_bearer_setup_resp_t *resp) -//----------------------------------------------------------------------------- +/** @brief Fill DRB to Be Setup List for F1 UE Context Setup Request + * Returns number of DRBs filled, 0 if none */ +static int rrc_fill_f1_drb_to_setup(const gNB_RRC_INST *rrc, const gNB_RRC_UE_t *ue, f1ap_drb_to_setup_t drbs[MAX_DRBS_PER_UE]) +{ + int nb_drb = 0; + + FOR_EACH_SEQ_ARR(drb_t *, rrc_drb, &ue->drbs) { + DevAssert(nb_drb < MAX_DRBS_PER_UE); + f1ap_drb_to_setup_t *drb = &drbs[nb_drb]; + nr_pdcp_configuration_t *pdcp = &rrc_drb->pdcp_config; + nb_drb++; + /* fetch an existing PDU session for this DRB */ + rrc_pdu_session_param_t *pdu = find_pduSession_from_drbId((gNB_RRC_UE_t *)ue, rrc_drb->drb_id); + AssertFatal(pdu != NULL, "no PDU session for DRB ID %d\n", rrc_drb->drb_id); + + drb->id = rrc_drb->drb_id; + + drb->qos_choice = F1AP_QOS_CHOICE_NR; + drb->nr.nssai = pdu->param.nssai; + drb->nr.flows_len = 1; + drb->nr.flows = calloc_or_fail(1, sizeof(*drb->nr.flows)); + + /* QoS flow associated with this DRB: use first QoS flow */ + AssertFatal(seq_arr_size(&pdu->param.qos) == 1, "only 1 Qos flow supported\n"); + nr_rrc_qos_t *qos_param = (nr_rrc_qos_t *)seq_arr_at(&pdu->param.qos, 0); + DevAssert(qos_param->qos.qfi > 0); + drb->nr.flows[0].qfi = qos_param->qos.qfi; + drb->nr.flows[0].param = get_qos_char_from_qos_flow_param(&qos_param->qos); + /* the DRB QoS parameters: reuse the ones from the first flow */ + drb->nr.drb_qos = drb->nr.flows[0].param; + + memcpy(&drb->up_ul_tnl[0].tl_address, &rrc_drb->cuup_tunnel_config.addr.buffer, sizeof(uint8_t) * 4); + drb->up_ul_tnl[0].teid = rrc_drb->cuup_tunnel_config.teid; + drb->up_ul_tnl_len = 1; + + drb->rlc_mode = rrc->configuration.um_on_default_drb ? F1AP_RLC_MODE_UM_BIDIR : F1AP_RLC_MODE_AM; + DevAssert(pdcp->drb.sn_size == 18 || pdcp->drb.sn_size == 12); + drb->dl_pdcp_sn_len = malloc_or_fail(sizeof(*drb->dl_pdcp_sn_len)); + *drb->dl_pdcp_sn_len = pdcp->drb.sn_size == 18 ? F1AP_PDCP_SN_18B : F1AP_PDCP_SN_12B; + drb->ul_pdcp_sn_len = malloc_or_fail(sizeof(*drb->ul_pdcp_sn_len)); + *drb->ul_pdcp_sn_len = pdcp->drb.sn_size == 18 ? F1AP_PDCP_SN_18B : F1AP_PDCP_SN_12B; + } + return nb_drb; +} + +void rrc_f1_ue_context_setup_for_target_du(const gNB_RRC_INST *rrc, + gNB_RRC_UE_t *ue, + const nr_rrc_du_container_t *du, + const byte_array_t *ho_prep_info) +{ + DevAssert(du != NULL); + byte_array_t *hpi = NULL; + if (ho_prep_info) { + hpi = malloc_or_fail(sizeof(*hpi)); + *hpi = copy_byte_array(*ho_prep_info); + } + int nb_srb = 2; + f1ap_srb_to_setup_t *srbs = calloc_or_fail(nb_srb, sizeof(*srbs)); + f1ap_drb_to_setup_t *drbs = calloc_or_fail(MAX_DRBS_PER_UE, sizeof(*drbs)); + int nb_drb = rrc_fill_f1_drb_to_setup(rrc, ue, drbs); + srbs[0].id = SRB1; + srbs[1].id = SRB2; + free_MeasConfig(ue->measConfig); + ue->measConfig = nr_rrc_get_measconfig(rrc, du->setup_req->cell[0].info.nr_cellid); + byte_array_t *meas_config = calloc_or_fail(1, sizeof(*meas_config)); + meas_config->buf = calloc_or_fail(1, NR_RRC_BUF_SIZE); + meas_config->len = do_NR_MeasConfig(ue->measConfig, meas_config->buf, NR_RRC_BUF_SIZE); + f1ap_ue_context_setup_req_t req = rrc_fill_f1_ue_context_setup(ue, du, NULL); + req.srbs_len = 2; + req.srbs = srbs; + req.drbs_len = nb_drb; + req.drbs = drbs; + req.cu_to_du_rrc_info.meas_config = meas_config; + req.cu_to_du_rrc_info.ho_prep_info = hpi; + + RETURN_IF_INVALID_ASSOC_ID(du->assoc_id); + rrc->mac_rrc.ue_context_setup_request(du->assoc_id, &req); + free_ue_context_setup_req(&req); +} + +void rrc_f1_ue_context_setup_from_e1_response(const gNB_RRC_INST *rrc, + rrc_gNB_ue_context_t *const ue_context_pP, + const e1ap_bearer_setup_resp_t *resp) { gNB_RRC_UE_t *ue_p = &ue_context_pP->ue_context; AssertFatal(!ue_p->f1_ue_context_active, "logic error: ue context already active\n"); AssertFatal(!NODE_IS_DU(rrc->node_type), "illegal node type DU!\n"); - f1ap_cu_to_du_rrc_info_t cu2du = {0}; - if (ue_p->ue_cap_buffer.len > 0) { - cu2du.ue_cap = malloc_or_fail(sizeof(byte_array_t)); - *cu2du.ue_cap = copy_byte_array(ue_p->ue_cap_buffer); - } - nr_rrc_du_container_t *du = get_du_for_ue((gNB_RRC_INST *)rrc, ue_p->rrc_ue_id); - cu2du.meas_timing_config = get_meas_timing_config(du->mtc, ue_p->measConfig); int nb_srb = 1; f1ap_srb_to_setup_t *srbs = calloc_or_fail(nb_srb, sizeof(*srbs)); @@ -3426,29 +3528,14 @@ void rrc_gNB_generate_UeContextSetupRequest(const gNB_RRC_INST *rrc, f1ap_drb_to_setup_t *drbs = calloc_or_fail(32, sizeof(*drbs)); // maximum DRB can be 32 int n_drbs = fill_drb_to_be_setup_from_e1_resp(rrc, ue_p, resp->pduSession, resp->numPDUSessions, drbs); - /* gNB-DU UE Aggregate Maximum Bit Rate Uplink is C- ifDRBSetup */ - uint64_t *agg_mbr = malloc_or_fail(sizeof(*agg_mbr)); - *agg_mbr = 1000000000 /* bps */; - f1_ue_data_t ue_data = cu_get_f1_ue_data(ue_p->rrc_ue_id); RETURN_IF_INVALID_ASSOC_ID(ue_data.du_assoc_id); - uint32_t *du_ue_id = malloc_or_fail(sizeof(*du_ue_id)); - *du_ue_id = ue_data.secondary_ue; - f1ap_ue_context_setup_req_t ue_context_setup_req = { - .gNB_CU_ue_id = ue_p->rrc_ue_id, - .gNB_DU_ue_id = du_ue_id, - .plmn.mcc = rrc->configuration.plmn[0].mcc, - .plmn.mnc = rrc->configuration.plmn[0].mnc, - .plmn.mnc_digit_length = rrc->configuration.plmn[0].mnc_digit_length, - .nr_cellid = rrc->nr_cellid, - .servCellIndex = 0, /* TODO: correct value? */ - .srbs_len = nb_srb, - .srbs = srbs, - .drbs_len = n_drbs, - .drbs = drbs, - .cu_to_du_rrc_info = cu2du, - .gnb_du_ue_agg_mbr_ul = agg_mbr, - }; + const uint32_t du_ue_id = ue_data.secondary_ue; + f1ap_ue_context_setup_req_t ue_context_setup_req = rrc_fill_f1_ue_context_setup(ue_p, du, &du_ue_id); + ue_context_setup_req.srbs_len = nb_srb; + ue_context_setup_req.srbs = srbs; + ue_context_setup_req.drbs_len = n_drbs; + ue_context_setup_req.drbs = drbs; rrc->mac_rrc.ue_context_setup_request(ue_data.du_assoc_id, &ue_context_setup_req); free_ue_context_setup_req(&ue_context_setup_req); diff --git a/openair2/RRC/NR/rrc_gNB_mobility.c b/openair2/RRC/NR/rrc_gNB_mobility.c index 1fc274db5b..c2495ad879 100644 --- a/openair2/RRC/NR/rrc_gNB_mobility.c +++ b/openair2/RRC/NR/rrc_gNB_mobility.c @@ -91,52 +91,6 @@ void nr_rrc_apply_target_context(gNB_RRC_UE_t *UE) 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]) -{ - int nb_drb = 0; - - FOR_EACH_SEQ_ARR(drb_t *, rrc_drb, &ue->drbs) { - DevAssert(nb_drb < MAX_DRBS_PER_UE); - f1ap_drb_to_setup_t *drb = &drbs[nb_drb]; - nr_pdcp_configuration_t *pdcp = &rrc_drb->pdcp_config; - nb_drb++; - /* fetch an existing PDU session for this DRB */ - rrc_pdu_session_param_t *pdu = find_pduSession_from_drbId(ue, rrc_drb->drb_id); - AssertFatal(pdu != NULL, "no PDU session for DRB ID %d\n", rrc_drb->drb_id); - - drb->id = rrc_drb->drb_id; - - drb->qos_choice = F1AP_QOS_CHOICE_NR; - drb->nr.nssai = pdu->param.nssai; - drb->nr.flows_len = 1; - drb->nr.flows = calloc_or_fail(1, sizeof(*drb->nr.flows)); - - // Find the QoS flow associated with this DRB - // Since we don't have QFI mapping in the new structure, we'll use the first QoS flow - AssertFatal(seq_arr_size(&pdu->param.qos) == 1, "only 1 Qos flow supported\n"); - nr_rrc_qos_t *qos_param = (nr_rrc_qos_t *)seq_arr_at(&pdu->param.qos, 0); - DevAssert(qos_param->qos.qfi > 0); - drb->nr.flows[0].qfi = qos_param->qos.qfi; - drb->nr.flows[0].param = get_qos_char_from_qos_flow_param(&qos_param->qos); - /* the DRB QoS parameters: we just reuse the ones from the first flow */ - drb->nr.drb_qos = drb->nr.flows[0].param; - - memcpy(&drb->up_ul_tnl[0].tl_address, &rrc_drb->cuup_tunnel_config.addr.buffer, sizeof(uint8_t) * 4); - drb->up_ul_tnl[0].teid = rrc_drb->cuup_tunnel_config.teid; - drb->up_ul_tnl_len = 1; - - drb->rlc_mode = rrc->configuration.um_on_default_drb ? F1AP_RLC_MODE_UM_BIDIR : F1AP_RLC_MODE_AM; - DevAssert(pdcp->drb.sn_size == 18 || pdcp->drb.sn_size == 12); - drb->dl_pdcp_sn_len = malloc_or_fail(sizeof(*drb->dl_pdcp_sn_len)); - *drb->dl_pdcp_sn_len = pdcp->drb.sn_size == 18 ? F1AP_PDCP_SN_18B : F1AP_PDCP_SN_12B; - drb->ul_pdcp_sn_len = malloc_or_fail(sizeof(*drb->ul_pdcp_sn_len)); - *drb->ul_pdcp_sn_len = pdcp->drb.sn_size == 18 ? F1AP_PDCP_SN_18B : F1AP_PDCP_SN_12B; - } - return nb_drb; -} - /* \brief Initiate a handover of UE to a specific target cell handled by this * CU. * \param ue a UE context for which the handover should be triggered. The UE @@ -206,48 +160,7 @@ static void nr_initiate_handover(const gNB_RRC_INST *rrc, target_du->assoc_id, target_du->setup_req->cell[0].info.nr_pci); - f1ap_drb_to_setup_t *drbs = calloc_or_fail(MAX_DRBS_PER_UE, sizeof(*drbs)); - int nb_drb = fill_drb_to_be_setup(rrc, ue, drbs); - - int nb_srb = 2; - f1ap_srb_to_setup_t *srbs = calloc_or_fail(nb_srb, sizeof(*srbs)); - srbs[0].id = 1; - srbs[1].id = 2; - - free_MeasConfig(ue->measConfig); - ue->measConfig = nr_rrc_get_measconfig(rrc, target_du->setup_req->cell[0].info.nr_cellid); - byte_array_t *meas_config = calloc_or_fail(1, sizeof(*meas_config)); - meas_config->buf = calloc_or_fail(1, NR_RRC_BUF_SIZE); - meas_config->len = do_NR_MeasConfig(ue->measConfig, meas_config->buf, NR_RRC_BUF_SIZE); - - byte_array_t *meas_timing_config = get_meas_timing_config(target_du->mtc, ue->measConfig); - - byte_array_t *hpi = malloc_or_fail(sizeof(*hpi)); - *hpi = copy_byte_array(*ho_prep_info); - - uint64_t *ue_agg_mbr = malloc_or_fail(sizeof(*ue_agg_mbr)); - *ue_agg_mbr = 1000000000 /*bps*/; - - f1ap_served_cell_info_t *cell_info = &target_du->setup_req->cell[0].info; - RETURN_IF_INVALID_ASSOC_ID(target_du->assoc_id); - f1ap_ue_context_setup_req_t ue_context_setup_req = { - .gNB_CU_ue_id = ue->rrc_ue_id, - .plmn.mcc = cell_info->plmn.mcc, - .plmn.mnc = cell_info->plmn.mnc, - .plmn.mnc_digit_length = cell_info->plmn.mnc_digit_length, - .nr_cellid = cell_info->nr_cellid, - .servCellIndex = 0, // TODO: correct value? - .srbs_len = nb_srb, - .srbs = srbs, - .drbs_len = nb_drb, - .drbs = drbs, - .cu_to_du_rrc_info.ho_prep_info = hpi, - .cu_to_du_rrc_info.meas_config = meas_config, - .cu_to_du_rrc_info.meas_timing_config = meas_timing_config, - .gnb_du_ue_agg_mbr_ul = ue_agg_mbr, - }; - rrc->mac_rrc.ue_context_setup_request(target_du->assoc_id, &ue_context_setup_req); - free_ue_context_setup_req(&ue_context_setup_req); + rrc_f1_ue_context_setup_for_target_du(rrc, ue, target_du, ho_prep_info); } typedef struct deliver_ue_ctxt_modification_data_t { From 94bb169ac531ede08809dafcb5005f5c4511826e Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Tue, 28 Oct 2025 14:48:42 +0100 Subject: [PATCH 05/23] RRC/F1: add documentation/logs for F1 UE context setup functions - Add function documentation blocks for `rrc_fill_f1_ue_context_setup`, `rrc_f1_ue_context_setup_for_target_du`, and `rrc_f1_ue_context_setup_from_e1_response` explaining their purpose, parameters, and usage scenarios - Add inline comments throughout these functions --- openair2/RRC/NR/nr_rrc_proto.h | 5 +++ openair2/RRC/NR/rrc_gNB.c | 63 +++++++++++++++++++++++++++++++++- 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/openair2/RRC/NR/nr_rrc_proto.h b/openair2/RRC/NR/nr_rrc_proto.h index 0f8c262e89..1db6a4d236 100644 --- a/openair2/RRC/NR/nr_rrc_proto.h +++ b/openair2/RRC/NR/nr_rrc_proto.h @@ -124,6 +124,11 @@ void activate_srb(gNB_RRC_UE_t *UE, int srb_id); void e1_notify_pdcp_status(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, const ngap_drb_status_t *drb_status); void init_delayed_action(delayed_action_state_t *delayed_action); +/** @brief Prepare and send F1AP UE Context Setup Request for a target DU + * @param rrc RRC instance + * @param ue UE context + * @param du Target DU container containing cell and setup information + * @param ho_prep_info Optional handover preparation information (NULL if not provided) */ void rrc_f1_ue_context_setup_for_target_du(const gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue, const nr_rrc_du_container_t *du, diff --git a/openair2/RRC/NR/rrc_gNB.c b/openair2/RRC/NR/rrc_gNB.c index 6f59a1956b..78314f8ee1 100644 --- a/openair2/RRC/NR/rrc_gNB.c +++ b/openair2/RRC/NR/rrc_gNB.c @@ -529,6 +529,13 @@ void activate_srb(gNB_RRC_UE_t *UE, int srb_id) freeSRBlist(list); } +/** @brief Fill common fields of F1AP UE Context Setup Request + * This helper function initializes common fields that are shared between + * different UE context setup scenarios (serving DU, target DU). + * @param ue UE context + * @param du DU container for measurement timing configuration + * @param gNB_DU_ue_id Optional gNB-DU UE ID to include in the request (NULL if not provided) + * @return F1AP UE Context Setup Request */ static f1ap_ue_context_setup_req_t rrc_fill_f1_ue_context_setup(gNB_RRC_UE_t *ue, const nr_rrc_du_container_t *du, const uint32_t *gNB_DU_ue_id) @@ -538,13 +545,20 @@ static f1ap_ue_context_setup_req_t rrc_fill_f1_ue_context_setup(gNB_RRC_UE_t *ue .servCellIndex = 0, }; + /* UE Aggregate Maximum Bit Rate Uplink is C-ifDRBSetup: 1 Gbps */ req.gnb_du_ue_agg_mbr_ul = malloc_or_fail(sizeof(*req.gnb_du_ue_agg_mbr_ul)); - *req.gnb_du_ue_agg_mbr_ul = 1000000000; + *req.gnb_du_ue_agg_mbr_ul = 1000000000; /* bps */ + + /* Measurement timing configuration */ req.cu_to_du_rrc_info.meas_timing_config = get_meas_timing_config(du->mtc, ue->measConfig); + + /* UE capabilities */ if (ue->ue_cap_buffer.len > 0) { req.cu_to_du_rrc_info.ue_cap = malloc_or_fail(sizeof(byte_array_t)); *req.cu_to_du_rrc_info.ue_cap = copy_byte_array(ue->ue_cap_buffer); } + + /* gNB-DU UE ID */ if (gNB_DU_ue_id) { req.gNB_DU_ue_id = malloc_or_fail(sizeof(*req.gNB_DU_ue_id)); *req.gNB_DU_ue_id = *gNB_DU_ue_id; @@ -3473,29 +3487,60 @@ static int rrc_fill_f1_drb_to_setup(const gNB_RRC_INST *rrc, const gNB_RRC_UE_t return nb_drb; } +/** @brief Prepare and send F1AP UE Context Setup Request for a target DU + * This function prepares and sends an F1AP UE Context Setup Request for + * a target DU in handover/mobility or reestablishment scenarios. + * It configures: + * - SRB1 and SRB2 for signaling + * - All existing DRBs from the UE context + * - Measurement configuration updated for the target DU's cell + * - PLMN and cell information from the target DU + * - UE aggregate maximum bit rate + * - Handover preparation information (if provided) + * @param rrc RRC instance + * @param ue UE context + * @param du Target DU container containing cell and setup information + * @param ho_prep_info Optional handover preparation information */ void rrc_f1_ue_context_setup_for_target_du(const gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue, const nr_rrc_du_container_t *du, const byte_array_t *ho_prep_info) { DevAssert(du != NULL); + /* Handle two scenarios: + * 1. Handover (ho_prep_info provided): Prepare and copy handover preparation information + * for the target DU. The target DU will assign a new gNB-DU UE ID in the response. + * 2. Reestablishment on different DU (ho_prep_info NULL): The UE is accessing a different + * DU than the original one. Use secondary_ue (the new DU's RNTI) as gNB-DU UE ID because + * the UE context already exists in MAC layer from when RRCReestablishmentRequest was + * received on the new DU. The MAC layer will find the existing UE context using this RNTI. */ byte_array_t *hpi = NULL; if (ho_prep_info) { + /* Handover scenario: copy handover preparation information */ hpi = malloc_or_fail(sizeof(*hpi)); *hpi = copy_byte_array(*ho_prep_info); } + LOG_I(NR_RRC, "Triggering UE Context Setup for UE %d on DU %d\n", ue->rrc_ue_id, du->assoc_id); int nb_srb = 2; f1ap_srb_to_setup_t *srbs = calloc_or_fail(nb_srb, sizeof(*srbs)); f1ap_drb_to_setup_t *drbs = calloc_or_fail(MAX_DRBS_PER_UE, sizeof(*drbs)); + + /* Fill DRBs */ int nb_drb = rrc_fill_f1_drb_to_setup(rrc, ue, drbs); + + /* Prepare SRBs */ srbs[0].id = SRB1; srbs[1].id = SRB2; + + /* Update measurement config for target DU */ free_MeasConfig(ue->measConfig); ue->measConfig = nr_rrc_get_measconfig(rrc, du->setup_req->cell[0].info.nr_cellid); byte_array_t *meas_config = calloc_or_fail(1, sizeof(*meas_config)); meas_config->buf = calloc_or_fail(1, NR_RRC_BUF_SIZE); meas_config->len = do_NR_MeasConfig(ue->measConfig, meas_config->buf, NR_RRC_BUF_SIZE); + /* Fill common fields */ f1ap_ue_context_setup_req_t req = rrc_fill_f1_ue_context_setup(ue, du, NULL); + /* Fill target DU specific fields */ req.srbs_len = 2; req.srbs = srbs; req.drbs_len = nb_drb; @@ -3508,6 +3553,18 @@ void rrc_f1_ue_context_setup_for_target_du(const gNB_RRC_INST *rrc, free_ue_context_setup_req(&req); } +/** @brief Prepare and send F1AP UE Context Setup Request to serving DU after E1AP bearer setup + * This function is called when the CU-CP receives an E1AP Bearer Context Setup Response + * and the UE context is not yet active on the F1 interface. It prepares a complete F1AP + * UE Context Setup Request containing: + * - SRB configuration (SRB2) + * - DRB configuration derived from the E1AP bearer setup response + * - UE capabilities and measurement timing configuration + * - PLMN and cell information from the serving DU + * The request is sent to the serving DU. + * @param rrc RRC instance + * @param ue_context_pP UE context + * @param resp E1AP bearer setup response containing PDU session and DRB information */ void rrc_f1_ue_context_setup_from_e1_response(const gNB_RRC_INST *rrc, rrc_gNB_ue_context_t *const ue_context_pP, const e1ap_bearer_setup_resp_t *resp) @@ -3528,10 +3585,14 @@ void rrc_f1_ue_context_setup_from_e1_response(const gNB_RRC_INST *rrc, f1ap_drb_to_setup_t *drbs = calloc_or_fail(32, sizeof(*drbs)); // maximum DRB can be 32 int n_drbs = fill_drb_to_be_setup_from_e1_resp(rrc, ue_p, resp->pduSession, resp->numPDUSessions, drbs); + /* Get gNB DU UE ID */ f1_ue_data_t ue_data = cu_get_f1_ue_data(ue_p->rrc_ue_id); RETURN_IF_INVALID_ASSOC_ID(ue_data.du_assoc_id); const uint32_t du_ue_id = ue_data.secondary_ue; + + /* Fill common fields */ f1ap_ue_context_setup_req_t ue_context_setup_req = rrc_fill_f1_ue_context_setup(ue_p, du, &du_ue_id); + /* Fill bearers information for serving DU */ ue_context_setup_req.srbs_len = nb_srb; ue_context_setup_req.srbs = srbs; ue_context_setup_req.drbs_len = n_drbs; From 0b8fb49a38015e9d3f7cdd457cac3ab15b72775e Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Fri, 9 Jan 2026 23:39:55 +0100 Subject: [PATCH 06/23] Refactor (MAC): extract CellGroupConfig encoding into shared function Extract common CellGroupConfig encoding logic from ue_context_setup_request() and ue_context_modification_request() into encode_cellgroup_config(). Replace uper_encode_to_buffer() with uper_encode_to_new_buffer() to: - Remove 1024-byte buffer size limit - Automatically allocate correct buffer size - Simplify code (no manual bit-to-byte conversion) - Align with other protocol encoders --- .../LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c b/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c index a7213cdfd9..ee72267213 100644 --- a/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c +++ b/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c @@ -607,6 +607,20 @@ static NR_UE_info_t *create_new_UE(gNB_MAC_INST *mac, uint32_t cu_id, const NR_C return UE; } +/** @brief Encode CellGroupConfig to byte array for transparent forwarding in F1AP messages. + * The encoded bytes are intended for transparent forwarding to the UE without + * decode/re-encode cycles per TS 38.473 transparency requirements. + * @param cellGroup CellGroupConfig to encode + * @return Encoded byte array */ +static byte_array_t encode_cellgroup_config(const NR_CellGroupConfig_t *cellGroup) +{ + byte_array_t cgc = {0}; + ssize_t encoded = uper_encode_to_new_buffer(&asn_DEF_NR_CellGroupConfig, NULL, cellGroup, (void **)&cgc.buf); + AssertFatal(encoded > 0, "Could not encode CellGroup\n"); + cgc.len = encoded; + return cgc; +} + void ue_context_setup_request(const f1ap_ue_context_setup_req_t *req) { const bool is_SA = IS_SA_MODE(get_softmodem_params()); @@ -688,12 +702,7 @@ void ue_context_setup_request(const f1ap_ue_context_setup_req_t *req) } } - byte_array_t cgc = { .buf = calloc_or_fail(1,1024) }; - asn_enc_rval_t enc_rval = - uper_encode_to_buffer(&asn_DEF_NR_CellGroupConfig, NULL, new_CellGroup, cgc.buf, 1024); - AssertFatal(enc_rval.encoded > 0, "Could not encode CellGroup, failed element %s\n", enc_rval.failed_type->name); - cgc.len = (enc_rval.encoded + 7) >> 3; - resp.du_to_cu_rrc_info.cell_group_config = cgc; + resp.du_to_cu_rrc_info.cell_group_config = encode_cellgroup_config(new_CellGroup); ASN_STRUCT_FREE(asn_DEF_NR_CellGroupConfig, UE->reconfigCellGroup); UE->reconfigCellGroup = new_CellGroup; @@ -803,15 +812,7 @@ void ue_context_modification_request(const f1ap_ue_context_mod_req_t *req) if (req->srbs_len > 0 || req->drbs_len > 0 || req->drbs_rel_len > 0 || ue_cap != NULL) { resp.du_to_cu_rrc_info = calloc_or_fail(1, sizeof(du_to_cu_rrc_information_t)); - byte_array_t cgc = { .buf = calloc_or_fail(1, 1024) }; - asn_enc_rval_t enc_rval = uper_encode_to_buffer(&asn_DEF_NR_CellGroupConfig, - NULL, - new_CellGroup, - cgc.buf, - 1024); - AssertFatal(enc_rval.encoded > 0, "Could not encode CellGroup, failed element %s\n", enc_rval.failed_type->name); - cgc.len = (enc_rval.encoded + 7) >> 3; - resp.du_to_cu_rrc_info->cell_group_config = cgc; + resp.du_to_cu_rrc_info->cell_group_config = encode_cellgroup_config(new_CellGroup); ASN_STRUCT_FREE(asn_DEF_NR_CellGroupConfig, UE->reconfigCellGroup); UE->reconfigCellGroup = new_CellGroup; From b621929f9b5049bf4dd553006b177936c64612a8 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Fri, 9 Jan 2026 23:56:21 +0100 Subject: [PATCH 07/23] Refactor (MAC): extract RRC container handling into shared function Extract the common RRC container handling logic from ue_context_setup_request() and ue_context_modification_request() into a new static function handle_ue_context_rrc_container(). This refactoring: - Eliminates code duplication between the two functions - Centralizes RRC container forwarding logic - Improves code maintainability and consistency --- .../LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c b/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c index ee72267213..3bc5d7a7af 100644 --- a/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c +++ b/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c @@ -621,6 +621,19 @@ static byte_array_t encode_cellgroup_config(const NR_CellGroupConfig_t *cellGrou return cgc; } +/** @brief Handle RRC container from UE Context Setup/Modification request. + * Per 3GPP TS 38.473, the RRCContainer IE contains an RRC message (e.g., DL-DCCH-Message + * as defined in TS 38.331) that is encapsulated in a PDCP PDU. The DU forwards this + * container transparently to the UE via SRB1 without decode/re-encode cycles. + * @param rrc_container RRC container + * @param rnti RNTI of the UE */ +static void handle_ue_context_rrc_container(const byte_array_t *rrc_container, const rnti_t rnti) +{ + DevAssert(rrc_container); + logical_chan_id_t id = 1; + nr_rlc_srb_recv_sdu(rnti, id, rrc_container->buf, rrc_container->len); +} + void ue_context_setup_request(const f1ap_ue_context_setup_req_t *req) { const bool is_SA = IS_SA_MODE(get_softmodem_params()); @@ -681,8 +694,7 @@ void ue_context_setup_request(const f1ap_ue_context_setup_req_t *req) } if (req->rrc_container != NULL) { - logical_chan_id_t id = 1; - nr_rlc_srb_recv_sdu(UE->rnti, id, req->rrc_container->buf, req->rrc_container->len); + handle_ue_context_rrc_container(req->rrc_container, UE->rnti); } NR_ServingCellConfigCommon_t *scc = mac->common_channels[0].ServingCellConfigCommon; @@ -769,8 +781,7 @@ void ue_context_modification_request(const f1ap_ue_context_mod_req_t *req) } if (req->rrc_container != NULL) { - logical_chan_id_t id = 1; - nr_rlc_srb_recv_sdu(req->gNB_DU_ue_id, id, req->rrc_container->buf, req->rrc_container->len); + handle_ue_context_rrc_container(req->rrc_container, req->gNB_DU_ue_id); } NR_ServingCellConfigCommon_t *scc = mac->common_channels[0].ServingCellConfigCommon; From ebc9158ca47d13b9c331a5640029ef57bf24e571 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Wed, 21 Jan 2026 16:53:23 +0100 Subject: [PATCH 08/23] RRC/F1: provide DU UE ID for re-establishment scenario Handle target DU UE context setup when handover preparation info is unavailable (i.e. re-establishment scenario) by retrieving secondary UE ID from F1 UE data. rrc_f1_ue_context_setup_for_target_du is either applying to handover or re-establishment. In the latter scenario, populate DU UE ID, which should be known. --- openair2/RRC/NR/rrc_gNB.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/openair2/RRC/NR/rrc_gNB.c b/openair2/RRC/NR/rrc_gNB.c index 78314f8ee1..33708d575c 100644 --- a/openair2/RRC/NR/rrc_gNB.c +++ b/openair2/RRC/NR/rrc_gNB.c @@ -3515,10 +3515,18 @@ void rrc_f1_ue_context_setup_for_target_du(const gNB_RRC_INST *rrc, * the UE context already exists in MAC layer from when RRCReestablishmentRequest was * received on the new DU. The MAC layer will find the existing UE context using this RNTI. */ byte_array_t *hpi = NULL; + uint32_t du_ue_id = 0; + uint32_t *opt_du_ue_id = NULL; if (ho_prep_info) { /* Handover scenario: copy handover preparation information */ hpi = malloc_or_fail(sizeof(*hpi)); *hpi = copy_byte_array(*ho_prep_info); + } else { + /* Reestablishment scenario: use secondary_ue from existing UE context */ + f1_ue_data_t ue_data = cu_get_f1_ue_data(ue->rrc_ue_id); + RETURN_IF_INVALID_ASSOC_ID(ue_data.du_assoc_id); + du_ue_id = ue_data.secondary_ue; + opt_du_ue_id = &du_ue_id; } LOG_I(NR_RRC, "Triggering UE Context Setup for UE %d on DU %d\n", ue->rrc_ue_id, du->assoc_id); int nb_srb = 2; @@ -3539,7 +3547,7 @@ void rrc_f1_ue_context_setup_for_target_du(const gNB_RRC_INST *rrc, meas_config->buf = calloc_or_fail(1, NR_RRC_BUF_SIZE); meas_config->len = do_NR_MeasConfig(ue->measConfig, meas_config->buf, NR_RRC_BUF_SIZE); /* Fill common fields */ - f1ap_ue_context_setup_req_t req = rrc_fill_f1_ue_context_setup(ue, du, NULL); + f1ap_ue_context_setup_req_t req = rrc_fill_f1_ue_context_setup(ue, du, opt_du_ue_id); /* Fill target DU specific fields */ req.srbs_len = 2; req.srbs = srbs; From 290306d1f862a22de8dfac31b832988294e6b0aa Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Wed, 7 Jan 2026 19:45:46 +0100 Subject: [PATCH 09/23] RRC: Refactor re-establishment request handling - Extract old_rnti as const variable and reuse consistently - Move as_security_active check earlier for fail-fast validation - Move ue_data declaration earlier to avoid duplicates - Improve code organization and readability No functional changes. --- openair2/RRC/NR/rrc_gNB.c | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/openair2/RRC/NR/rrc_gNB.c b/openair2/RRC/NR/rrc_gNB.c index 33708d575c..b0b9a64e7d 100644 --- a/openair2/RRC/NR/rrc_gNB.c +++ b/openair2/RRC/NR/rrc_gNB.c @@ -1469,11 +1469,12 @@ static void rrc_handle_RRCReestablishmentRequest(gNB_RRC_INST *rrc, const char *scause = get_reestab_cause(req->reestablishmentCause); const long physCellId = req->ue_Identity.physCellId; long ngap_cause = NGAP_CAUSE_RADIO_NETWORK_UNSPECIFIED; /* cause in case of NGAP release req */ + const rnti_t old_rnti = req->ue_Identity.c_RNTI; rrc_gNB_ue_context_t *ue_context_p = NULL; LOG_I(NR_RRC, - "Reestablishment RNTI %04x req C-RNTI %04lx physCellId %ld cause %s\n", + "Reestablishment RNTI %04x req C-RNTI %04x physCellId %ld cause %s\n", msg->crnti, - req->ue_Identity.c_RNTI, + old_rnti, physCellId, scause); @@ -1483,10 +1484,9 @@ static void rrc_handle_RRCReestablishmentRequest(gNB_RRC_INST *rrc, return; } - // 3GPP TS 38.321 version 15.13.0 Section 7.1 Table 7.1-1: RNTI values - if (req->ue_Identity.c_RNTI < 0x1 || req->ue_Identity.c_RNTI > 0xffef) { - /* c_RNTI range error should not happen */ - LOG_E(NR_RRC, "NR_RRCReestablishmentRequest c_RNTI %04lx range error, fallback to RRC setup\n", req->ue_Identity.c_RNTI); + // Validate C-RNTI range (3GPP TS 38.321 version 15.13.0 Section 7.1 Table 7.1-1) + if (old_rnti < 0x1 || old_rnti > 0xffef) { + LOG_E(NR_RRC, "NR_RRCReestablishmentRequest c_RNTI %04x range error, fallback to RRC setup\n", old_rnti); goto fallback_rrc_setup; } @@ -1507,7 +1507,6 @@ static void rrc_handle_RRCReestablishmentRequest(gNB_RRC_INST *rrc, goto fallback_rrc_setup; } - rnti_t old_rnti = req->ue_Identity.c_RNTI; ue_context_p = rrc_gNB_get_ue_context_by_rnti(rrc, assoc_id, old_rnti); if (ue_context_p == NULL) { ue_context_p = rrc_gNB_get_ue_context_source_cell(rrc, physCellId, old_rnti); @@ -1518,9 +1517,17 @@ static void rrc_handle_RRCReestablishmentRequest(gNB_RRC_INST *rrc, } gNB_RRC_UE_t *UE = &ue_context_p->ue_context; + if (!UE->as_security_active) { + /* no active security context, need to restart entire connection */ + LOG_E(NR_RRC, "UE requested Reestablishment without activated AS security\n"); + ngap_cause = NGAP_CAUSE_RADIO_NETWORK_RELEASE_DUE_TO_NGRAN_GENERATED_REASON; + goto fallback_rrc_setup; + } + /* should check phys cell ID to identify the correct cell */ const f1ap_served_cell_info_t *cell_info = &du->setup_req->cell[0].info; const f1ap_served_cell_info_t *previous_cell_info = get_cell_information_by_phycellId(physCellId); + f1_ue_data_t ue_data = cu_get_f1_ue_data(UE->rrc_ue_id); nr_ho_source_cu_t *source_ctx = UE->ho_context ? UE->ho_context->source : NULL; DevAssert(!source_ctx || source_ctx->du->setup_req->num_cells_available == 1); @@ -1546,7 +1553,6 @@ static void rrc_handle_RRCReestablishmentRequest(gNB_RRC_INST *rrc, source_ctx->old_cellGroupConfig = NULL; /* update to old DU assoc id -- RNTI + secondary DU UE ID further below */ - f1_ue_data_t ue_data = cu_get_f1_ue_data(UE->rrc_ue_id); ue_data.du_assoc_id = source_ctx->du->assoc_id; bool success = cu_update_f1_ue_data(UE->rrc_ue_id, &ue_data); DevAssert(success); @@ -1565,19 +1571,11 @@ static void rrc_handle_RRCReestablishmentRequest(gNB_RRC_INST *rrc, goto fallback_rrc_setup; } - if (!UE->as_security_active) { - /* no active security context, need to restart entire connection */ - LOG_E(NR_RRC, "UE requested Reestablishment without activated AS security\n"); - ngap_cause = NGAP_CAUSE_RADIO_NETWORK_RELEASE_DUE_TO_NGRAN_GENERATED_REASON; - goto fallback_rrc_setup; - } - /* TODO: start timer in ITTI and drop UE if it does not come back */ // update with new RNTI, and update secondary UE association UE->rnti = msg->crnti; UE->nr_cellid = msg->nr_cellid; - f1_ue_data_t ue_data = cu_get_f1_ue_data(UE->rrc_ue_id); ue_data.secondary_ue = msg->gNB_DU_ue_id; bool success = cu_update_f1_ue_data(UE->rrc_ue_id, &ue_data); DevAssert(success); From 825cbcdb47e61b58f8cc76e9c7ee45fafa0a9bc2 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Wed, 7 Jan 2026 19:51:36 +0100 Subject: [PATCH 10/23] RRC: Allow re-establishment on different DU when physCellId differs When a UE re-establishes on a different DU, the physCellId in the re-establishment request (from the old cell) will differ from the new DU's physCellId. The previous check incorrectly treated this as a "too fast movement" scenario and triggered fallback to RRC setup. This fix distinguishes between two scenarios: 1. Different DU scenario: physCellId differs because UE is on a different DU - this is legitimate and should be allowed. UE Context Setup will be triggered at RRCReestablishmentComplete. 2. Same DU but different physCellId: "too fast movement" scenario - fallback to RRC setup as before. The fix checks if assoc_id != ue_data.du_assoc_id to detect a different DU scenario and allows re-establishment in that case. This addresses the issue where re-establishment on a different DU was incorrectly rejected, preventing proper UE Context Setup per TS 38.401 Section 8.7. --- openair2/RRC/NR/rrc_gNB.c | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/openair2/RRC/NR/rrc_gNB.c b/openair2/RRC/NR/rrc_gNB.c index b0b9a64e7d..20b25fe494 100644 --- a/openair2/RRC/NR/rrc_gNB.c +++ b/openair2/RRC/NR/rrc_gNB.c @@ -1558,17 +1558,25 @@ static void rrc_handle_RRCReestablishmentRequest(gNB_RRC_INST *rrc, DevAssert(success); nr_rrc_finalize_ho(UE); } else if (physCellId != cell_info->nr_pci) { - /* UE was moving from previous cell so quickly that RRCReestablishment for previous cell was received in this cell */ - LOG_I(NR_RRC, - "RRC Reestablishment Request from different physCellId (%ld) than current physCellId (%d), fallback to RRC setup\n", - physCellId, - cell_info->nr_pci); - ngap_cause = NGAP_CAUSE_RADIO_NETWORK_RELEASE_DUE_TO_NGRAN_GENERATED_REASON; - /* 38.401 8.7: "If the UE accessed from a gNB-DU other than the original - * one, the gNB-CU should trigger the UE Context Setup procedure". Let's - * assume that the old DU will trigger a release request, also freeing the - * ongoing context at the CU. Hence, create new below */ - goto fallback_rrc_setup; + /* Check if this is a different DU scenario or "too fast movement" scenario */ + if (assoc_id != ue_data.du_assoc_id) { + /* Different DU scenario - physCellId differs because UE is re-establishing on a different DU. + * Re-establishment is allowed, UE Context Setup will be triggered at RRCReestablishmentComplete. */ + LOG_I(NR_RRC, + "UE %d: Re-establishment on different DU (physCellId %ld from old cell != %d from new DU)\n", + UE->rrc_ue_id, + physCellId, + cell_info->nr_pci); + } else { + /* Same DU but different physCellId - "too fast movement" scenario: + * UE was moving from previous cell so quickly that RRCReestablishment for previous cell was received in this cell */ + LOG_I(NR_RRC, + "RRC Reestablishment Request from different physCellId (%ld) than current physCellId (%d), fallback to RRC setup\n", + physCellId, + cell_info->nr_pci); + ngap_cause = NGAP_CAUSE_RADIO_NETWORK_RELEASE_DUE_TO_NGRAN_GENERATED_REASON; + goto fallback_rrc_setup; + } } /* TODO: start timer in ITTI and drop UE if it does not come back */ From d18c2d27f26af16785cfdca94102c9f53f78766b Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Fri, 16 Jan 2026 20:33:51 +0100 Subject: [PATCH 11/23] RRC: cleanup and improve safety of rrc_gNB_generate_RRCReestablishment() - Remove unused masterCellGroup_from_DU parameter from rrc_gNB_generate_RRCReestablishment() as it is not used in the RRC re-establishment generation, RRCReestablishment message does not contain CellGroupConfig which is obtained later via UE Context Modification Request/Response - Update function documentation - Initialize security_parameters struct to zero for safety - Add check on ciphering and integrity algorithm ranges --- openair2/RRC/NR/rrc_gNB.c | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/openair2/RRC/NR/rrc_gNB.c b/openair2/RRC/NR/rrc_gNB.c index 20b25fe494..a727418160 100644 --- a/openair2/RRC/NR/rrc_gNB.c +++ b/openair2/RRC/NR/rrc_gNB.c @@ -1137,12 +1137,13 @@ static void cuup_notify_reestablishment(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue_p) free_e1ap_context_mod_request(&req); } -/** - * @brief RRCReestablishment message - * Direction: Network to UE - */ +/** @brief Generate and send RRCReestablishment message to UE + * This function implements the gNB side of the RRC Connection Re-establishment procedure + * as specified in 3GPP TS 38.331 clause 5.3.7.4. + * @param ue_context_pP UE context pointer + * @param old_rnti Previous RNTI of the UE before re-establishment + * @param du DU container containing cell information for key derivation */ static void rrc_gNB_generate_RRCReestablishment(rrc_gNB_ue_context_t *ue_context_pP, - const uint8_t *masterCellGroup_from_DU, const rnti_t old_rnti, const nr_rrc_du_container_t *du) { @@ -1167,7 +1168,9 @@ static void rrc_gNB_generate_RRCReestablishment(rrc_gNB_ue_context_t *ue_context LOG_A(NR_RRC, "Send RRCReestablishment [%d bytes] to RNTI %04x\n", size, ue_p->rnti); /* Ciphering and Integrity according to TS 33.501 */ - nr_pdcp_entity_security_keys_and_algos_t security_parameters; + nr_pdcp_entity_security_keys_and_algos_t security_parameters = {0}; + DevAssert(ue_p->ciphering_algorithm >= NR_CipheringAlgorithm_nea0 && ue_p->ciphering_algorithm <= NR_CipheringAlgorithm_nea2); + DevAssert(ue_p->integrity_algorithm >= NR_IntegrityProtAlgorithm_nia0 && ue_p->integrity_algorithm <= NR_IntegrityProtAlgorithm_nia2); /* Derive the keys from kgnb */ nr_derive_key(RRC_ENC_ALG, ue_p->ciphering_algorithm, ue_p->kgnb, security_parameters.ciphering_key); nr_derive_key(RRC_INT_ALG, ue_p->integrity_algorithm, ue_p->kgnb, security_parameters.integrity_key); @@ -1588,7 +1591,7 @@ static void rrc_handle_RRCReestablishmentRequest(gNB_RRC_INST *rrc, bool success = cu_update_f1_ue_data(UE->rrc_ue_id, &ue_data); DevAssert(success); - rrc_gNB_generate_RRCReestablishment(ue_context_p, msg->du2cu_rrc_container, old_rnti, du); + rrc_gNB_generate_RRCReestablishment(ue_context_p, old_rnti, du); return; fallback_rrc_setup: From 9a11690f818106b015e663204ecd11db0740269b Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Wed, 7 Jan 2026 20:10:45 +0100 Subject: [PATCH 12/23] RRC: Improve UE context lookup for re-establishment on different DU Add intermediate fallback to rrc_gNB_get_ue_context_by_rnti_any_du() in re-establishment request handling to better handle cases where UE re-establishes on a different DU. RNTI is NOT globally unique - different DUs can assign the same RNTI to different UEs. The assoc_id check in rrc_gNB_get_ue_context_by_rnti() disambiguates RNTI across DUs and prevents returning the wrong UE. However, during re-establishment on a different DU, du_assoc_id hasn't been updated yet (message comes from new DU, but ue_data.du_assoc_id still points to old DU), causing the primary lookup to fail even though the UE exists. The lookup now uses a three-tier fallback: 1. Primary: rrc_gNB_get_ue_context_by_rnti() with assoc_id check (normal case: same DU) 2. Fallback 1: rrc_gNB_get_ue_context_by_rnti_any_du() by RNTI only (re-establishment on different DU) 3. Fallback 2: rrc_gNB_get_ue_context_source_cell() using physCellId (handover scenarios) --- openair2/RRC/NR/rrc_gNB.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/openair2/RRC/NR/rrc_gNB.c b/openair2/RRC/NR/rrc_gNB.c index a727418160..32ddaadecf 100644 --- a/openair2/RRC/NR/rrc_gNB.c +++ b/openair2/RRC/NR/rrc_gNB.c @@ -1510,12 +1510,18 @@ static void rrc_handle_RRCReestablishmentRequest(gNB_RRC_INST *rrc, goto fallback_rrc_setup; } + // Try to find UE context by RNTI and assoc_id (normal case: same DU) ue_context_p = rrc_gNB_get_ue_context_by_rnti(rrc, assoc_id, old_rnti); if (ue_context_p == NULL) { - ue_context_p = rrc_gNB_get_ue_context_source_cell(rrc, physCellId, old_rnti); + // Fallback 1: Try to find UE by RNTI only (re-establishment on different DU scenario) + ue_context_p = rrc_gNB_get_ue_context_by_rnti_any_du(rrc, old_rnti); if (ue_context_p == NULL) { - LOG_E(NR_RRC, "NR_RRCReestablishmentRequest without UE context, fallback to RRC setup\n"); - goto fallback_rrc_setup; + // Fallback 2: Try to find UE by source cell (handover scenario) + ue_context_p = rrc_gNB_get_ue_context_source_cell(rrc, physCellId, old_rnti); + if (ue_context_p == NULL) { + LOG_E(NR_RRC, "NR_RRCReestablishmentRequest without UE context, fallback to RRC setup\n"); + goto fallback_rrc_setup; + } } } gNB_RRC_UE_t *UE = &ue_context_p->ue_context; From aff0ea576090dc7f3dc7e5b374540d0e5033cbc8 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Wed, 29 Oct 2025 16:44:54 +0100 Subject: [PATCH 13/23] RRC logging: add utility function to dump ASN1 MCG Add debugging utilities dump_cgc() and dump_mcg() to decode and print CellGroupConfig in XER format when DEBUG_ASN1 flag is enabled. This helps debug RRC Reconfiguration messages containing masterCellGroup. --- openair2/RRC/NR/MESSAGES/asn1_msg.c | 44 +++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/openair2/RRC/NR/MESSAGES/asn1_msg.c b/openair2/RRC/NR/MESSAGES/asn1_msg.c index e179b5f4e8..67172cccbe 100644 --- a/openair2/RRC/NR/MESSAGES/asn1_msg.c +++ b/openair2/RRC/NR/MESSAGES/asn1_msg.c @@ -630,6 +630,49 @@ static byte_array_t do_HO_RRCReconfiguration(nr_rrc_reconfig_param_t *params) return msg; } +static void dump_cgc(const uint8_t *buf, size_t len) +{ + // Decode the encoded CellGroupConfig for debugging + NR_CellGroupConfig_t *temp_cellGroupConfig = NULL; + asn_dec_rval_t dec_rval = uper_decode_complete(NULL, &asn_DEF_NR_CellGroupConfig, (void **)&temp_cellGroupConfig, buf, len); + if (dec_rval.code == RC_OK && dec_rval.consumed > 0) { + xer_fprint(stdout, &asn_DEF_NR_CellGroupConfig, temp_cellGroupConfig); + ASN_STRUCT_FREE(asn_DEF_NR_CellGroupConfig, temp_cellGroupConfig); + } else { + LOG_W(NR_RRC, "Failed to decode CellGroupConfig (code=%d consumed=%zu)\n", dec_rval.code, dec_rval.consumed); + } +} + +static void dump_mcg(const NR_DL_DCCH_Message_t *dl_dcch_msg) +{ + if (!dl_dcch_msg) { + LOG_W(NR_RRC, "DL_DCCH_Message is NULL\n"); + return; + } + + const NR_RRCReconfiguration_IEs_t *reconf_ies = NULL; + const struct NR_DL_DCCH_MessageType__c1 *c1 = NULL; + + if (dl_dcch_msg->message.present == NR_DL_DCCH_MessageType_PR_c1 + && (c1 = dl_dcch_msg->message.choice.c1) + && c1->present == NR_DL_DCCH_MessageType__c1_PR_rrcReconfiguration + && c1->choice.rrcReconfiguration + && c1->choice.rrcReconfiguration->criticalExtensions.present + == NR_RRCReconfiguration__criticalExtensions_PR_rrcReconfiguration + && c1->choice.rrcReconfiguration->criticalExtensions.choice.rrcReconfiguration) { + reconf_ies = c1->choice.rrcReconfiguration->criticalExtensions.choice.rrcReconfiguration; + } + + if (reconf_ies && reconf_ies->nonCriticalExtension && reconf_ies->nonCriticalExtension->masterCellGroup) { + const OCTET_STRING_t *mcg = reconf_ies->nonCriticalExtension->masterCellGroup; + + /* decode and XER print CellGroupConfig */ + dump_cgc(mcg->buf, mcg->size); + } else { + LOG_W(NR_RRC, "No masterCellGroup found in RRCReconfiguration nonCriticalExtension\n"); + } +} + byte_array_t do_RRCReconfiguration(const nr_rrc_reconfig_param_t *params) { byte_array_t msg = {.buf = NULL, .len = 0}; @@ -651,6 +694,7 @@ byte_array_t do_RRCReconfiguration(const nr_rrc_reconfig_param_t *params) if (LOG_DEBUGFLAG(DEBUG_ASN1)) { xer_fprint(stdout, &asn_DEF_NR_DL_DCCH_Message, (void *)&dl_dcch_msg); + dump_mcg(&dl_dcch_msg); } int val = uper_encode_to_new_buffer(&asn_DEF_NR_DL_DCCH_Message, NULL, &dl_dcch_msg, (void **)&msg.buf); From 129b62e708cdcb0975f55ac4a49f74a8030e3258 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Thu, 4 Dec 2025 18:17:23 +0100 Subject: [PATCH 14/23] Telnet: improve fetch_du_by_ue_id to auto-detect connected UE Issue: sometimes it happens that an ul failure occurs in the handover pipeline, UE reconnects with different UE ID and fetch_du_by_ue_id fails. - Modified fetch_du_by_ue_id in telnetsrv_ci.c to automatically find the connected UE when no UE ID is provided, similar to other telnet commands like rrc_gNB_trigger_f1_ho - Updated test cases in container_sa_f1_ho_b210_quectel.xml to use the improved command without hardcoded UE ID parameter - This makes the command more robust and easier to use in CI tests --- ci-scripts/xml_files/container_5g_f1_rfsim.xml | 6 +++--- ci-scripts/xml_files/container_5g_rfsim_n2_ho.xml | 6 +++--- ci-scripts/xml_files/container_sa_f1_ho_b210_quectel.xml | 6 +++--- ci-scripts/xml_files/container_sa_n2_ho_b210_quectel.xml | 4 ++-- common/utils/telnetsrv/telnetsrv_ci.c | 9 ++++++++- 5 files changed, 19 insertions(+), 12 deletions(-) diff --git a/ci-scripts/xml_files/container_5g_f1_rfsim.xml b/ci-scripts/xml_files/container_5g_f1_rfsim.xml index 1ffba40f77..7385daacfc 100644 --- a/ci-scripts/xml_files/container_5g_f1_rfsim.xml +++ b/ci-scripts/xml_files/container_5g_f1_rfsim.xml @@ -240,7 +240,7 @@ Custom_Command UE (1) connected to DU-PCI 1? localhost - echo ci fetch_du_by_ue_id 1 | ncat 192.168.71.150 9090 | grep "3585" + echo ci fetch_du_by_ue_id | ncat 192.168.71.150 9090 | grep "3585" @@ -271,7 +271,7 @@ Custom_Command UE (1) connected to DU-PCI 0? localhost - echo ci fetch_du_by_ue_id 1 | ncat 192.168.71.150 9090 | grep "3584" + echo ci fetch_du_by_ue_id | ncat 192.168.71.150 9090 | grep "3584" @@ -302,7 +302,7 @@ Custom_Command UE (1) connected to DU-PCI 1? localhost - echo ci fetch_du_by_ue_id 1 | ncat 192.168.71.150 9090 | grep "3585" + echo ci fetch_du_by_ue_id | ncat 192.168.71.150 9090 | grep "3585" diff --git a/ci-scripts/xml_files/container_5g_rfsim_n2_ho.xml b/ci-scripts/xml_files/container_5g_rfsim_n2_ho.xml index 6a4d43f93a..9ef14478b1 100644 --- a/ci-scripts/xml_files/container_5g_rfsim_n2_ho.xml +++ b/ci-scripts/xml_files/container_5g_rfsim_n2_ho.xml @@ -120,7 +120,7 @@ Custom_Command UE (1) connected to DU-PCI 1? localhost - echo ci fetch_du_by_ue_id 1 | ncat 192.168.71.180 9090 | grep "1234" + echo ci fetch_du_by_ue_id | ncat 192.168.71.180 9090 | grep "1234" @@ -181,7 +181,7 @@ Custom_Command UE (1) connected to DU-PCI 0? localhost - echo ci fetch_du_by_ue_id 1 | ncat 192.168.71.150 9090 | grep "3584" + echo ci fetch_du_by_ue_id | ncat 192.168.71.150 9090 | grep "3584" @@ -218,7 +218,7 @@ Custom_Command UE (1) connected to DU-PCI 1? localhost - echo ci fetch_du_by_ue_id 1 | ncat 192.168.71.180 9090 | grep "1234" + echo ci fetch_du_by_ue_id | ncat 192.168.71.180 9090 | grep "1234" diff --git a/ci-scripts/xml_files/container_sa_f1_ho_b210_quectel.xml b/ci-scripts/xml_files/container_sa_f1_ho_b210_quectel.xml index ff36947a57..2804e3c14e 100644 --- a/ci-scripts/xml_files/container_sa_f1_ho_b210_quectel.xml +++ b/ci-scripts/xml_files/container_sa_f1_ho_b210_quectel.xml @@ -129,7 +129,7 @@ Custom_Command UE connected to DU1? groot - echo ci fetch_du_by_ue_id 1 | ncat 127.0.0.1 9090 | grep "3585" + echo ci fetch_du_by_ue_id | ncat 127.0.0.1 9090 | grep "3585" @@ -152,7 +152,7 @@ Custom_Command UE connected to DU0? groot - echo ci fetch_du_by_ue_id 1 | ncat 127.0.0.1 9090 | grep "3584" + echo ci fetch_du_by_ue_id | ncat 127.0.0.1 9090 | grep "3584" @@ -175,7 +175,7 @@ Custom_Command UE connected to DU1? groot - echo ci fetch_du_by_ue_id 1 | ncat 127.0.0.1 9090 | grep "3585" + echo ci fetch_du_by_ue_id | ncat 127.0.0.1 9090 | grep "3585" diff --git a/ci-scripts/xml_files/container_sa_n2_ho_b210_quectel.xml b/ci-scripts/xml_files/container_sa_n2_ho_b210_quectel.xml index 84c5a5bea5..1f4c7c1981 100644 --- a/ci-scripts/xml_files/container_sa_n2_ho_b210_quectel.xml +++ b/ci-scripts/xml_files/container_sa_n2_ho_b210_quectel.xml @@ -121,7 +121,7 @@ Custom_Command UE connected to gNB-PCI1? rocket - echo ci fetch_du_by_ue_id 1 | ncat 127.0.0.1 9090 | grep "3585" + echo ci fetch_du_by_ue_id | ncat 127.0.0.1 9090 | grep "3585" @@ -144,7 +144,7 @@ Custom_Command UE connected to gNB-PCI0? groot - echo ci fetch_du_by_ue_id 1 | ncat 127.0.0.1 9090 | grep "3584" + echo ci fetch_du_by_ue_id | ncat 127.0.0.1 9090 | grep "3584" diff --git a/common/utils/telnetsrv/telnetsrv_ci.c b/common/utils/telnetsrv/telnetsrv_ci.c index 10f9626ef0..a2bcccd150 100644 --- a/common/utils/telnetsrv/telnetsrv_ci.c +++ b/common/utils/telnetsrv/telnetsrv_ci.c @@ -154,10 +154,17 @@ int fetch_du_by_ue_id(char *buf, int debug, telnet_printfunc_t prnt) if (!RC.nrrrc) ERROR_MSG_RET("no RRC present, cannot list counts\n"); - ue_id_t ue_id = 1; + ue_id_t ue_id; if (buf) { ue_id = strtol(buf, NULL, 10); + } else { + // No UE ID provided: find the connected UE first + rrc_gNB_ue_context_t *ue = get_single_rrc_ue(); + if (!ue) + ERROR_MSG_RET("no single UE in RRC present\n"); + ue_id = ue->ue_context.rrc_ue_id; } + nr_rrc_du_container_t *du = get_du_for_ue(RC.nrrrc[0], ue_id); if (du) { From b74cd9c6128fd0dacd79b8fa3f3dc62c291aafff Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Fri, 17 Oct 2025 15:31:54 +0200 Subject: [PATCH 15/23] F1AP: Add gNB-DU Configuration Query IE support This field will be adopted to trigger cgc encoding at DU, according to the specs (see subsequent commit for details). Changes: - Add gNB_DU_Configuration_Query field to f1ap_ue_context_mod_req_t - Implement encoding/decoding - Add to cp, eq, and free functions for the new field - Update tests to include the new field --- openair2/COMMON/f1ap_messages_types.h | 3 +++ openair2/F1AP/lib/f1ap_ue_context.c | 20 ++++++++++++++++++++ openair2/F1AP/tests/f1ap_lib_test.c | 1 + 3 files changed, 24 insertions(+) diff --git a/openair2/COMMON/f1ap_messages_types.h b/openair2/COMMON/f1ap_messages_types.h index a92f14ca50..09a3bf4b43 100644 --- a/openair2/COMMON/f1ap_messages_types.h +++ b/openair2/COMMON/f1ap_messages_types.h @@ -552,6 +552,9 @@ typedef struct f1ap_ue_context_mod_req_t { byte_array_t *rrc_container; + // gNB-DU Configuration Query + bool *gNB_DU_Configuration_Query; + int srbs_len; f1ap_srb_to_setup_t *srbs; // reuse the same type as for setup; there is a // list for SRB modification diff --git a/openair2/F1AP/lib/f1ap_ue_context.c b/openair2/F1AP/lib/f1ap_ue_context.c index 829cdacdfb..728849d8e5 100644 --- a/openair2/F1AP/lib/f1ap_ue_context.c +++ b/openair2/F1AP/lib/f1ap_ue_context.c @@ -1666,6 +1666,15 @@ F1AP_F1AP_PDU_t *encode_ue_context_mod_req(const f1ap_ue_context_mod_req_t *req) OCTET_STRING_fromBuf(&ie81->value.choice.RRCContainer, (const char *)req->rrc_container->buf, req->rrc_container->len); } + /* optional: gNB-DU Configuration Query */ + if (req->gNB_DU_Configuration_Query && *req->gNB_DU_Configuration_Query) { + asn1cSequenceAdd(out->protocolIEs.list, F1AP_UEContextModificationRequestIEs_t, ie82); + ie82->id = F1AP_ProtocolIE_ID_id_GNB_DUConfigurationQuery; + ie82->criticality = F1AP_Criticality_ignore; + ie82->value.present = F1AP_UEContextModificationRequestIEs__value_PR_GNB_DUConfigurationQuery; + ie82->value.choice.GNB_DUConfigurationQuery = F1AP_GNB_DUConfigurationQuery_true; + } + /* optional: SRBs_ToBeSetupMod */ if (req->srbs_len > 0) { DevAssert(req->srbs); @@ -1777,6 +1786,13 @@ bool decode_ue_context_mod_req(const F1AP_F1AP_PDU_t *pdu, f1ap_ue_context_mod_r *out->rrc_container = create_byte_array(os->size, os->buf); } break; + case F1AP_ProtocolIE_ID_id_GNB_DUConfigurationQuery: + _F1_EQ_CHECK_INT(ie->value.present, F1AP_UEContextModificationRequestIEs__value_PR_GNB_DUConfigurationQuery); + { + bool v = ie->value.choice.GNB_DUConfigurationQuery == F1AP_GNB_DUConfigurationQuery_true; + _F1_MALLOC(out->gNB_DU_Configuration_Query, v); + } + break; case F1AP_ProtocolIE_ID_id_SRBs_ToBeSetupMod_List: _F1_EQ_CHECK_INT(ie->value.present, F1AP_UEContextModificationRequestIEs__value_PR_SRBs_ToBeSetupMod_List); _F1_CHECK_EXP(decode_srbs_to_setupmod(&ie->value.choice.SRBs_ToBeSetupMod_List, &out->srbs_len, &out->srbs)); @@ -1862,6 +1878,8 @@ f1ap_ue_context_mod_req_t cp_ue_context_mod_req(const f1ap_ue_context_mod_req_t } if (orig->status) _F1_MALLOC(cp.status, *orig->status); + if (orig->gNB_DU_Configuration_Query) + _F1_MALLOC(cp.gNB_DU_Configuration_Query, *orig->gNB_DU_Configuration_Query); return cp; } @@ -1898,6 +1916,7 @@ bool eq_ue_context_mod_req(const f1ap_ue_context_mod_req_t *a, const f1ap_ue_con _F1_CHECK_EXP(eq_drb_to_release(&a->drbs_rel[i], &b->drbs_rel[i])); _F1_EQ_CHECK_OPTIONAL_IE(a, b, status, _F1_EQ_CHECK_INT); + _F1_EQ_CHECK_OPTIONAL_IE(a, b, gNB_DU_Configuration_Query, _F1_EQ_CHECK_INT); return true; } @@ -1925,6 +1944,7 @@ void free_ue_context_mod_req(f1ap_ue_context_mod_req_t *req) free_drb_to_release(&req->drbs_rel[i]); free(req->drbs_rel); free(req->status); + free(req->gNB_DU_Configuration_Query); } /** diff --git a/openair2/F1AP/tests/f1ap_lib_test.c b/openair2/F1AP/tests/f1ap_lib_test.c index 75fa75bcc1..cba787f4dc 100644 --- a/openair2/F1AP/tests/f1ap_lib_test.c +++ b/openair2/F1AP/tests/f1ap_lib_test.c @@ -1039,6 +1039,7 @@ static void test_f1ap_ue_context_modification_request() orig.drbs_rel[2].id = 19; _F1_MALLOC(orig.status, LOWER_LAYERS_RESUME); + _F1_MALLOC(orig.gNB_DU_Configuration_Query, true); F1AP_F1AP_PDU_t *f1enc = encode_ue_context_mod_req(&orig); F1AP_F1AP_PDU_t *f1dec = f1ap_encode_decode(f1enc); From 50de6bd8fb277fa4fed10c2da24f16c8b0242b61 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Fri, 17 Oct 2025 15:47:54 +0200 Subject: [PATCH 16/23] F1: encode CGC in UE Context Modification Response upon GNB-DU Configuration Query Apply CGC encoding through GNB-DU Configuration Query. This applies to the pdu session release path (rrc_gNB_send_f1_drb_release_request) and E1 bearer mod / DRB add+release path (rrc_gNB_generate_UeContextModificationRequest). This is necessary to implement 3GPP TS 38.473 requirement for gNB-DU Configuration Query IE in UE Context Modification Request: "If the GNB-DU Configuration Query IE is contained in the UE CONTEXT MODIFICATION REQUEST message, gNB-DU shall include the CellGroupConfig IE in the DU To CU RRC Information IE in the UE CONTEXT MODIFICATION RESPONSE message." i.e.: The DU sends the CGC only when this IE is enabled in the REQ message from the CU. The only other scenario according to the same specs is when the Full Configuration IE is present. Outside of these two cases, CGC shall not be sent. --- openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c | 4 +++- openair2/RRC/NR/rrc_gNB.c | 8 ++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c b/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c index 3bc5d7a7af..3add62a338 100644 --- a/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c +++ b/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c @@ -821,7 +821,9 @@ void ue_context_modification_request(const f1ap_ue_context_mod_req_t *req) update_cellGroupConfig(new_CellGroup, UE->uid, UE->capability, &mac->radio_config, scc); } - if (req->srbs_len > 0 || req->drbs_len > 0 || req->drbs_rel_len > 0 || ue_cap != NULL) { + // 3GPP TS 38.473 Clause 8.3.4: If gNB-DU Configuration Query is present, include CellGroupConfig + if (req->gNB_DU_Configuration_Query != NULL && *req->gNB_DU_Configuration_Query) { + LOG_I(NR_MAC, "UE %04x: gNB-DU Configuration Query received, will include CellGroupConfig in response\n", UE->rnti); resp.du_to_cu_rrc_info = calloc_or_fail(1, sizeof(du_to_cu_rrc_information_t)); resp.du_to_cu_rrc_info->cell_group_config = encode_cellgroup_config(new_CellGroup); diff --git a/openair2/RRC/NR/rrc_gNB.c b/openair2/RRC/NR/rrc_gNB.c index 32ddaadecf..3323261c91 100644 --- a/openair2/RRC/NR/rrc_gNB.c +++ b/openair2/RRC/NR/rrc_gNB.c @@ -1027,6 +1027,10 @@ static void rrc_gNB_send_f1_drb_release_request(gNB_RRC_INST *rrc, gNB_RRC_UE_t req.drbs_rel_len = n_drb_to_release; memcpy(req.drbs_rel, drb_to_release, n_drb_to_release * sizeof(int)); + // Request CellGroupConfig from DU in the response + req.gNB_DU_Configuration_Query = calloc_or_fail(1, sizeof(*req.gNB_DU_Configuration_Query)); + *req.gNB_DU_Configuration_Query = true; + /* send UE Context Modification to DU without attaching any RRC container */ rrc->mac_rrc.ue_context_modification_request(ue_data.du_assoc_id, &req); LOG_I(NR_RRC, "UE %d: send F1 UE Context Modification Request with DRB release (%d DRBs)\n", ue_p->rrc_ue_id, req.drbs_rel_len); @@ -3662,6 +3666,10 @@ void rrc_gNB_generate_UeContextModificationRequest(const gNB_RRC_INST *rrc, .drbs_rel = (f1ap_drb_to_release_t *)rel_drbs, .cu_to_du_rrc_info = cu2du, }; + + // Request CellGroupConfig from DU in the response + ue_context_modif_req.gNB_DU_Configuration_Query = calloc_or_fail(1, sizeof(*ue_context_modif_req.gNB_DU_Configuration_Query)); + *ue_context_modif_req.gNB_DU_Configuration_Query = true; rrc->mac_rrc.ue_context_modification_request(ue_data.du_assoc_id, &ue_context_modif_req); // avoid attempt to release rel_drbs ue_context_modif_req.drbs_rel_len = 0; From 806a15da01acc4d33f851ade69e2d5790033bdae Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Fri, 17 Oct 2025 17:22:10 +0200 Subject: [PATCH 17/23] Implement TS 38.473 transparency for CellGroupConfig forwarding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit implements transparent forwarding of CellGroupConfig as required by TS 38.473, eliminating CU-side decode/re-encode cycles that violate transparency requirements. Main Design Change: - Before: CU decoded CellGroupConfig from DU, stored in the UE context then re-encoded when sending to UE. CU constructed new CellGroupConfig after RRCReestablishmentComplete. - After: CU requests CellGroupConfig from DU via UE Context Modification Request with `gNB_DU_Configuration_Query=true`, DU sends CellGroupConfig which CU stores as encoded `byte_array_t` and forwards transparently via reconfiguration without decode/re-encode cycles To this purpose, all decode operations of CellGroupConfig were removed from DU and all encode operations were removed from the CU. Key Changes: CU: - Replace NR_CellGroupConfig_t* masterCellGroup with byte_array_t mcg in gNB_RRC_UE_t for storing encoded CellGroupConfig - Rename cell_group_config to cgc in nr_rrc_reconfig_param_t for brevity - Implement transparent forwarding in build_RRCReconfiguration_IEs() - Add helper functions: - `store_cgc()` to store encoded bytes - `get_meas_gap_config()`: Decode MeasGapConfig from DU to CU response - `rrc_detect_reestablishment()` to detect re-establishment by decoding temporarily (only for detection) - Remove legacy decode/re-encode paths in RRC message generation - Update all CellGroupConfig storage to use encoded bytes - Update reconfiguration generation to accept is_reestablishment parameter DU: - Add `get_cellgroup_config()` to select source (CellGroup vs reconfigCellGroup) based on re-establishment state - Add re-establishment detection logic in `ue_context_modification_request()` - Add `update_cellgroup_for_reestablishment()` to set reestablishRLC flags and remove SRB1 from bearer list - When `gNB_DU_Configuration_Query` is present and re-establishment detected: - Clones from reconfigCellGroup (which contains spCellConfig) instead of CellGroup (where spCellConfig was removed) - Sets `reestablishRLC` flags for all bearers except SRB1 - Removes SRB1 from bearer list (already re-established) Data Structure Changes: - gNB_RRC_UE_t: Add byte_array_t mcg field - nr_rrc_reconfig_param_t: Change cell_group_config to cgc (byte_array_t*) - Remove masterCellGroup field usage in favor of encoded mcg New re-establishment flow: 1. UE sends RRCReestablishmentRequest 2. CU processes -> sends RRCReestablishment 3. CU sends RRCReestablishmentComplete 4. CU sends UE Context Modification Request to DU with `gNB_DU_Configuration_Query=true` 5. DU detects re-establishment (`UE->reestablish_rlc && UE->reconfigCellGroup != NULL`): - Clones `UE->reconfigCellGroup` -> `new_CellGroup` (contains spCellConfig) - Sets `reestablishRLC` flags for all bearers except SRB1 - Removes SRB1 from bearer list (already re-established) - Encodes `new_CellGroup` and sends to CU 6. CU receives encoded CellGroupConfig -> stores in `UE->mcg` via `store_cgc()` 7. CU detects re-establishment via `rrc_detect_reestablishment()` 8. CU calls `rrc_gNB_generate_dedicatedRRCReconfiguration()` with `is_reestablishment=true`: - Creates SRB/DRB lists with re-establishment flags - Generates RRC Reconfiguration with transparent CellGroupConfig - Sends to UE Why spCellConfig must be added in the DU in case of re-establishment: With transparent forwarding, CU requests CellGroupConfig from DU instead of constructing it. During re-establishment, DU's runtime CellGroup has spCellConfig = NULL (removed per TS 38.331 §5.3.7.2). However, for the gNB_DU_Configuration_Query response, DU must provide a complete CellGroupConfig including spCellConfig. Therefore, DU restores spCellConfig by encoding reconfigCellGroup (with the original spCellConfig) in CellGroupConfig. In the old design CU constructed CellGroupConfig itself in `rrc_gNB_process_RRCReestablishmentComplete()` and used `spCellConfig` from its stored `masterCellGroup`. Closes #837 --- .../LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c | 67 +++- openair2/RRC/NR/MESSAGES/asn1_msg.c | 40 +-- openair2/RRC/NR/MESSAGES/asn1_msg.h | 3 +- openair2/RRC/NR/nr_rrc_defs.h | 3 +- openair2/RRC/NR/rrc_gNB.c | 294 +++++++++--------- openair2/RRC/NR/rrc_gNB_mobility.c | 6 +- openair2/RRC/NR/rrc_gNB_mobility.h | 2 +- openair2/RRC/NR/rrc_gNB_nsa.c | 15 +- 8 files changed, 229 insertions(+), 201 deletions(-) diff --git a/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c b/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c index 3add62a338..f3fd5edda2 100644 --- a/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c +++ b/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c @@ -634,6 +634,59 @@ static void handle_ue_context_rrc_container(const byte_array_t *rrc_container, c nr_rlc_srb_recv_sdu(rnti, id, rrc_container->buf, rrc_container->len); } +/** @brief Get and clone CellGroupConfig for UE Context Setup/Modification response. + * The source depends on re-establishment state: + * - CellGroup: Currently active/runtime CellGroupConfig used by MAC for scheduling. + * During re-establishment, spCellConfig is removed from it (per TS 38.331 §5.3.7.2). + * - reconfigCellGroup: Staging area that holds a complete CellGroupConfig saved before + * modifications. During re-establishment, it still contains spCellConfig (saved before + * removal). It becomes the new CellGroup when reconfiguration completes. + * During re-establishment, we must clone from reconfigCellGroup to get a complete + * CellGroupConfig with spCellConfig for transparent forwarding to the UE. + * @param UE UE context + * @return Cloned CellGroupConfig */ +static NR_CellGroupConfig_t *get_cellgroup_config(NR_UE_info_t *UE) +{ + if (UE->reestablish_rlc && UE->reconfigCellGroup != NULL) { + return clone_CellGroupConfig(UE->reconfigCellGroup); + } else { + return clone_CellGroupConfig(UE->CellGroup); + } +} + +/** @brief Update CellGroupConfig for RRC re-establishment procedure. + * This function prepares a complete CellGroupConfig for gNB-DU Configuration Query response + * during RRC re-establishment. + * The function sets reestablishRLC flags for all RLC bearers except SRB1. + * SRB1 is removed from the bearer list since it is already re-established during + * RRCReestablishment and should not be included. */ +static void update_cellgroup_for_reestablishment(NR_UE_info_t *UE, NR_CellGroupConfig_t *new_CellGroup) +{ + DevAssert(new_CellGroup); + DevAssert(new_CellGroup->spCellConfig); + DevAssert(UE->reestablish_rlc); + LOG_I(NR_MAC, "UE %04x: Re-establishment detected, setting reestablishRLC flags\n", UE->rnti); + struct NR_CellGroupConfig__rlc_BearerToAddModList *addmod = new_CellGroup->rlc_BearerToAddModList; + if (addmod && addmod->list.count > 0) { + LOG_I(NR_MAC, "UE %04x: CellGroupConfig has %d bearers:\n", UE->rnti, addmod->list.count); + for (int i = 0; i < addmod->list.count; ++i) { + NR_RLC_BearerConfig_t *bearer = addmod->list.array[i]; + int lcid = bearer->logicalChannelIdentity; + int rb_type = bearer->servedRadioBearer->present; + int rb_id = (rb_type == NR_RLC_BearerConfig__servedRadioBearer_PR_srb_Identity) + ? bearer->servedRadioBearer->choice.srb_Identity + : bearer->servedRadioBearer->choice.drb_Identity; + if (rb_type == NR_RLC_BearerConfig__servedRadioBearer_PR_srb_Identity && rb_id == 1) { + asn_sequence_del(&addmod->list, i, 1); + --i; + continue; + } + LOG_I(NR_MAC, "UE %04x: Re-establishing RLC for LCID %d\n", UE->rnti, lcid); + asn1cCallocOne(addmod->list.array[i]->reestablishRLC, NR_RLC_BearerConfig__reestablishRLC_true); + } + } +} + void ue_context_setup_request(const f1ap_ue_context_setup_req_t *req) { const bool is_SA = IS_SA_MODE(get_softmodem_params()); @@ -766,7 +819,7 @@ void ue_context_modification_request(const f1ap_ue_context_mod_req_t *req) return; } - NR_CellGroupConfig_t *new_CellGroup = clone_CellGroupConfig(UE->CellGroup); + NR_CellGroupConfig_t *new_CellGroup = get_cellgroup_config(UE); if (req->srbs_len > 0) { resp.srbs_len = handle_ue_context_srbs_setup(UE, req->srbs_len, req->srbs, &resp.srbs, new_CellGroup, &mac->rlc_config); @@ -804,6 +857,7 @@ void ue_context_modification_request(const f1ap_ue_context_mod_req_t *req) for (int i = 1; i < seq_arr_size(&UE->UE_sched_ctrl.lc_config); ++i) { nr_lc_config_t *c = seq_arr_at(&UE->UE_sched_ctrl.lc_config, i); c->suspended = false; + LOG_I(NR_MAC, "UE %04x: Re-establishing RLC for LCID %d\n", UE->rnti, c->lcid); nr_rlc_reestablish_entity(req->gNB_DU_ue_id, c->lcid); } UE->reestablish_rlc = false; @@ -821,12 +875,21 @@ void ue_context_modification_request(const f1ap_ue_context_mod_req_t *req) update_cellGroupConfig(new_CellGroup, UE->uid, UE->capability, &mac->radio_config, scc); } - // 3GPP TS 38.473 Clause 8.3.4: If gNB-DU Configuration Query is present, include CellGroupConfig + /* 3GPP TS 38.473 Clause 8.3.4: If gNB-DU Configuration Query is present, include CellGroupConfig + * (CU requested CellGroupConfig for transparent forwarding) */ if (req->gNB_DU_Configuration_Query != NULL && *req->gNB_DU_Configuration_Query) { LOG_I(NR_MAC, "UE %04x: gNB-DU Configuration Query received, will include CellGroupConfig in response\n", UE->rnti); + + if (UE->reestablish_rlc) { + update_cellgroup_for_reestablishment(UE, new_CellGroup); + } + + /* Encode CellGroupConfig for transparent forwarding in the DU to CU trasnfer IE + * CU will forward these encoded bytes directly to UE without decode/re-encode cycles */ resp.du_to_cu_rrc_info = calloc_or_fail(1, sizeof(du_to_cu_rrc_information_t)); resp.du_to_cu_rrc_info->cell_group_config = encode_cellgroup_config(new_CellGroup); + // Replace reconfigCellGroup with new_CellGroup (now contains complete config with spCellConfig restored) ASN_STRUCT_FREE(asn_DEF_NR_CellGroupConfig, UE->reconfigCellGroup); UE->reconfigCellGroup = new_CellGroup; configure_UE_BWP(mac, scc, UE, false, NR_SearchSpace__searchSpaceType_PR_common, -1, -1); diff --git a/openair2/RRC/NR/MESSAGES/asn1_msg.c b/openair2/RRC/NR/MESSAGES/asn1_msg.c index 67172cccbe..7a80ff57d2 100644 --- a/openair2/RRC/NR/MESSAGES/asn1_msg.c +++ b/openair2/RRC/NR/MESSAGES/asn1_msg.c @@ -532,7 +532,7 @@ static NR_RRCReconfiguration_IEs_t *build_RRCReconfiguration_IEs(const nr_rrc_re ie->measConfig = params->meas_config; /* nonCriticalExtension, RRCReconfiguration-v1530-IEs */ - if (params->cell_group_config || params->num_nas_msg) { + if (params->cgc || params->num_nas_msg) { // Allocate memory for extension IE ie->nonCriticalExtension = calloc_or_fail(1, sizeof(*ie->nonCriticalExtension)); } @@ -549,37 +549,13 @@ static NR_RRCReconfiguration_IEs_t *build_RRCReconfiguration_IEs(const nr_rrc_re } } - /* masterCellGroup */ - if (params->cell_group_config) { - uint8_t temp[4096]; - asn_enc_rval_t enc = uper_encode_to_buffer(&asn_DEF_NR_CellGroupConfig, NULL, params->cell_group_config, temp, sizeof(temp)); - - if (enc.encoded <= 0) { - LOG_E(NR_RRC, "ASN.1 encoding failed for NR_CellGroupConfig (encoded=%ld)\n", enc.encoded); - if (enc.failed_type) { - LOG_E(NR_RRC, "Failed at ASN.1 type: %s\n", enc.failed_type->name); - } - if (enc.structure_ptr) { - LOG_E(NR_RRC, "Failed at structure element: %p\n", enc.structure_ptr); - } - // Print diagnostic information to help debug the issue - LOG_E(NR_RRC, "CellGroupConfig structure that failed to encode:\n"); - xer_fprint(stdout, &asn_DEF_NR_CellGroupConfig, (const void *)params->cell_group_config); - ASN_STRUCT_FREE(asn_DEF_NR_RRCReconfiguration_IEs, ie); - return NULL; - } - DevAssert(enc.encoded <= sizeof(temp) * 8); // Encoded data must fit in temp buffer - // Allocate buffer for the encoded data and copy it - // enc.encoded is in bits, convert to bytes - size_t encoded_bytes = (enc.encoded + 7) / 8; - uint8_t *buf = calloc_or_fail(1, encoded_bytes); - memcpy(buf, temp, encoded_bytes); - - if (LOG_DEBUGFLAG(DEBUG_ASN1)) { - xer_fprint(stdout, &asn_DEF_NR_CellGroupConfig, (const void *)params->cell_group_config); - } + /* masterCellGroup - Transparent forwarding per TS 38.473 */ + if (params->cgc) { + // Copy pre-encoded CellGroupConfig bytes directly without decode/re-encode (TS 38.473 transparency) + // CU receives encoded bytes from DU and forwards to UE without modification + LOG_D(NR_RRC, "Transparent forwarding CellGroupConfig (len=%ld bytes)\n", params->cgc->len); ie->nonCriticalExtension->masterCellGroup = calloc_or_fail(1, sizeof(*ie->nonCriticalExtension->masterCellGroup)); - *ie->nonCriticalExtension->masterCellGroup = (OCTET_STRING_t){.buf = buf, .size = encoded_bytes}; + OCTET_STRING_fromBuf(ie->nonCriticalExtension->masterCellGroup, (const char *)params->cgc->buf, params->cgc->len); } /* masterKeyUpdate */ @@ -630,7 +606,7 @@ static byte_array_t do_HO_RRCReconfiguration(nr_rrc_reconfig_param_t *params) return msg; } -static void dump_cgc(const uint8_t *buf, size_t len) +void dump_cgc(const uint8_t *buf, size_t len) { // Decode the encoded CellGroupConfig for debugging NR_CellGroupConfig_t *temp_cellGroupConfig = NULL; diff --git a/openair2/RRC/NR/MESSAGES/asn1_msg.h b/openair2/RRC/NR/MESSAGES/asn1_msg.h index 2e09397e60..ee6cfc8414 100644 --- a/openair2/RRC/NR/MESSAGES/asn1_msg.h +++ b/openair2/RRC/NR/MESSAGES/asn1_msg.h @@ -70,7 +70,7 @@ typedef struct { NR_MeasConfig_t *meas_config; byte_array_t dedicated_NAS_msg_list[MAX_DRBS_PER_UE]; int num_nas_msg; - NR_CellGroupConfig_t *cell_group_config; + byte_array_t *cgc; bool masterKeyUpdate; int nextHopChainingCount; byte_array_t ue_cap; @@ -179,5 +179,6 @@ int doRRCReconfiguration_from_HandoverCommand(byte_array_t *ba, byte_array_t han 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); +void dump_cgc(const uint8_t *buf, size_t len); #endif /* __RRC_NR_MESSAGES_ASN1_MSG__H__ */ diff --git a/openair2/RRC/NR/nr_rrc_defs.h b/openair2/RRC/NR/nr_rrc_defs.h index 4d24a771c0..46e1ef3384 100644 --- a/openair2/RRC/NR/nr_rrc_defs.h +++ b/openair2/RRC/NR/nr_rrc_defs.h @@ -186,7 +186,8 @@ typedef struct gNB_RRC_UE_s { NR_UE_MRDC_Capability_t* UE_Capability_MRDC; int UE_MRDC_Capability_size; - NR_CellGroupConfig_t *masterCellGroup; + // Transparent forwarding of CellGroupConfig + byte_array_t mcg; NR_RadioBearerConfig_t *rb_config; /* KgNB as derived from KASME received from EPC */ diff --git a/openair2/RRC/NR/rrc_gNB.c b/openair2/RRC/NR/rrc_gNB.c index 3323261c91..073fc0c34f 100644 --- a/openair2/RRC/NR/rrc_gNB.c +++ b/openair2/RRC/NR/rrc_gNB.c @@ -861,7 +861,7 @@ nr_rrc_reconfig_param_t get_RRCReconfiguration_params(gNB_RRC_INST *rrc, gNB_RRC NR_SRB_ToAddModList_t *SRBs = createSRBlist(UE, srb_reest_bitmap); NR_DRB_ToAddModList_t *DRBs = createDRBlist(UE, drb_reestablish, do_integrity, do_ciphering); - nr_rrc_reconfig_param_t params = {.cell_group_config = UE->masterCellGroup, + nr_rrc_reconfig_param_t params = {.cgc = &UE->mcg, .transaction_id = xid, .drb_config_list = DRBs, .meas_config = UE->measConfig, @@ -918,10 +918,22 @@ byte_array_t rrc_gNB_encode_RRCReconfiguration(gNB_RRC_INST *rrc, gNB_RRC_UE_t * return msg; } -static void rrc_gNB_generate_dedicatedRRCReconfiguration(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue_p) +/** @brief Generate and send RRC Reconfiguration message. + * Handles both normal reconfiguration and first reconfiguration after re-establishment. + * @param rrc RRC instance + * @param ue_p UE context + * @param is_reestablishment True if this is the first reconfiguration after re-establishment */ +static void rrc_gNB_generate_dedicatedRRCReconfiguration(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue_p, bool is_reestablishment) { - /* do not re-establish PDCP for any bearer */ - nr_rrc_reconfig_param_t params = get_RRCReconfiguration_params(rrc, ue_p, 0, false); + /** Get RRC Reconfiguration parameters + * For the first reconfiguration after a re-establishment: re-establish PDCP for SRB2 + * (SRB1 already re-established in RRCReestablishment) and all DRBs, + * For the subsequent reconfigurations: do not re-establish PDCP for any bearer */ + uint8_t srb_reest_bitmap = is_reestablishment ? (1 << SRB2) : 0; + bool drb_reestablish = is_reestablishment; + nr_rrc_reconfig_param_t params = get_RRCReconfiguration_params(rrc, ue_p, srb_reest_bitmap, drb_reestablish); + + // Handle PDU session status updates FOR_EACH_SEQ_ARR(rrc_pdu_session_param_t *, item, &ue_p->pduSessions) { if (item->status == PDU_SESSION_STATUS_NEW) { item->status = PDU_SESSION_STATUS_DONE; @@ -932,15 +944,27 @@ static void rrc_gNB_generate_dedicatedRRCReconfiguration(gNB_RRC_INST *rrc, gNB_ } // Set xid for RRC transaction - ue_p->xids[params.transaction_id] = params.n_drb_rel > 0 ? RRC_PDUSESSION_RELEASE : RRC_PDUSESSION_ESTABLISH; + if (is_reestablishment) { + ue_p->xids[params.transaction_id] = RRC_REESTABLISH_COMPLETE; + } else { + ue_p->xids[params.transaction_id] = params.n_drb_rel > 0 ? RRC_PDUSESSION_RELEASE : RRC_PDUSESSION_ESTABLISH; + } byte_array_t msg = rrc_gNB_encode_RRCReconfiguration(rrc, ue_p, params); if (msg.len <= 0) { - LOG_E(NR_RRC, "UE %d: Failed to generate RRCReconfiguration\n", ue_p->rrc_ue_id); + LOG_E(NR_RRC, + "UE %d: Failed to generate RRCReconfiguration%s\n", + ue_p->rrc_ue_id, + is_reestablishment ? " for re-establishment" : ""); + free_RRCReconfiguration_params(params); return; } - LOG_UE_DL_EVENT(ue_p, "Generate RRCReconfiguration (bytes %ld, xid %d)\n", msg.len, params.transaction_id); + LOG_UE_DL_EVENT(ue_p, + "Generate RRCReconfiguration%s (bytes %ld, xid %d)\n", + is_reestablishment ? " after re-establishment" : "", + msg.len, + params.transaction_id); const uint32_t msg_id = NR_DL_DCCH_MessageType__c1_PR_rrcReconfiguration; nr_rrc_transfer_protected_rrc_message(rrc, ue_p, DL_SCH_LCID_DCCH, msg_id, msg.buf, msg.len); free_RRCReconfiguration_params(params); @@ -1230,47 +1254,10 @@ static void rrc_gNB_process_RRCReestablishmentComplete(gNB_RRC_INST *rrc, gNB_RR { LOG_I(NR_RRC, "UE %d Processing NR_RRCReestablishmentComplete from UE\n", ue_p->rrc_ue_id); - int i = 0; - ue_p->xids[xid] = RRC_ACTION_NONE; ue_p->Srb[1].Active = 1; - NR_CellGroupConfig_t *cellGroupConfig = calloc(1, sizeof(NR_CellGroupConfig_t)); - - cellGroupConfig->spCellConfig = ue_p->masterCellGroup->spCellConfig; - cellGroupConfig->mac_CellGroupConfig = ue_p->masterCellGroup->mac_CellGroupConfig; - cellGroupConfig->physicalCellGroupConfig = ue_p->masterCellGroup->physicalCellGroupConfig; - cellGroupConfig->rlc_BearerToReleaseList = NULL; - cellGroupConfig->rlc_BearerToAddModList = calloc(1, sizeof(*cellGroupConfig->rlc_BearerToAddModList)); - - /* - * Get SRB2, DRB configuration from the existing UE context, - * also start from SRB2 (i=1) and not from SRB1 (i=0). - */ - for (i = 1; i < ue_p->masterCellGroup->rlc_BearerToAddModList->list.count; ++i) - asn1cSeqAdd(&cellGroupConfig->rlc_BearerToAddModList->list, ue_p->masterCellGroup->rlc_BearerToAddModList->list.array[i]); - - /* - * At this stage, PDCP entity are re-established and reestablishRLC is flagged - * with RRCReconfiguration to complete RLC re-establishment of remaining bearers - */ - for (i = 0; i < cellGroupConfig->rlc_BearerToAddModList->list.count; i++) { - asn1cCallocOne(cellGroupConfig->rlc_BearerToAddModList->list.array[i]->reestablishRLC, - NR_RLC_BearerConfig__reestablishRLC_true); - } - - /* TODO: remove a reconfigurationWithSync, we don't need it for - * reestablishment. The whole reason why this might be here is that we store - * the CellGroupConfig (after handover), and simply reuse it for - * reestablishment, instead of re-requesting the CellGroupConfig from the DU. - * Hence, add below hack; the solution would be to request the - * CellGroupConfig from the DU when doing reestablishment. */ - if (cellGroupConfig->spCellConfig && cellGroupConfig->spCellConfig->reconfigurationWithSync) { - ASN_STRUCT_FREE(asn_DEF_NR_ReconfigurationWithSync, cellGroupConfig->spCellConfig->reconfigurationWithSync); - cellGroupConfig->spCellConfig->reconfigurationWithSync = NULL; - } - /* Re-establish SRB2 according to clause 5.3.5.6.3 of 3GPP TS 38.331 * (SRB1 is re-established with RRCReestablishment message) */ @@ -1287,37 +1274,26 @@ static void rrc_gNB_process_RRCReestablishmentComplete(gNB_RRC_INST *rrc, gNB_RR /* PDCP Reestablishment of DRBs according to 5.3.5.6.5 of 3GPP TS 38.331 (over E1) */ cuup_notify_reestablishment(rrc, ue_p); - /* Create srb-ToAddModList, 3GPP TS 38.331 RadioBearerConfig: - * do not re-establish PDCP for SRB1, when resuming an RRC connection, - * or at the first reconfiguration after RRC connection reestablishment in NR */ - NR_SRB_ToAddModList_t *SRBs = createSRBlist(ue_p, 1 << SRB2); // Re-establish PDCP for SRB2 only - /* Create drb-ToAddModList */ - bool do_integrity = rrc->security.do_drb_integrity; - bool do_ciphering = rrc->security.do_drb_ciphering; - NR_DRB_ToAddModList_t *DRBs = createDRBlist(ue_p, true, do_integrity, do_ciphering); + // Request DU to provide CellGroupConfig with reestablishRLC flags for re-establishment + // According to TS 38.473 transparency requirements, CU should not construct or re-encode CellGroupConfig + f1_ue_data_t ue_data = cu_get_f1_ue_data(ue_p->rrc_ue_id); + RETURN_IF_INVALID_ASSOC_ID(ue_data.du_assoc_id); + + f1ap_ue_context_mod_req_t ue_context_modif_req = { + .gNB_CU_ue_id = ue_p->rrc_ue_id, + .gNB_DU_ue_id = ue_data.secondary_ue, + }; + + // Request CellGroupConfig from DU in the response + ue_context_modif_req.gNB_DU_Configuration_Query = calloc_or_fail(1, sizeof(bool)); + *ue_context_modif_req.gNB_DU_Configuration_Query = true; + + /** Send UE context modification request to DU, which will respond with CellGroupConfig + * containing reestablishRLC flags for re-establishment. + * The response will be handled in rrc_CU_process_ue_context_modification_response() + * which will store the encoded CellGroupConfig and trigger RRC Reconfiguration */ + rrc->mac_rrc.ue_context_modification_request(ue_data.du_assoc_id, &ue_context_modif_req); - uint8_t new_xid = rrc_gNB_get_next_transaction_identifier(rrc->module_id); - ue_p->xids[new_xid] = RRC_REESTABLISH_COMPLETE; - nr_rrc_reconfig_param_t params = {.cell_group_config = cellGroupConfig, - .meas_config = ue_p->measConfig, - .drb_config_list = DRBs, - .srb_config_list = SRBs, - .transaction_id = new_xid}; - byte_array_t msg = do_RRCReconfiguration(¶ms); - if (msg.len <= 0) { - LOG_E(NR_RRC, "UE %d: Failed to generate RRCReconfiguration\n", ue_p->rrc_ue_id); - return; - } - LOG_DUMPMSG(NR_RRC, DEBUG_RRC, msg.buf, msg.len, "[MSG] RRC Reconfiguration\n"); - LOG_I(NR_RRC, - "UE %d RNTI %04x: Generate NR_RRCReconfiguration after reestablishment complete (bytes %ld)\n", - ue_p->rrc_ue_id, - ue_p->rnti, - msg.len); - const uint32_t msg_id = NR_DL_DCCH_MessageType__c1_PR_rrcReconfiguration; - nr_rrc_transfer_protected_rrc_message(rrc, ue_p, DL_SCH_LCID_DCCH, msg_id, msg.buf, msg.len); - free_RRCReconfiguration_params(params); - free_byte_array(msg); } int nr_rrc_reconfiguration_req(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue_p, const int dl_bwp_id, const int ul_bwp_id) @@ -1326,16 +1302,7 @@ int nr_rrc_reconfiguration_req(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue_p, const int ue_p->xids[xid] = RRC_DEDICATED_RECONF; ue_p->ongoing_reconfiguration = true; - NR_CellGroupConfig_t *masterCellGroup = ue_p->masterCellGroup; - if (dl_bwp_id > 0) { - *masterCellGroup->spCellConfig->spCellConfigDedicated->firstActiveDownlinkBWP_Id = dl_bwp_id; - *masterCellGroup->spCellConfig->spCellConfigDedicated->defaultDownlinkBWP_Id = dl_bwp_id; - } - if (ul_bwp_id > 0) { - *masterCellGroup->spCellConfig->spCellConfigDedicated->uplinkConfig->firstActiveUplinkBWP_Id = ul_bwp_id; - } - - nr_rrc_reconfig_param_t params = {.cell_group_config = masterCellGroup, .transaction_id = xid}; + nr_rrc_reconfig_param_t params = {.cgc = &ue_p->mcg, .transaction_id = xid}; byte_array_t msg = do_RRCReconfiguration(¶ms); if (msg.len <= 0) { LOG_E(NR_RRC, "UE %d: Failed to generate RRCReconfiguration\n", ue_p->rrc_ue_id); @@ -1348,6 +1315,19 @@ int nr_rrc_reconfiguration_req(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue_p, const int return 0; } +/** @brief Store encoded CellGroupConfig for transparent forwarding (TS 38.473 requirement). + * CU must not decode/re-encode CellGroupConfig, only forward encoded bytes to UE. */ +static void store_cgc(gNB_RRC_UE_t *UE, const byte_array_t *cgc) +{ + LOG_D(RRC, "UE %04x storing CellGroupConfig encoded bytes for transparent forwarding (len=%ld)\n", UE->rnti, cgc->len); + + free_byte_array(UE->mcg); + UE->mcg = copy_byte_array(*cgc); + + if (LOG_DEBUGFLAG(DEBUG_ASN1)) + dump_cgc(UE->mcg.buf, UE->mcg.len); +} + static void rrc_handle_RRCSetupRequest(gNB_RRC_INST *rrc, sctp_assoc_t assoc_id, const NR_RRCSetupRequest_IEs_t *rrcSetupRequest, @@ -1405,19 +1385,12 @@ static void rrc_handle_RRCSetupRequest(gNB_RRC_INST *rrc, rrc_gNB_generate_RRCReject(rrc, ue_context_p); return; } - NR_CellGroupConfig_t *cellGroupConfig = NULL; - asn_dec_rval_t dec_rval = uper_decode_complete(NULL, - &asn_DEF_NR_CellGroupConfig, - (void **)&cellGroupConfig, - msg->du2cu_rrc_container, - msg->du2cu_rrc_container_length); - AssertFatal(dec_rval.code == RC_OK && dec_rval.consumed > 0, "Cell group config decode error\n"); gNB_RRC_UE_t *UE = &ue_context_p->ue_context; - UE = &ue_context_p->ue_context; + byte_array_t cgc = {.buf = msg->du2cu_rrc_container, .len = msg->du2cu_rrc_container_length}; + store_cgc(UE, &cgc); UE->establishment_cause = rrcSetupRequest->establishmentCause; UE->nr_cellid = msg->nr_cellid; - UE->masterCellGroup = cellGroupConfig; UE->ongoing_reconfiguration = false; UE->measConfig = nr_rrc_get_measconfig(rrc, UE->nr_cellid); activate_srb(UE, 1); @@ -1561,9 +1534,7 @@ static void rrc_handle_RRCReestablishmentRequest(gNB_RRC_INST *rrc, f1u_dl_gtp_rollback(UE); /* we need the original CellGroupConfig */ - ASN_STRUCT_FREE(asn_DEF_NR_CellGroupConfig, UE->masterCellGroup); - UE->masterCellGroup = source_ctx->old_cellGroupConfig; - source_ctx->old_cellGroupConfig = NULL; + store_cgc(UE, &source_ctx->old_cgc); /* update to old DU assoc id -- RNTI + secondary DU UE ID further below */ ue_data.du_assoc_id = source_ctx->du->assoc_id; @@ -2449,6 +2420,61 @@ void e1_notify_pdcp_status(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, const ngap_drb_s free_e1ap_context_mod_request(&req); } +static NR_MeasGapConfig_t *get_meas_gap_config(const f1ap_du_to_cu_rrc_info_t *du_to_cu_rrc_info) +{ + if (!du_to_cu_rrc_info->meas_gap_config) { + return NULL; + } + + NR_MeasGapConfig_t *measGapConfig = NULL; + asn_dec_rval_t dec_rval = uper_decode_complete(NULL, + &asn_DEF_NR_MeasGapConfig, + (void **)&measGapConfig, + (uint8_t *)du_to_cu_rrc_info->meas_gap_config->buf, + du_to_cu_rrc_info->meas_gap_config->len); + AssertFatal(dec_rval.code == RC_OK && dec_rval.consumed > 0, "measGapConfig decode error\n"); + return measGapConfig; +} + +/** @brief Detect re-establishment by checking reestablishRLC flags in CellGroupConfig. + * Temporary decode only for detection (not for modification, to preserve transparency). + * DU sets reestablishRLC=true for bearers needing RLC re-establishment (except SRB1). + * @param cgc Encoded CellGroupConfig from DU to CU transfer IE + * @return True if re-establishment is detected, false otherwise */ +static bool rrc_detect_reestablishment(const byte_array_t *cgc) +{ + if (!cgc || !cgc->buf || cgc->len == 0) { + return false; + } + + NR_CellGroupConfig_t *temp_cellGroupConfig = NULL; + asn_dec_rval_t dec_rval = + uper_decode_complete(NULL, &asn_DEF_NR_CellGroupConfig, (void **)&temp_cellGroupConfig, cgc->buf, cgc->len); + if (dec_rval.code != RC_OK || dec_rval.consumed == 0) { + LOG_W(NR_RRC, + "Failed to decode CellGroupConfig for re-establishment detection (code=%d, consumed=%zu)\n", + dec_rval.code, + dec_rval.consumed); + return false; + } + + bool is_reestablishment = false; + // Check if any RLC bearer has reestablishRLC flag set + if (temp_cellGroupConfig->rlc_BearerToAddModList && temp_cellGroupConfig->rlc_BearerToAddModList->list.count > 0) { + for (int i = 0; i < temp_cellGroupConfig->rlc_BearerToAddModList->list.count; i++) { + if (temp_cellGroupConfig->rlc_BearerToAddModList->list.array[i]->reestablishRLC + && *temp_cellGroupConfig->rlc_BearerToAddModList->list.array[i]->reestablishRLC + == NR_RLC_BearerConfig__reestablishRLC_true) { + is_reestablishment = true; + break; + } + } + } + ASN_STRUCT_FREE(asn_DEF_NR_CellGroupConfig, temp_cellGroupConfig); + return is_reestablishment; +} + + static void rrc_CU_process_ue_context_setup_response(MessageDef *msg_p, instance_t instance) { f1ap_ue_context_setup_resp_t *resp = &F1AP_UE_CONTEXT_SETUP_RESP(msg_p); @@ -2461,31 +2487,16 @@ static void rrc_CU_process_ue_context_setup_response(MessageDef *msg_p, instance gNB_RRC_UE_t *UE = &ue_context_p->ue_context; UE->f1_ue_context_active = true; - NR_CellGroupConfig_t *cellGroupConfig = NULL; + // Store the encoded CellGroupConfig for transparent forwarding byte_array_t *cgc = &resp->du_to_cu_rrc_info.cell_group_config; - asn_dec_rval_t dec_rval = - uper_decode_complete(NULL, &asn_DEF_NR_CellGroupConfig, (void **)&cellGroupConfig, (uint8_t *)cgc->buf, cgc->len); - AssertFatal(dec_rval.code == RC_OK && dec_rval.consumed > 0, "Cell group config decode error\n"); + store_cgc(UE, cgc); - if (resp->du_to_cu_rrc_info.meas_gap_config) { - NR_MeasGapConfig_t *measGapConfig = NULL; - asn_dec_rval_t dec_rval_mgc = uper_decode_complete(NULL, - &asn_DEF_NR_MeasGapConfig, - (void **)&measGapConfig, - (uint8_t *)resp->du_to_cu_rrc_info.meas_gap_config->buf, - resp->du_to_cu_rrc_info.meas_gap_config->len); - AssertFatal(dec_rval_mgc.code == RC_OK && dec_rval_mgc.consumed > 0, "measGapConfig decode error\n"); + // Process measurement gap config if present + NR_MeasGapConfig_t *measGapConfig = get_meas_gap_config(&resp->du_to_cu_rrc_info); + if (measGapConfig) { UE->measConfig->measGapConfig = measGapConfig; } - if (UE->masterCellGroup) { - ASN_STRUCT_FREE(asn_DEF_NR_CellGroupConfig, UE->masterCellGroup); - LOG_I(RRC, "UE %04x replacing existing CellGroupConfig with new one received from DU\n", UE->rnti); - } - UE->masterCellGroup = cellGroupConfig; - if (LOG_DEBUGFLAG(DEBUG_ASN1)) - xer_fprint(stdout, &asn_DEF_NR_CellGroupConfig, UE->masterCellGroup); - if (!IS_SA_MODE(get_softmodem_params())) { rrc_add_nsa_user_resp(rrc, UE, resp); return; @@ -2506,7 +2517,9 @@ static void rrc_CU_process_ue_context_setup_response(MessageDef *msg_p, instance } if (UE->ho_context == NULL) { - rrc_gNB_generate_dedicatedRRCReconfiguration(rrc, UE); + // Check if this is a re-establishment scenario by examining the CellGroupConfig + bool is_reestablishment = rrc_detect_reestablishment(cgc); + rrc_gNB_generate_dedicatedRRCReconfiguration(rrc, UE, is_reestablishment); } else { // case of handover // handling of "target CU" information @@ -2578,7 +2591,7 @@ static void rrc_delete_ue_data(gNB_RRC_UE_t *UE) if (UE->ho_context) nr_rrc_finalize_ho(UE); ASN_STRUCT_FREE(asn_DEF_NR_UE_NR_Capability, UE->UE_Capability_nr); - ASN_STRUCT_FREE(asn_DEF_NR_CellGroupConfig, UE->masterCellGroup); + free_byte_array(UE->mcg); ASN_STRUCT_FREE(asn_DEF_NR_MeasResults, UE->measResults); FREE_AND_ZERO_BYTE_ARRAY(UE->ue_cap_buffer); free_MeasConfig(UE->measConfig); @@ -2641,22 +2654,18 @@ static void rrc_CU_process_ue_context_modification_response(MessageDef *msg_p, i } if (resp->du_to_cu_rrc_info) { - NR_CellGroupConfig_t *cellGroupConfig = NULL; byte_array_t *cgc = &resp->du_to_cu_rrc_info->cell_group_config; - asn_dec_rval_t dec_rval = - uper_decode_complete(NULL, &asn_DEF_NR_CellGroupConfig, (void **)&cellGroupConfig, (uint8_t *)cgc->buf, cgc->len); - AssertFatal(dec_rval.code == RC_OK && dec_rval.consumed > 0, "Cell group config decode error\n"); /* hack for interoperability with srsRAN: ignore cell group config if empty */ /* cell group config is empty if buffer length is 2 and content is all 0 */ bool cgc_is_empty = cgc->len == 2 && cgc->buf[0] == 0 && cgc->buf[1] == 0; if (!cgc_is_empty) { - if (UE->masterCellGroup) { - ASN_STRUCT_FREE(asn_DEF_NR_CellGroupConfig, UE->masterCellGroup); - LOG_I(RRC, "UE %04x replacing existing CellGroupConfig with new one received from DU\n", UE->rnti); - } - UE->masterCellGroup = cellGroupConfig; - rrc_gNB_generate_dedicatedRRCReconfiguration(rrc, UE); + // Store the encoded CellGroupConfig for transparent forwarding + store_cgc(UE, cgc); + + // Check if this is a re-establishment scenario by examining the CellGroupConfig + bool is_reestablishment = rrc_detect_reestablishment(cgc); + rrc_gNB_generate_dedicatedRRCReconfiguration(rrc, UE, is_reestablishment); } else { LOG_W(NR_RRC, "hack: UE %d: ignore empty CellGroupConfig in UEContextModificationResponse\n", UE->rrc_ue_id); } @@ -2704,31 +2713,12 @@ static void rrc_CU_process_ue_modification_required(MessageDef *msg_p, instance_ UE->rrc_ue_id, UE->rnti); - NR_CellGroupConfig_t *cellGroupConfig = NULL; - asn_dec_rval_t dec_rval = uper_decode_complete(NULL, - &asn_DEF_NR_CellGroupConfig, - (void **)&cellGroupConfig, - (uint8_t *)required->du_to_cu_rrc_information->cellGroupConfig, - required->du_to_cu_rrc_information->cellGroupConfig_length); - if (dec_rval.code != RC_OK && dec_rval.consumed == 0) { - LOG_E(RRC, "Cell group config decode error, refusing reconfiguration\n"); - f1ap_ue_context_modif_refuse_t refuse = { - .gNB_CU_ue_id = required->gNB_CU_ue_id, - .gNB_DU_ue_id = required->gNB_DU_ue_id, - .cause = F1AP_CAUSE_PROTOCOL, - .cause_value = F1AP_CauseProtocol_transfer_syntax_error, - }; - rrc->mac_rrc.ue_context_modification_refuse(msg_p->ittiMsgHeader.originInstance, &refuse); - return; - } + // Store the encoded CellGroupConfig for transparent forwarding + uint8_t *cgc_buf = required->du_to_cu_rrc_information->cellGroupConfig; + size_t cgc_len = required->du_to_cu_rrc_information->cellGroupConfig_length; - if (UE->masterCellGroup) { - ASN_STRUCT_FREE(asn_DEF_NR_CellGroupConfig, UE->masterCellGroup); - LOG_I(RRC, "UE %d/RNTI %04x replacing existing CellGroupConfig with new one received from DU\n", UE->rrc_ue_id, UE->rnti); - } - UE->masterCellGroup = cellGroupConfig; - if (LOG_DEBUGFLAG(DEBUG_ASN1)) - xer_fprint(stdout, &asn_DEF_NR_CellGroupConfig, UE->masterCellGroup); + byte_array_t temp_cgc = {.buf = cgc_buf, .len = cgc_len}; + store_cgc(UE, &temp_cgc); /* trigger reconfiguration */ if (!UE->ongoing_reconfiguration) diff --git a/openair2/RRC/NR/rrc_gNB_mobility.c b/openair2/RRC/NR/rrc_gNB_mobility.c index c2495ad879..a2230c1146 100644 --- a/openair2/RRC/NR/rrc_gNB_mobility.c +++ b/openair2/RRC/NR/rrc_gNB_mobility.c @@ -148,8 +148,8 @@ static void nr_initiate_handover(const gNB_RRC_INST *rrc, ho_ctx->source->old_du_tunnel_config = drb->du_tunnel_config; } - 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"); + // Store the old CellGroupConfig for reestablishment + ho_ctx->source->old_cgc = copy_byte_array(ue->mcg); } LOG_A(NR_RRC, @@ -305,7 +305,7 @@ void nr_rrc_trigger_f1_ho(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue, nr_rrc_du_contain void nr_rrc_finalize_ho(gNB_RRC_UE_t *ue) { if (ue->ho_context->source) - ASN_STRUCT_FREE(asn_DEF_NR_CellGroupConfig, ue->ho_context->source->old_cellGroupConfig); + free_byte_array(ue->ho_context->source->old_cgc); free_ho_ctx(ue->ho_context); ue->ho_context = NULL; } diff --git a/openair2/RRC/NR/rrc_gNB_mobility.h b/openair2/RRC/NR/rrc_gNB_mobility.h index 54691c2edd..456730060e 100644 --- a/openair2/RRC/NR/rrc_gNB_mobility.h +++ b/openair2/RRC/NR/rrc_gNB_mobility.h @@ -50,7 +50,7 @@ typedef struct nr_ho_source_cu { rnti_t old_rnti; /// old (source) CellGroupConfig (to send the cellGroupConfig in case of /// reestablishment) - NR_CellGroupConfig_t *old_cellGroupConfig; + byte_array_t old_cgc; /// function pointer to announce the handover cancellation, e.g., /// reestablishment ho_cancel_t ho_cancel; diff --git a/openair2/RRC/NR/rrc_gNB_nsa.c b/openair2/RRC/NR/rrc_gNB_nsa.c index 3e3c462145..2241ca9468 100644 --- a/openair2/RRC/NR/rrc_gNB_nsa.c +++ b/openair2/RRC/NR/rrc_gNB_nsa.c @@ -386,18 +386,15 @@ void rrc_add_nsa_user(gNB_RRC_INST *rrc, x2ap_ENDC_sgnb_addition_req_t *m, sctp_ free_ue_context_setup_req(&req); } -static NR_RRCReconfiguration_IEs_t *get_default_reconfig(const NR_CellGroupConfig_t *secondaryCellGroup) +static NR_RRCReconfiguration_IEs_t *get_default_reconfig(const byte_array_t cgc) { NR_RRCReconfiguration_IEs_t *reconfig = calloc(1, sizeof(NR_RRCReconfiguration_IEs_t)); AssertFatal(reconfig != NULL, "out of memory\n"); - AssertFatal(secondaryCellGroup != NULL, "secondaryCellGroup is null\n"); + AssertFatal(cgc.buf != NULL, "cgc is null\n"); reconfig->radioBearerConfig = NULL; - - char scg_buffer[1024]; - asn_enc_rval_t enc_rval = uper_encode_to_buffer(&asn_DEF_NR_CellGroupConfig, NULL, (void *)secondaryCellGroup, scg_buffer, 1024); - AssertFatal(enc_rval.encoded > 0, "ASN1 message encoding failed (%s, %jd)!\n", enc_rval.failed_type->name, enc_rval.encoded); - reconfig->secondaryCellGroup = calloc(1, sizeof(*reconfig->secondaryCellGroup)); - OCTET_STRING_fromBuf(reconfig->secondaryCellGroup, (const char *)scg_buffer, (enc_rval.encoded + 7) >> 3); + // Copy the stored CellGroupConfig to the RRCReconfiguration message + reconfig->secondaryCellGroup = calloc_or_fail(1, sizeof(*reconfig->secondaryCellGroup)); + OCTET_STRING_fromBuf(reconfig->secondaryCellGroup, (const char *)cgc.buf, cgc.len); reconfig->measConfig = NULL; reconfig->lateNonCriticalExtension = NULL; reconfig->nonCriticalExtension = NULL; @@ -469,7 +466,7 @@ void rrc_add_nsa_user_resp(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, const f1ap_ue_co NR_RRCReconfiguration_t *reconfig = calloc(1, sizeof(NR_RRCReconfiguration_t)); reconfig->rrc_TransactionIdentifier = 0; reconfig->criticalExtensions.present = NR_RRCReconfiguration__criticalExtensions_PR_rrcReconfiguration; - reconfig->criticalExtensions.choice.rrcReconfiguration = get_default_reconfig(UE->masterCellGroup); + reconfig->criticalExtensions.choice.rrcReconfiguration = get_default_reconfig(UE->mcg); NR_CG_Config_t *CG_Config = generate_CG_Config(reconfig, UE->rb_config); ASN_STRUCT_FREE(asn_DEF_NR_RRCReconfiguration, reconfig); From c5787aa9a9c4da1d8ad179520667a8fcbdd4eace Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Fri, 7 Nov 2025 12:28:20 +0100 Subject: [PATCH 18/23] RRC: Trigger UE Context Setup upon re-establishment on different DU MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As per TS 38.401 §8.7: if the UE accesses a DU different from the original one during re-establishment, the CU must trigger UE Context Setup toward the new DU. This refers to steps 9-10 and steps 9'-10'. Either one or the other can be executed during re-establishment. e.g. if this happens during F1 handover, it shall trigger a UE Context Setup fallback on the CU. This commit implements the required behavior: 1. Detection mechanism: - Add `old_du_assoc_id` field to `f1_ue_data_t` to store the original DU association ID - At RRCReestablishmentRequest: Store original `du_assoc_id` in `old_du_assoc_id` and update `du_assoc_id` to the new DU - At RRCReestablishmentComplete: Compare `old_du_assoc_id` with the `assoc_id` from the Complete message to detect different DU 2. UE Context Setup trigger: - In `rrc_gNB_process_RRCReestablishmentComplete()`: When different DU is detected, call `rrc_f1_ue_context_setup_for_target_du()` to trigger UE Context Setup procedure (per TS 38.401 §8.7, NOTE on steps 9-10) upon reception of UL RRC MESSAGE TRANSFER - In `rrc_CU_process_ue_modification_required()`: Refuse UE Context Modification Required when `old_du_assoc_id != 0` to avoid conflicts with the ongoing UE Context Setup procedure (per TS 38.401 §8.7, NOTE on steps 9'-10') 3. UE Context Setup: - Replace `clone_CellGroupConfig()` with `get_cellgroup_config()` in UE Context Setup handler - Call `update_cellgroup_for_reestablishment()` during UE Context Setup when `reestablish_rlc` flag is set to prepare CellGroupConfig with reestablishRLC flags - Clean up `old_du_assoc_id` in `rrc_CU_process_ue_context_setup_response()` - Add `free_ue_context_mod_req()` call after UE Context Modification request 4. Function signature updates: - Update `handle_rrcReestablishmentComplete()` to accept `gNB_DU_ue_id` and `assoc_id` parameters, change return type from `int` to `void` - Update `rrc_gNB_process_RRCReestablishmentComplete()` to accept `assoc_id` parameter - Update `rrc_gNB_decode_dcch()` to accept and forward `assoc_id` parameter Re-establishment on different DU flow (TS 38.401 §8.7, steps 9-10): 1. UE sends RRCReestablishmentRequest to new DU 2. DU forwards to CU via UL RRC MESSAGE TRANSFER 3. CU detects different DU (`assoc_id != ue_data.du_assoc_id`): - Stores `old_du_assoc_id = ue_data.du_assoc_id` (original DU) - Updates `ue_data.du_assoc_id = assoc_id` (new DU) 4. CU sends RRCReestablishment to UE 5. UE sends RRCReestablishmentComplete to new DU 6. DU forwards to CU via UL RRC MESSAGE TRANSFER 7. CU detects different DU scenario (with old_du_assoc_id): - Triggers UE Context Setup Request to new DU via `rrc_f1_ue_context_setup_for_target_du()` 8. DU receives UE Context Setup Request: - Detects re-establishment (`UE->reestablish_rlc` flag) - Calls `update_cellgroup_for_reestablishment()` to set reestablishRLC flags - Encodes CellGroupConfig with reestablishRLC flags and sends to CU 9. CU receives UE Context Setup Response: - Stores encoded CellGroupConfig - Clears `old_du_assoc_id = 0` 10. CU sends RRC Reconfiguration with transparent CellGroupConfig to UE --- .../LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c | 12 +- openair2/RRC/NR/rrc_gNB.c | 138 ++++++++++++++---- 2 files changed, 124 insertions(+), 26 deletions(-) diff --git a/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c b/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c index f3fd5edda2..b6115e39f2 100644 --- a/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c +++ b/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c @@ -733,7 +733,8 @@ void ue_context_setup_request(const f1ap_ue_context_setup_req_t *req) AssertFatal(UE, "no UE found or could not be created, but UE Context Setup Failed not implemented\n"); resp.gNB_DU_ue_id = UE->rnti; - NR_CellGroupConfig_t *new_CellGroup = clone_CellGroupConfig(UE->CellGroup); + NR_CellGroupConfig_t *new_CellGroup = get_cellgroup_config(UE); + // Needed for DRB Setup (e.g., RLC might reduce SN size) UE->capability = ue_cap; @@ -757,6 +758,15 @@ void ue_context_setup_request(const f1ap_ue_context_setup_req_t *req) update_cellGroupConfig(new_CellGroup, UE->uid, UE->capability, &mac->radio_config, scc); } + /* During re-establishment, prepare CellGroupConfig for UE Context Setup response. + * Per TS 38.401 §8.7: when a UE re-establishes on a different DU, the CU triggers + * UE Context Setup on the new DU. The DU must respond with a CellGroupConfig that has + * reestablishRLC flags set for all RLC bearers except SRB1. This prepares the CellGroupConfig + * for transparent forwarding to the UE per TS 38.473 transparency requirements. */ + if (UE->reestablish_rlc) { + update_cellgroup_for_reestablishment(UE, new_CellGroup); + } + if (!ue_id_provided && cg_configinfo == NULL) { /* new UE: tell the UE to reestablish RLC */ struct NR_CellGroupConfig__rlc_BearerToAddModList *addmod = new_CellGroup->rlc_BearerToAddModList; diff --git a/openair2/RRC/NR/rrc_gNB.c b/openair2/RRC/NR/rrc_gNB.c index 073fc0c34f..82abc30f28 100644 --- a/openair2/RRC/NR/rrc_gNB.c +++ b/openair2/RRC/NR/rrc_gNB.c @@ -1250,7 +1250,10 @@ static void rrc_gNB_generate_RRCReestablishment(rrc_gNB_ue_context_t *ue_context } /// @brief Function tha processes RRCReestablishmentComplete message sent by the UE, after RRCReestasblishment request. -static void rrc_gNB_process_RRCReestablishmentComplete(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue_p, const uint8_t xid) +static void rrc_gNB_process_RRCReestablishmentComplete(gNB_RRC_INST *rrc, + gNB_RRC_UE_t *ue_p, + const uint8_t xid, + const sctp_assoc_t assoc_id) { LOG_I(NR_RRC, "UE %d Processing NR_RRCReestablishmentComplete from UE\n", ue_p->rrc_ue_id); @@ -1274,26 +1277,40 @@ static void rrc_gNB_process_RRCReestablishmentComplete(gNB_RRC_INST *rrc, gNB_RR /* PDCP Reestablishment of DRBs according to 5.3.5.6.5 of 3GPP TS 38.331 (over E1) */ cuup_notify_reestablishment(rrc, ue_p); - // Request DU to provide CellGroupConfig with reestablishRLC flags for re-establishment - // According to TS 38.473 transparency requirements, CU should not construct or re-encode CellGroupConfig f1_ue_data_t ue_data = cu_get_f1_ue_data(ue_p->rrc_ue_id); RETURN_IF_INVALID_ASSOC_ID(ue_data.du_assoc_id); - f1ap_ue_context_mod_req_t ue_context_modif_req = { - .gNB_CU_ue_id = ue_p->rrc_ue_id, - .gNB_DU_ue_id = ue_data.secondary_ue, - }; + bool different_du_detected = !ue_p->f1_ue_context_active; + if (different_du_detected) { + /** Handle UE access on a different DU than the original one + * Per 38.401 8.7: "If the UE accessed from a gNB-DU other than the original + * one, the gNB-CU should trigger the UE Context Setup procedure". */ + /* UE is accessing a different DU - trigger UE Context Setup per TS 38.401 §8.7 */ + nr_rrc_du_container_t *du = get_du_by_assoc_id(rrc, assoc_id); + if (du == NULL) { + LOG_E(NR_RRC, "UE %d: cannot find DU for assoc_id %d\n", ue_p->rrc_ue_id, assoc_id); + return; + } + rrc_f1_ue_context_setup_for_target_du(rrc, ue_p, du, NULL); + } else { + // Request DU to provide CellGroupConfig with reestablishRLC flags for re-establishment + // According to TS 38.473 transparency requirements, CU should not construct or re-encode CellGroupConfig + f1ap_ue_context_mod_req_t ue_context_modif_req = { + .gNB_CU_ue_id = ue_p->rrc_ue_id, + .gNB_DU_ue_id = ue_data.secondary_ue, + }; - // Request CellGroupConfig from DU in the response - ue_context_modif_req.gNB_DU_Configuration_Query = calloc_or_fail(1, sizeof(bool)); - *ue_context_modif_req.gNB_DU_Configuration_Query = true; - - /** Send UE context modification request to DU, which will respond with CellGroupConfig - * containing reestablishRLC flags for re-establishment. - * The response will be handled in rrc_CU_process_ue_context_modification_response() - * which will store the encoded CellGroupConfig and trigger RRC Reconfiguration */ - rrc->mac_rrc.ue_context_modification_request(ue_data.du_assoc_id, &ue_context_modif_req); + // Request CellGroupConfig from DU in the response + ue_context_modif_req.gNB_DU_Configuration_Query = calloc_or_fail(1, sizeof(*ue_context_modif_req.gNB_DU_Configuration_Query)); + *ue_context_modif_req.gNB_DU_Configuration_Query = true; + /** Send UE context modification request to DU, which will respond with CellGroupConfig + * containing reestablishRLC flags for re-establishment. + * The response will be handled in rrc_CU_process_ue_context_modification_response() + * which will store the encoded CellGroupConfig and trigger RRC Reconfiguration */ + rrc->mac_rrc.ue_context_modification_request(ue_data.du_assoc_id, &ue_context_modif_req); + free_ue_context_mod_req(&ue_context_modif_req); + } } int nr_rrc_reconfiguration_req(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue_p, const int dl_bwp_id, const int ul_bwp_id) @@ -1546,11 +1563,46 @@ static void rrc_handle_RRCReestablishmentRequest(gNB_RRC_INST *rrc, if (assoc_id != ue_data.du_assoc_id) { /* Different DU scenario - physCellId differs because UE is re-establishing on a different DU. * Re-establishment is allowed, UE Context Setup will be triggered at RRCReestablishmentComplete. */ + // Store original du_assoc_id and du_ue_id for immediate release + const sctp_assoc_t old_du_assoc_id = ue_data.du_assoc_id; + const uint32_t old_du_ue_id = ue_data.secondary_ue; LOG_I(NR_RRC, - "UE %d: Re-establishment on different DU (physCellId %ld from old cell != %d from new DU)\n", + "UE %d: Re-establishment on different DU (physCellId %ld from old cell != %d from new DU, du_assoc_id %d -> %d)\n", UE->rrc_ue_id, physCellId, - cell_info->nr_pci); + cell_info->nr_pci, + ue_data.du_assoc_id, + assoc_id); + + // Update to new DU + ue_data.du_assoc_id = assoc_id; + + /* Release old DU context immediately per 3GPP TS 38.473 §8.3.3.2: + * "Interactions with UE Context Setup procedure: + * The UE Context Release procedure may be performed before the UE Context Setup + * procedure to release an existing UE-associated logical F1-connection and related + * resources in the gNB-DU" */ + DevAssert(old_du_ue_id != 0); + RETURN_IF_INVALID_ASSOC_ID(old_du_assoc_id); + f1ap_ue_context_rel_cmd_t cmd = { + .gNB_CU_ue_id = UE->rrc_ue_id, + .gNB_DU_ue_id = old_du_ue_id, + .cause = F1AP_CAUSE_RADIO_NETWORK, + .cause_value = F1AP_CauseRadioNetwork_normal_release, + }; + rrc->mac_rrc.ue_context_release_command(old_du_assoc_id, &cmd); + LOG_I(NR_RRC, + "UE %d: Re-establishment on different DU - releasing old DU immediately (assoc_id %d, DU UE ID %u)\n", + UE->rrc_ue_id, + old_du_assoc_id, + old_du_ue_id); + + // on a new DU: we will have to send f1_ue_context again + UE->f1_ue_context_active = false; + + // Update f1_ue_data after release + bool success = cu_update_f1_ue_data(UE->rrc_ue_id, &ue_data); + DevAssert(success); } else { /* Same DU but different physCellId - "too fast movement" scenario: * UE was moving from previous cell so quickly that RRCReestablishment for previous cell was received in this cell */ @@ -1763,17 +1815,21 @@ static void rrc_gNB_process_MeasurementReport(gNB_RRC_INST *rrc, gNB_RRC_UE_t *U LOG_E(NR_RRC, "Incoming Report Type: %d is not supported! \n", report_config->choice.reportConfigNR->reportType.present); } -static int handle_rrcReestablishmentComplete(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, const NR_RRCReestablishmentComplete_t *cplt) +static void handle_rrcReestablishmentComplete(gNB_RRC_INST *rrc, + gNB_RRC_UE_t *UE, + const uint32_t gNB_DU_ue_id, + const NR_RRCReestablishmentComplete_t *cplt, + const sctp_assoc_t assoc_id) { NR_RRCReestablishmentComplete__criticalExtensions_PR p = cplt->criticalExtensions.present; if (p != NR_RRCReestablishmentComplete__criticalExtensions_PR_rrcReestablishmentComplete) { LOG_E(NR_RRC, "UE %d: expected presence of rrcReestablishmentComplete, but message has %d\n", UE->rrc_ue_id, p); - return -1; + return; } - rrc_gNB_process_RRCReestablishmentComplete(rrc, UE, cplt->rrc_TransactionIdentifier); + + rrc_gNB_process_RRCReestablishmentComplete(rrc, UE, cplt->rrc_TransactionIdentifier, assoc_id); UE->ue_reestablishment_counter++; - return 0; } /** @@ -2079,7 +2135,7 @@ static void rrc_gNB_generate_UECapabilityEnquiry(gNB_RRC_INST *rrc, gNB_RRC_UE_t nr_rrc_transfer_protected_rrc_message(rrc, ue, DL_SCH_LCID_DCCH, msg_id, buffer, size); } -static int rrc_gNB_decode_dcch(gNB_RRC_INST *rrc, const f1ap_ul_rrc_message_t *msg) +static int rrc_gNB_decode_dcch(gNB_RRC_INST *rrc, const f1ap_ul_rrc_message_t *msg, const sctp_assoc_t assoc_id) { /* we look up by CU UE ID! Do NOT change back to RNTI! */ rrc_gNB_ue_context_t *ue_context_p = rrc_gNB_get_ue_context(rrc, msg->gNB_CU_ue_id); @@ -2184,7 +2240,8 @@ static int rrc_gNB_decode_dcch(gNB_RRC_INST *rrc, const f1ap_ul_rrc_message_t *m case NR_UL_DCCH_MessageType__c1_PR_rrcReestablishmentComplete: LOG_UE_UL_EVENT(UE, "Received RRCReestablishmentComplete\n"); - handle_rrcReestablishmentComplete(rrc, UE, ul_dcch_msg->message.choice.c1->choice.rrcReestablishmentComplete); + const NR_RRCReestablishmentComplete_t *rc = ul_dcch_msg->message.choice.c1->choice.rrcReestablishmentComplete; + handle_rrcReestablishmentComplete(rrc, UE, msg->gNB_DU_ue_id, rc, assoc_id); break; default: @@ -2706,6 +2763,37 @@ static void rrc_CU_process_ue_modification_required(MessageDef *msg_p, instance_ return; } + /* Check if UE is accessing a different DU than its current one */ + f1_ue_data_t ue_data = cu_get_f1_ue_data(UE->rrc_ue_id); + RETURN_IF_INVALID_ASSOC_ID(ue_data.du_assoc_id); + + /** Refuse UE Context Modification Required from any DU if: + * UE Context Setup Request was sent but Setup Response hasn't arrived yet. + * This includes the case where UE is re-establishing on a different DU. The CU has + * already triggered UE Context Setup on the new DU (per TS 38.401 §8.7), and Setup Response + * is pending. Modification Required from any DU during this transition should be refused + * to avoid conflicts with the ongoing Setup procedure. + * + * Note: according to TS 38.401 §8.7 "it is assumed that the UE accessed the original gNB-DU + * where the UE context is available for that UE, and either steps 9-10 or steps 9’-10’ + * may be executed or both could be skipped." + * By design, CU-CP will trigger UE Context Setup Modification Request and DU does not send + * Modification Required during re-establishment. */ + if (!UE->f1_ue_context_active) { + LOG_W(NR_RRC, + "UE %d: UE Context Modification Required received while UE Context Setup is pending (CU UE ID %d), refusing\n", + UE->rrc_ue_id, + required->gNB_CU_ue_id); + f1ap_ue_context_modif_refuse_t refuse = { + .gNB_CU_ue_id = required->gNB_CU_ue_id, + .gNB_DU_ue_id = required->gNB_DU_ue_id, + .cause = F1AP_CAUSE_RADIO_NETWORK, + .cause_value = F1AP_CauseRadioNetwork_interaction_with_other_procedure, + }; + rrc->mac_rrc.ue_context_modification_refuse(msg_p->ittiMsgHeader.originInstance, &refuse); + return; + } + if (required->du_to_cu_rrc_information && required->du_to_cu_rrc_information->cellGroupConfig) { gNB_RRC_UE_t *UE = &ue_context_p->ue_context; LOG_I(RRC, @@ -3152,7 +3240,7 @@ void *rrc_gnb_task(void *args_p) { /* Messages from PDCP */ /* From DU -> CU */ case F1AP_UL_RRC_MESSAGE: - rrc_gNB_decode_dcch(RC.nrrrc[instance], &F1AP_UL_RRC_MESSAGE(msg_p)); + rrc_gNB_decode_dcch(RC.nrrrc[instance], &F1AP_UL_RRC_MESSAGE(msg_p), msg_p->ittiMsgHeader.originInstance); free_ul_rrc_message_transfer(&F1AP_UL_RRC_MESSAGE(msg_p)); break; From fc8e537f20cc4faf5616b12a0a36c948b4143f55 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Fri, 16 Jan 2026 20:54:33 +0100 Subject: [PATCH 19/23] RRC: align SRB and PDCP re-establishment timing with TS 38.331 specifications This commit fixes the timing of SRB and DRB PDCP re-establishment during RRC connection re-establishment to align with 3GPP TS 38.331 specifications. Key Changes: 1. SRB1 activation and re-establishment (TS 38.331 5.3.7.4): - Move SRB1 activation to rrc_gNB_generate_RRCReestablishment() - Use SRB1 constant instead of magic number 1 for clarity - Remove redundant security configuration loop (handled by nr_pdcp_reestablishment internally) 2. SRB2 re-establishment (TS 38.331 5.3.5.6.3 SRB addition/modification): - Move SRB2 PDCP re-establishment from rrc_gNB_process_RRCReestablishmentComplete() to rrc_gNB_generate_dedicatedRRCReconfiguration(): per spec: SRB2 should be re-established in the first RRCReconfiguration after re-establishment (with reestablishPDCP=true flag) 3. DRB re-establishment (TS 38.331 5.3.5.6.5 DRB addition/modification): - Move cuup_notify_reestablishment() from rrc_gNB_process_RRCReestablishmentComplete() to rrc_gNB_generate_dedicatedRRCReconfiguration(). e.g at the first reconfiguration after reestablishment - Ensures CU-UP is notified at the correct time --- openair2/RRC/NR/rrc_gNB.c | 55 +++++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 28 deletions(-) diff --git a/openair2/RRC/NR/rrc_gNB.c b/openair2/RRC/NR/rrc_gNB.c index 82abc30f28..7f41925d26 100644 --- a/openair2/RRC/NR/rrc_gNB.c +++ b/openair2/RRC/NR/rrc_gNB.c @@ -918,6 +918,9 @@ byte_array_t rrc_gNB_encode_RRCReconfiguration(gNB_RRC_INST *rrc, gNB_RRC_UE_t * return msg; } +/* Forward declaration */ +static void cuup_notify_reestablishment(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue_p); + /** @brief Generate and send RRC Reconfiguration message. * Handles both normal reconfiguration and first reconfiguration after re-establishment. * @param rrc RRC instance @@ -960,6 +963,22 @@ static void rrc_gNB_generate_dedicatedRRCReconfiguration(gNB_RRC_INST *rrc, gNB_ return; } + /* Per TS 38.331 5.3.5.6.3, re-establish SRB2 PDCP when sending the first + * RRCReconfiguration after reestablishment */ + if (is_reestablishment) { + if (ue_p->Srb[SRB2].Active) { + nr_pdcp_entity_security_keys_and_algos_t security_parameters = {0}; + security_parameters.ciphering_algorithm = ue_p->ciphering_algorithm; + security_parameters.integrity_algorithm = ue_p->integrity_algorithm; + nr_derive_key(RRC_ENC_ALG, ue_p->ciphering_algorithm, ue_p->kgnb, security_parameters.ciphering_key); + nr_derive_key(RRC_INT_ALG, ue_p->integrity_algorithm, ue_p->kgnb, security_parameters.integrity_key); + nr_pdcp_reestablishment(ue_p->rrc_ue_id, SRB2, true, &security_parameters); + } + /* Per TS 38.331 5.3.5.6.5, re-establish PDCP for all DRBs when sending the first + * RRCReconfiguration after RRC connection re-establishment (over E1 to CU-UP) */ + cuup_notify_reestablishment(rrc, ue_p); + } + LOG_UE_DL_EVENT(ue_p, "Generate RRCReconfiguration%s (bytes %ld, xid %d)\n", is_reestablishment ? " after re-establishment" : "", @@ -1215,16 +1234,14 @@ static void rrc_gNB_generate_RRCReestablishment(rrc_gNB_ue_context_t *ue_context security_parameters.integrity_algorithm = ue_p->integrity_algorithm; security_parameters.ciphering_algorithm = 0; - /* SRBs */ - for (int srb_id = 1; srb_id < NR_NUM_SRB; srb_id++) { - if (ue_p->Srb[srb_id].Active) - nr_pdcp_config_set_security(ue_p->rrc_ue_id, srb_id, true, &security_parameters); - } - /* Re-establish PDCP for SRB1, according to 5.3.7.4 of 3GPP TS 38.331 */ - nr_pdcp_reestablishment(ue_p->rrc_ue_id, - 1, - true, - &security_parameters); + /* Re-establish PDCP for SRB1, according to 5.3.7.4 of 3GPP TS 38.331, + * and configure security via set_security internally. + * Note: SRB2 is NOT re-established here. Per TS 38.331 5.3.5.6.3, SRB2 + * should be re-established in the first RRCReconfiguration after + * RRCReestablishmentComplete */ + ue_p->Srb[SRB1].Active = 1; + nr_pdcp_reestablishment(ue_p->rrc_ue_id, SRB1, true, &security_parameters); + /* F1AP DL RRC Message Transfer */ f1_ue_data_t ue_data = cu_get_f1_ue_data(ue_p->rrc_ue_id); RETURN_IF_INVALID_ASSOC_ID(ue_data.du_assoc_id); @@ -1259,24 +1276,6 @@ static void rrc_gNB_process_RRCReestablishmentComplete(gNB_RRC_INST *rrc, ue_p->xids[xid] = RRC_ACTION_NONE; - ue_p->Srb[1].Active = 1; - - /* Re-establish SRB2 according to clause 5.3.5.6.3 of 3GPP TS 38.331 - * (SRB1 is re-established with RRCReestablishment message) - */ - int srb_id = 2; - if (ue_p->Srb[srb_id].Active) { - nr_pdcp_entity_security_keys_and_algos_t security_parameters; - security_parameters.ciphering_algorithm = ue_p->ciphering_algorithm; - security_parameters.integrity_algorithm = ue_p->integrity_algorithm; - nr_derive_key(RRC_ENC_ALG, ue_p->ciphering_algorithm, ue_p->kgnb, security_parameters.ciphering_key); - nr_derive_key(RRC_INT_ALG, ue_p->integrity_algorithm, ue_p->kgnb, security_parameters.integrity_key); - - nr_pdcp_reestablishment(ue_p->rrc_ue_id, srb_id, true, &security_parameters); - } - /* PDCP Reestablishment of DRBs according to 5.3.5.6.5 of 3GPP TS 38.331 (over E1) */ - cuup_notify_reestablishment(rrc, ue_p); - f1_ue_data_t ue_data = cu_get_f1_ue_data(ue_p->rrc_ue_id); RETURN_IF_INVALID_ASSOC_ID(ue_data.du_assoc_id); From 84a49e93adb00933835736c25741a2d3e1c5a31b Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Wed, 3 Dec 2025 22:55:17 +0100 Subject: [PATCH 20/23] DU: Handle missing old UE context in DL RRC Message Transfer During F1 handover reestablishment, the CU may forward a DL RRC Message Transfer to the target DU with old_gNB_DU_ue_id included, even though the target DU does not have the old UE context yet. The code previously used AssertFatal when the old UE context was not found, causing a crash: CU claims we should know UE %04x, but we don't According to TS 38.473, the gNB-DU shall release the old gNB-DU UE F1AP ID only "if the gNB-DU identifies the UE-associated logical F1-connection". If there is no matching connection, there is nothing to release. However, the primary purpose of DL RRC MESSAGE TRANSFER is to deliver the RRC message to the UE. The RRC message delivery is independent of whether the old UE context can be retrieved. Per TS 38.473, the RRC message must be delivered to the new UE regardless of old context retrieval status. Additionally, per TS 38.331 5.3.7.2 and 5.3.7.4, during reestablishment: - The UE releases spCellConfig, which must be removed from CellGroupConfig - SRB1 RLC must be reconfigured with specific parameters These operations are required for the new UE during reestablishment and are independent of whether the old UE context can be retrieved. Fixes: - Check if old UE context exists before accessing it - If old UE does not exist, log error and clean up any stale F1 UE data, but continue to deliver the RRC message - Restructure code so context transfer only happens when oldUE exists - Keep GTP-U update and RLC context transfer inside oldUE check (they require old UE context to exist) - Move spCellConfig handling and reestablish_rlc flag to execute regardless during re-establishemnt (per TS 38.331 5.3.7.2) - Move SRB1 RLC reconfiguration outside the oldUE check so it executes regardless during re-establishment (per TS 38.331 5.3.7.4) - Add function documentation and improve spec references in comments --- .../LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c | 95 ++++++++++++------- 1 file changed, 59 insertions(+), 36 deletions(-) diff --git a/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c b/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c index b6115e39f2..7229b98fd0 100644 --- a/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c +++ b/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c @@ -1006,6 +1006,11 @@ void ue_context_release_command(const f1ap_ue_context_rel_cmd_t *cmd) NR_SCHED_UNLOCK(&mac->sched_lock); } +/** @brief Process a DL RRC MESSAGE TRANSFER. Handles delivery of an RRC message to a UE + * via the F1AP DL RRC MESSAGE TRANSFER procedure, as specified in TS 38.473. This procedure + * is also responsible for re-establishing UE context when required (e.g., during RRC connection + * reestablishment). + * @param dl_rrc Pointer to the DL RRC MESSAGE TRANSFER data structure. */ void dl_rrc_message_transfer(const f1ap_dl_rrc_message_t *dl_rrc) { LOG_D(NR_MAC, @@ -1033,50 +1038,71 @@ void dl_rrc_message_transfer(const f1ap_dl_rrc_message_t *dl_rrc) DevAssert(success); } - - /* if we get the old-gNB-DU-UE-ID, this means there is a reestablishment - * ongoing. */ + /* Per TS 38.473: "The DL RRC MESSAGE TRANSFER message shall include, if available, + * the old gNB-DU UE F1AP ID IE so that the gNB-DU can retrieve the existing UE context + * in RRC connection reestablishment procedure, as defined in TS 38.401" + * + * "If the gNB-DU identifies the UE-associated logical F1-connection by the gNB-DU UE F1AP ID + * IE in the DL RRC MESSAGE TRANSFER message and the old gNB-DU UE F1AP ID IE is included, + * it shall release the old gNB-DU UE F1AP ID and the related configurations associated + * with the old gNB-DU UE F1AP ID." */ if (dl_rrc->old_gNB_DU_ue_id != NULL) { AssertFatal(*dl_rrc->old_gNB_DU_ue_id != dl_rrc->gNB_DU_ue_id, "logic bug: current and old gNB DU UE ID cannot be the same\n"); - /* 38.401 says: "Find UE context based on old gNB-DU UE F1AP ID, replace - * old C-RNTI/PCI with new C-RNTI/PCI". Below, we do the inverse: we keep - * the new UE context (with new C-RNTI), but set up everything to reuse the - * old config. */ NR_UE_info_t *oldUE = find_nr_UE(&mac->UE_info, *dl_rrc->old_gNB_DU_ue_id); - AssertFatal(oldUE, "CU claims we should know UE %04x, but we don't\n", *dl_rrc->old_gNB_DU_ue_id); - pthread_mutex_lock(&mac->sched_lock); - uid_t temp_uid = UE->uid; - UE->uid = oldUE->uid; - oldUE->uid = temp_uid; - for (int i = 1; i < seq_arr_size(&oldUE->UE_sched_ctrl.lc_config); ++i) { - const nr_lc_config_t *c = seq_arr_at(&oldUE->UE_sched_ctrl.lc_config, i); - nr_lc_config_t new = *c; - new.suspended = true; - nr_mac_add_lcid(&UE->UE_sched_ctrl, &new); + if (oldUE == NULL) { + /* No matching UE-associated logical F1-connection for the old gNB-DU UE F1AP ID. + * Per TS 38.473, if there's no matching connection, there's nothing to release. */ + LOG_I(NR_MAC, + "DL RRC Message Transfer: old gNB-DU UE F1AP ID %04x has no matching UE-associated logical F1-connection, nothing to " + "release\n", + *dl_rrc->old_gNB_DU_ue_id); + /* Clean up any F1 UE data associated with the old gNB-DU UE F1AP ID */ + if (du_exists_f1_ue_data(*dl_rrc->old_gNB_DU_ue_id)) { + du_remove_f1_ue_data(*dl_rrc->old_gNB_DU_ue_id); + } + } else { + /* Per TS 38.401: "Find UE context based on old gNB-DU UE F1AP ID, replace + * old C-RNTI/PCI with new C-RNTI/PCI". Below, we do the inverse: we keep + * the new UE context (with new C-RNTI), but set up everything to reuse the + * old config. */ + pthread_mutex_lock(&mac->sched_lock); + uid_t temp_uid = UE->uid; + UE->uid = oldUE->uid; + oldUE->uid = temp_uid; + for (int i = 1; i < seq_arr_size(&oldUE->UE_sched_ctrl.lc_config); ++i) { + const nr_lc_config_t *c = seq_arr_at(&oldUE->UE_sched_ctrl.lc_config, i); + nr_lc_config_t new = *c; + new.suspended = true; + nr_mac_add_lcid(&UE->UE_sched_ctrl, &new); + } + ASN_STRUCT_FREE(asn_DEF_NR_CellGroupConfig, UE->CellGroup); + UE->CellGroup = oldUE->CellGroup; + oldUE->CellGroup = NULL; + ASN_STRUCT_FREE(asn_DEF_NR_UE_NR_Capability, UE->capability); + UE->capability = oldUE->capability; + oldUE->capability = NULL; + UE->mac_stats = oldUE->mac_stats; + UE->measgap_config = oldUE->measgap_config; + UE->local_bwp_id = oldUE->local_bwp_id; + mac_remove_nr_ue(mac, *dl_rrc->old_gNB_DU_ue_id); + pthread_mutex_unlock(&mac->sched_lock); + nr_rlc_remove_ue(dl_rrc->gNB_DU_ue_id); + nr_rlc_update_id(*dl_rrc->old_gNB_DU_ue_id, dl_rrc->gNB_DU_ue_id); + instance_t f1inst = get_f1_gtp_instance(); + if (f1inst >= 0) // we actually use F1-U + gtpv1u_update_ue_id(f1inst, *dl_rrc->old_gNB_DU_ue_id, dl_rrc->gNB_DU_ue_id); } - ASN_STRUCT_FREE(asn_DEF_NR_CellGroupConfig, UE->CellGroup); - UE->CellGroup = oldUE->CellGroup; - oldUE->CellGroup = NULL; - ASN_STRUCT_FREE(asn_DEF_NR_UE_NR_Capability, UE->capability); - UE->capability = oldUE->capability; - oldUE->capability = NULL; - UE->mac_stats = oldUE->mac_stats; - UE->measgap_config = oldUE->measgap_config; - UE->local_bwp_id = oldUE->local_bwp_id; - /* 38.331 5.3.7.2 says that the UE releases the spCellConfig, so we drop it + /* Per TS 38.331 5.3.7.2: the UE releases the spCellConfig, so we drop it * from the current configuration. It will be reapplied when the * reconfiguration has succeeded (indicated by the CU) */ asn_copy(&asn_DEF_NR_CellGroupConfig, (void **)&UE->reconfigCellGroup, UE->CellGroup); ASN_STRUCT_FREE(asn_DEF_NR_SpCellConfig, UE->CellGroup->spCellConfig); UE->CellGroup->spCellConfig = NULL; UE->reestablish_rlc = true; - mac_remove_nr_ue(mac, *dl_rrc->old_gNB_DU_ue_id); - pthread_mutex_unlock(&mac->sched_lock); - nr_rlc_remove_ue(dl_rrc->gNB_DU_ue_id); - nr_rlc_update_id(*dl_rrc->old_gNB_DU_ue_id, dl_rrc->gNB_DU_ue_id); - /* 38.331 clause 5.3.7.4: apply gNB RLC configuration for SRB1 to match the UE RLC configuration defined in 9.2.1 */ - nr_rlc_configuration_t rlc_configuration = mac->rlc_config; // use configuration file values for timers t_poll_retransmit, t_reassembly and t_status_prohibit + /* Per TS 38.331 clause 5.3.7.4: apply gNB RLC configuration for SRB1 to match the UE RLC configuration defined in 9.2.1. + * Use configuration file values for timers t_poll_retransmit, t_reassembly and t_status_prohibit */ + nr_rlc_configuration_t rlc_configuration = mac->rlc_config; rlc_configuration.srb.poll_pdu = -1; rlc_configuration.srb.poll_byte = -1; rlc_configuration.srb.max_retx_threshold = 8; @@ -1084,9 +1110,6 @@ void dl_rrc_message_transfer(const f1ap_dl_rrc_message_t *dl_rrc) NR_RLC_Config_t *rlc_Config = nr_srb_config(&rlc_configuration); nr_rlc_reconfigure_entity(dl_rrc->gNB_DU_ue_id, 1, rlc_Config); ASN_STRUCT_FREE(asn_DEF_NR_RLC_Config, rlc_Config); - instance_t f1inst = get_f1_gtp_instance(); - if (f1inst >= 0) // we actually use F1-U - gtpv1u_update_ue_id(f1inst, *dl_rrc->old_gNB_DU_ue_id, dl_rrc->gNB_DU_ue_id); } /* the DU ue id is the RNTI */ From d401cf0d31cc1a876f2bfbab0438497d5378512d Mon Sep 17 00:00:00 2001 From: luis_pereira87 Date: Thu, 22 Jan 2026 13:57:05 +0000 Subject: [PATCH 21/23] Prevent CU to process UE context release requests from DUs for UEs with outdated or unexpected association ids --- openair2/RRC/NR/rrc_gNB.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/openair2/RRC/NR/rrc_gNB.c b/openair2/RRC/NR/rrc_gNB.c index 7f41925d26..2263190191 100644 --- a/openair2/RRC/NR/rrc_gNB.c +++ b/openair2/RRC/NR/rrc_gNB.c @@ -2633,6 +2633,16 @@ static void rrc_CU_process_ue_context_release_request(MessageDef *msg_p, sctp_as // if we receive the release request from the target DU (regardless if // successful), we assume it is "genuine" and ask the AMF to release nr_rrc_finalize_ho(UE); + } else { + f1_ue_data_t ue_data = cu_get_f1_ue_data(UE->rrc_ue_id); + if (ue_data.du_assoc_id != assoc_id) { + LOG_W(NR_RRC, + "UE context release request from unexpected DU with assoc_id %d for rrc_ue_id %d with du_assoc_id %d\n", + assoc_id, + UE->rrc_ue_id, + ue_data.du_assoc_id); + return; + } } /* TODO: marshall types correctly */ From 3c35bad530ba9f6e117b021dfa967b21fd46aa4e Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Thu, 8 Jan 2026 00:20:51 +0100 Subject: [PATCH 22/23] Doc: update RRC re-establishment documentation --- doc/RRC/rrc-dev.md | 55 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 45 insertions(+), 10 deletions(-) diff --git a/doc/RRC/rrc-dev.md b/doc/RRC/rrc-dev.md index 534d55be37..f5c4956c9d 100644 --- a/doc/RRC/rrc-dev.md +++ b/doc/RRC/rrc-dev.md @@ -175,6 +175,12 @@ with the reestablishment procedure; if any check fails, the CU-CP tries to release the old UE at the AMF, and continues with the "normal" connection setup (registration/service request) described above. +The re-establishment flow implements transparent forwarding of CellGroupConfig +per TS 38.473: the CU requests CellGroupConfig from the DU via UE Context +Modification Request with `gNB_DU_Configuration_Query=true`, and the DU responds +with an encoded CellGroupConfig that the CU forwards to the UE without +decode/re-encode cycles. + ```mermaid sequenceDiagram participant ue as UE @@ -185,24 +191,45 @@ sequenceDiagram ue->>du: Msg1/Preamble du->>ue: Msg2/RAR - ue->>du: Msg3 w/ RRC Reestabishment Request (old C-RNTI/PCI) + ue->>du: Msg3 w/ RRC Reestablishment Request (old C-RNTI/PCI) Note over du: DU creates UE context for new RNTI du->>cucp: F1AP Initial UL RRC Msg Tr (RRC Reestab Req, in Msg3) Note over cucp: rrc_handle_RRCReestablishmentRequest() - alt UE known and at the original DU - Note over cucp: rrc_gNB_generate_RRCReestablishment()
SRB1 PDCP reestablishment - cucp->>du: F1AP DL RRC Msg Tr (old gNB-DU-UE ID) - Note over du: Reuse configuration of and delete old UE
(in dl_rrc_message_transfer()) + alt UE known + opt PCI mismatch AND Association ID mismatch + Note over cucp: Detect different DU (assoc_id != du_assoc_id)
Update du_assoc_id, set f1_ue_context_active=false + end + Note over cucp: rrc_gNB_generate_RRCReestablishment()
SRB1 PDCP reestablishment + cucp->>du: F1AP DL RRC Msg Tr (gNB-DU-UE ID) + Note over du: dl_rrc_message_transfer() + alt Old UE found + Note over du: Reuse configuration of and delete old UE + else Old UE not found + Note over du: do nothing + end + Note over du: Set reestablishRLC=true + Note over du: - Store CellGroup in reconfigCellGroup
- Remove spCellConfig from CellGroup du->>ue: RRC Reestablishment ue->>du: RRC Reestablishment Complete du->>cucp: F1AP UL RRC Msg Tr (RRC Reestab Complete) Note over cucp: handle_rrcReestablishmentComplete() - Note over cucp: cuup_notify_reestablishment() - cucp->>cuup: E1AP Bearer Context Modification Req - cucp->>ue: F1AP DL RRC Msg Transfer (RRC Reconfiguration) - cuup->>cucp: E1AP Bearer Context Modification Resp + Note over cucp: rrc_gNB_process_RRCReestablishmentComplete()
Re-establish SRB2 PDCP + Note over cucp: cuup_notify_reestablishment()
Notify CU-UP for DRB re-establishment + cucp->>cuup: E1AP Bearer Context Modification Req + cuup->>cucp: E1AP Bearer Context Modification Resp (asynchronous) + alt on the original DU + Note over cucp: Send UE Context Modification Request
with gNB_DU_Configuration_Query=true + cucp->>du: F1AP UE Context Modification Req + du->>cucp: F1AP UE Context Modification Resp
(CellGroupConfig with spCellConfig and reestablishRLC flags) + else on a different DU + Note over cucp: Detect different DU (!f1_ue_context_active)
Trigger UE Context Setup on new DU + cucp->>du: F1AP UE Context Setup Request
+ du->>cucp: F1AP UE Context Setup Response
(CellGroupConfig with spCellConfig and reestablishRLC flags) + end + Note over cucp: rrc_CU_process_ue_context_setup/modification_response()
Detect re-establishment via rrc_detect_reestablishment()
rrc_gNB_generate_dedicatedRRCReconfiguration()
(with is_reestablishment=true) + cucp->>ue: F1AP DL RRC Msg Transfer (RRC Reconfiguration
with transparent CellGroupConfig) ue->>cucp: F1AP UL RRC Msg Transfer (RRC Reconfiguration Complete) - Note over cucp: handle_rrcReestablishmentComplete() + Note over cucp: handle_rrcReconfigurationComplete() else Fallback* cucp->>amf: NGAP UE Context Release Request Note over cucp: rrc_gNB_generate_RRCSetup() @@ -210,6 +237,14 @@ sequenceDiagram end ``` +Note on re-establishment on different DU: The "UE known and on a different DU" +case is shown in the sequence diagram above. When a UE re-establishes on a +different DU than the original one (detected by comparing `du_assoc_id` at +RRCReestablishmentRequest), the CU updates `du_assoc_id` and sets +`f1_ue_context_active=false`. At RRCReestablishmentComplete, if +`f1_ue_context_active` is false, the CU triggers UE Context Setup on the new +DU per TS 38.401 §8.7. + ### Inter-DU Handover (F1) The basic handover (HO) structure is as follows. In order to support various From bc62e8abca09180fd2efb15cc182c1bf27b6a92f Mon Sep 17 00:00:00 2001 From: luis_pereira87 Date: Wed, 21 Jan 2026 14:03:35 +0000 Subject: [PATCH 23/23] CI: upgrade RAN-SA-Handover-CN5G F1 handover test to test Reestablishment on a different DU - increase requirements for Event A3 trigger to avoid unwanted handovers and allow testing reestablishment on different DUs - increase ping_packetloss_threshold from 5 to 30 because we are forcing a reestablishment, thus, packet loss will inevitably happen - further adjustments to avoid A3 Events --- .../conf_files/neighbour-config-ho.conf | 6 +- .../container_sa_f1_ho_b210_quectel.xml | 131 ++++++++++++++---- .../container_sa_n2_ho_b210_quectel.xml | 10 +- 3 files changed, 111 insertions(+), 36 deletions(-) diff --git a/ci-scripts/conf_files/neighbour-config-ho.conf b/ci-scripts/conf_files/neighbour-config-ho.conf index 07d58e14e5..2b5bbb02bd 100644 --- a/ci-scripts/conf_files/neighbour-config-ho.conf +++ b/ci-scripts/conf_files/neighbour-config-ho.conf @@ -47,8 +47,8 @@ nr_measurement_configuration = { A3 = ({ physCellId = -1; #Default - offset = 10; - hysteresis = 0; - time_to_trigger = 1 + offset = 16; + hysteresis = 1; + time_to_trigger = 10; }) }; diff --git a/ci-scripts/xml_files/container_sa_f1_ho_b210_quectel.xml b/ci-scripts/xml_files/container_sa_f1_ho_b210_quectel.xml index 2804e3c14e..53f7e006d9 100644 --- a/ci-scripts/xml_files/container_sa_f1_ho_b210_quectel.xml +++ b/ci-scripts/xml_files/container_sa_f1_ho_b210_quectel.xml @@ -59,9 +59,9 @@ Custom_Command - Set attenuator to 0 for DU0 and 17 for DU1 + Set attenuator to 0 for DU0 and 22 for DU1 rocket - python3 %%workspace%%/ci-scripts/attenuatorctl.py -s 1,2 0 -s 3,4 25 + python3 %%workspace%%/ci-scripts/attenuatorctl.py -s 1,2 0 -s 3,4 22 @@ -76,8 +76,8 @@ Deploy_Object Deploy gNB-DU0 (TDD/Band77/20MHz/B210) in a container ci-scripts/yaml_files/5g_sa_f1_b210_ho - oai-du0 groot + oai-du0 @@ -94,26 +94,11 @@ raspix - - Ping - Ping: 100pings in 20sec - raspix - oc-cn5g-00103-ho - -c 100 -i 0.2 - 5 - - - - IdleSleep - Sleep - 5 - - Custom_Command - Attenuate DU0 by 15 and remove the attenuation on DU1 to trigger Handover - rocket - python3 %%workspace%%/ci-scripts/attenuatorctl.py -r 1,2 15 -r 3,4 0 -d 5 + UE connected to DU0? + groot + echo ci fetch_du_by_ue_id | ncat 127.0.0.1 9090 | grep "3584" @@ -122,7 +107,30 @@ raspix oc-cn5g-00103-ho -c 50 -i 0.2 - 5 + 30 + + + + Custom_Command + Set attenuation 5 on DU 0 and remove attenuation on DU 1 to test Reestablishment on a different DU (DU 1) + rocket + python3 %%workspace%%/ci-scripts/attenuatorctl.py -r 1,2 5 -r 3,4 0 -d 5 + + + + Custom_Command + Trigger Reestablishment (on DU 0) + groot + echo ci force_reestab | ncat 127.0.0.1 9091 | grep -E 'Reset RLC counters of UE RNTI [0-9a-f]{4} to trigger reestablishment' + + + + Ping + Ping: 50pings in 10sec + raspix + oc-cn5g-00103-ho + -c 50 -i 0.2 + 30 @@ -134,9 +142,23 @@ Custom_Command - Attenuate DU1 by 17dB and remove attenuation on DU0 to trigger Handover + Verify Reestablishment (on CU) + groot + echo ci get_reestab_count | ncat 127.0.0.1 9090 | grep -E 'UE RNTI [0-9a-f]{4} reestab 1' + + + + Custom_Command + Remove attenuation on DU 0 and set attenuation on DU 1 to 5 to test Reestablishment on a different DU (DU 0) rocket - python3 %%workspace%%/ci-scripts/attenuatorctl.py -r 1,2 0 -r 3,4 25 -d 5 + python3 %%workspace%%/ci-scripts/attenuatorctl.py -r 1,2 0 -r 3,4 5 -d 5 + + + + Custom_Command + Trigger Reestablishment (on DU 1) + rocket + echo ci force_reestab | ncat 127.0.0.1 9091 | grep -E 'Reset RLC counters of UE RNTI [0-9a-f]{4} to trigger reestablishment' @@ -145,7 +167,7 @@ raspix oc-cn5g-00103-ho -c 50 -i 0.2 - 5 + 30 @@ -157,9 +179,16 @@ Custom_Command - Attenuate DU0 by 15 and remove the attenuation on DU1 to trigger Handover + Verify Reestablishment (on CU) + groot + echo ci get_reestab_count | ncat 127.0.0.1 9090 | grep -E 'UE RNTI [0-9a-f]{4} reestab 2' + + + + Custom_Command + Attenuate DU0 by 22 and remove the attenuation on DU1 to trigger Handover rocket - python3 %%workspace%%/ci-scripts/attenuatorctl.py -r 1,2 15 -r 3,4 0 -d 5 + python3 %%workspace%%/ci-scripts/attenuatorctl.py -r 1,2 22 -r 3,4 0 -d 5 @@ -168,7 +197,53 @@ raspix oc-cn5g-00103-ho -c 50 -i 0.2 - 5 + 30 + + + + Custom_Command + UE connected to DU1? + groot + echo ci fetch_du_by_ue_id | ncat 127.0.0.1 9090 | grep "3585" + + + + Custom_Command + Attenuate DU1 by 22dB and remove attenuation on DU0 to trigger Handover + rocket + python3 %%workspace%%/ci-scripts/attenuatorctl.py -r 1,2 0 -r 3,4 22 -d 5 + + + + Ping + Ping: 50pings in 10sec + raspix + oc-cn5g-00103-ho + -c 50 -i 0.2 + 30 + + + + Custom_Command + UE connected to DU0? + groot + echo ci fetch_du_by_ue_id | ncat 127.0.0.1 9090 | grep "3584" + + + + Custom_Command + Attenuate DU0 by 22 and remove the attenuation on DU1 to trigger Handover + rocket + python3 %%workspace%%/ci-scripts/attenuatorctl.py -r 1,2 22 -r 3,4 0 -d 5 + + + + Ping + Ping: 50pings in 10sec + raspix + oc-cn5g-00103-ho + -c 50 -i 0.2 + 30 diff --git a/ci-scripts/xml_files/container_sa_n2_ho_b210_quectel.xml b/ci-scripts/xml_files/container_sa_n2_ho_b210_quectel.xml index 1f4c7c1981..06fff38745 100644 --- a/ci-scripts/xml_files/container_sa_n2_ho_b210_quectel.xml +++ b/ci-scripts/xml_files/container_sa_n2_ho_b210_quectel.xml @@ -55,7 +55,7 @@ Custom_Command Set initial attenuation on target gNB rocket - python3 %%workspace%%/ci-scripts/attenuatorctl.py -s 1,2 0 -s 3,4 25 + python3 %%workspace%%/ci-scripts/attenuatorctl.py -s 1,2 0 -s 3,4 22 @@ -103,9 +103,9 @@ Custom_Command - Gradually attenuate gNB-PCI0 (ch1-2=15dB) and gNB-PCI1 (ch3-4=0dB) over 5s to trigger N2 Handover to gNB-PCI1 + Gradually attenuate gNB-PCI0 (ch1-2=22dB) and gNB-PCI1 (ch3-4=0dB) over 5s to trigger N2 Handover to gNB-PCI1 rocket - python3 %%workspace%%/ci-scripts/attenuatorctl.py -r 1,2 15 -r 3,4 0 -d 5 + python3 %%workspace%%/ci-scripts/attenuatorctl.py -r 1,2 22 -r 3,4 0 -d 5 @@ -126,9 +126,9 @@ Custom_Command - Gradually attenuate gNB-PCI0 (ch1-2=0dB) and gNB-PCI1 (ch3-4=15dB) over 5s to trigger N2 Handover back to gNB-PCI0 + Gradually attenuate gNB-PCI0 (ch1-2=0dB) and gNB-PCI1 (ch3-4=22dB) over 5s to trigger N2 Handover back to gNB-PCI0 rocket - python3 %%workspace%%/ci-scripts/attenuatorctl.py -r 1,2 0 -r 3,4 25 -d 5 + python3 %%workspace%%/ci-scripts/attenuatorctl.py -r 1,2 0 -r 3,4 22 -d 5