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);
}