Add NGAP Handover Command decoder and RRC handler on source gNB

* add NG Handover Command message decoder
* process in RRC: encode RRCReconfiguration message
  from the received HandoverCommandMessage
* trigger RRCReconfiguration for handover

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 6fd02ee7e8
commit f66524879d
14 changed files with 269 additions and 2 deletions
+1
View File
@@ -85,6 +85,7 @@ MESSAGE_DEF(NGAP_PDUSESSION_MODIFY_REQ , MESSAGE_PRIORITY_MED, ngap_pd
MESSAGE_DEF(NGAP_PDUSESSION_RELEASE_COMMAND , MESSAGE_PRIORITY_MED, ngap_pdusession_release_command_t , ngap_pdusession_release_command)
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)
/* 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)
+32
View File
@@ -69,6 +69,7 @@
#define NGAP_HANDOVER_FAILURE(mSGpTR) (mSGpTR)->ittiMsg.ngap_handover_failure
#define NGAP_HANDOVER_REQUEST(mSGpTR) (mSGpTR)->ittiMsg.ngap_handover_request
#define NGAP_HANDOVER_REQUEST_ACKNOWLEDGE(mSGpTR) (mSGpTR)->ittiMsg.ngap_handover_request_ack
#define NGAP_HANDOVER_COMMAND(mSGpTR) (mSGpTR)->ittiMsg.ngap_handover_command
#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
@@ -648,6 +649,37 @@ typedef struct {
byte_array_t target2source;
} ngap_handover_request_ack_t;
/* 9.3.4.10 3GPP TS 38.413 */
typedef struct {
// QoS Flow to be Forwarded List
uint8_t nb_of_qos_flow;
pdusession_associate_qosflow_t qos_setup_list[QOSFLOW_MAX_VALUE];
// UL Forwarding UP TNL Information
uint32_t gtp_teid;
transport_layer_addr_t gNB_addr;
} ho_command_transfer_t;
typedef struct {
// PDU Session ID (M)
uint8_t pdusession_id;
// Handover Command Transfer (M)
ho_command_transfer_t ho_command_transfer;
} pdusession_resource_handover_t;
typedef struct {
// AMF UE NGAP ID
uint64_t amf_ue_ngap_id;
// RAN UE NGAP ID
uint32_t gNB_ue_ngap_id;
// Handover Type
ho_type_t handoverType;
// PDU Session Resource Handover List
uint8_t nb_of_pdusessions;
pdusession_resource_handover_t pdu_sessions[NGAP_MAX_PDU_SESSION];
// Target to Source Transparent Container
byte_array_t handoverCommand;
} ngap_handover_command_t;
typedef struct ngap_ue_cap_info_ind_s {
uint32_t gNB_ue_ngap_id;
byte_array_t ue_radio_cap;
+39
View File
@@ -64,6 +64,10 @@
#include "NR_LogicalChannelConfig.h"
#include "NR_PDCP-Config.h"
#include "NR_MAC-CellGroupConfig.h"
#include "NR_HandoverPreparationInformation.h"
#include "NR_HandoverCommand.h"
#include "NR_HandoverCommand-IEs.h"
#include "NR_AS-Config.h"
#include "NR_SecurityModeCommand.h"
#include "NR_CipheringAlgorithm.h"
#include "NR_RRCReconfiguration-IEs.h"
@@ -1221,6 +1225,41 @@ void fill_removal_lists_from_source_measConfig(NR_MeasConfig_t *currentMC, byte_
}
}
int doRRCReconfiguration_from_HandoverCommand(byte_array_t *ba, const byte_array_t handoverCommand)
{
// 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;
}
// 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;
asn1cCalloc(c1->choice.rrcReconfiguration, rrcReconf);
// 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 (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);
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);
}
byte_array_t get_HandoverCommandMessage(nr_rrc_reconfig_param_t *params)
{
// Encode RRCReconfiguration to buffer
+1
View File
@@ -165,5 +165,6 @@ 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);
#endif /* __RRC_NR_MESSAGES_ASN1_MSG__H__ */
+6
View File
@@ -98,6 +98,7 @@
#include "NR_DL-DCCH-Message.h"
#include "ds/byte_array.h"
#include "alg/find.h"
#include "NR_HandoverCommand.h"
#ifdef E2_AGENT
#include "openair2/E2AP/RAN_FUNCTION/O-RAN/ran_func_rc_extern.h"
@@ -2949,6 +2950,11 @@ void *rrc_gnb_task(void *args_p) {
rrc_gNB_free_Handover_Request(&NGAP_HANDOVER_REQUEST(msg_p)); // Free transfered NG message
break;
case NGAP_HANDOVER_COMMAND:
rrc_gNB_process_HandoverCommand(RC.nrrrc[instance], &NGAP_HANDOVER_COMMAND(msg_p));
rrc_gNB_free_Handover_Command(&NGAP_HANDOVER_COMMAND(msg_p)); // Free transfered NG message
break;
default:
LOG_E(NR_RRC, "[gNB %ld] Received unexpected message %s\n", instance, msg_name_p);
break;
+28
View File
@@ -39,6 +39,9 @@
#include <arpa/inet.h>
#include "E1AP_ConfidentialityProtectionIndication.h"
#include "E1AP_IntegrityProtectionIndication.h"
#include "NR_UE-CapabilityRAT-ContainerList.h"
#include "NR_HandoverCommand.h"
#include "NR_HandoverCommand-IEs.h"
#include "NGAP_CauseRadioNetwork.h"
#include "NGAP_Dynamic5QIDescriptor.h"
#include "NGAP_GTPTunnel.h"
@@ -1161,6 +1164,31 @@ void rrc_gNB_free_Handover_Request(ngap_handover_request_t *msg)
free(msg->mobility_restriction);
}
/** @brief Process NG Handover Command on Source gNB */
void rrc_gNB_process_HandoverCommand(gNB_RRC_INST *rrc, const ngap_handover_command_t *msg)
{
rrc_gNB_ue_context_t *ue_context_p = rrc_gNB_get_ue_context(rrc, msg->gNB_ue_ngap_id);
if (ue_context_p == NULL) {
LOG_W(NR_RRC, "Unknown UE context associated to gNB_ue_ngap_id (%u)\n", msg->gNB_ue_ngap_id);
return;
}
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);
rrc_gNB_trigger_reconfiguration_for_handover(rrc, UE, buffer, enc);
LOG_A(NR_RRC, "Send reconfiguration (HO Command) to UE %u/RNTI %04x\n", UE->rrc_ue_id, UE->rnti);
}
void rrc_gNB_free_Handover_Command(ngap_handover_command_t *msg)
{
free_byte_array(msg->handoverCommand);
}
/*
* Process the NG command NGAP_UE_CONTEXT_RELEASE_COMMAND, sent by AMF.
* The gNB should remove all pdu session, NG context, and other context of the UE.
+3
View File
@@ -101,4 +101,7 @@ void rrc_gNB_free_Handover_Request(ngap_handover_request_t *msg);
void rrc_gNB_send_NGAP_HANDOVER_REQUEST_ACKNOWLEDGE(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, byte_array_t ho_command);
void rrc_gNB_process_HandoverCommand(gNB_RRC_INST *rrc, const ngap_handover_command_t *msg);
void rrc_gNB_free_Handover_Command(ngap_handover_command_t *msg);
#endif
+2 -1
View File
@@ -231,7 +231,8 @@ static void rrc_deliver_ue_ctxt_modif_req(void *deliver_pdu_data, ue_id_t ue_id,
data->modification_req->rrc_container = &ba;
data->rrc->mac_rrc.ue_context_modification_request(data->assoc_id, data->modification_req);
}
static void rrc_gNB_trigger_reconfiguration_for_handover(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue, uint8_t *rrc_reconf, int rrc_reconf_len)
void rrc_gNB_trigger_reconfiguration_for_handover(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue, uint8_t *rrc_reconf, int rrc_reconf_len)
{
f1_ue_data_t ue_data = cu_get_f1_ue_data(ue->rrc_ue_id);
+2
View File
@@ -89,4 +89,6 @@ void nr_rrc_trigger_n2_ho(gNB_RRC_INST *rrc,
int serving_pci,
const nr_neighbour_cell_t *neighbour_config);
void rrc_gNB_trigger_reconfiguration_for_handover(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue, uint8_t *rrc_reconf, int rrc_reconf_len);
#endif /* RRC_GNB_MOBILITY_H_ */
+55 -1
View File
@@ -997,6 +997,60 @@ static int ngap_gNB_handle_pdusession_setup_request(sctp_assoc_t assoc_id, uint3
return 0;
}
/** @brief Handler for the NG Handover Command
* AMF -> source NG-RAN */
static int ngap_gNB_handle_handover_command(sctp_assoc_t assoc_id, uint32_t stream, NGAP_NGAP_PDU_t *pdu)
{
NGAP_INFO("Received NG Handover Command\n");
ngap_gNB_amf_data_t *amf_desc_p = NULL;
DevAssert(pdu != NULL);
if ((amf_desc_p = ngap_gNB_get_AMF(NULL, assoc_id, 0)) == NULL) {
NGAP_ERROR(
"[SCTP %u] Received Handover Command for non "
"existing AMF context\n",
assoc_id);
return -1;
}
MessageDef *message_p = itti_alloc_new_message(TASK_NGAP, 0, NGAP_HANDOVER_COMMAND);
ngap_handover_command_t *msg = &NGAP_HANDOVER_COMMAND(message_p);
memset(msg, 0, sizeof(*msg));
if (decode_ng_handover_command(msg, pdu) < 0) {
NGAP_ERROR("Failed to decode NG Handover Command");
free_ng_handover_command(msg);
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] Received HO Command Msg for non existing UE context (gNB_ue_ngap_id %d) \n",
assoc_id,
msg->gNB_ue_ngap_id);
free_ng_handover_command(msg);
itti_free(TASK_NGAP, message_p);
return -1;
}
ue_desc_p->rx_stream = stream;
if (ue_desc_p->amf_ue_ngap_id != msg->amf_ue_ngap_id) {
NGAP_ERROR("UE context amf_ue_ngap_id is different form that of the message (%ld != %ld)",
ue_desc_p->amf_ue_ngap_id,
msg->amf_ue_ngap_id);
free_ng_handover_command(msg);
itti_free(TASK_NGAP, message_p);
return -1;
}
NGAP_DEBUG("Handover Command is parsed with a size of: %ld. Sending to RRC GNB!\n", msg->handoverCommand.len);
itti_send_msg_to_task(TASK_RRC_GNB, amf_desc_p->ngap_gNB_instance->instance, message_p);
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;
@@ -1338,7 +1392,7 @@ const ngap_message_decoded_callback ngap_messages_callback[][3] = {
{ngap_gNB_handle_error_indication, 0, 0}, /* ErrorIndication */
{0, 0, 0}, /* HandoverCancel */
{0, 0, 0}, /* HandoverNotification */
{0, 0, 0}, /* HandoverPreparation */
{0, ngap_gNB_handle_handover_command, 0}, /* HandoverPreparation */
{ngap_gNB_handle_handover_request, 0, 0}, /* HandoverResourceAllocation */
{ngap_gNB_handle_initial_context_request, 0, 0}, /* InitialContextSetup */
{0, 0, 0}, /* InitialUEMessage */
@@ -200,3 +200,11 @@ void tnl_to_bitstring(BIT_STRING_t *out, const transport_layer_addr_t in)
out->bits_unused = 0;
}
}
void bitstring_to_tnl(transport_layer_addr_t *out, const BIT_STRING_t in)
{
if (in.size) {
memcpy(out->buffer, in.buf, in.size);
out->length = in.size;
}
}
@@ -50,4 +50,6 @@ void ngap_gNB_prepare_internal_data(void);
void tnl_to_bitstring(BIT_STRING_t *out, const transport_layer_addr_t in);
void bitstring_to_tnl(transport_layer_addr_t *out, const BIT_STRING_t in);
#endif /* NGAP_GNB_MANAGEMENT_PROCEDURES_H_ */
@@ -472,3 +472,91 @@ void free_ng_handover_req_ack(ngap_handover_request_ack_t *msg)
{
free_byte_array(msg->target2source);
}
/** @brief Decoder for the NG Handover Command */
int decode_ng_handover_command(ngap_handover_command_t *msg, NGAP_NGAP_PDU_t *pdu)
{
DevAssert(pdu != NULL);
NGAP_HandoverCommandIEs_t *ie;
NGAP_HandoverCommand_t *container = &pdu->choice.successfulOutcome->value.choice.HandoverCommand;
// AMF UE NGAP ID (M)
NGAP_FIND_PROTOCOLIE_BY_ID(NGAP_HandoverCommandIEs_t, ie, container, NGAP_ProtocolIE_ID_id_AMF_UE_NGAP_ID, true);
asn_INTEGER2ulong(&(ie->value.choice.AMF_UE_NGAP_ID), &msg->amf_ue_ngap_id);
// RAN UE NGAP ID (M)
NGAP_FIND_PROTOCOLIE_BY_ID(NGAP_HandoverCommandIEs_t, ie, container, NGAP_ProtocolIE_ID_id_RAN_UE_NGAP_ID, true);
msg->gNB_ue_ngap_id = ie->value.choice.RAN_UE_NGAP_ID;
// Handover Type (M)
NGAP_FIND_PROTOCOLIE_BY_ID(NGAP_HandoverCommandIEs_t, ie, container, NGAP_ProtocolIE_ID_id_HandoverType, true);
if (ie->value.choice.HandoverType != HANDOVER_TYPE_INTRA5GS) {
NGAP_ERROR("Only Intra 5GS Handover is supported at the moment!\n");
return -1;
}
// PDU Session Resource Handover List (O)
NGAP_FIND_PROTOCOLIE_BY_ID(NGAP_HandoverCommandIEs_t, ie, container, NGAP_ProtocolIE_ID_id_HandoverType, false);
if (ie != NULL) {
msg->nb_of_pdusessions = ie->value.choice.PDUSessionResourceHandoverList.list.count;
for (int i = 0; i < msg->nb_of_pdusessions; ++i) {
NGAP_PDUSessionResourceHandoverItem_t *item = ie->value.choice.PDUSessionResourceHandoverList.list.array[i];
// PDU Session ID (M)
msg->pdu_sessions[i].pdusession_id = item->pDUSessionID;
// Handover Command Transfer (M)
NGAP_HandoverCommandTransfer_t *hoCommandTransfer = NULL;
asn_dec_rval_t dec_rval = uper_decode_complete(NULL,
&asn_DEF_NGAP_HandoverCommandTransfer,
(void **)&hoCommandTransfer,
item->handoverCommandTransfer.buf,
item->handoverCommandTransfer.size);
if (dec_rval.code != RC_OK) {
NGAP_ERROR("Failed to decode handoverCommandTransfer\n");
continue;
}
// DL Forwarding UP TNL Information (O)
if (hoCommandTransfer->dLForwardingUP_TNLInformation) {
NGAP_UPTransportLayerInformation_t *up_tnl = hoCommandTransfer->dLForwardingUP_TNLInformation;
if (hoCommandTransfer->dLForwardingUP_TNLInformation->present == NGAP_UPTransportLayerInformation_PR_gTPTunnel) {
OCTET_STRING_TO_INT32(&(up_tnl->choice.gTPTunnel->gTP_TEID), msg->pdu_sessions[i].ho_command_transfer.gtp_teid);
bitstring_to_tnl(&msg->pdu_sessions[i].ho_command_transfer.gNB_addr, up_tnl->choice.gTPTunnel->transportLayerAddress);
} else {
NGAP_WARN("Missing DL Forwarding UP TNL Information\n");
}
}
ASN_STRUCT_FREE(asn_DEF_NGAP_HandoverCommandTransfer, hoCommandTransfer);
}
}
// Target to Source Transparent Container (M)
NGAP_FIND_PROTOCOLIE_BY_ID(NGAP_HandoverCommandIEs_t,
ie,
container,
NGAP_ProtocolIE_ID_id_TargetToSource_TransparentContainer,
true);
NGAP_TargetToSource_TransparentContainer_t *target2source = &(ie->value.choice.TargetToSource_TransparentContainer);
NGAP_TargetNGRANNode_ToSourceNGRANNode_TransparentContainer_t *transparentContainer = NULL;
asn_dec_rval_t dec_rval = aper_decode_complete(NULL,
&asn_DEF_NGAP_TargetNGRANNode_ToSourceNGRANNode_TransparentContainer,
(void **)&transparentContainer,
target2source->buf,
target2source->size);
if (dec_rval.code != RC_OK) {
NGAP_ERROR("Failed to decode TargetToSource_TransparentContainer\n");
return -1;
}
msg->handoverCommand = create_byte_array(transparentContainer->rRCContainer.size, transparentContainer->rRCContainer.buf);
ASN_STRUCT_FREE(asn_DEF_NGAP_TargetNGRANNode_ToSourceNGRANNode_TransparentContainer, transparentContainer);
return 0;
}
void free_ng_handover_command(ngap_handover_command_t *msg)
{
free_byte_array(msg->handoverCommand);
}
@@ -31,5 +31,7 @@ NGAP_NGAP_PDU_t *encode_ng_handover_failure(const ngap_handover_failure_t *msg);
int decode_ng_handover_request(ngap_handover_request_t *out, const NGAP_NGAP_PDU_t *pdu);
NGAP_NGAP_PDU_t *encode_ng_handover_request_ack(ngap_handover_request_ack_t *msg);
void free_ng_handover_req_ack(ngap_handover_request_ack_t *msg);
int decode_ng_handover_command(ngap_handover_command_t *msg, NGAP_NGAP_PDU_t *pdu);
void free_ng_handover_command(ngap_handover_command_t *msg);
#endif /* NGAP_GNB_MOBILITY_MANAGEMENT_H_ */