Implement TS 38.473 transparency for CellGroupConfig forwarding

This commit implements transparent forwarding of CellGroupConfig
as required by TS 38.473, eliminating CU-side decode/re-encode
cycles that violate transparency requirements.

Main Design Change:
- Before: CU decoded CellGroupConfig from DU, stored in the UE
  context then re-encoded when sending to UE. CU constructed
  new CellGroupConfig after RRCReestablishmentComplete.
- After: CU requests CellGroupConfig from DU via UE Context Modification
  Request with `gNB_DU_Configuration_Query=true`, DU sends CellGroupConfig
  which CU stores as encoded `byte_array_t` and forwards transparently
  via reconfiguration without decode/re-encode cycles

To this purpose, all decode operations of CellGroupConfig were removed
from DU and all encode operations were removed from the CU.

Key Changes:

CU:
- Replace NR_CellGroupConfig_t* masterCellGroup with byte_array_t mcg in
  gNB_RRC_UE_t for storing encoded CellGroupConfig
- Rename cell_group_config to cgc in nr_rrc_reconfig_param_t for brevity
- Implement transparent forwarding in build_RRCReconfiguration_IEs()
- Add helper functions:
  - `store_cgc()` to store encoded bytes
  - `get_meas_gap_config()`: Decode MeasGapConfig from DU to CU response
  - `rrc_detect_reestablishment()` to detect re-establishment by decoding
    temporarily (only for detection)
- Remove legacy decode/re-encode paths in RRC message generation
- Update all CellGroupConfig storage to use encoded bytes
- Update reconfiguration generation to accept is_reestablishment parameter

DU:
- Add `get_cellgroup_config()` to select source (CellGroup vs reconfigCellGroup)
  based on re-establishment state
- Add re-establishment detection logic in `ue_context_modification_request()`
- Add `update_cellgroup_for_reestablishment()` to set reestablishRLC flags and
  remove SRB1 from bearer list
- When `gNB_DU_Configuration_Query` is present and re-establishment detected:
  - Clones from reconfigCellGroup (which contains spCellConfig) instead of
    CellGroup (where spCellConfig was removed)
  - Sets `reestablishRLC` flags for all bearers except SRB1
  - Removes SRB1 from bearer list (already re-established)

Data Structure Changes:
- gNB_RRC_UE_t: Add byte_array_t mcg field
- nr_rrc_reconfig_param_t: Change cell_group_config to cgc (byte_array_t*)
- Remove masterCellGroup field usage in favor of encoded mcg

New re-establishment flow:
1. UE sends RRCReestablishmentRequest
2. CU processes -> sends RRCReestablishment
3. CU sends RRCReestablishmentComplete
4. CU sends UE Context Modification Request to DU with `gNB_DU_Configuration_Query=true`
5. DU detects re-establishment (`UE->reestablish_rlc && UE->reconfigCellGroup != NULL`):
   - Clones `UE->reconfigCellGroup` -> `new_CellGroup` (contains spCellConfig)
   - Sets `reestablishRLC` flags for all bearers except SRB1
   - Removes SRB1 from bearer list (already re-established)
   - Encodes `new_CellGroup` and sends to CU
6. CU receives encoded CellGroupConfig -> stores in `UE->mcg` via `store_cgc()`
7. CU detects re-establishment via `rrc_detect_reestablishment()`
8. CU calls `rrc_gNB_generate_dedicatedRRCReconfiguration()` with `is_reestablishment=true`:
   - Creates SRB/DRB lists with re-establishment flags
   - Generates RRC Reconfiguration with transparent CellGroupConfig
   - Sends to UE

Why spCellConfig must be added in the DU in case of re-establishment:

With transparent forwarding, CU requests CellGroupConfig from DU instead
of constructing it. During re-establishment, DU's runtime CellGroup has
spCellConfig = NULL (removed per TS 38.331 §5.3.7.2). However, for the
gNB_DU_Configuration_Query response, DU must provide a complete CellGroupConfig
including spCellConfig. Therefore, DU restores spCellConfig by encoding
reconfigCellGroup (with the original spCellConfig) in CellGroupConfig.
In the old design CU constructed CellGroupConfig itself in
`rrc_gNB_process_RRCReestablishmentComplete()` and used `spCellConfig`
from its stored `masterCellGroup`.

