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 <batuhan.duyuler@firecell.io>
This commit is contained in:
Guido Casati
2025-09-18 11:34:30 +02:00
co-authored by batuhan duyuler
parent cfb654e77f
commit 20e534d483
13 changed files with 506 additions and 2 deletions
+1
View File
@@ -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 )
+86 -1
View File
@@ -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;
+78
View File
@@ -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);
}
+5
View File
@@ -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