Inside of passing the RRCReconfiguration buffer, call RRCReconfiguration message generation inside the F1 callback. This will simplify the code in the rrc_CU_process_ue_context_setup_response() function once the N2 handover case is introduced. In rrc_CU_process_ue_context_setup_response(), with presence of the HO context, the HO ACK callback is called only once and depending on the type of HO it will do either RRCReconfiguration for F1 and HandoverCommand for N2.
380 lines
16 KiB
C
380 lines
16 KiB
C
/*
|
|
* 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 <stdlib.h>
|
|
|
|
#include "assertions.h"
|
|
|
|
#include "rrc_gNB_mobility.h"
|
|
|
|
#include "nr_rrc_proto.h"
|
|
#include "rrc_gNB_du.h"
|
|
#include "rrc_gNB_radio_bearers.h"
|
|
#include "rrc_gNB_UE_context.h"
|
|
#include "openair2/LAYER2/NR_MAC_COMMON/nr_mac.h"
|
|
#include "openair2/F1AP/f1ap_ids.h"
|
|
#include "MESSAGES/asn1_msg.h"
|
|
#include "nr_pdcp/nr_pdcp_oai_api.h"
|
|
#include "openair3/SECU/key_nas_deriver.h"
|
|
#include "openair2/RRC/NR/rrc_gNB_NGAP.h"
|
|
#include "NR_DL-DCCH-MessageType.h"
|
|
|
|
#ifdef E2_AGENT
|
|
#include "openair2/E2AP/RAN_FUNCTION/O-RAN/ran_func_rc_extern.h"
|
|
#endif
|
|
|
|
typedef enum { HO_CTX_BOTH, HO_CTX_SOURCE, HO_CTX_TARGET } ho_ctx_type_t;
|
|
static nr_handover_context_t *alloc_ho_ctx(ho_ctx_type_t type)
|
|
{
|
|
nr_handover_context_t *ho_ctx = calloc_or_fail(1, sizeof(*ho_ctx));
|
|
if (type == HO_CTX_SOURCE || type == HO_CTX_BOTH) {
|
|
ho_ctx->source = calloc_or_fail(1, sizeof(*ho_ctx->source));
|
|
}
|
|
if (type == HO_CTX_TARGET || type == HO_CTX_BOTH) {
|
|
ho_ctx->target = calloc_or_fail(1, sizeof(*ho_ctx->target));
|
|
}
|
|
return ho_ctx;
|
|
}
|
|
|
|
static void free_ho_ctx(nr_handover_context_t *ho_ctx)
|
|
{
|
|
free(ho_ctx->source);
|
|
free(ho_ctx->target);
|
|
free(ho_ctx);
|
|
}
|
|
|
|
static int fill_drb_to_be_setup(const gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue, f1ap_drb_to_be_setup_t drbs[MAX_DRBS_PER_UE])
|
|
{
|
|
int nb_drb = 0;
|
|
for (int i = 0; i < MAX_DRBS_PER_UE; ++i) {
|
|
drb_t *rrc_drb = &ue->established_drbs[i];
|
|
if (rrc_drb->status == DRB_INACTIVE)
|
|
continue;
|
|
|
|
f1ap_drb_to_be_setup_t *drb = &drbs[nb_drb];
|
|
nb_drb++;
|
|
drb->drb_id = rrc_drb->drb_id;
|
|
drb->rlc_mode = rrc->configuration.um_on_default_drb ? F1AP_RLC_MODE_UM_BIDIR : F1AP_RLC_MODE_AM;
|
|
memcpy(&drb->up_ul_tnl[0].tl_address, &rrc_drb->cuup_tunnel_config.addr.buffer, sizeof(uint8_t) * 4);
|
|
drb->up_ul_tnl[0].port = rrc->eth_params_s.my_portd;
|
|
drb->up_ul_tnl[0].teid = rrc_drb->cuup_tunnel_config.teid;
|
|
drb->up_ul_tnl_length = 1;
|
|
|
|
/* fetch an existing PDU session for this DRB */
|
|
rrc_pdu_session_param_t *pdu = find_pduSession_from_drbId(ue, drb->drb_id);
|
|
AssertFatal(pdu != NULL, "no PDU session for DRB ID %ld\n", drb->drb_id);
|
|
drb->nssai = pdu->param.nssai;
|
|
|
|
// for the moment, we only support one QoS flow. Put a reminder in case
|
|
// this changes
|
|
AssertFatal(pdu->param.nb_qos == 1, "only 1 Qos flow supported\n");
|
|
drb->drb_info.flows_to_be_setup_length = 1;
|
|
drb->drb_info.flows_mapped_to_drb = calloc_or_fail(1, sizeof(drb->drb_info.flows_mapped_to_drb[0]));
|
|
AssertFatal(drb->drb_info.flows_mapped_to_drb, "could not allocate memory\n");
|
|
int qfi = rrc_drb->cnAssociation.sdap_config.mappedQoS_FlowsToAdd[0];
|
|
DevAssert(qfi > 0);
|
|
drb->drb_info.flows_mapped_to_drb[0].qfi = qfi;
|
|
pdusession_level_qos_parameter_t *in_qos_char = get_qos_characteristics(qfi, pdu);
|
|
drb->drb_info.flows_mapped_to_drb[0].qos_params.qos_characteristics = get_qos_char_from_qos_flow_param(in_qos_char);
|
|
|
|
/* the DRB QoS parameters: we just reuse the ones from the first flow */
|
|
drb->drb_info.drb_qos = drb->drb_info.flows_mapped_to_drb[0].qos_params;
|
|
}
|
|
return nb_drb;
|
|
}
|
|
|
|
/* \brief Initiate a handover of UE to a specific target cell handled by this
|
|
* CU.
|
|
* \param ue a UE context for which the handover should be triggered. The UE
|
|
* context must be non-null (if the HO request comes from "outside" (N2, Xn),
|
|
* the UE contex must be created first.
|
|
* \param target_du the DU towards which to handover. Note: currently, the CU
|
|
* is limited to one cell per DU, so DU and cell are equivalent here.
|
|
* \param ho_ctxt contextual data for the type of handover (F1, N2, Xn) */
|
|
static void nr_initiate_handover(const gNB_RRC_INST *rrc,
|
|
gNB_RRC_UE_t *ue,
|
|
const nr_rrc_du_container_t *source_du,
|
|
const nr_rrc_du_container_t *target_du,
|
|
byte_array_t *ho_prep_info,
|
|
ho_req_ack_t ack,
|
|
ho_success_t success,
|
|
ho_cancel_t cancel)
|
|
{
|
|
DevAssert(rrc != NULL);
|
|
DevAssert(ue != NULL);
|
|
DevAssert(target_du != NULL);
|
|
// source_du might be NULL -> inter-CU handover
|
|
DevAssert(ho_prep_info->buf != NULL && ho_prep_info->len > 0);
|
|
|
|
if (ue->ho_context != NULL) {
|
|
LOG_E(NR_RRC, "handover for UE %u ongoing, cannot trigger new\n", ue->rrc_ue_id);
|
|
return;
|
|
}
|
|
// if any reconfiguration is ongoing, abort handover request
|
|
for (int i = 0; i < NR_RRC_TRANSACTION_IDENTIFIER_NUMBER; ++i) {
|
|
if (ue->xids[i] != RRC_ACTION_NONE) {
|
|
LOG_E(NR_RRC, "UE %d: ongoig transaction %d (action %d), cannot trigger handover\n", ue->rrc_ue_id, i, ue->xids[i]);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// if we know the source DU (F1 handover), allocate the context for it,
|
|
// otherwise only for the target DU (N2, Xn)
|
|
ho_ctx_type_t ctx_type = source_du != NULL ? HO_CTX_BOTH : HO_CTX_TARGET;
|
|
nr_handover_context_t *ho_ctx = alloc_ho_ctx(ctx_type);
|
|
ho_ctx->target->du = target_du;
|
|
// we will know target->{du_ue_id,new_rnti} once we have UE ctxt setup
|
|
// response
|
|
ho_ctx->target->ho_req_ack = ack;
|
|
ho_ctx->target->ho_success = success;
|
|
|
|
const f1_ue_data_t ue_data = cu_get_f1_ue_data(ue->rrc_ue_id);
|
|
if (source_du != NULL) {
|
|
DevAssert(source_du->assoc_id == ue_data.du_assoc_id);
|
|
// we also have the source DU (F1 handover), store meta info
|
|
ho_ctx->source->ho_cancel = cancel;
|
|
|
|
ho_ctx->source->du = source_du;
|
|
ho_ctx->source->du_ue_id = ue_data.secondary_ue;
|
|
ho_ctx->source->old_rnti = ue->rnti;
|
|
|
|
int result = asn_copy(&asn_DEF_NR_CellGroupConfig, (void **)&ho_ctx->source->old_cellGroupConfig, ue->masterCellGroup);
|
|
AssertFatal(result == 0, "error during asn_copy() of CellGroupConfig\n");
|
|
}
|
|
ue->ho_context = ho_ctx;
|
|
LOG_A(NR_RRC,
|
|
"Handover triggered for UE %u/RNTI %04x towards DU %ld/assoc_id %d/PCI %d\n",
|
|
ue->rrc_ue_id,
|
|
ue->rnti,
|
|
target_du->setup_req->gNB_DU_id,
|
|
target_du->assoc_id,
|
|
target_du->setup_req->cell[0].info.nr_pci);
|
|
|
|
cu_to_du_rrc_information_t cu2du = {
|
|
.handoverPreparationInfo = ho_prep_info->buf,
|
|
.handoverPreparationInfo_length = ho_prep_info->len,
|
|
};
|
|
|
|
f1ap_drb_to_be_setup_t drbs[MAX_DRBS_PER_UE] = {0};
|
|
int nb_drb = fill_drb_to_be_setup(rrc, ue, drbs);
|
|
|
|
f1ap_srb_to_be_setup_t srbs[2] = {{.srb_id = 1, .lcid = 1}, {.srb_id = 2, .lcid = 2}};
|
|
f1ap_served_cell_info_t *cell_info = &target_du->setup_req->cell[0].info;
|
|
RETURN_IF_INVALID_ASSOC_ID(target_du->assoc_id);
|
|
f1ap_ue_context_setup_t ue_context_setup_req = {
|
|
.gNB_CU_ue_id = ue->rrc_ue_id,
|
|
.gNB_DU_ue_id = 0, /* TODO: this should be optional! */
|
|
.plmn.mcc = cell_info->plmn.mcc,
|
|
.plmn.mnc = cell_info->plmn.mnc,
|
|
.plmn.mnc_digit_length = cell_info->plmn.mnc_digit_length,
|
|
.nr_cellid = cell_info->nr_cellid,
|
|
.servCellId = 0, // TODO: correct value?
|
|
.srbs_to_be_setup_length = 2,
|
|
.srbs_to_be_setup = srbs,
|
|
.drbs_to_be_setup_length = nb_drb,
|
|
.drbs_to_be_setup = drbs,
|
|
.cu_to_du_rrc_information = &cu2du,
|
|
};
|
|
rrc->mac_rrc.ue_context_setup_request(target_du->assoc_id, &ue_context_setup_req);
|
|
}
|
|
|
|
/*
|
|
void nr_rrc_trigger_n2_ho(gNB_RRC_INST *rrc, int nr_cgi, uint8_t *ho_prep_info, uint32_t *ho_prep_len, void *pdu_session)
|
|
{
|
|
// call from outside (via NGAP message), with the request for handover for a
|
|
// (any) UE. This handler should, in order:
|
|
//
|
|
// 1. look up the target DU =du= via get_du_by_cell_id(nr_cgi)
|
|
// (and reject the request if it can't find the DU)
|
|
// 2. create a context for a new UE =UE=, based on pdu_session information
|
|
// 3. call nr_initiate_handover(rrc, UE, NULL, du, ho_prep_info, ho_prep_len);
|
|
//
|
|
// after success of the DU, send the result via =success_ptr= to the source
|
|
// CU.
|
|
}
|
|
*/
|
|
|
|
typedef struct deliver_ue_ctxt_modification_data_t {
|
|
gNB_RRC_INST *rrc;
|
|
f1ap_ue_context_modif_req_t *modification_req;
|
|
sctp_assoc_t assoc_id;
|
|
} deliver_ue_ctxt_modification_data_t;
|
|
static void rrc_deliver_ue_ctxt_modif_req(void *deliver_pdu_data, ue_id_t ue_id, int srb_id, char *buf, int size, int sdu_id)
|
|
{
|
|
DevAssert(deliver_pdu_data != NULL);
|
|
deliver_ue_ctxt_modification_data_t *data = deliver_pdu_data;
|
|
data->modification_req->rrc_container = (uint8_t*)buf;
|
|
data->modification_req->rrc_container_length = size;
|
|
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)
|
|
{
|
|
f1_ue_data_t ue_data = cu_get_f1_ue_data(ue->rrc_ue_id);
|
|
|
|
TransmActionInd_t transmission_action_indicator = TransmActionInd_STOP;
|
|
RETURN_IF_INVALID_ASSOC_ID(ue_data.du_assoc_id);
|
|
f1ap_ue_context_modif_req_t ue_context_modif_req = {
|
|
.gNB_CU_ue_id = ue->rrc_ue_id,
|
|
.gNB_DU_ue_id = ue_data.secondary_ue,
|
|
.plmn = rrc->configuration.plmn[0],
|
|
.nr_cellid = rrc->nr_cellid, // TODO target cell ID
|
|
.servCellId = 0, // TODO: correct value?
|
|
.transm_action_ind = &transmission_action_indicator,
|
|
};
|
|
deliver_ue_ctxt_modification_data_t data = {.rrc = rrc,
|
|
.modification_req = &ue_context_modif_req,
|
|
.assoc_id = ue_data.du_assoc_id};
|
|
int srb_id = 1;
|
|
nr_pdcp_data_req_srb(ue->rrc_ue_id,
|
|
srb_id,
|
|
rrc_gNB_mui++,
|
|
rrc_reconf_len,
|
|
(unsigned char *const)rrc_reconf,
|
|
rrc_deliver_ue_ctxt_modif_req,
|
|
&data);
|
|
#ifdef E2_AGENT
|
|
uint32_t message_id = NR_DL_DCCH_MessageType__c1_PR_rrcReconfiguration;
|
|
byte_array_t buffer_ba = {.len = rrc_reconf_len};
|
|
buffer_ba.buf = rrc_reconf;
|
|
signal_rrc_msg(DL_DCCH_NR_RRC_CLASS, message_id, buffer_ba);
|
|
#endif
|
|
}
|
|
|
|
static void nr_rrc_f1_ho_acknowledge(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE)
|
|
{
|
|
/* 3GPP TS 38.331 specifies that PDCP shall be re-established whenever the security key
|
|
* used for the radio bearer changes, with some expections for SRB1 (i.e. when resuming
|
|
* an RRC connection, or at the first reconfiguration after RRC connection reestablishment
|
|
* in NR, do not re-establish PDCP */
|
|
nr_rrc_reconfig_param_t params = get_RRCReconfiguration_params(rrc, UE, (1 << SRB2), true);
|
|
UE->xids[params.transaction_id] = RRC_DEDICATED_RECONF;
|
|
byte_array_t buffer = rrc_gNB_encode_RRCReconfiguration(rrc, UE, params);
|
|
free_RRCReconfiguration_params(params);
|
|
if (!buffer.len) {
|
|
LOG_E(NR_RRC, "UE %d: Failed to generate RRCReconfiguration\n", UE->rrc_ue_id);
|
|
return;
|
|
}
|
|
|
|
// F1 HO: handling of "source CU" information
|
|
DevAssert(UE->ho_context->source != NULL);
|
|
rrc_gNB_trigger_reconfiguration_for_handover(rrc, UE, buffer.buf, buffer.len);
|
|
LOG_A(NR_RRC, "HO acknowledged: Send reconfiguration for UE %u/RNTI %04x...\n", UE->rrc_ue_id, UE->rnti);
|
|
|
|
/* Re-establish SRB2 according to clause 5.3.5.6.3 of 3GPP TS 38.331
|
|
* (SRB1 is re-established with RRCReestablishment message)
|
|
*/
|
|
int srb_id = 2;
|
|
if (UE->Srb[srb_id].Active) {
|
|
nr_pdcp_entity_security_keys_and_algos_t security_parameters;
|
|
/* Derive the keys from kgnb */
|
|
nr_derive_key(RRC_ENC_ALG, UE->ciphering_algorithm, UE->kgnb, security_parameters.ciphering_key);
|
|
nr_derive_key(RRC_INT_ALG, UE->integrity_algorithm, UE->kgnb, security_parameters.integrity_key);
|
|
security_parameters.integrity_algorithm = UE->integrity_algorithm;
|
|
security_parameters.ciphering_algorithm = UE->ciphering_algorithm;
|
|
nr_pdcp_reestablishment(UE->rrc_ue_id, srb_id, true, &security_parameters);
|
|
}
|
|
}
|
|
|
|
static void nr_rrc_f1_ho_complete(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE)
|
|
{
|
|
DevAssert(UE->ho_context != NULL);
|
|
nr_ho_source_cu_t *source_ctx = UE->ho_context->source;
|
|
DevAssert(source_ctx != NULL);
|
|
RETURN_IF_INVALID_ASSOC_ID(source_ctx->du->assoc_id);
|
|
f1ap_ue_context_release_cmd_t cmd = {
|
|
.gNB_CU_ue_id = UE->rrc_ue_id,
|
|
.gNB_DU_ue_id = source_ctx->du_ue_id,
|
|
.cause = F1AP_CAUSE_RADIO_NETWORK,
|
|
.cause_value = 5, // 5 = F1AP_CauseRadioNetwork_interaction_with_other_procedure
|
|
.srb_id = DL_SCH_LCID_DCCH,
|
|
};
|
|
rrc->mac_rrc.ue_context_release_command(source_ctx->du->assoc_id, &cmd);
|
|
LOG_I(NR_RRC, "UE %d Handover: trigger release on DU assoc_id %d\n", UE->rrc_ue_id, source_ctx->du->assoc_id);
|
|
}
|
|
|
|
static void nr_rrc_cancel_f1_ho(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE)
|
|
{
|
|
DevAssert(UE->ho_context != NULL);
|
|
nr_ho_target_cu_t *target_ctx = UE->ho_context->target;
|
|
DevAssert(target_ctx != NULL);
|
|
f1ap_ue_context_release_cmd_t cmd = {
|
|
.gNB_CU_ue_id = UE->rrc_ue_id,
|
|
.gNB_DU_ue_id = target_ctx->du_ue_id,
|
|
.cause = F1AP_CAUSE_RADIO_NETWORK, // better
|
|
.cause_value = 5, // 5 = F1AP_CauseRadioNetwork_interaction_with_other_procedure
|
|
.srb_id = DL_SCH_LCID_DCCH,
|
|
};
|
|
rrc->mac_rrc.ue_context_release_command(target_ctx->du->assoc_id, &cmd);
|
|
}
|
|
|
|
void nr_rrc_trigger_f1_ho(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue, nr_rrc_du_container_t *source_du, nr_rrc_du_container_t *target_du)
|
|
{
|
|
DevAssert(rrc != NULL);
|
|
DevAssert(ue != NULL);
|
|
|
|
uint8_t buf[NR_RRC_BUF_SIZE];
|
|
int size = do_NR_HandoverPreparationInformation(ue->ue_cap_buffer.buf, ue->ue_cap_buffer.len, buf, sizeof buf);
|
|
|
|
// corresponds to a "handover request", 38.300 Sec 9.3.2.3
|
|
// see also 38.413 Sec 9.3.1.29 for information on source-CU to target-CU
|
|
// information (Source NG-RAN Node to Target NG-RAN Node Transparent Container)
|
|
// here: target Cell is preselected, target CU has access to UE information
|
|
// and therefore also the PDU sessions. Orig RRC reconfiguration should be in
|
|
// handover preparation information
|
|
ho_req_ack_t ack = nr_rrc_f1_ho_acknowledge;
|
|
ho_success_t success = nr_rrc_f1_ho_complete;
|
|
ho_cancel_t cancel = nr_rrc_cancel_f1_ho;
|
|
byte_array_t hpi = {.buf = buf, .len = size};
|
|
nr_initiate_handover(rrc, ue, source_du, target_du, &hpi, ack, success, cancel);
|
|
}
|
|
|
|
void nr_rrc_finalize_ho(gNB_RRC_UE_t *ue)
|
|
{
|
|
if (ue->ho_context->source)
|
|
ASN_STRUCT_FREE(asn_DEF_NR_CellGroupConfig, ue->ho_context->source->old_cellGroupConfig);
|
|
free_ho_ctx(ue->ho_context);
|
|
ue->ho_context = NULL;
|
|
}
|
|
|
|
void nr_HO_F1_trigger_telnet(gNB_RRC_INST *rrc, uint32_t rrc_ue_id)
|
|
{
|
|
rrc_gNB_ue_context_t *ue_context_p = rrc_gNB_get_ue_context(rrc, rrc_ue_id);
|
|
if (ue_context_p == NULL) {
|
|
LOG_E(NR_RRC, "cannot find UE context for UE ID %d\n", rrc_ue_id);
|
|
return;
|
|
}
|
|
gNB_RRC_UE_t *ue = &ue_context_p->ue_context;
|
|
nr_rrc_du_container_t *source_du = get_du_for_ue(rrc, ue->rrc_ue_id);
|
|
if (source_du == NULL) {
|
|
f1_ue_data_t ue_data = cu_get_f1_ue_data(rrc_ue_id);
|
|
LOG_E(NR_RRC, "cannot get source gNB-DU with assoc_id %d for UE %u\n", ue_data.du_assoc_id, ue->rrc_ue_id);
|
|
return;
|
|
}
|
|
|
|
nr_rrc_du_container_t *target_du = find_target_du(rrc, source_du->assoc_id);
|
|
if (target_du == NULL) {
|
|
LOG_E(NR_RRC, "No target gNB-DU found. Handover for UE %u aborted.\n", ue->rrc_ue_id);
|
|
return;
|
|
}
|
|
|
|
nr_rrc_trigger_f1_ho(rrc, ue, source_du, target_du);
|
|
}
|