Merge remote-tracking branch 'origin/issue-837' into integration_2026_w05 (!3732)

Implement TS 38.473 transparency for CellGroupConfig forwarding and handle re-establishment on a different DU

This MR implements the following fixes that are relevant in RRC
procedures (e.g. re-establishment, reconfiguration):

1. Transparent CellGroupConfig forwarding

This MR implements TS 38.473 transparency requirements by ensuring that
the CellGroupConfig IE is transparently forwarded from the gNB-DU to the
UE without any re-encoding at the gNB-CU. The CU stores and forwards the
encoded bytes transparently, while the DU is responsible for all
encoding and modifications (e.g., reestablishRLC flags).  Motivation

Motivation: The 3GPP TS 38.473 specification mandates that the
CellGroupConfig IE shall be transparently signaled to the UE.
Previously, the CU was re-encoding CellGroupConfig depending on the
scenario, instead of retrieving it from the DU, violating the
transparency requirement. CU may decode for internal use but must not
re-encode for forwarding.  Solution

Solution: Core Principle: The CU stores the encoded CellGroupConfig
bytes received from the DU and forwards them directly to the UE without
any re-encoding. The DU handles all encoding and modifications.

Key Changes:

  1. Transparent Forwarding in RRC Reconfiguration
     - Modified build_RRCReconfiguration_IEs() to use pre-encoded bytes
       directly
     - Changed parameter from cell_group_config to cgc (encoded byte
       array with buffer and size)
  2. Encoded Bytes Storage
     - Added byte_array_t mcg field to RRC UE structure to store encoded
       CellGroupConfig
     - Added store_cgc() helper function to save encoded cell group in
       the RRC UE Context at gNB (from F1AP messages)
  3. DU Encoding Responsibilities
     - Add gNB-DU Configuration Query IE support to trigger cgc encoding
       to request CellGroupConfig from DU
     - DU encodes CellGroupConfig with proper reestablishRLC flags
       during re-establishment
     - DU removes SRB1 from rlc-BearerToAddModList during
       re-establishment (SRB1 re-establishment is done before
       RRCReconfiguration)
  4. New functions
     - Add rrc_detect_reestablishment() function to detect
       re-establishment during UE Context Modification
     - Extracted rrc_gNB_first_reconfiguration_after_reestablishment()
       function to handle first reconfiguration after re-establishment
     - Added dump_cgc() and dump_mcg() utility functions for debugging
       purposes
  5. Re-establishment Flow
     - rrc_gNB_process_RRCReestablishmentComplete() requests
       CellGroupConfig from DU via gNB_DU_Configuration_Query in UE
       Context Modification Request
     - DU detects re-establishment and add reestablishRLC IEs
     - CU stores encoded CGC from the UE Context Modification Response
       and forwards it transparently in subsequent RRC Reconfiguration

Testing

Manual Testing:

  - RRC re-establishment procedure completes successfully
  - RRC Reconfiguration after re-establishment includes all DU-provided information

Issues fixed

Closes issue #837

2. Trigger UE Context Setup fallback upon reestablishment on DU
   different from the original one

As per 38.401 §8.7: if the UE accesses a DU different from the original
one during re-establishment, the CU must trigger UE Context Setup toward
the new DU. This refers to steps 9-10 and steps 9’-10’. Either one or
the other can be executed during re-establishment.

e.g. if this happens during handover, it shall trigger a UE Context
Setup fallback on the CU.

This MR implements the required behavior by:

- Adding a new helper function rrc_handle_f1_ho_reestab_on_target() to
  handle F1 handover reestablishment on target DU. The function triggers
  UE Context Setup procedure when the UE reestablishes on the target DU
  during an ongoing F1 handover.
- Calling this function when detecting that the UE is reestablishing on
  the target DU during handover, i.e.: (1) from
  handle_rrcReestablishmentComplete(), as per 38.401 §8.7, NOTE on steps
  9-10: CU gets UL RRC MESSAGE TRANSFER from a DU different than the
  original (2) from rrc_CU_process_ue_modification_required, as per
  38.401 §8.7, NOTE on steps 9'-10': CU gets a UE CONTEXT MODIFICATION
  REQUIRED from a DU different from the original one.

Also:

