Files
openairinterface5g/openair2/RRC/NR/rrc_gNB_nsa.c
Guido Casati c8b99ce011 GTPU: move QFI handling to send path and update tunnel API usage
Refactor GTP-U tunnel and send APIs so QFI is handled when sending packets,
not stored in tunnel creation state. Update SDAP and CU-UP integration to
use PDU-session keyed N3 mappings and explicit QFI-marked sends.

This commit clarifies layering ownership: GTP-U stays transport-only (TEID
lookup, decapsulation, extension parsing, callback dispatch), while SDAP
owns QoS semantics (QFI handling, QoS-flow-to-DRB policy, default DRB behavior,
and mapping updates); GTP-U does not perform runtime QFI-to-DRB mapping or
synthesize QFI.

Changes:
- remove `outgoing_qfi` from `gtpv1u_gnb_create_tunnel_req_t` and stop storing
  QFI as tunnel creation metadata; `newGtpuCreateTunnel(...)` now carries only
  transport/tunnel identity parameters (incoming_bearer_id, outgoing_bearer_id,
  outgoing_teid, remote address, callbacks)
- add `gtpv1uSendDirectWithQFI()` and pass QFI into `_gtpv1uSendDirect`
  to build UL PDU Session Container extensions
- shift QFI handling from tunnel provisioning to per-packet TX APIs: QFI is passed
  explicitly only when sending (`gtpv1uSendDirectWithQFI(...)`) and is absent from
  non-SDAP/F1 sends (`gtpv1uSendDirect(...)`)
- align N3 tunnel request semantics with session-level keys by setting incoming_rb_id
  to PDU session ID on N3 paths, while F1 paths keep DRB ID
- keep `gtpv1uSendDirect()` and `gtpv1uSendDirectWithNRUSeqNum()` on
  `NO_QFI`, and enforce non-SDAP RX callback path only when QFI is absent
- update `nr_sdap_rx_entity` to extract/validate QFI from SDAP UL headers,
  send UL data with `gtpv1uSendDirectWithQFI`, and use non-QFI send when
  SDAP header is disabled
- add disabled-SDAP safety checks in SDAP entity setup/mapping to enforce
  single-DRB and single-flow constraints per PDU session
- extend `test_gtp.cpp` with a `multi_qos_flows` scenario and QFI-aware send calls
- update `nr-cuup-load-test.c` bearer setup fields and tunnel creation calls
  to match the new API
- update tests/nr-cuup/nr-cuup-load-test.c to set explicit PDU session and
  QoS/SDAP parameters (sessionType, qosFlows[0], SDAP header flags), and to
  migrate both N3 and F1 tunnel creation calls to the new newGtpuCreateTunnel(...)
  signature (without outgoing_qfi)

Signed-off-by: Guido Casati <guido.casati@openairinterface.org>
2026-05-04 12:03:27 +02:00

530 lines
23 KiB
C

