Add N2 Handover Cancel (8.4.5 of 3GPP TS 38.413)

This commit completes the NGAP Mobility Management procedure for N2 handover.
Upon failure on source gNB the Handover Cancel message is sent to the AMF which
in return responds with an ack.
This commit is contained in:
Guido Casati
2025-09-18 11:34:31 +02:00
parent 4facb4d635
commit 85f586c76d
9 changed files with 215 additions and 1 deletions
+55
View File
@@ -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;
+41 -1
View File
@@ -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 */
@@ -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)
{
@@ -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_ */