From f6d23422aadfa138829e8b58ea547c4c0b03e3ce Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Wed, 14 Jan 2026 16:12:49 +0100 Subject: [PATCH 1/2] NGAP: enable Handover Cancel encoding This commit adds support for the Handover Cancel initiating message procedure code (NGAP_ProcedureCode_id_HandoverCancel). Changes: - ngap_gNB_decoder.c: Added case handler for Handover Cancel in ngap_gNB_decode_initiating_message() to properly decode and log incoming Handover Cancel messages from the AMF. - ngap_gNB_encoder.c: Added Handover Cancel to the list of supported initiating message procedure codes in ngap_gNB_encode_initiating(), allowing the gNB to encode and send Handover Cancel messages. The Handover Cancel procedure is already implemented in other parts of the NGAP stack, these changes were missing to complete the support by ensuring the procedure code is properly recognized in both the encoder and decoder switch statements, following the same pattern as other procedure codes. --- openair3/NGAP/ngap_gNB_decoder.c | 6 ++++++ openair3/NGAP/ngap_gNB_encoder.c | 1 + 2 files changed, 7 insertions(+) diff --git a/openair3/NGAP/ngap_gNB_decoder.c b/openair3/NGAP/ngap_gNB_decoder.c index 481cccf876..43110e9037 100644 --- a/openair3/NGAP/ngap_gNB_decoder.c +++ b/openair3/NGAP/ngap_gNB_decoder.c @@ -76,6 +76,12 @@ static int ngap_gNB_decode_initiating_message(NGAP_NGAP_PDU_t *pdu) { NGAP_INFO("Handover Preparation initiating message\n"); break; + case NGAP_ProcedureCode_id_HandoverCancel: + res = asn_encode_to_new_buffer(NULL, ATS_CANONICAL_XER, &asn_DEF_NGAP_NGAP_PDU, pdu); + free(res.buffer); + NGAP_INFO("Handover Cancel initiating message\n"); + break; + case NGAP_ProcedureCode_id_PDUSessionResourceModify: res = asn_encode_to_new_buffer(NULL, ATS_CANONICAL_XER, &asn_DEF_NGAP_NGAP_PDU, pdu); free(res.buffer); diff --git a/openair3/NGAP/ngap_gNB_encoder.c b/openair3/NGAP/ngap_gNB_encoder.c index be3945c2f0..ff318ed184 100644 --- a/openair3/NGAP/ngap_gNB_encoder.c +++ b/openair3/NGAP/ngap_gNB_encoder.c @@ -55,6 +55,7 @@ static inline int ngap_gNB_encode_initiating(NGAP_NGAP_PDU_t *pdu, uint8_t **buf NGAP_ProcedureCode_id_PDUSessionResourceModifyIndication, NGAP_ProcedureCode_id_UplinkRANStatusTransfer, NGAP_ProcedureCode_id_HandoverPreparation, + NGAP_ProcedureCode_id_HandoverCancel, NGAP_ProcedureCode_id_HandoverNotification}; int i; for (i = 0; i < sizeofArray(tmp); i++) From d613cf7b0b05fe8eff62383eba0a1b22441f15c5 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Tue, 13 Jan 2026 11:26:41 +0100 Subject: [PATCH 2/2] RRC: improve error handling in doRRCReconfiguration_from_HandoverCommand - Refactor function to return byte_array_t instead of taking buffer parameter for consistency with other encoding functions (do_RRCReconfiguration, do_HO_RRCReconfiguration) and to prevent buffer overflow issues since the memory size is determined automatically - Add comprehensive error checking: * Input validation * Check return of each decode (HandoverCommand and RRCReconfiguration) * Validate HandoverCommand structure before access * Validate OCTET_STRING before decoding RRCReconfiguration * Error handling and cleanup for enc/dec failures - Update caller in rrc_gNB_NGAP.c * use new signature * free buffer * send HO Cancel on decoding failure (source gNB) Closes #1043 --- openair2/RRC/NR/MESSAGES/asn1_msg.c | 52 +++++++++++++++++++++++------ openair2/RRC/NR/MESSAGES/asn1_msg.h | 2 +- openair2/RRC/NR/rrc_gNB_NGAP.c | 17 +++++++--- 3 files changed, 55 insertions(+), 16 deletions(-) diff --git a/openair2/RRC/NR/MESSAGES/asn1_msg.c b/openair2/RRC/NR/MESSAGES/asn1_msg.c index 585b1e48e2..271330b21d 100644 --- a/openair2/RRC/NR/MESSAGES/asn1_msg.c +++ b/openair2/RRC/NR/MESSAGES/asn1_msg.c @@ -1318,20 +1318,33 @@ void fill_removal_lists_from_source_measConfig(NR_MeasConfig_t *currentMC, byte_ ASN_STRUCT_FREE(asn_DEF_NR_RRCReconfiguration, rrcReconf); } -int doRRCReconfiguration_from_HandoverCommand(byte_array_t *ba, const byte_array_t handoverCommand) +byte_array_t doRRCReconfiguration_from_HandoverCommand(const byte_array_t handoverCommand) { + DevAssert(handoverCommand.buf); + DevAssert(handoverCommand.len > 0); + byte_array_t msg = {.buf = NULL, .len = 0}; + // Decode Handover Command NR_HandoverCommand_t *hoCommand = NULL; asn_dec_rval_t dec_rval = uper_decode_complete(NULL, &asn_DEF_NR_HandoverCommand, (void **)&hoCommand, handoverCommand.buf, handoverCommand.len); - if (dec_rval.code != RC_OK && dec_rval.consumed == 0) { - LOG_E(NR_RRC, "Can not decode Handover Command!\n"); - return -1; + if (dec_rval.code != RC_OK || !hoCommand) { + LOG_E(NR_RRC, "Failed to decode Handover Command (dec_rval.code=%d)!\n", dec_rval.code); + return msg; + } + + // Validate HandoverCommand structure + if (hoCommand->criticalExtensions.present != NR_HandoverCommand__criticalExtensions_PR_c1 + || !hoCommand->criticalExtensions.choice.c1 + || hoCommand->criticalExtensions.choice.c1->present != NR_HandoverCommand__criticalExtensions__c1_PR_handoverCommand + || !hoCommand->criticalExtensions.choice.c1->choice.handoverCommand) { + LOG_E(NR_RRC, "Invalid HandoverCommand in criticalExtensions.choice.c1->choice.handoverCommand\n"); + ASN_STRUCT_FREE(asn_DEF_NR_HandoverCommand, hoCommand); + return msg; } // Prepare DL DCCH message for RRCReconfiguration NR_DL_DCCH_Message_t dl_dcch_msg = {0}; - asn_enc_rval_t enc_rval; dl_dcch_msg.message.present = NR_DL_DCCH_MessageType_PR_c1; asn1cCalloc(dl_dcch_msg.message.choice.c1, c1); c1->present = NR_DL_DCCH_MessageType__c1_PR_rrcReconfiguration; @@ -1339,18 +1352,37 @@ int doRRCReconfiguration_from_HandoverCommand(byte_array_t *ba, const byte_array // Decode RRCReconfiguration from handoverCommandMessage OCTET_STRING_t *ho = &hoCommand->criticalExtensions.choice.c1->choice.handoverCommand->handoverCommandMessage; - uper_decode_complete(NULL, &asn_DEF_NR_RRCReconfiguration, (void **)&rrcReconf, ho->buf, ho->size); + if (!ho->buf || ho->size == 0) { + LOG_E(NR_RRC, "Invalid handoverCommandMessage OCTET_STRING (buf=%p, size=%zu)!\n", ho->buf, ho->size); + ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_NR_DL_DCCH_Message, &dl_dcch_msg); + ASN_STRUCT_FREE(asn_DEF_NR_HandoverCommand, hoCommand); + return msg; + } + asn_dec_rval_t rrc_dec_rval = uper_decode_complete(NULL, &asn_DEF_NR_RRCReconfiguration, (void **)&rrcReconf, ho->buf, ho->size); + if (rrc_dec_rval.code != RC_OK || !rrcReconf) { + LOG_E(NR_RRC, "Failed to decode RRCReconfiguration from Handover Command!\n"); + ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_NR_DL_DCCH_Message, &dl_dcch_msg); + ASN_STRUCT_FREE(asn_DEF_NR_HandoverCommand, hoCommand); + return msg; + } if (LOG_DEBUGFLAG(DEBUG_ASN1)) xer_fprint(stdout, &asn_DEF_NR_DL_DCCH_Message, (void *)&dl_dcch_msg); - // Encode DL DCCH message in buffer - enc_rval = uper_encode_to_buffer(&asn_DEF_NR_DL_DCCH_Message, NULL, &dl_dcch_msg, ba->buf, ba->len); - AssertFatal(enc_rval.encoded > 0, "ASN1 message encoding failed (%s, %lu)!\n", enc_rval.failed_type->name, enc_rval.encoded); + // Encode DL DCCH message to new buffer + int val = uper_encode_to_new_buffer(&asn_DEF_NR_DL_DCCH_Message, NULL, &dl_dcch_msg, (void **)&msg.buf); + if (val <= 0) { + LOG_E(NR_RRC, "Failed to encode RRCReconfiguration from Handover Command!\n"); + ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_NR_DL_DCCH_Message, &dl_dcch_msg); + ASN_STRUCT_FREE(asn_DEF_NR_HandoverCommand, hoCommand); + return msg; + } + msg.len = val; + LOG_D(NR_RRC, "RRCReconfiguration from HandoverCommand: Encoded (%ld bytes)\n", msg.len); ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_NR_DL_DCCH_Message, &dl_dcch_msg); ASN_STRUCT_FREE(asn_DEF_NR_HandoverCommand, hoCommand); - return ((enc_rval.encoded + 7) / 8); + return msg; } byte_array_t get_HandoverCommandMessage(nr_rrc_reconfig_param_t *params) diff --git a/openair2/RRC/NR/MESSAGES/asn1_msg.h b/openair2/RRC/NR/MESSAGES/asn1_msg.h index eb8da91c2a..9df35f7570 100644 --- a/openair2/RRC/NR/MESSAGES/asn1_msg.h +++ b/openair2/RRC/NR/MESSAGES/asn1_msg.h @@ -171,7 +171,7 @@ int do_NR_Paging(uint8_t Mod_id, uint8_t *buffer, uint32_t tmsi); byte_array_t get_HandoverPreparationInformation(nr_rrc_reconfig_param_t *params, int scell_pci); byte_array_t get_HandoverCommandMessage(nr_rrc_reconfig_param_t *params); void fill_removal_lists_from_source_measConfig(NR_MeasConfig_t *measConfig, byte_array_t prep_info); -int doRRCReconfiguration_from_HandoverCommand(byte_array_t *ba, byte_array_t handoverCommand); +byte_array_t doRRCReconfiguration_from_HandoverCommand(byte_array_t handoverCommand); struct NR_UE_NR_Capability *get_ue_nr_capability(int rnti, uint8_t *buf, uint32_t len); NR_UE_NR_Capability_t *decode_nr_ue_capability(int rnti, const NR_UE_CapabilityRAT_ContainerList_t *clist); diff --git a/openair2/RRC/NR/rrc_gNB_NGAP.c b/openair2/RRC/NR/rrc_gNB_NGAP.c index 65991f714d..3c5ec81aba 100644 --- a/openair2/RRC/NR/rrc_gNB_NGAP.c +++ b/openair2/RRC/NR/rrc_gNB_NGAP.c @@ -1275,13 +1275,20 @@ void rrc_gNB_process_HandoverCommand(gNB_RRC_INST *rrc, const ngap_handover_comm } gNB_RRC_UE_t *UE = &ue_context_p->ue_context; - uint8_t buffer[NR_RRC_BUF_SIZE]; - byte_array_t ba = {.buf = buffer, .len = sizeof(buffer)}; - int enc = doRRCReconfiguration_from_HandoverCommand(&ba, msg->handoverCommand); - DevAssert(enc > 0); + byte_array_t buffer = doRRCReconfiguration_from_HandoverCommand(msg->handoverCommand); + if (!buffer.buf || buffer.len == 0) { + LOG_E(NR_RRC, "Failed to decode/encode RRCReconfiguration from HandoverCommand\n"); + // Notify AMF that handover was cancelled + DevAssert(UE->ho_context); + DevAssert(UE->ho_context->source); + DevAssert(UE->ho_context->source->ho_cancel); + UE->ho_context->source->ho_cancel(rrc, UE); + return; + } - rrc_gNB_trigger_reconfiguration_for_handover(rrc, UE, buffer, enc); + rrc_gNB_trigger_reconfiguration_for_handover(rrc, UE, buffer.buf, buffer.len); LOG_A(NR_RRC, "Send reconfiguration (HO Command) to UE %u/RNTI %04x\n", UE->rrc_ue_id, UE->rnti); + free_byte_array(buffer); } void rrc_gNB_free_Handover_Command(ngap_handover_command_t *msg)