/*
* SPDX-License-Identifier: LicenseRef-CSSL-1.0
*/
/*!
* \brief rrc NSA procedures for gNB
*/
#include <assert.h>
#include <assertions.h>
#include <openair2/RRC/NR/nr_rrc_proto.h>
#include <openair2/RRC/NR/rrc_gNB_UE_context.h>
#include <openair3/ocp-gtpu/gtp_itf.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "MESSAGES/asn1_msg.h"
#include "OCTET_STRING.h"
#include "T.h"
#include "asn_codecs.h"
#include "asn_internal.h"
#include "assertions.h"
#include "common/ngran_types.h"
#include "common/ran_context.h"
#include "common/utils/T/T.h"
#include "constr_TYPE.h"
#include "executables/nr-softmodem.h"
#include "executables/softmodem-common.h"
#include "gtpv1_u_messages_types.h"
#include "intertask_interface.h"
#include "ngap_messages_types.h"
#include "nr_pdcp/nr_pdcp_entity.h"
#include "nr_pdcp/nr_pdcp_oai_api.h"
#include "nr_rrc_defs.h"
#include "openair2/F1AP/f1ap_ids.h"
#include "openair2/LAYER2/NR_MAC_gNB/mac_proto.h"
#include "openair2/LAYER2/nr_rlc/nr_rlc_oai_api.h"
#include "openair3/SECU/key_nas_deriver.h"
#include "openair2/SDAP/nr_sdap/nr_sdap_entity.h"
#include "rrc_gNB_du.h"
#include "rrc_cell_management.h"
#include "rlc.h"
#include "s1ap_messages_types.h"
#include "tree.h"
#include "uper_decoder.h"
#include "uper_encoder.h"
#include "x2ap_messages_types.h"
#include "xer_decoder.h"
#include "xer_encoder.h"
#include "f1ap_common.h"
#include "lib/f1ap_ue_context.h"
#include "rrc_gNB_asn1.h"
// In case of phy-test and do-ra mode, read UE capabilities directly from file
// and put it into a CG-ConfigInfo field
static int cg_config_info_from_ue_cap_file(uint32_t maxlen, uint8_t buf[maxlen])
{
OCTET_STRING_t *list_buf = NULL;
if (uecap_file != NULL) {
LOG_I(NR_RRC, "creating CG-ConfigInfo from UE capability file %s\n", uecap_file);
FILE *f = fopen(uecap_file, "r");
if (!f) {
LOG_E(NR_RRC, "cannot open file %s, cannot read UE capabilities\n", uecap_file);
return 0;
}
char UE_NR_Capability_xer[65536];
size_t size = fread(UE_NR_Capability_xer, 1, sizeof UE_NR_Capability_xer, f);
fclose(f);
if (size == 0 || size == sizeof UE_NR_Capability_xer) {
LOG_E(NR_RRC, "UE Capabilities XER file %s could not be read (read %ld bytes)\n", uecap_file, size);
return 0;
}
NR_UE_NR_Capability_t *cap = calloc_or_fail(1, sizeof(*cap));
asn_dec_rval_t dec_rval = xer_decode(0, &asn_DEF_NR_UE_NR_Capability, (void *)&cap, UE_NR_Capability_xer, size);
DevAssert(dec_rval.code == RC_OK);
//xer_fprint(stdout, &asn_DEF_NR_UE_NR_Capability, cap);
uint8_t *buf = NULL;
int sizebuf = uper_encode_to_new_buffer(&asn_DEF_NR_UE_NR_Capability, NULL, cap, (void **)&buf);
NR_UE_CapabilityRAT_Container_t *cont = calloc_or_fail(1, sizeof(*cont));
cont->rat_Type = NR_RAT_Type_nr;
cont->ue_CapabilityRAT_Container.buf = buf;
cont->ue_CapabilityRAT_Container.size = sizebuf;
NR_UE_CapabilityRAT_ContainerList_t *clist = calloc_or_fail(1, sizeof(*clist));
asn1cSeqAdd(&clist->list, cont);
list_buf = calloc_or_fail(1, sizeof(*list_buf));
list_buf->size = uper_encode_to_new_buffer(&asn_DEF_NR_UE_CapabilityRAT_ContainerList, NULL, clist, (void **)&list_buf->buf);
ASN_STRUCT_FREE(asn_DEF_NR_UE_CapabilityRAT_ContainerList, clist);
}
NR_CG_ConfigInfo_t *cgci = calloc_or_fail(1, sizeof(*cgci));
cgci->criticalExtensions.present = NR_CG_ConfigInfo__criticalExtensions_PR_c1;
cgci->criticalExtensions.choice.c1 = calloc_or_fail(1, sizeof(*cgci->criticalExtensions.choice.c1));
cgci->criticalExtensions.choice.c1->present = NR_CG_ConfigInfo__criticalExtensions__c1_PR_cg_ConfigInfo;
NR_CG_ConfigInfo_IEs_t *cgci_ies = calloc_or_fail(1, sizeof(*cgci_ies));
cgci->criticalExtensions.choice.c1->choice.cg_ConfigInfo = cgci_ies;
cgci_ies->ue_CapabilityInfo = list_buf;
//xer_fprint(stdout, &asn_DEF_NR_CG_ConfigInfo, cgci);
asn_enc_rval_t rval = uper_encode_to_buffer(&asn_DEF_NR_CG_ConfigInfo, NULL, cgci, buf, maxlen);
DevAssert(rval.encoded > 0);
ASN_STRUCT_FREE(asn_DEF_NR_CG_ConfigInfo, cgci);
return (rval.encoded + 7) >> 3;
}
static instance_t get_f1_gtp_instance(void)
{
const f1ap_cudu_inst_t *inst = getCxt(0);
if (!inst)
return -1; // means no F1
return inst->gtpInst;
}
/* returns a default radio bearer config suitable for NSA etc */
static NR_RadioBearerConfig_t *get_default_rbconfig(int eps_bearer_id,
int rb_id,
e_NR_CipheringAlgorithm ciphering_algorithm,
e_NR_SecurityConfig__keyToUse key_to_use,
const nr_pdcp_configuration_t *pdcp_config)
{
NR_RadioBearerConfig_t *rbconfig = calloc_or_fail(1, sizeof(*rbconfig));
rbconfig->drb_ToAddModList = calloc_or_fail(1, sizeof(*rbconfig->drb_ToAddModList));
NR_DRB_ToAddMod_t *drb_ToAddMod = calloc_or_fail(1, sizeof(*drb_ToAddMod));
drb_ToAddMod->drb_Identity = rb_id;
drb_ToAddMod->pdcp_Config = nr_rrc_build_pdcp_config_ie(false, false, pdcp_config);
asn1cCalloc(drb_ToAddMod->cnAssociation, cn_association);
cn_association->present = NR_DRB_ToAddMod__cnAssociation_PR_eps_BearerIdentity;
cn_association->choice.eps_BearerIdentity = eps_bearer_id;
asn1cSeqAdd(&rbconfig->drb_ToAddModList->list, drb_ToAddMod);
asn1cCalloc(rbconfig->securityConfig, secConf);
asn1cCalloc(secConf->securityAlgorithmConfig, secConfAlgo);
secConfAlgo->cipheringAlgorithm = ciphering_algorithm;
secConfAlgo->integrityProtAlgorithm = NULL;
asn1cCallocOne(secConf->keyToUse, key_to_use);
if (LOG_DEBUGFLAG(DEBUG_ASN1))
xer_fprint(stdout, &asn_DEF_NR_DRB_ToAddMod, drb_ToAddMod);
return rbconfig;
}
static void rrc_nsa_add_drb(const int ue_id,
const NR_DRB_ToAddModList_t *addMod,
const nr_pdcp_entity_security_keys_and_algos_t *sp)
{
for (int i = 0; i < addMod->list.count; i++) {
NR_DRB_ToAddMod_t *drb = addMod->list.array[i];
DevAssert(drb->cnAssociation);
DevAssert(drb->cnAssociation->present != NR_DRB_ToAddMod__cnAssociation_PR_NOTHING);
DevAssert(drb->cnAssociation->present == NR_DRB_ToAddMod__cnAssociation_PR_eps_BearerIdentity);
// get SDAP config
sdap_config_t sdap = {0};
// EPC association
sdap.pdusession_id = drb->cnAssociation->choice.eps_BearerIdentity;
sdap.drb_id = drb->drb_Identity;
sdap.defaultDRB = true;
// add SDAP entity (terminated at gNB, since it's EPC)
nr_sdap_addmod_entity(GNB_FLAG_YES, ue_id, &sdap);
// add PDCP entity
nr_pdcp_add_drb(GNB_FLAG_YES, ue_id, drb->pdcp_Config, &sdap, sp);
}
}
/* generate prototypes for the tree management functions (RB_INSERT used in rrc_add_nsa_user) */
RB_PROTOTYPE(rrc_nr_ue_tree_s, rrc_gNB_ue_context_s, entries,
rrc_gNB_compare_ue_rnti_id);
void rrc_add_nsa_user(gNB_RRC_INST *rrc, x2ap_ENDC_sgnb_addition_req_t *m, sctp_assoc_t assoc_id)
{
AssertFatal(!IS_SA_MODE(get_softmodem_params()), "%s() cannot be called in SA mode, it is intrinsically for NSA\n", __func__);
/* all the 0 are DU-related info that we will fill later, see rrc_add_nsa_user_resp() below */
rrc_gNB_ue_context_t *ue_context_p = rrc_gNB_create_ue_context(assoc_id, 0, rrc, 0, 0);
gNB_RRC_UE_t *UE = &ue_context_p->ue_context;
nr_pdcp_entity_security_keys_and_algos_t security_parameters = {0};
uint8_t tmp[1024];
byte_array_t cgci = {0};
if (get_softmodem_params()->phy_test == 1 || get_softmodem_params()->do_ra == 1) {
DevAssert(m == NULL);
UE->rb_config = get_default_rbconfig(DEFAULT_NOS1_PDU_ID, 1 /* drb ID */, NR_CipheringAlgorithm_nea0, NR_SecurityConfig__keyToUse_master, &rrc->pdcp_config);
int len = cg_config_info_from_ue_cap_file(sizeof tmp, tmp);
DevAssert(len > 0);
cgci = create_byte_array(len, tmp);
} else {
DevAssert(m != NULL);
cgci = create_byte_array(m->rrc_buffer_size, m->rrc_buffer);
/* TODO: handle more than one bearer */
if (m->nb_e_rabs_tobeadded != 1) {
LOG_E(RRC, "fatal: m->nb_e_rabs_tobeadded = %d, should be 1\n", m->nb_e_rabs_tobeadded);
exit(1);
}
/* store security key and security capabilities */
memcpy(UE->kgnb, m->kgnb, 32);
UE->security_capabilities.nRencryption_algorithms = m->security_capabilities.encryption_algorithms;
UE->security_capabilities.nRintegrity_algorithms = m->security_capabilities.integrity_algorithms;
/* Select ciphering algorithm based on gNB configuration file and
* UE's supported algorithms.
* We take the first from the list that is supported by the UE.
* The ordering of the list comes from the configuration file.
*/
/* preset nea0 as fallback */
UE->ciphering_algorithm = 0;
for (int i = 0; i < rrc->security.ciphering_algorithms_count; i++) {
int nea_mask[4] = {
0,
0x8000, /* nea1 */
0x4000, /* nea2 */
0x2000 /* nea3 */
};
if (rrc->security.ciphering_algorithms[i] == 0) {
/* nea0 already preselected */
break;
}
if (UE->security_capabilities.nRencryption_algorithms & nea_mask[rrc->security.ciphering_algorithms[i]]) {
UE->ciphering_algorithm = rrc->security.ciphering_algorithms[i];
break;
}
}
LOG_I(RRC, "selecting ciphering algorithm %d\n", (int)UE->ciphering_algorithm);
/* integrity: no integrity protection for DRB in ENDC mode
* as written in 38.331: "If UE is connected to E-UTRA/EPC, this field
* indicates the integrity protection algorithm to be used for SRBs
* configured with NR PDCP, as specified in TS 33.501"
* So nothing for DRB. Plus a test with a COTS UE revealed that it
* did not apply integrity on the DRB.
*/
UE->integrity_algorithm = 0;
LOG_I(RRC, "selecting integrity algorithm %d\n", UE->integrity_algorithm);
/* derive UP security key */
security_parameters.ciphering_algorithm = UE->ciphering_algorithm;
security_parameters.integrity_algorithm = UE->integrity_algorithm;
nr_derive_key(UP_ENC_ALG, UE->ciphering_algorithm, UE->kgnb, security_parameters.ciphering_key);
nr_derive_key(UP_INT_ALG, UE->integrity_algorithm, UE->kgnb, security_parameters.integrity_key);
e_NR_CipheringAlgorithm cipher_algo;
switch (UE->ciphering_algorithm) {
case 0:
cipher_algo = NR_CipheringAlgorithm_nea0;
break;
case 1:
cipher_algo = NR_CipheringAlgorithm_nea1;
break;
case 2:
cipher_algo = NR_CipheringAlgorithm_nea2;
break;
case 3:
cipher_algo = NR_CipheringAlgorithm_nea3;
break;
default:
LOG_E(RRC, "%s:%d:%s: fatal\n", __FILE__, __LINE__, __FUNCTION__);
exit(1);
}
UE->rb_config = get_default_rbconfig(m->e_rabs_tobeadded[0].e_rab_id, m->e_rabs_tobeadded[0].drb_ID, cipher_algo, NR_SecurityConfig__keyToUse_secondary, &rrc->pdcp_config);
}
if(m!=NULL) {
UE->x2_target_assoc = m->target_assoc_id;
UE->MeNB_ue_x2_id = m->ue_x2_id;
gtpv1u_enb_create_tunnel_req_t create_tunnel_req = {0};
gtpv1u_enb_create_tunnel_resp_t create_tunnel_resp = {0};
if (m->nb_e_rabs_tobeadded>0) {
for (int i=0; i<m->nb_e_rabs_tobeadded; i++) {
// Add the new E-RABs at the corresponding rrc ue context of the gNB
UE->e_rab[i].param.e_rab_id = m->e_rabs_tobeadded[i].e_rab_id;
UE->e_rab[i].param.gtp_teid = m->e_rabs_tobeadded[i].gtp_teid;
memcpy(&UE->e_rab[i].param.sgw_addr, &m->e_rabs_tobeadded[i].sgw_addr, sizeof(transport_layer_addr_t));
UE->nb_of_e_rabs++;
//Fill the required E-RAB specific information for the creation of the S1-U tunnel between the gNB and the SGW
create_tunnel_req.eps_bearer_id[i] = UE->e_rab[i].param.e_rab_id;
create_tunnel_req.sgw_S1u_teid[i] = UE->e_rab[i].param.gtp_teid;
memcpy(&create_tunnel_req.sgw_addr[i], &UE->e_rab[i].param.sgw_addr, sizeof(transport_layer_addr_t));
LOG_I(RRC,"S1-U tunnel: index %d target sgw ip %d.%d.%d.%d length %d gtp teid %u\n",
i,
create_tunnel_req.sgw_addr[i].buffer[0],
create_tunnel_req.sgw_addr[i].buffer[1],
create_tunnel_req.sgw_addr[i].buffer[2],
create_tunnel_req.sgw_addr[i].buffer[3],
create_tunnel_req.sgw_addr[i].length,
create_tunnel_req.sgw_S1u_teid[i]);
}
create_tunnel_req.rnti = ue_context_p->ue_context.rrc_ue_id;
create_tunnel_req.num_tunnels = m->nb_e_rabs_tobeadded;
RB_INSERT(rrc_nr_ue_tree_s, &RC.nrrrc[rrc->module_id]->rrc_ue_head, ue_context_p);
if (!IS_SOFTMODEM_NOS1) {
gtpv1u_create_s1u_tunnel(rrc->module_id, &create_tunnel_req, &create_tunnel_resp, nr_pdcp_data_req_drb);
DevAssert(create_tunnel_resp.num_tunnels == 1);
UE->nsa_gtp_teid[0] = create_tunnel_resp.enb_S1u_teid[0];
UE->nsa_gtp_addrs[0] = create_tunnel_resp.enb_addr;
UE->nsa_gtp_ebi[0] = create_tunnel_resp.eps_bearer_id[0];
}
}
}
DevAssert(UE->rb_config != NULL);
rrc_nsa_add_drb(UE->rrc_ue_id, UE->rb_config->drb_ToAddModList, &security_parameters);
/* assumption: only a single bearer, see above */
NR_DRB_ToAddModList_t *rb_list = UE->rb_config->drb_ToAddModList;
AssertFatal(rb_list->list.count == 1, "can only handle one bearer for NSA/phy-test/do-ra, but has %d\n", rb_list->list.count);
int drb_id = rb_list->list.array[0]->drb_Identity;
f1ap_drb_to_setup_t *drb = calloc_or_fail(1, sizeof(*drb));
drb->id = drb_id;
// hardcoded keep it backwards compatible for now
// rrc->configuration.um_on_default_drb ? F1AP_RLC_MODE_UM_BIDIR : F1AP_RLC_MODE_AM,
drb->rlc_mode = F1AP_RLC_MODE_UM_BIDIR;
drb->up_ul_tnl_len = 1;
drb->qos_choice = F1AP_QOS_CHOICE_NR; // we don't have EUTRAN yet, "approximate" it
drb->nr.flows_len = 1;
f1ap_drb_flows_mapped_t *flow = drb->nr.flows = calloc_or_fail(drb->nr.flows_len, sizeof(*flow));
flow->qfi = 9;
flow->param.qos_type = NON_DYNAMIC;
flow->param.nondyn.fiveQI = 9;
flow->param.arp.prio = 5;
// Note: E1 support for NSA/phy-test/do-ra not implemented yet
instance_t f1inst = get_f1_gtp_instance();
if (f1inst >= 0) {
gtpv1u_gnb_create_tunnel_req_t req = {
.ue_id = UE->rrc_ue_id,
.incoming_rb_id = drb_id,
.pdusession_id = drb_id,
.outgoing_teid = 0xffff, // will be updated later
.dst_addr.length = 32,
};
gtpv1u_gnb_create_tunnel_resp_t resp = {0};
int ret = gtpv1u_create_ngu_tunnel(f1inst, &req, &resp, NULL, NULL);
AssertFatal(ret == 0, "gtpv1u_create_ngu_tunnel failed: ret %d\n", ret);
AssertFatal(resp.gnb_addr.length == sizeof(in_addr_t),
"GTP tunnel response address length %d does not match IPv4 size %zu\n",
resp.gnb_addr.length,
sizeof(in_addr_t));
memcpy(&drb->up_ul_tnl[0].tl_address, &resp.gnb_addr.buffer, resp.gnb_addr.length);
drb->up_ul_tnl[0].teid = resp.gnb_NGu_teid;
drb->up_ul_tnl_len = 1;
}
uint64_t *ue_agg_mbr_ul = malloc_or_fail(sizeof(*ue_agg_mbr_ul));
*ue_agg_mbr_ul = 1000000000;
byte_array_t *cg_configinfo = malloc_or_fail(sizeof(*cg_configinfo));
*cg_configinfo = cgci;
f1_ue_data_t ue_data = cu_get_f1_ue_data(UE->rrc_ue_id);
RETURN_IF_INVALID_ASSOC_ID(ue_data.du_assoc_id);
nr_rrc_du_container_t *du = get_du_by_assoc_id(rrc, ue_data.du_assoc_id);
if (!du) {
LOG_E(NR_RRC, "UE %d: no valid cell for UE context setup (DU assoc_id %d not found)\n", UE->rrc_ue_id, ue_data.du_assoc_id);
return;
}
// Get the first cell from the DU (NSA assumption: 1 cell per DU)
if (seq_arr_size(&du->cells) == 0) {
LOG_E(NR_RRC, "UE %d: no cells available in DU (assoc_id %d)\n", UE->rrc_ue_id, ue_data.du_assoc_id);
return;
}
nr_rrc_cell_container_t *ue_cell = *(nr_rrc_cell_container_t **)seq_arr_at(&du->cells, 0);
// Add the cell as PCell
if (rrc_add_ue_serving_cell(UE, ue_cell, RRC_PCELL_INDEX) == NULL) {
LOG_E(NR_RRC, "UE %d: failed to add PCell (cell %ld) for UE context setup\n", UE->rrc_ue_id, ue_cell->info.cell_id);
return;
}
f1ap_ue_context_setup_req_t req = {
.gNB_CU_ue_id = UE->rrc_ue_id,
.plmn.mcc = rrc->configuration.plmn[0].mcc,
.plmn.mnc = rrc->configuration.plmn[0].mnc,
.plmn.mnc_digit_length = rrc->configuration.plmn[0].mnc_digit_length,
.nr_cellid = ue_cell->info.cell_id,
.servCellIndex = RRC_PCELL_INDEX,
.drbs_len = 1,
.drbs = drb,
.cu_to_du_rrc_info.cg_configinfo = cg_configinfo,
.gnb_du_ue_agg_mbr_ul = ue_agg_mbr_ul,
};
rrc->mac_rrc.ue_context_setup_request(ue_data.du_assoc_id, &req);
free_ue_context_setup_req(&req);
}
static NR_RRCReconfiguration_IEs_t *get_default_reconfig(const byte_array_t cgc)
{
NR_RRCReconfiguration_IEs_t *reconfig = calloc(1, sizeof(NR_RRCReconfiguration_IEs_t));
AssertFatal(reconfig != NULL, "out of memory\n");
AssertFatal(cgc.buf != NULL, "cgc is null\n");
reconfig->radioBearerConfig = NULL;
// Copy the stored CellGroupConfig to the RRCReconfiguration message
reconfig->secondaryCellGroup = calloc_or_fail(1, sizeof(*reconfig->secondaryCellGroup));
OCTET_STRING_fromBuf(reconfig->secondaryCellGroup, (const char *)cgc.buf, cgc.len);
reconfig->measConfig = NULL;
reconfig->lateNonCriticalExtension = NULL;
reconfig->nonCriticalExtension = NULL;
return reconfig;
}
static NR_CG_Config_t *generate_CG_Config(const NR_RRCReconfiguration_t *reconfig, const NR_RadioBearerConfig_t *rbconfig)
{
NR_CG_Config_t *cg_Config = calloc_or_fail(1, sizeof(*cg_Config));
cg_Config->criticalExtensions.present = NR_CG_Config__criticalExtensions_PR_c1;
cg_Config->criticalExtensions.choice.c1 = calloc_or_fail(1, sizeof(*cg_Config->criticalExtensions.choice.c1));
cg_Config->criticalExtensions.choice.c1->present = NR_CG_Config__criticalExtensions__c1_PR_cg_Config;
NR_CG_Config_IEs_t *cgc_ie = calloc_or_fail(1, sizeof(*cgc_ie));
cg_Config->criticalExtensions.choice.c1->choice.cg_Config = cgc_ie;
cgc_ie->scg_CellGroupConfig = calloc_or_fail(1, sizeof(*cgc_ie->scg_CellGroupConfig));
cgc_ie->scg_CellGroupConfig->size =
uper_encode_to_new_buffer(&asn_DEF_NR_RRCReconfiguration, NULL, reconfig, (void **)&cgc_ie->scg_CellGroupConfig->buf);
AssertFatal(cgc_ie->scg_CellGroupConfig->size > 0,
"ASN1 message encoding of RRCReconfiguration failed (%ld)!\n",
cgc_ie->scg_CellGroupConfig->size);
cgc_ie->scg_RB_Config = calloc_or_fail(1, sizeof(*cgc_ie->scg_RB_Config));
cgc_ie->scg_RB_Config->size =
uper_encode_to_new_buffer(&asn_DEF_NR_RadioBearerConfig, NULL, rbconfig, (void **)&cgc_ie->scg_RB_Config->buf);
AssertFatal(cgc_ie->scg_RB_Config->size > 0, "ASN1 message encoding failed (%ld)!\n", cgc_ie->scg_RB_Config->size);
if (get_softmodem_params()->phy_test == 1 || get_softmodem_params()->do_ra > 0) {
// This is for phytest only, emulate first X2 message if uecap.raw file is present
LOG_I(RRC, "Dumping NR_RRCReconfiguration message (%jd bytes) to reconfig.raw\n", cgc_ie->scg_CellGroupConfig->size);
FILE *fd = fopen("reconfig.raw", "w");
AssertFatal(fd != NULL, "could not open reconig.raw for writing: %d, %s\n", errno, strerror(errno));
fwrite(cgc_ie->scg_CellGroupConfig->buf, cgc_ie->scg_CellGroupConfig->size, 1, fd);
fclose(fd);
LOG_I(RRC, "Dumping scg_RB_Config message (%jd bytes) to reconfig.raw\n", cgc_ie->scg_RB_Config->size);
fd = fopen("rbconfig.raw", "w");
AssertFatal(fd != NULL, "could not open rbconig.raw for writing: %d, %s\n", errno, strerror(errno));
fwrite(cgc_ie->scg_RB_Config->buf, cgc_ie->scg_RB_Config->size, 1, fd);
fclose(fd);
}
return cg_Config;
}
void rrc_add_nsa_user_resp(gNB_RRC_UE_t *UE, const f1ap_ue_context_setup_resp_t *resp)
{
DevAssert(resp->crnti != NULL);
/* we did not fill any DU-related ID info in rrc_add_nsa_user() */
UE->rnti = *resp->crnti;
DevAssert(cu_exists_f1_ue_data(UE->rrc_ue_id));
f1_ue_data_t ue_data = cu_get_f1_ue_data(UE->rrc_ue_id);
ue_data.secondary_ue = resp->gNB_DU_ue_id;
bool success = cu_update_f1_ue_data(UE->rrc_ue_id, &ue_data);
DevAssert(success);
instance_t f1inst = get_f1_gtp_instance();
if (f1inst >= 0) {
// Note: E1 support for NSA/phy-test/do-ra not implemented yet
// so set up GTP from here
for (int i = 0; i < resp->drbs_len; ++i) {
f1ap_drb_setup_t *drb = &resp->drbs[i];
DevAssert(drb->up_dl_tnl_len == 1);
in_addr_t addr = drb->up_dl_tnl[0].tl_address;
uint32_t teid = drb->up_dl_tnl[0].teid;
GtpuUpdateTunnelOutgoingAddressAndTeid(f1inst, UE->rrc_ue_id, drb->id, addr, teid);
}
}
NR_RRCReconfiguration_t *reconfig = calloc(1, sizeof(NR_RRCReconfiguration_t));
reconfig->rrc_TransactionIdentifier = 0;
reconfig->criticalExtensions.present = NR_RRCReconfiguration__criticalExtensions_PR_rrcReconfiguration;
reconfig->criticalExtensions.choice.rrcReconfiguration = get_default_reconfig(UE->mcg);
NR_CG_Config_t *CG_Config = generate_CG_Config(reconfig, UE->rb_config);
ASN_STRUCT_FREE(asn_DEF_NR_RRCReconfiguration, reconfig);
if (get_softmodem_params()->phy_test > 0 || get_softmodem_params()->do_ra > 0) {
/* we are done, no X2 answer necessary */
ASN_STRUCT_FREE(asn_DEF_NR_CG_Config, CG_Config);
return;
}
MessageDef *msg = itti_alloc_new_message(TASK_RRC_ENB, 0, X2AP_ENDC_SGNB_ADDITION_REQ_ACK);
x2ap_ENDC_sgnb_addition_req_ACK_t *ack = &X2AP_ENDC_SGNB_ADDITION_REQ_ACK(msg);
ack->nb_e_rabs_admitted_tobeadded = UE->nb_of_e_rabs;
DevAssert(UE->x2_target_assoc > 0);
ack->target_assoc_id = UE->x2_target_assoc;
for (int i = 0; i < UE->nb_of_e_rabs; i++) {
ack->e_rabs_admitted_tobeadded[i].e_rab_id = UE->e_rab[i].param.e_rab_id;
ack->e_rabs_admitted_tobeadded[i].gtp_teid = UE->nsa_gtp_teid[0];
memcpy(&ack->e_rabs_admitted_tobeadded[i].gnb_addr, &UE->nsa_gtp_addrs[0], sizeof(transport_layer_addr_t));
ack->e_rabs_admitted_tobeadded[i].gnb_addr.length = 32; // bits, IPv4 only
}
ack->MeNB_ue_x2_id = UE->MeNB_ue_x2_id;
ack->SgNB_ue_x2_id = UE->rrc_ue_id;
// Send to X2 entity to transport to MeNB
asn_enc_rval_t enc_rval =
uper_encode_to_buffer(&asn_DEF_NR_CG_Config, NULL, (void *)CG_Config, ack->rrc_buffer, sizeof(ack->rrc_buffer));
ack->rrc_buffer_size = (enc_rval.encoded + 7) >> 3;
itti_send_msg_to_task(TASK_X2AP, ENB_MODULE_ID_TO_INSTANCE(0), msg);
ASN_STRUCT_FREE(asn_DEF_NR_CG_Config, CG_Config);
}
void rrc_remove_nsa_user_context(gNB_RRC_INST *rrc, rrc_gNB_ue_context_t *ue_context)
{
if (!IS_SOFTMODEM_NOS1)
gtpv1u_delete_all_s1u_tunnel(rrc->module_id, ue_context->ue_context.rrc_ue_id);
instance_t f1inst = get_f1_gtp_instance();
if (f1inst >= 0)
gtpv1u_delete_all_s1u_tunnel(f1inst, ue_context->ue_context.rrc_ue_id);
// we don't use E1 => we have to free SDAP
nr_sdap_delete_ue_entities(ue_context->ue_context.rrc_ue_id);
rrc_remove_ue(rrc, ue_context);
}
void rrc_release_nsa_user(gNB_RRC_INST *rrc, rrc_gNB_ue_context_t *ue_context)
{
gNB_RRC_UE_t *UE = &ue_context->ue_context;
f1_ue_data_t ue_data = cu_get_f1_ue_data(UE->rrc_ue_id);
RETURN_IF_INVALID_ASSOC_ID(ue_data.du_assoc_id);
f1ap_ue_context_rel_cmd_t cmd = {
.gNB_CU_ue_id = UE->rrc_ue_id,
.gNB_DU_ue_id = ue_data.secondary_ue,
.cause = F1AP_CAUSE_RADIO_NETWORK,
.cause_value = 10, // 10 = F1AP_CauseRadioNetwork_normal_release
};
rrc->mac_rrc.ue_context_release_command(ue_data.du_assoc_id, &cmd);
rrc_remove_nsa_user_context(rrc, ue_context);
}