From 20e534d483e5dbbb8e2cacc030d8dd74a5104966 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Tue, 25 Mar 2025 10:04:10 +0100 Subject: [PATCH] Add NGAP Handover Required message NGAP encoder and RRC callback on source NG-RAN This commit introduces the NG Mobility Management Procedure known as Handover Preparation. The outbound message is sent by the source NG-RAN and is known as Handover Required. * introduce NGAP library for Mobility Management * introduce NGAP IEs encoder functions for Handover Required and relevant common IEs * handle RRC trigger in NGAP and Send NGAP Handover Required to the AMF Co-authored-by: batuhan duyuler --- CMakeLists.txt | 1 + openair2/COMMON/ngap_messages_def.h | 1 + openair2/COMMON/ngap_messages_types.h | 87 ++++++++- openair2/RRC/NR/rrc_gNB_NGAP.c | 78 ++++++++ openair2/RRC/NR/rrc_gNB_NGAP.h | 5 + openair3/NGAP/ngap_common.c | 29 +++ openair3/NGAP/ngap_common.h | 3 + openair3/NGAP/ngap_gNB.c | 59 ++++++ openair3/NGAP/ngap_gNB_decoder.c | 10 + openair3/NGAP/ngap_gNB_encoder.c | 3 +- openair3/NGAP/ngap_gNB_mobility_management.c | 193 +++++++++++++++++++ openair3/NGAP/ngap_gNB_mobility_management.h | 31 +++ openair3/NGAP/ngap_msg_includes.h | 8 + 13 files changed, 506 insertions(+), 2 deletions(-) create mode 100644 openair3/NGAP/ngap_gNB_mobility_management.c create mode 100644 openair3/NGAP/ngap_gNB_mobility_management.h diff --git a/CMakeLists.txt b/CMakeLists.txt index fa2d497589..058bfd7a39 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -363,6 +363,7 @@ add_library(ngap ${NGAP_DIR}/ngap_gNB.c ${NGAP_DIR}/ngap_common.c ${NGAP_DIR}/ngap_gNB_context_management_procedures.c + ${NGAP_DIR}/ngap_gNB_mobility_management.c ${NGAP_DIR}/ngap_gNB_decoder.c ${NGAP_DIR}/ngap_gNB_encoder.c ${NGAP_DIR}/ngap_gNB_handlers.c diff --git a/openair2/COMMON/ngap_messages_def.h b/openair2/COMMON/ngap_messages_def.h index 4f05a564c4..eea62d6c00 100644 --- a/openair2/COMMON/ngap_messages_def.h +++ b/openair2/COMMON/ngap_messages_def.h @@ -71,6 +71,7 @@ MESSAGE_DEF(NGAP_UE_CTXT_MODIFICATION_FAIL , MESSAGE_PRIORITY_MED, ngap_ue_ctxt_ MESSAGE_DEF(NGAP_PDUSESSION_SETUP_RESP , MESSAGE_PRIORITY_MED, ngap_pdusession_setup_resp_t , ngap_pdusession_setup_resp) MESSAGE_DEF(NGAP_PDUSESSION_MODIFY_RESP , MESSAGE_PRIORITY_MED, ngap_pdusession_modify_resp_t , ngap_pdusession_modify_resp) MESSAGE_DEF(NGAP_PDUSESSION_RELEASE_RESPONSE , MESSAGE_PRIORITY_MED, ngap_pdusession_release_resp_t , ngap_pdusession_release_resp) +MESSAGE_DEF(NGAP_HANDOVER_REQUIRED, MESSAGE_PRIORITY_MED, ngap_handover_required_t, ngap_handover_required) /* NGAP -> RRC messages */ MESSAGE_DEF(NGAP_DOWNLINK_NAS , MESSAGE_PRIORITY_MED, ngap_downlink_nas_t , ngap_downlink_nas ) diff --git a/openair2/COMMON/ngap_messages_types.h b/openair2/COMMON/ngap_messages_types.h index 3ee858ecca..57ecc51e79 100644 --- a/openair2/COMMON/ngap_messages_types.h +++ b/openair2/COMMON/ngap_messages_types.h @@ -65,7 +65,7 @@ #define NGAP_PDUSESSION_SETUP_REQ(mSGpTR) (mSGpTR)->ittiMsg.ngap_pdusession_setup_req #define NGAP_PDUSESSION_MODIFY_REQ(mSGpTR) (mSGpTR)->ittiMsg.ngap_pdusession_modify_req #define NGAP_PAGING_IND(mSGpTR) (mSGpTR)->ittiMsg.ngap_paging_ind - +#define NGAP_HANDOVER_REQUIRED(mSGpTR) (mSGpTR)->ittiMsg.ngap_handover_required #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 #define NGAP_PDUSESSION_RELEASE_RESPONSE(mSGpTR) (mSGpTR)->ittiMsg.ngap_pdusession_release_resp @@ -463,6 +463,91 @@ typedef struct ngap_uplink_nas_s { byte_array_t nas_pdu; } ngap_uplink_nas_t; +typedef struct target_cell_id_s { + plmn_id_t plmn_identity; + uint32_t nrCellIdentity; +} cell_id_t; + +typedef struct target_ran_node_id_s { + uint32_t targetgNBId; + plmn_id_t plmn_identity; + uint32_t tac; +} target_ran_node_id_t; + +/* 3GPP TS 38.413 9.3.1.29 */ +typedef struct { + // QoS Flow Identifier + uint8_t qfi; +} qosflow_info_t; + +/* 3GPP TS 38.413 9.3.1.29 */ +typedef struct { + // PDU Session ID + uint8_t pdusession_id; + // QoS Flow Information List + uint8_t nb_of_qos_flow; + qosflow_info_t qos_flow_info[QOSFLOW_MAX_VALUE]; +} pdusession_resource_info_t; + +/* 3GPP TS 38.413 9.3.1.97 */ +typedef struct { + // Global Cell ID + cell_id_t id; + // Cell type + uint8_t type; + // Time UE Stayed in Cell + long time_in_cell; + // Cause + ngap_cause_t *cause; +} last_visited_ngran_cell_info_t; + +/* 3GPP TS 38.413 9.3.1.29 */ +typedef struct { + // RRC Container: HandoverPreparationInformation message + byte_array_t handoverInfo; + // Target Cell ID + cell_id_t targetCellId; + // PDU Session Resource Information List + uint16_t nb_pdu_session_resource; + pdusession_resource_info_t pdu_session_resource[NGAP_MAX_PDU_SESSION]; + // UE History Information + last_visited_ngran_cell_info_t ue_history_info; +} source_to_target_transparent_container_t; + +typedef enum { + HANDOVER_TYPE_INTRA5GS, // Intra5GS: NG-RAN node to NG-RAN node + HANDOVER_TYPE_5GSTOEPS, // 5GStoEPS: NG-RAN node to eNB + HANDOVER_TYPE_EPSTO5GS, // EPSto5GS: eNB to NG-RAN node + HANDOVER_TYPE_5GSTOUTRA, // 5GStoUTRA: NG-RAN node to UTRA +} ho_type_t; + +/* 3GPP TS 38.413 9.2.3.1 */ +typedef struct { + // PDU Session ID + uint8_t pdusession_id; + // Handover Required Transfer + byte_array_t ho_required_transfer; +} pdusession_resource_t; + +/* 3GPP TS 38.413 9.2.3.1 */ +typedef struct { + // RAN UE NGAP ID + uint32_t gNB_ue_ngap_id; + // AMF UE NGAP ID + uint64_t amf_ue_ngap_id; + // Handover Type + ho_type_t handoverType; + // Cause + ngap_cause_t cause; + // Target ID + target_ran_node_id_t target_gnb_id; + // PDU Session Resource List + uint8_t nb_of_pdusessions; + pdusession_resource_t pdusessions[NGAP_MAX_PDU_SESSION]; + // Source to Target Transparent Container + source_to_target_transparent_container_t *source2target; +} ngap_handover_required_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 407a0d03b7..5632e5932b 100644 --- a/openair2/RRC/NR/rrc_gNB_NGAP.c +++ b/openair2/RRC/NR/rrc_gNB_NGAP.c @@ -49,6 +49,7 @@ #include "NGAP_QosFlowSetupRequestItem.h" #include "NGAP_asn_constant.h" #include "NGAP_ProtocolIE-Field.h" +#include "NGAP_CellSize.h" #include "NR_UE-NR-Capability.h" #include "NR_UERadioAccessCapabilityInformation.h" #include "MAC/mac.h" @@ -1297,3 +1298,80 @@ int rrc_gNB_process_PAGING_IND(MessageDef *msg_p, instance_t instance) return 0; } + +/** @brief Callback for NGAP Handover Required message (3GPP TS 38.413 9.2.3.1) + * Direction: source gNB -> AMF */ +void rrc_gNB_send_NGAP_HANDOVER_REQUIRED(gNB_RRC_INST *rrc, + gNB_RRC_UE_t *UE, + const nr_neighbour_cell_t *neighbour, + const byte_array_t hoPrepInfo) +{ + LOG_I(NR_RRC, "Handover Preparation: send Handover Required (target gNB ID=%d, PCI=%d)\n", neighbour->gNB_ID, neighbour->physicalCellId); + + const plmn_id_t plmn = {.mcc = neighbour->plmn.mnc, + .mnc = neighbour->plmn.mnc, + .mnc_digit_length = neighbour->plmn.mnc_digit_length}; + + const target_ran_node_id_t target = { + .plmn_identity = plmn, + .tac = neighbour->tac, + .targetgNBId = neighbour->gNB_ID, + }; + + ngap_handover_required_t msg = { + .amf_ue_ngap_id = UE->amf_ue_ngap_id, + .gNB_ue_ngap_id = UE->rrc_ue_id, + .nb_of_pdusessions = UE->nb_of_pdusessions, + .cause.type = NGAP_CAUSE_RADIO_NETWORK, + .cause.value = NGAP_CAUSE_RADIO_NETWORK_HANDOVER_DESIRABLE_FOR_RADIO_REASON, + .handoverType = HANDOVER_TYPE_INTRA5GS, + .target_gnb_id = target, + }; + + cell_id_t target_cell = { + .nrCellIdentity = neighbour->nrcell_id, + .plmn_identity = plmn, + }; + + // Source to Target Transparent Container (M) + msg.source2target = calloc_or_fail(1, sizeof(*msg.source2target)); + // Target Cell ID (M) + msg.source2target->targetCellId = target_cell; + // RRC Container (M) + msg.source2target->handoverInfo = copy_byte_array(hoPrepInfo); + // UE History Info (M) + msg.source2target->ue_history_info.cause = malloc_or_fail(sizeof(*msg.source2target->ue_history_info.cause)); + msg.source2target->ue_history_info.cause->type = NGAP_CAUSE_RADIO_NETWORK; + msg.source2target->ue_history_info.cause->value = NGAP_CAUSE_RADIO_NETWORK_HANDOVER_DESIRABLE_FOR_RADIO_REASON; + msg.source2target->ue_history_info.type = NGAP_CellSize_small; + msg.source2target->ue_history_info.time_in_cell = 500; // dummy number for now + msg.source2target->ue_history_info.id = target_cell; + + /* Fill both PDU Session Resource List IE (M) in Handover Required + and PDU Session Resource Information List IE (O) in the Source NG-RAN + Node to Target NG-RAN Node Transparent Container */ + for (int i = 0; i < UE->nb_of_pdusessions; ++i) { + if (UE->pduSession[i].status == PDU_SESSION_STATUS_DONE || UE->pduSession[i].status == PDU_SESSION_STATUS_ESTABLISHED) { + rrc_pdu_session_param_t *pduSession = find_pduSession(UE, UE->pduSession[i].param.pdusession_id, false); + if (!pduSession) + continue; + // Handover Required Transfer (M) + uint8_t ho_required_transfer[128] = {0}; + msg.pdusessions[i].ho_required_transfer.buf = ho_required_transfer; + msg.pdusessions[i].ho_required_transfer.len = sizeof(ho_required_transfer); + // PDU Session ID + msg.pdusessions[i].pdusession_id = pduSession->param.pdusession_id; + + // PDU Session Resource Information List (O) + msg.source2target->pdu_session_resource[i].nb_of_qos_flow = pduSession->param.nb_qos; + for (int j = 0; j < pduSession->param.nb_qos; ++j) { + msg.source2target->pdu_session_resource[i].qos_flow_info[j].qfi = pduSession->param.qos[j].qfi; + } + msg.source2target->nb_pdu_session_resource++; + } + } + + MessageDef *msg_p = itti_alloc_new_message(TASK_RRC_GNB, 0, NGAP_HANDOVER_REQUIRED); + NGAP_HANDOVER_REQUIRED(msg_p) = msg; + itti_send_msg_to_task(TASK_NGAP, rrc->module_id, msg_p); +} diff --git a/openair2/RRC/NR/rrc_gNB_NGAP.h b/openair2/RRC/NR/rrc_gNB_NGAP.h index 22fff8705f..fd8cb31ff1 100644 --- a/openair2/RRC/NR/rrc_gNB_NGAP.h +++ b/openair2/RRC/NR/rrc_gNB_NGAP.h @@ -89,4 +89,9 @@ void nr_rrc_pdcp_config_security(gNB_RRC_UE_t *UE, bool enable_ciphering); int rrc_gNB_process_PAGING_IND(MessageDef *msg_p, instance_t instance); +void rrc_gNB_send_NGAP_HANDOVER_REQUIRED(gNB_RRC_INST *rrc, + gNB_RRC_UE_t *UE, + const nr_neighbour_cell_t *neighbourCellConfiguration, + const byte_array_t hoPrepInfo); + #endif diff --git a/openair3/NGAP/ngap_common.c b/openair3/NGAP/ngap_common.c index 82c2ccaf2a..b51c8e80de 100644 --- a/openair3/NGAP/ngap_common.c +++ b/openair3/NGAP/ngap_common.c @@ -112,3 +112,32 @@ ngap_mobility_restriction_t decode_ngap_mobility_restriction(const NGAP_Mobility TBCD_TO_MCC_MNC(&in->servingPLMN, out.serving_plmn.mcc, out.serving_plmn.mnc, out.serving_plmn.mnc_digit_length); return out; } + +void encode_ngap_target_id(NGAP_HandoverRequiredIEs_t *out, const target_ran_node_id_t *in) +{ + out->id = NGAP_ProtocolIE_ID_id_TargetID; + out->criticality = NGAP_Criticality_reject; + out->value.present = NGAP_HandoverRequiredIEs__value_PR_TargetID; + // CHOICE Target ID: NG-RAN (M) + out->value.choice.TargetID.present = NGAP_TargetID_PR_targetRANNodeID; + asn1cCalloc(out->value.choice.TargetID.choice.targetRANNodeID, targetRan); + // Global RAN Node ID (M) + targetRan->globalRANNodeID.present = NGAP_GlobalRANNodeID_PR_globalGNB_ID; + asn1cCalloc(targetRan->globalRANNodeID.choice.globalGNB_ID, globalGnbId); + globalGnbId->gNB_ID.present = NGAP_GNB_ID_PR_gNB_ID; + MACRO_GNB_ID_TO_BIT_STRING(in->targetgNBId, &globalGnbId->gNB_ID.choice.gNB_ID); + MCC_MNC_TO_PLMNID(in->plmn_identity.mcc, in->plmn_identity.mnc, in->plmn_identity.mnc_digit_length, &globalGnbId->pLMNIdentity); + // Selected TAI (M) + INT24_TO_OCTET_STRING(in->tac, &targetRan->selectedTAI.tAC); + MCC_MNC_TO_PLMNID(in->plmn_identity.mcc, + in->plmn_identity.mnc, + in->plmn_identity.mnc_digit_length, + &targetRan->selectedTAI.pLMNIdentity); +} + +void encode_ngap_nr_cgi(NGAP_NR_CGI_t *out, const plmn_id_t *plmn, const uint32_t cell_id) +{ + out->iE_Extensions = NULL; + MCC_MNC_TO_PLMNID(plmn->mcc, plmn->mnc, plmn->mnc_digit_length, &out->pLMNIdentity); + NR_CELL_ID_TO_BIT_STRING(cell_id, &out->nRCellIdentity); +} diff --git a/openair3/NGAP/ngap_common.h b/openair3/NGAP/ngap_common.h index dd7fdebc54..1e53d9dd1a 100644 --- a/openair3/NGAP/ngap_common.h +++ b/openair3/NGAP/ngap_common.h @@ -87,6 +87,9 @@ ngap_ambr_t decode_ngap_UEAggregateMaximumBitRate(const NGAP_UEAggregateMaximumB nssai_t decode_ngap_nssai(const NGAP_S_NSSAI_t *in); ngap_security_capabilities_t decode_ngap_security_capabilities(const NGAP_UESecurityCapabilities_t *in); ngap_mobility_restriction_t decode_ngap_mobility_restriction(const NGAP_MobilityRestrictionList_t *in); +void encode_ngap_target_id(NGAP_HandoverRequiredIEs_t *out, const target_ran_node_id_t *in); +void encode_ngap_nr_cgi(NGAP_NR_CGI_t *out, const plmn_id_t *plmn, const uint32_t cell_id); +pdusession_level_qos_parameter_t fill_qos(uint8_t qfi, const NGAP_QosFlowLevelQosParameters_t *params); /** @}*/ diff --git a/openair3/NGAP/ngap_gNB.c b/openair3/NGAP/ngap_gNB.c index 8edb5c3aa3..4b4fc2f850 100644 --- a/openair3/NGAP/ngap_gNB.c +++ b/openair3/NGAP/ngap_gNB.c @@ -55,6 +55,8 @@ #include "ngap_gNB_management_procedures.h" #include "ngap_gNB_nas_procedures.h" #include "ngap_messages_types.h" +#include "ngap_gNB_mobility_management.h" +#include "ngap_gNB_ue_context.h" #include "oai_asn1.h" #include "openair3/SECU/kdf.h" #include "queue.h" @@ -275,6 +277,57 @@ void ngap_gNB_handle_sctp_data_ind(sctp_data_ind_t *sctp_data_ind) { AssertFatal (result == EXIT_SUCCESS, "Failed to free memory (%d)!\n", result); } + /** @brief UE Mobility Management: callback for Handover Required */ +int ngap_handover_required(instance_t instance, ngap_handover_required_t *msg) +{ + DevAssert(msg != NULL); + NGAP_DEBUG("Triggered Handover Required\n"); + /* Retrieve the NGAP gNB instance associated with Mod_id */ + ngap_gNB_instance_t *ngap_gNB_instance_p = ngap_gNB_get_instance(instance); + DevAssert(ngap_gNB_instance_p != NULL); + + ngap_gNB_ue_context_t *ue_context_p = NULL; + if ((ue_context_p = ngap_get_ue_context(msg->gNB_ue_ngap_id)) == NULL) { + /* The context for this gNB ue ngap id doesn't exist in the map of gNB UEs */ + NGAP_WARN("Failed to find ue context associated with gNB ue ngap id: 0x%08x\n", msg->gNB_ue_ngap_id); + return -1; + } + + if ((ue_context_p->gNB_ue_ngap_id != msg->gNB_ue_ngap_id)) { + NGAP_ERROR("ue_context_p->gNB_ue_ngap_id %d does not match msg->gNB_ue_ngap_id %d\n", + ue_context_p->gNB_ue_ngap_id, + msg->gNB_ue_ngap_id); + return -1; + } + + NGAP_NGAP_PDU_t *pdu = encode_ng_handover_required(msg); + if (!pdu) { + NGAP_ERROR("Failed to encode Handover Required\n"); + ASN_STRUCT_FREE(asn_DEF_NGAP_NGAP_PDU, pdu); + return -1; + } + + if (LOG_DEBUGFLAG(DEBUG_ASN1)) + xer_fprint(stdout, &asn_DEF_NGAP_NGAP_PDU, &pdu); + + byte_array_t out = { .buf = NULL, .len = 0 }; + if (ngap_gNB_encode_pdu(pdu, &out.buf, (uint32_t *)&out.len) < 0) { + ASN_STRUCT_FREE(asn_DEF_NGAP_NGAP_PDU, pdu); + NGAP_ERROR("Failed to encode Handover Required message\n"); + return -1; + } + free(pdu); + + /* UE associated signalling -> use the allocated stream */ + ngap_gNB_itti_send_sctp_data_req(ngap_gNB_instance_p->instance, + ue_context_p->amf_ref->assoc_id, + out.buf, + out.len, + ue_context_p->tx_stream); + + return 0; +} + void ngap_gNB_init(void) { NGAP_DEBUG("Starting NGAP layer\n"); ngap_gNB_prepare_internal_data(); @@ -356,6 +409,12 @@ void *ngap_gNB_process_itti_msg(void *notUsed) { ngap_gNB_pdusession_release_resp(instance, &NGAP_PDUSESSION_RELEASE_RESPONSE(received_msg)); break; + case NGAP_HANDOVER_REQUIRED: + if (ngap_handover_required(instance, &NGAP_HANDOVER_REQUIRED(received_msg)) < 0) { + NGAP_ERROR("Handover Required failure: indication to RRC is not sent!\n"); + } + break; + default: NGAP_ERROR("Received unhandled message: %d:%s\n", ITTI_MSG_ID(received_msg), ITTI_MSG_NAME(received_msg)); break; diff --git a/openair3/NGAP/ngap_gNB_decoder.c b/openair3/NGAP/ngap_gNB_decoder.c index 4b76abecbb..58c70011cf 100644 --- a/openair3/NGAP/ngap_gNB_decoder.c +++ b/openair3/NGAP/ngap_gNB_decoder.c @@ -70,6 +70,12 @@ static int ngap_gNB_decode_initiating_message(NGAP_NGAP_PDU_t *pdu) { NGAP_INFO("PDUSESSIONSetup initiating message\n"); break; + case NGAP_ProcedureCode_id_HandoverPreparation: + res = asn_encode_to_new_buffer(NULL, ATS_CANONICAL_XER, &asn_DEF_NGAP_NGAP_PDU, pdu); + free(res.buffer); + NGAP_INFO("Handover Preparation 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); @@ -119,6 +125,10 @@ static int ngap_gNB_decode_successful_outcome(NGAP_NGAP_PDU_t *pdu) { free(res.buffer); break; + case NGAP_ProcedureCode_id_HandoverPreparation: + res = asn_encode_to_new_buffer(NULL, ATS_CANONICAL_XER, &asn_DEF_NGAP_NGAP_PDU, pdu); + free(res.buffer); + break; default: NGAP_ERROR("Unknown procedure ID (%d) for successfull outcome message\n", diff --git a/openair3/NGAP/ngap_gNB_encoder.c b/openair3/NGAP/ngap_gNB_encoder.c index c3c095541d..a6e91ba9c8 100644 --- a/openair3/NGAP/ngap_gNB_encoder.c +++ b/openair3/NGAP/ngap_gNB_encoder.c @@ -52,7 +52,8 @@ static inline int ngap_gNB_encode_initiating(NGAP_NGAP_PDU_t *pdu, uint8_t **buf NGAP_ProcedureCode_id_NASNonDeliveryIndication, NGAP_ProcedureCode_id_UEContextReleaseRequest, NGAP_ProcedureCode_id_PathSwitchRequest, - NGAP_ProcedureCode_id_PDUSessionResourceModifyIndication}; + NGAP_ProcedureCode_id_PDUSessionResourceModifyIndication, + NGAP_ProcedureCode_id_HandoverPreparation}; int i; for (i = 0; i < sizeofArray(tmp); i++) if (pdu->choice.initiatingMessage->procedureCode == tmp[i]) diff --git a/openair3/NGAP/ngap_gNB_mobility_management.c b/openair3/NGAP/ngap_gNB_mobility_management.c new file mode 100644 index 0000000000..4f116c8f34 --- /dev/null +++ b/openair3/NGAP/ngap_gNB_mobility_management.c @@ -0,0 +1,193 @@ +/* + * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The OpenAirInterface Software Alliance licenses this file to You under + * the OAI Public License, Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.openairinterface.org/?page_id=698 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *------------------------------------------------------------------------------- + * For more information about the OpenAirInterface (OAI) Software Alliance: + * contact@openairinterface.org + */ + +#include +#include +#include +#include "ngap_common.h" +#include "ngap_msg_includes.h" +#include "ngap_gNB_defs.h" +#include "ngap_gNB_ue_context.h" +#include "oai_asn1.h" +#include "ngap_gNB_management_procedures.h" +#include "ngap_gNB_encoder.h" +#include "ngap_gNB_itti_messaging.h" + +/** @brief UE Mobility Management: encode Handover Required + * (9.2.3.1 of 3GPP TS 38.413) NG-RAN node → AMF */ +NGAP_NGAP_PDU_t *encode_ng_handover_required(const ngap_handover_required_t *msg) +{ + NGAP_NGAP_PDU_t *pdu = malloc_or_fail(sizeof(*pdu)); + + /* Prepare the NGAP message to encode */ + pdu->present = NGAP_NGAP_PDU_PR_initiatingMessage; + asn1cCalloc(pdu->choice.initiatingMessage, head); + head->procedureCode = NGAP_ProcedureCode_id_HandoverPreparation; + head->criticality = NGAP_Criticality_reject; + head->value.present = NGAP_InitiatingMessage__value_PR_HandoverRequired; + NGAP_HandoverRequired_t *out = &head->value.choice.HandoverRequired; + + // AMF UE NGAP ID (M) + asn1cSequenceAdd(out->protocolIEs.list, NGAP_HandoverRequiredIEs_t, ie1); + ie1->id = NGAP_ProtocolIE_ID_id_AMF_UE_NGAP_ID; + ie1->criticality = NGAP_Criticality_reject; + ie1->value.present = NGAP_HandoverRequiredIEs__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_HandoverRequiredIEs_t, ie2); + ie2->id = NGAP_ProtocolIE_ID_id_RAN_UE_NGAP_ID; + ie2->criticality = NGAP_Criticality_reject; + ie2->value.present = NGAP_HandoverRequiredIEs__value_PR_RAN_UE_NGAP_ID; + ie2->value.choice.RAN_UE_NGAP_ID = msg->gNB_ue_ngap_id; + + // Handover Type (M) + asn1cSequenceAdd(out->protocolIEs.list, NGAP_HandoverRequiredIEs_t, ie3); + ie3->id = NGAP_ProtocolIE_ID_id_HandoverType; + ie3->criticality = NGAP_Criticality_reject; + ie3->value.present = NGAP_HandoverRequiredIEs__value_PR_HandoverType; + ie3->value.choice.HandoverType = msg->handoverType; + + // Cause (M) + asn1cSequenceAdd(out->protocolIEs.list, NGAP_HandoverRequiredIEs_t, ie4); + ie4->id = NGAP_ProtocolIE_ID_id_Cause; + ie4->criticality = NGAP_Criticality_ignore; + ie4->value.present = NGAP_HandoverRequiredIEs__value_PR_Cause; + encode_ngap_cause(&ie4->value.choice.Cause, &msg->cause); + + // Target ID (M) + asn1cSequenceAdd(out->protocolIEs.list, NGAP_HandoverRequiredIEs_t, ie5); + encode_ngap_target_id(ie5, &msg->target_gnb_id); + + // PDU Session Resource List (M) + asn1cSequenceAdd(out->protocolIEs.list, NGAP_HandoverRequiredIEs_t, ie6); + ie6->id = NGAP_ProtocolIE_ID_id_PDUSessionResourceListHORqd; + ie6->criticality = NGAP_Criticality_reject; + ie6->value.present = NGAP_HandoverRequiredIEs__value_PR_PDUSessionResourceListHORqd; + for (int i = 0; i < msg->nb_of_pdusessions; ++i) { + asn1cSequenceAdd(ie6->value.choice.PDUSessionResourceListHORqd.list, NGAP_PDUSessionResourceItemHORqd_t, hoRequiredPduSession); + // PDU Session ID (M) + hoRequiredPduSession->pDUSessionID = msg->pdusessions[i].pdusession_id; + // Handover Required Transfer (M) + NGAP_HandoverRequiredTransfer_t hoRequiredTransfer = {0}; + uint8_t ho_req_transfer_transparent_container_buffer[128] = {0}; + if (LOG_DEBUGFLAG(DEBUG_ASN1)) + xer_fprint(stdout, &asn_DEF_NGAP_HandoverRequiredTransfer, &hoRequiredTransfer); + asn_enc_rval_t enc_rval = aper_encode_to_buffer(&asn_DEF_NGAP_HandoverRequiredTransfer, + NULL, + &hoRequiredTransfer, + ho_req_transfer_transparent_container_buffer, + 128); + AssertFatal(enc_rval.encoded > 0, "ASN1 message encoding failed (%s, %lu)!\n", enc_rval.failed_type->name, enc_rval.encoded); + hoRequiredPduSession->handoverRequiredTransfer.buf = CALLOC(1, (enc_rval.encoded + 7) / 8); + memcpy(hoRequiredPduSession->handoverRequiredTransfer.buf, + ho_req_transfer_transparent_container_buffer, + (enc_rval.encoded + 7) / 8); + hoRequiredPduSession->handoverRequiredTransfer.size = (enc_rval.encoded + 7) / 8; + + ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_NGAP_HandoverRequiredTransfer, &hoRequiredTransfer); + } + + // Source NG-RAN Node to Target NG-RAN Node Transparent Container (M) + asn1cSequenceAdd(out->protocolIEs.list, NGAP_HandoverRequiredIEs_t, ie); + ie->id = NGAP_ProtocolIE_ID_id_SourceToTarget_TransparentContainer; + ie->criticality = NGAP_Criticality_reject; + ie->value.present = NGAP_HandoverRequiredIEs__value_PR_SourceToTarget_TransparentContainer; + + NGAP_SourceNGRANNode_ToTargetNGRANNode_TransparentContainer_t *source2target = calloc_or_fail(1, sizeof(*source2target)); + + // RRC Container (M) (HandoverPreparationInformation) + source2target->rRCContainer.size = msg->source2target->handoverInfo.len; + source2target->rRCContainer.buf = malloc_or_fail(msg->source2target->handoverInfo.len); + memcpy(source2target->rRCContainer.buf, msg->source2target->handoverInfo.buf, source2target->rRCContainer.size); + + // PDU Session Resource Information List (O) + asn1cCalloc(source2target->pDUSessionResourceInformationList, pduSessionList); + for (uint8_t i = 0; i < msg->nb_of_pdusessions; ++i) { + const pdusession_resource_t *pduSession = &msg->pdusessions[i]; + NGAP_DEBUG("Handover Required: preparing PDU Session Resource Information List for PDU Session ID %d\n", + pduSession->pdusession_id); + asn1cSequenceAdd(pduSessionList->list, NGAP_PDUSessionResourceInformationItem_t, item); + // PDU Session ID (M) + item->pDUSessionID = msg->source2target->pdu_session_resource[i].pdusession_id; + // QoS Flow Information List (M) + for (int q = 0; q < msg->source2target->pdu_session_resource[i].nb_of_qos_flow; ++q) { + asn1cSequenceAdd(item->qosFlowInformationList.list, NGAP_QosFlowInformationItem_t, qosFlowInfo); + qosFlowInfo->qosFlowIdentifier = msg->source2target->pdu_session_resource[i].qos_flow_info[q].qfi; + } + } + + // Target Cell ID (NG-RAN CGI) (M) + source2target->targetCell_ID.present = NGAP_NGRAN_CGI_PR_nR_CGI; + asn1cCalloc(source2target->targetCell_ID.choice.nR_CGI, tNrCGI); + encode_ngap_nr_cgi(tNrCGI, &msg->target_gnb_id.plmn_identity, msg->source2target->targetCellId.nrCellIdentity); + + // UE history Information (M) + asn1cSequenceAdd(source2target->uEHistoryInformation.list, NGAP_LastVisitedCellItem_t, lastVisitedCell); + lastVisitedCell->iE_Extensions = NULL; + // Last Visited Cell Information (M) + lastVisitedCell->lastVisitedCellInformation.present = NGAP_LastVisitedCellInformation_PR_nGRANCell; + // CHOICE (M): NG-RAN Cell + asn1cCalloc(lastVisitedCell->lastVisitedCellInformation.choice.nGRANCell, lastVisitedNR); + // Cell Type (M) + lastVisitedNR->cellType.cellSize = msg->source2target->ue_history_info.type; + // Global Cell ID (M) + lastVisitedNR->globalCellID.present = NGAP_NGRAN_CGI_PR_nR_CGI; + asn1cCalloc(lastVisitedNR->globalCellID.choice.nR_CGI, lastVisitedNrCGI); + cell_id_t *cell = &msg->source2target->ue_history_info.id; + encode_ngap_nr_cgi(lastVisitedNrCGI, &cell->plmn_identity, cell->nrCellIdentity); + // HO Cause Value (O) + if (msg->source2target->ue_history_info.cause) { + asn1cCalloc(lastVisitedNR->hOCauseValue, lastVisitedCause); + encode_ngap_cause(lastVisitedCause, msg->source2target->ue_history_info.cause); + } + // Time UE Stayed in Cell (M) + lastVisitedNR->timeUEStayedInCell = msg->source2target->ue_history_info.time_in_cell; + + if (LOG_DEBUGFLAG(DEBUG_ASN1)) + xer_fprint(stdout, &asn_DEF_NGAP_SourceNGRANNode_ToTargetNGRANNode_TransparentContainer, source2target); + + uint8_t source_to_target_transparent_container_buf[16384] = {0}; + asn_enc_rval_t enc_rval = aper_encode_to_buffer(&asn_DEF_NGAP_SourceNGRANNode_ToTargetNGRANNode_TransparentContainer, + NULL, + (void *)source2target, + (void *)&source_to_target_transparent_container_buf, + 16384); + ASN_STRUCT_FREE(asn_DEF_NGAP_SourceNGRANNode_ToTargetNGRANNode_TransparentContainer, source2target); + if (enc_rval.encoded < 0) { + AssertFatal(enc_rval.encoded > 0, + "HO LOG: Source to Transparent ASN1 message encoding failed (%s, %lu)!\n", + enc_rval.failed_type->name, + enc_rval.encoded); + return NULL; + } + + int total_bytes = (enc_rval.encoded + 7) / 8; + int ret = OCTET_STRING_fromBuf(&ie->value.choice.SourceToTarget_TransparentContainer, + (const char *)&source_to_target_transparent_container_buf, + total_bytes); + if (ret != 0) { + LOG_E(NR_RRC, "HO LOG: Can not perform OCTET_STRING_fromBuf for the SourceToTarget_TransparentContainer"); + return NULL; + } + + return pdu; +} diff --git a/openair3/NGAP/ngap_gNB_mobility_management.h b/openair3/NGAP/ngap_gNB_mobility_management.h new file mode 100644 index 0000000000..b3926877eb --- /dev/null +++ b/openair3/NGAP/ngap_gNB_mobility_management.h @@ -0,0 +1,31 @@ +/* + * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The OpenAirInterface Software Alliance licenses this file to You under + * the OAI Public License, Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.openairinterface.org/?page_id=698 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *------------------------------------------------------------------------------- + * For more information about the OpenAirInterface (OAI) Software Alliance: + * contact@openairinterface.org + */ + +#ifndef NGAP_GNB_MOBILITY_MANAGEMENT_H_ +#define NGAP_GNB_MOBILITY_MANAGEMENT_H_ + +#include +#include "common/platform_types.h" +#include "ngap_messages_types.h" + +NGAP_NGAP_PDU_t *encode_ng_handover_required(const ngap_handover_required_t *msg); + +#endif /* NGAP_GNB_MOBILITY_MANAGEMENT_H_ */ diff --git a/openair3/NGAP/ngap_msg_includes.h b/openair3/NGAP/ngap_msg_includes.h index 1debc2e9f1..cf7d0d9f49 100644 --- a/openair3/NGAP/ngap_msg_includes.h +++ b/openair3/NGAP/ngap_msg_includes.h @@ -78,5 +78,13 @@ #include "NGAP_Dynamic5QIDescriptor.h" #include "NGAP_PDUSessionResourceModifyRequestTransfer.h" #include "NGAP_QosFlowAddOrModifyRequestItem.h" +#include "NGAP_PDUSessionResourceItemHORqd.h" +#include "NGAP_PDUSessionResourceInformationItem.h" +#include "NGAP_QosFlowInformationItem.h" +#include "NGAP_LastVisitedCellItem.h" +#include "NGAP_LastVisitedNGRANCellInformation.h" +#include "NGAP_PDUSessionResourceInformationList.h" +#include "NGAP_HandoverRequiredTransfer.h" +#include "NGAP_SourceNGRANNode-ToTargetNGRANNode-TransparentContainer.h" #endif // NGAP_MSG_INCLUDES_H