diff --git a/ci-scripts/conf_files/neighbour-config-ho.conf b/ci-scripts/conf_files/neighbour-config-ho.conf index 07d58e14e5..2b5bbb02bd 100644 --- a/ci-scripts/conf_files/neighbour-config-ho.conf +++ b/ci-scripts/conf_files/neighbour-config-ho.conf @@ -47,8 +47,8 @@ nr_measurement_configuration = { A3 = ({ physCellId = -1; #Default - offset = 10; - hysteresis = 0; - time_to_trigger = 1 + offset = 16; + hysteresis = 1; + time_to_trigger = 10; }) }; diff --git a/ci-scripts/xml_files/container_5g_f1_rfsim.xml b/ci-scripts/xml_files/container_5g_f1_rfsim.xml index 1ffba40f77..7385daacfc 100644 --- a/ci-scripts/xml_files/container_5g_f1_rfsim.xml +++ b/ci-scripts/xml_files/container_5g_f1_rfsim.xml @@ -240,7 +240,7 @@ Custom_Command UE (1) connected to DU-PCI 1? localhost - echo ci fetch_du_by_ue_id 1 | ncat 192.168.71.150 9090 | grep "3585" + echo ci fetch_du_by_ue_id | ncat 192.168.71.150 9090 | grep "3585" @@ -271,7 +271,7 @@ Custom_Command UE (1) connected to DU-PCI 0? localhost - echo ci fetch_du_by_ue_id 1 | ncat 192.168.71.150 9090 | grep "3584" + echo ci fetch_du_by_ue_id | ncat 192.168.71.150 9090 | grep "3584" @@ -302,7 +302,7 @@ Custom_Command UE (1) connected to DU-PCI 1? localhost - echo ci fetch_du_by_ue_id 1 | ncat 192.168.71.150 9090 | grep "3585" + echo ci fetch_du_by_ue_id | ncat 192.168.71.150 9090 | grep "3585" diff --git a/ci-scripts/xml_files/container_5g_rfsim_n2_ho.xml b/ci-scripts/xml_files/container_5g_rfsim_n2_ho.xml index 6a4d43f93a..9ef14478b1 100644 --- a/ci-scripts/xml_files/container_5g_rfsim_n2_ho.xml +++ b/ci-scripts/xml_files/container_5g_rfsim_n2_ho.xml @@ -120,7 +120,7 @@ Custom_Command UE (1) connected to DU-PCI 1? localhost - echo ci fetch_du_by_ue_id 1 | ncat 192.168.71.180 9090 | grep "1234" + echo ci fetch_du_by_ue_id | ncat 192.168.71.180 9090 | grep "1234" @@ -181,7 +181,7 @@ Custom_Command UE (1) connected to DU-PCI 0? localhost - echo ci fetch_du_by_ue_id 1 | ncat 192.168.71.150 9090 | grep "3584" + echo ci fetch_du_by_ue_id | ncat 192.168.71.150 9090 | grep "3584" @@ -218,7 +218,7 @@ Custom_Command UE (1) connected to DU-PCI 1? localhost - echo ci fetch_du_by_ue_id 1 | ncat 192.168.71.180 9090 | grep "1234" + echo ci fetch_du_by_ue_id | ncat 192.168.71.180 9090 | grep "1234" diff --git a/ci-scripts/xml_files/container_sa_f1_ho_b210_quectel.xml b/ci-scripts/xml_files/container_sa_f1_ho_b210_quectel.xml index ff36947a57..53f7e006d9 100644 --- a/ci-scripts/xml_files/container_sa_f1_ho_b210_quectel.xml +++ b/ci-scripts/xml_files/container_sa_f1_ho_b210_quectel.xml @@ -59,9 +59,9 @@ Custom_Command - Set attenuator to 0 for DU0 and 17 for DU1 + Set attenuator to 0 for DU0 and 22 for DU1 rocket - python3 %%workspace%%/ci-scripts/attenuatorctl.py -s 1,2 0 -s 3,4 25 + python3 %%workspace%%/ci-scripts/attenuatorctl.py -s 1,2 0 -s 3,4 22 @@ -76,8 +76,8 @@ Deploy_Object Deploy gNB-DU0 (TDD/Band77/20MHz/B210) in a container ci-scripts/yaml_files/5g_sa_f1_b210_ho - oai-du0 groot + oai-du0 @@ -94,26 +94,11 @@ raspix - - Ping - Ping: 100pings in 20sec - raspix - oc-cn5g-00103-ho - -c 100 -i 0.2 - 5 - - - - IdleSleep - Sleep - 5 - - Custom_Command - Attenuate DU0 by 15 and remove the attenuation on DU1 to trigger Handover - rocket - python3 %%workspace%%/ci-scripts/attenuatorctl.py -r 1,2 15 -r 3,4 0 -d 5 + UE connected to DU0? + groot + echo ci fetch_du_by_ue_id | ncat 127.0.0.1 9090 | grep "3584" @@ -122,21 +107,58 @@ raspix oc-cn5g-00103-ho -c 50 -i 0.2 - 5 + 30 + + + + Custom_Command + Set attenuation 5 on DU 0 and remove attenuation on DU 1 to test Reestablishment on a different DU (DU 1) + rocket + python3 %%workspace%%/ci-scripts/attenuatorctl.py -r 1,2 5 -r 3,4 0 -d 5 + + + + Custom_Command + Trigger Reestablishment (on DU 0) + groot + echo ci force_reestab | ncat 127.0.0.1 9091 | grep -E 'Reset RLC counters of UE RNTI [0-9a-f]{4} to trigger reestablishment' + + + + Ping + Ping: 50pings in 10sec + raspix + oc-cn5g-00103-ho + -c 50 -i 0.2 + 30 Custom_Command UE connected to DU1? groot - echo ci fetch_du_by_ue_id 1 | ncat 127.0.0.1 9090 | grep "3585" + echo ci fetch_du_by_ue_id | ncat 127.0.0.1 9090 | grep "3585" Custom_Command - Attenuate DU1 by 17dB and remove attenuation on DU0 to trigger Handover + Verify Reestablishment (on CU) + groot + echo ci get_reestab_count | ncat 127.0.0.1 9090 | grep -E 'UE RNTI [0-9a-f]{4} reestab 1' + + + + Custom_Command + Remove attenuation on DU 0 and set attenuation on DU 1 to 5 to test Reestablishment on a different DU (DU 0) rocket - python3 %%workspace%%/ci-scripts/attenuatorctl.py -r 1,2 0 -r 3,4 25 -d 5 + python3 %%workspace%%/ci-scripts/attenuatorctl.py -r 1,2 0 -r 3,4 5 -d 5 + + + + Custom_Command + Trigger Reestablishment (on DU 1) + rocket + echo ci force_reestab | ncat 127.0.0.1 9091 | grep -E 'Reset RLC counters of UE RNTI [0-9a-f]{4} to trigger reestablishment' @@ -145,21 +167,28 @@ raspix oc-cn5g-00103-ho -c 50 -i 0.2 - 5 + 30 Custom_Command UE connected to DU0? groot - echo ci fetch_du_by_ue_id 1 | ncat 127.0.0.1 9090 | grep "3584" + echo ci fetch_du_by_ue_id | ncat 127.0.0.1 9090 | grep "3584" Custom_Command - Attenuate DU0 by 15 and remove the attenuation on DU1 to trigger Handover + Verify Reestablishment (on CU) + groot + echo ci get_reestab_count | ncat 127.0.0.1 9090 | grep -E 'UE RNTI [0-9a-f]{4} reestab 2' + + + + Custom_Command + Attenuate DU0 by 22 and remove the attenuation on DU1 to trigger Handover rocket - python3 %%workspace%%/ci-scripts/attenuatorctl.py -r 1,2 15 -r 3,4 0 -d 5 + python3 %%workspace%%/ci-scripts/attenuatorctl.py -r 1,2 22 -r 3,4 0 -d 5 @@ -168,14 +197,60 @@ raspix oc-cn5g-00103-ho -c 50 -i 0.2 - 5 + 30 Custom_Command UE connected to DU1? groot - echo ci fetch_du_by_ue_id 1 | ncat 127.0.0.1 9090 | grep "3585" + echo ci fetch_du_by_ue_id | ncat 127.0.0.1 9090 | grep "3585" + + + + Custom_Command + Attenuate DU1 by 22dB and remove attenuation on DU0 to trigger Handover + rocket + python3 %%workspace%%/ci-scripts/attenuatorctl.py -r 1,2 0 -r 3,4 22 -d 5 + + + + Ping + Ping: 50pings in 10sec + raspix + oc-cn5g-00103-ho + -c 50 -i 0.2 + 30 + + + + Custom_Command + UE connected to DU0? + groot + echo ci fetch_du_by_ue_id | ncat 127.0.0.1 9090 | grep "3584" + + + + Custom_Command + Attenuate DU0 by 22 and remove the attenuation on DU1 to trigger Handover + rocket + python3 %%workspace%%/ci-scripts/attenuatorctl.py -r 1,2 22 -r 3,4 0 -d 5 + + + + Ping + Ping: 50pings in 10sec + raspix + oc-cn5g-00103-ho + -c 50 -i 0.2 + 30 + + + + Custom_Command + UE connected to DU1? + groot + echo ci fetch_du_by_ue_id | ncat 127.0.0.1 9090 | grep "3585" diff --git a/ci-scripts/xml_files/container_sa_n2_ho_b210_quectel.xml b/ci-scripts/xml_files/container_sa_n2_ho_b210_quectel.xml index 84c5a5bea5..06fff38745 100644 --- a/ci-scripts/xml_files/container_sa_n2_ho_b210_quectel.xml +++ b/ci-scripts/xml_files/container_sa_n2_ho_b210_quectel.xml @@ -55,7 +55,7 @@ Custom_Command Set initial attenuation on target gNB rocket - python3 %%workspace%%/ci-scripts/attenuatorctl.py -s 1,2 0 -s 3,4 25 + python3 %%workspace%%/ci-scripts/attenuatorctl.py -s 1,2 0 -s 3,4 22 @@ -103,9 +103,9 @@ Custom_Command - Gradually attenuate gNB-PCI0 (ch1-2=15dB) and gNB-PCI1 (ch3-4=0dB) over 5s to trigger N2 Handover to gNB-PCI1 + Gradually attenuate gNB-PCI0 (ch1-2=22dB) and gNB-PCI1 (ch3-4=0dB) over 5s to trigger N2 Handover to gNB-PCI1 rocket - python3 %%workspace%%/ci-scripts/attenuatorctl.py -r 1,2 15 -r 3,4 0 -d 5 + python3 %%workspace%%/ci-scripts/attenuatorctl.py -r 1,2 22 -r 3,4 0 -d 5 @@ -121,14 +121,14 @@ Custom_Command UE connected to gNB-PCI1? rocket - echo ci fetch_du_by_ue_id 1 | ncat 127.0.0.1 9090 | grep "3585" + echo ci fetch_du_by_ue_id | ncat 127.0.0.1 9090 | grep "3585" Custom_Command - Gradually attenuate gNB-PCI0 (ch1-2=0dB) and gNB-PCI1 (ch3-4=15dB) over 5s to trigger N2 Handover back to gNB-PCI0 + Gradually attenuate gNB-PCI0 (ch1-2=0dB) and gNB-PCI1 (ch3-4=22dB) over 5s to trigger N2 Handover back to gNB-PCI0 rocket - python3 %%workspace%%/ci-scripts/attenuatorctl.py -r 1,2 0 -r 3,4 25 -d 5 + python3 %%workspace%%/ci-scripts/attenuatorctl.py -r 1,2 0 -r 3,4 22 -d 5 @@ -144,7 +144,7 @@ Custom_Command UE connected to gNB-PCI0? groot - echo ci fetch_du_by_ue_id 1 | ncat 127.0.0.1 9090 | grep "3584" + echo ci fetch_du_by_ue_id | ncat 127.0.0.1 9090 | grep "3584" diff --git a/common/utils/telnetsrv/telnetsrv_ci.c b/common/utils/telnetsrv/telnetsrv_ci.c index 10f9626ef0..a2bcccd150 100644 --- a/common/utils/telnetsrv/telnetsrv_ci.c +++ b/common/utils/telnetsrv/telnetsrv_ci.c @@ -154,10 +154,17 @@ int fetch_du_by_ue_id(char *buf, int debug, telnet_printfunc_t prnt) if (!RC.nrrrc) ERROR_MSG_RET("no RRC present, cannot list counts\n"); - ue_id_t ue_id = 1; + ue_id_t ue_id; if (buf) { ue_id = strtol(buf, NULL, 10); + } else { + // No UE ID provided: find the connected UE first + rrc_gNB_ue_context_t *ue = get_single_rrc_ue(); + if (!ue) + ERROR_MSG_RET("no single UE in RRC present\n"); + ue_id = ue->ue_context.rrc_ue_id; } + nr_rrc_du_container_t *du = get_du_for_ue(RC.nrrrc[0], ue_id); if (du) { diff --git a/doc/RRC/rrc-dev.md b/doc/RRC/rrc-dev.md index 534d55be37..f5c4956c9d 100644 --- a/doc/RRC/rrc-dev.md +++ b/doc/RRC/rrc-dev.md @@ -175,6 +175,12 @@ with the reestablishment procedure; if any check fails, the CU-CP tries to release the old UE at the AMF, and continues with the "normal" connection setup (registration/service request) described above. +The re-establishment flow implements transparent forwarding of CellGroupConfig +per TS 38.473: the CU requests CellGroupConfig from the DU via UE Context +Modification Request with `gNB_DU_Configuration_Query=true`, and the DU responds +with an encoded CellGroupConfig that the CU forwards to the UE without +decode/re-encode cycles. + ```mermaid sequenceDiagram participant ue as UE @@ -185,24 +191,45 @@ sequenceDiagram ue->>du: Msg1/Preamble du->>ue: Msg2/RAR - ue->>du: Msg3 w/ RRC Reestabishment Request (old C-RNTI/PCI) + ue->>du: Msg3 w/ RRC Reestablishment Request (old C-RNTI/PCI) Note over du: DU creates UE context for new RNTI du->>cucp: F1AP Initial UL RRC Msg Tr (RRC Reestab Req, in Msg3) Note over cucp: rrc_handle_RRCReestablishmentRequest() - alt UE known and at the original DU - Note over cucp: rrc_gNB_generate_RRCReestablishment()
SRB1 PDCP reestablishment - cucp->>du: F1AP DL RRC Msg Tr (old gNB-DU-UE ID) - Note over du: Reuse configuration of and delete old UE
(in dl_rrc_message_transfer()) + alt UE known + opt PCI mismatch AND Association ID mismatch + Note over cucp: Detect different DU (assoc_id != du_assoc_id)
Update du_assoc_id, set f1_ue_context_active=false + end + Note over cucp: rrc_gNB_generate_RRCReestablishment()
SRB1 PDCP reestablishment + cucp->>du: F1AP DL RRC Msg Tr (gNB-DU-UE ID) + Note over du: dl_rrc_message_transfer() + alt Old UE found + Note over du: Reuse configuration of and delete old UE + else Old UE not found + Note over du: do nothing + end + Note over du: Set reestablishRLC=true + Note over du: - Store CellGroup in reconfigCellGroup
- Remove spCellConfig from CellGroup du->>ue: RRC Reestablishment ue->>du: RRC Reestablishment Complete du->>cucp: F1AP UL RRC Msg Tr (RRC Reestab Complete) Note over cucp: handle_rrcReestablishmentComplete() - Note over cucp: cuup_notify_reestablishment() - cucp->>cuup: E1AP Bearer Context Modification Req - cucp->>ue: F1AP DL RRC Msg Transfer (RRC Reconfiguration) - cuup->>cucp: E1AP Bearer Context Modification Resp + Note over cucp: rrc_gNB_process_RRCReestablishmentComplete()
Re-establish SRB2 PDCP + Note over cucp: cuup_notify_reestablishment()
Notify CU-UP for DRB re-establishment + cucp->>cuup: E1AP Bearer Context Modification Req + cuup->>cucp: E1AP Bearer Context Modification Resp (asynchronous) + alt on the original DU + Note over cucp: Send UE Context Modification Request
with gNB_DU_Configuration_Query=true + cucp->>du: F1AP UE Context Modification Req + du->>cucp: F1AP UE Context Modification Resp
(CellGroupConfig with spCellConfig and reestablishRLC flags) + else on a different DU + Note over cucp: Detect different DU (!f1_ue_context_active)
Trigger UE Context Setup on new DU + cucp->>du: F1AP UE Context Setup Request
+ du->>cucp: F1AP UE Context Setup Response
(CellGroupConfig with spCellConfig and reestablishRLC flags) + end + Note over cucp: rrc_CU_process_ue_context_setup/modification_response()
Detect re-establishment via rrc_detect_reestablishment()
rrc_gNB_generate_dedicatedRRCReconfiguration()
(with is_reestablishment=true) + cucp->>ue: F1AP DL RRC Msg Transfer (RRC Reconfiguration
with transparent CellGroupConfig) ue->>cucp: F1AP UL RRC Msg Transfer (RRC Reconfiguration Complete) - Note over cucp: handle_rrcReestablishmentComplete() + Note over cucp: handle_rrcReconfigurationComplete() else Fallback* cucp->>amf: NGAP UE Context Release Request Note over cucp: rrc_gNB_generate_RRCSetup() @@ -210,6 +237,14 @@ sequenceDiagram end ``` +Note on re-establishment on different DU: The "UE known and on a different DU" +case is shown in the sequence diagram above. When a UE re-establishes on a +different DU than the original one (detected by comparing `du_assoc_id` at +RRCReestablishmentRequest), the CU updates `du_assoc_id` and sets +`f1_ue_context_active=false`. At RRCReestablishmentComplete, if +`f1_ue_context_active` is false, the CU triggers UE Context Setup on the new +DU per TS 38.401 §8.7. + ### Inter-DU Handover (F1) The basic handover (HO) structure is as follows. In order to support various diff --git a/openair2/COMMON/f1ap_messages_types.h b/openair2/COMMON/f1ap_messages_types.h index 15bb1ba4a1..0cd090ebc7 100644 --- a/openair2/COMMON/f1ap_messages_types.h +++ b/openair2/COMMON/f1ap_messages_types.h @@ -550,6 +550,9 @@ typedef struct f1ap_ue_context_mod_req_t { byte_array_t *rrc_container; + // gNB-DU Configuration Query + bool *gNB_DU_Configuration_Query; + int srbs_len; f1ap_srb_to_setup_t *srbs; // reuse the same type as for setup; there is a // list for SRB modification diff --git a/openair2/F1AP/lib/f1ap_ue_context.c b/openair2/F1AP/lib/f1ap_ue_context.c index 829cdacdfb..728849d8e5 100644 --- a/openair2/F1AP/lib/f1ap_ue_context.c +++ b/openair2/F1AP/lib/f1ap_ue_context.c @@ -1666,6 +1666,15 @@ F1AP_F1AP_PDU_t *encode_ue_context_mod_req(const f1ap_ue_context_mod_req_t *req) OCTET_STRING_fromBuf(&ie81->value.choice.RRCContainer, (const char *)req->rrc_container->buf, req->rrc_container->len); } + /* optional: gNB-DU Configuration Query */ + if (req->gNB_DU_Configuration_Query && *req->gNB_DU_Configuration_Query) { + asn1cSequenceAdd(out->protocolIEs.list, F1AP_UEContextModificationRequestIEs_t, ie82); + ie82->id = F1AP_ProtocolIE_ID_id_GNB_DUConfigurationQuery; + ie82->criticality = F1AP_Criticality_ignore; + ie82->value.present = F1AP_UEContextModificationRequestIEs__value_PR_GNB_DUConfigurationQuery; + ie82->value.choice.GNB_DUConfigurationQuery = F1AP_GNB_DUConfigurationQuery_true; + } + /* optional: SRBs_ToBeSetupMod */ if (req->srbs_len > 0) { DevAssert(req->srbs); @@ -1777,6 +1786,13 @@ bool decode_ue_context_mod_req(const F1AP_F1AP_PDU_t *pdu, f1ap_ue_context_mod_r *out->rrc_container = create_byte_array(os->size, os->buf); } break; + case F1AP_ProtocolIE_ID_id_GNB_DUConfigurationQuery: + _F1_EQ_CHECK_INT(ie->value.present, F1AP_UEContextModificationRequestIEs__value_PR_GNB_DUConfigurationQuery); + { + bool v = ie->value.choice.GNB_DUConfigurationQuery == F1AP_GNB_DUConfigurationQuery_true; + _F1_MALLOC(out->gNB_DU_Configuration_Query, v); + } + break; case F1AP_ProtocolIE_ID_id_SRBs_ToBeSetupMod_List: _F1_EQ_CHECK_INT(ie->value.present, F1AP_UEContextModificationRequestIEs__value_PR_SRBs_ToBeSetupMod_List); _F1_CHECK_EXP(decode_srbs_to_setupmod(&ie->value.choice.SRBs_ToBeSetupMod_List, &out->srbs_len, &out->srbs)); @@ -1862,6 +1878,8 @@ f1ap_ue_context_mod_req_t cp_ue_context_mod_req(const f1ap_ue_context_mod_req_t } if (orig->status) _F1_MALLOC(cp.status, *orig->status); + if (orig->gNB_DU_Configuration_Query) + _F1_MALLOC(cp.gNB_DU_Configuration_Query, *orig->gNB_DU_Configuration_Query); return cp; } @@ -1898,6 +1916,7 @@ bool eq_ue_context_mod_req(const f1ap_ue_context_mod_req_t *a, const f1ap_ue_con _F1_CHECK_EXP(eq_drb_to_release(&a->drbs_rel[i], &b->drbs_rel[i])); _F1_EQ_CHECK_OPTIONAL_IE(a, b, status, _F1_EQ_CHECK_INT); + _F1_EQ_CHECK_OPTIONAL_IE(a, b, gNB_DU_Configuration_Query, _F1_EQ_CHECK_INT); return true; } @@ -1925,6 +1944,7 @@ void free_ue_context_mod_req(f1ap_ue_context_mod_req_t *req) free_drb_to_release(&req->drbs_rel[i]); free(req->drbs_rel); free(req->status); + free(req->gNB_DU_Configuration_Query); } /** diff --git a/openair2/F1AP/tests/f1ap_lib_test.c b/openair2/F1AP/tests/f1ap_lib_test.c index 75fa75bcc1..cba787f4dc 100644 --- a/openair2/F1AP/tests/f1ap_lib_test.c +++ b/openair2/F1AP/tests/f1ap_lib_test.c @@ -1039,6 +1039,7 @@ static void test_f1ap_ue_context_modification_request() orig.drbs_rel[2].id = 19; _F1_MALLOC(orig.status, LOWER_LAYERS_RESUME); + _F1_MALLOC(orig.gNB_DU_Configuration_Query, true); F1AP_F1AP_PDU_t *f1enc = encode_ue_context_mod_req(&orig); F1AP_F1AP_PDU_t *f1dec = f1ap_encode_decode(f1enc); diff --git a/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c b/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c index c271824e75..7229b98fd0 100644 --- a/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c +++ b/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c @@ -607,6 +607,86 @@ static NR_UE_info_t *create_new_UE(gNB_MAC_INST *mac, uint32_t cu_id, const NR_C return UE; } +/** @brief Encode CellGroupConfig to byte array for transparent forwarding in F1AP messages. + * The encoded bytes are intended for transparent forwarding to the UE without + * decode/re-encode cycles per TS 38.473 transparency requirements. + * @param cellGroup CellGroupConfig to encode + * @return Encoded byte array */ +static byte_array_t encode_cellgroup_config(const NR_CellGroupConfig_t *cellGroup) +{ + byte_array_t cgc = {0}; + ssize_t encoded = uper_encode_to_new_buffer(&asn_DEF_NR_CellGroupConfig, NULL, cellGroup, (void **)&cgc.buf); + AssertFatal(encoded > 0, "Could not encode CellGroup\n"); + cgc.len = encoded; + return cgc; +} + +/** @brief Handle RRC container from UE Context Setup/Modification request. + * Per 3GPP TS 38.473, the RRCContainer IE contains an RRC message (e.g., DL-DCCH-Message + * as defined in TS 38.331) that is encapsulated in a PDCP PDU. The DU forwards this + * container transparently to the UE via SRB1 without decode/re-encode cycles. + * @param rrc_container RRC container + * @param rnti RNTI of the UE */ +static void handle_ue_context_rrc_container(const byte_array_t *rrc_container, const rnti_t rnti) +{ + DevAssert(rrc_container); + logical_chan_id_t id = 1; + 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()); @@ -653,7 +733,8 @@ void ue_context_setup_request(const f1ap_ue_context_setup_req_t *req) AssertFatal(UE, "no UE found or could not be created, but UE Context Setup Failed not implemented\n"); resp.gNB_DU_ue_id = UE->rnti; - NR_CellGroupConfig_t *new_CellGroup = clone_CellGroupConfig(UE->CellGroup); + NR_CellGroupConfig_t *new_CellGroup = get_cellgroup_config(UE); + // Needed for DRB Setup (e.g., RLC might reduce SN size) UE->capability = ue_cap; @@ -667,8 +748,7 @@ void ue_context_setup_request(const f1ap_ue_context_setup_req_t *req) } if (req->rrc_container != NULL) { - logical_chan_id_t id = 1; - nr_rlc_srb_recv_sdu(UE->rnti, id, req->rrc_container->buf, req->rrc_container->len); + handle_ue_context_rrc_container(req->rrc_container, UE->rnti); } NR_ServingCellConfigCommon_t *scc = mac->common_channels[0].ServingCellConfigCommon; @@ -678,6 +758,15 @@ void ue_context_setup_request(const f1ap_ue_context_setup_req_t *req) update_cellGroupConfig(new_CellGroup, UE->uid, UE->capability, &mac->radio_config, scc); } + /* During re-establishment, prepare CellGroupConfig for UE Context Setup response. + * Per TS 38.401 §8.7: when a UE re-establishes on a different DU, the CU triggers + * UE Context Setup on the new DU. The DU must respond with a CellGroupConfig that has + * reestablishRLC flags set for all RLC bearers except SRB1. This prepares the CellGroupConfig + * for transparent forwarding to the UE per TS 38.473 transparency requirements. */ + if (UE->reestablish_rlc) { + update_cellgroup_for_reestablishment(UE, new_CellGroup); + } + if (!ue_id_provided && cg_configinfo == NULL) { /* new UE: tell the UE to reestablish RLC */ struct NR_CellGroupConfig__rlc_BearerToAddModList *addmod = new_CellGroup->rlc_BearerToAddModList; @@ -688,12 +777,7 @@ void ue_context_setup_request(const f1ap_ue_context_setup_req_t *req) } } - byte_array_t cgc = { .buf = calloc_or_fail(1,1024) }; - asn_enc_rval_t enc_rval = - uper_encode_to_buffer(&asn_DEF_NR_CellGroupConfig, NULL, new_CellGroup, cgc.buf, 1024); - AssertFatal(enc_rval.encoded > 0, "Could not encode CellGroup, failed element %s\n", enc_rval.failed_type->name); - cgc.len = (enc_rval.encoded + 7) >> 3; - resp.du_to_cu_rrc_info.cell_group_config = cgc; + resp.du_to_cu_rrc_info.cell_group_config = encode_cellgroup_config(new_CellGroup); ASN_STRUCT_FREE(asn_DEF_NR_CellGroupConfig, UE->reconfigCellGroup); UE->reconfigCellGroup = new_CellGroup; @@ -745,7 +829,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); @@ -760,8 +844,7 @@ void ue_context_modification_request(const f1ap_ue_context_mod_req_t *req) } if (req->rrc_container != NULL) { - logical_chan_id_t id = 1; - nr_rlc_srb_recv_sdu(req->gNB_DU_ue_id, id, req->rrc_container->buf, req->rrc_container->len); + handle_ue_context_rrc_container(req->rrc_container, req->gNB_DU_ue_id); } NR_ServingCellConfigCommon_t *scc = mac->common_channels[0].ServingCellConfigCommon; @@ -771,6 +854,10 @@ void ue_context_modification_request(const f1ap_ue_context_mod_req_t *req) } else if (req->reconfig_compl) { LOG_I(NR_MAC, "DU received confirmation of successful RRC Reconfiguration\n"); if (UE->reconfigCellGroup) { + /** During handover, target DU never sends RRC Reconfiguration (source DU does), + * so ack_reconfig is never called on target DU - this warning is expected. + * If RRC Reconfiguration was sent via PDSCH, ack_reconfig should have been called when UE ACKed it. + * If not, this warning would indicate a bug. */ LOG_W(NR_MAC, "reconfigCellGroup still present, did we miss ACK for RRCReconfiguration?\n"); ASN_STRUCT_FREE(asn_DEF_NR_CellGroupConfig, UE->CellGroup); UE->CellGroup = UE->reconfigCellGroup; @@ -780,6 +867,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; @@ -797,18 +885,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); } - if (req->srbs_len > 0 || req->drbs_len > 0 || req->drbs_rel_len > 0 || ue_cap != NULL) { - resp.du_to_cu_rrc_info = calloc_or_fail(1, sizeof(du_to_cu_rrc_information_t)); - byte_array_t cgc = { .buf = calloc_or_fail(1, 1024) }; - asn_enc_rval_t enc_rval = uper_encode_to_buffer(&asn_DEF_NR_CellGroupConfig, - NULL, - new_CellGroup, - cgc.buf, - 1024); - AssertFatal(enc_rval.encoded > 0, "Could not encode CellGroup, failed element %s\n", enc_rval.failed_type->name); - cgc.len = (enc_rval.encoded + 7) >> 3; - resp.du_to_cu_rrc_info->cell_group_config = cgc; + /* 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); @@ -915,6 +1006,11 @@ void ue_context_release_command(const f1ap_ue_context_rel_cmd_t *cmd) NR_SCHED_UNLOCK(&mac->sched_lock); } +/** @brief Process a DL RRC MESSAGE TRANSFER. Handles delivery of an RRC message to a UE + * via the F1AP DL RRC MESSAGE TRANSFER procedure, as specified in TS 38.473. This procedure + * is also responsible for re-establishing UE context when required (e.g., during RRC connection + * reestablishment). + * @param dl_rrc Pointer to the DL RRC MESSAGE TRANSFER data structure. */ void dl_rrc_message_transfer(const f1ap_dl_rrc_message_t *dl_rrc) { LOG_D(NR_MAC, @@ -942,50 +1038,71 @@ void dl_rrc_message_transfer(const f1ap_dl_rrc_message_t *dl_rrc) DevAssert(success); } - - /* if we get the old-gNB-DU-UE-ID, this means there is a reestablishment - * ongoing. */ + /* Per TS 38.473: "The DL RRC MESSAGE TRANSFER message shall include, if available, + * the old gNB-DU UE F1AP ID IE so that the gNB-DU can retrieve the existing UE context + * in RRC connection reestablishment procedure, as defined in TS 38.401" + * + * "If the gNB-DU identifies the UE-associated logical F1-connection by the gNB-DU UE F1AP ID + * IE in the DL RRC MESSAGE TRANSFER message and the old gNB-DU UE F1AP ID IE is included, + * it shall release the old gNB-DU UE F1AP ID and the related configurations associated + * with the old gNB-DU UE F1AP ID." */ if (dl_rrc->old_gNB_DU_ue_id != NULL) { AssertFatal(*dl_rrc->old_gNB_DU_ue_id != dl_rrc->gNB_DU_ue_id, "logic bug: current and old gNB DU UE ID cannot be the same\n"); - /* 38.401 says: "Find UE context based on old gNB-DU UE F1AP ID, replace - * old C-RNTI/PCI with new C-RNTI/PCI". Below, we do the inverse: we keep - * the new UE context (with new C-RNTI), but set up everything to reuse the - * old config. */ NR_UE_info_t *oldUE = find_nr_UE(&mac->UE_info, *dl_rrc->old_gNB_DU_ue_id); - AssertFatal(oldUE, "CU claims we should know UE %04x, but we don't\n", *dl_rrc->old_gNB_DU_ue_id); - pthread_mutex_lock(&mac->sched_lock); - uid_t temp_uid = UE->uid; - UE->uid = oldUE->uid; - oldUE->uid = temp_uid; - for (int i = 1; i < seq_arr_size(&oldUE->UE_sched_ctrl.lc_config); ++i) { - const nr_lc_config_t *c = seq_arr_at(&oldUE->UE_sched_ctrl.lc_config, i); - nr_lc_config_t new = *c; - new.suspended = true; - nr_mac_add_lcid(&UE->UE_sched_ctrl, &new); + if (oldUE == NULL) { + /* No matching UE-associated logical F1-connection for the old gNB-DU UE F1AP ID. + * Per TS 38.473, if there's no matching connection, there's nothing to release. */ + LOG_I(NR_MAC, + "DL RRC Message Transfer: old gNB-DU UE F1AP ID %04x has no matching UE-associated logical F1-connection, nothing to " + "release\n", + *dl_rrc->old_gNB_DU_ue_id); + /* Clean up any F1 UE data associated with the old gNB-DU UE F1AP ID */ + if (du_exists_f1_ue_data(*dl_rrc->old_gNB_DU_ue_id)) { + du_remove_f1_ue_data(*dl_rrc->old_gNB_DU_ue_id); + } + } else { + /* Per TS 38.401: "Find UE context based on old gNB-DU UE F1AP ID, replace + * old C-RNTI/PCI with new C-RNTI/PCI". Below, we do the inverse: we keep + * the new UE context (with new C-RNTI), but set up everything to reuse the + * old config. */ + pthread_mutex_lock(&mac->sched_lock); + uid_t temp_uid = UE->uid; + UE->uid = oldUE->uid; + oldUE->uid = temp_uid; + for (int i = 1; i < seq_arr_size(&oldUE->UE_sched_ctrl.lc_config); ++i) { + const nr_lc_config_t *c = seq_arr_at(&oldUE->UE_sched_ctrl.lc_config, i); + nr_lc_config_t new = *c; + new.suspended = true; + nr_mac_add_lcid(&UE->UE_sched_ctrl, &new); + } + ASN_STRUCT_FREE(asn_DEF_NR_CellGroupConfig, UE->CellGroup); + UE->CellGroup = oldUE->CellGroup; + oldUE->CellGroup = NULL; + ASN_STRUCT_FREE(asn_DEF_NR_UE_NR_Capability, UE->capability); + UE->capability = oldUE->capability; + oldUE->capability = NULL; + UE->mac_stats = oldUE->mac_stats; + UE->measgap_config = oldUE->measgap_config; + UE->local_bwp_id = oldUE->local_bwp_id; + mac_remove_nr_ue(mac, *dl_rrc->old_gNB_DU_ue_id); + pthread_mutex_unlock(&mac->sched_lock); + nr_rlc_remove_ue(dl_rrc->gNB_DU_ue_id); + nr_rlc_update_id(*dl_rrc->old_gNB_DU_ue_id, dl_rrc->gNB_DU_ue_id); + instance_t f1inst = get_f1_gtp_instance(); + if (f1inst >= 0) // we actually use F1-U + gtpv1u_update_ue_id(f1inst, *dl_rrc->old_gNB_DU_ue_id, dl_rrc->gNB_DU_ue_id); } - ASN_STRUCT_FREE(asn_DEF_NR_CellGroupConfig, UE->CellGroup); - UE->CellGroup = oldUE->CellGroup; - oldUE->CellGroup = NULL; - ASN_STRUCT_FREE(asn_DEF_NR_UE_NR_Capability, UE->capability); - UE->capability = oldUE->capability; - oldUE->capability = NULL; - UE->mac_stats = oldUE->mac_stats; - UE->measgap_config = oldUE->measgap_config; - UE->local_bwp_id = oldUE->local_bwp_id; - /* 38.331 5.3.7.2 says that the UE releases the spCellConfig, so we drop it + /* Per TS 38.331 5.3.7.2: the UE releases the spCellConfig, so we drop it * from the current configuration. It will be reapplied when the * reconfiguration has succeeded (indicated by the CU) */ asn_copy(&asn_DEF_NR_CellGroupConfig, (void **)&UE->reconfigCellGroup, UE->CellGroup); ASN_STRUCT_FREE(asn_DEF_NR_SpCellConfig, UE->CellGroup->spCellConfig); UE->CellGroup->spCellConfig = NULL; UE->reestablish_rlc = true; - mac_remove_nr_ue(mac, *dl_rrc->old_gNB_DU_ue_id); - pthread_mutex_unlock(&mac->sched_lock); - nr_rlc_remove_ue(dl_rrc->gNB_DU_ue_id); - nr_rlc_update_id(*dl_rrc->old_gNB_DU_ue_id, dl_rrc->gNB_DU_ue_id); - /* 38.331 clause 5.3.7.4: apply gNB RLC configuration for SRB1 to match the UE RLC configuration defined in 9.2.1 */ - nr_rlc_configuration_t rlc_configuration = mac->rlc_config; // use configuration file values for timers t_poll_retransmit, t_reassembly and t_status_prohibit + /* Per TS 38.331 clause 5.3.7.4: apply gNB RLC configuration for SRB1 to match the UE RLC configuration defined in 9.2.1. + * Use configuration file values for timers t_poll_retransmit, t_reassembly and t_status_prohibit */ + nr_rlc_configuration_t rlc_configuration = mac->rlc_config; rlc_configuration.srb.poll_pdu = -1; rlc_configuration.srb.poll_byte = -1; rlc_configuration.srb.max_retx_threshold = 8; @@ -993,9 +1110,6 @@ void dl_rrc_message_transfer(const f1ap_dl_rrc_message_t *dl_rrc) NR_RLC_Config_t *rlc_Config = nr_srb_config(&rlc_configuration); nr_rlc_reconfigure_entity(dl_rrc->gNB_DU_ue_id, 1, rlc_Config); ASN_STRUCT_FREE(asn_DEF_NR_RLC_Config, rlc_Config); - instance_t f1inst = get_f1_gtp_instance(); - if (f1inst >= 0) // we actually use F1-U - gtpv1u_update_ue_id(f1inst, *dl_rrc->old_gNB_DU_ue_id, dl_rrc->gNB_DU_ue_id); } /* the DU ue id is the RNTI */ diff --git a/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.c b/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.c index 88f9fec043..e588c2665b 100644 --- a/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.c +++ b/openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.c @@ -506,6 +506,7 @@ srb_found: * RRC, the RLC-PDCP interface does not transport this information */ f1_ue_data_t ue_data = cu_get_f1_ue_data(ue->ue_id); ul_rrc->gNB_DU_ue_id = ue_data.secondary_ue; + message_p->ittiMsgHeader.originInstance = ue_data.du_assoc_id; ul_rrc->srb_id = srb_id; ul_rrc->rrc_container = malloc(size); AssertFatal(ul_rrc->rrc_container != NULL, "OUT OF MEMORY\n"); diff --git a/openair2/RRC/NR/MESSAGES/asn1_msg.c b/openair2/RRC/NR/MESSAGES/asn1_msg.c index e97dc1a000..2c3cf2717a 100644 --- a/openair2/RRC/NR/MESSAGES/asn1_msg.c +++ b/openair2/RRC/NR/MESSAGES/asn1_msg.c @@ -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,6 +606,49 @@ static byte_array_t do_HO_RRCReconfiguration(nr_rrc_reconfig_param_t *params) return msg; } +void dump_cgc(const uint8_t *buf, size_t len) +{ + // Decode the encoded CellGroupConfig for debugging + NR_CellGroupConfig_t *temp_cellGroupConfig = NULL; + asn_dec_rval_t dec_rval = uper_decode_complete(NULL, &asn_DEF_NR_CellGroupConfig, (void **)&temp_cellGroupConfig, buf, len); + if (dec_rval.code == RC_OK && dec_rval.consumed > 0) { + xer_fprint(stdout, &asn_DEF_NR_CellGroupConfig, temp_cellGroupConfig); + ASN_STRUCT_FREE(asn_DEF_NR_CellGroupConfig, temp_cellGroupConfig); + } else { + LOG_W(NR_RRC, "Failed to decode CellGroupConfig (code=%d consumed=%zu)\n", dec_rval.code, dec_rval.consumed); + } +} + +static void dump_mcg(const NR_DL_DCCH_Message_t *dl_dcch_msg) +{ + if (!dl_dcch_msg) { + LOG_W(NR_RRC, "DL_DCCH_Message is NULL\n"); + return; + } + + const NR_RRCReconfiguration_IEs_t *reconf_ies = NULL; + const struct NR_DL_DCCH_MessageType__c1 *c1 = NULL; + + if (dl_dcch_msg->message.present == NR_DL_DCCH_MessageType_PR_c1 + && (c1 = dl_dcch_msg->message.choice.c1) + && c1->present == NR_DL_DCCH_MessageType__c1_PR_rrcReconfiguration + && c1->choice.rrcReconfiguration + && c1->choice.rrcReconfiguration->criticalExtensions.present + == NR_RRCReconfiguration__criticalExtensions_PR_rrcReconfiguration + && c1->choice.rrcReconfiguration->criticalExtensions.choice.rrcReconfiguration) { + reconf_ies = c1->choice.rrcReconfiguration->criticalExtensions.choice.rrcReconfiguration; + } + + if (reconf_ies && reconf_ies->nonCriticalExtension && reconf_ies->nonCriticalExtension->masterCellGroup) { + const OCTET_STRING_t *mcg = reconf_ies->nonCriticalExtension->masterCellGroup; + + /* decode and XER print CellGroupConfig */ + dump_cgc(mcg->buf, mcg->size); + } else { + LOG_W(NR_RRC, "No masterCellGroup found in RRCReconfiguration nonCriticalExtension\n"); + } +} + byte_array_t do_RRCReconfiguration(const nr_rrc_reconfig_param_t *params) { byte_array_t msg = {.buf = NULL, .len = 0}; @@ -651,6 +670,7 @@ byte_array_t do_RRCReconfiguration(const nr_rrc_reconfig_param_t *params) if (LOG_DEBUGFLAG(DEBUG_ASN1)) { xer_fprint(stdout, &asn_DEF_NR_DL_DCCH_Message, (void *)&dl_dcch_msg); + dump_mcg(&dl_dcch_msg); } int val = uper_encode_to_new_buffer(&asn_DEF_NR_DL_DCCH_Message, NULL, &dl_dcch_msg, (void **)&msg.buf); diff --git a/openair2/RRC/NR/MESSAGES/asn1_msg.h b/openair2/RRC/NR/MESSAGES/asn1_msg.h index a763f854bd..2e955f8cff 100644 --- a/openair2/RRC/NR/MESSAGES/asn1_msg.h +++ b/openair2/RRC/NR/MESSAGES/asn1_msg.h @@ -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 @@ byte_array_t doRRCReconfiguration_from_HandoverCommand(byte_array_t handoverComm 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__ */ diff --git a/openair2/RRC/NR/nr_rrc_defs.h b/openair2/RRC/NR/nr_rrc_defs.h index 4d24a771c0..46e1ef3384 100644 --- a/openair2/RRC/NR/nr_rrc_defs.h +++ b/openair2/RRC/NR/nr_rrc_defs.h @@ -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 */ diff --git a/openair2/RRC/NR/nr_rrc_proto.h b/openair2/RRC/NR/nr_rrc_proto.h index a36a0778c6..1db6a4d236 100644 --- a/openair2/RRC/NR/nr_rrc_proto.h +++ b/openair2/RRC/NR/nr_rrc_proto.h @@ -100,9 +100,9 @@ int rrc_gNB_generate_pcch_msg(sctp_assoc_t assoc_id, const NR_SIB1_t *sib, uint3 /* UE Management Procedures */ -void rrc_gNB_generate_UeContextSetupRequest(const gNB_RRC_INST *rrc, - rrc_gNB_ue_context_t *const ue_context_pP, - const e1ap_bearer_setup_resp_t *resp); +void rrc_f1_ue_context_setup_from_e1_response(const gNB_RRC_INST *rrc, + rrc_gNB_ue_context_t *const ue_context_pP, + const e1ap_bearer_setup_resp_t *resp); void rrc_gNB_generate_UeContextModificationRequest(const gNB_RRC_INST *rrc, rrc_gNB_ue_context_t *const ue_context_pP, @@ -124,4 +124,14 @@ void activate_srb(gNB_RRC_UE_t *UE, int srb_id); void e1_notify_pdcp_status(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, const ngap_drb_status_t *drb_status); void init_delayed_action(delayed_action_state_t *delayed_action); +/** @brief Prepare and send F1AP UE Context Setup Request for a target DU + * @param rrc RRC instance + * @param ue UE context + * @param du Target DU container containing cell and setup information + * @param ho_prep_info Optional handover preparation information (NULL if not provided) */ +void rrc_f1_ue_context_setup_for_target_du(const gNB_RRC_INST *rrc, + gNB_RRC_UE_t *ue, + const nr_rrc_du_container_t *du, + const byte_array_t *ho_prep_info); + #endif diff --git a/openair2/RRC/NR/rrc_gNB.c b/openair2/RRC/NR/rrc_gNB.c index a2cc04b948..6b914c27e9 100644 --- a/openair2/RRC/NR/rrc_gNB.c +++ b/openair2/RRC/NR/rrc_gNB.c @@ -527,6 +527,51 @@ void activate_srb(gNB_RRC_UE_t *UE, int srb_id) freeSRBlist(list); } +/** @brief Fill common fields of F1AP UE Context Setup Request + * This helper function initializes common fields that are shared between + * different UE context setup scenarios (serving DU, target DU). + * @param ue UE context + * @param du DU container for measurement timing configuration + * @param gNB_DU_ue_id Optional gNB-DU UE ID to include in the request (NULL if not provided) + * @return F1AP UE Context Setup Request */ +static f1ap_ue_context_setup_req_t rrc_fill_f1_ue_context_setup(gNB_RRC_UE_t *ue, + const nr_rrc_du_container_t *du, + const uint32_t *gNB_DU_ue_id) +{ + f1ap_ue_context_setup_req_t req = { + .gNB_CU_ue_id = ue->rrc_ue_id, + .servCellIndex = 0, + }; + + /* UE Aggregate Maximum Bit Rate Uplink is C-ifDRBSetup: 1 Gbps */ + req.gnb_du_ue_agg_mbr_ul = malloc_or_fail(sizeof(*req.gnb_du_ue_agg_mbr_ul)); + *req.gnb_du_ue_agg_mbr_ul = 1000000000; /* bps */ + + /* Measurement timing configuration */ + req.cu_to_du_rrc_info.meas_timing_config = get_meas_timing_config(du->mtc, ue->measConfig); + + /* UE capabilities */ + if (ue->ue_cap_buffer.len > 0) { + req.cu_to_du_rrc_info.ue_cap = malloc_or_fail(sizeof(byte_array_t)); + *req.cu_to_du_rrc_info.ue_cap = copy_byte_array(ue->ue_cap_buffer); + } + + /* gNB-DU UE ID */ + if (gNB_DU_ue_id) { + req.gNB_DU_ue_id = malloc_or_fail(sizeof(*req.gNB_DU_ue_id)); + *req.gNB_DU_ue_id = *gNB_DU_ue_id; + } + + /* PLMN and cell ID from DU setup request */ + const f1ap_served_cell_info_t *cell_info = &du->setup_req->cell[0].info; + req.plmn.mcc = cell_info->plmn.mcc; + req.plmn.mnc = cell_info->plmn.mnc; + req.plmn.mnc_digit_length = cell_info->plmn.mnc_digit_length; + req.nr_cellid = cell_info->nr_cellid; + + return req; +} + //----------------------------------------------------------------------------- static void rrc_gNB_generate_RRCSetup(instance_t instance, rnti_t rnti, @@ -814,7 +859,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, @@ -871,10 +916,25 @@ 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) +/* Forward declaration */ +static void cuup_notify_reestablishment(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; @@ -885,15 +945,43 @@ 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); + /* Per TS 38.331 5.3.5.6.3, re-establish SRB2 PDCP when sending the first + * RRCReconfiguration after reestablishment */ + if (is_reestablishment) { + if (ue_p->Srb[SRB2].Active) { + nr_pdcp_entity_security_keys_and_algos_t security_parameters = {0}; + security_parameters.ciphering_algorithm = ue_p->ciphering_algorithm; + security_parameters.integrity_algorithm = ue_p->integrity_algorithm; + nr_derive_key(RRC_ENC_ALG, ue_p->ciphering_algorithm, ue_p->kgnb, security_parameters.ciphering_key); + nr_derive_key(RRC_INT_ALG, ue_p->integrity_algorithm, ue_p->kgnb, security_parameters.integrity_key); + nr_pdcp_reestablishment(ue_p->rrc_ue_id, SRB2, true, &security_parameters); + } + /* Per TS 38.331 5.3.5.6.5, re-establish PDCP for all DRBs when sending the first + * RRCReconfiguration after RRC connection re-establishment (over E1 to CU-UP) */ + cuup_notify_reestablishment(rrc, ue_p); + } + + 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); @@ -980,6 +1068,10 @@ static void rrc_gNB_send_f1_drb_release_request(gNB_RRC_INST *rrc, gNB_RRC_UE_t req.drbs_rel_len = n_drb_to_release; memcpy(req.drbs_rel, drb_to_release, n_drb_to_release * sizeof(int)); + // Request CellGroupConfig from DU in the response + req.gNB_DU_Configuration_Query = calloc_or_fail(1, sizeof(*req.gNB_DU_Configuration_Query)); + *req.gNB_DU_Configuration_Query = true; + /* send UE Context Modification to DU without attaching any RRC container */ rrc->mac_rrc.ue_context_modification_request(ue_data.du_assoc_id, &req); LOG_I(NR_RRC, "UE %d: send F1 UE Context Modification Request with DRB release (%d DRBs)\n", ue_p->rrc_ue_id, req.drbs_rel_len); @@ -1090,12 +1182,13 @@ static void cuup_notify_reestablishment(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue_p) free_e1ap_context_mod_request(&req); } -/** - * @brief RRCReestablishment message - * Direction: Network to UE - */ +/** @brief Generate and send RRCReestablishment message to UE + * This function implements the gNB side of the RRC Connection Re-establishment procedure + * as specified in 3GPP TS 38.331 clause 5.3.7.4. + * @param ue_context_pP UE context pointer + * @param old_rnti Previous RNTI of the UE before re-establishment + * @param du DU container containing cell information for key derivation */ static void rrc_gNB_generate_RRCReestablishment(rrc_gNB_ue_context_t *ue_context_pP, - const uint8_t *masterCellGroup_from_DU, const rnti_t old_rnti, const nr_rrc_du_container_t *du) { @@ -1120,7 +1213,9 @@ static void rrc_gNB_generate_RRCReestablishment(rrc_gNB_ue_context_t *ue_context LOG_A(NR_RRC, "Send RRCReestablishment [%d bytes] to RNTI %04x\n", size, ue_p->rnti); /* Ciphering and Integrity according to TS 33.501 */ - nr_pdcp_entity_security_keys_and_algos_t security_parameters; + nr_pdcp_entity_security_keys_and_algos_t security_parameters = {0}; + DevAssert(ue_p->ciphering_algorithm >= NR_CipheringAlgorithm_nea0 && ue_p->ciphering_algorithm <= NR_CipheringAlgorithm_nea2); + DevAssert(ue_p->integrity_algorithm >= NR_IntegrityProtAlgorithm_nia0 && ue_p->integrity_algorithm <= NR_IntegrityProtAlgorithm_nia2); /* Derive the keys from kgnb */ nr_derive_key(RRC_ENC_ALG, ue_p->ciphering_algorithm, ue_p->kgnb, security_parameters.ciphering_key); nr_derive_key(RRC_INT_ALG, ue_p->integrity_algorithm, ue_p->kgnb, security_parameters.integrity_key); @@ -1137,16 +1232,14 @@ static void rrc_gNB_generate_RRCReestablishment(rrc_gNB_ue_context_t *ue_context security_parameters.integrity_algorithm = ue_p->integrity_algorithm; security_parameters.ciphering_algorithm = 0; - /* SRBs */ - for (int srb_id = 1; srb_id < NR_NUM_SRB; srb_id++) { - if (ue_p->Srb[srb_id].Active) - nr_pdcp_config_set_security(ue_p->rrc_ue_id, srb_id, true, &security_parameters); - } - /* Re-establish PDCP for SRB1, according to 5.3.7.4 of 3GPP TS 38.331 */ - nr_pdcp_reestablishment(ue_p->rrc_ue_id, - 1, - true, - &security_parameters); + /* Re-establish PDCP for SRB1, according to 5.3.7.4 of 3GPP TS 38.331, + * and configure security via set_security internally. + * Note: SRB2 is NOT re-established here. Per TS 38.331 5.3.5.6.3, SRB2 + * should be re-established in the first RRCReconfiguration after + * RRCReestablishmentComplete */ + ue_p->Srb[SRB1].Active = 1; + nr_pdcp_reestablishment(ue_p->rrc_ue_id, SRB1, true, &security_parameters); + /* F1AP DL RRC Message Transfer */ 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); @@ -1172,98 +1265,49 @@ static void rrc_gNB_generate_RRCReestablishment(rrc_gNB_ue_context_t *ue_context } /// @brief Function tha processes RRCReestablishmentComplete message sent by the UE, after RRCReestasblishment request. -static void rrc_gNB_process_RRCReestablishmentComplete(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue_p, const uint8_t xid) +static void rrc_gNB_process_RRCReestablishmentComplete(gNB_RRC_INST *rrc, + gNB_RRC_UE_t *ue_p, + const uint8_t xid, + const sctp_assoc_t assoc_id) { 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; + 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); - NR_CellGroupConfig_t *cellGroupConfig = calloc(1, sizeof(NR_CellGroupConfig_t)); + bool different_du_detected = !ue_p->f1_ue_context_active; + if (different_du_detected) { + /** Handle UE access on a different DU than the original one + * Per 38.401 8.7: "If the UE accessed from a gNB-DU other than the original + * one, the gNB-CU should trigger the UE Context Setup procedure". */ + /* UE is accessing a different DU - trigger UE Context Setup per TS 38.401 §8.7 */ + nr_rrc_du_container_t *du = get_du_by_assoc_id(rrc, assoc_id); + if (du == NULL) { + LOG_E(NR_RRC, "UE %d: cannot find DU for assoc_id %d\n", ue_p->rrc_ue_id, assoc_id); + return; + } + rrc_f1_ue_context_setup_for_target_du(rrc, ue_p, du, NULL); + } else { + // 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 + 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, + }; - 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)); + // Request CellGroupConfig from DU in the response + ue_context_modif_req.gNB_DU_Configuration_Query = calloc_or_fail(1, sizeof(*ue_context_modif_req.gNB_DU_Configuration_Query)); + *ue_context_modif_req.gNB_DU_Configuration_Query = true; - /* - * 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); + /** 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); + free_ue_context_mod_req(&ue_context_modif_req); } - - /* 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) - */ - int srb_id = 2; - if (ue_p->Srb[srb_id].Active) { - nr_pdcp_entity_security_keys_and_algos_t security_parameters; - security_parameters.ciphering_algorithm = ue_p->ciphering_algorithm; - security_parameters.integrity_algorithm = ue_p->integrity_algorithm; - nr_derive_key(RRC_ENC_ALG, ue_p->ciphering_algorithm, ue_p->kgnb, security_parameters.ciphering_key); - nr_derive_key(RRC_INT_ALG, ue_p->integrity_algorithm, ue_p->kgnb, security_parameters.integrity_key); - - nr_pdcp_reestablishment(ue_p->rrc_ue_id, srb_id, true, &security_parameters); - } - /* 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); - - 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(¶ms); - 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) @@ -1272,16 +1316,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(¶ms); if (msg.len <= 0) { LOG_E(NR_RRC, "UE %d: Failed to generate RRCReconfiguration\n", ue_p->rrc_ue_id); @@ -1294,6 +1329,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, @@ -1351,19 +1399,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); @@ -1422,11 +1463,12 @@ static void rrc_handle_RRCReestablishmentRequest(gNB_RRC_INST *rrc, const char *scause = get_reestab_cause(req->reestablishmentCause); const long physCellId = req->ue_Identity.physCellId; long ngap_cause = NGAP_CAUSE_RADIO_NETWORK_UNSPECIFIED; /* cause in case of NGAP release req */ + const rnti_t old_rnti = req->ue_Identity.c_RNTI; rrc_gNB_ue_context_t *ue_context_p = NULL; LOG_I(NR_RRC, - "Reestablishment RNTI %04x req C-RNTI %04lx physCellId %ld cause %s\n", + "Reestablishment RNTI %04x req C-RNTI %04x physCellId %ld cause %s\n", msg->crnti, - req->ue_Identity.c_RNTI, + old_rnti, physCellId, scause); @@ -1436,10 +1478,9 @@ static void rrc_handle_RRCReestablishmentRequest(gNB_RRC_INST *rrc, return; } - // 3GPP TS 38.321 version 15.13.0 Section 7.1 Table 7.1-1: RNTI values - if (req->ue_Identity.c_RNTI < 0x1 || req->ue_Identity.c_RNTI > 0xffef) { - /* c_RNTI range error should not happen */ - LOG_E(NR_RRC, "NR_RRCReestablishmentRequest c_RNTI %04lx range error, fallback to RRC setup\n", req->ue_Identity.c_RNTI); + // Validate C-RNTI range (3GPP TS 38.321 version 15.13.0 Section 7.1 Table 7.1-1) + if (old_rnti < 0x1 || old_rnti > 0xffef) { + LOG_E(NR_RRC, "NR_RRCReestablishmentRequest c_RNTI %04x range error, fallback to RRC setup\n", old_rnti); goto fallback_rrc_setup; } @@ -1460,20 +1501,33 @@ static void rrc_handle_RRCReestablishmentRequest(gNB_RRC_INST *rrc, goto fallback_rrc_setup; } - rnti_t old_rnti = req->ue_Identity.c_RNTI; + // Try to find UE context by RNTI and assoc_id (normal case: same DU) ue_context_p = rrc_gNB_get_ue_context_by_rnti(rrc, assoc_id, old_rnti); if (ue_context_p == NULL) { - ue_context_p = rrc_gNB_get_ue_context_source_cell(rrc, physCellId, old_rnti); + // Fallback 1: Try to find UE by RNTI only (re-establishment on different DU scenario) + ue_context_p = rrc_gNB_get_ue_context_by_rnti_any_du(rrc, old_rnti); if (ue_context_p == NULL) { - LOG_E(NR_RRC, "NR_RRCReestablishmentRequest without UE context, fallback to RRC setup\n"); - goto fallback_rrc_setup; + // Fallback 2: Try to find UE by source cell (handover scenario) + ue_context_p = rrc_gNB_get_ue_context_source_cell(rrc, physCellId, old_rnti); + if (ue_context_p == NULL) { + LOG_E(NR_RRC, "NR_RRCReestablishmentRequest without UE context, fallback to RRC setup\n"); + goto fallback_rrc_setup; + } } } gNB_RRC_UE_t *UE = &ue_context_p->ue_context; + if (!UE->as_security_active) { + /* no active security context, need to restart entire connection */ + LOG_E(NR_RRC, "UE requested Reestablishment without activated AS security\n"); + ngap_cause = NGAP_CAUSE_RADIO_NETWORK_RELEASE_DUE_TO_NGRAN_GENERATED_REASON; + goto fallback_rrc_setup; + } + /* should check phys cell ID to identify the correct cell */ const f1ap_served_cell_info_t *cell_info = &du->setup_req->cell[0].info; const f1ap_served_cell_info_t *previous_cell_info = get_cell_information_by_phycellId(physCellId); + f1_ue_data_t ue_data = cu_get_f1_ue_data(UE->rrc_ue_id); nr_ho_source_cu_t *source_ctx = UE->ho_context ? UE->ho_context->source : NULL; DevAssert(!source_ctx || source_ctx->du->setup_req->num_cells_available == 1); @@ -1494,35 +1548,68 @@ 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 */ - f1_ue_data_t ue_data = cu_get_f1_ue_data(UE->rrc_ue_id); ue_data.du_assoc_id = source_ctx->du->assoc_id; bool success = cu_update_f1_ue_data(UE->rrc_ue_id, &ue_data); DevAssert(success); nr_rrc_finalize_ho(UE); } else if (physCellId != cell_info->nr_pci) { - /* UE was moving from previous cell so quickly that RRCReestablishment for previous cell was received in this cell */ - LOG_I(NR_RRC, - "RRC Reestablishment Request from different physCellId (%ld) than current physCellId (%d), fallback to RRC setup\n", - physCellId, - cell_info->nr_pci); - ngap_cause = NGAP_CAUSE_RADIO_NETWORK_RELEASE_DUE_TO_NGRAN_GENERATED_REASON; - /* 38.401 8.7: "If the UE accessed from a gNB-DU other than the original - * one, the gNB-CU should trigger the UE Context Setup procedure". Let's - * assume that the old DU will trigger a release request, also freeing the - * ongoing context at the CU. Hence, create new below */ - goto fallback_rrc_setup; - } + /* Check if this is a different DU scenario or "too fast movement" scenario */ + if (assoc_id != ue_data.du_assoc_id) { + /* Different DU scenario - physCellId differs because UE is re-establishing on a different DU. + * Re-establishment is allowed, UE Context Setup will be triggered at RRCReestablishmentComplete. */ + // Store original du_assoc_id and du_ue_id for immediate release + const sctp_assoc_t old_du_assoc_id = ue_data.du_assoc_id; + const uint32_t old_du_ue_id = ue_data.secondary_ue; + LOG_I(NR_RRC, + "UE %d: Re-establishment on different DU (physCellId %ld from old cell != %d from new DU, du_assoc_id %d -> %d)\n", + UE->rrc_ue_id, + physCellId, + cell_info->nr_pci, + ue_data.du_assoc_id, + assoc_id); - if (!UE->as_security_active) { - /* no active security context, need to restart entire connection */ - LOG_E(NR_RRC, "UE requested Reestablishment without activated AS security\n"); - ngap_cause = NGAP_CAUSE_RADIO_NETWORK_RELEASE_DUE_TO_NGRAN_GENERATED_REASON; - goto fallback_rrc_setup; + // Update to new DU + ue_data.du_assoc_id = assoc_id; + + /* Release old DU context immediately per 3GPP TS 38.473 §8.3.3.2: + * "Interactions with UE Context Setup procedure: + * The UE Context Release procedure may be performed before the UE Context Setup + * procedure to release an existing UE-associated logical F1-connection and related + * resources in the gNB-DU" */ + DevAssert(old_du_ue_id != 0); + RETURN_IF_INVALID_ASSOC_ID(old_du_assoc_id); + f1ap_ue_context_rel_cmd_t cmd = { + .gNB_CU_ue_id = UE->rrc_ue_id, + .gNB_DU_ue_id = old_du_ue_id, + .cause = F1AP_CAUSE_RADIO_NETWORK, + .cause_value = F1AP_CauseRadioNetwork_normal_release, + }; + rrc->mac_rrc.ue_context_release_command(old_du_assoc_id, &cmd); + LOG_I(NR_RRC, + "UE %d: Re-establishment on different DU - releasing old DU immediately (assoc_id %d, DU UE ID %u)\n", + UE->rrc_ue_id, + old_du_assoc_id, + old_du_ue_id); + + // on a new DU: we will have to send f1_ue_context again + UE->f1_ue_context_active = false; + + // Update f1_ue_data after release + bool success = cu_update_f1_ue_data(UE->rrc_ue_id, &ue_data); + DevAssert(success); + } else { + /* Same DU but different physCellId - "too fast movement" scenario: + * UE was moving from previous cell so quickly that RRCReestablishment for previous cell was received in this cell */ + LOG_I(NR_RRC, + "RRC Reestablishment Request from different physCellId (%ld) than current physCellId (%d), fallback to RRC setup\n", + physCellId, + cell_info->nr_pci); + ngap_cause = NGAP_CAUSE_RADIO_NETWORK_RELEASE_DUE_TO_NGRAN_GENERATED_REASON; + goto fallback_rrc_setup; + } } /* TODO: start timer in ITTI and drop UE if it does not come back */ @@ -1530,12 +1617,11 @@ static void rrc_handle_RRCReestablishmentRequest(gNB_RRC_INST *rrc, // update with new RNTI, and update secondary UE association UE->rnti = msg->crnti; UE->nr_cellid = msg->nr_cellid; - f1_ue_data_t ue_data = cu_get_f1_ue_data(UE->rrc_ue_id); ue_data.secondary_ue = msg->gNB_DU_ue_id; bool success = cu_update_f1_ue_data(UE->rrc_ue_id, &ue_data); DevAssert(success); - rrc_gNB_generate_RRCReestablishment(ue_context_p, msg->du2cu_rrc_container, old_rnti, du); + rrc_gNB_generate_RRCReestablishment(ue_context_p, old_rnti, du); return; fallback_rrc_setup: @@ -1726,17 +1812,21 @@ static void rrc_gNB_process_MeasurementReport(gNB_RRC_INST *rrc, gNB_RRC_UE_t *U LOG_E(NR_RRC, "Incoming Report Type: %d is not supported! \n", report_config->choice.reportConfigNR->reportType.present); } -static int handle_rrcReestablishmentComplete(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, const NR_RRCReestablishmentComplete_t *cplt) +static void handle_rrcReestablishmentComplete(gNB_RRC_INST *rrc, + gNB_RRC_UE_t *UE, + const uint32_t gNB_DU_ue_id, + const NR_RRCReestablishmentComplete_t *cplt, + const sctp_assoc_t assoc_id) { NR_RRCReestablishmentComplete__criticalExtensions_PR p = cplt->criticalExtensions.present; if (p != NR_RRCReestablishmentComplete__criticalExtensions_PR_rrcReestablishmentComplete) { LOG_E(NR_RRC, "UE %d: expected presence of rrcReestablishmentComplete, but message has %d\n", UE->rrc_ue_id, p); - return -1; + return; } - rrc_gNB_process_RRCReestablishmentComplete(rrc, UE, cplt->rrc_TransactionIdentifier); + + rrc_gNB_process_RRCReestablishmentComplete(rrc, UE, cplt->rrc_TransactionIdentifier, assoc_id); UE->ue_reestablishment_counter++; - return 0; } /** @@ -2042,7 +2132,7 @@ static void rrc_gNB_generate_UECapabilityEnquiry(gNB_RRC_INST *rrc, gNB_RRC_UE_t nr_rrc_transfer_protected_rrc_message(rrc, ue, DL_SCH_LCID_DCCH, msg_id, buffer, size); } -static int rrc_gNB_decode_dcch(gNB_RRC_INST *rrc, const f1ap_ul_rrc_message_t *msg) +static int rrc_gNB_decode_dcch(gNB_RRC_INST *rrc, const f1ap_ul_rrc_message_t *msg, const sctp_assoc_t assoc_id) { /* we look up by CU UE ID! Do NOT change back to RNTI! */ rrc_gNB_ue_context_t *ue_context_p = rrc_gNB_get_ue_context(rrc, msg->gNB_CU_ue_id); @@ -2147,7 +2237,8 @@ static int rrc_gNB_decode_dcch(gNB_RRC_INST *rrc, const f1ap_ul_rrc_message_t *m case NR_UL_DCCH_MessageType__c1_PR_rrcReestablishmentComplete: LOG_UE_UL_EVENT(UE, "Received RRCReestablishmentComplete\n"); - handle_rrcReestablishmentComplete(rrc, UE, ul_dcch_msg->message.choice.c1->choice.rrcReestablishmentComplete); + const NR_RRCReestablishmentComplete_t *rc = ul_dcch_msg->message.choice.c1->choice.rrcReestablishmentComplete; + handle_rrcReestablishmentComplete(rrc, UE, msg->gNB_DU_ue_id, rc, assoc_id); break; default: @@ -2383,6 +2474,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); @@ -2395,31 +2541,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; @@ -2440,7 +2571,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 @@ -2498,6 +2631,16 @@ static void rrc_CU_process_ue_context_release_request(MessageDef *msg_p, sctp_as // if we receive the release request from the target DU (regardless if // successful), we assume it is "genuine" and ask the AMF to release nr_rrc_finalize_ho(UE); + } else { + f1_ue_data_t ue_data = cu_get_f1_ue_data(UE->rrc_ue_id); + if (ue_data.du_assoc_id != assoc_id) { + LOG_W(NR_RRC, + "UE context release request from unexpected DU with assoc_id %d for rrc_ue_id %d with du_assoc_id %d\n", + assoc_id, + UE->rrc_ue_id, + ue_data.du_assoc_id); + return; + } } /* TODO: marshall types correctly */ @@ -2512,7 +2655,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); @@ -2575,22 +2718,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); } @@ -2631,6 +2770,37 @@ static void rrc_CU_process_ue_modification_required(MessageDef *msg_p, instance_ return; } + /* Check if UE is accessing a different DU than its current one */ + 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); + + /** Refuse UE Context Modification Required from any DU if: + * UE Context Setup Request was sent but Setup Response hasn't arrived yet. + * This includes the case where UE is re-establishing on a different DU. The CU has + * already triggered UE Context Setup on the new DU (per TS 38.401 §8.7), and Setup Response + * is pending. Modification Required from any DU during this transition should be refused + * to avoid conflicts with the ongoing Setup procedure. + * + * Note: according to TS 38.401 §8.7 "it is assumed that the UE accessed the original gNB-DU + * where the UE context is available for that UE, and either steps 9-10 or steps 9’-10’ + * may be executed or both could be skipped." + * By design, CU-CP will trigger UE Context Setup Modification Request and DU does not send + * Modification Required during re-establishment. */ + if (!UE->f1_ue_context_active) { + LOG_W(NR_RRC, + "UE %d: UE Context Modification Required received while UE Context Setup is pending (CU UE ID %d), refusing\n", + UE->rrc_ue_id, + required->gNB_CU_ue_id); + 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_RADIO_NETWORK, + .cause_value = F1AP_CauseRadioNetwork_interaction_with_other_procedure, + }; + rrc->mac_rrc.ue_context_modification_refuse(msg_p->ittiMsgHeader.originInstance, &refuse); + return; + } + if (required->du_to_cu_rrc_information && required->du_to_cu_rrc_information->cellGroupConfig) { gNB_RRC_UE_t *UE = &ue_context_p->ue_context; LOG_I(RRC, @@ -2638,31 +2808,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) @@ -2813,7 +2964,7 @@ void rrc_gNB_process_e1_bearer_context_setup_resp(e1ap_bearer_setup_resp_t *resp } if (!UE->f1_ue_context_active) - rrc_gNB_generate_UeContextSetupRequest(rrc, ue_context_p, resp); + rrc_f1_ue_context_setup_from_e1_response(rrc, ue_context_p, resp); else rrc_gNB_generate_UeContextModificationRequest(rrc, ue_context_p, resp, 0, NULL); } @@ -3096,7 +3247,7 @@ void *rrc_gnb_task(void *args_p) { /* Messages from PDCP */ /* From DU -> CU */ case F1AP_UL_RRC_MESSAGE: - rrc_gNB_decode_dcch(RC.nrrrc[instance], &F1AP_UL_RRC_MESSAGE(msg_p)); + rrc_gNB_decode_dcch(RC.nrrrc[instance], &F1AP_UL_RRC_MESSAGE(msg_p), msg_p->ittiMsgHeader.originInstance); free_ul_rrc_message_transfer(&F1AP_UL_RRC_MESSAGE(msg_p)); break; @@ -3395,25 +3546,147 @@ int rrc_gNB_generate_pcch_msg(sctp_assoc_t assoc_id, const NR_SIB1_t *sib1, uint /* F1AP UE Context Management Procedures */ -//----------------------------------------------------------------------------- -void rrc_gNB_generate_UeContextSetupRequest(const gNB_RRC_INST *rrc, - rrc_gNB_ue_context_t *const ue_context_pP, - const e1ap_bearer_setup_resp_t *resp) -//----------------------------------------------------------------------------- +/** @brief Fill DRB to Be Setup List for F1 UE Context Setup Request + * Returns number of DRBs filled, 0 if none */ +static int rrc_fill_f1_drb_to_setup(const gNB_RRC_INST *rrc, const gNB_RRC_UE_t *ue, f1ap_drb_to_setup_t drbs[MAX_DRBS_PER_UE]) +{ + int nb_drb = 0; + + FOR_EACH_SEQ_ARR(drb_t *, rrc_drb, &ue->drbs) { + DevAssert(nb_drb < MAX_DRBS_PER_UE); + f1ap_drb_to_setup_t *drb = &drbs[nb_drb]; + nr_pdcp_configuration_t *pdcp = &rrc_drb->pdcp_config; + nb_drb++; + /* fetch an existing PDU session for this DRB */ + rrc_pdu_session_param_t *pdu = find_pduSession_from_drbId((gNB_RRC_UE_t *)ue, rrc_drb->drb_id); + AssertFatal(pdu != NULL, "no PDU session for DRB ID %d\n", rrc_drb->drb_id); + + drb->id = rrc_drb->drb_id; + + drb->qos_choice = F1AP_QOS_CHOICE_NR; + drb->nr.nssai = pdu->param.nssai; + drb->nr.flows_len = 1; + drb->nr.flows = calloc_or_fail(1, sizeof(*drb->nr.flows)); + + /* QoS flow associated with this DRB: use first QoS flow */ + AssertFatal(seq_arr_size(&pdu->param.qos) == 1, "only 1 Qos flow supported\n"); + nr_rrc_qos_t *qos_param = (nr_rrc_qos_t *)seq_arr_at(&pdu->param.qos, 0); + DevAssert(qos_param->qos.qfi > 0); + drb->nr.flows[0].qfi = qos_param->qos.qfi; + drb->nr.flows[0].param = get_qos_char_from_qos_flow_param(&qos_param->qos); + /* the DRB QoS parameters: reuse the ones from the first flow */ + drb->nr.drb_qos = drb->nr.flows[0].param; + + memcpy(&drb->up_ul_tnl[0].tl_address, &rrc_drb->cuup_tunnel_config.addr.buffer, sizeof(uint8_t) * 4); + drb->up_ul_tnl[0].teid = rrc_drb->cuup_tunnel_config.teid; + drb->up_ul_tnl_len = 1; + + drb->rlc_mode = rrc->configuration.um_on_default_drb ? F1AP_RLC_MODE_UM_BIDIR : F1AP_RLC_MODE_AM; + DevAssert(pdcp->drb.sn_size == 18 || pdcp->drb.sn_size == 12); + drb->dl_pdcp_sn_len = malloc_or_fail(sizeof(*drb->dl_pdcp_sn_len)); + *drb->dl_pdcp_sn_len = pdcp->drb.sn_size == 18 ? F1AP_PDCP_SN_18B : F1AP_PDCP_SN_12B; + drb->ul_pdcp_sn_len = malloc_or_fail(sizeof(*drb->ul_pdcp_sn_len)); + *drb->ul_pdcp_sn_len = pdcp->drb.sn_size == 18 ? F1AP_PDCP_SN_18B : F1AP_PDCP_SN_12B; + } + return nb_drb; +} + +/** @brief Prepare and send F1AP UE Context Setup Request for a target DU + * This function prepares and sends an F1AP UE Context Setup Request for + * a target DU in handover/mobility or reestablishment scenarios. + * It configures: + * - SRB1 and SRB2 for signaling + * - All existing DRBs from the UE context + * - Measurement configuration updated for the target DU's cell + * - PLMN and cell information from the target DU + * - UE aggregate maximum bit rate + * - Handover preparation information (if provided) + * @param rrc RRC instance + * @param ue UE context + * @param du Target DU container containing cell and setup information + * @param ho_prep_info Optional handover preparation information */ +void rrc_f1_ue_context_setup_for_target_du(const gNB_RRC_INST *rrc, + gNB_RRC_UE_t *ue, + const nr_rrc_du_container_t *du, + const byte_array_t *ho_prep_info) +{ + DevAssert(du != NULL); + /* Handle two scenarios: + * 1. Handover (ho_prep_info provided): Prepare and copy handover preparation information + * for the target DU. The target DU will assign a new gNB-DU UE ID in the response. + * 2. Reestablishment on different DU (ho_prep_info NULL): The UE is accessing a different + * DU than the original one. Use secondary_ue (the new DU's RNTI) as gNB-DU UE ID because + * the UE context already exists in MAC layer from when RRCReestablishmentRequest was + * received on the new DU. The MAC layer will find the existing UE context using this RNTI. */ + byte_array_t *hpi = NULL; + uint32_t du_ue_id = 0; + uint32_t *opt_du_ue_id = NULL; + if (ho_prep_info) { + /* Handover scenario: copy handover preparation information */ + hpi = malloc_or_fail(sizeof(*hpi)); + *hpi = copy_byte_array(*ho_prep_info); + } else { + /* Reestablishment scenario: use secondary_ue from existing 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); + du_ue_id = ue_data.secondary_ue; + opt_du_ue_id = &du_ue_id; + } + LOG_I(NR_RRC, "Triggering UE Context Setup for UE %d on DU %d\n", ue->rrc_ue_id, du->assoc_id); + int nb_srb = 2; + f1ap_srb_to_setup_t *srbs = calloc_or_fail(nb_srb, sizeof(*srbs)); + f1ap_drb_to_setup_t *drbs = calloc_or_fail(MAX_DRBS_PER_UE, sizeof(*drbs)); + + /* Fill DRBs */ + int nb_drb = rrc_fill_f1_drb_to_setup(rrc, ue, drbs); + + /* Prepare SRBs */ + srbs[0].id = SRB1; + srbs[1].id = SRB2; + + /* Update measurement config for target DU */ + free_MeasConfig(ue->measConfig); + ue->measConfig = nr_rrc_get_measconfig(rrc, du->setup_req->cell[0].info.nr_cellid); + byte_array_t *meas_config = calloc_or_fail(1, sizeof(*meas_config)); + meas_config->buf = calloc_or_fail(1, NR_RRC_BUF_SIZE); + meas_config->len = do_NR_MeasConfig(ue->measConfig, meas_config->buf, NR_RRC_BUF_SIZE); + /* Fill common fields */ + f1ap_ue_context_setup_req_t req = rrc_fill_f1_ue_context_setup(ue, du, opt_du_ue_id); + /* Fill target DU specific fields */ + req.srbs_len = 2; + req.srbs = srbs; + req.drbs_len = nb_drb; + req.drbs = drbs; + req.cu_to_du_rrc_info.meas_config = meas_config; + req.cu_to_du_rrc_info.ho_prep_info = hpi; + + RETURN_IF_INVALID_ASSOC_ID(du->assoc_id); + rrc->mac_rrc.ue_context_setup_request(du->assoc_id, &req); + free_ue_context_setup_req(&req); +} + +/** @brief Prepare and send F1AP UE Context Setup Request to serving DU after E1AP bearer setup + * This function is called when the CU-CP receives an E1AP Bearer Context Setup Response + * and the UE context is not yet active on the F1 interface. It prepares a complete F1AP + * UE Context Setup Request containing: + * - SRB configuration (SRB2) + * - DRB configuration derived from the E1AP bearer setup response + * - UE capabilities and measurement timing configuration + * - PLMN and cell information from the serving DU + * The request is sent to the serving DU. + * @param rrc RRC instance + * @param ue_context_pP UE context + * @param resp E1AP bearer setup response containing PDU session and DRB information */ +void rrc_f1_ue_context_setup_from_e1_response(const gNB_RRC_INST *rrc, + rrc_gNB_ue_context_t *const ue_context_pP, + const e1ap_bearer_setup_resp_t *resp) { gNB_RRC_UE_t *ue_p = &ue_context_pP->ue_context; AssertFatal(!ue_p->f1_ue_context_active, "logic error: ue context already active\n"); AssertFatal(!NODE_IS_DU(rrc->node_type), "illegal node type DU!\n"); - f1ap_cu_to_du_rrc_info_t cu2du = {0}; - if (ue_p->ue_cap_buffer.len > 0) { - cu2du.ue_cap = malloc_or_fail(sizeof(byte_array_t)); - *cu2du.ue_cap = copy_byte_array(ue_p->ue_cap_buffer); - } - nr_rrc_du_container_t *du = get_du_for_ue((gNB_RRC_INST *)rrc, ue_p->rrc_ue_id); - cu2du.meas_timing_config = get_meas_timing_config(du->mtc, ue_p->measConfig); int nb_srb = 1; f1ap_srb_to_setup_t *srbs = calloc_or_fail(nb_srb, sizeof(*srbs)); @@ -3424,29 +3697,18 @@ void rrc_gNB_generate_UeContextSetupRequest(const gNB_RRC_INST *rrc, f1ap_drb_to_setup_t *drbs = calloc_or_fail(32, sizeof(*drbs)); // maximum DRB can be 32 int n_drbs = fill_drb_to_be_setup_from_e1_resp(rrc, ue_p, resp->pduSession, resp->numPDUSessions, drbs); - /* gNB-DU UE Aggregate Maximum Bit Rate Uplink is C- ifDRBSetup */ - uint64_t *agg_mbr = malloc_or_fail(sizeof(*agg_mbr)); - *agg_mbr = 1000000000 /* bps */; - + /* Get gNB DU UE ID */ 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); - uint32_t *du_ue_id = malloc_or_fail(sizeof(*du_ue_id)); - *du_ue_id = ue_data.secondary_ue; - f1ap_ue_context_setup_req_t ue_context_setup_req = { - .gNB_CU_ue_id = ue_p->rrc_ue_id, - .gNB_DU_ue_id = du_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 = rrc->nr_cellid, - .servCellIndex = 0, /* TODO: correct value? */ - .srbs_len = nb_srb, - .srbs = srbs, - .drbs_len = n_drbs, - .drbs = drbs, - .cu_to_du_rrc_info = cu2du, - .gnb_du_ue_agg_mbr_ul = agg_mbr, - }; + const uint32_t du_ue_id = ue_data.secondary_ue; + + /* Fill common fields */ + f1ap_ue_context_setup_req_t ue_context_setup_req = rrc_fill_f1_ue_context_setup(ue_p, du, &du_ue_id); + /* Fill bearers information for serving DU */ + ue_context_setup_req.srbs_len = nb_srb; + ue_context_setup_req.srbs = srbs; + ue_context_setup_req.drbs_len = n_drbs; + ue_context_setup_req.drbs = drbs; rrc->mac_rrc.ue_context_setup_request(ue_data.du_assoc_id, &ue_context_setup_req); free_ue_context_setup_req(&ue_context_setup_req); @@ -3489,6 +3751,10 @@ void rrc_gNB_generate_UeContextModificationRequest(const gNB_RRC_INST *rrc, .drbs_rel = (f1ap_drb_to_release_t *)rel_drbs, .cu_to_du_rrc_info = cu2du, }; + + // Request CellGroupConfig from DU in the response + ue_context_modif_req.gNB_DU_Configuration_Query = calloc_or_fail(1, sizeof(*ue_context_modif_req.gNB_DU_Configuration_Query)); + *ue_context_modif_req.gNB_DU_Configuration_Query = true; rrc->mac_rrc.ue_context_modification_request(ue_data.du_assoc_id, &ue_context_modif_req); // avoid attempt to release rel_drbs ue_context_modif_req.drbs_rel_len = 0; diff --git a/openair2/RRC/NR/rrc_gNB_mobility.c b/openair2/RRC/NR/rrc_gNB_mobility.c index 1fc274db5b..a2230c1146 100644 --- a/openair2/RRC/NR/rrc_gNB_mobility.c +++ b/openair2/RRC/NR/rrc_gNB_mobility.c @@ -91,52 +91,6 @@ void nr_rrc_apply_target_context(gNB_RRC_UE_t *UE) UE->nr_cellid = target_ctx->du->setup_req->cell[0].info.nr_cellid; } -/** @brief Fill DRB to Be Setup List in F1 UE Context Setup Request (optional list) - * @return 0 if list is empty, list size otherwise */ -static int fill_drb_to_be_setup(const gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue, f1ap_drb_to_setup_t drbs[MAX_DRBS_PER_UE]) -{ - int nb_drb = 0; - - FOR_EACH_SEQ_ARR(drb_t *, rrc_drb, &ue->drbs) { - DevAssert(nb_drb < MAX_DRBS_PER_UE); - f1ap_drb_to_setup_t *drb = &drbs[nb_drb]; - nr_pdcp_configuration_t *pdcp = &rrc_drb->pdcp_config; - nb_drb++; - /* fetch an existing PDU session for this DRB */ - rrc_pdu_session_param_t *pdu = find_pduSession_from_drbId(ue, rrc_drb->drb_id); - AssertFatal(pdu != NULL, "no PDU session for DRB ID %d\n", rrc_drb->drb_id); - - drb->id = rrc_drb->drb_id; - - drb->qos_choice = F1AP_QOS_CHOICE_NR; - drb->nr.nssai = pdu->param.nssai; - drb->nr.flows_len = 1; - drb->nr.flows = calloc_or_fail(1, sizeof(*drb->nr.flows)); - - // Find the QoS flow associated with this DRB - // Since we don't have QFI mapping in the new structure, we'll use the first QoS flow - AssertFatal(seq_arr_size(&pdu->param.qos) == 1, "only 1 Qos flow supported\n"); - nr_rrc_qos_t *qos_param = (nr_rrc_qos_t *)seq_arr_at(&pdu->param.qos, 0); - DevAssert(qos_param->qos.qfi > 0); - drb->nr.flows[0].qfi = qos_param->qos.qfi; - drb->nr.flows[0].param = get_qos_char_from_qos_flow_param(&qos_param->qos); - /* the DRB QoS parameters: we just reuse the ones from the first flow */ - drb->nr.drb_qos = drb->nr.flows[0].param; - - memcpy(&drb->up_ul_tnl[0].tl_address, &rrc_drb->cuup_tunnel_config.addr.buffer, sizeof(uint8_t) * 4); - drb->up_ul_tnl[0].teid = rrc_drb->cuup_tunnel_config.teid; - drb->up_ul_tnl_len = 1; - - drb->rlc_mode = rrc->configuration.um_on_default_drb ? F1AP_RLC_MODE_UM_BIDIR : F1AP_RLC_MODE_AM; - DevAssert(pdcp->drb.sn_size == 18 || pdcp->drb.sn_size == 12); - drb->dl_pdcp_sn_len = malloc_or_fail(sizeof(*drb->dl_pdcp_sn_len)); - *drb->dl_pdcp_sn_len = pdcp->drb.sn_size == 18 ? F1AP_PDCP_SN_18B : F1AP_PDCP_SN_12B; - drb->ul_pdcp_sn_len = malloc_or_fail(sizeof(*drb->ul_pdcp_sn_len)); - *drb->ul_pdcp_sn_len = pdcp->drb.sn_size == 18 ? F1AP_PDCP_SN_18B : F1AP_PDCP_SN_12B; - } - 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 @@ -194,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, @@ -206,48 +160,7 @@ static void nr_initiate_handover(const gNB_RRC_INST *rrc, target_du->assoc_id, target_du->setup_req->cell[0].info.nr_pci); - f1ap_drb_to_setup_t *drbs = calloc_or_fail(MAX_DRBS_PER_UE, sizeof(*drbs)); - int nb_drb = fill_drb_to_be_setup(rrc, ue, drbs); - - int nb_srb = 2; - f1ap_srb_to_setup_t *srbs = calloc_or_fail(nb_srb, sizeof(*srbs)); - srbs[0].id = 1; - srbs[1].id = 2; - - free_MeasConfig(ue->measConfig); - ue->measConfig = nr_rrc_get_measconfig(rrc, target_du->setup_req->cell[0].info.nr_cellid); - byte_array_t *meas_config = calloc_or_fail(1, sizeof(*meas_config)); - meas_config->buf = calloc_or_fail(1, NR_RRC_BUF_SIZE); - meas_config->len = do_NR_MeasConfig(ue->measConfig, meas_config->buf, NR_RRC_BUF_SIZE); - - byte_array_t *meas_timing_config = get_meas_timing_config(target_du->mtc, ue->measConfig); - - byte_array_t *hpi = malloc_or_fail(sizeof(*hpi)); - *hpi = copy_byte_array(*ho_prep_info); - - uint64_t *ue_agg_mbr = malloc_or_fail(sizeof(*ue_agg_mbr)); - *ue_agg_mbr = 1000000000 /*bps*/; - - 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_req_t ue_context_setup_req = { - .gNB_CU_ue_id = ue->rrc_ue_id, - .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, - .servCellIndex = 0, // TODO: correct value? - .srbs_len = nb_srb, - .srbs = srbs, - .drbs_len = nb_drb, - .drbs = drbs, - .cu_to_du_rrc_info.ho_prep_info = hpi, - .cu_to_du_rrc_info.meas_config = meas_config, - .cu_to_du_rrc_info.meas_timing_config = meas_timing_config, - .gnb_du_ue_agg_mbr_ul = ue_agg_mbr, - }; - rrc->mac_rrc.ue_context_setup_request(target_du->assoc_id, &ue_context_setup_req); - free_ue_context_setup_req(&ue_context_setup_req); + rrc_f1_ue_context_setup_for_target_du(rrc, ue, target_du, ho_prep_info); } typedef struct deliver_ue_ctxt_modification_data_t { @@ -392,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; } diff --git a/openair2/RRC/NR/rrc_gNB_mobility.h b/openair2/RRC/NR/rrc_gNB_mobility.h index 54691c2edd..456730060e 100644 --- a/openair2/RRC/NR/rrc_gNB_mobility.h +++ b/openair2/RRC/NR/rrc_gNB_mobility.h @@ -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; diff --git a/openair2/RRC/NR/rrc_gNB_nsa.c b/openair2/RRC/NR/rrc_gNB_nsa.c index 8c24b8aedd..88f06338d7 100644 --- a/openair2/RRC/NR/rrc_gNB_nsa.c +++ b/openair2/RRC/NR/rrc_gNB_nsa.c @@ -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); diff --git a/openair2/RRC/NR_UE/rrc_UE.c b/openair2/RRC/NR_UE/rrc_UE.c index 9e77dcfa8f..4860e0b63e 100644 --- a/openair2/RRC/NR_UE/rrc_UE.c +++ b/openair2/RRC/NR_UE/rrc_UE.c @@ -2617,7 +2617,7 @@ static int nr_rrc_ue_decode_dcch(NR_UE_RRC_INST_t *rrc, set_DRB_status(rrc, i, RB_ESTABLISHED); nr_mac_rrc_message_t rrc_msg = {0}; rrc_msg.payload_type = NR_MAC_RRC_RESUME_RB; - rrc_msg.payload.resume_rb.is_srb = true; + rrc_msg.payload.resume_rb.is_srb = false; rrc_msg.payload.resume_rb.rb_id = i; nr_rrc_send_msg_to_mac(rrc, &rrc_msg); }