Closes #837
This commit is contained in:
Guido Casati
2026-01-29 10:17:42 +01:00
parent 50de6bd8fb
commit 806a15da01
8 changed files with 229 additions and 201 deletions
@@ -634,6 +634,59 @@ static void handle_ue_context_rrc_container(const byte_array_t *rrc_container, c
nr_rlc_srb_recv_sdu(rnti, id, rrc_container->buf, rrc_container->len);
}
/** @brief Get and clone CellGroupConfig for UE Context Setup/Modification response.
* The source depends on re-establishment state:
* - CellGroup: Currently active/runtime CellGroupConfig used by MAC for scheduling.
* During re-establishment, spCellConfig is removed from it (per TS 38.331 §5.3.7.2).
* - reconfigCellGroup: Staging area that holds a complete CellGroupConfig saved before
* modifications. During re-establishment, it still contains spCellConfig (saved before
* removal). It becomes the new CellGroup when reconfiguration completes.
* During re-establishment, we must clone from reconfigCellGroup to get a complete
* CellGroupConfig with spCellConfig for transparent forwarding to the UE.
* @param UE UE context
* @return Cloned CellGroupConfig */
static NR_CellGroupConfig_t *get_cellgroup_config(NR_UE_info_t *UE)
{
if (UE->reestablish_rlc && UE->reconfigCellGroup != NULL) {
return clone_CellGroupConfig(UE->reconfigCellGroup);
} else {
return clone_CellGroupConfig(UE->CellGroup);
}
}
/** @brief Update CellGroupConfig for RRC re-establishment procedure.
* This function prepares a complete CellGroupConfig for gNB-DU Configuration Query response
* during RRC re-establishment.
* The function sets reestablishRLC flags for all RLC bearers except SRB1.
* SRB1 is removed from the bearer list since it is already re-established during
* RRCReestablishment and should not be included. */
static void update_cellgroup_for_reestablishment(NR_UE_info_t *UE, NR_CellGroupConfig_t *new_CellGroup)
{
DevAssert(new_CellGroup);
DevAssert(new_CellGroup->spCellConfig);
DevAssert(UE->reestablish_rlc);
LOG_I(NR_MAC, "UE %04x: Re-establishment detected, setting reestablishRLC flags\n", UE->rnti);
struct NR_CellGroupConfig__rlc_BearerToAddModList *addmod = new_CellGroup->rlc_BearerToAddModList;
if (addmod && addmod->list.count > 0) {
LOG_I(NR_MAC, "UE %04x: CellGroupConfig has %d bearers:\n", UE->rnti, addmod->list.count);
for (int i = 0; i < addmod->list.count; ++i) {
NR_RLC_BearerConfig_t *bearer = addmod->list.array[i];
int lcid = bearer->logicalChannelIdentity;
int rb_type = bearer->servedRadioBearer->present;
int rb_id = (rb_type == NR_RLC_BearerConfig__servedRadioBearer_PR_srb_Identity)
? bearer->servedRadioBearer->choice.srb_Identity
: bearer->servedRadioBearer->choice.drb_Identity;
if (rb_type == NR_RLC_BearerConfig__servedRadioBearer_PR_srb_Identity && rb_id == 1) {
asn_sequence_del(&addmod->list, i, 1);
--i;
continue;
}
LOG_I(NR_MAC, "UE %04x: Re-establishing RLC for LCID %d\n", UE->rnti, lcid);
asn1cCallocOne(addmod->list.array[i]->reestablishRLC, NR_RLC_BearerConfig__reestablishRLC_true);
}
}
}
void ue_context_setup_request(const f1ap_ue_context_setup_req_t *req)
{
const bool is_SA = IS_SA_MODE(get_softmodem_params());
@@ -766,7 +819,7 @@ void ue_context_modification_request(const f1ap_ue_context_mod_req_t *req)
return;
}
NR_CellGroupConfig_t *new_CellGroup = clone_CellGroupConfig(UE->CellGroup);
NR_CellGroupConfig_t *new_CellGroup = get_cellgroup_config(UE);
if (req->srbs_len > 0) {
resp.srbs_len = handle_ue_context_srbs_setup(UE, req->srbs_len, req->srbs, &resp.srbs, new_CellGroup, &mac->rlc_config);
@@ -804,6 +857,7 @@ void ue_context_modification_request(const f1ap_ue_context_mod_req_t *req)
for (int i = 1; i < seq_arr_size(&UE->UE_sched_ctrl.lc_config); ++i) {
nr_lc_config_t *c = seq_arr_at(&UE->UE_sched_ctrl.lc_config, i);
c->suspended = false;
LOG_I(NR_MAC, "UE %04x: Re-establishing RLC for LCID %d\n", UE->rnti, c->lcid);
nr_rlc_reestablish_entity(req->gNB_DU_ue_id, c->lcid);
}
UE->reestablish_rlc = false;
@@ -821,12 +875,21 @@ void ue_context_modification_request(const f1ap_ue_context_mod_req_t *req)
update_cellGroupConfig(new_CellGroup, UE->uid, UE->capability, &mac->radio_config, scc);
}
// 3GPP TS 38.473 Clause 8.3.4: If gNB-DU Configuration Query is present, include CellGroupConfig
/* 3GPP TS 38.473 Clause 8.3.4: If gNB-DU Configuration Query is present, include CellGroupConfig
* (CU requested CellGroupConfig for transparent forwarding) */
if (req->gNB_DU_Configuration_Query != NULL && *req->gNB_DU_Configuration_Query) {
LOG_I(NR_MAC, "UE %04x: gNB-DU Configuration Query received, will include CellGroupConfig in response\n", UE->rnti);
if (UE->reestablish_rlc) {
update_cellgroup_for_reestablishment(UE, new_CellGroup);
}
/* Encode CellGroupConfig for transparent forwarding in the DU to CU trasnfer IE
* CU will forward these encoded bytes directly to UE without decode/re-encode cycles */
resp.du_to_cu_rrc_info = calloc_or_fail(1, sizeof(du_to_cu_rrc_information_t));
resp.du_to_cu_rrc_info->cell_group_config = encode_cellgroup_config(new_CellGroup);
// Replace reconfigCellGroup with new_CellGroup (now contains complete config with spCellConfig restored)
ASN_STRUCT_FREE(asn_DEF_NR_CellGroupConfig, UE->reconfigCellGroup);
UE->reconfigCellGroup = new_CellGroup;
configure_UE_BWP(mac, scc, UE, false, NR_SearchSpace__searchSpaceType_PR_common, -1, -1);
+8 -32
View File
@@ -532,7 +532,7 @@ static NR_RRCReconfiguration_IEs_t *build_RRCReconfiguration_IEs(const nr_rrc_re
ie->measConfig = params->meas_config;
/* nonCriticalExtension, RRCReconfiguration-v1530-IEs */
if (params->cell_group_config || params->num_nas_msg) {
if (params->cgc || params->num_nas_msg) {
// Allocate memory for extension IE
ie->nonCriticalExtension = calloc_or_fail(1, sizeof(*ie->nonCriticalExtension));
}
@@ -549,37 +549,13 @@ static NR_RRCReconfiguration_IEs_t *build_RRCReconfiguration_IEs(const nr_rrc_re
}
}
/* masterCellGroup */
if (params->cell_group_config) {
uint8_t temp[4096];
asn_enc_rval_t enc = uper_encode_to_buffer(&asn_DEF_NR_CellGroupConfig, NULL, params->cell_group_config, temp, sizeof(temp));
if (enc.encoded <= 0) {
LOG_E(NR_RRC, "ASN.1 encoding failed for NR_CellGroupConfig (encoded=%ld)\n", enc.encoded);
if (enc.failed_type) {
LOG_E(NR_RRC, "Failed at ASN.1 type: %s\n", enc.failed_type->name);
}
if (enc.structure_ptr) {
LOG_E(NR_RRC, "Failed at structure element: %p\n", enc.structure_ptr);
}
// Print diagnostic information to help debug the issue
LOG_E(NR_RRC, "CellGroupConfig structure that failed to encode:\n");
xer_fprint(stdout, &asn_DEF_NR_CellGroupConfig, (const void *)params->cell_group_config);
ASN_STRUCT_FREE(asn_DEF_NR_RRCReconfiguration_IEs, ie);
return NULL;
}
DevAssert(enc.encoded <= sizeof(temp) * 8); // Encoded data must fit in temp buffer
// Allocate buffer for the encoded data and copy it
// enc.encoded is in bits, convert to bytes
size_t encoded_bytes = (enc.encoded + 7) / 8;
uint8_t *buf = calloc_or_fail(1, encoded_bytes);
memcpy(buf, temp, encoded_bytes);
if (LOG_DEBUGFLAG(DEBUG_ASN1)) {
xer_fprint(stdout, &asn_DEF_NR_CellGroupConfig, (const void *)params->cell_group_config);
}
/* masterCellGroup - Transparent forwarding per TS 38.473 */
if (params->cgc) {
// Copy pre-encoded CellGroupConfig bytes directly without decode/re-encode (TS 38.473 transparency)
// CU receives encoded bytes from DU and forwards to UE without modification
LOG_D(NR_RRC, "Transparent forwarding CellGroupConfig (len=%ld bytes)\n", params->cgc->len);
ie->nonCriticalExtension->masterCellGroup = calloc_or_fail(1, sizeof(*ie->nonCriticalExtension->masterCellGroup));
*ie->nonCriticalExtension->masterCellGroup = (OCTET_STRING_t){.buf = buf, .size = encoded_bytes};
OCTET_STRING_fromBuf(ie->nonCriticalExtension->masterCellGroup, (const char *)params->cgc->buf, params->cgc->len);
}
/* masterKeyUpdate */
@@ -630,7 +606,7 @@ static byte_array_t do_HO_RRCReconfiguration(nr_rrc_reconfig_param_t *params)
return msg;
}
static void dump_cgc(const uint8_t *buf, size_t len)
void dump_cgc(const uint8_t *buf, size_t len)
{
// Decode the encoded CellGroupConfig for debugging
NR_CellGroupConfig_t *temp_cellGroupConfig = NULL;
+2 -1
View File
@@ -70,7 +70,7 @@ typedef struct {
NR_MeasConfig_t *meas_config;
byte_array_t dedicated_NAS_msg_list[MAX_DRBS_PER_UE];
int num_nas_msg;
NR_CellGroupConfig_t *cell_group_config;
byte_array_t *cgc;
bool masterKeyUpdate;
int nextHopChainingCount;
byte_array_t ue_cap;
@@ -179,5 +179,6 @@ int doRRCReconfiguration_from_HandoverCommand(byte_array_t *ba, byte_array_t han
struct NR_UE_NR_Capability *get_ue_nr_capability(int rnti, uint8_t *buf, uint32_t len);
NR_UE_NR_Capability_t *decode_nr_ue_capability(int rnti, const NR_UE_CapabilityRAT_ContainerList_t *clist);
void dump_cgc(const uint8_t *buf, size_t len);
#endif /* __RRC_NR_MESSAGES_ASN1_MSG__H__ */
+2 -1
View File
@@ -186,7 +186,8 @@ typedef struct gNB_RRC_UE_s {
NR_UE_MRDC_Capability_t* UE_Capability_MRDC;
int UE_MRDC_Capability_size;
NR_CellGroupConfig_t *masterCellGroup;
// Transparent forwarding of CellGroupConfig
byte_array_t mcg;
NR_RadioBearerConfig_t *rb_config;
/* KgNB as derived from KASME received from EPC */
+142 -152
View File
@@ -861,7 +861,7 @@ nr_rrc_reconfig_param_t get_RRCReconfiguration_params(gNB_RRC_INST *rrc, gNB_RRC
NR_SRB_ToAddModList_t *SRBs = createSRBlist(UE, srb_reest_bitmap);
NR_DRB_ToAddModList_t *DRBs = createDRBlist(UE, drb_reestablish, do_integrity, do_ciphering);
nr_rrc_reconfig_param_t params = {.cell_group_config = UE->masterCellGroup,
nr_rrc_reconfig_param_t params = {.cgc = &UE->mcg,
.transaction_id = xid,
.drb_config_list = DRBs,
.meas_config = UE->measConfig,
@@ -918,10 +918,22 @@ byte_array_t rrc_gNB_encode_RRCReconfiguration(gNB_RRC_INST *rrc, gNB_RRC_UE_t *
return msg;
}
static void rrc_gNB_generate_dedicatedRRCReconfiguration(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue_p)
/** @brief Generate and send RRC Reconfiguration message.
* Handles both normal reconfiguration and first reconfiguration after re-establishment.
* @param rrc RRC instance
* @param ue_p UE context
* @param is_reestablishment True if this is the first reconfiguration after re-establishment */
static void rrc_gNB_generate_dedicatedRRCReconfiguration(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue_p, bool is_reestablishment)
{
/* do not re-establish PDCP for any bearer */
nr_rrc_reconfig_param_t params = get_RRCReconfiguration_params(rrc, ue_p, 0, false);
/** Get RRC Reconfiguration parameters
* For the first reconfiguration after a re-establishment: re-establish PDCP for SRB2
* (SRB1 already re-established in RRCReestablishment) and all DRBs,
* For the subsequent reconfigurations: do not re-establish PDCP for any bearer */
uint8_t srb_reest_bitmap = is_reestablishment ? (1 << SRB2) : 0;
bool drb_reestablish = is_reestablishment;
nr_rrc_reconfig_param_t params = get_RRCReconfiguration_params(rrc, ue_p, srb_reest_bitmap, drb_reestablish);
// Handle PDU session status updates
FOR_EACH_SEQ_ARR(rrc_pdu_session_param_t *, item, &ue_p->pduSessions) {
if (item->status == PDU_SESSION_STATUS_NEW) {
item->status = PDU_SESSION_STATUS_DONE;
@@ -932,15 +944,27 @@ static void rrc_gNB_generate_dedicatedRRCReconfiguration(gNB_RRC_INST *rrc, gNB_
}
// Set xid for RRC transaction
ue_p->xids[params.transaction_id] = params.n_drb_rel > 0 ? RRC_PDUSESSION_RELEASE : RRC_PDUSESSION_ESTABLISH;
if (is_reestablishment) {
ue_p->xids[params.transaction_id] = RRC_REESTABLISH_COMPLETE;
} else {
ue_p->xids[params.transaction_id] = params.n_drb_rel > 0 ? RRC_PDUSESSION_RELEASE : RRC_PDUSESSION_ESTABLISH;
}
byte_array_t msg = rrc_gNB_encode_RRCReconfiguration(rrc, ue_p, params);
if (msg.len <= 0) {
LOG_E(NR_RRC, "UE %d: Failed to generate RRCReconfiguration\n", ue_p->rrc_ue_id);
LOG_E(NR_RRC,
"UE %d: Failed to generate RRCReconfiguration%s\n",
ue_p->rrc_ue_id,
is_reestablishment ? " for re-establishment" : "");
free_RRCReconfiguration_params(params);
return;
}
LOG_UE_DL_EVENT(ue_p, "Generate RRCReconfiguration (bytes %ld, xid %d)\n", msg.len, params.transaction_id);
LOG_UE_DL_EVENT(ue_p,
"Generate RRCReconfiguration%s (bytes %ld, xid %d)\n",
is_reestablishment ? " after re-establishment" : "",
msg.len,
params.transaction_id);
const uint32_t msg_id = NR_DL_DCCH_MessageType__c1_PR_rrcReconfiguration;
nr_rrc_transfer_protected_rrc_message(rrc, ue_p, DL_SCH_LCID_DCCH, msg_id, msg.buf, msg.len);
free_RRCReconfiguration_params(params);
@@ -1230,47 +1254,10 @@ static void rrc_gNB_process_RRCReestablishmentComplete(gNB_RRC_INST *rrc, gNB_RR
{
LOG_I(NR_RRC, "UE %d Processing NR_RRCReestablishmentComplete from UE\n", ue_p->rrc_ue_id);
int i = 0;
ue_p->xids[xid] = RRC_ACTION_NONE;
ue_p->Srb[1].Active = 1;
NR_CellGroupConfig_t *cellGroupConfig = calloc(1, sizeof(NR_CellGroupConfig_t));
cellGroupConfig->spCellConfig = ue_p->masterCellGroup->spCellConfig;
cellGroupConfig->mac_CellGroupConfig = ue_p->masterCellGroup->mac_CellGroupConfig;
cellGroupConfig->physicalCellGroupConfig = ue_p->masterCellGroup->physicalCellGroupConfig;
cellGroupConfig->rlc_BearerToReleaseList = NULL;
cellGroupConfig->rlc_BearerToAddModList = calloc(1, sizeof(*cellGroupConfig->rlc_BearerToAddModList));
/*
* Get SRB2, DRB configuration from the existing UE context,
* also start from SRB2 (i=1) and not from SRB1 (i=0).
*/
for (i = 1; i < ue_p->masterCellGroup->rlc_BearerToAddModList->list.count; ++i)
asn1cSeqAdd(&cellGroupConfig->rlc_BearerToAddModList->list, ue_p->masterCellGroup->rlc_BearerToAddModList->list.array[i]);
/*
* At this stage, PDCP entity are re-established and reestablishRLC is flagged
* with RRCReconfiguration to complete RLC re-establishment of remaining bearers
*/
for (i = 0; i < cellGroupConfig->rlc_BearerToAddModList->list.count; i++) {
asn1cCallocOne(cellGroupConfig->rlc_BearerToAddModList->list.array[i]->reestablishRLC,
NR_RLC_BearerConfig__reestablishRLC_true);
}
/* TODO: remove a reconfigurationWithSync, we don't need it for
* reestablishment. The whole reason why this might be here is that we store
* the CellGroupConfig (after handover), and simply reuse it for
* reestablishment, instead of re-requesting the CellGroupConfig from the DU.
* Hence, add below hack; the solution would be to request the
* CellGroupConfig from the DU when doing reestablishment. */
if (cellGroupConfig->spCellConfig && cellGroupConfig->spCellConfig->reconfigurationWithSync) {
ASN_STRUCT_FREE(asn_DEF_NR_ReconfigurationWithSync, cellGroupConfig->spCellConfig->reconfigurationWithSync);
cellGroupConfig->spCellConfig->reconfigurationWithSync = NULL;
}
/* Re-establish SRB2 according to clause 5.3.5.6.3 of 3GPP TS 38.331
* (SRB1 is re-established with RRCReestablishment message)
*/
@@ -1287,37 +1274,26 @@ static void rrc_gNB_process_RRCReestablishmentComplete(gNB_RRC_INST *rrc, gNB_RR
/* PDCP Reestablishment of DRBs according to 5.3.5.6.5 of 3GPP TS 38.331 (over E1) */
cuup_notify_reestablishment(rrc, ue_p);
/* Create srb-ToAddModList, 3GPP TS 38.331 RadioBearerConfig:
* do not re-establish PDCP for SRB1, when resuming an RRC connection,
* or at the first reconfiguration after RRC connection reestablishment in NR */
NR_SRB_ToAddModList_t *SRBs = createSRBlist(ue_p, 1 << SRB2); // Re-establish PDCP for SRB2 only
/* Create drb-ToAddModList */
bool do_integrity = rrc->security.do_drb_integrity;
bool do_ciphering = rrc->security.do_drb_ciphering;
NR_DRB_ToAddModList_t *DRBs = createDRBlist(ue_p, true, do_integrity, do_ciphering);
// Request DU to provide CellGroupConfig with reestablishRLC flags for re-establishment
// According to TS 38.473 transparency requirements, CU should not construct or re-encode CellGroupConfig
f1_ue_data_t ue_data = cu_get_f1_ue_data(ue_p->rrc_ue_id);
RETURN_IF_INVALID_ASSOC_ID(ue_data.du_assoc_id);
f1ap_ue_context_mod_req_t ue_context_modif_req = {
.gNB_CU_ue_id = ue_p->rrc_ue_id,
.gNB_DU_ue_id = ue_data.secondary_ue,
};
// Request CellGroupConfig from DU in the response
ue_context_modif_req.gNB_DU_Configuration_Query = calloc_or_fail(1, sizeof(bool));
*ue_context_modif_req.gNB_DU_Configuration_Query = true;
/** Send UE context modification request to DU, which will respond with CellGroupConfig
* containing reestablishRLC flags for re-establishment.
* The response will be handled in rrc_CU_process_ue_context_modification_response()
* which will store the encoded CellGroupConfig and trigger RRC Reconfiguration */
rrc->mac_rrc.ue_context_modification_request(ue_data.du_assoc_id, &ue_context_modif_req);
uint8_t new_xid = rrc_gNB_get_next_transaction_identifier(rrc->module_id);
ue_p->xids[new_xid] = RRC_REESTABLISH_COMPLETE;
nr_rrc_reconfig_param_t params = {.cell_group_config = cellGroupConfig,
.meas_config = ue_p->measConfig,
.drb_config_list = DRBs,
.srb_config_list = SRBs,
.transaction_id = new_xid};
byte_array_t msg = do_RRCReconfiguration(&params);
if (msg.len <= 0) {
LOG_E(NR_RRC, "UE %d: Failed to generate RRCReconfiguration\n", ue_p->rrc_ue_id);
return;
}
LOG_DUMPMSG(NR_RRC, DEBUG_RRC, msg.buf, msg.len, "[MSG] RRC Reconfiguration\n");
LOG_I(NR_RRC,
"UE %d RNTI %04x: Generate NR_RRCReconfiguration after reestablishment complete (bytes %ld)\n",
ue_p->rrc_ue_id,
ue_p->rnti,
msg.len);
const uint32_t msg_id = NR_DL_DCCH_MessageType__c1_PR_rrcReconfiguration;
nr_rrc_transfer_protected_rrc_message(rrc, ue_p, DL_SCH_LCID_DCCH, msg_id, msg.buf, msg.len);
free_RRCReconfiguration_params(params);
free_byte_array(msg);
}
int nr_rrc_reconfiguration_req(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue_p, const int dl_bwp_id, const int ul_bwp_id)
@@ -1326,16 +1302,7 @@ int nr_rrc_reconfiguration_req(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue_p, const int
ue_p->xids[xid] = RRC_DEDICATED_RECONF;
ue_p->ongoing_reconfiguration = true;
NR_CellGroupConfig_t *masterCellGroup = ue_p->masterCellGroup;
if (dl_bwp_id > 0) {
*masterCellGroup->spCellConfig->spCellConfigDedicated->firstActiveDownlinkBWP_Id = dl_bwp_id;
*masterCellGroup->spCellConfig->spCellConfigDedicated->defaultDownlinkBWP_Id = dl_bwp_id;
}
if (ul_bwp_id > 0) {
*masterCellGroup->spCellConfig->spCellConfigDedicated->uplinkConfig->firstActiveUplinkBWP_Id = ul_bwp_id;
}
nr_rrc_reconfig_param_t params = {.cell_group_config = masterCellGroup, .transaction_id = xid};
nr_rrc_reconfig_param_t params = {.cgc = &ue_p->mcg, .transaction_id = xid};
byte_array_t msg = do_RRCReconfiguration(&params);
if (msg.len <= 0) {
LOG_E(NR_RRC, "UE %d: Failed to generate RRCReconfiguration\n", ue_p->rrc_ue_id);
@@ -1348,6 +1315,19 @@ int nr_rrc_reconfiguration_req(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue_p, const int
return 0;
}
/** @brief Store encoded CellGroupConfig for transparent forwarding (TS 38.473 requirement).
* CU must not decode/re-encode CellGroupConfig, only forward encoded bytes to UE. */
static void store_cgc(gNB_RRC_UE_t *UE, const byte_array_t *cgc)
{
LOG_D(RRC, "UE %04x storing CellGroupConfig encoded bytes for transparent forwarding (len=%ld)\n", UE->rnti, cgc->len);
free_byte_array(UE->mcg);
UE->mcg = copy_byte_array(*cgc);
if (LOG_DEBUGFLAG(DEBUG_ASN1))
dump_cgc(UE->mcg.buf, UE->mcg.len);
}
static void rrc_handle_RRCSetupRequest(gNB_RRC_INST *rrc,
sctp_assoc_t assoc_id,
const NR_RRCSetupRequest_IEs_t *rrcSetupRequest,
@@ -1405,19 +1385,12 @@ static void rrc_handle_RRCSetupRequest(gNB_RRC_INST *rrc,
rrc_gNB_generate_RRCReject(rrc, ue_context_p);
return;
}
NR_CellGroupConfig_t *cellGroupConfig = NULL;
asn_dec_rval_t dec_rval = uper_decode_complete(NULL,
&asn_DEF_NR_CellGroupConfig,
(void **)&cellGroupConfig,
msg->du2cu_rrc_container,
msg->du2cu_rrc_container_length);
AssertFatal(dec_rval.code == RC_OK && dec_rval.consumed > 0, "Cell group config decode error\n");
gNB_RRC_UE_t *UE = &ue_context_p->ue_context;
UE = &ue_context_p->ue_context;
byte_array_t cgc = {.buf = msg->du2cu_rrc_container, .len = msg->du2cu_rrc_container_length};
store_cgc(UE, &cgc);
UE->establishment_cause = rrcSetupRequest->establishmentCause;
UE->nr_cellid = msg->nr_cellid;
UE->masterCellGroup = cellGroupConfig;
UE->ongoing_reconfiguration = false;
UE->measConfig = nr_rrc_get_measconfig(rrc, UE->nr_cellid);
activate_srb(UE, 1);
@@ -1561,9 +1534,7 @@ static void rrc_handle_RRCReestablishmentRequest(gNB_RRC_INST *rrc,
f1u_dl_gtp_rollback(UE);
/* we need the original CellGroupConfig */
ASN_STRUCT_FREE(asn_DEF_NR_CellGroupConfig, UE->masterCellGroup);
UE->masterCellGroup = source_ctx->old_cellGroupConfig;
source_ctx->old_cellGroupConfig = NULL;
store_cgc(UE, &source_ctx->old_cgc);
/* update to old DU assoc id -- RNTI + secondary DU UE ID further below */
ue_data.du_assoc_id = source_ctx->du->assoc_id;
@@ -2449,6 +2420,61 @@ void e1_notify_pdcp_status(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, const ngap_drb_s
free_e1ap_context_mod_request(&req);
}
static NR_MeasGapConfig_t *get_meas_gap_config(const f1ap_du_to_cu_rrc_info_t *du_to_cu_rrc_info)
{
if (!du_to_cu_rrc_info->meas_gap_config) {
return NULL;
}
NR_MeasGapConfig_t *measGapConfig = NULL;
asn_dec_rval_t dec_rval = uper_decode_complete(NULL,
&asn_DEF_NR_MeasGapConfig,
(void **)&measGapConfig,
(uint8_t *)du_to_cu_rrc_info->meas_gap_config->buf,
du_to_cu_rrc_info->meas_gap_config->len);
AssertFatal(dec_rval.code == RC_OK && dec_rval.consumed > 0, "measGapConfig decode error\n");
return measGapConfig;
}
/** @brief Detect re-establishment by checking reestablishRLC flags in CellGroupConfig.
* Temporary decode only for detection (not for modification, to preserve transparency).
* DU sets reestablishRLC=true for bearers needing RLC re-establishment (except SRB1).
* @param cgc Encoded CellGroupConfig from DU to CU transfer IE
* @return True if re-establishment is detected, false otherwise */
static bool rrc_detect_reestablishment(const byte_array_t *cgc)
{
if (!cgc || !cgc->buf || cgc->len == 0) {
return false;
}
NR_CellGroupConfig_t *temp_cellGroupConfig = NULL;
asn_dec_rval_t dec_rval =
uper_decode_complete(NULL, &asn_DEF_NR_CellGroupConfig, (void **)&temp_cellGroupConfig, cgc->buf, cgc->len);
if (dec_rval.code != RC_OK || dec_rval.consumed == 0) {
LOG_W(NR_RRC,
"Failed to decode CellGroupConfig for re-establishment detection (code=%d, consumed=%zu)\n",
dec_rval.code,
dec_rval.consumed);
return false;
}
bool is_reestablishment = false;
// Check if any RLC bearer has reestablishRLC flag set
if (temp_cellGroupConfig->rlc_BearerToAddModList && temp_cellGroupConfig->rlc_BearerToAddModList->list.count > 0) {
for (int i = 0; i < temp_cellGroupConfig->rlc_BearerToAddModList->list.count; i++) {
if (temp_cellGroupConfig->rlc_BearerToAddModList->list.array[i]->reestablishRLC
&& *temp_cellGroupConfig->rlc_BearerToAddModList->list.array[i]->reestablishRLC
== NR_RLC_BearerConfig__reestablishRLC_true) {
is_reestablishment = true;
break;
}
}
}
ASN_STRUCT_FREE(asn_DEF_NR_CellGroupConfig, temp_cellGroupConfig);
return is_reestablishment;
}
static void rrc_CU_process_ue_context_setup_response(MessageDef *msg_p, instance_t instance)
{
f1ap_ue_context_setup_resp_t *resp = &F1AP_UE_CONTEXT_SETUP_RESP(msg_p);
@@ -2461,31 +2487,16 @@ static void rrc_CU_process_ue_context_setup_response(MessageDef *msg_p, instance
gNB_RRC_UE_t *UE = &ue_context_p->ue_context;
UE->f1_ue_context_active = true;
NR_CellGroupConfig_t *cellGroupConfig = NULL;
// Store the encoded CellGroupConfig for transparent forwarding
byte_array_t *cgc = &resp->du_to_cu_rrc_info.cell_group_config;
asn_dec_rval_t dec_rval =
uper_decode_complete(NULL, &asn_DEF_NR_CellGroupConfig, (void **)&cellGroupConfig, (uint8_t *)cgc->buf, cgc->len);
AssertFatal(dec_rval.code == RC_OK && dec_rval.consumed > 0, "Cell group config decode error\n");
store_cgc(UE, cgc);
if (resp->du_to_cu_rrc_info.meas_gap_config) {
NR_MeasGapConfig_t *measGapConfig = NULL;
asn_dec_rval_t dec_rval_mgc = uper_decode_complete(NULL,
&asn_DEF_NR_MeasGapConfig,
(void **)&measGapConfig,
(uint8_t *)resp->du_to_cu_rrc_info.meas_gap_config->buf,
resp->du_to_cu_rrc_info.meas_gap_config->len);
AssertFatal(dec_rval_mgc.code == RC_OK && dec_rval_mgc.consumed > 0, "measGapConfig decode error\n");
// Process measurement gap config if present
NR_MeasGapConfig_t *measGapConfig = get_meas_gap_config(&resp->du_to_cu_rrc_info);
if (measGapConfig) {
UE->measConfig->measGapConfig = measGapConfig;
}
if (UE->masterCellGroup) {
ASN_STRUCT_FREE(asn_DEF_NR_CellGroupConfig, UE->masterCellGroup);
LOG_I(RRC, "UE %04x replacing existing CellGroupConfig with new one received from DU\n", UE->rnti);
}
UE->masterCellGroup = cellGroupConfig;
if (LOG_DEBUGFLAG(DEBUG_ASN1))
xer_fprint(stdout, &asn_DEF_NR_CellGroupConfig, UE->masterCellGroup);
if (!IS_SA_MODE(get_softmodem_params())) {
rrc_add_nsa_user_resp(rrc, UE, resp);
return;
@@ -2506,7 +2517,9 @@ static void rrc_CU_process_ue_context_setup_response(MessageDef *msg_p, instance
}
if (UE->ho_context == NULL) {
rrc_gNB_generate_dedicatedRRCReconfiguration(rrc, UE);
// Check if this is a re-establishment scenario by examining the CellGroupConfig
bool is_reestablishment = rrc_detect_reestablishment(cgc);
rrc_gNB_generate_dedicatedRRCReconfiguration(rrc, UE, is_reestablishment);
} else {
// case of handover
// handling of "target CU" information
@@ -2578,7 +2591,7 @@ static void rrc_delete_ue_data(gNB_RRC_UE_t *UE)
if (UE->ho_context)
nr_rrc_finalize_ho(UE);
ASN_STRUCT_FREE(asn_DEF_NR_UE_NR_Capability, UE->UE_Capability_nr);
ASN_STRUCT_FREE(asn_DEF_NR_CellGroupConfig, UE->masterCellGroup);
free_byte_array(UE->mcg);
ASN_STRUCT_FREE(asn_DEF_NR_MeasResults, UE->measResults);
FREE_AND_ZERO_BYTE_ARRAY(UE->ue_cap_buffer);
free_MeasConfig(UE->measConfig);
@@ -2641,22 +2654,18 @@ static void rrc_CU_process_ue_context_modification_response(MessageDef *msg_p, i
}
if (resp->du_to_cu_rrc_info) {
NR_CellGroupConfig_t *cellGroupConfig = NULL;
byte_array_t *cgc = &resp->du_to_cu_rrc_info->cell_group_config;
asn_dec_rval_t dec_rval =
uper_decode_complete(NULL, &asn_DEF_NR_CellGroupConfig, (void **)&cellGroupConfig, (uint8_t *)cgc->buf, cgc->len);
AssertFatal(dec_rval.code == RC_OK && dec_rval.consumed > 0, "Cell group config decode error\n");
/* hack for interoperability with srsRAN: ignore cell group config if empty */
/* cell group config is empty if buffer length is 2 and content is all 0 */
bool cgc_is_empty = cgc->len == 2 && cgc->buf[0] == 0 && cgc->buf[1] == 0;
if (!cgc_is_empty) {
if (UE->masterCellGroup) {
ASN_STRUCT_FREE(asn_DEF_NR_CellGroupConfig, UE->masterCellGroup);
LOG_I(RRC, "UE %04x replacing existing CellGroupConfig with new one received from DU\n", UE->rnti);
}
UE->masterCellGroup = cellGroupConfig;
rrc_gNB_generate_dedicatedRRCReconfiguration(rrc, UE);
// Store the encoded CellGroupConfig for transparent forwarding
store_cgc(UE, cgc);
// Check if this is a re-establishment scenario by examining the CellGroupConfig
bool is_reestablishment = rrc_detect_reestablishment(cgc);
rrc_gNB_generate_dedicatedRRCReconfiguration(rrc, UE, is_reestablishment);
} else {
LOG_W(NR_RRC, "hack: UE %d: ignore empty CellGroupConfig in UEContextModificationResponse\n", UE->rrc_ue_id);
}
@@ -2704,31 +2713,12 @@ static void rrc_CU_process_ue_modification_required(MessageDef *msg_p, instance_
UE->rrc_ue_id,
UE->rnti);
NR_CellGroupConfig_t *cellGroupConfig = NULL;
asn_dec_rval_t dec_rval = uper_decode_complete(NULL,
&asn_DEF_NR_CellGroupConfig,
(void **)&cellGroupConfig,
(uint8_t *)required->du_to_cu_rrc_information->cellGroupConfig,
required->du_to_cu_rrc_information->cellGroupConfig_length);
if (dec_rval.code != RC_OK && dec_rval.consumed == 0) {
LOG_E(RRC, "Cell group config decode error, refusing reconfiguration\n");
f1ap_ue_context_modif_refuse_t refuse = {
.gNB_CU_ue_id = required->gNB_CU_ue_id,
.gNB_DU_ue_id = required->gNB_DU_ue_id,
.cause = F1AP_CAUSE_PROTOCOL,
.cause_value = F1AP_CauseProtocol_transfer_syntax_error,
};
rrc->mac_rrc.ue_context_modification_refuse(msg_p->ittiMsgHeader.originInstance, &refuse);
return;
}
// Store the encoded CellGroupConfig for transparent forwarding
uint8_t *cgc_buf = required->du_to_cu_rrc_information->cellGroupConfig;
size_t cgc_len = required->du_to_cu_rrc_information->cellGroupConfig_length;
if (UE->masterCellGroup) {
ASN_STRUCT_FREE(asn_DEF_NR_CellGroupConfig, UE->masterCellGroup);
LOG_I(RRC, "UE %d/RNTI %04x replacing existing CellGroupConfig with new one received from DU\n", UE->rrc_ue_id, UE->rnti);
}
UE->masterCellGroup = cellGroupConfig;
if (LOG_DEBUGFLAG(DEBUG_ASN1))
xer_fprint(stdout, &asn_DEF_NR_CellGroupConfig, UE->masterCellGroup);
byte_array_t temp_cgc = {.buf = cgc_buf, .len = cgc_len};
store_cgc(UE, &temp_cgc);
/* trigger reconfiguration */
if (!UE->ongoing_reconfiguration)
+3 -3
View File
@@ -148,8 +148,8 @@ static void nr_initiate_handover(const gNB_RRC_INST *rrc,
ho_ctx->source->old_du_tunnel_config = drb->du_tunnel_config;
}
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");
// Store the old CellGroupConfig for reestablishment
ho_ctx->source->old_cgc = copy_byte_array(ue->mcg);
}
LOG_A(NR_RRC,
@@ -305,7 +305,7 @@ void nr_rrc_trigger_f1_ho(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue, nr_rrc_du_contain
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_byte_array(ue->ho_context->source->old_cgc);
free_ho_ctx(ue->ho_context);
ue->ho_context = NULL;
}
+1 -1
View File
@@ -50,7 +50,7 @@ typedef struct nr_ho_source_cu {
rnti_t old_rnti;
/// old (source) CellGroupConfig (to send the cellGroupConfig in case of
/// reestablishment)
NR_CellGroupConfig_t *old_cellGroupConfig;
byte_array_t old_cgc;
/// function pointer to announce the handover cancellation, e.g.,
/// reestablishment
ho_cancel_t ho_cancel;
+6 -9
View File
@@ -386,18 +386,15 @@ void rrc_add_nsa_user(gNB_RRC_INST *rrc, x2ap_ENDC_sgnb_addition_req_t *m, sctp_
free_ue_context_setup_req(&req);
}
static NR_RRCReconfiguration_IEs_t *get_default_reconfig(const NR_CellGroupConfig_t *secondaryCellGroup)
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(secondaryCellGroup != NULL, "secondaryCellGroup is null\n");
AssertFatal(cgc.buf != NULL, "cgc is null\n");
reconfig->radioBearerConfig = NULL;
char scg_buffer[1024];
asn_enc_rval_t enc_rval = uper_encode_to_buffer(&asn_DEF_NR_CellGroupConfig, NULL, (void *)secondaryCellGroup, scg_buffer, 1024);
AssertFatal(enc_rval.encoded > 0, "ASN1 message encoding failed (%s, %jd)!\n", enc_rval.failed_type->name, enc_rval.encoded);
reconfig->secondaryCellGroup = calloc(1, sizeof(*reconfig->secondaryCellGroup));
OCTET_STRING_fromBuf(reconfig->secondaryCellGroup, (const char *)scg_buffer, (enc_rval.encoded + 7) >> 3);
// 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;
@@ -469,7 +466,7 @@ void rrc_add_nsa_user_resp(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, const f1ap_ue_co
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->masterCellGroup);
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);