diff --git a/openair2/COMMON/ngap_messages_def.h b/openair2/COMMON/ngap_messages_def.h index bdeb2455b4..98c1f82260 100644 --- a/openair2/COMMON/ngap_messages_def.h +++ b/openair2/COMMON/ngap_messages_def.h @@ -75,6 +75,7 @@ MESSAGE_DEF(NGAP_HANDOVER_REQUIRED, MESSAGE_PRIORITY_MED, ngap_handover_required MESSAGE_DEF(NGAP_HANDOVER_FAILURE, MESSAGE_PRIORITY_MED, ngap_handover_failure_t, ngap_handover_failure) MESSAGE_DEF(NGAP_HANDOVER_REQUEST_ACKNOWLEDGE, MESSAGE_PRIORITY_MED, ngap_handover_request_ack_t, ngap_handover_request_ack) MESSAGE_DEF(NGAP_HANDOVER_NOTIFY, MESSAGE_PRIORITY_MED, ngap_handover_notify_t, ngap_handover_notify) +MESSAGE_DEF(NGAP_HANDOVER_CANCEL, MESSAGE_PRIORITY_MED, ngap_handover_cancel_t, ngap_handover_cancel) MESSAGE_DEF(NGAP_UL_RAN_STATUS_TRANSFER, MESSAGE_PRIORITY_MED, ngap_ran_status_transfer_t, ngap_ul_ran_status_transfer) MESSAGE_DEF(NGAP_DL_RAN_STATUS_TRANSFER, MESSAGE_PRIORITY_MED, ngap_ran_status_transfer_t, ngap_dl_ran_status_transfer) @@ -89,6 +90,7 @@ MESSAGE_DEF(NGAP_PDUSESSION_RELEASE_COMMAND , MESSAGE_PRIORITY_MED, ngap_pdu MESSAGE_DEF(NGAP_UE_CONTEXT_RELEASE_COMMAND, MESSAGE_PRIORITY_MED, ngap_ue_release_command_t , ngap_ue_release_command) MESSAGE_DEF(NGAP_HANDOVER_REQUEST, MESSAGE_PRIORITY_MED, ngap_handover_request_t, ngap_handover_request) MESSAGE_DEF(NGAP_HANDOVER_COMMAND, MESSAGE_PRIORITY_MED, ngap_handover_command_t, ngap_handover_command) +MESSAGE_DEF(NGAP_HANDOVER_CANCEL_ACK, MESSAGE_PRIORITY_MED, ngap_handover_cancel_ack_t, ngap_handover_cancel_ack) /* NGAP <-> RRC messages (can be initiated either by MME or gNB) */ MESSAGE_DEF(NGAP_UE_CONTEXT_RELEASE_REQ , MESSAGE_PRIORITY_MED, ngap_ue_release_req_t , ngap_ue_release_req) diff --git a/openair2/COMMON/ngap_messages_types.h b/openair2/COMMON/ngap_messages_types.h index a8c2bab58a..ef25f6997d 100644 --- a/openair2/COMMON/ngap_messages_types.h +++ b/openair2/COMMON/ngap_messages_types.h @@ -71,6 +71,8 @@ #define NGAP_HANDOVER_REQUEST_ACKNOWLEDGE(mSGpTR) (mSGpTR)->ittiMsg.ngap_handover_request_ack #define NGAP_HANDOVER_COMMAND(mSGpTR) (mSGpTR)->ittiMsg.ngap_handover_command #define NGAP_HANDOVER_NOTIFY(mSGpTR) (mSGpTR)->ittiMsg.ngap_handover_notify +#define NGAP_HANDOVER_CANCEL(mSGpTR) (mSGpTR)->ittiMsg.ngap_handover_cancel +#define NGAP_HANDOVER_CANCEL_ACK(mSGpTR) (mSGpTR)->ittiMsg.ngap_handover_cancel_ack #define NGAP_UE_CONTEXT_RELEASE_REQ(mSGpTR) (mSGpTR)->ittiMsg.ngap_ue_release_req #define NGAP_PDUSESSION_RELEASE_COMMAND(mSGpTR) (mSGpTR)->ittiMsg.ngap_pdusession_release_command @@ -701,6 +703,24 @@ typedef struct { user_location_information_t user_info; } ngap_handover_notify_t; +/* 9.2.3.11 3GPP TS 38.413 */ +typedef struct { + // RAN UE NGAP ID + uint32_t gNB_ue_ngap_id; + // AMF UE NGAP ID + uint64_t amf_ue_ngap_id; + // Cause + ngap_cause_t cause; +} ngap_handover_cancel_t; + +/* 9.2.3.12 3GPP TS 38.413 */ +typedef struct { + // RAN UE NGAP ID + uint32_t gNB_ue_ngap_id; + // AMF UE NGAP ID + uint64_t amf_ue_ngap_id; +} ngap_handover_cancel_ack_t; + typedef struct ngap_ue_cap_info_ind_s { uint32_t gNB_ue_ngap_id; byte_array_t ue_radio_cap; diff --git a/openair2/RRC/NR/rrc_gNB_NGAP.c b/openair2/RRC/NR/rrc_gNB_NGAP.c index d4068b46a5..be7dce5ca1 100644 --- a/openair2/RRC/NR/rrc_gNB_NGAP.c +++ b/openair2/RRC/NR/rrc_gNB_NGAP.c @@ -1403,6 +1403,30 @@ void rrc_gNB_send_NGAP_HANDOVER_NOTIFY(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE) itti_send_msg_to_task(TASK_NGAP, rrc->module_id, msg_p); } +/** @brief Prepare NG Handover Cancel (source NG-RAN) and inform NGAP (N2-based HO only) + * 3GPP TS 38.413 §8.4.5 (Handover Cancellation) - source NG-RAN -> AMF + * 3GPP TS 38.413 §9.2.3.11 (HANDOVER CANCEL) + * @param rrc CU/RRC instance + * @param UE UE context + * @param cause cancellation cause */ +void rrc_gNB_send_NGAP_HANDOVER_CANCEL(int module_id, gNB_RRC_UE_t *UE, ngap_cause_t cause) +{ + DevAssert(UE != NULL); + + LOG_I(NR_RRC, "Triggering NGAP Handover Cancel (gNB_ue_ngap_id=%d, amf_ue_ngap_id=%ld)\n", UE->rrc_ue_id, UE->amf_ue_ngap_id); + + MessageDef *msg_p = itti_alloc_new_message(TASK_RRC_GNB, 0, NGAP_HANDOVER_CANCEL); + ngap_handover_cancel_t *ho_cancel = &NGAP_HANDOVER_CANCEL(msg_p); + memset(ho_cancel, 0, sizeof(*ho_cancel)); + + /* Mandatory IEs (38.413 §9.2.3.11) */ + ho_cancel->gNB_ue_ngap_id = UE->rrc_ue_id; + ho_cancel->amf_ue_ngap_id = UE->amf_ue_ngap_id; + ho_cancel->cause = cause; + + itti_send_msg_to_task(TASK_NGAP, module_id, msg_p); +} + void rrc_gNB_send_NGAP_PDUSESSION_RELEASE_RESPONSE(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, uint8_t xid) { int pdu_sessions_released = 0; diff --git a/openair2/RRC/NR/rrc_gNB_NGAP.h b/openair2/RRC/NR/rrc_gNB_NGAP.h index d1867173a2..b90d9a1493 100644 --- a/openair2/RRC/NR/rrc_gNB_NGAP.h +++ b/openair2/RRC/NR/rrc_gNB_NGAP.h @@ -105,6 +105,7 @@ void rrc_gNB_process_HandoverCommand(gNB_RRC_INST *rrc, const ngap_handover_comm void rrc_gNB_free_Handover_Command(ngap_handover_command_t *msg); void rrc_gNB_send_NGAP_HANDOVER_NOTIFY(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE); +void rrc_gNB_send_NGAP_HANDOVER_CANCEL(int module_id, gNB_RRC_UE_t *UE, ngap_cause_t cause); int rrc_gNB_send_NGAP_ul_ran_status_transfer(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, const int n_to_mod, const e1_pdcp_status_info_t *pdcp_status); diff --git a/openair2/RRC/NR/rrc_gNB_mobility.c b/openair2/RRC/NR/rrc_gNB_mobility.c index 614c27e242..07b742e16c 100644 --- a/openair2/RRC/NR/rrc_gNB_mobility.c +++ b/openair2/RRC/NR/rrc_gNB_mobility.c @@ -489,6 +489,16 @@ static void nr_rrc_n2_ho_complete(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE) rrc_gNB_send_NGAP_HANDOVER_NOTIFY(rrc, UE); } +/** @brief This callback is used by the source gNB to inform the AMF + * about the cancellation of an ongoing handover */ +static void nr_rrc_n2_ho_cancel(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE) +{ + DevAssert(UE->ho_context); + DevAssert(UE->ho_context->source); + ngap_cause_t cause = {.type = NGAP_CAUSE_RADIO_NETWORK, .value = NGAP_CAUSE_RADIO_NETWORK_HANDOVER_CANCELLED}; + rrc_gNB_send_NGAP_HANDOVER_CANCEL(rrc->module_id, UE, cause); +} + /** @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 */ @@ -546,6 +556,7 @@ void nr_rrc_trigger_n2_ho(gNB_RRC_INST *rrc, ue->ho_context = alloc_ho_ctx(HO_CTX_SOURCE); ue->ho_context->source->du = get_du_for_ue(rrc, ue->rrc_ue_id); ue->ho_context->source->ho_status_transfer = rrc_gNB_send_NGAP_ul_ran_status_transfer; + ue->ho_context->source->ho_cancel = nr_rrc_n2_ho_cancel; rrc_gNB_send_NGAP_HANDOVER_REQUIRED(rrc, ue, neighbour_config, hoPrepInfo); free_byte_array(hoPrepInfo); diff --git a/openair3/NGAP/ngap_gNB.c b/openair3/NGAP/ngap_gNB.c index afa69c4c9d..8c9ac6a973 100644 --- a/openair3/NGAP/ngap_gNB.c +++ b/openair3/NGAP/ngap_gNB.c @@ -463,6 +463,57 @@ static int ngap_gNB_handover_notify(instance_t instance, const ngap_handover_not return 0; } +/** @brief NGAP: Handover Cancel callback */ +static int ngap_gNB_handover_cancel(instance_t instance, const ngap_handover_cancel_t *msg) +{ + DevAssert(msg != NULL); + byte_array_t ba = {.buf = NULL, .len = 0}; + + // Retrieve NGAP gNB instance + ngap_gNB_instance_t *ngap_gNB_instance_p = ngap_gNB_get_instance(instance); + DevAssert(ngap_gNB_instance_p != NULL); + + // we must have the UE context by RAN UE NGAP ID + ngap_gNB_ue_context_t *ue_context_p = ngap_get_ue_context(msg->gNB_ue_ngap_id); + if (ue_context_p == NULL) { + NGAP_ERROR("Failed to encode Handover Cancel: no UE context for gNB_ue_ngap_id=%d\n", msg->gNB_ue_ngap_id); + return -1; + } + + if (ue_context_p->gNB_ue_ngap_id != msg->gNB_ue_ngap_id) { + NGAP_ERROR("Failed to encode Handover Cancel: mismatched gNB_ue_ngap_id=%d\n", msg->gNB_ue_ngap_id); + return -1; + } + + if (ue_context_p->amf_ue_ngap_id != msg->amf_ue_ngap_id) { + NGAP_ERROR("Failed to encode Handover Cancel: mismatched amf_ue_ngap_id=%ld\n", msg->amf_ue_ngap_id); + return -1; + } + + /* Encode NGAP PDU */ + NGAP_NGAP_PDU_t *pdu = encode_ng_handover_cancel(msg); + if (pdu == NULL) { + NGAP_ERROR("Failed to encode NG Handover Cancel\n"); + return -1; + } + + if (ngap_gNB_encode_pdu(pdu, &ba.buf, (uint32_t *)&ba.len) < 0) { + NGAP_ERROR("Failed to encode NG Handover Cancel message PDU\n"); + ASN_STRUCT_FREE(asn_DEF_NGAP_NGAP_PDU, pdu); + return -1; + } + ASN_STRUCT_FREE(asn_DEF_NGAP_NGAP_PDU, pdu); + + /* UE-associated signalling -> use the UE's allocated SCTP stream */ + ngap_gNB_itti_send_sctp_data_req(ngap_gNB_instance_p->instance, + ue_context_p->amf_ref->assoc_id, + ba.buf, + ba.len, + ue_context_p->tx_stream); + + return 0; +} + int ngap_gNB_handle_ul_ran_status_transfer(instance_t instance, const ngap_ran_status_transfer_t *msg) { NGAP_DEBUG("Triggered UL RAN Status Transfer\n"); @@ -587,6 +638,10 @@ void *ngap_gNB_process_itti_msg(void *notUsed) { ngap_gNB_handover_notify(instance, &NGAP_HANDOVER_NOTIFY(received_msg)); break; + case NGAP_HANDOVER_CANCEL: + ngap_gNB_handover_cancel(instance, &NGAP_HANDOVER_CANCEL(received_msg)); + break; + case NGAP_UL_RAN_STATUS_TRANSFER: ngap_gNB_handle_ul_ran_status_transfer(instance, &NGAP_UL_RAN_STATUS_TRANSFER(received_msg)); break; diff --git a/openair3/NGAP/ngap_gNB_handlers.c b/openair3/NGAP/ngap_gNB_handlers.c index 0257e49698..95c2dd45a7 100644 --- a/openair3/NGAP/ngap_gNB_handlers.c +++ b/openair3/NGAP/ngap_gNB_handlers.c @@ -1051,6 +1051,46 @@ static int ngap_gNB_handle_handover_command(sctp_assoc_t assoc_id, uint32_t stre return 0; } +/** @brief Handler for NG Handover Cancel Acknowledge */ +static int ngap_gNB_handle_handover_cancel_ack(sctp_assoc_t assoc_id, uint32_t stream, NGAP_NGAP_PDU_t *pdu) +{ + NGAP_INFO("Received NG Handover Cancel Acknowledge\n"); + ngap_gNB_amf_data_t *amf_desc_p = NULL; + DevAssert(pdu != NULL); + + amf_desc_p = ngap_gNB_get_AMF(NULL, assoc_id, 0); + if (amf_desc_p == NULL) { + NGAP_ERROR("[SCTP %u] Received Handover Cancel Ack for non-existing AMF context\n", assoc_id); + return -1; + } + + MessageDef *message_p = itti_alloc_new_message(TASK_NGAP, 0, NGAP_HANDOVER_CANCEL_ACK); + ngap_handover_cancel_ack_t *msg = &NGAP_HANDOVER_CANCEL_ACK(message_p); + + if (decode_ng_handover_cancel_ack(msg, pdu) < 0) { + NGAP_ERROR("Failed to decode NG Handover Cancel Acknowledge\n"); + itti_free(TASK_NGAP, message_p); + return -1; + } + + ngap_gNB_ue_context_t *ue_desc_p = ngap_get_ue_context(msg->gNB_ue_ngap_id); + if (!ue_desc_p) { + NGAP_ERROR("[SCTP %u] Handover Cancel Ack for unknown UE context (gNB_ue_ngap_id %d)\n", assoc_id, msg->gNB_ue_ngap_id); + itti_free(TASK_NGAP, message_p); + return -1; + } + + if (ue_desc_p->amf_ue_ngap_id != msg->amf_ue_ngap_id) { + NGAP_WARN("AMF UE NGAP ID mismatch in HO Cancel Ack (ctx %ld != msg %ld)", ue_desc_p->amf_ue_ngap_id, msg->amf_ue_ngap_id); + } + + ue_desc_p->rx_stream = stream; + + // cancel was already handled at RRC level, do not forward + + return 0; +} + static int ngap_gNB_handle_paging(sctp_assoc_t assoc_id, uint32_t stream, NGAP_NGAP_PDU_t *pdu) { ngap_gNB_amf_data_t *amf_desc_p = NULL; @@ -1484,7 +1524,7 @@ const ngap_message_decoded_callback ngap_messages_callback[][3] = { {ngap_gNB_handle_dl_ran_status_transfer, 0, 0}, /* DownlinkRANStatusTransfer */ {0, 0, 0}, /* DownlinkUEAssociatedNRPPaTransport */ {ngap_gNB_handle_error_indication, 0, 0}, /* ErrorIndication */ - {0, 0, 0}, /* HandoverCancel */ + {0, ngap_gNB_handle_handover_cancel_ack, 0}, /* HandoverCancel */ {0, 0, 0}, /* HandoverNotification */ {0, ngap_gNB_handle_handover_command, 0}, /* HandoverPreparation */ {ngap_gNB_handle_handover_request, 0, 0}, /* HandoverResourceAllocation */ diff --git a/openair3/NGAP/ngap_gNB_mobility_management.c b/openair3/NGAP/ngap_gNB_mobility_management.c index 4f1383e5e0..3d281be7c9 100644 --- a/openair3/NGAP/ngap_gNB_mobility_management.c +++ b/openair3/NGAP/ngap_gNB_mobility_management.c @@ -618,6 +618,65 @@ NGAP_NGAP_PDU_t *encode_ng_handover_notify(const ngap_handover_notify_t *msg) return pdu; } +/** @brief NGAP Handover Cancel encoding (9.2.3.11 3GPP TS 38.413 Handover Cancel) + * 8.4.5 Handover Cancellation (source NG-RAN -> AMF) */ +NGAP_NGAP_PDU_t *encode_ng_handover_cancel(const ngap_handover_cancel_t *msg) +{ + DevAssert(msg != NULL); + + NGAP_NGAP_PDU_t *pdu = calloc_or_fail(1, sizeof(*pdu)); + + pdu->present = NGAP_NGAP_PDU_PR_initiatingMessage; + asn1cCalloc(pdu->choice.initiatingMessage, head); + head->procedureCode = NGAP_ProcedureCode_id_HandoverCancel; + head->criticality = NGAP_Criticality_reject; + head->value.present = NGAP_InitiatingMessage__value_PR_HandoverCancel; + NGAP_HandoverCancel_t *out = &head->value.choice.HandoverCancel; + + // AMF UE NGAP ID (M) + asn1cSequenceAdd(out->protocolIEs.list, NGAP_HandoverCancelIEs_t, ie1); + ie1->id = NGAP_ProtocolIE_ID_id_AMF_UE_NGAP_ID; + ie1->criticality = NGAP_Criticality_reject; + ie1->value.present = NGAP_HandoverCancelIEs__value_PR_AMF_UE_NGAP_ID; + asn_uint642INTEGER(&ie1->value.choice.AMF_UE_NGAP_ID, msg->amf_ue_ngap_id); + + // RAN UE NGAP ID (M) + asn1cSequenceAdd(out->protocolIEs.list, NGAP_HandoverCancelIEs_t, ie2); + ie2->id = NGAP_ProtocolIE_ID_id_RAN_UE_NGAP_ID; + ie2->criticality = NGAP_Criticality_reject; + ie2->value.present = NGAP_HandoverCancelIEs__value_PR_RAN_UE_NGAP_ID; + ie2->value.choice.RAN_UE_NGAP_ID = msg->gNB_ue_ngap_id; + + // Cause (M) + asn1cSequenceAdd(out->protocolIEs.list, NGAP_HandoverCancelIEs_t, ie3); + ie3->id = NGAP_ProtocolIE_ID_id_Cause; + ie3->criticality = NGAP_Criticality_reject; + ie3->value.present = NGAP_HandoverCancelIEs__value_PR_Cause; + encode_ngap_cause(&ie3->value.choice.Cause, &msg->cause); + + return pdu; +} + +/** @brief Decode NGAP Handover Cancel Acknowledge (9.2.3.12 3GPP TS 38.413) */ +int decode_ng_handover_cancel_ack(ngap_handover_cancel_ack_t *out, const NGAP_NGAP_PDU_t *pdu) +{ + DevAssert(out != NULL); + DevAssert(pdu != NULL); + + NGAP_HandoverCancelAcknowledge_t *container = &pdu->choice.successfulOutcome->value.choice.HandoverCancelAcknowledge; + NGAP_HandoverCancelAcknowledgeIEs_t *ie; + + /* AMF UE NGAP ID (M) */ + NGAP_FIND_PROTOCOLIE_BY_ID(NGAP_HandoverCancelAcknowledgeIEs_t, ie, container, NGAP_ProtocolIE_ID_id_AMF_UE_NGAP_ID, true); + asn_INTEGER2ulong(&ie->value.choice.AMF_UE_NGAP_ID, &out->amf_ue_ngap_id); + + /* RAN UE NGAP ID (M) */ + NGAP_FIND_PROTOCOLIE_BY_ID(NGAP_HandoverCancelAcknowledgeIEs_t, ie, container, NGAP_ProtocolIE_ID_id_RAN_UE_NGAP_ID, true); + out->gNB_ue_ngap_id = ie->value.choice.RAN_UE_NGAP_ID; + + return 0; +} + /** @brief Encode NGAP UL RAN Status Transfer (9.2.3.14 of 3GPP TS 38.413) */ NGAP_NGAP_PDU_t *encode_ng_ul_ran_status_transfer(const ngap_ran_status_transfer_t *msg) { diff --git a/openair3/NGAP/ngap_gNB_mobility_management.h b/openair3/NGAP/ngap_gNB_mobility_management.h index b7a6f3547c..18aac7c61d 100644 --- a/openair3/NGAP/ngap_gNB_mobility_management.h +++ b/openair3/NGAP/ngap_gNB_mobility_management.h @@ -35,5 +35,7 @@ int decode_ng_handover_command(ngap_handover_command_t *msg, NGAP_NGAP_PDU_t *pd void free_ng_handover_command(ngap_handover_command_t *msg); NGAP_NGAP_PDU_t *encode_ng_handover_notify(const ngap_handover_notify_t *msg); NGAP_NGAP_PDU_t *encode_ng_ul_ran_status_transfer(const ngap_ran_status_transfer_t *msg); +NGAP_NGAP_PDU_t *encode_ng_handover_cancel(const ngap_handover_cancel_t *msg); +int decode_ng_handover_cancel_ack(ngap_handover_cancel_ack_t *out, const NGAP_NGAP_PDU_t *pdu); #endif /* NGAP_GNB_MOBILITY_MANAGEMENT_H_ */