- add helper rrc_fill_f1_ue_context_setup and adopt it in handover
- move DRB list filler fill_drb_to_be_setup into rrc_gNB.c and rename it
  rrc_fill_f1_drb_to_setup for consistency

See also TS 38.401 Figure 8.7-1 "RRC connection reestablishment
procedure" for reference

3. DU: Handle missing old UE context in DL RRC Message Transfer

During F1 handover reestablishment, the CU may forward a DL RRC Message
Transfer to the target DU with old_gNB_DU_ue_id included, even though
the target DU does not have the old UE context yet.

The code previously used AssertFatal when the old UE context was not
found, causing a crash: "CU claims we should know UE %04x, but we don't"

According to TS 38.473, the gNB-DU shall release the old gNB-DU UE F1AP ID only:

> if the gNB-DU identifies the UE-associated logical F1-connection If
> there is no matching connection, there is nothing to release.

Fix:

- Check if old UE context exists before accessing it
- If old UE does not exist, log error, clean up any stale F1 UE data,
  and return early (do not deliver RRC message since reestablishment
  can't be completed)

The goal is to address a recurrent issue in the F1 pipeline, see
jenkins-oai.eurecom.fr/job/RAN-SA-Handover-CN5G/514:

    Assertion (oldUE) failed!
    In dl_rrc_message_transfer() /oai-ran/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c:956
    CU claims we should know UE 35ce, but we don't
This commit is contained in:
Robert Schmidt
2026-01-29 17:20:43 +01:00
21 changed files with 974 additions and 510 deletions
@@ -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;
})
};
@@ -240,7 +240,7 @@
<class>Custom_Command</class>
<desc>UE (1) connected to DU-PCI 1?</desc>
<node>localhost</node>
<command>echo ci fetch_du_by_ue_id 1 | ncat 192.168.71.150 9090 | grep "3585"</command>
<command>echo ci fetch_du_by_ue_id | ncat 192.168.71.150 9090 | grep "3585"</command>
</testCase>
<testCase>
@@ -271,7 +271,7 @@
<class>Custom_Command</class>
<desc>UE (1) connected to DU-PCI 0?</desc>
<node>localhost</node>
<command>echo ci fetch_du_by_ue_id 1 | ncat 192.168.71.150 9090 | grep "3584"</command>
<command>echo ci fetch_du_by_ue_id | ncat 192.168.71.150 9090 | grep "3584"</command>
</testCase>
<testCase>
@@ -302,7 +302,7 @@
<class>Custom_Command</class>
<desc>UE (1) connected to DU-PCI 1?</desc>
<node>localhost</node>
<command>echo ci fetch_du_by_ue_id 1 | ncat 192.168.71.150 9090 | grep "3585"</command>
<command>echo ci fetch_du_by_ue_id | ncat 192.168.71.150 9090 | grep "3585"</command>
</testCase>
<testCase>
@@ -120,7 +120,7 @@
<class>Custom_Command</class>
<desc>UE (1) connected to DU-PCI 1?</desc>
<node>localhost</node>
<command>echo ci fetch_du_by_ue_id 1 | ncat 192.168.71.180 9090 | grep "1234"</command>
<command>echo ci fetch_du_by_ue_id | ncat 192.168.71.180 9090 | grep "1234"</command>
</testCase>
<testCase>
@@ -181,7 +181,7 @@
<class>Custom_Command</class>
<desc>UE (1) connected to DU-PCI 0?</desc>
<node>localhost</node>
<command>echo ci fetch_du_by_ue_id 1 | ncat 192.168.71.150 9090 | grep "3584"</command>
<command>echo ci fetch_du_by_ue_id | ncat 192.168.71.150 9090 | grep "3584"</command>
</testCase>
<testCase>
@@ -218,7 +218,7 @@
<class>Custom_Command</class>
<desc>UE (1) connected to DU-PCI 1?</desc>
<node>localhost</node>
<command>echo ci fetch_du_by_ue_id 1 | ncat 192.168.71.180 9090 | grep "1234"</command>
<command>echo ci fetch_du_by_ue_id | ncat 192.168.71.180 9090 | grep "1234"</command>
</testCase>
<testCase>
@@ -59,9 +59,9 @@
<testCase>
<class>Custom_Command</class>
<desc>Set attenuator to 0 for DU0 and 17 for DU1</desc>
<desc>Set attenuator to 0 for DU0 and 22 for DU1</desc>
<node>rocket</node>
<command>python3 %%workspace%%/ci-scripts/attenuatorctl.py -s 1,2 0 -s 3,4 25</command>
<command>python3 %%workspace%%/ci-scripts/attenuatorctl.py -s 1,2 0 -s 3,4 22</command>
</testCase>
<testCase>
@@ -76,8 +76,8 @@
<class>Deploy_Object</class>
<desc>Deploy gNB-DU0 (TDD/Band77/20MHz/B210) in a container</desc>
<yaml_path>ci-scripts/yaml_files/5g_sa_f1_b210_ho</yaml_path>
<services>oai-du0</services>
<node>groot</node>
<services>oai-du0</services>
</testCase>
<testCase>
@@ -94,26 +94,11 @@
<id>raspix</id>
</testCase>
<testCase>
<class>Ping</class>
<desc>Ping: 100pings in 20sec</desc>
<id>raspix</id>
<svr_id>oc-cn5g-00103-ho</svr_id>
<ping_args>-c 100 -i 0.2</ping_args>
<ping_packetloss_threshold>5</ping_packetloss_threshold>
</testCase>
<testCase>
<class>IdleSleep</class>
<desc>Sleep</desc>
<idle_sleep_time_in_sec>5</idle_sleep_time_in_sec>
</testCase>
<testCase>
<class>Custom_Command</class>
<desc>Attenuate DU0 by 15 and remove the attenuation on DU1 to trigger Handover</desc>
<node>rocket</node>
<command>python3 %%workspace%%/ci-scripts/attenuatorctl.py -r 1,2 15 -r 3,4 0 -d 5</command>
<desc>UE connected to DU0?</desc>
<node>groot</node>
<command>echo ci fetch_du_by_ue_id | ncat 127.0.0.1 9090 | grep "3584"</command>
</testCase>
<testCase>
@@ -122,21 +107,58 @@
<id>raspix</id>
<svr_id>oc-cn5g-00103-ho</svr_id>
<ping_args>-c 50 -i 0.2</ping_args>
<ping_packetloss_threshold>5</ping_packetloss_threshold>
<ping_packetloss_threshold>30</ping_packetloss_threshold>
</testCase>
<testCase>
<class>Custom_Command</class>
<desc>Set attenuation 5 on DU 0 and remove attenuation on DU 1 to test Reestablishment on a different DU (DU 1)</desc>
<node>rocket</node>
<command>python3 %%workspace%%/ci-scripts/attenuatorctl.py -r 1,2 5 -r 3,4 0 -d 5</command>
</testCase>
<testCase>
<class>Custom_Command</class>
<desc>Trigger Reestablishment (on DU 0)</desc>
<node>groot</node>
<command>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'</command>
</testCase>
<testCase>
<class>Ping</class>
<desc>Ping: 50pings in 10sec</desc>
<id>raspix</id>
<svr_id>oc-cn5g-00103-ho</svr_id>
<ping_args>-c 50 -i 0.2</ping_args>
<ping_packetloss_threshold>30</ping_packetloss_threshold>
</testCase>
<testCase>
<class>Custom_Command</class>
<desc>UE connected to DU1?</desc>
<node>groot</node>
<command>echo ci fetch_du_by_ue_id 1 | ncat 127.0.0.1 9090 | grep "3585"</command>
<command>echo ci fetch_du_by_ue_id | ncat 127.0.0.1 9090 | grep "3585"</command>
</testCase>
<testCase>
<class>Custom_Command</class>
<desc>Attenuate DU1 by 17dB and remove attenuation on DU0 to trigger Handover</desc>
<desc>Verify Reestablishment (on CU)</desc>
<node>groot</node>
<command>echo ci get_reestab_count | ncat 127.0.0.1 9090 | grep -E 'UE RNTI [0-9a-f]{4} reestab 1'</command>
</testCase>
<testCase>
<class>Custom_Command</class>
<desc>Remove attenuation on DU 0 and set attenuation on DU 1 to 5 to test Reestablishment on a different DU (DU 0)</desc>
<node>rocket</node>
<command>python3 %%workspace%%/ci-scripts/attenuatorctl.py -r 1,2 0 -r 3,4 25 -d 5</command>
<command>python3 %%workspace%%/ci-scripts/attenuatorctl.py -r 1,2 0 -r 3,4 5 -d 5</command>
</testCase>
<testCase>
<class>Custom_Command</class>
<desc>Trigger Reestablishment (on DU 1)</desc>
<node>rocket</node>
<command>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'</command>
</testCase>
<testCase>
@@ -145,21 +167,28 @@
<id>raspix</id>
<svr_id>oc-cn5g-00103-ho</svr_id>
<ping_args>-c 50 -i 0.2</ping_args>
<ping_packetloss_threshold>5</ping_packetloss_threshold>
<ping_packetloss_threshold>30</ping_packetloss_threshold>
</testCase>
<testCase>
<class>Custom_Command</class>
<desc>UE connected to DU0?</desc>
<node>groot</node>
<command>echo ci fetch_du_by_ue_id 1 | ncat 127.0.0.1 9090 | grep "3584"</command>
<command>echo ci fetch_du_by_ue_id | ncat 127.0.0.1 9090 | grep "3584"</command>
</testCase>
<testCase>
<class>Custom_Command</class>
<desc>Attenuate DU0 by 15 and remove the attenuation on DU1 to trigger Handover</desc>
<desc>Verify Reestablishment (on CU)</desc>
<node>groot</node>
<command>echo ci get_reestab_count | ncat 127.0.0.1 9090 | grep -E 'UE RNTI [0-9a-f]{4} reestab 2'</command>
</testCase>
<testCase>
<class>Custom_Command</class>
<desc>Attenuate DU0 by 22 and remove the attenuation on DU1 to trigger Handover</desc>
<node>rocket</node>
<command>python3 %%workspace%%/ci-scripts/attenuatorctl.py -r 1,2 15 -r 3,4 0 -d 5</command>
<command>python3 %%workspace%%/ci-scripts/attenuatorctl.py -r 1,2 22 -r 3,4 0 -d 5</command>
</testCase>
<testCase>
@@ -168,14 +197,60 @@
<id>raspix</id>
<svr_id>oc-cn5g-00103-ho</svr_id>
<ping_args>-c 50 -i 0.2</ping_args>
<ping_packetloss_threshold>5</ping_packetloss_threshold>
<ping_packetloss_threshold>30</ping_packetloss_threshold>
</testCase>
<testCase>
<class>Custom_Command</class>
<desc>UE connected to DU1?</desc>
<node>groot</node>
<command>echo ci fetch_du_by_ue_id 1 | ncat 127.0.0.1 9090 | grep "3585"</command>
<command>echo ci fetch_du_by_ue_id | ncat 127.0.0.1 9090 | grep "3585"</command>
</testCase>
<testCase>
<class>Custom_Command</class>
<desc>Attenuate DU1 by 22dB and remove attenuation on DU0 to trigger Handover</desc>
<node>rocket</node>
<command>python3 %%workspace%%/ci-scripts/attenuatorctl.py -r 1,2 0 -r 3,4 22 -d 5</command>
</testCase>
<testCase>
<class>Ping</class>
<desc>Ping: 50pings in 10sec</desc>
<id>raspix</id>
<svr_id>oc-cn5g-00103-ho</svr_id>
<ping_args>-c 50 -i 0.2</ping_args>
<ping_packetloss_threshold>30</ping_packetloss_threshold>
</testCase>
<testCase>
<class>Custom_Command</class>
<desc>UE connected to DU0?</desc>
<node>groot</node>
<command>echo ci fetch_du_by_ue_id | ncat 127.0.0.1 9090 | grep "3584"</command>
</testCase>
<testCase>
<class>Custom_Command</class>
<desc>Attenuate DU0 by 22 and remove the attenuation on DU1 to trigger Handover</desc>
<node>rocket</node>
<command>python3 %%workspace%%/ci-scripts/attenuatorctl.py -r 1,2 22 -r 3,4 0 -d 5</command>
</testCase>
<testCase>
<class>Ping</class>
<desc>Ping: 50pings in 10sec</desc>
<id>raspix</id>
<svr_id>oc-cn5g-00103-ho</svr_id>
<ping_args>-c 50 -i 0.2</ping_args>
<ping_packetloss_threshold>30</ping_packetloss_threshold>
</testCase>
<testCase>
<class>Custom_Command</class>
<desc>UE connected to DU1?</desc>
<node>groot</node>
<command>echo ci fetch_du_by_ue_id | ncat 127.0.0.1 9090 | grep "3585"</command>
</testCase>
<testCase>
@@ -55,7 +55,7 @@
<class>Custom_Command</class>
<desc>Set initial attenuation on target gNB</desc>
<node>rocket</node>
<command>python3 %%workspace%%/ci-scripts/attenuatorctl.py -s 1,2 0 -s 3,4 25</command>
<command>python3 %%workspace%%/ci-scripts/attenuatorctl.py -s 1,2 0 -s 3,4 22</command>
</testCase>
<testCase>
@@ -103,9 +103,9 @@
<testCase>
<class>Custom_Command</class>
<desc>Gradually attenuate gNB-PCI0 (ch1-2=15dB) and gNB-PCI1 (ch3-4=0dB) over 5s to trigger N2 Handover to gNB-PCI1</desc>
<desc>Gradually attenuate gNB-PCI0 (ch1-2=22dB) and gNB-PCI1 (ch3-4=0dB) over 5s to trigger N2 Handover to gNB-PCI1</desc>
<node>rocket</node>
<command>python3 %%workspace%%/ci-scripts/attenuatorctl.py -r 1,2 15 -r 3,4 0 -d 5</command>
<command>python3 %%workspace%%/ci-scripts/attenuatorctl.py -r 1,2 22 -r 3,4 0 -d 5</command>
</testCase>
<testCase>
@@ -121,14 +121,14 @@
<class>Custom_Command</class>
<desc>UE connected to gNB-PCI1?</desc>
<node>rocket</node>
<command>echo ci fetch_du_by_ue_id 1 | ncat 127.0.0.1 9090 | grep "3585"</command>
<command>echo ci fetch_du_by_ue_id | ncat 127.0.0.1 9090 | grep "3585"</command>
</testCase>
<testCase>
<class>Custom_Command</class>
<desc>Gradually attenuate gNB-PCI0 (ch1-2=0dB) and gNB-PCI1 (ch3-4=15dB) over 5s to trigger N2 Handover back to gNB-PCI0</desc>
<desc>Gradually attenuate gNB-PCI0 (ch1-2=0dB) and gNB-PCI1 (ch3-4=22dB) over 5s to trigger N2 Handover back to gNB-PCI0</desc>
<node>rocket</node>
<command>python3 %%workspace%%/ci-scripts/attenuatorctl.py -r 1,2 0 -r 3,4 25 -d 5</command>
<command>python3 %%workspace%%/ci-scripts/attenuatorctl.py -r 1,2 0 -r 3,4 22 -d 5</command>
</testCase>
<testCase>
@@ -144,7 +144,7 @@
<class>Custom_Command</class>
<desc>UE connected to gNB-PCI0?</desc>
<node>groot</node>
<command>echo ci fetch_du_by_ue_id 1 | ncat 127.0.0.1 9090 | grep "3584"</command>
<command>echo ci fetch_du_by_ue_id | ncat 127.0.0.1 9090 | grep "3584"</command>
</testCase>
<testCase>
+8 -1
View File
@@ -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) {
+45 -10
View File
@@ -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()<br />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<br />(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)<br/>Update du_assoc_id, set f1_ue_context_active=false
end
Note over cucp: rrc_gNB_generate_RRCReestablishment()<br/>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<br/>- 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()<br/>Re-establish SRB2 PDCP
Note over cucp: cuup_notify_reestablishment()<br/>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<br/>with gNB_DU_Configuration_Query=true
cucp->>du: F1AP UE Context Modification Req
du->>cucp: F1AP UE Context Modification Resp<br/>(CellGroupConfig with spCellConfig and reestablishRLC flags)
else on a different DU
Note over cucp: Detect different DU (!f1_ue_context_active)<br/>Trigger UE Context Setup on new DU
cucp->>du: F1AP UE Context Setup Request<br/>
du->>cucp: F1AP UE Context Setup Response<br/>(CellGroupConfig with spCellConfig and reestablishRLC flags)
end
Note over cucp: rrc_CU_process_ue_context_setup/modification_response()<br/>Detect re-establishment via rrc_detect_reestablishment()<br/>rrc_gNB_generate_dedicatedRRCReconfiguration()<br/>(with is_reestablishment=true)
cucp->>ue: F1AP DL RRC Msg Transfer (RRC Reconfiguration<br/>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
+3
View File
@@ -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
+20
View File
@@ -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);
}
/**
+1
View File
@@ -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);
+173 -59
View File
@@ -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 */
@@ -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");
+51 -31
View File
@@ -532,7 +532,7 @@ static NR_RRCReconfiguration_IEs_t *build_RRCReconfiguration_IEs(const nr_rrc_re
ie->measConfig = params->meas_config;
/* nonCriticalExtension, RRCReconfiguration-v1530-IEs */
if (params->cell_group_config || params->num_nas_msg) {
if (params->cgc || params->num_nas_msg) {
// Allocate memory for extension IE
ie->nonCriticalExtension = calloc_or_fail(1, sizeof(*ie->nonCriticalExtension));
}
@@ -549,37 +549,13 @@ static NR_RRCReconfiguration_IEs_t *build_RRCReconfiguration_IEs(const nr_rrc_re
}
}
/* masterCellGroup */
if (params->cell_group_config) {
uint8_t temp[4096];
asn_enc_rval_t enc = uper_encode_to_buffer(&asn_DEF_NR_CellGroupConfig, NULL, params->cell_group_config, temp, sizeof(temp));
if (enc.encoded <= 0) {
LOG_E(NR_RRC, "ASN.1 encoding failed for NR_CellGroupConfig (encoded=%ld)\n", enc.encoded);
if (enc.failed_type) {
LOG_E(NR_RRC, "Failed at ASN.1 type: %s\n", enc.failed_type->name);
}
if (enc.structure_ptr) {
LOG_E(NR_RRC, "Failed at structure element: %p\n", enc.structure_ptr);
}
// Print diagnostic information to help debug the issue
LOG_E(NR_RRC, "CellGroupConfig structure that failed to encode:\n");
xer_fprint(stdout, &asn_DEF_NR_CellGroupConfig, (const void *)params->cell_group_config);
ASN_STRUCT_FREE(asn_DEF_NR_RRCReconfiguration_IEs, ie);
return NULL;
}
DevAssert(enc.encoded <= sizeof(temp) * 8); // Encoded data must fit in temp buffer
// Allocate buffer for the encoded data and copy it
// enc.encoded is in bits, convert to bytes
size_t encoded_bytes = (enc.encoded + 7) / 8;
uint8_t *buf = calloc_or_fail(1, encoded_bytes);
memcpy(buf, temp, encoded_bytes);
if (LOG_DEBUGFLAG(DEBUG_ASN1)) {
xer_fprint(stdout, &asn_DEF_NR_CellGroupConfig, (const void *)params->cell_group_config);
}
/* masterCellGroup - Transparent forwarding per TS 38.473 */
if (params->cgc) {
// Copy pre-encoded CellGroupConfig bytes directly without decode/re-encode (TS 38.473 transparency)
// CU receives encoded bytes from DU and forwards to UE without modification
LOG_D(NR_RRC, "Transparent forwarding CellGroupConfig (len=%ld bytes)\n", params->cgc->len);
ie->nonCriticalExtension->masterCellGroup = calloc_or_fail(1, sizeof(*ie->nonCriticalExtension->masterCellGroup));
*ie->nonCriticalExtension->masterCellGroup = (OCTET_STRING_t){.buf = buf, .size = encoded_bytes};
OCTET_STRING_fromBuf(ie->nonCriticalExtension->masterCellGroup, (const char *)params->cgc->buf, params->cgc->len);
}
/* masterKeyUpdate */
@@ -630,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);
+2 -1
View File
@@ -70,7 +70,7 @@ typedef struct {
NR_MeasConfig_t *meas_config;
byte_array_t dedicated_NAS_msg_list[MAX_DRBS_PER_UE];
int num_nas_msg;
NR_CellGroupConfig_t *cell_group_config;
byte_array_t *cgc;
bool masterKeyUpdate;
int nextHopChainingCount;
byte_array_t ue_cap;
@@ -179,5 +179,6 @@ 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__ */
+2 -1
View File
@@ -186,7 +186,8 @@ typedef struct gNB_RRC_UE_s {
NR_UE_MRDC_Capability_t* UE_Capability_MRDC;
int UE_MRDC_Capability_size;
NR_CellGroupConfig_t *masterCellGroup;
// Transparent forwarding of CellGroupConfig
byte_array_t mcg;
NR_RadioBearerConfig_t *rb_config;
/* KgNB as derived from KASME received from EPC */
+13 -3
View File
@@ -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
+521 -255
View File
File diff suppressed because it is too large Load Diff
+4 -91
View File
@@ -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;
}
+1 -1
View File
@@ -50,7 +50,7 @@ typedef struct nr_ho_source_cu {
rnti_t old_rnti;
/// old (source) CellGroupConfig (to send the cellGroupConfig in case of
/// reestablishment)
NR_CellGroupConfig_t *old_cellGroupConfig;
byte_array_t old_cgc;
/// function pointer to announce the handover cancellation, e.g.,
/// reestablishment
ho_cancel_t ho_cancel;
+6 -9
View File
@@ -386,18 +386,15 @@ void rrc_add_nsa_user(gNB_RRC_INST *rrc, x2ap_ENDC_sgnb_addition_req_t *m, sctp_
free_ue_context_setup_req(&req);
}
static NR_RRCReconfiguration_IEs_t *get_default_reconfig(const NR_CellGroupConfig_t *secondaryCellGroup)
static NR_RRCReconfiguration_IEs_t *get_default_reconfig(const byte_array_t cgc)
{
NR_RRCReconfiguration_IEs_t *reconfig = calloc(1, sizeof(NR_RRCReconfiguration_IEs_t));
AssertFatal(reconfig != NULL, "out of memory\n");
AssertFatal(secondaryCellGroup != NULL, "secondaryCellGroup is null\n");
AssertFatal(cgc.buf != NULL, "cgc is null\n");
reconfig->radioBearerConfig = NULL;
char scg_buffer[1024];
asn_enc_rval_t enc_rval = uper_encode_to_buffer(&asn_DEF_NR_CellGroupConfig, NULL, (void *)secondaryCellGroup, scg_buffer, 1024);
AssertFatal(enc_rval.encoded > 0, "ASN1 message encoding failed (%s, %jd)!\n", enc_rval.failed_type->name, enc_rval.encoded);
reconfig->secondaryCellGroup = calloc(1, sizeof(*reconfig->secondaryCellGroup));
OCTET_STRING_fromBuf(reconfig->secondaryCellGroup, (const char *)scg_buffer, (enc_rval.encoded + 7) >> 3);
// Copy the stored CellGroupConfig to the RRCReconfiguration message
reconfig->secondaryCellGroup = calloc_or_fail(1, sizeof(*reconfig->secondaryCellGroup));
OCTET_STRING_fromBuf(reconfig->secondaryCellGroup, (const char *)cgc.buf, cgc.len);
reconfig->measConfig = NULL;
reconfig->lateNonCriticalExtension = NULL;
reconfig->nonCriticalExtension = NULL;
@@ -469,7 +466,7 @@ void rrc_add_nsa_user_resp(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, const f1ap_ue_co
NR_RRCReconfiguration_t *reconfig = calloc(1, sizeof(NR_RRCReconfiguration_t));
reconfig->rrc_TransactionIdentifier = 0;
reconfig->criticalExtensions.present = NR_RRCReconfiguration__criticalExtensions_PR_rrcReconfiguration;
reconfig->criticalExtensions.choice.rrcReconfiguration = get_default_reconfig(UE->masterCellGroup);
reconfig->criticalExtensions.choice.rrcReconfiguration = get_default_reconfig(UE->mcg);
NR_CG_Config_t *CG_Config = generate_CG_Config(reconfig, UE->rb_config);
ASN_STRUCT_FREE(asn_DEF_NR_RRCReconfiguration, reconfig);
+1 -1
View File
@@ -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);
}