From 6fd02ee7e886b9a3fcfc044291761e173ff54fb2 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Mon, 26 May 2025 10:38:30 +0200 Subject: [PATCH] Handle N2 handover in UE Context Setup Response on the target NG-RAN * generate Handover Command for the source NG-RAN to be added to the target to source transparent container of the Handover Acknowledge message * add Handover Acknowledge callback for N2 * send NG Handover Acknowledge to AMF Co-authored-by: batuhan duyuler --- openair2/RRC/NR/MESSAGES/asn1_msg.c | 108 ++++++++++++++++++++++++++++ openair2/RRC/NR/MESSAGES/asn1_msg.h | 2 + openair2/RRC/NR/rrc_gNB_mobility.c | 67 +++++++++++++++++ openair2/RRC/NR/rrc_gNB_mobility.h | 1 + 4 files changed, 178 insertions(+) diff --git a/openair2/RRC/NR/MESSAGES/asn1_msg.c b/openair2/RRC/NR/MESSAGES/asn1_msg.c index 8bd9716107..fc3ca002f4 100644 --- a/openair2/RRC/NR/MESSAGES/asn1_msg.c +++ b/openair2/RRC/NR/MESSAGES/asn1_msg.c @@ -82,6 +82,7 @@ #include "NR_UE-CapabilityRequestFilterNR.h" #include "NR_HandoverPreparationInformation.h" #include "NR_HandoverPreparationInformation-IEs.h" +#include "NR_HandoverCommand.h" #include "common/utils/nr/nr_common.h" #if defined(NR_Rel16) #include "NR_SCS-SpecificCarrier.h" @@ -1138,6 +1139,113 @@ NR_MeasConfig_t *get_MeasConfig(const NR_MeasTiming_t *mt, return mc; } +/** @brief Prepares removal of measurement configurations related to the source gNB. + * This function decodes the HandoverPreparationInformation received from the source gNB, + * extracts the source MeasConfig, and marks those entries (measurement objects, report + * configurations, and measurement IDs) for removal by populating the corresponding + * ToRemoveList fields in the UE's current measConfig. + * This ensures that obsolete or source-specific measurement configurations are removed + * from the UE after handover and allows the target gNB to install its own valid configuration. + * @param measConfig Target gNB's current measurement configuration to be updated. + * @param prep_info Encoded HandoverPreparationInformation received from the source gNB.*/ +void fill_removal_lists_from_source_measConfig(NR_MeasConfig_t *currentMC, byte_array_t prep_info) +{ + if (currentMC == NULL) { + LOG_E(NR_RRC, "HO LOG: UE's Measurement Configuration is NULL!\n"); + return; + } + + // Decodes the HandoverPreparationInformation message + NR_HandoverPreparationInformation_t *hpi = NULL; + asn_dec_rval_t hoPrep_dec_rval = uper_decode_complete(NULL, + &asn_DEF_NR_HandoverPreparationInformation, + (void **)&hpi, + (uint8_t *)prep_info.buf, + prep_info.len); + + if (hoPrep_dec_rval.code != RC_OK || hoPrep_dec_rval.consumed < 0) { + LOG_E(NR_RRC, "Handover Prep Info decode error while removing source gnb measurement configuration!\n"); + return; + } + + /* Decodes the RRCReconfiguration message within the HandoverPreparationInformation + and extract the measConfig provided by the source gNB */ + if (!hpi->criticalExtensions.choice.c1->choice.handoverPreparationInformation->sourceConfig) { + LOG_W(NR_RRC, "Missing sourceConfig: in source gNB rrcReconfiguration\n"); + return; + } + NR_RRCReconfiguration_t *rrcReconf = NULL; + NR_AS_Config_t *sourceConfig = hpi->criticalExtensions.choice.c1->choice.handoverPreparationInformation->sourceConfig; + asn_dec_rval_t rrcReconf_dec_rval = uper_decode_complete(NULL, + &asn_DEF_NR_RRCReconfiguration, + (void **)&rrcReconf, + (uint8_t *)sourceConfig->rrcReconfiguration.buf, + sourceConfig->rrcReconfiguration.size); + if (rrcReconf_dec_rval.code != RC_OK || rrcReconf_dec_rval.consumed < 0) { + LOG_E(NR_RRC, "Failed to decode source gNB rrcReconfiguration!\n"); + return; + } + + if (rrcReconf->criticalExtensions.choice.rrcReconfiguration->measConfig == NULL) + return; + + NR_MeasConfig_t *sourceMC = rrcReconf->criticalExtensions.choice.rrcReconfiguration->measConfig; + + /* Add measurement objects, report configurations, and measurement IDs + to the UE's measConfig removal lists, and update UE->measConfig */ + if (sourceMC->measObjectToAddModList->list.count > 0) { + currentMC->measObjectToRemoveList = calloc_or_fail(1, sizeof(*currentMC->measObjectToRemoveList)); + for (int i = 0; i < sourceMC->measObjectToAddModList->list.count; i++) { + NR_MeasObjectId_t *measObjId = calloc_or_fail(1, sizeof(NR_MeasObjectId_t)); + *measObjId = sourceMC->measObjectToAddModList->list.array[i]->measObjectId; + asn1cSeqAdd(¤tMC->measObjectToRemoveList->list, measObjId); + } + } + + if (sourceMC->reportConfigToAddModList->list.count > 0) { + currentMC->reportConfigToRemoveList = calloc_or_fail(1, sizeof(*currentMC->reportConfigToRemoveList)); + for (int i = 0; i < sourceMC->reportConfigToAddModList->list.count; i++) { + NR_ReportConfigId_t *reportConfigId = calloc_or_fail(1, sizeof(NR_ReportConfigId_t)); + *reportConfigId = sourceMC->reportConfigToAddModList->list.array[i]->reportConfigId; + asn1cSeqAdd(¤tMC->reportConfigToRemoveList->list, reportConfigId); + } + } + + if (sourceMC->measIdToAddModList->list.count > 0) { + currentMC->measIdToRemoveList = calloc_or_fail(1, sizeof(*currentMC->measIdToRemoveList)); + for (int i = 0; i < sourceMC->measIdToAddModList->list.count; i++) { + NR_MeasId_t *measId = calloc_or_fail(1, sizeof(NR_MeasId_t)); + *measId = sourceMC->measIdToAddModList->list.array[i]->measId; + asn1cSeqAdd(¤tMC->measIdToRemoveList->list, measId); + } + } +} + +byte_array_t get_HandoverCommandMessage(nr_rrc_reconfig_param_t *params) +{ + // Encode RRCReconfiguration to buffer + byte_array_t msg = do_HO_RRCReconfiguration(params); + + // Add RRCReconfiguration to handoverCommand + NR_HandoverCommand_t HoCommand = {0}; + HoCommand.criticalExtensions.present = NR_HandoverCommand__criticalExtensions_PR_c1; + asn1cCalloc(HoCommand.criticalExtensions.choice.c1, c1); + c1->present = NR_HandoverCommand__criticalExtensions__c1_PR_handoverCommand; + asn1cCalloc(HoCommand.criticalExtensions.choice.c1->choice.handoverCommand, hc); + OCTET_STRING_fromBuf(&hc->handoverCommandMessage, (const char *)msg.buf, msg.len); + free_byte_array(msg); + + if (LOG_DEBUGFLAG(DEBUG_ASN1)) + xer_fprint(stdout, &asn_DEF_NR_HandoverCommand, (void *)&HoCommand); + + // Encode handoverCommand to buffer + byte_array_t buffer = {.len = -1, .buf = NULL}; + buffer.len = uper_encode_to_new_buffer(&asn_DEF_NR_HandoverCommand, NULL, (void *)&HoCommand, (void **)&buffer.buf); + ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_NR_HandoverCommand, &HoCommand); + + return buffer; +} + /** @brief HandoverPreparationInformation message (11.2.2 of TS 38.331) * 1) generate RRCReconfiguration to be transferred * 2) encodes UE Capabilities from UE Context diff --git a/openair2/RRC/NR/MESSAGES/asn1_msg.h b/openair2/RRC/NR/MESSAGES/asn1_msg.h index aab65f6685..668f2f8ade 100644 --- a/openair2/RRC/NR/MESSAGES/asn1_msg.h +++ b/openair2/RRC/NR/MESSAGES/asn1_msg.h @@ -163,5 +163,7 @@ void free_MeasConfig(NR_MeasConfig_t *mc); int do_NR_Paging(uint8_t Mod_id, uint8_t *buffer, uint32_t tmsi); byte_array_t get_HandoverPreparationInformation(nr_rrc_reconfig_param_t *params, int scell_pci); +byte_array_t get_HandoverCommandMessage(nr_rrc_reconfig_param_t *params); +void fill_removal_lists_from_source_measConfig(NR_MeasConfig_t *measConfig, byte_array_t prep_info); #endif /* __RRC_NR_MESSAGES_ASN1_MSG__H__ */ diff --git a/openair2/RRC/NR/rrc_gNB_mobility.c b/openair2/RRC/NR/rrc_gNB_mobility.c index c9d60422e8..bf6ecb5865 100644 --- a/openair2/RRC/NR/rrc_gNB_mobility.c +++ b/openair2/RRC/NR/rrc_gNB_mobility.c @@ -408,6 +408,73 @@ static byte_array_t rrc_gNB_generate_HandoverPreparationInformation(gNB_RRC_INST return hoPrepInfo; } +static byte_array_t rrc_gNB_encode_HandoverCommand(gNB_RRC_UE_t *UE, gNB_RRC_INST *rrc) +{ + DevAssert(UE->ho_context); + DevAssert(UE->ho_context->target); + + NR_SecurityConfig_t *sec = calloc_or_fail(1, sizeof(*sec)); + sec->keyToUse = calloc_or_fail(1, sizeof(*sec->keyToUse)); + *sec->keyToUse = NR_SecurityConfig__keyToUse_master; + sec->securityAlgorithmConfig = calloc_or_fail(1, sizeof(*sec->securityAlgorithmConfig)); + struct NR_SecurityAlgorithmConfig *alg = sec->securityAlgorithmConfig; + alg->cipheringAlgorithm = UE->ciphering_algorithm; + alg->integrityProtAlgorithm = calloc_or_fail(1, sizeof(*alg->integrityProtAlgorithm)); + *alg->integrityProtAlgorithm = UE->integrity_algorithm; + + // Mark source gNB's measurement configuration for removal in UE->measConfig + if (UE->ho_context->target->ue_ho_prep_info.len) { + fill_removal_lists_from_source_measConfig(UE->measConfig, UE->ho_context->target->ue_ho_prep_info); + } + + /* 3GPP TS 38.331 RadioBearerConfig: re-establish PDCP whenever + the security key used for this radio bearer changes, e.g. for SRB2 + when receiving reconfiguration with sync [...]. Similarly, for DRBs + PDCP re-establishment is necessary with derivation of new UP keys */ + nr_rrc_reconfig_param_t params = get_RRCReconfiguration_params(rrc, UE, (1 << 1) | (1 << 2), true); + UE->xids[params.transaction_id] = RRC_DEDICATED_RECONF; + + params.security_config = sec; + params.masterKeyUpdate = true; + params.nextHopChainingCount = UE->nh_ncc; + byte_array_t out = get_HandoverCommandMessage(¶ms); + + // Free remove lists in UE->measConfig + ASN_STRUCT_FREE(asn_DEF_NR_MeasIdToRemoveList, UE->measConfig->measIdToRemoveList); + ASN_STRUCT_FREE(asn_DEF_NR_ReportConfigToRemoveList, UE->measConfig->reportConfigToRemoveList); + ASN_STRUCT_FREE(asn_DEF_NR_MeasObjectToRemoveList, UE->measConfig->measObjectToRemoveList); + + free_RRCReconfiguration_params(params); + return out; +} + +/** @brief This callback is used by the target gNB + * to trigger the Handover Request Acknowledge towards the AMF */ +static void nr_rrc_n2_ho_acknowledge(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE) +{ + AssertFatal(cu_exists_f1_ue_data(UE->rrc_ue_id), "No CU found for rrc_ue_id %d\n", UE->rrc_ue_id); + + f1_ue_data_t previous_data = cu_get_f1_ue_data(UE->rrc_ue_id); + /* this is the callback for N2 handover after F1 UE context setup response in + * the handover case. Check that there was no associated DU, then set it */ + AssertFatal(previous_data.secondary_ue == -1, "there was already a DU present\n"); + previous_data.secondary_ue = UE->ho_context->target->du_ue_id; + bool success = cu_update_f1_ue_data(UE->rrc_ue_id, &previous_data); + DevAssert(success); + LOG_I(NR_RRC, "Updated CU F1AP Context for UE %d, DU Id : %u\n", UE->rrc_ue_id, UE->ho_context->target->du_ue_id); + + byte_array_t hoCommand = rrc_gNB_encode_HandoverCommand(UE, rrc); + if (hoCommand.len < 0) { + LOG_E(NR_RRC, "ASN1 message encoding failed: failed to generate Handover Command Message\n"); + return; + } else { + LOG_D(NR_RRC, "HO LOG: Handover Command for UE %u Encoded (%ld bytes)\n", UE->rrc_ue_id, hoCommand.len); + } + + rrc_gNB_send_NGAP_HANDOVER_REQUEST_ACKNOWLEDGE(rrc, UE, hoCommand); + free_byte_array(hoCommand); +} + /** @brief Callback function to trigger NG Handover Failure on the target gNB, to inform the AMF * that the preparation of resources has failed (e.g. unsatisfied criteria, gNB is already loaded). * This message represents an Unsuccessful Outcome of the Handover Resource Allocation */ diff --git a/openair2/RRC/NR/rrc_gNB_mobility.h b/openair2/RRC/NR/rrc_gNB_mobility.h index 77b90ac3e0..4f31c433ec 100644 --- a/openair2/RRC/NR/rrc_gNB_mobility.h +++ b/openair2/RRC/NR/rrc_gNB_mobility.h @@ -53,6 +53,7 @@ typedef struct nr_ho_source_cu { /* acknowledgement of handover request. buf+len is the RRC Reconfiguration */ typedef void (*ho_req_ack_t)(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue); typedef void (*ho_success_t)(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue); +typedef void (*ho_failure_t)(gNB_RRC_INST *rrc, uint32_t gnb_ue_id, ngap_handover_failure_t *msg); typedef struct nr_ho_target_cu { /// pointer to the (target) DU structure