diff --git a/openair2/RRC/NR/MESSAGES/asn1_msg.c b/openair2/RRC/NR/MESSAGES/asn1_msg.c index 3ff6aecea9..8bd9716107 100644 --- a/openair2/RRC/NR/MESSAGES/asn1_msg.c +++ b/openair2/RRC/NR/MESSAGES/asn1_msg.c @@ -564,6 +564,43 @@ static NR_RRCReconfiguration_IEs_t *build_RRCReconfiguration_IEs(const nr_rrc_re return ie; } +static byte_array_t do_HO_RRCReconfiguration(nr_rrc_reconfig_param_t *params) +{ + NR_RRCReconfiguration_IEs_t *ie = build_RRCReconfiguration_IEs(params); + byte_array_t msg = {.buf = NULL, .len = 0}; + + // Create the standalone RRCReconfiguration message + NR_RRCReconfiguration_t rrcReconf = {0}; + rrcReconf.rrc_TransactionIdentifier = params->transaction_id; + rrcReconf.criticalExtensions.present = NR_RRCReconfiguration__criticalExtensions_PR_rrcReconfiguration; + rrcReconf.criticalExtensions.choice.rrcReconfiguration = ie; + + // Encode the message + int val = uper_encode_to_new_buffer(&asn_DEF_NR_RRCReconfiguration, NULL, &rrcReconf, (void **)&msg.buf); + if (val <= 0) { + LOG_E(NR_RRC, "ASN1 RRCReconfiguration message encoding failed\n"); + ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_NR_RRCReconfiguration, &rrcReconf); + return msg; + } + msg.len = val; + LOG_D(NR_RRC, "RRCReconfiguration: Encoded (%ld bytes)\n", msg.len); + + // don't free what we did not allocate, so set fields with pointers to NULL + // if memory comes from outside + ie->measConfig = NULL; + if (ie->radioBearerConfig) { + ie->radioBearerConfig->srb3_ToRelease = NULL; + ie->radioBearerConfig->drb_ToReleaseList = NULL; + ie->radioBearerConfig->drb_ToAddModList = NULL; + ie->radioBearerConfig->srb_ToAddModList = NULL; + ie->radioBearerConfig->securityConfig = NULL; + } + + ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_NR_RRCReconfiguration, &rrcReconf); + + return msg; +} + byte_array_t do_RRCReconfiguration(const nr_rrc_reconfig_param_t *params) { byte_array_t msg = {.buf = NULL, .len = 0}; @@ -1101,6 +1138,50 @@ NR_MeasConfig_t *get_MeasConfig(const NR_MeasTiming_t *mt, return mc; } +/** @brief HandoverPreparationInformation message (11.2.2 of TS 38.331) + * 1) generate RRCReconfiguration to be transferred + * 2) encodes UE Capabilities from UE Context + * 3) encodes AS context + * 4) generates HO Preparation Info */ +byte_array_t get_HandoverPreparationInformation(nr_rrc_reconfig_param_t *params, int scell_pci) +{ + // Buffer to return + byte_array_t buffer = {.buf = NULL, .len = 0}; + + /* Prepare handoverPreparationInformation IEs */ + NR_HandoverPreparationInformation_t hoPrepInfo = {0}; + hoPrepInfo.criticalExtensions.present = NR_HandoverPreparationInformation__criticalExtensions_PR_c1; + asn1cCalloc(hoPrepInfo.criticalExtensions.choice.c1, c1); + c1->present = NR_HandoverPreparationInformation__criticalExtensions__c1_PR_handoverPreparationInformation; + asn1cCalloc(c1->choice.handoverPreparationInformation, hpi); + + /* Decode stored NR_UE_CapabilityRAT_ContainerList and add to IEs */ + NR_UE_CapabilityRAT_ContainerList_t *clist = NULL; + asn_dec_rval_t dec_rval = + uper_decode(NULL, &asn_DEF_NR_UE_CapabilityRAT_ContainerList, (void **)&clist, params->ue_cap.buf, params->ue_cap.len, 0, 0); + if (dec_rval.code != RC_OK) { + LOG_W(NR_RRC, "Failed to decode UE capability container list for HandoverPreparationInformation, ignoring capabilities\n"); + return buffer; + } + hpi->ue_CapabilityRAT_List = *clist; + free(clist); + + /* sourceConfig: Encode RRCReconfiguration as used in the source cell */ + byte_array_t msg = do_HO_RRCReconfiguration(params); + asn1cCalloc(hpi->sourceConfig, sourceConfig); + OCTET_STRING_fromBuf(&sourceConfig->rrcReconfiguration, (const char *)msg.buf, msg.len); + free_byte_array(msg); + + if (LOG_DEBUGFLAG(DEBUG_ASN1)) + xer_fprint(stdout, &asn_DEF_NR_HandoverPreparationInformation, (void **)&hoPrepInfo); + + /* encode */ + buffer.len = uper_encode_to_new_buffer(&asn_DEF_NR_HandoverPreparationInformation, NULL, (void *)&hoPrepInfo, (void *)&buffer.buf); + AssertFatal(buffer.len > 0, "ASN1 message encoding failed (%ld)!\n", buffer.len); + ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_NR_HandoverPreparationInformation, &hoPrepInfo); + return buffer; +} + void free_MeasConfig(NR_MeasConfig_t *mc) { ASN_STRUCT_FREE(asn_DEF_NR_MeasConfig, mc); diff --git a/openair2/RRC/NR/MESSAGES/asn1_msg.h b/openair2/RRC/NR/MESSAGES/asn1_msg.h index dde62d142f..aab65f6685 100644 --- a/openair2/RRC/NR/MESSAGES/asn1_msg.h +++ b/openair2/RRC/NR/MESSAGES/asn1_msg.h @@ -69,6 +69,7 @@ typedef struct { NR_CellGroupConfig_t *cell_group_config; bool masterKeyUpdate; int nextHopChainingCount; + byte_array_t ue_cap; } nr_rrc_reconfig_param_t; /* @@ -161,4 +162,6 @@ NR_MeasConfig_t *get_MeasConfig(const NR_MeasTiming_t *mt, void free_MeasConfig(NR_MeasConfig_t *mc); int do_NR_Paging(uint8_t Mod_id, uint8_t *buffer, uint32_t tmsi); +byte_array_t get_HandoverPreparationInformation(nr_rrc_reconfig_param_t *params, int scell_pci); + #endif /* __RRC_NR_MESSAGES_ASN1_MSG__H__ */ diff --git a/openair2/RRC/NR/nr_rrc_proto.h b/openair2/RRC/NR/nr_rrc_proto.h index 0d675ff525..a1918bdd34 100644 --- a/openair2/RRC/NR/nr_rrc_proto.h +++ b/openair2/RRC/NR/nr_rrc_proto.h @@ -125,4 +125,7 @@ nr_rrc_reconfig_param_t get_RRCReconfiguration_params(gNB_RRC_INST *rrc, gNB_RRC pdusession_level_qos_parameter_t *get_qos_characteristics(const int qfi, rrc_pdu_session_param_t *pduSession); f1ap_qos_flow_param_t get_qos_char_from_qos_flow_param(const pdusession_level_qos_parameter_t *qos_param); void openair_rrc_gNB_configuration(gNB_RRC_INST *rrc, gNB_RrcConfigurationReq *configuration); +NR_SRB_ToAddModList_t *createSRBlist(gNB_RRC_UE_t *ue, uint8_t reestablish); +NR_DRB_ToAddModList_t *createDRBlist(gNB_RRC_UE_t *ue, bool reestablish); + #endif diff --git a/openair2/RRC/NR/rrc_gNB.c b/openair2/RRC/NR/rrc_gNB.c index 0e5912aafe..ad36b9c10d 100644 --- a/openair2/RRC/NR/rrc_gNB.c +++ b/openair2/RRC/NR/rrc_gNB.c @@ -307,7 +307,7 @@ unsigned int rrc_gNB_get_next_transaction_identifier(module_id_t gnb_mod_idP) * for the radio bearer changes, with some expections for SRB1 (i.e. when resuming * an RRC connection, or at the first reconfiguration after RRC connection * reestablishment in NR, do not re-establish PDCP) */ -static NR_SRB_ToAddModList_t *createSRBlist(gNB_RRC_UE_t *ue, uint8_t reestablish) +NR_SRB_ToAddModList_t *createSRBlist(gNB_RRC_UE_t *ue, uint8_t reestablish) { if (!ue->Srb[SRB1].Active) { LOG_E(NR_RRC, "Call SRB list while SRB1 doesn't exist\n"); @@ -328,7 +328,7 @@ static NR_SRB_ToAddModList_t *createSRBlist(gNB_RRC_UE_t *ue, uint8_t reestablis return list; } -static NR_DRB_ToAddModList_t *createDRBlist(gNB_RRC_UE_t *ue, bool reestablish) +NR_DRB_ToAddModList_t *createDRBlist(gNB_RRC_UE_t *ue, bool reestablish) { NR_DRB_ToAddMod_t *DRB_config = NULL; NR_DRB_ToAddModList_t *DRB_configList = CALLOC(sizeof(*DRB_configList), 1); @@ -349,7 +349,7 @@ static NR_DRB_ToAddModList_t *createDRBlist(gNB_RRC_UE_t *ue, bool reestablish) return DRB_configList; } -static void freeSRBlist(NR_SRB_ToAddModList_t *l) +void freeSRBlist(NR_SRB_ToAddModList_t *l) { ASN_STRUCT_FREE(asn_DEF_NR_SRB_ToAddModList, l); } @@ -1383,6 +1383,7 @@ static void process_Event_Based_Measurement_Report(gNB_RRC_INST *rrc, int servingCellRSRP = 0; int neighbourCellRSRP = 0; int scell_pci = -1; + int best_rsrp = -10000; switch (event_triggered->eventId.present) { case NR_EventTriggerConfig__eventId_PR_eventA2: @@ -1443,14 +1444,20 @@ static void process_Event_Based_Measurement_Report(gNB_RRC_INST *rrc, // No F1 connection but static neighbour configuration is available const nr_a3_event_t *a3_event_configuration = get_a3_configuration(rrc, neighbour->physicalCellId); // Additional check - This part can be modified according to additional cell specific Handover Margin + // a3-Offset: The actual value is field value * 0.5 dB. if (a3_event_configuration - && ((a3_event_configuration->a3_offset + a3_event_configuration->hysteresis) + && ((a3_event_configuration->a3_offset * 0.5 + a3_event_configuration->hysteresis) < (neighbourCellRSRP - servingCellRSRP))) { + if (neighbourCellRSRP > best_rsrp) { + // UE can send multiple neighbour cells A3 event report in 1 Meas Report. So, we need to find the best neighbour + best_rsrp = neighbourCellRSRP; + LOG_I(NR_RRC, "HO LOG: Serving Cell RSRP: %d - Best Neighbor RSRP: %d ! Trigger N2 HO\n", servingCellRSRP, best_rsrp); + nr_rrc_trigger_n2_ho(rrc, ue, scell_pci, neighbour); + } LOG_D(NR_RRC, "HO LOG: Trigger N2 HO for the neighbour gnb: %u cell: %lu\n", neighbour->gNB_ID, neighbour->nrcell_id); } } else if (neigh_cell && neighbour) { /* we know the cell and are connected to the DU! */ - gNB_RRC_INST *rrc = RC.nrrrc[0]; nr_rrc_du_container_t *source_du = get_du_by_cell_id(rrc, serving_cell->nr_cellid); DevAssert(source_du); nr_rrc_du_container_t *target_du = get_du_by_cell_id(rrc, neigh_cell->nr_cellid); @@ -1459,7 +1466,6 @@ static void process_Event_Based_Measurement_Report(gNB_RRC_INST *rrc, LOG_W(NR_RRC, "UE %d: received A3 event for stronger neighbor PCI %d, but no such neighbour in configuration\n", ue->rrc_ue_id, neighbour_pci); } } - } break; default: LOG_D(NR_RRC, "NR_EventTriggerConfig__eventId_PR_NOTHING or Other event report\n"); diff --git a/openair2/RRC/NR/rrc_gNB_mobility.c b/openair2/RRC/NR/rrc_gNB_mobility.c index fbccc637a7..fa2eead5ca 100644 --- a/openair2/RRC/NR/rrc_gNB_mobility.c +++ b/openair2/RRC/NR/rrc_gNB_mobility.c @@ -215,27 +215,12 @@ static void nr_initiate_handover(const gNB_RRC_INST *rrc, free_ue_context_setup_req(&ue_context_setup_req); } -/* -void nr_rrc_trigger_n2_ho(gNB_RRC_INST *rrc, int nr_cgi, uint8_t *ho_prep_info, uint32_t *ho_prep_len, void *pdu_session) -{ - // call from outside (via NGAP message), with the request for handover for a - // (any) UE. This handler should, in order: - // - // 1. look up the target DU =du= via get_du_by_cell_id(nr_cgi) - // (and reject the request if it can't find the DU) - // 2. create a context for a new UE =UE=, based on pdu_session information - // 3. call nr_initiate_handover(rrc, UE, NULL, du, ho_prep_info, ho_prep_len); - // - // after success of the DU, send the result via =success_ptr= to the source - // CU. -} -*/ - typedef struct deliver_ue_ctxt_modification_data_t { gNB_RRC_INST *rrc; f1ap_ue_context_mod_req_t *modification_req; sctp_assoc_t assoc_id; } deliver_ue_ctxt_modification_data_t; + static void rrc_deliver_ue_ctxt_modif_req(void *deliver_pdu_data, ue_id_t ue_id, int srb_id, char *buf, int size, int sdu_id) { DevAssert(deliver_pdu_data != NULL); @@ -399,3 +384,52 @@ void nr_HO_F1_trigger_telnet(gNB_RRC_INST *rrc, uint32_t rrc_ue_id) nr_rrc_trigger_f1_ho(rrc, ue, source_du, target_du); } + +/** @brief Generate the HandoverPreparationInformation to be carried + * in the RRC Container (9.3.1.29 of 3GPP TS 38.413) of the Source + * NG-RAN Node to Target NG-RAN Node Transparent Container IE */ +static byte_array_t rrc_gNB_generate_HandoverPreparationInformation(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue, int serving_pci) +{ + nr_rrc_reconfig_param_t params = get_RRCReconfiguration_params(rrc, ue, 0, false); + params.ue_cap = ue->ue_cap_buffer; + + byte_array_t hoPrepInfo = get_HandoverPreparationInformation(¶ms, serving_pci); + free_RRCReconfiguration_params(params); + + if (hoPrepInfo.len < 0) { + LOG_E(NR_RRC, "HandoverPreparationInformation generation failed for UE %d\n", ue->rrc_ue_id); + return hoPrepInfo; + } + + LOG_D(NR_RRC, "HO LOG: Handover Preparation for UE %lu Encoded (%zd bytes)\n", ue->amf_ue_ngap_id, hoPrepInfo.len); + + return hoPrepInfo; +} + +/** @brief Trigger N2 handover on source gNB: + * 1) Prepare RRC Container with HandoverPreparationInformation message + * 2) send NGAP Handover Required message */ +void nr_rrc_trigger_n2_ho(gNB_RRC_INST *rrc, + gNB_RRC_UE_t *ue, + int serving_pci, + const nr_neighbour_cell_t *neighbour_config) +{ + byte_array_t hoPrepInfo = rrc_gNB_generate_HandoverPreparationInformation(rrc, ue, serving_pci); + if (hoPrepInfo.len < 0) { + free_byte_array(hoPrepInfo); + LOG_E(NR_RRC, "Failed to trigger N2 handover on source gNB for UE %x\n", ue->rrc_ue_id); + return; + } + + // allocate context for source + if (ue->ho_context != NULL) { + LOG_E(NR_RRC, "Ongoing handover for UE %d, cannot trigger new\n", ue->rrc_ue_id); + return; + } + + ue->ho_context = alloc_ho_ctx(HO_CTX_SOURCE); + ue->ho_context->source->du = get_du_for_ue(rrc, ue->rrc_ue_id); + + rrc_gNB_send_NGAP_HANDOVER_REQUIRED(rrc, ue, neighbour_config, hoPrepInfo); + free_byte_array(hoPrepInfo); +} diff --git a/openair2/RRC/NR/rrc_gNB_mobility.h b/openair2/RRC/NR/rrc_gNB_mobility.h index a907b3c472..3169ea23c0 100644 --- a/openair2/RRC/NR/rrc_gNB_mobility.h +++ b/openair2/RRC/NR/rrc_gNB_mobility.h @@ -23,6 +23,7 @@ #include #include "common/utils/ds/byte_array.h" +#include "nr_rrc_defs.h" /* forward declarations */ typedef struct gNB_RRC_INST_s gNB_RRC_INST; @@ -78,4 +79,9 @@ nr_handover_context_t *alloc_ho_ctx(ho_ctx_type_t type); void nr_rrc_trigger_f1_ho(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue, nr_rrc_du_container_t *source_du, nr_rrc_du_container_t *target_du); void nr_rrc_finalize_ho(gNB_RRC_UE_t *ue); +void nr_rrc_trigger_n2_ho(gNB_RRC_INST *rrc, + gNB_RRC_UE_t *ue, + int serving_pci, + const nr_neighbour_cell_t *neighbour_config); + #endif /* RRC_GNB_MOBILITY_H_ */